Skip to content

Commit

Permalink
avm2: Split Class into a i_class Class and a c_class Class
Browse files Browse the repository at this point in the history
  • Loading branch information
Lord-McSweeney authored and Lord-McSweeney committed Jun 19, 2024
1 parent 68d2a61 commit 5480caf
Show file tree
Hide file tree
Showing 22 changed files with 589 additions and 192 deletions.
372 changes: 303 additions & 69 deletions core/src/avm2/class.rs

Large diffs are not rendered by default.

27 changes: 17 additions & 10 deletions core/src/avm2/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,12 +231,19 @@ pub fn display_function<'gc>(
method: &Method<'gc>,
superclass: Option<ClassObject<'gc>>,
) {
let class_def = superclass.map(|superclass| {
let class_def = superclass.inner_class_definition();
let name = class_def.name().to_qualified_name_no_mc();
let class_defs = superclass.map(|superclass| {
let i_class = superclass.inner_class_definition();
let name = i_class.name().to_qualified_name_no_mc();
output.push_str(&name);
class_def

(
i_class,
i_class
.c_class()
.expect("inner_class_definition should be an i_class"),
)
});

match method {
Method::Native(method) => {
output.push_char('/');
Expand All @@ -245,15 +252,15 @@ pub fn display_function<'gc>(
Method::Bytecode(method) => {
// NOTE: The name of a bytecode method refers to the name of the trait that contains the method,
// rather than the name of the method itself.
if let Some(class_def) = class_def {
if class_def
.class_init()
if let Some((i_class, c_class)) = class_defs {
if c_class
.instance_init()
.into_bytecode()
.map(|b| Gc::ptr_eq(b, *method))
.unwrap_or(false)
{
output.push_utf8("$cinit");
} else if !class_def
} else if !i_class
.instance_init()
.into_bytecode()
.map(|b| Gc::ptr_eq(b, *method))
Expand All @@ -264,7 +271,7 @@ pub fn display_function<'gc>(
let mut method_trait = None;

// First search instance traits for the method
let instance_traits = class_def.instance_traits();
let instance_traits = i_class.traits();
for t in &*instance_traits {
if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) {
if Gc::ptr_eq(b, *method) {
Expand All @@ -274,7 +281,7 @@ pub fn display_function<'gc>(
}
}

let class_traits = class_def.class_traits();
let class_traits = c_class.traits();
if method_trait.is_none() {
// If we can't find it in instance traits, search class traits instead
for t in class_traits.iter() {
Expand Down
55 changes: 28 additions & 27 deletions core/src/avm2/globals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,32 +486,34 @@ pub fn load_player_globals<'gc>(
// - Function's prototype is an instance of itself
// - All methods created by the above-mentioned classes are also instances
// of Function
// - All classes are put on Global's trait list, but Global needs
// to be initialized first, but you can't do that until Object/Class are ready.
//
// Hence, this ridiculously complicated dance of classdef, type allocation,
// and partial initialization.
let object_classdef = object::create_class(activation);
let object_class = ClassObject::from_class_partial(activation, object_classdef, None)?;
let object_proto = ScriptObject::custom_object(mc, Some(object_class), None);
domain.export_class(object_classdef.name(), object_classdef, mc);
let object_i_class = object::create_i_class(activation);
let class_i_class = class::create_i_class(activation, object_i_class);

let fn_classdef = function::create_class(activation, object_classdef);
let fn_class = ClassObject::from_class_partial(activation, fn_classdef, Some(object_class))?;
let fn_proto = ScriptObject::custom_object(mc, Some(fn_class), Some(object_proto));
domain.export_class(fn_classdef.name(), fn_classdef, mc);
let object_c_class = object::create_c_class(activation, class_i_class);
object_i_class.set_c_class(mc, object_c_class);
object_c_class.set_i_class(mc, object_i_class);

let class_c_class = class::create_c_class(activation, class_i_class);
class_i_class.set_c_class(mc, class_c_class);
class_c_class.set_i_class(mc, class_i_class);

let object_class = ClassObject::from_class_partial(activation, object_i_class, None)?;
let object_proto = ScriptObject::custom_object(mc, Some(object_class), None);
domain.export_class(object_i_class.name(), object_i_class, mc);

let class_classdef = class::create_class(activation, object_classdef);
let class_class =
ClassObject::from_class_partial(activation, class_classdef, Some(object_class))?;
ClassObject::from_class_partial(activation, class_i_class, Some(object_class))?;
let class_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto));
domain.export_class(class_classdef.name(), class_classdef, mc);
domain.export_class(class_i_class.name(), class_i_class, mc);

let global_classdef = global_scope::create_class(activation, object_classdef);
let global_class =
ClassObject::from_class_partial(activation, global_classdef, Some(object_class))?;
let global_proto = ScriptObject::custom_object(mc, Some(object_class), Some(object_proto));
domain.export_class(global_classdef.name(), global_classdef, mc);
// Function is more of a "normal" class than the other two, so we can create it normally.
let fn_classdef = function::create_class(activation, object_i_class, class_i_class);
let fn_class = ClassObject::from_class_partial(activation, fn_classdef, Some(object_class))?;
let fn_proto = ScriptObject::custom_object(mc, Some(fn_class), Some(object_proto));
domain.export_class(fn_classdef.name(), fn_classdef, mc);

// Now to weave the Gordian knot...
object_class.link_prototype(activation, object_proto)?;
Expand All @@ -523,9 +525,6 @@ pub fn load_player_globals<'gc>(
class_class.link_prototype(activation, class_proto)?;
class_class.link_type(mc, class_proto, class_class);

global_class.link_prototype(activation, global_proto)?;
global_class.link_type(mc, class_proto, class_class);

// At this point, we need at least a partial set of system classes in
// order to continue initializing the player. The rest of the classes
// are set to a temporary class until we have a chance to initialize them.
Expand All @@ -540,22 +539,24 @@ pub fn load_player_globals<'gc>(

// First, initialize the instance vtable, starting with `Object`. This
// ensures that properties defined in `Object` (e.g. `hasOwnProperty`)
// get copied into the vtable of `Class`, `Function`, and `Global`.
// (which are all subclasses of `Object`)
// get copied into the vtable of `Class` and `Function` (which are both
// subclasses of `Object`).
object_class.init_instance_vtable(activation)?;
class_class.init_instance_vtable(activation)?;
fn_class.init_instance_vtable(activation)?;
global_class.init_instance_vtable(activation)?;

// Now, construct the `ClassObject`s, starting with `Class`. This ensures
// that the `prototype` property of `Class` gets copied into the *class*
// vtables for `Object`, `Function`, and `Global`.
// vtables for `Object` and `Function`.
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)?;

globals.set_proto(mc, global_proto);
// Construct the global class.
let global_classdef = global_scope::create_class(activation);
let global_class = ClassObject::from_class(activation, global_classdef, Some(object_class))?;

globals.set_proto(mc, global_class.prototype());
globals.set_instance_of(mc, global_class);

activation.context.avm2.toplevel_global_object = Some(globals);
Expand Down
8 changes: 8 additions & 0 deletions core/src/avm2/globals/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
Some(activation.avm2().classes().object.inner_class_definition()),
Method::from_builtin(instance_init, "<Array instance initializer>", mc),
Method::from_builtin(class_init, "<Array class initializer>", mc),
activation.avm2().classes().class.inner_class_definition(),
mc,
);

Expand Down Expand Up @@ -1306,5 +1307,12 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

let c_class = class.c_class().expect("Class::new returns an i_class");

c_class.mark_traits_loaded(activation.context.gc_context);
c_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

class
}
2 changes: 1 addition & 1 deletion core/src/avm2/globals/avmplus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ fn describe_internal_body<'gc>(
}

let declared_by_name = declared_by
.name()
.dollar_removed_name(activation.context.gc_context)
.to_qualified_name(activation.context.gc_context);

let trait_metadata = vtable.get_metadata_for_disp(disp_id);
Expand Down
8 changes: 8 additions & 0 deletions core/src/avm2/globals/boolean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
Some(activation.avm2().classes().object.inner_class_definition()),
Method::from_builtin(instance_init, "<Boolean instance initializer>", mc),
Method::from_builtin(class_init, "<Boolean class initializer>", mc),
activation.avm2().classes().class.inner_class_definition(),
mc,
);

Expand Down Expand Up @@ -175,5 +176,12 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

let c_class = class.c_class().expect("Class::new returns an i_class");

c_class.mark_traits_loaded(activation.context.gc_context);
c_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

class
}
58 changes: 39 additions & 19 deletions core/src/avm2/globals/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,44 +41,64 @@ fn prototype<'gc>(
Ok(Value::Undefined)
}

/// Construct `Class`'s class.
pub fn create_class<'gc>(
/// Construct `Class`'s i_class.
pub fn create_i_class<'gc>(
activation: &mut Activation<'_, 'gc>,
object_class: Class<'gc>,
object_i_class: Class<'gc>,
) -> Class<'gc> {
let gc_context = activation.context.gc_context;
let class_class = Class::new(
let class_i_class = Class::custom_new(
QName::new(activation.avm2().public_namespace_base_version, "Class"),
Some(object_class),
Some(object_i_class),
Method::from_builtin(instance_init, "<Class instance initializer>", gc_context),
Method::from_builtin(class_init, "<Class class initializer>", gc_context),
gc_context,
);

// 'length' is a weird undocumented constant in Class.
// We need to define it, since it shows up in 'describeType'
const CLASS_CONSTANTS: &[(&str, i32)] = &[("length", 1)];
class_class.define_constant_int_class_traits(
activation.avm2().public_namespace_base_version,
CLASS_CONSTANTS,
activation,
);

const PUBLIC_INSTANCE_PROPERTIES: &[(
&str,
Option<NativeMethodImpl>,
Option<NativeMethodImpl>,
)] = &[("prototype", Some(prototype), None)];
class_class.define_builtin_instance_properties(
class_i_class.define_builtin_instance_properties(
gc_context,
activation.avm2().public_namespace_base_version,
PUBLIC_INSTANCE_PROPERTIES,
);

class_class.mark_traits_loaded(activation.context.gc_context);
class_class
class_i_class.mark_traits_loaded(activation.context.gc_context);
class_i_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

class_i_class
}

/// Construct `Class`'s c_class.
pub fn create_c_class<'gc>(
activation: &mut Activation<'_, 'gc>,
class_i_class: Class<'gc>,
) -> Class<'gc> {
let gc_context = activation.context.gc_context;
let class_c_class = Class::custom_new(
QName::new(activation.avm2().public_namespace_base_version, "Class$"),
Some(class_i_class),
Method::from_builtin(class_init, "<Class class initializer>", gc_context),
gc_context,
);

// 'length' is a weird undocumented constant in Class.
// We need to define it, since it shows up in 'describeType'
const CLASS_CONSTANTS: &[(&str, i32)] = &[("length", 1)];
class_c_class.define_constant_int_instance_traits(
activation.avm2().public_namespace_base_version,
CLASS_CONSTANTS,
activation,
);

class_c_class.mark_traits_loaded(activation.context.gc_context);
class_c_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

class_class
class_c_class
}
8 changes: 8 additions & 0 deletions core/src/avm2/globals/date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1328,6 +1328,7 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
Some(activation.avm2().classes().object.inner_class_definition()),
Method::from_builtin(instance_init, "<Date instance initializer>", mc),
Method::from_builtin(class_init, "<Date class initializer>", mc),
activation.avm2().classes().class.inner_class_definition(),
mc,
);

Expand Down Expand Up @@ -1397,5 +1398,12 @@ pub fn create_class<'gc>(activation: &mut Activation<'_, 'gc>) -> Class<'gc> {
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

let c_class = class.c_class().expect("Class::new returns an i_class");

c_class.mark_traits_loaded(activation.context.gc_context);
c_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

class
}
37 changes: 26 additions & 11 deletions core/src/avm2/globals/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -222,20 +222,30 @@ fn set_prototype<'gc>(
/// Construct `Function`'s class.
pub fn create_class<'gc>(
activation: &mut Activation<'_, 'gc>,
object_classdef: Class<'gc>,
object_i_class: Class<'gc>,
class_i_class: Class<'gc>,
) -> Class<'gc> {
let gc_context = activation.context.gc_context;
let function_class = Class::new(
let function_i_class = Class::custom_new(
QName::new(activation.avm2().public_namespace_base_version, "Function"),
Some(object_classdef),
Some(object_i_class),
Method::from_builtin(instance_init, "<Function instance initializer>", gc_context),
gc_context,
);

let function_c_class = Class::custom_new(
QName::new(activation.avm2().public_namespace_base_version, "Function"),
Some(class_i_class),
Method::from_builtin(class_init, "<Function class initializer>", gc_context),
gc_context,
);

function_i_class.set_c_class(gc_context, function_c_class);
function_c_class.set_i_class(gc_context, function_i_class);

// Fixed traits (in AS3 namespace)
const AS3_INSTANCE_METHODS: &[(&str, NativeMethodImpl)] = &[("call", call), ("apply", apply)];
function_class.define_builtin_instance_methods(
function_i_class.define_builtin_instance_methods(
gc_context,
activation.avm2().as3_namespace,
AS3_INSTANCE_METHODS,
Expand All @@ -249,29 +259,34 @@ pub fn create_class<'gc>(
("prototype", Some(prototype), Some(set_prototype)),
("length", Some(length), None),
];
function_class.define_builtin_instance_properties(
function_i_class.define_builtin_instance_properties(
gc_context,
activation.avm2().public_namespace_base_version,
PUBLIC_INSTANCE_PROPERTIES,
);

const CONSTANTS_INT: &[(&str, i32)] = &[("length", 1)];
function_class.define_constant_int_class_traits(
function_c_class.define_constant_int_instance_traits(
activation.avm2().public_namespace_base_version,
CONSTANTS_INT,
activation,
);

function_class.set_instance_allocator(gc_context, function_allocator);
function_class.set_call_handler(
function_i_class.set_instance_allocator(gc_context, function_allocator);
function_i_class.set_call_handler(
gc_context,
Method::from_builtin(class_call, "<Function call handler>", gc_context),
);

function_class.mark_traits_loaded(activation.context.gc_context);
function_class
function_i_class.mark_traits_loaded(activation.context.gc_context);
function_i_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

function_c_class.mark_traits_loaded(activation.context.gc_context);
function_c_class
.init_vtable(&mut activation.context)
.expect("Native class's vtable should initialize");

function_class
function_i_class
}
Loading

0 comments on commit 5480caf

Please sign in to comment.