blob: e5c9c4d2ece43512c79464690915ca455f184e1a [file] [log] [blame] [edit]
/*
* Copyright 2016 WebAssembly Community Group participants
*
* 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
*
* http://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 "src/binary-writer.h"
#include <cassert>
#include <cmath>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <vector>
#include "config.h"
#include "src/binary.h"
#include "src/cast.h"
#include "src/ir.h"
#include "src/leb128.h"
#include "src/stream.h"
#include "src/string-view.h"
#define PRINT_HEADER_NO_INDEX -1
#define MAX_U32_LEB128_BYTES 5
namespace wabt {
void WriteStr(Stream* stream,
string_view s,
const char* desc,
PrintChars print_chars) {
WriteU32Leb128(stream, s.length(), "string length");
stream->WriteData(s.data(), s.length(), desc, print_chars);
}
void WriteOpcode(Stream* stream, Opcode opcode) {
if (opcode.HasPrefix()) {
stream->WriteU8(opcode.GetPrefix(), "prefix");
WriteU32Leb128(stream, opcode.GetCode(), opcode.GetName());
} else {
stream->WriteU8(opcode.GetCode(), opcode.GetName());
}
}
void WriteType(Stream* stream, Type type, const char* desc) {
WriteS32Leb128(stream, type, desc ? desc : type.GetName());
}
void WriteLimits(Stream* stream, const Limits* limits) {
uint32_t flags = limits->has_max ? WABT_BINARY_LIMITS_HAS_MAX_FLAG : 0;
flags |= limits->is_shared ? WABT_BINARY_LIMITS_IS_SHARED_FLAG : 0;
WriteU32Leb128(stream, flags, "limits: flags");
WriteU32Leb128(stream, limits->initial, "limits: initial");
if (limits->has_max) {
WriteU32Leb128(stream, limits->max, "limits: max");
}
}
void WriteDebugName(Stream* stream, string_view name, const char* desc) {
string_view stripped_name = name;
if (!stripped_name.empty()) {
// Strip leading $ from name
assert(stripped_name.front() == '$');
stripped_name.remove_prefix(1);
}
WriteStr(stream, stripped_name, desc, PrintChars::Yes);
}
namespace {
/* TODO(binji): better leb size guess. Some sections we know will only be 1
byte, but others we can be fairly certain will be larger. */
static const size_t LEB_SECTION_SIZE_GUESS = 1;
#define ALLOC_FAILURE \
fprintf(stderr, "%s:%d: allocation failed\n", __FILE__, __LINE__)
struct RelocSection {
RelocSection(const char* name, Index index)
: name(name), section_index(index) {}
const char* name;
Index section_index;
std::vector<Reloc> relocations;
};
struct Symbol {
Index symbol_index;
SymbolType type;
Index element_index;
};
class BinaryWriter {
WABT_DISALLOW_COPY_AND_ASSIGN(BinaryWriter);
public:
BinaryWriter(Stream*,
const WriteBinaryOptions& options,
const Module* module);
Result WriteModule();
private:
void WriteHeader(const char* name, int index);
Offset WriteU32Leb128Space(Offset leb_size_guess, const char* desc);
Offset WriteFixupU32Leb128Size(Offset offset,
Offset leb_size_guess,
const char* desc);
void BeginKnownSection(BinarySection section_code);
void BeginCustomSection(const char* name);
void WriteSectionHeader(const char* desc, BinarySection section_code);
void EndSection();
void BeginSubsection(const char* name);
void EndSubsection();
Index GetLabelVarDepth(const Var* var);
Index GetEventVarDepth(const Var* var);
Index GetLocalIndex(const Func* func, const Var& var);
Index GetSymbolIndex(RelocType reloc_type, Index index);
void AddReloc(RelocType reloc_type, Index index);
void WriteBlockDecl(const BlockDeclaration& decl);
void WriteU32Leb128WithReloc(Index index,
const char* desc,
RelocType reloc_type);
template <typename T>
void WriteLoadStoreExpr(const Func* func, const Expr* expr, const char* desc);
void WriteExpr(const Func* func, const Expr* expr);
void WriteExprList(const Func* func, const ExprList& exprs);
void WriteInitExpr(const ExprList& expr);
void WriteFuncLocals(const Func* func, const LocalTypes& local_types);
void WriteFunc(const Func* func);
void WriteTable(const Table* table);
void WriteMemory(const Memory* memory);
void WriteGlobalHeader(const Global* global);
void WriteEventType(const Event* event);
void WriteRelocSection(const RelocSection* reloc_section);
void WriteLinkingSection();
Stream* stream_;
const WriteBinaryOptions& options_;
const Module* module_;
std::unordered_map<std::string, Index> symtab_;
std::vector<Symbol> symbols_;
std::vector<RelocSection> reloc_sections_;
RelocSection* current_reloc_section_ = nullptr;
Index section_count_ = 0;
size_t last_section_offset_ = 0;
size_t last_section_leb_size_guess_ = 0;
BinarySection last_section_type_ = BinarySection::Invalid;
size_t last_section_payload_offset_ = 0;
size_t last_subsection_offset_ = 0;
size_t last_subsection_leb_size_guess_ = 0;
size_t last_subsection_payload_offset_ = 0;
// Information about the data count section, so it can be removed if it is
// not needed.
size_t data_count_start_ = 0;
size_t data_count_end_ = 0;
bool has_data_segment_instruction_ = false;
};
static uint8_t log2_u32(uint32_t x) {
uint8_t result = 0;
while (x > 1) {
x >>= 1;
result++;
}
return result;
}
BinaryWriter::BinaryWriter(Stream* stream,
const WriteBinaryOptions& options,
const Module* module)
: stream_(stream), options_(options), module_(module) {}
void BinaryWriter::WriteHeader(const char* name, int index) {
if (stream_->has_log_stream()) {
if (index == PRINT_HEADER_NO_INDEX) {
stream_->log_stream().Writef("; %s\n", name);
} else {
stream_->log_stream().Writef("; %s %d\n", name, index);
}
}
}
/* returns offset of leb128 */
Offset BinaryWriter::WriteU32Leb128Space(Offset leb_size_guess,
const char* desc) {
assert(leb_size_guess <= MAX_U32_LEB128_BYTES);
uint8_t data[MAX_U32_LEB128_BYTES] = {0};
Offset result = stream_->offset();
Offset bytes_to_write =
options_.canonicalize_lebs ? leb_size_guess : MAX_U32_LEB128_BYTES;
stream_->WriteData(data, bytes_to_write, desc);
return result;
}
Offset BinaryWriter::WriteFixupU32Leb128Size(Offset offset,
Offset leb_size_guess,
const char* desc) {
if (options_.canonicalize_lebs) {
Offset size = stream_->offset() - offset - leb_size_guess;
Offset leb_size = U32Leb128Length(size);
Offset delta = leb_size - leb_size_guess;
if (delta != 0) {
Offset src_offset = offset + leb_size_guess;
Offset dst_offset = offset + leb_size;
stream_->MoveData(dst_offset, src_offset, size);
}
WriteU32Leb128At(stream_, offset, size, desc);
stream_->AddOffset(delta);
return delta;
} else {
Offset size = stream_->offset() - offset - MAX_U32_LEB128_BYTES;
WriteFixedU32Leb128At(stream_, offset, size, desc);
return 0;
}
}
void BinaryWriter::WriteBlockDecl(const BlockDeclaration& decl) {
if (decl.sig.GetNumParams() == 0 && decl.sig.GetNumResults() <= 1) {
if (decl.sig.GetNumResults() == 0) {
WriteType(stream_, Type::Void);
} else if (decl.sig.GetNumResults() == 1) {
WriteType(stream_, decl.sig.GetResultType(0));
}
return;
}
Index index = decl.has_func_type ? module_->GetFuncTypeIndex(decl.type_var)
: module_->GetFuncTypeIndex(decl.sig);
assert(index != kInvalidIndex);
WriteS32Leb128(stream_, index, "block type function index");
}
void BinaryWriter::WriteSectionHeader(const char* desc,
BinarySection section_code) {
assert(last_section_leb_size_guess_ == 0);
WriteHeader(desc, PRINT_HEADER_NO_INDEX);
stream_->WriteU8Enum(section_code, "section code");
last_section_type_ = section_code;
last_section_leb_size_guess_ = LEB_SECTION_SIZE_GUESS;
last_section_offset_ =
WriteU32Leb128Space(LEB_SECTION_SIZE_GUESS, "section size (guess)");
last_section_payload_offset_ = stream_->offset();
}
void BinaryWriter::BeginKnownSection(BinarySection section_code) {
char desc[100];
wabt_snprintf(desc, sizeof(desc), "section \"%s\" (%u)",
GetSectionName(section_code),
static_cast<unsigned>(section_code));
WriteSectionHeader(desc, section_code);
}
void BinaryWriter::BeginCustomSection(const char* name) {
char desc[100];
wabt_snprintf(desc, sizeof(desc), "section \"%s\"", name);
WriteSectionHeader(desc, BinarySection::Custom);
WriteStr(stream_, name, "custom section name", PrintChars::Yes);
}
void BinaryWriter::EndSection() {
assert(last_section_leb_size_guess_ != 0);
Offset delta = WriteFixupU32Leb128Size(
last_section_offset_, last_section_leb_size_guess_, "FIXUP section size");
if (current_reloc_section_ && delta != 0) {
for (Reloc& reloc : current_reloc_section_->relocations) {
reloc.offset += delta;
}
}
last_section_leb_size_guess_ = 0;
section_count_++;
}
void BinaryWriter::BeginSubsection(const char* name) {
assert(last_subsection_leb_size_guess_ == 0);
last_subsection_leb_size_guess_ = LEB_SECTION_SIZE_GUESS;
last_subsection_offset_ =
WriteU32Leb128Space(LEB_SECTION_SIZE_GUESS, "subsection size (guess)");
last_subsection_payload_offset_ = stream_->offset();
}
void BinaryWriter::EndSubsection() {
assert(last_subsection_leb_size_guess_ != 0);
WriteFixupU32Leb128Size(last_subsection_offset_,
last_subsection_leb_size_guess_,
"FIXUP subsection size");
last_subsection_leb_size_guess_ = 0;
}
Index BinaryWriter::GetLabelVarDepth(const Var* var) {
return var->index();
}
Index BinaryWriter::GetEventVarDepth(const Var* var) {
return var->index();
}
Index BinaryWriter::GetSymbolIndex(RelocType reloc_type, Index index) {
std::string name;
SymbolType type = SymbolType::Function;
switch (reloc_type) {
case RelocType::FuncIndexLEB:
name = module_->funcs[index]->name;
break;
case RelocType::GlobalIndexLEB:
type = SymbolType::Global;
name = module_->globals[index]->name;
break;
default:
// TODO: Add support for TypeIndexLEB.
fprintf(stderr, "warning: unsupported relocation type: %s\n",
GetRelocTypeName(reloc_type));
return kInvalidIndex;
}
auto iter = symtab_.find(name);
if (iter != symtab_.end()) {
return iter->second;
}
Index sym_index = Index(symbols_.size());
symtab_[name] = sym_index;
symbols_.push_back(Symbol{sym_index, type, index});
return sym_index;
}
void BinaryWriter::AddReloc(RelocType reloc_type, Index index) {
// Add a new reloc section if needed
if (!current_reloc_section_ ||
current_reloc_section_->section_index != section_count_) {
reloc_sections_.emplace_back(GetSectionName(last_section_type_), section_count_);
current_reloc_section_ = &reloc_sections_.back();
}
// Add a new relocation to the curent reloc section
size_t offset = stream_->offset() - last_section_payload_offset_;
Index symbol_index = GetSymbolIndex(reloc_type, index);
current_reloc_section_->relocations.emplace_back(reloc_type, offset,
symbol_index);
}
void BinaryWriter::WriteU32Leb128WithReloc(Index index,
const char* desc,
RelocType reloc_type) {
if (options_.relocatable) {
AddReloc(reloc_type, index);
WriteFixedU32Leb128(stream_, index, desc);
} else {
WriteU32Leb128(stream_, index, desc);
}
}
Index BinaryWriter::GetLocalIndex(const Func* func, const Var& var) {
// func can be nullptr when using local.get/local.set/local.tee in an
// init_expr.
if (func) {
return func->GetLocalIndex(var);
} else if (var.is_index()) {
return var.index();
} else {
return kInvalidIndex;
}
}
// TODO(binji): Rename this, it is used for more than loads/stores now.
template <typename T>
void BinaryWriter::WriteLoadStoreExpr(const Func* func,
const Expr* expr,
const char* desc) {
auto* typed_expr = cast<T>(expr);
WriteOpcode(stream_, typed_expr->opcode);
Address align = typed_expr->opcode.GetAlignment(typed_expr->align);
stream_->WriteU8(log2_u32(align), "alignment");
WriteU32Leb128(stream_, typed_expr->offset, desc);
}
void BinaryWriter::WriteExpr(const Func* func, const Expr* expr) {
switch (expr->type()) {
case ExprType::AtomicLoad:
WriteLoadStoreExpr<AtomicLoadExpr>(func, expr, "memory offset");
break;
case ExprType::AtomicRmw:
WriteLoadStoreExpr<AtomicRmwExpr>(func, expr, "memory offset");
break;
case ExprType::AtomicRmwCmpxchg:
WriteLoadStoreExpr<AtomicRmwCmpxchgExpr>(func, expr, "memory offset");
break;
case ExprType::AtomicStore:
WriteLoadStoreExpr<AtomicStoreExpr>(func, expr, "memory offset");
break;
case ExprType::AtomicWait:
WriteLoadStoreExpr<AtomicWaitExpr>(func, expr, "memory offset");
break;
case ExprType::AtomicNotify:
WriteLoadStoreExpr<AtomicNotifyExpr>(func, expr, "memory offset");
break;
case ExprType::Binary:
WriteOpcode(stream_, cast<BinaryExpr>(expr)->opcode);
break;
case ExprType::Block:
WriteOpcode(stream_, Opcode::Block);
WriteBlockDecl(cast<BlockExpr>(expr)->block.decl);
WriteExprList(func, cast<BlockExpr>(expr)->block.exprs);
WriteOpcode(stream_, Opcode::End);
break;
case ExprType::Br:
WriteOpcode(stream_, Opcode::Br);
WriteU32Leb128(stream_, GetLabelVarDepth(&cast<BrExpr>(expr)->var),
"break depth");
break;
case ExprType::BrIf:
WriteOpcode(stream_, Opcode::BrIf);
WriteU32Leb128(stream_, GetLabelVarDepth(&cast<BrIfExpr>(expr)->var),
"break depth");
break;
case ExprType::BrOnExn: {
auto* br_on_exn_expr = cast<BrOnExnExpr>(expr);
WriteOpcode(stream_, Opcode::BrOnExn);
WriteU32Leb128(stream_, GetLabelVarDepth(&br_on_exn_expr->label_var),
"break depth");
WriteU32Leb128(stream_, module_->GetEventIndex(br_on_exn_expr->event_var),
"event index");
break;
}
case ExprType::BrTable: {
auto* br_table_expr = cast<BrTableExpr>(expr);
WriteOpcode(stream_, Opcode::BrTable);
WriteU32Leb128(stream_, br_table_expr->targets.size(), "num targets");
Index depth;
for (const Var& var : br_table_expr->targets) {
depth = GetLabelVarDepth(&var);
WriteU32Leb128(stream_, depth, "break depth");
}
depth = GetLabelVarDepth(&br_table_expr->default_target);
WriteU32Leb128(stream_, depth, "break depth for default");
break;
}
case ExprType::Call:{
Index index = module_->GetFuncIndex(cast<CallExpr>(expr)->var);
WriteOpcode(stream_, Opcode::Call);
WriteU32Leb128WithReloc(index, "function index", RelocType::FuncIndexLEB);
break;
}
case ExprType::ReturnCall: {
Index index = module_->GetFuncIndex(cast<ReturnCallExpr>(expr)->var);
WriteOpcode(stream_, Opcode::ReturnCall);
WriteU32Leb128WithReloc(index, "function index", RelocType::FuncIndexLEB);
break;
}
case ExprType::CallIndirect:{
Index sig_index =
module_->GetFuncTypeIndex(cast<CallIndirectExpr>(expr)->decl);
Index table_index =
module_->GetTableIndex(cast<CallIndirectExpr>(expr)->table);
WriteOpcode(stream_, Opcode::CallIndirect);
WriteU32Leb128WithReloc(sig_index, "signature index", RelocType::TypeIndexLEB);
WriteU32Leb128(stream_, table_index, "table index");
break;
}
case ExprType::ReturnCallIndirect: {
Index sig_index =
module_->GetFuncTypeIndex(cast<ReturnCallIndirectExpr>(expr)->decl);
Index table_index =
module_->GetTableIndex(cast<ReturnCallIndirectExpr>(expr)->table);
WriteOpcode(stream_, Opcode::ReturnCallIndirect);
WriteU32Leb128WithReloc(sig_index, "signature index", RelocType::TypeIndexLEB);
WriteU32Leb128(stream_, table_index, "table index");
break;
}
case ExprType::Compare:
WriteOpcode(stream_, cast<CompareExpr>(expr)->opcode);
break;
case ExprType::Const: {
const Const& const_ = cast<ConstExpr>(expr)->const_;
switch (const_.type()) {
case Type::I32: {
WriteOpcode(stream_, Opcode::I32Const);
WriteS32Leb128(stream_, const_.u32(), "i32 literal");
break;
}
case Type::I64:
WriteOpcode(stream_, Opcode::I64Const);
WriteS64Leb128(stream_, const_.u64(), "i64 literal");
break;
case Type::F32:
WriteOpcode(stream_, Opcode::F32Const);
stream_->WriteU32(const_.f32_bits(), "f32 literal");
break;
case Type::F64:
WriteOpcode(stream_, Opcode::F64Const);
stream_->WriteU64(const_.f64_bits(), "f64 literal");
break;
case Type::V128:
WriteOpcode(stream_, Opcode::V128Const);
stream_->WriteU128(const_.vec128(), "v128 literal");
break;
default:
assert(0);
}
break;
}
case ExprType::Convert:
WriteOpcode(stream_, cast<ConvertExpr>(expr)->opcode);
break;
case ExprType::Drop:
WriteOpcode(stream_, Opcode::Drop);
break;
case ExprType::GlobalGet: {
Index index = module_->GetGlobalIndex(cast<GlobalGetExpr>(expr)->var);
WriteOpcode(stream_, Opcode::GlobalGet);
WriteU32Leb128WithReloc(index, "global index", RelocType::GlobalIndexLEB);
break;
}
case ExprType::GlobalSet: {
Index index = module_->GetGlobalIndex(cast<GlobalSetExpr>(expr)->var);
WriteOpcode(stream_, Opcode::GlobalSet);
WriteU32Leb128WithReloc(index, "global index", RelocType::GlobalIndexLEB);
break;
}
case ExprType::If: {
auto* if_expr = cast<IfExpr>(expr);
WriteOpcode(stream_, Opcode::If);
WriteBlockDecl(if_expr->true_.decl);
WriteExprList(func, if_expr->true_.exprs);
if (!if_expr->false_.empty()) {
WriteOpcode(stream_, Opcode::Else);
WriteExprList(func, if_expr->false_);
}
WriteOpcode(stream_, Opcode::End);
break;
}
case ExprType::Load:
WriteLoadStoreExpr<LoadExpr>(func, expr, "load offset");
break;
case ExprType::LocalGet: {
Index index = GetLocalIndex(func, cast<LocalGetExpr>(expr)->var);
WriteOpcode(stream_, Opcode::LocalGet);
WriteU32Leb128(stream_, index, "local index");
break;
}
case ExprType::LocalSet: {
Index index = GetLocalIndex(func, cast<LocalSetExpr>(expr)->var);
WriteOpcode(stream_, Opcode::LocalSet);
WriteU32Leb128(stream_, index, "local index");
break;
}
case ExprType::LocalTee: {
Index index = GetLocalIndex(func, cast<LocalTeeExpr>(expr)->var);
WriteOpcode(stream_, Opcode::LocalTee);
WriteU32Leb128(stream_, index, "local index");
break;
}
case ExprType::Loop:
WriteOpcode(stream_, Opcode::Loop);
WriteBlockDecl(cast<LoopExpr>(expr)->block.decl);
WriteExprList(func, cast<LoopExpr>(expr)->block.exprs);
WriteOpcode(stream_, Opcode::End);
break;
case ExprType::MemoryCopy:
WriteOpcode(stream_, Opcode::MemoryCopy);
WriteU32Leb128(stream_, 0, "memory.copy reserved");
WriteU32Leb128(stream_, 0, "memory.copy reserved");
break;
case ExprType::DataDrop: {
Index index =
module_->GetDataSegmentIndex(cast<DataDropExpr>(expr)->var);
WriteOpcode(stream_, Opcode::DataDrop);
WriteU32Leb128(stream_, index, "data.drop segment");
has_data_segment_instruction_ = true;
break;
}
case ExprType::MemoryFill:
WriteOpcode(stream_, Opcode::MemoryFill);
WriteU32Leb128(stream_, 0, "memory.fill reserved");
break;
case ExprType::MemoryGrow:
WriteOpcode(stream_, Opcode::MemoryGrow);
WriteU32Leb128(stream_, 0, "memory.grow reserved");
break;
case ExprType::MemoryInit: {
Index index =
module_->GetDataSegmentIndex(cast<MemoryInitExpr>(expr)->var);
WriteOpcode(stream_, Opcode::MemoryInit);
WriteU32Leb128(stream_, index, "memory.init segment");
WriteU32Leb128(stream_, 0, "memory.init reserved");
has_data_segment_instruction_ = true;
break;
}
case ExprType::MemorySize:
WriteOpcode(stream_, Opcode::MemorySize);
WriteU32Leb128(stream_, 0, "memory.size reserved");
break;
case ExprType::TableCopy: {
auto* copy_expr = cast<TableCopyExpr>(expr);
Index dst = module_->GetTableIndex(copy_expr->dst_table);
Index src = module_->GetTableIndex(copy_expr->src_table);
WriteOpcode(stream_, Opcode::TableCopy);
WriteU32Leb128(stream_, dst, "table.copy dst_table");
WriteU32Leb128(stream_, src, "table.copy src_table");
break;
}
case ExprType::ElemDrop: {
Index index =
module_->GetElemSegmentIndex(cast<ElemDropExpr>(expr)->var);
WriteOpcode(stream_, Opcode::ElemDrop);
WriteU32Leb128(stream_, index, "elem.drop segment");
break;
}
case ExprType::TableInit: {
auto* init_expr = cast<TableInitExpr>(expr);
Index table_index = module_->GetTableIndex(init_expr->table_index);
Index segment_index =
module_->GetElemSegmentIndex(init_expr->segment_index);
WriteOpcode(stream_, Opcode::TableInit);
WriteU32Leb128(stream_, segment_index, "table.init segment");
WriteU32Leb128(stream_, table_index, "table.init table");
break;
}
case ExprType::TableGet: {
Index index =
module_->GetTableIndex(cast<TableGetExpr>(expr)->var);
WriteOpcode(stream_, Opcode::TableGet);
WriteU32Leb128(stream_, index, "table.get table index");
break;
}
case ExprType::TableSet: {
Index index =
module_->GetTableIndex(cast<TableSetExpr>(expr)->var);
WriteOpcode(stream_, Opcode::TableSet);
WriteU32Leb128(stream_, index, "table.set table index");
break;
}
case ExprType::TableGrow: {
Index index =
module_->GetTableIndex(cast<TableGrowExpr>(expr)->var);
WriteOpcode(stream_, Opcode::TableGrow);
WriteU32Leb128(stream_, index, "table.grow table index");
break;
}
case ExprType::TableSize: {
Index index =
module_->GetTableIndex(cast<TableSizeExpr>(expr)->var);
WriteOpcode(stream_, Opcode::TableSize);
WriteU32Leb128(stream_, index, "table.size table index");
break;
}
case ExprType::TableFill: {
Index index =
module_->GetTableIndex(cast<TableFillExpr>(expr)->var);
WriteOpcode(stream_, Opcode::TableFill);
WriteU32Leb128(stream_, index, "table.fill table index");
break;
}
case ExprType::RefFunc: {
WriteOpcode(stream_, Opcode::RefFunc);
Index index = module_->GetFuncIndex(cast<RefFuncExpr>(expr)->var);
WriteU32Leb128WithReloc(index, "function index", RelocType::FuncIndexLEB);
break;
}
case ExprType::RefNull: {
WriteOpcode(stream_, Opcode::RefNull);
break;
}
case ExprType::RefIsNull: {
WriteOpcode(stream_, Opcode::RefIsNull);
break;
}
case ExprType::Nop:
WriteOpcode(stream_, Opcode::Nop);
break;
case ExprType::Rethrow:
WriteOpcode(stream_, Opcode::Rethrow);
break;
case ExprType::Return:
WriteOpcode(stream_, Opcode::Return);
break;
case ExprType::Select: {
auto* select_expr = cast<SelectExpr>(expr);
if (select_expr->result_type.size() == 1 &&
select_expr->result_type[0] == Type::Any) {
WriteOpcode(stream_, Opcode::Select);
} else {
WriteOpcode(stream_, Opcode::SelectT);
WriteU32Leb128(stream_, select_expr->result_type.size(),
"num result types");
for (Type t : select_expr->result_type) {
WriteType(stream_, t, "result type");
}
}
break;
}
case ExprType::Store:
WriteLoadStoreExpr<StoreExpr>(func, expr, "store offset");
break;
case ExprType::Throw:
WriteOpcode(stream_, Opcode::Throw);
WriteU32Leb128(stream_, GetEventVarDepth(&cast<ThrowExpr>(expr)->var),
"throw event");
break;
case ExprType::Try: {
auto* try_expr = cast<TryExpr>(expr);
WriteOpcode(stream_, Opcode::Try);
WriteBlockDecl(try_expr->block.decl);
WriteExprList(func, try_expr->block.exprs);
WriteOpcode(stream_, Opcode::Catch);
WriteExprList(func, try_expr->catch_);
WriteOpcode(stream_, Opcode::End);
break;
}
case ExprType::Unary:
WriteOpcode(stream_, cast<UnaryExpr>(expr)->opcode);
break;
case ExprType::Ternary:
WriteOpcode(stream_, cast<TernaryExpr>(expr)->opcode);
break;
case ExprType::SimdLaneOp: {
const Opcode opcode = cast<SimdLaneOpExpr>(expr)->opcode;
WriteOpcode(stream_, opcode);
stream_->WriteU8(static_cast<uint8_t>(cast<SimdLaneOpExpr>(expr)->val),
"Simd Lane literal");
break;
}
case ExprType::SimdShuffleOp: {
const Opcode opcode = cast<SimdShuffleOpExpr>(expr)->opcode;
WriteOpcode(stream_, opcode);
stream_->WriteU128(cast<SimdShuffleOpExpr>(expr)->val,
"Simd Lane[16] literal");
break;
}
case ExprType::LoadSplat:
WriteLoadStoreExpr<LoadSplatExpr>(func, expr, "load offset");
break;
case ExprType::Unreachable:
WriteOpcode(stream_, Opcode::Unreachable);
break;
}
}
void BinaryWriter::WriteExprList(const Func* func, const ExprList& exprs) {
for (const Expr& expr : exprs) {
WriteExpr(func, &expr);
}
}
void BinaryWriter::WriteInitExpr(const ExprList& expr) {
WriteExprList(nullptr, expr);
WriteOpcode(stream_, Opcode::End);
}
void BinaryWriter::WriteFuncLocals(const Func* func,
const LocalTypes& local_types) {
if (local_types.size() == 0) {
WriteU32Leb128(stream_, 0, "local decl count");
return;
}
Index local_decl_count = local_types.decls().size();
WriteU32Leb128(stream_, local_decl_count, "local decl count");
for (auto decl : local_types.decls()) {
WriteU32Leb128(stream_, decl.second, "local type count");
WriteType(stream_, decl.first);
}
}
void BinaryWriter::WriteFunc(const Func* func) {
WriteFuncLocals(func, func->local_types);
WriteExprList(func, func->exprs);
WriteOpcode(stream_, Opcode::End);
}
void BinaryWriter::WriteTable(const Table* table) {
WriteType(stream_, table->elem_type);
WriteLimits(stream_, &table->elem_limits);
}
void BinaryWriter::WriteMemory(const Memory* memory) {
WriteLimits(stream_, &memory->page_limits);
}
void BinaryWriter::WriteGlobalHeader(const Global* global) {
WriteType(stream_, global->type);
stream_->WriteU8(global->mutable_, "global mutability");
}
void BinaryWriter::WriteEventType(const Event* event) {
WriteU32Leb128(stream_, 0, "event attribute");
WriteU32Leb128(stream_, module_->GetFuncTypeIndex(event->decl),
"event signature index");
}
void BinaryWriter::WriteRelocSection(const RelocSection* reloc_section) {
char section_name[128];
wabt_snprintf(section_name, sizeof(section_name), "%s.%s",
WABT_BINARY_SECTION_RELOC, reloc_section->name);
BeginCustomSection(section_name);
WriteU32Leb128(stream_, reloc_section->section_index, "reloc section index");
const std::vector<Reloc>& relocs = reloc_section->relocations;
WriteU32Leb128(stream_, relocs.size(), "num relocs");
for (const Reloc& reloc : relocs) {
WriteU32Leb128(stream_, reloc.type, "reloc type");
WriteU32Leb128(stream_, reloc.offset, "reloc offset");
WriteU32Leb128(stream_, reloc.index, "reloc index");
switch (reloc.type) {
case RelocType::MemoryAddressLEB:
case RelocType::MemoryAddressSLEB:
case RelocType::MemoryAddressRelSLEB:
case RelocType::MemoryAddressI32:
case RelocType::FunctionOffsetI32:
case RelocType::SectionOffsetI32:
WriteU32Leb128(stream_, reloc.addend, "reloc addend");
break;
default:
break;
}
}
EndSection();
}
void BinaryWriter::WriteLinkingSection() {
BeginCustomSection(WABT_BINARY_SECTION_LINKING);
WriteU32Leb128(stream_, 2, "metadata version");
if (symbols_.size()) {
stream_->WriteU8Enum(LinkingEntryType::SymbolTable, "symbol table");
BeginSubsection("symbol table");
WriteU32Leb128(stream_, symbols_.size(), "num symbols");
for (const Symbol& sym : symbols_) {
bool is_defined = true;
if (sym.type == SymbolType::Function) {
if (sym.element_index < module_->num_func_imports) {
is_defined = false;
}
}
if (sym.type == SymbolType::Global) {
if (sym.element_index < module_->num_global_imports) {
is_defined = false;
}
}
stream_->WriteU8Enum(sym.type, "symbol type");
WriteU32Leb128(stream_, is_defined ? 0 : WABT_SYMBOL_FLAG_UNDEFINED,
"symbol flags");
WriteU32Leb128(stream_, sym.element_index, "element index");
if (is_defined) {
if (sym.type == SymbolType::Function) {
WriteStr(stream_, module_->funcs[sym.element_index]->name,
"function name", PrintChars::Yes);
} else if (sym.type == SymbolType::Global) {
WriteStr(stream_, module_->globals[sym.element_index]->name,
"global name", PrintChars::Yes);
}
}
}
EndSubsection();
}
EndSection();
}
Result BinaryWriter::WriteModule() {
stream_->WriteU32(WABT_BINARY_MAGIC, "WASM_BINARY_MAGIC");
stream_->WriteU32(WABT_BINARY_VERSION, "WASM_BINARY_VERSION");
if (module_->types.size()) {
BeginKnownSection(BinarySection::Type);
WriteU32Leb128(stream_, module_->types.size(), "num types");
for (size_t i = 0; i < module_->types.size(); ++i) {
const TypeEntry* type = module_->types[i];
switch (type->kind()) {
case TypeEntryKind::Func: {
const FuncType* func_type = cast<FuncType>(type);
const FuncSignature* sig = &func_type->sig;
WriteHeader("type", i); // TODO: switch to "func type"?
WriteType(stream_, Type::Func);
Index num_params = sig->param_types.size();
Index num_results = sig->result_types.size();
WriteU32Leb128(stream_, num_params, "num params");
for (size_t j = 0; j < num_params; ++j) {
WriteType(stream_, sig->param_types[j]);
}
WriteU32Leb128(stream_, num_results, "num results");
for (size_t j = 0; j < num_results; ++j) {
WriteType(stream_, sig->result_types[j]);
}
break;
}
case TypeEntryKind::Struct: {
const StructType* struct_type = cast<StructType>(type);
WriteHeader("struct type", i);
WriteType(stream_, Type::Struct);
Index num_fields = struct_type->fields.size();
WriteU32Leb128(stream_, num_fields, "num fields");
for (size_t j = 0; j < num_fields; ++j) {
const Field& field = struct_type->fields[j];
WriteType(stream_, field.type);
stream_->WriteU8(field.mutable_, "field mutability");
}
break;
}
case TypeEntryKind::Array: {
const ArrayType* array_type = cast<ArrayType>(type);
WriteHeader("array type", i);
WriteType(stream_, Type::Array);
WriteType(stream_, array_type->field.type);
stream_->WriteU8(array_type->field.mutable_, "field mutability");
break;
}
}
}
EndSection();
}
if (module_->imports.size()) {
BeginKnownSection(BinarySection::Import);
WriteU32Leb128(stream_, module_->imports.size(), "num imports");
for (size_t i = 0; i < module_->imports.size(); ++i) {
const Import* import = module_->imports[i];
WriteHeader("import header", i);
WriteStr(stream_, import->module_name, "import module name",
PrintChars::Yes);
WriteStr(stream_, import->field_name, "import field name",
PrintChars::Yes);
stream_->WriteU8Enum(import->kind(), "import kind");
switch (import->kind()) {
case ExternalKind::Func:
WriteU32Leb128(
stream_,
module_->GetFuncTypeIndex(cast<FuncImport>(import)->func.decl),
"import signature index");
break;
case ExternalKind::Table:
WriteTable(&cast<TableImport>(import)->table);
break;
case ExternalKind::Memory:
WriteMemory(&cast<MemoryImport>(import)->memory);
break;
case ExternalKind::Global:
WriteGlobalHeader(&cast<GlobalImport>(import)->global);
break;
case ExternalKind::Event:
WriteEventType(&cast<EventImport>(import)->event);
break;
}
}
EndSection();
}
assert(module_->funcs.size() >= module_->num_func_imports);
Index num_funcs = module_->funcs.size() - module_->num_func_imports;
if (num_funcs) {
BeginKnownSection(BinarySection::Function);
WriteU32Leb128(stream_, num_funcs, "num functions");
for (size_t i = 0; i < num_funcs; ++i) {
const Func* func = module_->funcs[i + module_->num_func_imports];
char desc[100];
wabt_snprintf(desc, sizeof(desc), "function %" PRIzd " signature index",
i);
WriteU32Leb128(stream_, module_->GetFuncTypeIndex(func->decl), desc);
}
EndSection();
}
assert(module_->tables.size() >= module_->num_table_imports);
Index num_tables = module_->tables.size() - module_->num_table_imports;
if (num_tables) {
BeginKnownSection(BinarySection::Table);
WriteU32Leb128(stream_, num_tables, "num tables");
for (size_t i = 0; i < num_tables; ++i) {
const Table* table = module_->tables[i + module_->num_table_imports];
WriteHeader("table", i);
WriteTable(table);
}
EndSection();
}
assert(module_->memories.size() >= module_->num_memory_imports);
Index num_memories = module_->memories.size() - module_->num_memory_imports;
if (num_memories) {
BeginKnownSection(BinarySection::Memory);
WriteU32Leb128(stream_, num_memories, "num memories");
for (size_t i = 0; i < num_memories; ++i) {
const Memory* memory = module_->memories[i + module_->num_memory_imports];
WriteHeader("memory", i);
WriteMemory(memory);
}
EndSection();
}
assert(module_->events.size() >= module_->num_event_imports);
Index num_events = module_->events.size() - module_->num_event_imports;
if (num_events) {
BeginKnownSection(BinarySection::Event);
WriteU32Leb128(stream_, num_events, "event count");
for (size_t i = 0; i < num_events; ++i) {
WriteHeader("event", i);
const Event* event = module_->events[i + module_->num_event_imports];
WriteEventType(event);
}
EndSection();
}
assert(module_->globals.size() >= module_->num_global_imports);
Index num_globals = module_->globals.size() - module_->num_global_imports;
if (num_globals) {
BeginKnownSection(BinarySection::Global);
WriteU32Leb128(stream_, num_globals, "num globals");
for (size_t i = 0; i < num_globals; ++i) {
const Global* global = module_->globals[i + module_->num_global_imports];
WriteGlobalHeader(global);
WriteInitExpr(global->init_expr);
}
EndSection();
}
if (module_->exports.size()) {
BeginKnownSection(BinarySection::Export);
WriteU32Leb128(stream_, module_->exports.size(), "num exports");
for (const Export* export_ : module_->exports) {
WriteStr(stream_, export_->name, "export name", PrintChars::Yes);
stream_->WriteU8Enum(export_->kind, "export kind");
switch (export_->kind) {
case ExternalKind::Func: {
Index index = module_->GetFuncIndex(export_->var);
WriteU32Leb128(stream_, index, "export func index");
break;
}
case ExternalKind::Table: {
Index index = module_->GetTableIndex(export_->var);
WriteU32Leb128(stream_, index, "export table index");
break;
}
case ExternalKind::Memory: {
Index index = module_->GetMemoryIndex(export_->var);
WriteU32Leb128(stream_, index, "export memory index");
break;
}
case ExternalKind::Global: {
Index index = module_->GetGlobalIndex(export_->var);
WriteU32Leb128(stream_, index, "export global index");
break;
}
case ExternalKind::Event: {
Index index = module_->GetEventIndex(export_->var);
WriteU32Leb128(stream_, index, "export event index");
break;
}
}
}
EndSection();
}
if (module_->starts.size()) {
Index start_func_index = module_->GetFuncIndex(*module_->starts[0]);
if (start_func_index != kInvalidIndex) {
BeginKnownSection(BinarySection::Start);
WriteU32Leb128(stream_, start_func_index, "start func index");
EndSection();
}
}
if (module_->elem_segments.size()) {
BeginKnownSection(BinarySection::Elem);
WriteU32Leb128(stream_, module_->elem_segments.size(), "num elem segments");
for (size_t i = 0; i < module_->elem_segments.size(); ++i) {
ElemSegment* segment = module_->elem_segments[i];
WriteHeader("elem segment header", i);
// 1. flags
uint8_t flags = segment->GetFlags(module_);
stream_->WriteU8(flags, "segment flags");
// 2. optional target table
if (flags & SegExplicitIndex && segment->kind != SegmentKind::Declared) {
WriteU32Leb128(stream_, module_->GetTableIndex(segment->table_var),
"table index");
}
// 3. optional target location within the table (active segments only)
if (!(flags & SegPassive)) {
WriteInitExpr(segment->offset);
}
// 4. type of item in the following list (omitted for "legacy" segments)
if (flags & (SegPassive | SegExplicitIndex)) {
if (flags & SegUseElemExprs) {
WriteType(stream_, segment->elem_type, "elem expr list type");
} else {
stream_->WriteU8Enum(ExternalKind::Func, "elem list type");
}
}
// 5. actual list of elements (with extern indexes or elem expr's)
// preceeded by length
WriteU32Leb128(stream_, segment->elem_exprs.size(), "num elems");
if (flags & SegUseElemExprs) {
for (const ElemExpr& elem_expr : segment->elem_exprs) {
switch (elem_expr.kind) {
case ElemExprKind::RefNull:
WriteOpcode(stream_, Opcode::RefNull);
break;
case ElemExprKind::RefFunc:
WriteOpcode(stream_, Opcode::RefFunc);
WriteU32Leb128WithReloc(module_->GetFuncIndex(elem_expr.var),
"elem expr function index",
RelocType::FuncIndexLEB);
break;
}
WriteOpcode(stream_, Opcode::End);
}
} else {
for (const ElemExpr& elem_expr : segment->elem_exprs) {
assert(elem_expr.kind == ElemExprKind::RefFunc);
WriteU32Leb128WithReloc(module_->GetFuncIndex(elem_expr.var),
"elem function index",
RelocType::FuncIndexLEB);
}
}
}
EndSection();
}
if (options_.features.bulk_memory_enabled()) {
// Keep track of the data count section offset so it can be removed if
// it isn't needed.
data_count_start_ = stream_->offset();
BeginKnownSection(BinarySection::DataCount);
WriteU32Leb128(stream_, module_->data_segments.size(), "data count");
EndSection();
data_count_end_ = stream_->offset();
}
if (num_funcs) {
BeginKnownSection(BinarySection::Code);
WriteU32Leb128(stream_, num_funcs, "num functions");
for (size_t i = 0; i < num_funcs; ++i) {
WriteHeader("function body", i);
const Func* func = module_->funcs[i + module_->num_func_imports];
/* TODO(binji): better guess of the size of the function body section */
const Offset leb_size_guess = 1;
Offset body_size_offset =
WriteU32Leb128Space(leb_size_guess, "func body size (guess)");
WriteFunc(func);
WriteFixupU32Leb128Size(body_size_offset, leb_size_guess,
"FIXUP func body size");
}
EndSection();
}
// Remove the DataCount section if there are no instructions that require it.
if (options_.features.bulk_memory_enabled() &&
!has_data_segment_instruction_) {
Offset size = stream_->offset() - data_count_end_;
if (data_count_start_ != data_count_end_) {
stream_->MoveData(data_count_start_, data_count_end_, size);
}
stream_->Truncate(data_count_start_ + size);
}
if (module_->data_segments.size()) {
BeginKnownSection(BinarySection::Data);
WriteU32Leb128(stream_, module_->data_segments.size(), "num data segments");
for (size_t i = 0; i < module_->data_segments.size(); ++i) {
const DataSegment* segment = module_->data_segments[i];
WriteHeader("data segment header", i);
uint8_t flags = segment->GetFlags(module_);
stream_->WriteU8(flags, "segment flags");
if (!(flags & SegPassive)) {
assert(module_->GetMemoryIndex(segment->memory_var) == 0);
WriteInitExpr(segment->offset);
}
WriteU32Leb128(stream_, segment->data.size(), "data segment size");
WriteHeader("data segment data", i);
stream_->WriteData(segment->data, "data segment data");
}
EndSection();
}
if (options_.write_debug_names) {
std::vector<std::string> index_to_name;
char desc[100];
BeginCustomSection(WABT_BINARY_SECTION_NAME);
size_t named_functions = 0;
for (const Func* func : module_->funcs) {
if (!func->name.empty()) {
named_functions++;
}
}
if (!module_->name.empty()) {
WriteU32Leb128(stream_, 0, "module name type");
BeginSubsection("module name subsection");
WriteDebugName(stream_, module_->name, "module name");
EndSubsection();
}
if (named_functions > 0) {
WriteU32Leb128(stream_, 1, "function name type");
BeginSubsection("function name subsection");
WriteU32Leb128(stream_, named_functions, "num functions");
for (size_t i = 0; i < module_->funcs.size(); ++i) {
const Func* func = module_->funcs[i];
if (func->name.empty()) {
continue;
}
WriteU32Leb128(stream_, i, "function index");
wabt_snprintf(desc, sizeof(desc), "func name %" PRIzd, i);
WriteDebugName(stream_, func->name, desc);
}
EndSubsection();
}
WriteU32Leb128(stream_, 2, "local name type");
BeginSubsection("local name subsection");
WriteU32Leb128(stream_, module_->funcs.size(), "num functions");
for (size_t i = 0; i < module_->funcs.size(); ++i) {
const Func* func = module_->funcs[i];
Index num_params_and_locals = func->GetNumParamsAndLocals();
WriteU32Leb128(stream_, i, "function index");
WriteU32Leb128(stream_, num_params_and_locals, "num locals");
MakeTypeBindingReverseMapping(num_params_and_locals, func->bindings,
&index_to_name);
for (size_t j = 0; j < num_params_and_locals; ++j) {
const std::string& name = index_to_name[j];
wabt_snprintf(desc, sizeof(desc), "local name %" PRIzd, j);
WriteU32Leb128(stream_, j, "local index");
WriteDebugName(stream_, name, desc);
}
}
EndSubsection();
EndSection();
}
if (options_.relocatable) {
WriteLinkingSection();
for (RelocSection& section : reloc_sections_) {
WriteRelocSection(&section);
}
}
return stream_->result();
}
} // end anonymous namespace
Result WriteBinaryModule(Stream* stream,
const Module* module,
const WriteBinaryOptions& options) {
BinaryWriter binary_writer(stream, options, module);
return binary_writer.WriteModule();
}
} // namespace wabt