@awayfl/avm2
Version:
Virtual machine for executing AS3 code
1,072 lines (883 loc) • 26.6 kB
text/typescript
import { Bytecode } from '../Bytecode';
import { ExceptionInfo } from '../abc/lazy/ExceptionInfo';
import { MethodInfo } from '../abc/lazy/MethodInfo';
import { Instruction } from './Instruction';
import { COMPILATION_FAIL_REASON } from '../flags';
import { Settings } from '../Settings';
import { ABCFile } from '../abc/lazy/ABCFile';
const enum PRIMITIVE_TYPE {
VOID = -1,
BOOL = -2,
NUMBER = -3,
STRING = -4,
UNDEF = -5,
NULL = -6,
ANY = -1000
}
export interface IAnalyzeError {
error: {
message: string, reason: COMPILATION_FAIL_REASON
};
}
export interface IAnalyseResult {
jumps: Array<number>;
set: Array<Instruction>;
catchStart: NumberMap<ExceptionInfo[]>,
catchEnd: NumberMap<ExceptionInfo[]>
}
/**
* Propogade stack for calculation real stack size for every instruction
*/
export function propagateStack(position: number, stack: number, q: Array<Instruction>): number {
let v = stack;
const l = q.length;
let minStack = stack;
for (let i = 0; i < l; i++) {
if (q[i].position >= position) {
if (q[i].stack >= 0)
return minStack;
q[i].stack = v;
v += q[i].delta;
if (v < minStack) minStack = v;
for (let j = 0; j < q[i].refs.length; j++) {
const s = propagateStack(q[i].refs[j], v, q);
if (s < minStack) minStack = s;
}
if (q[i].terminal)
return minStack;
}
}
return minStack;
}
/**
* Like as propogadeStack, only for scope
*/
export function propagateScope (position: number, scope: number, q: Array<Instruction>) {
let v = scope;
const l = q.length;
for (let i = 0; i < l; i++) {
if (q[i].position >= position) {
if (q[i].scope >= 0)
return;
q[i].scope = v;
v += q[i].deltaScope;
for (let j = 0; j < q[i].refs.length; j++) {
propagateScope(q[i].refs[j], v, q);
}
if (q[i].terminal)
return;
}
}
}
export function propogateTree(q: Array<Instruction>, jumps: number[]): void {
const branches = {};
const condNodes: Instruction[] = [];
branches[0] = 0;
const l = q.length;
for (let i = 1; i < l; i++) {
const inst = q[i];
if (jumps.indexOf(inst.position) > -1) {
branches[inst.position] = i;
}
if (inst.name >= Bytecode.IFNLT
&& inst.name <= Bytecode.LOOKUPSWITCH) {
condNodes.push(inst);
}
}
for (const c of condNodes) {
if (c.name === Bytecode.LOOKUPSWITCH) {
const params = <number[]> c.params;
c.childs = [];
for (let i = 0; i < params.length - 1; i++) {
c.childs.push(branches[i]);
}
continue;
}
if (c.name === Bytecode.JUMP) {
c.childs = [branches[c.params[0]]];
continue;
}
c.childs.push(branches[c.params[0]]);
}
}
type IMnRecord = Array<number>;
interface IPreprocessorState {
index: number;
readonly abc: ABCFile;
readonly code: Uint8Array;
readonly currentMn: IMnRecord;
}
function u30 (state: IPreprocessorState): number {
const code = state.code;
let i = state.index;
let u = code[i++];
if (u & 0x80) {
u = u & 0x7f | code[i++] << 7;
if (u & 0x4000) {
u = u & 0x3fff | code[i++] << 14;
if (u & 0x200000) {
u = u & 0x1fffff | code[i++] << 21;
if (u & 0x10000000) {
u = u & 0x0fffffff | code[i++] << 28;
u = u & 0xffffffff;
}
}
}
}
state.index = i;
return u >>> 0;
}
function mn (state: IPreprocessorState) {
const index = u30(state);
const name = state.abc.getMultiname(index);
const mnResult = state.currentMn;
mnResult[0] = index;
if (name.isRuntimeName() || name.isRuntimeNamespace()) {
mnResult[1] = 256;
mnResult[2] = name.isRuntimeName() && name.isRuntimeNamespace() ? -2 : -1;
return mnResult;
}
mnResult[1] = 0;
mnResult[2] = 0;
return mnResult;
}
function s24 (state: IPreprocessorState): number {
const code = state.code;
let i = state.index;
let u = code[i++] | (code[i++] << 8) | (code[i++] << 16);
u = (u << 8) >> 8;
state.index = i;
return u;
}
function s8(state: IPreprocessorState): number {
return (state.code[state.index++] << 24) >> 24;
}
/**
* Analyzing instruction set from method info
* @param methodInfo
*/
export function analyze(methodInfo: MethodInfo): IAnalyseResult | IAnalyzeError {
const abc = methodInfo.abc;
const body = methodInfo.getBody();
const code = body.code;
const q: Instruction[] = [];
const state = {
index: 0,
abc: abc,
code: code,
currentMn: [0,0,0]
};
let type: number = 0;
let lastType: number = 0;
let requireScope = false;
for (; state.index < code.length;) {
const oldi = state.index;
const z = code[state.index++];
const last = Settings.OPTIMISE_ON_IR ? q[q.length - 1] : null;
let ins: Instruction;
switch (z) {
case Bytecode.NOP:
ins = (new Instruction(oldi, z));
break;
case Bytecode.LABEL:
ins = (new Instruction(oldi, z));
break;
case Bytecode.DXNSLATE:
ins = (new Instruction(oldi, z, 0, -1));
break;
case Bytecode.DEBUGFILE:
case Bytecode.DEBUGLINE:
ins = (new Instruction(oldi, z, u30(state)));
break;
case Bytecode.DEBUG:
ins = (new Instruction(oldi, z, [s8(state), u30(state), s8(state), u30(state)]));
break;
case Bytecode.THROW:
ins = (new Instruction(oldi, z, null, -1, 0, true));
break;
case Bytecode.PUSHSCOPE:
ins = (new Instruction(oldi, z, null, -1, 1));
ins.returnTypeId = lastType = ++type;
break;
case Bytecode.PUSHWITH:
ins = (new Instruction(oldi, z, null, -1, 1));
break;
case Bytecode.POPSCOPE:
ins = (new Instruction(oldi, z, null, 0, -1));
break;
case Bytecode.GETSCOPEOBJECT:
ins = (new Instruction(oldi, z, s8(state), 1, 0));
ins.returnTypeId = lastType = ++type;
requireScope = true;
break;
case Bytecode.GETGLOBALSCOPE:
ins = (new Instruction(oldi, z, null, 1));
ins.returnTypeId = lastType = ++type;
requireScope = true;
break;
case Bytecode.GETSLOT:
ins = (new Instruction(oldi, z, u30(state), 0));
ins.returnTypeId = lastType = ++type;
break;
case Bytecode.SETSLOT:
ins = (new Instruction(oldi, z, u30(state), -2));
break;
case Bytecode.NEXTNAME:
ins = (new Instruction(oldi, z, null, -1));
ins.returnTypeId = lastType = ++type;
break;
case Bytecode.NEXTVALUE:
ins = (new Instruction(oldi, z, null, -1));
ins.returnTypeId = lastType = ++type;
break;
case Bytecode.HASNEXT:
ins = (new Instruction(oldi, z, null, -1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
break;
case Bytecode.HASNEXT2:
ins = (new Instruction(oldi, z, [u30(state), u30(state)], 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
break;
case Bytecode.IN:
ins = (new Instruction(oldi, z, null, -1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
break;
case Bytecode.DUP:
ins = (new Instruction(oldi, z, null, 1));
ins.returnTypeId = lastType = type;
if (last) {
switch (last.name) {
case Bytecode.PUSHTRUE:
case Bytecode.PUSHFALSE:
case Bytecode.PUSHNAN:
case Bytecode.PUSHINT:
case Bytecode.PUSHDOUBLE:
case Bytecode.PUSHBYTE:
case Bytecode.PUSHFLOAT:
case Bytecode.PUSHSTRING:
case Bytecode.PUSHNULL: {
ins.name = last.name;
ins.params = last.params;
ins.comment = 'IR: DUP changed to PUSH*, reason: prevent optimisation';
}
}
}
break;
case Bytecode.POP: {
ins = (new Instruction(oldi, z, null, -1));
ins.returnTypeId = lastType = type;
//
if (last && last.name === Bytecode.CALLPROPERTY) {
last.name = Bytecode.CALLPROPVOID;
last.comment = 'IR: Optimised from "CALLPROPERTY", reason: POP STACK';
}
break;
}
case Bytecode.SWAP:
ins = (new Instruction(oldi, z, null, 0));
break;
case Bytecode.PUSHTRUE:
ins = (new Instruction(oldi, z, null, 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
break;
case Bytecode.PUSHFALSE:
ins = (new Instruction(oldi, z, null, 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
break;
case Bytecode.PUSHBYTE:
ins = (new Instruction(oldi, z, s8(state), 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.PUSHSHORT:
ins = (new Instruction(oldi, z, u30(state) << 16 >> 16, 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.PUSHINT:
ins = (new Instruction(oldi, z, u30(state), 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.PUSHUINT:
ins = (new Instruction(oldi, z, u30(state), 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.PUSHDOUBLE:
ins = (new Instruction(oldi, z, u30(state), 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.PUSHNAN:
ins = (new Instruction(oldi, z, null, 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.PUSHNULL:
ins = (new Instruction(oldi, z, null, 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NULL;
break;
case Bytecode.PUSHUNDEFINED:
ins = (new Instruction(oldi, z, null, 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.UNDEF;
break;
case Bytecode.PUSHSTRING:
ins = (new Instruction(oldi, z, u30(state), 1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.STRING;
break;
case Bytecode.IFEQ: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -2, 0, false, [i + j]));
break;
}
case Bytecode.IFNE: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -2, 0, false, [i + j]));
break;
}
case Bytecode.IFSTRICTEQ: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -2, 0, false, [i + j]));
break;
}
case Bytecode.IFSTRICTNE: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -2, 0, false, [i + j]));
break;
}
case Bytecode.IFGT:
case Bytecode.IFNLE: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -2, 0, false, [i + j]));
break;
}
case Bytecode.IFGE:
case Bytecode.IFNLT: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -2, 0, false, [i + j]));
break;
}
case Bytecode.IFLT:
case Bytecode.IFNGE: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -2, 0, false, [i + j]));
break;
}
case Bytecode.IFLE:
case Bytecode.IFNGT: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -2, 0, false, [i + j]));
break;
}
case Bytecode.IFTRUE: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -1, 0, false, [i + j]));
break;
}
case Bytecode.IFFALSE: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, -1, 0, false, [i + j]));
break;
}
case Bytecode.LOOKUPSWITCH: {
const offset = oldi + s24(state);
const cases = u30(state);
const table = [offset];
for (let j = 0; j <= cases; j++)
table.push(oldi + s24(state));
ins = (new Instruction(oldi, z, table, -1, 0, true, table));
break;
}
case Bytecode.JUMP: {
const j = s24(state);
const i = state.index;
ins = (new Instruction(oldi, z, i + j, 0, 0, true, [i + j]));
break;
}
case Bytecode.RETURNVALUE:
ins = (new Instruction(oldi, z, null, -1, 0, true));
break;
case Bytecode.RETURNVOID:
ins = (new Instruction(oldi, z, null, 0, 0, true));
break;
case Bytecode.NOT:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
break;
case Bytecode.BITNOT:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.NEGATE:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.INCREMENT:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.DECREMENT:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.INCLOCAL:
case Bytecode.DECLOCAL:
ins = (new Instruction(oldi, z, u30(state), 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.INCREMENT_I:
case Bytecode.DECREMENT_I:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.INCLOCAL_I:
case Bytecode.DECLOCAL_I:
ins = (new Instruction(oldi, z, u30(state), 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.NEGATE_I:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.ADD_I:
case Bytecode.SUBTRACT_I:
case Bytecode.MULTIPLY_I:
case Bytecode.ADD:
case Bytecode.SUBTRACT:
case Bytecode.MULTIPLY:
case Bytecode.DIVIDE:
case Bytecode.MODULO:
case Bytecode.LSHIFT:
case Bytecode.RSHIFT:
case Bytecode.URSHIFT:
case Bytecode.BITAND:
case Bytecode.BITOR:
case Bytecode.BITXOR:
ins = (new Instruction(oldi, z, null, -1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
case Bytecode.EQUALS:
case Bytecode.STRICTEQUALS:
case Bytecode.GREATERTHAN:
case Bytecode.GREATEREQUALS:
case Bytecode.LESSTHAN:
case Bytecode.LESSEQUALS:
ins = (new Instruction(oldi, z, null, -1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
break;
case Bytecode.TYPEOF:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType = ++type;
break;
case Bytecode.INSTANCEOF: {
ins = (new Instruction(oldi, z, null, -1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
break;
}
case Bytecode.ISTYPE: {
const [index, , d] = mn(state);
ins = (new Instruction(oldi, z, index, 0 + d));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
requireScope = true;
break;
}
case Bytecode.ISTYPELATE:
ins = (new Instruction(oldi, z, null, -1));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.BOOL;
requireScope = true;
break;
case Bytecode.ASTYPELATE:
ins = (new Instruction(oldi, z, null, -1));
break;
case Bytecode.ASTYPE: {
const [index, , d] = mn(state);
ins = (new Instruction(oldi, z, index, 0 + d));
requireScope = true;
break;
}
case Bytecode.CALL: {
const argnum = u30(state);
ins = (new Instruction(oldi, z, argnum, -argnum - 1));
requireScope = true;
break;
}
case Bytecode.CONSTRUCT: {
const argnum = u30(state);
ins = (new Instruction(oldi, z, argnum, -argnum));
ins.returnTypeId = lastType = ++type;
break;
}
case Bytecode.CALLPROPERTY: {
const [index, dyn, d] = mn(state);
const argnum = u30(state);
ins = (new Instruction(oldi, z + dyn, [argnum, index], -argnum + d));
ins.returnTypeId = lastType = ++type;
requireScope = requireScope || dyn !== 0;
break;
}
case Bytecode.CALLPROPLEX: {
const [index, dyn, d] = mn(state);
const argnum = u30(state);
ins = (new Instruction(oldi, z + dyn, [argnum, index], -argnum + d));
ins.returnTypeId = lastType = ++type;
requireScope = requireScope || dyn !== 0;
break;
}
case Bytecode.CALLPROPVOID: {
const [index, dyn, d] = mn(state);
const argnum = u30(state);
ins = (new Instruction(oldi, z + dyn, [argnum, index], -(argnum + 1) + d));
break;
}
case Bytecode.APPLYTYPE: {
const argnum = u30(state);
ins = (new Instruction(oldi, z, argnum, -argnum));
ins.returnTypeId = lastType = type;
break;
}
case Bytecode.FINDPROPSTRICT: {
const [index, dyn, d] = mn(state);
ins = (new Instruction(oldi, z + dyn, index, 1 + d));
ins.returnTypeId = lastType = ++type;
requireScope = true;
break;
}
case Bytecode.FINDPROPERTY: {
const [index, dyn, d] = mn(state);
ins = (new Instruction(oldi, z + dyn, index, 1 + d));
ins.returnTypeId = lastType = ++type;
requireScope = true;
break;
}
case Bytecode.NEWFUNCTION:
ins = (new Instruction(oldi, z, u30(state), 1));
ins.returnTypeId = lastType = ++type;
requireScope = true;
break;
case Bytecode.NEWCLASS:
ins = (new Instruction(oldi, z, u30(state), 0));
ins.returnTypeId = lastType = ++type;
requireScope = true;
break;
case Bytecode.GETDESCENDANTS:
ins = (new Instruction(oldi, z, u30(state), 0));
ins.returnTypeId = lastType = ++type;
break;
case Bytecode.NEWARRAY: {
const argnum = u30(state);
ins = (new Instruction(oldi, z, argnum, -argnum + 1));
ins.returnTypeId = lastType = ++type;
break;
}
case Bytecode.NEWOBJECT: {
const argnum = u30(state);
ins = (new Instruction(oldi, z, argnum, -2 * argnum + 1));
ins.returnTypeId = lastType = ++type;
break;
}
case Bytecode.NEWACTIVATION:
ins = (new Instruction(oldi, z, null, 1));
ins.returnTypeId = lastType = ++type;
requireScope = true;
break;
case Bytecode.NEWCATCH:
ins = (new Instruction(oldi, z, u30(state), 1));
requireScope = true;
break;
case Bytecode.CONSTRUCTSUPER: {
const argnum = u30(state);
ins = (new Instruction(oldi, z, argnum, -(argnum + 1)));
break;
}
case Bytecode.CALLSUPER: {
const [index, dyn, d] = mn(state);
const argnum = u30(state);
ins = (new Instruction(oldi, z + dyn, [argnum, index], -argnum + d));
break;
}
case Bytecode.CALLSUPERVOID: {
const [index, dyn, d] = mn(state);
const argnum = u30(state);
ins = (new Instruction(oldi, z + dyn, [argnum, index], -(argnum + 1) + d));
break;
}
case Bytecode.CONSTRUCTPROP: {
const [index, dyn, d] = mn(state);
const argnum = u30(state);
ins = (new Instruction(oldi, z + dyn, [argnum, index], -argnum + d));
ins.returnTypeId = lastType = ++type;
break;
}
case Bytecode.GETPROPERTY: {
const [index, dyn, d] = mn(state);
ins = (new Instruction(oldi, Bytecode.GETPROPERTY + dyn, index, 0 + d));
ins.returnTypeId = lastType = ++type;
break;
}
// we collapse 2 operation to one, but this is can prevent optimisations
case Bytecode.INITPROPERTY:
case Bytecode.SETPROPERTY: {
const [index, dyn, d] = mn(state);
ins = (new Instruction(oldi, Bytecode.SETPROPERTY + dyn, index, -2 + d));
break;
}
case Bytecode.DELETEPROPERTY: {
const [index, dyn, d] = mn(state);
ins = (new Instruction(oldi, z + dyn, index, 0 + d));
break;
}
case Bytecode.GETSUPER: {
const [index, dyn, d] = mn(state);
ins = (new Instruction(oldi, z + dyn, index, 0 + d));
ins.returnTypeId = lastType = ++type;
break;
}
case Bytecode.SETSUPER: {
const [index, dyn, d] = mn(state);
ins = (new Instruction(oldi, z + dyn, index, -2 + d));
break;
}
case Bytecode.COERCE: {
const [index, dyn, d] = mn(state);
ins = (new Instruction(oldi, z + dyn, index, 0 + d));
ins.returnTypeId = lastType;
// construct prop was called with same MN that used for coerce, redundant
if (last && last.name === Bytecode.CONSTRUCTPROP && last.params[1] === index) {
ins.returnTypeId = PRIMITIVE_TYPE.VOID;
ins.name = Bytecode.LABEL;
ins.comment = 'IR: Drop coerce, reason: redundant';
break;
}
requireScope = true;
break;
}
case Bytecode.COERCE_A:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType;
break;
case Bytecode.COERCE_S:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = PRIMITIVE_TYPE.STRING;
break;
case Bytecode.CONVERT_D: {
ins = (new Instruction(oldi, z, null, 0));
if (last) {
switch (last.name) {
case Bytecode.PUSHINT:
case Bytecode.PUSHFLOAT:
case Bytecode.PUSHDOUBLE:
case Bytecode.ADD_I:
case Bytecode.INCREMENT:
case Bytecode.DECREMENT:
case Bytecode.DECREMENT_I:
case Bytecode.INCREMENT_I: {
ins.name = Bytecode.LABEL;
ins.comment = 'IR: CONVERT_D removed, reason: arguments strictly number';
break;
}
}
}
break;
}
case Bytecode.CONVERT_B: {
ins = (new Instruction(oldi, z, null, 0));
if (last) {
switch (last.name) {
case Bytecode.EQUALS:
case Bytecode.STRICTEQUALS:
case Bytecode.GREATERTHAN:
case Bytecode.GREATEREQUALS:
case Bytecode.LESSTHAN:
case Bytecode.LESSEQUALS:
case Bytecode.NOT:
{
ins.name = Bytecode.LABEL;
ins.comment = 'IR: CONVERT_B removed, reason: arguments strictly boolean';
break;
}
}
}
break;
}
case Bytecode.ESC_XATTR:
case Bytecode.ESC_XELEM:
case Bytecode.CONVERT_I:
case Bytecode.CONVERT_U:
case Bytecode.CONVERT_S:
case Bytecode.CONVERT_O:
ins = (new Instruction(oldi, z, null, 0));
break;
case Bytecode.CHECKFILTER:
ins = (new Instruction(oldi, z, null, 0));
break;
case Bytecode.GETLOCAL:
ins = (new Instruction(oldi, Bytecode.GETLOCAL, u30(state), 1));
ins.returnTypeId = lastType;
break;
case Bytecode.GETLOCAL0:
ins = (new Instruction(oldi, Bytecode.GETLOCAL, 0, 1));
ins.returnTypeId = lastType;
break;
case Bytecode.GETLOCAL1:
ins = (new Instruction(oldi, Bytecode.GETLOCAL, 1, 1));
ins.returnTypeId = lastType;
break;
case Bytecode.GETLOCAL2:
ins = (new Instruction(oldi, Bytecode.GETLOCAL, 2, 1));
ins.returnTypeId = lastType;
break;
case Bytecode.GETLOCAL3:
ins = (new Instruction(oldi, Bytecode.GETLOCAL, 3, 1));
ins.returnTypeId = lastType;
break;
case Bytecode.SETLOCAL:
ins = (new Instruction(oldi, Bytecode.SETLOCAL, u30(state), -1));
ins.returnTypeId = lastType;
break;
case Bytecode.SETLOCAL0:
ins = (new Instruction(oldi, Bytecode.SETLOCAL, 0, -1));
ins.returnTypeId = lastType;
break;
case Bytecode.SETLOCAL1:
ins = (new Instruction(oldi, Bytecode.SETLOCAL, 1, -1));
ins.returnTypeId = lastType;
break;
case Bytecode.SETLOCAL2:
ins = (new Instruction(oldi, Bytecode.SETLOCAL, 2, -1));
ins.returnTypeId = lastType;
break;
case Bytecode.SETLOCAL3:
ins = (new Instruction(oldi, Bytecode.SETLOCAL, 3, -1));
ins.returnTypeId = lastType;
break;
case Bytecode.KILL:
ins = (new Instruction(oldi, z, u30(state), 0));
ins.name = Bytecode.LABEL;
ins.comment = 'IR: KILL removed, reason: prevent optimisation';
break;
case Bytecode.GETLEX: {
const [index, dyn, d] = mn(state);
ins = (new Instruction(oldi, z + dyn, index, 1 + d));
ins.returnTypeId = lastType = ++type;
requireScope = true;
break;
}
//http://docs.redtamarin.com/0.4.1T124/avm2/intrinsics/memory/package.html#si32()
case Bytecode.SI8:
case Bytecode.SI16:
case Bytecode.SI32:
case Bytecode.SF32:
case Bytecode.SF64:
ins = (new Instruction(oldi, z, null, -2));
break;
//http://docs.redtamarin.com/0.4.1T124/avm2/intrinsics/memory/package.html#li32()
case Bytecode.LI8:
case Bytecode.LI16:
case Bytecode.LI32:
case Bytecode.LF32:
case Bytecode.LF64:
ins = (new Instruction(oldi, z, null, 0));
ins.returnTypeId = lastType = PRIMITIVE_TYPE.NUMBER;
break;
default: {
const c = code[state.index - 1];
return {
error: {
message: `UNKNOWN BYTECODE ${c.toString(16)} ${Bytecode[c]} at ${oldi}`,
reason: COMPILATION_FAIL_REASON.UNKNOW_BYTECODE,
}
} as any;
}
}
q.push(ins);
}
let minStack = propagateStack(0, 0, q);
if (requireScope) {
propagateScope(0, 0, q);
} else {
const scopeIndexes = [];
for (let i = 0; i < q.length; i++) {
if (q[i].name === Bytecode.PUSHSCOPE) {
scopeIndexes.push(i);
}
}
for (const i of scopeIndexes) {
// we remove 3 commands, because push scope shift stack before
const comment = new Instruction(0, Bytecode.LABEL);
comment.comment = 'IR: PUSHSCOPE removed, reason: unused';
q.splice(i - 1, 2, comment);
}
}
const jumps: number[] = [0];
for (let i = 0; i < q.length; i++) {
for (let j = 0; j < q[i].refs.length; j++) {
jumps.push(q[i].refs[j]);
}
}
let catchStart: NumberMap<ExceptionInfo[]>;
let catchEnd: NumberMap<ExceptionInfo[]>;
if (body.catchBlocks.length) {
// collect try-catch blocks sorted by their start-position
catchStart = {};
// collect try-catch blocks sorted by their end-position
catchEnd = {};
for (let i: number = 0; i < body.catchBlocks.length; i++) {
const block = body.catchBlocks[i];
//let stack = 0;
let start = -1;
let end = -1;
let scope = 0;
for (let c: number = 0; c < q.length; c++) {
const pos = q[c].position;
if (pos >= block.start) {
start = pos;
break;
}
// propogade it
// stack = q[c].stack;
if (!Settings.NO_PROPAGATE_SCOPES_FOR_TRY)
scope = q[c].scope;
}
for (let c: number = q.length - 1; c >= 0; c--) {
const pos = q[c].position;
if (pos <= block.end) {
end = pos;
break;
}
}
if (!catchStart[start])
catchStart[start] = [];
catchStart[start].push(block);
if (!catchEnd[end])
catchEnd[end] = [];
catchEnd[end].push(block);
// make sure that the target-instruction for the catch is propagated and set as target
// using the stack value of the start-instruction does seem to do the trick
// and this broadcast bug, if breakpoints is exist
// IMPORTANT! Catch block push error on top of stack
// this is why stack should start from 1 instead of 0
const s = propagateStack(block.target, 1, q);
if (s < minStack) minStack = s;
// IMPORTANT! SCOPE SHOULD BE PROPOGADED TOO
propagateScope(block.target, scope, q);
jumps.push(block.target);
}
}
propogateTree(q, jumps);
const error = minStack < 0 ? {
message: 'Stack underrun while preprocess, stack:' + minStack,
reason: COMPILATION_FAIL_REASON.UNDERRUN
} : null;
return {
set: q,
jumps,
catchStart,
catchEnd,
error
};
}