Bug 1329676 - Wasm: eliminate redundant bounds checks on indirect calls. r=luke
authorLars T Hansen <lhansen@mozilla.com>
Mon, 27 Feb 2017 09:28:53 +0100
changeset 374631 757e96ecffc2d17406d7b66afe5368a801a412ec
parent 374630 7453899cfe444fbc04fb034b3365abaf5eda6af5
child 374632 f44aa69221576643bad692023619a49dd6ea2859
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1329676
milestone54.0a1
Bug 1329676 - Wasm: eliminate redundant bounds checks on indirect calls. r=luke
js/src/jit/Lowering.cpp
js/src/jit/MacroAssembler.cpp
js/src/jit/MacroAssembler.h
js/src/jit/shared/CodeGenerator-shared.cpp
js/src/jit/shared/LIR-shared.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmTypes.h
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -4406,24 +4406,33 @@ LIRGenerator::visitWasmCall(MWasmCall* i
     if (!args) {
         abort(AbortReason::Alloc, "Couldn't allocate for MWasmCall");
         return;
     }
 
     for (unsigned i = 0; i < ins->numArgs(); i++)
         args[i] = useFixedAtStart(ins->getOperand(i), ins->registerForArg(i));
 
-    if (ins->callee().isTable())
-        args[ins->numArgs()] = useFixedAtStart(ins->getOperand(ins->numArgs()), WasmTableCallIndexReg);
+    bool needsBoundsCheck = true;
+    if (ins->callee().isTable()) {
+        MDefinition* index = ins->getOperand(ins->numArgs());
+
+        if (ins->callee().which() == wasm::CalleeDesc::WasmTable && index->isConstant()) {
+            if (uint32_t(index->toConstant()->toInt32()) < ins->callee().wasmTableMinLength())
+                needsBoundsCheck = false;
+        }
+
+        args[ins->numArgs()] = useFixedAtStart(index, WasmTableCallIndexReg);
+    }
 
     LInstruction* lir;
     if (ins->type() == MIRType::Int64)
-        lir = new(alloc()) LWasmCallI64(args, ins->numOperands());
+        lir = new(alloc()) LWasmCallI64(args, ins->numOperands(), needsBoundsCheck);
     else
-        lir = new(alloc()) LWasmCall(args, ins->numOperands());
+        lir = new(alloc()) LWasmCall(args, ins->numOperands(), needsBoundsCheck);
 
     if (ins->type() == MIRType::None)
         add(lir, ins);
     else
         defineReturn(lir, ins);
 }
 
 void
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -2814,17 +2814,18 @@ MacroAssembler::wasmCallBuiltinInstanceM
     } else {
         MOZ_CRASH("Unknown abi passing style for pointer");
     }
 
     call(builtin);
 }
 
 void
-MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee)
+MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee,
+                                 bool needsBoundsCheck)
 {
     Register scratch = WasmTableCallScratchReg;
     Register index = WasmTableCallIndexReg;
 
     if (callee.which() == wasm::CalleeDesc::AsmJSTable) {
         // asm.js tables require no signature check, have had their index masked
         // into range and thus need no bounds check and cannot be external.
         loadWasmGlobalPtr(callee.tableBaseGlobalDataOffset(), scratch);
@@ -2843,22 +2844,25 @@ MacroAssembler::wasmCallIndirect(const w
         break;
       case wasm::SigIdDesc::Kind::Immediate:
         move32(Imm32(sigId.immediate()), WasmTableCallSigReg);
         break;
       case wasm::SigIdDesc::Kind::None:
         break;
     }
 
+    wasm::TrapOffset trapOffset(desc.lineOrBytecode());
+
     // WebAssembly throws if the index is out-of-bounds.
-    loadWasmGlobalPtr(callee.tableLengthGlobalDataOffset(), scratch);
-
-    wasm::TrapOffset trapOffset(desc.lineOrBytecode());
-    wasm::TrapDesc oobTrap(trapOffset, wasm::Trap::OutOfBounds, framePushed());
-    branch32(Assembler::Condition::AboveOrEqual, index, scratch, oobTrap);
+    if (needsBoundsCheck) {
+        loadWasmGlobalPtr(callee.tableLengthGlobalDataOffset(), scratch);
+
+        wasm::TrapDesc oobTrap(trapOffset, wasm::Trap::OutOfBounds, framePushed());
+        branch32(Assembler::Condition::AboveOrEqual, index, scratch, oobTrap);
+    }
 
     // Load the base pointer of the table.
     loadWasmGlobalPtr(callee.tableBaseGlobalDataOffset(), scratch);
 
     // Load the callee from the table.
     wasm::TrapDesc nullTrap(trapOffset, wasm::Trap::IndirectCallToNull, framePushed());
     if (callee.wasmTableIsExternal()) {
         static_assert(sizeof(wasm::ExternalTableElem) == 8 || sizeof(wasm::ExternalTableElem) == 16,
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1426,17 +1426,17 @@ class MacroAssembler : public MacroAssem
     void outOfLineWasmTruncateDoubleToInt64(FloatRegister input, bool isUnsigned, wasm::TrapOffset off, Label* rejoin) DEFINED_ON(x86_shared);
     void outOfLineWasmTruncateFloat32ToInt64(FloatRegister input, bool isUnsigned, wasm::TrapOffset off, Label* rejoin) DEFINED_ON(x86_shared);
 
     // This function takes care of loading the callee's TLS and pinned regs but
     // it is the caller's responsibility to save/restore TLS or pinned regs.
     void wasmCallImport(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee);
 
     // WasmTableCallIndexReg must contain the index of the indirect call.
-    void wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee);
+    void wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee, bool needsBoundsCheck);
 
     // This function takes care of loading the pointer to the current instance
     // as the implicit first argument. It preserves TLS and pinned registers.
     // (TLS & pinned regs are non-volatile registers in the system ABI).
     void wasmCallBuiltinInstanceMethod(const ABIArg& instanceArg,
                                        wasm::SymbolicAddress builtin);
 
     // Emit the out-of-line trap code to which trapping jumps/branches are
--- a/js/src/jit/shared/CodeGenerator-shared.cpp
+++ b/js/src/jit/shared/CodeGenerator-shared.cpp
@@ -1510,17 +1510,17 @@ CodeGeneratorShared::emitWasmCallBase(LW
       case wasm::CalleeDesc::Func:
         masm.call(desc, callee.funcIndex());
         break;
       case wasm::CalleeDesc::Import:
         masm.wasmCallImport(desc, callee);
         break;
       case wasm::CalleeDesc::WasmTable:
       case wasm::CalleeDesc::AsmJSTable:
-        masm.wasmCallIndirect(desc, callee);
+        masm.wasmCallIndirect(desc, callee, ins->needsBoundsCheck());
         break;
       case wasm::CalleeDesc::Builtin:
         masm.call(callee.builtin());
         break;
       case wasm::CalleeDesc::BuiltinInstanceMethod:
         masm.wasmCallBuiltinInstanceMethod(mir->instanceArg(), callee.builtin());
         break;
     }
--- a/js/src/jit/shared/LIR-shared.h
+++ b/js/src/jit/shared/LIR-shared.h
@@ -8554,22 +8554,24 @@ class LWasmStackArgI64 : public LInstruc
         return getInt64Operand(0);
     }
 };
 
 class LWasmCallBase : public LInstruction
 {
     LAllocation* operands_;
     uint32_t numOperands_;
-
-  public:
-
-    LWasmCallBase(LAllocation* operands, uint32_t numOperands)
+    uint32_t needsBoundsCheck_;
+
+  public:
+
+    LWasmCallBase(LAllocation* operands, uint32_t numOperands, bool needsBoundsCheck)
       : operands_(operands),
-        numOperands_(numOperands)
+        numOperands_(numOperands),
+        needsBoundsCheck_(needsBoundsCheck)
     {}
 
     MWasmCall* mir() const {
         return mir_->toWasmCall();
     }
 
     bool isCall() const override {
         return true;
@@ -8607,27 +8609,30 @@ class LWasmCallBase : public LInstructio
         return 0;
     }
     MBasicBlock* getSuccessor(size_t i) const override {
         MOZ_CRASH("no successors");
     }
     void setSuccessor(size_t i, MBasicBlock*) override {
         MOZ_CRASH("no successors");
     }
+    bool needsBoundsCheck() const {
+        return needsBoundsCheck_;
+    }
 };
 
 class LWasmCall : public LWasmCallBase
 {
      LDefinition def_;
 
   public:
     LIR_HEADER(WasmCall);
 
-    LWasmCall(LAllocation* operands, uint32_t numOperands)
-      : LWasmCallBase(operands, numOperands),
+    LWasmCall(LAllocation* operands, uint32_t numOperands, bool needsBoundsCheck)
+      : LWasmCallBase(operands, numOperands, needsBoundsCheck),
         def_(LDefinition::BogusTemp())
     {}
 
     // LInstruction interface
     size_t numDefs() const {
         return def_.isBogusTemp() ? 0 : 1;
     }
     LDefinition* getDef(size_t index) {
@@ -8643,18 +8648,18 @@ class LWasmCall : public LWasmCallBase
 
 class LWasmCallI64 : public LWasmCallBase
 {
     LDefinition defs_[INT64_PIECES];
 
   public:
     LIR_HEADER(WasmCallI64);
 
-    LWasmCallI64(LAllocation* operands, uint32_t numOperands)
-      : LWasmCallBase(operands, numOperands)
+    LWasmCallI64(LAllocation* operands, uint32_t numOperands, bool needsBoundsCheck)
+      : LWasmCallBase(operands, numOperands, needsBoundsCheck)
     {
         for (size_t i = 0; i < numDefs(); i++)
             defs_[i] = LDefinition::BogusTemp();
     }
 
     // LInstruction interface
     size_t numDefs() const {
         return INT64_PIECES;
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -116,16 +116,17 @@ namespace wasm {
 using namespace js::jit;
 using JS::GenericNaN;
 
 typedef bool HandleNaNSpecially;
 typedef bool InvertBranch;
 typedef bool IsKnownNotZero;
 typedef bool IsSigned;
 typedef bool IsUnsigned;
+typedef bool NeedsBoundsCheck;
 typedef bool PopStack;
 typedef bool ZeroOnOverflow;
 
 typedef unsigned ByteSize;
 typedef unsigned BitSize;
 
 // UseABI::Wasm implies that the Tls/Heap/Global registers are nonvolatile,
 // except when InterModule::True is also set, when they are volatile.
@@ -2541,17 +2542,17 @@ class BaseCompiler
 
         MOZ_ASSERT(env_.tables.length() == 1);
         const TableDesc& table = env_.tables[0];
 
         loadI32(WasmTableCallIndexReg, indexVal);
 
         CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::Dynamic);
         CalleeDesc callee = CalleeDesc::wasmTable(table, sig.id);
-        masm.wasmCallIndirect(desc, callee);
+        masm.wasmCallIndirect(desc, callee, NeedsBoundsCheck(true));
     }
 
     // Precondition: sync()
 
     void callImport(unsigned globalDataOffset, const FunctionCall& call)
     {
         CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::Dynamic);
         CalleeDesc callee = CalleeDesc::import(globalDataOffset);
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -1258,16 +1258,17 @@ class CalleeDesc
     union U {
         U() {}
         uint32_t funcIndex_;
         struct {
             uint32_t globalDataOffset_;
         } import;
         struct {
             uint32_t globalDataOffset_;
+            uint32_t minLength_;
             bool external_;
             SigIdDesc sigId_;
         } table;
         SymbolicAddress builtin_;
     } u;
 
   public:
     CalleeDesc() {}
@@ -1282,16 +1283,17 @@ class CalleeDesc
         c.which_ = Import;
         c.u.import.globalDataOffset_ = globalDataOffset;
         return c;
     }
     static CalleeDesc wasmTable(const TableDesc& desc, SigIdDesc sigId) {
         CalleeDesc c;
         c.which_ = WasmTable;
         c.u.table.globalDataOffset_ = desc.globalDataOffset;
+        c.u.table.minLength_ = desc.limits.initial;
         c.u.table.external_ = desc.external;
         c.u.table.sigId_ = sigId;
         return c;
     }
     static CalleeDesc asmJSTable(const TableDesc& desc) {
         CalleeDesc c;
         c.which_ = AsmJSTable;
         c.u.table.globalDataOffset_ = desc.globalDataOffset;
@@ -1334,16 +1336,20 @@ class CalleeDesc
     bool wasmTableIsExternal() const {
         MOZ_ASSERT(which_ == WasmTable);
         return u.table.external_;
     }
     SigIdDesc wasmTableSigId() const {
         MOZ_ASSERT(which_ == WasmTable);
         return u.table.sigId_;
     }
+    uint32_t wasmTableMinLength() const {
+        MOZ_ASSERT(which_ == WasmTable);
+        return u.table.minLength_;
+    }
     SymbolicAddress builtin() const {
         MOZ_ASSERT(which_ == Builtin || which_ == BuiltinInstanceMethod);
         return u.builtin_;
     }
 };
 
 // Because ARM has a fixed-width instruction encoding, ARM can only express a
 // limited subset of immediates (in a single instruction).