avm2: unify abstract type propagation
This commit is contained in:
parent
9b012fa567
commit
88e5f9a898
|
@ -10,48 +10,80 @@ use gc_arena::{Gc, GcCell};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
enum ValueType<'gc> {
|
struct OptValue<'gc> {
|
||||||
// Either a class, or null.
|
// This corresponds to the compile-time assumptions about the type:
|
||||||
Class(ClassObject<'gc>),
|
// - primitive types can't be undefined or null,
|
||||||
Int,
|
// - Object (and any other non-primitive type) is non-undefined, but can be null
|
||||||
Uint,
|
// - None (the * type) can be any value,
|
||||||
Number,
|
// - a value typed as int can be stored as a Number (and vice versa),
|
||||||
Boolean,
|
// BUT an int-typed value should always pass `is int`
|
||||||
Null,
|
// (say, a Value::Number above hardcoded int-range that's still representable as i32).
|
||||||
Any,
|
// Note that `null is Object` is still `false`. So think of this type more in terms of
|
||||||
|
// "could this value be a possible value of `var t: T`"
|
||||||
|
pub class: Option<ClassObject<'gc>>,
|
||||||
|
|
||||||
|
// true if the value is guaranteed to be Value::Integer
|
||||||
|
// should only be set if class is numeric.
|
||||||
|
pub contains_valid_integer: bool,
|
||||||
|
// true if the value is guaranteed to be Value::Integer AND is >=0
|
||||||
|
// should only be set if class is numeric.
|
||||||
|
pub contains_valid_unsigned: bool,
|
||||||
|
|
||||||
|
// true if value is guaranteed to be null.
|
||||||
|
// TODO: FP actually has a separate `null` type just for this, this can be observed in VerifyErrors
|
||||||
|
// (a separate type would also prevent accidental "null int" values)
|
||||||
|
pub guaranteed_null: bool,
|
||||||
|
}
|
||||||
|
impl<'gc> OptValue<'gc> {
|
||||||
|
pub fn any() -> Self {
|
||||||
|
Self {
|
||||||
|
class: None,
|
||||||
|
contains_valid_integer: false,
|
||||||
|
contains_valid_unsigned: false,
|
||||||
|
guaranteed_null: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn null() -> Self {
|
||||||
|
Self {
|
||||||
|
class: None,
|
||||||
|
guaranteed_null: true,
|
||||||
|
..Self::any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn of_type(class: ClassObject<'gc>) -> Self {
|
||||||
|
Self {
|
||||||
|
class: Some(class),
|
||||||
|
..Self::any()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn of_type_from_class(class: GcCell<'gc, Class<'gc>>) -> Self {
|
||||||
|
// FIXME: Getting the ClassObject this way should be unnecessary
|
||||||
|
// after the ClassObject refactor
|
||||||
|
if let Some(cls) = class.read().class_object() {
|
||||||
|
Self::of_type(cls)
|
||||||
|
} else {
|
||||||
|
Self::any()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Locals<'gc>(Vec<ValueType<'gc>>);
|
struct Locals<'gc>(Vec<OptValue<'gc>>);
|
||||||
|
|
||||||
impl<'gc> Locals<'gc> {
|
impl<'gc> Locals<'gc> {
|
||||||
fn new(size: usize) -> Self {
|
fn new(size: usize) -> Self {
|
||||||
Self(vec![ValueType::Any; size])
|
Self(vec![OptValue::any(); size])
|
||||||
}
|
|
||||||
|
|
||||||
fn set_class_object(&mut self, index: usize, class: ClassObject<'gc>) {
|
|
||||||
self.0[index] = ValueType::Class(class);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_class(&mut self, index: usize, class: GcCell<'gc, Class<'gc>>) {
|
|
||||||
// FIXME: Getting the ClassObject this way should be unnecessary
|
|
||||||
// after the ClassObject refactor
|
|
||||||
self.0[index] = class
|
|
||||||
.read()
|
|
||||||
.class_object()
|
|
||||||
.map(ValueType::Class)
|
|
||||||
.unwrap_or(ValueType::Any);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_any(&mut self, index: usize) {
|
fn set_any(&mut self, index: usize) {
|
||||||
self.0[index] = ValueType::Any;
|
self.0[index] = OptValue::any();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(&mut self, index: usize, value: ValueType<'gc>) {
|
fn set(&mut self, index: usize, value: OptValue<'gc>) {
|
||||||
self.0[index] = value;
|
self.0[index] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn at(&self, index: usize) -> Option<ValueType<'gc>> {
|
fn at(&self, index: usize) -> Option<OptValue<'gc>> {
|
||||||
self.0.get(index).copied()
|
self.0.get(index).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +93,7 @@ impl<'gc> Locals<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
struct Stack<'gc>(Vec<ValueType<'gc>>);
|
struct Stack<'gc>(Vec<OptValue<'gc>>);
|
||||||
|
|
||||||
impl<'gc> Stack<'gc> {
|
impl<'gc> Stack<'gc> {
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
|
@ -69,53 +101,31 @@ impl<'gc> Stack<'gc> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_class_object(&mut self, class: ClassObject<'gc>) {
|
fn push_class_object(&mut self, class: ClassObject<'gc>) {
|
||||||
self.0.push(ValueType::Class(class));
|
self.0.push(OptValue::of_type(class));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_class(&mut self, class: GcCell<'gc, Class<'gc>>) {
|
fn push_class(&mut self, class: GcCell<'gc, Class<'gc>>) {
|
||||||
// FIXME: Getting the ClassObject this way should be unnecessary
|
self.0.push(OptValue::of_type_from_class(class));
|
||||||
// after the ClassObject refactor
|
|
||||||
self.0.push(
|
|
||||||
class
|
|
||||||
.read()
|
|
||||||
.class_object()
|
|
||||||
.map(ValueType::Class)
|
|
||||||
.unwrap_or(ValueType::Any),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_int(&mut self) {
|
|
||||||
self.0.push(ValueType::Int);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_uint(&mut self) {
|
|
||||||
self.0.push(ValueType::Uint);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_number(&mut self) {
|
|
||||||
self.0.push(ValueType::Number);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push_boolean(&mut self) {
|
|
||||||
self.0.push(ValueType::Boolean);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_any(&mut self) {
|
fn push_any(&mut self) {
|
||||||
self.0.push(ValueType::Any);
|
self.0.push(OptValue::any());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_null(&mut self) {
|
fn push(&mut self, value: OptValue<'gc>) {
|
||||||
self.0.push(ValueType::Null);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, value: ValueType<'gc>) {
|
|
||||||
self.0.push(value);
|
self.0.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn pop(&mut self) -> Option<ValueType<'gc>> {
|
fn pop(&mut self) -> Option<OptValue<'gc>> {
|
||||||
|
// the Option will not needed once we get cross-block stack verification
|
||||||
self.0.pop()
|
self.0.pop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn pop_or_any(&mut self) -> OptValue<'gc> {
|
||||||
|
// the unwrap will not needed once we get cross-block stack verification
|
||||||
|
self.0.pop().unwrap_or(OptValue::any())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pop_for_multiname(&mut self, multiname: Gc<'gc, Multiname<'gc>>) {
|
pub fn pop_for_multiname(&mut self, multiname: Gc<'gc, Multiname<'gc>>) {
|
||||||
if multiname.has_lazy_name() {
|
if multiname.has_lazy_name() {
|
||||||
self.0.pop();
|
self.0.pop();
|
||||||
|
@ -146,6 +156,32 @@ pub fn optimize<'gc>(
|
||||||
#![allow(clippy::manual_filter)]
|
#![allow(clippy::manual_filter)]
|
||||||
#![allow(clippy::single_match)]
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
|
// this is unfortunate, but way more convenient than grabbing types from Activation
|
||||||
|
struct Types<'gc> {
|
||||||
|
pub object: ClassObject<'gc>,
|
||||||
|
pub int: ClassObject<'gc>,
|
||||||
|
pub uint: ClassObject<'gc>,
|
||||||
|
pub number: ClassObject<'gc>,
|
||||||
|
pub boolean: ClassObject<'gc>,
|
||||||
|
pub class: ClassObject<'gc>,
|
||||||
|
pub string: ClassObject<'gc>,
|
||||||
|
pub array: ClassObject<'gc>,
|
||||||
|
pub function: ClassObject<'gc>,
|
||||||
|
pub void: ClassObject<'gc>,
|
||||||
|
}
|
||||||
|
let types = Types {
|
||||||
|
object: activation.avm2().classes().object,
|
||||||
|
int: activation.avm2().classes().int,
|
||||||
|
uint: activation.avm2().classes().uint,
|
||||||
|
number: activation.avm2().classes().number,
|
||||||
|
boolean: activation.avm2().classes().boolean,
|
||||||
|
class: activation.avm2().classes().class,
|
||||||
|
string: activation.avm2().classes().string,
|
||||||
|
array: activation.avm2().classes().array,
|
||||||
|
function: activation.avm2().classes().function,
|
||||||
|
void: activation.avm2().classes().void,
|
||||||
|
};
|
||||||
|
|
||||||
let method_body = method
|
let method_body = method
|
||||||
.body()
|
.body()
|
||||||
.expect("Cannot verify non-native method without body!");
|
.expect("Cannot verify non-native method without body!");
|
||||||
|
@ -184,12 +220,12 @@ pub fn optimize<'gc>(
|
||||||
// Initial set of local types
|
// Initial set of local types
|
||||||
let mut initial_local_types = Locals::new(method_body.num_locals as usize);
|
let mut initial_local_types = Locals::new(method_body.num_locals as usize);
|
||||||
if let Some(this_class) = this_class {
|
if let Some(this_class) = this_class {
|
||||||
initial_local_types.set_class_object(0, this_class);
|
initial_local_types.set(0, OptValue::of_type(this_class));
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i, argument_type) in argument_types.iter().enumerate() {
|
for (i, argument_type) in argument_types.iter().enumerate() {
|
||||||
if let Some(argument_type) = argument_type {
|
if let Some(argument_type) = argument_type {
|
||||||
initial_local_types.set_class(i + 1, *argument_type);
|
initial_local_types.set(i + 1, OptValue::of_type_from_class(*argument_type));
|
||||||
// `i + 1` because the receiver takes up local #0
|
// `i + 1` because the receiver takes up local #0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,32 +274,37 @@ pub fn optimize<'gc>(
|
||||||
|
|
||||||
match op {
|
match op {
|
||||||
Op::CoerceB => {
|
Op::CoerceB => {
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
if matches!(stack_value, Some(ValueType::Boolean)) {
|
if stack_value.class == Some(types.boolean) {
|
||||||
*op = Op::Nop;
|
*op = Op::Nop;
|
||||||
}
|
}
|
||||||
stack.push_boolean();
|
stack.push_class_object(types.boolean);
|
||||||
}
|
}
|
||||||
Op::CoerceD => {
|
Op::CoerceD => {
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
if matches!(stack_value, Some(ValueType::Number)) {
|
if stack_value.class == Some(types.number)
|
||||||
|
|| stack_value.class == Some(types.int)
|
||||||
|
|| stack_value.class == Some(types.uint)
|
||||||
|
{
|
||||||
*op = Op::Nop;
|
*op = Op::Nop;
|
||||||
}
|
}
|
||||||
stack.push_number();
|
stack.push_class_object(types.number);
|
||||||
}
|
}
|
||||||
Op::CoerceI => {
|
Op::CoerceI => {
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
if matches!(stack_value, Some(ValueType::Int | ValueType::Uint)) {
|
// TODO: maybe the type check is safe here...?
|
||||||
|
if stack_value.contains_valid_integer {
|
||||||
*op = Op::Nop;
|
*op = Op::Nop;
|
||||||
}
|
}
|
||||||
stack.push_int();
|
stack.push_class_object(types.int);
|
||||||
}
|
}
|
||||||
Op::CoerceU => {
|
Op::CoerceU => {
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
if matches!(stack_value, Some(ValueType::Uint)) {
|
// TODO: maybe the type check is safe here...?
|
||||||
|
if stack_value.contains_valid_unsigned {
|
||||||
*op = Op::Nop;
|
*op = Op::Nop;
|
||||||
}
|
}
|
||||||
stack.push_uint();
|
stack.push_class_object(types.uint);
|
||||||
}
|
}
|
||||||
Op::CoerceA => {
|
Op::CoerceA => {
|
||||||
// This does actually inhibit optimizations in FP
|
// This does actually inhibit optimizations in FP
|
||||||
|
@ -271,11 +312,11 @@ pub fn optimize<'gc>(
|
||||||
stack.push_any();
|
stack.push_any();
|
||||||
}
|
}
|
||||||
Op::CoerceS => {
|
Op::CoerceS => {
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
if matches!(stack_value, Some(ValueType::Null)) {
|
if stack_value.guaranteed_null {
|
||||||
*op = Op::Nop;
|
*op = Op::Nop;
|
||||||
}
|
}
|
||||||
stack.push_class_object(activation.avm2().classes().string);
|
stack.push_class_object(types.string);
|
||||||
}
|
}
|
||||||
Op::Equals
|
Op::Equals
|
||||||
| Op::StrictEquals
|
| Op::StrictEquals
|
||||||
|
@ -285,80 +326,84 @@ pub fn optimize<'gc>(
|
||||||
| Op::GreaterEquals => {
|
| Op::GreaterEquals => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_boolean();
|
stack.push_class_object(types.boolean);
|
||||||
}
|
}
|
||||||
Op::Not => {
|
Op::Not => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_boolean();
|
stack.push_class_object(types.boolean);
|
||||||
}
|
}
|
||||||
Op::PushTrue | Op::PushFalse => {
|
Op::PushTrue | Op::PushFalse => {
|
||||||
stack.push_boolean();
|
stack.push_class_object(types.boolean);
|
||||||
}
|
}
|
||||||
Op::PushNull => {
|
Op::PushNull => {
|
||||||
stack.push_null();
|
// TODO: we should push null type here
|
||||||
|
stack.push(OptValue::null());
|
||||||
}
|
}
|
||||||
Op::PushUndefined => {
|
Op::PushUndefined => {
|
||||||
stack.push_any();
|
stack.push_class_object(types.void);
|
||||||
}
|
}
|
||||||
Op::PushNaN => {
|
Op::PushNaN => {
|
||||||
stack.push_number();
|
stack.push_class_object(types.number);
|
||||||
}
|
}
|
||||||
Op::PushByte { value } => {
|
Op::PushByte { value } => {
|
||||||
|
let mut new_value = OptValue::of_type(types.int);
|
||||||
|
new_value.contains_valid_integer = true;
|
||||||
if *value >= 0 {
|
if *value >= 0 {
|
||||||
stack.push_uint();
|
new_value.contains_valid_unsigned = true;
|
||||||
} else {
|
|
||||||
stack.push_int();
|
|
||||||
}
|
}
|
||||||
|
stack.push(new_value);
|
||||||
}
|
}
|
||||||
Op::PushShort { value } => {
|
Op::PushShort { value } => {
|
||||||
|
let mut new_value = OptValue::of_type(types.int);
|
||||||
|
new_value.contains_valid_integer = true;
|
||||||
if *value >= 0 {
|
if *value >= 0 {
|
||||||
stack.push_uint();
|
new_value.contains_valid_unsigned = true;
|
||||||
} else {
|
|
||||||
stack.push_int();
|
|
||||||
}
|
}
|
||||||
|
stack.push(new_value);
|
||||||
}
|
}
|
||||||
Op::PushInt { value } => {
|
Op::PushInt { value } => {
|
||||||
|
let mut new_value = OptValue::of_type(types.int);
|
||||||
if *value < -(1 << 28) || *value >= (1 << 28) {
|
if *value < -(1 << 28) || *value >= (1 << 28) {
|
||||||
stack.push_number();
|
// will be coerced to Number
|
||||||
} else if *value >= 0 {
|
|
||||||
stack.push_uint();
|
|
||||||
} else {
|
} else {
|
||||||
stack.push_int();
|
new_value.contains_valid_integer = true;
|
||||||
|
if *value >= 0 {
|
||||||
|
new_value.contains_valid_unsigned = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stack.push(new_value);
|
||||||
|
}
|
||||||
Op::DecrementI => {
|
Op::DecrementI => {
|
||||||
// This doesn't give any Number-int guarantees
|
// TODO (same for other I ops): analyze what _exactly_ the type int implies
|
||||||
|
// and whether we can use Number or (u)int here
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_any();
|
stack.push_any();
|
||||||
}
|
}
|
||||||
Op::IncrementI => {
|
Op::IncrementI => {
|
||||||
// This doesn't give any Number-int guarantees
|
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_any();
|
stack.push_any();
|
||||||
}
|
}
|
||||||
Op::DecLocalI { index } => {
|
Op::DecLocalI { index } => {
|
||||||
if (*index as usize) < local_types.len() {
|
if (*index as usize) < local_types.len() {
|
||||||
// This doesn't give any Number-int guarantees
|
|
||||||
local_types.set_any(*index as usize);
|
local_types.set_any(*index as usize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Op::IncLocalI { index } => {
|
Op::IncLocalI { index } => {
|
||||||
if (*index as usize) < local_types.len() {
|
if (*index as usize) < local_types.len() {
|
||||||
// This doesn't give any Number-int guarantees
|
|
||||||
local_types.set_any(*index as usize);
|
local_types.set_any(*index as usize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Op::Increment => {
|
Op::Increment => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_number();
|
stack.push_class_object(types.number);
|
||||||
}
|
}
|
||||||
Op::Decrement => {
|
Op::Decrement => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_number();
|
stack.push_class_object(types.number);
|
||||||
}
|
}
|
||||||
Op::Negate => {
|
Op::Negate => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_number();
|
stack.push_class_object(types.number);
|
||||||
}
|
}
|
||||||
Op::AddI => {
|
Op::AddI => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
@ -376,38 +421,39 @@ pub fn optimize<'gc>(
|
||||||
stack.push_any();
|
stack.push_any();
|
||||||
}
|
}
|
||||||
Op::Add => {
|
Op::Add => {
|
||||||
stack.pop();
|
let value2 = stack.pop_or_any();
|
||||||
stack.pop();
|
let value1 = stack.pop_or_any();
|
||||||
|
if (value1.class == Some(types.int)
|
||||||
|
|| value1.class == Some(types.uint)
|
||||||
|
|| value1.class == Some(types.number))
|
||||||
|
&& (value2.class == Some(types.int)
|
||||||
|
|| value2.class == Some(types.uint)
|
||||||
|
|| value2.class == Some(types.number))
|
||||||
|
{
|
||||||
|
stack.push_class_object(types.number);
|
||||||
|
} else {
|
||||||
stack.push_any();
|
stack.push_any();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Op::Subtract => {
|
Op::Subtract => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_any();
|
stack.push_class_object(types.number);
|
||||||
}
|
}
|
||||||
Op::Multiply => {
|
Op::Multiply => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
stack.push_class_object(types.number);
|
||||||
// NOTE: In our current implementation, this is guaranteed,
|
|
||||||
// but it may not be after correctness fixes to match avmplus
|
|
||||||
stack.push_number();
|
|
||||||
}
|
}
|
||||||
Op::Divide => {
|
Op::Divide => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
stack.push_class_object(types.number);
|
||||||
// NOTE: In our current implementation, this is guaranteed,
|
|
||||||
// but it may not be after correctness fixes to match avmplus
|
|
||||||
stack.push_number();
|
|
||||||
}
|
}
|
||||||
Op::Modulo => {
|
Op::Modulo => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
stack.push_class_object(types.number);
|
||||||
// NOTE: In our current implementation, this is guaranteed,
|
|
||||||
// but it may not be after correctness fixes to match avmplus
|
|
||||||
stack.push_number();
|
|
||||||
}
|
}
|
||||||
Op::BitNot => {
|
Op::BitNot => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
@ -444,35 +490,35 @@ pub fn optimize<'gc>(
|
||||||
stack.push_any();
|
stack.push_any();
|
||||||
}
|
}
|
||||||
Op::PushDouble { .. } => {
|
Op::PushDouble { .. } => {
|
||||||
stack.push_number();
|
stack.push_class_object(types.number);
|
||||||
}
|
}
|
||||||
Op::PushString { .. } => {
|
Op::PushString { .. } => {
|
||||||
stack.push_class_object(activation.avm2().classes().string);
|
stack.push_class_object(types.string);
|
||||||
}
|
}
|
||||||
Op::NewArray { num_args } => {
|
Op::NewArray { num_args } => {
|
||||||
stack.popn(*num_args);
|
stack.popn(*num_args);
|
||||||
|
|
||||||
stack.push_class_object(activation.avm2().classes().array);
|
stack.push_class_object(types.array);
|
||||||
}
|
}
|
||||||
Op::NewObject { num_args } => {
|
Op::NewObject { num_args } => {
|
||||||
stack.popn(*num_args * 2);
|
stack.popn(*num_args * 2);
|
||||||
|
|
||||||
stack.push_class_object(activation.avm2().classes().object);
|
stack.push_class_object(types.object);
|
||||||
}
|
}
|
||||||
Op::NewFunction { .. } => {
|
Op::NewFunction { .. } => {
|
||||||
stack.push_class_object(activation.avm2().classes().function);
|
stack.push_class_object(types.function);
|
||||||
}
|
}
|
||||||
Op::NewClass { .. } => {
|
Op::NewClass { .. } => {
|
||||||
stack.push_class_object(activation.avm2().classes().class);
|
stack.push_class_object(types.class);
|
||||||
}
|
}
|
||||||
Op::IsType { .. } => {
|
Op::IsType { .. } => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_boolean();
|
stack.push_class_object(types.boolean);
|
||||||
}
|
}
|
||||||
Op::IsTypeLate => {
|
Op::IsTypeLate => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_boolean();
|
stack.push_class_object(types.boolean);
|
||||||
}
|
}
|
||||||
Op::ApplyType { num_types } => {
|
Op::ApplyType { num_types } => {
|
||||||
stack.popn(*num_types);
|
stack.popn(*num_types);
|
||||||
|
@ -487,41 +533,38 @@ pub fn optimize<'gc>(
|
||||||
stack.push_any();
|
stack.push_any();
|
||||||
}
|
}
|
||||||
Op::AsType { class } => {
|
Op::AsType { class } => {
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
stack.push_class(*class);
|
|
||||||
|
|
||||||
if matches!(stack_value, Some(ValueType::Null)) {
|
let mut new_value = OptValue::any();
|
||||||
|
if let Some(class_object) = stack_value.class {
|
||||||
|
if GcCell::ptr_eq(*class, class_object.inner_class_definition()) {
|
||||||
|
// TODO: there are more cases when this can succeed,
|
||||||
|
// like inheritance and numbers (`x: Number = 1; x as int;`)
|
||||||
|
new_value = stack_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if stack_value.guaranteed_null {
|
||||||
|
// null always turns into null
|
||||||
*op = Op::Nop;
|
*op = Op::Nop;
|
||||||
}
|
}
|
||||||
|
stack.push(new_value);
|
||||||
}
|
}
|
||||||
Op::Coerce { class } => {
|
Op::Coerce { class } => {
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
stack.push_class(*class);
|
if stack_value.guaranteed_null {
|
||||||
|
// Coercing null to a non-primitive or void is a noop.
|
||||||
// As long as this Coerce isn't coercing to one
|
if !GcCell::ptr_eq(*class, types.int.inner_class_definition())
|
||||||
// of these special classes, we could remove it.
|
&& !GcCell::ptr_eq(*class, types.uint.inner_class_definition())
|
||||||
if !GcCell::ptr_eq(
|
&& !GcCell::ptr_eq(*class, types.number.inner_class_definition())
|
||||||
*class,
|
&& !GcCell::ptr_eq(*class, types.boolean.inner_class_definition())
|
||||||
activation.avm2().classes().int.inner_class_definition(),
|
&& !GcCell::ptr_eq(*class, types.void.inner_class_definition())
|
||||||
) && !GcCell::ptr_eq(
|
{
|
||||||
*class,
|
|
||||||
activation.avm2().classes().uint.inner_class_definition(),
|
|
||||||
) && !GcCell::ptr_eq(
|
|
||||||
*class,
|
|
||||||
activation.avm2().classes().number.inner_class_definition(),
|
|
||||||
) && !GcCell::ptr_eq(
|
|
||||||
*class,
|
|
||||||
activation.avm2().classes().boolean.inner_class_definition(),
|
|
||||||
) && !GcCell::ptr_eq(
|
|
||||||
*class,
|
|
||||||
activation.avm2().classes().void.inner_class_definition(),
|
|
||||||
) {
|
|
||||||
if matches!(stack_value, Some(ValueType::Null)) {
|
|
||||||
*op = Op::Nop;
|
|
||||||
} else if let Some(ValueType::Class(class_object)) = stack_value {
|
|
||||||
if GcCell::ptr_eq(*class, class_object.inner_class_definition()) {
|
|
||||||
*op = Op::Nop;
|
*op = Op::Nop;
|
||||||
}
|
}
|
||||||
|
} else if let Some(class_object) = stack_value.class {
|
||||||
|
// TODO: this could check for inheritance
|
||||||
|
if GcCell::ptr_eq(*class, class_object.inner_class_definition()) {
|
||||||
|
*op = Op::Nop;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -591,12 +634,11 @@ pub fn optimize<'gc>(
|
||||||
}
|
}
|
||||||
Op::GetProperty { multiname } => {
|
Op::GetProperty { multiname } => {
|
||||||
let mut stack_push_done = false;
|
let mut stack_push_done = false;
|
||||||
|
|
||||||
stack.pop_for_multiname(*multiname);
|
stack.pop_for_multiname(*multiname);
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
|
|
||||||
if !multiname.has_lazy_component() {
|
if !multiname.has_lazy_component() {
|
||||||
if let Some(ValueType::Class(class)) = stack_value {
|
if let Some(class) = stack_value.class {
|
||||||
if !class.inner_class_definition().read().is_interface() {
|
if !class.inner_class_definition().read().is_interface() {
|
||||||
match class.instance_vtable().get_trait(multiname) {
|
match class.instance_vtable().get_trait(multiname) {
|
||||||
Some(Property::Slot { slot_id })
|
Some(Property::Slot { slot_id })
|
||||||
|
@ -649,12 +691,10 @@ pub fn optimize<'gc>(
|
||||||
}
|
}
|
||||||
Op::InitProperty { multiname } => {
|
Op::InitProperty { multiname } => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
|
||||||
stack.pop_for_multiname(*multiname);
|
stack.pop_for_multiname(*multiname);
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
|
|
||||||
if !multiname.has_lazy_component() {
|
if !multiname.has_lazy_component() {
|
||||||
if let Some(ValueType::Class(class)) = stack_value {
|
if let Some(class) = stack_value.class {
|
||||||
if !class.inner_class_definition().read().is_interface() {
|
if !class.inner_class_definition().read().is_interface() {
|
||||||
match class.instance_vtable().get_trait(multiname) {
|
match class.instance_vtable().get_trait(multiname) {
|
||||||
Some(Property::Slot { slot_id })
|
Some(Property::Slot { slot_id })
|
||||||
|
@ -670,12 +710,10 @@ pub fn optimize<'gc>(
|
||||||
}
|
}
|
||||||
Op::SetProperty { multiname } => {
|
Op::SetProperty { multiname } => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
|
|
||||||
stack.pop_for_multiname(*multiname);
|
stack.pop_for_multiname(*multiname);
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
|
|
||||||
if !multiname.has_lazy_component() {
|
if !multiname.has_lazy_component() {
|
||||||
if let Some(ValueType::Class(class)) = stack_value {
|
if let Some(class) = stack_value.class {
|
||||||
if !class.inner_class_definition().read().is_interface() {
|
if !class.inner_class_definition().read().is_interface() {
|
||||||
match class.instance_vtable().get_trait(multiname) {
|
match class.instance_vtable().get_trait(multiname) {
|
||||||
Some(Property::Slot { slot_id }) => {
|
Some(Property::Slot { slot_id }) => {
|
||||||
|
@ -729,10 +767,10 @@ pub fn optimize<'gc>(
|
||||||
stack.pop_for_multiname(*multiname);
|
stack.pop_for_multiname(*multiname);
|
||||||
|
|
||||||
// Then receiver.
|
// Then receiver.
|
||||||
let stack_value = stack.pop();
|
let stack_value = stack.pop_or_any();
|
||||||
|
|
||||||
if !multiname.has_lazy_component() {
|
if !multiname.has_lazy_component() {
|
||||||
if let Some(ValueType::Class(class)) = stack_value {
|
if let Some(class) = stack_value.class {
|
||||||
if !class.inner_class_definition().read().is_interface() {
|
if !class.inner_class_definition().read().is_interface() {
|
||||||
match class.instance_vtable().get_trait(multiname) {
|
match class.instance_vtable().get_trait(multiname) {
|
||||||
Some(Property::Method { disp_id }) => {
|
Some(Property::Method { disp_id }) => {
|
||||||
|
@ -800,7 +838,9 @@ pub fn optimize<'gc>(
|
||||||
}
|
}
|
||||||
Op::Li8 => {
|
Op::Li8 => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
stack.push_int();
|
let mut value = OptValue::of_type(types.int);
|
||||||
|
value.contains_valid_integer = true;
|
||||||
|
stack.push_class_object(types.int);
|
||||||
}
|
}
|
||||||
Op::ReturnVoid
|
Op::ReturnVoid
|
||||||
| Op::ReturnValue
|
| Op::ReturnValue
|
||||||
|
|
Loading…
Reference in New Issue