Bug 913253: assertFloat32 function + tests for IonMonkey; r=nbp
authorBenjamin Bouvier <bbouvier@mozilla.com>
Wed, 11 Sep 2013 02:12:01 -0700
changeset 146886 6203c90cab31d5042fafaa9998ed4023723228e4
parent 146885 8621bdc408416276a715164dc7c7f7f14cfaaaf4
child 146887 211b9c5a464d1d1fadbae033d42c8f5e97cf8b87
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersnbp
bugs913253
milestone26.0a1
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