From 7a0d53f8abf999ba712f5e338d478603d24d497a Mon Sep 17 00:00:00 2001 From: Roy Tam Date: Sat, 19 Jan 2019 17:42:08 +0800 Subject: [PATCH] import changes from rmottola/Arctic-Fox: - Bug 1031152 - Part 2: Define a JSAPI test for the SavedFrame public API (5b391e61c) - add MOZ_OVVERRIDE and MOZ_FINAL taking from older upstream, since they were later removed. (45041af7f) - Bug 1129769 - Handle more kinds when resolving tracked type names. (76779d2bc) - Bug 1129769 - Followup: ignore indirect function calls from JSStreamWriter's use of std::ostream in hazard analysis. (df3fe0866) - Bug 1129780 - Report the youngest sampled frame's line number if it has optimization info. (46a5b92fc) - Bug 1131429 - Add a shell function to dump all of a function's tracked optimizations. (be91b86d0) - Bug 1136837 part 1 - Don't inline calls with incomplete type information for this or arguments. (0f88dc0f5) - Bug 1136837 part 2 - Improve |this| types when inlining after a CALLPROP/CALLELEM. (0f6e50f93) - Bug 1134638: 1. Add OperationName to MSimdBinaryBitwise (4100a5ff8) - Bug 1134638: 2. Inline some float32x4 binary arithmetic and bitwise operations (c7f3a22ea) - Bug 1134638: 3. Templatize inlineSimdBinary functions (39b6f6fab) - Bug 1134638: 4. Also test correctness of SIMD operations (8c9d75ed3) - Bug 1134638: 5. Add OperationName to MSimdUnaryArith; (69860fe64) - Bug 1134638: 6. Inline SIMD unary arithmetic operations (6a456e62e) - Bug 1134638: 7. Add spew for MSimd{Binary{Bitwise,Arith},Unary} (199320b8e) - Bug 1134638: 8. Inline SIMD conversions in Ion (6a06cc94c) - Bug 1047529 - Move caller field from MResumePoint to MBasicBlock. (2f2617ccd) - Revert "Bug 1047529 - Move caller field from MResumePoint to MBasicBlock." (b369b2f7d) - Bug 1120170 - part 1 - Change JSMSG_NOT_NONNULL_OBJECT message to accept an argument. (20c7374f3) - Bug 1120170 - part 2 - Self-host RegExp.prototype.flags. (469c3b324) - Bug 1135429 - Object.create shouldn't throw when its second argument is a primitive value. (a1b3545ba) - Bug 1047529 - Move caller field from MResumePoint to MBasicBlock. (7b00e8a79) - Bug 1134638: 9. Drive-by cleanup: FloatingTypePolicy can just have SPECIALIZATION_DATA (d5f86598c) --- js/public/TrackedOptimizationInfo.h | 28 +- js/src/Makefile.in | 1 + js/src/builtin/Object.cpp | 19 +- js/src/builtin/RegExp.cpp | 62 +- js/src/builtin/RegExp.js | 38 ++ js/src/builtin/WeakSet.js | 2 +- js/src/builtin/WeakSetObject.cpp | 5 +- js/src/devtools/rootAnalysis/annotations.js | 6 + js/src/jit-test/lib/simd.js | 39 ++ js/src/jit-test/tests/SIMD/convert.js | 34 + .../tests/SIMD/float32x4-binary-arith.js | 33 + .../tests/SIMD/float32x4-binary-bitwise.js | 40 ++ js/src/jit-test/tests/SIMD/unary.js | 33 + ...create-with-primitive-second-arg-in-ion.js | 8 + js/src/jit/BaselineIC.cpp | 18 +- js/src/jit/IonBuilder.cpp | 90 ++- js/src/jit/IonBuilder.h | 19 +- js/src/jit/JitcodeMap.cpp | 96 ++- js/src/jit/JitcodeMap.h | 44 ++ js/src/jit/LIR-Common.h | 3 + js/src/jit/MCallOptimize.cpp | 162 +++-- js/src/jit/MIR.cpp | 53 +- js/src/jit/MIR.h | 86 ++- js/src/jit/MIRGraph.cpp | 10 +- js/src/jit/MIRGraph.h | 8 +- js/src/jit/OptimizationTracking.cpp | 121 ++-- js/src/jit/OptimizationTracking.h | 15 + js/src/jit/TypePolicy.cpp | 26 +- js/src/jit/TypePolicy.h | 26 +- js/src/js.msg | 2 +- js/src/jsapi-tests/testSavedStacks.cpp | 42 ++ js/src/jsapi.h | 2 +- js/src/jsobj.cpp | 11 +- js/src/jsweakmap.cpp | 10 +- js/src/perf/jsperf.cpp | 5 +- js/src/shell/js.cpp | 176 +++++ ...object-create-with-primitive-second-arg.js | 8 + js/src/tests/ecma_6/RegExp/flags.js | 8 +- js/src/vm/Debugger.cpp | 2 +- js/src/vm/DebuggerMemory.cpp | 2 +- js/src/vm/SavedStacks.cpp | 2 +- mfbt/Attributes.h | 176 +++-- tools/profiler/ProfileEntry.cpp | 622 ++++++++++++++++++ tools/profiler/ProfileEntry.h | 185 ++++++ tools/profiler/moz.build | 1 + 45 files changed, 2043 insertions(+), 336 deletions(-) create mode 100644 js/src/builtin/RegExp.js create mode 100644 js/src/jit-test/lib/simd.js create mode 100644 js/src/jit-test/tests/SIMD/convert.js create mode 100644 js/src/jit-test/tests/SIMD/float32x4-binary-arith.js create mode 100644 js/src/jit-test/tests/SIMD/float32x4-binary-bitwise.js create mode 100644 js/src/jit-test/tests/SIMD/unary.js create mode 100644 js/src/jit-test/tests/ion/object-create-with-primitive-second-arg-in-ion.js create mode 100644 js/src/tests/ecma_5/Object/object-create-with-primitive-second-arg.js create mode 100644 tools/profiler/ProfileEntry.cpp create mode 100644 tools/profiler/ProfileEntry.h diff --git a/js/public/TrackedOptimizationInfo.h b/js/public/TrackedOptimizationInfo.h index 48962775b0..748d410039 100644 --- a/js/public/TrackedOptimizationInfo.h +++ b/js/public/TrackedOptimizationInfo.h @@ -205,6 +205,8 @@ namespace JS { "can't inline: not hot enough") \ _(CantInlineNotInDispatch, \ "can't inline: not in dispatch table") \ + _(CantInlineUnreachable, \ + "can't inline: unreachable due to incomplete types for this/arguments") \ _(CantInlineNativeBadForm, \ "can't inline native: bad form (arity mismatch/constructing)") \ _(CantInlineNativeBadType, \ @@ -281,7 +283,8 @@ struct ForEachTrackedOptimizationAttemptOp JS_PUBLIC_API(void) ForEachTrackedOptimizationAttempt(JSRuntime* rt, void* addr, - ForEachTrackedOptimizationAttemptOp& op); + ForEachTrackedOptimizationAttemptOp &op, + JSScript **scriptOut, jsbytecode **pcOut); struct ForEachTrackedOptimizationTypeInfoOp { @@ -295,15 +298,28 @@ struct ForEachTrackedOptimizationTypeInfoOp // function. // - "alloc site" for object types tied to an allocation site. // - "prototype" for object types tied neither to a constructor nor - // to an allocation site. + // to an allocation site, but to a prototype. + // - "singleton" for object types which only has a single value. + // - "function" for object types referring to scripted functions. + // - "native" for object types referring to native functions. // // The name parameter is the string representation of the type. If the // type is keyed by "constructor", or if the type itself refers to a - // scripted function, the name is the function's displayAtom. + // scripted function, the name is the function's displayAtom. If the type + // is keyed by "native", this is nullptr. // - // If the type is keyed by "constructor", "alloc site", or if the type - // itself refers to a scripted function, the location and lineno - // parameters will be respectively non-nullptr and non-0. + // The location parameter is the filename if the type is keyed by + // "constructor", "alloc site", or if the type itself refers to a scripted + // function. If the type is keyed by "native", it is the offset of the + // native function, suitable for use with addr2line on Linux or atos on OS + // X. Otherwise it is nullptr. + // + // The lineno parameter is the line number if the type is keyed by + // "constructor", "alloc site", or if the type itself refers to a scripted + // function. Otherwise it is UINT32_MAX. + // + // The location parameter is the only one that may need escaping if being + // quoted. virtual void readType(const char* keyedBy, const char* name, const char* location, unsigned lineno) = 0; diff --git a/js/src/Makefile.in b/js/src/Makefile.in index 303fafc314..8a7339ae62 100644 --- a/js/src/Makefile.in +++ b/js/src/Makefile.in @@ -314,6 +314,7 @@ selfhosting_srcs := \ $(srcdir)/builtin/Map.js \ $(srcdir)/builtin/Number.js \ $(srcdir)/builtin/Object.js \ + $(srcdir)/builtin/RegExp.js \ $(srcdir)/builtin/String.js \ $(srcdir)/builtin/Set.js \ $(srcdir)/builtin/TypedArray.js \ diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 1a9e2540cc..d5a25584f7 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -659,11 +659,13 @@ js::ObjectCreateWithTemplate(JSContext* cx, HandlePlainObject templateObj) return ObjectCreateImpl(cx, proto, GenericObject, group); } -/* ES5 15.2.3.5: Object.create(O [, Properties]) */ +// ES6 draft rev34 (2015/02/20) 19.1.2.2 Object.create(O [, Properties]) bool js::obj_create(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. if (args.length() == 0) { JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED, "Object.create", "0", "s"); @@ -681,24 +683,21 @@ js::obj_create(JSContext* cx, unsigned argc, Value* vp) return false; } + // Step 2. RootedObject proto(cx, args[0].toObjectOrNull()); RootedPlainObject obj(cx, ObjectCreateImpl(cx, proto)); if (!obj) return false; - /* 15.2.3.5 step 4. */ + // Step 3. if (args.hasDefined(1)) { - if (args[1].isPrimitive()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); - return false; - } - - RootedObject props(cx, &args[1].toObject()); - if (!DefineProperties(cx, obj, props)) + RootedValue val(cx, args[1]); + RootedObject props(cx, ToObject(cx, val)); + if (!props || !DefineProperties(cx, obj, props)) return false; } - /* 5. Return obj. */ + // Step 4. args.rval().setObject(*obj); return true; } diff --git a/js/src/builtin/RegExp.cpp b/js/src/builtin/RegExp.cpp index c4ea6edce0..eabb400531 100644 --- a/js/src/builtin/RegExp.cpp +++ b/js/src/builtin/RegExp.cpp @@ -345,66 +345,6 @@ regexp_toString(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod(cx, args); } -/* ES6 draft rev29 21.2.5.3 RegExp.prototype.flags */ -bool -regexp_flags(JSContext* cx, unsigned argc, JS::Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - /* Steps 1-2. */ - if (!args.thisv().isObject()) { - char* bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.thisv(), NullPtr()); - if (!bytes) - return false; - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_UNEXPECTED_TYPE, - bytes, "not an object"); - js_free(bytes); - return false; - } - RootedObject thisObj(cx, &args.thisv().toObject()); - - /* Step 3. */ - StringBuffer sb(cx); - - /* Steps 4-6. */ - RootedValue global(cx); - if (!GetProperty(cx, thisObj, thisObj, cx->names().global, &global)) - return false; - if (ToBoolean(global) && !sb.append('g')) - return false; - - /* Steps 7-9. */ - RootedValue ignoreCase(cx); - if (!GetProperty(cx, thisObj, thisObj, cx->names().ignoreCase, &ignoreCase)) - return false; - if (ToBoolean(ignoreCase) && !sb.append('i')) - return false; - - /* Steps 10-12. */ - RootedValue multiline(cx); - if (!GetProperty(cx, thisObj, thisObj, cx->names().multiline, &multiline)) - return false; - if (ToBoolean(multiline) && !sb.append('m')) - return false; - - /* Steps 13-15. */ - RootedValue unicode(cx); - if (!GetProperty(cx, thisObj, thisObj, cx->names().unicode, &unicode)) - return false; - if (ToBoolean(unicode) && !sb.append('u')) - return false; - - /* Steps 16-18. */ - RootedValue sticky(cx); - if (!GetProperty(cx, thisObj, thisObj, cx->names().sticky, &sticky)) - return false; - if (ToBoolean(sticky) && !sb.append('y')) - return false; - - /* Step 19. */ - args.rval().setString(sb.finishString()); - return true; -} /* ES6 draft rev32 21.2.5.4. */ MOZ_ALWAYS_INLINE bool @@ -487,7 +427,7 @@ regexp_sticky(JSContext* cx, unsigned argc, JS::Value* vp) } static const JSPropertySpec regexp_properties[] = { - JS_PSG("flags", regexp_flags, 0), + JS_SELF_HOSTED_GET("flags", "RegExpFlagsGetter", 0), JS_PSG("global", regexp_global, 0), JS_PSG("ignoreCase", regexp_ignoreCase, 0), JS_PSG("multiline", regexp_multiline, 0), diff --git a/js/src/builtin/RegExp.js b/js/src/builtin/RegExp.js new file mode 100644 index 0000000000..e56167fe80 --- /dev/null +++ b/js/src/builtin/RegExp.js @@ -0,0 +1,38 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// ES6 draft rev34 (2015/02/20) 21.2.5.3 get RegExp.prototype.flags +function RegExpFlagsGetter() { + // Steps 1-2. + var R = this; + if (!IsObject(R)) + ThrowError(JSMSG_NOT_NONNULL_OBJECT, R === null ? "null" : typeof R); + + // Step 3. + var result = ""; + + // Steps 4-6. + if (R.global) + result += "g"; + + // Steps 7-9. + if (R.ignoreCase) + result += "i"; + + // Steps 10-12. + if (R.multiline) + result += "m"; + + // Steps 13-15. + // TODO: Uncomment these steps when bug 1135377 is fixed. + // if (R.unicode) + // result += "u"; + + // Steps 16-18. + if (R.sticky) + result += "y"; + + // Step 19. + return result; +} diff --git a/js/src/builtin/WeakSet.js b/js/src/builtin/WeakSet.js index 31e56c5be0..e7913a4651 100644 --- a/js/src/builtin/WeakSet.js +++ b/js/src/builtin/WeakSet.js @@ -16,7 +16,7 @@ function WeakSet_add(value) { // Step 5. if (!IsObject(value)) - ThrowError(JSMSG_NOT_NONNULL_OBJECT); + ThrowError(JSMSG_NOT_NONNULL_OBJECT, DecompileArg(0, value)); // Steps 7-8. callFunction(std_WeakMap_set, entries, value, true); diff --git a/js/src/builtin/WeakSetObject.cpp b/js/src/builtin/WeakSetObject.cpp index 25e544e396..497fec1016 100644 --- a/js/src/builtin/WeakSetObject.cpp +++ b/js/src/builtin/WeakSetObject.cpp @@ -123,7 +123,10 @@ WeakSetObject::construct(JSContext* cx, unsigned argc, Value* vp) if (isOriginalAdder) { if (keyVal.isPrimitive()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); + char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, NullPtr()); + if (!bytes) + return false; + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes); return false; } diff --git a/js/src/devtools/rootAnalysis/annotations.js b/js/src/devtools/rootAnalysis/annotations.js index 4720eb02ed..d4dab1a730 100644 --- a/js/src/devtools/rootAnalysis/annotations.js +++ b/js/src/devtools/rootAnalysis/annotations.js @@ -81,6 +81,8 @@ var ignoreCallees = { "PLDHashTableOps.hashKey" : true, "z_stream_s.zfree" : true, "GrGLInterface.fCallback" : true, + "std::strstreambuf._M_alloc_fun" : true, + "std::strstreambuf._M_free_fun" : true, }; function fieldCallCannotGC(csu, fullfield) @@ -140,6 +142,10 @@ var ignoreFunctions = { // Bug 1056410 - devirtualization prevents the standard nsISupports::Release heuristic from working "uint32 nsXPConnect::Release()" : true, + // Has an indirect call under it by the name "__f", which seemed too + // generic to ignore by itself. + "void* std::_Locale_impl::~_Locale_impl(int32)" : true, + // FIXME! "NS_LogInit": true, "NS_LogTerm": true, diff --git a/js/src/jit-test/lib/simd.js b/js/src/jit-test/lib/simd.js new file mode 100644 index 0000000000..bf93286152 --- /dev/null +++ b/js/src/jit-test/lib/simd.js @@ -0,0 +1,39 @@ +function binaryX4(op, v, w) { + var arr = []; + var [varr, warr] = [simdToArray(v), simdToArray(w)]; + [varr, warr] = [varr.map(Math.fround), warr.map(Math.fround)]; + for (var i = 0; i < 4; i++) + arr[i] = op(varr[i], warr[i]); + return arr.map(Math.fround); +} + +function unaryX4(op, v, coerceFunc) { + var arr = []; + var varr = simdToArray(v).map(coerceFunc); + for (var i = 0; i < 4; i++) + arr[i] = op(varr[i]); + return arr.map(coerceFunc); +} + +function assertNear(a, b) { + assertEq((a != a && b != b) || Math.abs(a - b) < 0.001, true); +} + +function assertEqX4(vec, arr, ...opts) { + + var assertFunc; + if (opts.length == 1) { + assertFunc = opts[0]; + } else { + assertFunc = assertEq; + } + + assertFunc(vec.x, arr[0]); + assertFunc(vec.y, arr[1]); + assertFunc(vec.z, arr[2]); + assertFunc(vec.w, arr[3]); +} + +function simdToArray(vec) { + return [vec.x, vec.y, vec.z, vec.w]; +} diff --git a/js/src/jit-test/tests/SIMD/convert.js b/js/src/jit-test/tests/SIMD/convert.js new file mode 100644 index 0000000000..aab00d31e5 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/convert.js @@ -0,0 +1,34 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +var cast = (function() { + var i32 = new Int32Array(1); + var f32 = new Float32Array(i32.buffer); + return { + fromInt32Bits(x) { + i32[0] = x; + return f32[0]; + }, + + fromFloat32Bits(x) { + f32[0] = x; + return i32[0]; + } + } +})(); + +function f() { + var f4 = SIMD.float32x4(1, 2, 3, 4); + var i4 = SIMD.int32x4(1, 2, 3, 4); + var BitOrZero = (x) => x | 0; + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.float32x4.fromInt32x4(i4), unaryX4(BitOrZero, f4, Math.fround)); + assertEqX4(SIMD.float32x4.fromInt32x4Bits(i4), unaryX4(cast.fromInt32Bits, f4, Math.fround)); + assertEqX4(SIMD.int32x4.fromFloat32x4(f4), unaryX4(Math.fround, i4, BitOrZero)); + assertEqX4(SIMD.int32x4.fromFloat32x4Bits(f4), unaryX4(cast.fromFloat32Bits, i4, BitOrZero)); + } +} + +f(); + diff --git a/js/src/jit-test/tests/SIMD/float32x4-binary-arith.js b/js/src/jit-test/tests/SIMD/float32x4-binary-arith.js new file mode 100644 index 0000000000..7d0bbd40cb --- /dev/null +++ b/js/src/jit-test/tests/SIMD/float32x4-binary-arith.js @@ -0,0 +1,33 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +function maxNum(x, y) { + if (x != x) + return y; + if (y != y) + return x; + return Math.max(x, y); +} + +function minNum(x, y) { + if (x != x) + return y; + if (y != y) + return x; + return Math.min(x, y); +} + +function f() { + var f1 = SIMD.float32x4(1, 2, 3, 4); + var f2 = SIMD.float32x4(4, 3, 2, 1); + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.float32x4.div(f1, f2), binaryX4((x, y) => x / y, f1, f2)); + assertEqX4(SIMD.float32x4.min(f1, f2), binaryX4(Math.min, f1, f2)); + assertEqX4(SIMD.float32x4.max(f1, f2), binaryX4(Math.max, f1, f2)); + assertEqX4(SIMD.float32x4.minNum(f1, f2), binaryX4(minNum, f1, f2)); + assertEqX4(SIMD.float32x4.maxNum(f1, f2), binaryX4(maxNum, f1, f2)); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/float32x4-binary-bitwise.js b/js/src/jit-test/tests/SIMD/float32x4-binary-bitwise.js new file mode 100644 index 0000000000..e6fac5a8d5 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/float32x4-binary-bitwise.js @@ -0,0 +1,40 @@ +load(libdir + "simd.js"); + +setJitCompilerOption("ion.warmup.trigger", 50); + +var helpers = (function() { + var i32 = new Int32Array(2); + var f32 = new Float32Array(i32.buffer); + return { + and: function(x, y) { + f32[0] = x; + f32[1] = y; + i32[0] = i32[0] & i32[1]; + return f32[0]; + }, + or: function(x, y) { + f32[0] = x; + f32[1] = y; + i32[0] = i32[0] | i32[1]; + return f32[0]; + }, + xor: function(x, y) { + f32[0] = x; + f32[1] = y; + i32[0] = i32[0] ^ i32[1]; + return f32[0]; + }, + } +})(); + +function f() { + var f1 = SIMD.float32x4(1, 2, 3, 4); + var f2 = SIMD.float32x4(4, 3, 2, 1); + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.float32x4.and(f1, f2), binaryX4(helpers.and, f1, f2)); + assertEqX4(SIMD.float32x4.or(f1, f2), binaryX4(helpers.or, f1, f2)); + assertEqX4(SIMD.float32x4.xor(f1, f2), binaryX4(helpers.xor, f1, f2)); + } +} + +f(); diff --git a/js/src/jit-test/tests/SIMD/unary.js b/js/src/jit-test/tests/SIMD/unary.js new file mode 100644 index 0000000000..bc5b898435 --- /dev/null +++ b/js/src/jit-test/tests/SIMD/unary.js @@ -0,0 +1,33 @@ +load(libdir + 'simd.js'); + +setJitCompilerOption("ion.warmup.trigger", 50); + +var notf = (function() { + var i32 = new Int32Array(1); + var f32 = new Float32Array(i32.buffer); + return function(x) { + f32[0] = x; + i32[0] = ~i32[0]; + return f32[0]; + } +})(); + +function f() { + var f4 = SIMD.float32x4(1, 2, 3, 4); + var i4 = SIMD.int32x4(1, 2, 3, 4); + var BitOrZero = (x) => x | 0; + for (var i = 0; i < 150; i++) { + assertEqX4(SIMD.float32x4.not(f4), unaryX4(notf, f4, Math.fround)); + assertEqX4(SIMD.float32x4.neg(f4), unaryX4((x) => -x, f4, Math.fround)); + assertEqX4(SIMD.float32x4.abs(f4), unaryX4(Math.abs, f4, Math.fround)); + assertEqX4(SIMD.float32x4.sqrt(f4), unaryX4(Math.sqrt, f4, Math.fround)); + + assertEqX4(SIMD.float32x4.reciprocal(f4), unaryX4((x) => 1 / x, f4, Math.fround), assertNear); + assertEqX4(SIMD.float32x4.reciprocalSqrt(f4), unaryX4((x) => 1 / Math.sqrt(x), f4, Math.fround), assertNear); + + assertEqX4(SIMD.int32x4.not(i4), unaryX4((x) => ~x, i4, BitOrZero)); + assertEqX4(SIMD.int32x4.neg(i4), unaryX4((x) => -x, i4, BitOrZero)); + } +} + +f(); diff --git a/js/src/jit-test/tests/ion/object-create-with-primitive-second-arg-in-ion.js b/js/src/jit-test/tests/ion/object-create-with-primitive-second-arg-in-ion.js new file mode 100644 index 0000000000..05f82eb23d --- /dev/null +++ b/js/src/jit-test/tests/ion/object-create-with-primitive-second-arg-in-ion.js @@ -0,0 +1,8 @@ +// |jit-test| ion-eager +load(libdir + "asserts.js"); + +[1, "", true, Symbol(), undefined].forEach(props => { + assertEq(Object.getPrototypeOf(Object.create(null, props)), null); +}); + +assertThrowsInstanceOf(() => Object.create(null, null), TypeError); diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 36e1243ef2..08540ac4ae 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -9177,13 +9177,29 @@ GetTemplateObjectForNative(JSContext* cx, HandleScript script, jsbytecode* pc, #define ADD_INT32X4_SIMD_OP_NAME_(OP) || native == js::simd_int32x4_##OP if (false ARITH_COMMONX4_SIMD_OP(ADD_INT32X4_SIMD_OP_NAME_) - BITWISE_COMMONX4_SIMD_OP(ADD_INT32X4_SIMD_OP_NAME_)) + BITWISE_COMMONX4_SIMD_OP(ADD_INT32X4_SIMD_OP_NAME_) + || native == js::simd_int32x4_not || native == js::simd_int32x4_neg + || native == js::simd_int32x4_fromFloat32x4 || native == js::simd_int32x4_fromFloat32x4Bits) { Rooted descr(cx, &cx->global()->int32x4TypeDescr().as()); res.set(cx->compartment()->jitCompartment()->getSimdTemplateObjectFor(cx, descr)); return !!res; } #undef ADD_INT32X4_SIMD_OP_NAME_ +#define ADD_FLOAT32X4_SIMD_OP_NAME_(OP) || native == js::simd_float32x4_##OP + if (false + ARITH_FLOAT32X4_SIMD_OP(ADD_FLOAT32X4_SIMD_OP_NAME_) + BITWISE_COMMONX4_SIMD_OP(ADD_FLOAT32X4_SIMD_OP_NAME_) + || native == js::simd_float32x4_abs || native == js::simd_float32x4_sqrt + || native == js::simd_float32x4_reciprocal || native == js::simd_float32x4_reciprocalSqrt + || native == js::simd_float32x4_not || native == js::simd_float32x4_neg + || native == js::simd_float32x4_fromInt32x4 || native == js::simd_float32x4_fromInt32x4Bits) + { + Rooted descr(cx, &cx->global()->float32x4TypeDescr().as()); + res.set(cx->compartment()->jitCompartment()->getSimdTemplateObjectFor(cx, descr)); + return !!res; + } +#undef ADD_FLOAT32X4_SIMD_OP_NAME_ } return true; diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index e2d599fa9b..02b3bf0f62 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -420,6 +420,25 @@ IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo) return DontInline(nullptr, "Non-interpreted target"); } + if (info().analysisMode() != Analysis_DefiniteProperties) { + // If |this| or an argument has an empty resultTypeSet, don't bother + // inlining, as the call is currently unreachable due to incomplete type + // information. This does not apply to the definite properties analysis, + // in that case we want to inline anyway. + + if (callInfo.thisArg()->emptyResultTypeSet()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineUnreachable); + return DontInline(nullptr, "Empty TypeSet for |this|"); + } + + for (size_t i = 0; i < callInfo.argc(); i++) { + if (callInfo.getArg(i)->emptyResultTypeSet()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineUnreachable); + return DontInline(nullptr, "Empty TypeSet for argument"); + } + } + } + // Allow constructing lazy scripts when performing the definite properties // analysis, as baseline has not been used to warm the caller up yet. if (target->isInterpreted() && info().analysisMode() == Analysis_DefiniteProperties) { @@ -1851,7 +1870,11 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_GETELEM: case JSOP_CALLELEM: - return jsop_getelem(); + if (!jsop_getelem()) + return false; + if (op == JSOP_CALLELEM && !improveThisTypesForCall()) + return false; + return true; case JSOP_SETELEM: case JSOP_STRICTSETELEM: @@ -1876,7 +1899,11 @@ IonBuilder::inspectOpcode(JSOp op) case JSOP_CALLPROP: { PropertyName* name = info().getAtom(pc)->asPropertyName(); - return jsop_getprop(name); + if (!jsop_getprop(name)) + return false; + if (op == JSOP_CALLPROP && !improveThisTypesForCall()) + return false; + return true; } case JSOP_SETPROP: @@ -4476,7 +4503,7 @@ IonBuilder::inlineScriptedCall(CallInfo& callInfo, JSFunction* target) callInfo.pushFormals(current); MResumePoint* outerResumePoint = - MResumePoint::New(alloc(), current, pc, callerResumePoint_, MResumePoint::Outer); + MResumePoint::New(alloc(), current, pc, MResumePoint::Outer); if (!outerResumePoint) return false; current->setOuterResumePoint(outerResumePoint); @@ -5133,7 +5160,7 @@ IonBuilder::inlineObjectGroupFallback(CallInfo& callInfo, MBasicBlock* dispatchB // Capture stack prior to the call operation. This captures the function. MResumePoint* preCallResumePoint = - MResumePoint::New(alloc(), dispatchBlock, pc, callerResumePoint_, MResumePoint::ResumeAt); + MResumePoint::New(alloc(), dispatchBlock, pc, MResumePoint::ResumeAt); if (!preCallResumePoint) return false; @@ -6817,7 +6844,7 @@ IonBuilder::resume(MInstruction* ins, jsbytecode* pc, MResumePoint::Mode mode) { MOZ_ASSERT(ins->isEffectful() || !ins->isMovable()); - MResumePoint* resumePoint = MResumePoint::New(alloc(), ins->block(), pc, callerResumePoint_, + MResumePoint* resumePoint = MResumePoint::New(alloc(), ins->block(), pc, mode); if (!resumePoint) return false; @@ -7566,6 +7593,14 @@ IonBuilder::jsop_getelem() if (!resumeAfter(ins)) return false; + if (*pc == JSOP_CALLELEM && IsNullOrUndefined(obj->type())) { + // Due to inlining, it's possible the observed TypeSet is non-empty, + // even though we know |obj| is null/undefined and the MCallGetElement + // will throw. Don't push a TypeBarrier in this case, to avoid + // inlining the following (unreachable) JSOP_CALL. + return true; + } + TemporaryTypeSet* types = bytecodeTypes(pc); return pushTypeBarrier(ins, types, BarrierKind::TypeSet); } @@ -9630,7 +9665,7 @@ IonBuilder::annotateGetPropertyCache(MDefinition* obj, MGetPropertyCache* getPro if (inlinePropTable->numEntries() > 0) { // Push the object back onto the stack temporarily to capture the resume point. current->push(obj); - MResumePoint* resumePoint = MResumePoint::New(alloc(), current, pc, callerResumePoint_, + MResumePoint* resumePoint = MResumePoint::New(alloc(), current, pc, MResumePoint::ResumeAt); if (!resumePoint) return false; @@ -9838,9 +9873,52 @@ IonBuilder::jsop_getprop(PropertyName* name) if (!resumeAfter(call)) return false; + if (*pc == JSOP_CALLPROP && IsNullOrUndefined(obj->type())) { + // Due to inlining, it's possible the observed TypeSet is non-empty, + // even though we know |obj| is null/undefined and the MCallGetProperty + // will throw. Don't push a TypeBarrier in this case, to avoid + // inlining the following (unreachable) JSOP_CALL. + return true; + } + return pushTypeBarrier(call, types, BarrierKind::TypeSet); } +bool +IonBuilder::improveThisTypesForCall() +{ + // After a CALLPROP (or CALLELEM) for obj.prop(), the this-value and callee + // for the call are on top of the stack: + // + // ... [this: obj], [callee: obj.prop] + // + // If obj is null or undefined, obj.prop would have thrown an exception so + // at this point we can remove null and undefined from obj's TypeSet, to + // improve type information for the call that will follow. + + MOZ_ASSERT(*pc == JSOP_CALLPROP || *pc == JSOP_CALLELEM); + + // Ensure |this| has types {object, null/undefined}. + MDefinition *thisDef = current->peek(-2); + if (thisDef->type() != MIRType_Value || + !thisDef->mightBeType(MIRType_Object) || + !thisDef->resultTypeSet() || + !thisDef->resultTypeSet()->objectOrSentinel()) + { + return true; + } + + // Remove null/undefined from the TypeSet. + TemporaryTypeSet *types = thisDef->resultTypeSet()->cloneObjectsOnly(alloc_->lifoAlloc()); + if (!types) + return false; + + MFilterTypeSet *filter = MFilterTypeSet::New(alloc(), thisDef, types); + current->add(filter); + current->rewriteAtDepth(-2, filter); + return true; +} + bool IonBuilder::checkIsDefinitelyOptimizedArguments(MDefinition* obj, bool* isOptimizedArgs) { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 9d7650441e..086d815ee9 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -607,6 +607,7 @@ class IonBuilder return length; } + bool improveThisTypesForCall(); MDefinition* getCallee(); MDefinition* getAliasedVar(ScopeCoordinate sc); @@ -805,10 +806,20 @@ class IonBuilder // SIMD intrinsics and natives. InliningStatus inlineConstructSimdObject(CallInfo& callInfo, SimdTypeDescr* target); - InliningStatus inlineSimdInt32x4BinaryArith(CallInfo& callInfo, JSNative native, - MSimdBinaryArith::Operation op); - InliningStatus inlineSimdInt32x4BinaryBitwise(CallInfo& callInfo, JSNative native, - MSimdBinaryBitwise::Operation op); + + // helpers + bool checkInlineSimd(CallInfo &callInfo, JSNative native, SimdTypeDescr::Type type, + unsigned numArgs, InlineTypedObject **templateObj); + IonBuilder::InliningStatus boxSimd(CallInfo &callInfo, MInstruction *ins, + InlineTypedObject *templateObj); + + template + InliningStatus inlineBinarySimd(CallInfo &callInfo, JSNative native, + typename T::Operation op, SimdTypeDescr::Type type); + InliningStatus inlineUnarySimd(CallInfo &callInfo, JSNative native, + MSimdUnaryArith::Operation op, SimdTypeDescr::Type type); + InliningStatus inlineSimdConvert(CallInfo &callInfo, JSNative native, bool isCast, + SimdTypeDescr::Type from, SimdTypeDescr::Type to); // Utility intrinsics. InliningStatus inlineIsCallable(CallInfo& callInfo); diff --git a/js/src/jit/JitcodeMap.cpp b/js/src/jit/JitcodeMap.cpp index 1f685208ba..b25bb246ad 100644 --- a/js/src/jit/JitcodeMap.cpp +++ b/js/src/jit/JitcodeMap.cpp @@ -20,19 +20,28 @@ namespace js { namespace jit { +static inline JitcodeRegionEntry +RegionAtAddr(const JitcodeGlobalEntry::IonEntry &entry, void *ptr, + uint32_t *ptrOffset) +{ + MOZ_ASSERT(entry.containsPointer(ptr)); + *ptrOffset = reinterpret_cast(ptr) - + reinterpret_cast(entry.nativeStartAddr()); + + uint32_t regionIdx = entry.regionTable()->findRegionEntry(*ptrOffset); + MOZ_ASSERT(regionIdx < entry.regionTable()->numRegions()); + + return entry.regionTable()->regionEntry(regionIdx); +} + + bool JitcodeGlobalEntry::IonEntry::callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results, uint32_t* depth) const { - MOZ_ASSERT(containsPointer(ptr)); - uint32_t ptrOffset = reinterpret_cast(ptr) - - reinterpret_cast(nativeStartAddr()); - - uint32_t regionIdx = regionTable()->findRegionEntry(ptrOffset); - MOZ_ASSERT(regionIdx < regionTable()->numRegions()); - - JitcodeRegionEntry region = regionTable()->regionEntry(regionIdx); + uint32_t ptrOffset; + JitcodeRegionEntry region = RegionAtAddr(*this, ptr, &ptrOffset); *depth = region.scriptDepth(); JitcodeRegionEntry::ScriptPcIterator locationIter = region.scriptPcIterator(); @@ -61,15 +70,10 @@ JitcodeGlobalEntry::IonEntry::callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const { - MOZ_ASSERT(containsPointer(ptr)); MOZ_ASSERT(maxResults >= 1); - uint32_t ptrOffset = reinterpret_cast(ptr) - - reinterpret_cast(nativeStartAddr()); - - uint32_t regionIdx = regionTable()->findRegionEntry(ptrOffset); MOZ_ASSERT(regionIdx < regionTable()->numRegions()); - - JitcodeRegionEntry region = regionTable()->regionEntry(regionIdx); + uint32_t ptrOffset; + JitcodeRegionEntry region = RegionAtAddr(*this, ptr, &ptrOffset); JitcodeRegionEntry::ScriptPcIterator locationIter = region.scriptPcIterator(); MOZ_ASSERT(locationIter.hasMore()); @@ -88,6 +92,23 @@ JitcodeGlobalEntry::IonEntry::callStackAtAddr(JSRuntime* rt, void* ptr, return count; } +void +JitcodeGlobalEntry::IonEntry::youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr, + JSScript **script, jsbytecode **pc) const +{ + uint32_t ptrOffset; + JitcodeRegionEntry region = RegionAtAddr(*this, ptr, &ptrOffset); + + JitcodeRegionEntry::ScriptPcIterator locationIter = region.scriptPcIterator(); + MOZ_ASSERT(locationIter.hasMore()); + uint32_t scriptIdx, pcOffset; + locationIter.readNext(&scriptIdx, &pcOffset); + pcOffset = region.findPcOffset(ptrOffset, pcOffset); + + *script = getScript(scriptIdx); + *pc = (*script)->offsetToPC(pcOffset); +} + void JitcodeGlobalEntry::IonEntry::destroy() { @@ -155,6 +176,16 @@ JitcodeGlobalEntry::BaselineEntry::callStackAtAddr(JSRuntime* rt, void* ptr, return 1; } +void +JitcodeGlobalEntry::BaselineEntry::youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr, + JSScript **script, + jsbytecode **pc) const +{ + uint8_t *addr = reinterpret_cast(ptr); + *script = script_; + *pc = script_->baselineScript()->approximatePcForNativeAddress(script_, addr); +} + void JitcodeGlobalEntry::BaselineEntry::destroy() { @@ -164,19 +195,26 @@ JitcodeGlobalEntry::BaselineEntry::destroy() str_ = nullptr; } +static inline void +RejoinEntry(JSRuntime *rt, const JitcodeGlobalEntry::IonCacheEntry &cache, + void *ptr, JitcodeGlobalEntry *entry) +{ + MOZ_ASSERT(cache.containsPointer(ptr)); + + // There must exist an entry for the rejoin addr if this entry exists. + JitRuntime *jitrt = rt->jitRuntime(); + jitrt->getJitcodeGlobalTable()->lookupInfallible(cache.rejoinAddr(), entry, rt); + MOZ_ASSERT(entry->isIon()); +} + bool JitcodeGlobalEntry::IonCacheEntry::callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results, uint32_t* depth) const { - MOZ_ASSERT(containsPointer(ptr)); - - // There must exist an entry for the rejoin addr if this entry exists. - JitRuntime* jitrt = rt->jitRuntime(); JitcodeGlobalEntry entry; - jitrt->getJitcodeGlobalTable()->lookupInfallible(rejoinAddr(), &entry, rt); - MOZ_ASSERT(entry.isIon()); + RejoinEntry(rt, *this, ptr, &entry); return entry.callStackAtAddr(rt, rejoinAddr(), results, depth); } @@ -185,17 +223,21 @@ JitcodeGlobalEntry::IonCacheEntry::callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const { - MOZ_ASSERT(containsPointer(ptr)); - - // There must exist an entry for the rejoin addr if this entry exists. - JitRuntime* jitrt = rt->jitRuntime(); JitcodeGlobalEntry entry; - jitrt->getJitcodeGlobalTable()->lookupInfallible(rejoinAddr(), &entry, rt); - MOZ_ASSERT(entry.isIon()); + RejoinEntry(rt, *this, ptr, &entry); return entry.callStackAtAddr(rt, rejoinAddr(), results, maxResults); } +void +JitcodeGlobalEntry::IonCacheEntry::youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr, + JSScript **script, + jsbytecode **pc) const +{ + JitcodeGlobalEntry entry; + RejoinEntry(rt, *this, ptr, &entry); + return entry.youngestFrameLocationAtAddr(rt, ptr, script, pc); +} static int ComparePointers(const void* a, const void* b) { const uint8_t* a_ptr = reinterpret_cast(a); diff --git a/js/src/jit/JitcodeMap.h b/js/src/jit/JitcodeMap.h index 38979293ad..206fe36bc7 100644 --- a/js/src/jit/JitcodeMap.h +++ b/js/src/jit/JitcodeMap.h @@ -210,10 +210,23 @@ class JitcodeGlobalEntry uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const; + void youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr, + JSScript **script, jsbytecode **pc) const; + bool hasTrackedOptimizations() const { return !!optsRegionTable_; } + const IonTrackedOptimizationsRegionTable *trackedOptimizationsRegionTable() const { + MOZ_ASSERT(hasTrackedOptimizations()); + return optsRegionTable_; + } + + uint8_t numOptimizationAttempts() const { + MOZ_ASSERT(hasTrackedOptimizations()); + return optsAttemptsTable_->numEntries(); + } + IonTrackedOptimizationsAttempts trackedOptimizationAttempts(uint8_t index) { MOZ_ASSERT(hasTrackedOptimizations()); return optsAttemptsTable_->entry(index); @@ -278,6 +291,9 @@ class JitcodeGlobalEntry uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const; + + void youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr, + JSScript **script, jsbytecode **pc) const; }; struct IonCacheEntry : public BaseEntry @@ -302,6 +318,9 @@ class JitcodeGlobalEntry uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results, uint32_t maxResults) const; + + void youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr, + JSScript **script, jsbytecode **pc) const; }; // Dummy entries are created for jitcode generated when profiling is not turned on, @@ -326,6 +345,13 @@ class JitcodeGlobalEntry { return 0; } + + void youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr, + JSScript **script, jsbytecode **pc) const + { + *script = nullptr; + *pc = nullptr; + } }; // QueryEntry is never stored in the table, just used for queries @@ -551,6 +577,24 @@ class JitcodeGlobalEntry return false; } + + void youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr, + JSScript **script, jsbytecode **pc) const + { + switch (kind()) { + case Ion: + return ionEntry().youngestFrameLocationAtAddr(rt, ptr, script, pc); + case Baseline: + return baselineEntry().youngestFrameLocationAtAddr(rt, ptr, script, pc); + case IonCache: + return ionCacheEntry().youngestFrameLocationAtAddr(rt, ptr, script, pc); + case Dummy: + return dummyEntry().youngestFrameLocationAtAddr(rt, ptr, script, pc); + default: + MOZ_CRASH("Invalid JitcodeGlobalEntry kind."); + } + } + // Figure out the number of the (JSScript*, jsbytecode*) pairs that are active // at this location. uint32_t lookupInlineCallDepth(void* ptr); diff --git a/js/src/jit/LIR-Common.h b/js/src/jit/LIR-Common.h index 68f5f5b6b8..8a74cd5406 100644 --- a/js/src/jit/LIR-Common.h +++ b/js/src/jit/LIR-Common.h @@ -520,6 +520,9 @@ class LSimdBinaryBitwiseX4 : public LInstructionHelper<1, 2, 0> MSimdBinaryBitwise::Operation operation() const { return mir_->toSimdBinaryBitwise()->operation(); } + const char *extraName() const { + return MSimdBinaryBitwise::OperationName(operation()); + } MIRType type() const { return mir_->type(); } diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index cfe74e86ed..ea3692cd9b 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -255,17 +255,57 @@ IonBuilder::inlineNativeCall(CallInfo& callInfo, JSFunction* target) return inlineBoundFunction(callInfo, target); // Simd functions -#define INLINE_INT32X4_SIMD_ARITH_(OP) \ - if (native == js::simd_int32x4_##OP) \ - return inlineSimdInt32x4BinaryArith(callInfo, native, MSimdBinaryArith::Op_##OP); +#define INLINE_INT32X4_SIMD_ARITH_(OP) \ + if (native == js::simd_int32x4_##OP) \ + return inlineBinarySimd(callInfo, native, MSimdBinaryArith::Op_##OP, \ + SimdTypeDescr::TYPE_INT32); ARITH_COMMONX4_SIMD_OP(INLINE_INT32X4_SIMD_ARITH_) #undef INLINE_INT32X4_SIMD_ARITH_ -#define INLINE_INT32X4_SIMD_BITWISE_(OP) \ - if (native == js::simd_int32x4_##OP) \ - return inlineSimdInt32x4BinaryBitwise(callInfo, native, MSimdBinaryBitwise::OP##_); - BITWISE_COMMONX4_SIMD_OP(INLINE_INT32X4_SIMD_BITWISE_) -#undef INLINE_INT32X4_SIMD_BITWISE_ +#define INLINE_FLOAT32X4_SIMD_ARITH_(OP) \ + if (native == js::simd_float32x4_##OP) \ + return inlineBinarySimd(callInfo, native, MSimdBinaryArith::Op_##OP, \ + SimdTypeDescr::TYPE_FLOAT32); + ARITH_FLOAT32X4_SIMD_OP(INLINE_FLOAT32X4_SIMD_ARITH_) +#undef INLINE_FLOAT32X4_SIMD_ARITH_ + +#define INLINE_SIMD_BITWISE_(OP) \ + if (native == js::simd_int32x4_##OP) \ + return inlineBinarySimd(callInfo, native, MSimdBinaryBitwise::OP##_, \ + SimdTypeDescr::TYPE_INT32); \ + if (native == js::simd_float32x4_##OP) \ + return inlineBinarySimd(callInfo, native, MSimdBinaryBitwise::OP##_, \ + SimdTypeDescr::TYPE_FLOAT32); + BITWISE_COMMONX4_SIMD_OP(INLINE_SIMD_BITWISE_) +#undef INLINE_SIMD_BITWISE_ + + if (native == js::simd_int32x4_not) + return inlineUnarySimd(callInfo, native, MSimdUnaryArith::not_, SimdTypeDescr::TYPE_INT32); + if (native == js::simd_int32x4_neg) + return inlineUnarySimd(callInfo, native, MSimdUnaryArith::neg, SimdTypeDescr::TYPE_INT32); + + if (native == js::simd_float32x4_not) + return inlineUnarySimd(callInfo, native, MSimdUnaryArith::not_, SimdTypeDescr::TYPE_FLOAT32); + if (native == js::simd_float32x4_neg) + return inlineUnarySimd(callInfo, native, MSimdUnaryArith::neg, SimdTypeDescr::TYPE_FLOAT32); + if (native == js::simd_float32x4_abs) + return inlineUnarySimd(callInfo, native, MSimdUnaryArith::abs, SimdTypeDescr::TYPE_FLOAT32); + if (native == js::simd_float32x4_sqrt) + return inlineUnarySimd(callInfo, native, MSimdUnaryArith::sqrt, SimdTypeDescr::TYPE_FLOAT32); + if (native == js::simd_float32x4_reciprocal) + return inlineUnarySimd(callInfo, native, MSimdUnaryArith::reciprocal, SimdTypeDescr::TYPE_FLOAT32); + if (native == js::simd_float32x4_reciprocalSqrt) + return inlineUnarySimd(callInfo, native, MSimdUnaryArith::reciprocalSqrt, SimdTypeDescr::TYPE_FLOAT32); + + typedef bool IsCast; + if (native == js::simd_float32x4_fromInt32x4) + return inlineSimdConvert(callInfo, native, IsCast(false), SimdTypeDescr::TYPE_INT32, SimdTypeDescr::TYPE_FLOAT32); + if (native == js::simd_int32x4_fromFloat32x4) + return inlineSimdConvert(callInfo, native, IsCast(false), SimdTypeDescr::TYPE_FLOAT32, SimdTypeDescr::TYPE_INT32); + if (native == js::simd_float32x4_fromInt32x4Bits) + return inlineSimdConvert(callInfo, native, IsCast(true), SimdTypeDescr::TYPE_INT32, SimdTypeDescr::TYPE_FLOAT32); + if (native == js::simd_int32x4_fromFloat32x4Bits) + return inlineSimdConvert(callInfo, native, IsCast(true), SimdTypeDescr::TYPE_FLOAT32, SimdTypeDescr::TYPE_INT32); return InliningStatus_NotInlined; } @@ -2880,30 +2920,39 @@ IonBuilder::inlineConstructSimdObject(CallInfo& callInfo, SimdTypeDescr* descr) return InliningStatus_Inlined; } -IonBuilder::InliningStatus -IonBuilder::inlineSimdInt32x4BinaryArith(CallInfo& callInfo, JSNative native, - MSimdBinaryArith::Operation op) +static MIRType +SimdTypeDescrToMIRType(SimdTypeDescr::Type type) { - if (callInfo.argc() != 2) - return InliningStatus_NotInlined; + switch (type) { + case SimdTypeDescr::TYPE_FLOAT32: return MIRType_Float32x4; + case SimdTypeDescr::TYPE_INT32: return MIRType_Int32x4; + case SimdTypeDescr::TYPE_FLOAT64: break; + } + MOZ_CRASH("unexpected SimdTypeDescr"); +} + +bool +IonBuilder::checkInlineSimd(CallInfo &callInfo, JSNative native, SimdTypeDescr::Type type, + unsigned numArgs, InlineTypedObject **templateObj) +{ + if (callInfo.argc() != numArgs) + return false; JSObject* templateObject = inspector->getTemplateObjectForNative(pc, native); if (!templateObject) - return InliningStatus_NotInlined; + return false; InlineTypedObject* inlineTypedObject = &templateObject->as(); - MOZ_ASSERT(inlineTypedObject->typeDescr().as().type() == js::Int32x4::type); - - // If the type of any of the arguments is neither a SIMD type, an Object - // type, or a Value, then the applyTypes phase will add a fallible box & - // unbox sequence. This does not matter much as the binary arithmetic - // instruction is supposed to produce a TypeError once it is called. - MSimdBinaryArith* ins = MSimdBinaryArith::New(alloc(), callInfo.getArg(0), callInfo.getArg(1), - op, MIRType_Int32x4); - - MSimdBox* obj = MSimdBox::New(alloc(), constraints(), ins, inlineTypedObject, - inlineTypedObject->group()->initialHeap(constraints())); + MOZ_ASSERT(inlineTypedObject->typeDescr().as().type() == type); + *templateObj = inlineTypedObject; + return true; +} +IonBuilder::InliningStatus +IonBuilder::boxSimd(CallInfo &callInfo, MInstruction *ins, InlineTypedObject *templateObj) +{ + MSimdBox *obj = MSimdBox::New(alloc(), constraints(), ins, templateObj, + templateObj->group()->initialHeap(constraints())); current->add(ins); current->add(obj); current->push(obj); @@ -2912,36 +2961,57 @@ IonBuilder::inlineSimdInt32x4BinaryArith(CallInfo& callInfo, JSNative native, return InliningStatus_Inlined; } +template IonBuilder::InliningStatus -IonBuilder::inlineSimdInt32x4BinaryBitwise(CallInfo& callInfo, JSNative native, - MSimdBinaryBitwise::Operation op) +IonBuilder::inlineBinarySimd(CallInfo &callInfo, JSNative native, typename T::Operation op, + SimdTypeDescr::Type type) { - if (callInfo.argc() != 2) + InlineTypedObject *templateObj = nullptr; + if (!checkInlineSimd(callInfo, native, type, 2, &templateObj)) return InliningStatus_NotInlined; - JSObject* templateObject = inspector->getTemplateObjectForNative(pc, native); - if (!templateObject) - return InliningStatus_NotInlined; - - InlineTypedObject* inlineTypedObject = &templateObject->as(); - MOZ_ASSERT(inlineTypedObject->typeDescr().as().type() == js::Int32x4::type); - // If the type of any of the arguments is neither a SIMD type, an Object // type, or a Value, then the applyTypes phase will add a fallible box & - // unbox sequence. This does not matter much as the binary bitwise - // instruction is supposed to produce a TypeError once it is called. - MSimdBinaryBitwise* ins = MSimdBinaryBitwise::New(alloc(), callInfo.getArg(0), callInfo.getArg(1), - op, MIRType_Int32x4); + // unbox sequence. This does not matter much as all binary SIMD + // instructions are supposed to produce a TypeError when they're called + // with non SIMD-arguments. + MIRType mirType = SimdTypeDescrToMIRType(type); + T *ins = T::New(alloc(), callInfo.getArg(0), callInfo.getArg(1), op, mirType); + return boxSimd(callInfo, ins, templateObj); +} - MSimdBox* obj = MSimdBox::New(alloc(), constraints(), ins, inlineTypedObject, - inlineTypedObject->group()->initialHeap(constraints())); +IonBuilder::InliningStatus +IonBuilder::inlineUnarySimd(CallInfo &callInfo, JSNative native, MSimdUnaryArith::Operation op, + SimdTypeDescr::Type type) +{ + InlineTypedObject *templateObj = nullptr; + if (!checkInlineSimd(callInfo, native, type, 1, &templateObj)) + return InliningStatus_NotInlined; - current->add(ins); - current->add(obj); - current->push(obj); + // See comment in inlineBinarySimd + MIRType mirType = SimdTypeDescrToMIRType(type); + MSimdUnaryArith *ins = MSimdUnaryArith::New(alloc(), callInfo.getArg(0), op, mirType); + return boxSimd(callInfo, ins, templateObj); +} - callInfo.setImplicitlyUsedUnchecked(); - return InliningStatus_Inlined; +IonBuilder::InliningStatus +IonBuilder::inlineSimdConvert(CallInfo &callInfo, JSNative native, bool isCast, + SimdTypeDescr::Type from, SimdTypeDescr::Type to) +{ + InlineTypedObject *templateObj = nullptr; + if (!checkInlineSimd(callInfo, native, to, 1, &templateObj)) + return InliningStatus_NotInlined; + + // See comment in inlineBinarySimd + MInstruction *ins; + MIRType fromType = SimdTypeDescrToMIRType(from); + MIRType toType = SimdTypeDescrToMIRType(to); + if (isCast) + ins = MSimdReinterpretCast::New(alloc(), callInfo.getArg(0), fromType, toType); + else + ins = MSimdConvert::New(alloc(), callInfo.getArg(0), fromType, toType); + + return boxSimd(callInfo, ins, templateObj); } } // namespace jit diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index dca2234b64..9837fa1c6f 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -929,6 +929,31 @@ MSimdSwizzle::foldsTo(TempAllocator& alloc) return this; } + +template +static void +PrintOpcodeOperation(T *mir, FILE *fp) +{ + mir->MDefinition::printOpcode(fp); + fprintf(fp, " (%s)", T::OperationName(mir->operation())); +} + +void +MSimdBinaryArith::printOpcode(FILE *fp) const +{ + PrintOpcodeOperation(this, fp); +} +void +MSimdBinaryBitwise::printOpcode(FILE *fp) const +{ + PrintOpcodeOperation(this, fp); +} +void +MSimdUnaryArith::printOpcode(FILE *fp) const +{ + PrintOpcodeOperation(this, fp); +} + MCloneLiteral* MCloneLiteral::New(TempAllocator& alloc, MDefinition* obj) { @@ -1316,7 +1341,7 @@ MFloor::trySpecializeFloat32(TempAllocator& alloc) { MOZ_ASSERT(type() == MIRType_Int32); if (EnsureFloatInputOrConvert(this, alloc)) - setPolicyType(MIRType_Float32); + specialization_ = MIRType_Float32; } void @@ -1324,7 +1349,7 @@ MCeil::trySpecializeFloat32(TempAllocator& alloc) { MOZ_ASSERT(type() == MIRType_Int32); if (EnsureFloatInputOrConvert(this, alloc)) - setPolicyType(MIRType_Float32); + specialization_ = MIRType_Float32; } void @@ -1332,7 +1357,7 @@ MRound::trySpecializeFloat32(TempAllocator& alloc) { MOZ_ASSERT(type() == MIRType_Int32); if (EnsureFloatInputOrConvert(this, alloc)) - setPolicyType(MIRType_Float32); + specialization_ = MIRType_Float32; } MCompare* @@ -2290,7 +2315,7 @@ MMathFunction::trySpecializeFloat32(TempAllocator& alloc) } setResultType(MIRType_Float32); - setPolicyType(MIRType_Float32); + specialization_ = MIRType_Float32; } MHypot* MHypot::New(TempAllocator& alloc, const MDefinitionVector & vector) @@ -2956,10 +2981,10 @@ MUrsh::NewAsmJS(TempAllocator& alloc, MDefinition* left, MDefinition* right) } MResumePoint* -MResumePoint::New(TempAllocator& alloc, MBasicBlock* block, jsbytecode* pc, MResumePoint* parent, +MResumePoint::New(TempAllocator& alloc, MBasicBlock* block, jsbytecode* pc, Mode mode) { - MResumePoint* resume = new(alloc) MResumePoint(block, pc, parent, mode); + MResumePoint* resume = new(alloc) MResumePoint(block, pc, mode); if (!resume->init(alloc)) return nullptr; resume->inherit(block); @@ -2970,7 +2995,7 @@ MResumePoint* MResumePoint::New(TempAllocator& alloc, MBasicBlock* block, MResumePoint* model, const MDefinitionVector& operands) { - MResumePoint* resume = new(alloc) MResumePoint(block, model->pc(), model->caller(), model->mode()); + MResumePoint* resume = new(alloc) MResumePoint(block, model->pc(), model->mode()); // Allocate the same number of operands as the original resume point, and // copy operands from the operands vector and not the not from the current @@ -2989,7 +3014,7 @@ MResumePoint* MResumePoint::Copy(TempAllocator& alloc, MResumePoint* src) { MResumePoint* resume = new(alloc) MResumePoint(src->block(), src->pc(), - src->caller(), src->mode()); + src->mode()); // Copy the operands from the original resume point, and not from the // current block stack. if (!resume->operands_.init(alloc, src->numAllocatedOperands())) @@ -3001,11 +3026,9 @@ MResumePoint::Copy(TempAllocator& alloc, MResumePoint* src) return resume; } -MResumePoint::MResumePoint(MBasicBlock* block, jsbytecode* pc, MResumePoint* caller, - Mode mode) +MResumePoint::MResumePoint(MBasicBlock* block, jsbytecode* pc, Mode mode) : MNode(block), pc_(pc), - caller_(caller), instruction_(nullptr), mode_(mode) { @@ -3018,6 +3041,12 @@ MResumePoint::init(TempAllocator& alloc) return operands_.init(alloc, block()->stackDepth()); } +MResumePoint* +MResumePoint::caller() const +{ + return block_->callerResumePoint(); +} + void MResumePoint::inherit(MBasicBlock* block) { @@ -4235,7 +4264,7 @@ MSqrt::trySpecializeFloat32(TempAllocator& alloc) { } setResultType(MIRType_Float32); - setPolicyType(MIRType_Float32); + specialization_ = MIRType_Float32; } MDefinition* diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index b07fcc72e6..90093c8e2e 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -246,6 +246,7 @@ class MNode : public TempObject MBasicBlock* block() const { return block_; } + MBasicBlock *caller() const; // Sets an already set operand, updating use information. If you're looking // for setOperand, this is probably what you want. @@ -1497,20 +1498,27 @@ class MSimdConstant // Converts all lanes of a given vector into the type of another vector class MSimdConvert : public MUnaryInstruction, - public NoTypePolicy::Data + public SimdPolicy<0>::Data { MSimdConvert(MDefinition* obj, MIRType fromType, MIRType toType) : MUnaryInstruction(obj) { - MOZ_ASSERT(IsSimdType(obj->type()) && fromType == obj->type()); MOZ_ASSERT(IsSimdType(toType)); setResultType(toType); + specialization_ = fromType; // expects fromType as input } public: INSTRUCTION_HEADER(SimdConvert) static MSimdConvert* NewAsmJS(TempAllocator& alloc, MDefinition* obj, MIRType fromType, MIRType toType) + { + MOZ_ASSERT(IsSimdType(obj->type()) && fromType == obj->type()); + return new(alloc) MSimdConvert(obj, fromType, toType); + } + + static MSimdConvert *New(TempAllocator &alloc, MDefinition *obj, MIRType fromType, + MIRType toType) { return new(alloc) MSimdConvert(obj, fromType, toType); } @@ -1527,20 +1535,27 @@ class MSimdConvert // Casts bits of a vector input to another SIMD type (doesn't generate code). class MSimdReinterpretCast : public MUnaryInstruction, - public NoTypePolicy::Data + public SimdPolicy<0>::Data { MSimdReinterpretCast(MDefinition* obj, MIRType fromType, MIRType toType) : MUnaryInstruction(obj) { - MOZ_ASSERT(IsSimdType(obj->type()) && fromType == obj->type()); MOZ_ASSERT(IsSimdType(toType)); setResultType(toType); + specialization_ = fromType; // expects fromType as input } public: INSTRUCTION_HEADER(SimdReinterpretCast) static MSimdReinterpretCast* NewAsmJS(TempAllocator& alloc, MDefinition* obj, MIRType fromType, MIRType toType) + { + MOZ_ASSERT(IsSimdType(obj->type()) && fromType == obj->type()); + return new(alloc) MSimdReinterpretCast(obj, fromType, toType); + } + + static MSimdReinterpretCast* New(TempAllocator &alloc, MDefinition *obj, MIRType fromType, + MIRType toType) { return new(alloc) MSimdReinterpretCast(obj, fromType, toType); } @@ -1828,7 +1843,7 @@ class MSimdShuffle class MSimdUnaryArith : public MUnaryInstruction, - public NoTypePolicy::Data + public SimdSameAsReturnedTypePolicy<0>::Data { public: enum Operation { @@ -1840,14 +1855,24 @@ class MSimdUnaryArith sqrt }; + static const char* OperationName(Operation op) { + switch (op) { + case abs: return "abs"; + case neg: return "neg"; + case not_: return "not"; + case reciprocal: return "reciprocal"; + case reciprocalSqrt: return "reciprocalSqrt"; + case sqrt: return "sqrt"; + } + MOZ_CRASH("unexpected operation"); + } + private: Operation operation_; MSimdUnaryArith(MDefinition* def, Operation op, MIRType type) : MUnaryInstruction(def), operation_(op) { - MOZ_ASSERT(IsSimdType(type)); - MOZ_ASSERT(def->type() == type); MOZ_ASSERT_IF(type == MIRType_Int32x4, op == neg || op == not_); setResultType(type); setMovable(); @@ -1855,9 +1880,17 @@ class MSimdUnaryArith public: INSTRUCTION_HEADER(SimdUnaryArith) + + static MSimdUnaryArith *New(TempAllocator &alloc, MDefinition *def, Operation op, MIRType t) + { + return new(alloc) MSimdUnaryArith(def, op, t); + } + static MSimdUnaryArith* NewAsmJS(TempAllocator& alloc, MDefinition* def, Operation op, MIRType t) { + MOZ_ASSERT(IsSimdType(t)); + MOZ_ASSERT(def->type() == t); return new(alloc) MSimdUnaryArith(def, op, t); } @@ -1871,6 +1904,8 @@ class MSimdUnaryArith return congruentIfOperandsEqual(ins) && ins->toSimdUnaryArith()->operation() == operation(); } + void printOpcode(FILE *fp) const MOZ_OVERRIDE; + ALLOW_CLONE(MSimdUnaryArith); }; @@ -2022,6 +2057,8 @@ class MSimdBinaryArith return operation_ == ins->toSimdBinaryArith()->operation(); } + void printOpcode(FILE *fp) const MOZ_OVERRIDE; + ALLOW_CLONE(MSimdBinaryArith) }; @@ -2036,6 +2073,15 @@ class MSimdBinaryBitwise xor_ }; + static const char* OperationName(Operation op) { + switch (op) { + case and_: return "and"; + case or_: return "or"; + case xor_: return "xor"; + } + MOZ_CRASH("unexpected operation"); + } + private: Operation operation_; @@ -2076,6 +2122,8 @@ class MSimdBinaryBitwise return operation_ == ins->toSimdBinaryBitwise()->operation(); } + void printOpcode(FILE *fp) const MOZ_OVERRIDE; + ALLOW_CLONE(MSimdBinaryBitwise) }; @@ -5434,7 +5482,7 @@ class MSqrt : MUnaryInstruction(num) { setResultType(type); - setPolicyType(type); + specialization_ = type; setMovable(); } @@ -5715,7 +5763,7 @@ class MMathFunction : MUnaryInstruction(input), function_(function), cache_(cache) { setResultType(MIRType_Double); - setPolicyType(MIRType_Double); + specialization_ = MIRType_Double; setMovable(); } @@ -10858,7 +10906,7 @@ class MFloor : MUnaryInstruction(num) { setResultType(MIRType_Int32); - setPolicyType(MIRType_Double); + specialization_ = MIRType_Double; setMovable(); } @@ -10902,7 +10950,7 @@ class MCeil : MUnaryInstruction(num) { setResultType(MIRType_Int32); - setPolicyType(MIRType_Double); + specialization_ = MIRType_Double; setMovable(); } @@ -10946,7 +10994,7 @@ class MRound : MUnaryInstruction(num) { setResultType(MIRType_Int32); - setPolicyType(MIRType_Double); + specialization_ = MIRType_Double; setMovable(); } @@ -11705,11 +11753,10 @@ class MResumePoint final : MStoresToRecoverList stores_; jsbytecode* pc_; - MResumePoint* caller_; MInstruction* instruction_; Mode mode_; - MResumePoint(MBasicBlock* block, jsbytecode* pc, MResumePoint* parent, Mode mode); + MResumePoint(MBasicBlock* block, jsbytecode* pc, Mode mode); void inherit(MBasicBlock* state); protected: @@ -11731,7 +11778,7 @@ class MResumePoint final : public: static MResumePoint* New(TempAllocator& alloc, MBasicBlock* block, jsbytecode* pc, - MResumePoint* parent, Mode mode); + Mode mode); static MResumePoint* New(TempAllocator& alloc, MBasicBlock* block, MResumePoint* model, const MDefinitionVector& operands); static MResumePoint* Copy(TempAllocator& alloc, MResumePoint* src); @@ -11771,15 +11818,10 @@ class MResumePoint final : jsbytecode* pc() const { return pc_; } - MResumePoint* caller() const { - return caller_; - } - void setCaller(MResumePoint* caller) { - caller_ = caller; - } + MResumePoint *caller() const; uint32_t frameCount() const { uint32_t count = 1; - for (MResumePoint* it = caller_; it; it = it->caller_) + for (MResumePoint* it = caller(); it; it = it->caller()) count++; return count; } diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index 4c5cf8e372..89e7fc721b 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -406,11 +406,10 @@ MBasicBlock::inherit(TempAllocator& alloc, BytecodeAnalysis* analysis, MBasicBlo MOZ_ASSERT(!entryResumePoint_); // Propagate the caller resume point from the inherited block. - MResumePoint* callerResumePoint = pred ? pred->callerResumePoint() : nullptr; + callerResumePoint_ = pred ? pred->callerResumePoint() : nullptr; // Create a resume point using our initial stack state. - entryResumePoint_ = new(alloc) MResumePoint(this, pc(), callerResumePoint, - MResumePoint::ResumeAt); + entryResumePoint_ = new(alloc) MResumePoint(this, pc(), MResumePoint::ResumeAt); if (!entryResumePoint_->init(alloc)) return false; @@ -475,6 +474,8 @@ MBasicBlock::inheritResumePoint(MBasicBlock* pred) MOZ_ASSERT(kind_ != PENDING_LOOP_HEADER); MOZ_ASSERT(pred != nullptr); + callerResumePoint_ = pred->callerResumePoint(); + if (!predecessors_.append(pred)) return false; @@ -495,8 +496,7 @@ MBasicBlock::initEntrySlots(TempAllocator& alloc) discardResumePoint(entryResumePoint_); // Create a resume point using our initial stack state. - entryResumePoint_ = MResumePoint::New(alloc, this, pc(), callerResumePoint(), - MResumePoint::ResumeAt); + entryResumePoint_ = MResumePoint::New(alloc, this, pc(), MResumePoint::ResumeAt); if (!entryResumePoint_) return false; return true; diff --git a/js/src/jit/MIRGraph.h b/js/src/jit/MIRGraph.h index ee8d24fcfc..21f59ea4e5 100644 --- a/js/src/jit/MIRGraph.h +++ b/js/src/jit/MIRGraph.h @@ -57,6 +57,8 @@ class MBasicBlock : public TempObject, public InlineListNode // This block cannot be reached by any means. bool unreachable_; + MResumePoint *callerResumePoint_; + // Pushes a copy of a local variable or argument. void pushVariable(uint32_t slot); @@ -545,11 +547,11 @@ class MBasicBlock : public TempObject, public InlineListNode discardResumePoint(outerResumePoint_); outerResumePoint_ = nullptr; } - MResumePoint* callerResumePoint() { - return entryResumePoint() ? entryResumePoint()->caller() : nullptr; + MResumePoint* callerResumePoint() const { + return callerResumePoint_; } void setCallerResumePoint(MResumePoint* caller) { - entryResumePoint()->setCaller(caller); + callerResumePoint_ = caller; } size_t numEntrySlots() const { return entryResumePoint()->stackDepth(); diff --git a/js/src/jit/OptimizationTracking.cpp b/js/src/jit/OptimizationTracking.cpp index 26f22102ad..e6895eed40 100644 --- a/js/src/jit/OptimizationTracking.cpp +++ b/js/src/jit/OptimizationTracking.cpp @@ -1123,11 +1123,13 @@ IonBuilder::trackInlineSuccessUnchecked(InliningStatus status) JS_PUBLIC_API(void) JS::ForEachTrackedOptimizationAttempt(JSRuntime* rt, void* addr, - ForEachTrackedOptimizationAttemptOp& op) + ForEachTrackedOptimizationAttemptOp &op, + JSScript **scriptOut, jsbytecode **pcOut) { JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable(); JitcodeGlobalEntry entry; table->lookupInfallible(addr, &entry, rt); + entry.youngestFrameLocationAtAddr(rt, addr, scriptOut, pcOut); Maybe index = entry.trackedOptimizationIndexAtAddr(addr); entry.trackedOptimizationAttempts(index.value()).forEach(op); } @@ -1143,11 +1145,11 @@ InterpretedFunctionFilenameAndLineNumber(JSFunction* fun, const char** filename, source = fun->lazyScript()->maybeForwardedScriptSource(); *lineno = fun->lazyScript()->lineno(); } - *filename = source->introducerFilename(); + *filename = source->filename(); } static JSFunction* -InterpretedFunctionFromTrackedType(const IonTrackedTypeWithAddendum& tracked) +FunctionFromTrackedType(const IonTrackedTypeWithAddendum& tracked) { if (tracked.hasConstructor()) return tracked.constructor; @@ -1162,55 +1164,78 @@ InterpretedFunctionFromTrackedType(const IonTrackedTypeWithAddendum& tracked) return ty.group()->maybeInterpretedFunction(); } -// This adapter is needed as the internal API can deal with engine-internal -// data structures directly, while the public API cannot. -class ForEachTypeInfoAdapter : public IonTrackedOptimizationsTypeInfo::ForEachOp +void +IonTrackedOptimizationsTypeInfo::ForEachOpAdapter::readType(const IonTrackedTypeWithAddendum &tracked) { - ForEachTrackedOptimizationTypeInfoOp& op_; + TypeSet::Type ty = tracked.type; - public: - explicit ForEachTypeInfoAdapter(ForEachTrackedOptimizationTypeInfoOp& op) - : op_(op) - { } - - void readType(const IonTrackedTypeWithAddendum& tracked) override { - TypeSet::Type ty = tracked.type; - - if (ty.isPrimitive() || ty.isUnknown() || ty.isAnyObject()) { - op_.readType("primitive", TypeSet::NonObjectTypeString(ty), nullptr, 0); - return; - } - - char buf[512]; - const uint32_t bufsize = mozilla::ArrayLength(buf); - - if (JSFunction* fun = InterpretedFunctionFromTrackedType(tracked)) { - PutEscapedString(buf, bufsize, fun->displayAtom(), 0); - const char* filename; - unsigned lineno; - InterpretedFunctionFilenameAndLineNumber(fun, &filename, &lineno); - op_.readType(tracked.constructor ? "constructor" : "function", buf, filename, lineno); - return; - } - - const char* className = ty.objectKey()->clasp()->name; - JS_snprintf(buf, bufsize, "[object %s]", className); - - if (tracked.hasAllocationSite()) { - JSScript* script = tracked.script; - op_.readType("alloc site", buf, - script->maybeForwardedScriptSource()->introducerFilename(), - PCToLineNumber(script, script->offsetToPC(tracked.offset))); - return; - } - - op_.readType("prototype", buf, nullptr, 0); + if (ty.isPrimitive() || ty.isUnknown() || ty.isAnyObject()) { + op_.readType("primitive", TypeSet::NonObjectTypeString(ty), nullptr, 0); + return; } - void operator()(JS::TrackedTypeSite site, MIRType mirType) override { - op_(site, StringFromMIRType(mirType)); + char buf[512]; + const uint32_t bufsize = mozilla::ArrayLength(buf); + + if (JSFunction *fun = FunctionFromTrackedType(tracked)) { + if (fun->isNative()) { + // + // Print out the absolute address of the function pointer. + // + // Note that this address is not usable without knowing the + // starting address at which our shared library is loaded. Shared + // library information is exposed by the profiler. If this address + // needs to be symbolicated manually (e.g., when it is gotten via + // debug spewing of all optimization information), it needs to be + // converted to an offset from the beginning of the shared library + // for use with utilities like `addr2line` on Linux and `atos` on + // OS X. Converting to an offset may be done via dladdr(): + // + // void *addr = JS_FUNC_TO_DATA_PTR(void *, fun->native()); + // uintptr_t offset; + // Dl_info info; + // if (dladdr(addr, &info) != 0) + // offset = uintptr_t(addr) - uintptr_t(info.dli_fbase); + // + uintptr_t addr = JS_FUNC_TO_DATA_PTR(uintptr_t, fun->native()); + JS_snprintf(buf, bufsize, "%llx", addr); + op_.readType("native", nullptr, buf, UINT32_MAX); + return; + } + + PutEscapedString(buf, bufsize, fun->displayAtom(), 0); + const char *filename; + unsigned lineno; + InterpretedFunctionFilenameAndLineNumber(fun, &filename, &lineno); + op_.readType(tracked.constructor ? "constructor" : "function", buf, filename, lineno); + return; } -}; + + const char *className = ty.objectKey()->clasp()->name; + JS_snprintf(buf, bufsize, "[object %s]", className); + + if (tracked.hasAllocationSite()) { + JSScript *script = tracked.script; + op_.readType("alloc site", buf, + script->maybeForwardedScriptSource()->filename(), + PCToLineNumber(script, script->offsetToPC(tracked.offset))); + return; + } + + if (ty.isGroup()) { + op_.readType("prototype", buf, nullptr, UINT32_MAX); + return; + } + + op_.readType("singleton", buf, nullptr, UINT32_MAX); +} + +void +IonTrackedOptimizationsTypeInfo::ForEachOpAdapter::operator()(JS::TrackedTypeSite site, + MIRType mirType) +{ + op_(site, StringFromMIRType(mirType)); +} JS_PUBLIC_API(void) JS::ForEachTrackedOptimizationTypeInfo(JSRuntime* rt, void* addr, @@ -1219,7 +1244,7 @@ JS::ForEachTrackedOptimizationTypeInfo(JSRuntime* rt, void* addr, JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable(); JitcodeGlobalEntry entry; table->lookupInfallible(addr, &entry, rt); - ForEachTypeInfoAdapter adapter(op); + IonTrackedOptimizationsTypeInfo::ForEachOpAdapter adapter(op); Maybe index = entry.trackedOptimizationIndexAtAddr(addr); entry.trackedOptimizationTypeInfo(index.value()).forEach(adapter, entry.allTrackedTypes()); } diff --git a/js/src/jit/OptimizationTracking.h b/js/src/jit/OptimizationTracking.h index aee805d9ad..de3415234e 100644 --- a/js/src/jit/OptimizationTracking.h +++ b/js/src/jit/OptimizationTracking.h @@ -484,12 +484,27 @@ class IonTrackedOptimizationsTypeInfo // JS::ForEachTrackedOptimizaitonTypeInfoOp cannot be used directly. The // internal API needs to deal with engine-internal data structures (e.g., // TypeSet::Type) directly. + // + // An adapter is provided below. struct ForEachOp { virtual void readType(const IonTrackedTypeWithAddendum& tracked) = 0; virtual void operator()(JS::TrackedTypeSite site, MIRType mirType) = 0; }; + class ForEachOpAdapter : public ForEachOp + { + JS::ForEachTrackedOptimizationTypeInfoOp &op_; + + public: + explicit ForEachOpAdapter(JS::ForEachTrackedOptimizationTypeInfoOp &op) + : op_(op) + { } + + void readType(const IonTrackedTypeWithAddendum &tracked) MOZ_OVERRIDE; + void operator()(JS::TrackedTypeSite site, MIRType mirType) MOZ_OVERRIDE; + }; + void forEach(ForEachOp& op, const IonTrackedTypeVector* allTypes); }; diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 2510d507e6..b4622e2001 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -740,12 +740,10 @@ template bool ObjectPolicy<2>::staticAdjustInputs(TempAllocator& alloc, MInstruc template bool ObjectPolicy<3>::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins); template -bool -SimdSameAsReturnedTypePolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) +static bool +MaybeSimdUnbox(TempAllocator &alloc, MInstruction *ins, MIRType type) { - MIRType type = ins->type(); MOZ_ASSERT(IsSimdType(type)); - MDefinition* in = ins->getOperand(Op); if (in->type() == type) return true; @@ -757,11 +755,29 @@ SimdSameAsReturnedTypePolicy::staticAdjustInputs(TempAllocator& alloc, MInst return replace->typePolicy()->adjustInputs(alloc, replace); } + +template +bool +SimdSameAsReturnedTypePolicy::staticAdjustInputs(TempAllocator &alloc, MInstruction *ins) +{ + return MaybeSimdUnbox(alloc, ins, ins->type()); +} + template bool SimdSameAsReturnedTypePolicy<0>::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins); template bool SimdSameAsReturnedTypePolicy<1>::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins); +template +bool +SimdPolicy::adjustInputs(TempAllocator &alloc, MInstruction *ins) +{ + return MaybeSimdUnbox(alloc, ins, ins->typePolicySpecialization()); +} + +template bool +SimdPolicy<0>::adjustInputs(TempAllocator &alloc, MInstruction *ins); + bool CallPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) { @@ -1097,6 +1113,8 @@ FilterTypeSetPolicy::adjustInputs(TempAllocator& alloc, MInstruction* ins) _(ObjectPolicy<0>) \ _(ObjectPolicy<1>) \ _(ObjectPolicy<3>) \ + _(SimdPolicy<0>) \ + _(SimdSameAsReturnedTypePolicy<0>) \ _(StringPolicy<0>) diff --git a/js/src/jit/TypePolicy.h b/js/src/jit/TypePolicy.h index d2541f29df..00a7777727 100644 --- a/js/src/jit/TypePolicy.h +++ b/js/src/jit/TypePolicy.h @@ -221,24 +221,8 @@ class Float32Policy final : public TypePolicy template class FloatingPointPolicy final : public TypePolicy { - public: - struct PolicyTypeData - { - MIRType policyType_; - - void setPolicyType(MIRType type) { - policyType_ = type; - } - - protected: - MIRType& thisTypeSpecialization() { - return policyType_; - } - }; - - INHERIT_DATA_(PolicyTypeData); - + SPECIALIZATION_DATA_; virtual bool adjustInputs(TempAllocator& alloc, MInstruction* def) override; }; @@ -324,6 +308,14 @@ class SimdScalarPolicy final : public TypePolicy } }; +template +class SimdPolicy MOZ_FINAL : public TypePolicy +{ + public: + SPECIALIZATION_DATA_; + virtual bool adjustInputs(TempAllocator &alloc, MInstruction *ins) MOZ_OVERRIDE; +}; + // SIMD value-type policy, use the returned type of the instruction to determine // how to unbox its operand. template diff --git a/js/src/js.msg b/js/src/js.msg index 9be978fe7f..0916919a56 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -80,7 +80,7 @@ MSG_DEF(JSMSG_BAD_GENERATOR_YIELD, 1, JSEXN_TYPEERR, "yield from closing gen MSG_DEF(JSMSG_EMPTY_ARRAY_REDUCE, 0, JSEXN_TYPEERR, "reduce of empty array with no initial value") MSG_DEF(JSMSG_UNEXPECTED_TYPE, 2, JSEXN_TYPEERR, "{0} is {1}") MSG_DEF(JSMSG_MISSING_FUN_ARG, 2, JSEXN_TYPEERR, "missing argument {0} when calling function {1}") -MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 0, JSEXN_TYPEERR, "value is not a non-null object") +MSG_DEF(JSMSG_NOT_NONNULL_OBJECT, 1, JSEXN_TYPEERR, "{0} is not a non-null object") MSG_DEF(JSMSG_INVALID_DESCRIPTOR, 0, JSEXN_TYPEERR, "property descriptors must not specify a value or be writable when a getter or setter has been specified") MSG_DEF(JSMSG_OBJECT_NOT_EXTENSIBLE, 1, JSEXN_TYPEERR, "{0} is not extensible") MSG_DEF(JSMSG_CANT_REDEFINE_PROP, 1, JSEXN_TYPEERR, "can't redefine non-configurable property '{0}'") diff --git a/js/src/jsapi-tests/testSavedStacks.cpp b/js/src/jsapi-tests/testSavedStacks.cpp index 7af401d31e..41355f8555 100644 --- a/js/src/jsapi-tests/testSavedStacks.cpp +++ b/js/src/jsapi-tests/testSavedStacks.cpp @@ -22,3 +22,45 @@ BEGIN_TEST(testSavedStacks_withNoStack) return true; } END_TEST(testSavedStacks_withNoStack) + +BEGIN_TEST(testSavedStacks_ApiDefaultValues) +{ + js::RootedSavedFrame savedFrame(cx, nullptr); + + // Source + JS::RootedString str(cx); + JS::SavedFrameResult result = JS::GetSavedFrameSource(cx, savedFrame, &str); + CHECK(result == JS::SavedFrameResult::AccessDenied); + CHECK(str.get() == cx->runtime()->emptyString); + + // Line + uint32_t line = 123; + result = JS::GetSavedFrameLine(cx, savedFrame, &line); + CHECK(result == JS::SavedFrameResult::AccessDenied); + CHECK(line == 0); + + // Column + uint32_t column = 123; + result = JS::GetSavedFrameColumn(cx, savedFrame, &column); + CHECK(result == JS::SavedFrameResult::AccessDenied); + CHECK(column == 0); + + // Function display name + result = JS::GetSavedFrameFunctionDisplayName(cx, savedFrame, &str); + CHECK(result == JS::SavedFrameResult::AccessDenied); + CHECK(str.get() == nullptr); + + // Parent + JS::RootedObject parent(cx); + result = JS::GetSavedFrameParent(cx, savedFrame, &parent); + CHECK(result == JS::SavedFrameResult::AccessDenied); + CHECK(parent.get() == nullptr); + + // Stack string + result = JS::GetSavedFrameSource(cx, savedFrame, &str); + CHECK(result == JS::SavedFrameResult::AccessDenied); + CHECK(str.get() == cx->runtime()->emptyString); + + return true; +} +END_TEST(testSavedStacks_ApiDefaultValues) diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 673aebdd9a..4486840ac3 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -2185,7 +2185,7 @@ inline int CheckIsStrictPropertyOp(JSStrictPropertyOp op); #define JS_SELF_HOSTED_GET(name, getterName, flags) \ {name, \ uint8_t(JS_CHECK_ACCESSOR_FLAGS(flags) | JSPROP_SHARED | JSPROP_GETTER), \ - { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo*) }, \ + { { nullptr, JS_CAST_STRING_TO(getterName, const JSJitInfo *) } }, \ JSNATIVE_WRAPPER(nullptr) } #define JS_SELF_HOSTED_GETSET(name, getterName, setterName, flags) \ {name, \ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index a93818bfc5..298ce134fb 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -92,7 +92,11 @@ JSObject* js::NonNullObject(JSContext* cx, const Value& v) { if (v.isPrimitive()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); + RootedValue value(cx, v); + char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, value, NullPtr()); + if (!bytes) + return nullptr; + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes); return nullptr; } return &v.toObject(); @@ -280,7 +284,10 @@ PropDesc::initialize(JSContext* cx, const Value& origval, bool checkAccessors) /* 8.10.5 step 1 */ if (v.isPrimitive()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); + char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, NullPtr()); + if (!bytes) + return false; + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes); return false; } RootedObject desc(cx, &v.toObject()); diff --git a/js/src/jsweakmap.cpp b/js/src/jsweakmap.cpp index 4434dce9eb..61b49d6bd3 100644 --- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -388,7 +388,10 @@ WeakMap_set_impl(JSContext* cx, const CallArgs& args) MOZ_ASSERT(IsWeakMap(args.thisv())); if (!args.get(0).isObject()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); + char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, args.get(0), NullPtr()); + if (!bytes) + return false; + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes); return false; } @@ -569,7 +572,10 @@ WeakMap_construct(JSContext* cx, unsigned argc, Value* vp) // Steps 12k-l. if (isOriginalAdder) { if (keyVal.isPrimitive()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); + char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, keyVal, NullPtr()); + if (!bytes) + return false; + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, bytes); return false; } diff --git a/js/src/perf/jsperf.cpp b/js/src/perf/jsperf.cpp index 4a84eac113..47e5b454ce 100644 --- a/js/src/perf/jsperf.cpp +++ b/js/src/perf/jsperf.cpp @@ -210,7 +210,10 @@ static PerfMeasurement* GetPM(JSContext* cx, JS::HandleValue value, const char* fname) { if (!value.isObject()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, 0, JSMSG_NOT_NONNULL_OBJECT); + char *bytes = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, value, NullPtr()); + if (!bytes) + return nullptr; + JS_ReportErrorNumber(cx, js_GetErrorMessage, 0, JSMSG_NOT_NONNULL_OBJECT, bytes); return nullptr; } RootedObject obj(cx, &value.toObject()); diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 191ab90939..28ed1f4ac3 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -61,9 +61,12 @@ #include "gc/GCInternals.h" #include "jit/arm/Simulator-arm.h" #include "jit/Ion.h" +#include "jit/JitcodeMap.h" +#include "jit/OptimizationTracking.h" #include "js/Debug.h" #include "js/GCAPI.h" #include "js/StructuredClone.h" +#include "js/TrackedOptimizationInfo.h" #include "perf/jsperf.h" #include "shell/jsheaptools.h" #include "shell/jsoptparse.h" @@ -4337,6 +4340,173 @@ SetSharedArrayBuffer(JSContext* cx, unsigned argc, Value* vp) return true; } +class SprintOptimizationTypeInfoOp : public ForEachTrackedOptimizationTypeInfoOp +{ + Sprinter *sp; + bool startedTypes_; + + public: + explicit SprintOptimizationTypeInfoOp(Sprinter *sp) + : sp(sp), + startedTypes_(false) + { } + + void readType(const char *keyedBy, const char *name, + const char *location, unsigned lineno) MOZ_OVERRIDE + { + if (!startedTypes_) { + startedTypes_ = true; + Sprint(sp, "{\"typeset\": ["); + } + Sprint(sp, "{\"keyedBy\":\"%s\"", keyedBy); + if (name) + Sprint(sp, ",\"name\":\"%s\"", name); + if (location) { + char buf[512]; + PutEscapedString(buf, mozilla::ArrayLength(buf), location, strlen(location), '"'); + Sprint(sp, ",\"location\":%s", buf); + } + if (lineno != UINT32_MAX) + Sprint(sp, ",\"line\":%u", lineno); + Sprint(sp, "},"); + } + + void operator()(TrackedTypeSite site, const char *mirType) MOZ_OVERRIDE { + if (startedTypes_) { + // Clear trailing , + if ((*sp)[sp->getOffset() - 1] == ',') + (*sp)[sp->getOffset() - 1] = ' '; + Sprint(sp, "],"); + startedTypes_ = false; + } else { + Sprint(sp, "{"); + } + + Sprint(sp, "\"site\":\"%s\",\"mirType\":\"%s\"},", + TrackedTypeSiteString(site), mirType); + } +}; + +class SprintOptimizationAttemptsOp : public ForEachTrackedOptimizationAttemptOp +{ + Sprinter *sp; + + public: + explicit SprintOptimizationAttemptsOp(Sprinter *sp) + : sp(sp) + { } + + void operator()(TrackedStrategy strategy, TrackedOutcome outcome) MOZ_OVERRIDE { + Sprint(sp, "{\"strategy\":\"%s\",\"outcome\":\"%s\"},", + TrackedStrategyString(strategy), TrackedOutcomeString(outcome)); + } +}; + +static bool +ReflectTrackedOptimizations(JSContext *cx, unsigned argc, Value *vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + RootedObject callee(cx, &args.callee()); + JSRuntime *rt = cx->runtime(); + + if (!rt->hasJitRuntime() || !rt->jitRuntime()->isOptimizationTrackingEnabled(rt)) { + JS_ReportError(cx, "Optimization tracking is off."); + return false; + } + + if (args.length() != 1) { + ReportUsageError(cx, callee, "Wrong number of arguments"); + return false; + } + + if (!args[0].isObject() || !args[0].toObject().is()) { + ReportUsageError(cx, callee, "Argument must be a function"); + return false; + } + + RootedFunction fun(cx, &args[0].toObject().as()); + if (!fun->hasScript() || !fun->nonLazyScript()->hasIonScript()) { + args.rval().setNull(); + return true; + } + + jit::JitcodeGlobalTable *table = rt->jitRuntime()->getJitcodeGlobalTable(); + jit::JitcodeGlobalEntry entry; + jit::IonScript *ion = fun->nonLazyScript()->ionScript(); + table->lookupInfallible(ion->method()->raw(), &entry, rt); + + if (!entry.hasTrackedOptimizations()) { + JSObject *obj = JS_NewPlainObject(cx); + if (!obj) + return false; + args.rval().setObject(*obj); + return true; + } + + Sprinter sp(cx); + if (!sp.init()) + return false; + + const jit::IonTrackedOptimizationsRegionTable *regions = + entry.ionEntry().trackedOptimizationsRegionTable(); + + Sprint(&sp, "{\"regions\": ["); + for (uint32_t i = 0; i < regions->numEntries(); i++) { + jit::IonTrackedOptimizationsRegion region = regions->entry(i); + jit::IonTrackedOptimizationsRegion::RangeIterator iter = region.ranges(); + while (iter.more()) { + uint32_t startOffset, endOffset; + uint8_t index; + iter.readNext(&startOffset, &endOffset, &index); + JSScript *script; + jsbytecode *pc; + // Use endOffset, as startOffset may be associated with a + // previous, adjacent region ending exactly at startOffset. That + // is, suppose we have two regions [0, startOffset], [startOffset, + // endOffset]. Since we are not querying a return address, we want + // the second region and not the first. + uint8_t *addr = ion->method()->raw() + endOffset; + entry.youngestFrameLocationAtAddr(rt, addr, &script, &pc); + Sprint(&sp, "{\"location\":\"%s:%u\",\"offset\":%u,\"index\":%u}%s", + script->filename(), script->lineno(), script->pcToOffset(pc), index, + iter.more() ? "," : ""); + } + } + Sprint(&sp, "],"); + + Sprint(&sp, "\"opts\": ["); + for (uint8_t i = 0; i < entry.ionEntry().numOptimizationAttempts(); i++) { + Sprint(&sp, "%s{\"typeinfo\":[", i == 0 ? "" : ","); + SprintOptimizationTypeInfoOp top(&sp); + jit::IonTrackedOptimizationsTypeInfo::ForEachOpAdapter adapter(top); + entry.trackedOptimizationTypeInfo(i).forEach(adapter, entry.allTrackedTypes()); + // Clear the trailing , + if (sp[sp.getOffset() - 1] == ',') + sp[sp.getOffset() - 1] = ' '; + Sprint(&sp, "],\"attempts\":["); + SprintOptimizationAttemptsOp aop(&sp); + entry.trackedOptimizationAttempts(i).forEach(aop); + // Clear the trailing , + if (sp[sp.getOffset() - 1] == ',') + sp[sp.getOffset() - 1] = ' '; + Sprint(&sp, "]}"); + } + Sprint(&sp, "]}"); + + if (sp.hadOutOfMemory()) + return false; + + RootedString str(cx, JS_NewStringCopyZ(cx, sp.string())); + if (!str) + return false; + RootedValue jsonVal(cx); + if (!JS_ParseJSON(cx, str, &jsonVal)) + return false; + + args.rval().set(jsonVal); + return true; +} + static const JSFunctionSpecWithHelp shell_functions[] = { JS_FN_HELP("version", Version, 0, 0, "version([number])", @@ -4781,6 +4951,12 @@ static const JSFunctionSpecWithHelp fuzzing_unsafe_functions[] = { " Note: This is not fuzzing safe because it can be used to construct\n" " deeply nested wrapper chains that cannot exist in the wild."), + JS_FN_HELP("trackedOpts", ReflectTrackedOptimizations, 1, 0, +"trackedOpts(fun)", +" Returns an object describing the tracked optimizations of |fun|, if\n" +" any. If |fun| is not a scripted function or has not been compiled by\n" +" Ion, null is returned."), + JS_FS_HELP_END }; diff --git a/js/src/tests/ecma_5/Object/object-create-with-primitive-second-arg.js b/js/src/tests/ecma_5/Object/object-create-with-primitive-second-arg.js new file mode 100644 index 0000000000..44afd53c25 --- /dev/null +++ b/js/src/tests/ecma_5/Object/object-create-with-primitive-second-arg.js @@ -0,0 +1,8 @@ +[1, "", true, Symbol(), undefined].forEach(props => { + assertEq(Object.getPrototypeOf(Object.create(null, props)), null); +}); + +assertThrowsInstanceOf(() => Object.create(null, null), TypeError); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/ecma_6/RegExp/flags.js b/js/src/tests/ecma_6/RegExp/flags.js index bf33c577d9..5a0245d991 100644 --- a/js/src/tests/ecma_6/RegExp/flags.js +++ b/js/src/tests/ecma_6/RegExp/flags.js @@ -7,14 +7,16 @@ assertEq(RegExp.prototype.flags, ""); assertEq(/foo/iymg.flags, "gimy"); assertEq(RegExp("").flags, ""); assertEq(RegExp("", "mygi").flags, "gimy"); +// TODO: Uncomment lines 12, 16, 19 and remove lines 11, 15, 18 when bug 1135377 is fixed. assertThrowsInstanceOf(() => RegExp("", "mygui").flags, SyntaxError); -// When the /u flag is supported, uncomment the line below and remove the line above // assertEq(RegExp("", "mygui").flags, "gimuy"); assertEq(genericFlags({}), ""); assertEq(genericFlags({ignoreCase: true}), "i"); -assertEq(genericFlags({sticky:1, unicode:1, global: 0}), "uy"); +assertEq(genericFlags({sticky:1, unicode:1, global: 0}), "y"); +// assertEq(genericFlags({sticky:1, unicode:1, global: 0}), "uy"); assertEq(genericFlags({__proto__: {multiline: true}}), "m"); -assertEq(genericFlags(new Proxy({}, {get(){return true}})), "gimuy"); +assertEq(genericFlags(new Proxy({}, {get(){return true}})), "gimy"); +// assertEq(genericFlags(new Proxy({}, {get(){return true}})), "gimuy"); assertThrowsInstanceOf(() => genericFlags(), TypeError); assertThrowsInstanceOf(() => genericFlags(1), TypeError); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index f5ecec68b8..56ba1c4037 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -123,7 +123,7 @@ GetOrCreateFunctionScript(JSContext* cx, HandleFunction fun) bool js::ReportObjectRequired(JSContext* cx) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, "value"); return false; } diff --git a/js/src/vm/DebuggerMemory.cpp b/js/src/vm/DebuggerMemory.cpp index de4a5fcf57..b6b5fe1896 100644 --- a/js/src/vm/DebuggerMemory.cpp +++ b/js/src/vm/DebuggerMemory.cpp @@ -79,7 +79,7 @@ DebuggerMemory::checkThis(JSContext* cx, CallArgs& args, const char* fnName) const Value& thisValue = args.thisv(); if (!thisValue.isObject()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, InformalValueTypeName(thisValue)); return nullptr; } diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index b75ae0014a..753472cddf 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -341,7 +341,7 @@ SavedFrame::checkThis(JSContext* cx, CallArgs& args, const char* fnName, const Value& thisValue = args.thisv(); if (!thisValue.isObject()) { - JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT); + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT, InformalValueTypeName(thisValue)); return false; } diff --git a/mfbt/Attributes.h b/mfbt/Attributes.h index 4bdae1f537..ffb6b5fe54 100644 --- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -50,6 +50,8 @@ * don't indicate support for them here, due to * http://stackoverflow.com/questions/20498142/visual-studio-2013-explicit-keyword-bug */ +# define MOZ_HAVE_CXX11_FINAL final +# define MOZ_HAVE_CXX11_OVERRIDE # define MOZ_HAVE_NEVER_INLINE __declspec(noinline) # define MOZ_HAVE_NORETURN __declspec(noreturn) # if _MSC_VER >= 1900 @@ -81,6 +83,10 @@ # if __has_extension(cxx_explicit_conversions) # define MOZ_HAVE_EXPLICIT_CONVERSION # endif +# if __has_extension(cxx_override_control) +# define MOZ_HAVE_CXX11_OVERRIDE +# define MOZ_HAVE_CXX11_FINAL final +# endif # if __has_attribute(noinline) # define MOZ_HAVE_NEVER_INLINE __attribute__((noinline)) # endif @@ -89,8 +95,19 @@ # endif #elif defined(__GNUC__) # if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L +# if MOZ_GCC_VERSION_AT_LEAST(4, 7, 0) +# define MOZ_HAVE_CXX11_OVERRIDE +# define MOZ_HAVE_CXX11_FINAL final +# endif +# if MOZ_GCC_VERSION_AT_LEAST(4, 6, 0) # define MOZ_HAVE_CXX11_CONSTEXPR +# endif # define MOZ_HAVE_EXPLICIT_CONVERSION +# else + /* __final is a non-C++11 GCC synonym for 'final', per GCC r176655. */ +# if MOZ_GCC_VERSION_AT_LEAST(4, 7, 0) +# define MOZ_HAVE_CXX11_FINAL __final +# endif # endif # define MOZ_HAVE_NEVER_INLINE __attribute__((noinline)) # define MOZ_HAVE_NORETURN __attribute__((noreturn)) @@ -301,6 +318,116 @@ #ifdef __cplusplus +/* + * MOZ_OVERRIDE explicitly indicates that a virtual member function in a class + * overrides a member function of a base class, rather than potentially being a + * new member function. MOZ_OVERRIDE should be placed immediately before the + * ';' terminating the member function's declaration, or before '= 0;' if the + * member function is pure. If the member function is defined in the class + * definition, it should appear before the opening brace of the function body. + * + * class Base + * { + * public: + * virtual void f() = 0; + * }; + * class Derived1 : public Base + * { + * public: + * virtual void f() MOZ_OVERRIDE; + * }; + * class Derived2 : public Base + * { + * public: + * virtual void f() MOZ_OVERRIDE = 0; + * }; + * class Derived3 : public Base + * { + * public: + * virtual void f() MOZ_OVERRIDE { } + * }; + * + * In compilers supporting C++11 override controls, MOZ_OVERRIDE *requires* that + * the function marked with it override a member function of a base class: it + * is a compile error if it does not. Otherwise MOZ_OVERRIDE does not affect + * semantics and merely documents the override relationship to the reader (but + * of course must still be used correctly to not break C++11 compilers). + */ +#if defined(MOZ_HAVE_CXX11_OVERRIDE) +# define MOZ_OVERRIDE override +#else +# define MOZ_OVERRIDE /* no support */ +#endif + +/* + * MOZ_FINAL indicates that some functionality cannot be overridden through + * inheritance. It can be used to annotate either classes/structs or virtual + * member functions. + * + * To annotate a class/struct with MOZ_FINAL, place MOZ_FINAL immediately after + * the name of the class, before the list of classes from which it derives (if + * any) and before its opening brace. MOZ_FINAL must not be used to annotate + * unnamed classes or structs. (With some compilers, and with C++11 proper, the + * underlying expansion is ambiguous with specifying a class name.) + * + * class Base MOZ_FINAL + * { + * public: + * Base(); + * ~Base(); + * virtual void f() { } + * }; + * // This will be an error in some compilers: + * class Derived : public Base + * { + * public: + * ~Derived() { } + * }; + * + * One particularly common reason to specify MOZ_FINAL upon a class is to tell + * the compiler that it's not dangerous for it to have a non-virtual destructor + * yet have one or more virtual functions, silencing the warning it might emit + * in this case. Suppose Base above weren't annotated with MOZ_FINAL. Because + * ~Base() is non-virtual, an attempt to delete a Derived* through a Base* + * wouldn't call ~Derived(), so any cleanup ~Derived() might do wouldn't happen. + * (Formally C++ says behavior is undefined, but compilers will likely just call + * ~Base() and not ~Derived().) Specifying MOZ_FINAL tells the compiler that + * it's safe for the destructor to be non-virtual. + * + * In compilers implementing final controls, it is an error to inherit from a + * class annotated with MOZ_FINAL. In other compilers it serves only as + * documentation. + * + * To annotate a virtual member function with MOZ_FINAL, place MOZ_FINAL + * immediately before the ';' terminating the member function's declaration, or + * before '= 0;' if the member function is pure. If the member function is + * defined in the class definition, it should appear before the opening brace of + * the function body. (This placement is identical to that for MOZ_OVERRIDE. + * If both are used, they should appear in the order 'MOZ_FINAL MOZ_OVERRIDE' + * for consistency.) + * + * class Base + * { + * public: + * virtual void f() MOZ_FINAL; + * }; + * class Derived + * { + * public: + * // This will be an error in some compilers: + * virtual void f(); + * }; + * + * In compilers implementing final controls, it is an error for a derived class + * to override a method annotated with MOZ_FINAL. In other compilers it serves + * only as documentation. + */ +#if defined(MOZ_HAVE_CXX11_FINAL) +# define MOZ_FINAL MOZ_HAVE_CXX11_FINAL +#else +# define MOZ_FINAL /* no support */ +#endif + /** * MOZ_WARN_UNUSED_RESULT tells the compiler to emit a warning if a function's * return value is not used by the caller. @@ -320,55 +447,6 @@ # define MOZ_WARN_UNUSED_RESULT #endif -/* - * MOZ_OVERRIDE explicitly indicates that a virtual member function in a class - * overrides a member function of a base class, rather than being a completely - * new member function. MOZ_OVERRIDE should be placed immediately before the - * ';' terminating the member function's declaration, or before '= 0;' if the - * member function is pure. If the member function is defined in the class - * definition, it should appear before the opening brace of the function body. - * - * class Base - * { - * public: - * virtual void f() = 0; - * }; - * class Derived1 : public Base - * { - * public: - * virtual void f() MOZ_OVERRIDE; - * }; - * class Derived2 : public Base - * { - * public: - * virtual void f() MOZ_OVERRIDE = 0; - * }; - * class Derived3 : public Base - * { - * public: - * virtual void f() MOZ_OVERRIDE { } - * }; - * - * In compilers supporting C++11 override controls, MOZ_OVERRIDE *requires* that - * the function marked with it override a member function of a base class: it - * is a compile error if it does not. Otherwise MOZ_OVERRIDE does not affect - * semantics and merely documents the override relationship to the reader (but - * of course must still be used correctly to not break C++11 compilers). - */ -#if defined(__clang__) && __clang_major__ >= 3 -# define MOZ_OVERRIDE override -#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)) -# if defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L -# define MOZ_OVERRIDE override -# else -# define MOZ_OVERRIDE /* override */ -# endif -#elif defined(_MSC_VER) && _MSC_VER >= 1400 -# define MOZ_OVERRIDE override -#else -# define MOZ_OVERRIDE /* no override support, or unknown support */ -#endif - /* * The following macros are attributes that support the static analysis plugin * included with Mozilla, and will be implemented (when such support is enabled) diff --git a/tools/profiler/ProfileEntry.cpp b/tools/profiler/ProfileEntry.cpp new file mode 100644 index 0000000000..4ec25b9880 --- /dev/null +++ b/tools/profiler/ProfileEntry.cpp @@ -0,0 +1,622 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include "platform.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" + +// JS +#include "jsapi.h" +#include "js/TrackedOptimizationInfo.h" + +// JSON +#include "JSStreamWriter.h" + +// Self +#include "ProfileEntry.h" + +#if defined(_MSC_VER) && _MSC_VER < 1900 + #define snprintf _snprintf +#endif + +//////////////////////////////////////////////////////////////////////// +// BEGIN ProfileEntry + +ProfileEntry::ProfileEntry() + : mTagData(nullptr) + , mTagName(0) +{ } + +// aTagData must not need release (i.e. be a string from the text segment) +ProfileEntry::ProfileEntry(char aTagName, const char *aTagData) + : mTagData(aTagData) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, ProfilerMarker *aTagMarker) + : mTagMarker(aTagMarker) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, void *aTagPtr) + : mTagPtr(aTagPtr) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, float aTagFloat) + : mTagFloat(aTagFloat) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, uintptr_t aTagOffset) + : mTagOffset(aTagOffset) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, Address aTagAddress) + : mTagAddress(aTagAddress) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, int aTagInt) + : mTagInt(aTagInt) + , mTagName(aTagName) +{ } + +ProfileEntry::ProfileEntry(char aTagName, char aTagChar) + : mTagChar(aTagChar) + , mTagName(aTagName) +{ } + +bool ProfileEntry::is_ent_hint(char hintChar) { + return mTagName == 'h' && mTagChar == hintChar; +} + +bool ProfileEntry::is_ent_hint() { + return mTagName == 'h'; +} + +bool ProfileEntry::is_ent(char tagChar) { + return mTagName == tagChar; +} + +void* ProfileEntry::get_tagPtr() { + // No consistency checking. Oh well. + return mTagPtr; +} + +// END ProfileEntry +//////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////// +// BEGIN ProfileBuffer + +ProfileBuffer::ProfileBuffer(int aEntrySize) + : mEntries(MakeUnique(aEntrySize)) + , mWritePos(0) + , mReadPos(0) + , mEntrySize(aEntrySize) + , mGeneration(0) +{ +} + +// Called from signal, call only reentrant functions +void ProfileBuffer::addTag(const ProfileEntry& aTag) +{ + mEntries[mWritePos++] = aTag; + if (mWritePos == mEntrySize) { + mGeneration++; + mWritePos = 0; + } + if (mWritePos == mReadPos) { + // Keep one slot open. + mEntries[mReadPos] = ProfileEntry(); + mReadPos = (mReadPos + 1) % mEntrySize; + } +} + +void ProfileBuffer::addStoredMarker(ProfilerMarker *aStoredMarker) { + aStoredMarker->SetGeneration(mGeneration); + mStoredMarkers.insert(aStoredMarker); +} + +void ProfileBuffer::deleteExpiredStoredMarkers() { + // Delete markers of samples that have been overwritten due to circular + // buffer wraparound. + int generation = mGeneration; + while (mStoredMarkers.peek() && + mStoredMarkers.peek()->HasExpired(generation)) { + delete mStoredMarkers.popHead(); + } +} + +#define DYNAMIC_MAX_STRING 512 + +char* ProfileBuffer::processDynamicTag(int readPos, + int* tagsConsumed, char* tagBuff) +{ + int readAheadPos = (readPos + 1) % mEntrySize; + int tagBuffPos = 0; + + // Read the string stored in mTagData until the null character is seen + bool seenNullByte = false; + while (readAheadPos != mWritePos && !seenNullByte) { + (*tagsConsumed)++; + ProfileEntry readAheadEntry = mEntries[readAheadPos]; + for (size_t pos = 0; pos < sizeof(void*); pos++) { + tagBuff[tagBuffPos] = readAheadEntry.mTagChars[pos]; + if (tagBuff[tagBuffPos] == '\0' || tagBuffPos == DYNAMIC_MAX_STRING-2) { + seenNullByte = true; + break; + } + tagBuffPos++; + } + if (!seenNullByte) + readAheadPos = (readAheadPos + 1) % mEntrySize; + } + return tagBuff; +} + +void ProfileBuffer::IterateTagsForThread(IterateTagsCallback aCallback, int aThreadId) +{ + MOZ_ASSERT(aCallback); + + int readPos = mReadPos; + int currentThreadID = -1; + + while (readPos != mWritePos) { + const ProfileEntry& entry = mEntries[readPos]; + + if (entry.mTagName == 'T') { + currentThreadID = entry.mTagInt; + readPos = (readPos + 1) % mEntrySize; + continue; + } + + // Number of tags consumed + int incBy = 1; + + // Read ahead to the next tag, if it's a 'd' tag process it now + const char* tagStringData = entry.mTagData; + int readAheadPos = (readPos + 1) % mEntrySize; + char tagBuff[DYNAMIC_MAX_STRING]; + // Make sure the string is always null terminated if it fills up DYNAMIC_MAX_STRING-2 + tagBuff[DYNAMIC_MAX_STRING-1] = '\0'; + + if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') { + tagStringData = processDynamicTag(readPos, &incBy, tagBuff); + } + + if (currentThreadID == aThreadId) { + aCallback(entry, tagStringData); + } + + readPos = (readPos + incBy) % mEntrySize; + } +} + +class StreamOptimizationTypeInfoOp : public JS::ForEachTrackedOptimizationTypeInfoOp +{ + JSStreamWriter& mWriter; + bool mStartedTypeList; + +public: + explicit StreamOptimizationTypeInfoOp(JSStreamWriter& b) + : mWriter(b) + , mStartedTypeList(false) + { } + + void readType(const char *keyedBy, const char *name, + const char *location, unsigned lineno) MOZ_OVERRIDE { + if (!mStartedTypeList) { + mStartedTypeList = true; + mWriter.BeginObject(); + mWriter.Name("types"); + mWriter.BeginArray(); + } + + mWriter.BeginObject(); + mWriter.NameValue("keyedBy", keyedBy); + if (name) { + mWriter.NameValue("name", name); + } + if (location) { + mWriter.NameValue("location", location); + } + if (lineno != UINT32_MAX) { + mWriter.NameValue("line", lineno); + } + mWriter.EndObject(); + } + + void operator()(JS::TrackedTypeSite site, const char *mirType) MOZ_OVERRIDE { + if (mStartedTypeList) { + mWriter.EndArray(); + mStartedTypeList = false; + } else { + mWriter.BeginObject(); + } + + mWriter.NameValue("site", JS::TrackedTypeSiteString(site)); + mWriter.NameValue("mirType", mirType); + mWriter.EndObject(); + } +}; + +class StreamOptimizationAttemptsOp : public JS::ForEachTrackedOptimizationAttemptOp +{ + JSStreamWriter& mWriter; + +public: + explicit StreamOptimizationAttemptsOp(JSStreamWriter& b) + : mWriter(b) + { } + + void operator()(JS::TrackedStrategy strategy, JS::TrackedOutcome outcome) MOZ_OVERRIDE { + mWriter.BeginObject(); + { + // Stringify the reasons for now; could stream enum values in the future + // to save space. + mWriter.NameValue("strategy", JS::TrackedStrategyString(strategy)); + mWriter.NameValue("outcome", JS::TrackedOutcomeString(outcome)); + } + mWriter.EndObject(); + } +}; + +void ProfileBuffer::StreamSamplesToJSObject(JSStreamWriter& b, int aThreadId, JSRuntime* rt) +{ + b.BeginArray(); + + bool sample = false; + int readPos = mReadPos; + int currentThreadID = -1; + while (readPos != mWritePos) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T') { + currentThreadID = entry.mTagInt; + } + if (currentThreadID == aThreadId) { + switch (entry.mTagName) { + case 'r': + { + if (sample) { + b.NameValue("responsiveness", entry.mTagFloat); + } + } + break; + case 'p': + { + if (sample) { + b.NameValue("power", entry.mTagFloat); + } + } + break; + case 'R': + { + if (sample) { + b.NameValue("rss", entry.mTagFloat); + } + } + break; + case 'U': + { + if (sample) { + b.NameValue("uss", entry.mTagFloat); + } + } + break; + case 'f': + { + if (sample) { + b.NameValue("frameNumber", entry.mTagInt); + } + } + break; + case 't': + { + if (sample) { + b.NameValue("time", entry.mTagFloat); + } + } + break; + case 's': + { + // end the previous sample if there was one + if (sample) { + b.EndObject(); + } + // begin the next sample + b.BeginObject(); + + sample = true; + + // Seek forward through the entire sample, looking for frames + // this is an easier approach to reason about than adding more + // control variables and cases to the loop that goes through the buffer once + b.Name("frames"); + b.BeginArray(); + + b.BeginObject(); + b.NameValue("location", "(root)"); + b.EndObject(); + + int framePos = (readPos + 1) % mEntrySize; + ProfileEntry frame = mEntries[framePos]; + while (framePos != mWritePos && frame.mTagName != 's' && frame.mTagName != 'T') { + int incBy = 1; + frame = mEntries[framePos]; + + // Read ahead to the next tag, if it's a 'd' tag process it now + const char* tagStringData = frame.mTagData; + int readAheadPos = (framePos + 1) % mEntrySize; + char tagBuff[DYNAMIC_MAX_STRING]; + // Make sure the string is always null terminated if it fills up + // DYNAMIC_MAX_STRING-2 + tagBuff[DYNAMIC_MAX_STRING-1] = '\0'; + + if (readAheadPos != mWritePos && mEntries[readAheadPos].mTagName == 'd') { + tagStringData = processDynamicTag(framePos, &incBy, tagBuff); + } + + // Write one frame. It can have either + // 1. only location - 'l' containing a memory address + // 2. location and line number - 'c' followed by 'd's, + // an optional 'n' and an optional 'y' + if (frame.mTagName == 'l') { + b.BeginObject(); + // Bug 753041 + // We need a double cast here to tell GCC that we don't want to sign + // extend 32-bit addresses starting with 0xFXXXXXX. + unsigned long long pc = (unsigned long long)(uintptr_t)frame.mTagPtr; + snprintf(tagBuff, DYNAMIC_MAX_STRING, "%#llx", pc); + b.NameValue("location", tagBuff); + b.EndObject(); + } else if (frame.mTagName == 'c') { + b.BeginObject(); + b.NameValue("location", tagStringData); + readAheadPos = (framePos + incBy) % mEntrySize; + if (readAheadPos != mWritePos && + mEntries[readAheadPos].mTagName == 'n') { + b.NameValue("line", mEntries[readAheadPos].mTagInt); + incBy++; + } + readAheadPos = (framePos + incBy) % mEntrySize; + if (readAheadPos != mWritePos && + mEntries[readAheadPos].mTagName == 'y') { + b.NameValue("category", mEntries[readAheadPos].mTagInt); + incBy++; + } + readAheadPos = (framePos + incBy) % mEntrySize; + if (readAheadPos != mWritePos && + mEntries[readAheadPos].mTagName == 'J') { + void* pc = mEntries[readAheadPos].mTagPtr; + + // TODOshu: cannot stream tracked optimization info if + // the JS engine has already shut down when streaming. + if (rt) { + JSScript *optsScript; + jsbytecode *optsPC; + b.Name("opts"); + b.BeginArray(); + StreamOptimizationTypeInfoOp typeInfoOp(b); + JS::ForEachTrackedOptimizationTypeInfo(rt, pc, typeInfoOp); + StreamOptimizationAttemptsOp attemptOp(b); + JS::ForEachTrackedOptimizationAttempt(rt, pc, attemptOp, + &optsScript, &optsPC); + b.EndArray(); + b.NameValue("optsLine", JS_PCToLineNumber(optsScript, optsPC)); + } + } + b.EndObject(); + } + framePos = (framePos + incBy) % mEntrySize; + } + b.EndArray(); + } + break; + } + } + readPos = (readPos + 1) % mEntrySize; + } + if (sample) { + b.EndObject(); + } + b.EndArray(); +} + +void ProfileBuffer::StreamMarkersToJSObject(JSStreamWriter& b, int aThreadId) +{ + b.BeginArray(); + int readPos = mReadPos; + int currentThreadID = -1; + while (readPos != mWritePos) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T') { + currentThreadID = entry.mTagInt; + } else if (currentThreadID == aThreadId && entry.mTagName == 'm') { + entry.getMarker()->StreamJSObject(b); + } + readPos = (readPos + 1) % mEntrySize; + } + b.EndArray(); +} + +int ProfileBuffer::FindLastSampleOfThread(int aThreadId) +{ + // We search backwards from mWritePos-1 to mReadPos. + // Adding mEntrySize makes the result of the modulus positive. + for (int readPos = (mWritePos + mEntrySize - 1) % mEntrySize; + readPos != (mReadPos + mEntrySize - 1) % mEntrySize; + readPos = (readPos + mEntrySize - 1) % mEntrySize) { + ProfileEntry entry = mEntries[readPos]; + if (entry.mTagName == 'T' && entry.mTagInt == aThreadId) { + return readPos; + } + } + + return -1; +} + +void ProfileBuffer::DuplicateLastSample(int aThreadId) +{ + int lastSampleStartPos = FindLastSampleOfThread(aThreadId); + if (lastSampleStartPos == -1) { + return; + } + + MOZ_ASSERT(mEntries[lastSampleStartPos].mTagName == 'T'); + + addTag(mEntries[lastSampleStartPos]); + + // Go through the whole entry and duplicate it, until we find the next one. + for (int readPos = (lastSampleStartPos + 1) % mEntrySize; + readPos != mWritePos; + readPos = (readPos + 1) % mEntrySize) { + switch (mEntries[readPos].mTagName) { + case 'T': + // We're done. + return; + case 't': + // Copy with new time + addTag(ProfileEntry('t', static_cast((mozilla::TimeStamp::Now() - sStartTime).ToMilliseconds()))); + break; + case 'm': + // Don't copy markers + break; + // Copy anything else we don't know about + // L, B, S, c, s, d, l, f, h, r, t, p + default: + addTag(mEntries[readPos]); + break; + } + } +} + +// END ProfileBuffer +//////////////////////////////////////////////////////////////////////// + + +//////////////////////////////////////////////////////////////////////// +// BEGIN ThreadProfile + +ThreadProfile::ThreadProfile(ThreadInfo* aInfo, ProfileBuffer* aBuffer) + : mThreadInfo(aInfo) + , mBuffer(aBuffer) + , mPseudoStack(aInfo->Stack()) + , mMutex("ThreadProfile::mMutex") + , mThreadId(int(aInfo->ThreadId())) + , mIsMainThread(aInfo->IsMainThread()) + , mPlatformData(aInfo->GetPlatformData()) + , mStackTop(aInfo->StackTop()) + , mRespInfo(this) +#ifdef XP_LINUX + , mRssMemory(0) + , mUssMemory(0) +#endif +{ + MOZ_COUNT_CTOR(ThreadProfile); + MOZ_ASSERT(aBuffer); + + // I don't know if we can assert this. But we should warn. + MOZ_ASSERT(aInfo->ThreadId() >= 0, "native thread ID is < 0"); + MOZ_ASSERT(aInfo->ThreadId() <= INT32_MAX, "native thread ID is > INT32_MAX"); +} + +ThreadProfile::~ThreadProfile() +{ + MOZ_COUNT_DTOR(ThreadProfile); +} + +void ThreadProfile::addTag(const ProfileEntry& aTag) +{ + mBuffer->addTag(aTag); +} + +void ThreadProfile::addStoredMarker(ProfilerMarker *aStoredMarker) { + mBuffer->addStoredMarker(aStoredMarker); +} + +void ThreadProfile::IterateTags(IterateTagsCallback aCallback) +{ + mBuffer->IterateTagsForThread(aCallback, mThreadId); +} + +void ThreadProfile::ToStreamAsJSON(std::ostream& stream) +{ + JSStreamWriter b(stream); + StreamJSObject(b); +} + +void ThreadProfile::StreamJSObject(JSStreamWriter& b) +{ + b.BeginObject(); + // Thread meta data + if (XRE_GetProcessType() == GeckoProcessType_Plugin) { + // TODO Add the proper plugin name + b.NameValue("name", "Plugin"); + } else if (XRE_GetProcessType() == GeckoProcessType_Content) { + // This isn't going to really help once we have multiple content + // processes, but it'll do for now. + b.NameValue("name", "Content"); + } else { + b.NameValue("name", Name()); + } + b.NameValue("tid", static_cast(mThreadId)); + + b.Name("samples"); + mBuffer->StreamSamplesToJSObject(b, mThreadId, mPseudoStack->mRuntime); + + b.Name("markers"); + mBuffer->StreamMarkersToJSObject(b, mThreadId); + b.EndObject(); +} + +JSObject* ThreadProfile::ToJSObject(JSContext *aCx) +{ + JS::RootedValue val(aCx); + std::stringstream ss; + { + // Define a scope to prevent a moving GC during ~JSStreamWriter from + // trashing the return value. + JSStreamWriter b(ss); + StreamJSObject(b); + NS_ConvertUTF8toUTF16 js_string(nsDependentCString(ss.str().c_str())); + JS_ParseJSON(aCx, static_cast(js_string.get()), + js_string.Length(), &val); + } + return &val.toObject(); +} + +PseudoStack* ThreadProfile::GetPseudoStack() +{ + return mPseudoStack; +} + +void ThreadProfile::BeginUnwind() +{ + mMutex.Lock(); +} + +void ThreadProfile::EndUnwind() +{ + mMutex.Unlock(); +} + +mozilla::Mutex* ThreadProfile::GetMutex() +{ + return &mMutex; +} + +void ThreadProfile::DuplicateLastSample() +{ + mBuffer->DuplicateLastSample(mThreadId); +} + +// END ThreadProfile +//////////////////////////////////////////////////////////////////////// diff --git a/tools/profiler/ProfileEntry.h b/tools/profiler/ProfileEntry.h new file mode 100644 index 0000000000..c81668ab0e --- /dev/null +++ b/tools/profiler/ProfileEntry.h @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef MOZ_PROFILE_ENTRY_H +#define MOZ_PROFILE_ENTRY_H + +#include +#include "GeckoProfiler.h" +#include "platform.h" +#include "JSStreamWriter.h" +#include "ProfilerBacktrace.h" +#include "nsRefPtr.h" +#include "mozilla/Mutex.h" +#include "gtest/MozGtestFriend.h" +#include "mozilla/UniquePtr.h" + +class ThreadProfile; + +#pragma pack(push, 1) + +class ProfileEntry +{ +public: + ProfileEntry(); + + // aTagData must not need release (i.e. be a string from the text segment) + ProfileEntry(char aTagName, const char *aTagData); + ProfileEntry(char aTagName, void *aTagPtr); + ProfileEntry(char aTagName, ProfilerMarker *aTagMarker); + ProfileEntry(char aTagName, float aTagFloat); + ProfileEntry(char aTagName, uintptr_t aTagOffset); + ProfileEntry(char aTagName, Address aTagAddress); + ProfileEntry(char aTagName, int aTagLine); + ProfileEntry(char aTagName, char aTagChar); + bool is_ent_hint(char hintChar); + bool is_ent_hint(); + bool is_ent(char tagName); + void* get_tagPtr(); + const ProfilerMarker* getMarker() { + MOZ_ASSERT(mTagName == 'm'); + return mTagMarker; + } + + char getTagName() const { return mTagName; } + +private: + FRIEND_TEST(ThreadProfile, InsertOneTag); + FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer); + FRIEND_TEST(ThreadProfile, InsertTagsNoWrap); + FRIEND_TEST(ThreadProfile, InsertTagsWrap); + FRIEND_TEST(ThreadProfile, MemoryMeasure); + friend class ProfileBuffer; + union { + const char* mTagData; + char mTagChars[sizeof(void*)]; + void* mTagPtr; + ProfilerMarker* mTagMarker; + float mTagFloat; + Address mTagAddress; + uintptr_t mTagOffset; + int mTagInt; + char mTagChar; + }; + char mTagName; +}; + +#pragma pack(pop) + +typedef void (*IterateTagsCallback)(const ProfileEntry& entry, const char* tagStringData); + +class ProfileBuffer { +public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ProfileBuffer) + + explicit ProfileBuffer(int aEntrySize); + + void addTag(const ProfileEntry& aTag); + void IterateTagsForThread(IterateTagsCallback aCallback, int aThreadId); + void StreamSamplesToJSObject(JSStreamWriter& b, int aThreadId, JSRuntime* rt); + void StreamMarkersToJSObject(JSStreamWriter& b, int aThreadId); + void DuplicateLastSample(int aThreadId); + + void addStoredMarker(ProfilerMarker* aStoredMarker); + void deleteExpiredStoredMarkers(); + +protected: + char* processDynamicTag(int readPos, int* tagsConsumed, char* tagBuff); + int FindLastSampleOfThread(int aThreadId); + + ~ProfileBuffer() {} + +public: + // Circular buffer 'Keep One Slot Open' implementation for simplicity + mozilla::UniquePtr mEntries; + + // Points to the next entry we will write to, which is also the one at which + // we need to stop reading. + int mWritePos; + + // Points to the entry at which we can start reading. + int mReadPos; + + // The number of entries in our buffer. + int mEntrySize; + + // How many times mWritePos has wrapped around. + int mGeneration; + + // Markers that marker entries in the buffer might refer to. + ProfilerMarkerLinkedList mStoredMarkers; +}; + +class ThreadProfile +{ +public: + ThreadProfile(ThreadInfo* aThreadInfo, ProfileBuffer* aBuffer); + virtual ~ThreadProfile(); + void addTag(const ProfileEntry& aTag); + + /** + * Track a marker which has been inserted into the ThreadProfile. + * This marker can safely be deleted once the generation has + * expired. + */ + void addStoredMarker(ProfilerMarker *aStoredMarker); + + void IterateTags(IterateTagsCallback aCallback); + void ToStreamAsJSON(std::ostream& stream); + JSObject *ToJSObject(JSContext *aCx); + PseudoStack* GetPseudoStack(); + mozilla::Mutex* GetMutex(); + void StreamJSObject(JSStreamWriter& b); + void BeginUnwind(); + virtual void EndUnwind(); + virtual SyncProfile* AsSyncProfile() { return nullptr; } + + bool IsMainThread() const { return mIsMainThread; } + const char* Name() const { return mThreadInfo->Name(); } + int ThreadId() const { return mThreadId; } + + PlatformData* GetPlatformData() const { return mPlatformData; } + void* GetStackTop() const { return mStackTop; } + void DuplicateLastSample(); + + ThreadInfo* GetThreadInfo() const { return mThreadInfo; } + ThreadResponsiveness* GetThreadResponsiveness() { return &mRespInfo; } + void SetPendingDelete() + { + mPseudoStack = nullptr; + mPlatformData = nullptr; + } +private: + FRIEND_TEST(ThreadProfile, InsertOneTag); + FRIEND_TEST(ThreadProfile, InsertOneTagWithTinyBuffer); + FRIEND_TEST(ThreadProfile, InsertTagsNoWrap); + FRIEND_TEST(ThreadProfile, InsertTagsWrap); + FRIEND_TEST(ThreadProfile, MemoryMeasure); + ThreadInfo* mThreadInfo; + + const nsRefPtr mBuffer; + + PseudoStack* mPseudoStack; + mozilla::Mutex mMutex; + int mThreadId; + bool mIsMainThread; + PlatformData* mPlatformData; // Platform specific data. + void* const mStackTop; + ThreadResponsiveness mRespInfo; + + // Only Linux is using a signal sender, instead of stopping the thread, so we + // need some space to store the data which cannot be collected in the signal + // handler code. +#ifdef XP_LINUX +public: + int64_t mRssMemory; + int64_t mUssMemory; +#endif + + void StreamTrackedOptimizations(JSStreamWriter& b, void* addr, uint8_t index); +}; + +#endif /* ndef MOZ_PROFILE_ENTRY_H */ diff --git a/tools/profiler/moz.build b/tools/profiler/moz.build index 29b3b49306..746f7505bf 100644 --- a/tools/profiler/moz.build +++ b/tools/profiler/moz.build @@ -17,6 +17,7 @@ if CONFIG['MOZ_TASK_TRACER']: UNIFIED_SOURCES += [ 'GoannaTaskTracer.cpp', 'TracedTaskCommon.cpp', + 'ProfileEntry.cpp', ] XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell.ini']