Parcourir la source

Support running DromaeoBenchmark with modern JS syntax (#2041)

Marko Lahma il y a 6 mois
Parent
commit
cb4effda8a

+ 77 - 29
Jint.Benchmark/DromaeoBenchmark.cs

@@ -1,70 +1,118 @@
 using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
 
 namespace Jint.Benchmark;
 
 [MemoryDiagnoser]
+[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByMethod)]
 public class DromaeoBenchmark
 {
     private static readonly Dictionary<string, string> _files = new()
     {
-        {"dromaeo-3d-cube", null},
-        {"dromaeo-core-eval", null},
-        {"dromaeo-object-array", null},
-        {"dromaeo-object-regexp", null},
-        {"dromaeo-object-string", null},
-        {"dromaeo-string-base64", null}
+        { "dromaeo-3d-cube", null },
+        { "dromaeo-core-eval", null },
+        { "dromaeo-object-array", null },
+        { "dromaeo-object-regexp", null },
+        { "dromaeo-object-string", null },
+        { "dromaeo-string-base64", null }
     };
 
     private readonly Dictionary<string, Prepared<Script>> _prepared = new();
 
-    private Engine engine;
+    private Engine _engine;
 
     [GlobalSetup]
     public void Setup()
     {
-        foreach (var fileName in _files.Keys)
+        foreach (var fileName in _files.Keys.ToArray())
         {
-            var script = File.ReadAllText($"Scripts/{fileName}.js");
-            _files[fileName] = script;
-            _prepared[fileName] = Engine.PrepareScript(script);
+            foreach (var suffix in new[] {"", "-modern"})
+            {
+                var name = fileName + suffix;
+                var script = File.ReadAllText($"Scripts/{name}.js");
+                _files[name] = script;
+                _prepared[name] = Engine.PrepareScript(script, name);
+            }
         }
+    }
+
+    [IterationSetup]
+    public void IterationSetup()
+    {
+        _engine = CreteEngine();
+    }
 
-        engine = new Engine()
+    private static Engine CreteEngine()
+    {
+        var engine = new Engine()
             .SetValue("log", new Action<object>(Console.WriteLine))
             .SetValue("assert", new Action<bool>(b => { }));
 
-        engine.Execute(@"
-var startTest = function () { };
-var test = function (name, fn) { fn(); };
-var endTest = function () { };
-var prep = function (fn) { fn(); };
-");
+        engine.Execute("""
+
+                       var startTest = function () { };
+                       var test = function (name, fn) { fn(); };
+                       var endTest = function () { };
+                       var prep = function (fn) { fn(); };
+
+                       """);
+
+        return engine;
     }
 
-    [ParamsSource(nameof(FileNames))]
-    public string FileName { get; set; }
+    [Params(false, true, Priority = 50)]
+    public bool Modern { get; set; }
 
-    [Params(true, false)]
+    [Params(true, false, Priority = 100)]
     public bool Prepared { get; set; }
 
-    public IEnumerable<string> FileNames()
+    [Benchmark]
+    public void CoreEval()
     {
-        foreach (var entry in _files)
-        {
-            yield return entry.Key;
-        }
+        Run("dromaeo-core-eval");
+    }
+
+    [Benchmark]
+    public void Cube()
+    {
+        Run("dromaeo-3d-cube");
     }
 
     [Benchmark]
-    public void Run()
+    public void ObjectArray()
     {
+        Run("dromaeo-object-array");
+    }
+
+    [Benchmark]
+    public void ObjectRegExp()
+    {
+        Run("dromaeo-object-regexp");
+    }
+
+    [Benchmark]
+    public void ObjectString()
+    {
+        Run("dromaeo-object-string");
+    }
+
+    [Benchmark]
+    public void StringBase64()
+    {
+        Run("dromaeo-string-base64");
+    }
+
+    private void Run(string fileName)
+    {
+        var finalName = Modern ? fileName + "-modern" : fileName;
+
         if (Prepared)
         {
-            engine.Execute(_prepared[FileName]);
+            _engine.Execute(_prepared[finalName]);
         }
         else
         {
-            engine.Execute(_files[FileName]);
+            _engine.Execute(_files[finalName], finalName);
         }
     }
 }

+ 8 - 0
Jint.Benchmark/EngineComparisonBenchmark.cs

@@ -18,15 +18,23 @@ public class EngineComparisonBenchmark
     {
         { "array-stress", null },
         { "evaluation", null },
+        { "evaluation-modern", null },
         { "linq-js", null },
         { "minimal", null },
         { "stopwatch", null },
+        { "stopwatch-modern", null },
         { "dromaeo-3d-cube", null },
+        { "dromaeo-3d-cube-modern", null },
         { "dromaeo-core-eval", null },
+        { "dromaeo-core-eval-modern", null },
         { "dromaeo-object-array", null },
+        { "dromaeo-object-array-modern", null },
         { "dromaeo-object-regexp", null },
+        { "dromaeo-object-regexp-modern", null },
         { "dromaeo-object-string", null },
+        { "dromaeo-object-string-modern", null },
         { "dromaeo-string-base64", null },
+        { "dromaeo-string-base64-modern", null },
     };
 
     private static readonly string _dromaeoHelpers = @"

+ 4 - 1
Jint.Benchmark/EvaluationBenchmark.cs

@@ -5,5 +5,8 @@ namespace Jint.Benchmark;
 [MemoryDiagnoser]
 public class EvaluationBenchmark : SingleScriptBenchmark
 {
-    protected override string FileName => "evaluation.js";
+    [Params(false, true)]
+    public bool Modern { get; set; }
+
+    protected override string FileName => Modern ? "evaluation-modern.js" : "evaluation.js";
 }

+ 331 - 0
Jint.Benchmark/Scripts/dromaeo-3d-cube-modern.js

@@ -0,0 +1,331 @@
+// 3D Cube Rotation
+// http://www.speich.net/computer/moztesting/3d.htm
+// Created by Simon Speich
+
+const Q = [];
+let MTrans = [];	// transformation matrix
+let MQube = [];	// position information of qube
+let I = [];			// entity matrix
+const Origin = {};
+const Testing = {};
+let LoopTimer;
+
+const DisplArea = {};
+DisplArea.Width = 300;
+DisplArea.Height = 300;
+
+const DrawLine = (From, To) => {
+    const x1 = From.V[0];
+    const x2 = To.V[0];
+    const y1 = From.V[1];
+    const y2 = To.V[1];
+    const dx = Math.abs(x2 - x1);
+    const dy = Math.abs(y2 - y1);
+    let x = x1;
+    let y = y1;
+    let IncX1, IncY1;
+    let IncX2, IncY2;
+    let Den;
+    let Num;
+    let NumAdd;
+    let NumPix;
+
+    if (x2 >= x1) { IncX1 = 1; IncX2 = 1; }
+    else { IncX1 = -1; IncX2 = -1; }
+    if (y2 >= y1) { IncY1 = 1; IncY2 = 1; }
+    else { IncY1 = -1; IncY2 = -1; }
+    if (dx >= dy) {
+        IncX1 = 0;
+        IncY2 = 0;
+        Den = dx;
+        Num = dx / 2;
+        NumAdd = dy;
+        NumPix = dx;
+    }
+    else {
+        IncX2 = 0;
+        IncY1 = 0;
+        Den = dy;
+        Num = dy / 2;
+        NumAdd = dx;
+        NumPix = dy;
+    }
+
+    NumPix = Math.round(Q.LastPx + NumPix);
+
+    let i = Q.LastPx;
+    for (; i < NumPix; i++) {
+        Num += NumAdd;
+        if (Num >= Den) {
+            Num -= Den;
+            x += IncX1;
+            y += IncY1;
+        }
+        x += IncX2;
+        y += IncY2;
+    }
+    Q.LastPx = NumPix;
+};
+
+const CalcCross = (V0, V1) => {
+    const Cross = [];
+    Cross[0] = V0[1] * V1[2] - V0[2] * V1[1];
+    Cross[1] = V0[2] * V1[0] - V0[0] * V1[2];
+    Cross[2] = V0[0] * V1[1] - V0[1] * V1[0];
+    return Cross;
+};
+
+const CalcNormal = (V0, V1, V2) => {
+    let A = [];
+    const B = [];
+    for (let i = 0; i < 3; i++) {
+        A[i] = V0[i] - V1[i];
+        B[i] = V2[i] - V1[i];
+    }
+    A = CalcCross(A, B);
+    const Length = Math.sqrt(A[0] * A[0] + A[1] * A[1] + A[2] * A[2]);
+    for (let i = 0; i < 3; i++) A[i] = A[i] / Length;
+    A[3] = 1;
+    return A;
+};
+
+const CreateP = function (X, Y, Z) {
+    this.V = [X, Y, Z, 1];
+};
+
+// mulitplies two matrices
+const MMulti = (M1, M2) => {
+    const M = [[], [], [], []];
+    let i = 0;
+    let j = 0;
+    for (; i < 4; i++) {
+        j = 0;
+        for (; j < 4; j++) M[i][j] = M1[i][0] * M2[0][j] + M1[i][1] * M2[1][j] + M1[i][2] * M2[2][j] + M1[i][3] * M2[3][j];
+    }
+    return M;
+};
+
+//multiplies matrix with vector
+const VMulti = (M, V) => {
+    const Vect = [];
+    let i = 0;
+    for (; i < 4; i++) Vect[i] = M[i][0] * V[0] + M[i][1] * V[1] + M[i][2] * V[2] + M[i][3] * V[3];
+    return Vect;
+};
+
+const VMulti2 = (M, V) => {
+    const Vect = [];
+    let i = 0;
+    for (; i < 3; i++) Vect[i] = M[i][0] * V[0] + M[i][1] * V[1] + M[i][2] * V[2];
+    return Vect;
+};
+
+// add to matrices
+const MAdd = (M1, M2) => {
+    const M = [[], [], [], []];
+    let i = 0;
+    let j = 0;
+    for (; i < 4; i++) {
+        j = 0;
+        for (; j < 4; j++) M[i][j] = M1[i][j] + M2[i][j];
+    }
+    return M;
+};
+
+const Translate = (M, Dx, Dy, Dz) => {
+    const T = [
+        [1, 0, 0, Dx],
+        [0, 1, 0, Dy],
+        [0, 0, 1, Dz],
+        [0, 0, 0, 1]
+    ];
+    return MMulti(T, M);
+};
+
+const RotateX = (M, Phi) => {
+    let a = Phi;
+    a *= Math.PI / 180;
+    const Cos = Math.cos(a);
+    const Sin = Math.sin(a);
+    const R = [
+        [1, 0, 0, 0],
+        [0, Cos, -Sin, 0],
+        [0, Sin, Cos, 0],
+        [0, 0, 0, 1]
+    ];
+    return MMulti(R, M);
+};
+
+const RotateY = (M, Phi) => {
+    let a = Phi;
+    a *= Math.PI / 180;
+    const Cos = Math.cos(a);
+    const Sin = Math.sin(a);
+    const R = [
+        [Cos, 0, Sin, 0],
+        [0, 1, 0, 0],
+        [-Sin, 0, Cos, 0],
+        [0, 0, 0, 1]
+    ];
+    return MMulti(R, M);
+};
+
+const RotateZ = (M, Phi) => {
+    let a = Phi;
+    a *= Math.PI / 180;
+    const Cos = Math.cos(a);
+    const Sin = Math.sin(a);
+    const R = [
+        [Cos, -Sin, 0, 0],
+        [Sin, Cos, 0, 0],
+        [0, 0, 1, 0],
+        [0, 0, 0, 1]
+    ];
+    return MMulti(R, M);
+};
+
+const DrawQube = () => {
+    // calc current normals
+    const CurN = [];
+    let i = 5;
+    Q.LastPx = 0;
+    for (; i > -1; i--) CurN[i] = VMulti2(MQube, Q.Normal[i]);
+    if (CurN[0][2] < 0) {
+        if (!Q.Line[0]) { DrawLine(Q[0], Q[1]); Q.Line[0] = true; }
+        if (!Q.Line[1]) { DrawLine(Q[1], Q[2]); Q.Line[1] = true; }
+        if (!Q.Line[2]) { DrawLine(Q[2], Q[3]); Q.Line[2] = true; }
+        if (!Q.Line[3]) { DrawLine(Q[3], Q[0]); Q.Line[3] = true; }
+    }
+    if (CurN[1][2] < 0) {
+        if (!Q.Line[2]) { DrawLine(Q[3], Q[2]); Q.Line[2] = true; }
+        if (!Q.Line[9]) { DrawLine(Q[2], Q[6]); Q.Line[9] = true; }
+        if (!Q.Line[6]) { DrawLine(Q[6], Q[7]); Q.Line[6] = true; }
+        if (!Q.Line[10]) { DrawLine(Q[7], Q[3]); Q.Line[10] = true; }
+    }
+    if (CurN[2][2] < 0) {
+        if (!Q.Line[4]) { DrawLine(Q[4], Q[5]); Q.Line[4] = true; }
+        if (!Q.Line[5]) { DrawLine(Q[5], Q[6]); Q.Line[5] = true; }
+        if (!Q.Line[6]) { DrawLine(Q[6], Q[7]); Q.Line[6] = true; }
+        if (!Q.Line[7]) { DrawLine(Q[7], Q[4]); Q.Line[7] = true; }
+    }
+    if (CurN[3][2] < 0) {
+        if (!Q.Line[4]) { DrawLine(Q[4], Q[5]); Q.Line[4] = true; }
+        if (!Q.Line[8]) { DrawLine(Q[5], Q[1]); Q.Line[8] = true; }
+        if (!Q.Line[0]) { DrawLine(Q[1], Q[0]); Q.Line[0] = true; }
+        if (!Q.Line[11]) { DrawLine(Q[0], Q[4]); Q.Line[11] = true; }
+    }
+    if (CurN[4][2] < 0) {
+        if (!Q.Line[11]) { DrawLine(Q[4], Q[0]); Q.Line[11] = true; }
+        if (!Q.Line[3]) { DrawLine(Q[0], Q[3]); Q.Line[3] = true; }
+        if (!Q.Line[10]) { DrawLine(Q[3], Q[7]); Q.Line[10] = true; }
+        if (!Q.Line[7]) { DrawLine(Q[7], Q[4]); Q.Line[7] = true; }
+    }
+    if (CurN[5][2] < 0) {
+        if (!Q.Line[8]) { DrawLine(Q[1], Q[5]); Q.Line[8] = true; }
+        if (!Q.Line[5]) { DrawLine(Q[5], Q[6]); Q.Line[5] = true; }
+        if (!Q.Line[9]) { DrawLine(Q[6], Q[2]); Q.Line[9] = true; }
+        if (!Q.Line[1]) { DrawLine(Q[2], Q[1]); Q.Line[1] = true; }
+    }
+    Q.Line = [false, false, false, false, false, false, false, false, false, false, false, false];
+    Q.LastPx = 0;
+};
+
+const Loop = () => {
+    if (Testing.LoopCount > Testing.LoopMax) return;
+    let TestingStr = String(Testing.LoopCount);
+    while (TestingStr.length < 3) TestingStr = "0" + TestingStr;
+    MTrans = Translate(I, -Q[8].V[0], -Q[8].V[1], -Q[8].V[2]);
+    MTrans = RotateX(MTrans, 1);
+    MTrans = RotateY(MTrans, 3);
+    MTrans = RotateZ(MTrans, 5);
+    MTrans = Translate(MTrans, Q[8].V[0], Q[8].V[1], Q[8].V[2]);
+    MQube = MMulti(MTrans, MQube);
+    let i = 8;
+    for (; i > -1; i--) {
+        Q[i].V = VMulti(MTrans, Q[i].V);
+    }
+    DrawQube();
+    Testing.LoopCount++;
+    Loop();
+};
+
+function Init(CubeSize) {
+    // init/reset vars
+    Origin.V = [150, 150, 20, 1];
+    Testing.LoopCount = 0;
+    Testing.LoopMax = 50;
+    Testing.TimeMax = 0;
+    Testing.TimeAvg = 0;
+    Testing.TimeMin = 0;
+    Testing.TimeTemp = 0;
+    Testing.TimeTotal = 0;
+    Testing.Init = false;
+
+    // transformation matrix
+    MTrans = [
+	[1, 0, 0, 0],
+	[0, 1, 0, 0],
+	[0, 0, 1, 0],
+	[0, 0, 0, 1]
+    ];
+
+    // position information of qube
+    MQube = [
+	[1, 0, 0, 0],
+	[0, 1, 0, 0],
+	[0, 0, 1, 0],
+	[0, 0, 0, 1]
+    ];
+
+    // entity matrix
+    I = [
+	[1, 0, 0, 0],
+	[0, 1, 0, 0],
+	[0, 0, 1, 0],
+	[0, 0, 0, 1]
+    ];
+
+    // create qube
+    Q[0] = new CreateP(-CubeSize, -CubeSize, CubeSize);
+    Q[1] = new CreateP(-CubeSize, CubeSize, CubeSize);
+    Q[2] = new CreateP(CubeSize, CubeSize, CubeSize);
+    Q[3] = new CreateP(CubeSize, -CubeSize, CubeSize);
+    Q[4] = new CreateP(-CubeSize, -CubeSize, -CubeSize);
+    Q[5] = new CreateP(-CubeSize, CubeSize, -CubeSize);
+    Q[6] = new CreateP(CubeSize, CubeSize, -CubeSize);
+    Q[7] = new CreateP(CubeSize, -CubeSize, -CubeSize);
+
+    // center of gravity
+    Q[8] = new CreateP(0, 0, 0);
+
+    // anti-clockwise edge check
+    Q.Edge = [[0, 1, 2], [3, 2, 6], [7, 6, 5], [4, 5, 1], [4, 0, 3], [1, 5, 6]];
+
+    // calculate squad normals
+    Q.Normal = [];
+    for (let i = 0; i < Q.Edge.length; i++) Q.Normal[i] = CalcNormal(Q[Q.Edge[i][0]].V, Q[Q.Edge[i][1]].V, Q[Q.Edge[i][2]].V);
+
+    // line drawn ?
+    Q.Line = [false, false, false, false, false, false, false, false, false, false, false, false];
+
+    // create line pixels
+    Q.NumPx = 9 * 2 * CubeSize;
+    for (let i = 0; i < Q.NumPx; i++) new CreateP(0, 0, 0);
+
+    MTrans = Translate(MTrans, Origin.V[0], Origin.V[1], Origin.V[2]);
+    MQube = MMulti(MTrans, MQube);
+
+    let i = 0;
+    for (; i < 9; i++) {
+        Q[i].V = VMulti(MTrans, Q[i].V);
+    }
+    DrawQube();
+    Testing.Init = true;
+    Loop();
+}
+
+startTest("dromaeo-3d-cube", '979cd0f1');
+
+test("Rotate 3D Cube", () => Init(20));
+
+endTest();

+ 23 - 0
Jint.Benchmark/Scripts/dromaeo-core-eval-modern.js

@@ -0,0 +1,23 @@
+startTest("dromaeo-core-eval", 'efec1da2');
+
+// Try to force real results
+let ret, tmp;
+
+// The commands that we'll be evaling
+const cmd = 'var str="";for(var i=0;i<1000;i++){str += "a";}ret = str;';
+
+// TESTS: eval()
+const num = 4;
+
+prep(() => {
+    tmp = cmd;
+
+    for (let n = 0; n < num; n++)
+        tmp += tmp;
+});
+
+test("Normal eval", () => eval(tmp));
+
+test("new Function", () => (new Function(tmp))());
+
+endTest();

+ 57 - 0
Jint.Benchmark/Scripts/dromaeo-object-array-modern.js

@@ -0,0 +1,57 @@
+startTest("dromaeo-object-array", 'bde4f5f4');
+
+let ret = [], tmp;
+const num = 500;
+const i = 1024;
+
+// TESTS: Array Building
+
+test("Array Construction, []", () => {
+    for (let j = 0; j < i * 15; j++) {
+        ret = [];
+        ret.length = i;
+    }
+});
+
+test("Array Construction, new Array()", () => {
+    for (let j = 0; j < i * 10; j++)
+        ret = new Array(i);
+});
+
+test("Array Construction, unshift", () => {
+    ret = [];
+    for (let j = 0; j < i; j++)
+        ret.unshift(j);
+});
+
+test("Array Construction, splice", () => {
+    ret = [];
+    for (let j = 0; j < i; j++)
+        ret.splice(0, 0, j);
+});
+
+test("Array Deconstruction, shift", () => {
+    const a = ret.slice();
+    for (let j = 0; j < i; j++)
+        tmp = a.shift();
+});
+
+test("Array Deconstruction, splice", () => {
+    const a = ret.slice();
+    for (let j = 0; j < i; j++)
+        tmp = a.splice(0, 1);
+});
+
+test("Array Construction, push", () => {
+    ret = [];
+    for (let j = 0; j < i * 25; j++)
+        ret.push(j);
+});
+
+test("Array Deconstruction, pop", () => {
+    const a = ret.slice();
+    for (let j = 0; j < i * 25; j++)
+        tmp = a.pop();
+});
+
+endTest();

+ 366 - 0
Jint.Benchmark/Scripts/dromaeo-object-regexp-modern.js

@@ -0,0 +1,366 @@
+startTest("dromaeo-object-regexp", '812dde38');
+
+// Try to force real results
+let str = [], tmp, ret, re;
+const testStrings = [];
+var i = 65536;
+
+const randomChar = () => String.fromCharCode((25 * Math.random()) + 97);
+
+for (let i = 0; i < 16384; i++)
+    str.push(randomChar());
+
+str = str.join("");
+str += str;
+str += str;
+
+const generateTestStrings = count => {
+    let t, nest;
+    if (testStrings.length >= count)
+        return testStrings.slice(0, count);
+    for (let i = testStrings.length; i < count; i++) {
+        // Make all tested strings different
+        t = randomChar() + str + randomChar();
+        nest = Math.floor(4 * Math.random());
+        for (let j = 0; j < nest; j++) {
+            t = randomChar() + t + randomChar();
+        }
+        // Try to minimize benchmark order dependencies by
+        // exercising the strings
+        for (let j = 0; j < t.length; j += 100) {
+            ret = t[j];
+            ret = t.substring(j, j + 100);
+        }
+        testStrings[i] = t;
+    }
+    return testStrings;
+};
+
+// TESTS: split
+
+prep(() => {
+    // It's impossible to specify empty regexp by simply
+    // using two slashes as this will be interpreted as a
+    // comment start. See note to ECMA-262 5th 7.8.5.
+    re = /(?:)/;
+    tmp = generateTestStrings(30);
+});
+
+test("Compiled Object Empty Split", () => {
+    for (let i = 0; i < 30; i++)
+        ret = tmp[i].split(re);
+});
+
+prep(() => {
+    re = /a/;
+    tmp = generateTestStrings(30);
+});
+
+test("Compiled Object Char Split", () => {
+    for (let i = 0; i < 30; i++)
+        ret = tmp[i].split(re);
+});
+
+prep(() => {
+    re = /.*/;
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Object Variable Split", () => {
+    for (let i = 0; i < 100; i++)
+        ret = tmp[i].split(re);
+});
+
+// TESTS: Compiled RegExps
+
+prep(() => {
+    re = /aaaaaaaaaa/g;
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Match", () => {
+    for (let i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(() => {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Test", () => {
+    for (let i = 0; i < 100; i++)
+        ret = re.test(tmp[i]);
+});
+
+prep(() => {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Empty Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled 12 Char Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(() => {
+    re = new RegExp("aaaaaaaaaa", "g");
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Object Match", () => {
+    for (let i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(() => {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Object Test", () => {
+    for (let i = 0; i < 100; i++)
+        ret = re.test(tmp[i]);
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Object Empty Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Object 12 Char Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Object 12 Char Replace Function", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, all => "asdfasdfasdf");
+});
+
+// TESTS: Variable Length
+
+prep(() => {
+    re = /a.*a/;
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Variable Match", () => {
+    for (let i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(() => {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Variable Test", () => {
+    for (let i = 0; i < 100; i++)
+        ret = re.test(tmp[i]);
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable Empty Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable 12 Char Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(() => {
+    re = new RegExp("aaaaaaaaaa", "g");
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Variable Object Match", () => {
+    for (let i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(() => {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Variable Object Test", () => {
+    for (let i = 0; i < 100; i++)
+        ret = re.test(tmp[i]);
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable Object Empty Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable Object 12 Char Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable Object 12 Char Replace Function", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, all => "asdfasdfasdf");
+});
+
+// TESTS: Capturing
+
+prep(() => {
+    re = /aa(b)aa/g;
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Capture Match", () => {
+    for (let i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Capture Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Capture Replace with Capture", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdf\\1asdfasdf");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Capture Replace with Capture Function", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, (all, capture) => "asdf" + capture + "asdfasdf");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Capture Replace with Upperase Capture Function", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, (all, capture) => capture.toUpperCase());
+});
+
+// TESTS: Uncompiled RegExps
+
+prep(() => {
+    tmp = generateTestStrings(100);
+});
+
+test("Uncompiled Match", () => {
+    for (let i = 0; i < 100; i++)
+        ret = tmp[i].match(/aaaaaaaaaa/g);
+});
+
+prep(() => {
+    tmp = generateTestStrings(100);
+});
+
+test("Uncompiled Test", () => {
+    for (let i = 0; i < 100; i++)
+        ret = (/aaaaaaaaaa/g).test(tmp[i]);
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Uncompiled Empty Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(/aaaaaaaaaa/g, "");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Uncompiled 12 Char Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(/aaaaaaaaaa/g, "asdfasdfasdf");
+});
+
+prep(() => {
+    tmp = generateTestStrings(100);
+});
+
+test("Uncompiled Object Match", () => {
+    for (let i = 0; i < 100; i++)
+        ret = tmp[i].match(new RegExp("aaaaaaaaaa", "g"));
+});
+
+prep(() => {
+    tmp = generateTestStrings(100);
+});
+
+test("Uncompiled Object Test", () => {
+    for (let i = 0; i < 100; i++)
+        ret = (new RegExp("aaaaaaaaaa", "g")).test(tmp[i]);
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Uncompiled Object Empty Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(new RegExp("aaaaaaaaaa", "g"), "");
+});
+
+prep(() => {
+    tmp = generateTestStrings(50);
+});
+
+test("Uncompiled Object 12 Char Replace", () => {
+    for (let i = 0; i < 50; i++)
+        ret = tmp[i].replace(new RegExp("aaaaaaaaaa", "g"), "asdfasdfasdf");
+});
+
+endTest();

+ 186 - 0
Jint.Benchmark/Scripts/dromaeo-object-string-modern.js

@@ -0,0 +1,186 @@
+startTest("dromaeo-object-string", 'ef8605c3');
+
+// Try to force real results
+let ret;
+const num = 80000;
+
+// TESTS: String concatenation
+
+test("Concat String", () => {
+    let str = "";
+    for (let i = 0; i < num; i++)
+        str += "a";
+    ret = str;
+});
+
+test("Concat String Object", () => {
+    let str = new String();
+    for (let i = 0; i < num; i++)
+        str += "a";
+    ret = str;
+});
+
+test("Concat String from charCode", () => {
+    let str = "";
+    for (let i = 0; i < num / 2; i++)
+        str += String.fromCharCode(97);
+    ret = str;
+});
+
+test("Array String Join", () => {
+    const str = [];
+    for (let i = 0; i < num / 2; i++)
+        str.push("a");
+    ret = str.join("");
+});
+
+let ostr = [], tmp, tmp2, tmpstr;
+
+for (let i = 0; i < 16384; i++)
+    ostr.push(String.fromCharCode((25 * Math.random()) + 97));
+
+ostr = ostr.join("");
+ostr += ostr;
+ostr += ostr;
+
+let str;
+let i = 52288;
+
+prep(() => str = new String(ostr));
+
+// TESTS: split
+test("String Split", () => ret = str.split(""));
+
+prep(() => {
+    tmpstr = str;
+    tmpstr += tmpstr;
+    tmpstr += tmpstr;
+    tmpstr += tmpstr;
+    tmpstr += tmpstr;
+});
+
+test("String Split on Char", () => ret = tmpstr.split("a"));
+
+prep(() => str += str);
+
+// TESTS: characters
+
+test("charAt", () => {
+    for (let j = 0; j < num; j++) {
+        ret = str.charAt(0);
+        ret = str.charAt(str.length - 1);
+        ret = str.charAt(15000);
+        ret = str.charAt(12000);
+    }
+});
+
+test("[Number]", () => {
+    for (let j = 0; j < num; j++) {
+        ret = str[0];
+        ret = str[str.length - 1];
+        ret = str[15000];
+        ret = str[10000];
+        ret = str[5000];
+    }
+});
+
+test("charCodeAt", () => {
+    for (let j = 0; j < num; j++) {
+        ret = str.charCodeAt(0);
+        ret = str.charCodeAt(str.length - 1);
+        ret = str.charCodeAt(15000);
+        ret = str.charCodeAt(10000);
+        ret = str.charCodeAt(5000);
+    }
+});
+
+// TESTS: indexOf
+
+test("indexOf", () => {
+    for (let j = 0; j < num; j++) {
+        ret = str.indexOf("a");
+        ret = str.indexOf("b");
+        ret = str.indexOf("c");
+        ret = str.indexOf("d");
+    }
+});
+
+test("lastIndexOf", () => {
+    for (let j = 0; j < num; j++) {
+        ret = str.lastIndexOf("a");
+        ret = str.lastIndexOf("b");
+        ret = str.lastIndexOf("c");
+        ret = str.lastIndexOf("d");
+    }
+});
+
+// TESTS: slice
+
+test("slice", () => {
+    for (let j = 0; j < num; j++) {
+        ret = str.slice(0);
+        ret = str.slice(0, 5);
+        ret = str.slice(-1);
+        ret = str.slice(-6, -1);
+        ret = str.slice(15000, 15005);
+        ret = str.slice(12000, -1);
+    }
+});
+
+// TESTS: substr
+
+test("substr", () => {
+    for (let j = 0; j < num; j++) {
+        ret = str.substr(0);
+        ret = str.substr(0, 5);
+        ret = str.substr(-1);
+        ret = str.substr(-6, 1);
+        ret = str.substr(15000, 5);
+        ret = str.substr(12000, 5);
+    }
+});
+
+// TESTS: substring
+
+test("substring", () => {
+    for (let j = 0; j < num; j++) {
+        ret = str.substring(0);
+        ret = str.substring(0, 5);
+        ret = str.substring(-1);
+        ret = str.substring(-6, -1);
+        ret = str.substring(15000, 15005);
+        ret = str.substring(12000, -1);
+    }
+});
+
+// TESTS: toLower/UpperCase
+
+test("toLowerCase", () => {
+    for (let j = 0; j < num / 1000; j++) {
+        ret = str.toLowerCase();
+    }
+});
+
+test("toUpperCase", () => {
+    for (let j = 0; j < num / 1000; j++) {
+        ret = str.toUpperCase();
+    }
+});
+
+// TESTS: comparing
+prep(() => {
+    tmp = str;
+    tmp2 = str;
+});
+
+test("comparing", () => {
+    tmp = "a" + tmp + "a";
+    tmp2 = "a" + tmp2 + "a";
+    for (let j = 0; j < num / 1000; j++) {
+        ret = tmp == tmp2;
+        ret = tmp < tmp2;
+        ret = tmp > tmp2;
+    }
+});
+
+endTest();

+ 147 - 0
Jint.Benchmark/Scripts/dromaeo-string-base64-modern.js

@@ -0,0 +1,147 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla XML-RPC Client component.
+ *
+ * The Initial Developer of the Original Code is
+ * Digital Creations 2, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Martijn Pieters <[email protected]> (original author)
+ *   Samuel Sieb <[email protected]>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// From: http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
+
+/* Convert data (an array of integers) to a Base64 string. */
+const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+const base64Pad = '=';
+
+function toBase64(data) {
+    let result = '';
+    const length = data.length;
+    let i;
+    // Convert every three bytes to 4 ascii characters.
+    for (i = 0; i < (length - 2) ; i += 3) {
+        result += toBase64Table[data.charCodeAt(i) >> 2];
+        result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.charCodeAt(i + 1) >> 4)];
+        result += toBase64Table[((data.charCodeAt(i + 1) & 0x0f) << 2) + (data.charCodeAt(i + 2) >> 6)];
+        result += toBase64Table[data.charCodeAt(i + 2) & 0x3f];
+    }
+
+    // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
+    if (length % 3) {
+        i = length - (length % 3);
+        result += toBase64Table[data.charCodeAt(i) >> 2];
+        if ((length % 3) == 2) {
+            result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.chartCodeAt(i + 1) >> 4)];
+            result += toBase64Table[(data.charCodeAt(i + 1) & 0x0f) << 2];
+            result += base64Pad;
+        } else {
+            result += toBase64Table[(data.charCodeAt(i) & 0x03) << 4];
+            result += base64Pad + base64Pad;
+        }
+    }
+
+    return result;
+}
+
+/* Convert Base64 data to a string */
+const toBinaryTable = [
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
+    -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
+];
+
+const base64ToString = data => {
+    let result = '';
+    let leftbits = 0; // number of bits decoded, but yet to be appended
+    let leftdata = 0; // bits decoded, but yet to be appended
+
+    // Convert one by one.
+    for (let i = 0; i < data.length; i++) {
+        const c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+        const padding = (data.charCodeAt(i) == base64Pad.charCodeAt(0));
+        // Skip illegal characters and whitespace
+        if (c == -1) continue;
+
+        // Collect data into leftdata, update bitcount
+        leftdata = (leftdata << 6) | c;
+        leftbits += 6;
+
+        // If we have 8 or more bits, append 8 bits to the result
+        if (leftbits >= 8) {
+            leftbits -= 8;
+            // Append if not padding.
+            if (!padding)
+                result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+            leftdata &= (1 << leftbits) - 1;
+        }
+    }
+
+    // If there are any bits left, the base64 string was corrupted
+    if (leftbits)
+        throw Components.Exception('Corrupted base64 string');
+
+    return result;
+};
+
+startTest("dromaeo-string-base64", '09340c18');
+
+let str = [];
+
+for (let i = 0; i < 4096; i++)
+    str.push(String.fromCharCode((25 * Math.random()) + 97));
+
+str = str.join("");
+str += str;
+str += str;
+
+let base64;
+
+test("Convert String to Base 64", () => {
+    base64 = toBase64(str);
+});
+
+prep(() => {
+    if (!base64)
+        base64 = toBase64(str);
+});
+
+test("Convert Base 64 to String", () => {
+    if (str !== base64ToString(base64)) {
+        throw "String conversion mis-match.";
+    }
+});
+
+endTest();

+ 16 - 0
Jint.Benchmark/Scripts/evaluation-modern.js

@@ -0,0 +1,16 @@
+const o = {};
+o.Foo = 'bar';
+o.Baz = 42.0001;
+o.Blah = o.Foo + o.Baz;
+
+if(o.Blah != 'bar42.0001') throw TypeError;
+
+const fib = n => {
+    if(n<2) {
+        return n;
+    }
+
+    return fib(n-1) + fib(n-2);
+};
+
+if(fib(3) != 2) throw TypeError;

+ 56 - 0
Jint.Benchmark/Scripts/stopwatch-modern.js

@@ -0,0 +1,56 @@
+function Stopwatch() {
+    const sw = this;
+    let start = null;
+    let stop = null;
+    let isRunning = false;
+
+    sw.Start = function () {
+        if (isRunning)
+            return;
+
+        start = new Date();
+        stop = null;
+        isRunning = true;
+    }
+
+    sw.Stop = function () {
+        if (!isRunning)
+            return;
+
+        stop = new Date();
+        isRunning = false;
+    }
+
+    sw.Reset = function () {
+        start = isRunning ? new Date() : null;
+        stop = null;
+    }
+
+    sw.Restart = function () {
+        isRunning = true;
+        sw.Reset();
+    }
+
+    sw.ElapsedMilliseconds = function () { return (isRunning ? new Date() : stop) - start; }
+    sw.IsRunning = function() { return isRunning; }
+
+}
+
+var sw = new Stopwatch();
+sw.Start();
+for (let x = 0; x < 1021; x++) {
+    for (let y = 0; y < 383; y++) {
+        const z = x ^ y;
+        if (z % 2 == 0)
+            sw.Start();
+        else if(z % 3 == 0)
+            sw.Stop();
+        else if (z % 5 == 0)
+            sw.Reset();
+        else if (z % 7 == 0)
+            sw.Restart();
+        const ms = sw.ElapsedMilliseconds;
+        const rn = sw.IsRunning;
+    }
+}
+sw.Stop();

+ 4 - 1
Jint.Benchmark/StopwatchBenchmark.cs

@@ -5,5 +5,8 @@ namespace Jint.Benchmark;
 [MemoryDiagnoser]
 public class StopwatchBenchmark : SingleScriptBenchmark
 {
-    protected override string FileName => "stopwatch.js";
+    [Params(false, true)]
+    public bool Modern { get; set; }
+
+    protected override string FileName => Modern ? "stopwatch-modern.js" : "stopwatch.js";
 }