diff --git a/src/ir/type-updating.cpp b/src/ir/type-updating.cpp index 423e7cd9791..6e41fb24e60 100644 --- a/src/ir/type-updating.cpp +++ b/src/ir/type-updating.cpp @@ -498,11 +498,22 @@ Type GlobalTypeRewriter::getTempTupleType(Tuple tuple) { namespace TypeUpdating { -void handleNonDefaultableLocals(Function* func, Module& wasm) { +void handleNonDefaultableLocals(Function* func, + Module& wasm, + const std::unordered_set* forced) { if (!wasm.features.hasReferenceTypes()) { // No references, so no non-nullable ones at all. return; } + std::unordered_set badIndexes; + if (forced) { + for (auto index : *forced) { + assert(!func->isParam(index)); + if (!func->getLocalType(index).isDefaultable()) { + badIndexes.insert(index); + } + } + } bool hasNonNullable = false; for (auto varType : func->vars) { for (auto type : varType) { @@ -522,7 +533,6 @@ void handleNonDefaultableLocals(Function* func, Module& wasm) { // the wasm spec. We do not need to modify such locals. LocalStructuralDominance info( func, wasm, LocalStructuralDominance::NonNullableOnly); - std::unordered_set badIndexes; for (auto index : info.nonDominatingIndices) { badIndexes.insert(index); diff --git a/src/ir/type-updating.h b/src/ir/type-updating.h index 7aaf02a8234..ab4e59b9ecf 100644 --- a/src/ir/type-updating.h +++ b/src/ir/type-updating.h @@ -17,6 +17,8 @@ #ifndef wasm_ir_type_updating_h #define wasm_ir_type_updating_h +#include + #include "ir/branch-utils.h" #include "ir/module-utils.h" #include "support/insert_ordered.h" @@ -564,7 +566,13 @@ namespace TypeUpdating { // them. Atm this turns them into nullable ones, and adds ref.as_non_null on // their uses (which keeps the type of the users identical). // This may also handle other types of nondefaultable locals in the future. -void handleNonDefaultableLocals(Function* func, Module& wasm); +// +// If `forced` is provided, the locals at those indexes are made nullable as +// well, even if they validate as they are. +void handleNonDefaultableLocals( + Function* func, + Module& wasm, + const std::unordered_set* forced = nullptr); // Returns the type that a local should be, after handling of non- // defaultability. diff --git a/src/passes/Asyncify.cpp b/src/passes/Asyncify.cpp index 94a3261d78a..f3c83e8e266 100644 --- a/src/passes/Asyncify.cpp +++ b/src/passes/Asyncify.cpp @@ -342,6 +342,9 @@ // of their original range. // +#include +#include + #include "asmjs/shared-constants.h" #include "cfg/liveness-traversal.h" #include "ir/effects.h" @@ -351,10 +354,12 @@ #include "ir/memory-utils.h" #include "ir/module-utils.h" #include "ir/names.h" +#include "ir/type-updating.h" #include "ir/utils.h" #include "pass.h" #include "passes/pass-utils.h" #include "support/file.h" +#include "support/insert_ordered.h" #include "support/string.h" #include "wasm-builder.h" #include "wasm.h" @@ -387,6 +392,38 @@ enum class DataOffset { BStackPos = 0, BStackEnd = 4, BStackEnd64 = 8 }; const auto STACK_ALIGN = 4; +// Reference locals are spilled into one table per heap type/exactness, with a +// global holding the current slot position (like the Asyncify stack pointer). +// The saved region grows on unwind and shrinks on rewind (which clears slots). +// Initial size covers live ref locals of that type; recursion grows as needed, +// and overflow traps on the out-of-bounds table.set. +const uint32_t REF_TABLE_MAX = 1 << 26; + +static Name refName(Module& module, Type type, const char* prefix) { + std::string name = prefix; + auto heapType = type.getHeapType(); + if (auto it = module.typeNames.find(heapType); + it != module.typeNames.end() && it->second.name.is()) { + name += it->second.name.toString(); + } else { + // Basic types and unnamed types have no name in the module + name += heapType.toString(); + } + if (type.isExact()) { + // Exact references need their own tables + name += "_exact"; + } + return Name(name); +} + +static Name refTableName(Module& module, Type type) { + return refName(module, type, "__asyncify_ref_table_"); +} + +static Name refCursorName(Module& module, Type type) { + return refName(module, type, "__asyncify_ref_pos_"); +} + // A helper class for managing fake global names. Creates the globals and // provides mappings for using them. // Fake globals are used to stash and then use return values from calls. We need @@ -398,29 +435,44 @@ const auto STACK_ALIGN = 4; // i.e., we don't store to the target local if not running normally). class FakeGlobalHelper { Module& module; + std::unordered_set storageTypes; public: FakeGlobalHelper(Module& module) : module(module) { - Builder builder(module); - std::string prefix = "asyncify_fake_call_global_"; + // Store the types in their nullable versions so that makeZero can + // initialize the globals (non-defaultable types have no zero) for (auto type : collectTypes()) { - auto global = prefix + Type(type).toString(); - map[type] = global; - rev[global] = type; + storageTypes.insert(getStorageType(type)); + } + Builder builder(module); + for (auto type : storageTypes) { + auto name = getNameForStorageType(type); + rev[name] = type; module.addGlobal(builder.makeGlobal( - global, type, LiteralUtils::makeZero(type, module), Builder::Mutable)); + name, type, LiteralUtils::makeZero(type, module), Builder::Mutable)); } } ~FakeGlobalHelper() { - for (auto& pair : map) { - auto name = pair.second; - module.removeGlobal(name); + for (auto type : storageTypes) { + module.removeGlobal(getNameForStorageType(type)); } } - Name getName(Type type) { return map.at(type); } + // The name of the fake global used to stash a call result of this type + Name getName(Type type) { + return getNameForStorageType(getStorageType(type)); + } + + // The type a call result is stored as. + // Non-defaultable reference types are stored as their nullable versions, + // and readers cast back with ref.as_non_null + Type getStorageType(Type type) { + return TypeUpdating::getValidLocalType(type, module.features); + } + // The storage type of the fake global with this name, or none if this is not + // a fake global. Type getTypeOrNone(Name name) { auto iter = rev.find(name); if (iter != rev.end()) { @@ -430,9 +482,12 @@ class FakeGlobalHelper { } private: - std::unordered_map map; std::unordered_map rev; + Name getNameForStorageType(Type type) { + return Name(std::string("asyncify_fake_call_global_") + type.toString()); + } + // Collect the types returned from all calls for which call support globals // may need to be generated. using Types = std::unordered_set; @@ -872,6 +927,9 @@ static bool doesCall(Expression* curr) { return curr->is() || curr->is(); } +// Counts of spill slots per reference type +using RefCountMap = InsertOrderedMap; + class AsyncifyBuilder : public Builder { public: Module& wasm; @@ -882,6 +940,60 @@ class AsyncifyBuilder : public Builder { : Builder(wasm), wasm(wasm), pointerType(pointerType), asyncifyMemory(asyncifyMemory) {} + Name getRefTable(Type type) { return refTableName(wasm, type); } + + Expression* makeGetRefPos(Type type) { + return makeGlobalGet(refCursorName(wasm, type), pointerType); + } + + // The table index for the `offset`th spilled slot of this reference type + // within the current frame, i.e. the cursor global advanced by the slot + // offset. + Expression* makeRefIndex(Type type, Index offset) { + return makeBinary(Abstract::getBinary(pointerType, Abstract::Add), + makeGetRefPos(type), + makeConst(Literal::makeFromInt64(offset, pointerType))); + } + + // A spill slot is a GC root, so once a reference has been restored into its + // local the slot must be cleared to stop the table retaining the object. + Expression* makeClearRefSlot(Type type, Index offset) { + return makeTableSet(getRefTable(type), + makeRefIndex(type, offset), + LiteralUtils::makeZero(type, wasm)); + } + + Expression* makeIncRefPos(Type type, int32_t by) { + if (by == 0) { + return makeNop(); + } + return makeGlobalSet( + refCursorName(wasm, type), + makeBinary(Abstract::getBinary(pointerType, Abstract::Add), + makeGetRefPos(type), + makeConst(Literal::makeFromInt64(by, pointerType)))); + } + + // Ensure the table has room for `count` more slots at the cursor, so that + // recursive callers (live on the stack more than once) still fit. + Expression* makeEnsureRefCapacity(Type type, uint32_t count) { + if (count == 0) { + return makeNop(); + } + auto table = getRefTable(type); + // Each operand gets its own copy: the IR is a tree, so an Expression* + // cannot be shared between the condition and the grow. + auto deficit = makeBinary(Abstract::getBinary(pointerType, Abstract::Sub), + makeRefIndex(type, count), + makeTableSize(table)); + auto full = makeBinary(Abstract::getBinary(pointerType, Abstract::GtU), + makeRefIndex(type, count), + makeTableSize(table)); + return makeIf(full, + makeDrop(makeTableGrow( + table, LiteralUtils::makeZero(type, wasm), deficit))); + } + Expression* makeGetStackPos() { return makeLoad(pointerType.getByteSize(), false, @@ -1190,6 +1302,32 @@ struct AsyncifyFlow : public Pass { return builder->makeIf(builder->makeStateCheck(State::Normal), curr); } + // Reads back a fake call-result global, restoring the call result's type. + // The global stores a nullable version of that type, so non-nullable + // references need ref.as_non_null. + Expression* makeFakeGlobalGet(Name name, Type type) { + auto storageType = analyzer->fakeGlobals.getStorageType(type); + if (storageType == type) { + return builder->makeGlobalGet(name, type); + } + if (!type.isTuple()) { + return builder->makeRefAs(RefAsNonNull, + builder->makeGlobalGet(name, storageType)); + } + std::vector elems(type.size()); + for (Index i = 0, size = type.size(); i < size; ++i) { + // Each component needs its own global.get: the IR is a tree, so an + // Expression* cannot be shared. + Expression* elem = + builder->makeTupleExtract(builder->makeGlobalGet(name, storageType), i); + if (type[i].isNonNullableRef()) { + elem = builder->makeRefAs(RefAsNonNull, elem); + } + elems[i] = elem; + } + return builder->makeTupleMake(std::move(elems)); + } + Expression* makeCallSupport(Expression* curr) { // TODO: stop doing this after code can no longer reach a call that may // change the state @@ -1209,9 +1347,10 @@ struct AsyncifyFlow : public Pass { // AsyncifyLocals locals adds local saving/restoring. auto* set = curr->dynCast(); if (set) { - auto name = analyzer->fakeGlobals.getName(set->value->type); + auto type = set->value->type; + auto name = analyzer->fakeGlobals.getName(type); curr = builder->makeGlobalSet(name, set->value); - set->value = builder->makeGlobalGet(name, set->value->type); + set->value = makeFakeGlobalGet(name, type); } // Instrument the call itself (or, if a drop, the drop+call). auto index = callIndex++; @@ -1440,6 +1579,40 @@ struct AsyncifyAssertUnwindCorrectness : Pass { }; // Instrument local saving/restoring. +// TODO: look more precisely inside basic blocks +std::set computeRelevantLiveLocals(Function* func, Module* module) { + struct RelevantLiveLocalsWalker + : public LivenessWalker> { + // Basic blocks that have a possible unwind/rewind in them. + std::set relevantBasicBlocks; + + void visitCall(Call* curr) { + if (!currBasicBlock) { + return; + } + // Blocks where we might unwind/rewind, each marked by a call to + // ASYNCIFY_CHECK_CALL_INDEX right before the real call. + if (curr->target == ASYNCIFY_CHECK_CALL_INDEX) { + relevantBasicBlocks.insert(currBasicBlock); + } + } + }; + + RelevantLiveLocalsWalker walker; + walker.setFunction(func); + walker.walkFunctionInModule(func, module); + std::set relevant; + for (auto* block : walker.liveBlocks) { + if (walker.relevantBasicBlocks.contains(block)) { + for (auto local : block->contents.start) { + relevant.insert(local); + } + } + } + return relevant; +} + struct AsyncifyLocals : public WalkerPass> { bool isFunctionParallel() override { return true; } @@ -1513,6 +1686,10 @@ struct AsyncifyLocals : public WalkerPass> { // Find the locals that we actually need to load and save: any local that is // alive at a relevant call site must be handled, but others can be ignored. findRelevantLiveLocals(func); + // A spilled local may not be initialized before its first call, so make + // non-nullable ones nullable. Reads/writes are cast back with + // ref.as_non_null. + makeRelevantLocalsDefaultable(func); // The new function body has a prelude to load locals if rewinding, // then the actual main body, which does all its unwindings by breaking // to the unwind block, which then handles pushing the call index, as @@ -1565,66 +1742,135 @@ struct AsyncifyLocals : public WalkerPass> { std::set relevantLiveLocals; void findRelevantLiveLocals(Function* func) { - struct RelevantLiveLocalsWalker - : public LivenessWalker> { - // Basic blocks that have a possible unwind/rewind in them. - std::set relevantBasicBlocks; + relevantLiveLocals = computeRelevantLiveLocals(func, getModule()); + } - void visitCall(Call* curr) { - if (!currBasicBlock) { - return; - } - // Note blocks where we might unwind/rewind, all of which have a - // possible call to ASYNCIFY_CHECK_CALL_INDEX emitted right before the - // actual call. - // Note that each relevant original call was turned into a sequence of - // instructions, one of which is an if and then a call to this special - // intrinsic. We rely on the fact that if a local was live at the - // original call, it also would be in all that sequence of instructions, - // and in particular at the call we look for here (which is right before - // the call, and so anything that has its final use at the call is still - // live here). - if (curr->target == ASYNCIFY_CHECK_CALL_INDEX) { - relevantBasicBlocks.insert(currBasicBlock); - } + // Make the non-defaultable locals that we spill nullable + void makeRelevantLocalsDefaultable(Function* func) { + std::unordered_set forced; + for (auto index : relevantLiveLocals) { + if (!func->isParam(index) && !func->getLocalType(index).isDefaultable()) { + forced.insert(index); } - }; + } + if (!forced.empty()) { + TypeUpdating::handleNonDefaultableLocals(func, *getModule(), &forced); + } + } - RelevantLiveLocalsWalker walker; - walker.setFunction(func); - walker.walkFunctionInModule(func, getModule()); - // The relevant live locals are ones that are alive at an unwind/rewind - // location. TODO look more precisely inside basic blocks, as one might stop - // being live in the middle - for (auto* block : walker.liveBlocks) { - if (walker.relevantBasicBlocks.contains(block)) { - for (auto local : block->contents.start) { - relevantLiveLocals.insert(local); + // Next slot for a local: a table slot for reference components, a byte + // range in linear memory for everything else + struct SpillCursors { + AsyncifyBuilder& builder; + RefCountMap refOffsets; + Index memOffset = 0; + + SpillCursors(AsyncifyBuilder& builder) : builder(builder) {} + + // Table slot for the next reference + Expression* nextRef(Type type) { + return builder.makeRefIndex(type, refOffsets[type]++); + } + + // Byte offset of the next non-reference component + Index takeMem(Index size) { + auto offset = memOffset; + memOffset += size; + return offset; + } + }; + + // Reads one component back from its spill slot + Expression* + makeComponentLoading(Type type, Index tempIndex, SpillCursors& cursors) { + if (type.isRef()) { + // The table is nullable, so a non-nullable local needs a cast back. + auto storageType = type.with(Nullable); + Expression* load = + builder->makeTableGet(builder->getRefTable(storageType), + cursors.nextRef(storageType), + storageType); + if (type.isNonNullableRef()) { + load = builder->makeRefAs(RefAsNonNull, load); + } + return load; + } + auto size = getByteSize(type); + assert(size % STACK_ALIGN == 0); + // TODO: higher alignment? + return builder->makeLoad( + size, + true, + cursors.takeMem(size), + STACK_ALIGN, + builder->makeLocalGet(tempIndex, builder->pointerType), + type, + asyncifyMemory); + } + + // Writes one component to its spill slot + Expression* makeComponentSaving(Type type, + Expression* value, + Index tempIndex, + SpillCursors& cursors) { + if (type.isRef()) { + auto storageType = type.with(Nullable); + return builder->makeTableSet( + builder->getRefTable(storageType), cursors.nextRef(storageType), value); + } + auto size = getByteSize(type); + assert(size % STACK_ALIGN == 0); + // TODO: higher alignment? + return builder->makeStore( + size, + cursors.takeMem(size), + STACK_ALIGN, + builder->makeLocalGet(tempIndex, builder->pointerType), + value, + type, + asyncifyMemory); + } + + struct SpillLayout { + Index memTotal = 0; + RefCountMap refTotals; + }; + + SpillLayout computeSpillLayout() { + SpillLayout layout; + auto* func = getFunction(); + for (Index i = 0; i < func->getNumLocals(); i++) { + if (!relevantLiveLocals.contains(i)) { + continue; + } + for (const auto& type : func->getLocalType(i)) { + if (type.isRef()) { + layout.refTotals[type.with(Nullable)] += 1; + } else { + layout.memTotal += getByteSize(type); } } } + return layout; } Expression* makeLocalLoading() { if (relevantLiveLocals.empty()) { return builder->makeNop(); } + auto layout = computeSpillLayout(); auto* func = getFunction(); auto numLocals = func->getNumLocals(); - Index total = 0; - for (Index i = 0; i < numLocals; i++) { - if (!relevantLiveLocals.contains(i)) { - continue; - } - total += getByteSize(func->getLocalType(i)); - } auto* block = builder->makeBlock(); - block->list.push_back(builder->makeIncStackPos(-total)); + + block->list.push_back(builder->makeIncStackPos(-layout.memTotal)); + for (const auto& [type, count] : layout.refTotals) { + block->list.push_back(builder->makeIncRefPos(type, -int32_t(count))); + } auto tempIndex = builder->addVar(func, builder->pointerType); block->list.push_back( builder->makeLocalSet(tempIndex, builder->makeGetStackPos())); - Index offset = 0; + SpillCursors cursors(*builder); for (Index i = 0; i < numLocals; i++) { if (!relevantLiveLocals.contains(i)) { continue; @@ -1632,18 +1878,7 @@ struct AsyncifyLocals : public WalkerPass> { auto localType = func->getLocalType(i); SmallVector loads; for (const auto& type : localType) { - auto size = getByteSize(type); - assert(size % STACK_ALIGN == 0); - // TODO: higher alignment? - loads.push_back(builder->makeLoad( - size, - true, - offset, - STACK_ALIGN, - builder->makeLocalGet(tempIndex, builder->pointerType), - type, - asyncifyMemory)); - offset += size; + loads.push_back(makeComponentLoading(type, tempIndex, cursors)); } Expression* load; if (loads.size() == 1) { @@ -1655,6 +1890,13 @@ struct AsyncifyLocals : public WalkerPass> { } block->list.push_back(builder->makeLocalSet(i, load)); } + // Release the slots just restored, so the table does not keep the + // references alive for the life of the instance. + for (const auto& [type, count] : layout.refTotals) { + for (Index offset = 0; offset < count; offset++) { + block->list.push_back(builder->makeClearRefSlot(type, offset)); + } + } block->finalize(); return block; } @@ -1663,13 +1905,18 @@ struct AsyncifyLocals : public WalkerPass> { if (relevantLiveLocals.empty()) { return builder->makeNop(); } + auto layout = computeSpillLayout(); auto* func = getFunction(); auto numLocals = func->getNumLocals(); auto* block = builder->makeBlock(); auto tempIndex = builder->addVar(func, builder->pointerType); block->list.push_back( builder->makeLocalSet(tempIndex, builder->makeGetStackPos())); - Index offset = 0; + // Ensure room for this frame's reference spill before writing any of it + for (const auto& [type, total] : layout.refTotals) { + block->list.push_back(builder->makeEnsureRefCapacity(type, total)); + } + SpillCursors cursors(*builder); for (Index i = 0; i < numLocals; i++) { if (!relevantLiveLocals.contains(i)) { continue; @@ -1677,26 +1924,19 @@ struct AsyncifyLocals : public WalkerPass> { auto localType = func->getLocalType(i); size_t j = 0; for (const auto& type : localType) { - auto size = getByteSize(type); Expression* localGet = builder->makeLocalGet(i, localType); if (localType.size() > 1) { localGet = builder->makeTupleExtract(localGet, j); } - assert(size % STACK_ALIGN == 0); - // TODO: higher alignment? - block->list.push_back(builder->makeStore( - size, - offset, - STACK_ALIGN, - builder->makeLocalGet(tempIndex, builder->pointerType), - localGet, - type, - asyncifyMemory)); - offset += size; + block->list.push_back( + makeComponentSaving(type, localGet, tempIndex, cursors)); ++j; } } - block->list.push_back(builder->makeIncStackPos(offset)); + block->list.push_back(builder->makeIncStackPos(cursors.memOffset)); + for (const auto& [type, total] : cursors.refOffsets) { + block->list.push_back(builder->makeIncRefPos(type, int32_t(total))); + } block->finalize(); return block; } @@ -1714,10 +1954,12 @@ struct AsyncifyLocals : public WalkerPass> { builder->makeIncStackPos(4)); } + // Only called for non-reference types unsigned getByteSize(Type type) { if (!type.hasByteSize()) { - Fatal() << "Asyncify does not yet support non-number types, like " - "references (see " + Fatal() << "Asyncify cannot spill a value of type " << type + << " into the linear-memory spill stack, as it has no linear " + "memory representation (see " "https://github.com/WebAssembly/binaryen/issues/3739)"; } return type.getByteSize(); @@ -1894,16 +2136,22 @@ struct Asyncify : public Pass { runner.setValidateGlobally(false); runner.run(); } - // Next, add local saving/restoring logic. We optimize before doing this, - // to undo the extra code generated by flattening, and to arrive at the - // minimal amount of locals (which is important as we must save and - // restore those locals). We also and optimize after as well to simplify - // the code as much as possible. + // Optimize before adding local saving/restoring, to undo flattening and + // reach the minimal set of locals to save/restore. This must run before + // setting up the spill tables, since optimization can add/remove ref locals + // (e.g. heap2local) that the tables must match. + if (optimize) { + PassUtils::FilteredPassRunner runner(module, instrumentedFuncs); + runner.addDefaultFunctionOptimizationPasses(); + runner.setIsNested(true); + runner.setValidateGlobally(false); + runner.run(); + } + setupRefSpill(module, instrumentedFuncs); + // Next, add local saving/restoring logic. We optimize after as well to + // simplify the code as much as possible. { PassUtils::FilteredPassRunner runner(module, instrumentedFuncs); - if (optimize) { - runner.addDefaultFunctionOptimizationPasses(); - } runner.add(std::make_unique( &analyzer, pointerType, asyncifyMemory)); if (optimize) { @@ -1953,6 +2201,45 @@ struct Asyncify : public Pass { } } + // Reference locals have no linear-memory representation, so they are spilled + // into pass-created tables + void setupRefSpill(Module* module, + const PassUtils::FuncSet& instrumentedFuncs) { + Builder builder(*module); + RefCountMap counts; + // Iterate in module order + for (auto& func : module->functions) { + if (!instrumentedFuncs.contains(func.get()) || !func->body) { + continue; + } + for (auto local : computeRelevantLiveLocals(func.get(), module)) { + for (const auto& type : func->getLocalType(local)) { + if (type.isRef()) { + counts[type.with(Nullable)] += 1; + } + } + } + } + for (const auto& [type, count] : counts) { + if (count == 0) { + continue; + } + + auto table = Builder::makeTable(refTableName(*module, type), + type, // nullable table type + count, // initial size + REF_TABLE_MAX, // max size (growable) + pointerType, // address type + nullptr); // init: default null + module->addTable(std::move(table)); + module->addGlobal(builder.makeGlobal(refCursorName(*module, type), + pointerType, + builder.makeConst(pointerType), + Builder::Mutable)); + refTypes.insert(type); + } + } + void addFunctions(Module* module) { Builder builder(*module); auto makeFunction = [&](Name name, bool setData, State state) { @@ -1967,6 +2254,19 @@ struct Asyncify : public Pass { body->list.push_back(builder.makeGlobalSet( ASYNCIFY_DATA, builder.makeLocalGet(0, pointerType))); } + // Asyncify only supports one pause at a time. + if (name == ASYNCIFY_START_UNWIND) { + for (auto type : refTypes) { + body->list.push_back(builder.makeIf( + builder.makeBinary( + Abstract::getBinary(pointerType, Abstract::Ne), + builder.makeGlobalGet(refCursorName(*module, type), pointerType), + builder.makeConst(pointerType)), + builder.makeUnreachable())); + body->list.push_back(builder.makeGlobalSet( + refCursorName(*module, type), builder.makeConst(pointerType))); + } + } // Verify the data is valid. auto* stackPos = builder.makeLoad(pointerType.getByteSize(), @@ -2020,6 +2320,7 @@ struct Asyncify : public Pass { Type pointerType; Name asyncifyMemory; + InsertOrderedSet refTypes; }; Pass* createAsyncifyPass() { return new Asyncify(); } diff --git a/test/lit/passes/asyncify-reference-types-heap2local.wast b/test/lit/passes/asyncify-reference-types-heap2local.wast new file mode 100644 index 00000000000..0eadf721dcc --- /dev/null +++ b/test/lit/passes/asyncify-reference-types-heap2local.wast @@ -0,0 +1,292 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: wasm-opt %s --asyncify -O2 --all-features -S -o - | filecheck %s + +;; Optimizations run inside Asyncify can create new reference locals, so the +;; spill tables must be set up after them. Here Heap2Local unboxes the struct +;; into locals, and the externref field becomes a local that is spilled across +;; the call. +(module + ;; CHECK: (type $A (struct (field externref) (field i32))) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $2 (func (param i32))) + + ;; CHECK: (type $3 (func (param externref) (result i32))) + + ;; CHECK: (type $4 (func (result i32))) + + ;; CHECK: (import "env" "unwind" (func $unwind (type $1))) + (import "env" "unwind" (func $unwind)) + (memory 1) + (type $A (struct (field externref) (field i32))) + (func (export "run") (param $e externref) (result i32) + (local $a (ref $A)) + (local.set $a (struct.new $A (local.get $e) (i32.const 1))) + (call $unwind) + (ref.is_null (struct.get $A 0 (local.get $a))) + ) +) +;; CHECK: (global $__asyncify_state (mut i32) (i32.const 0)) + +;; CHECK: (global $__asyncify_data (mut i32) (i32.const 0)) + +;; CHECK: (global $__asyncify_ref_pos_A (mut i32) (i32.const 0)) + +;; CHECK: (memory $0 1) + +;; CHECK: (table $__asyncify_ref_table_A 1 67108864 (ref null $A)) + +;; CHECK: (export "run" (func $0)) + +;; CHECK: (export "asyncify_start_unwind" (func $asyncify_start_unwind)) + +;; CHECK: (export "asyncify_stop_unwind" (func $asyncify_stop_unwind)) + +;; CHECK: (export "asyncify_start_rewind" (func $asyncify_start_rewind)) + +;; CHECK: (export "asyncify_stop_rewind" (func $asyncify_stop_unwind)) + +;; CHECK: (export "asyncify_get_state" (func $asyncify_get_state)) + +;; CHECK: (func $0 (type $3) (param $0 externref) (result i32) +;; CHECK-NEXT: (local $1 (ref null $A)) +;; CHECK-NEXT: (local $2 i32) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.eq +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (global.set $__asyncify_ref_pos_A +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $1 +;; CHECK-NEXT: (table.get $__asyncify_ref_table_A +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (table.set $__asyncify_ref_table_A +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) +;; CHECK-NEXT: (ref.null none) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $2 +;; CHECK-NEXT: (block $__asyncify_unwind (result i32) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.eq +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (i32.store +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.const 4) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.set $2 +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (local.set $1 +;; CHECK-NEXT: (struct.new $A +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.or +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (call $unwind) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (br_if $__asyncify_unwind +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: (i32.eq +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.eqz +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (return +;; CHECK-NEXT: (ref.is_null +;; CHECK-NEXT: (struct.get $A 0 +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.store +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (local.get $2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.store +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.const 4) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (table.size $__asyncify_ref_table_A) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (drop +;; CHECK-NEXT: (table.grow $__asyncify_ref_table_A +;; CHECK-NEXT: (ref.null none) +;; CHECK-NEXT: (i32.sub +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (table.size $__asyncify_ref_table_A) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (table.set $__asyncify_ref_table_A +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) +;; CHECK-NEXT: (local.get $1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_ref_pos_A +;; CHECK-NEXT: (i32.add +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_start_unwind (type $2) (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_ref_pos_A +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_stop_unwind (type $1) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_start_rewind (type $2) (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_get_state (type $4) (result i32) +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: ) diff --git a/test/lit/passes/asyncify-reference-types.wast b/test/lit/passes/asyncify-reference-types.wast new file mode 100644 index 00000000000..ac5f09c3ffb --- /dev/null +++ b/test/lit/passes/asyncify-reference-types.wast @@ -0,0 +1,1672 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. + +;; RUN: foreach %s %t wasm-opt --asyncify --all-features -S -o - | filecheck %s + +;; Reference locals are spilled to one table per heap type. The initial size +;; covers the reference locals of that type that are live across calls, and the +;; table grows on unwind. +(module + ;; CHECK: (type $A (struct)) + + ;; CHECK: (type $1 (func)) + + ;; CHECK: (type $2 (func (result i32))) + + ;; CHECK: (type $3 (func (param i32))) + + ;; CHECK: (type $4 (func (result (ref func)))) + + ;; CHECK: (type $5 (func (param (ref func)))) + + ;; CHECK: (type $6 (func (param externref) (result i32))) + + ;; CHECK: (type $7 (func (param funcref) (result i32))) + + ;; CHECK: (type $8 (func (param (ref (exact $A)) (ref $A)) (result i32))) + + ;; CHECK: (import "env" "unwind" (func $unwind (type $1))) + (import "env" "unwind" (func $unwind)) + ;; CHECK: (import "env" "get_func" (func $get_func (type $4) (result (ref func)))) + (import "env" "get_func" (func $get_func (result (ref func)))) + ;; CHECK: (import "env" "use" (func $use (type $5) (param (ref func)))) + (import "env" "use" (func $use (param (ref func)))) + (type $A (struct)) + (memory 1) + ;; CHECK: (global $__asyncify_state (mut i32) (i32.const 0)) + + ;; CHECK: (global $__asyncify_data (mut i32) (i32.const 0)) + + ;; CHECK: (global $__asyncify_ref_pos_extern (mut i32) (i32.const 0)) + + ;; CHECK: (global $__asyncify_ref_pos_func (mut i32) (i32.const 0)) + + ;; CHECK: (global $__asyncify_ref_pos_A_exact (mut i32) (i32.const 0)) + + ;; CHECK: (global $__asyncify_ref_pos_A (mut i32) (i32.const 0)) + + ;; CHECK: (memory $0 1) + + ;; CHECK: (table $__asyncify_ref_table_extern 2 67108864 externref) + + ;; CHECK: (table $__asyncify_ref_table_func 4 67108864 funcref) + + ;; CHECK: (table $__asyncify_ref_table_A_exact 1 67108864 (ref null (exact $A))) + + ;; CHECK: (table $__asyncify_ref_table_A 1 67108864 (ref null $A)) + + ;; CHECK: (elem declare func $target) + + ;; CHECK: (export "reference-locals" (func $reference-locals)) + + ;; CHECK: (export "funcref-local" (func $funcref-local)) + + ;; CHECK: (export "call-result" (func $call-result)) + + ;; CHECK: (export "uninitialized-local" (func $uninitialized-local)) + + ;; CHECK: (export "exact" (func $exact)) + + ;; CHECK: (export "asyncify_start_unwind" (func $asyncify_start_unwind)) + + ;; CHECK: (export "asyncify_stop_unwind" (func $asyncify_stop_unwind)) + + ;; CHECK: (export "asyncify_start_rewind" (func $asyncify_start_rewind)) + + ;; CHECK: (export "asyncify_stop_rewind" (func $asyncify_stop_rewind)) + + ;; CHECK: (export "asyncify_get_state" (func $asyncify_get_state)) + + ;; CHECK: (func $target (type $1) + ;; CHECK-NEXT: ) + (func $target) + + ;; CHECK: (func $reference-locals (type $6) (param $e externref) (result i32) + ;; CHECK-NEXT: (local $e2 externref) + ;; CHECK-NEXT: (local $e3 externref) + ;; CHECK-NEXT: (local $3 externref) + ;; CHECK-NEXT: (local $4 externref) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 externref) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (local $10 i32) + ;; CHECK-NEXT: (local $11 i32) + ;; CHECK-NEXT: (local $12 i32) + ;; CHECK-NEXT: (local $13 i32) + ;; CHECK-NEXT: (local $14 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_extern + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const -2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $13 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $e2 + ;; CHECK-NEXT: (table.get $__asyncify_ref_table_extern + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $e3 + ;; CHECK-NEXT: (table.get $__asyncify_ref_table_extern + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_extern + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_extern + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $11 + ;; CHECK-NEXT: (block $__asyncify_unwind (result i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $12 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $e2 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $e3 + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $unwind) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__asyncify_unwind + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $e2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $e3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $14 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_extern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $__asyncify_ref_table_extern + ;; CHECK-NEXT: (ref.null noextern) + ;; CHECK-NEXT: (i32.sub + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_extern) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_extern + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $e2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_extern + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $e3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (local.get $14) + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_extern + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $reference-locals (export "reference-locals") + (param $e externref) + (result i32) + (local $e2 externref) + (local $e3 externref) + (local.set $e2 (local.get $e)) + (local.set $e3 (ref.null extern)) + (call $unwind) + (i32.and (ref.is_null (local.get $e2)) (ref.is_null (local.get $e3))) + ) + + ;; CHECK: (func $funcref-local (type $7) (param $f funcref) (result i32) + ;; CHECK-NEXT: (local $f2 funcref) + ;; CHECK-NEXT: (local $2 funcref) + ;; CHECK-NEXT: (local $3 funcref) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (local $10 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $f2 + ;; CHECK-NEXT: (table.get $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (block $__asyncify_unwind (result i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $f2 + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $unwind) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__asyncify_unwind + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (local.get $f2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $__asyncify_ref_table_func + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: (i32.sub + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $f2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $funcref-local (export "funcref-local") + (param $f funcref) + (result i32) + (local $f2 funcref) + (local.set $f2 (local.get $f)) + (call $unwind) + (ref.is_null (local.get $f2)) + ) + + ;; A non-nullable call result is stashed in a nullable fake global, so the + ;; read-back needs ref.as_non_null. + ;; CHECK: (func $call-result (type $2) (result i32) + ;; CHECK-NEXT: (local $0 funcref) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (local $2 i32) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 funcref) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (table.get $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (block $__asyncify_unwind (result i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (call $get_func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__asyncify_unwind + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $__asyncify_ref_table_func + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: (i32.sub + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $call-result (export "call-result") + (result i32) + (ref.is_null (call $get_func)) + ) + + ;; A non-nullable local may be live at a call before it is initialized. It is + ;; made nullable and spilled as null, and its uses are read back with + ;; ref.as_non_null. + ;; CHECK: (func $uninitialized-local (type $2) (result i32) + ;; CHECK-NEXT: (local $f funcref) + ;; CHECK-NEXT: (local $1 funcref) + ;; CHECK-NEXT: (local $2 (ref func)) + ;; CHECK-NEXT: (local $3 i32) + ;; CHECK-NEXT: (local $4 i32) + ;; CHECK-NEXT: (local $5 i32) + ;; CHECK-NEXT: (local $6 i32) + ;; CHECK-NEXT: (local $7 i32) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const -2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $f + ;; CHECK-NEXT: (table.get $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (table.get $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (block $__asyncify_unwind (result i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $unwind) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__asyncify_unwind + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $f + ;; CHECK-NEXT: (ref.func $target) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $use + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__asyncify_unwind + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $3 + ;; CHECK-NEXT: (ref.is_null + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $__asyncify_ref_table_func + ;; CHECK-NEXT: (ref.null nofunc) + ;; CHECK-NEXT: (i32.sub + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_func) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $f) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_func + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $uninitialized-local (export "uninitialized-local") + (result i32) + (local $f (ref func)) + (call $unwind) + (local.set $f (ref.func $target)) + (call $use (local.get $f)) + (ref.is_null (local.get $f)) + ) + + ;; Exact references need their own tables, since the table element type must + ;; be exact as well. + ;; CHECK: (func $exact (type $8) (param $exact (ref (exact $A))) (param $inexact (ref $A)) (result i32) + ;; CHECK-NEXT: (local $e (ref null (exact $A))) + ;; CHECK-NEXT: (local $i (ref null $A)) + ;; CHECK-NEXT: (local $4 (ref (exact $A))) + ;; CHECK-NEXT: (local $5 (ref $A)) + ;; CHECK-NEXT: (local $6 (ref (exact $A))) + ;; CHECK-NEXT: (local $7 (ref (exact $A))) + ;; CHECK-NEXT: (local $8 i32) + ;; CHECK-NEXT: (local $9 (ref $A)) + ;; CHECK-NEXT: (local $10 (ref $A)) + ;; CHECK-NEXT: (local $11 i32) + ;; CHECK-NEXT: (local $12 i32) + ;; CHECK-NEXT: (local $13 i32) + ;; CHECK-NEXT: (local $14 i32) + ;; CHECK-NEXT: (local $15 i32) + ;; CHECK-NEXT: (local $16 i32) + ;; CHECK-NEXT: (local $17 i32) + ;; CHECK-NEXT: (local $18 i32) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_A_exact + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A_exact) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_A + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) + ;; CHECK-NEXT: (i32.const -1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $17 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $e + ;; CHECK-NEXT: (table.get $__asyncify_ref_table_A_exact + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A_exact) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (table.get $__asyncify_ref_table_A + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $13 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (local.get $17) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_A_exact + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A_exact) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_A + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $15 + ;; CHECK-NEXT: (block $__asyncify_unwind (result i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const -4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $16 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $4 + ;; CHECK-NEXT: (local.get $exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $e + ;; CHECK-NEXT: (local.get $4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $5 + ;; CHECK-NEXT: (local.get $inexact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $i + ;; CHECK-NEXT: (local.get $5) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.or + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (local.get $16) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $unwind) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (br $__asyncify_unwind + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $6 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $7 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $8 + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $6) + ;; CHECK-NEXT: (local.get $7) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $9 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $10 + ;; CHECK-NEXT: (ref.as_non_null + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $11 + ;; CHECK-NEXT: (ref.eq + ;; CHECK-NEXT: (local.get $9) + ;; CHECK-NEXT: (local.get $10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $12 + ;; CHECK-NEXT: (i32.and + ;; CHECK-NEXT: (local.get $8) + ;; CHECK-NEXT: (local.get $11) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $13 + ;; CHECK-NEXT: (local.get $12) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.eq + ;; CHECK-NEXT: (global.get $__asyncify_state) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (local.set $14 + ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (local.get $14) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (nop) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $15) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $18 + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A_exact) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_A_exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $__asyncify_ref_table_A_exact + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.sub + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A_exact) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_A_exact) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.gt_u + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (table.grow $__asyncify_ref_table_A + ;; CHECK-NEXT: (ref.null none) + ;; CHECK-NEXT: (i32.sub + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.size $__asyncify_ref_table_A) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_A_exact + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A_exact) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $e) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (table.set $__asyncify_ref_table_A + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $i) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (local.get $18) + ;; CHECK-NEXT: (local.get $13) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.store + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (i32.load + ;; CHECK-NEXT: (global.get $__asyncify_data) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 4) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_A_exact + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A_exact) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (global.set $__asyncify_ref_pos_A + ;; CHECK-NEXT: (i32.add + ;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + (func $exact (export "exact") + (param $exact (ref (exact $A))) + (param $inexact (ref $A)) + (result i32) + (local $e (ref (exact $A))) + (local $i (ref $A)) + (local.set $e (local.get $exact)) + (local.set $i (local.get $inexact)) + (call $unwind) + (i32.and + (ref.eq (local.get $e) (local.get $e)) + (ref.eq (local.get $i) (local.get $i))) + ) +) +;; CHECK: (func $asyncify_start_unwind (type $3) (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 1) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.ne +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_extern) +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_ref_pos_extern +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.ne +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_func) +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_ref_pos_func +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.ne +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A_exact) +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_ref_pos_A_exact +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.ne +;; CHECK-NEXT: (global.get $__asyncify_ref_pos_A) +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_ref_pos_A +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_stop_unwind (type $1) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_start_rewind (type $3) (param $0 i32) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 2) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (global.set $__asyncify_data +;; CHECK-NEXT: (local.get $0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_stop_rewind (type $1) +;; CHECK-NEXT: (global.set $__asyncify_state +;; CHECK-NEXT: (i32.const 0) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (if +;; CHECK-NEXT: (i32.gt_u +;; CHECK-NEXT: (i32.load +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (i32.load offset=4 +;; CHECK-NEXT: (global.get $__asyncify_data) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: (then +;; CHECK-NEXT: (unreachable) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) +;; CHECK-NEXT: ) + +;; CHECK: (func $asyncify_get_state (type $2) (result i32) +;; CHECK-NEXT: (global.get $__asyncify_state) +;; CHECK-NEXT: )