diff options
-rw-r--r-- | src/passes/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/passes/MemoryCopyFillLowering.cpp | 260 | ||||
-rw-r--r-- | src/passes/pass.cpp | 4 | ||||
-rw-r--r-- | src/passes/passes.h | 1 | ||||
-rw-r--r-- | test/lit/exec/memory-copy.wat | 267 | ||||
-rw-r--r-- | test/lit/exec/memory-fill.wat | 184 | ||||
-rw-r--r-- | test/lit/help/wasm-metadce.test | 4 | ||||
-rw-r--r-- | test/lit/help/wasm-opt.test | 4 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 4 | ||||
-rw-r--r-- | test/lit/passes/memory-copy-fill-lowering.wast | 168 |
10 files changed, 897 insertions, 0 deletions
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index a219438b4..6053c5522 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 000000000..5855e5450 --- /dev/null +++ b/src/passes/MemoryCopyFillLowering.cpp @@ -0,0 +1,260 @@ +/* + * 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 "ir/names.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" + +// Replace memory.copy and memory.fill with a call to a function that +// 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 + : public WalkerPass<PostWalker<MemoryCopyFillLowering>> { + bool needsMemoryCopy = false; + bool needsMemoryFill = false; + Name memCopyFuncName; + Name memFillFuncName; + + void visitMemoryCopy(MemoryCopy* curr) { + 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)); + needsMemoryCopy = true; + } + + void visitMemoryFill(MemoryFill* curr) { + 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()) { + 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); + + 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}}); + memCopyFunc->body = b.makeBlock(); + module->addFunction(memCopyFunc.release()); + auto memFillFunc = b.makeFunction( + memFillFuncName, + {{"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) { + createMemoryCopyFunc(module); + } else { + module->removeFunction(memCopyFuncName); + } + + if (needsMemoryFill) { + createMemoryFillFunc(module); + } else { + module->removeFunction(memFillFuncName); + } + 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())); + // 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), + 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(memCopyFuncName)->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(memFillFuncName)->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"; + } + 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() { + return new MemoryCopyFillLowering(); +} + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index d55e22111..fcefb89ad 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 c100cd2f9..121dbcd9d 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(); diff --git a/test/lit/exec/memory-copy.wat b/test/lit/exec/memory-copy.wat new file mode 100644 index 000000000..793f94656 --- /dev/null +++ b/test/lit/exec/memory-copy.wat @@ -0,0 +1,267 @@ +;; 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.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)) + ) +) + + + +;; 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 000000000..dc9aead6d --- /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 diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 50295b1f0..908b381e8 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -239,6 +239,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 0691d1c18..7d173b084 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -248,6 +248,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 89dcaa028..514f638d9 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -202,6 +202,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/passes/memory-copy-fill-lowering.wast b/test/lit/passes/memory-copy-fill-lowering.wast new file mode 100644 index 000000000..540237ff7 --- /dev/null +++ b/test/lit/passes/memory-copy-fill-lowering.wast @@ -0,0 +1,168 @@ +;; 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 $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) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.or +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $end) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $src) +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $end) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.lt_u +;; CHECK-NEXT: (local.get $src) +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: ) +;; 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 $i) +;; CHECK-NEXT: (local.get $end) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.store8 +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $dst) +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load8_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $src) +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $i +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $i) +;; CHECK-NEXT: (local.get $step) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (br $copy) +;; 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: ) |