Code Monkey home page Code Monkey logo

Comments (10)

paulbartrum avatar paulbartrum commented on June 25, 2024

If you just want to compile some javascript once, and then run it multiple times, you can use the ScriptEngine.Compile() method. If you want something that persists across application restarts, then indeed you need some way to save a DLL. Unfortunately, there is no built-in support for this. There is some hacky code here though: https://github.com/paulbartrum/jurassic/blob/master/Benchmarker/Program.cs (check out SaveDLL method, which is in the commented code at the bottom of the file).

from jurassic.

CPardi avatar CPardi commented on June 25, 2024

I am looking for something that persists across application restarts, so compiling to a dll would be preferred.

Using the code you mentioned above I have managed to save a DLL. Am I right in saying EnableDebugging = true causes IL to be generated as soon as the code is loaded, rather than when used for the first time? Also, what does EnableILAnalysis = true do?

I do have a problem using the generated DLL. What seems to be generated is a number of JavaScriptClass classes, which I assume correspond to each of the definitions in the source JavaScript. What would probably be most helpful is to return a ScriptEngine preloaded with the compiled JavaScript. Would this be possible?

from jurassic.

paulbartrum avatar paulbartrum commented on June 25, 2024

EnableDebugging = true switches to an alternate way of generating the dynamic in-memory assembly. Technically it uses AppDomain.DefineDynamicAssembly rather than the default, which is to use the somewhat faster DynamicMethod type. DynamicMethod doesn't support support DLL serialization, hence the need for EnableDebugging to be true.

EnableILAnalysis = true turns on logging of the generated MSIL/CIL. It is not needed in your scenario.

You can get a populated ScriptEngine by creating a new ScriptEngine and then running the generated class method that corresponds to the global code in your script (JavaScriptClass0.global_?) and passing in your script engine. I'm not sure how else this could possibly work?

from jurassic.

CPardi avatar CPardi commented on June 25, 2024

Now I think about how else it could work, neither am I.

I looked around in JavaScriptClass0 and found the global_ function. It has the following signature:

public static object global_(ScriptEngine scriptEngine, Scope scope, object obj)

Obviously scriptEngine is the engine I want to populate. However, what do scope and obj refer to?

What I have at the moment gives an exception. I am trying to run a simple test where the following:

var test = function() { return "test"; };

is compiled using the ScriptCompiler.Save() in the fork here and the resulting dll is loaded using ScriptCompiler.Load(). I have had a guess at the arguments, so they may well(probably) be incorrect.

Doing this gives me the exception shown below

Unhandled Exception: System.InvalidOperationException: Internal error: no generated method cache available.

Any ideas? From what I can see from peaking in the code is that the Jurassic.Compiler.GeneratedMethod.Save() method is not called and therefore Jurassic.Compiler.GeneratedMethod.generatedMethodCache dictionary is not initialized.

In case its of any use, the decompiled IL looks like this:

using Jurassic;
using Jurassic.Compiler;
using Jurassic.Library;
using System;

public class JavaScriptClass0
{
    public JavaScriptClass0()
    {
    }

    public static object global_(ScriptEngine scriptEngine, Scope scope, object obj)
    {
        object obj1 = null;
        int num = 0;
        ((ObjectScope)scope).ScopeObject.DefineProperty("test", new PropertyDescriptor(Undefined.Value, PropertyAttributes.Writable | PropertyAttributes.Enumerable), false);
        UserDefinedFunction userDefinedFunction = new UserDefinedFunction(scriptEngine.Function.InstancePrototype, "", new string[0], scope, " return \"test\"; ", GeneratedMethod.Load(0L), false);
        userDefinedFunction.SetPropertyValue("displayName", "test", false);
        object obj2 = userDefinedFunction;
        ObjectInstance scopeObject = ((ObjectScope)scope).ScopeObject;
        if (obj1 == scopeObject.InlineCacheKey)
        {
            scopeObject.InlinePropertyValues[num] = obj2;
        }
        else
        {
            scopeObject.InlineSetPropertyValue("test", obj2, false, out num, out obj1);
        }
        return Undefined.Value;
    }
}

from jurassic.

paulbartrum avatar paulbartrum commented on June 25, 2024

Ugh, I told you this was hacky :-/

The scope parameter is used to manage variable scopes and lifetimes -- try calling this method to get a suitable one: Jurassic.Compiler.ObjectScope.CreateRuntimeScope(null, engine.Global, false, false);.

The obj parameter I think represents the value of this... just pass in engine.Global, that should work.

from jurassic.

CPardi avatar CPardi commented on June 25, 2024

I've tried this and the same exception comes up. It seems to me that not all the information in the original ScriptEngine is contained within the generated dll. It seems that the dictionary of function references is not captured in the IL. Would this be right?

from jurassic.

paulbartrum avatar paulbartrum commented on June 25, 2024

It's likely the code is assuming that you can't run any compiled JS code without first compiling it. As a workaround, you could try compiling something prior to running the DLL code e.g. engine.Execute("").

from jurassic.

CPardi avatar CPardi commented on June 25, 2024

I am still getting the same exception. This maybe harder than we first thought.

from jurassic.

CPardi avatar CPardi commented on June 25, 2024

I had a play around and got a little further. It appears that the method cache Jurassic.Compiler.GeneratedMethod.generatedMethodCache is not repopulated by any method outputted in the IL. I am using the code below to create the DLL with the ScriptCompiler.Save() method here. Please ignore the comments, I was playing with Reflection.Emit.

        private static void JurassicCompileTest()
        {
            var sw = new Stopwatch();

            // Create an engine.
            var compiler = new ScriptCompiler();

            // Create a Javscript object using Javscript object initializer.
            sw.Restart();
            compiler.IncludeInput("var test = { 'foo' : 'bar', 'bar':'foo' };");
            compiler.IncludeInput("var helloWorldAndTest = function() { return JSON.stringify(test); };");
            Console.WriteLine(sw.ElapsedMilliseconds / 1000.0);

            compiler.Save("Test.dll");
        }

I've had some success when I try to repopulate manually using the code below which uses the ScriptCompiler.Load() method.

        private static void CompiledJurassicTest()
        {
            var sw = new Stopwatch();

            // Create an engine.

            var compiler = new ScriptCompiler();
            var engine = compiler.Load(
                new Func<ScriptEngine, Scope, object, object>[] { JavaScriptClass0.global_, JavaScriptClass1.global_ },
                new[] { new FunctionDelegate((a, b, c, d, e) => JavaScriptClass2.anonymous(a, b, c, d, e)) });

            engine.SetGlobalFunction("log", new Action<string>(Console.WriteLine));
            Console.WriteLine(engine.CallGlobalFunction("helloWorldAndTest"));

        }

Does this seem a reasonable thing to do? I am thinking of

  • In the Load method using reflection to find all .anonymous methods and add them to the GeneratedMethod.generatedMethodCache and then find the '.global_' methods in each type and run them on the ScriptEngine.

from jurassic.

CPardi avatar CPardi commented on June 25, 2024

Hi, I have managed to get something working by recreating the method cache. Compiling a DLL using .Save() create an assembly with a RestoreScriptEngine() method that can be called to get the saved engine back. Disappointingly, this call appears to take slightly longer than just executing the original script. Running the global_() methods seem to take the majority of the time, I do not know whether it would be possible for that step to be shortened.

from jurassic.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.