Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Jurassic/Compiler/Emit/OptimizationInfo.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace Jurassic.Compiler
{
Expand Down Expand Up @@ -409,6 +410,21 @@ public int LongJumpStackSizeThreshold
set;
}

public Action EmitOnLoopIteration { get; set; }

public void TryEmitOnLoopIteration(ILGenerator generator)
{
if (EmitOnLoopIteration == null)
return;

if (EmitOnLoopIteration.Target != null)
{
generator.LoadArgument(0);
generator.LoadField(typeof(ScriptEngine).GetField(nameof(ScriptEngine.OnLoopIterationCallTarget)));
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The GetField call can be in ReflectionHelpers.

}
generator.Call(EmitOnLoopIteration.Method);
}

/// <summary>
/// Emits code to branch between statements, even if code generation is within a finally
/// block (where unconditional branches are not allowed).
Expand All @@ -417,6 +433,7 @@ public int LongJumpStackSizeThreshold
/// <param name="targetLabel"> The label to jump to. </param>
public void EmitLongJump(ILGenerator generator, ILLabel targetLabel)
{
TryEmitOnLoopIteration(generator);
if (this.LongJumpCallback == null)
{
// Code generation is not inside a finally block.
Expand Down
2 changes: 2 additions & 0 deletions Jurassic/Compiler/MethodGenerator/CompilerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public CompilerOptions()
/// </summary>
public bool EnableILAnalysis { get; set; }

public Action EmitOnLoopIteration { get; set; }
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing doc comment.


/// <summary>
/// Performs a shallow clone of this instance.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions Jurassic/Compiler/MethodGenerator/MethodGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,8 @@ public void GenerateCode()
optimizationInfo.MethodOptimizationHints = this.MethodOptimizationHints;
optimizationInfo.FunctionName = this.GetStackName();
optimizationInfo.Source = this.Source;
optimizationInfo.EmitOnLoopIteration = Options.EmitOnLoopIteration;


ILGenerator generator;
if (this.Options.EnableDebugging == false)
Expand Down
1 change: 1 addition & 0 deletions Jurassic/Compiler/Statements/ForInStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public override void GenerateCode(ILGenerator generator, OptimizationInfo optimi
this.Body.GenerateCode(generator, optimizationInfo);
optimizationInfo.PopBreakOrContinueInfo();

optimizationInfo.TryEmitOnLoopIteration(generator);
generator.Branch(continueTarget);
generator.DefineLabelPosition(breakTarget);

Expand Down
1 change: 1 addition & 0 deletions Jurassic/Compiler/Statements/ForOfStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ public override void GenerateCode(ILGenerator generator, OptimizationInfo optimi
this.Body.GenerateCode(generator, optimizationInfo);
optimizationInfo.PopBreakOrContinueInfo();

optimizationInfo.TryEmitOnLoopIteration(generator);
generator.Branch(continueTarget);
generator.DefineLabelPosition(breakTarget);

Expand Down
1 change: 1 addition & 0 deletions Jurassic/Compiler/Statements/LoopStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ public override void GenerateCode(ILGenerator generator, OptimizationInfo optimi
if (this.IncrementStatement != null)
this.IncrementStatement.GenerateCode(generator, optimizationInfo);

optimizationInfo.TryEmitOnLoopIteration(generator);
// Unconditionally branch back to the start of the loop.
generator.Branch(startOfLoop);

Expand Down
15 changes: 15 additions & 0 deletions Jurassic/Core/ScriptEngine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -881,6 +881,7 @@ private CompilerOptions CreateOptions()
EnableDebugging = this.EnableDebugging,
CompatibilityMode = this.CompatibilityMode,
EnableILAnalysis = this.EnableILAnalysis,
EmitOnLoopIteration = this.OnLoopIterationCall
};
}

Expand Down Expand Up @@ -1368,5 +1369,19 @@ internal Dictionary<Type, ClrStaticTypeWrapper> StaticTypeWrapperCache
return this.staticTypeWrapperCache;
}
}

// Emit on loop iteration (before loop's branch call)
//_________________________________________________________________________________________
private Action _onLoopIterationCall;
public Action OnLoopIterationCall
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing doc comment.

{
get { return _onLoopIterationCall; }
set
{
_onLoopIterationCall = value;
OnLoopIterationCallTarget = OnLoopIterationCall?.Target;
}
}
public object OnLoopIterationCallTarget;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be a property that returns OnLoopIterationCall?.Target?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea was to reduce further the amount of work required. In particular, avoid a call in favor of a field load

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good point, keep it a field then.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing doc comment.

}
}
207 changes: 207 additions & 0 deletions Unit Tests/Library/EmitUserCodeOnLoopIterationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
using System;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this file under the Core folder please.

using System.Collections.Generic;
using System.Linq;
using System.Text;
using Jurassic;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTests.Library
{

public class ClassWithCallCounters
{
private readonly int _iterationsLimit;
public int InstanceCounter = 0;
public static int StaticCounter = 0;

public ClassWithCallCounters(int iterationsLimit=100)
{
_iterationsLimit = iterationsLimit;
}
public void InstanceMethod()
{
InstanceCounter++;

if (InstanceCounter > _iterationsLimit)
throw new InvalidOperationException($"Reached a maximum of "+_iterationsLimit +" iterations");


}

public static void StaticMetod()
{
StaticCounter++;
}
}

[TestClass]
public class EmitUserCodeOnLoopIterationTests
{
[TestMethod]
public void OnLoopCallWithInstanceMethod()
{
var engine = new ScriptEngine();
var classWithCallCounters = new ClassWithCallCounters();
engine.OnLoopIterationCall = classWithCallCounters.InstanceMethod;
var loopScript = @"
function Test(){

for (var i=0; i< 20; i++){

}
}";
engine.Evaluate(loopScript);
engine.CallGlobalFunction("Test");
Assert.AreEqual(19, classWithCallCounters.InstanceCounter);
}

[TestMethod]
public void OnLoopCallWithInstanceMethodInDebug()
{
var engine = new ScriptEngine();
engine.EnableDebugging = true;
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use this property, it doesn't work in .NET core.

var classWithCallCounters = new ClassWithCallCounters();
engine.OnLoopIterationCall = classWithCallCounters.InstanceMethod;
var loopScript = @"
function Test(){

for (var i=0; i< 20; i++){

}
}";
engine.Evaluate(loopScript);
engine.CallGlobalFunction("Test");
Assert.AreEqual(19, classWithCallCounters.InstanceCounter);
}

[TestMethod]
public void OnLoopCallWithStaticMethod()
{
var engine = new ScriptEngine();

engine.OnLoopIterationCall = ClassWithCallCounters.StaticMetod;
var staticCounterBefore = ClassWithCallCounters.StaticCounter;
var loopScript = @"
function Test(){

for (var i=0; i< 20; i++){

}
}";
engine.Evaluate(loopScript);
engine.CallGlobalFunction("Test");
Assert.AreEqual(19, ClassWithCallCounters.StaticCounter - staticCounterBefore);
}


[TestMethod]
public void OnLoopTerationAndTrivialContinueStatement()
{
var engine = new ScriptEngine();
var classWithCallCounters = new ClassWithCallCounters();
engine.OnLoopIterationCall = classWithCallCounters.InstanceMethod;
var loopScript = @"
function Test(){
for ( var i=0; i< 10; i++){
continue;
}
}";
engine.Evaluate(loopScript);
engine.CallGlobalFunction("Test");
Assert.AreEqual(19, classWithCallCounters.InstanceCounter);
}

[TestMethod]
public void OnLoopTerationAndLabeledContinueStatement()
{
var engine = new ScriptEngine();
var classWithCallCounters = new ClassWithCallCounters();
engine.OnLoopIterationCall = classWithCallCounters.InstanceMethod;
var loopScript = @"
function Test(){
label1:
for ( var i=0; i< 10; i++){
continue label1;
}
}";
engine.Evaluate(loopScript);
engine.CallGlobalFunction("Test");
Assert.AreEqual(19, classWithCallCounters.InstanceCounter);
}

[TestMethod]
public void OnNestedLoop()
{
var engine = new ScriptEngine();
var classWithCallCounters = new ClassWithCallCounters();
engine.OnLoopIterationCall = classWithCallCounters.InstanceMethod;
var loopScript = @"
function Test(){

for ( var i=0; i< 10; i++){
for (var j=0; j< 10; j++){
}

}
}";
engine.Evaluate(loopScript);

engine.CallGlobalFunction("Test");

Assert.AreEqual(99, classWithCallCounters.InstanceCounter);

}

[TestMethod]
public void OnLoopTerationAndNestedLabeledContinueStatement()
{
var engine = new ScriptEngine();
var classWithCallCounters = new ClassWithCallCounters();
engine.OnLoopIterationCall = classWithCallCounters.InstanceMethod;
var loopScript = @"
function Test(){
label1:
for ( var i=0; i< 10; i++){
label2:
for (var j=0; j< 10; j++){
continue label1;
}

}
}";
engine.Evaluate(loopScript);

engine.CallGlobalFunction("Test");

Assert.AreEqual(19, classWithCallCounters.InstanceCounter);
}

[TestMethod]
public void ThrowRightExceptionOnALotOfIterations()
{
var engine = new ScriptEngine();
var classWithCallCounters = new ClassWithCallCounters(5);
engine.OnLoopIterationCall = classWithCallCounters.InstanceMethod;
var loopScript = @"
function Test(){

for ( var i=0; i< 10; i++){

}
}";
engine.Evaluate(loopScript);

Exception e = null;
try
{
engine.CallGlobalFunction("Test");
}
catch (Exception ex)
{
e = ex;
}

Assert.IsInstanceOfType(e, typeof(InvalidOperationException));
}
}
}
1 change: 1 addition & 0 deletions Unit Tests/Unit Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
<Compile Include="Library\DataViewTests.cs" />
<Compile Include="Library\ArrayTests.cs" />
<Compile Include="Library\BooleanTests.cs" />
<Compile Include="Library\EmitUserCodeOnLoopIterationTests.cs" />
<Compile Include="Library\JSONTests.cs" />
<Compile Include="Library\FunctionTests.cs" />
<Compile Include="Core\TypeConverterTests.cs" />
Expand Down