core: Clean up `DefineFunction` action

* Have `DefineFunction` and `DefineFunction2` go through the same
   code path by implementing `From<DefineFunction>` for
   `DefineFunction2`.
 * Change `register` to a `Option<NonZeroU8>` for size optimization.
 * Add `function::Param` to store param info instead of a tuple.
This commit is contained in:
Mike Welsh 2022-01-16 17:36:09 -08:00
parent c618a12d14
commit 4c3e14eae5
5 changed files with 72 additions and 116 deletions

View File

@ -486,8 +486,8 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Action::CloneSprite => self.action_clone_sprite(),
Action::ConstantPool(action) => self.action_constant_pool(action),
Action::Decrement => self.action_decrement(),
Action::DefineFunction(action) => self.action_define_function(action, data),
Action::DefineFunction2(action) => self.action_define_function_2(action, data),
Action::DefineFunction(action) => self.action_define_function(action.into(), data),
Action::DefineFunction2(action) => self.action_define_function(action, data),
Action::DefineLocal => self.action_define_local(),
Action::DefineLocal2 => self.action_define_local_2(),
Action::Delete => self.action_delete(),
@ -863,23 +863,21 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
fn action_define_function(
&mut self,
action: DefineFunction,
action: DefineFunction2,
parent_data: &SwfSlice,
) -> Result<FrameControl<'gc>, Error<'gc>> {
let name = action.name.to_str_lossy(self.encoding());
let name = name.as_ref();
let swf_version = self.swf_version();
let func_data = parent_data.to_unbounded_subslice(action.actions).unwrap();
let scope = Scope::new_closure_scope(self.scope_cell(), self.context.gc_context);
let constant_pool = self.constant_pool();
let func = Avm1Function::from_df1(
let func = Avm1Function::from_swf_function(
self.context.gc_context,
swf_version,
parent_data.to_unbounded_subslice(action.actions).unwrap(),
name,
&action.params[..],
func_data,
action,
scope,
constant_pool,
self.target_clip_or_root()?,
self.base_clip(),
);
let name = func.name();
let prototype = ScriptObject::object(
@ -902,46 +900,6 @@ impl<'a, 'gc, 'gc_context> Activation<'a, 'gc, 'gc_context> {
Ok(FrameControl::Continue)
}
fn action_define_function_2(
&mut self,
action: DefineFunction2,
parent_data: &SwfSlice,
) -> Result<FrameControl<'gc>, Error<'gc>> {
let swf_version = self.swf_version();
let func_data = parent_data.to_unbounded_subslice(action.actions).unwrap();
let scope = Scope::new_closure_scope(self.scope_cell(), self.context.gc_context);
let constant_pool = self.constant_pool();
let func = Avm1Function::from_df2(
self.context.gc_context,
swf_version,
func_data,
&action,
scope,
constant_pool,
self.base_clip(),
);
let prototype = ScriptObject::object(
self.context.gc_context,
Some(self.context.avm1.prototypes.object),
)
.into();
let func_obj = FunctionObject::function(
self.context.gc_context,
Gc::allocate(self.context.gc_context, func),
Some(self.context.avm1.prototypes.function),
prototype,
);
if action.name.is_empty() {
self.context.avm1.push(func_obj.into());
} else {
let name = action.name.to_str_lossy(self.encoding());
let name = AvmString::new_utf8(self.context.gc_context, name);
self.define_local(name, func_obj.into())?;
}
Ok(FrameControl::Continue)
}
fn action_define_local(&mut self) -> Result<FrameControl<'gc>, Error<'gc>> {
// If the property does not exist on the local object's prototype chain, it is created on the local object.
// Otherwise, the property is set (including calling virtual setters).

View File

@ -10,8 +10,7 @@ use crate::avm1::{ArrayObject, AvmString, Object, ObjectPtr, ScriptObject, TObje
use crate::display_object::{DisplayObject, TDisplayObject};
use crate::tag_utils::SwfSlice;
use gc_arena::{Collect, CollectionContext, Gc, GcCell, MutationContext};
use std::borrow::Cow;
use std::fmt;
use std::{borrow::Cow, fmt, num::NonZeroU8};
use swf::{avm1::types::FunctionFlags, SwfStr};
/// Represents a function defined in Ruffle's code.
@ -62,10 +61,8 @@ pub struct Avm1Function<'gc> {
/// set. Any register beyond this ID will be served from the global one.
register_count: u8,
/// The names of the function parameters and their register mappings.
/// r0 indicates that no register shall be written and the parameter stored
/// as a Variable instead.
params: Vec<(Option<u8>, AvmString<'gc>)>,
/// The parameters of the function.
params: Vec<Param<'gc>>,
/// The scope the function was born into.
scope: GcCell<'gc, Scope<'gc>>,
@ -83,62 +80,21 @@ pub struct Avm1Function<'gc> {
}
impl<'gc> Avm1Function<'gc> {
/// Construct a function from a DefineFunction action.
///
/// Parameters not specified in DefineFunction are filled with reasonable
/// defaults.
#[allow(clippy::too_many_arguments)]
pub fn from_df1(
gc_context: MutationContext<'gc, '_>,
swf_version: u8,
actions: SwfSlice,
name: &str,
params: &[&'_ SwfStr],
scope: GcCell<'gc, Scope<'gc>>,
constant_pool: GcCell<'gc, Vec<Value<'gc>>>,
base_clip: DisplayObject<'gc>,
) -> Self {
let name = if name.is_empty() {
None
} else {
Some(AvmString::new_utf8(gc_context, name))
};
Avm1Function {
swf_version,
data: actions,
name,
register_count: 0,
params: params
.iter()
.map(|&s| {
let name = s.to_str_lossy(SwfStr::encoding_for_version(swf_version));
(None, AvmString::new_utf8(gc_context, name))
})
.collect(),
scope,
constant_pool,
base_clip,
flags: FunctionFlags::empty(),
}
}
/// Construct a function from a DefineFunction2 action.
pub fn from_df2(
pub fn from_swf_function(
gc_context: MutationContext<'gc, '_>,
swf_version: u8,
actions: SwfSlice,
swf_function: &swf::avm1::types::DefineFunction2,
swf_function: swf::avm1::types::DefineFunction2,
scope: GcCell<'gc, Scope<'gc>>,
constant_pool: GcCell<'gc, Vec<Value<'gc>>>,
base_clip: DisplayObject<'gc>,
) -> Self {
let encoding = SwfStr::encoding_for_version(swf_version);
let name = if swf_function.name.is_empty() {
None
} else {
let name = swf_function
.name
.to_str_lossy(SwfStr::encoding_for_version(swf_version));
let name = swf_function.name.to_str_lossy(encoding);
Some(AvmString::new_utf8(gc_context, name))
};
@ -146,10 +102,11 @@ impl<'gc> Avm1Function<'gc> {
.params
.iter()
.map(|p| {
let name = p
.name
.to_str_lossy(SwfStr::encoding_for_version(swf_version));
(p.register_index, AvmString::new_utf8(gc_context, name))
let name = p.name.to_str_lossy(encoding);
Param {
register: p.register_index,
name: AvmString::new_utf8(gc_context, name),
}
})
.collect();
@ -187,6 +144,22 @@ impl<'gc> Avm1Function<'gc> {
}
}
#[derive(Debug, Clone, Collect)]
#[collect(no_drop)]
struct Param<'gc> {
/// The register the argument will be preloaded into.
///
/// If `register` is `None`, then this parameter will be stored in a named variable in the
/// function activation and can be accessed using `GetVariable`/`SetVariable`.
/// Otherwise, the parameter is loaded into a register and must be accessed with
/// `Push`/`StoreRegister`.
#[collect(require_static)]
register: Option<NonZeroU8>,
/// The name of the parameter.
name: AvmString<'gc>,
}
/// Represents a function that can be defined in the Ruffle runtime or by the
/// AVM1 bytecode itself.
#[derive(Clone)]
@ -394,9 +367,10 @@ impl<'gc> Executable<'gc> {
//TODO: What happens if the argument registers clash with the
//preloaded registers? What gets done last?
for (param, value) in af.params.iter().zip(args_iter) {
match param {
(Some(argreg), _argname) => frame.set_local_register(*argreg, value),
(None, argname) => frame.force_define_local(*argname, value),
if let Some(register) = param.register {
frame.set_local_register(register.get(), value);
} else {
frame.force_define_local(param.name, value);
}
}

View File

@ -1,6 +1,7 @@
use crate::avm1::{opcode::OpCode, types::*};
use crate::error::{Error, Result};
use crate::extensions::ReadSwfExt;
use std::num::NonZeroU8;
pub struct Reader<'a> {
input: &'a [u8],
@ -235,10 +236,9 @@ impl<'a> Reader<'a> {
let flags = FunctionFlags::from_bits_truncate(self.read_u16()?);
let mut params = Vec::with_capacity(num_params as usize);
for _ in 0..num_params {
let register = self.read_u8()?;
params.push(FunctionParam {
register_index: NonZeroU8::new(self.read_u8()?),
name: self.read_str()?,
register_index: if register == 0 { None } else { Some(register) },
});
}
// code_length isn't included in the DefineFunction's length.

View File

@ -1,5 +1,6 @@
use crate::string::SwfStr;
use bitflags::bitflags;
use std::num::NonZeroU8;
#[derive(Clone, Debug, PartialEq)]
pub enum Action<'a> {
@ -127,10 +128,31 @@ pub struct DefineFunction2<'a> {
pub actions: &'a [u8],
}
impl<'a> From<DefineFunction<'a>> for DefineFunction2<'a> {
#[inline]
fn from(function: DefineFunction<'a>) -> Self {
let params = function
.params
.into_iter()
.map(|param| FunctionParam {
name: param,
register_index: None,
})
.collect();
Self {
name: function.name,
register_count: 0,
params,
flags: FunctionFlags::default(),
actions: function.actions,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct FunctionParam<'a> {
pub name: &'a SwfStr,
pub register_index: Option<u8>,
pub register_index: Option<NonZeroU8>,
}
bitflags! {
@ -147,6 +169,12 @@ bitflags! {
}
}
impl Default for FunctionFlags {
fn default() -> Self {
Self::empty()
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct GetUrl<'a> {
pub url: &'a SwfStr,

View File

@ -248,11 +248,7 @@ impl<W: Write> Writer<W> {
self.write_u8(action.register_count)?;
self.write_u16(action.flags.bits())?;
for param in &action.params {
self.write_u8(if let Some(n) = param.register_index {
n
} else {
0
})?;
self.write_u8(param.register_index.map(|n| n.get()).unwrap_or_default())?;
self.write_string(param.name)?;
}
self.write_u16(action.actions.len() as u16)?;