From 0ed5b59fff8d402c914720c35fc31d0ab10a1345 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 17 Oct 2024 17:41:41 -0700 Subject: [PATCH 01/17] Lower memory.copy --- src/passes/CMakeLists.txt | 1 + src/passes/MemoryCopyFillLowering.cpp | 140 ++++++++++++++++++++++++++ src/passes/pass.cpp | 4 + src/passes/passes.h | 1 + 4 files changed, 146 insertions(+) create mode 100644 src/passes/MemoryCopyFillLowering.cpp diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index a219438b410..6053c55222a 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -63,6 +63,7 @@ set(passes_SOURCES LogExecution.cpp LoopInvariantCodeMotion.cpp Memory64Lowering.cpp + MemoryCopyFillLowering.cpp MemoryPacking.cpp MergeBlocks.cpp MergeSimilarFunctions.cpp diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp new file mode 100644 index 00000000000..db680045f62 --- /dev/null +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -0,0 +1,140 @@ +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" +#include + +// By default LLVM emits nontrapping float-to-int instructions to implement its +// fptoui/fptosi conversion instructions. This pass replaces these instructions +// with code sequences which also implement LLVM's fptoui/fptosi, but which are +// not semantically equivalent in wasm. This is because out-of-range inputs to +// these instructions produce poison values. So we need only ensure that there +// is no trap, but need not ensure any particular result. + +namespace wasm { +struct MemoryCopyFillLowering + : public WalkerPass> { + bool needsMemoryCopy = false; + bool needsMemoryFill = false; + + void visitMemoryCopy(MemoryCopy* curr) { + if (curr->destMemory != curr->sourceMemory) { + return; // Throw an error here instead of silently ignoring? + } + Builder builder(*getModule()); + replaceCurrent(builder.makeCall( + "__memory_copy", {curr->dest, curr->source, curr->size}, + Type::none)); + needsMemoryCopy = true; + } + + void visitMemoryFill(MemoryFill* curr) { + if (curr->memory != "what's the default name of the memory?") { + return; + } + Builder builder(*getModule()); + replaceCurrent(builder.makeCall( + "__memory_fill", {curr->dest, curr->value, curr->size}, Type::none) + ); + needsMemoryFill = true; + } + + void run(Module* module) override { + if (!module->features.hasBulkMemory()) { + return; + } + if (module->features.hasMemory64() || module->features.hasMultiMemory()) { + throw "Memory64 and multi-memory not supported";// TODO: best way to return an error? + } + Builder b(*module); + auto memCpyFunc = b.makeFunction("__memory_copy", + Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), {Type::i32}); + memCpyFunc->body = b.makeBlock(); + module->addFunction(memCpyFunc.release()); + + Super::run(module); + if (needsMemoryCopy) { + Index dst = 0, src = 1, size = 2, temp = 3; + Name memory = module->memories.front()->name; + Block* body = b.makeBlock(); + // temp = memory size in bytes + body->list.push_back(b.makeLocalSet(temp, + b.makeBinary(BinaryOp::MulInt32, + b.makeMemorySize(memory), + b.makeConst(Memory::kPageSize)))); + // if dst + size > memsize or src + size > memsize, then trap. + body->list.push_back( + b.makeIf( + b.makeBinary(BinaryOp::OrInt32, + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(temp, Type::i32)), + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(src, Type::i32), b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(temp, Type::i32))), + b.makeUnreachable()) + ); + // temp = dst + body->list.push_back(b.makeLocalSet(temp, b.makeLocalGet(dst, Type::i32))); + // dst = dst + size + body->list.push_back( + b.makeLocalSet(dst, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)))); + // src = src + size + body->list.push_back( + b.makeLocalSet(src, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(size, Type::i32)))); + // loop ( + body->list.push_back( + b.makeLoop("copy", b.makeBlock({ + // if dst > temp, then break + b.makeBreak("copy", nullptr, + b.makeBinary(BinaryOp::GeUInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(temp, Type::i32))), + b.makeStore(1, 0, 1, b.makeLocalGet(dst, Type::i32), + b.makeLoad(1, false, 0, 1, + b.makeLocalGet(src, Type::i32), Type::i32, memory), Type::i32, memory), + b.makeLocalSet(dst, + b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(dst, Type::i32), b.makeConst(1))), + b.makeLocalSet(src, + b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(src, Type::i32), b.makeConst(1))) + })) + ); + module->getFunction("__memory_copy")->body = body; + + /* + local.set($temp, i32.mul(memory.size 0, i32.const 65536)) + if ( + i32.or( + i32.ugt(i32.add(local.get $src, local.get $size), local.get $temp), + i32.ugt(i32.add(local.get $dst, local.get $size), local.get $temp), + ) + ) then unreachable + local.set($temp, local.get $dst) + local.set($dst, i32.add(local.get $dst, local.get $size)) + local.set($src, i32.add(local.get $src, local.get $size)) + loop ( + br_if (i32.uge(local.get $dst, local.get $temp) + i32.store(local.get $dst, i32.load(get_local $src)) + local.set($dst, i32.sub(local.get($dst), i32.const 1)) + local.set($src, i32.sub(local.get($src), i32.const 1)) + ) + */ + } else { + module->removeFunction("__memory_copy"); + } + module->features.disable(FeatureSet::BulkMemory); + } +}; + +Pass* createMemoryCopyFillLoweringPass() { + return new MemoryCopyFillLowering(); +} + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index d55e22111df..4987dd988fe 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -272,6 +272,10 @@ void PassRegistry::registerPasses() { registerPass("table64-lowering", "lower 64-bit tables 32-bit ones", createTable64LoweringPass); + registerPass("memory-copy-fill-lowering", + "Lower memory.copy and memory.fill to wasm mvp and disable " + "the bulk-memory feature.", + createMemoryCopyFillLoweringPass); registerPass("memory-packing", "packs memory into separate segments, skipping zeros", createMemoryPackingPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index c100cd2f954..121dbcd9de7 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -116,6 +116,7 @@ Pass* createOptimizeForJSPass(); Pass* createOutliningPass(); #endif Pass* createPickLoadSignsPass(); +Pass* createMemoryCopyFillLoweringPass(); Pass* createModAsyncifyAlwaysOnlyUnwindPass(); Pass* createModAsyncifyNeverUnwindPass(); Pass* createPoppifyPass(); From 9bbd7585da6086da88805dc44b3ce098b63a4329 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 17 Oct 2024 18:17:31 -0700 Subject: [PATCH 02/17] update help tests --- test/lit/help/wasm-metadce.test | 9 +++++++++ test/lit/help/wasm-opt.test | 9 +++++++++ test/lit/help/wasm2js.test | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 50295b1f02d..00ac9814258 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -230,6 +230,11 @@ ;; CHECK-NEXT: --limit-segments attempt to merge segments to fit ;; CHECK-NEXT: within web limits ;; CHECK-NEXT: +;; CHECK-NEXT: --llvm-nontrapping-fptoint-lowering lower nontrapping float-to-int +;; CHECK-NEXT: operations to wasm mvp and +;; CHECK-NEXT: disable the nontrapping fptoint +;; CHECK-NEXT: feature +;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: @@ -239,6 +244,10 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: +;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and +;; CHECK-NEXT: memory.fill to wasm mvp and +;; CHECK-NEXT: disable the bulk-memory feature. +;; CHECK-NEXT: ;; CHECK-NEXT: --memory-packing packs memory into separate ;; CHECK-NEXT: segments, skipping zeros ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 0691d1c1839..2db92e1cea3 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -239,6 +239,11 @@ ;; CHECK-NEXT: --limit-segments attempt to merge segments to fit ;; CHECK-NEXT: within web limits ;; CHECK-NEXT: +;; CHECK-NEXT: --llvm-nontrapping-fptoint-lowering lower nontrapping float-to-int +;; CHECK-NEXT: operations to wasm mvp and +;; CHECK-NEXT: disable the nontrapping fptoint +;; CHECK-NEXT: feature +;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: @@ -248,6 +253,10 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: +;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and +;; CHECK-NEXT: memory.fill to wasm mvp and +;; CHECK-NEXT: disable the bulk-memory feature. +;; CHECK-NEXT: ;; CHECK-NEXT: --memory-packing packs memory into separate ;; CHECK-NEXT: segments, skipping zeros ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 89dcaa0280d..1e7a97bb43e 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -193,6 +193,11 @@ ;; CHECK-NEXT: --limit-segments attempt to merge segments to fit ;; CHECK-NEXT: within web limits ;; CHECK-NEXT: +;; CHECK-NEXT: --llvm-nontrapping-fptoint-lowering lower nontrapping float-to-int +;; CHECK-NEXT: operations to wasm mvp and +;; CHECK-NEXT: disable the nontrapping fptoint +;; CHECK-NEXT: feature +;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: @@ -202,6 +207,10 @@ ;; CHECK-NEXT: --log-execution instrument the build with ;; CHECK-NEXT: logging of where execution goes ;; CHECK-NEXT: +;; CHECK-NEXT: --memory-copy-fill-lowering Lower memory.copy and +;; CHECK-NEXT: memory.fill to wasm mvp and +;; CHECK-NEXT: disable the bulk-memory feature. +;; CHECK-NEXT: ;; CHECK-NEXT: --memory-packing packs memory into separate ;; CHECK-NEXT: segments, skipping zeros ;; CHECK-NEXT: From 16285bb04b87482d150acc2fd613190ea4a9508d Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 17 Oct 2024 18:18:35 -0700 Subject: [PATCH 03/17] reverse loop condition --- src/passes/MemoryCopyFillLowering.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index db680045f62..b6517b77973 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -1,14 +1,9 @@ #include "pass.h" #include "wasm-builder.h" #include "wasm.h" -#include -// By default LLVM emits nontrapping float-to-int instructions to implement its -// fptoui/fptosi conversion instructions. This pass replaces these instructions -// with code sequences which also implement LLVM's fptoui/fptosi, but which are -// not semantically equivalent in wasm. This is because out-of-range inputs to -// these instructions produce poison values. So we need only ensure that there -// is no trap, but need not ensure any particular result. +// Replace memory.copy and memory.fill with a call to a function that +// implements the same semantics. namespace wasm { struct MemoryCopyFillLowering @@ -92,14 +87,16 @@ struct MemoryCopyFillLowering // loop ( body->list.push_back( b.makeLoop("copy", b.makeBlock({ - // if dst > temp, then break + // if dst < temp, then break b.makeBreak("copy", nullptr, - b.makeBinary(BinaryOp::GeUInt32, + b.makeBinary(BinaryOp::LtUInt32, b.makeLocalGet(dst, Type::i32), b.makeLocalGet(temp, Type::i32))), + // *dst = *src b.makeStore(1, 0, 1, b.makeLocalGet(dst, Type::i32), b.makeLoad(1, false, 0, 1, b.makeLocalGet(src, Type::i32), Type::i32, memory), Type::i32, memory), + // --dst; --src; b.makeLocalSet(dst, b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(dst, Type::i32), b.makeConst(1))), b.makeLocalSet(src, @@ -120,8 +117,8 @@ struct MemoryCopyFillLowering local.set($dst, i32.add(local.get $dst, local.get $size)) local.set($src, i32.add(local.get $src, local.get $size)) loop ( - br_if (i32.uge(local.get $dst, local.get $temp) - i32.store(local.get $dst, i32.load(get_local $src)) + br_if (i32.ult(local.get $dst, local.get $temp) + i32.store8(local.get $dst, i32.load8_u(get_local $src)) local.set($dst, i32.sub(local.get($dst), i32.const 1)) local.set($src, i32.sub(local.get($src), i32.const 1)) ) From 47dc98f88cdc8555539ff4cfd0e2e2ed2b5e4b52 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 18 Oct 2024 15:51:30 -0700 Subject: [PATCH 04/17] add memory fill --- src/passes/MemoryCopyFillLowering.cpp | 91 ++++++++--- test/lit/help/wasm-metadce.test | 5 - test/lit/help/wasm-opt.test | 5 - test/lit/help/wasm2js.test | 5 - .../lit/passes/memory-copy-fill-lowering.wast | 142 ++++++++++++++++++ 5 files changed, 211 insertions(+), 37 deletions(-) create mode 100644 test/lit/passes/memory-copy-fill-lowering.wast diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index b6517b77973..43732b26497 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -23,9 +23,6 @@ struct MemoryCopyFillLowering } void visitMemoryFill(MemoryFill* curr) { - if (curr->memory != "what's the default name of the memory?") { - return; - } Builder builder(*getModule()); replaceCurrent(builder.makeCall( "__memory_fill", {curr->dest, curr->value, curr->size}, Type::none) @@ -40,13 +37,19 @@ struct MemoryCopyFillLowering if (module->features.hasMemory64() || module->features.hasMultiMemory()) { throw "Memory64 and multi-memory not supported";// TODO: best way to return an error? } + // In order to introduce a call to a function, it must first exist, so create an empty stub. Builder b(*module); - auto memCpyFunc = b.makeFunction("__memory_copy", + auto memCpyFunc = b.makeFunction("__memory_copy", Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), {Type::i32}); memCpyFunc->body = b.makeBlock(); module->addFunction(memCpyFunc.release()); - + auto memFillFunc = b.makeFunction("__memory_fill", + Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), {}); + memFillFunc->body = b.makeBlock(); + module->addFunction(memFillFunc.release()); + Super::run(module); + if (needsMemoryCopy) { Index dst = 0, src = 1, size = 2, temp = 3; Name memory = module->memories.front()->name; @@ -54,18 +57,18 @@ struct MemoryCopyFillLowering // temp = memory size in bytes body->list.push_back(b.makeLocalSet(temp, b.makeBinary(BinaryOp::MulInt32, - b.makeMemorySize(memory), + b.makeMemorySize(memory), b.makeConst(Memory::kPageSize)))); // if dst + size > memsize or src + size > memsize, then trap. body->list.push_back( b.makeIf( b.makeBinary(BinaryOp::OrInt32, - b.makeBinary(BinaryOp::GtUInt32, - b.makeBinary(BinaryOp::AddInt32, + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), b.makeLocalGet(temp, Type::i32)), - b.makeBinary(BinaryOp::GtUInt32, - b.makeBinary(BinaryOp::AddInt32, + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(src, Type::i32), b.makeLocalGet(size, Type::i32)), b.makeLocalGet(temp, Type::i32))), b.makeUnreachable()) @@ -84,25 +87,24 @@ struct MemoryCopyFillLowering b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(src, Type::i32), b.makeLocalGet(size, Type::i32)))); - // loop ( - body->list.push_back( + body->list.push_back(b.makeBlock("out", b.makeLoop("copy", b.makeBlock({ - // if dst < temp, then break - b.makeBreak("copy", nullptr, - b.makeBinary(BinaryOp::LtUInt32, + // break if dst == temp + b.makeBreak("out", nullptr, + b.makeBinary(BinaryOp::EqInt32, b.makeLocalGet(dst, Type::i32), b.makeLocalGet(temp, Type::i32))), - // *dst = *src - b.makeStore(1, 0, 1, b.makeLocalGet(dst, Type::i32), - b.makeLoad(1, false, 0, 1, - b.makeLocalGet(src, Type::i32), Type::i32, memory), Type::i32, memory), // --dst; --src; b.makeLocalSet(dst, b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(dst, Type::i32), b.makeConst(1))), b.makeLocalSet(src, - b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(src, Type::i32), b.makeConst(1))) + b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(src, Type::i32), b.makeConst(1))), + // *dst = *src + b.makeStore(1, 0, 1, b.makeLocalGet(dst, Type::i32), + b.makeLoad(1, false, 0, 1, + b.makeLocalGet(src, Type::i32), Type::i32, memory), Type::i32, memory) })) - ); + )); module->getFunction("__memory_copy")->body = body; /* @@ -117,7 +119,7 @@ struct MemoryCopyFillLowering local.set($dst, i32.add(local.get $dst, local.get $size)) local.set($src, i32.add(local.get $src, local.get $size)) loop ( - br_if (i32.ult(local.get $dst, local.get $temp) + br_if (i32.eq(local.get $dst, local.get $temp) i32.store8(local.get $dst, i32.load8_u(get_local $src)) local.set($dst, i32.sub(local.get($dst), i32.const 1)) local.set($src, i32.sub(local.get($src), i32.const 1)) @@ -126,6 +128,51 @@ struct MemoryCopyFillLowering } else { module->removeFunction("__memory_copy"); } + + if (needsMemoryFill) { + Index dst = 0, val = 1, size = 2; + Name memory = module->memories.front()->name; + Block* body = b.makeBlock(); + + // if dst + size > memsize in bytes, then trap. + body->list.push_back( + b.makeIf( + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), + b.makeBinary(BinaryOp::MulInt32, + b.makeMemorySize(memory), + b.makeConst(Memory::kPageSize))), + b.makeUnreachable())); + + body->list.push_back(b.makeBlock("out", b.makeLoop("copy", b.makeBlock({ + // break if size == 0 + b.makeBreak("out", nullptr, b.makeLocalGet(size, Type::i32)), + // size-- + b.makeLocalSet(size, + b.makeBinary(BinaryOp::SubInt32, + b.makeLocalGet(size, Type::i32), b.makeConst(1))), + // *(dst+size) = val + b.makeStore(1, 0, 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(val, Type::i32), + Type::i32, memory) + }))) + ); + module->getFunction("__memory_fill")->body = body; + /* + if ( + i32.ugt(i32.add(local.get $dst, local.get $size))), i32.mul(memory.size 0, i32.const 65536)) + ) then unreachable + loop ( + local.set($size, i32.sub((local.get $size), (i32.const 1)) + br_if (local.get $size)) + i32.store8((i32.add((local.get $dst), (local.get $size), (local.get $val)) ) + */ + } else { + module->removeFunction("__memory_fill"); + } module->features.disable(FeatureSet::BulkMemory); } }; diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 00ac9814258..908b381e84f 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -230,11 +230,6 @@ ;; CHECK-NEXT: --limit-segments attempt to merge segments to fit ;; CHECK-NEXT: within web limits ;; CHECK-NEXT: -;; CHECK-NEXT: --llvm-nontrapping-fptoint-lowering lower nontrapping float-to-int -;; CHECK-NEXT: operations to wasm mvp and -;; CHECK-NEXT: disable the nontrapping fptoint -;; CHECK-NEXT: feature -;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 2db92e1cea3..7d173b08421 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -239,11 +239,6 @@ ;; CHECK-NEXT: --limit-segments attempt to merge segments to fit ;; CHECK-NEXT: within web limits ;; CHECK-NEXT: -;; CHECK-NEXT: --llvm-nontrapping-fptoint-lowering lower nontrapping float-to-int -;; CHECK-NEXT: operations to wasm mvp and -;; CHECK-NEXT: disable the nontrapping fptoint -;; CHECK-NEXT: feature -;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 1e7a97bb43e..514f638d912 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -193,11 +193,6 @@ ;; CHECK-NEXT: --limit-segments attempt to merge segments to fit ;; CHECK-NEXT: within web limits ;; CHECK-NEXT: -;; CHECK-NEXT: --llvm-nontrapping-fptoint-lowering lower nontrapping float-to-int -;; CHECK-NEXT: operations to wasm mvp and -;; CHECK-NEXT: disable the nontrapping fptoint -;; CHECK-NEXT: feature -;; CHECK-NEXT: ;; CHECK-NEXT: --local-cse common subexpression elimination ;; CHECK-NEXT: inside basic blocks ;; CHECK-NEXT: diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast new file mode 100644 index 00000000000..33c444da3af --- /dev/null +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -0,0 +1,142 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt --enable-bulk-memory %s --memory-copy-fill-lowering -S -o - | filecheck %s + +(module + (memory 0) + ;; CHECK: (type $0 (func (param i32 i32 i32))) + + ;; CHECK: (memory $0 0) + + ;; CHECK: (func $memcpy (param $dst i32) (param $src i32) (param $size i32) + ;; CHECK-NEXT: (call $__memory_copy + ;; CHECK-NEXT: (local.get $dst) + ;; CHECK-NEXT: (local.get $src) + ;; CHECK-NEXT: (local.get $size) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $memcpy (param $dst i32) (param $src i32) (param $size i32) + (memory.copy (local.get $dst) (local.get $src) (local.get $size)) + ) + ;; CHECK: (func $memfill (param $dst i32) (param $val i32) (param $size i32) + ;; CHECK-NEXT: (call $__memory_fill + ;; CHECK-NEXT: (local.get $dst) + ;; CHECK-NEXT: (local.get $val) + ;; CHECK-NEXT: (local.get $size) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $memfill (param $dst i32) (param $val i32) (param $size i32) + (memory.fill (local.get $dst) (local.get $val) (local.get $size)) + ) +) +;; CHECK: (func $__memory_copy (param $0 i32) (param $1 i32) (param $2 i32) +;; CHECK-NEXT: (local $3 i32) +;; CHECK-NEXT: (local.set $3 +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (memory.size) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.or +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $3) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $3) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $3 +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $0 +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $1 +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $out +;; CHECK-NEXT: (loop $copy +;; CHECK-NEXT: (br_if $out +;; CHECK-NEXT: (i32.eq +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (local.get $3) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $0 +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $1 +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.store8 +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (i32.load8_u +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $__memory_fill (param $0 i32) (param $1 i32) (param $2 i32) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (memory.size) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $out +;; CHECK-NEXT: (loop $copy +;; CHECK-NEXT: (br_if $out +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $2 +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.store8 +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) From c373c133402d63356e6f5b1f999ebd6fa215fae9 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Mon, 21 Oct 2024 10:04:23 -0700 Subject: [PATCH 05/17] copy in both directions, add var names --- src/passes/MemoryCopyFillLowering.cpp | 233 ++++++++++-------- src/passes/pass.cpp | 6 +- .../lit/passes/memory-copy-fill-lowering.wast | 108 ++++---- 3 files changed, 198 insertions(+), 149 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index 43732b26497..a59aba6e76e 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -17,16 +17,14 @@ struct MemoryCopyFillLowering } Builder builder(*getModule()); replaceCurrent(builder.makeCall( - "__memory_copy", {curr->dest, curr->source, curr->size}, - Type::none)); + "__memory_copy", {curr->dest, curr->source, curr->size}, Type::none)); needsMemoryCopy = true; } void visitMemoryFill(MemoryFill* curr) { Builder builder(*getModule()); replaceCurrent(builder.makeCall( - "__memory_fill", {curr->dest, curr->value, curr->size}, Type::none) - ); + "__memory_fill", {curr->dest, curr->value, curr->size}, Type::none)); needsMemoryFill = true; } @@ -35,78 +33,100 @@ struct MemoryCopyFillLowering return; } if (module->features.hasMemory64() || module->features.hasMultiMemory()) { - throw "Memory64 and multi-memory not supported";// TODO: best way to return an error? + throw "Memory64 and multi-memory not supported"; // TODO: best way to + // return an error? } - // In order to introduce a call to a function, it must first exist, so create an empty stub. + // In order to introduce a call to a function, it must first exist, so + // create an empty stub. Builder b(*module); - auto memCpyFunc = b.makeFunction("__memory_copy", - Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), {Type::i32}); + auto memCpyFunc = + b.makeFunction("__memory_copy", + {{"dst", Type::i32}, {"src", Type::i32}, {"size", Type::i32}}, + Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), + {{"start", Type::i32}, {"end", Type::i32}, {"step", Type::i32}, {"i", Type::i32}}); memCpyFunc->body = b.makeBlock(); module->addFunction(memCpyFunc.release()); - auto memFillFunc = b.makeFunction("__memory_fill", - Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), {}); + auto memFillFunc = + b.makeFunction("__memory_fill", + {{"dst", Type::i32}, {"val", Type::i32}, {"size", Type::i32}}, + Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), + {}); memFillFunc->body = b.makeBlock(); module->addFunction(memFillFunc.release()); Super::run(module); if (needsMemoryCopy) { - Index dst = 0, src = 1, size = 2, temp = 3; + Index dst = 0, src = 1, size = 2, start = 3, end = 4, step = 5, i = 6; Name memory = module->memories.front()->name; Block* body = b.makeBlock(); - // temp = memory size in bytes - body->list.push_back(b.makeLocalSet(temp, - b.makeBinary(BinaryOp::MulInt32, - b.makeMemorySize(memory), - b.makeConst(Memory::kPageSize)))); - // if dst + size > memsize or src + size > memsize, then trap. - body->list.push_back( - b.makeIf( - b.makeBinary(BinaryOp::OrInt32, - b.makeBinary(BinaryOp::GtUInt32, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), - b.makeLocalGet(temp, Type::i32)), - b.makeBinary(BinaryOp::GtUInt32, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(src, Type::i32), b.makeLocalGet(size, Type::i32)), - b.makeLocalGet(temp, Type::i32))), - b.makeUnreachable()) - ); - // temp = dst - body->list.push_back(b.makeLocalSet(temp, b.makeLocalGet(dst, Type::i32))); - // dst = dst + size - body->list.push_back( - b.makeLocalSet(dst, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), - b.makeLocalGet(size, Type::i32)))); - // src = src + size + // end = memory size in bytes body->list.push_back( - b.makeLocalSet(src, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(src, Type::i32), - b.makeLocalGet(size, Type::i32)))); - body->list.push_back(b.makeBlock("out", - b.makeLoop("copy", b.makeBlock({ - // break if dst == temp - b.makeBreak("out", nullptr, - b.makeBinary(BinaryOp::EqInt32, - b.makeLocalGet(dst, Type::i32), - b.makeLocalGet(temp, Type::i32))), - // --dst; --src; - b.makeLocalSet(dst, - b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(dst, Type::i32), b.makeConst(1))), - b.makeLocalSet(src, - b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(src, Type::i32), b.makeConst(1))), - // *dst = *src - b.makeStore(1, 0, 1, b.makeLocalGet(dst, Type::i32), - b.makeLoad(1, false, 0, 1, - b.makeLocalGet(src, Type::i32), Type::i32, memory), Type::i32, memory) + b.makeLocalSet(end, + b.makeBinary(BinaryOp::MulInt32, + b.makeMemorySize(memory), + b.makeConst(Memory::kPageSize)))); + // if dst + size > memsize or src + size > memsize, then trap. + body->list.push_back(b.makeIf( + b.makeBinary(BinaryOp::OrInt32, + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(end, Type::i32)), + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(end, Type::i32))), + b.makeUnreachable())); + // if src < dest + body->list.push_back(b.makeIf( + b.makeBinary(BinaryOp::LtUInt32, + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(dst, Type::i32)), + b.makeBlock({ + b.makeLocalSet(start, b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(size, Type::i32), b.makeConst(-1U))), + b.makeLocalSet(end, b.makeConst(-1U)), + b.makeLocalSet(step, b.makeConst(-1U)), + }), + b.makeBlock({ + b.makeLocalSet(start, b.makeConst(0)), + b.makeLocalSet(end, b.makeLocalGet(size, Type::i32)), + b.makeLocalSet(step, b.makeConst(1)), })) - )); + ); + // i = start + body->list.push_back(b.makeLocalSet(i, b.makeLocalGet(start, Type::i32))); + body->list.push_back(b.makeBlock( + "out", + b.makeLoop( + "copy", + b.makeBlock( + {// break if i == end + b.makeBreak("out", + nullptr, + b.makeBinary(BinaryOp::EqInt32, + b.makeLocalGet(i, Type::i32), + b.makeLocalGet(end, Type::i32))), + // dst[i] = src[i] + b.makeStore(1, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), b.makeLocalGet(i, Type::i32)), + b.makeLoad(1, + false, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(src, Type::i32), b.makeLocalGet(i, Type::i32)), + Type::i32, + memory), + Type::i32, + memory)})))); module->getFunction("__memory_copy")->body = body; - + /* local.set($temp, i32.mul(memory.size 0, i32.const 65536)) if ( @@ -115,14 +135,20 @@ struct MemoryCopyFillLowering i32.ugt(i32.add(local.get $dst, local.get $size), local.get $temp), ) ) then unreachable - local.set($temp, local.get $dst) - local.set($dst, i32.add(local.get $dst, local.get $size)) - local.set($src, i32.add(local.get $src, local.get $size)) + if (i32.lt(local.get $src), (local.get $dst))) + then ( + local.set($start, i32.sub(local.get $size, i32.const 1) + local.set($end, (i32.const -1)) + local.set($step, i32.const -1) + ) else ( + local.set($start, i32.const 0) + local.set($end, local.get $size) + local.set($step, i32.const 1) loop ( - br_if (i32.eq(local.get $dst, local.get $temp) - i32.store8(local.get $dst, i32.load8_u(get_local $src)) - local.set($dst, i32.sub(local.get($dst), i32.const 1)) - local.set($src, i32.sub(local.get($src), i32.const 1)) + br_if (i32.eq(local.get $end, local.get $i) + i32.store8(i32.add((local.get $dst), (local.get $i)), + i32.load8_u(i32.add((local.get $i) (local.get $src))) + local.set($dst, i32.add(local.get $i, (local.get $step))) ) */ } else { @@ -136,40 +162,47 @@ struct MemoryCopyFillLowering // if dst + size > memsize in bytes, then trap. body->list.push_back( - b.makeIf( - b.makeBinary(BinaryOp::GtUInt32, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), - b.makeBinary(BinaryOp::MulInt32, - b.makeMemorySize(memory), - b.makeConst(Memory::kPageSize))), - b.makeUnreachable())); + b.makeIf(b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeBinary(BinaryOp::MulInt32, + b.makeMemorySize(memory), + b.makeConst(Memory::kPageSize))), + b.makeUnreachable())); - body->list.push_back(b.makeBlock("out", b.makeLoop("copy", b.makeBlock({ - // break if size == 0 - b.makeBreak("out", nullptr, b.makeLocalGet(size, Type::i32)), - // size-- - b.makeLocalSet(size, - b.makeBinary(BinaryOp::SubInt32, - b.makeLocalGet(size, Type::i32), b.makeConst(1))), - // *(dst+size) = val - b.makeStore(1, 0, 1, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), - b.makeLocalGet(val, Type::i32), - Type::i32, memory) - }))) - ); + body->list.push_back(b.makeBlock( + "out", + b.makeLoop( + "copy", + b.makeBlock( + {// break if size == 0 + b.makeBreak("out", nullptr, b.makeLocalGet(size, Type::i32)), + // size-- + b.makeLocalSet(size, + b.makeBinary(BinaryOp::SubInt32, + b.makeLocalGet(size, Type::i32), + b.makeConst(1))), + // *(dst+size) = val + b.makeStore(1, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(val, Type::i32), + Type::i32, + memory)})))); module->getFunction("__memory_fill")->body = body; - /* - if ( - i32.ugt(i32.add(local.get $dst, local.get $size))), i32.mul(memory.size 0, i32.const 65536)) - ) then unreachable - loop ( - local.set($size, i32.sub((local.get $size), (i32.const 1)) - br_if (local.get $size)) - i32.store8((i32.add((local.get $dst), (local.get $size), (local.get $val)) ) - */ + /* + if ( + i32.ugt(i32.add(local.get $dst, local.get $size))),i32.mul(memory.size 0, i32.const 65536)) + ) then unreachable + loop ( + local.set($size, i32.sub((local.get $size), (i32.const 1)) br_if (local.get $size)) + i32.store8((i32.add((local.get $dst), (local.get $size), (local.get$ val)) + ) + */ } else { module->removeFunction("__memory_fill"); } diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 4987dd988fe..fcefb89ad49 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -273,9 +273,9 @@ void PassRegistry::registerPasses() { "lower 64-bit tables 32-bit ones", createTable64LoweringPass); registerPass("memory-copy-fill-lowering", - "Lower memory.copy and memory.fill to wasm mvp and disable " - "the bulk-memory feature.", - createMemoryCopyFillLoweringPass); + "Lower memory.copy and memory.fill to wasm mvp and disable " + "the bulk-memory feature.", + createMemoryCopyFillLoweringPass); registerPass("memory-packing", "packs memory into separate segments, skipping zeros", createMemoryPackingPass); diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast index 33c444da3af..e9ce7784f2d 100644 --- a/test/lit/passes/memory-copy-fill-lowering.wast +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -29,9 +29,12 @@ (memory.fill (local.get $dst) (local.get $val) (local.get $size)) ) ) -;; CHECK: (func $__memory_copy (param $0 i32) (param $1 i32) (param $2 i32) -;; CHECK-NEXT: (local $3 i32) -;; CHECK-NEXT: (local.set $3 +;; CHECK: (func $__memory_copy (param $dst i32) (param $src i32) (param $size i32) +;; CHECK-NEXT: (local $start i32) +;; CHECK-NEXT: (local $end i32) +;; CHECK-NEXT: (local $step i32) +;; CHECK-NEXT: (local $i i32) +;; CHECK-NEXT: (local.set $end ;; CHECK-NEXT: (i32.mul ;; CHECK-NEXT: (memory.size) ;; CHECK-NEXT: (i32.const 65536) @@ -41,74 +44,87 @@ ;; CHECK-NEXT: (i32.or ;; CHECK-NEXT: (i32.gt_u ;; CHECK-NEXT: (i32.add -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $size) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.get $3) +;; CHECK-NEXT: (local.get $end) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.gt_u ;; CHECK-NEXT: (i32.add -;; CHECK-NEXT: (local.get $1) -;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: (local.get $src) +;; CHECK-NEXT: (local.get $size) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.get $3) +;; CHECK-NEXT: (local.get $end) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (then ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $3 -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $0 -;; CHECK-NEXT: (i32.add -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.lt_u +;; CHECK-NEXT: (local.get $src) +;; CHECK-NEXT: (local.get $dst) ;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $1 -;; CHECK-NEXT: (i32.add -;; CHECK-NEXT: (local.get $1) -;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (local.set $start +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: (i32.const -1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $end +;; CHECK-NEXT: (i32.const -1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $step +;; CHECK-NEXT: (i32.const -1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (else +;; CHECK-NEXT: (local.set $start +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $end +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $step +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $i +;; CHECK-NEXT: (local.get $start) +;; CHECK-NEXT: ) ;; CHECK-NEXT: (block $out ;; CHECK-NEXT: (loop $copy ;; CHECK-NEXT: (br_if $out ;; CHECK-NEXT: (i32.eq -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: (local.get $3) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $0 -;; CHECK-NEXT: (i32.sub -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: (i32.const 1) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $1 -;; CHECK-NEXT: (i32.sub -;; CHECK-NEXT: (local.get $1) -;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: (local.get $end) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.store8 -;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.load8_u -;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $src) +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) -;; CHECK: (func $__memory_fill (param $0 i32) (param $1 i32) (param $2 i32) +;; CHECK: (func $__memory_fill (param $dst i32) (param $val i32) (param $size i32) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (i32.gt_u ;; CHECK-NEXT: (i32.add -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $size) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.mul ;; CHECK-NEXT: (memory.size) @@ -122,20 +138,20 @@ ;; CHECK-NEXT: (block $out ;; CHECK-NEXT: (loop $copy ;; CHECK-NEXT: (br_if $out -;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: (local.get $size) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $2 +;; CHECK-NEXT: (local.set $size ;; CHECK-NEXT: (i32.sub -;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: (local.get $size) ;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (i32.store8 ;; CHECK-NEXT: (i32.add -;; CHECK-NEXT: (local.get $0) -;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $size) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From 46aaa06a786161bfea8ad5e861074faadb122b1f Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Wed, 30 Oct 2024 14:35:26 -0700 Subject: [PATCH 06/17] Fix bugs --- src/passes/MemoryCopyFillLowering.cpp | 27 +++++++++--- .../lit/passes/memory-copy-fill-lowering.wast | 43 +++---------------- 2 files changed, 26 insertions(+), 44 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index a59aba6e76e..ad7dde50e68 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -12,9 +12,8 @@ struct MemoryCopyFillLowering bool needsMemoryFill = false; void visitMemoryCopy(MemoryCopy* curr) { - if (curr->destMemory != curr->sourceMemory) { - return; // Throw an error here instead of silently ignoring? - } + assert(curr->destMemory == + curr->sourceMemory); // multi-memory not supported. Builder builder(*getModule()); replaceCurrent(builder.makeCall( "__memory_copy", {curr->dest, curr->source, curr->size}, Type::none)); @@ -32,6 +31,9 @@ struct MemoryCopyFillLowering if (!module->features.hasBulkMemory()) { return; } + if (module->features.hasAtomics()) { + throw "Don't use this pass with atomics"; // alternatively... just don't unset bulkmem feature if atomics? or do nothing? + } if (module->features.hasMemory64() || module->features.hasMultiMemory()) { throw "Memory64 and multi-memory not supported"; // TODO: best way to // return an error? @@ -86,7 +88,7 @@ struct MemoryCopyFillLowering b.makeLocalGet(src, Type::i32), b.makeLocalGet(dst, Type::i32)), b.makeBlock({ - b.makeLocalSet(start, b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(size, Type::i32), b.makeConst(-1U))), + b.makeLocalSet(start, b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(size, Type::i32), b.makeConst(1))), b.makeLocalSet(end, b.makeConst(-1U)), b.makeLocalSet(step, b.makeConst(-1U)), }), @@ -124,7 +126,14 @@ struct MemoryCopyFillLowering Type::i32, memory), Type::i32, - memory)})))); + memory), + // i += step + b.makeLocalSet(i, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(i, Type::i32), + b.makeLocalGet(step, Type::i32))), + // loop + b.makeBreak("copy", nullptr)})))); module->getFunction("__memory_copy")->body = body; /* @@ -177,7 +186,10 @@ struct MemoryCopyFillLowering "copy", b.makeBlock( {// break if size == 0 - b.makeBreak("out", nullptr, b.makeLocalGet(size, Type::i32)), + b.makeBreak( + "out", + nullptr, + b.makeUnary(UnaryOp::EqZInt32, b.makeLocalGet(size, Type::i32))), // size-- b.makeLocalSet(size, b.makeBinary(BinaryOp::SubInt32, @@ -192,7 +204,8 @@ struct MemoryCopyFillLowering b.makeLocalGet(size, Type::i32)), b.makeLocalGet(val, Type::i32), Type::i32, - memory)})))); + memory), + b.makeBreak("copy", nullptr)})))); module->getFunction("__memory_fill")->body = body; /* if ( diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast index e9ce7784f2d..43e2538cdae 100644 --- a/test/lit/passes/memory-copy-fill-lowering.wast +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -19,7 +19,7 @@ (memory.copy (local.get $dst) (local.get $src) (local.get $size)) ) ;; CHECK: (func $memfill (param $dst i32) (param $val i32) (param $size i32) - ;; CHECK-NEXT: (call $__memory_fill + ;; CHECK-NEXT: (memory.fill ;; CHECK-NEXT: (local.get $dst) ;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: (local.get $size) @@ -70,7 +70,7 @@ ;; CHECK-NEXT: (local.set $start ;; CHECK-NEXT: (i32.sub ;; CHECK-NEXT: (local.get $size) -;; CHECK-NEXT: (i32.const -1) +;; CHECK-NEXT: (i32.const 1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (local.set $end @@ -115,44 +115,13 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) - -;; CHECK: (func $__memory_fill (param $dst i32) (param $val i32) (param $size i32) -;; CHECK-NEXT: (if -;; CHECK-NEXT: (i32.gt_u -;; CHECK-NEXT: (i32.add -;; CHECK-NEXT: (local.get $dst) -;; CHECK-NEXT: (local.get $size) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (i32.mul -;; CHECK-NEXT: (memory.size) -;; CHECK-NEXT: (i32.const 65536) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (then -;; CHECK-NEXT: (unreachable) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (block $out -;; CHECK-NEXT: (loop $copy -;; CHECK-NEXT: (br_if $out -;; CHECK-NEXT: (local.get $size) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.set $size -;; CHECK-NEXT: (i32.sub -;; CHECK-NEXT: (local.get $size) -;; CHECK-NEXT: (i32.const 1) -;; CHECK-NEXT: ) -;; CHECK-NEXT: ) -;; CHECK-NEXT: (i32.store8 +;; CHECK-NEXT: (local.set $i ;; CHECK-NEXT: (i32.add -;; CHECK-NEXT: (local.get $dst) -;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: (local.get $step) ;; CHECK-NEXT: ) -;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: ) +;; CHECK-NEXT: (br $copy) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) From 8c7487496322e9bec1b6c85fafa260eebdaef212 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 7 Nov 2024 14:07:40 -0800 Subject: [PATCH 07/17] use Fatal instead of throw; error on other bulk-memory instructions; update test --- src/passes/MemoryCopyFillLowering.cpp | 29 +++++++++++-- .../lit/passes/memory-copy-fill-lowering.wast | 43 ++++++++++++++++++- 2 files changed, 68 insertions(+), 4 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index ad7dde50e68..77dc39b1a24 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -32,12 +32,26 @@ struct MemoryCopyFillLowering return; } if (module->features.hasAtomics()) { - throw "Don't use this pass with atomics"; // alternatively... just don't unset bulkmem feature if atomics? or do nothing? + return; } if (module->features.hasMemory64() || module->features.hasMultiMemory()) { - throw "Memory64 and multi-memory not supported"; // TODO: best way to - // return an error? + Fatal() << "Memory64 and multi-memory not supported by memory.copy lowering"; + } + + // Check for the presence of any passive data or table segments. + for (auto& segment: module->dataSegments) { + if (segment->isPassive) { + Fatal() << "memory.copy lowering should only be run on modules with " + "no passive segments"; + } + } + for (auto& segment: module->elementSegments) { + if (!segment->table.is()) { + Fatal() << "memory.copy lowering should only be run on modules with" + " no passive segments"; + } } + // In order to introduce a call to a function, it must first exist, so // create an empty stub. Builder b(*module); @@ -221,6 +235,15 @@ struct MemoryCopyFillLowering } module->features.disable(FeatureSet::BulkMemory); } + + void VisitTableCopy(TableCopy* curr) { + Fatal() << "table.copy instruction found. Memory copy lowering is not " + "designed to work on modules with bulk table operations"; + } + void VisitTableFill(TableCopy* curr) { + Fatal() << "table.fill instruction found. Memory copy lowering is not " + "designed to work on modules with bulk table operations"; + } }; Pass* createMemoryCopyFillLoweringPass() { diff --git a/test/lit/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast index 43e2538cdae..540237ff7b5 100644 --- a/test/lit/passes/memory-copy-fill-lowering.wast +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -19,7 +19,7 @@ (memory.copy (local.get $dst) (local.get $src) (local.get $size)) ) ;; CHECK: (func $memfill (param $dst i32) (param $val i32) (param $size i32) - ;; CHECK-NEXT: (memory.fill + ;; CHECK-NEXT: (call $__memory_fill ;; CHECK-NEXT: (local.get $dst) ;; CHECK-NEXT: (local.get $val) ;; CHECK-NEXT: (local.get $size) @@ -125,3 +125,44 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) + +;; CHECK: (func $__memory_fill (param $dst i32) (param $val i32) (param $size i32) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (memory.size) +;; CHECK-NEXT: (i32.const 65536) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $out +;; CHECK-NEXT: (loop $copy +;; CHECK-NEXT: (br_if $out +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $size +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.store8 +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $val) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (br $copy) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) From d0f3c8725ecc8a83f8333011da30583f453b1dd2 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 7 Nov 2024 14:24:07 -0800 Subject: [PATCH 08/17] review --- src/passes/MemoryCopyFillLowering.cpp | 299 +++++++++++++------------- 1 file changed, 144 insertions(+), 155 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index 77dc39b1a24..23c0e74bbee 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -1,3 +1,19 @@ +/* + * Copyright 2024 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 "pass.h" #include "wasm-builder.h" #include "wasm.h" @@ -31,9 +47,6 @@ struct MemoryCopyFillLowering if (!module->features.hasBulkMemory()) { return; } - if (module->features.hasAtomics()) { - return; - } if (module->features.hasMemory64() || module->features.hasMultiMemory()) { Fatal() << "Memory64 and multi-memory not supported by memory.copy lowering"; } @@ -73,169 +86,145 @@ struct MemoryCopyFillLowering Super::run(module); if (needsMemoryCopy) { - Index dst = 0, src = 1, size = 2, start = 3, end = 4, step = 5, i = 6; - Name memory = module->memories.front()->name; - Block* body = b.makeBlock(); - // end = memory size in bytes - body->list.push_back( - b.makeLocalSet(end, - b.makeBinary(BinaryOp::MulInt32, - b.makeMemorySize(memory), - b.makeConst(Memory::kPageSize)))); - // if dst + size > memsize or src + size > memsize, then trap. - body->list.push_back(b.makeIf( - b.makeBinary(BinaryOp::OrInt32, - b.makeBinary(BinaryOp::GtUInt32, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), - b.makeLocalGet(size, Type::i32)), - b.makeLocalGet(end, Type::i32)), - b.makeBinary(BinaryOp::GtUInt32, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(src, Type::i32), - b.makeLocalGet(size, Type::i32)), - b.makeLocalGet(end, Type::i32))), - b.makeUnreachable())); - // if src < dest - body->list.push_back(b.makeIf( - b.makeBinary(BinaryOp::LtUInt32, - b.makeLocalGet(src, Type::i32), - b.makeLocalGet(dst, Type::i32)), - b.makeBlock({ - b.makeLocalSet(start, b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(size, Type::i32), b.makeConst(1))), - b.makeLocalSet(end, b.makeConst(-1U)), - b.makeLocalSet(step, b.makeConst(-1U)), - }), - b.makeBlock({ - b.makeLocalSet(start, b.makeConst(0)), - b.makeLocalSet(end, b.makeLocalGet(size, Type::i32)), - b.makeLocalSet(step, b.makeConst(1)), - })) - ); - // i = start - body->list.push_back(b.makeLocalSet(i, b.makeLocalGet(start, Type::i32))); - body->list.push_back(b.makeBlock( - "out", - b.makeLoop( - "copy", - b.makeBlock( - {// break if i == end - b.makeBreak("out", - nullptr, - b.makeBinary(BinaryOp::EqInt32, - b.makeLocalGet(i, Type::i32), - b.makeLocalGet(end, Type::i32))), - // dst[i] = src[i] - b.makeStore(1, - 0, - 1, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), b.makeLocalGet(i, Type::i32)), - b.makeLoad(1, - false, - 0, - 1, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(src, Type::i32), b.makeLocalGet(i, Type::i32)), - Type::i32, - memory), - Type::i32, - memory), - // i += step - b.makeLocalSet(i, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(i, Type::i32), - b.makeLocalGet(step, Type::i32))), - // loop - b.makeBreak("copy", nullptr)})))); - module->getFunction("__memory_copy")->body = body; - - /* - local.set($temp, i32.mul(memory.size 0, i32.const 65536)) - if ( - i32.or( - i32.ugt(i32.add(local.get $src, local.get $size), local.get $temp), - i32.ugt(i32.add(local.get $dst, local.get $size), local.get $temp), - ) - ) then unreachable - if (i32.lt(local.get $src), (local.get $dst))) - then ( - local.set($start, i32.sub(local.get $size, i32.const 1) - local.set($end, (i32.const -1)) - local.set($step, i32.const -1) - ) else ( - local.set($start, i32.const 0) - local.set($end, local.get $size) - local.set($step, i32.const 1) - loop ( - br_if (i32.eq(local.get $end, local.get $i) - i32.store8(i32.add((local.get $dst), (local.get $i)), - i32.load8_u(i32.add((local.get $i) (local.get $src))) - local.set($dst, i32.add(local.get $i, (local.get $step))) - ) - */ + CreateMemoryCopyFunc(module); } else { module->removeFunction("__memory_copy"); } if (needsMemoryFill) { - Index dst = 0, val = 1, size = 2; - Name memory = module->memories.front()->name; - Block* body = b.makeBlock(); - - // if dst + size > memsize in bytes, then trap. - body->list.push_back( - b.makeIf(b.makeBinary(BinaryOp::GtUInt32, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), - b.makeLocalGet(size, Type::i32)), - b.makeBinary(BinaryOp::MulInt32, - b.makeMemorySize(memory), - b.makeConst(Memory::kPageSize))), - b.makeUnreachable())); - - body->list.push_back(b.makeBlock( - "out", - b.makeLoop( - "copy", - b.makeBlock( - {// break if size == 0 - b.makeBreak( - "out", - nullptr, - b.makeUnary(UnaryOp::EqZInt32, b.makeLocalGet(size, Type::i32))), - // size-- - b.makeLocalSet(size, - b.makeBinary(BinaryOp::SubInt32, - b.makeLocalGet(size, Type::i32), - b.makeConst(1))), - // *(dst+size) = val - b.makeStore(1, - 0, - 1, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), - b.makeLocalGet(size, Type::i32)), - b.makeLocalGet(val, Type::i32), - Type::i32, - memory), - b.makeBreak("copy", nullptr)})))); - module->getFunction("__memory_fill")->body = body; - /* - if ( - i32.ugt(i32.add(local.get $dst, local.get $size))),i32.mul(memory.size 0, i32.const 65536)) - ) then unreachable - loop ( - local.set($size, i32.sub((local.get $size), (i32.const 1)) br_if (local.get $size)) - i32.store8((i32.add((local.get $dst), (local.get $size), (local.get$ val)) - ) - */ + CreateMemoryFillFunc(module); } else { module->removeFunction("__memory_fill"); } module->features.disable(FeatureSet::BulkMemory); } + void CreateMemoryCopyFunc(Module* module) { + Builder b(*module); + Index dst = 0, src = 1, size = 2, start = 3, end = 4, step = 5, i = 6; + Name memory = module->memories.front()->name; + Block* body = b.makeBlock(); + // end = memory size in bytes + body->list.push_back( + b.makeLocalSet(end, + b.makeBinary(BinaryOp::MulInt32, + b.makeMemorySize(memory), + b.makeConst(Memory::kPageSize)))); + // if dst + size > memsize or src + size > memsize, then trap. + body->list.push_back(b.makeIf( + b.makeBinary(BinaryOp::OrInt32, + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(end, Type::i32)), + b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(end, Type::i32))), + b.makeUnreachable())); + // if src < dest + body->list.push_back(b.makeIf( + b.makeBinary(BinaryOp::LtUInt32, + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(dst, Type::i32)), + b.makeBlock({ + b.makeLocalSet(start, b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(size, Type::i32), b.makeConst(1))), + b.makeLocalSet(end, b.makeConst(-1U)), + b.makeLocalSet(step, b.makeConst(-1U)), + }), + b.makeBlock({ + b.makeLocalSet(start, b.makeConst(0)), + b.makeLocalSet(end, b.makeLocalGet(size, Type::i32)), + b.makeLocalSet(step, b.makeConst(1)), + })) + ); + // i = start + body->list.push_back(b.makeLocalSet(i, b.makeLocalGet(start, Type::i32))); + body->list.push_back(b.makeBlock( + "out", + b.makeLoop( + "copy", + b.makeBlock( + {// break if i == end + b.makeBreak("out", + nullptr, + b.makeBinary(BinaryOp::EqInt32, + b.makeLocalGet(i, Type::i32), + b.makeLocalGet(end, Type::i32))), + // dst[i] = src[i] + b.makeStore(1, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), b.makeLocalGet(i, Type::i32)), + b.makeLoad(1, + false, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(src, Type::i32), b.makeLocalGet(i, Type::i32)), + Type::i32, + memory), + Type::i32, + memory), + // i += step + b.makeLocalSet(i, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(i, Type::i32), + b.makeLocalGet(step, Type::i32))), + // loop + b.makeBreak("copy", nullptr)})))); + module->getFunction("__memory_copy")->body = body; + } + + void CreateMemoryFillFunc(Module* module) { + Builder b(*module); + Index dst = 0, val = 1, size = 2; + Name memory = module->memories.front()->name; + Block* body = b.makeBlock(); + + // if dst + size > memsize in bytes, then trap. + body->list.push_back( + b.makeIf(b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeBinary(BinaryOp::MulInt32, + b.makeMemorySize(memory), + b.makeConst(Memory::kPageSize))), + b.makeUnreachable())); + + body->list.push_back(b.makeBlock( + "out", + b.makeLoop( + "copy", + b.makeBlock( + {// break if size == 0 + b.makeBreak( + "out", + nullptr, + b.makeUnary(UnaryOp::EqZInt32, b.makeLocalGet(size, Type::i32))), + // size-- + b.makeLocalSet(size, + b.makeBinary(BinaryOp::SubInt32, + b.makeLocalGet(size, Type::i32), + b.makeConst(1))), + // *(dst+size) = val + b.makeStore(1, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(val, Type::i32), + Type::i32, + memory), + b.makeBreak("copy", nullptr)})))); + module->getFunction("__memory_fill")->body = body; + } + void VisitTableCopy(TableCopy* curr) { Fatal() << "table.copy instruction found. Memory copy lowering is not " "designed to work on modules with bulk table operations"; From 914def0a1ff971242e9e0ec67f614de6674da193 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 7 Nov 2024 14:24:27 -0800 Subject: [PATCH 09/17] clang-format --- src/passes/MemoryCopyFillLowering.cpp | 170 ++++++++++++++------------ 1 file changed, 89 insertions(+), 81 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index 23c0e74bbee..ddbb07989f5 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -48,38 +48,42 @@ struct MemoryCopyFillLowering return; } if (module->features.hasMemory64() || module->features.hasMultiMemory()) { - Fatal() << "Memory64 and multi-memory not supported by memory.copy lowering"; + Fatal() + << "Memory64 and multi-memory not supported by memory.copy lowering"; } // Check for the presence of any passive data or table segments. - for (auto& segment: module->dataSegments) { + for (auto& segment : module->dataSegments) { if (segment->isPassive) { Fatal() << "memory.copy lowering should only be run on modules with " - "no passive segments"; + "no passive segments"; } } - for (auto& segment: module->elementSegments) { + for (auto& segment : module->elementSegments) { if (!segment->table.is()) { Fatal() << "memory.copy lowering should only be run on modules with" - " no passive segments"; + " no passive segments"; } } // In order to introduce a call to a function, it must first exist, so // create an empty stub. Builder b(*module); - auto memCpyFunc = - b.makeFunction("__memory_copy", - {{"dst", Type::i32}, {"src", Type::i32}, {"size", Type::i32}}, - Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), - {{"start", Type::i32}, {"end", Type::i32}, {"step", Type::i32}, {"i", Type::i32}}); + auto memCpyFunc = b.makeFunction( + "__memory_copy", + {{"dst", Type::i32}, {"src", Type::i32}, {"size", Type::i32}}, + Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), + {{"start", Type::i32}, + {"end", Type::i32}, + {"step", Type::i32}, + {"i", Type::i32}}); memCpyFunc->body = b.makeBlock(); module->addFunction(memCpyFunc.release()); - auto memFillFunc = - b.makeFunction("__memory_fill", - {{"dst", Type::i32}, {"val", Type::i32}, {"size", Type::i32}}, - Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), - {}); + auto memFillFunc = b.makeFunction( + "__memory_fill", + {{"dst", Type::i32}, {"val", Type::i32}, {"size", Type::i32}}, + Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), + {}); memFillFunc->body = b.makeBlock(); module->addFunction(memFillFunc.release()); @@ -107,39 +111,41 @@ struct MemoryCopyFillLowering // end = memory size in bytes body->list.push_back( b.makeLocalSet(end, - b.makeBinary(BinaryOp::MulInt32, + b.makeBinary(BinaryOp::MulInt32, b.makeMemorySize(memory), b.makeConst(Memory::kPageSize)))); // if dst + size > memsize or src + size > memsize, then trap. body->list.push_back(b.makeIf( b.makeBinary(BinaryOp::OrInt32, - b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::GtUInt32, b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), - b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), b.makeLocalGet(end, Type::i32)), - b.makeBinary(BinaryOp::GtUInt32, + b.makeBinary(BinaryOp::GtUInt32, b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(src, Type::i32), - b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(size, Type::i32)), b.makeLocalGet(end, Type::i32))), b.makeUnreachable())); // if src < dest - body->list.push_back(b.makeIf( - b.makeBinary(BinaryOp::LtUInt32, - b.makeLocalGet(src, Type::i32), - b.makeLocalGet(dst, Type::i32)), - b.makeBlock({ - b.makeLocalSet(start, b.makeBinary(BinaryOp::SubInt32, b.makeLocalGet(size, Type::i32), b.makeConst(1))), - b.makeLocalSet(end, b.makeConst(-1U)), - b.makeLocalSet(step, b.makeConst(-1U)), - }), - b.makeBlock({ - b.makeLocalSet(start, b.makeConst(0)), - b.makeLocalSet(end, b.makeLocalGet(size, Type::i32)), - b.makeLocalSet(step, b.makeConst(1)), - })) - ); + body->list.push_back( + b.makeIf(b.makeBinary(BinaryOp::LtUInt32, + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(dst, Type::i32)), + b.makeBlock({ + b.makeLocalSet(start, + b.makeBinary(BinaryOp::SubInt32, + b.makeLocalGet(size, Type::i32), + b.makeConst(1))), + b.makeLocalSet(end, b.makeConst(-1U)), + b.makeLocalSet(step, b.makeConst(-1U)), + }), + b.makeBlock({ + b.makeLocalSet(start, b.makeConst(0)), + b.makeLocalSet(end, b.makeLocalGet(size, Type::i32)), + b.makeLocalSet(step, b.makeConst(1)), + }))); // i = start body->list.push_back(b.makeLocalSet(i, b.makeLocalGet(start, Type::i32))); body->list.push_back(b.makeBlock( @@ -148,34 +154,36 @@ struct MemoryCopyFillLowering "copy", b.makeBlock( {// break if i == end - b.makeBreak("out", - nullptr, - b.makeBinary(BinaryOp::EqInt32, + b.makeBreak("out", + nullptr, + b.makeBinary(BinaryOp::EqInt32, b.makeLocalGet(i, Type::i32), b.makeLocalGet(end, Type::i32))), - // dst[i] = src[i] - b.makeStore(1, - 0, - 1, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), b.makeLocalGet(i, Type::i32)), - b.makeLoad(1, + // dst[i] = src[i] + b.makeStore(1, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(i, Type::i32)), + b.makeLoad(1, false, 0, 1, b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(src, Type::i32), b.makeLocalGet(i, Type::i32)), + b.makeLocalGet(src, Type::i32), + b.makeLocalGet(i, Type::i32)), Type::i32, memory), - Type::i32, - memory), - // i += step - b.makeLocalSet(i, - b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(i, Type::i32), - b.makeLocalGet(step, Type::i32))), - // loop - b.makeBreak("copy", nullptr)})))); + Type::i32, + memory), + // i += step + b.makeLocalSet(i, + b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(i, Type::i32), + b.makeLocalGet(step, Type::i32))), + // loop + b.makeBreak("copy", nullptr)})))); module->getFunction("__memory_copy")->body = body; } @@ -189,12 +197,12 @@ struct MemoryCopyFillLowering body->list.push_back( b.makeIf(b.makeBinary(BinaryOp::GtUInt32, b.makeBinary(BinaryOp::AddInt32, - b.makeLocalGet(dst, Type::i32), - b.makeLocalGet(size, Type::i32)), + b.makeLocalGet(dst, Type::i32), + b.makeLocalGet(size, Type::i32)), b.makeBinary(BinaryOp::MulInt32, - b.makeMemorySize(memory), - b.makeConst(Memory::kPageSize))), - b.makeUnreachable())); + b.makeMemorySize(memory), + b.makeConst(Memory::kPageSize))), + b.makeUnreachable())); body->list.push_back(b.makeBlock( "out", @@ -202,36 +210,36 @@ struct MemoryCopyFillLowering "copy", b.makeBlock( {// break if size == 0 - b.makeBreak( - "out", - nullptr, - b.makeUnary(UnaryOp::EqZInt32, b.makeLocalGet(size, Type::i32))), - // size-- - b.makeLocalSet(size, + b.makeBreak( + "out", + nullptr, + b.makeUnary(UnaryOp::EqZInt32, b.makeLocalGet(size, Type::i32))), + // size-- + b.makeLocalSet(size, b.makeBinary(BinaryOp::SubInt32, - b.makeLocalGet(size, Type::i32), - b.makeConst(1))), - // *(dst+size) = val - b.makeStore(1, - 0, - 1, - b.makeBinary(BinaryOp::AddInt32, + b.makeLocalGet(size, Type::i32), + b.makeConst(1))), + // *(dst+size) = val + b.makeStore(1, + 0, + 1, + b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), - b.makeLocalGet(val, Type::i32), - Type::i32, - memory), - b.makeBreak("copy", nullptr)})))); + b.makeLocalGet(val, Type::i32), + Type::i32, + memory), + b.makeBreak("copy", nullptr)})))); module->getFunction("__memory_fill")->body = body; } void VisitTableCopy(TableCopy* curr) { Fatal() << "table.copy instruction found. Memory copy lowering is not " - "designed to work on modules with bulk table operations"; + "designed to work on modules with bulk table operations"; } void VisitTableFill(TableCopy* curr) { Fatal() << "table.fill instruction found. Memory copy lowering is not " - "designed to work on modules with bulk table operations"; + "designed to work on modules with bulk table operations"; } }; From c758b5fba0fad1087250528cadfa36a47a1af4cb Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Thu, 7 Nov 2024 14:51:26 -0800 Subject: [PATCH 10/17] fix func names --- src/passes/MemoryCopyFillLowering.cpp | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index ddbb07989f5..02c418627b8 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "ir/names.h" #include "pass.h" #include "wasm-builder.h" #include "wasm.h" @@ -26,6 +27,8 @@ struct MemoryCopyFillLowering : public WalkerPass> { bool needsMemoryCopy = false; bool needsMemoryFill = false; + Name memCopyFuncName; + Name memFillFuncName; void visitMemoryCopy(MemoryCopy* curr) { assert(curr->destMemory == @@ -69,18 +72,21 @@ struct MemoryCopyFillLowering // In order to introduce a call to a function, it must first exist, so // create an empty stub. Builder b(*module); - auto memCpyFunc = b.makeFunction( - "__memory_copy", + + memCopyFuncName = Names::getValidFunctionName(*module, "__memory_copy"); + memFillFuncName = Names::getValidFunctionName(*module, "__memory_fill"); + auto memCopyFunc = b.makeFunction( + memCopyFuncName, {{"dst", Type::i32}, {"src", Type::i32}, {"size", Type::i32}}, Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), {{"start", Type::i32}, {"end", Type::i32}, {"step", Type::i32}, {"i", Type::i32}}); - memCpyFunc->body = b.makeBlock(); - module->addFunction(memCpyFunc.release()); + memCopyFunc->body = b.makeBlock(); + module->addFunction(memCopyFunc.release()); auto memFillFunc = b.makeFunction( - "__memory_fill", + memFillFuncName, {{"dst", Type::i32}, {"val", Type::i32}, {"size", Type::i32}}, Signature({Type::i32, Type::i32, Type::i32}, {Type::none}), {}); @@ -92,13 +98,13 @@ struct MemoryCopyFillLowering if (needsMemoryCopy) { CreateMemoryCopyFunc(module); } else { - module->removeFunction("__memory_copy"); + module->removeFunction(memCopyFuncName); } if (needsMemoryFill) { CreateMemoryFillFunc(module); } else { - module->removeFunction("__memory_fill"); + module->removeFunction(memFillFuncName); } module->features.disable(FeatureSet::BulkMemory); } @@ -184,7 +190,7 @@ struct MemoryCopyFillLowering b.makeLocalGet(step, Type::i32))), // loop b.makeBreak("copy", nullptr)})))); - module->getFunction("__memory_copy")->body = body; + module->getFunction(memCopyFuncName)->body = body; } void CreateMemoryFillFunc(Module* module) { @@ -230,7 +236,7 @@ struct MemoryCopyFillLowering Type::i32, memory), b.makeBreak("copy", nullptr)})))); - module->getFunction("__memory_fill")->body = body; + module->getFunction(memFillFuncName)->body = body; } void VisitTableCopy(TableCopy* curr) { From 02201ef3d123b35d864b7357a6d928a842e69126 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 8 Nov 2024 09:44:08 -0800 Subject: [PATCH 11/17] improve formatting --- src/passes/MemoryCopyFillLowering.cpp | 28 ++++++++------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index 02c418627b8..2806edb320b 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -154,10 +154,8 @@ struct MemoryCopyFillLowering }))); // i = start body->list.push_back(b.makeLocalSet(i, b.makeLocalGet(start, Type::i32))); - body->list.push_back(b.makeBlock( - "out", - b.makeLoop( - "copy", + body->list.push_back(b.makeBlock("out", + b.makeLoop("copy", b.makeBlock( {// break if i == end b.makeBreak("out", @@ -166,16 +164,11 @@ struct MemoryCopyFillLowering b.makeLocalGet(i, Type::i32), b.makeLocalGet(end, Type::i32))), // dst[i] = src[i] - b.makeStore(1, - 0, - 1, + b.makeStore(1, 0, 1, b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(dst, Type::i32), b.makeLocalGet(i, Type::i32)), - b.makeLoad(1, - false, - 0, - 1, + b.makeLoad(1, false, 0, 1, b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(src, Type::i32), b.makeLocalGet(i, Type::i32)), @@ -210,14 +203,11 @@ struct MemoryCopyFillLowering b.makeConst(Memory::kPageSize))), b.makeUnreachable())); - body->list.push_back(b.makeBlock( - "out", - b.makeLoop( - "copy", + body->list.push_back(b.makeBlock("out", + b.makeLoop("copy", b.makeBlock( {// break if size == 0 - b.makeBreak( - "out", + b.makeBreak("out", nullptr, b.makeUnary(UnaryOp::EqZInt32, b.makeLocalGet(size, Type::i32))), // size-- @@ -226,9 +216,7 @@ struct MemoryCopyFillLowering b.makeLocalGet(size, Type::i32), b.makeConst(1))), // *(dst+size) = val - b.makeStore(1, - 0, - 1, + b.makeStore(1, 0, 1, b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), From 2854ff3e95b72d85e7256bd0aaf3e1e7b64aa0a2 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 8 Nov 2024 10:52:26 -0800 Subject: [PATCH 12/17] suppress clang-format --- src/passes/MemoryCopyFillLowering.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index 2806edb320b..b401b910c8f 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -109,6 +109,7 @@ struct MemoryCopyFillLowering module->features.disable(FeatureSet::BulkMemory); } + // clang-format off void CreateMemoryCopyFunc(Module* module) { Builder b(*module); Index dst = 0, src = 1, size = 2, start = 3, end = 4, step = 5, i = 6; @@ -226,6 +227,7 @@ struct MemoryCopyFillLowering b.makeBreak("copy", nullptr)})))); module->getFunction(memFillFuncName)->body = body; } + // clang-format on void VisitTableCopy(TableCopy* curr) { Fatal() << "table.copy instruction found. Memory copy lowering is not " From 3ccb64f85ab2250d306818ad7a835a0fc11f5582 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 8 Nov 2024 10:56:40 -0800 Subject: [PATCH 13/17] revert formatting changes; not worth it --- src/passes/MemoryCopyFillLowering.cpp | 30 ++++++++++++++++++--------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index b401b910c8f..02c418627b8 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -109,7 +109,6 @@ struct MemoryCopyFillLowering module->features.disable(FeatureSet::BulkMemory); } - // clang-format off void CreateMemoryCopyFunc(Module* module) { Builder b(*module); Index dst = 0, src = 1, size = 2, start = 3, end = 4, step = 5, i = 6; @@ -155,8 +154,10 @@ struct MemoryCopyFillLowering }))); // i = start body->list.push_back(b.makeLocalSet(i, b.makeLocalGet(start, Type::i32))); - body->list.push_back(b.makeBlock("out", - b.makeLoop("copy", + body->list.push_back(b.makeBlock( + "out", + b.makeLoop( + "copy", b.makeBlock( {// break if i == end b.makeBreak("out", @@ -165,11 +166,16 @@ struct MemoryCopyFillLowering b.makeLocalGet(i, Type::i32), b.makeLocalGet(end, Type::i32))), // dst[i] = src[i] - b.makeStore(1, 0, 1, + b.makeStore(1, + 0, + 1, b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(dst, Type::i32), b.makeLocalGet(i, Type::i32)), - b.makeLoad(1, false, 0, 1, + b.makeLoad(1, + false, + 0, + 1, b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(src, Type::i32), b.makeLocalGet(i, Type::i32)), @@ -204,11 +210,14 @@ struct MemoryCopyFillLowering b.makeConst(Memory::kPageSize))), b.makeUnreachable())); - body->list.push_back(b.makeBlock("out", - b.makeLoop("copy", + body->list.push_back(b.makeBlock( + "out", + b.makeLoop( + "copy", b.makeBlock( {// break if size == 0 - b.makeBreak("out", + b.makeBreak( + "out", nullptr, b.makeUnary(UnaryOp::EqZInt32, b.makeLocalGet(size, Type::i32))), // size-- @@ -217,7 +226,9 @@ struct MemoryCopyFillLowering b.makeLocalGet(size, Type::i32), b.makeConst(1))), // *(dst+size) = val - b.makeStore(1, 0, 1, + b.makeStore(1, + 0, + 1, b.makeBinary(BinaryOp::AddInt32, b.makeLocalGet(dst, Type::i32), b.makeLocalGet(size, Type::i32)), @@ -227,7 +238,6 @@ struct MemoryCopyFillLowering b.makeBreak("copy", nullptr)})))); module->getFunction(memFillFuncName)->body = body; } - // clang-format on void VisitTableCopy(TableCopy* curr) { Fatal() << "table.copy instruction found. Memory copy lowering is not " From 621e6b5ad1a205c48922cc36cb83632c8cd2a99b Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 8 Nov 2024 11:59:34 -0800 Subject: [PATCH 14/17] review suggestions --- src/passes/MemoryCopyFillLowering.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index 02c418627b8..6ec3b1869ff 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -20,7 +20,9 @@ #include "wasm.h" // Replace memory.copy and memory.fill with a call to a function that -// implements the same semantics. +// implements the same semantics. This is intended to be used with LLVM output, +// so anything considered undefined behavior in LLVM is ignored. (In +// particular, pointer overflow is UB and not handled here). namespace wasm { struct MemoryCopyFillLowering @@ -96,20 +98,20 @@ struct MemoryCopyFillLowering Super::run(module); if (needsMemoryCopy) { - CreateMemoryCopyFunc(module); + createMemoryCopyFunc(module); } else { module->removeFunction(memCopyFuncName); } if (needsMemoryFill) { - CreateMemoryFillFunc(module); + createMemoryFillFunc(module); } else { module->removeFunction(memFillFuncName); } module->features.disable(FeatureSet::BulkMemory); } - void CreateMemoryCopyFunc(Module* module) { + void createMemoryCopyFunc(Module* module) { Builder b(*module); Index dst = 0, src = 1, size = 2, start = 3, end = 4, step = 5, i = 6; Name memory = module->memories.front()->name; @@ -193,7 +195,7 @@ struct MemoryCopyFillLowering module->getFunction(memCopyFuncName)->body = body; } - void CreateMemoryFillFunc(Module* module) { + void createMemoryFillFunc(Module* module) { Builder b(*module); Index dst = 0, val = 1, size = 2; Name memory = module->memories.front()->name; From 77528243b082f53fbc40ee794f1e743cf7129e37 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Fri, 8 Nov 2024 12:03:54 -0800 Subject: [PATCH 15/17] add explanation --- src/passes/MemoryCopyFillLowering.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/passes/MemoryCopyFillLowering.cpp b/src/passes/MemoryCopyFillLowering.cpp index 6ec3b1869ff..5855e545066 100644 --- a/src/passes/MemoryCopyFillLowering.cpp +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -136,7 +136,9 @@ struct MemoryCopyFillLowering b.makeLocalGet(size, Type::i32)), b.makeLocalGet(end, Type::i32))), b.makeUnreachable())); - // if src < dest + // start and end are the starting and past-the-end indexes + // if src < dest: start = size - 1, end = -1, step = -1 + // else: start = 0, end = size, step = 1 body->list.push_back( b.makeIf(b.makeBinary(BinaryOp::LtUInt32, b.makeLocalGet(src, Type::i32), From 0b7c98a37376472c646d43d74086ad39385b4e64 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Tue, 12 Nov 2024 15:52:15 -0800 Subject: [PATCH 16/17] Add memory copy test --- test/lit/exec/memory-copy.wat | 175 ++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 test/lit/exec/memory-copy.wat diff --git a/test/lit/exec/memory-copy.wat b/test/lit/exec/memory-copy.wat new file mode 100644 index 00000000000..ef35d0dff84 --- /dev/null +++ b/test/lit/exec/memory-copy.wat @@ -0,0 +1,175 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s --enable-bulk-memory --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s +;; RUN: wasm-opt %s --enable-bulk-memory --memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +;; Tests derived from bulk-memory.wast spec tests + +;; memory.copy +(module + (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) + (memory 1 1) + (data (i32.const 0) "\aa\bb\cc\dd") + + (func $assert_load (param i32 i32) + (if (i32.ne (local.get 1) (i32.load8_u (local.get 0))) + (then (unreachable))) + ) + + (func $print_memory + (local $i i32) + (local.set 0 (i32.const 9)) + (loop $loop + (call $log-i32 (local.get 0)) + (call $log-i32 (i32.load8_u (local.get 0))) + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + (br_if $loop (i32.ne (local.get 0) (i32.const 17))) + ) + ) + + ;; non-overlapping copy + ;; CHECK: [fuzz-exec] calling test1 + ;; CHECK-NEXT: [LoggingExternalInterface logging 9] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 11] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 12] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 13] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 14] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 15] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 16] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 9] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [LoggingExternalInterface logging 170] + ;; CHECK-NEXT: [LoggingExternalInterface logging 11] + ;; CHECK-NEXT: [LoggingExternalInterface logging 187] + ;; CHECK-NEXT: [LoggingExternalInterface logging 12] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 13] + ;; CHECK-NEXT: [LoggingExternalInterface logging 221] + ;; CHECK-NEXT: [LoggingExternalInterface logging 14] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 15] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 16] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test1 (export "test1") + (call $print_memory) + (memory.copy (i32.const 10) (i32.const 0) (i32.const 4)) + (call $print_memory) + (call $assert_load (i32.const 9) (i32.const 0)) + (call $assert_load (i32.const 10) (i32.const 0xaa)) + (call $assert_load (i32.const 11) (i32.const 0xbb)) + (call $assert_load (i32.const 12) (i32.const 0xcc)) + (call $assert_load (i32.const 13) (i32.const 0xdd)) + (call $assert_load (i32.const 14) (i32.const 0)) + ) + ;; Overlap, src > dst + ;; CHECK: [fuzz-exec] calling test2 + ;; CHECK-NEXT: [LoggingExternalInterface logging 9] + ;; CHECK-NEXT: [LoggingExternalInterface logging 187] + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 11] + ;; CHECK-NEXT: [LoggingExternalInterface logging 221] + ;; CHECK-NEXT: [LoggingExternalInterface logging 12] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 13] + ;; CHECK-NEXT: [LoggingExternalInterface logging 221] + ;; CHECK-NEXT: [LoggingExternalInterface logging 14] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 15] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 16] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test2 (export "test2") + (memory.copy (i32.const 8) (i32.const 10) (i32.const 4)) + (call $print_memory) + (call $assert_load (i32.const 8) (i32.const 0xaa)) + (call $assert_load (i32.const 9) (i32.const 0xbb)) + (call $assert_load (i32.const 10) (i32.const 0xcc)) + (call $assert_load (i32.const 11) (i32.const 0xdd)) + (call $assert_load (i32.const 12) (i32.const 0xcc)) + (call $assert_load (i32.const 13) (i32.const 0xdd)) + ) + ;; Overlap, src < dst + ;; CHECK: [fuzz-exec] calling test3 + ;; CHECK-NEXT: [LoggingExternalInterface logging 9] + ;; CHECK-NEXT: [LoggingExternalInterface logging 187] + ;; CHECK-NEXT: [LoggingExternalInterface logging 10] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 11] + ;; CHECK-NEXT: [LoggingExternalInterface logging 170] + ;; CHECK-NEXT: [LoggingExternalInterface logging 12] + ;; CHECK-NEXT: [LoggingExternalInterface logging 187] + ;; CHECK-NEXT: [LoggingExternalInterface logging 13] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 14] + ;; CHECK-NEXT: [LoggingExternalInterface logging 221] + ;; CHECK-NEXT: [LoggingExternalInterface logging 15] + ;; CHECK-NEXT: [LoggingExternalInterface logging 204] + ;; CHECK-NEXT: [LoggingExternalInterface logging 16] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test3 (export "test3") + (memory.copy (i32.const 10) (i32.const 7) (i32.const 6)) + (call $print_memory) + (call $assert_load (i32.const 10) (i32.const 0x0)) + (call $assert_load (i32.const 11) (i32.const 0xaa)) + (call $assert_load (i32.const 12) (i32.const 0xbb)) + (call $assert_load (i32.const 13) (i32.const 0xcc)) + (call $assert_load (i32.const 14) (i32.const 0xdd)) + (call $assert_load (i32.const 15) (i32.const 0xcc)) + (call $assert_load (i32.const 16) (i32.const 0)) + ) + ;; Overlap src < dst but size is OOB (but the address does not overflow) + ;; CHECK: [fuzz-exec] calling test4a + ;; CHECK-NEXT: [trap out of bounds segment access in memory.copy] + (func $test4a (export "test4a") + (memory.copy (i32.const 13) (i32.const 11) (i32.const 0x10000)) + (call $print_memory) ;; not reached. + ) + ;; CHECK: [fuzz-exec] calling test4b + (func $test4b (export "test4b") + (call $assert_load (i32.const 10) (i32.const 0x0)) + (call $assert_load (i32.const 11) (i32.const 0xaa)) + (call $assert_load (i32.const 12) (i32.const 0xbb)) + (call $assert_load (i32.const 13) (i32.const 0xcc)) + (call $assert_load (i32.const 14) (i32.const 0xdd)) + (call $assert_load (i32.const 15) (i32.const 0xcc)) + (call $assert_load (i32.const 16) (i32.const 0)) + ) + ;; Copy ending at memory limit is ok + ;; CHECK: [fuzz-exec] calling test5 + (func $test5 (export "test5") + (memory.copy (i32.const 0xff00) (i32.const 0) (i32.const 0x100)) + (memory.copy (i32.const 0xfe00) (i32.const 0xff00) (i32.const 0x100)) + ) + ;; Succeed when copying 0 bytes at the end of the region + ;; CHECK: [fuzz-exec] calling test6 + (func $test6 (export "test6") + (memory.copy (i32.const 0x10000) (i32.const 0) (i32.const 0)) + (memory.copy (i32.const 0x0) (i32.const 0x10000) (i32.const 0)) + ) + ;; copying 0 bytes outside of the limit is not allowed. + ;; CHECK: [fuzz-exec] calling test7 + ;; CHECK-NEXT: [trap out of bounds segment access in memory.copy] + (func $test7 (export "test7") + (memory.copy (i32.const 0x10001) (i32.const 0) (i32.const 0x0)) + ) + ;; CHECK: [fuzz-exec] calling test8 + ;; CHECK-NEXT: [trap out of bounds segment access in memory.copy] + (func $test8 (export "test8") + (memory.copy (i32.const 0x0) (i32.const 0x10001) (i32.const 0)) + ) +) + + + From 91b60ade1d287964f1832ed3db4b941949bae602 Mon Sep 17 00:00:00 2001 From: Derek Schuff Date: Tue, 12 Nov 2024 17:02:30 -0800 Subject: [PATCH 17/17] update tests --- test/lit/exec/memory-copy.wat | 94 ++++++++++++++++- test/lit/exec/memory-fill.wat | 184 ++++++++++++++++++++++++++++++++++ 2 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 test/lit/exec/memory-fill.wat diff --git a/test/lit/exec/memory-copy.wat b/test/lit/exec/memory-copy.wat index ef35d0dff84..793f94656a2 100644 --- a/test/lit/exec/memory-copy.wat +++ b/test/lit/exec/memory-copy.wat @@ -1,6 +1,5 @@ ;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. -;; RUN: wasm-opt %s --enable-bulk-memory --fuzz-exec-before -q -o /dev/null 2>&1 | filecheck %s ;; RUN: wasm-opt %s --enable-bulk-memory --memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s ;; Tests derived from bulk-memory.wast spec tests @@ -173,3 +172,96 @@ +;; CHECK: [fuzz-exec] calling test1 +;; CHECK-NEXT: [LoggingExternalInterface logging 9] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 10] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 11] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 12] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 13] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 14] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 15] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 16] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 9] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 10] +;; CHECK-NEXT: [LoggingExternalInterface logging 170] +;; CHECK-NEXT: [LoggingExternalInterface logging 11] +;; CHECK-NEXT: [LoggingExternalInterface logging 187] +;; CHECK-NEXT: [LoggingExternalInterface logging 12] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 13] +;; CHECK-NEXT: [LoggingExternalInterface logging 221] +;; CHECK-NEXT: [LoggingExternalInterface logging 14] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 15] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 16] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test2 +;; CHECK-NEXT: [LoggingExternalInterface logging 9] +;; CHECK-NEXT: [LoggingExternalInterface logging 187] +;; CHECK-NEXT: [LoggingExternalInterface logging 10] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 11] +;; CHECK-NEXT: [LoggingExternalInterface logging 221] +;; CHECK-NEXT: [LoggingExternalInterface logging 12] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 13] +;; CHECK-NEXT: [LoggingExternalInterface logging 221] +;; CHECK-NEXT: [LoggingExternalInterface logging 14] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 15] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 16] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test3 +;; CHECK-NEXT: [LoggingExternalInterface logging 9] +;; CHECK-NEXT: [LoggingExternalInterface logging 187] +;; CHECK-NEXT: [LoggingExternalInterface logging 10] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 11] +;; CHECK-NEXT: [LoggingExternalInterface logging 170] +;; CHECK-NEXT: [LoggingExternalInterface logging 12] +;; CHECK-NEXT: [LoggingExternalInterface logging 187] +;; CHECK-NEXT: [LoggingExternalInterface logging 13] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 14] +;; CHECK-NEXT: [LoggingExternalInterface logging 221] +;; CHECK-NEXT: [LoggingExternalInterface logging 15] +;; CHECK-NEXT: [LoggingExternalInterface logging 204] +;; CHECK-NEXT: [LoggingExternalInterface logging 16] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test4a +;; CHECK-NEXT: [trap unreachable] + +;; CHECK: [fuzz-exec] calling test4b + +;; CHECK: [fuzz-exec] calling test5 + +;; CHECK: [fuzz-exec] calling test6 + +;; CHECK: [fuzz-exec] calling test7 +;; CHECK-NEXT: [trap unreachable] + +;; CHECK: [fuzz-exec] calling test8 +;; CHECK-NEXT: [trap unreachable] +;; CHECK-NEXT: [fuzz-exec] comparing test1 +;; CHECK-NEXT: [fuzz-exec] comparing test2 +;; CHECK-NEXT: [fuzz-exec] comparing test3 +;; CHECK-NEXT: [fuzz-exec] comparing test4a +;; CHECK-NEXT: [fuzz-exec] comparing test4b +;; CHECK-NEXT: [fuzz-exec] comparing test5 +;; CHECK-NEXT: [fuzz-exec] comparing test6 +;; CHECK-NEXT: [fuzz-exec] comparing test7 +;; CHECK-NEXT: [fuzz-exec] comparing test8 diff --git a/test/lit/exec/memory-fill.wat b/test/lit/exec/memory-fill.wat new file mode 100644 index 00000000000..dc9aead6da9 --- /dev/null +++ b/test/lit/exec/memory-fill.wat @@ -0,0 +1,184 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s --enable-bulk-memory --memory-copy-fill-lowering --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +;; Tests derived from bulk-memory.wast spec tests + +;; memory.fill +(module + (import "fuzzing-support" "log-i32" (func $log-i32 (param i32))) + (memory 1) + + + (func $assert_load (param i32 i32) + (if (i32.ne (local.get 1) (i32.load8_u (local.get 0))) + (then (unreachable))) + ) + + (func $print_memory (param i32 i32) + (loop $loop + (call $log-i32 (local.get 0)) + (call $log-i32 (i32.load8_u (local.get 0))) + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + (br_if $loop (i32.ne (local.get 0) (local.get 1))) + ) + ) + ;; basic fill test + ;; CHECK: [fuzz-exec] calling test1 + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 255] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 255] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 255] + ;; CHECK-NEXT: [LoggingExternalInterface logging 4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test1 (export "test1") + (call $print_memory (i32.const 0) (i32.const 5)) + (memory.fill (i32.const 1) (i32.const 0xff) (i32.const 3)) + (call $print_memory (i32.const 0) (i32.const 5)) + (call $assert_load (i32.const 0) (i32.const 0)) + (call $assert_load (i32.const 1) (i32.const 0xff)) + (call $assert_load (i32.const 2) (i32.const 0xff)) + (call $assert_load (i32.const 3) (i32.const 0xff)) + (call $assert_load (i32.const 4) (i32.const 0x0)) + ) + ;; Fill value is stored as a byte + ;; CHECK: [fuzz-exec] calling test2 + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 170] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 170] + (func $test2 (export "test2") + (memory.fill (i32.const 0) (i32.const 0xbbaa) (i32.const 2)) + (call $print_memory (i32.const 0) (i32.const 2)) + (call $assert_load (i32.const 0) (i32.const 0xaa)) + (call $assert_load (i32.const 1) (i32.const 0xaa)) + ) + ;; Fill all of memory + ;; CHECK: [fuzz-exec] calling test3 + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 5] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test3 (export "test3") + (memory.fill (i32.const 0) (i32.const 0) (i32.const 0x10000)) + ;; let's not print all of memory; just beyond what we filled before + (call $print_memory (i32.const 0) (i32.const 6)) + ) + ;; Succeed when writing 0 bytes at the end of the region + ;; CHECK: [fuzz-exec] calling test4 + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 1] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 2] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 3] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 4] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 5] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + ;; CHECK-NEXT: [LoggingExternalInterface logging 6] + ;; CHECK-NEXT: [LoggingExternalInterface logging 0] + (func $test4 (export "test4") + (memory.fill (i32.const 0x10000) (i32.const 0) (i32.const 0)) + ;; let's not print all of memory; just beyond what we filled before + (call $print_memory (i32.const 0) (i32.const 7)) + ) + ;; Writing 0 bytes outside of memory is not allowed + ;; CHECK: [fuzz-exec] calling test5 + ;; CHECK-NEXT: [trap out of bounds memory access in memory.fill] + (func $test5 (export "test5") + (memory.fill (i32.const 0x10001) (i32.const 0) (i32.const 0)) + ;; should not be reached + (call $print_memory (i32.const 0) (i32.const 6)) + ) + ;; again we do not test negative/overflowing addresses as the spec test does. +) +;; CHECK: [fuzz-exec] calling test1 +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 2] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 3] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 4] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 255] +;; CHECK-NEXT: [LoggingExternalInterface logging 2] +;; CHECK-NEXT: [LoggingExternalInterface logging 255] +;; CHECK-NEXT: [LoggingExternalInterface logging 3] +;; CHECK-NEXT: [LoggingExternalInterface logging 255] +;; CHECK-NEXT: [LoggingExternalInterface logging 4] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test2 +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 170] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 170] + +;; CHECK: [fuzz-exec] calling test3 +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 2] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 3] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 4] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 5] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test4 +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 1] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 2] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 3] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 4] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 5] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] +;; CHECK-NEXT: [LoggingExternalInterface logging 6] +;; CHECK-NEXT: [LoggingExternalInterface logging 0] + +;; CHECK: [fuzz-exec] calling test5 +;; CHECK-NEXT: [trap unreachable] +;; CHECK-NEXT: [fuzz-exec] comparing test1 +;; CHECK-NEXT: [fuzz-exec] comparing test2 +;; CHECK-NEXT: [fuzz-exec] comparing test3 +;; CHECK-NEXT: [fuzz-exec] comparing test4 +;; CHECK-NEXT: [fuzz-exec] comparing test5