Implement call/return for bare functions

This commit is contained in:
David Wendt 2020-02-08 16:59:59 -05:00
parent 115f0393aa
commit 52ac7a6583
4 changed files with 125 additions and 3 deletions

View File

@ -1,6 +1,7 @@
//! ActionScript Virtual Machine 2 (AS3) support
use crate::avm2::activation::Activation;
use crate::avm2::object::TObject;
use crate::avm2::value::Value;
use crate::context::UpdateContext;
use crate::tag_utils::SwfSlice;
@ -84,6 +85,36 @@ impl<'gc> Avm2<'gc> {
self.stack_frames.push(frame)
}
/// Destroy the current stack frame (if there is one).
///
/// The given return value will be pushed on the stack if there is a
/// function to return it to. Otherwise, it will be discarded.
///
/// NOTE: This means that if you are starting a brand new AVM stack just to
/// get it's return value, you won't get that value. Instead, retain a cell
/// referencing the oldest activation frame and use that to retrieve the
/// return value.
fn retire_stack_frame(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
return_value: Value<'gc>,
) -> Result<(), Error> {
if let Some(frame) = self.current_stack_frame() {
self.stack_frames.pop();
let can_return = !self.stack_frames.is_empty();
if can_return {
frame
.write(context.gc_context)
.set_return_value(return_value.clone());
self.push(return_value);
}
}
Ok(())
}
/// Perform some action with the current stack frame's reader.
///
/// This function constructs a reader based off the current stack frame's
@ -166,6 +197,37 @@ impl<'gc> Avm2<'gc> {
Ok(())
}
/// Execute the AVM stack until a given activation returns.
pub fn run_current_frame(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
stop_frame: GcCell<'gc, Activation<'gc>>,
) -> Result<(), Error> {
let mut stop_frame_id = None;
for (index, frame) in self.stack_frames.iter().enumerate() {
if GcCell::ptr_eq(stop_frame, *frame) {
stop_frame_id = Some(index);
}
}
if let Some(stop_frame_id) = stop_frame_id {
while self
.stack_frames
.get(stop_frame_id)
.map(|fr| GcCell::ptr_eq(stop_frame, *fr))
.unwrap_or(false)
{
self.with_current_reader_mut(context, |this, r, context| {
this.do_next_opcode(context, r)
})?;
}
Ok(())
} else {
Err("Attempted to run a frame not on the current interpreter stack".into())
}
}
/// Push a value onto the operand stack.
fn push(&mut self, value: impl Into<Value<'gc>>) {
let value = value.into();
@ -264,6 +326,9 @@ impl<'gc> Avm2<'gc> {
Op::PushUndefined => self.op_push_undefined(),
Op::GetLocal { index } => self.op_get_local(index),
Op::SetLocal { index } => self.op_set_local(context, index),
Op::Call { num_args } => self.op_call(context, num_args),
Op::ReturnValue => self.op_return_value(context),
Op::ReturnVoid => self.op_return_void(context),
_ => self.unknown_op(op),
};
@ -349,4 +414,31 @@ impl<'gc> Avm2<'gc> {
let value = self.pop();
self.set_register_value(context, register_index, value)
}
fn op_call(
&mut self,
context: &mut UpdateContext<'_, 'gc, '_>,
arg_count: u32,
) -> Result<(), Error> {
let function = self.pop().as_object()?;
let receiver = self.pop().as_object()?;
let mut args = Vec::new();
for _ in 0..arg_count {
args.push(self.pop());
}
function.call(receiver, &args, self, context)?.push(self);
Ok(())
}
fn op_return_value(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
let return_value = self.pop();
self.retire_stack_frame(context, return_value)
}
fn op_return_void(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error> {
self.retire_stack_frame(context, Value::Undefined)
}
}

View File

@ -72,6 +72,12 @@ pub struct Activation<'gc> {
/// All activations have local registers, but it is possible for multiple
/// activations (such as a rescope) to execute from the same register set.
local_registers: GcCell<'gc, RegisterSet<'gc>>,
/// What was returned from the function.
///
/// A return value of `None` indicates that the called function is still
/// executing. Functions that do not return instead return `Undefined`.
return_value: Option<Value<'gc>>,
}
impl<'gc> Activation<'gc> {
@ -97,6 +103,7 @@ impl<'gc> Activation<'gc> {
context.gc_context,
RegisterSet::new(method_body.num_locals),
),
return_value: None,
})
}
@ -156,4 +163,15 @@ impl<'gc> Activation<'gc> {
false
}
}
/// Retrieve the return value from a completed activation, if the function
/// has already returned.
pub fn return_value(&self) -> Option<Value<'gc>> {
self.return_value.clone()
}
/// Set the return value.
pub fn set_return_value(&mut self, value: Value<'gc>) {
self.return_value = Some(value);
}
}

View File

@ -65,7 +65,7 @@ impl<'gc> ReturnValue<'gc> {
use ReturnValue::*;
match self {
Immediate(val) => {} //TODO: avm.push(val)
Immediate(val) => avm.push(val),
ResultOf(_frame) => {}
};
}
@ -82,8 +82,9 @@ impl<'gc> ReturnValue<'gc> {
match self {
Immediate(val) => Ok(val),
ResultOf(frame) => {
//TODO: Execute AVM frame, pop return value
Ok(Value::Undefined)
avm.run_current_frame(context, frame)?;
Ok(avm.pop())
}
}
}

View File

@ -1,6 +1,7 @@
//! AVM2 values
use crate::avm2::object::Object;
use crate::avm2::Error;
use gc_arena::Collect;
/// An AVM2 value.
@ -124,3 +125,13 @@ impl PartialEq for Value<'_> {
}
}
}
impl<'gc> Value<'gc> {
pub fn as_object(&self) -> Result<Object<'gc>, Error> {
if let Value::Object(object) = self {
Ok(*object)
} else {
Err(format!("Expected Object, found {:?}", self).into())
}
}
}