diff --git a/src/passes/MakeSharedObjects.cpp b/src/passes/MakeSharedObjects.cpp index 8a52fb79948..e1242a3b24b 100644 --- a/src/passes/MakeSharedObjects.cpp +++ b/src/passes/MakeSharedObjects.cpp @@ -79,6 +79,8 @@ struct LazyTable { // The names of conversion functions, if ever used. Name refToIndexName; Name indexToRefName; + // Initial elements used to populate the table via an active element segment. + std::vector init; LazyTable(Name base, Type type) : base(base), type(type) {} @@ -190,16 +192,16 @@ struct LazyTable { wasm->addFunction(std::move(func)); } - // Returns true if the table was added. (If it is never used, it will not be - // added.) - bool maybeAdd(Address initial, Address max) { + // Add the table, conversion functions, and active element segment if the + // table was ever used. + void maybeAdd(Address max) { assert(wasm); if (!name) { - return false; + return; } Table* table = wasm->addTable(Builder::makeTable(name)); table->type = type; - table->initial = initial; + table->initial = init.size(); table->max = max; if (refToIndexName) { addRefToIndexFunction(); @@ -207,32 +209,40 @@ struct LazyTable { if (indexToRefName) { addIndexToRefFunction(); } - return true; + if (!init.empty()) { + Builder builder(*wasm); + Name segName = Names::getValidElementSegmentName(*wasm, base); + auto* offset = builder.makeConst(Literal(int32_t(0))); + auto* segment = wasm->addElementSegment( + Builder::makeElementSegment(segName, name, offset)); + segment->type = type; + segment->data = std::move(init); + } } - bool isTableType(Type t) const { - return t.isRef() && Type::isSubType(t, type); + bool canHold(Type t) const { + return t.isRef() && !t.isNull() && Type::isSubType(t, type); } - bool hasTableType(Type t) const { + bool hasHoldable(Type t) const { if (t.isTuple()) { for (Type elem : t) { - if (hasTableType(elem)) { + if (hasHoldable(elem)) { return true; } } return false; } - return isTableType(t); + return canHold(t); } bool funcHasTableType(Function* func) const { Signature sig = func->type.getHeapType().getSignature(); - return hasTableType(sig.params) || hasTableType(sig.results); + return hasHoldable(sig.params) || hasHoldable(sig.results); } Expression* convertToRef(Expression* arg, Type origType) { - if (!isTableType(origType)) { + if (!canHold(origType)) { return arg; } Builder builder(*wasm); @@ -244,7 +254,7 @@ struct LazyTable { } Expression* convertToIndex(Expression* arg, Type origType, Type targetType) { - if (!isTableType(origType)) { + if (!canHold(origType)) { return arg; } Builder builder(*wasm); @@ -264,10 +274,16 @@ struct MakeSharedObjects Type funcref = Type(HeapTypes::func, Nullable); Type externref = Type(HeapTypes::ext, Nullable); + // Tables storing unshared funcrefs and externrefs that are lowered to shared + // i31ref indices inside the module. LazyTable funcTable{"funcs", funcref}; LazyTable externTable{"externs", externref}; - std::vector funcs; + + // Map from function name to index in `funcTable`. std::unordered_map funcIndices; + + // Imported helper functions used to lower `extern.convert_any` and + // `any.convert_extern`, if needed. Name anyToExternName; Name externToAnyName; @@ -287,10 +303,11 @@ struct MakeSharedObjects return externToAnyName; } - Index getIndex(Name func) { - auto [it, inserted] = funcIndices.insert({func, funcs.size()}); + Index getIndex(RefFunc* curr) { + auto [it, inserted] = + funcIndices.insert({curr->func, funcTable.init.size()}); if (inserted) { - funcs.push_back(func); + funcTable.init.push_back(curr); } return it->second; } @@ -342,7 +359,7 @@ struct MakeSharedObjects std::vector params; Index i = 0; for (Type param : origSig.params) { - if (externTable.isTableType(param)) { + if (externTable.canHold(param)) { params.push_back(param); } else { params.push_back(rewrittenSig.params[i]); @@ -353,7 +370,7 @@ struct MakeSharedObjects std::vector results; Index j = 0; for (Type result : origSig.results) { - if (externTable.isTableType(result)) { + if (externTable.canHold(result)) { results.push_back(result); } else { results.push_back(rewrittenSig.results[j]); @@ -404,7 +421,7 @@ struct MakeSharedObjects auto* call = builder.makeCall(importName, callArgs, extResults); // Convert externrefs received as results to indices. - if (!externTable.hasTableType(extResults)) { + if (!externTable.hasHoldable(extResults)) { func->body = call; } else if (extResults.isSingle()) { Type targetType = func->getResults(); @@ -462,7 +479,7 @@ struct MakeSharedObjects // Forward results, converting indices to externrefs. Type extResults = boundarySig.results; - if (!externTable.hasTableType(extResults)) { + if (!externTable.hasHoldable(extResults)) { exportWrapper->body = call; } else if (extResults.isSingle()) { exportWrapper->body = externTable.convertToRef(call, extResults); @@ -488,8 +505,8 @@ struct MakeSharedObjects void visitRefFunc(RefFunc* curr) { Builder builder(*getModule()); - replaceCurrent(builder.makeRefI31( - builder.makeConst(Literal(getIndex(curr->func))), Shared)); + replaceCurrent( + builder.makeRefI31(builder.makeConst(Literal(getIndex(curr))), Shared)); } std::unordered_map callRefTypes; @@ -718,7 +735,54 @@ struct MakeSharedObjects void visitElementSegment(ElementSegment* curr) { updateType(curr->type); } - void visitGlobal(Global* curr) { updateType(curr->type); } + // Create a new imported global with the original externref type and add it + // to the externref table at instantiation time. The original global ceases to + // be an import and is initialized to the shared i31 table index so internal + // uses of the global receive the table index. + void wrapGlobalImport(Global* global, Type origType) { + // Mutable imported/exported externref globals are not supported because all + // accesses to them would have to be rewritten to be function calls that + // accessed the externref table. We cannot update such accesses outside this + // module. + if (global->mutable_ == Mutable) { + Fatal() << "Cannot wrap mutable global " << global->name; + } + Builder builder(*getModule()); + Name origName = global->name; + Name importName = + Names::getValidGlobalName(*getModule(), origName.toString() + "$import"); + + auto importGlobal = std::make_unique(); + importGlobal->name = importName; + importGlobal->module = global->module; + importGlobal->base = global->base; + importGlobal->type = origType; + importGlobal->mutable_ = false; + importGlobal->hasExplicitName = true; + + Index index = externTable.init.size(); + externTable.init.push_back(builder.makeGlobalGet(importName, origType)); + + global->module = Name(); + global->base = Name(); + global->init = + builder.makeRefI31(builder.makeConst(Literal(int32_t(index))), Shared); + + getModule()->addGlobal(std::move(importGlobal)); + } + + struct GlobalImportToWrap { + Global* global; + Type origType; + }; + std::vector globalImportsToWrap; + + void visitGlobal(Global* curr) { + if (curr->imported() && externTable.canHold(curr->type)) { + globalImportsToWrap.push_back({curr, curr->type}); + } + updateType(curr->type); + } void doWalkModule(Module* wasm) { funcTable.wasm = wasm; @@ -727,7 +791,7 @@ struct MakeSharedObjects WalkerPass::doWalkModule(wasm); } - struct ImportToWrap { + struct FuncImportToWrap { Function* func; Type origType; }; @@ -738,7 +802,7 @@ struct MakeSharedObjects }; void visitModule(Module* wasm) { - std::vector importsToWrap; + std::vector importsToWrap; for (auto& func : wasm->functions) { if (func->imported() && externTable.funcHasTableType(func.get())) { importsToWrap.push_back({func.get(), func->type}); @@ -759,6 +823,9 @@ struct MakeSharedObjects rewriteTypes(); + for (auto& info : globalImportsToWrap) { + wrapGlobalImport(info.global, info.origType); + } for (auto& info : importsToWrap) { wrapImport(info.func, info.origType); } @@ -773,9 +840,10 @@ struct MakeSharedObjects } } - addFunctionTable(); + funcTable.maybeAdd(funcTable.init.size()); addExternTable(); ReFinalize().run(getPassRunner(), wasm); + ReFinalize().walkModuleCode(wasm); } void rewriteTypes() { @@ -822,27 +890,6 @@ struct MakeSharedObjects rewriter.mapTypeNamesAndIndices(oldToNew); } - void addFunctionTable() { - if (!funcTable.maybeAdd(funcs.size(), funcs.size())) { - return; - } - if (funcs.empty()) { - return; - } - Builder builder(*getModule()); - Name segName = Names::getValidElementSegmentName(*getModule(), "funcs"); - auto* offset = builder.makeConst(Literal(int32_t(0))); - auto* segment = getModule()->addElementSegment( - Builder::makeElementSegment(segName, funcTable.getName(), offset)); - segment->type = funcTable.type; - - segment->data.reserve(funcs.size()); - for (auto func : funcs) { - segment->data.push_back( - builder.makeRefFunc(func, getModule()->getFunction(func)->type)); - } - } - void addAnyToExternFunction() { Type sharedAnyNullable = Type(HeapTypes::any.getBasic(Shared), Nullable); Type externrefNullable = Type(HeapTypes::ext, Nullable); @@ -866,7 +913,7 @@ struct MakeSharedObjects } void addExternTable() { - externTable.maybeAdd(0, Table::kUnlimitedSize); + externTable.maybeAdd(Table::kUnlimitedSize); if (anyToExternName) { addAnyToExternFunction(); } diff --git a/test/lit/passes/make-shared-objects.wast b/test/lit/passes/make-shared-objects.wast index f9713a4b4c3..a51a20cd5ff 100644 --- a/test/lit/passes/make-shared-objects.wast +++ b/test/lit/passes/make-shared-objects.wast @@ -361,18 +361,20 @@ ;; CHECK: (type $shared-sig (func (param i32) (result i32))) (type $shared-sig (shared (func (param i32) (result i32)))) - ;; CHECK: (type $2 (func)) - ;; CHECK: (type $struct (shared (struct (field (ref null (shared i31)))))) (type $struct (struct (field (ref null func)))) (func $effect (import "" "") (result i32)) + ;; CHECK: (type $3 (func)) + ;; CHECK: (type $4 (func (param (ref (shared i31)) i32) (result i32))) - ;; CHECK: (type $5 (func (param (ref (shared i31))) (result i32))) + ;; CHECK: (type $5 (func (param (ref $struct)))) + + ;; CHECK: (type $6 (func (param (ref (shared i31))) (result i32))) - ;; CHECK: (type $6 (func (param i32 i32) (result i32))) + ;; CHECK: (type $7 (func (param i32 i32) (result i32))) ;; CHECK: (import "" "" (func $effect (type $0) (result i32))) @@ -407,16 +409,16 @@ ;; CHECK: (elem $funcs (table $funcs) (i32.const 0) func $a $b $d $c) - ;; CHECK: (func $a (type $2) + ;; CHECK: (func $a (type $5) (param $0 (ref $struct)) ;; CHECK-NEXT: ) - (func $a) - ;; CHECK: (func $b (type $2) + (func $a (param (ref $struct))) + ;; CHECK: (func $b (type $3) ;; CHECK-NEXT: ) (func $b) - ;; CHECK: (func $c (type $2) + ;; CHECK: (func $c (type $3) ;; CHECK-NEXT: ) (func $c) - ;; CHECK: (func $d (type $2) + ;; CHECK: (func $d (type $3) ;; CHECK-NEXT: ) (func $d) @@ -468,7 +470,7 @@ (call_ref $sig (call $effect) (ref.null nofunc)) ) - ;; CHECK: (func $shared-call-ref (type $5) (param $0 (ref (shared i31))) (result i32) + ;; CHECK: (func $shared-call-ref (type $6) (param $0 (ref (shared i31))) (result i32) ;; CHECK-NEXT: (call_indirect $funcs (type $shared-sig) ;; CHECK-NEXT: (i32.const 42) ;; CHECK-NEXT: (i31.get_u @@ -529,7 +531,7 @@ ) ;; call_indirect lowering - ;; CHECK: (func $call-indirect (type $6) (param $0 i32) (param $1 i32) (result i32) + ;; CHECK: (func $call-indirect (type $7) (param $0 i32) (param $1 i32) (result i32) ;; CHECK-NEXT: (call_indirect $funcs (type $shared-sig) ;; CHECK-NEXT: (local.get $0) ;; CHECK-NEXT: (i31.get_u @@ -2418,6 +2420,7 @@ (call $im_mixed (local.get 0) (local.get 1)) ) ) + ;; CHECK: (func $test_mixed$export (type $2) (param $0 (ref $struct)) (param $1 externref) (result (ref $struct) externref) ;; CHECK-NEXT: (local $2 (tuple (ref $struct) (ref null (shared i31)))) ;; CHECK-NEXT: (local.set $2 @@ -2490,3 +2493,193 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) +(module + ;; Imported externref globals should remain externref imports, be inserted + ;; into the externs table at instantiation, and have internal uses replaced + ;; with i31 globals referring to their table indices. + ;; CHECK: (type $struct (shared (struct (field (ref null (shared i31))) (field (ref (shared i31)))))) + (type $struct (struct (field externref) (field (ref extern)))) + (import "env" "g_nullable" (global $g_nullable externref)) + (import "env" "g_non_null" (global $g_non_null (ref extern))) + + ;; CHECK: (type $1 (func (result externref (ref extern)))) + + ;; CHECK: (type $2 (func (result (ref null (shared i31)) (ref (shared i31))))) + + ;; CHECK: (type $3 (func (param (ref null (shared i31))) (result externref))) + + ;; CHECK: (import "env" "g_nullable" (global $g_nullable$import externref)) + + ;; CHECK: (import "env" "g_non_null" (global $g_non_null$import (ref extern))) + + ;; CHECK: (global $g_nullable (ref null (shared i31)) (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $g_non_null (ref (shared i31)) (ref.i31_shared + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $user (ref $struct) (struct.new $struct + ;; CHECK-NEXT: (global.get $g_nullable) + ;; CHECK-NEXT: (global.get $g_non_null) + ;; CHECK-NEXT: )) + (global $user (ref $struct) (struct.new $struct + (global.get $g_nullable) + (global.get $g_non_null) + )) + + ;; CHECK: (table $externs 2 externref) + + ;; CHECK: (elem $externs (table $externs) (i32.const 0) externref (item (global.get $g_nullable$import)) (item (global.get $g_non_null$import))) + + ;; CHECK: (export "get-globals" (func $get-globals$export)) + + ;; CHECK: (func $get-globals (type $2) (result (ref null (shared i31)) (ref (shared i31))) + ;; CHECK-NEXT: (tuple.make 2 + ;; CHECK-NEXT: (global.get $g_nullable) + ;; CHECK-NEXT: (global.get $g_non_null) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $get-globals (export "get-globals") (result externref (ref extern)) + ;; Read both imported externref globals inside an exported function. + (tuple.make 2 + (global.get $g_nullable) + (global.get $g_non_null) + ) + ) +) + +;; CHECK: (func $get-globals$export (type $1) (result externref (ref extern)) +;; CHECK-NEXT: (local $0 (tuple (ref null (shared i31)) (ref (shared i31)))) +;; CHECK-NEXT: (local.set $0 +;; CHECK-NEXT: (call $get-globals) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (tuple.make 2 +;; CHECK-NEXT: (call $index_to_extern +;; CHECK-NEXT: (tuple.extract 2 0 +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (ref.cast (ref extern) +;; CHECK-NEXT: (call $index_to_extern +;; CHECK-NEXT: (tuple.extract 2 1 +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $index_to_extern (type $3) (param $0 (ref null (shared i31))) (result externref) +;; CHECK-NEXT: (if (result externref) +;; CHECK-NEXT: (ref.is_null +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (ref.null noextern) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (else +;; CHECK-NEXT: (table.get $externs +;; CHECK-NEXT: (i31.get_u +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +(module + ;; If imported externref globals are never used in a way that requires the + ;; externs table, they still get table indices, but no table is emitted. + (import "env" "g_nullable" (global $g_nullable externref)) + (import "env" "g_non_null" (global $g_non_null (ref extern))) +) + +;; CHECK: (import "env" "g_nullable" (global $g_nullable$import externref)) + +;; CHECK: (import "env" "g_non_null" (global $g_non_null$import (ref extern))) + +;; CHECK: (global $g_nullable (ref null (shared i31)) (ref.i31_shared +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: )) + +;; CHECK: (global $g_non_null (ref (shared i31)) (ref.i31_shared +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: )) +(module + ;; Imported string constants should remain externref/stringref imports, be + ;; inserted into the externs table, and be converted back when passed to + ;; string builtins. + (import "'" "hello" (global $hello (ref extern))) + (import "'" "world" (global $world (ref string))) + (import "wasm:js-string" "equals" (func $equals (param externref externref) (result i32))) + + ;; CHECK: (type $0 (func (param (ref null (shared i31)) (ref null (shared i31))) (result i32))) + + ;; CHECK: (type $1 (func (result i32))) + + ;; CHECK: (type $2 (func (param externref externref) (result i32))) + + ;; CHECK: (type $3 (func (param (ref null (shared i31))) (result externref))) + + ;; CHECK: (import "\'" "hello" (global $hello$import (ref extern))) + + ;; CHECK: (import "\'" "world" (global $world$import (ref string))) + + ;; CHECK: (import "wasm:js-string" "equals" (func $equals$import (type $2) (param externref externref) (result i32))) + + ;; CHECK: (global $hello (ref (shared i31)) (ref.i31_shared + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $world (ref (shared i31)) (ref.i31_shared + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: )) + + ;; CHECK: (table $externs 2 externref) + + ;; CHECK: (elem $externs (table $externs) (i32.const 0) externref (item (global.get $hello$import)) (item (global.get $world$import))) + + ;; CHECK: (func $equals (type $0) (param $0 (ref null (shared i31))) (param $1 (ref null (shared i31))) (result i32) + ;; CHECK-NEXT: (call $equals$import + ;; CHECK-NEXT: (call $index_to_extern + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $index_to_extern + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + + ;; CHECK: (func $compare-strings (type $1) (result i32) + ;; CHECK-NEXT: (call $equals + ;; CHECK-NEXT: (global.get $hello) + ;; CHECK-NEXT: (global.get $world) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $compare-strings (result i32) + ;; Compare two imported string constants using wasm:js-string equals. + (call $equals + (global.get $hello) + (global.get $world) + ) + ) +) + +;; CHECK: (func $index_to_extern (type $3) (param $0 (ref null (shared i31))) (result externref) +;; CHECK-NEXT: (if (result externref) +;; CHECK-NEXT: (ref.is_null +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (ref.null noextern) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (else +;; CHECK-NEXT: (table.get $externs +;; CHECK-NEXT: (i31.get_u +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: )