blob: 5690295120e1d57cb70130476fb2f60f71e711ba [file] [log] [blame]
// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/wasm/wasm-text.h"
#include "src/debug/interface-types.h"
#include "src/utils/ostreams.h"
#include "src/utils/vector.h"
#include "src/objects/objects-inl.h"
#include "src/wasm/function-body-decoder-impl.h"
#include "src/wasm/function-body-decoder.h"
#include "src/wasm/wasm-module.h"
#include "src/wasm/wasm-opcodes.h"
#include "src/zone/zone.h"
#if V8_OS_STARBOARD
#include "src/poems.h"
#endif
namespace v8 {
namespace internal {
namespace wasm {
namespace {
bool IsValidFunctionName(const Vector<const char> &name) {
if (name.empty()) return false;
const char *special_chars = "_.+-*/\\^~=<>!?@#$%&|:'`";
for (char c : name) {
bool valid_char = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') || strchr(special_chars, c);
if (!valid_char) return false;
}
return true;
}
} // namespace
void PrintWasmText(const WasmModule* module, const ModuleWireBytes& wire_bytes,
uint32_t func_index, std::ostream& os,
debug::WasmDisassembly::OffsetTable* offset_table) {
DCHECK_NOT_NULL(module);
DCHECK_GT(module->functions.size(), func_index);
const WasmFunction *fun = &module->functions[func_index];
AccountingAllocator allocator;
Zone zone(&allocator, ZONE_NAME);
int line_nr = 0;
int control_depth = 1;
// Print the function signature.
os << "func";
WasmName fun_name = wire_bytes.GetNameOrNull(fun, module);
if (IsValidFunctionName(fun_name)) {
os << " $";
os.write(fun_name.begin(), fun_name.length());
}
if (fun->sig->parameter_count()) {
os << " (param";
for (auto param : fun->sig->parameters())
os << ' ' << ValueTypes::TypeName(param);
os << ')';
}
if (fun->sig->return_count()) {
os << " (result";
for (auto ret : fun->sig->returns()) os << ' ' << ValueTypes::TypeName(ret);
os << ')';
}
os << "\n";
++line_nr;
// Print the local declarations.
BodyLocalDecls decls(&zone);
Vector<const byte> func_bytes = wire_bytes.GetFunctionBytes(fun);
BytecodeIterator i(func_bytes.begin(), func_bytes.end(), &decls);
DCHECK_LT(func_bytes.begin(), i.pc());
if (!decls.type_list.empty()) {
os << "(local";
for (const ValueType &v : decls.type_list) {
os << ' ' << ValueTypes::TypeName(v);
}
os << ")\n";
++line_nr;
}
for (; i.has_next(); i.next()) {
WasmOpcode opcode = i.current();
if (opcode == kExprElse || opcode == kExprCatch || opcode == kExprEnd) {
--control_depth;
}
DCHECK_LE(0, control_depth);
const int kMaxIndentation = 64;
int indentation = std::min(kMaxIndentation, 2 * control_depth);
if (offset_table) {
offset_table->emplace_back(i.pc_offset(), line_nr, indentation);
}
// 64 whitespaces
const char padding[kMaxIndentation + 1] =
" ";
os.write(padding, indentation);
switch (opcode) {
case kExprLoop:
case kExprIf:
case kExprBlock:
case kExprTry: {
BlockTypeImmediate<Decoder::kNoValidate> imm(kAllWasmFeatures, &i,
i.pc());
os << WasmOpcodes::OpcodeName(opcode);
if (imm.type == kWasmBottom) {
os << " (type " << imm.sig_index << ")";
} else if (imm.out_arity() > 0) {
os << " " << ValueTypes::TypeName(imm.out_type(0));
}
control_depth++;
break;
}
case kExprBr:
case kExprBrIf: {
BranchDepthImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.depth;
break;
}
case kExprBrOnExn: {
BranchOnExceptionImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.depth.depth << ' '
<< imm.index.index;
break;
}
case kExprElse:
case kExprCatch:
os << WasmOpcodes::OpcodeName(opcode);
control_depth++;
break;
case kExprEnd:
os << "end";
break;
case kExprBrTable: {
BranchTableImmediate<Decoder::kNoValidate> imm(&i, i.pc());
BranchTableIterator<Decoder::kNoValidate> iterator(&i, imm);
os << "br_table";
while (iterator.has_next()) os << ' ' << iterator.next();
break;
}
case kExprCallIndirect:
case kExprReturnCallIndirect: {
CallIndirectImmediate<Decoder::kNoValidate> imm(kAllWasmFeatures, &i,
i.pc());
DCHECK_EQ(0, imm.table_index);
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.sig_index;
break;
}
case kExprCallFunction:
case kExprReturnCall: {
CallFunctionImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
case kExprGetLocal:
case kExprSetLocal:
case kExprTeeLocal: {
LocalIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
case kExprThrow: {
ExceptionIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
case kExprGetGlobal:
case kExprSetGlobal: {
GlobalIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
case kExprTableGet:
case kExprTableSet: {
TableIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
case kExprSelectWithType: {
SelectTypeImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' '
<< ValueTypes::TypeName(imm.type);
break;
}
#define CASE_CONST(type, str, cast_type) \
case kExpr##type##Const: { \
Imm##type##Immediate<Decoder::kNoValidate> imm(&i, i.pc()); \
os << #str ".const " << static_cast<cast_type>(imm.value); \
break; \
}
CASE_CONST(I32, i32, int32_t)
CASE_CONST(I64, i64, int64_t)
CASE_CONST(F32, f32, float)
CASE_CONST(F64, f64, double)
#undef CASE_CONST
case kExprRefFunc: {
FunctionIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
#define CASE_OPCODE(opcode, _, __) case kExpr##opcode:
FOREACH_LOAD_MEM_OPCODE(CASE_OPCODE)
FOREACH_STORE_MEM_OPCODE(CASE_OPCODE) {
MemoryAccessImmediate<Decoder::kNoValidate> imm(&i, i.pc(),
kMaxUInt32);
os << WasmOpcodes::OpcodeName(opcode) << " offset=" << imm.offset
<< " align=" << (1ULL << imm.alignment);
break;
}
FOREACH_SIMPLE_OPCODE(CASE_OPCODE)
FOREACH_SIMPLE_PROTOTYPE_OPCODE(CASE_OPCODE)
case kExprUnreachable:
case kExprNop:
case kExprReturn:
case kExprMemorySize:
case kExprMemoryGrow:
case kExprDrop:
case kExprSelect:
case kExprRethrow:
case kExprRefNull:
os << WasmOpcodes::OpcodeName(opcode);
break;
case kNumericPrefix: {
WasmOpcode numeric_opcode = i.prefixed_opcode();
switch (numeric_opcode) {
case kExprI32SConvertSatF32:
case kExprI32UConvertSatF32:
case kExprI32SConvertSatF64:
case kExprI32UConvertSatF64:
case kExprI64SConvertSatF32:
case kExprI64UConvertSatF32:
case kExprI64SConvertSatF64:
case kExprI64UConvertSatF64:
case kExprMemoryCopy:
case kExprMemoryFill:
os << WasmOpcodes::OpcodeName(opcode);
break;
case kExprMemoryInit: {
MemoryInitImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' '
<< imm.data_segment_index;
break;
}
case kExprDataDrop: {
DataDropImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
case kExprTableInit: {
TableInitImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' '
<< imm.elem_segment_index << ' ' << imm.table.index;
break;
}
case kExprElemDrop: {
ElemDropImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
case kExprTableCopy: {
TableCopyImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.table_src.index
<< ' ' << imm.table_dst.index;
break;
}
case kExprTableGrow:
case kExprTableSize:
case kExprTableFill: {
TableIndexImmediate<Decoder::kNoValidate> imm(&i, i.pc() + 1);
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.index;
break;
}
default:
UNREACHABLE();
break;
}
break;
}
case kSimdPrefix: {
WasmOpcode simd_opcode = i.prefixed_opcode();
switch (simd_opcode) {
case kExprS128LoadMem:
case kExprS128StoreMem: {
MemoryAccessImmediate<Decoder::kNoValidate> imm(&i, i.pc(),
kMaxUInt32);
os << WasmOpcodes::OpcodeName(opcode) << " offset=" << imm.offset
<< " align=" << (1ULL << imm.alignment);
break;
}
case kExprS8x16Shuffle: {
Simd8x16ShuffleImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode);
for (uint8_t v : imm.shuffle) {
os << ' ' << v;
}
break;
}
case kExprI8x16ExtractLane:
case kExprI16x8ExtractLane:
case kExprI32x4ExtractLane:
case kExprI64x2ExtractLane:
case kExprF32x4ExtractLane:
case kExprF64x2ExtractLane:
case kExprI8x16ReplaceLane:
case kExprI16x8ReplaceLane:
case kExprI32x4ReplaceLane:
case kExprI64x2ReplaceLane:
case kExprF32x4ReplaceLane:
case kExprF64x2ReplaceLane: {
SimdLaneImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.lane;
break;
}
case kExprI8x16Shl:
case kExprI8x16ShrS:
case kExprI8x16ShrU:
case kExprI16x8Shl:
case kExprI16x8ShrS:
case kExprI16x8ShrU:
case kExprI32x4Shl:
case kExprI32x4ShrS:
case kExprI32x4ShrU:
case kExprI64x2Shl:
case kExprI64x2ShrS:
case kExprI64x2ShrU: {
SimdShiftImmediate<Decoder::kNoValidate> imm(&i, i.pc());
os << WasmOpcodes::OpcodeName(opcode) << ' ' << imm.shift;
break;
}
FOREACH_SIMD_0_OPERAND_OPCODE(CASE_OPCODE) {
os << WasmOpcodes::OpcodeName(opcode);
break;
}
default:
UNREACHABLE();
break;
}
break;
}
case kAtomicPrefix: {
WasmOpcode atomic_opcode = i.prefixed_opcode();
switch (atomic_opcode) {
FOREACH_ATOMIC_OPCODE(CASE_OPCODE) {
MemoryAccessImmediate<Decoder::kNoValidate> imm(&i, i.pc() + 1,
kMaxUInt32);
os << WasmOpcodes::OpcodeName(atomic_opcode)
<< " offset=" << imm.offset
<< " align=" << (1ULL << imm.alignment);
break;
}
FOREACH_ATOMIC_0_OPERAND_OPCODE(CASE_OPCODE) {
os << WasmOpcodes::OpcodeName(atomic_opcode);
break;
}
default:
UNREACHABLE();
break;
}
break;
}
// This group is just printed by their internal opcode name, as they
// should never be shown to end-users.
FOREACH_ASMJS_COMPAT_OPCODE(CASE_OPCODE) {
os << WasmOpcodes::OpcodeName(opcode);
}
break;
#undef CASE_OPCODE
default:
UNREACHABLE();
break;
}
os << '\n';
++line_nr;
}
DCHECK_EQ(0, control_depth);
DCHECK(i.ok());
}
} // namespace wasm
} // namespace internal
} // namespace v8