avm2: Reduce size of `CallNode` from 56 bytes to 24

This commit is contained in:
EmperorBale 2023-03-21 18:07:32 -07:00 committed by Bale
parent 1d12fc6169
commit a63ee977fa
3 changed files with 114 additions and 90 deletions

View File

@ -452,7 +452,7 @@ impl<'gc> Avm2<'gc> {
}
/// Pushes an executable on the call stack
pub fn push_call(&self, mc: MutationContext<'gc, '_>, calling: Executable<'gc>) {
pub fn push_call(&self, mc: MutationContext<'gc, '_>, calling: &Executable<'gc>) {
self.call_stack.write(mc).push(calling)
}

View File

@ -1,4 +1,6 @@
use crate::avm2::function::Executable;
use crate::avm2::function::{display_function, Executable};
use crate::avm2::method::Method;
use crate::avm2::object::ClassObject;
use crate::string::WString;
use gc_arena::Collect;
@ -8,7 +10,10 @@ use super::script::Script;
#[collect(no_drop)]
pub enum CallNode<'gc> {
GlobalInit(Script<'gc>),
Method(Executable<'gc>),
Method {
method: Method<'gc>,
superclass: Option<ClassObject<'gc>>,
},
}
#[derive(Collect, Clone)]
@ -22,8 +27,11 @@ impl<'gc> CallStack<'gc> {
Self { stack: Vec::new() }
}
pub fn push(&mut self, exec: Executable<'gc>) {
self.stack.push(CallNode::Method(exec))
pub fn push(&mut self, exec: &Executable<'gc>) {
self.stack.push(CallNode::Method {
method: exec.as_method(),
superclass: exec.bound_superclass(),
})
}
pub fn push_global_init(&mut self, script: Script<'gc>) {
@ -54,7 +62,9 @@ impl<'gc> CallStack<'gc> {
// added by Ruffle
output.push_utf8(&format!("global$init() [TU={}]", name));
}
CallNode::Method(exec) => exec.write_full_name(output),
CallNode::Method { method, superclass } => {
display_function(output, method, *superclass)
}
}
}
}

View File

@ -141,7 +141,7 @@ impl<'gc> Executable<'gc> {
activation
.context
.avm2
.push_call(activation.context.gc_context, self.clone());
.push_call(activation.context.gc_context, self);
method(&mut activation, receiver, &arguments)
}
Executable::Action(bm) => {
@ -167,7 +167,7 @@ impl<'gc> Executable<'gc> {
activation
.context
.avm2
.push_call(activation.context.gc_context, self.clone());
.push_call(activation.context.gc_context, self);
activation.run_actions(bm.method)
}
};
@ -189,90 +189,15 @@ impl<'gc> Executable<'gc> {
}
}
pub fn write_full_name(&self, output: &mut WString) {
let class_def = self.bound_superclass().map(|superclass| {
let class_def = superclass.inner_class_definition();
let name = class_def.read().name().to_qualified_name_no_mc();
output.push_str(&name);
class_def
});
pub fn as_method(&self) -> Method<'gc> {
match self {
Executable::Native(NativeExecutable { method, .. }) => {
output.push_char('/');
output.push_utf8(method.name)
}
Executable::Action(BytecodeExecutable { 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
.read()
.class_init()
.into_bytecode()
.map(|b| Gc::ptr_eq(b, *method))
.unwrap_or(false)
{
output.push_utf8("$cinit");
} else if !class_def
.read()
.instance_init()
.into_bytecode()
.map(|b| Gc::ptr_eq(b, *method))
.unwrap_or(false)
{
// TODO: Ideally, the declaring trait of this executable should already be attached here, that way
// we can avoid needing to lookup the trait like this.
let class_def = class_def.read();
let mut method_trait = None;
// First search instance traits for the method
for t in class_def.instance_traits() {
if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) {
if Gc::ptr_eq(b, *method) {
method_trait = Some(t);
break;
}
}
}
if method_trait.is_none() {
// If we can't find it in instance traits, search class traits instead
for t in class_def.class_traits() {
if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok())
{
if Gc::ptr_eq(b, *method) {
// Class traits always start with $
output.push_char('$');
method_trait = Some(t);
break;
}
}
}
}
if let Some(method_trait) = method_trait {
output.push_char('/');
match method_trait.kind() {
TraitKind::Setter { .. } => output.push_utf8("set "),
TraitKind::Getter { .. } => output.push_utf8("get "),
_ => (),
}
if method_trait.name().namespace().is_namespace() {
output.push_str(&method_trait.name().to_qualified_name_no_mc());
} else {
output.push_str(&method_trait.name().local_name());
}
}
// TODO: What happens if we can't find the trait?
}
// We purposely do nothing for instance initializers
} else if method.is_function && !method.method_name().is_empty() {
output.push_utf8("Function/");
output.push_utf8(method.method_name());
} else {
output.push_utf8("MethodInfo-");
output.push_utf8(&method.abc_method.to_string());
}
}
Executable::Native(nm) => Method::Native(nm.method),
Executable::Action(bm) => Method::Bytecode(bm.method),
}
output.push_utf8("()");
}
pub fn write_full_name(&self, output: &mut WString) {
display_function(output, &self.as_method(), self.bound_superclass());
}
pub fn num_parameters(&self) -> usize {
@ -300,3 +225,92 @@ impl<'gc> fmt::Debug for Executable<'gc> {
}
}
}
pub fn display_function<'gc>(
output: &mut WString,
method: &Method<'gc>,
superclass: Option<ClassObject<'gc>>,
) {
let class_def = superclass.map(|superclass| {
let class_def = superclass.inner_class_definition();
let name = class_def.read().name().to_qualified_name_no_mc();
output.push_str(&name);
class_def
});
match method {
Method::Native(method) => {
output.push_char('/');
output.push_utf8(method.name)
}
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
.read()
.class_init()
.into_bytecode()
.map(|b| Gc::ptr_eq(b, *method))
.unwrap_or(false)
{
output.push_utf8("$cinit");
} else if !class_def
.read()
.instance_init()
.into_bytecode()
.map(|b| Gc::ptr_eq(b, *method))
.unwrap_or(false)
{
// TODO: Ideally, the declaring trait of this executable should already be attached here, that way
// we can avoid needing to lookup the trait like this.
let class_def = class_def.read();
let mut method_trait = None;
// First search instance traits for the method
for t in class_def.instance_traits() {
if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) {
if Gc::ptr_eq(b, *method) {
method_trait = Some(t);
break;
}
}
}
if method_trait.is_none() {
// If we can't find it in instance traits, search class traits instead
for t in class_def.class_traits() {
if let Some(b) = t.as_method().and_then(|m| m.into_bytecode().ok()) {
if Gc::ptr_eq(b, *method) {
// Class traits always start with $
output.push_char('$');
method_trait = Some(t);
break;
}
}
}
}
if let Some(method_trait) = method_trait {
output.push_char('/');
match method_trait.kind() {
TraitKind::Setter { .. } => output.push_utf8("set "),
TraitKind::Getter { .. } => output.push_utf8("get "),
_ => (),
}
if method_trait.name().namespace().is_namespace() {
output.push_str(&method_trait.name().to_qualified_name_no_mc());
} else {
output.push_str(&method_trait.name().local_name());
}
}
// TODO: What happens if we can't find the trait?
}
// We purposely do nothing for instance initializers
} else if method.is_function && !method.method_name().is_empty() {
output.push_utf8("Function/");
output.push_utf8(method.method_name());
} else {
output.push_utf8("MethodInfo-");
output.push_utf8(&method.abc_method.to_string());
}
}
}
output.push_utf8("()");
}