blob: 532abc7b44b4cfc6140bcacd7a3ced3709fb4820 [file] [log] [blame]
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "vm/bytecode_disassembler.h"
#include <iomanip>
#include <sstream>
#include "absl/base/macros.h"
#include "absl/container/inlined_vector.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "base/status.h"
#include "schemas/bytecode/bytecode_v0.h"
#include "schemas/source_map_def_generated.h"
#include "vm/bytecode_module.h"
#include "vm/bytecode_util.h"
#include "vm/type.h"
namespace iree {
namespace vm {
namespace {
using ::iree::rt::SourceOffset;
template <typename T>
StatusOr<T> ReadValue(absl::Span<const uint8_t> data, SourceOffset* offset) {
if (*offset + sizeof(T) > data.size()) {
return OutOfRangeErrorBuilder(IREE_LOC) << "Bytecode data underrun";
}
auto value = *reinterpret_cast<const T*>(&data[*offset]);
*offset = *offset + sizeof(T);
return value;
}
StatusOr<const Type> ReadType(absl::Span<const uint8_t> data,
SourceOffset* offset) {
ASSIGN_OR_RETURN(uint8_t type_index, ReadValue<uint8_t>(data, offset));
return Type::FromTypeIndex(type_index);
}
StatusOr<uint8_t> ReadCount(absl::Span<const uint8_t> data,
SourceOffset* offset) {
return ReadValue<uint8_t>(data, offset);
}
StatusOr<uint16_t> ReadValueSlot(absl::Span<const uint8_t> data,
SourceOffset* offset) {
return ReadValue<uint16_t>(data, offset);
}
absl::string_view ConstantEncodingToString(ConstantEncoding encoding) {
switch (encoding) {
#define GET_NAME(ordinal, enum_name, str, ...) \
case ConstantEncoding::enum_name: \
return str;
IREE_CONSTANT_ENCODING_LIST(GET_NAME)
#undef GET_NAME
default:
return "unknown";
}
}
template <typename T>
std::string TypedDataToString(absl::Span<const uint8_t> bytes) {
auto typed_data = absl::Span<const T>{
reinterpret_cast<const T*>(bytes.data()), bytes.size() / sizeof(T)};
return absl::StrJoin(typed_data, ",");
}
std::string ConstantToString(const Type& type,
absl::Span<const uint8_t> bytes) {
if (!type.is_builtin()) {
return absl::StrJoin(bytes, ",");
}
switch (type.builtin_type()) {
case BuiltinType::kI8:
return TypedDataToString<uint8_t>(bytes);
case BuiltinType::kI16:
return TypedDataToString<uint16_t>(bytes);
case BuiltinType::kI32:
return TypedDataToString<uint32_t>(bytes);
case BuiltinType::kI64:
return TypedDataToString<uint64_t>(bytes);
case BuiltinType::kF16:
return TypedDataToString<uint16_t>(bytes);
case BuiltinType::kF32:
return TypedDataToString<float>(bytes);
case BuiltinType::kF64:
return TypedDataToString<double>(bytes);
default:
return "<unsupported>";
}
}
} // namespace
StatusOr<std::vector<rt::Instruction>>
BytecodeDisassembler::DisassembleInstructions(const rt::Function& function,
SourceOffset offset,
int32_t instruction_count) const {
std::vector<rt::Instruction> instructions;
ASSIGN_OR_RETURN(
auto* function_def,
static_cast<const BytecodeModule*>(function.module())
->GetFunctionDef(function.linkage(), function.ordinal()));
auto* bytecode_def = function_def->bytecode();
if (!bytecode_def) {
return UnavailableErrorBuilder(IREE_LOC) << "Function contains no body";
}
auto data = absl::MakeSpan(
reinterpret_cast<const uint8_t*>(bytecode_def->contents()->data()),
bytecode_def->contents()->size());
// TODO(benvanik): scan and find all branch offsets to insert labels
while (offset < data.length() && instructions.size() < instruction_count) {
instructions.push_back({});
auto& instruction = instructions.back();
instruction.offset = offset;
uint8_t opcode = data[offset++];
const auto& opcode_info = GetOpcodeInfo(opcode_table_, opcode);
if (!opcode_info.mnemonic) {
return UnimplementedErrorBuilder(IREE_LOC)
<< "Unhandled opcode " << opcode << " at offset " << (offset - 1);
}
int payload_offset = offset;
std::ostringstream stream;
// Print out return values, if any.
int base_result_index = 0;
int printed_result_count = 0;
for (int i = base_result_index; i < ABSL_ARRAYSIZE(opcode_info.operands);
++i) {
if (opcode_info.operands[i] == OperandEncoding::kNone) break;
if (printed_result_count > 0) {
stream << ", ";
}
switch (opcode_info.operands[i]) {
default:
case OperandEncoding::kNone:
return UnimplementedErrorBuilder(IREE_LOC)
<< "Unhandled op encoding "
<< static_cast<int>(opcode_info.operands[i]) << " at offset "
<< (offset - 1);
case OperandEncoding::kInputSlot:
case OperandEncoding::kOutputSlot: {
// Printing handled below.
offset += sizeof(uint16_t);
break;
}
case OperandEncoding::kVariadicInputSlots:
case OperandEncoding::kVariadicOutputSlots: {
// Printing handled below.
ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
offset += count * sizeof(uint16_t);
break;
}
case OperandEncoding::kResultSlot: {
++printed_result_count;
ASSIGN_OR_RETURN(uint16_t slot_ordinal, ReadValueSlot(data, &offset));
stream << "%" << slot_ordinal;
break;
}
case OperandEncoding::kVariadicResultSlots: {
++printed_result_count;
stream << "[";
ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
for (int j = 0; j < count; ++j) {
ASSIGN_OR_RETURN(uint16_t slot_ordinal,
ReadValueSlot(data, &offset));
if (j > 0) stream << ", ";
stream << "%" << slot_ordinal;
}
stream << "]";
break;
}
case OperandEncoding::kVariadicTransferSlots: {
// Printing handled below.
ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
offset += count * 2 * sizeof(uint16_t);
break;
}
case OperandEncoding::kConstant: {
// Printing handled below.
ASSIGN_OR_RETURN(auto type, ReadType(data, &offset));
ASSIGN_OR_RETURN(int rank, ReadCount(data, &offset));
int element_count = 1;
for (int j = 0; j < rank; ++j) {
ASSIGN_OR_RETURN(int dim, ReadValue<int32_t>(data, &offset));
element_count *= dim;
}
offset += sizeof(ConstantEncoding);
offset += element_count * type.element_size();
break;
}
case OperandEncoding::kFunctionOrdinal: {
// Printing handled below.
offset += sizeof(uint32_t);
break;
}
case OperandEncoding::kDispatchOrdinal: {
// Printing handled below.
offset += sizeof(uint32_t) + sizeof(uint16_t);
break;
}
case OperandEncoding::kBlockOffset: {
// Printing handled below.
offset += sizeof(uint32_t);
break;
}
case OperandEncoding::kTypeIndex: {
// Printing handled below.
offset += sizeof(uint8_t);
break;
}
case OperandEncoding::kIndex: {
// Printing handled below.
offset += sizeof(int32_t);
break;
}
case OperandEncoding::kIndexList: {
// Printing handled below.
ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
offset += count * sizeof(int32_t);
break;
}
case OperandEncoding::kCmpIPredicate:
case OperandEncoding::kCmpFPredicate: {
// Printing handled below.
offset += sizeof(uint8_t);
break;
}
}
}
if (printed_result_count > 0) {
stream << " = ";
}
offset = payload_offset;
stream << opcode_info.mnemonic;
// Print out operands.
int base_operand_index = 0;
int printed_operand_count = 0;
for (int i = base_operand_index; i < ABSL_ARRAYSIZE(opcode_info.operands);
++i) {
if (opcode_info.operands[i] == OperandEncoding::kNone) break;
if (opcode_info.operands[i] != OperandEncoding::kResultSlot &&
opcode_info.operands[i] != OperandEncoding::kVariadicResultSlots) {
if (i == base_operand_index) {
stream << " ";
} else if (printed_operand_count > 0) {
stream << ", ";
}
}
switch (opcode_info.operands[i]) {
default:
case OperandEncoding::kNone:
return UnimplementedErrorBuilder(IREE_LOC)
<< "Unhandled op encoding "
<< static_cast<int>(opcode_info.operands[i]) << " at offset "
<< (offset - 1);
case OperandEncoding::kInputSlot: {
++printed_operand_count;
ASSIGN_OR_RETURN(uint16_t slot_ordinal, ReadValueSlot(data, &offset));
stream << "%" << slot_ordinal;
break;
}
case OperandEncoding::kVariadicInputSlots: {
++printed_operand_count;
stream << "[";
ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
for (int j = 0; j < count; ++j) {
ASSIGN_OR_RETURN(uint16_t slot_ordinal,
ReadValueSlot(data, &offset));
if (j > 0) stream << ", ";
stream << "%" << slot_ordinal;
}
stream << "]";
break;
}
case OperandEncoding::kOutputSlot: {
++printed_operand_count;
ASSIGN_OR_RETURN(uint16_t slot_ordinal, ReadValueSlot(data, &offset));
stream << "&"
<< "%" << slot_ordinal;
break;
}
case OperandEncoding::kVariadicOutputSlots: {
++printed_operand_count;
stream << "[";
ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
for (int j = 0; j < count; ++j) {
ASSIGN_OR_RETURN(uint16_t slot_ordinal,
ReadValueSlot(data, &offset));
if (j > 0) stream << ", ";
stream << "&"
<< "%" << slot_ordinal;
}
stream << "]";
break;
}
case OperandEncoding::kResultSlot: {
// Printing handled above.
offset += sizeof(uint16_t);
break;
}
case OperandEncoding::kVariadicResultSlots: {
// Printing handled above.
ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
offset += count * sizeof(uint16_t);
break;
}
case OperandEncoding::kVariadicTransferSlots: {
++printed_operand_count;
stream << "[";
ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
for (int j = 0; j < count; ++j) {
ASSIGN_OR_RETURN(uint16_t src_slot_ordinal,
ReadValueSlot(data, &offset));
ASSIGN_OR_RETURN(uint16_t dst_slot_ordinal,
ReadValueSlot(data, &offset));
if (j > 0) stream << ", ";
stream << "%" << src_slot_ordinal << "=>%" << dst_slot_ordinal;
}
stream << "]";
break;
}
case OperandEncoding::kConstant: {
++printed_operand_count;
ASSIGN_OR_RETURN(auto type, ReadType(data, &offset));
ASSIGN_OR_RETURN(int rank, ReadCount(data, &offset));
absl::InlinedVector<int32_t, 4> shape(rank);
int element_count = 1;
for (int j = 0; j < rank; ++j) {
ASSIGN_OR_RETURN(int dim, ReadValue<int32_t>(data, &offset));
shape[j] = dim;
element_count *= dim;
}
ASSIGN_OR_RETURN(auto encoding,
ReadValue<ConstantEncoding>(data, &offset));
stream << ConstantEncodingToString(encoding);
int serialized_element_count = 1;
switch (encoding) {
case ConstantEncoding::kDense:
serialized_element_count = element_count;
break;
case ConstantEncoding::kSplat:
serialized_element_count = 1;
break;
default:
return UnimplementedErrorBuilder(IREE_LOC)
<< "Unimplemented constant encoding "
<< static_cast<int>(encoding);
}
stream << " buffer_view<";
if (!shape.empty()) {
stream << absl::StrJoin(shape, "x") << "x";
}
stream << type << ">{";
size_t element_size = type.element_size();
auto bytes = data.subspan(
offset, std::min(serialized_element_count, 1024) * element_size);
stream << ConstantToString(type, bytes);
if (serialized_element_count > 1024) stream << "...";
offset += serialized_element_count * element_size;
stream << "}";
break;
}
case OperandEncoding::kFunctionOrdinal: {
++printed_operand_count;
ASSIGN_OR_RETURN(auto function_ordinal,
ReadValue<uint32_t>(data, &offset));
ASSIGN_OR_RETURN(
auto target_function,
function.module()->LookupFunctionByOrdinal(
rt::Function::Linkage::kInternal, function_ordinal));
stream << "@" << function_ordinal << " " << target_function.name();
break;
}
case OperandEncoding::kDispatchOrdinal: {
++printed_operand_count;
ASSIGN_OR_RETURN(auto dispatch_ordinal,
ReadValue<uint32_t>(data, &offset));
ASSIGN_OR_RETURN(auto export_ordinal,
ReadValue<uint16_t>(data, &offset));
// TODO(benvanik): lookup in executable table.
stream << "@" << dispatch_ordinal << ":" << export_ordinal;
break;
}
case OperandEncoding::kImportOrdinal: {
++printed_operand_count;
ASSIGN_OR_RETURN(auto import_ordinal,
ReadValue<uint32_t>(data, &offset));
ASSIGN_OR_RETURN(auto target_function,
function.module()->LookupFunctionByOrdinal(
rt::Function::Linkage::kImport, import_ordinal));
stream << "@i" << import_ordinal << " " << target_function.name();
break;
}
case OperandEncoding::kBlockOffset: {
++printed_operand_count;
ASSIGN_OR_RETURN(uint32_t block_offset,
ReadValue<uint32_t>(data, &offset));
stream << ":" << block_offset;
break;
}
case OperandEncoding::kTypeIndex: {
++printed_operand_count;
ASSIGN_OR_RETURN(auto type, ReadType(data, &offset));
stream << type;
break;
}
case OperandEncoding::kIndex: {
++printed_operand_count;
ASSIGN_OR_RETURN(auto index, ReadValue<int32_t>(data, &offset));
stream << "#" << index;
break;
}
case OperandEncoding::kIndexList: {
++printed_operand_count;
stream << "{";
ASSIGN_OR_RETURN(int count, ReadCount(data, &offset));
for (int j = 0; j < count; ++j) {
ASSIGN_OR_RETURN(auto dim, ReadValue<int32_t>(data, &offset));
if (j > 0) stream << ",";
stream << dim;
}
stream << "}";
break;
}
case OperandEncoding::kCmpIPredicate: {
++printed_operand_count;
ASSIGN_OR_RETURN(auto predicate_value,
ReadValue<uint8_t>(data, &offset));
stream << "<"
<< PredicateToString(
static_cast<CmpIPredicate>(predicate_value))
<< ">";
break;
}
case OperandEncoding::kCmpFPredicate: {
++printed_operand_count;
ASSIGN_OR_RETURN(auto predicate_value,
ReadValue<uint8_t>(data, &offset));
stream << "<"
<< PredicateToString(
static_cast<CmpFPredicate>(predicate_value))
<< ">";
break;
}
}
}
stream << "\n";
instruction.long_text = stream.str();
instruction.short_text = instruction.long_text;
}
return instructions;
}
} // namespace vm
} // namespace iree