Skip to content
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);
Comment thread
kripken marked this conversation as resolved.

// 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