avm2: Add a vtable to Class, support getting vtable of static methods in optimizer

This commit is contained in:
Lord-McSweeney 2024-05-19 11:02:53 -07:00 committed by Lord-McSweeney
parent cc70686592
commit 43549ab1d7
22 changed files with 564 additions and 295 deletions

View File

@ -7,6 +7,7 @@ use crate::avm2::object::{ClassObject, Object};
use crate::avm2::script::TranslationUnit;
use crate::avm2::traits::{Trait, TraitKind};
use crate::avm2::value::Value;
use crate::avm2::vtable::VTable;
use crate::avm2::Error;
use crate::avm2::Multiname;
use crate::avm2::Namespace;
@ -17,9 +18,9 @@ use fnv::FnvHashMap;
use gc_arena::{Collect, GcCell, Mutation};
use std::cell::Ref;
use std::collections::HashSet;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use swf::avm2::types::{
Class as AbcClass, Instance as AbcInstance, Method as AbcMethod, MethodBody as AbcMethodBody,
@ -101,7 +102,7 @@ pub struct ClassData<'gc> {
/// The list of interfaces this class directly implements. This does not include any
/// superinterfaces, nor interfaces implemented by the superclass.
direct_interfaces: Vec<Multiname<'gc>>,
direct_interfaces: Vec<Class<'gc>>,
/// The instance allocator for this class.
///
@ -137,6 +138,8 @@ pub struct ClassData<'gc> {
/// properties that would match.
instance_traits: Vec<Trait<'gc>>,
instance_vtable: VTable<'gc>,
/// The class initializer for this class.
///
/// Must be called once and only once prior to any use of this class.
@ -238,6 +241,7 @@ impl<'gc> Class<'gc> {
instance_init,
native_instance_init,
instance_traits: Vec::new(),
instance_vtable: VTable::empty(mc),
class_init,
class_initializer_called: false,
call_handler: None,
@ -302,6 +306,9 @@ impl<'gc> Class<'gc> {
new_class.set_param(mc, Some(Some(param)));
new_class.0.write(mc).call_handler = object_vector_cls.call_handler();
new_class
.init_vtable(context)
.expect("Vector class doesn't have any interfaces, so `init_vtable` cannot error");
drop(this_read);
@ -383,10 +390,18 @@ impl<'gc> Class<'gc> {
let mut interfaces = Vec::with_capacity(abc_instance.interfaces.len());
for interface_name in &abc_instance.interfaces {
let multiname = unit.pool_multiname_static(*interface_name, &mut activation.context)?;
interfaces.push(
unit.pool_multiname_static(*interface_name, &mut activation.context)?
.deref()
.clone(),
activation
.domain()
.get_class(&mut activation.context, &multiname)
.ok_or_else(|| {
make_error_1014(
activation,
multiname.to_qualified_name(activation.context.gc_context),
)
})?,
);
}
@ -453,6 +468,7 @@ impl<'gc> Class<'gc> {
instance_init,
native_instance_init,
instance_traits: Vec::new(),
instance_vtable: VTable::empty(activation.context.gc_context),
class_init,
class_initializer_called: false,
call_handler: native_call_handler,
@ -588,6 +604,79 @@ impl<'gc> Class<'gc> {
Ok(())
}
pub fn init_vtable(self, context: &mut UpdateContext<'_, 'gc>) -> Result<(), Error<'gc>> {
let read = self.0.read();
if !read.traits_loaded {
panic!(
"Attempted to initialize vtable on a class that did not have its traits loaded yet"
);
}
read.instance_vtable.init_vtable(
None,
self.protected_namespace(),
&read.instance_traits,
None,
read.super_class.map(|c| c.instance_vtable()),
context,
);
drop(read);
self.link_interfaces(context)?;
Ok(())
}
pub fn link_interfaces(self, context: &mut UpdateContext<'_, 'gc>) -> Result<(), Error<'gc>> {
let mut interfaces = Vec::with_capacity(self.direct_interfaces().len());
let mut dedup = HashSet::new();
let mut queue = vec![self];
while let Some(cls) = queue.pop() {
for interface in &*cls.direct_interfaces() {
if !interface.is_interface() {
return Err(format!(
"Class {:?} is not an interface and cannot be implemented by classes",
interface.name().local_name()
)
.into());
}
if dedup.insert(ClassHashWrapper(*interface)) {
queue.push(*interface);
interfaces.push(*interface);
}
}
if let Some(super_class) = cls.super_class() {
queue.push(super_class);
}
}
// FIXME - we should only be copying properties for newly-implemented
// interfaces (i.e. those that were not already implemented by the superclass)
// Otherwise, our behavior diverges from Flash Player in certain cases.
// See the ignored test 'tests/tests/swfs/avm2/weird_superinterface_properties/'
for interface in interfaces {
for interface_trait in &*interface.instance_traits() {
if !interface_trait.name().namespace().is_public() {
let public_name = QName::new(
context.avm2.public_namespace_vm_internal,
interface_trait.name().local_name(),
);
self.0.read().instance_vtable.copy_property_for_interface(
context.gc_context,
public_name,
interface_trait.name(),
);
}
}
}
Ok(())
}
pub fn for_activation(
activation: &mut Activation<'_, 'gc>,
translation_unit: TranslationUnit<'gc>,
@ -606,7 +695,7 @@ impl<'gc> Class<'gc> {
)?);
}
Ok(Class(GcCell::new(
let class = Class(GcCell::new(
activation.context.gc_context,
ClassData {
name: QName::new(activation.avm2().public_namespace_base_version, name),
@ -627,6 +716,7 @@ impl<'gc> Class<'gc> {
activation.context.gc_context,
),
instance_traits: traits,
instance_vtable: VTable::empty(activation.context.gc_context),
class_init: Method::from_builtin(
|_, _, _| Ok(Value::Undefined),
"<Activation object class constructor>",
@ -640,7 +730,15 @@ impl<'gc> Class<'gc> {
applications: Default::default(),
class_objects: Vec::new(),
},
)))
));
class.init_vtable(&mut activation.context)?;
Ok(class)
}
pub fn instance_vtable(self) -> VTable<'gc> {
self.0.read().instance_vtable
}
pub fn name(self) -> QName<'gc> {
@ -667,6 +765,42 @@ impl<'gc> Class<'gc> {
self.0.read().protected_namespace
}
pub fn mark_traits_loaded(self, mc: &Mutation<'gc>) {
self.0.write(mc).traits_loaded = true;
}
#[inline(never)]
pub fn define_constant_class_instance_trait(
self,
activation: &mut Activation<'_, 'gc>,
name: QName<'gc>,
value: Value<'gc>,
) {
self.define_instance_trait(
activation.context.gc_context,
Trait::from_const(
name,
Multiname::new(activation.avm2().public_namespace_base_version, "Class"),
Some(value),
),
);
}
#[inline(never)]
pub fn define_constant_function_instance_trait(
self,
activation: &mut Activation<'_, 'gc>,
name: QName<'gc>,
value: Value<'gc>,
) {
self.define_instance_trait(
activation.context.gc_context,
Trait::from_const(
name,
Multiname::new(activation.avm2().public_namespace_base_version, "Function"),
Some(value),
),
);
}
#[inline(never)]
pub fn define_constant_number_class_traits(
self,
@ -941,14 +1075,10 @@ impl<'gc> Class<'gc> {
self.0.write(mc).class_initializer_called = true;
}
pub fn direct_interfaces(&self) -> Ref<Vec<Multiname<'gc>>> {
pub fn direct_interfaces(&self) -> Ref<Vec<Class<'gc>>> {
Ref::map(self.0.read(), |c| &c.direct_interfaces)
}
pub fn implements(self, mc: &Mutation<'gc>, iface: Multiname<'gc>) {
self.0.write(mc).direct_interfaces.push(iface)
}
/// Determine if this class is sealed (no dynamic properties)
pub fn is_sealed(self) -> bool {
self.0.read().attributes.contains(ClassAttributes::SEALED)

View File

@ -11,7 +11,7 @@ use crate::avm2::Namespace;
use crate::avm2::QName;
use crate::string::AvmString;
use crate::tag_utils::{self, ControlFlow, SwfMovie, SwfSlice, SwfStream};
use gc_arena::{Collect, Mutation};
use gc_arena::Collect;
use std::sync::Arc;
use swf::TagCode;
@ -335,6 +335,9 @@ fn define_fn_on_global<'gc>(
func,
activation.avm2().classes().function,
);
script
.global_class()
.define_constant_function_instance_trait(activation, qname, func);
}
/// Add a fully-formed class object builtin to the global scope.
@ -342,7 +345,7 @@ fn define_fn_on_global<'gc>(
/// This allows the caller to pre-populate the class's prototype with dynamic
/// properties, if necessary.
fn dynamic_class<'gc>(
mc: &Mutation<'gc>,
activation: &mut Activation<'_, 'gc>,
class_object: ClassObject<'gc>,
script: Script<'gc>,
// The `ClassObject` of the `Class` class
@ -352,8 +355,18 @@ fn dynamic_class<'gc>(
let class = class_object.inner_class_definition();
let name = class.name();
global.install_const_late(mc, name, class_object.into(), class_class);
domain.export_definition(name, script, mc)
global.install_const_late(
activation.context.gc_context,
name,
class_object.into(),
class_class,
);
script.global_class().define_constant_class_instance_trait(
activation,
name,
class_object.into(),
);
domain.export_definition(name, script, activation.context.gc_context)
}
/// Add a class builtin to the global scope.
@ -387,6 +400,11 @@ fn class<'gc>(
class_object.into(),
activation.avm2().classes().class,
);
script.global_class().define_constant_class_instance_trait(
activation,
class_name,
class_object.into(),
);
domain.export_definition(class_name, script, mc);
domain.export_class(class_name, class_def, mc);
Ok(class_object)
@ -421,6 +439,11 @@ fn vector_class<'gc>(
vector_cls.into(),
activation.avm2().classes().class,
);
script.global_class().define_constant_class_instance_trait(
activation,
legacy_name,
vector_cls.into(),
);
domain.export_definition(legacy_name, script, mc);
Ok(vector_cls)
}
@ -531,18 +554,20 @@ pub fn load_player_globals<'gc>(
let class_class = class_class.into_finished_class(activation)?;
let object_class = object_class.into_finished_class(activation)?;
let fn_class = fn_class.into_finished_class(activation)?;
let _global_class = global_class.into_finished_class(activation)?;
let global_class = global_class.into_finished_class(activation)?;
globals.set_proto(mc, global_proto);
globals.set_instance_of(mc, global_class);
activation.context.avm2.toplevel_global_object = Some(globals);
script.set_global_class(mc, global_classdef);
// From this point, `globals` is safe to be modified
dynamic_class(mc, object_class, script, class_class);
dynamic_class(mc, fn_class, script, class_class);
dynamic_class(mc, class_class, script, class_class);
dynamic_class(activation, object_class, script, class_class);
dynamic_class(activation, fn_class, script, class_class);
dynamic_class(activation, class_class, script, class_class);
// After this point, it is safe to initialize any other classes.
// Make sure to initialize superclasses *before* their subclasses!
@ -624,6 +649,9 @@ pub fn load_player_globals<'gc>(
define_fn_on_global(activation, "", "parseFloat", script);
define_fn_on_global(activation, "", "parseInt", script);
global_classdef.mark_traits_loaded(mc);
global_classdef.init_vtable(&mut activation.context)?;
Ok(())
}

View File

@ -1300,5 +1300,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
activation,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -383,12 +383,13 @@ fn describe_internal_body<'gc>(
let declared_by = method.class;
if flags.contains(DescribeTypeFlags::HIDE_OBJECT)
&& declared_by == activation.avm2().classes().object
&& declared_by == Some(activation.avm2().classes().object)
{
continue;
}
let declared_by_name = declared_by
.unwrap()
.inner_class_definition()
.name()
.to_qualified_name(activation.context.gc_context);
@ -476,6 +477,7 @@ fn describe_internal_body<'gc>(
let accessor_type =
method_type.to_qualified_name_or_star(activation.context.gc_context);
let declared_by = defining_class
.unwrap()
.inner_class_definition()
.name()
.to_qualified_name(activation.context.gc_context);

View File

@ -170,5 +170,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
activation,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -75,5 +75,10 @@ pub fn create_class<'gc>(
PUBLIC_INSTANCE_PROPERTIES,
);
class_class.mark_traits_loaded(activation.context.gc_context);
class_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class_class
}

View File

@ -1392,5 +1392,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
activation,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -268,5 +268,10 @@ pub fn create_class<'gc>(
Method::from_builtin(class_call, "<Function call handler>", gc_context),
);
function_class.mark_traits_loaded(activation.context.gc_context);
function_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
function_class
}

View File

@ -36,11 +36,18 @@ pub fn create_class<'gc>(
object_class: Class<'gc>,
) -> Class<'gc> {
let mc = activation.context.gc_context;
Class::new(
let class = Class::new(
QName::new(activation.avm2().public_namespace_base_version, "global"),
Some(object_class),
Method::from_builtin(instance_init, "<global instance initializer>", mc),
Method::from_builtin(class_init, "<global class initializer>", mc),
mc,
)
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -273,5 +273,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
AS3_INSTANCE_METHODS,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -217,5 +217,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
PUBLIC_INSTANCE_AND_PROTO_METHODS,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -432,5 +432,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
AS3_INSTANCE_METHODS,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -321,5 +321,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
INTERNAL_INIT_METHOD,
);
object_class.mark_traits_loaded(activation.context.gc_context);
object_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
object_class
}

View File

@ -750,6 +750,11 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
PUBLIC_CLASS_METHODS,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -276,5 +276,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
AS3_INSTANCE_METHODS,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -922,6 +922,12 @@ pub fn create_generic_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<
class.set_attributes(mc, ClassAttributes::GENERIC | ClassAttributes::FINAL);
class.set_instance_allocator(mc, generic_vector_allocator);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}
@ -1007,5 +1013,10 @@ pub fn create_builtin_class<'gc>(
AS3_INSTANCE_METHODS,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -29,5 +29,10 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
mc,
);
class.mark_traits_loaded(activation.context.gc_context);
class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");
class
}

View File

@ -602,12 +602,12 @@ pub trait TObject<'gc>: 'gc + Collect + Debug + Into<Object<'gc>> + Clone + Copy
return exec(
method,
scope,
scope.expect("Scope should exist here"),
self.into(),
Some(class),
class,
arguments,
activation,
class.into(), //Callee deliberately invalid.
ScriptObject::custom_object(activation.context.gc_context, None, None), // Callee deliberately invalid.
);
}

View File

@ -248,12 +248,13 @@ impl<'gc> ClassObject<'gc> {
class.validate_class(self.superclass_object())?;
self.instance_vtable().init_vtable(
self,
Some(self),
class.protected_namespace(),
&class.instance_traits(),
self.instance_scope(),
Some(self.instance_scope()),
self.superclass_object().map(|cls| cls.instance_vtable()),
activation,
)?;
&mut activation.context,
);
Ok(())
}
@ -283,12 +284,13 @@ impl<'gc> ClassObject<'gc> {
// class vtable == class traits + Class instance traits
self.class_vtable().init_vtable(
self,
Some(self),
class.protected_namespace(),
&class.class_traits(),
self.class_scope(),
Some(self.class_scope()),
Some(self.instance_of().unwrap().instance_vtable()),
activation,
)?;
&mut activation.context,
);
self.link_interfaces(activation)?;
self.install_class_vtable_and_slots(activation.context.gc_context);
@ -327,22 +329,13 @@ impl<'gc> ClassObject<'gc> {
pub fn link_interfaces(self, activation: &mut Activation<'_, 'gc>) -> Result<(), Error<'gc>> {
let mut write = self.0.write(activation.context.gc_context);
let class = write.class;
let scope = write.class_scope;
let interface_names = class.direct_interfaces().to_vec();
let mut interfaces = Vec::with_capacity(interface_names.len());
let mut interfaces = Vec::with_capacity(class.direct_interfaces().len());
let mut dedup = HashSet::new();
let mut queue = vec![class];
while let Some(cls) = queue.pop() {
for interface_name in &*cls.direct_interfaces() {
let interface = scope
.domain()
.get_class(&mut activation.context, interface_name)
.ok_or_else(|| {
Error::from(format!("Could not resolve interface {interface_name:?}"))
})?;
for interface in &*cls.direct_interfaces() {
if !interface.is_interface() {
return Err(format!(
"Class {:?} is not an interface and cannot be implemented by classes",
@ -351,9 +344,9 @@ impl<'gc> ClassObject<'gc> {
.into());
}
if dedup.insert(ClassHashWrapper(interface)) {
queue.push(interface);
interfaces.push(interface);
if dedup.insert(ClassHashWrapper(*interface)) {
queue.push(*interface);
interfaces.push(*interface);
}
}
@ -569,8 +562,13 @@ impl<'gc> ClassObject<'gc> {
scope,
method,
} = self.instance_vtable().get_full_method(disp_id).unwrap();
let callee =
FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class));
let callee = FunctionObject::from_method(
activation,
method,
scope.expect("Scope should exist here"),
Some(receiver),
class,
);
callee.call(receiver.into(), arguments, activation)
} else {
@ -626,9 +624,9 @@ impl<'gc> ClassObject<'gc> {
let callee = FunctionObject::from_method(
activation,
method,
scope,
scope.expect("Scope should exist here"),
Some(receiver),
Some(class),
class,
);
// We call getters, but return the actual function object for normal methods
@ -706,7 +704,7 @@ impl<'gc> ClassObject<'gc> {
method,
} = self.instance_vtable().get_full_method(disp_id).unwrap();
let callee =
FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class));
FunctionObject::from_method(activation, method, scope.expect("Scope should exist here"), Some(receiver), class);
callee.call(receiver.into(), &[value], activation)?;
Ok(())

View File

@ -6,11 +6,12 @@ use crate::avm2::object::{ClassObject, TObject};
use crate::avm2::op::Op;
use crate::avm2::property::Property;
use crate::avm2::verify::JumpSources;
use crate::avm2::vtable::VTable;
use gc_arena::Gc;
use std::collections::HashMap;
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy)]
struct OptValue<'gc> {
// This corresponds to the compile-time assumptions about the type:
// - primitive types can't be undefined or null,
@ -21,7 +22,9 @@ struct OptValue<'gc> {
// (say, a Value::Number above hardcoded int-range that's still representable as i32).
// Note that `null is Object` is still `false`. So think of this type more in terms of
// "could this value be a possible value of `var t: T`"
pub class: Option<ClassObject<'gc>>,
pub class: Option<Class<'gc>>,
pub vtable: Option<VTable<'gc>>,
// true if the value is guaranteed to be Value::Integer
// should only be set if class is numeric.
@ -39,33 +42,59 @@ impl<'gc> OptValue<'gc> {
pub fn any() -> Self {
Self {
class: None,
vtable: None,
contains_valid_integer: false,
contains_valid_unsigned: false,
guaranteed_null: false,
}
}
pub fn null() -> Self {
Self {
class: None,
vtable: None,
guaranteed_null: true,
..Self::any()
}
}
pub fn of_type(class: ClassObject<'gc>) -> Self {
let class = class.inner_class_definition();
Self {
class: Some(class),
vtable: Some(class.instance_vtable()),
..Self::any()
}
}
pub fn of_type_from_class(class: Class<'gc>) -> Self {
// FIXME: Getting the ClassObject this way should be unnecessary
// after the ClassObject refactor
if let Some(cls) = class.class_object() {
Self::of_type(cls)
} else {
Self::any()
Self {
class: Some(class),
vtable: Some(class.instance_vtable()),
..Self::any()
}
}
pub fn vtable(self) -> Option<VTable<'gc>> {
if let Some(class) = self.class {
if class.is_interface() {
return None;
}
}
self.vtable
}
}
impl<'gc> std::fmt::Debug for OptValue<'gc> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("OptValue")
.field("class", &self.class)
.field("contains_valid_integer", &self.contains_valid_integer)
.field("contains_valid_unsigned", &self.contains_valid_unsigned)
.field("guaranteed_null", &self.guaranteed_null)
.finish()
}
}
#[derive(Clone, Debug)]
@ -196,14 +225,32 @@ pub fn optimize<'gc>(
// but this works since it's guaranteed to be set in `Activation::from_method`.
let this_value = activation.local_register(0);
let this_class = if let Some(this_class) = activation.subclass_object() {
let (this_class, this_vtable) = if let Some(this_class) = activation.subclass_object() {
if this_value.is_of_type(activation, this_class.inner_class_definition()) {
Some(this_class)
(Some(this_class), Some(this_class.inner_class_definition().instance_vtable()))
} else if this_value
.as_object()
.and_then(|o| o.as_class_object())
.map(|c| c.inner_class_definition() == this_class.inner_class_definition())
.unwrap_or(false) {
// Static method
(
Some(activation.avm2().classes().class),
Some(this_class.class_vtable()),
)
} else {
None
(None, None)
}
} else {
None
(None, None)
};
let this_value = OptValue {
class: this_class.map(|c| c.inner_class_definition()),
vtable: this_vtable,
contains_valid_integer: false,
contains_valid_unsigned: false,
guaranteed_null: false,
};
let argument_types = resolved_parameters
@ -213,9 +260,7 @@ pub fn optimize<'gc>(
// Initial set of local types
let mut initial_local_types = Locals::new(method_body.num_locals as usize);
if let Some(this_class) = this_class {
initial_local_types.set(0, OptValue::of_type(this_class));
}
initial_local_types.set(0, this_value);
for (i, argument_type) in argument_types.iter().enumerate() {
if let Some(argument_type) = argument_type {
@ -303,10 +348,11 @@ pub fn optimize<'gc>(
if let (Some(source_local_class), Some(target_local_class)) =
(source_local.class, target_local.class)
{
if source_local_class.inner_class_definition()
== target_local_class.inner_class_definition()
{
merged_types.set(i, OptValue::of_type(source_local_class));
if source_local_class == target_local_class {
merged_types.set(
i,
OptValue::of_type_from_class(source_local_class),
);
}
}
}
@ -337,16 +383,16 @@ pub fn optimize<'gc>(
}
Op::CoerceB => {
let stack_value = stack.pop_or_any();
if stack_value.class == Some(types.boolean) {
if stack_value.class == Some(types.boolean.inner_class_definition()) {
*op = Op::Nop;
}
stack.push_class_object(types.boolean);
}
Op::CoerceD => {
let stack_value = stack.pop_or_any();
if stack_value.class == Some(types.number)
|| stack_value.class == Some(types.int)
|| stack_value.class == Some(types.uint)
if stack_value.class == Some(types.number.inner_class_definition())
|| stack_value.class == Some(types.int.inner_class_definition())
|| stack_value.class == Some(types.uint.inner_class_definition())
{
*op = Op::Nop;
}
@ -508,12 +554,12 @@ pub fn optimize<'gc>(
Op::Add => {
let value2 = stack.pop_or_any();
let value1 = stack.pop_or_any();
if (value1.class == Some(types.int)
|| value1.class == Some(types.uint)
|| value1.class == Some(types.number))
&& (value2.class == Some(types.int)
|| value2.class == Some(types.uint)
|| value2.class == Some(types.number))
if (value1.class == Some(types.int.inner_class_definition())
|| value1.class == Some(types.uint.inner_class_definition())
|| value1.class == Some(types.number.inner_class_definition()))
&& (value2.class == Some(types.int.inner_class_definition())
|| value2.class == Some(types.uint.inner_class_definition())
|| value2.class == Some(types.number.inner_class_definition()))
{
stack.push_class_object(types.number);
} else {
@ -668,8 +714,8 @@ pub fn optimize<'gc>(
// if T is non-nullable, we can assume the result is typed T
new_value = OptValue::of_type_from_class(*class);
}
if let Some(class_object) = stack_value.class {
if *class == class_object.inner_class_definition() {
if let Some(stack_class) = stack_value.class {
if *class == stack_class {
// If type check is guaranteed, preserve original type
// TODO: there are more cases when this can succeed,
// like inheritance and numbers (`x: Number = 1; x as int;`)
@ -696,9 +742,9 @@ pub fn optimize<'gc>(
{
*op = Op::Nop;
}
} else if let Some(class_object) = stack_value.class {
} else if let Some(stack_class) = stack_value.class {
// TODO: this could check for inheritance
if *class == class_object.inner_class_definition() {
if *class == stack_class {
*op = Op::Nop;
}
}
@ -760,12 +806,12 @@ pub fn optimize<'gc>(
if !multiname.has_lazy_component() && has_simple_scoping {
let outer_scope = activation.outer();
if !outer_scope.is_empty() {
if let Some(this_class) = this_class {
if this_class.instance_vtable().has_trait(&multiname) {
if let Some(this_vtable) = this_vtable {
if this_vtable.has_trait(&multiname) {
*op = Op::GetScopeObject { index: 0 };
stack_push_done = true;
stack.push_class_object(this_class);
stack.push(this_value);
}
} else {
stack_push_done = true;
@ -859,10 +905,10 @@ pub fn optimize<'gc>(
let mut stack_push_done = false;
let stack_value = stack.pop_or_any();
if let Some(class) = stack_value.class {
if !class.inner_class_definition().is_interface() {
let mut value_class =
class.instance_vtable().slot_classes()[*slot_id as usize];
if let Some(vtable) = stack_value.vtable() {
let slot_classes = vtable.slot_classes();
let value_class = slot_classes.get(*slot_id as usize).copied();
if let Some(mut value_class) = value_class {
let resolved_value_class = value_class.get_class(activation);
if let Ok(class) = resolved_value_class {
stack_push_done = true;
@ -874,7 +920,8 @@ pub fn optimize<'gc>(
}
}
class.instance_vtable().set_slot_class(
drop(slot_classes);
vtable.set_slot_class(
activation.context.gc_context,
*slot_id as usize,
value_class,
@ -896,15 +943,13 @@ pub fn optimize<'gc>(
let stack_value = stack.pop_or_any();
if !multiname.has_lazy_component() {
if let Some(class) = stack_value.class {
if !class.inner_class_definition().is_interface() {
match class.instance_vtable().get_trait(multiname) {
if let Some(vtable) = stack_value.vtable() {
match vtable.get_trait(multiname) {
Some(Property::Slot { slot_id })
| Some(Property::ConstSlot { slot_id }) => {
*op = Op::GetSlot { index: slot_id };
let mut value_class =
class.instance_vtable().slot_classes()[slot_id as usize];
let mut value_class = vtable.slot_classes()[slot_id as usize];
let resolved_value_class = value_class.get_class(activation);
if let Ok(class) = resolved_value_class {
stack_push_done = true;
@ -916,7 +961,7 @@ pub fn optimize<'gc>(
}
}
class.instance_vtable().set_slot_class(
vtable.set_slot_class(
activation.context.gc_context,
slot_id as usize,
value_class,
@ -935,7 +980,6 @@ pub fn optimize<'gc>(
}
}
}
}
// `stack_pop_multiname` handled lazy
if !stack_push_done {
@ -948,25 +992,21 @@ pub fn optimize<'gc>(
stack.pop_for_multiname(*multiname);
let stack_value = stack.pop_or_any();
if !multiname.has_lazy_component() {
if let Some(class) = stack_value.class {
if !class.inner_class_definition().is_interface() {
match class.instance_vtable().get_trait(multiname) {
if let Some(vtable) = stack_value.vtable() {
match vtable.get_trait(multiname) {
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 mut value_class = 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 set_value_class.inner_class_definition()
== slot_class
{
if set_value_class == slot_class {
*op = Op::SetSlotNoCoerce { index: slot_id };
}
}
@ -989,7 +1029,6 @@ pub fn optimize<'gc>(
}
}
}
}
// `stack_pop_multiname` handled lazy
}
Op::SetProperty { multiname } => {
@ -998,24 +1037,20 @@ pub fn optimize<'gc>(
stack.pop_for_multiname(*multiname);
let stack_value = stack.pop_or_any();
if !multiname.has_lazy_component() {
if let Some(class) = stack_value.class {
if !class.inner_class_definition().is_interface() {
match class.instance_vtable().get_trait(multiname) {
if let Some(vtable) = stack_value.vtable() {
match 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 mut value_class = 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 set_value_class.inner_class_definition()
== slot_class
{
if set_value_class == slot_class {
*op = Op::SetSlotNoCoerce { index: slot_id };
}
}
@ -1038,7 +1073,6 @@ pub fn optimize<'gc>(
}
}
}
}
// `stack_pop_multiname` handled lazy
}
Op::DeleteProperty { multiname } => {
@ -1128,9 +1162,8 @@ pub fn optimize<'gc>(
let stack_value = stack.pop_or_any();
if !multiname.has_lazy_component() {
if let Some(class) = stack_value.class {
if !class.inner_class_definition().is_interface() {
match class.instance_vtable().get_trait(multiname) {
if let Some(vtable) = stack_value.vtable() {
match vtable.get_trait(multiname) {
Some(Property::Method { disp_id }) => {
*op = Op::CallMethod {
num_args: *num_args,
@ -1142,7 +1175,6 @@ pub fn optimize<'gc>(
}
}
}
}
// `stack_pop_multiname` handled lazy
// Avoid checking return value for now
@ -1161,9 +1193,8 @@ pub fn optimize<'gc>(
let stack_value = stack.pop_or_any();
if !multiname.has_lazy_component() {
if let Some(class) = stack_value.class {
if !class.inner_class_definition().is_interface() {
match class.instance_vtable().get_trait(multiname) {
if let Some(vtable) = stack_value.vtable() {
match vtable.get_trait(multiname) {
Some(Property::Method { disp_id }) => {
*op = Op::CallMethod {
num_args: *num_args,
@ -1175,7 +1206,6 @@ pub fn optimize<'gc>(
}
}
}
}
// `stack_pop_multiname` handled lazy
}
Op::GetSuper { multiname } => {
@ -1246,7 +1276,6 @@ pub fn optimize<'gc>(
let global_scope = outer_scope.get_unchecked(0);
if let Some(class) = global_scope.values().instance_of() {
if !class.inner_class_definition().is_interface() {
let mut value_class =
class.instance_vtable().slot_classes()[*slot_id as usize];
let resolved_value_class = value_class.get_class(activation);
@ -1267,7 +1296,6 @@ pub fn optimize<'gc>(
);
}
}
}
if !stack_push_done {
stack.push_any();
@ -1348,7 +1376,7 @@ pub fn optimize<'gc>(
if let Some(return_type) = return_type {
if let Some(stack_value_class) = stack_value.class {
if stack_value_class.inner_class_definition() == return_type {
if stack_value_class == return_type {
*op = Op::ReturnValueNoCoerce;
}
}

View File

@ -229,6 +229,7 @@ impl<'gc> TranslationUnit<'gc> {
self.0.write(activation.context.gc_context).classes[class_index as usize] = Some(class);
class.load_traits(activation, self, class_index)?;
class.init_vtable(&mut activation.context)?;
Ok(class)
}
@ -258,8 +259,14 @@ impl<'gc> TranslationUnit<'gc> {
let global_obj = global_class.construct(activation, &[])?;
let mut script =
Script::from_abc_index(self, script_index, global_obj, domain, activation)?;
let mut script = Script::from_abc_index(
self,
script_index,
global_obj,
global_classdef,
domain,
activation,
)?;
self.0.write(activation.context.gc_context).scripts[script_index as usize] = Some(script);
script.load_traits(self, script_index, activation)?;
@ -416,6 +423,9 @@ pub struct ScriptData<'gc> {
/// The global object for the script.
globals: Object<'gc>,
/// The class of this script's global object.
global_class: Option<Class<'gc>>,
/// The domain associated with this script.
domain: Domain<'gc>,
@ -451,6 +461,7 @@ impl<'gc> Script<'gc> {
mc,
ScriptData {
globals,
global_class: None,
domain,
init: Method::from_builtin(
|_, _, _| Ok(Value::Undefined),
@ -478,6 +489,7 @@ impl<'gc> Script<'gc> {
unit: TranslationUnit<'gc>,
script_index: u32,
globals: Object<'gc>,
global_class: Class<'gc>,
domain: Domain<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Result<Self, Error<'gc>> {
@ -494,6 +506,7 @@ impl<'gc> Script<'gc> {
activation.context.gc_context,
ScriptData {
globals,
global_class: Some(global_class),
domain,
init,
traits: Vec::new(),
@ -542,9 +555,19 @@ impl<'gc> Script<'gc> {
.export_class(newtrait.name(), *class, activation.context.gc_context);
}
write.traits.push(newtrait);
write.traits.push(newtrait.clone());
write
.global_class
.expect("Global class should be initialized")
.define_instance_trait(activation.context.gc_context, newtrait);
}
drop(write);
self.global_class()
.mark_traits_loaded(activation.context.gc_context);
self.global_class().init_vtable(&mut activation.context)?;
Ok(())
}
@ -562,6 +585,17 @@ impl<'gc> Script<'gc> {
self.0.read().translation_unit
}
pub fn global_class(self) -> Class<'gc> {
self.0
.read()
.global_class
.expect("Global class should be initialized if it is accessed")
}
pub fn set_global_class(self, mc: &Mutation<'gc>, global_class: Class<'gc>) {
self.0.write(mc).global_class = Some(global_class);
}
/// Return the global scope for the script.
///
/// If the script has not yet been initialized, this will initialize it on
@ -581,12 +615,17 @@ impl<'gc> Script<'gc> {
let scope = ScopeChain::new(domain);
globals.vtable().unwrap().init_vtable(
globals.instance_of().unwrap(),
globals.instance_of(),
globals
.instance_of()
.unwrap()
.inner_class_definition()
.protected_namespace(),
&self.traits()?,
scope,
Some(scope),
None,
&mut null_activation,
)?;
&mut null_activation.context,
);
globals.install_instance_slots(context.gc_context);
Avm2::run_script_initializer(*self, context)?;

View File

@ -11,6 +11,7 @@ use crate::avm2::Error;
use crate::avm2::Multiname;
use crate::avm2::Namespace;
use crate::avm2::QName;
use crate::context::UpdateContext;
use crate::string::AvmString;
use gc_arena::{Collect, GcCell, Mutation};
use std::cell::Ref;
@ -24,10 +25,6 @@ pub struct VTable<'gc>(GcCell<'gc, VTableData<'gc>>);
#[derive(Collect, Clone)]
#[collect(no_drop)]
pub struct VTableData<'gc> {
/// should always be Some post-initialization
defining_class: Option<ClassObject<'gc>>,
/// should always be Some post-initialization
scope: Option<ScopeChain<'gc>>,
protected_namespace: Option<Namespace<'gc>>,
@ -55,8 +52,8 @@ pub struct VTableData<'gc> {
#[derive(Collect, Clone)]
#[collect(no_drop)]
pub struct ClassBoundMethod<'gc> {
pub class: ClassObject<'gc>,
pub scope: ScopeChain<'gc>,
pub class: Option<ClassObject<'gc>>,
pub scope: Option<ScopeChain<'gc>>,
pub method: Method<'gc>,
}
@ -65,7 +62,6 @@ impl<'gc> VTable<'gc> {
VTable(GcCell::new(
mc,
VTableData {
defining_class: None,
scope: None,
protected_namespace: None,
resolved_traits: PropertyMap::new(),
@ -87,7 +83,6 @@ impl<'gc> VTable<'gc> {
let vt = VTable(GcCell::new(
mc,
VTableData {
defining_class: None,
scope: None,
protected_namespace: None,
resolved_traits: rt,
@ -106,10 +101,6 @@ impl<'gc> VTable<'gc> {
vt
}
pub fn duplicate(self, mc: &Mutation<'gc>) -> Self {
VTable(GcCell::new(mc, self.0.read().clone()))
}
pub fn resolved_traits(&self) -> Ref<'_, PropertyMap<'gc, Property>> {
Ref::map(self.0.read(), |v| &v.resolved_traits)
}
@ -220,12 +211,13 @@ impl<'gc> VTable<'gc> {
#[allow(clippy::if_same_then_else)]
pub fn init_vtable(
self,
defining_class: ClassObject<'gc>,
defining_class: Option<ClassObject<'gc>>,
protected_namespace: Option<Namespace<'gc>>,
traits: &[Trait<'gc>],
scope: ScopeChain<'gc>,
scope: Option<ScopeChain<'gc>>,
superclass_vtable: Option<Self>,
activation: &mut Activation<'_, 'gc>,
) -> Result<(), Error<'gc>> {
context: &mut UpdateContext<'_, 'gc>,
) {
// Let's talk about slot_ids and disp_ids.
// Specification is one thing, but reality is another.
@ -277,15 +269,12 @@ impl<'gc> VTable<'gc> {
// so long-term it's still something we should verify.
// (and it's far from the only verification check we lack anyway)
let mut write = self.0.write(activation.context.gc_context);
let mut write = self.0.write(context.gc_context);
let write = write.deref_mut();
write.defining_class = Some(defining_class);
write.scope = Some(scope);
write.scope = scope;
write.protected_namespace = defining_class
.inner_class_definition()
.protected_namespace();
write.protected_namespace = protected_namespace;
if let Some(superclass_vtable) = superclass_vtable {
write.resolved_traits = superclass_vtable.0.read().resolved_traits.clone();
@ -434,11 +423,10 @@ impl<'gc> VTable<'gc> {
}
TraitKind::Slot { slot_id, .. }
| TraitKind::Const { slot_id, .. }
| TraitKind::Function { slot_id, .. }
| TraitKind::Class { slot_id, .. } => {
let slot_id = *slot_id;
let value = trait_to_default_value(scope, trait_data, activation);
let value = trait_to_default_value(trait_data);
let value = Some(value);
let new_slot_id = if slot_id == 0 {
@ -467,36 +455,18 @@ impl<'gc> VTable<'gc> {
type_name, unit, ..
} => (
Property::new_slot(new_slot_id),
PropertyClass::name(
activation.context.gc_context,
type_name.clone(),
*unit,
),
),
TraitKind::Function { .. } => (
Property::new_slot(new_slot_id),
PropertyClass::Class(
activation
.avm2()
.classes()
.function
.inner_class_definition(),
),
PropertyClass::name(context.gc_context, type_name.clone(), *unit),
),
TraitKind::Const {
type_name, unit, ..
} => (
Property::new_const_slot(new_slot_id),
PropertyClass::name(
activation.context.gc_context,
type_name.clone(),
*unit,
),
PropertyClass::name(context.gc_context, type_name.clone(), *unit),
),
TraitKind::Class { .. } => (
Property::new_const_slot(new_slot_id),
PropertyClass::Class(
activation.avm2().classes().class.inner_class_definition(),
context.avm2.classes().class.inner_class_definition(),
),
),
_ => unreachable!(),
@ -510,10 +480,9 @@ impl<'gc> VTable<'gc> {
slot_classes[new_slot_id as usize] = new_class;
}
TraitKind::Function { .. } => panic!("TraitKind::Function shouldn't appear"),
}
}
Ok(())
}
/// Retrieve a bound instance method suitable for use as a value.
@ -548,7 +517,13 @@ impl<'gc> VTable<'gc> {
method,
} = method;
FunctionObject::from_method(activation, method, scope, Some(receiver), Some(class))
FunctionObject::from_method(
activation,
method,
scope.expect("Scope should exist here"),
Some(receiver),
class,
)
}
/// Install a const trait on the global object.
@ -606,20 +581,11 @@ impl<'gc> VTable<'gc> {
}
}
fn trait_to_default_value<'gc>(
scope: ScopeChain<'gc>,
trait_data: &Trait<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> Value<'gc> {
fn trait_to_default_value<'gc>(trait_data: &Trait<'gc>) -> Value<'gc> {
match trait_data.kind() {
TraitKind::Slot { default_value, .. } => *default_value,
TraitKind::Const { default_value, .. } => *default_value,
TraitKind::Function { function, .. } => {
FunctionObject::from_function(activation, *function, scope)
.unwrap()
.into()
}
TraitKind::Class { .. } => Value::Undefined,
TraitKind::Class { .. } => Value::Null,
_ => unreachable!(),
}
}