Bug 1254142: Baldr: make control flow opcodes yield subexpressions; r=luke
authorBenjamin Bouvier <benj@benj.me>
Thu, 31 Mar 2016 12:30:23 +0200
changeset 291245 c79a34d5b3a3d8edce927f6a8a0d57cc7565a9dd
parent 291244 db49ab7e9e4f1460553637b79eb91538a497a062
child 291246 bf8dc00cc76fad3ef265e391d84b41d6b3e86abc
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1254142
milestone48.0a1
Bug 1254142: Baldr: make control flow opcodes yield subexpressions; r=luke MozReview-Commit-ID: 7mf8RgGZpSL
js/src/asmjs/Wasm.cpp
js/src/asmjs/WasmIonCompile.cpp
js/src/asmjs/WasmTextToBinary.cpp
js/src/jit-test/tests/wasm/basic-control-flow.js
js/src/jit-test/tests/wasm/random-control-flow.js
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -554,25 +554,22 @@ static bool
 DecodeBranch(FunctionDecoder& f, Expr expr, ExprType* type)
 {
     MOZ_ASSERT(expr == Expr::Br || expr == Expr::BrIf);
 
     uint32_t relativeDepth;
     if (!f.d().readVarU32(&relativeDepth))
         return f.fail("expected relative depth");
 
-    if (!f.branchWithType(relativeDepth, ExprType::Void))
-        return f.fail("branch depth exceeds current nesting level");
-
-    Expr value;
-    if (!f.d().readExpr(&value))
+    ExprType brType;
+    if (!DecodeExpr(f, &brType))
         return f.fail("expected branch value");
 
-    if (value != Expr::Nop)
-        return f.fail("NYI: branch values");
+    if (!f.branchWithType(relativeDepth, brType))
+        return f.fail("branch depth exceeds current nesting level");
 
     if (expr == Expr::BrIf) {
         ExprType actual;
         if (!DecodeExpr(f, &actual))
             return false;
 
         if (!CheckType(f, actual, ValType::I32))
             return false;
--- a/js/src/asmjs/WasmIonCompile.cpp
+++ b/js/src/asmjs/WasmIonCompile.cpp
@@ -12,16 +12,17 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "asmjs/WasmIonCompile.h"
+
 #include "asmjs/WasmGenerator.h"
 
 #include "jit/CodeGenerator.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
@@ -933,56 +934,45 @@ class FunctionCompiler
   private:
     static bool hasPushed(MBasicBlock* block)
     {
         uint32_t numPushed = block->stackDepth() - block->info().firstStackSlot();
         MOZ_ASSERT(numPushed == 0 || numPushed == 1);
         return numPushed;
     }
 
-    static void push(MBasicBlock* block, MDefinition* def)
-    {
-        MOZ_ASSERT(!hasPushed(block));
-        block->push(def);
-    }
-
-    static void popAll(BlockVector* blocks)
-    {
-        for (MBasicBlock* block : *blocks)
-            block->pop();
-    }
-
   public:
-    bool addJoinPredecessor(MDefinition* def, BlockVector* blocks)
+    void pushDef(MDefinition* def)
     {
         if (inDeadCode())
-            return true;
-
-        // Preserve the invariant that, for every MBasicBlock in 'blocks',
+            return;
+        MOZ_ASSERT(!hasPushed(curBlock_));
+        if (def && def->type() != MIRType_None)
+            curBlock_->push(def);
+    }
+
+    template <typename GetBlock>
+    void ensurePushInvariants(const GetBlock& getBlock, size_t numBlocks)
+    {
+        // Preserve the invariant that, for every iterated MBasicBlock,
         // either: every MBasicBlock has a non-void pushed expression OR no
         // MBasicBlock has any pushed expression. This is required by
         // MBasicBlock::addPredecessor.
-        if (def) {
-            if (blocks->empty()) {
-                if (def->type() != MIRType_None)
-                    push(curBlock_, def);
-            } else {
-                if (hasPushed((*blocks)[0])) {
-                    if (def->type() == MIRType_None)
-                        popAll(blocks);
-                    else
-                        push(curBlock_, def);
-                }
+        bool allPushed = true;
+
+        for (size_t i = 0; allPushed && i < numBlocks; i++)
+            allPushed = hasPushed(getBlock(i));
+
+        if (!allPushed) {
+            for (size_t i = 0; i < numBlocks; i++) {
+                MBasicBlock* block = getBlock(i);
+                if (hasPushed(block))
+                    block->pop();
             }
-        } else {
-            if (!blocks->empty() && hasPushed((*blocks)[0]))
-                popAll(blocks);
         }
-
-        return blocks->append(curBlock_);
     }
 
     bool joinIf(MBasicBlock* joinBlock, BlockVector* blocks, MDefinition** def)
     {
         MOZ_ASSERT_IF(curBlock_, blocks->back() == curBlock_);
         curBlock_ = joinBlock;
         return joinIfElse(nullptr, blocks, def);
     }
@@ -990,21 +980,32 @@ class FunctionCompiler
     void switchToElse(MBasicBlock* elseBlock)
     {
         if (!elseBlock)
             return;
         curBlock_ = elseBlock;
         mirGraph().moveBlockToEnd(curBlock_);
     }
 
+    bool addJoinPredecessor(MDefinition* def, BlockVector* blocks)
+    {
+        if (inDeadCode())
+            return true;
+        pushDef(def);
+        return blocks->append(curBlock_);
+    }
+
     bool joinIfElse(MDefinition* elseDef, BlockVector* blocks, MDefinition** def)
     {
         if (!addJoinPredecessor(elseDef, blocks))
             return false;
 
+        auto getBlock = [&](size_t i) -> MBasicBlock* { return (*blocks)[i]; };
+        ensurePushInvariants(getBlock, blocks->length());
+
         if (blocks->empty()) {
             *def = nullptr;
             return true;
         }
 
         MBasicBlock* join;
         if (!goToNewBlock((*blocks)[0], &join))
             return false;
@@ -1023,21 +1024,21 @@ class FunctionCompiler
 
     bool startBlock()
     {
         MOZ_ASSERT_IF(blockDepth_ < blockPatches_.length(), blockPatches_[blockDepth_].empty());
         blockDepth_++;
         return true;
     }
 
-    bool finishBlock()
+    bool finishBlock(MDefinition** def)
     {
         MOZ_ASSERT(blockDepth_);
         uint32_t topLabel = --blockDepth_;
-        return bindBranches(topLabel);
+        return bindBranches(topLabel, def);
     }
 
     bool startLoop(MBasicBlock** loopHeader)
     {
         *loopHeader = nullptr;
 
         blockDepth_ += 2;
         loopDepth_++;
@@ -1068,33 +1069,28 @@ class FunctionCompiler
     {
         for (size_t i = 0, depth = b->stackDepth(); i < depth; i++) {
             MDefinition* def = b->getSlot(i);
             if (def->isUnused())
                 b->setSlot(i, def->toPhi()->getOperand(0));
         }
     }
 
-    bool setLoopBackedge(MBasicBlock* loopEntry, MBasicBlock* loopBody, MBasicBlock* backedge,
-                         MDefinition** loopResult)
+    bool setLoopBackedge(MBasicBlock* loopEntry, MBasicBlock* loopBody, MBasicBlock* backedge)
     {
         if (!loopEntry->setBackedgeAsmJS(backedge))
             return false;
 
         // Flag all redundant phis as unused.
         for (MPhiIterator phi = loopEntry->phisBegin(); phi != loopEntry->phisEnd(); phi++) {
             MOZ_ASSERT(phi->numOperands() == 2);
             if (phi->getOperand(0) == phi->getOperand(1))
                 phi->setUnused();
         }
 
-        // The loop result may also be referencing a recycled phi.
-        if (*loopResult && (*loopResult)->isUnused())
-            *loopResult = (*loopResult)->toPhi()->getOperand(0);
-
         // Fix up phis stored in the slots Vector of pending blocks.
         for (ControlFlowPatchVector& patches : blockPatches_) {
             for (ControlFlowPatch& p : patches) {
                 MBasicBlock* block = p.ins->block();
                 if (block->loopDepth() >= loopEntry->loopDepth())
                     fixupRedundantPhis(block);
             }
         }
@@ -1138,33 +1134,37 @@ class FunctionCompiler
         // Expr::Loop doesn't have an implicit backedge so temporarily set
         // aside the end of the loop body to bind backedges.
         MBasicBlock* loopBody = curBlock_;
         curBlock_ = nullptr;
 
         // TODO (bug 1253544): blocks branching to the top join to a single
         // backedge block. Could they directly be set as backedges of the loop
         // instead?
-        if (!bindBranches(headerLabel))
+        MDefinition* _;
+        if (!bindBranches(headerLabel, &_))
             return false;
 
         MOZ_ASSERT(loopHeader->loopDepth() == loopDepth_);
 
         if (curBlock_) {
             // We're on the loop backedge block, created by bindBranches.
+            if (hasPushed(curBlock_))
+                curBlock_->pop();
+
             MOZ_ASSERT(curBlock_->loopDepth() == loopDepth_);
             curBlock_->end(MGoto::New(alloc(), loopHeader));
-            if (!setLoopBackedge(loopHeader, loopBody, curBlock_, loopResult))
+            if (!setLoopBackedge(loopHeader, loopBody, curBlock_))
                 return false;
         }
 
         curBlock_ = loopBody;
 
         loopDepth_--;
-        if (!bindBranches(afterLabel))
+        if (!bindBranches(afterLabel, loopResult))
             return false;
 
         // If we have not created a new block in bindBranches, we're still on
         // the inner loop body, which loop depth is incorrect.
         if (curBlock_ && curBlock_->loopDepth() != loopDepth_) {
             MBasicBlock* out;
             if (!goToNewBlock(curBlock_, &out))
                 return false;
@@ -1180,43 +1180,47 @@ class FunctionCompiler
         uint32_t absolute = blockDepth_ - 1 - relative;
 
         if (absolute >= blockPatches_.length() && !blockPatches_.resize(absolute + 1))
             return false;
 
         return blockPatches_[absolute].append(ControlFlowPatch(ins, index));
     }
 
-    bool br(uint32_t relativeDepth)
+    bool br(uint32_t relativeDepth, MDefinition* maybeValue)
     {
         if (inDeadCode())
             return true;
 
         MGoto* jump = MGoto::NewAsm(alloc());
         if (!addControlFlowPatch(jump, relativeDepth, MGoto::TargetIndex))
             return false;
 
+        pushDef(maybeValue);
+
         curBlock_->end(jump);
         curBlock_ = nullptr;
         return true;
     }
 
-    bool brIf(uint32_t relativeDepth, MDefinition* condition)
+    bool brIf(uint32_t relativeDepth, MDefinition* maybeValue, MDefinition* condition)
     {
         if (inDeadCode())
             return true;
 
         MBasicBlock* joinBlock = nullptr;
         if (!newBlock(curBlock_, &joinBlock))
             return false;
 
         MTest* test = MTest::NewAsm(alloc(), condition, joinBlock);
         if (!addControlFlowPatch(test, relativeDepth, MTest::TrueBranchIndex))
             return false;
 
+        pushDef(maybeValue);
+
         curBlock_->end(test);
         curBlock_ = joinBlock;
         return true;
     }
 
     bool brTable(MDefinition* expr, uint32_t defaultDepth, const Uint32Vector& depths)
     {
         if (inDeadCode())
@@ -1315,24 +1319,31 @@ class FunctionCompiler
     bool goToExistingBlock(MBasicBlock* prev, MBasicBlock* next)
     {
         MOZ_ASSERT(prev);
         MOZ_ASSERT(next);
         prev->end(MGoto::New(alloc(), next));
         return next->addPredecessor(alloc(), prev);
     }
 
-    bool bindBranches(uint32_t absolute)
+    bool bindBranches(uint32_t absolute, MDefinition** def)
     {
-        if (absolute >= blockPatches_.length())
+        if (absolute >= blockPatches_.length() || blockPatches_[absolute].empty()) {
+            *def = !inDeadCode() && hasPushed(curBlock_) ? curBlock_->pop() : nullptr;
             return true;
+        }
 
         ControlFlowPatchVector& patches = blockPatches_[absolute];
-        if (patches.empty())
-            return true;
+
+        auto getBlock = [&](size_t i) -> MBasicBlock* {
+            if (i < patches.length())
+                return patches[i].ins->block();
+            return curBlock_;
+        };
+        ensurePushInvariants(getBlock, patches.length() + !!curBlock_);
 
         MBasicBlock* join = nullptr;
         MControlInstruction* ins = patches[0].ins;
         if (!newBlock(ins->block(), &join))
             return false;
 
         ins->replaceSuccessor(patches[0].index, join);
 
@@ -1342,16 +1353,18 @@ class FunctionCompiler
                 return false;
             ins->replaceSuccessor(patches[i].index, join);
         }
 
         if (curBlock_ && !goToExistingBlock(curBlock_, join))
             return false;
         curBlock_ = join;
 
+        *def = hasPushed(curBlock_) ? curBlock_->pop() : nullptr;
+
         patches.clear();
         return true;
     }
 };
 
 static bool
 EmitLiteral(FunctionCompiler& f, ValType type, MDefinition** def)
 {
@@ -2525,20 +2538,20 @@ EmitLoop(FunctionCompiler& f, MDefinitio
     f.addInterruptCheck();
 
     if (uint32_t numStmts = f.readVarU32()) {
         for (uint32_t i = 0; i < numStmts - 1; i++) {
             MDefinition* _;
             if (!EmitExpr(f, &_))
                 return false;
         }
-        if (!EmitExpr(f, def))
+        MDefinition* last = nullptr;
+        if (!EmitExpr(f, &last))
             return false;
-    } else {
-        *def = nullptr;
+        f.pushDef(last);
     }
 
     return f.closeLoop(loopHeader, def);
 }
 
 static bool
 EmitIfElse(FunctionCompiler& f, Expr op, MDefinition** def)
 {
@@ -2603,17 +2616,17 @@ EmitBrTable(FunctionCompiler& f, MDefini
     MDefinition* index;
     if (!EmitExpr(f, &index))
         return false;
 
     *def = nullptr;
 
     // Empty table
     if (!numCases)
-        return f.br(defaultDepth);
+        return f.br(defaultDepth, nullptr);
 
     return f.brTable(index, defaultDepth, depths);
 }
 
 static bool
 EmitReturn(FunctionCompiler& f, MDefinition** def)
 {
     ExprType ret = f.sig().ret();
@@ -2643,46 +2656,48 @@ EmitUnreachable(FunctionCompiler& f, MDe
 
 static bool
 EmitBlock(FunctionCompiler& f, MDefinition** def)
 {
     if (!f.startBlock())
         return false;
     if (uint32_t numStmts = f.readVarU32()) {
         for (uint32_t i = 0; i < numStmts - 1; i++) {
-            MDefinition* _;
+            MDefinition* _ = nullptr;
             if (!EmitExpr(f, &_))
                 return false;
         }
-        if (!EmitExpr(f, def))
+        MDefinition* last = nullptr;
+        if (!EmitExpr(f, &last))
             return false;
-    } else {
-        *def = nullptr;
+        f.pushDef(last);
     }
-    return f.finishBlock();
+    return f.finishBlock(def);
 }
 
 static bool
 EmitBranch(FunctionCompiler& f, Expr op, MDefinition** def)
 {
     MOZ_ASSERT(op == Expr::Br || op == Expr::BrIf);
 
     uint32_t relativeDepth = f.readVarU32();
 
-    MOZ_ALWAYS_TRUE(f.readExpr() == Expr::Nop);
+    MDefinition* maybeValue = nullptr;
+    if (!EmitExpr(f, &maybeValue))
+        return false;
 
     if (op == Expr::Br) {
-        if (!f.br(relativeDepth))
+        if (!f.br(relativeDepth, maybeValue))
             return false;
     } else {
         MDefinition* condition;
         if (!EmitExpr(f, &condition))
             return false;
 
-        if (!f.brIf(relativeDepth, condition))
+        if (!f.brIf(relativeDepth, maybeValue, condition))
             return false;
     }
 
     *def = nullptr;
     return true;
 }
 
 static bool
--- a/js/src/asmjs/WasmTextToBinary.cpp
+++ b/js/src/asmjs/WasmTextToBinary.cpp
@@ -320,28 +320,32 @@ class WasmAstBlock : public WasmAstExpr
     const WasmAstExprVector& exprs() const { return exprs_; }
 };
 
 class WasmAstBranch : public WasmAstExpr
 {
     Expr expr_;
     WasmAstExpr* cond_;
     WasmRef target_;
+    WasmAstExpr* value_;
 
   public:
     static const WasmAstExprKind Kind = WasmAstExprKind::Branch;
-    explicit WasmAstBranch(Expr expr, WasmAstExpr* cond, WasmRef target)
+    explicit WasmAstBranch(Expr expr, WasmAstExpr* cond, WasmRef target, WasmAstExpr* value)
       : WasmAstExpr(Kind),
         expr_(expr),
         cond_(cond),
-        target_(target)
+        target_(target),
+        value_(value)
     {}
+
     Expr expr() const { return expr_; }
     WasmRef& target() { return target_; }
     WasmAstExpr& cond() const { MOZ_ASSERT(cond_); return *cond_; }
+    WasmAstExpr* maybeValue() const { return value_; }
 };
 
 class WasmAstCall : public WasmAstExpr
 {
     Expr expr_;
     WasmRef func_;
     WasmAstExprVector args_;
 
@@ -2073,24 +2077,40 @@ static WasmAstBranch*
 ParseBranch(WasmParseContext& c, Expr expr)
 {
     MOZ_ASSERT(expr == Expr::Br || expr == Expr::BrIf);
 
     WasmRef target;
     if (!c.ts.matchRef(&target, c.error))
         return nullptr;
 
+    WasmAstExpr* value = nullptr;
+    if (c.ts.getIf(WasmToken::OpenParen)) {
+        value = ParseExprInsideParens(c);
+        if (!value)
+            return nullptr;
+        if (!c.ts.match(WasmToken::CloseParen, c.error))
+            return nullptr;
+    }
+
     WasmAstExpr* cond = nullptr;
     if (expr == Expr::BrIf) {
-        cond = ParseExpr(c);
-        if (!cond)
-            return nullptr;
+        if (c.ts.getIf(WasmToken::OpenParen)) {
+            cond = ParseExprInsideParens(c);
+            if (!cond)
+                return nullptr;
+            if (!c.ts.match(WasmToken::CloseParen, c.error))
+                return nullptr;
+        } else {
+            cond = value;
+            value = nullptr;
+        }
     }
 
-    return new(c.lifo) WasmAstBranch(expr, cond, target);
+    return new(c.lifo) WasmAstBranch(expr, cond, target, value);
 }
 
 static bool
 ParseArgs(WasmParseContext& c, WasmAstExprVector* args)
 {
     while (c.ts.getIf(WasmToken::OpenParen)) {
         WasmAstExpr* arg = ParseExprInsideParens(c);
         if (!arg || !args->append(arg))
@@ -3604,17 +3624,17 @@ EncodeBranch(Encoder& e, WasmAstBranch& 
     MOZ_ASSERT(br.expr() == Expr::Br || br.expr() == Expr::BrIf);
 
     if (!e.writeExpr(br.expr()))
         return false;
 
     if (!e.writeVarU32(br.target().index()))
         return false;
 
-    if (!e.writeExpr(Expr::Nop))
+    if (br.maybeValue() ? !EncodeExpr(e, *br.maybeValue()) : !e.writeExpr(Expr::Nop))
         return false;
 
     if (br.expr() == Expr::BrIf) {
         if (!EncodeExpr(e, br.cond()))
             return false;
     }
 
     return true;
--- a/js/src/jit-test/tests/wasm/basic-control-flow.js
+++ b/js/src/jit-test/tests/wasm/basic-control-flow.js
@@ -139,18 +139,16 @@ assertEq(wasmEvalText('(module (func (re
 assertEq(wasmEvalText('(module (func (result i32) (return (i32.const 1))) (export "" 0))')(), 1);
 assertEq(wasmEvalText('(module (func (if (return) (i32.const 0))) (export "" 0))')(), undefined);
 assertErrorMessage(() => wasmEvalText('(module (func (result f32) (return (i32.const 1))) (export "" 0))'), TypeError, mismatchError("i32", "f32"));
 assertThrowsInstanceOf(() => wasmEvalText('(module (func (result i32) (return)) (export "" 0))'), TypeError);
 
 // TODO: Reenable when syntactic arities are added for returns
 //assertThrowsInstanceOf(() => wasmEvalText('(module (func (return (i32.const 1))) (export "" 0))'), TypeError);
 
-// TODO: convert these to wasmEval and assert some results once they are implemented
-
 // ----------------------------------------------------------------------------
 // br / br_if
 
 assertErrorMessage(() => wasmEvalText('(module (func (result i32) (block (br 0))) (export "" 0))'), TypeError, mismatchError("void", "i32"));
 assertErrorMessage(() => wasmEvalText('(module (func (result i32) (block (br_if 0 (i32.const 0)))) (export "" 0))'), TypeError, mismatchError("void", "i32"));
 
 const DEPTH_OUT_OF_BOUNDS = /branch depth exceeds current nesting level/;
 
@@ -264,16 +262,126 @@ var isNonZero = wasmEvalText(`(module (f
   )
   (return (i32.const 1))
 ) (export "" 0))`);
 
 assertEq(isNonZero(0), 0);
 assertEq(isNonZero(1), 1);
 assertEq(isNonZero(-1), 1);
 
+// branches with values
+// br/br_if and block
+assertErrorMessage(() => wasmEvalText('(module (func (result i32) (block (br 0))))'), TypeError, mismatchError("void", "i32"));
+assertErrorMessage(() => wasmEvalText('(module (func (result i32) (block (br 0 (f32.const 42)))))'), TypeError, mismatchError("f32", "i32"));
+
+assertErrorMessage(() => wasmEvalText(`(module (func (result i32) (param i32) (block (if (get_local 0) (br 0 (i32.const 42))))) (export "" 0))`), TypeError, mismatchError("void", "i32"));
+assertErrorMessage(() => wasmEvalText(`(module (func (result i32) (param i32) (block (if (get_local 0) (br 0 (i32.const 42))) (br 0 (f32.const 42)))) (export "" 0))`), TypeError, mismatchError("void", "i32"));
+
+assertEq(wasmEvalText('(module (func (result i32) (block (br 0 (i32.const 42)) (i32.const 13))) (export "" 0))')(), 42);
+
+assertEq(wasmEvalText('(module (func) (func (block (br 0 (call 0)) (i32.const 13))) (export "" 0))')(), undefined);
+assertEq(wasmEvalText('(module (func) (func (block (br_if 0 (call 0) (i32.const 1)) (i32.const 13))) (export "" 0))')(), undefined);
+
+var f = wasmEvalText(`(module (func (result i32) (param i32) (block (if (get_local 0) (br 0 (i32.const 42))) (i32.const 43))) (export "" 0))`);
+assertEq(f(0), 43);
+assertEq(f(1), 42);
+
+var f = wasmEvalText(`(module (func (result i32) (param i32) (block (br_if 0 (i32.const 42) (get_local 0)) (i32.const 43))) (export "" 0))`);
+assertEq(f(0), 43);
+assertEq(f(1), 42);
+
+var f = wasmEvalText(`(module (func (result i32) (param i32) (block (if (get_local 0) (br 0 (i32.const 42))) (br 0 (i32.const 43)))) (export "" 0))`);
+assertEq(f(0), 43);
+assertEq(f(1), 42);
+
+var f = wasmEvalText(`(module (func (result i32) (param i32) (block (br_if 0 (i32.const 42) (get_local 0)) (br 0 (i32.const 43)))) (export "" 0))`);
+assertEq(f(0), 43);
+assertEq(f(1), 42);
+
+var f = wasmEvalText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block (if (get_local 0) (br 0 (i32.const 99))) (i32.const -1)))) (export "" 0))`);
+assertEq(f(0), 0);
+assertEq(f(1), 100);
+
+var f = wasmEvalText(`(module (func (param i32) (result i32) (i32.add (i32.const 1) (block (br_if 0 (i32.const 99) (get_local 0)) (i32.const -1)))) (export "" 0))`);
+assertEq(f(0), 0);
+assertEq(f(1), 100);
+
+assertEq(wasmEvalText(`(module (func (result i32) (block (br 0 (return (i32.const 42))) (i32.const 0))) (export "" 0))`)(), 42);
+assertEq(wasmEvalText(`(module (func (result i32) (block (return (br 0 (i32.const 42))))) (export "" 0))`)(), 42);
+assertEq(wasmEvalText(`(module (func (result i32) (block (return (br 0 (i32.const 42))) (i32.const 0))) (export "" 0))`)(), 42);
+
+assertEq(wasmEvalText(`(module (func (result f32) (block (br 0 (i32.const 0))) (block (br 0 (f32.const 42)))) (export "" 0))`)(), 42);
+
+var called = 0;
+var imports = {
+    sideEffects: {
+        ifTrue(x) {assertEq(x, 13); called++;},
+        ifFalse(x) {assertEq(x, 37); called--;}
+    }
+}
+var f = wasmEvalText(`(module
+ (import "sideEffects" "ifTrue" (param i32))
+ (import "sideEffects" "ifFalse" (param i32))
+ (func
+  (param i32) (result i32)
+  (block $outer
+   (if
+    (get_local 0)
+    (br $outer (call_import 0 (i32.const 13)))
+   )
+   (if
+    (i32.eqz (get_local 0))
+    (br $outer (call_import 1 (i32.const 37)))
+   )
+  )
+  (i32.const 42)
+ )
+(export "" 0))`, imports);
+assertEq(f(0), 42);
+assertEq(called, -1);
+assertEq(f(1), 42);
+assertEq(called, 0);
+
+// br/br_if and loop
+assertEq(wasmEvalText(`(module (func (param i32) (result i32) (loop $out $in (br $out (get_local 0)))) (export "" 0))`)(1), 1);
+
+assertEq(wasmEvalText(`(module (func (param i32) (result i32)
+  (loop $out $in
+   (if (get_local 0) (br $in (i32.const 1)))
+   (if (get_local 0) (br $in (f32.const 2)))
+   (if (get_local 0) (br $in (f64.const 3)))
+   (if (get_local 0) (br $in))
+   (i32.const 7)
+  )
+ ) (export "" 0))`)(0), 7);
+
+assertEq(wasmEvalText(`(module
+ (func
+  (result i32)
+  (local i32)
+  (loop $out $in
+   (set_local 0 (i32.add (get_local 0) (i32.const 1)))
+   (if (i32.ge_s (get_local 0) (i32.const 7)) (br $out (get_local 0)))
+   (br $in)
+  )
+ )
+(export "" 0))`)(), 7);
+
+assertEq(wasmEvalText(`(module
+ (func
+  (result i32)
+  (local i32)
+  (loop $out $in
+   (set_local 0 (i32.add (get_local 0) (i32.const 1)))
+   (br_if $out (get_local 0) (i32.ge_s (get_local 0) (i32.const 7)))
+   (br $in)
+  )
+ )
+(export "" 0))`)(), 7);
+
 // ----------------------------------------------------------------------------
 // loop
 
 assertErrorMessage(() => wasmEvalText('(module (func (loop (br 2))))'), TypeError, DEPTH_OUT_OF_BOUNDS);
 
 assertEq(wasmEvalText('(module (func (loop)) (export "" 0))')(), undefined);
 assertEq(wasmEvalText('(module (func (result i32) (loop (i32.const 2)) (i32.const 1)) (export "" 0))')(), 1);
 
--- a/js/src/jit-test/tests/wasm/random-control-flow.js
+++ b/js/src/jit-test/tests/wasm/random-control-flow.js
@@ -5,8 +5,26 @@ assertEq(wasmEvalText(`(module
      (loop (if (i32.const 0) (br 0)) (get_local 0)))
    (export "" 0)
 )`)(42), 42);
 
 wasmEvalText(`(module (func $func$0
       (block (if (i32.const 1) (loop (br_table 0 (br 0)))))
   )
 )`);
+
+wasmEvalText(`(module (func
+  (select
+    (block
+      (block
+        (br_table
+         1
+         0
+         (i32.const 1)
+        )
+      )
+      (i32.const 2)
+    )
+    (i32.const 3)
+    (i32.const 4)
+  )
+))
+`);