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
14 changes: 14 additions & 0 deletions src/passes/TypeRefining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
// themselves.
//

#include "ir/eh-utils.h"
#include "ir/find_all.h"
#include "ir/lubs.h"
#include "ir/possible-contents.h"
Expand Down Expand Up @@ -398,6 +399,7 @@ struct TypeRefining : public Pass {
bool requiresNonNullableLocalFixups() override { return false; }

TypeRefining& parent;
bool addedBlock = false;

ReadUpdater(TypeRefining& parent) : parent(parent) {}

Expand Down Expand Up @@ -426,6 +428,7 @@ struct TypeRefining : public Pass {
Builder builder(*getModule());
replaceCurrent(builder.makeSequence(builder.makeDrop(curr->ref),
builder.makeUnreachable()));
addedBlock = true;
return;
}

Expand Down Expand Up @@ -455,6 +458,12 @@ struct TypeRefining : public Pass {
// ourselves here, before.)
curr->type = newFieldType;
}

void visitFunction(Function* func) {
if (addedBlock) {
EHUtils::handleBlockNestedPops(func, *getModule());
}
}
Comment on lines +462 to +466

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

In sequential/single-threaded execution mode, the same ReadUpdater instance is reused across all functions in the module. If addedBlock is set to true in one function, it will carry over to all subsequent functions, causing EHUtils::handleBlockNestedPops to be run unnecessarily on them. Resetting addedBlock to false at the end of visitFunction prevents this state carry-over and avoids unnecessary performance overhead.

      void visitFunction(Function* func) {
        if (addedBlock) {
          EHUtils::handleBlockNestedPops(func, *getModule());
          addedBlock = false;
        }
      }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not the case because the pass is function-parallel. Every function gets a fresh instance.

};

ReadUpdater updater(*this);
Expand Down Expand Up @@ -582,6 +591,7 @@ struct TypeRefining : public Pass {
}

bool refinalize = false;
bool addedBlock = false;

// Fix up a given value so it fits into the type the location it is
// written to.
Expand All @@ -596,6 +606,7 @@ struct TypeRefining : public Pass {
Builder builder(*getModule());
auto heapType = type.getHeapType();
if (heapType.isBottom()) {
addedBlock = true;
auto* drop = builder.makeDrop(value);
if (type.isNonNullable()) {
// This will just trap. Make it trap, and update parents' types.
Expand All @@ -612,6 +623,9 @@ struct TypeRefining : public Pass {
if (refinalize) {
ReFinalize().walkFunctionInModule(func, getModule());
}
if (addedBlock) {
EHUtils::handleBlockNestedPops(func, *getModule());
}
}
};

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

;; Handle block-nested pops created when updating writes to and reads from
;; refined fields.
(module
;; NRML: (rec
;; NRML-NEXT: (type $struct (struct (field eqref) (field nullref)))
;; GUFA: (rec
;; GUFA-NEXT: (type $struct (struct (field nullref) (field nullref)))
(type $struct (struct (field eqref) (field anyref)))
;; NRML: (type $1 (func (param eqref)))

;; NRML: (type $2 (func (param nullref)))

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

;; NRML: (tag $ref (type $1) (param eqref))
;; GUFA: (type $1 (func (param eqref)))

;; GUFA: (type $2 (func (param nullref)))

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

;; GUFA: (tag $ref (type $1) (param eqref))
(tag $ref (param eqref))
;; NRML: (tag $null (type $2) (param nullref))
;; GUFA: (tag $null (type $2) (param nullref))
(tag $null (param nullref))

;; NRML: (export "write" (func $write))

;; NRML: (export "read" (func $read))

;; NRML: (func $write (type $3)
;; NRML-NEXT: (drop
;; NRML-NEXT: (struct.new_default $struct)
;; NRML-NEXT: )
;; NRML-NEXT: (try
;; NRML-NEXT: (do
;; NRML-NEXT: )
;; NRML-NEXT: (catch $ref
;; NRML-NEXT: (drop
;; NRML-NEXT: (struct.new $struct
;; NRML-NEXT: (pop eqref)
;; NRML-NEXT: (ref.null none)
;; NRML-NEXT: )
;; NRML-NEXT: )
;; NRML-NEXT: )
;; NRML-NEXT: )
;; NRML-NEXT: )
;; GUFA: (export "write" (func $write))

;; GUFA: (export "read" (func $read))

;; GUFA: (func $write (type $3)
;; GUFA-NEXT: (local $0 eqref)
;; GUFA-NEXT: (drop
;; GUFA-NEXT: (struct.new_default $struct)
;; GUFA-NEXT: )
;; GUFA-NEXT: (try
;; GUFA-NEXT: (do
;; GUFA-NEXT: )
;; GUFA-NEXT: (catch $ref
;; GUFA-NEXT: (local.set $0
;; GUFA-NEXT: (pop eqref)
;; GUFA-NEXT: )
;; GUFA-NEXT: (drop
;; GUFA-NEXT: (struct.new $struct
;; GUFA-NEXT: (block (result nullref)
;; GUFA-NEXT: (drop
;; GUFA-NEXT: (local.get $0)
;; GUFA-NEXT: )
;; GUFA-NEXT: (ref.null none)
;; GUFA-NEXT: )
;; GUFA-NEXT: (ref.null none)
;; GUFA-NEXT: )
;; GUFA-NEXT: )
;; GUFA-NEXT: )
;; GUFA-NEXT: )
;; GUFA-NEXT: )
;; O3O3: (type $0 (func))

;; O3O3: (export "write" (func $write))

;; O3O3: (export "read" (func $write))

;; O3O3: (func $write (type $0)
;; O3O3-NEXT: (nop)
;; O3O3-NEXT: )
(func $write (export "write")
;; In GUFA, the first field is refined to nullref, so the write operand in
;; the catch block is replaced with a block that drops the pop and produces
;; a null. The newly nested pop must be fixed up.
(drop
(struct.new_default $struct)
)
(try
(do)
(catch $ref
(drop
(struct.new $struct
(pop eqref)
(ref.null none)
)
)
)
)
)

;; NRML: (func $read (type $3)
;; NRML-NEXT: (local $0 nullref)
;; NRML-NEXT: (try
;; NRML-NEXT: (do
;; NRML-NEXT: )
;; NRML-NEXT: (catch $null
;; NRML-NEXT: (local.set $0
;; NRML-NEXT: (pop nullref)
;; NRML-NEXT: )
;; NRML-NEXT: (drop
;; NRML-NEXT: (block
;; NRML-NEXT: (drop
;; NRML-NEXT: (local.get $0)
;; NRML-NEXT: )
;; NRML-NEXT: (unreachable)
;; NRML-NEXT: )
;; NRML-NEXT: )
;; NRML-NEXT: )
;; NRML-NEXT: )
;; NRML-NEXT: )
;; GUFA: (func $read (type $3)
;; GUFA-NEXT: (local $0 nullref)
;; GUFA-NEXT: (try
;; GUFA-NEXT: (do
;; GUFA-NEXT: )
;; GUFA-NEXT: (catch $null
;; GUFA-NEXT: (local.set $0
;; GUFA-NEXT: (pop nullref)
;; GUFA-NEXT: )
;; GUFA-NEXT: (drop
;; GUFA-NEXT: (block
;; GUFA-NEXT: (drop
;; GUFA-NEXT: (local.get $0)
;; GUFA-NEXT: )
;; GUFA-NEXT: (unreachable)
;; GUFA-NEXT: )
;; GUFA-NEXT: )
;; GUFA-NEXT: )
;; GUFA-NEXT: )
;; GUFA-NEXT: )
(func $read (export "read")
;; Reading from a null reference is replaced with a block that drops the pop
;; and traps. The newly nested pop must be fixed up.
(try
(do)
(catch $null
(drop
(struct.get $struct 0
(pop nullref)
)
)
)
)
)
)
Loading