Ծրագրավորման լեզուների կոմպիլյացիայի հերցերն ուսումնասիրելիս ես տևական ժամանակ փնտրում էի մի գրադարան, որը հարավորություն կտար առանց մանրամասնությունների մեջ մտնելու գեներացնել 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