/* * Copyright (C) 2017 Yusuke Suzuki * Copyright (C) 2017-2022 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 "BytecodeGenerator.h" #include "BytecodeGraph.h" #include "BytecodeStructs.h" #include "CodeBlock.h" #include "JSCJSValueInlines.h" #include "UnlinkedCodeBlockGenerator.h" #include "UnlinkedMetadataTableInlines.h" #include "WasmModuleInformation.h" #include "WasmTypeDefinitionInlines.h" WTF_ALLOW_UNSAFE_BUFFER_USAGE_BEGIN namespace JSC { template void BytecodeDumperBase::printLocationAndOp(typename InstructionStreamType::Offset location, const char* op) { m_currentLocation = location; m_out.printf("[%4u] %-18s ", location, op); } template void BytecodeDumperBase::dumpValue(VirtualRegister reg) { m_out.print(registerName(reg)); } template template void BytecodeDumperBase::dumpValue(GenericBoundLabel label) { int target = label.target(); if (!target) target = outOfLineJumpOffset(m_currentLocation); auto targetOffset = target + m_currentLocation; m_out.print(target, "(->", targetOffset, ")"); } template void BytecodeDumperBase::dumpValue(GenericBoundLabel); template UTF8CString BytecodeDumper::registerName(VirtualRegister r) const { if (r.isConstant()) return constantName(r); return toUTF8CString(r); } template int BytecodeDumper::outOfLineJumpOffset(JSInstructionStream::Offset offset) const { return m_block->outOfLineJumpOffset(offset); } template UTF8CString BytecodeDumper::constantName(VirtualRegister reg) const { if (reg.toConstantIndex() >= (int) block()->constantRegisters().size()) return toUTF8CString("INVALID_CONSTANT(", reg, ")"); auto value = block()->getConstant(reg); return toUTF8CString(value, "(", reg, ")"); } template void BytecodeDumper::dumpBytecode(const JSInstructionStream::Ref& it, const ICStatusMap&) { ::JSC::dumpBytecode(this, it.offset(), it.ptr()); this->m_out.print("\n"); } template void BytecodeDumper::dumpBytecode(Block* block, PrintStream& out, const JSInstructionStream::Ref& it, const ICStatusMap& statusMap) { BytecodeDumper dumper(block, out); dumper.dumpBytecode(it, statusMap); } template VM& CodeBlockBytecodeDumper::vm() const { return this->block()->vm(); } template const Identifier& CodeBlockBytecodeDumper::identifier(int index) const { return this->block()->identifier(index); } template void CodeBlockBytecodeDumper::dumpIdentifiers() { if (size_t count = this->block()->numberOfIdentifiers()) { this->m_out.printf("\nIdentifiers:\n"); size_t i = 0; do { this->m_out.print(" id", static_cast(i), " = ", identifier(i), "\n"); ++i; } while (i != count); } } template void CodeBlockBytecodeDumper::dumpConstants() { if (!this->block()->constantRegisters().isEmpty()) { this->m_out.printf("\nConstants:\n"); size_t i = 0; for (const auto& constant : this->block()->constantRegisters()) { const char* sourceCodeRepresentationDescription = nullptr; switch (this->block()->constantSourceCodeRepresentation(i)) { case SourceCodeRepresentation::Double: sourceCodeRepresentationDescription = ": in source as double"; break; case SourceCodeRepresentation::Integer: sourceCodeRepresentationDescription = ": in source as integer"; break; case SourceCodeRepresentation::Other: sourceCodeRepresentationDescription = ""; break; case SourceCodeRepresentation::LinkTimeConstant: sourceCodeRepresentationDescription = ": in source as link-time-constant"; break; } this->m_out.print(" k", static_cast(i), " = ", constant.get(), sourceCodeRepresentationDescription, "\n"); ++i; } } } template void CodeBlockBytecodeDumper::dumpExceptionHandlers() { if (unsigned count = this->block()->numberOfExceptionHandlers()) { this->m_out.printf("\nException Handlers:\n"); unsigned i = 0; do { const auto& handler = this->block()->exceptionHandler(i); this->m_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 void CodeBlockBytecodeDumper::dumpSwitchJumpTables() { if (unsigned count = this->block()->numberOfUnlinkedSwitchJumpTables()) { this->m_out.printf("Switch Jump Tables:\n"); unsigned i = 0; do { this->m_out.printf(" %1d = {\n", i); const auto& unlinkedTable = this->block()->unlinkedSwitchJumpTable(i); int entry = 0; auto end = unlinkedTable.m_branchOffsets.end(); if (unlinkedTable.isList()) { for (auto iter = unlinkedTable.m_branchOffsets.begin(); iter != end;) { int32_t value = *iter++; int32_t target = *iter++; this->m_out.printf("\t\t%4d => %04d\n", value, target); } } else { for (auto iter = unlinkedTable.m_branchOffsets.begin(); iter != end; ++iter, ++entry) { if (!*iter) continue; this->m_out.printf("\t\t%4d => %04d\n", entry + unlinkedTable.m_min, *iter); } } this->m_out.printf("\t\tdefault => %04d\n", unlinkedTable.m_defaultOffset); this->m_out.printf(" }\n"); ++i; } while (i < count); } } template void CodeBlockBytecodeDumper::dumpStringSwitchJumpTables() { if (unsigned count = this->block()->numberOfUnlinkedStringSwitchJumpTables()) { this->m_out.printf("\nString Switch Jump Tables:\n"); unsigned i = 0; do { this->m_out.printf(" %1d = {\n", i); auto& unlinkedTable = this->block()->unlinkedStringSwitchJumpTable(i); for (const auto& entry : unlinkedTable.m_offsetTable) { this->m_out.print("\t\t\"", entry.key->utf8(), "\" => "); this->m_out.printf("%04d\n", entry.value.m_branchOffset); } this->m_out.printf("\t\tdefault => %04d\n", unlinkedTable.m_defaultOffset); this->m_out.printf(" }\n"); ++i; } while (i < count); } } template static void dumpHeader(Block* block, const JSInstructionStream& instructions, PrintStream& out) { size_t instructionCount = 0; size_t wide16InstructionCount = 0; size_t wide32InstructionCount = 0; size_t instructionWithMetadataCount = 0; for (const auto& instruction : instructions) { if (instruction->isWide16()) ++wide16InstructionCount; else if (instruction->isWide32()) ++wide32InstructionCount; if (instruction->hasMetadata()) ++instructionWithMetadataCount; ++instructionCount; } out.print(*block); out.printf( ": %lu instructions (%lu 16-bit instructions, %lu 32-bit instructions, %lu instructions with metadata); %lu bytes (%lu metadata bytes); %d parameter(s); %d callee register(s); %d variable(s)", static_cast(instructionCount), static_cast(wide16InstructionCount), static_cast(wide32InstructionCount), static_cast(instructionWithMetadataCount), static_cast(instructions.sizeInBytes() + block->metadataSizeInBytes()), static_cast(block->metadataSizeInBytes()), block->numParameters(), block->numCalleeLocals(), block->numVars()); out.print("; scope at ", block->scopeRegister()); out.printf("\n"); } template static void dumpFooter(Dumper& dumper) { dumper.dumpIdentifiers(); dumper.dumpConstants(); dumper.dumpExceptionHandlers(); dumper.dumpSwitchJumpTables(); dumper.dumpStringSwitchJumpTables(); } template void CodeBlockBytecodeDumper::dumpBlock(Block* block, const JSInstructionStream& instructions, PrintStream& out, const ICStatusMap& statusMap) { dumpHeader(block, instructions, out); CodeBlockBytecodeDumper dumper(block, out); for (const auto& it : instructions) dumper.dumpBytecode(it, statusMap); dumpFooter(dumper); out.printf("\n"); } template void CodeBlockBytecodeDumper::dumpGraph(Block* block, const JSInstructionStream& instructions, BytecodeGraph& graph, PrintStream& out, const ICStatusMap& icStatusMap) { dumpHeader(block, instructions, out); CodeBlockBytecodeDumper dumper(block, out); out.printf("\n"); Vector> predecessors(graph.size()); for (auto& block : graph) { if (block.isEntryBlock() || block.isExitBlock()) continue; for (auto successorIndex : block.successors()) { if (!predecessors[successorIndex].contains(block.index())) predecessors[successorIndex].append(block.index()); } } for (auto& block : graph) { if (block.isEntryBlock() || block.isExitBlock()) continue; out.print("bb#", block.index(), "\n"); out.print("Predecessors: ["); for (unsigned predecessor : predecessors[block.index()]) { if (!graph[predecessor].isEntryBlock()) out.print(" #", predecessor); } out.print(" ]\n"); for (unsigned i = 0; i < block.totalLength(); ) { auto& currentInstruction = instructions.at(i + block.leaderOffset()); dumper.dumpBytecode(currentInstruction, icStatusMap); i += currentInstruction.ptr()->size(); } out.print("Successors: ["); for (unsigned successor : block.successors()) { if (!graph[successor].isExitBlock()) out.print(" #", successor); } out.print(" ]\n\n"); } dumpFooter(dumper); out.printf("\n"); } template class BytecodeDumperBase; template class BytecodeDumper; template class CodeBlockBytecodeDumper; template class CodeBlockBytecodeDumper; } WTF_ALLOW_UNSAFE_BUFFER_USAGE_END