Skip to content

Commit

Permalink
[TypeGeneralizing] Properly re-analyze blocks when locals are updated
Browse files Browse the repository at this point in the history
Whenever the constraint on a local is updated, any block that does a local.set
on that global may need to be re-analyzed. Update the TypeGeneralizing transfer
function to include these blocks in the set of dependent blocks it returns. Add
a test that depends on this logic to validate.
  • Loading branch information
tlively committed Nov 4, 2023
1 parent 943688d commit dbad282
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 23 deletions.
58 changes: 43 additions & 15 deletions src/passes/TypeGeneralizing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ struct State : StateLattice {
return getLocals(elem)[i];
}

void updateLocal(Element& elem, Index i, Type type) const noexcept {
localsLattice().join(
bool updateLocal(Element& elem, Index i, Type type) const noexcept {
return localsLattice().join(
locals(elem),
Vector<TypeRequirement>::SingletonElement(i, std::move(type)));
}
Expand Down Expand Up @@ -151,17 +151,43 @@ struct TransferFn : OverriddenVisitor<TransferFn> {
State lattice;
typename State::Element* state = nullptr;

TransferFn(Module& wasm, Function* func)
: wasm(wasm), func(func), lattice(func) {}
// For each local, the set of blocks we may need to re-analyze when we update
// the constraint on the local.
std::vector<std::vector<const BasicBlock*>> localDependents;

// The set of basic blocks that may depend on the result of the current
// transfer.
std::unordered_set<const BasicBlock*> currDependents;

TransferFn(Module& wasm, Function* func, CFG& cfg)
: wasm(wasm), func(func), lattice(func),
localDependents(func->getNumLocals()) {
// Initialize `localDependents`. Any block containing a `local.set l` may
// need to be re-analyzed whenever the constraint on `l` is updated.
auto numLocals = func->getNumLocals();
std::vector<std::unordered_set<const BasicBlock*>> dependentSets(numLocals);
for (const auto& bb : cfg) {
for (const auto* inst : bb) {
if (auto set = inst->dynCast<LocalSet>()) {
dependentSets[set->index].insert(&bb);
}
}
}
for (size_t i = 0, n = dependentSets.size(); i < n; ++i) {
localDependents[i] = std::vector<const BasicBlock*>(
dependentSets[i].begin(), dependentSets[i].end());
}
}

Type pop() noexcept { return lattice.pop(*state); }
void push(Type type) noexcept { lattice.push(*state, type); }
void clearStack() noexcept { lattice.clearStack(*state); }
Type getLocal(Index i) noexcept { return lattice.getLocal(*state, i); }
void updateLocal(Index i, Type type) noexcept {
// TODO: Collect possible successor blocks that might depend on an updated
// local requirement here.
return lattice.updateLocal(*state, i, type);
if (lattice.updateLocal(*state, i, type)) {
currDependents.insert(localDependents[i].begin(),
localDependents[i].end());
}
}

void dumpState() {
Expand All @@ -185,12 +211,18 @@ struct TransferFn : OverriddenVisitor<TransferFn> {
#endif // TYPE_GENERALIZING_DEBUG
}

std::vector<const BasicBlock*>
std::unordered_set<const BasicBlock*>
transfer(const BasicBlock& bb, typename State::Element& elem) noexcept {
DBG(std::cerr << "transferring bb " << bb.getIndex() << "\n");
state = &elem;

// This is a backward analysis: The constraints on a type depend on how it
// will be used in the future. Traverse the basic block in reverse.
// will be used in the future. Traverse the basic block in reverse and
// return the predecessors as the dependent blocks.
assert(currDependents.empty());
const auto& preds = bb.preds();
currDependents.insert(preds.begin(), preds.end());

dumpState();
if (bb.isExit()) {
DBG(std::cerr << "visiting exit\n");
Expand All @@ -207,11 +239,7 @@ struct TransferFn : OverriddenVisitor<TransferFn> {
state = nullptr;

// Return the blocks that may need to be re-analyzed.
const auto& preds = bb.preds();
std::vector<const BasicBlock*> dependents;
dependents.reserve(preds.size());
dependents.insert(dependents.end(), preds.begin(), preds.end());
return dependents;
return std::move(currDependents);
}

void visitFunctionExit() {
Expand Down Expand Up @@ -395,9 +423,9 @@ struct TypeGeneralizing : WalkerPass<PostWalker<TypeGeneralizing>> {
}

void runOnFunction(Module* wasm, Function* func) override {
TransferFn txfn(*wasm, func);
auto cfg = CFG::fromFunction(func);
DBG(cfg.print(std::cerr));
TransferFn txfn(*wasm, func, cfg);
MonotoneCFGAnalyzer analyzer(txfn.lattice, txfn, cfg);
analyzer.evaluate();

Expand Down
73 changes: 65 additions & 8 deletions test/lit/passes/type-generalizing.wast
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

(module

;; CHECK: (type $0 (func))
;; CHECK: (type $0 (func (result eqref)))

;; CHECK: (type $1 (func (result eqref)))
;; CHECK: (type $1 (func))

;; CHECK: (type $2 (func (param anyref)))

Expand All @@ -15,7 +15,7 @@

;; CHECK: (type $5 (func (param eqref)))

;; CHECK: (func $unconstrained (type $0)
;; CHECK: (func $unconstrained (type $1)
;; CHECK-NEXT: (local $x i32)
;; CHECK-NEXT: (local $y anyref)
;; CHECK-NEXT: (local $z (anyref i32))
Expand All @@ -30,7 +30,7 @@
(local $z (anyref i32))
)

;; CHECK: (func $implicit-return (type $1) (result eqref)
;; CHECK: (func $implicit-return (type $0) (result eqref)
;; CHECK-NEXT: (local $var eqref)
;; CHECK-NEXT: (local.get $var)
;; CHECK-NEXT: )
Expand All @@ -41,7 +41,7 @@
(local.get $var)
)

;; CHECK: (func $implicit-return-unreachable (type $1) (result eqref)
;; CHECK: (func $implicit-return-unreachable (type $0) (result eqref)
;; CHECK-NEXT: (local $var none)
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
Expand All @@ -53,7 +53,7 @@
(local.get $var)
)

;; CHECK: (func $if (type $1) (result eqref)
;; CHECK: (func $if (type $0) (result eqref)
;; CHECK-NEXT: (local $x eqref)
;; CHECK-NEXT: (local $y eqref)
;; CHECK-NEXT: (if (result eqref)
Expand All @@ -74,7 +74,7 @@
)
)

;; CHECK: (func $local-set (type $0)
;; CHECK: (func $local-set (type $1)
;; CHECK-NEXT: (local $var anyref)
;; CHECK-NEXT: (local.set $var
;; CHECK-NEXT: (ref.i31
Expand Down Expand Up @@ -147,6 +147,63 @@
)
)

;; CHECK: (func $local-get-set-chain (type $0) (result eqref)
;; CHECK-NEXT: (local $a eqref)
;; CHECK-NEXT: (local $b eqref)
;; CHECK-NEXT: (local $c eqref)
;; CHECK-NEXT: (local.set $b
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $c
;; CHECK-NEXT: (local.get $b)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $c)
;; CHECK-NEXT: )
(func $local-get-set-chain (result eqref)
(local $a i31ref)
(local $b i31ref)
(local $c i31ref)
;; Require that typeof($a) <: typeof($b).
(local.set $b
(local.get $a)
)
;; Require that typeof($b) <: typeof($c).
(local.set $c
(local.get $b)
)
;; Require that typeof($c) <: eqref.
(local.get $c)
)

;; CHECK: (func $local-get-set-chain-out-of-order (type $0) (result eqref)
;; CHECK-NEXT: (local $a eqref)
;; CHECK-NEXT: (local $b eqref)
;; CHECK-NEXT: (local $c eqref)
;; CHECK-NEXT: (local.set $c
;; CHECK-NEXT: (local.get $b)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $b
;; CHECK-NEXT: (local.get $a)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $c)
;; CHECK-NEXT: )
(func $local-get-set-chain-out-of-order (result eqref)
(local $a i31ref)
(local $b i31ref)
(local $c i31ref)
;; Require that typeof($b) <: typeof($c).
(local.set $c
(local.get $b)
)
;; Require that typeof($a) <: typeof($b). We don't know until we evaluate the
;; set above that this will constrain $a to eqref.
(local.set $b
(local.get $a)
)
;; Require that typeof($c) <: eqref.
(local.get $c)
)

;; CHECK: (func $local-tee (type $5) (param $dest eqref)
;; CHECK-NEXT: (local $var eqref)
;; CHECK-NEXT: (drop
Expand All @@ -173,7 +230,7 @@
)
)

;; CHECK: (func $i31-get (type $0)
;; CHECK: (func $i31-get (type $1)
;; CHECK-NEXT: (local $nullable i31ref)
;; CHECK-NEXT: (local $nonnullable (ref i31))
;; CHECK-NEXT: (local.set $nonnullable
Expand Down

0 comments on commit dbad282

Please sign in to comment.