Air should eliminate dead code
authorfpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 31 Oct 2015 23:12:53 +0000 (23:12 +0000)
committerfpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 31 Oct 2015 23:12:53 +0000 (23:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=150746

Reviewed by Geoffrey Garen.

This adds a very simple dead code elimination to Air. It simply looks at whether a Tmp or
StackSlot has ever been used by a live instruction. An instruction is live if it has non-arg
effects (branching, returning, calling, etc) or if it stores to a live Arg. An Arg is live if
it references a live Tmp or StackSlot, or if it is neither a Tmp nor a StackSlot. The phase
runs these rules to fixpoint, and then removes the dead instructions.

This also changes the AirOpcodes parser to handle multiple attributes per opcode, so that we
could conceivably say things like "FooBar /branch /effects". It also adds the /effects
attribute, which we currently use for Breakpoint and nothing else. C calls, patchpoints, and
checks are all Specials, and the Special base class by default always claims that the
instruction has effects. In the future, we could have B3 use a Patch in Air to implement
exotic math constructs; then the Special associated with that thing would claim that there
are no effects.

* JavaScriptCore.xcodeproj/project.pbxproj:
* b3/air/AirBasicBlock.h:
(JSC::B3::Air::BasicBlock::begin):
(JSC::B3::Air::BasicBlock::end):
(JSC::B3::Air::BasicBlock::at):
(JSC::B3::Air::BasicBlock::last):
(JSC::B3::Air::BasicBlock::resize):
(JSC::B3::Air::BasicBlock::appendInst):
* b3/air/AirEliminateDeadCode.cpp: Added.
(JSC::B3::Air::eliminateDeadCode):
* b3/air/AirEliminateDeadCode.h: Added.
* b3/air/AirGenerate.cpp:
(JSC::B3::Air::generate):
* b3/air/AirInst.h:
* b3/air/AirOpcode.opcodes:
* b3/air/AirSpecial.cpp:
(JSC::B3::Air::Special::name):
(JSC::B3::Air::Special::hasNonArgNonControlEffects):
(JSC::B3::Air::Special::dump):
* b3/air/AirSpecial.h:
* b3/air/opcode_generator.rb:

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@191846 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
Source/JavaScriptCore/b3/air/AirBasicBlock.h
Source/JavaScriptCore/b3/air/AirEliminateDeadCode.cpp [new file with mode: 0644]
Source/JavaScriptCore/b3/air/AirEliminateDeadCode.h [new file with mode: 0644]
Source/JavaScriptCore/b3/air/AirGenerate.cpp
Source/JavaScriptCore/b3/air/AirInst.h
Source/JavaScriptCore/b3/air/AirOpcode.opcodes
Source/JavaScriptCore/b3/air/AirSpecial.cpp
Source/JavaScriptCore/b3/air/AirSpecial.h
Source/JavaScriptCore/b3/air/opcode_generator.rb

index 96a2c39c5f9d8848b7863de413d3fbfa07a46890..d1981050db15b9c511b8466cc39909a5e12ebccb 100644 (file)
@@ -1,3 +1,46 @@
+2015-10-30  Filip Pizlo  <fpizlo@apple.com>
+
+        Air should eliminate dead code
+        https://bugs.webkit.org/show_bug.cgi?id=150746
+
+        Reviewed by Geoffrey Garen.
+
+        This adds a very simple dead code elimination to Air. It simply looks at whether a Tmp or
+        StackSlot has ever been used by a live instruction. An instruction is live if it has non-arg
+        effects (branching, returning, calling, etc) or if it stores to a live Arg. An Arg is live if
+        it references a live Tmp or StackSlot, or if it is neither a Tmp nor a StackSlot. The phase
+        runs these rules to fixpoint, and then removes the dead instructions.
+
+        This also changes the AirOpcodes parser to handle multiple attributes per opcode, so that we
+        could conceivably say things like "FooBar /branch /effects". It also adds the /effects
+        attribute, which we currently use for Breakpoint and nothing else. C calls, patchpoints, and
+        checks are all Specials, and the Special base class by default always claims that the
+        instruction has effects. In the future, we could have B3 use a Patch in Air to implement
+        exotic math constructs; then the Special associated with that thing would claim that there
+        are no effects.
+
+        * JavaScriptCore.xcodeproj/project.pbxproj:
+        * b3/air/AirBasicBlock.h:
+        (JSC::B3::Air::BasicBlock::begin):
+        (JSC::B3::Air::BasicBlock::end):
+        (JSC::B3::Air::BasicBlock::at):
+        (JSC::B3::Air::BasicBlock::last):
+        (JSC::B3::Air::BasicBlock::resize):
+        (JSC::B3::Air::BasicBlock::appendInst):
+        * b3/air/AirEliminateDeadCode.cpp: Added.
+        (JSC::B3::Air::eliminateDeadCode):
+        * b3/air/AirEliminateDeadCode.h: Added.
+        * b3/air/AirGenerate.cpp:
+        (JSC::B3::Air::generate):
+        * b3/air/AirInst.h:
+        * b3/air/AirOpcode.opcodes:
+        * b3/air/AirSpecial.cpp:
+        (JSC::B3::Air::Special::name):
+        (JSC::B3::Air::Special::hasNonArgNonControlEffects):
+        (JSC::B3::Air::Special::dump):
+        * b3/air/AirSpecial.h:
+        * b3/air/opcode_generator.rb:
+
 2015-10-31  Filip Pizlo  <fpizlo@apple.com>
 
         Air needs a late register liveness phase that calls Special::reportUsedRegisters()
index fca616e3eac73dfe2cfcb334557873818880b4a8..c4587b5e62c85f565749f5962e9d68d35a59ad0b 100644 (file)
                0F426A491460CBB700131F8F /* VirtualRegister.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F426A461460CBAB00131F8F /* VirtualRegister.h */; settings = {ATTRIBUTES = (Private, ); }; };
                0F426A4B1460CD6E00131F8F /* DataFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F426A4A1460CD6B00131F8F /* DataFormat.h */; settings = {ATTRIBUTES = (Private, ); }; };
                0F431738146BAC69007E3890 /* ListableHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F431736146BAC65007E3890 /* ListableHandler.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               0F4570381BE44C910062A629 /* AirEliminateDeadCode.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F4570361BE44C910062A629 /* AirEliminateDeadCode.cpp */; };
+               0F4570391BE44C910062A629 /* AirEliminateDeadCode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F4570371BE44C910062A629 /* AirEliminateDeadCode.h */; };
                0F45703C1BE45F0A0062A629 /* AirReportUsedRegisters.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F45703A1BE45F0A0062A629 /* AirReportUsedRegisters.cpp */; };
                0F45703D1BE45F0A0062A629 /* AirReportUsedRegisters.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F45703B1BE45F0A0062A629 /* AirReportUsedRegisters.h */; };
                0F46808214BA572D00BFE272 /* JITExceptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F46808014BA572700BFE272 /* JITExceptions.h */; settings = {ATTRIBUTES = (Private, ); }; };
                0F426A461460CBAB00131F8F /* VirtualRegister.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VirtualRegister.h; sourceTree = "<group>"; };
                0F426A4A1460CD6B00131F8F /* DataFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DataFormat.h; sourceTree = "<group>"; };
                0F431736146BAC65007E3890 /* ListableHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ListableHandler.h; sourceTree = "<group>"; };
+               0F4570361BE44C910062A629 /* AirEliminateDeadCode.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AirEliminateDeadCode.cpp; path = b3/air/AirEliminateDeadCode.cpp; sourceTree = "<group>"; };
+               0F4570371BE44C910062A629 /* AirEliminateDeadCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AirEliminateDeadCode.h; path = b3/air/AirEliminateDeadCode.h; sourceTree = "<group>"; };
                0F45703A1BE45F0A0062A629 /* AirReportUsedRegisters.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = AirReportUsedRegisters.cpp; path = b3/air/AirReportUsedRegisters.cpp; sourceTree = "<group>"; };
                0F45703B1BE45F0A0062A629 /* AirReportUsedRegisters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AirReportUsedRegisters.h; path = b3/air/AirReportUsedRegisters.h; sourceTree = "<group>"; };
                0F46807F14BA572700BFE272 /* JITExceptions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JITExceptions.cpp; sourceTree = "<group>"; };
                                0FEC854F1BDACDC70080FF74 /* AirCCallSpecial.h */,
                                0FEC85501BDACDC70080FF74 /* AirCode.cpp */,
                                0FEC85511BDACDC70080FF74 /* AirCode.h */,
+                               0F4570361BE44C910062A629 /* AirEliminateDeadCode.cpp */,
+                               0F4570371BE44C910062A629 /* AirEliminateDeadCode.h */,
                                0FEC85521BDACDC70080FF74 /* AirFrequentedBlock.h */,
                                0FEC85531BDACDC70080FF74 /* AirGenerate.cpp */,
                                0FEC85541BDACDC70080FF74 /* AirGenerate.h */,
                                A7B601821639FD2A00372BA3 /* UnlinkedCodeBlock.h in Headers */,
                                996B73261BDA08EF00331B84 /* StringIteratorPrototype.lut.h in Headers */,
                                14142E511B796ECE00F4BF4B /* UnlinkedFunctionExecutable.h in Headers */,
+                               0F4570391BE44C910062A629 /* AirEliminateDeadCode.h in Headers */,
                                0F2E892C16D028AD009E4FD2 /* UnusedPointer.h in Headers */,
                                99DA00B11BD5994E00F4575C /* UpdateContents.py in Headers */,
                                0F963B3813FC6FE90002D9B2 /* ValueProfile.h in Headers */,
                                0FD8A32517D51F5700CA2C40 /* DFGOSREntrypointCreationPhase.cpp in Sources */,
                                0FC09791146A6F7100CF2442 /* DFGOSRExit.cpp in Sources */,
                                0F235BEB17178E7300690C7F /* DFGOSRExitBase.cpp in Sources */,
+                               0F4570381BE44C910062A629 /* AirEliminateDeadCode.cpp in Sources */,
                                0FC09792146A6F7300CF2442 /* DFGOSRExitCompiler.cpp in Sources */,
                                0FC09776146943B000CF2442 /* DFGOSRExitCompiler32_64.cpp in Sources */,
                                0FC0977214693AF900CF2442 /* DFGOSRExitCompiler64.cpp in Sources */,
index f217afddae9c155e28527294e7f6bdfc1a8e589b..df4fa5b8e2ba5990ba986661a2bd0139d53dccd1 100644 (file)
@@ -57,12 +57,14 @@ public:
     InstList::const_iterator begin() const { return m_insts.begin(); }
     InstList::const_iterator end() const { return m_insts.end(); }
 
-    const Inst& at(size_t index) const { return m_insts[index]; }
-    Inst& at(size_t index) { return m_insts[index]; }
+    const Inst& at(unsigned index) const { return m_insts[index]; }
+    Inst& at(unsigned index) { return m_insts[index]; }
 
     const Inst& last() const { return m_insts.last(); }
     Inst& last() { return m_insts.last(); }
 
+    void resize(unsigned size) { m_insts.resize(size); }
+
     template<typename Inst>
     void appendInst(Inst&& inst)
     {
diff --git a/Source/JavaScriptCore/b3/air/AirEliminateDeadCode.cpp b/Source/JavaScriptCore/b3/air/AirEliminateDeadCode.cpp
new file mode 100644 (file)
index 0000000..91a3c61
--- /dev/null
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2015 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 "AirEliminateDeadCode.h"
+
+#if ENABLE(B3_JIT)
+
+#include "AirCode.h"
+#include "AirInstInlines.h"
+#include "AirPhaseScope.h"
+#include "B3IndexSet.h"
+
+namespace JSC { namespace B3 { namespace Air {
+
+bool eliminateDeadCode(Code& code)
+{
+    PhaseScope phaseScope(code, "eliminateDeadCode");
+
+    HashSet<Tmp> liveTmps;
+    IndexSet<StackSlot> liveStackSlots;
+    bool changed;
+
+    auto isArgLive = [&] (const Arg& arg) -> bool {
+        switch (arg.kind()) {
+        case Arg::Tmp:
+            if (arg.isReg())
+                return true;
+            return liveTmps.contains(arg.tmp());
+        case Arg::Stack:
+            if (arg.stackSlot()->isLocked())
+                return true;
+            return liveStackSlots.contains(arg.stackSlot());
+        default:
+            return true;
+        }
+    };
+
+    auto addLiveArg = [&] (const Arg& arg) -> bool {
+        switch (arg.kind()) {
+        case Arg::Tmp:
+            if (arg.isReg())
+                return false;
+            return liveTmps.add(arg.tmp()).isNewEntry;
+        case Arg::Stack:
+            if (arg.stackSlot()->isLocked())
+                return false;
+            return liveStackSlots.add(arg.stackSlot());
+        default:
+            return false;
+        }
+    };
+
+    auto isInstLive = [&] (Inst& inst) -> bool {
+        if (inst.hasNonArgEffects())
+            return true;
+
+        // This instruction should be presumed dead, if its Args are all dead.
+        bool storesToLive = false;
+        inst.forEachArg(
+            [&] (Arg& arg, Arg::Role role, Arg::Type) {
+                if (!Arg::isDef(role))
+                    return;
+                storesToLive |= isArgLive(arg);
+            });
+        return storesToLive;
+    };
+
+    auto handleInst = [&] (Inst& inst) {
+        if (!isInstLive(inst))
+            return;
+
+        // We get here if the Inst is live. For simplicity we say that a live instruction forces
+        // liveness upon everything it mentions.
+        for (Arg& arg : inst.args) {
+            changed |= addLiveArg(arg);
+            arg.forEachTmpFast(
+                [&] (Tmp& tmp) {
+                    changed |= addLiveArg(tmp);
+                });
+        }
+    };
+
+    auto runForward = [&] () -> bool {
+        changed = false;
+        for (BasicBlock* block : code) {
+            for (Inst& inst : *block)
+                handleInst(inst);
+        }
+        return changed;
+    };
+
+    auto runBackward = [&] () -> bool {
+        changed = false;
+        for (unsigned blockIndex = code.size(); blockIndex--;) {
+            BasicBlock* block = code[blockIndex];
+            for (unsigned instIndex = block->size(); instIndex--;)
+                handleInst(block->at(instIndex));
+        }
+        return changed;
+    };
+
+    for (;;) {
+        // Propagating backward is most likely to be profitable.
+        if (!runBackward())
+            break;
+        if (!runBackward())
+            break;
+
+        // Occasionally propagating forward greatly reduces the likelihood of pathologies.
+        if (!runForward())
+            break;
+    }
+
+    changed = false;
+    for (BasicBlock* block : code) {
+        unsigned sourceIndex = 0;
+        unsigned targetIndex = 0;
+        while (sourceIndex < block->size()) {
+            Inst inst = WTF::move(block->at(sourceIndex++));
+            if (isInstLive(inst))
+                block->at(targetIndex++) = WTF::move(inst);
+            else
+                changed = true;
+        }
+        block->resize(targetIndex);
+    }
+
+    return changed;
+}
+
+} } } // namespace JSC::B3::Air
+
+#endif // ENABLE(B3_JIT)
+
diff --git a/Source/JavaScriptCore/b3/air/AirEliminateDeadCode.h b/Source/JavaScriptCore/b3/air/AirEliminateDeadCode.h
new file mode 100644 (file)
index 0000000..d458bc9
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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. 
+ */
+
+#ifndef AirEliminateDeadCode_h
+#define AirEliminateDeadCode_h
+
+#if ENABLE(B3_JIT)
+
+namespace JSC { namespace B3 { namespace Air {
+
+class Code;
+
+// This eliminates instructions that have no observable effect. These are instructions whose only
+// effect would be storing to some Arg, except that we proved that the location specified by the Arg
+// is never loaded from. The only Args for which we can do such analysis are non-Reg Tmps and
+// anonymous StackSlots.
+
+bool eliminateDeadCode(Code&);
+
+} } } // namespace JSC::B3::Air
+
+#endif // ENABLE(B3_JIT)
+
+#endif // AirEliminateDeadCode_h
+
index bed36f3b712b06c25087b64ede4a22549224fce7..927dcf90e661876c2422ef2ac3cd300a129d3f69 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "AirAllocateStack.h"
 #include "AirCode.h"
+#include "AirEliminateDeadCode.h"
 #include "AirGenerationContext.h"
 #include "AirHandleCalleeSaves.h"
 #include "AirReportUsedRegisters.h"
@@ -58,6 +59,8 @@ void generate(Code& code, CCallHelpers& jit)
     // This is where we run our optimizations and transformations.
     // FIXME: Add Air optimizations.
     // https://bugs.webkit.org/show_bug.cgi?id=150456
+    
+    eliminateDeadCode(code);
 
     // This is where we would have a real register allocator. Then, we could use spillEverything()
     // in place of the register allocator only for testing.
index e38862d5f96984f506c600b251e0eb909666a8e5..568d0a298496255bde615445a66e4d8ddf608f97 100644 (file)
@@ -141,6 +141,14 @@ public:
     // This function is auto-generated by opcode_generator.rb.
     bool admitsStack(unsigned argIndex);
 
+    // Returns true if this instruction can have any effects other than control flow or arguments.
+    bool hasNonArgNonControlEffects();
+
+    // Returns true if this instruction can have any effects other than what is implied by arguments.
+    // For example, "Move $42, (%rax)" will return false because the effect of storing to (%rax) is
+    // implied by the second argument.
+    bool hasNonArgEffects();
+
     // Generate some code for this instruction. This is, like, literally our backend. If this is the
     // terminal, it returns the jump that needs to be linked for the "then" case, with the "else"
     // case being fall-through. This function is auto-generated by opcode_generator.rb.
index 43fea5c1641aa40e8353376f95f7d3a401e3e4d6..3341ab5f0354383eef0de43e8367ea92fac09011 100644 (file)
@@ -131,6 +131,8 @@ Move32 U:G, D:G
     Index, Tmp as load32
     Tmp, Addr as store32
     Tmp, Index as store32
+    Imm, Addr as store32
+    Imm, Index as store32
 
 MoveDouble U:F, D:F
     Tmp, Tmp
@@ -186,7 +188,7 @@ Jump /branch
 
 Ret /terminal
 
-Breakpoint /terminal
+Breakpoint /effects
 
 # Air allows for exotic behavior. A Patch's behavior is determined entirely by the Special operand,
 # which must be the first operand.
index 6b57a3b7e2924d6ba3f57f9f37a1ca5539beb720..627076be4d0b1324781b72e1ccc41b0e71aadbf6 100644 (file)
@@ -50,6 +50,11 @@ CString Special::name() const
     return out.toCString();
 }
 
+bool Special::hasNonArgNonControlEffects()
+{
+    return true;
+}
+
 void Special::dump(PrintStream& out) const
 {
     out.print(dumpPrefix);
index 2fae3d5f346af5b8be1ff3d975c0c113c86f828e..25f5e446bb554f5f3e1b78d3efc33f47d69dd9f6 100644 (file)
@@ -82,6 +82,9 @@ public:
 
     virtual const RegisterSet& extraClobberedRegs(Inst&) = 0;
 
+    // By default, this returns true.
+    virtual bool hasNonArgNonControlEffects();
+
     void dump(PrintStream&) const;
     void deepDump(PrintStream&) const;
 
index 988db0f16f941fd28d518fb8817185beffceca16..02c4f67e3febb71b9191fafbec06047a9df53298 100644 (file)
@@ -27,12 +27,12 @@ require "pathname"
 
 class Opcode
     attr_reader :name, :special, :overloads
-    attr_accessor :kind
+    attr_reader :attributes
 
     def initialize(name, special)
         @name = name
         @special = special
-        @kind = :inst
+        @attributes = {}
         unless special
             @overloads = []
         end
@@ -291,13 +291,15 @@ class Parser
                     }
                 end
 
-                if token == "/"
+                while token == "/"
                     consume("/")
                     case token.string
                     when "branch"
-                        opcode.kind = :branch
+                        opcode.attributes[:branch] = true
                     when "terminal"
-                        opcode.kind = :terminal
+                        opcode.attributes[:terminal] = true
+                    when "effects"
+                        opcode.attributes[:effects] = true
                     else
                         parseError("Bad / directive")
                     end
@@ -580,7 +582,7 @@ writeH("OpcodeUtils") {
     outp.puts "switch (opcode) {"
     $opcodes.values.each {
         | opcode |
-        if opcode.kind == :terminal or opcode.kind == :branch
+        if opcode.attributes[:terminal] or opcode.attributes[:branch]
             outp.puts "case #{opcode.name}:"
         end
     }
@@ -786,6 +788,50 @@ writeH("OpcodeGenerated") {
     outp.puts "return false;"
     outp.puts "}"
 
+    outp.puts "bool Inst::hasNonArgNonControlEffects()"
+    outp.puts "{"
+    outp.puts "switch (opcode) {"
+    $opcodes.values.each {
+        | opcode |
+        if opcode.attributes[:effects]
+            outp.puts "case #{opcode.name}:"
+        end
+    }
+    outp.puts "return true;"
+    $opcodes.values.each {
+        | opcode |
+        if opcode.special
+            outp.puts "case #{opcode.name}:"
+        end
+    }
+    outp.puts "return args[0].special()->hasNonArgNonControlEffects();"
+    outp.puts "default:"
+    outp.puts "return false;"
+    outp.puts "}"
+    outp.puts "}"
+    
+    outp.puts "bool Inst::hasNonArgEffects()"
+    outp.puts "{"
+    outp.puts "switch (opcode) {"
+    $opcodes.values.each {
+        | opcode |
+        if opcode.attributes[:branch] or opcode.attributes[:terminal] or opcode.attributes[:effects]
+            outp.puts "case #{opcode.name}:"
+        end
+    }
+    outp.puts "return true;"
+    $opcodes.values.each {
+        | opcode |
+        if opcode.special
+            outp.puts "case #{opcode.name}:"
+        end
+    }
+    outp.puts "return args[0].special()->hasNonArgNonControlEffects();"
+    outp.puts "default:"
+    outp.puts "return false;"
+    outp.puts "}"
+    outp.puts "}"
+    
     outp.puts "CCallHelpers::Jump Inst::generate(CCallHelpers& jit, GenerationContext& context)"
     outp.puts "{"
     outp.puts "UNUSED_PARAM(jit);"
@@ -801,7 +847,7 @@ writeH("OpcodeGenerated") {
             else
                 methodName = opcode.masmName
             end
-            if opcode.kind == :branch
+            if opcode.attributes[:branch]
                 outp.print "result = "
             end
             outp.print "jit.#{methodName}("