diff options
215 files changed, 8080 insertions, 904 deletions
diff --git a/build/autoconf/sanitize.m4 b/build/autoconf/sanitize.m4 index 3193d5c624..4f038ccb6f 100644 --- a/build/autoconf/sanitize.m4 +++ b/build/autoconf/sanitize.m4 @@ -13,7 +13,7 @@ MOZ_ARG_ENABLE_BOOL(address-sanitizer, MOZ_ASAN= ) if test -n "$MOZ_ASAN"; then MOZ_LLVM_HACKS=1 - if test -n "$CLANG_CL"; then + if test "$OS_ARCH" = "WINNT"; then # Look for the ASan runtime binary if test "$CPU_ARCH" = "x86_64"; then MOZ_CLANG_RT_ASAN_LIB=clang_rt.asan_dynamic-x86_64.dll @@ -27,14 +27,14 @@ if test -n "$MOZ_ASAN"; then fi AC_SUBST(MOZ_CLANG_RT_ASAN_LIB_PATH) # Suppressing errors in recompiled code. - if test "$OS_ARCH" = "WINNT"; then + if test -n "$CLANG_CL"; then CFLAGS="-fsanitize-blacklist=$_topsrcdir/build/sanitizers/asan_blacklist_win.txt $CFLAGS" CXXFLAGS="-fsanitize-blacklist=$_topsrcdir/build/sanitizers/asan_blacklist_win.txt $CXXFLAGS" fi fi CFLAGS="-fsanitize=address $CFLAGS" CXXFLAGS="-fsanitize=address $CXXFLAGS" - if test -z "$CLANG_CL"; then + if test "$OS_ARCH" != "WINNT"; then LDFLAGS="-fsanitize=address $LDFLAGS" fi AC_DEFINE(MOZ_ASAN) @@ -80,6 +80,40 @@ if test -n "$MOZ_TSAN"; then fi AC_SUBST(MOZ_TSAN) +dnl ======================================================== +dnl = Use Leak Sanitizer +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(leak-sanitizer, +[ --enable-leak-sanitizer Enable Leak Sanitizer (default=no)], + MOZ_LSAN=1, + MOZ_LSAN= ) +if test -n "$MOZ_LSAN"; then + MOZ_LLVM_HACKS=1 + LDFLAGS="-fsanitize=leak $LDFLAGS" + AC_DEFINE(MOZ_LSAN) + MOZ_PATH_PROG(LLVM_SYMBOLIZER, llvm-symbolizer) +fi +AC_SUBST(MOZ_LSAN) + +dnl ======================================================== +dnl = Use Undefined Behavior Sanitizer +dnl ======================================================== +MOZ_ARG_ENABLE_BOOL(undefined-sanitizer, +[ --enable-undefined-sanitizer Enable Undefined Behavior Sanitizer (default=no)], + MOZ_UBSAN=1, + MOZ_UBSAN= ) +if test -n "$MOZ_UBSAN"; then + MOZ_LLVM_HACKS=1 + CFLAGS="-fsanitize=undefined $CFLAGS" + CXXFLAGS="-fsanitize=undefined $CXXFLAGS" + if test -z "$CLANG_CL"; then + LDFLAGS="-fsanitize=undefined $LDFLAGS" + fi + AC_DEFINE(MOZ_UBSAN) + MOZ_PATH_PROG(LLVM_SYMBOLIZER, llvm-symbolizer) +fi +AC_SUBST(MOZ_UBSAN) + # The LLVM symbolizer is used by all sanitizers AC_SUBST(LLVM_SYMBOLIZER) diff --git a/build/moz.build b/build/moz.build index 6567dd944c..1f1c7474cd 100644 --- a/build/moz.build +++ b/build/moz.build @@ -58,9 +58,11 @@ FINAL_TARGET_FILES += ['/.gdbinit'] FINAL_TARGET_PP_FILES += ['.gdbinit_python.in'] OBJDIR_FILES += ['!/dist/bin/.gdbinit_python'] -# Install the clang-cl runtime library for ASAN next to the binaries we produce. -if CONFIG['MOZ_ASAN'] and CONFIG['CLANG_CL']: - FINAL_TARGET_FILES += ['%' + CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']] +# Install the clang runtime library for ASAN next to the binaries we produce. +if CONFIG['MOZ_ASAN'] and CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']: + FINAL_TARGET_FILES += [CONFIG['MOZ_CLANG_RT_ASAN_LIB_PATH']] +if CONFIG['LLVM_SYMBOLIZER']: + FINAL_TARGET_FILES += ['/' + CONFIG['LLVM_SYMBOLIZER']] if CONFIG['MOZ_APP_BASENAME']: FINAL_TARGET_PP_FILES += ['application.ini'] diff --git a/build/moz.configure/old.configure b/build/moz.configure/old.configure index 3d3873eae8..0a3b84be7b 100644 --- a/build/moz.configure/old.configure +++ b/build/moz.configure/old.configure @@ -197,6 +197,7 @@ def old_configure_options(*options): '--enable-ios-target', '--enable-jitspew', '--enable-js-lto', + '--enable-leak-sanitizer', '--enable-libjpeg-turbo', '--enable-libproxy', '--enable-llvm-hacks', @@ -246,6 +247,7 @@ def old_configure_options(*options): '--enable-thread-sanitizer', '--enable-trace-logging', '--enable-ui-locale', + '--enable-undefined-sanitizer', '--enable-universalchardet', '--enable-updater', '--enable-url-classifier', @@ -305,6 +307,8 @@ def old_configure_options(*options): '--enable-ldap', '--enable-mapi', '--enable-calendar', + '--enable-inspector', + '--enable-irc', '--enable-incomplete-external-linkage', '--enable-mailnews', '--enable-mailnews-oauth2', diff --git a/build/unix/moz.build b/build/unix/moz.build index 88c933295c..cd455b7a4d 100644 --- a/build/unix/moz.build +++ b/build/unix/moz.build @@ -9,9 +9,6 @@ if CONFIG['MOZ_LIBSTDCXX_TARGET_VERSION'] or CONFIG['MOZ_LIBSTDCXX_HOST_VERSION' if CONFIG['USE_ELF_HACK']: DIRS += ['elfhack'] -if CONFIG['LLVM_SYMBOLIZER']: - FINAL_TARGET_FILES += ['/' + CONFIG['LLVM_SYMBOLIZER']] - SDK_FILES.bin += [ 'run-mozilla.sh', ] diff --git a/config/milestone.txt b/config/milestone.txt index 30351e76a8..b463b5d4d2 100644 --- a/config/milestone.txt +++ b/config/milestone.txt @@ -8,4 +8,4 @@ # Referenced by milestone.py. #-------------------------------------------------------- -6.2.0
\ No newline at end of file +6.3.0
\ No newline at end of file diff --git a/devtools/client/debugger/new/bundle.js b/devtools/client/debugger/new/bundle.js index 2291d48f34..f94bc3aee4 100644 --- a/devtools/client/debugger/new/bundle.js +++ b/devtools/client/debugger/new/bundle.js @@ -2509,6 +2509,10 @@ var Debugger = var Attribute = _require9.Attribute; + var _require23 = __webpack_require__(460); + + var BigInt = _require23.BigInt; + var _require10 = __webpack_require__(47); var DateTime = _require10.DateTime; @@ -2565,7 +2569,7 @@ var Debugger = // XXX there should be a way for extensions to register a new // or modify an existing rep. - var reps = [RegExp, StyleSheet, Event, DateTime, TextNode, Attribute, Func, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, GripArray, GripMap, Grip, Undefined, Null, StringRep, Number, SymbolRep]; + var reps = [RegExp, StyleSheet, Event, DateTime, TextNode, Attribute, Func, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, GripArray, GripMap, Grip, Undefined, Null, StringRep, Number, BigInt, SymbolRep]; /** * Generic rep that is using for rendering native JS types or an object. @@ -2605,10 +2609,12 @@ var Debugger = var defaultRep = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Obj; var type = typeof object; - if (type == "object" && object instanceof String) { - type = "string"; - } else if (type == "object" && object.type === "symbol") { - type = "symbol"; + if (type == "object") { + if (object instanceof String) { + type = "string"; + } else if (["symbol", "BigInt"].includes(object.type)) { + type = object.type; + } } if (isGrip(object)) { @@ -58325,6 +58331,59 @@ var Debugger = setBundle }; +/***/ }, +/* 460 */ +/***/ function(module, exports, __webpack_require__) { + + var __WEBPACK_AMD_DEFINE_RESULT__;/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ + /* vim: set ft=javascript ts=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/. */ + + "use strict"; + + // Make this available to both AMD and CJS environments + + !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) { + // Dependencies + var React = __webpack_require__(2); + + // Shortcuts + var span = React.DOM.span; + + /** + * Renders a number + */ + + var BigInt = React.createClass({ + displayName: "BigInt", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + render: function () { + let {object} = this.props; + let {text} = object; + + return span({ className: "objectBox objectBox-number" }, `${text}n`); + } + }); + + function supportsObject(object, type) { + return type == "BigInt"; + } + + // Exports from this module + + exports.BigInt = { + rep: BigInt, + supportsObject: supportsObject + }; + }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); + + /***/ } /******/ ]); //# sourceMappingURL=bundle.js.map diff --git a/devtools/client/shared/components/reps/big-int.js b/devtools/client/shared/components/reps/big-int.js new file mode 100644 index 0000000000..2dd251fba3 --- /dev/null +++ b/devtools/client/shared/components/reps/big-int.js @@ -0,0 +1,48 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */ + +"use strict"; + +// Make this available to both AMD and CJS environments +define(function (require, exports, module) { + // Dependencies + const React = require("devtools/client/shared/vendor/react"); + + // Shortcuts + const { span } = React.DOM; + + /** + * Renders a BigInt + */ + const BigInt = React.createClass({ + displayName: "BigInt", + + propTypes: { + object: React.PropTypes.object.isRequired + }, + + render: function () { + let {object} = this.props; + let {text} = object; + + return ( + span({className: "objectBox objectBox-number"}, + `${text}n` + ) + ); + } + }); + + function supportsObject(object, type) { + return (type == "BigInt"); + } + + // Exports from this module + + exports.BigInt = { + rep: BigInt, + supportsObject: supportsObject + }; +}); diff --git a/devtools/client/shared/components/reps/moz.build b/devtools/client/shared/components/reps/moz.build index 722bd7fdda..9100c0e452 100644 --- a/devtools/client/shared/components/reps/moz.build +++ b/devtools/client/shared/components/reps/moz.build @@ -6,6 +6,7 @@ DevToolsModules( 'array.js', 'attribute.js', + 'big-int.js', 'caption.js', 'comment-node.js', 'date-time.js', diff --git a/devtools/client/shared/components/reps/rep.js b/devtools/client/shared/components/reps/rep.js index 80d25b69e4..93a2bf3d7c 100644 --- a/devtools/client/shared/components/reps/rep.js +++ b/devtools/client/shared/components/reps/rep.js @@ -26,6 +26,7 @@ define(function (require, exports, module) { // DOM types (grips) const { Attribute } = require("./attribute"); + const { BigInt } = require("./big-int"); const { DateTime } = require("./date-time"); const { Document } = require("./document"); const { Event } = require("./event"); @@ -70,6 +71,7 @@ define(function (require, exports, module) { Null, StringRep, Number, + BigInt, SymbolRep, InfinityRep, NaNRep, @@ -111,10 +113,12 @@ define(function (require, exports, module) { */ function getRep(object, defaultRep = Obj) { let type = typeof object; - if (type == "object" && object instanceof String) { - type = "string"; - } else if (object && type == "object" && object.type) { - type = object.type; + if (type == "object") { + if (object instanceof String) { + type = "string"; + } else if (["symbol", "BigInt"].includes(object.type)) { + type = object.type; + } } if (isGrip(object)) { diff --git a/devtools/client/shared/widgets/VariablesView.jsm b/devtools/client/shared/widgets/VariablesView.jsm index f7be87f44f..2ba8d74916 100644 --- a/devtools/client/shared/widgets/VariablesView.jsm +++ b/devtools/client/shared/widgets/VariablesView.jsm @@ -3236,14 +3236,6 @@ VariablesView.prototype.isOverridden = function (aItem) { * The variable's descriptor. */ VariablesView.isPrimitive = function (aDescriptor) { - // For accessor property descriptors, the getter and setter need to be - // contained in 'get' and 'set' properties. - let getter = aDescriptor.get; - let setter = aDescriptor.set; - if (getter || setter) { - return false; - } - // As described in the remote debugger protocol, the value grip // must be contained in a 'value' property. let grip = aDescriptor.value; @@ -3261,7 +3253,8 @@ VariablesView.isPrimitive = function (aDescriptor) { type == "NaN" || type == "-0" || type == "symbol" || - type == "longString") { + type == "longString" || + type == "BigInt") { return true; } @@ -3354,6 +3347,10 @@ VariablesView.getGrip = function (aValue) { return { type: "-0" }; } return aValue; + case "bigint": + return { type: "BigInt", + text: aValue.toString(), + }; case "undefined": // document.all is also "undefined" if (aValue === undefined) { @@ -3496,6 +3493,10 @@ VariablesView.stringifiers.byType = { return keyString + " \u2192 " + valueString; }, + BigInt: function (aGrip, aOptions) { + return aGrip.text + "n"; + }, + }; // VariablesView.stringifiers.byType VariablesView.stringifiers.byObjectClass = { diff --git a/devtools/client/webconsole/console-output.js b/devtools/client/webconsole/console-output.js index 649ec65ae6..8fbaec94fe 100644 --- a/devtools/client/webconsole/console-output.js +++ b/devtools/client/webconsole/console-output.js @@ -1293,6 +1293,7 @@ Messages.Extended.prototype = extend(Messages.Simple.prototype, { { let map = { "number": "cm-number", + "bigint": "cm-number", "longstring": "console-string", "string": "console-string", "regexp": "cm-string-2", @@ -3365,6 +3366,15 @@ Widgets.ObjectRenderers.add({ }); /** + * The widget used for displaying BigInt previews. + */ +Widgets.ObjectRenderers.add({ + byClass: "BigInt", + + render: WrappedPrimitiveRenderer, +}); + +/** * The widget used for displaying String previews. */ Widgets.ObjectRenderers.add({ diff --git a/devtools/server/actors/object.js b/devtools/server/actors/object.js index ab04b97048..7688e35c88 100644 --- a/devtools/server/actors/object.js +++ b/devtools/server/actors/object.js @@ -2159,6 +2159,12 @@ function createValueGrip(value, pool, makeObjectGrip) { } return value; + case "bigint": + return { + type: "BigInt", + text: value.toString(), + }; + case "undefined": return { type: "undefined" }; diff --git a/devtools/server/tests/unit/test_objectgrips-08.js b/devtools/server/tests/unit/test_objectgrips-08.js index ecaa7146db..2bd48a34c6 100644 --- a/devtools/server/tests/unit/test_objectgrips-08.js +++ b/devtools/server/tests/unit/test_objectgrips-08.js @@ -41,25 +41,15 @@ function test_object_grip() let objClient = gThreadClient.pauseGrip(args[0]); objClient.getPrototypeAndProperties(function (aResponse) { - do_check_eq(aResponse.ownProperties.a.configurable, true); - do_check_eq(aResponse.ownProperties.a.enumerable, true); - do_check_eq(aResponse.ownProperties.a.writable, true); - do_check_eq(aResponse.ownProperties.a.value.type, "Infinity"); + const {a, b, c, d, e, f, g} = aResponse.ownProperties; - do_check_eq(aResponse.ownProperties.b.configurable, true); - do_check_eq(aResponse.ownProperties.b.enumerable, true); - do_check_eq(aResponse.ownProperties.b.writable, true); - do_check_eq(aResponse.ownProperties.b.value.type, "-Infinity"); - - do_check_eq(aResponse.ownProperties.c.configurable, true); - do_check_eq(aResponse.ownProperties.c.enumerable, true); - do_check_eq(aResponse.ownProperties.c.writable, true); - do_check_eq(aResponse.ownProperties.c.value.type, "NaN"); - - do_check_eq(aResponse.ownProperties.d.configurable, true); - do_check_eq(aResponse.ownProperties.d.enumerable, true); - do_check_eq(aResponse.ownProperties.d.writable, true); - do_check_eq(aResponse.ownProperties.d.value.type, "-0"); + testPropertyType(a, "Infinity"); + testPropertyType(b, "-Infinity"); + testPropertyType(c, "NaN"); + testPropertyType(d, "-0"); + testPropertyType(e, "BigInt"); + testPropertyType(f, "BigInt"); + testPropertyType(g, "BigInt"); gThreadClient.resume(function () { gClient.close().then(gCallback); @@ -67,6 +57,20 @@ function test_object_grip() }); }); - gDebuggee.eval("stopMe({ a: Infinity, b: -Infinity, c: NaN, d: -0 })"); + gDebuggee.eval(`stopMe({ + a: Infinity, + b: -Infinity, + c: NaN, + d: -0, + e: 1n, + f: -2n, + g: 0n, + })`); } +function testPropertyType(prop, expectedType) { + do_check_eq(prop.configurable, true); + do_check_eq(prop.enumerable, true); + do_check_eq(prop.writable, true); + do_check_eq(prop.value.type, expectedType); +} diff --git a/dom/base/Crypto.cpp b/dom/base/Crypto.cpp index 4226832f4b..863c26c902 100644 --- a/dom/base/Crypto.cpp +++ b/dom/base/Crypto.cpp @@ -75,6 +75,8 @@ Crypto::GetRandomValues(JSContext* aCx, const ArrayBufferView& aArray, case js::Scalar::Uint16: case js::Scalar::Int32: case js::Scalar::Uint32: + case js::Scalar::BigInt64: + case js::Scalar::BigUint64: break; default: aRv.Throw(NS_ERROR_DOM_TYPE_MISMATCH_ERR); diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp index ccfe960cb7..3438c0c733 100644 --- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -1022,7 +1022,12 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) nsContentUtils::Retarget(relatedTargetAsNode, this); nsCOMPtr<nsINode> targetInKnownToBeHandledScope = FindChromeAccessOnlySubtreeOwner(aVisitor.mTargetInKnownToBeHandledScope); - if (nsContentUtils::ContentIsShadowIncludingDescendantOf( + // If aVisitor.mTargetInKnownToBeHandledScope wasn't nsINode, + // targetInKnownToBeHandledScope will be null. This may happen when + // dispatching event to Window object in a content page and + // propagating the event to a chrome Element. + if (targetInKnownToBeHandledScope && + nsContentUtils::ContentIsShadowIncludingDescendantOf( this, targetInKnownToBeHandledScope->SubtreeRoot())) { // Part of step 11.4. // "If target's root is a shadow-including inclusive ancestor of diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index d55be9eb89..467b134864 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -1037,11 +1037,17 @@ MaybeWrapValue(JSContext* cx, JS::MutableHandle<JS::Value> rval) return MaybeWrapStringValue(cx, rval); } - if (!rval.isObject()) { - return true; + if (rval.isObject()) { + return MaybeWrapObjectValue(cx, rval); + } + // This could be optimized by checking the zone first, similar to + // the way strings are handled. At present, this is used primarily + // for structured cloning, so avoiding the overhead of JS_WrapValue + // calls is less important than for other types. + if (rval.isBigInt()) { + return JS_WrapValue(cx, rval); } - - return MaybeWrapObjectValue(cx, rval); + return true; } namespace binding_detail { diff --git a/dom/ipc/FilePickerParent.cpp b/dom/ipc/FilePickerParent.cpp index 1438e374fe..0271890758 100644 --- a/dom/ipc/FilePickerParent.cpp +++ b/dom/ipc/FilePickerParent.cpp @@ -286,6 +286,7 @@ FilePickerParent::RecvOpen(const int16_t& aSelectedType, } } + MOZ_ASSERT(!mCallback); mCallback = new FilePickerShownCallback(this); mFilePicker->Open(mCallback); diff --git a/dom/ipc/FilePickerParent.h b/dom/ipc/FilePickerParent.h index 332007a715..89c472cb8a 100644 --- a/dom/ipc/FilePickerParent.h +++ b/dom/ipc/FilePickerParent.h @@ -27,8 +27,12 @@ class FilePickerParent : public PFilePickerParent , mMode(aMode) {} + private: virtual ~FilePickerParent(); + public: + NS_INLINE_DECL_REFCOUNTING(FilePickerParent) + void Done(int16_t aResult); struct BlobImplOrString @@ -69,7 +73,7 @@ class FilePickerParent : public PFilePickerParent private: virtual ~FilePickerShownCallback() {} - FilePickerParent* mFilePickerParent; + RefPtr<FilePickerParent> mFilePickerParent; }; private: @@ -78,7 +82,7 @@ class FilePickerParent : public PFilePickerParent // This runnable is used to do some I/O operation on a separate thread. class IORunnable : public Runnable { - FilePickerParent* mFilePickerParent; + RefPtr<FilePickerParent> mFilePickerParent; nsTArray<nsCOMPtr<nsIFile>> mFiles; nsTArray<BlobImplOrString> mResults; nsCOMPtr<nsIEventTarget> mEventTarget; diff --git a/dom/locales/en-US/chrome/dom/dom.properties b/dom/locales/en-US/chrome/dom/dom.properties index 27b4eebf12..f0a6363af0 100644 --- a/dom/locales/en-US/chrome/dom/dom.properties +++ b/dom/locales/en-US/chrome/dom/dom.properties @@ -321,3 +321,5 @@ PushStateFloodingPrevented=Call to pushState or replaceState ignored due to exce # LOCALIZATION NOTE: Do not translate "Reload" ReloadFloodingPrevented=Call to Reload ignored due to excessive calls within a short timeframe. DOMQuadBoundsAttrWarning=DOMQuad.bounds is deprecated in favor of DOMQuad.getBounds() +UnsupportedEntryTypesIgnored=Ignoring unsupported entryTypes: %S. +AllEntryTypesIgnored=No valid entryTypes; aborting registration. diff --git a/dom/notification/Notification.cpp b/dom/notification/Notification.cpp index 9a7c833794..5439a4fe59 100644 --- a/dom/notification/Notification.cpp +++ b/dom/notification/Notification.cpp @@ -1688,6 +1688,11 @@ Notification::GetPermissionInternal(nsISupports* aGlobal, ErrorResult& aRv) } nsCOMPtr<nsIPrincipal> principal = sop->GetPrincipal(); + if (!principal) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return NotificationPermission::Denied; + } + return GetPermissionInternal(principal, aRv); } diff --git a/dom/performance/Performance.cpp b/dom/performance/Performance.cpp index 217faa5afe..e8a46409ef 100755 --- a/dom/performance/Performance.cpp +++ b/dom/performance/Performance.cpp @@ -712,9 +712,21 @@ Performance::QueueEntry(PerformanceEntry* aEntry) if (mObservers.IsEmpty()) { return; } - NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(mObservers, - PerformanceObserver, - QueueEntry, (aEntry)); + nsTObserverArray<PerformanceObserver*> interestedObservers; + nsTObserverArray<PerformanceObserver*>::ForwardIterator observerIt( + mObservers); + while (observerIt.HasMore()) { + PerformanceObserver* observer = observerIt.GetNext(); + if (observer->ObservesTypeOfEntry(aEntry)) { + interestedObservers.AppendElement(observer); + } + } + + if (interestedObservers.IsEmpty()) { + return; + } + + NS_OBSERVER_ARRAY_NOTIFY_XPCOM_OBSERVERS(interestedObservers, PerformanceObserver, QueueEntry, (aEntry)); if (!mPendingNotificationObserversTask) { RunNotificationObserversTask(); diff --git a/dom/performance/PerformanceObserver.cpp b/dom/performance/PerformanceObserver.cpp index 26f93e8fc1..5482c1f2a6 100644 --- a/dom/performance/PerformanceObserver.cpp +++ b/dom/performance/PerformanceObserver.cpp @@ -9,6 +9,7 @@ #include "mozilla/dom/PerformanceBinding.h" #include "mozilla/dom/PerformanceEntryBinding.h" #include "mozilla/dom/PerformanceObserverBinding.h" +#include "nsIScriptError.h" #include "nsPIDOMWindow.h" #include "nsQueryObject.h" #include "nsString.h" @@ -27,12 +28,14 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PerformanceObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance) NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mQueuedEntries) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(PerformanceObserver) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mQueuedEntries) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(PerformanceObserver) @@ -43,10 +46,14 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PerformanceObserver) NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END +const char UnsupportedEntryTypesIgnoredMsgId[] = "UnsupportedEntryTypesIgnored"; +const char AllEntryTypesIgnoredMsgId[] = "AllEntryTypesIgnored"; + PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner, PerformanceObserverCallback& aCb) : mOwner(aOwner) , mCallback(&aCb) + , mObserverType(ObserverTypeUndefined) , mConnected(false) { MOZ_ASSERT(mOwner); @@ -56,6 +63,7 @@ PerformanceObserver::PerformanceObserver(nsPIDOMWindowInner* aOwner, PerformanceObserver::PerformanceObserver(WorkerPrivate* aWorkerPrivate, PerformanceObserverCallback& aCb) : mCallback(&aCb) + , mObserverType(ObserverTypeUndefined) , mConnected(false) { MOZ_ASSERT(aWorkerPrivate); @@ -115,7 +123,8 @@ PerformanceObserver::Notify() mQueuedEntries.Clear(); ErrorResult rv; - mCallback->Call(this, *list, *this, rv); + RefPtr<PerformanceObserverCallback> callback(mCallback); + callback->Call(this, *list, *this, rv); if (NS_WARN_IF(rv.Failed())) { rv.SuppressException(); } @@ -126,64 +135,228 @@ PerformanceObserver::QueueEntry(PerformanceEntry* aEntry) { MOZ_ASSERT(aEntry); - nsAutoString entryType; - aEntry->GetEntryType(entryType); - if (!mEntryTypes.Contains<nsString>(entryType)) { + if (!ObservesTypeOfEntry(aEntry)) { return; } mQueuedEntries.AppendElement(aEntry); } +/* + * Keep this list in alphabetical order. + * https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute + */ static const char16_t *const sValidTypeNames[4] = { u"mark", u"measure", - u"resource", - u"server" + u"navigation", + u"resource" }; void -PerformanceObserver::Observe(const PerformanceObserverInit& aOptions, - ErrorResult& aRv) -{ - if (aOptions.mEntryTypes.IsEmpty()) { - aRv.Throw(NS_ERROR_DOM_TYPE_ERR); +PerformanceObserver::ReportUnsupportedTypesErrorToConsole( + bool aIsMainThread, const char* msgId, const nsString& aInvalidTypes) { + if (!aIsMainThread) { + nsTArray<nsString> params; + params.AppendElement(aInvalidTypes); + WorkerPrivate::ReportErrorToConsole(msgId, params); + } else { + nsCOMPtr<nsPIDOMWindowInner> ownerWindow = do_QueryInterface(mOwner); + nsIDocument* document = ownerWindow->GetExtantDoc(); + const char16_t* params[] = {aInvalidTypes.get()}; + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, NS_LITERAL_CSTRING("DOM"), document, + nsContentUtils::eDOM_PROPERTIES, msgId, params, 1); + } + return; +} + +void PerformanceObserver::Observe(const PerformanceObserverInit& aOptions, + ErrorResult& aRv) { + const Optional<Sequence<nsString>>& maybeEntryTypes = aOptions.mEntryTypes; + const Optional<nsString>& maybeType = aOptions.mType; + const Optional<bool>& maybeBuffered = aOptions.mBuffered; + + if (!mPerformance) { + aRv.Throw(NS_ERROR_FAILURE); return; } - nsTArray<nsString> validEntryTypes; + if (!maybeEntryTypes.WasPassed() && !maybeType.WasPassed()) { + /* Per spec (3.3.1.2), this should be a syntax error. */ + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } - for (const char16_t* name : sValidTypeNames) { - nsDependentString validTypeName(name); - if (aOptions.mEntryTypes.Contains<nsString>(validTypeName) && - !validEntryTypes.Contains<nsString>(validTypeName)) { - validEntryTypes.AppendElement(validTypeName); + if (maybeEntryTypes.WasPassed() && + (maybeType.WasPassed() || maybeBuffered.WasPassed())) { + /* Per spec (3.3.1.3), this, too, should be a syntax error. */ + aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + /* 3.3.1.4.1 */ + if (mObserverType == ObserverTypeUndefined) { + if (maybeEntryTypes.WasPassed()) { + mObserverType = ObserverTypeMultiple; + } else { + mObserverType = ObserverTypeSingle; } } - if (validEntryTypes.IsEmpty()) { - aRv.Throw(NS_ERROR_DOM_TYPE_ERR); + /* 3.3.1.4.2 */ + if (mObserverType == ObserverTypeSingle && maybeEntryTypes.WasPassed()) { + aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR); + return; + } + /* 3.3.1.4.3 */ + if (mObserverType == ObserverTypeMultiple && maybeType.WasPassed()) { + aRv.Throw(NS_ERROR_DOM_INVALID_MODIFICATION_ERR); return; } - mEntryTypes.SwapElements(validEntryTypes); + /* 3.3.1.5 */ + if (mObserverType == ObserverTypeMultiple) { + const Sequence<nsString>& entryTypes = maybeEntryTypes.Value(); - mPerformance->AddObserver(this); + if (entryTypes.IsEmpty()) { + return; + } + + /* 3.3.1.5.2 */ + nsTArray<nsString> validEntryTypes; + for (const char16_t* name : sValidTypeNames) { + nsDependentString validTypeName(name); + if (entryTypes.Contains<nsString>(validTypeName) && + !validEntryTypes.Contains<nsString>(validTypeName)) { + validEntryTypes.AppendElement(validTypeName); + } + } + + nsAutoString invalidTypesJoined; + bool addComma = false; + for (const auto& type : entryTypes) { + if (!validEntryTypes.Contains<nsString>(type)) { + if (addComma) { + invalidTypesJoined.AppendLiteral(", "); + } + addComma = true; + invalidTypesJoined.Append(type); + } + } + + if (!invalidTypesJoined.IsEmpty()) { + ReportUnsupportedTypesErrorToConsole(NS_IsMainThread(), + UnsupportedEntryTypesIgnoredMsgId, + invalidTypesJoined); + } + + /* 3.3.1.5.3 */ + if (validEntryTypes.IsEmpty()) { + nsString errorString; + ReportUnsupportedTypesErrorToConsole( + NS_IsMainThread(), AllEntryTypesIgnoredMsgId, errorString); + return; + } + + /* + * Registered or not, we clear out the list of options, and start fresh + * with the one that we are using here. (3.3.1.5.4,5) + */ + mOptions.Clear(); + mOptions.AppendElement(aOptions); + + } else { + MOZ_ASSERT(mObserverType == ObserverTypeSingle); + bool typeValid = false; + nsString type = maybeType.Value(); + + /* 3.3.1.6.2 */ + for (const char16_t* name : sValidTypeNames) { + nsDependentString validTypeName(name); + if (type == validTypeName) { + typeValid = true; + break; + } + } + + if (!typeValid) { + ReportUnsupportedTypesErrorToConsole( + NS_IsMainThread(), UnsupportedEntryTypesIgnoredMsgId, type); + return; + } + + /* 3.3.1.6.4, 3.3.1.6.4 */ + bool didUpdateOptionsList = false; + nsTArray<PerformanceObserverInit> updatedOptionsList; + for (auto& option : mOptions) { + if (option.mType.WasPassed() && option.mType.Value() == type) { + updatedOptionsList.AppendElement(aOptions); + didUpdateOptionsList = true; + } else { + updatedOptionsList.AppendElement(option); + } + } + if (!didUpdateOptionsList) { + updatedOptionsList.AppendElement(aOptions); + } + mOptions.SwapElements(updatedOptionsList); - if (aOptions.mBuffered) { - for (auto entryType : mEntryTypes) { + /* 3.3.1.6.5 */ + if (maybeBuffered.WasPassed() && maybeBuffered.Value()) { nsTArray<RefPtr<PerformanceEntry>> existingEntries; - mPerformance->GetEntriesByType(entryType, existingEntries); + mPerformance->GetEntriesByType(type, existingEntries); if (!existingEntries.IsEmpty()) { mQueuedEntries.AppendElements(existingEntries); } } } - + /* Add ourselves to the list of registered performance + * observers, if necessary. (3.3.1.5.4,5; 3.3.1.6.4) + */ + mPerformance->AddObserver(this); mConnected = true; } void +PerformanceObserver::GetSupportedEntryTypes(const GlobalObject& aGlobal, JS::MutableHandle<JSObject*> aObject) +{ + nsTArray<nsString> validTypes; + JS::Rooted<JS::Value> val(aGlobal.Context()); + + for (const char16_t* name : sValidTypeNames) { + nsString validTypeName(name); + validTypes.AppendElement(validTypeName); + } + + if (!ToJSValue(aGlobal.Context(), validTypes, &val)) { + /* + * If this conversion fails, we don't set a result. + * The spec does not allow us to throw an exception. + */ + return; + } + aObject.set(&val.toObject()); +} + +bool +PerformanceObserver::ObservesTypeOfEntry(PerformanceEntry* aEntry) +{ + for (auto& option : mOptions) { + if (option.mType.WasPassed()) { + if (option.mType.Value() == aEntry->GetEntryType()) { + return true; + } + } else { + if (option.mEntryTypes.Value().Contains(aEntry->GetEntryType())) { + return true; + } + } + } + return false; +} + +void PerformanceObserver::Disconnect() { if (mConnected) { @@ -192,3 +365,10 @@ PerformanceObserver::Disconnect() mConnected = false; } } + +void +PerformanceObserver::TakeRecords(nsTArray<RefPtr<PerformanceEntry>>& aRetval) +{ + MOZ_ASSERT(aRetval.IsEmpty()); + aRetval.SwapElements(mQueuedEntries); +} diff --git a/dom/performance/PerformanceObserver.h b/dom/performance/PerformanceObserver.h index 283000d581..25d5f98f6a 100644 --- a/dom/performance/PerformanceObserver.h +++ b/dom/performance/PerformanceObserver.h @@ -55,19 +55,37 @@ public: void Observe(const PerformanceObserverInit& aOptions, mozilla::ErrorResult& aRv); + static void GetSupportedEntryTypes(const GlobalObject& aGlobal, + JS::MutableHandle<JSObject*> aObject); void Disconnect(); + void TakeRecords(nsTArray<RefPtr<PerformanceEntry>>& aRetval); + void Notify(); void QueueEntry(PerformanceEntry* aEntry); + bool ObservesTypeOfEntry(PerformanceEntry* aEntry); + private: + void ReportUnsupportedTypesErrorToConsole(bool aIsMainThread, + const char* msgId, + const nsString& aInvalidTypes); ~PerformanceObserver(); nsCOMPtr<nsISupports> mOwner; RefPtr<PerformanceObserverCallback> mCallback; RefPtr<Performance> mPerformance; nsTArray<nsString> mEntryTypes; + nsTArray<PerformanceObserverInit> mOptions; + enum { + ObserverTypeUndefined, + ObserverTypeSingle, + ObserverTypeMultiple, + } mObserverType; + /* + * This is also known as registered, in the spec. + */ bool mConnected; nsTArray<RefPtr<PerformanceEntry>> mQueuedEntries; }; diff --git a/dom/performance/tests/test_performance_observer.js b/dom/performance/tests/test_performance_observer.js index 9716570e29..767240a99b 100644 --- a/dom/performance/tests/test_performance_observer.js +++ b/dom/performance/tests/test_performance_observer.js @@ -14,25 +14,25 @@ test(t => { var observer = new PerformanceObserver(() => { }); - assert_throws({name: "TypeError"}, function() { + assert_throws({name: "SyntaxError"}, function() { observer.observe(); - }, "observe() should throw TypeError exception if no option specified."); + }, "observe() should throw SyntaxError exception if no option specified."); - assert_throws({name: "TypeError"}, function() { + assert_throws({name: "SyntaxError"}, function() { observer.observe({ unsupportedAttribute: "unsupported" }); - }, "obsrve() should throw TypeError exception if the option has no 'entryTypes' attribute."); + }, "observe() should throw SyntaxError exception if the option has no 'entryTypes' attribute."); assert_throws({name: "TypeError"}, function() { observer.observe({ entryTypes: [] }); - }, "obsrve() should throw TypeError exception if 'entryTypes' attribute is an empty sequence."); + }, "observe() should throw TypeError exception if 'entryTypes' attribute is an empty sequence."); assert_throws({name: "TypeError"}, function() { observer.observe({ entryTypes: null }); - }, "obsrve() should throw TypeError exception if 'entryTypes' attribute is null."); + }, "observe() should throw TypeError exception if 'entryTypes' attribute is null."); assert_throws({name: "TypeError"}, function() { observer.observe({ entryTypes: ["invalid"]}); - }, "obsrve() should throw TypeError exception if 'entryTypes' attribute value is invalid."); + }, "observe() should throw TypeError exception if 'entryTypes' attribute value is invalid."); }, "Test that PerformanceObserver.observe throws exception"); function promiseObserve(test, options) { diff --git a/dom/webidl/PerformanceObserver.webidl b/dom/webidl/PerformanceObserver.webidl index 4cebecbeba..4e08adb34b 100644 --- a/dom/webidl/PerformanceObserver.webidl +++ b/dom/webidl/PerformanceObserver.webidl @@ -8,8 +8,9 @@ */ dictionary PerformanceObserverInit { - required sequence<DOMString> entryTypes; - boolean buffered = false; + sequence<DOMString> entryTypes; + DOMString type; + boolean buffered; }; callback PerformanceObserverCallback = void (PerformanceObserverEntryList entries, PerformanceObserver observer); @@ -18,7 +19,8 @@ callback PerformanceObserverCallback = void (PerformanceObserverEntryList entrie Constructor(PerformanceObserverCallback callback), Exposed=(Window,Worker)] interface PerformanceObserver { - [Throws] - void observe(PerformanceObserverInit options); + [Throws] void observe(optional PerformanceObserverInit options); void disconnect(); + PerformanceEntryList takeRecords(); + static readonly attribute object supportedEntryTypes; }; diff --git a/dom/workers/WorkerPrivate.cpp b/dom/workers/WorkerPrivate.cpp index a7a4929f3b..2f2fa78d42 100644 --- a/dom/workers/WorkerPrivate.cpp +++ b/dom/workers/WorkerPrivate.cpp @@ -1000,11 +1000,13 @@ private: class ReportErrorToConsoleRunnable final : public WorkerRunnable { const char* mMessage; + const nsTArray<nsString> mParams; public: // aWorkerPrivate is the worker thread we're on (or the main thread, if null) static void - Report(WorkerPrivate* aWorkerPrivate, const char* aMessage) + Report(WorkerPrivate* aWorkerPrivate, const char* aMessage, + const nsTArray<nsString>& aParams) { if (aWorkerPrivate) { aWorkerPrivate->AssertIsOnWorkerThread(); @@ -1015,23 +1017,30 @@ public: // Now fire a runnable to do the same on the parent's thread if we can. if (aWorkerPrivate) { RefPtr<ReportErrorToConsoleRunnable> runnable = - new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage); + new ReportErrorToConsoleRunnable(aWorkerPrivate, aMessage, aParams); runnable->Dispatch(); return; } + uint16_t paramCount = aParams.Length(); + const char16_t** params = new const char16_t*[paramCount]; + for (uint16_t i=0; i<paramCount; ++i) { + params[i] = aParams[i].get(); + } + // Log a warning to the console. nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, - NS_LITERAL_CSTRING("DOM"), - nullptr, - nsContentUtils::eDOM_PROPERTIES, - aMessage); + NS_LITERAL_CSTRING("DOM"), nullptr, + nsContentUtils::eDOM_PROPERTIES, aMessage, + paramCount ? params : nullptr, paramCount); + delete[] params; } private: - ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate, const char* aMessage) + ReportErrorToConsoleRunnable(WorkerPrivate* aWorkerPrivate, const char* aMessage, + const nsTArray<nsString>& aParams) : WorkerRunnable(aWorkerPrivate, ParentThreadUnchangedBusyCount), - mMessage(aMessage) + mMessage(aMessage), mParams(aParams) { } virtual void @@ -1048,7 +1057,7 @@ private: { WorkerPrivate* parent = aWorkerPrivate->GetParent(); MOZ_ASSERT_IF(!parent, NS_IsMainThread()); - Report(parent, mMessage); + Report(parent, mMessage, mParams); return true; } }; @@ -6023,12 +6032,21 @@ WorkerPrivate::ReportError(JSContext* aCx, JS::ConstUTF8CharsZ aToStringResult, void WorkerPrivate::ReportErrorToConsole(const char* aMessage) { + nsTArray<nsString> emptyParams; + WorkerPrivate::ReportErrorToConsole(aMessage, emptyParams); +} + +// static +void +WorkerPrivate::ReportErrorToConsole(const char* aMessage, + const nsTArray<nsString>& aParams) +{ WorkerPrivate* wp = nullptr; if (!NS_IsMainThread()) { wp = GetCurrentThreadWorkerPrivate(); } - ReportErrorToConsoleRunnable::Report(wp, aMessage); + ReportErrorToConsoleRunnable::Report(wp, aMessage, aParams); } int32_t diff --git a/dom/workers/WorkerPrivate.h b/dom/workers/WorkerPrivate.h index 0a71420047..351f5458f1 100644 --- a/dom/workers/WorkerPrivate.h +++ b/dom/workers/WorkerPrivate.h @@ -1206,6 +1206,9 @@ public: static void ReportErrorToConsole(const char* aMessage); + static void + ReportErrorToConsole(const char* aMessage, const nsTArray<nsString>& aParams); + int32_t SetTimeout(JSContext* aCx, nsIScriptTimeoutHandler* aHandler, int32_t aTimeout, bool aIsInterval, diff --git a/extensions/spellcheck/hunspell/src/moz.build b/extensions/spellcheck/hunspell/src/moz.build index 09764ec711..01f49ddd03 100644 --- a/extensions/spellcheck/hunspell/src/moz.build +++ b/extensions/spellcheck/hunspell/src/moz.build @@ -19,11 +19,18 @@ SOURCES += [ DEFINES['BUILDING_LIBHUNSPELL'] = True -SharedLibrary('hunspell') +# Hunspell must be built into libxul on SunOS. See UXP Issue #2290. +if CONFIG['OS_TARGET'] == 'SunOS': -USE_LIBS += [ - 'mozglue' -] + FINAL_LIBRARY = 'xul' + +else: + + SharedLibrary('hunspell') + + USE_LIBS += [ + 'mozglue' + ] LOCAL_INCLUDES += [ '../glue', diff --git a/gfx/gl/SharedSurfaceGL.cpp b/gfx/gl/SharedSurfaceGL.cpp index adb4429ae8..2639d64afa 100644 --- a/gfx/gl/SharedSurfaceGL.cpp +++ b/gfx/gl/SharedSurfaceGL.cpp @@ -94,7 +94,8 @@ SharedSurface_Basic::~SharedSurface_Basic() mGL->fDeleteFramebuffers(1, &mFB); if (mOwnsTex) - mGL->fDeleteTextures(1, &mTex); + if (mTex) + mGL->fDeleteTextures(1, &mTex); } diff --git a/js/public/Class.h b/js/public/Class.h index f1d7739718..40885e6082 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -913,7 +913,7 @@ struct JSClass { // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 #define JSCLASS_GLOBAL_SLOT_COUNT \ - (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 50) + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 2 + 52) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ @@ -1100,6 +1100,7 @@ enum class ESClass { SetIterator, Arguments, Error, + BigInt, /** None of the above. */ Other diff --git a/js/public/Conversions.h b/js/public/Conversions.h index 4978583eca..200fc030fb 100644 --- a/js/public/Conversions.h +++ b/js/public/Conversions.h @@ -11,6 +11,7 @@ #include "mozilla/Casting.h" #include "mozilla/FloatingPoint.h" #include "mozilla/TypeTraits.h" +#include "mozilla/WrappingOperations.h" #include <math.h> @@ -120,7 +121,7 @@ ToBoolean(HandleValue v) if (v.isSymbol()) return true; - /* The slow path handles strings and objects. */ + /* The slow path handles strings, BigInts and objects. */ return js::ToBooleanSlow(v); } diff --git a/js/public/GCPolicyAPI.h b/js/public/GCPolicyAPI.h index 151ed4e048..cffaee7140 100644 --- a/js/public/GCPolicyAPI.h +++ b/js/public/GCPolicyAPI.h @@ -47,6 +47,7 @@ // Expand the given macro D for each public GC pointer. #define FOR_EACH_PUBLIC_GC_POINTER_TYPE(D) \ D(JS::Symbol*) \ + D(JS::BigInt*) \ D(JSAtom*) \ D(JSFunction*) \ D(JSObject*) \ @@ -125,6 +126,7 @@ struct GCPointerPolicy } }; template <> struct GCPolicy<JS::Symbol*> : public GCPointerPolicy<JS::Symbol*> {}; +template <> struct GCPolicy<JS::BigInt*> : public GCPointerPolicy<JS::BigInt*> {}; template <> struct GCPolicy<JSAtom*> : public GCPointerPolicy<JSAtom*> {}; template <> struct GCPolicy<JSFunction*> : public GCPointerPolicy<JSFunction*> {}; template <> struct GCPolicy<JSObject*> : public GCPointerPolicy<JSObject*> {}; diff --git a/js/public/MemoryMetrics.h b/js/public/MemoryMetrics.h index 72764aec0e..d9660a7cf8 100644 --- a/js/public/MemoryMetrics.h +++ b/js/public/MemoryMetrics.h @@ -580,6 +580,7 @@ struct UnusedGCThingSizes macro(Other, GCHeapUnused, objectGroup) \ macro(Other, GCHeapUnused, string) \ macro(Other, GCHeapUnused, symbol) \ + macro(Other, GCHeapUnused, bigInt) \ macro(Other, GCHeapUnused, jitcode) \ macro(Other, GCHeapUnused, scope) \ macro(Other, GCHeapUnused, regExpShared) @@ -599,6 +600,7 @@ struct UnusedGCThingSizes case JS::TraceKind::Object: object += n; break; case JS::TraceKind::String: string += n; break; case JS::TraceKind::Symbol: symbol += n; break; + case JS::TraceKind::BigInt: bigInt += n; break; case JS::TraceKind::Script: script += n; break; case JS::TraceKind::Shape: shape += n; break; case JS::TraceKind::BaseShape: baseShape += n; break; @@ -640,6 +642,8 @@ struct ZoneStats { #define FOR_EACH_SIZE(macro) \ macro(Other, GCHeapUsed, symbolsGCHeap) \ + macro(Other, GCHeapUsed, bigIntsGCHeap) \ + macro(Other, MallocHeap, bigIntsMallocHeap) \ macro(Other, GCHeapAdmin, gcHeapArenaAdmin) \ macro(Other, GCHeapUsed, lazyScriptsGCHeap) \ macro(Other, MallocHeap, lazyScriptsMallocHeap) \ diff --git a/js/public/TraceKind.h b/js/public/TraceKind.h index 13228a9612..78cf20f3a1 100644 --- a/js/public/TraceKind.h +++ b/js/public/TraceKind.h @@ -60,7 +60,8 @@ enum class TraceKind JitCode = 0x1F, LazyScript = 0x2F, Scope = 0x3F, - RegExpShared = 0x4F + RegExpShared = 0x4F, + BigInt = 0x5F }; const static uintptr_t OutOfLineTraceKindMask = 0x07; static_assert(uintptr_t(JS::TraceKind::BaseShape) & OutOfLineTraceKindMask, "mask bits are set"); @@ -91,6 +92,7 @@ struct MapTypeToTraceKind { D(Shape, js::Shape, true) \ D(String, JSString, false) \ D(Symbol, JS::Symbol, false) \ + D(BigInt, JS::BigInt, false) \ D(RegExpShared, js::RegExpShared, true) // Map from all public types to their trace kind. diff --git a/js/public/TracingAPI.h b/js/public/TracingAPI.h index 01d28d93c3..cc03a54609 100644 --- a/js/public/TracingAPI.h +++ b/js/public/TracingAPI.h @@ -141,6 +141,7 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer virtual void onObjectEdge(JSObject** objp) { onChild(JS::GCCellPtr(*objp)); } virtual void onStringEdge(JSString** strp) { onChild(JS::GCCellPtr(*strp)); } virtual void onSymbolEdge(JS::Symbol** symp) { onChild(JS::GCCellPtr(*symp)); } + virtual void onBigIntEdge(JS::BigInt** bip) { onChild(JS::GCCellPtr(*bip)); } virtual void onScriptEdge(JSScript** scriptp) { onChild(JS::GCCellPtr(*scriptp)); } virtual void onShapeEdge(js::Shape** shapep) { onChild(JS::GCCellPtr(*shapep, JS::TraceKind::Shape)); @@ -226,6 +227,7 @@ class JS_PUBLIC_API(CallbackTracer) : public JSTracer void dispatchToOnEdge(JSObject** objp) { onObjectEdge(objp); } void dispatchToOnEdge(JSString** strp) { onStringEdge(strp); } void dispatchToOnEdge(JS::Symbol** symp) { onSymbolEdge(symp); } + void dispatchToOnEdge(JS::BigInt** bip) { onBigIntEdge(bip); } void dispatchToOnEdge(JSScript** scriptp) { onScriptEdge(scriptp); } void dispatchToOnEdge(js::Shape** shapep) { onShapeEdge(shapep); } void dispatchToOnEdge(js::ObjectGroup** groupp) { onObjectGroupEdge(groupp); } diff --git a/js/public/TypeDecls.h b/js/public/TypeDecls.h index 2b36ed95b9..27f652f04f 100644 --- a/js/public/TypeDecls.h +++ b/js/public/TypeDecls.h @@ -39,6 +39,7 @@ namespace JS { typedef unsigned char Latin1Char; class Symbol; +class BigInt; class Value; template <typename T> class Handle; template <typename T> class MutableHandle; @@ -53,6 +54,7 @@ typedef Handle<JSObject*> HandleObject; typedef Handle<JSScript*> HandleScript; typedef Handle<JSString*> HandleString; typedef Handle<JS::Symbol*> HandleSymbol; +typedef Handle<JS::BigInt*> HandleBigInt; typedef Handle<Value> HandleValue; typedef Handle<StackGCVector<Value>> HandleValueVector; @@ -62,6 +64,7 @@ typedef MutableHandle<JSObject*> MutableHandleObject; typedef MutableHandle<JSScript*> MutableHandleScript; typedef MutableHandle<JSString*> MutableHandleString; typedef MutableHandle<JS::Symbol*> MutableHandleSymbol; +typedef MutableHandle<JS::BigInt*> MutableHandleBigInt; typedef MutableHandle<Value> MutableHandleValue; typedef MutableHandle<StackGCVector<Value>> MutableHandleValueVector; @@ -70,6 +73,7 @@ typedef Rooted<JSFunction*> RootedFunction; typedef Rooted<JSScript*> RootedScript; typedef Rooted<JSString*> RootedString; typedef Rooted<JS::Symbol*> RootedSymbol; +typedef Rooted<JS::BigInt*> RootedBigInt; typedef Rooted<jsid> RootedId; typedef Rooted<JS::Value> RootedValue; @@ -81,6 +85,7 @@ typedef PersistentRooted<JSObject*> PersistentRootedObject; typedef PersistentRooted<JSScript*> PersistentRootedScript; typedef PersistentRooted<JSString*> PersistentRootedString; typedef PersistentRooted<JS::Symbol*> PersistentRootedSymbol; +typedef PersistentRooted<JS::BigInt*> PersistentRootedBigInt; typedef PersistentRooted<Value> PersistentRootedValue; diff --git a/js/public/UbiNode.h b/js/public/UbiNode.h index a4ed0dff49..3df3a4840b 100644 --- a/js/public/UbiNode.h +++ b/js/public/UbiNode.h @@ -1057,6 +1057,22 @@ class JS_PUBLIC_API(Concrete<JS::Symbol>) : TracerConcrete<JS::Symbol> { }; template<> +class JS_PUBLIC_API(Concrete<JS::BigInt>) : TracerConcrete<JS::BigInt> { + protected: + explicit Concrete(JS::BigInt* ptr) : TracerConcrete(ptr) {} + + public: + static void construct(void* storage, JS::BigInt* ptr) { + new (storage) Concrete(ptr); + } + + Size size(mozilla::MallocSizeOf mallocSizeOf) const override; + + const char16_t* typeName() const override { return concreteTypeName; } + static const char16_t concreteTypeName[]; +}; + +template<> class JS_PUBLIC_API(Concrete<JSScript>) : TracerConcreteWithCompartment<JSScript> { protected: explicit Concrete(JSScript *ptr) : TracerConcreteWithCompartment<JSScript>(ptr) { } diff --git a/js/public/Utility.h b/js/public/Utility.h index cadcef7000..dbe69e18c0 100644 --- a/js/public/Utility.h +++ b/js/public/Utility.h @@ -14,6 +14,7 @@ #include "mozilla/Scoped.h" #include "mozilla/TemplateLib.h" #include "mozilla/UniquePtr.h" +#include "mozilla/WrappingOperations.h" #include <stdlib.h> #include <string.h> @@ -539,7 +540,7 @@ ScrambleHashCode(HashNumber h) * are stored in a hash table; see Knuth for details. */ static const HashNumber goldenRatio = 0x9E3779B9U; - return h * goldenRatio; + return mozilla::WrappingMultiply(h, goldenRatio); } } /* namespace detail */ diff --git a/js/public/Value.h b/js/public/Value.h index c645f07733..a6ceaad669 100644 --- a/js/public/Value.h +++ b/js/public/Value.h @@ -56,6 +56,7 @@ JS_ENUM_HEADER(JSValueType, uint8_t) JSVAL_TYPE_STRING = 0x06, JSVAL_TYPE_SYMBOL = 0x07, JSVAL_TYPE_PRIVATE_GCTHING = 0x08, + JSVAL_TYPE_BIGINT = 0x09, JSVAL_TYPE_OBJECT = 0x0c, /* These never appear in a jsval; they are only provided as an out-of-band value. */ @@ -80,6 +81,7 @@ JS_ENUM_HEADER(JSValueTag, uint32_t) JSVAL_TAG_BOOLEAN = JSVAL_TAG_CLEAR | JSVAL_TYPE_BOOLEAN, JSVAL_TAG_MAGIC = JSVAL_TAG_CLEAR | JSVAL_TYPE_MAGIC, JSVAL_TAG_OBJECT = JSVAL_TAG_CLEAR | JSVAL_TYPE_OBJECT, + JSVAL_TAG_BIGINT = JSVAL_TAG_CLEAR | JSVAL_TYPE_BIGINT, JSVAL_TAG_PRIVATE_GCTHING = JSVAL_TAG_CLEAR | JSVAL_TYPE_PRIVATE_GCTHING } JS_ENUM_FOOTER(JSValueTag); @@ -100,6 +102,7 @@ JS_ENUM_HEADER(JSValueTag, uint32_t) JSVAL_TAG_BOOLEAN = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_BOOLEAN, JSVAL_TAG_MAGIC = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_MAGIC, JSVAL_TAG_OBJECT = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_OBJECT, + JSVAL_TAG_BIGINT = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_BIGINT, JSVAL_TAG_PRIVATE_GCTHING = JSVAL_TAG_MAX_DOUBLE | JSVAL_TYPE_PRIVATE_GCTHING } JS_ENUM_FOOTER(JSValueTag); @@ -117,6 +120,7 @@ JS_ENUM_HEADER(JSValueShiftedTag, uint64_t) JSVAL_SHIFTED_TAG_BOOLEAN = (((uint64_t)JSVAL_TAG_BOOLEAN) << JSVAL_TAG_SHIFT), JSVAL_SHIFTED_TAG_MAGIC = (((uint64_t)JSVAL_TAG_MAGIC) << JSVAL_TAG_SHIFT), JSVAL_SHIFTED_TAG_OBJECT = (((uint64_t)JSVAL_TAG_OBJECT) << JSVAL_TAG_SHIFT), + JSVAL_SHIFTED_TAG_BIGINT = (((uint64_t)JSVAL_TAG_BIGINT) << JSVAL_TAG_SHIFT), JSVAL_SHIFTED_TAG_PRIVATE_GCTHING = (((uint64_t)JSVAL_TAG_PRIVATE_GCTHING) << JSVAL_TAG_SHIFT) } JS_ENUM_FOOTER(JSValueShiftedTag); @@ -275,7 +279,7 @@ CanonicalizeNaN(double d) * * - JS::Value has setX() and isX() members for X in * - * { Int32, Double, String, Symbol, Boolean, Undefined, Null, Object, Magic } + * { Int32, Double, String, Symbol, BigInt, Boolean, Undefined, Null, Object, Magic } * * JS::Value also contains toX() for each of the non-singleton types. * @@ -370,6 +374,11 @@ class MOZ_NON_PARAM alignas(8) Value data.asBits = bitsFromTagAndPayload(JSVAL_TAG_SYMBOL, PayloadType(sym)); } + void setBigInt(JS::BigInt* bi) { + MOZ_ASSERT(uintptr_t(bi) > 0x1000); + data.asBits = bitsFromTagAndPayload(JSVAL_TAG_BIGINT, PayloadType(bi)); + } + void setObject(JSObject& obj) { MOZ_ASSERT(uintptr_t(&obj) > 0x1000 || uintptr_t(&obj) == 0x48); #if defined(JS_PUNBOX64) @@ -498,7 +507,7 @@ class MOZ_NON_PARAM alignas(8) Value #if defined(JS_NUNBOX32) return uint32_t(toTag()) <= uint32_t(JSVAL_TAG_CLEAR); #elif defined(JS_PUNBOX64) - return (data.asBits | mozilla::DoubleTypeTraits::kSignBit) <= JSVAL_SHIFTED_TAG_MAX_DOUBLE; + return (data.asBits | mozilla::FloatingPoint<double>::kSignBit) <= JSVAL_SHIFTED_TAG_MAX_DOUBLE; #endif } @@ -519,6 +528,10 @@ class MOZ_NON_PARAM alignas(8) Value return toTag() == JSVAL_TAG_SYMBOL; } + bool isBigInt() const { + return toTag() == JSVAL_TAG_BIGINT; + } + bool isObject() const { #if defined(JS_NUNBOX32) return toTag() == JSVAL_TAG_OBJECT; @@ -540,6 +553,10 @@ class MOZ_NON_PARAM alignas(8) Value return isObject() || isNull(); } + bool isNumeric() const { + return isNumber() || isBigInt(); + } + bool isGCThing() const { #if defined(JS_NUNBOX32) /* gcc sometimes generates signed < without explicit casts. */ @@ -583,6 +600,8 @@ class MOZ_NON_PARAM alignas(8) Value "Value type tags must correspond with JS::TraceKinds."); if (MOZ_UNLIKELY(isPrivateGCThing())) return JS::GCThingTraceKind(toGCThing()); + if (MOZ_UNLIKELY(isBigInt())) + return JS::TraceKind::BigInt; return JS::TraceKind(toTag() & 0x03); } @@ -647,6 +666,15 @@ class MOZ_NON_PARAM alignas(8) Value #endif } + JS::BigInt* toBigInt() const { + MOZ_ASSERT(isBigInt()); +#if defined(JS_NUNBOX32) + return data.s.payload.bi; +#elif defined(JS_PUNBOX64) + return reinterpret_cast<JS::BigInt*>(data.asBits & JSVAL_PAYLOAD_MASK); +#endif + } + JSObject& toObject() const { MOZ_ASSERT(isObject()); #if defined(JS_NUNBOX32) @@ -759,6 +787,8 @@ class MOZ_NON_PARAM alignas(8) Value "Private GC thing Values must not be strings. Make a StringValue instead."); MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::Symbol, "Private GC thing Values must not be symbols. Make a SymbolValue instead."); + MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::BigInt, + "Private GC thing Values must not be BigInts. Make a BigIntValue instead."); MOZ_ASSERT(JS::GCThingTraceKind(cell) != JS::TraceKind::Object, "Private GC thing Values must not be objects. Make an ObjectValue instead."); @@ -811,6 +841,7 @@ class MOZ_NON_PARAM alignas(8) Value uint32_t boo; // Don't use |bool| -- it must be four bytes. JSString* str; JS::Symbol* sym; + JS::BigInt* bi; JSObject* obj; js::gc::Cell* cell; void* ptr; @@ -866,6 +897,7 @@ class MOZ_NON_PARAM alignas(8) Value uint32_t boo; // Don't use |bool| -- it must be four bytes. JSString* str; JS::Symbol* sym; + JS::BigInt* bi; JSObject* obj; js::gc::Cell* cell; void* ptr; @@ -1062,7 +1094,7 @@ IsCanonicalized(double d) uint64_t bits; mozilla::BitwiseCast<uint64_t>(d, &bits); - return (bits & ~mozilla::DoubleTypeTraits::kSignBit) == detail::CanonicalizedNaNBits; + return (bits & ~mozilla::FloatingPoint<double>::kSignBit) == detail::CanonicalizedNaNBits; } static inline Value @@ -1098,6 +1130,14 @@ SymbolValue(JS::Symbol* sym) } static inline Value +BigIntValue(JS::BigInt* bi) +{ + Value v; + v.setBigInt(bi); + return v; +} + +static inline Value BooleanValue(bool boo) { Value v; @@ -1365,6 +1405,7 @@ class WrappedPtrOperations<JS::Value, Wrapper> bool isDouble() const { return value().isDouble(); } bool isString() const { return value().isString(); } bool isSymbol() const { return value().isSymbol(); } + bool isBigInt() const { return value().isBigInt(); } bool isObject() const { return value().isObject(); } bool isMagic() const { return value().isMagic(); } bool isMagic(JSWhyMagic why) const { return value().isMagic(why); } @@ -1373,6 +1414,7 @@ class WrappedPtrOperations<JS::Value, Wrapper> bool isNullOrUndefined() const { return value().isNullOrUndefined(); } bool isObjectOrNull() const { return value().isObjectOrNull(); } + bool isNumeric() const { return value().isNumeric(); } bool toBoolean() const { return value().toBoolean(); } double toNumber() const { return value().toNumber(); } @@ -1380,6 +1422,7 @@ class WrappedPtrOperations<JS::Value, Wrapper> double toDouble() const { return value().toDouble(); } JSString* toString() const { return value().toString(); } JS::Symbol* toSymbol() const { return value().toSymbol(); } + JS::BigInt* toBigInt() const { return value().toBigInt(); } JSObject& toObject() const { return value().toObject(); } JSObject* toObjectOrNull() const { return value().toObjectOrNull(); } gc::Cell* toGCThing() const { return value().toGCThing(); } @@ -1421,6 +1464,7 @@ class MutableWrappedPtrOperations<JS::Value, Wrapper> : public WrappedPtrOperati void setNumber(double d) { set(JS::NumberValue(d)); } void setString(JSString* str) { set(JS::StringValue(str)); } void setSymbol(JS::Symbol* sym) { set(JS::SymbolValue(sym)); } + void setBigInt(JS::BigInt* bi) { set(JS::BigIntValue(bi)); } void setObject(JSObject& obj) { set(JS::ObjectValue(obj)); } void setObjectOrNull(JSObject* arg) { set(JS::ObjectOrNullValue(arg)); } void setPrivate(void* ptr) { set(JS::PrivateValue(ptr)); } @@ -1469,6 +1513,8 @@ DispatchTyped(F f, const JS::Value& val, Args&&... args) return f(&val.toObject(), mozilla::Forward<Args>(args)...); if (val.isSymbol()) return f(val.toSymbol(), mozilla::Forward<Args>(args)...); + if (val.isBigInt()) + return f(val.toBigInt(), mozilla::Forward<Args>(args)...); if (MOZ_UNLIKELY(val.isPrivateGCThing())) return DispatchTyped(f, val.toGCCellPtr(), mozilla::Forward<Args>(args)...); MOZ_ASSERT(!val.isGCThing()); diff --git a/js/src/NamespaceImports.h b/js/src/NamespaceImports.h index a1d8bca1c3..dd940dc808 100644 --- a/js/src/NamespaceImports.h +++ b/js/src/NamespaceImports.h @@ -50,6 +50,7 @@ class PropertyResult; class Symbol; enum class SymbolCode: uint32_t; +class BigInt; } // namespace JS // Do the importing. @@ -122,6 +123,7 @@ using JS::RootedObject; using JS::RootedScript; using JS::RootedString; using JS::RootedSymbol; +using JS::RootedBigInt; using JS::RootedValue; using JS::PersistentRooted; @@ -131,6 +133,7 @@ using JS::PersistentRootedObject; using JS::PersistentRootedScript; using JS::PersistentRootedString; using JS::PersistentRootedSymbol; +using JS::PersistentRootedBigInt; using JS::PersistentRootedValue; using JS::Handle; @@ -140,6 +143,7 @@ using JS::HandleObject; using JS::HandleScript; using JS::HandleString; using JS::HandleSymbol; +using JS::HandleBigInt; using JS::HandleValue; using JS::MutableHandle; @@ -149,6 +153,7 @@ using JS::MutableHandleObject; using JS::MutableHandleScript; using JS::MutableHandleString; using JS::MutableHandleSymbol; +using JS::MutableHandleBigInt; using JS::MutableHandleValue; using JS::NullHandleValue; @@ -166,6 +171,7 @@ using JS::Zone; using JS::Symbol; using JS::SymbolCode; +using JS::BigInt; } /* namespace js */ #endif /* NamespaceImports_h */ diff --git a/js/src/builtin/BigInt.cpp b/js/src/builtin/BigInt.cpp new file mode 100644 index 0000000000..8a630534a8 --- /dev/null +++ b/js/src/builtin/BigInt.cpp @@ -0,0 +1,232 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 "builtin/BigInt.h" + +#include "jsapi.h" + +#include "builtin/TypedObject.h" +#include "gc/Tracer.h" +#include "js/TracingAPI.h" +#include "vm/ArrayBufferObject.h" +#include "vm/BigIntType.h" +#include "vm/SelfHosting.h" +#include "vm/TaggedProto.h" + +#include "vm/NativeObject-inl.h" + +using namespace js; + +static MOZ_ALWAYS_INLINE bool +IsBigInt(HandleValue v) +{ + return v.isBigInt() || (v.isObject() && v.toObject().is<BigIntObject>()); +} + +static JSObject* +CreateBigIntPrototype(JSContext* cx, JSProtoKey key) +{ + return GlobalObject::createBlankPrototype<PlainObject>(cx, cx->global()); +} + +// BigInt proposal section 5.1.3 +static bool +BigIntConstructor(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + if (args.isConstructing()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_CONSTRUCTOR, "BigInt"); + return false; + } + + // Step 2. + RootedValue v(cx, args.get(0)); + if (!ToPrimitive(cx, JSTYPE_NUMBER, &v)) + return false; + + // Steps 3-4. + BigInt* bi = v.isNumber() + ? NumberToBigInt(cx, v.toNumber()) + : ToBigInt(cx, v); + if (!bi) + return false; + + args.rval().setBigInt(bi); + return true; +} + +JSObject* +BigIntObject::create(JSContext* cx, HandleBigInt bigInt) +{ + RootedObject obj(cx, NewBuiltinClassInstance(cx, &class_)); + if (!obj) + return nullptr; + BigIntObject& bn = obj->as<BigIntObject>(); + bn.setFixedSlot(PRIMITIVE_VALUE_SLOT, BigIntValue(bigInt)); + return &bn; +} + +BigInt* +BigIntObject::unbox() const +{ + return getFixedSlot(PRIMITIVE_VALUE_SLOT).toBigInt(); +} + +// BigInt proposal section 5.3.4 +bool +BigIntObject::valueOf_impl(JSContext* cx, const CallArgs& args) +{ + // Step 1. + HandleValue thisv = args.thisv(); + MOZ_ASSERT(IsBigInt(thisv)); + RootedBigInt bi(cx, thisv.isBigInt() + ? thisv.toBigInt() + : thisv.toObject().as<BigIntObject>().unbox()); + + args.rval().setBigInt(bi); + return true; +} + +bool +BigIntObject::valueOf(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsBigInt, valueOf_impl>(cx, args); +} + +// BigInt proposal section 5.3.3 +bool +BigIntObject::toString_impl(JSContext* cx, const CallArgs& args) +{ + // Step 1. + HandleValue thisv = args.thisv(); + MOZ_ASSERT(IsBigInt(thisv)); + RootedBigInt bi(cx, thisv.isBigInt() + ? thisv.toBigInt() + : thisv.toObject().as<BigIntObject>().unbox()); + + // Steps 2-3. + uint8_t radix = 10; + + // Steps 4-5. + if (args.hasDefined(0)) { + double d; + if (!ToInteger(cx, args.get(0), &d)) + return false; + if (d < 2 || d > 36) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_RADIX); + return false; + } + radix = d; + } + + // Steps 6-7. + JSLinearString* str = BigInt::toString(cx, bi, radix); + if (!str) + return false; + args.rval().setString(str); + return true; +} + +bool +BigIntObject::toString(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<IsBigInt, toString_impl>(cx, args); +} + +// BigInt proposal section 5.2.1. BigInt.asUintN ( bits, bigint ) +bool +BigIntObject::asUintN(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + uint64_t bits; + if (!ToIndex(cx, args.get(0), &bits)) { + return false; + } + + // Step 2. + RootedBigInt bi(cx, ToBigInt(cx, args.get(1))); + if (!bi) { + return false; + } + + // Step 3. + BigInt* res = BigInt::asUintN(cx, bi, bits); + if (!res) { + return false; + } + + args.rval().setBigInt(res); + return true; +} + +// BigInt proposal section 5.2.2. BigInt.asIntN ( bits, bigint ) +bool +BigIntObject::asIntN(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + // Step 1. + uint64_t bits; + if (!ToIndex(cx, args.get(0), &bits)) { + return false; + } + + // Step 2. + RootedBigInt bi(cx, ToBigInt(cx, args.get(1))); + if (!bi) { + return false; + } + + // Step 3. + BigInt* res = BigInt::asIntN(cx, bi, bits); + if (!res) { + return false; + } + + args.rval().setBigInt(res); + return true; +} + +const ClassSpec BigIntObject::classSpec_ = { + GenericCreateConstructor<BigIntConstructor, 1, gc::AllocKind::FUNCTION>, + CreateBigIntPrototype, + BigIntObject::staticMethods, + nullptr, + BigIntObject::methods, + BigIntObject::properties +}; + +const Class BigIntObject::class_ = { + "BigInt", + JSCLASS_HAS_CACHED_PROTO(JSProto_BigInt) | + JSCLASS_HAS_RESERVED_SLOTS(RESERVED_SLOTS), + JS_NULL_CLASS_OPS, + &BigIntObject::classSpec_ +}; + +const JSPropertySpec BigIntObject::properties[] = { + // BigInt proposal section 5.3.5 + JS_STRING_SYM_PS(toStringTag, "BigInt", JSPROP_READONLY), + JS_PS_END +}; + +const JSFunctionSpec BigIntObject::methods[] = { + JS_FN("valueOf", valueOf, 0, 0), + JS_FN("toString", toString, 0, 0), + JS_SELF_HOSTED_FN("toLocaleString", "BigInt_toLocaleString", 0, 0), + JS_FS_END +}; + +const JSFunctionSpec BigIntObject::staticMethods[] = { + JS_FN("asUintN", asUintN, 2, 0), + JS_FN("asIntN", asIntN, 2, 0), + JS_FS_END +}; diff --git a/js/src/builtin/BigInt.h b/js/src/builtin/BigInt.h new file mode 100644 index 0000000000..be447b2285 --- /dev/null +++ b/js/src/builtin/BigInt.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * 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 builtin_BigInt_h +#define builtin_BigInt_h + +#include "js/Class.h" +#include "js/RootingAPI.h" +#include "vm/BigIntType.h" +#include "vm/NativeObject.h" + +namespace js { + +class GlobalObject; + +class BigIntObject : public NativeObject +{ + static const unsigned PRIMITIVE_VALUE_SLOT = 0; + static const unsigned RESERVED_SLOTS = 1; + + public: + static const ClassSpec classSpec_; + static const Class class_; + + static JSObject* create(JSContext* cx, JS::Handle<JS::BigInt*> bi); + + // Methods defined on BigInt.prototype. + static bool valueOf_impl(JSContext* cx, const CallArgs& args); + static bool valueOf(JSContext* cx, unsigned argc, JS::Value* vp); + static bool toString_impl(JSContext* cx, const CallArgs& args); + static bool toString(JSContext* cx, unsigned argc, JS::Value* vp); + static bool asUintN(JSContext* cx, unsigned argc, JS::Value* vp); + static bool asIntN(JSContext* cx, unsigned argc, JS::Value* vp); + + JS::BigInt* unbox() const; + + private: + static const JSPropertySpec properties[]; + static const JSFunctionSpec methods[]; + static const JSFunctionSpec staticMethods[]; +}; + +extern JSObject* +InitBigIntClass(JSContext* cx, Handle<GlobalObject*> global); + +} // namespace js + +#endif diff --git a/js/src/builtin/BigInt.js b/js/src/builtin/BigInt.js new file mode 100644 index 0000000000..3ed3da5933 --- /dev/null +++ b/js/src/builtin/BigInt.js @@ -0,0 +1,34 @@ +/* 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/. */ + +/** + * Format this BigInt object into a string, using the locale and formatting + * options provided. + * + * Spec PR: https://github.com/tc39/ecma402/pull/236 + */ +function BigInt_toLocaleString() { + // Step 1. Note that valueOf enforces "thisBigIntValue" restrictions. + var x = callFunction(std_BigInt_valueOf, this); + + var locales = arguments.length > 0 ? arguments[0] : undefined; + var options = arguments.length > 1 ? arguments[1] : undefined; + + // Step 2. + var numberFormat; + if (locales === undefined && options === undefined) { + // This cache only optimizes when no explicit locales and options + // arguments were supplied. + if (!IsRuntimeDefaultLocale(numberFormatCache.runtimeDefaultLocale)) { + numberFormatCache.numberFormat = intl_NumberFormat(locales, options); + numberFormatCache.runtimeDefaultLocale = RuntimeDefaultLocale(); + } + numberFormat = numberFormatCache.numberFormat; + } else { + numberFormat = intl_NumberFormat(locales, options); + } + + // Step 3. + return intl_FormatNumber(numberFormat, x, /* formatToParts = */ false); +} diff --git a/js/src/builtin/MapObject.cpp b/js/src/builtin/MapObject.cpp index 893e0448a4..fe748a6bde 100644 --- a/js/src/builtin/MapObject.cpp +++ b/js/src/builtin/MapObject.cpp @@ -61,7 +61,7 @@ HashableValue::setValue(JSContext* cx, HandleValue v) } MOZ_ASSERT(value.isUndefined() || value.isNull() || value.isBoolean() || value.isNumber() || - value.isString() || value.isSymbol() || value.isObject()); + value.isString() || value.isSymbol() || value.isObject() || value.isBigInt()); return true; } @@ -81,6 +81,8 @@ HashValue(const Value& v, const mozilla::HashCodeScrambler& hcs) return v.toString()->asAtom().hash(); if (v.isSymbol()) return v.toSymbol()->hash(); + if (v.isBigInt()) + return MaybeForwarded(v.toBigInt())->hash(); if (v.isObject()) return hcs.scramble(v.asRawBits()); @@ -100,6 +102,12 @@ HashableValue::operator==(const HashableValue& other) const // Two HashableValues are equal if they have equal bits. bool b = (value.asRawBits() == other.value.asRawBits()); + // BigInt values are considered equal if they represent the same + // mathematical value. + if (!b && (value.isBigInt() && other.value.isBigInt())) { + b = BigInt::equal(value.toBigInt(), other.value.toBigInt()); + } + #ifdef DEBUG bool same; JS::RootingContext* rcx = GetJSContextFromMainThread(); @@ -378,8 +386,9 @@ MarkKey(Range& r, const HashableValue& key, JSTracer* trc) HashableValue newKey = key.mark(trc); if (newKey.get() != key.get()) { - // The hash function only uses the bits of the Value, so it is safe to - // rekey even when the object or string has been modified by the GC. + // The hash function must take account of the fact that the thing being + // hashed may have been moved by GC. This is only an issue for BigInt as for + // other types the hash function only uses the bits of the Value. r.rekeyFront(newKey); } } diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index 5221afb617..f4353480ba 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -7,6 +7,8 @@ #include "mozilla/ArrayUtils.h" +#include "builtin/BigInt.h" + #include "jscntxt.h" #include "jsstr.h" @@ -471,6 +473,9 @@ js::obj_toString(JSContext* cx, unsigned argc, Value* vp) case ESClass::RegExp: builtinTag = cx->names().objectRegExp; break; + case ESClass::BigInt: + builtinTag = cx->names().objectBigInt; + break; default: if (obj->isCallable()) { // Non-standard: Prevent <object> from showing up as Function. @@ -837,7 +842,9 @@ EnumerableOwnProperties(JSContext* cx, const JS::CallArgs& args, EnumerableOwnPr if (obj->is<NativeObject>()) { HandleNativeObject nobj = obj.as<NativeObject>(); if (JSID_IS_INT(id) && nobj->containsDenseElement(JSID_TO_INT(id))) { - value = nobj->getDenseOrTypedArrayElement(JSID_TO_INT(id)); + if(!nobj->getDenseOrTypedArrayElement<CanGC>(cx, JSID_TO_INT(id), &value)) { + return false; + } } else { shape = nobj->lookup(cx, id); if (!shape || !(shape->attributes() & JSPROP_ENUMERATE)) diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index f0b2001422..a8dd93aab2 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -21,6 +21,7 @@ #include "frontend/TokenStream.h" #include "js/CharacterEncoding.h" #include "vm/RegExpObject.h" +#include "vm/BigIntType.h" #include "jsobjinlines.h" @@ -3434,6 +3435,7 @@ ASTSerializer::expression(ParseNode* pn, MutableHandleValue dst) case PNK_STRING: case PNK_REGEXP: case PNK_NUMBER: + case PNK_BIGINT: case PNK_TRUE: case PNK_FALSE: case PNK_NULL: @@ -3604,7 +3606,7 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst) case PNK_REGEXP: { - RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object); + RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object()); LOCAL_ASSERT(re1 && re1->is<RegExpObject>()); RootedObject re2(cx, CloneRegExpObject(cx, re1)); @@ -3619,6 +3621,13 @@ ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst) val.setNumber(pn->as<NumericLiteral>().value()); break; + case PNK_BIGINT: + { + BigInt* x = pn->as<BigIntLiteral>().box()->value(); + val.setBigInt(x); + break; + } + case PNK_NULL: val.setNull(); break; diff --git a/js/src/builtin/TypedArray.js b/js/src/builtin/TypedArray.js index 22023aa7ca..57f6d738ca 100644 --- a/js/src/builtin/TypedArray.js +++ b/js/src/builtin/TypedArray.js @@ -879,7 +879,7 @@ function SetFromNonTypedArray(target, array, targetOffset, targetLength, targetB // Steps 12-15, 21, 23-24. while (targetOffset < limitOffset) { // Steps 24a-c. - var kNumber = ToNumber(src[k]); + var kNumber = ToNumeric(src[k]); // Step 24d. This explicit check will be unnecessary when we implement // throw-on-getting/setting-element-in-detached-buffer semantics. @@ -1098,6 +1098,30 @@ function TypedArrayCompare(x, y) { return Number_isNaN(y) ? -1 : 0; } +// https://tc39.github.io/proposal-bigint/#sec-%typedarray%.prototype.sort +// TypedArray SortCompare specialization for BigInt values. +function TypedArrayCompareBigInt(x, y) { + // Step 1. + // eslint-disable-next-line valid-typeof + assert(typeof x === "bigint" && typeof y === "bigint", + "x and y are not BigInts."); + + // Step 2 (Implemented in TypedArraySort). + + // Step 6. + if (x < y) + return -1; + + // Step 7. + if (x > y) + return 1; + + // Steps 3-5, 8-9 (Not applicable when sorting BigInt values). + + // Step 10. + return 0; +} + // TypedArray SortCompare specialization for integer values. function TypedArrayCompareInt(x, y) { // Step 1. diff --git a/js/src/builtin/TypedObject.h b/js/src/builtin/TypedObject.h index 83700001d4..9318a0f795 100644 --- a/js/src/builtin/TypedObject.h +++ b/js/src/builtin/TypedObject.h @@ -245,6 +245,10 @@ class ScalarTypeDescr : public SimpleTypeDescr "TypedObjectConstants.h must be consistent with Scalar::Type"); static_assert(Scalar::Uint32 == JS_SCALARTYPEREPR_UINT32, "TypedObjectConstants.h must be consistent with Scalar::Type"); + static_assert(Scalar::BigInt64 == JS_SCALARTYPEREPR_BIGINT64, + "TypedObjectConstants.h must be consistent with Scalar::Type"); + static_assert(Scalar::BigUint64 == JS_SCALARTYPEREPR_BIGUINT64, + "TypedObjectConstants.h must be consistent with Scalar::Type"); static_assert(Scalar::Float32 == JS_SCALARTYPEREPR_FLOAT32, "TypedObjectConstants.h must be consistent with Scalar::Type"); static_assert(Scalar::Float64 == JS_SCALARTYPEREPR_FLOAT64, @@ -270,14 +274,16 @@ class ScalarTypeDescr : public SimpleTypeDescr // unique C representation. In particular, omits Uint8Clamped since it // is just a Uint8. #define JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(macro_) \ - macro_(Scalar::Int8, int8_t, int8) \ - macro_(Scalar::Uint8, uint8_t, uint8) \ - macro_(Scalar::Int16, int16_t, int16) \ - macro_(Scalar::Uint16, uint16_t, uint16) \ - macro_(Scalar::Int32, int32_t, int32) \ - macro_(Scalar::Uint32, uint32_t, uint32) \ - macro_(Scalar::Float32, float, float32) \ - macro_(Scalar::Float64, double, float64) + macro_(Scalar::Int8, int8_t, int8) \ + macro_(Scalar::Uint8, uint8_t, uint8) \ + macro_(Scalar::Int16, int16_t, int16) \ + macro_(Scalar::Uint16, uint16_t, uint16) \ + macro_(Scalar::Int32, int32_t, int32) \ + macro_(Scalar::Uint32, uint32_t, uint32) \ + macro_(Scalar::Float32, float, float32) \ + macro_(Scalar::Float64, double, float64) \ + macro_(Scalar::BigInt64, int64_t, bigint64) \ + macro_(Scalar::BigUint64, uint64_t, biguint64) // Must be in same order as the enum ScalarTypeDescr::Type: #define JS_FOR_EACH_SCALAR_TYPE_REPR(macro_) \ diff --git a/js/src/builtin/TypedObjectConstants.h b/js/src/builtin/TypedObjectConstants.h index a28c9159a5..aa930d29bb 100644 --- a/js/src/builtin/TypedObjectConstants.h +++ b/js/src/builtin/TypedObjectConstants.h @@ -88,10 +88,12 @@ #define JS_SCALARTYPEREPR_FLOAT32 6 #define JS_SCALARTYPEREPR_FLOAT64 7 #define JS_SCALARTYPEREPR_UINT8_CLAMPED 8 -#define JS_SCALARTYPEREPR_FLOAT32X4 11 -#define JS_SCALARTYPEREPR_INT8X16 12 -#define JS_SCALARTYPEREPR_INT16X8 13 -#define JS_SCALARTYPEREPR_INT32X4 14 +#define JS_SCALARTYPEREPR_BIGINT64 9 +#define JS_SCALARTYPEREPR_BIGUINT64 10 +#define JS_SCALARTYPEREPR_FLOAT32X4 13 +#define JS_SCALARTYPEREPR_INT8X16 14 +#define JS_SCALARTYPEREPR_INT16X8 15 +#define JS_SCALARTYPEREPR_INT32X4 16 // These constants are for use exclusively in JS code. In C++ code, // prefer ReferenceTypeRepresentation::TYPE_ANY etc, which allows diff --git a/js/src/builtin/intl/CommonFunctions.h b/js/src/builtin/intl/CommonFunctions.h index c6856c9b6d..8c538a489b 100644 --- a/js/src/builtin/intl/CommonFunctions.h +++ b/js/src/builtin/intl/CommonFunctions.h @@ -18,6 +18,8 @@ #include "js/Vector.h"
#include "vm/String.h"
+#include "jscntxt.h"
+
namespace JS { class Value; }
class JSObject;
@@ -159,4 +161,4 @@ CallICU(JSContext* cx, const ICUStringFunction& strFn) } // namespace js
-#endif /* builtin_intl_CommonFunctions_h */
\ No newline at end of file +#endif /* builtin_intl_CommonFunctions_h */
diff --git a/js/src/builtin/intl/NumberFormat.cpp b/js/src/builtin/intl/NumberFormat.cpp index 9ee3b02109..8820166f56 100644 --- a/js/src/builtin/intl/NumberFormat.cpp +++ b/js/src/builtin/intl/NumberFormat.cpp @@ -33,6 +33,7 @@ using namespace js; using mozilla::AssertedCast;
using mozilla::IsFinite;
+using mozilla::IsNegative;
using mozilla::IsNaN;
using mozilla::IsNegativeZero;
using js::intl::CallICU;
@@ -401,24 +402,51 @@ NewUNumberFormat(JSContext* cx, Handle<NumberFormatObject*> numberFormat) }
static JSString*
-PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, double* x,
+PartitionNumberPattern(JSContext* cx, UNumberFormat* nf, HandleValue x,
UFieldPositionIterator* fpositer)
{
- // PartitionNumberPattern doesn't consider -0.0 to be negative.
- if (IsNegativeZero(*x))
- *x = 0.0;
-
- return CallICU(cx, [nf, x, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
- return unum_formatDoubleForFields(nf, *x, chars, size, fpositer, status);
- });
+ if (x.isNumber()) {
+ double num = x.toNumber();
+
+ // PartitionNumberPattern doesn't consider -0.0 to be negative.
+ if (IsNegativeZero(num))
+ num = 0.0;
+
+ return CallICU(cx, [nf, num, fpositer](UChar* chars, int32_t size, UErrorCode* status) {
+ return unum_formatDoubleForFields(nf, num, chars, size, fpositer, status);
+ });
+ } else if(x.isBigInt()) {
+ RootedBigInt bi(cx, x.toBigInt());
+ int64_t num;
+
+ if (BigInt::isInt64(bi, &num)) {
+ return CallICU(cx, [nf, num](UChar* chars, int32_t size, UErrorCode* status) {
+ return unum_formatInt64(nf, num, chars, size, nullptr, status);
+ });
+ } else {
+ JSLinearString* str = BigInt::toString(cx, bi, 10);
+ if (!str) {
+ return nullptr;
+ }
+ MOZ_ASSERT(str->hasLatin1Chars());
+
+ JS::AutoCheckCannotGC noGC(cx);
+ const char* latinchars = reinterpret_cast<const char*>(str->latin1Chars(noGC));
+ size_t length = str->length();
+ return CallICU(cx, [nf, latinchars, length](UChar* chars, int32_t size, UErrorCode* status) {
+ return unum_formatDecimal(nf, latinchars, length, chars, size, nullptr, status);
+ });
+ }
+ }
+ return nullptr;
}
bool
-js::intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
+js::FormatNumeric(JSContext* cx, UNumberFormat* nf, HandleValue x, MutableHandleValue result)
{
// Passing null for |fpositer| will just not compute partition information,
// letting us common up all ICU number-formatting code.
- JSString* str = PartitionNumberPattern(cx, nf, &x, nullptr);
+ JSString* str = PartitionNumberPattern(cx, nf, x, nullptr);
if (!str)
return false;
@@ -429,7 +457,7 @@ js::intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleV using FieldType = ImmutablePropertyNamePtr JSAtomState::*;
static FieldType
-GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d)
+GetFieldTypeForNumberField(UNumberFormatFields fieldName, HandleValue x)
{
// See intl/icu/source/i18n/unicode/unum.h for a detailed field list. This
// list is deliberately exhaustive: cases might have to be added/removed if
@@ -438,10 +466,15 @@ GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d) // version-testing #ifdefs, should cross-version divergence occur.
switch (fieldName) {
case UNUM_INTEGER_FIELD:
- if (IsNaN(d))
- return &JSAtomState::nan;
- if (!IsFinite(d))
- return &JSAtomState::infinity;
+ if (x.isNumber()) {
+ double d = x.toNumber();
+ if (IsNaN(d)) {
+ return &JSAtomState::nan;
+ }
+ if (!IsFinite(d)) {
+ return &JSAtomState::infinity;
+ }
+ }
return &JSAtomState::integer;
case UNUM_GROUPING_SEPARATOR_FIELD:
@@ -454,13 +487,17 @@ GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d) return &JSAtomState::fraction;
case UNUM_SIGN_FIELD: {
- MOZ_ASSERT(!IsNegativeZero(d),
- "-0 should have been excluded by PartitionNumberPattern");
-
- // Manual trawling through the ICU call graph appears to indicate that
- // the basic formatting we request will never include a positive sign.
- // But this analysis may be mistaken, so don't absolutely trust it.
- return d < 0 ? &JSAtomState::minusSign : &JSAtomState::plusSign;
+ // Manual trawling through the ICU call graph appears to indicate that
+ // the basic formatting we request will never include a positive sign.
+ // But this analysis may be mistaken, so don't absolutely trust it.
+ MOZ_ASSERT(!x.isNumber() || !IsNaN(x.toNumber()),
+ "ICU appearing not to produce positive-sign among fields, "
+ "plus our coercing all NaNs to one with sign bit unset "
+ "(i.e. \"positive\"), means we shouldn't reach here with a "
+ "NaN value");
+ bool isNegative =
+ x.isNumber() ? IsNegative(x.toNumber()) : x.toBigInt()->isNegative();
+ return isNegative ? &JSAtomState::minusSign : &JSAtomState::plusSign;
}
case UNUM_PERCENT_FIELD:
@@ -495,7 +532,7 @@ GetFieldTypeForNumberField(UNumberFormatFields fieldName, double d) }
static bool
-intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result)
+FormatNumericToParts(JSContext* cx, UNumberFormat* nf, HandleValue x, MutableHandleValue result)
{
UErrorCode status = U_ZERO_ERROR;
@@ -508,7 +545,7 @@ intl_FormatNumberToParts(JSContext* cx, UNumberFormat* nf, double x, MutableHand MOZ_ASSERT(fpositer);
ScopedICUObject<UFieldPositionIterator, ufieldpositer_close> toClose(fpositer);
- RootedString overallResult(cx, PartitionNumberPattern(cx, nf, &x, fpositer));
+ RootedString overallResult(cx, PartitionNumberPattern(cx, nf, x, fpositer));
if (!overallResult)
return false;
@@ -824,7 +861,7 @@ js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args[0].isObject());
- MOZ_ASSERT(args[1].isNumber());
+ MOZ_ASSERT(args[1].isNumeric());
MOZ_ASSERT(args[2].isBoolean());
Rooted<NumberFormatObject*> numberFormat(cx, &args[0].toObject().as<NumberFormatObject>());
@@ -842,8 +879,7 @@ js::intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp) // Use the UNumberFormat to actually format the number.
if (args[2].toBoolean()) {
- return intl_FormatNumberToParts(cx, nf, args[1].toNumber(), args.rval());
+ return FormatNumericToParts(cx, nf, args.get(1), args.rval());
}
- return intl_FormatNumber(cx, nf, args[1].toNumber(), args.rval());
+ return FormatNumeric(cx, nf, args.get(1), args.rval());
}
-
diff --git a/js/src/builtin/intl/NumberFormat.h b/js/src/builtin/intl/NumberFormat.h index befa0c3e0d..bc2f659527 100644 --- a/js/src/builtin/intl/NumberFormat.h +++ b/js/src/builtin/intl/NumberFormat.h @@ -71,7 +71,7 @@ intl_numberingSystem(JSContext* cx, unsigned argc, Value* vp); extern MOZ_MUST_USE bool
intl_FormatNumber(JSContext* cx, unsigned argc, Value* vp);
extern MOZ_MUST_USE bool
-intl_FormatNumber(JSContext* cx, UNumberFormat* nf, double x, MutableHandleValue result);
+FormatNumeric(JSContext* cx, UNumberFormat* nf, HandleValue x, MutableHandleValue result);
} // namespace js
diff --git a/js/src/builtin/intl/NumberFormat.js b/js/src/builtin/intl/NumberFormat.js index 238a59405b..261bff1dc6 100644 --- a/js/src/builtin/intl/NumberFormat.js +++ b/js/src/builtin/intl/NumberFormat.js @@ -454,14 +454,14 @@ function numberFormatFormatToBind(value) { // ES5.1 10.5, step 4.d.ii.
// Step 1.a.ii-iii.
- var x = ToNumber(value);
+ var x = ToNumeric(value);
return intl_FormatNumber(this, x, /* formatToParts = */ false);
}
/**
* Returns a function bound to this NumberFormat that returns a String value
- * representing the result of calling ToNumber(value) according to the
+ * representing the result of calling ToNumeric(value) according to the
* effective locale and the formatting options of this NumberFormat.
*
* Spec: ECMAScript Internationalization API Specification, 11.4.3.
@@ -497,7 +497,7 @@ function Intl_NumberFormat_formatToParts(value) { getNumberFormatInternals(nf);
// Step 4.
- var x = ToNumber(value);
+ var x = ToNumeric(value);
// Step 5.
return intl_FormatNumber(nf, x, /* formatToParts = */ true);
diff --git a/js/src/builtin/intl/PluralRules.cpp b/js/src/builtin/intl/PluralRules.cpp index e1e8e37044..ce2f3c3893 100644 --- a/js/src/builtin/intl/PluralRules.cpp +++ b/js/src/builtin/intl/PluralRules.cpp @@ -292,8 +292,6 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) if (!type)
return false;
- double x = args[1].toNumber();
-
// We need a NumberFormat in order to format the number
// using the number formatting options (minimum/maximum*Digits)
// before we push the result to PluralRules
@@ -302,7 +300,7 @@ js::intl_SelectPluralRule(JSContext* cx, unsigned argc, Value* vp) // API: http://bugs.icu-project.org/trac/ticket/12763
//
RootedValue fmtNumValue(cx);
- if (!intl_FormatNumber(cx, nf, x, &fmtNumValue))
+ if (!FormatNumeric(cx, nf, args[1], &fmtNumValue))
return false;
RootedString fmtNumValueString(cx, fmtNumValue.toString());
AutoStableStringChars stableChars(cx);
diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index e5a47c496a..462edfd91e 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -1093,6 +1093,11 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) *answer = false; return true; + case PNK_BIGINT: + MOZ_ASSERT(pn->is<BigIntLiteral>()); + *answer = false; + return true; + // |this| can throw in derived class constructors, including nested arrow // functions or eval. case PNK_THIS: @@ -4225,6 +4230,9 @@ ParseNode::getConstantValue(ExclusiveContext* cx, AllowConstantObjects allowObje case PNK_NUMBER: vp.setNumber(as<NumericLiteral>().value()); return true; + case PNK_BIGINT: + vp.setBigInt(as<BigIntLiteral>().box()->value()); + return true; case PNK_TEMPLATE_STRING: case PNK_STRING: vp.setString(as<NameNode>().atom()); @@ -4835,6 +4843,15 @@ BytecodeEmitter::emitCopyDataProperties(CopyOption option) } bool +BytecodeEmitter::emitBigIntOp(BigInt* bigint) +{ + if (!constList.append(BigIntValue(bigint))) { + return false; + } + return emitIndex32(JSOP_BIGINT, constList.length() - 1); +} + +bool BytecodeEmitter::emitIterator() { // Convert iterable to iterator. @@ -7737,10 +7754,23 @@ bool BytecodeEmitter::emitLeftAssociative(ListNode* node) { // Left-associative operator chain. - if (!emitTree(node->head())) - return false; JSOp op = node->getOp(); - ParseNode* nextExpr = node->head()->pn_next; + ParseNode* headExpr = node->head(); + if (op == JSOP_IN && headExpr->isKind(PNK_NAME) && headExpr->as<NameNode>().isPrivateName()) { + // {Goanna} The only way a "naked" private name can show up as the leftmost side of an in-chain + // is from an ergonomic brand check (`this.#x in ...` would be a PNK_DOT child node). + // Instead of going through the emitTree machinery, we pretend that this identifier + // reference is actually a string, which allows us to use the JSOP_IN interpreter routines. + // This erroneously doesn't call updateLineNumberNotes, but this is not a big issue: + // the begin pos is correct as we're on the start of the current tree, the end is on the + // same line anyway. + if (!emitAtomOp(headExpr->as<NameNode>().atom(), JSOP_STRING)) + return false; + } else { + if (!emitTree(headExpr)) + return false; + } + ParseNode* nextExpr = headExpr->pn_next; do { if (!emitTree(nextExpr)) return false; @@ -9579,6 +9609,12 @@ BytecodeEmitter::emitTree(ParseNode* pn, ValueUsage valueUsage /* = ValueUsage:: return false; break; + case PNK_BIGINT: + if (!emitBigIntOp(pn->as<BigIntLiteral>().box()->value())) { + return false; + } + break; + case PNK_REGEXP: if (!emitRegExp(objectList.add(pn->as<RegExpLiteral>().objbox()))) return false; @@ -10317,7 +10353,7 @@ CGConstList::finish(ConstArray* array) MOZ_ASSERT(length() == array->length); for (unsigned i = 0; i < length(); i++) - array->vector[i] = list[i]; + array->vector[i] = vector[i]; } /* @@ -10331,6 +10367,7 @@ CGConstList::finish(ConstArray* array) unsigned CGObjectList::add(ObjectBox* objbox) { + MOZ_ASSERT(objbox->isObjectBox()); MOZ_ASSERT(!objbox->emitLink); objbox->emitLink = lastbox; lastbox = objbox; @@ -10342,7 +10379,7 @@ CGObjectList::indexOf(JSObject* obj) { MOZ_ASSERT(length > 0); unsigned index = length - 1; - for (ObjectBox* box = lastbox; box->object != obj; box = box->emitLink) + for (ObjectBox* box = lastbox; box->object() != obj; box = box->emitLink) index--; return index; } @@ -10358,8 +10395,8 @@ CGObjectList::finish(ObjectArray* array) do { --cursor; MOZ_ASSERT(!*cursor); - MOZ_ASSERT(objbox->object->isTenured()); - *cursor = objbox->object; + MOZ_ASSERT(objbox->object()->isTenured()); + *cursor = objbox->object(); } while ((objbox = objbox->emitLink) != nullptr); MOZ_ASSERT(cursor == array->vector); } diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 732a1f24f8..9ca4856cf5 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -35,20 +35,21 @@ class SharedContext; class TokenStream; class CGConstList { - Vector<Value> list; + Rooted<ValueVector> vector; public: - explicit CGConstList(ExclusiveContext* cx) : list(cx) {} + explicit CGConstList(ExclusiveContext* cx) + : vector(cx, ValueVector(cx)) + { } MOZ_MUST_USE bool append(const Value& v) { - MOZ_ASSERT_IF(v.isString(), v.toString()->isAtom()); - return list.append(v); + return vector.append(v); } - size_t length() const { return list.length(); } + size_t length() const { return vector.length(); } void finish(ConstArray* array); }; struct CGObjectList { uint32_t length; /* number of emitted so far objects */ - ObjectBox* lastbox; /* last emitted object */ + ObjectBox* lastbox; /* last emitted object */ CGObjectList() : length(0), lastbox(nullptr) {} @@ -198,7 +199,7 @@ struct MOZ_STACK_CLASS BytecodeEmitter return innermostEmitterScope_; } - CGConstList constList; /* constants to be included with the script */ + CGConstList constList; /* double and bigint values used by script */ CGObjectList objectList; /* list of emitted objects */ CGScopeList scopeList; /* list of emitted scopes */ CGTryNoteList tryNoteList; /* list of emitted try notes */ @@ -478,6 +479,8 @@ struct MOZ_STACK_CLASS BytecodeEmitter MOZ_MUST_USE bool emitNumberOp(double dval); + MOZ_MUST_USE bool emitBigIntOp(BigInt* bigint); + MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn); MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn); MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 9fb963f63e..b457c43942 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -395,6 +395,7 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_THIS: case PNK_ELISION: case PNK_NUMBER: + case PNK_BIGINT: case PNK_NEW: case PNK_GENERATOR: case PNK_GENEXP: @@ -485,6 +486,7 @@ IsEffectless(ParseNode* node) node->isKind(PNK_FALSE) || node->isKind(PNK_STRING) || node->isKind(PNK_TEMPLATE_STRING) || + node->isKind(PNK_BIGINT) || node->isKind(PNK_NUMBER) || node->isKind(PNK_NULL) || node->isKind(PNK_RAW_UNDEFINED) || @@ -503,6 +505,9 @@ Boolish(ParseNode* pn, bool isNullish = false) return (isNullish || isNonZeroNumber) ? Truthy : Falsy; } + case PNK_BIGINT: + return (pn->as<BigIntLiteral>().box()->value()->isZero()) ? Falsy : Truthy; + case PNK_STRING: case PNK_TEMPLATE_STRING: { bool isNonZeroLengthString = (pn->as<NameNode>().atom()->length() > 0); @@ -591,6 +596,8 @@ FoldTypeOfExpr(ExclusiveContext* cx, UnaryNode* node, Parser<FullParseHandler>& result = cx->names().string; else if (expr->isKind(PNK_NUMBER)) result = cx->names().number; + else if (expr->isKind(PNK_BIGINT)) + result = cx->names().bigint; else if (expr->isKind(PNK_NULL)) result = cx->names().object; else if (expr->isKind(PNK_TRUE) || expr->isKind(PNK_FALSE)) @@ -1699,6 +1706,10 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser<FullParseHandler>& parser, bo MOZ_ASSERT(pn->is<NumericLiteral>()); return true; + case PNK_BIGINT: + MOZ_ASSERT(pn->is<BigIntLiteral>()); + return true; + case PNK_SUPERBASE: case PNK_TYPEOFNAME: { #ifdef DEBUG diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 14733d74f2..d2c8bbc2df 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -151,6 +151,18 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return new_<NumericLiteral>(value, decimalPoint, pos); } + // The Boxer object here is any object that can allocate BigIntBoxes. + // Specifically, a Boxer has a .newBigIntBox(T) method that accepts a + // BigInt* argument and returns a BigIntBox*. + template <class Boxer> + BigIntLiteralType newBigInt(BigInt* bi, const TokenPos& pos, Boxer& boxer) { + BigIntBox* box = boxer.newBigIntBox(bi); + if (!box) { + return null(); + } + return new_<BigIntLiteral>(box, pos); + } + BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { return new_<BooleanLiteral>(cond, pos); } @@ -953,6 +965,10 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return node->isKind(PNK_NAME); } + bool isPrivateName(Node node) { + return node->isKind(PNK_NAME) && node->as<NameNode>().isPrivateName(); + } + bool isArgumentsAnyParentheses(Node node, ExclusiveContext* cx) { return node->isKind(PNK_NAME) && node->as<NameNode>().atom() == cx->names().arguments; } diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index 6ddb554ad7..3fbdd3fc8a 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -420,6 +420,9 @@ class NameResolver MOZ_ASSERT(cur->is<NumericLiteral>()); break; + case PNK_BIGINT: + MOZ_ASSERT(cur->is<BigIntLiteral>()); + break; case PNK_TYPEOFNAME: case PNK_SUPERBASE: diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 7ad470865f..065efa8380 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -218,6 +218,10 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) MOZ_ASSERT(pn->is<NumericLiteral>()); return PushResult::Recyclable; + case PNK_BIGINT: + MOZ_ASSERT(pn->is<BigIntLiteral>()); + return PushResult::Recyclable; + // Nodes with a single non-null child. case PNK_TYPEOFNAME: case PNK_TYPEOFEXPR: @@ -716,6 +720,9 @@ ParseNode::dump(int indent) case PN_NUMBER: as<NumericLiteral>().dump(indent); return; + case PN_BIGINT: + as<BigIntLiteral>().dump(indent); + return; case PN_REGEXP: as<RegExpLiteral>().dump(indent); return; @@ -760,6 +767,12 @@ NumericLiteral::dump(int indent) } void +BigIntLiteral::dump(int indent) +{ + fprintf(stderr, "(%s)", parseNodeNames[size_t(getKind())]); +} + +void RegExpLiteral::dump(int indent) { fprintf(stderr, "(%s)", parseNodeNames[size_t(getKind())]); @@ -962,23 +975,45 @@ LexicalScopeNode::dump(int indent) } #endif -ObjectBox::ObjectBox(JSObject* object, ObjectBox* traceLink) - : object(object), - traceLink(traceLink), +TraceListNode::TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink) + : gcThing(gcThing), + traceLink(traceLink) +{ + MOZ_ASSERT(gcThing->isTenured()); +} + +BigIntBox* +TraceListNode::asBigIntBox() +{ + MOZ_ASSERT(isBigIntBox()); + return static_cast<BigIntBox*>(this); +} + +ObjectBox* +TraceListNode::asObjectBox() +{ + MOZ_ASSERT(isObjectBox()); + return static_cast<ObjectBox*>(this); +} + +BigIntBox::BigIntBox(BigInt* bi, TraceListNode* traceLink) + : TraceListNode(bi, traceLink) +{ +} + +ObjectBox::ObjectBox(JSObject* obj, TraceListNode* traceLink) + : TraceListNode(obj, traceLink), emitLink(nullptr) { - MOZ_ASSERT(!object->is<JSFunction>()); - MOZ_ASSERT(object->isTenured()); + MOZ_ASSERT(!object()->is<JSFunction>()); } -ObjectBox::ObjectBox(JSFunction* function, ObjectBox* traceLink) - : object(function), - traceLink(traceLink), +ObjectBox::ObjectBox(JSFunction* function, TraceListNode* traceLink) + : TraceListNode(function, traceLink), emitLink(nullptr) { - MOZ_ASSERT(object->is<JSFunction>()); + MOZ_ASSERT(object()->is<JSFunction>()); MOZ_ASSERT(asFunctionBox()->function() == function); - MOZ_ASSERT(object->isTenured()); } FunctionBox* @@ -989,16 +1024,17 @@ ObjectBox::asFunctionBox() } /* static */ void -ObjectBox::TraceList(JSTracer* trc, ObjectBox* listHead) +TraceListNode::TraceList(JSTracer* trc, TraceListNode* listHead) { - for (ObjectBox* box = listHead; box; box = box->traceLink) - box->trace(trc); + for (TraceListNode* node = listHead; node; node = node->traceLink) { + node->trace(trc); + } } void -ObjectBox::trace(JSTracer* trc) +TraceListNode::trace(JSTracer* trc) { - TraceRoot(trc, &object, "parser.object"); + TraceGenericPointerRoot(trc, &gcThing, "parser.traceListNode"); } void diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 46977ee253..439a6d8937 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -12,6 +12,7 @@ #include "builtin/ModuleObject.h" #include "frontend/TokenStream.h" +#include "vm/BigIntType.h" namespace js { namespace frontend { @@ -20,6 +21,7 @@ class ParseContext; class FullParseHandler; class FunctionBox; class ObjectBox; +class BigIntBox; #define FOR_EACH_PARSE_NODE_KIND(F) \ F(NOP) \ @@ -53,6 +55,7 @@ class ObjectBox; F(OBJECT_PROPERTY_NAME) \ F(COMPUTED_NAME) \ F(NUMBER) \ + F(BIGINT) \ F(STRING) \ F(TEMPLATE_STRING_LIST) \ F(TEMPLATE_STRING) \ @@ -524,6 +527,8 @@ IsTypeofKind(ParseNodeKind kind) * regexp: RegExp model object * PNK_NUMBER (NumericLiteral) * value: double value of numeric literal + * PNK_BIGINT (BigIntLiteral) + * box: BigIntBox holding BigInt* value * PNK_TRUE, PNK_FALSE (BooleanLiteral) * pn_op: JSOp bytecode * PNK_NULL (NullLiteral) @@ -571,6 +576,7 @@ enum ParseNodeArity PN_LIST, /* generic singly linked list */ PN_NAME, /* name, label, string */ PN_NUMBER, /* numeric literal */ + PN_BIGINT, /* BigInt literal */ PN_REGEXP, /* regexp literal */ PN_LOOP, /* loop control (break/continue) */ PN_SCOPE /* lexical scope */ @@ -613,6 +619,7 @@ enum ParseNodeArity macro(RawUndefinedLiteral, RawUndefinedLiteralType, asRawUndefinedLiteral) \ \ macro(NumericLiteral, NumericLiteralType, asNumericLiteral) \ + macro(BigIntLiteral, BigIntLiteralType, asBigIntLiteral) \ \ macro(RegExpLiteral, RegExpLiteralType, asRegExpLiteral) \ \ @@ -828,6 +835,11 @@ class ParseNode double value; /* aligned numeric literal value */ DecimalPoint decimalPoint; /* Whether the number has a decimal point */ } number; + struct { + private: + friend class BigIntLiteral; + BigIntBox* box; + } bigint; class { private: friend class LoopControlStatement; @@ -849,6 +861,7 @@ class ParseNode /* True if pn is a parsenode representing a literal constant. */ bool isLiteral() const { return isKind(PNK_NUMBER) || + isKind(PNK_BIGINT) || isKind(PNK_STRING) || isKind(PNK_TRUE) || isKind(PNK_FALSE) || @@ -940,6 +953,10 @@ class NameNode : public ParseNode JSAtom* atom() const { return pn_u.name.atom; } + + bool isPrivateName() const { + return atom()->asPropertyName()->latin1OrTwoByteChar(0) == '#'; + } ParseNode* initializer() const { return pn_u.name.initOrStmt; @@ -1631,6 +1648,30 @@ class NumericLiteral : public ParseNode } }; +class BigIntLiteral : public ParseNode +{ + public: + BigIntLiteral(BigIntBox* bibox, const TokenPos& pos) + : ParseNode(PNK_BIGINT, JSOP_NOP, PN_BIGINT, pos) + { + pn_u.bigint.box = bibox; + } + + static bool test(const ParseNode& node) { + bool match = node.isKind(PNK_BIGINT); + MOZ_ASSERT_IF(match, node.isArity(PN_BIGINT)); + return match; + } + +#ifdef DEBUG + void dump(int indent); +#endif + + BigIntBox* box() const { + return pn_u.bigint.box; + } +}; + class LexicalScopeNode : public ParseNode { public: @@ -2350,25 +2391,48 @@ ParseNode::isConstant() } } -class ObjectBox +class TraceListNode { - public: - JSObject* object; + protected: + js::gc::Cell* gcThing; + TraceListNode* traceLink; + + TraceListNode(js::gc::Cell* gcThing, TraceListNode* traceLink); + + bool isBigIntBox() const { return gcThing->is<BigInt>(); } + bool isObjectBox() const { return gcThing->is<JSObject>(); } + + BigIntBox* asBigIntBox(); + ObjectBox* asObjectBox(); - ObjectBox(JSObject* object, ObjectBox* traceLink); - bool isFunctionBox() { return object->is<JSFunction>(); } - FunctionBox* asFunctionBox(); virtual void trace(JSTracer* trc); - static void TraceList(JSTracer* trc, ObjectBox* listHead); + public: + static void TraceList(JSTracer* trc, TraceListNode* listHead); +}; + +class BigIntBox : public TraceListNode +{ + public: + BigIntBox(BigInt* bi, TraceListNode* link); + BigInt* value() const { return gcThing->as<BigInt>(); } +}; +class ObjectBox : public TraceListNode +{ protected: friend struct CGObjectList; - - ObjectBox* traceLink; ObjectBox* emitLink; + + ObjectBox(JSFunction* function, TraceListNode* link); - ObjectBox(JSFunction* function, ObjectBox* traceLink); + public: + ObjectBox(JSObject* obj, TraceListNode* link); + + JSObject* object() const { return gcThing->as<JSObject>(); } + + bool isFunctionBox() const { return object()->is<JSFunction>(); } + FunctionBox* asFunctionBox(); }; enum ParseReportKind diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index a1b669bf91..4e3181e06b 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -438,7 +438,7 @@ UsedNameTracker::rewind(RewindToken token) r.front().value().resetToScope(token.scriptId, token.scopeId); } -FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, +FunctionBox::FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, TraceListNode* traceListHead, JSFunction* fun, uint32_t toStringStart, Directives directives, bool extraWarnings, GeneratorKind generatorKind, FunctionAsyncKind asyncKind) @@ -882,11 +882,11 @@ Parser<FullParseHandler>::setAwaitHandling(AwaitHandling awaitHandling) parser->setAwaitHandling(awaitHandling); } -template <typename ParseHandler> -ObjectBox* -Parser<ParseHandler>::newObjectBox(JSObject* obj) +template <typename BoxT, typename ArgT> +BoxT* +ParserBase::newTraceListNode(ArgT* arg) { - MOZ_ASSERT(obj); + MOZ_ASSERT(arg); /* * We use JSContext.tempLifoAlloc to allocate parsed objects and place them @@ -896,15 +896,27 @@ Parser<ParseHandler>::newObjectBox(JSObject* obj) * function. */ - ObjectBox* objbox = alloc.new_<ObjectBox>(obj, traceListHead); - if (!objbox) { + BoxT* box = alloc.template new_<BoxT>(arg, traceListHead); + if (!box) { ReportOutOfMemory(context); return nullptr; } - traceListHead = objbox; + traceListHead = box; + + return box; +} + +ObjectBox* +ParserBase::newObjectBox(JSObject* obj) +{ + return newTraceListNode<ObjectBox, JSObject>(obj); +} - return objbox; +BigIntBox* +ParserBase::newBigIntBox(BigInt* val) +{ + return newTraceListNode<BigIntBox, BigInt>(val); } template <typename ParseHandler> @@ -8937,6 +8949,15 @@ Parser<ParseHandler>::orExpr1(InHandling inHandling, YieldHandling yieldHandling if (!tokenStream.getToken(&tok)) return null(); + // Ensure that if we have a private name lhs we are legally constructing a + // `#x in obj` expression: + if (handler.isPrivateName(pn)) { + if (tok != TOK_IN) { + error(JSMSG_ILLEGAL_PRIVATE_NAME); + return null(); + } + } + ParseNodeKind pnk; if (tok == TOK_IN ? inHandling == InAllowed : TokenKindIsBinaryOp(tok)) { // We're definitely not in a destructuring context, so report any @@ -8973,7 +8994,20 @@ Parser<ParseHandler>::orExpr1(InHandling inHandling, YieldHandling yieldHandling // If we have not detected a mixing error at this point, record that // we have an unparenthesized expression, in case we have one later. unparenthesizedExpression = EnforcedParentheses::CoalesceExpr; - break; + break; + case TOK_IN: + // if the LHS is a private name, and the operator is In, + // ensure we're construcing an ergonomic brand check of + // '#x in y', rather than having a higher precedence operator + // like + cause a different reduction, such as + // 1 + #x in y. + if (handler.isPrivateName(pn)) { + if (depth > 0 && Precedence(kindStack[depth - 1]) >= Precedence(PNK_IN)) { + error(JSMSG_ILLEGAL_PRIVATE_NAME); + return null(); + } + } + break; default: // Do nothing in other cases. break; @@ -10579,6 +10613,37 @@ Parser<ParseHandler>::newRegExp() return handler.newRegExp(reobj, pos(), *this); } +template <> +BigIntLiteral* +Parser<FullParseHandler>::newBigInt() +{ + // The token's charBuffer contains the DecimalIntegerLiteral or + // NumericLiteralBase production, and as such does not include the + // BigIntLiteralSuffix (the trailing "n"). Note that NumericLiteralBase + // productions may start with 0[bBoOxX], indicating binary/octal/hex. + const auto& chars = tokenStream.getTokenbuf(); + mozilla::Range<const char16_t> source(chars.begin(), chars.length()); + + BigInt* b = js::ParseBigIntLiteral(context, source); + if (!b) { + return null(); + } + + // newBigInt immediately puts "b" in a BigIntBox, which is allocated using + // tempLifoAlloc, avoiding any potential GC. Therefore it's OK to pass a + // raw pointer. + return handler.newBigInt(b, pos(), *this); +} + +template <> +SyntaxParseHandler::BigIntLiteralType +Parser<SyntaxParseHandler>::newBigInt() +{ + // The tokenizer has already checked the syntax of the bigint. + + return handler.newBigInt(); +} + template <typename ParseHandler> void Parser<ParseHandler>::checkDestructuringAssignmentTarget(Node expr, TokenPos exprPos, @@ -11474,6 +11539,9 @@ Parser<ParseHandler>::primaryExpr(YieldHandling yieldHandling, TripledotHandling case TOK_NUMBER: return newNumber(tokenStream.currentToken()); + case TOK_BIGINT: + return newBigInt(); + case TOK_TRUE: return handler.newBooleanLiteral(true, pos()); case TOK_FALSE: diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index fd1bd034c4..4a8e038d6e 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -779,8 +779,8 @@ class ParserBase : public StrictModeGetter TokenStream tokenStream; LifoAlloc::Mark tempPoolMark; - /* list of parsed objects for GC tracing */ - ObjectBox* traceListHead; + /* list of parsed objects and BigInts for GC tracing */ + TraceListNode* traceListHead; /* innermost parse context (stack-allocated) */ ParseContext* pc; @@ -915,6 +915,13 @@ class ParserBase : public StrictModeGetter bool warnOnceAboutExprClosure(); bool warnOnceAboutForEach(); + ObjectBox* newObjectBox(JSObject* obj); + BigIntBox* newBigIntBox(BigInt* val); + +private: + template <typename BoxT, typename ArgT> + BoxT* newTraceListNode(ArgT* arg); + protected: enum InvokedPrediction { PredictUninvoked = false, PredictInvoked = true }; enum ForInitLocation { InForInit, NotInForInit }; @@ -1085,7 +1092,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) { friend class Parser; LifoAlloc::Mark mark; - ObjectBox* traceListHead; + TraceListNode* traceListHead; }; Mark mark() const { Mark m; @@ -1174,7 +1181,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) * Allocate a new parsed object or function container from * cx->tempLifoAlloc. */ - ObjectBox* newObjectBox(JSObject* obj); + public: FunctionBox* newFunctionBox(FunctionNodeType funNode, JSFunction* fun, uint32_t toStringStart, Directives directives, GeneratorKind generatorKind, FunctionAsyncKind asyncKind, @@ -1660,6 +1667,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_TYPE) const mozilla::Maybe<DeclarationKind>& maybeDecl, ListNodeType literal); ListNodeType arrayInitializer(YieldHandling yieldHandling, PossibleError* possibleError); RegExpLiteralType newRegExp(); + BigIntLiteralType newBigInt(); ListNodeType objectLiteral(YieldHandling yieldHandling, PossibleError* possibleError); diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index a241907482..bc0212054b 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -443,7 +443,7 @@ class FunctionBox : public ObjectBox, public SharedContext FunctionContextFlags funCxFlags; - FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, ObjectBox* traceListHead, JSFunction* fun, + FunctionBox(ExclusiveContext* cx, LifoAlloc& alloc, TraceListNode* traceListHead, JSFunction* fun, uint32_t toStringStart, Directives directives, bool extraWarnings, GeneratorKind generatorKind, FunctionAsyncKind asyncKind); @@ -467,7 +467,8 @@ class FunctionBox : public ObjectBox, public SharedContext void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); ObjectBox* toObjectBox() override { return this; } - JSFunction* function() const { return &object->as<JSFunction>(); } + JSFunction* function() const { return &object()->as<JSFunction>(); } + void clobberFunction(JSFunction* function) { gcThing = function; } Scope* compilationEnclosingScope() const override { // This method is used to distinguish the outermost SharedContext. If diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 130a5da61d..80ca13ce45 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -104,6 +104,9 @@ class SyntaxParseHandler // Node representing the "async" name, which may actually be a // contextual keyword. NodePotentialAsyncKeyword, + + // Node representing a private name. Handled mostly like NodeUnparenthesizedName. + NodePrivateName, // Valuable for recognizing potential destructuring patterns. NodeUnparenthesizedArray, @@ -212,6 +215,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return NodePotentialAsyncKeyword; if (name == cx->names().eval) return NodeUnparenthesizedEvalName; + if (name->length() >= 1 && name->latin1OrTwoByteChar(0) == '#') + return NodePrivateName; return NodeUnparenthesizedName; } @@ -224,6 +229,7 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) } NumericLiteralType newNumber(double value, DecimalPoint decimalPoint, const TokenPos& pos) { return NodeGeneric; } + BigIntLiteralType newBigInt() { return NodeGeneric; } BooleanLiteralType newBooleanLiteral(bool cond, const TokenPos& pos) { return NodeGeneric; } NameNodeType newStringLiteral(JSAtom* atom, const TokenPos& pos) { @@ -613,7 +619,8 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) return node == NodeUnparenthesizedArgumentsName || node == NodeUnparenthesizedEvalName || node == NodeUnparenthesizedName || - node == NodePotentialAsyncKeyword; + node == NodePotentialAsyncKeyword || + node == NodePrivateName; } bool isNameAnyParentheses(Node node) { @@ -624,6 +631,10 @@ FOR_EACH_PARSENODE_SUBCLASS(DECLARE_AS) node == NodeParenthesizedName; } + bool isPrivateName(Node node) { + return node == NodePrivateName; + } + bool isArgumentsAnyParentheses(Node node, ExclusiveContext* cx) { return node == NodeUnparenthesizedArgumentsName || node == NodeParenthesizedArgumentsName; } diff --git a/js/src/frontend/TokenKind.h b/js/src/frontend/TokenKind.h index 232e373dcf..3e1ff68bbd 100644 --- a/js/src/frontend/TokenKind.h +++ b/js/src/frontend/TokenKind.h @@ -74,6 +74,7 @@ macro(PRIVATE_NAME, "private identifier") \ macro(NUMBER, "numeric literal") \ macro(STRING, "string literal") \ + macro(BIGINT, "bigint literal") \ \ /* start of template literal with substitutions */ \ macro(TEMPLATE_HEAD, "'${'") \ diff --git a/js/src/frontend/TokenStream.cpp b/js/src/frontend/TokenStream.cpp index a201e42a52..b11c7df584 100644 --- a/js/src/frontend/TokenStream.cpp +++ b/js/src/frontend/TokenStream.cpp @@ -1382,6 +1382,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) const char16_t* identStart; NameVisibility identVisibility; bool hadUnicodeEscape; + bool isBigInt = false; // Check if in the middle of a template string. Have to get this out of // the way first. @@ -1619,6 +1620,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } } while (true); } + if (c == 'n') { + isBigInt = true; + c = getCharIgnoreEOL(); + } ungetCharIgnoreEOL(c); if (c != EOF) { @@ -1638,6 +1643,19 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } } + if (isBigInt) { + size_t length = userbuf.addressOfNextRawChar() - numStart - 1; + tokenbuf.clear(); + if(!tokenbuf.reserve(length > 0 ? length : 1)) + goto error; + if(length > 0) + tokenbuf.infallibleAppend(numStart, length); + else + tokenbuf.infallibleAppend("0", 1); + tp->type = TOK_BIGINT; + goto out; + } + // Unlike identifiers and strings, numbers cannot contain escaped // chars, so we don't need to use tokenbuf. Instead we can just // convert the char16_t characters in userbuf to the numeric value. @@ -1677,7 +1695,7 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) // if (c1kind == BasePrefix) { tp = newToken(-1); - int radix; + int radix = 10; c = getCharIgnoreEOL(); if (c == 'x' || c == 'X') { radix = 16; @@ -1777,6 +1795,10 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) hasExp = false; goto decimal_rest; } + if (c == 'n') { + isBigInt = true; + c = getCharIgnoreEOL(); + } ungetCharIgnoreEOL(c); if (c != EOF) { @@ -1796,6 +1818,28 @@ TokenStream::getTokenInternal(TokenKind* ttp, Modifier modifier) } } + if (isBigInt) { + size_t length = userbuf.addressOfNextRawChar() - numStart - 1; + tokenbuf.clear(); + if(!tokenbuf.reserve(radix == 10 ? length : (length + 2))) + goto error; + switch(radix) + { + case 2: + tokenbuf.infallibleAppend("0b", 2); + break; + case 8: + tokenbuf.infallibleAppend("0o", 2); + break; + case 16: + tokenbuf.infallibleAppend("0x", 2); + break; + } + tokenbuf.infallibleAppend(numStart, length); + tp->type = TOK_BIGINT; + goto out; + } + double dval; const char16_t* dummy; if (!GetPrefixInteger(cx, numStart, userbuf.addressOfNextRawChar(), radix, diff --git a/js/src/gc/GCInternals.h b/js/src/gc/GCInternals.h index 5c51540351..c0e980a7c1 100644 --- a/js/src/gc/GCInternals.h +++ b/js/src/gc/GCInternals.h @@ -14,6 +14,7 @@ #include "gc/Zone.h" #include "vm/HelperThreads.h" #include "vm/Runtime.h" +#include "vm/BigIntType.h" namespace js { namespace gc { @@ -71,6 +72,7 @@ struct MovingTracer : JS::CallbackTracer void onObjectEdge(JSObject** objp) override; void onShapeEdge(Shape** shapep) override; void onStringEdge(JSString** stringp) override; + void onBigIntEdge(JS::BigInt** bip) override; void onScriptEdge(JSScript** scriptp) override; void onLazyScriptEdge(LazyScript** lazyp) override; void onBaseShapeEdge(BaseShape** basep) override; diff --git a/js/src/gc/Heap.h b/js/src/gc/Heap.h index 371e4119f9..3e14ed906f 100644 --- a/js/src/gc/Heap.h +++ b/js/src/gc/Heap.h @@ -111,6 +111,7 @@ enum class AllocKind : uint8_t { FAT_INLINE_ATOM, ATOM, SYMBOL, + BIGINT, JITCODE, SCOPE, REGEXP_SHARED, @@ -151,6 +152,7 @@ enum class AllocKind : uint8_t { D(FAT_INLINE_ATOM, String, js::FatInlineAtom, js::FatInlineAtom) \ D(ATOM, String, js::NormalAtom, js::NormalAtom) \ D(SYMBOL, Symbol, JS::Symbol, JS::Symbol) \ + D(BIGINT, BigInt, JS::BigInt, JS::BigInt) \ D(JITCODE, JitCode, js::jit::JitCode, js::jit::JitCode) \ D(SCOPE, Scope, js::Scope, js::Scope) \ D(REGEXP_SHARED, RegExpShared, js::RegExpShared, js::RegExpShared) diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 42c872a1da..1376df5a4d 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -21,6 +21,7 @@ #include "js/SliceBudget.h" #include "vm/ArgumentsObject.h" #include "vm/ArrayObject.h" +#include "vm/BigIntType.h" #include "vm/Debugger.h" #include "vm/EnvironmentObject.h" #include "vm/Scope.h" @@ -787,7 +788,8 @@ ShouldMark<JSObject*>(GCMarker* gcmarker, JSObject* obj) // Don't mark things outside a zone if we are in a per-zone GC. It is // faster to check our own arena, which we can do since we know that // the object is tenured. - return obj->asTenured().zone()->shouldMarkInZone(); + Zone* zone = obj->asTenured().zone(); + return (zone && zone->shouldMarkInZone()); } template <typename T> @@ -875,6 +877,7 @@ js::GCMarker::markAndTraceChildren(T* thing) namespace js { template <> void GCMarker::traverse(BaseShape* thing) { markAndTraceChildren(thing); } template <> void GCMarker::traverse(JS::Symbol* thing) { markAndTraceChildren(thing); } +template <> void GCMarker::traverse(JS::BigInt* thing) { markAndTraceChildren(thing); } template <> void GCMarker::traverse(RegExpShared* thing) { markAndTraceChildren(thing); } } // namespace js @@ -1458,6 +1461,12 @@ js::GCMarker::lazilyMarkChildren(ObjectGroup* group) traverseEdge(group, static_cast<JSObject*>(fun)); } +void +JS::BigInt::traceChildren(JSTracer* trc) +{ + return; +} + struct TraverseObjectFunctor { template <typename T> @@ -1704,6 +1713,8 @@ GCMarker::processMarkStackTop(SliceBudget& budget) } } else if (v.isSymbol()) { traverseEdge(obj, v.toSymbol()); + } else if (v.isBigInt()) { + traverseEdge(obj, v.toBigInt()); } else if (v.isPrivateGCThing()) { traverseEdge(obj, v.toGCCellPtr()); } diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index 40b331b311..414079f799 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -425,6 +425,7 @@ struct RewrapTaggedPointer{}; DECLARE_REWRAP(JS::Value, JSObject, JS::ObjectOrNullValue, ); DECLARE_REWRAP(JS::Value, JSString, JS::StringValue, ); DECLARE_REWRAP(JS::Value, JS::Symbol, JS::SymbolValue, ); +DECLARE_REWRAP(JS::Value, JS::BigInt, JS::BigIntValue, ); DECLARE_REWRAP(jsid, JSString, NON_INTEGER_ATOM_TO_JSID, (JSAtom*)); DECLARE_REWRAP(jsid, JS::Symbol, SYMBOL_TO_JSID, ); DECLARE_REWRAP(js::TaggedProto, JSObject, js::TaggedProto, ); @@ -435,7 +436,8 @@ struct IsPrivateGCThingInValue : public mozilla::EnableIf<mozilla::IsBaseOf<Cell, T>::value && !mozilla::IsBaseOf<JSObject, T>::value && !mozilla::IsBaseOf<JSString, T>::value && - !mozilla::IsBaseOf<JS::Symbol, T>::value, T> + !mozilla::IsBaseOf<JS::Symbol, T>::value && + !mozilla::IsBaseOf<JS::BigInt, T>::value, T> { static_assert(!mozilla::IsSame<Cell, T>::value && !mozilla::IsSame<TenuredCell, T>::value, "T must not be Cell or TenuredCell"); diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp index 26a9e5ff78..7be4543029 100644 --- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -20,6 +20,7 @@ #include "gc/Marking.h" #include "gc/Zone.h" +#include "vm/BigIntType.h" #include "vm/Shape.h" #include "vm/Symbol.h" @@ -319,6 +320,10 @@ JS_GetTraceThingInfo(char* buf, size_t bufsize, JSTracer* trc, void* thing, name = "symbol"; break; + case JS::TraceKind::BigInt: + name = "BigInt"; + break; + case JS::TraceKind::BaseShape: name = "base_shape"; break; @@ -339,6 +344,10 @@ JS_GetTraceThingInfo(char* buf, size_t bufsize, JSTracer* trc, void* thing, name = "object_group"; break; + case JS::TraceKind::RegExpShared: + name = "reg_exp_shared"; + break; + default: name = "INVALID"; break; diff --git a/js/src/gdb/mozilla/jsval.py b/js/src/gdb/mozilla/jsval.py index f5ae78b1c2..d6f5e01fdf 100644 --- a/js/src/gdb/mozilla/jsval.py +++ b/js/src/gdb/mozilla/jsval.py @@ -165,6 +165,15 @@ class jsvalTypeCache(object): self.NULL = d['JSVAL_TYPE_NULL'] self.OBJECT = d['JSVAL_TYPE_OBJECT'] + self.enable_bigint = False + try: + # Looking up the tag will throw an exception if BigInt is not + # enabled. + self.BIGINT = get('JSVAL_TYPE_BIGINT') + self.enable_bigint = True + except: + pass + # Let self.magic_names be an array whose i'th element is the name of # the i'th magic value. d = gdb.types.make_enum_dict(gdb.lookup_type('JSWhyMagic')) @@ -206,6 +215,8 @@ class jsval_layout(object): value = self.box.as_address().cast(self.cache.JSString_ptr_t) elif tag == self.jtc.SYMBOL: value = self.box.as_address().cast(self.cache.JSSymbol_ptr_t) + elif self.jtc.enable_bigint and tag == self.jtc.BIGINT: + return '$JS::BigIntValue()' elif tag == self.jtc.NULL: return 'JSVAL_NULL' elif tag == self.jtc.OBJECT: diff --git a/js/src/gdb/tests/test-jsval.cpp b/js/src/gdb/tests/test-jsval.cpp index f3c3247e2c..2007464cbe 100644 --- a/js/src/gdb/tests/test-jsval.cpp +++ b/js/src/gdb/tests/test-jsval.cpp @@ -1,6 +1,8 @@ #include "gdb-tests.h" #include "jsapi.h" +#include "vm/BigIntType.h" + FRAGMENT(jsval, simple) { using namespace JS; @@ -17,6 +19,7 @@ FRAGMENT(jsval, simple) { RootedString hello(cx, JS_NewStringCopyZ(cx, "Hello!")); RootedValue friendly_string(cx, StringValue(hello)); RootedValue symbol(cx, SymbolValue(GetSymbolFor(cx, hello))); + RootedValue bi(cx, BigIntValue(BigInt::zero(cx))); RootedValue global(cx); global.setObject(*CurrentGlobalOrNull(cx)); @@ -36,5 +39,6 @@ FRAGMENT(jsval, simple) { (void) empty_string; (void) friendly_string; (void) symbol; + (void) bi; (void) global; } diff --git a/js/src/gdb/tests/test-jsval.py b/js/src/gdb/tests/test-jsval.py index f39a6591fd..38edb28197 100644 --- a/js/src/gdb/tests/test-jsval.py +++ b/js/src/gdb/tests/test-jsval.py @@ -14,5 +14,7 @@ assert_pretty('elements_hole', '$jsmagic(JS_ELEMENTS_HOLE)') assert_pretty('empty_string', '$jsval("")') assert_pretty('friendly_string', '$jsval("Hello!")') assert_pretty('symbol', '$jsval(Symbol.for("Hello!"))') +if enable_bigint: + assert_pretty('bi', '$JS::BigIntValue()') assert_pretty('global', '$jsval((JSObject *) [object global] delegate)') assert_pretty('onehundredthirtysevenonehundredtwentyeighths', '$jsval(1.0703125)') diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index 30c83a5042..ce27e4de19 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -1958,6 +1958,7 @@ jit::FinishBailoutToBaseline(BaselineBailoutInfo* bailoutInfo) case Bailout_NonObjectInput: case Bailout_NonStringInput: case Bailout_NonSymbolInput: + case Bailout_NonBigIntInput: case Bailout_UnexpectedSimdInput: case Bailout_NonSharedTypedArrayInput: case Bailout_Debugger: diff --git a/js/src/jit/BaselineCacheIR.cpp b/js/src/jit/BaselineCacheIR.cpp index 9bea352ae7..5317f0e4e5 100644 --- a/js/src/jit/BaselineCacheIR.cpp +++ b/js/src/jit/BaselineCacheIR.cpp @@ -710,6 +710,9 @@ BaselineCacheIRCompiler::emitGuardType() case JSVAL_TYPE_SYMBOL: masm.branchTestSymbol(Assembler::NotEqual, input, failure->label()); break; + case JSVAL_TYPE_BIGINT: + masm.branchTestBigInt(Assembler::NotEqual, input, failure->label()); + break; case JSVAL_TYPE_DOUBLE: masm.branchTestNumber(Assembler::NotEqual, input, failure->label()); break; diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 74cfb82e43..7dbd076c6b 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -1621,6 +1621,13 @@ BaselineCompiler::emit_JSOP_DOUBLE() } bool +BaselineCompiler::emit_JSOP_BIGINT() +{ + frame.push(script->getConst(GET_UINT32_INDEX(pc))); + return true; +} + +bool BaselineCompiler::emit_JSOP_STRING() { frame.push(StringValue(script->getAtom(pc))); diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index 24a97f20b6..8e9976b17f 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -65,6 +65,7 @@ namespace jit { _(JSOP_UINT16) \ _(JSOP_UINT24) \ _(JSOP_DOUBLE) \ + _(JSOP_BIGINT) \ _(JSOP_STRING) \ _(JSOP_SYMBOL) \ _(JSOP_OBJECT) \ diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 61d77adc2e..600b56d096 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -3204,6 +3204,9 @@ StoreToTypedArray(JSContext* cx, MacroAssembler& masm, Scalar::Type type, Addres } else { masm.jump(failure); } + } else if (type == Scalar::BigInt64 || type == Scalar::BigUint64) { + // FIXME: https://bugzil.la/1536703 + masm.jump(failure); } else { Label notInt32; masm.branchTestInt32(Assembler::NotEqual, value, ¬Int32); @@ -8093,6 +8096,10 @@ ICTypeOf_Typed::Compiler::generateStubCode(MacroAssembler& masm) masm.branchTestSymbol(Assembler::NotEqual, R0, &failure); break; + case JSTYPE_BIGINT: + masm.branchTestBigInt(Assembler::NotEqual, R0, &failure); + break; + default: MOZ_CRASH("Unexpected type"); } diff --git a/js/src/jit/CacheIR.cpp b/js/src/jit/CacheIR.cpp index 9168a344e5..4da9b7539f 100644 --- a/js/src/jit/CacheIR.cpp +++ b/js/src/jit/CacheIR.cpp @@ -446,6 +446,9 @@ GetPropIRGenerator::tryAttachPrimitive(CacheIRWriter& writer, ValOperandId valId } else if (val_.isSymbol()) { primitiveType = JSVAL_TYPE_SYMBOL; proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_Symbol)); + } else if (val_.isBigInt()) { + primitiveType = JSVAL_TYPE_BIGINT; + proto = MaybeNativeObject(GetBuiltinPrototypePure(cx_->global(), JSProto_BigInt)); } else { MOZ_ASSERT(val_.isNullOrUndefined() || val_.isMagic()); return true; diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 1f7d292f29..0459592448 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -526,9 +526,11 @@ CodeGenerator::testValueTruthyKernel(const ValueOperand& value, bool mightBeString = valueMIR->mightBeType(MIRType::String); bool mightBeSymbol = valueMIR->mightBeType(MIRType::Symbol); bool mightBeDouble = valueMIR->mightBeType(MIRType::Double); + bool mightBeBigInt = valueMIR->mightBeType(MIRType::BigInt); int tagCount = int(mightBeUndefined) + int(mightBeNull) + int(mightBeBoolean) + int(mightBeInt32) + int(mightBeObject) + - int(mightBeString) + int(mightBeSymbol) + int(mightBeDouble); + int(mightBeString) + int(mightBeSymbol) + int(mightBeDouble) + + int(mightBeBigInt);; MOZ_ASSERT_IF(!valueMIR->emptyResultTypeSet(), tagCount > 0); @@ -618,6 +620,20 @@ CodeGenerator::testValueTruthyKernel(const ValueOperand& value, --tagCount; } + if (mightBeBigInt) { + MOZ_ASSERT(tagCount != 0); + Label notBigInt; + if (tagCount != 1) { + masm.branchTestBigInt(Assembler::NotEqual, tag, ¬BigInt); + } + masm.branchTestBigIntTruthy(false, value, ifFalsy); + if (tagCount != 1) { + masm.jump(ifTruthy); + } + masm.bind(¬BigInt); + --tagCount; + } + if (mightBeSymbol) { // All symbols are truthy. MOZ_ASSERT(tagCount != 0); @@ -954,8 +970,15 @@ CodeGenerator::visitValueToString(LValueToString* lir) } // Symbol - if (lir->mir()->input()->mightBeType(MIRType::Symbol)) + if (lir->mir()->input()->mightBeType(MIRType::Symbol)) { masm.branchTestSymbol(Assembler::Equal, tag, ool->entry()); + } + + // BigInt + if (lir->mir()->input()->mightBeType(MIRType::BigInt)) { + // No fastpath currently implemented. + masm.branchTestBigInt(Assembler::Equal, tag, ool->entry()); + } #ifdef DEBUG masm.assumeUnreachable("Unexpected type for MValueToString."); @@ -4902,10 +4925,11 @@ CodeGenerator::branchIfInvalidated(Register temp, Label* invalidated) } void -CodeGenerator::emitAssertObjectOrStringResult(Register input, MIRType type, const TemporaryTypeSet* typeset) +CodeGenerator::emitAssertGCThingResult(Register input, MIRType type, const TemporaryTypeSet* typeset) { MOZ_ASSERT(type == MIRType::Object || type == MIRType::ObjectOrNull || - type == MIRType::String || type == MIRType::Symbol); + type == MIRType::String || type == MIRType::Symbol || + type == MIRType::BigInt); AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); regs.take(input); @@ -4960,6 +4984,9 @@ CodeGenerator::emitAssertObjectOrStringResult(Register input, MIRType type, cons case MIRType::Symbol: callee = JS_FUNC_TO_DATA_PTR(void*, AssertValidSymbolPtr); break; + case MIRType::BigInt: + callee = JS_FUNC_TO_DATA_PTR(void*, AssertValidBigIntPtr); + break; default: MOZ_CRASH(); } @@ -5029,7 +5056,7 @@ CodeGenerator::emitAssertResultV(const ValueOperand input, const TemporaryTypeSe #ifdef DEBUG void -CodeGenerator::emitObjectOrStringResultChecks(LInstruction* lir, MDefinition* mir) +CodeGenerator::emitGCThingResultChecks(LInstruction* lir, MDefinition* mir) { if (lir->numDefs() == 0) return; @@ -5037,7 +5064,7 @@ CodeGenerator::emitObjectOrStringResultChecks(LInstruction* lir, MDefinition* mi MOZ_ASSERT(lir->numDefs() == 1); Register output = ToRegister(lir->getDef(0)); - emitAssertObjectOrStringResult(output, mir->type(), mir->resultTypeSet()); + emitAssertGCThingResult(output, mir->type(), mir->resultTypeSet()); } void @@ -5069,7 +5096,8 @@ CodeGenerator::emitDebugResultChecks(LInstruction* ins) case MIRType::ObjectOrNull: case MIRType::String: case MIRType::Symbol: - emitObjectOrStringResultChecks(ins, mir); + case MIRType::BigInt: + emitGCThingResultChecks(ins, mir); break; case MIRType::Value: emitValueResultChecks(ins, mir); @@ -10306,7 +10334,7 @@ CodeGenerator::visitThrow(LThrow* lir) callVM(ThrowInfoCodeGen, lir); } -typedef bool (*BitNotFn)(JSContext*, HandleValue, int* p); +typedef bool (*BitNotFn)(JSContext*, MutableHandleValue, MutableHandleValue); static const VMFunction BitNotInfo = FunctionInfo<BitNotFn>(BitNot, "BitNot"); void @@ -10316,7 +10344,7 @@ CodeGenerator::visitBitNotV(LBitNotV* lir) callVM(BitNotInfo, lir); } -typedef bool (*BitopFn)(JSContext*, HandleValue, HandleValue, int* p); +typedef bool (*BitopFn)(JSContext*, MutableHandleValue, MutableHandleValue, MutableHandleValue); static const VMFunction BitAndInfo = FunctionInfo<BitopFn>(BitAnd, "BitAnd"); static const VMFunction BitOrInfo = FunctionInfo<BitopFn>(BitOr, "BitOr"); static const VMFunction BitXorInfo = FunctionInfo<BitopFn>(BitXor, "BitXor"); @@ -10386,9 +10414,11 @@ CodeGenerator::visitTypeOfV(LTypeOfV* lir) bool testNull = input->mightBeType(MIRType::Null); bool testString = input->mightBeType(MIRType::String); bool testSymbol = input->mightBeType(MIRType::Symbol); + bool testBigInt = input->mightBeType(MIRType::BigInt); unsigned numTests = unsigned(testObject) + unsigned(testNumber) + unsigned(testBoolean) + - unsigned(testUndefined) + unsigned(testNull) + unsigned(testString) + unsigned(testSymbol); + unsigned(testUndefined) + unsigned(testNull) + unsigned(testString) + unsigned(testSymbol) + + unsigned(testBigInt); MOZ_ASSERT_IF(!input->emptyResultTypeSet(), numTests > 0); @@ -10484,6 +10514,19 @@ CodeGenerator::visitTypeOfV(LTypeOfV* lir) numTests--; } + if (testBigInt) { + Label notBigInt; + if (numTests > 1) { + masm.branchTestBigInt(Assembler::NotEqual, tag, ¬BigInt); + } + masm.movePtr(ImmGCPtr(names.bigint), output); + if (numTests > 1) { + masm.jump(&done); + } + masm.bind(¬BigInt); + numTests--; + } + MOZ_ASSERT(numTests == 0); masm.bind(&done); @@ -11794,7 +11837,7 @@ CodeGenerator::visitAssertResultT(LAssertResultT* ins) Register input = ToRegister(ins->input()); MDefinition* mir = ins->mirRaw(); - emitAssertObjectOrStringResult(input, mir->type(), mir->resultTypeSet()); + emitAssertGCThingResult(input, mir->type(), mir->resultTypeSet()); } void diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index 2749a68b05..64fe9378b8 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -431,7 +431,7 @@ class CodeGenerator final : public CodeGeneratorSpecific void visitAssertResultV(LAssertResultV* ins); void visitAssertResultT(LAssertResultT* ins); void emitAssertResultV(const ValueOperand output, const TemporaryTypeSet* typeset); - void emitAssertObjectOrStringResult(Register input, MIRType type, const TemporaryTypeSet* typeset); + void emitAssertGCThingResult(Register input, MIRType type, const TemporaryTypeSet* typeset); void visitInterruptCheck(LInterruptCheck* lir); void visitOutOfLineInterruptCheckImplicit(OutOfLineInterruptCheckImplicit* ins); @@ -571,7 +571,7 @@ class CodeGenerator final : public CodeGeneratorSpecific #ifdef DEBUG void emitDebugResultChecks(LInstruction* ins); - void emitObjectOrStringResultChecks(LInstruction* lir, MDefinition* mir); + void emitGCThingResultChecks(LInstruction* lir, MDefinition* mir); void emitValueResultChecks(LInstruction* lir, MDefinition* mir); #endif diff --git a/js/src/jit/IonAnalysis.cpp b/js/src/jit/IonAnalysis.cpp index 410769251c..aa8f8164b5 100644 --- a/js/src/jit/IonAnalysis.cpp +++ b/js/src/jit/IonAnalysis.cpp @@ -2611,6 +2611,7 @@ IsResumableMIRType(MIRType type) case MIRType::Float32: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: case MIRType::MagicOptimizedArguments: case MIRType::MagicOptimizedOut: diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 0a9734a335..a440bfa598 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -721,6 +721,9 @@ IonBuilder::analyzeNewLoopTypes(MBasicBlock* entry, jsbytecode* start, jsbytecod case JSOP_NEG: type = inspector->expectedResultType(last); break; + case JSOP_BIGINT: + type = MIRType::BigInt; + break; default: break; } @@ -1347,6 +1350,7 @@ IonBuilder::addOsrValueTypeBarrier(uint32_t slot, MInstruction** def_, case MIRType::Double: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: if (type != def->type()) { MUnbox* unbox = MUnbox::New(alloc(), def, type, MUnbox::Fallible); @@ -1765,6 +1769,7 @@ IonBuilder::inspectOpcode(JSOp op) return jsop_compare(op); case JSOP_DOUBLE: + case JSOP_BIGINT: pushConstant(info().getConst(pc)); return true; @@ -4760,8 +4765,10 @@ IonBuilder::bitnotTrySpecialized(bool* emitted, MDefinition* input) // Try to emit a specialized bitnot instruction based on the input type // of the operand. - if (input->mightBeType(MIRType::Object) || input->mightBeType(MIRType::Symbol)) + if (input->mightBeType(MIRType::Object) || input->mightBeType(MIRType::Symbol) || + input->mightBeType(MIRType::BigInt)) { return true; + } MBitNot* ins = MBitNot::New(alloc(), input); ins->setSpecialization(MIRType::Int32); @@ -7209,6 +7216,7 @@ ObjectOrSimplePrimitive(MDefinition* op) // Return true if op is either undefined/null/boolean/int32 or an object. return !op->mightBeType(MIRType::String) && !op->mightBeType(MIRType::Symbol) + && !op->mightBeType(MIRType::BigInt) && !op->mightBeType(MIRType::Double) && !op->mightBeType(MIRType::Float32) && !op->mightBeType(MIRType::MagicOptimizedArguments) @@ -8348,6 +8356,10 @@ IonBuilder::testSingletonPropertyTypes(MDefinition* obj, jsid id) key = JSProto_Symbol; break; + case MIRType::BigInt: + key = JSProto_BigInt; + break; + case MIRType::Int32: case MIRType::Double: key = JSProto_Number; diff --git a/js/src/jit/IonTypes.h b/js/src/jit/IonTypes.h index c7a3e282aa..50b09cc30e 100644 --- a/js/src/jit/IonTypes.h +++ b/js/src/jit/IonTypes.h @@ -103,6 +103,7 @@ enum BailoutKind Bailout_NonObjectInput, Bailout_NonStringInput, Bailout_NonSymbolInput, + Bailout_NonBigIntInput, // SIMD Unbox expects a given type, bails out if it doesn't match. Bailout_UnexpectedSimdInput, @@ -212,6 +213,8 @@ BailoutKindString(BailoutKind kind) return "Bailout_NonStringInput"; case Bailout_NonSymbolInput: return "Bailout_NonSymbolInput"; + case Bailout_NonBigIntInput: + return "Bailout_NonBigIntInput"; case Bailout_UnexpectedSimdInput: return "Bailout_UnexpectedSimdInput"; case Bailout_NonSharedTypedArrayInput: @@ -412,6 +415,7 @@ enum class MIRType // Types above have trivial conversion to a number. String, Symbol, + BigInt, // Types above are primitive (including undefined and null). Object, MagicOptimizedArguments, // JS_OPTIMIZED_ARGUMENTS magic value. @@ -496,6 +500,8 @@ MIRTypeFromValueType(JSValueType type) return MIRType::String; case JSVAL_TYPE_SYMBOL: return MIRType::Symbol; + case JSVAL_TYPE_BIGINT: + return MIRType::BigInt; case JSVAL_TYPE_BOOLEAN: return MIRType::Boolean; case JSVAL_TYPE_NULL: @@ -528,6 +534,8 @@ ValueTypeFromMIRType(MIRType type) return JSVAL_TYPE_STRING; case MIRType::Symbol: return JSVAL_TYPE_SYMBOL; + case MIRType::BigInt: + return JSVAL_TYPE_BIGINT; case MIRType::MagicOptimizedArguments: case MIRType::MagicOptimizedOut: case MIRType::MagicHole: @@ -568,6 +576,8 @@ StringFromMIRType(MIRType type) return "String"; case MIRType::Symbol: return "Symbol"; + case MIRType::BigInt: + return "BigInt"; case MIRType::Object: return "Object"; case MIRType::MagicOptimizedArguments: @@ -714,34 +724,9 @@ ScalarTypeToMIRType(Scalar::Type type) return MIRType::Int16x8; case Scalar::Int32x4: return MIRType::Int32x4; - case Scalar::MaxTypedArrayViewType: - break; - } - MOZ_CRASH("unexpected SIMD kind"); -} - -static inline unsigned -ScalarTypeToLength(Scalar::Type type) -{ - switch (type) { - case Scalar::Int8: - case Scalar::Uint8: - case Scalar::Int16: - case Scalar::Uint16: - case Scalar::Int32: - case Scalar::Uint32: - case Scalar::Int64: - case Scalar::Float32: - case Scalar::Float64: - case Scalar::Uint8Clamped: - return 1; - case Scalar::Float32x4: - case Scalar::Int32x4: - return 4; - case Scalar::Int16x8: - return 8; - case Scalar::Int8x16: - return 16; + case Scalar::BigInt64: + case Scalar::BigUint64: + MOZ_CRASH("NYI"); case Scalar::MaxTypedArrayViewType: break; } diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index c343800e0d..7ee9b24eab 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -1777,6 +1777,12 @@ FromSymbolPayload(uintptr_t payload) } static Value +FromBigIntPayload(uintptr_t payload) +{ + return BigIntValue(reinterpret_cast<JS::BigInt*>(payload)); +} + +static Value FromTypedPayload(JSValueType type, uintptr_t payload) { switch (type) { @@ -1788,6 +1794,8 @@ FromTypedPayload(JSValueType type, uintptr_t payload) return FromStringPayload(payload); case JSVAL_TYPE_SYMBOL: return FromSymbolPayload(payload); + case JSVAL_TYPE_BIGINT: + return FromBigIntPayload(payload); case JSVAL_TYPE_OBJECT: return FromObjectPayload(payload); default: @@ -1887,6 +1895,8 @@ SnapshotIterator::allocationValue(const RValueAllocation& alloc, ReadMethod rm) return FromStringPayload(fromStack(alloc.stackOffset2())); case JSVAL_TYPE_SYMBOL: return FromSymbolPayload(fromStack(alloc.stackOffset2())); + case JSVAL_TYPE_BIGINT: + return FromBigIntPayload(fromStack(alloc.stackOffset2())); case JSVAL_TYPE_OBJECT: return FromObjectPayload(fromStack(alloc.stackOffset2())); default: diff --git a/js/src/jit/LIR.h b/js/src/jit/LIR.h index 4083f7f1bb..e9143a6f41 100644 --- a/js/src/jit/LIR.h +++ b/js/src/jit/LIR.h @@ -599,6 +599,7 @@ class LDefinition return LDefinition::INT32; case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: case MIRType::ObjectOrNull: return LDefinition::OBJECT; diff --git a/js/src/jit/Lowering.cpp b/js/src/jit/Lowering.cpp index 1c80c74716..d315c618e7 100644 --- a/js/src/jit/Lowering.cpp +++ b/js/src/jit/Lowering.cpp @@ -717,6 +717,10 @@ LIRGenerator::visitTest(MTest* test) // TestPolicy). MOZ_ASSERT(opd->type() != MIRType::String); + // BigInt is boxed in type analysis. + MOZ_ASSERT(opd->type() != MIRType::BigInt, + "BigInt should be boxed by TestPolicy"); + // Testing a constant. if (MConstant* constant = opd->maybeConstantValue()) { bool b; @@ -2149,9 +2153,11 @@ LIRGenerator::visitToInt32(MToInt32* convert) case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: case MIRType::Undefined: - // Objects might be effectful. Symbols throw. Undefined coerces to NaN, not int32. + // Objects might be effectful. Symbols and BigInts throw. Undefined + // coerces to NaN, not int32. MOZ_CRASH("ToInt32 invalid input type"); default: @@ -2939,6 +2945,8 @@ LIRGenerator::visitNot(MNot* ins) // String is converted to length of string in the type analysis phase (see // TestPolicy). MOZ_ASSERT(op->type() != MIRType::String); + MOZ_ASSERT(op->type() != MIRType::BigInt, + "BigInt should be boxed by TestPolicy"); // - boolean: x xor 1 // - int32: LCompare(x, 0) diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 1a98432ffa..064c7ee7d2 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -2894,6 +2894,7 @@ IonBuilder::inlineToInteger(CallInfo& callInfo) if (input->mightBeType(MIRType::Object) || input->mightBeType(MIRType::String) || input->mightBeType(MIRType::Symbol) || + input->mightBeType(MIRType::BigInt) || input->mightBeType(MIRType::Undefined) || input->mightBeMagicType()) { @@ -3021,12 +3022,16 @@ IonBuilder::inlineAtomicsCompareExchange(CallInfo& callInfo) // These guards are desirable here and in subsequent atomics to // avoid bad bailouts with MTruncateToInt32, see https://bugzilla.mozilla.org/show_bug.cgi?id=1141986#c20. MDefinition* oldval = callInfo.getArg(2); - if (oldval->mightBeType(MIRType::Object) || oldval->mightBeType(MIRType::Symbol)) + if (oldval->mightBeType(MIRType::Object) || oldval->mightBeType(MIRType::Symbol) || + oldval->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } MDefinition* newval = callInfo.getArg(3); - if (newval->mightBeType(MIRType::Object) || newval->mightBeType(MIRType::Symbol)) + if (newval->mightBeType(MIRType::Object) || newval->mightBeType(MIRType::Symbol) || + newval->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } Scalar::Type arrayType; bool requiresCheck = false; @@ -3063,8 +3068,10 @@ IonBuilder::inlineAtomicsExchange(CallInfo& callInfo) } MDefinition* value = callInfo.getArg(2); - if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol)) + if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol) || + value->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } Scalar::Type arrayType; bool requiresCheck = false; @@ -3151,8 +3158,10 @@ IonBuilder::inlineAtomicsStore(CallInfo& callInfo) return InliningStatus_NotInlined; } - if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol)) + if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol) || + value->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } Scalar::Type arrayType; bool requiresCheck = false; @@ -3194,8 +3203,10 @@ IonBuilder::inlineAtomicsBinop(CallInfo& callInfo, InlinableNative target) } MDefinition* value = callInfo.getArg(2); - if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol)) + if (value->mightBeType(MIRType::Object) || value->mightBeType(MIRType::Symbol) || + value->mightBeType(MIRType::BigInt)) { return InliningStatus_NotInlined; + } Scalar::Type arrayType; bool requiresCheck = false; diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 6b89af32de..2264bed4f2 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -935,6 +935,9 @@ MConstant::MConstant(const js::Value& vp, CompilerConstraintList* constraints) case MIRType::Symbol: payload_.sym = vp.toSymbol(); break; + case MIRType::BigInt: + payload_.bi = vp.toBigInt(); + break; case MIRType::Object: payload_.obj = &vp.toObject(); // Create a singleton type set for the object. This isn't necessary for @@ -1014,7 +1017,12 @@ MConstant::assertInitializedPayload() const case MIRType::String: case MIRType::Object: case MIRType::Symbol: + case MIRType::BigInt: +#if MOZ_LITTLE_ENDIAN MOZ_ASSERT_IF(JS_BITS_PER_WORD == 32, (payload_.asBits >> 32) == 0); +#else + MOZ_ASSERT_IF(JS_BITS_PER_WORD == 32, (payload_.asBits << 32) == 0); +#endif break; default: MOZ_ASSERT(IsNullOrUndefined(type()) || IsMagicType(type())); @@ -1103,6 +1111,9 @@ MConstant::printOpcode(GenericPrinter& out) const case MIRType::Symbol: out.printf("symbol at %p", (void*)toSymbol()); break; + case MIRType::BigInt: + out.printf("BigInt at %p", (void*)toBigInt()); + break; case MIRType::String: out.printf("string %p", (void*)toString()); break; @@ -1164,6 +1175,8 @@ MConstant::toJSValue() const return StringValue(toString()); case MIRType::Symbol: return SymbolValue(toSymbol()); + case MIRType::BigInt: + return BigIntValue(toBigInt()); case MIRType::Object: return ObjectValue(toObject()); case MIRType::MagicOptimizedArguments: @@ -1207,6 +1220,9 @@ MConstant::valueToBoolean(bool* res) const case MIRType::Symbol: *res = true; return true; + case MIRType::BigInt: + *res = !toBigInt()->isZero(); + return true; case MIRType::String: *res = toString()->length() != 0; return true; @@ -2199,6 +2215,7 @@ MUnbox::printOpcode(GenericPrinter& out) const case MIRType::Boolean: out.printf("to Boolean"); break; case MIRType::String: out.printf("to String"); break; case MIRType::Symbol: out.printf("to Symbol"); break; + case MIRType::BigInt: out.printf("to BigInt"); break; case MIRType::Object: out.printf("to Object"); break; default: break; } @@ -2591,6 +2608,7 @@ jit::TypeSetIncludes(TypeSet* types, MIRType input, TypeSet* inputTypes) case MIRType::Float32: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::MagicOptimizedArguments: return types->hasType(TypeSet::PrimitiveType(ValueTypeFromMIRType(input))); @@ -2846,9 +2864,11 @@ void MBinaryBitwiseInstruction::infer(BaselineInspector*, jsbytecode*) { if (getOperand(0)->mightBeType(MIRType::Object) || getOperand(0)->mightBeType(MIRType::Symbol) || - getOperand(1)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Symbol)) + getOperand(1)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Symbol) || + getOperand(1)->mightBeType(MIRType::BigInt)) { specialization_ = MIRType::None; + setResultType(MIRType::Value); } else { specializeAs(MIRType::Int32); } @@ -2858,9 +2878,10 @@ void MBinaryBitwiseInstruction::specializeAs(MIRType type) { MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64); - MOZ_ASSERT(this->type() == type); + MOZ_ASSERT(this->type() == MIRType::Value || this->type() == type); specialization_ = type; + setResultType(type); if (isBitOr() || isBitAnd() || isBitXor()) setCommutative(); @@ -2870,17 +2891,23 @@ void MShiftInstruction::infer(BaselineInspector*, jsbytecode*) { if (getOperand(0)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Object) || - getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol)) + getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol) || + getOperand(0)->mightBeType(MIRType::BigInt) || getOperand(1)->mightBeType(MIRType::BigInt)) + { specialization_ = MIRType::None; - else + setResultType(MIRType::Value); + } else { specialization_ = MIRType::Int32; + setResultType(MIRType::Int32); + } } void MUrsh::infer(BaselineInspector* inspector, jsbytecode* pc) { if (getOperand(0)->mightBeType(MIRType::Object) || getOperand(1)->mightBeType(MIRType::Object) || - getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol)) + getOperand(0)->mightBeType(MIRType::Symbol) || getOperand(1)->mightBeType(MIRType::Symbol) || + getOperand(0)->mightBeType(MIRType::BigInt) || getOperand(1)->mightBeType(MIRType::BigInt)) { specialization_ = MIRType::None; setResultType(MIRType::Value); @@ -3828,7 +3855,7 @@ MBitNot::NewInt32(TempAllocator& alloc, MDefinition* input) { MBitNot* ins = new(alloc) MBitNot(input); ins->specialization_ = MIRType::Int32; - MOZ_ASSERT(ins->type() == MIRType::Int32); + ins->setResultType(MIRType::Int32); return ins; } @@ -3874,6 +3901,9 @@ MTypeOf::foldsTo(TempAllocator& alloc) case MIRType::Symbol: type = JSTYPE_SYMBOL; break; + case MIRType::BigInt: + type = JSTYPE_BIGINT; + break; case MIRType::Null: type = JSTYPE_OBJECT; break; @@ -4453,6 +4483,12 @@ MCompare::tryFoldTypeOf(bool* result) *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); return true; } + } + else if (constant->toString() == TypeName(JSTYPE_BIGINT, names)) { + if (!typeOf->input()->mightBeType(MIRType::BigInt)) { + *result = (jsop() == JSOP_STRICTNE || jsop() == JSOP_NE); + return true; + } } else if (constant->toString() == TypeName(JSTYPE_OBJECT, names)) { if (!typeOf->input()->mightBeType(MIRType::Object) && !typeOf->input()->mightBeType(MIRType::Null)) @@ -5606,6 +5642,8 @@ MConstant::appendRoots(MRootList& roots) const return roots.append(toString()); case MIRType::Symbol: return roots.append(toSymbol()); + case MIRType::BigInt: + return roots.append(toBigInt()); case MIRType::Object: return roots.append(&toObject()); case MIRType::Undefined: @@ -5967,7 +6005,14 @@ jit::ElementAccessIsTypedArray(CompilerConstraintList* constraints, return false; *arrayType = types->getTypedArrayType(constraints); - return *arrayType != Scalar::MaxTypedArrayViewType; + + // FIXME: https://bugzil.la/1536699 + if (*arrayType == Scalar::MaxTypedArrayViewType || + Scalar::isBigIntType(*arrayType)) { + return false; + } + + return true; } bool diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 6c0616c71d..72c5214845 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -1548,6 +1548,7 @@ class MConstant : public MNullaryInstruction double d; JSString* str; JS::Symbol* sym; + BigInt* bi; JSObject* obj; uint64_t asBits; }; @@ -1672,6 +1673,10 @@ class MConstant : public MNullaryInstruction MOZ_ASSERT(type() == MIRType::Symbol); return payload_.sym; } + BigInt* toBigInt() const { + MOZ_ASSERT(type() == MIRType::BigInt); + return payload_.bi; + } JSObject& toObject() const { MOZ_ASSERT(type() == MIRType::Object); return *payload_.obj; @@ -4743,6 +4748,7 @@ class MUnbox final : public MUnaryInstruction, public BoxInputsPolicy::Data type == MIRType::Double || type == MIRType::String || type == MIRType::Symbol || + type == MIRType::BigInt || type == MIRType::Object); TemporaryTypeSet* resultSet = ins->resultTypeSet(); @@ -4781,6 +4787,9 @@ class MUnbox final : public MUnaryInstruction, public BoxInputsPolicy::Data case MIRType::Symbol: kind = Bailout_NonSymbolInput; break; + case MIRType::BigInt: + kind = Bailout_NonBigIntInput; + break; case MIRType::Object: kind = Bailout_NonObjectInput; break; @@ -5189,9 +5198,11 @@ class MToDouble setMovable(); // An object might have "valueOf", which means it is effectful. - // ToNumber(symbol) throws. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // ToNumber(symbol) and ToNumber(bigint) throw. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } public: @@ -5226,11 +5237,15 @@ class MToDouble MOZ_MUST_USE bool writeRecoverData(CompactBufferWriter& writer) const override; bool canRecoverOnBailout() const override { - if (input()->type() == MIRType::Value) + if (input()->type() == MIRType::Value) { return false; - if (input()->type() == MIRType::Symbol) + } + if (input()->type() == MIRType::Symbol) { return false; - + } + if (input()->type() == MIRType::BigInt) { + return false; + } return true; } @@ -5253,9 +5268,11 @@ class MToFloat32 setMovable(); // An object might have "valueOf", which means it is effectful. - // ToNumber(symbol) throws. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // ToNumber(symbol) and ToNumber(BigInt) throw. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } explicit MToFloat32(MDefinition* def, bool mustPreserveNaN) @@ -5537,9 +5554,11 @@ class MToInt32 setMovable(); // An object might have "valueOf", which means it is effectful. - // ToNumber(symbol) throws. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // ToInt32(symbol) and ToInt32(BigInt) throw. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } public: @@ -5594,9 +5613,11 @@ class MTruncateToInt32 setMovable(); // An object might have "valueOf", which means it is effectful. - // ToInt32(symbol) throws. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // ToInt32(symbol) and ToInt32(BigInt) throw. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } public: @@ -5639,9 +5660,12 @@ class MToString : setResultType(MIRType::String); setMovable(); - // Objects might override toString and Symbols throw. - if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol)) + // Objects might override toString; Symbol and BigInts throw. We bailout in + // those cases and run side-effects in baseline instead. + if (def->mightBeType(MIRType::Object) || def->mightBeType(MIRType::Symbol) || + def->mightBeType(MIRType::BigInt)) { setGuard(); + } } public: @@ -5701,7 +5725,7 @@ class MBitNot : MUnaryInstruction(input) { specialization_ = MIRType::None; - setResultType(MIRType::Int32); + setResultType(MIRType::Value); setMovable(); } @@ -5861,7 +5885,7 @@ class MBinaryBitwiseInstruction maskMatchesRightRange(false) { MOZ_ASSERT(type == MIRType::Int32 || type == MIRType::Int64); - setResultType(type); + setResultType(MIRType::Value); setMovable(); } @@ -14419,6 +14443,9 @@ MIRTypeForTypedArrayRead(Scalar::Type arrayType, bool observedDouble) return MIRType::Float32; case Scalar::Float64: return MIRType::Double; + case Scalar::BigInt64: + case Scalar::BigUint64: + return MIRType::BigInt; default: break; } diff --git a/js/src/jit/MacroAssembler-inl.h b/js/src/jit/MacroAssembler-inl.h index dd20f67317..98316eb643 100644 --- a/js/src/jit/MacroAssembler-inl.h +++ b/js/src/jit/MacroAssembler-inl.h @@ -545,6 +545,7 @@ MacroAssembler::branchTestMIRType(Condition cond, const Value& val, MIRType type case MIRType::Int32: return branchTestInt32(cond, val, label); case MIRType::String: return branchTestString(cond, val, label); case MIRType::Symbol: return branchTestSymbol(cond, val, label); + case MIRType::BigInt: return branchTestBigInt(cond, val, label); case MIRType::Object: return branchTestObject(cond, val, label); case MIRType::Double: return branchTestDouble(cond, val, label); case MIRType::MagicOptimizedArguments: // Fall through. @@ -643,7 +644,7 @@ void MacroAssembler::canonicalizeFloatIfDeterministic(FloatRegister reg) { #ifdef JS_MORE_DETERMINISTIC - // See the comment in TypedArrayObjectTemplate::getIndexValue. + // See the comment in TypedArrayObjectTemplate::getElement. canonicalizeFloat(reg); #endif // JS_MORE_DETERMINISTIC } @@ -661,7 +662,7 @@ void MacroAssembler::canonicalizeDoubleIfDeterministic(FloatRegister reg) { #ifdef JS_MORE_DETERMINISTIC - // See the comment in TypedArrayObjectTemplate::getIndexValue. + // See the comment in TypedArrayObjectTemplate::getElement. canonicalizeDouble(reg); #endif // JS_MORE_DETERMINISTIC } diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 0425ac03a1..07f7b6fdcd 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -45,12 +45,13 @@ MacroAssembler::guardTypeSet(const Source& address, const TypeSet* types, Barrie MOZ_ASSERT(!types->unknown()); Label matched; - TypeSet::Type tests[8] = { + TypeSet::Type tests[9] = { TypeSet::Int32Type(), TypeSet::UndefinedType(), TypeSet::BooleanType(), TypeSet::StringType(), TypeSet::SymbolType(), + TypeSet::BigIntType(), TypeSet::NullType(), TypeSet::MagicArgType(), TypeSet::AnyObjectType() @@ -344,6 +345,11 @@ MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const T& src, AnyRegi branchTest32(Assembler::Signed, dest.gpr(), dest.gpr(), fail); } break; + case Scalar::BigInt64: + case Scalar::BigUint64: + // FIXME: https://bugzil.la/1536702 + jump(fail); + break; case Scalar::Float32: loadFloat32(src, dest.fpu()); canonicalizeFloat(dest.fpu()); @@ -446,17 +452,25 @@ MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const T& src, const V tagValue(JSVAL_TYPE_INT32, temp, dest); } break; - case Scalar::Float32: + case Scalar::Float32: { loadFromTypedArray(arrayType, src, AnyRegister(ScratchFloat32Reg), dest.scratchReg(), nullptr); convertFloat32ToDouble(ScratchFloat32Reg, ScratchDoubleReg); boxDouble(ScratchDoubleReg, dest); break; - case Scalar::Float64: + } + case Scalar::Float64: { loadFromTypedArray(arrayType, src, AnyRegister(ScratchDoubleReg), dest.scratchReg(), nullptr); boxDouble(ScratchDoubleReg, dest); break; + } + // FIXME: https://bugzil.la/1536702 + case Scalar::BigInt64: + case Scalar::BigUint64: { + jump(fail); + break; + } default: MOZ_CRASH("Invalid typed array type"); } @@ -2736,6 +2750,9 @@ MacroAssembler::maybeBranchTestType(MIRType type, MDefinition* maybeDef, Registe case MIRType::Symbol: branchTestSymbol(Equal, tag, label); break; + case MIRType::BigInt: + branchTestBigInt(Equal, tag, label); + break; case MIRType::Object: branchTestObject(Equal, tag, label); break; diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index 6d9888469e..173a39014c 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -1137,6 +1137,7 @@ class MacroAssembler : public MacroAssemblerSpecific inline void branchTestBoolean(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestString(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestSymbol(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; + inline void branchTestBigInt(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestNull(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestObject(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; inline void branchTestPrimitive(Condition cond, Register tag, Label* label) PER_SHARED_ARCH; @@ -1177,6 +1178,10 @@ class MacroAssembler : public MacroAssemblerSpecific inline void branchTestSymbol(Condition cond, const ValueOperand& value, Label* label) DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); + inline void branchTestBigInt(Condition cond, const BaseIndex& address, Label* label) PER_SHARED_ARCH; + inline void branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) + DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); + inline void branchTestNull(Condition cond, const Address& address, Label* label) PER_SHARED_ARCH; inline void branchTestNull(Condition cond, const BaseIndex& address, Label* label) PER_SHARED_ARCH; inline void branchTestNull(Condition cond, const ValueOperand& value, Label* label) @@ -1216,6 +1221,8 @@ class MacroAssembler : public MacroAssemblerSpecific inline void branchTestBooleanTruthy(bool truthy, const ValueOperand& value, Label* label) PER_ARCH; inline void branchTestStringTruthy(bool truthy, const ValueOperand& value, Label* label) DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); + inline void branchTestBigIntTruthy(bool truthy, const ValueOperand& value, Label* label) + DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); private: @@ -1256,6 +1263,9 @@ class MacroAssembler : public MacroAssemblerSpecific inline void branchTestSymbolImpl(Condition cond, const T& t, Label* label) DEFINED_ON(arm, arm64, x86_shared); template <typename T> + inline void branchTestBigIntImpl(Condition cond, const T& t, Label* label) + DEFINED_ON(arm, arm64, x86_shared); + template <typename T> inline void branchTestNullImpl(Condition cond, const T& t, Label* label) DEFINED_ON(arm, arm64, x86_shared); template <typename T> diff --git a/js/src/jit/RangeAnalysis.cpp b/js/src/jit/RangeAnalysis.cpp index b5f617ce32..52c737e677 100644 --- a/js/src/jit/RangeAnalysis.cpp +++ b/js/src/jit/RangeAnalysis.cpp @@ -1780,6 +1780,8 @@ GetTypedArrayRange(TempAllocator& alloc, Scalar::Type type) case Scalar::Int32: return Range::NewInt32Range(alloc, INT32_MIN, INT32_MAX); + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Int64: case Scalar::Float32: case Scalar::Float64: diff --git a/js/src/jit/Recover.cpp b/js/src/jit/Recover.cpp index 833ad871d0..0d6882f52c 100644 --- a/js/src/jit/Recover.cpp +++ b/js/src/jit/Recover.cpp @@ -164,13 +164,13 @@ bool RBitNot::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue operand(cx, iter.read()); + RootedValue result(cx); - int32_t result; - if (!js::BitNot(cx, operand, &result)) + if (!js::BitNot(cx, &operand, &result)) { return false; + } - RootedValue rootedResult(cx, js::Int32Value(result)); - iter.storeInstructionResult(rootedResult); + iter.storeInstructionResult(result); return true; } @@ -190,14 +190,14 @@ RBitAnd::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; + RootedValue result(cx); MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - if (!js::BitAnd(cx, lhs, rhs, &result)) + if (!js::BitAnd(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue rootedResult(cx, js::Int32Value(result)); - iter.storeInstructionResult(rootedResult); + iter.storeInstructionResult(result); return true; } @@ -217,14 +217,14 @@ RBitOr::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; + RootedValue result(cx); MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - if (!js::BitOr(cx, lhs, rhs, &result)) + if (!js::BitOr(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue asValue(cx, js::Int32Value(result)); - iter.storeInstructionResult(asValue); + iter.storeInstructionResult(result);; return true; } @@ -244,13 +244,13 @@ RBitXor::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); + RootedValue result(cx); - int32_t result; - if (!js::BitXor(cx, lhs, rhs, &result)) + if (!js::BitXor(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue rootedResult(cx, js::Int32Value(result)); - iter.storeInstructionResult(rootedResult); + iter.storeInstructionResult(result); return true; } @@ -270,14 +270,14 @@ RLsh::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); - int32_t result; + RootedValue result(cx); MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - if (!js::BitLsh(cx, lhs, rhs, &result)) + if (!js::BitLsh(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue asValue(cx, js::Int32Value(result)); - iter.storeInstructionResult(asValue); + iter.storeInstructionResult(result); return true; } @@ -297,14 +297,14 @@ RRsh::recover(JSContext* cx, SnapshotIterator& iter) const { RootedValue lhs(cx, iter.read()); RootedValue rhs(cx, iter.read()); + RootedValue result(cx); MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); - int32_t result; - if (!js::BitRsh(cx, lhs, rhs, &result)) + if (!js::BitRsh(cx, &lhs, &rhs, &result)) { return false; + } - RootedValue rootedResult(cx, js::Int32Value(result)); - iter.storeInstructionResult(rootedResult); + iter.storeInstructionResult(result); return true; } @@ -327,8 +327,9 @@ RUrsh::recover(JSContext* cx, SnapshotIterator& iter) const MOZ_ASSERT(!lhs.isObject() && !rhs.isObject()); RootedValue result(cx); - if (!js::UrshOperation(cx, lhs, rhs, &result)) + if (!js::UrshOperation(cx, &lhs, &rhs, &result)) { return false; + } iter.storeInstructionResult(result); return true; @@ -778,7 +779,7 @@ RPow::recover(JSContext* cx, SnapshotIterator& iter) const RootedValue result(cx); MOZ_ASSERT(base.isNumber() && power.isNumber()); - if (!js::math_pow_handle(cx, base, power, &result)) + if (!js::PowValues(cx, &base, &power, &result)) return false; iter.storeInstructionResult(result); @@ -805,7 +806,7 @@ RPowHalf::recover(JSContext* cx, SnapshotIterator& iter) const power.setNumber(0.5); MOZ_ASSERT(base.isNumber()); - if (!js::math_pow_handle(cx, base, power, &result)) + if (!js::PowValues(cx, &base, &power, &result)) return false; iter.storeInstructionResult(result); diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index f8f4433af7..375edb1400 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -942,47 +942,43 @@ DoBinaryArithFallback(JSContext* cx, void* payload, ICBinaryArith_Fallback* stub return false; break; case JSOP_POW: - if (!math_pow_handle(cx, lhsCopy, rhsCopy, ret)) + if (!PowValues(cx, &lhsCopy, &rhsCopy, ret)) return false; break; case JSOP_BITOR: { - int32_t result; - if (!BitOr(cx, lhs, rhs, &result)) + if (!BitOr(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_BITXOR: { - int32_t result; - if (!BitXor(cx, lhs, rhs, &result)) + if (!BitXor(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_BITAND: { - int32_t result; - if (!BitAnd(cx, lhs, rhs, &result)) + if (!BitAnd(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_LSH: { - int32_t result; - if (!BitLsh(cx, lhs, rhs, &result)) + if (!BitLsh(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_RSH: { - int32_t result; - if (!BitRsh(cx, lhs, rhs, &result)) + if (!BitRsh(cx, &lhsCopy, &rhsCopy, ret)) { return false; - ret.setInt32(result); + } break; } case JSOP_URSH: { - if (!UrshOperation(cx, lhs, rhs, ret)) + if (!UrshOperation(cx, &lhsCopy, &rhsCopy, ret)) { return false; + } break; } default: @@ -1475,16 +1471,19 @@ DoUnaryArithFallback(JSContext* cx, void* payload, ICUnaryArith_Fallback* stub_, switch (op) { case JSOP_BITNOT: { - int32_t result; - if (!BitNot(cx, val, &result)) + RootedValue valCopy(cx, val); + if (!BitNot(cx, &valCopy, res)) { return false; - res.setInt32(result); + } break; } - case JSOP_NEG: - if (!NegOperation(cx, script, pc, val, res)) + case JSOP_NEG: { + // We copy val here because the original value is needed below. + RootedValue valCopy(cx, val); + if (!NegOperation(cx, script, pc, &valCopy, res)) return false; break; + } default: MOZ_CRASH("Unexpected op"); } diff --git a/js/src/jit/Snapshots.cpp b/js/src/jit/Snapshots.cpp index 6d9a461412..3111941315 100644 --- a/js/src/jit/Snapshots.cpp +++ b/js/src/jit/Snapshots.cpp @@ -404,6 +404,8 @@ ValTypeToString(JSValueType type) { return "string"; case JSVAL_TYPE_SYMBOL: return "symbol"; + case JSVAL_TYPE_BIGINT: + return "BigInt"; case JSVAL_TYPE_BOOLEAN: return "boolean"; case JSVAL_TYPE_OBJECT: diff --git a/js/src/jit/TypePolicy.cpp b/js/src/jit/TypePolicy.cpp index 396fdc57f8..1222cdd2b2 100644 --- a/js/src/jit/TypePolicy.cpp +++ b/js/src/jit/TypePolicy.cpp @@ -700,7 +700,8 @@ ToDoublePolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) case MIRType::Object: case MIRType::String: case MIRType::Symbol: - // Objects might be effectful. Symbols give TypeError. + case MIRType::BigInt: + // Objects might be effectful. Symbols and BigInts give TypeError. break; default: break; @@ -748,7 +749,8 @@ ToInt32Policy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) case MIRType::Object: case MIRType::String: case MIRType::Symbol: - // Objects might be effectful. Symbols give TypeError. + case MIRType::BigInt: + // Objects might be effectful. Symbols and BigInts give TypeError. break; default: break; @@ -765,7 +767,8 @@ ToStringPolicy::staticAdjustInputs(TempAllocator& alloc, MInstruction* ins) MOZ_ASSERT(ins->isToString()); MIRType type = ins->getOperand(0)->type(); - if (type == MIRType::Object || type == MIRType::Symbol) { + if (type == MIRType::Object || type == MIRType::Symbol || + type == MIRType::BigInt) { ins->replaceOperand(0, BoxAt(alloc, ins, ins->getOperand(0))); return true; } @@ -955,6 +958,7 @@ StoreUnboxedScalarPolicy::adjustValueInput(TempAllocator& alloc, MInstruction* i case MIRType::Object: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: value = BoxAt(alloc, ins, value); break; default: diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 6e5676f153..8802d3582b 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -1236,15 +1236,27 @@ AssertValidSymbolPtr(JSContext* cx, JS::Symbol* sym) MOZ_ASSERT(sym->getAllocKind() == gc::AllocKind::SYMBOL); } +void +AssertValidBigIntPtr(JSContext* cx, JS::BigInt* bi) { + // FIXME: check runtime? + MOZ_ASSERT(cx->zone() == bi->zone()); + MOZ_ASSERT(bi->isAligned()); + MOZ_ASSERT(bi->isTenured()); + MOZ_ASSERT(bi->getAllocKind() == gc::AllocKind::BIGINT); +} + void AssertValidValue(JSContext* cx, Value* v) { - if (v->isObject()) + if (v->isObject()) { AssertValidObjectPtr(cx, &v->toObject()); - else if (v->isString()) + } else if (v->isString()) { AssertValidStringPtr(cx, v->toString()); - else if (v->isSymbol()) + } else if (v->isSymbol()) { AssertValidSymbolPtr(cx, v->toSymbol()); + } else if (v->isBigInt()) { + AssertValidBigIntPtr(cx, v->toBigInt()); + } } bool diff --git a/js/src/jit/VMFunctions.h b/js/src/jit/VMFunctions.h index 32830038d1..f4280f5800 100644 --- a/js/src/jit/VMFunctions.h +++ b/js/src/jit/VMFunctions.h @@ -751,6 +751,7 @@ void AssertValidObjectPtr(JSContext* cx, JSObject* obj); void AssertValidObjectOrNullPtr(JSContext* cx, JSObject* obj); void AssertValidStringPtr(JSContext* cx, JSString* str); void AssertValidSymbolPtr(JSContext* cx, JS::Symbol* sym); +void AssertValidBigIntPtr(JSContext* cx, JS::BigInt* bi); void AssertValidValue(JSContext* cx, Value* v); void MarkValueFromIon(JSRuntime* rt, Value* vp); diff --git a/js/src/jit/arm/MacroAssembler-arm-inl.h b/js/src/jit/arm/MacroAssembler-arm-inl.h index 2cc26b2242..3fc07e0de5 100644 --- a/js/src/jit/arm/MacroAssembler-arm-inl.h +++ b/js/src/jit/arm/MacroAssembler-arm-inl.h @@ -1880,6 +1880,29 @@ MacroAssembler::branchTestSymbolImpl(Condition cond, const T& t, Label* label) ma_b(label, c); } +void MacroAssembler::branchTestBigInt(Condition cond, Register tag, Label* label) { + branchTestBigIntImpl(cond, tag, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const BaseIndex& address, Label* label) { + branchTestBigIntImpl(cond, address, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) { + branchTestBigIntImpl(cond, value, label); +} + +template <typename T> +void MacroAssembler::branchTestBigIntImpl(Condition cond, const T& t, Label* label) { + Condition c = testBigInt(cond, t); + ma_b(label, c); +} + +void MacroAssembler::branchTestBigIntTruthy(bool truthy, const ValueOperand& value, Label* label) { + Condition c = testBigIntTruthy(truthy, value); + ma_b(label, c); +} + void MacroAssembler::branchTestNull(Condition cond, Register tag, Label* label) { diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index a50046d697..e099022c27 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -2717,6 +2717,11 @@ MacroAssemblerARMCompat::testSymbol(Assembler::Condition cond, const ValueOperan return testSymbol(cond, value.typeReg()); } +Assembler::Condition MacroAssemblerARMCompat::testBigInt(Assembler::Condition cond, const ValueOperand& value) +{ + return testBigInt(cond, value.typeReg()); +} + Assembler::Condition MacroAssemblerARMCompat::testObject(Assembler::Condition cond, const ValueOperand& value) { @@ -2790,6 +2795,13 @@ MacroAssemblerARMCompat::testSymbol(Assembler::Condition cond, Register tag) return cond; } +Assembler::Condition MacroAssemblerARMCompat::testBigInt(Assembler::Condition cond, Register tag) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_cmp(tag, ImmTag(JSVAL_TAG_BIGINT)); + return cond; +} + Assembler::Condition MacroAssemblerARMCompat::testObject(Assembler::Condition cond, Register tag) { @@ -2907,6 +2919,14 @@ MacroAssemblerARMCompat::testObject(Condition cond, const Address& address) return testObject(cond, scratch); } +Assembler::Condition MacroAssemblerARMCompat::testBigInt(Condition cond, const Address& address) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ScratchRegisterScope scratch(asMasm()); + Register tag = extractTag(address, scratch); + return testBigInt(cond, tag); +} + Assembler::Condition MacroAssemblerARMCompat::testNumber(Condition cond, const Address& address) { @@ -2993,6 +3013,16 @@ MacroAssemblerARMCompat::testInt32(Condition cond, const BaseIndex& src) return cond; } ++Assembler::Condition +MacroAssemblerARMCompat::testBigInt(Condition cond,const BaseIndex& src) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ScratchRegisterScope scratch(asMasm()); + Register tag = extractTag(src, scratch); + ma_cmp(tag, ImmTag(JSVAL_TAG_BIGINT)); + return cond; +} + Assembler::Condition MacroAssemblerARMCompat::testObject(Condition cond, const BaseIndex& src) { @@ -3736,6 +3766,19 @@ MacroAssemblerARMCompat::testStringTruthy(bool truthy, const ValueOperand& value return truthy ? Assembler::NotEqual : Assembler::Equal; } ++Assembler::Condition +MacroAssemblerARMCompat::testBigIntTruthy(bool truthy, const ValueOperand& value) +{ + Register bi = value.payloadReg(); + ScratchRegisterScope scratch(asMasm()); + SecondScratchRegisterScope scratch2(asMasm()); + + ma_dtr(IsLoad, bi, Imm32(BigInt::offsetOfLengthSignAndReservedBits()), + scratch, scratch2); + as_cmp(scratch, Imm8(0)); + return truthy ? Assembler::NotEqual : Assembler::Equal; +} + void MacroAssemblerARMCompat::floor(FloatRegister input, Register output, Label* bail) { diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 2ed9a4f6e1..aaf92539a3 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -695,6 +695,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testUndefined(Condition cond, const ValueOperand& value); Condition testString(Condition cond, const ValueOperand& value); Condition testSymbol(Condition cond, const ValueOperand& value); + Condition testBigInt(Condition cond, const ValueOperand& value); Condition testObject(Condition cond, const ValueOperand& value); Condition testNumber(Condition cond, const ValueOperand& value); Condition testMagic(Condition cond, const ValueOperand& value); @@ -708,6 +709,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testUndefined(Condition cond, Register tag); Condition testString(Condition cond, Register tag); Condition testSymbol(Condition cond, Register tag); + Condition testBigInt(Condition cond, Register tag); Condition testObject(Condition cond, Register tag); Condition testDouble(Condition cond, Register tag); Condition testNumber(Condition cond, Register tag); @@ -723,6 +725,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testUndefined(Condition cond, const Address& address); Condition testString(Condition cond, const Address& address); Condition testSymbol(Condition cond, const Address& address); + Condition testBigInt(Condition cond, const Address& address); Condition testObject(Condition cond, const Address& address); Condition testNumber(Condition cond, const Address& address); @@ -731,6 +734,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testBoolean(Condition cond, const BaseIndex& src); Condition testString(Condition cond, const BaseIndex& src); Condition testSymbol(Condition cond, const BaseIndex& src); + Condition testBigInt(Condition cond, const BaseIndex& src); Condition testInt32(Condition cond, const BaseIndex& src); Condition testObject(Condition cond, const BaseIndex& src); Condition testDouble(Condition cond, const BaseIndex& src); @@ -749,6 +753,8 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM void unboxString(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const Address& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const BaseIndex& src, Register dest) { unboxNonDouble(src, dest); } @@ -797,6 +803,7 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM Condition testBooleanTruthy(bool truthy, const ValueOperand& operand); Condition testDoubleTruthy(bool truthy, FloatRegister reg); Condition testStringTruthy(bool truthy, const ValueOperand& value); + Condition testBigIntTruthy(bool truthy, const ValueOperand& value); void boolValueToFloat32(const ValueOperand& operand, FloatRegister dest); void int32ValueToFloat32(const ValueOperand& operand, FloatRegister dest); diff --git a/js/src/jit/mips32/MacroAssembler-mips32-inl.h b/js/src/jit/mips32/MacroAssembler-mips32-inl.h index 8a2e8c6bfd..a805f7bdb9 100644 --- a/js/src/jit/mips32/MacroAssembler-mips32-inl.h +++ b/js/src/jit/mips32/MacroAssembler-mips32-inl.h @@ -963,6 +963,19 @@ MacroAssembler::branchTestSymbol(Condition cond, const ValueOperand& value, Labe branchTestSymbol(cond, value.typeReg(), label); } +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) +{ + branchTestBigInt(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestBigIntTruthy(bool b, const ValueOperand& value, Label* label) +{ + Register bi = value.payloadReg(); + SecondScratchRegisterScope scratch2(*this); + ma_lw(scratch2, Address(bi, BigInt::offsetOfLengthSignAndReservedBits())); + ma_b(scratch2, Imm32(0), label, b ? NotEqual : Equal); +} + void MacroAssembler::branchTestNull(Condition cond, const ValueOperand& value, Label* label) { diff --git a/js/src/jit/mips64/CodeGenerator-mips64.cpp b/js/src/jit/mips64/CodeGenerator-mips64.cpp index 7cd90fba7a..8e96171615 100644 --- a/js/src/jit/mips64/CodeGenerator-mips64.cpp +++ b/js/src/jit/mips64/CodeGenerator-mips64.cpp @@ -196,6 +196,9 @@ CodeGeneratorMIPS64::visitUnbox(LUnbox* unbox) case MIRType::Symbol: masm.unboxSymbol(inputReg, result); break; + case MIRType::BigInt: + masm.unboxBigInt(inputReg, result); + break; default: MOZ_CRASH("Given MIRType cannot be unboxed."); } @@ -219,6 +222,9 @@ CodeGeneratorMIPS64::visitUnbox(LUnbox* unbox) case MIRType::Symbol: masm.unboxSymbol(inputAddr, result); break; + case MIRType::BigInt: + masm.unboxBigInt(inputAddr, result); + break; default: MOZ_CRASH("Given MIRType cannot be unboxed."); } diff --git a/js/src/jit/mips64/MacroAssembler-mips64-inl.h b/js/src/jit/mips64/MacroAssembler-mips64-inl.h index 4c1f3e37f0..9ed2b35a36 100644 --- a/js/src/jit/mips64/MacroAssembler-mips64-inl.h +++ b/js/src/jit/mips64/MacroAssembler-mips64-inl.h @@ -622,6 +622,21 @@ MacroAssembler::branchTestSymbol(Condition cond, const ValueOperand& value, Labe branchTestSymbol(cond, scratch2, label); } +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) +{ + SecondScratchRegisterScope scratch2(*this); + splitTag(value, scratch2); + branchTestBigInt(cond, scratch2, label); +} + +void MacroAssembler::branchTestBigIntTruthy(bool b, const ValueOperand& value, Label* label) +{ + SecondScratchRegisterScope scratch2(*this); + unboxBigInt(value, scratch2); + loadPtr(Address(scratch2, BigInt::offsetOfLengthSignAndReservedBits()), scratch2); + ma_b(scratch2, ImmWord(0), label, b ? NotEqual : Equal); +} + void MacroAssembler::branchTestNull(Condition cond, const ValueOperand& value, Label* label) { diff --git a/js/src/jit/mips64/MacroAssembler-mips64.cpp b/js/src/jit/mips64/MacroAssembler-mips64.cpp index 96989e393d..df7ec624a9 100644 --- a/js/src/jit/mips64/MacroAssembler-mips64.cpp +++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp @@ -1495,6 +1495,21 @@ MacroAssemblerMIPS64Compat::unboxSymbol(Register src, Register dest) ma_dext(dest, src, Imm32(0), Imm32(JSVAL_TAG_SHIFT)); } +void MacroAssemblerMIPS64Compat::unboxBigInt(const ValueOperand& operand, Register dest) +{ + unboxNonDouble(operand, dest, JSVAL_TYPE_BIGINT); +} + +void MacroAssemblerMIPS64Compat::unboxBigInt(Register src, Register dest) +{ + unboxNonDouble(src, dest, JSVAL_TYPE_BIGINT); +} + +void MacroAssemblerMIPS64Compat::unboxBigInt(const Address& src, Register dest) +{ + unboxNonDouble(src, dest, JSVAL_TYPE_BIGINT); +} + void MacroAssemblerMIPS64Compat::unboxSymbol(const Address& src, Register dest) { diff --git a/js/src/jit/mips64/MacroAssembler-mips64.h b/js/src/jit/mips64/MacroAssembler-mips64.h index 04aa2ea702..3a84b7c0c9 100644 --- a/js/src/jit/mips64/MacroAssembler-mips64.h +++ b/js/src/jit/mips64/MacroAssembler-mips64.h @@ -361,6 +361,9 @@ class MacroAssemblerMIPS64Compat : public MacroAssemblerMIPS64 void unboxSymbol(const ValueOperand& src, Register dest); void unboxSymbol(Register src, Register dest); void unboxSymbol(const Address& src, Register dest); + void unboxBigInt(const ValueOperand& operand, Register dest); + void unboxBigInt(Register src, Register dest); + void unboxBigInt(const Address& src, Register dest); void unboxObject(const ValueOperand& src, Register dest); void unboxObject(Register src, Register dest); void unboxObject(const Address& src, Register dest); diff --git a/js/src/jit/none/MacroAssembler-none.h b/js/src/jit/none/MacroAssembler-none.h index 599fe75bfb..f71439a6cc 100644 --- a/js/src/jit/none/MacroAssembler-none.h +++ b/js/src/jit/none/MacroAssembler-none.h @@ -359,6 +359,7 @@ class MacroAssemblerNone : public Assembler template <typename T> void unboxBoolean(T, Register) { MOZ_CRASH(); } template <typename T> void unboxString(T, Register) { MOZ_CRASH(); } template <typename T> void unboxSymbol(T, Register) { MOZ_CRASH(); } + template <typename T> void unboxBigInt(T, Register) { MOZ_CRASH(); } template <typename T> void unboxObject(T, Register) { MOZ_CRASH(); } template <typename T> void unboxDouble(T, FloatRegister) { MOZ_CRASH(); } void unboxValue(const ValueOperand&, AnyRegister) { MOZ_CRASH(); } @@ -393,6 +394,7 @@ class MacroAssemblerNone : public Assembler void loadConstantFloat32(wasm::RawF32, FloatRegister) { MOZ_CRASH(); } Condition testInt32Truthy(bool, ValueOperand) { MOZ_CRASH(); } Condition testStringTruthy(bool, ValueOperand) { MOZ_CRASH(); } + Condition testBigIntTruthy(bool, ValueOperand) { MOZ_CRASH(); } template <typename T> void loadUnboxedValue(T, MIRType, AnyRegister) { MOZ_CRASH(); } template <typename T> void storeUnboxedValue(const ConstantOrRegister&, MIRType, T, MIRType) { MOZ_CRASH(); } diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 6d623293cd..f18cbb9e1d 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -725,7 +725,6 @@ class MemoryAccessDesc trapOffset_(trapOffset) { MOZ_ASSERT(Scalar::isSimdType(type) == (numSimdElems > 0)); - MOZ_ASSERT(numSimdElems <= jit::ScalarTypeToLength(type)); MOZ_ASSERT(mozilla::IsPowerOfTwo(align)); MOZ_ASSERT_IF(isSimd(), hasTrap()); MOZ_ASSERT_IF(isAtomic(), hasTrap()); diff --git a/js/src/jit/shared/CodeGenerator-shared.cpp b/js/src/jit/shared/CodeGenerator-shared.cpp index 16e082745c..3701f24872 100644 --- a/js/src/jit/shared/CodeGenerator-shared.cpp +++ b/js/src/jit/shared/CodeGenerator-shared.cpp @@ -431,6 +431,7 @@ CodeGeneratorShared::encodeAllocation(LSnapshot* snapshot, MDefinition* mir, case MIRType::Int32: case MIRType::String: case MIRType::Symbol: + case MIRType::BigInt: case MIRType::Object: case MIRType::ObjectOrNull: case MIRType::Boolean: diff --git a/js/src/jit/shared/LIR-shared.h b/js/src/jit/shared/LIR-shared.h index 2d51a580bf..69782f8061 100644 --- a/js/src/jit/shared/LIR-shared.h +++ b/js/src/jit/shared/LIR-shared.h @@ -3211,7 +3211,7 @@ class LBitNotI : public LInstructionHelper<1, 1, 0> }; // Call a VM function to perform a BITNOT operation. -class LBitNotV : public LCallInstructionHelper<1, BOX_PIECES, 0> +class LBitNotV : public LCallInstructionHelper<BOX_PIECES, BOX_PIECES, 0> { public: LIR_HEADER(BitNotV) @@ -3271,7 +3271,7 @@ class LBitOpI64 : public LInstructionHelper<INT64_PIECES, 2 * INT64_PIECES, 0> }; // Call a VM function to perform a bitwise operation. -class LBitOpV : public LCallInstructionHelper<1, 2 * BOX_PIECES, 0> +class LBitOpV : public LCallInstructionHelper<BOX_PIECES, 2 * BOX_PIECES, 0> { JSOp jsop_; diff --git a/js/src/jit/shared/Lowering-shared-inl.h b/js/src/jit/shared/Lowering-shared-inl.h index d17a9ba568..cdbba9ef84 100644 --- a/js/src/jit/shared/Lowering-shared-inl.h +++ b/js/src/jit/shared/Lowering-shared-inl.h @@ -402,7 +402,8 @@ LIRGeneratorShared::redefine(MDefinition* def, MDefinition* as) case MIRType::Object: case MIRType::ObjectOrNull: case MIRType::String: - case MIRType::Symbol: { + case MIRType::Symbol: + case MIRType::BigInt: { LAssertResultT* check = new(alloc()) LAssertResultT(useRegister(def)); add(check, def->toInstruction()); break; diff --git a/js/src/jit/shared/Lowering-shared.cpp b/js/src/jit/shared/Lowering-shared.cpp index 0acf34dfb4..f0f4e5eb85 100644 --- a/js/src/jit/shared/Lowering-shared.cpp +++ b/js/src/jit/shared/Lowering-shared.cpp @@ -100,6 +100,9 @@ LIRGeneratorShared::visitConstant(MConstant* ins) case MIRType::Symbol: define(new(alloc()) LPointer(ins->toSymbol()), ins); break; + case MIRType::BigInt: + define(new (alloc()) LPointer(ins->toBigInt()), ins); + break; case MIRType::Object: define(new(alloc()) LPointer(&ins->toObject()), ins); break; diff --git a/js/src/jit/x64/CodeGenerator-x64.cpp b/js/src/jit/x64/CodeGenerator-x64.cpp index 61ce8a187c..3f4052f51d 100644 --- a/js/src/jit/x64/CodeGenerator-x64.cpp +++ b/js/src/jit/x64/CodeGenerator-x64.cpp @@ -121,6 +121,9 @@ CodeGeneratorX64::visitUnbox(LUnbox* unbox) case MIRType::Symbol: cond = masm.testSymbol(Assembler::NotEqual, value); break; + case MIRType::BigInt: + cond = masm.testBigInt(Assembler::NotEqual, value); + break; default: MOZ_CRASH("Given MIRType cannot be unboxed."); } @@ -145,6 +148,9 @@ CodeGeneratorX64::visitUnbox(LUnbox* unbox) case MIRType::Symbol: masm.unboxSymbol(input, result); break; + case MIRType::BigInt: + masm.unboxBigInt(input, result); + break; default: MOZ_CRASH("Given MIRType cannot be unboxed."); } @@ -439,6 +445,8 @@ CodeGeneratorX64::wasmStore(const wasm::MemoryAccessDesc& access, const LAllocat case Scalar::Int16x8: case Scalar::Int32x4: case Scalar::Uint8Clamped: + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); } diff --git a/js/src/jit/x64/Lowering-x64.cpp b/js/src/jit/x64/Lowering-x64.cpp index db92d8dac5..4aebe05af2 100644 --- a/js/src/jit/x64/Lowering-x64.cpp +++ b/js/src/jit/x64/Lowering-x64.cpp @@ -239,6 +239,8 @@ LIRGeneratorX64::visitWasmStore(MWasmStore* ins) case Scalar::Int32x4: valueAlloc = useRegisterAtStart(value); break; + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); diff --git a/js/src/jit/x64/MacroAssembler-x64.cpp b/js/src/jit/x64/MacroAssembler-x64.cpp index 759977bac1..1fab669d2b 100644 --- a/js/src/jit/x64/MacroAssembler-x64.cpp +++ b/js/src/jit/x64/MacroAssembler-x64.cpp @@ -713,6 +713,8 @@ MacroAssembler::wasmLoad(const wasm::MemoryAccessDesc& access, Operand srcAddr, break; case Scalar::Int64: MOZ_CRASH("int64 loads must use load64"); + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); @@ -759,6 +761,8 @@ MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, Operand srcAdd case Scalar::Int16x8: case Scalar::Int32x4: MOZ_CRASH("non-int64 loads should use load()"); + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); @@ -822,6 +826,8 @@ MacroAssembler::wasmStore(const wasm::MemoryAccessDesc& access, AnyRegister valu MOZ_ASSERT(access.numSimdElems() == 8, "unexpected partial store"); storeUnalignedSimd128Int(value.fpu(), dstAddr); break; + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); diff --git a/js/src/jit/x64/MacroAssembler-x64.h b/js/src/jit/x64/MacroAssembler-x64.h index 3af87e1ef8..7acb2d34f6 100644 --- a/js/src/jit/x64/MacroAssembler-x64.h +++ b/js/src/jit/x64/MacroAssembler-x64.h @@ -231,6 +231,11 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared cmp32(tag, ImmTag(JSVAL_TAG_SYMBOL)); return cond; } + Condition testBigInt(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + cmp32(tag, ImmTag(JSVAL_TAG_BIGINT)); + return cond; + } Condition testObject(Condition cond, Register tag) { MOZ_ASSERT(cond == Equal || cond == NotEqual); cmp32(tag, ImmTag(JSVAL_TAG_OBJECT)); @@ -306,6 +311,11 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared splitTag(src, scratch); return testSymbol(cond, scratch); } + Condition testBigInt(Condition cond, const ValueOperand& src) { + ScratchRegisterScope scratch(asMasm()); + splitTag(src, scratch); + return testBigInt(cond, scratch); + } Condition testObject(Condition cond, const ValueOperand& src) { ScratchRegisterScope scratch(asMasm()); splitTag(src, scratch); @@ -359,6 +369,11 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared splitTag(src, scratch); return testSymbol(cond, scratch); } + Condition testBigInt(Condition cond, const Address& src) { + ScratchRegisterScope scratch(asMasm()); + splitTag(src, scratch); + return testBigInt(cond, scratch); + } Condition testObject(Condition cond, const Address& src) { ScratchRegisterScope scratch(asMasm()); splitTag(src, scratch); @@ -406,6 +421,11 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared splitTag(src, scratch); return testSymbol(cond, scratch); } + Condition testBigInt(Condition cond, const BaseIndex& src) { + ScratchRegisterScope scratch(asMasm()); + splitTag(src, scratch); + return testBigInt(cond, scratch); + } Condition testInt32(Condition cond, const BaseIndex& src) { ScratchRegisterScope scratch(asMasm()); splitTag(src, scratch); @@ -794,6 +814,9 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared void unboxSymbol(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const Operand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const Operand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxObject(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const Operand& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const Address& src, Register dest) { unboxNonDouble(Operand(src), dest); } @@ -894,6 +917,13 @@ class MacroAssemblerX64 : public MacroAssemblerX86Shared cmp32(Operand(scratch, JSString::offsetOfLength()), Imm32(0)); return truthy ? Assembler::NotEqual : Assembler::Equal; } + Condition testBigIntTruthy(bool truthy, const ValueOperand& value) { + ScratchRegisterScope scratch(asMasm()); + unboxBigInt(value, scratch); + cmpPtr(Operand(scratch, BigInt::offsetOfLengthSignAndReservedBits()), + ImmWord(0)); + return truthy ? Assembler::NotEqual : Assembler::Equal; + } template <typename T> inline void loadInt32OrDouble(const T& src, FloatRegister dest); diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp index 3111ae4c92..5ec00da849 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp @@ -402,6 +402,8 @@ CodeGeneratorX86Shared::visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTyp case Scalar::Int8x16: case Scalar::Int16x8: case Scalar::Int32x4: + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); case Scalar::Float32: diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h b/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h index e7f058c396..36f3a008a9 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared-inl.h @@ -944,6 +944,30 @@ MacroAssembler::branchTestSymbolImpl(Condition cond, const T& t, Label* label) j(cond, label); } +void MacroAssembler::branchTestBigInt(Condition cond, Register tag, Label* label) { + branchTestBigIntImpl(cond, tag, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const BaseIndex& address, Label* label) { + branchTestBigIntImpl(cond, address, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, Label* label) { + branchTestBigIntImpl(cond, value, label); +} + +template <typename T> +void MacroAssembler::branchTestBigIntImpl(Condition cond, const T& t, Label* label) { + cond = testBigInt(cond, t); + j(cond, label); +} + +void MacroAssembler::branchTestBigIntTruthy(bool truthy, const ValueOperand& value, Label* label) +{ + Condition cond = testBigIntTruthy(truthy, value); + j(cond, label); +} + void MacroAssembler::branchTestNull(Condition cond, Register tag, Label* label) { diff --git a/js/src/jit/x86/Lowering-x86.cpp b/js/src/jit/x86/Lowering-x86.cpp index ad3dd8afcb..27859c0772 100644 --- a/js/src/jit/x86/Lowering-x86.cpp +++ b/js/src/jit/x86/Lowering-x86.cpp @@ -326,6 +326,8 @@ LIRGeneratorX86::visitWasmStore(MWasmStore* ins) add(lir, ins); return; } + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); diff --git a/js/src/jit/x86/MacroAssembler-x86.cpp b/js/src/jit/x86/MacroAssembler-x86.cpp index aef460e2f4..9962b9c594 100644 --- a/js/src/jit/x86/MacroAssembler-x86.cpp +++ b/js/src/jit/x86/MacroAssembler-x86.cpp @@ -616,6 +616,8 @@ MacroAssembler::wasmLoad(const wasm::MemoryAccessDesc& access, Operand srcAddr, vmovdquWithPatch(srcAddr, out.fpu()); break; case Scalar::Int64: + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected type"); @@ -727,6 +729,8 @@ MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, Operand srcAdd case Scalar::Int16x8: case Scalar::Int32x4: MOZ_CRASH("non-int64 loads should use load()"); + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Uint8Clamped: case Scalar::MaxTypedArrayViewType: MOZ_CRASH("unexpected array type"); @@ -790,6 +794,8 @@ MacroAssembler::wasmStore(const wasm::MemoryAccessDesc& access, AnyRegister valu case Scalar::Int64: MOZ_CRASH("Should be handled in storeI64."); case Scalar::MaxTypedArrayViewType: + case Scalar::BigInt64: + case Scalar::BigUint64: MOZ_CRASH("unexpected type"); } append(wasm::MemoryPatch(size())); diff --git a/js/src/jit/x86/MacroAssembler-x86.h b/js/src/jit/x86/MacroAssembler-x86.h index 01efd007d3..cbe1d6da8c 100644 --- a/js/src/jit/x86/MacroAssembler-x86.h +++ b/js/src/jit/x86/MacroAssembler-x86.h @@ -294,6 +294,11 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cmp32(tag, ImmTag(JSVAL_TAG_SYMBOL)); return cond; } + Condition testBigInt(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + cmp32(tag, ImmTag(JSVAL_TAG_BIGINT)); + return cond; + } Condition testObject(Condition cond, Register tag) { MOZ_ASSERT(cond == Equal || cond == NotEqual); cmp32(tag, ImmTag(JSVAL_TAG_OBJECT)); @@ -410,6 +415,9 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared Condition testSymbol(Condition cond, const ValueOperand& value) { return testSymbol(cond, value.typeReg()); } + Condition testBigInt(Condition cond, const ValueOperand& value) { + return testBigInt(cond, value.typeReg()); + } Condition testObject(Condition cond, const ValueOperand& value) { return testObject(cond, value.typeReg()); } @@ -455,6 +463,11 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cmp32(tagOf(address), ImmTag(JSVAL_TAG_SYMBOL)); return cond; } + Condition testBigInt(Condition cond, const BaseIndex& address) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + cmp32(tagOf(address), ImmTag(JSVAL_TAG_BIGINT)); + return cond; + } Condition testInt32(Condition cond, const BaseIndex& address) { MOZ_ASSERT(cond == Equal || cond == NotEqual); cmp32(tagOf(address), ImmTag(JSVAL_TAG_INT32)); @@ -696,6 +709,8 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared void unboxString(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxSymbol(const Address& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } + void unboxBigInt(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const ValueOperand& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const Address& src, Register dest) { unboxNonDouble(src, dest); } void unboxObject(const BaseIndex& src, Register dest) { unboxNonDouble(src, dest); } @@ -793,6 +808,12 @@ class MacroAssemblerX86 : public MacroAssemblerX86Shared cmp32(Operand(string, JSString::offsetOfLength()), Imm32(0)); return truthy ? Assembler::NotEqual : Assembler::Equal; } + Condition testBigIntTruthy(bool truthy, const ValueOperand& value) { + Register bi = value.payloadReg(); + cmpPtr(Operand(bi, BigInt::offsetOfLengthSignAndReservedBits()), + ImmWord(0)); + return truthy ? Assembler::NotEqual : Assembler::Equal; + } template <typename T> inline void loadInt32OrDouble(const T& src, FloatRegister dest); diff --git a/js/src/js.msg b/js/src/js.msg index 242d81a5c8..340cfd6ad2 100644 --- a/js/src/js.msg +++ b/js/src/js.msg @@ -364,6 +364,7 @@ MSG_DEF(JSMSG_BAD_NEW_OPTIONAL, 0, JSEXN_SYNTAXERR, "new keyword cannot b MSG_DEF(JSMSG_BAD_OPTIONAL_TEMPLATE, 0, JSEXN_SYNTAXERR, "tagged template cannot be used with optional chain") MSG_DEF(JSMSG_ESCAPED_KEYWORD, 0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes") MSG_DEF(JSMSG_FIELDS_NOT_SUPPORTED, 0, JSEXN_SYNTAXERR, "fields are not currently supported") +MSG_DEF(JSMSG_ILLEGAL_PRIVATE_NAME, 0, JSEXN_SYNTAXERR, "private names aren't valid in this context") // asm.js MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL, 1, JSEXN_TYPEERR, "asm.js type error: {0}") @@ -619,3 +620,13 @@ MSG_DEF(JSMSG_FOR_AWAIT_NOT_OF, 0, JSEXN_TYPEERR, "'for await' loop shoul MSG_DEF(JSMSG_NOT_AN_ASYNC_GENERATOR, 0, JSEXN_TYPEERR, "Not an async generator") MSG_DEF(JSMSG_NOT_AN_ASYNC_ITERATOR, 0, JSEXN_TYPEERR, "Not an async from sync iterator") MSG_DEF(JSMSG_GET_ASYNC_ITER_RETURNED_PRIMITIVE, 0, JSEXN_TYPEERR, "[Symbol.asyncIterator]() returned a non-object value") + +// BigInt +MSG_DEF(JSMSG_BIGINT_TO_NUMBER, 0, JSEXN_TYPEERR, "can't convert BigInt to number") +MSG_DEF(JSMSG_NUMBER_TO_BIGINT, 0, JSEXN_RANGEERR, "can't convert non-finite number to BigInt") +MSG_DEF(JSMSG_BIGINT_TOO_LARGE, 0, JSEXN_RANGEERR, "BigInt is too large to allocate") +MSG_DEF(JSMSG_BIGINT_DIVISION_BY_ZERO, 0, JSEXN_RANGEERR, "BigInt division by zero") +MSG_DEF(JSMSG_BIGINT_NEGATIVE_EXPONENT, 0, JSEXN_RANGEERR, "BigInt negative exponent") +MSG_DEF(JSMSG_BIGINT_INVALID_SYNTAX, 0, JSEXN_SYNTAXERR, "invalid BigInt syntax") +MSG_DEF(JSMSG_NOT_BIGINT, 0, JSEXN_TYPEERR, "not a BigInt") +MSG_DEF(JSMSG_BIGINT_NOT_SERIALIZABLE, 0, JSEXN_TYPEERR, "BigInt value can't be serialized in JSON") diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index b166a786ca..4f5c717be5 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -376,6 +376,13 @@ js::GetElements(JSContext* cx, HandleObject aobj, uint32_t length, Value* vp) } } + if (aobj->is<TypedArrayObject>()) { + Handle<TypedArrayObject*> typedArray = aobj.as<TypedArrayObject>(); + if (typedArray->length() == length) { + return TypedArrayObject::getElements(cx, typedArray, vp); + } + } + if (js::GetElementsOp op = aobj->getOpsGetElements()) { ElementAdder adder(cx, vp, length, ElementAdder::GetElement); return op(cx, aobj, 0, length, &adder); @@ -1099,6 +1106,12 @@ ArrayJoinDenseKernel(JSContext* cx, SeparatorOp sepOp, HandleObject obj, uint32_ * with those as well. */ break; + } else if (elem.isBigInt()) { + // ToString(bigint) doesn't access bigint.toString or + // anything like that, so it can't mutate the array we're + // walking through, so it *could* be handled here. We don't + // do so yet for reasons of initial-implementation economy. + break; } else { MOZ_ASSERT(elem.isMagic(JS_ELEMENTS_HOLE) || elem.isNullOrUndefined()); } diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 02308314c9..2a72ac38a3 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -28,6 +28,8 @@ #include "vm/String-inl.h" +#include "vm/BigIntType.h" + using namespace js; using namespace js::gc; @@ -484,6 +486,13 @@ ToAtomSlow(ExclusiveContext* cx, typename MaybeRooted<Value, allowGC>::HandleTyp } return nullptr; } + if (v.isBigInt()) { + RootedBigInt i(cx, v.toBigInt()); + JSAtom* atom = BigIntToAtom(cx, i); + if (!allowGC && !atom) + cx->recoverFromOutOfMemory(); + return atom; + } MOZ_ASSERT(v.isUndefined()); return cx->names().undefined; } diff --git a/js/src/jsbool.cpp b/js/src/jsbool.cpp index 324868216b..0a70fe49f2 100644 --- a/js/src/jsbool.cpp +++ b/js/src/jsbool.cpp @@ -18,6 +18,7 @@ #include "vm/GlobalObject.h" #include "vm/ProxyObject.h" #include "vm/StringBuffer.h" +#include "vm/BigIntType.h" #include "vm/BooleanObject-inl.h" @@ -170,6 +171,8 @@ js::ToBooleanSlow(HandleValue v) { if (v.isString()) return v.toString()->length() != 0; + if (v.isBigInt()) + return !v.toBigInt()->isZero(); MOZ_ASSERT(v.isObject()); return !EmulatesUndefined(&v.toObject()); diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index c93dee510b..926c7e3c86 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -340,6 +340,21 @@ JSCompartment::wrap(JSContext* cx, MutableHandleString strp) } bool +JSCompartment::wrap(JSContext* cx, MutableHandleBigInt bi) +{ + MOZ_ASSERT(cx->compartment() == this); + + if (bi->zone() == cx->zone()) + return true; + + BigInt* copy = BigInt::copy(cx, bi); + if (!copy) + return false; + bi.set(copy); + return true; +} + +bool JSCompartment::getNonWrapperObjectForCurrentCompartment(JSContext* cx, MutableHandleObject obj) { // Ensure that we have entered a compartment. diff --git a/js/src/jscompartment.h b/js/src/jscompartment.h index dbada58439..a02b39301d 100644 --- a/js/src/jscompartment.h +++ b/js/src/jscompartment.h @@ -583,6 +583,7 @@ struct JSCompartment MOZ_MUST_USE inline bool wrap(JSContext* cx, JS::MutableHandleValue vp); MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandleString strp); + MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandle<JS::BigInt*> bi); MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandleObject obj); MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<js::PropertyDescriptor> desc); MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<JS::GCVector<JS::Value>> vec); diff --git a/js/src/jscompartmentinlines.h b/js/src/jscompartmentinlines.h index c17ba99de2..c092889e28 100644 --- a/js/src/jscompartmentinlines.h +++ b/js/src/jscompartmentinlines.h @@ -79,6 +79,14 @@ JSCompartment::wrap(JSContext* cx, JS::MutableHandleValue vp) return true; } + if (vp.isBigInt()) { + JS::RootedBigInt bi(cx, vp.toBigInt()); + if (!wrap(cx, &bi)) + return false; + vp.setBigInt(bi); + return true; + } + MOZ_ASSERT(vp.isObject()); /* diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 0cd57ab52b..f73cc5fd65 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -9,6 +9,8 @@ #include <stdint.h> +#include "builtin/BigInt.h" + #include "jscntxt.h" #include "jscompartment.h" #include "jsgc.h" @@ -299,6 +301,8 @@ js::GetBuiltinClass(JSContext* cx, HandleObject obj, ESClass* cls) *cls = ESClass::Arguments; else if (obj->is<ErrorObject>()) *cls = ESClass::Error; + else if (obj->is<BigIntObject>()) + *cls = ESClass::BigInt; else *cls = ESClass::Other; diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index 6a13d4343c..6a873e15da 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -1467,12 +1467,15 @@ GetSCOffset(JSStructuredCloneWriter* writer); namespace Scalar { -/** - * Scalar types that can appear in typed arrays and typed objects. The enum - * values must to be kept in sync with the JS_SCALARTYPEREPR_ constants, as - * well as the TypedArrayObject::classes and TypedArrayObject::protoClasses - * definitions. - */ +// Scalar types that can appear in typed arrays and typed objects. +// The enum values must be kept in sync with: +// * the JS_SCALARTYPEREPR constants +// * the TYPEDARRAY_KIND constants +// * the SCTAG_TYPED_ARRAY constants +// * JS_FOR_EACH_TYPEDARRAY +// * JS_FOR_PROTOTYPES_ +// * JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE +// * JIT compilation enum Type { Int8 = 0, Uint8, @@ -1489,6 +1492,9 @@ enum Type { */ Uint8Clamped, + BigInt64, + BigUint64, + /** * Types that don't have their own TypedArray equivalent, for now. */ @@ -1518,6 +1524,8 @@ byteSize(Type atype) return 4; case Int64: case Float64: + case BigInt64: + case BigUint64: return 8; case Int8x16: case Int16x8: @@ -1539,6 +1547,7 @@ isSignedIntType(Type atype) { case Int8x16: case Int16x8: case Int32x4: + case BigInt64: return true; case Uint8: case Uint8Clamped: @@ -1547,12 +1556,39 @@ isSignedIntType(Type atype) { case Float32: case Float64: case Float32x4: + case BigUint64: return false; default: MOZ_CRASH("invalid scalar type"); } } +static inline bool isBigIntType(Type atype) { + switch (atype) { + case BigInt64: + case BigUint64: + return true; + case Int8: + case Int16: + case Int32: + case Int64: + case Uint8: + case Uint8Clamped: + case Uint16: + case Uint32: + case Float32: + case Float64: + case Int8x16: + case Int16x8: + case Int32x4: + case Float32x4: + return false; + case MaxTypedArrayViewType: + break; + } + MOZ_CRASH("invalid scalar type"); +} + static inline bool isSimdType(Type atype) { switch (atype) { @@ -1566,6 +1602,8 @@ isSimdType(Type atype) { case Int64: case Float32: case Float64: + case BigInt64: + case BigUint64: return false; case Int8x16: case Int16x8: @@ -1598,6 +1636,8 @@ scalarByteSize(Type atype) { case Int64: case Float32: case Float64: + case BigInt64: + case BigUint64: case MaxTypedArrayViewType: break; } @@ -1628,6 +1668,10 @@ JS_NewInt32Array(JSContext* cx, uint32_t nelements); extern JS_FRIEND_API(JSObject*) JS_NewUint32Array(JSContext* cx, uint32_t nelements); extern JS_FRIEND_API(JSObject*) +JS_NewBigInt64Array(JSContext* cx, int32_t nelements); +extern JS_FRIEND_API(JSObject*) +JS_NewBigUint64Array(JSContext* cx, int32_t nelements); +extern JS_FRIEND_API(JSObject*) JS_NewFloat32Array(JSContext* cx, uint32_t nelements); extern JS_FRIEND_API(JSObject*) JS_NewFloat64Array(JSContext* cx, uint32_t nelements); @@ -1655,6 +1699,10 @@ JS_NewInt32ArrayFromArray(JSContext* cx, JS::HandleObject array); extern JS_FRIEND_API(JSObject*) JS_NewUint32ArrayFromArray(JSContext* cx, JS::HandleObject array); extern JS_FRIEND_API(JSObject*) +JS_NewBigInt64ArrayWithBuffer(JSContext* cx, JS::HandleObject array); +extern JS_FRIEND_API(JSObject*) +JS_NewBigUint64ArrayWithBuffer(JSContext* cx, JS::HandleObject array); +extern JS_FRIEND_API(JSObject*) JS_NewFloat32ArrayFromArray(JSContext* cx, JS::HandleObject array); extern JS_FRIEND_API(JSObject*) JS_NewFloat64ArrayFromArray(JSContext* cx, JS::HandleObject array); @@ -1688,6 +1736,12 @@ extern JS_FRIEND_API(JSObject*) JS_NewUint32ArrayWithBuffer(JSContext* cx, JS::HandleObject arrayBuffer, uint32_t byteOffset, int32_t length); extern JS_FRIEND_API(JSObject*) +JS_NewBigInt64ArrayWithBuffer(JSContext* cx, JS::HandleObject arrayBuffer, + uint32_t byteOffset, int32_t length); +extern JS_FRIEND_API(JSObject*) +JS_NewBigUint64ArrayWithBuffer(JSContext* cx, JS::HandleObject arrayBuffer, + uint32_t byteOffset, int32_t length); +extern JS_FRIEND_API(JSObject*) JS_NewFloat32ArrayWithBuffer(JSContext* cx, JS::HandleObject arrayBuffer, uint32_t byteOffset, int32_t length); extern JS_FRIEND_API(JSObject*) @@ -1747,6 +1801,10 @@ JS_IsInt32Array(JSObject* obj); extern JS_FRIEND_API(bool) JS_IsUint32Array(JSObject* obj); extern JS_FRIEND_API(bool) +JS_IsBigInt64Array(JSObject* obj); +extern JS_FRIEND_API(bool) +JS_IsBigUint64Array(JSObject* obj); +extern JS_FRIEND_API(bool) JS_IsFloat32Array(JSObject* obj); extern JS_FRIEND_API(bool) JS_IsFloat64Array(JSObject* obj); @@ -1784,6 +1842,10 @@ UnwrapInt32Array(JSObject* obj); extern JS_FRIEND_API(JSObject*) UnwrapUint32Array(JSObject* obj); extern JS_FRIEND_API(JSObject*) +UnwrapBigInt64Array(JSObject* obj); +extern JS_FRIEND_API(JSObject*) +UnwrapBigUint64Array(JSObject* obj); +extern JS_FRIEND_API(JSObject*) UnwrapFloat32Array(JSObject* obj); extern JS_FRIEND_API(JSObject*) UnwrapFloat64Array(JSObject* obj); @@ -1807,6 +1869,8 @@ extern JS_FRIEND_DATA(const Class* const) Int16ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Uint16ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Int32ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Uint32ArrayClassPtr; +extern JS_FRIEND_DATA(const Class* const) BigInt64ArrayClassPtr; +extern JS_FRIEND_DATA(const Class* const) BigUint64ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Float32ArrayClassPtr; extern JS_FRIEND_DATA(const Class* const) Float64ArrayClassPtr; diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index e172ea36c2..c849bacc8c 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -222,6 +222,7 @@ #include "jit/JitcodeMap.h" #include "js/SliceBudget.h" #include "proxy/DeadObjectProxy.h" +#include "vm/BigIntType.h" #include "vm/Debugger.h" #include "vm/ProxyObject.h" #include "vm/Shape.h" @@ -371,7 +372,8 @@ static const FinalizePhase BackgroundFinalizePhases[] = { AllocKind::STRING, AllocKind::FAT_INLINE_ATOM, AllocKind::ATOM, - AllocKind::SYMBOL + AllocKind::SYMBOL, + AllocKind::BIGINT } }, { @@ -1986,6 +1988,7 @@ MovingTracer::updateEdge(T** thingp) void MovingTracer::onObjectEdge(JSObject** objp) { updateEdge(objp); } void MovingTracer::onShapeEdge(Shape** shapep) { updateEdge(shapep); } void MovingTracer::onStringEdge(JSString** stringp) { updateEdge(stringp); } +void MovingTracer::onBigIntEdge(JS::BigInt** bip) { updateEdge(bip); } void MovingTracer::onScriptEdge(JSScript** scriptp) { updateEdge(scriptp); } void MovingTracer::onLazyScriptEdge(LazyScript** lazyp) { updateEdge(lazyp); } void MovingTracer::onBaseShapeEdge(BaseShape** basep) { updateEdge(basep); } @@ -2320,7 +2323,8 @@ static const AllocKinds UpdatePhaseMisc { AllocKind::ACCESSOR_SHAPE, AllocKind::OBJECT_GROUP, AllocKind::STRING, - AllocKind::JITCODE + AllocKind::JITCODE, + AllocKind::REGEXP_SHARED }; static const AllocKinds UpdatePhaseObjects { @@ -6557,6 +6561,8 @@ JS::GCCellPtr::GCCellPtr(const Value& v) ptr = checkedCast(&v.toObject(), JS::TraceKind::Object); else if (v.isSymbol()) ptr = checkedCast(v.toSymbol(), JS::TraceKind::Symbol); + else if (v.isBigInt()) + ptr = checkedCast(v.toBigInt(), JS::TraceKind::BigInt); else if (v.isPrivateGCThing()) ptr = checkedCast(v.toGCThing(), v.toGCThing()->getTraceKind()); else diff --git a/js/src/jsgc.h b/js/src/jsgc.h index ffd6102747..521dea05c6 100644 --- a/js/src/jsgc.h +++ b/js/src/jsgc.h @@ -20,6 +20,7 @@ #include "threading/ConditionVariable.h" #include "threading/Thread.h" #include "vm/NativeObject.h" +#include "vm/BigIntType.h" namespace js { @@ -119,6 +120,7 @@ IsNurseryAllocable(AllocKind kind) false, /* AllocKind::FAT_INLINE_ATOM */ false, /* AllocKind::ATOM */ false, /* AllocKind::SYMBOL */ + false, /* AllocKind::BIGINT */ false, /* AllocKind::JITCODE */ false, /* AllocKind::SCOPE */ false, /* AllocKind::REGEXP_SHARED */ @@ -158,6 +160,7 @@ IsBackgroundFinalized(AllocKind kind) true, /* AllocKind::FAT_INLINE_ATOM */ true, /* AllocKind::ATOM */ true, /* AllocKind::SYMBOL */ + true, /* AllocKind::BIGINT */ false, /* AllocKind::JITCODE */ true, /* AllocKind::SCOPE */ true, /* AllocKind::REGEXP_SHARED */ diff --git a/js/src/jsmath.cpp b/js/src/jsmath.cpp index 75ab8fcb29..8f17ff15ca 100644 --- a/js/src/jsmath.cpp +++ b/js/src/jsmath.cpp @@ -13,6 +13,7 @@ #include "mozilla/MathAlgorithms.h" #include "mozilla/MemoryReporting.h" #include "mozilla/Unused.h" +#include "mozilla/WrappingOperations.h" #include <algorithm> // for std::max #include <fcntl.h> @@ -103,6 +104,7 @@ using mozilla::IsNegative; using mozilla::IsNegativeZero; using mozilla::PositiveInfinity; using mozilla::NegativeInfinity; +using mozilla::WrappingMultiply; using JS::ToNumber; using JS::GenericNaN; @@ -458,16 +460,13 @@ js::math_floor(JSContext* cx, unsigned argc, Value* vp) bool js::math_imul_handle(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) { - uint32_t a = 0, b = 0; - if (!lhs.isUndefined() && !ToUint32(cx, lhs, &a)) + int32_t a = 0, b = 0; + if (!lhs.isUndefined() && !ToInt32(cx, lhs, &a)) return false; - if (!rhs.isUndefined() && !ToUint32(cx, rhs, &b)) + if (!rhs.isUndefined() && !ToInt32(cx, rhs, &b)) return false; - uint32_t product = a * b; - res.setInt32(product > INT32_MAX - ? int32_t(INT32_MIN + (product - INT32_MAX - 1)) - : int32_t(product)); + res.setInt32(WrappingMultiply(a, b)); return true; } @@ -686,29 +685,23 @@ js::ecmaPow(double x, double y) } bool -js::math_pow_handle(JSContext* cx, HandleValue base, HandleValue power, MutableHandleValue result) +js::math_pow(JSContext* cx, unsigned argc, Value* vp) { + CallArgs args = CallArgsFromVp(argc, vp); + double x; - if (!ToNumber(cx, base, &x)) + if (!ToNumber(cx, args.get(0), &x)) return false; double y; - if (!ToNumber(cx, power, &y)) + if (!ToNumber(cx, args.get(1), &y)) return false; double z = ecmaPow(x, y); - result.setNumber(z); + args.rval().setNumber(z); return true; } -bool -js::math_pow(JSContext* cx, unsigned argc, Value* vp) -{ - CallArgs args = CallArgsFromVp(argc, vp); - - return math_pow_handle(cx, args.get(0), args.get(1), args.rval()); -} - uint64_t js::GenerateRandomSeed() { diff --git a/js/src/jsmath.h b/js/src/jsmath.h index e716edce14..558d3d356a 100644 --- a/js/src/jsmath.h +++ b/js/src/jsmath.h @@ -124,10 +124,6 @@ extern bool math_sqrt(JSContext* cx, unsigned argc, js::Value* vp); extern bool -math_pow_handle(JSContext* cx, js::HandleValue base, js::HandleValue power, - js::MutableHandleValue result); - -extern bool math_pow(JSContext* cx, unsigned argc, js::Value* vp); extern bool diff --git a/js/src/jsnum.cpp b/js/src/jsnum.cpp index 549596e579..fd23e6ccd5 100644 --- a/js/src/jsnum.cpp +++ b/js/src/jsnum.cpp @@ -524,21 +524,30 @@ Number(JSContext* cx, unsigned argc, Value* vp) bool isConstructing = args.isConstructing(); if (args.length() > 0) { - if (!ToNumber(cx, args[0])) + // BigInt proposal section 6.2, steps 2a-c. + if (!ToNumeric(cx, args[0])) return false; - args.rval().set(args[0]); - } else { - args.rval().setInt32(0); + if (args[0].isBigInt()) + args[0].setNumber(BigInt::numberValue(args[0].toBigInt())); + MOZ_ASSERT(args[0].isNumber()); } - if (!isConstructing) + if (!isConstructing) { + if (args.length() > 0) { + args.rval().set(args[0]); + } else { + args.rval().setInt32(0); + } return true; + } RootedObject newTarget(cx, &args.newTarget().toObject()); RootedObject proto(cx); if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) return false; - JSObject* obj = NumberObject::create(cx, args.rval().toNumber(), proto); + + double d = args.length() > 0 ? args[0].toNumber() : 0; + JSObject* obj = NumberObject::create(cx, d, proto); if (!obj) return false; args.rval().setObject(*obj); @@ -1452,9 +1461,19 @@ js::ToNumberSlow(ExclusiveContext* cx, HandleValue v_, double* out) return false; } - MOZ_ASSERT(v.isUndefined()); - *out = GenericNaN(); - return true; + if (v.isUndefined()) { + *out = GenericNaN(); + return true; + } + + MOZ_ASSERT(v.isSymbol() || v.isBigInt()); + if (cx->isJSContext()) { + unsigned errnum = JSMSG_SYMBOL_TO_NUMBER; + if (v.isBigInt()) + errnum = JSMSG_BIGINT_TO_NUMBER; + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, errnum); + } + return false; } JS_PUBLIC_API(bool) @@ -1482,6 +1501,29 @@ js::ToInt8Slow(JSContext *cx, const HandleValue v, int8_t *out) return true; } +// BigInt proposal section 3.1.6 +bool +js::ToNumericSlow(ExclusiveContext* cx, MutableHandleValue vp) +{ + MOZ_ASSERT(!vp.isNumeric()); + + // Step 1. + if (!vp.isPrimitive()) { + if (!cx->isJSContext()) + return false; + if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, vp)) + return false; + } + + // Step 2. + if (vp.isBigInt()) { + return true; + } + + // Step 3. + return ToNumber(cx->asJSContext(), vp); +} + /* * Convert a value to an uint8_t, according to the ToUInt8() function in ES6 * ECMA-262, 7.1.10. Return converted value in *out on success, false on failure. @@ -1573,6 +1615,27 @@ js::ToInt32Slow(JSContext* cx, const HandleValue v, int32_t* out) return true; } +bool +js::ToInt32OrBigIntSlow(JSContext* cx, MutableHandleValue vp) +{ + MOZ_ASSERT(!vp.isInt32()); + if (vp.isDouble()) { + vp.setInt32(ToInt32(vp.toDouble())); + return true; + } + + if (!ToNumeric(cx, vp)) { + return false; + } + + if (vp.isBigInt()) { + return true; + } + + vp.setInt32(ToInt32(vp.toNumber())); + return true; +} + JS_PUBLIC_API(bool) js::ToUint32Slow(JSContext* cx, const HandleValue v, uint32_t* out) { @@ -1706,6 +1769,35 @@ js::ToIntegerIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) return true; } +// ES2017 draft 7.1.17 ToIndex +bool +js::ToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index) +{ + // Step 1. + if (v.isUndefined()) { + *index = 0; + return true; + } + + // Step 2.a. + double integerIndex; + if (!ToInteger(cx, v, &integerIndex)) + return false; + + // Inlined version of ToLength. + // 1. Already an integer. + // 2. Step eliminates < 0, +0 == -0 with SameValueZero. + // 3/4. Limit to <= 2^53-1, so everything above should fail. + if (integerIndex < 0 || integerIndex >= DOUBLE_INTEGRAL_PRECISION_LIMIT) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); + return false; + } + + // Step 3. + *index = uint64_t(integerIndex); + return true; +} + template <typename CharT> bool js_strtod(ExclusiveContext* cx, const CharT* begin, const CharT* end, const CharT** dEnd, diff --git a/js/src/jsnum.h b/js/src/jsnum.h index f169a9c38d..ee07d0a35d 100644 --- a/js/src/jsnum.h +++ b/js/src/jsnum.h @@ -308,6 +308,15 @@ MOZ_MUST_USE bool ToLengthClamped(T* cx, HandleValue v, uint32_t* out, bool* ove */ MOZ_MUST_USE bool ToIntegerIndex(JSContext* cx, JS::HandleValue v, uint64_t* index); +/* ES2017 draft 7.1.17 ToIndex + * + * Return true and set |*index| to the integer value if |v| is a valid + * integer index value. Otherwise report a RangeError and return false. + * + * The returned index will always be in the range 0 <= *index <= 2^53-1. + */ +MOZ_MUST_USE bool ToIndex(JSContext* cx, JS::HandleValue v, uint64_t* index); + MOZ_MUST_USE inline bool SafeAdd(int32_t one, int32_t two, int32_t* res) { @@ -362,6 +371,31 @@ ToNumber(ExclusiveContext* cx, HandleValue v, double* out) return ToNumberSlow(cx, v, out); } +bool +ToNumericSlow(ExclusiveContext* cx, JS::MutableHandleValue vp); + +// BigInt proposal section 3.1.6 +MOZ_ALWAYS_INLINE MOZ_MUST_USE bool +ToNumeric(ExclusiveContext* cx, JS::MutableHandleValue vp) +{ + if (vp.isNumeric()) { + return true; + } + return ToNumericSlow(cx, vp); +} + +bool +ToInt32OrBigIntSlow(JSContext* cx, JS::MutableHandleValue vp); + +MOZ_ALWAYS_INLINE MOZ_MUST_USE bool +ToInt32OrBigInt(JSContext* cx, JS::MutableHandleValue vp) +{ + if (vp.isInt32()) { + return true; + } + return ToInt32OrBigIntSlow(cx, vp); +} + void FIX_FPU(); } /* namespace js */ diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index b0cf6bc69b..730805e038 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -35,6 +35,7 @@ #include "jswin.h" #include "jswrapper.h" +#include "builtin/BigInt.h" #include "builtin/Eval.h" #include "builtin/Object.h" #include "builtin/SymbolObject.h" @@ -2355,15 +2356,15 @@ js::LookupOwnPropertyPure(ExclusiveContext* cx, JSObject* obj, jsid id, Property } static inline bool -NativeGetPureInline(NativeObject* pobj, jsid id, PropertyResult prop, Value* vp) +NativeGetPureInline(NativeObject* pobj, jsid id, PropertyResult prop, Value* vp, + ExclusiveContext* cx) { if (prop.isDenseOrTypedArrayElement()) { // For simplicity we ignore the TypedArray with string index case. if (!JSID_IS_INT(id)) return false; - *vp = pobj->getDenseOrTypedArrayElement(JSID_TO_INT(id)); - return true; + return pobj->getDenseOrTypedArrayElement<NoGC>(cx, JSID_TO_INT(id), vp); } // Fail if we have a custom getter. @@ -2394,7 +2395,7 @@ js::GetPropertyPure(ExclusiveContext* cx, JSObject* obj, jsid id, Value* vp) return true; } - return pobj->isNative() && NativeGetPureInline(&pobj->as<NativeObject>(), id, prop, vp); + return pobj->isNative() && NativeGetPureInline(&pobj->as<NativeObject>(), id, prop, vp, cx); } static inline bool @@ -3104,9 +3105,13 @@ js::PrimitiveToObject(JSContext* cx, const Value& v) return NumberObject::create(cx, v.toNumber()); if (v.isBoolean()) return BooleanObject::create(cx, v.toBoolean()); - MOZ_ASSERT(v.isSymbol()); - RootedSymbol symbol(cx, v.toSymbol()); - return SymbolObject::create(cx, symbol); + if (v.isSymbol()) { + RootedSymbol symbol(cx, v.toSymbol()); + return SymbolObject::create(cx, symbol); + } + MOZ_ASSERT(v.isBigInt()); + RootedBigInt bigInt(cx, v.toBigInt()); + return BigIntObject::create(cx, bigInt); } /* diff --git a/js/src/json.cpp b/js/src/json.cpp index f3cf22dac9..d426fc721a 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -9,6 +9,8 @@ #include "mozilla/Range.h" #include "mozilla/ScopeExit.h" +#include "builtin/BigInt.h" + #include "jsarray.h" #include "jsatom.h" #include "jscntxt.h" @@ -328,6 +330,8 @@ PreprocessValue(JSContext* cx, HandleObject holder, KeyType key, MutableHandleVa } else if (cls == ESClass::Boolean) { if (!Unbox(cx, obj, vp)) return false; + } else if (cls == ESClass::BigInt) { + vp.setBigInt(obj->as<BigIntObject>().unbox()); } } @@ -626,6 +630,12 @@ Str(JSContext* cx, const Value& v, StringifyContext* scx) return NumberValueToStringBuffer(cx, v, scx->sb); } + /* Step 10 in the BigInt proposal. */ + if (v.isBigInt()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BIGINT_NOT_SERIALIZABLE); + return false; + } + /* Step 10. */ MOZ_ASSERT(v.isObject()); RootedObject obj(cx, &v.toObject()); diff --git a/js/src/jsopcode.cpp b/js/src/jsopcode.cpp index 5573d3d48b..c9ec240e7f 100644 --- a/js/src/jsopcode.cpp +++ b/js/src/jsopcode.cpp @@ -1039,6 +1039,9 @@ js::Disassemble1(JSContext* cx, HandleScript script, jsbytecode* pc, break; } + case JOF_BIGINT: + // Fallthrough. + case JOF_DOUBLE: { RootedValue v(cx, script->getConst(GET_UINT32_INDEX(pc))); JSAutoByteString bytes; diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 11d0429298..70ae964b23 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -56,6 +56,7 @@ enum { JOF_ATOMOBJECT = 19, /* uint16_t constant index + object index */ JOF_SCOPE = 20, /* unsigned 32-bit scope index */ JOF_ENVCOORD = 21, /* embedded ScopeCoordinate immediate */ + JOF_BIGINT = 22, /* uint32_t index for BigInt value */ JOF_TYPEMASK = 0x001f, /* mask for above immediate types */ JOF_NAME = 1 << 5, /* name operation */ diff --git a/js/src/jsprototypes.h b/js/src/jsprototypes.h index 7de6b0245a..34f835bc1d 100644 --- a/js/src/jsprototypes.h +++ b/js/src/jsprototypes.h @@ -91,6 +91,9 @@ real(Float32Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Float32)) \ real(Float64Array, InitViaClassSpec, TYPED_ARRAY_CLASP(Float64)) \ real(Uint8ClampedArray, InitViaClassSpec, TYPED_ARRAY_CLASP(Uint8Clamped)) \ + real(BigInt64Array, InitViaClassSpec, TYPED_ARRAY_CLASP(BigInt64)) \ + real(BigUint64Array, InitViaClassSpec, TYPED_ARRAY_CLASP(BigUint64)) \ + real(BigInt, InitViaClassSpec, OCLASP(BigInt)) \ real(Proxy, InitProxyClass, js::ProxyClassPtr) \ real(WeakMap, InitWeakMapClass, OCLASP(WeakMap)) \ real(Map, InitMapClass, OCLASP(Map)) \ diff --git a/js/src/jspubtd.h b/js/src/jspubtd.h index 01d3143023..4bc8b0649a 100644 --- a/js/src/jspubtd.h +++ b/js/src/jspubtd.h @@ -80,6 +80,7 @@ enum JSType { JSTYPE_BOOLEAN, /* boolean */ JSTYPE_NULL, /* null */ JSTYPE_SYMBOL, /* symbol */ + JSTYPE_BIGINT, /* BigInt */ JSTYPE_LIMIT }; diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 49a8a3ad04..5cf0d19195 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -83,7 +83,8 @@ js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp) SCRIPT_NULL, SCRIPT_OBJECT, SCRIPT_VOID, - SCRIPT_HOLE + SCRIPT_HOLE, + SCRIPT_BIGINT }; ConstTag tag; @@ -104,6 +105,8 @@ js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp) tag = SCRIPT_OBJECT; } else if (vp.isMagic(JS_ELEMENTS_HOLE)) { tag = SCRIPT_HOLE; + } else if (vp.isBigInt()) { + tag = SCRIPT_BIGINT; } else { MOZ_ASSERT(vp.isUndefined()); tag = SCRIPT_VOID; @@ -176,6 +179,20 @@ js::XDRScriptConst(XDRState<mode>* xdr, MutableHandleValue vp) if (mode == XDR_DECODE) vp.setMagic(JS_ELEMENTS_HOLE); break; + case SCRIPT_BIGINT: { + RootedBigInt bi(cx); + if (mode == XDR_ENCODE) { + bi = vp.toBigInt(); + } + + if(!XDRBigInt(xdr, &bi)) + return false; + + if (mode == XDR_DECODE) { + vp.setBigInt(bi); + } + break; + } default: // Fail in debug, but only soft-fail in release MOZ_ASSERT(false, "Bad XDR value kind"); @@ -3418,6 +3435,38 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, } } + /* Constants */ + + AutoValueVector consts(cx); + if (nconsts != 0) { + GCPtrValue* vector = src->consts()->vector; + RootedValue val(cx); + RootedValue clone(cx); + for (unsigned i = 0; i < nconsts; i++) { + val = vector[i]; + if (val.isDouble()) { + clone = val; + } else if (val.isBigInt()) { + if (cx->zone() == val.toBigInt()->zone()) { + clone.setBigInt(val.toBigInt()); + } else { + RootedBigInt b(cx, val.toBigInt()); + BigInt* copy = BigInt::copy(cx, b); + if (!copy) { + return false; + } + clone.setBigInt(copy); + } + } else { + MOZ_ASSERT_UNREACHABLE("bad script consts() element"); + } + + if (!consts.append(clone)) { + return false; + } + } + } + /* Objects */ AutoObjectVector objects(cx); @@ -3508,7 +3557,7 @@ js::detail::CopyScript(JSContext* cx, HandleScript src, HandleScript dst, GCPtrValue* vector = Rebase<GCPtrValue>(dst, src, src->consts()->vector); dst->consts()->vector = vector; for (unsigned i = 0; i < nconsts; ++i) - MOZ_ASSERT_IF(vector[i].isGCThing(), vector[i].toString()->isAtom()); + vector[i].init(consts[i]); } if (nobjects != 0) { GCPtrObject* vector = Rebase<GCPtrObject>(dst, src, src->objects()->vector); diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index fdee274c32..593cf4d708 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -3734,6 +3734,11 @@ js::ToStringSlow(ExclusiveContext* cx, typename MaybeRooted<Value, allowGC>::Han JSMSG_SYMBOL_TO_STRING); } return nullptr; + } else if (v.isBigInt()) { + if (!allowGC) + return nullptr; + RootedBigInt i(cx, v.toBigInt()); + str = BigInt::toString(cx, i, 10); } else { MOZ_ASSERT(v.isUndefined()); str = cx->names().undefined; diff --git a/js/src/moz.build b/js/src/moz.build index cecb7ae32d..8e14de6e85 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -384,6 +384,7 @@ main_deunified_sources = [ # instantiations may or may not be needed depending on what it gets bundled # with. SOURCES += [ + 'builtin/BigInt.cpp', 'builtin/RegExp.cpp', 'frontend/Parser.cpp', 'gc/StoreBuffer.cpp', @@ -392,6 +393,7 @@ SOURCES += [ 'jsdtoa.cpp', 'jsmath.cpp', 'jsutil.cpp', + 'vm/BigIntType.cpp', 'vm/Initialization.cpp', ] @@ -703,6 +705,7 @@ selfhosted.inputs = [ 'builtin/Utilities.js', 'builtin/Array.js', 'builtin/AsyncIteration.js', + 'builtin/BigInt.js', 'builtin/Classes.js', 'builtin/Date.js', 'builtin/Error.js', diff --git a/js/src/vm/ArrayBufferObject.cpp b/js/src/vm/ArrayBufferObject.cpp index 5d355ada9d..3bed40af47 100644 --- a/js/src/vm/ArrayBufferObject.cpp +++ b/js/src/vm/ArrayBufferObject.cpp @@ -264,27 +264,24 @@ ArrayBufferObject::fun_isView(JSContext* cx, unsigned argc, Value* vp) return true; } -/* - * new ArrayBuffer(byteLength) - */ + +// ES2017 draft 24.1.2.1 bool ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); + // Step 1. if (!ThrowIfNotConstructing(cx, args, "ArrayBuffer")) return false; - int32_t nbytes = 0; - if (argc > 0 && !ToInt32(cx, args[0], &nbytes)) + // Step 2. + uint64_t byteLength; + if (!ToIndex(cx, args.get(0), &byteLength)) return false; - if (nbytes < 0) { - /* - * We're just not going to support arrays that are bigger than what will fit - * as an integer value; if someone actually ever complains (validly), then we - * can fix. - */ + // Non-standard: Refuse to allocate buffers larger than ~2 GiB. + if (byteLength > INT32_MAX) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_ARRAY_LENGTH); return false; } @@ -294,7 +291,7 @@ ArrayBufferObject::class_constructor(JSContext* cx, unsigned argc, Value* vp) if (!GetPrototypeFromConstructor(cx, newTarget, &proto)) return false; - JSObject* bufobj = create(cx, uint32_t(nbytes), proto); + JSObject* bufobj = create(cx, uint32_t(byteLength), proto); if (!bufobj) return false; args.rval().setObject(*bufobj); diff --git a/js/src/vm/ArrayBufferObject.h b/js/src/vm/ArrayBufferObject.h index 87dce34ba1..4ff7962cfb 100644 --- a/js/src/vm/ArrayBufferObject.h +++ b/js/src/vm/ArrayBufferObject.h @@ -463,9 +463,11 @@ struct uint8_clamped { explicit uint8_clamped(uint8_t x) { *this = x; } explicit uint8_clamped(uint16_t x) { *this = x; } explicit uint8_clamped(uint32_t x) { *this = x; } + explicit uint8_clamped(uint64_t x) { *this = x; } explicit uint8_clamped(int8_t x) { *this = x; } explicit uint8_clamped(int16_t x) { *this = x; } explicit uint8_clamped(int32_t x) { *this = x; } + explicit uint8_clamped(int64_t x) { *this = x; } explicit uint8_clamped(double x) { *this = x; } uint8_clamped& operator=(const uint8_clamped& x) = default; @@ -485,6 +487,11 @@ struct uint8_clamped { return *this; } + uint8_clamped& operator=(uint64_t x) { + val = (x > 255) ? 255 : uint8_t(x); + return *this; + } + uint8_clamped& operator=(int8_t x) { val = (x >= 0) ? uint8_t(x) : 0; return *this; @@ -508,6 +515,15 @@ struct uint8_clamped { return *this; } + uint8_clamped& operator=(int64_t x) { + val = (x >= 0) + ? ((x < 255) + ? uint8_t(x) + : 255) + : 0; + return *this; + } + uint8_clamped& operator=(const double x) { val = uint8_t(ClampDoubleToUint8(x)); return *this; diff --git a/js/src/vm/BigIntType.cpp b/js/src/vm/BigIntType.cpp new file mode 100644 index 0000000000..8382c641fa --- /dev/null +++ b/js/src/vm/BigIntType.cpp @@ -0,0 +1,3260 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +/* + * Portions of this code taken from WebKit, whose copyright is as follows: + * + * Copyright (C) 2017 Caio Lima <ticaiolima@gmail.com> + * Copyright (C) 2017-2018 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Portions of this code taken from V8, whose copyright notice is as follows: + * + * Copyright 2017 the V8 project authors. All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Portions of this code taken from Dart, whose copyright notice is as follows: + * + * Copyright (c) 2014 the Dart project authors. Please see the AUTHORS file + * [1] for details. All rights reserved. Use of this source code is governed by + * a BSD-style license that can be found in the LICENSE file [2]. + * + * [1] https://github.com/dart-lang/sdk/blob/master/AUTHORS + * [2] https://github.com/dart-lang/sdk/blob/master/LICENSE + * + * Portions of this code taken from Go, whose copyright notice is as follows: + * + * Copyright 2009 The Go Authors. All rights reserved. + * Use of this source code is governed by a BSD-style + * license that can be found in the LICENSE file [3]. + * + * [3] https://golang.org/LICENSE + */ + +#include "vm/BigIntType.h" + +#include "mozilla/Casting.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Maybe.h" +#include "mozilla/Range.h" +#include "mozilla/RangedPtr.h" +#include "mozilla/WrappingOperations.h" + +#include <functional> +#include <limits> +#include <math.h> +#include <memory> + +#include "jsapi.h" +#include "jsnum.h" +#include "jscntxt.h" + +#include "builtin/BigInt.h" +#include "gc/Allocator.h" +#include "js/Initialization.h" +#include "js/Utility.h" +#include "vm/SelfHosting.h" + +#include "vm/String.h" + +using namespace js; + +using mozilla::Abs; +using mozilla::AssertedCast; +using mozilla::BitwiseCast; +using mozilla::IsFinite; +using mozilla::Maybe; +using mozilla::NegativeInfinity; +using mozilla::Nothing; +using mozilla::PositiveInfinity; +using mozilla::Range; +using mozilla::RangedPtr; +using mozilla::Some; +using mozilla::WrapToSigned; + +static inline unsigned DigitLeadingZeroes(BigInt::Digit x) { + return sizeof(x) == 4 ? mozilla::CountLeadingZeroes32(x) + : mozilla::CountLeadingZeroes64(x); +} + +BigInt* BigInt::createUninitialized(ExclusiveContext* cx, size_t length, + bool isNegative) { + if (length > MaxDigitLength) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TOO_LARGE); + } + return nullptr; + } + + UniquePtr<Digit[], JS::FreePolicy> heapDigits; + if (length > InlineDigitsLength) { + heapDigits = cx->make_pod_array<Digit>(length); + if (!heapDigits) { + return nullptr; + } + } else { + heapDigits = nullptr; + } + + BigInt* x = Allocate<BigInt>(cx); + if (!x) { + return nullptr; + } + + x->lengthSignAndReservedBits_ = + (length << LengthShift) | (isNegative ? SignBit : 0); + MOZ_ASSERT(x->digitLength() == length); + MOZ_ASSERT(x->isNegative() == isNegative); + + if (heapDigits) { + x->heapDigits_ = heapDigits.release(); + } + + return x; +} + +void BigInt::initializeDigitsToZero() { + auto digs = digits(); + std::uninitialized_fill_n(digs.begin(), digs.Length(), 0); +} + +void BigInt::finalize(js::FreeOp* fop) { + if (hasHeapDigits()) { + fop->free_(heapDigits_); + } +} + +js::HashNumber BigInt::hash() { + js::HashNumber h = + mozilla::HashBytes(digits().data(), digitLength() * sizeof(Digit)); + return mozilla::AddToHash(h, isNegative()); +} + +size_t BigInt::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + return hasInlineDigits() ? 0 : mallocSizeOf(heapDigits_); +} + +BigInt* BigInt::zero(ExclusiveContext* cx) { + return createUninitialized(cx, 0, false); +} + +BigInt* BigInt::one(ExclusiveContext* cx) { + BigInt* ret = createUninitialized(cx, 1, false); + + if (!ret) { + return nullptr; + } + + ret->setDigit(0, 1); + + return ret; +} + +BigInt* BigInt::neg(ExclusiveContext* cx, HandleBigInt x) { + if (x->isZero()) { + return x; + } + + BigInt* result = copy(cx, x); + if (!result) { + return nullptr; + } + result->lengthSignAndReservedBits_ ^= SignBit; + return result; +} + +#if !defined(JS_64BIT) +#define HAVE_TWO_DIGIT 1 +using TwoDigit = uint64_t; +#elif defined(HAVE_INT128_SUPPORT) +#define HAVE_TWO_DIGIT 1 +using TwoDigit = __uint128_t; +#endif + +inline BigInt::Digit BigInt::digitMul(Digit a, Digit b, Digit* high) { +#if defined(HAVE_TWO_DIGIT) + TwoDigit result = static_cast<TwoDigit>(a) * static_cast<TwoDigit>(b); + *high = result >> DigitBits; + + return static_cast<Digit>(result); +#else + // Multiply in half-pointer-sized chunks. + // For inputs [AH AL]*[BH BL], the result is: + // + // [AL*BL] // rLow + // + [AL*BH] // rMid1 + // + [AH*BL] // rMid2 + // + [AH*BH] // rHigh + // = [R4 R3 R2 R1] // high = [R4 R3], low = [R2 R1] + // + // Where of course we must be careful with carries between the columns. + Digit aLow = a & HalfDigitMask; + Digit aHigh = a >> HalfDigitBits; + Digit bLow = b & HalfDigitMask; + Digit bHigh = b >> HalfDigitBits; + + Digit rLow = aLow * bLow; + Digit rMid1 = aLow * bHigh; + Digit rMid2 = aHigh * bLow; + Digit rHigh = aHigh * bHigh; + + Digit carry = 0; + Digit low = digitAdd(rLow, rMid1 << HalfDigitBits, &carry); + low = digitAdd(low, rMid2 << HalfDigitBits, &carry); + + *high = (rMid1 >> HalfDigitBits) + (rMid2 >> HalfDigitBits) + rHigh + carry; + + return low; +#endif +} + +BigInt::Digit BigInt::digitDiv(Digit high, Digit low, Digit divisor, + Digit* remainder) { + MOZ_ASSERT(high < divisor, "division must not overflow"); +#if defined(__x86_64__) + Digit quotient; + Digit rem; + __asm__("divq %[divisor]" + // Outputs: `quotient` will be in rax, `rem` in rdx. + : "=a"(quotient), "=d"(rem) + // Inputs: put `high` into rdx, `low` into rax, and `divisor` into + // any register or stack slot. + : "d"(high), "a"(low), [divisor] "rm"(divisor)); + *remainder = rem; + return quotient; +#elif defined(__i386__) + Digit quotient; + Digit rem; + __asm__("divl %[divisor]" + // Outputs: `quotient` will be in eax, `rem` in edx. + : "=a"(quotient), "=d"(rem) + // Inputs: put `high` into edx, `low` into eax, and `divisor` into + // any register or stack slot. + : "d"(high), "a"(low), [divisor] "rm"(divisor)); + *remainder = rem; + return quotient; +#else + static constexpr Digit HalfDigitBase = 1ull << HalfDigitBits; + // Adapted from Warren, Hacker's Delight, p. 152. + unsigned s = DigitLeadingZeroes(divisor); + // If `s` is DigitBits here, it causes an undefined behavior. + // But `s` is never DigitBits since `divisor` is never zero here. + MOZ_ASSERT(s != DigitBits); + divisor <<= s; + + Digit vn1 = divisor >> HalfDigitBits; + Digit vn0 = divisor & HalfDigitMask; + + // `sZeroMask` which is 0 if s == 0 and all 1-bits otherwise. + // + // `s` can be 0. If `s` is 0, performing "low >> (DigitBits - s)" must not + // be done since it causes an undefined behavior since `>> DigitBits` is + // undefined in C++. Quoted from C++ spec, "The type of the result is that of + // the promoted left operand. + // + // The behavior is undefined if the right operand is negative, or greater + // than or equal to the length in bits of the promoted left operand". We + // mask the right operand of the shift by `shiftMask` (`DigitBits - 1`), + // which makes `DigitBits - 0` zero. + // + // This shifting produces a value which covers 0 < `s` <= (DigitBits - 1) + // cases. `s` == DigitBits never happen as we asserted. Since `sZeroMask` + // clears the value in the case of `s` == 0, `s` == 0 case is also covered. + static_assert(sizeof(intptr_t) == sizeof(Digit), + "unexpected size of BigInt::Digit"); + Digit sZeroMask = + static_cast<Digit>((-static_cast<intptr_t>(s)) >> (DigitBits - 1)); + static constexpr unsigned shiftMask = DigitBits - 1; + Digit un32 = + (high << s) | ((low >> ((DigitBits - s) & shiftMask)) & sZeroMask); + + Digit un10 = low << s; + Digit un1 = un10 >> HalfDigitBits; + Digit un0 = un10 & HalfDigitMask; + Digit q1 = un32 / vn1; + Digit rhat = un32 - q1 * vn1; + + while (q1 >= HalfDigitBase || q1 * vn0 > rhat * HalfDigitBase + un1) { + q1--; + rhat += vn1; + if (rhat >= HalfDigitBase) { + break; + } + } + + Digit un21 = un32 * HalfDigitBase + un1 - q1 * divisor; + Digit q0 = un21 / vn1; + rhat = un21 - q0 * vn1; + + while (q0 >= HalfDigitBase || q0 * vn0 > rhat * HalfDigitBase + un0) { + q0--; + rhat += vn1; + if (rhat >= HalfDigitBase) { + break; + } + } + + *remainder = (un21 * HalfDigitBase + un0 - q0 * divisor) >> s; + return q1 * HalfDigitBase + q0; +#endif +} + +// Multiplies `source` with `factor` and adds `summand` to the result. +// `result` and `source` may be the same BigInt for inplace modification. +void BigInt::internalMultiplyAdd(BigInt* source, Digit factor, Digit summand, + unsigned n, BigInt* result) { + MOZ_ASSERT(source->digitLength() >= n); + MOZ_ASSERT(result->digitLength() >= n); + + Digit carry = summand; + Digit high = 0; + for (unsigned i = 0; i < n; i++) { + Digit current = source->digit(i); + Digit newCarry = 0; + + // Compute this round's multiplication. + Digit newHigh = 0; + current = digitMul(current, factor, &newHigh); + + // Add last round's carryovers. + current = digitAdd(current, high, &newCarry); + current = digitAdd(current, carry, &newCarry); + + // Store result and prepare for next round. + result->setDigit(i, current); + carry = newCarry; + high = newHigh; + } + + if (result->digitLength() > n) { + result->setDigit(n++, carry + high); + + // Current callers don't pass in such large results, but let's be robust. + while (n < result->digitLength()) { + result->setDigit(n++, 0); + } + } else { + MOZ_ASSERT(!(carry + high)); + } +} + +// Multiplies `this` with `factor` and adds `summand` to the result. +void BigInt::inplaceMultiplyAdd(Digit factor, Digit summand) { + internalMultiplyAdd(this, factor, summand, digitLength(), this); +} + +// Multiplies `multiplicand` with `multiplier` and adds the result to +// `accumulator`, starting at `accumulatorIndex` for the least-significant +// digit. Callers must ensure that `accumulator`'s digitLength and +// corresponding digit storage is long enough to hold the result. +void BigInt::multiplyAccumulate(BigInt* multiplicand, Digit multiplier, + BigInt* accumulator, + unsigned accumulatorIndex) { + MOZ_ASSERT(accumulator->digitLength() > + multiplicand->digitLength() + accumulatorIndex); + if (!multiplier) { + return; + } + + Digit carry = 0; + Digit high = 0; + for (unsigned i = 0; i < multiplicand->digitLength(); + i++, accumulatorIndex++) { + Digit acc = accumulator->digit(accumulatorIndex); + Digit newCarry = 0; + + // Add last round's carryovers. + acc = digitAdd(acc, high, &newCarry); + acc = digitAdd(acc, carry, &newCarry); + + // Compute this round's multiplication. + Digit multiplicandDigit = multiplicand->digit(i); + Digit low = digitMul(multiplier, multiplicandDigit, &high); + acc = digitAdd(acc, low, &newCarry); + + // Store result and prepare for next round. + accumulator->setDigit(accumulatorIndex, acc); + carry = newCarry; + } + + while (carry || high) { + MOZ_ASSERT(accumulatorIndex < accumulator->digitLength()); + Digit acc = accumulator->digit(accumulatorIndex); + Digit newCarry = 0; + acc = digitAdd(acc, high, &newCarry); + high = 0; + acc = digitAdd(acc, carry, &newCarry); + accumulator->setDigit(accumulatorIndex, acc); + carry = newCarry; + accumulatorIndex++; + } +} + +inline int8_t BigInt::absoluteCompare(BigInt* x, BigInt* y) { + MOZ_ASSERT(!x->digitLength() || x->digit(x->digitLength() - 1)); + MOZ_ASSERT(!y->digitLength() || y->digit(y->digitLength() - 1)); + + // Sanity checks to catch negative zeroes escaping to the wild. + MOZ_ASSERT(!x->isNegative() || !x->isZero()); + MOZ_ASSERT(!y->isNegative() || !y->isZero()); + + int diff = x->digitLength() - y->digitLength(); + if (diff) { + return diff < 0 ? -1 : 1; + } + + int i = x->digitLength() - 1; + while (i >= 0 && x->digit(i) == y->digit(i)) { + i--; + } + + if (i < 0) { + return 0; + } + + return x->digit(i) > y->digit(i) ? 1 : -1; +} + +BigInt* BigInt::absoluteAdd(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y, + bool resultNegative) { + bool swap = x->digitLength() < y->digitLength(); + // Ensure `left` has at least as many digits as `right`. + HandleBigInt& left = swap ? y : x; + HandleBigInt& right = swap ? x : y; + + if (left->isZero()) { + MOZ_ASSERT(right->isZero()); + return left; + } + + if (right->isZero()) { + return resultNegative == left->isNegative() ? left : neg(cx, left); + } + + RootedBigInt result( + cx, createUninitialized(cx, left->digitLength() + 1, resultNegative)); + if (!result) { + return nullptr; + } + Digit carry = 0; + unsigned i = 0; + for (; i < right->digitLength(); i++) { + Digit newCarry = 0; + Digit sum = digitAdd(left->digit(i), right->digit(i), &newCarry); + sum = digitAdd(sum, carry, &newCarry); + result->setDigit(i, sum); + carry = newCarry; + } + + for (; i < left->digitLength(); i++) { + Digit newCarry = 0; + Digit sum = digitAdd(left->digit(i), carry, &newCarry); + result->setDigit(i, sum); + carry = newCarry; + } + + result->setDigit(i, carry); + + return destructivelyTrimHighZeroDigits(cx, result); +} + +BigInt* BigInt::absoluteSub(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y, + bool resultNegative) { + MOZ_ASSERT(x->digitLength() >= y->digitLength()); + + if (x->isZero()) { + MOZ_ASSERT(y->isZero()); + return x; + } + + if (y->isZero()) { + return resultNegative == x->isNegative() ? x : neg(cx, x); + } + + int8_t comparisonResult = absoluteCompare(x, y); + MOZ_ASSERT(comparisonResult >= 0); + if (comparisonResult == 0) { + return zero(cx); + } + + RootedBigInt result( + cx, createUninitialized(cx, x->digitLength(), resultNegative)); + if (!result) { + return nullptr; + } + Digit borrow = 0; + unsigned i = 0; + for (; i < y->digitLength(); i++) { + Digit newBorrow = 0; + Digit difference = digitSub(x->digit(i), y->digit(i), &newBorrow); + difference = digitSub(difference, borrow, &newBorrow); + result->setDigit(i, difference); + borrow = newBorrow; + } + + for (; i < x->digitLength(); i++) { + Digit newBorrow = 0; + Digit difference = digitSub(x->digit(i), borrow, &newBorrow); + result->setDigit(i, difference); + borrow = newBorrow; + } + + MOZ_ASSERT(!borrow); + return destructivelyTrimHighZeroDigits(cx, result); +} + +// Divides `x` by `divisor`, returning the result in `quotient` and `remainder`. +// Mathematically, the contract is: +// +// quotient = (x - remainder) / divisor, with 0 <= remainder < divisor. +// +// If `quotient` is an empty handle, an appropriately sized BigInt will be +// allocated for it; otherwise the caller must ensure that it is big enough. +// `quotient` can be the same as `x` for an in-place division. `quotient` can +// also be `Nothing()` if the caller is only interested in the remainder. +// +// This function returns false if `quotient` is an empty handle, but allocating +// the quotient failed. Otherwise it returns true, indicating success. +bool BigInt::absoluteDivWithDigitDivisor(ExclusiveContext* cx, HandleBigInt x, + Digit divisor, + const Maybe<MutableHandleBigInt>& quotient, + Digit* remainder, + bool quotientNegative) { + MOZ_ASSERT(divisor); + + MOZ_ASSERT(!x->isZero()); + *remainder = 0; + if (divisor == 1) { + if (quotient) { + BigInt* q; + if (x->isNegative() == quotientNegative) { + q = x; + } else { + q = neg(cx, x); + if (!q) { + return false; + } + } + quotient.value().set(q); + } + return true; + } + + unsigned length = x->digitLength(); + if (quotient) { + if (!quotient.value()) { + BigInt* q = createUninitialized(cx, length, quotientNegative); + if (!q) { + return false; + } + quotient.value().set(q); + } + + for (int i = length - 1; i >= 0; i--) { + Digit q = digitDiv(*remainder, x->digit(i), divisor, remainder); + quotient.value()->setDigit(i, q); + } + } else { + for (int i = length - 1; i >= 0; i--) { + digitDiv(*remainder, x->digit(i), divisor, remainder); + } + } + + return true; +} + +// Adds `summand` onto `this`, starting with `summand`'s 0th digit +// at `this`'s `startIndex`'th digit. Returns the "carry" (0 or 1). +BigInt::Digit BigInt::absoluteInplaceAdd(BigInt* summand, unsigned startIndex) { + Digit carry = 0; + unsigned n = summand->digitLength(); + MOZ_ASSERT(digitLength() > startIndex, + "must start adding at an in-range digit"); + MOZ_ASSERT(digitLength() - startIndex >= n, + "digits being added to must not extend above the digits in " + "this (except for the returned carry digit)"); + for (unsigned i = 0; i < n; i++) { + Digit newCarry = 0; + Digit sum = digitAdd(digit(startIndex + i), summand->digit(i), &newCarry); + sum = digitAdd(sum, carry, &newCarry); + setDigit(startIndex + i, sum); + carry = newCarry; + } + + return carry; +} + +// Subtracts `subtrahend` from this, starting with `subtrahend`'s 0th digit +// at `this`'s `startIndex`-th digit. Returns the "borrow" (0 or 1). +BigInt::Digit BigInt::absoluteInplaceSub(BigInt* subtrahend, + unsigned startIndex) { + Digit borrow = 0; + unsigned n = subtrahend->digitLength(); + MOZ_ASSERT(digitLength() > startIndex, + "must start subtracting from an in-range digit"); + MOZ_ASSERT(digitLength() - startIndex >= n, + "digits being subtracted from must not extend above the " + "digits in this (except for the returned borrow digit)"); + for (unsigned i = 0; i < n; i++) { + Digit newBorrow = 0; + Digit difference = + digitSub(digit(startIndex + i), subtrahend->digit(i), &newBorrow); + difference = digitSub(difference, borrow, &newBorrow); + setDigit(startIndex + i, difference); + borrow = newBorrow; + } + + return borrow; +} + +// Returns whether (factor1 * factor2) > (high << kDigitBits) + low. +inline bool BigInt::productGreaterThan(Digit factor1, Digit factor2, Digit high, + Digit low) { + Digit resultHigh; + Digit resultLow = digitMul(factor1, factor2, &resultHigh); + return resultHigh > high || (resultHigh == high && resultLow > low); +} + +void BigInt::inplaceRightShiftLowZeroBits(unsigned shift) { + MOZ_ASSERT(shift < DigitBits); + MOZ_ASSERT(!(digit(0) & ((static_cast<Digit>(1) << shift) - 1)), + "should only be shifting away zeroes"); + + if (!shift) { + return; + } + + Digit carry = digit(0) >> shift; + unsigned last = digitLength() - 1; + for (unsigned i = 0; i < last; i++) { + Digit d = digit(i + 1); + setDigit(i, (d << (DigitBits - shift)) | carry); + carry = d >> shift; + } + setDigit(last, carry); +} + +// Always copies the input, even when `shift` == 0. +BigInt* BigInt::absoluteLeftShiftAlwaysCopy(ExclusiveContext* cx, HandleBigInt x, + unsigned shift, + LeftShiftMode mode) { + MOZ_ASSERT(shift < DigitBits); + MOZ_ASSERT(!x->isZero()); + + unsigned n = x->digitLength(); + unsigned resultLength = mode == LeftShiftMode::AlwaysAddOneDigit ? n + 1 : n; + RootedBigInt result(cx, + createUninitialized(cx, resultLength, x->isNegative())); + if (!result) { + return nullptr; + } + + if (!shift) { + for (unsigned i = 0; i < n; i++) { + result->setDigit(i, x->digit(i)); + } + if (mode == LeftShiftMode::AlwaysAddOneDigit) { + result->setDigit(n, 0); + } + + return result; + } + + Digit carry = 0; + for (unsigned i = 0; i < n; i++) { + Digit d = x->digit(i); + result->setDigit(i, (d << shift) | carry); + carry = d >> (DigitBits - shift); + } + + if (mode == LeftShiftMode::AlwaysAddOneDigit) { + result->setDigit(n, carry); + } else { + MOZ_ASSERT(mode == LeftShiftMode::SameSizeResult); + MOZ_ASSERT(!carry); + } + + return result; +} + +// Divides `dividend` by `divisor`, returning the result in `quotient` and +// `remainder`. Mathematically, the contract is: +// +// quotient = (dividend - remainder) / divisor, with 0 <= remainder < divisor. +// +// Both `quotient` and `remainder` are optional, for callers that are only +// interested in one of them. See Knuth, Volume 2, section 4.3.1, Algorithm D. +// Also see the overview of the algorithm by Jan Marthedal Rasmussen over at +// https://janmr.com/blog/2014/04/basic-multiple-precision-long-division/. +bool BigInt::absoluteDivWithBigIntDivisor(ExclusiveContext* cx, HandleBigInt dividend, + HandleBigInt divisor, + const Maybe<MutableHandleBigInt>& quotient, + const Maybe<MutableHandleBigInt>& remainder, + bool isNegative) { + MOZ_ASSERT(divisor->digitLength() >= 2); + MOZ_ASSERT(dividend->digitLength() >= divisor->digitLength()); + + // Any early error return is detectable by checking the quotient and/or + // remainder output values. + MOZ_ASSERT(!quotient || !quotient.value()); + MOZ_ASSERT(!remainder || !remainder.value()); + + // The unusual variable names inside this function are consistent with + // Knuth's book, as well as with Go's implementation of this algorithm. + // Maintaining this consistency is probably more useful than trying to + // come up with more descriptive names for them. + const unsigned n = divisor->digitLength(); + const unsigned m = dividend->digitLength() - n; + + // The quotient to be computed. + RootedBigInt q(cx); + if (quotient) { + q = createUninitialized(cx, m + 1, isNegative); + if (!q) { + return false; + } + } + + // In each iteration, `qhatv` holds `divisor` * `current quotient digit`. + // "v" is the book's name for `divisor`, `qhat` the current quotient digit. + RootedBigInt qhatv(cx, createUninitialized(cx, n + 1, isNegative)); + if (!qhatv) { + return false; + } + + // D1. + // Left-shift inputs so that the divisor's MSB is set. This is necessary to + // prevent the digit-wise divisions (see digitDiv call below) from + // overflowing (they take a two digits wide input, and return a one digit + // result). + Digit lastDigit = divisor->digit(n - 1); + unsigned shift = DigitLeadingZeroes(lastDigit); + + RootedBigInt shiftedDivisor(cx); + if (shift > 0) { + shiftedDivisor = absoluteLeftShiftAlwaysCopy(cx, divisor, shift, + LeftShiftMode::SameSizeResult); + if (!shiftedDivisor) { + return false; + } + } else { + shiftedDivisor = divisor; + } + + // Holds the (continuously updated) remaining part of the dividend, which + // eventually becomes the remainder. + RootedBigInt u(cx, + absoluteLeftShiftAlwaysCopy(cx, dividend, shift, + LeftShiftMode::AlwaysAddOneDigit)); + if (!u) { + return false; + } + + // D2. + // Iterate over the dividend's digit (like the "grade school" algorithm). + // `vn1` is the divisor's most significant digit. + Digit vn1 = shiftedDivisor->digit(n - 1); + for (int j = m; j >= 0; j--) { + // D3. + // Estimate the current iteration's quotient digit (see Knuth for details). + // `qhat` is the current quotient digit. + Digit qhat = std::numeric_limits<Digit>::max(); + + // `ujn` is the dividend's most significant remaining digit. + Digit ujn = u->digit(j + n); + if (ujn != vn1) { + // `rhat` is the current iteration's remainder. + Digit rhat = 0; + // Estimate the current quotient digit by dividing the most significant + // digits of dividend and divisor. The result will not be too small, + // but could be a bit too large. + qhat = digitDiv(ujn, u->digit(j + n - 1), vn1, &rhat); + + // Decrement the quotient estimate as needed by looking at the next + // digit, i.e. by testing whether + // qhat * v_{n-2} > (rhat << DigitBits) + u_{j+n-2}. + Digit vn2 = shiftedDivisor->digit(n - 2); + Digit ujn2 = u->digit(j + n - 2); + while (productGreaterThan(qhat, vn2, rhat, ujn2)) { + qhat--; + Digit prevRhat = rhat; + rhat += vn1; + // v[n-1] >= 0, so this tests for overflow. + if (rhat < prevRhat) { + break; + } + } + } + + // D4. + // Multiply the divisor with the current quotient digit, and subtract + // it from the dividend. If there was "borrow", then the quotient digit + // was one too high, so we must correct it and undo one subtraction of + // the (shifted) divisor. + internalMultiplyAdd(shiftedDivisor, qhat, 0, n, qhatv); + Digit c = u->absoluteInplaceSub(qhatv, j); + if (c) { + c = u->absoluteInplaceAdd(shiftedDivisor, j); + u->setDigit(j + n, u->digit(j + n) + c); + qhat--; + } + + if (quotient) { + q->setDigit(j, qhat); + } + } + + if (quotient) { + BigInt* bi = destructivelyTrimHighZeroDigits(cx, q); + if (!bi) { + return false; + } + quotient.value().set(q); + } + + if (remainder) { + u->inplaceRightShiftLowZeroBits(shift); + remainder.value().set(u); + } + + return true; +} + +// Helper for Absolute{And,AndNot,Or,Xor}. +// Performs the given binary `op` on digit pairs of `x` and `y`; when the +// end of the shorter of the two is reached, `kind` configures how +// remaining digits are handled. +// Example: +// y: [ y2 ][ y1 ][ y0 ] +// x: [ x3 ][ x2 ][ x1 ][ x0 ] +// | | | | +// (Fill) (op) (op) (op) +// | | | | +// v v v v +// result: [ 0 ][ x3 ][ r2 ][ r1 ][ r0 ] +template <BigInt::BitwiseOpKind kind, typename BitwiseOp> +inline BigInt* BigInt::absoluteBitwiseOp(ExclusiveContext* cx, HandleBigInt x, + HandleBigInt y, BitwiseOp&& op) { + unsigned xLength = x->digitLength(); + unsigned yLength = y->digitLength(); + unsigned numPairs = std::min(xLength, yLength); + unsigned resultLength; + if (kind == BitwiseOpKind::SymmetricTrim) { + resultLength = numPairs; + } else if (kind == BitwiseOpKind::SymmetricFill) { + resultLength = std::max(xLength, yLength); + } else { + MOZ_ASSERT(kind == BitwiseOpKind::AsymmetricFill); + resultLength = xLength; + } + bool resultNegative = false; + + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + + unsigned i = 0; + for (; i < numPairs; i++) { + result->setDigit(i, op(x->digit(i), y->digit(i))); + } + + if (kind != BitwiseOpKind::SymmetricTrim) { + HandleBigInt& source = + kind == BitwiseOpKind::AsymmetricFill ? x : xLength == i ? y : x; + for (; i < resultLength; i++) { + result->setDigit(i, source->digit(i)); + } + } + + MOZ_ASSERT(i == resultLength); + + return destructivelyTrimHighZeroDigits(cx, result); +} + +BigInt* BigInt::absoluteAnd(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + return absoluteBitwiseOp<BitwiseOpKind::SymmetricTrim>(cx, x, y, + std::bit_and<Digit>()); +} + +BigInt* BigInt::absoluteOr(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + return absoluteBitwiseOp<BitwiseOpKind::SymmetricFill>(cx, x, y, + std::bit_or<Digit>()); +} + +BigInt* BigInt::absoluteAndNot(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + auto digitOperation = [](Digit a, Digit b) { return a & ~b; }; + return absoluteBitwiseOp<BitwiseOpKind::AsymmetricFill>(cx, x, y, + digitOperation); +} + +BigInt* BigInt::absoluteXor(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + return absoluteBitwiseOp<BitwiseOpKind::SymmetricFill>(cx, x, y, + std::bit_xor<Digit>()); +} + +BigInt* BigInt::absoluteAddOne(ExclusiveContext* cx, HandleBigInt x, + bool resultNegative) { + unsigned inputLength = x->digitLength(); + // The addition will overflow into a new digit if all existing digits are + // at maximum. + bool willOverflow = true; + for (unsigned i = 0; i < inputLength; i++) { + if (std::numeric_limits<Digit>::max() != x->digit(i)) { + willOverflow = false; + break; + } + } + + unsigned resultLength = inputLength + willOverflow; + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + + Digit carry = 1; + for (unsigned i = 0; i < inputLength; i++) { + Digit newCarry = 0; + result->setDigit(i, digitAdd(x->digit(i), carry, &newCarry)); + carry = newCarry; + } + if (resultLength > inputLength) { + MOZ_ASSERT(carry == 1); + result->setDigit(inputLength, 1); + } else { + MOZ_ASSERT(!carry); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// Like the above, but you can specify that the allocated result should have +// length `resultLength`, which must be at least as large as `x->digitLength()`. +// The result will be unsigned. +BigInt* BigInt::absoluteSubOne(ExclusiveContext* cx, HandleBigInt x, + unsigned resultLength) { + MOZ_ASSERT(!x->isZero()); + MOZ_ASSERT(resultLength >= x->digitLength()); + bool resultNegative = false; + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + + unsigned length = x->digitLength(); + Digit borrow = 1; + for (unsigned i = 0; i < length; i++) { + Digit newBorrow = 0; + result->setDigit(i, digitSub(x->digit(i), borrow, &newBorrow)); + borrow = newBorrow; + } + MOZ_ASSERT(!borrow); + for (unsigned i = length; i < resultLength; i++) { + result->setDigit(i, 0); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// Lookup table for the maximum number of bits required per character of a +// base-N string representation of a number. To increase accuracy, the array +// value is the actual value multiplied by 32. To generate this table: +// for (var i = 0; i <= 36; i++) { print(Math.ceil(Math.log2(i) * 32) + ","); } +static constexpr uint8_t maxBitsPerCharTable[] = { + 0, 0, 32, 51, 64, 75, 83, 90, 96, // 0..8 + 102, 107, 111, 115, 119, 122, 126, 128, // 9..16 + 131, 134, 136, 139, 141, 143, 145, 147, // 17..24 + 149, 151, 153, 154, 156, 158, 159, 160, // 25..32 + 162, 163, 165, 166, // 33..36 +}; + +static constexpr unsigned bitsPerCharTableShift = 5; +static constexpr size_t bitsPerCharTableMultiplier = 1u + << bitsPerCharTableShift; +static constexpr char radixDigits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + +static inline uint64_t CeilDiv(uint64_t numerator, uint64_t denominator) { + MOZ_ASSERT(numerator != 0); + return 1 + (numerator - 1) / denominator; +}; + +// Compute (an overapproximation of) the length of the string representation of +// a BigInt. In base B an X-digit number has maximum value: +// +// B**X - 1 +// +// We're trying to find N for an N-digit number in base |radix| full +// representing a |bitLength|-digit number in base 2, so we have: +// +// radix**N - 1 ≥ 2**bitLength - 1 +// radix**N ≥ 2**bitLength +// N ≥ log2(2**bitLength) / log2(radix) +// N ≥ bitLength / log2(radix) +// +// so the smallest N is: +// +// N = ⌈bitLength / log2(radix)⌉ +// +// We want to avoid floating-point computations and precompute the logarithm, so +// we multiply both sides of the division by |bitsPerCharTableMultiplier|: +// +// N = ⌈(bPCTM * bitLength) / (bPCTM * log2(radix))⌉ +// +// and then because |maxBitsPerChar| representing the denominator may have been +// rounded *up* -- which could produce an overall under-computation -- we reduce +// by one to undo any rounding and conservatively compute: +// +// N ≥ ⌈(bPCTM * bitLength) / (maxBitsPerChar - 1)⌉ +// +size_t BigInt::calculateMaximumCharactersRequired(HandleBigInt x, + unsigned radix) { + MOZ_ASSERT(!x->isZero()); + MOZ_ASSERT(radix >= 2 && radix <= 36); + + size_t length = x->digitLength(); + Digit lastDigit = x->digit(length - 1); + size_t bitLength = length * DigitBits - DigitLeadingZeroes(lastDigit); + + uint8_t maxBitsPerChar = maxBitsPerCharTable[radix]; + uint64_t maximumCharactersRequired = + CeilDiv(static_cast<uint64_t>(bitsPerCharTableMultiplier) * bitLength, + maxBitsPerChar - 1); + maximumCharactersRequired += x->isNegative(); + + return AssertedCast<size_t>(maximumCharactersRequired); +} + +JSLinearString* BigInt::toStringBasePowerOfTwo(ExclusiveContext* cx, HandleBigInt x, + unsigned radix) { + MOZ_ASSERT(mozilla::IsPowerOfTwo(radix)); + MOZ_ASSERT(radix >= 2 && radix <= 32); + MOZ_ASSERT(!x->isZero()); + + const unsigned length = x->digitLength(); + const bool sign = x->isNegative(); + const unsigned bitsPerChar = mozilla::CountTrailingZeroes32(radix); + const unsigned charMask = radix - 1; + // Compute the length of the resulting string: divide the bit length of the + // BigInt by the number of bits representable per character (rounding up). + const Digit msd = x->digit(length - 1); + + const size_t bitLength = length * DigitBits - DigitLeadingZeroes(msd); + const size_t charsRequired = CeilDiv(bitLength, bitsPerChar) + sign; + + if (charsRequired > JSString::MAX_LENGTH) { + ReportOutOfMemory(cx); + return nullptr; + } + + auto resultChars = cx->make_pod_array<char>(charsRequired); + if (!resultChars) { + return nullptr; + } + + Digit digit = 0; + // Keeps track of how many unprocessed bits there are in |digit|. + unsigned availableBits = 0; + size_t pos = charsRequired; + for (unsigned i = 0; i < length - 1; i++) { + Digit newDigit = x->digit(i); + // Take any leftover bits from the last iteration into account. + unsigned current = (digit | (newDigit << availableBits)) & charMask; + MOZ_ASSERT(pos); + resultChars[--pos] = radixDigits[current]; + unsigned consumedBits = bitsPerChar - availableBits; + digit = newDigit >> consumedBits; + availableBits = DigitBits - consumedBits; + while (availableBits >= bitsPerChar) { + MOZ_ASSERT(pos); + resultChars[--pos] = radixDigits[digit & charMask]; + digit >>= bitsPerChar; + availableBits -= bitsPerChar; + } + } + + // Write out the character containing the lowest-order bit of |msd|. + // + // This character may include leftover bits from the Digit below |msd|. For + // example, if |x === 2n**64n| and |radix == 32|: the preceding loop writes + // twelve zeroes for low-order bits 0-59 in |x->digit(0)| (and |x->digit(1)| + // on 32-bit); then the highest 4 bits of of |x->digit(0)| (or |x->digit(1)| + // on 32-bit) and bit 0 of |x->digit(1)| (|x->digit(2)| on 32-bit) will + // comprise the |current == 0b1'0000| computed below for the high-order 'g' + // character. + unsigned current = (digit | (msd << availableBits)) & charMask; + MOZ_ASSERT(pos); + resultChars[--pos] = radixDigits[current]; + + // Write out remaining characters represented by |msd|. (There may be none, + // as in the example above.) + digit = msd >> (bitsPerChar - availableBits); + while (digit != 0) { + MOZ_ASSERT(pos); + resultChars[--pos] = radixDigits[digit & charMask]; + digit >>= bitsPerChar; + } + + if (sign) { + MOZ_ASSERT(pos); + resultChars[--pos] = '-'; + } + + MOZ_ASSERT(pos == 0); + return NewStringCopyN<CanGC>(cx, resultChars.get(), charsRequired); +} + +static constexpr BigInt::Digit MaxPowerInDigit(uint8_t radix) { + BigInt::Digit result = 1; + while (result < BigInt::Digit(-1) / radix) { + result *= radix; + } + return result; +} + +static constexpr uint8_t MaxExponentInDigit(uint8_t radix) { + uint8_t exp = 0; + BigInt::Digit result = 1; + while (result < BigInt::Digit(-1) / radix) { + result *= radix; + exp += 1; + } + return exp; +} + +struct RadixInfo { + BigInt::Digit maxPowerInDigit; + uint8_t maxExponentInDigit; + + constexpr RadixInfo(BigInt::Digit maxPower, uint8_t maxExponent) + : maxPowerInDigit(maxPower), maxExponentInDigit(maxExponent) {} + + explicit constexpr RadixInfo(uint8_t radix) + : RadixInfo(MaxPowerInDigit(radix), MaxExponentInDigit(radix)) {} +}; + +static constexpr const RadixInfo toStringInfo[37] = { + {0, 0}, {0, 0}, RadixInfo(2), RadixInfo(3), RadixInfo(4), + RadixInfo(5), RadixInfo(6), RadixInfo(7), RadixInfo(8), RadixInfo(9), + RadixInfo(10), RadixInfo(11), RadixInfo(12), RadixInfo(13), RadixInfo(14), + RadixInfo(15), RadixInfo(16), RadixInfo(17), RadixInfo(18), RadixInfo(19), + RadixInfo(20), RadixInfo(21), RadixInfo(22), RadixInfo(23), RadixInfo(24), + RadixInfo(25), RadixInfo(26), RadixInfo(27), RadixInfo(28), RadixInfo(29), + RadixInfo(30), RadixInfo(31), RadixInfo(32), RadixInfo(33), RadixInfo(34), + RadixInfo(35), RadixInfo(36), +}; + +JSLinearString* BigInt::toStringGeneric(ExclusiveContext* cx, HandleBigInt x, + unsigned radix) { + MOZ_ASSERT(radix >= 2 && radix <= 36); + MOZ_ASSERT(!x->isZero()); + + size_t maximumCharactersRequired = + calculateMaximumCharactersRequired(x, radix); + if (maximumCharactersRequired > JSString::MAX_LENGTH) { + ReportOutOfMemory(cx); + return nullptr; + } + + UniqueChars resultString(js_pod_malloc<char>(maximumCharactersRequired)); + if (!resultString) { + ReportOutOfMemory(cx); + return nullptr; + } + + size_t writePos = maximumCharactersRequired; + unsigned length = x->digitLength(); + Digit lastDigit; + if (length == 1) { + lastDigit = x->digit(0); + } else { + unsigned chunkChars = toStringInfo[radix].maxExponentInDigit; + Digit chunkDivisor = toStringInfo[radix].maxPowerInDigit; + + unsigned nonZeroDigit = length - 1; + MOZ_ASSERT(x->digit(nonZeroDigit) != 0); + + // `rest` holds the part of the BigInt that we haven't looked at yet. + // Not to be confused with "remainder"! + RootedBigInt rest(cx); + + // In the first round, divide the input, allocating a new BigInt for + // the result == rest; from then on divide the rest in-place. + // + // FIXME: absoluteDivWithDigitDivisor doesn't + // destructivelyTrimHighZeroDigits for in-place divisions, leading to + // worse constant factors. See + // https://bugzilla.mozilla.org/show_bug.cgi?id=1510213. + RootedBigInt dividend(cx, x); + do { + Digit chunk; + if (!absoluteDivWithDigitDivisor(cx, dividend, chunkDivisor, Some(&rest), + &chunk, dividend->isNegative())) { + return nullptr; + } + + dividend = rest; + for (unsigned i = 0; i < chunkChars; i++) { + MOZ_ASSERT(writePos > 0); + resultString[--writePos] = radixDigits[chunk % radix]; + chunk /= radix; + } + MOZ_ASSERT(!chunk); + + if (!rest->digit(nonZeroDigit)) { + nonZeroDigit--; + } + + MOZ_ASSERT(rest->digit(nonZeroDigit) != 0, + "division by a single digit can't remove more than one " + "digit from a number"); + } while (nonZeroDigit > 0); + + lastDigit = rest->digit(0); + } + + do { + MOZ_ASSERT(writePos > 0); + resultString[--writePos] = radixDigits[lastDigit % radix]; + lastDigit /= radix; + } while (lastDigit > 0); + MOZ_ASSERT(writePos < maximumCharactersRequired); + MOZ_ASSERT(maximumCharactersRequired - writePos <= + static_cast<size_t>(maximumCharactersRequired)); + + // Remove leading zeroes. + while (writePos + 1 < maximumCharactersRequired && + resultString[writePos] == '0') { + writePos++; + } + + if (x->isNegative()) { + MOZ_ASSERT(writePos > 0); + resultString[--writePos] = '-'; + } + + MOZ_ASSERT(writePos < maximumCharactersRequired); + // Would be better to somehow adopt resultString directly. + return NewStringCopyN<CanGC>(cx, resultString.get() + writePos, + maximumCharactersRequired - writePos); +} + +BigInt* BigInt::trimHighZeroDigits(ExclusiveContext* cx, HandleBigInt x) { + if (x->isZero()) { + MOZ_ASSERT(!x->isNegative()); + return x; + } + MOZ_ASSERT(x->digitLength()); + + int nonZeroIndex = x->digitLength() - 1; + while (nonZeroIndex >= 0 && x->digit(nonZeroIndex) == 0) { + nonZeroIndex--; + } + + if (nonZeroIndex < 0) { + return zero(cx); + } + + if (nonZeroIndex == static_cast<int>(x->digitLength() - 1)) { + return x; + } + + unsigned newLength = nonZeroIndex + 1; + BigInt* trimmedBigInt = createUninitialized(cx, newLength, x->isNegative()); + if (!trimmedBigInt) { + return nullptr; + } + for (unsigned i = 0; i < newLength; i++) { + trimmedBigInt->setDigit(i, x->digit(i)); + } + + return trimmedBigInt; +} + +BigInt* BigInt::destructivelyTrimHighZeroDigits(ExclusiveContext* cx, HandleBigInt x) { + // TODO: Modify in place instead of allocating. + return trimHighZeroDigits(cx, x); +} + +// The maximum value `radix**charCount - 1` must be represented as a max number +// `2**(N * DigitBits) - 1` for `N` digits, so +// +// 2**(N * DigitBits) - 1 ≥ radix**charcount - 1 +// 2**(N * DigitBits) ≥ radix**charcount +// N * DigitBits ≥ log2(radix**charcount) +// N * DigitBits ≥ charcount * log2(radix) +// N ≥ ⌈charcount * log2(radix) / DigitBits⌉ (conservatively) +// +// or in the code's terms (all numbers promoted to exact mathematical values), +// +// N ≥ ⌈charcount * bitsPerChar / (DigitBits * bitsPerCharTableMultiplier)⌉ +// +// Note that `N` is computed even more conservatively here because `bitsPerChar` +// is rounded up. +bool BigInt::calculateMaximumDigitsRequired(ExclusiveContext* cx, uint8_t radix, + size_t charcount, size_t* result) { + MOZ_ASSERT(2 <= radix && radix <= 36); + + size_t bitsPerChar = maxBitsPerCharTable[radix]; + + MOZ_ASSERT(charcount > 0); + MOZ_ASSERT(charcount <= std::numeric_limits<size_t>::max() / bitsPerChar); + uint64_t n = + CeilDiv(charcount * bitsPerChar, DigitBits * bitsPerCharTableMultiplier); + if (n > MaxDigitLength) { + ReportAllocationOverflow(cx); + return false; + } + + *result = n; + return true; +} + +template <typename CharT> +BigInt* BigInt::parseLiteralDigits(ExclusiveContext* cx, + const Range<const CharT> chars, + unsigned radix, bool isNegative, + bool* haveParseError) { + MOZ_ASSERT(chars.length()); + + RangedPtr<const CharT> start = chars.begin(); + RangedPtr<const CharT> end = chars.end(); + + // Skipping leading zeroes. + while (start[0] == '0') { + start++; + if (start == end) { + return zero(cx); + } + } + + unsigned limit0 = '0' + std::min(radix, 10u); + unsigned limita = 'a' + (radix - 10); + unsigned limitA = 'A' + (radix - 10); + + size_t length; + if (!calculateMaximumDigitsRequired(cx, radix, end - start, &length)) { + return nullptr; + } + RootedBigInt result(cx, createUninitialized(cx, length, isNegative)); + if (!result) { + return nullptr; + } + + result->initializeDigitsToZero(); + + RangedPtr<const CharT> begin = start; + for (; start < end; start++) { + uint32_t digit; + CharT c = *start; + if (c == '_' && start > begin && start < end - 1) { + // skip over block delimiters unless at the very start or end + continue; + } else if (c >= '0' && c < limit0) { + digit = c - '0'; + } else if (c >= 'a' && c < limita) { + digit = c - 'a' + 10; + } else if (c >= 'A' && c < limitA) { + digit = c - 'A' + 10; + } else { + *haveParseError = true; + return nullptr; + } + + result->inplaceMultiplyAdd(static_cast<Digit>(radix), + static_cast<Digit>(digit)); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// BigInt proposal section 7.2 +template <typename CharT> +BigInt* BigInt::parseLiteral(ExclusiveContext* cx, const Range<const CharT> chars, + bool* haveParseError) { + RangedPtr<const CharT> start = chars.begin(); + const RangedPtr<const CharT> end = chars.end(); + bool isNegative = false; + + MOZ_ASSERT(chars.length()); + + if (end - start > 2 && start[0] == '0') { + if (start[1] == 'b' || start[1] == 'B') { + // StringNumericLiteral ::: BinaryIntegerLiteral + return parseLiteralDigits(cx, Range<const CharT>(start + 2, end), 2, + isNegative, haveParseError); + } + if (start[1] == 'x' || start[1] == 'X') { + // StringNumericLiteral ::: HexIntegerLiteral + return parseLiteralDigits(cx, Range<const CharT>(start + 2, end), 16, + isNegative, haveParseError); + } + if (start[1] == 'o' || start[1] == 'O') { + // StringNumericLiteral ::: OctalIntegerLiteral + return parseLiteralDigits(cx, Range<const CharT>(start + 2, end), 8, + isNegative, haveParseError); + } + } + + return parseLiteralDigits(cx, Range<const CharT>(start, end), 10, isNegative, + haveParseError); +} + +// BigInt proposal section 5.1.1 +static bool IsInteger(double d) { + // Step 1 is an assertion checked by the caller. + // Step 2. + if (!mozilla::IsFinite(d)) { + return false; + } + + // Step 3. + double i = JS::ToInteger(d); + + // Step 4. + if (i != d) { + return false; + } + + // Step 5. + return true; +} + +BigInt* BigInt::createFromDouble(ExclusiveContext* cx, double d) { + MOZ_ASSERT(::IsInteger(d), + "Only integer-valued doubles can convert to BigInt"); + + if (d == 0) { + return zero(cx); + } + + int exponent = mozilla::ExponentComponent(d); + MOZ_ASSERT(exponent >= 0); + int length = exponent / DigitBits + 1; + BigInt* result = createUninitialized(cx, length, d < 0); + if (!result) { + return nullptr; + } + + // We construct a BigInt from the double `d` by shifting its mantissa + // according to its exponent and mapping the bit pattern onto digits. + // + // <----------- bitlength = exponent + 1 -----------> + // <----- 52 ------> <------ trailing zeroes ------> + // mantissa: 1yyyyyyyyyyyyyyyyy 0000000000000000000000000000000 + // digits: 0001xxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx xxxxxxxx + // <--> <------> + // msdTopBits DigitBits + // + using Double = mozilla::FloatingPoint<double>; + uint64_t mantissa = + mozilla::BitwiseCast<uint64_t>(d) & Double::kSignificandBits; + // Add implicit high bit. + mantissa |= 1ull << Double::kSignificandWidth; + + const int mantissaTopBit = Double::kSignificandWidth; // 0-indexed. + + // 0-indexed position of `d`'s most significant bit within the `msd`. + int msdTopBit = exponent % DigitBits; + + // Next digit under construction. + Digit digit; + + // First, build the MSD by shifting the mantissa appropriately. + if (msdTopBit < mantissaTopBit) { + int remainingMantissaBits = mantissaTopBit - msdTopBit; + digit = mantissa >> remainingMantissaBits; + mantissa = mantissa << (64 - remainingMantissaBits); + } else { + MOZ_ASSERT(msdTopBit >= mantissaTopBit); + digit = mantissa << (msdTopBit - mantissaTopBit); + mantissa = 0; + } + result->setDigit(--length, digit); + + // Fill in digits containing mantissa contributions. + while (mantissa) { + MOZ_ASSERT(length > 0, + "double bits were all non-fractional, so there must be " + "digits present to hold them"); + + if (DigitBits == 64) { + result->setDigit(--length, mantissa); + break; + } + + MOZ_ASSERT(DigitBits == 32); + Digit current = mantissa >> 32; + mantissa = mantissa << 32; + result->setDigit(--length, current); + } + + // Fill in low-order zeroes. + for (int i = length - 1; i >= 0; i--) { + result->setDigit(i, 0); + } + + return result; +} + +BigInt* BigInt::createFromUint64(ExclusiveContext* cx, uint64_t n) { + if (n == 0) { + return zero(cx); + } + + const bool isNegative = false; + + if (DigitBits == 32) { + Digit low = n; + Digit high = n >> 32; + size_t length = high ? 2 : 1; + + BigInt* res = createUninitialized(cx, length, isNegative); + if (!res) { + return nullptr; + } + res->setDigit(0, low); + if (high) { + res->setDigit(1, high); + } + return res; + } + + BigInt* res = createUninitialized(cx, 1, isNegative); + if (!res) { + return nullptr; + } + + res->setDigit(0, n); + return res; +} + +BigInt* BigInt::createFromInt64(ExclusiveContext* cx, int64_t n) { + BigInt* res = createFromUint64(cx, Abs(n)); + if (!res) { + return nullptr; + } + + if (n < 0) { + res->lengthSignAndReservedBits_ |= SignBit; + } + MOZ_ASSERT(res->isNegative() == (n < 0)); + + return res; +} + +// BigInt proposal section 5.1.2 +BigInt* js::NumberToBigInt(ExclusiveContext* cx, double d) { + // Step 1 is an assertion checked by the caller. + // Step 2. + if (!::IsInteger(d)) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_NUMBER_TO_BIGINT); + } + return nullptr; + } + + // Step 3. + return BigInt::createFromDouble(cx, d); +} + +BigInt* BigInt::copy(ExclusiveContext* cx, HandleBigInt x) { + if (x->isZero()) { + return zero(cx); + } + + BigInt* result = createUninitialized(cx, x->digitLength(), x->isNegative()); + if (!result) { + return nullptr; + } + for (size_t i = 0; i < x->digitLength(); i++) { + result->setDigit(i, x->digit(i)); + } + return result; +} + +// BigInt proposal section 1.1.7 +BigInt* BigInt::add(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + bool xNegative = x->isNegative(); + + // x + y == x + y + // -x + -y == -(x + y) + if (xNegative == y->isNegative()) { + return absoluteAdd(cx, x, y, xNegative); + } + + // x + -y == x - y == -(y - x) + // -x + y == y - x == -(x - y) + if (absoluteCompare(x, y) >= 0) { + return absoluteSub(cx, x, y, xNegative); + } + + return absoluteSub(cx, y, x, !xNegative); +} + +// BigInt proposal section 1.1.8 +BigInt* BigInt::sub(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + bool xNegative = x->isNegative(); + if (xNegative != y->isNegative()) { + // x - (-y) == x + y + // (-x) - y == -(x + y) + return absoluteAdd(cx, x, y, xNegative); + } + // x - y == -(y - x) + // (-x) - (-y) == y - x == -(x - y) + if (absoluteCompare(x, y) >= 0) { + return absoluteSub(cx, x, y, xNegative); + } + + return absoluteSub(cx, y, x, !xNegative); +} + +// BigInt proposal section 1.1.4 +BigInt* BigInt::mul(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero()) { + return x; + } + if (y->isZero()) { + return y; + } + + unsigned resultLength = x->digitLength() + y->digitLength(); + bool resultNegative = x->isNegative() != y->isNegative(); + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + result->initializeDigitsToZero(); + + for (size_t i = 0; i < x->digitLength(); i++) { + multiplyAccumulate(y, x->digit(i), result, i); + } + + return destructivelyTrimHighZeroDigits(cx, result); +} + +// BigInt proposal section 1.1.5 +BigInt* BigInt::div(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + // 1. If y is 0n, throw a RangeError exception. + if (y->isZero()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_DIVISION_BY_ZERO); + } + return nullptr; + } + + // 2. Let quotient be the mathematical value of x divided by y. + // 3. Return a BigInt representing quotient rounded towards 0 to the next + // integral value. + if (x->isZero()) { + return x; + } + + if (absoluteCompare(x, y) < 0) { + return zero(cx); + } + + RootedBigInt quotient(cx); + bool resultNegative = x->isNegative() != y->isNegative(); + if (y->digitLength() == 1) { + Digit divisor = y->digit(0); + if (divisor == 1) { + return resultNegative == x->isNegative() ? x : neg(cx, x); + } + + Digit remainder; + if (!absoluteDivWithDigitDivisor(cx, x, divisor, Some("ient), + &remainder, resultNegative)) { + return nullptr; + } + } else { + if (!absoluteDivWithBigIntDivisor(cx, x, y, Some("ient), Nothing(), + resultNegative)) { + return nullptr; + } + } + + return destructivelyTrimHighZeroDigits(cx, quotient); +} + +// BigInt proposal section 1.1.6 +BigInt* BigInt::mod(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + // 1. If y is 0n, throw a RangeError exception. + if (y->isZero()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_DIVISION_BY_ZERO); + } + return nullptr; + } + + // 2. If x is 0n, return x. + if (x->isZero()) { + return x; + } + // 3. Let r be the BigInt defined by the mathematical relation r = x - (y × + // q) where q is a BigInt that is negative only if x/y is negative and + // positive only if x/y is positive, and whose magnitude is as large as + // possible without exceeding the magnitude of the true mathematical + // quotient of x and y. + if (absoluteCompare(x, y) < 0) { + return x; + } + + if (y->digitLength() == 1) { + Digit divisor = y->digit(0); + if (divisor == 1) { + return zero(cx); + } + + Digit remainderDigit; + bool unusedQuotientNegative = false; + if (!absoluteDivWithDigitDivisor(cx, x, divisor, Nothing(), &remainderDigit, + unusedQuotientNegative)) { + MOZ_CRASH("BigInt div by digit failed unexpectedly"); + } + + if (!remainderDigit) { + return zero(cx); + } + + BigInt* remainder = createUninitialized(cx, 1, x->isNegative()); + if (!remainder) { + return nullptr; + } + remainder->setDigit(0, remainderDigit); + return remainder; + } else { + RootedBigInt remainder(cx); + if (!absoluteDivWithBigIntDivisor(cx, x, y, Nothing(), Some(&remainder), + x->isNegative())) { + return nullptr; + } + MOZ_ASSERT(remainder); + return destructivelyTrimHighZeroDigits(cx, remainder); + } +} + +// BigInt proposal section 1.1.3 +BigInt* BigInt::pow(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + // 1. If exponent is < 0, throw a RangeError exception. + if (y->isNegative()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_NEGATIVE_EXPONENT); + } + return nullptr; + } + + // 2. If base is 0n and exponent is 0n, return 1n. + if (y->isZero()) { + return one(cx); + } + + if (x->isZero()) { + return x; + } + + // 3. Return a BigInt representing the mathematical value of base raised + // to the power exponent. + if (x->digitLength() == 1 && x->digit(0) == 1) { + // (-1) ** even_number == 1. + if (x->isNegative() && (y->digit(0) & 1) == 0) { + return neg(cx, x); + } + // (-1) ** odd_number == -1; 1 ** anything == 1. + return x; + } + + // For all bases >= 2, very large exponents would lead to unrepresentable + // results. + static_assert(MaxBitLength < std::numeric_limits<Digit>::max(), + "unexpectedly large MaxBitLength"); + if (y->digitLength() > 1) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TOO_LARGE); + } + return nullptr; + } + Digit exponent = y->digit(0); + if (exponent == 1) { + return x; + } + if (exponent >= MaxBitLength) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TOO_LARGE); + } + return nullptr; + } + + static_assert(MaxBitLength <= std::numeric_limits<int>::max(), + "unexpectedly large MaxBitLength"); + int n = static_cast<int>(exponent); + if (x->digitLength() == 1 && x->digit(0) == 2) { + // Fast path for 2^n. + int length = 1 + (n / DigitBits); + // Result is negative for odd powers of -2n. + bool resultNegative = x->isNegative() && (n & 1); + RootedBigInt result(cx, createUninitialized(cx, length, resultNegative)); + if (!result) { + return nullptr; + } + result->initializeDigitsToZero(); + result->setDigit(length - 1, static_cast<Digit>(1) << (n % DigitBits)); + return result; + } + + // This implicitly sets the result's sign correctly. + RootedBigInt result(cx, (n & 1) ? x : nullptr); + RootedBigInt runningSquare(cx, x); + for (n /= 2; n; n /= 2) { + runningSquare = mul(cx, runningSquare, runningSquare); + if (!runningSquare) { + return nullptr; + } + if (n & 1) { + if (!result) { + result = runningSquare; + } else { + result = mul(cx, result, runningSquare); + if (!result) { + return nullptr; + } + } + } + } + return result; +} + +BigInt* BigInt::lshByAbsolute(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero() || y->isZero()) { + return x; + } + + if (y->digitLength() > 1 || y->digit(0) > MaxBitLength) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TOO_LARGE); + } + return nullptr; + } + Digit shift = y->digit(0); + int digitShift = static_cast<int>(shift / DigitBits); + int bitsShift = static_cast<int>(shift % DigitBits); + int length = x->digitLength(); + bool grow = bitsShift && (x->digit(length - 1) >> (DigitBits - bitsShift)); + int resultLength = length + digitShift + grow; + RootedBigInt result(cx, + createUninitialized(cx, resultLength, x->isNegative())); + if (!result) { + return nullptr; + } + + int i = 0; + for (; i < digitShift; i++) { + result->setDigit(i, 0); + } + + if (bitsShift == 0) { + for (int j = 0; i < resultLength; i++, j++) { + result->setDigit(i, x->digit(j)); + } + } else { + Digit carry = 0; + for (int j = 0; j < length; i++, j++) { + Digit d = x->digit(j); + result->setDigit(i, (d << bitsShift) | carry); + carry = d >> (DigitBits - bitsShift); + } + if (grow) { + result->setDigit(i, carry); + } else { + MOZ_ASSERT(!carry); + } + } + return result; +} + +BigInt* BigInt::rshByMaximum(ExclusiveContext* cx, bool isNegative) { + if (isNegative) { + RootedBigInt negativeOne(cx, createUninitialized(cx, 1, isNegative)); + if (!negativeOne) { + return nullptr; + } + negativeOne->setDigit(0, 1); + return negativeOne; + } + return zero(cx); +} + +BigInt* BigInt::rshByAbsolute(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero() || y->isZero()) { + return x; + } + + if (y->digitLength() > 1 || y->digit(0) >= MaxBitLength) { + return rshByMaximum(cx, x->isNegative()); + } + Digit shift = y->digit(0); + int length = x->digitLength(); + int digitShift = static_cast<int>(shift / DigitBits); + int bitsShift = static_cast<int>(shift % DigitBits); + int resultLength = length - digitShift; + if (resultLength <= 0) { + return rshByMaximum(cx, x->isNegative()); + } + // For negative numbers, round down if any bit was shifted out (so that e.g. + // -5n >> 1n == -3n and not -2n). Check now whether this will happen and + // whether it can cause overflow into a new digit. If we allocate the result + // large enough up front, it avoids having to do a second allocation later. + bool mustRoundDown = false; + if (x->isNegative()) { + const Digit mask = (static_cast<Digit>(1) << bitsShift) - 1; + if ((x->digit(digitShift) & mask)) { + mustRoundDown = true; + } else { + for (int i = 0; i < digitShift; i++) { + if (x->digit(i)) { + mustRoundDown = true; + break; + } + } + } + } + // If bits_shift is non-zero, it frees up bits, preventing overflow. + if (mustRoundDown && bitsShift == 0) { + // Overflow cannot happen if the most significant digit has unset bits. + Digit msd = x->digit(length - 1); + bool roundingCanOverflow = msd == std::numeric_limits<Digit>::max(); + if (roundingCanOverflow) { + resultLength++; + } + } + + MOZ_ASSERT(resultLength <= length); + RootedBigInt result(cx, + createUninitialized(cx, resultLength, x->isNegative())); + if (!result) { + return nullptr; + } + if (!bitsShift) { + // If roundingCanOverflow, manually initialize the overflow digit. + result->setDigit(resultLength - 1, 0); + for (int i = digitShift; i < length; i++) { + result->setDigit(i - digitShift, x->digit(i)); + } + } else { + Digit carry = x->digit(digitShift) >> bitsShift; + int last = length - digitShift - 1; + for (int i = 0; i < last; i++) { + Digit d = x->digit(i + digitShift + 1); + result->setDigit(i, (d << (DigitBits - bitsShift)) | carry); + carry = d >> bitsShift; + } + result->setDigit(last, carry); + } + + if (mustRoundDown) { + MOZ_ASSERT(x->isNegative()); + // Since the result is negative, rounding down means adding one to + // its absolute value. This cannot overflow. TODO: modify the result in + // place. + return absoluteAddOne(cx, result, x->isNegative()); + } + return destructivelyTrimHighZeroDigits(cx, result); +} + +// BigInt proposal section 1.1.9. BigInt::leftShift ( x, y ) +BigInt* BigInt::lsh(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (y->isNegative()) { + return rshByAbsolute(cx, x, y); + } + return lshByAbsolute(cx, x, y); +} + +// BigInt proposal section 1.1.10. BigInt::signedRightShift ( x, y ) +BigInt* BigInt::rsh(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (y->isNegative()) { + return lshByAbsolute(cx, x, y); + } + return rshByAbsolute(cx, x, y); +} + +// BigInt proposal section 1.1.17. BigInt::bitwiseAND ( x, y ) +BigInt* BigInt::bitAnd(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero()) { + return x; + } + + if (y->isZero()) { + return y; + } + + if (!x->isNegative() && !y->isNegative()) { + return absoluteAnd(cx, x, y); + } + + if (x->isNegative() && y->isNegative()) { + int resultLength = std::max(x->digitLength(), y->digitLength()) + 1; + // (-x) & (-y) == ~(x-1) & ~(y-1) == ~((x-1) | (y-1)) + // == -(((x-1) | (y-1)) + 1) + RootedBigInt x1(cx, absoluteSubOne(cx, x, resultLength)); + if (!x1) { + return nullptr; + } + RootedBigInt y1(cx, absoluteSubOne(cx, y, y->digitLength())); + if (!y1) { + return nullptr; + } + RootedBigInt result(cx, absoluteOr(cx, x1, y1)); + if (!result) { + return nullptr; + } + bool resultNegative = true; + return absoluteAddOne(cx, result, resultNegative); + } + + MOZ_ASSERT(x->isNegative() != y->isNegative()); + HandleBigInt& pos = x->isNegative() ? y : x; + HandleBigInt& neg = x->isNegative() ? x : y; + + RootedBigInt neg1(cx, absoluteSubOne(cx, neg, neg->digitLength())); + if (!neg1) { + return nullptr; + } + + // x & (-y) == x & ~(y-1) == x & ~(y-1) + return absoluteAndNot(cx, pos, neg1); +} + +// BigInt proposal section 1.1.18. BigInt::bitwiseXOR ( x, y ) +BigInt* BigInt::bitXor(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero()) { + return y; + } + + if (y->isZero()) { + return x; + } + + if (!x->isNegative() && !y->isNegative()) { + return absoluteXor(cx, x, y); + } + + if (x->isNegative() && y->isNegative()) { + int resultLength = std::max(x->digitLength(), y->digitLength()); + + // (-x) ^ (-y) == ~(x-1) ^ ~(y-1) == (x-1) ^ (y-1) + RootedBigInt x1(cx, absoluteSubOne(cx, x, resultLength)); + if (!x1) { + return nullptr; + } + RootedBigInt y1(cx, absoluteSubOne(cx, y, y->digitLength())); + if (!y1) { + return nullptr; + } + return absoluteXor(cx, x1, y1); + } + MOZ_ASSERT(x->isNegative() != y->isNegative()); + int resultLength = std::max(x->digitLength(), y->digitLength()) + 1; + + HandleBigInt& pos = x->isNegative() ? y : x; + HandleBigInt& neg = x->isNegative() ? x : y; + + // x ^ (-y) == x ^ ~(y-1) == ~(x ^ (y-1)) == -((x ^ (y-1)) + 1) + RootedBigInt result(cx, absoluteSubOne(cx, neg, resultLength)); + if (!result) { + return nullptr; + } + result = absoluteXor(cx, result, pos); + if (!result) { + return nullptr; + } + bool resultNegative = true; + return absoluteAddOne(cx, result, resultNegative); +} + +// BigInt proposal section 1.1.19. BigInt::bitwiseOR ( x, y ) +BigInt* BigInt::bitOr(ExclusiveContext* cx, HandleBigInt x, HandleBigInt y) { + if (x->isZero()) { + return y; + } + + if (y->isZero()) { + return x; + } + + unsigned resultLength = std::max(x->digitLength(), y->digitLength()); + bool resultNegative = x->isNegative() || y->isNegative(); + + if (!resultNegative) { + return absoluteOr(cx, x, y); + } + + if (x->isNegative() && y->isNegative()) { + // (-x) | (-y) == ~(x-1) | ~(y-1) == ~((x-1) & (y-1)) + // == -(((x-1) & (y-1)) + 1) + RootedBigInt result(cx, absoluteSubOne(cx, x, resultLength)); + if (!result) { + return nullptr; + } + RootedBigInt y1(cx, absoluteSubOne(cx, y, y->digitLength())); + if (!y1) { + return nullptr; + } + result = absoluteAnd(cx, result, y1); + if (!result) { + return nullptr; + } + return absoluteAddOne(cx, result, resultNegative); + } + + MOZ_ASSERT(x->isNegative() != y->isNegative()); + HandleBigInt& pos = x->isNegative() ? y : x; + HandleBigInt& neg = x->isNegative() ? x : y; + + // x | (-y) == x | ~(y-1) == ~((y-1) &~ x) == -(((y-1) &~ x) + 1) + RootedBigInt result(cx, absoluteSubOne(cx, neg, resultLength)); + if (!result) { + return nullptr; + } + result = absoluteAndNot(cx, result, pos); + if (!result) { + return nullptr; + } + return absoluteAddOne(cx, result, resultNegative); +} + +// BigInt proposal section 1.1.2. BigInt::bitwiseNOT ( x ) +BigInt* BigInt::bitNot(ExclusiveContext* cx, HandleBigInt x) { + if (x->isNegative()) { + // ~(-x) == ~(~(x-1)) == x-1 + return absoluteSubOne(cx, x, x->digitLength()); + } else { + // ~x == -x-1 == -(x+1) + bool resultNegative = true; + return absoluteAddOne(cx, x, resultNegative); + } +} + +int64_t BigInt::toInt64(BigInt* x) { return WrapToSigned(toUint64(x)); } + +uint64_t BigInt::toUint64(BigInt* x) { + if (x->isZero()) { + return 0; + } + + uint64_t digit = x->digit(0); + + if (DigitBits == 32 && x->digitLength() > 1) { + digit |= static_cast<uint64_t>(x->digit(1)) << 32; + } + + // Return the two's complement if x is negative. + if (x->isNegative()) { + return ~(digit - 1); + } + + return digit; +} + +bool BigInt::isInt64(BigInt* x, int64_t* result) { + MOZ_MAKE_MEM_UNDEFINED(result, sizeof(*result)); + + size_t length = x->digitLength(); + if (length > (DigitBits == 32 ? 2 : 1)) { + return false; + } + + if (length == 0) { + *result = 0; + return true; + } + + uint64_t magnitude = x->digit(0); + if (DigitBits == 32 && length > 1) { + magnitude |= static_cast<uint64_t>(x->digit(1)) << 32; + } + + if (x->isNegative()) { + constexpr uint64_t Int64MinMagnitude = uint64_t(1) << 63; + if (magnitude <= Int64MinMagnitude) { + *result = magnitude == Int64MinMagnitude + ? std::numeric_limits<int64_t>::min() + : -AssertedCast<int64_t>(magnitude); + return true; + } + } else { + if (magnitude <= + static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) { + *result = AssertedCast<int64_t>(magnitude); + return true; + } + } + + return false; +} + +// Compute `2**bits - (x & (2**bits - 1))`. Used when treating BigInt values as +// arbitrary-precision two's complement signed integers. +BigInt* BigInt::truncateAndSubFromPowerOfTwo(ExclusiveContext* cx, HandleBigInt x, + uint64_t bits, + bool resultNegative) { + MOZ_ASSERT(bits != 0); + MOZ_ASSERT(!x->isZero()); + + size_t resultLength = CeilDiv(bits, DigitBits); + RootedBigInt result(cx, + createUninitialized(cx, resultLength, resultNegative)); + if (!result) { + return nullptr; + } + + // Process all digits except the MSD. + size_t xLength = x->digitLength(); + Digit borrow = 0; + // Take digits from `x` until its length is exhausted. + for (size_t i = 0; i < std::min(resultLength - 1, xLength); i++) { + Digit newBorrow = 0; + Digit difference = digitSub(0, x->digit(i), &newBorrow); + difference = digitSub(difference, borrow, &newBorrow); + result->setDigit(i, difference); + borrow = newBorrow; + } + // Then simulate leading zeroes in `x` as needed. + for (size_t i = xLength; i < resultLength - 1; i++) { + Digit newBorrow = 0; + Digit difference = digitSub(0, borrow, &newBorrow); + result->setDigit(i, difference); + borrow = newBorrow; + } + + // The MSD might contain extra bits that we don't want. + Digit xMSD = resultLength <= xLength ? x->digit(resultLength - 1) : 0; + Digit resultMSD; + if (bits % DigitBits == 0) { + Digit newBorrow = 0; + resultMSD = digitSub(0, xMSD, &newBorrow); + resultMSD = digitSub(resultMSD, borrow, &newBorrow); + } else { + size_t drop = DigitBits - (bits % DigitBits); + xMSD = (xMSD << drop) >> drop; + Digit minuendMSD = Digit(1) << (DigitBits - drop); + Digit newBorrow = 0; + resultMSD = digitSub(minuendMSD, xMSD, &newBorrow); + resultMSD = digitSub(resultMSD, borrow, &newBorrow); + MOZ_ASSERT(newBorrow == 0, "result < 2^bits"); + // If all subtracted bits were zero, we have to get rid of the + // materialized minuendMSD again. + resultMSD &= (minuendMSD - 1); + } + result->setDigit(resultLength - 1, resultMSD); + + return trimHighZeroDigits(cx, result); +} + +BigInt* BigInt::asUintN(ExclusiveContext* cx, HandleBigInt x, uint64_t bits) { + if (x->isZero()) { + return x; + } + + if (bits == 0) { + return zero(cx); + } + + // When truncating a negative number, simulate two's complement. + if (x->isNegative()) { + bool resultNegative = false; + return truncateAndSubFromPowerOfTwo(cx, x, bits, resultNegative); + } + + if (bits <= 64) { + uint64_t u64 = toUint64(x); + uint64_t mask = uint64_t(-1) >> (64 - bits); + return createFromUint64(cx, u64 & mask); + } + + if (bits >= MaxBitLength) { + return x; + } + + Digit msd = x->digit(x->digitLength() - 1); + size_t msdBits = DigitBits - DigitLeadingZeroes(msd); + size_t bitLength = msdBits + (x->digitLength() - 1) * DigitBits; + + if (bits >= bitLength) { + return x; + } + + size_t length = CeilDiv(bits, DigitBits); + bool isNegative = false; + + BigInt* res = createUninitialized(cx, length, isNegative); + if (!res) { + return nullptr; + } + + MOZ_ASSERT(length >= 2, "single-digit cases should be handled above"); + MOZ_ASSERT(length <= x->digitLength()); + for (size_t i = 0; i < length - 1; i++) { + res->setDigit(i, x->digit(i)); + } + + Digit mask = Digit(-1) >> (DigitBits - (bits % DigitBits)); + res->setDigit(length - 1, x->digit(length - 1) & mask); + + return res; +} + +BigInt* BigInt::asIntN(ExclusiveContext* cx, HandleBigInt x, uint64_t bits) { + if (x->isZero()) { + return x; + } + + if (bits == 0) { + return zero(cx); + } + + if (bits == 64) { + return createFromInt64(cx, toInt64(x)); + } + + if (bits > MaxBitLength) { + return x; + } + + Digit msd = x->digit(x->digitLength() - 1); + size_t msdBits = DigitBits - DigitLeadingZeroes(msd); + size_t bitLength = msdBits + (x->digitLength() - 1) * DigitBits; + + if (bits > bitLength) { + return x; + } + + Digit signBit = Digit(1) << ((bits - 1) % DigitBits); + if (bits == bitLength && msd < signBit) { + return x; + } + + // All the cases above were the trivial cases: truncating zero, or to zero + // bits, or to more bits than are in `x` (so we return `x` directly), or we + // already have the 64-bit fast path. If we get here, follow the textbook + // algorithm from the specification. + + // BigInt.asIntN step 3: Let `mod` be `x` modulo `2**bits`. + RootedBigInt mod(cx, asUintN(cx, x, bits)); + if (!mod) { + return nullptr; + } + + // Step 4: If `mod >= 2**(bits - 1)`, return `mod - 2**bits`; otherwise, + // return `mod`. + if (mod->digitLength() == CeilDiv(bits, DigitBits) && + (mod->digit(mod->digitLength() - 1) & signBit) != 0) { + bool resultNegative = true; + return truncateAndSubFromPowerOfTwo(cx, mod, bits, resultNegative); + } + + return mod; +} + +static bool ValidBigIntOperands(ExclusiveContext* cx, HandleValue lhs, + HandleValue rhs) { + MOZ_ASSERT(lhs.isBigInt() || rhs.isBigInt()); + + if (!lhs.isBigInt() || !rhs.isBigInt()) { + if (cx->isJSContext()) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_TO_NUMBER); + } + return false; + } + + return true; +} + +bool BigInt::add(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::add(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::sub(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::sub(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::mul(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::mul(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::div(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::div(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::mod(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::mod(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::pow(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::pow(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::neg(ExclusiveContext* cx, HandleValue operand, MutableHandleValue res) { + MOZ_ASSERT(operand.isBigInt()); + + RootedBigInt operandBigInt(cx, operand.toBigInt()); + BigInt* resBigInt = BigInt::neg(cx, operandBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::lsh(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::lsh(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::rsh(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::rsh(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::bitAnd(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::bitAnd(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::bitXor(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::bitXor(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::bitOr(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + MutableHandleValue res) { + if (!ValidBigIntOperands(cx, lhs, rhs)) { + return false; + } + + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + BigInt* resBigInt = BigInt::bitOr(cx, lhsBigInt, rhsBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +bool BigInt::bitNot(ExclusiveContext* cx, HandleValue operand, + MutableHandleValue res) { + MOZ_ASSERT(operand.isBigInt()); + + RootedBigInt operandBigInt(cx, operand.toBigInt()); + BigInt* resBigInt = BigInt::bitNot(cx, operandBigInt); + if (!resBigInt) { + return false; + } + res.setBigInt(resBigInt); + return true; +} + +// BigInt proposal section 7.3 +BigInt* js::ToBigInt(ExclusiveContext* cx, HandleValue val) { + RootedValue v(cx, val); + + if(cx->isJSContext()) { + // Step 1. + if (!ToPrimitive(cx->asJSContext(), JSTYPE_NUMBER, &v)) { + return nullptr; + } + + // Step 2. + if (v.isBigInt()) { + return v.toBigInt(); + } + + if (v.isBoolean()) { + return v.toBoolean() ? BigInt::one(cx) : BigInt::zero(cx); + } + + if (v.isString()) { + BigInt* bi = nullptr; + RootedString str(cx, v.toString()); + JS_TRY_VAR_OR_RETURN_NULL(cx, bi, StringToBigInt(cx, str)); + if (!bi) { + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, + JSMSG_BIGINT_INVALID_SYNTAX); + return nullptr; + } + return bi; + } + + JS_ReportErrorNumberASCII(cx->asJSContext(), GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); + } + return nullptr; +} + +JS::Result<int64_t> js::ToBigInt64(JSContext* cx, HandleValue v) { + BigInt* bi = ToBigInt(cx, v); + if (!bi) { + return cx->alreadyReportedError(); + } + return BigInt::toInt64(bi); +} + +JS::Result<uint64_t> js::ToBigUint64(JSContext* cx, HandleValue v) { + BigInt* bi = ToBigInt(cx, v); + if (!bi) { + return cx->alreadyReportedError(); + } + return BigInt::toUint64(bi); +} + +double BigInt::numberValue(BigInt* x) { + if (x->isZero()) { + return 0.0; + } + + using Double = mozilla::FloatingPoint<double>; + constexpr uint8_t ExponentShift = Double::kExponentShift; + constexpr uint8_t SignificandWidth = Double::kSignificandWidth; + constexpr unsigned ExponentBias = Double::kExponentBias; + constexpr uint8_t SignShift = Double::kExponentWidth + SignificandWidth; + + size_t length = x->digitLength(); + MOZ_ASSERT(length != 0); + + // Fast path for the likely-common case of up to a uint64_t of magnitude + // that doesn't exceed integral precision in IEEE-754. + if (length <= 64 / DigitBits) { + uint64_t magnitude = x->digit(0); + if (DigitBits == 32 && length > 1) { + magnitude |= static_cast<uint64_t>(x->digit(1)) << 32; + } + const uint64_t MaxIntegralPrecisionDouble = uint64_t(1) + << (SignificandWidth + 1); + if (magnitude <= MaxIntegralPrecisionDouble) { + return x->isNegative() ? -double(magnitude) : +double(magnitude); + } + } + + Digit msd = x->digit(length - 1); + uint8_t msdLeadingZeroes = DigitLeadingZeroes(msd); + + // `2**ExponentBias` is the largest power of two in a finite IEEE-754 + // double. If this bigint has a greater power of two, it'll round to + // infinity. + uint64_t exponent = length * DigitBits - msdLeadingZeroes - 1; + if (exponent > ExponentBias) { + return x->isNegative() ? mozilla::NegativeInfinity<double>() + : mozilla::PositiveInfinity<double>(); + } + + // Otherwise munge the most significant bits of the number into proper + // position in an IEEE-754 double and go to town. + + // Omit the most significant bit: the IEEE-754 format includes this bit + // implicitly for all double-precision integers. + const uint8_t msdIgnoredBits = msdLeadingZeroes + 1; + const uint8_t msdIncludedBits = DigitBits - msdIgnoredBits; + + uint8_t bitsFilled = msdIncludedBits; + + // Shift `msd`'s contributed bits upward to remove high-order zeroes and + // the highest set bit (which is implicit in IEEE-754 integral values so + // must be removed) and to add low-order zeroes. + uint64_t shiftedMantissa = + msdIncludedBits == 0 ? 0 : uint64_t(msd) << (64 - msdIncludedBits); + + // Add in bits from the next one or two digits if `msd` didn't contain all + // bits necessary to define the result. (The extra bit allows us to + // properly round an inexact overall result.) Any lower bits that are + // uselessly set will be shifted away when `shiftedMantissa` is converted to + // a real mantissa. + if (bitsFilled < SignificandWidth + 1) { + MOZ_ASSERT(length >= 2, + "single-Digit numbers with this few bits should have been " + "handled by the fast-path above"); + + Digit second = x->digit(length - 2); + if (DigitBits == 32) { + shiftedMantissa |= uint64_t(second) << msdIgnoredBits; + bitsFilled += DigitBits; + + // Add in bits from another digit, if any, if we still have unfilled + // significand bits. + if (bitsFilled < SignificandWidth + 1 && length >= 3) { + Digit third = x->digit(length - 3); + shiftedMantissa |= uint64_t(third) >> msdIncludedBits; + // The second and third 32-bit digits contributed 64 bits total, filling + // well beyond the mantissa. + bitsFilled = 64; + } + } else { + shiftedMantissa |= second >> msdIncludedBits; + // A full 64-bit digit's worth of bits (some from the most significant + // digit, the rest from the next) fills well beyond the mantissa. + bitsFilled = 64; + } + } + + // Round the overall result, if necessary. (It's possible we don't need to + // round -- the number might not have enough bits to round.) + if (bitsFilled >= SignificandWidth + 1) { + constexpr uint64_t LeastSignificantBit = uint64_t(1) + << (64 - SignificandWidth); + constexpr uint64_t ExtraBit = LeastSignificantBit >> 1; + + // When the first bit outside the significand is set, the overall value + // is rounded: downward (i.e. no change to the bits) if the least + // significant bit in the significand is zero, upward if it instead is + // one. + if ((shiftedMantissa & ExtraBit) && + (shiftedMantissa & LeastSignificantBit)) { + // We're rounding upward: add to the significand bits. If they + // overflow, the exponent must also be increased. If *that* + // overflows, return the appropriate infinity. + uint64_t before = shiftedMantissa; + shiftedMantissa += ExtraBit; + if (shiftedMantissa < before) { + exponent++; + if (exponent > ExponentBias) { + return x->isNegative() ? NegativeInfinity<double>() + : PositiveInfinity<double>(); + } + } + } + } + + uint64_t significandBits = shiftedMantissa >> (64 - SignificandWidth); + uint64_t signBit = uint64_t(x->isNegative() ? 1 : 0) << SignShift; + uint64_t exponentBits = (exponent + ExponentBias) << ExponentShift; + return mozilla::BitwiseCast<double>(signBit | exponentBits | significandBits); +} + +int8_t BigInt::compare(BigInt* x, BigInt* y) { + // Sanity checks to catch negative zeroes escaping to the wild. + MOZ_ASSERT(!x->isNegative() || !x->isZero()); + MOZ_ASSERT(!y->isNegative() || !y->isZero()); + + bool xSign = x->isNegative(); + + if (xSign != y->isNegative()) { + return xSign ? -1 : 1; + } + + if (xSign) { + mozilla::Swap(x, y); + } + + return absoluteCompare(x, y); +} + +bool BigInt::equal(BigInt* lhs, BigInt* rhs) { + if (lhs == rhs) { + return true; + } + if (lhs->digitLength() != rhs->digitLength()) { + return false; + } + if (lhs->isNegative() != rhs->isNegative()) { + return false; + } + for (size_t i = 0; i < lhs->digitLength(); i++) { + if (lhs->digit(i) != rhs->digit(i)) { + return false; + } + } + return true; +} + +int8_t BigInt::compare(BigInt* x, double y) { + MOZ_ASSERT(!mozilla::IsNaN(y)); + + constexpr int LessThan = -1, Equal = 0, GreaterThan = 1; + + // ±Infinity exceeds a finite bigint value. + if (!mozilla::IsFinite(y)) { + return y > 0 ? LessThan : GreaterThan; + } + + // Handle `x === 0n` and `y == 0` special cases. + if (x->isZero()) { + if (y == 0) { + // -0 and +0 are treated identically. + return Equal; + } + + return y > 0 ? LessThan : GreaterThan; + } + + const bool xNegative = x->isNegative(); + if (y == 0) { + return xNegative ? LessThan : GreaterThan; + } + + // Nonzero `x` and `y` with different signs are trivially compared. + const bool yNegative = y < 0; + if (xNegative != yNegative) { + return xNegative ? LessThan : GreaterThan; + } + + // `x` and `y` are same-signed. Determine which has greater magnitude, + // then combine that with the signedness just computed to reach a result. + const int exponent = mozilla::ExponentComponent(y); + if (exponent < 0) { + // `y` is a nonzero fraction of magnitude less than 1. + return xNegative ? LessThan : GreaterThan; + } + + size_t xLength = x->digitLength(); + MOZ_ASSERT(xLength > 0); + + Digit xMSD = x->digit(xLength - 1); + const int shift = DigitLeadingZeroes(xMSD); + int xBitLength = xLength * DigitBits - shift; + + // Differing bit-length makes for a simple comparison. + int yBitLength = exponent + 1; + if (xBitLength < yBitLength) { + return xNegative ? GreaterThan : LessThan; + } + if (xBitLength > yBitLength) { + return xNegative ? LessThan : GreaterThan; + } + + // Compare the high 64 bits of both numbers. (Lower-order bits not present + // in either number are zeroed.) Either that distinguishes `x` and `y`, or + // `x` and `y` differ only if a subsequent nonzero bit in `x` means `x` has + // larger magnitude. + + using Double = mozilla::FloatingPoint<double>; + constexpr uint8_t SignificandWidth = Double::kSignificandWidth; + constexpr uint64_t SignificandBits = Double::kSignificandBits; + + const uint64_t doubleBits = mozilla::BitwiseCast<uint64_t>(y); + const uint64_t significandBits = doubleBits & SignificandBits; + + // Readd the implicit-one bit when constructing `y`'s high 64 bits. + const uint64_t yHigh64Bits = + ((uint64_t(1) << SignificandWidth) | significandBits) + << (64 - SignificandWidth - 1); + + // Cons up `x`'s high 64 bits, backfilling zeroes for binary fractions of 1 + // if `x` doesn't have 64 bits. + uint8_t xBitsFilled = DigitBits - shift; + uint64_t xHigh64Bits = uint64_t(xMSD) << (64 - xBitsFilled); + + // At this point we no longer need to look at the most significant digit. + xLength--; + + // The high 64 bits from `x` will probably not align to a digit boundary. + // `xHasNonZeroLeftoverBits` will be set to true if any remaining + // least-significant bit from the digit holding xHigh64Bits's + // least-significant bit is nonzero. + bool xHasNonZeroLeftoverBits = false; + + if (xBitsFilled < std::min(xBitLength, 64)) { + MOZ_ASSERT(xLength >= 1, + "If there are more bits to fill, there should be " + "more digits to fill them from"); + + Digit second = x->digit(--xLength); + if (DigitBits == 32) { + xBitsFilled += 32; + xHigh64Bits |= uint64_t(second) << (64 - xBitsFilled); + if (xBitsFilled < 64 && xLength >= 1) { + Digit third = x->digit(--xLength); + const uint8_t neededBits = 64 - xBitsFilled; + xHigh64Bits |= uint64_t(third) >> (DigitBits - neededBits); + xHasNonZeroLeftoverBits = (third << neededBits) != 0; + } + } else { + const uint8_t neededBits = 64 - xBitsFilled; + xHigh64Bits |= uint64_t(second) >> (DigitBits - neededBits); + xHasNonZeroLeftoverBits = (second << neededBits) != 0; + } + } + + // If high bits are unequal, the larger one has greater magnitude. + if (yHigh64Bits > xHigh64Bits) { + return xNegative ? GreaterThan : LessThan; + } + if (xHigh64Bits > yHigh64Bits) { + return xNegative ? LessThan : GreaterThan; + } + + // Otherwise the top 64 bits of both are equal. If the values differ, a + // lower-order bit in `x` is nonzero and `x` has greater magnitude than + // `y`; otherwise `x == y`. + if (xHasNonZeroLeftoverBits) { + return xNegative ? LessThan : GreaterThan; + } + while (xLength != 0) { + if (x->digit(--xLength) != 0) { + return xNegative ? LessThan : GreaterThan; + } + } + + return Equal; +} + +bool BigInt::equal(BigInt* lhs, double rhs) { + if (mozilla::IsNaN(rhs)) { + return false; + } + return compare(lhs, rhs) == 0; +} + +// BigInt proposal section 3.2.5 +JS::Result<bool> BigInt::looselyEqual(ExclusiveContext* cx, HandleBigInt lhs, + HandleValue rhs) { + // Step 1. + if (rhs.isBigInt()) { + return equal(lhs, rhs.toBigInt()); + } + + // Steps 2-5 (not applicable). + + // Steps 6-7. + if (rhs.isString()) { + RootedBigInt rhsBigInt(cx); + RootedString rhsString(cx, rhs.toString()); + MOZ_TRY_VAR(rhsBigInt, StringToBigInt(cx, rhsString)); + if (!rhsBigInt) { + return false; + } + return equal(lhs, rhsBigInt); + } + + // Steps 8-9 (not applicable). + + // Steps 10-11. + if (rhs.isObject()) { + RootedValue rhsPrimitive(cx, rhs); + if (!cx->isJSContext() || !ToPrimitive(cx->asJSContext(), &rhsPrimitive)) { + return cx->alreadyReportedError(); + } + return looselyEqual(cx, lhs, rhsPrimitive); + } + + // Step 12. + if (rhs.isNumber()) { + return equal(lhs, rhs.toNumber()); + } + + // Step 13. + return false; +} + +// BigInt proposal section 1.1.12. BigInt::lessThan ( x, y ) +bool BigInt::lessThan(BigInt* x, BigInt* y) { return compare(x, y) < 0; } + +Maybe<bool> BigInt::lessThan(BigInt* lhs, double rhs) { + if (mozilla::IsNaN(rhs)) { + return Maybe<bool>(Nothing()); + } + return Some(compare(lhs, rhs) < 0); +} + +Maybe<bool> BigInt::lessThan(double lhs, BigInt* rhs) { + if (mozilla::IsNaN(lhs)) { + return Maybe<bool>(Nothing()); + } + return Some(-compare(rhs, lhs) < 0); +} + +bool BigInt::lessThan(ExclusiveContext* cx, HandleBigInt lhs, HandleString rhs, + Maybe<bool>& res) { + RootedBigInt rhsBigInt(cx); + JS_TRY_VAR_OR_RETURN_FALSE(cx, rhsBigInt, StringToBigInt(cx, rhs)); + if (!rhsBigInt) { + res = Nothing(); + return true; + } + res = Some(lessThan(lhs, rhsBigInt)); + return true; +} + +bool BigInt::lessThan(ExclusiveContext* cx, HandleString lhs, HandleBigInt rhs, + Maybe<bool>& res) { + RootedBigInt lhsBigInt(cx); + JS_TRY_VAR_OR_RETURN_FALSE(cx, lhsBigInt, StringToBigInt(cx, lhs)); + if (!lhsBigInt) { + res = Nothing(); + return true; + } + res = Some(lessThan(lhsBigInt, rhs)); + return true; +} + +bool BigInt::lessThan(ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + Maybe<bool>& res) { + if (lhs.isBigInt()) { + if (rhs.isString()) { + RootedBigInt lhsBigInt(cx, lhs.toBigInt()); + RootedString rhsString(cx, rhs.toString()); + return lessThan(cx, lhsBigInt, rhsString, res); + } + + if (rhs.isNumber()) { + res = lessThan(lhs.toBigInt(), rhs.toNumber()); + return true; + } + + MOZ_ASSERT(rhs.isBigInt()); + res = Some(lessThan(lhs.toBigInt(), rhs.toBigInt())); + return true; + } + + MOZ_ASSERT(rhs.isBigInt()); + if (lhs.isString()) { + RootedString lhsString(cx, lhs.toString()); + RootedBigInt rhsBigInt(cx, rhs.toBigInt()); + return lessThan(cx, lhsString, rhsBigInt, res); + } + + MOZ_ASSERT(lhs.isNumber()); + res = lessThan(lhs.toNumber(), rhs.toBigInt()); + return true; +} + +JSLinearString* BigInt::toString(ExclusiveContext* cx, HandleBigInt x, uint8_t radix) { + MOZ_ASSERT(2 <= radix && radix <= 36); + + if (x->isZero()) { + return cx->staticStrings().getInt(0); + } + + if (mozilla::IsPowerOfTwo(radix)) { + return toStringBasePowerOfTwo(cx, x, radix); + } + + return toStringGeneric(cx, x, radix); +} + +template <typename CharT> +static inline BigInt* ParseStringBigIntLiteral(ExclusiveContext* cx, + Range<const CharT> range, + bool* haveParseError) { + auto start = range.begin(); + auto end = range.end(); + + while (start < end && unicode::IsSpace(start[0])) { + start++; + } + + while (start < end && unicode::IsSpace(end[-1])) { + end--; + } + + if (start == end) { + return BigInt::zero(cx); + } + + // StringNumericLiteral ::: StrDecimalLiteral, but without Infinity, decimal + // points, or exponents. Note that the raw '+' or '-' cases fall through + // because the string is too short, and eventually signal a parse error. + if (end - start > 1) { + if (start[0] == '+') { + bool isNegative = false; + start++; + return BigInt::parseLiteralDigits(cx, Range<const CharT>(start, end), 10, + isNegative, haveParseError); + } else if (start[0] == '-') { + bool isNegative = true; + start++; + return BigInt::parseLiteralDigits(cx, Range<const CharT>(start, end), 10, + isNegative, haveParseError); + } + } + + return BigInt::parseLiteral(cx, Range<const CharT>(start, end), + haveParseError); +} + +// Called from BigInt constructor. +JS::Result<BigInt*, JS::OOM&> js::StringToBigInt(ExclusiveContext* cx, + HandleString str) { + JSLinearString* linear = str->ensureLinear(cx); + if (!linear) { + return cx->alreadyReportedOOM(); + } + + BigInt* res = nullptr; + bool parseError = false; + + if(cx->isJSContext()) { + AutoStableStringChars chars(cx->asJSContext()); + if (!chars.init(cx->asJSContext(), str)) { + return cx->alreadyReportedOOM(); + } + + if (chars.isLatin1()) { + res = ParseStringBigIntLiteral(cx->asJSContext(), chars.latin1Range(), &parseError); + } else { + res = ParseStringBigIntLiteral(cx->asJSContext(), chars.twoByteRange(), &parseError); + } + } + + // A nullptr result can indicate either a parse error or out-of-memory. + if (!res && !parseError) { + return cx->alreadyReportedOOM(); + } + + return res; +} + +// Called from parser with already trimmed and validated token. +BigInt* js::ParseBigIntLiteral(ExclusiveContext* cx, + const Range<const char16_t>& chars) { + bool parseError = false; + BigInt* res = BigInt::parseLiteral(cx, chars, &parseError); + if (!res) { + return nullptr; + } + MOZ_RELEASE_ASSERT(!parseError); + return res; +} + +JSAtom* js::BigIntToAtom(ExclusiveContext* cx, HandleBigInt bi) { + JSString* str = BigInt::toString(cx, bi, 10); + if (!str) { + return nullptr; + } + return AtomizeString(cx, str); +} + +JS::ubi::Node::Size JS::ubi::Concrete<BigInt>::size( + mozilla::MallocSizeOf mallocSizeOf) const { + BigInt& bi = get(); + MOZ_ASSERT(bi.isTenured()); + size_t size = js::gc::Arena::thingSize(bi.asTenured().getAllocKind()); + size += bi.sizeOfExcludingThis(mallocSizeOf); + return size; +} + +template <XDRMode mode> +bool js::XDRBigInt(XDRState<mode>* xdr, MutableHandleBigInt bi) { + ExclusiveContext* cx = xdr->cx(); + + uint8_t sign; + uint32_t length; + + if (mode == XDR_ENCODE) { + sign = static_cast<uint8_t>(bi->isNegative()); + uint64_t sz = bi->digitLength() * sizeof(BigInt::Digit); + // As the maximum source code size is currently UINT32_MAX code units + // (see BytecodeCompiler::checkLength), any bigint literal's length in + // word-sized digits will be less than UINT32_MAX as well. That could + // change or FoldConstants could start creating these though, so leave + // this as a release-enabled assert. + MOZ_RELEASE_ASSERT(sz <= UINT32_MAX); + length = static_cast<uint32_t>(sz); + } + + if(!xdr->codeUint8(&sign)) + return false; + if(!xdr->codeUint32(&length)) + return false; + + MOZ_RELEASE_ASSERT(length % sizeof(BigInt::Digit) == 0); + uint32_t digitLength = length / sizeof(BigInt::Digit); + auto buf = cx->make_pod_array<BigInt::Digit>(digitLength); + if (!buf) { + return xdr->fail(JS::TranscodeResult_Throw); + } + + if (mode == XDR_ENCODE) { + std::uninitialized_copy_n(bi->digits().Elements(), digitLength, buf.get()); + } + + if(!xdr->codeBytes(buf.get(), length)) + return false; + + if (mode == XDR_DECODE) { + BigInt* res = BigInt::createUninitialized(cx, digitLength, sign); + if (!res) { + return xdr->fail(JS::TranscodeResult_Throw); + } + std::uninitialized_copy_n(buf.get(), digitLength, bi->digits().Elements()); + bi.set(res); + } + + return true; +} + +template bool js::XDRBigInt(XDRState<XDR_ENCODE>* xdr, MutableHandleBigInt bi); + +template bool js::XDRBigInt(XDRState<XDR_DECODE>* xdr, MutableHandleBigInt bi); + diff --git a/js/src/vm/BigIntType.h b/js/src/vm/BigIntType.h new file mode 100644 index 0000000000..ea0317fd9c --- /dev/null +++ b/js/src/vm/BigIntType.h @@ -0,0 +1,386 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +#ifndef vm_BigIntType_h +#define vm_BigIntType_h + +#include "mozilla/Range.h" +#include "mozilla/Span.h" + +#include "gc/Barrier.h" +#include "gc/Marking.h" +#include "gc/Heap.h" +#include "js/GCHashTable.h" +#include "js/RootingAPI.h" +#include "js/TraceKind.h" +#include "js/TypeDecls.h" +#include "vm/String.h" +#include "vm/Xdr.h" + +// Handle future js::gc::Cell::ReservedBits, we have no reserved bits... +#define js_gc_Cell_ReservedBits 0 +// Handle future js::gc::MinCellSize, 16 bytes, twice our js:gc:CellSize +#define js_gc_MinCellSize (js::gc::CellSize*2) + +namespace JS { + +class BigInt; + +} // namespace JS + +namespace js { + +template <XDRMode mode> +bool XDRBigInt(XDRState<mode>* xdr, MutableHandle<JS::BigInt*> bi); + +} // namespace js + +namespace JS { + +class BigInt final : public js::gc::TenuredCell { + public: + using Digit = uintptr_t; + + private: + // The low js::gc::Cell::ReservedBits are reserved. + static constexpr uintptr_t SignBit = JS_BIT(js_gc_Cell_ReservedBits); + static constexpr uintptr_t LengthShift = js_gc_Cell_ReservedBits + 1; + static constexpr size_t InlineDigitsLength = + (js_gc_MinCellSize - sizeof(uintptr_t)) / sizeof(Digit); + + uintptr_t lengthSignAndReservedBits_; + + // The digit storage starts with the least significant digit (little-endian + // digit order). Byte order within a digit is of course native endian. + union { + Digit* heapDigits_; + Digit inlineDigits_[InlineDigitsLength]; + }; + + public: + static const JS::TraceKind TraceKind = JS::TraceKind::BigInt; + + size_t digitLength() const { + return lengthSignAndReservedBits_ >> LengthShift; + } + + bool hasInlineDigits() const { return digitLength() <= InlineDigitsLength; } + bool hasHeapDigits() const { return !hasInlineDigits(); } + + using Digits = mozilla::Span<Digit>; + Digits digits() { + return Digits(hasInlineDigits() ? inlineDigits_ : heapDigits_, + digitLength()); + } + Digit digit(size_t idx) { return digits()[idx]; } + void setDigit(size_t idx, Digit digit) { digits()[idx] = digit; } + + bool isZero() const { return digitLength() == 0; } + bool isNegative() const { return lengthSignAndReservedBits_ & SignBit; } + + // Offset for direct access from JIT code. + static constexpr size_t offsetOfLengthSignAndReservedBits() { + return offsetof(BigInt, lengthSignAndReservedBits_); + } + + void initializeDigitsToZero(); + + void traceChildren(JSTracer* trc); + void finalize(js::FreeOp* fop); + js::HashNumber hash(); + size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + static BigInt* createUninitialized(js::ExclusiveContext* cx, size_t length, + bool isNegative); + static BigInt* createFromDouble(js::ExclusiveContext* cx, double d); + static BigInt* createFromUint64(js::ExclusiveContext* cx, uint64_t n); + static BigInt* createFromInt64(js::ExclusiveContext* cx, int64_t n); + // FIXME: Cache these values. + static BigInt* zero(js::ExclusiveContext* cx); + static BigInt* one(js::ExclusiveContext* cx); + + static BigInt* copy(js::ExclusiveContext* cx, Handle<BigInt*> x); + static BigInt* add(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* sub(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* mul(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* div(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* mod(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* pow(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* neg(js::ExclusiveContext* cx, Handle<BigInt*> x); + static BigInt* lsh(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* rsh(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* bitAnd(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* bitXor(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* bitOr(js::ExclusiveContext* cx, Handle<BigInt*> x, Handle<BigInt*> y); + static BigInt* bitNot(js::ExclusiveContext* cx, Handle<BigInt*> x); + + static int64_t toInt64(BigInt* x); + static uint64_t toUint64(BigInt* x); + + // Return true if the BigInt is without loss of precision representable as an + // int64 and store the int64 value in the output. Otherwise return false and + // leave the value of the output parameter unspecified. + static bool isInt64(BigInt* x, int64_t* result); + + static BigInt* asIntN(js::ExclusiveContext* cx, Handle<BigInt*> x, uint64_t bits); + static BigInt* asUintN(js::ExclusiveContext* cx, Handle<BigInt*> x, uint64_t bits); + + // Type-checking versions of arithmetic operations. These methods + // must be called with at least one BigInt operand. Binary + // operations will throw a TypeError if one of the operands is not a + // BigInt value. + static bool add(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool sub(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool mul(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool div(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool mod(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool pow(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool neg(js::ExclusiveContext* cx, Handle<Value> operand, + MutableHandle<Value> res); + static bool lsh(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool rsh(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool bitAnd(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool bitXor(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool bitOr(js::ExclusiveContext* cx, Handle<Value> lhs, Handle<Value> rhs, + MutableHandle<Value> res); + static bool bitNot(js::ExclusiveContext* cx, Handle<Value> operand, + MutableHandle<Value> res); + + static double numberValue(BigInt* x); + + static JSLinearString* toString(js::ExclusiveContext* cx, Handle<BigInt*> x, + uint8_t radix); + template <typename CharT> + static BigInt* parseLiteral(js::ExclusiveContext* cx, + const mozilla::Range<const CharT> chars, + bool* haveParseError); + template <typename CharT> + static BigInt* parseLiteralDigits(js::ExclusiveContext* cx, + const mozilla::Range<const CharT> chars, + unsigned radix, bool isNegative, + bool* haveParseError); + + static int8_t compare(BigInt* lhs, BigInt* rhs); + static bool equal(BigInt* lhs, BigInt* rhs); + static JS::Result<bool> looselyEqual(js::ExclusiveContext* cx, Handle<BigInt*> lhs, + HandleValue rhs); + + static bool lessThan(BigInt* x, BigInt* y); + // These methods return Nothing when the non-BigInt operand is NaN + // or a string that can't be interpreted as a BigInt. + static mozilla::Maybe<bool> lessThan(BigInt* lhs, double rhs); + static mozilla::Maybe<bool> lessThan(double lhs, BigInt* rhs); + static bool lessThan(js::ExclusiveContext* cx, Handle<BigInt*> lhs, HandleString rhs, + mozilla::Maybe<bool>& res); + static bool lessThan(js::ExclusiveContext* cx, HandleString lhs, Handle<BigInt*> rhs, + mozilla::Maybe<bool>& res); + static bool lessThan(js::ExclusiveContext* cx, HandleValue lhs, HandleValue rhs, + mozilla::Maybe<bool>& res); + + private: + static constexpr size_t DigitBits = sizeof(Digit) * CHAR_BIT; + static constexpr size_t HalfDigitBits = DigitBits / 2; + static constexpr Digit HalfDigitMask = (1ull << HalfDigitBits) - 1; + + static_assert(DigitBits == 32 || DigitBits == 64, + "Unexpected BigInt Digit size"); + + // The maximum number of digits that the current implementation supports + // would be 0x7fffffff / DigitBits. However, we use a lower limit for now, + // because raising it later is easier than lowering it. Support up to 1 + // million bits. + static constexpr size_t MaxBitLength = 1024 * 1024; + static constexpr size_t MaxDigitLength = MaxBitLength / DigitBits; + + // BigInts can be serialized to strings of radix between 2 and 36. For a + // given bigint, radix 2 will take the most characters (one per bit). + // Ensure that the max bigint size is small enough so that we can fit the + // corresponding character count into a size_t, with space for a possible + // sign prefix. + static_assert(MaxBitLength <= std::numeric_limits<size_t>::max() - 1, + "BigInt max length must be small enough to be serialized as a " + "binary string"); + + static size_t calculateMaximumCharactersRequired(HandleBigInt x, + unsigned radix); + static MOZ_MUST_USE bool calculateMaximumDigitsRequired(js::ExclusiveContext* cx, + uint8_t radix, + size_t charCount, + size_t* result); + + static bool absoluteDivWithDigitDivisor( + js::ExclusiveContext* cx, Handle<BigInt*> x, Digit divisor, + const mozilla::Maybe<MutableHandle<BigInt*>>& quotient, Digit* remainder, + bool quotientNegative); + static void internalMultiplyAdd(BigInt* source, Digit factor, Digit summand, + unsigned, BigInt* result); + static void multiplyAccumulate(BigInt* multiplicand, Digit multiplier, + BigInt* accumulator, + unsigned accumulatorIndex); + static bool absoluteDivWithBigIntDivisor( + js::ExclusiveContext* cx, Handle<BigInt*> dividend, Handle<BigInt*> divisor, + const mozilla::Maybe<MutableHandle<BigInt*>>& quotient, + const mozilla::Maybe<MutableHandle<BigInt*>>& remainder, + bool quotientNegative); + + enum class LeftShiftMode { SameSizeResult, AlwaysAddOneDigit }; + + static BigInt* absoluteLeftShiftAlwaysCopy(js::ExclusiveContext* cx, Handle<BigInt*> x, + unsigned shift, LeftShiftMode); + static bool productGreaterThan(Digit factor1, Digit factor2, Digit high, + Digit low); + static BigInt* lshByAbsolute(js::ExclusiveContext* cx, HandleBigInt x, HandleBigInt y); + static BigInt* rshByAbsolute(js::ExclusiveContext* cx, HandleBigInt x, HandleBigInt y); + static BigInt* rshByMaximum(js::ExclusiveContext* cx, bool isNegative); + static BigInt* truncateAndSubFromPowerOfTwo(js::ExclusiveContext* cx, HandleBigInt x, + uint64_t bits, + bool resultNegative); + + Digit absoluteInplaceAdd(BigInt* summand, unsigned startIndex); + Digit absoluteInplaceSub(BigInt* subtrahend, unsigned startIndex); + void inplaceRightShiftLowZeroBits(unsigned shift); + void inplaceMultiplyAdd(Digit multiplier, Digit part); + + // The result of an SymmetricTrim bitwise op has as many digits as the + // smaller operand. A SymmetricFill bitwise op result has as many digits as + // the larger operand, with high digits (if any) copied from the larger + // operand. AsymmetricFill is like SymmetricFill, except the result has as + // many digits as the first operand; this kind is used for the and-not + // operation. + enum class BitwiseOpKind { SymmetricTrim, SymmetricFill, AsymmetricFill }; + + template <BitwiseOpKind kind, typename BitwiseOp> + static BigInt* absoluteBitwiseOp(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y, BitwiseOp&& op); + + // Return `|x| & |y|`. + static BigInt* absoluteAnd(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y); + + // Return `|x| | |y|`. + static BigInt* absoluteOr(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y); + + // Return `|x| & ~|y|`. + static BigInt* absoluteAndNot(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y); + + // Return `|x| ^ |y|`. + static BigInt* absoluteXor(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y); + + // Return `(|x| + 1) * (resultNegative ? -1 : +1)`. + static BigInt* absoluteAddOne(js::ExclusiveContext* cx, Handle<BigInt*> x, + bool resultNegative); + + // Return `(|x| - 1) * (resultNegative ? -1 : +1)`, with the precondition that + // |x| != 0. + static BigInt* absoluteSubOne(js::ExclusiveContext* cx, Handle<BigInt*> x, + unsigned resultLength); + + // Return `a + b`, incrementing `*carry` if the addition overflows. + static inline Digit digitAdd(Digit a, Digit b, Digit* carry) { + Digit result = a + b; + *carry += static_cast<Digit>(result < a); + return result; + } + + // Return `left - right`, incrementing `*borrow` if the addition overflows. + static inline Digit digitSub(Digit left, Digit right, Digit* borrow) { + Digit result = left - right; + *borrow += static_cast<Digit>(result > left); + return result; + } + + // Compute `a * b`, returning the low half of the result and putting the + // high half in `*high`. + static Digit digitMul(Digit a, Digit b, Digit* high); + + // Divide `(high << DigitBits) + low` by `divisor`, returning the quotient + // and storing the remainder in `*remainder`, with the precondition that + // `high < divisor` so that the result fits in a Digit. + static Digit digitDiv(Digit high, Digit low, Digit divisor, Digit* remainder); + + // Return `(|x| + |y|) * (resultNegative ? -1 : +1)`. + static BigInt* absoluteAdd(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y, bool resultNegative); + + // Return `(|x| - |y|) * (resultNegative ? -1 : +1)`, with the precondition + // that |x| >= |y|. + static BigInt* absoluteSub(js::ExclusiveContext* cx, Handle<BigInt*> x, + Handle<BigInt*> y, bool resultNegative); + + // If `|x| < |y|` return -1; if `|x| == |y|` return 0; otherwise return 1. + static int8_t absoluteCompare(BigInt* lhs, BigInt* rhs); + + static int8_t compare(BigInt* lhs, double rhs); + + static bool equal(BigInt* lhs, double rhs); + + static JSLinearString* toStringBasePowerOfTwo(js::ExclusiveContext* cx, Handle<BigInt*>, + unsigned radix); + static JSLinearString* toStringGeneric(js::ExclusiveContext* cx, Handle<BigInt*>, + unsigned radix); + + static BigInt* trimHighZeroDigits(js::ExclusiveContext* cx, Handle<BigInt*> x); + static BigInt* destructivelyTrimHighZeroDigits(js::ExclusiveContext* cx, + Handle<BigInt*> x); + + friend struct JSStructuredCloneReader; + friend struct JSStructuredCloneWriter; + template <js::XDRMode mode> + friend bool js::XDRBigInt(js::XDRState<mode>* xdr, MutableHandle<BigInt*> bi); + + BigInt() = delete; + BigInt(const BigInt& other) = delete; + void operator=(const BigInt& other) = delete; +}; + +static_assert( + sizeof(BigInt) >= js_gc_MinCellSize, + "sizeof(BigInt) must be greater than the minimum allocation size"); + +static_assert( + sizeof(BigInt) == js_gc_MinCellSize, + "sizeof(BigInt) intended to be the same as the minimum allocation size"); + +} // namespace JS + +namespace js { + +extern JSAtom* BigIntToAtom(js::ExclusiveContext* cx, JS::HandleBigInt bi); + +extern JS::BigInt* NumberToBigInt(js::ExclusiveContext* cx, double d); +extern JS::Result<int64_t> ToBigInt64(JSContext* cx, JS::Handle<JS::Value> v); +extern JS::Result<uint64_t> ToBigUint64(JSContext* cx, JS::Handle<JS::Value> v); + +// Parse a BigInt from a string, using the method specified for StringToBigInt. +// Used by the BigInt constructor among other places. +extern JS::Result<JS::BigInt*, JS::OOM&> StringToBigInt( + js::ExclusiveContext* cx, JS::Handle<JSString*> str); + +// Parse a BigInt from an already-validated numeric literal. Used by the +// parser. Can only fail in out-of-memory situations. +extern JS::BigInt* ParseBigIntLiteral( + js::ExclusiveContext* cx, const mozilla::Range<const char16_t>& chars); + +extern JS::BigInt* ToBigInt(js::ExclusiveContext* cx, JS::Handle<JS::Value> v); + +} // namespace js + +#undef js_gc_MinCellSize +#undef js_gc_Cell_ReservedBits + +#endif diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index d5e7a2d058..f05a4db9be 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -36,6 +36,8 @@ macro(AsyncWrapped, AsyncWrapped, "AsyncWrapped") \ macro(async, async, "async") \ macro(await, await, "await") \ + macro(bigint64, bigint64, "bigint64") \ + macro(biguint64, biguint64, "biguint64") \ macro(Bool8x16, Bool8x16, "Bool8x16") \ macro(Bool16x8, Bool16x8, "Bool16x8") \ macro(Bool32x4, Bool32x4, "Bool32x4") \ @@ -275,6 +277,7 @@ macro(objectArguments, objectArguments, "[object Arguments]") \ macro(objectArray, objectArray, "[object Array]") \ macro(objectBoolean, objectBoolean, "[object Boolean]") \ + macro(objectBigInt, objectBigInt, "[object BigInt]") \ macro(objectDate, objectDate, "[object Date]") \ macro(objectError, objectError, "[object Error]") \ macro(objectFunction, objectFunction, "[object Function]") \ @@ -424,5 +427,6 @@ macro(boolean, boolean, "boolean") \ macro(null, null, "null") \ macro(symbol, symbol, "symbol") \ + macro(bigint, bigint, "bigint") \ #endif /* vm_CommonPropertyNames_h */ diff --git a/js/src/vm/EqualityOperations.cpp b/js/src/vm/EqualityOperations.cpp index 6f90450b49..91a9ca06c7 100644 --- a/js/src/vm/EqualityOperations.cpp +++ b/js/src/vm/EqualityOperations.cpp @@ -31,6 +31,10 @@ EqualGivenSameType(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bo *equal = (lval.toDouble() == rval.toDouble()); return true; } + if (lval.isBigInt()) { + *equal = JS::BigInt::equal(lval.toBigInt(), rval.toBigInt()); + return true; + } if (lval.isGCThing()) { // objects or symbols *equal = (lval.toGCThing() == rval.toGCThing()); return true; @@ -134,6 +138,22 @@ js::LooselyEqual(JSContext* cx, JS::HandleValue lval, JS::HandleValue rval, bool return js::LooselyEqual(cx, lvalue, rval, result); } + if (lval.isBigInt()) { + RootedBigInt lbi(cx, lval.toBigInt()); + bool tmpResult; + JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult, BigInt::looselyEqual(cx, lbi, rval)); + *result = tmpResult; + return true; + } + + if (rval.isBigInt()) { + RootedBigInt rbi(cx, rval.toBigInt()); + bool tmpResult; + JS_TRY_VAR_OR_RETURN_FALSE(cx, tmpResult, BigInt::looselyEqual(cx, rbi, lval)); + *result = tmpResult; + return true; + } + // Step 12. *result = false; return true; diff --git a/js/src/vm/GlobalObject.cpp b/js/src/vm/GlobalObject.cpp index 542160ce56..b7d3344b3e 100644 --- a/js/src/vm/GlobalObject.cpp +++ b/js/src/vm/GlobalObject.cpp @@ -15,6 +15,7 @@ #include "jsweakmap.h" #include "builtin/AtomicsObject.h" +#include "builtin/BigInt.h" #include "builtin/Eval.h" #include "builtin/MapObject.h" #include "builtin/ModuleObject.h" diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 1e10fe5da3..c4d9cf7287 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -82,6 +82,8 @@ class GlobalObject : public NativeObject FROM_BUFFER_INT16, FROM_BUFFER_UINT32, FROM_BUFFER_INT32, + FROM_BUFFER_UINT64, + FROM_BUFFER_INT64, FROM_BUFFER_FLOAT32, FROM_BUFFER_FLOAT64, FROM_BUFFER_UINT8CLAMPED, @@ -959,6 +961,20 @@ GlobalObject::setCreateArrayFromBuffer<int32_t>(Handle<JSFunction*> fun) template<> inline void +GlobalObject::setCreateArrayFromBuffer<uint64_t>(Handle<JSFunction*> fun) +{ + setCreateArrayFromBufferHelper(FROM_BUFFER_UINT64, fun); +} + +template<> +inline void +GlobalObject::setCreateArrayFromBuffer<int64_t>(Handle<JSFunction*> fun) +{ + setCreateArrayFromBufferHelper(FROM_BUFFER_INT64, fun); +} + +template<> +inline void GlobalObject::setCreateArrayFromBuffer<float>(Handle<JSFunction*> fun) { setCreateArrayFromBufferHelper(FROM_BUFFER_FLOAT32, fun); @@ -1022,6 +1038,20 @@ GlobalObject::createArrayFromBuffer<int32_t>() const template<> inline Value +GlobalObject::createArrayFromBuffer<uint64_t>() const +{ + return createArrayFromBufferHelper(FROM_BUFFER_UINT64); +} + +template<> +inline Value +GlobalObject::createArrayFromBuffer<int64_t>() const +{ + return createArrayFromBufferHelper(FROM_BUFFER_INT64); +} + +template<> +inline Value GlobalObject::createArrayFromBuffer<float>() const { return createArrayFromBufferHelper(FROM_BUFFER_FLOAT32); diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 79a4b90200..a48c753f1d 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -8,6 +8,8 @@ #include "vm/Interpreter.h" +#include "mozilla/Maybe.h" + #include "jscompartment.h" #include "jsnum.h" #include "jsstr.h" @@ -390,7 +392,7 @@ DefVarOperation(JSContext* cx, HandleObject varobj, HandlePropertyName dn, unsig } static MOZ_ALWAYS_INLINE bool -NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue val, +NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, MutableHandleValue val, MutableHandleValue res) { /* @@ -401,13 +403,16 @@ NegOperation(JSContext* cx, HandleScript script, jsbytecode* pc, HandleValue val int32_t i; if (val.isInt32() && (i = val.toInt32()) != 0 && i != INT32_MIN) { res.setInt32(-i); - } else { - double d; - if (!ToNumber(cx, val, &d)) - return false; - res.setNumber(-d); + return true; } + if (!ToNumeric(cx, val)) + return false; + + if (val.isBigInt()) + return BigInt::neg(cx, val, res); + + res.setNumber(-val.toNumber()); return true; } @@ -657,120 +662,268 @@ ProcessCallSiteObjOperation(JSContext* cx, RootedObject& cso, RootedObject& raw, return true; } -#define RELATIONAL_OP(OP) \ - JS_BEGIN_MACRO \ - /* Optimize for two int-tagged operands (typical loop control). */ \ - if (lhs.isInt32() && rhs.isInt32()) { \ - *res = lhs.toInt32() OP rhs.toInt32(); \ - } else { \ - if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) \ - return false; \ - if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) \ - return false; \ - if (lhs.isString() && rhs.isString()) { \ - JSString* l = lhs.toString(); \ - JSString* r = rhs.toString(); \ - int32_t result; \ - if (!CompareStrings(cx, l, r, &result)) \ - return false; \ - *res = result OP 0; \ - } else { \ - double l, r; \ - if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r)) \ - return false; \ - *res = (l OP r); \ - } \ - } \ - return true; \ - JS_END_MACRO +// BigInt proposal 3.2.4 Abstract Relational Comparison +// Returns Nothing when at least one operand is a NaN, or when +// ToNumeric or StringToBigInt can't interpret a string as a numeric +// value. (These cases correspond to a NaN result in the spec.) +// Otherwise, return a boolean to indicate whether lhs is less than +// rhs. The operands must be primitives; the caller is responsible for +// evaluating them in the correct order. +static MOZ_ALWAYS_INLINE bool +LessThanImpl(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, + mozilla::Maybe<bool>& res) +{ + // Steps 1 and 2 are performed by the caller. + + // Step 3. + if (lhs.isString() && rhs.isString()) { + JSString* l = lhs.toString(); + JSString* r = rhs.toString(); + int32_t result; + if (!CompareStrings(cx, l, r, &result)) { + return false; + } + res = mozilla::Some(result < 0); + return true; + } + + // Step 4a. + if (lhs.isBigInt() && rhs.isString()) { + return BigInt::lessThan(cx, lhs, rhs, res); + } + // Step 4b. + if (lhs.isString() && rhs.isBigInt()) { + return BigInt::lessThan(cx, lhs, rhs, res); + } + + // Steps 4c and 4d. + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { + return false; + } + + // Steps 4e-j. + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::lessThan(cx, lhs, rhs, res); + } + + // Step 4e for Number operands. + MOZ_ASSERT(lhs.isNumber() && rhs.isNumber()); + double lhsNum = lhs.toNumber(); + double rhsNum = rhs.toNumber(); + + if (mozilla::IsNaN(lhsNum) || mozilla::IsNaN(rhsNum)) { + res = mozilla::Maybe<bool>(mozilla::Nothing()); + return true; + } + + res = mozilla::Some(lhsNum < rhsNum); + return true; +} static MOZ_ALWAYS_INLINE bool -LessThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(<); +LessThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() < rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe<bool> tmpResult; + if (!LessThanImpl(cx, lhs, rhs, tmpResult)) { + return false; + } + *res = tmpResult.valueOr(false); + return true; } static MOZ_ALWAYS_INLINE bool -LessThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(<=); +LessThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() <= rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe<bool> tmpResult; + if (!LessThanImpl(cx, rhs, lhs, tmpResult)) { + return false; + } + *res = !tmpResult.valueOr(true); + return true; } static MOZ_ALWAYS_INLINE bool -GreaterThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(>); +GreaterThanOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() > rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe<bool> tmpResult; + if (!LessThanImpl(cx, rhs, lhs, tmpResult)) { + return false; + } + *res = tmpResult.valueOr(false); + return true; } static MOZ_ALWAYS_INLINE bool -GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) { - RELATIONAL_OP(>=); +GreaterThanOrEqualOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, bool* res) +{ + if (lhs.isInt32() && rhs.isInt32()) { + *res = lhs.toInt32() >= rhs.toInt32(); + return true; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, lhs)) { + return false; + } + + if (!ToPrimitive(cx, JSTYPE_NUMBER, rhs)) { + return false; + } + + mozilla::Maybe<bool> tmpResult; + if (!LessThanImpl(cx, lhs, rhs, tmpResult)) { + return false; + } + *res = !tmpResult.valueOr(true); + return true; } static MOZ_ALWAYS_INLINE bool -BitNot(JSContext* cx, HandleValue in, int* out) +BitNot(JSContext* cx, MutableHandleValue in, MutableHandleValue out) { - int i; - if (!ToInt32(cx, in, &i)) + if (!ToInt32OrBigInt(cx, in)) { return false; - *out = ~i; + } + + if (in.isBigInt()) { + return BigInt::bitNot(cx, in, out); + } + + out.setInt32(~in.toInt32()); return true; } static MOZ_ALWAYS_INLINE bool -BitXor(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitXor(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = left ^ right; + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::bitXor(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() ^ rhs.toInt32()); return true; } static MOZ_ALWAYS_INLINE bool -BitOr(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitOr(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = left | right; + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::bitOr(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() | rhs.toInt32()); return true; } static MOZ_ALWAYS_INLINE bool -BitAnd(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitAnd(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = left & right; + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::bitAnd(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() & rhs.toInt32()); return true; } static MOZ_ALWAYS_INLINE bool -BitLsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitLsh(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int32_t left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = uint32_t(left) << (right & 31); + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::lsh(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() << (rhs.toInt32() & 31)); return true; } static MOZ_ALWAYS_INLINE bool -BitRsh(JSContext* cx, HandleValue lhs, HandleValue rhs, int* out) +BitRsh(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { - int32_t left, right; - if (!ToInt32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + if (!ToInt32OrBigInt(cx, lhs) || !ToInt32OrBigInt(cx, rhs)) { return false; - *out = left >> (right & 31); + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + return BigInt::rsh(cx, lhs, rhs, out); + } + + out.setInt32(lhs.toInt32() >> (rhs.toInt32() & 31)); return true; } static MOZ_ALWAYS_INLINE bool -UrshOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue out) +UrshOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue out) { + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) { + return false; + } + + if (lhs.isBigInt() || rhs.isBigInt()) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, + JSMSG_BIGINT_TO_NUMBER); + return false; + } + uint32_t left; - int32_t right; - if (!ToUint32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) + int32_t right; + if (!ToUint32(cx, lhs, &left) || !ToInt32(cx, rhs, &right)) { return false; + } left >>= right & 31; out.setNumber(uint32_t(left)); return true; diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 3515a9336b..a03fa847f7 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -40,6 +40,7 @@ #include "jit/IonAnalysis.h" #include "vm/AsyncFunction.h" #include "vm/AsyncIteration.h" +#include "vm/BigIntType.h" #include "vm/Debugger.h" #include "vm/EqualityOperations.h" // js::StrictlyEqual #include "vm/GeneratorObject.h" @@ -812,6 +813,8 @@ js::TypeOfValue(const Value& v) return TypeOfObject(&v.toObject()); if (v.isBoolean()) return JSTYPE_BOOLEAN; + if (v.isBigInt()) + return JSTYPE_BIGINT; MOZ_ASSERT(v.isSymbol()); return JSTYPE_SYMBOL; } @@ -1325,50 +1328,63 @@ AddOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Muta return false; } res.setString(str); - } else { - double l, r; - if (!ToNumber(cx, lhs, &l) || !ToNumber(cx, rhs, &r)) - return false; - res.setNumber(l + r); + return true; } + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) + return false; + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::add(cx, lhs, rhs, res); + + res.setNumber(lhs.toNumber() + rhs.toNumber()); return true; } static MOZ_ALWAYS_INLINE bool -SubOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +SubOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(d1 - d2); + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::sub(cx, lhs, rhs, res); + + res.setNumber(lhs.toNumber() - rhs.toNumber()); return true; } static MOZ_ALWAYS_INLINE bool -MulOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +MulOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(d1 * d2); + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::mul(cx, lhs, rhs, res); + + res.setNumber(lhs.toNumber() * rhs.toNumber()); return true; } static MOZ_ALWAYS_INLINE bool -DivOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +DivOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(NumberDiv(d1, d2)); + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::div(cx, lhs, rhs, res); + + res.setNumber(NumberDiv(lhs.toNumber(), rhs.toNumber())); return true; } static MOZ_ALWAYS_INLINE bool -ModOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue res) +ModOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { int32_t l, r; + if (lhs.isInt32() && rhs.isInt32() && (l = lhs.toInt32()) >= 0 && (r = rhs.toInt32()) > 0) { int32_t mod = l % r; @@ -1376,11 +1392,26 @@ ModOperation(JSContext* cx, HandleValue lhs, HandleValue rhs, MutableHandleValue return true; } - double d1, d2; - if (!ToNumber(cx, lhs, &d1) || !ToNumber(cx, rhs, &d2)) + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) return false; - res.setNumber(NumberMod(d1, d2)); + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::mod(cx, lhs, rhs, res); + + res.setNumber(NumberMod(lhs.toNumber(), rhs.toNumber())); + return true; +} + +static MOZ_ALWAYS_INLINE bool +PowOperation(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) +{ + if (!ToNumeric(cx, lhs) || !ToNumeric(cx, rhs)) + return false; + + if (lhs.isBigInt() || rhs.isBigInt()) + return BigInt::pow(cx, lhs, rhs, res); + + res.setNumber(ecmaPow(lhs.toNumber(), rhs.toNumber())); return true; } @@ -2147,32 +2178,42 @@ CASE(JSOP_BINDVAR) } END_CASE(JSOP_BINDVAR) -#define BITWISE_OP(OP) \ - JS_BEGIN_MACRO \ - int32_t i, j; \ - if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \ - goto error; \ - if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \ - goto error; \ - i = i OP j; \ - REGS.sp--; \ - REGS.sp[-1].setInt32(i); \ - JS_END_MACRO - CASE(JSOP_BITOR) - BITWISE_OP(|); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitOr(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_BITOR) CASE(JSOP_BITXOR) - BITWISE_OP(^); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitXor(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_BITXOR) CASE(JSOP_BITAND) - BITWISE_OP(&); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitAnd(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_BITAND) -#undef BITWISE_OP - CASE(JSOP_EQ) if (!LooseEqualityOp<true>(cx, REGS)) goto error; @@ -2228,8 +2269,9 @@ CASE(JSOP_LT) bool cond; MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); - if (!LessThanOperation(cx, lval, rval, &cond)) + if (!LessThanOperation(cx, lval, rval, &cond)) { goto error; + } TRY_BRANCH_AFTER_COND(cond, 2); REGS.sp[-2].setBoolean(cond); REGS.sp--; @@ -2241,8 +2283,9 @@ CASE(JSOP_LE) bool cond; MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); - if (!LessThanOrEqualOperation(cx, lval, rval, &cond)) + if (!LessThanOrEqualOperation(cx, lval, rval, &cond)) { goto error; + } TRY_BRANCH_AFTER_COND(cond, 2); REGS.sp[-2].setBoolean(cond); REGS.sp--; @@ -2254,8 +2297,9 @@ CASE(JSOP_GT) bool cond; MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); - if (!GreaterThanOperation(cx, lval, rval, &cond)) + if (!GreaterThanOperation(cx, lval, rval, &cond)) { goto error; + } TRY_BRANCH_AFTER_COND(cond, 2); REGS.sp[-2].setBoolean(cond); REGS.sp--; @@ -2267,43 +2311,47 @@ CASE(JSOP_GE) bool cond; MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); - if (!GreaterThanOrEqualOperation(cx, lval, rval, &cond)) + if (!GreaterThanOrEqualOperation(cx, lval, rval, &cond)) { goto error; + } TRY_BRANCH_AFTER_COND(cond, 2); REGS.sp[-2].setBoolean(cond); REGS.sp--; } END_CASE(JSOP_GE) -#define SIGNED_SHIFT_OP(OP) \ - JS_BEGIN_MACRO \ - int32_t i, j; \ - if (!ToInt32(cx, REGS.stackHandleAt(-2), &i)) \ - goto error; \ - if (!ToInt32(cx, REGS.stackHandleAt(-1), &j)) \ - goto error; \ - i = i OP (j & 31); \ - REGS.sp--; \ - REGS.sp[-1].setInt32(i); \ - JS_END_MACRO - CASE(JSOP_LSH) - SIGNED_SHIFT_OP(<<); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitLsh(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_LSH) CASE(JSOP_RSH) - SIGNED_SHIFT_OP(>>); +{ + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-2); + if (!BitRsh(cx, lhs, rhs, res)) { + goto error; + } + REGS.sp--; +} END_CASE(JSOP_RSH) -#undef SIGNED_SHIFT_OP - CASE(JSOP_URSH) { - HandleValue lval = REGS.stackHandleAt(-2); - HandleValue rval = REGS.stackHandleAt(-1); + MutableHandleValue lhs = REGS.stackHandleAt(-2); + MutableHandleValue rhs = REGS.stackHandleAt(-1); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!UrshOperation(cx, lval, rval, res)) + if (!UrshOperation(cx, lhs, rhs, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_URSH) @@ -2313,8 +2361,9 @@ CASE(JSOP_ADD) MutableHandleValue lval = REGS.stackHandleAt(-2); MutableHandleValue rval = REGS.stackHandleAt(-1); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!AddOperation(cx, lval, rval, res)) + if (!AddOperation(cx, lval, rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_ADD) @@ -2324,8 +2373,9 @@ CASE(JSOP_SUB) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!SubOperation(cx, lval, rval, res)) + if (!SubOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_SUB) @@ -2335,8 +2385,9 @@ CASE(JSOP_MUL) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!MulOperation(cx, lval, rval, res)) + if (!MulOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_MUL) @@ -2346,8 +2397,9 @@ CASE(JSOP_DIV) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!DivOperation(cx, lval, rval, res)) + if (!DivOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_DIV) @@ -2357,8 +2409,9 @@ CASE(JSOP_MOD) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!ModOperation(cx, lval, rval, res)) + if (!ModOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_MOD) @@ -2368,8 +2421,9 @@ CASE(JSOP_POW) ReservedRooted<Value> lval(&rootValue0, REGS.sp[-2]); ReservedRooted<Value> rval(&rootValue1, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-2); - if (!math_pow_handle(cx, lval, rval, res)) + if (!PowOperation(cx, &lval, &rval, res)) { goto error; + } REGS.sp--; } END_CASE(JSOP_POW) @@ -2384,11 +2438,11 @@ END_CASE(JSOP_NOT) CASE(JSOP_BITNOT) { - int32_t i; - HandleValue value = REGS.stackHandleAt(-1); - if (!BitNot(cx, value, &i)) + MutableHandleValue value = REGS.stackHandleAt(-1); + MutableHandleValue res = REGS.stackHandleAt(-1); + if (!BitNot(cx, value, res)) { goto error; - REGS.sp[-1].setInt32(i); + } } END_CASE(JSOP_BITNOT) @@ -2396,7 +2450,7 @@ CASE(JSOP_NEG) { ReservedRooted<Value> val(&rootValue0, REGS.sp[-1]); MutableHandleValue res = REGS.stackHandleAt(-1); - if (!NegOperation(cx, script, REGS.pc, val, res)) + if (!NegOperation(cx, script, REGS.pc, &val, res)) goto error; } END_CASE(JSOP_NEG) @@ -4122,6 +4176,13 @@ CASE(JSOP_IS_CONSTRUCTING) PUSH_MAGIC(JS_IS_CONSTRUCTING); END_CASE(JSOP_IS_CONSTRUCTING) +CASE(JSOP_BIGINT) +{ + PUSH_COPY(script->getConst(GET_UINT32_INDEX(REGS.pc))); + MOZ_ASSERT(REGS.sp[-1].isBigInt()); +} +END_CASE(JSOP_BIGINT) + DEFAULT() { char numBuf[12]; @@ -4222,7 +4283,8 @@ js::GetProperty(JSContext* cx, HandleValue v, HandlePropertyName name, MutableHa // Optimize common cases like (2).toString() or "foo".valueOf() to not // create a wrapper object. - if (v.isPrimitive() && !v.isNullOrUndefined()) { + if (v.isPrimitive() && !v.isNullOrUndefined() && !v.isBigInt()) + { NativeObject* proto; if (v.isNumber()) { proto = GlobalObject::getOrCreateNumberPrototype(cx, cx->global()); @@ -4577,6 +4639,12 @@ js::ModValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, Mut } bool +js::PowValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) +{ + return PowOperation(cx, lhs, rhs, res); +} + +bool js::UrshValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res) { return UrshOperation(cx, lhs, rhs, res); diff --git a/js/src/vm/Interpreter.h b/js/src/vm/Interpreter.h index 1927e8cc7f..3c7b80a14c 100644 --- a/js/src/vm/Interpreter.h +++ b/js/src/vm/Interpreter.h @@ -457,6 +457,9 @@ bool ModValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res); bool +PowValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res); + +bool UrshValues(JSContext* cx, MutableHandleValue lhs, MutableHandleValue rhs, MutableHandleValue res); bool diff --git a/js/src/vm/MemoryMetrics.cpp b/js/src/vm/MemoryMetrics.cpp index 9b6d3dda72..2a28cf23c0 100644 --- a/js/src/vm/MemoryMetrics.cpp +++ b/js/src/vm/MemoryMetrics.cpp @@ -17,6 +17,7 @@ #include "jit/BaselineJIT.h" #include "jit/Ion.h" #include "vm/ArrayObject.h" +#include "vm/BigIntType.h" #include "vm/Runtime.h" #include "vm/Shape.h" #include "vm/String.h" @@ -546,6 +547,13 @@ StatsCellCallback(JSRuntime* rt, void* data, void* thing, JS::TraceKind traceKin zStats->symbolsGCHeap += thingSize; break; + case JS::TraceKind::BigInt: { + JS::BigInt* bi = static_cast<BigInt*>(thing); + zStats->bigIntsGCHeap += thingSize; + zStats->bigIntsMallocHeap += bi->sizeOfExcludingThis(rtStats->mallocSizeOf_); + break; + } + case JS::TraceKind::BaseShape: { JS::ShapeInfo info; // This zeroes all the sizes. info.shapesGCHeapBase += thingSize; diff --git a/js/src/vm/NativeObject-inl.h b/js/src/vm/NativeObject-inl.h index 69976bc462..1fa5cbd10e 100644 --- a/js/src/vm/NativeObject-inl.h +++ b/js/src/vm/NativeObject-inl.h @@ -239,12 +239,15 @@ NativeObject::ensureDenseElements(ExclusiveContext* cx, uint32_t index, uint32_t return DenseElementResult::Success; } -inline Value -NativeObject::getDenseOrTypedArrayElement(uint32_t idx) +template <AllowGC allowGC> +inline bool +NativeObject::getDenseOrTypedArrayElement(ExclusiveContext* cx, uint32_t idx, + typename MaybeRooted<Value, allowGC>::MutableHandleType val) { if (is<TypedArrayObject>()) - return as<TypedArrayObject>().getElement(idx); - return getDenseElement(idx); + return as<TypedArrayObject>().getElement<allowGC>(cx, idx, val); + val.set(getDenseElement(idx)); + return true; } /* static */ inline NativeObject* diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index a6bb9826ee..cde86fb829 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -20,6 +20,7 @@ #include "vm/ArrayObject-inl.h" #include "vm/EnvironmentObject-inl.h" #include "vm/Shape-inl.h" +#include "vm/TypedArrayObject.h" using namespace js; @@ -1283,8 +1284,7 @@ GetExistingPropertyValue(ExclusiveContext* cx, HandleNativeObject obj, HandleId Handle<PropertyResult> prop, MutableHandleValue vp) { if (prop.isDenseOrTypedArrayElement()) { - vp.set(obj->getDenseOrTypedArrayElement(JSID_TO_INT(id))); - return true; + return obj->getDenseOrTypedArrayElement<CanGC>(cx, JSID_TO_INT(id), vp); } if (!cx->shouldBeJSContext()) return false; @@ -1814,7 +1814,9 @@ js::NativeGetOwnPropertyDescriptor(JSContext* cx, HandleNativeObject obj, Handle desc.attributesRef() &= ~JSPROP_SHARED; if (prop.isDenseOrTypedArrayElement()) { - desc.value().set(obj->getDenseOrTypedArrayElement(JSID_TO_INT(id))); + if (!obj->getDenseOrTypedArrayElement<CanGC>(cx, JSID_TO_INT(id), desc.value())) { + return false; + } } else { RootedShape shape(cx, prop.shape()); if (!NativeGetExistingProperty(cx, obj, obj, shape, desc.value())) @@ -2110,8 +2112,7 @@ NativeGetPropertyInline(JSContext* cx, // Steps 5-8. Special case for dense elements because // GetExistingProperty doesn't support those. if (prop.isDenseOrTypedArrayElement()) { - vp.set(pobj->getDenseOrTypedArrayElement(JSID_TO_INT(id))); - return true; + return pobj->template getDenseOrTypedArrayElement<allowGC>(cx, JSID_TO_INT(id), vp); } typename MaybeRooted<Shape*, allowGC>::RootType shape(cx, prop.shape()); @@ -2365,38 +2366,6 @@ SetNonexistentProperty(JSContext* cx, HandleId id, HandleValue v, HandleValue re } /* - * Set an existing own property obj[index] that's a dense element or typed - * array element. - */ -static bool -SetDenseOrTypedArrayElement(JSContext* cx, HandleNativeObject obj, uint32_t index, HandleValue v, - ObjectOpResult& result) -{ - if (obj->is<TypedArrayObject>()) { - double d; - if (!ToNumber(cx, v, &d)) - return false; - - // Silently do nothing for out-of-bounds sets, for consistency with - // current behavior. (ES6 currently says to throw for this in - // strict mode code, so we may eventually need to change.) - uint32_t len = obj->as<TypedArrayObject>().length(); - if (index < len) - TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, d); - return result.succeed(); - } - - if (WouldDefinePastNonwritableLength(obj, index)) - return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH); - - if (!obj->maybeCopyElementsForWrite(cx)) - return false; - - obj->setDenseElementWithType(cx, index, v); - return result.succeed(); -} - -/* * Finish the assignment `receiver[id] = v` when an existing property (shape) * has been found on a native object (pobj). This implements ES6 draft rev 32 * (2015 Feb 2) 9.1.9 steps 5 and 6. @@ -2416,8 +2385,23 @@ SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleVa return result.fail(JSMSG_READ_ONLY); // Pure optimization for the common case: - if (receiver.isObject() && pobj == &receiver.toObject()) - return SetDenseOrTypedArrayElement(cx, pobj, JSID_TO_INT(id), v, result); + if (receiver.isObject() && pobj == &receiver.toObject()) { + uint32_t index = JSID_TO_INT(id); + + if (pobj->is<TypedArrayObject>()) { + Rooted<TypedArrayObject*> tobj(cx, &pobj->as<TypedArrayObject>()); + return SetTypedArrayElement(cx, tobj, index, v, result); + } + + if (WouldDefinePastNonwritableLength(pobj, index)) + return result.fail(JSMSG_CANT_DEFINE_PAST_ARRAY_LENGTH); + + if (!pobj->maybeCopyElementsForWrite(cx)) + return false; + + pobj->setDenseElementWithType(cx, index, v); + return result.succeed(); + } // Steps 5.b-f. return SetPropertyByDefining(cx, id, v, receiver, result); diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index c5865caa03..86977d109f 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -18,6 +18,7 @@ #include "gc/Barrier.h" #include "gc/Heap.h" #include "gc/Marking.h" +#include "js/RootingAPI.h" #include "js/Value.h" #include "vm/Shape.h" #include "vm/ShapedObject.h" @@ -1088,7 +1089,9 @@ class NativeObject : public ShapedObject static inline void removeDenseElementForSparseIndex(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index); - inline Value getDenseOrTypedArrayElement(uint32_t idx); + template <AllowGC allowGC> inline bool + getDenseOrTypedArrayElement(ExclusiveContext* cx, uint32_t idx, + typename MaybeRooted<Value, allowGC>::MutableHandleType val); void copyDenseElements(uint32_t dstStart, const Value* src, uint32_t count) { MOZ_ASSERT(dstStart + count <= getDenseCapacity()); diff --git a/js/src/vm/ObjectGroup.cpp b/js/src/vm/ObjectGroup.cpp index 741531f015..408e346608 100644 --- a/js/src/vm/ObjectGroup.cpp +++ b/js/src/vm/ObjectGroup.cpp @@ -686,6 +686,8 @@ GetClassForProtoKey(JSProtoKey key) case JSProto_Float32Array: case JSProto_Float64Array: case JSProto_Uint8ClampedArray: + case JSProto_BigInt64Array: + case JSProto_BigUint64Array: return &TypedArrayObject::classes[key - JSProto_Int8Array]; case JSProto_ArrayBuffer: diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index f6856f2290..ff707aac08 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -2357,14 +2357,20 @@ * Operands: * Stack: arg => rval */ \ - macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE) - + macro(JSOP_DYNAMIC_IMPORT, 234, "call-import", NULL, 1, 1, 1, JOF_BYTE) \ + /* + * Pushes a BigInt constant onto the stack. + * Category: Literals + * Type: Constants + * Operands: uint32_t constIndex + * Stack: => val + */ \ + macro(JSOP_BIGINT, 235, "bigint", NULL, 5, 0, 1, JOF_BIGINT) /* * In certain circumstances it may be useful to "pad out" the opcode space to * a power of two. Use this macro to do so. */ #define FOR_EACH_TRAILING_UNUSED_OPCODE(macro) \ - macro(235) \ macro(236) \ macro(237) \ macro(238) \ diff --git a/js/src/vm/RegExpObject.h b/js/src/vm/RegExpObject.h index 14ec8509ee..5247731112 100644 --- a/js/src/vm/RegExpObject.h +++ b/js/src/vm/RegExpObject.h @@ -259,6 +259,9 @@ class RegExpShared : public gc::TenuredCell static bool dumpBytecode(JSContext* cx, MutableHandleRegExpShared res, bool match_only, HandleLinearString input); #endif + + public: + static const JS::TraceKind TraceKind = JS::TraceKind::RegExpShared; }; class RegExpCompartment diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index ceb7a498b0..053b7c44b0 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -877,6 +877,9 @@ js::CurrentThreadCanAccessRuntime(const JSRuntime* rt) bool js::CurrentThreadCanAccessZone(Zone* zone) { + if (!zone) + return false; + if (CurrentThreadCanAccessRuntime(zone->runtime_)) return true; diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index de497d02e1..2a60a1885c 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -22,6 +22,7 @@ #include "jswrapper.h" #include "selfhosted.out.h" +#include "builtin/BigInt.h" #include "builtin/intl/Collator.h" #include "builtin/intl/DateTimeFormat.h" #include "builtin/intl/IntlObject.h" @@ -44,6 +45,7 @@ #include "jit/InlinableNatives.h" #include "js/CharacterEncoding.h" #include "js/Date.h" +#include "vm/BigIntType.h" #include "vm/Compression.h" #include "vm/GeneratorObject.h" #include "vm/Interpreter.h" @@ -1182,6 +1184,18 @@ intrinsic_IsFloat32TypedArray(JSContext* cx, unsigned argc, Value* vp) } static bool +intrinsic_IsBigInt64TypedArray(JSContext* cx, unsigned argc, Value* vp) +{ + return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::BigInt64); +} + +static bool +intrinsic_IsBigUint64TypedArray(JSContext* cx, unsigned argc, Value* vp) +{ + return intrinsic_IsSpecificTypedArray(cx, argc, vp, Scalar::BigUint64); +} + +static bool intrinsic_TypedArrayBuffer(JSContext* cx, unsigned argc, Value* vp) { CallArgs args = CallArgsFromVp(argc, vp); @@ -1521,6 +1535,14 @@ struct DisjointElements CopyValues(dest, src.cast<double*>(), count); return; + case Scalar::BigInt64: + CopyValues(dest, src.cast<int64_t*>(), count); + return; + + case Scalar::BigUint64: + CopyValues(dest, src.cast<uint64_t*>(), count); + return; + case Scalar::Uint8Clamped: CopyValues(dest, src.cast<uint8_clamped*>(), count); return; @@ -1579,6 +1601,16 @@ CopyToDisjointArray(TypedArrayObject* target, uint32_t targetOffset, SharedMem<v break; } + case Scalar::BigInt64: { + DisjointElements::copy(dest.cast<int64_t*>(), src, srcType, count); + break; + } + + case Scalar::BigUint64: { + DisjointElements::copy(dest.cast<uint64_t*>(), src, srcType, count); + break; + } + case Scalar::Uint8Clamped: { DisjointElements::copy(dest.cast<uint8_clamped*>(), src, srcType, count); break; @@ -2164,6 +2196,29 @@ intrinsic_PromiseResolve(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool intrinsic_ToBigInt(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + BigInt* res = ToBigInt(cx, args[0]); + if (!res) { + return false; + } + args.rval().setBigInt(res); + return true; +} + +static bool intrinsic_ToNumeric(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + if (!ToNumeric(cx, args[0])) { + return false; + } + args.rval().set(args[0]); + return true; +} + // The self-hosting global isn't initialized with the normal set of builtins. // Instead, individual C++-implemented functions that're required by // self-hosted code are defined as global functions. Accessing these @@ -2187,6 +2242,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("std_Array_reverse", array_reverse, 0,0), JS_FNINFO("std_Array_splice", array_splice, &array_splice_info, 2,0), + JS_FN("std_BigInt_valueOf", BigIntObject::valueOf, 0,0), + JS_FN("std_Date_now", date_now, 0,0), JS_FN("std_Date_valueOf", date_valueOf, 0,0), @@ -2399,6 +2456,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("IsUint32TypedArray", intrinsic_IsUint32TypedArray, 1,0), JS_FN("IsInt32TypedArray", intrinsic_IsInt32TypedArray, 1,0), JS_FN("IsFloat32TypedArray", intrinsic_IsFloat32TypedArray, 1,0), + JS_FN("IsBigInt64TypedArray", intrinsic_IsBigInt64TypedArray, 1,0), + JS_FN("IsBigUint64TypedArray", intrinsic_IsBigUint64TypedArray, 1,0), JS_INLINABLE_FN("IsTypedArray", intrinsic_IsInstanceOfBuiltin<TypedArrayObject>, 1,0, IntrinsicIsTypedArray), @@ -2591,6 +2650,9 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("CallPromiseMethodIfWrapped", CallNonGenericSelfhostedMethod<Is<PromiseObject>>, 2, 0), JS_FN("PromiseResolve", intrinsic_PromiseResolve, 2, 0), + JS_FN("ToBigInt", intrinsic_ToBigInt, 1, 0), + JS_FN("ToNumeric", intrinsic_ToNumeric, 1, 0), + JS_FS_END }; diff --git a/js/src/vm/Stopwatch.cpp b/js/src/vm/Stopwatch.cpp index 46841b27f8..2c267d446d 100644 --- a/js/src/vm/Stopwatch.cpp +++ b/js/src/vm/Stopwatch.cpp @@ -409,7 +409,8 @@ AutoStopwatch::getCycles(JSRuntime* runtime) const cpuid_t inline AutoStopwatch::getCPU() const { -#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA +// Temporary disable untested code path. Issue #2255 +#if 0 //defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA PROCESSOR_NUMBER proc; GetCurrentProcessorNumberEx(&proc); @@ -423,7 +424,8 @@ AutoStopwatch::getCPU() const bool inline AutoStopwatch::isSameCPU(const cpuid_t& a, const cpuid_t& b) const { -#if defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA +// Temporary disable untested code path. Issue #2255 +#if 0 //defined(XP_WIN) && WINVER >= _WIN32_WINNT_VISTA return a.group_ == b.group_ && a.number_ == b.number_; #else return true; diff --git a/js/src/vm/Stopwatch.h b/js/src/vm/Stopwatch.h index f3985c53aa..84da265a25 100644 --- a/js/src/vm/Stopwatch.h +++ b/js/src/vm/Stopwatch.h @@ -271,7 +271,8 @@ struct PerformanceMonitoring { uint64_t highestTimestampCounter_; }; -#if WINVER >= 0x0600 +// Temporary disable untested code path. Issue #2255 +#if 0 // WINVER >= 0x0600 struct cpuid_t { uint16_t group_; uint8_t number_; diff --git a/js/src/vm/StringBuffer.cpp b/js/src/vm/StringBuffer.cpp index e4f0e4f4d6..ce0fc4e719 100644 --- a/js/src/vm/StringBuffer.cpp +++ b/js/src/vm/StringBuffer.cpp @@ -170,6 +170,13 @@ js::ValueToStringBufferSlow(JSContext* cx, const Value& arg, StringBuffer& sb) JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_SYMBOL_TO_STRING); return false; } + if (v.isBigInt()) { + RootedBigInt i(cx, v.toBigInt()); + JSLinearString* str = BigInt::toString(cx, i, 10); + if (!str) + return false; + return sb.append(str); + } MOZ_ASSERT(v.isUndefined()); return sb.append(cx->names().undefined); } diff --git a/js/src/vm/StructuredClone.cpp b/js/src/vm/StructuredClone.cpp index 26e57976fc..e99cfe8f71 100644 --- a/js/src/vm/StructuredClone.cpp +++ b/js/src/vm/StructuredClone.cpp @@ -31,6 +31,7 @@ #include "mozilla/CheckedInt.h" #include "mozilla/EndianUtils.h" #include "mozilla/FloatingPoint.h" +#include "mozilla/RangedPtr.h" #include <algorithm> @@ -39,6 +40,8 @@ #include "jsdate.h" #include "jswrapper.h" +#include "vm/BigIntType.h" + #include "builtin/MapObject.h" #include "js/Date.h" #include "js/GCHashTable.h" @@ -57,6 +60,7 @@ using mozilla::IsNaN; using mozilla::LittleEndian; using mozilla::NativeEndian; using mozilla::NumbersAreIdentical; +using mozilla::RangedPtr; using JS::CanonicalizeNaN; // When you make updates here, make sure you consider whether you need to bump the @@ -104,6 +108,9 @@ enum StructuredDataType : uint32_t { SCTAG_SHARED_ARRAY_BUFFER_OBJECT, + SCTAG_BIGINT, + SCTAG_BIGINT_OBJECT, + SCTAG_TYPED_ARRAY_V1_MIN = 0xFFFF0100, SCTAG_TYPED_ARRAY_V1_INT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Int8, SCTAG_TYPED_ARRAY_V1_UINT8 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8, @@ -114,7 +121,8 @@ enum StructuredDataType : uint32_t { SCTAG_TYPED_ARRAY_V1_FLOAT32 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float32, SCTAG_TYPED_ARRAY_V1_FLOAT64 = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Float64, SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::Uint8Clamped, - SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_MIN + Scalar::MaxTypedArrayViewType - 1, + // BigInt64 and BigUint64 are not supported in the v1 format. + SCTAG_TYPED_ARRAY_V1_MAX = SCTAG_TYPED_ARRAY_V1_UINT8_CLAMPED, /* * Define a separate range of numbers for Transferable-only tags, since @@ -349,6 +357,8 @@ struct JSStructuredCloneReader { JSString* readStringImpl(uint32_t nchars); JSString* readString(uint32_t data); + BigInt* readBigInt(uint32_t data); + bool checkDouble(double d); bool readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp, bool v1Read = false); @@ -439,6 +449,8 @@ struct JSStructuredCloneWriter { bool traverseSet(HandleObject obj); bool traverseSavedFrame(HandleObject obj); + bool writeBigInt(uint32_t tag, BigInt* bi); + bool reportDataCloneError(uint32_t errorId); bool parseTransferable(); @@ -1055,6 +1067,23 @@ JSStructuredCloneWriter::writeString(uint32_t tag, JSString* str) : out.writeChars(linear->twoByteChars(nogc), length); } +bool +JSStructuredCloneWriter::writeBigInt(uint32_t tag, BigInt* bi) +{ + bool signBit = bi->isNegative(); + size_t length = bi->digitLength(); + // The length must fit in 31 bits to leave room for a sign bit. + if (length > size_t(INT32_MAX)) { + return false; + } + uint32_t lengthAndSign = length | (static_cast<uint32_t>(signBit) << 31); + + if (!out.writePair(tag, lengthAndSign)) { + return false; + } + return out.writeArray(bi->digits().data(), length); +} + inline void JSStructuredCloneWriter::checkStack() { @@ -1388,6 +1417,8 @@ JSStructuredCloneWriter::startWrite(HandleValue v) return out.writePair(SCTAG_NULL, 0); } else if (v.isUndefined()) { return out.writePair(SCTAG_UNDEFINED, 0); + } else if (v.isBigInt()) { + return writeBigInt(SCTAG_BIGINT, v.toBigInt()); } else if (v.isObject()) { RootedObject obj(context(), &v.toObject()); @@ -1441,6 +1472,12 @@ JSStructuredCloneWriter::startWrite(HandleValue v) return writeString(SCTAG_STRING_OBJECT, unboxed.toString()); } else if (cls == ESClass::Map) { return traverseMap(obj); + } else if (cls == ESClass::BigInt) { + RootedValue unboxed(context()); + if (!Unbox(context(), obj, &unboxed)) { + return false; + } + return writeBigInt(SCTAG_BIGINT_OBJECT, unboxed.toBigInt()); } else if (cls == ESClass::Set) { return traverseSet(obj); } else if (SavedFrame::isSavedFrameOrWrapperAndNotProto(*obj)) { @@ -1745,6 +1782,22 @@ JSStructuredCloneReader::readString(uint32_t data) return latin1 ? readStringImpl<Latin1Char>(nchars) : readStringImpl<char16_t>(nchars); } +BigInt* JSStructuredCloneReader::readBigInt(uint32_t data) { + size_t length = data & JS_BITMASK(31); + bool isNegative = data & (1 << 31); + if (length == 0) { + return BigInt::zero(context()); + } + BigInt* result = BigInt::createUninitialized(context(), length, isNegative); + if (!result) { + return nullptr; + } + if (!in.readArray(result->digits().data(), length)) { + return nullptr; + } + return result; +} + static uint32_t TagToV1ArrayType(uint32_t tag) { @@ -1756,7 +1809,7 @@ bool JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, MutableHandleValue vp, bool v1Read) { - if (arrayType > Scalar::Uint8Clamped) { + if (arrayType > (v1Read ? Scalar::Uint8Clamped : Scalar::BigUint64)) { JS_ReportErrorNumberASCII(context(), GetErrorMessage, nullptr, JSMSG_SC_BAD_SERIALIZED_DATA, "unhandled typed array element type"); return false; @@ -1820,6 +1873,12 @@ JSStructuredCloneReader::readTypedArray(uint32_t arrayType, uint32_t nelems, Mut case Scalar::Uint8Clamped: obj = JS_NewUint8ClampedArrayWithBuffer(context(), buffer, byteOffset, nelems); break; + case Scalar::BigInt64: + obj = JS_NewBigInt64ArrayWithBuffer(context(), buffer, byteOffset, nelems); + break; + case Scalar::BigUint64: + obj = JS_NewBigUint64ArrayWithBuffer(context(), buffer, byteOffset, nelems); + break; default: MOZ_CRASH("Can't happen: arrayType range checked above"); } @@ -1955,6 +2014,8 @@ JSStructuredCloneReader::readV1ArrayBuffer(uint32_t arrayType, uint32_t nelems, case Scalar::Float32: return in.readArray((uint32_t*) buffer.dataPointer(), nelems); case Scalar::Float64: + case Scalar::BigInt64: + case Scalar::BigUint64: return in.readArray((uint64_t*) buffer.dataPointer(), nelems); default: MOZ_CRASH("Can't happen: arrayType range checked by caller"); @@ -2021,6 +2082,19 @@ JSStructuredCloneReader::startRead(MutableHandleValue vp) break; } + case SCTAG_BIGINT: + case SCTAG_BIGINT_OBJECT: { + RootedBigInt bi(context(), readBigInt(data)); + if (!bi) { + return false; + } + vp.setBigInt(bi); + if (tag == SCTAG_BIGINT_OBJECT && !PrimitiveToObject(context(), vp)) { + return false; + } + break; + } + case SCTAG_DATE_OBJECT: { double d; if (!in.readDouble(&d) || !checkDouble(d)) diff --git a/js/src/vm/TypeInference-inl.h b/js/src/vm/TypeInference-inl.h index 5e1bac6bc9..8c9b179223 100644 --- a/js/src/vm/TypeInference-inl.h +++ b/js/src/vm/TypeInference-inl.h @@ -213,6 +213,8 @@ PrimitiveTypeFlag(JSValueType type) return TYPE_FLAG_STRING; case JSVAL_TYPE_SYMBOL: return TYPE_FLAG_SYMBOL; + case JSVAL_TYPE_BIGINT: + return TYPE_FLAG_BIGINT; case JSVAL_TYPE_MAGIC: return TYPE_FLAG_LAZYARGS; default: @@ -238,6 +240,8 @@ TypeFlagPrimitive(TypeFlags flags) return JSVAL_TYPE_STRING; case TYPE_FLAG_SYMBOL: return JSVAL_TYPE_SYMBOL; + case TYPE_FLAG_BIGINT: + return JSVAL_TYPE_BIGINT; case TYPE_FLAG_LAZYARGS: return JSVAL_TYPE_MAGIC; default: diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index ca34b184e8..a36926eb94 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -111,6 +111,8 @@ TypeSet::NonObjectTypeString(TypeSet::Type type) return "string"; case JSVAL_TYPE_SYMBOL: return "symbol"; + case JSVAL_TYPE_BIGINT: + return "BigInt"; case JSVAL_TYPE_MAGIC: return "lazyargs"; default: @@ -366,6 +368,8 @@ TypeSet::mightBeMIRType(jit::MIRType type) const return baseFlags() & TYPE_FLAG_STRING; case jit::MIRType::Symbol: return baseFlags() & TYPE_FLAG_SYMBOL; + case jit::MIRType::BigInt: + return baseFlags() & TYPE_FLAG_BIGINT; case jit::MIRType::MagicOptimizedArguments: return baseFlags() & TYPE_FLAG_LAZYARGS; case jit::MIRType::MagicHole: @@ -755,6 +759,8 @@ TypeSet::print(FILE* fp) fprintf(fp, " string"); if (flags & TYPE_FLAG_SYMBOL) fprintf(fp, " symbol"); + if (flags & TYPE_FLAG_BIGINT) + fprintf(fp, " BigInt"); if (flags & TYPE_FLAG_LAZYARGS) fprintf(fp, " lazyargs"); @@ -1631,6 +1637,8 @@ GetMIRTypeFromTypeFlags(TypeFlags flags) return jit::MIRType::String; case TYPE_FLAG_SYMBOL: return jit::MIRType::Symbol; + case TYPE_FLAG_BIGINT: + return jit::MIRType::BigInt; case TYPE_FLAG_LAZYARGS: return jit::MIRType::MagicOptimizedArguments; case TYPE_FLAG_ANYOBJECT: diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index 764f99ca07..58c606912c 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -59,17 +59,18 @@ enum : uint32_t { TYPE_FLAG_DOUBLE = 0x10, TYPE_FLAG_STRING = 0x20, TYPE_FLAG_SYMBOL = 0x40, - TYPE_FLAG_LAZYARGS = 0x80, - TYPE_FLAG_ANYOBJECT = 0x100, + TYPE_FLAG_BIGINT = 0x80, + TYPE_FLAG_LAZYARGS = 0x100, + TYPE_FLAG_ANYOBJECT = 0x200, /* Mask containing all primitives */ TYPE_FLAG_PRIMITIVE = TYPE_FLAG_UNDEFINED | TYPE_FLAG_NULL | TYPE_FLAG_BOOLEAN | TYPE_FLAG_INT32 | TYPE_FLAG_DOUBLE | TYPE_FLAG_STRING | - TYPE_FLAG_SYMBOL, + TYPE_FLAG_SYMBOL |TYPE_FLAG_BIGINT, /* Mask/shift for the number of objects in objectSet */ - TYPE_FLAG_OBJECT_COUNT_MASK = 0x3e00, - TYPE_FLAG_OBJECT_COUNT_SHIFT = 9, + TYPE_FLAG_OBJECT_COUNT_MASK = 0x3c00, + TYPE_FLAG_OBJECT_COUNT_SHIFT = 10, TYPE_FLAG_OBJECT_COUNT_LIMIT = 7, TYPE_FLAG_DOMOBJECT_COUNT_LIMIT = TYPE_FLAG_OBJECT_COUNT_MASK >> TYPE_FLAG_OBJECT_COUNT_SHIFT, @@ -78,7 +79,7 @@ enum : uint32_t { TYPE_FLAG_UNKNOWN = 0x00004000, /* Mask of normal type flags on a type set. */ - TYPE_FLAG_BASE_MASK = 0x000041ff, + TYPE_FLAG_BASE_MASK = TYPE_FLAG_PRIMITIVE | TYPE_FLAG_LAZYARGS | TYPE_FLAG_ANYOBJECT | TYPE_FLAG_UNKNOWN, /* Additional flags for HeapTypeSet sets. */ @@ -109,6 +110,10 @@ enum : uint32_t { }; typedef uint32_t TypeFlags; +static_assert(TYPE_FLAG_PRIMITIVE < TYPE_FLAG_ANYOBJECT && + TYPE_FLAG_LAZYARGS < TYPE_FLAG_ANYOBJECT, + "TYPE_FLAG_ANYOBJECT should be greater than primitive type flags"); + /* Flags and other state stored in ObjectGroup::Flags */ enum : uint32_t { /* Whether this group is associated with some allocation site. */ @@ -366,6 +371,7 @@ class TypeSet static inline Type DoubleType() { return Type(JSVAL_TYPE_DOUBLE); } static inline Type StringType() { return Type(JSVAL_TYPE_STRING); } static inline Type SymbolType() { return Type(JSVAL_TYPE_SYMBOL); } + static inline Type BigIntType() { return Type(JSVAL_TYPE_BIGINT); } static inline Type MagicArgType() { return Type(JSVAL_TYPE_MAGIC); } static inline Type AnyObjectType() { return Type(JSVAL_TYPE_OBJECT); } static inline Type UnknownType() { return Type(JSVAL_TYPE_UNKNOWN); } diff --git a/js/src/vm/TypedArrayCommon.h b/js/src/vm/TypedArrayCommon.h index eb7b94f109..8e66587a1e 100644 --- a/js/src/vm/TypedArrayCommon.h +++ b/js/src/vm/TypedArrayCommon.h @@ -112,6 +112,20 @@ ConvertNumber<uint32_t, float>(float src) return JS::ToUint32(src); } +template <> +inline int64_t +ConvertNumber<int64_t, float>(float src) +{ + return JS::ToInt64(src); +} + +template <> +inline uint64_t +ConvertNumber<uint64_t, float>(float src) +{ + return JS::ToUint64(src); +} + template<> inline int8_t ConvertNumber<int8_t, double>(double src) { @@ -160,6 +174,20 @@ ConvertNumber<uint32_t, double>(double src) return JS::ToUint32(src); } +template <> +inline int64_t +ConvertNumber<int64_t, double>(double src) +{ + return JS::ToInt64(src); +} + +template <> +inline uint64_t +ConvertNumber<uint64_t, double>(double src) +{ + return JS::ToUint64(src); +} + template<typename To, typename From> inline To ConvertNumber(From src) @@ -178,6 +206,8 @@ template<> struct TypeIDOfType<int16_t> { static const Scalar::Type id = Scalar: template<> struct TypeIDOfType<uint16_t> { static const Scalar::Type id = Scalar::Uint16; }; template<> struct TypeIDOfType<int32_t> { static const Scalar::Type id = Scalar::Int32; }; template<> struct TypeIDOfType<uint32_t> { static const Scalar::Type id = Scalar::Uint32; }; +template<> struct TypeIDOfType<int64_t> { static const Scalar::Type id = Scalar::BigInt64; }; +template<> struct TypeIDOfType<uint64_t> { static const Scalar::Type id = Scalar::BigUint64; }; template<> struct TypeIDOfType<float> { static const Scalar::Type id = Scalar::Float32; }; template<> struct TypeIDOfType<double> { static const Scalar::Type id = Scalar::Float64; }; template<> struct TypeIDOfType<uint8_clamped> { static const Scalar::Type id = Scalar::Uint8Clamped; }; @@ -355,6 +385,18 @@ class ElementSpecific Ops::store(dest++, ConvertNumber<T>(Ops::load(src++))); break; } + case Scalar::BigInt64: { + SharedMem<int64_t*> src = data.cast<int64_t*>(); + for (uint32_t i = 0; i < count; ++i) + Ops::store(dest++, ConvertNumber<T>(Ops::load(src++))); + break; + } + case Scalar::BigUint64: { + SharedMem<uint64_t*> src = data.cast<uint64_t*>(); + for (uint32_t i = 0; i < count; ++i) + Ops::store(dest++, ConvertNumber<T>(Ops::load(src++))); + break; + } case Scalar::Float32: { SharedMem<JS_VOLATILE_ARM float*> src = data.cast<JS_VOLATILE_ARM float*>(); for (uint32_t i = 0; i < count; ++i) @@ -399,12 +441,12 @@ class ElementSpecific SharedMem<T*> dest = target->template as<TypedArrayObject>().viewDataEither().template cast<T*>() + offset; - MOZ_ASSERT(!canConvertInfallibly(MagicValue(JS_ELEMENTS_HOLE)), + MOZ_ASSERT(!canConvertInfallibly(MagicValue(JS_ELEMENTS_HOLE), target->type()), "the following loop must abort on holes"); const Value* srcValues = source->as<NativeObject>().getDenseElements(); for (; i < bound; i++) { - if (!canConvertInfallibly(srcValues[i])) + if (!canConvertInfallibly(srcValues[i], target->type())) break; Ops::store(dest + i, infallibleValueToNative(srcValues[i])); } @@ -459,7 +501,7 @@ class ElementSpecific const Value* srcValues = source->getDenseElements(); for (; i < len; i++) { - if (!canConvertInfallibly(srcValues[i])) + if (!canConvertInfallibly(srcValues[i], target->type())) break; Ops::store(dest + i, infallibleValueToNative(srcValues[i])); } @@ -568,6 +610,18 @@ class ElementSpecific Ops::store(dest++, ConvertNumber<T>(*src++)); break; } + case Scalar::BigInt64: { + int64_t* src = static_cast<int64_t*>(data); + for (uint32_t i = 0; i < len; ++i) + Ops::store(dest++, ConvertNumber<T>(*src++)); + break; + } + case Scalar::BigUint64: { + uint64_t* src = static_cast<uint64_t*>(data); + for (uint32_t i = 0; i < len; ++i) + Ops::store(dest++, ConvertNumber<T>(*src++)); + break; + } case Scalar::Float32: { float* src = static_cast<float*>(data); for (uint32_t i = 0; i < len; ++i) @@ -589,8 +643,11 @@ class ElementSpecific } static bool - canConvertInfallibly(const Value& v) + canConvertInfallibly(const Value& v, Scalar::Type type) { + if (type == Scalar::BigInt64 || type == Scalar::BigUint64) { + return false; + } return v.isNumber() || v.isBoolean() || v.isNull() || v.isUndefined(); } @@ -615,11 +672,21 @@ class ElementSpecific { MOZ_ASSERT(!v.isMagic()); - if (MOZ_LIKELY(canConvertInfallibly(v))) { + if (MOZ_LIKELY(canConvertInfallibly(v, TypeIDOfType<T>::id))) { *result = infallibleValueToNative(v); return true; } + if (std::is_same<T, int64_t>::value) { + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v)); + return true; + } + + if (std::is_same<T, uint64_t>::value) { + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v)); + return true; + } + double d; MOZ_ASSERT(v.isString() || v.isObject() || v.isSymbol()); if (!(v.isString() ? StringToNumber(cx, v.toString(), &d) : ToNumber(cx, v, &d))) @@ -668,6 +735,8 @@ class TypedArrayMethods typedef typename SomeTypedArray::template OfType<uint16_t>::Type Uint16ArrayType; typedef typename SomeTypedArray::template OfType<int32_t>::Type Int32ArrayType; typedef typename SomeTypedArray::template OfType<uint32_t>::Type Uint32ArrayType; + typedef typename SomeTypedArray::template OfType<int64_t>::Type BigInt64ArrayType; + typedef typename SomeTypedArray::template OfType<uint64_t>::Type BigUint64ArrayType; typedef typename SomeTypedArray::template OfType<float>::Type Float32ArrayType; typedef typename SomeTypedArray::template OfType<double>::Type Float64ArrayType; typedef typename SomeTypedArray::template OfType<uint8_clamped>::Type Uint8ClampedArrayType; @@ -759,6 +828,14 @@ class TypedArrayMethods if (isShared) return ElementSpecific<Uint32ArrayType, SharedOps>::setFromTypedArray(cx, target, source, offset); return ElementSpecific<Uint32ArrayType, UnsharedOps>::setFromTypedArray(cx, target, source, offset); + case Scalar::BigInt64: + if (isShared) + return ElementSpecific<BigInt64ArrayType, SharedOps>::setFromTypedArray(cx, target, source, offset); + return ElementSpecific<BigInt64ArrayType, UnsharedOps>::setFromTypedArray(cx, target, source, offset); + case Scalar::BigUint64: + if (isShared) + return ElementSpecific<BigUint64ArrayType, SharedOps>::setFromTypedArray(cx, target, source, offset); + return ElementSpecific<BigUint64ArrayType, UnsharedOps>::setFromTypedArray(cx, target, source, offset); case Scalar::Float32: if (isShared) return ElementSpecific<Float32ArrayType, SharedOps>::setFromTypedArray(cx, target, source, offset); @@ -816,6 +893,14 @@ class TypedArrayMethods if (isShared) return ElementSpecific<Uint32ArrayType, SharedOps>::setFromNonTypedArray(cx, target, source, len, offset); return ElementSpecific<Uint32ArrayType, UnsharedOps>::setFromNonTypedArray(cx, target, source, len, offset); + case Scalar::BigInt64: + if (isShared) + return ElementSpecific<BigInt64ArrayType, SharedOps>::setFromNonTypedArray(cx, target, source, len, offset); + return ElementSpecific<BigInt64ArrayType, UnsharedOps>::setFromNonTypedArray(cx, target, source, len, offset); + case Scalar::BigUint64: + if (isShared) + return ElementSpecific<BigUint64ArrayType, SharedOps>::setFromNonTypedArray(cx, target, source, len, offset); + return ElementSpecific<BigUint64ArrayType, UnsharedOps>::setFromNonTypedArray(cx, target, source, len, offset); case Scalar::Float32: if (isShared) return ElementSpecific<Float32ArrayType, SharedOps>::setFromNonTypedArray(cx, target, source, len, offset); @@ -870,6 +955,14 @@ class TypedArrayMethods if (isShared) return ElementSpecific<Uint32ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source); return ElementSpecific<Uint32ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source); + case Scalar::BigInt64: + if (isShared) + return ElementSpecific<BigInt64ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source); + return ElementSpecific<BigInt64ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source); + case Scalar::BigUint64: + if (isShared) + return ElementSpecific<BigUint64ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source); + return ElementSpecific<BigUint64ArrayType, UnsharedOps>::initFromIterablePackedArray(cx, target, source); case Scalar::Float32: if (isShared) return ElementSpecific<Float32ArrayType, SharedOps>::initFromIterablePackedArray(cx, target, source); diff --git a/js/src/vm/TypedArrayObject.cpp b/js/src/vm/TypedArrayObject.cpp index 9d82fca6ec..28e4090eb8 100644 --- a/js/src/vm/TypedArrayObject.cpp +++ b/js/src/vm/TypedArrayObject.cpp @@ -65,6 +65,33 @@ using JS::ToUint32; * the subclasses. */ +bool TypedArrayObject::convertForSideEffect(JSContext* cx, HandleValue v) const +{ + switch (type()) { + case Scalar::BigInt64: + case Scalar::BigUint64: { + return ToBigInt(cx, v) != nullptr; + } + case Scalar::Int8: + case Scalar::Uint8: + case Scalar::Int16: + case Scalar::Uint16: + case Scalar::Int32: + case Scalar::Uint32: + case Scalar::Float32: + case Scalar::Float64: + case Scalar::Uint8Clamped: { + double ignore; + return ToNumber(cx, v, &ignore); + } + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + MOZ_CRASH("Unsupported TypedArray type"); + } + MOZ_ASSERT_UNREACHABLE("Invalid scalar type"); + return false; +} + /* static */ int TypedArrayObject::lengthOffset() { @@ -414,30 +441,7 @@ class TypedArrayObjectTemplate : public TypedArrayObject return v.isObject() && v.toObject().hasClass(instanceClass()); } - static void - setIndexValue(TypedArrayObject& tarray, uint32_t index, double d) - { - // If the array is an integer array, we only handle up to - // 32-bit ints from this point on. if we want to handle - // 64-bit ints, we'll need some changes. - - // Assign based on characteristics of the destination type - if (ArrayTypeIsFloatingPoint()) { - setIndex(tarray, index, NativeType(d)); - } else if (ArrayTypeIsUnsigned()) { - MOZ_ASSERT(sizeof(NativeType) <= 4); - uint32_t n = ToUint32(d); - setIndex(tarray, index, NativeType(n)); - } else if (ArrayTypeID() == Scalar::Uint8Clamped) { - // The uint8_clamped type has a special rounding converter - // for doubles. - setIndex(tarray, index, NativeType(d)); - } else { - MOZ_ASSERT(sizeof(NativeType) <= 4); - int32_t n = ToInt32(d); - setIndex(tarray, index, NativeType(n)); - } - } + static bool convertValue(JSContext* cx, HandleValue v, NativeType* result); static TypedArrayObject* makeProtoInstance(JSContext* cx, HandleObject proto, AllocKind allocKind) @@ -1001,9 +1005,128 @@ class TypedArrayObjectTemplate : public TypedArrayObject jit::AtomicOperations::storeSafeWhenRacy(tarray.viewDataEither().cast<NativeType*>() + index, val); } - static Value getIndexValue(JSObject* tarray, uint32_t index); + static bool getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, MutableHandleValue val); + static bool getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp); + + static bool setElement(JSContext* cx, Handle<TypedArrayObject*> obj, uint64_t index, HandleValue v, + ObjectOpResult& result); + static bool defineElement(JSContext* cx, HandleObject obj, uint64_t index, HandleValue v, + ObjectOpResult& result); }; +template <typename NativeType> +bool TypedArrayObjectTemplate<NativeType>::convertValue(JSContext* cx, HandleValue v, NativeType* result) +{ + double d; + if (!ToNumber(cx, v, &d)) { + return false; + } + +#ifdef JS_MORE_DETERMINISTIC + // See the comment in ElementSpecific::doubleToNative. + d = JS::CanonicalizeNaN(d); +#endif + + // Assign based on characteristics of the destination type + if (ArrayTypeIsFloatingPoint()) { + *result = NativeType(d); + } else if (ArrayTypeIsUnsigned()) { + MOZ_ASSERT(sizeof(NativeType) <= 4); + uint32_t n = ToUint32(d); + *result = NativeType(n); + } else if (ArrayTypeID() == Scalar::Uint8Clamped) { + // The uint8_clamped type has a special rounding converter + // for doubles. + *result = NativeType(d); + } else { + MOZ_ASSERT(sizeof(NativeType) <= 4); + int32_t n = ToInt32(d); + *result = NativeType(n); + } + return true; +} + +template <> +bool TypedArrayObjectTemplate<int64_t>::convertValue(JSContext* cx, HandleValue v, int64_t* result) +{ + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigInt64(cx, v)); + return true; +} + +template <> +bool TypedArrayObjectTemplate<uint64_t>::convertValue(JSContext* cx, HandleValue v, uint64_t* result) +{ + JS_TRY_VAR_OR_RETURN_FALSE(cx, *result, ToBigUint64(cx, v)); + return true; +} + +// https://tc39.github.io/proposal-bigint/#sec-integerindexedelementset +// 7.8 IntegerIndexedElementSet ( O, index, value ) +template <typename NativeType> +/* static */ bool TypedArrayObjectTemplate<NativeType>::setElement( + JSContext* cx, Handle<TypedArrayObject*> obj, uint64_t index, HandleValue v, + ObjectOpResult& result) +{ + // Steps 1-2 are enforced by the caller. + + // Steps 3-6. + NativeType nativeValue; + if (!convertValue(cx, v, &nativeValue)) { + return false; + } + + // Step 8. + if (obj->hasDetachedBuffer()) { + return result.failSoft(JSMSG_TYPED_ARRAY_DETACHED); + } + + // Steps 9-10 are enforced by the caller. + + // Step 11. + uint32_t length = obj->length(); + + // Step 12. + if (index >= length) { + return result.failSoft(JSMSG_BAD_INDEX); + } + + // Steps 7, 13-16. + TypedArrayObjectTemplate<NativeType>::setIndex(*obj, index, nativeValue); + + // Step 17. + return result.succeed(); +} + +// Version of IntegerIndexedElementSet with no length check, used in +// [[DefineOwnProperty]] +template <typename NativeType> +/* static */ bool TypedArrayObjectTemplate<NativeType>::defineElement( + JSContext* cx, HandleObject obj, uint64_t index, HandleValue v, + ObjectOpResult& result) +{ + // Steps 1-2 are enforced by the caller. + + // Steps 3-6. + NativeType nativeValue; + if (!convertValue(cx, v, &nativeValue)) { + return false; + } + + // Step 8. + if (obj->as<TypedArrayObject>().hasDetachedBuffer()) { + return result.fail(JSMSG_TYPED_ARRAY_DETACHED); + } + + // Steps 9-12 are enforced by the caller. + + // Steps 7, 13-16. + TypedArrayObjectTemplate<NativeType>::setIndex(obj->as<TypedArrayObject>(), + index, nativeValue); + + // Step 17. + return result.succeed(); +} + #define CREATE_TYPE_FOR_TYPED_ARRAY(T, N) \ typedef TypedArrayObjectTemplate<T> N##Array; JS_FOR_EACH_TYPED_ARRAY(CREATE_TYPE_FOR_TYPED_ARRAY) @@ -1240,6 +1363,13 @@ TypedArrayObjectTemplate<T>::fromTypedArray(JSContext* cx, HandleObject other, b } } + // BigInt proposal 7.24, step 19.c. + if (Scalar::isBigIntType(ArrayTypeID()) != + Scalar::isBigIntType(srcArray->type())) { + JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_BIGINT); + return nullptr; + } + // Steps 3-4 (remaining part), 18-21. Rooted<TypedArrayObject*> obj(cx, makeInstance(cx, buffer, 0, elementLength, proto)); if (!obj) @@ -1584,40 +1714,43 @@ ArrayBufferObject::createTypedArrayFromBuffer(JSContext* cx, unsigned argc, Valu return CallNonGenericMethod<IsAnyArrayBuffer, createTypedArrayFromBufferImpl<T> >(cx, args); } -// this default implementation is only valid for integer types -// less than 32-bits in size. +// This default implementation is only valid for integer types less +// than 32-bits in size. template<typename NativeType> -Value -TypedArrayObjectTemplate<NativeType>::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate<NativeType>::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { static_assert(sizeof(NativeType) < 4, "this method must only handle NativeType values that are " "always exact int32_t values"); - return Int32Value(getIndex(tarray, index)); + *vp = Int32Value(getIndex(tarray, index)); + return true; } namespace { -// and we need to specialize for 32-bit integers and floats +// We need to specialize for floats and other integer types. template<> -Value -TypedArrayObjectTemplate<int32_t>::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate<int32_t>::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { - return Int32Value(getIndex(tarray, index)); + *vp = Int32Value(getIndex(tarray, index)); + return true; } template<> -Value -TypedArrayObjectTemplate<uint32_t>::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate<uint32_t>::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { uint32_t val = getIndex(tarray, index); - return NumberValue(val); + *vp = NumberValue(val); + return true; } template<> -Value -TypedArrayObjectTemplate<float>::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate<float>::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { float val = getIndex(tarray, index); double dval = val; @@ -1632,12 +1765,13 @@ TypedArrayObjectTemplate<float>::getIndexValue(JSObject* tarray, uint32_t index) * This could be removed for platforms/compilers known to convert a 32-bit * non-canonical nan to a 64-bit canonical nan. */ - return DoubleValue(CanonicalizeNaN(dval)); + *vp = DoubleValue(CanonicalizeNaN(dval)); + return true; } template<> -Value -TypedArrayObjectTemplate<double>::getIndexValue(JSObject* tarray, uint32_t index) +bool +TypedArrayObjectTemplate<double>::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) { double val = getIndex(tarray, index); @@ -1648,9 +1782,60 @@ TypedArrayObjectTemplate<double>::getIndexValue(JSObject* tarray, uint32_t index * confuse the engine into interpreting a double-typed jsval as an * object-typed jsval. */ - return DoubleValue(CanonicalizeNaN(val)); + *vp = DoubleValue(CanonicalizeNaN(val)); + return true; +} + +template <> +bool +TypedArrayObjectTemplate<int64_t>::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) +{ + return false; } +template <> +bool +TypedArrayObjectTemplate<uint64_t>::getElementPure(TypedArrayObject* tarray, uint32_t index, Value* vp) +{ + return false; +} +} /* anonymous namespace */ + +namespace { + +template <typename NativeType> +bool TypedArrayObjectTemplate<NativeType>::getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, + MutableHandleValue val) +{ + MOZ_ALWAYS_TRUE(getElementPure(tarray, index, val.address())); + return true; +} + +template <> +bool TypedArrayObjectTemplate<int64_t>::getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, + MutableHandleValue val) +{ + int64_t n = getIndex(tarray, index); + BigInt* res = BigInt::createFromInt64(cx, n); + if (!res) { + return false; + } + val.setBigInt(res); + return true; +} + +template <> +bool TypedArrayObjectTemplate<uint64_t>::getElement(ExclusiveContext* cx, TypedArrayObject* tarray, uint32_t index, + MutableHandleValue val) +{ + uint64_t n = getIndex(tarray, index); + BigInt* res = BigInt::createFromUint64(cx, n); + if (!res) { + return false; + } + val.setBigInt(res); + return true; +} } /* anonymous namespace */ static NewObjectKind @@ -1898,10 +2083,8 @@ DataViewObject::class_constructor(JSContext* cx, unsigned argc, Value* vp) template <typename NativeType> /* static */ uint8_t* -DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, double offset) +DataViewObject::getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint64_t offset) { - MOZ_ASSERT(offset >= 0); - const size_t TypeSize = sizeof(NativeType); if (offset > UINT32_MAX - TypeSize || offset + TypeSize > obj->byteLength()) { JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_ARG_INDEX_OUT_OF_RANGE, @@ -1959,6 +2142,8 @@ template <> struct DataToRepType<int16_t> { typedef uint16_t result; }; template <> struct DataToRepType<uint16_t> { typedef uint16_t result; }; template <> struct DataToRepType<int32_t> { typedef uint32_t result; }; template <> struct DataToRepType<uint32_t> { typedef uint32_t result; }; +template <> struct DataToRepType<int64_t> { typedef uint64_t result; }; +template <> struct DataToRepType<uint64_t> { typedef uint64_t result; }; template <> struct DataToRepType<float> { typedef uint32_t result; }; template <> struct DataToRepType<double> { typedef uint64_t result; }; @@ -1987,31 +2172,6 @@ struct DataViewIO } }; -static bool -ToIndex(JSContext* cx, HandleValue v, double* index) -{ - if (v.isUndefined()) { - *index = 0.0; - return true; - } - - double integerIndex; - if (!ToInteger(cx, v, &integerIndex)) - return false; - - // Inlined version of ToLength. - // 1. Already an integer - // 2. Step eliminates < 0, +0 == -0 with SameValueZero - // 3/4. Limit to <= 2^53-1, so everything above should fail. - if (integerIndex < 0 || integerIndex > 9007199254740991) { - JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX); - return false; - } - - *index = integerIndex; - return true; -} - template<typename NativeType> /* static */ bool DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj, @@ -2021,7 +2181,7 @@ DataViewObject::read(JSContext* cx, Handle<DataViewObject*> obj, // Step 3. unnecessary assert // Step 4. - double getIndex; + uint64_t getIndex; if (!ToIndex(cx, args.get(0), &getIndex)) return false; @@ -2059,6 +2219,28 @@ WebIDLCast(JSContext* cx, HandleValue value, NativeType* out) } template <> +inline bool WebIDLCast<int64_t>(JSContext* cx, HandleValue value, int64_t* out) +{ + RootedBigInt bi(cx, ToBigInt(cx, value)); + if (!bi) { + return false; + } + *out = BigInt::toInt64(bi); + return true; +} + +template <> +inline bool WebIDLCast<uint64_t>(JSContext* cx, HandleValue value, uint64_t* out) +{ + RootedBigInt bi(cx, ToBigInt(cx, value)); + if (!bi) { + return false; + } + *out = BigInt::toUint64(bi); + return true; +} + +template <> inline bool WebIDLCast<float>(JSContext* cx, HandleValue value, float* out) { @@ -2076,6 +2258,8 @@ WebIDLCast<double>(JSContext* cx, HandleValue value, double* out) return ToNumber(cx, value, out); } +// https://tc39.github.io/ecma262/#sec-setviewvalue +// SetViewValue ( view, requestIndex, isLittleEndian, type, value ) template<typename NativeType> /* static */ bool DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj, @@ -2085,11 +2269,11 @@ DataViewObject::write(JSContext* cx, Handle<DataViewObject*> obj, // Step 3. unnecessary assert // Step 4. - double getIndex; + uint64_t getIndex; if (!ToIndex(cx, args.get(0), &getIndex)) return false; - // Step 5. Should just call ToNumber (unobservable) + // Step 5. Extended by the BigInt proposal to call either ToBigInt or ToNumber NativeType value; if (!WebIDLCast(cx, args.get(1), &value)) return false; @@ -2245,6 +2429,60 @@ DataViewObject::fun_getUint32(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod<is, getUint32Impl>(cx, args); } +// BigInt proposal 7.26 +// DataView.prototype.getBigInt64 ( byteOffset [ , littleEndian ] ) +bool DataViewObject::getBigInt64Impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); + + int64_t val; + if (!read(cx, thisView, args, &val, "getBigInt64")) { + return false; + } + + BigInt* bi = BigInt::createFromInt64(cx, val); + if (!bi) { + return false; + } + args.rval().setBigInt(bi); + return true; +} + +bool DataViewObject::fun_getBigInt64(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<is, getBigInt64Impl>(cx, args); +} + +// BigInt proposal 7.27 +// DataView.prototype.getBigUint64 ( byteOffset [ , littleEndian ] ) +bool DataViewObject::getBigUint64Impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); + + int64_t val; + if (!read(cx, thisView, args, &val, "getBigUint64")) { + return false; + } + + BigInt* bi = BigInt::createFromUint64(cx, val); + if (!bi) { + return false; + } + args.rval().setBigInt(bi); + return true; +} + +bool DataViewObject::fun_getBigUint64(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<is, getBigUint64Impl>(cx, args); +} + bool DataViewObject::getFloat32Impl(JSContext* cx, const CallArgs& args) { @@ -2409,6 +2647,48 @@ DataViewObject::fun_setUint32(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod<is, setUint32Impl>(cx, args); } +// BigInt proposal 7.28 +// DataView.prototype.setBigInt64 ( byteOffset, value [ , littleEndian ] ) +bool DataViewObject::setBigInt64Impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); + + if (!write<int64_t>(cx, thisView, args, "setBigInt64")) { + return false; + } + args.rval().setUndefined(); + return true; +} + +bool DataViewObject::fun_setBigInt64(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<is, setBigInt64Impl>(cx, args); +} + +// BigInt proposal 7.29 +// DataView.prototype.setBigUint64 ( byteOffset, value [ , littleEndian ] ) +bool DataViewObject::setBigUint64Impl(JSContext* cx, const CallArgs& args) +{ + MOZ_ASSERT(is(args.thisv())); + + Rooted<DataViewObject*> thisView(cx, &args.thisv().toObject().as<DataViewObject>()); + + if (!write<uint64_t>(cx, thisView, args, "setBigUint64")) { + return false; + } + args.rval().setUndefined(); + return true; +} + +bool DataViewObject::fun_setBigUint64(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + return CallNonGenericMethod<is, setBigUint64Impl>(cx, args); +} + bool DataViewObject::setFloat32Impl(JSContext* cx, const CallArgs& args) { @@ -2449,28 +2729,17 @@ DataViewObject::fun_setFloat64(JSContext* cx, unsigned argc, Value* vp) return CallNonGenericMethod<is, setFloat64Impl>(cx, args); } -Value -TypedArrayObject::getElement(uint32_t index) +namespace js { + +template <> +bool TypedArrayObject::getElement<CanGC>(ExclusiveContext* cx, uint32_t index, MutableHandleValue val) { switch (type()) { - case Scalar::Int8: - return Int8Array::getIndexValue(this, index); - case Scalar::Uint8: - return Uint8Array::getIndexValue(this, index); - case Scalar::Int16: - return Int16Array::getIndexValue(this, index); - case Scalar::Uint16: - return Uint16Array::getIndexValue(this, index); - case Scalar::Int32: - return Int32Array::getIndexValue(this, index); - case Scalar::Uint32: - return Uint32Array::getIndexValue(this, index); - case Scalar::Float32: - return Float32Array::getIndexValue(this, index); - case Scalar::Float64: - return Float64Array::getIndexValue(this, index); - case Scalar::Uint8Clamped: - return Uint8ClampedArray::getIndexValue(this, index); +#define GET_ELEMENT(T, N) \ + case Scalar::N: \ + return N##Array::getElement(cx, this, index, val); + JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT) +#undef GET_ELEMENT case Scalar::Int64: case Scalar::Float32x4: case Scalar::Int8x16: @@ -2483,44 +2752,23 @@ TypedArrayObject::getElement(uint32_t index) MOZ_CRASH("Unknown TypedArray type"); } -void -TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d) -{ - MOZ_ASSERT(index < obj.length()); +template <> +bool TypedArrayObject::getElement<NoGC>( + ExclusiveContext* cx, uint32_t index, + typename MaybeRooted<Value, NoGC>::MutableHandleType vp) { + return getElementPure(index, vp.address()); +} -#ifdef JS_MORE_DETERMINISTIC - // See the comment in ElementSpecific::doubleToNative. - d = JS::CanonicalizeNaN(d); -#endif +} // namespace js - switch (obj.type()) { - case Scalar::Int8: - Int8Array::setIndexValue(obj, index, d); - return; - case Scalar::Uint8: - Uint8Array::setIndexValue(obj, index, d); - return; - case Scalar::Uint8Clamped: - Uint8ClampedArray::setIndexValue(obj, index, d); - return; - case Scalar::Int16: - Int16Array::setIndexValue(obj, index, d); - return; - case Scalar::Uint16: - Uint16Array::setIndexValue(obj, index, d); - return; - case Scalar::Int32: - Int32Array::setIndexValue(obj, index, d); - return; - case Scalar::Uint32: - Uint32Array::setIndexValue(obj, index, d); - return; - case Scalar::Float32: - Float32Array::setIndexValue(obj, index, d); - return; - case Scalar::Float64: - Float64Array::setIndexValue(obj, index, d); - return; +bool TypedArrayObject::getElementPure(uint32_t index, Value* vp) +{ + switch (type()) { +#define GET_ELEMENT_PURE(T, N) \ + case Scalar::N: \ + return N##Array::getElementPure(this, index, vp); + JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENT_PURE) +#undef GET_ELEMENT case Scalar::Int64: case Scalar::Float32x4: case Scalar::Int8x16: @@ -2533,6 +2781,38 @@ TypedArrayObject::setElement(TypedArrayObject& obj, uint32_t index, double d) MOZ_CRASH("Unknown TypedArray type"); } +/* static */ +bool TypedArrayObject::getElements(JSContext* cx, Handle<TypedArrayObject*> tarray, Value* vp) +{ + uint32_t length = tarray->length(); + MOZ_ASSERT_IF(length > 0, !tarray->hasDetachedBuffer()); + + switch (tarray->type()) { +#define GET_ELEMENTS(T, N) \ + case Scalar::N: \ + for (uint32_t i = 0; i < length; ++i, ++vp) { \ + if (!N##Array::getElement(cx, tarray, i, \ + MutableHandleValue::fromMarkedLocation(vp))) { \ + return false; \ + } \ + } \ + return true; + JS_FOR_EACH_TYPED_ARRAY(GET_ELEMENTS) +#undef GET_ELEMENTS + default: + MOZ_CRASH("Unknown TypedArray type"); + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + case Scalar::Float32x4: + case Scalar::Int8x16: + case Scalar::Int16x8: + case Scalar::Int32x4: + break; + } + + MOZ_CRASH("Unknown TypedArray type"); +} + /*** *** JS impl ***/ @@ -2585,6 +2865,8 @@ IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Int32, int32_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Uint32, uint32_t) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Float32, float) IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(Float64, double) +IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(BigInt64, int64_t) +IMPL_TYPED_ARRAY_JSAPI_CONSTRUCTORS(BigUint64, uint64_t) #define IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Name, ExternalType, InternalType) \ JS_FRIEND_API(JSObject*) JS_GetObjectAs ## Name ## Array(JSObject* obj, \ @@ -2616,6 +2898,8 @@ IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Int32, int32_t, int32_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Uint32, uint32_t, uint32_t) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float32, float, float) IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(Float64, double, double) +IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(BigInt64, int64_t, int64_t) +IMPL_TYPED_ARRAY_COMBINED_UNWRAPPERS(BigUint64, uint32_t, uint64_t) static const ClassOps TypedArrayClassOps = { nullptr, /* addProperty */ @@ -2653,7 +2937,9 @@ static const JSPropertySpec static_prototype_properties[Scalar::MaxTypedArrayVie IMPL_TYPED_ARRAY_PROPERTIES(Uint32), IMPL_TYPED_ARRAY_PROPERTIES(Float32), IMPL_TYPED_ARRAY_PROPERTIES(Float64), - IMPL_TYPED_ARRAY_PROPERTIES(Uint8Clamped) + IMPL_TYPED_ARRAY_PROPERTIES(Uint8Clamped), + IMPL_TYPED_ARRAY_PROPERTIES(BigInt64), + IMPL_TYPED_ARRAY_PROPERTIES(BigUint64) }; #define IMPL_TYPED_ARRAY_CLASS_SPEC(_type) \ @@ -2677,7 +2963,9 @@ static const ClassSpec TypedArrayObjectClassSpecs[Scalar::MaxTypedArrayViewType] IMPL_TYPED_ARRAY_CLASS_SPEC(Uint32), IMPL_TYPED_ARRAY_CLASS_SPEC(Float32), IMPL_TYPED_ARRAY_CLASS_SPEC(Float64), - IMPL_TYPED_ARRAY_CLASS_SPEC(Uint8Clamped) + IMPL_TYPED_ARRAY_CLASS_SPEC(Uint8Clamped), + IMPL_TYPED_ARRAY_CLASS_SPEC(BigInt64), + IMPL_TYPED_ARRAY_CLASS_SPEC(BigUint64) }; #define IMPL_TYPED_ARRAY_CLASS(_type) \ @@ -2703,7 +2991,9 @@ const Class TypedArrayObject::classes[Scalar::MaxTypedArrayViewType] = { IMPL_TYPED_ARRAY_CLASS(Uint32), IMPL_TYPED_ARRAY_CLASS(Float32), IMPL_TYPED_ARRAY_CLASS(Float64), - IMPL_TYPED_ARRAY_CLASS(Uint8Clamped) + IMPL_TYPED_ARRAY_CLASS(Uint8Clamped), + IMPL_TYPED_ARRAY_CLASS(BigInt64), + IMPL_TYPED_ARRAY_CLASS(BigUint64) }; #define IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(_type) \ @@ -2727,7 +3017,9 @@ static const ClassSpec TypedArrayObjectProtoClassSpecs[Scalar::MaxTypedArrayView IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint32), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Float32), IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Float64), - IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint8Clamped) + IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(Uint8Clamped), + IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(BigInt64), + IMPL_TYPED_ARRAY_PROTO_CLASS_SPEC(BigUint64) }; // The various typed array prototypes are supposed to 1) be normal objects, @@ -2761,7 +3053,9 @@ const Class TypedArrayObject::protoClasses[Scalar::MaxTypedArrayViewType] = { IMPL_TYPED_ARRAY_PROTO_CLASS(Uint32), IMPL_TYPED_ARRAY_PROTO_CLASS(Float32), IMPL_TYPED_ARRAY_PROTO_CLASS(Float64), - IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8Clamped) + IMPL_TYPED_ARRAY_PROTO_CLASS(Uint8Clamped), + IMPL_TYPED_ARRAY_PROTO_CLASS(BigInt64), + IMPL_TYPED_ARRAY_PROTO_CLASS(BigUint64) }; /* static */ bool @@ -2820,6 +3114,13 @@ const JSFunctionSpec DataViewObject::jsfuncs[] = { JS_FS_END }; +const JSFunctionSpec DataViewObject::bigIntMethods[] = { + JS_FN("getBigInt64", DataViewObject::fun_getBigInt64, 1, 0), + JS_FN("getBigUint64", DataViewObject::fun_getBigUint64, 1, 0), + JS_FN("setBigInt64", DataViewObject::fun_setBigInt64, 2, 0), + JS_FN("setBigUint64", DataViewObject::fun_setBigUint64, 2, 0), + JS_FS_END}; + template<Value ValueGetter(DataViewObject* view)> bool DataViewObject::getterImpl(JSContext* cx, const CallArgs& args) @@ -2888,6 +3189,9 @@ DataViewObject::initClass(JSContext* cx) if (!JS_DefineFunctions(cx, proto, DataViewObject::jsfuncs)) return false; + if (!JS_DefineFunctions(cx, proto, DataViewObject::bigIntMethods)) + return false; + if (!DefineToStringTag(cx, proto, cx->names().DataView)) return false; @@ -3007,6 +3311,25 @@ js::StringIsTypedArrayIndex(const char16_t* s, size_t length, uint64_t* indexp); template bool js::StringIsTypedArrayIndex(const Latin1Char* s, size_t length, uint64_t* indexp); +bool js::SetTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj, + uint64_t index, HandleValue v, ObjectOpResult& result) +{ + TypedArrayObject* tobj = &obj->as<TypedArrayObject>(); + + switch (tobj->type()) { +#define SET_TYPED_ARRAY_ELEMENT(T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate<T>::setElement(cx, obj, index, v, result); + JS_FOR_EACH_TYPED_ARRAY(SET_TYPED_ARRAY_ELEMENT) +#undef SET_TYPED_ARRAY_ELEMENT + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + break; + } + + MOZ_CRASH("Unsupported TypedArray type"); +} + /* ES6 draft rev 34 (2015 Feb 20) 9.4.5.3 [[DefineOwnProperty]] step 3.c. */ bool js::DefineTypedArrayElement(JSContext* cx, HandleObject obj, uint64_t index, @@ -3042,22 +3365,18 @@ js::DefineTypedArrayElement(JSContext* cx, HandleObject obj, uint64_t index, // Step x. if (desc.hasValue()) { - // The following step numbers refer to 9.4.5.9 - // IntegerIndexedElementSet. - - // Steps 1-2 are enforced by the caller. - - // Step 3. - double numValue; - if (!ToNumber(cx, desc.value(), &numValue)) - return false; - - // Steps 4-5, 8-9. - if (obj->as<TypedArrayObject>().hasDetachedBuffer()) - return result.fail(JSMSG_TYPED_ARRAY_DETACHED); - - // Steps 10-16. - TypedArrayObject::setElement(obj->as<TypedArrayObject>(), index, numValue); + TypedArrayObject* tobj = &obj->as<TypedArrayObject>(); + switch (tobj->type()) { +#define DEFINE_TYPED_ARRAY_ELEMENT(T, N) \ + case Scalar::N: \ + return TypedArrayObjectTemplate<T>::defineElement(cx, obj, index, \ + desc.value(), result); + JS_FOR_EACH_TYPED_ARRAY(DEFINE_TYPED_ARRAY_ELEMENT) +#undef DEFINE_TYPED_ARRAY_ELEMENT + case Scalar::MaxTypedArrayViewType: + case Scalar::Int64: + break; + } } // Step xii. diff --git a/js/src/vm/TypedArrayObject.h b/js/src/vm/TypedArrayObject.h index 2616402b3d..06aaea0617 100644 --- a/js/src/vm/TypedArrayObject.h +++ b/js/src/vm/TypedArrayObject.h @@ -24,7 +24,9 @@ macro(uint32_t, Uint32) \ macro(float, Float32) \ macro(double, Float64) \ - macro(uint8_clamped, Uint8Clamped) + macro(uint8_clamped, Uint8Clamped) \ + macro(int64_t, BigInt64) \ + macro(uint64_t, BigUint64) typedef struct JSProperty JSProperty; @@ -183,8 +185,12 @@ class TypedArrayObject : public NativeObject void assertZeroLengthArrayData() const {}; #endif - Value getElement(uint32_t index); - static void setElement(TypedArrayObject& obj, uint32_t index, double d); + template <AllowGC allowGC> + bool getElement(ExclusiveContext* cx, uint32_t index, + typename MaybeRooted<Value, allowGC>::MutableHandleType val); + bool getElementPure(uint32_t index, Value* vp); + + static bool getElements(JSContext* cx, Handle<TypedArrayObject*> tarray, Value* vp); void notifyBufferDetached(JSContext* cx, void* newData); @@ -306,6 +312,8 @@ class TypedArrayObject : public NativeObject static bool is(HandleValue v); static bool set(JSContext* cx, unsigned argc, Value* vp); + + bool convertForSideEffect(JSContext* cx, HandleValue v) const; }; MOZ_MUST_USE bool TypedArray_bufferGetter(JSContext* cx, unsigned argc, Value* vp); @@ -373,6 +381,10 @@ IsTypedArrayIndex(jsid id, uint64_t* indexp) return StringIsTypedArrayIndex(s, length, indexp); } +bool SetTypedArrayElement(JSContext* cx, Handle<TypedArrayObject*> obj, + uint64_t index, HandleValue v, + ObjectOpResult& result); + /* * Implements [[DefineOwnProperty]] for TypedArrays when the property * key is a TypedArray index. @@ -396,6 +408,8 @@ TypedArrayShift(Scalar::Type viewType) case Scalar::Uint32: case Scalar::Float32: return 2; + case Scalar::BigInt64: + case Scalar::BigUint64: case Scalar::Int64: case Scalar::Float64: return 3; @@ -442,7 +456,7 @@ class DataViewObject : public NativeObject template <typename NativeType> static uint8_t* - getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, double offset); + getDataPointer(JSContext* cx, Handle<DataViewObject*> obj, uint64_t offset); template<Value ValueGetter(DataViewObject* view)> static bool @@ -521,6 +535,12 @@ class DataViewObject : public NativeObject static bool getUint32Impl(JSContext* cx, const CallArgs& args); static bool fun_getUint32(JSContext* cx, unsigned argc, Value* vp); + static bool getBigInt64Impl(JSContext* cx, const CallArgs& args); + static bool fun_getBigInt64(JSContext* cx, unsigned argc, Value* vp); + + static bool getBigUint64Impl(JSContext* cx, const CallArgs& args); + static bool fun_getBigUint64(JSContext* cx, unsigned argc, Value* vp); + static bool getFloat32Impl(JSContext* cx, const CallArgs& args); static bool fun_getFloat32(JSContext* cx, unsigned argc, Value* vp); @@ -545,6 +565,12 @@ class DataViewObject : public NativeObject static bool setUint32Impl(JSContext* cx, const CallArgs& args); static bool fun_setUint32(JSContext* cx, unsigned argc, Value* vp); + static bool setBigInt64Impl(JSContext* cx, const CallArgs& args); + static bool fun_setBigInt64(JSContext* cx, unsigned argc, Value* vp); + + static bool setBigUint64Impl(JSContext* cx, const CallArgs& args); + static bool fun_setBigUint64(JSContext* cx, unsigned argc, Value* vp); + static bool setFloat32Impl(JSContext* cx, const CallArgs& args); static bool fun_setFloat32(JSContext* cx, unsigned argc, Value* vp); @@ -564,6 +590,10 @@ class DataViewObject : public NativeObject private: static const JSFunctionSpec jsfuncs[]; + + static const JSFunctionSpec bigIntMethods[]; + static bool finishInit(JSContext* cx, JS::HandleObject ctor, + JS::HandleObject proto); }; static inline int32_t diff --git a/js/src/vm/UbiNode.cpp b/js/src/vm/UbiNode.cpp index ab966cbb4d..3a3b1ee2bf 100644 --- a/js/src/vm/UbiNode.cpp +++ b/js/src/vm/UbiNode.cpp @@ -23,6 +23,7 @@ #include "js/TypeDecls.h" #include "js/Utility.h" #include "js/Vector.h" +#include "vm/BigIntType.h" #include "vm/Debugger.h" #include "vm/EnvironmentObject.h" #include "vm/GlobalObject.h" @@ -202,7 +203,11 @@ Node::exposeToJS() const v.setString(as<JSString>()); } else if (is<JS::Symbol>()) { v.setSymbol(as<JS::Symbol>()); - } else { + } + else if (is<BigInt>()) { + v.setBigInt(as<BigInt>()); + } + else { v.setUndefined(); } @@ -314,6 +319,7 @@ template JS::Zone* TracerConcrete<js::ObjectGroup>::zone() const; template JS::Zone* TracerConcrete<js::RegExpShared>::zone() const; template JS::Zone* TracerConcrete<js::Scope>::zone() const; template JS::Zone* TracerConcrete<JS::Symbol>::zone() const; +template JS::Zone* TracerConcrete<BigInt>::zone() const; template JS::Zone* TracerConcrete<JSString>::zone() const; template<typename Referent> @@ -337,6 +343,7 @@ template UniquePtr<EdgeRange> TracerConcrete<js::ObjectGroup>::edges(JSContext* template UniquePtr<EdgeRange> TracerConcrete<js::RegExpShared>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<js::Scope>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<JS::Symbol>::edges(JSContext* cx, bool wantNames) const; +template UniquePtr<EdgeRange> TracerConcrete<BigInt>::edges(JSContext* cx, bool wantNames) const; template UniquePtr<EdgeRange> TracerConcrete<JSString>::edges(JSContext* cx, bool wantNames) const; template<typename Referent> @@ -392,6 +399,7 @@ Concrete<JSObject>::jsObjectConstructorName(JSContext* cx, UniqueTwoByteChars& o } const char16_t Concrete<JS::Symbol>::concreteTypeName[] = u"JS::Symbol"; +const char16_t Concrete<BigInt>::concreteTypeName[] = u"JS::BigInt"; const char16_t Concrete<JSScript>::concreteTypeName[] = u"JSScript"; const char16_t Concrete<js::LazyScript>::concreteTypeName[] = u"js::LazyScript"; const char16_t Concrete<js::jit::JitCode>::concreteTypeName[] = u"js::jit::JitCode"; diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index a91b0e804d..3eee8c4c18 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -8708,7 +8708,7 @@ js::CompileAsmJS(ExclusiveContext* cx, AsmJSParser& parser, ParseNode* stmtList, // generating bytecode for asm.js functions, allowing this asm.js module // function to be the finished result. MOZ_ASSERT(funbox->function()->isInterpreted()); - funbox->object = moduleFun; + funbox->clobberFunction(moduleFun); // Success! Write to the console with a "warning" message. *validated = true; diff --git a/js/src/wasm/WasmTextUtils.cpp b/js/src/wasm/WasmTextUtils.cpp index aab3deb4b2..78367f3984 100644 --- a/js/src/wasm/WasmTextUtils.cpp +++ b/js/src/wasm/WasmTextUtils.cpp @@ -54,7 +54,7 @@ template<class T> bool js::wasm::RenderNaN(StringBuffer& sb, Raw<T> num) { - typedef typename mozilla::SelectTrait<T> Traits; + typedef typename mozilla::FloatingPoint<T> Traits; MOZ_ASSERT(IsNaN(num.fp())); diff --git a/js/xpconnect/src/XPCVariant.cpp b/js/xpconnect/src/XPCVariant.cpp index f8363993c7..c663e7ebf2 100644 --- a/js/xpconnect/src/XPCVariant.cpp +++ b/js/xpconnect/src/XPCVariant.cpp @@ -179,7 +179,7 @@ XPCArrayHomogenizer::GetTypeForArray(JSContext* cx, HandleObject array, type = tDbl; } else if (val.isBoolean()) { type = tBool; - } else if (val.isUndefined() || val.isSymbol()) { + } else if (val.isUndefined() || val.isSymbol() || val.isBigInt()) { state = tVar; break; } else if (val.isNull()) { @@ -187,7 +187,7 @@ XPCArrayHomogenizer::GetTypeForArray(JSContext* cx, HandleObject array, } else if (val.isString()) { type = tStr; } else { - MOZ_ASSERT(val.isObject(), "invalid type of jsval!"); + MOZ_RELEASE_ASSERT(val.isObject(), "invalid type of jsval!"); jsobj = &val.toObject(); bool isArray; @@ -273,8 +273,8 @@ bool XPCVariant::InitializeData(JSContext* cx) mData.SetFromBool(val.toBoolean()); return true; } - // We can't represent symbol on C++ side, so pretend it is void. - if (val.isUndefined() || val.isSymbol()) { + // We can't represent symbol or BigInt on C++ side, so pretend it is void. + if (val.isUndefined() || val.isSymbol() || val.isBigInt()) { mData.SetToVoid(); return true; } @@ -302,7 +302,7 @@ bool XPCVariant::InitializeData(JSContext* cx) } // leaving only JSObject... - MOZ_ASSERT(val.isObject(), "invalid type of jsval!"); + MOZ_RELEASE_ASSERT(val.isObject(), "invalid type of jsval!"); RootedObject jsobj(cx, &val.toObject()); diff --git a/layout/generic/ReflowInput.cpp b/layout/generic/ReflowInput.cpp index 1af7e798ef..4f5424dc44 100644 --- a/layout/generic/ReflowInput.cpp +++ b/layout/generic/ReflowInput.cpp @@ -226,6 +226,7 @@ ReflowInput::ReflowInput( mFlags.mAssumingHScrollbar = mFlags.mAssumingVScrollbar = false; mFlags.mIsColumnBalancing = false; mFlags.mIsFlexContainerMeasuringHeight = false; + mFlags.mTreatBSizeAsIndefinite = false; mFlags.mDummyParentReflowInput = false; mFlags.mShrinkWrap = !!(aFlags & COMPUTE_SIZE_SHRINK_WRAP); mFlags.mUseAutoBSize = !!(aFlags & COMPUTE_SIZE_USE_AUTO_BSIZE); @@ -2041,6 +2042,10 @@ ReflowInput::ComputeContainingBlockRectangle( WritingMode wm = aContainingBlockRI->GetWritingMode(); + if (aContainingBlockRI->mFlags.mTreatBSizeAsIndefinite) { + cbSize.BSize(wm) = NS_UNCONSTRAINEDSIZE; + } + // mFrameType for abs-pos tables is NS_CSS_FRAME_TYPE_BLOCK, so we need to // special case them here. if (NS_FRAME_GET_TYPE(mFrameType) == NS_CSS_FRAME_TYPE_ABSOLUTE || @@ -2077,12 +2082,13 @@ ReflowInput::ComputeContainingBlockRectangle( } } else { // an element in quirks mode gets a containing block based on looking for a - // parent with a non-auto height if the element has a percent height - // Note: We don't emulate this quirk for percents in calc() or in - // vertical writing modes. + // parent with a non-auto height if the element has a percent height. + // Note: We don't emulate this quirk for percents in calc(), or in vertical + // writing modes, or if the containing block is a flex or grid item. if (!wm.IsVertical() && NS_AUTOHEIGHT == cbSize.BSize(wm)) { if (eCompatibility_NavQuirks == aPresContext->CompatibilityMode() && + !aContainingBlockRI->mFrame->IsFlexOrGridItem() && mStylePosition->mHeight.GetUnit() == eStyleUnit_Percent) { cbSize.BSize(wm) = CalcQuirkContainingBlockHeight(aContainingBlockRI); } @@ -2218,7 +2224,8 @@ ReflowInput::InitConstraints(nsPresContext* aPresContext, // in quirks mode, get the cb height using the special quirk method if (!wm.IsVertical() && eCompatibility_NavQuirks == aPresContext->CompatibilityMode()) { - if (!IS_TABLE_CELL(fType)) { + if (!IS_TABLE_CELL(fType) && + !cbrs->mFrame->IsFlexOrGridItem()) { cbSize.BSize(wm) = CalcQuirkContainingBlockHeight(cbrs); if (cbSize.BSize(wm) == NS_AUTOHEIGHT) { blockSizeUnit = eStyleUnit_Auto; diff --git a/layout/generic/ReflowInput.h b/layout/generic/ReflowInput.h index a70549d8eb..52c90e6eeb 100644 --- a/layout/generic/ReflowInput.h +++ b/layout/generic/ReflowInput.h @@ -206,6 +206,13 @@ public: uint32_t mIsFlexContainerMeasuringHeight:1; // nsFlexContainerFrame is // reflowing this child to // measure its intrinsic height. + uint32_t mTreatBSizeAsIndefinite:1; // If this flag is set, the BSize of this frame should be considered + // indefinite for the purposes of percent resolution on child frames (we + // should behave as if ComputedBSize() were NS_INTRINSIC_ISIZE when doing + // percent resolution against this.ComputedBSize()). For example: flex + // items may have their ComputedBSize() resolved ahead-of-time by their + // flex container, and yet their BSize might have to be considered + // indefinite per https://drafts.csswg.org/css-flexbox/#definite-sizes uint32_t mDummyParentReflowInput:1; // a "fake" reflow state made // in order to be the parent // of a real one diff --git a/layout/generic/nsFlexContainerFrame.cpp b/layout/generic/nsFlexContainerFrame.cpp index 18a0643f14..d60308f424 100644 --- a/layout/generic/nsFlexContainerFrame.cpp +++ b/layout/generic/nsFlexContainerFrame.cpp @@ -557,6 +557,8 @@ public: return mFlexShrink * mFlexBaseSize; } + bool TreatBSizeAsIndefinite() const { return mTreatBSizeAsIndefinite; } + const AspectRatio IntrinsicRatio() const { return mIntrinsicRatio; } bool HasIntrinsicRatio() const { return !!mIntrinsicRatio; } @@ -799,6 +801,9 @@ protected: // Does this item need to resolve a min-[width|height]:auto (in main-axis). bool mNeedsMinSizeAutoResolution; + // Should we take care to treat this item's resolved BSize as indefinite? + bool mTreatBSizeAsIndefinite; + const WritingMode mWM; // The flex item's writing mode. uint8_t mAlignSelf; // My "align-self" computed value (with "auto" // swapped out for parent"s "align-items" value, @@ -1990,6 +1995,40 @@ FlexItem::FlexItem(ReflowInput& aFlexItemReflowInput, mAlignSelf &= ~NS_STYLE_ALIGN_FLAG_BITS; } + // Our main-size is considered definite if any of these are true: + // (a) flex container has definite main size. + // (b) flex item has a definite flex basis and is fully inflexible + // (NOTE: We don't actually check "fully inflexible" because webcompat + // may not agree with that restriction...) + // + // Hence, we need to take care to treat the final main-size as *indefinite* + // if none of these conditions are satisfied. + + // The flex item's main size is its BSize, and is considered definite under + // certain conditions laid out for definite flex-item main-sizes in the spec. + if (aAxisTracker.IsMainAxisHorizontal() || + (containerRS->ComputedBSize() != NS_UNCONSTRAINEDSIZE && + !containerRS->mFlags.mTreatBSizeAsIndefinite)) { + // The flex *container* has a definite main-size (either by being + // row-oriented [and using its own inline size which is by definition + // definite, or by being column-oriented and having a definite + // block-size). The spec says this means all of the flex items' + // post-flexing main sizes should *also* be treated as definite. + mTreatBSizeAsIndefinite = false; + } else if (aFlexBaseSize != NS_UNCONSTRAINEDSIZE) { + // The flex item has a definite flex basis, which we'll treat as making + // its main-size definite. + // XXXdholbert Technically the spec requires the flex item to *also* be + // fully inflexible in order to have its size treated as definite in this + // scenario, but no browser implements that additional restriction, so + // it's not clear that this additional requirement would be + // web-compatible... + mTreatBSizeAsIndefinite = false; + } else { + // Otherwise, we have to treat the item's BSize as indefinite. + mTreatBSizeAsIndefinite = true; + } + SetFlexBaseSizeAndMainSize(aFlexBaseSize); CheckForMinSizeAuto(aFlexItemReflowInput, aAxisTracker); @@ -2054,6 +2093,7 @@ FlexItem::FlexItem(nsIFrame* aChildFrame, nscoord aCrossSize, mIsStretched(false), mIsStrut(true), // (this is the constructor for making struts, after all) mNeedsMinSizeAutoResolution(false), + mTreatBSizeAsIndefinite(false), mWM(aContainerWM), mAlignSelf(NS_STYLE_ALIGN_FLEX_START) { @@ -4463,6 +4503,9 @@ nsFlexContainerFrame::DoFlexLayout(nsPresContext* aPresContext, childReflowInput.SetComputedWidth(item->GetMainSize()); } else { childReflowInput.SetComputedHeight(item->GetMainSize()); + if (item->TreatBSizeAsIndefinite()) { + childReflowInput.mFlags.mTreatBSizeAsIndefinite = true; + } } } @@ -4802,6 +4845,9 @@ nsFlexContainerFrame::ReflowFlexItem(nsPresContext* aPresContext, } else { childReflowInput.SetComputedHeight(aItem.GetMainSize()); didOverrideComputedHeight = true; + if (aItem.TreatBSizeAsIndefinite()) { + childReflowInput.mFlags.mTreatBSizeAsIndefinite = true; + } } // Override reflow state's computed cross-size if either: @@ -4819,6 +4865,10 @@ nsFlexContainerFrame::ReflowFlexItem(nsPresContext* aPresContext, childReflowInput.SetComputedWidth(aItem.GetCrossSize()); didOverrideComputedWidth = true; } else { + // Note that in the above cases we don't need to worry about the BSize + // needing to be treated as indefinite, because this is for cases where + // the block size would always be considered definite (or where its + // definiteness would be irrelevant). childReflowInput.SetComputedHeight(aItem.GetCrossSize()); didOverrideComputedHeight = true; } diff --git a/layout/reftests/bugs/1128354-1-ref.html b/layout/reftests/bugs/1128354-1-ref.html index a559370f22..a90b26e281 100644 --- a/layout/reftests/bugs/1128354-1-ref.html +++ b/layout/reftests/bugs/1128354-1-ref.html @@ -13,10 +13,21 @@ } .flexInnerHoriz { - display: flex; + height: -moz-fit-content; + margin: 0 0 auto 0; background: pink; } - + .height50pct { + height: -moz-fit-content; + margin: 0 0 auto 0; + background: brown; + } + .height0pct { + height: -moz-fit-content; + margin: 0 0 auto 0; + background: yellow; + } + .spacer { background: lightblue; height: 200px; @@ -28,6 +39,8 @@ <div class="flexVert"> <div class="flexIntermediateHoriz"> <div class="flexInnerHoriz">text</div> + <div class="height50pct">fifty</div> + <div class="height0pct">zero</div> <div class="spacer"></div> </div> </div> diff --git a/layout/reftests/bugs/1128354-1.html b/layout/reftests/bugs/1128354-1.html index c83bcf8b25..7c6dc87272 100644 --- a/layout/reftests/bugs/1128354-1.html +++ b/layout/reftests/bugs/1128354-1.html @@ -14,11 +14,26 @@ .flexInnerHoriz { display: flex; - height: 100%; /* If you delete this line, then pink area is stretched - to have its height match blue area. */ + /* This percent should not be resolvable, because our parent's + * height is indefinite (because our parent is a flex item with an + * indefinite flex basis, in an indefinite-main-sized flex container). + * So we just end up with our content height. + */ + height: 100%; background: pink; } - + .height50pct { + /* This percent should not be resolvable, for the same reason as above. + */ + height: 50%; + background: brown; + } + .height0pct { + /* This percent should not be resolvable, for the same reason as above. + */ + height: 0%; + background: yellow; + } .spacer { background: lightblue; height: 200px; @@ -30,6 +45,8 @@ <div class="flexVert"> <div class="flexIntermediateHoriz"> <div class="flexInnerHoriz">text</div> + <div class="height50pct">fifty</div> + <div class="height0pct">zero</div> <div class="spacer"></div> </div> </div> diff --git a/layout/style/nsCSSPropList.h b/layout/style/nsCSSPropList.h index f2903273e5..0694817515 100644 --- a/layout/style/nsCSSPropList.h +++ b/layout/style/nsCSSPropList.h @@ -366,14 +366,14 @@ CSS_PROP_SHORTHAND( animation, Animation, CSS_PROPERTY_PARSE_FUNCTION, - "") + "layout.css.animation.enabled") CSS_PROP_DISPLAY( animation-delay, animation_delay, AnimationDelay, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_TIME, // used by list parsing nullptr, CSS_PROP_NO_OFFSET, @@ -384,7 +384,7 @@ CSS_PROP_DISPLAY( AnimationDirection, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD, // used by list parsing kAnimationDirectionKTable, CSS_PROP_NO_OFFSET, @@ -395,7 +395,7 @@ CSS_PROP_DISPLAY( AnimationDuration, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_TIME | VARIANT_NONNEGATIVE_DIMENSION, // used by list parsing nullptr, CSS_PROP_NO_OFFSET, @@ -406,7 +406,7 @@ CSS_PROP_DISPLAY( AnimationFillMode, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD, // used by list parsing kAnimationFillModeKTable, CSS_PROP_NO_OFFSET, @@ -420,7 +420,7 @@ CSS_PROP_DISPLAY( // http://lists.w3.org/Archives/Public/www-style/2011Mar/0355.html CSS_PROPERTY_VALUE_NONNEGATIVE | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD | VARIANT_NUMBER, // used by list parsing kAnimationIterationCountKTable, CSS_PROP_NO_OFFSET, @@ -431,7 +431,7 @@ CSS_PROP_DISPLAY( AnimationName, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", // FIXME: The spec should say something about 'inherit' and 'initial' // not being allowed. VARIANT_NONE | VARIANT_IDENTIFIER_NO_INHERIT, // used by list parsing @@ -444,7 +444,7 @@ CSS_PROP_DISPLAY( AnimationPlayState, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD, // used by list parsing kAnimationPlayStateKTable, CSS_PROP_NO_OFFSET, @@ -455,7 +455,7 @@ CSS_PROP_DISPLAY( AnimationTimingFunction, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.animation.enabled", VARIANT_KEYWORD | VARIANT_TIMING_FUNCTION, // used by list parsing kTransitionTimingFunctionKTable, CSS_PROP_NO_OFFSET, @@ -4277,14 +4277,14 @@ CSS_PROP_SHORTHAND( transition, Transition, CSS_PROPERTY_PARSE_FUNCTION, - "") + "layout.css.transition.enabled") CSS_PROP_DISPLAY( transition-delay, transition_delay, TransitionDelay, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.transition.enabled", VARIANT_TIME, // used by list parsing nullptr, CSS_PROP_NO_OFFSET, @@ -4295,7 +4295,7 @@ CSS_PROP_DISPLAY( TransitionDuration, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.transition.enabled", VARIANT_TIME | VARIANT_NONNEGATIVE_DIMENSION, // used by list parsing nullptr, CSS_PROP_NO_OFFSET, @@ -4306,7 +4306,7 @@ CSS_PROP_DISPLAY( TransitionProperty, CSS_PROPERTY_PARSE_FUNCTION | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.transition.enabled", VARIANT_IDENTIFIER | VARIANT_NONE | VARIANT_ALL, // used only in shorthand nullptr, CSS_PROP_NO_OFFSET, @@ -4317,7 +4317,7 @@ CSS_PROP_DISPLAY( TransitionTimingFunction, CSS_PROPERTY_PARSE_VALUE_LIST | CSS_PROPERTY_VALUE_LIST_USES_COMMAS, - "", + "layout.css.transition.enabled", VARIANT_KEYWORD | VARIANT_TIMING_FUNCTION, // used by list parsing kTransitionTimingFunctionKTable, CSS_PROP_NO_OFFSET, diff --git a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp index e6db06a685..37dcf05005 100755 --- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp +++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp @@ -105,7 +105,7 @@ WebrtcVideoConduit::~WebrtcVideoConduit() // Release AudioConduit first by dropping reference on MainThread, where it expects to be SyncTo(nullptr); - MOZ_ASSERT(!mSendStream && !mRecvStream, "Call DeleteStreams prior to ~WebrtcVideoConduit."); + MOZ_ASSERT(!mPtrViEBase, "Call DeleteStreams prior to ~WebrtcVideoConduit."); } bool WebrtcVideoConduit::SetLocalSSRC(unsigned int ssrc) diff --git a/mfbt/Attributes.h b/mfbt/Attributes.h index 9bce9a128a..f5572e1861 100644 --- a/mfbt/Attributes.h +++ b/mfbt/Attributes.h @@ -301,6 +301,130 @@ #else # define MOZ_FALLTHROUGH /* FALLTHROUGH */ #endif +/* + * MOZ_ASAN_BLACKLIST is a macro to tell AddressSanitizer (a compile-time + * instrumentation shipped with Clang and GCC) to not instrument the annotated + * function. Furthermore, it will prevent the compiler from inlining the + * function because inlining currently breaks the blacklisting mechanism of + * AddressSanitizer. + */ +#if defined(__has_feature) +# if __has_feature(address_sanitizer) +# define MOZ_HAVE_ASAN_BLACKLIST +# endif +#elif defined(__GNUC__) +# if defined(__SANITIZE_ADDRESS__) +# define MOZ_HAVE_ASAN_BLACKLIST +# endif +#endif + +#if defined(MOZ_HAVE_ASAN_BLACKLIST) +# define MOZ_ASAN_BLACKLIST \ + MOZ_NEVER_INLINE __attribute__((no_sanitize_address)) +#else +# define MOZ_ASAN_BLACKLIST /* nothing */ +#endif + +/* + * MOZ_TSAN_BLACKLIST is a macro to tell ThreadSanitizer (a compile-time + * instrumentation shipped with Clang) to not instrument the annotated function. + * Furthermore, it will prevent the compiler from inlining the function because + * inlining currently breaks the blacklisting mechanism of ThreadSanitizer. + */ +#if defined(__has_feature) +# if __has_feature(thread_sanitizer) +# define MOZ_TSAN_BLACKLIST \ + MOZ_NEVER_INLINE __attribute__((no_sanitize_thread)) +# else +# define MOZ_TSAN_BLACKLIST /* nothing */ +# endif +#else +# define MOZ_TSAN_BLACKLIST /* nothing */ +#endif + +#if defined(__has_attribute) +# if __has_attribute(no_sanitize) +# define MOZ_HAVE_NO_SANITIZE_ATTR +# endif +#endif + +#ifdef __clang__ +# ifdef MOZ_HAVE_NO_SANITIZE_ATTR +# define MOZ_HAVE_UNSIGNED_OVERFLOW_SANITIZE_ATTR +# define MOZ_HAVE_SIGNED_OVERFLOW_SANITIZE_ATTR +# endif +#endif + +/* + * MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW disables *un*signed integer overflow + * checking on the function it annotates, in builds configured to perform it. + * (Currently this is only Clang using -fsanitize=unsigned-integer-overflow, or + * via --enable-unsigned-overflow-sanitizer in Mozilla's build system.) It has + * no effect in other builds. + * + * Place this attribute at the very beginning of a function declaration. + * + * Unsigned integer overflow isn't *necessarily* a bug. It's well-defined in + * C/C++, and code may reasonably depend upon it. For example, + * + * MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW inline bool + * IsDecimal(char aChar) + * { + * // For chars less than '0', unsigned integer underflow occurs, to a value + * // much greater than 10, so the overall test is false. + * // For chars greater than '0', no overflow occurs, and only '0' to '9' + * // pass the overall test. + * return static_cast<unsigned int>(aChar) - '0' < 10; + * } + * + * But even well-defined unsigned overflow often causes bugs when it occurs, so + * it should be restricted to functions annotated with this attribute. + * + * The compiler instrumentation to detect unsigned integer overflow has costs + * both at compile time and at runtime. Functions that are repeatedly inlined + * at compile time will also implicitly inline the necessary instrumentation, + * increasing compile time. Similarly, frequently-executed functions that + * require large amounts of instrumentation will also notice significant runtime + * slowdown to execute that instrumentation. Use this attribute to eliminate + * those costs -- but only after carefully verifying that no overflow can occur. + */ +#ifdef MOZ_HAVE_UNSIGNED_OVERFLOW_SANITIZE_ATTR +# define MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW \ + __attribute__((no_sanitize("unsigned-integer-overflow"))) +#else +# define MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW /* nothing */ +#endif + +/* + * MOZ_NO_SANITIZE_SIGNED_OVERFLOW disables *signed* integer overflow checking + * on the function it annotates, in builds configured to perform it. (Currently + * this is only Clang using -fsanitize=signed-integer-overflow, or via + * --enable-signed-overflow-sanitizer in Mozilla's build system. GCC support + * will probably be added in the future.) It has no effect in other builds. + * + * Place this attribute at the very beginning of a function declaration. + * + * Signed integer overflow is undefined behavior in C/C++: *anything* can happen + * when it occurs. *Maybe* wraparound behavior will occur, but maybe also the + * compiler will assume no overflow happens and will adversely optimize the rest + * of your code. Code that contains signed integer overflow needs to be fixed. + * + * The compiler instrumentation to detect signed integer overflow has costs both + * at compile time and at runtime. Functions that are repeatedly inlined at + * compile time will also implicitly inline the necessary instrumentation, + * increasing compile time. Similarly, frequently-executed functions that + * require large amounts of instrumentation will also notice significant runtime + * slowdown to execute that instrumentation. Use this attribute to eliminate + * those costs -- but only after carefully verifying that no overflow can occur. + */ +#ifdef MOZ_HAVE_SIGNED_OVERFLOW_SANITIZE_ATTR +# define MOZ_NO_SANITIZE_SIGNED_OVERFLOW \ + __attribute__((no_sanitize("signed-integer-overflow"))) +#else +# define MOZ_NO_SANITIZE_SIGNED_OVERFLOW /* nothing */ +#endif + +#undef MOZ_HAVE_NO_SANITIZE_ATTR /* * The following macros are attributes that support the static analysis plugin diff --git a/mfbt/FloatingPoint.h b/mfbt/FloatingPoint.h index 7d73d7e848..a2846ce298 100644 --- a/mfbt/FloatingPoint.h +++ b/mfbt/FloatingPoint.h @@ -33,33 +33,38 @@ namespace mozilla { * compiler bustage, particularly PGO-specific bustage. */ -struct FloatTypeTraits +namespace detail { + +/* + * These implementations assume float/double are 32/64-bit single/double + * format number types compatible with the IEEE-754 standard. C++ doesn't + * require this, but we required it in implementations of these algorithms that + * preceded this header, so we shouldn't break anything to continue doing so. + */ +template<typename T> +struct FloatingPointTrait; + +template<> +struct FloatingPointTrait<float> { +protected: typedef uint32_t Bits; - static const unsigned kExponentBias = 127; - static const unsigned kExponentShift = 23; - - static const Bits kSignBit = 0x80000000UL; - static const Bits kExponentBits = 0x7F800000UL; - static const Bits kSignificandBits = 0x007FFFFFUL; + static constexpr unsigned kExponentWidth = 8; + static constexpr unsigned kSignificandWidth = 23; }; -struct DoubleTypeTraits +template<> +struct FloatingPointTrait<double> { +protected: typedef uint64_t Bits; - static const unsigned kExponentBias = 1023; - static const unsigned kExponentShift = 52; - - static const Bits kSignBit = 0x8000000000000000ULL; - static const Bits kExponentBits = 0x7ff0000000000000ULL; - static const Bits kSignificandBits = 0x000fffffffffffffULL; + static constexpr unsigned kExponentWidth = 11; + static constexpr unsigned kSignificandWidth = 52; }; -template<typename T> struct SelectTrait; -template<> struct SelectTrait<float> : public FloatTypeTraits {}; -template<> struct SelectTrait<double> : public DoubleTypeTraits {}; +} // namespace detail /* * This struct contains details regarding the encoding of floating-point @@ -88,30 +93,65 @@ template<> struct SelectTrait<double> : public DoubleTypeTraits {}; * http://en.wikipedia.org/wiki/Floating_point#IEEE_754:_floating_point_in_modern_computers */ template<typename T> -struct FloatingPoint : public SelectTrait<T> +struct FloatingPoint final : private detail::FloatingPointTrait<T> { - typedef SelectTrait<T> Base; - typedef typename Base::Bits Bits; +private: + using Base = detail::FloatingPointTrait<T>; + +public: + /** + * An unsigned integral type suitable for accessing the bitwise representation + * of T. + */ + using Bits = typename Base::Bits; + + static_assert(sizeof(T) == sizeof(Bits), "Bits must be same size as T"); - static_assert((Base::kSignBit & Base::kExponentBits) == 0, + /** The bit-width of the exponent component of T. */ + using Base::kExponentWidth; + + /** The bit-width of the significand component of T. */ + using Base::kSignificandWidth; + + static_assert(1 + kExponentWidth + kSignificandWidth == + CHAR_BIT * sizeof(T), + "sign bit plus bit widths should sum to overall bit width"); + + /** + * The exponent field in an IEEE-754 floating point number consists of bits + * encoding an unsigned number. The *actual* represented exponent (for all + * values finite and not denormal) is that value, minus a bias |kExponentBias| + * so that a useful range of numbers is represented. + */ + static constexpr unsigned kExponentBias = (1U << (kExponentWidth - 1)) - 1; + + /** + * The amount by which the bits of the exponent-field in an IEEE-754 floating + * point number are shifted from the LSB of the floating point type. + */ + static constexpr unsigned kExponentShift = kSignificandWidth; + + /** The sign bit in the floating point representation. */ + static constexpr Bits kSignBit = + static_cast<Bits>(1) << (CHAR_BIT * sizeof(Bits) - 1); + + /** The exponent bits in the floating point representation. */ + static constexpr Bits kExponentBits = + ((static_cast<Bits>(1) << kExponentWidth) - 1) << kSignificandWidth; + + /** The significand bits in the floating point representation. */ + static constexpr Bits kSignificandBits = + (static_cast<Bits>(1) << kSignificandWidth) - 1; + + static_assert((kSignBit & kExponentBits) == 0, "sign bit shouldn't overlap exponent bits"); - static_assert((Base::kSignBit & Base::kSignificandBits) == 0, + static_assert((kSignBit & kSignificandBits) == 0, "sign bit shouldn't overlap significand bits"); - static_assert((Base::kExponentBits & Base::kSignificandBits) == 0, + static_assert((kExponentBits & kSignificandBits) == 0, "exponent bits shouldn't overlap significand bits"); - static_assert((Base::kSignBit | Base::kExponentBits | Base::kSignificandBits) == - ~Bits(0), + static_assert((kSignBit | kExponentBits | kSignificandBits) == ~Bits(0), "all bits accounted for"); - - /* - * These implementations assume float/double are 32/64-bit single/double - * format number types compatible with the IEEE-754 standard. C++ don't - * require this to be the case. But we required this in implementations of - * these algorithms that preceded this header, so we shouldn't break anything - * if we keep doing so. - */ - static_assert(sizeof(T) == sizeof(Bits), "Bits must be same size as T"); }; /** Determines whether a float/double is NaN. */ diff --git a/mfbt/HashFunctions.h b/mfbt/HashFunctions.h index cc9a1d68c1..d287081174 100644 --- a/mfbt/HashFunctions.h +++ b/mfbt/HashFunctions.h @@ -51,6 +51,7 @@ #include "mozilla/Char16.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Types.h" +#include "mozilla/WrappingOperations.h" #include <stdint.h> @@ -95,7 +96,9 @@ AddU32ToHash(uint32_t aHash, uint32_t aValue) * Otherwise, if |aHash| is 0 (as it often is for the beginning of a * message), the expression * - * (kGoldenRatioU32 * RotateBitsLeft(aHash, 5)) |xor| aValue + * mozilla::WrappingMultiply(kGoldenRatioU32, RotateBitsLeft(aHash, 5)) + * |xor| + * aValue * * evaluates to |aValue|. * @@ -113,7 +116,8 @@ AddU32ToHash(uint32_t aHash, uint32_t aValue) * multiplicative effect. Our golden ratio constant has order 2^29, which is * more than enough for our purposes.) */ - return kGoldenRatioU32 * (RotateBitsLeft32(aHash, 5) ^ aValue); + return mozilla::WrappingMultiply(kGoldenRatioU32, + (RotateBitsLeft32(aHash, 5) ^ aValue)); } /** diff --git a/mfbt/WrappingOperations.h b/mfbt/WrappingOperations.h new file mode 100644 index 0000000000..c93626f653 --- /dev/null +++ b/mfbt/WrappingOperations.h @@ -0,0 +1,188 @@ +/* -*- Mode: C++; tab-width: 8; 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/. */ + +/* + * Math operations that implement wraparound semantics on overflow or underflow + * without performing C++ undefined behavior or tripping up compiler-based + * integer-overflow sanitizers. + */ + +#ifndef mozilla_WrappingOperations_h +#define mozilla_WrappingOperations_h + +#include "mozilla/Attributes.h" +#include "mozilla/TypeTraits.h" + +#include <limits.h> + +namespace mozilla { + +namespace detail { + +template<typename UnsignedType> +struct WrapToSignedHelper +{ + static_assert(mozilla::IsUnsigned<UnsignedType>::value, + "WrapToSigned must be passed an unsigned type"); + + using SignedType = typename mozilla::MakeSigned<UnsignedType>::Type; + + static constexpr SignedType MaxValue = + (UnsignedType(1) << (CHAR_BIT * sizeof(SignedType) - 1)) - 1; + static constexpr SignedType MinValue = -MaxValue - 1; + + static constexpr UnsignedType MinValueUnsigned = + static_cast<UnsignedType>(MinValue); + static constexpr UnsignedType MaxValueUnsigned = + static_cast<UnsignedType>(MaxValue); + + // Overflow-correctness was proven in bug 1432646 and is explained in the + // comment below. This function is very hot, both at compile time and + // runtime, so disable all overflow checking in it. + MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW MOZ_NO_SANITIZE_SIGNED_OVERFLOW + static constexpr SignedType compute(UnsignedType aValue) + { + // This algorithm was originally provided here: + // https://stackoverflow.com/questions/13150449/efficient-unsigned-to-signed-cast-avoiding-implementation-defined-behavior + // + // If the value is in the non-negative signed range, just cast. + // + // If the value will be negative, compute its delta from the first number + // past the max signed integer, then add that to the minimum signed value. + // + // At the low end: if |u| is the maximum signed value plus one, then it has + // the same mathematical value as |MinValue| cast to unsigned form. The + // delta is zero, so the signed form of |u| is |MinValue| -- exactly the + // result of adding zero delta to |MinValue|. + // + // At the high end: if |u| is the maximum *unsigned* value, then it has all + // bits set. |MinValue| cast to unsigned form is purely the high bit set. + // So the delta is all bits but high set -- exactly |MaxValue|. And as + // |MinValue = -MaxValue - 1|, we have |MaxValue + (-MaxValue - 1)| to + // equal -1. + // + // Thus the delta below is in signed range, the corresponding cast is safe, + // and this computation produces values spanning [MinValue, 0): exactly the + // desired range of all negative signed integers. + return (aValue <= MaxValueUnsigned) + ? static_cast<SignedType>(aValue) + : static_cast<SignedType>(aValue - MinValueUnsigned) + MinValue; + } +}; + +} // namespace detail + +/** + * Convert an unsigned value to signed, if necessary wrapping around. + * + * This is the behavior normal C++ casting will perform in most implementations + * these days -- but this function makes explicit that such conversion is + * happening. + */ +template<typename UnsignedType> +inline constexpr typename detail::WrapToSignedHelper<UnsignedType>::SignedType +WrapToSigned(UnsignedType aValue) +{ + return detail::WrapToSignedHelper<UnsignedType>::compute(aValue); +} + +namespace detail { + +template<typename T> +struct WrappingMultiplyHelper +{ +private: + using UnsignedT = typename MakeUnsigned<T>::Type; + + MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW + static UnsignedT + multiply(UnsignedT aX, UnsignedT aY) + { + // |mozilla::WrappingMultiply| isn't constexpr because MSVC warns about well- + // defined unsigned integer overflows that may happen here. + // https://msdn.microsoft.com/en-us/library/4kze989h.aspx And constexpr + // seems to cause the warning to be emitted at |WrappingMultiply| call *sites* + // instead of here, so these #pragmas are ineffective. + // + // https://stackoverflow.com/questions/37658794/integer-constant-overflow-warning-in-constexpr + // + // If/when MSVC fix this bug, we should make these functions constexpr. + + // Begin with |1U| to ensure the overall operation chain is never promoted + // to signed integer operations that might have *signed* integer overflow. + return static_cast<UnsignedT>(1U * aX * aY); + } + + static T + toResult(UnsignedT aX, UnsignedT aY) + { + // We could always return WrapToSigned and rely on unsigned conversion + // undoing the wrapping when |T| is unsigned, but this seems clearer. + return IsSigned<T>::value + ? WrapToSigned(multiply(aX, aY)) + : multiply(aX, aY); + } + +public: + MOZ_NO_SANITIZE_UNSIGNED_OVERFLOW + static T compute(T aX, T aY) + { + return toResult(static_cast<UnsignedT>(aX), static_cast<UnsignedT>(aY)); + } +}; + +} // namespace detail + +/** + * Multiply two integers of the same type, and return the result converted to + * that type using wraparound semantics. This function: + * + * 1) makes explicit the desire for and dependence upon wraparound semantics, + * 2) provides wraparound semantics *safely* with no signed integer overflow + * that would have undefined behavior, and + * 3) won't trip up {,un}signed-integer overflow sanitizers (see + * build/autoconf/sanitize.m4) at runtime. + * + * For N-bit unsigned integer types, this is equivalent to multiplying the two + * numbers, then taking the result mod 2**N: + * + * WrappingMultiply(uint32_t(42), uint32_t(17)) is 714 (714 mod 2**32); + * WrappingMultiply(uint8_t(16), uint8_t(24)) is 128 (384 mod 2**8); + * WrappingMultiply(uint16_t(3), uint16_t(32768)) is 32768 (98304 mod 2*16). + * + * Use this function for any unsigned multiplication that can wrap (instead of + * normal C++ multiplication) to play nice with the sanitizers. But it's + * especially important to use it for uint16_t multiplication: in most compilers + * for uint16_t*uint16_t some operand values will trigger signed integer + * overflow with undefined behavior! http://kqueue.org/blog/2013/09/17/cltq/ + * has the grody details. Other than that one weird case, WrappingMultiply on + * unsigned types is the same as C++ multiplication. + * + * For N-bit signed integer types, this is equivalent to multiplying the two + * numbers wrapped to unsigned, taking the product mod 2**N, then wrapping that + * number to the signed range: + * + * WrappingMultiply(int16_t(-456), int16_t(123)) is 9448 ((-56088 mod 2**16) + 2**16); + * WrappingMultiply(int32_t(-7), int32_t(-9)) is 63 (63 mod 2**32); + * WrappingMultiply(int8_t(16), int8_t(24)) is -128 ((384 mod 2**8) - 2**8); + * WrappingMultiply(int8_t(16), int8_t(255)) is -16 ((4080 mod 2**8) - 2**8). + * + * There is no ready equivalent to this operation in C++, as applying C++ + * multiplication to signed integer types in ways that trigger overflow has + * undefined behavior. However, it's how multiplication *tends* to behave with + * most compilers in most situations, even though it's emphatically not required + * to do so. + */ +template<typename T> +inline T +WrappingMultiply(T aX, T aY) +{ + return detail::WrappingMultiplyHelper<T>::compute(aX, aY); +} + +} /* namespace mozilla */ + +#endif /* mozilla_WrappingOperations_h */ diff --git a/mfbt/moz.build b/mfbt/moz.build index 20c7234dd3..54b13f21b9 100644 --- a/mfbt/moz.build +++ b/mfbt/moz.build @@ -101,6 +101,7 @@ EXPORTS.mozilla = [ 'Variant.h', 'Vector.h', 'WeakPtr.h', + 'WrappingOperations.h', 'XorShift128PlusRNG.h', ] diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 267cb39d6d..9aada9bb90 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2448,6 +2448,12 @@ pref("layout.css.mix-blend-mode.enabled", true); // Is support for isolation enabled? pref("layout.css.isolation.enabled", true); +// Is support for CSS animation properties enabled? +pref("layout.css.animation.enabled", true); + +// Is support for CSS transition properties enabled? +pref("layout.css.transition.enabled", true); + // Is support for CSS Filters enabled? pref("layout.css.filters.enabled", true); diff --git a/netwerk/protocol/websocket/WebSocketChannel.cpp b/netwerk/protocol/websocket/WebSocketChannel.cpp index 058dc46417..9fca09a087 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.cpp +++ b/netwerk/protocol/websocket/WebSocketChannel.cpp @@ -520,7 +520,7 @@ private: ~nsOpenConn() { MOZ_COUNT_DTOR(nsOpenConn); } nsCString mAddress; - WebSocketChannel *mChannel; + RefPtr<WebSocketChannel> mChannel; }; void ConnectNext(nsCString &hostName) @@ -1191,6 +1191,7 @@ WebSocketChannel::WebSocketChannel() : mBufferSize(kIncomingBufferInitialSize), mCurrentOut(nullptr), mCurrentOutSent(0), + mCompressorMutex("WebSocketChannel::mCompressorMutex"), mDynamicOutputSize(0), mDynamicOutput(nullptr), mPrivateBrowsing(false), @@ -1627,6 +1628,7 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count) if (rsvBits) { // PMCE sets RSV1 bit in the first fragment when the non-control frame // is deflated + MutexAutoLock lock(mCompressorMutex); if (mPMCECompressor && rsvBits == kRsv1Bit && mFragmentAccumulator == 0 && !(opcode & kControlFrameMask)) { mPMCECompressor->SetMessageDeflated(); @@ -1700,25 +1702,28 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count) LOG(("WebSocketChannel:: ignoring read frame code %d after completion\n", opcode)); } else if (opcode == nsIWebSocketFrame::OPCODE_TEXT) { - bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated(); - LOG(("WebSocketChannel:: %stext frame received\n", - isDeflated ? "deflated " : "")); - if (mListenerMT) { nsCString utf8Data; - if (isDeflated) { - rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data); - if (NS_FAILED(rv)) { - return rv; - } - LOG(("WebSocketChannel:: message successfully inflated " - "[origLength=%d, newLength=%d]\n", payloadLength, - utf8Data.Length())); - } else { - if (!utf8Data.Assign((const char *)payload, payloadLength, - mozilla::fallible)) { - return NS_ERROR_OUT_OF_MEMORY; + { // lockscope + MutexAutoLock lock(mCompressorMutex); + bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated(); + LOG(("WebSocketChannel:: %stext frame received\n", + isDeflated ? "deflated " : "")); + + if (isDeflated) { + rv = mPMCECompressor->Inflate(payload, payloadLength, utf8Data); + if (NS_FAILED(rv)) { + return rv; + } + LOG(("WebSocketChannel:: message successfully inflated " + "[origLength=%d, newLength=%d]\n", payloadLength, + utf8Data.Length())); + } else { + if (!utf8Data.Assign((const char *)payload, payloadLength, + mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } } } @@ -1835,25 +1840,28 @@ WebSocketChannel::ProcessInput(uint8_t *buffer, uint32_t count) mService->FrameReceived(mSerial, mInnerWindowID, frame.forget()); } } else if (opcode == nsIWebSocketFrame::OPCODE_BINARY) { - bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated(); - LOG(("WebSocketChannel:: %sbinary frame received\n", - isDeflated ? "deflated " : "")); - if (mListenerMT) { nsCString binaryData; - if (isDeflated) { - rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData); - if (NS_FAILED(rv)) { - return rv; - } - LOG(("WebSocketChannel:: message successfully inflated " - "[origLength=%d, newLength=%d]\n", payloadLength, - binaryData.Length())); - } else { - if (!binaryData.Assign((const char *)payload, payloadLength, - mozilla::fallible)) { - return NS_ERROR_OUT_OF_MEMORY; + { //lockscope + MutexAutoLock lock(mCompressorMutex); + bool isDeflated = mPMCECompressor && mPMCECompressor->IsMessageDeflated(); + LOG(("WebSocketChannel:: %sbinary frame received\n", + isDeflated ? "deflated " : "")); + + if (isDeflated) { + rv = mPMCECompressor->Inflate(payload, payloadLength, binaryData); + if (NS_FAILED(rv)) { + return rv; + } + LOG(("WebSocketChannel:: message successfully inflated " + "[origLength=%d, newLength=%d]\n", payloadLength, + binaryData.Length())); + } else { + if (!binaryData.Assign((const char *)payload, payloadLength, + mozilla::fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } } } @@ -2156,6 +2164,7 @@ WebSocketChannel::PrimeNewOutgoingMessage() } // deflate the payload if PMCE is negotiated + MutexAutoLock lock(mCompressorMutex); if (mPMCECompressor && (msgType == kMsgTypeString || msgType == kMsgTypeBinaryString)) { if (mCurrentOut->DeflatePayload(mPMCECompressor)) { @@ -2447,7 +2456,10 @@ WebSocketChannel::StopSession(nsresult reason) mCancelable = nullptr; } - mPMCECompressor = nullptr; + { + MutexAutoLock lock(mCompressorMutex); + mPMCECompressor = nullptr; + } if (!mCalledOnStop) { mCalledOnStop = 1; @@ -2702,6 +2714,7 @@ WebSocketChannel::HandleExtensions() serverMaxWindowBits = 15; } + MutexAutoLock lock(mCompressorMutex); mPMCECompressor = new PMCECompression(clientNoContextTakeover, clientMaxWindowBits, serverMaxWindowBits); @@ -3678,6 +3691,7 @@ WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport, serverMaxWindowBits = 15; } + MutexAutoLock lock(mCompressorMutex); mPMCECompressor = new PMCECompression(serverNoContextTakeover, serverMaxWindowBits, clientMaxWindowBits); diff --git a/netwerk/protocol/websocket/WebSocketChannel.h b/netwerk/protocol/websocket/WebSocketChannel.h index 879027b2ad..691de4102c 100644 --- a/netwerk/protocol/websocket/WebSocketChannel.h +++ b/netwerk/protocol/websocket/WebSocketChannel.h @@ -18,6 +18,7 @@ #include "nsIChannelEventSink.h" #include "nsIHttpChannelInternal.h" #include "nsIStringStream.h" +#include "mozilla/Mutex.h" #include "BaseWebSocketChannel.h" #include "nsCOMPtr.h" @@ -288,6 +289,7 @@ private: uint32_t mHdrOutToSend; uint8_t *mHdrOut; uint8_t mOutHeader[kCopyBreak + 16]; + Mutex mCompressorMutex; nsAutoPtr<PMCECompression> mPMCECompressor; uint32_t mDynamicOutputSize; uint8_t *mDynamicOutput; diff --git a/python/mozbuild/mozbuild/backend/common.py b/python/mozbuild/mozbuild/backend/common.py index a90aa1e5d5..351c6dbc81 100644 --- a/python/mozbuild/mozbuild/backend/common.py +++ b/python/mozbuild/mozbuild/backend/common.py @@ -29,6 +29,7 @@ from mozbuild.frontend.data import ( FinalTargetFiles, GeneratedEventWebIDLFile, GeneratedWebIDLFile, + PreprocessedIPDLFile, PreprocessedTestWebIDLFile, PreprocessedWebIDLFile, SharedLibrary, @@ -215,6 +216,21 @@ class BinariesCollection(object): self.shared_libraries = [] self.programs = [] +class IPDLCollection(object): + """Collects IPDL files during the build.""" + + def __init__(self): + self.sources = set() + self.preprocessed_sources = set() + + def all_sources(self): + return self.sources | self.preprocessed_sources + + def all_regular_sources(self): + return self.sources + + def all_preprocessed_sources(self): + return self.preprocessed_sources class CommonBackend(BuildBackend): """Holds logic common to all build backends.""" @@ -225,7 +241,7 @@ class CommonBackend(BuildBackend): self._webidls = WebIDLCollection() self._binaries = BinariesCollection() self._configs = set() - self._ipdl_sources = set() + self._ipdls = IPDLCollection() def consume_object(self, obj): self._configs.add(obj.config) @@ -270,6 +286,10 @@ class CommonBackend(BuildBackend): self._webidls.generated_sources.add(mozpath.join(obj.srcdir, obj.basename)) + elif isinstance(obj, PreprocessedIPDLFile): + self._ipdls.preprocessed_sources.add(mozpath.join( + obj.srcdir, obj.basename)) + elif isinstance(obj, PreprocessedWebIDLFile): self._webidls.preprocessed_sources.add(mozpath.join( obj.srcdir, obj.basename)) @@ -278,7 +298,7 @@ class CommonBackend(BuildBackend): self._webidls.example_interfaces.add(obj.name) elif isinstance(obj, IPDLFile): - self._ipdl_sources.add(mozpath.join(obj.srcdir, obj.basename)) + self._ipdls.sources.add(mozpath.join(obj.srcdir, obj.basename)) elif isinstance(obj, UnifiedSources): if obj.have_unified_mapping: @@ -305,7 +325,9 @@ class CommonBackend(BuildBackend): self._handle_webidl_collection(self._webidls) - sorted_ipdl_sources = list(sorted(self._ipdl_sources)) + sorted_ipdl_sources = list(sorted(self._ipdls.all_sources())) + sorted_nonstatic_ipdl_sources = list(sorted(self._ipdls.all_preprocessed_sources())) + sorted_static_ipdl_sources = list(sorted(self._ipdls.all_regular_sources())) def files_from(ipdl): base = mozpath.basename(ipdl) @@ -328,7 +350,8 @@ class CommonBackend(BuildBackend): files_per_unified_file=16)) self._write_unified_files(unified_source_mapping, ipdl_dir, poison_windows_h=False) - self._handle_ipdl_sources(ipdl_dir, sorted_ipdl_sources, unified_source_mapping) + self._handle_ipdl_sources(ipdl_dir, sorted_ipdl_sources, sorted_nonstatic_ipdl_sources, + sorted_static_ipdl_sources, unified_source_mapping) for config in self._configs: self.backend_input_files.add(config.source) diff --git a/python/mozbuild/mozbuild/backend/recursivemake.py b/python/mozbuild/mozbuild/backend/recursivemake.py index 4a37b29663..7f3b36c597 100644 --- a/python/mozbuild/mozbuild/backend/recursivemake.py +++ b/python/mozbuild/mozbuild/backend/recursivemake.py @@ -1350,18 +1350,32 @@ class RecursiveMakeBackend(CommonBackend): self._makefile_out_count += 1 - def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, - unified_ipdl_cppsrcs_mapping): + def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, sorted_nonstatic_ipdl_sources, + sorted_static_ipdl_sources, unified_ipdl_cppsrcs_mapping): # Write out a master list of all IPDL source files. mk = Makefile() - mk.add_statement('ALL_IPDLSRCS := %s' % ' '.join(sorted_ipdl_sources)) + sorted_nonstatic_ipdl_basenames = list() + for source in sorted_nonstatic_ipdl_sources: + basename = os.path.basename(source) + sorted_nonstatic_ipdl_basenames.append(basename) + rule = mk.create_rule([basename]) + rule.add_dependencies([source]) + rule.add_commands([ + '$(RM) $@', + '$(call py_action,preprocessor,$(DEFINES) $(ACDEFINES) ' + '$< -o $@)' + ]) + + mk.add_statement('ALL_IPDLSRCS := %s %s' % (' '.join(sorted_nonstatic_ipdl_basenames), + ' '.join(sorted_static_ipdl_sources))) self._add_unified_build_rules(mk, unified_ipdl_cppsrcs_mapping, unified_files_makefile_variable='CPPSRCS') - mk.add_statement('IPDLDIRS := %s' % ' '.join(sorted(set(mozpath.dirname(p) - for p in self._ipdl_sources)))) + # Preprocessed ipdl files are generated in ipdl_dir. + mk.add_statement('IPDLDIRS := %s %s' % (ipdl_dir, ' '.join(sorted(set(mozpath.dirname(p) + for p in sorted_static_ipdl_sources))))) with self._write_file(mozpath.join(ipdl_dir, 'ipdlsrcs.mk')) as ipdls: mk.dump(ipdls, removal_guard=False) diff --git a/python/mozbuild/mozbuild/backend/tup.py b/python/mozbuild/mozbuild/backend/tup.py index 0f7250eb01..b148532c89 100644 --- a/python/mozbuild/mozbuild/backend/tup.py +++ b/python/mozbuild/mozbuild/backend/tup.py @@ -298,8 +298,8 @@ class TupOnly(CommonBackend, PartialBackend): outputs=[output], ) - def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, - unified_ipdl_cppsrcs_mapping): + def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, sorted_nonstatic_ipdl_sources, + sorted_static_ipdl_sources, unified_ipdl_cppsrcs_mapping): # TODO: This isn't implemented yet in the tup backend, but it is called # by the CommonBackend. pass diff --git a/python/mozbuild/mozbuild/compilation/database.py b/python/mozbuild/mozbuild/compilation/database.py index 4193e1bcf4..20b09df488 100644 --- a/python/mozbuild/mozbuild/compilation/database.py +++ b/python/mozbuild/mozbuild/compilation/database.py @@ -177,8 +177,8 @@ class CompileDBBackend(CommonBackend): def _handle_idl_manager(self, idl_manager): pass - def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, - unified_ipdl_cppsrcs_mapping): + def _handle_ipdl_sources(self, ipdl_dir, sorted_ipdl_sources, sorted_nonstatic_ipdl_sources, + sorted_static_ipdl_sources, unified_ipdl_cppsrcs_mapping): for f in unified_ipdl_cppsrcs_mapping: self._build_db_line(ipdl_dir, None, self.environment, f[0], '.cpp') diff --git a/python/mozbuild/mozbuild/frontend/context.py b/python/mozbuild/mozbuild/frontend/context.py index 7c44a6836a..46f6d12cb5 100644 --- a/python/mozbuild/mozbuild/frontend/context.py +++ b/python/mozbuild/mozbuild/frontend/context.py @@ -1388,6 +1388,13 @@ VARIABLES = { not use this flag. """), + 'PREPROCESSED_IPDL_SOURCES': (StrictOrderingOnAppendList, list, + """Preprocessed IPDL source files. + + These files will be preprocessed, then parsed and converted to + ``.cpp`` files. + """), + 'IPDL_SOURCES': (StrictOrderingOnAppendList, list, """IPDL source files. diff --git a/python/mozbuild/mozbuild/frontend/data.py b/python/mozbuild/mozbuild/frontend/data.py index 83d7b122ab..c6ed4f65ef 100644 --- a/python/mozbuild/mozbuild/frontend/data.py +++ b/python/mozbuild/mozbuild/frontend/data.py @@ -218,6 +218,18 @@ class IPDLFile(ContextDerived): self.basename = path +class PreprocessedIPDLFile(ContextDerived): + """Describes an individual .ipdl source file that requires preprocessing.""" + + __slots__ = ( + 'basename', + ) + + def __init__(self, context, path): + ContextDerived.__init__(self, context) + + self.basename = path + class WebIDLFile(ContextDerived): """Describes an individual .webidl source file.""" diff --git a/python/mozbuild/mozbuild/frontend/emitter.py b/python/mozbuild/mozbuild/frontend/emitter.py index 78d92c4236..faa41786ac 100644 --- a/python/mozbuild/mozbuild/frontend/emitter.py +++ b/python/mozbuild/mozbuild/frontend/emitter.py @@ -54,6 +54,7 @@ from .data import ( ObjdirFiles, ObjdirPreprocessedFiles, PerSourceFlag, + PreprocessedIPDLFile, PreprocessedTestWebIDLFile, PreprocessedWebIDLFile, Program, @@ -862,6 +863,7 @@ class TreeMetadataEmitter(LoggingMixin): ('GENERATED_EVENTS_WEBIDL_FILES', GeneratedEventWebIDLFile), ('GENERATED_WEBIDL_FILES', GeneratedWebIDLFile), ('IPDL_SOURCES', IPDLFile), + ('PREPROCESSED_IPDL_SOURCES', PreprocessedIPDLFile), ('PREPROCESSED_TEST_WEBIDL_FILES', PreprocessedTestWebIDLFile), ('PREPROCESSED_WEBIDL_FILES', PreprocessedWebIDLFile), ('TEST_WEBIDL_FILES', TestWebIDLFile), diff --git a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build index eb37725230..126854c383 100644 --- a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build +++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/bar/moz.build @@ -3,6 +3,10 @@ # 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/. +PREPROCESSED_IPDL_SOURCES += [ + 'bar1.ipdl', +] + IPDL_SOURCES += [ 'bar.ipdl', 'bar2.ipdlh', diff --git a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build index 84d40ac1d3..4047c31216 100644 --- a/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build +++ b/python/mozbuild/mozbuild/test/backend/data/ipdl_sources/foo/moz.build @@ -3,6 +3,10 @@ # 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/. +PREPROCESSED_IPDL_SOURCES += [ + 'foo1.ipdl', +] + IPDL_SOURCES += [ 'foo.ipdl', 'foo2.ipdlh', diff --git a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py index 87f50f4973..a592ab55a3 100644 --- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py +++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py @@ -656,7 +656,7 @@ class TestRecursiveMakeBackend(BackendTester): self.assertEqual(m, m2) def test_ipdl_sources(self): - """Test that IPDL_SOURCES are written to ipdlsrcs.mk correctly.""" + """Test that PREPROCESSED_IPDL_SOURCES and IPDL_SOURCES are written to ipdlsrcs.mk correctly.""" env = self._consume('ipdl_sources', RecursiveMakeBackend) manifest_path = mozpath.join(env.topobjdir, @@ -667,9 +667,9 @@ class TestRecursiveMakeBackend(BackendTester): topsrcdir = env.topsrcdir.replace(os.sep, '/') expected = [ - "ALL_IPDLSRCS := %s/bar/bar.ipdl %s/bar/bar2.ipdlh %s/foo/foo.ipdl %s/foo/foo2.ipdlh" % tuple([topsrcdir] * 4), + "ALL_IPDLSRCS := bar1.ipdl foo1.ipdl %s/bar/bar.ipdl %s/bar/bar2.ipdlh %s/foo/foo.ipdl %s/foo/foo2.ipdlh" % tuple([topsrcdir] * 4), "CPPSRCS := UnifiedProtocols0.cpp", - "IPDLDIRS := %s/bar %s/foo" % (topsrcdir, topsrcdir), + "IPDLDIRS := %s/ipc/ipdl %s/bar %s/foo" % (env.topobjdir, topsrcdir, topsrcdir), ] found = [str for str in lines if str.startswith(('ALL_IPDLSRCS', diff --git a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build index eb37725230..126854c383 100644 --- a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build +++ b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/bar/moz.build @@ -3,6 +3,10 @@ # 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/. +PREPROCESSED_IPDL_SOURCES += [ + 'bar1.ipdl', +] + IPDL_SOURCES += [ 'bar.ipdl', 'bar2.ipdlh', diff --git a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build index 84d40ac1d3..4047c31216 100644 --- a/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build +++ b/python/mozbuild/mozbuild/test/frontend/data/ipdl_sources/foo/moz.build @@ -3,6 +3,10 @@ # 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/. +PREPROCESSED_IPDL_SOURCES += [ + 'foo1.ipdl', +] + IPDL_SOURCES += [ 'foo.ipdl', 'foo2.ipdlh', diff --git a/python/mozbuild/mozbuild/test/frontend/test_emitter.py b/python/mozbuild/mozbuild/test/frontend/test_emitter.py index 191727381d..e1e3fa5ce2 100644 --- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py +++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py @@ -30,6 +30,7 @@ from mozbuild.frontend.data import ( JARManifest, LinkageMultipleRustLibrariesError, LocalInclude, + PreprocessedIPDLFile, Program, SdkFiles, SharedLibrary, @@ -688,9 +689,12 @@ class TestEmitterBasic(unittest.TestCase): objs = self.read_topsrcdir(reader) ipdls = [] + nonstatic_ipdls = [] for o in objs: if isinstance(o, IPDLFile): ipdls.append('%s/%s' % (o.relativedir, o.basename)) + elif isinstance(o, PreprocessedIPDLFile): + nonstatic_ipdls.append('%s/%s' % (o.relativedir, o.basename)) expected = [ 'bar/bar.ipdl', @@ -699,7 +703,12 @@ class TestEmitterBasic(unittest.TestCase): 'foo/foo2.ipdlh', ] - self.assertEqual(ipdls, expected) + expected = [ + 'bar/bar1.ipdl', + 'foo/foo1.ipdl', + ] + + self.assertEqual(nonstatic_ipdls, expected) def test_local_includes(self): """Test that LOCAL_INCLUDES is emitted correctly.""" diff --git a/toolkit/library/moz.build b/toolkit/library/moz.build index a80cf1dc73..d274cfa70b 100644 --- a/toolkit/library/moz.build +++ b/toolkit/library/moz.build @@ -196,7 +196,7 @@ if CONFIG['MOZ_SYSTEM_JPEG']: if CONFIG['MOZ_SYSTEM_HUNSPELL']: OS_LIBS += CONFIG['MOZ_HUNSPELL_LIBS'] -else: +elif CONFIG['OS_TARGET'] != 'SunOS': USE_LIBS += [ 'hunspell' ] if not CONFIG['MOZ_TREE_PIXMAN']: diff --git a/toolkit/themes/shared/in-content/info-pages.inc.css b/toolkit/themes/shared/in-content/info-pages.inc.css index 4889602a09..29fcbe1ea8 100644 --- a/toolkit/themes/shared/in-content/info-pages.inc.css +++ b/toolkit/themes/shared/in-content/info-pages.inc.css @@ -96,7 +96,7 @@ ul { /* Trees */ .tree-container { margin-top: 1.2em; - flex-grow: 1; + flex: 1 0px; min-height: 12em; } diff --git a/widget/cocoa/RectTextureImage.mm b/widget/cocoa/RectTextureImage.mm index c67af97d0a..270ac5d8b5 100644 --- a/widget/cocoa/RectTextureImage.mm +++ b/widget/cocoa/RectTextureImage.mm @@ -144,7 +144,7 @@ RectTextureImage::DeleteTexture() void RectTextureImage::BindIOSurfaceToTexture(gl::GLContext* aGL) { - if (!mTexture) { + if (!mTexture && mIOSurface) { MOZ_ASSERT(aGL); aGL->fGenTextures(1, &mTexture); aGL->fActiveTexture(LOCAL_GL_TEXTURE0); diff --git a/xpcom/io/nsLocalFileWin.cpp b/xpcom/io/nsLocalFileWin.cpp index 42bd83d45e..e5a0131a77 100644 --- a/xpcom/io/nsLocalFileWin.cpp +++ b/xpcom/io/nsLocalFileWin.cpp @@ -3086,7 +3086,8 @@ nsLocalFile::IsExecutable(bool* aResult) "ws", "wsc", "wsf", - "wsh" + "wsh", + "xll" // MS Excel dynamic link library }; nsDependentSubstring ext = Substring(path, dotIdx + 1); for (size_t i = 0; i < ArrayLength(executableExts); ++i) { |