diff options
-rw-r--r-- | src/ir/names.h | 4 | ||||
-rw-r--r-- | src/tools/wasm-split/instrumenter.cpp | 77 | ||||
-rw-r--r-- | src/tools/wasm-split/instrumenter.h | 25 | ||||
-rw-r--r-- | src/tools/wasm-split/split-options.cpp | 31 | ||||
-rw-r--r-- | src/tools/wasm-split/split-options.h | 2 | ||||
-rw-r--r-- | src/tools/wasm-split/wasm-split.cpp | 11 | ||||
-rw-r--r-- | test/lit/help/wasm-split.test | 22 | ||||
-rw-r--r-- | test/lit/wasm-split/instrument-in-secondary-memory-custom-names.wast | 31 | ||||
-rw-r--r-- | test/lit/wasm-split/instrument-in-secondary-memory.wast | 92 | ||||
-rw-r--r-- | test/lit/wasm-split/invalid-options.wast | 13 |
10 files changed, 270 insertions, 38 deletions
diff --git a/src/ir/names.h b/src/ir/names.h index 4bdfacb1e..b2165c2c2 100644 --- a/src/ir/names.h +++ b/src/ir/names.h @@ -83,6 +83,10 @@ inline Name getValidElementSegmentName(Module& module, Name root) { return getValidName( root, [&](Name test) { return !module.getElementSegmentOrNull(test); }); } +inline Name getValidMemoryName(Module& module, Name root) { + return getValidName(root, + [&](Name test) { return !module.getMemoryOrNull(test); }); +} inline Name getValidLocalName(Function& func, Name root) { return getValidName(root, [&](Name test) { return !func.hasLocalIndex(test); }); diff --git a/src/tools/wasm-split/instrumenter.cpp b/src/tools/wasm-split/instrumenter.cpp index 50da9d034..7f9ff9c75 100644 --- a/src/tools/wasm-split/instrumenter.cpp +++ b/src/tools/wasm-split/instrumenter.cpp @@ -22,25 +22,31 @@ namespace wasm { -Instrumenter::Instrumenter(const WasmSplitOptions& options, uint64_t moduleHash) - : options(options), moduleHash(moduleHash) {} +Instrumenter::Instrumenter(const InstrumenterConfig& config, + uint64_t moduleHash) + : config(config), moduleHash(moduleHash) {} void Instrumenter::run(PassRunner* runner, Module* wasm) { this->runner = runner; this->wasm = wasm; - addGlobals(); + + size_t numFuncs = 0; + ModuleUtils::iterDefinedFunctions(*wasm, [&](Function*) { ++numFuncs; }); + + addGlobals(numFuncs); + addSecondaryMemory(numFuncs); instrumentFuncs(); - addProfileExport(); + addProfileExport(numFuncs); } -void Instrumenter::addGlobals() { - if (options.storageKind != WasmSplitOptions::StorageKind::InGlobals) { +void Instrumenter::addGlobals(size_t numFuncs) { + if (config.storageKind != WasmSplitOptions::StorageKind::InGlobals) { // Don't need globals return; } // Create fresh global names (over-reserves, but that's ok) counterGlobal = Names::getValidGlobalName(*wasm, "monotonic_counter"); - functionGlobals.reserve(wasm->functions.size()); + functionGlobals.reserve(numFuncs); ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) { functionGlobals.push_back(Names::getValidGlobalName( *wasm, std::string(func->name.c_str()) + "_timestamp")); @@ -62,11 +68,31 @@ void Instrumenter::addGlobals() { } } +void Instrumenter::addSecondaryMemory(size_t numFuncs) { + if (config.storageKind != WasmSplitOptions::StorageKind::InSecondaryMemory) { + // Don't need secondary memory + return; + } + if (!wasm->features.hasMultiMemories()) { + Fatal() + << "error: --in-secondary-memory requires multi-memories to be enabled"; + } + + secondaryMemory = + Names::getValidMemoryName(*wasm, config.secondaryMemoryName); + // Create a memory with enough pages to write into + size_t pages = (numFuncs + Memory::kPageSize - 1) / Memory::kPageSize; + auto mem = Builder::makeMemory(secondaryMemory, pages, pages, true); + mem->module = config.importNamespace; + mem->base = config.secondaryMemoryName; + wasm->addMemory(std::move(mem)); +} + void Instrumenter::instrumentFuncs() { // Inject code at the beginning of each function to advance the monotonic // counter and set the function's timestamp if it hasn't already been set. Builder builder(*wasm); - switch (options.storageKind) { + switch (config.storageKind) { case WasmSplitOptions::StorageKind::InGlobals: { // (if (i32.eqz (global.get $timestamp)) // (block @@ -102,13 +128,22 @@ void Instrumenter::instrumentFuncs() { }); break; } - case WasmSplitOptions::StorageKind::InMemory: { + case WasmSplitOptions::StorageKind::InMemory: + case WasmSplitOptions::StorageKind::InSecondaryMemory: { if (!wasm->features.hasAtomics()) { - Fatal() << "error: --in-memory requires atomics to be enabled"; + const char* command = + config.storageKind == WasmSplitOptions::StorageKind::InMemory + ? "in-memory" + : "in-secondary-memory"; + Fatal() << "error: --" << command << " requires atomics to be enabled"; } // (i32.atomic.store8 offset=funcidx (i32.const 0) (i32.const 1)) Index funcIdx = 0; assert(!wasm->memories.empty()); + Name memoryName = + config.storageKind == WasmSplitOptions::StorageKind::InMemory + ? wasm->memories[0]->name + : secondaryMemory; ModuleUtils::iterDefinedFunctions(*wasm, [&](Function* func) { func->body = builder.makeSequence( builder.makeAtomicStore(1, @@ -116,7 +151,7 @@ void Instrumenter::instrumentFuncs() { builder.makeConstPtr(0, Type::i32), builder.makeConst(uint32_t(1)), Type::i32, - wasm->memories[0]->name), + memoryName), func->body, func->body->type); ++funcIdx; @@ -141,21 +176,18 @@ void Instrumenter::instrumentFuncs() { // otherwise. Functions with smaller non-zero timestamps were called earlier in // the instrumented run than funtions with larger timestamps. -void Instrumenter::addProfileExport() { +void Instrumenter::addProfileExport(size_t numFuncs) { // Create and export a function to dump the profile into a given memory // buffer. The function takes the available address and buffer size as // arguments and returns the total size of the profile. It only actually // writes the profile if the given space is sufficient to hold it. - auto name = Names::getValidFunctionName(*wasm, options.profileExport); + auto name = Names::getValidFunctionName(*wasm, config.profileExport); auto writeProfile = Builder::makeFunction( name, Signature({Type::i32, Type::i32}, Type::i32), {}); writeProfile->hasExplicitName = true; writeProfile->setLocalName(0, "addr"); writeProfile->setLocalName(1, "size"); - size_t numFuncs = 0; - ModuleUtils::iterDefinedFunctions(*wasm, [&](Function*) { ++numFuncs; }); - // Calculate the size of the profile: // 8 bytes module hash + // 4 bytes for the timestamp for each function @@ -188,7 +220,7 @@ void Instrumenter::addProfileExport() { 8, 0, 1, getAddr(), hashConst(), Type::i64, wasm->memories[0]->name); uint32_t offset = 8; - switch (options.storageKind) { + switch (config.storageKind) { case WasmSplitOptions::StorageKind::InGlobals: { for (const auto& global : functionGlobals) { writeData = builder.blockify( @@ -204,12 +236,17 @@ void Instrumenter::addProfileExport() { } break; } - case WasmSplitOptions::StorageKind::InMemory: { + case WasmSplitOptions::StorageKind::InMemory: + case WasmSplitOptions::StorageKind::InSecondaryMemory: { Index funcIdxVar = Builder::addVar(writeProfile.get(), "funcIdx", Type::i32); auto getFuncIdx = [&]() { return builder.makeLocalGet(funcIdxVar, Type::i32); }; + Name loadMemoryName = + config.storageKind == WasmSplitOptions::StorageKind::InMemory + ? wasm->memories[0]->name + : secondaryMemory; // (block $outer // (loop $l // (br_if $outer (i32.eq (local.get $fucIdx) (i32.const numFuncs)) @@ -249,7 +286,7 @@ void Instrumenter::addProfileExport() { builder.makeBinary( MulInt32, getFuncIdx(), builder.makeConst(uint32_t(4)))), builder.makeAtomicLoad( - 1, 0, getFuncIdx(), Type::i32, wasm->memories[0]->name), + 1, 0, getFuncIdx(), Type::i32, loadMemoryName), Type::i32, wasm->memories[0]->name), builder.makeLocalSet( @@ -269,7 +306,7 @@ void Instrumenter::addProfileExport() { // Create an export for the function wasm->addFunction(std::move(writeProfile)); wasm->addExport( - Builder::makeExport(options.profileExport, name, ExternalKind::Function)); + Builder::makeExport(config.profileExport, name, ExternalKind::Function)); // Export the memory if it is not already exported or imported. if (!wasm->memories[0]->imported()) { diff --git a/src/tools/wasm-split/instrumenter.h b/src/tools/wasm-split/instrumenter.h index 7de5a9135..f4f2f2119 100644 --- a/src/tools/wasm-split/instrumenter.h +++ b/src/tools/wasm-split/instrumenter.h @@ -23,6 +23,20 @@ namespace wasm { +struct InstrumenterConfig { + // The namespace from which to import the secondary memory + Name importNamespace = "env"; + // The name of the secondary memory created to store profile data during + // instrumentation + Name secondaryMemoryName = "profile-data"; + // Where to store the profile data + WasmSplitOptions::StorageKind storageKind = + WasmSplitOptions::StorageKind::InGlobals; + // The export name of the function the embedder calls to write the profile + // into memory + std::string profileExport = DEFAULT_PROFILE_EXPORT; +}; + // Add a global monotonic counter and a timestamp global for each function, code // at the beginning of each function to set its timestamp, and a new exported // function for dumping the profile data. @@ -30,20 +44,23 @@ struct Instrumenter : public Pass { PassRunner* runner = nullptr; Module* wasm = nullptr; - const WasmSplitOptions& options; + const InstrumenterConfig& config; uint64_t moduleHash; Name counterGlobal; std::vector<Name> functionGlobals; - Instrumenter(const WasmSplitOptions& options, uint64_t moduleHash); + Name secondaryMemory; + + Instrumenter(const InstrumenterConfig& config, uint64_t moduleHash); void run(PassRunner* runner, Module* wasm) override; private: - void addGlobals(); + void addGlobals(size_t numFuncs); + void addSecondaryMemory(size_t numFuncs); void instrumentFuncs(); - void addProfileExport(); + void addProfileExport(size_t numFuncs); }; } // namespace wasm diff --git a/src/tools/wasm-split/split-options.cpp b/src/tools/wasm-split/split-options.cpp index b8ccde83e..d077c70bc 100644 --- a/src/tools/wasm-split/split-options.cpp +++ b/src/tools/wasm-split/split-options.cpp @@ -185,10 +185,12 @@ WasmSplitOptions::WasmSplitOptions() [&](Options* o, const std::string& argument) { placeholderMap = true; }) .add("--import-namespace", "", - "The namespace from which to import objects from the primary " - "module into the secondary module.", + "When provided as an option for module splitting, the namespace from " + "which to import objects from the primary " + "module into the secondary module. In instrument mode, refers to the " + "namespace from which to import the secondary memory, if any.", WasmSplitOption, - {Mode::Split}, + {Mode::Split, Mode::Instrument}, Options::Arguments::One, [&](Options* o, const std::string& argument) { importNamespace = argument; @@ -246,6 +248,29 @@ WasmSplitOptions::WasmSplitOptions() storageKind = StorageKind::InMemory; }) .add( + "--in-secondary-memory", + "", + "Store profile information in a separate memory, rather than in module " + "main memory or globals (the default). With this option, users do not " + "need to reserve the initial memory region for profile data and the " + "data can be shared between multiple threads.", + WasmSplitOption, + {Mode::Instrument}, + Options::Arguments::Zero, + [&](Options* o, const std::string& argument) { + storageKind = StorageKind::InSecondaryMemory; + }) + .add("--secondary-memory-name", + "", + "The name of the secondary memory created to store profile " + "information.", + WasmSplitOption, + {Mode::Instrument}, + Options::Arguments::One, + [&](Options* o, const std::string& argument) { + secondaryMemoryName = argument; + }) + .add( "--emit-module-names", "", "Emit module names, even if not emitting the rest of the names section. " diff --git a/src/tools/wasm-split/split-options.h b/src/tools/wasm-split/split-options.h index 9f6253626..5aba0ba39 100644 --- a/src/tools/wasm-split/split-options.h +++ b/src/tools/wasm-split/split-options.h @@ -37,6 +37,7 @@ struct WasmSplitOptions : ToolOptions { enum class StorageKind : unsigned { InGlobals, // Store profile data in WebAssembly Globals InMemory, // Store profile data in memory, accessible from all threads + InSecondaryMemory, // Store profile data in memory separate from main memory }; StorageKind storageKind = StorageKind::InGlobals; @@ -63,6 +64,7 @@ struct WasmSplitOptions : ToolOptions { std::string importNamespace; std::string placeholderNamespace; + std::string secondaryMemoryName; std::string exportPrefix; // A hack to ensure the split and instrumented modules have the same table diff --git a/src/tools/wasm-split/wasm-split.cpp b/src/tools/wasm-split/wasm-split.cpp index 43db1b7d2..ec1f5555c 100644 --- a/src/tools/wasm-split/wasm-split.cpp +++ b/src/tools/wasm-split/wasm-split.cpp @@ -111,7 +111,16 @@ void instrumentModule(const WasmSplitOptions& options) { uint64_t moduleHash = hashFile(options.inputFiles[0]); PassRunner runner(&wasm, options.passOptions); - Instrumenter(options, moduleHash).run(&runner, &wasm); + InstrumenterConfig config; + if (options.importNamespace.size()) { + config.importNamespace = options.importNamespace; + } + if (options.secondaryMemoryName.size()) { + config.secondaryMemoryName = options.secondaryMemoryName; + } + config.storageKind = options.storageKind; + config.profileExport = options.profileExport; + Instrumenter(config, moduleHash).run(&runner, &wasm); adjustTableSize(wasm, options.initialTableSize); diff --git a/test/lit/help/wasm-split.test b/test/lit/help/wasm-split.test index 1994c681a..2e798b077 100644 --- a/test/lit/help/wasm-split.test +++ b/test/lit/help/wasm-split.test @@ -54,9 +54,13 @@ ;; CHECK-NEXT: --placeholdermap [split] Write a file mapping placeholder ;; CHECK-NEXT: indices to the function names. ;; CHECK-NEXT: -;; CHECK-NEXT: --import-namespace [split] The namespace from which to import -;; CHECK-NEXT: objects from the primary module into the -;; CHECK-NEXT: secondary module. +;; CHECK-NEXT: --import-namespace [split, instrument] When provided as an +;; CHECK-NEXT: option for module splitting, the namespace +;; CHECK-NEXT: from which to import objects from the +;; CHECK-NEXT: primary module into the secondary module. +;; CHECK-NEXT: In instrument mode, refers to the +;; CHECK-NEXT: namespace from which to import the +;; CHECK-NEXT: secondary memory, if any. ;; CHECK-NEXT: ;; CHECK-NEXT: --placeholder-namespace [split] The namespace from which to import ;; CHECK-NEXT: placeholder functions into the primary @@ -85,6 +89,18 @@ ;; CHECK-NEXT: does not use the initial memory region for ;; CHECK-NEXT: anything else. ;; CHECK-NEXT: +;; CHECK-NEXT: --in-secondary-memory [instrument] Store profile information in +;; CHECK-NEXT: a separate memory, rather than in module +;; CHECK-NEXT: main memory or globals (the default). With +;; CHECK-NEXT: this option, users do not need to reserve +;; CHECK-NEXT: the initial memory region for profile data +;; CHECK-NEXT: and the data can be shared between +;; CHECK-NEXT: multiple threads. +;; CHECK-NEXT: +;; CHECK-NEXT: --secondary-memory-name [instrument] The name of the secondary +;; CHECK-NEXT: memory created to store profile +;; CHECK-NEXT: information. +;; CHECK-NEXT: ;; CHECK-NEXT: --emit-module-names [split, instrument] Emit module names, ;; CHECK-NEXT: even if not emitting the rest of the names ;; CHECK-NEXT: section. Can help differentiate the diff --git a/test/lit/wasm-split/instrument-in-secondary-memory-custom-names.wast b/test/lit/wasm-split/instrument-in-secondary-memory-custom-names.wast new file mode 100644 index 000000000..9035a0b8b --- /dev/null +++ b/test/lit/wasm-split/instrument-in-secondary-memory-custom-names.wast @@ -0,0 +1,31 @@ +;; RUN: wasm-split %s --instrument --in-secondary-memory --import-namespace=custom_env --secondary-memory-name=custom_name -all -S -o - | filecheck %s + +;; Check that the output round trips and validates as well +;; RUN: wasm-split %s --instrument --in-secondary-memory -all -g -o %t.wasm +;; RUN: wasm-opt -all %t.wasm -S -o - + +(module + (import "env" "foo" (func $foo)) + (export "bar" (func $bar)) + (memory $0 1 1) + (func $bar + (call $foo) + ) + (func $baz (param i32) (result i32) + (local.get 0) + ) +) + +;; Check that a memory import has been added for secondary memory +;; CHECK: (import "custom_env" "custom_name" (memory $custom_name (shared 1 1))) + +;; And the profiling function exported +;; CHECK: (export "__write_profile" (func $__write_profile)) + +;; And main memory has been exported +;; CHECK: (export "profile-memory" (memory $0)) + +;; Check that the function instrumentation uses the correct memory name +;; CHECK: (i32.atomic.store8 $custom_name +;; CHECK: (i32.atomic.store8 $custom_name offset=1 +;; CHECK: (i32.atomic.load8_u $custom_name diff --git a/test/lit/wasm-split/instrument-in-secondary-memory.wast b/test/lit/wasm-split/instrument-in-secondary-memory.wast new file mode 100644 index 000000000..3e012293b --- /dev/null +++ b/test/lit/wasm-split/instrument-in-secondary-memory.wast @@ -0,0 +1,92 @@ +;; RUN: wasm-split %s --instrument --in-secondary-memory -all -S -o - | filecheck %s + +;; Check that the output round trips and validates as well +;; RUN: wasm-split %s --instrument --in-secondary-memory -all -g -o %t.wasm +;; RUN: wasm-opt -all %t.wasm -S -o - + +(module + (import "env" "foo" (func $foo)) + (export "bar" (func $bar)) + (memory $0 1 1) + (func $bar + (call $foo) + ) + (func $baz (param i32) (result i32) + (local.get 0) + ) +) + +;; Check that a memory import has been added for secondary memory +;; CHECK: (import "env" "profile-data" (memory $profile-data (shared 1 1))) + +;; And the profiling function exported +;; CHECK: (export "__write_profile" (func $__write_profile)) + +;; And main memory has been exported +;; CHECK: (export "profile-memory" (memory $0)) + +;; Check that the function instrumentation is correct + +;; CHECK: (func $bar +;; CHECK-NEXT: (i32.atomic.store8 $profile-data +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (call $foo) +;; CHECK-NEXT: ) + +;; CHECK-NEXT: (func $baz (param $0 i32) (result i32) +;; CHECK-NEXT: (i32.atomic.store8 $profile-data offset=1 +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) + +;; Check that the profiling function is correct. + +;; CHECK: (func $__write_profile (param $addr i32) (param $size i32) (result i32) +;; CHECK-NEXT: (local $funcIdx i32) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.ge_u +;; CHECK-NEXT: (local.get $size) +;; CHECK-NEXT: (i32.const 16) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (block +;; CHECK-NEXT: (i64.store $0 align=1 +;; CHECK-NEXT: (local.get $addr) +;; CHECK-NEXT: (i64.const {{.*}}) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (block $outer +;; CHECK-NEXT: (loop $l +;; CHECK-NEXT: (br_if $outer +;; CHECK-NEXT: (i32.eq +;; CHECK-NEXT: (local.get $funcIdx) +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.store $0 offset=8 +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $addr) +;; CHECK-NEXT: (i32.mul +;; CHECK-NEXT: (local.get $funcIdx) +;; CHECK-NEXT: (i32.const 4) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.atomic.load8_u $profile-data +;; CHECK-NEXT: (local.get $funcIdx) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $funcIdx +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (local.get $funcIdx) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (br $l) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.const 16) +;; CHECK-NEXT: ) diff --git a/test/lit/wasm-split/invalid-options.wast b/test/lit/wasm-split/invalid-options.wast index 2f9c0a148..c5a56b579 100644 --- a/test/lit/wasm-split/invalid-options.wast +++ b/test/lit/wasm-split/invalid-options.wast @@ -17,14 +17,9 @@ ;; RUN: not wasm-split %s --instrument --symbolmap 2>&1 \ ;; RUN: | filecheck %s --check-prefix INSTRUMENT-SYMBOLMAP -;; --instrument cannot be used with --import-namespace -;; RUN: not wasm-split %s --instrument --import-namespace=foo 2>&1 \ -;; RUN: | filecheck %s --check-prefix INSTRUMENT-IMPORT-NS - ;; --instrument cannot be used with --placeholder-namespace ;; RUN: not wasm-split %s --instrument --placeholder-namespace=foo 2>&1 \ ;; RUN: | filecheck %s --check-prefix INSTRUMENT-PLACEHOLDER-NS - ;; --instrument cannot be used with --export-prefix ;; RUN: not wasm-split %s --instrument --export-prefix=foo 2>&1 \ ;; RUN: | filecheck %s --check-prefix INSTRUMENT-EXPORT-PREFIX @@ -45,6 +40,10 @@ ;; RUN: not wasm-split %s --profile-export=foo 2>&1 \ ;; RUN: | filecheck %s --check-prefix SPLIT-PROFILE-EXPORT +;; --secondary-memory-name cannot be used with Split mode +;; RUN: not wasm-split %s --secondary-memory-name=foo 2>&1 \ +;; RUN: | filecheck %s --check-prefix SPLIT-SECONDARY-MEMORY-NAME + ;; -S cannot be used with --merge-profiles ;; RUN: not wasm-split %s --merge-profiles -S 2>&1 \ ;; RUN: | filecheck %s --check-prefix MERGE-EMIT-TEXT @@ -73,8 +72,6 @@ ;; INSTRUMENT-SYMBOLMAP: error: Option --symbolmap cannot be used in instrument mode. -;; INSTRUMENT-IMPORT-NS: error: Option --import-namespace cannot be used in instrument mode. - ;; INSTRUMENT-PLACEHOLDER-NS: error: Option --placeholder-namespace cannot be used in instrument mode. ;; INSTRUMENT-EXPORT-PREFIX: error: Option --export-prefix cannot be used in instrument mode. @@ -87,6 +84,8 @@ ;; SPLIT-PROFILE-EXPORT: error: Option --profile-export cannot be used in split mode. +;; SPLIT-SECONDARY-MEMORY-NAME: error: Option --secondary-memory-name cannot be used in split mode. + ;; MERGE-EMIT-TEXT: error: Option --emit-text cannot be used in merge-profiles mode. ;; MERGE-DEBUGINFO: error: Option --debuginfo cannot be used in merge-profiles mode. |