UNPKG

@awayfl/avm2

Version:

Virtual machine for executing AS3 code

1,543 lines (1,309 loc) 73.7 kB
/* eslint-disable no-fallthrough */ import { assert } from '@awayjs/graphics'; import { Scope } from './run/Scope'; import { HasNext2Info } from './run/HasNext2Info'; import { AXSecurityDomain } from './run/AXSecurityDomain'; import { validateCall } from './run/validateCall'; import { validateConstruct } from './run/validateConstruct'; import { axCoerceString } from './run/axCoerceString'; import { axCheckFilter } from './run/axCheckFilter'; import { isNumeric, jsGlobal, release } from '@awayfl/swf-loader'; import { Multiname } from './abc/lazy/Multiname'; import { CONSTANT } from './abc/lazy/CONSTANT'; import { MethodInfo } from './abc/lazy/MethodInfo'; import { internNamespace } from './abc/lazy/internNamespace'; import { AXClass, IS_AX_CLASS } from './run/AXClass'; import { axCoerceName } from './run/axCoerceName'; import { ABCFile } from './abc/lazy/ABCFile'; import { ExceptionInfo } from './abc/lazy/ExceptionInfo'; import { Bytecode } from './Bytecode'; import { ASObject } from './nat/ASObject'; import { escapeAttributeValue, escapeElementValue } from './natives/xml'; import { COMPILATION_FAIL_REASON, COMPILER_DEFAULT_OPT, COMPILER_OPT_FLAGS } from './flags'; // generators import { analyze, IAnalyseResult, IAnalyzeError } from './gen/analyze'; import { TinyConstructor } from './gen/TinyConstructor'; import { ICallEntry } from './gen/FastCall'; import { Stat } from './gen/Stat'; import { ComplexGenerator, PhysicsLex, StaticHoistLex, TopLevelLex } from './gen/LexImportsGenerator'; import { emitAnnotation, emitAnnotationOld, emitCloseTryCatch, emitDomainMemOppcodes, emitInlineAccessor as emitAccess, emitInlineLocal, emitInlineMultiname, emitInlineStack, emitOpenTryCatch, emitPrimitiveCoerce, isPrimitiveType, UNDERRUN } from './gen/emiters'; import { emitIsAX, emitIsAXOrPrimitive, extClassConstructor, getExtClassField, IS_EXTERNAL_CLASS, needFastCheck } from './ext/external'; import { AXCallable } from './run/AXCallable'; import { ASClass } from './nat/ASClass'; import { AXObject } from './run/AXObject'; import { COERCE_MODE_ENUM, COERCE_RETURN_MODE_ENUM, Settings } from './Settings'; import { AXFunction } from './run/AXFunction'; import { CompilerState, VAR_KIND } from './gen/CompilerState'; import { Instruction } from './gen/Instruction'; import { TRAIT, TRAITNames } from './abc/lazy/TRAIT'; import { InstanceInfo } from './abc/lazy/InstanceInfo'; import { SlotTraitInfo } from './abc/lazy/SlotTraitInfo'; import { TraitInfo } from './abc/lazy/TraitInfo'; import { RuntimeTraitInfo } from './abc/lazy/RuntimeTraitInfo'; import { axConstructFast, isFastConstructSupport } from './run/axConstruct'; import { ASRegExp } from './nat/ASRegExp'; import { AXGlobal } from './run/AXGlobal'; const METHOD_HOOKS: StringMap<{path: string, place: 'begin' | 'return', hook: Function}> = {}; export const BytecodeName = Bytecode; /** * Try resolve method and attach hook to it */ export function UNSAFE_attachMethodHook( path: string, place: 'begin' | 'return' = 'begin', hook: Function) { if (!path || typeof hook !== 'function') { throw 'Hook path should be exits and function should be a function'; } METHOD_HOOKS[path + '__' + place] = { path, place, hook }; } function generateFunc(body: string, path: string) { body += `\n//# sourceURL=${Settings.HTTP_STRING}jit/${path}.js`; try { return new Function('context', body); } catch (e) { throw new Error('Compiler error:\n\n' + body); } } function findJumpTarget( instrs: Instruction[], jumps: number[], initial: number, target: number ): { startIndex: number, endIndex: number } { let startIndex = -1; let endIndex = -1; for (let i = initial; i < instrs.length; i++) { const inst = instrs[i]; if (inst.position === target) { startIndex = i; } if (startIndex > i && jumps.indexOf(inst.position) >= 0) { endIndex = i; break; } } if (startIndex >= 0 && endIndex === -1) endIndex = instrs.length; return { startIndex, endIndex }; } function isFastReturnVoid( instrs: Instruction[], jumps: number[], initial: number, target: number ): boolean { if (Settings.MAX_INLINE_RETURN <= 0) { return false; } const { startIndex } = findJumpTarget(instrs, jumps, initial, target); return startIndex >= 0 && instrs[startIndex].name === Bytecode.RETURNVOID; } //@ts-ignore self.attach_hook = UNSAFE_attachMethodHook; function resolveTrait(info: InstanceInfo, name: Multiname, scope: Scope): TraitInfo | RuntimeTraitInfo { let trait = info.traits.getTrait(name); let superInstance = info; while (Settings.CHEK_SUPER_TRAITS && !trait && superInstance && superInstance instanceof InstanceInfo) { const superName = superInstance.superName; if (!superName) { return null; } const addDom = (<AXGlobal> scope.global.object).applicationDomain; const superClass = addDom.getClass(superName); superInstance = superClass.classInfo.instanceInfo; if (superInstance.runtimeTraits) { trait = <any> superInstance.runtimeTraits.getTrait(name.namespaces, name.name); if (trait) { return trait; } } trait = superInstance.traits.getTrait(name); if (trait) { return trait; } } return trait; } export interface ICompilerProcess { error?: { message: string, reason: COMPILATION_FAIL_REASON }; source?: string; compiling?: Promise<Function> | undefined; compiled?: Function; names?: Multiname[]; } export interface ICompilerOptions { scope?: Scope; optimise?: COMPILER_OPT_FLAGS; encrupted?: boolean } export function compile(methodInfo: MethodInfo, options: ICompilerOptions = {}): ICompilerProcess { const { optimise = COMPILER_DEFAULT_OPT, scope: executionScope, } = options; const state = new CompilerState(methodInfo); const staticHoistLex = new StaticHoistLex(); // lex generator const lexGen = new ComplexGenerator([ new PhysicsLex(), // generate static aliases for Physics engine new TopLevelLex(), // generate alias for TopLevel props //staticHoistLex // collided with fastCall yet, need fix it ]); const tinyCtr = new TinyConstructor(); // todo // fastCall not supported now, we change compile flow and it generate wrong results // remove or refact it const fastCall = null;//new FastCall(lexGen, executionScope); Stat.begin(''); const USE_OPT = (opt: any) => { return optimise & COMPILER_OPT_FLAGS.ALLOW_CUSTOM_OPTIMISER && !!opt; }; if (USE_OPT(tinyCtr)) { const b = tinyCtr.getBody(methodInfo); if (typeof b === 'function') { Stat.drop(); return { names: [], compiled: b }; } } const meta = methodInfo.meta; const methodName = meta.name; const { error, jumps, catchStart, catchEnd, set : q } = analyze(methodInfo) as IAnalyseResult & IAnalyzeError; // if affilate a generate error, broadcast it if (error) { Stat.drop(); // if a error is not debuggable, drop compilation if (error.reason !== COMPILATION_FAIL_REASON.UNDERRUN || !(optimise & COMPILER_OPT_FLAGS.DEBUG_UNDERRUN)) { return { error }; } } const instanceInfo = <InstanceInfo> (methodInfo.instanceInfo || methodInfo.trait?.holder); const abc = methodInfo.abc; const body = methodInfo.getBody(); const maxstack = body.maxStack; const maxlocal = body.localCount - 1; const maxscope = body.maxScopeDepth - body.initScopeDepth; const js0 = state.headerBlock; const js = state.mainBlock; let domMem = false; for (const q_i of q) { const b = q_i.name; domMem = domMem || (b >= Bytecode.LI8 && b <= Bytecode.SF64); } state.moveIndent(1); const params = methodInfo.parameters; const useESArguments = optimise & COMPILER_OPT_FLAGS.USE_ES_PARAMS; const { paramsShift, annotation } = useESArguments ? emitAnnotation(state) : emitAnnotationOld(state); // store indend after annotation const namesIndent = state.indent; js0.push(annotation); const LOCALS_POS = js0.length; // hack js0.push('__PLACE__FOR__OPTIONAL__LOCALS__'); const optionalLocalVars: Array<{index: number, die: boolean, read: number, write: 0, isArgumentList: boolean}> = []; for (let i: number = params.length + 1 + paramsShift; i <= maxlocal; i++) { optionalLocalVars[i] = { index: i, isArgumentList: i === params.length + 1, read: 0, write: 0, die: false, }; } for (let i = 0; i < maxstack; i++) js0.push(`${namesIndent}let stack${i} = undefined;`); for (let i: number = 0; i < maxscope; i++) js0.push(`${namesIndent}let scope${i} = undefined;`); js0.push(`${namesIndent}let temp = undefined;`); if (domMem) js0.push(`${namesIndent}let domainMemory; // domainMemory`); const names: Multiname[] = state.names; const getname = (n: number) => emitInlineMultiname(state, state.getMultinameIndex(n)); js0.push(`${namesIndent}let sec = context.sec;`); const genBrancher = jumps.length > 1 || catchStart; if (METHOD_HOOKS && METHOD_HOOKS[meta.classPath + '__begin']) { state.emitMain('/* ATTACH METHOD HOOK */'); state.emitMain(`context.executeHook(${emitInlineLocal(state, 0)}, '${meta.classPath + '__begin'}')`); } state.emitMain(''); const catches = catchStart ? Object.keys(catchStart).length : 0; const useLoopGuard = ( Settings.ENABLE_LOOP_GUARD && genBrancher // every cathch has 2 jumps, ignore it && (jumps.length - catches * 2) >= Settings.LOOP_GUARD_MIN_BRANCHES ); if (genBrancher) { if (useLoopGuard) { state.emitMain('let tick = 0;'); } state.emitMain('let p = 0;'); state.emitBeginMain('while (true) {'); // add { automatically if (useLoopGuard) { const loops = Settings.LOOP_GUARD_MAX_LOOPS; state.emitBeginMain(`if (tick++ > ${loops}) {`); state.emitMain( // eslint-disable-next-line max-len `throw 'To many loops (> ${loops}) in "${meta.classPath}" at' + p + ' ,method was dropped to avoid stucking';\n'` ); state.emitEndMain(); } state.emitBeginMain('switch (p) {'); } let currentCatchBlocks: ExceptionInfo[]; let lastZ: Instruction; let z: Instruction; // case + case int genBrancher && state.moveIndent(2); const stackF = (n: number, alias = true) => emitInlineStack(state, n, alias); const local = (n: number) => emitInlineLocal(state, n); const param = (n: number, oppcode?: Instruction) => { const p = oppcode || state.currentOpcode.params; return typeof p === 'number' ? p : p[n]; }; for (let i: number = 0; i < q.length; i++) { // store oppcode in state state.currentOpcode = q[i]; z && (lastZ = z); z = q[i]; USE_OPT(fastCall) && fastCall.killFar(i); if (jumps.indexOf(z.position) >= 0) { // drop aliases for stack, because branching, alias outside branch can be invalid state.dropAllAliases(); // if we are in any try-catch-blocks, we must close them //if (state.openTryCatchGroups) state.openTryCatchGroups.forEach(e => emitCloseTryCatch(state, e)); if (genBrancher) { state.moveIndent(-1); state.emitMain(`case ${z.position}:`); state.moveIndent(1); } // now we reopen all the try-catch again state.openTryCatchGroups.forEach(e => emitOpenTryCatch(state, e)); } /* DIRTY */ currentCatchBlocks = catchStart ? catchStart[z.position] : null; if (currentCatchBlocks) { state.openTryCatchGroups.push(currentCatchBlocks); state.emitBeginMain('try {'); state.moveIndent(1); } if (Settings.PRINT_BYTE_INSTRUCTION) { const params = typeof z.params === 'number' ? z.params : z.params.join(' / '); state.emitMain(`//${BytecodeName[z.name]} ${params} -> ${z.returnTypeId}`); } if (z.comment) { state.emitMain(`//${z.comment}`); } const stack0 = stackF(0); const stack1 = stackF(1); const stack2 = stackF(2); const stack3 = stackF(3); const scope = z.scope > 0 ? `scope${(z.scope - 1)}` : 'context.savedScope'; const scopeN = 'scope' + z.scope; if (z.stack < 0) { state.emitMain('// unreachable'); } else { let localIndex = 0; switch (z.name) { case Bytecode.LABEL: break; case Bytecode.DXNSLATE: state.emitMain(`${scope}.defaultNamespace = context.internNamespace(0, ${stack0});`); break; case Bytecode.DEBUGFILE: break; case Bytecode.DEBUGLINE: break; case Bytecode.DEBUG: break; case Bytecode.THROW: state.emitMain(`throw ${stack0};`); break; case Bytecode.GETLOCAL: { localIndex = param(0); optionalLocalVars[localIndex] && (optionalLocalVars[localIndex].read++); // going onto state, we have a lot of test that allow inlining state.emitGetLocal(-1, localIndex); break; } case Bytecode.SETLOCAL: localIndex = param(0); if (optionalLocalVars[localIndex]) { optionalLocalVars[localIndex].write++; if (!optionalLocalVars[localIndex].read) { optionalLocalVars[localIndex].die = true; } } state.killConstAliasInstruction([stackF(0, false)]); state.popAnyAlias(local(localIndex)); state.emitMain(`${local(localIndex)} = ${stack0};`); // this is unpossible, because AVM not store `this` in local another that 0 /* if (state.isThisAlias(stack0)) { state.pushThisAlias(local(localIndex)); } */ break; case Bytecode.GETSLOT: state.popAnyAlias(stack0); // slots can be get/set only on AX objects state.emitMain(`${stackF(0, false)} = ${stack0}.axGetSlot(${param(0)});`); break; case Bytecode.SETSLOT: state.emitMain(`${stack1}.axSetSlot(${param(0)}, ${stack0});`); break; case Bytecode.GETGLOBALSCOPE: state.popAnyAlias(stackF(-1, false)); state.emitMain(`${stackF(-1, false)} = context.savedScope.global.object;`); break; case Bytecode.PUSHSCOPE: staticHoistLex?.markScope(scopeN, js.length); // extends can be used only on AXObject state.emitMain(`${scopeN} = ${scope}.extend(${stack0});`); break; case Bytecode.PUSHWITH: state.emitMain(`${scopeN} = context.pushwith(${scope}, ${stack0});`); break; case Bytecode.POPSCOPE: state.emitMain(`${scope} = undefined;`); break; case Bytecode.GETSCOPEOBJECT: state.popAnyAlias(stackF(-1, false)); state.emitMain(`${stackF(-1)} = scope${param(0)}.object;`); break; case Bytecode.NEXTNAME: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = sec.box(${stack1}).axNextName(${stack0});`); break; case Bytecode.NEXTVALUE: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = sec.box(${stack1}).axNextValue(${stack0});`); break; case Bytecode.HASNEXT: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = sec.box(${stack1}).axNextNameIndex(${stack0});`); break; case Bytecode.HASNEXT2: state.popAnyAlias(stackF(-1, false)); state.emitMain(`temp = context.hasnext2(${local(param(0))}, ${local(param(1))});`); state.emitMain(`${local(param(0))} = temp[0];`); state.emitMain(`${local(param(1))} = temp[1];`); state.emitMain(`${stackF(-1)} = ${local(param(1))} > 0;`); break; case Bytecode.IN: state.popAnyAlias(stackF(1, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(1)} = (${stack1} && ${stack1}.axClass === sec.AXQName) ? obj.axHasProperty(${stack1}.name) : ${stack0}.axHasPublicProperty(${stack1});`); break; case Bytecode.DUP: state.popAnyAlias(stackF(-1, false)); state.emitMain(`${stackF(-1)} = ${stack0};`); state.pushThisAlias(stackF(-1), stack0); break; case Bytecode.POP: // it real pop stack0 ? state.popAnyAlias(stackF(0, false)); //js.push(`${idnt};`) break; case Bytecode.SWAP: { state.popAnyAlias(stackF(0, false)); state.popAnyAlias(stackF(1, false)); state.emitMain(`temp = ${stack0};`); state.emitMain(`${stackF(0)} = ${stack1};`); state.emitMain(`${stackF(1)} = temp;`); state.emitMain('temp = undefined;'); break; } case Bytecode.PUSHTRUE: state.emitConst(-1,true); break; case Bytecode.PUSHFALSE: state.emitConst(-1,false); break; case Bytecode.PUSHBYTE: state.emitConst(-1, param(0)); break; case Bytecode.PUSHSHORT: state.emitConst(-1, param(0)); break; case Bytecode.PUSHINT: state.emitConst(-1, abc.ints[param(0)]); break; case Bytecode.PUSHUINT: state.emitConst(-1, abc.uints[param(0)]); break; case Bytecode.PUSHDOUBLE: state.emitConst(-1, abc.doubles[param(0)]); break; case Bytecode.PUSHSTRING: state.emitConst(-1, abc.getString(param(0))); break; case Bytecode.PUSHNAN: state.emitConst(-1, NaN); break; case Bytecode.PUSHNULL: state.emitConst(-1, null); break; case Bytecode.PUSHUNDEFINED: state.emitConst(-1, undefined); break; case Bytecode.IFEQ: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (${stack0} == ${stack1}) { return; }`); break; } state.emitMain(`if (${stack0} == ${stack1}) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFNE: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (${stack0} != ${stack1}) { return; }`); break; } state.emitMain(`if (${stack0} != ${stack1}) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFSTRICTEQ: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (${stack0} === ${stack1}) { return; }`); break; } state.emitMain(`if (${stack0} === ${stack1}) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFSTRICTNE: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (${stack0} !== ${stack1}) { return; }`); break; } state.emitMain(`if (${stack0} !== ${stack1}) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFNLE: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (!(${stack0} >= ${stack1})) { return; }`); break; } state.emitMain(`if (!(${stack0} >= ${stack1})) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFGT: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (${stack0} < ${stack1}) { return; }`); break; } state.emitMain(`if (${stack0} < ${stack1}) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFNLT: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (!(${stack0} > ${stack1})) { return; }`); break; } state.emitMain(`if (!(${stack0} > ${stack1})) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFGE: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (${stack0} <= ${stack1}) { return; }`); break; } state.emitMain(`if (${stack0} <= ${stack1}) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFNGE: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (!(${stack0} <= ${stack1})) { return; }`); break; } state.emitMain(`if (!(${stack0} <= ${stack1})) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFLT: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (${stack0} > ${stack1}) { return; }`); break; } state.emitMain(`if (${stack0} > ${stack1}) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFNGT: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (!(${stack0} < ${stack1})) { return; }`); break; } state.emitMain(`if (!(${stack0} < ${stack1})) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFLE: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (${stack0} >= ${stack1}) { return; }`); break; } state.emitMain(`if (${stack0} >= ${stack1}) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFFALSE: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (!${stack0}) { return; }`); break; } state.emitMain(`if (!${stack0}) { p = ${param(0)}; continue; };`); break; } case Bytecode.IFTRUE: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain(`if (${stack0}) { return; }`); break; } state.emitMain(`if (${stack0}) { p = ${param(0)}; continue; };`); break; } case Bytecode.LOOKUPSWITCH: { const jj = (<[]>z.params).concat(); const dj = jj.shift(); // eslint-disable-next-line max-len state.emitMain(`if (${stack0} >= 0 && ${stack0} < ${jj.length}) { p = [${jj.join(', ')}][${stack0}]; continue; } else { p = ${dj}; continue; };`); break; } case Bytecode.JUMP: { if (isFastReturnVoid(q, jumps, i, param(0))) { state.emitMain('//JIT: Emit inline return'); state.emitMain('return;'); break; } state.emitMain(`{ p = ${param(0)}; continue; };`); break; } case Bytecode.INCREMENT: state.popAnyAlias(stackF(0, false)); state.emitMain(`${stackF(0)}++;`); break; case Bytecode.DECREMENT: state.popAnyAlias(stackF(0, false)); state.emitMain(`${stackF(0)}--;`); break; case Bytecode.INCLOCAL: state.emitMain(`${local(param(0))}++;`); break; case Bytecode.DECLOCAL: state.emitMain(`${local(param(0))}--;`); break; case Bytecode.INCREMENT_I: state.popAnyAlias(stackF(0, false)); state.emitMain(`${stackF(0)} = (${stackF(0)} | 0) + 1;`); break; case Bytecode.DECREMENT_I: state.popAnyAlias(stackF(0, false)); state.emitMain(`${stackF(0)} = (${stackF(0)} | 0) - 1;`); break; case Bytecode.INCLOCAL_I: state.emitMain(`${local(param(0))} = (${local(param(0))} | 0) + 1;`); break; case Bytecode.DECLOCAL_I: state.emitMain(`${local(param(0))} = (${local(param(0))} | 0) - 1;`); break; case Bytecode.NEGATE_I: state.popAnyAlias(stackF(0, false)); state.emitMain(`${stackF(0)} = -(${stack0} | 0);`); break; case Bytecode.ADD_I: state.killConstAliasInstruction([stackF(0, false)]); state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = (${stack1} | 0) + (${stack0} | 0);`); break; case Bytecode.SUBTRACT_I: state.killConstAliasInstruction([stackF(0, false)]); state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = (${stack1} | 0) - (${stack0} | 0);`); break; case Bytecode.MULTIPLY_I: state.killConstAliasInstruction([stackF(0, false)]); state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = (${stack1} | 0) * (${stack0} | 0);`); break; case Bytecode.ADD: state.killConstAliasInstruction([stackF(0, false)]); // LOL, this can be used when this used in string concation state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} += ${stack0};`); break; case Bytecode.SUBTRACT: state.killConstAliasInstruction([stackF(0, false)]); state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} -= ${stack0};`); break; case Bytecode.MULTIPLY: state.killConstAliasInstruction([stackF(0, false)]); state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} *= ${stack0};`); break; case Bytecode.DIVIDE: state.killConstAliasInstruction([stackF(0, false)]); state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} /= ${stack0};`); break; case Bytecode.MODULO: state.killConstAliasInstruction([stackF(0, false)]); state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} %= ${stack0};`); break; case Bytecode.LSHIFT: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} <<= ${stack0};`); break; case Bytecode.RSHIFT: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} >>= ${stack0};`); break; case Bytecode.URSHIFT: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} >>>= ${stack0};`); break; case Bytecode.BITAND: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} &= ${stack0};`); break; case Bytecode.BITOR: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} |= ${stack0};`); break; case Bytecode.BITXOR: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} ^= ${stack0};`); break; case Bytecode.EQUALS: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = ${stack1} == ${stack0};`); break; case Bytecode.STRICTEQUALS: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = ${stack1} === ${stack0};`); break; case Bytecode.GREATERTHAN: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = ${stack1} > ${stack0};`); break; case Bytecode.GREATEREQUALS: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = ${stack1} >= ${stack0};`); break; case Bytecode.LESSTHAN: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = ${stack1} < ${stack0};`); break; case Bytecode.LESSEQUALS: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = ${stack1} <= ${stack0};`); break; case Bytecode.NOT: state.popAnyAlias(stackF(0, false)); state.emitMain(`${stackF(0)} = !${stack0};`); break; case Bytecode.BITNOT: state.popAnyAlias(stackF(0, false)); state.emitMain(`${stackF(0)} = ~${stack0};`); break; case Bytecode.NEGATE: state.popAnyAlias(stackF(0, false)); state.emitMain(`${stackF(0)} = -${stack0};`); break; case Bytecode.TYPEOF: state.popAnyAlias(stackF(0, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(0)} = typeof ${stack0} === 'undefined' ? 'undefined' : context.typeof(${stack0});`); break; case Bytecode.INSTANCEOF: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = ${stack0}.axIsInstanceOf(${stack1});`); break; case Bytecode.ISTYPE: state.popAnyAlias(stackF(0, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(0)} = ${scope}.getScopeProperty(${getname(param(0))}, true, false).axIsType(${stack0});`); break; case Bytecode.ISTYPELATE: state.popAnyAlias(stackF(1, false)); state.emitMain(`${stackF(1)} = ${stack0}.axIsType(${stack1});`); break; case Bytecode.ASTYPE: if ((optimise & COMPILER_OPT_FLAGS.SKIP_NULL_COERCE) && (lastZ.name === Bytecode.PUSHNULL || lastZ.name === Bytecode.PUSHUNDEFINED)) { state.emitMain('// SKIP_NULL_COERCE'); break; } state.popAnyAlias(stackF(0, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(0)} = ${scope}.getScopeProperty(${getname(param(0))}, true, false).axAsType(${stack0});`); break; case Bytecode.ASTYPELATE: state.popAnyAlias(stackF(1, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(1)} = ${emitIsAXOrPrimitive(stack1)} ? ${stack0}.axAsType(${stack1}) : ${stack1};`); break; case Bytecode.CALL: { state.popAnyAlias(stackF(param(0) + 1, false)); const pp = []; const obj = stackF(param(0) + 1); for (let j: number = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } // eslint-disable-next-line max-len state.emitMain(`${obj} = context.call(${stackF(param(0) + 1)}, ${stackF(param(0))}, [${pp.join(', ')}], ${scope});`); break; } case Bytecode.CONSTRUCT: { state.popAnyAlias(stackF(param(0), false)); const pp = []; const obj = stackF(param(0)); for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } const alias = state.getStackAlias(param(0)); if (alias && (alias.kind === VAR_KIND.LOOKUP || alias.kind === VAR_KIND.ALIAS) && alias.type) { state.emitMain('//JIT: Possible source:' + alias.type); if (alias.type.name === 'RegExp') { state.emitMain(`${obj} = context.getRegExp([${pp.join(', ')}]);`); break; } } // eslint-disable-next-line max-len state.emitMain(`${obj} = context.construct(${obj}, [${pp.join(', ')}]);`); break; } case Bytecode.CALLPROPERTY: { const mn = abc.getMultiname(param(1)); const pp = []; const obj = stackF(param(0)); for (let j: number = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } state.killConstAliasInstruction([stackF(param(0), false)]); state.popAnyAlias(stackF(param(0), false)); const targetStack = stackF(param(0)); if (abc.getMultiname(param(1)).name == 'getDefinitionByName') { // eslint-disable-next-line max-len state.emitMain(`${targetStack} = context.getdefinitionbyname(${scope}, ${obj}, [${pp.join(', ')}]);`); } else { let d: ICallEntry; if (USE_OPT(fastCall) && (d = fastCall.sureThatFast(`${obj}`, mn.getMangledName()))) { const n = d.isMangled ? Multiname.getPublicMangledName(mn.name) : mn.name; fastCall.kill(`${obj}`); state.emitMain('/* We sure that this safe call */'); if (d.isFunc) { state.emitMain(`${targetStack} = ${emitAccess(obj, n)}(${pp.join(', ')});`); } else { // eslint-disable-next-line max-len state.emitMain(`${targetStack} = /*fast*/sec.box(${obj}).axCallProperty(${getname(param(1))}, [${pp.join(', ')}], false);`); } break; } // we can check trite for `this` or any types that has trite // @todo move this to fast-call if (Settings.CHEK_TRAIT_GET_CALL && obj === 'this' && instanceInfo) { const trait = resolveTrait(instanceInfo, mn, executionScope); if (trait) { // eslint-disable-next-line max-len state.emitMain(`/* We sure that this safe call, represented in TRAIT as ${TRAITNames[trait.kind]} */ `); if (trait.kind === TRAIT.Method) { // eslint-disable-next-line max-len state.emitMain(`${targetStack} = ${emitAccess(obj, mn.getMangledName())}(${pp.join(', ')});`); } else { // when is method, we should wrap caller, because JS miss `this` // when method is used as outside object // we can do with BIND, but this is can be unstable // eslint-disable-next-line max-len state.emitMain(`${targetStack} = /*fast*/${obj}.axCallProperty(${getname(param(1))}, [${pp.join(', ')}], false);`); } break; } } const fast = needFastCheck() && (obj !== 'this' || !Settings.NO_CHECK_FASTCALL_FOR_THIS); if (fast) { state.emitMain(`if (!${emitIsAXOrPrimitive(obj)}) {`); // fast instruction already binded if (obj.split('__')[1] == mn.name) state.emitMain(` ${targetStack} = ${pp.join(', ')};`); else state.emitMain(` ${targetStack} = ${emitAccess(obj, mn.name)}(${pp.join(', ')});`); state.emitBeginMain('} else {'); } state.emitMain(`// ${mn}`); state.emitBeginMain(); // { const box = !Settings.NO_CHECK_BOXED_THIS || obj !== 'this'; if (box) { state.emitMain(`let t = ${obj};`); const accessor = emitAccess('t', '$Bg' + mn.name); // eslint-disable-next-line max-len state.emitMain(`const m = ${accessor} || (t = sec.box(${obj}), ${accessor});`); } else { state.emitMain(`const m = ${emitAccess(obj, '$Bg' + mn.name)};`); } state.emitMain('if( typeof m === "function" ) { '); // eslint-disable-next-line max-len state.emitMain(` ${targetStack} = ${emitAccess(box ? 't' : obj, '$Bg' + mn.name)} (${pp.join(', ')});`); state.emitMain('} else { '); // eslint-disable-next-line max-len state.emitMain(` ${targetStack} = ${obj}.axCallProperty(${getname(param(1))}, [${pp.join(', ')}], false);`); state.emitMain('}'); state.emitEndMain(); // } if (fast) { state.emitEndMain(); // } } } break; } case Bytecode.CALLPROPLEX: { const mn = abc.getMultiname(param(1)); state.popAnyAlias(stackF(param(0), false)); const targetStack = stackF(param(0)); const pp = []; for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } state.emitMain(`temp = sec.box(${targetStack});`); const accessor = emitAccess('temp', '$Bg' + mn.name); // eslint-disable-next-line max-len state.emitMain(`${targetStack} = (typeof ${accessor} === 'function')? ${accessor}(${pp.join(', ')}) : temp.axCallProperty(${getname(param(1))}, [${pp.join(', ')}], true);`); } break; case Bytecode.CALLPROPVOID: { const mn = abc.getMultiname(param(1)); const pp = []; const obj = stackF(param(0)); for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } state.killConstAliasInstruction([stackF(param(0), false)]); { if (USE_OPT(fastCall) && fastCall.sureThatFast(obj)) { const n = fastCall.sureThatFast(obj).isMangled ? Multiname.getPublicMangledName(mn.name) : mn.name; state.emitMain('/* We sure that this safe call */ '); state.emitMain(`${emitAccess(obj, n)}(${pp.join(', ')});`); fastCall.kill(`${obj}`); break; } } // we can check trite for `this` or any types that has trite // @todo move this to fast-call if (Settings.CHEK_TRAIT_GET_CALL && obj === 'this' && instanceInfo) { const trait = resolveTrait(instanceInfo, mn, executionScope); if (trait) { // eslint-disable-next-line max-len state.emitMain(`/* We sure that this safe call, represented in TRAIT as ${TRAITNames[trait.kind]} */ `); if (trait.kind === TRAIT.Method) { // eslint-disable-next-line max-len state.emitMain(`${emitAccess(obj, mn.getMangledName())}(${pp.join(', ')});`); } else { // when is method, we should wrap caller, because JS miss `this` // when method is used as outside object // we can do with BIND, but this is can be unstable // eslint-disable-next-line max-len state.emitMain(`/*fast*/${obj}.axCallProperty(${getname(param(1))}, [${pp.join(', ')}], false);`); } break; } } const fast = needFastCheck() && (obj !== 'this' || !Settings.NO_CHECK_FASTCALL_FOR_THIS); if (fast) { state.emitMain(`if (!${emitIsAXOrPrimitive(obj)}) {`); state.emitMain(` ${emitAccess(obj, mn.name)}(${pp.join(', ')});`); state.emitMain('} else {'); state.moveIndent(1); } state.emitMain(`// ${mn}`); state.emitBeginMain(); // { state.emitMain(`let t = ${obj};`); const accessor = emitAccess('t', '$Bg' + mn.name); state.emitMain(`const m = ${accessor} || (t = sec.box(${obj}), ${accessor});`); state.emitMain('if( typeof m === "function" ) { '); state.emitMain(` m.call(t${pp.length ? ', ' : ''}${pp.join(', ')});`); state.emitMain('} else { '); state.emitMain(` ${obj}.axCallProperty(${getname(param(1))}, [${pp.join(', ')}], false);`); state.emitMain('}'); state.emitEndMain(); // } if (fast) { state.emitEndMain(); // } } } break; case Bytecode.APPLYTYPE: { const pp = []; state.popAnyAlias(stackF(param(0), false)); const targetStack = stackF(param(0)); for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } state.emitMain(`${targetStack} = sec.applyType(${targetStack}, [${pp.join(', ')}]);`); break; } case Bytecode.FINDPROPSTRICT: { const mn = abc.getMultiname(param(0)); state.emitMain(`// ${mn}`); state.popAnyAlias(stackF(-1, false)); const target = stackF(-1); if (USE_OPT(lexGen) && lexGen.test(mn, false)) { state.emitMain('/* GenerateLexImports */'); // eslint-disable-next-line max-len const lexAlias = lexGen.getPropStrictAlias(mn,{ //nameAlias: getname(param(0)), mnIndex: state.getMultinameIndex(param(0)) /*findProp: true,*/ }); //state.emitMain(`${target} = ${lexAlias};`); // alias as const, this allow inline it state.emitConst(-1, lexAlias, false); if (USE_OPT(fastCall)) { const mangled = (lexGen.getGenerator(mn, false) instanceof TopLevelLex); fastCall.mark(target, i, mangled, mn); } break; } // generate fast lookup for get/call // scope === 1 is extended scope if (Settings.CHEK_TRAIT_GET_CALL && Settings.CHEK_TRAIT_FIND_PROP && z.scope === 1 && instanceInfo ) { const trait = resolveTrait(instanceInfo, mn, executionScope); if (trait) { // eslint-disable-next-line max-len state.emitMain(`/* We sure that this scope owner, represented in TRAIT as ${TRAITNames[trait.kind]} */ `); state.emitGetLocal(-1, 0); break; } } state.emitMain(`${stackF(-1)} = ${scope}.findScopeProperty(${getname(param(0))}, true, false);`); break; } case Bytecode.FINDPROPERTY: state.emitMain(`// ${abc.getMultiname(param(0))}`); state.popAnyAlias(stackF(-1, false)); state.emitMain(`${stackF(-1)} = ${scope}.findScopeProperty(${getname(param(0))}, false, false);`); break; case Bytecode.NEWFUNCTION: state.emitMain(`// ${abc.getMethodInfo(param(0))}`); state.popAnyAlias(stackF(-1, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(-1)} = context.createFunction(${param(0)}, ${scope}, true, ${methodInfo.index()});`); break; case Bytecode.NEWCLASS: state.emitMain(`// ${abc.classes[param(0)]}`); state.popAnyAlias(stackF(0, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(0)} = sec.createClass(context.abc.classes[${param(0)}], ${stack0}, ${scope});`); break; case Bytecode.GETDESCENDANTS: { const mn = abc.getMultiname(param(0)); state.emitMain(`// ${mn}`); if (mn.isRuntimeName()) { const runtime = mn.isRuntimeName() && mn.isRuntimeNamespace(); state.popAnyAlias(runtime ? stackF(2, false) : stackF(1, false)); const target = runtime ? stackF(2) : stackF(1); state.emitBeginMain(); //{ if (runtime) { // eslint-disable-next-line max-len state.emitMain(`const rn = context.runtimename(${getname(param(0))}, ${stack0}, ${stack1});`); } else { state.emitMain(`const rn = context.runtimename(${getname(param(0))}, ${stack0});`); } state.emitMain(`${target} = ${target}.descendants(rn);`); state.emitEndMain(); // } break; } else { state.popAnyAlias(stackF(0, false)); state.emitMain(`${stackF(0)} = ${stack0}.descendants(${getname(param(0))});`); } break; } case Bytecode.NEWARRAY: { const pp = []; for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } state.popAnyAlias(stackF(param(0) - 1, false)); state.emitMain(`${stackF(param(0) - 1)} = sec.AXArray.axBox([${pp.join(', ')}]);`); break; } case Bytecode.NEWOBJECT: { state.emitMain('temp = Object.create(sec.AXObject.tPrototype);'); for (let j: number = 1; j <= param(0); j++) { // eslint-disable-next-line max-len state.emitMain(`temp.axSetPublicProperty(${stackF(2 * param(0) - 2 * j + 1)}, ${stackF(2 * param(0) - 2 * j)});`); } state.popAnyAlias(stackF(2 * param(0) - 1, false)); state.emitMain(`${stackF(2 * param(0) - 1)} = temp;`); state.emitMain('temp = undefined;'); break; } case Bytecode.NEWACTIVATION: state.popAnyAlias(stackF(-1, false)); state.emitMain(`${stackF(-1)} = sec.createActivation(context.mi, ${scope});`); break; case Bytecode.NEWCATCH: state.popAnyAlias(stackF(-1, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(-1)} = sec.createCatch(context.mi.getBody().catchBlocks[${param(0)}], ${scope});`); break; case Bytecode.CONSTRUCTSUPER: { const pp = []; for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } state.emitMain(`context.savedScope.superConstructor.call(${stackF(param(0))}, ${pp.join(', ')});`); } break; case Bytecode.CALLSUPER: { const pp = []; state.popAnyAlias(stackF(param(0), false)); const targetStack = stackF(param(0)); for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } // eslint-disable-next-line max-len state.emitMain(`${targetStack} = sec.box(${targetStack}).axCallSuper(${getname(param(1))}, context.savedScope, [${pp.join(', ')}]);`); break; } case Bytecode.CALLSUPER_DYN: { const pp = []; for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } const mn = abc.getMultiname(param(1)); if (mn.isRuntimeName() && mn.isRuntimeNamespace()) { state.popAnyAlias(stackF(param(0) + 2, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(param(0) + 2)} = sec.box(${stackF(param(0) + 2)}).axGetSuper(context.runtimename(${getname(param(1))}, ${stackF(param(0))}, ${stackF(param(0) + 1)}), context.savedScope, [${pp.join(', ')}]);`); } else { state.popAnyAlias(stackF(param(0) + 1, false)); // eslint-disable-next-line max-len state.emitMain(`${stackF(param(0) + 1)} = sec.box(${stackF(param(0) + 1)}).axGetSuper(context.runtimename(${getname(param(1))}, ${stackF(param(0))}), context.savedScope, [${pp.join(', ')}]);`); } break; } case Bytecode.CALLSUPERVOID: { const pp = []; for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } // eslint-disable-next-line max-len state.emitMain(`sec.box(${stackF(param(0))}).axCallSuper(${getname(param(1))}, context.savedScope, [${pp.join(', ')}]);`); } break; case Bytecode.CONSTRUCTPROP: { const pp = []; const targetStack = stackF(param(0), false); const of = stackF(param(0)); // order is important, we should check constant assigment to stack/local // before pop alias for this stack/local, // otherwise we lost a offset and can't remove redundant instruction state.killConstAliasInstruction([targetStack]); state.popAnyAlias(targetStack); state.setStackAlias(param(0), { kind: VAR_KIND.LOOKUP, type: abc.getMultiname(param(1)), scope: -1000, }); for (let j = 1; j <= param(0); j++) { pp.push(stackF(param(0) - j)); state.killConstAliasInstruction([stackF(param(0) - j, false)]); } let supportFast = false; const trace = []; if (Settings.CHECK_FAST_CONSTRUCTOR) { supportFast = isFastConstructSupport(abc.getMultiname(param(1)), trace); } if (supportFast) { state.emitMain('//JIT: Support fast construct:' + trace.join('/')); // eslint-disable-next-line max-len state.emitMain(`${stackF(param(0))} = context.constructFast(${of}, [${pp.join(', ')}], ${getname(param(1))});`); } else { // eslint-disable-next-line max-len state.emitMain(`${stackF(param(0))} = context.constructprop(${getname(param(1))}, ${of}, [${pp.join(', ')}]);`); } USE_OPT(fastCall) && fastCall.kill(stackF(param(0))); } break; case Bytecode.GETPROPERTY: { const mn = abc.getMultiname(param(0)); const of = stackF(0); const target = stackF(0, false); if (lastZ.name === Bytecode.FINDPROPSTRICT && state.constAliases[target]?.value == mn.namespace.uri.replace(/\./g, '_') + '__' + mn.name) { break; } state.killConstAliasInstruction([target]); state.popAnyAlias(stackF(0, false)); state.emitMain(`// ${mn}`); { let d: ICallEntry; if (USE_OPT(fastCall) && (d = fastCall.sureThatFast(of, mn.name))) { const n = d.isMangled ? Multiname.getPublicMangledName(mn.name) : mn.name; fastCall.kill(of); state.emitMain('/* We sure that this safe get */ '); state.emitMain(`${target} = ${emitAccess(of, n)};`); break; } } const info = state.getStackAlias(0); if (info) { state.emitMain('//Possible type:' + (<any>info).type); } // we can check trite for `this` or any types that has trite // @todo Move this inside fast-call optimiser if (Settings.CHEK_TRAIT_GET_CALL && stack0 === 'this' && instanceInfo) { const trait = resolveTrait(instanceInfo, mn, executionScope); if (trait) { // eslint-disable-next-line max-len state.emitMain(`/* We sure that this safe get, represented in TRAIT as ${TRAITNames[trait.kind]}, type: ${(<SlotTraitInfo> trait).typeName} */ `); if (trait.kind === TRAIT.Method) { state.emitMain(`${target} = this.axGetProperty(${getname(param(0))});`); break; } else if ( trait.kind === TRAIT.Slot || trait.kind === TRAIT.GetterSetter || trait.kind === TRAIT.Getter ) { state.emitMain(`${target} = ${emitAccess(stack0, mn.getMangledName())};`); state.setStackAlias(0, { kind: VAR_KIND.VAR, type: <Multiname> (<SlotTraitInfo> trait).typeName, }); break; } } } state.setStackAlias(0); const fast = needFastCheck() && (stack0 !== 'this' || !Settings.NO_CHECK_FASTCALL_FOR_THIS); if (fast) { state.emitMain(`if (!${emitIsAX(stack0)}) {`); state.emitMain(` ${target} = ${stack0}['${mn.name}'];`); state.emitBeginMain('} else {'); } const box = !Settings.NO_CHECK_BOXED_THIS || stack0 !== 'this'; if (box) { state.emitMain(`temp = ${stack0}[AX_CLASS_SYMBOL] ? ${stack0} : sec.box(${stack0});`); } state.emitMain(`${target} = ${emitAccess(box ? 'temp' : stack0, '$Bg' + mn.name)};`); state.emitMain(`if (${target} === undefined || typeof ${target} === 'function') {`); state.emitMain(` ${target} = ${box ? 'temp' : stack0}.axGetProperty(${getname(param(0))});`); state.emitMain('}'); if (fast) { state.emitEndMain(); } break; } case Bytecode.GETPROPERTY_DYN: { const mn = abc.getMultiname(param(0)); const runtime = mn.isRuntimeName() && mn.isRuntimeNamespace(); state.popAnyAlias(runtime ? stackF(2, false) : stackF(1, false)); const target = runtime ? stackF(2) : stackF(1); state.emitMain(`// ${mn}`); state.emitBeginMain(); // { if (runtime) { state.emitMain(`const rm = context.runtimename(${getname(param(0))}, ${stack0}, ${stack1});`); state.emitMain('const simple = rm.numeric ? rm.numericValue : rm.name;'); } else { state.emitMain(`let simple = ${stack0};`); } const box = !Settings.NO_CHECK_BOXED_THIS || target !== 'this'; if (box) { state.emitMain(`const b_obj = ${target}[AX_CLASS_SYMBOL] ? ${target} : sec.box(${target});\n`); } else { state.emitMain(`const b_obj = ${target}`); } state.emitMain('if (typeof simple === "number") {'); state.emitMain(` ${target} = b_obj.value[simple];`); state.emitBeginMain('} else {'); state.emitMain(`${target} = b_obj['$Bg' + simple];`); state.emitMain(`if (${target} === undefined || typeof ${target} === 'function') {`); if (!runtime) { state.emitMain(` const rm = context.runtimename(${getname(param(0))}, ${stack0});`); } state.emitMain(` ${target} = b_obj.axGetProperty(rm);`); state.emitMain('}'); state.emitEndMain(); // } state.emitEndMain(); // } break; } case Bytecode.SETPROPERTY: { const mn = abc.getMultiname(param(0)); state.killConstAliasInstruction([stackF(0, false), stackF(1, false)]); state.emitMain(`// ${mn}`); // we can check trite for `this` or any types that has trite // @todo Move this to fast-set optimisator if (Settings.CHEK_TRAIT_SET && stack1 === 'this' && instanceInfo) { const trait = resolveTrait(instanceInfo, mn