Skip to content

[Strings] Add a string-builtins feature, and lift/lower automatically when enabled #7601

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

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
start
  • Loading branch information
kripken committed May 15, 2025
commit 5db98f48ba75f817ac7464e445186b52c9af9be2
22 changes: 16 additions & 6 deletions src/pass.h
Original file line number Diff line number Diff line change
Expand Up @@ -324,26 +324,36 @@ struct PassRunner {
// warning.
void addIfNoDWARFIssues(std::string passName);

// Adds the default set of optimization passes; this is
// what -O does.
void addDefaultOptimizationPasses();
// By default, we do not know if we are running first in the ordering of
// optimization passes, or last - we could be anywhere.
struct Ordering {
bool first = false;
bool last = false;
};
static const UnknownOrdering;

// Adds the default set of optimization passes; this is what -O does.
//
// The ordering indicates our position relative to other default
// optimizations, that is, if ordering.first then we are first.
void addDefaultOptimizationPasses(Ordering ordering = UnknownOrdering);

// Adds the default optimization passes that work on
// individual functions.
void addDefaultFunctionOptimizationPasses();
void addDefaultFunctionOptimizationPasses(Ordering ordering = UnknownOrdering);

// Adds the default optimization passes that work on
// entire modules as a whole, and make sense to
// run before function passes.
void addDefaultGlobalOptimizationPrePasses();
void addDefaultGlobalOptimizationPrePasses(Ordering ordering = UnknownOrdering);

// Adds the default optimization passes that work on
// entire modules as a whole, and make sense to
// run after function passes.
// This is run at the very end of the optimization
// process - you can assume no other opts will be run
// afterwards.
void addDefaultGlobalOptimizationPostPasses();
void addDefaultGlobalOptimizationPostPasses(Ordering ordering = UnknownOrdering);

// Run the passes on the module
void run();
Expand Down
30 changes: 23 additions & 7 deletions src/passes/pass.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -605,13 +605,13 @@ void PassRunner::addIfNoDWARFIssues(std::string passName) {
}
}

void PassRunner::addDefaultOptimizationPasses() {
addDefaultGlobalOptimizationPrePasses();
addDefaultFunctionOptimizationPasses();
addDefaultGlobalOptimizationPostPasses();
void PassRunner::addDefaultOptimizationPasses(Ordering ordering) {
addDefaultGlobalOptimizationPrePasses(ordering);
addDefaultFunctionOptimizationPasses(ordering);
addDefaultGlobalOptimizationPostPasses(ordering);
}

void PassRunner::addDefaultFunctionOptimizationPasses() {
void PassRunner::addDefaultFunctionOptimizationPasses(Ordering ordering) {
// All the additions here are optional if DWARF must be preserved. That is,
// when DWARF is relevant we run fewer optimizations.
// FIXME: support DWARF in all of them.
Expand Down Expand Up @@ -723,7 +723,14 @@ void PassRunner::addDefaultFunctionOptimizationPasses() {
addIfNoDWARFIssues("vacuum"); // just to be safe
}

void PassRunner::addDefaultGlobalOptimizationPrePasses() {
void PassRunner::addDefaultGlobalOptimizationPrePasses(Ordering ordering) {
// If we are optimizing string builtins then we lift at the very start of the
// optimization pipeline, not just at the beginning here, but only when we are
// ordered before other bundles of passes.
if (wasm->features.hasStringBuiltins() && options.optimizeLevel >= 2 &&
ordering.first) {
addIfNoDWARFIssues("string-lifting");
}
// Removing duplicate functions is fast and saves work later.
addIfNoDWARFIssues("duplicate-function-elimination");
// Do a global cleanup before anything heavy, as it is fairly fast and can
Expand Down Expand Up @@ -772,7 +779,7 @@ void PassRunner::add(std::string passName, std::optional<std::string> passArg) {
doAdd(std::move(pass));
}

void PassRunner::addDefaultGlobalOptimizationPostPasses() {
void PassRunner::addDefaultGlobalOptimizationPostPasses(Ordering ordering) {
if (options.optimizeLevel >= 2 || options.shrinkLevel >= 1) {
addIfNoDWARFIssues("dae-optimizing");
}
Expand All @@ -794,6 +801,15 @@ void PassRunner::addDefaultGlobalOptimizationPostPasses() {
} else {
addIfNoDWARFIssues("simplify-globals");
}

// Lower away strings at the very very end. We do this before
// remove-unused-module-elements so we don't add unused imports, and also
// before reorder-globals, which will sort the new globals.
if (wasm->features.hasStringBuiltins() && options.optimizeLevel >= 2 &&
ordering.last) {
addIfNoDWARFIssues("string-lowering");
}

addIfNoDWARFIssues("remove-unused-module-elements");
if (options.optimizeLevel >= 2 && wasm->features.hasStrings()) {
// Gather strings to globals right before reorder-globals, which will then
Expand Down
7 changes: 6 additions & 1 deletion src/wasm-features.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,12 @@ struct FeatureSet {
// it does nothing. Binaryen always accepts LEB call-indirect encodings.
CallIndirectOverlong = 1 << 20,
CustomDescriptors = 1 << 21,
StringBuiltins = 1 << 22,
MVP = None,
// Keep in sync with llvm default features:
// https://github.com/llvm/llvm-project/blob/c7576cb89d6c95f03968076e902d3adfd1996577/clang/lib/Basic/Targets/WebAssembly.cpp#L150-L153
Default = SignExt | MutableGlobals,
All = (1 << 22) - 1,
All = (1 << 23) - 1,
};

static std::string toString(Feature f) {
Expand Down Expand Up @@ -108,6 +109,8 @@ struct FeatureSet {
return "call-indirect-overlong";
case CustomDescriptors:
return "custom-descriptors";
case StringBuiltins:
return "string-builtins";
case MVP:
case Default:
case All:
Expand Down Expand Up @@ -168,6 +171,7 @@ struct FeatureSet {
bool hasCustomDescriptors() const {
return (features & CustomDescriptors) != 0;
}
bool hasStringBuiltins() const { return (features & StringBuiltins) != 0; }
bool hasAll() const { return (features & All) != 0; }

void set(FeatureSet f, bool v = true) {
Expand All @@ -194,6 +198,7 @@ struct FeatureSet {
void setFP16(bool v = true) { set(FP16, v); }
void setBulkMemoryOpt(bool v = true) { set(BulkMemoryOpt, v); }
void setCustomDescriptors(bool v = true) { set(CustomDescriptors, v); }
void setStringBuiltins(bool v = true) { set(StringBuiltins, v); }
void setMVP() { features = MVP; }
void setAll() { features = All; }

Expand Down