avm1: Run entire stack frames at once
This commit is contained in:
parent
c6b9de883f
commit
af72f68f0f
|
@ -6,7 +6,6 @@ use crate::context::UpdateContext;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use gc_arena::{GcCell, MutationContext};
|
use gc_arena::{GcCell, MutationContext};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::TryInto;
|
|
||||||
use url::form_urlencoded;
|
use url::form_urlencoded;
|
||||||
|
|
||||||
use swf::avm1::read::Reader;
|
use swf::avm1::read::Reader;
|
||||||
|
@ -441,64 +440,6 @@ impl<'gc> Avm1<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform some action with the current stack frame's reader.
|
|
||||||
///
|
|
||||||
/// This function constructs a reader based off the current stack frame's
|
|
||||||
/// reader. You are permitted to mutate the stack frame as you wish. If the
|
|
||||||
/// stack frame we started with still exists in the same location on the
|
|
||||||
/// stack, it's PC will be updated to the Reader's current PC.
|
|
||||||
///
|
|
||||||
/// Stack frame identity (for the purpose of the above paragraph) is
|
|
||||||
/// determined by the data pointed to by the `SwfSlice` of a given frame.
|
|
||||||
///
|
|
||||||
/// # Warnings
|
|
||||||
///
|
|
||||||
/// It is incorrect to call this function multiple times in the same stack.
|
|
||||||
/// Doing so will result in any changes in duplicate readers being ignored.
|
|
||||||
/// Always pass the borrowed reader into functions that need it.
|
|
||||||
fn with_current_reader_mut<F, R>(
|
|
||||||
&mut self,
|
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
|
||||||
func: F,
|
|
||||||
) -> Result<R, Error<'gc>>
|
|
||||||
where
|
|
||||||
F: FnOnce(
|
|
||||||
&mut Self,
|
|
||||||
&mut Reader<'_>,
|
|
||||||
&mut UpdateContext<'_, 'gc, '_>,
|
|
||||||
) -> Result<R, Error<'gc>>,
|
|
||||||
{
|
|
||||||
let (frame_cell, swf_version, data, pc) = {
|
|
||||||
let frame = self.stack_frames.last().ok_or(Error::NoStackFrame)?;
|
|
||||||
let mut frame_ref = frame.write(context.gc_context);
|
|
||||||
frame_ref.lock()?;
|
|
||||||
|
|
||||||
(
|
|
||||||
*frame,
|
|
||||||
frame_ref.swf_version(),
|
|
||||||
frame_ref.data(),
|
|
||||||
frame_ref.pc(),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut read = Reader::new(data.as_ref(), swf_version);
|
|
||||||
read.seek(pc.try_into().unwrap());
|
|
||||||
|
|
||||||
let r = func(self, &mut read, context);
|
|
||||||
|
|
||||||
let mut frame_ref = frame_cell.write(context.gc_context);
|
|
||||||
frame_ref.unlock_execution();
|
|
||||||
frame_ref.set_pc(read.pos());
|
|
||||||
|
|
||||||
if let Err(error) = &r {
|
|
||||||
if error.is_halting() {
|
|
||||||
self.halt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Destroy the current stack frame (if there is one).
|
/// Destroy the current stack frame (if there is one).
|
||||||
///
|
///
|
||||||
/// The given return value will be pushed on the stack if there is a
|
/// The given return value will be pushed on the stack if there is a
|
||||||
|
@ -537,14 +478,8 @@ impl<'gc> Avm1<'gc> {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
while !self.stack_frames.is_empty() {
|
while !self.stack_frames.is_empty() {
|
||||||
if let Err(e) = self.with_current_reader_mut(context, |this, r, context| {
|
let activation = self.current_stack_frame().ok_or(Error::FrameNotOnStack)?;
|
||||||
if !this.halted {
|
if let Err(e) = StackFrame::new(self, activation).run(context) {
|
||||||
let activation = this.current_stack_frame().unwrap();
|
|
||||||
StackFrame::new(this, activation).do_next_action(context, r)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
if let Error::ThrownValue(error) = &e {
|
if let Error::ThrownValue(error) = &e {
|
||||||
let string = error
|
let string = error
|
||||||
.coerce_to_string(self, context)
|
.coerce_to_string(self, context)
|
||||||
|
@ -591,14 +526,8 @@ impl<'gc> Avm1<'gc> {
|
||||||
.map(|fr| GcCell::ptr_eq(stop_frame, *fr))
|
.map(|fr| GcCell::ptr_eq(stop_frame, *fr))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
{
|
{
|
||||||
self.with_current_reader_mut(context, |this, r, context| {
|
let activation = self.current_stack_frame().ok_or(Error::FrameNotOnStack)?;
|
||||||
if !this.halted {
|
StackFrame::new(self, activation).run(context)?
|
||||||
let activation = this.current_stack_frame().unwrap();
|
|
||||||
StackFrame::new(this, activation).do_next_action(context, r)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -15,6 +15,7 @@ use enumset::EnumSet;
|
||||||
use gc_arena::{Collect, GcCell};
|
use gc_arena::{Collect, GcCell};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
use std::convert::TryInto;
|
||||||
use swf::avm1::read::Reader;
|
use swf::avm1::read::Reader;
|
||||||
use swf::avm1::types::{Action, Function};
|
use swf::avm1::types::{Action, Function};
|
||||||
|
|
||||||
|
@ -25,6 +26,11 @@ macro_rules! avm_debug {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FrameControl {
|
||||||
|
Continue,
|
||||||
|
Return,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Collect)]
|
#[derive(Collect)]
|
||||||
#[collect(no_drop)]
|
#[collect(no_drop)]
|
||||||
pub struct StackFrame<'a, 'gc: 'a> {
|
pub struct StackFrame<'a, 'gc: 'a> {
|
||||||
|
@ -37,17 +43,49 @@ impl<'a, 'gc: 'a> StackFrame<'a, 'gc> {
|
||||||
Self { avm, activation }
|
Self { avm, activation }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, context: &mut UpdateContext<'_, 'gc, '_>) -> Result<(), Error<'gc>> {
|
||||||
|
let mut activation = self.activation.write(context.gc_context);
|
||||||
|
activation.lock()?;
|
||||||
|
let data = activation.data();
|
||||||
|
let mut read = Reader::new(data.as_ref(), activation.swf_version());
|
||||||
|
read.seek(activation.pc().try_into().unwrap());
|
||||||
|
drop(activation);
|
||||||
|
|
||||||
|
let result = loop {
|
||||||
|
let result = self.do_action(context, &mut read);
|
||||||
|
match result {
|
||||||
|
Ok(FrameControl::Return) => break Ok(()),
|
||||||
|
Ok(FrameControl::Continue) => {}
|
||||||
|
Err(e) => break Err(e),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut activation = self.activation.write(context.gc_context);
|
||||||
|
activation.unlock_execution();
|
||||||
|
activation.set_pc(read.pos());
|
||||||
|
drop(activation);
|
||||||
|
|
||||||
|
if let Err(error) = &result {
|
||||||
|
if error.is_halting() {
|
||||||
|
self.avm.halt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// Run a single action from a given action reader.
|
/// Run a single action from a given action reader.
|
||||||
pub fn do_next_action(
|
fn do_action(
|
||||||
&mut self,
|
&mut self,
|
||||||
context: &mut UpdateContext<'_, 'gc, '_>,
|
context: &mut UpdateContext<'_, 'gc, '_>,
|
||||||
reader: &mut Reader<'_>,
|
reader: &mut Reader<'_>,
|
||||||
) -> Result<(), Error<'gc>> {
|
) -> Result<FrameControl, Error<'gc>> {
|
||||||
let data = self.activation.read().data();
|
let data = self.activation.read().data();
|
||||||
|
|
||||||
if reader.pos() >= (data.end - data.start) {
|
if reader.pos() >= (data.end - data.start) {
|
||||||
//Executing beyond the end of a function constitutes an implicit return.
|
//Executing beyond the end of a function constitutes an implicit return.
|
||||||
self.avm.retire_stack_frame(context, Value::Undefined);
|
self.avm.retire_stack_frame(context, Value::Undefined);
|
||||||
|
return Ok(FrameControl::Return);
|
||||||
} else if let Some(action) = reader.read_action()? {
|
} else if let Some(action) = reader.read_action()? {
|
||||||
avm_debug!("Action: {:?}", action);
|
avm_debug!("Action: {:?}", action);
|
||||||
|
|
||||||
|
@ -135,7 +173,10 @@ impl<'a, 'gc: 'a> StackFrame<'a, 'gc> {
|
||||||
Action::PushDuplicate => self.action_push_duplicate(context),
|
Action::PushDuplicate => self.action_push_duplicate(context),
|
||||||
Action::RandomNumber => self.action_random_number(context),
|
Action::RandomNumber => self.action_random_number(context),
|
||||||
Action::RemoveSprite => self.action_remove_sprite(context),
|
Action::RemoveSprite => self.action_remove_sprite(context),
|
||||||
Action::Return => self.action_return(context),
|
Action::Return => {
|
||||||
|
self.action_return(context)?;
|
||||||
|
return Ok(FrameControl::Return);
|
||||||
|
}
|
||||||
Action::SetMember => self.action_set_member(context),
|
Action::SetMember => self.action_set_member(context),
|
||||||
Action::SetProperty => self.action_set_property(context),
|
Action::SetProperty => self.action_set_property(context),
|
||||||
Action::SetTarget(target) => self.action_set_target(context, &target),
|
Action::SetTarget(target) => self.action_set_target(context, &target),
|
||||||
|
@ -185,9 +226,10 @@ impl<'a, 'gc: 'a> StackFrame<'a, 'gc> {
|
||||||
} else {
|
} else {
|
||||||
//The explicit end opcode was encountered so return here
|
//The explicit end opcode was encountered so return here
|
||||||
self.avm.retire_stack_frame(context, Value::Undefined);
|
self.avm.retire_stack_frame(context, Value::Undefined);
|
||||||
|
return Ok(FrameControl::Return);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(FrameControl::Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn unknown_op(
|
fn unknown_op(
|
||||||
|
@ -372,15 +414,15 @@ impl<'a, 'gc: 'a> StackFrame<'a, 'gc> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(frame) = frame {
|
if let Some(frame) = frame {
|
||||||
// We must run the actions in the order that the tags appear,
|
for action in clip.actions_on_frame(context, frame) {
|
||||||
// so we want to push the stack frames in reverse order.
|
|
||||||
for action in clip.actions_on_frame(context, frame).rev() {
|
|
||||||
self.avm.insert_stack_frame_for_action(
|
self.avm.insert_stack_frame_for_action(
|
||||||
self.avm.target_clip_or_root(),
|
self.avm.target_clip_or_root(),
|
||||||
self.avm.current_swf_version(),
|
self.avm.current_swf_version(),
|
||||||
action,
|
action,
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
|
let frame = self.avm.current_stack_frame().unwrap();
|
||||||
|
self.avm.run_current_frame(context, frame)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log::warn!("Call: Invalid frame {:?}", frame);
|
log::warn!("Call: Invalid frame {:?}", frame);
|
||||||
|
@ -1975,10 +2017,12 @@ impl<'a, 'gc: 'a> StackFrame<'a, 'gc> {
|
||||||
object,
|
object,
|
||||||
context.gc_context,
|
context.gc_context,
|
||||||
);
|
);
|
||||||
let new_activation = self.activation.read().to_rescope(block, with_scope);
|
let new_activation = GcCell::allocate(
|
||||||
self.avm
|
context.gc_context,
|
||||||
.stack_frames
|
self.activation.read().to_rescope(block, with_scope),
|
||||||
.push(GcCell::allocate(context.gc_context, new_activation));
|
);
|
||||||
|
self.avm.stack_frames.push(new_activation);
|
||||||
|
self.avm.run_current_frame(context, new_activation)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue