| /* |
| * Copyright (C) 2017 Yusuke Suzuki <utatane.tea@gmail.com> |
| * Copyright (C) 2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "BytecodeDumper.h" |
| |
| #include "ArithProfile.h" |
| #include "CallLinkStatus.h" |
| #include "CodeBlock.h" |
| #include "Error.h" |
| #include "HeapInlines.h" |
| #include "InterpreterInlines.h" |
| #include "PolymorphicAccess.h" |
| #include "PutByIdFlags.h" |
| #include "StructureInlines.h" |
| #include "ToThisStatus.h" |
| #include "UnlinkedCodeBlock.h" |
| |
| namespace JSC { |
| |
| static StructureID getStructureID(const Instruction& instruction) |
| { |
| return instruction.u.structureID; |
| } |
| |
| static StructureID getStructureID(const UnlinkedInstruction&) |
| { |
| return 0; |
| } |
| |
| static Special::Pointer getSpecialPointer(const Instruction& instruction) |
| { |
| return instruction.u.specialPointer; |
| } |
| |
| static Special::Pointer getSpecialPointer(const UnlinkedInstruction& instruction) |
| { |
| return static_cast<Special::Pointer>(instruction.u.operand); |
| } |
| |
| static PutByIdFlags getPutByIdFlags(const Instruction& instruction) |
| { |
| return instruction.u.putByIdFlags; |
| } |
| |
| static PutByIdFlags getPutByIdFlags(const UnlinkedInstruction& instruction) |
| { |
| return static_cast<PutByIdFlags>(instruction.u.operand); |
| } |
| |
| static ToThisStatus getToThisStatus(const Instruction& instruction) |
| { |
| return instruction.u.toThisStatus; |
| } |
| |
| static ToThisStatus getToThisStatus(const UnlinkedInstruction& instruction) |
| { |
| return static_cast<ToThisStatus>(instruction.u.operand); |
| } |
| |
| static void* getPointer(const Instruction& instruction) |
| { |
| return instruction.u.pointer; |
| } |
| |
| static void* getPointer(const UnlinkedInstruction&) |
| { |
| return nullptr; |
| } |
| |
| static StructureChain* getStructureChain(const Instruction& instruction) |
| { |
| return instruction.u.structureChain.get(); |
| } |
| |
| static StructureChain* getStructureChain(const UnlinkedInstruction&) |
| { |
| return nullptr; |
| } |
| |
| static Structure* getStructure(const Instruction& instruction) |
| { |
| return instruction.u.structure.get(); |
| } |
| |
| static Structure* getStructure(const UnlinkedInstruction&) |
| { |
| return nullptr; |
| } |
| |
| static LLIntCallLinkInfo* getCallLinkInfo(const Instruction& instruction) |
| { |
| return instruction.u.callLinkInfo; |
| } |
| |
| static LLIntCallLinkInfo* getCallLinkInfo(const UnlinkedInstruction&) |
| { |
| return nullptr; |
| } |
| |
| static BasicBlockLocation* getBasicBlockLocation(const Instruction& instruction) |
| { |
| return instruction.u.basicBlockLocation; |
| } |
| |
| static BasicBlockLocation* getBasicBlockLocation(const UnlinkedInstruction&) |
| { |
| return nullptr; |
| } |
| |
| template<class Block> |
| void* BytecodeDumper<Block>::actualPointerFor(Special::Pointer) const |
| { |
| return nullptr; |
| } |
| |
| template<> |
| void* BytecodeDumper<CodeBlock>::actualPointerFor(Special::Pointer pointer) const |
| { |
| return block()->globalObject()->actualPointerFor(pointer); |
| } |
| |
| static void beginDumpProfiling(PrintStream& out, bool& hasPrintedProfiling) |
| { |
| if (hasPrintedProfiling) { |
| out.print("; "); |
| return; |
| } |
| |
| out.print(" "); |
| hasPrintedProfiling = true; |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpValueProfiling(PrintStream&, const typename Block::Instruction*& it, bool&) |
| { |
| ++it; |
| } |
| |
| template<> |
| void BytecodeDumper<CodeBlock>::dumpValueProfiling(PrintStream& out, const typename CodeBlock::Instruction*& it, bool& hasPrintedProfiling) |
| { |
| ConcurrentJSLocker locker(block()->m_lock); |
| |
| ++it; |
| CString description = it->u.profile->briefDescription(locker); |
| if (!description.length()) |
| return; |
| beginDumpProfiling(out, hasPrintedProfiling); |
| out.print(description); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpArrayProfiling(PrintStream&, const typename Block::Instruction*& it, bool&) |
| { |
| ++it; |
| } |
| |
| template<> |
| void BytecodeDumper<CodeBlock>::dumpArrayProfiling(PrintStream& out, const typename CodeBlock::Instruction*& it, bool& hasPrintedProfiling) |
| { |
| ConcurrentJSLocker locker(block()->m_lock); |
| |
| ++it; |
| if (!it->u.arrayProfile) |
| return; |
| CString description = it->u.arrayProfile->briefDescription(locker, block()); |
| if (!description.length()) |
| return; |
| beginDumpProfiling(out, hasPrintedProfiling); |
| out.print(description); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpProfilesForBytecodeOffset(PrintStream&, unsigned, bool&) |
| { |
| } |
| |
| static void dumpRareCaseProfile(PrintStream& out, const char* name, RareCaseProfile* profile, bool& hasPrintedProfiling) |
| { |
| if (!profile || !profile->m_counter) |
| return; |
| |
| beginDumpProfiling(out, hasPrintedProfiling); |
| out.print(name, profile->m_counter); |
| } |
| |
| static void dumpArithProfile(PrintStream& out, ArithProfile* profile, bool& hasPrintedProfiling) |
| { |
| if (!profile) |
| return; |
| |
| beginDumpProfiling(out, hasPrintedProfiling); |
| out.print("results: ", *profile); |
| } |
| |
| template<> |
| void BytecodeDumper<CodeBlock>::dumpProfilesForBytecodeOffset(PrintStream& out, unsigned location, bool& hasPrintedProfiling) |
| { |
| dumpRareCaseProfile(out, "rare case: ", block()->rareCaseProfileForBytecodeOffset(location), hasPrintedProfiling); |
| { |
| dumpArithProfile(out, block()->arithProfileForBytecodeOffset(location), hasPrintedProfiling); |
| } |
| |
| #if ENABLE(DFG_JIT) |
| Vector<DFG::FrequentExitSite> exitSites = block()->unlinkedCodeBlock()->exitProfile().exitSitesFor(location); |
| if (!exitSites.isEmpty()) { |
| out.print(" !! frequent exits: "); |
| CommaPrinter comma; |
| for (auto& exitSite : exitSites) |
| out.print(comma, exitSite.kind(), " ", exitSite.jitType()); |
| } |
| #else // ENABLE(DFG_JIT) |
| UNUSED_PARAM(location); |
| #endif // ENABLE(DFG_JIT) |
| } |
| |
| template<class Block> |
| VM* BytecodeDumper<Block>::vm() const |
| { |
| return block()->vm(); |
| } |
| |
| template<class Block> |
| const Identifier& BytecodeDumper<Block>::identifier(int index) const |
| { |
| return block()->identifier(index); |
| } |
| |
| static CString regexpToSourceString(RegExp* regExp) |
| { |
| char postfix[7] = { '/', 0, 0, 0, 0, 0, 0 }; |
| int index = 1; |
| if (regExp->global()) |
| postfix[index++] = 'g'; |
| if (regExp->ignoreCase()) |
| postfix[index++] = 'i'; |
| if (regExp->multiline()) |
| postfix[index] = 'm'; |
| if (regExp->dotAll()) |
| postfix[index++] = 's'; |
| if (regExp->unicode()) |
| postfix[index++] = 'u'; |
| if (regExp->sticky()) |
| postfix[index++] = 'y'; |
| |
| return toCString("/", regExp->pattern().impl(), postfix); |
| } |
| |
| static CString regexpName(int re, RegExp* regexp) |
| { |
| return toCString(regexpToSourceString(regexp), "(@re", re, ")"); |
| } |
| |
| template<class Instruction> |
| static void printLocationAndOp(PrintStream& out, int location, const Instruction*&, const char* op) |
| { |
| out.printf("[%4d] %-17s ", location, op); |
| } |
| |
| static ALWAYS_INLINE bool isConstantRegisterIndex(int index) |
| { |
| return index >= FirstConstantRegisterIndex; |
| } |
| |
| NEVER_INLINE static const char* debugHookName(int debugHookType) |
| { |
| switch (static_cast<DebugHookType>(debugHookType)) { |
| case DidEnterCallFrame: |
| return "didEnterCallFrame"; |
| case WillLeaveCallFrame: |
| return "willLeaveCallFrame"; |
| case WillExecuteStatement: |
| return "willExecuteStatement"; |
| case WillExecuteExpression: |
| return "willExecuteExpression"; |
| case WillExecuteProgram: |
| return "willExecuteProgram"; |
| case DidExecuteProgram: |
| return "didExecuteProgram"; |
| case DidReachBreakpoint: |
| return "didReachBreakpoint"; |
| } |
| |
| RELEASE_ASSERT_NOT_REACHED(); |
| return ""; |
| } |
| |
| template<class Block> |
| CString BytecodeDumper<Block>::registerName(int r) const |
| { |
| if (isConstantRegisterIndex(r)) |
| return constantName(r); |
| |
| return toCString(VirtualRegister(r)); |
| } |
| |
| static CString idName(int id0, const Identifier& ident) |
| { |
| return toCString(ident.impl(), "(@id", id0, ")"); |
| } |
| |
| template<class Block> |
| CString BytecodeDumper<Block>::constantName(int index) const |
| { |
| JSValue value = block()->getConstant(index); |
| return toCString(value, "(", VirtualRegister(index), ")"); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printUnaryOp(PrintStream& out, int location, const typename Block::Instruction*& it, const char* op) |
| { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| |
| printLocationAndOp(out, location, it, op); |
| out.printf("%s, %s", registerName(r0).data(), registerName(r1).data()); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printBinaryOp(PrintStream& out, int location, const typename Block::Instruction*& it, const char* op) |
| { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, op); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data()); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printConditionalJump(PrintStream& out, const typename Block::Instruction*, const typename Block::Instruction*& it, int location, const char* op) |
| { |
| int r0 = (++it)->u.operand; |
| int offset = (++it)->u.operand; |
| printLocationAndOp(out, location, it, op); |
| out.printf("%s, %d(->%d)", registerName(r0).data(), offset, location + offset); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printCompareJump(PrintStream& out, const typename Block::Instruction*, const typename Block::Instruction*& it, int location, const char* op) |
| { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int offset = (++it)->u.operand; |
| printLocationAndOp(out, location, it, op); |
| out.printf("%s, %s, %d(->%d)", registerName(r0).data(), registerName(r1).data(), offset, location + offset); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printGetByIdOp(PrintStream& out, int location, const typename Block::Instruction*& it) |
| { |
| const char* op; |
| switch (Interpreter::getOpcodeID(*it)) { |
| case op_get_by_id: |
| op = "get_by_id"; |
| break; |
| case op_get_by_id_proto_load: |
| op = "get_by_id_proto_load"; |
| break; |
| case op_get_by_id_unset: |
| op = "get_by_id_unset"; |
| break; |
| case op_get_array_length: |
| op = "array_length"; |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| #if COMPILER_QUIRK(CONSIDERS_UNREACHABLE_CODE) |
| op = 0; |
| #endif |
| } |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, op); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), idName(id0, identifier(id0)).data()); |
| it += 4; // Increment up to the value profiler. |
| } |
| |
| static void dumpStructure(PrintStream& out, const char* name, Structure* structure, const Identifier& ident) |
| { |
| if (!structure) |
| return; |
| |
| out.printf("%s = %p", name, structure); |
| |
| PropertyOffset offset = structure->getConcurrently(ident.impl()); |
| if (offset != invalidOffset) |
| out.printf(" (offset = %d)", offset); |
| } |
| |
| static void dumpChain(PrintStream& out, StructureChain* chain, const Identifier& ident) |
| { |
| out.printf("chain = %p: [", chain); |
| bool first = true; |
| for (WriteBarrier<Structure>* currentStructure = chain->head(); *currentStructure; ++currentStructure) { |
| if (first) |
| first = false; |
| else |
| out.printf(", "); |
| dumpStructure(out, "struct", currentStructure->get(), ident); |
| } |
| out.printf("]"); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printGetByIdCacheStatus(PrintStream& out, int location, const StubInfoMap& map) |
| { |
| const auto* instruction = instructionsBegin() + location; |
| |
| const Identifier& ident = identifier(instruction[3].u.operand); |
| |
| UNUSED_PARAM(ident); // tell the compiler to shut up in certain platform configurations. |
| |
| if (Interpreter::getOpcodeID(instruction[0]) == op_get_array_length) |
| out.printf(" llint(array_length)"); |
| else if (StructureID structureID = getStructureID(instruction[4])) { |
| Structure* structure = vm()->heap.structureIDTable().get(structureID); |
| out.printf(" llint("); |
| dumpStructure(out, "struct", structure, ident); |
| out.printf(")"); |
| if (Interpreter::getOpcodeID(instruction[0]) == op_get_by_id_proto_load) |
| out.printf(" proto(%p)", getPointer(instruction[6])); |
| } |
| |
| #if ENABLE(JIT) |
| if (StructureStubInfo* stubPtr = map.get(CodeOrigin(location))) { |
| StructureStubInfo& stubInfo = *stubPtr; |
| if (stubInfo.resetByGC) |
| out.print(" (Reset By GC)"); |
| |
| out.printf(" jit("); |
| |
| Structure* baseStructure = nullptr; |
| PolymorphicAccess* stub = nullptr; |
| |
| switch (stubInfo.cacheType) { |
| case CacheType::GetByIdSelf: |
| out.printf("self"); |
| baseStructure = stubInfo.u.byIdSelf.baseObjectStructure.get(); |
| break; |
| case CacheType::Stub: |
| out.printf("stub"); |
| stub = stubInfo.u.stub; |
| break; |
| case CacheType::Unset: |
| out.printf("unset"); |
| break; |
| case CacheType::ArrayLength: |
| out.printf("ArrayLength"); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| if (baseStructure) { |
| out.printf(", "); |
| dumpStructure(out, "struct", baseStructure, ident); |
| } |
| |
| if (stub) |
| out.print(", ", *stub); |
| |
| out.printf(")"); |
| } |
| #else |
| UNUSED_PARAM(map); |
| #endif |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printPutByIdCacheStatus(PrintStream& out, int location, const StubInfoMap& map) |
| { |
| const auto* instruction = instructionsBegin() + location; |
| |
| const Identifier& ident = identifier(instruction[2].u.operand); |
| |
| UNUSED_PARAM(ident); // tell the compiler to shut up in certain platform configurations. |
| |
| out.print(", ", getPutByIdFlags(instruction[8])); |
| |
| if (StructureID structureID = getStructureID(instruction[4])) { |
| Structure* structure = vm()->heap.structureIDTable().get(structureID); |
| out.print(" llint("); |
| if (StructureID newStructureID = getStructureID(instruction[6])) { |
| Structure* newStructure = vm()->heap.structureIDTable().get(newStructureID); |
| dumpStructure(out, "prev", structure, ident); |
| out.print(", "); |
| dumpStructure(out, "next", newStructure, ident); |
| if (StructureChain* chain = getStructureChain(instruction[7])) { |
| out.print(", "); |
| dumpChain(out, chain, ident); |
| } |
| } else |
| dumpStructure(out, "struct", structure, ident); |
| out.print(")"); |
| } |
| |
| #if ENABLE(JIT) |
| if (StructureStubInfo* stubPtr = map.get(CodeOrigin(location))) { |
| StructureStubInfo& stubInfo = *stubPtr; |
| if (stubInfo.resetByGC) |
| out.print(" (Reset By GC)"); |
| |
| out.printf(" jit("); |
| |
| switch (stubInfo.cacheType) { |
| case CacheType::PutByIdReplace: |
| out.print("replace, "); |
| dumpStructure(out, "struct", stubInfo.u.byIdSelf.baseObjectStructure.get(), ident); |
| break; |
| case CacheType::Stub: { |
| out.print("stub, ", *stubInfo.u.stub); |
| break; |
| } |
| case CacheType::Unset: |
| out.printf("unset"); |
| break; |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| break; |
| } |
| out.printf(")"); |
| } |
| #else |
| UNUSED_PARAM(map); |
| #endif |
| } |
| |
| #if ENABLE(JIT) |
| template<typename Block> |
| void BytecodeDumper<Block>::dumpCallLinkStatus(PrintStream&, unsigned, const CallLinkInfoMap&) |
| { |
| } |
| |
| template<> |
| void BytecodeDumper<CodeBlock>::dumpCallLinkStatus(PrintStream& out, unsigned location, const CallLinkInfoMap& map) |
| { |
| if (block()->jitType() != JITCode::FTLJIT) |
| out.print(" status(", CallLinkStatus::computeFor(block(), location, map), ")"); |
| } |
| #endif |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printCallOp(PrintStream& out, int location, const typename Block::Instruction*& it, const char* op, CacheDumpMode cacheDumpMode, bool& hasPrintedProfiling, const CallLinkInfoMap& map) |
| { |
| int dst = (++it)->u.operand; |
| int func = (++it)->u.operand; |
| int argCount = (++it)->u.operand; |
| int registerOffset = (++it)->u.operand; |
| printLocationAndOp(out, location, it, op); |
| out.print(registerName(dst), ", ", registerName(func), ", ", argCount, ", ", registerOffset); |
| out.print(" (this at ", virtualRegisterForArgument(0, -registerOffset), ")"); |
| if (cacheDumpMode == DumpCaches) { |
| LLIntCallLinkInfo* callLinkInfo = getCallLinkInfo(it[1]); |
| if (callLinkInfo->lastSeenCallee) { |
| JSObject* object = callLinkInfo->lastSeenCallee.get(); |
| if (auto* function = jsDynamicCast<JSFunction*>(*vm(), object)) |
| out.printf(" llint(%p, exec %p)", function, function->executable()); |
| else |
| out.printf(" llint(%p)", object); |
| } |
| #if ENABLE(JIT) |
| if (CallLinkInfo* info = map.get(CodeOrigin(location))) { |
| if (info->haveLastSeenCallee()) { |
| JSObject* object = info->lastSeenCallee(); |
| if (auto* function = jsDynamicCast<JSFunction*>(*vm(), object)) |
| out.printf(" jit(%p, exec %p)", function, function->executable()); |
| else |
| out.printf(" jit(%p)", object); |
| } |
| } |
| |
| dumpCallLinkStatus(out, location, map); |
| #else |
| UNUSED_PARAM(map); |
| #endif |
| } |
| ++it; |
| ++it; |
| dumpArrayProfiling(out, it, hasPrintedProfiling); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printPutByIdOp(PrintStream& out, int location, const typename Block::Instruction*& it, const char* op) |
| { |
| int r0 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, op); |
| out.printf("%s, %s, %s", registerName(r0).data(), idName(id0, identifier(id0)).data(), registerName(r1).data()); |
| it += 5; |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::printLocationOpAndRegisterOperand(PrintStream& out, int location, const typename Block::Instruction*& it, const char* op, int operand) |
| { |
| printLocationAndOp(out, location, it, op); |
| out.printf("%s", registerName(operand).data()); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpBytecode(PrintStream& out, const typename Block::Instruction* begin, const typename Block::Instruction*& it, const StubInfoMap& stubInfos, const CallLinkInfoMap& callLinkInfos) |
| { |
| int location = it - begin; |
| bool hasPrintedProfiling = false; |
| OpcodeID opcode = Interpreter::getOpcodeID(*it); |
| switch (opcode) { |
| case op_enter: { |
| printLocationAndOp(out, location, it, "enter"); |
| break; |
| } |
| case op_get_scope: { |
| int r0 = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "get_scope", r0); |
| break; |
| } |
| case op_create_direct_arguments: { |
| int r0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "create_direct_arguments"); |
| out.printf("%s", registerName(r0).data()); |
| break; |
| } |
| case op_create_scoped_arguments: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "create_scoped_arguments"); |
| out.printf("%s, %s", registerName(r0).data(), registerName(r1).data()); |
| break; |
| } |
| case op_create_cloned_arguments: { |
| int r0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "create_cloned_arguments"); |
| out.printf("%s", registerName(r0).data()); |
| break; |
| } |
| case op_argument_count: { |
| int r0 = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "argument_count", r0); |
| break; |
| } |
| case op_get_argument: { |
| int r0 = (++it)->u.operand; |
| int index = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "argument", r0); |
| out.printf(", %d", index); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_create_rest: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| unsigned argumentOffset = (++it)->u.unsignedValue; |
| printLocationAndOp(out, location, it, "create_rest"); |
| out.printf("%s, %s, ", registerName(r0).data(), registerName(r1).data()); |
| out.printf("ArgumentsOffset: %u", argumentOffset); |
| break; |
| } |
| case op_get_rest_length: { |
| int r0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "get_rest_length"); |
| out.printf("%s, ", registerName(r0).data()); |
| unsigned argumentOffset = (++it)->u.unsignedValue; |
| out.printf("ArgumentsOffset: %u", argumentOffset); |
| break; |
| } |
| case op_create_this: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| unsigned inferredInlineCapacity = (++it)->u.operand; |
| unsigned cachedFunction = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "create_this"); |
| out.printf("%s, %s, %u, %u", registerName(r0).data(), registerName(r1).data(), inferredInlineCapacity, cachedFunction); |
| break; |
| } |
| case op_to_this: { |
| int r0 = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "to_this", r0); |
| Structure* structure = getStructure(*(++it)); |
| if (structure) |
| out.print(", cache(struct = ", RawPointer(structure), ")"); |
| out.print(", ", getToThisStatus(*(++it))); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_check_tdz: { |
| int r0 = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "op_check_tdz", r0); |
| break; |
| } |
| case op_new_object: { |
| int r0 = (++it)->u.operand; |
| unsigned inferredInlineCapacity = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_object"); |
| out.printf("%s, %u", registerName(r0).data(), inferredInlineCapacity); |
| ++it; // Skip object allocation profile. |
| break; |
| } |
| case op_new_array: { |
| int dst = (++it)->u.operand; |
| int argv = (++it)->u.operand; |
| int argc = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_array"); |
| out.printf("%s, %s, %d", registerName(dst).data(), registerName(argv).data(), argc); |
| ++it; // Skip array allocation profile. |
| break; |
| } |
| case op_new_array_with_spread: { |
| int dst = (++it)->u.operand; |
| int argv = (++it)->u.operand; |
| int argc = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_array_with_spread"); |
| out.printf("%s, %s, %d, ", registerName(dst).data(), registerName(argv).data(), argc); |
| unsigned bitVectorIndex = (++it)->u.unsignedValue; |
| const BitVector& bitVector = block()->bitVector(bitVectorIndex); |
| out.print("BitVector:", bitVectorIndex, ":"); |
| for (unsigned i = 0; i < static_cast<unsigned>(argc); i++) { |
| if (bitVector.get(i)) |
| out.print("1"); |
| else |
| out.print("0"); |
| } |
| break; |
| } |
| case op_spread: { |
| int dst = (++it)->u.operand; |
| int arg = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "spread"); |
| out.printf("%s, %s", registerName(dst).data(), registerName(arg).data()); |
| break; |
| } |
| case op_new_array_with_size: { |
| int dst = (++it)->u.operand; |
| int length = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_array_with_size"); |
| out.printf("%s, %s", registerName(dst).data(), registerName(length).data()); |
| ++it; // Skip array allocation profile. |
| break; |
| } |
| case op_new_array_buffer: { |
| int dst = (++it)->u.operand; |
| int array = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_array_buffer"); |
| out.printf("%s, %s", registerName(dst).data(), registerName(array).data()); |
| ++it; // Skip array allocation profile. |
| break; |
| } |
| case op_new_regexp: { |
| int r0 = (++it)->u.operand; |
| int re0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_regexp"); |
| out.printf("%s, ", registerName(r0).data()); |
| if (r0 >=0 && r0 < (int)block()->numberOfRegExps()) |
| out.printf("%s", regexpName(re0, block()->regexp(re0)).data()); |
| else |
| out.printf("bad_regexp(%d)", re0); |
| break; |
| } |
| case op_mov: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "mov"); |
| out.printf("%s, %s", registerName(r0).data(), registerName(r1).data()); |
| break; |
| } |
| case op_profile_type: { |
| int r0 = (++it)->u.operand; |
| ++it; |
| ++it; |
| ++it; |
| ++it; |
| printLocationAndOp(out, location, it, "op_profile_type"); |
| out.printf("%s", registerName(r0).data()); |
| break; |
| } |
| case op_profile_control_flow: { |
| BasicBlockLocation* basicBlockLocation = getBasicBlockLocation(*(++it)); |
| printLocationAndOp(out, location, it, "profile_control_flow"); |
| if (basicBlockLocation) |
| out.printf("[%d, %d]", basicBlockLocation->startOffset(), basicBlockLocation->endOffset()); |
| break; |
| } |
| case op_not: { |
| printUnaryOp(out, location, it, "not"); |
| break; |
| } |
| case op_eq: { |
| printBinaryOp(out, location, it, "eq"); |
| break; |
| } |
| case op_eq_null: { |
| printUnaryOp(out, location, it, "eq_null"); |
| break; |
| } |
| case op_neq: { |
| printBinaryOp(out, location, it, "neq"); |
| break; |
| } |
| case op_neq_null: { |
| printUnaryOp(out, location, it, "neq_null"); |
| break; |
| } |
| case op_stricteq: { |
| printBinaryOp(out, location, it, "stricteq"); |
| break; |
| } |
| case op_nstricteq: { |
| printBinaryOp(out, location, it, "nstricteq"); |
| break; |
| } |
| case op_less: { |
| printBinaryOp(out, location, it, "less"); |
| break; |
| } |
| case op_lesseq: { |
| printBinaryOp(out, location, it, "lesseq"); |
| break; |
| } |
| case op_greater: { |
| printBinaryOp(out, location, it, "greater"); |
| break; |
| } |
| case op_greatereq: { |
| printBinaryOp(out, location, it, "greatereq"); |
| break; |
| } |
| case op_below: { |
| printBinaryOp(out, location, it, "below"); |
| break; |
| } |
| case op_beloweq: { |
| printBinaryOp(out, location, it, "beloweq"); |
| break; |
| } |
| case op_inc: { |
| int r0 = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "inc", r0); |
| break; |
| } |
| case op_dec: { |
| int r0 = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "dec", r0); |
| break; |
| } |
| case op_to_number: { |
| printUnaryOp(out, location, it, "to_number"); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_to_string: { |
| printUnaryOp(out, location, it, "to_string"); |
| break; |
| } |
| case op_to_object: { |
| printUnaryOp(out, location, it, "to_object"); |
| int id0 = (++it)->u.operand; |
| out.printf(" %s", idName(id0, identifier(id0)).data()); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_negate: { |
| printUnaryOp(out, location, it, "negate"); |
| ++it; // op_negate has an extra operand for the ArithProfile. |
| break; |
| } |
| case op_add: { |
| printBinaryOp(out, location, it, "add"); |
| ++it; |
| break; |
| } |
| case op_mul: { |
| printBinaryOp(out, location, it, "mul"); |
| ++it; |
| break; |
| } |
| case op_div: { |
| printBinaryOp(out, location, it, "div"); |
| ++it; |
| break; |
| } |
| case op_mod: { |
| printBinaryOp(out, location, it, "mod"); |
| break; |
| } |
| case op_pow: { |
| printBinaryOp(out, location, it, "pow"); |
| break; |
| } |
| case op_sub: { |
| printBinaryOp(out, location, it, "sub"); |
| ++it; |
| break; |
| } |
| case op_lshift: { |
| printBinaryOp(out, location, it, "lshift"); |
| break; |
| } |
| case op_rshift: { |
| printBinaryOp(out, location, it, "rshift"); |
| break; |
| } |
| case op_urshift: { |
| printBinaryOp(out, location, it, "urshift"); |
| break; |
| } |
| case op_bitand: { |
| printBinaryOp(out, location, it, "bitand"); |
| ++it; |
| break; |
| } |
| case op_bitxor: { |
| printBinaryOp(out, location, it, "bitxor"); |
| ++it; |
| break; |
| } |
| case op_bitor: { |
| printBinaryOp(out, location, it, "bitor"); |
| ++it; |
| break; |
| } |
| case op_overrides_has_instance: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "overrides_has_instance"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data()); |
| break; |
| } |
| case op_instanceof: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "instanceof"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data()); |
| break; |
| } |
| case op_instanceof_custom: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| int r3 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "instanceof_custom"); |
| out.printf("%s, %s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data(), registerName(r3).data()); |
| break; |
| } |
| case op_unsigned: { |
| printUnaryOp(out, location, it, "unsigned"); |
| break; |
| } |
| case op_typeof: { |
| printUnaryOp(out, location, it, "typeof"); |
| break; |
| } |
| case op_is_empty: { |
| printUnaryOp(out, location, it, "is_empty"); |
| break; |
| } |
| case op_is_undefined: { |
| printUnaryOp(out, location, it, "is_undefined"); |
| break; |
| } |
| case op_is_boolean: { |
| printUnaryOp(out, location, it, "is_boolean"); |
| break; |
| } |
| case op_is_number: { |
| printUnaryOp(out, location, it, "is_number"); |
| break; |
| } |
| case op_is_cell_with_type: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int type = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "is_cell_with_type"); |
| out.printf("%s, %s, %d", registerName(r0).data(), registerName(r1).data(), type); |
| break; |
| } |
| case op_is_object: { |
| printUnaryOp(out, location, it, "is_object"); |
| break; |
| } |
| case op_is_object_or_null: { |
| printUnaryOp(out, location, it, "is_object_or_null"); |
| break; |
| } |
| case op_is_function: { |
| printUnaryOp(out, location, it, "is_function"); |
| break; |
| } |
| case op_in: { |
| printBinaryOp(out, location, it, "in"); |
| dumpArrayProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_try_get_by_id: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "try_get_by_id"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), idName(id0, identifier(id0)).data()); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_get_by_id_direct: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "get_by_id_direct"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), idName(id0, identifier(id0)).data()); |
| it += 2; // Increment up to the value profiler. |
| printGetByIdCacheStatus(out, location, stubInfos); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_get_by_id: |
| case op_get_by_id_proto_load: |
| case op_get_by_id_unset: |
| case op_get_array_length: { |
| printGetByIdOp(out, location, it); |
| printGetByIdCacheStatus(out, location, stubInfos); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_get_by_id_with_this: { |
| printLocationAndOp(out, location, it, "get_by_id_with_this"); |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| out.printf("%s, %s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data(), idName(id0, identifier(id0)).data()); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_get_by_val_with_this: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| int r3 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "get_by_val_with_this"); |
| out.printf("%s, %s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data(), registerName(r3).data()); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_put_by_id: { |
| printPutByIdOp(out, location, it, "put_by_id"); |
| printPutByIdCacheStatus(out, location, stubInfos); |
| break; |
| } |
| case op_put_by_id_with_this: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_by_id_with_this"); |
| out.printf("%s, %s, %s, %s", registerName(r0).data(), registerName(r1).data(), idName(id0, identifier(id0)).data(), registerName(r2).data()); |
| break; |
| } |
| case op_put_by_val_with_this: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| int r3 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_by_val_with_this"); |
| out.printf("%s, %s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data(), registerName(r3).data()); |
| break; |
| } |
| case op_put_getter_by_id: { |
| int r0 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| int n0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_getter_by_id"); |
| out.printf("%s, %s, %d, %s", registerName(r0).data(), idName(id0, identifier(id0)).data(), n0, registerName(r1).data()); |
| break; |
| } |
| case op_put_setter_by_id: { |
| int r0 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| int n0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_setter_by_id"); |
| out.printf("%s, %s, %d, %s", registerName(r0).data(), idName(id0, identifier(id0)).data(), n0, registerName(r1).data()); |
| break; |
| } |
| case op_put_getter_setter_by_id: { |
| int r0 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| int n0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_getter_setter_by_id"); |
| out.printf("%s, %s, %d, %s, %s", registerName(r0).data(), idName(id0, identifier(id0)).data(), n0, registerName(r1).data(), registerName(r2).data()); |
| break; |
| } |
| case op_put_getter_by_val: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int n0 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_getter_by_val"); |
| out.printf("%s, %s, %d, %s", registerName(r0).data(), registerName(r1).data(), n0, registerName(r2).data()); |
| break; |
| } |
| case op_put_setter_by_val: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int n0 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_setter_by_val"); |
| out.printf("%s, %s, %d, %s", registerName(r0).data(), registerName(r1).data(), n0, registerName(r2).data()); |
| break; |
| } |
| case op_define_data_property: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| int r3 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "define_data_property"); |
| out.printf("%s, %s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data(), registerName(r3).data()); |
| break; |
| } |
| case op_define_accessor_property: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| int r3 = (++it)->u.operand; |
| int r4 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "define_accessor_property"); |
| out.printf("%s, %s, %s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data(), registerName(r3).data(), registerName(r4).data()); |
| break; |
| } |
| case op_del_by_id: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "del_by_id"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), idName(id0, identifier(id0)).data()); |
| break; |
| } |
| case op_get_by_val: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "get_by_val"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data()); |
| dumpArrayProfiling(out, it, hasPrintedProfiling); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_put_by_val: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_by_val"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data()); |
| dumpArrayProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_put_by_val_direct: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_by_val_direct"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data()); |
| dumpArrayProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_del_by_val: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int r2 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "del_by_val"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(r1).data(), registerName(r2).data()); |
| break; |
| } |
| case op_jmp: { |
| int offset = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "jmp"); |
| out.printf("%d(->%d)", offset, location + offset); |
| break; |
| } |
| case op_jtrue: { |
| printConditionalJump(out, begin, it, location, "jtrue"); |
| break; |
| } |
| case op_jfalse: { |
| printConditionalJump(out, begin, it, location, "jfalse"); |
| break; |
| } |
| case op_jeq_null: { |
| printConditionalJump(out, begin, it, location, "jeq_null"); |
| break; |
| } |
| case op_jneq_null: { |
| printConditionalJump(out, begin, it, location, "jneq_null"); |
| break; |
| } |
| case op_jneq_ptr: { |
| int r0 = (++it)->u.operand; |
| Special::Pointer pointer = getSpecialPointer(*(++it)); |
| int offset = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "jneq_ptr"); |
| out.printf("%s, %d (%p), %d(->%d)", registerName(r0).data(), pointer, actualPointerFor(pointer), offset, location + offset); |
| ++it; |
| break; |
| } |
| case op_jless: { |
| printCompareJump(out, begin, it, location, "jless"); |
| break; |
| } |
| case op_jlesseq: { |
| printCompareJump(out, begin, it, location, "jlesseq"); |
| break; |
| } |
| case op_jgreater: { |
| printCompareJump(out, begin, it, location, "jgreater"); |
| break; |
| } |
| case op_jgreatereq: { |
| printCompareJump(out, begin, it, location, "jgreatereq"); |
| break; |
| } |
| case op_jnless: { |
| printCompareJump(out, begin, it, location, "jnless"); |
| break; |
| } |
| case op_jnlesseq: { |
| printCompareJump(out, begin, it, location, "jnlesseq"); |
| break; |
| } |
| case op_jngreater: { |
| printCompareJump(out, begin, it, location, "jngreater"); |
| break; |
| } |
| case op_jngreatereq: { |
| printCompareJump(out, begin, it, location, "jngreatereq"); |
| break; |
| } |
| case op_jeq: { |
| printCompareJump(out, begin, it, location, "jeq"); |
| break; |
| } |
| case op_jneq: { |
| printCompareJump(out, begin, it, location, "jneq"); |
| break; |
| } |
| case op_jstricteq: { |
| printCompareJump(out, begin, it, location, "jstricteq"); |
| break; |
| } |
| case op_jnstricteq: { |
| printCompareJump(out, begin, it, location, "jnstricteq"); |
| break; |
| } |
| case op_jbelow: { |
| printCompareJump(out, begin, it, location, "jbelow"); |
| break; |
| } |
| case op_jbeloweq: { |
| printCompareJump(out, begin, it, location, "jbeloweq"); |
| break; |
| } |
| case op_loop_hint: { |
| printLocationAndOp(out, location, it, "loop_hint"); |
| break; |
| } |
| case op_check_traps: { |
| printLocationAndOp(out, location, it, "check_traps"); |
| break; |
| } |
| case op_nop: { |
| printLocationAndOp(out, location, it, "nop"); |
| break; |
| } |
| case op_super_sampler_begin: { |
| printLocationAndOp(out, location, it, "super_sampler_begin"); |
| break; |
| } |
| case op_super_sampler_end: { |
| printLocationAndOp(out, location, it, "super_sampler_end"); |
| break; |
| } |
| case op_log_shadow_chicken_prologue: { |
| int r0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "log_shadow_chicken_prologue"); |
| out.printf("%s", registerName(r0).data()); |
| break; |
| } |
| case op_log_shadow_chicken_tail: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "log_shadow_chicken_tail"); |
| out.printf("%s, %s", registerName(r0).data(), registerName(r1).data()); |
| break; |
| } |
| case op_switch_imm: { |
| int tableIndex = (++it)->u.operand; |
| int defaultTarget = (++it)->u.operand; |
| int scrutineeRegister = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "switch_imm"); |
| out.printf("%d, %d(->%d), %s", tableIndex, defaultTarget, location + defaultTarget, registerName(scrutineeRegister).data()); |
| break; |
| } |
| case op_switch_char: { |
| int tableIndex = (++it)->u.operand; |
| int defaultTarget = (++it)->u.operand; |
| int scrutineeRegister = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "switch_char"); |
| out.printf("%d, %d(->%d), %s", tableIndex, defaultTarget, location + defaultTarget, registerName(scrutineeRegister).data()); |
| break; |
| } |
| case op_switch_string: { |
| int tableIndex = (++it)->u.operand; |
| int defaultTarget = (++it)->u.operand; |
| int scrutineeRegister = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "switch_string"); |
| out.printf("%d, %d(->%d), %s", tableIndex, defaultTarget, location + defaultTarget, registerName(scrutineeRegister).data()); |
| break; |
| } |
| case op_new_func: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int f0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_func"); |
| out.printf("%s, %s, f%d", registerName(r0).data(), registerName(r1).data(), f0); |
| break; |
| } |
| case op_new_generator_func: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int f0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_generator_func"); |
| out.printf("%s, %s, f%d", registerName(r0).data(), registerName(r1).data(), f0); |
| break; |
| } |
| case op_new_async_func: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int f0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_async_func"); |
| out.printf("%s, %s, f%d", registerName(r0).data(), registerName(r1).data(), f0); |
| break; |
| } |
| case op_new_async_generator_func: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int f0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_async_generator_func"); |
| out.printf("%s, %s, f%d", registerName(r0).data(), registerName(r1).data(), f0); |
| break; |
| } |
| case op_new_func_exp: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int f0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_func_exp"); |
| out.printf("%s, %s, f%d", registerName(r0).data(), registerName(r1).data(), f0); |
| break; |
| } |
| case op_new_generator_func_exp: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int f0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_generator_func_exp"); |
| out.printf("%s, %s, f%d", registerName(r0).data(), registerName(r1).data(), f0); |
| break; |
| } |
| case op_new_async_func_exp: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int f0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "new_async_func_exp"); |
| out.printf("%s, %s, f%d", registerName(r0).data(), registerName(r1).data(), f0); |
| break; |
| } |
| case op_new_async_generator_func_exp: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int f0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "op_new_async_generator_func_exp"); |
| out.printf("%s, %s, f%d", registerName(r0).data(), registerName(r1).data(), f0); |
| break; |
| } |
| case op_set_function_name: { |
| int funcReg = (++it)->u.operand; |
| int nameReg = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "set_function_name"); |
| out.printf("%s, %s", registerName(funcReg).data(), registerName(nameReg).data()); |
| break; |
| } |
| case op_call: { |
| printCallOp(out, location, it, "call", DumpCaches, hasPrintedProfiling, callLinkInfos); |
| break; |
| } |
| case op_tail_call: { |
| printCallOp(out, location, it, "tail_call", DumpCaches, hasPrintedProfiling, callLinkInfos); |
| break; |
| } |
| case op_call_eval: { |
| printCallOp(out, location, it, "call_eval", DontDumpCaches, hasPrintedProfiling, callLinkInfos); |
| break; |
| } |
| |
| case op_construct_varargs: |
| case op_call_varargs: |
| case op_tail_call_varargs: |
| case op_tail_call_forward_arguments: { |
| int result = (++it)->u.operand; |
| int callee = (++it)->u.operand; |
| int thisValue = (++it)->u.operand; |
| int arguments = (++it)->u.operand; |
| int firstFreeRegister = (++it)->u.operand; |
| int varArgOffset = (++it)->u.operand; |
| ++it; |
| const char* opName; |
| if (opcode == op_call_varargs) |
| opName = "call_varargs"; |
| else if (opcode == op_construct_varargs) |
| opName = "construct_varargs"; |
| else if (opcode == op_tail_call_varargs) |
| opName = "tail_call_varargs"; |
| else if (opcode == op_tail_call_forward_arguments) |
| opName = "tail_call_forward_arguments"; |
| else |
| RELEASE_ASSERT_NOT_REACHED(); |
| |
| printLocationAndOp(out, location, it, opName); |
| out.printf("%s, %s, %s, %s, %d, %d", registerName(result).data(), registerName(callee).data(), registerName(thisValue).data(), registerName(arguments).data(), firstFreeRegister, varArgOffset); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| |
| case op_ret: { |
| int r0 = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "ret", r0); |
| break; |
| } |
| case op_construct: { |
| printCallOp(out, location, it, "construct", DumpCaches, hasPrintedProfiling, callLinkInfos); |
| break; |
| } |
| case op_strcat: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int count = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "strcat"); |
| out.printf("%s, %s, %d", registerName(r0).data(), registerName(r1).data(), count); |
| break; |
| } |
| case op_to_primitive: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "to_primitive"); |
| out.printf("%s, %s", registerName(r0).data(), registerName(r1).data()); |
| break; |
| } |
| case op_get_enumerable_length: { |
| int dst = it[1].u.operand; |
| int base = it[2].u.operand; |
| printLocationAndOp(out, location, it, "op_get_enumerable_length"); |
| out.printf("%s, %s", registerName(dst).data(), registerName(base).data()); |
| it += OPCODE_LENGTH(op_get_enumerable_length) - 1; |
| break; |
| } |
| case op_has_indexed_property: { |
| int dst = (++it)->u.operand; |
| int base = (++it)->u.operand; |
| int propertyName = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "op_has_indexed_property"); |
| out.printf("%s, %s, %s", registerName(dst).data(), registerName(base).data(), registerName(propertyName).data()); |
| dumpArrayProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_has_structure_property: { |
| int dst = it[1].u.operand; |
| int base = it[2].u.operand; |
| int propertyName = it[3].u.operand; |
| int enumerator = it[4].u.operand; |
| printLocationAndOp(out, location, it, "op_has_structure_property"); |
| out.printf("%s, %s, %s, %s", registerName(dst).data(), registerName(base).data(), registerName(propertyName).data(), registerName(enumerator).data()); |
| it += OPCODE_LENGTH(op_has_structure_property) - 1; |
| break; |
| } |
| case op_has_generic_property: { |
| int dst = it[1].u.operand; |
| int base = it[2].u.operand; |
| int propertyName = it[3].u.operand; |
| printLocationAndOp(out, location, it, "op_has_generic_property"); |
| out.printf("%s, %s, %s", registerName(dst).data(), registerName(base).data(), registerName(propertyName).data()); |
| it += OPCODE_LENGTH(op_has_generic_property) - 1; |
| break; |
| } |
| case op_get_direct_pname: { |
| int dst = (++it)->u.operand; |
| int base = (++it)->u.operand; |
| int propertyName = (++it)->u.operand; |
| int index = (++it)->u.operand; |
| int enumerator = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "op_get_direct_pname"); |
| out.printf("%s, %s, %s, %s, %s", registerName(dst).data(), registerName(base).data(), registerName(propertyName).data(), registerName(index).data(), registerName(enumerator).data()); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| |
| } |
| case op_get_property_enumerator: { |
| int dst = it[1].u.operand; |
| int base = it[2].u.operand; |
| printLocationAndOp(out, location, it, "op_get_property_enumerator"); |
| out.printf("%s, %s", registerName(dst).data(), registerName(base).data()); |
| it += OPCODE_LENGTH(op_get_property_enumerator) - 1; |
| break; |
| } |
| case op_enumerator_structure_pname: { |
| int dst = it[1].u.operand; |
| int enumerator = it[2].u.operand; |
| int index = it[3].u.operand; |
| printLocationAndOp(out, location, it, "op_enumerator_structure_pname"); |
| out.printf("%s, %s, %s", registerName(dst).data(), registerName(enumerator).data(), registerName(index).data()); |
| it += OPCODE_LENGTH(op_enumerator_structure_pname) - 1; |
| break; |
| } |
| case op_enumerator_generic_pname: { |
| int dst = it[1].u.operand; |
| int enumerator = it[2].u.operand; |
| int index = it[3].u.operand; |
| printLocationAndOp(out, location, it, "op_enumerator_generic_pname"); |
| out.printf("%s, %s, %s", registerName(dst).data(), registerName(enumerator).data(), registerName(index).data()); |
| it += OPCODE_LENGTH(op_enumerator_generic_pname) - 1; |
| break; |
| } |
| case op_to_index_string: { |
| int dst = it[1].u.operand; |
| int index = it[2].u.operand; |
| printLocationAndOp(out, location, it, "op_to_index_string"); |
| out.printf("%s, %s", registerName(dst).data(), registerName(index).data()); |
| it += OPCODE_LENGTH(op_to_index_string) - 1; |
| break; |
| } |
| case op_push_with_scope: { |
| int dst = (++it)->u.operand; |
| int newScope = (++it)->u.operand; |
| int currentScope = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "push_with_scope"); |
| out.printf("%s, %s, %s", registerName(dst).data(), registerName(newScope).data(), registerName(currentScope).data()); |
| break; |
| } |
| case op_get_parent_scope: { |
| int dst = (++it)->u.operand; |
| int parentScope = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "get_parent_scope"); |
| out.printf("%s, %s", registerName(dst).data(), registerName(parentScope).data()); |
| break; |
| } |
| case op_create_lexical_environment: { |
| int dst = (++it)->u.operand; |
| int scope = (++it)->u.operand; |
| int symbolTable = (++it)->u.operand; |
| int initialValue = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "create_lexical_environment"); |
| out.printf("%s, %s, %s, %s", |
| registerName(dst).data(), registerName(scope).data(), registerName(symbolTable).data(), registerName(initialValue).data()); |
| break; |
| } |
| case op_catch: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| void* pointer = getPointer(*(++it)); |
| printLocationAndOp(out, location, it, "catch"); |
| out.printf("%s, %s, %p", registerName(r0).data(), registerName(r1).data(), pointer); |
| break; |
| } |
| case op_throw: { |
| int r0 = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "throw", r0); |
| break; |
| } |
| case op_throw_static_error: { |
| int r0 = (++it)->u.operand; |
| ErrorType k1 = static_cast<ErrorType>((++it)->u.unsignedValue); |
| printLocationAndOp(out, location, it, "throw_static_error"); |
| out.printf("%s, ", registerName(r0).data()); |
| out.print(k1); |
| break; |
| } |
| case op_debug: { |
| int debugHookType = (++it)->u.operand; |
| int hasBreakpointFlag = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "debug"); |
| out.printf("%s, %d", debugHookName(debugHookType), hasBreakpointFlag); |
| break; |
| } |
| case op_identity_with_profile: { |
| int r0 = (++it)->u.operand; |
| ++it; // Profile top half |
| ++it; // Profile bottom half |
| printLocationAndOp(out, location, it, "identity_with_profile"); |
| out.printf("%s", registerName(r0).data()); |
| break; |
| } |
| case op_unreachable: { |
| printLocationAndOp(out, location, it, "unreachable"); |
| break; |
| } |
| case op_end: { |
| int r0 = (++it)->u.operand; |
| printLocationOpAndRegisterOperand(out, location, it, "end", r0); |
| break; |
| } |
| case op_resolve_scope_for_hoisting_func_decl_in_eval: { |
| int r0 = (++it)->u.operand; |
| int scope = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "resolve_scope_for_hoisting_func_decl_in_eval"); |
| out.printf("%s, %s, %s", registerName(r0).data(), registerName(scope).data(), idName(id0, identifier(id0)).data()); |
| break; |
| } |
| case op_resolve_scope: { |
| int r0 = (++it)->u.operand; |
| int scope = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| ResolveType resolveType = static_cast<ResolveType>((++it)->u.operand); |
| int depth = (++it)->u.operand; |
| void* pointer = getPointer(*(++it)); |
| printLocationAndOp(out, location, it, "resolve_scope"); |
| out.printf("%s, %s, %s, <%s>, %d, %p", registerName(r0).data(), registerName(scope).data(), idName(id0, identifier(id0)).data(), resolveTypeName(resolveType), depth, pointer); |
| break; |
| } |
| case op_get_from_scope: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| GetPutInfo getPutInfo = GetPutInfo((++it)->u.operand); |
| ++it; // Structure |
| int operand = (++it)->u.operand; // Operand |
| printLocationAndOp(out, location, it, "get_from_scope"); |
| out.print(registerName(r0), ", ", registerName(r1)); |
| if (static_cast<unsigned>(id0) == UINT_MAX) |
| out.print(", anonymous"); |
| else |
| out.print(", ", idName(id0, identifier(id0))); |
| out.print(", ", getPutInfo.operand(), "<", resolveModeName(getPutInfo.resolveMode()), "|", resolveTypeName(getPutInfo.resolveType()), "|", initializationModeName(getPutInfo.initializationMode()), ">, ", operand); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_put_to_scope: { |
| int r0 = (++it)->u.operand; |
| int id0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| GetPutInfo getPutInfo = GetPutInfo((++it)->u.operand); |
| ++it; // Structure |
| int operand = (++it)->u.operand; // Operand |
| printLocationAndOp(out, location, it, "put_to_scope"); |
| out.print(registerName(r0)); |
| if (static_cast<unsigned>(id0) == UINT_MAX) |
| out.print(", anonymous"); |
| else |
| out.print(", ", idName(id0, identifier(id0))); |
| out.print(", ", registerName(r1), ", ", getPutInfo.operand(), "<", resolveModeName(getPutInfo.resolveMode()), "|", resolveTypeName(getPutInfo.resolveType()), "|", initializationModeName(getPutInfo.initializationMode()), ">, <structure>, ", operand); |
| break; |
| } |
| case op_get_from_arguments: { |
| int r0 = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| int offset = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "get_from_arguments"); |
| out.printf("%s, %s, %d", registerName(r0).data(), registerName(r1).data(), offset); |
| dumpValueProfiling(out, it, hasPrintedProfiling); |
| break; |
| } |
| case op_put_to_arguments: { |
| int r0 = (++it)->u.operand; |
| int offset = (++it)->u.operand; |
| int r1 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "put_to_arguments"); |
| out.printf("%s, %d, %s", registerName(r0).data(), offset, registerName(r1).data()); |
| break; |
| } |
| case op_yield: { |
| int r0 = (++it)->u.operand; |
| unsigned yieldPoint = (++it)->u.unsignedValue; |
| int r1 = (++it)->u.operand; |
| printLocationAndOp(out, location, it, "yield"); |
| out.printf("%s, %u, %s", registerName(r0).data(), yieldPoint, registerName(r1).data()); |
| break; |
| } |
| default: |
| RELEASE_ASSERT_NOT_REACHED(); |
| } |
| dumpProfilesForBytecodeOffset(out, location, hasPrintedProfiling); |
| out.print("\n"); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpBytecode(Block* block, PrintStream& out, const typename Block::Instruction* begin, const typename Block::Instruction*& it, const StubInfoMap& stubInfos, const CallLinkInfoMap& callLinkInfos) |
| { |
| BytecodeDumper dumper(block, begin); |
| dumper.dumpBytecode(out, begin, it, stubInfos, callLinkInfos); |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpIdentifiers(PrintStream& out) |
| { |
| if (size_t count = block()->numberOfIdentifiers()) { |
| out.printf("\nIdentifiers:\n"); |
| size_t i = 0; |
| do { |
| out.printf(" id%u = %s\n", static_cast<unsigned>(i), identifier(i).string().utf8().data()); |
| ++i; |
| } while (i != count); |
| } |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpConstants(PrintStream& out) |
| { |
| if (!block()->constantRegisters().isEmpty()) { |
| out.printf("\nConstants:\n"); |
| size_t i = 0; |
| for (const auto& constant : block()->constantRegisters()) { |
| const char* sourceCodeRepresentationDescription = nullptr; |
| switch (block()->constantsSourceCodeRepresentation()[i]) { |
| case SourceCodeRepresentation::Double: |
| sourceCodeRepresentationDescription = ": in source as double"; |
| break; |
| case SourceCodeRepresentation::Integer: |
| sourceCodeRepresentationDescription = ": in source as integer"; |
| break; |
| case SourceCodeRepresentation::Other: |
| sourceCodeRepresentationDescription = ""; |
| break; |
| } |
| out.printf(" k%u = %s%s\n", static_cast<unsigned>(i), toCString(constant.get()).data(), sourceCodeRepresentationDescription); |
| ++i; |
| } |
| } |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpRegExps(PrintStream& out) |
| { |
| if (size_t count = block()->numberOfRegExps()) { |
| out.printf("\nm_regexps:\n"); |
| size_t i = 0; |
| do { |
| out.printf(" re%u = %s\n", static_cast<unsigned>(i), regexpToSourceString(block()->regexp(i)).data()); |
| ++i; |
| } while (i < count); |
| } |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpExceptionHandlers(PrintStream& out) |
| { |
| if (unsigned count = block()->numberOfExceptionHandlers()) { |
| out.printf("\nException Handlers:\n"); |
| unsigned i = 0; |
| do { |
| const auto& handler = block()->exceptionHandler(i); |
| out.printf("\t %d: { start: [%4d] end: [%4d] target: [%4d] } %s\n", i + 1, handler.start, handler.end, handler.target, handler.typeName()); |
| ++i; |
| } while (i < count); |
| } |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpSwitchJumpTables(PrintStream& out) |
| { |
| if (unsigned count = block()->numberOfSwitchJumpTables()) { |
| out.printf("Switch Jump Tables:\n"); |
| unsigned i = 0; |
| do { |
| out.printf(" %1d = {\n", i); |
| const auto& switchJumpTable = block()->switchJumpTable(i); |
| int entry = 0; |
| auto end = switchJumpTable.branchOffsets.end(); |
| for (auto iter = switchJumpTable.branchOffsets.begin(); iter != end; ++iter, ++entry) { |
| if (!*iter) |
| continue; |
| out.printf("\t\t%4d => %04d\n", entry + switchJumpTable.min, *iter); |
| } |
| out.printf(" }\n"); |
| ++i; |
| } while (i < count); |
| } |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpStringSwitchJumpTables(PrintStream& out) |
| { |
| if (unsigned count = block()->numberOfStringSwitchJumpTables()) { |
| out.printf("\nString Switch Jump Tables:\n"); |
| unsigned i = 0; |
| do { |
| out.printf(" %1d = {\n", i); |
| const auto& stringSwitchJumpTable = block()->stringSwitchJumpTable(i); |
| auto end = stringSwitchJumpTable.offsetTable.end(); |
| for (auto iter = stringSwitchJumpTable.offsetTable.begin(); iter != end; ++iter) |
| out.printf("\t\t\"%s\" => %04d\n", iter->key->utf8().data(), iter->value.branchOffset); |
| out.printf(" }\n"); |
| ++i; |
| } while (i < count); |
| } |
| } |
| |
| template<class Block> |
| void BytecodeDumper<Block>::dumpBlock(Block* block, const typename Block::UnpackedInstructions& instructions, PrintStream& out, const StubInfoMap& stubInfos, const CallLinkInfoMap& callLinkInfos) |
| { |
| size_t instructionCount = 0; |
| |
| for (size_t i = 0; i < instructions.size(); i += opcodeLengths[Interpreter::getOpcodeID(instructions[i])]) |
| ++instructionCount; |
| |
| out.print(*block); |
| out.printf( |
| ": %lu m_instructions; %lu bytes; %d parameter(s); %d callee register(s); %d variable(s)", |
| static_cast<unsigned long>(instructions.size()), |
| static_cast<unsigned long>(instructions.size() * sizeof(Instruction)), |
| block->numParameters(), block->numCalleeLocals(), block->m_numVars); |
| out.print("; scope at ", block->scopeRegister()); |
| out.printf("\n"); |
| |
| const auto* begin = instructions.begin(); |
| const auto* end = instructions.end(); |
| BytecodeDumper<Block> dumper(block, begin); |
| for (const auto* it = begin; it != end; ++it) |
| dumper.dumpBytecode(out, begin, it, stubInfos, callLinkInfos); |
| |
| dumper.dumpIdentifiers(out); |
| dumper.dumpConstants(out); |
| dumper.dumpRegExps(out); |
| dumper.dumpExceptionHandlers(out); |
| dumper.dumpSwitchJumpTables(out); |
| dumper.dumpStringSwitchJumpTables(out); |
| |
| out.printf("\n"); |
| } |
| |
| template class BytecodeDumper<UnlinkedCodeBlock>; |
| template class BytecodeDumper<CodeBlock>; |
| |
| } |