Bug 913253: assertFloat32 function + tests for IonMonkey; r=nbp
authorBenjamin Bouvier <bbouvier@mozilla.com>
Wed, 11 Sep 2013 02:12:01 -0700
changeset 146547 6203c90cab31d5042fafaa9998ed4023723228e4
parent 146546 8621bdc408416276a715164dc7c7f7f14cfaaaf4
child 146548 211b9c5a464d1d1fadbae033d42c8f5e97cf8b87
push id25266
push userryanvm@gmail.com
push dateThu, 12 Sep 2013 00:24:40 +0000
treeherdermozilla-central@162f05aec17a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnbp
bugs913253
milestone26.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 913253: assertFloat32 function + tests for IonMonkey; r=nbp
js/src/builtin/TestingFunctions.cpp
js/src/builtin/TestingFunctions.h
js/src/jit-test/tests/ion/testFloat32.js
js/src/jit/IonBuilder.h
js/src/jit/Lowering.cpp
js/src/jit/Lowering.h
js/src/jit/MCallOptimize.cpp
js/src/jit/MIR.h
js/src/jit/MOpcodes.h
js/src/jit/ParallelSafetyAnalysis.cpp
js/src/shell/js.cpp
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1002,16 +1002,24 @@ GetObjectMetadata(JSContext *cx, unsigne
 bool
 js::testingFunc_bailout(JSContext *cx, unsigned argc, jsval *vp)
 {
     // NOP when not in IonMonkey
     JS_SET_RVAL(cx, vp, JSVAL_VOID);
     return true;
 }
 
+bool
+js::testingFunc_assertFloat32(JSContext *cx, unsigned argc, jsval *vp)
+{
+    // NOP when not in IonMonkey
+    JS_SET_RVAL(cx, vp, JSVAL_VOID);
+    return true;
+}
+
 static bool
 SetJitCompilerOption(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject callee(cx, &args.callee());
 
     if (args.length() != 2) {
         ReportUsageError(cx, callee, "Wrong number of arguments.");
--- a/js/src/builtin/TestingFunctions.h
+++ b/js/src/builtin/TestingFunctions.h
@@ -15,11 +15,14 @@ bool
 DefineTestingFunctions(JSContext *cx, HandleObject obj);
 
 bool
 testingFunc_inParallelSection(JSContext *cx, unsigned argc, Value *vp);
 
 bool
 testingFunc_bailout(JSContext *cx, unsigned argc, Value *vp);
 
+bool
+testingFunc_assertFloat32(JSContext *cx, unsigned argc, Value *vp);
+
 } /* namespace js */
 
 #endif /* builtin_TestingFunctions_h */
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/testFloat32.js
@@ -0,0 +1,251 @@
+// Fuzz tests
+(function(){
+    //
+    (function(){
+        var g = {};
+        x = Float32Array()
+        Function('g', "g.o = x[1]")(g);
+    })();
+    //
+    (function() {
+        var g = new Float32Array(16);
+        var h = new Float64Array(16);
+        var farrays = [ g, h ];
+        for (aridx = 0; aridx < farrays.length; ++aridx) {
+            ar  = farrays[aridx];
+            !(ar[ar.length-2] == (NaN / Infinity)[ar.length-2])
+        }
+    })();
+    //
+    (function () {
+        var v = new Float32Array(32);
+        for (var i = 0; i < v.length; ++i)
+        v[i] = i;
+    var t = (false  );
+    for (var i = 0; i < i .length; ++i)
+        t += v[i];
+    })();
+    //
+    (function() {
+        if (ParallelArray !== undefined)
+        ParallelArray([1606], Math.fround)
+    })();
+    //
+    (function() {
+        x = y = {};
+        z = Float32Array(6)
+        for (c in this) {
+            Array.prototype.unshift.call(x, ArrayBuffer())
+        }
+        Array.prototype.sort.call(x, (function (j) {
+            y.s = z[2]
+        }))
+    })();
+    //
+})();
+//
+// ION TESTS
+//
+// The assertFloat32 function is deactivated in --ion-eager mode, as the first time, the function Math.fround
+// would be guarded against modifications (typeguard on Math and then on fround). In this case, Math.fround is
+// not inlined and the compiler will consider the return value to be a double, not a float32, making the
+// assertions fail. Note that as assertFloat32 is declared unsafe for fuzzing, this can't happen in fuzzed code.
+//
+// To be able to test it, we still need ion compilation though. A nice solution is to manually lower the ion usecount.
+setJitCompilerOption("ion.usecount.trigger", 50);
+
+function test(f) {
+    f32[0] = 1;
+    for(var n = 110; n; n--)
+        f();
+}
+
+var f32 = new Float32Array(2);
+var f64 = new Float64Array(2);
+
+function acceptAdd() {
+    var use = f32[0] + 1;
+    assertFloat32(use, true);
+    f32[0] = use;
+}
+test(acceptAdd);
+
+function acceptAddSeveral() {
+    var sum1 = f32[0] + 0.5;
+    var sum2 = f32[0] + 0.5;
+    f32[0] = sum1;
+    f32[0] = sum2;
+    assertFloat32(sum1, true);
+    assertFloat32(sum2, true);
+}
+test(acceptAddSeveral);
+
+function acceptAddVar() {
+    var x = f32[0] + 1;
+    f32[0] = x;
+    f32[1] = x;
+    assertFloat32(x, true);
+}
+test(acceptAddVar);
+
+function refuseAddCst() {
+    var x = f32[0] + 1234567890; // this constant can't be precisely represented as a float32
+    f32[0] = x;
+    assertFloat32(x, false);
+}
+test(refuseAddCst);
+
+function refuseAddVar() {
+    var x = f32[0] + 1;
+    f32[0] = x;
+    f32[1] = x;
+    f64[1] = x; // non consumer
+    assertFloat32(x, false);
+}
+test(refuseAddVar);
+
+function refuseAddStore64() {
+    var x = f32[0] + 1;
+    f64[0] = x; // non consumer
+    f32[0] = f64[0];
+    assertFloat32(x, false);
+}
+test(refuseAddStore64);
+
+function refuseAddStoreObj() {
+    var o = {}
+    var x = f32[0] + 1;
+    o.x = x; // non consumer
+    f32[0] = o['x'];
+    assertFloat32(x, false);
+}
+test(refuseAddStoreObj);
+
+function refuseAddSeveral() {
+    var sum = (f32[0] + 2) - 1; // second addition is not a consumer
+    f32[0] = sum;
+    assertFloat32(sum, false);
+}
+test(refuseAddSeveral);
+
+function refuseAddFunctionCall() {
+    function plusOne(x) { return Math.cos(x+1)*13.37; }
+    var res = plusOne(f32[0]); // func call is not a consumer
+    f32[0] = res;
+    assertFloat32(res, false);
+}
+test(refuseAddFunctionCall);
+
+function refuseLoop() {
+    var res = f32[0],
+        n = 10;
+    while (n--) {
+        res = res + 1; // this loop is equivalent to several additions => second addition is not a consumer
+        assertFloat32(res, false);
+    }
+    assertFloat32(res, false);
+    f32[0] = res;
+}
+test(refuseLoop);
+
+function acceptLoop() {
+    var res = f32[0],
+        n = 10;
+    while (n--) {
+        var sum = res + 1;
+        res = Math.fround(sum);
+        assertFloat32(sum, true);
+    }
+    assertFloat32(res, true);
+    f32[0] = res;
+}
+test(acceptLoop);
+
+function alternateCond(n) {
+    var x = f32[0];
+    if (n > 0) {
+        var s1 = x + 1;
+        f32[0] = s1;
+        assertFloat32(s1, true);
+    } else {
+        var s2 = x + 1;
+        f64[0] = s2; // non consumer
+        assertFloat32(s2, false);
+    }
+}
+(function() {
+    f32[0] = 0;
+    for (var n = 110; n; n--) {
+        alternateCond(n % 2);
+    }
+})();
+
+function phiTest(n) {
+    var x = (f32[0]);
+    var y = n;
+    if (n > 0) {
+        x = x + 2;
+        assertFloat32(x, true);
+    } else {
+        if (n < -10) {
+            x = Math.fround(Math.cos(y));
+            assertFloat32(x, true);
+        } else {
+            x = x - 1;
+            assertFloat32(x, true);
+        }
+    }
+    assertFloat32(x, true);
+    f32[0] = x;
+}
+(function() {
+    f32[0] = 0;
+    for (var n = 100; n; n--) {
+        phiTest( ((n % 3) - 1) * 15 );
+    }
+})();
+
+function mixedPhiTest(n) {
+    var x = (f32[0]);
+    var y = n;
+    if (n > 0) {
+        x = x + 2; // non consumer because of (1)
+        assertFloat32(x, false);
+    } else {
+        if (n < -10) {
+            x = Math.fround(Math.cos(y)); // new producer
+            assertFloat32(x, true);
+        } else {
+            x = x - 1; // non consumer because of (1)
+            assertFloat32(x, false);
+        }
+    }
+    assertFloat32(x, false);
+    x = x + 1; // (1) non consumer
+    f32[0] = x;
+}
+(function() {
+    f32[0] = 0;
+    for (var n = 100; n; n--) {
+        mixedPhiTest( ((n % 3) - 1) * 15 );
+    }
+})();
+
+function phiTest2(n) {
+    var x = f32[0];
+    while (n >= 0) {
+        x = Math.fround(Math.fround(x) + 1);
+        assertFloat32(x, true);
+        if (n < 10) {
+            x = f32[0] + 1;
+            assertFloat32(x, true);
+        }
+        n = n - 1;
+    }
+}
+(function(){
+    f32[0] = 0;
+    for (var n = 100; n > 10; n--) {
+        phiTest2(n);
+    }
+})();
--- a/js/src/jit/IonBuilder.h
+++ b/js/src/jit/IonBuilder.h
@@ -555,16 +555,17 @@ class IonBuilder : public MIRGenerator
     InliningStatus inlineNewObjectWithClassPrototype(CallInfo &callInfo);
     InliningStatus inlineHaveSameClass(CallInfo &callInfo);
     InliningStatus inlineToObject(CallInfo &callInfo);
     InliningStatus inlineDump(CallInfo &callInfo);
 
     // Testing functions.
     InliningStatus inlineForceSequentialOrInParallelSection(CallInfo &callInfo);
     InliningStatus inlineBailout(CallInfo &callInfo);
+    InliningStatus inlineAssertFloat32(CallInfo &callInfo);
 
     // Main inlining functions
     InliningStatus inlineNativeCall(CallInfo &callInfo, JSNative native);
     bool inlineScriptedCall(CallInfo &callInfo, JSFunction *target);
     InliningStatus inlineSingleCall(CallInfo &callInfo, JSFunction *target);
 
     // Call functions
     InliningStatus inlineCallsite(AutoObjectVector &targets, AutoObjectVector &originals,
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -491,16 +491,32 @@ LIRGenerator::visitApplyArgs(MApplyArgs 
 bool
 LIRGenerator::visitBail(MBail *bail)
 {
     LBail *lir = new LBail();
     return assignSnapshot(lir) && add(lir, bail);
 }
 
 bool
+LIRGenerator::visitAssertFloat32(MAssertFloat32 *assertion)
+{
+    MIRType type = assertion->input()->type();
+    bool checkIsFloat32 = assertion->mustBeFloat32();
+
+    if (!allowFloat32Optimizations())
+        return true;
+
+    if (type != MIRType_Value && !js_IonOptions.eagerCompilation) {
+        JS_ASSERT_IF(checkIsFloat32, type == MIRType_Float32);
+        JS_ASSERT_IF(!checkIsFloat32, type != MIRType_Float32);
+    }
+    return true;
+}
+
+bool
 LIRGenerator::visitGetDynamicName(MGetDynamicName *ins)
 {
     MDefinition *scopeChain = ins->getScopeChain();
     JS_ASSERT(scopeChain->type() == MIRType_Object);
 
     MDefinition *name = ins->getName();
     JS_ASSERT(name->type() == MIRType_String);
 
--- a/js/src/jit/Lowering.h
+++ b/js/src/jit/Lowering.h
@@ -109,16 +109,17 @@ class LIRGenerator : public LIRGenerator
     bool visitCreateArgumentsObject(MCreateArgumentsObject *ins);
     bool visitGetArgumentsObjectArg(MGetArgumentsObjectArg *ins);
     bool visitSetArgumentsObjectArg(MSetArgumentsObjectArg *ins);
     bool visitReturnFromCtor(MReturnFromCtor *ins);
     bool visitComputeThis(MComputeThis *ins);
     bool visitCall(MCall *call);
     bool visitApplyArgs(MApplyArgs *apply);
     bool visitBail(MBail *bail);
+    bool visitAssertFloat32(MAssertFloat32 *ins);
     bool visitGetDynamicName(MGetDynamicName *ins);
     bool visitFilterArguments(MFilterArguments *ins);
     bool visitCallDirectEval(MCallDirectEval *ins);
     bool visitTest(MTest *test);
     bool visitFunctionDispatch(MFunctionDispatch *ins);
     bool visitTypeObjectDispatch(MTypeObjectDispatch *ins);
     bool visitCompare(MCompare *comp);
     bool visitTypeOf(MTypeOf *ins);
--- a/js/src/jit/MCallOptimize.cpp
+++ b/js/src/jit/MCallOptimize.cpp
@@ -147,16 +147,18 @@ IonBuilder::inlineNativeCall(CallInfo &c
     if (native == intrinsic_ToObject)
         return inlineToObject(callInfo);
 
     // Testing Functions
     if (native == testingFunc_inParallelSection)
         return inlineForceSequentialOrInParallelSection(callInfo);
     if (native == testingFunc_bailout)
         return inlineBailout(callInfo);
+    if (native == testingFunc_assertFloat32)
+        return inlineAssertFloat32(callInfo);
 
     return InliningStatus_NotInlined;
 }
 
 types::StackTypeSet *
 IonBuilder::getInlineReturnTypeSet()
 {
     return types::TypeScript::BytecodeTypes(script(), pc);
@@ -1564,10 +1566,29 @@ IonBuilder::inlineBailout(CallInfo &call
     current->add(MBail::New());
 
     MConstant *undefined = MConstant::New(UndefinedValue());
     current->add(undefined);
     current->push(undefined);
     return InliningStatus_Inlined;
 }
 
+IonBuilder::InliningStatus
+IonBuilder::inlineAssertFloat32(CallInfo &callInfo)
+{
+    callInfo.unwrapArgs();
+
+    MDefinition *secondArg = callInfo.getArg(1);
+
+    JS_ASSERT(secondArg->type() == MIRType_Boolean);
+    JS_ASSERT(secondArg->isConstant());
+
+    bool mustBeFloat32 = JSVAL_TO_BOOLEAN(secondArg->toConstant()->value());
+    current->add(MAssertFloat32::New(callInfo.getArg(0), mustBeFloat32));
+
+    MConstant *undefined = MConstant::New(UndefinedValue());
+    current->add(undefined);
+    current->push(undefined);
+    return InliningStatus_Inlined;
+}
+
 } // namespace jit
 } // namespace js
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -1848,16 +1848,38 @@ class MBail : public MNullaryInstruction
 
     static MBail *
     New() {
         return new MBail();
     }
 
 };
 
+class MAssertFloat32 : public MUnaryInstruction
+{
+  protected:
+    bool mustBeFloat32_;
+
+    MAssertFloat32(MDefinition *value, bool mustBeFloat32)
+      : MUnaryInstruction(value), mustBeFloat32_(mustBeFloat32)
+    {
+    }
+
+  public:
+    INSTRUCTION_HEADER(AssertFloat32)
+
+    static MAssertFloat32 *New(MDefinition *value, bool mustBeFloat32) {
+        return new MAssertFloat32(value, mustBeFloat32);
+    }
+
+    bool canConsumeFloat32() const { return true; }
+
+    bool mustBeFloat32() { return mustBeFloat32_; }
+};
+
 class MGetDynamicName
   : public MAryInstruction<2>,
     public MixPolicy<ObjectPolicy<0>, StringPolicy<1> >
 {
   protected:
     MGetDynamicName(MDefinition *scopeChain, MDefinition *name)
     {
         setOperand(0, scopeChain);
--- a/js/src/jit/MOpcodes.h
+++ b/js/src/jit/MOpcodes.h
@@ -35,16 +35,17 @@ namespace jit {
     _(GetArgumentsObjectArg)                                                \
     _(SetArgumentsObjectArg)                                                \
     _(ComputeThis)                                                          \
     _(PrepareCall)                                                          \
     _(PassArg)                                                              \
     _(Call)                                                                 \
     _(ApplyArgs)                                                            \
     _(Bail)                                                                 \
+    _(AssertFloat32)                                                        \
     _(GetDynamicName)                                                       \
     _(FilterArguments)                                                      \
     _(CallDirectEval)                                                       \
     _(BitNot)                                                               \
     _(TypeOf)                                                               \
     _(ToId)                                                                 \
     _(BitAnd)                                                               \
     _(BitOr)                                                                \
--- a/js/src/jit/ParallelSafetyAnalysis.cpp
+++ b/js/src/jit/ParallelSafetyAnalysis.cpp
@@ -127,16 +127,17 @@ class ParallelSafetyVisitor : public MIn
     UNSAFE_OP(GetArgumentsObjectArg)
     UNSAFE_OP(SetArgumentsObjectArg)
     UNSAFE_OP(ComputeThis)
     SAFE_OP(PrepareCall)
     SAFE_OP(PassArg)
     CUSTOM_OP(Call)
     UNSAFE_OP(ApplyArgs)
     UNSAFE_OP(Bail)
+    UNSAFE_OP(AssertFloat32)
     UNSAFE_OP(GetDynamicName)
     UNSAFE_OP(FilterArguments)
     UNSAFE_OP(CallDirectEval)
     SAFE_OP(BitNot)
     UNSAFE_OP(TypeOf)
     SAFE_OP(ToId)
     SAFE_OP(BitAnd)
     SAFE_OP(BitOr)
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4016,16 +4016,20 @@ static const JSFunctionSpecWithHelp fuzz
     JS_FN_HELP("system", System, 1, 0,
 "system(command)",
 "  Execute command on the current host, returning result code."),
 
     JS_FN_HELP("trap", Trap, 3, 0,
 "trap([fun, [pc,]] exp)",
 "  Trap bytecode execution."),
 
+    JS_FN_HELP("assertFloat32", testingFunc_assertFloat32, 2, 0,
+"assertFloat32(value, isFloat32)",
+"  In IonMonkey only, asserts that value has (resp. hasn't) the MIRType_Float32 if isFloat32 is true (resp. false)."),
+
     JS_FN_HELP("untrap", Untrap, 2, 0,
 "untrap(fun[, pc])",
 "  Remove a trap."),
 
     JS_FS_HELP_END
 };
 
 #ifdef MOZ_PROFILING