Skip to content
Open
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
6 changes: 4 additions & 2 deletions src/ir/effects.h
Original file line number Diff line number Diff line change
Expand Up @@ -758,7 +758,8 @@ class EffectAnalyzer {
parent.implicitTrap = true;

const EffectAnalyzer* callTargetEffects = nullptr;
if (auto it = parent.module.indirectCallEffects.find(curr->heapType);
if (auto it =
parent.module.indirectCallEffects.find({curr->heapType, Inexact});
it != parent.module.indirectCallEffects.end()) {
callTargetEffects = it->second.get();
}
Expand All @@ -771,7 +772,8 @@ class EffectAnalyzer {

const EffectAnalyzer* callTargetEffects = nullptr;
if (auto it = parent.module.indirectCallEffects.find(
curr->target->type.getHeapType());
{curr->target->type.getHeapType(),
curr->target->type.getExactness()});
it != parent.module.indirectCallEffects.end()) {
callTargetEffects = it->second.get();
}
Expand Down
11 changes: 7 additions & 4 deletions src/ir/linear-execution.h
Original file line number Diff line number Diff line change
Expand Up @@ -185,8 +185,10 @@ struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> {
return true;
}

auto* effects = find_or_null(self->getModule()->indirectCallEffects,
callRef->target->type.getHeapType());
auto* effects =
find_or_null(self->getModule()->indirectCallEffects,
std::pair(callRef->target->type.getHeapType(),
callRef->target->type.getExactness()));
if (!effects) {
return false;
}
Expand All @@ -201,8 +203,9 @@ struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> {

bool refutesThrowEffect = false;
if (self->getModule()) {
if (auto* effects = find_or_null(
self->getModule()->indirectCallEffects, callIndirect->heapType);
if (auto* effects =
find_or_null(self->getModule()->indirectCallEffects,
std::pair(callIndirect->heapType, Inexact));
effects) {
refutesThrowEffect = !(*effects)->throws_;
}
Expand Down
8 changes: 5 additions & 3 deletions src/ir/type-updating.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -329,16 +329,18 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) {
// Update indirect call effects per type.
// When A is rewritten to B, B inherits the effects of A and A loses its
// effects.
std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>>
std::unordered_map<std::pair<HeapType, Exactness>,
std::shared_ptr<const EffectAnalyzer>>
newTypeEffects;
for (auto& [oldType, oldEffects] : wasm.indirectCallEffects) {
for (auto& [oldTypeExact, oldEffects] : wasm.indirectCallEffects) {
if (!oldEffects) {
continue;
}

auto [oldType, exactness] = oldTypeExact;
auto newType = updater.getNew(oldType);
std::shared_ptr<const EffectAnalyzer>& targetEffects =
newTypeEffects[newType];
newTypeEffects[{newType, exactness}];
if (!targetEffects) {
targetEffects = oldEffects;
} else {
Expand Down
103 changes: 67 additions & 36 deletions src/passes/GlobalEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ struct FuncInfo {
std::unordered_set<Name> calledFunctions;

// Types that are targets of indirect calls.
std::unordered_set<HeapType> indirectCalledTypes;
std::unordered_set<std::pair<HeapType, Exactness>> indirectCalledTypes;
};

// Only funcs that are referenced may be the target of an indirect call. A
Expand Down Expand Up @@ -162,19 +162,20 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
funcInfo.calledFunctions.insert(call->target);
} else if (effects.calls &&
options.worldMode == WorldMode::Closed) {
HeapType type;
std::pair<HeapType, Exactness> typeExact;
if (auto* callRef = curr->dynCast<CallRef>()) {
// call_ref on unreachable does not have a call effect,
// so this must be a HeapType.
type = callRef->target->type.getHeapType();
typeExact = {callRef->target->type.getHeapType(),
callRef->target->type.getExactness()};
} else if (auto* callIndirect = curr->dynCast<CallIndirect>()) {
type = callIndirect->heapType;
typeExact = {callIndirect->heapType, Inexact};
} else {
funcInfo.effects = std::nullopt;
return;
}

funcInfo.indirectCalledTypes.insert(type);
funcInfo.indirectCalledTypes.insert(typeExact);
} else if (effects.calls) {
assert(options.worldMode == WorldMode::Open);
funcInfo.effects = std::nullopt;
Expand All @@ -196,19 +197,28 @@ std::map<Function*, FuncInfo> analyzeFuncs(Module& module,
return std::move(analysis.map);
}

using CallGraphNode = std::variant<Function*, HeapType>;
using CallGraphNode = std::variant<Function*, std::pair<HeapType, Exactness>>;

// Call graph for indirect and direct calls.
//
// key (caller) -> value (callee)
// Function -> Function : direct call
// Function -> HeapType : indirect call to the given HeapType
// HeapType -> Function : The function `callee` has the type `caller`. The
// HeapType may essentially 'call' any of its
// potential implementations.
// HeapType -> HeapType : `callee` is a subtype of `caller`. A call_ref
// could target any subtype of the ref, so we need to
// aggregate effects of subtypes of the target type.
// Function -> Function :
// direct call
// Function -> HeapType :
// indirect call to the given HeapType (exact or inexact).
// HeapType -> Function :
// The function `callee` has the type `caller`. The HeapType may essentially
// 'call' any of its potential implementations. The HeapType is always Exact
// for these edges.
// HeapType -> HeapType :
// `callee` is a subtype of `caller`. An indirect call with an Inexact type
// could target any subtype of the ref, so we aggregate effects of subtypes of
// the target type. If B is a subtype of A, then we have edges:
// A (inexact) -> B (inexact)
// A (inexact) -> A (exact)
// B (inexact) -> B (exact)
// As a result, calls to (inexact A) include B's effects, and calls to
// (exact A) only include A's effects.
//
// If we're running in an open world, we only include Function -> Function
// edges, and don't compute effects for indirect calls, conservatively assuming
Expand All @@ -234,7 +244,7 @@ CallGraph buildCallGraph(const Module& module,
return callGraph;
}

std::unordered_set<HeapType> allFunctionTypes;
std::unordered_set<std::pair<HeapType, Exactness>> allFunctionTypes;
for (const auto& [caller, callerInfo] : funcInfos) {
auto& callees = callGraph[caller];

Expand All @@ -244,37 +254,54 @@ CallGraph buildCallGraph(const Module& module,
}

// Function -> Type
allFunctionTypes.insert(caller->type.getHeapType());
for (HeapType calleeType : callerInfo.indirectCalledTypes) {
callees.insert(calleeType);
allFunctionTypes.insert({caller->type.getHeapType(), Exact});
for (auto calleeTypeExact : callerInfo.indirectCalledTypes) {
callees.insert(calleeTypeExact);

// Add the key to ensure the lookup doesn't fail for indirect calls to
// uninhabited types.
callGraph[calleeType];
callGraph[calleeTypeExact];
allFunctionTypes.insert(calleeTypeExact);
}

// Type -> Function
if (referencedFuncs.contains(caller)) {
callGraph[caller->type.getHeapType()].insert(caller);
callGraph[std::pair(caller->type.getHeapType(), Exact)].insert(caller);
}
}

// Type -> Type
// Do a DFS up the type hierarchy for all function implementations.
// We are essentially walking up each supertype chain and adding edges from
// super -> subtype, but doing it via DFS to avoid repeated work.
Graph superTypeGraph(allFunctionTypes.begin(),
allFunctionTypes.end(),
[&callGraph](const auto& push, HeapType t) {
// Not needed except that during lookup we expect the
// key to exist.
callGraph[t];

if (auto super = t.getDeclaredSuperType()) {
callGraph[*super].insert(t);
push(*super);
}
});
Graph superTypeGraph(
allFunctionTypes.begin(),
allFunctionTypes.end(),
[&callGraph](const auto& push,
std::pair<HeapType, Exactness> typeAndExactness) {
// Not needed except that during lookup we expect the
// key to exist.
callGraph[typeAndExactness];

auto [type, exactness] = typeAndExactness;

// The supertype of an exact type is its inexact type.
// The supertype of an inexact type is its normal inexact supertype.
switch (exactness) {
case Exact: {
callGraph[std::pair(type, Inexact)].insert(typeAndExactness);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
callGraph[std::pair(type, Inexact)].insert(typeAndExactness);
callGraph[{type, Inexact}].insert(typeAndExactness);

and twice more below.

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 doesn't compile, I think because it would implicitly be calling two constructors in a row which isn't allowed (initializer_list -> pair -> variant).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ah sad

push({type, Inexact});
break;
}
case Inexact: {
if (auto super = type.getDeclaredSuperType()) {
callGraph[std::pair(*super, Inexact)].insert(typeAndExactness);
push({*super, Inexact});
}
break;
}
}
});
(void)superTypeGraph.traverseDepthFirst();

// Add Type -> Function edges to account for inexact imports. For (ref.func)
Expand All @@ -295,7 +322,11 @@ CallGraph buildCallGraph(const Module& module,
}

subtypes.iterSubTypes(func->type.getHeapType(), [&](auto subtype, int _) {
callGraph[subtype].insert(func);
// The import's effects must be included in both calls to its exact and
// inexact subtypes. Adding an edge from the exact subtype is enough
// because we propagate effects up the subtype chain and exact types are a
// subtype of their inexact type.
callGraph[std::pair(subtype, Exact)].insert(func);
return true;
});
});
Expand Down Expand Up @@ -334,8 +365,8 @@ void propagateEffects(
const Module& module,
const PassOptions& passOptions,
std::map<Function*, FuncInfo>& funcInfos,
std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>>&
typeEffects,
std::unordered_map<std::pair<HeapType, Exactness>,
std::shared_ptr<const EffectAnalyzer>>& typeEffects,
const CallGraph& callGraph) {
// We only care about Functions that are roots, not types.
// A type would be a root if a function exists with that type, but no-one
Expand Down Expand Up @@ -434,7 +465,7 @@ void propagateEffects(

// Assign each function's effects to its CC effects.
for (auto node : cc) {
std::visit(overloaded{[&](HeapType type) {
std::visit(overloaded{[&](std::pair<HeapType, Exactness> type) {
if (ccEffects != UnknownEffects) {
typeEffects[type] = ccEffects;
}
Expand Down
4 changes: 2 additions & 2 deletions src/wasm.h
Original file line number Diff line number Diff line change
Expand Up @@ -2746,8 +2746,8 @@ class Module {
// This data is only meaningful for indirect calls. If no indirect call
// exists to a function, the data can be out of date (no effort is made to
// clean up the data if e.g. all indirect calls to a function are removed).
// TODO: Account for exactness here.
std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>>
std::unordered_map<std::pair<HeapType, Exactness>,
std::shared_ptr<const EffectAnalyzer>>
indirectCallEffects;

MixedArena allocator;
Expand Down
10 changes: 8 additions & 2 deletions test/lit/passes/global-effects-closed-world-tnh.wast
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,18 @@
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (call_ref $sub
;; CHECK-NEXT: (ref.cast (ref (exact $sub))
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $downcast-import (param $ref (ref $super))
;; $import's exact type could be $sub although we imported it as $super.
;; Since we don't know, we need to propagate $import's effects to $sub as
;; well.
;; Since we don't know, we need to propagate $import's effects to $sub and
;; (exact $sub) as well.
(call_ref $sub (ref.cast (ref $sub) (local.get $ref)))
(call_ref $sub (ref.cast (ref (exact $sub)) (local.get $ref)))
)
)

Expand Down
8 changes: 3 additions & 5 deletions test/lit/passes/global-effects-closed-world.wast
Original file line number Diff line number Diff line change
Expand Up @@ -167,14 +167,12 @@
)

;; CHECK: (func $calls-ref-with-exact-supertype (type $3) (param $func (ref (exact $super)))
;; CHECK-NEXT: (call_ref $super
;; CHECK-NEXT: (local.get $func)
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $calls-ref-with-exact-supertype (param $func (ref (exact $super)))
;; Same as above but this time our reference is the exact supertype
;; so we know not to aggregate effects from the subtype.
;; TODO: this case doesn't optimize today. Add exact ref support in the pass.
;; so we know not to aggregate effects from the subtype. This can only
;; call $nop-with-supertype which has no effects.
(call_ref $super (local.get $func))
)
)
Expand Down
Loading