@awayfl/avm2
Version:
Virtual machine for executing AS3 code
325 lines (251 loc) • 7.96 kB
text/typescript
import { ABCFile } from '../abc/lazy/ABCFile';
import { ExceptionInfo } from '../abc/lazy/ExceptionInfo';
import { MethodInfo } from '../abc/lazy/MethodInfo';
import { TRAIT } from '../abc/lazy/TRAIT';
import { Settings } from '../Settings';
import { Multiname } from '../abc/lazy/Multiname';
import { Instruction } from './Instruction';
import { emitInlineLocal, emitInlineStack } from './emiters';
export const enum VAR_KIND {
CONST = 'const',
VAR = 'var',
LOOKUP = 'lookup',
ALIAS = 'alias',
}
interface IVar {
pos?: number;
}
interface IConstData extends IVar {
kind: VAR_KIND.CONST;
value: any;
}
interface ILookupData extends IVar {
kind: VAR_KIND.LOOKUP;
scope: number;
type: Multiname | null;
}
interface IAliasData extends IVar {
kind: VAR_KIND.ALIAS;
value: any;
type?: Multiname
}
interface IVarData extends IVar {
kind: VAR_KIND.VAR;
type: Multiname | null;
}
type TVariableArgument = IConstData | IVarData | IAliasData | ILookupData;
export class CompilerState {
private _indent: string = '';
private _indentLen: number = 0;
public methodInfo: MethodInfo
public abc: ABCFile;
public names: Multiname[] = [];
public openTryCatchGroups: ExceptionInfo[][] = [];
// same as js0 in compile
public mainBlock: string[] = [];
// same as js in compile
public headerBlock: string[] = [];
public opcodes: Instruction[];
public currentOpcode: Instruction;
public thisAliases: Set<string> = new Set();
public constAliases: Record<string, IConstData | IAliasData> = {};
// back ref to local-> stack
public localAliases: Record<string, string> = {};
public localTypes: Record<number, Array<Multiname>> = {};
public stackAliases: Record<number, TVariableArgument> = {};
public noHoistMultiname: boolean = Settings.NO_HOIST_MULTINAME;
public get indent() {
return this._indent;
}
public setStackAlias (stackIndex: number, alias: TVariableArgument | null = null): TVariableArgument {
const realIndex = this.evalStackIndex(stackIndex);
this.stackAliases [realIndex] = alias;
return alias;
}
public getStackAlias (stackIndex: number): TVariableArgument | null {
return this.stackAliases [this.evalStackIndex(stackIndex)];
}
public get isPossibleGlobalThis() {
const kind = this.methodInfo.trait && this.methodInfo.trait.kind;
// because in AS3 methods/get/set is stricted with this, it can't be global
return (
kind !== TRAIT.Method &&
kind !== TRAIT.Getter &&
kind !== TRAIT.Setter &&
!this.methodInfo.isConstructor
);
}
public get canUseRealThis() {
if (!Settings.EMIT_REAL_THIS) return false;
return !this.isPossibleGlobalThis;
}
constructor (methodInfo: MethodInfo) {
this.methodInfo = methodInfo;
this.abc = methodInfo.abc;
this.init();
}
private init() {
if (this.methodInfo.parentInfo) {
this.localTypes[0] = [this.methodInfo.parentInfo.typeName];
}
let i = 1;
for (const param of this.methodInfo.parameters) {
this.localTypes[i] = param.typeName ? [param.typeName] : [];
i++;
}
}
public evalStackIndex(stackOffset: number): number {
const stack = this.currentOpcode.stack;
const mapped = (stack - 1 - stackOffset);
if (mapped < 0) return -1;
return mapped;
}
public moveIndent (offset: number) {
this._indentLen += offset * 4;
if (this._indentLen < 0)
this._indentLen = 0;
this._indent = (' ').repeat(this._indentLen);
return this._indent;
}
public getMultinameIndex(nameOrIndex: Multiname | number) {
const name = typeof nameOrIndex === 'number'
? this.abc.getMultiname(nameOrIndex)
: nameOrIndex;
const index = this.names.indexOf(name);
if (index > -1)
return index;
this.names.push(name);
return this.names.length - 1;
}
/**
* Emit constant assigment, and store it in alias tree
* @param stackIndex
* @param value
* @param isConst - value real primitive const value, not a const alias
*/
public emitConst(stackIndex: number, value: any, isConst = true) {
const real = this.evalStackIndex(stackIndex);
const name = emitInlineStack(this, stackIndex, false);
this.popAnyAlias(name);
if (Settings.UNSAFE_INLINE_CONST) {
this.constAliases[name] = {
value,
kind: isConst ? VAR_KIND.CONST : VAR_KIND.ALIAS,
pos: this.mainBlock.length
};
this.stackAliases[real] = this.constAliases[name];
}
if (isConst && typeof value === 'string')
value = JSON.stringify(value);
return this.mainBlock.push(this.indent + name + ' = ' + value + ';');
}
public emitGetLocal(stackIndex: number, localIndex: number) {
const stack = emitInlineStack(this, stackIndex, false);
this.popAnyAlias(stack);
// local 0 is ALWAYS THIS
if (localIndex === 0) {
this.pushThisAlias(stack);
}
if (Settings.UNSAFE_INLINE_CONST) {
const local = 'local' + localIndex;
this.constAliases[stack] = { value: local, pos: this.mainBlock.length, kind: VAR_KIND.ALIAS };
this.localAliases[local] = stack;
}
if (this.localTypes[localIndex] && this.localTypes[localIndex][0]) {
this.emitMain('// JIT: potential type:' + this.localTypes[localIndex][0].toString());
}
return this.mainBlock.push(this.indent + stack + ' = ' + emitInlineLocal(this, localIndex) + ';');
}
/**
* Push line to main code block and prepend indent automatically
* @param line Line to emit to generated code
* @returns line count
*/
public emitMain(line: string = ''): number {
return this.mainBlock.push(this.indent + line);
}
/**
* Push line to head code block WITHOUT ident, because it not track it
* @param line Line to emit to generated code
* @returns line count
*/
public emitHead(line: string, indent: string = ''): number {
return this.headerBlock.push(indent + line);
}
/**
* Emit block begin ({) and move indent right
* @param value string that was emited instead of {
*/
public emitBeginMain(value: string = '{') {
const pos = this.emitMain(value);
this.moveIndent(1);
return pos;
}
/**
* Emit block end } and move indent left
*/
public emitEndMain() {
this.moveIndent(-1);
const pos = this.emitMain('}');
return pos;
}
public killConstAliasInstruction(aliases: string[]) {
if (!aliases || aliases.length === 0) return;
for (const a of aliases) {
if (this.constAliases[a]) {
const instr = this.mainBlock[this.constAliases[a].pos];
this.mainBlock[this.constAliases[a].pos] = '//' + instr + '// JIT: redundant assigment, value unused';
}
}
}
public getConstAliasMeta (stackOffset: number): IConstData | IAliasData {
if (!Settings.UNSAFE_INLINE_CONST)
return null;
return this.constAliases['stack' + this.evalStackIndex(stackOffset)];
}
public getConstAlias (alias: string): string {
if (!Settings.UNSAFE_INLINE_CONST)
return alias;
const val = this.constAliases[alias];
if (!val)
return alias;
// we should don't map value for this, because maybe a fast mapping
if (val.kind !== VAR_KIND.CONST) {
return val.value;
}
if (typeof val.value === 'string') {
return JSON.stringify(val.value);
}
return '' + val.value;
}
public isThisAlias(alias: string): boolean {
if (!Settings.UNSAFE_PROPAGATE_THIS)
return false;
return this.thisAliases.has(alias);
}
public pushThisAlias(alias: string, from?: string): boolean {
if (!Settings.UNSAFE_PROPAGATE_THIS)
return false;
if (from && !this.thisAliases.has(from))
return false;
if (this.thisAliases.has(alias))
return false;
this.thisAliases.add(alias);
return true;
}
public dropAllAliases() {
this.constAliases = {};
this.localAliases = {};
}
public popAnyAlias(stackOrLocal: string): boolean {
// remove back referenced alias for local
if (stackOrLocal in this.localAliases) {
const l = stackOrLocal;
stackOrLocal = this.localAliases[l];
delete this.localAliases[l];
}
//remove and const alias, reassigment
delete this.constAliases[stackOrLocal];
return this.thisAliases.delete(stackOrLocal);
}
}