summaryrefslogtreecommitdiff
path: root/Source/JavaScriptCore/b3/B3InferSwitches.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/JavaScriptCore/b3/B3InferSwitches.cpp')
-rw-r--r--Source/JavaScriptCore/b3/B3InferSwitches.cpp337
1 files changed, 337 insertions, 0 deletions
diff --git a/Source/JavaScriptCore/b3/B3InferSwitches.cpp b/Source/JavaScriptCore/b3/B3InferSwitches.cpp
new file mode 100644
index 000000000..2f1781241
--- /dev/null
+++ b/Source/JavaScriptCore/b3/B3InferSwitches.cpp
@@ -0,0 +1,337 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "B3InferSwitches.h"
+
+#if ENABLE(B3_JIT)
+
+#include "B3BasicBlockInlines.h"
+#include "B3CaseCollectionInlines.h"
+#include "B3InsertionSetInlines.h"
+#include "B3PhaseScope.h"
+#include "B3ProcedureInlines.h"
+#include "B3SwitchValue.h"
+#include "B3UseCounts.h"
+#include "B3ValueInlines.h"
+#include <wtf/ListDump.h>
+
+namespace JSC { namespace B3 {
+
+namespace {
+
+const bool verbose = false;
+
+class InferSwitches {
+public:
+ InferSwitches(Procedure& proc)
+ : m_proc(proc)
+ , m_insertionSet(proc)
+ , m_useCounts(proc)
+ {
+ }
+
+ bool run()
+ {
+ if (verbose)
+ dataLog("B3 before inferSwitches:\n", m_proc);
+
+ bool changed = true;
+ bool everChanged = false;
+ while (changed) {
+ changed = false;
+
+ if (verbose)
+ dataLog("Performing fixpoint iteration:\n");
+
+ for (BasicBlock* block : m_proc)
+ changed |= attemptToMergeWithPredecessor(block);
+
+ everChanged |= changed;
+ }
+
+ if (everChanged) {
+ m_proc.resetReachability();
+ m_proc.invalidateCFG();
+
+ m_proc.deleteOrphans();
+
+ if (verbose)
+ dataLog("B3 after inferSwitches:\n", m_proc);
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ bool attemptToMergeWithPredecessor(BasicBlock* block)
+ {
+ // No point in considering the root block. We also don't consider blocks with multiple
+ // predecessors, but we could handle this if we made this code a bit more general and we were
+ // not afraid of code bloat.
+ if (block->numPredecessors() != 1)
+ return false;
+
+ SwitchDescription description = describe(block);
+ if (verbose)
+ dataLog("Description of primary block ", *block, ": ", description, "\n");
+ if (!description) {
+ if (verbose)
+ dataLog(" Bailing because not switch-like.\n");
+ return false;
+ }
+
+ // We know that this block behaves like a switch. But we need to verify that it doesn't also
+ // perform any effects or do expensive things. We don't want to create a switch if that will
+ // make expensive things execute unconditionally. We're very conservative about how we define
+ // "expensive".
+ for (Value* value : *block) {
+ if (value->isFree())
+ continue;
+ if (value == description.extra)
+ continue;
+ if (value == description.branch)
+ continue;
+ if (verbose)
+ dataLog(" Bailing because of ", deepDump(m_proc, value), "\n");
+ return false;
+ }
+
+ BasicBlock* predecessor = block->predecessor(0);
+ SwitchDescription predecessorDescription = describe(predecessor);
+ if (verbose)
+ dataLog(" Description of predecessor block ", *predecessor, ": ", predecessorDescription, "\n");
+ if (!predecessorDescription) {
+ if (verbose)
+ dataLog(" Bailing because not switch-like.\n");
+ return false;
+ }
+
+ // Both us and the predecessor are switch-like, but that doesn't mean that we're compatible.
+ // We may be switching on different values!
+ if (description.source != predecessorDescription.source) {
+ if (verbose)
+ dataLog(" Bailing because sources don't match.\n");
+ return false;
+ }
+
+ // We expect that we are the fall-through destination of the predecessor. This is a bit of a
+ // goofy condition. If we were not the fall-through destination then our switch is probably
+ // just totally redundant and we should be getting rid of it. But we don't handle that here,
+ // yet.
+ if (predecessorDescription.fallThrough.block() != block) {
+ if (verbose)
+ dataLog(" Bailing because fall-through of predecessor is not the primary block.\n");
+ return false;
+ }
+
+ // Make sure that there ain't no loops.
+ if (description.fallThrough.block() == block
+ || description.fallThrough.block() == predecessor) {
+ if (verbose)
+ dataLog(" Bailing because of fall-through loop.\n");
+ return false;
+ }
+ for (SwitchCase switchCase : description.cases) {
+ if (switchCase.targetBlock() == block
+ || switchCase.targetBlock() == predecessor) {
+ if (verbose)
+ dataLog(" Bailing because of loop in primary cases.\n");
+ return false;
+ }
+ }
+ for (SwitchCase switchCase : predecessorDescription.cases) {
+ if (switchCase.targetBlock() == block
+ || switchCase.targetBlock() == predecessor) {
+ if (verbose)
+ dataLog(" Bailing because of loop in predecessor cases.\n");
+ return false;
+ }
+ }
+
+ if (verbose)
+ dataLog(" Doing it!\n");
+ // We're committed to doing the thing.
+
+ // Delete the extra value from the predecessor, since that would break downstream inference
+ // on the next fixpoint iteration. We would think that this block is too expensive to merge
+ // because of the Equal or NotEqual value even though that value is dead! We know it's dead
+ // so we kill it ourselves.
+ for (Value* value : *predecessor) {
+ if (value == predecessorDescription.extra)
+ value->replaceWithNopIgnoringType();
+ }
+
+ // Insert all non-terminal values from our block into our predecessor. We definitely need to
+ // do this for constants. We must not do it for the extra value, since that would break
+ // downstream inference on the next fixpoint iteration. As a bonus, we don't do it for nops,
+ // so that we limit how big blocks get in this phase.
+ for (unsigned i = 0; i < block->size() - 1; ++i) {
+ Value* value = block->at(i);
+ if (value != description.extra && value->opcode() != Nop)
+ m_insertionSet.insertValue(predecessor->size() - 1, value);
+ }
+ m_insertionSet.execute(predecessor);
+ block->values().resize(0);
+ block->appendNew<Value>(m_proc, Oops, description.branch->origin());
+ block->removePredecessor(predecessor);
+
+ for (BasicBlock* successorBlock : description.block->successorBlocks())
+ successorBlock->replacePredecessor(block, predecessor);
+
+ block->clearSuccessors();
+
+ SwitchValue* switchValue = predecessor->replaceLastWithNew<SwitchValue>(
+ m_proc, predecessor->last()->origin(), description.source);
+ predecessor->clearSuccessors();
+ switchValue->setFallThrough(description.fallThrough);
+
+ Vector<int64_t> predecessorCases;
+ for (SwitchCase switchCase : predecessorDescription.cases) {
+ switchValue->appendCase(switchCase);
+ predecessorCases.append(switchCase.caseValue());
+ }
+ std::sort(predecessorCases.begin(), predecessorCases.end());
+ auto isPredecessorCase = [&] (int64_t value) -> bool {
+ return !!tryBinarySearch<int64_t>(
+ predecessorCases, predecessorCases.size(), value,
+ [] (int64_t* element) -> int64_t { return *element; });
+ };
+
+ for (SwitchCase switchCase : description.cases) {
+ if (!isPredecessorCase(switchCase.caseValue()))
+ switchValue->appendCase(switchCase);
+ }
+ return true;
+ }
+
+ struct SwitchDescription {
+ SwitchDescription()
+ {
+ }
+
+ explicit operator bool() { return !!block; }
+
+ void dump(PrintStream& out) const
+ {
+ out.print(
+ "{block = ", pointerDump(block),
+ ", branch = ", pointerDump(branch),
+ ", extra = ", pointerDump(extra),
+ ", source = ", pointerDump(source),
+ ", cases = ", listDump(cases),
+ ", fallThrough = ", fallThrough, "}");
+ }
+
+ BasicBlock* block { nullptr };
+ Value* branch { nullptr };
+ Value* extra { nullptr }; // This is the Equal or NotEqual value, if applicable.
+ Value* source { nullptr };
+ Vector<SwitchCase, 1> cases;
+ FrequentedBlock fallThrough;
+ };
+
+ SwitchDescription describe(BasicBlock* block)
+ {
+ SwitchDescription result;
+ result.block = block;
+ result.branch = block->last();
+
+ switch (result.branch->opcode()) {
+ case Branch: {
+ Value* predicate = result.branch->child(0);
+ FrequentedBlock taken = result.block->taken();
+ FrequentedBlock notTaken = result.block->notTaken();
+ bool handled = false;
+ // NOTE: This uses UseCounts that we computed before any transformation. This is fine
+ // because although we may have mutated the IR, we would not have added any new
+ // predicates.
+ if (predicate->numChildren() == 2
+ && predicate->child(1)->hasInt()
+ && m_useCounts.numUses(predicate) == 1) {
+ switch (predicate->opcode()) {
+ case Equal:
+ result.source = predicate->child(0);
+ result.extra = predicate;
+ result.cases.append(SwitchCase(predicate->child(1)->asInt(), taken));
+ result.fallThrough = notTaken;
+ handled = true;
+ break;
+ case NotEqual:
+ result.source = predicate->child(0);
+ result.extra = predicate;
+ result.cases.append(SwitchCase(predicate->child(1)->asInt(), notTaken));
+ result.fallThrough = taken;
+ handled = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (handled)
+ break;
+ result.source = predicate;
+ result.cases.append(SwitchCase(0, notTaken));
+ result.fallThrough = taken;
+ break;
+ }
+
+ case Switch: {
+ SwitchValue* switchValue = result.branch->as<SwitchValue>();
+ result.source = switchValue->child(0);
+ for (SwitchCase switchCase : switchValue->cases(result.block))
+ result.cases.append(switchCase);
+ result.fallThrough = result.block->fallThrough();
+ break;
+ }
+
+ default:
+ result.block = nullptr;
+ result.branch = nullptr;
+ break;
+ }
+
+ return result;
+ }
+
+ Procedure& m_proc;
+ InsertionSet m_insertionSet;
+ UseCounts m_useCounts;
+};
+
+} // anonymous namespace
+
+bool inferSwitches(Procedure& proc)
+{
+ PhaseScope phaseScope(proc, "inferSwitches");
+ InferSwitches inferSwitches(proc);
+ return inferSwitches.run();
+}
+
+} } // namespace JSC::B3
+
+#endif // ENABLE(B3_JIT)
+