<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[206694] trunk/Source/JavaScriptCore</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/206694">206694</a></dd>
<dt>Author</dt> <dd>fpizlo@apple.com</dd>
<dt>Date</dt> <dd>2016-09-30 17:08:28 -0700 (Fri, 30 Sep 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>B3 should support trapping memory accesses
https://bugs.webkit.org/show_bug.cgi?id=162689

Reviewed by Geoffrey Garen.
        
This adds a traps flag to B3::Kind. It also makes B3::Kind work more like Air::Kind, in the
sense that it's a bag of distinct bits - it doesn't need to be a union unless we get enough
things that it would make a difference.
        
The only analysis that needs to know about traps is effects. It now knows that traps implies
sideExits, which means that this turns off DCE. The only optimization that needs to know
about traps is eliminateCommonSubexpressions(), which needs to pessimize its store
elimination if the store traps.
        
The hard part of this change is teaching the instruction selector to faithfully carry the
traps flag down to Air. I got this to work by making ArgPromise a non-copyable object that
knows whether you've used it in an instruction. It knows when you call consume(). If you do
this then ArgPromise cannot be destructed without first passing your inst through it. This,
along with a few other hacks, means that all of the load-op and load-op-store fusions
correctly carry the trap bit: if any of the B3 loads or stores involved traps then you get
traps in Air.
        
This framework also sets us up to do bug 162688, since the ArgPromise::inst() hook is
powerful enough to allow wrapping the instruction with a Patch.
        
I added some tests to testb3 that verify that optimizations are appropriately inhibited and
that the traps flag survives until the bitter end of Air.

* b3/B3EliminateCommonSubexpressions.cpp:
* b3/B3Kind.cpp:
(JSC::B3::Kind::dump):
* b3/B3Kind.h:
(JSC::B3::Kind::Kind):
(JSC::B3::Kind::hasExtraBits):
(JSC::B3::Kind::isChill):
(JSC::B3::Kind::setIsChill):
(JSC::B3::Kind::hasTraps):
(JSC::B3::Kind::traps):
(JSC::B3::Kind::setTraps):
(JSC::B3::Kind::operator==):
(JSC::B3::Kind::hash):
(JSC::B3::trapping):
* b3/B3LowerToAir.cpp:
(JSC::B3::Air::LowerToAir::ArgPromise::swap):
(JSC::B3::Air::LowerToAir::ArgPromise::ArgPromise):
(JSC::B3::Air::LowerToAir::ArgPromise::operator=):
(JSC::B3::Air::LowerToAir::ArgPromise::~ArgPromise):
(JSC::B3::Air::LowerToAir::ArgPromise::setTraps):
(JSC::B3::Air::LowerToAir::ArgPromise::consume):
(JSC::B3::Air::LowerToAir::ArgPromise::inst):
(JSC::B3::Air::LowerToAir::trappingInst):
(JSC::B3::Air::LowerToAir::loadPromiseAnyOpcode):
(JSC::B3::Air::LowerToAir::appendUnOp):
(JSC::B3::Air::LowerToAir::appendBinOp):
(JSC::B3::Air::LowerToAir::tryAppendStoreUnOp):
(JSC::B3::Air::LowerToAir::tryAppendStoreBinOp):
(JSC::B3::Air::LowerToAir::appendStore):
(JSC::B3::Air::LowerToAir::append):
(JSC::B3::Air::LowerToAir::createGenericCompare):
(JSC::B3::Air::LowerToAir::createBranch):
(JSC::B3::Air::LowerToAir::createCompare):
(JSC::B3::Air::LowerToAir::createSelect):
(JSC::B3::Air::LowerToAir::lower):
* b3/B3Validate.cpp:
* b3/B3Value.cpp:
(JSC::B3::Value::effects):
* b3/B3Value.h:
* b3/air/AirCode.h:
* b3/testb3.cpp:
(JSC::B3::testTrappingLoad):
(JSC::B3::testTrappingStore):
(JSC::B3::testTrappingLoadAddStore):
(JSC::B3::testTrappingLoadDCE):
(JSC::B3::testTrappingStoreElimination):
(JSC::B3::run):</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceJavaScriptCoreChangeLog">trunk/Source/JavaScriptCore/ChangeLog</a></li>
<li><a href="#trunkSourceJavaScriptCoreb3B3EliminateCommonSubexpressionscpp">trunk/Source/JavaScriptCore/b3/B3EliminateCommonSubexpressions.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreb3B3Kindcpp">trunk/Source/JavaScriptCore/b3/B3Kind.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreb3B3Kindh">trunk/Source/JavaScriptCore/b3/B3Kind.h</a></li>
<li><a href="#trunkSourceJavaScriptCoreb3B3LowerToAircpp">trunk/Source/JavaScriptCore/b3/B3LowerToAir.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreb3B3Validatecpp">trunk/Source/JavaScriptCore/b3/B3Validate.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreb3B3Valuecpp">trunk/Source/JavaScriptCore/b3/B3Value.cpp</a></li>
<li><a href="#trunkSourceJavaScriptCoreb3B3Valueh">trunk/Source/JavaScriptCore/b3/B3Value.h</a></li>
<li><a href="#trunkSourceJavaScriptCoreb3airAirCodeh">trunk/Source/JavaScriptCore/b3/air/AirCode.h</a></li>
<li><a href="#trunkSourceJavaScriptCoreb3testb3cpp">trunk/Source/JavaScriptCore/b3/testb3.cpp</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceJavaScriptCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/ChangeLog (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/ChangeLog        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/ChangeLog        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -1,3 +1,81 @@
</span><ins>+2016-09-30  Filip Pizlo  &lt;fpizlo@apple.com&gt;
+
+        B3 should support trapping memory accesses
+        https://bugs.webkit.org/show_bug.cgi?id=162689
+
+        Reviewed by Geoffrey Garen.
+        
+        This adds a traps flag to B3::Kind. It also makes B3::Kind work more like Air::Kind, in the
+        sense that it's a bag of distinct bits - it doesn't need to be a union unless we get enough
+        things that it would make a difference.
+        
+        The only analysis that needs to know about traps is effects. It now knows that traps implies
+        sideExits, which means that this turns off DCE. The only optimization that needs to know
+        about traps is eliminateCommonSubexpressions(), which needs to pessimize its store
+        elimination if the store traps.
+        
+        The hard part of this change is teaching the instruction selector to faithfully carry the
+        traps flag down to Air. I got this to work by making ArgPromise a non-copyable object that
+        knows whether you've used it in an instruction. It knows when you call consume(). If you do
+        this then ArgPromise cannot be destructed without first passing your inst through it. This,
+        along with a few other hacks, means that all of the load-op and load-op-store fusions
+        correctly carry the trap bit: if any of the B3 loads or stores involved traps then you get
+        traps in Air.
+        
+        This framework also sets us up to do bug 162688, since the ArgPromise::inst() hook is
+        powerful enough to allow wrapping the instruction with a Patch.
+        
+        I added some tests to testb3 that verify that optimizations are appropriately inhibited and
+        that the traps flag survives until the bitter end of Air.
+
+        * b3/B3EliminateCommonSubexpressions.cpp:
+        * b3/B3Kind.cpp:
+        (JSC::B3::Kind::dump):
+        * b3/B3Kind.h:
+        (JSC::B3::Kind::Kind):
+        (JSC::B3::Kind::hasExtraBits):
+        (JSC::B3::Kind::isChill):
+        (JSC::B3::Kind::setIsChill):
+        (JSC::B3::Kind::hasTraps):
+        (JSC::B3::Kind::traps):
+        (JSC::B3::Kind::setTraps):
+        (JSC::B3::Kind::operator==):
+        (JSC::B3::Kind::hash):
+        (JSC::B3::trapping):
+        * b3/B3LowerToAir.cpp:
+        (JSC::B3::Air::LowerToAir::ArgPromise::swap):
+        (JSC::B3::Air::LowerToAir::ArgPromise::ArgPromise):
+        (JSC::B3::Air::LowerToAir::ArgPromise::operator=):
+        (JSC::B3::Air::LowerToAir::ArgPromise::~ArgPromise):
+        (JSC::B3::Air::LowerToAir::ArgPromise::setTraps):
+        (JSC::B3::Air::LowerToAir::ArgPromise::consume):
+        (JSC::B3::Air::LowerToAir::ArgPromise::inst):
+        (JSC::B3::Air::LowerToAir::trappingInst):
+        (JSC::B3::Air::LowerToAir::loadPromiseAnyOpcode):
+        (JSC::B3::Air::LowerToAir::appendUnOp):
+        (JSC::B3::Air::LowerToAir::appendBinOp):
+        (JSC::B3::Air::LowerToAir::tryAppendStoreUnOp):
+        (JSC::B3::Air::LowerToAir::tryAppendStoreBinOp):
+        (JSC::B3::Air::LowerToAir::appendStore):
+        (JSC::B3::Air::LowerToAir::append):
+        (JSC::B3::Air::LowerToAir::createGenericCompare):
+        (JSC::B3::Air::LowerToAir::createBranch):
+        (JSC::B3::Air::LowerToAir::createCompare):
+        (JSC::B3::Air::LowerToAir::createSelect):
+        (JSC::B3::Air::LowerToAir::lower):
+        * b3/B3Validate.cpp:
+        * b3/B3Value.cpp:
+        (JSC::B3::Value::effects):
+        * b3/B3Value.h:
+        * b3/air/AirCode.h:
+        * b3/testb3.cpp:
+        (JSC::B3::testTrappingLoad):
+        (JSC::B3::testTrappingStore):
+        (JSC::B3::testTrappingLoadAddStore):
+        (JSC::B3::testTrappingLoadDCE):
+        (JSC::B3::testTrappingStoreElimination):
+        (JSC::B3::run):
+
</ins><span class="cx"> 2016-09-30  Joseph Pecoraro  &lt;pecoraro@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Web Inspector: Stepping over/out of a function sometimes resumes instead of taking you to caller
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreb3B3EliminateCommonSubexpressionscpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/b3/B3EliminateCommonSubexpressions.cpp (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/b3/B3EliminateCommonSubexpressions.cpp        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/b3/B3EliminateCommonSubexpressions.cpp        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -466,7 +466,7 @@
</span><span class="cx">     template&lt;typename Filter&gt;
</span><span class="cx">     void handleStoreAfterClobber(Value* ptr, HeapRange range, const Filter&amp; filter)
</span><span class="cx">     {
</span><del>-        if (findStoreAfterClobber(ptr, range, filter)) {
</del><ins>+        if (!m_value-&gt;traps() &amp;&amp; findStoreAfterClobber(ptr, range, filter)) {
</ins><span class="cx">             m_value-&gt;replaceWithNop();
</span><span class="cx">             m_changed = true;
</span><span class="cx">             return;
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreb3B3Kindcpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/b3/B3Kind.cpp (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/b3/B3Kind.cpp        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/b3/B3Kind.cpp        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -39,6 +39,8 @@
</span><span class="cx">     CommaPrinter comma(&quot;, &quot;, &quot;&lt;&quot;);
</span><span class="cx">     if (isChill())
</span><span class="cx">         out.print(comma, &quot;Chill&quot;);
</span><ins>+    if (traps())
+        out.print(comma, &quot;Traps&quot;);
</ins><span class="cx">     if (comma.didPrint())
</span><span class="cx">         out.print(&quot;&gt;&quot;);
</span><span class="cx"> }
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreb3B3Kindh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/b3/B3Kind.h (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/b3/B3Kind.h        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/b3/B3Kind.h        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -60,8 +60,9 @@
</span><span class="cx"> public:
</span><span class="cx">     Kind(Opcode opcode)
</span><span class="cx">         : m_opcode(opcode)
</span><ins>+        , m_isChill(false)
+        , m_traps(false)
</ins><span class="cx">     {
</span><del>-        u.bits = 0;
</del><span class="cx">     }
</span><span class="cx">     
</span><span class="cx">     Kind()
</span><span class="lines">@@ -72,7 +73,7 @@
</span><span class="cx">     Opcode opcode() const { return m_opcode; }
</span><span class="cx">     void setOpcode(Opcode opcode) { m_opcode = opcode; }
</span><span class="cx">     
</span><del>-    bool hasExtraBits() const { return !!u.bits; }
</del><ins>+    bool hasExtraBits() const { return m_isChill || m_traps; }
</ins><span class="cx">     
</span><span class="cx">     // Chill bit. This applies to division-based arithmetic ops, which may trap on some
</span><span class="cx">     // platforms or exhibit bizarre behavior when passed certain inputs. The non-chill
</span><span class="lines">@@ -100,14 +101,46 @@
</span><span class="cx">     }
</span><span class="cx">     bool isChill() const
</span><span class="cx">     {
</span><del>-        return hasIsChill() &amp;&amp; u.isChill;
</del><ins>+        return m_isChill;
</ins><span class="cx">     }
</span><span class="cx">     void setIsChill(bool isChill)
</span><span class="cx">     {
</span><span class="cx">         ASSERT(hasIsChill());
</span><del>-        u.isChill = isChill;
</del><ins>+        m_isChill = isChill;
</ins><span class="cx">     }
</span><span class="cx">     
</span><ins>+    // Traps bit. This applies to memory access ops. It means that the instruction could
+    // trap as part of some check it performs, and that we mean to make this observable. This
+    // currently only applies to memory accesses (loads and stores). You don't get to find out where
+    // in the Procedure the trap happened. If you try to work it out using Origin, you'll have a bad
+    // time because the instruction selector is too sloppy with Origin().
+    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=162688
+    bool hasTraps() const
+    {
+        switch (m_opcode) {
+        case Load8Z:
+        case Load8S:
+        case Load16Z:
+        case Load16S:
+        case Load:
+        case Store8:
+        case Store16:
+        case Store:
+            return true;
+        default:
+            return false;
+        }
+    }
+    bool traps() const
+    {
+        return m_traps;
+    }
+    void setTraps(bool traps)
+    {
+        ASSERT(hasTraps());
+        m_traps = traps;
+    }
+    
</ins><span class="cx">     // Rules for adding new properties:
</span><span class="cx">     // - Put the accessors here.
</span><span class="cx">     // - hasBlah() should check if the opcode allows for your property.
</span><span class="lines">@@ -119,7 +152,8 @@
</span><span class="cx">     bool operator==(const Kind&amp; other) const
</span><span class="cx">     {
</span><span class="cx">         return m_opcode == other.m_opcode
</span><del>-            &amp;&amp; u.bits == other.u.bits;
</del><ins>+            &amp;&amp; m_isChill == other.m_isChill
+            &amp;&amp; m_traps == other.m_traps;
</ins><span class="cx">     }
</span><span class="cx">     
</span><span class="cx">     bool operator!=(const Kind&amp; other) const
</span><span class="lines">@@ -133,13 +167,14 @@
</span><span class="cx">     {
</span><span class="cx">         // It's almost certainly more important that this hash function is cheap to compute than
</span><span class="cx">         // anything else. We can live with some kind hash collisions.
</span><del>-        return m_opcode + u.bits * 111;
</del><ins>+        return m_opcode + (static_cast&lt;unsigned&gt;(m_isChill) &lt;&lt; 16) + (static_cast&lt;unsigned&gt;(m_traps) &lt;&lt; 7);
</ins><span class="cx">     }
</span><span class="cx">     
</span><span class="cx">     Kind(WTF::HashTableDeletedValueType)
</span><span class="cx">         : m_opcode(Oops)
</span><ins>+        , m_isChill(true)
+        , m_traps(false)
</ins><span class="cx">     {
</span><del>-        u.bits = 1;
</del><span class="cx">     }
</span><span class="cx">     
</span><span class="cx">     bool isHashTableDeletedValue() const
</span><span class="lines">@@ -149,10 +184,8 @@
</span><span class="cx">     
</span><span class="cx"> private:
</span><span class="cx">     Opcode m_opcode;
</span><del>-    union {
-        bool isChill;
-        uint16_t bits;
-    } u;
</del><ins>+    bool m_isChill : 1;
+    bool m_traps : 1;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> // For every flag 'foo' you add, it's customary to create a Kind B3::foo(Kind) function that makes
</span><span class="lines">@@ -160,7 +193,8 @@
</span><span class="cx"> //
</span><span class="cx"> //     block-&gt;appendNew&lt;Value&gt;(m_proc, chill(Mod), Origin(), a, b);
</span><span class="cx"> //
</span><del>-// That looks pretty slick. Let's keep it that way.
</del><ins>+// I like to make the flag name fill in the sentence &quot;Mod _____&quot; (like &quot;isChill&quot; or &quot;traps&quot;) while
+// the flag constructor fills in the phrase &quot;_____ Mod&quot; (like &quot;chill&quot; or &quot;trapping&quot;).
</ins><span class="cx"> 
</span><span class="cx"> inline Kind chill(Kind kind)
</span><span class="cx"> {
</span><span class="lines">@@ -168,6 +202,12 @@
</span><span class="cx">     return kind;
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+inline Kind trapping(Kind kind)
+{
+    kind.setTraps(true);
+    return kind;
+}
+
</ins><span class="cx"> struct KindHash {
</span><span class="cx">     static unsigned hash(const Kind&amp; key) { return key.hash(); }
</span><span class="cx">     static bool equal(const Kind&amp; a, const Kind&amp; b) { return a == b; }
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreb3B3LowerToAircpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/b3/B3LowerToAir.cpp (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/b3/B3LowerToAir.cpp        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/b3/B3LowerToAir.cpp        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -181,6 +181,7 @@
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     class ArgPromise {
</span><ins>+        WTF_MAKE_NONCOPYABLE(ArgPromise);
</ins><span class="cx">     public:
</span><span class="cx">         ArgPromise() { }
</span><span class="cx"> 
</span><span class="lines">@@ -189,6 +190,37 @@
</span><span class="cx">             , m_value(valueToLock)
</span><span class="cx">         {
</span><span class="cx">         }
</span><ins>+        
+        void swap(ArgPromise&amp; other)
+        {
+            std::swap(m_arg, other.m_arg);
+            std::swap(m_value, other.m_value);
+            std::swap(m_wasConsumed, other.m_wasConsumed);
+            std::swap(m_wasWrapped, other.m_wasWrapped);
+            std::swap(m_traps, other.m_traps);
+        }
+        
+        ArgPromise(ArgPromise&amp;&amp; other)
+        {
+            swap(other);
+        }
+        
+        ArgPromise&amp; operator=(ArgPromise&amp;&amp; other)
+        {
+            swap(other);
+            return *this;
+        }
+        
+        ~ArgPromise()
+        {
+            if (m_wasConsumed)
+                RELEASE_ASSERT(m_wasWrapped);
+        }
+        
+        void setTraps(bool value)
+        {
+            m_traps = value;
+        }
</ins><span class="cx"> 
</span><span class="cx">         static ArgPromise tmp(Value* value)
</span><span class="cx">         {
</span><span class="lines">@@ -211,8 +243,9 @@
</span><span class="cx">             return m_arg;
</span><span class="cx">         }
</span><span class="cx"> 
</span><del>-        Arg consume(LowerToAir&amp; lower) const
</del><ins>+        Arg consume(LowerToAir&amp; lower)
</ins><span class="cx">         {
</span><ins>+            m_wasConsumed = true;
</ins><span class="cx">             if (!m_arg &amp;&amp; m_value)
</span><span class="cx">                 return lower.tmp(m_value);
</span><span class="cx">             if (m_value)
</span><span class="lines">@@ -220,6 +253,15 @@
</span><span class="cx">             return m_arg;
</span><span class="cx">         }
</span><span class="cx">         
</span><ins>+        template&lt;typename... Args&gt;
+        Inst inst(Args&amp;&amp;... args)
+        {
+            Inst result(std::forward&lt;Args&gt;(args)...);
+            result.kind.traps |= m_traps;
+            m_wasWrapped = true;
+            return result;
+        }
+        
</ins><span class="cx">     private:
</span><span class="cx">         // Three forms:
</span><span class="cx">         // Everything null: invalid.
</span><span class="lines">@@ -227,7 +269,10 @@
</span><span class="cx">         // Arg null, value non-null: it's a tmp, pin it when necessary.
</span><span class="cx">         // Arg non-null, value non-null: use the arg, lock the value.
</span><span class="cx">         Arg m_arg;
</span><del>-        Value* m_value;
</del><ins>+        Value* m_value { nullptr };
+        bool m_wasConsumed { false };
+        bool m_wasWrapped { false };
+        bool m_traps { false };
</ins><span class="cx">     };
</span><span class="cx"> 
</span><span class="cx">     // Consider using tmpPromise() in cases where you aren't sure that you want to pin the value yet.
</span><span class="lines">@@ -246,11 +291,12 @@
</span><span class="cx">     //         append(Foo, tmp(bar))
</span><span class="cx">     //
</span><span class="cx">     // Idiom #3: Same as Idiom #2, but using tmpPromise. Notice that this calls consume() only after
</span><del>-    // it's sure it will use the tmp. That's deliberate.
</del><ins>+    // it's sure it will use the tmp. That's deliberate. Also note that you're required to pass any
+    // Inst you create with consumed promises through that promise's inst() function.
</ins><span class="cx">     //
</span><span class="cx">     //     ArgPromise promise = tmpPromise(bar);
</span><span class="cx">     //     if (isValidForm(Foo, promise.kind()))
</span><del>-    //         append(Foo, promise.consume(*this))
</del><ins>+    //         append(promise.inst(Foo, promise.consume(*this)))
</ins><span class="cx">     //
</span><span class="cx">     // In both idiom #2 and idiom #3, we don't pin the value to a temporary except when we actually
</span><span class="cx">     // emit the instruction. Both tmp() and tmpPromise().consume(*this) will pin it. Pinning means
</span><span class="lines">@@ -288,9 +334,9 @@
</span><span class="cx">     // loadAddr() helper and require you to balance ArgPromise's in code like this. Such code will
</span><span class="cx">     // work fine if written as:
</span><span class="cx">     //
</span><del>-    //     auto tryThings = [this] (const ArgPromise&amp; left, const ArgPromise&amp; right) {
</del><ins>+    //     auto tryThings = [this] (ArgPromise&amp; left, ArgPromise&amp; right) {
</ins><span class="cx">     //         if (isValidForm(Foo, left.kind(), right.kind()))
</span><del>-    //             return Inst(Foo, m_value, left.consume(*this), right.consume(*this));
</del><ins>+    //             return left.inst(right.inst(Foo, m_value, left.consume(*this), right.consume(*this)));
</ins><span class="cx">     //         return Inst();
</span><span class="cx">     //     };
</span><span class="cx">     //     if (Inst result = tryThings(loadPromise(left), tmpPromise(right)))
</span><span class="lines">@@ -466,7 +512,21 @@
</span><span class="cx"> 
</span><span class="cx">         return result;
</span><span class="cx">     }
</span><del>-
</del><ins>+    
+    template&lt;typename... Args&gt;
+    Inst trappingInst(bool traps, Args&amp;&amp;... args)
+    {
+        Inst result(std::forward&lt;Args&gt;(args)...);
+        result.kind.traps |= traps;
+        return result;
+    }
+    
+    template&lt;typename... Args&gt;
+    Inst trappingInst(Value* value, Args&amp;&amp;... args)
+    {
+        return trappingInst(value-&gt;traps(), std::forward&lt;Args&gt;(args)...);
+    }
+    
</ins><span class="cx">     ArgPromise loadPromiseAnyOpcode(Value* loadValue)
</span><span class="cx">     {
</span><span class="cx">         if (!canBeInternal(loadValue))
</span><span class="lines">@@ -473,7 +533,10 @@
</span><span class="cx">             return Arg();
</span><span class="cx">         if (crossesInterference(loadValue))
</span><span class="cx">             return Arg();
</span><del>-        return ArgPromise(addr(loadValue), loadValue);
</del><ins>+        ArgPromise result(addr(loadValue), loadValue);
+        if (loadValue-&gt;traps())
+            result.setTraps(true);
+        return result;
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     ArgPromise loadPromise(Value* loadValue, B3::Opcode loadOpcode)
</span><span class="lines">@@ -583,7 +646,7 @@
</span><span class="cx"> 
</span><span class="cx">         ArgPromise addr = loadPromise(value);
</span><span class="cx">         if (isValidForm(opcode, addr.kind(), Arg::Tmp)) {
</span><del>-            append(opcode, addr.consume(*this), result);
</del><ins>+            append(addr.inst(opcode, m_value, addr.consume(*this), result));
</ins><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -717,7 +780,7 @@
</span><span class="cx">         if (left != right) {
</span><span class="cx">             ArgPromise leftAddr = loadPromise(left);
</span><span class="cx">             if (isValidForm(opcode, leftAddr.kind(), Arg::Tmp, Arg::Tmp)) {
</span><del>-                append(opcode, leftAddr.consume(*this), tmp(right), result);
</del><ins>+                append(leftAddr.inst(opcode, m_value, leftAddr.consume(*this), tmp(right), result));
</ins><span class="cx">                 return;
</span><span class="cx">             }
</span><span class="cx"> 
</span><span class="lines">@@ -724,7 +787,7 @@
</span><span class="cx">             if (commutativity == Commutative) {
</span><span class="cx">                 if (isValidForm(opcode, leftAddr.kind(), Arg::Tmp)) {
</span><span class="cx">                     append(relaxedMoveForType(m_value-&gt;type()), tmp(right), result);
</span><del>-                    append(opcode, leftAddr.consume(*this), result);
</del><ins>+                    append(leftAddr.inst(opcode, m_value, leftAddr.consume(*this), result));
</ins><span class="cx">                     return;
</span><span class="cx">                 }
</span><span class="cx">             }
</span><span class="lines">@@ -731,13 +794,13 @@
</span><span class="cx"> 
</span><span class="cx">             ArgPromise rightAddr = loadPromise(right);
</span><span class="cx">             if (isValidForm(opcode, Arg::Tmp, rightAddr.kind(), Arg::Tmp)) {
</span><del>-                append(opcode, tmp(left), rightAddr.consume(*this), result);
</del><ins>+                append(rightAddr.inst(opcode, m_value, tmp(left), rightAddr.consume(*this), result));
</ins><span class="cx">                 return;
</span><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             if (commutativity == Commutative) {
</span><span class="cx">                 if (isValidForm(opcode, rightAddr.kind(), Arg::Tmp, Arg::Tmp)) {
</span><del>-                    append(opcode, rightAddr.consume(*this), tmp(left), result);
</del><ins>+                    append(rightAddr.inst(opcode, m_value, rightAddr.consume(*this), tmp(left), result));
</ins><span class="cx">                     return;
</span><span class="cx">                 }
</span><span class="cx">             }
</span><span class="lines">@@ -744,7 +807,7 @@
</span><span class="cx"> 
</span><span class="cx">             if (isValidForm(opcode, rightAddr.kind(), Arg::Tmp)) {
</span><span class="cx">                 append(relaxedMoveForType(m_value-&gt;type()), tmp(left), result);
</span><del>-                append(opcode, rightAddr.consume(*this), result);
</del><ins>+                append(rightAddr.inst(opcode, m_value, rightAddr.consume(*this), result));
</ins><span class="cx">                 return;
</span><span class="cx">             }
</span><span class="cx">         }
</span><span class="lines">@@ -823,7 +886,7 @@
</span><span class="cx">             return false;
</span><span class="cx">         
</span><span class="cx">         loadPromise.consume(*this);
</span><del>-        append(opcode, storeAddr);
</del><ins>+        append(trappingInst(m_value, loadPromise.inst(opcode, m_value, storeAddr)));
</ins><span class="cx">         return true;
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="lines">@@ -875,7 +938,7 @@
</span><span class="cx"> 
</span><span class="cx">         if (isValidForm(opcode, Arg::Imm, storeAddr.kind()) &amp;&amp; imm(otherValue)) {
</span><span class="cx">             loadPromise.consume(*this);
</span><del>-            append(opcode, imm(otherValue), storeAddr);
</del><ins>+            append(trappingInst(m_value, loadPromise.inst(opcode, m_value, imm(otherValue), storeAddr)));
</ins><span class="cx">             return true;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -883,7 +946,7 @@
</span><span class="cx">             return false;
</span><span class="cx"> 
</span><span class="cx">         loadPromise.consume(*this);
</span><del>-        append(opcode, tmp(otherValue), storeAddr);
</del><ins>+        append(trappingInst(m_value, loadPromise.inst(opcode, m_value, tmp(otherValue), storeAddr)));
</ins><span class="cx">         return true;
</span><span class="cx">     }
</span><span class="cx"> 
</span><span class="lines">@@ -901,9 +964,10 @@
</span><span class="cx">         return createStore(moveOpcode, value, dest);
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    void appendStore(Value* value, const Arg&amp; dest)
</del><ins>+    template&lt;typename... Args&gt;
+    void appendStore(Args&amp;&amp;... args)
</ins><span class="cx">     {
</span><del>-        m_insts.last().append(createStore(value, dest));
</del><ins>+        append(trappingInst(m_value, createStore(std::forward&lt;Args&gt;(args)...)));
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     Air::Opcode moveForType(Type type)
</span><span class="lines">@@ -963,6 +1027,15 @@
</span><span class="cx">     {
</span><span class="cx">         m_insts.last().append(Inst(opcode, m_value, std::forward&lt;Arguments&gt;(arguments)...));
</span><span class="cx">     }
</span><ins>+    
+    void append(Inst&amp;&amp; inst)
+    {
+        m_insts.last().append(WTFMove(inst));
+    }
+    void append(const Inst&amp; inst)
+    {
+        m_insts.last().append(inst);
+    }
</ins><span class="cx"> 
</span><span class="cx">     template&lt;typename T, typename... Arguments&gt;
</span><span class="cx">     T* ensureSpecial(T*&amp; field, Arguments&amp;&amp;... arguments)
</span><span class="lines">@@ -1190,7 +1263,7 @@
</span><span class="cx">                 Arg rightImm = imm(right);
</span><span class="cx"> 
</span><span class="cx">                 auto tryCompare = [&amp;] (
</span><del>-                    Arg::Width width, const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+                    Arg::Width width, ArgPromise&amp;&amp; left, ArgPromise&amp;&amp; right) -&gt; Inst {
</ins><span class="cx">                     if (Inst result = compare(width, relCond, left, right))
</span><span class="cx">                         return result;
</span><span class="cx">                     if (Inst result = compare(width, relCond.flipped(), right, left))
</span><span class="lines">@@ -1279,13 +1352,17 @@
</span><span class="cx">                 }
</span><span class="cx"> 
</span><span class="cx">                 // Finally, handle comparison between tmps.
</span><del>-                return compare(width, relCond, tmpPromise(left), tmpPromise(right));
</del><ins>+                ArgPromise leftPromise = tmpPromise(left);
+                ArgPromise rightPromise = tmpPromise(right);
+                return compare(width, relCond, leftPromise, rightPromise);
</ins><span class="cx">             }
</span><span class="cx"> 
</span><span class="cx">             // Floating point comparisons can't really do anything smart.
</span><ins>+            ArgPromise leftPromise = tmpPromise(left);
+            ArgPromise rightPromise = tmpPromise(right);
</ins><span class="cx">             if (value-&gt;child(0)-&gt;type() == Float)
</span><del>-                return compareFloat(doubleCond, tmpPromise(left), tmpPromise(right));
-            return compareDouble(doubleCond, tmpPromise(left), tmpPromise(right));
</del><ins>+                return compareFloat(doubleCond, leftPromise, rightPromise);
+            return compareDouble(doubleCond, leftPromise, rightPromise);
</ins><span class="cx">         };
</span><span class="cx"> 
</span><span class="cx">         Arg::Width width = Arg::widthForB3Type(value-&gt;type());
</span><span class="lines">@@ -1292,7 +1369,7 @@
</span><span class="cx">         Arg resCond = Arg::resCond(MacroAssembler::NonZero).inverted(inverted);
</span><span class="cx">         
</span><span class="cx">         auto tryTest = [&amp;] (
</span><del>-            Arg::Width width, const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+            Arg::Width width, ArgPromise&amp;&amp; left, ArgPromise&amp;&amp; right) -&gt; Inst {
</ins><span class="cx">             if (Inst result = test(width, resCond, left, right))
</span><span class="cx">                 return result;
</span><span class="cx">             if (Inst result = test(width, resCond, right, left))
</span><span class="lines">@@ -1420,8 +1497,7 @@
</span><span class="cx">                 if (hasRightConst) {
</span><span class="cx">                     if ((width == Arg::Width32 &amp;&amp; rightConst == 0xffffffff)
</span><span class="cx">                         || (width == Arg::Width64 &amp;&amp; rightConst == -1)) {
</span><del>-                        ArgPromise argPromise = tmpPromise(left);
-                        if (Inst result = tryTest(width, argPromise, argPromise))
</del><ins>+                        if (Inst result = tryTest(width, tmpPromise(left), tmpPromise(left)))
</ins><span class="cx">                             return result;
</span><span class="cx">                     }
</span><span class="cx">                     if (isRepresentableAs&lt;uint32_t&gt;(rightConst)) {
</span><span class="lines">@@ -1481,13 +1557,17 @@
</span><span class="cx">                 }
</span><span class="cx">             }
</span><span class="cx"> 
</span><del>-            if (Inst result = test(width, resCond, tmpPromise(value), Arg::bitImm(-1)))
</del><ins>+            ArgPromise leftPromise = tmpPromise(value);
+            ArgPromise rightPromise = Arg::bitImm(-1);
+            if (Inst result = test(width, resCond, leftPromise, rightPromise))
</ins><span class="cx">                 return result;
</span><span class="cx">         }
</span><span class="cx">         
</span><span class="cx">         // Sometimes this is the only form of test available. We prefer not to use this because
</span><span class="cx">         // it's less canonical.
</span><del>-        return test(width, resCond, tmpPromise(value), tmpPromise(value));
</del><ins>+        ArgPromise leftPromise = tmpPromise(value);
+        ArgPromise rightPromise = tmpPromise(value);
+        return test(width, resCond, leftPromise, rightPromise);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     Inst createBranch(Value* value, bool inverted = false)
</span><span class="lines">@@ -1496,13 +1576,13 @@
</span><span class="cx">             value,
</span><span class="cx">             [this] (
</span><span class="cx">                 Arg::Width width, const Arg&amp; relCond,
</span><del>-                const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+                ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 switch (width) {
</span><span class="cx">                 case Arg::Width8:
</span><span class="cx">                     if (isValidForm(Branch8, Arg::RelCond, left.kind(), right.kind())) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             Branch8, m_value, relCond,
</span><del>-                            left.consume(*this), right.consume(*this));
</del><ins>+                            left.consume(*this), right.consume(*this)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width16:
</span><span class="lines">@@ -1509,16 +1589,16 @@
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width32:
</span><span class="cx">                     if (isValidForm(Branch32, Arg::RelCond, left.kind(), right.kind())) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             Branch32, m_value, relCond,
</span><del>-                            left.consume(*this), right.consume(*this));
</del><ins>+                            left.consume(*this), right.consume(*this)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width64:
</span><span class="cx">                     if (isValidForm(Branch64, Arg::RelCond, left.kind(), right.kind())) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             Branch64, m_value, relCond,
</span><del>-                            left.consume(*this), right.consume(*this));
</del><ins>+                            left.consume(*this), right.consume(*this)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 }
</span><span class="lines">@@ -1526,13 +1606,13 @@
</span><span class="cx">             },
</span><span class="cx">             [this] (
</span><span class="cx">                 Arg::Width width, const Arg&amp; resCond,
</span><del>-                const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+                ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 switch (width) {
</span><span class="cx">                 case Arg::Width8:
</span><span class="cx">                     if (isValidForm(BranchTest8, Arg::ResCond, left.kind(), right.kind())) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             BranchTest8, m_value, resCond,
</span><del>-                            left.consume(*this), right.consume(*this));
</del><ins>+                            left.consume(*this), right.consume(*this)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width16:
</span><span class="lines">@@ -1539,34 +1619,34 @@
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width32:
</span><span class="cx">                     if (isValidForm(BranchTest32, Arg::ResCond, left.kind(), right.kind())) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             BranchTest32, m_value, resCond,
</span><del>-                            left.consume(*this), right.consume(*this));
</del><ins>+                            left.consume(*this), right.consume(*this)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width64:
</span><span class="cx">                     if (isValidForm(BranchTest64, Arg::ResCond, left.kind(), right.kind())) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             BranchTest64, m_value, resCond,
</span><del>-                            left.consume(*this), right.consume(*this));
</del><ins>+                            left.consume(*this), right.consume(*this)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 }
</span><span class="cx">                 ASSERT_NOT_REACHED();
</span><span class="cx">             },
</span><del>-            [this] (Arg doubleCond, const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+            [this] (Arg doubleCond, ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 if (isValidForm(BranchDouble, Arg::DoubleCond, left.kind(), right.kind())) {
</span><del>-                    return Inst(
</del><ins>+                    return left.inst(right.inst(
</ins><span class="cx">                         BranchDouble, m_value, doubleCond,
</span><del>-                        left.consume(*this), right.consume(*this));
</del><ins>+                        left.consume(*this), right.consume(*this)));
</ins><span class="cx">                 }
</span><span class="cx">                 return Inst();
</span><span class="cx">             },
</span><del>-            [this] (Arg doubleCond, const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+            [this] (Arg doubleCond, ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 if (isValidForm(BranchFloat, Arg::DoubleCond, left.kind(), right.kind())) {
</span><del>-                    return Inst(
</del><ins>+                    return left.inst(right.inst(
</ins><span class="cx">                         BranchFloat, m_value, doubleCond,
</span><del>-                        left.consume(*this), right.consume(*this));
</del><ins>+                        left.consume(*this), right.consume(*this)));
</ins><span class="cx">                 }
</span><span class="cx">                 return Inst();
</span><span class="cx">             },
</span><span class="lines">@@ -1579,7 +1659,7 @@
</span><span class="cx">             value,
</span><span class="cx">             [this] (
</span><span class="cx">                 Arg::Width width, const Arg&amp; relCond,
</span><del>-                const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+                ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 switch (width) {
</span><span class="cx">                 case Arg::Width8:
</span><span class="cx">                 case Arg::Width16:
</span><span class="lines">@@ -1586,16 +1666,16 @@
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width32:
</span><span class="cx">                     if (isValidForm(Compare32, Arg::RelCond, left.kind(), right.kind(), Arg::Tmp)) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             Compare32, m_value, relCond,
</span><del>-                            left.consume(*this), right.consume(*this), tmp(m_value));
</del><ins>+                            left.consume(*this), right.consume(*this), tmp(m_value)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width64:
</span><span class="cx">                     if (isValidForm(Compare64, Arg::RelCond, left.kind(), right.kind(), Arg::Tmp)) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             Compare64, m_value, relCond,
</span><del>-                            left.consume(*this), right.consume(*this), tmp(m_value));
</del><ins>+                            left.consume(*this), right.consume(*this), tmp(m_value)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 }
</span><span class="lines">@@ -1603,7 +1683,7 @@
</span><span class="cx">             },
</span><span class="cx">             [this] (
</span><span class="cx">                 Arg::Width width, const Arg&amp; resCond,
</span><del>-                const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+                ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 switch (width) {
</span><span class="cx">                 case Arg::Width8:
</span><span class="cx">                 case Arg::Width16:
</span><span class="lines">@@ -1610,34 +1690,34 @@
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width32:
</span><span class="cx">                     if (isValidForm(Test32, Arg::ResCond, left.kind(), right.kind(), Arg::Tmp)) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             Test32, m_value, resCond,
</span><del>-                            left.consume(*this), right.consume(*this), tmp(m_value));
</del><ins>+                            left.consume(*this), right.consume(*this), tmp(m_value)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 case Arg::Width64:
</span><span class="cx">                     if (isValidForm(Test64, Arg::ResCond, left.kind(), right.kind(), Arg::Tmp)) {
</span><del>-                        return Inst(
</del><ins>+                        return left.inst(right.inst(
</ins><span class="cx">                             Test64, m_value, resCond,
</span><del>-                            left.consume(*this), right.consume(*this), tmp(m_value));
</del><ins>+                            left.consume(*this), right.consume(*this), tmp(m_value)));
</ins><span class="cx">                     }
</span><span class="cx">                     return Inst();
</span><span class="cx">                 }
</span><span class="cx">                 ASSERT_NOT_REACHED();
</span><span class="cx">             },
</span><del>-            [this] (const Arg&amp; doubleCond, const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+            [this] (const Arg&amp; doubleCond, ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 if (isValidForm(CompareDouble, Arg::DoubleCond, left.kind(), right.kind(), Arg::Tmp)) {
</span><del>-                    return Inst(
</del><ins>+                    return left.inst(right.inst(
</ins><span class="cx">                         CompareDouble, m_value, doubleCond,
</span><del>-                        left.consume(*this), right.consume(*this), tmp(m_value));
</del><ins>+                        left.consume(*this), right.consume(*this), tmp(m_value)));
</ins><span class="cx">                 }
</span><span class="cx">                 return Inst();
</span><span class="cx">             },
</span><del>-            [this] (const Arg&amp; doubleCond, const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+            [this] (const Arg&amp; doubleCond, ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 if (isValidForm(CompareFloat, Arg::DoubleCond, left.kind(), right.kind(), Arg::Tmp)) {
</span><del>-                    return Inst(
</del><ins>+                    return left.inst(right.inst(
</ins><span class="cx">                         CompareFloat, m_value, doubleCond,
</span><del>-                        left.consume(*this), right.consume(*this), tmp(m_value));
</del><ins>+                        left.consume(*this), right.consume(*this), tmp(m_value)));
</ins><span class="cx">                 }
</span><span class="cx">                 return Inst();
</span><span class="cx">             },
</span><span class="lines">@@ -1654,22 +1734,22 @@
</span><span class="cx">     };
</span><span class="cx">     Inst createSelect(const MoveConditionallyConfig&amp; config)
</span><span class="cx">     {
</span><del>-        auto createSelectInstruction = [&amp;] (Air::Opcode opcode, const Arg&amp; condition, const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+        auto createSelectInstruction = [&amp;] (Air::Opcode opcode, const Arg&amp; condition, ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">             if (isValidForm(opcode, condition.kind(), left.kind(), right.kind(), Arg::Tmp, Arg::Tmp, Arg::Tmp)) {
</span><span class="cx">                 Tmp result = tmp(m_value);
</span><span class="cx">                 Tmp thenCase = tmp(m_value-&gt;child(1));
</span><span class="cx">                 Tmp elseCase = tmp(m_value-&gt;child(2));
</span><del>-                return Inst(
</del><ins>+                return left.inst(right.inst(
</ins><span class="cx">                     opcode, m_value, condition,
</span><del>-                    left.consume(*this), right.consume(*this), thenCase, elseCase, result);
</del><ins>+                    left.consume(*this), right.consume(*this), thenCase, elseCase, result));
</ins><span class="cx">             }
</span><span class="cx">             if (isValidForm(opcode, condition.kind(), left.kind(), right.kind(), Arg::Tmp, Arg::Tmp)) {
</span><span class="cx">                 Tmp result = tmp(m_value);
</span><span class="cx">                 Tmp source = tmp(m_value-&gt;child(1));
</span><span class="cx">                 append(relaxedMoveForType(m_value-&gt;type()), tmp(m_value-&gt;child(2)), result);
</span><del>-                return Inst(
</del><ins>+                return left.inst(right.inst(
</ins><span class="cx">                     opcode, m_value, condition,
</span><del>-                    left.consume(*this), right.consume(*this), source, result);
</del><ins>+                    left.consume(*this), right.consume(*this), source, result));
</ins><span class="cx">             }
</span><span class="cx">             return Inst();
</span><span class="cx">         };
</span><span class="lines">@@ -1678,7 +1758,7 @@
</span><span class="cx">             m_value-&gt;child(0),
</span><span class="cx">             [&amp;] (
</span><span class="cx">                 Arg::Width width, const Arg&amp; relCond,
</span><del>-                const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+                ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 switch (width) {
</span><span class="cx">                 case Arg::Width8:
</span><span class="cx">                     // FIXME: Support these things.
</span><span class="lines">@@ -1695,7 +1775,7 @@
</span><span class="cx">             },
</span><span class="cx">             [&amp;] (
</span><span class="cx">                 Arg::Width width, const Arg&amp; resCond,
</span><del>-                const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+                ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 switch (width) {
</span><span class="cx">                 case Arg::Width8:
</span><span class="cx">                     // FIXME: Support more things.
</span><span class="lines">@@ -1710,10 +1790,10 @@
</span><span class="cx">                 }
</span><span class="cx">                 ASSERT_NOT_REACHED();
</span><span class="cx">             },
</span><del>-            [&amp;] (Arg doubleCond, const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+            [&amp;] (Arg doubleCond, ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 return createSelectInstruction(config.moveConditionallyDouble, doubleCond, left, right);
</span><span class="cx">             },
</span><del>-            [&amp;] (Arg doubleCond, const ArgPromise&amp; left, const ArgPromise&amp; right) -&gt; Inst {
</del><ins>+            [&amp;] (Arg doubleCond, ArgPromise&amp; left, ArgPromise&amp; right) -&gt; Inst {
</ins><span class="cx">                 return createSelectInstruction(config.moveConditionallyFloat, doubleCond, left, right);
</span><span class="cx">             },
</span><span class="cx">             false);
</span><span class="lines">@@ -1729,29 +1809,27 @@
</span><span class="cx">         }
</span><span class="cx">             
</span><span class="cx">         case Load: {
</span><del>-            append(
-                moveForType(m_value-&gt;type()),
-                addr(m_value), tmp(m_value));
</del><ins>+            append(trappingInst(m_value, moveForType(m_value-&gt;type()), m_value, addr(m_value), tmp(m_value)));
</ins><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx">             
</span><span class="cx">         case Load8S: {
</span><del>-            append(Load8SignedExtendTo32, addr(m_value), tmp(m_value));
</del><ins>+            append(trappingInst(m_value, Load8SignedExtendTo32, m_value, addr(m_value), tmp(m_value)));
</ins><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         case Load8Z: {
</span><del>-            append(Load8, addr(m_value), tmp(m_value));
</del><ins>+            append(trappingInst(m_value, Load8, m_value, addr(m_value), tmp(m_value)));
</ins><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         case Load16S: {
</span><del>-            append(Load16SignedExtendTo32, addr(m_value), tmp(m_value));
</del><ins>+            append(trappingInst(m_value, Load16SignedExtendTo32, m_value, addr(m_value), tmp(m_value)));
</ins><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="cx">         case Load16Z: {
</span><del>-            append(Load16, addr(m_value), tmp(m_value));
</del><ins>+            append(trappingInst(m_value, Load16, m_value, addr(m_value), tmp(m_value)));
</ins><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -2022,7 +2100,7 @@
</span><span class="cx">                     return;
</span><span class="cx">                 }
</span><span class="cx">             }
</span><del>-            m_insts.last().append(createStore(Air::Store8, valueToStore, addr(m_value)));
</del><ins>+            appendStore(Air::Store8, valueToStore, addr(m_value));
</ins><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx"> 
</span><span class="lines">@@ -2043,7 +2121,7 @@
</span><span class="cx">                     return;
</span><span class="cx">                 }
</span><span class="cx">             }
</span><del>-            m_insts.last().append(createStore(Air::Store16, valueToStore, addr(m_value)));
</del><ins>+            appendStore(Air::Store16, valueToStore, addr(m_value));
</ins><span class="cx">             return;
</span><span class="cx">         }
</span><span class="cx">             
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreb3B3Validatecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/b3/B3Validate.cpp (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/b3/B3Validate.cpp        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/b3/B3Validate.cpp        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -191,6 +191,7 @@
</span><span class="cx">             case Mod:
</span><span class="cx">             case BitAnd:
</span><span class="cx">             case BitXor:
</span><ins>+                VALIDATE(!value-&gt;kind().traps(), (&quot;At &quot;, *value));
</ins><span class="cx">                 switch (value-&gt;opcode()) {
</span><span class="cx">                 case Div:
</span><span class="cx">                 case Mod:
</span><span class="lines">@@ -339,7 +340,7 @@
</span><span class="cx">             case Load8S:
</span><span class="cx">             case Load16Z:
</span><span class="cx">             case Load16S:
</span><del>-                VALIDATE(!value-&gt;kind().hasExtraBits(), (&quot;At &quot;, *value));
</del><ins>+                VALIDATE(!value-&gt;kind().isChill(), (&quot;At &quot;, *value));
</ins><span class="cx">                 VALIDATE(value-&gt;numChildren() == 1, (&quot;At &quot;, *value));
</span><span class="cx">                 VALIDATE(value-&gt;child(0)-&gt;type() == pointerType(), (&quot;At &quot;, *value));
</span><span class="cx">                 VALIDATE(value-&gt;type() == Int32, (&quot;At &quot;, *value));
</span><span class="lines">@@ -346,7 +347,7 @@
</span><span class="cx">                 validateStackAccess(value);
</span><span class="cx">                 break;
</span><span class="cx">             case Load:
</span><del>-                VALIDATE(!value-&gt;kind().hasExtraBits(), (&quot;At &quot;, *value));
</del><ins>+                VALIDATE(!value-&gt;kind().isChill(), (&quot;At &quot;, *value));
</ins><span class="cx">                 VALIDATE(value-&gt;numChildren() == 1, (&quot;At &quot;, *value));
</span><span class="cx">                 VALIDATE(value-&gt;child(0)-&gt;type() == pointerType(), (&quot;At &quot;, *value));
</span><span class="cx">                 VALIDATE(value-&gt;type() != Void, (&quot;At &quot;, *value));
</span><span class="lines">@@ -354,7 +355,7 @@
</span><span class="cx">                 break;
</span><span class="cx">             case Store8:
</span><span class="cx">             case Store16:
</span><del>-                VALIDATE(!value-&gt;kind().hasExtraBits(), (&quot;At &quot;, *value));
</del><ins>+                VALIDATE(!value-&gt;kind().isChill(), (&quot;At &quot;, *value));
</ins><span class="cx">                 VALIDATE(value-&gt;numChildren() == 2, (&quot;At &quot;, *value));
</span><span class="cx">                 VALIDATE(value-&gt;child(0)-&gt;type() == Int32, (&quot;At &quot;, *value));
</span><span class="cx">                 VALIDATE(value-&gt;child(1)-&gt;type() == pointerType(), (&quot;At &quot;, *value));
</span><span class="lines">@@ -362,7 +363,7 @@
</span><span class="cx">                 validateStackAccess(value);
</span><span class="cx">                 break;
</span><span class="cx">             case Store:
</span><del>-                VALIDATE(!value-&gt;kind().hasExtraBits(), (&quot;At &quot;, *value));
</del><ins>+                VALIDATE(!value-&gt;kind().isChill(), (&quot;At &quot;, *value));
</ins><span class="cx">                 VALIDATE(value-&gt;numChildren() == 2, (&quot;At &quot;, *value));
</span><span class="cx">                 VALIDATE(value-&gt;child(1)-&gt;type() == pointerType(), (&quot;At &quot;, *value));
</span><span class="cx">                 VALIDATE(value-&gt;type() == Void, (&quot;At &quot;, *value));
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreb3B3Valuecpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/b3/B3Value.cpp (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/b3/B3Value.cpp        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/b3/B3Value.cpp        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -635,6 +635,7 @@
</span><span class="cx">         result.terminal = true;
</span><span class="cx">         break;
</span><span class="cx">     }
</span><ins>+    result.exitsSideways |= traps();
</ins><span class="cx">     return result;
</span><span class="cx"> }
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreb3B3Valueh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/b3/B3Value.h (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/b3/B3Value.h        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/b3/B3Value.h        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -69,6 +69,7 @@
</span><span class="cx">     // It's good practice to mirror Kind methods here, so you can say value-&gt;isBlah()
</span><span class="cx">     // instead of value-&gt;kind().isBlah().
</span><span class="cx">     bool isChill() const { return kind().isChill(); }
</span><ins>+    bool traps() const { return kind().traps(); }
</ins><span class="cx"> 
</span><span class="cx">     Origin origin() const { return m_origin; }
</span><span class="cx">     void setOrigin(Origin origin) { m_origin = origin; }
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreb3airAirCodeh"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/b3/air/AirCode.h (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/b3/air/AirCode.h        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/b3/air/AirCode.h        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -162,7 +162,7 @@
</span><span class="cx">     Vector&lt;std::unique_ptr&lt;BasicBlock&gt;&gt;&amp; blockList() { return m_blocks; }
</span><span class="cx"> 
</span><span class="cx">     // Finds the smallest index' such that at(index') != null and index' &gt;= index.
</span><del>-    unsigned findFirstBlockIndex(unsigned index) const;
</del><ins>+    JS_EXPORT_PRIVATE unsigned findFirstBlockIndex(unsigned index) const;
</ins><span class="cx"> 
</span><span class="cx">     // Finds the smallest index' such that at(index') != null and index' &gt; index.
</span><span class="cx">     unsigned findNextBlockIndex(unsigned index) const;
</span></span></pre></div>
<a id="trunkSourceJavaScriptCoreb3testb3cpp"></a>
<div class="modfile"><h4>Modified: trunk/Source/JavaScriptCore/b3/testb3.cpp (206693 => 206694)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/JavaScriptCore/b3/testb3.cpp        2016-09-30 23:26:48 UTC (rev 206693)
+++ trunk/Source/JavaScriptCore/b3/testb3.cpp        2016-10-01 00:08:28 UTC (rev 206694)
</span><span class="lines">@@ -13108,6 +13108,119 @@
</span><span class="cx">     checkDoesNotUseInstruction(*code, &quot;dmb    ishst&quot;);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void testTrappingLoad()
+{
+    Procedure proc;
+    BasicBlock* root = proc.addBlock();
+    int x = 42;
+    root-&gt;appendNew&lt;Value&gt;(
+        proc, Return, Origin(),
+        root-&gt;appendNew&lt;MemoryValue&gt;(
+            proc, trapping(Load), Int32, Origin(),
+            root-&gt;appendNew&lt;ConstPtrValue&gt;(proc, Origin(), &amp;x)));
+    CHECK_EQ(compileAndRun&lt;int&gt;(proc), 42);
+    unsigned trapsCount = 0;
+    for (Air::BasicBlock* block : proc.code()) {
+        for (Air::Inst&amp; inst : *block) {
+            if (inst.kind.traps)
+                trapsCount++;
+        }
+    }
+    CHECK_EQ(trapsCount, 1u);
+}
+
+void testTrappingStore()
+{
+    Procedure proc;
+    BasicBlock* root = proc.addBlock();
+    int x = 42;
+    root-&gt;appendNew&lt;MemoryValue&gt;(
+        proc, trapping(Store), Origin(),
+        root-&gt;appendNew&lt;Const32Value&gt;(proc, Origin(), 111),
+        root-&gt;appendNew&lt;ConstPtrValue&gt;(proc, Origin(), &amp;x));
+    root-&gt;appendNew&lt;Value&gt;(proc, Return, Origin());
+    compileAndRun&lt;int&gt;(proc);
+    CHECK_EQ(x, 111);
+    unsigned trapsCount = 0;
+    for (Air::BasicBlock* block : proc.code()) {
+        for (Air::Inst&amp; inst : *block) {
+            if (inst.kind.traps)
+                trapsCount++;
+        }
+    }
+    CHECK_EQ(trapsCount, 1u);
+}
+
+void testTrappingLoadAddStore()
+{
+    Procedure proc;
+    BasicBlock* root = proc.addBlock();
+    int x = 42;
+    ConstPtrValue* ptr = root-&gt;appendNew&lt;ConstPtrValue&gt;(proc, Origin(), &amp;x);
+    root-&gt;appendNew&lt;MemoryValue&gt;(
+        proc, trapping(Store), Origin(),
+        root-&gt;appendNew&lt;Value&gt;(
+            proc, Add, Origin(),
+            root-&gt;appendNew&lt;MemoryValue&gt;(proc, trapping(Load), Int32, Origin(), ptr),
+            root-&gt;appendNew&lt;Const32Value&gt;(proc, Origin(), 3)),
+        ptr);
+    root-&gt;appendNew&lt;Value&gt;(proc, Return, Origin());
+    compileAndRun&lt;int&gt;(proc);
+    CHECK_EQ(x, 45);
+    bool traps = false;
+    for (Air::BasicBlock* block : proc.code()) {
+        for (Air::Inst&amp; inst : *block) {
+            if (inst.kind.traps)
+                traps = true;
+        }
+    }
+    CHECK(traps);
+}
+
+void testTrappingLoadDCE()
+{
+    Procedure proc;
+    BasicBlock* root = proc.addBlock();
+    int x = 42;
+    root-&gt;appendNew&lt;MemoryValue&gt;(
+        proc, trapping(Load), Int32, Origin(),
+        root-&gt;appendNew&lt;ConstPtrValue&gt;(proc, Origin(), &amp;x));
+    root-&gt;appendNew&lt;Value&gt;(proc, Return, Origin());
+    compileAndRun&lt;int&gt;(proc);
+    unsigned trapsCount = 0;
+    for (Air::BasicBlock* block : proc.code()) {
+        for (Air::Inst&amp; inst : *block) {
+            if (inst.kind.traps)
+                trapsCount++;
+        }
+    }
+    CHECK_EQ(trapsCount, 1u);
+}
+
+void testTrappingStoreElimination()
+{
+    Procedure proc;
+    BasicBlock* root = proc.addBlock();
+    int x = 42;
+    Value* ptr = root-&gt;appendNew&lt;ConstPtrValue&gt;(proc, Origin(), &amp;x);
+    root-&gt;appendNew&lt;MemoryValue&gt;(
+        proc, trapping(Store), Origin(),
+        root-&gt;appendNew&lt;Const32Value&gt;(proc, Origin(), 43),
+        ptr);
+    root-&gt;appendNew&lt;MemoryValue&gt;(
+        proc, trapping(Store), Origin(),
+        root-&gt;appendNew&lt;Const32Value&gt;(proc, Origin(), 44),
+        ptr);
+    root-&gt;appendNew&lt;Value&gt;(proc, Return, Origin());
+    compileAndRun&lt;int&gt;(proc);
+    unsigned storeCount = 0;
+    for (Value* value : proc.values()) {
+        if (MemoryValue::isStore(value-&gt;opcode()))
+            storeCount++;
+    }
+    CHECK_EQ(storeCount, 2u);
+}
+
</ins><span class="cx"> void testMoveConstants()
</span><span class="cx"> {
</span><span class="cx">     auto check = [] (Procedure&amp; proc) {
</span><span class="lines">@@ -14609,6 +14722,11 @@
</span><span class="cx">     RUN(testMemoryFence());
</span><span class="cx">     RUN(testStoreFence());
</span><span class="cx">     RUN(testLoadFence());
</span><ins>+    RUN(testTrappingLoad());
+    RUN(testTrappingStore());
+    RUN(testTrappingLoadAddStore());
+    RUN(testTrappingLoadDCE());
+    RUN(testTrappingStoreElimination());
</ins><span class="cx">     RUN(testMoveConstants());
</span><span class="cx">     
</span><span class="cx">     if (tasks.isEmpty())
</span></span></pre>
</div>
</div>

</body>
</html>