| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft Corporation and contributors. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| |
| #include "RuntimeLanguagePch.h" |
| #include "RuntimeMathPch.h" |
| #include "EHBailoutData.h" |
| #include "Library/JavascriptRegularExpression.h" |
| #if DBG_DUMP |
| #include "ByteCode/OpCodeUtilAsmJs.h" |
| #endif |
| |
| #include "Language/InterpreterStackFrame.h" |
| #include "Library/JavascriptGeneratorFunction.h" |
| #include "Library/ForInObjectEnumerator.h" |
| #include "Library/AtomicsOperations.h" |
| #include "../../WasmReader/WasmParseTree.h" |
| ///---------------------------------------------------------------------------- |
| /// |
| /// macros PROCESS_INtoOUT |
| /// |
| /// This set of macros defines standard patterns for processing OpCodes in |
| /// RcInterpreter::Run(). Each macro is named for "in" - "out": |
| /// - A: Var |
| /// - I: Integer |
| /// - R: Double |
| /// - X: Nothing |
| /// |
| /// Examples: |
| /// - "A2toA1" reads two registers, each storing an Var, and writes a single |
| /// register with a new Var. |
| /// - "A1I1toA2" reads two registers, first an Var and second an Int32, then |
| /// writes two Var registers. |
| /// |
| /// Although these could use lookup tables to standard OpLayout types, this |
| /// additional indirection would slow the main interpreter loop further by |
| /// preventing the main 'switch' statement from using the OpCode to become a |
| /// direct local-function jump. |
| ///---------------------------------------------------------------------------- |
| |
| #define PROCESS_FALLTHROUGH(name, func) \ |
| case OpCode::name: |
| #define PROCESS_FALLTHROUGH_COMMON(name, func, suffix) \ |
| case OpCode::name: |
| |
| #define PROCESS_READ_LAYOUT(name, layout, suffix) \ |
| CompileAssert(OpCodeInfo<OpCode::name>::Layout == OpLayoutType::layout); \ |
| const unaligned OpLayout##layout##suffix * playout = m_reader.layout##suffix(ip); \ |
| Assert((playout != nullptr) == (Js::OpLayoutType::##layout != Js::OpLayoutType::Empty)); // Make sure playout is used |
| |
| |
| #define PROCESS_NOP_COMMON(name, layout, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| break; \ |
| } |
| |
| #define PROCESS_NOP(name, layout) PROCESS_NOP_COMMON(name, layout,) |
| |
| #define PROCESS_CUSTOM_COMMON(name, func, layout, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| func(playout); \ |
| break; \ |
| } |
| |
| #define PROCESS_CUSTOM(name, func, layout) PROCESS_CUSTOM_COMMON(name, func, layout,) |
| |
| #define PROCESS_CUSTOM_L_COMMON(name, func, layout, regslot, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| func(playout); \ |
| break; \ |
| } |
| |
| #define PROCESS_CUSTOM_L(name, func, layout, regslot) PROCESS_CUSTOM_L_COMMON(name, func, layout, regslot,) |
| |
| #define PROCESS_CUSTOM_L_Arg_COMMON(name, func, suffix) PROCESS_CUSTOM_L_COMMON(name, func, Arg, Arg, suffix) |
| #define PROCESS_CUSTOM_L_Arg2_COMMON(name, func, layout, suffix) PROCESS_CUSTOM_L_COMMON(name, func, layout, Arg, suffix) |
| #define PROCESS_CUSTOM_L_Arg(name, func) PROCESS_CUSTOM_L_COMMON(name, func, Arg, Arg,) |
| |
| #define PROCESS_CUSTOM_ArgNoSrc_COMMON(name, func, suffix) PROCESS_CUSTOM_COMMON(name, func, ArgNoSrc, suffix) |
| #define PROCESS_CUSTOM_ArgNoSrc(name, func) PROCESS_CUSTOM_COMMON(name, func, ArgNoSrc,) |
| |
| #define PROCESS_CUSTOM_L_R0_COMMON(name, func, layout, suffix) PROCESS_CUSTOM_L_COMMON(name, func, layout, R0, suffix) |
| #define PROCESS_CUSTOM_L_R0(name, func, layout) PROCESS_CUSTOM_L_COMMON(name, func, layout, R0,) |
| |
| #define PROCESS_CUSTOM_L_Value_COMMON(name, func, layout, suffix) PROCESS_CUSTOM_L_COMMON(name, func, layout, Value, suffix) |
| #define PROCESS_CUSTOM_L_Value(name, func, layout) PROCESS_CUSTOM_L_COMMON(name, func, layout, Value,) |
| |
| #define PROCESS_TRY(name, func) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Br,); \ |
| func(playout); \ |
| ip = m_reader.GetIP(); \ |
| break; \ |
| } |
| |
| #define PROCESS_EMPTY(name, func) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Empty, ); \ |
| func(); \ |
| ip = m_reader.GetIP(); \ |
| break; \ |
| } |
| |
| #define PROCESS_TRYBR2_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrReg2, suffix); \ |
| func((const byte*)(playout + 1), playout->RelativeJumpOffset, playout->R1, playout->R2); \ |
| ip = m_reader.GetIP(); \ |
| break; \ |
| } |
| |
| #define PROCESS_CALL_COMMON(name, func, layout, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| func(playout); \ |
| break; \ |
| } |
| |
| #define PROCESS_CALL(name, func, layout) PROCESS_CALL_COMMON(name, func, layout,) |
| |
| |
| #define PROCESS_CALL_FLAGS_None_COMMON(name, func, layout, suffix) PROCESS_CALL_COMMON(name, func, layout, suffix) |
| #define PROCESS_CALL_FLAGS_Value_COMMON(name, func, layout, suffix) PROCESS_CALL_COMMON(name, func, layout, suffix) |
| |
| |
| #define PROCESS_A1toXX_ALLOW_STACK_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1, suffix); \ |
| func(GetRegAllowStackVar(playout->R0)); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toXX_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1, suffix); \ |
| func(GetReg(playout->R0)); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toXX(name, func) PROCESS_A1toXX_COMMON(name, func,) |
| |
| #define PROCESS_A1toXXMem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1, suffix); \ |
| func(GetReg(playout->R0), GetScriptContext()); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toXXMem(name, func) PROCESS_A1toXXMem_COMMON(name, func,) |
| |
| #define PROCESS_A1toXXMemNonVar_COMMON(name, func, type, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1, suffix); \ |
| func((type)GetNonVarReg(playout->R0), GetScriptContext()); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toXXMemNonVar(name, func, type) PROCESS_A1toXXMemNonVar_COMMON(name, func, type,) |
| |
| #define PROCESS_XXtoA1_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1, suffix); \ |
| SetReg(playout->R0, \ |
| func()); \ |
| break; \ |
| } |
| |
| #define PROCESS_XXtoA1(name, func) PROCESS_XXtoA1_COMMON(name, func,) |
| |
| #define PROCESS_XXtoA1NonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1, suffix); \ |
| SetNonVarReg(playout->R0, \ |
| func()); \ |
| break; \ |
| } |
| |
| #define PROCESS_XXtoA1NonVar(name, func) PROCESS_XXtoA1NonVar_COMMON(name, func,) |
| |
| #define PROCESS_XXtoA1Mem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_XXtoA1Mem(name, func) PROCESS_XXtoA1Mem_COMMON(name, func,) |
| |
| #define PROCESS_A1toA1_ALLOW_STACK_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2, suffix); \ |
| SetRegAllowStackVar(playout->R0, \ |
| func(GetRegAllowStackVar(playout->R1))); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toA1_ALLOW_STACK(name, func) PROCESS_A1toA1_ALLOW_STACK_COMMON(name, func,) |
| |
| #define PROCESS_A1toA1_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1))); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toA1(name, func) PROCESS_A1toA1_COMMON(name, func,) |
| |
| |
| #define PROCESS_A1toA1Profiled_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ProfiledReg2, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1), playout->profileId)); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toA1Profiled(name, func) PROCESS_A1toA1Profiled_COMMON(name, func,) |
| |
| #define PROCESS_A1toA1CallNoArg_COMMON(name, func, layout, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| SetReg(playout->R0, \ |
| func(playout)); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toA1CallNoArg(name, func, layout) PROCESS_A1toA1CallNoArg_COMMON(name, func, layout,) |
| |
| #define PROCESS_A1toA1Mem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1),GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toA1Mem(name, func) PROCESS_A1toA1Mem_COMMON(name, func,) |
| |
| #define PROCESS_A1toA1NonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2, suffix); \ |
| SetNonVarReg(playout->R0, \ |
| func(GetNonVarReg(playout->R1))); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toA1NonVar(name, func) PROCESS_A1toA1NonVar_COMMON(name, func,) |
| |
| #define PROCESS_A1toA1MemNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2, suffix); \ |
| SetNonVarReg(playout->R0, \ |
| func(GetNonVarReg(playout->R1),GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1toA1MemNonVar(name, func) PROCESS_A1toA1MemNonVar_COMMON(name, func,) |
| |
| #define PROCESS_INNERtoA1_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1Unsigned1, suffix); \ |
| SetReg(playout->R0, InnerScopeFromIndex(playout->C1)); \ |
| break; \ |
| } |
| |
| #define PROCESS_INNERtoA1(name, fun) PROCESS_INNERtoA1_COMMON(name, func,) |
| |
| #define PROCESS_U1toINNERMemNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Unsigned1, suffix); \ |
| SetInnerScopeFromIndex(playout->C1, func(GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_U1toINNERMemNonVar(name, func) PROCESS_U1toINNERMemNonVar_COMMON(name, func,) |
| |
| #define PROCESS_XXINNERtoA1MemNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1Unsigned1, suffix); \ |
| SetNonVarReg(playout->R0, \ |
| func(InnerScopeFromIndex(playout->C1), GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_XXINNERtoA1MemNonVar(name, func) PROCESS_XXINNERtoA1MemNonVar_COMMON(name, func,) |
| |
| #define PROCESS_A1INNERtoA1MemNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2Int1, suffix); \ |
| SetNonVarReg(playout->R0, \ |
| func(InnerScopeFromIndex(playout->C1), GetNonVarReg(playout->R1), GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1LOCALtoA1MemNonVar(name, func) PROCESS_A1LOCALtoA1MemNonVar_COMMON(name, func,) |
| |
| #define PROCESS_LOCALI1toA1_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1Unsigned1, suffix); \ |
| SetReg(playout->R0, \ |
| func(this->localClosure, playout->C1)); \ |
| break; \ |
| } |
| |
| #define PROCESS_LOCALI1toA1(name, func) PROCESS_LOCALI1toA1_COMMON(name, func,) |
| |
| #define PROCESS_A1I1toA1_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2Int1, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1), playout->C1)); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1I1toA1(name, func) PROCESS_A1I1toA1_COMMON(name, func,) |
| |
| #define PROCESS_A1I1toA1Mem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2Int1, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1), playout->C1, GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1I1toA1Mem(name, func) PROCESS_A1I1toA1Mem_COMMON(name, func,) |
| |
| #define PROCESS_RegextoA1_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1Unsigned1, suffix); \ |
| SetReg(playout->R0, \ |
| func(this->m_functionBody->GetLiteralRegex(playout->C1), GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_RegextoA1(name, func) PROCESS_RegextoA1_COMMON(name, func,) |
| |
| #define PROCESS_A2toXX_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2, suffix); \ |
| func(GetReg(playout->R0), GetReg(playout->R1)); \ |
| break; \ |
| } |
| |
| #define PROCESS_A2toXX(name, func) PROCESS_A2toXX_COMMON(name, func,) |
| |
| #define PROCESS_A2toXXMemNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2, suffix); \ |
| func(GetNonVarReg(playout->R0), GetNonVarReg(playout->R1), GetScriptContext()); \ |
| break; \ |
| } |
| |
| #define PROCESS_A2toXXMemNonVar(name, func) PROCESS_A2toXXMemNonVar_COMMON(name, func,) |
| |
| #define PROCESS_A1NonVarToA1_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetNonVarReg(playout->R1))); \ |
| break; \ |
| } |
| |
| |
| #define PROCESS_A2NonVarToA1Reg_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg3, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetNonVarReg(playout->R1), playout->R2)); \ |
| break; \ |
| } |
| |
| #define PROCESS_A2toA1Mem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg3, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1), GetReg(playout->R2),GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_A2toA1Mem(name, func) PROCESS_A2toA1Mem_COMMON(name, func,) |
| |
| #define PROCESS_A2toA1MemProfiled_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ProfiledReg3, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1), GetReg(playout->R2),GetScriptContext(), playout->profileId)); \ |
| break; \ |
| } |
| |
| #define PROCESS_A2toA1MemProfiled(name, func) PROCESS_A2toA1MemProfiled_COMMON(name, func,) |
| |
| #define PROCESS_A2toA1NonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg3, suffix); \ |
| SetNonVarReg(playout->R0, \ |
| func(GetNonVarReg(playout->R1), GetNonVarReg(playout->R2))); \ |
| break; \ |
| } |
| |
| #define PROCESS_A2toA1NonVar(name, func) PROCESS_A2toA1NonVar_COMMON(name, func,) |
| |
| #define PROCESS_A2toA1MemNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg3, suffix); \ |
| SetNonVarReg(playout->R0, \ |
| func(GetNonVarReg(playout->R1), GetNonVarReg(playout->R2),GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_A2toA1MemNonVar(name, func) PROCESS_A2toA1MemNonVar_COMMON(name, func,) |
| |
| #define PROCESS_CMMem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg3, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1), GetReg(playout->R2), GetScriptContext()) ? JavascriptBoolean::OP_LdTrue(GetScriptContext()) : \ |
| JavascriptBoolean::OP_LdFalse(GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_CMMem(name, func) PROCESS_CMMem_COMMON(name, func,) |
| |
| #define PROCESS_ELEM_RtU_to_XX_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementRootU, suffix); \ |
| func(playout->PropertyIdIndex); \ |
| break; \ |
| } |
| |
| #define PROCESS_ELEM_RtU_to_XX(name, func) PROCESS_ELEM_RtU_to_XX_COMMON(name, func,) |
| |
| #define PROCESS_ELEM_C2_to_XX_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementScopedC, suffix); \ |
| func(GetEnvForEvalCode(), playout->PropertyIdIndex, GetReg(playout->Value)); \ |
| break; \ |
| } |
| |
| #define PROCESS_ELEM_C2_to_XX(name, func) PROCESS_ELEM_C2_to_XX_COMMON(name, func,) |
| |
| #define PROCESS_GET_ELEM_SLOT_FB_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementSlot, suffix); \ |
| SetReg(playout->Value, \ |
| func((FrameDisplay*)GetNonVarReg(playout->Instance), this->m_functionBody->GetNestedFuncReference(playout->SlotIndex))); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_ELEM_SLOT_FB(name, func) PROCESS_GET_ELEM_SLOT_FB_COMMON(name, func,) |
| |
| #define PROCESS_GET_ELEM_SLOT_FB_HMO_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementSlotI3, suffix); \ |
| SetReg(playout->Value, \ |
| func((FrameDisplay*)GetNonVarReg(playout->Instance), this->m_functionBody->GetNestedFuncReference(playout->SlotIndex), GetReg(playout->HomeObj))); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_ELEM_SLOT_FB_HMO(name, func) PROCESS_GET_ELEM_SLOT_FB_HMO_COMMON(name, func,) |
| |
| #define PROCESS_GET_SLOT_FB_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementSlotI1, suffix); \ |
| SetReg(playout->Value, \ |
| func(this->GetFrameDisplayForNestedFunc(), this->m_functionBody->GetNestedFuncReference(playout->SlotIndex))); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_SLOT_FB(name, func) PROCESS_GET_SLOT_FB_COMMON(name, func,) |
| |
| #define PROCESS_GET_SLOT_FB_HMO_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementSlot, suffix); \ |
| SetReg(playout->Value, \ |
| func(this->GetFrameDisplayForNestedFunc(), this->m_functionBody->GetNestedFuncReference(playout->SlotIndex), GetReg(playout->Instance))); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_SLOT_FB_HMO(name, func) PROCESS_GET_SLOT_FB_HMO_COMMON(name, func,) |
| |
| #define PROCESS_GET_ELEM_IMem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementI, suffix); \ |
| SetReg(playout->Value, \ |
| func(GetReg(playout->Instance), GetReg(playout->Element), GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_ELEM_IMem(name, func) PROCESS_GET_ELEM_IMem_COMMON(name, func,) |
| |
| #define PROCESS_GET_ELEM_IMem_Strict_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementI, suffix); \ |
| SetReg(playout->Value, \ |
| func(GetReg(playout->Instance), GetReg(playout->Element), GetScriptContext(), PropertyOperation_StrictMode)); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_ELEM_IMem_Strict(name, func) PROCESS_GET_ELEM_IMem_Strict_COMMON(name, func,) |
| |
| #define PROCESS_BR(name, func) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Br,); \ |
| ip = func(playout); \ |
| break; \ |
| } |
| |
| #ifdef BYTECODE_BRANCH_ISLAND |
| #define PROCESS_BRLONG(name, func) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrLong,); \ |
| ip = func(playout); \ |
| break; \ |
| } |
| #endif |
| |
| #define PROCESS_BRS(name,func) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrS,); \ |
| if (func(playout->val,GetScriptContext())) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| |
| #define PROCESS_BRB_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrReg1, suffix); \ |
| if (func(GetReg(playout->R1))) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| |
| #define PROCESS_BRB(name, func) PROCESS_BRB_COMMON(name, func,) |
| |
| #define PROCESS_BRB_ALLOW_STACK_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrReg1, suffix); \ |
| if (func(GetRegAllowStackVar(playout->R1))) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| |
| #define PROCESS_BRB_ALLOW_STACK(name, func) PROCESS_BRB_ALLOW_STACK_COMMON(name, func,) |
| |
| #define PROCESS_BRBS_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrReg1, suffix); \ |
| if (func(GetReg(playout->R1), GetScriptContext())) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| |
| #define PROCESS_BRBS(name, func) PROCESS_BRBS_COMMON(name, func,) |
| |
| #define PROCESS_BRBReturnP1toA1_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrReg1Unsigned1, suffix); \ |
| SetReg(playout->R1, func(GetForInEnumerator(playout->C2))); \ |
| if (!GetReg(playout->R1)) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| |
| #define PROCESS_BRBReturnP1toA1(name, func) PROCESS_BRBReturnP1toA1_COMMON(name, func,) |
| |
| #define PROCESS_BRBMem_ALLOW_STACK_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrReg1, suffix); \ |
| if (func(GetRegAllowStackVar(playout->R1),GetScriptContext())) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| #define PROCESS_BRBMem_ALLOW_STACK(name, func) PROCESS_BRBMem_ALLOW_STACK_COMMON(name, func,) |
| |
| #define PROCESS_BRCMem_COMMON(name, func,suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrReg2, suffix); \ |
| if (func(GetReg(playout->R1), GetReg(playout->R2),GetScriptContext())) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| |
| #define PROCESS_BRCMem(name, func) PROCESS_BRCMem_COMMON(name, func,) |
| |
| #define PROCESS_BRPROP(name, func) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrProperty,); \ |
| if (func(GetReg(playout->Instance), playout->PropertyIdIndex, GetScriptContext())) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| |
| #define PROCESS_BRLOCALPROP(name, func) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrLocalProperty,); \ |
| if (func(this->localClosure, playout->PropertyIdIndex, GetScriptContext())) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| |
| #define PROCESS_BRENVPROP(name, func) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, BrEnvProperty,); \ |
| if (func(LdEnv(), playout->SlotIndex, playout->PropertyIdIndex, GetScriptContext())) \ |
| { \ |
| ip = m_reader.SetCurrentRelativeOffset(ip, playout->RelativeJumpOffset); \ |
| } \ |
| break; \ |
| } |
| |
| #define PROCESS_W1(name, func) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, W1,); \ |
| func(playout->C1, GetScriptContext()); \ |
| break; \ |
| } |
| |
| #define PROCESS_U1toA1_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1Unsigned1, suffix); \ |
| SetReg(playout->R0, \ |
| func(playout->C1,GetScriptContext())); \ |
| break; \ |
| } |
| #define PROCESS_U1toA1(name, func) PROCESS_U1toA1_COMMON(name, func,) |
| |
| #define PROCESS_U1toA1NonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1Unsigned1, suffix); \ |
| SetNonVarReg(playout->R0, \ |
| func(playout->C1)); \ |
| break; \ |
| } |
| #define PROCESS_U1toA1NonVar(name, func) PROCESS_U1toA1NonVar_COMMON(name, func,) |
| |
| #define PROCESS_U1toA1NonVar_FuncBody_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1Unsigned1, suffix); \ |
| SetNonVarReg(playout->R0, \ |
| func(playout->C1,GetScriptContext(), this->m_functionBody)); \ |
| break; \ |
| } |
| #define PROCESS_U1toA1NonVar_FuncBody(name, func) PROCESS_U1toA1NonVar_FuncBody_COMMON(name, func,) |
| |
| #define PROCESS_A1I2toXXNonVar_FuncBody(name, func) PROCESS_A1I2toXXNonVar_FuncBody_COMMON(name, func,) |
| |
| #define PROCESS_A1I2toXXNonVar_FuncBody_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg3, suffix); \ |
| func(playout->R0, playout->R1, playout->R2, GetScriptContext(), this->m_functionBody); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1U1toXX_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg1Unsigned1, suffix); \ |
| func(GetReg(playout->R0), playout->C1); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1U1toXX(name, func) PROCESS_A1U1toXX_COMMON(name, func,) |
| |
| #define PROCESS_A1U1toXXWithCache_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ProfiledReg1Unsigned1, suffix); \ |
| func(GetReg(playout->R0), playout->C1, playout->profileId); \ |
| break; \ |
| } |
| |
| #define PROCESS_A1U1toXXWithCache(name, func) PROCESS_A1U1toXXWithCache_COMMON(name, func,) |
| |
| #define PROCESS_EnvU1toXX_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Unsigned1, suffix); \ |
| func(LdEnv(), playout->C1); \ |
| break; \ |
| } |
| |
| #define PROCESS_EnvU1toXX(name, func) PROCESS_EnvU1toXX_COMMON(name, func,) |
| |
| #define PROCESS_GET_ELEM_SLOTNonVar_COMMON(name, func, layout, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| SetNonVarReg(playout->Value, func(GetNonVarReg(playout->Instance), playout)); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_ELEM_SLOTNonVar(name, func, layout) PROCESS_GET_ELEM_SLOTNonVar_COMMON(name, func, layout,) |
| |
| #define PROCESS_GET_ELEM_LOCALSLOTNonVar_COMMON(name, func, layout, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| SetNonVarReg(playout->Value, func((Var*)GetLocalClosure(), playout)); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_ELEM_LOCALSLOTNonVar(name, func, layout) PROCESS_GET_ELEM_LOCALSLOTNonVar_COMMON(name, func, layout,) |
| |
| #define PROCESS_GET_ELEM_PARAMSLOTNonVar_COMMON(name, func, layout, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| SetNonVarReg(playout->Value, func((Var*)GetParamClosure(), playout)); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_ELEM_PARAMSLOTNonVar(name, func, layout) PROCESS_GET_ELEM_PARAMSLOTNonVar_COMMON(name, func, layout,) |
| |
| #define PROCESS_GET_ELEM_INNERSLOTNonVar_COMMON(name, func, layout, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| SetNonVarReg(playout->Value, func(InnerScopeFromIndex(playout->SlotIndex1), playout)); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_ELEM_INNERSLOTNonVar(name, func, layout) PROCESS_GET_ELEM_INNERSLOTNonVar_COMMON(name, func, layout,) |
| |
| #define PROCESS_GET_ELEM_ENVSLOTNonVar_COMMON(name, func, layout, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, layout, suffix); \ |
| SetNonVarReg(playout->Value, func(LdEnv(), playout)); \ |
| break; \ |
| } |
| |
| #define PROCESS_GET_ELEM_ENVSLOTNonVar(name, func, layout) PROCESS_GET_ELEM_ENVSLOTNonVar_COMMON(name, func, layout,) |
| |
| #define PROCESS_SET_ELEM_SLOTNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementSlot, suffix); \ |
| func(GetNonVarReg(playout->Instance), playout->SlotIndex, GetRegAllowStackVarEnableOnly(playout->Value)); \ |
| break; \ |
| } |
| |
| #define PROCESS_SET_ELEM_SLOTNonVar(name, func) PROCESS_SET_ELEM_SLOTNonVar_COMMON(name, func,) |
| |
| #define PROCESS_SET_ELEM_LOCALSLOTNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementSlotI1, suffix); \ |
| func((Var*)GetLocalClosure(), playout->SlotIndex, GetRegAllowStackVarEnableOnly(playout->Value)); \ |
| break; \ |
| } |
| |
| #define PROCESS_SET_ELEM_LOCALSLOTNonVar(name, func) PROCESS_SET_ELEM_LOCALSLOTNonVar_COMMON(name, func,) |
| |
| #define PROCESS_SET_ELEM_PARAMSLOTNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementSlotI1, suffix); \ |
| func((Var*)GetParamClosure(), playout->SlotIndex, GetRegAllowStackVarEnableOnly(playout->Value)); \ |
| break; \ |
| } |
| |
| #define PROCESS_SET_ELEM_PARAMSLOTNonVar(name, func) PROCESS_SET_ELEM_PARAMSLOTNonVar_COMMON(name, func,); \ |
| |
| #define PROCESS_SET_ELEM_INNERSLOTNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementSlotI2, suffix); \ |
| func(InnerScopeFromIndex(playout->SlotIndex1), playout->SlotIndex2, GetRegAllowStackVarEnableOnly(playout->Value)); \ |
| break; \ |
| } |
| |
| #define PROCESS_SET_ELEM_INNERSLOTNonVar(name, func) PROCESS_SET_ELEM_INNERSLOTNonVar_COMMON(name, func,) |
| |
| #define PROCESS_SET_ELEM_ENVSLOTNonVar_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, ElementSlotI2, suffix); \ |
| func(LdEnv(), playout->SlotIndex1, playout->SlotIndex2, GetRegAllowStackVarEnableOnly(playout->Value)); \ |
| break; \ |
| } |
| |
| #define PROCESS_SET_ELEM_ENVSLOTNonVar(name, func) PROCESS_SET_ELEM_ENVSLOTNonVar_COMMON(name, func,) |
| |
| /*---------------------------------------------------------------------------------------------- */ |
| #define PROCESS_A3toA1Mem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg4, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1), GetReg(playout->R2), GetReg(playout->R3), GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_A3toA1Mem(name, func) PROCESS_A3toA1Mem_COMMON(name, func,) |
| |
| /*---------------------------------------------------------------------------------------------- */ |
| #define PROCESS_A2I1toA1Mem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg3B1, suffix); \ |
| SetReg(playout->R0, \ |
| func(GetReg(playout->R1), GetReg(playout->R2), playout->B3, GetScriptContext())); \ |
| break; \ |
| } |
| |
| #define PROCESS_A2I1toA1Mem(name, func) PROCESS_A2I1toA1Mem_COMMON(name, func,) |
| |
| /*---------------------------------------------------------------------------------------------- */ |
| #define PROCESS_A2I1toXXMem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg2B1, suffix); \ |
| func(GetReg(playout->R0), GetReg(playout->R1), playout->B2, scriptContext); \ |
| break; \ |
| } |
| |
| #define PROCESS_A2I1toXXMem(name, func) PROCESS_A2I1toXXMem_COMMON(name, func,) |
| |
| /*---------------------------------------------------------------------------------------------- */ |
| #define PROCESS_A3I1toXXMem_COMMON(name, func, suffix) \ |
| case OpCode::name: \ |
| { \ |
| PROCESS_READ_LAYOUT(name, Reg3B1, suffix); \ |
| func(GetReg(playout->R0), GetReg(playout->R1), GetReg(playout->R2), playout->B3, scriptContext); \ |
| break; \ |
| } |
| |
| #define PROCESS_A3I1toXXMem(name, func) PROCESS_A3I1toXXMem_COMMON(name, func,) |
| |
| #if ENABLE_PROFILE_INFO |
| #define PROCESS_IP_TARG_IMPL(name, func, layoutSize) \ |
| case OpCode::name: \ |
| { \ |
| Assert(!switchProfileMode); \ |
| ip = func<layoutSize, INTERPRETERPROFILE>(ip); \ |
| if(switchProfileMode) \ |
| { \ |
| m_reader.SetIP(ip); \ |
| return nullptr; \ |
| } \ |
| break; \ |
| } |
| #else |
| #define PROCESS_IP_TARG_IMPL(name, func, layoutSize) \ |
| case OpCode::name: \ |
| { \ |
| ip = func<layoutSize, INTERPRETERPROFILE>(ip); \ |
| break; \ |
| } |
| #endif |
| |
| #define PROCESS_IP_TARG_COMMON(name, func, suffix) PROCESS_IP_TARG##suffix(name, func) |
| |
| #define PROCESS_IP_TARG_Large(name, func) PROCESS_IP_TARG_IMPL(name, func, Js::LargeLayout) |
| #define PROCESS_IP_TARG_Medium(name, func) PROCESS_IP_TARG_IMPL(name, func, Js::MediumLayout) |
| #define PROCESS_IP_TARG_Small(name, func) PROCESS_IP_TARG_IMPL(name, func, Js::SmallLayout) |
| |
| #if ENABLE_TTD |
| #if ENABLE_TTD_DIAGNOSTICS_TRACING |
| #define SHOULD_DO_TTD_STACK_STMT_OP(CTX) ((CTX)->ShouldPerformRecordOrReplayAction()) |
| #else |
| #define SHOULD_DO_TTD_STACK_STMT_OP(CTX) ((CTX)->ShouldPerformRecordOrReplayDebuggerAction()) |
| #endif |
| #endif |
| |
| namespace Js |
| { |
| Var InterpreterStackFrame::InnerScopeFromRegSlot(RegSlot reg) const |
| { |
| return InnerScopeFromIndex(reg - m_functionBody->GetFirstInnerScopeRegister()); |
| } |
| |
| Var InterpreterStackFrame::InnerScopeFromIndex(uint32 index) const |
| { |
| if (index >= m_functionBody->GetInnerScopeCount()) |
| { |
| AssertMsg(false, "Illegal byte code: bad inner scope index"); |
| Js::Throw::FatalInternalError(); |
| } |
| Assert(this->innerScopeArray != nullptr); |
| return this->innerScopeArray[index]; |
| } |
| |
| void InterpreterStackFrame::SetInnerScopeFromIndex(uint32 index, Var scope) |
| { |
| if (index >= m_functionBody->GetInnerScopeCount()) |
| { |
| AssertMsg(false, "Illegal byte code: bad inner scope index"); |
| Js::Throw::FatalInternalError(); |
| } |
| Assert(this->innerScopeArray != nullptr); |
| this->innerScopeArray[index] = scope; |
| } |
| |
| const int k_stackFrameVarCount = (sizeof(InterpreterStackFrame) + sizeof(Var) - 1) / sizeof(Var); |
| InterpreterStackFrame::Setup::Setup(Js::ScriptFunction * function, Js::Arguments& args, bool bailedOut, bool inlinee) |
| : function(function), inParams(args.Values), inSlotsCount(args.Info.Count), executeFunction(function->GetFunctionBody()), callFlags(args.Info.Flags), bailedOutOfInlinee(inlinee), bailedOut(bailedOut) |
| { |
| SetupInternal(); |
| } |
| |
| InterpreterStackFrame::Setup::Setup(Js::ScriptFunction * function, Var * inParams, int inSlotsCount) |
| : function(function), inParams(inParams), inSlotsCount(inSlotsCount), executeFunction(function->GetFunctionBody()), callFlags(CallFlags_None), bailedOutOfInlinee(false), bailedOut(false) |
| { |
| SetupInternal(); |
| } |
| |
| void InterpreterStackFrame::Setup::SetupInternal() |
| { |
| if (this->function->GetHasInlineCaches() && Js::ScriptFunctionWithInlineCache::Is(this->function)) |
| { |
| this->inlineCaches = (void**)Js::ScriptFunctionWithInlineCache::FromVar(this->function)->GetInlineCaches(); |
| Assert(this->inlineCaches != nullptr); |
| } |
| else |
| { |
| this->inlineCaches = this->executeFunction->GetInlineCaches(); |
| } |
| this->inlineCacheCount = this->executeFunction->GetInlineCacheCount(); |
| |
| // |
| // Compute the amount of memory needed on the stack: |
| // - We compute this in 'Atoms' instead of 'bytes' to keep everything natural word aligned. |
| // |
| |
| this->localCount = this->executeFunction->GetLocalsCount(); |
| uint extraVarCount = 0; |
| #if ENABLE_PROFILE_INFO |
| if (Js::DynamicProfileInfo::EnableImplicitCallFlags(this->executeFunction)) |
| { |
| extraVarCount += (sizeof(ImplicitCallFlags) * this->executeFunction->GetLoopCount() + sizeof(Var) - 1) / sizeof(Var); |
| } |
| #endif |
| // If we bailed out, we will use the JIT frame's for..in enumerators |
| uint forInVarCount = bailedOut ? 0 : (this->executeFunction->GetForInLoopDepth() * (sizeof(Js::ForInObjectEnumerator) / sizeof(Var))); |
| this->varAllocCount = k_stackFrameVarCount + localCount + this->executeFunction->GetOutParamMaxDepth() + forInVarCount + |
| extraVarCount + this->executeFunction->GetInnerScopeCount(); |
| this->stackVarAllocCount = 0; |
| |
| if (this->executeFunction->DoStackNestedFunc() && this->executeFunction->GetNestedCount() != 0) |
| { |
| // Track stack funcs... |
| this->stackVarAllocCount += (sizeof(StackScriptFunction) * this->executeFunction->GetNestedCount()) / sizeof(Var); |
| if (!this->bailedOutOfInlinee) |
| { |
| // Frame display (if environment depth is statically known)... |
| if (this->executeFunction->DoStackFrameDisplay()) |
| { |
| uint16 envDepth = this->executeFunction->GetEnvDepth(); |
| Assert(envDepth != (uint16)-1); |
| this->stackVarAllocCount += sizeof(FrameDisplay) / sizeof(Var) + (envDepth + 1); |
| } |
| // ...and scope slots (if any) |
| if (this->executeFunction->DoStackScopeSlots()) |
| { |
| uint32 scopeSlots = this->executeFunction->scopeSlotArraySize; |
| Assert(scopeSlots != 0); |
| this->stackVarAllocCount += scopeSlots + Js::ScopeSlots::FirstSlotIndex; |
| } |
| } |
| } |
| } |
| |
| InterpreterStackFrame * |
| InterpreterStackFrame::Setup::InitializeAllocation(__in_ecount(varAllocCount) Var * allocation, __in_ecount(stackVarAllocCount) Var * stackAllocation |
| , bool initParams, bool profileParams, LoopHeader* loopHeaderArray, DWORD_PTR stackAddr |
| #if DBG |
| , Var invalidStackVar |
| #endif |
| ) |
| { |
| |
| // |
| // Initialize the new InterpreterStackFrame instance on the program stack. |
| // |
| |
| //This will fail if InterpreterStackFrame ever gets a non-empty ctor (you'll need to use |
| //placement_new(allocation, InterpreterStackFrame) instead, though that will cause problems |
| //if the placement_new is surrounded by a try/finally since this would mix C++/SEH exception |
| //handling. |
| |
| __analysis_assume(varAllocCount >= k_stackFrameVarCount + localCount); |
| InterpreterStackFrame* newInstance = (InterpreterStackFrame*)allocation; |
| |
| newInstance->scriptContext = this->executeFunction->GetScriptContext(); |
| newInstance->m_inSlotsCount = this->inSlotsCount; |
| newInstance->m_inParams = this->inParams; |
| newInstance->m_callFlags = this->callFlags; |
| newInstance->m_outParams = newInstance->m_localSlots + localCount; |
| newInstance->m_outSp = newInstance->m_outParams; |
| newInstance->m_outSpCached = nullptr; |
| newInstance->m_arguments = NULL; |
| newInstance->function = this->function; |
| newInstance->m_functionBody = this->executeFunction; |
| newInstance->inlineCaches = this->inlineCaches; |
| newInstance->inlineCacheCount = this->inlineCacheCount; |
| newInstance->currentLoopNum = LoopHeader::NoLoop; |
| newInstance->currentLoopCounter = 0; |
| newInstance->m_flags = InterpreterStackFrameFlags_None; |
| newInstance->closureInitDone = false; |
| newInstance->isParamScopeDone = false; |
| newInstance->shouldCacheSP = true; |
| #if ENABLE_PROFILE_INFO |
| newInstance->switchProfileMode = false; |
| newInstance->isAutoProfiling = false; |
| newInstance->switchProfileModeOnLoopEndNumber = 0u - 1; |
| #endif |
| newInstance->ehBailoutData = nullptr; |
| newInstance->nestedTryDepth = -1; |
| newInstance->nestedCatchDepth = -1; |
| newInstance->nestedFinallyDepth = -1; |
| newInstance->retOffset = 0; |
| newInstance->localFrameDisplay = nullptr; |
| newInstance->localClosure = nullptr; |
| newInstance->paramClosure = nullptr; |
| newInstance->innerScopeArray = nullptr; |
| newInstance->m_asmJsBuffer = nullptr; |
| #ifdef ENABLE_WASM |
| newInstance->m_wasmMemory = nullptr; |
| #endif |
| Var* outparamsEnd = newInstance->m_outParams + this->executeFunction->GetOutParamMaxDepth(); |
| #if DBG |
| newInstance->m_outParamsEnd = outparamsEnd; |
| #endif |
| |
| bool doInterruptProbe = newInstance->scriptContext->GetThreadContext()->DoInterruptProbe(this->executeFunction); |
| #if ENABLE_NATIVE_CODEGEN |
| bool doJITLoopBody = |
| !this->executeFunction->GetScriptContext()->GetConfig()->IsNoNative() && |
| !(this->executeFunction->GetHasTry() && (PHASE_OFF((Js::JITLoopBodyInTryCatchPhase), this->executeFunction))) && |
| !(this->executeFunction->GetHasFinally() && (PHASE_OFF((Js::JITLoopBodyInTryFinallyPhase), this->executeFunction))) && |
| (this->executeFunction->ForceJITLoopBody() || this->executeFunction->IsJitLoopBodyPhaseEnabled()) && |
| !this->executeFunction->IsInDebugMode(); |
| #endif |
| |
| // Pick a version of the LoopBodyStart OpCode handlers that is hardcoded to do loop body JIT and |
| // interrupt probes as needed. |
| if (doInterruptProbe) |
| { |
| #if ENABLE_NATIVE_CODEGEN |
| if (doJITLoopBody) |
| { |
| newInstance->opProfiledLoopBodyStart = &InterpreterStackFrame::ProfiledLoopBodyStart<true, true>; |
| newInstance->opLoopBodyStart = &InterpreterStackFrame::LoopBodyStart<true, true>; |
| } |
| else |
| #endif |
| { |
| #if ENABLE_PROFILE_INFO |
| newInstance->opProfiledLoopBodyStart = &InterpreterStackFrame::ProfiledLoopBodyStart<true, false>; |
| #endif |
| newInstance->opLoopBodyStart = &InterpreterStackFrame::LoopBodyStart<true, false>; |
| } |
| } |
| else |
| { |
| #if ENABLE_NATIVE_CODEGEN |
| if (doJITLoopBody) |
| { |
| newInstance->opProfiledLoopBodyStart = &InterpreterStackFrame::ProfiledLoopBodyStart<false, true>; |
| newInstance->opLoopBodyStart = &InterpreterStackFrame::LoopBodyStart<false, true>; |
| } |
| else |
| #endif |
| { |
| #if ENABLE_PROFILE_INFO |
| newInstance->opProfiledLoopBodyStart = &InterpreterStackFrame::ProfiledLoopBodyStart<false, false>; |
| #endif |
| newInstance->opLoopBodyStart = &InterpreterStackFrame::LoopBodyStart<false, false>; |
| } |
| } |
| |
| newInstance->loopHeaderArray = loopHeaderArray; |
| newInstance->m_stackAddress = stackAddr; |
| |
| #if ENABLE_PROFILE_INFO |
| // the savedLoopImplicitCallFlags is allocated at the end of the out param array |
| newInstance->savedLoopImplicitCallFlags = nullptr; |
| #endif |
| char * nextAllocBytes = (char *)(outparamsEnd); |
| |
| // If we bailed out, we will use the JIT frame's for..in enumerators |
| if (bailedOut || this->executeFunction->GetForInLoopDepth() == 0) |
| { |
| newInstance->forInObjectEnumerators = nullptr; |
| } |
| else |
| { |
| newInstance->forInObjectEnumerators = (ForInObjectEnumerator *)nextAllocBytes; |
| nextAllocBytes += sizeof(ForInObjectEnumerator) * this->executeFunction->GetForInLoopDepth(); |
| } |
| |
| if (this->executeFunction->GetInnerScopeCount()) |
| { |
| newInstance->innerScopeArray = (Var*)nextAllocBytes; |
| nextAllocBytes += this->executeFunction->GetInnerScopeCount() * sizeof(Var); |
| } |
| |
| if (this->executeFunction->DoStackNestedFunc() && this->executeFunction->GetNestedCount() != 0) |
| { |
| char * stackAllocBytes = (stackAllocation != nullptr) ? (char*)stackAllocation : nextAllocBytes; |
| |
| newInstance->InitializeStackFunctions((StackScriptFunction *)stackAllocBytes); |
| stackAllocBytes += sizeof(StackScriptFunction) * this->executeFunction->GetNestedCount(); |
| |
| if (!this->bailedOutOfInlinee) |
| { |
| if (this->executeFunction->DoStackFrameDisplay()) |
| { |
| uint16 envDepth = this->executeFunction->GetEnvDepth(); |
| Assert(envDepth != (uint16)-1); |
| newInstance->localFrameDisplay = (FrameDisplay*)stackAllocBytes; |
| newInstance->localFrameDisplay->SetLength(0); // Start with no scopes. It will get set in NewFrameDisplay |
| stackAllocBytes += sizeof(FrameDisplay) + (envDepth + 1) * sizeof(Var); |
| } |
| |
| if (this->executeFunction->DoStackScopeSlots()) |
| { |
| uint32 scopeSlots = this->executeFunction->scopeSlotArraySize; |
| Assert(scopeSlots != 0); |
| ScopeSlots((Field(Var)*)stackAllocBytes).SetCount(0); // Start with count as 0. It will get set in NewScopeSlots |
| newInstance->localClosure = stackAllocBytes; |
| stackAllocBytes += (scopeSlots + ScopeSlots::FirstSlotIndex) * sizeof(Var); |
| } |
| } |
| if (stackAllocation == nullptr) |
| { |
| nextAllocBytes = stackAllocBytes; |
| } |
| } |
| #if ENABLE_PROFILE_INFO |
| if (Js::DynamicProfileInfo::EnableImplicitCallFlags(this->executeFunction)) |
| { |
| /* |
| __analysis_assume(varAllocCount == (k_stackFrameVarCount + localCount + executeFunction->GetOutParamMaxDepth() |
| + ((sizeof(ImplicitCallFlags) * executeFunction->GetLoopCount() + sizeof(Var) - 1) / sizeof(Var)))); |
| */ |
| newInstance->savedLoopImplicitCallFlags = (ImplicitCallFlags *)nextAllocBytes; |
| for (uint i = 0; i < this->executeFunction->GetLoopCount(); i++) |
| { |
| #pragma prefast(suppress:26015, "Above analysis assume doesn't work") |
| newInstance->savedLoopImplicitCallFlags[i] = ImplicitCall_None; |
| } |
| |
| |
| } |
| #endif |
| #if DBG |
| if (CONFIG_ISENABLED(InitializeInterpreterSlotsWithInvalidStackVarFlag)) |
| { |
| // Fill the local slots with the invalid stack var so that we will crash deterministically if something goes wrong |
| for (uint i = 0; i < localCount; ++i) |
| { |
| newInstance->m_localSlots[i] = invalidStackVar; |
| } |
| } |
| else |
| { |
| memset(newInstance->m_localSlots, 0, sizeof(Js::Var) * localCount); |
| } |
| #else |
| if (newInstance->m_functionBody->IsInDebugMode()) |
| { |
| // In the debug mode zero out the local slot, so this could prevent locals being uninitialized in the case of setNextStatement. |
| memset(newInstance->m_localSlots, 0, sizeof(Js::Var) * localCount); |
| } |
| else |
| { |
| Js::RegSlot varCount = function->GetFunctionBody()->GetVarCount(); |
| if (varCount) |
| { |
| // Zero out the non-constant var slots. |
| Js::RegSlot constantCount = function->GetFunctionBody()->GetConstantCount(); |
| memset(newInstance->m_localSlots + constantCount, 0, varCount * sizeof(Js::Var)); |
| } |
| // Zero out the return slot. This is not a user local, so the byte code will not initialize |
| // it to "undefined". And it's not an expression temp, so, for instance, a jitted loop body may expect |
| // it to be valid on entry to the loop, where "valid" means either a var or null. |
| newInstance->SetNonVarReg(0, NULL); |
| } |
| #endif |
| // Wasm doesn't use const table |
| if (!executeFunction->IsWasmFunction()) |
| { |
| // Initialize the low end of the local slots from the constant table. |
| // Skip the slot for the return value register. |
| this->executeFunction->InitConstantSlots(&newInstance->m_localSlots[FunctionBody::FirstRegSlot]); |
| } |
| // Set local FD/SS pointers to null until after we've successfully probed the stack in the process loop. |
| // That way we avoid trying to box these structures before they've been initialized in the byte code. |
| if (this->executeFunction->DoStackFrameDisplay()) |
| { |
| newInstance->SetNonVarReg(executeFunction->GetLocalFrameDisplayRegister(), nullptr); |
| } |
| if (this->executeFunction->DoStackScopeSlots()) |
| { |
| Assert(!executeFunction->HasScopeObject()); |
| newInstance->SetNonVarReg(executeFunction->GetLocalClosureRegister(), nullptr); |
| } |
| |
| Var *prestDest = &newInstance->m_localSlots[this->executeFunction->GetConstantCount()]; |
| if (initParams) |
| { |
| #if ENABLE_PROFILE_INFO |
| Assert(!this->executeFunction->NeedEnsureDynamicProfileInfo()); |
| #endif |
| if (profileParams) |
| { |
| #if ENABLE_PROFILE_INFO |
| Assert(this->executeFunction->HasExecutionDynamicProfileInfo()); |
| #endif |
| FunctionBody* functionBody = this->executeFunction; |
| InitializeParams(newInstance, [functionBody](Var param, ArgSlot index) |
| { |
| #if ENABLE_PROFILE_INFO |
| functionBody->GetDynamicProfileInfo()->RecordParameterInfo(functionBody, index - 1, param); |
| #endif |
| }, &prestDest); |
| } |
| else |
| { |
| InitializeParams(newInstance, [](Var param, ArgSlot index) {}, &prestDest); |
| } |
| } |
| |
| if (this->executeFunction->GetHasRestParameter()) |
| { |
| InitializeRestParam(newInstance, prestDest); |
| } |
| |
| Js::RegSlot envReg = executeFunction->GetEnvRegister(); |
| if (envReg != Js::Constants::NoRegister && envReg < executeFunction->GetConstantCount()) |
| { |
| Assert(this->executeFunction->GetThisRegisterForEventHandler() == Constants::NoRegister); |
| // The correct FD (possibly distinct from the one on the function) is passed in the constant table. |
| this->function->SetEnvironment((Js::FrameDisplay*)newInstance->GetNonVarReg(envReg)); |
| } |
| |
| return newInstance; |
| } |
| |
| template <class Fn> |
| void InterpreterStackFrame::Setup::InitializeParams(InterpreterStackFrame * newInstance, Fn callback, Var **pprestDest) |
| { |
| ArgSlot requiredInParamCount = executeFunction->GetInParamsCount(); |
| Assert(requiredInParamCount > 1); |
| if (this->inSlotsCount >= requiredInParamCount) |
| { |
| Var * pArg = &newInstance->m_localSlots[executeFunction->GetConstantCount()]; |
| Var * paGivenSrc = this->inParams + 1; |
| ArgSlot paramIndex = 1; |
| do |
| { |
| Var src = *paGivenSrc++; |
| callback(src, paramIndex); |
| *pArg++ = src; |
| paramIndex++; |
| } while (paramIndex < requiredInParamCount); |
| *pprestDest = pArg; |
| } |
| else |
| { |
| InitializeParamsAndUndef(newInstance, callback, pprestDest); |
| } |
| } |
| |
| template <class Fn> |
| void InterpreterStackFrame::Setup::InitializeParamsAndUndef(InterpreterStackFrame * newInstance, Fn callback, Var **pprestDest) |
| { |
| Var * pArg = &newInstance->m_localSlots[executeFunction->GetConstantCount()]; |
| Var * paGivenSrc = this->inParams + 1; |
| ArgSlot requiredInParamCount = executeFunction->GetInParamsCount(); |
| ArgSlot paramIndex = 1; |
| while (paramIndex < this->inSlotsCount) |
| { |
| Var src = *paGivenSrc++; |
| callback(src, paramIndex); |
| *pArg++ = src; |
| paramIndex++; |
| } |
| Var varUndef = executeFunction->GetScriptContext()->GetLibrary()->GetUndefined(); |
| do |
| { |
| callback(varUndef, paramIndex); |
| *pArg++ = varUndef; |
| paramIndex++; |
| } while (paramIndex < requiredInParamCount); |
| |
| *pprestDest = pArg; |
| } |
| |
| void InterpreterStackFrame::Setup::InitializeRestParam(InterpreterStackFrame * newInstance, Var *dest) |
| { |
| Var *src = this->inParams + executeFunction->GetInParamsCount(); |
| |
| if (this->inSlotsCount > executeFunction->GetInParamsCount()) |
| { |
| // Create the rest array and copy the args directly into the contiguous head segment. |
| int excess = this->inSlotsCount - executeFunction->GetInParamsCount(); |
| *dest = JavascriptArray::OP_NewScArray(excess, executeFunction->GetScriptContext()); |
| JavascriptArray *array = static_cast<JavascriptArray *>(*dest); |
| Field(Var)* elements = SparseArraySegment<Var>::From(array->GetHead())->elements; |
| CopyArray(elements, excess, src, excess); |
| } |
| else |
| { |
| // Rest is an empty array when there are no excess parameters. |
| *dest = JavascriptArray::OP_NewScArray(0, executeFunction->GetScriptContext()); |
| } |
| } |
| |
| FrameDisplay * InterpreterStackFrame::GetEnvForEvalCode() |
| { |
| FrameDisplay *pScope; |
| if (m_functionBody->GetIsStrictMode() && m_functionBody->GetIsGlobalFunc()) |
| { |
| pScope = this->GetLocalFrameDisplay(); |
| } |
| else |
| { |
| pScope = (FrameDisplay*)this->LdEnv(); |
| } |
| |
| return pScope; |
| } |
| |
| void InterpreterStackFrame::InitializeClosures() |
| { |
| FunctionBody *executeFunction = this->function->GetFunctionBody(); |
| Var environment; |
| |
| if (executeFunction->IsParamAndBodyScopeMerged()) |
| { |
| this->SetIsParamScopeDone(true); |
| } |
| |
| RegSlot thisRegForEventHandler = executeFunction->GetThisRegisterForEventHandler(); |
| if (thisRegForEventHandler != Constants::NoRegister) |
| { |
| Var varThis = OP_ArgIn0(); |
| SetReg(thisRegForEventHandler, varThis); |
| environment = JavascriptOperators::OP_LdHandlerScope(varThis, GetScriptContext()); |
| this->SetEnv((FrameDisplay*)environment); |
| } |
| else if (this->paramClosure != nullptr) |
| { |
| // When paramClosure is non-null we are calling this method to initialize the closure for body scope. |
| // In this case we have to use the param scope's closure as the parent for the body scope's frame display. |
| Assert(!executeFunction->IsParamAndBodyScopeMerged()); |
| environment = this->GetLocalFrameDisplay(); |
| } |
| else |
| { |
| environment = this->LdEnv(); |
| } |
| |
| Var funcExprScope = nullptr; |
| Js::RegSlot funcExprScopeReg = executeFunction->GetFuncExprScopeRegister(); |
| if (funcExprScopeReg != Constants::NoRegister && this->paramClosure == nullptr) |
| { |
| // t0 = NewPseudoScope |
| // t1 = LdFrameDisplay t0 env |
| |
| funcExprScope = JavascriptOperators::OP_NewPseudoScope(GetScriptContext()); |
| SetReg(funcExprScopeReg, funcExprScope); |
| } |
| |
| RegSlot closureReg = executeFunction->GetLocalClosureRegister(); |
| if (closureReg != Js::Constants::NoRegister) |
| { |
| Assert(closureReg >= executeFunction->GetConstantCount()); |
| if (executeFunction->HasScopeObject()) |
| { |
| this->NewScopeObject(); |
| } |
| else |
| { |
| this->NewScopeSlots(); |
| } |
| this->SetNonVarReg(closureReg, nullptr); |
| } |
| |
| Js::RegSlot frameDisplayReg = executeFunction->GetLocalFrameDisplayRegister(); |
| if (frameDisplayReg != Js::Constants::NoRegister) |
| { |
| Assert(frameDisplayReg >= executeFunction->GetConstantCount()); |
| |
| if (funcExprScope != nullptr) |
| { |
| environment = OP_LdFrameDisplay(funcExprScope, environment, GetScriptContext()); |
| } |
| |
| if (closureReg != Js::Constants::NoRegister) |
| { |
| void *argHead = this->GetLocalClosure(); |
| environment = this->NewFrameDisplay(argHead, environment); |
| } |
| |
| this->SetLocalFrameDisplay((Js::FrameDisplay*)environment); |
| this->SetNonVarReg(frameDisplayReg, nullptr); |
| } |
| |
| this->closureInitDone = true; |
| } |
| |
| #ifdef _M_IX86 |
| #ifdef ASMJS_PLAT |
| int InterpreterStackFrame::GetAsmJsArgSize(AsmJsCallStackLayout* stack) |
| { |
| JavascriptFunction * func = stack->functionObject; |
| AsmJsFunctionInfo* asmInfo = func->GetFunctionBody()->GetAsmJsFunctionInfo(); |
| uint argSize = (uint)(asmInfo->GetArgByteSize()); |
| argSize = ::Math::Align<int32>(argSize, 8); |
| // 2 * sizeof(Var) is for functionObject, and another push that DynamicInterpreterThunk does |
| return argSize + 2 * sizeof(Var); |
| } |
| |
| int InterpreterStackFrame::GetDynamicRetType(AsmJsCallStackLayout* stack) |
| { |
| return GetRetType(stack->functionObject); |
| } |
| |
| int InterpreterStackFrame::GetRetType(JavascriptFunction* func) |
| { |
| AsmJsFunctionInfo* asmInfo = func->GetFunctionBody()->GetAsmJsFunctionInfo(); |
| return asmInfo->GetReturnType().which(); |
| } |
| |
| #ifdef ASMJS_PLAT |
| /* |
| AsmInterpreterThunk |
| ------------------- |
| This is the entrypoint for all Asm Interpreter calls (external and internal) |
| TODO - Make this a dynamic Interpreter thunk to support ETW |
| Functionality: |
| 1) Prolog |
| 2) call AsmInterpreter passing the function object |
| 3) Get The return type |
| 4) Check for Double or Float return type |
| 5) If true then retrieve the value stored at a constant offset from the ScriptContext |
| 6) Get Argument Size for callee cleanup |
| 7) EpiLog |
| a) Retrieve the frame pointer |
| b) Store the return address in register (edx) |
| c) Clean the arguments based on the arguments size |
| d) push the return address back into the stack |
| */ |
| __declspec(naked) |
| void InterpreterStackFrame::InterpreterAsmThunk(AsmJsCallStackLayout* layout) |
| { |
| __asm |
| { |
| //Prologue |
| push ebp; |
| mov ebp, esp; |
| |
| // Allocate space to put the return value |
| sub esp, 16; |
| and esp, -16; |
| |
| push esp; |
| push layout; |
| call InterpreterStackFrame::AsmJsInterpreter; |
| |
| // we need to move stack around in order to do callee cleanup |
| // unfortunately, we don't really have enough registers to do this cleanly |
| // |
| // we are rearranging the stack from this: |
| // 0x14 caller push scriptArg1 |
| // 0x10 caller push functionObject |
| // 0x0C DynamicInterpreterThunk return address |
| // 0x08 DynamicInterpreterThunk push ebp |
| // 0x04 DynamicInterpreterThunk push functionObject |
| // 0x00 InterpreterAsmThunk return address <- stack pointer |
| // to this: |
| // 0x14 DynamicInterpreterThunk return address |
| // 0x10 DynamicInterpreterThunk push ebp |
| // 0x0C InterpreterAsmThunk return address <- stack pointer |
| // Read return value into possible registers |
| movups xmm0, [esp]; |
| mov edx, [esp + 4]; |
| mov eax, [esp]; |
| |
| mov ecx, layout; |
| // Epilog, callee cleanup |
| mov esp, ebp; |
| pop ebp; |
| |
| push eax; // save eax |
| push edx; // save edx |
| // we have to do +0x8 on all stack addresses because we saved 2 registers |
| push ecx; // ebp has been restored, we can't use parameters anymore |
| call InterpreterStackFrame::GetAsmJsArgSize; |
| mov ecx, eax; |
| |
| lea eax, [esp + ecx * 1 + 0x8 + 0x8]; // eax will be our stack destination |
| mov edx, [esp + 0xC + 0x8]; |
| mov[eax], edx; // move the dynamic interpreter thunk return location |
| mov edx, [esp + 0x8 + 0x8]; |
| mov[eax - 0x4], edx; // move the dynamic interpreter thunk "push ebp" location |
| // skip "push functionObject" |
| mov edx, [esp + 0x0 + 0x8]; |
| mov[eax - 0x8], edx; // move the return location |
| |
| pop edx; // restore possible int64 return value |
| pop eax; // restore return value |
| add esp, ecx; // cleanup arguments |
| |
| ret; |
| } |
| } |
| #endif |
| #endif |
| #endif |
| |
| |
| #if DYNAMIC_INTERPRETER_THUNK |
| #ifdef _M_IX86 |
| __declspec(naked) |
| Var InterpreterStackFrame::AsmJsDelayDynamicInterpreterThunk(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| __asm |
| { |
| push ebp; |
| mov ebp, esp; |
| push[esp + 8]; // push function object |
| call WasmLibrary::EnsureWasmEntrypoint; |
| test eax, eax; |
| jne skipThunk; |
| |
| push[esp + 8]; // push function object |
| call InterpreterStackFrame::EnsureDynamicInterpreterThunk; |
| skipThunk: |
| #ifdef _CONTROL_FLOW_GUARD |
| // verify that the call target is valid |
| mov ecx, eax; |
| call[__guard_check_icall_fptr]; |
| mov eax, ecx; |
| #endif |
| |
| pop ebp; |
| |
| jmp eax; |
| } |
| } |
| |
| __declspec(naked) |
| Var InterpreterStackFrame::DelayDynamicInterpreterThunk(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| __asm |
| { |
| push ebp; |
| mov ebp, esp; |
| push[esp + 8]; // push function object |
| call InterpreterStackFrame::EnsureDynamicInterpreterThunk; |
| |
| #ifdef _CONTROL_FLOW_GUARD |
| // verify that the call target is valid |
| mov ecx, eax; |
| call[__guard_check_icall_fptr]; |
| mov eax, ecx; |
| #endif |
| |
| pop ebp; |
| |
| jmp eax; |
| } |
| } |
| #elif !defined(_M_AMD64) |
| Var InterpreterStackFrame::AsmJsDelayDynamicInterpreterThunk(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| // Asm.js only supported on x64 and x86 |
| AssertOrFailFast(UNREACHED); |
| return nullptr; |
| } |
| #endif |
| #endif |
| |
| #if ENABLE_PROFILE_INFO |
| JavascriptMethod InterpreterStackFrame::EnsureDynamicInterpreterThunk(Js::ScriptFunction * function) |
| { |
| #if DYNAMIC_INTERPRETER_THUNK |
| Assert(function); |
| Js::FunctionBody *functionBody = function->GetFunctionBody(); |
| JavascriptMethod entrypoint = functionBody->EnsureDynamicInterpreterThunk(function->GetFunctionEntryPointInfo()); |
| Assert(!IsDelayDynamicInterpreterThunk(functionBody->GetDirectEntryPoint(function->GetEntryPointInfo()))); |
| if (function->GetEntryPoint() == InterpreterStackFrame::DelayDynamicInterpreterThunk) |
| { |
| // If we are not profiling, or the function object is not cross site, this is the direct entry point. |
| // Change the entry point on the object |
| Assert(functionBody->GetDirectEntryPoint(function->GetEntryPointInfo()) == entrypoint); |
| function->ChangeEntryPoint(function->GetEntryPointInfo(), entrypoint); |
| } |
| // Return the original entry point to be called |
| return entrypoint; |
| #else |
| return function->GetEntryPoint(); |
| #endif |
| } |
| #endif |
| |
| bool InterpreterStackFrame::IsDelayDynamicInterpreterThunk(JavascriptMethod entryPoint) |
| { |
| return false |
| #if DYNAMIC_INTERPRETER_THUNK |
| || entryPoint == InterpreterStackFrame::DelayDynamicInterpreterThunk |
| #ifdef ASMJS_PLAT |
| || entryPoint == InterpreterStackFrame::AsmJsDelayDynamicInterpreterThunk |
| #endif |
| #endif |
| ; |
| } |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| THREAD_LOCAL int InterpreterThunkStackCountTracker::s_count = 0; |
| #endif |
| |
| #if DYNAMIC_INTERPRETER_THUNK |
| #pragma optimize("", off) |
| #ifdef ASMJS_PLAT |
| void InterpreterStackFrame::StaticInterpreterAsmThunk(RecyclableObject* function, ...) |
| { |
| InterpreterAsmThunk((AsmJsCallStackLayout*)&function); |
| } |
| #endif |
| |
| #if !defined(_M_ARM64) |
| Var InterpreterStackFrame::StaticInterpreterThunk(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| return InterpreterThunk((JavascriptCallStackLayout*)&function); |
| } |
| #else |
| // Language\arm64\arm64_Thunks.asm |
| #endif |
| #pragma optimize("", on) |
| |
| Var InterpreterStackFrame::InterpreterThunk(JavascriptCallStackLayout* layout) |
| { |
| Js::ScriptFunction * function = Js::ScriptFunction::UnsafeFromVar(layout->functionObject); |
| Js::ArgumentReader args(&layout->callInfo, layout->args); |
| void* localReturnAddress = _ReturnAddress(); |
| void* localAddressOfReturnAddress = _AddressOfReturnAddress(); |
| return InterpreterHelper(function, args, localReturnAddress, localAddressOfReturnAddress); |
| } |
| #else |
| |
| #pragma optimize("", off) |
| Var InterpreterStackFrame::InterpreterThunk(RecyclableObject* function, CallInfo callInfo, ...) |
| { |
| ARGUMENTS(args, callInfo); |
| void* localReturnAddress = _ReturnAddress(); |
| void* localAddressOfReturnAddress = _AddressOfReturnAddress(); |
| Assert(ScriptFunction::Is(function)); |
| return InterpreterHelper(ScriptFunction::FromVar(function), args, localReturnAddress, localAddressOfReturnAddress); |
| } |
| #pragma optimize("", on) |
| #endif |
| |
| const bool InterpreterStackFrame::ShouldDoProfile(FunctionBody* executeFunction) |
| { |
| #if ENABLE_PROFILE_INFO |
| const bool doProfile = executeFunction->GetInterpreterExecutionMode(false) == ExecutionMode::ProfilingInterpreter || |
| (executeFunction->IsInDebugMode() && DynamicProfileInfo::IsEnabled(executeFunction)); |
| return doProfile; |
| #else |
| return false; |
| #endif |
| } |
| |
| InterpreterStackFrame* InterpreterStackFrame::CreateInterpreterStackFrameForGenerator(ScriptFunction* function, FunctionBody* executeFunction, JavascriptGenerator* generator, bool doProfile) |
| { |
| // |
| // Allocate a new InterpreterStackFrame instance on the recycler heap. |
| // It will live with the JavascriptGenerator object. |
| // |
| ScriptContext* functionScriptContext = function->GetScriptContext(); |
| Arguments generatorArgs = generator->GetArguments(); |
| InterpreterStackFrame::Setup setup(function, generatorArgs); |
| Assert(setup.GetStackAllocationVarCount() == 0); |
| size_t varAllocCount = setup.GetAllocationVarCount(); |
| size_t varSizeInBytes = varAllocCount * sizeof(Var); |
| DWORD_PTR stackAddr = reinterpret_cast<DWORD_PTR>(&generator); // use any stack address from this frame to ensure correct debugging functionality |
| LoopHeader* loopHeaderArray = executeFunction->GetHasAllocatedLoopHeaders() ? executeFunction->GetLoopHeaderArrayPtr() : nullptr; |
| |
| Var* allocation = RecyclerNewPlus(functionScriptContext->GetRecycler(), varSizeInBytes, Var); |
| AnalysisAssert(allocation); |
| InterpreterStackFrame* newInstance; |
| #if DBG |
| // Allocate invalidVar on GC instead of stack since this InterpreterStackFrame will out live the current real frame |
| Js::RecyclableObject* invalidVar = (Js::RecyclableObject*)RecyclerNewPlusLeaf(functionScriptContext->GetRecycler(), sizeof(Js::RecyclableObject), Var); |
| AnalysisAssert(invalidVar); |
| memset(reinterpret_cast<void*>(invalidVar), 0xFE, sizeof(Js::RecyclableObject)); |
| #endif |
| |
| newInstance = setup.InitializeAllocation(allocation, nullptr, executeFunction->GetHasImplicitArgIns(), doProfile, loopHeaderArray, stackAddr |
| #if DBG |
| , invalidVar |
| #endif |
| ); |
| |
| newInstance->m_reader.Create(executeFunction); |
| |
| generator->SetFrame(newInstance, varSizeInBytes); |
| return newInstance; |
| } |
| |
| Var InterpreterStackFrame::InterpreterHelper(ScriptFunction* function, ArgumentReader args, void* returnAddress, void* addressOfReturnAddress, AsmJsReturnStruct* asmJsReturn) |
| { |
| const bool isAsmJs = asmJsReturn != nullptr; |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| // Support for simulating partially initialized interpreter stack frame. |
| InterpreterThunkStackCountTracker tracker; |
| |
| if (CONFIG_ISENABLED(InjectPartiallyInitializedInterpreterFrameErrorFlag) && |
| CONFIG_FLAG(InjectPartiallyInitializedInterpreterFrameError) == InterpreterThunkStackCountTracker::GetCount()) |
| { |
| switch (CONFIG_FLAG(InjectPartiallyInitializedInterpreterFrameErrorType)) |
| { |
| case 0: |
| DebugBreak(); |
| break; |
| case 1: |
| Js::JavascriptError::MapAndThrowError(function->GetScriptContext(), VBSERR_InternalError); |
| break; |
| default: |
| DebugBreak(); |
| } |
| } |
| #endif |
| ScriptContext* functionScriptContext = function->GetScriptContext(); |
| ThreadContext * threadContext = functionScriptContext->GetThreadContext(); |
| Assert(!threadContext->IsDisableImplicitException()); |
| functionScriptContext->VerifyAlive(!function->IsExternal()); |
| Assert(threadContext->IsScriptActive()); |
| Assert(threadContext->IsInScript()); |
| |
| FunctionBody* executeFunction = JavascriptFunction::UnsafeFromVar(function)->GetFunctionBody(); |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| if (!isAsmJs && executeFunction->IsInDebugMode() != functionScriptContext->IsScriptContextInDebugMode()) // debug mode mismatch |
| { |
| if (executeFunction->GetUtf8SourceInfo()->GetIsLibraryCode()) |
| { |
| Assert(!executeFunction->IsInDebugMode()); // Library script byteCode is never in debug mode |
| } |
| else |
| { |
| Throw::FatalInternalError(); |
| } |
| } |
| #endif |
| |
| if (executeFunction->GetInterpretedCount() == 0) |
| { |
| executeFunction->TraceInterpreterExecutionMode(); |
| } |
| |
| |
| class AutoRestore |
| { |
| private: |
| ThreadContext * const threadContext; |
| const uint8 savedLoopDepth; |
| |
| public: |
| AutoRestore(ThreadContext *const threadContext, FunctionBody *const executeFunction) |
| : threadContext(threadContext), |
| savedLoopDepth(threadContext->LoopDepth()) |
| { |
| if (savedLoopDepth != 0 && !executeFunction->GetIsAsmJsFunction()) |
| { |
| executeFunction->SetWasCalledFromLoop(); |
| } |
| } |
| |
| ~AutoRestore() |
| { |
| threadContext->SetLoopDepth(savedLoopDepth); |
| } |
| } autoRestore(threadContext, executeFunction); |
| |
| #if ENABLE_PROFILE_INFO |
| DynamicProfileInfo * dynamicProfileInfo = nullptr; |
| const bool doProfile = ShouldDoProfile(executeFunction); |
| if (doProfile) |
| { |
| #if !DYNAMIC_INTERPRETER_THUNK |
| executeFunction->EnsureDynamicProfileInfo(); |
| #endif |
| dynamicProfileInfo = executeFunction->GetDynamicProfileInfo(); |
| threadContext->ClearImplicitCallFlags(); |
| } |
| #else |
| const bool doProfile = false; |
| #endif |
| |
| executeFunction->IncreaseInterpretedCount(); |
| #ifdef BGJIT_STATS |
| functionScriptContext->interpretedCount++; |
| functionScriptContext->maxFuncInterpret = max(functionScriptContext->maxFuncInterpret, executeFunction->GetInterpretedCount()); |
| #endif |
| |
| AssertMsg(!executeFunction->IsDeferredParseFunction(), |
| "Non-intrinsic functions must provide byte-code to execute"); |
| |
| executeFunction->BeginExecution(); |
| |
| bool fReleaseAlloc = false; |
| InterpreterStackFrame* newInstance = nullptr; |
| |
| if (!isAsmJs && executeFunction->IsCoroutine()) |
| { |
| // If the FunctionBody is a generator then this call is being made by one of the three |
| // generator resuming methods: next(), throw(), or return(). They all pass the generator |
| // object as the first of two arguments. The real user arguments are obtained from the |
| // generator object. The second argument is the ResumeYieldData which is only needed |
| // when resuming a generator and so it only used here if a frame already exists on the |
| // generator object. |
| AssertOrFailFastMsg(args.Info.Count == 2 && ((args.Info.Flags & CallFlags_ExtraArg) == CallFlags_None), "Generator ScriptFunctions should only be invoked by generator APIs with the pair of arguments they pass in -- the generator object and a ResumeYieldData pointer"); |
| |
| JavascriptGenerator* generator = JavascriptGenerator::FromVar(args[0]); |
| newInstance = generator->GetFrame(); |
| |
| if (newInstance != nullptr) |
| { |
| ResumeYieldData* resumeYieldData = static_cast<ResumeYieldData*>(args[1]); |
| newInstance->SetNonVarReg(executeFunction->GetYieldRegister(), resumeYieldData); |
| |
| // The debugger relies on comparing stack addresses of frames to decide when a step_out is complete so |
| // give the InterpreterStackFrame a legit enough stack address to make this comparison work. |
| newInstance->m_stackAddress = reinterpret_cast<DWORD_PTR>(&generator); |
| } |
| else |
| { |
| newInstance = CreateInterpreterStackFrameForGenerator(function, executeFunction, generator, doProfile); |
| } |
| } |
| else |
| { |
| InterpreterStackFrame::Setup setup(function, args); |
| size_t varAllocCount = setup.GetAllocationVarCount(); |
| size_t stackVarAllocCount = setup.GetStackAllocationVarCount(); |
| size_t varSizeInBytes; |
| |
| // |
| // Allocate a new InterpreterStackFrame instance on the interpreter's virtual stack. |
| // |
| DWORD_PTR stackAddr; |
| |
| Var* allocation; |
| Var* stackAllocation = nullptr; |
| |
| // If the locals area exceeds a certain limit, allocate it from a private arena rather than |
| // this frame. The current limit is based on an old assert on the number of locals we would allow here. |
| if ((varAllocCount + stackVarAllocCount) > InterpreterStackFrame::LocalsThreshold) |
| { |
| ArenaAllocator *tmpAlloc = nullptr; |
| fReleaseAlloc = functionScriptContext->EnsureInterpreterArena(&tmpAlloc); |
| varSizeInBytes = varAllocCount * sizeof(Var); |
| allocation = (Var*)tmpAlloc->Alloc(varSizeInBytes); |
| stackAddr = reinterpret_cast<DWORD_PTR>(&allocation); // use a stack address so the debugger stepping logic works (step-out, for example, compares stack depths to determine when to complete the step) |
| if (stackVarAllocCount != 0) |
| { |
| size_t stackVarSizeInBytes = stackVarAllocCount * sizeof(Var); |
| PROBE_STACK_PARTIAL_INITIALIZED_INTERPRETER_FRAME(functionScriptContext, Js::Constants::MinStackInterpreter + stackVarSizeInBytes); |
| stackAllocation = (Var*)_alloca(stackVarSizeInBytes); |
| } |
| } |
| else |
| { |
| varSizeInBytes = (varAllocCount + stackVarAllocCount) * sizeof(Var); |
| PROBE_STACK_PARTIAL_INITIALIZED_INTERPRETER_FRAME(functionScriptContext, Js::Constants::MinStackInterpreter + varSizeInBytes); |
| allocation = (Var*)_alloca(varSizeInBytes); |
| #if DBG |
| memset(allocation, 0xFE, varSizeInBytes); |
| #endif |
| stackAddr = reinterpret_cast<DWORD_PTR>(allocation); |
| } |
| |
| /* |
| * If the function has any loop headers, we allocate an array for the loop headers wrappers, and |
| * reference the wrappers in the array. We then push the pointer to the array onto the stack itself. |
| * We do this so that while the function is being interpreted, we don't want the jitted loop |
| * bodies to be collected, even if the loop body isn't being executed. The loop body will |
| * get collected when the function has been JITted, and when the function exits the interpreter. |
| * The array contains nulls if the loop body isn't jitted (or hasn't been jitted yet) but |
| * it's cheaper to just copy them all into the recycler array rather than just the ones that |
| * have been jitted. |
| */ |
| LoopHeader* loopHeaderArray = nullptr; |
| |
| if (executeFunction->GetHasAllocatedLoopHeaders()) |
| { |
| // Loop header array is recycler allocated, so we push it on the stack |
| // When we scan the stack, we'll recognize it as a recycler allocated |
| // object, and mark it's contents and keep the individual loop header |
| // wrappers alive |
| loopHeaderArray = executeFunction->GetLoopHeaderArrayPtr(); |
| } |
| |
| #if DBG |
| Js::RecyclableObject * invalidStackVar = (Js::RecyclableObject*)_alloca(sizeof(Js::RecyclableObject)); |
| memset(reinterpret_cast<void*>(invalidStackVar), 0xFE, sizeof(Js::RecyclableObject)); |
| #endif |
| |
| newInstance = setup.InitializeAllocation(allocation, stackAllocation, executeFunction->GetHasImplicitArgIns() && !isAsmJs, doProfile, loopHeaderArray, stackAddr |
| #if DBG |
| , invalidStackVar |
| #endif |
| ); |
| |
| newInstance->m_reader.Create(executeFunction); |
| } |
| // |
| // Execute the function's byte-code, returning the return-value: |
| // - Mark that the function is current executing and may not be modified. |
| // |
| |
| #if ENABLE_TTD |
| TTD::TTDExceptionFramePopper exceptionFramePopper; |
| if (SHOULD_DO_TTD_STACK_STMT_OP(functionScriptContext)) |
| { |
| bool isInFinally = newInstance->TestFlags(Js::InterpreterStackFrameFlags_WithinFinallyBlock); |
| |
| threadContext->TTDExecutionInfo->PushCallEvent(function, args.Info.Count, args.Values, isInFinally); |
| exceptionFramePopper.PushInfo(threadContext->TTDExecutionInfo, function); |
| } |
| #endif |
| |
| Var aReturn = nullptr; |
| |
| { |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (!isAsmJs && executeFunction->IsInDebugMode()) |
| { |
| #if DYNAMIC_INTERPRETER_THUNK |
| PushPopFrameHelper pushPopFrameHelper(newInstance, returnAddress, addressOfReturnAddress); |
| aReturn = newInstance->DebugProcess(); |
| #else |
| aReturn = newInstance->DebugProcessThunk(_ReturnAddress(), _AddressOfReturnAddress()); |
| #endif |
| } |
| else |
| #endif |
| { |
| #if DYNAMIC_INTERPRETER_THUNK |
| PushPopFrameHelper pushPopFrameHelper(newInstance, returnAddress, addressOfReturnAddress); |
| aReturn = newInstance->Process(); |
| #else |
| aReturn = newInstance->ProcessThunk(_ReturnAddress(), _AddressOfReturnAddress()); |
| #endif |
| } |
| } |
| |
| executeFunction->EndExecution(); |
| |
| #if ENABLE_TTD |
| if (SHOULD_DO_TTD_STACK_STMT_OP(functionScriptContext)) |
| { |
| exceptionFramePopper.PopInfo(); |
| threadContext->TTDExecutionInfo->PopCallEvent(function, aReturn); |
| } |
| #endif |
| |
| #ifdef ASMJS_PLAT |
| if (isAsmJs) |
| { |
| asmJsReturn->i = newInstance->GetRegRawInt(0); |
| asmJsReturn->l = newInstance->GetRegRawInt64(0); |
| asmJsReturn->d = newInstance->GetRegRawDouble(0); |
| asmJsReturn->f = newInstance->GetRegRawFloat(0); |
| asmJsReturn->simd = newInstance->GetRegRawSimd(0); |
| } |
| #endif |
| |
| if (fReleaseAlloc) |
| { |
| functionScriptContext->ReleaseInterpreterArena(); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| if (doProfile) |
| { |
| dynamicProfileInfo->RecordImplicitCallFlags(threadContext->GetImplicitCallFlags()); |
| } |
| #endif |
| |
| return aReturn; |
| } |
| |
| #ifdef ASMJS_PLAT |
| |
| #if _M_IX86 |
| void InterpreterStackFrame::AsmJsInterpreter(AsmJsCallStackLayout* stack, byte* retDst) |
| { |
| ScriptFunction * function = (ScriptFunction*)stack->functionObject; |
| Var* paramsAddr = stack->args; |
| int flags = CallFlags_Value; |
| ArgSlot nbArgs = ArgSlotMath::Add(function->GetFunctionBody()->GetAsmJsFunctionInfo()->GetArgCount(), 1); |
| CallInfo callInfo((CallFlags)flags, nbArgs); |
| ArgumentReader args(&callInfo, paramsAddr); |
| void* returnAddress = _ReturnAddress(); |
| void* addressOfReturnAddress = _AddressOfReturnAddress(); |
| #if ENABLE_PROFILE_INFO |
| function->GetFunctionBody()->EnsureDynamicProfileInfo(); |
| #endif |
| AsmJsReturnStruct asmJsReturn = { 0 }; |
| InterpreterHelper(function, args, returnAddress, addressOfReturnAddress, &asmJsReturn); |
| |
| //Handle return value |
| AsmJsRetType::Which retType = (AsmJsRetType::Which) GetRetType(function); |
| |
| switch (retType) |
| { |
| #ifdef ENABLE_WASM_SIMD |
| case AsmJsRetType::Int32x4: |
| case AsmJsRetType::Bool32x4: |
| case AsmJsRetType::Bool16x8: |
| case AsmJsRetType::Bool8x16: |
| case AsmJsRetType::Float32x4: |
| case AsmJsRetType::Float64x2: |
| case AsmJsRetType::Int16x8: |
| case AsmJsRetType::Int8x16: |
| case AsmJsRetType::Uint32x4: |
| case AsmJsRetType::Uint16x8: |
| case AsmJsRetType::Uint8x16: |
| Assert(Wasm::Simd::IsEnabled()); |
| *(AsmJsSIMDValue*)retDst = asmJsReturn.simd; |
| break; |
| #endif |
| // double return |
| case AsmJsRetType::Double: |
| *(double*)retDst = asmJsReturn.d; |
| break; |
| // float return |
| case AsmJsRetType::Float: |
| *(float*)retDst = asmJsReturn.f; |
| break; |
| // signed or void return |
| case AsmJsRetType::Signed: |
| case AsmJsRetType::Void: |
| *(int*)retDst = asmJsReturn.i; |
| break; |
| case AsmJsRetType::Int64: |
| *(int64*)retDst = asmJsReturn.l; |
| break; |
| default: |
| Assume(false); |
| } |
| } |
| |
| #elif _M_X64 |
| typedef double(*AsmJsInterpreterDoubleEP)(AsmJsCallStackLayout*, void *); |
| typedef float(*AsmJsInterpreterFloatEP)(AsmJsCallStackLayout*, void *); |
| typedef int(*AsmJsInterpreterIntEP)(AsmJsCallStackLayout*, void *); |
| typedef int64(*AsmJsInterpreterInt64EP)(AsmJsCallStackLayout*, void *); |
| |
| void * InterpreterStackFrame::GetAsmJsInterpreterEntryPoint(AsmJsCallStackLayout* stack) |
| { |
| JavascriptFunction * function = stack->functionObject; |
| void * entryPoint = nullptr; |
| switch (function->GetFunctionBody()->GetAsmJsFunctionInfo()->GetReturnType().which()) |
| { |
| case Js::AsmJsRetType::Double: |
| { |
| entryPoint = (void*)(AsmJsInterpreterDoubleEP)Js::InterpreterStackFrame::AsmJsInterpreter < double >; |
| break; |
| } |
| case Js::AsmJsRetType::Float: |
| { |
| entryPoint = (void*)(AsmJsInterpreterFloatEP)Js::InterpreterStackFrame::AsmJsInterpreter < float >; |
| break; |
| } |
| case Js::AsmJsRetType::Signed: |
| case Js::AsmJsRetType::Void: |
| { |
| entryPoint = (void*)(AsmJsInterpreterIntEP)Js::InterpreterStackFrame::AsmJsInterpreter < int >; |
| break; |
| } |
| case Js::AsmJsRetType::Int64: |
| { |
| entryPoint = (void*)(AsmJsInterpreterInt64EP)Js::InterpreterStackFrame::AsmJsInterpreter < int64 >; |
| break; |
| } |
| #ifdef ENABLE_WASM_SIMD |
| case Js::AsmJsRetType::Int32x4: |
| case Js::AsmJsRetType::Bool32x4: |
| case Js::AsmJsRetType::Bool16x8: |
| case Js::AsmJsRetType::Bool8x16: |
| case Js::AsmJsRetType::Float32x4: |
| case Js::AsmJsRetType::Float64x2: |
| case Js::AsmJsRetType::Int16x8: |
| case Js::AsmJsRetType::Int8x16: |
| case Js::AsmJsRetType::Uint32x4: |
| case Js::AsmJsRetType::Uint16x8: |
| case Js::AsmJsRetType::Uint8x16: |
| { |
| entryPoint = (void*)Js::InterpreterStackFrame::AsmJsInterpreterSimdJs; |
| break; |
| } |
| #endif |
| default: |
| Assume(UNREACHED); |
| } |
| return entryPoint; |
| } |
| |
| |
| |
| |
| template<typename T> |
| T InterpreterStackFrame::AsmJsInterpreter(AsmJsCallStackLayout* layout) |
| { |
| Js::ScriptFunction * function = Js::ScriptFunction::FromVar(layout->functionObject); |
| int flags = CallFlags_Value; |
| ArgSlot nbArgs = ArgSlotMath::Add(function->GetFunctionBody()->GetAsmJsFunctionInfo()->GetArgCount(), 1); |
| |
| CallInfo callInfo((CallFlags)flags, nbArgs); |
| ArgumentReader args(&callInfo, (Var*)layout->args); |
| void* returnAddress = _ReturnAddress(); |
| void* addressOfReturnAddress = _AddressOfReturnAddress(); |
| function->GetFunctionBody()->EnsureDynamicProfileInfo(); |
| AsmJsReturnStruct asmJsReturn = { 0 }; |
| InterpreterHelper(function, args, returnAddress, addressOfReturnAddress, &asmJsReturn); |
| |
| return asmJsReturn.GetRetVal<T>(); |
| } |
| |
| __m128 InterpreterStackFrame::AsmJsInterpreterSimdJs(AsmJsCallStackLayout* layout) |
| { |
| return AsmJsInterpreter<X86SIMDValue>(layout).m128_value; |
| } |
| #endif |
| #endif |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// InterpreterStackFrame::SetOut() |
| /// |
| /// SetOut() change the Var value stored in the specified "out parameter" |
| /// register. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| inline void InterpreterStackFrame::SetOut(ArgSlot outRegisterID, Var aValue) |
| { |
| // |
| // The "out" parameter slots are located at the end of the local register range, counting |
| // forwards. This results in the "in" parameter slots being disjoint from the rest of the |
| // InterpreterStackFrame. |
| // ..., InterpreterStackFrame A, Locals A[], ..., Out A:0, Out A:1, Out A:2, ... |
| // | In B:0, In B:1, ..., InterpreterStackFrame B, Locals B[], ... |
| // (current 'this') | |
| // (new 'this' after call) |
| // |
| |
| Assert(m_outParams + outRegisterID < m_outSp); |
| m_outParams[outRegisterID] = aValue; |
| } |
| |
| inline void InterpreterStackFrame::SetOut(ArgSlot_OneByte outRegisterID, Var aValue) |
| { |
| Assert(m_outParams + outRegisterID < m_outSp); |
| m_outParams[outRegisterID] = aValue; |
| } |
| |
| inline void InterpreterStackFrame::OP_SetOutAsmDb(RegSlot outRegisterID, double val) |
| { |
| Assert(m_outParams + outRegisterID < m_outSp); |
| m_outParams[outRegisterID] = JavascriptNumber::NewWithCheck(val, scriptContext); |
| } |
| |
| inline void InterpreterStackFrame::OP_SetOutAsmInt(RegSlot outRegisterID, int val) |
| { |
| Assert(m_outParams + outRegisterID < m_outSp); |
| m_outParams[outRegisterID] = JavascriptNumber::ToVar(val, scriptContext); |
| } |
| |
| void InterpreterStackFrame::OP_SetOutAsmFlt(RegSlot outRegisterID, float val) |
| { |
| OP_SetOutAsmDb(outRegisterID, (double)val); |
| } |
| |
| inline void InterpreterStackFrame::OP_I_SetOutAsmFlt(RegSlot outRegisterID, float val) |
| { |
| Assert(m_outParams + outRegisterID < m_outSp); |
| *(float*)(&(m_outParams[outRegisterID])) = val; |
| } |
| |
| inline void InterpreterStackFrame::OP_I_SetOutAsmLong(RegSlot outRegisterID, int64 val) |
| { |
| Assert(m_outParams + outRegisterID < m_outSp); |
| *(int64*)(&(m_outParams[outRegisterID])) = val; |
| } |
| |
| inline void InterpreterStackFrame::OP_I_SetOutAsmInt(RegSlot outRegisterID, int val) |
| { |
| Assert(m_outParams + outRegisterID < m_outSp); |
| *(int*)(&(m_outParams[outRegisterID])) = val; |
| } |
| |
| inline void InterpreterStackFrame::OP_I_SetOutAsmDb(RegSlot outRegisterID, double val) |
| { |
| Assert(m_outParams + outRegisterID < m_outSp); |
| *(double*)(&(m_outParams[outRegisterID])) = val; |
| } |
| |
| inline void InterpreterStackFrame::OP_I_SetOutAsmSimd(RegSlot outRegisterID, AsmJsSIMDValue val) |
| { |
| Assert(m_outParams + outRegisterID < m_outSp); |
| *(AsmJsSIMDValue*)(&(m_outParams[outRegisterID])) = val; |
| } |
| |
| template<int type, bool toJs> |
| void InterpreterStackFrame::OP_InvalidWasmTypeConversion(...) |
| { |
| #ifdef ENABLE_WASM |
| CompileAssert(type < Wasm::WasmTypes::Limit); |
| const char16* fromType = toJs ? Wasm::WasmTypes::GetTypeName(static_cast<Wasm::WasmTypes::WasmType>(type)) : _u("Javascript Variable"); |
| const char16* toType = toJs ? _u("Javascript Variable") : Wasm::WasmTypes::GetTypeName(static_cast<Wasm::WasmTypes::WasmType>(type)); |
| JavascriptError::ThrowTypeErrorVar(scriptContext, WASMERR_InvalidTypeConversion, fromType, toType); |
| #else |
| Assert(UNREACHED); //shouldn't get there |
| JavascriptError::ThrowTypeErrorVar(scriptContext, WASMERR_InvalidTypeConversion, _u("unknown"), _u("unknown")); //throw for a release build |
| #endif |
| } |
| |
| // This will be called in the beginning of the try_finally. |
| inline void InterpreterStackFrame::CacheSp() |
| { |
| // Before caching the current m_outSp, we will be storing the previous the previously stored value in the m_outSpCached. |
| *m_outSp++ = (Var)m_outSpCached; |
| *m_outSp++ = (Var)m_outParams; |
| m_outSpCached = m_outSp - 2; |
| } |
| |
| inline void InterpreterStackFrame::RestoreSp() |
| { |
| // This will be called in the Finally block to restore from the previous SP cached. |
| |
| // m_outSpCached can be null if the catch block is called. |
| if (m_outSpCached != nullptr) |
| { |
| Assert(m_outSpCached < m_outSp); |
| m_outSp = m_outSpCached; |
| |
| m_outSpCached = (Var*)*m_outSp; |
| Assert(m_outSpCached == nullptr || m_outSpCached <= m_outSp); |
| |
| m_outParams = (Var*)*(m_outSp + 1); |
| } |
| else |
| { |
| ResetOut(); |
| } |
| } |
| |
| inline void InterpreterStackFrame::PushOut(Var aValue) |
| { |
| *m_outSp++ = aValue; |
| } |
| |
| inline void InterpreterStackFrame::PopOut(ArgSlot argCount) |
| { |
| ArgSlotMath::Inc(argCount); |
| m_outSp -= argCount; |
| m_outParams = (Var*)*m_outSp; |
| |
| AssertMsg(m_localSlots + this->m_functionBody->GetLocalsCount() <= m_outSp && |
| m_outSp < (m_localSlots + this->m_functionBody->GetLocalsCount() + this->m_functionBody->GetOutParamMaxDepth()), |
| "out args Stack pointer not in range after Pop"); |
| } |
| |
| void InterpreterStackFrame::ResetOut() |
| { |
| // |
| // Reset the m_outParams and m_outSp |
| // |
| m_outParams = m_localSlots + this->m_functionBody->GetLocalsCount(); |
| |
| m_outSp = m_outParams; |
| m_outSpCached = nullptr; |
| } |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| _NOINLINE |
| Var InterpreterStackFrame::DebugProcessThunk(void* returnAddress, void* addressOfReturnAddress) |
| { |
| PushPopFrameHelper pushPopFrameHelper(this, returnAddress, addressOfReturnAddress); |
| return this->DebugProcess(); |
| } |
| |
| // |
| // Under debug mode allow the exception to be swallowed and execution to continue |
| // if the debugger has specified that behavior. |
| // |
| Var InterpreterStackFrame::DebugProcess() |
| { |
| Assert(this->returnAddress != nullptr); |
| while (true) |
| { |
| JavascriptExceptionObject *exception = nullptr; |
| try |
| { |
| #if ENABLE_TTD |
| if (SHOULD_DO_TTD_STACK_STMT_OP(this->scriptContext)) |
| { |
| return this->ProcessWithDebugging_PreviousStmtTracking(); |
| } |
| else |
| { |
| return this->ProcessWithDebugging(); |
| } |
| #else |
| return this->ProcessWithDebugging(); |
| #endif |
| } |
| catch (const Js::JavascriptException& err) |
| { |
| JavascriptExceptionObject *exception_ = err.GetAndClear(); |
| Assert(exception_); |
| exception = exception_; |
| } |
| |
| if (exception) |
| { |
| bool skipException = false; |
| if (!exception->IsGeneratorReturnException() && |
| exception != scriptContext->GetThreadContext()->GetPendingSOErrorObject() && |
| exception != scriptContext->GetThreadContext()->GetPendingOOMErrorObject()) |
| { |
| skipException = exception->IsDebuggerSkip(); |
| } |
| if (skipException) |
| { |
| // If we are going to swallow the exception then advance to the beginning of the next user statement |
| if (exception->IsIgnoreAdvanceToNextStatement() |
| || this->scriptContext->GetDebugContext()->GetProbeContainer()->AdvanceToNextUserStatement(this->m_functionBody, &this->m_reader)) |
| { |
| // We must fix up the return value to at least be undefined: |
| this->SetReg((RegSlot)0, this->scriptContext->GetLibrary()->GetUndefined()); |
| |
| // If we recover from the exception, there may be a chance the out pointers in the InterpreterStackframe are not in a proper state. |
| // Reset them to correct the stack. |
| ResetOut(); |
| |
| // If we can successfully advance then continuing processing |
| continue; |
| } |
| } |
| |
| JavascriptExceptionOperators::DoThrowCheckClone(exception, scriptContext); |
| } |
| } |
| } |
| #endif |
| |
| template<typename OpCodeType, Js::OpCode(ReadOpFunc)(const byte*&), void (TracingFunc)(InterpreterStackFrame*, OpCodeType)> |
| OpCodeType InterpreterStackFrame::ReadOp(const byte *& ip) |
| { |
| #if DBG || DBG_DUMP |
| // |
| // For debugging byte-code, store the current offset before the instruction is read: |
| // - We convert this to "void *" to encourage the debugger to always display in hex, |
| // which matches the displayed offsets used by ByteCodeDumper. |
| // |
| this->DEBUG_currentByteOffset = (void *)m_reader.GetCurrentOffset(); |
| #endif |
| |
| OpCodeType op = (OpCodeType)ReadOpFunc(ip); |
| |
| #if DBG_DUMP |
| TracingFunc(this, op); |
| #endif |
| return op; |
| } |
| |
| void InterpreterStackFrame::TraceOpCode(InterpreterStackFrame* that, Js::OpCode op) |
| { |
| #if DBG_DUMP |
| that->scriptContext->byteCodeHistogram[(int)op]++; |
| if (PHASE_TRACE(Js::InterpreterPhase, that->m_functionBody)) |
| { |
| Output::Print(_u("%d.%d:Executing %s at offset 0x%X\n"), that->m_functionBody->GetSourceContextId(), that->m_functionBody->GetLocalFunctionId(), Js::OpCodeUtil::GetOpCodeName(op), that->DEBUG_currentByteOffset); |
| } |
| #endif |
| } |
| |
| void InterpreterStackFrame::TraceAsmJsOpCode(InterpreterStackFrame* that, Js::OpCodeAsmJs op) |
| { |
| #if DBG_DUMP && defined(ASMJS_PLAT) |
| if (PHASE_TRACE(Js::AsmjsInterpreterPhase, that->m_functionBody)) |
| { |
| Output::Print(_u("%d.%d:Executing %s at offset 0x%X\n"), that->m_functionBody->GetSourceContextId(), that->m_functionBody->GetLocalFunctionId(), Js::OpCodeUtilAsmJs::GetOpCodeName(op), that->DEBUG_currentByteOffset); |
| } |
| #endif |
| } |
| |
| #if ENABLE_TTD |
| template<typename OpCodeType, Js::OpCode(ReadOpFunc)(const byte*&), void (TracingFunc)(InterpreterStackFrame*, OpCodeType)> |
| OpCodeType InterpreterStackFrame::ReadOp_WPreviousStmtTracking(const byte *& ip) |
| { |
| #if DBG || DBG_DUMP |
| // |
| // For debugging byte-code, store the current offset before the instruction is read: |
| // - We convert this to "void *" to encourage the debugger to always display in hex, |
| // which matches the displayed offsets used by ByteCodeDumper. |
| // |
| this->DEBUG_currentByteOffset = (void *)m_reader.GetCurrentOffset(); |
| #endif |
| |
| #if ENABLE_TTD |
| AssertMsg(this->scriptContext->GetThreadContext()->IsRuntimeInTTDMode(), "We never be fetching an opcode via this path if this is not true!!!"); |
| #endif |
| |
| if (SHOULD_DO_TTD_STACK_STMT_OP(this->scriptContext)) |
| { |
| TTD::ExecutionInfoManager* executionMgr = this->scriptContext->GetThreadContext()->TTDExecutionInfo; |
| executionMgr->UpdateCurrentStatementInfo(m_reader.GetCurrentOffset()); |
| } |
| |
| OpCodeType op = (OpCodeType)ReadOpFunc(ip); |
| |
| #if DBG_DUMP |
| TracingFunc(this, op); |
| #endif |
| return op; |
| } |
| #endif |
| |
| _NOINLINE |
| Var InterpreterStackFrame::ProcessThunk(void* address, void* addressOfReturnAddress) |
| { |
| PushPopFrameHelper pushPopFrameHelper(this, address, addressOfReturnAddress); |
| return this->Process(); |
| } |
| |
| |
| #ifdef ASMJS_PLAT |
| Var InterpreterStackFrame::ProcessAsmJsModule() |
| { |
| FunctionBody* asmJsModuleFunctionBody = GetFunctionBody(); |
| AsmJsModuleInfo* info = asmJsModuleFunctionBody->GetAsmJsModuleInfo(); |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| if (Configuration::Global.flags.ForceAsmJsLinkFail) |
| { |
| AsmJSCompiler::OutputError(this->scriptContext, _u("Asm.js Runtime Error : Forcing link failure")); |
| return this->ProcessLinkFailedAsmJsModule(); |
| } |
| #endif |
| if (m_inSlotsCount != info->GetArgInCount() + 1) |
| { |
| // Error reparse without asm.js |
| AsmJSCompiler::OutputError(this->scriptContext, _u("Asm.js Runtime Error : Invalid module argument count")); |
| return this->ProcessLinkFailedAsmJsModule(); |
| } |
| |
| const AsmJsModuleMemory& moduleMemory = info->GetModuleMemory(); |
| Field(Var)* moduleMemoryPtr = RecyclerNewArray(scriptContext->GetRecycler(), Field(Var), moduleMemory.mMemorySize); |
| Field(Var)* arrayBufferPtr = moduleMemoryPtr + moduleMemory.mArrayBufferOffset; |
| Assert(moduleMemory.mArrayBufferOffset == AsmJsModuleMemory::MemoryTableBeginOffset); |
| Field(Var)* stdLibPtr = moduleMemoryPtr + moduleMemory.mStdLibOffset; |
| int* localIntSlots = (int*)(moduleMemoryPtr + moduleMemory.mIntOffset); |
| float* localFloatSlots = (float*)(moduleMemoryPtr + moduleMemory.mFloatOffset); |
| double* localDoubleSlots = (double*)(moduleMemoryPtr + moduleMemory.mDoubleOffset); |
| Field(Var)* localFunctionImports = moduleMemoryPtr + moduleMemory.mFFIOffset; |
| Field(Var)* localModuleFunctions = moduleMemoryPtr + moduleMemory.mFuncOffset; |
| Field(Field(Var)*)* localFunctionTables = (Field(Field(Var)*)*)(moduleMemoryPtr + moduleMemory.mFuncPtrOffset); |
| |
| ThreadContext* threadContext = this->scriptContext->GetThreadContext(); |
| *stdLibPtr = (m_inSlotsCount > 1) ? m_inParams[1] : nullptr; |
| |
| Var foreign = (m_inSlotsCount > 2) ? m_inParams[2] : nullptr; |
| *arrayBufferPtr = (m_inSlotsCount > 3) ? m_inParams[3] : nullptr; |
| //cache the current state of the disable implicit call flag |
| DisableImplicitFlags prevDisableImplicitFlags = threadContext->GetDisableImplicitFlags(); |
| ImplicitCallFlags saveImplicitcallFlags = threadContext->GetImplicitCallFlags(); |
| // Disable implicit calls to check if any of the VarImport or Function Import leads to implicit calls |
| threadContext->DisableImplicitCall(); |
| threadContext->SetImplicitCallFlags(ImplicitCallFlags::ImplicitCall_None); |
| bool checkParamResult = ASMLink::CheckParams(this->scriptContext, info, *stdLibPtr, foreign, *arrayBufferPtr); |
| if (!checkParamResult) |
| { |
| // don't need to print, because checkParams will do it for us |
| goto linkFailure; |
| } |
| else if (this->CheckAndResetImplicitCall(prevDisableImplicitFlags, saveImplicitcallFlags)) |
| { |
| AsmJSCompiler::OutputError(this->scriptContext, _u("Asm.js Runtime Error : Params have side effects")); |
| return this->ProcessLinkFailedAsmJsModule(); |
| } |
| // Initialize Variables |
| for (int i = 0; i < info->GetVarCount(); i++) |
| { |
| const auto& var = info->GetVar(i); |
| const AsmJsVarType type(var.type); |
| if (type.isInt()) |
| { |
| localIntSlots[var.location] = var.initialiser.intInit; |
| } |
| else if (type.isFloat()) |
| { |
| localFloatSlots[var.location] = var.initialiser.floatInit; |
| } |
| else if (type.isDouble()) |
| { |
| localDoubleSlots[var.location] = var.initialiser.doubleInit; |
| } |
| else |
| { |
| Assert(UNREACHED); |
| } |
| } |
| |
| // Load constant variables |
| for (int i = 0; i < info->GetVarImportCount(); i++) |
| { |
| const auto& import = info->GetVarImport(i); |
| const AsmJsVarType type(import.type); |
| // this might throw, but it would anyway in non-asm.js |
| Var value = JavascriptOperators::OP_GetProperty(foreign, import.field, scriptContext); |
| // check if there is implicit call and if there is implicit call then clear the disableimplicitcall flag |
| if (this->CheckAndResetImplicitCall(prevDisableImplicitFlags, saveImplicitcallFlags)) |
| { |
| AsmJSCompiler::OutputError(this->scriptContext, _u("Asm.js Runtime Error : Accessing var import %s has side effects"), this->scriptContext->GetPropertyName(import.field)->GetBuffer()); |
| return this->ProcessLinkFailedAsmJsModule(); |
| } |
| if (CONFIG_FLAG(AsmJsEdge)) |
| { |
| // emscripten had a bug which caused this check to fail in some circumstances, so this check fails for some demos |
| if (!TaggedNumber::Is(value) && (!RecyclableObject::Is(value) || DynamicType::Is(RecyclableObject::FromVar(value)->GetTypeId()))) |
| { |
| AsmJSCompiler::OutputError(this->scriptContext, _u("Asm.js Runtime Error : Var import %s must be primitive"), this->scriptContext->GetPropertyName(import.field)->GetBuffer()); |
| goto linkFailure; |
| } |
| } |
| |
| if (type.isInt()) |
| { |
| int val = JavascriptMath::ToInt32(value, scriptContext); |
| localIntSlots[import.location] = val; |
| } |
| else if (type.isFloat()) |
| { |
| float val = (float)JavascriptConversion::ToNumber(value, scriptContext); |
| localFloatSlots[import.location] = val; |
| } |
| else if (type.isDouble()) |
| { |
| double val = JavascriptConversion::ToNumber(value, scriptContext); |
| localDoubleSlots[import.location] = val; |
| } |
| |
| // check for implicit call after converting to number |
| if (this->CheckAndResetImplicitCall(prevDisableImplicitFlags, saveImplicitcallFlags)) |
| { |
| // Runtime error |
| AsmJSCompiler::OutputError(this->scriptContext, _u("Asm.js Runtime Error : Accessing var import %s has side effects"), this->scriptContext->GetPropertyName(import.field)->GetBuffer()); |
| return this->ProcessLinkFailedAsmJsModule(); |
| } |
| } |
| // Load external functions |
| for (int i = 0; i < info->GetFunctionImportCount(); i++) |
| { |
| const auto& import = info->GetFunctionImport(i); |
| // this might throw, but it would anyway in non-asm.js |
| Var importFunc = JavascriptOperators::OP_GetProperty(foreign, import.field, scriptContext); |
| // check if there is implicit call and if there is implicit call then clear the disableimplicitcall flag |
| if (this->CheckAndResetImplicitCall(prevDisableImplicitFlags, saveImplicitcallFlags)) |
| { |
| AsmJSCompiler::OutputError(this->scriptContext, _u("Asm.js Runtime Error : Accessing foreign function import %s has side effects"), this->scriptContext->GetPropertyName(import.field)->GetBuffer()); |
| return this->ProcessLinkFailedAsmJsModule(); |
| } |
| if (!JavascriptFunction::Is(importFunc)) |
| { |
| AsmJSCompiler::OutputError(this->scriptContext, _u("Asm.js Runtime Error : Foreign function import %s is not a function"), this->scriptContext->GetPropertyName(import.field)->GetBuffer()); |
| goto linkFailure; |
| } |
| localFunctionImports[import.location] = importFunc; |
| } |
| |
| threadContext->SetDisableImplicitFlags(prevDisableImplicitFlags); |
| threadContext->SetImplicitCallFlags(saveImplicitcallFlags); |
| // scope |
| { |
| FrameDisplay* pDisplay = RecyclerNewPlus(scriptContext->GetRecycler(), sizeof(void*), FrameDisplay, 0); |
| //DynamicObject* asmModule = scriptContext->GetLibrary()->CreateObject(false, 1); |
| //JavascriptOperators::OP_SetProperty(asmModule, PropertyIds::module, moduleMemoryPtr, scriptContext); |
| //pDisplay->SetItem(0, this->function); |
| for (int i = 0; i < info->GetFunctionCount(); i++) |
| { |
| const auto& modFunc = info->GetFunction(i); |
| |
| // TODO: add more runtime checks here |
| FunctionInfoPtrPtr functionInfo = m_functionBody->GetNestedFuncReference(i); |
| |
| AsmJsScriptFunction* scriptFuncObj = AsmJsScriptFunction::OP_NewAsmJsFunc(pDisplay, functionInfo); |
| localModuleFunctions[modFunc.location] = scriptFuncObj; |
| |
| if (scriptFuncObj->GetDynamicType()->GetEntryPoint() == DefaultDeferredDeserializeThunk) |
| { |
| JavascriptFunction::DeferredDeserialize(scriptFuncObj); |
| } |
| |
| scriptFuncObj->GetDynamicType()->SetEntryPoint(AsmJsExternalEntryPoint); |
| scriptFuncObj->GetFunctionBody()->GetAsmJsFunctionInfo()->SetModuleFunctionBody(asmJsModuleFunctionBody); |
| scriptFuncObj->SetModuleEnvironment(moduleMemoryPtr); |
| if (!info->IsRuntimeProcessed()) |
| { |
| // don't reset entrypoint upon relinking |
| FunctionEntryPointInfo* entrypointInfo = (FunctionEntryPointInfo*)scriptFuncObj->GetEntryPointInfo(); |
| entrypointInfo->SetIsAsmJSFunction(true); |
| |
| #if DYNAMIC_INTERPRETER_THUNK |
| if (!PHASE_ON1(AsmJsJITTemplatePhase)) |
| { |
| entrypointInfo->jsMethod = AsmJsDefaultEntryThunk; |
| } |
| #endif |
| } |
| } |
| } |
| |
| // Initialize function table arrays |
| for (int i = 0; i < info->GetFunctionTableCount(); i++) |
| { |
| const auto& modFuncTable = info->GetFunctionTable(i); |
| Field(Var)* funcTableArray = RecyclerNewArray(scriptContext->GetRecycler(), Field(Var), modFuncTable.size); |
| for (uint j = 0; j < modFuncTable.size; j++) |
| { |
| // get the module function index |
| const RegSlot index = modFuncTable.moduleFunctionIndex[j]; |
| // assign the module function pointer to the array |
| Var functionPtr = localModuleFunctions[index]; |
| funcTableArray[j] = functionPtr; |
| } |
| localFunctionTables[i] = funcTableArray; |
| } |
| // Do MTJRC/MAIC:0 check |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if ( |
| (PHASE_ON1(Js::AsmJsJITTemplatePhase) && CONFIG_FLAG(MaxTemplatizedJitRunCount) == 0) || |
| (!PHASE_ON1(Js::AsmJsJITTemplatePhase) && (CONFIG_FLAG(MaxAsmJsInterpreterRunCount) == 0 || CONFIG_FLAG(ForceNative))) |
| ) |
| { |
| if (PHASE_TRACE1(AsmjsEntryPointInfoPhase)) |
| { |
| Output::Print(_u("%s Scheduling For Full JIT at callcount:%d\n"), asmJsModuleFunctionBody->GetDisplayName(), 0); |
| Output::Flush(); |
| } |
| for (int i = 0; i < info->GetFunctionCount(); i++) |
| { |
| ScriptFunction* functionObj = (ScriptFunction*)PointerValue(localModuleFunctions[i]); |
| AnalysisAssert(functionObj != nullptr); |
| // don't want to generate code for APIs like changeHeap |
| if (functionObj->GetEntryPoint() == Js::AsmJsExternalEntryPoint) |
| { |
| WAsmJs::JitFunctionIfReady(functionObj); |
| } |
| } |
| } |
| #endif |
| |
| info->SetIsRuntimeProcessed(true); |
| |
| // create export object |
| if (info->GetExportsCount()) |
| { |
| Var newObj = JavascriptOperators::NewScObjectLiteral(GetScriptContext(), info->GetExportsIdArray(), |
| this->GetFunctionBody()->GetObjectLiteralTypeRef(0)); |
| for (int i = 0; i < info->GetExportsCount(); i++) |
| { |
| auto ex = info->GetExport(i); |
| Var func = localModuleFunctions[*ex.location]; |
| JavascriptOperators::OP_InitProperty(newObj, *ex.id, func); |
| } |
| SetReg((RegSlot)0, newObj); |
| return newObj; |
| } |
| |
| |
| // export only 1 function |
| { |
| Var exportFunc = localModuleFunctions[info->GetExportFunctionIndex()]; |
| SetReg((RegSlot)0, exportFunc); |
| return exportFunc; |
| } |
| |
| linkFailure: |
| threadContext->SetDisableImplicitFlags(prevDisableImplicitFlags); |
| threadContext->SetImplicitCallFlags(saveImplicitcallFlags); |
| return this->ProcessLinkFailedAsmJsModule(); |
| } |
| |
| Var InterpreterStackFrame::ProcessLinkFailedAsmJsModule() |
| { |
| AsmJSCompiler::OutputError(this->scriptContext, _u("asm.js linking failed.")); |
| |
| Js::FunctionBody* asmJsModuleFunctionBody = GetFunctionBody(); |
| AsmJsModuleInfo* info = asmJsModuleFunctionBody->GetAsmJsModuleInfo(); |
| |
| // do not support relinking with failed relink |
| if (info->IsRuntimeProcessed()) |
| { |
| Js::Throw::OutOfMemory(); |
| } |
| |
| ScriptFunction * funcObj = GetJavascriptFunction(); |
| ScriptFunction::ReparseAsmJsModule(&funcObj); |
| const bool doProfile = |
| funcObj->GetFunctionBody()->GetInterpreterExecutionMode(false) == ExecutionMode::ProfilingInterpreter || |
| (funcObj->GetFunctionBody()->IsInDebugMode() && DynamicProfileInfo::IsEnabled(funcObj->GetFunctionBody())); |
| |
| DynamicProfileInfo * dynamicProfileInfo = nullptr; |
| if (doProfile) |
| { |
| dynamicProfileInfo = funcObj->GetFunctionBody()->GetDynamicProfileInfo(); |
| funcObj->GetScriptContext()->GetThreadContext()->ClearImplicitCallFlags(); |
| } |
| |
| // after reparsing, we want to also use a new interpreter stack frame, as it will have different characteristics than the asm.js version |
| InterpreterStackFrame::Setup setup(funcObj, m_inParams, m_inSlotsCount); |
| size_t varAllocCount = setup.GetAllocationVarCount(); |
| size_t stackVarAllocCount = setup.GetStackAllocationVarCount(); |
| size_t varSizeInBytes; |
| |
| Var* allocation = nullptr; |
| Var* stackAllocation = nullptr; |
| DWORD_PTR stackAddr; |
| bool fReleaseAlloc = false; |
| if ((varAllocCount + stackVarAllocCount) > InterpreterStackFrame::LocalsThreshold) |
| { |
| ArenaAllocator *tmpAlloc = nullptr; |
| fReleaseAlloc = GetScriptContext()->EnsureInterpreterArena(&tmpAlloc); |
| varSizeInBytes = varAllocCount * sizeof(Var); |
| allocation = (Var*)tmpAlloc->Alloc(varSizeInBytes); |
| if (stackVarAllocCount != 0) |
| { |
| size_t stackVarSizeInBytes = stackVarAllocCount * sizeof(Var); |
| PROBE_STACK_PARTIAL_INITIALIZED_INTERPRETER_FRAME(GetScriptContext(), Js::Constants::MinStackInterpreter + stackVarSizeInBytes); |
| stackAllocation = (Var*)_alloca(stackVarSizeInBytes); |
| } |
| // use a stack address so the debugger stepping logic works (step-out, for example, compares stack depths to determine when to complete the step) |
| // debugger stepping does not matter here, but it's worth being consistent with normal stack frame |
| stackAddr = reinterpret_cast<DWORD_PTR>(&allocation); |
| } |
| else |
| { |
| varSizeInBytes = (varAllocCount + stackVarAllocCount) * sizeof(Var); |
| PROBE_STACK_PARTIAL_INITIALIZED_INTERPRETER_FRAME(GetScriptContext(), Js::Constants::MinStackInterpreter + varSizeInBytes); |
| allocation = (Var*)_alloca(varSizeInBytes); |
| stackAddr = reinterpret_cast<DWORD_PTR>(allocation); |
| } |
| |
| #if DBG |
| Var invalidStackVar = (Js::RecyclableObject*)_alloca(sizeof(Js::RecyclableObject)); |
| memset(invalidStackVar, 0xFE, sizeof(Js::RecyclableObject)); |
| #endif |
| |
| InterpreterStackFrame * newInstance = setup.InitializeAllocation(allocation, stackAllocation, funcObj->GetFunctionBody()->GetHasImplicitArgIns(), doProfile, nullptr, stackAddr |
| #if DBG |
| , invalidStackVar |
| #endif |
| ); |
| |
| newInstance->m_reader.Create(funcObj->GetFunctionBody()); |
| // now that we have set up the new frame, let's interpret it! |
| funcObj->GetFunctionBody()->BeginExecution(); |
| |
| PushPopFrameHelper pushPopFrameHelper(newInstance, this->returnAddress, this->addressOfReturnAddress); |
| Var retVal = newInstance->ProcessUnprofiled(); |
| |
| if (doProfile) |
| { |
| dynamicProfileInfo->RecordImplicitCallFlags(GetScriptContext()->GetThreadContext()->GetImplicitCallFlags()); |
| } |
| |
| if (fReleaseAlloc) |
| { |
| GetScriptContext()->ReleaseInterpreterArena(); |
| } |
| |
| return retVal; |
| } |
| |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| int AsmJsCallDepth = 0; |
| #endif |
| |
| // Function memory allocation should be done the same way as |
| // T AsmJsCommunEntryPoint(Js::ScriptFunction* func, ...) (AsmJSJitTemplate.cpp) |
| // update any changes there |
| /* |
| This function does the following fixup |
| Stack Before Stack After |
| ============== ================ |
| | VarConstants | | VarConstants | |
| |--------------| |----------------- |
| | IntConstants | | IntConstants | |
| |--------------| | ------------ | |
| | FloatConst | | Int Vars+Tmps | |
| |--------------| |----------------| |
| | DoubleConst | | FloatConst | |
| |--------------| | ---------- | |
| | Var&Temps | | Flt Vars+tmps | |
| |==============| |----------------| |
| | DoubleConst | |
| | ----------- | |
| | Dbl Vars+Tmps | |
| ================ |
| |
| intSrc,FltSrc&DblSrc are pointers to the stack before the change |
| m_localIntSlots,m_localFloatSlots,m_localDoubleSlots are pointers to the stack after the change |
| */ |
| void InterpreterStackFrame::AlignMemoryForAsmJs() |
| { |
| FunctionBody *const functionBody = GetFunctionBody(); |
| ScriptFunction* func = GetJavascriptFunction(); |
| uint32& callCount = ((FunctionEntryPointInfo*)func->GetEntryPointInfo())->callsCount; |
| WAsmJs::JitFunctionIfReady(func, ++callCount); |
| AsmJsFunctionInfo* info = functionBody->GetAsmJsFunctionInfo(); |
| |
| // The const table is copied after the FirstRegSlot |
| byte* constTable = (byte*)(m_localSlots + FunctionBody::FirstRegSlot); |
| byte* slotsStart = (byte*)m_localSlots; |
| |
| // Must do in reverse order to avoid overwriting const of other type as we move things around |
| for (int i = WAsmJs::LIMIT - 1; i >= 0; --i) |
| { |
| WAsmJs::Types type = (WAsmJs::Types)i; |
| auto typeInfo = info->GetTypedSlotInfo(type); |
| |
| byte* destination = slotsStart + typeInfo->byteOffset; |
| switch (type) |
| { |
| case WAsmJs::INT32: m_localIntSlots = (int*)destination; break; |
| case WAsmJs::INT64: m_localInt64Slots = (int64*)destination; break; |
| case WAsmJs::FLOAT32: m_localFloatSlots = (float*)destination; break; |
| case WAsmJs::FLOAT64: m_localDoubleSlots = (double*)destination; break; |
| case WAsmJs::SIMD: m_localSimdSlots = (AsmJsSIMDValue*)destination; break; |
| default: |
| CompileAssert(WAsmJs::SIMD == WAsmJs::LastType); |
| Assert(false); |
| break; |
| } |
| |
| byte* source = constTable + typeInfo->constSrcByteOffset; |
| if (typeInfo->constCount > 0 && source != destination) |
| { |
| // Make sure slots are aligned for this type |
| Assert(::Math::Align<intptr_t>((intptr_t)destination, (intptr_t)WAsmJs::GetTypeByteSize(type)) == (intptr_t)destination); |
| Assert(typeInfo->constSrcByteOffset != Js::Constants::InvalidOffset); |
| uint constByteSize = typeInfo->constCount * WAsmJs::GetTypeByteSize(type); |
| memmove_s(destination, constByteSize, source, constByteSize); |
| } |
| } |
| |
| // Load module environment |
| AsmJsScriptFunction* asmJsFunc = AsmJsScriptFunction::FromVar(this->function); |
| m_localSlots[AsmJsFunctionMemory::ModuleEnvRegister] = asmJsFunc->GetModuleEnvironment(); |
| m_localSlots[AsmJsFunctionMemory::ArrayBufferRegister] = nullptr; |
| #ifdef ENABLE_WASM |
| if (WasmScriptFunction::Is(func)) |
| { |
| WasmScriptFunction* wasmFunc = WasmScriptFunction::FromVar(func); |
| m_wasmMemory = wasmFunc->GetWebAssemblyMemory(); |
| m_signatures = func->GetFunctionBody()->GetAsmJsFunctionInfo()->GetWebAssemblyModule()->GetSignatures(); |
| } |
| else |
| #endif |
| { |
| m_asmJsBuffer = asmJsFunc->GetAsmJsArrayBuffer(); |
| } |
| |
| m_localSlots[AsmJsFunctionMemory::ArraySizeRegister] = 0; // do not cache ArraySize in the interpreter |
| m_localSlots[AsmJsFunctionMemory::ScriptContextBufferRegister] = functionBody->GetScriptContext(); |
| |
| int* intArg = m_localIntSlots + info->GetTypedSlotInfo(WAsmJs::INT32)->constCount; |
| int64* int64Arg = m_localInt64Slots + info->GetTypedSlotInfo(WAsmJs::INT64)->constCount; |
| double* doubleArg = m_localDoubleSlots + info->GetTypedSlotInfo(WAsmJs::FLOAT64)->constCount; |
| float* floatArg = m_localFloatSlots + info->GetTypedSlotInfo(WAsmJs::FLOAT32)->constCount; |
| #ifdef ENABLE_WASM_SIMD |
| AsmJsSIMDValue* simdArg = m_localSimdSlots + info->GetTypedSlotInfo(WAsmJs::SIMD)->constCount; |
| #endif |
| // Move the arguments to the right location |
| ArgSlot argCount = info->GetArgCount(); |
| |
| #if _M_X64 && _WIN32 |
| uint homingAreaSize = 0; |
| #endif |
| |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| const bool tracingFunc = PHASE_TRACE(AsmjsFunctionEntryPhase, functionBody); |
| if (tracingFunc) |
| { |
| if (AsmJsCallDepth) |
| { |
| Output::Print(_u("%*c"), AsmJsCallDepth, ' '); |
| } |
| Output::Print(_u("Executing function %s("), functionBody->GetDisplayName()); |
| ++AsmJsCallDepth; |
| } |
| #endif |
| uintptr_t argAddress = (uintptr_t)m_inParams; |
| for (ArgSlot i = 0; i < argCount; i++) |
| { |
| #if _M_X64 && _WIN32 |
| // 3rd Argument should be at the end of the homing area. |
| Assert(i != 3 || argAddress == (uintptr_t)m_inParams + homingAreaSize); |
| if (i < 3) |
| { |
| // for x64 we spill the first 3 floating point args below the rest of the arguments on the stack |
| // m_inParams will be from DynamicInterpreterThunk's frame. Floats are in InterpreterAsmThunk's frame. Stack will be set up like so: |
| // DIT arg2 <- first scriptArg, m_inParams points here |
| // DIT arg1 |
| // padding |
| // IAT r9 home |
| // IAT r8 home |
| // IAT rdx home |
| // IAT rcx home |
| // IAT return address |
| // IAT push rbp |
| // IAT padding |
| // IAT xmm3 spill |
| // IAT xmm2 spill |
| // IAT xmm1 spill <- floatSpillAddress for arg1 |
| |
| #define FLOAT_SPILL_ADDRESS_OFFSET_WORDS 15 |
| // floats are spilled as xmmwords |
| uintptr_t floatSpillAddress = (uintptr_t)m_inParams - MachPtr * (FLOAT_SPILL_ADDRESS_OFFSET_WORDS - 2 * i); |
| |
| if (info->GetArgType(i).isInt()) |
| { |
| *intArg = *(int*)argAddress; |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (tracingFunc) |
| { |
| Output::Print(_u("%d, "), *intArg); |
| } |
| #endif |
| ++intArg; |
| homingAreaSize += MachPtr; |
| } |
| else if (info->GetArgType(i).isInt64()) |
| { |
| *int64Arg = *(int64*)argAddress; |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (tracingFunc) |
| { |
| Output::Print(_u("%lld, "), *int64Arg); |
| } |
| #endif |
| ++int64Arg; |
| homingAreaSize += MachPtr; |
| } |
| else if (info->GetArgType(i).isFloat()) |
| { |
| *floatArg = *(float*)floatSpillAddress; |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (tracingFunc) |
| { |
| Output::Print(_u("%.2f, "), *floatArg); |
| } |
| #endif |
| ++floatArg; |
| homingAreaSize += MachPtr; |
| } |
| else if (info->GetArgType(i).isDouble()) |
| { |
| *doubleArg = *(double*)floatSpillAddress; |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (tracingFunc) |
| { |
| Output::Print(_u("%.2f, "), *doubleArg); |
| } |
| #endif |
| ++doubleArg; |
| homingAreaSize += MachPtr; |
| } |
| else |
| { |
| #ifdef ENABLE_WASM_SIMD |
| Assert(info->GetArgType(i).isSIMD()); |
| *simdArg = *(AsmJsSIMDValue*)floatSpillAddress; |
| ++simdArg; |
| homingAreaSize += sizeof(AsmJsSIMDValue); |
| #else |
| Assert(UNREACHED); |
| #endif |
| } |
| |
| if (Wasm::Simd::IsEnabled() && i == 2) // last argument ? |
| { |
| // If we have simd arguments, the homing area in m_inParams can be larger than 3 64-bit slots. This is because SIMD values are unboxed there too. |
| // After unboxing, the homing area is overwritten by rdx, r8 and r9, and we read/skip 64-bit slots from the homing area (argAddress += MachPtr). |
| // After the last argument of the 3 is read, we need to advance argAddress to skip over the possible extra space and to the start of the rest of the arguments. |
| argAddress = (uintptr_t)m_inParams + homingAreaSize; |
| } |
| else |
| { |
| argAddress += MachPtr; |
| } |
| } |
| else |
| #endif |
| if (info->GetArgType(i).isInt()) |
| { |
| *intArg = *(int*)argAddress; |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (tracingFunc) |
| { |
| Output::Print(_u("%d, "), *intArg); |
| } |
| #endif |
| ++intArg; |
| argAddress += MachPtr; |
| } |
| else if (info->GetArgType(i).isInt64()) |
| { |
| *int64Arg = *(int64*)argAddress; |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (tracingFunc) |
| { |
| Output::Print(_u("%lld, "), *int64Arg); |
| } |
| #endif |
| ++int64Arg; |
| argAddress += sizeof(int64); |
| } |
| else if (info->GetArgType(i).isFloat()) |
| { |
| *floatArg = *(float*)argAddress; |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (tracingFunc) |
| { |
| Output::Print(_u("%.2f, "), *floatArg); |
| } |
| #endif |
| ++floatArg; |
| argAddress += MachPtr; |
| } |
| else if (info->GetArgType(i).isDouble()) |
| { |
| Assert(info->GetArgType(i).isDouble()); |
| *doubleArg = *(double*)argAddress; |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (tracingFunc) |
| { |
| Output::Print(_u("%.2f, "), *doubleArg); |
| } |
| #endif |
| ++doubleArg; |
| argAddress += sizeof(double); |
| } |
| #ifdef ENABLE_WASM_SIMD |
| else if (Wasm::Simd::IsEnabled() && info->GetArgType(i).isSIMD()) |
| { |
| *simdArg = *(AsmJsSIMDValue*)argAddress; |
| ++simdArg; |
| argAddress += sizeof(AsmJsSIMDValue); |
| } |
| #endif |
| else |
| { |
| AssertMsg(UNREACHED, "Invalid function arg type."); |
| } |
| } |
| |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (tracingFunc) |
| { |
| Output::Print(_u("){\n")); |
| Output::Flush(); |
| } |
| #endif |
| } |
| #endif |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// InterpreterStackFrame::Process |
| /// |
| /// Process() processes a single loop of execution for the current |
| /// JavascriptFunction being executed: |
| /// - Individual instructions are dispatched to specific handlers for different |
| /// OpCodes. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| #if ENABLE_PROFILE_INFO |
| #define INTERPRETERLOOPNAME ProcessProfiled |
| #define PROVIDE_INTERPRETERPROFILE |
| #include "InterpreterLoop.inl" |
| #undef PROVIDE_INTERPRETERPROFILE |
| #undef INTERPRETERLOOPNAME |
| #endif |
| |
| #define INTERPRETERLOOPNAME ProcessUnprofiled |
| #include "InterpreterLoop.inl" |
| #undef INTERPRETERLOOPNAME |
| |
| #if ENABLE_TTD_DIAGNOSTICS_TRACING |
| #define PROVIDE_INTERPRETER_STMTS |
| #define INTERPRETERLOOPNAME ProcessUnprofiled_PreviousStmtTracking |
| #include "InterpreterLoop.inl" |
| #undef INTERPRETERLOOPNAME |
| #undef PROVIDE_INTERPRETER_STMTS |
| #endif |
| |
| #ifdef ASMJS_PLAT |
| #define INTERPRETERLOOPNAME ProcessAsmJs |
| #define INTERPRETER_ASMJS |
| #include "InterpreterProcessOpCodeAsmJs.h" |
| #include "InterpreterLoop.inl" |
| #undef INTERPRETER_ASMJS |
| #undef INTERPRETERLOOPNAME |
| #endif |
| |
| // For now, always collect profile data when debugging, |
| // otherwise the backend will be confused if there's no profile data. |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| #define INTERPRETERLOOPNAME ProcessWithDebugging |
| #define PROVIDE_DEBUGGING |
| #if ENABLE_PROFILE_INFO |
| #define PROVIDE_INTERPRETERPROFILE |
| #endif |
| #include "InterpreterLoop.inl" |
| #if ENABLE_PROFILE_INFO |
| #undef PROVIDE_INTERPRETERPROFILE |
| #endif |
| #undef PROVIDE_DEBUGGING |
| #undef INTERPRETERLOOPNAME |
| #endif |
| |
| #if ENABLE_TTD |
| #define PROVIDE_INTERPRETER_STMTS |
| #define INTERPRETERLOOPNAME ProcessWithDebugging_PreviousStmtTracking |
| #define PROVIDE_DEBUGGING |
| #if ENABLE_PROFILE_INFO |
| #define PROVIDE_INTERPRETERPROFILE |
| #endif |
| #include "InterpreterLoop.inl" |
| #if ENABLE_PROFILE_INFO |
| #undef PROVIDE_INTERPRETERPROFILE |
| #endif |
| #undef PROVIDE_DEBUGGING |
| #undef INTERPRETERLOOPNAME |
| #undef PROVIDE_INTERPRETER_STMTS |
| #endif |
| |
| Var InterpreterStackFrame::Process() |
| { |
| #if ENABLE_PROFILE_INFO |
| class AutoRestore |
| { |
| private: |
| InterpreterStackFrame * const interpreterStackFrame; |
| const uint32 savedSwitchProfileModeOnLoopEndNumber; |
| const bool savedIsAutoProfiling; |
| const bool savedSwitchProfileMode; |
| |
| public: |
| AutoRestore(InterpreterStackFrame *const interpreterStackFrame) |
| : interpreterStackFrame(interpreterStackFrame), |
| savedIsAutoProfiling(interpreterStackFrame->isAutoProfiling), |
| savedSwitchProfileMode(interpreterStackFrame->switchProfileMode), |
| savedSwitchProfileModeOnLoopEndNumber(interpreterStackFrame->switchProfileModeOnLoopEndNumber) |
| { |
| } |
| |
| ~AutoRestore() |
| { |
| interpreterStackFrame->isAutoProfiling = savedIsAutoProfiling; |
| interpreterStackFrame->switchProfileMode = savedSwitchProfileMode; |
| interpreterStackFrame->switchProfileModeOnLoopEndNumber = savedSwitchProfileModeOnLoopEndNumber; |
| } |
| } autoRestore(this); |
| #endif |
| |
| if (this->ehBailoutData && !this->TestFlags(InterpreterStackFrameFlags_FromBailOutInInlinee)) |
| { |
| if (this->TestFlags(Js::InterpreterStackFrameFlags_FromBailOut) && !this->TestFlags(InterpreterStackFrameFlags_ProcessingBailOutFromEHCode)) |
| { |
| this->OrFlags(Js::InterpreterStackFrameFlags_ProcessingBailOutFromEHCode); |
| EHBailoutData * topLevelEHBailoutData = this->ehBailoutData; |
| while (topLevelEHBailoutData->parent->nestingDepth != -1) |
| { |
| topLevelEHBailoutData->parent->child = topLevelEHBailoutData; |
| topLevelEHBailoutData = topLevelEHBailoutData->parent; |
| } |
| ProcessTryHandlerBailout(topLevelEHBailoutData, this->ehBailoutData->nestingDepth); |
| this->ClearFlags(Js::InterpreterStackFrameFlags_ProcessingBailOutFromEHCode); |
| this->ehBailoutData = nullptr; |
| } |
| } |
| |
| #ifdef ASMJS_PLAT |
| if (GetFunctionBody()->GetIsAsmjsMode()) |
| { |
| FunctionBody *const functionBody = GetFunctionBody(); |
| AsmJsFunctionInfo* asmInfo = functionBody->GetAsmJsFunctionInfo(); |
| if (asmInfo) |
| { |
| AlignMemoryForAsmJs(); |
| Var returnVar = ProcessAsmJs(); |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (PHASE_TRACE(AsmjsFunctionEntryPhase, functionBody)) |
| { |
| --AsmJsCallDepth; |
| if (AsmJsCallDepth) |
| { |
| Output::Print(_u("%*c}"), AsmJsCallDepth, ' '); |
| } |
| else |
| { |
| Output::Print(_u("}")); |
| } |
| switch (asmInfo->GetReturnType().which()) |
| { |
| case AsmJsRetType::Void: |
| break; |
| case AsmJsRetType::Signed: |
| Output::Print(_u(" = %d"), m_localIntSlots[0]); |
| break; |
| case AsmJsRetType::Int64: |
| Output::Print(_u(" = %lld"), m_localInt64Slots[0]); |
| break; |
| case AsmJsRetType::Float: |
| Output::Print(_u(" = %.4f"), m_localFloatSlots[0]); |
| break; |
| case AsmJsRetType::Double: |
| Output::Print(_u(" = %.4f"), m_localDoubleSlots[0]); |
| break; |
| default: |
| break; |
| } |
| Output::Print(_u(";\n")); |
| Output::Flush(); |
| } |
| #endif |
| return returnVar; |
| } |
| else |
| { |
| Assert(functionBody->GetAsmJsModuleInfo()); |
| return ProcessAsmJsModule(); |
| } |
| } |
| #endif |
| |
| #if ENABLE_PROFILE_INFO |
| switchProfileMode = false; |
| switchProfileModeOnLoopEndNumber = 0u - 1; |
| #endif |
| |
| #if ENABLE_PROFILE_INFO |
| FunctionBody *const functionBody = GetFunctionBody(); |
| const ExecutionMode interpreterExecutionMode = |
| functionBody->GetInterpreterExecutionMode(TestFlags(InterpreterStackFrameFlags_FromBailOut)); |
| if (interpreterExecutionMode == ExecutionMode::ProfilingInterpreter) |
| { |
| #if ENABLE_TTD |
| AssertMsg(!SHOULD_DO_TTD_STACK_STMT_OP(this->scriptContext), "We should have pinned into Interpreter mode in this case!!!"); |
| #endif |
| |
| isAutoProfiling = false; |
| return ProcessProfiled(); |
| } |
| |
| Assert( |
| interpreterExecutionMode == ExecutionMode::Interpreter || |
| interpreterExecutionMode == ExecutionMode::AutoProfilingInterpreter); |
| isAutoProfiling = interpreterExecutionMode == ExecutionMode::AutoProfilingInterpreter; |
| |
| Var result; |
| while (true) |
| { |
| Assert(!switchProfileMode); |
| |
| #if ENABLE_TTD_DIAGNOSTICS_TRACING |
| if (this->scriptContext->ShouldPerformRecordOrReplayAction()) |
| { |
| result = ProcessUnprofiled_PreviousStmtTracking(); |
| } |
| else |
| { |
| result = ProcessUnprofiled(); |
| } |
| #else |
| result = ProcessUnprofiled(); |
| #endif |
| |
| Assert(!(switchProfileMode && result)); |
| if (switchProfileMode) |
| { |
| switchProfileMode = false; |
| } |
| else |
| { |
| break; |
| } |
| Assert(isAutoProfiling); |
| |
| #if DBG_DUMP |
| |
| if (PHASE_TRACE(InterpreterAutoProfilePhase, functionBody)) |
| { |
| char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; |
| Output::Print(_u("InterpreterAutoProfile - Func %s - Started profiling\n"), functionBody->GetDebugNumberSet(debugStringBuffer)); |
| Output::Flush(); |
| } |
| #endif |
| |
| Assert(!switchProfileMode); |
| result = ProcessProfiled(); |
| Assert(!(switchProfileMode && result)); |
| if (switchProfileMode) |
| { |
| switchProfileMode = false; |
| } |
| else |
| { |
| break; |
| } |
| Assert(isAutoProfiling); |
| |
| #if DBG_DUMP |
| if (PHASE_TRACE(InterpreterAutoProfilePhase, functionBody)) |
| { |
| char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; |
| Output::Print(_u("InterpreterAutoProfile - Func %s - Stopped profiling\n"), functionBody->GetDebugNumberSet(debugStringBuffer)); |
| Output::Flush(); |
| } |
| #endif |
| } |
| return result; |
| #else |
| #if ENABLE_TTD_DIAGNOSTICS_TRACING |
| if (this->scriptContext->ShouldPerformRecordOrReplayAction()) |
| { |
| return ProcessUnprofiled_PreviousStmtTracking(); |
| } |
| else |
| { |
| return ProcessUnprofiled(); |
| } |
| #else |
| return ProcessUnprofiled(); |
| #endif |
| #endif |
| } |
| |
| |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetMethodProperty(unaligned T *playout) |
| { |
| Var varInstance = GetReg(playout->Instance); |
| OP_GetMethodProperty(varInstance, playout); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetLocalMethodProperty(unaligned T *playout) |
| { |
| OP_GetMethodProperty(this->localClosure, playout); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetMethodProperty(Var varInstance, unaligned T *playout) |
| { |
| #if ENABLE_COPYONACCESS_ARRAY |
| JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(varInstance); |
| #endif |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| RecyclableObject* obj = JavascriptOperators::TryFromVar<RecyclableObject>(varInstance); |
| if (obj) |
| { |
| ScriptFunction *fn = JavascriptOperators::TryFromVar<ScriptFunction>(obj); |
| if ((propertyId == PropertyIds::apply || propertyId == PropertyIds::call) && fn) |
| { |
| // If the property being loaded is "apply"/"call", make an optimistic assumption that apply/call is not overridden and |
| // undefer the function right here if it was defer parsed before. This is required so that the load of "apply"/"call" |
| // happens from the same "type". Otherwise, we will have a polymorphic cache for load of "apply"/"call". |
| if (fn->GetType()->GetEntryPoint() == JavascriptFunction::DeferredParsingThunk) |
| { |
| JavascriptFunction::DeferredParse(&fn); |
| } |
| } |
| } |
| |
| InlineCache *inlineCache = this->GetInlineCache(playout->inlineCacheIndex); |
| |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->inlineCacheIndex, true); |
| Var aValue; |
| if (obj && |
| CacheOperators::TryGetProperty<true, true, false, false, false, false, true, false, false, false>( |
| obj, false, obj, propertyId, &aValue, GetScriptContext(), nullptr, &info)) |
| { |
| SetReg(playout->Value, aValue); |
| return; |
| } |
| |
| OP_GetMethodProperty_NoFastPath(varInstance, playout); |
| } |
| |
| template <class T> |
| _NOINLINE void InterpreterStackFrame::OP_GetMethodProperty_NoFastPath(Var instance, unaligned T *playout) |
| { |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| |
| Var value = JavascriptOperators::PatchGetMethod<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| instance, |
| propertyId |
| ); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetMethod throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetMethodProperty(instance, propertyId, value, true); |
| } |
| #endif |
| |
| SetReg(playout->Value, value); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetRootMethodProperty(unaligned T *playout) |
| { |
| Assert(playout->inlineCacheIndex >= this->m_functionBody->GetRootObjectLoadInlineCacheStart()); |
| Js::Var instance = this->GetRootObject(); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| InlineCache *inlineCache = this->GetInlineCache(playout->inlineCacheIndex); |
| DynamicObject *obj = DynamicObject::UnsafeFromVar(instance); |
| |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->inlineCacheIndex, true); |
| Var aValue; |
| if (CacheOperators::TryGetProperty<true, true, false, false, false, false, true, false, false, false>( |
| obj, true, obj, propertyId, &aValue, GetScriptContext(), nullptr, &info)) |
| { |
| SetReg(playout->Value, aValue); |
| return; |
| } |
| |
| OP_GetRootMethodProperty_NoFastPath(playout); |
| } |
| |
| template <class T> |
| _NOINLINE void InterpreterStackFrame::OP_GetRootMethodProperty_NoFastPath(unaligned T *playout) |
| { |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| |
| Var rootInstance = this->GetRootObject(); |
| |
| Var value = JavascriptOperators::PatchGetRootMethod<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| DynamicObject::UnsafeFromVar(rootInstance), |
| propertyId |
| ); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetMethod throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetMethodProperty(rootInstance, propertyId, value, true); |
| } |
| #endif |
| |
| SetReg(playout->Value, value); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetMethodPropertyScoped(unaligned T *playout) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| Var varInstance = GetReg(playout->Instance); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| |
| RecyclableObject* obj = NULL; |
| if (RecyclableObject::Is(varInstance)) |
| { |
| obj = RecyclableObject::FromVar(varInstance); |
| } |
| |
| InlineCache *inlineCache = this->GetInlineCache(playout->inlineCacheIndex); |
| |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->inlineCacheIndex, true); |
| Var aValue; |
| if (obj && |
| CacheOperators::TryGetProperty<true, true, false, false, false, false, true, false, false, false>( |
| obj, false, obj, propertyId, &aValue, GetScriptContext(), nullptr, &info)) |
| { |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| |
| SetReg(playout->Value, aValue); |
| return; |
| } |
| |
| OP_GetMethodPropertyScoped_NoFastPath(playout); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| |
| template <class T> |
| _NOINLINE void InterpreterStackFrame::OP_GetMethodPropertyScoped_NoFastPath(unaligned T *playout) |
| { |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| Js::Var instance = GetReg(playout->Instance); |
| |
| Js::Var value = JavascriptOperators::PatchScopedGetMethod<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| instance, |
| propertyId |
| ); |
| |
| SetReg(playout->Value, value); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetMethod throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetMethodProperty(instance, propertyId, value, true); |
| } |
| #endif |
| |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetMethodProperty(unaligned T *playout) |
| { |
| ProfiledGetProperty<T, false, true, false>(playout, GetReg(playout->Instance)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetLocalMethodProperty(unaligned T *playout) |
| { |
| ProfiledGetProperty<T, false, true, false>(playout, this->localClosure); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetRootMethodProperty(unaligned T *playout) |
| { |
| ProfiledGetProperty<T, true, true, false>(playout, GetRootObject()); |
| } |
| |
| RecyclableObject * |
| InterpreterStackFrame::OP_CallGetFunc(Var target) |
| { |
| return JavascriptOperators::GetCallableObjectOrThrow(target, GetScriptContext()); |
| } |
| |
| void InterpreterStackFrame::OP_AsmStartCall(const unaligned OpLayoutStartCall * playout) |
| { |
| OP_StartCall(playout->ArgCount / sizeof(Var)); |
| m_outParams[0] = scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| void InterpreterStackFrame::OP_StartCall(const unaligned OpLayoutStartCall * playout) |
| { |
| OP_StartCall(playout->ArgCount); |
| } |
| |
| void InterpreterStackFrame::OP_StartCall(uint outParamCount) |
| { |
| // Save the outParams for the current callsite on the outparam stack |
| PushOut(m_outParams); |
| |
| // Update outParams for the indicated callsite |
| m_outParams = m_outSp; |
| |
| m_outSp += outParamCount; |
| |
| AssertMsg(m_localSlots + this->m_functionBody->GetLocalsCount() < m_outSp && |
| m_outSp <= (m_localSlots + this->m_functionBody->GetLocalsCount() + this->m_functionBody->GetOutParamMaxDepth()), |
| "out args Stack pointer not in range after Push"); |
| |
| } |
| |
| #ifdef ASMJS_PLAT |
| void InterpreterStackFrame::OP_ProfiledCallAsmInternal(RegSlot funcReg, RegSlot returnReg, ProfileId profileId) |
| { |
| Var target = GetRegRawPtr(funcReg); |
| ScriptFunction* function = (ScriptFunction*)OP_CallGetFunc(target); |
| |
| Js::FunctionBody * body = function->GetFunctionBody(); |
| DynamicProfileInfo * dynamicProfileInfo = GetFunctionBody()->GetDynamicProfileInfo(); |
| dynamicProfileInfo->RecordAsmJsCallSiteInfo(GetFunctionBody(), profileId, body); |
| return OP_CallAsmInternalCommon(function, returnReg); |
| } |
| void InterpreterStackFrame::OP_CallAsmInternal(RegSlot funcReg, RegSlot returnReg) |
| { |
| Var target = GetRegRawPtr(funcReg); |
| ScriptFunction* function = (ScriptFunction*)OP_CallGetFunc(target); |
| return OP_CallAsmInternalCommon(function, returnReg); |
| } |
| void InterpreterStackFrame::OP_CallAsmInternalCommon(ScriptFunction* function, RegSlot returnReg) |
| { |
| AsmJsScriptFunction* scriptFunc = AsmJsScriptFunction::FromVar(function); |
| AsmJsFunctionInfo* asmInfo = scriptFunc->GetFunctionBody()->GetAsmJsFunctionInfo(); |
| uint alignedArgsSize = ::Math::Align<uint32>(asmInfo->GetArgByteSize(), 16); |
| #if _M_X64 && _WIN32 |
| // convention is to always allocate spill space for rcx,rdx,r8,r9 |
| if (alignedArgsSize < 0x20) alignedArgsSize = 0x20; |
| uint* argSizes = asmInfo->GetArgsSizesArray(); |
| Assert(asmInfo->GetArgSizeArrayLength() >= 2); |
| byte* curOutParams = (byte*)m_outParams + sizeof(Var); |
| Assert(curOutParams + argSizes[0] + argSizes[1] + 16 <= (byte*)this->m_outParamsEnd); |
| |
| // Prepare in advance the possible arguments that will need to be put in register |
| byte _declspec(align(16)) reg[3 * 16]; |
| CompileAssert((FunctionBody::MinAsmJsOutParams() * sizeof(Var)) == (sizeof(Var) * 2 + sizeof(reg))); |
| js_memcpy_s(reg, 16, curOutParams, 16); |
| js_memcpy_s(reg + 16, 16, curOutParams + argSizes[0], 16); |
| js_memcpy_s(reg + 32, 16, curOutParams + argSizes[0] + argSizes[1], 16); |
| #else |
| byte* reg = nullptr; |
| #endif |
| |
| ScriptContext * scriptContext = function->GetScriptContext(); |
| Js::FunctionEntryPointInfo* entrypointInfo = (Js::FunctionEntryPointInfo*)function->GetEntryPointInfo(); |
| PROBE_STACK_CALL(scriptContext, function, alignedArgsSize + Js::Constants::MinStackDefault); |
| // Calling the jsMethod might change the entrypoint, adding the variable here |
| // will save the method on the stack helping debug what really got called |
| JavascriptMethod jsMethod = entrypointInfo->jsMethod; |
| |
| switch (asmInfo->GetReturnType().which()) |
| { |
| case AsmJsRetType::Void: |
| JavascriptFunction::CallAsmJsFunction<int>(function, jsMethod, m_outParams, alignedArgsSize, reg); |
| break; |
| case AsmJsRetType::Signed: |
| |
| m_localIntSlots[returnReg] = JavascriptFunction::CallAsmJsFunction<int>(function, jsMethod, m_outParams, alignedArgsSize, reg); |
| break; |
| case AsmJsRetType::Int64: |
| m_localInt64Slots[returnReg] = JavascriptFunction::CallAsmJsFunction<int64>(function, jsMethod, m_outParams, alignedArgsSize, reg); |
| break; |
| case AsmJsRetType::Double: |
| m_localDoubleSlots[returnReg] = JavascriptFunction::CallAsmJsFunction<double>(function, jsMethod, m_outParams, alignedArgsSize, reg); |
| break; |
| case AsmJsRetType::Float: |
| m_localFloatSlots[returnReg] = JavascriptFunction::CallAsmJsFunction<float>(function, jsMethod, m_outParams, alignedArgsSize, reg); |
| break; |
| #ifdef ENABLE_WASM_SIMD |
| case AsmJsRetType::Float32x4: |
| case AsmJsRetType::Int32x4: |
| case AsmJsRetType::Bool32x4: |
| case AsmJsRetType::Bool16x8: |
| case AsmJsRetType::Bool8x16: |
| case AsmJsRetType::Float64x2: |
| case AsmJsRetType::Int16x8: |
| case AsmJsRetType::Int8x16: |
| case AsmJsRetType::Uint32x4: |
| case AsmJsRetType::Uint16x8: |
| case AsmJsRetType::Uint8x16: |
| #if _WIN32 //WASM.SIMD ToDo: Enable thunk for Xplat |
| #if _M_X64 |
| X86SIMDValue simdVal; |
| simdVal.m128_value = JavascriptFunction::CallAsmJsFunction<__m128>(function, jsMethod, m_outParams, alignedArgsSize, reg); |
| m_localSimdSlots[returnReg] = X86SIMDValue::ToSIMDValue(simdVal); |
| #else |
| m_localSimdSlots[returnReg] = JavascriptFunction::CallAsmJsFunction<AsmJsSIMDValue>(function, jsMethod, m_outParams, alignedArgsSize, reg); |
| #endif |
| #endif |
| break; |
| #endif |
| default: |
| Assume(UNREACHED); |
| } |
| |
| const ArgSlot nVarToPop = (asmInfo->GetArgByteSize() / sizeof(Var)) + 1; |
| PopOut(nVarToPop); |
| |
| Assert(function); |
| } |
| #endif |
| |
| template <class T> |
| void InterpreterStackFrame::OP_AsmCall(const unaligned T* playout) |
| { |
| Var target = GetRegRawPtr(playout->Function); |
| |
| RecyclableObject * function = OP_CallGetFunc(target); |
| |
| #if DBG |
| if (this->IsInDebugMode()) |
| { |
| JavascriptFunction::CheckValidDebugThunk(scriptContext, function); |
| } |
| #endif |
| |
| BEGIN_SAFE_REENTRANT_REGION(this->scriptContext->GetThreadContext()) |
| { |
| if (playout->Return == Js::Constants::NoRegister) |
| |
| { |
| Arguments args(CallInfo(CallFlags_NotUsed, playout->ArgCount), m_outParams); |
| JavascriptFunction::CallFunction<true>(function, function->GetEntryPoint(), args); |
| } |
| else |
| { |
| Arguments args(CallInfo(CallFlags_Value, playout->ArgCount), m_outParams); |
| Var retVal = JavascriptFunction::CallFunction<true>(function, function->GetEntryPoint(), args); |
| SetRegRawPtr(playout->Return, retVal); |
| } |
| } |
| END_SAFE_REENTRANT_REGION |
| |
| PopOut(playout->ArgCount); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_EnsureHeapAttached(const unaligned T* playout) |
| { |
| AsmJsModuleInfo::EnsureHeapAttached(this->function); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_CallCommon(const unaligned T * playout, RecyclableObject * function, unsigned flags, const Js::AuxArray<uint32> *spreadIndices) |
| { |
| // Always save and restore implicit call flags when calling out |
| // REVIEW: Can we avoid it if we don't collect dynamic profile info? |
| ThreadContext * threadContext = scriptContext->GetThreadContext(); |
| Js::ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| #if DBG |
| if (this->IsInDebugMode()) |
| { |
| JavascriptFunction::CheckValidDebugThunk(scriptContext, function); |
| } |
| #endif |
| #endif |
| ArgSlot argCount = playout->ArgCount; |
| BEGIN_SAFE_REENTRANT_REGION(threadContext) |
| { |
| if (playout->Return == Js::Constants::NoRegister) |
| { |
| flags |= CallFlags_NotUsed; |
| Arguments args(CallInfo((CallFlags)flags, argCount), m_outParams); |
| AssertMsg(static_cast<unsigned>(args.Info.Flags) == flags, "Flags don't fit into the CallInfo field?"); |
| argCount = args.GetArgCountWithExtraArgs(); |
| if (spreadIndices != nullptr) |
| { |
| JavascriptFunction::CallSpreadFunction(function, args, spreadIndices); |
| } |
| else |
| { |
| JavascriptFunction::CallFunction<true>(function, function->GetEntryPoint(), args); |
| } |
| } |
| else |
| { |
| flags |= CallFlags_Value; |
| Arguments args(CallInfo((CallFlags)flags, argCount), m_outParams); |
| AssertMsg(static_cast<unsigned>(args.Info.Flags) == flags, "Flags don't fit into the CallInfo field?"); |
| argCount = args.GetArgCountWithExtraArgs(); |
| if (spreadIndices != nullptr) |
| { |
| SetReg((RegSlot)playout->Return, JavascriptFunction::CallSpreadFunction(function, args, spreadIndices)); |
| } |
| else |
| { |
| SetReg((RegSlot)playout->Return, JavascriptFunction::CallFunction<true>(function, function->GetEntryPoint(), args)); |
| } |
| } |
| } |
| END_SAFE_REENTRANT_REGION |
| |
| threadContext->SetImplicitCallFlags(savedImplicitCallFlags); |
| PopOut(argCount); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_CallCommonI(const unaligned T * playout, RecyclableObject * function, unsigned flags) |
| { |
| OP_CallCommon(playout, function, flags); // CallCommon doesn't do anything with Member |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| void InterpreterStackFrame::OP_ProfileCallCommon(const unaligned T * playout, RecyclableObject * function, unsigned flags, ProfileId profileId, InlineCacheIndex inlineCacheIndex, const Js::AuxArray<uint32> *spreadIndices) |
| { |
| FunctionBody* functionBody = this->m_functionBody; |
| DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo(); |
| FunctionInfo* functionInfo = function->GetTypeId() == TypeIds_Function ? |
| JavascriptFunction::FromVar(function)->GetFunctionInfo() : nullptr; |
| bool isConstructorCall = (CallFlags_New & flags) == CallFlags_New; |
| dynamicProfileInfo->RecordCallSiteInfo(functionBody, profileId, functionInfo, functionInfo ? static_cast<JavascriptFunction*>(function) : nullptr, playout->ArgCount, isConstructorCall, inlineCacheIndex); |
| OP_CallCommon<T>(playout, function, flags, spreadIndices); |
| if (playout->Return != Js::Constants::NoRegister) |
| { |
| dynamicProfileInfo->RecordReturnTypeOnCallSiteInfo(functionBody, profileId, GetReg((RegSlot)playout->Return)); |
| } |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfileReturnTypeCallCommon(const unaligned T * playout, RecyclableObject * function, unsigned flags, ProfileId profileId, const Js::AuxArray<uint32> *spreadIndices) |
| { |
| OP_CallCommon<T>(playout, function, flags, spreadIndices); |
| FunctionBody* functionBody = this->m_functionBody; |
| DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo(); |
| if (playout->Return != Js::Constants::NoRegister) |
| { |
| dynamicProfileInfo->RecordReturnType(functionBody, profileId, GetReg((RegSlot)playout->Return)); |
| } |
| } |
| #endif |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetRootProperty(unaligned T* playout) |
| { |
| // Same fast path as in the backend. |
| Assert(playout->inlineCacheIndex >= this->m_functionBody->GetRootObjectLoadInlineCacheStart()); |
| Js::Var instance = this->GetRootObject(); |
| |
| InlineCache *inlineCache = this->GetInlineCache(playout->inlineCacheIndex); |
| |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| DynamicObject * obj = DynamicObject::UnsafeFromVar(instance); |
| |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->inlineCacheIndex, true); |
| Var value; |
| if (CacheOperators::TryGetProperty<true, false, false, false, false, false, true, false, false, false>( |
| obj, true, obj, propertyId, &value, GetScriptContext(), nullptr, &info)) |
| { |
| SetReg(playout->Value, value); |
| return; |
| } |
| |
| OP_GetRootProperty_NoFastPath(playout); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetRootPropertyForTypeOf(unaligned T* playout) |
| { |
| Var rootInstance = GetRootObject(); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| |
| Var value = JavascriptOperators::PatchGetRootValueForTypeOf<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| DynamicObject::UnsafeFromVar(rootInstance), |
| propertyId |
| ); |
| |
| SetReg(playout->Value, value); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetRootValue throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(rootInstance, propertyId, value, /*successful:*/true); |
| } |
| #endif |
| } |
| |
| template <class T> |
| _NOINLINE void InterpreterStackFrame::OP_GetRootProperty_NoFastPath(unaligned T* playout) |
| { |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| Var rootInstance = this->GetRootObject(); |
| |
| Var value = JavascriptOperators::PatchGetRootValue<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| DynamicObject::UnsafeFromVar(rootInstance), |
| propertyId |
| ); |
| |
| SetReg(playout->Value, value); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetRootValue throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(rootInstance, propertyId, value, /*successful:*/true); |
| } |
| #endif |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| void InterpreterStackFrame::UpdateFldInfoFlagsForGetSetInlineCandidate(unaligned T* playout, FldInfoFlags& fldInfoFlags, CacheType cacheType, |
| DynamicProfileInfo * dynamicProfileInfo, uint inlineCacheIndex, RecyclableObject * obj) |
| { |
| RecyclableObject *callee = nullptr; |
| //TODO: Setter case once we stop sharing inline caches for these callsites. |
| if ((cacheType & (CacheType_Getter | CacheType_Setter)) && GetInlineCache(inlineCacheIndex)->GetGetterSetter(obj->GetType(), &callee)) |
| { |
| const auto functionBody = this->m_functionBody; |
| bool canInline = dynamicProfileInfo->RecordLdFldCallSiteInfo(functionBody, callee, false /*callApplyTarget*/); |
| if (canInline) |
| { |
| //updates this fldInfoFlags passed by reference. |
| fldInfoFlags = DynamicProfileInfo::MergeFldInfoFlags(fldInfoFlags, FldInfo_InlineCandidate); |
| } |
| } |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::UpdateFldInfoFlagsForCallApplyInlineCandidate(unaligned T* playout, FldInfoFlags& fldInfoFlags, CacheType cacheType, |
| DynamicProfileInfo * dynamicProfileInfo, uint inlineCacheIndex, RecyclableObject * obj) |
| { |
| RecyclableObject *callee = nullptr; |
| if (!(fldInfoFlags & FldInfo_Polymorphic) && GetInlineCache(inlineCacheIndex)->GetCallApplyTarget(obj, &callee)) |
| { |
| const auto functionBody = this->m_functionBody; |
| bool canInline = dynamicProfileInfo->RecordLdFldCallSiteInfo(functionBody, callee, true /*callApplyTarget*/); |
| if (canInline) |
| { |
| //updates this fldInfoFlags passed by reference. |
| fldInfoFlags = DynamicProfileInfo::MergeFldInfoFlags(fldInfoFlags, FldInfo_InlineCandidate); |
| } |
| } |
| } |
| |
| template <class T, bool Root, bool Method, bool CallApplyTarget> |
| void InterpreterStackFrame::ProfiledGetProperty(unaligned T* playout, const Var instance) |
| { |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| Var value = ProfilingHelpers::ProfiledLdFld<Root, Method, CallApplyTarget>( |
| instance, |
| propertyId, |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| GetFunctionBody(), |
| instance); |
| |
| SetReg(playout->Value, value); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetRootValue throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(instance, propertyId, value, /*successful:*/true); |
| } |
| #endif |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetRootProperty(unaligned T* playout) |
| { |
| ProfiledGetProperty<T, true, false, false>(playout, GetRootObject()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetRootPropertyForTypeOf(unaligned T* playout) |
| { |
| Var rootInstance = GetRootObject(); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| Var value = ProfilingHelpers::ProfiledLdFldForTypeOf<true, false, false>( |
| rootInstance, |
| propertyId, |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| GetFunctionBody()); |
| |
| SetReg(playout->Value, value); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetRootValue throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(rootInstance, propertyId, value, /*successful:*/true); |
| } |
| #endif |
| } |
| #endif |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetPropertyForTypeOf(unaligned T* playout) |
| { |
| Var instance = GetReg(playout->Instance); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| Var value = JavascriptOperators::PatchGetValueForTypeOf<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| instance, |
| propertyId |
| ); |
| |
| SetReg(playout->Value, value); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetRootValue throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(instance, propertyId, value, /*successful:*/true); |
| } |
| #endif |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetProperty(unaligned T* playout) |
| { |
| // Same fast path as in the backend. |
| Var instance = GetReg(playout->Instance); |
| OP_GetProperty(instance, playout); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetLocalProperty(unaligned T* playout) |
| { |
| // Same fast path as in the backend. |
| Var instance = this->localClosure; |
| OP_GetProperty(instance, playout); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetProperty(Var instance, unaligned T* playout) |
| { |
| InlineCache *inlineCache = GetInlineCache(playout->inlineCacheIndex); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| RecyclableObject* obj = JavascriptOperators::TryFromVar<RecyclableObject>(instance); |
| if (obj) |
| { |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->inlineCacheIndex, true); |
| |
| Var value; |
| if (CacheOperators::TryGetProperty<true, false, false, false, false, false, true, false, false, false>( |
| obj, false, obj, propertyId, &value, GetScriptContext(), nullptr, &info)) |
| { |
| SetReg(playout->Value, value); |
| return; |
| } |
| } |
| OP_GetProperty_NoFastPath(instance, playout); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_GetSuperProperty(unaligned T* playout) |
| { |
| // Same fast path as in the backend. |
| Var instance = GetReg(playout->Instance); |
| Var thisInstance = GetReg(playout->Value2); |
| InlineCache *inlineCache = GetInlineCache(playout->PropertyIdIndex); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->PropertyIdIndex); |
| if (RecyclableObject::Is(instance) && RecyclableObject::Is(thisInstance)) |
| { |
| RecyclableObject* superObj = RecyclableObject::FromVar(instance); |
| RecyclableObject* thisObj = RecyclableObject::FromVar(thisInstance); |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->PropertyIdIndex, true); |
| |
| Var value; |
| if (CacheOperators::TryGetProperty<true, false, false, false, false, false, true, false, false, false>( |
| thisObj, false, superObj, propertyId, &value, GetScriptContext(), nullptr, &info)) |
| { |
| SetReg(playout->Value, value); |
| return; |
| } |
| } |
| SetReg( |
| playout->Value, |
| JavascriptOperators::PatchGetValueWithThisPtr<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->PropertyIdIndex), |
| playout->PropertyIdIndex, |
| GetReg(playout->Instance), |
| GetPropertyIdFromCacheId(playout->PropertyIdIndex), |
| GetReg(playout->Value2))); |
| } |
| |
| template <class T> |
| _NOINLINE void InterpreterStackFrame::OP_GetProperty_NoFastPath(Var instance, unaligned T* playout) |
| { |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| |
| Var value = JavascriptOperators::PatchGetValue<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| instance, |
| propertyId |
| ); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetMethod throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(instance, propertyId, value, true); |
| } |
| #endif |
| |
| SetReg(playout->Value, value); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetProperty(unaligned T* playout) |
| { |
| ProfiledGetProperty<T, false, false, false>(playout, GetReg(playout->Instance)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetLocalProperty(unaligned T* playout) |
| { |
| ProfiledGetProperty<T, false, false, false>(playout, this->localClosure); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetSuperProperty(unaligned T* playout) |
| { |
| SetReg( |
| playout->Value, |
| ProfilingHelpers::ProfiledLdFld<false, false, false>( |
| GetReg(playout->Instance), |
| GetPropertyIdFromCacheId(playout->PropertyIdIndex), |
| GetInlineCache(playout->PropertyIdIndex), |
| playout->PropertyIdIndex, |
| GetFunctionBody(), |
| GetReg(playout->Value2))); |
| |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetPropertyForTypeOf(unaligned T* playout) |
| { |
| Var instance = GetReg(playout->Instance); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| Var value = ProfilingHelpers::ProfiledLdFldForTypeOf<false, false, false>( |
| instance, |
| propertyId, |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| GetFunctionBody() |
| ); |
| |
| SetReg(playout->Value, value); |
| |
| #ifdef TELEMETRY_INTERPRETER |
| if (TELEMETRY_PROPERTY_OPCODE_FILTER(propertyId)) |
| { |
| // `successful` will be true as PatchGetMethod throws an exception if not found. |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().GetProperty(instance, propertyId, value, true); |
| } |
| #endif |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetPropertyCallApplyTarget(unaligned T* playout) |
| { |
| ProfiledGetProperty<T, false, false, true>(playout, GetReg(playout->Instance)); |
| } |
| #endif |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_GetPropertyScoped(const unaligned OpLayoutT_ElementP<T>* playout) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| // Get the property, using a scope stack rather than an individual instance. |
| // Use the cache |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| FrameDisplay *pScope = this->GetEnvForEvalCode(); |
| InlineCache *inlineCache = this->GetInlineCache(playout->inlineCacheIndex); |
| ScriptContext* scriptContext = GetScriptContext(); |
| int length = pScope->GetLength(); |
| if (1 == length) |
| { |
| RecyclableObject *obj = RecyclableObject::FromVar(pScope->GetItem(0)); |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->inlineCacheIndex, true); |
| Var value; |
| if (CacheOperators::TryGetProperty<true, false, false, false, false, false, true, false, false, false>( |
| obj, false, obj, propertyId, &value, scriptContext, nullptr, &info)) |
| { |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| |
| SetReg(playout->Value, value); |
| return; |
| } |
| } |
| |
| OP_GetPropertyScoped_NoFastPath(playout); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_GetPropertyForTypeOfScoped(const unaligned OpLayoutT_ElementP<T>* playout) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| // Get the property, using a scope stack rather than an individual instance. |
| // Use the cache |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| FrameDisplay *pScope = this->GetEnvForEvalCode(); |
| InlineCache *inlineCache = this->GetInlineCache(playout->inlineCacheIndex); |
| ScriptContext* scriptContext = GetScriptContext(); |
| int length = pScope->GetLength(); |
| if (1 == length) |
| { |
| DynamicObject *obj = (DynamicObject*)pScope->GetItem(0); |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->inlineCacheIndex, true); |
| Var value; |
| if (CacheOperators::TryGetProperty<true, false, false, false, false, false, true, false, false, false>( |
| obj, false, obj, propertyId, &value, scriptContext, nullptr, &info)) |
| { |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| |
| SetReg(playout->Value, value); |
| return; |
| } |
| } |
| |
| SetReg( |
| playout->Value, |
| JavascriptOperators::PatchGetPropertyForTypeOfScoped<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| GetEnvForEvalCode(), |
| GetPropertyIdFromCacheId(playout->inlineCacheIndex), |
| GetReg(Js::FunctionBody::RootObjectRegSlot))); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| |
| |
| template <typename T> |
| _NOINLINE void InterpreterStackFrame::OP_GetPropertyScoped_NoFastPath(const unaligned OpLayoutT_ElementP<T>* playout) |
| { |
| // Implicit root object as default instance |
| Var defaultInstance = GetReg(Js::FunctionBody::RootObjectRegSlot); |
| |
| // PatchGetPropertyScoped doesn't update type and slotIndex if the scope is not an array of length 1. |
| SetReg( |
| playout->Value, |
| JavascriptOperators::PatchGetPropertyScoped<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| GetEnvForEvalCode(), |
| GetPropertyIdFromCacheId(playout->inlineCacheIndex), |
| defaultInstance)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetPropertyScoped(unaligned T* playout, PropertyOperationFlags flags) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| // Set the property, using a scope stack rather than an individual instance. |
| // Use the cache |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| FrameDisplay *pScope = this->GetEnvForEvalCode(); |
| InlineCache *inlineCache = GetInlineCache(playout->inlineCacheIndex); |
| ScriptContext* scriptContext = GetScriptContext(); |
| Var value = GetReg(playout->Value); |
| |
| int length = pScope->GetLength(); |
| if (1 == length) |
| { |
| RecyclableObject* obj = RecyclableObject::FromVar(pScope->GetItem(0)); |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->inlineCacheIndex, true); |
| if (CacheOperators::TrySetProperty<true, false, false, false, false, true, false, false>( |
| obj, false, propertyId, value, scriptContext, flags, nullptr, &info)) |
| { |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| return; |
| } |
| } |
| |
| OP_SetPropertyScoped_NoFastPath(playout, flags); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| |
| template <class T> |
| _NOINLINE void InterpreterStackFrame::OP_SetPropertyScoped_NoFastPath(unaligned T* playout, PropertyOperationFlags flags) |
| { |
| // Implicit root object as default instance |
| Var defaultInstance = GetReg(Js::FunctionBody::RootObjectRegSlot); |
| |
| // PatchSetPropertyScoped doesn't update type and slotIndex if the scope is not an array of length 1. |
| JavascriptOperators::PatchSetPropertyScoped<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| GetEnvForEvalCode(), |
| GetPropertyIdFromCacheId(playout->inlineCacheIndex), |
| GetReg(playout->Value), |
| defaultInstance, |
| flags); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetPropertyScopedStrict(unaligned T* playout) |
| { |
| OP_SetPropertyScoped(playout, PropertyOperation_StrictMode); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ConsoleSetPropertyScoped(unaligned T* playout) |
| { |
| OP_SetPropertyScoped(playout, PropertyOperation_AllowUndeclInConsoleScope); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ConsoleSetPropertyScopedStrict(unaligned T* playout) |
| { |
| OP_SetPropertyScoped(playout, (PropertyOperationFlags)(PropertyOperation_StrictMode | PropertyOperation_AllowUndeclInConsoleScope)); |
| } |
| |
| template <class T> |
| inline bool InterpreterStackFrame::TrySetPropertyLocalFastPath(unaligned T* playout, PropertyId pid, RecyclableObject* instance, InlineCache*& inlineCache, PropertyOperationFlags flags) |
| { |
| inlineCache = this->GetInlineCache(playout->inlineCacheIndex); |
| |
| PropertyValueInfo info; |
| PropertyValueInfo::SetCacheInfo(&info, GetFunctionBody(), inlineCache, playout->inlineCacheIndex, true); |
| return |
| CacheOperators::TrySetProperty<true, false, false, false, false, true, false, false>( |
| instance, |
| !!(flags & PropertyOperation_Root), |
| pid, |
| GetReg(playout->Value), |
| GetScriptContext(), |
| flags, |
| nullptr, |
| &info); |
| } |
| |
| template <class T> |
| inline void InterpreterStackFrame::DoSetProperty(unaligned T* playout, Var instance, PropertyOperationFlags flags) |
| { |
| // Same fast path as in the backend. |
| |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| InlineCache *inlineCache; |
| |
| if (!TaggedNumber::Is(instance) |
| && TrySetPropertyLocalFastPath(playout, propertyId, RecyclableObject::UnsafeFromVar(instance), inlineCache, flags)) |
| { |
| if (GetJavascriptFunction()->GetConstructorCache()->NeedsUpdateAfterCtor()) |
| { |
| // This function has only 'this' statements and is being used as a constructor. When the constructor exits, the |
| // function object's constructor cache will be updated with the type produced by the constructor. From that |
| // point on, when the same function object is used as a constructor, the a new object with the final type will |
| // be created. Whatever is stored in the inline cache currently will cause cache misses after the constructor |
| // cache update. So, just clear it now so that the caches won't be flagged as polymorphic. |
| inlineCache->RemoveFromInvalidationListAndClear(this->GetScriptContext()->GetThreadContext()); |
| } |
| return; |
| } |
| |
| DoSetProperty_NoFastPath(playout, instance, flags); |
| } |
| |
| |
| template <class T> |
| inline void InterpreterStackFrame::DoSetSuperProperty(unaligned T* playout, Var instance, PropertyOperationFlags flags) |
| { |
| DoSetSuperProperty_NoFastPath(playout, instance, m_functionBody->GetIsStrictMode() ? |
| (PropertyOperationFlags)(flags | PropertyOperation_StrictMode) : flags); |
| } |
| |
| template <class T> |
| _NOINLINE void InterpreterStackFrame::DoSetProperty_NoFastPath(unaligned T* playout, Var instance, PropertyOperationFlags flags) |
| { |
| #if ENABLE_COPYONACCESS_ARRAY |
| JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(instance); |
| #endif |
| InlineCache *const inlineCache = GetInlineCache(playout->inlineCacheIndex); |
| |
| const auto PatchPutRootValue = &JavascriptOperators::PatchPutRootValueNoLocalFastPath<false, InlineCache>; |
| const auto PatchPutValue = &JavascriptOperators::PatchPutValueNoLocalFastPath<false, InlineCache>; |
| const auto PatchPut = flags & PropertyOperation_Root ? PatchPutRootValue : PatchPutValue; |
| PatchPut( |
| GetFunctionBody(), |
| inlineCache, |
| playout->inlineCacheIndex, |
| instance, |
| GetPropertyIdFromCacheId(playout->inlineCacheIndex), |
| GetReg(playout->Value), |
| flags); |
| |
| if (!TaggedNumber::Is(instance) && GetJavascriptFunction()->GetConstructorCache()->NeedsUpdateAfterCtor()) |
| { |
| // This function has only 'this' statements and is being used as a constructor. When the constructor exits, the |
| // function object's constructor cache will be updated with the type produced by the constructor. From that |
| // point on, when the same function object is used as a constructor, the a new object with the final type will |
| // be created. Whatever is stored in the inline cache currently will cause cache misses after the constructor |
| // cache update. So, just clear it now so that the caches won't be flagged as polymorphic. |
| inlineCache->RemoveFromInvalidationListAndClear(this->GetScriptContext()->GetThreadContext()); |
| } |
| } |
| |
| template <class T> |
| _NOINLINE void InterpreterStackFrame::DoSetSuperProperty_NoFastPath(unaligned T* playout, Var instance, PropertyOperationFlags flags) |
| { |
| #if ENABLE_COPYONACCESS_ARRAY |
| JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(instance); |
| #endif |
| InlineCache *const inlineCache = GetInlineCache(playout->PropertyIdIndex); |
| |
| JavascriptOperators::PatchPutValueWithThisPtrNoLocalFastPath<false, InlineCache>( |
| GetFunctionBody(), |
| inlineCache, |
| playout->PropertyIdIndex, |
| instance, |
| GetPropertyIdFromCacheId(playout->PropertyIdIndex), |
| GetReg(playout->Value), |
| GetReg(playout->Value2), |
| flags); |
| |
| if (!TaggedNumber::Is(instance) && GetJavascriptFunction()->GetConstructorCache()->NeedsUpdateAfterCtor()) |
| { |
| // This function has only 'this' statements and is being used as a constructor. When the constructor exits, the |
| // function object's constructor cache will be updated with the type produced by the constructor. From that |
| // point on, when the same function object is used as a constructor, the a new object with the final type will |
| // be created. Whatever is stored in the inline cache currently will cause cache misses after the constructor |
| // cache update. So, just clear it now so that the caches won't be flagged as polymorphic. |
| inlineCache->RemoveFromInvalidationListAndClear(this->GetScriptContext()->GetThreadContext()); |
| } |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T, bool Root> |
| void InterpreterStackFrame::ProfiledSetProperty(unaligned T* playout, Var instance, PropertyOperationFlags flags) |
| { |
| Assert(!Root || flags & PropertyOperation_Root); |
| |
| ProfilingHelpers::ProfiledStFld<Root>( |
| instance, |
| GetPropertyIdFromCacheId(playout->inlineCacheIndex), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| GetReg(playout->Value), |
| flags, |
| GetJavascriptFunction(), |
| instance); |
| } |
| |
| template <class T, bool Root> |
| void InterpreterStackFrame::ProfiledSetSuperProperty(unaligned T* playout, Var instance, Var thisInstance, PropertyOperationFlags flags) |
| { |
| Assert(!Root || flags & PropertyOperation_Root); |
| |
| ProfilingHelpers::ProfiledStFld<Root>( |
| instance, |
| GetPropertyIdFromCacheId(playout->PropertyIdIndex), |
| GetInlineCache(playout->PropertyIdIndex), |
| playout->PropertyIdIndex, |
| GetReg(playout->Value), |
| m_functionBody->GetIsStrictMode() ? |
| (PropertyOperationFlags)(flags | PropertyOperation_StrictMode) : flags, |
| GetJavascriptFunction(), |
| thisInstance); |
| } |
| #endif |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetProperty(unaligned T* playout) |
| { |
| DoSetProperty(playout, GetReg(playout->Instance), PropertyOperation_None); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetLocalProperty(unaligned T* playout) |
| { |
| DoSetProperty(playout, this->localClosure, PropertyOperation_None); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetSuperProperty(unaligned T* playout) |
| { |
| DoSetSuperProperty(playout, GetReg(playout->Instance), PropertyOperation_None); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledSetProperty(unaligned T* playout) |
| { |
| ProfiledSetProperty<T, false>(playout, GetReg(playout->Instance), PropertyOperation_None); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledSetLocalProperty(unaligned T* playout) |
| { |
| ProfiledSetProperty<T, false>(playout, this->localClosure, PropertyOperation_None); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledSetSuperProperty(unaligned T* playout) |
| { |
| ProfiledSetSuperProperty<T, false>(playout, GetReg(playout->Instance), GetReg(playout->Value2), PropertyOperation_None); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetRootProperty(unaligned T* playout) |
| { |
| DoSetProperty(playout, this->GetRootObject(), PropertyOperation_Root); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledSetRootProperty(unaligned T* playout) |
| { |
| ProfiledSetProperty<T, true>(playout, this->GetRootObject(), PropertyOperation_Root); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetPropertyStrict(unaligned T* playout) |
| { |
| DoSetProperty(playout, GetReg(playout->Instance), PropertyOperation_StrictMode); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledSetPropertyStrict(unaligned T* playout) |
| { |
| ProfiledSetProperty<T, false>(playout, GetReg(playout->Instance), PropertyOperation_StrictMode); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetRootPropertyStrict(unaligned T* playout) |
| { |
| DoSetProperty(playout, this->GetRootObject(), PropertyOperation_StrictModeRoot); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledSetRootPropertyStrict(unaligned T* playout) |
| { |
| ProfiledSetProperty<T, true>(playout, this->GetRootObject(), PropertyOperation_StrictModeRoot); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <bool doProfile> |
| Var InterpreterStackFrame::ProfiledDivide(Var aLeft, Var aRight, ScriptContext* scriptContext, ProfileId profileId) |
| { |
| Var result = JavascriptMath::Divide(aLeft, aRight, scriptContext); |
| |
| if (doProfile) |
| { |
| Js::FunctionBody* body = this->m_functionBody; |
| body->GetDynamicProfileInfo()->RecordDivideResultType(body, profileId, result); |
| } |
| |
| return result; |
| } |
| |
| template <bool doProfile> |
| Var InterpreterStackFrame::ProfileModulus(Var aLeft, Var aRight, ScriptContext* scriptContext, ProfileId profileId) |
| { |
| // If both arguments are TaggedInt, then try to do integer division |
| // This case is not handled by the lowerer. |
| if (doProfile) |
| { |
| Js::FunctionBody* body = this->function->GetFunctionBody(); |
| if (TaggedInt::IsPair(aLeft, aRight)) |
| { |
| int nLeft = TaggedInt::ToInt32(aLeft); |
| int nRight = TaggedInt::ToInt32(aRight); |
| |
| // nLeft is positive and nRight is +2^i |
| // Fast path for Power of 2 divisor |
| if (nLeft > 0 && ::Math::IsPow2(nRight)) |
| { |
| body->GetDynamicProfileInfo()->RecordModulusOpType(body, profileId, /*isModByPowerOf2*/ true); |
| return TaggedInt::ToVarUnchecked(nLeft & (nRight - 1)); |
| } |
| } |
| body->GetDynamicProfileInfo()->RecordModulusOpType(body, profileId, /*isModByPowerOf2*/ false); |
| } |
| |
| return JavascriptMath::Modulus(aLeft, aRight, scriptContext); |
| } |
| |
| template <bool doProfile> |
| Var InterpreterStackFrame::ProfiledSwitch(Var exp, ProfileId profileId) |
| { |
| if (doProfile) |
| { |
| Js::FunctionBody* body = this->m_functionBody; |
| body->GetDynamicProfileInfo()->RecordSwitchType(body, profileId, exp); |
| } |
| |
| return exp; |
| } |
| #else |
| template <bool doProfile> |
| Var InterpreterStackFrame::ProfiledDivide(Var aLeft, Var aRight, ScriptContext* scriptContext, ProfileId profileId) |
| { |
| Assert(!doProfile); |
| return JavascriptMath::Divide(aLeft, aRight, scriptContext); |
| } |
| |
| template <bool doProfile> |
| Var InterpreterStackFrame::ProfileModulus(Var aLeft, Var aRight, ScriptContext* scriptContext, ProfileId profileId) |
| { |
| Assert(!doProfile); |
| return JavascriptMath::Modulus(aLeft, aRight, scriptContext); |
| } |
| |
| template <bool doProfile> |
| Var InterpreterStackFrame::ProfiledSwitch(Var exp, ProfileId profileId) |
| { |
| Assert(!doProfile); |
| return exp; |
| } |
| #endif |
| |
| template <class T> |
| void InterpreterStackFrame::DoInitProperty(unaligned T* playout, Var instance) |
| { |
| // Same fast path as in the backend. |
| |
| InlineCache *inlineCache = nullptr; |
| |
| Assert(!TaggedNumber::Is(instance)); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| if (TrySetPropertyLocalFastPath(playout, propertyId, RecyclableObject::UnsafeFromVar(instance), inlineCache)) |
| { |
| return; |
| } |
| |
| DoInitProperty_NoFastPath(playout, instance); |
| } |
| |
| template <class T> |
| _NOINLINE void InterpreterStackFrame::DoInitProperty_NoFastPath(unaligned T* playout, Var instance) |
| { |
| JavascriptOperators::PatchInitValue<false>( |
| GetFunctionBody(), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| RecyclableObject::FromVar(instance), |
| GetPropertyIdFromCacheId(playout->inlineCacheIndex), |
| GetReg(playout->Value)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitClassMember(const unaligned T * playout) |
| { |
| uint inlineCacheIndex = playout->inlineCacheIndex; |
| InlineCache * inlineCache = this->GetInlineCache(inlineCacheIndex); |
| Var instance = GetReg(playout->Instance); |
| PropertyOperationFlags flags = PropertyOperation_None; |
| |
| Assert(!TaggedNumber::Is(instance)); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| if (!TrySetPropertyLocalFastPath(playout, propertyId, RecyclableObject::UnsafeFromVar(instance), inlineCache, flags)) |
| { |
| JavascriptOperators::OP_InitClassMember(instance, propertyId, GetReg(playout->Value)); |
| } |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitClassMemberGet(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitClassMemberGet( |
| GetReg(playout->Instance), |
| m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), |
| GetReg(playout->Value)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitClassMemberSet(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitClassMemberSet( |
| GetReg(playout->Instance), |
| m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), |
| GetReg(playout->Value)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitClassMemberSetComputedName(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitClassMemberSetComputedName( |
| GetReg(playout->Instance), |
| GetReg(playout->Element), |
| GetReg(playout->Value), |
| m_functionBody->GetScriptContext()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitClassMemberGetComputedName(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitClassMemberGetComputedName( |
| GetReg(playout->Instance), |
| GetReg(playout->Element), |
| GetReg(playout->Value), |
| m_functionBody->GetScriptContext()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitClassMemberComputedName(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitClassMemberComputedName( |
| GetReg(playout->Instance), |
| GetReg(playout->Element), |
| GetReg(playout->Value), |
| m_functionBody->GetScriptContext()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::DoInitLetFld(const unaligned T * playout, Var instance, PropertyOperationFlags flags) |
| { |
| uint inlineCacheIndex = playout->inlineCacheIndex; |
| InlineCache * inlineCache = this->GetInlineCache(inlineCacheIndex); |
| |
| Assert(!TaggedNumber::Is(instance)); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| if (!TrySetPropertyLocalFastPath(playout, propertyId, RecyclableObject::UnsafeFromVar(instance), inlineCache, flags)) |
| { |
| JavascriptOperators::OP_InitLetProperty(instance, propertyId, GetReg(playout->Value)); |
| } |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::DoInitConstFld(const unaligned T * playout, Var instance, PropertyOperationFlags flags) |
| { |
| uint inlineCacheIndex = playout->inlineCacheIndex; |
| InlineCache * inlineCache = this->GetInlineCache(inlineCacheIndex); |
| |
| Assert(!TaggedNumber::Is(instance)); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| if (!TrySetPropertyLocalFastPath(playout, propertyId, RecyclableObject::UnsafeFromVar(instance), inlineCache, flags)) |
| { |
| JavascriptOperators::OP_InitConstProperty(instance, propertyId, GetReg(playout->Value)); |
| } |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitProperty(unaligned T* playout) |
| { |
| DoInitProperty(playout, GetReg(playout->Instance)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitLocalProperty(unaligned T* playout) |
| { |
| DoInitProperty(playout, this->localClosure); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitInnerFld(const unaligned T* playout) |
| { |
| DoInitProperty(playout, InnerScopeFromIndex(playout->scopeIndex)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitLetFld(const unaligned T * playout) |
| { |
| DoInitLetFld(playout, GetReg(playout->Instance)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitInnerLetFld(const unaligned T * playout) |
| { |
| DoInitLetFld(playout, InnerScopeFromIndex(playout->scopeIndex)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitLocalLetFld(const unaligned T * playout) |
| { |
| DoInitLetFld(playout, this->localClosure); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitConstFld(const unaligned T * playout) |
| { |
| DoInitConstFld(playout, GetReg(playout->Instance)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitRootProperty(unaligned T* playout) |
| { |
| Assert(playout->inlineCacheIndex >= this->m_functionBody->GetRootObjectLoadInlineCacheStart()); |
| |
| DoInitProperty(playout, this->GetRootObject()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitRootLetFld(const unaligned T * playout) |
| { |
| Assert(playout->inlineCacheIndex >= this->m_functionBody->GetRootObjectLoadInlineCacheStart()); |
| |
| DoInitLetFld(playout, this->GetRootObject(), PropertyOperation_Root); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitRootConstFld(const unaligned T * playout) |
| { |
| Assert(playout->inlineCacheIndex >= this->m_functionBody->GetRootObjectLoadInlineCacheStart()); |
| |
| DoInitConstFld(playout, this->GetRootObject(), PropertyOperation_Root); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitUndeclLetProperty(unaligned T* playout) |
| { |
| Var instance = InnerScopeFromIndex(playout->scopeIndex); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| JavascriptOperators::OP_InitLetProperty(instance, propertyId, this->scriptContext->GetLibrary()->GetUndeclBlockVar()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitUndeclLocalLetProperty(unaligned T* playout) |
| { |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| JavascriptOperators::OP_InitLetProperty(this->localClosure, propertyId, this->scriptContext->GetLibrary()->GetUndeclBlockVar()); |
| } |
| |
| void InterpreterStackFrame::OP_InitUndeclRootLetProperty(uint propertyIdIndex) |
| { |
| Var instance = this->GetRootObject(); |
| PropertyId propertyId = this->m_functionBody->GetReferencedPropertyId(propertyIdIndex); |
| JavascriptOperators::OP_InitUndeclRootLetProperty(instance, propertyId); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitUndeclConstProperty(unaligned T* playout) |
| { |
| Var instance = InnerScopeFromIndex(playout->scopeIndex); |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| JavascriptOperators::OP_InitConstProperty(instance, propertyId, this->scriptContext->GetLibrary()->GetUndeclBlockVar()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitUndeclLocalConstProperty(unaligned T* playout) |
| { |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| JavascriptOperators::OP_InitConstProperty(this->localClosure, propertyId, this->scriptContext->GetLibrary()->GetUndeclBlockVar()); |
| } |
| |
| void InterpreterStackFrame::OP_InitUndeclRootConstProperty(uint propertyIdIndex) |
| { |
| Var instance = this->GetRootObject(); |
| PropertyId propertyId = this->m_functionBody->GetReferencedPropertyId(propertyIdIndex); |
| JavascriptOperators::OP_InitUndeclRootConstProperty(instance, propertyId); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitUndeclConsoleLetProperty(unaligned T* playout) |
| { |
| FrameDisplay* pScope = (FrameDisplay*)this->LdEnv(); |
| AssertMsg(ConsoleScopeActivationObject::Is((DynamicObject*)pScope->GetItem(pScope->GetLength() - 1)), "How come we got this opcode without ConsoleScopeActivationObject?"); |
| PropertyId propertyId = m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex); |
| JavascriptOperators::OP_InitLetProperty(pScope->GetItem(0), propertyId, this->scriptContext->GetLibrary()->GetUndeclBlockVar()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitUndeclConsoleConstProperty(unaligned T* playout) |
| { |
| FrameDisplay* pScope = (FrameDisplay*)this->LdEnv(); |
| AssertMsg(ConsoleScopeActivationObject::Is((DynamicObject*)pScope->GetItem(pScope->GetLength() - 1)), "How come we got this opcode without ConsoleScopeActivationObject?"); |
| PropertyId propertyId = m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex); |
| JavascriptOperators::OP_InitConstProperty(pScope->GetItem(0), propertyId, this->scriptContext->GetLibrary()->GetUndeclBlockVar()); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| void InterpreterStackFrame::ProfiledInitProperty(unaligned T* playout, Var instance) |
| { |
| ProfilingHelpers::ProfiledInitFld( |
| RecyclableObject::FromVar(instance), |
| GetPropertyIdFromCacheId(playout->inlineCacheIndex), |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| GetReg(playout->Value), |
| GetFunctionBody()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledInitProperty(unaligned T* playout) |
| { |
| ProfiledInitProperty(playout, GetReg(playout->Instance)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledInitLocalProperty(unaligned T* playout) |
| { |
| ProfiledInitProperty(playout, this->localClosure); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledInitRootProperty(unaligned T* playout) |
| { |
| ProfiledInitProperty(playout, this->GetRootObject()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledGetElementI(const unaligned OpLayoutDynamicProfile<T>* playout) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| SetReg( |
| playout->Value, |
| ProfilingHelpers::ProfiledLdElem( |
| GetReg(playout->Instance), |
| GetReg(playout->Element), |
| m_functionBody, |
| playout->profileId, |
| this->TestFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArrayAccessHelperCall), |
| this->TestFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArraySpecialization))); |
| |
| this->ClearFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArrayAccessHelperCall | InterpreterStackFrameFlags_ProcessingBailOutOnArraySpecialization); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| #endif |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_GetElementI(const unaligned T* playout) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| // Same fast path as in the backend. |
| |
| Var instance = GetReg(playout->Instance); |
| |
| // Only enable fast path if the javascript array is not cross site |
| Var element; |
| #if ENABLE_PROFILE_INFO |
| if (!TaggedNumber::Is(instance) && VirtualTableInfo<JavascriptArray>::HasVirtualTable(instance)) |
| { |
| element = |
| ProfilingHelpers::ProfiledLdElem_FastPath( |
| JavascriptArray::UnsafeFromVar(instance), |
| GetReg(playout->Element), |
| GetScriptContext()); |
| } |
| else |
| #endif |
| { |
| element = JavascriptOperators::OP_GetElementI(instance, GetReg(playout->Element), GetScriptContext()); |
| } |
| |
| this->ClearFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArrayAccessHelperCall); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| |
| SetReg(playout->Value, element); |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_SetElementI(const unaligned T* playout, PropertyOperationFlags flags) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| // Same fast path as in the backend. |
| |
| Var instance = GetReg(playout->Instance); |
| const Var varIndex = GetReg(playout->Element); |
| const Var value = GetReg(playout->Value); |
| |
| #if ENABLE_PROFILE_INFO |
| // Only enable fast path if the javascript array is not cross site |
| if (!TaggedNumber::Is(instance) && |
| VirtualTableInfo<JavascriptArray>::HasVirtualTable(instance) && |
| !JavascriptOperators::SetElementMayHaveImplicitCalls(GetScriptContext())) |
| { |
| ProfilingHelpers::ProfiledStElem_FastPath( |
| JavascriptArray::UnsafeFromVar(instance), |
| varIndex, |
| value, |
| GetScriptContext(), |
| flags); |
| } |
| else |
| #endif |
| { |
| JavascriptOperators::OP_SetElementI(instance, varIndex, value, GetScriptContext(), flags); |
| } |
| |
| this->ClearFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArrayAccessHelperCall); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <typename T> |
| void InterpreterStackFrame::OP_ProfiledSetElementI( |
| const unaligned OpLayoutDynamicProfile<T>* playout, |
| PropertyOperationFlags flags) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| ProfilingHelpers::ProfiledStElem( |
| GetReg(playout->Instance), |
| GetReg(playout->Element), |
| GetReg(playout->Value), |
| m_functionBody, |
| playout->profileId, |
| flags, |
| this->TestFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArrayAccessHelperCall), |
| this->TestFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArraySpecialization)); |
| |
| this->ClearFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArrayAccessHelperCall | InterpreterStackFrameFlags_ProcessingBailOutOnArraySpecialization); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| #endif |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_SetElementIStrict(const unaligned T* playout) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| OP_SetElementI(playout, PropertyOperation_StrictMode); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <typename T> |
| void InterpreterStackFrame::OP_ProfiledSetElementIStrict(const unaligned OpLayoutDynamicProfile<T>* playout) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| OP_ProfiledSetElementI(playout, PropertyOperation_StrictMode); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| #endif |
| |
| template <class T> |
| void InterpreterStackFrame::OP_LdArrayHeadSegment(const unaligned T* playout) |
| { |
| JavascriptArray* array = JavascriptArray::FromAnyArray(GetReg(playout->R1)); |
| |
| // The array is create by the built-in on the same script context |
| Assert(array->GetScriptContext() == GetScriptContext()); |
| |
| SetNonVarReg(playout->R0, array->GetHead()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetArraySegmentItem_CI4(const unaligned T* playout) |
| { |
| SparseArraySegment<Var> * segment = (SparseArraySegment<Var> *)GetNonVarReg(playout->Instance); |
| |
| uint32 index = playout->Element; |
| Var value = GetReg(playout->Value); |
| |
| Assert(segment->left == 0); |
| Assert(index < segment->length); |
| |
| segment->elements[index] = value; |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_NewScArray(const unaligned T * playout) |
| { |
| JavascriptArray *arr; |
| arr = scriptContext->GetLibrary()->CreateArrayLiteral(playout->C1); |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| arr->CheckForceES5Array(); |
| #endif |
| |
| SetReg(playout->R0, arr); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <bool Profiled, class T> |
| void InterpreterStackFrame::ProfiledNewScArray(const unaligned OpLayoutDynamicProfile<T> * playout) |
| { |
| if (!Profiled && !isAutoProfiling) |
| { |
| OP_NewScArray(playout); |
| return; |
| } |
| |
| SetReg( |
| playout->R0, |
| ProfilingHelpers::ProfiledNewScArray( |
| playout->C1, |
| m_functionBody, |
| playout->profileId)); |
| } |
| #else |
| template <bool Profiled, class T> |
| void InterpreterStackFrame::ProfiledNewScArray(const unaligned OpLayoutDynamicProfile<T> * playout) |
| { |
| Assert(!Profiled); |
| OP_NewScArray(playout); |
| } |
| #endif |
| |
| void InterpreterStackFrame::OP_NewScIntArray(const unaligned OpLayoutAuxiliary * playout) |
| { |
| const Js::AuxArray<int32> *ints = Js::ByteCodeReader::ReadAuxArray<int32>(playout->Offset, this->GetFunctionBody()); |
| |
| JavascriptNativeIntArray *arr = scriptContext->GetLibrary()->CreateNativeIntArrayLiteral(ints->count); |
| |
| SparseArraySegment<int32> * segment = (SparseArraySegment<int32>*)arr->GetHead(); |
| |
| JavascriptOperators::AddIntsToArraySegment(segment, ints); |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| arr->CheckForceES5Array(); |
| #endif |
| |
| SetReg(playout->R0, arr); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <bool Profiled> |
| void InterpreterStackFrame::ProfiledNewScIntArray(const unaligned OpLayoutDynamicProfile<OpLayoutAuxiliary> * playout) |
| { |
| if (!Profiled && !isAutoProfiling) |
| { |
| OP_NewScIntArray(playout); |
| return; |
| } |
| |
| const Js::AuxArray<int32> *ints = Js::ByteCodeReader::ReadAuxArray<int32>(playout->Offset, this->GetFunctionBody()); |
| |
| Js::ProfileId profileId = playout->profileId; |
| FunctionBody *functionBody = this->m_functionBody; |
| ArrayCallSiteInfo *arrayInfo = functionBody->GetDynamicProfileInfo()->GetArrayCallSiteInfo(functionBody, profileId); |
| Assert(arrayInfo); |
| |
| JavascriptArray *arr; |
| if (arrayInfo && arrayInfo->IsNativeIntArray()) |
| { |
| #if ENABLE_COPYONACCESS_ARRAY |
| JavascriptLibrary *lib = scriptContext->GetLibrary(); |
| |
| if (JavascriptLibrary::IsCopyOnAccessArrayCallSite(lib, arrayInfo, ints->count)) |
| { |
| Assert(lib->cacheForCopyOnAccessArraySegments); |
| arr = scriptContext->GetLibrary()->CreateCopyOnAccessNativeIntArrayLiteral(arrayInfo, functionBody, ints); |
| } |
| else |
| #endif |
| { |
| arr = scriptContext->GetLibrary()->CreateNativeIntArrayLiteral(ints->count); |
| SparseArraySegment<int32> *segment = (SparseArraySegment<int32>*)arr->GetHead(); |
| JavascriptOperators::AddIntsToArraySegment(segment, ints); |
| } |
| |
| JavascriptNativeIntArray *intArray = reinterpret_cast<JavascriptNativeIntArray*>(arr); |
| Recycler *recycler = scriptContext->GetRecycler(); |
| intArray->SetArrayCallSite(profileId, recycler->CreateWeakReferenceHandle(functionBody)); |
| } |
| else if (arrayInfo && arrayInfo->IsNativeFloatArray()) |
| { |
| arr = scriptContext->GetLibrary()->CreateNativeFloatArrayLiteral(ints->count); |
| SparseArraySegment<double> * segment = (SparseArraySegment<double>*)arr->GetHead(); |
| for (uint i = 0; i < ints->count; i++) |
| { |
| segment->elements[i] = (double)ints->elements[i]; |
| } |
| |
| JavascriptNativeFloatArray *floatArray = reinterpret_cast<JavascriptNativeFloatArray*>(arr); |
| Recycler *recycler = scriptContext->GetRecycler(); |
| floatArray->SetArrayCallSite(profileId, recycler->CreateWeakReferenceHandle(functionBody)); |
| } |
| else |
| { |
| arr = scriptContext->GetLibrary()->CreateArrayLiteral(ints->count); |
| SparseArraySegment<Var> * segment = (SparseArraySegment<Var>*)arr->GetHead(); |
| for (uint i = 0; i < ints->count; i++) |
| { |
| segment->elements[i] = JavascriptNumber::ToVar(ints->elements[i], scriptContext); |
| } |
| } |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| arr->CheckForceES5Array(); |
| #endif |
| |
| SetReg(playout->R0, arr); |
| } |
| #else |
| template <bool Profiled> |
| void InterpreterStackFrame::ProfiledNewScIntArray(const unaligned OpLayoutDynamicProfile<OpLayoutAuxiliary> * playout) |
| { |
| OP_NewScIntArray(playout); |
| } |
| #endif |
| |
| void InterpreterStackFrame::OP_NewScFltArray(const unaligned OpLayoutAuxiliary * playout) |
| { |
| const Js::AuxArray<double> *doubles = Js::ByteCodeReader::ReadAuxArray<double>(playout->Offset, this->GetFunctionBody()); |
| |
| JavascriptNativeFloatArray *arr = scriptContext->GetLibrary()->CreateNativeFloatArrayLiteral(doubles->count); |
| |
| SparseArraySegment<double> * segment = (SparseArraySegment<double>*)arr->GetHead(); |
| |
| JavascriptOperators::AddFloatsToArraySegment(segment, doubles); |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| arr->CheckForceES5Array(); |
| #endif |
| |
| SetReg(playout->R0, arr); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <bool Profiled> |
| void InterpreterStackFrame::ProfiledNewScFltArray(const unaligned OpLayoutDynamicProfile<OpLayoutAuxiliary> * playout) |
| { |
| if (!Profiled && !isAutoProfiling) |
| { |
| OP_NewScFltArray(playout); |
| return; |
| } |
| |
| const Js::AuxArray<double> *doubles = Js::ByteCodeReader::ReadAuxArray<double>(playout->Offset, this->GetFunctionBody()); |
| |
| Js::ProfileId profileId = playout->profileId; |
| FunctionBody *functionBody = this->m_functionBody; |
| ArrayCallSiteInfo *arrayInfo = functionBody->GetDynamicProfileInfo()->GetArrayCallSiteInfo(functionBody, profileId); |
| Assert(arrayInfo); |
| |
| JavascriptArray *arr; |
| if (arrayInfo && arrayInfo->IsNativeFloatArray()) |
| { |
| arrayInfo->SetIsNotNativeIntArray(); |
| arr = scriptContext->GetLibrary()->CreateNativeFloatArrayLiteral(doubles->count); |
| SparseArraySegment<double> * segment = (SparseArraySegment<double>*)arr->GetHead(); |
| JavascriptOperators::AddFloatsToArraySegment(segment, doubles); |
| |
| JavascriptNativeFloatArray *floatArray = reinterpret_cast<JavascriptNativeFloatArray*>(arr); |
| Recycler *recycler = scriptContext->GetRecycler(); |
| floatArray->SetArrayCallSite(profileId, recycler->CreateWeakReferenceHandle(functionBody)); |
| } |
| else |
| { |
| arr = scriptContext->GetLibrary()->CreateArrayLiteral(doubles->count); |
| SparseArraySegment<Var> * segment = (SparseArraySegment<Var>*)arr->GetHead(); |
| for (uint i = 0; i < doubles->count; i++) |
| { |
| segment->elements[i] = JavascriptNumber::ToVarNoCheck(doubles->elements[i], scriptContext); |
| } |
| } |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| arr->CheckForceES5Array(); |
| #endif |
| |
| SetReg(playout->R0, arr); |
| } |
| #else |
| template <bool Profiled> |
| void InterpreterStackFrame::ProfiledNewScFltArray(const unaligned OpLayoutDynamicProfile<OpLayoutAuxiliary> * playout) |
| { |
| OP_NewScFltArray(playout); |
| } |
| #endif |
| |
| void InterpreterStackFrame::OP_SetArraySegmentVars(const unaligned OpLayoutAuxiliary * playout) |
| { |
| const Js::VarArray *vars = Js::ByteCodeReader::ReadAuxArray<Var>(playout->Offset, this->GetFunctionBody()); |
| |
| SparseArraySegment<Var> * segment = (SparseArraySegment<Var> *)GetNonVarReg(playout->R0); |
| |
| JavascriptOperators::AddVarsToArraySegment(segment, vars); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetArrayItemC_CI4(const unaligned T* playout) |
| { |
| JavascriptArray* array = JavascriptArray::FromAnyArray(GetReg(playout->Instance)); |
| uint32 index = playout->Element; |
| Var value = GetReg(playout->Value); |
| |
| #if ENABLE_COPYONACCESS_ARRAY |
| JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(value); |
| #endif |
| |
| // The array is create by the built-in on the same script context |
| Assert(array->GetScriptContext() == GetScriptContext()); |
| TypeId typeId = array->GetTypeId(); |
| if (typeId == TypeIds_NativeIntArray) |
| { |
| JavascriptArray::OP_SetNativeIntElementC(reinterpret_cast<JavascriptNativeIntArray*>(array), index, value, array->GetScriptContext()); |
| } |
| else if (typeId == TypeIds_NativeFloatArray) |
| { |
| JavascriptArray::OP_SetNativeFloatElementC(reinterpret_cast<JavascriptNativeFloatArray*>(array), index, value, array->GetScriptContext()); |
| } |
| else |
| { |
| array->SetArrayLiteralItem(index, value); |
| } |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SetArrayItemI_CI4(const unaligned T* playout) |
| { |
| // Note that this code assumes that we only get here when we see an array literal, |
| // so we know that the instance is truly an array, and the index is a uint32. |
| // If/when we use this for cases like "a[0] = x", we'll at least have to check |
| // whether "a" is really an array. |
| |
| JavascriptArray* array = JavascriptArray::FromAnyArray(GetReg(playout->Instance)); |
| |
| // The array is create by the built-in on the same script context |
| Assert(array->GetScriptContext() == GetScriptContext()); |
| |
| uint32 index = playout->Element; |
| Var value = GetReg(playout->Value); |
| |
| Assert(VirtualTableInfo<JavascriptArray>::HasVirtualTable(array)); |
| SparseArraySegment<Var>* lastUsedSeg = (SparseArraySegment<Var>*)array->GetLastUsedSegment(); |
| if (index >= lastUsedSeg->left) |
| { |
| uint32 index2 = index - lastUsedSeg->left; |
| if (index2 < lastUsedSeg->size) |
| { |
| // Successful fastpath |
| array->DirectSetItemInLastUsedSegmentAt(index2, value); |
| return; |
| } |
| } |
| |
| ScriptContext* scriptContext = array->GetScriptContext(); |
| JavascriptOperators::SetItem(array, array, index, value, scriptContext); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| Var InterpreterStackFrame::OP_ProfiledLdThis(Var thisVar, int moduleID, ScriptContext *scriptContext) |
| { |
| FunctionBody * functionBody = this->m_functionBody; |
| DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo(); |
| TypeId typeId = JavascriptOperators::GetTypeId(thisVar); |
| |
| if (JavascriptOperators::IsThisSelf(typeId)) |
| { |
| Assert(typeId != TypeIds_GlobalObject || ((Js::GlobalObject*)thisVar)->ToThis() == thisVar); |
| Assert(typeId != TypeIds_ModuleRoot || JavascriptOperators::GetThisFromModuleRoot(thisVar) == thisVar); |
| |
| // Record the fact that we saw a trivial LdThis. |
| dynamicProfileInfo->RecordThisInfo(thisVar, ThisType_Simple); |
| |
| return thisVar; |
| } |
| |
| thisVar = JavascriptOperators::OP_GetThis(thisVar, moduleID, scriptContext); |
| |
| // Record the fact that we saw a LdThis that had to map its source to something else, or at least |
| // forced us to call a helper, e.g., a FastDOM object with an unrecognized type ID. |
| dynamicProfileInfo->RecordThisInfo(thisVar, ThisType_Mapped); |
| |
| return thisVar; |
| } |
| |
| Var InterpreterStackFrame::OP_ProfiledStrictLdThis(Var thisVar, ScriptContext* scriptContext) |
| { |
| FunctionBody * functionBody = this->m_functionBody; |
| DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo(); |
| TypeId typeId = JavascriptOperators::GetTypeId(thisVar); |
| |
| if (typeId == TypeIds_ActivationObject) |
| { |
| thisVar = scriptContext->GetLibrary()->GetUndefined(); |
| dynamicProfileInfo->RecordThisInfo(thisVar, ThisType_Mapped); |
| return thisVar; |
| } |
| |
| dynamicProfileInfo->RecordThisInfo(thisVar, ThisType_Simple); |
| return thisVar; |
| } |
| #endif |
| |
| void InterpreterStackFrame::OP_InitCachedFuncs(const unaligned OpLayoutAuxNoReg * playout) |
| { |
| const FuncInfoArray *info = Js::ByteCodeReader::ReadAuxArray<FuncInfoEntry>(playout->Offset, this->GetFunctionBody()); |
| JavascriptOperators::OP_InitCachedFuncs(this->localClosure, GetLocalFrameDisplay(), info, GetScriptContext()); |
| } |
| |
| Var InterpreterStackFrame::OP_GetCachedFunc(Var instance, int32 index) |
| { |
| ActivationObjectEx *obj = ActivationObjectEx::FromVar(instance); |
| |
| FuncCacheEntry *entry = obj->GetFuncCacheEntry((uint)index); |
| return entry->func; |
| } |
| |
| void InterpreterStackFrame::OP_CommitScope() |
| { |
| const Js::PropertyIdArray *propIds = this->m_functionBody->GetFormalsPropIdArray(); |
| this->OP_CommitScopeHelper(propIds); |
| } |
| |
| void InterpreterStackFrame::OP_CommitScopeHelper(const PropertyIdArray *propIds) |
| { |
| ActivationObjectEx *obj = ActivationObjectEx::FromVar(this->localClosure); |
| ScriptFunction *func = obj->GetParentFunc(); |
| |
| Assert(obj->GetParentFunc() == func); |
| if (func->GetCachedScope() == obj) |
| { |
| PropertyId firstVarSlot = ActivationObjectEx::GetFirstVarSlot(propIds); |
| |
| Var undef = scriptContext->GetLibrary()->GetUndefined(); |
| |
| for (uint i = firstVarSlot; i < propIds->count; i++) |
| { |
| obj->SetSlot(SetSlotArguments(propIds->elements[i], i, undef)); |
| } |
| |
| obj->SetCommit(true); |
| } |
| } |
| |
| Var InterpreterStackFrame::OP_NewScObjectSimple() |
| { |
| Var object = scriptContext->GetLibrary()->CreateObject(true); |
| JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_OBJECT(object)); |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (Js::Configuration::Global.flags.IsEnabled(Js::autoProxyFlag)) |
| { |
| object = JavascriptProxy::AutoProxyWrapper(object); |
| } |
| #endif |
| return object; |
| } |
| |
| void InterpreterStackFrame::OP_NewScObjectLiteral(const unaligned OpLayoutAuxiliary * playout) |
| { |
| const Js::PropertyIdArray *propIds = Js::ByteCodeReader::ReadPropertyIdArray(playout->Offset, this->GetFunctionBody()); |
| |
| Var newObj = JavascriptOperators::NewScObjectLiteral(GetScriptContext(), propIds, |
| this->GetFunctionBody()->GetObjectLiteralTypeRef(playout->C1)); |
| |
| SetReg(playout->R0, newObj); |
| } |
| |
| void InterpreterStackFrame::OP_NewScObjectLiteral_LS(const unaligned OpLayoutAuxiliary * playout, RegSlot& target) |
| { |
| const Js::PropertyIdArray *propIds = Js::ByteCodeReader::ReadPropertyIdArray(playout->Offset, this->GetFunctionBody()); |
| |
| target = playout->R0; |
| |
| Var newObj = JavascriptOperators::NewScObjectLiteral(GetScriptContext(), propIds, |
| this->GetFunctionBody()->GetObjectLiteralTypeRef(playout->C1)); |
| |
| SetReg(playout->R0, newObj); |
| |
| target = Js::Constants::NoRegister; |
| } |
| |
| void InterpreterStackFrame::OP_LdPropIds(const unaligned OpLayoutAuxiliary * playout) |
| { |
| const Js::PropertyIdArray *propIds = Js::ByteCodeReader::ReadPropertyIdArray(playout->Offset, this->GetFunctionBody()); |
| SetNonVarReg(playout->R0, (Var)propIds); |
| } |
| |
| bool InterpreterStackFrame::IsCurrentLoopNativeAddr(void * codeAddr) const |
| { |
| if (this->GetCurrentLoopNum() == LoopHeader::NoLoop) |
| { |
| return false; |
| } |
| // TODO: Do more verification? |
| return true; |
| } |
| |
| template <LayoutSize layoutSize, bool profiled> |
| const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(const byte * ip) |
| { |
| uint32 loopId = m_reader.GetLayout<OpLayoutT_Unsigned1<LayoutSizePolicy<layoutSize>>>(ip)->C1; |
| return OP_ProfiledLoopBodyStart<layoutSize, profiled>(loopId); |
| } |
| |
| #ifdef ASMJS_PLAT |
| template <LayoutSize layoutSize, bool profiled> |
| const byte * InterpreterStackFrame::OP_ProfiledWasmLoopBodyStart(const byte * ip) |
| { |
| uint32 loopId = m_reader.GetLayout<OpLayoutT_WasmLoopStart<LayoutSizePolicy<layoutSize>>>(ip)->loopId; |
| return OP_ProfiledLoopBodyStart<layoutSize, profiled>(loopId); |
| } |
| #endif |
| |
| #if ENABLE_PROFILE_INFO |
| void InterpreterStackFrame::OP_RecordImplicitCall(uint loopNumber) |
| { |
| Assert(Js::DynamicProfileInfo::EnableImplicitCallFlags(GetFunctionBody())); |
| Assert(loopNumber < this->m_functionBody->GetLoopCount()); |
| |
| FunctionBody* functionBody = this->m_functionBody; |
| DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo(); |
| ThreadContext * threadContext = scriptContext->GetThreadContext(); |
| dynamicProfileInfo->RecordLoopImplicitCallFlags(functionBody, loopNumber, threadContext->GetImplicitCallFlags()); |
| } |
| |
| template <LayoutSize layoutSize, bool profiled> |
| const byte * InterpreterStackFrame::OP_ProfiledLoopStart(const byte * ip) |
| { |
| const uint32 C1 = m_reader.GetLayout<OpLayoutT_Unsigned1<LayoutSizePolicy<layoutSize>>>(ip)->C1; |
| if (!profiled && !isAutoProfiling) |
| { |
| return ip; |
| } |
| |
| ThreadContext *const threadContext = GetScriptContext()->GetThreadContext(); |
| threadContext->IncrementLoopDepth(); |
| |
| // Save the implicit call flags. The interpreter may switch to profiling mode during LoopBodyStart, so always do this. |
| Assert(Js::DynamicProfileInfo::EnableImplicitCallFlags(GetFunctionBody())); |
| this->savedLoopImplicitCallFlags[C1] = threadContext->GetImplicitCallFlags(); |
| threadContext->SetImplicitCallFlags(ImplicitCall_None); |
| |
| this->currentLoopCounter = 0; |
| |
| if (!profiled) |
| { |
| return ip; |
| } |
| |
| LayoutSize localLayoutSize; |
| OpCode peekOp = m_reader.PeekOp(ip, localLayoutSize); |
| Assert(peekOp != OpCode::LoopBodyStart); |
| if (peekOp == OpCode::ProfiledLoopBodyStart) |
| { |
| Assert(localLayoutSize == layoutSize); |
| ip += Js::OpCodeUtil::EncodedSize(peekOp, layoutSize); |
| // We are doing JIT loop body. Process the first ProfiledLoopBodyStart to avoid recording |
| // the implicit call before the first iteration |
| uint32 C2 = m_reader.GetLayout<OpLayoutT_Unsigned1<LayoutSizePolicy<layoutSize>>>(ip)->C1; |
| Assert(C1 == C2); |
| (this->*opProfiledLoopBodyStart)(C1, layoutSize, true /* isFirstIteration */); |
| return m_reader.GetIP(); |
| } |
| |
| return ip; |
| } |
| |
| template <LayoutSize layoutSize, bool profiled> |
| const byte * InterpreterStackFrame::OP_ProfiledLoopEnd(const byte * ip) |
| { |
| uint32 loopNumber = m_reader.GetLayout<OpLayoutT_Unsigned1<LayoutSizePolicy<layoutSize>>>(ip)->C1; |
| if (!profiled && !isAutoProfiling) |
| { |
| return ip; |
| } |
| |
| this->CheckIfLoopIsHot(this->currentLoopCounter); |
| Js::FunctionBody *fn = this->function->GetFunctionBody(); |
| if (fn->HasDynamicProfileInfo()) |
| { |
| fn->GetAnyDynamicProfileInfo()->SetLoopInterpreted(loopNumber); |
| // If the counter is 0, there is a high chance that some config disabled tracking that information. (ie: -off:jitloopbody) |
| // Assume it is valid for memop in this case. |
| if (this->currentLoopCounter >= (uint)CONFIG_FLAG(MinMemOpCount) || |
| (this->currentLoopCounter == 0 && !this->m_functionBody->DoJITLoopBody()) |
| ) |
| { |
| // This flag becomes relevant only if the loop has been interpreted |
| fn->GetAnyDynamicProfileInfo()->SetMemOpMinReached(loopNumber); |
| } |
| } |
| |
| this->currentLoopCounter = 0; |
| |
| if (profiled) |
| { |
| Assert(Js::DynamicProfileInfo::EnableImplicitCallFlags(GetFunctionBody())); |
| OP_RecordImplicitCall(loopNumber); |
| |
| if (switchProfileModeOnLoopEndNumber == loopNumber) |
| { |
| // Stop profiling since the jitted loop body would be exiting the loop |
| Assert(!switchProfileMode); |
| switchProfileMode = true; |
| switchProfileModeOnLoopEndNumber = 0u - 1; |
| } |
| } |
| |
| // Restore the implicit call flags state and add with flags in the loop as well |
| ThreadContext *const threadContext = GetScriptContext()->GetThreadContext(); |
| threadContext->AddImplicitCallFlags(this->savedLoopImplicitCallFlags[loopNumber]); |
| |
| threadContext->DecrementLoopDepth(); |
| return ip; |
| } |
| |
| template <LayoutSize layoutSize, bool profiled> |
| const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(uint loopId) |
| { |
| if (profiled || isAutoProfiling) |
| { |
| this->currentLoopCounter++; |
| } |
| |
| if (profiled) |
| { |
| OP_RecordImplicitCall(loopId); |
| } |
| |
| (this->*(profiled ? opProfiledLoopBodyStart : opLoopBodyStart))(loopId, layoutSize, false /* isFirstIteration */); |
| return m_reader.GetIP(); |
| } |
| |
| template<bool InterruptProbe, bool JITLoopBody> |
| void InterpreterStackFrame::ProfiledLoopBodyStart(uint32 loopNumber, LayoutSize layoutSize, bool isFirstIteration) |
| { |
| Assert(Js::DynamicProfileInfo::EnableImplicitCallFlags(GetFunctionBody())); |
| |
| if (InterruptProbe) |
| { |
| this->DoInterruptProbe(); |
| } |
| |
| #if ENABLE_TTD |
| if (SHOULD_DO_TTD_STACK_STMT_OP(this->scriptContext)) |
| { |
| this->scriptContext->GetThreadContext()->TTDExecutionInfo->UpdateLoopCountInfo(); |
| } |
| #endif |
| |
| if (!JITLoopBody || this->IsInCatchOrFinallyBlock()) |
| { |
| // For functions having try-catch-finally, jit loop bodies for loops that are contained only in a try block, |
| // not even indirect containment in a Catch or Finally. |
| return; |
| } |
| |
| LoopHeader const * loopHeader = DoLoopBodyStart(loopNumber, layoutSize, false, isFirstIteration); |
| Assert(loopHeader == nullptr || this->m_functionBody->GetLoopNumber(loopHeader) == loopNumber); |
| if (loopHeader != nullptr) |
| { |
| // We executed jitted loop body, no implicit call information available for this loop |
| uint currentOffset = m_reader.GetCurrentOffset(); |
| |
| if (!loopHeader->Contains(currentOffset) || (m_reader.PeekOp() == OpCode::ProfiledLoopEnd)) |
| { |
| // Restore the outer loop's implicit call flags |
| scriptContext->GetThreadContext()->SetImplicitCallFlags(this->savedLoopImplicitCallFlags[loopNumber]); |
| } |
| else |
| { |
| // We bailout from the loop, just continue collect implicit call flags for this loop |
| } |
| } |
| } |
| #else |
| template <LayoutSize layoutSize, bool profiled> |
| const byte * InterpreterStackFrame::OP_ProfiledLoopStart(const byte * ip) |
| { |
| Assert(!profiled); |
| return ip; |
| } |
| |
| template <LayoutSize layoutSize, bool profiled> |
| const byte * InterpreterStackFrame::OP_ProfiledLoopEnd(const byte * ip) |
| { |
| Assert(!profiled); |
| return ip; |
| } |
| |
| template <LayoutSize layoutSize, bool profiled> |
| const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(uint loopId) |
| { |
| Assert(!profiled); |
| (this->*opLoopBodyStart)(loopId, layoutSize, false /* isFirstIteration */); |
| return m_reader.GetIP(); |
| } |
| #endif |
| |
| template<bool InterruptProbe, bool JITLoopBody> |
| void InterpreterStackFrame::LoopBodyStart(uint32 loopNumber, LayoutSize layoutSize, bool isFirstIteration) |
| { |
| if (InterruptProbe) |
| { |
| this->DoInterruptProbe(); |
| } |
| |
| #if ENABLE_TTD |
| if (SHOULD_DO_TTD_STACK_STMT_OP(this->scriptContext)) |
| { |
| this->scriptContext->GetThreadContext()->TTDExecutionInfo->UpdateLoopCountInfo(); |
| } |
| #endif |
| |
| if (!JITLoopBody || this->IsInCatchOrFinallyBlock()) |
| { |
| // For functions having try-catch-finally, jit loop bodies for loops that are contained only in a try block, |
| // not even indirect containment in a Catch or Finally. |
| return; |
| } |
| |
| DoLoopBodyStart(loopNumber, layoutSize, true, isFirstIteration); |
| } |
| |
| LoopHeader const * InterpreterStackFrame::DoLoopBodyStart(uint32 loopNumber, LayoutSize layoutSize, const bool doProfileLoopCheck, const bool isFirstIteration) |
| { |
| #if ENABLE_PROFILE_INFO |
| class AutoRestoreLoopNumbers |
| { |
| private: |
| InterpreterStackFrame * const interpreterStackFrame; |
| uint32 loopNumber; |
| bool doProfileLoopCheck; |
| |
| public: |
| AutoRestoreLoopNumbers(InterpreterStackFrame *const interpreterStackFrame, uint32 loopNumber, bool doProfileLoopCheck) |
| : interpreterStackFrame(interpreterStackFrame), loopNumber(loopNumber), doProfileLoopCheck(doProfileLoopCheck) |
| { |
| Assert(interpreterStackFrame->currentLoopNum == LoopHeader::NoLoop); |
| interpreterStackFrame->currentLoopNum = loopNumber; |
| interpreterStackFrame->m_functionBody->SetRecentlyBailedOutOfJittedLoopBody(false); |
| } |
| |
| ~AutoRestoreLoopNumbers() |
| { |
| interpreterStackFrame->currentLoopNum = LoopHeader::NoLoop; |
| interpreterStackFrame->currentLoopCounter = 0; |
| Js::FunctionBody* fn = interpreterStackFrame->m_functionBody; |
| if (fn->RecentlyBailedOutOfJittedLoopBody()) |
| { |
| if (doProfileLoopCheck && interpreterStackFrame->isAutoProfiling) |
| { |
| // Start profiling the loop after a bailout. Some bailouts require subsequent profile data collection such |
| // that the rejitted loop body would not bail out again for the same reason. |
| Assert(!interpreterStackFrame->switchProfileMode); |
| interpreterStackFrame->switchProfileMode = true; |
| Assert(interpreterStackFrame->switchProfileModeOnLoopEndNumber == 0u - 1); |
| interpreterStackFrame->switchProfileModeOnLoopEndNumber = loopNumber; |
| } |
| } |
| else |
| { |
| if (interpreterStackFrame->switchProfileModeOnLoopEndNumber == loopNumber) |
| { |
| // Stop profiling since the jitted loop body would be exiting the loop |
| Assert(!interpreterStackFrame->switchProfileMode); |
| interpreterStackFrame->switchProfileMode = true; |
| interpreterStackFrame->switchProfileModeOnLoopEndNumber = 0u - 1; |
| } |
| |
| interpreterStackFrame->scriptContext->GetThreadContext()->DecrementLoopDepth(); |
| } |
| } |
| }; |
| #endif |
| |
| Js::FunctionBody* fn = this->m_functionBody; |
| |
| Assert(loopNumber < fn->GetLoopCount()); |
| Assert(!this->IsInCatchOrFinallyBlock()); |
| |
| Js::LoopHeader *loopHeader = fn->GetLoopHeader(loopNumber); |
| loopHeader->isInTry = this->TestFlags(Js::InterpreterStackFrameFlags_WithinTryBlock); |
| loopHeader->isInTryFinally = this->TestFlags(Js::InterpreterStackFrameFlags_WithinTryFinallyBlock); |
| |
| Js::LoopEntryPointInfo * entryPointInfo = loopHeader->GetCurrentEntryPointInfo(); |
| |
| if (fn->ForceJITLoopBody() && loopHeader->interpretCount == 0 && |
| (entryPointInfo != NULL && entryPointInfo->IsNotScheduled())) |
| { |
| #if ENABLE_PROFILE_INFO |
| if (Js::DynamicProfileInfo::EnableImplicitCallFlags(GetFunctionBody())) |
| { |
| scriptContext->GetThreadContext()->AddImplicitCallFlags(this->savedLoopImplicitCallFlags[loopNumber]); |
| } |
| #endif |
| |
| #if ENABLE_NATIVE_CODEGEN |
| GenerateLoopBody(scriptContext->GetNativeCodeGenerator(), fn, loopHeader, entryPointInfo, fn->GetLocalsCount(), this->m_localSlots); |
| #endif |
| } |
| |
| #if ENABLE_NATIVE_CODEGEN |
| // If we have JITted the loop, call the JITted code |
| if (entryPointInfo != NULL && entryPointInfo->IsCodeGenDone()) |
| { |
| #if DBG_DUMP |
| if (PHASE_TRACE1(Js::JITLoopBodyPhase) && CONFIG_FLAG(Verbose)) |
| { |
| fn->DumpFunctionId(true); |
| Output::Print(_u(": %-20s LoopBody Execute Loop: %2d\n"), fn->GetDisplayName(), loopNumber); |
| Output::Flush(); |
| } |
| loopHeader->nativeCount++; |
| #endif |
| #ifdef BGJIT_STATS |
| entryPointInfo->MarkAsUsed(); |
| #endif |
| |
| entryPointInfo->EnsureIsReadyToCall(); |
| entryPointInfo->SetNativeEntryPointProcessed(); |
| |
| RegSlot envReg = this->m_functionBody->GetEnvRegister(); |
| if (envReg != Constants::NoRegister) |
| { |
| this->SetNonVarReg(envReg, this->LdEnv()); |
| } |
| |
| RegSlot localClosureReg = this->m_functionBody->GetLocalClosureRegister(); |
| RegSlot localFrameDisplayReg = this->m_functionBody->GetLocalFrameDisplayRegister(); |
| RegSlot paramClosureReg = this->m_functionBody->GetParamClosureRegister(); |
| |
| if (entryPointInfo->HasJittedStackClosure()) |
| { |
| // The jitted code is expecting the closure registers to point to known stack locations where |
| // the closures can be found and possibly boxed. |
| // In a jitted loop body, those locations are the local closure fields on the interpreter instance. |
| if (localClosureReg != Constants::NoRegister) |
| { |
| this->SetNonVarReg(localClosureReg, &this->localClosure); |
| } |
| |
| if (localFrameDisplayReg != Constants::NoRegister) |
| { |
| this->SetNonVarReg(localFrameDisplayReg, &this->localFrameDisplay); |
| } |
| |
| if (paramClosureReg != Constants::NoRegister) |
| { |
| this->SetNonVarReg(paramClosureReg, &this->paramClosure); |
| } |
| } |
| else |
| { |
| // In non-stack-closure jitted code, the closure registers are expected to hold the addresses |
| // of the actual structures. |
| if (localClosureReg != Constants::NoRegister) |
| { |
| this->SetNonVarReg(localClosureReg, this->localClosure); |
| } |
| |
| if (localFrameDisplayReg != Constants::NoRegister) |
| { |
| this->SetNonVarReg(localFrameDisplayReg, this->localFrameDisplay); |
| } |
| |
| if (paramClosureReg != Constants::NoRegister) |
| { |
| this->SetNonVarReg(paramClosureReg, this->paramClosure); |
| } |
| } |
| |
| uint32 innerScopeCount = this->m_functionBody->GetInnerScopeCount(); |
| for (uint32 i = 0; i < innerScopeCount; i++) |
| { |
| // As with the function-level scope, transfer the inner scopes from the interpreter's side storage |
| // to their dedicated register slots. |
| SetNonVarReg(this->m_functionBody->GetFirstInnerScopeRegister() + i, InnerScopeFromIndex(i)); |
| } |
| |
| uint newOffset = 0; |
| if (fn->GetIsAsmJsFunction()) |
| { |
| AutoRestoreLoopNumbers autoRestore(this, loopNumber, doProfileLoopCheck); |
| newOffset = this->CallAsmJsLoopBody(entryPointInfo->jsMethod); |
| } |
| else |
| { |
| AutoRestoreLoopNumbers autoRestore(this, loopNumber, doProfileLoopCheck); |
| newOffset = this->CallLoopBody(entryPointInfo->jsMethod); |
| } |
| |
| if (envReg != Constants::NoRegister) |
| { |
| SetNonVarReg(envReg, nullptr); |
| } |
| |
| if (localClosureReg != Constants::NoRegister) |
| { |
| SetNonVarReg(localClosureReg, nullptr); |
| } |
| |
| if (localFrameDisplayReg != Constants::NoRegister) |
| { |
| SetNonVarReg(localFrameDisplayReg, nullptr); |
| } |
| |
| if (paramClosureReg != Constants::NoRegister) |
| { |
| SetNonVarReg(paramClosureReg, nullptr); |
| } |
| |
| for (uint32 i = 0; i < innerScopeCount; i++) |
| { |
| // Get the (possibly updated) scopes from their registers and put them back in side storage. |
| // (Getting the updated values may not be necessary, actually, but it can't hurt.) |
| // Then null out the registers. |
| RegSlot reg = this->m_functionBody->GetFirstInnerScopeRegister() + i; |
| SetInnerScopeFromIndex(i, GetNonVarReg(reg)); |
| SetNonVarReg(reg, nullptr); |
| } |
| |
| Assert(Js::OpCodeUtil::GetOpCodeLayout(OpCode::ProfiledLoopBodyStart) == Js::OpLayoutType::Unsigned1); |
| Assert(Js::OpCodeUtil::GetOpCodeLayout(OpCode::LoopBodyStart) == Js::OpLayoutType::Unsigned1); |
| Assert(Js::OpCodeUtil::EncodedSize(Js::OpCode::LoopBodyStart, layoutSize) == Js::OpCodeUtil::EncodedSize(Js::OpCode::ProfiledLoopBodyStart, layoutSize)); |
| uint byteCodeSize = Js::OpCodeUtil::EncodedSize(Js::OpCode::LoopBodyStart, layoutSize); |
| if (layoutSize == SmallLayout) |
| { |
| byteCodeSize += sizeof(OpLayoutUnsigned1_Small); |
| } |
| else if (layoutSize == MediumLayout) |
| { |
| byteCodeSize += sizeof(OpLayoutUnsigned1_Medium); |
| } |
| else |
| { |
| byteCodeSize += sizeof(OpLayoutUnsigned1_Large); |
| } |
| |
| if (newOffset == loopHeader->startOffset || newOffset == m_reader.GetCurrentOffset() - byteCodeSize) |
| { |
| // If we bail out back the start of the loop, or start of this LoopBodyStart just skip and interpret the loop |
| // instead of trying to start the loop body again |
| |
| // Increment the interpret count of the loop |
| loopHeader->interpretCount++; |
| } |
| else |
| { |
| this->CheckIfLoopIsHot(loopHeader->profiledLoopCounter); |
| |
| if (newOffset >= loopHeader->endOffset) |
| { |
| // Reset the totalJittedLoopIterations for the next invocation of this loop entry point |
| entryPointInfo->totalJittedLoopIterations = |
| static_cast<uint8>( |
| min( |
| static_cast<uint>(static_cast<uint8>(CONFIG_FLAG(MinBailOutsBeforeRejit))) * |
| (Js::LoopEntryPointInfo::GetDecrLoopCountPerBailout() - 1), |
| entryPointInfo->totalJittedLoopIterations)); |
| entryPointInfo->jittedLoopIterationsSinceLastBailout = 0; |
| } |
| m_reader.SetCurrentOffset(newOffset); |
| } |
| |
| return loopHeader; |
| } |
| #endif |
| |
| // Increment the interpret count of the loop |
| loopHeader->interpretCount += !isFirstIteration; |
| |
| const uint loopInterpretCount = GetFunctionBody()->GetLoopInterpretCount(loopHeader); |
| if (loopHeader->interpretCount > loopInterpretCount) |
| { |
| if (this->scriptContext->GetConfig()->IsNoNative()) |
| { |
| return nullptr; |
| } |
| |
| if (!fn->DoJITLoopBody()) |
| { |
| return nullptr; |
| } |
| |
| #if ENABLE_NATIVE_CODEGEN |
| #if ENABLE_OOP_NATIVE_CODEGEN |
| // If for some reason OOP JIT isn't connected (e.g. it crashed), don't attempt to JIT a loop body |
| if (JITManager::GetJITManager()->IsOOPJITEnabled() && !JITManager::GetJITManager()->IsConnected()) |
| { |
| return nullptr; |
| } |
| #endif |
| // If the job is not scheduled then we need to schedule it now. |
| // It is possible a job was scheduled earlier and we find ourselves looking at the same entry point |
| // again. For example, if the function with the loop was JITed and bailed out then as we finish |
| // the call in the interpreter we might encounter a loop for which we had scheduled a JIT job before |
| // the function was initially scheduled. In such cases, that old JIT job will complete. If it completes |
| // successfully then we can go ahead and use it. If it fails then it will eventually revert to the |
| // NotScheduled state. Since transitions from NotScheduled can only occur on the main thread, |
| // by checking the state we are safe from racing with the JIT thread when looking at the other fields |
| // of the entry point. |
| if (entryPointInfo != NULL && entryPointInfo->IsNotScheduled()) |
| { |
| GenerateLoopBody(scriptContext->GetNativeCodeGenerator(), fn, loopHeader, entryPointInfo, fn->GetLocalsCount(), this->m_localSlots); |
| } |
| #endif |
| } |
| #if ENABLE_PROFILE_INFO |
| else if ( |
| doProfileLoopCheck && |
| isAutoProfiling && |
| loopHeader->interpretCount > fn->GetLoopProfileThreshold(loopInterpretCount)) |
| { |
| // Start profiling the loop so that the jitted loop body will have some profile data to use |
| Assert(!switchProfileMode); |
| switchProfileMode = true; |
| Assert(switchProfileModeOnLoopEndNumber == 0u - 1); |
| switchProfileModeOnLoopEndNumber = loopNumber; |
| } |
| #endif |
| |
| return nullptr; |
| } |
| |
| void |
| InterpreterStackFrame::CheckIfLoopIsHot(uint profiledLoopCounter) |
| { |
| Js::FunctionBody *fn = this->function->GetFunctionBody(); |
| if (!fn->GetHasHotLoop() && profiledLoopCounter > (uint)CONFIG_FLAG(JitLoopBodyHotLoopThreshold)) |
| { |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| if (PHASE_TRACE(Js::JITLoopBodyPhase, fn)) |
| { |
| char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; |
| |
| Output::Print( |
| _u("Speculate Jit set for this function with loopbody: function: %s (%s)\n"), |
| fn->GetDisplayName(), |
| fn->GetDebugNumberSet(debugStringBuffer)); |
| Output::Flush(); |
| } |
| #endif |
| fn->SetHasHotLoop(); |
| } |
| } |
| |
| bool InterpreterStackFrame::CheckAndResetImplicitCall(DisableImplicitFlags prevDisableImplicitFlags, ImplicitCallFlags savedImplicitCallFlags) |
| { |
| ImplicitCallFlags curImplicitCallFlags = this->scriptContext->GetThreadContext()->GetImplicitCallFlags(); |
| if (curImplicitCallFlags > ImplicitCall_None) |
| { |
| //error implicit bit is set , reparse without asmjs |
| this->scriptContext->GetThreadContext()->SetDisableImplicitFlags(prevDisableImplicitFlags); |
| this->scriptContext->GetThreadContext()->SetImplicitCallFlags(savedImplicitCallFlags); |
| return true; |
| } |
| return false; |
| } |
| |
| uint |
| InterpreterStackFrame::CallLoopBody(JavascriptMethod address) |
| { |
| #ifdef _M_IX86 |
| void *savedEsp = NULL; |
| __asm |
| { |
| // Save ESP |
| mov savedEsp, esp |
| |
| // 8-byte align frame to improve floating point perf of our JIT'd code. |
| and esp, -8 |
| |
| // Add an extra 4-bytes to the stack since we'll be pushing 3 arguments |
| push eax |
| } |
| #endif |
| |
| #if defined(_M_ARM32_OR_ARM64) |
| // For ARM we need to make sure that pipeline is synchronized with memory/cache for newly jitted code. |
| // Note: this does not seem to affect perf, but if it was, we could add a boolean isCalled to EntryPointInfo |
| // and do ISB only for 1st time this entry point is called (potential working set regression though). |
| _InstructionSynchronizationBarrier(); |
| #endif |
| uint newOffset = ::Math::PointerCastToIntegral<uint>( |
| CALL_ENTRYPOINT_NOASSERT(address, function, CallInfo(CallFlags_InternalFrame, 1), this)); |
| |
| #ifdef _M_IX86 |
| _asm |
| { |
| // Restore ESP |
| mov esp, savedEsp |
| } |
| #endif |
| return newOffset; |
| } |
| |
| |
| uint |
| InterpreterStackFrame::CallAsmJsLoopBody(JavascriptMethod address) |
| { |
| #ifdef _M_IX86 |
| void *savedEsp = NULL; |
| __asm |
| { |
| // Save ESP |
| mov savedEsp, esp |
| // Add an extra 4-bytes to the stack since we'll be pushing 3 arguments |
| push eax |
| } |
| #endif |
| |
| #if defined(_M_ARM32_OR_ARM64) |
| // For ARM we need to make sure that pipeline is synchronized with memory/cache for newly jitted code. |
| // Note: this does not seem to affect perf, but if it was, we could add a boolean isCalled to EntryPointInfo |
| // and do ISB only for 1st time this entry point is called (potential working set regression though). |
| _InstructionSynchronizationBarrier(); |
| #endif |
| uint newOffset = ::Math::PointerCastToIntegral<uint>( |
| CALL_ENTRYPOINT_NOASSERT(address, function, CallInfo(CallFlags_InternalFrame, 1), this)); |
| |
| #ifdef _M_IX86 |
| _asm |
| { |
| // Restore ESP |
| mov esp, savedEsp |
| } |
| #endif |
| return newOffset; |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_NewScObjectNoCtorFull(const unaligned T* playout) |
| { |
| Var function = GetReg(playout->R1); |
| Var newObj = JavascriptOperators::NewScObjectNoCtorFull(function, GetScriptContext()); |
| SetReg(playout->R0, newObj); |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// InterpreterStackFrame::OP_NewScObject |
| /// |
| /// OP_NewScObject() allocates a new DynamicObject and initializes it with an |
| /// optional "constructor" function. |
| /// |
| /// NOTE: The return register must be carefully chosen to ensure proper |
| /// behavior: |
| /// 1. OpCode::NewInstance should never specify "R0" as the register to |
| /// store the new instance, because it will get whacked from the |
| /// "constructor" function's return value: |
| /// |
| /// var a1 = Date(); <-- a1 = string returned from Date() function |
| /// var a2 = new Date(); <-- a2 = instance return from NewInstance. |
| /// Date()'s return value is thrown away. |
| /// |
| /// 2. If an exception is thrown during construction, the destination |
| /// variable / field should __not__ be modified. Therefore, the destination |
| /// register should always be a temporary and never a valid local variable. |
| /// After successfully returning from the constructor function, the new |
| /// instance is valid and may be stored in its final destination variable / |
| /// field. |
| /// |
| /// OPCODE NewObject: |
| /// T1 = new DynamicObject(Function.Prototype) |
| /// OutArg[0] = T1 |
| /// Call(Function, ArgCount) |
| /// Local[Return] = T1 |
| /// |
| /// - R0: Destination "local" register |
| /// - R1: Optional constructor JavascriptFunction instance or 'null' |
| /// |
| ///---------------------------------------------------------------------------- |
| template <class T, bool Profiled, bool ICIndex> |
| void InterpreterStackFrame::OP_NewScObject_Impl(const unaligned T* playout, InlineCacheIndex inlineCacheIndex, const Js::AuxArray<uint32> *spreadIndices) |
| { |
| if (ICIndex) |
| { |
| Assert(inlineCacheIndex != Js::Constants::NoInlineCacheIndex); |
| } |
| Var newVarInstance = |
| #if ENABLE_PROFILE_INFO |
| Profiled ? |
| ProfiledNewScObject_Helper( |
| GetReg(playout->Function), |
| playout->ArgCount, |
| static_cast<const unaligned OpLayoutDynamicProfile<T> *>(playout)->profileId, |
| inlineCacheIndex, |
| spreadIndices) : |
| #endif |
| NewScObject_Helper(GetReg(playout->Function), playout->ArgCount, spreadIndices); |
| SetReg((RegSlot)playout->Return, newVarInstance); |
| } |
| |
| template <class T, bool Profiled> |
| void InterpreterStackFrame::OP_ProfiledNewScObjArray_Impl(const unaligned T* playout, const Js::AuxArray<uint32> *spreadIndices) |
| { |
| // Always profile this operation when auto-profiling so that array type changes are tracked |
| #if ENABLE_PROFILE_INFO |
| if (!Profiled && !isAutoProfiling) |
| #else |
| Assert(!Profiled); |
| #endif |
| { |
| OP_NewScObjArray_Impl<T, Profiled>(playout, spreadIndices); |
| return; |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| Arguments args(CallInfo(CallFlags_New, playout->ArgCount), m_outParams); |
| |
| uint32 spreadSize = 0; |
| if (spreadIndices != nullptr) |
| { |
| spreadSize = JavascriptFunction::GetSpreadSize(args, spreadIndices, scriptContext); |
| |
| // Allocate room on the stack for the spread args. |
| Arguments outArgs(CallInfo(CallFlags_New, 0), nullptr); |
| outArgs.Info.Count = spreadSize; |
| const unsigned STACK_ARGS_ALLOCA_THRESHOLD = 8; // Number of stack args we allow before using _alloca |
| Var stackArgs[STACK_ARGS_ALLOCA_THRESHOLD]; |
| size_t outArgsSize = 0; |
| if (outArgs.Info.Count > STACK_ARGS_ALLOCA_THRESHOLD) |
| { |
| PROBE_STACK(scriptContext, outArgs.Info.Count * sizeof(Var) + Js::Constants::MinStackDefault); // args + function call |
| outArgsSize = outArgs.Info.Count * sizeof(Var); |
| outArgs.Values = (Var*)_alloca(outArgsSize); |
| ZeroMemory(outArgs.Values, outArgsSize); |
| } |
| else |
| { |
| outArgs.Values = stackArgs; |
| outArgsSize = STACK_ARGS_ALLOCA_THRESHOLD * sizeof(Var); |
| ZeroMemory(outArgs.Values, outArgsSize); // We may not use all of the elements |
| } |
| JavascriptFunction::SpreadArgs(args, outArgs, spreadIndices, scriptContext); |
| |
| SetReg( |
| (RegSlot)playout->Return, |
| ProfilingHelpers::ProfiledNewScObjArray( |
| GetReg(playout->Function), |
| outArgs, |
| function, |
| static_cast<const unaligned OpLayoutDynamicProfile2<T> *>(playout)->profileId, |
| static_cast<const unaligned OpLayoutDynamicProfile2<T> *>(playout)->profileId2)); |
| } |
| else |
| { |
| SetReg( |
| (RegSlot)playout->Return, |
| ProfilingHelpers::ProfiledNewScObjArray( |
| GetReg(playout->Function), |
| args, |
| function, |
| static_cast<const unaligned OpLayoutDynamicProfile2<T> *>(playout)->profileId, |
| static_cast<const unaligned OpLayoutDynamicProfile2<T> *>(playout)->profileId2)); |
| } |
| PopOut(playout->ArgCount); |
| #endif |
| } |
| |
| void InterpreterStackFrame::OP_NewScObject_A_Impl(const unaligned OpLayoutAuxiliary * playout, RegSlot *target) |
| { |
| const Js::VarArrayVarCount * vars = Js::ByteCodeReader::ReadVarArrayVarCount(playout->Offset, this->GetFunctionBody()); |
| |
| int count = Js::TaggedInt::ToInt32(vars->count); |
| |
| // Push the parameters to stack |
| for (int i = 0; i < count; i++) |
| { |
| SetOut((ArgSlot)(i + 1), vars->elements[i]); |
| } |
| |
| Var newVarInstance = NewScObject_Helper(GetReg((RegSlot)playout->C1), (ArgSlot)count + 1); |
| SetReg((RegSlot)playout->R0, newVarInstance); |
| |
| } |
| |
| Var InterpreterStackFrame::NewScObject_Helper(Var target, ArgSlot ArgCount, const Js::AuxArray<uint32> *spreadIndices) |
| { |
| Arguments args(CallInfo(CallFlags_New, ArgCount), m_outParams); |
| |
| Var newVarInstance = nullptr; |
| BEGIN_SAFE_REENTRANT_CALL(this->scriptContext->GetThreadContext()) |
| { |
| newVarInstance = JavascriptOperators::NewScObject(target, args, GetScriptContext(), spreadIndices); |
| } |
| END_SAFE_REENTRANT_CALL |
| |
| PopOut(ArgCount); |
| JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_OBJECT(newVarInstance)); |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (Js::Configuration::Global.flags.IsEnabled(Js::autoProxyFlag)) |
| { |
| newVarInstance = JavascriptProxy::AutoProxyWrapper(newVarInstance); |
| // this might come from a different scriptcontext. |
| newVarInstance = CrossSite::MarshalVar(GetScriptContext(), newVarInstance); |
| } |
| #endif |
| #ifdef ENABLE_BASIC_TELEMETRY |
| { |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().NewScriptObject(target, args, newVarInstance); |
| } |
| #endif |
| return newVarInstance; |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| Var InterpreterStackFrame::ProfiledNewScObject_Helper(Var target, ArgSlot ArgCount, ProfileId profileId, InlineCacheIndex inlineCacheIndex, const Js::AuxArray<uint32> *spreadIndices) |
| { |
| Arguments args(CallInfo(CallFlags_New, ArgCount), m_outParams); |
| |
| Var newVarInstance = nullptr; |
| BEGIN_SAFE_REENTRANT_CALL(this->scriptContext->GetThreadContext()) |
| { |
| newVarInstance = ProfilingHelpers::ProfiledNewScObject( |
| target, |
| args, |
| GetFunctionBody(), |
| profileId, |
| inlineCacheIndex, |
| spreadIndices); |
| } |
| END_SAFE_REENTRANT_CALL |
| |
| PopOut(ArgCount); |
| JS_ETW(EventWriteJSCRIPT_RECYCLER_ALLOCATE_OBJECT(newVarInstance)); |
| #if ENABLE_DEBUG_CONFIG_OPTIONS |
| if (Js::Configuration::Global.flags.IsEnabled(Js::autoProxyFlag)) |
| { |
| newVarInstance = JavascriptProxy::AutoProxyWrapper(newVarInstance); |
| // this might come from a different scriptcontext. |
| newVarInstance = CrossSite::MarshalVar(GetScriptContext(), newVarInstance); |
| } |
| #endif |
| #ifdef TELEMETRY_PROFILED |
| { |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().NewScriptObject(target, args, newVarInstance); |
| } |
| #endif |
| return newVarInstance; |
| } |
| #endif |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_LdElementUndefined(const unaligned OpLayoutT_ElementU<T>* playout) |
| { |
| if (this->m_functionBody->IsEval()) |
| { |
| JavascriptOperators::OP_LoadUndefinedToElementDynamic(GetReg(playout->Instance), |
| this->m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetScriptContext()); |
| } |
| else |
| { |
| JavascriptOperators::OP_LoadUndefinedToElement(GetReg(playout->Instance), |
| this->m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex)); |
| } |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_LdLocalElementUndefined(const unaligned OpLayoutT_ElementRootU<T>* playout) |
| { |
| if (this->m_functionBody->IsEval()) |
| { |
| JavascriptOperators::OP_LoadUndefinedToElementDynamic(this->localClosure, |
| this->m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetScriptContext()); |
| } |
| else |
| { |
| JavascriptOperators::OP_LoadUndefinedToElement(this->localClosure, |
| this->m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex)); |
| } |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_LdElementUndefinedScoped(const unaligned OpLayoutT_ElementScopedU<T>* playout) |
| { |
| // Implicit root object as default instance |
| JavascriptOperators::OP_LoadUndefinedToElementScoped(GetEnvForEvalCode(), |
| this->m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetReg(Js::FunctionBody::RootObjectRegSlot), GetScriptContext()); |
| } |
| |
| void InterpreterStackFrame::OP_ChkUndecl(Var aValue) |
| { |
| if (this->scriptContext->IsUndeclBlockVar(aValue)) |
| { |
| JavascriptError::ThrowReferenceError(scriptContext, JSERR_UseBeforeDeclaration); |
| } |
| } |
| |
| void InterpreterStackFrame::OP_ChkNewCallFlag() |
| { |
| if (!(this->m_callFlags & CallFlags_New) && !this->TestFlags(InterpreterStackFrameFlags_FromBailOutInInlinee)) |
| { |
| JavascriptError::ThrowTypeError(scriptContext, JSERR_ClassConstructorCannotBeCalledWithoutNew); |
| } |
| } |
| |
| void InterpreterStackFrame::OP_EnsureNoRootProperty(uint propertyIdIndex) |
| { |
| Var instance = this->GetRootObject(); |
| JavascriptOperators::OP_EnsureNoRootProperty(instance, this->m_functionBody->GetReferencedPropertyId(propertyIdIndex)); |
| } |
| |
| void InterpreterStackFrame::OP_EnsureNoRootRedeclProperty(uint propertyIdIndex) |
| { |
| Var instance = this->GetRootObject(); |
| JavascriptOperators::OP_EnsureNoRootRedeclProperty(instance, this->m_functionBody->GetReferencedPropertyId(propertyIdIndex)); |
| } |
| |
| void InterpreterStackFrame::OP_ScopedEnsureNoRedeclProperty(Var aValue, uint propertyIdIndex, Var aValue2) |
| { |
| Js::PropertyId propertyId = this->m_functionBody->GetReferencedPropertyId(propertyIdIndex); |
| JavascriptOperators::OP_ScopedEnsureNoRedeclProperty((FrameDisplay*)aValue, propertyId, aValue2); |
| } |
| |
| Var InterpreterStackFrame::OP_InitUndecl() |
| { |
| return this->scriptContext->GetLibrary()->GetUndeclBlockVar(); |
| } |
| |
| void InterpreterStackFrame::OP_InitUndeclSlot(Var aValue, int32 slot) |
| { |
| this->OP_StSlot(aValue, slot, this->scriptContext->GetLibrary()->GetUndeclBlockVar()); |
| } |
| |
| void InterpreterStackFrame::OP_TryCatch(const unaligned OpLayoutBr* playout) |
| { |
| Js::JavascriptExceptionObject* exception = NULL; |
| try |
| { |
| this->nestedTryDepth++; |
| // mark the stackFrame as 'in try block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinTryBlock); |
| |
| Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext); |
| void * addrOfReturnAddr = _AddressOfReturnAddress(); |
| Js::JavascriptExceptionOperators::TryHandlerAddrOfReturnAddrStack tryHandlerAddrOfReturnAddrStack(scriptContext, addrOfReturnAddr); |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (this->IsInDebugMode()) |
| { |
| #if ENABLE_TTD |
| if (SHOULD_DO_TTD_STACK_STMT_OP(this->scriptContext)) |
| { |
| this->ProcessWithDebugging_PreviousStmtTracking(); |
| } |
| else |
| { |
| this->ProcessWithDebugging(); |
| } |
| #else |
| this->ProcessWithDebugging(); |
| #endif |
| |
| this->TrySetRetOffset(); |
| } |
| else |
| #endif |
| { |
| this->Process(); |
| this->TrySetRetOffset(); |
| } |
| } |
| catch (const Js::JavascriptException& err) |
| { |
| // We are using C++ exception handling which does not unwind the stack in the catch block. |
| // For stack overflow and OOM exceptions, we cannot run user code here because the stack is not unwind. |
| exception = err.GetAndClear(); |
| } |
| |
| if (--this->nestedTryDepth == -1) |
| { |
| // unmark the stackFrame as 'in try block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinTryBlock); |
| } |
| |
| // Now that the stack is unwound, let's run the catch block. |
| if (exception) |
| { |
| if (exception->IsGeneratorReturnException()) |
| { |
| // Generator return scenario, so no need to go into the catch block and we must rethrow to propagate the exception to down level |
| JavascriptExceptionOperators::DoThrow(exception, scriptContext); |
| } |
| |
| exception = exception->CloneIfStaticExceptionObject(scriptContext); |
| // We've got a JS exception. Grab the exception object and assign it to the |
| // catch object's location, then call the handler (i.e., we consume the Catch op here). |
| Var catchObject = exception->GetThrownObject(scriptContext); |
| |
| m_reader.SetCurrentRelativeOffset((const byte *)(playout + 1), playout->RelativeJumpOffset); |
| |
| LayoutSize layoutSize; |
| OpCode catchOp = m_reader.ReadOp(layoutSize); |
| #ifdef BYTECODE_BRANCH_ISLAND |
| if (catchOp == Js::OpCode::BrLong) |
| { |
| Assert(layoutSize == SmallLayout); |
| auto playoutBrLong = m_reader.BrLong(); |
| m_reader.SetCurrentRelativeOffset((const byte *)(playoutBrLong + 1), playoutBrLong->RelativeJumpOffset); |
| catchOp = m_reader.ReadOp(layoutSize); |
| } |
| #endif |
| AssertMsg(catchOp == OpCode::Catch, "Catch op not found at catch offset"); |
| RegSlot reg = layoutSize == SmallLayout ? m_reader.Reg1_Small()->R0 : |
| layoutSize == MediumLayout ? m_reader.Reg1_Medium()->R0 : m_reader.Reg1_Large()->R0; |
| SetReg(reg, catchObject); |
| |
| ResetOut(); |
| |
| this->nestedCatchDepth++; |
| // mark the stackFrame as 'in catch block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinCatchBlock); |
| |
| this->ProcessCatch(); |
| |
| if (--this->nestedCatchDepth == -1) |
| { |
| // unmark the stackFrame as 'in catch block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinCatchBlock); |
| } |
| } |
| } |
| |
| void InterpreterStackFrame::ProcessCatch() |
| { |
| #if ENABLE_TTD |
| //Clear any previous Exception Info |
| if (SHOULD_DO_TTD_STACK_STMT_OP(this->scriptContext)) |
| { |
| this->scriptContext->GetThreadContext()->TTDExecutionInfo->ProcessCatchInfoForLastExecutedStatements(); |
| } |
| #endif |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (this->IsInDebugMode()) |
| { |
| this->DebugProcess(); |
| } |
| else |
| #endif |
| { |
| this->Process(); |
| } |
| } |
| |
| int InterpreterStackFrame::ProcessFinally() |
| { |
| this->nestedFinallyDepth++; |
| |
| int newOffset = 0; |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (this->IsInDebugMode()) |
| { |
| newOffset = ::Math::PointerCastToIntegral<int>(this->DebugProcess()); |
| } |
| else |
| #endif |
| { |
| newOffset = ::Math::PointerCastToIntegral<int>(this->Process()); |
| } |
| |
| return newOffset; |
| } |
| |
| void InterpreterStackFrame::ProcessTryHandlerBailout(EHBailoutData * ehBailoutData, uint32 tryNestingDepth) |
| { |
| int catchOffset = ehBailoutData->catchOffset; |
| int finallyOffset = ehBailoutData->finallyOffset; |
| Js::JavascriptExceptionObject* exception = NULL; |
| |
| if (catchOffset != 0 || finallyOffset != 0) |
| { |
| try |
| { |
| this->nestedTryDepth++; |
| // mark the stackFrame as 'in try block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinTryBlock); |
| |
| if (finallyOffset != 0) |
| { |
| this->OrFlags(InterpreterStackFrameFlags_WithinTryFinallyBlock); |
| } |
| |
| if (tryNestingDepth != 0) |
| { |
| this->ProcessTryHandlerBailout(ehBailoutData->child, --tryNestingDepth); |
| } |
| |
| if (catchOffset != 0) |
| { |
| Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext); |
| } |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (this->IsInDebugMode()) |
| { |
| #if ENABLE_TTD |
| if (SHOULD_DO_TTD_STACK_STMT_OP(this->scriptContext)) |
| { |
| this->ProcessWithDebugging_PreviousStmtTracking(); |
| } |
| else |
| { |
| this->ProcessWithDebugging(); |
| } |
| #else |
| this->ProcessWithDebugging(); |
| #endif |
| |
| this->TrySetRetOffset(); |
| } |
| else |
| #endif |
| { |
| this->Process(); |
| this->TrySetRetOffset(); |
| } |
| } |
| catch (const Js::JavascriptException& err) |
| { |
| // We are using C++ exception handling which does not unwind the stack in the catch block. |
| // For stack overflow and OOM exceptions, we cannot run user code here because the stack is not unwind. |
| exception = err.GetAndClear(); |
| } |
| } |
| else if (ehBailoutData->ht == HandlerType::HT_Catch) |
| { |
| this->nestedCatchDepth++; |
| // mark the stackFrame as 'in catch block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinCatchBlock); |
| |
| if (tryNestingDepth != 0) |
| { |
| this->ProcessTryHandlerBailout(ehBailoutData->child, --tryNestingDepth); |
| } |
| this->ProcessCatch(); |
| |
| if (--this->nestedCatchDepth == -1) |
| { |
| // unmark the stackFrame as 'in catch block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinCatchBlock); |
| } |
| return; |
| } |
| else |
| { |
| Assert(ehBailoutData->ht == HandlerType::HT_Finally); |
| this->nestedFinallyDepth++; |
| // mark the stackFrame as 'in finally block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| |
| if (tryNestingDepth != 0) |
| { |
| this->ProcessTryHandlerBailout(ehBailoutData->child, --tryNestingDepth); |
| } |
| |
| Js::JavascriptExceptionObject * exceptionObj = this->scriptContext->GetThreadContext()->GetPendingFinallyException(); |
| this->scriptContext->GetThreadContext()->SetPendingFinallyException(nullptr); |
| |
| int finallyEndOffset = this->ProcessFinally(); |
| |
| if (--this->nestedFinallyDepth == -1) |
| { |
| // unmark the stackFrame as 'in finally block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| } |
| |
| // Finally exited with LeaveNull, We don't throw for early returns |
| if (finallyEndOffset == 0 && exceptionObj) |
| { |
| #if ENABLE_NATIVE_CODEGEN |
| if (scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr() != nullptr) |
| { |
| JavascriptExceptionOperators::WalkStackForCleaningUpInlineeInfo(scriptContext, nullptr, scriptContext->GetThreadContext()->GetTryHandlerAddrOfReturnAddr()); |
| } |
| #endif |
| JavascriptExceptionOperators::DoThrow(const_cast<Js::JavascriptExceptionObject *>(exceptionObj), scriptContext); |
| } |
| if (finallyEndOffset != 0) |
| { |
| m_reader.SetCurrentOffset(finallyEndOffset); |
| } |
| |
| return; |
| } |
| |
| if (--this->nestedTryDepth == -1) |
| { |
| // unmark the stackFrame as 'in try block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinTryBlock | InterpreterStackFrameFlags_WithinTryFinallyBlock); |
| } |
| |
| // Now that the stack is unwound, let's run the catch block. |
| if (exception) |
| { |
| if (exception->IsGeneratorReturnException()) |
| { |
| // Generator return scenario, so no need to go into the catch block and we must rethrow to propagate the exception to down level |
| JavascriptExceptionOperators::DoThrow(exception, scriptContext); |
| } |
| if (catchOffset != 0) |
| { |
| exception = exception->CloneIfStaticExceptionObject(scriptContext); |
| // We've got a JS exception. Grab the exception object and assign it to the |
| // catch object's location, then call the handler (i.e., we consume the Catch op here). |
| Var catchObject = exception->GetThrownObject(scriptContext); |
| |
| m_reader.SetCurrentOffset(catchOffset); |
| |
| LayoutSize layoutSize; |
| OpCode catchOp = m_reader.ReadOp(layoutSize); |
| #ifdef BYTECODE_BRANCH_ISLAND |
| if (catchOp == Js::OpCode::BrLong) |
| { |
| Assert(layoutSize == SmallLayout); |
| auto playoutBrLong = m_reader.BrLong(); |
| m_reader.SetCurrentRelativeOffset((const byte *)(playoutBrLong + 1), playoutBrLong->RelativeJumpOffset); |
| catchOp = m_reader.ReadOp(layoutSize); |
| } |
| #endif |
| AssertMsg(catchOp == OpCode::Catch, "Catch op not found at catch offset"); |
| RegSlot reg = layoutSize == SmallLayout ? m_reader.Reg1_Small()->R0 : |
| layoutSize == MediumLayout ? m_reader.Reg1_Medium()->R0 : m_reader.Reg1_Large()->R0; |
| SetReg(reg, catchObject); |
| |
| ResetOut(); |
| |
| this->nestedCatchDepth++; |
| // mark the stackFrame as 'in catch block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinCatchBlock); |
| |
| this->ProcessCatch(); |
| |
| if (--this->nestedCatchDepth == -1) |
| { |
| // unmark the stackFrame as 'in catch block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinCatchBlock); |
| } |
| } |
| else |
| { |
| Assert(finallyOffset != 0); |
| exception = exception->CloneIfStaticExceptionObject(scriptContext); |
| |
| m_reader.SetCurrentOffset(finallyOffset); |
| |
| ResetOut(); |
| |
| this->nestedFinallyDepth++; |
| // mark the stackFrame as 'in finally block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| |
| LayoutSize layoutSize; |
| OpCode finallyOp = m_reader.ReadOp(layoutSize); |
| #ifdef BYTECODE_BRANCH_ISLAND |
| if (finallyOp == Js::OpCode::BrLong) |
| { |
| Assert(layoutSize == SmallLayout); |
| auto playoutBrLong = m_reader.BrLong(); |
| m_reader.SetCurrentRelativeOffset((const byte *)(playoutBrLong + 1), playoutBrLong->RelativeJumpOffset); |
| finallyOp = m_reader.ReadOp(layoutSize); |
| } |
| #endif |
| Assert(finallyOp == Js::OpCode::Finally); |
| |
| int finallyEndOffset = this->ProcessFinally(); |
| |
| if (--this->nestedFinallyDepth == -1) |
| { |
| // unmark the stackFrame as 'in finally block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| } |
| if (finallyEndOffset == 0) |
| { |
| JavascriptExceptionOperators::DoThrow(exception, scriptContext); |
| } |
| m_reader.SetCurrentOffset(finallyEndOffset); |
| } |
| } |
| else |
| { |
| if (finallyOffset != 0) |
| { |
| int currOffset = m_reader.GetCurrentOffset(); |
| |
| m_reader.SetCurrentOffset(finallyOffset); |
| |
| this->nestedFinallyDepth++; |
| |
| // mark the stackFrame as 'in finally block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| |
| LayoutSize layoutSize; |
| OpCode finallyOp = m_reader.ReadOp(layoutSize); |
| #ifdef BYTECODE_BRANCH_ISLAND |
| if (finallyOp == Js::OpCode::BrLong) |
| { |
| Assert(layoutSize == SmallLayout); |
| auto playoutBrLong = m_reader.BrLong(); |
| m_reader.SetCurrentRelativeOffset((const byte *)(playoutBrLong + 1), playoutBrLong->RelativeJumpOffset); |
| finallyOp = m_reader.ReadOp(layoutSize); |
| } |
| #endif |
| Assert(finallyOp == Js::OpCode::Finally); |
| |
| int finallyEndOffset = this->ProcessFinally(); |
| |
| if (--this->nestedFinallyDepth == -1) |
| { |
| // unmark the stackFrame as 'in finally block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| } |
| if (finallyEndOffset == 0) |
| { |
| m_reader.SetCurrentOffset(currOffset); |
| } |
| } |
| } |
| } |
| |
| void InterpreterStackFrame::TrySetRetOffset() |
| { |
| Assert(this->TestFlags(Js::InterpreterStackFrameFlags_WithinTryBlock)); |
| // It may happen that a JITted loop body returned the offset of RET. If the loop body was |
| // called from a try, the interpreter "Process()" should also just return. |
| if (this->retOffset != 0) |
| { |
| m_reader.SetCurrentOffset(this->retOffset); |
| } |
| } |
| |
| bool InterpreterStackFrame::IsInCatchOrFinallyBlock() |
| { |
| return this->TestFlags(Js::InterpreterStackFrameFlags_WithinCatchBlock) || |
| this->TestFlags(Js::InterpreterStackFrameFlags_WithinFinallyBlock); |
| } |
| |
| void InterpreterStackFrame::OP_BeginBodyScope() |
| { |
| // Currently we are using the closures created for the param scope. |
| // This marks the beginning of the body scope, so let's create new closures for the body scope. |
| FunctionBody *executeFunction = this->function->GetFunctionBody(); |
| Assert(!this->IsParamScopeDone() && !executeFunction->IsParamAndBodyScopeMerged()); |
| |
| // Save the current closure. We have to use this while copying the initial value of body symbols |
| // from the corresponding symbols in the param. |
| this->SetParamClosure(this->GetLocalClosure()); |
| this->SetNonVarReg(executeFunction->GetParamClosureRegister(), nullptr); |
| |
| this->SetIsParamScopeDone(true); |
| |
| // Create a new local closure for the body when either body scope has scope slots allocated or |
| // eval is present which can leak declarations. |
| if (executeFunction->scopeSlotArraySize > 0 || executeFunction->HasScopeObject()) |
| { |
| this->InitializeClosures(); |
| } |
| } |
| |
| void InterpreterStackFrame::OP_ResumeCatch() |
| { |
| this->OrFlags(InterpreterStackFrameFlags_WithinCatchBlock); |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (this->IsInDebugMode()) |
| { |
| this->DebugProcess(); |
| } |
| else |
| #endif |
| { |
| this->Process(); |
| } |
| |
| this->ClearFlags(InterpreterStackFrameFlags_WithinCatchBlock); |
| } |
| |
| /// --------------------------------------------------------------------------------------------------- |
| /// The behavior we want is the following: |
| /// - If the control leaves the user's try without throwing, execute the finally and continue |
| /// after the end of the try. |
| /// - If the user code throws, catch this exception and then execute this finally while unwinding to |
| /// the handler (if any). |
| /// --------------------------------------------------------------------------------------------------- |
| void InterpreterStackFrame::ProcessTryFinally(const byte* ip, Js::JumpOffset jumpOffset, Js::RegSlot regException, Js::RegSlot regOffset, bool hasYield) |
| { |
| Js::JavascriptExceptionObject* pExceptionObject = nullptr; |
| bool skipFinallyBlock = false; |
| |
| try |
| { |
| Js::Var result = nullptr; |
| |
| this->nestedTryDepth++; |
| // mark the stackFrame as 'in try block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinTryBlock | InterpreterStackFrameFlags_WithinTryFinallyBlock); |
| |
| if (shouldCacheSP) |
| { |
| CacheSp(); |
| } |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (this->IsInDebugMode()) |
| { |
| #if ENABLE_TTD |
| if (SHOULD_DO_TTD_STACK_STMT_OP(this->scriptContext)) |
| { |
| result = this->ProcessWithDebugging_PreviousStmtTracking(); |
| } |
| else |
| { |
| result = this->ProcessWithDebugging(); |
| } |
| #else |
| result = this->ProcessWithDebugging(); |
| #endif |
| } |
| else |
| #endif |
| { |
| result = this->Process(); |
| } |
| |
| if (result == nullptr) |
| { |
| Assert(hasYield); |
| skipFinallyBlock = true; |
| } |
| } |
| catch (const Js::JavascriptException& err) |
| { |
| pExceptionObject = err.GetAndClear(); |
| } |
| |
| if (--this->nestedTryDepth == -1) |
| { |
| // unmark the stackFrame as 'in try block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinTryBlock | InterpreterStackFrameFlags_WithinTryFinallyBlock); |
| } |
| |
| shouldCacheSP = !skipFinallyBlock; |
| |
| if (skipFinallyBlock) |
| { |
| // A leave occurred due to a yield |
| return; |
| } |
| |
| // Save the current IP so execution can continue there if the finally doesn't |
| // take control of the flow. |
| int newOffset = 0; |
| int currOffset = m_reader.GetCurrentOffset(); |
| if (hasYield) |
| { |
| // save the exception if there is one to a register in case we yield during the finally block |
| // and need to get that exception object back upon resume in OP_ResumeFinally |
| SetNonVarReg(regException, pExceptionObject); |
| SetNonVarReg(regOffset, reinterpret_cast<Js::Var>(currOffset)); |
| } |
| |
| if (pExceptionObject && !pExceptionObject->IsGeneratorReturnException()) |
| { |
| // Clone static exception object early in case finally block overwrites it |
| pExceptionObject = pExceptionObject->CloneIfStaticExceptionObject(scriptContext); |
| } |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (pExceptionObject && this->IsInDebugMode() && |
| pExceptionObject != scriptContext->GetThreadContext()->GetPendingSOErrorObject()) |
| { |
| // Swallowing an exception that has triggered a finally is not implemented |
| // (This appears to be the same behavior as ie8) |
| pExceptionObject->SetDebuggerSkip(false); |
| } |
| #endif |
| |
| // Call into the finally by setting the IP, consuming the Finally, and letting the interpreter recurse. |
| m_reader.SetCurrentRelativeOffset(ip, jumpOffset); |
| |
| RestoreSp(); |
| // mark the stackFrame as 'in finally block' |
| this->OrFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| |
| LayoutSize layoutSize; |
| OpCode finallyOp = m_reader.ReadOp(layoutSize); |
| #ifdef BYTECODE_BRANCH_ISLAND |
| if (finallyOp == Js::OpCode::BrLong) |
| { |
| Assert(layoutSize == SmallLayout); |
| auto playoutBrLong = m_reader.BrLong(); |
| m_reader.SetCurrentRelativeOffset((const byte *)(playoutBrLong + 1), playoutBrLong->RelativeJumpOffset); |
| finallyOp = m_reader.ReadOp(layoutSize); |
| } |
| #endif |
| AssertMsg(finallyOp == OpCode::Finally, "Finally op not found at catch offset"); |
| |
| newOffset = this->ProcessFinally(); |
| |
| if (--this->nestedFinallyDepth == -1) |
| { |
| // unmark the stackFrame as 'in finally block' |
| this->ClearFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| } |
| |
| bool endOfFinallyBlock = newOffset == 0; |
| if (endOfFinallyBlock) |
| { |
| // Finally completed without taking over the flow. Resume where we left off before calling it. |
| m_reader.SetCurrentOffset(currOffset); |
| } |
| else |
| { |
| // Finally seized the flow with a jump out of its scope. Resume at the jump target and |
| // force the runtime to return to this frame without executing the catch. |
| m_reader.SetCurrentOffset(newOffset); |
| return; |
| } |
| |
| if (pExceptionObject && (endOfFinallyBlock || !pExceptionObject->IsGeneratorReturnException())) |
| { |
| JavascriptExceptionOperators::DoThrow(pExceptionObject, scriptContext); |
| } |
| } |
| |
| void InterpreterStackFrame::OP_TryFinally(const unaligned OpLayoutBr* playout) |
| { |
| ProcessTryFinally((const byte*)(playout + 1), playout->RelativeJumpOffset); |
| } |
| |
| void InterpreterStackFrame::OP_TryFinallyWithYield(const byte* ip, Js::JumpOffset jumpOffset, Js::RegSlot regException, Js::RegSlot regOffset) |
| { |
| ProcessTryFinally(ip, jumpOffset, regException, regOffset, true); |
| } |
| |
| void InterpreterStackFrame::OP_ResumeFinally(const byte* ip, Js::JumpOffset jumpOffset, RegSlot exceptionRegSlot, RegSlot offsetRegSlot) |
| { |
| this->OrFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| |
| int newOffset = 0; |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| if (this->IsInDebugMode()) |
| { |
| newOffset = ::Math::PointerCastToIntegral<int>(this->DebugProcess()); |
| } |
| else |
| #endif |
| { |
| newOffset = ::Math::PointerCastToIntegral<int>(this->Process()); |
| } |
| |
| this->ClearFlags(InterpreterStackFrameFlags_WithinFinallyBlock); |
| |
| bool endOfFinallyBlock = newOffset == 0; |
| if (endOfFinallyBlock) |
| { |
| // Finally completed without taking over the flow. Resume where we left off before calling it. |
| int currOffset = ::Math::PointerCastToIntegral<int>(GetNonVarReg(offsetRegSlot)); |
| m_reader.SetCurrentOffset(currOffset); |
| } |
| else |
| { |
| // Finally seized the flow with a jump out of its scope. Resume at the jump target and |
| // force the runtime to return to this frame without executing the catch. |
| m_reader.SetCurrentOffset(newOffset); |
| return; |
| } |
| |
| Js::JavascriptExceptionObject* exceptionObj = (Js::JavascriptExceptionObject*)GetNonVarReg(exceptionRegSlot); |
| if (exceptionObj && (endOfFinallyBlock || !exceptionObj->IsGeneratorReturnException())) |
| { |
| JavascriptExceptionOperators::DoThrow(exceptionObj, scriptContext); |
| } |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_IsInst(const unaligned T* playout) |
| { |
| Var instance = GetReg(playout->R1); |
| Var function = GetReg(playout->R2); |
| IsInstInlineCache *inlineCache = this->GetIsInstInlineCache(playout->inlineCacheIndex); |
| ScriptContext* scriptContext = GetScriptContext(); |
| |
| Var result = JavascriptOperators::OP_IsInst(instance, function, scriptContext, inlineCache); |
| |
| #ifdef ENABLE_BASIC_TELEMETRY |
| { |
| this->scriptContext->GetTelemetry().GetOpcodeTelemetry().IsInstanceOf(instance, function, result); |
| } |
| #endif |
| |
| SetReg(playout->R0, result); |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_ApplyArgs(const unaligned OpLayoutT_Reg5<T> * playout) |
| { |
| // Always save and restore implicit call flags when calling out |
| // REVIEW: Can we avoid it if we don't collect dynamic profile info? |
| ThreadContext * threadContext = scriptContext->GetThreadContext(); |
| Js::ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| // Currently ApplyArgs is equivalent to CallFldVoid (where we don't use the return value) |
| Var v = GetNonVarReg(playout->R4); |
| JavascriptOperators::OP_ApplyArgs(GetReg(playout->R1), GetReg(playout->R2), |
| (void**)GetNonVarReg(playout->R3), *((CallInfo*)&v), GetScriptContext()); |
| threadContext->SetImplicitCallFlags(savedImplicitCallFlags); |
| } |
| |
| void InterpreterStackFrame::OP_SpreadArrayLiteral(const unaligned OpLayoutReg2Aux * playout) |
| { |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| Var instance = GetReg(playout->R1); |
| #if ENABLE_COPYONACCESS_ARRAY |
| JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(instance); |
| #endif |
| const Js::AuxArray<uint32> *spreadIndices = m_reader.ReadAuxArray<uint32>(playout->Offset, this->GetFunctionBody()); |
| ScriptContext* scriptContext = GetScriptContext(); |
| Var result = JavascriptArray::SpreadArrayArgs(instance, spreadIndices, scriptContext); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| |
| SetReg(playout->R0, result); |
| } |
| |
| FrameDisplay * |
| InterpreterStackFrame::OP_LdInnerFrameDisplay(void *argHead, void *argEnv, ScriptContext *scriptContext) |
| { |
| JavascriptOperators::CheckInnerFrameDisplayArgument(argHead); |
| return OP_LdFrameDisplay(argHead, argEnv, scriptContext); |
| } |
| |
| FrameDisplay * |
| InterpreterStackFrame::OP_LdInnerFrameDisplayNoParent(void *argHead, ScriptContext *scriptContext) |
| { |
| JavascriptOperators::CheckInnerFrameDisplayArgument(argHead); |
| return OP_LdFrameDisplayNoParent<true>(argHead, scriptContext); |
| } |
| |
| FrameDisplay * |
| InterpreterStackFrame::OP_LdFrameDisplay(void *argHead, void *argEnv, ScriptContext *scriptContext) |
| { |
| FrameDisplay *frameDisplay; |
| bool strict = this->m_functionBody->GetIsStrictMode(); |
| if (strict) |
| { |
| frameDisplay = JavascriptOperators::OP_LdStrictFrameDisplay(argHead, argEnv, scriptContext); |
| } |
| else |
| { |
| frameDisplay = JavascriptOperators::OP_LdFrameDisplay(argHead, argEnv, scriptContext); |
| } |
| return frameDisplay; |
| } |
| |
| FrameDisplay * |
| InterpreterStackFrame::OP_LdFrameDisplaySetLocal(void *argHead, void *argEnv, ScriptContext *scriptContext) |
| { |
| FrameDisplay *frameDisplay = OP_LdFrameDisplay(argHead, argEnv, scriptContext); |
| this->SetLocalFrameDisplay(frameDisplay); |
| return frameDisplay; |
| } |
| |
| FrameDisplay * |
| InterpreterStackFrame::NewFrameDisplay(void *argHead, void *argEnv) |
| { |
| FrameDisplay *frameDisplay; |
| bool strict = this->m_functionBody->GetIsStrictMode(); |
| |
| if (!this->m_functionBody->DoStackFrameDisplay() || !this->GetLocalFrameDisplay()) |
| { |
| // Null local frame display probably indicates that we bailed out of an inlinee. |
| // Once we support stack closures in inlined functions, we can just assert that this value |
| // is never null if we should be allocating on the stack. |
| return this->OP_LdFrameDisplaySetLocal(argHead, argEnv, this->GetScriptContext()); |
| } |
| |
| frameDisplay = this->GetLocalFrameDisplay(); |
| Assert(frameDisplay != nullptr); |
| |
| frameDisplay->SetTag(true); |
| frameDisplay->SetStrictMode(strict); |
| frameDisplay->SetLength(this->m_functionBody->GetEnvDepth() + 1); |
| Assert(frameDisplay->GetLength() == ((FrameDisplay*)argEnv)->GetLength() + 1); |
| |
| for (uint i = 0; i < ((FrameDisplay*)argEnv)->GetLength(); i++) |
| { |
| frameDisplay->SetItem(i + 1, ((FrameDisplay*)argEnv)->GetItem(i)); |
| } |
| frameDisplay->SetItem(0, argHead); |
| |
| return frameDisplay; |
| } |
| |
| template<bool innerFD> |
| FrameDisplay * |
| InterpreterStackFrame::OP_LdFrameDisplayNoParent(void *argHead, ScriptContext *scriptContext) |
| { |
| FrameDisplay *frameDisplay; |
| bool strict = this->m_functionBody->GetIsStrictMode(); |
| |
| Var argEnv = nullptr; |
| if (innerFD && this->m_functionBody->GetLocalFrameDisplayRegister() != Constants::NoRegister) |
| { |
| argEnv = this->GetLocalFrameDisplay(); |
| } |
| if (argEnv == nullptr && this->m_functionBody->GetEnvRegister() != Constants::NoRegister) |
| { |
| argEnv = this->LdEnv(); |
| } |
| |
| if (argEnv == nullptr) |
| { |
| if (strict) |
| { |
| frameDisplay = JavascriptOperators::OP_LdStrictFrameDisplayNoParent(argHead, scriptContext); |
| } |
| else |
| { |
| frameDisplay = JavascriptOperators::OP_LdFrameDisplayNoParent(argHead, scriptContext); |
| } |
| } |
| else |
| { |
| if (strict) |
| { |
| frameDisplay = JavascriptOperators::OP_LdStrictFrameDisplay(argHead, argEnv, scriptContext); |
| } |
| else |
| { |
| frameDisplay = JavascriptOperators::OP_LdFrameDisplay(argHead, argEnv, scriptContext); |
| } |
| } |
| return frameDisplay; |
| } |
| |
| FrameDisplay * |
| InterpreterStackFrame::OP_LdFuncExprFrameDisplaySetLocal(void *argHead1, void *argHead2, ScriptContext *scriptContext) |
| { |
| FrameDisplay *frameDisplay = OP_LdFrameDisplayNoParent<false>(argHead2, scriptContext); |
| frameDisplay = OP_LdFrameDisplay(argHead1, frameDisplay, scriptContext); |
| this->SetLocalFrameDisplay(frameDisplay); |
| return frameDisplay; |
| } |
| |
| FrameDisplay* InterpreterStackFrame::GetLocalFrameDisplay() const |
| { |
| Assert(this->localFrameDisplay == nullptr || this->IsClosureInitDone() || this->localFrameDisplay->GetLength() == 0); |
| return this->localFrameDisplay; |
| } |
| |
| void InterpreterStackFrame::SetLocalFrameDisplay(FrameDisplay* frameDisplay) |
| { |
| this->localFrameDisplay = frameDisplay; |
| } |
| |
| Var InterpreterStackFrame::GetLocalClosure() const |
| { |
| return this->localClosure; |
| } |
| |
| void InterpreterStackFrame::SetLocalClosure(Var closure) |
| { |
| this->localClosure = closure; |
| } |
| |
| Var InterpreterStackFrame::GetParamClosure() const |
| { |
| return this->paramClosure; |
| } |
| |
| void InterpreterStackFrame::SetParamClosure(Var closure) |
| { |
| this->paramClosure = closure; |
| } |
| |
| void |
| InterpreterStackFrame::OP_NewInnerScopeSlots(uint innerScopeIndex, uint count, int scopeIndex, ScriptContext *scriptContext, FunctionBody *functionBody) |
| { |
| Field(Var)* slotArray = |
| JavascriptOperators::OP_NewScopeSlotsWithoutPropIds(count, scopeIndex, scriptContext, functionBody); |
| this->SetInnerScopeFromIndex(innerScopeIndex, slotArray); |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_CloneInnerScopeSlots(const unaligned OpLayoutT_Unsigned1<T> *playout) |
| { |
| uint innerScopeIndex = playout->C1; |
| Field(Var) * slotArray; |
| |
| slotArray = (Field(Var)*)this->InnerScopeFromIndex(innerScopeIndex); |
| slotArray = JavascriptOperators::OP_CloneScopeSlots(slotArray, scriptContext); |
| this->SetInnerScopeFromIndex(innerScopeIndex, slotArray); |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_CloneBlockScope(const unaligned OpLayoutT_Unsigned1<T> *playout) |
| { |
| uint innerScopeIndex = playout->C1; |
| Var scope = this->InnerScopeFromIndex(innerScopeIndex); |
| BlockActivationObject* blockScope = BlockActivationObject::FromVar(scope); |
| |
| scope = JavascriptOperators::OP_CloneBlockScope(blockScope, scriptContext); |
| this->SetInnerScopeFromIndex(innerScopeIndex, scope); |
| } |
| |
| Field(Var)* |
| InterpreterStackFrame::NewScopeSlots(unsigned int size, ScriptContext *scriptContext, Var scope) |
| { |
| Field(Var)* slotArray = JavascriptOperators::OP_NewScopeSlots(size, scriptContext, scope); |
| this->SetLocalClosure(slotArray); |
| return slotArray; |
| } |
| |
| Field(Var)* |
| InterpreterStackFrame::NewScopeSlots() |
| { |
| Field(Var)* slotArray; |
| FunctionBody * functionBody = this->m_functionBody; |
| uint scopeSlotCount = this->IsParamScopeDone() ? functionBody->scopeSlotArraySize : functionBody->paramScopeSlotArraySize; |
| Assert(scopeSlotCount != 0); |
| |
| if (!functionBody->DoStackScopeSlots()) |
| { |
| return this->NewScopeSlots( |
| scopeSlotCount + ScopeSlots::FirstSlotIndex, this->GetScriptContext(), (Var)functionBody->GetFunctionInfo()); |
| } |
| |
| slotArray = (Field(Var)*)this->GetLocalClosure(); |
| Assert(slotArray != nullptr); |
| |
| ScopeSlots scopeSlots(slotArray); |
| scopeSlots.SetCount(scopeSlotCount); |
| scopeSlots.SetScopeMetadata((Var)functionBody->GetFunctionInfo()); |
| Var undef = functionBody->GetScriptContext()->GetLibrary()->GetUndefined(); |
| for (unsigned int i = 0; i < scopeSlotCount; i++) |
| { |
| scopeSlots.Set(i, undef); |
| } |
| |
| return slotArray; |
| } |
| |
| Var |
| InterpreterStackFrame::NewScopeObject() |
| { |
| Var scopeObject; |
| |
| if (m_functionBody->HasCachedScopePropIds()) |
| { |
| const Js::PropertyIdArray *propIds = this->m_functionBody->GetFormalsPropIdArray(); |
| |
| JavascriptFunction* funcExpr = this->GetFunctionExpression(); |
| PropertyId objectId = ActivationObjectEx::GetLiteralObjectRef(propIds); |
| scopeObject = JavascriptOperators::OP_InitCachedScope(funcExpr, propIds, |
| this->GetFunctionBody()->GetObjectLiteralTypeRef(objectId), |
| propIds->hasNonSimpleParams, GetScriptContext()); |
| |
| } |
| else |
| { |
| scopeObject = JavascriptOperators::OP_NewScopeObject(GetScriptContext()); |
| } |
| this->SetLocalClosure(scopeObject); |
| return scopeObject; |
| } |
| |
| FrameDisplay * |
| InterpreterStackFrame::GetFrameDisplayForNestedFunc() const |
| { |
| if (this->localFrameDisplay == nullptr) |
| { |
| return (FrameDisplay*)LdEnv(); |
| } |
| return this->localFrameDisplay; |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_NewStackScFunc(const unaligned T * playout) |
| { |
| uint funcIndex = playout->SlotIndex; |
| FrameDisplay *frameDisplay = this->GetFrameDisplayForNestedFunc(); |
| SetRegAllowStackVarEnableOnly(playout->Value, |
| StackScriptFunction::OP_NewStackScFunc(frameDisplay, |
| this->m_functionBody->GetNestedFuncReference(funcIndex), |
| this->GetStackNestedFunction(funcIndex))); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_NewInnerStackScFunc(const unaligned T * playout) |
| { |
| uint funcIndex = playout->SlotIndex; |
| FrameDisplay *frameDisplay = (FrameDisplay*)GetNonVarReg(playout->Instance); |
| SetRegAllowStackVarEnableOnly(playout->Value, |
| StackScriptFunction::OP_NewStackScFunc(frameDisplay, |
| this->m_functionBody->GetNestedFuncReference(funcIndex), |
| this->GetStackNestedFunction(funcIndex))); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_DeleteFld(const unaligned T * playout) |
| { |
| Var result = JavascriptOperators::OP_DeleteProperty(GetReg(playout->Instance), m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetScriptContext()); |
| SetReg(playout->Value, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_DeleteLocalFld(const unaligned T * playout) |
| { |
| Var result = JavascriptOperators::OP_DeleteProperty(this->localClosure, m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetScriptContext()); |
| SetReg(playout->Instance, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_DeleteRootFld(const unaligned T * playout) |
| { |
| Var result = JavascriptOperators::OP_DeleteRootProperty(GetReg(playout->Instance), m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetScriptContext()); |
| SetReg(playout->Value, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_DeleteFldStrict(const unaligned T * playout) |
| { |
| Var result = JavascriptOperators::OP_DeleteProperty(GetReg(playout->Instance), m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetScriptContext(), PropertyOperation_StrictMode); |
| SetReg(playout->Value, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_DeleteRootFldStrict(const unaligned T * playout) |
| { |
| Var result = JavascriptOperators::OP_DeleteRootProperty(GetReg(playout->Instance), m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetScriptContext(), PropertyOperation_StrictMode); |
| SetReg(playout->Value, result); |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_ScopedDeleteFld(const unaligned OpLayoutT_ElementScopedC<T> * playout) |
| { |
| // Implicit root object as default instance |
| Var result = JavascriptOperators::OP_DeletePropertyScoped(GetEnvForEvalCode(), |
| m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), |
| GetReg(Js::FunctionBody::RootObjectRegSlot), GetScriptContext()); |
| SetReg(playout->Value, result); |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_ScopedDeleteFldStrict(const unaligned OpLayoutT_ElementScopedC<T> * playout) |
| { |
| // Implicit root object as default instance |
| Var result = JavascriptOperators::OP_DeletePropertyScoped(GetEnvForEvalCode(), |
| m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), |
| GetReg(Js::FunctionBody::RootObjectRegSlot), GetScriptContext(), PropertyOperation_StrictMode); |
| SetReg(playout->Value, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ScopedLdInst(const unaligned T * playout) |
| { |
| Var thisVar = nullptr; |
| Var rootObject = GetFunctionBody()->GetRootObject(); |
| Var result = JavascriptOperators::OP_GetInstanceScoped(GetEnvForEvalCode(), |
| m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), rootObject, &thisVar, GetScriptContext()); |
| SetReg(playout->Value, result); |
| SetReg(playout->Value2, thisVar); |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::OP_ScopedInitFunc(const unaligned OpLayoutT_ElementScopedC<T> * playout) |
| { |
| JavascriptOperators::OP_InitFuncScoped(GetEnvForEvalCode(), |
| m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), |
| GetReg(playout->Value), GetReg(Js::FunctionBody::RootObjectRegSlot), GetScriptContext()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ClearAttributes(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_ClearAttributes(GetReg(playout->Instance), m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitGetFld(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitGetter(GetReg(playout->Instance), m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetReg(playout->Value)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitSetFld(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitSetter(GetReg(playout->Instance), m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetReg(playout->Value)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitSetElemI(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitElemSetter( |
| GetReg(playout->Instance), |
| GetReg(playout->Element), |
| GetReg(playout->Value), |
| m_functionBody->GetScriptContext() |
| ); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitGetElemI(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitElemGetter( |
| GetReg(playout->Instance), |
| GetReg(playout->Element), |
| GetReg(playout->Value), |
| m_functionBody->GetScriptContext() |
| ); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitComputedProperty(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitComputedProperty( |
| GetReg(playout->Instance), |
| GetReg(playout->Element), |
| GetReg(playout->Value), |
| m_functionBody->GetScriptContext() |
| ); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitProto(const unaligned T * playout) |
| { |
| JavascriptOperators::OP_InitProto(GetReg(playout->Instance), m_functionBody->GetReferencedPropertyId(playout->PropertyIdIndex), GetReg(playout->Value)); |
| } |
| |
| void InterpreterStackFrame::DoInterruptProbe() |
| { |
| PROBE_STACK(scriptContext, 0); |
| } |
| |
| void InterpreterStackFrame::InitializeStackFunctions(StackScriptFunction * scriptFunctions) |
| { |
| this->stackNestedFunctions = scriptFunctions; |
| FunctionBody * functionBody = this->m_functionBody; |
| uint nestedCount = functionBody->GetNestedCount(); |
| for (uint i = 0; i < nestedCount; i++) |
| { |
| StackScriptFunction * stackScriptFunction = scriptFunctions + i; |
| FunctionProxy* nestedProxy = functionBody->GetNestedFunctionProxy(i); |
| ScriptFunctionType* type = nestedProxy->EnsureDeferredPrototypeType(); |
| new (stackScriptFunction)StackScriptFunction(nestedProxy, type); |
| } |
| } |
| |
| StackScriptFunction * InterpreterStackFrame::GetStackNestedFunction(uint index) |
| { |
| Assert(index < this->m_functionBody->GetNestedCount()); |
| // Re-check if we have disable stack nested function |
| if (this->m_functionBody->DoStackNestedFunc()) |
| { |
| return this->stackNestedFunctions + index; |
| } |
| return nullptr; |
| } |
| |
| void InterpreterStackFrame::SetExecutingStackFunction(ScriptFunction * scriptFunction) |
| { |
| Assert(ThreadContext::IsOnStack(this->function)); |
| Assert(this->m_functionBody == scriptFunction->GetFunctionBody()); |
| this->function = scriptFunction; |
| } |
| |
| DWORD_PTR InterpreterStackFrame::GetStackAddress() const |
| { |
| return m_stackAddress; |
| } |
| |
| void* InterpreterStackFrame::GetAddressOfReturnAddress() const |
| { |
| return this->addressOfReturnAddress; |
| } |
| |
| template <class T> |
| const byte * InterpreterStackFrame::OP_Br(const unaligned T * playout) |
| { |
| return m_reader.SetCurrentRelativeOffset((const byte *)(playout + 1), playout->RelativeJumpOffset); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_InitClass(const unaligned OpLayoutT_Class<T> * playout) |
| { |
| JavascriptOperators::OP_InitClass(GetReg(playout->Constructor), playout->Extends != Js::Constants::NoRegister ? GetReg(playout->Extends) : NULL, GetScriptContext()); |
| } |
| |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| template <class T> |
| void InterpreterStackFrame::OP_EmitTmpRegCount(const unaligned OpLayoutT_Unsigned1<T> * playout) |
| { |
| this->scriptContext->GetDebugContext()->GetProbeContainer()->SetCurrentTmpRegCount(playout->C1); |
| } |
| #endif |
| |
| Var InterpreterStackFrame::OP_LdHomeObj(ScriptContext * scriptContext) |
| { |
| return JavascriptOperators::OP_LdHomeObj(function, scriptContext); |
| } |
| |
| Var InterpreterStackFrame::OP_LdFuncObj(ScriptContext * scriptContext) |
| { |
| return JavascriptOperators::OP_LdFuncObj(function, scriptContext); |
| } |
| |
| Var InterpreterStackFrame::OP_ImportCall(Var specifier, ScriptContext *scriptContext) |
| { |
| return JavascriptOperators::OP_ImportCall(function, specifier, scriptContext); |
| } |
| |
| void InterpreterStackFrame::ValidateRegValue(Var value, bool allowStackVar, bool allowStackVarOnDisabledStackNestedFunc) const |
| { |
| #if DBG |
| if (value != nullptr && !TaggedNumber::Is(value)) |
| { |
| if (!allowStackVar || !this->m_functionBody->DoStackNestedFunc()) |
| { |
| Assert(!ThreadContext::IsOnStack(value) |
| || (allowStackVar && allowStackVarOnDisabledStackNestedFunc && StackScriptFunction::IsBoxed(value))); |
| } |
| Assert(!CrossSite::NeedMarshalVar(value, GetScriptContext())); |
| } |
| #endif |
| } |
| |
| template <typename RegSlotType> |
| Var InterpreterStackFrame::GetReg(RegSlotType localRegisterID) const |
| { |
| Var value = m_localSlots[localRegisterID]; |
| ValidateRegValue(value); |
| return value; |
| } |
| |
| template <typename RegSlotType> |
| void InterpreterStackFrame::SetReg(RegSlotType localRegisterID, Var value) |
| { |
| Assert(localRegisterID == 0 || localRegisterID >= m_functionBody->GetConstantCount()); |
| ValidateRegValue(value); |
| m_localSlots[localRegisterID] = value; |
| |
| #if ENABLE_OBJECT_SOURCE_TRACKING |
| if (value != nullptr && DynamicType::Is(Js::JavascriptOperators::GetTypeId(value))) |
| { |
| static_cast<DynamicObject*>(value)->SetDiagOriginInfoAsNeeded(); |
| } |
| #endif |
| |
| #if ENABLE_VALUE_TRACE |
| if (this->function->GetScriptContext()->ShouldPerformRecordOrReplayAction()) |
| { |
| this->function->GetScriptContext()->GetThreadContext()->TTDExecutionInfo->GetTraceLogger()->WriteTraceValue(value); |
| } |
| #endif |
| } |
| |
| template <typename T> |
| T InterpreterStackFrame::GetRegRaw(RegSlot localRegisterID) const |
| { |
| return (T)m_localIntSlots[localRegisterID]; |
| } |
| |
| // specialized version for doubles |
| template <> |
| double VECTORCALL InterpreterStackFrame::GetRegRaw(RegSlot localRegisterID) const |
| { |
| return (double)m_localDoubleSlots[localRegisterID]; |
| } |
| |
| template <> |
| float VECTORCALL InterpreterStackFrame::GetRegRaw(RegSlot localRegisterID) const |
| { |
| return (float)m_localFloatSlots[localRegisterID]; |
| } |
| |
| template <> |
| int64 InterpreterStackFrame::GetRegRaw(RegSlot localRegisterID) const |
| { |
| return m_localInt64Slots[localRegisterID]; |
| } |
| |
| template <typename T> |
| void InterpreterStackFrame::SetRegRaw(RegSlot localRegisterID, T bValue) |
| { |
| m_localIntSlots[localRegisterID] = (int)bValue; |
| } |
| |
| template <> |
| void InterpreterStackFrame::SetRegRaw(RegSlot localRegisterID, float bValue) |
| { |
| m_localFloatSlots[localRegisterID] = (float)bValue; |
| } |
| |
| template <> |
| void InterpreterStackFrame::SetRegRaw(RegSlot localRegisterID, double bValue) |
| { |
| m_localDoubleSlots[localRegisterID] = bValue; |
| } |
| |
| template <> |
| void InterpreterStackFrame::SetRegRaw(RegSlot localRegisterID, int64 bValue) |
| { |
| m_localInt64Slots[localRegisterID] = bValue; |
| } |
| |
| template <typename RegSlotType> |
| int InterpreterStackFrame::GetRegRawInt(RegSlotType localRegisterID) const |
| { |
| return m_localIntSlots[localRegisterID]; |
| } |
| template <typename RegSlotType> |
| double InterpreterStackFrame::GetRegRawDouble(RegSlotType localRegisterID) const |
| { |
| return m_localDoubleSlots[localRegisterID]; |
| } |
| |
| template <typename RegSlotType> |
| float InterpreterStackFrame::GetRegRawFloat(RegSlotType localRegisterID) const |
| { |
| return m_localFloatSlots[localRegisterID]; |
| } |
| |
| template <typename RegSlotType> |
| void InterpreterStackFrame::SetRegRawInt(RegSlotType localRegisterID, int bValue) |
| { |
| m_localIntSlots[localRegisterID] = bValue; |
| } |
| |
| template <typename RegSlotType> |
| int64 InterpreterStackFrame::GetRegRawInt64(RegSlotType localRegisterID) const |
| { |
| return m_localInt64Slots[localRegisterID]; |
| } |
| |
| template <typename RegSlotType> |
| void* InterpreterStackFrame::GetRegRawPtr(RegSlotType localRegisterID) const |
| { |
| #if TARGET_32 |
| return (void*)m_localIntSlots[localRegisterID]; |
| #elif TARGET_64 |
| return (void*)m_localInt64Slots[localRegisterID]; |
| #endif |
| } |
| |
| template <typename RegSlotType> |
| void InterpreterStackFrame::SetRegRawPtr(RegSlotType localRegisterID, void* val) |
| { |
| #if TARGET_32 |
| m_localIntSlots[localRegisterID] = (int32)val; |
| #elif TARGET_64 |
| m_localInt64Slots[localRegisterID] = (int64)val; |
| #endif |
| } |
| |
| template <typename RegSlotType> |
| void InterpreterStackFrame::SetRegRawInt64(RegSlotType localRegisterID, int64 bValue) |
| { |
| m_localInt64Slots[localRegisterID] = bValue; |
| } |
| |
| template <typename RegSlotType> |
| void InterpreterStackFrame::SetRegRawDouble(RegSlotType localRegisterID, double bValue) |
| { |
| m_localDoubleSlots[localRegisterID] = bValue; |
| } |
| |
| template <typename RegSlotType> |
| void InterpreterStackFrame::SetRegRawFloat(RegSlotType localRegisterID, float bValue) |
| { |
| m_localFloatSlots[localRegisterID] = bValue; |
| } |
| |
| template <typename RegSlotType> |
| Var InterpreterStackFrame::GetRegAllowStackVar(RegSlotType localRegisterID) const |
| { |
| Var value = m_localSlots[localRegisterID]; |
| ValidateRegValue(value, true); |
| return value; |
| } |
| |
| template <typename RegSlotType> |
| void InterpreterStackFrame::SetRegAllowStackVar(RegSlotType localRegisterID, Var value) |
| { |
| Assert(localRegisterID == 0 || localRegisterID >= m_functionBody->GetConstantCount()); |
| ValidateRegValue(value, true); |
| m_localSlots[localRegisterID] = value; |
| |
| #if ENABLE_OBJECT_SOURCE_TRACKING |
| if (value != nullptr && DynamicType::Is(Js::JavascriptOperators::GetTypeId(value))) |
| { |
| static_cast<DynamicObject*>(value)->SetDiagOriginInfoAsNeeded(); |
| } |
| #endif |
| |
| #if ENABLE_VALUE_TRACE |
| if (this->function->GetScriptContext()->ShouldPerformRecordOrReplayAction()) |
| { |
| this->function->GetScriptContext()->GetThreadContext()->TTDExecutionInfo->GetTraceLogger()->WriteTraceValue(value); |
| } |
| #endif |
| } |
| |
| template <typename RegSlotType> |
| Var InterpreterStackFrame::GetRegAllowStackVarEnableOnly(RegSlotType localRegisterID) const |
| { |
| Var value = m_localSlots[localRegisterID]; |
| ValidateRegValue(value, true, false); |
| return value; |
| } |
| |
| template <typename RegSlotType> |
| void InterpreterStackFrame::SetRegAllowStackVarEnableOnly(RegSlotType localRegisterID, Var value) |
| { |
| Assert(localRegisterID == 0 || localRegisterID >= m_functionBody->GetConstantCount()); |
| ValidateRegValue(value, true, false); |
| m_localSlots[localRegisterID] = value; |
| |
| #if ENABLE_OBJECT_SOURCE_TRACKING |
| if (value != nullptr && DynamicType::Is(Js::JavascriptOperators::GetTypeId(value))) |
| { |
| static_cast<DynamicObject*>(value)->SetDiagOriginInfoAsNeeded(); |
| } |
| #endif |
| |
| #if ENABLE_VALUE_TRACE |
| if (this->function->GetScriptContext()->ShouldPerformRecordOrReplayAction()) |
| { |
| this->function->GetScriptContext()->GetThreadContext()->TTDExecutionInfo->GetTraceLogger()->WriteTraceValue(value); |
| } |
| #endif |
| } |
| |
| template <> |
| AsmJsSIMDValue InterpreterStackFrame::GetRegRaw(RegSlot localRegisterID) const |
| { |
| return (AsmJsSIMDValue)m_localSimdSlots[localRegisterID]; |
| } |
| |
| template<> |
| void InterpreterStackFrame::SetRegRaw(RegSlot localRegisterID, AsmJsSIMDValue bValue) |
| { |
| m_localSimdSlots[localRegisterID] = bValue; |
| } |
| |
| template <typename RegSlotType> |
| AsmJsSIMDValue InterpreterStackFrame::GetRegRawSimd(RegSlotType localRegisterID) const |
| { |
| return m_localSimdSlots[localRegisterID]; |
| } |
| |
| template <typename RegSlotType> |
| void InterpreterStackFrame::SetRegRawSimd(RegSlotType localRegisterID, AsmJsSIMDValue bValue) |
| { |
| m_localSimdSlots[localRegisterID] = bValue; |
| } |
| |
| int InterpreterStackFrame::OP_GetMemorySize() |
| { |
| #ifdef ENABLE_WASM |
| return (int)GetWebAssemblyMemory()->GetCurrentMemoryPages(); |
| #else |
| Assert(UNREACHED); |
| return 0; |
| #endif |
| } |
| |
| int InterpreterStackFrame::OP_GrowMemory(int32 delta) |
| { |
| #ifdef ENABLE_WASM |
| return GetWebAssemblyMemory()->GrowInternal((uint32)delta); |
| #else |
| Assert(UNREACHED); |
| return 0; |
| #endif |
| } |
| |
| template <typename T, InterpreterStackFrame::AsmJsMathPtr<T> func> T InterpreterStackFrame::OP_UnsignedDivRemCheck(T aLeft, T aRight, ScriptContext* scriptContext) |
| { |
| if (aRight == 0) |
| { |
| JavascriptError::ThrowWebAssemblyRuntimeError(scriptContext, WASMERR_DivideByZero); |
| } |
| |
| return func(aLeft, aRight); |
| } |
| |
| template <typename T, InterpreterStackFrame::AsmJsMathPtr<T> func> T InterpreterStackFrame::OP_DivOverflow(T aLeft, T aRight, ScriptContext* scriptContext) |
| { |
| if (aRight == 0) |
| { |
| JavascriptError::ThrowWebAssemblyRuntimeError(scriptContext, WASMERR_DivideByZero); |
| } |
| |
| if (aLeft == SignedTypeTraits<T>::MinValue && aRight == -1) |
| { |
| JavascriptError::ThrowWebAssemblyRuntimeError(scriptContext, VBSERR_Overflow); |
| } |
| |
| return func(aLeft, aRight); |
| } |
| |
| template <typename T, InterpreterStackFrame::AsmJsMathPtr<T> func> T InterpreterStackFrame::OP_RemOverflow(T aLeft, T aRight, ScriptContext* scriptContext) |
| { |
| if (aRight == 0) |
| { |
| JavascriptError::ThrowWebAssemblyRuntimeError(scriptContext, WASMERR_DivideByZero); |
| } |
| |
| if (aLeft == SignedTypeTraits<T>::MinValue && aRight == -1) |
| { |
| return 0; |
| } |
| |
| return func(aLeft, aRight); |
| } |
| |
| void InterpreterStackFrame::OP_Unreachable() |
| { |
| JavascriptError::ThrowUnreachable(scriptContext); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdLdArrGeneric(const unaligned T* playout) |
| { |
| Assert(playout->ViewType < Js::ArrayBufferView::TYPE_COUNT); |
| const uint64 index = ((uint64)(uint32)GetRegRawInt(playout->SlotIndex) + playout->Offset /* WASM only */) & (int64)(int)ArrayBufferView::ViewMask[playout->ViewType]; |
| |
| ArrayBufferBase* arr = |
| #ifdef ENABLE_WASM_SIMD |
| (m_functionBody->IsWasmFunction()) ? |
| m_wasmMemory->GetBuffer() : |
| #endif |
| GetAsmJsBuffer(); |
| |
| BYTE* buffer = arr->GetBuffer(); |
| uint8 dataWidth = playout->DataWidth; |
| RegSlot dstReg = playout->Value; |
| if (index + dataWidth > arr->GetByteLength()) |
| { |
| JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange, _u("Simd typed array access")); |
| } |
| AsmJsSIMDValue *data = (AsmJsSIMDValue*)(buffer + index); |
| AsmJsSIMDValue value; |
| |
| value = SIMDUtils::SIMDLdData(data, dataWidth); |
| SetRegRawSimd(dstReg, value); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdLdArrConstIndex(const unaligned T* playout) |
| { |
| Assert(playout->ViewType < Js::ArrayBufferView::TYPE_COUNT); |
| const uint64 index = (uint32)playout->SlotIndex; |
| JavascriptArrayBuffer* arr = GetAsmJsBuffer(); |
| BYTE* buffer = arr->GetBuffer(); |
| uint8 dataWidth = playout->DataWidth; |
| RegSlot dstReg = playout->Value; |
| |
| if (index + dataWidth > arr->GetByteLength()) |
| { |
| JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange, _u("Simd typed array access")); |
| } |
| AsmJsSIMDValue *data = (AsmJsSIMDValue*)(buffer + index); |
| AsmJsSIMDValue value; |
| |
| value = SIMDUtils::SIMDLdData(data, dataWidth); |
| SetRegRawSimd(dstReg, value); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdStArrGeneric(const unaligned T* playout) |
| { |
| Assert(playout->ViewType < Js::ArrayBufferView::TYPE_COUNT); |
| const uint64 index = ((uint64)(uint32)GetRegRawInt(playout->SlotIndex) + playout->Offset /* WASM only */) & (int64)(int)ArrayBufferView::ViewMask[playout->ViewType]; |
| |
| ArrayBufferBase* arr = |
| #ifdef ENABLE_WASM_SIMD |
| (m_functionBody->IsWasmFunction()) ? |
| m_wasmMemory->GetBuffer() : |
| #endif |
| GetAsmJsBuffer(); |
| |
| BYTE* buffer = arr->GetBuffer(); |
| uint8 dataWidth = playout->DataWidth; |
| RegSlot srcReg = playout->Value; |
| |
| if (index + dataWidth > arr->GetByteLength()) |
| { |
| JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange, _u("Simd typed array access")); |
| } |
| AsmJsSIMDValue *data = (AsmJsSIMDValue*)(buffer + index); |
| AsmJsSIMDValue value = GetRegRawSimd(srcReg); |
| SIMDUtils::SIMDStData(data, value, dataWidth); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdStArrConstIndex(const unaligned T* playout) |
| { |
| Assert(playout->ViewType < Js::ArrayBufferView::TYPE_COUNT); |
| const uint64 index = (uint32)playout->SlotIndex; |
| JavascriptArrayBuffer* arr = GetAsmJsBuffer(); |
| BYTE* buffer = arr->GetBuffer(); |
| uint8 dataWidth = playout->DataWidth; |
| RegSlot srcReg = playout->Value; |
| |
| if (index + dataWidth > arr->GetByteLength()) |
| { |
| JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange, _u("Simd typed array access")); |
| } |
| AsmJsSIMDValue *data = (AsmJsSIMDValue*)(buffer + index); |
| AsmJsSIMDValue value = GetRegRawSimd(srcReg); |
| SIMDUtils::SIMDStData(data, value, dataWidth); |
| |
| } |
| |
| |
| bool InterpreterStackFrame::SIMDAnyNaN(AsmJsSIMDValue& input) |
| { |
| if (!GetFunctionBody()->IsWasmFunction()) |
| { |
| return false; |
| } |
| |
| AsmJsSIMDValue compResult = SIMDFloat32x4Operation::OpEqual(input, input); |
| return !SIMDBool32x4Operation::OpAllTrue(compResult); |
| } |
| |
| // handler for SIMD.Int32x4.FromFloat32x4 |
| template <class T> |
| void InterpreterStackFrame::OP_SimdInt32x4FromFloat32x4(const unaligned T* playout) |
| { |
| bool throws = false; |
| AsmJsSIMDValue input = GetRegRawSimd(playout->F4_1); |
| AsmJsSIMDValue result{ 0 }; |
| |
| #ifdef ENABLE_WASM_SIMD |
| throws = SIMDAnyNaN(input); |
| if (!throws) |
| #endif |
| { |
| result = SIMDInt32x4Operation::OpFromFloat32x4(input, throws); |
| } |
| |
| // value is out of bound |
| if (throws) |
| { |
| JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange, _u("SIMD.Int32x4.FromFloat32x4")); |
| } |
| SetRegRawSimd(playout->I4_0, result); |
| } |
| // handler for SIMD.Uint32x4.FromFloat32x4 |
| template <class T> |
| void InterpreterStackFrame::OP_SimdUint32x4FromFloat32x4(const unaligned T* playout) |
| { |
| bool throws = false; |
| AsmJsSIMDValue input = GetRegRawSimd(playout->F4_1); |
| AsmJsSIMDValue result{ 0 }; |
| |
| #ifdef ENABLE_WASM_SIMD |
| throws = SIMDAnyNaN(input); |
| if (!throws) |
| #endif |
| { |
| result = SIMDUint32x4Operation::OpFromFloat32x4(input, throws); |
| } |
| |
| if (throws) |
| { |
| JavascriptError::ThrowRangeError(scriptContext, JSERR_ArgumentOutOfRange, _u("SIMD.Int32x4.FromFloat32x4")); |
| } |
| SetRegRawSimd(playout->U4_0, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_WasmSimdConst(const unaligned T* playout) |
| { |
| AsmJsSIMDValue result{ playout->C1, playout->C2, playout->C3, playout->C4 }; |
| SetRegRawSimd(playout->F4_0, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdShuffleV8X16(const unaligned T* playout) |
| { |
| uint32 lanes[Wasm::Simd::MAX_LANES]; |
| for (uint32 i = 0; i < Wasm::Simd::MAX_LANES; i++) |
| { |
| Assert(playout->INDICES[i] < Wasm::Simd::MAX_LANES * 2); |
| lanes[i] = playout->INDICES[i]; |
| } |
| SetRegRawSimd(playout->R0, SIMDUtils::SIMD128InnerShuffle(GetRegRawSimd(playout->R1), GetRegRawSimd(playout->R2), Wasm::Simd::MAX_LANES, lanes)); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdInt16x8(const unaligned T* playout) |
| { |
| int16 values[8]; |
| values[0] = (int16)GetRegRawInt(playout->I1); |
| values[1] = (int16)GetRegRawInt(playout->I2); |
| values[2] = (int16)GetRegRawInt(playout->I3); |
| values[3] = (int16)GetRegRawInt(playout->I4); |
| values[4] = (int16)GetRegRawInt(playout->I5); |
| values[5] = (int16)GetRegRawInt(playout->I6); |
| values[6] = (int16)GetRegRawInt(playout->I7); |
| values[7] = (int16)GetRegRawInt(playout->I8); |
| |
| AsmJsSIMDValue result = SIMDInt16x8Operation::OpInt16x8(values); |
| SetRegRawSimd(playout->I8_0, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdInt8x16(const unaligned T* playout) |
| { |
| int8 values[16]; |
| values[0] = (int8)GetRegRawInt(playout->I1); |
| values[1] = (int8)GetRegRawInt(playout->I2); |
| values[2] = (int8)GetRegRawInt(playout->I3); |
| values[3] = (int8)GetRegRawInt(playout->I4); |
| values[4] = (int8)GetRegRawInt(playout->I5); |
| values[5] = (int8)GetRegRawInt(playout->I6); |
| values[6] = (int8)GetRegRawInt(playout->I7); |
| values[7] = (int8)GetRegRawInt(playout->I8); |
| values[8] = (int8)GetRegRawInt(playout->I9); |
| values[9] = (int8)GetRegRawInt(playout->I10); |
| values[10] = (int8)GetRegRawInt(playout->I11); |
| values[11] = (int8)GetRegRawInt(playout->I12); |
| values[12] = (int8)GetRegRawInt(playout->I13); |
| values[13] = (int8)GetRegRawInt(playout->I14); |
| values[14] = (int8)GetRegRawInt(playout->I15); |
| values[15] = (int8)GetRegRawInt(playout->I16); |
| |
| AsmJsSIMDValue result = SIMDInt8x16Operation::OpInt8x16(values); |
| SetRegRawSimd(playout->I16_0, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdUint16x8(const unaligned T* playout) |
| { |
| |
| uint16 values[8]; |
| values[0] = (uint16)GetRegRawInt(playout->I1); |
| values[1] = (uint16)GetRegRawInt(playout->I2); |
| values[2] = (uint16)GetRegRawInt(playout->I3); |
| values[3] = (uint16)GetRegRawInt(playout->I4); |
| values[4] = (uint16)GetRegRawInt(playout->I5); |
| values[5] = (uint16)GetRegRawInt(playout->I6); |
| values[6] = (uint16)GetRegRawInt(playout->I7); |
| values[7] = (uint16)GetRegRawInt(playout->I8); |
| |
| AsmJsSIMDValue result = SIMDUint16x8Operation::OpUint16x8(values); |
| SetRegRawSimd(playout->U8_0, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdUint8x16(const unaligned T* playout) |
| { |
| uint8 values[16]; |
| values[0] = (uint8)GetRegRawInt(playout->I1); |
| values[1] = (uint8)GetRegRawInt(playout->I2); |
| values[2] = (uint8)GetRegRawInt(playout->I3); |
| values[3] = (uint8)GetRegRawInt(playout->I4); |
| values[4] = (uint8)GetRegRawInt(playout->I5); |
| values[5] = (uint8)GetRegRawInt(playout->I6); |
| values[6] = (uint8)GetRegRawInt(playout->I7); |
| values[7] = (uint8)GetRegRawInt(playout->I8); |
| values[8] = (uint8)GetRegRawInt(playout->I9); |
| values[9] = (uint8)GetRegRawInt(playout->I10); |
| values[10] = (uint8)GetRegRawInt(playout->I11); |
| values[11] = (uint8)GetRegRawInt(playout->I12); |
| values[12] = (uint8)GetRegRawInt(playout->I13); |
| values[13] = (uint8)GetRegRawInt(playout->I14); |
| values[14] = (uint8)GetRegRawInt(playout->I15); |
| values[15] = (uint8)GetRegRawInt(playout->I16); |
| |
| AsmJsSIMDValue result = SIMDUint8x16Operation::OpUint8x16(values); |
| SetRegRawSimd(playout->U16_0, result); |
| } |
| |
| // Bool constructors |
| template <class T> |
| void InterpreterStackFrame::OP_SimdBool32x4(const unaligned T* playout) |
| { |
| bool arg1 = GetRegRawInt(playout->I1) ? true : false; |
| bool arg2 = GetRegRawInt(playout->I2) ? true : false; |
| bool arg3 = GetRegRawInt(playout->I3) ? true : false; |
| bool arg4 = GetRegRawInt(playout->I4) ? true : false; |
| AsmJsSIMDValue result = SIMDBool32x4Operation::OpBool32x4(arg1, arg2, arg3, arg4); |
| SetRegRawSimd(playout->B4_0, result); |
| |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdBool16x8(const unaligned T* playout) |
| { |
| bool values[8]; |
| values[0] = GetRegRawInt(playout->I1) ? true : false; |
| values[1] = GetRegRawInt(playout->I2) ? true : false; |
| values[2] = GetRegRawInt(playout->I3) ? true : false; |
| values[3] = GetRegRawInt(playout->I4) ? true : false; |
| values[4] = GetRegRawInt(playout->I5) ? true : false; |
| values[5] = GetRegRawInt(playout->I6) ? true : false; |
| values[6] = GetRegRawInt(playout->I7) ? true : false; |
| values[7] = GetRegRawInt(playout->I8) ? true : false; |
| |
| AsmJsSIMDValue result = SIMDBool16x8Operation::OpBool16x8(values); |
| SetRegRawSimd(playout->B8_0, result); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_SimdBool8x16(const unaligned T* playout) |
| { |
| bool values[16]; |
| values[0] = GetRegRawInt(playout->I1) ? true : false; |
| values[1] = GetRegRawInt(playout->I2) ? true : false; |
| values[2] = GetRegRawInt(playout->I3) ? true : false; |
| values[3] = GetRegRawInt(playout->I4) ? true : false; |
| values[4] = GetRegRawInt(playout->I5) ? true : false; |
| values[5] = GetRegRawInt(playout->I6) ? true : false; |
| values[6] = GetRegRawInt(playout->I7) ? true : false; |
| values[7] = GetRegRawInt(playout->I8) ? true : false; |
| values[8] = GetRegRawInt(playout->I9) ? true : false; |
| values[9] = GetRegRawInt(playout->I10) ? true : false; |
| values[10] = GetRegRawInt(playout->I11) ? true : false; |
| values[11] = GetRegRawInt(playout->I12) ? true : false; |
| values[12] = GetRegRawInt(playout->I13) ? true : false; |
| values[13] = GetRegRawInt(playout->I14) ? true : false; |
| values[14] = GetRegRawInt(playout->I15) ? true : false; |
| values[15] = GetRegRawInt(playout->I16) ? true : false; |
| |
| AsmJsSIMDValue result = SIMDBool8x16Operation::OpBool8x16(values); |
| SetRegRawSimd(playout->B16_0, result); |
| } |
| |
| Var InterpreterStackFrame::GetNonVarReg(RegSlot localRegisterID) const |
| { |
| return m_localSlots[localRegisterID]; |
| } |
| |
| void InterpreterStackFrame::SetNonVarReg(RegSlot localRegisterID, Var aValue) |
| { |
| m_localSlots[localRegisterID] = aValue; |
| } |
| |
| Var InterpreterStackFrame::GetRootObject() const |
| { |
| Assert(!this->GetFunctionBody()->IsJsBuiltInCode()); |
| Var rootObject = GetReg(Js::FunctionBody::RootObjectRegSlot); |
| Assert(rootObject == this->GetFunctionBody()->LoadRootObject()); |
| return rootObject; |
| } |
| |
| Var InterpreterStackFrame::OP_ArgIn0() |
| { |
| return m_inParams[0]; |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| void InterpreterStackFrame::OP_ProfiledArgOut_A(const unaligned T * playout) |
| { |
| FunctionBody* functionBody = this->m_functionBody; |
| DynamicProfileInfo * dynamicProfileInfo = functionBody->GetDynamicProfileInfo(); |
| |
| Assert(playout->Reg > FunctionBody::FirstRegSlot); |
| Var value = GetReg(playout->Reg); |
| dynamicProfileInfo->RecordParameterAtCallSite(functionBody, playout->profileId, value, playout->Arg, playout->Reg); |
| SetOut(playout->Arg, value); |
| } |
| #endif |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ArgOut_A(const unaligned T * playout) |
| { |
| SetOut(playout->Arg, GetReg(playout->Reg)); |
| } |
| #if DBG |
| template <class T> |
| void InterpreterStackFrame::OP_ArgOut_ANonVar(const unaligned T * playout) |
| { |
| SetOut(playout->Arg, GetNonVarReg(playout->Reg)); |
| } |
| #endif |
| |
| template <class T> |
| void InterpreterStackFrame::OP_ArgOut_Env(const unaligned T * playout) |
| { |
| Var argEnv; |
| if (this->m_functionBody->GetLocalFrameDisplayRegister() != Constants::NoRegister) |
| { |
| argEnv = this->GetLocalFrameDisplay(); |
| } |
| else |
| { |
| argEnv = this->LdEnv(); |
| } |
| SetOut(playout->Arg, argEnv); |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrFalse_A(Var aValue, ScriptContext* scriptContext) |
| { |
| return !JavascriptConversion::ToBoolean(aValue, scriptContext); |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrTrue_A(Var aValue, ScriptContext* scriptContext) |
| { |
| return JavascriptConversion::ToBoolean(aValue, scriptContext); |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrNotNull_A(Var aValue) |
| { |
| return aValue != NULL; |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrUndecl_A(Var aValue) |
| { |
| return this->scriptContext->GetLibrary()->IsUndeclBlockVar(aValue); |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrNotUndecl_A(Var aValue) |
| { |
| return !this->scriptContext->GetLibrary()->IsUndeclBlockVar(aValue); |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrOnHasProperty(Var argInstance, uint propertyIdIndex, ScriptContext* scriptContext) |
| { |
| return JavascriptOperators::OP_HasProperty(argInstance, |
| this->m_functionBody->GetReferencedPropertyId(propertyIdIndex), scriptContext); |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrOnNoProperty(Var argInstance, uint propertyIdIndex, ScriptContext* scriptContext) |
| { |
| return !JavascriptOperators::OP_HasProperty(argInstance, |
| this->m_functionBody->GetReferencedPropertyId(propertyIdIndex), scriptContext); |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrOnNoEnvProperty(Var envInstance, int32 slotIndex, uint propertyIdIndex, ScriptContext* scriptContext) |
| { |
| Var instance = OP_LdFrameDisplaySlot(envInstance, slotIndex); |
| return !JavascriptOperators::OP_HasProperty(instance, |
| this->m_functionBody->GetReferencedPropertyId(propertyIdIndex), scriptContext); |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrOnClassConstructor(Var aValue) |
| { |
| return JavascriptOperators::IsClassConstructor(aValue); |
| } |
| |
| BOOL InterpreterStackFrame::OP_BrOnBaseConstructorKind(Var aValue) |
| { |
| return JavascriptOperators::IsBaseConstructorKind(aValue); |
| } |
| |
| template<class T> |
| void InterpreterStackFrame::OP_LdLen(const unaligned T * const playout) |
| { |
| Assert(playout); |
| |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| Var instance = GetReg(playout->Instance); |
| Var length = JavascriptOperators::OP_GetLength(instance, GetScriptContext()); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| |
| SetReg(playout->Value, length); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template<class T> |
| void InterpreterStackFrame::OP_ProfiledLdLen(const unaligned OpLayoutDynamicProfile<T> *const playout) |
| { |
| Assert(playout); |
| FunctionBody * functionBody = m_functionBody; |
| DynamicProfileInfo * profileData = functionBody->GetDynamicProfileInfo(); |
| |
| Var instance = GetReg(playout->Instance); |
| |
| LdLenInfo ldLenInfo; |
| ldLenInfo.arrayType = ValueType::Uninitialized.Merge(instance); |
| |
| if (this->TestFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArraySpecialization)) |
| { |
| ldLenInfo.disableAggressiveSpecialization = true; |
| this->ClearFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArraySpecialization); |
| } |
| |
| profileData->RecordLengthLoad(functionBody, playout->profileId, ldLenInfo); |
| |
| ThreadContext* threadContext = this->GetScriptContext()->GetThreadContext(); |
| ImplicitCallFlags savedImplicitCallFlags = threadContext->GetImplicitCallFlags(); |
| threadContext->ClearImplicitCallFlags(); |
| |
| PropertyId propertyId = GetPropertyIdFromCacheId(playout->inlineCacheIndex); |
| Var value = ProfilingHelpers::ProfiledLdFld<false, false, false>( |
| instance, |
| propertyId, |
| GetInlineCache(playout->inlineCacheIndex), |
| playout->inlineCacheIndex, |
| GetFunctionBody(), |
| instance); |
| |
| SetReg(playout->Value, value); |
| |
| threadContext->CheckAndResetImplicitCallAccessorFlag(); |
| threadContext->AddImplicitCallFlags(savedImplicitCallFlags); |
| } |
| #endif |
| |
| #if ENABLE_PROFILE_INFO |
| |
| template <bool doProfile> |
| Var InterpreterStackFrame::ProfiledIsIn(Var argProperty, Var instance, ScriptContext* scriptContext, ProfileId profileId) |
| { |
| if (doProfile) |
| { |
| DynamicProfileInfo * profileData = m_functionBody->GetDynamicProfileInfo(); |
| |
| LdElemInfo ldElemInfo; |
| ldElemInfo.arrayType = ValueType::Uninitialized.Merge(instance); |
| |
| if (this->TestFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArraySpecialization)) |
| { |
| ldElemInfo.disableAggressiveSpecialization = true; |
| } |
| this->ClearFlags(InterpreterStackFrameFlags_ProcessingBailOutOnArraySpecialization); |
| |
| profileData->RecordElementLoad(m_functionBody, profileId, ldElemInfo); |
| } |
| |
| return JavascriptOperators::IsIn(argProperty, instance, scriptContext); |
| } |
| |
| #else |
| |
| template <bool doProfile> |
| Var InterpreterStackFrame::ProfiledIsIn(Var argProperty, Var instance, ScriptContext* scriptContext, ProfileId profileId) |
| { |
| Assert(!doProfile); |
| return JavascriptOperators::IsIn(argProperty, instance, scriptContext); |
| } |
| |
| #endif |
| |
| JavascriptFunction* InterpreterStackFrame::GetFunctionExpression() |
| { |
| // Make sure we get the boxed function object if is there, (or the function itself) |
| return StackScriptFunction::GetCurrentFunctionObject(this->function->GetRealFunctionObject()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_LdFunctionExpression(const unaligned T * playout) |
| { |
| SetRegAllowStackVar(playout->R0, this->GetFunctionExpression()); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_StFunctionExpression(const unaligned T * playout) |
| { |
| OP_StFunctionExpression(GetReg(playout->Instance), GetReg(playout->Value), playout->PropertyIdIndex); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_StLocalFunctionExpression(const unaligned T * playout) |
| { |
| OP_StFunctionExpression(this->localClosure, GetReg(playout->Instance), playout->PropertyIdIndex); |
| } |
| |
| void InterpreterStackFrame::OP_StFunctionExpression(Var instance, Var value, PropertyIdIndexType index) |
| { |
| JavascriptOperators::OP_StFunctionExpression(instance, |
| this->m_functionBody->GetReferencedPropertyId(index), value); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_LdNewTarget(const unaligned T* playout) |
| { |
| if (Js::CallInfo::HasNewTarget(this->m_callFlags)) |
| { |
| SetRegAllowStackVar(playout->R0, (Js::RecyclableObject*)Js::CallInfo::GetNewTarget(this->m_callFlags, this->m_inParams, (ArgSlot)this->m_inSlotsCount)); |
| } |
| else if (this->m_callFlags & CallFlags_New) |
| { |
| SetRegAllowStackVar(playout->R0, this->GetFunctionExpression()); |
| } |
| else |
| { |
| SetReg(playout->R0, this->GetScriptContext()->GetLibrary()->GetUndefined()); |
| } |
| } |
| |
| Var InterpreterStackFrame::OP_Ld_A(Var aValue) |
| { |
| return aValue; |
| } |
| |
| Var InterpreterStackFrame::LdEnv() const |
| { |
| return this->function->GetEnvironment(); |
| } |
| |
| void InterpreterStackFrame::SetEnv(FrameDisplay *frameDisplay) |
| { |
| this->function->SetEnvironment(frameDisplay); |
| } |
| |
| Var InterpreterStackFrame::OP_LdLocalObj() |
| { |
| if (!VirtualTableInfo<ActivationObject>::HasVirtualTable(this->localClosure) && |
| !VirtualTableInfo<ActivationObjectEx>::HasVirtualTable(this->localClosure)) |
| { |
| Js::Throw::FatalInternalError(); |
| } |
| return this->localClosure; |
| } |
| |
| Var InterpreterStackFrame::OP_LdParamObj() |
| { |
| if (!VirtualTableInfo<ActivationObject>::HasVirtualTable(this->paramClosure) && |
| !VirtualTableInfo<ActivationObjectEx>::HasVirtualTable(this->paramClosure)) |
| { |
| Js::Throw::FatalInternalError(); |
| } |
| return this->paramClosure; |
| } |
| |
| #ifdef ASMJS_PLAT |
| template <typename ArrayType, typename RegType> |
| void InterpreterStackFrame::OP_StArr(uint32 index, RegSlot regSlot) |
| { |
| JavascriptArrayBuffer* arr = GetAsmJsBuffer(); |
| if (index < arr->GetByteLength()) |
| { |
| BYTE* buffer = arr->GetBuffer(); |
| *(ArrayType*)(buffer + index) = (ArrayType)GetRegRaw<RegType>(regSlot); |
| } |
| } |
| #endif |
| |
| template<> inline double InterpreterStackFrame::GetArrayViewOverflowVal() |
| { |
| return *(double*)&NumberConstants::k_Nan; |
| } |
| |
| template<> inline float InterpreterStackFrame::GetArrayViewOverflowVal() |
| { |
| return (float)*(double*)&NumberConstants::k_Nan; |
| } |
| |
| template<typename T> T InterpreterStackFrame::GetArrayViewOverflowVal() |
| { |
| return 0; |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_LdArrFunc(const unaligned T* playout) |
| { |
| Var* arr = (Var*)GetRegRawPtr(playout->Instance); |
| const uint32 index = (uint32)GetRegRawInt(playout->SlotIndex); |
| SetRegRawPtr(playout->Value, arr[index]); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_LdArrWasmFunc(const unaligned T* playout) |
| { |
| #ifdef ENABLE_WASM |
| WebAssemblyTable * table = WebAssemblyTable::FromVar(GetRegRawPtr(playout->Instance)); |
| const uint32 index = (uint32)GetRegRawInt(playout->SlotIndex); |
| if (index >= table->GetCurrentLength()) |
| { |
| JavascriptError::ThrowWebAssemblyRuntimeError(GetScriptContext(), WASMERR_TableIndexOutOfRange); |
| } |
| Var func = table->DirectGetValue(index); |
| if (!func) |
| { |
| JavascriptError::ThrowWebAssemblyRuntimeError(GetScriptContext(), WASMERR_NeedWebAssemblyFunc); |
| } |
| SetRegRawPtr(playout->Value, func); |
| #endif |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_CheckSignature(const unaligned T* playout) |
| { |
| #ifdef ENABLE_WASM |
| ScriptFunction * func = ScriptFunction::FromVar(GetRegRawPtr(playout->R0)); |
| int sigIndex = playout->C1; |
| Wasm::WasmSignature * expected = &m_signatures[sigIndex]; |
| if (func->GetFunctionInfo()->IsDeferredParseFunction()) |
| { |
| // TODO: should be able to assert this once imports are converted to wasm functions |
| JavascriptError::ThrowWebAssemblyRuntimeError(GetScriptContext(), WASMERR_NeedWebAssemblyFunc); |
| } |
| AsmJsFunctionInfo * asmInfo = func->GetFunctionBody()->GetAsmJsFunctionInfo(); |
| if (!asmInfo) |
| { |
| // TODO: should be able to assert this once imports are converted to wasm functions |
| JavascriptError::ThrowWebAssemblyRuntimeError(GetScriptContext(), WASMERR_NeedWebAssemblyFunc); |
| } |
| if (!expected->IsEquivalent(asmInfo->GetWasmSignature())) |
| { |
| JavascriptError::ThrowWebAssemblyRuntimeError(GetScriptContext(), WASMERR_SignatureMismatch); |
| } |
| #endif |
| } |
| |
| #ifdef ASMJS_PLAT |
| template <typename ArrayType, typename RegType> |
| void InterpreterStackFrame::OP_LdArr(uint32 index, RegSlot regSlot) |
| { |
| JavascriptArrayBuffer* arr = GetAsmJsBuffer(); |
| BYTE* buffer = arr->GetBuffer(); |
| ArrayType val = index < (arr->GetByteLength()) ? *(ArrayType*)(buffer + index) : GetArrayViewOverflowVal<ArrayType>(); |
| SetRegRaw<RegType>(regSlot, (RegType)val); |
| } |
| #endif |
| |
| template <class T, typename T2> |
| void InterpreterStackFrame::OP_StSlotPrimitive(const unaligned T* playout) |
| { |
| T2* buffer = (T2*)GetNonVarReg(playout->Instance); |
| buffer[playout->SlotIndex] = GetRegRaw<T2>(playout->Value); |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_LdAsmJsSlot(const unaligned T* playout) |
| { |
| Var * slotArray = (Var*)GetNonVarReg(playout->Instance); |
| SetRegRawPtr(playout->Value, slotArray[playout->SlotIndex]); |
| } |
| |
| template <class T, typename T2> |
| void InterpreterStackFrame::OP_LdSlotPrimitive(const unaligned T* playout) |
| { |
| T2* buffer = (T2*)GetNonVarReg(playout->Instance); |
| SetRegRaw<T2>(playout->Value, buffer[playout->SlotIndex]); |
| } |
| |
| #ifndef TEMP_DISABLE_ASMJS |
| template <class T> |
| void InterpreterStackFrame::OP_LdArrGeneric(const unaligned T* playout) |
| { |
| const uint32 index = (uint32)GetRegRawInt(playout->SlotIndex); |
| switch (playout->ViewType) |
| { |
| #define ARRAYBUFFER_VIEW(name, align, RegType, MemType, ...) \ |
| case ArrayBufferView::ViewType::TYPE_##name: \ |
| OP_LdArr<MemType, RegType>(index & ARRAYBUFFER_VIEW_MASK(align), playout->Value); \ |
| return; |
| #include "AsmJsArrayBufferViews.h" |
| default:Assert(UNREACHED); |
| } |
| } |
| |
| template<typename MemType> |
| void InterpreterStackFrame::WasmArrayBoundsCheck(uint64 index, uint32 byteLength) |
| { |
| if (index + sizeof(MemType) > byteLength) |
| { |
| JavascriptError::ThrowWebAssemblyRuntimeError(scriptContext, WASMERR_ArrayIndexOutOfRange); |
| } |
| } |
| |
| template<typename MemType> |
| MemType* InterpreterStackFrame::WasmAtomicsArrayBoundsCheck(byte* buffer, uint64 index, uint32 byteLength) |
| { |
| MemType* readBuffer = (MemType*)(buffer + index); |
| // Do alignment check to be coherent with the order the jit does the checks |
| if (!::Math::IsAligned<intptr_t>((intptr_t)readBuffer, sizeof(MemType))) |
| { |
| JavascriptError::ThrowWebAssemblyRuntimeError(scriptContext, WASMERR_UnalignedAtomicAccess); |
| } |
| WasmArrayBoundsCheck<MemType>(index, byteLength); |
| return readBuffer; |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_LdArrWasm(const unaligned T* playout) |
| { |
| #ifdef ENABLE_WASM |
| Assert(playout->ViewType < Js::ArrayBufferView::TYPE_COUNT); |
| const uint64 index = playout->Offset + (uint64)(uint32)GetRegRawInt(playout->SlotIndex); |
| ArrayBufferBase* arr = GetWebAssemblyMemory()->GetBuffer(); |
| |
| uint32 byteLength = arr->GetByteLength(); |
| BYTE* buffer = arr->GetBuffer(); |
| switch (playout->ViewType) |
| { |
| #define ARRAYBUFFER_VIEW(name, align, RegType, MemType, ...) \ |
| case ArrayBufferView::ViewType::TYPE_##name: \ |
| WasmArrayBoundsCheck<MemType>(index, byteLength); \ |
| SetRegRaw<RegType>(playout->Value, (RegType)*(MemType*)(buffer + index)); \ |
| return; |
| #include "AsmJsArrayBufferViews.h" |
| default:Assert(UNREACHED); |
| } |
| #else |
| Assert(UNREACHED); |
| #endif |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_LdArrAtomic(const unaligned T* playout) |
| { |
| #ifdef ENABLE_WASM |
| Assert(Wasm::Threads::IsEnabled()); |
| Assert(playout->ViewType < Js::ArrayBufferView::TYPE_COUNT); |
| const uint64 index = playout->Offset + (uint64)(uint32)GetRegRawInt(playout->SlotIndex); |
| ArrayBufferBase* arr = GetWebAssemblyMemory()->GetBuffer(); |
| |
| uint32 byteLength = arr->GetByteLength(); |
| BYTE* buffer = arr->GetBuffer(); |
| switch (playout->ViewType) |
| { |
| #define ARRAYBUFFER_VIEW_INT(name, align, RegType, MemType, ...) \ |
| case ArrayBufferView::ViewType::TYPE_##name: {\ |
| MemType* readBuffer = WasmAtomicsArrayBoundsCheck<MemType>(buffer, index, byteLength); \ |
| MemType value = AtomicsOperations::Load<MemType>(readBuffer); \ |
| SetRegRaw<RegType>(playout->Value, (RegType)value); \ |
| return; \ |
| } |
| #include "AsmJsArrayBufferViews.h" |
| default:Assert(UNREACHED); |
| } |
| #else |
| Assert(UNREACHED); |
| #endif |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_StArrAtomic(const unaligned T* playout) |
| { |
| #ifdef ENABLE_WASM |
| Assert(Wasm::Threads::IsEnabled()); |
| Assert(playout->ViewType < Js::ArrayBufferView::TYPE_COUNT); |
| const uint64 index = playout->Offset + (uint64)(uint32)GetRegRawInt(playout->SlotIndex); |
| ArrayBufferBase* arr = GetWebAssemblyMemory()->GetBuffer(); |
| |
| uint32 byteLength = arr->GetByteLength(); |
| BYTE* buffer = arr->GetBuffer(); |
| switch (playout->ViewType) |
| { |
| #define ARRAYBUFFER_VIEW_INT(name, align, RegType, MemType, ...) \ |
| case ArrayBufferView::ViewType::TYPE_##name: {\ |
| MemType* readBuffer = WasmAtomicsArrayBoundsCheck<MemType>(buffer, index, byteLength); \ |
| MemType value = (MemType)GetRegRaw<RegType>(playout->Value); \ |
| MemType storedValue = AtomicsOperations::Store<MemType>(readBuffer, value); \ |
| Assert(storedValue == value); \ |
| return; \ |
| } |
| #include "AsmJsArrayBufferViews.h" |
| default:Assert(UNREACHED); |
| } |
| #else |
| Assert(UNREACHED); |
| #endif |
| } |
| |
| template <class T> |
| void InterpreterStackFrame::OP_LdArrConstIndex(const unaligned T* playout) |
| { |
| switch (playout->ViewType) |
| { |
| #define ARRAYBUFFER_VIEW(name, align, RegType, MemType, ...) \ |
| case ArrayBufferView::ViewType::TYPE_##name: \ |
| OP_LdArr<MemType, RegType>(playout->SlotIndex, playout->Value); \ |
| return; |
| #include "AsmJsArrayBufferViews.h" |
| default:Assert(UNREACHED); |
| } |
| } |
| template <class T> |
| void InterpreterStackFrame::OP_StArrGeneric(const unaligned T* playout) |
| { |
| const uint32 index = (uint32)GetRegRawInt(playout->SlotIndex); |
| switch (playout->ViewType) |
| { |
| #define ARRAYBUFFER_VIEW(name, align, RegType, MemType, ...) \ |
| case ArrayBufferView::ViewType::TYPE_##name: \ |
| OP_StArr<MemType, RegType>(index & ARRAYBUFFER_VIEW_MASK(align), playout->Value); \ |
| return; |
| #include "AsmJsArrayBufferViews.h" |
| default:Assert(UNREACHED); |
| } |
| } |
| template <class T> |
| void InterpreterStackFrame::OP_StArrWasm(const unaligned T* playout) |
| { |
| #ifdef ENABLE_WASM |
| Assert(playout->ViewType < Js::ArrayBufferView::TYPE_COUNT); |
| const uint64 index = playout->Offset + (uint64)(uint32)GetRegRawInt(playout->SlotIndex); |
| ArrayBufferBase* arr = GetWebAssemblyMemory()->GetBuffer(); |
| |
| uint32 byteLength = arr->GetByteLength(); |
| BYTE* buffer = arr->GetBuffer(); |
| switch (playout->ViewType) |
| { |
| #define ARRAYBUFFER_VIEW(name, align, RegType, MemType, ...) \ |
| case ArrayBufferView::ViewType::TYPE_##name: \ |
| WasmArrayBoundsCheck<MemType>(index, byteLength); \ |
| *(MemType*)(buffer + index) = (MemType)(GetRegRaw<RegType>(playout->Value)); \ |
| break; |
| #include "AsmJsArrayBufferViews.h" |
| default:Assert(UNREACHED); |
| } |
| CompileAssert(ArrayBufferView::ViewType::TYPE_COUNT == 15); |
| #if DBG |
| if (PHASE_TRACE(WasmMemWritesPhase, m_functionBody)) |
| { |
| GetWebAssemblyMemory()->TraceMemWrite(GetWebAssemblyMemory(), (uint32)GetRegRawInt(playout->SlotIndex), playout->Offset, playout->ViewType, (uint32)(size_t)this->DEBUG_currentByteOffset, scriptContext); |
| } |
| #endif |
| return; |
| #else |
| Assert(UNREACHED); |
| #endif |
| } |
| template <class T> |
| void InterpreterStackFrame::OP_StArrConstIndex(const unaligned T* playout) |
| { |
| switch (playout->ViewType) |
| { |
| #define ARRAYBUFFER_VIEW(name, align, RegType, MemType, ...) \ |
| case ArrayBufferView::ViewType::TYPE_##name: \ |
| OP_StArr<MemType, RegType>(playout->SlotIndex, playout->Value); \ |
| return; |
| #include "AsmJsArrayBufferViews.h" |
| default:Assert(UNREACHED); |
| } |
| } |
| #endif |
| |
| Var InterpreterStackFrame::OP_LdSlot(Var instance, int32 slotIndex) |
| { |
| if (!PHASE_OFF(ClosureRangeCheckPhase, this->m_functionBody)) |
| { |
| if ((uintptr_t)((Var*)instance)[ScopeSlots::EncodedSlotCountSlotIndex] <= (uintptr_t)(slotIndex - ScopeSlots::FirstSlotIndex)) |
| { |
| Js::Throw::FatalInternalError(); |
| } |
| } |
| return ((Var*)(instance))[slotIndex]; |
| } |
| |
| template <class T> |
| Var InterpreterStackFrame::OP_LdSlot(Var instance, const unaligned T* playout) |
| { |
| return OP_LdSlot(instance, playout->SlotIndex); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| Var InterpreterStackFrame::OP_ProfiledLdSlot(Var instance, const unaligned T* playout) |
| { |
| Var value = OP_LdSlot(instance, playout->SlotIndex); |
| ProfilingHelpers::ProfileLdSlot(value, GetFunctionBody(), playout->profileId); |
| return value; |
| } |
| #endif |
| |
| template <class T> |
| Var InterpreterStackFrame::OP_LdInnerSlot(Var slotArray, const unaligned T* playout) |
| { |
| return OP_LdSlot(slotArray, playout->SlotIndex2); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| Var InterpreterStackFrame::OP_ProfiledLdInnerSlot(Var slotArray, const unaligned T* playout) |
| { |
| Var value = OP_LdInnerSlot(slotArray, playout); |
| ProfilingHelpers::ProfileLdSlot(value, GetFunctionBody(), playout->profileId); |
| return value; |
| } |
| #endif |
| |
| template <class T> |
| Var InterpreterStackFrame::OP_LdInnerObjSlot(Var slotArray, const unaligned T* playout) |
| { |
| return OP_LdObjSlot(slotArray, playout->SlotIndex2); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| Var InterpreterStackFrame::OP_ProfiledLdInnerObjSlot(Var slotArray, const unaligned T* playout) |
| { |
| Var value = OP_LdInnerObjSlot(slotArray, playout); |
| ProfilingHelpers::ProfileLdSlot(value, GetFunctionBody(), playout->profileId); |
| return value; |
| } |
| #endif |
| |
| Var InterpreterStackFrame::OP_LdFrameDisplaySlot(Var instance, int32 slotIndex) |
| { |
| if (!PHASE_OFF(ClosureRangeCheckPhase, this->m_functionBody)) |
| { |
| if (((FrameDisplay*)instance)->GetLength() <= slotIndex - Js::FrameDisplay::GetOffsetOfScopes() / sizeof(Var)) |
| { |
| Js::Throw::FatalInternalError(); |
| } |
| } |
| return ((Var*)instance)[slotIndex]; |
| } |
| |
| template <class T> |
| Var InterpreterStackFrame::OP_LdEnvObj(Var instance, const unaligned T* playout) |
| { |
| return OP_LdFrameDisplaySlot(instance, playout->SlotIndex); |
| } |
| |
| template <class T> |
| Var InterpreterStackFrame::OP_LdEnvSlot(Var instance, const unaligned T* playout) |
| { |
| Var slotArray = OP_LdFrameDisplaySlot(instance, playout->SlotIndex1); |
| return OP_LdSlot(slotArray, playout->SlotIndex2); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| Var InterpreterStackFrame::OP_ProfiledLdEnvSlot(Var instance, const unaligned T* playout) |
| { |
| Var value = OP_LdEnvSlot(instance, playout); |
| ProfilingHelpers::ProfileLdSlot(value, GetFunctionBody(), playout->profileId); |
| return value; |
| } |
| #endif |
| |
| Var InterpreterStackFrame::OP_LdObjSlot(Var instance, int32 slotIndex) |
| { |
| Var *slotArray = *(Var**)((char*)instance + DynamicObject::GetOffsetOfAuxSlots()); |
| return slotArray[slotIndex]; |
| } |
| |
| template <class T> |
| Var InterpreterStackFrame::OP_LdObjSlot(Var instance, const unaligned T* playout) |
| { |
| return OP_LdObjSlot(instance, playout->SlotIndex); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| Var InterpreterStackFrame::OP_ProfiledLdObjSlot(Var instance, const unaligned T* playout) |
| { |
| Var value = OP_LdObjSlot(instance, playout->SlotIndex); |
| ProfilingHelpers::ProfileLdSlot(value, GetFunctionBody(), playout->profileId); |
| return value; |
| } |
| #endif |
| |
| template <class T> |
| Var InterpreterStackFrame::OP_LdEnvObjSlot(Var instance, const unaligned T* playout) |
| { |
| Var slotArray = OP_LdFrameDisplaySlot(instance, playout->SlotIndex1); |
| return OP_LdObjSlot(slotArray, playout->SlotIndex2); |
| } |
| |
| template <class T> |
| Var InterpreterStackFrame::OP_LdModuleSlot(Var instance, const unaligned T* playout) |
| { |
| return JavascriptOperators::OP_LdModuleSlot(playout->SlotIndex1, playout->SlotIndex2, scriptContext); |
| } |
| |
| inline void InterpreterStackFrame::OP_StModuleSlot(Var instance, uint32 slotIndex1, uint32 slotIndex2, Var value) |
| { |
| JavascriptOperators::OP_StModuleSlot(slotIndex1, slotIndex2, value, scriptContext); |
| } |
| |
| #if ENABLE_PROFILE_INFO |
| template <class T> |
| Var InterpreterStackFrame::OP_ProfiledLdEnvObjSlot(Var instance, const unaligned T* playout) |
| { |
| Var value = OP_LdEnvObjSlot(instance, playout); |
| ProfilingHelpers::ProfileLdSlot(value, GetFunctionBody(), playout->profileId); |
| return value; |
| } |
| #endif |
| |
| void InterpreterStackFrame::OP_StSlot(Var instance, int32 slotIndex, Var value) |
| { |
| // We emit OpCode::StSlot in the bytecode only for scope slot arrays, which are not recyclable objects. |
| if (!PHASE_OFF(ClosureRangeCheckPhase, this->m_functionBody)) |
| { |
| if ((uintptr_t)((Var*)instance)[ScopeSlots::EncodedSlotCountSlotIndex] <= (uintptr_t)(slotIndex - ScopeSlots::FirstSlotIndex)) |
| { |
| Js::Throw::FatalInternalError(); |
| } |
| } |
| ((Field(Var)*)(instance))[slotIndex] = value; |
| } |
| |
| void InterpreterStackFrame::OP_StEnvSlot(Var instance, int32 slotIndex1, int32 slotIndex2, Var value) |
| { |
| Var slotArray = (Var*)OP_LdFrameDisplaySlot(instance, slotIndex1); |
| OP_StSlot(slotArray, slotIndex2, value); |
| } |
| |
| void InterpreterStackFrame::OP_StSlotChkUndecl(Var instance, int32 slotIndex, Var value) |
| { |
| // We emit OpCode::StSlot in the bytecode only for scope slot arrays, which are not recyclable objects. |
| if (!PHASE_OFF(ClosureRangeCheckPhase, this->m_functionBody)) |
| { |
| if ((uintptr_t)((Var*)instance)[ScopeSlots::EncodedSlotCountSlotIndex] <= (uintptr_t)(slotIndex - ScopeSlots::FirstSlotIndex)) |
| { |
| Js::Throw::FatalInternalError(); |
| } |
| } |
| OP_ChkUndecl(((Field(Var)*)instance)[slotIndex]); |
| ((Field(Var)*)(instance))[slotIndex] = value; |
| } |
| |
| void InterpreterStackFrame::OP_StEnvSlotChkUndecl(Var instance, int32 slotIndex1, int32 slotIndex2, Var value) |
| { |
| Var slotArray = OP_LdFrameDisplaySlot(instance, slotIndex1); |
| OP_StSlotChkUndecl(slotArray, slotIndex2, value); |
| } |
| |
| void InterpreterStackFrame::OP_StObjSlot(Var instance, int32 slotIndex, Var value) |
| { |
| // It would be nice to assert that it's ok to store directly to slot, but we don't have the propertyId. |
| Field(Var) *slotArray = *(Field(Var)**)((char*)instance + DynamicObject::GetOffsetOfAuxSlots()); |
| slotArray[slotIndex] = value; |
| } |
| |
| void InterpreterStackFrame::OP_StObjSlotChkUndecl(Var instance, int32 slotIndex, Var value) |
| { |
| // It would be nice to assert that it's ok to store directly to slot, but we don't have the propertyId. |
| Field(Var) *slotArray = *(Field(Var)**)((char*)instance + DynamicObject::GetOffsetOfAuxSlots()); |
| OP_ChkUndecl(slotArray[slotIndex]); |
| slotArray[slotIndex] = value; |
| } |
| |
| void InterpreterStackFrame::OP_StEnvObjSlot(Var instance, int32 slotIndex1, int32 slotIndex2, Var value) |
| { |
| // It would be nice to assert that it's ok to store directly to slot, but we don't have the propertyId. |
| Var envInstance = (Var*)OP_LdFrameDisplaySlot(instance, slotIndex1); |
| OP_StObjSlot(envInstance, slotIndex2, value); |
| } |
| |
| void InterpreterStackFrame::OP_StEnvObjSlotChkUndecl(Var instance, int32 slotIndex1, int32 slotIndex2, Var value) |
| { |
| // It would be nice to assert that it's ok to store directly to slot, but we don't have the propertyId. |
| Var envInstance = (Var*)OP_LdFrameDisplaySlot(instance, slotIndex1); |
| OP_StObjSlotChkUndecl(envInstance, slotIndex2, value); |
| } |
| |
| Var InterpreterStackFrame::OP_LdStackArgPtr(void) |
| { |
| // Return the address of the first param after "this". |
| return m_inParams + 1; |
| } |
| |
| ForInObjectEnumerator * InterpreterStackFrame::GetForInEnumerator(uint forInLoopLevel) |
| { |
| Assert(forInLoopLevel < this->m_functionBody->GetForInLoopDepth()); |
| return &this->forInObjectEnumerators[forInLoopLevel]; |
| } |
| |
| void InterpreterStackFrame::OP_InitForInEnumerator(Var object, uint forInLoopLevel) |
| { |
| JavascriptOperators::OP_InitForInEnumerator(object, GetForInEnumerator(forInLoopLevel), this->GetScriptContext()); |
| } |
| |
| void InterpreterStackFrame::OP_InitForInEnumeratorWithCache(Var object, uint forInLoopLevel, ProfileId profileId) |
| { |
| JavascriptOperators::OP_InitForInEnumerator(object, GetForInEnumerator(forInLoopLevel), this->GetScriptContext(), |
| m_functionBody->GetForInCache(profileId)); |
| } |
| |
| // Called for the debug purpose, to create the arguments object explicitly even though script has not declared it. |
| Var InterpreterStackFrame::CreateHeapArguments(ScriptContext* scriptContext) |
| { |
| return JavascriptOperators::LoadHeapArguments(this->function->GetRealFunctionObject(), this->m_inSlotsCount - 1, &this->m_inParams[1], scriptContext->GetLibrary()->GetNull(), scriptContext->GetLibrary()->GetNull(), scriptContext, false); |
| } |
| |
| template <bool letArgs> |
| Var InterpreterStackFrame::LdHeapArgumentsImpl(Var argsArray, ScriptContext* scriptContext) |
| { |
| Var frameObj; |
| if (m_functionBody->HasScopeObject() && argsArray != scriptContext->GetLibrary()->GetNull()) |
| { |
| frameObj = this->localClosure; |
| Assert(frameObj); |
| } |
| else |
| { |
| frameObj = scriptContext->GetLibrary()->GetNull(); |
| } |
| Var args = JavascriptOperators::LoadHeapArguments(this->function->GetRealFunctionObject(), this->m_inSlotsCount - 1, &this->m_inParams[1], frameObj, argsArray, scriptContext, letArgs); |
| this->m_arguments = args; |
| return args; |
| } |
| |
| Var InterpreterStackFrame::OP_LdHeapArguments(ScriptContext* scriptContext) |
| { |
| Var argsArray = m_functionBody->GetFormalsPropIdArrayOrNullObj(); |
| return LdHeapArgumentsImpl<false>(argsArray, scriptContext); |
| } |
| |
| Var InterpreterStackFrame::OP_LdLetHeapArguments(ScriptContext* scriptContext) |
| { |
| Var argsArray = m_functionBody->GetFormalsPropIdArrayOrNullObj(); |
| return LdHeapArgumentsImpl<true>(argsArray, scriptContext); |
| } |
| |
| Var InterpreterStackFrame::OP_LdHeapArgsCached(ScriptContext* scriptContext) |
| { |
| uint32 formalsCount = this->m_functionBody->GetInParamsCount() - 1; |
| Var args = JavascriptOperators::LoadHeapArgsCached(this->function->GetRealFunctionObject(), this->m_inSlotsCount - 1, formalsCount, &this->m_inParams[1], this->localClosure, scriptContext, false); |
| this->m_arguments = args; |
| return args; |
| } |
| |
| Var InterpreterStackFrame::OP_LdLetHeapArgsCached(ScriptContext* scriptContext) |
| { |
| uint32 formalsCount = this->m_functionBody->GetInParamsCount() - 1; |
| Var args = JavascriptOperators::LoadHeapArgsCached(this->function->GetRealFunctionObject(), this->m_inSlotsCount - 1, formalsCount, &this->m_inParams[1], this->localClosure, scriptContext, true); |
| this->m_arguments = args; |
| return args; |
| } |
| |
| HeapArgumentsObject * InterpreterStackFrame::CreateEmptyHeapArgumentsObject(ScriptContext* scriptContext) |
| { |
| HeapArgumentsObject * args = JavascriptOperators::CreateHeapArguments(this->function->GetRealFunctionObject(), this->m_inSlotsCount - 1, 0, nullptr, scriptContext); |
| this->m_arguments = args; |
| return args; |
| } |
| |
| void InterpreterStackFrame::TrySetFrameObjectInHeapArgObj(ScriptContext * scriptContext, bool hasNonSimpleParams, bool isScopeObjRestored) |
| { |
| Var frameObject = nullptr; |
| |
| uint32 formalsCount = this->m_functionBody->GetInParamsCount() - 1; |
| Js::PropertyIdArray * propIds = nullptr; |
| Js::HeapArgumentsObject* heapArgObj = nullptr; |
| |
| //We always set the Frame object to nullptr in BailOutRecord::EnsureArguments for stack args optimization. |
| if (m_arguments != nullptr && ((Js::HeapArgumentsObject*)(m_arguments))->GetFrameObject() == nullptr) |
| { |
| heapArgObj = (Js::HeapArgumentsObject*)m_arguments; |
| } |
| |
| bool isCachedScope = false; |
| |
| //For Non-simple params, we don't have a scope object created. |
| if (this->m_functionBody->NeedScopeObjectForArguments(hasNonSimpleParams)) |
| { |
| frameObject = GetLocalClosure(); |
| |
| isCachedScope = m_functionBody->HasCachedScopePropIds(); |
| propIds = this->m_functionBody->GetFormalsPropIdArray(); |
| |
| if (isScopeObjRestored && ActivationObject::Is(frameObject)) |
| { |
| Assert(this->GetFunctionBody()->GetDoScopeObjectCreation()); |
| isCachedScope = true; |
| if (PHASE_VERBOSE_TRACE1(Js::StackArgFormalsOptPhase) && m_functionBody->GetInParamsCount() > 1) |
| { |
| Output::Print(_u("StackArgFormals : %s (%d) :Using the restored scope object in the bail out path. \n"), m_functionBody->GetDisplayName(), m_functionBody->GetFunctionNumber()); |
| Output::Flush(); |
| } |
| } |
| else |
| { |
| if (isCachedScope) |
| { |
| Field(DynamicType*) literalType = nullptr; |
| Assert(!propIds->hasNonSimpleParams && !hasNonSimpleParams); |
| frameObject = JavascriptOperators::OP_InitCachedScope(this->GetJavascriptFunction(), propIds, &literalType, hasNonSimpleParams, scriptContext); |
| } |
| else |
| { |
| frameObject = JavascriptOperators::OP_NewScopeObject(GetScriptContext()); |
| } |
| Assert(propIds != nullptr); |
| SetLocalClosure(frameObject); |
| |
| if (PHASE_VERBOSE_TRACE1(Js::StackArgFormalsOptPhase) && m_functionBody->GetInParamsCount() > 1) |
| { |
| Output::Print(_u("StackArgFormals : %s (%d) :Creating scope object in the bail out path. \n"), m_functionBody->GetDisplayName(), m_functionBody->GetFunctionNumber()); |
| Output::Flush(); |
| } |
| } |
| } |
| else |
| { |
| //We reached here because, either we don't have any formals or we don't have a scope object (it could be in strict mode or have non-simple param list) |
| Assert(formalsCount == 0 || (m_functionBody->GetIsStrictMode() || hasNonSimpleParams)); |
| frameObject = nullptr; |
| formalsCount = 0; |
| |
| if (PHASE_VERBOSE_TRACE1(Js::StackArgOptPhase)) |
| { |
| Output::Print(_u("StackArgOpt : %s (%d) :Creating NULL scope object in the bail out path. \n"), m_functionBody->GetDisplayName(), m_functionBody->GetFunctionNumber()); |
| Output::Flush(); |
| } |
| } |
| |
| if (heapArgObj) |
| { |
| Assert(frameObject == nullptr || ActivationObject::Is(frameObject)); |
| heapArgObj->SetFormalCount(formalsCount); |
| heapArgObj->SetFrameObject(frameObject != nullptr ? |
| static_cast<ActivationObject*>(frameObject) : nullptr); |
| |
| if (PHASE_TRACE1(Js::StackArgFormalsOptPhase) && formalsCount > 0) |
| { |
| Output::Print(_u("StackArgFormals : %s (%d) :Attaching the scope object with the heap arguments object in the bail out path. \n"), m_functionBody->GetDisplayName(), m_functionBody->GetFunctionNumber()); |
| Output::Flush(); |
| } |
| } |
| |
| //Fill the Heap arguments and scope object with values |
| // If there is no heap arguments object, then fill only the scope object with actuals. |
| JavascriptOperators::FillScopeObject(this->function->GetRealFunctionObject(), this->m_inSlotsCount - 1, formalsCount, frameObject, &this->m_inParams[1], propIds, heapArgObj, scriptContext, hasNonSimpleParams, isCachedScope); |
| } |
| |
| Var InterpreterStackFrame::OP_LdArgumentsFromFrame() |
| { |
| return this->m_arguments; |
| } |
| |
| void* InterpreterStackFrame::OP_LdArgCnt() |
| { |
| return (void*)m_inSlotsCount; |
| } |
| |
| Var InterpreterStackFrame::OP_ResumeYield(Var yieldDataVar, RegSlot yieldStarIterator) |
| { |
| ResumeYieldData* yieldData = static_cast<ResumeYieldData*>(yieldDataVar); |
| RecyclableObject* iterator = yieldStarIterator != Constants::NoRegister ? RecyclableObject::FromVar(GetNonVarReg(yieldStarIterator)) : nullptr; |
| |
| return JavascriptOperators::OP_ResumeYield(yieldData, iterator); |
| } |
| |
| void* InterpreterStackFrame::operator new(size_t byteSize, void* previousAllocation) throw() |
| { |
| // |
| // Placement 'new' is used by InterpreterStackFrame to initialize the C++ object on the RcInterpreter's |
| // program stack: |
| // - Unlike most other allocations, the previously allocated memory will __not__ be |
| // zero-initialized, as we do not want the overhead of zero-initializing the frame when |
| // calling functions. |
| // |
| // NOTE: If we wanted to add C# semantics of all locals are automatically zero-initialized, |
| // need to determine the most efficient mechanism for this. |
| // |
| |
| return previousAllocation; |
| } |
| |
| void __cdecl InterpreterStackFrame::operator delete(void * allocationToFree, void * previousAllocation) throw() |
| { |
| AssertMsg(allocationToFree == previousAllocation, "Memory locations should match"); |
| AssertMsg(false, "This function should never actually be called"); |
| } |
| |
| void InterpreterStackFrame::OP_WasmPrintFunc(int regIndex) |
| { |
| #if defined(ENABLE_DEBUG_CONFIG_OPTIONS) && defined(ENABLE_WASM) |
| Assert(m_functionBody->IsWasmFunction()); |
| uint index = GetRegRawInt(regIndex); |
| Wasm::WasmFunctionInfo* info = m_functionBody->GetAsmJsFunctionInfo()->GetWebAssemblyModule()->GetWasmFunctionInfo(index); |
| int col = WAsmJs::Tracing::GetPrintCol(); |
| if (col > 0) |
| { |
| Output::SkipToColumn(col); |
| } |
| info->GetBody()->DumpFullFunctionName(); |
| Output::Print(_u("(")); |
| #endif |
| } |
| |
| JavascriptArrayBuffer* InterpreterStackFrame::GetAsmJsBuffer() const |
| { |
| AssertMsg(!m_functionBody->IsWasmFunction(), "Do not use GetAsmJsBuffer for WebAssembly, Use GetWebAssemblyMemory instead"); |
| return m_asmJsBuffer; |
| } |
| |
| #ifdef ENABLE_WASM |
| WebAssemblyMemory* InterpreterStackFrame::GetWebAssemblyMemory() const |
| { |
| AssertMsg(m_functionBody->IsWasmFunction(), "Do not use GetWebAssemblyMemory for Asm.js, Use GetAsmJsBuffer instead"); |
| return m_wasmMemory; |
| } |
| #endif |
| |
| template void* Js::InterpreterStackFrame::GetReg<unsigned int>(unsigned int) const; |
| template void Js::InterpreterStackFrame::SetReg<unsigned int>(unsigned int, void*); |
| } // namespace Js |
| |
| // Make sure the macro and the layout for the op is consistent |
| #define DEF2(x, op, ...) \ |
| CompileAssert(!Js::OpCodeInfo<Js::OpCode::op>::HasMultiSizeLayout); \ |
| CompileAssert(!Js::OpCodeInfo<Js::OpCode::op>::IsExtendedOpcode); |
| #define DEF3(x, op, ...) DEF2(x, op) |
| #define EXDEF2(x, op, ...) \ |
| CompileAssert(!Js::OpCodeInfo<Js::OpCode::op>::HasMultiSizeLayout); \ |
| CompileAssert(Js::OpCodeInfo<Js::OpCode::op>::IsExtendedOpcode); |
| #define EXDEF3(x, op, ...) EXDEF2(x, op) |
| #define DEF2_WMS(x, op, ...) \ |
| CompileAssert(Js::OpCodeInfo<Js::OpCode::op>::HasMultiSizeLayout); \ |
| CompileAssert(!Js::OpCodeInfo<Js::OpCode::op>::IsExtendedOpcode); |
| #define DEF3_WMS(x, op, ...) DEF2_WMS(x, op) |
| #define EXDEF2_WMS(x, op, ...) \ |
| CompileAssert(Js::OpCodeInfo<Js::OpCode::op>::HasMultiSizeLayout); \ |
| CompileAssert(Js::OpCodeInfo<Js::OpCode::op>::IsExtendedOpcode); |
| #define EXDEF3_WMS(x, op, ...) EXDEF2_WMS(x, op) |
| #include "InterpreterHandler.inl" |
| |
| // Make sure the macro and the layout for the op is consistent |
| #define DEF2(x, op, ...) \ |
| CompileAssert(!Js::OpCodeInfoAsmJs<Js::OpCodeAsmJs::op>::HasMultiSizeLayout); \ |
| CompileAssert(!Js::OpCodeInfoAsmJs<Js::OpCodeAsmJs::op>::IsExtendedOpcode); |
| #define DEF3(x, op, ...) DEF2(x, op) |
| #define DEF4(x, op, ...) DEF2(x, op) |
| #define EXDEF2(x, op, ...) \ |
| CompileAssert(!Js::OpCodeInfoAsmJs<Js::OpCodeAsmJs::op>::HasMultiSizeLayout); \ |
| CompileAssert(Js::OpCodeInfoAsmJs<Js::OpCodeAsmJs::op>::IsExtendedOpcode); |
| #define EXDEF3(x, op, ...) EXDEF2(x, op) |
| #define EXDEF4(x, op, ...) EXDEF2(x, op) |
| #define DEF2_WMS(x, op, ...) \ |
| CompileAssert(Js::OpCodeInfoAsmJs<Js::OpCodeAsmJs::op>::HasMultiSizeLayout); \ |
| CompileAssert(!Js::OpCodeInfoAsmJs<Js::OpCodeAsmJs::op>::IsExtendedOpcode); |
| #define DEF3_WMS(x, op, ...) DEF2_WMS(x, op) |
| #define DEF4_WMS(x, op, ...) DEF2_WMS(x, op) |
| #define EXDEF2_WMS(x, op, ...) \ |
| CompileAssert(Js::OpCodeInfoAsmJs<Js::OpCodeAsmJs::op>::HasMultiSizeLayout); \ |
| CompileAssert(Js::OpCodeInfoAsmJs<Js::OpCodeAsmJs::op>::IsExtendedOpcode); |
| #define EXDEF3_WMS(x, op, ...) EXDEF2_WMS(x, op) |
| #define EXDEF4_WMS(x, op, ...) EXDEF2_WMS(x, op) |
| #include "InterpreterHandlerAsmJs.inl" |