Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUFA: Assume nothing about the contents of public tables #7234

Merged
merged 8 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 6 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
44 changes: 38 additions & 6 deletions src/ir/possible-contents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,12 @@ namespace wasm {

namespace {

// Information that is shared with InfoCollector.
struct SharedInfo {
// The names of tables that are imported or exported.
std::unordered_set<Name> publicTables;
};

// The data we gather from each function, as we process them in parallel. Later
// this will be merged into a single big graph.
struct CollectedFuncInfo {
Expand Down Expand Up @@ -533,11 +539,14 @@ struct BreakTargetWalker : public PostWalker<SubType, VisitorType> {
// main flow will begin.
struct InfoCollector
: public BreakTargetWalker<InfoCollector, OverriddenVisitor<InfoCollector>> {
SharedInfo& shared;
CollectedFuncInfo& info;
const PassOptions& options;

InfoCollector(CollectedFuncInfo& info, const PassOptions& options)
: info(info), options(options) {}
InfoCollector(SharedInfo& shared,
CollectedFuncInfo& info,
const PassOptions& options)
: shared(shared), info(info), options(options) {}

// Check if a type is relevant for us. If not, we can ignore it entirely.
bool isRelevant(Type type) {
Expand Down Expand Up @@ -888,9 +897,16 @@ struct InfoCollector
curr->operands.push_back(target);
}
void visitCallIndirect(CallIndirect* curr) {
// TODO: the table identity could also be used here
// TODO: optimize the call target like CallRef
handleIndirectCall(curr, curr->heapType);

// If this goes to a public table, then we must root the output, as the
// table could contain anything at all, and calling functions there could
// return anything at all.
if (shared.publicTables.count(curr->table)) {
addRoot(curr);
}
// TODO: the table identity could also be used here in more ways
}
void visitCallRef(CallRef* curr) {
handleIndirectCall(curr, curr->target->type);
Expand Down Expand Up @@ -2121,10 +2137,26 @@ Flower::Flower(Module& wasm, const PassOptions& options)
std::cout << "parallel phase\n";
#endif

// First, collect information from each function.
// Compute shared info that we need for the main pass over each function, such
// as the imported/exported tables.
SharedInfo shared;

for (auto& table : wasm.tables) {
if (table->imported()) {
shared.publicTables.insert(table->name);
}
}

for (auto& ex : wasm.exports) {
if (ex->kind == ExternalKind::Table) {
shared.publicTables.insert(ex->value);
}
}

// Collect information from each function.
ModuleUtils::ParallelFunctionAnalysis<CollectedFuncInfo> analysis(
wasm, [&](Function* func, CollectedFuncInfo& info) {
InfoCollector finder(info, options);
InfoCollector finder(shared, info, options);

if (func->imported()) {
// Imports return unknown values.
Expand All @@ -2146,7 +2178,7 @@ Flower::Flower(Module& wasm, const PassOptions& options)
// Also walk the global module code (for simplicity, also add it to the
// function map, using a "function" key of nullptr).
auto& globalInfo = analysis.map[nullptr];
InfoCollector finder(globalInfo, options);
InfoCollector finder(shared, globalInfo, options);
finder.walkModuleCode(&wasm);

#ifdef POSSIBLE_CONTENTS_DEBUG
Expand Down
68 changes: 68 additions & 0 deletions test/lit/passes/gufa-tables.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-opt -all --gufa -S -o - | filecheck %s

;; Non-private tables can contain anything, so we cannot assume anything about
;; the results of a call to them.
(module
;; CHECK: (type $type (func (result f64)))
(type $type (func (result f64)))

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

;; CHECK: (import "a" "b" (table $imported 30 40 funcref))
(import "a" "b" (table $imported 30 40 funcref))

;; CHECK: (table $exported 10 20 funcref)
(table $exported 10 20 funcref)

;; CHECK: (table $private 50 60 funcref)
(table $private 50 60 funcref)

;; CHECK: (export "func" (func $func))

;; CHECK: (export "table" (table $exported))
(export "table" (table $exported))

;; CHECK: (func $func (type $1)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call_indirect $exported (type $type)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call_indirect $imported (type $type)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (call_indirect $private (type $type)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (unreachable)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $func (export "func")
;; We cannot optimize anything with the exported or imported table.
(drop
(call_indirect $exported (type $type)
(i32.const 0)
)
)
(drop
(call_indirect $imported (type $type)
(i32.const 0)
)
)
;; We can optimize the private table: this will trap.
(drop
(call_indirect $private (type $type)
(i32.const 0)
)
)
)
)

Loading