From fb1f2d7a8122f372d5f344ac340b000925594434 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 10 Feb 2026 07:00:23 +0000 Subject: [PATCH 01/12] Start adding runtime memory --- src/ir/runtime-memory.h | 40 ++++++++++++++++++++++++++++++++++++++++ src/wasm-interpreter.h | 1 + 2 files changed, 41 insertions(+) create mode 100644 src/ir/runtime-memory.h diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h new file mode 100644 index 00000000000..3ddb6436be0 --- /dev/null +++ b/src/ir/runtime-memory.h @@ -0,0 +1,40 @@ +/* + * Copyright 2026 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. + */ + +#ifndef wasm_ir_runtime_memory_h +#define wasm_ir_runtime_memory_h + +#include "wasm.h" + +namespace wasm { + +// TODO split into pure virtual class +class RuntimeMemory { + RuntimeMemory(Memory memory) : memoryDefinition(memory) {} + + // variants for load8 etc? + // Do we care about the order here? + Literal load(Address addr) const { return {}; } + + const Memory* getDefinition() const { return &memoryDefinition; } + +private: + const Memory memoryDefinition; +}; + +} // namespace wasm + +#endif // wasm_ir_runtime_memory_h \ No newline at end of file diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5e6b0cb3e51..f0a3091d984 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -41,6 +41,7 @@ #include "ir/memory-utils.h" #include "ir/module-utils.h" #include "ir/properties.h" +#include "ir/runtime-memory.h" #include "ir/runtime-table.h" #include "ir/table-utils.h" #include "support/bits.h" From a0f2b9c5a335a532672dec61666a15165c5ae7a3 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 12 Feb 2026 07:53:58 +0000 Subject: [PATCH 02/12] Add importing logic --- src/ir/import-utils.h | 23 ++++++++++++++++-- src/ir/runtime-memory.h | 9 +++++-- src/ir/runtime-table.h | 3 ++- src/tools/wasm-ctor-eval.cpp | 6 +++++ src/wasm-interpreter.h | 47 ++++++++++++++++++++++++++++++++++++ 5 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/ir/import-utils.h b/src/ir/import-utils.h index 3697b9f0269..bc6039d85de 100644 --- a/src/ir/import-utils.h +++ b/src/ir/import-utils.h @@ -18,6 +18,7 @@ #define wasm_ir_import_h #include "ir/import-names.h" +#include "ir/runtime-memory.h" #include "ir/runtime-table.h" #include "literal.h" #include "wasm-type.h" @@ -137,12 +138,19 @@ class ImportResolver { virtual Literals* getGlobalOrNull(ImportNames name, Type type, bool mut) const = 0; - // Returns null if the imported table does not exist. The returned - // RuntimeTable* lives as long as the ImportResolver instance. + // Returns null if the `name` wasn't found. The returned RuntimeTable lives + // as long as the ImportResolver instance. virtual RuntimeTable* getTableOrNull(ImportNames name, const Table& type) const = 0; + // Returns null if the `name` wasn't found. The returned Tag lives + // as long as the ImportResolver instance. virtual Tag* getTagOrNull(ImportNames name, const Signature& type) const = 0; + + // Returns null if the `name` wasn't found. The returned RuntimeMemory lives + // as long as the ImportResolver instance. + virtual RuntimeMemory* getMemoryOrNull(ImportNames name, + const Memory& type) const = 0; }; // Looks up imports from the given `linkedInstances`. @@ -185,6 +193,17 @@ class LinkedInstancesImportResolver : public ImportResolver { return instance->getExportedTagOrNull(name.name); } + RuntimeMemory* getMemoryOrNull(ImportNames name, + const Memory& type) const override { + auto it = linkedInstances.find(name.module); + if (it == linkedInstances.end()) { + return nullptr; + } + + ModuleRunnerType* instance = it->second.get(); + return instance->getExportedMemoryOrNull(name.name); + } + private: const std::map> linkedInstances; }; diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index 3ddb6436be0..83e5c587e54 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -23,18 +23,23 @@ namespace wasm { // TODO split into pure virtual class class RuntimeMemory { +public: RuntimeMemory(Memory memory) : memoryDefinition(memory) {} + virtual ~RuntimeMemory() = default; + // variants for load8 etc? // Do we care about the order here? - Literal load(Address addr) const { return {}; } + virtual Literal load(Address addr) const { return {}; } const Memory* getDefinition() const { return &memoryDefinition; } -private: +protected: const Memory memoryDefinition; }; +class RealRuntimeMemory : public RuntimeMemory {}; + } // namespace wasm #endif // wasm_ir_runtime_memory_h \ No newline at end of file diff --git a/src/ir/runtime-table.h b/src/ir/runtime-table.h index ca3f8b1268c..4ce55823a6d 100644 --- a/src/ir/runtime-table.h +++ b/src/ir/runtime-table.h @@ -30,7 +30,6 @@ namespace wasm { // out-of-bounds access. class RuntimeTable { public: - RuntimeTable(Table table) : tableDefinition(table) {} virtual ~RuntimeTable() = default; virtual void set(Address i, Literal l) = 0; @@ -54,6 +53,8 @@ class RuntimeTable { const Table* getDefinition() const { return &tableDefinition; } protected: + RuntimeTable(Table table) : tableDefinition(table) {} + const Table tableDefinition; }; diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 9b2800a2d84..b849ae29f40 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -95,6 +95,12 @@ class EvallingImportResolver : public ImportResolver { return &it->second; } + RuntimeMemory* getMemoryOrNull(ImportNames name, + const Memory& memory) const override { + Fatal() << "todo"; + return nullptr; + } + private: mutable Literals stubLiteral; mutable std::unordered_map importedTags; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index f0a3091d984..fa5c70c4f44 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3208,6 +3208,8 @@ class ModuleRunnerBase : public ExpressionRunner { std::unordered_map allTags; + std::unordered_map allMemories; + using CreateTableFunc = std::unique_ptr(Literal, Table); ModuleRunnerBase( @@ -3321,6 +3323,19 @@ class ModuleRunnerBase : public ExpressionRunner { return iter->second; } + RuntimeMemory* getExportedMemoryOrNull(Name name) { + Export* export_ = wasm.getExportOrNull(name); + if (!export_ || export_->kind != ExternalKind::Memory) { + return nullptr; + } + Name internalName = *export_->getInternalName(); + auto iter = allMemories.find(internalName); + if (iter == allMemories.end()) { + return nullptr; + } + return iter->second; + } + Literals& getExportedGlobalOrTrap(Name name) { auto* global = getExportedGlobalOrNull(name); if (!global) { @@ -3372,6 +3387,7 @@ class ModuleRunnerBase : public ExpressionRunner { std::vector definedGlobals; std::vector> definedTables; std::vector definedTags; + std::vector> definedMemories; // Keep a record of call depth, to guard against excessive recursion. size_t callDepth = 0; @@ -3595,6 +3611,37 @@ class ModuleRunnerBase : public ExpressionRunner { }); } + void initializeMemories() { + int definedMemoryCount = 0; + ModuleUtils::iterDefinedMemories( + wasm, [&definedMemoryCount](auto&& _) { ++definedMemoryCount; }); + definedMemories.reserve(definedMemoryCount); + + for (auto& memory : wasm.memories) { + if (memory->imported()) { + auto importNames = memory->importNames(); + auto* importedMemory = + importResolver->getMemoryOrNull(importNames, *memory); + if (!importedMemory) { + externalInterface->trap((std::stringstream() + << "Imported memory " << importNames + << " not found.") + .str()); + } + [[maybe_unused]] auto [_, inserted] = + allMemories.try_emplace(memory->name, importedMemory); + // parsing/validation checked this already. + assert(inserted && "Unexpected repeated memory name"); + } else { + auto& runtimeMemory = definedMemories.emplace_back( + std::make_unique(memory)); + [[maybe_unused]] auto [_, inserted] = + allMemories.try_emplace(memory->name, runtimeMemory.get()); + assert(inserted && "Unexpected repeated memory name"); + } + } + } + struct MemoryInstanceInfo { // The ModuleRunner instance in which the memory is defined. SubType* instance; From 1c67da4d73706ca96ecff75ab0a3e2e084490328 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 12 Feb 2026 08:05:51 +0000 Subject: [PATCH 03/12] Start changing interpreter code --- src/ir/runtime-memory.h | 4 +++- src/wasm-interpreter.h | 21 ++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index 83e5c587e54..ef026400acf 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -30,7 +30,9 @@ class RuntimeMemory { // variants for load8 etc? // Do we care about the order here? - virtual Literal load(Address addr) const { return {}; } + // todo: address types? Address::address32_t is strange + virtual Literal load(uint32_t addr) const { return {}; } + virtual Literal load(uint64_t addr) const { return {}; } const Memory* getDefinition() const { return &memoryDefinition; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index fa5c70c4f44..6d7f8c98441 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4107,15 +4107,18 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitLoad(Load* curr) { VISIT(flow, curr->ptr) - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - auto addr = info.instance->getFinalAddress( - curr, flow.getSingleValue(), memorySizeBytes); - if (curr->isAtomic()) { - info.instance->checkAtomicAddress(addr, curr->bytes, memorySizeBytes); - } - auto ret = info.interface()->load(curr, addr, info.name); - return ret; + auto* memory = allMemories[curr->memory]; + return memory->load(static_cast(flow.getSingleValue().geti32())); + // auto info = getMemoryInstanceInfo(curr->memory); + // auto memorySize = info.instance->getMemorySize(info.name); + // auto addr = + // info.instance->getFinalAddress(curr, flow.getSingleValue(), + // memorySize); + // if (curr->isAtomic()) { + // info.instance->checkAtomicAddress(addr, curr->bytes, memorySize); + // } + // auto ret = info.interface()->load(curr, addr, info.name); + // return ret; } Flow visitStore(Store* curr) { VISIT(ptr, curr->ptr) From cc38d632fd257f9c3e771dc1ade84bb1a9d1b9c9 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 12 Feb 2026 18:38:55 +0000 Subject: [PATCH 04/12] wip --- src/ir/runtime-memory.h | 52 ++++++++++++++++++++++++++++++++++++++++- src/wasm-interpreter.h | 5 +++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index ef026400acf..ef30fa08e5f 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -17,10 +17,52 @@ #ifndef wasm_ir_runtime_memory_h #define wasm_ir_runtime_memory_h +#include "interpreter/exception.h" #include "wasm.h" namespace wasm { +namespace { + +void trapIfGt(uint64_t lhs, uint64_t rhs, const char* msg) { + if (lhs > rhs) { + std::stringstream ss; + ss << msg << ": " << lhs << " > " << rhs; + // ss.str(); + throw TrapException{}; + } +} + +void checkLoadAddress(Address addr, Index bytes, Address memorySize) { + Address memorySizeBytes = memorySize * Memory::kPageSize; + trapIfGt(addr, memorySizeBytes - bytes, "highest > memory"); +} + +// void checkAtomicAddress(Address addr, Index bytes, Address memorySize) { +// checkLoadAddress(addr, bytes, memorySize); +// // Unaligned atomics trap. +// if (bytes > 1) { +// if (addr & (bytes - 1)) { +// // "unaligned atomic operation" +// throw TrapException{}; +// } +// } +// } + +Address +getFinalAddress(uint64_t offset, Literal ptr, Index bytes, Address memorySize) { + Address memorySizeBytes = memorySize * Memory::kPageSize; + uint64_t addr = ptr.type == Type::i32 ? ptr.geti32() : ptr.geti64(); + trapIfGt(offset, memorySizeBytes, "offset > memory"); + trapIfGt(addr, memorySizeBytes - offset, "final > memory"); + addr += offset; + trapIfGt(bytes, memorySizeBytes, "bytes > memory"); + checkLoadAddress(addr, bytes, memorySize); + return addr; +} + +} // namespace + // TODO split into pure virtual class class RuntimeMemory { public: @@ -31,13 +73,21 @@ class RuntimeMemory { // variants for load8 etc? // Do we care about the order here? // todo: address types? Address::address32_t is strange - virtual Literal load(uint32_t addr) const { return {}; } + // todo: type of offset? + virtual Literal + load(uint32_t addr, uint64_t offset, MemoryOrder order) const { + Address address = getFinalAddress(offset, Literal(addr), 4, 1); + return {}; + } virtual Literal load(uint64_t addr) const { return {}; } const Memory* getDefinition() const { return &memoryDefinition; } protected: const Memory memoryDefinition; + +private: + std::vector memory; }; class RealRuntimeMemory : public RuntimeMemory {}; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6d7f8c98441..5a12b00daeb 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4106,9 +4106,12 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitLoad(Load* curr) { + WASM_UNREACHABLE("??"); VISIT(flow, curr->ptr) auto* memory = allMemories[curr->memory]; - return memory->load(static_cast(flow.getSingleValue().geti32())); + return memory->load(static_cast(flow.getSingleValue().geti32()), + curr->offset, + curr->order); // auto info = getMemoryInstanceInfo(curr->memory); // auto memorySize = info.instance->getMemorySize(info.name); // auto addr = From 85e68f27402bb97ee98118e24de602e684ed6c0f Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 10 Mar 2026 22:11:11 +0000 Subject: [PATCH 05/12] wip --- src/ir/runtime-memory.h | 3 +-- src/wasm-interpreter.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index ef30fa08e5f..6d3c0e27032 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -74,8 +74,7 @@ class RuntimeMemory { // Do we care about the order here? // todo: address types? Address::address32_t is strange // todo: type of offset? - virtual Literal - load(uint32_t addr, uint64_t offset, MemoryOrder order) const { + virtual Literal load(Address addr, Address offset, MemoryOrder order) const { Address address = getFinalAddress(offset, Literal(addr), 4, 1); return {}; } diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 5a12b00daeb..880897e254a 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -4106,7 +4106,6 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitLoad(Load* curr) { - WASM_UNREACHABLE("??"); VISIT(flow, curr->ptr) auto* memory = allMemories[curr->memory]; return memory->load(static_cast(flow.getSingleValue().geti32()), From d7e8dae6cf872a901e721e8aba672940a6d0b4f3 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 11 Mar 2026 17:02:23 +0000 Subject: [PATCH 06/12] wip, starting to work --- src/ir/runtime-memory.h | 67 +- src/wasm-interpreter.h | 7 +- src/wasm.h | 6 +- test/spec/if.wast | 1809 ++++++++++++++++++++------------------- 4 files changed, 966 insertions(+), 923 deletions(-) diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index 6d3c0e27032..b03facbf9c7 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -26,18 +26,14 @@ namespace { void trapIfGt(uint64_t lhs, uint64_t rhs, const char* msg) { if (lhs > rhs) { - std::stringstream ss; - ss << msg << ": " << lhs << " > " << rhs; + // std::stringstream ss; + std::cerr << msg << ": " << lhs << " > " << rhs << "\n"; // ss.str(); + // std::cerr< memory"); -} - // void checkAtomicAddress(Address addr, Index bytes, Address memorySize) { // checkLoadAddress(addr, bytes, memorySize); // // Unaligned atomics trap. @@ -49,24 +45,47 @@ void checkLoadAddress(Address addr, Index bytes, Address memorySize) { // } // } -Address -getFinalAddress(uint64_t offset, Literal ptr, Index bytes, Address memorySize) { - Address memorySizeBytes = memorySize * Memory::kPageSize; - uint64_t addr = ptr.type == Type::i32 ? ptr.geti32() : ptr.geti64(); +Address getFinalAddress(Address addr, + Address offset, + Index bytes, + Address memorySizeBytes) { trapIfGt(offset, memorySizeBytes, "offset > memory"); trapIfGt(addr, memorySizeBytes - offset, "final > memory"); - addr += offset; + + // TODO: overflow here? + addr = size_t(addr) + offset; trapIfGt(bytes, memorySizeBytes, "bytes > memory"); - checkLoadAddress(addr, bytes, memorySize); + + // checkLoadAddress(addr, bytes, memorySizeBytes); + trapIfGt(addr, memorySizeBytes - bytes, "highest > memory"); return addr; } +template static bool aligned(const uint8_t* address) { + static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2"); + return 0 == (reinterpret_cast(address) & (sizeof(T) - 1)); +} + +template +T asdf(const std::vector& memory, size_t address) { + if (aligned(&memory[address])) { + return *reinterpret_cast(&memory[address]); + } else { + T loaded; + std::memcpy(&loaded, &memory[address], sizeof(T)); + return loaded; + } +} } // namespace // TODO split into pure virtual class class RuntimeMemory { public: - RuntimeMemory(Memory memory) : memoryDefinition(memory) {} + // todo: might want a constructor that takes data segments + RuntimeMemory(Memory memory) + : memoryDefinition(std::move(memory)), memory(memory.initialByteSize(), 0) { + // this->memory.reserve(memory.initialByteSize()); + } virtual ~RuntimeMemory() = default; @@ -74,9 +93,18 @@ class RuntimeMemory { // Do we care about the order here? // todo: address types? Address::address32_t is strange // todo: type of offset? - virtual Literal load(Address addr, Address offset, MemoryOrder order) const { - Address address = getFinalAddress(offset, Literal(addr), 4, 1); - return {}; + virtual Literal load(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order) const { + Address final = getFinalAddress(addr, offset, byteCount, memory.size()); + (void) final; + + // return memory.get() + + return Literal(asdf(memory, (size_t) final)); + + // return Literal(1); } virtual Literal load(uint64_t addr) const { return {}; } @@ -89,7 +117,10 @@ class RuntimeMemory { std::vector memory; }; -class RealRuntimeMemory : public RuntimeMemory {}; +class RealRuntimeMemory : public RuntimeMemory { +public: + using RuntimeMemory::RuntimeMemory; +}; } // namespace wasm diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 880897e254a..e1b7c18befa 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3262,7 +3262,9 @@ class ModuleRunnerBase : public ExpressionRunner { initializeTables(); initializeTags(); - initializeMemoryContents(); + initializeMemories(); + // TODO: + // initializeMemoryContents(); // run start, if present if (wasm.start.is()) { @@ -3634,7 +3636,7 @@ class ModuleRunnerBase : public ExpressionRunner { assert(inserted && "Unexpected repeated memory name"); } else { auto& runtimeMemory = definedMemories.emplace_back( - std::make_unique(memory)); + std::make_unique(*memory)); [[maybe_unused]] auto [_, inserted] = allMemories.try_emplace(memory->name, runtimeMemory.get()); assert(inserted && "Unexpected repeated memory name"); @@ -4110,6 +4112,7 @@ class ModuleRunnerBase : public ExpressionRunner { auto* memory = allMemories[curr->memory]; return memory->load(static_cast(flow.getSingleValue().geti32()), curr->offset, + curr->bytes, curr->order); // auto info = getMemoryInstanceInfo(curr->memory); // auto memorySize = info.instance->getMemorySize(info.name); diff --git a/src/wasm.h b/src/wasm.h index 81cb60baf9d..5eb952d362b 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2572,9 +2572,9 @@ class Memory : public Importable { } return 1ull << (64 - pageSizeLog2); } - Address::address64_t pageSize() const { - return 1ull << static_cast(pageSizeLog2); - } + Address::address64_t pageSize() const { return 1ull << pageSizeLog2; } + + Address::address64_t initialByteSize() const { return 1ull << pageSizeLog2; } }; class Global : public Importable { diff --git a/test/spec/if.wast b/test/spec/if.wast index 0fef1078f06..052b2a4153f 100644 --- a/test/spec/if.wast +++ b/test/spec/if.wast @@ -1,935 +1,944 @@ ;; Test `if` operator (module - ;; Auxiliary definition - (memory 1) - - (func $dummy) - - (func (export "empty") (param i32) - (if (local.get 0) (then)) - (if (local.get 0) (then) (else)) - (if $l (local.get 0) (then)) - (if $l (local.get 0) (then) (else)) - ) - - (func (export "singular") (param i32) (result i32) - (if (local.get 0) (then (nop))) - (if (local.get 0) (then (nop)) (else (nop))) - (if (result i32) (local.get 0) (then (i32.const 7)) (else (i32.const 8))) - ) - - (func (export "multi") (param i32) (result i32) - (if (local.get 0) (then (call $dummy) (call $dummy) (call $dummy))) - (if (local.get 0) (then) (else (call $dummy) (call $dummy) (call $dummy))) - (if (result i32) (local.get 0) - (then (call $dummy) (call $dummy) (i32.const 8)) - (else (call $dummy) (call $dummy) (i32.const 9)) - ) - ) - - (func (export "nested") (param i32 i32) (result i32) - (if (result i32) (local.get 0) - (then - (if (local.get 1) (then (call $dummy) (block) (nop))) - (if (local.get 1) (then) (else (call $dummy) (block) (nop))) - (if (result i32) (local.get 1) - (then (call $dummy) (i32.const 9)) - (else (call $dummy) (i32.const 10)) - ) - ) - (else - (if (local.get 1) (then (call $dummy) (block) (nop))) - (if (local.get 1) (then) (else (call $dummy) (block) (nop))) - (if (result i32) (local.get 1) - (then (call $dummy) (i32.const 10)) - (else (call $dummy) (i32.const 11)) - ) - ) - ) - ) - - (func (export "as-select-first") (param i32) (result i32) - (select - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (i32.const 2) (i32.const 3) - ) - ) - (func (export "as-select-mid") (param i32) (result i32) - (select - (i32.const 2) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (i32.const 3) - ) - ) - (func (export "as-select-last") (param i32) (result i32) - (select - (i32.const 2) (i32.const 3) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - ) - ) - - (func (export "as-loop-first") (param i32) (result i32) - (loop (result i32) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (call $dummy) (call $dummy) - ) - ) - (func (export "as-loop-mid") (param i32) (result i32) - (loop (result i32) - (call $dummy) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (call $dummy) - ) - ) - (func (export "as-loop-last") (param i32) (result i32) - (loop (result i32) - (call $dummy) (call $dummy) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - ) - ) - - (func (export "as-if-condition") (param i32) (result i32) - (if (result i32) - (if (result i32) (local.get 0) - (then (i32.const 1)) (else (i32.const 0)) - ) - (then (call $dummy) (i32.const 2)) - (else (call $dummy) (i32.const 3)) - ) - ) - - (func (export "as-br_if-first") (param i32) (result i32) - (block (result i32) - (br_if 0 - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (i32.const 2) - ) - (return (i32.const 3)) - ) - ) - (func (export "as-br_if-last") (param i32) (result i32) - (block (result i32) - (br_if 0 - (i32.const 2) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - ) - (return (i32.const 3)) - ) - ) - - (func (export "as-br_table-first") (param i32) (result i32) - (block (result i32) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (i32.const 2) - (br_table 0 0) - ) - ) - (func (export "as-br_table-last") (param i32) (result i32) - (block (result i32) - (i32.const 2) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (br_table 0 0) - ) + (memory 1 1) + (func (export "f") (result i32) + (i32.load (i32.const 0)) ) +) - (func $func (param i32 i32) (result i32) (local.get 0)) - (type $check (func (param i32 i32) (result i32))) - (table funcref (elem $func)) - (func (export "as-call_indirect-first") (param i32) (result i32) - (block (result i32) - (call_indirect (type $check) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (i32.const 2) (i32.const 0) - ) - ) - ) - (func (export "as-call_indirect-mid") (param i32) (result i32) - (block (result i32) - (call_indirect (type $check) - (i32.const 2) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (i32.const 0) - ) - ) - ) - (func (export "as-call_indirect-last") (param i32) (result i32) - (block (result i32) - (call_indirect (type $check) - (i32.const 2) (i32.const 0) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - ) - ) - ) +(assert_return (invoke "f") (i32.const 0)) - (func (export "as-store-first") (param i32) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (i32.const 2) - (i32.store) - ) - (func (export "as-store-last") (param i32) - (i32.const 2) - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 1)) - (else (call $dummy) (i32.const 0)) - ) - (i32.store) - ) +;; (module +;; ;; Auxiliary definition +;; (memory 1) - (func (export "as-memory.grow-value") (param i32) (result i32) - (memory.grow - (if (result i32) (local.get 0) - (then (i32.const 1)) - (else (i32.const 0)) - ) - ) - ) +;; (func $dummy) - (func $f (param i32) (result i32) (local.get 0)) +;; (func (export "empty") (param i32) +;; (if (local.get 0) (then)) +;; (if (local.get 0) (then) (else)) +;; (if $l (local.get 0) (then)) +;; (if $l (local.get 0) (then) (else)) +;; ) - (func (export "as-call-value") (param i32) (result i32) - (call $f - (if (result i32) (local.get 0) - (then (i32.const 1)) - (else (i32.const 0)) - ) - ) - ) - (func (export "as-return-value") (param i32) (result i32) - (if (result i32) (local.get 0) - (then (i32.const 1)) - (else (i32.const 0))) - (return) - ) - (func (export "as-drop-operand") (param i32) - (drop - (if (result i32) (local.get 0) - (then (i32.const 1)) - (else (i32.const 0)) - ) - ) - ) - (func (export "as-br-value") (param i32) (result i32) - (block (result i32) - (br 0 - (if (result i32) (local.get 0) - (then (i32.const 1)) - (else (i32.const 0)) - ) - ) - ) - ) - (func (export "as-local.set-value") (param i32) (result i32) - (local i32) - (local.set 0 - (if (result i32) (local.get 0) - (then (i32.const 1)) - (else (i32.const 0)) - ) - ) - (local.get 0) - ) - (func (export "as-local.tee-value") (param i32) (result i32) - (local.tee 0 - (if (result i32) (local.get 0) - (then (i32.const 1)) - (else (i32.const 0)) - ) - ) - ) - (global $a (mut i32) (i32.const 10)) - (func (export "as-global.set-value") (param i32) (result i32) - (global.set $a - (if (result i32) (local.get 0) - (then (i32.const 1)) - (else (i32.const 0)) - ) - ) (global.get $a) - ) - (func (export "as-load-operand") (param i32) (result i32) - (i32.load - (if (result i32) (local.get 0) - (then (i32.const 11)) - (else (i32.const 10)) - ) - ) - ) +;; (func (export "singular") (param i32) (result i32) +;; (if (local.get 0) (then (nop))) +;; (if (local.get 0) (then (nop)) (else (nop))) +;; (if (result i32) (local.get 0) (then (i32.const 7)) (else (i32.const 8))) +;; ) - (func (export "as-unary-operand") (param i32) (result i32) - (i32.ctz - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 13)) - (else (call $dummy) (i32.const -13)) - ) - ) - ) - (func (export "as-binary-operand") (param i32 i32) (result i32) - (i32.mul - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 3)) - (else (call $dummy) (i32.const -3)) - ) - (if (result i32) (local.get 1) - (then (call $dummy) (i32.const 4)) - (else (call $dummy) (i32.const -5)) - ) - ) - ) - (func (export "as-test-operand") (param i32) (result i32) - (i32.eqz - (if (result i32) (local.get 0) - (then (call $dummy) (i32.const 13)) - (else (call $dummy) (i32.const 0)) - ) - ) - ) - (func (export "as-compare-operand") (param i32 i32) (result i32) - (f32.gt - (if (result f32) (local.get 0) - (then (call $dummy) (f32.const 3)) - (else (call $dummy) (f32.const -3)) - ) - (if (result f32) (local.get 1) - (then (call $dummy) (f32.const 4)) - (else (call $dummy) (f32.const -4)) - ) - ) - ) +;; (func (export "multi") (param i32) (result i32) +;; (if (local.get 0) (then (call $dummy) (call $dummy) (call $dummy))) +;; (if (local.get 0) (then) (else (call $dummy) (call $dummy) (call $dummy))) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (call $dummy) (i32.const 8)) +;; (else (call $dummy) (call $dummy) (i32.const 9)) +;; ) +;; ) + +;; (func (export "nested") (param i32 i32) (result i32) +;; (if (result i32) (local.get 0) +;; (then +;; (if (local.get 1) (then (call $dummy) (block) (nop))) +;; (if (local.get 1) (then) (else (call $dummy) (block) (nop))) +;; (if (result i32) (local.get 1) +;; (then (call $dummy) (i32.const 9)) +;; (else (call $dummy) (i32.const 10)) +;; ) +;; ) +;; (else +;; (if (local.get 1) (then (call $dummy) (block) (nop))) +;; (if (local.get 1) (then) (else (call $dummy) (block) (nop))) +;; (if (result i32) (local.get 1) +;; (then (call $dummy) (i32.const 10)) +;; (else (call $dummy) (i32.const 11)) +;; ) +;; ) +;; ) +;; ) + +;; (func (export "as-select-first") (param i32) (result i32) +;; (select +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (i32.const 2) (i32.const 3) +;; ) +;; ) +;; (func (export "as-select-mid") (param i32) (result i32) +;; (select +;; (i32.const 2) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (i32.const 3) +;; ) +;; ) +;; (func (export "as-select-last") (param i32) (result i32) +;; (select +;; (i32.const 2) (i32.const 3) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; ) +;; ) + +;; (func (export "as-loop-first") (param i32) (result i32) +;; (loop (result i32) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (call $dummy) (call $dummy) +;; ) +;; ) +;; (func (export "as-loop-mid") (param i32) (result i32) +;; (loop (result i32) +;; (call $dummy) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (call $dummy) +;; ) +;; ) +;; (func (export "as-loop-last") (param i32) (result i32) +;; (loop (result i32) +;; (call $dummy) (call $dummy) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; ) +;; ) + +;; (func (export "as-if-condition") (param i32) (result i32) +;; (if (result i32) +;; (if (result i32) (local.get 0) +;; (then (i32.const 1)) (else (i32.const 0)) +;; ) +;; (then (call $dummy) (i32.const 2)) +;; (else (call $dummy) (i32.const 3)) +;; ) +;; ) + +;; (func (export "as-br_if-first") (param i32) (result i32) +;; (block (result i32) +;; (br_if 0 +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (i32.const 2) +;; ) +;; (return (i32.const 3)) +;; ) +;; ) +;; (func (export "as-br_if-last") (param i32) (result i32) +;; (block (result i32) +;; (br_if 0 +;; (i32.const 2) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; ) +;; (return (i32.const 3)) +;; ) +;; ) + +;; (func (export "as-br_table-first") (param i32) (result i32) +;; (block (result i32) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (i32.const 2) +;; (br_table 0 0) +;; ) +;; ) +;; (func (export "as-br_table-last") (param i32) (result i32) +;; (block (result i32) +;; (i32.const 2) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (br_table 0 0) +;; ) +;; ) + +;; (func $func (param i32 i32) (result i32) (local.get 0)) +;; (type $check (func (param i32 i32) (result i32))) +;; (table funcref (elem $func)) +;; (func (export "as-call_indirect-first") (param i32) (result i32) +;; (block (result i32) +;; (call_indirect (type $check) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (i32.const 2) (i32.const 0) +;; ) +;; ) +;; ) +;; (func (export "as-call_indirect-mid") (param i32) (result i32) +;; (block (result i32) +;; (call_indirect (type $check) +;; (i32.const 2) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (i32.const 0) +;; ) +;; ) +;; ) +;; (func (export "as-call_indirect-last") (param i32) (result i32) +;; (block (result i32) +;; (call_indirect (type $check) +;; (i32.const 2) (i32.const 0) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; ) +;; ) +;; ) - (func (export "break-bare") (result i32) - (if (i32.const 1) (then (br 0) (unreachable))) - (if (i32.const 1) (then (br 0) (unreachable)) (else (unreachable))) - (if (i32.const 0) (then (unreachable)) (else (br 0) (unreachable))) - (if (i32.const 1) (then (br_if 0 (i32.const 1)) (unreachable))) - (if (i32.const 1) (then (br_if 0 (i32.const 1)) (unreachable)) (else (unreachable))) - (if (i32.const 0) (then (unreachable)) (else (br_if 0 (i32.const 1)) (unreachable))) - (if (i32.const 1) (then (br_table 0 (i32.const 0)) (unreachable))) - (if (i32.const 1) (then (br_table 0 (i32.const 0)) (unreachable)) (else (unreachable))) - (if (i32.const 0) (then (unreachable)) (else (br_table 0 (i32.const 0)) (unreachable))) - (i32.const 19) - ) +;; (func (export "as-store-first") (param i32) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (i32.const 2) +;; (i32.store) +;; ) +;; (func (export "as-store-last") (param i32) +;; (i32.const 2) +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 1)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; (i32.store) +;; ) + +;; (func (export "as-memory.grow-value") (param i32) (result i32) +;; (memory.grow +;; (if (result i32) (local.get 0) +;; (then (i32.const 1)) +;; (else (i32.const 0)) +;; ) +;; ) +;; ) - (func (export "break-value") (param i32) (result i32) - (if (result i32) (local.get 0) - (then (br 0 (i32.const 18)) (i32.const 19)) - (else (br 0 (i32.const 21)) (i32.const 20)) - ) - ) +;; (func $f (param i32) (result i32) (local.get 0)) - (func (export "effects") (param i32) (result i32) - (local i32) - (if - (block (result i32) (local.set 1 (i32.const 1)) (local.get 0)) - (then - (local.set 1 (i32.mul (local.get 1) (i32.const 3))) - (local.set 1 (i32.sub (local.get 1) (i32.const 5))) - (local.set 1 (i32.mul (local.get 1) (i32.const 7))) - (br 0) - (local.set 1 (i32.mul (local.get 1) (i32.const 100))) - ) - (else - (local.set 1 (i32.mul (local.get 1) (i32.const 5))) - (local.set 1 (i32.sub (local.get 1) (i32.const 7))) - (local.set 1 (i32.mul (local.get 1) (i32.const 3))) - (br 0) - (local.set 1 (i32.mul (local.get 1) (i32.const 1000))) - ) - ) - (local.get 1) - ) -) +;; (func (export "as-call-value") (param i32) (result i32) +;; (call $f +;; (if (result i32) (local.get 0) +;; (then (i32.const 1)) +;; (else (i32.const 0)) +;; ) +;; ) +;; ) +;; (func (export "as-return-value") (param i32) (result i32) +;; (if (result i32) (local.get 0) +;; (then (i32.const 1)) +;; (else (i32.const 0))) +;; (return) +;; ) +;; (func (export "as-drop-operand") (param i32) +;; (drop +;; (if (result i32) (local.get 0) +;; (then (i32.const 1)) +;; (else (i32.const 0)) +;; ) +;; ) +;; ) +;; (func (export "as-br-value") (param i32) (result i32) +;; (block (result i32) +;; (br 0 +;; (if (result i32) (local.get 0) +;; (then (i32.const 1)) +;; (else (i32.const 0)) +;; ) +;; ) +;; ) +;; ) +;; (func (export "as-local.set-value") (param i32) (result i32) +;; (local i32) +;; (local.set 0 +;; (if (result i32) (local.get 0) +;; (then (i32.const 1)) +;; (else (i32.const 0)) +;; ) +;; ) +;; (local.get 0) +;; ) +;; (func (export "as-local.tee-value") (param i32) (result i32) +;; (local.tee 0 +;; (if (result i32) (local.get 0) +;; (then (i32.const 1)) +;; (else (i32.const 0)) +;; ) +;; ) +;; ) +;; (global $a (mut i32) (i32.const 10)) +;; (func (export "as-global.set-value") (param i32) (result i32) +;; (global.set $a +;; (if (result i32) (local.get 0) +;; (then (i32.const 1)) +;; (else (i32.const 0)) +;; ) +;; ) (global.get $a) +;; ) +;; (func (export "as-load-operand") (param i32) (result i32) +;; (i32.load +;; (if (result i32) (local.get 0) +;; (then (i32.const 11)) +;; (else (i32.const 10)) +;; ) +;; ) +;; ) + +;; (func (export "as-unary-operand") (param i32) (result i32) +;; (i32.ctz +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 13)) +;; (else (call $dummy) (i32.const -13)) +;; ) +;; ) +;; ) +;; (func (export "as-binary-operand") (param i32 i32) (result i32) +;; (i32.mul +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 3)) +;; (else (call $dummy) (i32.const -3)) +;; ) +;; (if (result i32) (local.get 1) +;; (then (call $dummy) (i32.const 4)) +;; (else (call $dummy) (i32.const -5)) +;; ) +;; ) +;; ) +;; (func (export "as-test-operand") (param i32) (result i32) +;; (i32.eqz +;; (if (result i32) (local.get 0) +;; (then (call $dummy) (i32.const 13)) +;; (else (call $dummy) (i32.const 0)) +;; ) +;; ) +;; ) +;; (func (export "as-compare-operand") (param i32 i32) (result i32) +;; (f32.gt +;; (if (result f32) (local.get 0) +;; (then (call $dummy) (f32.const 3)) +;; (else (call $dummy) (f32.const -3)) +;; ) +;; (if (result f32) (local.get 1) +;; (then (call $dummy) (f32.const 4)) +;; (else (call $dummy) (f32.const -4)) +;; ) +;; ) +;; ) + +;; (func (export "break-bare") (result i32) +;; (if (i32.const 1) (then (br 0) (unreachable))) +;; (if (i32.const 1) (then (br 0) (unreachable)) (else (unreachable))) +;; (if (i32.const 0) (then (unreachable)) (else (br 0) (unreachable))) +;; (if (i32.const 1) (then (br_if 0 (i32.const 1)) (unreachable))) +;; (if (i32.const 1) (then (br_if 0 (i32.const 1)) (unreachable)) (else (unreachable))) +;; (if (i32.const 0) (then (unreachable)) (else (br_if 0 (i32.const 1)) (unreachable))) +;; (if (i32.const 1) (then (br_table 0 (i32.const 0)) (unreachable))) +;; (if (i32.const 1) (then (br_table 0 (i32.const 0)) (unreachable)) (else (unreachable))) +;; (if (i32.const 0) (then (unreachable)) (else (br_table 0 (i32.const 0)) (unreachable))) +;; (i32.const 19) +;; ) + +;; (func (export "break-value") (param i32) (result i32) +;; (if (result i32) (local.get 0) +;; (then (br 0 (i32.const 18)) (i32.const 19)) +;; (else (br 0 (i32.const 21)) (i32.const 20)) +;; ) +;; ) + +;; (func (export "effects") (param i32) (result i32) +;; (local i32) +;; (if +;; (block (result i32) (local.set 1 (i32.const 1)) (local.get 0)) +;; (then +;; (local.set 1 (i32.mul (local.get 1) (i32.const 3))) +;; (local.set 1 (i32.sub (local.get 1) (i32.const 5))) +;; (local.set 1 (i32.mul (local.get 1) (i32.const 7))) +;; (br 0) +;; (local.set 1 (i32.mul (local.get 1) (i32.const 100))) +;; ) +;; (else +;; (local.set 1 (i32.mul (local.get 1) (i32.const 5))) +;; (local.set 1 (i32.sub (local.get 1) (i32.const 7))) +;; (local.set 1 (i32.mul (local.get 1) (i32.const 3))) +;; (br 0) +;; (local.set 1 (i32.mul (local.get 1) (i32.const 1000))) +;; ) +;; ) +;; (local.get 1) +;; ) +;; ) -(assert_return (invoke "empty" (i32.const 0))) -(assert_return (invoke "empty" (i32.const 1))) -(assert_return (invoke "empty" (i32.const 100))) -(assert_return (invoke "empty" (i32.const -2))) - -(assert_return (invoke "singular" (i32.const 0)) (i32.const 8)) -(assert_return (invoke "singular" (i32.const 1)) (i32.const 7)) -(assert_return (invoke "singular" (i32.const 10)) (i32.const 7)) -(assert_return (invoke "singular" (i32.const -10)) (i32.const 7)) - -(assert_return (invoke "multi" (i32.const 0)) (i32.const 9)) -(assert_return (invoke "multi" (i32.const 1)) (i32.const 8)) -(assert_return (invoke "multi" (i32.const 13)) (i32.const 8)) -(assert_return (invoke "multi" (i32.const -5)) (i32.const 8)) - -(assert_return (invoke "nested" (i32.const 0) (i32.const 0)) (i32.const 11)) -(assert_return (invoke "nested" (i32.const 1) (i32.const 0)) (i32.const 10)) -(assert_return (invoke "nested" (i32.const 0) (i32.const 1)) (i32.const 10)) -(assert_return (invoke "nested" (i32.const 3) (i32.const 2)) (i32.const 9)) -(assert_return (invoke "nested" (i32.const 0) (i32.const -100)) (i32.const 10)) -(assert_return (invoke "nested" (i32.const 10) (i32.const 10)) (i32.const 9)) -(assert_return (invoke "nested" (i32.const 0) (i32.const -1)) (i32.const 10)) -(assert_return (invoke "nested" (i32.const -111) (i32.const -2)) (i32.const 9)) - -(assert_return (invoke "as-select-first" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-select-first" (i32.const 1)) (i32.const 1)) -(assert_return (invoke "as-select-mid" (i32.const 0)) (i32.const 2)) -(assert_return (invoke "as-select-mid" (i32.const 1)) (i32.const 2)) -(assert_return (invoke "as-select-last" (i32.const 0)) (i32.const 3)) -(assert_return (invoke "as-select-last" (i32.const 1)) (i32.const 2)) - -(assert_return (invoke "as-loop-first" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-loop-first" (i32.const 1)) (i32.const 1)) -(assert_return (invoke "as-loop-mid" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-loop-mid" (i32.const 1)) (i32.const 1)) -(assert_return (invoke "as-loop-last" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-loop-last" (i32.const 1)) (i32.const 1)) - -(assert_return (invoke "as-if-condition" (i32.const 0)) (i32.const 3)) -(assert_return (invoke "as-if-condition" (i32.const 1)) (i32.const 2)) - -(assert_return (invoke "as-br_if-first" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-br_if-first" (i32.const 1)) (i32.const 1)) -(assert_return (invoke "as-br_if-last" (i32.const 0)) (i32.const 3)) -(assert_return (invoke "as-br_if-last" (i32.const 1)) (i32.const 2)) - -(assert_return (invoke "as-br_table-first" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-br_table-first" (i32.const 1)) (i32.const 1)) -(assert_return (invoke "as-br_table-last" (i32.const 0)) (i32.const 2)) -(assert_return (invoke "as-br_table-last" (i32.const 1)) (i32.const 2)) - -(assert_return (invoke "as-call_indirect-first" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-call_indirect-first" (i32.const 1)) (i32.const 1)) -(assert_return (invoke "as-call_indirect-mid" (i32.const 0)) (i32.const 2)) -(assert_return (invoke "as-call_indirect-mid" (i32.const 1)) (i32.const 2)) -(assert_return (invoke "as-call_indirect-last" (i32.const 0)) (i32.const 2)) -(assert_trap (invoke "as-call_indirect-last" (i32.const 1)) "undefined element") - -(assert_return (invoke "as-store-first" (i32.const 0))) -(assert_return (invoke "as-store-first" (i32.const 1))) -(assert_return (invoke "as-store-last" (i32.const 0))) -(assert_return (invoke "as-store-last" (i32.const 1))) - -(assert_return (invoke "as-memory.grow-value" (i32.const 0)) (i32.const 1)) -(assert_return (invoke "as-memory.grow-value" (i32.const 1)) (i32.const 1)) - -(assert_return (invoke "as-call-value" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-call-value" (i32.const 1)) (i32.const 1)) - -(assert_return (invoke "as-return-value" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-return-value" (i32.const 1)) (i32.const 1)) - -(assert_return (invoke "as-drop-operand" (i32.const 0))) -(assert_return (invoke "as-drop-operand" (i32.const 1))) - -(assert_return (invoke "as-br-value" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-br-value" (i32.const 1)) (i32.const 1)) - -(assert_return (invoke "as-local.set-value" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-local.set-value" (i32.const 1)) (i32.const 1)) - -(assert_return (invoke "as-local.tee-value" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-local.tee-value" (i32.const 1)) (i32.const 1)) - -(assert_return (invoke "as-global.set-value" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-global.set-value" (i32.const 1)) (i32.const 1)) - -(assert_return (invoke "as-load-operand" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-load-operand" (i32.const 1)) (i32.const 0)) - -(assert_return (invoke "as-unary-operand" (i32.const 0)) (i32.const 0)) -(assert_return (invoke "as-unary-operand" (i32.const 1)) (i32.const 0)) -(assert_return (invoke "as-unary-operand" (i32.const -1)) (i32.const 0)) - -(assert_return (invoke "as-binary-operand" (i32.const 0) (i32.const 0)) (i32.const 15)) -(assert_return (invoke "as-binary-operand" (i32.const 0) (i32.const 1)) (i32.const -12)) -(assert_return (invoke "as-binary-operand" (i32.const 1) (i32.const 0)) (i32.const -15)) -(assert_return (invoke "as-binary-operand" (i32.const 1) (i32.const 1)) (i32.const 12)) - -(assert_return (invoke "as-test-operand" (i32.const 0)) (i32.const 1)) -(assert_return (invoke "as-test-operand" (i32.const 1)) (i32.const 0)) - -(assert_return (invoke "as-compare-operand" (i32.const 0) (i32.const 0)) (i32.const 1)) -(assert_return (invoke "as-compare-operand" (i32.const 0) (i32.const 1)) (i32.const 0)) -(assert_return (invoke "as-compare-operand" (i32.const 1) (i32.const 0)) (i32.const 1)) -(assert_return (invoke "as-compare-operand" (i32.const 1) (i32.const 1)) (i32.const 0)) - -(assert_return (invoke "break-bare") (i32.const 19)) -(assert_return (invoke "break-value" (i32.const 1)) (i32.const 18)) -(assert_return (invoke "break-value" (i32.const 0)) (i32.const 21)) - -(assert_return (invoke "effects" (i32.const 1)) (i32.const -14)) -(assert_return (invoke "effects" (i32.const 0)) (i32.const -6)) - -(assert_invalid - (module (func $type-empty-i32 (result i32) (if (i32.const 0) (then)))) - "type mismatch" -) -(assert_invalid - (module (func $type-empty-i64 (result i64) (if (i32.const 0) (then)))) - "type mismatch" -) -(assert_invalid - (module (func $type-empty-f32 (result f32) (if (i32.const 0) (then)))) - "type mismatch" -) -(assert_invalid - (module (func $type-empty-f64 (result f64) (if (i32.const 0) (then)))) - "type mismatch" -) +;; (assert_return (invoke "empty" (i32.const 0))) +;; (assert_return (invoke "empty" (i32.const 1))) +;; (assert_return (invoke "empty" (i32.const 100))) +;; (assert_return (invoke "empty" (i32.const -2))) + +;; (assert_return (invoke "singular" (i32.const 0)) (i32.const 8)) +;; (assert_return (invoke "singular" (i32.const 1)) (i32.const 7)) +;; (assert_return (invoke "singular" (i32.const 10)) (i32.const 7)) +;; (assert_return (invoke "singular" (i32.const -10)) (i32.const 7)) + +;; (assert_return (invoke "multi" (i32.const 0)) (i32.const 9)) +;; (assert_return (invoke "multi" (i32.const 1)) (i32.const 8)) +;; (assert_return (invoke "multi" (i32.const 13)) (i32.const 8)) +;; (assert_return (invoke "multi" (i32.const -5)) (i32.const 8)) + +;; (assert_return (invoke "nested" (i32.const 0) (i32.const 0)) (i32.const 11)) +;; (assert_return (invoke "nested" (i32.const 1) (i32.const 0)) (i32.const 10)) +;; (assert_return (invoke "nested" (i32.const 0) (i32.const 1)) (i32.const 10)) +;; (assert_return (invoke "nested" (i32.const 3) (i32.const 2)) (i32.const 9)) +;; (assert_return (invoke "nested" (i32.const 0) (i32.const -100)) (i32.const 10)) +;; (assert_return (invoke "nested" (i32.const 10) (i32.const 10)) (i32.const 9)) +;; (assert_return (invoke "nested" (i32.const 0) (i32.const -1)) (i32.const 10)) +;; (assert_return (invoke "nested" (i32.const -111) (i32.const -2)) (i32.const 9)) + +;; (assert_return (invoke "as-select-first" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-select-first" (i32.const 1)) (i32.const 1)) +;; (assert_return (invoke "as-select-mid" (i32.const 0)) (i32.const 2)) +;; (assert_return (invoke "as-select-mid" (i32.const 1)) (i32.const 2)) +;; (assert_return (invoke "as-select-last" (i32.const 0)) (i32.const 3)) +;; (assert_return (invoke "as-select-last" (i32.const 1)) (i32.const 2)) + +;; (assert_return (invoke "as-loop-first" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-loop-first" (i32.const 1)) (i32.const 1)) +;; (assert_return (invoke "as-loop-mid" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-loop-mid" (i32.const 1)) (i32.const 1)) +;; (assert_return (invoke "as-loop-last" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-loop-last" (i32.const 1)) (i32.const 1)) + +;; (assert_return (invoke "as-if-condition" (i32.const 0)) (i32.const 3)) +;; (assert_return (invoke "as-if-condition" (i32.const 1)) (i32.const 2)) + +;; (assert_return (invoke "as-br_if-first" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-br_if-first" (i32.const 1)) (i32.const 1)) +;; (assert_return (invoke "as-br_if-last" (i32.const 0)) (i32.const 3)) +;; (assert_return (invoke "as-br_if-last" (i32.const 1)) (i32.const 2)) + +;; (assert_return (invoke "as-br_table-first" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-br_table-first" (i32.const 1)) (i32.const 1)) +;; (assert_return (invoke "as-br_table-last" (i32.const 0)) (i32.const 2)) +;; (assert_return (invoke "as-br_table-last" (i32.const 1)) (i32.const 2)) + +;; (assert_return (invoke "as-call_indirect-first" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-call_indirect-first" (i32.const 1)) (i32.const 1)) +;; (assert_return (invoke "as-call_indirect-mid" (i32.const 0)) (i32.const 2)) +;; (assert_return (invoke "as-call_indirect-mid" (i32.const 1)) (i32.const 2)) +;; (assert_return (invoke "as-call_indirect-last" (i32.const 0)) (i32.const 2)) +;; (assert_trap (invoke "as-call_indirect-last" (i32.const 1)) "undefined element") + +;; (assert_return (invoke "as-store-first" (i32.const 0))) +;; (assert_return (invoke "as-store-first" (i32.const 1))) +;; (assert_return (invoke "as-store-last" (i32.const 0))) +;; (assert_return (invoke "as-store-last" (i32.const 1))) + +;; (assert_return (invoke "as-memory.grow-value" (i32.const 0)) (i32.const 1)) +;; (assert_return (invoke "as-memory.grow-value" (i32.const 1)) (i32.const 1)) + +;; (assert_return (invoke "as-call-value" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-call-value" (i32.const 1)) (i32.const 1)) + +;; (assert_return (invoke "as-return-value" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-return-value" (i32.const 1)) (i32.const 1)) + +;; (assert_return (invoke "as-drop-operand" (i32.const 0))) +;; (assert_return (invoke "as-drop-operand" (i32.const 1))) + +;; (assert_return (invoke "as-br-value" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-br-value" (i32.const 1)) (i32.const 1)) + +;; (assert_return (invoke "as-local.set-value" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-local.set-value" (i32.const 1)) (i32.const 1)) + +;; (assert_return (invoke "as-local.tee-value" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-local.tee-value" (i32.const 1)) (i32.const 1)) + +;; (assert_return (invoke "as-global.set-value" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-global.set-value" (i32.const 1)) (i32.const 1)) + +;; (assert_return (invoke "as-load-operand" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-load-operand" (i32.const 1)) (i32.const 0)) + +;; (assert_return (invoke "as-unary-operand" (i32.const 0)) (i32.const 0)) +;; (assert_return (invoke "as-unary-operand" (i32.const 1)) (i32.const 0)) +;; (assert_return (invoke "as-unary-operand" (i32.const -1)) (i32.const 0)) + +;; (assert_return (invoke "as-binary-operand" (i32.const 0) (i32.const 0)) (i32.const 15)) +;; (assert_return (invoke "as-binary-operand" (i32.const 0) (i32.const 1)) (i32.const -12)) +;; (assert_return (invoke "as-binary-operand" (i32.const 1) (i32.const 0)) (i32.const -15)) +;; (assert_return (invoke "as-binary-operand" (i32.const 1) (i32.const 1)) (i32.const 12)) + +;; (assert_return (invoke "as-test-operand" (i32.const 0)) (i32.const 1)) +;; (assert_return (invoke "as-test-operand" (i32.const 1)) (i32.const 0)) + +;; (assert_return (invoke "as-compare-operand" (i32.const 0) (i32.const 0)) (i32.const 1)) +;; (assert_return (invoke "as-compare-operand" (i32.const 0) (i32.const 1)) (i32.const 0)) +;; (assert_return (invoke "as-compare-operand" (i32.const 1) (i32.const 0)) (i32.const 1)) +;; (assert_return (invoke "as-compare-operand" (i32.const 1) (i32.const 1)) (i32.const 0)) + +;; (assert_return (invoke "break-bare") (i32.const 19)) +;; (assert_return (invoke "break-value" (i32.const 1)) (i32.const 18)) +;; (assert_return (invoke "break-value" (i32.const 0)) (i32.const 21)) + +;; (assert_return (invoke "effects" (i32.const 1)) (i32.const -14)) +;; (assert_return (invoke "effects" (i32.const 0)) (i32.const -6)) -(assert_invalid - (module (func $type-empty-i32 (result i32) (if (i32.const 0) (then) (else)))) - "type mismatch" -) -(assert_invalid - (module (func $type-empty-i64 (result i64) (if (i32.const 0) (then) (else)))) - "type mismatch" -) -(assert_invalid - (module (func $type-empty-f32 (result f32) (if (i32.const 0) (then) (else)))) - "type mismatch" -) -(assert_invalid - (module (func $type-empty-f64 (result f64) (if (i32.const 0) (then) (else)))) - "type mismatch" -) +;; (assert_invalid +;; (module (func $type-empty-i32 (result i32) (if (i32.const 0) (then)))) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-empty-i64 (result i64) (if (i32.const 0) (then)))) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-empty-f32 (result f32) (if (i32.const 0) (then)))) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-empty-f64 (result f64) (if (i32.const 0) (then)))) +;; "type mismatch" +;; ) -(assert_invalid - (module (func $type-then-value-num-vs-void - (if (i32.const 1) (then (i32.const 1))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-then-value-num-vs-void - (if (i32.const 1) (then (i32.const 1)) (else)) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-else-value-num-vs-void - (if (i32.const 1) (then) (else (i32.const 1))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-both-value-num-vs-void - (if (i32.const 1) (then (i32.const 1)) (else (i32.const 1))) - )) - "type mismatch" -) +;; (assert_invalid +;; (module (func $type-empty-i32 (result i32) (if (i32.const 0) (then) (else)))) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-empty-i64 (result i64) (if (i32.const 0) (then) (else)))) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-empty-f32 (result f32) (if (i32.const 0) (then) (else)))) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-empty-f64 (result f64) (if (i32.const 0) (then) (else)))) +;; "type mismatch" +;; ) -(assert_invalid - (module (func $type-then-value-empty-vs-num (result i32) - (if (result i32) (i32.const 1) (then) (else (i32.const 0))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-then-value-empty-vs-num (result i32) - (if (result i32) (i32.const 1) (then (i32.const 0)) (else)) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-both-value-empty-vs-num (result i32) - (if (result i32) (i32.const 1) (then) (else)) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-no-else-vs-num (result i32) - (if (result i32) (i32.const 1) (then (i32.const 1))) - )) - "type mismatch" -) +;; (assert_invalid +;; (module (func $type-then-value-num-vs-void +;; (if (i32.const 1) (then (i32.const 1))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-then-value-num-vs-void +;; (if (i32.const 1) (then (i32.const 1)) (else)) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-else-value-num-vs-void +;; (if (i32.const 1) (then) (else (i32.const 1))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-both-value-num-vs-void +;; (if (i32.const 1) (then (i32.const 1)) (else (i32.const 1))) +;; )) +;; "type mismatch" +;; ) -(assert_invalid - (module (func $type-then-value-void-vs-num (result i32) - (if (result i32) (i32.const 1) (then (nop)) (else (i32.const 0))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-then-value-void-vs-num (result i32) - (if (result i32) (i32.const 1) (then (i32.const 0)) (else (nop))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-both-value-void-vs-num (result i32) - (if (result i32) (i32.const 1) (then (nop)) (else (nop))) - )) - "type mismatch" -) +;; (assert_invalid +;; (module (func $type-then-value-empty-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then) (else (i32.const 0))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-then-value-empty-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (i32.const 0)) (else)) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-both-value-empty-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then) (else)) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-no-else-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (i32.const 1))) +;; )) +;; "type mismatch" +;; ) -(assert_invalid - (module (func $type-then-value-num-vs-num (result i32) - (if (result i32) (i32.const 1) (then (i64.const 1)) (else (i32.const 1))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-then-value-num-vs-num (result i32) - (if (result i32) (i32.const 1) (then (i32.const 1)) (else (i64.const 1))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-both-value-num-vs-num (result i32) - (if (result i32) (i32.const 1) (then (i64.const 1)) (else (i64.const 1))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-both-different-value-num-vs-num (result i32) - (if (result i32) (i32.const 1) (then (i64.const 1)) (else (f64.const 1))) - )) - "type mismatch" -) +;; (assert_invalid +;; (module (func $type-then-value-void-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (nop)) (else (i32.const 0))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-then-value-void-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (i32.const 0)) (else (nop))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-both-value-void-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (nop)) (else (nop))) +;; )) +;; "type mismatch" +;; ) -(assert_invalid - (module (func $type-then-value-unreached-select (result i32) - (if (result i64) - (i32.const 0) - (then (select (unreachable) (unreachable) (unreachable))) - (else (i64.const 0)) - ) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-else-value-unreached-select (result i32) - (if (result i64) - (i32.const 1) - (then (i64.const 0)) - (else (select (unreachable) (unreachable) (unreachable))) - ) - )) - "type mismatch" -) +;; (assert_invalid +;; (module (func $type-then-value-num-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (i64.const 1)) (else (i32.const 1))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-then-value-num-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (i32.const 1)) (else (i64.const 1))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-both-value-num-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (i64.const 1)) (else (i64.const 1))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-both-different-value-num-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (i64.const 1)) (else (f64.const 1))) +;; )) +;; "type mismatch" +;; ) -;; We don't pass this test because we type the `if` as unreachable. +;; (assert_invalid +;; (module (func $type-then-value-unreached-select (result i32) +;; (if (result i64) +;; (i32.const 0) +;; (then (select (unreachable) (unreachable) (unreachable))) +;; (else (i64.const 0)) +;; ) +;; )) +;; "type mismatch" +;; ) ;; (assert_invalid ;; (module (func $type-else-value-unreached-select (result i32) ;; (if (result i64) ;; (i32.const 1) -;; (then (select (unreachable) (unreachable) (unreachable))) +;; (then (i64.const 0)) ;; (else (select (unreachable) (unreachable) (unreachable))) ;; ) ;; )) ;; "type mismatch" ;; ) -(assert_invalid - (module (func $type-then-break-last-void-vs-num (result i32) - (if (result i32) (i32.const 1) (then (br 0)) (else (i32.const 1))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-else-break-last-void-vs-num (result i32) - (if (result i32) (i32.const 1) (then (i32.const 1)) (else (br 0))) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-then-break-empty-vs-num (result i32) - (if (result i32) (i32.const 1) - (then (br 0) (i32.const 1)) - (else (i32.const 1)) - ) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-else-break-empty-vs-num (result i32) - (if (result i32) (i32.const 1) - (then (i32.const 1)) - (else (br 0) (i32.const 1)) - ) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-then-break-void-vs-num (result i32) - (if (result i32) (i32.const 1) - (then (br 0 (nop)) (i32.const 1)) - (else (i32.const 1)) - ) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-else-break-void-vs-num (result i32) - (if (result i32) (i32.const 1) - (then (i32.const 1)) - (else (br 0 (nop)) (i32.const 1)) - ) - )) - "type mismatch" -) +;; ;; We don't pass this test because we type the `if` as unreachable. +;; ;; (assert_invalid +;; ;; (module (func $type-else-value-unreached-select (result i32) +;; ;; (if (result i64) +;; ;; (i32.const 1) +;; ;; (then (select (unreachable) (unreachable) (unreachable))) +;; ;; (else (select (unreachable) (unreachable) (unreachable))) +;; ;; ) +;; ;; )) +;; ;; "type mismatch" +;; ;; ) -(assert_invalid - (module (func $type-then-break-num-vs-num (result i32) - (if (result i32) (i32.const 1) - (then (br 0 (i64.const 1)) (i32.const 1)) - (else (i32.const 1)) - ) - )) - "type mismatch" -) -(assert_invalid - (module (func $type-else-break-num-vs-num (result i32) - (if (result i32) (i32.const 1) - (then (i32.const 1)) - (else (br 0 (i64.const 1)) (i32.const 1)) - ) - )) - "type mismatch" -) +;; (assert_invalid +;; (module (func $type-then-break-last-void-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (br 0)) (else (i32.const 1))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-else-break-last-void-vs-num (result i32) +;; (if (result i32) (i32.const 1) (then (i32.const 1)) (else (br 0))) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-then-break-empty-vs-num (result i32) +;; (if (result i32) (i32.const 1) +;; (then (br 0) (i32.const 1)) +;; (else (i32.const 1)) +;; ) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-else-break-empty-vs-num (result i32) +;; (if (result i32) (i32.const 1) +;; (then (i32.const 1)) +;; (else (br 0) (i32.const 1)) +;; ) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-then-break-void-vs-num (result i32) +;; (if (result i32) (i32.const 1) +;; (then (br 0 (nop)) (i32.const 1)) +;; (else (i32.const 1)) +;; ) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-else-break-void-vs-num (result i32) +;; (if (result i32) (i32.const 1) +;; (then (i32.const 1)) +;; (else (br 0 (nop)) (i32.const 1)) +;; ) +;; )) +;; "type mismatch" +;; ) -(assert_invalid - (module - (func $type-condition-empty - (if (then)) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-block - (i32.const 0) - (block (if (then))) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-loop - (i32.const 0) - (loop (if (then))) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-then - (i32.const 0) (i32.const 0) - (if (then (if (then)))) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-else - (i32.const 0) (i32.const 0) - (if (result i32) (then (i32.const 0)) (else (if (then)) (i32.const 0))) - (drop) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-br - (i32.const 0) - (block (br 0 (if(then))) (drop)) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-br_if - (i32.const 0) - (block (br_if 0 (if(then)) (i32.const 1)) (drop)) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-br_table - (i32.const 0) - (block (br_table 0 (if(then))) (drop)) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-return - (return (if(then))) (drop) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-select - (select (if(then)) (i32.const 1) (i32.const 2)) (drop) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-call - (call 1 (if(then))) (drop) - ) - (func (param i32) (result i32) (local.get 0)) - ) - "type mismatch" -) -(assert_invalid - (module - (func $f (param i32) (result i32) (local.get 0)) - (type $sig (func (param i32) (result i32))) - (table funcref (elem $f)) - (func $type-condition-empty-in-call_indirect - (block (result i32) - (call_indirect (type $sig) - (if(then)) (i32.const 0) - ) - (drop) - ) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-local.set - (local i32) - (local.set 0 (if(then))) (local.get 0) (drop) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (func $type-condition-empty-in-local.tee - (local i32) - (local.tee 0 (if(then))) (drop) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (global $x (mut i32) (i32.const 0)) - (func $type-condition-empty-in-global.set - (global.set $x (if(then))) (global.get $x) (drop) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (memory 0) - (func $type-condition-empty-in-memory.grow - (memory.grow (if(then))) (drop) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (memory 0) - (func $type-condition-empty-in-load - (i32.load (if(then))) (drop) - ) - ) - "type mismatch" -) -(assert_invalid - (module - (memory 1) - (func $type-condition-empty-in-store - (i32.store (if(then)) (i32.const 1)) - ) - ) - "type mismatch" -) +;; (assert_invalid +;; (module (func $type-then-break-num-vs-num (result i32) +;; (if (result i32) (i32.const 1) +;; (then (br 0 (i64.const 1)) (i32.const 1)) +;; (else (i32.const 1)) +;; ) +;; )) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module (func $type-else-break-num-vs-num (result i32) +;; (if (result i32) (i32.const 1) +;; (then (i32.const 1)) +;; (else (br 0 (i64.const 1)) (i32.const 1)) +;; ) +;; )) +;; "type mismatch" +;; ) + +;; (assert_invalid +;; (module +;; (func $type-condition-empty +;; (if (then)) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-block +;; (i32.const 0) +;; (block (if (then))) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-loop +;; (i32.const 0) +;; (loop (if (then))) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-then +;; (i32.const 0) (i32.const 0) +;; (if (then (if (then)))) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-else +;; (i32.const 0) (i32.const 0) +;; (if (result i32) (then (i32.const 0)) (else (if (then)) (i32.const 0))) +;; (drop) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-br +;; (i32.const 0) +;; (block (br 0 (if(then))) (drop)) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-br_if +;; (i32.const 0) +;; (block (br_if 0 (if(then)) (i32.const 1)) (drop)) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-br_table +;; (i32.const 0) +;; (block (br_table 0 (if(then))) (drop)) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-return +;; (return (if(then))) (drop) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-select +;; (select (if(then)) (i32.const 1) (i32.const 2)) (drop) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-call +;; (call 1 (if(then))) (drop) +;; ) +;; (func (param i32) (result i32) (local.get 0)) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $f (param i32) (result i32) (local.get 0)) +;; (type $sig (func (param i32) (result i32))) +;; (table funcref (elem $f)) +;; (func $type-condition-empty-in-call_indirect +;; (block (result i32) +;; (call_indirect (type $sig) +;; (if(then)) (i32.const 0) +;; ) +;; (drop) +;; ) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-local.set +;; (local i32) +;; (local.set 0 (if(then))) (local.get 0) (drop) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (func $type-condition-empty-in-local.tee +;; (local i32) +;; (local.tee 0 (if(then))) (drop) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (global $x (mut i32) (i32.const 0)) +;; (func $type-condition-empty-in-global.set +;; (global.set $x (if(then))) (global.get $x) (drop) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (memory 0) +;; (func $type-condition-empty-in-memory.grow +;; (memory.grow (if(then))) (drop) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (memory 0) +;; (func $type-condition-empty-in-load +;; (i32.load (if(then))) (drop) +;; ) +;; ) +;; "type mismatch" +;; ) +;; (assert_invalid +;; (module +;; (memory 1) +;; (func $type-condition-empty-in-store +;; (i32.store (if(then)) (i32.const 1)) +;; ) +;; ) +;; "type mismatch" +;; ) -(assert_malformed - (module quote "(func i32.const 0 if end $l)") - "mismatching label" -) -(assert_malformed - (module quote "(func i32.const 0 if $a end $l)") - "mismatching label" -) -(assert_malformed - (module quote "(func i32.const 0 if else $l end)") - "mismatching label" -) -(assert_malformed - (module quote "(func i32.const 0 if $a else $l end)") - "mismatching label" -) -(assert_malformed - (module quote "(func i32.const 0 if else end $l)") - "mismatching label" -) -(assert_malformed - (module quote "(func i32.const 0 if else $l end $l)") - "mismatching label" -) -(assert_malformed - (module quote "(func i32.const 0 if else $l1 end $l2)") - "mismatching label" -) -(assert_malformed - (module quote "(func i32.const 0 if $a else end $l)") - "mismatching label" -) -(assert_malformed - (module quote "(func i32.const 0 if $a else $a end $l)") - "mismatching label" -) -(assert_malformed - (module quote "(func i32.const 0 if $a else $l end $l)") - "mismatching label" -) \ No newline at end of file +;; (assert_malformed +;; (module quote "(func i32.const 0 if end $l)") +;; "mismatching label" +;; ) +;; (assert_malformed +;; (module quote "(func i32.const 0 if $a end $l)") +;; "mismatching label" +;; ) +;; (assert_malformed +;; (module quote "(func i32.const 0 if else $l end)") +;; "mismatching label" +;; ) +;; (assert_malformed +;; (module quote "(func i32.const 0 if $a else $l end)") +;; "mismatching label" +;; ) +;; (assert_malformed +;; (module quote "(func i32.const 0 if else end $l)") +;; "mismatching label" +;; ) +;; (assert_malformed +;; (module quote "(func i32.const 0 if else $l end $l)") +;; "mismatching label" +;; ) +;; (assert_malformed +;; (module quote "(func i32.const 0 if else $l1 end $l2)") +;; "mismatching label" +;; ) +;; (assert_malformed +;; (module quote "(func i32.const 0 if $a else end $l)") +;; "mismatching label" +;; ) +;; (assert_malformed +;; (module quote "(func i32.const 0 if $a else $a end $l)") +;; "mismatching label" +;; ) +;; (assert_malformed +;; (module quote "(func i32.const 0 if $a else $l end $l)") +;; "mismatching label" +;; ) \ No newline at end of file From 9bff1fe202bd209efaede761447aa435287cb26a Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 11 Mar 2026 21:50:37 +0000 Subject: [PATCH 07/12] wip gemini --- src/ir/runtime-memory.h | 272 +++++++++++--- src/shell-interface.h | 160 --------- src/tools/wasm-ctor-eval.cpp | 167 ++++----- src/wasm-interpreter.h | 679 +++++++++-------------------------- src/wasm.h | 12 +- 5 files changed, 469 insertions(+), 821 deletions(-) diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index b03facbf9c7..9e472ea9b88 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -17,6 +17,7 @@ #ifndef wasm_ir_runtime_memory_h #define wasm_ir_runtime_memory_h +#include "fp16.h" #include "interpreter/exception.h" #include "wasm.h" @@ -26,25 +27,11 @@ namespace { void trapIfGt(uint64_t lhs, uint64_t rhs, const char* msg) { if (lhs > rhs) { - // std::stringstream ss; std::cerr << msg << ": " << lhs << " > " << rhs << "\n"; - // ss.str(); - // std::cerr< 1) { -// if (addr & (bytes - 1)) { -// // "unaligned atomic operation" -// throw TrapException{}; -// } -// } -// } - Address getFinalAddress(Address addr, Address offset, Index bytes, @@ -52,69 +39,254 @@ Address getFinalAddress(Address addr, trapIfGt(offset, memorySizeBytes, "offset > memory"); trapIfGt(addr, memorySizeBytes - offset, "final > memory"); - // TODO: overflow here? addr = size_t(addr) + offset; trapIfGt(bytes, memorySizeBytes, "bytes > memory"); - // checkLoadAddress(addr, bytes, memorySizeBytes); trapIfGt(addr, memorySizeBytes - bytes, "highest > memory"); return addr; } -template static bool aligned(const uint8_t* address) { - static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2"); - return 0 == (reinterpret_cast(address) & (sizeof(T) - 1)); -} - -template -T asdf(const std::vector& memory, size_t address) { - if (aligned(&memory[address])) { - return *reinterpret_cast(&memory[address]); - } else { - T loaded; - std::memcpy(&loaded, &memory[address], sizeof(T)); - return loaded; - } -} } // namespace -// TODO split into pure virtual class class RuntimeMemory { public: - // todo: might want a constructor that takes data segments - RuntimeMemory(Memory memory) - : memoryDefinition(std::move(memory)), memory(memory.initialByteSize(), 0) { - // this->memory.reserve(memory.initialByteSize()); + RuntimeMemory(Memory memory) : memoryDefinition(std::move(memory)) { + resize(memoryDefinition.initialByteSize()); } virtual ~RuntimeMemory() = default; - // variants for load8 etc? - // Do we care about the order here? - // todo: address types? Address::address32_t is strange - // todo: type of offset? virtual Literal load(Address addr, Address offset, uint8_t byteCount, - MemoryOrder order) const { - Address final = getFinalAddress(addr, offset, byteCount, memory.size()); - (void) final; + MemoryOrder order, + Type type, + bool signed_) const { + Address final = getFinalAddress(addr, offset, byteCount, size()); + if (order != MemoryOrder::Unordered) { + checkAtomicAddress(final, byteCount, size()); + } + switch (type.getBasic()) { + case Type::i32: { + switch (byteCount) { + case 1: + return signed_ ? Literal((int32_t)get(final)) + : Literal((int32_t)get(final)); + case 2: + return signed_ ? Literal((int32_t)get(final)) + : Literal((int32_t)get(final)); + case 4: + return Literal((int32_t)get(final)); + default: + WASM_UNREACHABLE("invalid size"); + } + } + case Type::i64: { + switch (byteCount) { + case 1: + return signed_ ? Literal((int64_t)get(final)) + : Literal((int64_t)get(final)); + case 2: + return signed_ ? Literal((int64_t)get(final)) + : Literal((int64_t)get(final)); + case 4: + return signed_ ? Literal((int64_t)get(final)) + : Literal((int64_t)get(final)); + case 8: + return Literal((int64_t)get(final)); + default: + WASM_UNREACHABLE("invalid size"); + } + } + case Type::f32: { + switch (byteCount) { + case 2: + return Literal(bit_cast( + fp16_ieee_to_fp32_value(get(final)))) + .castToF32(); + case 4: + return Literal(get(final)).castToF32(); + default: + WASM_UNREACHABLE("invalid size"); + } + } + case Type::f64: + return Literal(get(final)).castToF64(); + case Type::v128: + return Literal(get>(final).data()); + default: + WASM_UNREACHABLE("unexpected type"); + } + } + + virtual void store(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Literal value, + Type type) { + Address final = getFinalAddress(addr, offset, byteCount, size()); + if (order != MemoryOrder::Unordered) { + checkAtomicAddress(final, byteCount, size()); + } + switch (type.getBasic()) { + case Type::i32: { + switch (byteCount) { + case 1: + set(final, value.geti32()); + break; + case 2: + set(final, value.geti32()); + break; + case 4: + set(final, value.geti32()); + break; + default: + WASM_UNREACHABLE("invalid size"); + } + break; + } + case Type::i64: { + switch (byteCount) { + case 1: + set(final, value.geti64()); + break; + case 2: + set(final, value.geti64()); + break; + case 4: + set(final, value.geti64()); + break; + case 8: + set(final, value.geti64()); + break; + default: + WASM_UNREACHABLE("invalid size"); + } + break; + } + case Type::f32: { + switch (byteCount) { + case 2: + set(final, + fp16_ieee_from_fp32_value( + bit_cast(value.reinterpreti32()))); + break; + case 4: + set(final, value.reinterpreti32()); + break; + default: + WASM_UNREACHABLE("invalid size"); + } + break; + } + case Type::f64: + set(final, value.reinterpreti64()); + break; + case Type::v128: + set>(final, value.getv128()); + break; + default: + WASM_UNREACHABLE("unexpected type"); + } + } + + virtual bool grow(Address delta) { + Address pageSize = memoryDefinition.pageSize(); + Address oldPages = intendedSize / pageSize; + Address newPages = oldPages + delta; + if (newPages > memoryDefinition.max && memoryDefinition.hasMax()) { + return false; + } + // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the + // interpreter. + if (newPages * pageSize > 1024 * 1024 * 1024) { + return false; + } + resize(newPages * pageSize); + return true; + } + + virtual Address size() const { return intendedSize; } + + virtual void + init(Address dest, Address src, Address byteCount, const DataSegment* data) { + trapIfGt(uint64_t(src), uint64_t(data->data.size()), "src > data"); + trapIfGt(uint64_t(byteCount), + uint64_t(data->data.size() - src), + "src + size > data"); + Address final = getFinalAddress(dest, 0, byteCount, size()); + std::memcpy(&memory[final], &data->data[src], byteCount); + } - // return memory.get() + virtual void + copy(Address dest, Address src, Address byteCount, const RuntimeMemory* srcMemory) { + Address finalDest = getFinalAddress(dest, 0, byteCount, size()); + Address finalSrc = getFinalAddress(src, 0, byteCount, srcMemory->size()); + std::memmove(&memory[finalDest], &srcMemory->memory[finalSrc], byteCount); + } - return Literal(asdf(memory, (size_t) final)); + virtual void fill(Address dest, uint8_t value, Address byteCount) { + Address final = getFinalAddress(dest, 0, byteCount, size()); + std::memset(&memory[final], value, byteCount); + } - // return Literal(1); + void resize(size_t newSize) { + intendedSize = newSize; + const size_t minSize = 1 << 12; + size_t oldAllocatedSize = memory.size(); + memory.resize(std::max(minSize, newSize), 0); + if (newSize < oldAllocatedSize && newSize < minSize) { + std::memset(&memory[newSize], 0, minSize - newSize); + } } - virtual Literal load(uint64_t addr) const { return {}; } const Memory* getDefinition() const { return &memoryDefinition; } + template T get(size_t address) const { + if (aligned(&memory[address])) { + return *reinterpret_cast(&memory[address]); + } else { + T loaded; + std::memcpy(&loaded, &memory[address], sizeof(T)); + return loaded; + } + } + + template void set(size_t address, T value) { + if (aligned(&memory[address])) { + *reinterpret_cast(&memory[address]) = value; + } else { + std::memcpy(&memory[address], &value, sizeof(T)); + } + } + + void checkLoadAddress(Address addr, Index bytes, Address memorySizeBytes) const { + trapIfGt(addr, memorySizeBytes - bytes, "highest > memory"); + } + + void + checkAtomicAddress(Address addr, Index bytes, Address memorySizeBytes) const { + checkLoadAddress(addr, bytes, memorySizeBytes); + // Unaligned atomics trap. + if (bytes > 1) { + if (addr & (bytes - 1)) { + std::cerr << "unaligned atomic operation: " << addr << " " << bytes + << "\n"; + throw TrapException{}; + } + } + } + protected: const Memory memoryDefinition; - -private: std::vector memory; + Address intendedSize = 0; + + template static bool aligned(const uint8_t* address) { + static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2"); + return 0 == (reinterpret_cast(address) & (sizeof(T) - 1)); + } }; class RealRuntimeMemory : public RuntimeMemory { @@ -124,4 +296,4 @@ class RealRuntimeMemory : public RuntimeMemory { } // namespace wasm -#endif // wasm_ir_runtime_memory_h \ No newline at end of file +#endif // wasm_ir_runtime_memory_h diff --git a/src/shell-interface.h b/src/shell-interface.h index 9a16499f4e3..f4c3d697af8 100644 --- a/src/shell-interface.h +++ b/src/shell-interface.h @@ -33,56 +33,6 @@ namespace wasm { struct ShellExternalInterface : ModuleRunner::ExternalInterface { - // The underlying memory can be accessed through unaligned pointers which - // isn't well-behaved in C++. WebAssembly nonetheless expects it to behave - // properly. Avoid emitting unaligned load/store by checking for alignment - // explicitly, and performing memcpy if unaligned. - // - // The allocated memory tries to have the same alignment as the memory being - // simulated. - class Memory { - // Use char because it doesn't run afoul of aliasing rules. - std::vector memory; - template static bool aligned(const char* address) { - static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2"); - return 0 == (reinterpret_cast(address) & (sizeof(T) - 1)); - } - - public: - Memory() = default; - void resize(size_t newSize) { - // Ensure the smallest allocation is large enough that most allocators - // will provide page-aligned storage. This hopefully allows the - // interpreter's memory to be as aligned as the memory being simulated, - // ensuring that the performance doesn't needlessly degrade. - // - // The code is optimistic this will work until WG21's p0035r0 happens. - const size_t minSize = 1 << 12; - size_t oldSize = memory.size(); - memory.resize(std::max(minSize, newSize)); - if (newSize < oldSize && newSize < minSize) { - std::memset(&memory[newSize], 0, minSize - newSize); - } - } - template void set(size_t address, T value) { - if (aligned(&memory[address])) { - *reinterpret_cast(&memory[address]) = value; - } else { - std::memcpy(&memory[address], &value, sizeof(T)); - } - } - template T get(size_t address) { - if (aligned(&memory[address])) { - return *reinterpret_cast(&memory[address]); - } else { - T loaded; - std::memcpy(&loaded, &memory[address], sizeof(T)); - return loaded; - } - } - }; - - std::map memories; std::map> linkedInstances; ShellExternalInterface( @@ -108,14 +58,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { return ret; } - void init(Module& wasm, ModuleRunner& instance) override { - ModuleUtils::iterDefinedMemories(wasm, [&](wasm::Memory* memory) { - auto shellMemory = Memory(); - shellMemory.resize(memory->initial << memory->pageSizeLog2); - memories[memory->name] = shellMemory; - }); - } - Literal getImportedFunction(Function* import) override { // TODO: We should perhaps restrict the types with which the well-known // functions can be imported. @@ -151,108 +93,6 @@ struct ShellExternalInterface : ModuleRunner::ExternalInterface { return Literal::makeFunc(import->name, import->type); } - int8_t load8s(Address addr, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - return memory.get(addr); - } - uint8_t load8u(Address addr, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - return memory.get(addr); - } - int16_t load16s(Address addr, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - return memory.get(addr); - } - uint16_t load16u(Address addr, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - return memory.get(addr); - } - int32_t load32s(Address addr, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - return memory.get(addr); - } - uint32_t load32u(Address addr, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - return memory.get(addr); - } - int64_t load64s(Address addr, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - return memory.get(addr); - } - uint64_t load64u(Address addr, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - return memory.get(addr); - } - std::array load128(Address addr, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - return memory.get>(addr); - } - - void store8(Address addr, int8_t value, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - memory.set(addr, value); - } - void store16(Address addr, int16_t value, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - memory.set(addr, value); - } - void store32(Address addr, int32_t value, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - memory.set(addr, value); - } - void store64(Address addr, int64_t value, Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - memory.set(addr, value); - } - void store128(Address addr, - const std::array& value, - Name memoryName) override { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - memory.set>(addr, value); - } - bool - growMemory(Name memoryName, Address /*oldSize*/, Address newSize) override { - // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the - // interpreter. - if (newSize > 1024 * 1024 * 1024) { - return false; - } - auto it = memories.find(memoryName); - if (it == memories.end()) { - trap("growMemory on non-existing memory"); - } - auto& memory = it->second; - memory.resize(newSize); - return true; - } void trap(std::string_view why) override { std::cout << "[trap " << why << "]\n"; throw TrapException(); diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index b849ae29f40..6fc32bef809 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -184,6 +184,44 @@ class EvallingRuntimeTable : public RuntimeTable { const std::function makeFuncData; }; +class CtorEvalRuntimeMemory : public RuntimeMemory { +public: + using RuntimeMemory::RuntimeMemory; + + // override to grow on access + Literal load(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Type type, + bool signed_) const override { + const_cast(this)->ensureCapacity(addr + offset + + byteCount); + return RuntimeMemory::load(addr, offset, byteCount, order, type, signed_); + } + + void store(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Literal value, + Type type) override { + ensureCapacity(addr + offset + byteCount); + RuntimeMemory::store(addr, offset, byteCount, order, value, type); + } + + void ensureCapacity(Address size) { + if (size > memory.size()) { + if (size > 100 * 1024 * 1024) { // MaximumMemory + throw FailToEvalException("excessively high memory address accessed"); + } + resize(size); + } + } + + std::vector& getBuffer() { return memory; } +}; + class EvallingModuleRunner : public ModuleRunnerBase { public: EvallingModuleRunner( @@ -204,6 +242,9 @@ class EvallingModuleRunner : public ModuleRunnerBase { instanceInitialized, this->wasm, [this](Name name, Type type) { return makeFuncData(name, type); }); + }, + [](Memory memory) { + return std::make_unique(memory); }) {} Flow visitGlobalGet(GlobalGet* curr) { @@ -291,9 +332,6 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { EvallingModuleRunner* instance; std::map> linkedInstances; - // A representation of the contents of wasm memory as we execute. - std::unordered_map> memories; - // All the names of globals we've seen in the module. We cannot reuse these. // We must track these manually as we will be adding more, and as we do so we // also reorder them, so we remove and re-add globals, which means the module @@ -315,23 +353,13 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { void applyToModule() { clearApplyState(); - // If nothing was ever written to memories then there is nothing to update. - if (!memories.empty()) { - applyMemoryToModule(); - } - + applyMemoryToModule(); applyGlobalsToModule(); } void init(Module& wasm_, EvallingModuleRunner& instance_) override { wasm = &wasm_; instance = &instance_; - for (auto& memory : wasm->memories) { - if (!memory->imported()) { - std::vector data; - memories[memory->name] = data; - } - } for (auto& global : wasm->globals) { usedGlobalNames.insert(global->name); @@ -352,7 +380,13 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { // Write out a count of i32(0) and return __WASI_ERRNO_SUCCESS // (0). - store32(arguments[0].geti32(), 0, wasm->memories[0]->name); + auto* memory = instance->allMemories[wasm->memories[0]->name]; + memory->store(arguments[0].geti32(), + 0, + 4, + MemoryOrder::Unordered, + Literal(int32_t(0)), + Type::i32); return {Literal(int32_t(0))}; } @@ -374,7 +408,13 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { // Write out an argc of i32(0) and return a __WASI_ERRNO_SUCCESS // (0). - store32(arguments[0].geti32(), 0, wasm->memories[0]->name); + auto* memory = instance->allMemories[wasm->memories[0]->name]; + memory->store(arguments[0].geti32(), + 0, + 4, + MemoryOrder::Unordered, + Literal(int32_t(0)), + Type::i32); return {Literal(int32_t(0))}; } @@ -411,58 +451,6 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { import->type); } - int8_t load8s(Address addr, Name memoryName) override { - return doLoad(addr, memoryName); - } - uint8_t load8u(Address addr, Name memoryName) override { - return doLoad(addr, memoryName); - } - int16_t load16s(Address addr, Name memoryName) override { - return doLoad(addr, memoryName); - } - uint16_t load16u(Address addr, Name memoryName) override { - return doLoad(addr, memoryName); - } - int32_t load32s(Address addr, Name memoryName) override { - return doLoad(addr, memoryName); - } - uint32_t load32u(Address addr, Name memoryName) override { - return doLoad(addr, memoryName); - } - int64_t load64s(Address addr, Name memoryName) override { - return doLoad(addr, memoryName); - } - uint64_t load64u(Address addr, Name memoryName) override { - return doLoad(addr, memoryName); - } - std::array load128(Address addr, Name memoryName) override { - return doLoad>(addr, memoryName); - } - - void store8(Address addr, int8_t value, Name memoryName) override { - doStore(addr, value, memoryName); - } - void store16(Address addr, int16_t value, Name memoryName) override { - doStore(addr, value, memoryName); - } - void store32(Address addr, int32_t value, Name memoryName) override { - doStore(addr, value, memoryName); - } - void store64(Address addr, int64_t value, Name memoryName) override { - doStore(addr, value, memoryName); - } - void store128(Address addr, - const std::array& value, - Name memoryName) override { - doStore>(addr, value, memoryName); - } - - bool growMemory(Name memoryName, - Address /*oldSize*/, - Address /*newSize*/) override { - throw FailToEvalException("grow memory"); - } - void trap(std::string_view why) override { throw FailToEvalException(std::string("trap: ") + std::string(why)); } @@ -478,42 +466,6 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { } private: - // We limit the size of memory to some reasonable amount. We handle memory in - // a linear/dense manner, so when we see a write to address X we allocate X - // memory to represent that, and so very high addresses can lead to OOM. In - // practice, ctor-eval should only run on low addresses anyhow, since static - // memory tends to be reasonably-sized and mallocs start at the start of the - // heap, so it's simpler to add an arbitrary limit here to avoid OOMs for now. - const size_t MaximumMemory = 100 * 1024 * 1024; - - // TODO: handle unaligned too, see shell-interface - void* getMemory(Address address, Name memoryName, size_t size) { - auto it = memories.find(memoryName); - assert(it != memories.end()); - auto& memory = it->second; - // resize the memory buffer as needed. - auto max = address + size; - if (max > memory.size()) { - if (max > MaximumMemory) { - throw FailToEvalException("excessively high memory address accessed"); - } - memory.resize(max); - } - return &memory[address]; - } - - template void doStore(Address address, T value, Name memoryName) { - // Use memcpy to avoid UB if unaligned. - memcpy(getMemory(address, memoryName, sizeof(T)), &value, sizeof(T)); - } - - template T doLoad(Address address, Name memoryName) { - // Use memcpy to avoid UB if unaligned. - T ret; - memcpy(&ret, getMemory(address, memoryName, sizeof(T)), sizeof(T)); - return ret; - } - // Clear the state of the operation of applying the interpreter's runtime // information into the module. // @@ -549,7 +501,12 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { // Copy the current memory contents after execution into the Module's // memory. - segment->data = memories[memory->name]; + auto* runtimeMemory = + static_cast(instance->allMemories[memory->name]); + segment->data.resize(runtimeMemory->getBuffer().size()); + std::memcpy(segment->data.data(), + runtimeMemory->getBuffer().data(), + runtimeMemory->getBuffer().size()); } // Serializing GC data requires more work than linear memory, because diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index e1b7c18befa..d44dce58fb7 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3009,185 +3009,9 @@ class ModuleRunnerBase : public ExpressionRunner { virtual ~ExternalInterface() = default; virtual void init(Module& wasm, SubType& instance) {} virtual Literal getImportedFunction(Function* import) = 0; - virtual bool growMemory(Name name, Address oldSize, Address newSize) = 0; virtual void trap(std::string_view why) = 0; virtual void hostLimit(std::string_view why) = 0; virtual void throwException(const WasmException& exn) = 0; - - // the default impls for load and store switch on the sizes. you can either - // customize load/store, or the sub-functions which they call - virtual Literal load(Load* load, Address addr, Name memory) { - switch (load->type.getBasic()) { - case Type::i32: { - switch (load->bytes) { - case 1: - return load->signed_ ? Literal((int32_t)load8s(addr, memory)) - : Literal((int32_t)load8u(addr, memory)); - case 2: - return load->signed_ ? Literal((int32_t)load16s(addr, memory)) - : Literal((int32_t)load16u(addr, memory)); - case 4: - return Literal((int32_t)load32s(addr, memory)); - default: - WASM_UNREACHABLE("invalid size"); - } - break; - } - case Type::i64: { - switch (load->bytes) { - case 1: - return load->signed_ ? Literal((int64_t)load8s(addr, memory)) - : Literal((int64_t)load8u(addr, memory)); - case 2: - return load->signed_ ? Literal((int64_t)load16s(addr, memory)) - : Literal((int64_t)load16u(addr, memory)); - case 4: - return load->signed_ ? Literal((int64_t)load32s(addr, memory)) - : Literal((int64_t)load32u(addr, memory)); - case 8: - return Literal((int64_t)load64s(addr, memory)); - default: - WASM_UNREACHABLE("invalid size"); - } - break; - } - case Type::f32: { - switch (load->bytes) { - case 2: { - // Convert the float16 to float32 and store the binary - // representation. - return Literal(bit_cast( - fp16_ieee_to_fp32_value(load16u(addr, memory)))) - .castToF32(); - } - case 4: - return Literal(load32u(addr, memory)).castToF32(); - default: - WASM_UNREACHABLE("invalid size"); - } - break; - } - case Type::f64: - return Literal(load64u(addr, memory)).castToF64(); - case Type::v128: - return Literal(load128(addr, load->memory).data()); - case Type::none: - case Type::unreachable: - WASM_UNREACHABLE("unexpected type"); - } - WASM_UNREACHABLE("invalid type"); - } - virtual void store(Store* store, Address addr, Literal value, Name memory) { - switch (store->valueType.getBasic()) { - case Type::i32: { - switch (store->bytes) { - case 1: - store8(addr, value.geti32(), memory); - break; - case 2: - store16(addr, value.geti32(), memory); - break; - case 4: - store32(addr, value.geti32(), memory); - break; - default: - WASM_UNREACHABLE("invalid store size"); - } - break; - } - case Type::i64: { - switch (store->bytes) { - case 1: - store8(addr, value.geti64(), memory); - break; - case 2: - store16(addr, value.geti64(), memory); - break; - case 4: - store32(addr, value.geti64(), memory); - break; - case 8: - store64(addr, value.geti64(), memory); - break; - default: - WASM_UNREACHABLE("invalid store size"); - } - break; - } - // write floats carefully, ensuring all bits reach memory - case Type::f32: { - switch (store->bytes) { - case 2: { - float f32 = bit_cast(value.reinterpreti32()); - // Convert the float32 to float16 and store the binary - // representation. - store16(addr, fp16_ieee_from_fp32_value(f32), memory); - break; - } - case 4: - store32(addr, value.reinterpreti32(), memory); - break; - default: - WASM_UNREACHABLE("invalid store size"); - } - break; - } - case Type::f64: - store64(addr, value.reinterpreti64(), memory); - break; - case Type::v128: - store128(addr, value.getv128(), memory); - break; - case Type::none: - case Type::unreachable: - WASM_UNREACHABLE("unexpected type"); - } - } - - virtual int8_t load8s(Address addr, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual uint8_t load8u(Address addr, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual int16_t load16s(Address addr, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual uint16_t load16u(Address addr, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual int32_t load32s(Address addr, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual uint32_t load32u(Address addr, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual int64_t load64s(Address addr, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual uint64_t load64u(Address addr, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual std::array load128(Address addr, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - - virtual void store8(Address addr, int8_t value, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual void store16(Address addr, int16_t value, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual void store32(Address addr, int32_t value, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual void store64(Address addr, int64_t value, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } - virtual void - store128(Address addr, const std::array&, Name memoryName) { - WASM_UNREACHABLE("unimp"); - } }; SubType* self() { return static_cast(this); } @@ -3211,13 +3035,15 @@ class ModuleRunnerBase : public ExpressionRunner { std::unordered_map allMemories; using CreateTableFunc = std::unique_ptr(Literal, Table); + using CreateMemoryFunc = std::unique_ptr(Memory); ModuleRunnerBase( Module& wasm, ExternalInterface* externalInterface, std::shared_ptr importResolver, std::map> linkedInstances_ = {}, - std::function createTable = {}) + std::function createTable = {}, + std::function createMemory = {}) : ExpressionRunner(&wasm), wasm(wasm), externalInterface(externalInterface), linkedInstances(std::move(linkedInstances_)), @@ -3228,6 +3054,13 @@ class ModuleRunnerBase : public ExpressionRunner { : static_cast>( [](Literal initial, Table t) -> std::unique_ptr { return std::make_unique(initial, t); + })), + createMemory( + createMemory != nullptr + ? std::move(createMemory) + : static_cast>( + [](Memory m) -> std::unique_ptr { + return std::make_unique(m); })) { // Set up a single shared CurrContinuations for all these linked instances, // reusing one if it exists. @@ -3263,8 +3096,7 @@ class ModuleRunnerBase : public ExpressionRunner { initializeTags(); initializeMemories(); - // TODO: - // initializeMemoryContents(); + initializeMemoryContents(); // run start, if present if (wasm.start.is()) { @@ -3635,8 +3467,8 @@ class ModuleRunnerBase : public ExpressionRunner { // parsing/validation checked this already. assert(inserted && "Unexpected repeated memory name"); } else { - auto& runtimeMemory = definedMemories.emplace_back( - std::make_unique(*memory)); + auto& runtimeMemory = + definedMemories.emplace_back(createMemory(*memory)); [[maybe_unused]] auto [_, inserted] = allMemories.try_emplace(memory->name, runtimeMemory.get()); assert(inserted && "Unexpected repeated memory name"); @@ -3670,8 +3502,6 @@ class ModuleRunnerBase : public ExpressionRunner { } void initializeMemoryContents() { - initializeMemorySizes(); - // apply active memory segments for (size_t i = 0, e = wasm.dataSegments.size(); i < e; ++i) { auto& segment = wasm.dataSegments[i]; @@ -3708,37 +3538,14 @@ class ModuleRunnerBase : public ExpressionRunner { } // in pages, used to keep track of memorySize throughout the below memops - std::unordered_map memorySizes; - - void initializeMemorySizes() { - for (auto& memory : wasm.memories) { - memorySizes[memory->name] = memory->initial; - } - } - - Address getMemorySize(Name memory) { - auto info = getMemoryInstanceInfo(memory); - if (info.instance != self()) { - return info.instance->getMemorySize(info.name); - } - - auto iter = memorySizes.find(memory); - if (iter == memorySizes.end()) { - externalInterface->trap("getMemorySize called on non-existing memory"); - } - return iter->second; + Address getMemorySize(Name memoryName) { + auto* memory = allMemories[memoryName]; + return memory->size() / memory->getDefinition()->pageSize(); } - void setMemorySize(Name memory, Address size) { - auto iter = memorySizes.find(memory); - if (iter == memorySizes.end()) { - externalInterface->trap("setMemorySize called on non-existing memory"); - } - iter->second = size; - } - - Address getMemorySizeBytes(Name memory) { - return getMemorySize(memory) * wasm.getMemory(memory)->pageSize(); + Address getMemorySizeBytes(Name memoryName) { + auto* memory = allMemories[memoryName]; + return memory->size(); } public: @@ -4110,44 +3917,35 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitLoad(Load* curr) { VISIT(flow, curr->ptr) auto* memory = allMemories[curr->memory]; - return memory->load(static_cast(flow.getSingleValue().geti32()), + return memory->load(flow.getSingleValue().getUnsigned(), curr->offset, curr->bytes, - curr->order); - // auto info = getMemoryInstanceInfo(curr->memory); - // auto memorySize = info.instance->getMemorySize(info.name); - // auto addr = - // info.instance->getFinalAddress(curr, flow.getSingleValue(), - // memorySize); - // if (curr->isAtomic()) { - // info.instance->checkAtomicAddress(addr, curr->bytes, memorySize); - // } - // auto ret = info.interface()->load(curr, addr, info.name); - // return ret; + curr->order, + curr->type, + curr->signed_); } + Flow visitStore(Store* curr) { VISIT(ptr, curr->ptr) VISIT(value, curr->value) - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - auto addr = info.instance->getFinalAddress( - curr, ptr.getSingleValue(), memorySizeBytes); - if (curr->isAtomic()) { - info.instance->checkAtomicAddress(addr, curr->bytes, memorySizeBytes); - } - info.interface()->store(curr, addr, value.getSingleValue(), info.name); + auto* memory = allMemories[curr->memory]; + memory->store(ptr.getSingleValue().getUnsigned(), + curr->offset, + curr->bytes, + curr->order, + value.getSingleValue(), + curr->valueType); return Flow(); } + Flow visitAtomicRMW(AtomicRMW* curr) { VISIT(ptr, curr->ptr) VISIT(value, curr->value) - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - auto addr = info.instance->getFinalAddress( - curr, ptr.getSingleValue(), memorySizeBytes); - auto loaded = info.instance->doAtomicLoad( - addr, curr->bytes, curr->type, info.name, memorySizeBytes, curr->order); + auto* memory = allMemories[curr->memory]; + auto addr = ptr.getSingleValue().getUnsigned(); + auto loaded = memory->load( + addr, curr->offset, curr->bytes, curr->order, curr->type, false); auto computed = value.getSingleValue(); switch (curr->op) { case RMWAdd: @@ -4168,27 +3966,27 @@ class ModuleRunnerBase : public ExpressionRunner { case RMWXchg: break; } - info.instance->doAtomicStore( - addr, curr->bytes, computed, info.name, memorySizeBytes); + memory->store( + addr, curr->offset, curr->bytes, curr->order, computed, curr->type); return loaded; } Flow visitAtomicCmpxchg(AtomicCmpxchg* curr) { VISIT(ptr, curr->ptr) VISIT(expected, curr->expected) VISIT(replacement, curr->replacement) - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - auto addr = info.instance->getFinalAddress( - curr, ptr.getSingleValue(), memorySizeBytes); - expected = Flow(wrapToSmallerSize(expected.getSingleValue(), curr->bytes)); - auto loaded = info.instance->doAtomicLoad( - addr, curr->bytes, curr->type, info.name, memorySizeBytes, curr->order); - if (loaded == expected.getSingleValue()) { - info.instance->doAtomicStore(addr, - curr->bytes, - replacement.getSingleValue(), - info.name, - memorySizeBytes); + auto* memory = allMemories[curr->memory]; + auto addr = ptr.getSingleValue().getUnsigned(); + auto expectedVal = + Flow(wrapToSmallerSize(expected.getSingleValue(), curr->bytes)); + auto loaded = memory->load( + addr, curr->offset, curr->bytes, curr->order, curr->type, false); + if (loaded == expectedVal.getSingleValue()) { + memory->store(addr, + curr->offset, + curr->bytes, + curr->order, + replacement.getSingleValue(), + curr->type); } return loaded; } @@ -4197,16 +3995,10 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(expected, curr->expected) VISIT(timeout, curr->timeout) auto bytes = curr->expectedType.getByteSize(); - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - auto addr = info.instance->getFinalAddress( - curr, ptr.getSingleValue(), bytes, memorySizeBytes); - auto loaded = info.instance->doAtomicLoad(addr, - bytes, - curr->expectedType, - info.name, - memorySizeBytes, - MemoryOrder::SeqCst); + auto* memory = allMemories[curr->memory]; + auto addr = ptr.getSingleValue().getUnsigned(); + auto loaded = memory->load( + addr, curr->offset, bytes, MemoryOrder::SeqCst, curr->expectedType, false); if (loaded != expected.getSingleValue()) { return Literal(int32_t{1}); // not equal } @@ -4223,12 +4015,14 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitAtomicNotify(AtomicNotify* curr) { VISIT(ptr, curr->ptr) VISIT(count, curr->notifyCount) - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - auto addr = info.instance->getFinalAddress( - curr, ptr.getSingleValue(), 4, memorySizeBytes); + auto* memory = allMemories[curr->memory]; // Just check TODO actual threads support - info.instance->checkAtomicAddress(addr, 4, memorySizeBytes); + memory->load(ptr.getSingleValue().getUnsigned(), + curr->offset, + 4, + MemoryOrder::SeqCst, + Type::i32, + false); return Literal(int32_t{0}); // none woken up } Flow visitSIMDLoad(SIMDLoad* curr) { @@ -4289,35 +4083,45 @@ class ModuleRunnerBase : public ExpressionRunner { Flow visitSIMDLoadExtend(SIMDLoad* curr) { VISIT(flow, curr->ptr) Address src(flow.getSingleValue().getUnsigned()); - auto info = getMemoryInstanceInfo(curr->memory); - auto loadLane = [&](Address addr) { + auto* memory = allMemories[curr->memory]; + auto loadLane = [&](Address addr, size_t laneBytes) { + Type type = Type::none; + bool signed_ = false; switch (curr->op) { case Load8x8SVec128: - return Literal(int32_t(info.interface()->load8s(addr, info.name))); + type = Type::i32; + signed_ = true; + break; case Load8x8UVec128: - return Literal(int32_t(info.interface()->load8u(addr, info.name))); + type = Type::i32; + signed_ = false; + break; case Load16x4SVec128: - return Literal(int32_t(info.interface()->load16s(addr, info.name))); + type = Type::i32; + signed_ = true; + break; case Load16x4UVec128: - return Literal(int32_t(info.interface()->load16u(addr, info.name))); + type = Type::i32; + signed_ = false; + break; case Load32x2SVec128: - return Literal(int64_t(info.interface()->load32s(addr, info.name))); + type = Type::i64; + signed_ = true; + break; case Load32x2UVec128: - return Literal(int64_t(info.interface()->load32u(addr, info.name))); + type = Type::i64; + signed_ = false; + break; default: WASM_UNREACHABLE("unexpected op"); } - WASM_UNREACHABLE("invalid op"); + return memory->load( + addr, curr->offset, laneBytes, MemoryOrder::Unordered, type, signed_); }; - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - auto addressType = curr->ptr->type; auto fillLanes = [&](auto lanes, size_t laneBytes) { for (auto& lane : lanes) { - auto ptr = Literal::makeFromInt64(src, addressType); - lane = loadLane(info.instance->getFinalAddress( - curr, ptr, laneBytes, memorySizeBytes)); - src = - ptr.add(Literal::makeFromInt32(laneBytes, addressType)).getUnsigned(); + lane = loadLane(src, laneBytes); + src = src + laneBytes; } return Literal(lanes); }; @@ -4344,39 +4148,48 @@ class ModuleRunnerBase : public ExpressionRunner { } Flow visitSIMDLoadZero(SIMDLoad* curr) { VISIT(flow, curr->ptr) - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - Address src = info.instance->getFinalAddress( - curr, flow.getSingleValue(), curr->getMemBytes(), memorySizeBytes); + auto* memory = allMemories[curr->memory]; auto zero = Literal::makeZero(curr->op == Load32ZeroVec128 ? Type::i32 : Type::i64); if (curr->op == Load32ZeroVec128) { - auto val = Literal(info.interface()->load32u(src, info.name)); + auto val = memory->load(flow.getSingleValue().getUnsigned(), + curr->offset, + 4, + MemoryOrder::Unordered, + Type::i32, + false); return Literal(std::array{{val, zero, zero, zero}}); } else { - auto val = Literal(info.interface()->load64u(src, info.name)); + auto val = memory->load(flow.getSingleValue().getUnsigned(), + curr->offset, + 8, + MemoryOrder::Unordered, + Type::i64, + false); return Literal(std::array{{val, zero}}); } } Flow visitSIMDLoadStoreLane(SIMDLoadStoreLane* curr) { VISIT(ptrFlow, curr->ptr) VISIT(vecFlow, curr->vec) - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - Address addr = info.instance->getFinalAddress( - curr, ptrFlow.getSingleValue(), curr->getMemBytes(), memorySizeBytes); + auto* memory = allMemories[curr->memory]; + auto addr = ptrFlow.getSingleValue().getUnsigned(); Literal vec = vecFlow.getSingleValue(); switch (curr->op) { case Load8LaneVec128: case Store8LaneVec128: { std::array lanes = vec.getLanesUI8x16(); if (curr->isLoad()) { - lanes[curr->index] = - Literal(info.interface()->load8u(addr, info.name)); + lanes[curr->index] = memory->load( + addr, curr->offset, 1, MemoryOrder::Unordered, Type::i32, false); return Literal(lanes); } else { - info.interface()->store8( - addr, lanes[curr->index].geti32(), info.name); + memory->store(addr, + curr->offset, + 1, + MemoryOrder::Unordered, + lanes[curr->index], + Type::i32); return {}; } } @@ -4384,12 +4197,16 @@ class ModuleRunnerBase : public ExpressionRunner { case Store16LaneVec128: { std::array lanes = vec.getLanesUI16x8(); if (curr->isLoad()) { - lanes[curr->index] = - Literal(info.interface()->load16u(addr, info.name)); + lanes[curr->index] = memory->load( + addr, curr->offset, 2, MemoryOrder::Unordered, Type::i32, false); return Literal(lanes); } else { - info.interface()->store16( - addr, lanes[curr->index].geti32(), info.name); + memory->store(addr, + curr->offset, + 2, + MemoryOrder::Unordered, + lanes[curr->index], + Type::i32); return {}; } } @@ -4397,12 +4214,16 @@ class ModuleRunnerBase : public ExpressionRunner { case Store32LaneVec128: { std::array lanes = vec.getLanesI32x4(); if (curr->isLoad()) { - lanes[curr->index] = - Literal(info.interface()->load32u(addr, info.name)); + lanes[curr->index] = memory->load( + addr, curr->offset, 4, MemoryOrder::Unordered, Type::i32, false); return Literal(lanes); } else { - info.interface()->store32( - addr, lanes[curr->index].geti32(), info.name); + memory->store(addr, + curr->offset, + 4, + MemoryOrder::Unordered, + lanes[curr->index], + Type::i32); return {}; } } @@ -4410,12 +4231,16 @@ class ModuleRunnerBase : public ExpressionRunner { case Load64LaneVec128: { std::array lanes = vec.getLanesI64x2(); if (curr->isLoad()) { - lanes[curr->index] = - Literal(info.interface()->load64u(addr, info.name)); + lanes[curr->index] = memory->load( + addr, curr->offset, 8, MemoryOrder::Unordered, Type::i64, false); return Literal(lanes); } else { - info.interface()->store64( - addr, lanes[curr->index].geti64(), info.name); + memory->store(addr, + curr->offset, + 8, + MemoryOrder::Unordered, + lanes[curr->index], + Type::i64); return {}; } } @@ -4423,77 +4248,40 @@ class ModuleRunnerBase : public ExpressionRunner { WASM_UNREACHABLE("unexpected op"); } Flow visitMemorySize(MemorySize* curr) { - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySize = info.instance->getMemorySize(info.name); - auto* memory = info.instance->wasm.getMemory(info.name); - return Literal::makeFromInt64(memorySize, memory->addressType); + auto* memory = allMemories[curr->memory]; + auto pageSize = memory->getDefinition()->pageSize(); + return Literal::makeFromInt64(memory->size() / pageSize, + memory->getDefinition()->addressType); } Flow visitMemoryGrow(MemoryGrow* curr) { VISIT(flow, curr->delta) - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySize = info.instance->getMemorySize(info.name); - Memory* memory = info.instance->wasm.getMemory(info.name); - auto addressType = memory->addressType; - auto fail = Literal::makeFromInt64(-1, addressType); - Flow ret = Literal::makeFromInt64(memorySize, addressType); - uint64_t delta = flow.getSingleValue().getUnsigned(); - uint64_t maxAddr = addressType == Type::i32 - ? std::numeric_limits::max() - : std::numeric_limits::max(); - Address::address64_t pageSizeLog2 = memory->pageSizeLog2; - if (delta > (maxAddr >> pageSizeLog2)) { - // Impossible to grow this much. - return fail; - } - if (memorySize >= maxAddr - delta) { - // Overflow. - return fail; - } - auto newSize = memorySize + delta; - if (newSize > memory->max) { - return fail; - } - if (!info.interface()->growMemory( - info.name, (memorySize << pageSizeLog2), (newSize << pageSizeLog2))) { - // We failed to grow the memory in practice, even though it was valid - // to try to do so. - return fail; - } - memorySize = newSize; - info.instance->setMemorySize(info.name, memorySize); - return ret; + auto* memory = allMemories[curr->memory]; + auto addressType = memory->getDefinition()->addressType; + auto pageSize = memory->getDefinition()->pageSize(); + auto oldPages = memory->size() / pageSize; + if (memory->grow(flow.getSingleValue().getUnsigned())) { + return Literal::makeFromInt64(oldPages, addressType); + } else { + return Literal::makeFromInt64(-1, addressType); + } } Flow visitMemoryInit(MemoryInit* curr) { VISIT(dest, curr->dest) VISIT(offset, curr->offset) VISIT(size, curr->size) + auto destVal = dest.getSingleValue().getUnsigned(); + auto offsetVal = offset.getSingleValue().getUnsigned(); + auto sizeVal = size.getSingleValue().getUnsigned(); - auto* segment = wasm.getDataSegment(curr->segment); - - Address destVal(dest.getSingleValue().getUnsigned()); - Address offsetVal(offset.getSingleValue().getUnsigned()); - Address sizeVal(size.getSingleValue().getUnsigned()); - - if (offsetVal + sizeVal > 0 && droppedDataSegments.count(curr->segment)) { + if (sizeVal > 0 && droppedDataSegments.count(curr->segment)) { trap("out of bounds segment access in memory.init"); } - if (offsetVal + sizeVal > segment->data.size()) { - trap("out of bounds segment access in memory.init"); - } - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - if (destVal + sizeVal > memorySizeBytes) { - trap("out of bounds memory access in memory.init"); - } - for (size_t i = 0; i < sizeVal; ++i) { - Literal addr(destVal + i); - info.interface()->store8( - info.instance->getFinalAddressWithoutOffset(addr, 1, memorySizeBytes), - segment->data[offsetVal + i], - info.name); - } + auto* memory = allMemories[curr->memory]; + auto* segment = wasm.getDataSegment(curr->segment); + memory->init(destVal, offsetVal, sizeVal, segment); return {}; } + Flow visitDataDrop(DataDrop* curr) { droppedDataSegments.insert(curr->segment); return {}; @@ -4502,66 +4290,25 @@ class ModuleRunnerBase : public ExpressionRunner { VISIT(dest, curr->dest) VISIT(source, curr->source) VISIT(size, curr->size) - Address destVal(dest.getSingleValue().getUnsigned()); - Address sourceVal(source.getSingleValue().getUnsigned()); - Address sizeVal(size.getSingleValue().getUnsigned()); - - auto destInfo = getMemoryInstanceInfo(curr->destMemory); - auto sourceInfo = getMemoryInstanceInfo(curr->sourceMemory); - auto sourceMemorySizeBytes = - sourceInfo.instance->getMemorySizeBytes(sourceInfo.name); - auto destMemorySizeBytes = - destInfo.instance->getMemorySizeBytes(destInfo.name); - if (sourceVal + sizeVal > sourceMemorySizeBytes || - destVal + sizeVal > destMemorySizeBytes || - // FIXME: better/cheaper way to detect wrapping? - sourceVal + sizeVal < sourceVal || sourceVal + sizeVal < sizeVal || - destVal + sizeVal < destVal || destVal + sizeVal < sizeVal) { - trap("out of bounds segment access in memory.copy"); - } + auto destVal = dest.getSingleValue().getUnsigned(); + auto sourceVal = source.getSingleValue().getUnsigned(); + auto sizeVal = size.getSingleValue().getUnsigned(); - int64_t start = 0; - int64_t end = sizeVal; - int step = 1; - // Reverse direction if source is below dest - if (sourceVal < destVal) { - start = int64_t(sizeVal) - 1; - end = -1; - step = -1; - } - for (int64_t i = start; i != end; i += step) { - destInfo.interface()->store8( - destInfo.instance->getFinalAddressWithoutOffset( - Literal(destVal + i), 1, destMemorySizeBytes), - sourceInfo.interface()->load8s( - sourceInfo.instance->getFinalAddressWithoutOffset( - Literal(sourceVal + i), 1, sourceMemorySizeBytes), - sourceInfo.name), - destInfo.name); - } + auto* destMemory = allMemories[curr->destMemory]; + auto* sourceMemory = allMemories[curr->sourceMemory]; + destMemory->copy(destVal, sourceVal, sizeVal, sourceMemory); return {}; } Flow visitMemoryFill(MemoryFill* curr) { VISIT(dest, curr->dest) VISIT(value, curr->value) VISIT(size, curr->size) - Address destVal(dest.getSingleValue().getUnsigned()); - Address sizeVal(size.getSingleValue().getUnsigned()); + auto destVal = dest.getSingleValue().getUnsigned(); + auto sizeVal = size.getSingleValue().getUnsigned(); + uint8_t valueVal = value.getSingleValue().geti32(); - auto info = getMemoryInstanceInfo(curr->memory); - auto memorySizeBytes = info.instance->getMemorySizeBytes(info.name); - // FIXME: cheaper wrapping detection? - if (destVal > memorySizeBytes || sizeVal > memorySizeBytes || - destVal + sizeVal > memorySizeBytes) { - trap("out of bounds memory access in memory.fill"); - } - uint8_t val(value.getSingleValue().geti32()); - for (size_t i = 0; i < sizeVal; ++i) { - info.interface()->store8(info.instance->getFinalAddressWithoutOffset( - Literal(destVal + i), 1, memorySizeBytes), - val, - info.name); - } + auto* memory = allMemories[curr->memory]; + memory->fill(destVal, valueVal, sizeVal); return {}; } Flow visitRefFunc(RefFunc* curr) { @@ -5237,53 +4984,6 @@ class ModuleRunnerBase : public ExpressionRunner { static const Index maxDepth = 200; protected: - void trapIfGt(uint64_t lhs, uint64_t rhs, const char* msg) { - if (lhs > rhs) { - std::stringstream ss; - ss << msg << ": " << lhs << " > " << rhs; - externalInterface->trap(ss.str()); - } - } - - template - Address - getFinalAddress(LS* curr, Literal ptr, Index bytes, Address memorySizeBytes) { - uint64_t addr = ptr.getUnsigned(); - trapIfGt(curr->offset, memorySizeBytes, "offset > memory"); - trapIfGt(addr, memorySizeBytes - curr->offset, "final > memory"); - addr += curr->offset; - trapIfGt(bytes, memorySizeBytes, "bytes > memory"); - checkLoadAddress(addr, bytes, memorySizeBytes); - return addr; - } - - template - Address getFinalAddress(LS* curr, Literal ptr, Address memorySizeBytes) { - return getFinalAddress(curr, ptr, curr->bytes, memorySizeBytes); - } - - Address getFinalAddressWithoutOffset(Literal ptr, - Index bytes, - Address memorySizeBytes) { - uint64_t addr = ptr.getUnsigned(); - checkLoadAddress(addr, bytes, memorySizeBytes); - return addr; - } - - void checkLoadAddress(Address addr, Index bytes, Address memorySizeBytes) { - trapIfGt(addr, memorySizeBytes - bytes, "highest > memory"); - } - - void checkAtomicAddress(Address addr, Index bytes, Address memorySizeBytes) { - checkLoadAddress(addr, bytes, memorySizeBytes); - // Unaligned atomics trap. - if (bytes > 1) { - if (addr & (bytes - 1)) { - externalInterface->trap("unaligned atomic operation"); - } - } - } - Literal doAtomicLoad(Address addr, Index bytes, Type type, @@ -5293,50 +4993,24 @@ class ModuleRunnerBase : public ExpressionRunner { if (order == MemoryOrder::Unordered) { Fatal() << "Expected a non-unordered MemoryOrder in doAtomicLoad"; } - checkAtomicAddress(addr, bytes, memorySizeBytes); - Const ptr; - ptr.value = Literal(int32_t(addr)); - ptr.type = Type::i32; - Load load; - load.bytes = bytes; - // When an atomic loads a partial number of bytes for the type, it is - // always an unsigned extension. - load.signed_ = false; - load.align = bytes; - load.order = order; - load.ptr = &ptr; - load.type = type; - load.memory = memoryName; - return externalInterface->load(&load, addr, memoryName); + auto* memory = allMemories[memoryName]; + return memory->load(addr, 0, bytes, order, type, false); } void doAtomicStore(Address addr, Index bytes, - Literal toStore, + Literal value, Name memoryName, Address memorySizeBytes) { - checkAtomicAddress(addr, bytes, memorySizeBytes); - Const ptr; - ptr.value = Literal(int32_t(addr)); - ptr.type = Type::i32; - Const value; - value.value = toStore; - value.type = toStore.type; - Store store; - store.bytes = bytes; - store.align = bytes; - store.order = MemoryOrder::SeqCst; - store.ptr = &ptr; - store.value = &value; - store.valueType = value.type; - store.memory = memoryName; - return externalInterface->store(&store, addr, toStore, memoryName); + auto* memory = allMemories[memoryName]; + memory->store(addr, 0, bytes, MemoryOrder::SeqCst, value, value.type); } ExternalInterface* externalInterface; std::map> linkedInstances; std::shared_ptr importResolver; std::function createTable; + std::function createMemory; }; class ModuleRunner : public ModuleRunnerBase { @@ -5345,7 +5019,8 @@ class ModuleRunner : public ModuleRunnerBase { Module& wasm, ExternalInterface* externalInterface, std::map> linkedInstances = {}, - std::shared_ptr importResolver = nullptr) + std::shared_ptr importResolver = nullptr, + std::function createMemory = {}) : ModuleRunnerBase( wasm, externalInterface, @@ -5353,7 +5028,9 @@ class ModuleRunner : public ModuleRunnerBase { ? importResolver : std::make_shared>( linkedInstances), - linkedInstances) {} + linkedInstances, + {}, + std::move(createMemory)) {} Literal makeFuncData(Name name, Type type) { // As the super's |makeFuncData|, but here we also provide a way to diff --git a/src/wasm.h b/src/wasm.h index 5eb952d362b..f793b2c8575 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -2526,8 +2526,8 @@ class Table : public Importable { Type addressType = Type::i32; Type type = Type(HeapType::func, Nullable); - bool hasMax() { return max != kUnlimitedSize; } - bool is64() { return addressType == Type::i64; } + bool hasMax() const { return max != kUnlimitedSize; } + bool is64() const { return addressType == Type::i64; } void clear() { name = ""; initial = 0; @@ -2563,8 +2563,8 @@ class Memory : public Importable { bool shared = false; Type addressType = Type::i32; - bool hasMax() { return max != kUnlimitedSize; } - bool is64() { return addressType == Type::i64; } + bool hasMax() const { return max != kUnlimitedSize; } + bool is64() const { return addressType == Type::i64; } Address::address64_t maxSize32() const { return 1ull << (32 - pageSizeLog2); } Address::address64_t maxSize64() const { if (pageSizeLog2 == 0) { @@ -2574,7 +2574,9 @@ class Memory : public Importable { } Address::address64_t pageSize() const { return 1ull << pageSizeLog2; } - Address::address64_t initialByteSize() const { return 1ull << pageSizeLog2; } + Address::address64_t initialByteSize() const { + return uint64_t(initial) << pageSizeLog2; + } }; class Global : public Importable { From 334de91cb7af1247845ec4e32d23e3a9d95d73cb Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 11 Mar 2026 22:47:30 +0000 Subject: [PATCH 08/12] Passing spec tests --- src/ir/CMakeLists.txt | 1 + src/ir/runtime-memory.cpp | 350 +++++++++++++++++++++++++++++++++++ src/ir/runtime-memory.h | 293 ++++++----------------------- src/tools/wasm-ctor-eval.cpp | 16 +- src/wasm-interpreter.h | 10 +- 5 files changed, 419 insertions(+), 251 deletions(-) create mode 100644 src/ir/runtime-memory.cpp diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index a67ee5a91d3..005b9b3c859 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -20,6 +20,7 @@ set(ir_SOURCES public-type-validator.cpp ReFinalize.cpp return-utils.cpp + runtime-memory.cpp runtime-table.cpp stack-utils.cpp table-utils.cpp diff --git a/src/ir/runtime-memory.cpp b/src/ir/runtime-memory.cpp new file mode 100644 index 00000000000..0ea3e705b5a --- /dev/null +++ b/src/ir/runtime-memory.cpp @@ -0,0 +1,350 @@ +/* + * Copyright 2026 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 "ir/runtime-memory.h" +#include "fp16.h" +#include "interpreter/exception.h" + +namespace wasm { + +RuntimeMemory::RuntimeMemory(Memory memory, ExternalInterface* externalInterface) + : externalInterface(externalInterface), memoryDefinition(std::move(memory)) {} + +namespace { + +Address getFinalAddress(const RuntimeMemory& runtimeMemory, + Address addr, + Address offset, + Index bytes, + Address memorySizeBytes) { + if (offset > memorySizeBytes) { + std::string msg = "offset > memory: "; + msg += std::to_string(uint64_t(offset)); + msg += " > "; + msg += std::to_string(uint64_t(memorySizeBytes)); + runtimeMemory.trap(msg); + } + if (addr > memorySizeBytes - offset) { + std::string msg = "final > memory: "; + msg += std::to_string(uint64_t(addr)); + msg += " > "; + msg += std::to_string(uint64_t(memorySizeBytes - offset)); + runtimeMemory.trap(msg); + } + + addr = size_t(addr) + offset; + + if (bytes > memorySizeBytes - addr) { + std::string msg = "highest > memory: "; + msg += std::to_string(uint64_t(addr)); + msg += " + "; + msg += std::to_string(uint64_t(bytes)); + msg += " > "; + msg += std::to_string(uint64_t(memorySizeBytes)); + runtimeMemory.trap(msg); + } + return addr; +} + +void checkLoadAddress(const RuntimeMemory& runtimeMemory, + Address addr, + Index bytes, + Address memorySizeBytes) { + if (addr > memorySizeBytes || bytes > memorySizeBytes - addr) { + std::string msg = "highest > memory: "; + msg += std::to_string(uint64_t(addr)); + msg += " + "; + msg += std::to_string(uint64_t(bytes)); + msg += " > "; + msg += std::to_string(uint64_t(memorySizeBytes)); + runtimeMemory.trap(msg); + } +} + +void checkAtomicAddress(const RuntimeMemory& runtimeMemory, + Address addr, + Index bytes, + Address memorySizeBytes) { + checkLoadAddress(runtimeMemory, addr, bytes, memorySizeBytes); + // Unaligned atomics trap. + if (bytes > 1) { + if (addr & (bytes - 1)) { + runtimeMemory.trap("unaligned atomic operation"); + } + } +} + +template bool aligned(const uint8_t* address) { + static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2"); + return 0 == (reinterpret_cast(address) & (sizeof(T) - 1)); +} + +} // namespace + +RealRuntimeMemory::RealRuntimeMemory(Memory memory, + ExternalInterface* externalInterface) + : RuntimeMemory(std::move(memory), externalInterface) { + resize(memoryDefinition.initialByteSize()); +} + +Literal RealRuntimeMemory::load(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Type type, + bool signed_) const { + Address final = getFinalAddress(*this, addr, offset, byteCount, size()); + if (order != MemoryOrder::Unordered) { + checkAtomicAddress(*this, final, byteCount, size()); + } + switch (type.getBasic()) { + case Type::i32: { + switch (byteCount) { + case 1: + return signed_ ? Literal((int32_t)get(final)) + : Literal((int32_t)get(final)); + case 2: + return signed_ ? Literal((int32_t)get(final)) + : Literal((int32_t)get(final)); + case 4: + return Literal((int32_t)get(final)); + default: + WASM_UNREACHABLE("invalid size"); + } + } + case Type::i64: { + switch (byteCount) { + case 1: + return signed_ ? Literal((int64_t)get(final)) + : Literal((int64_t)get(final)); + case 2: + return signed_ ? Literal((int64_t)get(final)) + : Literal((int64_t)get(final)); + case 4: + return signed_ ? Literal((int64_t)get(final)) + : Literal((int64_t)get(final)); + case 8: + return Literal((int64_t)get(final)); + default: + WASM_UNREACHABLE("invalid size"); + } + } + case Type::f32: { + switch (byteCount) { + case 2: + return Literal(bit_cast( + fp16_ieee_to_fp32_value(get(final)))) + .castToF32(); + case 4: + return Literal(get(final)).castToF32(); + default: + WASM_UNREACHABLE("invalid size"); + } + } + case Type::f64: + return Literal(get(final)).castToF64(); + case Type::v128: + return Literal(get>(final).data()); + default: + WASM_UNREACHABLE("unexpected type"); + } +} + +void RealRuntimeMemory::store(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Literal value, + Type type) { + Address final = getFinalAddress(*this, addr, offset, byteCount, size()); + if (order != MemoryOrder::Unordered) { + checkAtomicAddress(*this, final, byteCount, size()); + } + switch (type.getBasic()) { + case Type::i32: { + switch (byteCount) { + case 1: + set(final, value.geti32()); + break; + case 2: + set(final, value.geti32()); + break; + case 4: + set(final, value.geti32()); + break; + default: + WASM_UNREACHABLE("invalid size"); + } + break; + } + case Type::i64: { + switch (byteCount) { + case 1: + set(final, value.geti64()); + break; + case 2: + set(final, value.geti64()); + break; + case 4: + set(final, value.geti64()); + break; + case 8: + set(final, value.geti64()); + break; + default: + WASM_UNREACHABLE("invalid size"); + } + break; + } + case Type::f32: { + switch (byteCount) { + case 2: + set(final, + fp16_ieee_from_fp32_value( + bit_cast(value.reinterpreti32()))); + break; + case 4: + set(final, value.reinterpreti32()); + break; + default: + WASM_UNREACHABLE("invalid size"); + } + break; + } + case Type::f64: + set(final, value.reinterpreti64()); + break; + case Type::v128: + set>(final, value.getv128()); + break; + default: + WASM_UNREACHABLE("unexpected type"); + } +} + +bool RealRuntimeMemory::grow(Address delta) { + Address pageSize = memoryDefinition.pageSize(); + Address oldPages = intendedSize / pageSize; + Address newPages = oldPages + delta; + if (newPages > memoryDefinition.max && memoryDefinition.hasMax()) { + return false; + } + // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the + // interpreter. + if (newPages * pageSize > 1024 * 1024 * 1024) { + return false; + } + resize(newPages * pageSize); + return true; +} + +Address RealRuntimeMemory::size() const { return intendedSize; } + +void RealRuntimeMemory::init(Address dest, + Address src, + Address byteCount, + const DataSegment* data) { + if (src > data->data.size() || byteCount > data->data.size() - src) { + trap("out of bounds segment access in memory.init"); + } + Address final = getFinalAddress(*this, dest, 0, byteCount, size()); + if (byteCount > 0) { + std::memcpy(&memory[final], &data->data[src], byteCount); + } +} + +void RealRuntimeMemory::copy(Address dest, + Address src, + Address byteCount, + const RuntimeMemory* srcMemory) { + Address finalDest = getFinalAddress(*this, dest, 0, byteCount, size()); + Address finalSrc = getFinalAddress( + *srcMemory, src, 0, byteCount, srcMemory->size()); + const std::vector* srcBuffer = srcMemory->getBuffer(); + if (!srcBuffer) { + // If it's not a memory with a direct buffer, we might need another way to + // access it, or we can just fail if this is the only implementation we + // support for now. + WASM_UNREACHABLE("unsupported srcMemory type in copy"); + } + if (byteCount > 0) { + std::memmove(&memory[finalDest], &(*srcBuffer)[finalSrc], byteCount); + } +} + +void RealRuntimeMemory::fill(Address dest, uint8_t value, Address byteCount) { + Address final = getFinalAddress(*this, dest, 0, byteCount, size()); + if (byteCount > 0) { + std::memset(&memory[final], value, byteCount); + } +} + +void RealRuntimeMemory::resize(size_t newSize) { + intendedSize = newSize; + const size_t minSize = 1 << 12; + size_t oldAllocatedSize = memory.size(); + size_t newAllocatedSize = std::max(minSize, newSize); + if (newAllocatedSize > oldAllocatedSize) { + memory.resize(newAllocatedSize); + std::memset(&memory[oldAllocatedSize], 0, newAllocatedSize - oldAllocatedSize); + } + if (newSize < oldAllocatedSize && newSize < minSize) { + std::memset(&memory[newSize], 0, minSize - newSize); + } +} + +template T RealRuntimeMemory::get(size_t address) const { + if (aligned(&memory[address])) { + return *reinterpret_cast(&memory[address]); + } else { + T loaded; + std::memcpy(&loaded, &memory[address], sizeof(T)); + return loaded; + } +} + +template void RealRuntimeMemory::set(size_t address, T value) { + if (aligned(&memory[address])) { + *reinterpret_cast(&memory[address]) = value; + } else { + std::memcpy(&memory[address], &value, sizeof(T)); + } +} + +// Explicit instantiations for the templates +template int8_t RealRuntimeMemory::get(size_t) const; +template uint8_t RealRuntimeMemory::get(size_t) const; +template int16_t RealRuntimeMemory::get(size_t) const; +template uint16_t RealRuntimeMemory::get(size_t) const; +template int32_t RealRuntimeMemory::get(size_t) const; +template uint32_t RealRuntimeMemory::get(size_t) const; +template int64_t RealRuntimeMemory::get(size_t) const; +template uint64_t RealRuntimeMemory::get(size_t) const; +template std::array +RealRuntimeMemory::get>(size_t) const; + +template void RealRuntimeMemory::set(size_t, int8_t); +template void RealRuntimeMemory::set(size_t, uint8_t); +template void RealRuntimeMemory::set(size_t, int16_t); +template void RealRuntimeMemory::set(size_t, uint16_t); +template void RealRuntimeMemory::set(size_t, int32_t); +template void RealRuntimeMemory::set(size_t, uint32_t); +template void RealRuntimeMemory::set(size_t, int64_t); +template void RealRuntimeMemory::set(size_t, uint64_t); +template void +RealRuntimeMemory::set>(size_t, std::array); + +} // namespace wasm diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index 9e472ea9b88..8ca2dd667a9 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -17,43 +17,19 @@ #ifndef wasm_ir_runtime_memory_h #define wasm_ir_runtime_memory_h -#include "fp16.h" -#include "interpreter/exception.h" #include "wasm.h" namespace wasm { -namespace { - -void trapIfGt(uint64_t lhs, uint64_t rhs, const char* msg) { - if (lhs > rhs) { - std::cerr << msg << ": " << lhs << " > " << rhs << "\n"; - throw TrapException{}; - } -} - -Address getFinalAddress(Address addr, - Address offset, - Index bytes, - Address memorySizeBytes) { - trapIfGt(offset, memorySizeBytes, "offset > memory"); - trapIfGt(addr, memorySizeBytes - offset, "final > memory"); - - addr = size_t(addr) + offset; - trapIfGt(bytes, memorySizeBytes, "bytes > memory"); - - trapIfGt(addr, memorySizeBytes - bytes, "highest > memory"); - return addr; -} - -} // namespace - class RuntimeMemory { public: - RuntimeMemory(Memory memory) : memoryDefinition(std::move(memory)) { - resize(memoryDefinition.initialByteSize()); - } + // Forward declare to avoid circular dependency + struct ExternalInterface { + virtual ~ExternalInterface() = default; + virtual void trap(std::string_view why) = 0; + }; + RuntimeMemory(Memory memory, ExternalInterface* externalInterface); virtual ~RuntimeMemory() = default; virtual Literal load(Address addr, @@ -61,237 +37,80 @@ class RuntimeMemory { uint8_t byteCount, MemoryOrder order, Type type, - bool signed_) const { - Address final = getFinalAddress(addr, offset, byteCount, size()); - if (order != MemoryOrder::Unordered) { - checkAtomicAddress(final, byteCount, size()); - } - switch (type.getBasic()) { - case Type::i32: { - switch (byteCount) { - case 1: - return signed_ ? Literal((int32_t)get(final)) - : Literal((int32_t)get(final)); - case 2: - return signed_ ? Literal((int32_t)get(final)) - : Literal((int32_t)get(final)); - case 4: - return Literal((int32_t)get(final)); - default: - WASM_UNREACHABLE("invalid size"); - } - } - case Type::i64: { - switch (byteCount) { - case 1: - return signed_ ? Literal((int64_t)get(final)) - : Literal((int64_t)get(final)); - case 2: - return signed_ ? Literal((int64_t)get(final)) - : Literal((int64_t)get(final)); - case 4: - return signed_ ? Literal((int64_t)get(final)) - : Literal((int64_t)get(final)); - case 8: - return Literal((int64_t)get(final)); - default: - WASM_UNREACHABLE("invalid size"); - } - } - case Type::f32: { - switch (byteCount) { - case 2: - return Literal(bit_cast( - fp16_ieee_to_fp32_value(get(final)))) - .castToF32(); - case 4: - return Literal(get(final)).castToF32(); - default: - WASM_UNREACHABLE("invalid size"); - } - } - case Type::f64: - return Literal(get(final)).castToF64(); - case Type::v128: - return Literal(get>(final).data()); - default: - WASM_UNREACHABLE("unexpected type"); - } - } + bool signed_) const = 0; virtual void store(Address addr, Address offset, uint8_t byteCount, MemoryOrder order, Literal value, - Type type) { - Address final = getFinalAddress(addr, offset, byteCount, size()); - if (order != MemoryOrder::Unordered) { - checkAtomicAddress(final, byteCount, size()); - } - switch (type.getBasic()) { - case Type::i32: { - switch (byteCount) { - case 1: - set(final, value.geti32()); - break; - case 2: - set(final, value.geti32()); - break; - case 4: - set(final, value.geti32()); - break; - default: - WASM_UNREACHABLE("invalid size"); - } - break; - } - case Type::i64: { - switch (byteCount) { - case 1: - set(final, value.geti64()); - break; - case 2: - set(final, value.geti64()); - break; - case 4: - set(final, value.geti64()); - break; - case 8: - set(final, value.geti64()); - break; - default: - WASM_UNREACHABLE("invalid size"); - } - break; - } - case Type::f32: { - switch (byteCount) { - case 2: - set(final, - fp16_ieee_from_fp32_value( - bit_cast(value.reinterpreti32()))); - break; - case 4: - set(final, value.reinterpreti32()); - break; - default: - WASM_UNREACHABLE("invalid size"); - } - break; - } - case Type::f64: - set(final, value.reinterpreti64()); - break; - case Type::v128: - set>(final, value.getv128()); - break; - default: - WASM_UNREACHABLE("unexpected type"); - } - } + Type type) = 0; - virtual bool grow(Address delta) { - Address pageSize = memoryDefinition.pageSize(); - Address oldPages = intendedSize / pageSize; - Address newPages = oldPages + delta; - if (newPages > memoryDefinition.max && memoryDefinition.hasMax()) { - return false; - } - // Apply a reasonable limit on memory size, 1GB, to avoid DOS on the - // interpreter. - if (newPages * pageSize > 1024 * 1024 * 1024) { - return false; - } - resize(newPages * pageSize); - return true; - } + virtual bool grow(Address delta) = 0; - virtual Address size() const { return intendedSize; } + virtual Address size() const = 0; virtual void - init(Address dest, Address src, Address byteCount, const DataSegment* data) { - trapIfGt(uint64_t(src), uint64_t(data->data.size()), "src > data"); - trapIfGt(uint64_t(byteCount), - uint64_t(data->data.size() - src), - "src + size > data"); - Address final = getFinalAddress(dest, 0, byteCount, size()); - std::memcpy(&memory[final], &data->data[src], byteCount); - } + init(Address dest, Address src, Address byteCount, const DataSegment* data) = 0; virtual void - copy(Address dest, Address src, Address byteCount, const RuntimeMemory* srcMemory) { - Address finalDest = getFinalAddress(dest, 0, byteCount, size()); - Address finalSrc = getFinalAddress(src, 0, byteCount, srcMemory->size()); - std::memmove(&memory[finalDest], &srcMemory->memory[finalSrc], byteCount); - } + copy(Address dest, Address src, Address byteCount, const RuntimeMemory* srcMemory) = 0; - virtual void fill(Address dest, uint8_t value, Address byteCount) { - Address final = getFinalAddress(dest, 0, byteCount, size()); - std::memset(&memory[final], value, byteCount); - } - - void resize(size_t newSize) { - intendedSize = newSize; - const size_t minSize = 1 << 12; - size_t oldAllocatedSize = memory.size(); - memory.resize(std::max(minSize, newSize), 0); - if (newSize < oldAllocatedSize && newSize < minSize) { - std::memset(&memory[newSize], 0, minSize - newSize); - } - } + virtual void fill(Address dest, uint8_t value, Address byteCount) = 0; const Memory* getDefinition() const { return &memoryDefinition; } - template T get(size_t address) const { - if (aligned(&memory[address])) { - return *reinterpret_cast(&memory[address]); - } else { - T loaded; - std::memcpy(&loaded, &memory[address], sizeof(T)); - return loaded; - } - } - - template void set(size_t address, T value) { - if (aligned(&memory[address])) { - *reinterpret_cast(&memory[address]) = value; - } else { - std::memcpy(&memory[address], &value, sizeof(T)); - } - } + virtual const std::vector* getBuffer() const { return nullptr; } - void checkLoadAddress(Address addr, Index bytes, Address memorySizeBytes) const { - trapIfGt(addr, memorySizeBytes - bytes, "highest > memory"); - } - - void - checkAtomicAddress(Address addr, Index bytes, Address memorySizeBytes) const { - checkLoadAddress(addr, bytes, memorySizeBytes); - // Unaligned atomics trap. - if (bytes > 1) { - if (addr & (bytes - 1)) { - std::cerr << "unaligned atomic operation: " << addr << " " << bytes - << "\n"; - throw TrapException{}; - } - } - } + void trap(std::string_view why) const { externalInterface->trap(why); } protected: + ExternalInterface* externalInterface; const Memory memoryDefinition; - std::vector memory; - Address intendedSize = 0; - - template static bool aligned(const uint8_t* address) { - static_assert(!(sizeof(T) & (sizeof(T) - 1)), "must be a power of 2"); - return 0 == (reinterpret_cast(address) & (sizeof(T) - 1)); - } }; class RealRuntimeMemory : public RuntimeMemory { public: - using RuntimeMemory::RuntimeMemory; + RealRuntimeMemory(Memory memory, ExternalInterface* externalInterface); + virtual ~RealRuntimeMemory() = default; + + Literal load(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Type type, + bool signed_) const override; + + void store(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Literal value, + Type type) override; + + bool grow(Address delta) override; + + Address size() const override; + + void + init(Address dest, Address src, Address byteCount, const DataSegment* data) override; + + void + copy(Address dest, Address src, Address byteCount, const RuntimeMemory* srcMemory) override; + + void fill(Address dest, uint8_t value, Address byteCount) override; + + void resize(size_t newSize); + + template T get(size_t address) const; + template void set(size_t address, T value); + + const std::vector* getBuffer() const override { return &memory; } + std::vector& getBuffer() { return memory; } + +protected: + std::vector memory; + Address intendedSize = 0; }; } // namespace wasm diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 6fc32bef809..9bc16040e89 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -184,9 +184,9 @@ class EvallingRuntimeTable : public RuntimeTable { const std::function makeFuncData; }; -class CtorEvalRuntimeMemory : public RuntimeMemory { +class CtorEvalRuntimeMemory : public RealRuntimeMemory { public: - using RuntimeMemory::RuntimeMemory; + using RealRuntimeMemory::RealRuntimeMemory; // override to grow on access Literal load(Address addr, @@ -197,7 +197,7 @@ class CtorEvalRuntimeMemory : public RuntimeMemory { bool signed_) const override { const_cast(this)->ensureCapacity(addr + offset + byteCount); - return RuntimeMemory::load(addr, offset, byteCount, order, type, signed_); + return RealRuntimeMemory::load(addr, offset, byteCount, order, type, signed_); } void store(Address addr, @@ -207,19 +207,17 @@ class CtorEvalRuntimeMemory : public RuntimeMemory { Literal value, Type type) override { ensureCapacity(addr + offset + byteCount); - RuntimeMemory::store(addr, offset, byteCount, order, value, type); + RealRuntimeMemory::store(addr, offset, byteCount, order, value, type); } void ensureCapacity(Address size) { - if (size > memory.size()) { + if (size > getBuffer().size()) { if (size > 100 * 1024 * 1024) { // MaximumMemory throw FailToEvalException("excessively high memory address accessed"); } resize(size); } } - - std::vector& getBuffer() { return memory; } }; class EvallingModuleRunner : public ModuleRunnerBase { @@ -243,8 +241,8 @@ class EvallingModuleRunner : public ModuleRunnerBase { this->wasm, [this](Name name, Type type) { return makeFuncData(name, type); }); }, - [](Memory memory) { - return std::make_unique(memory); + [](Memory memory, ExternalInterface* externalInterface) { + return std::make_unique(memory, externalInterface); }) {} Flow visitGlobalGet(GlobalGet* curr) { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index d44dce58fb7..338cf62e1ac 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3003,7 +3003,7 @@ class ModuleRunnerBase : public ExpressionRunner { // ExternalInterface provides embedding-specific functionality like calling // an imported function or accessing memory. // - struct ExternalInterface { + struct ExternalInterface : RuntimeMemory::ExternalInterface { ExternalInterface( std::map> linkedInstances = {}) {} virtual ~ExternalInterface() = default; @@ -3035,7 +3035,7 @@ class ModuleRunnerBase : public ExpressionRunner { std::unordered_map allMemories; using CreateTableFunc = std::unique_ptr(Literal, Table); - using CreateMemoryFunc = std::unique_ptr(Memory); + using CreateMemoryFunc = std::unique_ptr(Memory, ExternalInterface*); ModuleRunnerBase( Module& wasm, @@ -3059,8 +3059,8 @@ class ModuleRunnerBase : public ExpressionRunner { createMemory != nullptr ? std::move(createMemory) : static_cast>( - [](Memory m) -> std::unique_ptr { - return std::make_unique(m); + [externalInterface](Memory m, ExternalInterface* ei) -> std::unique_ptr { + return std::make_unique(m, ei); })) { // Set up a single shared CurrContinuations for all these linked instances, // reusing one if it exists. @@ -3468,7 +3468,7 @@ class ModuleRunnerBase : public ExpressionRunner { assert(inserted && "Unexpected repeated memory name"); } else { auto& runtimeMemory = - definedMemories.emplace_back(createMemory(*memory)); + definedMemories.emplace_back(createMemory(*memory, externalInterface)); [[maybe_unused]] auto [_, inserted] = allMemories.try_emplace(memory->name, runtimeMemory.get()); assert(inserted && "Unexpected repeated memory name"); From 9e88bbfe39702e021c0213700083dcc158805628 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 11 Mar 2026 22:53:07 +0000 Subject: [PATCH 09/12] Passing ctor-eval --- src/tools/wasm-ctor-eval.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 9bc16040e89..656505e6499 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -482,9 +482,15 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { } void applyMemoryToModule() { + if (wasm->memories.empty()) { + return; + } // Memory must have already been flattened into the standard form: one // segment at offset 0, or none. auto& memory = wasm->memories[0]; + if (memory->imported()) { + return; + } if (wasm->dataSegments.empty()) { Builder builder(*wasm); auto curr = builder.makeDataSegment(); From 2c2fd5ad435d25ccd68d5b07470c4189ae2e4fee Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Wed, 11 Mar 2026 22:53:45 +0000 Subject: [PATCH 10/12] formatting --- src/ir/runtime-memory.cpp | 19 +++++++++++-------- src/ir/runtime-memory.h | 24 ++++++++++++++++-------- src/tools/wasm-ctor-eval.cpp | 6 ++++-- src/wasm-interpreter.h | 21 +++++++++++---------- 4 files changed, 42 insertions(+), 28 deletions(-) diff --git a/src/ir/runtime-memory.cpp b/src/ir/runtime-memory.cpp index 0ea3e705b5a..8c11f81c22e 100644 --- a/src/ir/runtime-memory.cpp +++ b/src/ir/runtime-memory.cpp @@ -20,7 +20,8 @@ namespace wasm { -RuntimeMemory::RuntimeMemory(Memory memory, ExternalInterface* externalInterface) +RuntimeMemory::RuntimeMemory(Memory memory, + ExternalInterface* externalInterface) : externalInterface(externalInterface), memoryDefinition(std::move(memory)) {} namespace { @@ -212,9 +213,9 @@ void RealRuntimeMemory::store(Address addr, case Type::f32: { switch (byteCount) { case 2: - set(final, - fp16_ieee_from_fp32_value( - bit_cast(value.reinterpreti32()))); + set( + final, + fp16_ieee_from_fp32_value(bit_cast(value.reinterpreti32()))); break; case 4: set(final, value.reinterpreti32()); @@ -271,8 +272,8 @@ void RealRuntimeMemory::copy(Address dest, Address byteCount, const RuntimeMemory* srcMemory) { Address finalDest = getFinalAddress(*this, dest, 0, byteCount, size()); - Address finalSrc = getFinalAddress( - *srcMemory, src, 0, byteCount, srcMemory->size()); + Address finalSrc = + getFinalAddress(*srcMemory, src, 0, byteCount, srcMemory->size()); const std::vector* srcBuffer = srcMemory->getBuffer(); if (!srcBuffer) { // If it's not a memory with a direct buffer, we might need another way to @@ -299,7 +300,8 @@ void RealRuntimeMemory::resize(size_t newSize) { size_t newAllocatedSize = std::max(minSize, newSize); if (newAllocatedSize > oldAllocatedSize) { memory.resize(newAllocatedSize); - std::memset(&memory[oldAllocatedSize], 0, newAllocatedSize - oldAllocatedSize); + std::memset( + &memory[oldAllocatedSize], 0, newAllocatedSize - oldAllocatedSize); } if (newSize < oldAllocatedSize && newSize < minSize) { std::memset(&memory[newSize], 0, minSize - newSize); @@ -345,6 +347,7 @@ template void RealRuntimeMemory::set(size_t, uint32_t); template void RealRuntimeMemory::set(size_t, int64_t); template void RealRuntimeMemory::set(size_t, uint64_t); template void -RealRuntimeMemory::set>(size_t, std::array); +RealRuntimeMemory::set>(size_t, + std::array); } // namespace wasm diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index 8ca2dd667a9..d57bf100cb1 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -50,11 +50,15 @@ class RuntimeMemory { virtual Address size() const = 0; - virtual void - init(Address dest, Address src, Address byteCount, const DataSegment* data) = 0; + virtual void init(Address dest, + Address src, + Address byteCount, + const DataSegment* data) = 0; - virtual void - copy(Address dest, Address src, Address byteCount, const RuntimeMemory* srcMemory) = 0; + virtual void copy(Address dest, + Address src, + Address byteCount, + const RuntimeMemory* srcMemory) = 0; virtual void fill(Address dest, uint8_t value, Address byteCount) = 0; @@ -92,11 +96,15 @@ class RealRuntimeMemory : public RuntimeMemory { Address size() const override; - void - init(Address dest, Address src, Address byteCount, const DataSegment* data) override; + void init(Address dest, + Address src, + Address byteCount, + const DataSegment* data) override; - void - copy(Address dest, Address src, Address byteCount, const RuntimeMemory* srcMemory) override; + void copy(Address dest, + Address src, + Address byteCount, + const RuntimeMemory* srcMemory) override; void fill(Address dest, uint8_t value, Address byteCount) override; diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 656505e6499..5b76849a9c2 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -197,7 +197,8 @@ class CtorEvalRuntimeMemory : public RealRuntimeMemory { bool signed_) const override { const_cast(this)->ensureCapacity(addr + offset + byteCount); - return RealRuntimeMemory::load(addr, offset, byteCount, order, type, signed_); + return RealRuntimeMemory::load( + addr, offset, byteCount, order, type, signed_); } void store(Address addr, @@ -242,7 +243,8 @@ class EvallingModuleRunner : public ModuleRunnerBase { [this](Name name, Type type) { return makeFuncData(name, type); }); }, [](Memory memory, ExternalInterface* externalInterface) { - return std::make_unique(memory, externalInterface); + return std::make_unique(memory, + externalInterface); }) {} Flow visitGlobalGet(GlobalGet* curr) { diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 338cf62e1ac..9d56cd3cb1d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3035,7 +3035,8 @@ class ModuleRunnerBase : public ExpressionRunner { std::unordered_map allMemories; using CreateTableFunc = std::unique_ptr(Literal, Table); - using CreateMemoryFunc = std::unique_ptr(Memory, ExternalInterface*); + using CreateMemoryFunc = std::unique_ptr(Memory, + ExternalInterface*); ModuleRunnerBase( Module& wasm, @@ -3055,13 +3056,13 @@ class ModuleRunnerBase : public ExpressionRunner { [](Literal initial, Table t) -> std::unique_ptr { return std::make_unique(initial, t); })), - createMemory( - createMemory != nullptr - ? std::move(createMemory) - : static_cast>( - [externalInterface](Memory m, ExternalInterface* ei) -> std::unique_ptr { - return std::make_unique(m, ei); - })) { + createMemory(createMemory != nullptr + ? std::move(createMemory) + : static_cast>( + [externalInterface](Memory m, ExternalInterface* ei) + -> std::unique_ptr { + return std::make_unique(m, ei); + })) { // Set up a single shared CurrContinuations for all these linked instances, // reusing one if it exists. std::shared_ptr shared; @@ -3467,8 +3468,8 @@ class ModuleRunnerBase : public ExpressionRunner { // parsing/validation checked this already. assert(inserted && "Unexpected repeated memory name"); } else { - auto& runtimeMemory = - definedMemories.emplace_back(createMemory(*memory, externalInterface)); + auto& runtimeMemory = definedMemories.emplace_back( + createMemory(*memory, externalInterface)); [[maybe_unused]] auto [_, inserted] = allMemories.try_emplace(memory->name, runtimeMemory.get()); assert(inserted && "Unexpected repeated memory name"); From bdde2f3447507cb751e21d8f354d205ff0c09102 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 12 Mar 2026 17:33:38 +0000 Subject: [PATCH 11/12] Followups with trap to remove ExternalInterface --- src/ir/runtime-memory.cpp | 65 +++++++++++++++++------------------- src/ir/runtime-memory.h | 26 ++++----------- src/tools/wasm-ctor-eval.cpp | 22 +++++++++--- src/wasm-interpreter.h | 24 ++++++------- 4 files changed, 67 insertions(+), 70 deletions(-) diff --git a/src/ir/runtime-memory.cpp b/src/ir/runtime-memory.cpp index 8c11f81c22e..18fad752193 100644 --- a/src/ir/runtime-memory.cpp +++ b/src/ir/runtime-memory.cpp @@ -17,17 +17,18 @@ #include "ir/runtime-memory.h" #include "fp16.h" #include "interpreter/exception.h" +#include namespace wasm { -RuntimeMemory::RuntimeMemory(Memory memory, - ExternalInterface* externalInterface) - : externalInterface(externalInterface), memoryDefinition(std::move(memory)) {} - namespace { -Address getFinalAddress(const RuntimeMemory& runtimeMemory, - Address addr, +[[noreturn]] void trap(std::string_view reason) { + std::cout << "[trap " << reason << "]\n"; + throw TrapException{}; +} + +Address getFinalAddress(Address addr, Address offset, Index bytes, Address memorySizeBytes) { @@ -36,14 +37,14 @@ Address getFinalAddress(const RuntimeMemory& runtimeMemory, msg += std::to_string(uint64_t(offset)); msg += " > "; msg += std::to_string(uint64_t(memorySizeBytes)); - runtimeMemory.trap(msg); + trap(msg); } if (addr > memorySizeBytes - offset) { std::string msg = "final > memory: "; msg += std::to_string(uint64_t(addr)); msg += " > "; msg += std::to_string(uint64_t(memorySizeBytes - offset)); - runtimeMemory.trap(msg); + trap(msg); } addr = size_t(addr) + offset; @@ -55,13 +56,12 @@ Address getFinalAddress(const RuntimeMemory& runtimeMemory, msg += std::to_string(uint64_t(bytes)); msg += " > "; msg += std::to_string(uint64_t(memorySizeBytes)); - runtimeMemory.trap(msg); + trap(msg); } return addr; } -void checkLoadAddress(const RuntimeMemory& runtimeMemory, - Address addr, +void checkLoadAddress(Address addr, Index bytes, Address memorySizeBytes) { if (addr > memorySizeBytes || bytes > memorySizeBytes - addr) { @@ -71,19 +71,18 @@ void checkLoadAddress(const RuntimeMemory& runtimeMemory, msg += std::to_string(uint64_t(bytes)); msg += " > "; msg += std::to_string(uint64_t(memorySizeBytes)); - runtimeMemory.trap(msg); + trap(msg); } } -void checkAtomicAddress(const RuntimeMemory& runtimeMemory, - Address addr, +void checkAtomicAddress(Address addr, Index bytes, Address memorySizeBytes) { - checkLoadAddress(runtimeMemory, addr, bytes, memorySizeBytes); + checkLoadAddress(addr, bytes, memorySizeBytes); // Unaligned atomics trap. if (bytes > 1) { if (addr & (bytes - 1)) { - runtimeMemory.trap("unaligned atomic operation"); + trap("unaligned atomic operation"); } } } @@ -95,9 +94,8 @@ template bool aligned(const uint8_t* address) { } // namespace -RealRuntimeMemory::RealRuntimeMemory(Memory memory, - ExternalInterface* externalInterface) - : RuntimeMemory(std::move(memory), externalInterface) { +RealRuntimeMemory::RealRuntimeMemory(Memory memory) + : RuntimeMemory(std::move(memory)) { resize(memoryDefinition.initialByteSize()); } @@ -107,9 +105,9 @@ Literal RealRuntimeMemory::load(Address addr, MemoryOrder order, Type type, bool signed_) const { - Address final = getFinalAddress(*this, addr, offset, byteCount, size()); + Address final = getFinalAddress(addr, offset, byteCount, size()); if (order != MemoryOrder::Unordered) { - checkAtomicAddress(*this, final, byteCount, size()); + checkAtomicAddress(final, byteCount, size()); } switch (type.getBasic()) { case Type::i32: { @@ -170,9 +168,9 @@ void RealRuntimeMemory::store(Address addr, MemoryOrder order, Literal value, Type type) { - Address final = getFinalAddress(*this, addr, offset, byteCount, size()); + Address final = getFinalAddress(addr, offset, byteCount, size()); if (order != MemoryOrder::Unordered) { - checkAtomicAddress(*this, final, byteCount, size()); + checkAtomicAddress(final, byteCount, size()); } switch (type.getBasic()) { case Type::i32: { @@ -213,9 +211,9 @@ void RealRuntimeMemory::store(Address addr, case Type::f32: { switch (byteCount) { case 2: - set( - final, - fp16_ieee_from_fp32_value(bit_cast(value.reinterpreti32()))); + set(final, + fp16_ieee_from_fp32_value( + bit_cast(value.reinterpreti32()))); break; case 4: set(final, value.reinterpreti32()); @@ -261,7 +259,7 @@ void RealRuntimeMemory::init(Address dest, if (src > data->data.size() || byteCount > data->data.size() - src) { trap("out of bounds segment access in memory.init"); } - Address final = getFinalAddress(*this, dest, 0, byteCount, size()); + Address final = getFinalAddress(dest, 0, byteCount, size()); if (byteCount > 0) { std::memcpy(&memory[final], &data->data[src], byteCount); } @@ -271,9 +269,8 @@ void RealRuntimeMemory::copy(Address dest, Address src, Address byteCount, const RuntimeMemory* srcMemory) { - Address finalDest = getFinalAddress(*this, dest, 0, byteCount, size()); - Address finalSrc = - getFinalAddress(*srcMemory, src, 0, byteCount, srcMemory->size()); + Address finalDest = getFinalAddress(dest, 0, byteCount, size()); + Address finalSrc = getFinalAddress(src, 0, byteCount, srcMemory->size()); const std::vector* srcBuffer = srcMemory->getBuffer(); if (!srcBuffer) { // If it's not a memory with a direct buffer, we might need another way to @@ -287,7 +284,7 @@ void RealRuntimeMemory::copy(Address dest, } void RealRuntimeMemory::fill(Address dest, uint8_t value, Address byteCount) { - Address final = getFinalAddress(*this, dest, 0, byteCount, size()); + Address final = getFinalAddress(dest, 0, byteCount, size()); if (byteCount > 0) { std::memset(&memory[final], value, byteCount); } @@ -300,8 +297,7 @@ void RealRuntimeMemory::resize(size_t newSize) { size_t newAllocatedSize = std::max(minSize, newSize); if (newAllocatedSize > oldAllocatedSize) { memory.resize(newAllocatedSize); - std::memset( - &memory[oldAllocatedSize], 0, newAllocatedSize - oldAllocatedSize); + std::memset(&memory[oldAllocatedSize], 0, newAllocatedSize - oldAllocatedSize); } if (newSize < oldAllocatedSize && newSize < minSize) { std::memset(&memory[newSize], 0, minSize - newSize); @@ -347,7 +343,6 @@ template void RealRuntimeMemory::set(size_t, uint32_t); template void RealRuntimeMemory::set(size_t, int64_t); template void RealRuntimeMemory::set(size_t, uint64_t); template void -RealRuntimeMemory::set>(size_t, - std::array); +RealRuntimeMemory::set>(size_t, std::array); } // namespace wasm diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index d57bf100cb1..89f35158a78 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -23,13 +23,7 @@ namespace wasm { class RuntimeMemory { public: - // Forward declare to avoid circular dependency - struct ExternalInterface { - virtual ~ExternalInterface() = default; - virtual void trap(std::string_view why) = 0; - }; - - RuntimeMemory(Memory memory, ExternalInterface* externalInterface); + RuntimeMemory(Memory memory) : memoryDefinition(std::move(memory)) {} virtual ~RuntimeMemory() = default; virtual Literal load(Address addr, @@ -50,15 +44,11 @@ class RuntimeMemory { virtual Address size() const = 0; - virtual void init(Address dest, - Address src, - Address byteCount, - const DataSegment* data) = 0; + virtual void + init(Address dest, Address src, Address byteCount, const DataSegment* data) = 0; - virtual void copy(Address dest, - Address src, - Address byteCount, - const RuntimeMemory* srcMemory) = 0; + virtual void + copy(Address dest, Address src, Address byteCount, const RuntimeMemory* srcMemory) = 0; virtual void fill(Address dest, uint8_t value, Address byteCount) = 0; @@ -66,16 +56,14 @@ class RuntimeMemory { virtual const std::vector* getBuffer() const { return nullptr; } - void trap(std::string_view why) const { externalInterface->trap(why); } - protected: - ExternalInterface* externalInterface; const Memory memoryDefinition; }; class RealRuntimeMemory : public RuntimeMemory { public: - RealRuntimeMemory(Memory memory, ExternalInterface* externalInterface); + RealRuntimeMemory(Memory memory); + virtual ~RealRuntimeMemory() = default; Literal load(Address addr, diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 5b76849a9c2..531b4707593 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -42,6 +42,7 @@ #include "wasm-interpreter.h" #include "wasm-io.h" #include "wasm-validator.h" +#include "interpreter/exception.h" using namespace wasm; @@ -242,9 +243,8 @@ class EvallingModuleRunner : public ModuleRunnerBase { this->wasm, [this](Name name, Type type) { return makeFuncData(name, type); }); }, - [](Memory memory, ExternalInterface* externalInterface) { - return std::make_unique(memory, - externalInterface); + [](Memory memory) { + return std::make_unique(memory); }) {} Flow visitGlobalGet(GlobalGet* curr) { @@ -1117,6 +1117,12 @@ EvalCtorOutcome evalCtor(EvallingModuleRunner& instance, Flow flow; try { flow = instance.visit(curr); + } catch (TrapException&) { + throw FailToEvalException("trap"); + } catch (WasmException& exn) { + std::stringstream ss; + ss << "exception thrown: " << exn; + throw FailToEvalException(ss.str()); } catch (FailToEvalException& fail) { if (!quiet) { if (successes == 0) { @@ -1353,7 +1359,15 @@ void evalCtors(Module& wasm, // create an instance for evalling EvallingModuleRunner instance( wasm, &interface, interface.instanceInitialized, linkedInstances); - instance.instantiate(); + try { + instance.instantiate(); + } catch (TrapException&) { + throw FailToEvalException("trap"); + } catch (WasmException& exn) { + std::stringstream ss; + ss << "exception thrown: " << exn; + throw FailToEvalException(ss.str()); + } interface.instanceInitialized = true; // go one by one, in order, until we fail // TODO: if we knew priorities, we could reorder? diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 9d56cd3cb1d..dfada7c5415 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -3003,7 +3003,7 @@ class ModuleRunnerBase : public ExpressionRunner { // ExternalInterface provides embedding-specific functionality like calling // an imported function or accessing memory. // - struct ExternalInterface : RuntimeMemory::ExternalInterface { + struct ExternalInterface { ExternalInterface( std::map> linkedInstances = {}) {} virtual ~ExternalInterface() = default; @@ -3035,8 +3035,7 @@ class ModuleRunnerBase : public ExpressionRunner { std::unordered_map allMemories; using CreateTableFunc = std::unique_ptr(Literal, Table); - using CreateMemoryFunc = std::unique_ptr(Memory, - ExternalInterface*); + using CreateMemoryFunc = std::unique_ptr(Memory); ModuleRunnerBase( Module& wasm, @@ -3056,13 +3055,14 @@ class ModuleRunnerBase : public ExpressionRunner { [](Literal initial, Table t) -> std::unique_ptr { return std::make_unique(initial, t); })), - createMemory(createMemory != nullptr - ? std::move(createMemory) - : static_cast>( - [externalInterface](Memory m, ExternalInterface* ei) - -> std::unique_ptr { - return std::make_unique(m, ei); - })) { + createMemory( + createMemory != nullptr + ? std::move(createMemory) + : static_cast>( + [](Memory m) -> std::unique_ptr { + return std::make_unique(m); + })) { + // Set up a single shared CurrContinuations for all these linked instances, // reusing one if it exists. std::shared_ptr shared; @@ -3468,8 +3468,8 @@ class ModuleRunnerBase : public ExpressionRunner { // parsing/validation checked this already. assert(inserted && "Unexpected repeated memory name"); } else { - auto& runtimeMemory = definedMemories.emplace_back( - createMemory(*memory, externalInterface)); + auto& runtimeMemory = + definedMemories.emplace_back(createMemory(*memory)); [[maybe_unused]] auto [_, inserted] = allMemories.try_emplace(memory->name, runtimeMemory.get()); assert(inserted && "Unexpected repeated memory name"); From 1cd38b364b3d07b3657efcb25c74f33f75e693e1 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Thu, 12 Mar 2026 17:43:14 +0000 Subject: [PATCH 12/12] Change the methods --- src/ir/runtime-memory.cpp | 104 ++++++++++++----------------------- src/ir/runtime-memory.h | 16 ++++-- src/tools/wasm-ctor-eval.cpp | 8 +-- 3 files changed, 49 insertions(+), 79 deletions(-) diff --git a/src/ir/runtime-memory.cpp b/src/ir/runtime-memory.cpp index 18fad752193..fc6581cbae7 100644 --- a/src/ir/runtime-memory.cpp +++ b/src/ir/runtime-memory.cpp @@ -28,60 +28,12 @@ namespace { throw TrapException{}; } -Address getFinalAddress(Address addr, - Address offset, - Index bytes, - Address memorySizeBytes) { - if (offset > memorySizeBytes) { - std::string msg = "offset > memory: "; - msg += std::to_string(uint64_t(offset)); - msg += " > "; - msg += std::to_string(uint64_t(memorySizeBytes)); - trap(msg); - } - if (addr > memorySizeBytes - offset) { - std::string msg = "final > memory: "; - msg += std::to_string(uint64_t(addr)); - msg += " > "; - msg += std::to_string(uint64_t(memorySizeBytes - offset)); - trap(msg); - } - - addr = size_t(addr) + offset; - - if (bytes > memorySizeBytes - addr) { - std::string msg = "highest > memory: "; - msg += std::to_string(uint64_t(addr)); - msg += " + "; - msg += std::to_string(uint64_t(bytes)); - msg += " > "; - msg += std::to_string(uint64_t(memorySizeBytes)); - trap(msg); - } - return addr; -} - -void checkLoadAddress(Address addr, - Index bytes, - Address memorySizeBytes) { - if (addr > memorySizeBytes || bytes > memorySizeBytes - addr) { - std::string msg = "highest > memory: "; - msg += std::to_string(uint64_t(addr)); - msg += " + "; - msg += std::to_string(uint64_t(bytes)); - msg += " > "; - msg += std::to_string(uint64_t(memorySizeBytes)); - trap(msg); - } -} - -void checkAtomicAddress(Address addr, - Index bytes, - Address memorySizeBytes) { - checkLoadAddress(addr, bytes, memorySizeBytes); +void checkAtomicAddress(const RuntimeMemory& runtimeMemory, + Address finalAddr, + Index bytes) { // Unaligned atomics trap. if (bytes > 1) { - if (addr & (bytes - 1)) { + if (finalAddr & (bytes - 1)) { trap("unaligned atomic operation"); } } @@ -105,9 +57,9 @@ Literal RealRuntimeMemory::load(Address addr, MemoryOrder order, Type type, bool signed_) const { - Address final = getFinalAddress(addr, offset, byteCount, size()); + Address final = validateAddress(addr, offset, byteCount); if (order != MemoryOrder::Unordered) { - checkAtomicAddress(final, byteCount, size()); + checkAtomicAddress(*this, final, byteCount); } switch (type.getBasic()) { case Type::i32: { @@ -168,9 +120,9 @@ void RealRuntimeMemory::store(Address addr, MemoryOrder order, Literal value, Type type) { - Address final = getFinalAddress(addr, offset, byteCount, size()); + Address final = validateAddress(addr, offset, byteCount); if (order != MemoryOrder::Unordered) { - checkAtomicAddress(final, byteCount, size()); + checkAtomicAddress(*this, final, byteCount); } switch (type.getBasic()) { case Type::i32: { @@ -259,7 +211,7 @@ void RealRuntimeMemory::init(Address dest, if (src > data->data.size() || byteCount > data->data.size() - src) { trap("out of bounds segment access in memory.init"); } - Address final = getFinalAddress(dest, 0, byteCount, size()); + Address final = validateAddress(dest, 0, byteCount); if (byteCount > 0) { std::memcpy(&memory[final], &data->data[src], byteCount); } @@ -269,27 +221,43 @@ void RealRuntimeMemory::copy(Address dest, Address src, Address byteCount, const RuntimeMemory* srcMemory) { - Address finalDest = getFinalAddress(dest, 0, byteCount, size()); - Address finalSrc = getFinalAddress(src, 0, byteCount, srcMemory->size()); - const std::vector* srcBuffer = srcMemory->getBuffer(); - if (!srcBuffer) { - // If it's not a memory with a direct buffer, we might need another way to - // access it, or we can just fail if this is the only implementation we - // support for now. - WASM_UNREACHABLE("unsupported srcMemory type in copy"); - } + Address finalDest = validateAddress(dest, 0, byteCount); if (byteCount > 0) { - std::memmove(&memory[finalDest], &(*srcBuffer)[finalSrc], byteCount); + srcMemory->copyTo(&memory[finalDest], src, byteCount); + } else { + // still need to validate src even for 0-byte copy + srcMemory->validateAddress(src, 0, 0); } } void RealRuntimeMemory::fill(Address dest, uint8_t value, Address byteCount) { - Address final = getFinalAddress(dest, 0, byteCount, size()); + Address final = validateAddress(dest, 0, byteCount); if (byteCount > 0) { std::memset(&memory[final], value, byteCount); } } +void RealRuntimeMemory::copyTo(uint8_t* dest, Address src, Address byteCount) const { + Address finalSrc = validateAddress(src, 0, byteCount); + if (byteCount > 0 && dest) { + std::memcpy(dest, &memory[finalSrc], byteCount); + } +} + +Address RealRuntimeMemory::validateAddress(Address addr, Address offset, Address byteCount) const { + Address memorySizeBytes = size(); + if (offset > memorySizeBytes || addr > memorySizeBytes - offset) { + trap("out of bounds memory access"); + } + + addr = size_t(addr) + offset; + + if (byteCount > memorySizeBytes - addr) { + trap("out of bounds memory access"); + } + return addr; +} + void RealRuntimeMemory::resize(size_t newSize) { intendedSize = newSize; const size_t minSize = 1 << 12; diff --git a/src/ir/runtime-memory.h b/src/ir/runtime-memory.h index 89f35158a78..eaf4a688408 100644 --- a/src/ir/runtime-memory.h +++ b/src/ir/runtime-memory.h @@ -54,7 +54,9 @@ class RuntimeMemory { const Memory* getDefinition() const { return &memoryDefinition; } - virtual const std::vector* getBuffer() const { return nullptr; } + virtual void copyTo(uint8_t* dest, Address src, Address byteCount) const = 0; + + virtual Address validateAddress(Address addr, Address offset, Address byteCount) const = 0; protected: const Memory memoryDefinition; @@ -96,17 +98,19 @@ class RealRuntimeMemory : public RuntimeMemory { void fill(Address dest, uint8_t value, Address byteCount) override; - void resize(size_t newSize); + void copyTo(uint8_t* dest, Address src, Address byteCount) const override; - template T get(size_t address) const; - template void set(size_t address, T value); + Address validateAddress(Address addr, Address offset, Address byteCount) const override; - const std::vector* getBuffer() const override { return &memory; } - std::vector& getBuffer() { return memory; } + void resize(size_t newSize); protected: std::vector memory; Address intendedSize = 0; + +private: + template T get(size_t address) const; + template void set(size_t address, T value); }; } // namespace wasm diff --git a/src/tools/wasm-ctor-eval.cpp b/src/tools/wasm-ctor-eval.cpp index 531b4707593..dc5ef6f4f96 100644 --- a/src/tools/wasm-ctor-eval.cpp +++ b/src/tools/wasm-ctor-eval.cpp @@ -213,7 +213,7 @@ class CtorEvalRuntimeMemory : public RealRuntimeMemory { } void ensureCapacity(Address size) { - if (size > getBuffer().size()) { + if (size > memory.size()) { if (size > 100 * 1024 * 1024) { // MaximumMemory throw FailToEvalException("excessively high memory address accessed"); } @@ -509,10 +509,8 @@ struct CtorEvalExternalInterface : EvallingModuleRunner::ExternalInterface { // memory. auto* runtimeMemory = static_cast(instance->allMemories[memory->name]); - segment->data.resize(runtimeMemory->getBuffer().size()); - std::memcpy(segment->data.data(), - runtimeMemory->getBuffer().data(), - runtimeMemory->getBuffer().size()); + segment->data.resize(runtimeMemory->size()); + runtimeMemory->copyTo((uint8_t*)segment->data.data(), 0, runtimeMemory->size()); } // Serializing GC data requires more work than linear memory, because