Skip to content

Commit e4bbd39

Browse files
authored
Optimize typedarray casting (#5037)
# Walkthrough: TypedArray Optimization via Direct Casting This optimization improves the performance of creating a [TypedArray](file:///home/mayank/boa/core/engine/src/builtins/typed_array/mod.rs#40-44) from another [TypedArray](file:///home/mayank/boa/core/engine/src/builtins/typed_array/mod.rs#40-44) of the same `ContentType`. By implementing a direct [cast](file:///home/mayank/boa/core/engine/src/builtins/typed_array/mod.rs#580-732) method on `TypedArrayElement`, we avoid unnecessary conversions to and from `JsValue`. ## Changes Made ### [Core Engine] #### [mod.rs](file:///home/mayank/boa/core/engine/src/builtins/typed_array/mod.rs) - Implemented `TypedArrayElement::cast(self, target: TypedArrayKind) -> Self`. - This method handles direct numeric casting and clamping (for [Uint8Clamped](file:///home/mayank/boa/core/engine/src/builtins/typed_array/mod.rs#245-246)) between all non-BigInt and BigInt types respectively. - It panics if attempting to cast between different content types (BigInt vs Number), which is consistent with the ECMAScript specification requirements in [initialize_from_typed_array](file:///home/mayank/boa/core/engine/src/builtins/typed_array/builtin.rs#2858-3008). #### [builtin.rs](file:///home/mayank/boa/core/engine/src/builtins/typed_array/builtin.rs) - Updated [initialize_from_typed_array](file:///home/mayank/boa/core/engine/src/builtins/typed_array/builtin.rs#2858-3008) to use `value.cast(element_type)` instead of converting through `JsValue`. - Removed the `TODO` comment: "TODO: cast between types instead of converting to `JsValue`." ## Verification Results ### Automated Tests Successfully ran the existing [TypedArray](file:///home/mayank/boa/core/engine/src/builtins/typed_array/mod.rs#40-44) tests in `boa_engine`: ```bash cargo test --package boa_engine --lib builtins::typed_array::tests ``` **Results:** ``` running 12 tests test builtins::typed_array::tests::float32_precision_behavior ... ok test builtins::typed_array::tests::float32array_nan_behavior ... ok test builtins::typed_array::tests::float32array_storage ... ok test builtins::typed_array::tests::typedarray_prototype_set ... ok test builtins::typed_array::tests::typedarray_prototype_fill ... ok test builtins::typed_array::tests::typedarray_prototype_subarray_shared_memory ... ok test builtins::typed_array::tests::uint8array_constructor_from_array ... ok test builtins::typed_array::tests::uint8array_constructor_from_array_buffer ... ok test builtins::typed_array::tests::uint8array_constructor_length ... ok test builtins::typed_array::tests::uint8array_numeric_coercion ... ok test builtins::typed_array::tests::uint8array_out_of_bounds_behavior ... ok test builtins::typed_array::tests::uint8array_read_write_semantics ... ok test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 968 filtered out; finished in 0.09s ``` All relevant tests passed, ensuring no regressions in typed array constructor behavior or standard operations.
1 parent 1d0d8cb commit e4bbd39

File tree

3 files changed

+138
-7
lines changed

3 files changed

+138
-7
lines changed

core/engine/src/builtins/typed_array/builtin.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2963,12 +2963,7 @@ impl BuiltinTypedArray {
29632963
.get_value(src_type, Ordering::Relaxed)
29642964
};
29652965

2966-
let value = JsValue::from(value);
2967-
2968-
// TODO: cast between types instead of converting to `JsValue`.
2969-
let value = element_type
2970-
.get_element(&value, context)
2971-
.js_expect("value must be bigint or float")?;
2966+
let value = value.cast(element_type);
29722967

29732968
// ii. Perform SetValueInBuffer(data, targetByteIndex, elementType, value, true, unordered).
29742969
// SAFETY: The newly created buffer has at least `element_size * element_length`

core/engine/src/builtins/typed_array/mod.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,41 @@ impl TypedArrayKind {
533533
TypedArrayKind::Float64 => value.to_number(context).map(TypedArrayElement::Float64),
534534
}
535535
}
536+
537+
/// Convert `value` into the typed array element corresponding to this `TypedArrayKind`,
538+
/// assuming the `ContentType` of this kind is `Number`.
539+
pub(crate) fn to_element_f64(self, value: f64) -> TypedArrayElement {
540+
match self {
541+
TypedArrayKind::Int8 => TypedArrayElement::Int8(value as i8),
542+
TypedArrayKind::Uint8 => TypedArrayElement::Uint8(value as u8),
543+
TypedArrayKind::Uint8Clamped => {
544+
TypedArrayElement::Uint8Clamped(ClampedU8(value.clamp(0.0, 255.0).round() as u8))
545+
}
546+
TypedArrayKind::Int16 => TypedArrayElement::Int16(value as i16),
547+
TypedArrayKind::Uint16 => TypedArrayElement::Uint16(value as u16),
548+
TypedArrayKind::Int32 => TypedArrayElement::Int32(value as i32),
549+
TypedArrayKind::Uint32 => TypedArrayElement::Uint32(value as u32),
550+
#[cfg(feature = "float16")]
551+
TypedArrayKind::Float16 => {
552+
TypedArrayElement::Float16(Float16(float16::f16::from_f64(value)))
553+
}
554+
TypedArrayKind::Float32 => TypedArrayElement::Float32(value as f32),
555+
TypedArrayKind::Float64 => TypedArrayElement::Float64(value),
556+
TypedArrayKind::BigInt64 | TypedArrayKind::BigUint64 => {
557+
panic!("cannot convert f64 to BigInt typed array element")
558+
}
559+
}
560+
}
561+
562+
/// Convert `value` into the typed array element corresponding to this `TypedArrayKind`,
563+
/// assuming the `ContentType` of this kind is `BigInt`.
564+
pub(crate) fn to_element_i64(self, value: i64) -> TypedArrayElement {
565+
match self {
566+
TypedArrayKind::BigInt64 => TypedArrayElement::BigInt64(value),
567+
TypedArrayKind::BigUint64 => TypedArrayElement::BigUint64(value as u64),
568+
_ => panic!("cannot convert i64 to Number typed array element"),
569+
}
570+
}
536571
}
537572

538573
/// An element of a certain `TypedArray` kind.
@@ -576,6 +611,55 @@ impl TypedArrayElement {
576611
TypedArrayElement::Float64(num) => num.to_bits(),
577612
}
578613
}
614+
615+
/// Gets the `f64` representation of this `TypedArrayElement`.
616+
///
617+
/// # Panics
618+
///
619+
/// Panics if the `ContentType` of `self` is not `Number`.
620+
pub(crate) fn as_f64(self) -> f64 {
621+
match self {
622+
TypedArrayElement::Int8(v) => v.into(),
623+
TypedArrayElement::Uint8(v) => v.into(),
624+
TypedArrayElement::Uint8Clamped(v) => v.0.into(),
625+
TypedArrayElement::Int16(v) => v.into(),
626+
TypedArrayElement::Uint16(v) => v.into(),
627+
TypedArrayElement::Int32(v) => v.into(),
628+
TypedArrayElement::Uint32(v) => v.into(),
629+
#[cfg(feature = "float16")]
630+
TypedArrayElement::Float16(v) => v.0.to_f64(),
631+
TypedArrayElement::Float32(v) => v.into(),
632+
TypedArrayElement::Float64(v) => v,
633+
TypedArrayElement::BigInt64(_) | TypedArrayElement::BigUint64(_) => {
634+
panic!("cannot convert BigInt typed array element to f64")
635+
}
636+
}
637+
}
638+
639+
/// Gets the `i64` representation of this `TypedArrayElement`.
640+
///
641+
/// # Panics
642+
///
643+
/// Panics if the `ContentType` of `self` is not `BigInt`.
644+
pub(crate) fn as_i64(self) -> i64 {
645+
match self {
646+
TypedArrayElement::BigInt64(v) => v,
647+
TypedArrayElement::BigUint64(v) => v as i64,
648+
_ => panic!("cannot convert Number typed array element to i64"),
649+
}
650+
}
651+
652+
/// Casts this `TypedArrayElement` into another `TypedArrayKind`.
653+
///
654+
/// # Panics
655+
///
656+
/// Panics if the `ContentType` of `self` and `target` don't match.
657+
pub(crate) fn cast(self, target: TypedArrayKind) -> Self {
658+
match target.content_type() {
659+
ContentType::Number => target.to_element_f64(self.as_f64()),
660+
ContentType::BigInt => target.to_element_i64(self.as_i64()),
661+
}
662+
}
579663
}
580664

581665
impl From<i8> for TypedArrayElement {

core/engine/src/builtins/typed_array/tests.rs

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{TestAction, run_test_actions};
1+
use crate::{JsNativeErrorKind, TestAction, run_test_actions};
22

33
#[test]
44
fn uint8array_constructor_length() {
@@ -109,3 +109,55 @@ fn typedarray_prototype_subarray_shared_memory() {
109109
TestAction::assert_eq("b[0]", 99),
110110
]);
111111
}
112+
113+
#[test]
114+
fn typedarray_conversion_number() {
115+
run_test_actions([
116+
TestAction::run("let a = new Int8Array([1, -1, 127]);"),
117+
TestAction::run("let b = new Float64Array(a);"),
118+
TestAction::assert_eq("b.length", 3),
119+
TestAction::assert_eq("b[0]", 1),
120+
TestAction::assert_eq("b[1]", -1),
121+
TestAction::assert_eq("b[2]", 127),
122+
]);
123+
}
124+
125+
#[test]
126+
fn typedarray_conversion_bigint() {
127+
run_test_actions([
128+
TestAction::run("let a = new BigInt64Array([1n, -1n]);"),
129+
TestAction::run("let b = new BigUint64Array(a);"),
130+
TestAction::assert_eq("b.length", 2),
131+
TestAction::assert("b[0] === 1n"),
132+
TestAction::assert("b[1] === 0xffffffffffffffffn"),
133+
]);
134+
}
135+
136+
#[test]
137+
fn typedarray_conversion_clamped() {
138+
run_test_actions([
139+
TestAction::run("let a = new Float64Array([255.5, 256.1, -0.5]);"),
140+
TestAction::run("let b = new Uint8ClampedArray(a);"),
141+
TestAction::assert_eq("b[0]", 255),
142+
TestAction::assert_eq("b[1]", 255),
143+
TestAction::assert_eq("b[2]", 0),
144+
]);
145+
}
146+
147+
#[test]
148+
fn typedarray_conversion_mismatch_throws() {
149+
run_test_actions([
150+
TestAction::run("let a = new Int8Array([1]);"),
151+
TestAction::assert_native_error(
152+
"new BigInt64Array(a)",
153+
JsNativeErrorKind::Type,
154+
"Cannot initialize typed array from different content type",
155+
),
156+
TestAction::run("let b = new BigInt64Array([1n]);"),
157+
TestAction::assert_native_error(
158+
"new Int8Array(b)",
159+
JsNativeErrorKind::Type,
160+
"Cannot initialize typed array from different content type",
161+
),
162+
]);
163+
}

0 commit comments

Comments
 (0)