Land the stackmap register liveness glue with the uses of the liveness disabled
authorfpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 30 Mar 2014 18:43:41 +0000 (18:43 +0000)
committerfpizlo@apple.com <fpizlo@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 30 Mar 2014 18:43:41 +0000 (18:43 +0000)
https://bugs.webkit.org/show_bug.cgi?id=130924

Source/JavaScriptCore:

Reviewed by Oliver Hunt.

Add the liveness and fix other bugs I found.

* bytecode/PutByIdStatus.cpp:
(JSC::PutByIdStatus::computeFor):
* ftl/FTLCompile.cpp:
(JSC::FTL::usedRegistersFor):
(JSC::FTL::fixFunctionBasedOnStackMaps):
* ftl/FTLSlowPathCall.cpp:
* ftl/FTLSlowPathCallKey.cpp:
(JSC::FTL::SlowPathCallKey::dump):
* ftl/FTLSlowPathCallKey.h:
(JSC::FTL::SlowPathCallKey::SlowPathCallKey):
(JSC::FTL::SlowPathCallKey::argumentRegisters):
(JSC::FTL::SlowPathCallKey::withCallTarget):
* ftl/FTLStackMaps.cpp:
(JSC::FTL::StackMaps::Record::locationSet):
(JSC::FTL::StackMaps::Record::liveOutsSet):
(JSC::FTL::StackMaps::Record::usedRegisterSet):
* ftl/FTLStackMaps.h:
* ftl/FTLThunks.cpp:
(JSC::FTL::registerClobberCheck):
(JSC::FTL::slowPathCallThunkGenerator):
* jit/RegisterSet.cpp:
(JSC::RegisterSet::stackRegisters):
(JSC::RegisterSet::reservedHardwareRegisters):
(JSC::RegisterSet::runtimeRegisters):
(JSC::RegisterSet::specialRegisters):
(JSC::RegisterSet::dump):
* jit/RegisterSet.h:
(JSC::RegisterSet::RegisterSet):
(JSC::RegisterSet::setAny):
(JSC::RegisterSet::setMany):
* jit/Repatch.cpp:
(JSC::tryCacheGetByID):
(JSC::tryCachePutByID):
(JSC::tryRepatchIn):
* runtime/Options.cpp:
(JSC::recomputeDependentOptions):
* runtime/Options.h:

Tools:

Reviewed by Oliver Hunt.

* Scripts/run-jsc-stress-tests:

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

16 files changed:
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/bytecode/PutByIdStatus.cpp
Source/JavaScriptCore/ftl/FTLCompile.cpp
Source/JavaScriptCore/ftl/FTLSlowPathCall.cpp
Source/JavaScriptCore/ftl/FTLSlowPathCallKey.cpp
Source/JavaScriptCore/ftl/FTLSlowPathCallKey.h
Source/JavaScriptCore/ftl/FTLStackMaps.cpp
Source/JavaScriptCore/ftl/FTLStackMaps.h
Source/JavaScriptCore/ftl/FTLThunks.cpp
Source/JavaScriptCore/jit/RegisterSet.cpp
Source/JavaScriptCore/jit/RegisterSet.h
Source/JavaScriptCore/jit/Repatch.cpp
Source/JavaScriptCore/runtime/Options.cpp
Source/JavaScriptCore/runtime/Options.h
Tools/ChangeLog
Tools/Scripts/run-jsc-stress-tests

index ef6dfeb..c3581ec 100644 (file)
@@ -1,3 +1,50 @@
+2014-03-28  Filip Pizlo  <fpizlo@apple.com>
+
+        Land the stackmap register liveness glue with the uses of the liveness disabled
+        https://bugs.webkit.org/show_bug.cgi?id=130924
+
+        Reviewed by Oliver Hunt.
+        
+        Add the liveness and fix other bugs I found.
+
+        * bytecode/PutByIdStatus.cpp:
+        (JSC::PutByIdStatus::computeFor):
+        * ftl/FTLCompile.cpp:
+        (JSC::FTL::usedRegistersFor):
+        (JSC::FTL::fixFunctionBasedOnStackMaps):
+        * ftl/FTLSlowPathCall.cpp:
+        * ftl/FTLSlowPathCallKey.cpp:
+        (JSC::FTL::SlowPathCallKey::dump):
+        * ftl/FTLSlowPathCallKey.h:
+        (JSC::FTL::SlowPathCallKey::SlowPathCallKey):
+        (JSC::FTL::SlowPathCallKey::argumentRegisters):
+        (JSC::FTL::SlowPathCallKey::withCallTarget):
+        * ftl/FTLStackMaps.cpp:
+        (JSC::FTL::StackMaps::Record::locationSet):
+        (JSC::FTL::StackMaps::Record::liveOutsSet):
+        (JSC::FTL::StackMaps::Record::usedRegisterSet):
+        * ftl/FTLStackMaps.h:
+        * ftl/FTLThunks.cpp:
+        (JSC::FTL::registerClobberCheck):
+        (JSC::FTL::slowPathCallThunkGenerator):
+        * jit/RegisterSet.cpp:
+        (JSC::RegisterSet::stackRegisters):
+        (JSC::RegisterSet::reservedHardwareRegisters):
+        (JSC::RegisterSet::runtimeRegisters):
+        (JSC::RegisterSet::specialRegisters):
+        (JSC::RegisterSet::dump):
+        * jit/RegisterSet.h:
+        (JSC::RegisterSet::RegisterSet):
+        (JSC::RegisterSet::setAny):
+        (JSC::RegisterSet::setMany):
+        * jit/Repatch.cpp:
+        (JSC::tryCacheGetByID):
+        (JSC::tryCachePutByID):
+        (JSC::tryRepatchIn):
+        * runtime/Options.cpp:
+        (JSC::recomputeDependentOptions):
+        * runtime/Options.h:
+
 2014-03-28  Mark Lam  <mark.lam@apple.com>
 
         mandreel throws a checksum error on 32-bit x86.
index 17504a6..2a208a2 100644 (file)
@@ -238,7 +238,10 @@ PutByIdStatus PutByIdStatus::computeFor(CodeBlock* baselineBlock, CodeBlock* dfg
             result = computeForStubInfo(locker, dfgBlock, dfgMap.get(codeOrigin), uid);
         }
         
-        if (result.isSet())
+        // We use TakesSlowPath in some cases where the stub was unset. That's weird and
+        // it would be better not to do that. But it means that we have to defend
+        // ourselves here.
+        if (result.isSimple())
             return result;
     }
 #else
index 5d19c4e..319b809 100644 (file)
@@ -163,6 +163,13 @@ void generateICFastPath(
     }
 }
 
+static RegisterSet usedRegistersFor(const StackMaps::Record& record)
+{
+    if (Options::assumeAllRegsInFTLICAreLive())
+        return RegisterSet::allRegisters();
+    return record.usedRegisterSet();
+}
+
 static void fixFunctionBasedOnStackMaps(
     State& state, CodeBlock* codeBlock, JITCode* jitCode, GeneratedFunction generatedFunction,
     StackMaps::RecordMap& recordMap, bool didSeeUnwindInfo)
@@ -300,9 +307,7 @@ static void fixFunctionBasedOnStackMaps(
             for (unsigned i = 0; i < iter->value.size(); ++i) {
                 StackMaps::Record& record = iter->value[i];
             
-                // FIXME: Use the liveness information that LLVM gives us.
-                // https://bugs.webkit.org/show_bug.cgi?id=130791
-                RegisterSet usedRegisters = RegisterSet::allRegisters();
+                RegisterSet usedRegisters = usedRegistersFor(record);
                 
                 GPRReg result = record.locations[0].directGPR();
                 GPRReg base = record.locations[1].directGPR();
@@ -339,9 +344,7 @@ static void fixFunctionBasedOnStackMaps(
             for (unsigned i = 0; i < iter->value.size(); ++i) {
                 StackMaps::Record& record = iter->value[i];
                 
-                // FIXME: Use the liveness information that LLVM gives us.
-                // https://bugs.webkit.org/show_bug.cgi?id=130791
-                RegisterSet usedRegisters = RegisterSet::allRegisters();
+                RegisterSet usedRegisters = usedRegistersFor(record);
                 
                 GPRReg base = record.locations[0].directGPR();
                 GPRReg value = record.locations[1].directGPR();
index 2dbc15b..e48f862 100644 (file)
@@ -67,7 +67,8 @@ public:
             (std::max(m_numArgs, NUMBER_OF_ARGUMENT_REGISTERS) - NUMBER_OF_ARGUMENT_REGISTERS) * wordSize;
         
         for (unsigned i = std::min(NUMBER_OF_ARGUMENT_REGISTERS, numArgs); i--;)
-            m_callingConventionRegisters.set(GPRInfo::toArgumentRegister(i));
+            m_argumentRegisters.set(GPRInfo::toArgumentRegister(i));
+        m_callingConventionRegisters.merge(m_argumentRegisters);
         if (returnRegister != InvalidGPRReg)
             m_callingConventionRegisters.set(GPRInfo::returnValueGPR);
         m_callingConventionRegisters.filter(m_usedRegisters);
@@ -84,9 +85,7 @@ public:
             stackBytesNeededForReturnAddress +
             (m_usedRegisters.numberOfSetRegisters() - numberOfCallingConventionRegisters) * wordSize;
         
-        size_t stackAlignment = 16;
-        
-        m_stackBytesNeeded = (m_stackBytesNeeded + stackAlignment - 1) & ~(stackAlignment - 1);
+        m_stackBytesNeeded = (m_stackBytesNeeded + stackAlignmentBytes() - 1) & ~(stackAlignmentBytes() - 1);
         
         m_jit.subPtr(CCallHelpers::TrustedImm32(m_stackBytesNeeded), CCallHelpers::stackPointerRegister);
         
@@ -133,7 +132,7 @@ public:
     
     SlowPathCallKey keyWithTarget(void* callTarget) const
     {
-        return SlowPathCallKey(usedRegisters(), callTarget, offset());
+        return SlowPathCallKey(usedRegisters(), callTarget, m_argumentRegisters, offset());
     }
     
     MacroAssembler::Call makeCall(void* callTarget, MacroAssembler::JumpList* exceptionTarget)
@@ -149,6 +148,7 @@ public:
 private:
     State& m_state;
     RegisterSet m_usedRegisters;
+    RegisterSet m_argumentRegisters;
     RegisterSet m_callingConventionRegisters;
     CCallHelpers& m_jit;
     unsigned m_numArgs;
index aaad665..4cc835d 100644 (file)
@@ -32,7 +32,7 @@ namespace JSC { namespace FTL {
 
 void SlowPathCallKey::dump(PrintStream& out) const
 {
-    out.print("<usedRegisters = ", m_usedRegisters, ", offset = ", m_offset, ", callTarget = ", RawPointer(m_callTarget), ">");
+    out.print("<usedRegisters = ", m_usedRegisters, ", offset = ", m_offset, ", callTarget = ", RawPointer(m_callTarget), ", argumentRegisters = ", m_argumentRegisters, ">");
 }
 
 } } // namespace JSC::FTL
index 0c7c329..22f8710 100644 (file)
@@ -50,20 +50,24 @@ public:
     {
     }
     
-    SlowPathCallKey(const RegisterSet& set, void* callTarget, ptrdiff_t offset)
+    SlowPathCallKey(
+        const RegisterSet& set, void* callTarget, const RegisterSet& argumentRegisters,
+        ptrdiff_t offset)
         : m_usedRegisters(set)
         , m_callTarget(callTarget)
+        , m_argumentRegisters(argumentRegisters)
         , m_offset(offset)
     {
     }
     
     const RegisterSet& usedRegisters() const { return m_usedRegisters; }
     void* callTarget() const { return m_callTarget; }
+    const RegisterSet& argumentRegisters() const { return m_argumentRegisters; }
     ptrdiff_t offset() const { return m_offset; }
     
     SlowPathCallKey withCallTarget(void* callTarget)
     {
-        return SlowPathCallKey(usedRegisters(), callTarget, offset());
+        return SlowPathCallKey(usedRegisters(), callTarget, argumentRegisters(), offset());
     }
     
     void dump(PrintStream&) const;
@@ -102,6 +106,7 @@ public:
 private:
     RegisterSet m_usedRegisters;
     void* m_callTarget;
+    RegisterSet m_argumentRegisters;
     ptrdiff_t m_offset;
 };
 
index 0b48ae4..f79b71b 100644 (file)
@@ -149,6 +149,44 @@ void StackMaps::Record::dump(PrintStream& out) const
         listDump(liveOuts), "])");
 }
 
+RegisterSet StackMaps::Record::locationSet() const
+{
+    RegisterSet result;
+    for (unsigned i = locations.size(); i--;) {
+        Reg reg = locations[i].dwarfReg.reg();
+        if (!reg)
+            continue;
+        result.set(reg);
+    }
+    return result;
+}
+
+RegisterSet StackMaps::Record::liveOutsSet() const
+{
+    RegisterSet result;
+    for (unsigned i = liveOuts.size(); i--;) {
+        LiveOut liveOut = liveOuts[i];
+        Reg reg = liveOut.dwarfReg.reg();
+        // FIXME: Either assert that size is not greater than sizeof(pointer), or actually
+        // save the high bits of registers.
+        // https://bugs.webkit.org/show_bug.cgi?id=130885
+        if (!reg) {
+            dataLog("Invalid liveOuts entry in: ", *this, "\n");
+            RELEASE_ASSERT_NOT_REACHED();
+        }
+        result.set(reg);
+    }
+    return result;
+}
+
+RegisterSet StackMaps::Record::usedRegisterSet() const
+{
+    RegisterSet result;
+    result.merge(locationSet());
+    result.merge(liveOutsSet());
+    return result;
+}
+
 bool StackMaps::parse(DataView* view)
 {
     ParseContext context;
index ae5a1f4..6440b0f 100644 (file)
@@ -31,6 +31,7 @@
 #include "DataView.h"
 #include "FTLDWARFRegister.h"
 #include "GPRInfo.h"
+#include "RegisterSet.h"
 #include <wtf/HashMap.h>
 
 namespace JSC {
@@ -106,6 +107,10 @@ struct StackMaps {
         
         bool parse(ParseContext&);
         void dump(PrintStream&) const;
+        
+        RegisterSet liveOutsSet() const;
+        RegisterSet locationSet() const;
+        RegisterSet usedRegisterSet() const;
     };
 
     unsigned version;
index 9d17a5d..9acc6b2 100644 (file)
@@ -106,6 +106,34 @@ MacroAssemblerCodeRef osrExitGenerationThunkGenerator(VM* vm)
     return FINALIZE_CODE(patchBuffer, ("FTL OSR exit generation thunk"));
 }
 
+static void registerClobberCheck(AssemblyHelpers& jit, RegisterSet dontClobber)
+{
+    if (!Options::clobberAllRegsInFTLICSlowPath())
+        return;
+    
+    RegisterSet clobber = RegisterSet::allRegisters();
+    clobber.exclude(RegisterSet::reservedHardwareRegisters());
+    clobber.exclude(RegisterSet::stackRegisters());
+    clobber.exclude(RegisterSet::calleeSaveRegisters());
+    clobber.exclude(dontClobber);
+    
+    GPRReg someGPR;
+    for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) {
+        if (!clobber.get(reg) || !reg.isGPR())
+            continue;
+        
+        jit.move(AssemblyHelpers::TrustedImm32(0x1337beef), reg.gpr());
+        someGPR = reg.gpr();
+    }
+    
+    for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) {
+        if (!clobber.get(reg) || !reg.isFPR())
+            continue;
+        
+        jit.move64ToDouble(someGPR, reg.fpr());
+    }
+}
+
 MacroAssemblerCodeRef slowPathCallThunkGenerator(VM& vm, const SlowPathCallKey& key)
 {
     AssemblyHelpers jit(&vm, 0);
@@ -134,13 +162,13 @@ MacroAssemblerCodeRef slowPathCallThunkGenerator(VM& vm, const SlowPathCallKey&
         currentOffset += sizeof(double);
     }
     
-    // FIXME: CStack - Need to do soemething like jit.emitFunctionPrologue();
     jit.preserveReturnAddressAfterCall(GPRInfo::nonArgGPR0);
     jit.storePtr(GPRInfo::nonArgGPR0, AssemblyHelpers::Address(MacroAssembler::stackPointerRegister, key.offset()));
     
+    registerClobberCheck(jit, key.argumentRegisters());
+    
     AssemblyHelpers::Call call = jit.call();
 
-    // FIXME: CStack - Need to do something like jit.emitFunctionEpilogue();
     jit.loadPtr(AssemblyHelpers::Address(MacroAssembler::stackPointerRegister, key.offset()), GPRInfo::nonPreservedNonReturnGPR);
     jit.restoreReturnAddressBeforeReturn(GPRInfo::nonPreservedNonReturnGPR);
     
index 8bc7f29..608ebd5 100644 (file)
 #include "GPRInfo.h"
 #include "MacroAssembler.h"
 #include "JSCInlines.h"
+#include <wtf/CommaPrinter.h>
 
 namespace JSC {
 
 RegisterSet RegisterSet::stackRegisters()
 {
-    RegisterSet result;
-    result.set(MacroAssembler::stackPointerRegister);
-    result.set(MacroAssembler::framePointerRegister);
-    return result;
+    return RegisterSet(
+        MacroAssembler::stackPointerRegister,
+        MacroAssembler::framePointerRegister);
 }
 
 RegisterSet RegisterSet::reservedHardwareRegisters()
 {
-    RegisterSet result;
 #if CPU(ARM64)
-    result.set(ARM64Registers::lr);
+    return RegisterSet(ARM64Registers::lr);
+#else
+    return RegisterSet();
 #endif
-    return result;
 }
 
 RegisterSet RegisterSet::runtimeRegisters()
 {
-    RegisterSet result;
 #if USE(JSVALUE64)
-    result.set(GPRInfo::tagTypeNumberRegister);
-    result.set(GPRInfo::tagMaskRegister);
+    return RegisterSet(GPRInfo::tagTypeNumberRegister, GPRInfo::tagMaskRegister);
+#else
+    return RegisterSet();
 #endif
-    return result;
 }
 
 RegisterSet RegisterSet::specialRegisters()
 {
-    RegisterSet result;
-    result.merge(stackRegisters());
-    result.merge(reservedHardwareRegisters());
-    result.merge(runtimeRegisters());
-    return result;
+    return RegisterSet(
+        stackRegisters(), reservedHardwareRegisters(), runtimeRegisters());
 }
 
 RegisterSet RegisterSet::calleeSaveRegisters()
@@ -155,7 +151,13 @@ size_t RegisterSet::numberOfSetFPRs() const
 
 void RegisterSet::dump(PrintStream& out) const
 {
-    m_vector.dump(out);
+    CommaPrinter comma;
+    out.print("[");
+    for (Reg reg = Reg::first(); reg <= Reg::last(); reg = reg.next()) {
+        if (get(reg))
+            out.print(comma, reg);
+    }
+    out.print("]");
 }
 
 } // namespace JSC
index 22f403f..44bfecd 100644 (file)
@@ -39,7 +39,11 @@ namespace JSC {
 
 class RegisterSet {
 public:
-    RegisterSet() { }
+    template<typename... Regs>
+    explicit RegisterSet(Regs... regs)
+    {
+        setMany(regs...);
+    }
     
     static RegisterSet stackRegisters();
     static RegisterSet reservedHardwareRegisters();
@@ -55,7 +59,7 @@ public:
         ASSERT(!!reg);
         m_vector.set(reg.index(), value);
     }
-
+    
     void set(JSValueRegs regs)
     {
         if (regs.tagGPR() != InvalidGPRReg)
@@ -105,6 +109,16 @@ public:
     unsigned hash() const { return m_vector.hash(); }
     
 private:
+    void setAny(Reg reg) { set(reg); }
+    void setAny(const RegisterSet& set) { merge(set); }
+    void setMany() { }
+    template<typename RegType, typename... Regs>
+    void setMany(RegType reg, Regs... regs)
+    {
+        setAny(reg);
+        setMany(regs...);
+    }
+
     BitVector m_vector;
 };
 
index 0605e12..bf583cd 100644 (file)
@@ -490,6 +490,9 @@ static void generateGetByIdStub(
 
 static bool tryCacheGetByID(ExecState* exec, JSValue baseValue, const Identifier& propertyName, const PropertySlot& slot, StructureStubInfo& stubInfo)
 {
+    if (Options::forceICFailure())
+        return false;
+    
     // FIXME: Write a test that proves we need to check for recursion here just
     // like the interpreter does, then add a check for recursion.
 
@@ -1110,6 +1113,9 @@ static void emitCustomSetterStub(ExecState* exec, const PutPropertySlot& slot,
 
 static bool tryCachePutByID(ExecState* exec, JSValue baseValue, const Identifier& ident, const PutPropertySlot& slot, StructureStubInfo& stubInfo, PutKind putKind)
 {
+    if (Options::forceICFailure())
+        return false;
+    
     CodeBlock* codeBlock = exec->codeBlock();
     VM* vm = &exec->vm();
 
@@ -1139,6 +1145,8 @@ static bool tryCachePutByID(ExecState* exec, JSValue baseValue, const Identifier
             
             // Skip optimizing the case where we need realloc, and the structure has
             // indexing storage.
+            // FIXME: We shouldn't skip this!  Implement it!
+            // https://bugs.webkit.org/show_bug.cgi?id=130914
             if (oldStructure->couldHaveIndexingHeader())
                 return false;
             
@@ -1340,6 +1348,9 @@ static bool tryRepatchIn(
     ExecState* exec, JSCell* base, const Identifier& ident, bool wasFound,
     const PropertySlot& slot, StructureStubInfo& stubInfo)
 {
+    if (Options::forceICFailure())
+        return false;
+    
     if (!base->structure()->propertyAccessesAreCacheable())
         return false;
     
index a1ce05c..f67da2c 100644 (file)
@@ -206,7 +206,7 @@ static void recomputeDependentOptions()
 #if !ENABLE(FTL_JIT)
     Options::useFTLJIT() = false;
 #endif
-    
+
     if (Options::showDisassembly()
         || Options::showDFGDisassembly()
         || Options::showFTLDisassembly()
index efb327d..6f405ec 100644 (file)
@@ -102,6 +102,7 @@ typedef OptionRange optionRange;
     v(bool, crashIfCantAllocateJITMemory, false) \
     \
     v(bool, forceDFGCodeBlockLiveness, false) \
+    v(bool, forceICFailure, false) \
     \
     v(bool, dumpGeneratedBytecodes, false) \
     v(bool, dumpBytecodeLivenessResults, false) \
@@ -157,6 +158,8 @@ typedef OptionRange optionRange;
     v(unsigned, llvmMaxStackSize, 128 * KB) \
     v(bool, llvmDisallowAVX, true) \
     v(bool, ftlCrashes, false) /* fool-proof way of checking that you ended up in the FTL. ;-) */\
+    v(bool, clobberAllRegsInFTLICSlowPath, !ASSERT_DISABLED) \
+    v(bool, assumeAllRegsInFTLICAreLive, true) \
     \
     v(bool, enableConcurrentJIT, true) \
     v(unsigned, numberOfDFGCompilerThreads, computeNumberOfWorkerThreads(2, 2) - 1) \
index 5f00e11..925a4bc 100644 (file)
@@ -1,3 +1,12 @@
+2014-03-28  Filip Pizlo  <fpizlo@apple.com>
+
+        Land the stackmap register liveness glue with the uses of the liveness disabled
+        https://bugs.webkit.org/show_bug.cgi?id=130924
+
+        Reviewed by Oliver Hunt.
+
+        * Scripts/run-jsc-stress-tests:
+
 2014-03-29  Alexey Proskuryakov  <ap@apple.com>
 
         Update WebKit1.StringTruncator for Mountain Lion.
index f19b0a1..c8af099 100755 (executable)
@@ -250,6 +250,10 @@ def prefixCommand(prefix)
     "awk " + Shellwords.shellescape("{ printf #{(prefix + ': ').inspect}; print }")
 end
 
+def redirectAndPrefixCommand(prefix)
+    prefixCommand(prefix) + " 2>&1"
+end
+
 def pipeAndPrefixCommand(outputFilename, prefix)
     "tee " + Shellwords.shellescape(outputFilename.to_s) + " | " + prefixCommand(prefix)
 end
@@ -276,7 +280,7 @@ def simpleErrorHandler
         | outp, plan |
         outp.puts "if test -e #{plan.failFile}"
         outp.puts "then"
-        outp.puts "    (echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "    (echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "    " + plan.failCommand
         outp.puts "else"
         outp.puts "    " + plan.successCommand
@@ -293,7 +297,7 @@ def diffErrorHandler(expectedFilename)
         
         outp.puts "if test -e #{plan.failFile}"
         outp.puts "then"
-        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "    " + plan.failCommand
         outp.puts "elif test -e ../#{Shellwords.shellescape(expectedFilename)}"
         outp.puts "then"
@@ -302,11 +306,11 @@ def diffErrorHandler(expectedFilename)
         outp.puts "    then"
         outp.puts "    " + plan.successCommand
         outp.puts "    else"
-        outp.puts "        (echo \"DIFF FAILURE!\" && cat #{diffFilename}) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "        (echo \"DIFF FAILURE!\" && cat #{diffFilename}) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "        " + plan.failCommand
         outp.puts "    fi"
         outp.puts "else"
-        outp.puts "    (echo \"NO EXPECTATION!\" && cat #{outputFilename}) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "    (echo \"NO EXPECTATION!\" && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "    " + plan.failCommand
         outp.puts "fi"
     }
@@ -321,11 +325,11 @@ def mozillaErrorHandler
 
         outp.puts "if test -e #{plan.failFile}"
         outp.puts "then"
-        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "    (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "    " + plan.failCommand
         outp.puts "elif grep -i -q failed! #{outputFilename}"
         outp.puts "then"
-        outp.puts "    (echo Detected failures: && cat #{outputFilename}) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "    (echo Detected failures: && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "    " + plan.failCommand
         outp.puts "else"
         outp.puts "    " + plan.successCommand
@@ -347,7 +351,7 @@ def mozillaFailErrorHandler
         outp.puts "then"
         outp.puts "    " + plan.successCommand
         outp.puts "else"
-        outp.puts "    (echo NOTICE: You made this test pass, but it was expected to fail) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "    (echo NOTICE: You made this test pass, but it was expected to fail) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "    " + plan.failCommand
         outp.puts "fi"
     }
@@ -366,17 +370,17 @@ def mozillaExit3ErrorHandler
         outp.puts "    then"
         outp.puts "        if grep -i -q failed! #{outputFilename}"
         outp.puts "        then"
-        outp.puts "            (echo Detected failures: && cat #{outputFilename}) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "            (echo Detected failures: && cat #{outputFilename}) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "            " + plan.failCommand
         outp.puts "        else"
         outp.puts "            " + plan.successCommand
         outp.puts "        fi"
         outp.puts "    else"
-        outp.puts "        (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "        (cat #{outputFilename} && echo ERROR: Unexpected exit code: `cat #{plan.failFile}`) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "        " + plan.failCommand
         outp.puts "    fi"
         outp.puts "else"
-        outp.puts "    (cat #{outputFilename} && echo ERROR: Test expected to fail, but returned successfully) | " + prefixCommand(plan.name) + "1>&2"
+        outp.puts "    (cat #{outputFilename} && echo ERROR: Test expected to fail, but returned successfully) | " + redirectAndPrefixCommand(plan.name)
         outp.puts "    " + plan.failCommand
         outp.puts "fi"
     }
@@ -610,6 +614,11 @@ def defaultQuickRun
     runFTLNoCJIT
 end
 
+def defaultFTLSpotCheck
+    defaultQuickRun
+    runFTLNoSimpleOpt
+end
+
 # This is expected to not do eager runs because eager runs can have a lot of recompilations
 # for reasons that don't arise in the real world. It's used for tests that assert convergence
 # by counting recompilations.
@@ -639,7 +648,7 @@ def runLayoutTest(kind, *options)
     prepareExtraRelativeFiles(["../#{testName}-expected.txt"], $benchmarkDirectory)
     prepareExtraAbsoluteFiles(LAYOUTTESTS_PATH, ["resources/standalone-pre.js", "resources/standalone-post.js"])
 
-    args = [pathToVM.to_s] + options +
+    args = [pathToVM.to_s] + NO_FTL_OPTIONS + options +
         [(Pathname.new("resources") + "standalone-pre.js").to_s,
          $benchmark.to_s,
          (Pathname.new("resources") + "standalone-post.js").to_s]
@@ -721,7 +730,7 @@ def runMozillaTest(kind, mode, extraFiles, *options)
         kind = "mozilla"
     end
     prepareExtraRelativeFiles(extraFiles.map{|v| (Pathname("..") + v).to_s}, $collection)
-    args = [pathToVM.to_s] + options + extraFiles.map{|v| v.to_s} + [$benchmark.to_s]
+    args = [pathToVM.to_s] + NO_FTL_OPTIONS + options + extraFiles.map{|v| v.to_s} + [$benchmark.to_s]
     case mode
     when :normal
         errorHandler = mozillaErrorHandler