Ծրագրավորման լեզուների կոմպիլյացիայի հերցերն ուսումնասիրելիս ես տևական ժամանակ փնտրում էի մի գրադարան, որը հարավորություն կտար առանց մանրամասնությունների մեջ մտնելու գեներացնել Java վիրտուալ մեքենայի class ֆայլեր։ Առաջին որոնումները բերեցին ASM և BCEL գրադարաններին և Jasmin «Java ասեմբլերին»։ Վերջինս հնարավորություն է տալիս մնեմենիկ հրամաններով հրագրավորել ալգորիթմը, ապա այն «ասեմբլացնել» ու ստանալ վիրտուալ մեքենայի բայթ-կոդ՝ class ֆայլ։
Բայց, տարբեր պատճառներով, այդ գրադարաններից ոչ մեկը ես հարմար չգտա սովորելու համար։ Հետագա որոնումների ընթացքում հանդիպեցի GNU/bytecode գրադարանին, որ Scheme ծրագրավորման լեզվի Kawa իրականացման մաս է կազմում։
Այս գրառման մեջ ես ուզում եմ ֆակտորիալի և ամենամեծ ընդհանուր բաժանարարի հաշվման պարզ օրինակներով ցույց տալ, թե ինչպես կարելի է գեներացնել այդ երկու ալգորիթմները պարունակող class ֆայլը։
Նախ սկսեմ Java տարբերակից.
public class Algorithms { public static int factorial( int n ) { if( n == 1 ) return 1; return n * factorial( n - 1 ); } public static int gcd( int n, int m ) { while( n != m ) if( n > m ) n -= m; else m -= n; return n; } }
Այս դասի նկարագրությունը Algorithms.java
ֆայլի մեջ գրառելուց և javac
կոմպիլյատորով թարգմանելուց հետո ստացվում է Algorithms.class
ֆայլը։ class ֆայլը կարելի է javap
դիզասեմբլերով հետ թարգմանել ու տեսնել թե Java լեզվի կոմպիլյատորն ինչ հրամաններ է գեներացրել։ Այդ բոլոր հրամանների նկարագրությունը կարելի է գտնել Java վիրտուալ մեքենայի նկարագրության մեջ։
Compiled from "Algorithms.java" public class Algorithms { public Algorithms(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static int factorial(int); Code: 0: iload_0 1: iconst_1 2: if_icmpne 7 5: iconst_1 6: ireturn 7: iload_0 8: iload_0 9: iconst_1 10: isub 11: invokestatic #2 // Method factorial:(I)I 14: imul 15: ireturn public static int gcd(int, int); Code: 0: iload_0 1: iload_1 2: if_icmpeq 24 5: iload_0 6: iload_1 7: if_icmple 17 10: iload_0 11: iload_1 12: isub 13: istore_0 14: goto 0 17: iload_1 18: iload_0 19: isub 20: istore_1 21: goto 0 24: iload_0 25: ireturn }
Այս արտածումը տրված է JVM-ի հրամաններ մնեմոնիկ ներկայացմամբ, որոնց բացատրությունները բերված են JVM Specification էջում։
Հիմա ցույց տամ, թե ինչպես եմ gnu.bytecode գրադարանի օգտագործմամբ ստեղծում factorial
և gcd
ալգորիթմները պարունակող class ֆայլը։ Արդեն նշեցի, որ gnu.bytecode գրադարանը Kawa լեզվի իրականացման մաս է։ Չնայած որ կարելի է կոդից առանձնացնել միայն gnu.bytecode-ն և կառուցել առանձին *.jar
ֆայլ, ես կօգտագործեմ հենց kawa-1.13.jar
ֆայլը։
ՈՒրեմն, նախ ներմուծում եմ gnu.bytecode
գրադարանը․ import gnu.bytecode.*;
Հետո սահմանում եմ AlgoEx
դասն իր main
մեթոդով․
public class AlgoEx { public static void main(String[] args) throws Exception {
Հետո ստեղծում եմ ClassType
դասի օբյեկտ, որը ներկայացնում է Java վիրտուալ մեքենայի դասը։ Այնուհետև դասի համար որպես ծնող նշում եմ java.lang.Object
դասը, իսկ որպես տեսանելիության մոդիֆիկատոր նշում եմ public
- Access.PUBLIC
հոստատունի օգնությամբ։
// class `Algorithms' ClassType clo = new ClassType("Algorithms"); clo.setSuper("java.lang.Object"); clo.setModifiers(Access.PUBLIC);
Քանի որ և՛ factroial
, և՛ gcd
մեդոդները սահմանելու եմ public
ու static
մոդիֆիկատորներով, այստեղ սահմանել եմ pubstat
հաստատունը․
final int pubstat = Access.PUBLIC | Access.STATIC;
Հետո clo
դասում ավելացնում եմ նոր մեթոդ՝ "factorial"
անունով և "(I)I"
սիգնատուրայով։ addMethod
մեթոդի վերադարձրած հղումև վերագրում եմ Method
տիպի mfac
փոփոխականին։
// method `factorial' Method mfac = clo.addMethod("factorial", "(I)I", pubstat);
CodeAttr
դասը նախատեսված է բուն կոդի գեներացիայի համար։ Ստորև բերված բլոկում կառուցված է ամբողջ թվի ֆակտորիալը հաշվող ֆունկցիայի կոդի գեներացիան․
CodeAttr code = mfac.startCode(); code.pushScope(); code.emitLoad(code.getArg(0)); code.emitPushInt(1); code.emitIfEq(); code.emitPushInt(1); code.emitReturn(); code.emitElse(); code.emitLoad(code.getArg(0)); code.emitDup(); code.emitPushInt(1); code.emitSub(Type.intType); code.emitInvoke(mfac); code.emitMul(); code.emitReturn(); code.emitFi(); code.popScope();
Նույն կերպ կառուցված է gcd
մեթոդի կոդի գեներացիան։
// method `gcd' Method mgcd = clo.addMethod("gcd", "(II)I", pubstat); Label bw = new Label(code); Label ew = new Label(code); code = mgcd.startCode(); code.pushScope(); bw.define(code); Variable n = code.getArg(0); Variable m = code.getArg(1); code.emitLoad(n); code.emitLoad(m); code.emitGotoIfEq(ew); code.emitLoad(n); code.emitLoad(m); code.emitIfGt(); code.emitLoad(n); code.emitLoad(m); code.emitSub(Type.intType); code.emitStore(n); code.emitElse(); code.emitLoad(m); code.emitLoad(n); code.emitSub(Type.intType); code.emitStore(m); code.emitFi(); code.emitGoto(bw); ew.define(code); code.emitLoad(n); code.emitReturn(); code.popScope();
Եվ վերջում writeToFile
մեթոդով կառուցված դասի պարունակությունը գրվում է class ֆայլի մեջ։
// write class file clo.writeToFile(); } }
Հիմա թարգմանեմ այս ֆայլը Java կոմպիլյատորով և գործարկեմ այն, որպեսզի ստանամ Algorithms.class
ֆայլը։
$ javac -classpath .:kawa-1.13.jar AlgoEx.java $ java -classpath .:kawa-1.13.jar AlgoEx
Այս երկու հրամանների կատարումից հետո գեներացվում է Algorithms.class
ֆայլը։ Կարելի է javap դիզասեմբլերով տեսնել այս ֆայլի պարունակությունը։ Բայց ես կգրեմ մի տեստային ֆայլ, որը կօգտագործի Algwrithms
դասի factorial
և gcd
ֆունկցիաները։
public class Test { public static void main(String[] args) { System.out.println(Algorithms.factorial(10)); System.out.println(Algorithms.gcd(123,31)); } }
Այս դասի թարգմանությունն ու կատարումը ցույց է տալիս, որ gnu.bytecode գրադարանի օգնությամբ կառուցված class ֆայլը ճիշտ է․
$ javac Test.java $ java Test 3628800 1
No comments:
Post a Comment