Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 79 additions & 3 deletions src/passes/MakeSharedObjects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
// references.

#include "ir/drop.h"
#include "ir/eh-utils.h"
#include "ir/module-utils.h"
#include "ir/names.h"
#include "ir/type-updating.h"
Expand Down Expand Up @@ -510,8 +511,16 @@ struct MakeSharedObjects
}

std::unordered_map<CallRef*, HeapType> callRefTypes;
bool hasBlockNestedPop = false;

void walkFunction(Function* curr) {
hasBlockNestedPop = false;
// Update local types before walking the body so that any scratch locals
// added during the walk with original boundary types (e.g. externref) are
// not overwritten afterward.
for (auto& type : curr->vars) {
updateType(type);
}
// When we replace call_ref target operands with i31s, we will lose
// information we need about the intended call target type. Collect those
// types up front before we update anything.
Expand All @@ -528,6 +537,9 @@ struct MakeSharedObjects
CallRefCollector collector(callRefTypes);
collector.walk(curr->body);
WalkerPass::walkFunction(curr);
if (hasBlockNestedPop) {
EHUtils::handleBlockNestedPops(curr, *getModule());
}
}

void visitCallRef(CallRef* curr) {
Expand Down Expand Up @@ -725,10 +737,56 @@ struct MakeSharedObjects
#include "wasm-delegations-fields.def"
}

void visitFunction(Function* curr) {
for (auto& type : curr->vars) {
updateType(type);
// Compute the boundary parameter types for a tag, keeping externref
// parameters unchanged while lowering other parameter types.
Type getTagBoundaryParams(Type origParams) {
std::vector<Type> params;
for (Type t : origParams) {
params.push_back(externTable.isTableType(t) ? t : updatedType(t));
}
return Type(params);
}

// Convert lowered i31ref operands back to externrefs when throwing a tag
// that expects externref parameters.
void visitThrow(Throw* curr) {
visitExpression(curr);
Type origParams = getModule()->getTag(curr->tag)->params();
for (Index i = 0; i < curr->operands.size(); ++i) {
curr->operands[i] =
externTable.convertToRef(curr->operands[i], origParams[i]);
}
}

// Wrap pops with externref types in conversions from the caught externrefs
// to i31ref table indices.
void visitPop(Pop* curr) {
if (!externTable.hasTableType(curr->type)) {
visitExpression(curr);
return;
}
Type origParams = curr->type;
Type internalParams = updatedType(origParams);
Type boundaryParams = getTagBoundaryParams(origParams);
curr->type = boundaryParams;
if (origParams.isSingle()) {
replaceCurrent(
externTable.convertToIndex(curr, origParams, internalParams));
return;
}
Builder builder(*getModule());
Index scratch = Builder::addVar(getFunction(), boundaryParams);
auto* set = builder.makeLocalSet(scratch, curr);
std::vector<Expression*> elems;
elems.reserve(origParams.size());
for (Index i = 0; i < origParams.size(); ++i) {
auto* extract = builder.makeTupleExtract(
builder.makeLocalGet(scratch, boundaryParams), i);
elems.push_back(
externTable.convertToIndex(extract, origParams[i], internalParams[i]));
}
replaceCurrent(builder.makeSequence(set, builder.makeTupleMake(elems)));
hasBlockNestedPop = true;
}

void visitTable(Table* curr) { updateType(curr->type); }
Expand Down Expand Up @@ -794,6 +852,11 @@ struct MakeSharedObjects
Type origType;
};

struct TagToUpdate {
Tag* tag;
HeapType origType;
};

void visitModule(Module* wasm) {
std::vector<ImportToWrap> importsToWrap;
for (auto& func : wasm->functions) {
Expand All @@ -814,8 +877,21 @@ struct MakeSharedObjects
}
}

std::vector<TagToUpdate> tagsToUpdate;
for (auto& tag : wasm->tags) {
if (externTable.hasTableType(tag->params())) {
tagsToUpdate.push_back({tag.get(), tag->type});
}
}

rewriteTypes();

for (auto& [tag, origType] : tagsToUpdate) {
tag->type = getBoundaryType(Type(origType, NonNullable),
Type(tag->type, NonNullable))
.getHeapType();
}

for (auto& info : globalImportsToWrap) {
wrapGlobalImport(info.global, info.origType);
}
Expand Down
266 changes: 266 additions & 0 deletions test/lit/passes/make-shared-objects.wast
Original file line number Diff line number Diff line change
Expand Up @@ -2683,3 +2683,269 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(module
;; All tags keep externref parameters unchanged while other reference
;; parameters are lowered to shared types.
(tag $js-tag (import "WebAssembly" "JSTag") (param externref))
;; CHECK: (type $0 (func (result i32 (ref (shared i31)))))

;; CHECK: (type $1 (func (param externref)))

;; CHECK: (type $2 (func (param i32 (ref extern))))

;; CHECK: (type $3 (func (param (ref null (shared any)))))

;; CHECK: (type $4 (func (param (ref null (shared i31)) (ref (shared i31)) (ref null (shared any)))))

;; CHECK: (type $5 (func (result (ref null (shared i31)))))

;; CHECK: (type $6 (func))

;; CHECK: (type $7 (func (param externref) (result (ref null (shared i31)))))

;; CHECK: (type $8 (func (param (ref null (shared i31))) (result externref)))

;; CHECK: (import "WebAssembly" "JSTag" (tag $js-tag (type $1) (param externref)))

;; CHECK: (table $externs 0 externref)

;; CHECK: (tag $export-tag (type $2) (param i32 (ref extern)))
(tag $export-tag (export "export_tag") (param i32 (ref extern)))
;; CHECK: (tag $internal-tag (type $1) (param externref))
(tag $internal-tag (param externref))
;; CHECK: (tag $anyref-tag (type $3) (param (ref null (shared any))))
(tag $anyref-tag (param anyref))

;; CHECK: (export "export_tag" (tag $export-tag))

;; CHECK: (func $throw-tags (type $4) (param $x (ref null (shared i31))) (param $y (ref (shared i31))) (param $z (ref null (shared any)))
;; CHECK-NEXT: (throw $js-tag
;; CHECK-NEXT: (call $index_to_extern
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (throw $export-tag
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (ref.cast (ref extern)
;; CHECK-NEXT: (call $index_to_extern
;; CHECK-NEXT: (local.get $y)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (throw $internal-tag
;; CHECK-NEXT: (call $index_to_extern
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (throw $anyref-tag
;; CHECK-NEXT: (local.get $z)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $throw-tags (param $x externref) (param $y (ref extern)) (param $z anyref)
;; Throwing any tag with externref parameters converts externref operands
;; from i31ref to externref, while other reference parameters are lowered.
(throw $js-tag
(local.get $x)
)
(throw $export-tag
(i32.const 42)
(local.get $y)
)
(throw $internal-tag
(local.get $x)
)
(throw $anyref-tag
(local.get $z)
)
)

;; CHECK: (func $try-legacy (type $5) (result (ref null (shared i31)))
;; CHECK-NEXT: (try (result (ref null (shared i31)))
;; CHECK-NEXT: (do
;; CHECK-NEXT: (ref.null (shared none))
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $js-tag
;; CHECK-NEXT: (call $extern_to_index
;; CHECK-NEXT: (pop externref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $internal-tag
;; CHECK-NEXT: (call $extern_to_index
;; CHECK-NEXT: (pop externref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $anyref-tag
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (pop (ref null (shared any)))
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null (shared none))
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $try-legacy (result externref)
;; Legacy try/catch wraps the pop of any tag with externref parameters in a
;; conversion from externref to i31ref.
(try (result externref)
(do
(ref.null noextern)
)
(catch $js-tag
(pop externref)
)
(catch $internal-tag
(pop externref)
)
(catch $anyref-tag
(drop
(pop anyref)
)
(ref.null noextern)
)
)
)

;; CHECK: (func $try-legacy-tuple (type $0) (result i32 (ref (shared i31)))
;; CHECK-NEXT: (local $0 (tuple i32 (ref extern)))
;; CHECK-NEXT: (try (type $0) (result i32 (ref (shared i31)))
;; CHECK-NEXT: (do
;; CHECK-NEXT: (tuple.make 2
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $export-tag
;; CHECK-NEXT: (local.set $0
;; CHECK-NEXT: (pop (tuple i32 (ref extern)))
;; CHECK-NEXT: )
;; CHECK-NEXT: (tuple.make 2
;; CHECK-NEXT: (tuple.extract 2 0
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (call $extern_to_index
;; CHECK-NEXT: (tuple.extract 2 1
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $try-legacy-tuple (result i32 (ref extern))
;; Legacy try/catch on a multi-value exported tag converts the externref
;; tuple element from externref to i31ref.
(try (result i32 (ref extern))
(do
(tuple.make 2
(i32.const 0)
(unreachable)
)
)
(catch $export-tag
(pop (tuple i32 (ref extern)))
)
)
)

;; CHECK: (func $try-legacy-tuple-nested (type $6)
;; CHECK-NEXT: (local $t (tuple i32 (ref (shared i31))))
;; CHECK-NEXT: (local $1 (tuple i32 (ref extern)))
;; CHECK-NEXT: (local $2 (tuple i32 (ref extern)))
;; CHECK-NEXT: (try
;; CHECK-NEXT: (do
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $export-tag
;; CHECK-NEXT: (local.set $2
;; CHECK-NEXT: (pop (tuple i32 (ref extern)))
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $t
;; CHECK-NEXT: (block (type $0) (result i32 (ref (shared i31)))
;; CHECK-NEXT: (local.set $1
;; CHECK-NEXT: (local.get $2)
;; CHECK-NEXT: )
;; CHECK-NEXT: (tuple.make 2
;; CHECK-NEXT: (tuple.extract 2 0
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (call $extern_to_index
;; CHECK-NEXT: (tuple.extract 2 1
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $try-legacy-tuple-nested
(local $t (tuple i32 (ref extern)))
;; A multi-value pop inside an expression is wrapped in a block by visitPop
;; and then hoisted to the start of the catch body by handleBlockNestedPop.
(try
(do
(nop)
)
(catch $export-tag
(local.set $t
(pop (tuple i32 (ref extern)))
)
)
)
)
)

;; CHECK: (func $extern_to_index (type $7) (param $0 externref) (result (ref null (shared i31)))
;; CHECK-NEXT: (local $1 i32)
;; CHECK-NEXT: (if (result (ref null (shared i31)))
;; CHECK-NEXT: (ref.is_null
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (then
;; CHECK-NEXT: (ref.null (shared none))
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: (if (result (ref (shared i31)))
;; CHECK-NEXT: (i32.ge_s
;; CHECK-NEXT: (local.tee $1
;; CHECK-NEXT: (table.grow $externs
;; CHECK-NEXT: (local.get $0)
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (then
;; CHECK-NEXT: (ref.i31_shared
;; CHECK-NEXT: (local.get $1)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (else
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )

;; CHECK: (func $index_to_extern (type $8) (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: )
Loading