Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inefficient use of local variables #104

Open
dorianru opened this issue Jun 14, 2017 · 0 comments
Open

Inefficient use of local variables #104

dorianru opened this issue Jun 14, 2017 · 0 comments

Comments

@dorianru
Copy link

dorianru commented Jun 14, 2017

Using large code bases consting mainly of callbacks into C# code, we started getting StackOverflowExceptions. As they could be fixed by increasing the stack size, they were actually caused by using too much stack space, even though we did not make heavy use of recursion.

Further analysis revealed that the generated IL code uses extremely many local variables. In fact it seems that local variables will be created for every function call. MWE:

static void Main(string[] args)
{
	int sum = 0;
	ScriptEngine engine = new ScriptEngine {EnableILAnalysis = true};
	engine.SetGlobalFunction("test", new Action<int>(a => sum += a));
	string code = string.Concat(Enumerable.Repeat("test(42);\n", 1000));
	engine.Execute(code);
	Console.WriteLine(sum);
	Console.ReadKey();
}

When replacing the 1000 with a 1, the code below is generated. My knowledge of IL assembly is highly limited but it seems as though locals 0 and 1 are unused (at least stloc is only called for 2).

.local [0] System.Object
.local [1] System.Int32
.local [2] Jurassic.Library.ObjectInstance
      ldarg      1
      castclass  Jurassic.Compiler.ObjectScope
      callvirt   Jurassic.Library.ObjectInstance get_ScopeObject()/Jurassic.Compiler.ObjectScope
      stloc      V2
      ldloc      V0
      ldloc      V2
      callvirt   System.Object get_InlineCacheKey()/Jurassic.Library.ObjectInstance
      beq        L000
      ldloc      V2
      ldstr      "test"
      ldloca     V1
      ldloca     V0
      callvirt   System.Object InlineGetPropertyValue(System.String, Int32 ByRef, System.Object ByRef)/Jurassic.Library.ObjectInstance
      br         L001
L000: ldloc      V2
      callvirt   System.Object[] get_InlinePropertyValues()/Jurassic.Library.ObjectInstance
      ldloc      V1
      ldelem     System.Object
L001: dup
      brtrue     L002
      ldarg      0
      ldc.i4     6
      ldstr      "test is not defined"
      ldc.i4     1
      ldnull
      ldnull
      newobj     Void .ctor(Jurassic.ScriptEngine, Jurassic.Library.ErrorType, System.String, Int32, System.String, System.String)/Jurassic.JavaScriptException
      throw
L002: ininst     Jurassic.Library.FunctionInstance
      dup
      brtrue     L003
      pop
      ldarg      0
      ldc.i4     2
      ldstr      "'test' is not a function"
      ldc.i4     1
      ldnull
      ldnull
      newobj     Void .ctor(Jurassic.ScriptEngine, Jurassic.Library.ErrorType, System.String, Int32, System.String, System.String)/Jurassic.JavaScriptException
      throw
L003: ldnull
      ldnull
      ldc.i4     1
      ldsfld     Jurassic.Undefined Value/Jurassic.Undefined
      ldc.i4     1
      newarr     System.Object
      dup
      ldc.i4     0
      ldc.i4     42
      box        System.Int32
      stelem     System.Object
      callvirt   System.Object CallWithStackTrace(System.String, System.String, Int32, System.Object, System.Object[])/Jurassic.Library.FunctionInstance
      pop
      ldsfld     Jurassic.Undefined Value/Jurassic.Undefined
      ret

Running the script as is leads to the following code:

.local [0] System.Object
.local [1] System.Int32
.local [2] Jurassic.Library.ObjectInstance
.local [3] System.Object
.local [4] System.Int32
.local [5] Jurassic.Library.ObjectInstance
[...]
.local [2997] System.Object
.local [2998] System.Int32
.local [2999] Jurassic.Library.ObjectInstance
      ldarg      1
      castclass  Jurassic.Compiler.ObjectScope
      callvirt   Jurassic.Library.ObjectInstance get_ScopeObject()/Jurassic.Compiler.ObjectScope
      stloc      V2
      ldloc      V0
      ldloc      V2
      callvirt   System.Object get_InlineCacheKey()/Jurassic.Library.ObjectInstance
      beq        L000
      ldloc      V2
      ldstr      "test"
      ldloca     V1
      ldloca     V0
      callvirt   System.Object InlineGetPropertyValue(System.String, Int32 ByRef, System.Object ByRef)/Jurassic.Library.ObjectInstance
      br         L001
L000: ldloc      V2
      callvirt   System.Object[] get_InlinePropertyValues()/Jurassic.Library.ObjectInstance
      ldloc      V1
      ldelem     System.Object
L001: dup
      brtrue     L002
      ldarg      0
      ldc.i4     6
      ldstr      "test is not defined"
      ldc.i4     1
      ldnull
      ldnull
      newobj     Void .ctor(Jurassic.ScriptEngine, Jurassic.Library.ErrorType, System.String, Int32, System.String, System.String)/Jurassic.JavaScriptException
      throw
L002: ininst     Jurassic.Library.FunctionInstance
      dup
      brtrue     L003
      pop
      ldarg      0
      ldc.i4     2
      ldstr      "'test' is not a function"
      ldc.i4     1
      ldnull
      ldnull
      newobj     Void .ctor(Jurassic.ScriptEngine, Jurassic.Library.ErrorType, System.String, Int32, System.String, System.String)/Jurassic.JavaScriptException
      throw
L003: ldnull
      ldnull
      ldc.i4     1
      ldsfld     Jurassic.Undefined Value/Jurassic.Undefined
      ldc.i4     1
      newarr     System.Object
      dup
      ldc.i4     0
      ldc.i4     42
      box        System.Int32
      stelem     System.Object
      callvirt   System.Object CallWithStackTrace(System.String, System.String, Int32, System.Object, System.Object[])/Jurassic.Library.FunctionInstance
      pop
      [...]
      ldarg      1
      castclass  Jurassic.Compiler.ObjectScope
      callvirt   Jurassic.Library.ObjectInstance get_ScopeObject()/Jurassic.Compiler.ObjectScope
      stloc      V2999
      ldloc      V2997
      ldloc      V2999
      callvirt   System.Object get_InlineCacheKey()/Jurassic.Library.ObjectInstance
      beq        L3996
      ldloc      V2999
      ldstr      "test"
      ldloca     V2998
      ldloca     V2997
      callvirt   System.Object InlineGetPropertyValue(System.String, Int32 ByRef, System.Object ByRef)/Jurassic.Library.ObjectInstance
      br         L3997
L3996: ldloc      V2999
      callvirt   System.Object[] get_InlinePropertyValues()/Jurassic.Library.ObjectInstance
      ldloc      V2998
      ldelem     System.Object
L3997: dup
      brtrue     L3998
      ldarg      0
      ldc.i4     6
      ldstr      "test is not defined"
      ldc.i4     1000
      ldnull
      ldnull
      newobj     Void .ctor(Jurassic.ScriptEngine, Jurassic.Library.ErrorType, System.String, Int32, System.String, System.String)/Jurassic.JavaScriptException
      throw
L3998: ininst     Jurassic.Library.FunctionInstance
      dup
      brtrue     L3999
      pop
      ldarg      0
      ldc.i4     2
      ldstr      "'test' is not a function"
      ldc.i4     1000
      ldnull
      ldnull
      newobj     Void .ctor(Jurassic.ScriptEngine, Jurassic.Library.ErrorType, System.String, Int32, System.String, System.String)/Jurassic.JavaScriptException
      throw
L3999: ldnull
      ldnull
      ldc.i4     1000
      ldsfld     Jurassic.Undefined Value/Jurassic.Undefined
      ldc.i4     1
      newarr     System.Object
      dup
      ldc.i4     0
      ldc.i4     42
      box        System.Int32
      stelem     System.Object
      callvirt   System.Object CallWithStackTrace(System.String, System.String, Int32, System.Object, System.Object[])/Jurassic.Library.FunctionInstance
      pop
      ldsfld     Jurassic.Undefined Value/Jurassic.Undefined
      ret

It seems that Jurassic allocates three local variables for every function call.
The same problem occurs with calls to functions directly defined in Javascript.
This seems unnecessary. Could Jurassic be optimized to reuse these locals?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant