Skip to content

Commit

Permalink
LibJS: Add support for Float16Array
Browse files Browse the repository at this point in the history
Implements TC39 stage three proposal for Float16Arrays:
https://tc39.es/proposal-float16array
  • Loading branch information
rmg-x committed Nov 10, 2024
1 parent 9cc4c6c commit f6b1f40
Show file tree
Hide file tree
Showing 49 changed files with 133 additions and 8 deletions.
1 change: 1 addition & 0 deletions Libraries/LibJS/Forward.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
__JS_ENUMERATE(Int16Array, int16_array, Int16ArrayPrototype, Int16ArrayConstructor, i16) \
__JS_ENUMERATE(Int32Array, int32_array, Int32ArrayPrototype, Int32ArrayConstructor, i32) \
__JS_ENUMERATE(BigInt64Array, big_int64_array, BigInt64ArrayPrototype, BigInt64ArrayConstructor, i64) \
__JS_ENUMERATE(Float16Array, float16_array, Float16ArrayPrototype, Float16ArrayConstructor, f16) \
__JS_ENUMERATE(Float32Array, float32_array, Float32ArrayPrototype, Float32ArrayConstructor, float) \
__JS_ENUMERATE(Float64Array, float64_array, Float64ArrayPrototype, Float64ArrayConstructor, double)

Expand Down
37 changes: 30 additions & 7 deletions Libraries/LibJS/Runtime/ArrayBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,12 @@ inline size_t array_buffer_byte_length(ArrayBuffer const& array_buffer, ArrayBuf
}

// 25.1.3.14 RawBytesToNumeric ( type, rawBytes, isLittleEndian ), https://tc39.es/ecma262/#sec-rawbytestonumeric
// 5 RawBytesToNumeric ( type, rawBytes, isLittleEndian ), https://tc39.es/proposal-float16array/#sec-rawbytestonumeric
template<typename T>
static Value raw_bytes_to_numeric(VM& vm, Bytes raw_value, bool is_little_endian)
{
// 1. Let elementSize be the Element Size value specified in Table 70 for Element Type type.
// NOTE: Used in step 6, but not needed with our implementation of that step.
// NOTE: Used in step 7, but not needed with our implementation of that step.

// 2. If isLittleEndian is false, reverse the order of the elements of rawBytes.
if (!is_little_endian) {
Expand All @@ -184,8 +185,23 @@ static Value raw_bytes_to_numeric(VM& vm, Bytes raw_value, bool is_little_endian
swap(raw_value[i], raw_value[raw_value.size() - 1 - i]);
}

// 3. If type is Float32, then
using UnderlyingBufferDataType = Conditional<IsSame<ClampedU8, T>, u8, T>;

// 3. If type is Float16, then
if constexpr (IsSame<UnderlyingBufferDataType, f16>) {
// a. Let value be the byte elements of rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary16 value.
f16 value;
raw_value.copy_to({ &value, sizeof(f16) });

// b. If value is an IEEE 754-2019 binary16 NaN value, return the NaN Number value.
if (isnan(static_cast<double>(value)))
return js_nan();

// c. Return the Number value that corresponds to value.
return Value(value);
}

// 4. If type is Float32, then
if constexpr (IsSame<UnderlyingBufferDataType, float>) {
// a. Let value be the byte elements of rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary32 value.
float value;
Expand All @@ -199,7 +215,7 @@ static Value raw_bytes_to_numeric(VM& vm, Bytes raw_value, bool is_little_endian
return Value(value);
}

// 4. If type is Float64, then
// 5. If type is Float64, then
if constexpr (IsSame<UnderlyingBufferDataType, double>) {
// a. Let value be the byte elements of rawBytes concatenated and interpreted as a little-endian bit string encoding of an IEEE 754-2019 binary64 value.
double value;
Expand All @@ -217,16 +233,16 @@ static Value raw_bytes_to_numeric(VM& vm, Bytes raw_value, bool is_little_endian
if constexpr (!IsIntegral<UnderlyingBufferDataType>)
VERIFY_NOT_REACHED();

// 5. If IsUnsignedElementType(type) is true, then
// 6. If IsUnsignedElementType(type) is true, then
// a. Let intValue be the byte elements of rawBytes concatenated and interpreted as a bit string encoding of an unsigned little-endian binary number.
// 6. Else,
// 7. Else,
// a. Let intValue be the byte elements of rawBytes concatenated and interpreted as a bit string encoding of a binary little-endian two's complement number of bit length elementSize × 8.
//
// NOTE: The signed/unsigned logic above is implemented in step 7 by the IsSigned<> check, and in step 8 by JS::Value constructor overloads.
UnderlyingBufferDataType int_value = 0;
raw_value.copy_to({ &int_value, sizeof(UnderlyingBufferDataType) });

// 7. If IsBigIntElementType(type) is true, return the BigInt value that corresponds to intValue.
// 8. If IsBigIntElementType(type) is true, return the BigInt value that corresponds to intValue.
if constexpr (sizeof(UnderlyingBufferDataType) == 8) {
if constexpr (IsSigned<UnderlyingBufferDataType>) {
static_assert(IsSame<UnderlyingBufferDataType, i64>);
Expand All @@ -236,7 +252,7 @@ static Value raw_bytes_to_numeric(VM& vm, Bytes raw_value, bool is_little_endian
return BigInt::create(vm, Crypto::SignedBigInteger { Crypto::UnsignedBigInteger { int_value } });
}
}
// 8. Otherwise, return the Number value that corresponds to intValue.
// 9. Otherwise, return the Number value that corresponds to intValue.
else {
return Value(int_value);
}
Expand Down Expand Up @@ -289,6 +305,7 @@ Value ArrayBuffer::get_value(size_t byte_index, [[maybe_unused]] bool is_typed_a
}

// 25.1.3.17 NumericToRawBytes ( type, value, isLittleEndian ), https://tc39.es/ecma262/#sec-numerictorawbytes
// 6 NumericToRawBytes ( type, value, isLittleEndian ), https://tc39.es/proposal-float16array/#sec-numerictorawbytes
template<typename T>
static void numeric_to_raw_bytes(VM& vm, Value value, bool is_little_endian, Bytes raw_bytes)
{
Expand All @@ -302,6 +319,12 @@ static void numeric_to_raw_bytes(VM& vm, Value value, bool is_little_endian, Byt
for (size_t i = 0; i < sizeof(UnderlyingBufferDataType) / 2; ++i)
swap(raw_bytes[i], raw_bytes[sizeof(UnderlyingBufferDataType) - 1 - i]);
};
if constexpr (IsSame<UnderlyingBufferDataType, f16>) {
auto raw_value = static_cast<f16>(MUST(value.to_double(vm)));
ReadonlyBytes { &raw_value, sizeof(f16) }.copy_to(raw_bytes);
flip_if_needed();
return;
}
if constexpr (IsSame<UnderlyingBufferDataType, float>) {
float raw_value = MUST(value.to_double(vm));
ReadonlyBytes { &raw_value, sizeof(float) }.copy_to(raw_bytes);
Expand Down
3 changes: 3 additions & 0 deletions Libraries/LibJS/Runtime/CommonPropertyNames.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ namespace JS {
P(fromEpochSeconds) \
P(fromHex) \
P(fround) \
P(f16round) \
P(gc) \
P(get) \
P(getBigInt64) \
Expand All @@ -232,6 +233,7 @@ namespace JS {
P(getCollations) \
P(getDate) \
P(getDay) \
P(getFloat16) \
P(getFloat32) \
P(getFloat64) \
P(getFullYear) \
Expand Down Expand Up @@ -464,6 +466,7 @@ namespace JS {
P(setBigInt64) \
P(setBigUint64) \
P(setDate) \
P(setFloat16) \
P(setFloat32) \
P(setFloat64) \
P(setFromBase64) \
Expand Down
20 changes: 20 additions & 0 deletions Libraries/LibJS/Runtime/DataViewPrototype.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ void DataViewPrototype::initialize(Realm& realm)

define_native_function(realm, vm.names.getBigInt64, get_big_int_64, 1, attr);
define_native_function(realm, vm.names.getBigUint64, get_big_uint_64, 1, attr);
define_native_function(realm, vm.names.getFloat16, get_float_16, 1, attr);
define_native_function(realm, vm.names.getFloat32, get_float_32, 1, attr);
define_native_function(realm, vm.names.getFloat64, get_float_64, 1, attr);
define_native_function(realm, vm.names.getInt8, get_int_8, 1, attr);
Expand All @@ -36,6 +37,7 @@ void DataViewPrototype::initialize(Realm& realm)
define_native_function(realm, vm.names.getUint32, get_uint_32, 1, attr);
define_native_function(realm, vm.names.setBigInt64, set_big_int_64, 2, attr);
define_native_function(realm, vm.names.setBigUint64, set_big_uint_64, 2, attr);
define_native_function(realm, vm.names.setFloat16, set_float_16, 2, attr);
define_native_function(realm, vm.names.setFloat32, set_float_32, 2, attr);
define_native_function(realm, vm.names.setFloat64, set_float_64, 2, attr);
define_native_function(realm, vm.names.setInt8, set_int_8, 2, attr);
Expand Down Expand Up @@ -236,6 +238,15 @@ JS_DEFINE_NATIVE_FUNCTION(DataViewPrototype::get_big_uint_64)
return get_view_value<u64>(vm, vm.argument(0), vm.argument(1));
}

// 7.1 DataView.prototype.getFloat16 ( byteOffset [ , littleEndian ] ), https://tc39.es/proposal-float16array/#sec-dataview.prototype.getfloat16
JS_DEFINE_NATIVE_FUNCTION(DataViewPrototype::get_float_16)
{
// 1. Let v be the this value.
// 2. If littleEndian is not present, set littleEndian to false.
// 3. Return ? GetViewValue(v, byteOffset, littleEndian, Float16).
return get_view_value<f16>(vm, vm.argument(0), vm.argument(1));
}

// 25.3.4.7 DataView.prototype.getFloat32 ( byteOffset [ , littleEndian ] ), https://tc39.es/ecma262/#sec-dataview.prototype.getfloat32
JS_DEFINE_NATIVE_FUNCTION(DataViewPrototype::get_float_32)
{
Expand Down Expand Up @@ -322,6 +333,15 @@ JS_DEFINE_NATIVE_FUNCTION(DataViewPrototype::set_big_uint_64)
return set_view_value<u64>(vm, vm.argument(0), vm.argument(2), vm.argument(1));
}

// 7.2 DataView.prototype.setFloat16 ( byteOffset, value [ , littleEndian ] ), https://tc39.es/proposal-float16array/#sec-dataview.prototype.setfloat16
JS_DEFINE_NATIVE_FUNCTION(DataViewPrototype::set_float_16)
{
// 1. Let v be the this value.
// 2. If littleEndian is not present, set littleEndian to false.
// 3. Return ? SetViewValue(v, byteOffset, littleEndian, Float16, value).
return set_view_value<f16>(vm, vm.argument(0), vm.argument(2), vm.argument(1));
}

// 25.3.4.17 DataView.prototype.setFloat32 ( byteOffset, value [ , littleEndian ] ), https://tc39.es/ecma262/#sec-dataview.prototype.setfloat32
JS_DEFINE_NATIVE_FUNCTION(DataViewPrototype::set_float_32)
{
Expand Down
2 changes: 2 additions & 0 deletions Libraries/LibJS/Runtime/DataViewPrototype.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class DataViewPrototype final : public PrototypeObject<DataViewPrototype, DataVi

JS_DECLARE_NATIVE_FUNCTION(get_big_int_64);
JS_DECLARE_NATIVE_FUNCTION(get_big_uint_64);
JS_DECLARE_NATIVE_FUNCTION(get_float_16);
JS_DECLARE_NATIVE_FUNCTION(get_float_32);
JS_DECLARE_NATIVE_FUNCTION(get_float_64);
JS_DECLARE_NATIVE_FUNCTION(get_int_8);
Expand All @@ -34,6 +35,7 @@ class DataViewPrototype final : public PrototypeObject<DataViewPrototype, DataVi
JS_DECLARE_NATIVE_FUNCTION(get_uint_32);
JS_DECLARE_NATIVE_FUNCTION(set_big_int_64);
JS_DECLARE_NATIVE_FUNCTION(set_big_uint_64);
JS_DECLARE_NATIVE_FUNCTION(set_float_16);
JS_DECLARE_NATIVE_FUNCTION(set_float_32);
JS_DECLARE_NATIVE_FUNCTION(set_float_64);
JS_DECLARE_NATIVE_FUNCTION(set_int_8);
Expand Down
1 change: 1 addition & 0 deletions Libraries/LibJS/Runtime/GlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ void set_default_global_bindings(Realm& realm)
global.define_intrinsic_accessor(vm.names.Error, attr, [](auto& realm) -> Value { return realm.intrinsics().error_constructor(); });
global.define_intrinsic_accessor(vm.names.EvalError, attr, [](auto& realm) -> Value { return realm.intrinsics().eval_error_constructor(); });
global.define_intrinsic_accessor(vm.names.FinalizationRegistry, attr, [](auto& realm) -> Value { return realm.intrinsics().finalization_registry_constructor(); });
global.define_intrinsic_accessor(vm.names.Float16Array, attr, [](auto& realm) -> Value { return realm.intrinsics().float16_array_constructor(); });
global.define_intrinsic_accessor(vm.names.Float32Array, attr, [](auto& realm) -> Value { return realm.intrinsics().float32_array_constructor(); });
global.define_intrinsic_accessor(vm.names.Float64Array, attr, [](auto& realm) -> Value { return realm.intrinsics().float64_array_constructor(); });
global.define_intrinsic_accessor(vm.names.Function, attr, [](auto& realm) -> Value { return realm.intrinsics().function_constructor(); });
Expand Down
21 changes: 21 additions & 0 deletions Libraries/LibJS/Runtime/MathObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ void MathObject::initialize(Realm& realm)
define_native_function(realm, vm.names.cbrt, cbrt, 1, attr);
define_native_function(realm, vm.names.atan2, atan2, 2, attr);
define_native_function(realm, vm.names.fround, fround, 1, attr);
define_native_function(realm, vm.names.f16round, f16round, 1, attr);
define_native_function(realm, vm.names.hypot, hypot, 2, attr);
define_native_function(realm, vm.names.imul, imul, 2, attr);
define_native_function(realm, vm.names.log, log, 1, attr, Bytecode::Builtin::MathLog);
Expand Down Expand Up @@ -513,6 +514,26 @@ JS_DEFINE_NATIVE_FUNCTION(MathObject::fround)
return Value((float)number.as_double());
}

// 3.1 Math.f16round ( x ), https://tc39.es/proposal-float16array/#sec-math.f16round
JS_DEFINE_NATIVE_FUNCTION(MathObject::f16round)
{
// 1. Let n be ? ToNumber(x).
auto number = TRY(vm.argument(0).to_number(vm));

// 2. If n is NaN, return NaN.
if (number.is_nan())
return js_nan();

// 3. If n is one of +0𝔽, -0𝔽, +∞𝔽, or -∞𝔽, return n.
if (number.as_double() == 0 || number.is_infinity())
return number;

// 4. Let n16 be the result of converting n to IEEE 754-2019 binary16 format using roundTiesToEven mode.
// 5. Let n64 be the result of converting n16 to IEEE 754-2019 binary64 format.
// 6. Return the ECMAScript Number value corresponding to n64.
return Value(static_cast<f16>(number.as_double()));
}

// 21.3.2.18 Math.hypot ( ...args ), https://tc39.es/ecma262/#sec-math.hypot
JS_DEFINE_NATIVE_FUNCTION(MathObject::hypot)
{
Expand Down
1 change: 1 addition & 0 deletions Libraries/LibJS/Runtime/MathObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ class MathObject final : public Object {
JS_DECLARE_NATIVE_FUNCTION(cbrt);
JS_DECLARE_NATIVE_FUNCTION(atan2);
JS_DECLARE_NATIVE_FUNCTION(fround);
JS_DECLARE_NATIVE_FUNCTION(f16round);
JS_DECLARE_NATIVE_FUNCTION(hypot);
JS_DECLARE_NATIVE_FUNCTION(imul);
JS_DECLARE_NATIVE_FUNCTION(log);
Expand Down
5 changes: 5 additions & 0 deletions Libraries/LibJS/Runtime/Value.h
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ class Value {
}
}

explicit Value(f16 value)
: Value(static_cast<double>(value))
{
}

// NOTE: A couple of integral types are excluded here:
// - i32 has its own dedicated Value constructor
// - i64 cannot safely be cast to a double
Expand Down
8 changes: 8 additions & 0 deletions Libraries/LibJS/Tests/builtins/Math/Math.f16round.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
test("basic functionality", () => {
expect(Math.f16round).toHaveLength(1);

expect(Math.f16round(5.5)).toBe(5.5);
expect(Math.f16round(5.05)).toBe(5.05078125);
expect(Math.f16round(5)).toBe(5);
expect(Math.f16round(-5.05)).toBe(-5.05078125);
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ test("basic functionality", () => {
expect(Int16Array.BYTES_PER_ELEMENT).toBe(2);
expect(Int32Array.BYTES_PER_ELEMENT).toBe(4);
expect(BigInt64Array.BYTES_PER_ELEMENT).toBe(8);
expect(Float16Array.BYTES_PER_ELEMENT).toBe(2);
expect(Float32Array.BYTES_PER_ELEMENT).toBe(4);
expect(Float64Array.BYTES_PER_ELEMENT).toBe(8);
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
4 changes: 3 additions & 1 deletion Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down Expand Up @@ -185,8 +186,9 @@ test("typed array from TypedArray element cast", () => {
[0x100, 0xff],
[0x100, 0xff],
[0x100, 0xff],
[0x100, 0xff],
];
const u8Expected = [0xff, 0xff, 0xff, 0xff, -1, 0xff, 0xff, 0xff, 0xff];
const u8Expected = [0xff, 0xff, 0xff, 0xff, -1, 0xff, 0xff, 0xff, 0xff, 0xff];

TYPED_ARRAYS.forEach((T, i) => {
const newArrFromU32 = new T(u32Array);
Expand Down
1 change: 1 addition & 0 deletions Libraries/LibJS/Tests/builtins/TypedArray/TypedArray.of.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const TYPED_ARRAYS = [
{ array: Int16Array, expected: 2 },
{ array: Int32Array, expected: 4 },
{ array: BigInt64Array, expected: 8 },
{ array: Float16Array, expected: 2 },
{ array: Float32Array, expected: 4 },
{ array: Float64Array, expected: 8 },
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const TYPED_ARRAYS = [
Int16Array,
Int32Array,
BigInt64Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const TYPED_ARRAYS = [
{ array: Int16Array, expected: 6 },
{ array: Int32Array, expected: 12 },
{ array: BigInt64Array, expected: 24 },
{ array: Float16Array, expected: 6 },
{ array: Float32Array, expected: 12 },
{ array: Float64Array, expected: 24 },
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const TYPED_ARRAYS = [
Int16Array,
Int32Array,
BigInt64Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TYPED_ARRAYS = [
Int8Array,
Int16Array,
Int32Array,
Float16Array,
Float32Array,
Float64Array,
];
Expand Down
Loading

0 comments on commit f6b1f40

Please sign in to comment.