Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
40 changes: 30 additions & 10 deletions scripts/fuzz_shell.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,29 @@ function printed(x, y) {
// Print bigints in legalized form, which is two 32-bit numbers of the low
// and high bits.
return (Number(x & 0xffffffffn) | 0) + ' ' + (Number(x >> 32n) | 0)
} else if (typeof x !== 'number') {
// Something that is not a number or string, like a reference. We can't
// print a reference because it could look different after opts - imagine
// that a function gets renamed internally (that is, the problem is that
// JS printing will emit some info about the reference and not a stable
// external representation of it). In those cases just print the type,
// which will be 'object' or 'function'.
return typeof x;
} else if (typeof x === 'object') {
// This may be one of the externref imports, in which case we can print its
// payload.
if (Object.hasOwn(x, 'payload')) {
return 'externref(' + x.payload + ')';
}
// Or maybe this is a JS error we caught.
if (x instanceof Error) {
return 'jserror';
}
// If this is a Wasm object, we can't access its type or any of its
// internal structure, which might have been changed by optimizations
// anyway. It might have a configured prototype, though, and that
// prototype may be an imported externref global we can identify by the
// payload we gave it.
return 'object(' + printed(Object.getPrototypeOf(x)) + ')';
} else if (typeof x === 'function') {
// We cannot print function names because they might have been changed by
// optimizations.
return 'function';
} else {
// A number. Print the whole thing.
assert(typeof x === 'number');
return '' + x;
}
}
Expand Down Expand Up @@ -468,8 +481,15 @@ function makeImports(module) {
baseImports[module] = {};
}
if (!baseImports[module][name]) {
// TODO: Use different payloads for different imports.
baseImports[module][name] = {};
// Compute a payload from the import names. This must be kept in sync
// with execution-results.h.
var payload = 0;
for (var name of [module, name]) {
for (var c of name) {
payload = (payload + c.charCodeAt(0)) % 251;
}
}
baseImports[module][name] = { payload };
}
}
}
Expand Down
8 changes: 8 additions & 0 deletions src/literal.h
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,14 @@ class Literal {
Literal externalize() const;
Literal internalize() const;

// Internalize an externalized value or externalize an internalized value,
// otherwise return the literal unmodified.
Literal unwrap() const;

// Get the JS prototype configured via this struct's descriptor, if it exists,
// or null. Assumes this is a reference value.
Literal getJSPrototype() const;

private:
Literal addSatSI8(const Literal& other) const;
Literal addSatUI8(const Literal& other) const;
Expand Down
119 changes: 102 additions & 17 deletions src/passes/GlobalTypeOptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,11 @@ struct GlobalTypeOptimization : public Pass {
// Combine the data from the functions.
functionSetGetInfos.combineInto(combinedSetGetInfos);

// Analyze functions called by JS to find fields holding configured
// prototypes that cannot be removed.
analyzeJSCalledFunctions(*module);
SubTypes subTypes(*module);

// Analyze the JS interface to find fields holding configured prototypes
// that cannot be removed.
analyzeJSInterface(*module, subTypes);
Copy link
Member

Choose a reason for hiding this comment

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

Does this PR need rebasing?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, just merged now.


// Propagate information to super and subtypes on set/get infos:
//
Expand Down Expand Up @@ -198,7 +200,6 @@ struct GlobalTypeOptimization : public Pass {
// subtypes (as wasm only allows the type to differ if the fields are
// immutable). Note that by making more things immutable we therefore
// make it possible to apply more specific subtypes in subtype fields.
SubTypes subTypes(*module);
StructUtils::TypeHierarchyPropagator<FieldInfo> propagator(subTypes);
auto dataFromSubsAndSupersMap = combinedSetGetInfos;
propagator.propagateToSuperAndSubTypes(dataFromSubsAndSupersMap);
Expand Down Expand Up @@ -278,7 +279,6 @@ struct GlobalTypeOptimization : public Pass {
// remove the field. That is so even if there are writes (it would be a
// pointless "write-only field").
auto hasNoReadsAnywhere = !dataFromSubsAndSupers[i].hasRead;

// Check for reads or writes in ourselves and our supers. If there are
// none, then operations only happen in our strict subtypes, and those
// subtypes can define the field there, and we don't need it here.
Expand Down Expand Up @@ -416,22 +416,107 @@ struct GlobalTypeOptimization : public Pass {
}
}

void analyzeJSCalledFunctions(Module& wasm) {
void analyzeJSInterface(Module& wasm, const SubTypes& subTypes) {
if (!wasm.features.hasCustomDescriptors()) {
return;
}
for (auto func : Intrinsics(wasm).getJSCalledFunctions()) {
// Look at the result types being returned to JS and make sure we preserve
// any configured prototypes they might expose.
for (auto type : wasm.getFunction(func)->getResults()) {
if (!type.isRef()) {
continue;

std::unordered_set<HeapType> subtypesExposed;

// Mark the relevant prototype field as read and return true iff we newly
// know we have to propate the exposure to subtypes.
auto noteExposed = [&](HeapType type, Exactness exact = Inexact) -> bool {
if (auto desc = type.getDescriptorType();
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
// This field holds a JS-visible prototype. Do not remove it.
combinedSetGetInfos[std::make_pair(*desc, exact)][0].noteRead();
}
if (exact == Inexact) {
return subtypesExposed.insert(type).second;
}
return false;
};

// Values flowing out to JS might have their prototype field on their
// descriptor read by JS.
auto flowOut = [&](Type type) {
if (type.isRef()) {
noteExposed(type.getHeapType(), type.getExactness());
}
};

// @binaryen.js.called functions are called from JS. Their results flow back
// out to JS.
for (auto f : Intrinsics(wasm).getJSCalledFunctions()) {
auto* func = wasm.getFunction(f);
for (auto type : func->getResults()) {
flowOut(type);
}
}

for (auto& ex : wasm.exports) {
switch (ex->kind) {
case ExternalKindImpl::Function: {
auto* func = wasm.getFunction(*ex->getInternalName());
for (auto type : func->getResults()) {
flowOut(type);
}
break;
}
case ExternalKindImpl::Table: {
auto* table = wasm.getTable(*ex->getInternalName());
flowOut(table->type);
break;
}
case ExternalKindImpl::Global: {
auto* global = wasm.getGlobal(*ex->getInternalName());
flowOut(global->type);
break;
}
if (auto desc = type.getHeapType().getDescriptorType();
desc && StructUtils::hasPossibleJSPrototypeField(*desc)) {
// This field holds a JS-visible prototype. Do not remove it.
auto exact = type.getExactness();
combinedSetGetInfos[std::make_pair(*desc, exact)][0].noteRead();
case ExternalKindImpl::Memory:
case ExternalKindImpl::Tag:
case ExternalKindImpl::Invalid:
break;
}
}

for (auto& func : wasm.functions) {
if (func->imported()) {
for (auto type : func->getParams()) {
flowOut(type);
}
}
}
for (auto& table : wasm.tables) {
if (table->imported()) {
flowOut(table->type);
}
}
for (auto& global : wasm.globals) {
if (global->imported() && global->mutable_) {
flowOut(global->type);
}
}

// Any type that is a subtype of an exposed type is also exposed. Propagate
// from supertypes to subtypes.
std::vector<HeapType> work(subtypesExposed.begin(), subtypesExposed.end());
while (!work.empty()) {
auto type = work.back();
work.pop_back();
if (type.isBasic()) {
// TODO: Unify this with the more incremental propagation below if
// SubTypes ever gets support for scanning basic types.
for (auto other : subTypes.types) {
if (HeapType::isSubType(other, type)) {
noteExposed(other);
}
}
} else {
for (auto sub : subTypes.getImmediateSubTypes(type)) {
if (noteExposed(sub)) {
work.push_back(sub);
}
}
}
}
Expand Down
Loading
Loading