avm2: Add SetSlotNoCoerce and ReturnValueNoCoerce ops for SetSlot and ReturnValue without coercion when type is guaranteed to be not coerced

This commit is contained in:
Lord-McSweeney 2024-04-05 09:00:53 -07:00 committed by Lord-McSweeney
parent daaf274245
commit 1f4eebfd8d
5 changed files with 107 additions and 4 deletions

View File

@ -933,6 +933,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
num_args,
} => self.op_call_super_void(*multiname, *num_args),
Op::ReturnValue => self.op_return_value(method),
Op::ReturnValueNoCoerce => self.op_return_value_no_coerce(),
Op::ReturnVoid => self.op_return_void(),
Op::GetProperty { multiname } => self.op_get_property(*multiname),
Op::SetProperty { multiname } => self.op_set_property(*multiname),
@ -955,6 +956,7 @@ impl<'a, 'gc> Activation<'a, 'gc> {
Op::GetDescendants { multiname } => self.op_get_descendants(*multiname),
Op::GetSlot { index } => self.op_get_slot(*index),
Op::SetSlot { index } => self.op_set_slot(*index),
Op::SetSlotNoCoerce { index } => self.op_set_slot_no_coerce(*index),
Op::GetGlobalSlot { index } => self.op_get_global_slot(*index),
Op::SetGlobalSlot { index } => self.op_set_global_slot(*index),
Op::Construct { num_args } => self.op_construct(*num_args),
@ -1358,6 +1360,12 @@ impl<'a, 'gc> Activation<'a, 'gc> {
Ok(FrameControl::Return(coerced))
}
fn op_return_value_no_coerce(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
let return_value = self.pop_stack();
Ok(FrameControl::Return(return_value))
}
fn op_return_void(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
Ok(FrameControl::Return(Value::Undefined))
}
@ -1777,6 +1785,15 @@ impl<'a, 'gc> Activation<'a, 'gc> {
Ok(FrameControl::Continue)
}
fn op_set_slot_no_coerce(&mut self, index: u32) -> Result<FrameControl<'gc>, Error<'gc>> {
let value = self.pop_stack();
let object = self.pop_stack().coerce_to_object_or_typeerror(self, None)?;
object.set_slot_no_coerce(index, value, self.context.gc_context)?;
Ok(FrameControl::Continue)
}
fn op_get_global_slot(&mut self, index: u32) -> Result<FrameControl<'gc>, Error<'gc>> {
let value = self
.global_scope()

View File

@ -597,6 +597,17 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
base.set_slot(id, value, activation.gc())
}
fn set_slot_no_coerce(
self,
id: u32,
value: Value<'gc>,
mc: &Mutation<'gc>,
) -> Result<(), Error<'gc>> {
let mut base = self.base_mut(mc);
base.set_slot(id, value, mc)
}
/// Call a method by its index.
///
/// This directly corresponds with the AVM2 operation `callmethod`.

View File

@ -296,6 +296,7 @@ pub enum Op<'gc> {
PushUndefined,
PushWith,
ReturnValue,
ReturnValueNoCoerce,
ReturnVoid,
RShift,
SetGlobalSlot {
@ -310,6 +311,9 @@ pub enum Op<'gc> {
SetSlot {
index: u32,
},
SetSlotNoCoerce {
index: u32,
},
SetSuper {
multiname: Gc<'gc, Multiname<'gc>>,
},

View File

@ -152,6 +152,7 @@ pub fn optimize<'gc>(
method: &BytecodeMethod<'gc>,
code: &mut Vec<Op<'gc>>,
resolved_parameters: &[ResolvedParamConfig<'gc>],
return_type: Option<GcCell<'gc, Class<'gc>>>,
jump_targets: HashMap<i32, JumpSources>,
) {
// These make the code less readable
@ -839,7 +840,8 @@ pub fn optimize<'gc>(
}
}
Op::InitProperty { multiname } => {
stack.pop();
let set_value = stack.pop_or_any();
stack.pop_for_multiname(*multiname);
let stack_value = stack.pop_or_any();
if !multiname.has_lazy_component() {
@ -849,6 +851,28 @@ pub fn optimize<'gc>(
Some(Property::Slot { slot_id })
| Some(Property::ConstSlot { slot_id }) => {
*op = Op::SetSlot { index: slot_id };
// If the set value's type is the same as the type of the slot,
// a SetSlotNoCoerce can be emitted.
let mut value_class =
class.instance_vtable().slot_classes()[slot_id as usize];
let resolved_value_class = value_class.get_class(activation);
if let Ok(slot_class) = resolved_value_class {
if let Some(slot_class) = slot_class {
if let Some(set_value_class) = set_value.class {
if GcCell::ptr_eq(
set_value_class.inner_class_definition(),
slot_class,
) {
*op = Op::SetSlotNoCoerce { index: slot_id };
}
}
} else {
// Slot type was Any, no coercion will be done anyways
*op = Op::SetSlotNoCoerce { index: slot_id };
}
}
}
Some(Property::Virtual {
set: Some(disp_id), ..
@ -867,7 +891,8 @@ pub fn optimize<'gc>(
// `stack_pop_multiname` handled lazy
}
Op::SetProperty { multiname } => {
stack.pop();
let set_value = stack.pop_or_any();
stack.pop_for_multiname(*multiname);
let stack_value = stack.pop_or_any();
if !multiname.has_lazy_component() {
@ -876,6 +901,28 @@ pub fn optimize<'gc>(
match class.instance_vtable().get_trait(multiname) {
Some(Property::Slot { slot_id }) => {
*op = Op::SetSlot { index: slot_id };
// If the set value's type is the same as the type of the slot,
// a SetSlotNoCoerce can be emitted.
let mut value_class =
class.instance_vtable().slot_classes()[slot_id as usize];
let resolved_value_class = value_class.get_class(activation);
if let Ok(slot_class) = resolved_value_class {
if let Some(slot_class) = slot_class {
if let Some(set_value_class) = set_value.class {
if GcCell::ptr_eq(
set_value_class.inner_class_definition(),
slot_class,
) {
*op = Op::SetSlotNoCoerce { index: slot_id };
}
}
} else {
// Slot type was Any, no coercion will be done anyways
*op = Op::SetSlotNoCoerce { index: slot_id };
}
}
}
Some(Property::Virtual {
set: Some(disp_id), ..
@ -940,7 +987,6 @@ pub fn optimize<'gc>(
// Avoid checking return value for now
stack.push_any();
}
Op::CallMethod { .. } => unreachable!("CallMethod cannot be emitted by verifier"),
Op::CallPropLex {
multiname,
num_args,
@ -1141,7 +1187,27 @@ pub fn optimize<'gc>(
stack.pop();
stack.push_class_object(types.number);
}
Op::ReturnVoid | Op::ReturnValue | Op::Throw | Op::LookupSwitch(_) => {
Op::ReturnVoid | Op::Throw | Op::LookupSwitch(_) => {
// End of block
stack.clear();
scope_stack.clear();
local_types = initial_local_types.clone();
last_op_was_block_terminating = true;
}
Op::ReturnValue => {
let stack_value = stack.pop_or_any();
if let Some(return_type) = return_type {
if let Some(stack_value_class) = stack_value.class {
if GcCell::ptr_eq(stack_value_class.inner_class_definition(), return_type) {
*op = Op::ReturnValueNoCoerce;
}
}
} else {
// Return type was Any, no coercion will be done anyways
*op = Op::ReturnValueNoCoerce;
}
// End of block
stack.clear();
scope_stack.clear();
@ -1157,6 +1223,10 @@ pub fn optimize<'gc>(
local_types = initial_local_types.clone();
last_op_was_block_terminating = true;
}
other => unreachable!(
"Optimizer hit op {:?}, which cannot appear after the verifier step",
other
),
}
}
}

View File

@ -581,6 +581,7 @@ pub fn verify_method<'gc>(
method,
&mut verified_code,
&resolved_param_config,
resolved_return_type,
potential_jump_targets,
);
}