Skip to content

Commit

Permalink
avm2: Initial AVM2 interpreter (merge #404)
Browse files Browse the repository at this point in the history
Initial work on the AVM2 interpreter.
  • Loading branch information
Herschel committed Jul 20, 2020
2 parents 4967fb4 + 7adabc8 commit 1a5d7fe
Show file tree
Hide file tree
Showing 175 changed files with 9,543 additions and 83 deletions.
29 changes: 27 additions & 2 deletions core/src/avm1/string.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
use gc_arena::{Collect, Gc, MutationContext};
use std::cmp::{Eq, Ord, Ordering, PartialOrd};
use std::fmt;
use std::hash::{Hash, Hasher};
use std::ops::Deref;

#[derive(Debug, Clone, Collect)]
#[derive(Debug, Clone, Copy, Collect)]
#[collect(no_drop)]
enum Source<'gc> {
Owned(Gc<'gc, String>),
Static(&'static str),
}

#[derive(Debug, Clone, Collect)]
#[derive(Debug, Clone, Copy, Collect)]
#[collect(no_drop)]
pub struct AvmString<'gc> {
source: Source<'gc>,
Expand Down Expand Up @@ -78,6 +80,29 @@ impl<'gc> PartialEq<AvmString<'gc>> for AvmString<'gc> {
}
}

impl<'gc> Eq for AvmString<'gc> {}

impl<'gc> PartialOrd<AvmString<'gc>> for AvmString<'gc> {
fn partial_cmp(&self, other: &AvmString<'gc>) -> Option<Ordering> {
self.as_ref().partial_cmp(other.as_ref())
}
}

impl<'gc> Ord for AvmString<'gc> {
fn cmp(&self, other: &AvmString<'gc>) -> Ordering {
self.as_ref().cmp(other.as_ref())
}
}

impl<'gc> Hash for AvmString<'gc> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
{
self.as_ref().hash(state)
}
}

macro_rules! impl_eq {
($lhs:ty, $rhs: ty) => {
#[allow(unused_lifetimes)]
Expand Down
164 changes: 164 additions & 0 deletions core/src/avm2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
//! ActionScript Virtual Machine 2 (AS3) support

use crate::avm2::activation::Activation;
use crate::avm2::globals::SystemPrototypes;
use crate::avm2::object::{Object, TObject};
use crate::avm2::scope::Scope;
use crate::avm2::script::Script;
use crate::avm2::script::TranslationUnit;
use crate::avm2::value::Value;
use crate::context::UpdateContext;
use crate::tag_utils::SwfSlice;
use gc_arena::{Collect, GcCell, MutationContext};
use std::rc::Rc;
use swf::avm2::read::Reader;

#[macro_export]
macro_rules! avm_debug {
($($arg:tt)*) => (
#[cfg(feature = "avm_debug")]
log::debug!($($arg)*)
)
}

mod activation;
mod class;
mod function;
mod globals;
mod method;
mod names;
mod object;
mod property;
mod property_map;
mod return_value;
mod scope;
mod script;
mod script_object;
mod slot;
mod string;
mod r#trait;
mod value;

/// Boxed error alias.
///
/// As AVM2 is a far stricter VM than AVM1, this may eventually be replaced
/// with a proper Avm2Error enum.
type Error = Box<dyn std::error::Error>;

/// The state of an AVM2 interpreter.
#[derive(Collect)]
#[collect(no_drop)]
pub struct Avm2<'gc> {
/// Values currently present on the operand stack.
stack: Vec<Value<'gc>>,

/// Global scope object.
globals: Object<'gc>,

/// System prototypes.
system_prototypes: SystemPrototypes<'gc>,
}

impl<'gc> Avm2<'gc> {
/// Construct a new AVM interpreter.
pub fn new(mc: MutationContext<'gc, '_>) -> Self {
let (globals, system_prototypes) = globals::construct_global_scope(mc);

Self {
stack: Vec::new(),
globals,
system_prototypes,
}
}

/// Return the current set of system prototypes.
pub fn prototypes(&self) -> &SystemPrototypes<'gc> {
&self.system_prototypes
}

/// Run a script's initializer method.
pub fn run_script_initializer(
&mut self,
script: GcCell<'gc, Script<'gc>>,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let mut init_activation = Activation::from_script(self, context, script, self.globals)?;

init_activation.run_stack_frame_for_script(context, script)
}

/// Load an ABC file embedded in a `SwfSlice`.
///
/// The `SwfSlice` must resolve to the contents of an ABC file.
pub fn load_abc(
&mut self,
abc: SwfSlice,
_abc_name: &str,
_lazy_init: bool,
context: &mut UpdateContext<'_, 'gc, '_>,
) -> Result<(), Error> {
let mut read = Reader::new(abc.as_ref());

let abc_file = Rc::new(read.read()?);
let tunit = TranslationUnit::from_abc(abc_file.clone(), context.gc_context);

for i in (0..abc_file.scripts.len()).rev() {
let script = tunit.load_script(i as u32, context.gc_context)?;
let mut globals = self.globals();
let scope = Scope::push_scope(None, globals, context.gc_context);
let mut null_activation = Activation::from_nothing(self, context);

// TODO: Lazyinit means we shouldn't do this until traits are
// actually mentioned...
for trait_entry in script.read().traits()?.iter() {
globals.install_foreign_trait(
&mut null_activation,
context,
trait_entry.clone(),
Some(scope),
globals,
)?;
}

drop(null_activation);

self.run_script_initializer(script, context)?;
}

Ok(())
}

pub fn globals(&self) -> Object<'gc> {
self.globals
}

/// Push a value onto the operand stack.
fn push(&mut self, value: impl Into<Value<'gc>>) {
let value = value.into();
avm_debug!("Stack push {}: {:?}", self.stack.len(), value);
self.stack.push(value);
}

/// Retrieve the top-most value on the operand stack.
#[allow(clippy::let_and_return)]
fn pop(&mut self) -> Value<'gc> {
let value = self.stack.pop().unwrap_or_else(|| {
log::warn!("Avm1::pop: Stack underflow");
Value::Undefined
});

avm_debug!("Stack pop {}: {:?}", self.stack.len(), value);

value
}

fn pop_args(&mut self, arg_count: u32) -> Vec<Value<'gc>> {
let mut args = Vec::with_capacity(arg_count as usize);
args.resize(arg_count as usize, Value::Undefined);
for arg in args.iter_mut().rev() {
*arg = self.pop();
}

args
}
}
Loading

0 comments on commit 1a5d7fe

Please sign in to comment.