Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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::make_pair(callIndirect->heapType, Inexact));
Comment thread
stevenfontanella marked this conversation as resolved.
Outdated
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[std::pair(newType, exactness)];
Comment thread
stevenfontanella marked this conversation as resolved.
Outdated
if (!targetEffects) {
targetEffects = oldEffects;
} else {
Expand Down
97 changes: 62 additions & 35 deletions src/passes/GlobalEffects.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,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 @@ -161,19 +161,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 @@ -195,19 +196,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 @@ -233,7 +243,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 @@ -243,37 +253,54 @@ CallGraph buildCallGraph(const Module& module,
}

// Function -> Type
allFunctionTypes.insert(caller->type.getHeapType());
for (HeapType calleeType : callerInfo.indirectCalledTypes) {
callees.insert(calleeType);
allFunctionTypes.insert(std::pair(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);
Comment thread
stevenfontanella marked this conversation as resolved.
push(std::pair(type, Inexact));
break;
}
case Inexact: {
if (auto super = type.getDeclaredSuperType()) {
callGraph[std::pair(*super, Inexact)].insert(typeAndExactness);
push(std::pair(*super, Inexact));
}
break;
}
}
});
(void)superTypeGraph.traverseDepthFirst();

return callGraph;
Expand Down Expand Up @@ -310,8 +337,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 @@ -410,7 +437,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 @@ -2743,8 +2743,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
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