Skip to content
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
76 changes: 76 additions & 0 deletions src/ir/table-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,80 @@ bool usesExpressions(ElementSegment* curr, Module* module) {
return !allElementsRefFunc || hasSpecializedType;
}

TableInfoMap computeTableInfo(Module& wasm, bool initialContentsImmutable) {
// Set up the initial info.
TableInfoMap tables;
if (wasm.tables.empty()) {
return tables;
}
for (auto& table : wasm.tables) {
tables[table->name].initialContentsImmutable = initialContentsImmutable;
tables[table->name].flatTable =
std::make_unique<TableUtils::FlatTable>(wasm, *table);
}

// Next, look at the imports and exports.

for (auto& table : wasm.tables) {
if (table->imported()) {
tables[table->name].mayBeModified = true;
}
}

for (auto& ex : wasm.exports) {
if (ex->kind == ExternalKind::Table) {
tables[*ex->getInternalName()].mayBeModified = true;
}
}

// Find which tables have sets, by scanning for instructions. Only do so if we
// might learn anything new.
auto hasUnmodifiableTable = false;
for (auto& [_, info] : tables) {
if (!info.mayBeModified) {
hasUnmodifiableTable = true;
break;
}
}
if (hasUnmodifiableTable) {
using TablesWithSet = std::unordered_set<Name>;
Copy link
Member

Choose a reason for hiding this comment

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

Let's do an early return here when !hasUnmodifiableTable to decrease the indentation below.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done.


ModuleUtils::ParallelFunctionAnalysis<TablesWithSet> analysis(
wasm, [&](Function* func, TablesWithSet& tablesWithSet) {
if (func->imported()) {
return;
}

struct Finder : public PostWalker<Finder> {
TablesWithSet& tablesWithSet;

Finder(TablesWithSet& tablesWithSet) : tablesWithSet(tablesWithSet) {}

void visitTableSet(TableSet* curr) {
tablesWithSet.insert(curr->table);
}
void visitTableFill(TableFill* curr) {
tablesWithSet.insert(curr->table);
}
void visitTableCopy(TableCopy* curr) {
tablesWithSet.insert(curr->destTable);
}
void visitTableInit(TableInit* curr) {
tablesWithSet.insert(curr->table);
}
};

Finder(tablesWithSet).walkFunction(func);
});

for (auto& [_, names] : analysis.map) {
for (auto name : names) {
tables[name].mayBeModified = true;
}
}
}

return tables;
}

} // namespace wasm::TableUtils
34 changes: 34 additions & 0 deletions src/ir/table-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,40 @@ std::set<Name> getFunctionsNeedingElemDeclare(Module& wasm);
// do so, and some do not, depending on their type and use.)
bool usesExpressions(ElementSegment* curr, Module* module);

// Information about a table's optimizability.
struct TableInfo {
// Whether the table may be modifed at runtime, either because it is imported
// or exported, or table.set operations exist for it in the code.
bool mayBeModified = false;

// Whether we can assume that the initial contents are immutable. See the
// toplevel comment.
bool initialContentsImmutable = false;
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure which comment this is referring to, and I'm not sure just by reading this how initialContentsImmutable differs from mayBeModified.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ah, good point, I moved code but not all the dependent comments 😄 Fixed.

I also clarified how the properties tie into each other.


std::unique_ptr<TableUtils::FlatTable> flatTable;

// Whether we can optimize using this table's data, that is, we know something
// useful about the data there at compile time. The specifics about what we
// know are in the above fields.
bool canOptimize() const {
// We can optimize if:
// * Either the table can't be modified at all, or it can be modified but
// the initial contents are immutable (so we can optimize those
// contents, even if other things might be appended later).
// * The table is flat (so we can see what is in it).
return (!mayBeModified || initialContentsImmutable) && flatTable->valid;
}
};

// A map of tables to their info.
using TableInfoMap = std::unordered_map<Name, TableInfo>;

// Compute a map with table optimizability info. We can be told that the initial
// contents of the tables are immutable (that is, existing data is not
// overwritten, but new things may be appended).
TableInfoMap computeTableInfo(Module& wasm,
bool initialContentsImmutable = false);

} // namespace wasm::TableUtils

#endif // wasm_ir_table_h
114 changes: 13 additions & 101 deletions src/passes/Directize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,36 +46,14 @@ namespace wasm {

namespace {

struct TableInfo {
// Whether the table may be modifed at runtime, either because it is imported
// or exported, or table.set operations exist for it in the code.
bool mayBeModified = false;

// Whether we can assume that the initial contents are immutable. See the
// toplevel comment.
bool initialContentsImmutable = false;

std::unique_ptr<TableUtils::FlatTable> flatTable;

bool canOptimize() const {
// We can optimize if:
// * Either the table can't be modified at all, or it can be modified but
// the initial contents are immutable (so we can optimize them).
// * The table is flat.
return (!mayBeModified || initialContentsImmutable) && flatTable->valid;
}
};

using TableInfoMap = std::unordered_map<Name, TableInfo>;

struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> {
bool isFunctionParallel() override { return true; }

std::unique_ptr<Pass> create() override {
return std::make_unique<FunctionDirectizer>(tables);
}

FunctionDirectizer(const TableInfoMap& tables) : tables(tables) {}
FunctionDirectizer(const TableUtils::TableInfoMap& tables) : tables(tables) {}

void visitCallIndirect(CallIndirect* curr) {
auto& table = tables.at(curr->table);
Expand Down Expand Up @@ -114,7 +92,7 @@ struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> {
}

private:
const TableInfoMap& tables;
const TableUtils::TableInfoMap& tables;

bool changedTypes = false;

Expand All @@ -123,7 +101,7 @@ struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> {
// that is, whether we know a direct call target, or we know it will trap, or
// if we know nothing.
CallUtils::IndirectCallInfo getTargetInfo(Expression* target,
const TableInfo& table,
const TableUtils::TableInfo& table,
CallIndirect* original) {
auto* c = target->dynCast<Const>();
if (!c) {
Expand Down Expand Up @@ -165,7 +143,7 @@ struct FunctionDirectizer : public WalkerPass<PostWalker<FunctionDirectizer>> {
// with an unreachable.
void makeDirectCall(const std::vector<Expression*>& operands,
Expression* c,
const TableInfo& table,
const TableUtils::TableInfo& table,
CallIndirect* original) {
auto info = getTargetInfo(c, table, original);
if (std::get_if<CallUtils::Unknown>(&info)) {
Expand Down Expand Up @@ -211,84 +189,18 @@ struct Directize : public Pass {
auto initialContentsImmutable =
hasArgument("directize-initial-contents-immutable");

// Set up the initial info.
TableInfoMap tables;
for (auto& table : module->tables) {
tables[table->name].initialContentsImmutable = initialContentsImmutable;
tables[table->name].flatTable =
std::make_unique<TableUtils::FlatTable>(*module, *table);
}

// Next, look at the imports and exports.

for (auto& table : module->tables) {
if (table->imported()) {
tables[table->name].mayBeModified = true;
}
}

for (auto& ex : module->exports) {
if (ex->kind == ExternalKind::Table) {
tables[*ex->getInternalName()].mayBeModified = true;
}
}

// This may already be enough information to know that we can't optimize
// anything. If so, skip scanning all the module contents.
auto canOptimize = [&]() {
for (auto& [_, info] : tables) {
if (info.canOptimize()) {
return true;
}
}
return false;
};

if (!canOptimize()) {
return;
}

// Find which tables have sets.
auto tables =
TableUtils::computeTableInfo(*module, initialContentsImmutable);

using TablesWithSet = std::unordered_set<Name>;

ModuleUtils::ParallelFunctionAnalysis<TablesWithSet> analysis(
*module, [&](Function* func, TablesWithSet& tablesWithSet) {
if (func->imported()) {
return;
}

struct Finder : public PostWalker<Finder> {
TablesWithSet& tablesWithSet;

Finder(TablesWithSet& tablesWithSet) : tablesWithSet(tablesWithSet) {}

void visitTableSet(TableSet* curr) {
tablesWithSet.insert(curr->table);
}
void visitTableFill(TableFill* curr) {
tablesWithSet.insert(curr->table);
}
void visitTableCopy(TableCopy* curr) {
tablesWithSet.insert(curr->destTable);
}
void visitTableInit(TableInit* curr) {
tablesWithSet.insert(curr->table);
}
};

Finder(tablesWithSet).walkFunction(func);
});

for (auto& [_, names] : analysis.map) {
for (auto name : names) {
tables[name].mayBeModified = true;
// Stop if we cannot optimize anything.
auto hasOptimizableTable = false;
for (auto& [_, info] : tables) {
if (info.canOptimize()) {
hasOptimizableTable = true;
break;
}
}

// Perhaps the new information about tables with sets shows we cannot
// optimize.
if (!canOptimize()) {
if (!hasOptimizableTable) {
return;
}

Expand Down
18 changes: 13 additions & 5 deletions src/passes/RemoveUnusedModuleElements.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "ir/module-utils.h"
#include "ir/struct-utils.h"
#include "ir/subtypes.h"
#include "ir/table-utils.h"
#include "ir/utils.h"
#include "pass.h"
#include "support/insert_ordered.h"
Expand Down Expand Up @@ -172,11 +173,6 @@ struct Noter : public PostWalker<Noter, UnifiedExpressionVisitor<Noter>> {
// the heap type we call with.
reference({ModuleElementKind::Table, curr->table});
noteIndirectCall(curr->table, curr->heapType);
// Note a possible call of a function reference as well, as something might
// be written into the table during runtime. With precise tracking of what
// is written into the table we could do better here; we could also see
// which tables are immutable. TODO
noteCallRef(curr->heapType);
}

void visitCallRef(CallRef* curr) {
Expand Down Expand Up @@ -413,6 +409,8 @@ struct Analyzer {

std::unordered_set<IndirectCall> usedIndirectCalls;

std::optional<TableUtils::TableInfoMap> tableInfoMap;

void useIndirectCall(IndirectCall call) {
auto [_, inserted] = usedIndirectCalls.insert(call);
if (!inserted) {
Expand All @@ -434,6 +432,16 @@ struct Analyzer {
reference({ModuleElementKind::ElementSegment, elemInfo.name});
}
}

// Note a possible call of a function reference as well, if something else
// might be written into the table during runtime.
// TODO: Add an option for immutable initial content like Directize?
if (!tableInfoMap) {
tableInfoMap = TableUtils::computeTableInfo(*module);
}
if ((*tableInfoMap)[table].mayBeModified) {
useCallRefType(type);
}
}

void useRefFunc(Name func) {
Expand Down
Loading
Loading