Skip to content

Commit e38b3bf

Browse files
committed
Speed ordinary construction property creation
1 parent 6954127 commit e38b3bf

5 files changed

Lines changed: 169 additions & 7 deletions

File tree

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1354,7 +1354,12 @@ impl<'a> Vm<'a> {
13541354
self.stack.push(value);
13551355
return Ok(());
13561356
}
1357-
OwnDataPropertyWrite::NeedsSlowPath => {}
1357+
OwnDataPropertyWrite::NeedsSlowPath => {
1358+
if self.try_create_ordinary_own_data_property(object, key, &value) {
1359+
self.stack.push(value);
1360+
return Ok(());
1361+
}
1362+
}
13581363
}
13591364
}
13601365
let mut env = self.current_env();

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

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,58 @@ impl Vm<'_> {
277277
}
278278
}
279279

280+
/// Creates a missing ordinary own string data property without cloning the
281+
/// call environment when the complete [[Set]] result is already known.
282+
/// Observable exotic behavior (accessors, Proxies, typed arrays, module
283+
/// namespaces, or non-extensible receivers) remains on the generic path.
284+
pub(super) fn try_create_ordinary_own_data_property(
285+
&self,
286+
object: &ObjectRef,
287+
key: &str,
288+
value: &Value,
289+
) -> bool {
290+
if symbol::is_symbol_primitive(object)
291+
|| crate::typed_array::is_typed_array_object(object)
292+
|| object.is_module_namespace_exotic()
293+
|| !object.is_extensible()
294+
|| !matches!(
295+
object.own_data_property_read(key),
296+
OwnDataPropertyRead::Missing
297+
)
298+
{
299+
return false;
300+
}
301+
302+
let mut current = object.prototype_slot();
303+
loop {
304+
match current {
305+
Some(crate::Prototype::Object(prototype)) => {
306+
if symbol::is_symbol_primitive(&prototype)
307+
|| crate::typed_array::is_typed_array_object(&prototype)
308+
|| prototype.is_module_namespace_exotic()
309+
{
310+
return false;
311+
}
312+
if let Some(property) = prototype.own_property(key) {
313+
if property.accessor || !property.writable {
314+
return false;
315+
}
316+
object.set(key.to_owned(), value.clone());
317+
return true;
318+
}
319+
current = prototype.prototype_slot();
320+
}
321+
Some(crate::Prototype::Function(_) | crate::Prototype::Proxy(_)) => {
322+
return false;
323+
}
324+
None => {
325+
object.set(key.to_owned(), value.clone());
326+
return true;
327+
}
328+
}
329+
}
330+
}
331+
280332
fn try_direct_get_symbol(&self, object: &Value, symbol: &ObjectRef) -> Option<Value> {
281333
match object {
282334
Value::Object(object) => match ordinary_chain_symbol_property(object, symbol) {

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ pub(crate) fn is_direct_leaf_function(callee: &Value) -> bool {
289289
function
290290
.bytecode
291291
.as_ref()
292-
.is_some_and(|bytecode| can_seed_direct_leaf_call(function, bytecode, false))
292+
.is_some_and(|bytecode| can_seed_direct_leaf_call(function, bytecode))
293293
}
294294

295295
fn class_constructor_call_error(function: &Function) -> RuntimeError {
@@ -664,7 +664,7 @@ fn direct_leaf_function_env<'a>(
664664
argument_values: &'a [Value],
665665
env: &CallEnv,
666666
) -> FunctionCallEnv<'a> {
667-
debug_assert!(can_seed_direct_leaf_call(function, bytecode, false));
667+
debug_assert!(can_seed_direct_leaf_call(function, bytecode));
668668
let mut frame_env = env.new_direct_leaf_function_frame();
669669
let direct_this_value = if bytecode.uses_lexical_this() {
670670
let this_env_storage = callee_this_realm_env(function, env);
@@ -703,7 +703,7 @@ fn function_env<'a>(
703703
env: &CallEnv,
704704
is_construct: bool,
705705
) -> FunctionCallEnv<'a> {
706-
let use_direct_call_slots = can_seed_direct_leaf_call(function, bytecode, is_construct);
706+
let use_direct_call_slots = can_seed_direct_leaf_call(function, bytecode);
707707
let lexical_this = received_upvalue_value(function, bytecode, "this");
708708
let lexical_field_initializer =
709709
received_upvalue_value(function, bytecode, FIELD_INITIALIZER_EVAL_BINDING);
@@ -872,9 +872,11 @@ fn function_env<'a>(
872872
}
873873
}
874874

875-
fn can_seed_direct_leaf_call(function: &Function, bytecode: &Bytecode, is_construct: bool) -> bool {
876-
!is_construct
877-
&& !function.lexical_this
875+
fn can_seed_direct_leaf_call(function: &Function, bytecode: &Bytecode) -> bool {
876+
// Ordinary constructors use the same slot-backed parameter and receiver
877+
// model as ordinary calls. Constructor-only state such as `new.target`
878+
// remains in the small compatibility frame installed by function_env.
879+
!function.lexical_this
878880
&& !function.lexical_arguments
879881
&& !function.is_generator
880882
&& !function.is_async

crates/qjs-runtime/src/tests/objects/assignment.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,49 @@ fn ordinary_set_honors_non_writable_data_properties() {
111111
);
112112
}
113113

114+
#[test]
115+
fn ordinary_missing_own_data_write_preserves_prototype_semantics() {
116+
assert_eq!(
117+
eval(
118+
"var prototype = { p: 1 };
119+
var object = Object.create(prototype);
120+
object.p = 2;
121+
var descriptor = Object.getOwnPropertyDescriptor(object, 'p');
122+
[object.p, prototype.p, descriptor.writable, descriptor.enumerable,
123+
descriptor.configurable].join(':');"
124+
),
125+
Ok(Value::String("2:1:true:true:true".to_owned().into()))
126+
);
127+
assert_eq!(
128+
eval(
129+
"var seen = 0;
130+
var prototype = { set p(value) { seen = value; } };
131+
var object = Object.create(prototype);
132+
object.p = 3;
133+
seen + ':' + object.hasOwnProperty('p');"
134+
),
135+
Ok(Value::String("3:false".to_owned().into()))
136+
);
137+
assert_eq!(
138+
eval(
139+
"var calls = 0;
140+
var prototype = new Proxy({}, { set() { calls += 1; return true; } });
141+
var object = Object.create(prototype);
142+
object.p = 4;
143+
calls + ':' + object.hasOwnProperty('p');"
144+
),
145+
Ok(Value::String("1:false".to_owned().into()))
146+
);
147+
assert_eq!(
148+
eval(
149+
"var object = Object.preventExtensions({});
150+
object.p = 5;
151+
object.hasOwnProperty('p');"
152+
),
153+
Ok(Value::Boolean(false))
154+
);
155+
}
156+
114157
#[test]
115158
fn ordinary_set_runs_setter_in_strict_mode() {
116159
// A successful accessor setter must not throw in strict mode.

tasks/T018-broad-performance.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1943,6 +1943,66 @@ Raw SHA-256:
19431943
`701c0bcb81998d84b6dec5cb3113c4df9c394e4cd36a67145d5e08becd41b76c`.
19441944
The next hosted receipt-bound artifact decides campaign progress.
19451945

1946+
Hosted run `29566653789` accepted the thirty-fifth unit at commit
1947+
`69541277`. CI run `29566653844` and full Test262 coverage run `29566855091`
1948+
also completed successfully. The receipt-bound broad artifact measured
1949+
0.984530x candidate/base and 0.362880x candidate/QuickJS-NG overall;
1950+
allocation remained the only failing broad family at 2.663908x. The external
1951+
artifact preserved 5/5 JetStream, 6/14 Kraken, and 23/26 SunSpider comparable
1952+
cases, with diagnostic ratios of 14.136785x, 8.704828x, and 15.858843x. The
1953+
unary change was therefore accepted as a general improvement, but all three
1954+
external suites still fail the <=1.00x B5 guard by a wide margin.
1955+
1956+
The thirty-sixth v2 unit targets ordinary construction and property creation,
1957+
not a benchmark identity. A profile of the pinned SunSpider
1958+
`access-binary-trees` port showed ordinary constructors repeatedly cloning and
1959+
writing back frame maps for parameters, `this`, and simple `this.x = value`
1960+
stores. Ordinary constructors now use the same slot-backed parameter and
1961+
receiver seed as semantically eligible ordinary calls; `new.target` stays in
1962+
the compatibility frame. A second general fast path creates a missing ordinary
1963+
own string data property only after proving that the receiver is extensible and
1964+
that its all-object prototype chain contains no accessor, read-only descriptor,
1965+
Proxy, typed array, module namespace, symbol primitive, or function prototype.
1966+
Every observable or exotic case remains on the full `OrdinarySet` path. The
1967+
implementation has no workload ID, source path, iteration count, or checksum
1968+
input.
1969+
1970+
Against clean base binary SHA-256
1971+
`2d45d24dddb5cd09afc6c72d5504a1c59de45204d9ce70aa0d5797edaac492e2`,
1972+
candidate binary SHA-256
1973+
`0fd0b797227d3e82086d49a81817d6178990b174a1f70f44ad21a736f42cec41`
1974+
reduced an eleven-block interleaved `access-binary-trees` warm median from
1975+
0.262770 s to 0.112107 s (0.426635x). A structurally independent 200,000-
1976+
property Point-construction diagnostic reduced its warm median from 0.505860 s
1977+
to 0.311988 s (0.616748x), confirming that the mechanism is not specific to
1978+
binary trees.
1979+
1980+
The complete local three-role broad diagnostic preserved all 25 exact-checksum
1981+
cases at 0.994993x candidate/base. Allocation moved 1.019465x and the worst
1982+
single case, `object_allocation`, moved 1.030024x; neither is a material
1983+
regression. The local candidate/QuickJS-NG diagnostic was 0.195824x overall,
1984+
with allocation still failing at 1.185220x. The strict report correctly rejects
1985+
these dirty, receipt-less development binaries, so the numbers are regression
1986+
diagnostics only. Raw SHA-256:
1987+
`1ff9cc3fef45df30875ccc402d90072d0629c959cbecb099bce66029cb9d2aae`.
1988+
1989+
Independent one-block full external A/B previews preserved identical coverage
1990+
at 5/5 JetStream, 8/14 Kraken, and 23/26 SunSpider. Across all 36 common
1991+
comparable cases, candidate/base was 0.966328x: seven cases improved by more
1992+
than 2%, 26 stayed within 2%, three moved between 2.19% and 2.43%, and no case
1993+
regressed by 25%. `access-binary-trees` reached 0.425861x; JetStream `cdjs`,
1994+
`hash-map`, and `stanford-crypto-aes` reached 0.806694x, 0.879076x, and
1995+
0.947559x. Candidate/QuickJS-NG suite diagnostics improved from
1996+
10.483653x/5.835611x/8.945211x to 9.685067x/5.827903x/8.667864x. Candidate raw
1997+
and report SHA-256 are
1998+
`eff0b411f9ef836a66722da8cf611115a5d802bbc25b44f7587f34940bdb8552`
1999+
and `7fef9228a7748ecea23fab85098d6c4993a326afd75a184b896c31809acf5d5d`;
2000+
base raw and report SHA-256 are
2001+
`10a588da704da9806f1c42fc9dda93a587092767021e8b9db917902f1df2e8c8`
2002+
and `8c9b2f0ab3c8f98ec6009f8a01ba6a93e4c2238220fa00ddff3ce9f5e9046264`.
2003+
These gains satisfy the no-overfitting regression boundary for this unit, but
2004+
the external <=1.00x target and broad allocation <=1.00x target remain open.
2005+
19462006
## Historical Broad V1 Baseline
19472007

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

0 commit comments

Comments
 (0)