Skip to content

Commit c05ccec

Browse files
authored
perf(vm): ObjectCreateWithShape (#802)
1 parent 20f1a0d commit c05ccec

File tree

8 files changed

+225
-3
lines changed

8 files changed

+225
-3
lines changed

nova_vm/src/ecmascript/types/language/object.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,24 @@ impl<'a> OrdinaryObject<'a> {
339339
Self::create_object_internal(agent, shape, entries)
340340
}
341341

342+
pub(crate) fn create_object_with_shape_and_data_properties(
343+
agent: &mut Agent,
344+
shape: ObjectShape<'a>,
345+
values: &[Value<'a>],
346+
) -> Self {
347+
// SAFETY: Option<Value> uses a niche in Value enum at discriminant 0.
348+
let values = unsafe { core::mem::transmute::<&[Value<'a>], &[Option<Value<'a>>]>(values) };
349+
let ElementsVector {
350+
elements_index: values,
351+
cap,
352+
len,
353+
len_writable: extensible,
354+
} = agent.heap.elements.allocate_property_storage(values, None);
355+
agent
356+
.heap
357+
.create(ObjectHeapData::new(shape, values, cap, len, extensible))
358+
}
359+
342360
/// Creates a new "intrinsic" object. An intrinsic object owns its Object
343361
/// Shape uniquely and thus any changes to the object properties mutate the
344362
/// Shape directly.

nova_vm/src/engine/bytecode/bytecode_compiler.rs

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ mod template_literals;
1616
mod with_statement;
1717

1818
use super::{FunctionExpression, Instruction, SendableRef, executable::ArrowFunctionExpression};
19-
use crate::ecmascript::execution::agent::ExceptionType;
19+
use crate::ecmascript::{
20+
builtins::ordinary::shape::ObjectShape, execution::agent::ExceptionType, types::IntoObject,
21+
};
2022
#[cfg(feature = "regexp")]
2123
use crate::ecmascript::{
2224
syntax_directed_operations::{
@@ -436,8 +438,109 @@ impl<'s> CompileEvaluation<'s> for ast::Function<'s> {
436438
}
437439
}
438440

441+
fn create_object_with_shape<'s>(
442+
expr: &'s ast::ObjectExpression<'s>,
443+
ctx: &mut CompileContext<'_, 's, '_, '_>,
444+
) {
445+
let proto_prop = expr.properties.iter().find(|prop| {
446+
let ast::ObjectPropertyKind::ObjectProperty(prop) = prop else {
447+
unreachable!()
448+
};
449+
prop.key.is_specific_static_name("__proto__")
450+
&& prop.kind == ast::PropertyKind::Init
451+
&& !prop.shorthand
452+
});
453+
let prototype = if let Some(proto_prop) = proto_prop {
454+
let ast::ObjectPropertyKind::ObjectProperty(proto_prop) = proto_prop else {
455+
unreachable!()
456+
};
457+
if proto_prop.value.is_null() {
458+
None
459+
} else {
460+
Some(
461+
ctx.get_agent()
462+
.current_realm_record()
463+
.intrinsics()
464+
.object_prototype()
465+
.into_object(),
466+
)
467+
}
468+
} else {
469+
Some(
470+
ctx.get_agent()
471+
.current_realm_record()
472+
.intrinsics()
473+
.object_prototype()
474+
.into_object(),
475+
)
476+
};
477+
let mut shape = ObjectShape::get_shape_for_prototype(ctx.get_agent_mut(), prototype);
478+
for prop in expr.properties.iter() {
479+
let ast::ObjectPropertyKind::ObjectProperty(prop) = prop else {
480+
unreachable!()
481+
};
482+
if !prop.shorthand && prop.key.is_specific_static_name("__proto__") {
483+
continue;
484+
}
485+
let ast::PropertyKey::StaticIdentifier(id) = &prop.key else {
486+
unreachable!()
487+
};
488+
let identifier = ctx.create_property_key(&id.name);
489+
shape = shape.get_child_shape(ctx.get_agent_mut(), identifier);
490+
if is_anonymous_function_definition(&prop.value) {
491+
ctx.add_instruction_with_constant(Instruction::StoreConstant, identifier);
492+
ctx.name_identifier = Some(NamedEvaluationParameter::Result);
493+
}
494+
prop.value.compile(ctx);
495+
if is_reference(&prop.value) {
496+
ctx.add_instruction(Instruction::GetValue);
497+
}
498+
ctx.add_instruction(Instruction::Load);
499+
}
500+
ctx.add_instruction_with_shape(Instruction::ObjectCreateWithShape, shape);
501+
}
502+
439503
impl<'s> CompileEvaluation<'s> for ast::ObjectExpression<'s> {
440504
fn compile(&'s self, ctx: &mut CompileContext<'_, 's, '_, '_>) {
505+
if !self.properties.is_empty()
506+
&& self.properties.iter().all(|prop| {
507+
!prop.is_spread() && {
508+
let ast::ObjectPropertyKind::ObjectProperty(prop) = prop else {
509+
unreachable!()
510+
};
511+
prop.kind == ast::PropertyKind::Init
512+
&& !prop.method
513+
&& prop.key.is_identifier()
514+
&& if prop.key.is_specific_static_name("__proto__") && !prop.shorthand {
515+
prop.value.is_null_or_undefined()
516+
} else {
517+
true
518+
}
519+
}
520+
})
521+
{
522+
let mut dedup_keys = self
523+
.properties
524+
.iter()
525+
.map(|prop| {
526+
let ast::ObjectPropertyKind::ObjectProperty(prop) = prop else {
527+
unreachable!()
528+
};
529+
let ast::PropertyKey::StaticIdentifier(key) = &prop.key else {
530+
unreachable!()
531+
};
532+
key.name.as_str()
533+
})
534+
.collect::<Vec<_>>();
535+
dedup_keys.sort();
536+
dedup_keys.dedup();
537+
// Check that there are no duplicates.
538+
if dedup_keys.len() == self.properties.len() {
539+
// Can create Object Shape beforehand and calculate
540+
create_object_with_shape(self, ctx);
541+
return;
542+
}
543+
}
441544
// TODO: Consider preparing the properties onto the stack and creating
442545
// the object with a known size.
443546
ctx.add_instruction(Instruction::ObjectCreate);

nova_vm/src/engine/bytecode/bytecode_compiler/compile_context.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use wtf8::Wtf8Buf;
77

88
use crate::{
99
ecmascript::{
10-
builtins::regexp::RegExp,
10+
builtins::{ordinary::shape::ObjectShape, regexp::RegExp},
1111
execution::Agent,
1212
syntax_directed_operations::function_definitions::CompileFunctionBodyData,
1313
types::{BigInt, Number, PropertyKey, String, Value},
@@ -1010,6 +1010,15 @@ impl<'agent, 'script, 'gc, 'scope> CompileContext<'agent, 'script, 'gc, 'scope>
10101010
)
10111011
}
10121012

1013+
pub(super) fn add_instruction_with_shape(
1014+
&mut self,
1015+
instruction: Instruction,
1016+
shape: ObjectShape<'gc>,
1017+
) {
1018+
self.executable
1019+
.add_instruction_with_shape(instruction, shape);
1020+
}
1021+
10131022
pub(super) fn add_arrow_function_expression(
10141023
&mut self,
10151024
arrow_function_expression: ArrowFunctionExpression,

nova_vm/src/engine/bytecode/bytecode_compiler/executable_context.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ use wtf8::Wtf8Buf;
88

99
use crate::{
1010
ecmascript::{
11-
builtins::regexp::{RegExp, reg_exp_create_literal},
11+
builtins::{
12+
ordinary::shape::ObjectShape,
13+
regexp::{RegExp, reg_exp_create_literal},
14+
},
1215
execution::Agent,
1316
types::{BigInt, IntoValue, Number, PropertyKey, String, Value},
1417
},
@@ -35,6 +38,8 @@ pub(super) struct ExecutableContext<'agent, 'gc, 'scope> {
3538
instructions: Vec<u8>,
3639
/// Constants being built
3740
constants: Vec<Value<'gc>>,
41+
/// Object Shapes being built
42+
shapes: Vec<ObjectShape<'gc>>,
3843
/// Function expressions being built
3944
function_expressions: Vec<FunctionExpression<'gc>>,
4045
/// Arrow function expressions being built
@@ -50,6 +55,7 @@ impl<'agent, 'gc, 'scope> ExecutableContext<'agent, 'gc, 'scope> {
5055
current_instruction_pointer_is_unreachable: false,
5156
instructions: Vec::new(),
5257
constants: Vec::new(),
58+
shapes: Vec::new(),
5359
function_expressions: Vec::new(),
5460
arrow_function_expressions: Vec::new(),
5561
class_initializer_bytecodes: Vec::new(),
@@ -114,6 +120,7 @@ impl<'agent, 'gc, 'scope> ExecutableContext<'agent, 'gc, 'scope> {
114120
self.agent.heap.create(ExecutableHeapData {
115121
instructions: self.instructions.into_boxed_slice(),
116122
constants: self.constants.unbind().into_boxed_slice(),
123+
shapes: self.shapes.unbind().into_boxed_slice(),
117124
function_expressions: self.function_expressions.unbind().into_boxed_slice(),
118125
arrow_function_expressions: self.arrow_function_expressions.into_boxed_slice(),
119126
class_initializer_bytecodes: self
@@ -189,6 +196,21 @@ impl<'agent, 'gc, 'scope> ExecutableContext<'agent, 'gc, 'scope> {
189196
})
190197
}
191198

199+
pub(super) fn add_shape(&mut self, shape: ObjectShape<'gc>) -> usize {
200+
let duplicate = self
201+
.shapes
202+
.iter()
203+
.enumerate()
204+
.find(|item| item.1.eq(&shape))
205+
.map(|(idx, _)| idx);
206+
207+
duplicate.unwrap_or_else(|| {
208+
let index = self.shapes.len();
209+
self.shapes.push(shape);
210+
index
211+
})
212+
}
213+
192214
pub(super) fn add_instruction_with_immediate(
193215
&mut self,
194216
instruction: Instruction,
@@ -299,6 +321,18 @@ impl<'agent, 'gc, 'scope> ExecutableContext<'agent, 'gc, 'scope> {
299321
index as IndexType
300322
}
301323

324+
pub(super) fn add_instruction_with_shape(
325+
&mut self,
326+
instruction: Instruction,
327+
shape: ObjectShape<'gc>,
328+
) {
329+
debug_assert_eq!(instruction.argument_count(), 1);
330+
debug_assert!(instruction.has_shape_index());
331+
self.push_instruction(instruction);
332+
let shape = self.add_shape(shape);
333+
self.add_index(shape);
334+
}
335+
302336
pub(super) fn add_arrow_function_expression(
303337
&mut self,
304338
arrow_function_expression: ArrowFunctionExpression,

nova_vm/src/engine/bytecode/executable.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::marker::PhantomData;
1010

1111
use crate::{
1212
ecmascript::{
13+
builtins::ordinary::shape::ObjectShape,
1314
execution::Agent,
1415
scripts_and_modules::{
1516
module::module_semantics::source_text_module_records::SourceTextModule, script::Script,
@@ -136,6 +137,7 @@ const EXECUTABLE_OPTION_SIZE_IS_U32: () =
136137
pub struct ExecutableHeapData<'a> {
137138
pub(crate) instructions: Box<[u8]>,
138139
pub(crate) constants: Box<[Value<'a>]>,
140+
pub(crate) shapes: Box<[ObjectShape<'a>]>,
139141
pub(crate) function_expressions: Box<[FunctionExpression<'a>]>,
140142
pub(crate) arrow_function_expressions: Box<[ArrowFunctionExpression]>,
141143
pub(crate) class_initializer_bytecodes: Box<[(Option<Executable<'a>>, bool)]>,
@@ -326,6 +328,15 @@ impl<'gc> Executable<'gc> {
326328
) -> (Option<Executable<'gc>>, bool) {
327329
agent[self].class_initializer_bytecodes[index]
328330
}
331+
332+
fn fetch_object_shape(
333+
self,
334+
agent: &Agent,
335+
index: usize,
336+
gc: NoGcScope<'gc, '_>,
337+
) -> ObjectShape<'gc> {
338+
agent[self].shapes[index].bind(gc)
339+
}
329340
}
330341

331342
impl Scoped<'_, Executable<'static>> {
@@ -400,6 +411,16 @@ impl Scoped<'_, Executable<'static>> {
400411
self.get(agent)
401412
.fetch_class_initializer_bytecode(agent, index, gc)
402413
}
414+
415+
#[inline]
416+
pub(super) fn fetch_object_shape<'gc>(
417+
&self,
418+
agent: &Agent,
419+
index: usize,
420+
gc: NoGcScope<'gc, '_>,
421+
) -> ObjectShape<'gc> {
422+
self.get(agent).fetch_object_shape(agent, index, gc)
423+
}
403424
}
404425

405426
impl Index<Executable<'_>> for Agent {
@@ -506,11 +527,13 @@ impl HeapMarkAndSweep for ExecutableHeapData<'static> {
506527
let Self {
507528
instructions: _,
508529
constants,
530+
shapes,
509531
function_expressions: _,
510532
arrow_function_expressions: _,
511533
class_initializer_bytecodes,
512534
} = self;
513535
constants.mark_values(queues);
536+
shapes.mark_values(queues);
514537
for ele in class_initializer_bytecodes {
515538
ele.0.mark_values(queues);
516539
}
@@ -520,11 +543,13 @@ impl HeapMarkAndSweep for ExecutableHeapData<'static> {
520543
let Self {
521544
instructions: _,
522545
constants,
546+
shapes,
523547
function_expressions: _,
524548
arrow_function_expressions: _,
525549
class_initializer_bytecodes,
526550
} = self;
527551
constants.sweep_values(compactions);
552+
shapes.sweep_values(compactions);
528553
for ele in class_initializer_bytecodes {
529554
ele.0.sweep_values(compactions);
530555
}

nova_vm/src/engine/bytecode/instructions.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ pub enum Instruction {
219219
LogicalNot,
220220
/// Store OrdinaryObjectCreate(%Object.prototype%) on the stack.
221221
ObjectCreate,
222+
/// Store a new as the result Object created with the given shape, with its
223+
/// properties coming from the stack.
224+
ObjectCreateWithShape,
222225
/// Call CreateDataPropertyOrThrow(object, key, value) with value being the
223226
/// result value, key being the top stack value and object being the second
224227
/// stack value. The object is not popped from the stack.
@@ -527,6 +530,7 @@ impl Instruction {
527530
| Self::LoadConstant
528531
| Self::MakePrivateReference
529532
| Self::MakeSuperPropertyReferenceWithIdentifierKey
533+
| Self::ObjectCreateWithShape
530534
| Self::ClassInitializePrivateValue
531535
| Self::ResolveBinding
532536
| Self::StoreConstant
@@ -559,6 +563,10 @@ impl Instruction {
559563
)
560564
}
561565

566+
pub fn has_shape_index(self) -> bool {
567+
matches!(self, Self::ObjectCreateWithShape)
568+
}
569+
562570
pub fn has_identifier_index(self) -> bool {
563571
matches!(
564572
self,
@@ -1177,6 +1185,7 @@ impl TryFrom<u8> for Instruction {
11771185
const EMPTY: u8 = Instruction::Empty.as_u8();
11781186
const LOGICALNOT: u8 = Instruction::LogicalNot.as_u8();
11791187
const OBJECTCREATE: u8 = Instruction::ObjectCreate.as_u8();
1188+
const OBJECTCREATEWITHSHAPE: u8 = Instruction::ObjectCreateWithShape.as_u8();
11801189
const OBJECTDEFINEPROPERTY: u8 = Instruction::ObjectDefineProperty.as_u8();
11811190
const OBJECTDEFINEMETHOD: u8 = Instruction::ObjectDefineMethod.as_u8();
11821191
const OBJECTDEFINEGETTER: u8 = Instruction::ObjectDefineGetter.as_u8();
@@ -1383,6 +1392,7 @@ impl TryFrom<u8> for Instruction {
13831392
EMPTY => Ok(Instruction::Empty),
13841393
LOGICALNOT => Ok(Instruction::LogicalNot),
13851394
OBJECTCREATE => Ok(Instruction::ObjectCreate),
1395+
OBJECTCREATEWITHSHAPE => Ok(Instruction::ObjectCreateWithShape),
13861396
OBJECTDEFINEPROPERTY => Ok(Instruction::ObjectDefineProperty),
13871397
OBJECTDEFINEMETHOD => Ok(Instruction::ObjectDefineMethod),
13881398
OBJECTDEFINEGETTER => Ok(Instruction::ObjectDefineGetter),

nova_vm/src/engine/bytecode/vm.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,19 @@ impl Vm {
11311131
);
11321132
vm.stack.push(object.into_value().unbind())
11331133
}
1134+
Instruction::ObjectCreateWithShape => {
1135+
let shape =
1136+
executable.fetch_object_shape(agent, instr.get_first_index(), gc.into_nogc());
1137+
let len = shape.get_length(agent);
1138+
let first_property_index = vm.stack.len() - len as usize;
1139+
let obj = OrdinaryObject::create_object_with_shape_and_data_properties(
1140+
agent,
1141+
shape,
1142+
&vm.stack[first_property_index..],
1143+
);
1144+
vm.stack.truncate(first_property_index);
1145+
vm.result = Some(obj.unbind().into_value());
1146+
}
11341147
Instruction::CopyDataProperties => {
11351148
let source = vm.result.take().unwrap();
11361149
let Value::Object(target) = *vm.stack.last().unwrap() else {

0 commit comments

Comments
 (0)