Skip to content

Commit a8726fb

Browse files
committed
Accelerate repeated sloppy global writes
1 parent 3bf3229 commit a8726fb

4 files changed

Lines changed: 121 additions & 0 deletions

File tree

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

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use crate::{
88
property::has_property,
99
property_value, property_value_key,
1010
symbol::unscopables_symbol,
11+
value::OwnDataPropertyWrite,
1112
};
1213

1314
use super::util::typeof_value;
@@ -1251,6 +1252,24 @@ impl Vm<'_> {
12511252
.locals
12521253
.get(slot)
12531254
.is_some_and(|local| local.sloppy_global_fallback);
1255+
if is_sloppy_global_fallback
1256+
&& self.locals.get(slot).is_some_and(Option::is_some)
1257+
&& let Some(Value::Object(global_this)) = self.env.global_this()
1258+
{
1259+
match global_this.write_existing_own_data_property(&name, &value) {
1260+
OwnDataPropertyWrite::Written => {
1261+
self.invalidate_array_prototype_cache(&name);
1262+
if !self.env.replace_existing_realm(&name, value.clone()) {
1263+
self.env.insert_realm(name.clone(), value.clone());
1264+
}
1265+
self.locals[slot] = Some(value);
1266+
self.sync_marked_dynamic_global(&name);
1267+
return Ok(());
1268+
}
1269+
OwnDataPropertyWrite::ReadOnly => return Ok(()),
1270+
OwnDataPropertyWrite::NeedsSlowPath => {}
1271+
}
1272+
}
12541273
match self.locals.get(slot) {
12551274
Some(Some(_)) => {
12561275
if self.local_slot_targets_non_writable_global(slot, &name) {

crates/qjs-runtime/src/function/env.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,6 +1065,24 @@ impl CallEnv {
10651065
self.realm.borrow_mut().insert(name, value)
10661066
}
10671067

1068+
/// Replaces an existing realm binding without allocating an owned lookup
1069+
/// key. Hot global stores use this after proving that the corresponding
1070+
/// global-object property is still an ordinary writable data property.
1071+
pub(crate) fn replace_existing_realm(&self, name: &str, value: Value) -> bool {
1072+
let mut bindings = self.realm.borrow_mut();
1073+
let Some(binding) = bindings.get_mut(name) else {
1074+
return false;
1075+
};
1076+
*binding = value.clone();
1077+
drop(bindings);
1078+
if let Some(cell) = self.realm.binding_cells.cell(name) {
1079+
cell.set(value.clone());
1080+
}
1081+
self.realm
1082+
.sync_dynamic_function_realm_binding(name, Some(&value));
1083+
true
1084+
}
1085+
10681086
/// Mirrors a data-property definition on this realm's global object into
10691087
/// the realm value table and any already-captured global cell.
10701088
pub(crate) fn sync_realm_global_object_property(&self, object: &ObjectRef, name: &str) {

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,24 @@ fn evaluates_sloppy_undeclared_global_assignment() {
10081008
),
10091009
Ok(Value::String("1:1:2".to_owned().into()))
10101010
);
1011+
assert_eq!(
1012+
eval(
1013+
"repeatedGlobal = 1; repeatedGlobal = 2; repeatedGlobal = 3; repeatedGlobal + this.repeatedGlobal;"
1014+
),
1015+
Ok(Value::Number(6.0))
1016+
);
1017+
assert_eq!(
1018+
eval(
1019+
"readonlyGlobal = 1; Object.defineProperty(this, 'readonlyGlobal', { writable: false }); readonlyGlobal = 2; readonlyGlobal + this.readonlyGlobal;"
1020+
),
1021+
Ok(Value::Number(2.0))
1022+
);
1023+
assert_eq!(
1024+
eval(
1025+
"deletedGlobal = 1; let removed = delete deletedGlobal; deletedGlobal = 3; removed && deletedGlobal === 3 && this.deletedGlobal === 3;"
1026+
),
1027+
Ok(Value::Boolean(true))
1028+
);
10111029
}
10121030

10131031
#[test]

tasks/T018-broad-performance.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,6 +2275,72 @@ evidence rather than a fixed-hardware claim. Broad raw SHA-256:
22752275
This unit generalizes across external string and crypto workloads, while B4
22762276
and B5 remain active.
22772277

2278+
The exact-SHA hosted workflows for
2279+
`3bf3229a87043bab78cbb775cbabaec4f2bc5298` all completed successfully. Hosted
2280+
broad retained 25/25 cases and measured 0.9955x base and 0.3531x QuickJS-NG
2281+
overall. Exact Test262 coverage remained 42,671 pass, one fail, and zero
2282+
timeout. Hosted external evidence confirmed the `charCodeAt` improvement:
2283+
`string-base64` fell from roughly 570 ms in the preceding artifact to 293 ms.
2284+
The suite diagnostics nevertheless still failed B5 at 12.485x QuickJS-NG for
2285+
5/5 JetStream, 7.317x for 6/14 Kraken, and 12.016x for 23/26 SunSpider. The
2286+
Kraken coverage variation came from timeout boundaries and is not accepted as
2287+
performance progress. Hosted broad raw/report SHA-256 are
2288+
`3637b5c5291435eb00c2bbd42f470d1cb8788e69631af0bb81aed481c5e8ea67`
2289+
and `3d81caf1949e5e5804a010f2a1a3dfc2564bcb27bf4bd2632f0a00ff3f0fd4f8`;
2290+
hosted external raw/report SHA-256 are
2291+
`97c4946e44132e7ad992c3c9b457ddfe7c07f6e3ee0be7d4041c24f74f51f7a4`
2292+
and `e516e6e262294dc09cc40d7c6f5635182232769cf2c3e3cf32bb67b9a06cf5d5`.
2293+
2294+
The forty-second v2 unit follows an external profile into the general sloppy
2295+
global write path. Repeated assignments to an already-created writable data
2296+
property previously cloned property descriptors and owned string keys, then
2297+
performed redundant global-object, realm-map, and captured-cell updates. The
2298+
VM now uses the existing borrowed-key data-property write operation and a
2299+
borrowed-key realm replacement after the first assignment establishes the
2300+
binding. Missing properties, accessors, read-only descriptors, module imports,
2301+
immutable bindings, deletion/recreation, and dynamic scope retain their full
2302+
paths. Tests cover repeated writes, global-object visibility, read-only
2303+
redefinition, and delete/recreate behavior. No workload name, source path,
2304+
iteration count, checksum, or expected value appears in the implementation.
2305+
2306+
Against base binary SHA-256
2307+
`4d6b4e3bf0626695baf98cbd69ebe8b4700045c63aa2f3a332558aea51186a67`,
2308+
candidate binary SHA-256
2309+
`8b566b3cb05d6570273b20248654403ba92af6288ecf487bd127c437b631a70b`
2310+
improved two independent external programs in eleven-block interleaved runs.
2311+
SunSpider `bitops-bitwise-and`, whose loop repeatedly updates one sloppy
2312+
global, measured 0.681459x base. `math-partial-sums` independently exercises
2313+
the same mechanism because its chained local initialization creates eight
2314+
sloppy globals that the inner numeric loop updates; it measured 0.450487x
2315+
base. Four unrelated SunSpider cases selected from the worst full-preview
2316+
movements reran between 0.989407x and 1.027093x base.
2317+
2318+
Complete one-block candidate and same-thermal-window base external previews
2319+
kept identical coverage at 5/5 JetStream, 10/14 Kraken, and 23/26 SunSpider.
2320+
Their common-case candidate/base suite diagnostics were 0.978390x, 0.950001x,
2321+
and 0.918161x. Candidate/QuickJS-NG still failed B5 at 9.523116x, 5.544422x,
2322+
and 6.543434x. Candidate external raw/report SHA-256 are
2323+
`636611c03d111cc6f9faa33cda7c12278a77cc5a8973b7c1b8141bd9fd214fbf`
2324+
and `79aa22a3612c17215ee1ea8ff1ade62b91f7ad72716467f51f9298282a717875`;
2325+
base raw/report SHA-256 are
2326+
`5a8693f1f7591acfd9d848fca473b0659fc567392609a9544882a8d23dfce7be`
2327+
and `163a3ec02518b110733375104ce6bf89c92b56c5d3ca464fe8607141ac23b0ee`.
2328+
2329+
The complete three-role broad diagnostic retained all 25 cases, 225 eligible
2330+
exact-checksum measurements, 600 passing linearity samples, and zero non-OK
2331+
records. Candidate/base was 1.003683x overall and candidate/QuickJS-NG was
2332+
0.194571x. Family candidate/base ratios ranged from 0.997989x for binding to
2333+
1.030763x for string. Eleven-block fixed-iteration reruns reduced the apparent
2334+
`string_slice` movement to 1.012958x; the six worst full-run cases ranged from
2335+
0.990570x to 1.021262x. Allocation still failed B4 at 1.145498x QuickJS-NG.
2336+
The strict analyzer correctly rejected the dirty, receipt-less development
2337+
binaries, so this is regression evidence rather than a fixed-hardware claim.
2338+
Broad raw SHA-256:
2339+
`3642bd71ef72428f9021db6f7569cee72da9f8376a1c3727da7b40a3af2f5969`.
2340+
This general global-binding optimization materially improves two unrelated
2341+
external sources while preserving the internal regression guard; B4 and B5
2342+
remain active.
2343+
22782344
## Historical Broad V1 Baseline
22792345

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

0 commit comments

Comments
 (0)