| /* |
| * Copyright 2015 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. |
| */ |
| |
| // |
| // Parses and emits WebAssembly binary code |
| // |
| |
| #ifndef wasm_wasm_binary_h |
| #define wasm_wasm_binary_h |
| |
| #include <cassert> |
| #include <istream> |
| #include <ostream> |
| #include <type_traits> |
| |
| #include "wasm.h" |
| #include "wasm-traversal.h" |
| #include "asmjs/shared-constants.h" |
| #include "asm_v_wasm.h" |
| #include "wasm-builder.h" |
| #include "ast_utils.h" |
| #include "parsing.h" |
| #include "wasm-validator.h" |
| |
| namespace wasm { |
| |
| template<typename T, typename MiniT> |
| struct LEB { |
| static_assert(sizeof(MiniT) == 1, "MiniT must be a byte"); |
| |
| T value; |
| |
| LEB() {} |
| LEB(T value) : value(value) {} |
| |
| bool hasMore(T temp, MiniT byte) { |
| // for signed, we must ensure the last bit has the right sign, as it will zero extend |
| return std::is_signed<T>::value ? (temp != 0 && int32_t(temp) != -1) || (value >= 0 && (byte & 64)) || (value < 0 && !(byte & 64)): (temp != 0); |
| } |
| |
| void write(std::vector<uint8_t>* out) { |
| T temp = value; |
| bool more; |
| do { |
| uint8_t byte = temp & 127; |
| temp >>= 7; |
| more = hasMore(temp, byte); |
| if (more) { |
| byte = byte | 128; |
| } |
| out->push_back(byte); |
| } while (more); |
| } |
| |
| void writeAt(std::vector<uint8_t>* out, size_t at, size_t minimum = 0) { |
| T temp = value; |
| size_t offset = 0; |
| bool more; |
| do { |
| uint8_t byte = temp & 127; |
| temp >>= 7; |
| more = hasMore(temp, byte) || offset + 1 < minimum; |
| if (more) { |
| byte = byte | 128; |
| } |
| (*out)[at + offset] = byte; |
| offset++; |
| } while (more); |
| } |
| |
| void read(std::function<MiniT()> get) { |
| value = 0; |
| T shift = 0; |
| MiniT byte; |
| while (1) { |
| byte = get(); |
| bool last = !(byte & 128); |
| T payload = byte & 127; |
| typedef typename std::make_unsigned<T>::type mask_type; |
| auto shift_mask = 0 == shift |
| ? ~mask_type(0) |
| : ((mask_type(1) << (sizeof(T) * 8 - shift)) - 1u); |
| T significant_payload = payload & shift_mask; |
| if (significant_payload != payload) { |
| assert(std::is_signed<T>::value && last && |
| "dropped bits only valid for signed LEB"); |
| } |
| value |= significant_payload << shift; |
| if (last) break; |
| shift += 7; |
| assert(size_t(shift) < sizeof(T) * 8 && "LEB overflow"); |
| } |
| // If signed LEB, then we might need to sign-extend. (compile should |
| // optimize this out if not needed). |
| if (std::is_signed<T>::value) { |
| shift += 7; |
| if ((byte & 64) && size_t(shift) < 8 * sizeof(T)) { |
| size_t sext_bits = 8 * sizeof(T) - size_t(shift); |
| value <<= sext_bits; |
| value >>= sext_bits; |
| assert(value < 0 && "sign-extend should produces a negative value"); |
| } |
| } |
| } |
| }; |
| |
| typedef LEB<uint32_t, uint8_t> U32LEB; |
| typedef LEB<uint64_t, uint8_t> U64LEB; |
| typedef LEB<int32_t, int8_t> S32LEB; |
| typedef LEB<int64_t, int8_t> S64LEB; |
| |
| // |
| // We mostly stream into a buffer as we create the binary format, however, |
| // sometimes we need to backtrack and write to a location behind us - wasm |
| // is optimized for reading, not writing. |
| // |
| class BufferWithRandomAccess : public std::vector<uint8_t> { |
| bool debug; |
| |
| public: |
| BufferWithRandomAccess(bool debug) : debug(debug) {} |
| |
| BufferWithRandomAccess& operator<<(int8_t x) { |
| if (debug) std::cerr << "writeInt8: " << (int)(uint8_t)x << " (at " << size() << ")" << std::endl; |
| push_back(x); |
| return *this; |
| } |
| BufferWithRandomAccess& operator<<(int16_t x) { |
| if (debug) std::cerr << "writeInt16: " << x << " (at " << size() << ")" << std::endl; |
| push_back(x & 0xff); |
| push_back(x >> 8); |
| return *this; |
| } |
| BufferWithRandomAccess& operator<<(int32_t x) { |
| if (debug) std::cerr << "writeInt32: " << x << " (at " << size() << ")" << std::endl; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); |
| return *this; |
| } |
| BufferWithRandomAccess& operator<<(int64_t x) { |
| if (debug) std::cerr << "writeInt64: " << x << " (at " << size() << ")" << std::endl; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); x >>= 8; |
| push_back(x & 0xff); |
| return *this; |
| } |
| BufferWithRandomAccess& operator<<(U32LEB x) { |
| if (debug) std::cerr << "writeU32LEB: " << x.value << " (at " << size() << ")" << std::endl; |
| x.write(this); |
| return *this; |
| } |
| BufferWithRandomAccess& operator<<(U64LEB x) { |
| if (debug) std::cerr << "writeU64LEB: " << x.value << " (at " << size() << ")" << std::endl; |
| x.write(this); |
| return *this; |
| } |
| BufferWithRandomAccess& operator<<(S32LEB x) { |
| if (debug) std::cerr << "writeS32LEB: " << x.value << " (at " << size() << ")" << std::endl; |
| x.write(this); |
| return *this; |
| } |
| BufferWithRandomAccess& operator<<(S64LEB x) { |
| if (debug) std::cerr << "writeS64LEB: " << x.value << " (at " << size() << ")" << std::endl; |
| x.write(this); |
| return *this; |
| } |
| |
| BufferWithRandomAccess& operator<<(uint8_t x) { |
| return *this << (int8_t)x; |
| } |
| BufferWithRandomAccess& operator<<(uint16_t x) { |
| return *this << (int16_t)x; |
| } |
| BufferWithRandomAccess& operator<<(uint32_t x) { |
| return *this << (int32_t)x; |
| } |
| BufferWithRandomAccess& operator<<(uint64_t x) { |
| return *this << (int64_t)x; |
| } |
| |
| BufferWithRandomAccess& operator<<(float x) { |
| if (debug) std::cerr << "writeFloat32: " << x << " (at " << size() << ")" << std::endl; |
| return *this << Literal(x).reinterpreti32(); |
| } |
| BufferWithRandomAccess& operator<<(double x) { |
| if (debug) std::cerr << "writeFloat64: " << x << " (at " << size() << ")" << std::endl; |
| return *this << Literal(x).reinterpreti64(); |
| } |
| |
| void writeAt(size_t i, uint16_t x) { |
| if (debug) std::cerr << "backpatchInt16: " << x << " (at " << i << ")" << std::endl; |
| (*this)[i] = x & 0xff; |
| (*this)[i+1] = x >> 8; |
| } |
| void writeAt(size_t i, uint32_t x) { |
| if (debug) std::cerr << "backpatchInt32: " << x << " (at " << i << ")" << std::endl; |
| (*this)[i] = x & 0xff; x >>= 8; |
| (*this)[i+1] = x & 0xff; x >>= 8; |
| (*this)[i+2] = x & 0xff; x >>= 8; |
| (*this)[i+3] = x & 0xff; |
| } |
| void writeAt(size_t i, U32LEB x) { |
| if (debug) std::cerr << "backpatchU32LEB: " << x.value << " (at " << i << ")" << std::endl; |
| x.writeAt(this, i, 5); // fill all 5 bytes, we have to do this when backpatching |
| } |
| |
| template <typename T> |
| void writeTo(T& o) { |
| for (auto c : *this) o << c; |
| } |
| }; |
| |
| namespace BinaryConsts { |
| |
| enum Meta { |
| Magic = 0x6d736100, |
| Version = 11 |
| }; |
| |
| namespace Section { |
| auto Memory = "memory"; |
| auto Signatures = "type"; |
| auto ImportTable = "import"; |
| auto FunctionSignatures = "function"; |
| auto Functions = "code"; |
| auto ExportTable = "export"; |
| auto DataSegments = "data"; |
| auto FunctionTable = "table"; |
| auto Names = "name"; |
| auto Start = "start"; |
| }; |
| |
| enum FunctionEntry { |
| Named = 1, |
| Import = 2, |
| Locals = 4, |
| Export = 8 |
| }; |
| |
| enum ASTNodes { |
| CurrentMemory = 0x3b, |
| GrowMemory = 0x39, |
| I32Add = 0x40, |
| I32Sub = 0x41, |
| I32Mul = 0x42, |
| I32DivS = 0x43, |
| I32DivU = 0x44, |
| I32RemS = 0x45, |
| I32RemU = 0x46, |
| I32And = 0x47, |
| I32Or = 0x48, |
| I32Xor = 0x49, |
| I32Shl = 0x4a, |
| I32ShrU = 0x4b, |
| I32ShrS = 0x4c, |
| I32Eq = 0x4d, |
| I32Ne = 0x4e, |
| I32LtS = 0x4f, |
| I32LeS = 0x50, |
| I32LtU = 0x51, |
| I32LeU = 0x52, |
| I32GtS = 0x53, |
| I32GeS = 0x54, |
| I32GtU = 0x55, |
| I32GeU = 0x56, |
| I32Clz = 0x57, |
| I32Ctz = 0x58, |
| I32Popcnt = 0x59, |
| I32EqZ = 0x5a, |
| I64Add = 0x5b, |
| I64Sub = 0x5c, |
| I64Mul = 0x5d, |
| I64DivS = 0x5e, |
| I64DivU = 0x5f, |
| I64RemS = 0x60, |
| I64RemU = 0x61, |
| I64And = 0x62, |
| I64Or = 0x63, |
| I64Xor = 0x64, |
| I64Shl = 0x65, |
| I64ShrU = 0x66, |
| I64ShrS = 0x67, |
| I64Eq = 0x68, |
| I64Ne = 0x69, |
| I64LtS = 0x6a, |
| I64LeS = 0x6b, |
| I64LtU = 0x6c, |
| I64LeU = 0x6d, |
| I64GtS = 0x6e, |
| I64GeS = 0x6f, |
| I64GtU = 0x70, |
| I64GeU = 0x71, |
| I64Clz = 0x72, |
| I64Ctz = 0x73, |
| I64Popcnt = 0x74, |
| I64EqZ = 0xba, |
| F32Add = 0x75, |
| F32Sub = 0x76, |
| F32Mul = 0x77, |
| F32Div = 0x78, |
| F32Min = 0x79, |
| F32Max = 0x7a, |
| F32Abs = 0x7b, |
| F32Neg = 0x7c, |
| F32CopySign = 0x7d, |
| F32Ceil = 0x7e, |
| F32Floor = 0x7f, |
| F32Trunc = 0x80, |
| F32NearestInt = 0x81, |
| F32Sqrt = 0x82, |
| F32Eq = 0x83, |
| F32Ne = 0x84, |
| F32Lt = 0x85, |
| F32Le = 0x86, |
| F32Gt = 0x87, |
| F32Ge = 0x88, |
| F64Add = 0x89, |
| F64Sub = 0x8a, |
| F64Mul = 0x8b, |
| F64Div = 0x8c, |
| F64Min = 0x8d, |
| F64Max = 0x8e, |
| F64Abs = 0x8f, |
| F64Neg = 0x90, |
| F64CopySign = 0x91, |
| F64Ceil = 0x92, |
| F64Floor = 0x93, |
| F64Trunc = 0x94, |
| F64NearestInt = 0x95, |
| F64Sqrt = 0x96, |
| F64Eq = 0x97, |
| F64Ne = 0x98, |
| F64Lt = 0x99, |
| F64Le = 0x9a, |
| F64Gt = 0x9b, |
| F64Ge = 0x9c, |
| |
| I32STruncF32 = 0x9d, |
| I32STruncF64 = 0x9e, |
| I32UTruncF32 = 0x9f, |
| I32UTruncF64 = 0xa0, |
| I32ConvertI64 = 0xa1, |
| I64STruncF32 = 0xa2, |
| I64STruncF64 = 0xa3, |
| I64UTruncF32 = 0xa4, |
| I64UTruncF64 = 0xa5, |
| I64STruncI32 = 0xa6, |
| I64UTruncI32 = 0xa7, |
| F32SConvertI32 = 0xa8, |
| F32UConvertI32 = 0xa9, |
| F32SConvertI64 = 0xaa, |
| F32UConvertI64 = 0xab, |
| F32ConvertF64 = 0xac, |
| F32ReinterpretI32 = 0xad, |
| F64SConvertI32 = 0xae, |
| F64UConvertI32 = 0xaf, |
| F64SConvertI64 = 0xb0, |
| F64UConvertI64 = 0xb1, |
| F64ConvertF32 = 0xb2, |
| F64ReinterpretI64 = 0xb3, |
| I32ReinterpretF32 = 0xb4, |
| I64ReinterpretF64 = 0xb5, |
| I32RotR = 0xb6, |
| I32RotL = 0xb7, |
| I64RotR = 0xb8, |
| I64RotL = 0xb9, |
| |
| I32LoadMem8S = 0x20, |
| I32LoadMem8U = 0x21, |
| I32LoadMem16S = 0x22, |
| I32LoadMem16U = 0x23, |
| I64LoadMem8S = 0x24, |
| I64LoadMem8U = 0x25, |
| I64LoadMem16S = 0x26, |
| I64LoadMem16U = 0x27, |
| I64LoadMem32S = 0x28, |
| I64LoadMem32U = 0x29, |
| I32LoadMem = 0x2a, |
| I64LoadMem = 0x2b, |
| F32LoadMem = 0x2c, |
| F64LoadMem = 0x2d, |
| I32StoreMem8 = 0x2e, |
| I32StoreMem16 = 0x2f, |
| I64StoreMem8 = 0x30, |
| I64StoreMem16 = 0x31, |
| I64StoreMem32 = 0x32, |
| I32StoreMem = 0x33, |
| I64StoreMem = 0x34, |
| F32StoreMem = 0x35, |
| F64StoreMem = 0x36, |
| |
| I32Const = 0x10, |
| I64Const = 0x11, |
| F64Const = 0x12, |
| F32Const = 0x13, |
| GetLocal = 0x14, |
| SetLocal = 0x15, |
| CallFunction = 0x16, |
| CallIndirect = 0x17, |
| CallImport = 0x18, |
| |
| Nop = 0x00, |
| Block = 0x01, |
| Loop = 0x02, |
| If = 0x03, |
| Else = 0x04, |
| Select = 0x05, |
| Br = 0x06, |
| BrIf = 0x07, |
| TableSwitch = 0x08, |
| Return = 0x09, |
| Unreachable = 0x0a, |
| End = 0x0f |
| }; |
| |
| enum MemoryAccess { |
| Offset = 0x10, // bit 4 |
| Alignment = 0x80, // bit 7 |
| NaturalAlignment = 0 |
| }; |
| |
| enum TypeForms { |
| Basic = 0x40 |
| }; |
| |
| } // namespace BinaryConsts |
| |
| int8_t binaryWasmType(WasmType type) { |
| switch (type) { |
| case none: return 0; |
| case i32: return 1; |
| case i64: return 2; |
| case f32: return 3; |
| case f64: return 4; |
| default: abort(); |
| } |
| } |
| |
| class WasmBinaryWriter : public Visitor<WasmBinaryWriter, void> { |
| Module* wasm; |
| BufferWithRandomAccess& o; |
| bool debug; |
| |
| MixedArena allocator; |
| |
| void 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; |
| } |
| } |
| } |
| |
| public: |
| WasmBinaryWriter(Module* input, BufferWithRandomAccess& o, bool debug) : wasm(input), o(o), debug(debug) { |
| prepare(); |
| } |
| |
| void write() { |
| writeHeader(); |
| |
| writeSignatures(); |
| writeImports(); |
| writeFunctionSignatures(); |
| writeFunctionTable(); |
| writeMemory(); |
| writeExports(); |
| writeStart(); |
| writeFunctions(); |
| writeDataSegments(); |
| writeNames(); |
| |
| finishUp(); |
| } |
| |
| void writeHeader() { |
| if (debug) std::cerr << "== writeHeader" << std::endl; |
| o << int32_t(BinaryConsts::Magic); // magic number \0asm |
| o << int32_t(BinaryConsts::Version); |
| } |
| |
| int32_t writeU32LEBPlaceholder() { |
| int32_t ret = o.size(); |
| o << int32_t(0); |
| o << int8_t(0); |
| return ret; |
| } |
| |
| int32_t startSection(const char* name) { |
| // emit 5 bytes of 0, which we'll fill with LEB later |
| writeInlineString(name); |
| return writeU32LEBPlaceholder(); |
| } |
| |
| void 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)); |
| } |
| |
| void 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 writeMemory() { |
| if (wasm->memory.max == 0) return; |
| if (debug) std::cerr << "== writeMemory" << std::endl; |
| auto start = startSection(BinaryConsts::Section::Memory); |
| o << U32LEB(wasm->memory.initial) |
| << U32LEB(wasm->memory.max) |
| << int8_t(wasm->memory.exportName.is()); // export memory |
| finishSection(start); |
| } |
| |
| void writeSignatures() { |
| if (wasm->functionTypes.size() == 0) return; |
| if (debug) std::cerr << "== writeSignatures" << std::endl; |
| auto start = startSection(BinaryConsts::Section::Signatures); |
| o << U32LEB(wasm->functionTypes.size()); |
| for (auto& type : wasm->functionTypes) { |
| if (debug) std::cerr << "write one" << std::endl; |
| o << int8_t(BinaryConsts::TypeForms::Basic); |
| 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 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 writeImports() { |
| if (wasm->imports.size() == 0) return; |
| if (debug) std::cerr << "== writeImports" << std::endl; |
| auto start = startSection(BinaryConsts::Section::ImportTable); |
| o << U32LEB(wasm->imports.size()); |
| for (auto& import : wasm->imports) { |
| if (debug) std::cerr << "write one" << std::endl; |
| o << U32LEB(getFunctionTypeIndex(import->type->name)); |
| writeInlineString(import->module.str); |
| writeInlineString(import->base.str); |
| } |
| finishSection(start); |
| } |
| |
| std::map<Index, size_t> mappedLocals; // local index => index in compact form of [all int32s][all int64s]etc |
| std::map<WasmType, size_t> numLocalsByType; // type => number of locals of that type in the compact form |
| |
| void 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 writeFunctionSignatures() { |
| if (wasm->functions.size() == 0) return; |
| if (debug) std::cerr << "== writeFunctionSignatures" << std::endl; |
| auto start = startSection(BinaryConsts::Section::FunctionSignatures); |
| 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 writeFunctions() { |
| if (wasm->functions.size() == 0) return; |
| if (debug) std::cerr << "== writeFunctions" << std::endl; |
| auto start = startSection(BinaryConsts::Section::Functions); |
| 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->getFunction(i); |
| 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); |
| depth = 0; |
| recurse(function->body); |
| assert(depth == 0); |
| 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)); |
| } |
| finishSection(start); |
| } |
| |
| void writeExports() { |
| if (wasm->exports.size() == 0) return; |
| if (debug) std::cerr << "== writeexports" << std::endl; |
| auto start = startSection(BinaryConsts::Section::ExportTable); |
| o << U32LEB(wasm->exports.size()); |
| for (auto& curr : wasm->exports) { |
| if (debug) std::cerr << "write one" << std::endl; |
| o << U32LEB(getFunctionIndex(curr->value)); |
| writeInlineString(curr->name.str); |
| } |
| finishSection(start); |
| } |
| |
| void 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::DataSegments); |
| o << U32LEB(num); |
| for (auto& segment : wasm->memory.segments) { |
| if (segment.data.size() == 0) continue; |
| o << U32LEB(segment.offset); |
| writeInlineBuffer(&segment.data[0], segment.data.size()); |
| } |
| finishSection(start); |
| } |
| |
| std::map<Name, uint32_t> mappedImports; // name of the Import => index |
| uint32_t getImportIndex(Name name) { |
| if (!mappedImports.size()) { |
| // Create name => index mapping. |
| for (size_t i = 0; i < wasm->imports.size(); i++) { |
| assert(mappedImports.count(wasm->imports[i]->name) == 0); |
| mappedImports[wasm->imports[i]->name] = i; |
| } |
| } |
| assert(mappedImports.count(name)); |
| return mappedImports[name]; |
| } |
| |
| std::map<Name, uint32_t> mappedFunctions; // name of the Function => index |
| uint32_t getFunctionIndex(Name name) { |
| if (!mappedFunctions.size()) { |
| // Create name => index mapping. |
| for (size_t i = 0; i < wasm->functions.size(); i++) { |
| assert(mappedFunctions.count(wasm->functions[i]->name) == 0); |
| mappedFunctions[wasm->functions[i]->name] = i; |
| } |
| } |
| assert(mappedFunctions.count(name)); |
| return mappedFunctions[name]; |
| } |
| |
| void writeFunctionTable() { |
| if (wasm->table.names.size() == 0) return; |
| if (debug) std::cerr << "== writeFunctionTable" << std::endl; |
| auto start = startSection(BinaryConsts::Section::FunctionTable); |
| o << U32LEB(wasm->table.names.size()); |
| for (auto name : wasm->table.names) { |
| o << U32LEB(getFunctionIndex(name)); |
| } |
| finishSection(start); |
| } |
| |
| void writeNames() { |
| if (wasm->functions.size() == 0) return; |
| if (debug) std::cerr << "== writeNames" << std::endl; |
| auto start = startSection(BinaryConsts::Section::Names); |
| o << U32LEB(wasm->functions.size()); |
| for (auto& curr : wasm->functions) { |
| writeInlineString(curr->name.str); |
| o << U32LEB(0); // TODO: locals |
| } |
| finishSection(start); |
| } |
| |
| // helpers |
| |
| void 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 writeInlineBuffer(const char* data, size_t size) { |
| o << U32LEB(size); |
| for (size_t i = 0; i < size; i++) { |
| o << int8_t(data[i]); |
| } |
| } |
| |
| struct Buffer { |
| const char* data; |
| size_t size; |
| size_t pointerLocation; |
| Buffer(const char* data, size_t size, size_t pointerLocation) : data(data), size(size), pointerLocation(pointerLocation) {} |
| }; |
| |
| std::vector<Buffer> buffersToWrite; |
| |
| void 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 emitString(const char *str) { |
| if (debug) std::cerr << "emitString " << str << std::endl; |
| emitBuffer(str, strlen(str) + 1); |
| } |
| |
| void 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]; |
| } |
| } |
| } |
| |
| // AST writing via visitors |
| |
| int depth; // only for debugging |
| |
| void 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; |
| } |
| |
| std::vector<Name> breakStack; |
| |
| void visitBlock(Block *curr) { |
| if (debug) std::cerr << "zz node: Block" << std::endl; |
| o << int8_t(BinaryConsts::Block); |
| breakStack.push_back(curr->name); |
| size_t 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(); |
| o << int8_t(BinaryConsts::End); |
| } |
| |
| // emits a node, but if it is a block with no name, emit a list of its contents |
| void recursePossibleBlockContents(Expression* curr) { |
| auto* block = curr->dynCast<Block>(); |
| if (!block || (block->name.is() && BreakSeeker::has(curr, block->name))) { |
| recurse(curr); |
| return; |
| } |
| for (auto* child : block->list) { |
| recurse(child); |
| } |
| } |
| |
| void visitIf(If *curr) { |
| if (debug) std::cerr << "zz node: If" << std::endl; |
| recurse(curr->condition); |
| o << int8_t(BinaryConsts::If); |
| 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); |
| } |
| void visitLoop(Loop *curr) { |
| if (debug) std::cerr << "zz node: Loop" << std::endl; |
| o << int8_t(BinaryConsts::Loop); |
| breakStack.push_back(curr->out); |
| breakStack.push_back(curr->in); |
| recursePossibleBlockContents(curr->body); |
| breakStack.pop_back(); |
| breakStack.pop_back(); |
| o << int8_t(BinaryConsts::End); |
| } |
| |
| int32_t 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 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(curr->value ? 1 : 0) << U32LEB(getBreakIndex(curr->name)); |
| } |
| void 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->value ? 1 : 0) << U32LEB(curr->targets.size()); |
| for (auto target : curr->targets) { |
| o << uint32_t(getBreakIndex(target)); |
| } |
| o << uint32_t(getBreakIndex(curr->default_)); |
| } |
| void 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(curr->operands.size()) << U32LEB(getFunctionIndex(curr->target)); |
| } |
| void visitCallImport(CallImport *curr) { |
| if (debug) std::cerr << "zz node: CallImport" << std::endl; |
| for (auto* operand : curr->operands) { |
| recurse(operand); |
| } |
| o << int8_t(BinaryConsts::CallImport) << U32LEB(curr->operands.size()) << U32LEB(getImportIndex(curr->target)); |
| } |
| void visitCallIndirect(CallIndirect *curr) { |
| if (debug) std::cerr << "zz node: CallIndirect" << std::endl; |
| recurse(curr->target); |
| for (auto* operand : curr->operands) { |
| recurse(operand); |
| } |
| o << int8_t(BinaryConsts::CallIndirect) << U32LEB(curr->operands.size()) << U32LEB(getFunctionTypeIndex(curr->fullType)); |
| } |
| void 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 visitSetLocal(SetLocal *curr) { |
| if (debug) std::cerr << "zz node: SetLocal" << std::endl; |
| recurse(curr->value); |
| o << int8_t(BinaryConsts::SetLocal) << U32LEB(mappedLocals[curr->index]); |
| } |
| |
| void emitMemoryAccess(size_t alignment, size_t bytes, uint32_t offset) { |
| o << U32LEB(Log2(alignment ? alignment : bytes)); |
| o << U32LEB(offset); |
| } |
| |
| void 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; |
| default: abort(); |
| } |
| emitMemoryAccess(curr->align, curr->bytes, curr->offset); |
| } |
| void visitStore(Store *curr) { |
| if (debug) std::cerr << "zz node: Store" << std::endl; |
| recurse(curr->ptr); |
| recurse(curr->value); |
| switch (curr->type) { |
| 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 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.getf32(); |
| break; |
| } |
| case f64: { |
| o << int8_t(BinaryConsts::F64Const) << curr->value.getf64(); |
| break; |
| } |
| default: abort(); |
| } |
| if (debug) std::cerr << "zz const node done.\n"; |
| } |
| void 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 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 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 visitReturn(Return *curr) { |
| if (debug) std::cerr << "zz node: Return" << std::endl; |
| if (curr->value) { |
| recurse(curr->value); |
| } |
| o << int8_t(BinaryConsts::Return) << U32LEB(curr->value ? 1 : 0); |
| } |
| void 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(); |
| } |
| } |
| void visitNop(Nop *curr) { |
| if (debug) std::cerr << "zz node: Nop" << std::endl; |
| o << int8_t(BinaryConsts::Nop); |
| } |
| void visitUnreachable(Unreachable *curr) { |
| if (debug) std::cerr << "zz node: Unreachable" << std::endl; |
| o << int8_t(BinaryConsts::Unreachable); |
| } |
| }; |
| |
| class WasmBinaryBuilder { |
| Module& wasm; |
| MixedArena& allocator; |
| std::vector<char>& input; |
| bool debug; |
| |
| size_t pos = 0; |
| Index startIndex = -1; |
| |
| public: |
| WasmBinaryBuilder(Module& wasm, std::vector<char>& input, bool debug) : wasm(wasm), allocator(wasm.allocator), input(input), debug(debug) {} |
| |
| void read() { |
| |
| readHeader(); |
| |
| // read sections until the end |
| while (more()) { |
| auto nameSize = getU32LEB(); |
| uint32_t sectionSize, before; |
| auto match = [&](const char* name) { |
| for (size_t i = 0; i < nameSize; i++) { |
| if (pos + i >= input.size()) return false; |
| if (name[i] == 0) return false; |
| if (input[pos + i] != name[i]) return false; |
| } |
| if (strlen(name) != nameSize) return false; |
| // name matched, read section size and then section itself |
| pos += nameSize; |
| sectionSize = getU32LEB(); |
| before = pos; |
| assert(pos + sectionSize <= input.size()); |
| return true; |
| }; |
| if (match(BinaryConsts::Section::Start)) readStart(); |
| else if (match(BinaryConsts::Section::Memory)) readMemory(); |
| else if (match(BinaryConsts::Section::Signatures)) readSignatures(); |
| else if (match(BinaryConsts::Section::ImportTable)) readImports(); |
| else if (match(BinaryConsts::Section::FunctionSignatures)) readFunctionSignatures(); |
| else if (match(BinaryConsts::Section::Functions)) readFunctions(); |
| else if (match(BinaryConsts::Section::ExportTable)) readExports(); |
| else if (match(BinaryConsts::Section::DataSegments)) readDataSegments(); |
| else if (match(BinaryConsts::Section::FunctionTable)) readFunctionTable(); |
| else if (match(BinaryConsts::Section::Names)) readNames(); |
| else { |
| std::cerr << "unfamiliar section: "; |
| assert(pos + nameSize - 1 < input.size()); |
| for (size_t i = 0; i < nameSize; i++) std::cerr << input[pos + i]; |
| std::cerr << std::endl; |
| abort(); |
| } |
| assert(pos == before + sectionSize); |
| } |
| |
| processFunctions(); |
| } |
| |
| bool more() { |
| return pos < input.size(); |
| } |
| |
| uint8_t 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 getInt16() { |
| if (debug) std::cerr << "<==" << std::endl; |
| auto ret = uint16_t(getInt8()) | (uint16_t(getInt8()) << 8); |
| if (debug) std::cerr << "getInt16: " << ret << " ==>" << std::endl; |
| return ret; |
| } |
| uint32_t getInt32() { |
| if (debug) std::cerr << "<==" << std::endl; |
| auto ret = uint32_t(getInt16()) | (uint32_t(getInt16()) << 16); |
| if (debug) std::cerr << "getInt32: " << ret << " ==>" << std::endl; |
| return ret; |
| } |
| uint64_t getInt64() { |
| if (debug) std::cerr << "<==" << std::endl; |
| auto ret = uint64_t(getInt32()) | (uint64_t(getInt32()) << 32); |
| if (debug) std::cerr << "getInt64: " << ret << " ==>" << std::endl; |
| return ret; |
| } |
| float getFloat32() { |
| if (debug) std::cerr << "<==" << std::endl; |
| auto ret = Literal(getInt32()).reinterpretf32(); |
| if (debug) std::cerr << "getFloat32: " << ret << " ==>" << std::endl; |
| return ret; |
| } |
| double getFloat64() { |
| if (debug) std::cerr << "<==" << std::endl; |
| auto ret = Literal(getInt64()).reinterpretf64(); |
| if (debug) std::cerr << "getFloat64: " << ret << " ==>" << std::endl; |
| return ret; |
| } |
| |
| uint32_t 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 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 getS32LEB() { |
| if (debug) std::cerr << "<==" << std::endl; |
| S32LEB ret; |
| ret.read([&]() { |
| return (int8_t)getInt8(); |
| }); |
| if (debug) std::cerr << "getU32LEB: " << ret.value << " ==>" << std::endl; |
| return ret.value; |
| } |
| int64_t getS64LEB() { |
| if (debug) std::cerr << "<==" << std::endl; |
| S64LEB ret; |
| ret.read([&]() { |
| return (int8_t)getInt8(); |
| }); |
| if (debug) std::cerr << "getU64LEB: " << ret.value << " ==>" << std::endl; |
| return ret.value; |
| } |
| WasmType getWasmType() { |
| int8_t type = getInt8(); |
| switch (type) { |
| case 0: return none; |
| case 1: return i32; |
| case 2: return i64; |
| case 3: return f32; |
| case 4: return f64; |
| default: abort(); |
| } |
| } |
| |
| Name 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 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 verifyInt8(int8_t x) { |
| int8_t y = getInt8(); |
| if (x != y) throw ParseException("surprising value", 0, pos); |
| } |
| void verifyInt16(int16_t x) { |
| int16_t y = getInt16(); |
| if (x != y) throw ParseException("surprising value", 0, pos); |
| } |
| void verifyInt32(int32_t x) { |
| int32_t y = getInt32(); |
| if (x != y) throw ParseException("surprising value", 0, pos); |
| } |
| void verifyInt64(int64_t x) { |
| int64_t y = getInt64(); |
| if (x != y) throw ParseException("surprising value", 0, pos); |
| } |
| void verifyFloat32(float x) { |
| float y = getFloat32(); |
| if (x != y) throw ParseException("surprising value", 0, pos); |
| } |
| void verifyFloat64(double x) { |
| double y = getFloat64(); |
| if (x != y) throw ParseException("surprising value", 0, pos); |
| } |
| |
| void ungetInt8() { |
| assert(pos > 0); |
| if (debug) std::cerr << "ungetInt8 (at " << pos << ")" << std::endl; |
| pos--; |
| } |
| |
| void readHeader() { |
| if (debug) std::cerr << "== readHeader" << std::endl; |
| verifyInt32(BinaryConsts::Magic); |
| verifyInt32(BinaryConsts::Version); |
| } |
| |
| void readStart() { |
| if (debug) std::cerr << "== readStart" << std::endl; |
| startIndex = getU32LEB(); |
| } |
| |
| void readMemory() { |
| if (debug) std::cerr << "== readMemory" << std::endl; |
| wasm.memory.initial = getU32LEB(); |
| wasm.memory.max = getU32LEB(); |
| auto exportMemory = getInt8(); |
| if (exportMemory) { |
| wasm.memory.exportName = Name("memory"); |
| } |
| } |
| |
| void 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 = getInt8(); |
| WASM_UNUSED(form); |
| assert(form == BinaryConsts::TypeForms::Basic); |
| 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 { |
| assert(numResults == 1); |
| curr->result = getWasmType(); |
| } |
| wasm.addFunctionType(curr); |
| } |
| } |
| |
| void 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)); |
| auto index = getU32LEB(); |
| assert(index < wasm.functionTypes.size()); |
| curr->type = wasm.getFunctionType(index); |
| assert(curr->type->name.is()); |
| curr->module = getInlineString(); |
| curr->base = getInlineString(); |
| wasm.addImport(curr); |
| } |
| } |
| |
| std::vector<FunctionType*> functionTypes; |
| |
| void 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(); |
| functionTypes.push_back(wasm.getFunctionType(index)); |
| } |
| } |
| |
| size_t nextLabel; |
| |
| Name getNextLabel() { |
| return cashew::IString(("label$" + std::to_string(nextLabel++)).c_str(), false); |
| } |
| |
| // We read functions before we know their names, so we need to backpatch the names later |
| |
| std::vector<Function*> functions; // we store functions here before wasm.addFunction after we know their names |
| std::map<size_t, std::vector<Call*>> functionCalls; // at index i we have all calls to i |
| Function* currFunction = nullptr; |
| size_t endOfFunction; |
| |
| void readFunctions() { |
| if (debug) std::cerr << "== readFunctions" << std::endl; |
| size_t total = getU32LEB(); |
| assert(total == functionTypes.size()); |
| for (size_t i = 0; i < total; i++) { |
| if (debug) std::cerr << "read one at " << pos << std::endl; |
| size_t size = getU32LEB(); |
| assert(size > 0); |
| 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; |
| // process body |
| assert(breakStack.empty()); |
| assert(expressionStack.empty()); |
| depth = 0; |
| func->body = getMaybeBlock(); |
| assert(depth == 0); |
| assert(breakStack.empty()); |
| assert(expressionStack.empty()); |
| assert(pos == endOfFunction); |
| } |
| currFunction = nullptr; |
| functions.push_back(func); |
| } |
| } |
| |
| std::map<Export*, size_t> exportIndexes; |
| |
| void readExports() { |
| if (debug) std::cerr << "== readExports" << 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 Export; |
| auto index = getU32LEB(); |
| assert(index < functionTypes.size()); |
| curr->name = getInlineString(); |
| exportIndexes[curr] = index; |
| } |
| } |
| |
| std::vector<Name> breakStack; |
| |
| std::vector<Expression*> expressionStack; |
| |
| BinaryConsts::ASTNodes lastSeparator = BinaryConsts::End; |
| |
| void 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* popExpression() { |
| assert(expressionStack.size() > 0); |
| auto ret = expressionStack.back(); |
| expressionStack.pop_back(); |
| return ret; |
| } |
| |
| void processFunctions() { |
| for (auto& func : functions) { |
| wasm.addFunction(func); |
| } |
| // now that we have names for each function, apply things |
| |
| if (startIndex != static_cast<Index>(-1) && startIndex < wasm.functions.size()) { |
| wasm.start = wasm.functions[startIndex]->name; |
| } |
| |
| for (auto& iter : exportIndexes) { |
| Export* curr = iter.first; |
| curr->value = wasm.functions[iter.second]->name; |
| 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 (size_t index : functionTable) { |
| assert(index < wasm.functions.size()); |
| wasm.table.names.push_back(wasm.functions[index]->name); |
| } |
| } |
| |
| void readDataSegments() { |
| if (debug) std::cerr << "== readDataSegments" << std::endl; |
| auto num = getU32LEB(); |
| for (size_t i = 0; i < num; i++) { |
| Memory::Segment curr; |
| auto offset = getU32LEB(); |
| 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); |
| } |
| } |
| |
| std::vector<size_t> functionTable; |
| |
| void readFunctionTable() { |
| if (debug) std::cerr << "== readFunctionTable" << std::endl; |
| auto num = getU32LEB(); |
| for (size_t i = 0; i < num; i++) { |
| auto index = getU32LEB(); |
| functionTable.push_back(index); |
| } |
| } |
| |
| void readNames() { |
| if (debug) std::cerr << "== readNames" << std::endl; |
| auto num = getU32LEB(); |
| assert(num == functions.size()); |
| for (size_t i = 0; i < num; i++) { |
| functions[i]->name = getInlineString(); |
| auto numLocals = getU32LEB(); |
| WASM_UNUSED(numLocals); |
| assert(numLocals == 0); // TODO |
| } |
| } |
| |
| // AST reading |
| |
| int depth; // only for debugging |
| |
| BinaryConsts::ASTNodes readExpression(Expression*& curr) { |
| if (pos == endOfFunction) { |
| curr = nullptr; |
| return BinaryConsts::End; |
| } |
| if (debug) std::cerr << "zz recurse into " << ++depth << " at " << pos << std::endl; |
| 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: visitCall((curr = allocator.alloc<Call>())->cast<Call>()); break; |
| case BinaryConsts::CallImport: visitCallImport((curr = allocator.alloc<CallImport>())->cast<CallImport>()); break; |
| case BinaryConsts::CallIndirect: visitCallIndirect((curr = allocator.alloc<CallIndirect>())->cast<CallIndirect>()); break; |
| case BinaryConsts::GetLocal: visitGetLocal((curr = allocator.alloc<GetLocal>())->cast<GetLocal>()); break; |
| case BinaryConsts::SetLocal: visitSetLocal((curr = allocator.alloc<SetLocal>())->cast<SetLocal>()); 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::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; |
| std::cerr << "bad code 0x" << std::hex << (int)code << std::endl; |
| abort(); |
| } |
| } |
| if (debug) std::cerr << "zz recurse from " << depth-- << " at " << pos << std::endl; |
| return BinaryConsts::ASTNodes(code); |
| } |
| |
| void 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->name = getNextLabel(); |
| breakStack.push_back(curr->name); |
| 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(); |
| assert(end >= start); |
| 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(); |
| breakStack.pop_back(); |
| } |
| } |
| |
| Expression* getMaybeBlock() { |
| auto start = expressionStack.size(); |
| processExpressions(); |
| size_t end = expressionStack.size(); |
| if (start - end == 1) { |
| return popExpression(); |
| } |
| auto* block = allocator.alloc<Block>(); |
| for (size_t i = start; i < end; i++) { |
| block->list.push_back(expressionStack[i]); |
| } |
| block->finalize(); |
| expressionStack.resize(start); |
| return block; |
| } |
| |
| Expression* getBlock() { |
| Name label = getNextLabel(); |
| breakStack.push_back(label); |
| auto* block = Builder(wasm).blockify(getMaybeBlock()); |
| breakStack.pop_back(); |
| block->cast<Block>()->name = label; |
| return block; |
| } |
| |
| void visitIf(If *curr) { |
| if (debug) std::cerr << "zz node: If" << std::endl; |
| curr->condition = popExpression(); |
| curr->ifTrue = getBlock(); |
| if (lastSeparator == BinaryConsts::Else) { |
| curr->ifFalse = getBlock(); |
| curr->finalize(); |
| } |
| assert(lastSeparator == BinaryConsts::End); |
| } |
| void visitLoop(Loop *curr) { |
| if (debug) std::cerr << "zz node: Loop" << std::endl; |
| curr->out = getNextLabel(); |
| curr->in = getNextLabel(); |
| breakStack.push_back(curr->out); |
| breakStack.push_back(curr->in); |
| curr->body = getMaybeBlock(); |
| breakStack.pop_back(); |
| breakStack.pop_back(); |
| curr->finalize(); |
| } |
| |
| Name getBreakName(int32_t offset) { |
| assert(breakStack.size() - 1 - offset < breakStack.size()); |
| return breakStack[breakStack.size() - 1 - offset]; |
| } |
| |
| void visitBreak(Break *curr, uint8_t code) { |
| if (debug) std::cerr << "zz node: Break" << std::endl; |
| auto arity = getU32LEB(); |
| assert(arity == 0 || arity == 1); |
| curr->name = getBreakName(getU32LEB()); |
| if (code == BinaryConsts::BrIf) curr->condition = popExpression(); |
| if (arity == 1) curr->value = popExpression(); |
| curr->finalize(); |
| } |
| void visitSwitch(Switch *curr) { |
| if (debug) std::cerr << "zz node: Switch" << std::endl; |
| auto arity = getU32LEB(); |
| assert(arity == 0 || arity == 1); |
| curr->condition = popExpression(); |
| if (arity == 1) curr->value = popExpression(); |
| auto numTargets = getU32LEB(); |
| for (size_t i = 0; i < numTargets; i++) { |
| curr->targets.push_back(getBreakName(getInt32())); |
| } |
| curr->default_ = getBreakName(getInt32()); |
| } |
| void visitCall(Call *curr) { |
| if (debug) std::cerr << "zz node: Call" << std::endl; |
| auto arity = getU32LEB(); |
| WASM_UNUSED(arity); |
| auto index = getU32LEB(); |
| assert(index < functionTypes.size()); |
| auto type = functionTypes[index]; |
| auto num = type->params.size(); |
| assert(num == arity); |
| curr->operands.resize(num); |
| for (size_t i = 0; i < num; i++) { |
| curr->operands[num - i - 1] = popExpression(); |
| } |
| curr->type = type->result; |
| functionCalls[index].push_back(curr); |
| } |
| void visitCallImport(CallImport *curr) { |
| if (debug) std::cerr << "zz node: CallImport" << std::endl; |
| auto arity = getU32LEB(); |
| WASM_UNUSED(arity); |
| auto import = wasm.getImport(getU32LEB()); |
| curr->target = import->name; |
| auto type = import->type; |
| assert(type); |
| auto num = type->params.size(); |
| assert(num == arity); |
| if (debug) std::cerr << "zz node: CallImport " << curr->target << " with type " << type->name << " and " << num << " params\n"; |
| curr->operands.resize(num); |
| for (size_t i = 0; i < num; i++) { |
| curr->operands[num - i - 1] = popExpression(); |
| } |
| curr->type = type->result; |
| } |
| void visitCallIndirect(CallIndirect *curr) { |
| if (debug) std::cerr << "zz node: CallIndirect" << std::endl; |
| auto arity = getU32LEB(); |
| WASM_UNUSED(arity); |
| auto* fullType = wasm.getFunctionType(getU32LEB()); |
| curr->fullType = fullType->name; |
| auto num = fullType->params.size(); |
| assert(num == arity); |
| curr->operands.resize(num); |
| for (size_t i = 0; i < num; i++) { |
| curr->operands[num - i - 1] = popExpression(); |
| } |
| curr->target = popExpression(); |
| curr->type = fullType->result; |
| } |
| void visitGetLocal(GetLocal *curr) { |
| if (debug) std::cerr << "zz node: GetLocal " << pos << std::endl; |
| curr->index = getU32LEB(); |
| assert(curr->index < currFunction->getNumLocals()); |
| curr->type = currFunction->getLocalType(curr->index); |
| } |
| void visitSetLocal(SetLocal *curr) { |
| if (debug) std::cerr << "zz node: SetLocal" << std::endl; |
| curr->index = getU32LEB(); |
| assert(curr->index < currFunction->getNumLocals()); |
| curr->value = popExpression(); |
| curr->type = curr->value->type; |
| } |
| |
| void readMemoryAccess(Address& alignment, size_t bytes, Address& offset) { |
| alignment = Pow2(getU32LEB()); |
| offset = getU32LEB(); |
| } |
| |
| bool 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 = popExpression(); |
| out = curr; |
| return true; |
| } |
| bool maybeVisitStore(Expression*& out, uint8_t code) { |
| Store* curr; |
| switch (code) { |
| case BinaryConsts::I32StoreMem8: curr = allocator.alloc<Store>(); curr->bytes = 1; curr->type = i32; break; |
| case BinaryConsts::I32StoreMem16: curr = allocator.alloc<Store>(); curr->bytes = 2; curr->type = i32; break; |
| case BinaryConsts::I32StoreMem: curr = allocator.alloc<Store>(); curr->bytes = 4; curr->type = i32; break; |
| case BinaryConsts::I64StoreMem8: curr = allocator.alloc<Store>(); curr->bytes = 1; curr->type = i64; break; |
| case BinaryConsts::I64StoreMem16: curr = allocator.alloc<Store>(); curr->bytes = 2; curr->type = i64; break; |
| case BinaryConsts::I64StoreMem32: curr = allocator.alloc<Store>(); curr->bytes = 4; curr->type = i64; break; |
| case BinaryConsts::I64StoreMem: curr = allocator.alloc<Store>(); curr->bytes = 8; curr->type = i64; break; |
| case BinaryConsts::F32StoreMem: curr = allocator.alloc<Store>(); curr->bytes = 4; curr->type = f32; break; |
| case BinaryConsts::F64StoreMem: curr = allocator.alloc<Store>(); curr->bytes = 8; curr->type = f64; break; |
| default: return false; |
| } |
| if (debug) std::cerr << "zz node: Store" << std::endl; |
| readMemoryAccess(curr->align, curr->bytes, curr->offset); |
| curr->value = popExpression(); |
| curr->ptr = popExpression(); |
| out = curr; |
| return true; |
| } |
| bool maybeVisitConst(Expression*& out, uint8_t code) { |
| Const* curr; |
| 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 = Literal(getFloat32()); break; |
| case BinaryConsts::F64Const: curr = allocator.alloc<Const>(); curr->value = Literal(getFloat64()); break; |
| default: return false; |
| } |
| curr->type = curr->value.type; |
| out = curr; |
| if (debug) std::cerr << "zz node: Const" << std::endl; |
| return true; |
| } |
| bool 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 = popExpression(); |
| out = curr; |
| return true; |
| } |
| bool 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 = popExpression(); |
| curr->left = popExpression(); |
| curr->finalize(); |
| out = curr; |
| return true; |
| #undef TYPED_CODE |
| #undef INT_TYPED_CODE |
| #undef FLOAT_TYPED_CODE |
| } |
| void visitSelect(Select *curr) { |
| if (debug) std::cerr << "zz node: Select" << std::endl; |
| curr->condition = popExpression(); |
| curr->ifFalse = popExpression(); |
| curr->ifTrue = popExpression(); |
| curr->finalize(); |
| } |
| void visitReturn(Return *curr) { |
| if (debug) std::cerr << "zz node: Return" << std::endl; |
| auto arity = getU32LEB(); |
| assert(arity == 0 || arity == 1); |
| if (arity == 1) { |
| curr->value = popExpression(); |
| } |
| } |
| bool 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] = popExpression(); |
| break; |
| } |
| default: return false; |
| } |
| if (debug) std::cerr << "zz node: Host" << std::endl; |
| curr->finalize(); |
| out = curr; |
| return true; |
| } |
| void visitNop(Nop *curr) { |
| if (debug) std::cerr << "zz node: Nop" << std::endl; |
| } |
| void visitUnreachable(Unreachable *curr) { |
| if (debug) std::cerr << "zz node: Unreachable" << std::endl; |
| } |
| }; |
| |
| } // namespace wasm |
| |
| #endif // wasm_wasm_binary_h |