Skip to content

Commit

Permalink
Add support for Float16Array (#1840)
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma authored Apr 25, 2024
1 parent 15b1fcf commit 2754f1c
Show file tree
Hide file tree
Showing 14 changed files with 240 additions and 89 deletions.
1 change: 0 additions & 1 deletion Jint.Tests.Test262/Test262Harness.settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"async-iteration",
"Atomics",
"decorators",
"Float16Array",
"import-assertions",
"iterator-helpers",
"regexp-lookbehind",
Expand Down
20 changes: 18 additions & 2 deletions Jint.Tests/Runtime/TypedArrayInteropTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,31 @@ public void CanInteropWithBigUint64()
Assert.Equal(source, fromEngine.AsBigUint64Array());
}

#if NET6_0_OR_GREATER
[Fact]
public void CanInteropWithFloat16()
{
var engine = new Engine();
var source = new[] { (Half) 42, (Half) 12 };

engine.SetValue("testSubject", engine.Realm.Intrinsics.Float16Array.Construct(source));
ValidateCreatedTypeArray(engine, "Float16Array");

var fromEngine = engine.GetValue("testSubject");
Assert.True(fromEngine.IsFloat16Array());
Assert.Equal(source, fromEngine.AsFloat16Array());
}
#endif

[Fact]
public void CanInteropWithFloat32()
{
var engine = new Engine();
var source = new float[] { 42f, 12f };

engine.SetValue("testSubject", engine.Realm.Intrinsics.Float32Array.Construct(source));
ValidateCreatedTypeArray(engine, "Float32Array");

var fromEngine = engine.GetValue("testSubject");
Assert.True(fromEngine.IsFloat32Array());
Assert.Equal(source, fromEngine.AsFloat32Array());
Expand Down
4 changes: 4 additions & 0 deletions Jint/Jint.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
<DefineConstants>$(DefineConstants);SUPPORTS_SPAN_PARSE;SUPPORTS_WEAK_TABLE_ADD_OR_UPDATE;SUPPORTS_WEAK_TABLE_CLEAR</DefineConstants>
</PropertyGroup>

<PropertyGroup Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net6.0'))">
<DefineConstants>$(DefineConstants);SUPPORTS_HALF</DefineConstants>
</PropertyGroup>

<ItemGroup>
<None Include="../README.md" Pack="true" PackagePath="\"/>
</ItemGroup>
Expand Down
19 changes: 19 additions & 0 deletions Jint/JsValueExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,25 @@ public static ulong[] AsBigUint64Array(this JsValue value)
return ((JsTypedArray) value).ToNativeArray<ulong>();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsFloat16Array(this JsValue value)
{
return value is JsTypedArray { _arrayElementType: TypedArrayElementType.Float16 };
}

#if SUPPORTS_HALF
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Half[] AsFloat16Array(this JsValue value)
{
if (!value.IsFloat16Array())
{
ThrowWrongTypeException(value, "Float16Array");
}

return ((JsTypedArray) value).ToNativeArray<Half>();
}
#endif

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool IsFloat32Array(this JsValue value)
{
Expand Down
23 changes: 16 additions & 7 deletions Jint/Native/DataView/DataViewPrototype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ protected override void Initialize()
{
const PropertyFlag lengthFlags = PropertyFlag.Configurable;
const PropertyFlag propertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;
var properties = new PropertyDictionary(24, checkExistingKeys: false)
var properties = new PropertyDictionary(26, checkExistingKeys: false)
{
["buffer"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get buffer", Buffer, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
["byteLength"] = new GetSetPropertyDescriptor(new ClrFunction(_engine, "get byteLength", ByteLength, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
["byteOffset"] = new GetSetPropertyDescriptor(new ClrFunction(Engine, "get byteOffset", ByteOffset, 0, lengthFlags), Undefined, PropertyFlag.Configurable),
["constructor"] = new PropertyDescriptor(_constructor, PropertyFlag.NonEnumerable),
["getBigInt64"] = new PropertyDescriptor(new ClrFunction(Engine, "getBigInt64", GetBigInt64, length: 1, lengthFlags), propertyFlags),
["getBigUint64"] = new PropertyDescriptor(new ClrFunction(Engine, "getBigUint64", GetBigUint64, length: 1, lengthFlags), propertyFlags),
["getFloat16"] = new PropertyDescriptor(new ClrFunction(Engine, "getFloat16", GetFloat16, length: 1, lengthFlags), propertyFlags),
["getFloat32"] = new PropertyDescriptor(new ClrFunction(Engine, "getFloat32", GetFloat32, length: 1, lengthFlags), propertyFlags),
["getFloat64"] = new PropertyDescriptor(new ClrFunction(Engine, "getFloat64", GetFloat64, length: 1, lengthFlags), propertyFlags),
["getInt8"] = new PropertyDescriptor(new ClrFunction(Engine, "getInt8", GetInt8, length: 1, lengthFlags), propertyFlags),
Expand All @@ -49,6 +50,7 @@ protected override void Initialize()
["getUint32"] = new PropertyDescriptor(new ClrFunction(Engine, "getUint32", GetUint32, length: 1, lengthFlags), propertyFlags),
["setBigInt64"] = new PropertyDescriptor(new ClrFunction(Engine, "setBigInt64", SetBigInt64, length: 2, lengthFlags), propertyFlags),
["setBigUint64"] = new PropertyDescriptor(new ClrFunction(Engine, "setBigUint64", SetBigUint64, length: 2, lengthFlags), propertyFlags),
["setFloat16"] = new PropertyDescriptor(new ClrFunction(Engine, "setFloat16", SetFloat16, length: 2, lengthFlags), propertyFlags),
["setFloat32"] = new PropertyDescriptor(new ClrFunction(Engine, "setFloat32", SetFloat32, length: 2, lengthFlags), propertyFlags),
["setFloat64"] = new PropertyDescriptor(new ClrFunction(Engine, "setFloat64", SetFloat64, length: 2, lengthFlags), propertyFlags),
["setInt8"] = new PropertyDescriptor(new ClrFunction(Engine, "setInt8", SetInt8, length: 2, lengthFlags), propertyFlags),
Expand All @@ -60,10 +62,7 @@ protected override void Initialize()
};
SetProperties(properties);

var symbols = new SymbolDictionary(1)
{
[GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("DataView", PropertyFlag.Configurable)
};
var symbols = new SymbolDictionary(1) { [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("DataView", PropertyFlag.Configurable) };
SetSymbols(symbols);
}

Expand Down Expand Up @@ -137,6 +136,11 @@ private JsValue GetBigUint64(JsValue thisObject, JsValue[] arguments)
return GetViewValue(thisObject, arguments.At(0), arguments.At(1), TypedArrayElementType.BigUint64);
}

private JsValue GetFloat16(JsValue thisObject, JsValue[] arguments)
{
return GetViewValue(thisObject, arguments.At(0), arguments.At(1, JsBoolean.False), TypedArrayElementType.Float16);
}

private JsValue GetFloat32(JsValue thisObject, JsValue[] arguments)
{
return GetViewValue(thisObject, arguments.At(0), arguments.At(1, JsBoolean.False), TypedArrayElementType.Float32);
Expand Down Expand Up @@ -187,7 +191,12 @@ private JsValue SetBigUint64(JsValue thisObject, JsValue[] arguments)
return SetViewValue(thisObject, arguments.At(0), arguments.At(2), TypedArrayElementType.BigUint64, arguments.At(1));
}

private JsValue SetFloat32 (JsValue thisObject, JsValue[] arguments)
private JsValue SetFloat16(JsValue thisObject, JsValue[] arguments)
{
return SetViewValue(thisObject, arguments.At(0), arguments.At(2, JsBoolean.False), TypedArrayElementType.Float16, arguments.At(1));
}

private JsValue SetFloat32(JsValue thisObject, JsValue[] arguments)
{
return SetViewValue(thisObject, arguments.At(0), arguments.At(2, JsBoolean.False), TypedArrayElementType.Float32, arguments.At(1));
}
Expand All @@ -197,7 +206,7 @@ private JsValue SetFloat64(JsValue thisObject, JsValue[] arguments)
return SetViewValue(thisObject, arguments.At(0), arguments.At(2, JsBoolean.False), TypedArrayElementType.Float64, arguments.At(1));
}

private JsValue SetInt8 (JsValue thisObject, JsValue[] arguments)
private JsValue SetInt8(JsValue thisObject, JsValue[] arguments)
{
return SetViewValue(thisObject, arguments.At(0), JsBoolean.True, TypedArrayElementType.Int8, arguments.At(1));
}
Expand Down
4 changes: 3 additions & 1 deletion Jint/Native/Global/GlobalObject.Properties.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public partial class GlobalObject
private static readonly Key propertyError = "Error";
private static readonly Key propertyEvalError = "EvalError";
private static readonly Key propertyFinalizationRegistry = "FinalizationRegistry";
private static readonly Key propertyFloat16Array = "Float16Array";
private static readonly Key propertyFloat32Array = "Float32Array";
private static readonly Key propertyFloat64Array = "Float64Array";
private static readonly Key propertyFunction = "Function";
Expand Down Expand Up @@ -81,7 +82,7 @@ protected override void Initialize()
const PropertyFlag LengthFlags = PropertyFlag.Configurable;
const PropertyFlag PropertyFlags = PropertyFlag.Configurable | PropertyFlag.Writable;

var properties = new StringDictionarySlim<PropertyDescriptor>(64);
var properties = new StringDictionarySlim<PropertyDescriptor>(65);
properties.AddDangerous(propertyAggregateError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.AggregateError, PropertyFlags));
properties.AddDangerous(propertyArray, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Array, PropertyFlags));
properties.AddDangerous(propertyArrayBuffer, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.ArrayBuffer, PropertyFlags));
Expand All @@ -95,6 +96,7 @@ protected override void Initialize()
properties.AddDangerous(propertyError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Error, PropertyFlags));
properties.AddDangerous(propertyEvalError, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.EvalError, PropertyFlags));
properties.AddDangerous(propertyFinalizationRegistry, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.FinalizationRegistry, PropertyFlags));
properties.AddDangerous(propertyFloat16Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float16Array, PropertyFlags));
properties.AddDangerous(propertyFloat32Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float32Array, PropertyFlags));
properties.AddDangerous(propertyFloat64Array, new LazyPropertyDescriptor(this, static state => ((GlobalObject) state!)._realm.Intrinsics.Float64Array, PropertyFlags));
properties.AddDangerous(propertyFunction, new PropertyDescriptor(_realm.Intrinsics.Function, PropertyFlags));
Expand Down
67 changes: 46 additions & 21 deletions Jint/Native/JsArrayBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ internal TypedArrayValue RawBytesToNumeric(TypedArrayElementType type, int byteI
// 8 byte values require a little more at the moment
var needsReverse = !isLittleEndian
&& elementSize > 1
&& type is TypedArrayElementType.Float32 or TypedArrayElementType.Float64 or TypedArrayElementType.BigInt64 or TypedArrayElementType.BigUint64;
&& type is TypedArrayElementType.Float16 or TypedArrayElementType.Float32 or TypedArrayElementType.Float64 or TypedArrayElementType.BigInt64 or TypedArrayElementType.BigUint64;

if (needsReverse)
{
Expand All @@ -120,6 +120,26 @@ internal TypedArrayValue RawBytesToNumeric(TypedArrayElementType type, int byteI
rawBytes = _workBuffer;
}

if (type == TypedArrayElementType.Float16)
{
#if SUPPORTS_HALF
// rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary32 value.
var value = BitConverter.ToHalf(rawBytes, byteIndex);

// If value is an IEEE 754-2019 binary32 NaN value, return the NaN Number value.
if (Half.IsNaN(value))
{
return double.NaN;
}

return value;
#else
ExceptionHelper.ThrowNotImplementedException("Float16/Half type is not supported in this build");
return default;
#endif

}

if (type == TypedArrayElementType.Float32)
{
// rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary32 value.
Expand Down Expand Up @@ -155,25 +175,21 @@ internal TypedArrayValue RawBytesToNumeric(TypedArrayElementType type, int byteI

TypedArrayValue? arrayValue = type switch
{
TypedArrayElementType.Int8 => ((sbyte) rawBytes[byteIndex]),
TypedArrayElementType.Uint8 => (rawBytes[byteIndex]),
TypedArrayElementType.Uint8C =>(rawBytes[byteIndex]),
TypedArrayElementType.Int16 => (isLittleEndian
? (short) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8))
: (short) (rawBytes[byteIndex + 1] | (rawBytes[byteIndex] << 8))
),
TypedArrayElementType.Uint16 => (isLittleEndian
? (ushort) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8))
: (ushort) (rawBytes[byteIndex + 1] | (rawBytes[byteIndex] << 8))
),
TypedArrayElementType.Int32 => (isLittleEndian
? rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8) | (rawBytes[byteIndex + 2] << 16) | (rawBytes[byteIndex + 3] << 24)
: rawBytes[byteIndex + 3] | (rawBytes[byteIndex + 2] << 8) | (rawBytes[byteIndex + 1] << 16) | (rawBytes[byteIndex + 0] << 24)
),
TypedArrayElementType.Uint32 => (isLittleEndian
? (uint) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8) | (rawBytes[byteIndex + 2] << 16) | (rawBytes[byteIndex + 3] << 24))
: (uint) (rawBytes[byteIndex + 3] | (rawBytes[byteIndex + 2] << 8) | (rawBytes[byteIndex + 1] << 16) | (rawBytes[byteIndex] << 24))
),
TypedArrayElementType.Int8 => (sbyte) rawBytes[byteIndex],
TypedArrayElementType.Uint8 => rawBytes[byteIndex],
TypedArrayElementType.Uint8C =>rawBytes[byteIndex],
TypedArrayElementType.Int16 => isLittleEndian
? (short) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8))
: (short) (rawBytes[byteIndex + 1] | (rawBytes[byteIndex] << 8)),
TypedArrayElementType.Uint16 => isLittleEndian
? (ushort) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8))
: (ushort) (rawBytes[byteIndex + 1] | (rawBytes[byteIndex] << 8)),
TypedArrayElementType.Int32 => isLittleEndian
? rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8) | (rawBytes[byteIndex + 2] << 16) | (rawBytes[byteIndex + 3] << 24)
: rawBytes[byteIndex + 3] | (rawBytes[byteIndex + 2] << 8) | (rawBytes[byteIndex + 1] << 16) | (rawBytes[byteIndex + 0] << 24),
TypedArrayElementType.Uint32 => isLittleEndian
? (uint) (rawBytes[byteIndex] | (rawBytes[byteIndex + 1] << 8) | (rawBytes[byteIndex + 2] << 16) | (rawBytes[byteIndex + 3] << 24))
: (uint) (rawBytes[byteIndex + 3] | (rawBytes[byteIndex + 2] << 8) | (rawBytes[byteIndex + 1] << 16) | (rawBytes[byteIndex] << 24)),
_ => null
};

Expand Down Expand Up @@ -205,7 +221,16 @@ internal void SetValueInBuffer(
private byte[] NumericToRawBytes(TypedArrayElementType type, TypedArrayValue value, bool isLittleEndian)
{
byte[] rawBytes;
if (type == TypedArrayElementType.Float32)
if (type == TypedArrayElementType.Float16)
{
#if SUPPORTS_HALF
rawBytes = BitConverter.GetBytes((Half) value.DoubleValue);
#else
ExceptionHelper.ThrowNotImplementedException("Float16/Half type is not supported in this build");
return default!;
#endif
}
else if (type == TypedArrayElementType.Float32)
{
// Let rawBytes be a List whose elements are the 4 bytes that are the result of converting value to IEEE 754-2019 binary32 format using roundTiesToEven mode. If isLittleEndian is false, the bytes are arranged in big endian order. Otherwise, the bytes are arranged in little endian order. If value is NaN, rawBytes may be set to any implementation chosen IEEE 754-2019 binary32 format Not-a-Number encoding. An implementation must always choose the same encoding for each implementation distinguishable NaN value.
rawBytes = BitConverter.GetBytes((float) value.DoubleValue);
Expand Down
Loading

0 comments on commit 2754f1c

Please sign in to comment.