blob: 3b6bbbb9def9ed5f11df45a856d140d5e28b2385 [file] [log] [blame]
/*
* 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 "wasm-binary.h"
#include <fstream>
#include "support/bits.h"
namespace wasm {
void WasmBinaryWriter::prepare() {
// we need function types for all our functions
for (auto& func : wasm->functions) {
if (func->type.isNull()) {
func->type = ensureFunctionType(getSig(func.get()), wasm)->name;
}
// TODO: depending on upstream flux https://github.com/WebAssembly/spec/pull/301 might want this: assert(!func->type.isNull());
}
}
void WasmBinaryWriter::write() {
writeHeader();
if (sourceMap) {
writeSourceMapProlog();
}
writeTypes();
writeImports();
writeFunctionSignatures();
writeFunctionTableDeclaration();
writeMemory();
writeGlobals();
writeExports();
writeStart();
writeTableElements();
writeFunctions();
writeDataSegments();
if (debugInfo) writeNames();
if (sourceMap) writeSourceMapUrl();
if (symbolMap.size() > 0) writeSymbolMap();
if (sourceMap) {
writeSourceMapEpilog();
}
finishUp();
}
void WasmBinaryWriter::writeHeader() {
if (debug) std::cerr << "== writeHeader" << std::endl;
o << int32_t(BinaryConsts::Magic); // magic number \0asm
o << int32_t(BinaryConsts::Version);
}
int32_t WasmBinaryWriter::writeU32LEBPlaceholder() {
int32_t ret = o.size();
o << int32_t(0);
o << int8_t(0);
return ret;
}
void WasmBinaryWriter::writeResizableLimits(Address initial, Address maximum, bool hasMaximum) {
uint32_t flags = hasMaximum ? 1 : 0;
o << U32LEB(flags);
o << U32LEB(initial);
if (hasMaximum) {
o << U32LEB(maximum);
}
}
int32_t WasmBinaryWriter::startSection(BinaryConsts::Section code) {
o << U32LEB(code);
return writeU32LEBPlaceholder(); // section size to be filled in later
}
void WasmBinaryWriter::finishSection(int32_t start) {
int32_t size = o.size() - start - 5; // section size does not include the 5 bytes of the size field itself
o.writeAt(start, U32LEB(size));
}
int32_t WasmBinaryWriter::startSubsection(BinaryConsts::UserSections::Subsection code) {
o << U32LEB(code);
return writeU32LEBPlaceholder(); // section size to be filled in later
}
void WasmBinaryWriter::finishSubsection(int32_t start) {
int32_t size = o.size() - start - 5; // section size does not include the 5 bytes of the size field itself
o.writeAt(start, U32LEB(size));
}
void WasmBinaryWriter::writeStart() {
if (!wasm->start.is()) return;
if (debug) std::cerr << "== writeStart" << std::endl;
auto start = startSection(BinaryConsts::Section::Start);
o << U32LEB(getFunctionIndex(wasm->start.str));
finishSection(start);
}
void WasmBinaryWriter::writeMemory() {
if (!wasm->memory.exists || wasm->memory.imported) return;
if (debug) std::cerr << "== writeMemory" << std::endl;
auto start = startSection(BinaryConsts::Section::Memory);
o << U32LEB(1); // Define 1 memory
writeResizableLimits(wasm->memory.initial, wasm->memory.max, wasm->memory.max != Memory::kMaxSize);
finishSection(start);
}
void WasmBinaryWriter::writeTypes() {
if (wasm->functionTypes.size() == 0) return;
if (debug) std::cerr << "== writeTypes" << std::endl;
auto start = startSection(BinaryConsts::Section::Type);
o << U32LEB(wasm->functionTypes.size());
for (auto& type : wasm->functionTypes) {
if (debug) std::cerr << "write one" << std::endl;
o << S32LEB(BinaryConsts::EncodedType::Func);
o << U32LEB(type->params.size());
for (auto param : type->params) {
o << binaryWasmType(param);
}
if (type->result == none) {
o << U32LEB(0);
} else {
o << U32LEB(1);
o << binaryWasmType(type->result);
}
}
finishSection(start);
}
int32_t WasmBinaryWriter::getFunctionTypeIndex(Name type) {
// TODO: optimize
for (size_t i = 0; i < wasm->functionTypes.size(); i++) {
if (wasm->functionTypes[i]->name == type) return i;
}
abort();
}
void WasmBinaryWriter::writeImports() {
if (wasm->imports.size() == 0) return;
if (debug) std::cerr << "== writeImports" << std::endl;
auto start = startSection(BinaryConsts::Section::Import);
o << U32LEB(wasm->imports.size());
for (auto& import : wasm->imports) {
if (debug) std::cerr << "write one" << std::endl;
writeInlineString(import->module.str);
writeInlineString(import->base.str);
o << U32LEB(int32_t(import->kind));
switch (import->kind) {
case ExternalKind::Function: o << U32LEB(getFunctionTypeIndex(import->functionType)); break;
case ExternalKind::Table: {
o << S32LEB(BinaryConsts::EncodedType::AnyFunc);
writeResizableLimits(wasm->table.initial, wasm->table.max, wasm->table.max != Table::kMaxSize);
break;
}
case ExternalKind::Memory: {
writeResizableLimits(wasm->memory.initial, wasm->memory.max, wasm->memory.max != Memory::kMaxSize);
break;
}
case ExternalKind::Global:
o << binaryWasmType(import->globalType);
o << U32LEB(0); // Mutable global's can't be imported for now.
break;
default: WASM_UNREACHABLE();
}
}
finishSection(start);
}
void WasmBinaryWriter::mapLocals(Function* function) {
for (Index i = 0; i < function->getNumParams(); i++) {
size_t curr = mappedLocals.size();
mappedLocals[i] = curr;
}
for (auto type : function->vars) {
numLocalsByType[type]++;
}
std::map<WasmType, size_t> currLocalsByType;
for (Index i = function->getVarIndexBase(); i < function->getNumLocals(); i++) {
size_t index = function->getVarIndexBase();
WasmType type = function->getLocalType(i);
currLocalsByType[type]++; // increment now for simplicity, must decrement it in returns
if (type == i32) {
mappedLocals[i] = index + currLocalsByType[i32] - 1;
continue;
}
index += numLocalsByType[i32];
if (type == i64) {
mappedLocals[i] = index + currLocalsByType[i64] - 1;
continue;
}
index += numLocalsByType[i64];
if (type == f32) {
mappedLocals[i] = index + currLocalsByType[f32] - 1;
continue;
}
index += numLocalsByType[f32];
if (type == f64) {
mappedLocals[i] = index + currLocalsByType[f64] - 1;
continue;
}
abort();
}
}
void WasmBinaryWriter::writeFunctionSignatures() {
if (wasm->functions.size() == 0) return;
if (debug) std::cerr << "== writeFunctionSignatures" << std::endl;
auto start = startSection(BinaryConsts::Section::Function);
o << U32LEB(wasm->functions.size());
for (auto& curr : wasm->functions) {
if (debug) std::cerr << "write one" << std::endl;
o << U32LEB(getFunctionTypeIndex(curr->type));
}
finishSection(start);
}
void WasmBinaryWriter::writeExpression(Expression* curr) {
assert(depth == 0);
recurse(curr);
assert(depth == 0);
}
void WasmBinaryWriter::writeFunctions() {
if (wasm->functions.size() == 0) return;
if (debug) std::cerr << "== writeFunctions" << std::endl;
auto start = startSection(BinaryConsts::Section::Code);
size_t total = wasm->functions.size();
o << U32LEB(total);
for (size_t i = 0; i < total; i++) {
if (debug) std::cerr << "write one at" << o.size() << std::endl;
size_t sizePos = writeU32LEBPlaceholder();
size_t start = o.size();
Function* function = wasm->functions[i].get();
currFunction = function;
mappedLocals.clear();
numLocalsByType.clear();
if (debug) std::cerr << "writing" << function->name << std::endl;
mapLocals(function);
o << U32LEB(
(numLocalsByType[i32] ? 1 : 0) +
(numLocalsByType[i64] ? 1 : 0) +
(numLocalsByType[f32] ? 1 : 0) +
(numLocalsByType[f64] ? 1 : 0)
);
if (numLocalsByType[i32]) o << U32LEB(numLocalsByType[i32]) << binaryWasmType(i32);
if (numLocalsByType[i64]) o << U32LEB(numLocalsByType[i64]) << binaryWasmType(i64);
if (numLocalsByType[f32]) o << U32LEB(numLocalsByType[f32]) << binaryWasmType(f32);
if (numLocalsByType[f64]) o << U32LEB(numLocalsByType[f64]) << binaryWasmType(f64);
writeExpression(function->body);
o << int8_t(BinaryConsts::End);
size_t size = o.size() - start;
assert(size <= std::numeric_limits<uint32_t>::max());
if (debug) std::cerr << "body size: " << size << ", writing at " << sizePos << ", next starts at " << o.size() << std::endl;
o.writeAt(sizePos, U32LEB(size));
}
currFunction = nullptr;
finishSection(start);
}
void WasmBinaryWriter::writeGlobals() {
if (wasm->globals.size() == 0) return;
if (debug) std::cerr << "== writeglobals" << std::endl;
auto start = startSection(BinaryConsts::Section::Global);
o << U32LEB(wasm->globals.size());
for (auto& curr : wasm->globals) {
if (debug) std::cerr << "write one" << std::endl;
o << binaryWasmType(curr->type);
o << U32LEB(curr->mutable_);
writeExpression(curr->init);
o << int8_t(BinaryConsts::End);
}
finishSection(start);
}
void WasmBinaryWriter::writeExports() {
if (wasm->exports.size() == 0) return;
if (debug) std::cerr << "== writeexports" << std::endl;
auto start = startSection(BinaryConsts::Section::Export);
o << U32LEB(wasm->exports.size());
for (auto& curr : wasm->exports) {
if (debug) std::cerr << "write one" << std::endl;
writeInlineString(curr->name.str);
o << U32LEB(int32_t(curr->kind));
switch (curr->kind) {
case ExternalKind::Function: o << U32LEB(getFunctionIndex(curr->value)); break;
case ExternalKind::Table: o << U32LEB(0); break;
case ExternalKind::Memory: o << U32LEB(0); break;
case ExternalKind::Global: o << U32LEB(getGlobalIndex(curr->value)); break;
default: WASM_UNREACHABLE();
}
}
finishSection(start);
}
void WasmBinaryWriter::writeDataSegments() {
if (wasm->memory.segments.size() == 0) return;
uint32_t num = 0;
for (auto& segment : wasm->memory.segments) {
if (segment.data.size() > 0) num++;
}
auto start = startSection(BinaryConsts::Section::Data);
o << U32LEB(num);
for (auto& segment : wasm->memory.segments) {
if (segment.data.size() == 0) continue;
o << U32LEB(0); // Linear memory 0 in the MVP
writeExpression(segment.offset);
o << int8_t(BinaryConsts::End);
writeInlineBuffer(&segment.data[0], segment.data.size());
}
finishSection(start);
}
uint32_t WasmBinaryWriter::getFunctionIndex(Name name) {
if (!mappedFunctions.size()) {
// Create name => index mapping.
for (auto& import : wasm->imports) {
if (import->kind != ExternalKind::Function) continue;
assert(mappedFunctions.count(import->name) == 0);
auto index = mappedFunctions.size();
mappedFunctions[import->name] = index;
}
for (size_t i = 0; i < wasm->functions.size(); i++) {
assert(mappedFunctions.count(wasm->functions[i]->name) == 0);
auto index = mappedFunctions.size();
mappedFunctions[wasm->functions[i]->name] = index;
}
}
assert(mappedFunctions.count(name));
return mappedFunctions[name];
}
uint32_t WasmBinaryWriter::getGlobalIndex(Name name) {
if (!mappedGlobals.size()) {
// Create name => index mapping.
for (auto& import : wasm->imports) {
if (import->kind != ExternalKind::Global) continue;
assert(mappedGlobals.count(import->name) == 0);
auto index = mappedGlobals.size();
mappedGlobals[import->name] = index;
}
for (size_t i = 0; i < wasm->globals.size(); i++) {
assert(mappedGlobals.count(wasm->globals[i]->name) == 0);
auto index = mappedGlobals.size();
mappedGlobals[wasm->globals[i]->name] = index;
}
}
assert(mappedGlobals.count(name));
return mappedGlobals[name];
}
void WasmBinaryWriter::writeFunctionTableDeclaration() {
if (!wasm->table.exists || wasm->table.imported) return;
if (debug) std::cerr << "== writeFunctionTableDeclaration" << std::endl;
auto start = startSection(BinaryConsts::Section::Table);
o << U32LEB(1); // Declare 1 table.
o << S32LEB(BinaryConsts::EncodedType::AnyFunc);
writeResizableLimits(wasm->table.initial, wasm->table.max, wasm->table.max != Table::kMaxSize);
finishSection(start);
}
void WasmBinaryWriter::writeTableElements() {
if (!wasm->table.exists) return;
if (debug) std::cerr << "== writeTableElements" << std::endl;
auto start = startSection(BinaryConsts::Section::Element);
o << U32LEB(wasm->table.segments.size());
for (auto& segment : wasm->table.segments) {
o << U32LEB(0); // Table index; 0 in the MVP (and binaryen IR only has 1 table)
writeExpression(segment.offset);
o << int8_t(BinaryConsts::End);
o << U32LEB(segment.data.size());
for (auto name : segment.data) {
o << U32LEB(getFunctionIndex(name));
}
}
finishSection(start);
}
void WasmBinaryWriter::writeNames() {
bool hasContents = false;
if (wasm->functions.size() > 0) {
hasContents = true;
getFunctionIndex(wasm->functions[0]->name); // generate mappedFunctions
} else {
for (auto& import : wasm->imports) {
if (import->kind == ExternalKind::Function) {
hasContents = true;
getFunctionIndex(import->name); // generate mappedFunctions
break;
}
}
}
if (!hasContents) return;
if (debug) std::cerr << "== writeNames" << std::endl;
auto start = startSection(BinaryConsts::Section::User);
writeInlineString(BinaryConsts::UserSections::Name);
auto substart = startSubsection(BinaryConsts::UserSections::Subsection::NameFunction);
o << U32LEB(mappedFunctions.size());
Index emitted = 0;
for (auto& import : wasm->imports) {
if (import->kind == ExternalKind::Function) {
o << U32LEB(emitted);
writeInlineString(import->name.str);
emitted++;
}
}
for (auto& curr : wasm->functions) {
o << U32LEB(emitted);
writeInlineString(curr->name.str);
emitted++;
}
assert(emitted == mappedFunctions.size());
finishSubsection(substart);
/* TODO: locals */
finishSection(start);
}
void WasmBinaryWriter::writeSourceMapUrl() {
if (debug) std::cerr << "== writeSourceMapUrl" << std::endl;
auto start = startSection(BinaryConsts::Section::User);
writeInlineString(BinaryConsts::UserSections::SourceMapUrl);
writeInlineString(sourceMapUrl.c_str());
finishSection(start);
}
void WasmBinaryWriter::writeSymbolMap() {
std::ofstream file(symbolMap);
for (auto& import : wasm->imports) {
if (import->kind == ExternalKind::Function) {
file << getFunctionIndex(import->name) << ":" << import->name.str << std::endl;
}
}
for (auto& func : wasm->functions) {
file << getFunctionIndex(func->name) << ":" << func->name.str << std::endl;
}
file.close();
}
void WasmBinaryWriter::writeSourceMapProlog() {
lastDebugLocation = { 0, /* lineNumber = */ 1, 0 };
lastBytecodeOffset = 0;
*sourceMap << "{\"version\":3,\"sources\":[";
for (size_t i = 0; i < wasm->debugInfoFileNames.size(); i++) {
if (i > 0) *sourceMap << ",";
// TODO respect JSON string encoding, e.g. quotes and control chars.
*sourceMap << "\"" << wasm->debugInfoFileNames[i] << "\"";
}
*sourceMap << "],\"names\":[],\"mappings\":\"";
}
void WasmBinaryWriter::writeSourceMapEpilog() {
*sourceMap << "\"}";
}
static void writeBase64VLQ(std::ostream& out, int32_t n) {
uint32_t value = n >= 0 ? n << 1 : ((-n) << 1) | 1;
while (1) {
uint32_t digit = value & 0x1F;
value >>= 5;
if (!value) {
// last VLQ digit -- base64 codes 'A'..'Z', 'a'..'f'
out << char(digit < 26 ? 'A' + digit : 'a' + digit - 26);
break;
}
// more VLG digit will follow -- add continuation bit (0x20),
// base64 codes 'g'..'z', '0'..'9', '+', '/'
out << char(digit < 20 ? 'g' + digit : digit < 30 ? '0' + digit - 20 : digit == 30 ? '+' : '/');
}
}
void WasmBinaryWriter::writeDebugLocation(size_t offset, const Function::DebugLocation& loc) {
if (lastBytecodeOffset > 0) {
*sourceMap << ",";
}
writeBase64VLQ(*sourceMap, int32_t(offset - lastBytecodeOffset));
writeBase64VLQ(*sourceMap, int32_t(loc.fileIndex - lastDebugLocation.fileIndex));
writeBase64VLQ(*sourceMap, int32_t(loc.lineNumber - lastDebugLocation.lineNumber));
writeBase64VLQ(*sourceMap, int32_t(loc.columnNumber - lastDebugLocation.columnNumber));
lastDebugLocation = loc;
lastBytecodeOffset = offset;
}
void WasmBinaryWriter::writeInlineString(const char* name) {
int32_t size = strlen(name);
o << U32LEB(size);
for (int32_t i = 0; i < size; i++) {
o << int8_t(name[i]);
}
}
void WasmBinaryWriter::writeInlineBuffer(const char* data, size_t size) {
o << U32LEB(size);
for (size_t i = 0; i < size; i++) {
o << int8_t(data[i]);
}
}
void WasmBinaryWriter::emitBuffer(const char* data, size_t size) {
assert(size > 0);
buffersToWrite.emplace_back(data, size, o.size());
o << uint32_t(0); // placeholder, we'll fill in the pointer to the buffer later when we have it
}
void WasmBinaryWriter::emitString(const char *str) {
if (debug) std::cerr << "emitString " << str << std::endl;
emitBuffer(str, strlen(str) + 1);
}
void WasmBinaryWriter::finishUp() {
if (debug) std::cerr << "finishUp" << std::endl;
// finish buffers
for (const auto& buffer : buffersToWrite) {
if (debug) std::cerr << "writing buffer" << (int)buffer.data[0] << "," << (int)buffer.data[1] << " at " << o.size() << " and pointer is at " << buffer.pointerLocation << std::endl;
o.writeAt(buffer.pointerLocation, (uint32_t)o.size());
for (size_t i = 0; i < buffer.size; i++) {
o << (uint8_t)buffer.data[i];
}
}
}
void WasmBinaryWriter::recurse(Expression*& curr) {
if (debug) std::cerr << "zz recurse into " << ++depth << " at " << o.size() << std::endl;
visit(curr);
if (debug) std::cerr << "zz recurse from " << depth-- << " at " << o.size() << std::endl;
}
static bool brokenTo(Block* block) {
return block->name.is() && BreakSeeker::has(block, block->name);
}
void WasmBinaryWriter::visitBlock(Block *curr) {
if (debug) std::cerr << "zz node: Block" << std::endl;
o << int8_t(BinaryConsts::Block);
o << binaryWasmType(curr->type != unreachable ? curr->type : none);
breakStack.push_back(curr->name);
Index i = 0;
for (auto* child : curr->list) {
if (debug) std::cerr << " " << size_t(curr) << "\n zz Block element " << i++ << std::endl;
recurse(child);
}
breakStack.pop_back();
if (curr->type == unreachable) {
// an unreachable block is one that cannot be exited. We cannot encode this directly
// in wasm, where blocks must be none,i32,i64,f32,f64. Since the block cannot be
// exited, we can emit an unreachable at the end, and that will always be valid,
// and then the block is ok as a none
o << int8_t(BinaryConsts::Unreachable);
}
o << int8_t(BinaryConsts::End);
if (curr->type == unreachable) {
// and emit an unreachable *outside* the block too, so later things can pop anything
o << int8_t(BinaryConsts::Unreachable);
}
}
// emits a node, but if it is a block with no name, emit a list of its contents
void WasmBinaryWriter::recursePossibleBlockContents(Expression* curr) {
auto* block = curr->dynCast<Block>();
if (!block || brokenTo(block)) {
recurse(curr);
return;
}
for (auto* child : block->list) {
recurse(child);
}
}
void WasmBinaryWriter::visitIf(If *curr) {
if (debug) std::cerr << "zz node: If" << std::endl;
if (curr->condition->type == unreachable) {
// this if-else is unreachable because of the condition, i.e., the condition
// does not exit. So don't emit the if, but do consume the condition
recurse(curr->condition);
o << int8_t(BinaryConsts::Unreachable);
return;
}
recurse(curr->condition);
o << int8_t(BinaryConsts::If);
o << binaryWasmType(curr->type != unreachable ? curr->type : none);
breakStack.push_back(IMPOSSIBLE_CONTINUE); // the binary format requires this; we have a block if we need one; TODO: optimize
recursePossibleBlockContents(curr->ifTrue); // TODO: emit block contents directly, if possible
breakStack.pop_back();
if (curr->ifFalse) {
o << int8_t(BinaryConsts::Else);
breakStack.push_back(IMPOSSIBLE_CONTINUE); // TODO ditto
recursePossibleBlockContents(curr->ifFalse);
breakStack.pop_back();
}
o << int8_t(BinaryConsts::End);
if (curr->type == unreachable) {
// we already handled the case of the condition being unreachable. otherwise,
// we may still be unreachable, if we are an if-else with both sides unreachable.
// wasm does not allow this to be emitted directly, so we must do something more. we could do
// better, but for now we emit an extra unreachable instruction after the if, so it is not consumed itself,
assert(curr->ifFalse);
o << int8_t(BinaryConsts::Unreachable);
}
}
void WasmBinaryWriter::visitLoop(Loop *curr) {
if (debug) std::cerr << "zz node: Loop" << std::endl;
o << int8_t(BinaryConsts::Loop);
o << binaryWasmType(curr->type != unreachable ? curr->type : none);
breakStack.push_back(curr->name);
recursePossibleBlockContents(curr->body);
breakStack.pop_back();
o << int8_t(BinaryConsts::End);
if (curr->type == unreachable) {
// we emitted a loop without a return type, so it must not be consumed
o << int8_t(BinaryConsts::Unreachable);
}
}
int32_t WasmBinaryWriter::getBreakIndex(Name name) { // -1 if not found
for (int i = breakStack.size() - 1; i >= 0; i--) {
if (breakStack[i] == name) {
return breakStack.size() - 1 - i;
}
}
std::cerr << "bad break: " << name << std::endl;
abort();
}
void WasmBinaryWriter::visitBreak(Break *curr) {
if (debug) std::cerr << "zz node: Break" << std::endl;
if (curr->value) {
recurse(curr->value);
}
if (curr->condition) recurse(curr->condition);
o << int8_t(curr->condition ? BinaryConsts::BrIf : BinaryConsts::Br)
<< U32LEB(getBreakIndex(curr->name));
}
void WasmBinaryWriter::visitSwitch(Switch *curr) {
if (debug) std::cerr << "zz node: Switch" << std::endl;
if (curr->value) {
recurse(curr->value);
}
recurse(curr->condition);
o << int8_t(BinaryConsts::TableSwitch) << U32LEB(curr->targets.size());
for (auto target : curr->targets) {
o << U32LEB(getBreakIndex(target));
}
o << U32LEB(getBreakIndex(curr->default_));
}
void WasmBinaryWriter::visitCall(Call *curr) {
if (debug) std::cerr << "zz node: Call" << std::endl;
for (auto* operand : curr->operands) {
recurse(operand);
}
o << int8_t(BinaryConsts::CallFunction) << U32LEB(getFunctionIndex(curr->target));
}
void WasmBinaryWriter::visitCallImport(CallImport *curr) {
if (debug) std::cerr << "zz node: CallImport" << std::endl;
for (auto* operand : curr->operands) {
recurse(operand);
}
o << int8_t(BinaryConsts::CallFunction) << U32LEB(getFunctionIndex(curr->target));
}
void WasmBinaryWriter::visitCallIndirect(CallIndirect *curr) {
if (debug) std::cerr << "zz node: CallIndirect" << std::endl;
for (auto* operand : curr->operands) {
recurse(operand);
}
recurse(curr->target);
o << int8_t(BinaryConsts::CallIndirect)
<< U32LEB(getFunctionTypeIndex(curr->fullType))
<< U32LEB(0); // Reserved flags field
}
void WasmBinaryWriter::visitGetLocal(GetLocal *curr) {
if (debug) std::cerr << "zz node: GetLocal " << (o.size() + 1) << std::endl;
o << int8_t(BinaryConsts::GetLocal) << U32LEB(mappedLocals[curr->index]);
}
void WasmBinaryWriter::visitSetLocal(SetLocal *curr) {
if (debug) std::cerr << "zz node: Set|TeeLocal" << std::endl;
recurse(curr->value);
o << int8_t(curr->isTee() ? BinaryConsts::TeeLocal : BinaryConsts::SetLocal) << U32LEB(mappedLocals[curr->index]);
}
void WasmBinaryWriter::visitGetGlobal(GetGlobal *curr) {
if (debug) std::cerr << "zz node: GetGlobal " << (o.size() + 1) << std::endl;
o << int8_t(BinaryConsts::GetGlobal) << U32LEB(getGlobalIndex(curr->name));
}
void WasmBinaryWriter::visitSetGlobal(SetGlobal *curr) {
if (debug) std::cerr << "zz node: SetGlobal" << std::endl;
recurse(curr->value);
o << int8_t(BinaryConsts::SetGlobal) << U32LEB(getGlobalIndex(curr->name));
}
void WasmBinaryWriter::emitMemoryAccess(size_t alignment, size_t bytes, uint32_t offset) {
o << U32LEB(Log2(alignment ? alignment : bytes));
o << U32LEB(offset);
}
void WasmBinaryWriter::visitLoad(Load *curr) {
if (debug) std::cerr << "zz node: Load" << std::endl;
recurse(curr->ptr);
switch (curr->type) {
case i32: {
switch (curr->bytes) {
case 1: o << int8_t(curr->signed_ ? BinaryConsts::I32LoadMem8S : BinaryConsts::I32LoadMem8U); break;
case 2: o << int8_t(curr->signed_ ? BinaryConsts::I32LoadMem16S : BinaryConsts::I32LoadMem16U); break;
case 4: o << int8_t(BinaryConsts::I32LoadMem); break;
default: abort();
}
break;
}
case i64: {
switch (curr->bytes) {
case 1: o << int8_t(curr->signed_ ? BinaryConsts::I64LoadMem8S : BinaryConsts::I64LoadMem8U); break;
case 2: o << int8_t(curr->signed_ ? BinaryConsts::I64LoadMem16S : BinaryConsts::I64LoadMem16U); break;
case 4: o << int8_t(curr->signed_ ? BinaryConsts::I64LoadMem32S : BinaryConsts::I64LoadMem32U); break;
case 8: o << int8_t(BinaryConsts::I64LoadMem); break;
default: abort();
}
break;
}
case f32: o << int8_t(BinaryConsts::F32LoadMem); break;
case f64: o << int8_t(BinaryConsts::F64LoadMem); break;
case unreachable: return; // the pointer is unreachable, so we are never reached; just don't emit a load
default: WASM_UNREACHABLE();
}
emitMemoryAccess(curr->align, curr->bytes, curr->offset);
}
void WasmBinaryWriter::visitStore(Store *curr) {
if (debug) std::cerr << "zz node: Store" << std::endl;
recurse(curr->ptr);
recurse(curr->value);
switch (curr->valueType) {
case i32: {
switch (curr->bytes) {
case 1: o << int8_t(BinaryConsts::I32StoreMem8); break;
case 2: o << int8_t(BinaryConsts::I32StoreMem16); break;
case 4: o << int8_t(BinaryConsts::I32StoreMem); break;
default: abort();
}
break;
}
case i64: {
switch (curr->bytes) {
case 1: o << int8_t(BinaryConsts::I64StoreMem8); break;
case 2: o << int8_t(BinaryConsts::I64StoreMem16); break;
case 4: o << int8_t(BinaryConsts::I64StoreMem32); break;
case 8: o << int8_t(BinaryConsts::I64StoreMem); break;
default: abort();
}
break;
}
case f32: o << int8_t(BinaryConsts::F32StoreMem); break;
case f64: o << int8_t(BinaryConsts::F64StoreMem); break;
default: abort();
}
emitMemoryAccess(curr->align, curr->bytes, curr->offset);
}
void WasmBinaryWriter::visitConst(Const *curr) {
if (debug) std::cerr << "zz node: Const" << curr << " : " << curr->type << std::endl;
switch (curr->type) {
case i32: {
o << int8_t(BinaryConsts::I32Const) << S32LEB(curr->value.geti32());
break;
}
case i64: {
o << int8_t(BinaryConsts::I64Const) << S64LEB(curr->value.geti64());
break;
}
case f32: {
o << int8_t(BinaryConsts::F32Const) << curr->value.reinterpreti32();
break;
}
case f64: {
o << int8_t(BinaryConsts::F64Const) << curr->value.reinterpreti64();
break;
}
default: abort();
}
if (debug) std::cerr << "zz const node done.\n";
}
void WasmBinaryWriter::visitUnary(Unary *curr) {
if (debug) std::cerr << "zz node: Unary" << std::endl;
recurse(curr->value);
switch (curr->op) {
case ClzInt32: o << int8_t(BinaryConsts::I32Clz); break;
case CtzInt32: o << int8_t(BinaryConsts::I32Ctz); break;
case PopcntInt32: o << int8_t(BinaryConsts::I32Popcnt); break;
case EqZInt32: o << int8_t(BinaryConsts::I32EqZ); break;
case ClzInt64: o << int8_t(BinaryConsts::I64Clz); break;
case CtzInt64: o << int8_t(BinaryConsts::I64Ctz); break;
case PopcntInt64: o << int8_t(BinaryConsts::I64Popcnt); break;
case EqZInt64: o << int8_t(BinaryConsts::I64EqZ); break;
case NegFloat32: o << int8_t(BinaryConsts::F32Neg); break;
case AbsFloat32: o << int8_t(BinaryConsts::F32Abs); break;
case CeilFloat32: o << int8_t(BinaryConsts::F32Ceil); break;
case FloorFloat32: o << int8_t(BinaryConsts::F32Floor); break;
case TruncFloat32: o << int8_t(BinaryConsts::F32Trunc); break;
case NearestFloat32: o << int8_t(BinaryConsts::F32NearestInt); break;
case SqrtFloat32: o << int8_t(BinaryConsts::F32Sqrt); break;
case NegFloat64: o << int8_t(BinaryConsts::F64Neg); break;
case AbsFloat64: o << int8_t(BinaryConsts::F64Abs); break;
case CeilFloat64: o << int8_t(BinaryConsts::F64Ceil); break;
case FloorFloat64: o << int8_t(BinaryConsts::F64Floor); break;
case TruncFloat64: o << int8_t(BinaryConsts::F64Trunc); break;
case NearestFloat64: o << int8_t(BinaryConsts::F64NearestInt); break;
case SqrtFloat64: o << int8_t(BinaryConsts::F64Sqrt); break;
case ExtendSInt32: o << int8_t(BinaryConsts::I64STruncI32); break;
case ExtendUInt32: o << int8_t(BinaryConsts::I64UTruncI32); break;
case WrapInt64: o << int8_t(BinaryConsts::I32ConvertI64); break;
case TruncUFloat32ToInt32: o << int8_t(BinaryConsts::I32UTruncF32); break;
case TruncUFloat32ToInt64: o << int8_t(BinaryConsts::I64UTruncF32); break;
case TruncSFloat32ToInt32: o << int8_t(BinaryConsts::I32STruncF32); break;
case TruncSFloat32ToInt64: o << int8_t(BinaryConsts::I64STruncF32); break;
case TruncUFloat64ToInt32: o << int8_t(BinaryConsts::I32UTruncF64); break;
case TruncUFloat64ToInt64: o << int8_t(BinaryConsts::I64UTruncF64); break;
case TruncSFloat64ToInt32: o << int8_t(BinaryConsts::I32STruncF64); break;
case TruncSFloat64ToInt64: o << int8_t(BinaryConsts::I64STruncF64); break;
case ConvertUInt32ToFloat32: o << int8_t(BinaryConsts::F32UConvertI32); break;
case ConvertUInt32ToFloat64: o << int8_t(BinaryConsts::F64UConvertI32); break;
case ConvertSInt32ToFloat32: o << int8_t(BinaryConsts::F32SConvertI32); break;
case ConvertSInt32ToFloat64: o << int8_t(BinaryConsts::F64SConvertI32); break;
case ConvertUInt64ToFloat32: o << int8_t(BinaryConsts::F32UConvertI64); break;
case ConvertUInt64ToFloat64: o << int8_t(BinaryConsts::F64UConvertI64); break;
case ConvertSInt64ToFloat32: o << int8_t(BinaryConsts::F32SConvertI64); break;
case ConvertSInt64ToFloat64: o << int8_t(BinaryConsts::F64SConvertI64); break;
case DemoteFloat64: o << int8_t(BinaryConsts::F32ConvertF64); break;
case PromoteFloat32: o << int8_t(BinaryConsts::F64ConvertF32); break;
case ReinterpretFloat32: o << int8_t(BinaryConsts::I32ReinterpretF32); break;
case ReinterpretFloat64: o << int8_t(BinaryConsts::I64ReinterpretF64); break;
case ReinterpretInt32: o << int8_t(BinaryConsts::F32ReinterpretI32); break;
case ReinterpretInt64: o << int8_t(BinaryConsts::F64ReinterpretI64); break;
default: abort();
}
}
void WasmBinaryWriter::visitBinary(Binary *curr) {
if (debug) std::cerr << "zz node: Binary" << std::endl;
recurse(curr->left);
recurse(curr->right);
switch (curr->op) {
case AddInt32: o << int8_t(BinaryConsts::I32Add); break;
case SubInt32: o << int8_t(BinaryConsts::I32Sub); break;
case MulInt32: o << int8_t(BinaryConsts::I32Mul); break;
case DivSInt32: o << int8_t(BinaryConsts::I32DivS); break;
case DivUInt32: o << int8_t(BinaryConsts::I32DivU); break;
case RemSInt32: o << int8_t(BinaryConsts::I32RemS); break;
case RemUInt32: o << int8_t(BinaryConsts::I32RemU); break;
case AndInt32: o << int8_t(BinaryConsts::I32And); break;
case OrInt32: o << int8_t(BinaryConsts::I32Or); break;
case XorInt32: o << int8_t(BinaryConsts::I32Xor); break;
case ShlInt32: o << int8_t(BinaryConsts::I32Shl); break;
case ShrUInt32: o << int8_t(BinaryConsts::I32ShrU); break;
case ShrSInt32: o << int8_t(BinaryConsts::I32ShrS); break;
case RotLInt32: o << int8_t(BinaryConsts::I32RotL); break;
case RotRInt32: o << int8_t(BinaryConsts::I32RotR); break;
case EqInt32: o << int8_t(BinaryConsts::I32Eq); break;
case NeInt32: o << int8_t(BinaryConsts::I32Ne); break;
case LtSInt32: o << int8_t(BinaryConsts::I32LtS); break;
case LtUInt32: o << int8_t(BinaryConsts::I32LtU); break;
case LeSInt32: o << int8_t(BinaryConsts::I32LeS); break;
case LeUInt32: o << int8_t(BinaryConsts::I32LeU); break;
case GtSInt32: o << int8_t(BinaryConsts::I32GtS); break;
case GtUInt32: o << int8_t(BinaryConsts::I32GtU); break;
case GeSInt32: o << int8_t(BinaryConsts::I32GeS); break;
case GeUInt32: o << int8_t(BinaryConsts::I32GeU); break;
case AddInt64: o << int8_t(BinaryConsts::I64Add); break;
case SubInt64: o << int8_t(BinaryConsts::I64Sub); break;
case MulInt64: o << int8_t(BinaryConsts::I64Mul); break;
case DivSInt64: o << int8_t(BinaryConsts::I64DivS); break;
case DivUInt64: o << int8_t(BinaryConsts::I64DivU); break;
case RemSInt64: o << int8_t(BinaryConsts::I64RemS); break;
case RemUInt64: o << int8_t(BinaryConsts::I64RemU); break;
case AndInt64: o << int8_t(BinaryConsts::I64And); break;
case OrInt64: o << int8_t(BinaryConsts::I64Or); break;
case XorInt64: o << int8_t(BinaryConsts::I64Xor); break;
case ShlInt64: o << int8_t(BinaryConsts::I64Shl); break;
case ShrUInt64: o << int8_t(BinaryConsts::I64ShrU); break;
case ShrSInt64: o << int8_t(BinaryConsts::I64ShrS); break;
case RotLInt64: o << int8_t(BinaryConsts::I64RotL); break;
case RotRInt64: o << int8_t(BinaryConsts::I64RotR); break;
case EqInt64: o << int8_t(BinaryConsts::I64Eq); break;
case NeInt64: o << int8_t(BinaryConsts::I64Ne); break;
case LtSInt64: o << int8_t(BinaryConsts::I64LtS); break;
case LtUInt64: o << int8_t(BinaryConsts::I64LtU); break;
case LeSInt64: o << int8_t(BinaryConsts::I64LeS); break;
case LeUInt64: o << int8_t(BinaryConsts::I64LeU); break;
case GtSInt64: o << int8_t(BinaryConsts::I64GtS); break;
case GtUInt64: o << int8_t(BinaryConsts::I64GtU); break;
case GeSInt64: o << int8_t(BinaryConsts::I64GeS); break;
case GeUInt64: o << int8_t(BinaryConsts::I64GeU); break;
case AddFloat32: o << int8_t(BinaryConsts::F32Add); break;
case SubFloat32: o << int8_t(BinaryConsts::F32Sub); break;
case MulFloat32: o << int8_t(BinaryConsts::F32Mul); break;
case DivFloat32: o << int8_t(BinaryConsts::F32Div); break;
case CopySignFloat32: o << int8_t(BinaryConsts::F32CopySign);break;
case MinFloat32: o << int8_t(BinaryConsts::F32Min); break;
case MaxFloat32: o << int8_t(BinaryConsts::F32Max); break;
case EqFloat32: o << int8_t(BinaryConsts::F32Eq); break;
case NeFloat32: o << int8_t(BinaryConsts::F32Ne); break;
case LtFloat32: o << int8_t(BinaryConsts::F32Lt); break;
case LeFloat32: o << int8_t(BinaryConsts::F32Le); break;
case GtFloat32: o << int8_t(BinaryConsts::F32Gt); break;
case GeFloat32: o << int8_t(BinaryConsts::F32Ge); break;
case AddFloat64: o << int8_t(BinaryConsts::F64Add); break;
case SubFloat64: o << int8_t(BinaryConsts::F64Sub); break;
case MulFloat64: o << int8_t(BinaryConsts::F64Mul); break;
case DivFloat64: o << int8_t(BinaryConsts::F64Div); break;
case CopySignFloat64: o << int8_t(BinaryConsts::F64CopySign);break;
case MinFloat64: o << int8_t(BinaryConsts::F64Min); break;
case MaxFloat64: o << int8_t(BinaryConsts::F64Max); break;
case EqFloat64: o << int8_t(BinaryConsts::F64Eq); break;
case NeFloat64: o << int8_t(BinaryConsts::F64Ne); break;
case LtFloat64: o << int8_t(BinaryConsts::F64Lt); break;
case LeFloat64: o << int8_t(BinaryConsts::F64Le); break;
case GtFloat64: o << int8_t(BinaryConsts::F64Gt); break;
case GeFloat64: o << int8_t(BinaryConsts::F64Ge); break;
default: abort();
}
}
void WasmBinaryWriter::visitSelect(Select *curr) {
if (debug) std::cerr << "zz node: Select" << std::endl;
recurse(curr->ifTrue);
recurse(curr->ifFalse);
recurse(curr->condition);
o << int8_t(BinaryConsts::Select);
}
void WasmBinaryWriter::visitReturn(Return *curr) {
if (debug) std::cerr << "zz node: Return" << std::endl;
if (curr->value) {
recurse(curr->value);
}
o << int8_t(BinaryConsts::Return);
}
void WasmBinaryWriter::visitHost(Host *curr) {
if (debug) std::cerr << "zz node: Host" << std::endl;
switch (curr->op) {
case CurrentMemory: {
o << int8_t(BinaryConsts::CurrentMemory);
break;
}
case GrowMemory: {
recurse(curr->operands[0]);
o << int8_t(BinaryConsts::GrowMemory);
break;
}
default: abort();
}
o << U32LEB(0); // Reserved flags field
}
void WasmBinaryWriter::visitNop(Nop *curr) {
if (debug) std::cerr << "zz node: Nop" << std::endl;
o << int8_t(BinaryConsts::Nop);
}
void WasmBinaryWriter::visitUnreachable(Unreachable *curr) {
if (debug) std::cerr << "zz node: Unreachable" << std::endl;
o << int8_t(BinaryConsts::Unreachable);
}
void WasmBinaryWriter::visitDrop(Drop *curr) {
if (debug) std::cerr << "zz node: Drop" << std::endl;
recurse(curr->value);
o << int8_t(BinaryConsts::Drop);
}
// reader
static Name RETURN_BREAK("binaryen|break-to-return");
void WasmBinaryBuilder::read() {
readHeader();
readSourceMapHeader();
// read sections until the end
while (more()) {
uint32_t sectionCode = getU32LEB();
uint32_t payloadLen = getU32LEB();
if (pos + payloadLen > input.size()) throw ParseException("Section extends beyond end of input");
auto oldPos = pos;
// note the section in the list of seen sections, as almost no sections can appear more than once,
// and verify those that shouldn't do not.
if (sectionCode != BinaryConsts::Section::User && sectionCode != BinaryConsts::Section::Code) {
if (!seenSections.insert(BinaryConsts::Section(sectionCode)).second) {
throw ParseException("section seen more than once: " + std::to_string(sectionCode));
}
}
switch (sectionCode) {
case BinaryConsts::Section::Start: readStart(); break;
case BinaryConsts::Section::Memory: readMemory(); break;
case BinaryConsts::Section::Type: readSignatures(); break;
case BinaryConsts::Section::Import: readImports(); break;
case BinaryConsts::Section::Function: readFunctionSignatures(); break;
case BinaryConsts::Section::Code: readFunctions(); break;
case BinaryConsts::Section::Export: readExports(); break;
case BinaryConsts::Section::Element: readTableElements(); break;
case BinaryConsts::Section::Global: {
readGlobals();
// imports can read global imports, so we run getGlobalName and create the mapping
// but after we read globals, we need to add the internal globals too, so do that here
mappedGlobals.clear(); // wipe the mapping
getGlobalName(-1); // force rebuild
break;
}
case BinaryConsts::Section::Data: readDataSegments(); break;
case BinaryConsts::Section::Table: readFunctionTableDeclaration(); break;
default: {
readUserSection(payloadLen);
if (pos > oldPos + payloadLen) {
throw ParseException("bad user section size, started at " + std::to_string(oldPos) + " plus payload " + std::to_string(payloadLen) + " not being equal to new position " + std::to_string(pos));
}
pos = oldPos + payloadLen;
}
}
// make sure we advanced exactly past this section
if (pos != oldPos + payloadLen) {
throw ParseException("bad section size, started at " + std::to_string(oldPos) + " plus payload " + std::to_string(payloadLen) + " not being equal to new position " + std::to_string(pos));
}
}
processFunctions();
}
void WasmBinaryBuilder::readUserSection(size_t payloadLen) {
auto oldPos = pos;
Name sectionName = getInlineString();
if (sectionName.equals(BinaryConsts::UserSections::Name)) {
readNames(payloadLen - (pos - oldPos));
} else {
// an unfamiliar custom section
wasm.userSections.resize(wasm.userSections.size() + 1);
auto& section = wasm.userSections.back();
section.name = sectionName.str;
auto sectionSize = payloadLen - (pos - oldPos);
section.data.resize(sectionSize);
for (size_t i = 0; i < sectionSize; i++) {
section.data[i] = getInt8();
}
}
}
uint8_t WasmBinaryBuilder::getInt8() {
if (!more()) throw ParseException("unexpected end of input");
if (debug) std::cerr << "getInt8: " << (int)(uint8_t)input[pos] << " (at " << pos << ")" << std::endl;
return input[pos++];
}
uint16_t WasmBinaryBuilder::getInt16() {
if (debug) std::cerr << "<==" << std::endl;
auto ret = uint16_t(getInt8());
ret |= uint16_t(getInt8()) << 8;
if (debug) std::cerr << "getInt16: " << ret << "/0x" << std::hex << ret << std::dec << " ==>" << std::endl;
return ret;
}
uint32_t WasmBinaryBuilder::getInt32() {
if (debug) std::cerr << "<==" << std::endl;
auto ret = uint32_t(getInt16());
ret |= uint32_t(getInt16()) << 16;
if (debug) std::cerr << "getInt32: " << ret << "/0x" << std::hex << ret << std::dec <<" ==>" << std::endl;
return ret;
}
uint64_t WasmBinaryBuilder::getInt64() {
if (debug) std::cerr << "<==" << std::endl;
auto ret = uint64_t(getInt32());
ret |= uint64_t(getInt32()) << 32;
if (debug) std::cerr << "getInt64: " << ret << "/0x" << std::hex << ret << std::dec << " ==>" << std::endl;
return ret;
}
Literal WasmBinaryBuilder::getFloat32Literal() {
if (debug) std::cerr << "<==" << std::endl;
auto ret = Literal(getInt32());
ret = ret.castToF32();
if (debug) std::cerr << "getFloat32: " << ret << " ==>" << std::endl;
return ret;
}
Literal WasmBinaryBuilder::getFloat64Literal() {
if (debug) std::cerr << "<==" << std::endl;
auto ret = Literal(getInt64());
ret = ret.castToF64();
if (debug) std::cerr << "getFloat64: " << ret << " ==>" << std::endl;
return ret;
}
uint32_t WasmBinaryBuilder::getU32LEB() {
if (debug) std::cerr << "<==" << std::endl;
U32LEB ret;
ret.read([&]() {
return getInt8();
});
if (debug) std::cerr << "getU32LEB: " << ret.value << " ==>" << std::endl;
return ret.value;
}
uint64_t WasmBinaryBuilder::getU64LEB() {
if (debug) std::cerr << "<==" << std::endl;
U64LEB ret;
ret.read([&]() {
return getInt8();
});
if (debug) std::cerr << "getU64LEB: " << ret.value << " ==>" << std::endl;
return ret.value;
}
int32_t WasmBinaryBuilder::getS32LEB() {
if (debug) std::cerr << "<==" << std::endl;
S32LEB ret;
ret.read([&]() {
return (int8_t)getInt8();
});
if (debug) std::cerr << "getS32LEB: " << ret.value << " ==>" << std::endl;
return ret.value;
}
int64_t WasmBinaryBuilder::getS64LEB() {
if (debug) std::cerr << "<==" << std::endl;
S64LEB ret;
ret.read([&]() {
return (int8_t)getInt8();
});
if (debug) std::cerr << "getS64LEB: " << ret.value << " ==>" << std::endl;
return ret.value;
}
WasmType WasmBinaryBuilder::getWasmType() {
int type = getS32LEB();
switch (type) {
// None only used for block signatures. TODO: Separate out?
case BinaryConsts::EncodedType::Empty: return none;
case BinaryConsts::EncodedType::i32: return i32;
case BinaryConsts::EncodedType::i64: return i64;
case BinaryConsts::EncodedType::f32: return f32;
case BinaryConsts::EncodedType::f64: return f64;
default: throw ParseException("invalid wasm type: " + std::to_string(type));
}
}
Name WasmBinaryBuilder::getString() {
if (debug) std::cerr << "<==" << std::endl;
size_t offset = getInt32();
Name ret = cashew::IString((&input[0]) + offset, false);
if (debug) std::cerr << "getString: " << ret << " ==>" << std::endl;
return ret;
}
Name WasmBinaryBuilder::getInlineString() {
if (debug) std::cerr << "<==" << std::endl;
auto len = getU32LEB();
std::string str;
for (size_t i = 0; i < len; i++) {
str = str + char(getInt8());
}
if (debug) std::cerr << "getInlineString: " << str << " ==>" << std::endl;
return Name(str);
}
void WasmBinaryBuilder::verifyInt8(int8_t x) {
int8_t y = getInt8();
if (x != y) throw ParseException("surprising value", 0, pos);
}
void WasmBinaryBuilder::verifyInt16(int16_t x) {
int16_t y = getInt16();
if (x != y) throw ParseException("surprising value", 0, pos);
}
void WasmBinaryBuilder::verifyInt32(int32_t x) {
int32_t y = getInt32();
if (x != y) throw ParseException("surprising value", 0, pos);
}
void WasmBinaryBuilder::verifyInt64(int64_t x) {
int64_t y = getInt64();
if (x != y) throw ParseException("surprising value", 0, pos);
}
void WasmBinaryBuilder::ungetInt8() {
assert(pos > 0);
if (debug) std::cerr << "ungetInt8 (at " << pos << ")" << std::endl;
pos--;
}
void WasmBinaryBuilder::readHeader() {
if (debug) std::cerr << "== readHeader" << std::endl;
verifyInt32(BinaryConsts::Magic);
verifyInt32(BinaryConsts::Version);
}
void WasmBinaryBuilder::readStart() {
if (debug) std::cerr << "== readStart" << std::endl;
startIndex = getU32LEB();
}
void WasmBinaryBuilder::readMemory() {
if (debug) std::cerr << "== readMemory" << std::endl;
auto numMemories = getU32LEB();
if (!numMemories) return;
if (numMemories != 1) {
throw ParseException("Must be exactly 1 memory");
}
if (wasm.memory.exists) {
throw ParseException("Memory cannot be both imported and defined");
}
wasm.memory.exists = true;
getResizableLimits(wasm.memory.initial, wasm.memory.max, Memory::kMaxSize);
}
void WasmBinaryBuilder::readSignatures() {
if (debug) std::cerr << "== readSignatures" << std::endl;
size_t numTypes = getU32LEB();
if (debug) std::cerr << "num: " << numTypes << std::endl;
for (size_t i = 0; i < numTypes; i++) {
if (debug) std::cerr << "read one" << std::endl;
auto curr = new FunctionType;
auto form = getS32LEB();
if (form != BinaryConsts::EncodedType::Func) {
throw ParseException("bad signature form " + std::to_string(form));
}
size_t numParams = getU32LEB();
if (debug) std::cerr << "num params: " << numParams << std::endl;
for (size_t j = 0; j < numParams; j++) {
curr->params.push_back(getWasmType());
}
auto numResults = getU32LEB();
if (numResults == 0) {
curr->result = none;
} else {
if (numResults != 1) {
throw ParseException("signature must have 1 result");
}
curr->result = getWasmType();
}
curr->name = Name::fromInt(wasm.functionTypes.size());
wasm.addFunctionType(curr);
}
}
Name WasmBinaryBuilder::getFunctionIndexName(Index i) {
if (i < functionImportIndexes.size()) {
auto* import = wasm.getImport(functionImportIndexes[i]);
assert(import->kind == ExternalKind::Function);
return import->name;
} else {
i -= functionImportIndexes.size();
if (i >= wasm.functions.size()) {
throw ParseException("bad function index");
}
return wasm.functions[i]->name;
}
}
void WasmBinaryBuilder::getResizableLimits(Address& initial, Address& max, Address defaultIfNoMax) {
auto flags = getU32LEB();
initial = getU32LEB();
bool hasMax = flags & 0x1;
if (hasMax) max = getU32LEB();
else max = defaultIfNoMax;
}
void WasmBinaryBuilder::readImports() {
if (debug) std::cerr << "== readImports" << std::endl;
size_t num = getU32LEB();
if (debug) std::cerr << "num: " << num << std::endl;
for (size_t i = 0; i < num; i++) {
if (debug) std::cerr << "read one" << std::endl;
auto curr = new Import;
curr->name = Name(std::string("import$") + std::to_string(i));
curr->module = getInlineString();
curr->base = getInlineString();
curr->kind = (ExternalKind)getU32LEB();
switch (curr->kind) {
case ExternalKind::Function: {
auto index = getU32LEB();
if (index >= wasm.functionTypes.size()) {
throw ParseException("invalid function index " + std::to_string(index) + " / " + std::to_string(wasm.functionTypes.size()));
}
curr->functionType = wasm.functionTypes[index]->name;
assert(curr->functionType.is());
functionImportIndexes.push_back(curr->name);
break;
}
case ExternalKind::Table: {
auto elementType = getS32LEB();
WASM_UNUSED(elementType);
if (elementType != BinaryConsts::EncodedType::AnyFunc) throw ParseException("Imported table type is not AnyFunc");
wasm.table.exists = true;
wasm.table.imported = true;
getResizableLimits(wasm.table.initial, wasm.table.max, Table::kMaxSize);
break;
}
case ExternalKind::Memory: {
wasm.memory.exists = true;
wasm.memory.imported = true;
getResizableLimits(wasm.memory.initial, wasm.memory.max, Memory::kMaxSize);
break;
}
case ExternalKind::Global: {
curr->globalType = getWasmType();
auto globalMutable = getU32LEB();
if (globalMutable) {
throw ParseException("imported globals cannot be mutable");
}
break;
}
default: {
throw ParseException("bad import kind");
}
}
wasm.addImport(curr);
}
}
void WasmBinaryBuilder::readFunctionSignatures() {
if (debug) std::cerr << "== readFunctionSignatures" << std::endl;
size_t num = getU32LEB();
if (debug) std::cerr << "num: " << num << std::endl;
for (size_t i = 0; i < num; i++) {
if (debug) std::cerr << "read one" << std::endl;
auto index = getU32LEB();
if (index >= wasm.functionTypes.size()) {
throw ParseException("invalid function type index for function");
}
functionTypes.push_back(wasm.functionTypes[index].get());
}
}
void WasmBinaryBuilder::readFunctions() {
if (debug) std::cerr << "== readFunctions" << std::endl;
size_t total = getU32LEB();
if (total != functionTypes.size()) {
throw ParseException("invalid function section size, must equal types");
}
for (size_t i = 0; i < total; i++) {
if (debug) std::cerr << "read one at " << pos << std::endl;
size_t size = getU32LEB();
if (size == 0) {
throw ParseException("empty function size");
}
endOfFunction = pos + size;
auto type = functionTypes[i];
if (debug) std::cerr << "reading " << i << std::endl;
size_t nextVar = 0;
auto addVar = [&]() {
Name name = cashew::IString(("var$" + std::to_string(nextVar++)).c_str(), false);
return name;
};
std::vector<NameType> params, vars;
for (size_t j = 0; j < type->params.size(); j++) {
params.emplace_back(addVar(), type->params[j]);
}
size_t numLocalTypes = getU32LEB();
for (size_t t = 0; t < numLocalTypes; t++) {
auto num = getU32LEB();
auto type = getWasmType();
while (num > 0) {
vars.emplace_back(addVar(), type);
num--;
}
}
auto func = Builder(wasm).makeFunction(
Name::fromInt(i),
std::move(params),
type->result,
std::move(vars)
);
func->type = type->name;
currFunction = func;
{
// process the function body
if (debug) std::cerr << "processing function: " << i << std::endl;
nextLabel = 0;
useDebugLocation = false;
breaksToReturn = false;
// process body
assert(breakStack.empty());
breakStack.emplace_back(RETURN_BREAK, func->result != none); // the break target for the function scope
assert(expressionStack.empty());
assert(depth == 0);
func->body = getMaybeBlock(func->result);
assert(depth == 0);
assert(breakStack.size() == 1);
breakStack.pop_back();
if (!expressionStack.empty()) {
throw ParseException("stack not empty on function exit");
}
if (pos != endOfFunction) {
throw ParseException("binary offset at function exit not at expected location");
}
if (breaksToReturn) {
// we broke to return, so we need an outer block to break to
func->body = Builder(wasm).blockifyWithName(func->body, RETURN_BREAK);
}
}
currFunction = nullptr;
functions.push_back(func);
}
if (debug) std::cerr << " end function bodies" << std::endl;
}
void WasmBinaryBuilder::readExports() {
if (debug) std::cerr << "== readExports" << std::endl;
size_t num = getU32LEB();
if (debug) std::cerr << "num: " << num << std::endl;
std::set<Name> names;
for (size_t i = 0; i < num; i++) {
if (debug) std::cerr << "read one" << std::endl;
auto curr = new Export;
curr->name = getInlineString();
if (names.count(curr->name) > 0) {
throw ParseException("duplicate export name");
}
names.insert(curr->name);
curr->kind = (ExternalKind)getU32LEB();
auto index = getU32LEB();
exportIndexes[curr] = index;
exportOrder.push_back(curr);
}
}
static int32_t readBase64VLQ(std::istream& in) {
uint32_t value = 0;
uint32_t shift = 0;
while (1) {
auto ch = in.get();
if (ch == EOF)
throw MapParseException("unexpected EOF in the middle of VLQ");
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch < 'g')) {
// last number digit
uint32_t digit = ch < 'a' ? ch - 'A' : ch - 'a' + 26;
value |= digit << shift;
break;
}
if (!(ch >= 'g' && ch <= 'z') && !(ch >= '0' && ch <= '9') &&
ch != '+' && ch != '/') {
throw MapParseException("invalid VLQ digit");
}
uint32_t digit = ch > '9' ? ch - 'g' : (ch >= '0' ? ch - '0' + 20 : (ch == '+' ? 30 : 31));
value |= digit << shift;
shift += 5;
}
return value & 1 ? -int32_t(value >> 1) : int32_t(value >> 1);
}
void WasmBinaryBuilder::readSourceMapHeader() {
if (!sourceMap) return;
auto maybeReadChar = [&](char expected) {
if (sourceMap->peek() != expected) return false;
sourceMap->get();
return true;
};
auto mustReadChar = [&](char expected) {
if (sourceMap->get() != expected) {
throw MapParseException("Unexpected char");
}
};
auto findField = [&](const char* name, size_t len) {
bool matching = false;
size_t pos;
while (1) {
int ch = sourceMap->get();
if (ch == EOF) return false;
if (ch == '\"') {
matching = true;
pos = 0;
} else if (matching && name[pos] == ch) {
++pos;
if (pos == len) {
if (maybeReadChar('\"')) break; // found field
}
} else {
matching = false;
}
}
mustReadChar(':');
return true;
};
auto readString = [&](std::string& str) {
std::vector<char> vec;
mustReadChar('\"');
if (!maybeReadChar('\"')) {
while (1) {
int ch = sourceMap->get();
if (ch == EOF) {
throw MapParseException("unexpected EOF in the middle of string");
}
if (ch == '\"') break;
vec.push_back(ch);
}
}
str = std::string(vec.begin(), vec.end());
};
if (!findField("sources", strlen("sources"))) {
throw MapParseException("cannot find the sources field in map");
}
mustReadChar('[');
if (!maybeReadChar(']')) {
do {
std::string file;
readString(file);
Index index = wasm.debugInfoFileNames.size();
wasm.debugInfoFileNames.push_back(file);
debugInfoFileIndices[file] = index;
} while (maybeReadChar(','));
mustReadChar(']');
}
if (!findField("mappings", strlen("mappings"))) {
throw MapParseException("cannot find the mappings field in map");
}
mustReadChar('\"');
if (maybeReadChar('\"')) { // empty mappings
nextDebugLocation.first = 0;
return;
}
// read first debug location
uint32_t position = readBase64VLQ(*sourceMap);
uint32_t fileIndex = readBase64VLQ(*sourceMap);
uint32_t lineNumber = readBase64VLQ(*sourceMap) + 1; // adjust zero-based line number
uint32_t columnNumber = readBase64VLQ(*sourceMap);
nextDebugLocation = { position, { fileIndex, lineNumber, columnNumber } };
}
void WasmBinaryBuilder::readNextDebugLocation() {
if (!sourceMap) return;
char ch;
*sourceMap >> ch;
if (ch == '\"') { // end of records
nextDebugLocation.first = 0;
return;
}
if (ch != ',') {
throw MapParseException("Unexpected delimiter");
}
int32_t positionDelta = readBase64VLQ(*sourceMap);
uint32_t position = nextDebugLocation.first + positionDelta;
int32_t fileIndexDelta = readBase64VLQ(*sourceMap);
uint32_t fileIndex = nextDebugLocation.second.fileIndex + fileIndexDelta;
int32_t lineNumberDelta = readBase64VLQ(*sourceMap);
uint32_t lineNumber = nextDebugLocation.second.lineNumber + lineNumberDelta;
int32_t columnNumberDelta = readBase64VLQ(*sourceMap);
uint32_t columnNumber = nextDebugLocation.second.columnNumber + columnNumberDelta;
nextDebugLocation = { position, { fileIndex, lineNumber, columnNumber } };
}
Expression* WasmBinaryBuilder::readExpression() {
assert(depth == 0);
processExpressions();
if (expressionStack.size() != 1) {
throw ParseException("expected to read a single expression");
}
auto* ret = popExpression();
assert(depth == 0);
return ret;
}
void WasmBinaryBuilder::readGlobals() {
if (debug) std::cerr << "== readGlobals" << std::endl;
size_t num = getU32LEB();
if (debug) std::cerr << "num: " << num << std::endl;
for (size_t i = 0; i < num; i++) {
if (debug) std::cerr << "read one" << std::endl;
auto curr = new Global;
curr->type = getWasmType();
auto mutable_ = getU32LEB();
if (bool(mutable_) != mutable_) throw ParseException("Global mutability must be 0 or 1");
curr->mutable_ = mutable_;
curr->init = readExpression();
curr->name = Name("global$" + std::to_string(wasm.globals.size()));
wasm.addGlobal(curr);
}
}
void WasmBinaryBuilder::processExpressions() { // until an end or else marker, or the end of the function
while (1) {
Expression* curr;
auto ret = readExpression(curr);
if (!curr) {
lastSeparator = ret;
return;
}
expressionStack.push_back(curr);
}
}
Expression* WasmBinaryBuilder::popExpression() {
if (expressionStack.empty()) {
throw ParseException("attempted pop from empty stack");
}
auto ret = expressionStack.back();
expressionStack.pop_back();
return ret;
}
Expression* WasmBinaryBuilder::popNonVoidExpression() {
auto* ret = popExpression();
if (ret->type != none) return ret;
// we found a void, so this is stacky code that we must handle carefully
Builder builder(wasm);
// add elements until we find a non-void
std::vector<Expression*> expressions;
expressions.push_back(ret);
while (1) {
auto* curr = popExpression();
expressions.push_back(curr);
if (curr->type != none) break;
}
auto* block = builder.makeBlock();
while (!expressions.empty()) {
block->list.push_back(expressions.back());
expressions.pop_back();
}
auto type = block->list[0]->type;
if (!currFunction) {
throw ParseException("popping void outside of function, where we need a new local");
}
auto local = builder.addVar(currFunction, type);
block->list[0] = builder.makeSetLocal(local, block->list[0]);
block->list.push_back(builder.makeGetLocal(local, type));
block->finalize();
return block;
}
Name WasmBinaryBuilder::getGlobalName(Index index) {
if (!mappedGlobals.size()) {
// Create name => index mapping.
for (auto& import : wasm.imports) {
if (import->kind != ExternalKind::Global) continue;
auto index = mappedGlobals.size();
mappedGlobals[index] = import->name;
}
for (size_t i = 0; i < wasm.globals.size(); i++) {
auto index = mappedGlobals.size();
mappedGlobals[index] = wasm.globals[i]->name;
}
}
if (index == Index(-1)) return Name("null"); // just a force-rebuild
if (mappedGlobals.count(index) == 0) {
throw ParseException("bad global index");
}
return mappedGlobals[index];
}
void WasmBinaryBuilder::processFunctions() {
for (auto& func : functions) {
wasm.addFunction(func);
}
// we should have seen all the functions
// we assume this later down in fact, when we read wasm.functions[index],
// as index was validated vs functionTypes.size()
if (wasm.functions.size() != functionTypes.size()) {
throw ParseException("did not see the right number of functions");
}
// now that we have names for each function, apply things
if (startIndex != static_cast<Index>(-1)) {
wasm.start = getFunctionIndexName(startIndex);
}
for (auto* curr : exportOrder) {
auto index = exportIndexes[curr];
switch (curr->kind) {
case ExternalKind::Function: {
curr->value = getFunctionIndexName(index);
break;
}
case ExternalKind::Table: curr->value = Name::fromInt(0); break;
case ExternalKind::Memory: curr->value = Name::fromInt(0); break;
case ExternalKind::Global: curr->value = getGlobalName(index); break;
default: throw ParseException("bad export kind");
}
wasm.addExport(curr);
}
for (auto& iter : functionCalls) {
size_t index = iter.first;
auto& calls = iter.second;
for (auto* call : calls) {
call->target = wasm.functions[index]->name;
}
}
for (auto& pair : functionTable) {
auto i = pair.first;
auto& indexes = pair.second;
for (auto j : indexes) {
wasm.table.segments[i].data.push_back(getFunctionIndexName(j));
}
}
}
void WasmBinaryBuilder::readDataSegments() {
if (debug) std::cerr << "== readDataSegments" << std::endl;
auto num = getU32LEB();
for (size_t i = 0; i < num; i++) {
auto memoryIndex = getU32LEB();
WASM_UNUSED(memoryIndex);
if (memoryIndex != 0) {
throw ParseException("bad memory index, must be 0");
}
Memory::Segment curr;
auto offset = readExpression();
auto size = getU32LEB();
std::vector<char> buffer;
buffer.resize(size);
for (size_t j = 0; j < size; j++) {
buffer[j] = char(getInt8());
}
wasm.memory.segments.emplace_back(offset, (const char*)&buffer[0], size);
}
}
void WasmBinaryBuilder::readFunctionTableDeclaration() {
if (debug) std::cerr << "== readFunctionTableDeclaration" << std::endl;
auto numTables = getU32LEB();
if (numTables != 1) throw ParseException("Only 1 table definition allowed in MVP");
if (wasm.table.exists) throw ParseException("Table cannot be both imported and defined");
wasm.table.exists = true;
auto elemType = getS32LEB();
if (elemType != BinaryConsts::EncodedType::AnyFunc) throw ParseException("ElementType must be AnyFunc in MVP");
getResizableLimits(wasm.table.initial, wasm.table.max, Table::kMaxSize);
}
void WasmBinaryBuilder::readTableElements() {
if (debug) std::cerr << "== readTableElements" << std::endl;
auto numSegments = getU32LEB();
if (numSegments >= Table::kMaxSize) throw ParseException("Too many segments");
for (size_t i = 0; i < numSegments; i++) {
auto tableIndex = getU32LEB();
if (tableIndex != 0) throw ParseException("Table elements must refer to table 0 in MVP");
wasm.table.segments.emplace_back(readExpression());
auto& indexSegment = functionTable[i];
auto size = getU32LEB();
for (Index j = 0; j < size; j++) {
indexSegment.push_back(getU32LEB());
}
}
}
void WasmBinaryBuilder::readNames(size_t payloadLen) {
if (debug) std::cerr << "== readNames" << std::endl;
auto sectionPos = pos;
while (pos < sectionPos + payloadLen) {
auto nameType = getU32LEB();
auto subsectionSize = getU32LEB();
auto subsectionPos = pos;
if (nameType != BinaryConsts::UserSections::Subsection::NameFunction) {
// TODO: locals
std::cerr << "unknown name subsection at " << pos << std::endl;
pos = subsectionPos + subsectionSize;
continue;
}
auto num = getU32LEB();
uint32_t importedFunctions = 0;
for (auto& import : wasm.imports) {
if (import->kind != ExternalKind::Function) continue;
importedFunctions++;
}
for (size_t i = 0; i < num; i++) {
auto index = getU32LEB();
if (index < importedFunctions) {
getInlineString(); // TODO: use this
} else if (index - importedFunctions < functions.size()) {
auto name = getInlineString();
functions[index - importedFunctions]->name = name;
}
}
// disallow duplicate names
std::set<Name> functionNames;
for (auto* func : functions) {
if (!functionNames.insert(func->name).second) {
throw ParseException("duplicate function name: " + std::string(func->name.str));
}
}
if (pos != subsectionPos + subsectionSize) {
throw ParseException("bad names subsection position change");
}
}
if (pos != sectionPos + payloadLen) {
throw ParseException("bad names section position change");
}
}
BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
if (pos == endOfFunction) {
throw ParseException("Reached function end without seeing End opcode");
}
if (debug) std::cerr << "zz recurse into " << ++depth << " at " << pos << std::endl;
if (nextDebugLocation.first) {
while (nextDebugLocation.first && nextDebugLocation.first <= pos) {
if (nextDebugLocation.first < pos) {
std::cerr << "skipping debug location info for " << nextDebugLocation.first << std::endl;
}
debugLocation = nextDebugLocation.second;
useDebugLocation = currFunction; // using only for function expressions
readNextDebugLocation();
}
}
uint8_t code = getInt8();
if (debug) std::cerr << "readExpression seeing " << (int)code << std::endl;
switch (code) {
case BinaryConsts::Block: visitBlock((curr = allocator.alloc<Block>())->cast<Block>()); break;
case BinaryConsts::If: visitIf((curr = allocator.alloc<If>())->cast<If>()); break;
case BinaryConsts::Loop: visitLoop((curr = allocator.alloc<Loop>())->cast<Loop>()); break;
case BinaryConsts::Br:
case BinaryConsts::BrIf: visitBreak((curr = allocator.alloc<Break>())->cast<Break>(), code); break; // code distinguishes br from br_if
case BinaryConsts::TableSwitch: visitSwitch((curr = allocator.alloc<Switch>())->cast<Switch>()); break;
case BinaryConsts::CallFunction: curr = visitCall(); break; // we don't know if it's a call or call_import yet
case BinaryConsts::CallIndirect: visitCallIndirect((curr = allocator.alloc<CallIndirect>())->cast<CallIndirect>()); break;
case BinaryConsts::GetLocal: visitGetLocal((curr = allocator.alloc<GetLocal>())->cast<GetLocal>()); break;
case BinaryConsts::TeeLocal:
case BinaryConsts::SetLocal: visitSetLocal((curr = allocator.alloc<SetLocal>())->cast<SetLocal>(), code); break;
case BinaryConsts::GetGlobal: visitGetGlobal((curr = allocator.alloc<GetGlobal>())->cast<GetGlobal>()); break;
case BinaryConsts::SetGlobal: visitSetGlobal((curr = allocator.alloc<SetGlobal>())->cast<SetGlobal>()); break;
case BinaryConsts::Select: visitSelect((curr = allocator.alloc<Select>())->cast<Select>()); break;
case BinaryConsts::Return: visitReturn((curr = allocator.alloc<Return>())->cast<Return>()); break;
case BinaryConsts::Nop: visitNop((curr = allocator.alloc<Nop>())->cast<Nop>()); break;
case BinaryConsts::Unreachable: visitUnreachable((curr = allocator.alloc<Unreachable>())->cast<Unreachable>()); break;
case BinaryConsts::Drop: visitDrop((curr = allocator.alloc<Drop>())->cast<Drop>()); break;
case BinaryConsts::End:
case BinaryConsts::Else: curr = nullptr; break;
default: {
// otherwise, the code is a subcode TODO: optimize
if (maybeVisitBinary(curr, code)) break;
if (maybeVisitUnary(curr, code)) break;
if (maybeVisitConst(curr, code)) break;
if (maybeVisitLoad(curr, code)) break;
if (maybeVisitStore(curr, code)) break;
if (maybeVisitHost(curr, code)) break;
throw ParseException("bad node code " + std::to_string(code));
}
}
if (useDebugLocation && curr) {
currFunction->debugLocations[curr] = debugLocation;
}
if (debug) std::cerr << "zz recurse from " << depth-- << " at " << pos << std::endl;
return BinaryConsts::ASTNodes(code);
}
void WasmBinaryBuilder::visitBlock(Block *curr) {
if (debug) std::cerr << "zz node: Block" << std::endl;
// special-case Block and de-recurse nested blocks in their first position, as that is
// a common pattern that can be very highly nested.
std::vector<Block*> stack;
while (1) {
curr->type = getWasmType();
curr->name = getNextLabel();
breakStack.push_back({curr->name, curr->type != none});
stack.push_back(curr);
if (getInt8() == BinaryConsts::Block) {
// a recursion
curr = allocator.alloc<Block>();
continue;
} else {
// end of recursion
ungetInt8();
break;
}
}
Block* last = nullptr;
while (stack.size() > 0) {
curr = stack.back();
stack.pop_back();
size_t start = expressionStack.size(); // everything after this, that is left when we see the marker, is ours
if (last) {
// the previous block is our first-position element
expressionStack.push_back(last);
}
last = curr;
processExpressions();
size_t end = expressionStack.size();
if (end < start) {
throw ParseException("block cannot pop from outside");
}
for (size_t i = start; i < end; i++) {
if (debug) std::cerr << " " << size_t(expressionStack[i]) << "\n zz Block element " << curr->list.size() << std::endl;
curr->list.push_back(expressionStack[i]);
}
expressionStack.resize(start);
curr->finalize(curr->type);
breakStack.pop_back();
}
}
Expression* WasmBinaryBuilder::getMaybeBlock(WasmType type) {
auto start = expressionStack.size();
processExpressions();
size_t end = expressionStack.size();
if (start - end == 1) {
return popExpression();
}
if (start > end) {
throw ParseException("block cannot pop from outside");
}
auto* block = allocator.alloc<Block>();
for (size_t i = start; i < end; i++) {
block->list.push_back(expressionStack[i]);
}
block->finalize(type);
expressionStack.resize(start);
return block;
}
Expression* WasmBinaryBuilder::getBlock(WasmType type) {
Name label = getNextLabel();
breakStack.push_back({label, type != none && type != unreachable});
auto* block = Builder(wasm).blockify(getMaybeBlock(type));
breakStack.pop_back();
block->cast<Block>()->name = label;
return block;
}
void WasmBinaryBuilder::visitIf(If *curr) {
if (debug) std::cerr << "zz node: If" << std::endl;
curr->type = getWasmType();
curr->condition = popNonVoidExpression();
curr->ifTrue = getBlock(curr->type);
if (lastSeparator == BinaryConsts::Else) {
curr->ifFalse = getBlock(curr->type);
}
curr->finalize(curr->type);
if (lastSeparator != BinaryConsts::End) {
throw ParseException("if should end with End");
}
}
void WasmBinaryBuilder::visitLoop(Loop *curr) {
if (debug) std::cerr << "zz node: Loop" << std::endl;
curr->type = getWasmType();
curr->name = getNextLabel();
breakStack.push_back({curr->name, 0});
curr->body = getMaybeBlock(curr->type);
breakStack.pop_back();
curr->finalize(curr->type);
}
WasmBinaryBuilder::BreakTarget WasmBinaryBuilder::getBreakTarget(int32_t offset) {
if (debug) std::cerr << "getBreakTarget " << offset << std::endl;
size_t index = breakStack.size() - 1 - offset;
if (index >= breakStack.size()) {
throw ParseException("bad breakindex");
}
if (index == 0) {
// trying to access the topmost element means we break out
// to the function scope, doing in effect a return, we'll
// need to create a block for that.
breaksToReturn = true;
}
if (debug) std::cerr << "breaktarget "<< breakStack[index].name << " arity " << breakStack[index].arity << std::endl;
return breakStack[index];
}
void WasmBinaryBuilder::visitBreak(Break *curr, uint8_t code) {
if (debug) std::cerr << "zz node: Break, code "<< int32_t(code) << std::endl;
BreakTarget target = getBreakTarget(getU32LEB());
curr->name = target.name;
if (code == BinaryConsts::BrIf) curr->condition = popNonVoidExpression();
if (target.arity) curr->value = popNonVoidExpression();
curr->finalize();
}
void WasmBinaryBuilder::visitSwitch(Switch *curr) {
if (debug) std::cerr << "zz node: Switch" << std::endl;
curr->condition = popNonVoidExpression();
auto numTargets = getU32LEB();
if (debug) std::cerr << "targets: "<< numTargets<<std::endl;
for (size_t i = 0; i < numTargets; i++) {
curr->targets.push_back(getBreakTarget(getU32LEB()).name);
}
auto defaultTarget = getBreakTarget(getU32LEB());
curr->default_ = defaultTarget.name;
if (debug) std::cerr << "default: "<< curr->default_<<std::endl;
if (defaultTarget.arity) curr->value = popNonVoidExpression();
curr->finalize();
}
Expression* WasmBinaryBuilder::visitCall() {
if (debug) std::cerr << "zz node: Call" << std::endl;
auto index = getU32LEB();
FunctionType* type;
Expression* ret;
if (index < functionImportIndexes.size()) {
// this is a call of an imported function
auto* call = allocator.alloc<CallImport>();
auto* import = wasm.getImport(functionImportIndexes[index]);
call->target = import->name;
type = wasm.getFunctionType(import->functionType);
fillCall(call, type);
call->finalize();
ret = call;
} else {
// this is a call of a defined function
auto* call = allocator.alloc<Call>();
auto adjustedIndex = index - functionImportIndexes.size();
if (adjustedIndex >= functionTypes.size()) {
throw ParseException("bad call index");
}
type = functionTypes[adjustedIndex];
fillCall(call, type);
functionCalls[adjustedIndex].push_back(call); // we don't know function names yet
call->finalize();
ret = call;
}
return ret;
}
void WasmBinaryBuilder::visitCallIndirect(CallIndirect *curr) {
if (debug) std::cerr << "zz node: CallIndirect" << std::endl;
auto index = getU32LEB();
if (index >= wasm.functionTypes.size()) {
throw ParseException("bad call_indirect function index");
}
auto* fullType = wasm.functionTypes[index].get();
auto reserved = getU32LEB();
if (reserved != 0) throw ParseException("Invalid flags field in call_indirect");
curr->fullType = fullType->name;
auto num = fullType->params.size();
curr->operands.resize(num);
curr->target = popNonVoidExpression();
for (size_t i = 0; i < num; i++) {
curr->operands[num - i - 1] = popNonVoidExpression();
}
curr->type = fullType->result;
curr->finalize();
}
void WasmBinaryBuilder::visitGetLocal(GetLocal *curr) {
if (debug) std::cerr << "zz node: GetLocal " << pos << std::endl;
if (!currFunction) {
throw ParseException("get_local outside of function");
}
curr->index = getU32LEB();
if (curr->index >= currFunction->getNumLocals()) {
throw ParseException("bad get_local index");
}
curr->type = currFunction->getLocalType(curr->index);
curr->finalize();
}
void WasmBinaryBuilder::visitSetLocal(SetLocal *curr, uint8_t code) {
if (debug) std::cerr << "zz node: Set|TeeLocal" << std::endl;
if (!currFunction) {
throw ParseException("set_local outside of function");
}
curr->index = getU32LEB();
if (curr->index >= currFunction->getNumLocals()) {
throw ParseException("bad set_local index");
}
curr->value = popNonVoidExpression();
curr->type = curr->value->type;
curr->setTee(code == BinaryConsts::TeeLocal);
curr->finalize();
}
void WasmBinaryBuilder::visitGetGlobal(GetGlobal *curr) {
if (debug) std::cerr << "zz node: GetGlobal " << pos << std::endl;
auto index = getU32LEB();
curr->name = getGlobalName(index);
auto* global = wasm.getGlobalOrNull(curr->name);
if (global) {
curr->type = global->type;
return;
}
auto* import = wasm.getImportOrNull(curr->name);
if (import && import->kind == ExternalKind::Global) {
curr->type = import->globalType;
return;
}
throw ParseException("bad get_global");
}
void WasmBinaryBuilder::visitSetGlobal(SetGlobal *curr) {
if (debug) std::cerr << "zz node: SetGlobal" << std::endl;
auto index = getU32LEB();
curr->name = getGlobalName(index);
curr->value = popNonVoidExpression();
curr->finalize();
}
void WasmBinaryBuilder::readMemoryAccess(Address& alignment, size_t bytes, Address& offset) {
alignment = Pow2(getU32LEB());
offset = getU32LEB();
}
bool WasmBinaryBuilder::maybeVisitLoad(Expression*& out, uint8_t code) {
Load* curr;
switch (code) {
case BinaryConsts::I32LoadMem8S: curr = allocator.alloc<Load>(); curr->bytes = 1; curr->type = i32; curr->signed_ = true; break;
case BinaryConsts::I32LoadMem8U: curr = allocator.alloc<Load>(); curr->bytes = 1; curr->type = i32; curr->signed_ = false; break;
case BinaryConsts::I32LoadMem16S: curr = allocator.alloc<Load>(); curr->bytes = 2; curr->type = i32; curr->signed_ = true; break;
case BinaryConsts::I32LoadMem16U: curr = allocator.alloc<Load>(); curr->bytes = 2; curr->type = i32; curr->signed_ = false; break;
case BinaryConsts::I32LoadMem: curr = allocator.alloc<Load>(); curr->bytes = 4; curr->type = i32; break;
case BinaryConsts::I64LoadMem8S: curr = allocator.alloc<Load>(); curr->bytes = 1; curr->type = i64; curr->signed_ = true; break;
case BinaryConsts::I64LoadMem8U: curr = allocator.alloc<Load>(); curr->bytes = 1; curr->type = i64; curr->signed_ = false; break;
case BinaryConsts::I64LoadMem16S: curr = allocator.alloc<Load>(); curr->bytes = 2; curr->type = i64; curr->signed_ = true; break;
case BinaryConsts::I64LoadMem16U: curr = allocator.alloc<Load>(); curr->bytes = 2; curr->type = i64; curr->signed_ = false; break;
case BinaryConsts::I64LoadMem32S: curr = allocator.alloc<Load>(); curr->bytes = 4; curr->type = i64; curr->signed_ = true; break;
case BinaryConsts::I64LoadMem32U: curr = allocator.alloc<Load>(); curr->bytes = 4; curr->type = i64; curr->signed_ = false; break;
case BinaryConsts::I64LoadMem: curr = allocator.alloc<Load>(); curr->bytes = 8; curr->type = i64; break;
case BinaryConsts::F32LoadMem: curr = allocator.alloc<Load>(); curr->bytes = 4; curr->type = f32; break;
case BinaryConsts::F64LoadMem: curr = allocator.alloc<Load>(); curr->bytes = 8; curr->type = f64; break;
default: return false;
}
if (debug) std::cerr << "zz node: Load" << std::endl;
readMemoryAccess(curr->align, curr->bytes, curr->offset);
curr->ptr = popNonVoidExpression();
curr->finalize();
out = curr;
return true;
}
bool WasmBinaryBuilder::maybeVisitStore(Expression*& out, uint8_t code) {
Store* curr;
switch (code) {
case BinaryConsts::I32StoreMem8: curr = allocator.alloc<Store>(); curr->bytes = 1; curr->valueType = i32; break;
case BinaryConsts::I32StoreMem16: curr = allocator.alloc<Store>(); curr->bytes = 2; curr->valueType = i32; break;
case BinaryConsts::I32StoreMem: curr = allocator.alloc<Store>(); curr->bytes = 4; curr->valueType = i32; break;
case BinaryConsts::I64StoreMem8: curr = allocator.alloc<Store>(); curr->bytes = 1; curr->valueType = i64; break;
case BinaryConsts::I64StoreMem16: curr = allocator.alloc<Store>(); curr->bytes = 2; curr->valueType = i64; break;
case BinaryConsts::I64StoreMem32: curr = allocator.alloc<Store>(); curr->bytes = 4; curr->valueType = i64; break;
case BinaryConsts::I64StoreMem: curr = allocator.alloc<Store>(); curr->bytes = 8; curr->valueType = i64; break;
case BinaryConsts::F32StoreMem: curr = allocator.alloc<Store>(); curr->bytes = 4; curr->valueType = f32; break;
case BinaryConsts::F64StoreMem: curr = allocator.alloc<Store>(); curr->bytes = 8; curr->valueType = f64; break;
default: return false;
}
if (debug) std::cerr << "zz node: Store" << std::endl;
readMemoryAccess(curr->align, curr->bytes, curr->offset);
curr->value = popNonVoidExpression();
curr->ptr = popNonVoidExpression();
curr->finalize();
out = curr;
return true;
}
bool WasmBinaryBuilder::maybeVisitConst(Expression*& out, uint8_t code) {
Const* curr;
if (debug) std::cerr << "zz node: Const, code " << code << std::endl;
switch (code) {
case BinaryConsts::I32Const: curr = allocator.alloc<Const>(); curr->value = Literal(getS32LEB()); break;
case BinaryConsts::I64Const: curr = allocator.alloc<Const>(); curr->value = Literal(getS64LEB()); break;
case BinaryConsts::F32Const: curr = allocator.alloc<Const>(); curr->value = getFloat32Literal(); break;
case BinaryConsts::F64Const: curr = allocator.alloc<Const>(); curr->value = getFloat64Literal(); break;
default: return false;
}
curr->type = curr->value.type;
out = curr;
return true;
}
bool WasmBinaryBuilder::maybeVisitUnary(Expression*& out, uint8_t code) {
Unary* curr;
switch (code) {
case BinaryConsts::I32Clz: curr = allocator.alloc<Unary>(); curr->op = ClzInt32; curr->type = i32; break;
case BinaryConsts::I64Clz: curr = allocator.alloc<Unary>(); curr->op = ClzInt64; curr->type = i64; break;
case BinaryConsts::I32Ctz: curr = allocator.alloc<Unary>(); curr->op = CtzInt32; curr->type = i32; break;
case BinaryConsts::I64Ctz: curr = allocator.alloc<Unary>(); curr->op = CtzInt64; curr->type = i64; break;
case BinaryConsts::I32Popcnt: curr = allocator.alloc<Unary>(); curr->op = PopcntInt32; curr->type = i32; break;
case BinaryConsts::I64Popcnt: curr = allocator.alloc<Unary>(); curr->op = PopcntInt64; curr->type = i64; break;
case BinaryConsts::I32EqZ: curr = allocator.alloc<Unary>(); curr->op = EqZInt32; curr->type = i32; break;
case BinaryConsts::I64EqZ: curr = allocator.alloc<Unary>(); curr->op = EqZInt64; curr->type = i32; break;
case BinaryConsts::F32Neg: curr = allocator.alloc<Unary>(); curr->op = NegFloat32; curr->type = f32; break;
case BinaryConsts::F64Neg: curr = allocator.alloc<Unary>(); curr->op = NegFloat64; curr->type = f64; break;
case BinaryConsts::F32Abs: curr = allocator.alloc<Unary>(); curr->op = AbsFloat32; curr->type = f32; break;
case BinaryConsts::F64Abs: curr = allocator.alloc<Unary>(); curr->op = AbsFloat64; curr->type = f64; break;
case BinaryConsts::F32Ceil: curr = allocator.alloc<Unary>(); curr->op = CeilFloat32; curr->type = f32; break;
case BinaryConsts::F64Ceil: curr = allocator.alloc<Unary>(); curr->op = CeilFloat64; curr->type = f64; break;
case BinaryConsts::F32Floor: curr = allocator.alloc<Unary>(); curr->op = FloorFloat32; curr->type = f32; break;
case BinaryConsts::F64Floor: curr = allocator.alloc<Unary>(); curr->op = FloorFloat64; curr->type = f64; break;
case BinaryConsts::F32NearestInt: curr = allocator.alloc<Unary>(); curr->op = NearestFloat32; curr->type = f32; break;
case BinaryConsts::F64NearestInt: curr = allocator.alloc<Unary>(); curr->op = NearestFloat64; curr->type = f64; break;
case BinaryConsts::F32Sqrt: curr = allocator.alloc<Unary>(); curr->op = SqrtFloat32; curr->type = f32; break;
case BinaryConsts::F64Sqrt: curr = allocator.alloc<Unary>(); curr->op = SqrtFloat64; curr->type = f64; break;
case BinaryConsts::F32UConvertI32: curr = allocator.alloc<Unary>(); curr->op = ConvertUInt32ToFloat32; curr->type = f32; break;
case BinaryConsts::F64UConvertI32: curr = allocator.alloc<Unary>(); curr->op = ConvertUInt32ToFloat64; curr->type = f64; break;
case BinaryConsts::F32SConvertI32: curr = allocator.alloc<Unary>(); curr->op = ConvertSInt32ToFloat32; curr->type = f32; break;
case BinaryConsts::F64SConvertI32: curr = allocator.alloc<Unary>(); curr->op = ConvertSInt32ToFloat64; curr->type = f64; break;
case BinaryConsts::F32UConvertI64: curr = allocator.alloc<Unary>(); curr->op = ConvertUInt64ToFloat32; curr->type = f32; break;
case BinaryConsts::F64UConvertI64: curr = allocator.alloc<Unary>(); curr->op = ConvertUInt64ToFloat64; curr->type = f64; break;
case BinaryConsts::F32SConvertI64: curr = allocator.alloc<Unary>(); curr->op = ConvertSInt64ToFloat32; curr->type = f32; break;
case BinaryConsts::F64SConvertI64: curr = allocator.alloc<Unary>(); curr->op = ConvertSInt64ToFloat64; curr->type = f64; break;
case BinaryConsts::I64STruncI32: curr = allocator.alloc<Unary>(); curr->op = ExtendSInt32; curr->type = i64; break;
case BinaryConsts::I64UTruncI32: curr = allocator.alloc<Unary>(); curr->op = ExtendUInt32; curr->type = i64; break;
case BinaryConsts::I32ConvertI64: curr = allocator.alloc<Unary>(); curr->op = WrapInt64; curr->type = i32; break;
case BinaryConsts::I32UTruncF32: curr = allocator.alloc<Unary>(); curr->op = TruncUFloat32ToInt32; curr->type = i32; break;
case BinaryConsts::I32UTruncF64: curr = allocator.alloc<Unary>(); curr->op = TruncUFloat64ToInt32; curr->type = i32; break;
case BinaryConsts::I32STruncF32: curr = allocator.alloc<Unary>(); curr->op = TruncSFloat32ToInt32; curr->type = i32; break;
case BinaryConsts::I32STruncF64: curr = allocator.alloc<Unary>(); curr->op = TruncSFloat64ToInt32; curr->type = i32; break;
case BinaryConsts::I64UTruncF32: curr = allocator.alloc<Unary>(); curr->op = TruncUFloat32ToInt64; curr->type = i64; break;
case BinaryConsts::I64UTruncF64: curr = allocator.alloc<Unary>(); curr->op = TruncUFloat64ToInt64; curr->type = i64; break;
case BinaryConsts::I64STruncF32: curr = allocator.alloc<Unary>(); curr->op = TruncSFloat32ToInt64; curr->type = i64; break;
case BinaryConsts::I64STruncF64: curr = allocator.alloc<Unary>(); curr->op = TruncSFloat64ToInt64; curr->type = i64; break;
case BinaryConsts::F32Trunc: curr = allocator.alloc<Unary>(); curr->op = TruncFloat32; curr->type = f32; break;
case BinaryConsts::F64Trunc: curr = allocator.alloc<Unary>(); curr->op = TruncFloat64; curr->type = f64; break;
case BinaryConsts::F32ConvertF64: curr = allocator.alloc<Unary>(); curr->op = DemoteFloat64; curr->type = f32; break;
case BinaryConsts::F64ConvertF32: curr = allocator.alloc<Unary>(); curr->op = PromoteFloat32; curr->type = f64; break;
case BinaryConsts::I32ReinterpretF32: curr = allocator.alloc<Unary>(); curr->op = ReinterpretFloat32; curr->type = i32; break;
case BinaryConsts::I64ReinterpretF64: curr = allocator.alloc<Unary>(); curr->op = ReinterpretFloat64; curr->type = i64; break;
case BinaryConsts::F32ReinterpretI32: curr = allocator.alloc<Unary>(); curr->op = ReinterpretInt32; curr->type = f32; break;
case BinaryConsts::F64ReinterpretI64: curr = allocator.alloc<Unary>(); curr->op = ReinterpretInt64; curr->type = f64; break;
default: return false;
}
if (debug) std::cerr << "zz node: Unary" << std::endl;
curr->value = popNonVoidExpression();
curr->finalize();
out = curr;
return true;
}
bool WasmBinaryBuilder::maybeVisitBinary(Expression*& out, uint8_t code) {
Binary* curr;
#define INT_TYPED_CODE(code) { \
case BinaryConsts::I32##code: curr = allocator.alloc<Binary>(); curr->op = code##Int32; curr->type = i32; break; \
case BinaryConsts::I64##code: curr = allocator.alloc<Binary>(); curr->op = code##Int64; curr->type = i64; break; \
}
#define FLOAT_TYPED_CODE(code) { \
case BinaryConsts::F32##code: curr = allocator.alloc<Binary>(); curr->op = code##Float32; curr->type = f32; break; \
case BinaryConsts::F64##code: curr = allocator.alloc<Binary>(); curr->op = code##Float64; curr->type = f64; break; \
}
#define TYPED_CODE(code) { \
INT_TYPED_CODE(code) \
FLOAT_TYPED_CODE(code) \
}
switch (code) {
TYPED_CODE(Add);
TYPED_CODE(Sub);
TYPED_CODE(Mul);
INT_TYPED_CODE(DivS);
INT_TYPED_CODE(DivU);
INT_TYPED_CODE(RemS);
INT_TYPED_CODE(RemU);
INT_TYPED_CODE(And);
INT_TYPED_CODE(Or);
INT_TYPED_CODE(Xor);
INT_TYPED_CODE(Shl);
INT_TYPED_CODE(ShrU);
INT_TYPED_CODE(ShrS);
INT_TYPED_CODE(RotL);
INT_TYPED_CODE(RotR);
FLOAT_TYPED_CODE(Div);
FLOAT_TYPED_CODE(CopySign);
FLOAT_TYPED_CODE(Min);
FLOAT_TYPED_CODE(Max);
TYPED_CODE(Eq);
TYPED_CODE(Ne);
INT_TYPED_CODE(LtS);
INT_TYPED_CODE(LtU);
INT_TYPED_CODE(LeS);
INT_TYPED_CODE(LeU);
INT_TYPED_CODE(GtS);
INT_TYPED_CODE(GtU);
INT_TYPED_CODE(GeS);
INT_TYPED_CODE(GeU);
FLOAT_TYPED_CODE(Lt);
FLOAT_TYPED_CODE(Le);
FLOAT_TYPED_CODE(Gt);
FLOAT_TYPED_CODE(Ge);
default: return false;
}
if (debug) std::cerr << "zz node: Binary" << std::endl;
curr->right = popNonVoidExpression();
curr->left = popNonVoidExpression();
curr->finalize();
out = curr;
return true;
#undef TYPED_CODE
#undef INT_TYPED_CODE
#undef FLOAT_TYPED_CODE
}
void WasmBinaryBuilder::visitSelect(Select *curr) {
if (debug) std::cerr << "zz node: Select" << std::endl;
curr->condition = popNonVoidExpression();
curr->ifFalse = popNonVoidExpression();
curr->ifTrue = popNonVoidExpression();
curr->finalize();
}
void WasmBinaryBuilder::visitReturn(Return *curr) {
if (debug) std::cerr << "zz node: Return" << std::endl;
if (!currFunction) {
throw ParseException("return outside of function");
}
if (currFunction->result != none) {
curr->value = popNonVoidExpression();
}
curr->finalize();
}
bool WasmBinaryBuilder::maybeVisitHost(Expression*& out, uint8_t code) {
Host* curr;
switch (code) {
case BinaryConsts::CurrentMemory: {
curr = allocator.alloc<Host>();
curr->op = CurrentMemory;
curr->type = i32;
break;
}
case BinaryConsts::GrowMemory: {
curr = allocator.alloc<Host>();
curr->op = GrowMemory;
curr->operands.resize(1);
curr->operands[0] = popNonVoidExpression();
break;
}
default: return false;
}
if (debug) std::cerr << "zz node: Host" << std::endl;
auto reserved = getU32LEB();
if (reserved != 0) throw ParseException("Invalid reserved field on grow_memory/current_memory");
curr->finalize();
out = curr;
return true;
}
void WasmBinaryBuilder::visitNop(Nop *curr) {
if (debug) std::cerr << "zz node: Nop" << std::endl;
}
void WasmBinaryBuilder::visitUnreachable(Unreachable *curr) {
if (debug) std::cerr << "zz node: Unreachable" << std::endl;
}
void WasmBinaryBuilder::visitDrop(Drop *curr) {
if (debug) std::cerr << "zz node: Drop" << std::endl;
curr->value = popNonVoidExpression();
curr->finalize();
}
} // namespace wasm