summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/passes/CMakeLists.txt1
-rw-r--r--src/passes/J2CLOpts.cpp220
-rw-r--r--src/passes/pass.cpp2
-rw-r--r--src/passes/passes.h1
4 files changed, 224 insertions, 0 deletions
diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt
index 279c50983..1c8418b24 100644
--- a/src/passes/CMakeLists.txt
+++ b/src/passes/CMakeLists.txt
@@ -52,6 +52,7 @@ set(passes_SOURCES
InstrumentLocals.cpp
InstrumentMemory.cpp
Intrinsics.cpp
+ J2CLOpts.cpp
JSPI.cpp
LegalizeJSInterface.cpp
LimitSegments.cpp
diff --git a/src/passes/J2CLOpts.cpp b/src/passes/J2CLOpts.cpp
new file mode 100644
index 000000000..b1fb8bb1f
--- /dev/null
+++ b/src/passes/J2CLOpts.cpp
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2023 WebAssembly Community Group participants
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Optimize J2CL specifics construct to simplify them and enable further
+// optimizations by other passes.
+//
+
+#include "ir/global-utils.h"
+#include "ir/utils.h"
+#include "opt-utils.h"
+#include "pass.h"
+#include "passes.h"
+#include "wasm.h"
+
+namespace wasm {
+
+namespace {
+
+bool isOnceFunction(Function* f) { return f->name.hasSubstring("_@once@_"); }
+
+using AssignmentCountMap = std::unordered_map<Name, Index>;
+
+// A visitor to count the number of GlobalSets of each global so we can later
+// identify the number of assignments of the global.
+// TODO: parallelize
+class GlobalAssignmentCollector
+ : public WalkerPass<PostWalker<GlobalAssignmentCollector>> {
+public:
+ GlobalAssignmentCollector(AssignmentCountMap& assignmentCounts)
+ : assignmentCounts(assignmentCounts) {}
+
+ void visitGlobal(Global* curr) {
+ if (isInitialValue(curr->init)) {
+ return;
+ }
+ // J2CL normally doesn't set non-default initial value however just in
+ // case if other passes in bineryen does something we should back off
+ // by recording this as an assignment.
+ recordGlobalAssignment(curr->name);
+ }
+ void visitGlobalSet(GlobalSet* curr) { recordGlobalAssignment(curr->name); }
+
+private:
+ bool isInitialValue(Expression* expr) {
+ if (auto* constExpr = expr->dynCast<Const>()) {
+ return constExpr->value.isZero();
+ } else {
+ return expr->is<RefNull>();
+ }
+ }
+
+ void recordGlobalAssignment(Name name) {
+ // Avoid optimizing class initialization condition variable itself. If we
+ // were optimizing it then it would become "true" and would defeat its
+ // functionality and the clinit would never trigger during runtime.
+ if (name.startsWith("f_$initialized__")) {
+ return;
+ }
+ assignmentCounts[name]++;
+ }
+
+ AssignmentCountMap& assignmentCounts;
+};
+
+// A visitor that moves initialization of constant-like globals from "once"
+// functions to global init.
+// TODO: parallelize
+class ConstantHoister : public WalkerPass<PostWalker<ConstantHoister>> {
+public:
+ ConstantHoister(AssignmentCountMap& assignmentCounts)
+ : assignmentCounts(assignmentCounts) {}
+
+ int optimized = 0;
+
+ void visitFunction(Function* curr) {
+ if (!isOnceFunction(curr)) {
+ return;
+ }
+ int optimizedBefore = optimized;
+ if (auto* block = curr->body->dynCast<Block>()) {
+ for (auto*& expr : block->list) {
+ maybeHoistConstant(expr);
+ }
+ } else {
+ maybeHoistConstant(curr->body);
+ }
+
+ if (optimized != optimizedBefore) {
+ // We introduced "nop" instruction. Run the vacuum to cleanup.
+ // TODO: maybe we should not introduce "nop" in the first place and try
+ // removing instead.
+ PassRunner runner(getModule());
+ runner.add("vacuum");
+ runner.setIsNested(true);
+ runner.runOnFunction(curr);
+ }
+ }
+
+private:
+ void maybeHoistConstant(Expression* expr) {
+ auto set = expr->dynCast<GlobalSet>();
+ if (!set) {
+ return;
+ }
+
+ if (assignmentCounts[set->name] != 1) {
+ // The global assigned in multiple places, so it is not safe to
+ // hoist them as global constants.
+ return;
+ }
+
+ if (!GlobalUtils::canInitializeGlobal(*getModule(), set->value)) {
+ // It is not a valid constant expression so cannot be hoisted to
+ // global init.
+ return;
+ }
+
+ // Move it to global init and mark it as immutable.
+ auto global = getModule()->getGlobal(set->name);
+ global->init = set->value;
+ global->mutable_ = false;
+ ExpressionManipulator::nop(expr);
+
+ optimized++;
+ }
+
+ AssignmentCountMap& assignmentCounts;
+};
+
+// A visitor that marks trivial once functions for removal.
+// TODO: parallelize
+class EnableInline : public WalkerPass<PostWalker<EnableInline>> {
+public:
+ void visitFunction(Function* curr) {
+ if (!isOnceFunction(curr)) {
+ return;
+ }
+ auto* body = curr->body;
+ if (Measurer::measure(body) <= 2) {
+ // Once function has become trivial: enable inlining so it could be
+ // removed.
+ //
+ // Note that the size of a trivial function is set to 2 to cover things
+ // like
+ // - nop (e.g. a clinit that is emptied)
+ // - call (e.g. a clinit just calling another one; note that clinits take
+ // no parameters)
+ // - return global.get abc (e.g. a string literal moved to global)
+ // - global.set abc 1 (e.g. partially inlined clinit that is empty)
+ // while rejecting more complex scenario like condition checks. Optimizing
+ // complex functions could change the shape of "once" functions and make
+ // the ConstantHoister in this pass and OnceReducer ineffective.
+ curr->noFullInline = false;
+ curr->noPartialInline = false;
+ }
+ }
+};
+
+struct J2CLOpts : public Pass {
+ void hoistConstants(Module* module) {
+ AssignmentCountMap assignmentCounts;
+
+ GlobalAssignmentCollector collector(assignmentCounts);
+ collector.run(getPassRunner(), module);
+
+ // TODO surprisingly looping help some but not a lot. Maybe we are missing
+ // something that helps globals to be considered immutable.
+ while (1) {
+ ConstantHoister hoister(assignmentCounts);
+ hoister.run(getPassRunner(), module);
+ int optimized = hoister.optimized;
+#ifdef J2CL_OPT_DEBUG
+ std::cout << "Optimized " << optimized << " global fields\n";
+#endif
+ if (optimized == 0) {
+ break;
+ }
+ }
+ }
+
+ void run(Module* module) override {
+ if (!module->features.hasGC()) {
+ return;
+ }
+ // Move constant like properties set by the once functions to global
+ // initialization.
+ hoistConstants(module);
+
+ EnableInline enableInline;
+ enableInline.run(getPassRunner(), module);
+
+ // We might have introduced new globals depending on other globals. Reorder
+ // order them so they follow initialization order.
+ // TODO: do this only if have introduced a new global.
+ PassRunner runner(module);
+ runner.add("reorder-globals-always");
+ runner.setIsNested(true);
+ runner.run();
+ }
+};
+
+} // anonymous namespace
+
+Pass* createJ2CLOptsPass() { return new J2CLOpts(); }
+
+} // namespace wasm
diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp
index 525248924..d7391a390 100644
--- a/src/passes/pass.cpp
+++ b/src/passes/pass.cpp
@@ -193,6 +193,8 @@ void PassRegistry::registerPasses() {
registerPass("gufa-optimizing",
"GUFA plus local optimizations in functions we modified",
createGUFAOptimizingPass);
+ registerPass(
+ "optimize-j2cl", "optimizes J2CL specific constructs.", createJ2CLOptsPass);
registerPass("type-refining",
"apply more specific subtypes to type fields where possible",
createTypeRefiningPass);
diff --git a/src/passes/passes.h b/src/passes/passes.h
index e6ff7709e..93a8245c8 100644
--- a/src/passes/passes.h
+++ b/src/passes/passes.h
@@ -66,6 +66,7 @@ Pass* createInlineMainPass();
Pass* createInliningPass();
Pass* createInliningOptimizingPass();
Pass* createJSPIPass();
+Pass* createJ2CLOptsPass();
Pass* createLegalizeJSInterfacePass();
Pass* createLegalizeJSInterfaceMinimallyPass();
Pass* createLimitSegmentsPass();