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/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.cpp b/src/ir/runtime-memory.cpp new file mode 100644 index 00000000000..fc6581cbae7 --- /dev/null +++ b/src/ir/runtime-memory.cpp @@ -0,0 +1,316 @@ +/* + * 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" +#include + +namespace wasm { + +namespace { + +[[noreturn]] void trap(std::string_view reason) { + std::cout << "[trap " << reason << "]\n"; + throw TrapException{}; +} + +void checkAtomicAddress(const RuntimeMemory& runtimeMemory, + Address finalAddr, + Index bytes) { + // Unaligned atomics trap. + if (bytes > 1) { + if (finalAddr & (bytes - 1)) { + 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) + : RuntimeMemory(std::move(memory)) { + resize(memoryDefinition.initialByteSize()); +} + +Literal RealRuntimeMemory::load(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Type type, + bool signed_) const { + Address final = validateAddress(addr, offset, byteCount); + if (order != MemoryOrder::Unordered) { + checkAtomicAddress(*this, final, byteCount); + } + 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 = validateAddress(addr, offset, byteCount); + if (order != MemoryOrder::Unordered) { + checkAtomicAddress(*this, final, byteCount); + } + 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 = validateAddress(dest, 0, byteCount); + 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 = validateAddress(dest, 0, byteCount); + if (byteCount > 0) { + 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 = 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; + 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 new file mode 100644 index 00000000000..eaf4a688408 --- /dev/null +++ b/src/ir/runtime-memory.h @@ -0,0 +1,118 @@ +/* + * 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 { + +class RuntimeMemory { +public: + RuntimeMemory(Memory memory) : memoryDefinition(std::move(memory)) {} + virtual ~RuntimeMemory() = default; + + virtual Literal load(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Type type, + bool signed_) const = 0; + + virtual void store(Address addr, + Address offset, + uint8_t byteCount, + MemoryOrder order, + Literal value, + Type type) = 0; + + virtual bool grow(Address delta) = 0; + + virtual Address size() const = 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 fill(Address dest, uint8_t value, Address byteCount) = 0; + + const Memory* getDefinition() const { return &memoryDefinition; } + + 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; +}; + +class RealRuntimeMemory : public RuntimeMemory { +public: + RealRuntimeMemory(Memory memory); + + 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 copyTo(uint8_t* dest, Address src, Address byteCount) const override; + + Address validateAddress(Address addr, Address offset, Address byteCount) const override; + + 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 + +#endif // wasm_ir_runtime_memory_h 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/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 9b2800a2d84..dc5ef6f4f96 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; @@ -95,6 +96,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; @@ -178,6 +185,43 @@ class EvallingRuntimeTable : public RuntimeTable { const std::function makeFuncData; }; +class CtorEvalRuntimeMemory : public RealRuntimeMemory { +public: + using RealRuntimeMemory::RealRuntimeMemory; + + // 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 RealRuntimeMemory::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); + RealRuntimeMemory::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); + } + } +}; + class EvallingModuleRunner : public ModuleRunnerBase { public: EvallingModuleRunner( @@ -198,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) { @@ -285,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 @@ -309,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); @@ -346,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))}; } @@ -368,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))}; } @@ -405,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)); } @@ -472,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. // @@ -526,9 +484,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(); @@ -543,7 +507,10 @@ 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->size()); + runtimeMemory->copyTo((uint8_t*)segment->data.data(), 0, runtimeMemory->size()); } // Serializing GC data requires more work than linear memory, because @@ -1148,6 +1115,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) { @@ -1384,7 +1357,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 5e6b0cb3e51..dfada7c5415 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" @@ -3008,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); } @@ -3207,14 +3032,18 @@ class ModuleRunnerBase : public ExpressionRunner { std::unordered_map allTags; + 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_)), @@ -3225,7 +3054,15 @@ 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. std::shared_ptr shared; @@ -3259,6 +3096,7 @@ class ModuleRunnerBase : public ExpressionRunner { initializeTables(); initializeTags(); + initializeMemories(); initializeMemoryContents(); // run start, if present @@ -3320,6 +3158,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) { @@ -3371,6 +3222,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; @@ -3594,6 +3446,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(createMemory(*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; @@ -3620,8 +3503,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]; @@ -3658,37 +3539,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 memoryName) { + auto* memory = allMemories[memoryName]; + return memory->size() / memory->getDefinition()->pageSize(); } - 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; - } - - 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: @@ -4059,39 +3917,36 @@ 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(flow.getSingleValue().getUnsigned(), + curr->offset, + curr->bytes, + 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: @@ -4112,27 +3967,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; } @@ -4141,16 +3996,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 } @@ -4167,12 +4016,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) { @@ -4233,35 +4084,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); }; @@ -4288,39 +4149,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 {}; } } @@ -4328,12 +4198,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 {}; } } @@ -4341,12 +4215,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 {}; } } @@ -4354,12 +4232,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 {}; } } @@ -4367,77 +4249,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 {}; @@ -4446,66 +4291,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 destVal = dest.getSingleValue().getUnsigned(); + auto sourceVal = source.getSingleValue().getUnsigned(); + auto 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"); - } - - 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) { @@ -5181,53 +4985,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, @@ -5237,50 +4994,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 { @@ -5289,7 +5020,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, @@ -5297,7 +5029,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 81cb60baf9d..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) { @@ -2572,8 +2572,10 @@ 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 uint64_t(initial) << pageSizeLog2; } }; 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