Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

How dynjs handles function

 I traced how dynjs execute javascript last month. Today I try to trace how dynjs handles function.

creating javascript

 I created a script which has a function returns x2 of argument.

function doubleFunction (arg) {
	return arg * 2;
}

 It creates 2 classes. 1st is AnonymousDynScript1 which implements org.dynjs.runtime.Script. It has execute() method and we call this method to execute our script.

public class org.dynjs.gen.AnonymousDynScript1 extends org.dynjs.compiler.BaseScript implements org.dynjs.runtime.Script {
public org.dynjs.gen.AnonymousDynScript1(org.dynjs.parser.Statement...);
Code:
0: aload_0
1: aload_1
2: invokespecial #10 // Method org/dynjs/compiler/BaseScript."":([Lorg/dynjs/parser/Statement;)V
5: return

public void execute(org.dynjs.runtime.DynThreadContext);
Code:
0: bipush 1
2: anewarray #14 // class java/lang/String
5: astore 4
7: aload 4
9: bipush 0
11: ldc #16 // String arg
13: aastore
14: aload_1
15: invokevirtual #22 // Method org/dynjs/runtime/DynThreadContext.getRuntime:()Lorg/dynjs/runtime/DynJS;
18: aload_1
19: bipush 0
21: invokevirtual #26 // Method org/dynjs/runtime/DynThreadContext.retrieve:(I)Lme/qmx/jitescript/CodeBlock;
24: aload 4
26: invokevirtual #32 // Method org/dynjs/runtime/DynJS.compile:(Lme/qmx/jitescript/CodeBlock;[Ljava/lang/String;)Lorg/dynjs/api/Function;
29: astore_3
30: aload_0
31: ldc #34 // String doubleFunction
33: aload_3
34: invokeinterface #40, 3 // InterfaceMethod org/dynjs/api/Scope.define:(Ljava/lang/String;Ljava/lang/Object;)V
39: return
}

 It is hard to read? We can use JD to convert byte codes to source codes. It creates a Function instance by DynJS#compile(CodeBlock, String[]).

 public void execute(DynThreadContext paramDynThreadContext)
  {
    String[] arrayOfString = new String[1];
    arrayOfString[0] = "arg";
    Function localFunction = paramDynThreadContext.getRuntime().compile(paramDynThreadContext.retrieve(0), arrayOfString);
    define("doubleFunction", localFunction);
  }

 Where are codes created from 'return arg * 2'? They are in AnonymousDynFunction2, which is the 2nd class. It has 2 methods and we call call(DynThreadContext, Object[]) method to execute this function.

public class org.dynjs.gen.AnonymousDynFunction2 extends org.dynjs.runtime.DynFunction implements org.dynjs.api.Function {
public org.dynjs.gen.AnonymousDynFunction2();
Code:
0: aload_0
1: invokespecial #10 // Method org/dynjs/runtime/DynFunction."":()V
4: return

public java.lang.Object call(org.dynjs.runtime.DynThreadContext, java.lang.Object);
Code:
0: aload_1
1: aload_0
2: ldc #14 // String arg
4: invokedynamic #25, 0 // InvokeDynamic #0:"dynjs:scope:resolve":(Lorg/dynjs/runtime/DynThreadContext;Lorg/dynjs/api/Scope;Ljava/lang/String;)Ljava/lang/Object;
9: aload_1
10: ldc #27 // String 2
12: invokevirtual #33 // Method org/dynjs/runtime/DynThreadContext.defineDecimalLiteral:(Ljava/lang/String;)Ljava/lang/Number;
15: invokedynamic #37, 0 // InvokeDynamic #0:mul:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
20: areturn

public java.lang.String getArguments();
Code:
0: bipush 1
2: anewarray #41 // class java/lang/String
5: dup
6: bipush 0
8: ldc #14 // String arg
10: aastore
11: areturn
}

 Sadly the JD cannot convert these byte codes so I converted them by hands. dynjs:scope:resolve() and mul() is called by invokedynamic. I don't know how to code it in Java so I code it as normal methods.

public Object call(org.dynjs.runtime.DynThreadContext ctx, java.lang.Object[] args) {
    Object givenArgument = dynjs:scope:resolve(ctx, this, "arg");
    Number number = ctx.defineDecimalLiteral("2");
    return mul(givenArgument, number);
}

Store a function to local variable

 Next, let's try to store a function to variable. Script to test is here.

var doubleFunction = function (arg) {
	return arg * 2;
};
print(doubleFunction(1));

 Definition of function is same to the previous one. So AnonymousDynFunction2 class is almost same but AnonymousDynScript1 class is changed.

public class org.dynjs.gen.AnonymousDynScript1 extends org.dynjs.compiler.BaseScript implements org.dynjs.runtime.Script {
public org.dynjs.gen.AnonymousDynScript1(org.dynjs.parser.Statement...);
Code:
0: aload_0
1: aload_1
2: invokespecial #10 // Method org/dynjs/compiler/BaseScript."":([Lorg/dynjs/parser/Statement;)V
5: return

public void execute(org.dynjs.runtime.DynThreadContext);
Code:
0: bipush 1
2: anewarray #14 // class java/lang/String
5: astore 4
7: aload 4
9: bipush 0
11: ldc #16 // String arg
13: aastore
14: aload_1
15: invokevirtual #22 // Method org/dynjs/runtime/DynThreadContext.getRuntime:()Lorg/dynjs/runtime/DynJS;
18: aload_1
19: bipush 0
21: invokevirtual #26 // Method org/dynjs/runtime/DynThreadContext.retrieve:(I)Lme/qmx/jitescript/CodeBlock;
24: aload 4
26: invokevirtual #32 // Method org/dynjs/runtime/DynJS.compile:(Lme/qmx/jitescript/CodeBlock;[Ljava/lang/String;)Lorg/dynjs/api/Function;
29: astore_3
30: aload_0
31: ldc #34 // String doubleFunction
33: aload_3
34: invokeinterface #40, 3 // InterfaceMethod org/dynjs/api/Scope.define:(Ljava/lang/String;Ljava/lang/Object;)V
39: bipush 1
41: anewarray #42 // class java/lang/Object
44: astore 4
46: aload 4
48: bipush 0
50: aload_1
51: ldc #44 // String 1
53: invokevirtual #48 // Method org/dynjs/runtime/DynThreadContext.defineDecimalLiteral:(Ljava/lang/String;)Ljava/lang/Number;
56: aastore
57: aload_1
58: aload_0
59: ldc #34 // String doubleFunction
61: invokedynamic #59, 0 // InvokeDynamic #0:"dynjs:scope:resolve":(Lorg/dynjs/runtime/DynThreadContext;Lorg/dynjs/api/Scope;Ljava/lang/String;)Ljava/lang/Object;
66: aload_1
67: aload 4
69: invokedynamic #63, 0 // InvokeDynamic #0:"dyn:call":(Lorg/dynjs/api/Function;Lorg/dynjs/runtime/DynThreadContext;[Ljava/lang/Object;)Ljava/lang/Object;
74: dup
75: getstatic #69 // Field java/lang/System.out:Ljava/io/PrintStream;
78: swap
79: invokevirtual #75 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
82: return
}

 We can convert these byte codes into source codes. It's very simple but I cannot explain why invokedynamic is used. I'll try to understand later.

    String[] arrayOfString = new String[1];
    arrayOfString[0] = "arg";
    Function localFunction = paramDynThreadContext.getRuntime().compile(paramDynThreadContext.retrieve(0), arrayOfString);
    define("doubleFunction", localFunction);

    Object[] argument = new Object[] { paramDynThreadContext.defineDecimalLiteral("1") };
    Object function = dynjs:scope:resolve(paramDynThreadContext, this. "doubleFunction");
    Object result = dyn:call(function, ctx, argument);
    System.out.println(result);