Skip to content
Merged
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
42 changes: 35 additions & 7 deletions src/passes/TypeRefining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ struct TypeRefining : public Pass {

TypeRewriter(wasm, *this).update();

// Refinalization fixes up types and makes them fit in the places we write
// them to.
ReFinalize().run(getPassRunner(), &wasm);

// After refinalizing, we may still have situations that do not validate.
Expand Down Expand Up @@ -514,9 +516,7 @@ struct TypeRefining : public Pass {
for (Index i = 0; i < fields.size(); i++) {
auto*& operand = curr->operands[i];
auto fieldType = fields[i].type;
if (!Type::isSubType(operand->type, fieldType)) {
operand = Builder(*getModule()).makeRefCast(operand, fieldType);
}
operand = fixType(operand, fieldType);
}
}

Expand All @@ -532,17 +532,45 @@ struct TypeRefining : public Pass {
}

auto fieldType = type.getStruct().fields[curr->index].type;
curr->value = fixType(curr->value, fieldType);
}

bool refinalize = false;

// Fix up a given value so it fits into the type the location it is
// written to.
Expression* fixType(Expression* value, Type type) {
if (Type::isSubType(value->type, type)) {
return value;
}
// We cast to fix this up. An exception is a bottom type, which we can
// handle by emitting a null (which works with types that cannot be
// cast, like continuations; it also explicitly provides the value being
// written, which other passes would do anyhow).
Builder builder(*getModule());
auto heapType = type.getHeapType();
if (heapType.isBottom()) {
auto* drop = builder.makeDrop(value);
if (type.isNonNullable()) {
// This will just trap. Make it trap, and update parents' types.
refinalize = true;
return builder.makeSequence(drop, builder.makeUnreachable());
} else {
return builder.makeSequence(drop, builder.makeRefNull(heapType));
}
}
return builder.makeRefCast(value, type);
}

if (!Type::isSubType(curr->value->type, fieldType)) {
curr->value =
Builder(*getModule()).makeRefCast(curr->value, fieldType);
void visitFunction(Function* func) {
if (refinalize) {
ReFinalize().walkFunctionInModule(func, getModule());
}
}
};

WriteUpdater updater;
updater.run(getPassRunner(), &wasm);
updater.runOnModuleCode(getPassRunner(), &wasm);
}
};

Expand Down
51 changes: 51 additions & 0 deletions test/lit/passes/type-refining-gufa.wast
Original file line number Diff line number Diff line change
Expand Up @@ -611,3 +611,54 @@
)
)

;; When refining to nullcontref we cannot use a cast. Just emit a null.
(module
(rec
;; NRML: (rec
;; NRML-NEXT: (type $func (sub (func)))
;; GUFA: (rec
;; GUFA-NEXT: (type $func (sub (func)))
(type $func (sub (func)))
;; NRML: (type $cont (sub (cont $func)))
;; GUFA: (type $cont (sub (cont $func)))
(type $cont (sub (cont $func)))
;; NRML: (type $struct (struct (field (ref null $cont))))
;; GUFA: (type $struct (struct (field nullcontref)))
(type $struct (struct (field (ref null $cont))))
)

;; NRML: (type $3 (func))

;; NRML: (func $test (type $3)
;; NRML-NEXT: (local $null (ref null $cont))
;; NRML-NEXT: (drop
;; NRML-NEXT: (struct.new $struct
;; NRML-NEXT: (local.get $null)
;; NRML-NEXT: )
;; NRML-NEXT: )
;; NRML-NEXT: )
;; GUFA: (type $3 (func))

;; GUFA: (func $test (type $3)
;; GUFA-NEXT: (local $null (ref null $cont))
;; GUFA-NEXT: (drop
;; GUFA-NEXT: (struct.new $struct
;; GUFA-NEXT: (block (result nullcontref)
;; GUFA-NEXT: (drop
;; GUFA-NEXT: (local.get $null)
;; GUFA-NEXT: )
;; GUFA-NEXT: (ref.null nocont)
;; GUFA-NEXT: )
;; GUFA-NEXT: )
;; GUFA-NEXT: )
;; GUFA-NEXT: )
(func $test
(local $null (ref null $cont))
(drop
(struct.new $struct
(local.get $null)
)
)
)
)

55 changes: 32 additions & 23 deletions test/lit/passes/type-refining.wast
Original file line number Diff line number Diff line change
Expand Up @@ -1252,23 +1252,29 @@
(tag $tag)

;; CHECK: (func $struct.new (type $2) (param $extern externref) (result anyref)
;; CHECK-NEXT: (struct.new $A
;; CHECK-NEXT: (ref.cast (ref noextern)
;; CHECK-NEXT: (try (result externref)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (struct.new $A
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (ref.null noextern)
;; CHECK-NEXT: (block ;; (replaces unreachable StructNew we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (try (result externref)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (struct.new $A
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (ref.null noextern)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $tag
;; CHECK-NEXT: (local.get $extern)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $tag
;; CHECK-NEXT: (local.get $extern)
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $struct.new (param $extern externref) (result anyref)
Expand All @@ -1282,8 +1288,8 @@
;; type than the body.
;;
;; In such situations we rely on other optimizations to improve things, like
;; getting rid of the catch in this case. In this pass we add a cast to get
;; things to validate, which should be removable by other passes later on.
;; getting rid of the catch in this case. In this pass we add an unreachable
;; for the uninhabitable type, which fixes validation.
(struct.new $A
(try (result externref)
(do
Expand All @@ -1305,21 +1311,24 @@
;; CHECK: (func $struct.set (type $3) (param $ref (ref $A)) (param $extern externref)
;; CHECK-NEXT: (struct.set $A 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (ref.cast (ref noextern)
;; CHECK-NEXT: (try (result externref)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (struct.new $A
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (ref.null noextern)
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (try (result externref)
;; CHECK-NEXT: (do
;; CHECK-NEXT: (struct.get $A 0
;; CHECK-NEXT: (struct.new $A
;; CHECK-NEXT: (ref.as_non_null
;; CHECK-NEXT: (ref.null noextern)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (catch $tag
;; CHECK-NEXT: (local.get $extern)
;; CHECK-NEXT: (catch $tag
;; CHECK-NEXT: (local.get $extern)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
Expand Down
Loading