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
35 changes: 34 additions & 1 deletion src/ir/LocalGraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ struct Flower : public CFGWalker<Flower, Visitor<Flower>, Info> {
allGets.resize(numLocals);
std::vector<FlowBlock*> work;

// Track if we have unreachable code anywhere, as if we do that may inhibit
// certain optimizations below.
bool hasUnreachable = false;

// Convert input blocks (basicBlocks) into more efficient flow blocks to
// improve memory access.
std::vector<FlowBlock> flowBlocks;
Expand All @@ -116,9 +120,19 @@ struct Flower : public CFGWalker<Flower, Visitor<Flower>, Info> {
// Init mapping between basicblocks and flowBlocks
std::unordered_map<BasicBlock*, FlowBlock*> basicToFlowMap;
for (Index i = 0; i < basicBlocks.size(); ++i) {
basicToFlowMap[basicBlocks[i].get()] = &flowBlocks[i];
auto* block = basicBlocks[i].get();
basicToFlowMap[block] = &flowBlocks[i];
// Check for unreachable code. Note we ignore the entry block (index 0) as
// that is always reached when we are called.
if (i != 0 && block->in.empty()) {
hasUnreachable = true;
}
}

// We note which local indexes have local.sets, as that can help us
// optimize later (if there are none at all).
std::vector<bool> hasSet(numLocals, false);

const size_t NULL_ITERATION = -1;

FlowBlock* entryFlowBlock = nullptr;
Expand All @@ -142,6 +156,7 @@ struct Flower : public CFGWalker<Flower, Visitor<Flower>, Info> {
flowBlock.lastSets.reserve(block->contents.lastSets.size());
for (auto set : block->contents.lastSets) {
flowBlock.lastSets.emplace_back(set);
hasSet[set.first] = true;
}
}
assert(entryFlowBlock != nullptr);
Expand Down Expand Up @@ -182,6 +197,24 @@ struct Flower : public CFGWalker<Flower, Visitor<Flower>, Info> {
if (gets.empty()) {
continue;
}
if (!hasUnreachable && !hasSet[index]) {
// This local index has no sets, so we know all gets will end up
// reaching the entry block. Do that here as an optimization to avoid
// flowing through the (potentially very many) blocks in the function.
//
// Note that we must check for unreachable code in this function, as
// if there is any then we would not be precise: in that case, the
// gets may either have the entry value, or no value at all. It would
// be safe to mark the entry value in that case anyhow (as it only
// matters in unreachable code), but to keep the IR consistent and to
// avoid confusion when debugging, simply do not optimize if
// there is anything unreachable (which will not happen normally, as
// DCE should run before passes that use this utility).
for (auto* get : gets) {
getSetses[get].insert(nullptr);
}
continue;
}
work.push_back(&block);
// Note that we may need to revisit the later parts of this initial
// block, if we are in a loop, so don't mark it as seen.
Expand Down
45 changes: 39 additions & 6 deletions test/lit/passes/precompute-gc.wast
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@
(struct.get $struct 0 (local.get $x))
)
)
;; CHECK: (func $ref-comparisons (type $8) (param $x (ref null $struct)) (param $y (ref null $struct))
;; CHECK: (func $ref-comparisons (type $9) (param $x (ref null $struct)) (param $y (ref null $struct))
;; CHECK-NEXT: (local $z (ref null $struct))
;; CHECK-NEXT: (local $w (ref null $struct))
;; CHECK-NEXT: (call $log
Expand Down Expand Up @@ -407,7 +407,7 @@
(local.get $tempresult)
)

;; CHECK: (func $propagate-different-params (type $9) (param $input1 (ref $empty)) (param $input2 (ref $empty)) (result i32)
;; CHECK: (func $propagate-different-params (type $10) (param $input1 (ref $empty)) (param $input2 (ref $empty)) (result i32)
;; CHECK-NEXT: (local $tempresult i32)
;; CHECK-NEXT: (local.set $tempresult
;; CHECK-NEXT: (ref.eq
Expand Down Expand Up @@ -723,7 +723,7 @@
)
)

;; CHECK: (func $helper (type $10) (param $0 i32) (result i32)
;; CHECK: (func $helper (type $11) (param $0 i32) (result i32)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $helper (param i32) (result i32)
Expand Down Expand Up @@ -801,14 +801,14 @@
)
)

;; CHECK: (func $receive-f64 (type $11) (param $0 f64)
;; CHECK: (func $receive-f64 (type $12) (param $0 f64)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
(func $receive-f64 (param f64)
(unreachable)
)

;; CHECK: (func $odd-cast-and-get-non-null (type $12) (param $temp (ref $func-return-i32))
;; CHECK: (func $odd-cast-and-get-non-null (type $13) (param $temp (ref $func-return-i32))
;; CHECK-NEXT: (local.set $temp
;; CHECK-NEXT: (ref.cast (ref nofunc)
;; CHECK-NEXT: (ref.func $receive-f64)
Expand Down Expand Up @@ -836,7 +836,7 @@
)
)

;; CHECK: (func $new_block_unreachable (type $13) (result anyref)
;; CHECK: (func $new_block_unreachable (type $8) (result anyref)
;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
Expand Down Expand Up @@ -1033,4 +1033,37 @@
)
)
)

;; CHECK: (func $get-nonnullable-in-unreachable (type $8) (result anyref)
;; CHECK-NEXT: (local $x (ref any))
;; CHECK-NEXT: (local.tee $x
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (if
;; CHECK-NEXT: (i32.const 1)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $x)
;; CHECK-NEXT: )
(func $get-nonnullable-in-unreachable (result anyref)
(local $x (ref any))
;; We cannot read a non-nullable local without setting it first, but it is ok
;; to do so here because we are in unreachable code. We should also not error
;; about this get seeming to read the default value from the function entry
;; (because it does not, as the entry is not reachable from it). Nothing is
;; expected to be optimized here.

;; This unreachable set is needed for the later get to validate.
(local.set $x
(unreachable)
)
;; This if is needed so we have an interesting enough CFG that a possible
;; assertion can be hit about reading the default value from the entry in a
;; later block.
(if
(i32.const 1)
(unreachable)
)
(local.get $x)
)
)