Skip to content

Commit 07970f8

Browse files
committed
Accelerate primitive Math.pow calls
1 parent b3cb72b commit 07970f8

3 files changed

Lines changed: 90 additions & 0 deletions

File tree

crates/qjs-runtime/src/bytecode/vm_call.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ pub(super) fn try_fast_global_native_call(
388388
| NativeFunction::MathTrunc => {
389389
Ok(Value::Number(fast_primitive_unary_math(native, arguments)?))
390390
}
391+
NativeFunction::MathPow => Ok(Value::Number(fast_primitive_math_pow(arguments)?)),
391392
NativeFunction::MathRandom if arguments.is_empty() => crate::math::native_math_random(),
392393
NativeFunction::ArrayPrototypeIndexOf => Ok(crate::array::fast_dense_array_index_of(
393394
this_value, arguments, realm_env,
@@ -520,6 +521,17 @@ fn fast_primitive_unary_math(native: NativeFunction, arguments: &[Value]) -> Opt
520521
Some(result)
521522
}
522523

524+
fn fast_primitive_math_pow(arguments: &[Value]) -> Option<f64> {
525+
let primitive_number = |value: Option<&Value>| match value {
526+
Some(Value::Number(number)) => Some(*number),
527+
None | Some(Value::Undefined) => Some(f64::NAN),
528+
_ => None,
529+
};
530+
let base = primitive_number(arguments.first())?;
531+
let exponent = primitive_number(arguments.get(1))?;
532+
Some(crate::operations::number_exponentiate(base, exponent))
533+
}
534+
523535
fn fast_string_sequence_native(
524536
native: NativeFunction,
525537
this_value: &Value,

crates/qjs-runtime/src/tests/math.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,3 +289,19 @@ fn detached_unary_math_natives_preserve_primitive_and_coercion_semantics() {
289289
Ok(Value::Boolean(true))
290290
);
291291
}
292+
293+
#[test]
294+
fn detached_math_pow_preserves_primitive_and_coercion_semantics() {
295+
assert_eq!(
296+
eval(
297+
"(function () { var pow = Math.pow; return pow(2, 8, 99) === 256 && Number.isNaN(pow()) && pow(undefined, 0) === 1 && Number.isNaN(pow(1, Infinity)) && 1 / pow(-0, 3) === -Infinity; })();"
298+
),
299+
Ok(Value::Boolean(true))
300+
);
301+
assert_eq!(
302+
eval(
303+
"(function () { var pow = Math.pow; var conversions = []; var base = { valueOf: function () { conversions.push('base'); return 2; } }; var exponent = { valueOf: function () { conversions.push('exponent'); return 3; } }; return pow(base, exponent) === 8 && pow('4', 0.5) === 2 && conversions.join(',') === 'base,exponent'; })();"
304+
),
305+
Ok(Value::Boolean(true))
306+
);
307+
}

tasks/T018-broad-performance.md

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2147,6 +2147,68 @@ and `9966d978fc232aa2719937e5c851a3ae2b9920c446b9cc08aaea9ddcf30ec4c8`.
21472147
This is external generalization progress, not completion: the external suites
21482148
still lose every comparable case to QuickJS-NG and both B4 and B5 remain active.
21492149

2150+
The exact-SHA hosted evidence for `b3cb72b416891ae7aeae6054ab28eacee9dc4e9e`
2151+
kept the distinction between the internal guard and general engine speed
2152+
explicit. The 25-case broad preview was 0.9984x base and 0.3529x QuickJS-NG,
2153+
but the external preview remained 12.523x QuickJS-NG for 5/5 JetStream cases,
2154+
7.640x for 5/14 Kraken cases, and 13.056x for 23/26 SunSpider cases. Thus the
2155+
internal number is not completion evidence. CI, the Test262 coverage scan, and
2156+
the informational performance workflow all completed successfully; Test262
2157+
remained 42,671 pass, one fail, and zero timeout. Hosted broad raw/report
2158+
SHA-256 are
2159+
`7568681b9a15b3baa6cfe458cf9cab3c236facac667927d23cdada5d92cf19d8`
2160+
and `96c3a7a75d11ea65bfc6e197977f20b7f91b76ed636b93cd5c14333dfcece9e8`;
2161+
hosted external raw/report SHA-256 are
2162+
`2872b5c0b6c97ad9bb82784932d17599dc9d003c8dab271a3354c79af0e421e0`
2163+
and `acbc87b66b4559efdb3c13e6d7d7bcf14078479e69d34b16e084e5e76fce2bb7`.
2164+
2165+
The fortieth v2 unit continues from the external profile rather than adding an
2166+
internal benchmark case. After the unary Math fast path, the pinned SunSpider
2167+
`math-partial-sums` profile still spent substantial time constructing,
2168+
snapshotting, and applying caller environments for primitive `Math.pow`
2169+
calls. The native-call fast path now evaluates `Math.pow` directly only when
2170+
both consumed arguments are numbers or absent/undefined, reusing the engine's
2171+
shared Number exponentiation semantics. Strings, objects, BigInts, and every
2172+
observable conversion still take the complete native path. Focused tests cover
2173+
detached calls, ignored extra arguments, missing arguments, NaN and infinity,
2174+
negative zero, string conversion, object conversion order, and side effects.
2175+
The implementation contains no benchmark name, source path, iteration count,
2176+
or checksum input.
2177+
2178+
Against clean base binary SHA-256
2179+
`f34503c0863f42860031e786119f74222106bc3d532b579ff51881ded1a526e9`,
2180+
candidate binary SHA-256
2181+
`40c55d677023f3829b964df0e75db88f9651d1b2dc064f5a3d7c49ddefaea478`
2182+
reduced the nine-block interleaved `math-partial-sums` source median from
2183+
approximately 0.39 seconds to 0.23 seconds, or about 0.59x base. A complete
2184+
one-block external A/B retained identical coverage at 5/5 JetStream, 10/14
2185+
Kraken, and 23/26 SunSpider. Common-case suite candidate/base geometric means
2186+
were 0.997046x, 0.978466x, and 0.973802x; `math-partial-sums` was 0.623923x and
2187+
Kraken `imaging-darkroom`, another `Math.pow` user, was 0.836867x. The suite
2188+
candidate/QuickJS-NG diagnostics remained far from B5 at 9.591883x, 5.579897x,
2189+
and 7.254125x. Eleven-block interleaved source reruns rejected the three
2190+
apparent unrelated regressions: `string-base64` was 0.994426x,
2191+
`access-nbody` 0.999028x, and `3d-morph` 1.004505x base. External raw/report
2192+
SHA-256 are
2193+
`e66c81e96cc2d6f9acc7edebce6d4053838f8314a505c6ca75eed3367a40c180`
2194+
and `1a38a0f85ee76cbf9ff9daa44759c36dce77caf5b3381e38d58d8bd83b3aa6cf`.
2195+
2196+
The full local broad diagnostic had no failed sample, but the unchanged base
2197+
binary's `captured_read` measurements were all timer-limited, so only 24/25
2198+
cases were formally common and this run is not complete claim evidence. The
2199+
24-case candidate/base diagnostic was 1.004864x overall. An independent
2200+
seven-block rerun reduced every apparent unrelated movement below 2%:
2201+
`string_slice` 1.000394x, `object_allocation` 1.001747x,
2202+
`function_call_reordered` 1.002308x, `top_level_function_call` 0.999709x, and
2203+
`array_allocation` 1.011237x. `captured_read` raw medians were 5.431 versus
2204+
5.408 ns/op, although the base remained timer-limited. The complete
2205+
candidate/QuickJS-NG side was 0.196533x overall, while allocation still failed
2206+
B4 at 1.181221x. Full and focused-rerun broad raw SHA-256 are
2207+
`81b0c5fc575eda3f1d54382d0531862e9ecf2f9a945c1b177ff044d5acfd20e3`
2208+
and `0cbf9018be8b65466ccbff2b4903111ec5bcdae4ba4476412ee743d2ef2c52f0`.
2209+
This is a general native-dispatch improvement with external evidence, not a
2210+
benchmark-specific shortcut; B4 and B5 remain active.
2211+
21502212
## Historical Broad V1 Baseline
21512213

21522214
The first complete baseline was recorded on 2026-07-15 at commit

0 commit comments

Comments
 (0)