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
8 changes: 6 additions & 2 deletions src/binaryen-c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5888,9 +5888,13 @@ void BinaryenSetTrapsNeverHappen(bool on) {
globalPassOptions.trapsNeverHappen = on;
}

bool BinaryenGetClosedWorld(void) { return globalPassOptions.closedWorld; }
bool BinaryenGetClosedWorld(void) {
return globalPassOptions.worldMode == WorldMode::Closed;
}

void BinaryenSetClosedWorld(bool on) { globalPassOptions.closedWorld = on; }
void BinaryenSetClosedWorld(bool on) {
globalPassOptions.worldMode = on ? WorldMode::Closed : WorldMode::Open;
}

bool BinaryenGetLowMemoryUnused(void) {
return globalPassOptions.lowMemoryUnused;
Expand Down
48 changes: 36 additions & 12 deletions src/ir/module-utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -481,12 +481,16 @@ struct CodeScanner : PostWalker<CodeScanner> {
};

void classifyTypeVisibility(Module& wasm,
InsertOrderedMap<HeapType, HeapTypeInfo>& types);
InsertOrderedMap<HeapType, HeapTypeInfo>& types,
WorldMode worldMode);

} // anonymous namespace

InsertOrderedMap<HeapType, HeapTypeInfo> collectHeapTypeInfo(
Module& wasm, TypeInclusion inclusion, VisibilityHandling visibility) {
InsertOrderedMap<HeapType, HeapTypeInfo>
collectHeapTypeInfo(Module& wasm,
WorldMode worldMode,
TypeInclusion inclusion,
VisibilityHandling visibility) {
// Collect module-level info.
TypeInfos info;
CodeScanner(wasm, info).walkModuleCode(&wasm);
Expand Down Expand Up @@ -593,7 +597,7 @@ InsertOrderedMap<HeapType, HeapTypeInfo> collectHeapTypeInfo(
}

if (visibility == VisibilityHandling::FindVisibility) {
classifyTypeVisibility(wasm, info.info);
classifyTypeVisibility(wasm, info.info, worldMode);
}

return std::move(info.info);
Expand All @@ -602,8 +606,9 @@ InsertOrderedMap<HeapType, HeapTypeInfo> collectHeapTypeInfo(
namespace {

void classifyTypeVisibility(Module& wasm,
InsertOrderedMap<HeapType, HeapTypeInfo>& types) {
for (auto type : getPublicHeapTypes(wasm)) {
InsertOrderedMap<HeapType, HeapTypeInfo>& types,
WorldMode worldMode) {
for (auto type : getPublicHeapTypes(wasm, worldMode)) {
if (auto it = types.find(type); it != types.end()) {
it->second.visibility = Visibility::Public;
}
Expand All @@ -624,7 +629,7 @@ void setIndices(IndexedHeapTypes& indexedTypes) {
} // anonymous namespace

std::vector<HeapType> collectHeapTypes(Module& wasm) {
auto info = collectHeapTypeInfo(wasm);
auto info = collectHeapTypeInfo(wasm, WorldMode::Open);
std::vector<HeapType> types;
types.reserve(info.size());
for (auto& [type, _] : info) {
Expand All @@ -633,18 +638,22 @@ std::vector<HeapType> collectHeapTypes(Module& wasm) {
return types;
}

std::vector<HeapType> getPublicHeapTypes(Module& wasm) {
std::vector<HeapType> getExposedPublicHeapTypes(Module& wasm) {
// Look at the types of imports as exports to get an initial set of public
// types, then traverse the types used by public types and collect the
// transitively reachable public types as well.
std::vector<HeapType> workList;
std::unordered_set<RecGroup> publicGroups;
std::unordered_set<HeapType> publicBasicTypes;

// The collected types.
std::vector<HeapType> publicTypes;

auto notePublic = [&](HeapType type) {
if (type.isBasic()) {
if (publicBasicTypes.insert(type).second) {
publicTypes.push_back(type);
}
return;
}
auto group = type.getRecGroup();
Expand Down Expand Up @@ -725,9 +734,23 @@ std::vector<HeapType> getPublicHeapTypes(Module& wasm) {
return publicTypes;
}

std::vector<HeapType> getPrivateHeapTypes(Module& wasm) {
auto info = collectHeapTypeInfo(
wasm, TypeInclusion::UsedIRTypes, VisibilityHandling::FindVisibility);
std::vector<HeapType> getPublicHeapTypes(Module& wasm, WorldMode worldMode) {
auto exposed = getExposedPublicHeapTypes(wasm);
std::vector<HeapType> publicTypes;
publicTypes.reserve(exposed.size());
for (auto type : exposed) {
if (!type.isBasic()) {
publicTypes.push_back(type);
}
}
return publicTypes;
}

std::vector<HeapType> getPrivateHeapTypes(Module& wasm, WorldMode worldMode) {
auto info = collectHeapTypeInfo(wasm,
worldMode,
TypeInclusion::UsedIRTypes,
VisibilityHandling::FindVisibility);
std::vector<HeapType> types;
types.reserve(info.size());
for (auto& [type, typeInfo] : info) {
Expand All @@ -739,7 +762,8 @@ std::vector<HeapType> getPrivateHeapTypes(Module& wasm) {
}

IndexedHeapTypes getOptimizedIndexedHeapTypes(Module& wasm) {
auto counts = collectHeapTypeInfo(wasm, TypeInclusion::BinaryTypes);
auto counts =
collectHeapTypeInfo(wasm, WorldMode::Open, TypeInclusion::BinaryTypes);

// Collect the rec groups.
std::unordered_map<RecGroup, size_t> groupIndices;
Expand Down
10 changes: 6 additions & 4 deletions src/ir/module-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -472,20 +472,22 @@ struct HeapTypeInfo {

InsertOrderedMap<HeapType, HeapTypeInfo> collectHeapTypeInfo(
Module& wasm,
WorldMode worldMode,
TypeInclusion inclusion = TypeInclusion::AllTypes,
VisibilityHandling visibility = VisibilityHandling::NoVisibility);

// Helper function for collecting all the non-basic heap types used in the
// module, i.e. the types that would appear in the type section.
std::vector<HeapType> collectHeapTypes(Module& wasm);

std::vector<HeapType> getExposedPublicHeapTypes(Module& wasm);
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.

This needs a comment, I think - how does it differ from getPublicHeapTypes?


// Collect all the heap types visible on the module boundary that cannot be
// changed. TODO: For open world use cases, this needs to include all subtypes
// of public types as well.
std::vector<HeapType> getPublicHeapTypes(Module& wasm);
// changed.
std::vector<HeapType> getPublicHeapTypes(Module& wasm, WorldMode worldMode);

// getHeapTypes - getPublicHeapTypes
std::vector<HeapType> getPrivateHeapTypes(Module& wasm);
std::vector<HeapType> getPrivateHeapTypes(Module& wasm, WorldMode worldMode);

struct IndexedHeapTypes {
std::vector<HeapType> types;
Expand Down
12 changes: 6 additions & 6 deletions src/ir/possible-contents.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -684,7 +684,7 @@ struct InfoCollector
SignatureResultLocation{func->type.getHeapType(), i}});
}

if (!options.closedWorld) {
if (options.worldMode == WorldMode::Open) {
info.calledFromOutside.insert(curr->func);
}
}
Expand Down Expand Up @@ -1711,7 +1711,7 @@ void TNHOracle::scan(Function* func,
void visitCallRef(CallRef* curr) {
// We can only optimize call_ref in closed world, as otherwise the
// call can go somewhere we can't see.
if (options.closedWorld) {
if (options.worldMode == WorldMode::Closed) {
info.callRefs.push_back(curr);
}
}
Expand Down Expand Up @@ -1834,7 +1834,7 @@ void TNHOracle::infer() {
// that type or a subtype, i.e., might be called when that type is seen in a
// call_ref target.
std::unordered_map<HeapType, std::vector<Function*>> typeFunctions;
if (options.closedWorld) {
if (options.worldMode == WorldMode::Closed) {
for (auto& func : wasm.functions) {
auto type = func->type;
auto& info = map[wasm.getFunction(func->name)];
Expand Down Expand Up @@ -1895,7 +1895,7 @@ void TNHOracle::infer() {
// We should only get here in a closed world, in which we know which
// functions might be called (the scan phase only notes callRefs if we are
// in fact in a closed world).
assert(options.closedWorld);
assert(options.worldMode == WorldMode::Closed);

auto iter = typeFunctions.find(targetType.getHeapType());
if (iter == typeFunctions.end()) {
Expand Down Expand Up @@ -2535,8 +2535,8 @@ Flower::Flower(Module& wasm, const PassOptions& options)
}

// In open world, public heap types may be written to from the outside.
if (!options.closedWorld) {
for (auto type : ModuleUtils::getPublicHeapTypes(wasm)) {
if (options.worldMode == WorldMode::Open) {
for (auto type : ModuleUtils::getPublicHeapTypes(wasm, options.worldMode)) {
if (type.isStruct()) {
auto& fields = type.getStruct().fields;
for (Index i = 0; i < fields.size(); i++) {
Expand Down
3 changes: 2 additions & 1 deletion src/ir/type-updating.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@

namespace wasm {

GlobalTypeRewriter::GlobalTypeRewriter(Module& wasm)
GlobalTypeRewriter::GlobalTypeRewriter(Module& wasm, WorldMode worldMode)
: wasm(wasm), publicGroups(wasm.features) {
// Find the heap types that are not publicly observable. Even in a closed
// world scenario, don't modify public types because we assume that they may
// be reflected on or used for linking. Figure out where each private type
// will be located in the builder.
typeInfo = ModuleUtils::collectHeapTypeInfo(
wasm,
worldMode,
ModuleUtils::TypeInclusion::UsedIRTypes,
ModuleUtils::VisibilityHandling::FindVisibility);

Expand Down
18 changes: 11 additions & 7 deletions src/ir/type-updating.h
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ class GlobalTypeRewriter {
// private types do not conflict with public types.
UniqueRecGroups publicGroups;

GlobalTypeRewriter(Module& wasm);
GlobalTypeRewriter(Module& wasm, WorldMode worldMode);
virtual ~GlobalTypeRewriter() {}

// Main entry point. This performs the entire process of creating new heap
Expand Down Expand Up @@ -427,7 +427,9 @@ class GlobalTypeRewriter {

// Helper for the repeating pattern of just updating Signature types using a
// map of old heap type => new Signature.
static void updateSignatures(const SignatureUpdates& updates, Module& wasm) {
static void updateSignatures(const SignatureUpdates& updates,
Module& wasm,
WorldMode worldMode) {
if (updates.empty()) {
return;
}
Expand All @@ -436,8 +438,10 @@ class GlobalTypeRewriter {
const SignatureUpdates& updates;

public:
SignatureRewriter(Module& wasm, const SignatureUpdates& updates)
: GlobalTypeRewriter(wasm), updates(updates) {
SignatureRewriter(Module& wasm,
const SignatureUpdates& updates,
WorldMode worldMode)
: GlobalTypeRewriter(wasm, worldMode), updates(updates) {
update();
}

Expand All @@ -448,7 +452,7 @@ class GlobalTypeRewriter {
sig.results = getTempType(iter->second.results);
}
}
} rewriter(wasm, updates);
} rewriter(wasm, updates, worldMode);
}

protected:
Expand All @@ -473,8 +477,8 @@ class TypeMapper : public GlobalTypeRewriter {

const TypeUpdates& mapping;

TypeMapper(Module& wasm, const TypeUpdates& mapping)
: GlobalTypeRewriter(wasm), mapping(mapping) {}
TypeMapper(Module& wasm, const TypeUpdates& mapping, WorldMode worldMode)
: GlobalTypeRewriter(wasm, worldMode), mapping(mapping) {}

void map() {
// Update the internals of types (struct fields, signatures, etc.) to
Expand Down
48 changes: 25 additions & 23 deletions src/pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,30 @@ struct InliningOptions {
Index partialInliningIfs = 0;
};

// Assume code outside of the module does not inspect or interact with GC and
// function references, with the goal of being able to aggressively optimize
// all user-defined types. The outside may hold on to references and pass them
// back in, but may not inspect their contents, call them, or reflect on their
// types in any way.
//
// By default we do not make this assumption, and assume anything that escapes
// to the outside may be inspected in detail, which prevents us from e.g.
// changing the type of any value that may escape except by refining it (so we
// can't remove or refine fields on an escaping struct type, for example,
// unless the new type declares the original type as a supertype).
//
// Note that the module can still have imports and exports - otherwise it
// could do nothing at all! - so the meaning of "closed world" is a little
// subtle here. We do still want to keep imports and exports unchanged, as
// they form a contract with the outside world. For example, if an import has
// two parameters, we can't remove one of them. A nuance regarding that is how
// type equality works between wasm modules using the isorecursive type
// system: not only do we need to not remove a parameter as just mentioned,
// but we also want to keep types of things on the boundary unchanged. For
// example, we should not change an exported function's signature, as the
// outside may need that type to properly call the export.
enum class WorldMode { Open, Closed };

struct PassOptions {
friend Pass;

Expand Down Expand Up @@ -196,29 +220,7 @@ struct PassOptions {
// creates it and we know it is all zeros right before the active segments are
// applied.)
bool zeroFilledMemory = false;
// Assume code outside of the module does not inspect or interact with GC and
// function references, with the goal of being able to aggressively optimize
// all user-defined types. The outside may hold on to references and pass them
// back in, but may not inspect their contents, call them, or reflect on their
// types in any way.
//
// By default we do not make this assumption, and assume anything that escapes
// to the outside may be inspected in detail, which prevents us from e.g.
// changing the type of any value that may escape except by refining it (so we
// can't remove or refine fields on an escaping struct type, for example,
// unless the new type declares the original type as a supertype).
//
// Note that the module can still have imports and exports - otherwise it
// could do nothing at all! - so the meaning of "closed world" is a little
// subtle here. We do still want to keep imports and exports unchanged, as
// they form a contract with the outside world. For example, if an import has
// two parameters, we can't remove one of them. A nuance regarding that is how
// type equality works between wasm modules using the isorecursive type
// system: not only do we need to not remove a parameter as just mentioned,
// but we also want to keep types of things on the boundary unchanged. For
// example, we should not change an exported function's signature, as the
// outside may need that type to properly call the export.
bool closedWorld = false;
WorldMode worldMode = WorldMode::Open;
// Whether to try to preserve debug info through, which are special calls.
bool debugInfo = false;
// Whether to generate StackIR during binary writing. This is on by default
Expand Down
14 changes: 9 additions & 5 deletions src/passes/AbstractTypeRefining.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ struct AbstractTypeRefining : public Pass {
return;
}

if (!getPassOptions().closedWorld) {
if (getPassOptions().worldMode == WorldMode::Open) {
Fatal() << "AbstractTypeRefining requires --closed-world";
}

Expand Down Expand Up @@ -116,7 +116,8 @@ struct AbstractTypeRefining : public Pass {
// module, given closed world, but we'd also need to make sure that
// we don't need to make any changes to public types that refer to
// them.
for (auto type : ModuleUtils::getPublicHeapTypes(*module)) {
for (auto type :
ModuleUtils::getPublicHeapTypes(*module, getPassOptions().worldMode)) {
createdTypes.insert(type);
}

Expand Down Expand Up @@ -289,16 +290,19 @@ struct AbstractTypeRefining : public Pass {
// that for Unsubtyping.
class AbstractTypeRefiningTypeMapper : public TypeMapper {
public:
AbstractTypeRefiningTypeMapper(Module& wasm, const TypeUpdates& mapping)
: TypeMapper(wasm, mapping) {}
AbstractTypeRefiningTypeMapper(Module& wasm,
const TypeUpdates& mapping,
WorldMode worldMode)
: TypeMapper(wasm, mapping, worldMode) {}

std::optional<HeapType> getDeclaredSuperType(HeapType oldType) override {
// We do not want to update subtype relationships.
return oldType.getDeclaredSuperType();
}
};

AbstractTypeRefiningTypeMapper(*module, mapping).map();
AbstractTypeRefiningTypeMapper(*module, mapping, getPassOptions().worldMode)
.map();

// Refinalize to propagate the type changes we made. For example, a refined
// cast may lead to a struct.get reading a more refined type using that
Expand Down
2 changes: 1 addition & 1 deletion src/passes/ConstantFieldPropagation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ struct ConstantFieldPropagation : public Pass {
return;
}

if (!getPassOptions().closedWorld) {
if (getPassOptions().worldMode == WorldMode::Open) {
Fatal() << "CFP requires --closed-world";
}

Expand Down
Loading
Loading