Friday, September 20, 2013

GNU/bytecode գրադարանի օգտագործման օրինակ

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