Skip to content

Commit

Permalink
Cache common JsValue allocations (sebastienros#456)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored and sebastienros committed Jan 2, 2018
1 parent c2f013d commit aaf42cc
Show file tree
Hide file tree
Showing 10 changed files with 165 additions and 60 deletions.
37 changes: 21 additions & 16 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,22 @@ public class Engine

internal static Dictionary<Type, Func<Engine, object, JsValue>> TypeMappers = new Dictionary<Type, Func<Engine, object, JsValue>>()
{
{ typeof(bool), (Engine engine, object v) => new JsValue((bool)v) },
{ typeof(byte), (Engine engine, object v) => new JsValue((byte)v) },
{ typeof(char), (Engine engine, object v) => new JsValue((char)v) },
{ typeof(bool), (Engine engine, object v) => (bool) v ? JsValue.True : JsValue.False },
{ typeof(byte), (Engine engine, object v) => JsValue.FromInt((byte)v) },
{ typeof(char), (Engine engine, object v) => JsValue.FromChar((char)v) },
{ typeof(DateTime), (Engine engine, object v) => engine.Date.Construct((DateTime)v) },
{ typeof(DateTimeOffset), (Engine engine, object v) => engine.Date.Construct((DateTimeOffset)v) },
{ typeof(decimal), (Engine engine, object v) => new JsValue((double)(decimal)v) },
{ typeof(double), (Engine engine, object v) => new JsValue((double)v) },
{ typeof(Int16), (Engine engine, object v) => new JsValue((Int16)v) },
{ typeof(Int32), (Engine engine, object v) => new JsValue((Int32)v) },
{ typeof(Int64), (Engine engine, object v) => new JsValue((Int64)v) },
{ typeof(SByte), (Engine engine, object v) => new JsValue((SByte)v) },
{ typeof(Single), (Engine engine, object v) => new JsValue((Single)v) },
{ typeof(string), (Engine engine, object v) => new JsValue((string)v) },
{ typeof(UInt16), (Engine engine, object v) => new JsValue((UInt16)v) },
{ typeof(UInt32), (Engine engine, object v) => new JsValue((UInt32)v) },
{ typeof(UInt64), (Engine engine, object v) => new JsValue((UInt64)v) },
{ typeof(decimal), (Engine engine, object v) => (JsValue) (double)(decimal)v },
{ typeof(double), (Engine engine, object v) => (JsValue)(double)v },
{ typeof(Int16), (Engine engine, object v) => JsValue.FromInt((Int16)v) },
{ typeof(Int32), (Engine engine, object v) => JsValue.FromInt((Int32)v) },
{ typeof(Int64), (Engine engine, object v) => (JsValue)(Int64)v },
{ typeof(SByte), (Engine engine, object v) => JsValue.FromInt((SByte)v) },
{ typeof(Single), (Engine engine, object v) => (JsValue)(Single)v },
{ typeof(string), (Engine engine, object v) => (JsValue) (string)v },
{ typeof(UInt16), (Engine engine, object v) => JsValue.FromInt((UInt16)v) },
{ typeof(UInt32), (Engine engine, object v) => JsValue.FromInt((UInt32)v) },
{ typeof(UInt64), (Engine engine, object v) => JsValue.FromInt((UInt64)v) },
{ typeof(JsValue), (Engine engine, object v) => (JsValue)v },
{ typeof(System.Text.RegularExpressions.Regex), (Engine engine, object v) => engine.RegExp.Construct((System.Text.RegularExpressions.Regex)v, "") }
};
Expand Down Expand Up @@ -257,9 +257,14 @@ public Engine SetValue(string name, double value)
return SetValue(name, new JsValue(value));
}

public Engine SetValue(string name, int value)
{
return SetValue(name, JsValue.FromInt(value));
}

public Engine SetValue(string name, bool value)
{
return SetValue(name, new JsValue(value));
return SetValue(name, value ? JsValue.True : JsValue.False);
}

public Engine SetValue(string name, JsValue value)
Expand Down Expand Up @@ -539,7 +544,7 @@ public JsValue GetValue(object value)
{
return baseValue;
}

if (reference.HasPrimitiveBase() == false)
{
var o = TypeConverter.ToObject(this, baseValue);
Expand Down
2 changes: 1 addition & 1 deletion Jint/Native/Argument/ArgumentsInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ public override void Put(string propertyName, JsValue value, bool throwOnError)
if (desc.IsAccessorDescriptor())
{
var setter = desc.Set.TryCast<ICallable>();
setter.Call(new JsValue(this), new[] { value });
setter.Call(JsValue, new[] { value });
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions Jint/Native/Array/ArrayInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public override void Put(string propertyName, JsValue value, bool throwOnError)
if (desc.IsAccessorDescriptor())
{
var setter = desc.Set.TryCast<ICallable>();
setter.Call(new JsValue(this), new[] { value });
setter.Call(JsValue, new[] { value });
}
else
{
Expand Down Expand Up @@ -128,7 +128,7 @@ public override bool DefineOwnProperty(string propertyName, PropertyDescriptor d
var deleteSucceeded = Delete(TypeConverter.ToString(keyIndex), false);
if (!deleteSucceeded)
{
newLenDesc.Value = new JsValue(keyIndex + 1);
newLenDesc.Value = JsValue.FromInt(keyIndex + 1);
if (!newWritable)
{
newLenDesc.Writable = false;
Expand Down
2 changes: 1 addition & 1 deletion Jint/Native/Function/ScriptFunctionInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public ScriptFunctionInstance(Engine engine, IFunction functionDeclaration, Lexi
Extensible = true;
Prototype = engine.Function.PrototypeObject;

DefineOwnProperty("length", new PropertyDescriptor(new JsValue(FormalParameters.Length), false, false, false ), false);
DefineOwnProperty("length", new PropertyDescriptor(JsValue.FromInt(FormalParameters.Length), false, false, false ), false);

var proto = engine.Object.Construct(Arguments.Empty);
proto.DefineOwnProperty("constructor", new PropertyDescriptor(this, true, false, true), false);
Expand Down
132 changes: 113 additions & 19 deletions Jint/Native/JsValue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,39 @@ namespace Jint.Native
[DebuggerTypeProxy(typeof(JsValueDebugView))]
public class JsValue : IEquatable<JsValue>
{
public readonly static JsValue Undefined = new JsValue(Types.Undefined);
public readonly static JsValue Null = new JsValue(Types.Null);
public readonly static JsValue False = new JsValue(false);
public readonly static JsValue True = new JsValue(true);
// we can cache most common values, doubles are used in indexing too at times so we also cache
// integer values converted to doubles
private static readonly Dictionary<double, JsValue> _doubleToJsValue = new Dictionary<double, JsValue>();
private static readonly JsValue[] _intToJsValue = new JsValue[1024];
private static readonly JsValue[] _charToJsValue = new JsValue[256];

private static readonly JsValue _function = new JsValue("function");

public static readonly JsValue Undefined = new JsValue(Types.Undefined);
public static readonly JsValue Null = new JsValue(Types.Null);
public static readonly JsValue False = new JsValue(false);
public static readonly JsValue True = new JsValue(true);

private readonly double _double;
private readonly object _object;
protected Types _type;

static JsValue()
{
for (int i = 0; i < _intToJsValue.Length; i++)
{
_intToJsValue[i] = new JsValue(i);
if (i != 0)
{
// zero can be problematic
_doubleToJsValue[i] = new JsValue((double) i);
}
}
for (int i = 0; i < _charToJsValue.Length; i++)
{
_charToJsValue[i] = new JsValue((char) i);
}
}

public JsValue(bool value)
{
Expand All @@ -40,6 +69,29 @@ public JsValue(double value)
_double = value;
}

public JsValue(int value)
{
_object = null;
_type = Types.Number;

_double = value;
}

public JsValue(uint value)
{
_object = null;
_type = Types.Number;

_double = value;
}

public JsValue(char value)
{
_double = double.NaN;
_object = value;
_type = Types.String;
}

public JsValue(string value)
{
_double = double.NaN;
Expand Down Expand Up @@ -70,12 +122,6 @@ private JsValue(Types type)
_type = type;
}

protected double _double;

protected object _object;

protected Types _type;

[Pure]
public bool IsPrimitive()
{
Expand Down Expand Up @@ -335,9 +381,42 @@ public bool Equals(JsValue other)
}
}

public Types Type
public Types Type => _type;

internal static JsValue FromInt(int value)
{
if (value >= 0 && value < _intToJsValue.Length)
{
return _intToJsValue[value];
}
return new JsValue(value);
}

internal static JsValue FromInt(uint value)
{
if (value >= 0 && value < _intToJsValue.Length)
{
return _intToJsValue[value];
}
return new JsValue(value);
}

internal static JsValue FromInt(ulong value)
{
get { return _type; }
if (value >= 0 && value < (ulong) _intToJsValue.Length)
{
return _intToJsValue[value];
}
return new JsValue(value);
}

internal static JsValue FromChar(char value)
{
if (value >= 0 && value < _charToJsValue.Length)
{
return _charToJsValue[value];
}
return new JsValue(value);
}

/// <summary>
Expand Down Expand Up @@ -380,16 +459,16 @@ public static JsValue FromObject(Engine engine, object value)
// Learn conversion, racy, worst case we'll try again later
Interlocked.CompareExchange(ref Engine.TypeMappers, new Dictionary<Type, Func<Engine, object, JsValue>>(typeMappers)
{
[valueType] = (Engine e, object v) => new JsValue((ObjectInstance)v)
[valueType] = (Engine e, object v) => ((ObjectInstance)v).JsValue
}, typeMappers);
return new JsValue(instance);
return instance.JsValue;
}

var type = value as Type;
if(type != null)
{
var typeReference = TypeReference.CreateTypeReference(engine, type);
return new JsValue(typeReference);
return typeReference.JsValue;
}

var a = value as System.Array;
Expand Down Expand Up @@ -424,7 +503,7 @@ public static JsValue FromObject(Engine engine, object value)

if (value.GetType().IsEnum())
{
return new JsValue((Int32)value);
return FromInt((int) value);
}

// if no known type could be guessed, wrap it as an ObjectInstance
Expand Down Expand Up @@ -662,24 +741,39 @@ public override string ToString()
return !a.Equals(b);
}

static public implicit operator JsValue(int value)
{
return FromInt(value);
}

static public implicit operator JsValue(double value)
{
return new JsValue(value);
if (value < 0 || value >= _doubleToJsValue.Count || !_doubleToJsValue.TryGetValue(value, out var jsValue))
{
jsValue = new JsValue(value);
}
return jsValue;
}

static public implicit operator JsValue(bool value)
{
return new JsValue(value);
return value ? True : False;
}

static public implicit operator JsValue(string value)
{
// some common ones can be cached here
if (value == "function")
{
return _function;
}

return new JsValue(value);
}

static public implicit operator JsValue(ObjectInstance value)
{
return new JsValue(value);
return value.JsValue;
}

internal class JsValueDebugView
Expand Down
2 changes: 1 addition & 1 deletion Jint/Native/Json/JsonParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,7 @@ private JsValue ParseJsonValue()
var v = Lex().Value;
return Null.Instance;
case Tokens.BooleanLiteral:
return new JsValue((bool)Lex().Value);
return (bool) Lex().Value ? JsValue.True : JsValue.False;
case Tokens.String:
return new JsValue((string)Lex().Value);
case Tokens.Number:
Expand Down
19 changes: 14 additions & 5 deletions Jint/Native/Object/ObjectInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Jint.Native.Object
{
public class ObjectInstance
{
private JsValue _jsValue;
private Dictionary<string, PropertyDescriptor> _intrinsicProperties;

public ObjectInstance(Engine engine)
Expand All @@ -20,6 +21,14 @@ public ObjectInstance(Engine engine)

protected IDictionary<string, PropertyDescriptor> Properties { get; private set; }

/// <summary>
/// Caches the constructed JS.
/// </summary>
internal JsValue JsValue
{
get { return _jsValue = _jsValue ?? new JsValue(this); }
}

protected bool TryGetIntrinsicValue(JsSymbol symbol, out JsValue value)
{
PropertyDescriptor descriptor;
Expand Down Expand Up @@ -228,7 +237,7 @@ public virtual void Put(string propertyName, JsValue value, bool throwOnError)
if (desc.IsAccessorDescriptor())
{
var setter = desc.Set.TryCast<ICallable>();
setter.Call(new JsValue(this), new [] {value});
setter.Call(JsValue, new [] {value});
}
else
{
Expand Down Expand Up @@ -356,7 +365,7 @@ public JsValue DefaultValue(Types hint)
var toString = Get("toString").TryCast<ICallable>();
if (toString != null)
{
var str = toString.Call(new JsValue(this), Arguments.Empty);
var str = toString.Call(JsValue, Arguments.Empty);
if (str.IsPrimitive())
{
return str;
Expand All @@ -366,7 +375,7 @@ public JsValue DefaultValue(Types hint)
var valueOf = Get("valueOf").TryCast<ICallable>();
if (valueOf != null)
{
var val = valueOf.Call(new JsValue(this), Arguments.Empty);
var val = valueOf.Call(JsValue, Arguments.Empty);
if (val.IsPrimitive())
{
return val;
Expand All @@ -381,7 +390,7 @@ public JsValue DefaultValue(Types hint)
var valueOf = Get("valueOf").TryCast<ICallable>();
if (valueOf != null)
{
var val = valueOf.Call(new JsValue(this), Arguments.Empty);
var val = valueOf.Call(JsValue, Arguments.Empty);
if (val.IsPrimitive())
{
return val;
Expand All @@ -391,7 +400,7 @@ public JsValue DefaultValue(Types hint)
var toString = Get("toString").TryCast<ICallable>();
if (toString != null)
{
var str = toString.Call(new JsValue(this), Arguments.Empty);
var str = toString.Call(JsValue, Arguments.Empty);
if (str.IsPrimitive())
{
return str;
Expand Down
2 changes: 1 addition & 1 deletion Jint/Runtime/Environments/ObjectEnvironmentRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public override JsValue ImplicitThisValue()
{
if (_provideThis)
{
return new JsValue(_bindingObject);
return _bindingObject.JsValue;
}

return Undefined.Instance;
Expand Down

0 comments on commit aaf42cc

Please sign in to comment.