Skip to content

Commit e8a39f0

Browse files
committed
[MakeSharedObjects] Support externref tags
Keep externref parameter types on tags unchanged so exceptions leaving or entering the module carry externrefs rather than lowered i31ref table indices. Convert operands from i31ref to externref in visitThrow and convert popped values from externref to i31ref in visitPop, fixing up any resulting block-nested pops after walking the function. This lets imports of JSTag continue working as expected.
1 parent f62a011 commit e8a39f0

2 files changed

Lines changed: 345 additions & 3 deletions

File tree

‎src/passes/MakeSharedObjects.cpp‎

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
// references.
4545

4646
#include "ir/drop.h"
47+
#include "ir/eh-utils.h"
4748
#include "ir/module-utils.h"
4849
#include "ir/names.h"
4950
#include "ir/type-updating.h"
@@ -510,8 +511,16 @@ struct MakeSharedObjects
510511
}
511512

512513
std::unordered_map<CallRef*, HeapType> callRefTypes;
514+
bool hasBlockNestedPop = false;
513515

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

533545
void visitCallRef(CallRef* curr) {
@@ -725,10 +737,56 @@ struct MakeSharedObjects
725737
#include "wasm-delegations-fields.def"
726738
}
727739

728-
void visitFunction(Function* curr) {
729-
for (auto& type : curr->vars) {
730-
updateType(type);
740+
// Compute the boundary parameter types for a tag, keeping externref
741+
// parameters unchanged while lowering other parameter types.
742+
Type getTagBoundaryParams(Type origParams) {
743+
std::vector<Type> params;
744+
for (Type t : origParams) {
745+
params.push_back(externTable.isTableType(t) ? t : updatedType(t));
746+
}
747+
return Type(params);
748+
}
749+
750+
// Convert lowered i31ref operands back to externrefs when throwing a tag
751+
// that expects externref parameters.
752+
void visitThrow(Throw* curr) {
753+
visitExpression(curr);
754+
Type origParams = getModule()->getTag(curr->tag)->params();
755+
for (Index i = 0; i < curr->operands.size(); ++i) {
756+
curr->operands[i] =
757+
externTable.convertToRef(curr->operands[i], origParams[i]);
758+
}
759+
}
760+
761+
// Wrap pops with externref types in conversions from the caught externrefs
762+
// to i31ref table indices.
763+
void visitPop(Pop* curr) {
764+
if (!externTable.hasTableType(curr->type)) {
765+
visitExpression(curr);
766+
return;
767+
}
768+
Type origParams = curr->type;
769+
Type internalParams = updatedType(origParams);
770+
Type boundaryParams = getTagBoundaryParams(origParams);
771+
curr->type = boundaryParams;
772+
if (origParams.isSingle()) {
773+
replaceCurrent(
774+
externTable.convertToIndex(curr, origParams, internalParams));
775+
return;
731776
}
777+
Builder builder(*getModule());
778+
Index scratch = Builder::addVar(getFunction(), boundaryParams);
779+
auto* set = builder.makeLocalSet(scratch, curr);
780+
std::vector<Expression*> elems;
781+
elems.reserve(origParams.size());
782+
for (Index i = 0; i < origParams.size(); ++i) {
783+
auto* extract = builder.makeTupleExtract(
784+
builder.makeLocalGet(scratch, boundaryParams), i);
785+
elems.push_back(
786+
externTable.convertToIndex(extract, origParams[i], internalParams[i]));
787+
}
788+
replaceCurrent(builder.makeSequence(set, builder.makeTupleMake(elems)));
789+
hasBlockNestedPop = true;
732790
}
733791

734792
void visitTable(Table* curr) { updateType(curr->type); }
@@ -794,6 +852,11 @@ struct MakeSharedObjects
794852
Type origType;
795853
};
796854

855+
struct TagToUpdate {
856+
Tag* tag;
857+
HeapType origType;
858+
};
859+
797860
void visitModule(Module* wasm) {
798861
std::vector<ImportToWrap> importsToWrap;
799862
for (auto& func : wasm->functions) {
@@ -814,8 +877,21 @@ struct MakeSharedObjects
814877
}
815878
}
816879

880+
std::vector<TagToUpdate> tagsToUpdate;
881+
for (auto& tag : wasm->tags) {
882+
if (externTable.hasTableType(tag->params())) {
883+
tagsToUpdate.push_back({tag.get(), tag->type});
884+
}
885+
}
886+
817887
rewriteTypes();
818888

889+
for (auto& [tag, origType] : tagsToUpdate) {
890+
tag->type = getBoundaryType(Type(origType, NonNullable),
891+
Type(tag->type, NonNullable))
892+
.getHeapType();
893+
}
894+
819895
for (auto& info : globalImportsToWrap) {
820896
wrapGlobalImport(info.global, info.origType);
821897
}

‎test/lit/passes/make-shared-objects.wast‎

Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2683,3 +2683,269 @@
26832683
;; CHECK-NEXT: )
26842684
;; CHECK-NEXT: )
26852685
;; CHECK-NEXT: )
2686+
(module
2687+
;; All tags keep externref parameters unchanged while other reference
2688+
;; parameters are lowered to shared types.
2689+
(tag $js-tag (import "WebAssembly" "JSTag") (param externref))
2690+
;; CHECK: (type $0 (func (result i32 (ref (shared i31)))))
2691+
2692+
;; CHECK: (type $1 (func (param externref)))
2693+
2694+
;; CHECK: (type $2 (func (param i32 (ref extern))))
2695+
2696+
;; CHECK: (type $3 (func (param (ref null (shared any)))))
2697+
2698+
;; CHECK: (type $4 (func (param (ref null (shared i31)) (ref (shared i31)) (ref null (shared any)))))
2699+
2700+
;; CHECK: (type $5 (func (result (ref null (shared i31)))))
2701+
2702+
;; CHECK: (type $6 (func))
2703+
2704+
;; CHECK: (type $7 (func (param externref) (result (ref null (shared i31)))))
2705+
2706+
;; CHECK: (type $8 (func (param (ref null (shared i31))) (result externref)))
2707+
2708+
;; CHECK: (import "WebAssembly" "JSTag" (tag $js-tag (type $1) (param externref)))
2709+
2710+
;; CHECK: (table $externs 0 externref)
2711+
2712+
;; CHECK: (tag $export-tag (type $2) (param i32 (ref extern)))
2713+
(tag $export-tag (export "export_tag") (param i32 (ref extern)))
2714+
;; CHECK: (tag $internal-tag (type $1) (param externref))
2715+
(tag $internal-tag (param externref))
2716+
;; CHECK: (tag $anyref-tag (type $3) (param (ref null (shared any))))
2717+
(tag $anyref-tag (param anyref))
2718+
2719+
;; CHECK: (export "export_tag" (tag $export-tag))
2720+
2721+
;; CHECK: (func $throw-tags (type $4) (param $x (ref null (shared i31))) (param $y (ref (shared i31))) (param $z (ref null (shared any)))
2722+
;; CHECK-NEXT: (throw $js-tag
2723+
;; CHECK-NEXT: (call $index_to_extern
2724+
;; CHECK-NEXT: (local.get $x)
2725+
;; CHECK-NEXT: )
2726+
;; CHECK-NEXT: )
2727+
;; CHECK-NEXT: (throw $export-tag
2728+
;; CHECK-NEXT: (i32.const 42)
2729+
;; CHECK-NEXT: (ref.cast (ref extern)
2730+
;; CHECK-NEXT: (call $index_to_extern
2731+
;; CHECK-NEXT: (local.get $y)
2732+
;; CHECK-NEXT: )
2733+
;; CHECK-NEXT: )
2734+
;; CHECK-NEXT: )
2735+
;; CHECK-NEXT: (throw $internal-tag
2736+
;; CHECK-NEXT: (call $index_to_extern
2737+
;; CHECK-NEXT: (local.get $x)
2738+
;; CHECK-NEXT: )
2739+
;; CHECK-NEXT: )
2740+
;; CHECK-NEXT: (throw $anyref-tag
2741+
;; CHECK-NEXT: (local.get $z)
2742+
;; CHECK-NEXT: )
2743+
;; CHECK-NEXT: )
2744+
(func $throw-tags (param $x externref) (param $y (ref extern)) (param $z anyref)
2745+
;; Throwing any tag with externref parameters converts externref operands
2746+
;; from i31ref to externref, while other reference parameters are lowered.
2747+
(throw $js-tag
2748+
(local.get $x)
2749+
)
2750+
(throw $export-tag
2751+
(i32.const 42)
2752+
(local.get $y)
2753+
)
2754+
(throw $internal-tag
2755+
(local.get $x)
2756+
)
2757+
(throw $anyref-tag
2758+
(local.get $z)
2759+
)
2760+
)
2761+
2762+
;; CHECK: (func $try-legacy (type $5) (result (ref null (shared i31)))
2763+
;; CHECK-NEXT: (try (result (ref null (shared i31)))
2764+
;; CHECK-NEXT: (do
2765+
;; CHECK-NEXT: (ref.null (shared none))
2766+
;; CHECK-NEXT: )
2767+
;; CHECK-NEXT: (catch $js-tag
2768+
;; CHECK-NEXT: (call $extern_to_index
2769+
;; CHECK-NEXT: (pop externref)
2770+
;; CHECK-NEXT: )
2771+
;; CHECK-NEXT: )
2772+
;; CHECK-NEXT: (catch $internal-tag
2773+
;; CHECK-NEXT: (call $extern_to_index
2774+
;; CHECK-NEXT: (pop externref)
2775+
;; CHECK-NEXT: )
2776+
;; CHECK-NEXT: )
2777+
;; CHECK-NEXT: (catch $anyref-tag
2778+
;; CHECK-NEXT: (drop
2779+
;; CHECK-NEXT: (pop (ref null (shared any)))
2780+
;; CHECK-NEXT: )
2781+
;; CHECK-NEXT: (ref.null (shared none))
2782+
;; CHECK-NEXT: )
2783+
;; CHECK-NEXT: )
2784+
;; CHECK-NEXT: )
2785+
(func $try-legacy (result externref)
2786+
;; Legacy try/catch wraps the pop of any tag with externref parameters in a
2787+
;; conversion from externref to i31ref.
2788+
(try (result externref)
2789+
(do
2790+
(ref.null noextern)
2791+
)
2792+
(catch $js-tag
2793+
(pop externref)
2794+
)
2795+
(catch $internal-tag
2796+
(pop externref)
2797+
)
2798+
(catch $anyref-tag
2799+
(drop
2800+
(pop anyref)
2801+
)
2802+
(ref.null noextern)
2803+
)
2804+
)
2805+
)
2806+
2807+
;; CHECK: (func $try-legacy-tuple (type $0) (result i32 (ref (shared i31)))
2808+
;; CHECK-NEXT: (local $0 (tuple i32 (ref extern)))
2809+
;; CHECK-NEXT: (try (type $0) (result i32 (ref (shared i31)))
2810+
;; CHECK-NEXT: (do
2811+
;; CHECK-NEXT: (tuple.make 2
2812+
;; CHECK-NEXT: (i32.const 0)
2813+
;; CHECK-NEXT: (unreachable)
2814+
;; CHECK-NEXT: )
2815+
;; CHECK-NEXT: )
2816+
;; CHECK-NEXT: (catch $export-tag
2817+
;; CHECK-NEXT: (local.set $0
2818+
;; CHECK-NEXT: (pop (tuple i32 (ref extern)))
2819+
;; CHECK-NEXT: )
2820+
;; CHECK-NEXT: (tuple.make 2
2821+
;; CHECK-NEXT: (tuple.extract 2 0
2822+
;; CHECK-NEXT: (local.get $0)
2823+
;; CHECK-NEXT: )
2824+
;; CHECK-NEXT: (ref.as_non_null
2825+
;; CHECK-NEXT: (call $extern_to_index
2826+
;; CHECK-NEXT: (tuple.extract 2 1
2827+
;; CHECK-NEXT: (local.get $0)
2828+
;; CHECK-NEXT: )
2829+
;; CHECK-NEXT: )
2830+
;; CHECK-NEXT: )
2831+
;; CHECK-NEXT: )
2832+
;; CHECK-NEXT: )
2833+
;; CHECK-NEXT: )
2834+
;; CHECK-NEXT: )
2835+
(func $try-legacy-tuple (result i32 (ref extern))
2836+
;; Legacy try/catch on a multi-value exported tag converts the externref
2837+
;; tuple element from externref to i31ref.
2838+
(try (result i32 (ref extern))
2839+
(do
2840+
(tuple.make 2
2841+
(i32.const 0)
2842+
(unreachable)
2843+
)
2844+
)
2845+
(catch $export-tag
2846+
(pop (tuple i32 (ref extern)))
2847+
)
2848+
)
2849+
)
2850+
2851+
;; CHECK: (func $try-legacy-tuple-nested (type $6)
2852+
;; CHECK-NEXT: (local $t (tuple i32 (ref (shared i31))))
2853+
;; CHECK-NEXT: (local $1 (tuple i32 (ref extern)))
2854+
;; CHECK-NEXT: (local $2 (tuple i32 (ref extern)))
2855+
;; CHECK-NEXT: (try
2856+
;; CHECK-NEXT: (do
2857+
;; CHECK-NEXT: (nop)
2858+
;; CHECK-NEXT: )
2859+
;; CHECK-NEXT: (catch $export-tag
2860+
;; CHECK-NEXT: (local.set $2
2861+
;; CHECK-NEXT: (pop (tuple i32 (ref extern)))
2862+
;; CHECK-NEXT: )
2863+
;; CHECK-NEXT: (local.set $t
2864+
;; CHECK-NEXT: (block (type $0) (result i32 (ref (shared i31)))
2865+
;; CHECK-NEXT: (local.set $1
2866+
;; CHECK-NEXT: (local.get $2)
2867+
;; CHECK-NEXT: )
2868+
;; CHECK-NEXT: (tuple.make 2
2869+
;; CHECK-NEXT: (tuple.extract 2 0
2870+
;; CHECK-NEXT: (local.get $1)
2871+
;; CHECK-NEXT: )
2872+
;; CHECK-NEXT: (ref.as_non_null
2873+
;; CHECK-NEXT: (call $extern_to_index
2874+
;; CHECK-NEXT: (tuple.extract 2 1
2875+
;; CHECK-NEXT: (local.get $1)
2876+
;; CHECK-NEXT: )
2877+
;; CHECK-NEXT: )
2878+
;; CHECK-NEXT: )
2879+
;; CHECK-NEXT: )
2880+
;; CHECK-NEXT: )
2881+
;; CHECK-NEXT: )
2882+
;; CHECK-NEXT: )
2883+
;; CHECK-NEXT: )
2884+
;; CHECK-NEXT: )
2885+
(func $try-legacy-tuple-nested
2886+
(local $t (tuple i32 (ref extern)))
2887+
;; A multi-value pop inside an expression is wrapped in a block by visitPop
2888+
;; and then hoisted to the start of the catch body by handleBlockNestedPop.
2889+
(try
2890+
(do
2891+
(nop)
2892+
)
2893+
(catch $export-tag
2894+
(local.set $t
2895+
(pop (tuple i32 (ref extern)))
2896+
)
2897+
)
2898+
)
2899+
)
2900+
)
2901+
2902+
;; CHECK: (func $extern_to_index (type $7) (param $0 externref) (result (ref null (shared i31)))
2903+
;; CHECK-NEXT: (local $1 i32)
2904+
;; CHECK-NEXT: (if (result (ref null (shared i31)))
2905+
;; CHECK-NEXT: (ref.is_null
2906+
;; CHECK-NEXT: (local.get $0)
2907+
;; CHECK-NEXT: )
2908+
;; CHECK-NEXT: (then
2909+
;; CHECK-NEXT: (ref.null (shared none))
2910+
;; CHECK-NEXT: )
2911+
;; CHECK-NEXT: (else
2912+
;; CHECK-NEXT: (if (result (ref (shared i31)))
2913+
;; CHECK-NEXT: (i32.ge_s
2914+
;; CHECK-NEXT: (local.tee $1
2915+
;; CHECK-NEXT: (table.grow $externs
2916+
;; CHECK-NEXT: (local.get $0)
2917+
;; CHECK-NEXT: (i32.const 1)
2918+
;; CHECK-NEXT: )
2919+
;; CHECK-NEXT: )
2920+
;; CHECK-NEXT: (i32.const 0)
2921+
;; CHECK-NEXT: )
2922+
;; CHECK-NEXT: (then
2923+
;; CHECK-NEXT: (ref.i31_shared
2924+
;; CHECK-NEXT: (local.get $1)
2925+
;; CHECK-NEXT: )
2926+
;; CHECK-NEXT: )
2927+
;; CHECK-NEXT: (else
2928+
;; CHECK-NEXT: (unreachable)
2929+
;; CHECK-NEXT: )
2930+
;; CHECK-NEXT: )
2931+
;; CHECK-NEXT: )
2932+
;; CHECK-NEXT: )
2933+
;; CHECK-NEXT: )
2934+
2935+
;; CHECK: (func $index_to_extern (type $8) (param $0 (ref null (shared i31))) (result externref)
2936+
;; CHECK-NEXT: (if (result externref)
2937+
;; CHECK-NEXT: (ref.is_null
2938+
;; CHECK-NEXT: (local.get $0)
2939+
;; CHECK-NEXT: )
2940+
;; CHECK-NEXT: (then
2941+
;; CHECK-NEXT: (ref.null noextern)
2942+
;; CHECK-NEXT: )
2943+
;; CHECK-NEXT: (else
2944+
;; CHECK-NEXT: (table.get $externs
2945+
;; CHECK-NEXT: (i31.get_u
2946+
;; CHECK-NEXT: (local.get $0)
2947+
;; CHECK-NEXT: )
2948+
;; CHECK-NEXT: )
2949+
;; CHECK-NEXT: )
2950+
;; CHECK-NEXT: )
2951+
;; CHECK-NEXT: )

0 commit comments

Comments
 (0)