BY Akash Kava26 Oct 2021 Edit
Yantra Expression Compiler for DotNet

YantraJS is JavaScript built on top of Brand new Expression Compiler which is based on System.Linq.Expressions (referred further as SLE) but with different implementation.

var a = YExpression.Parameter(typeof(int));
var b = YExpression.Parameter(typeof(int));

var exp = YExpression.Lambda<Func<int,int,int>>("add",
            YExpression.Binary(a, YOperator.Add, b),
            new YParameterExpression[] { a, b });

var fx = exp.Compile();

Assert.AreEqual(1, fx(1, 0));
Assert.AreEqual(3, fx(1, 2));

Methods

There are five different methods to generate and execute the code. They are all available in .NET Standard so they are even available for .NET Core.

Compile

This method will compile expression to IL in memory, it will not analyze nested lambdas, this is by design to generate IL faster. For nested lambda, please use CompileWithNestedLambdas method. It will return DynamicMethod delegate.

CompileWithNestedLambdas

When you expression tree contains nested lambda with closures, use this method to generate the IL. It will return DynamicMethod delegate.

CompileInAssembly

This is similar to CompileToMethod, this method generate an empty assembly and it will return generated method from MethodBuilder. This method will compile nested lambdas correctly.

CompileToStaticMethod

This is same as CompileToMethod of Linq. This method will compile nested lambdas correctly.

CompileToInstanceMethod

You can compile lambda with this parameter to an instance method. This method will compile nested lambdas correctly.

Examples

Read Array

var args = YExpression.Parameters(typeof(int[]), typeof(int));

var exp = YExpression.Lambda<Func<int[],int,int>>("load", YExpression.ArrayIndex(
    args[0],
    args[1]
    ), args);

var fx = exp.Compile();

var n = new int[]{1,2,3 };

Assert.AreEqual(1, fx(n, 0));

Nested Closures - Curry

var a = YExpression.Parameters(typeof(int));

var b = YExpression.Parameters(typeof(int));

var c = YExpression.Parameters(typeof(int));


var lamba = YExpression.Lambda<Func<int, Func<int, Func<int, int>>>>("c",
        YExpression.Lambda<Func<int, Func<int, int>>>("b",
            YExpression.Lambda<Func<int, int>>("inner",
                a[0] + b[0] + c[0],
                c
            ), b
        ),
        a
    );

var fx1 = lamba.CompileInAssembly();

var fx2 = fx1(1);

var fx3 = fx2(2);

var r = fx3(3);

Assert.AreEqual(6, r);

Recursive Fibonacci series

var a = YExpression.Parameters(typeof(int));

var fibs = YExpression.Parameters(typeof(Func<int,int>));
var fib = fibs[0];

var fibp = YExpression.Parameters(typeof(int));
var p1 = fibp[0];

var f = YExpression.Lambda<Func<Func<int,int>>>("fib_outer", 
    YExpression.Block(
        fibs.AsSequence(),
        YExpression.Assign(fib, YExpression.Lambda<Func<int, int>>("fib",
            YExpression.Block(
                    YExpression.Conditional(
                        p1 <= 1, 
                        YExpression.Constant(0),
                        YExpression.Conditional(
                            YExpression.Equal(p1, YExpression.Constant(2)), 
                            YExpression.Constant(1),
                                YExpression.Invoke(fib, p1 - 1)
                                +
                                YExpression.Invoke(fib, p1 - 2)
                            ))
                )
            ,
            fibp) )
        )
    , a); ;

var outer = f.CompileInAssembly();

var fx = outer();

Assert.AreEqual(3, fx(5));

Expression Visitors

YantraJS Expression Compiler comes with YExpressionVisitor<T> and YExpressionMapVisitor. YExpressionVisitor<T> is very basic, you may never need it unless you are translating expressions to something else. YExpressionMapVisitor provides visitor which analyzes entire tree and it will replace child nodes as required. Each node and children will be compared to see if any node is modified and it will modify entire tree. All expressions are immutable.

Closure Rewriter

SLE generates new types and fields for closures. In YantraJS Expression Compiler we are generating Box<T> inside array field of the root class Closure. Again we chose this approach to reduce compile time as AssemblyBuiler becomes slower for larger code base. We are still working constantly to improve the compilation speed.

No Validation

API is similar to that of SLE, but implementation is faster as it does not perform any checks whether the expression is correct or not. There are no null checks either. Since we needed to speed up the code generation, we had to skip various checks.

So in order to use the library, you must validate your expressions with SLE and then you can easily replace it with Yantra Expressions.

Following validations are not performed

  1. Label must be within correct block, SLE checks if jumps are possible in blocks. Jumps out of try/catch are not permitted etc.
  2. Return/Jump not permitted in finally block.
  3. Argument types mismatch, SLE checks each method's input output type with expressions.

There may be more validations, and we do not plan to add them.

Reason being, we are using this to generate JavaScript IL code, however all these validations are already performed and translated correctly by JavaScript parser and compiler. And there are thousands of unit test of JavaScript which tells us that Expression Compiler is generating code correctly.

Different Node Types

In SLE, ConstantExpression is a single expression holding type object of any type. This is a problem while generating code as everything a full list of type check occurs to generate the code. Where else in YantraJS we have classes like YInt32Constant, StringConstant etc, which can avoid all type checks and generate code faster. For native value types (int etc), there is no boxing for ConstantExpression leading to faster execution.

YArrayIndexExpression, YIndexExpression (for indexed property) and YPropertyExpression, YFieldExpression are various extra node types which simplifies the code generation.

Assign and Binary expressions are different expressions, again to simplify code generation.

Sequence of T

We also realized that List<T> causes too many re allocations while expression tree is being built, for smaller expressions it does not matter but for extremely large tree, building tree becomes exponentially slower as each node is moved from smaller array to larger array. To solve this issue, we replaced List<T> with Sequence<T>, which is fast for sequential access and appending items. In Expressions, most List<T> are only built once and only read once. In this case, smaller linked arrays inside Sequence reduces GC overhead and memory usage.

Show more
BY Akash Kava
LikeCommentSave
LikeCommentSaveShare
0
Categories
General
YantraJS
Developer Guides
Tutorials
Web Atoms Updates

POPULAR POSTS
17 Mar 2021
LATEST ACTIVITY
shubham.neurospeech
liked this post.
Simmi Kava
liked this post.
Show more
ARCHIVES
2021
2020
2019
2018
TAGS
javascript (46)
developer (25)
javascriptdeveloper (15)
Xamarin.Forms (15)
coding (10)
webatoms (10)
xamarin (10)
android (8)
arrays (8)
typescript (8)
csharp (6)
dotnet (5)
function (5)
iOS (5)
update (5)
web-atoms (5)
dotnet-standard (4)
methods (4)
object (4)
xamarin.android (4)
xamarin.io (4)




Web Atoms: JSX (TSX + TypeScript) for Xamarin.Forms, Hot Reload Your App in Production Environment

PlaygroundSamples Repository