From a128924c0d3af63f4f2bcdb65a9dfc3cc6960731 Mon Sep 17 00:00:00 2001 From: roytam1 Date: Fri, 28 Oct 2022 23:01:13 +0800 Subject: [PATCH] import changes from `dev' branch of rmottola/Arctic-Fox: - Bug 930414 - Instantiate module function defintions earlier as per the spec r=shu (c0f7f01c16) - Bug 819125 - Reset return value before executing catch/finally block. r=jandem (005fc7dc26) - Bug 1216623 - Part 2: In for (let ...) loops, evaluate initializers in the scope of the variables being initialized. r=Waldo. (a09e74569a) - Bug 1219877 - Allow let token with TOK_NAME in strict mode in Parser::shouldParseLetDeclaration. r=shu (d76dbab3d4) - Bug 932517 - Followup: remove let-block logic in sloppy mode ES6 let declaration check. (rs=evilpie) (c6e0c8661a) - Bug 1212305: Also mark lazy link frames, r=jandem (cd3601052c) - Bug 1216140 - Load the global lexical scope for the BINDGNAME Baseline IC when the script doesn't have a non-syntactic scope chain. (r=jandem) (370cccbd8a) - Bug 1144630 - Install class methods as non-enumerable on instances. (r=evilpie) (1158b1ffdb) - Bug 1218639 - IonMonkey: MIPS64: Add support into jit. r=nbp (f521201ec3) - Bug 1213740 - IonMonkey: MIPS64: Import Bailouts-mips64. r=nbp f=rankov (5e810a94d9) - Bug 1213741 - IonMonkey: MIPS64: Import BaselineCompiler-mips64. r=nbp f=rankov (c6eb2d72b1) - Bug 1213742 - IonMonkey: MIPS64: Import BaselineIC-mips64. r=nbp f=rankov (67b78d1345) - Bug 1213745 - IonMonkey: MIPS64: Import Lowering-mips64. r=nbp f=rankov (d1e60521f2) - Bug 1213749 - IonMonkey: MIPS64: Import SharedIC-mips64. r=nbp f=rankov (4a2184d535) - Bug 1213750 - IonMonkey: MIPS64: Import Trampoline-mips64. r=nbp f=rankov (382237c7db) - Bug 1213743 - IonMonkey: MIPS64: Import CodeGenerator-mips64. r=lth f=nbp,rankov (4f7d1720d6) - Bug 1213747 - IonMonkey: MIPS64: Import MoveEmitter-mips64. r=nbp f=rankov (63a53396e6) - Bug 1213751 - IonMonkey: MIPS64: Import Simulator-mips64. r=jandem (ac2f67fd43) - Bug 1218652 - IonMonkey: MIPS: Add mips-shared in check macroassembler style. r=arai (cf6f2bf5db) - Bug 1205134 - IonMonkey: MIPS: Split shareable code to mips-shared in MacroAssembler-mips32. r=lth f=nbp (3608ef227a) - Bug 1218652 - IonMonkey: MIPS: Move shared functions to mips-shared. r=arai (d716fa56bc) - Bug 1219137 - IonMonkey: MIPS: Fix PushRegsInMask and PopRegsInMaskIgnore can not shared. r=arai (13e5811e19) - Bug 1213746 - IonMonkey: MIPS64: Import MacroAssembler-mips64. r=lthf=rankov (3a7cb6fbd6) - Bug 1196545 - unboxed array snafus. r=jandem (31bbe773b2) - Bug 1224411 - Speed up FileRegistry._partial_paths by memoizing on the basis of directory. r=nalexander (d202d614a9) - Bug 1153790 - mac unification doesn't work with packaged addons. r=glandium (269cc3e37c) - Bug 1208320 - Support configuring zlib compression level; r=glandium (502de5e78a) - Bug 1163077 - Never elfhack if --disable-compile-environment is set. r=glandium (a484cb9a2f) - Bug 1213418 - Part 1: Use mozregression to download in |mach artifact|. r=me (93e1cb3aa2) - Bug 1213418 - Part 2: Use Fennec APKs in |mach artifact|; use mozversion to extract build IDs. r=me (27efbe8404) - Bug 1191051 - Use full 40 character hashes in |mach artifact|. r=gps (43992efbee) - Bug 1192064 - Add 'android-x86' job to |mach artifact|. r=jonalmeida (c0e7d6c8f8) - No bug - Don't write artifacts twice. r=me (e5eff7d6de) - Bug 1208808 - Move time zone adjustment information out of JSRuntime, into global state protected by a spinlock. r=till (20c6e76cc7) - Bug 1181612: Rename AsmJSFunctionLabels to make them easier to understand; r=luke (c6d88f944b) - Bug 1181612: Remove FunctionCompiler's RetType; r=luke (2f7483bfe2) - Bug 1214467 - Eliminate redundant loop header phis while emitting MIR for asm.js. r=luke (6c50f9b407) - Bug 1205390 - make Odin accept TypedArray constructors for shared memory. r=luke (400f892775) - Bug 1181612: Make asm.js internal calls thread-local; r=luke (aed88de095) - Bug 1219143 - IonMonkey: MIPS: Move callAndPushReturnAddress to mips-shared. r=lth (b9cd97f491) - Bug 1217326 - fork remaining atomics. r=h4writer. (65cc2a2b07) --- config/check_macroassembler_style.py | 4 +- dom/time/DateCacheCleaner.cpp | 4 +- dom/time/DateCacheCleaner.h | 2 +- js/public/Date.h | 16 + js/public/Initialization.h | 84 + js/src/asmjs/AsmJSCompile.cpp | 148 +- js/src/asmjs/AsmJSCompile.h | 4 +- js/src/asmjs/AsmJSFrameIterator.cpp | 4 +- js/src/asmjs/AsmJSGlobals.h | 92 +- js/src/asmjs/AsmJSLink.cpp | 2 +- js/src/asmjs/AsmJSModule.cpp | 16 +- js/src/asmjs/AsmJSModule.h | 39 +- js/src/asmjs/AsmJSValidate.cpp | 135 +- js/src/builtin/Module.js | 3 + js/src/builtin/ModuleObject.cpp | 70 + js/src/builtin/ModuleObject.h | 18 +- js/src/builtin/ReflectParse.cpp | 46 +- js/src/frontend/BytecodeEmitter.cpp | 154 +- js/src/frontend/BytecodeEmitter.h | 2 +- js/src/frontend/FoldConstants.cpp | 2 - js/src/frontend/FullParseHandler.h | 4 - js/src/frontend/NameFunctions.cpp | 1 - js/src/frontend/ParseNode.cpp | 1 - js/src/frontend/ParseNode.h | 1 - js/src/frontend/Parser.cpp | 180 +- js/src/gdb/gdb-tests.cpp | 1 + .../jit-test/tests/asm.js/sta-transition.js | 116 + js/src/jit-test/tests/asm.js/testAtomics.js | 51 +- js/src/jit-test/tests/baseline/bug1216140.js | 6 + js/src/jit-test/tests/basic/bug646968-3.js | 8 +- js/src/jit-test/tests/basic/bug646968-4.js | 8 +- js/src/jit-test/tests/basic/bug646968-6.js | 16 + .../tests/basic/letLegacyForOfOrInScope.js | 5 - js/src/jit-test/tests/basic/testLet.js | 41 +- .../module-declaration-instantiation.js | 16 +- .../tests/parser/let-after-directive.js | 6 + js/src/jit/AtomicOperations.h | 2 +- js/src/jit/BaselineBailouts.cpp | 4 +- js/src/jit/BaselineCompiler.cpp | 45 +- js/src/jit/BaselineCompiler.h | 9 +- js/src/jit/BaselineIC.cpp | 11 +- js/src/jit/BaselineJIT.cpp | 5 + js/src/jit/CodeGenerator.cpp | 52 +- js/src/jit/CodeGenerator.h | 4 +- js/src/jit/ExecutableAllocator.h | 9 +- js/src/jit/Ion.cpp | 14 +- js/src/jit/IonBuilder.cpp | 7 +- js/src/jit/IonCaches.h | 2 + js/src/jit/IonCode.h | 2 +- js/src/jit/JitCommon.h | 2 + js/src/jit/JitFrames.cpp | 5 + js/src/jit/LIR.h | 4 +- js/src/jit/LOpcodes.h | 2 + js/src/jit/Lowering.h | 2 + js/src/jit/MIR.h | 6 +- js/src/jit/MIRGraph.cpp | 19 +- js/src/jit/MIRGraph.h | 19 +- js/src/jit/MacroAssembler-inl.h | 17 +- js/src/jit/MacroAssembler.cpp | 89 - js/src/jit/MacroAssembler.h | 41 +- js/src/jit/MoveEmitter.h | 2 + js/src/jit/RegisterAllocator.h | 2 +- js/src/jit/RegisterSets.h | 2 +- js/src/jit/Registers.h | 2 + js/src/jit/SharedIC.cpp | 2 +- js/src/jit/SharedIC.h | 2 +- js/src/jit/SharedICHelpers.h | 2 + js/src/jit/SharedICRegisters.h | 2 + js/src/jit/VMFunctions.cpp | 1 + js/src/jit/arm/CodeGenerator-arm.cpp | 42 + js/src/jit/arm/CodeGenerator-arm.h | 2 + js/src/jit/arm/MacroAssembler-arm.cpp | 111 +- js/src/jit/arm/MacroAssembler-arm.h | 8 + js/src/jit/arm64/CodeGenerator-arm64.cpp | 44 + js/src/jit/arm64/CodeGenerator-arm64.h | 2 + js/src/jit/arm64/MacroAssembler-arm64.cpp | 107 +- js/src/jit/arm64/MacroAssembler-arm64.h | 8 + .../MacroAssembler-mips-shared-inl.h | 84 + .../MacroAssembler-mips-shared.cpp | 949 ++++ .../mips-shared/MacroAssembler-mips-shared.h | 185 + .../jit/mips-shared/MoveEmitter-mips-shared.h | 2 +- js/src/jit/mips32/MacroAssembler-mips32-inl.h | 61 +- js/src/jit/mips32/MacroAssembler-mips32.cpp | 905 ---- js/src/jit/mips32/MacroAssembler-mips32.h | 158 +- js/src/jit/mips64/Bailouts-mips64.cpp | 28 + js/src/jit/mips64/BaselineCompiler-mips64.cpp | 16 + js/src/jit/mips64/BaselineCompiler-mips64.h | 26 + js/src/jit/mips64/BaselineIC-mips64.cpp | 47 + js/src/jit/mips64/CodeGenerator-mips64.cpp | 282 ++ js/src/jit/mips64/CodeGenerator-mips64.h | 75 + js/src/jit/mips64/LIR-mips64.h | 52 + js/src/jit/mips64/LOpcodes-mips64.h | 16 + js/src/jit/mips64/Lowering-mips64.cpp | 128 + js/src/jit/mips64/Lowering-mips64.h | 50 + js/src/jit/mips64/MacroAssembler-mips64-inl.h | 109 + js/src/jit/mips64/MacroAssembler-mips64.cpp | 2754 ++++++++++++ js/src/jit/mips64/MacroAssembler-mips64.h | 1283 ++++++ js/src/jit/mips64/MoveEmitter-mips64.cpp | 155 + js/src/jit/mips64/MoveEmitter-mips64.h | 34 + js/src/jit/mips64/SharedIC-mips64.cpp | 191 + js/src/jit/mips64/SharedICHelpers-mips64.h | 350 ++ js/src/jit/mips64/SharedICRegisters-mips64.h | 47 + js/src/jit/mips64/Simulator-mips64.cpp | 3878 +++++++++++++++++ js/src/jit/mips64/Simulator-mips64.h | 440 ++ js/src/jit/mips64/Trampoline-mips64.cpp | 1319 ++++++ js/src/jit/shared/Assembler-shared.h | 45 +- js/src/jit/x86-shared/Assembler-x86-shared.h | 14 +- .../x86-shared/CodeGenerator-x86-shared.cpp | 43 + .../jit/x86-shared/CodeGenerator-x86-shared.h | 2 + .../x86-shared/MacroAssembler-x86-shared.cpp | 109 +- .../x86-shared/MacroAssembler-x86-shared.h | 8 + js/src/jsapi-tests/tests.cpp | 1 + js/src/jsapi.cpp | 164 +- js/src/jsapi.h | 67 +- js/src/jscompartment.cpp | 6 +- js/src/jsdate.cpp | 190 +- js/src/jsopcode.h | 8 + js/src/moz.build | 16 + js/src/shell/js.cpp | 1 + .../tests/ecma_6/Class/methodInstallation.js | 12 +- .../ecma_6/LexicalEnvironment/bug-1216623.js | 19 + .../ecma_6/LexicalEnvironment/for-loop.js | 6 +- .../tests/ecma_6/Statements/try-completion.js | 482 ++ .../destructuring-variable-declarations.js | 4 +- js/src/vm/DateObject.h | 6 +- js/src/vm/DateTime.cpp | 38 +- js/src/vm/DateTime.h | 68 +- js/src/vm/Initialization.cpp | 166 + js/src/vm/Interpreter-inl.h | 5 +- js/src/vm/Interpreter.cpp | 24 +- js/src/vm/Opcodes.h | 62 +- js/src/vm/Runtime.cpp | 3 +- js/src/vm/Runtime.h | 1 - js/src/vm/SelfHosting.cpp | 14 +- js/src/vm/UnboxedObject.cpp | 4 +- js/src/vm/Xdr.h | 2 +- python/mozbuild/mozbuild/artifacts.py | 83 +- python/mozbuild/mozpack/copier.py | 18 +- python/mozbuild/mozpack/executables.py | 5 +- python/mozbuild/mozpack/mozjar.py | 15 +- python/mozbuild/mozpack/test/test_copier.py | 10 + python/mozbuild/mozpack/test/test_unify.py | 85 +- python/mozbuild/mozpack/unify.py | 41 +- xpcom/build/XPCOMInit.cpp | 1 + 144 files changed, 15303 insertions(+), 2188 deletions(-) create mode 100644 js/public/Initialization.h create mode 100644 js/src/jit-test/tests/asm.js/sta-transition.js create mode 100644 js/src/jit-test/tests/baseline/bug1216140.js create mode 100644 js/src/jit-test/tests/basic/bug646968-6.js delete mode 100644 js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js create mode 100644 js/src/jit-test/tests/parser/let-after-directive.js create mode 100644 js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h create mode 100644 js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp create mode 100644 js/src/jit/mips-shared/MacroAssembler-mips-shared.h create mode 100644 js/src/jit/mips64/Bailouts-mips64.cpp create mode 100644 js/src/jit/mips64/BaselineCompiler-mips64.cpp create mode 100644 js/src/jit/mips64/BaselineCompiler-mips64.h create mode 100644 js/src/jit/mips64/BaselineIC-mips64.cpp create mode 100644 js/src/jit/mips64/CodeGenerator-mips64.cpp create mode 100644 js/src/jit/mips64/CodeGenerator-mips64.h create mode 100644 js/src/jit/mips64/LIR-mips64.h create mode 100644 js/src/jit/mips64/LOpcodes-mips64.h create mode 100644 js/src/jit/mips64/Lowering-mips64.cpp create mode 100644 js/src/jit/mips64/Lowering-mips64.h create mode 100644 js/src/jit/mips64/MacroAssembler-mips64-inl.h create mode 100644 js/src/jit/mips64/MacroAssembler-mips64.cpp create mode 100644 js/src/jit/mips64/MacroAssembler-mips64.h create mode 100644 js/src/jit/mips64/MoveEmitter-mips64.cpp create mode 100644 js/src/jit/mips64/MoveEmitter-mips64.h create mode 100644 js/src/jit/mips64/SharedIC-mips64.cpp create mode 100644 js/src/jit/mips64/SharedICHelpers-mips64.h create mode 100644 js/src/jit/mips64/SharedICRegisters-mips64.h create mode 100644 js/src/jit/mips64/Simulator-mips64.cpp create mode 100644 js/src/jit/mips64/Simulator-mips64.h create mode 100644 js/src/jit/mips64/Trampoline-mips64.cpp create mode 100644 js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js create mode 100644 js/src/tests/ecma_6/Statements/try-completion.js create mode 100644 js/src/vm/Initialization.cpp diff --git a/config/check_macroassembler_style.py b/config/check_macroassembler_style.py index 9354aa473c..1cd0c71fb6 100644 --- a/config/check_macroassembler_style.py +++ b/config/check_macroassembler_style.py @@ -30,8 +30,8 @@ import sys from check_utils import get_all_toplevel_filenames architecture_independent = set([ 'generic' ]) -all_architecture_names = set([ 'x86', 'x64', 'arm', 'arm64', 'mips32' ]) -all_shared_architecture_names = set([ 'x86_shared', 'arm', 'arm64', 'mips32' ]) +all_architecture_names = set([ 'x86', 'x64', 'arm', 'arm64', 'mips32', 'mips64' ]) +all_shared_architecture_names = set([ 'x86_shared', 'mips_shared', 'arm', 'arm64' ]) reBeforeArg = "(?<=[(,\s])" reArgType = "(?P[\w\s:*&]+)" diff --git a/dom/time/DateCacheCleaner.cpp b/dom/time/DateCacheCleaner.cpp index 0bff04034c..b2cc03fa89 100644 --- a/dom/time/DateCacheCleaner.cpp +++ b/dom/time/DateCacheCleaner.cpp @@ -6,7 +6,7 @@ #include "DateCacheCleaner.h" -#include "jsapi.h" +#include "js/Date.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/Hal.h" @@ -33,7 +33,7 @@ public: void Notify(const SystemTimezoneChangeInformation& aSystemTimezoneChangeInfo) { mozilla::AutoSafeJSContext cx; - JS_ClearDateCaches(cx); + JS::ResetTimeZone(); } }; diff --git a/dom/time/DateCacheCleaner.h b/dom/time/DateCacheCleaner.h index e964629152..5d734b2c44 100644 --- a/dom/time/DateCacheCleaner.h +++ b/dom/time/DateCacheCleaner.h @@ -7,7 +7,7 @@ /* * InitializeDateCacheCleaner registers DateCacheCleaner to * SystemTimeChangeObserver. When time zone is changed, DateCacheCleaner calls - * JS_ClearDateCaches to update the time zone information. + * JS::ResetTimeZone to update the time zone information. */ namespace mozilla { diff --git a/js/public/Date.h b/js/public/Date.h index 2324ad7104..f9df9672ce 100644 --- a/js/public/Date.h +++ b/js/public/Date.h @@ -39,6 +39,22 @@ struct JSContext; namespace JS { +/** + * Re-query the system to determine the current time zone adjustment from UTC, + * including any component due to DST. If the time zone has changed, this will + * cause all Date object non-UTC methods and formatting functions to produce + * appropriately adjusted results. + * + * Left to its own devices, SpiderMonkey itself may occasionally call this + * method to attempt to keep up with system time changes. However, no + * particular frequency of checking is guaranteed. Embedders unable to accept + * occasional inaccuracies should call this method in response to system time + * changes, or immediately before operations requiring instantaneous + * correctness, to guarantee correct behavior. + */ +extern JS_PUBLIC_API(void) +ResetTimeZone(); + class ClippedTime; inline ClippedTime TimeClip(double time); diff --git a/js/public/Initialization.h b/js/public/Initialization.h new file mode 100644 index 0000000000..fa022c9f60 --- /dev/null +++ b/js/public/Initialization.h @@ -0,0 +1,84 @@ +/* -*- 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/. */ + +/* SpiderMonkey initialization and shutdown APIs. */ + +#ifndef js_Initialization_h +#define js_Initialization_h + +#include "jstypes.h" + +namespace JS { +namespace detail { + +enum class InitState { Uninitialized = 0, Running, ShutDown }; + +/** + * SpiderMonkey's initialization status is tracked here, and it controls things + * that should happen only once across all runtimes. It's an API requirement + * that JS_Init (and JS_ShutDown, if called) be called in a thread-aware + * manner, so this (internal -- embedders, don't use!) variable doesn't need to + * be atomic. + */ +extern JS_PUBLIC_DATA(InitState) +libraryInitState; + +} // namespace detail +} // namespace JS + +// These are equivalent to ICU's |UMemAllocFn|, |UMemReallocFn|, and +// |UMemFreeFn| types. The first argument (called |context| in the ICU docs) +// will always be nullptr and should be ignored. +typedef void* (*JS_ICUAllocFn)(const void*, size_t size); +typedef void* (*JS_ICUReallocFn)(const void*, void* p, size_t size); +typedef void (*JS_ICUFreeFn)(const void*, void* p); + +/** + * This function can be used to track memory used by ICU. If it is called, it + * *must* be called before JS_Init. Don't use it unless you know what you're + * doing! + */ +extern JS_PUBLIC_API(bool) +JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, + JS_ICUReallocFn reallocFn, + JS_ICUFreeFn freeFn); + +/** + * Initialize SpiderMonkey, returning true only if initialization succeeded. + * Once this method has succeeded, it is safe to call JS_NewRuntime and other + * JSAPI methods. + * + * This method must be called before any other JSAPI method is used on any + * thread. Once it has been used, it is safe to call any JSAPI method, and it + * remains safe to do so until JS_ShutDown is correctly called. + * + * It is currently not possible to initialize SpiderMonkey multiple times (that + * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so + * again). This restriction may eventually be lifted. + */ +extern JS_PUBLIC_API(bool) +JS_Init(void); + +/** + * Destroy free-standing resources allocated by SpiderMonkey, not associated + * with any runtime, context, or other structure. + * + * This method should be called after all other JSAPI data has been properly + * cleaned up: every new runtime must have been destroyed, every new context + * must have been destroyed, and so on. Calling this method before all other + * resources have been destroyed has undefined behavior. + * + * Failure to call this method, at present, has no adverse effects other than + * leaking memory. This may not always be the case; it's recommended that all + * embedders call this method when all other JSAPI operations have completed. + * + * It is currently not possible to initialize SpiderMonkey multiple times (that + * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so + * again). This restriction may eventually be lifted. + */ +extern JS_PUBLIC_API(void) +JS_ShutDown(void); + +#endif /* js_Initialization_h */ diff --git a/js/src/asmjs/AsmJSCompile.cpp b/js/src/asmjs/AsmJSCompile.cpp index ec96c383a4..c46d0d4b3d 100644 --- a/js/src/asmjs/AsmJSCompile.cpp +++ b/js/src/asmjs/AsmJSCompile.cpp @@ -115,51 +115,6 @@ class ModuleCompiler /***************************************************** Mutable interface */ - bool getOrCreateFunctionEntry(uint32_t funcIndex, Label** label) - { - return compileResults_->getOrCreateFunctionEntry(funcIndex, label); - } - - bool finishGeneratingFunction(AsmFunction& func, CodeGenerator& codegen, - const AsmJSFunctionLabels& labels) - { - // If we have hit OOM then invariants which we assert below may not - // hold, so abort now. - if (masm().oom()) - return false; - - // Code range - unsigned line = func.lineno(); - unsigned column = func.column(); - PropertyName* funcName = func.name(); - if (!compileResults_->addCodeRange(AsmJSModule::FunctionCodeRange(funcName, line, labels))) - return false; - - // Script counts - jit::IonScriptCounts* counts = codegen.extractScriptCounts(); - if (counts && !compileResults_->addFunctionCounts(counts)) { - js_delete(counts); - return false; - } - - // Slow functions - if (func.compileTime() >= 250) { - ModuleCompileResults::SlowFunction sf(funcName, func.compileTime(), line, column); - if (!compileResults_->slowFunctions().append(Move(sf))) - return false; - } - -#if defined(MOZ_VTUNE) || defined(JS_ION_PERF) - // Perf and profiling information - unsigned begin = labels.begin.offset(); - unsigned end = labels.end.offset(); - AsmJSModule::ProfiledFunction profiledFunc(funcName, begin, end, line, column); - if (!compileResults_->addProfiledFunction(profiledFunc)) - return false; -#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF) - return true; - } - void finish(ScopedJSDeletePtr* results) { *results = compileResults_.forget(); } @@ -191,7 +146,6 @@ class FunctionCompiler ModuleCompiler & m_; LifoAlloc & lifo_; - RetType retType_; const AsmFunction & func_; size_t pc_; @@ -217,7 +171,6 @@ class FunctionCompiler FunctionCompiler(ModuleCompiler& m, const AsmFunction& func, LifoAlloc& lifo) : m_(m), lifo_(lifo), - retType_(func.returnedType()), func_(func), pc_(0), alloc_(nullptr), @@ -227,10 +180,10 @@ class FunctionCompiler curBlock_(nullptr) {} - ModuleCompiler & m() const { return m_; } - TempAllocator & alloc() const { return *alloc_; } - LifoAlloc & lifo() const { return lifo_; } - RetType returnedType() const { return retType_; } + ModuleCompiler & m() const { return m_; } + TempAllocator & alloc() const { return *alloc_; } + LifoAlloc & lifo() const { return lifo_; } + RetType returnedType() const { return func_.returnedType(); } bool init() { @@ -803,7 +756,7 @@ class FunctionCompiler return true; } - CallSiteDesc::Kind kind = CallSiteDesc::Kind(-1); // initialize to silence GCC warning + CallSiteDesc::Kind kind = CallSiteDesc::Kind(-1); switch (callee.which()) { case MAsmJSCall::Callee::Internal: kind = CallSiteDesc::Relative; break; case MAsmJSCall::Callee::Dynamic: kind = CallSiteDesc::Register; break; @@ -821,10 +774,10 @@ class FunctionCompiler } public: - bool internalCall(const Signature& sig, Label* entry, const Call& call, MDefinition** def) + bool internalCall(const Signature& sig, uint32_t funcIndex, const Call& call, MDefinition** def) { MIRType returnType = sig.retType().toMIRType(); - return callPrivate(MAsmJSCall::Callee(entry), call, returnType, def); + return callPrivate(MAsmJSCall::Callee(AsmJSInternalCallee(funcIndex)), call, returnType, def); } bool funcPtrCall(const Signature& sig, uint32_t maskLit, uint32_t globalDataOffset, MDefinition* index, @@ -1038,6 +991,61 @@ class FunctionCompiler return pos; } + void fixupRedundantPhis(MBasicBlock* b) + { + for (size_t i = 0, depth = b->stackDepth(); i < depth; i++) { + MDefinition* def = b->getSlot(i); + if (def->isUnused()) + b->setSlot(i, def->toPhi()->getOperand(0)); + } + } + template + void fixupRedundantPhis(MBasicBlock* loopEntry, T& map) + { + if (!map.initialized()) + return; + for (typename T::Enum e(map); !e.empty(); e.popFront()) { + BlockVector& blocks = e.front().value(); + for (size_t i = 0; i < blocks.length(); i++) { + if (blocks[i]->loopDepth() >= loopEntry->loopDepth()) + fixupRedundantPhis(blocks[i]); + } + } + } + bool setLoopBackedge(MBasicBlock* loopEntry, MBasicBlock* backedge, MBasicBlock* afterLoop) + { + if (!loopEntry->setBackedgeAsmJS(backedge)) + return false; + + // Flag all redundant phis as unused. + for (MPhiIterator phi = loopEntry->phisBegin(); phi != loopEntry->phisEnd(); phi++) { + MOZ_ASSERT(phi->numOperands() == 2); + if (phi->getOperand(0) == phi->getOperand(1)) + phi->setUnused(); + } + + // Fix up phis stored in the slots Vector of pending blocks. + if (afterLoop) + fixupRedundantPhis(afterLoop); + fixupRedundantPhis(loopEntry, labeledContinues_); + fixupRedundantPhis(loopEntry, labeledBreaks_); + fixupRedundantPhis(loopEntry, unlabeledContinues_); + fixupRedundantPhis(loopEntry, unlabeledBreaks_); + + // Discard redundant phis and add to the free list. + for (MPhiIterator phi = loopEntry->phisBegin(); phi != loopEntry->phisEnd(); ) { + MPhi* entryDef = *phi++; + if (!entryDef->isUnused()) + continue; + + entryDef->justReplaceAllUsesWith(entryDef->getOperand(0)); + loopEntry->discardPhi(entryDef); + mirGraph().addPhiToFreeList(entryDef); + } + + return true; + } + public: bool closeLoop(MBasicBlock* loopEntry, MBasicBlock* afterLoop) { @@ -1053,7 +1061,7 @@ class FunctionCompiler if (curBlock_) { MOZ_ASSERT(curBlock_->loopDepth() == loopStack_.length() + 1); curBlock_->end(MGoto::New(alloc(), loopEntry)); - if (!loopEntry->setBackedgeAsmJS(curBlock_)) + if (!setLoopBackedge(loopEntry, curBlock_, afterLoop)) return false; } curBlock_ = afterLoop; @@ -1076,7 +1084,7 @@ class FunctionCompiler if (cond->isConstant()) { if (cond->toConstant()->valueToBoolean()) { curBlock_->end(MGoto::New(alloc(), loopEntry)); - if (!loopEntry->setBackedgeAsmJS(curBlock_)) + if (!setLoopBackedge(loopEntry, curBlock_, nullptr)) return false; curBlock_ = nullptr; } else { @@ -1091,7 +1099,7 @@ class FunctionCompiler if (!newBlock(curBlock_, &afterLoop)) return false; curBlock_->end(MTest::New(alloc(), cond, loopEntry, afterLoop)); - if (!loopEntry->setBackedgeAsmJS(curBlock_)) + if (!setLoopBackedge(loopEntry, curBlock_, afterLoop)) return false; curBlock_ = afterLoop; } @@ -1696,13 +1704,7 @@ static bool EmitInternalCall(FunctionCompiler& f, RetType retType, MDefinition** def) { uint32_t funcIndex = f.readU32(); - - Label* entry; - if (!f.m().getOrCreateFunctionEntry(funcIndex, &entry)) - return false; - const Signature& sig = *f.readSignature(); - MOZ_ASSERT_IF(sig.retType() != RetType::Void, sig.retType() == retType); uint32_t lineno, column; @@ -1712,7 +1714,7 @@ EmitInternalCall(FunctionCompiler& f, RetType retType, MDefinition** def) if (!EmitCallArgs(f, sig, &call)) return false; - return f.internalCall(sig, entry, call, def); + return f.internalCall(sig, funcIndex, call, def); } static bool @@ -3123,7 +3125,8 @@ js::GenerateAsmFunctionMIR(ModuleCompiler& m, LifoAlloc& lifo, AsmFunction& func } bool -js::GenerateAsmFunctionCode(ModuleCompiler& m, AsmFunction& func, MIRGenerator& mir, LIRGraph& lir) +js::GenerateAsmFunctionCode(ModuleCompiler& m, AsmFunction& func, MIRGenerator& mir, LIRGraph& lir, + FunctionCompileResults* results) { JitContext jitContext(m.runtime(), /* CompileCompartment = */ nullptr, &mir.alloc()); @@ -3140,18 +3143,19 @@ js::GenerateAsmFunctionCode(ModuleCompiler& m, AsmFunction& func, MIRGenerator& if (!codegen) return false; - Label* funcEntry; - if (!m.getOrCreateFunctionEntry(func.funcIndex(), &funcEntry)) - return false; - - AsmJSFunctionLabels labels(*funcEntry, m.stackOverflowLabel()); + Label entry; + AsmJSFunctionLabels labels(entry, m.stackOverflowLabel()); if (!codegen->generateAsmJS(&labels)) return false; func.accumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC); - if (!m.finishGeneratingFunction(func, *codegen, labels)) - return false; + PropertyName* funcName = func.name(); + unsigned line = func.lineno(); + + // Fill in the results of the function's compilation + AsmJSModule::FunctionCodeRange codeRange(funcName, line, labels); + results->finishCodegen(func, codeRange, *codegen->extractScriptCounts()); // Unlike regular IonMonkey, which links and generates a new JitCode for // every function, we accumulate all the functions in the module in a diff --git a/js/src/asmjs/AsmJSCompile.h b/js/src/asmjs/AsmJSCompile.h index 677e3c0488..240518e1a9 100644 --- a/js/src/asmjs/AsmJSCompile.h +++ b/js/src/asmjs/AsmJSCompile.h @@ -27,6 +27,7 @@ class AsmFunction; class LifoAlloc; class ModuleCompiler; class ModuleCompileResults; +class FunctionCompileResults; namespace jit { class LIRGraph; @@ -76,7 +77,8 @@ class MOZ_RAII AsmModuleCompilerScope bool CreateAsmModuleCompiler(ModuleCompileInputs mci, AsmModuleCompilerScope* scope); bool GenerateAsmFunctionMIR(ModuleCompiler& m, LifoAlloc& lifo, AsmFunction& func, jit::MIRGenerator** mir); -bool GenerateAsmFunctionCode(ModuleCompiler& m, AsmFunction& func, jit::MIRGenerator& mir, jit::LIRGraph& lir); +bool GenerateAsmFunctionCode(ModuleCompiler& m, AsmFunction& func, jit::MIRGenerator& mir, + jit::LIRGraph& lir, FunctionCompileResults* results); void FinishAsmModuleCompilation(ModuleCompiler& m, ScopedJSDeletePtr* results); } // namespace js diff --git a/js/src/asmjs/AsmJSFrameIterator.cpp b/js/src/asmjs/AsmJSFrameIterator.cpp index c836ac930d..128acab7dd 100644 --- a/js/src/asmjs/AsmJSFrameIterator.cpp +++ b/js/src/asmjs/AsmJSFrameIterator.cpp @@ -280,13 +280,13 @@ js::GenerateAsmJSFunctionPrologue(MacroAssembler& masm, unsigned framePushed, masm.haltingAlign(CodeAlignment); - GenerateProfilingPrologue(masm, framePushed, AsmJSExit::None, &labels->begin); + GenerateProfilingPrologue(masm, framePushed, AsmJSExit::None, &labels->profilingEntry); Label body; masm.jump(&body); // Generate normal prologue: masm.haltingAlign(CodeAlignment); - masm.bind(&labels->entry); + masm.bind(&labels->nonProfilingEntry); PushRetAddr(masm); masm.subFromStackPtr(Imm32(framePushed + AsmJSFrameBytesAfterReturnAddress)); diff --git a/js/src/asmjs/AsmJSGlobals.h b/js/src/asmjs/AsmJSGlobals.h index 3675c1e4ff..ac4e283f2f 100644 --- a/js/src/asmjs/AsmJSGlobals.h +++ b/js/src/asmjs/AsmJSGlobals.h @@ -1046,43 +1046,39 @@ class AsmFunction } }; -const size_t LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12; +class FunctionCompileResults +{ + const AsmFunction* func_; + jit::IonScriptCounts* counts_; + AsmJSModule::FunctionCodeRange codeRange_; + + public: + FunctionCompileResults() + : func_(nullptr), + counts_(nullptr), + codeRange_() + {} + + const AsmFunction& func() const { MOZ_ASSERT(func_); return *func_; } + const AsmJSModule::FunctionCodeRange& codeRange() const { return codeRange_; } + jit::IonScriptCounts* counts() const { return counts_; } + + void finishCodegen(AsmFunction& func, AsmJSModule::FunctionCodeRange codeRange, + jit::IonScriptCounts& counts) + { + func_ = &func; + codeRange_ = codeRange; + counts_ = &counts; + } +}; class ModuleCompileResults { public: - struct SlowFunction - { - SlowFunction(PropertyName* name, unsigned ms, unsigned line, unsigned column) - : name(name), ms(ms), line(line), column(column) - {} - - PropertyName* name; - unsigned ms; - unsigned line; - unsigned column; - }; - - typedef Vector SlowFunctionVector; - typedef Vector LabelVector; - typedef Vector FunctionCodeRangeVector; - typedef Vector ScriptCountVector; -#if defined(MOZ_VTUNE) || defined(JS_ION_PERF) - typedef Vector ProfiledFunctionVector; -#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF) private: - LifoAlloc lifo_; jit::MacroAssembler masm_; - SlowFunctionVector slowFunctions_; - LabelVector functionEntries_; - FunctionCodeRangeVector codeRanges_; - ScriptCountVector functionCounts_; -#if defined(MOZ_VTUNE) || defined(JS_ION_PERF) - ProfiledFunctionVector profiledFunctions_; -#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF) - jit::NonAssertingLabel stackOverflowLabel_; jit::NonAssertingLabel asyncInterruptLabel_; jit::NonAssertingLabel syncInterruptLabel_; @@ -1093,8 +1089,7 @@ class ModuleCompileResults public: ModuleCompileResults() - : lifo_(LIFO_ALLOC_PRIMARY_CHUNK_SIZE), - masm_(jit::MacroAssembler::AsmJSToken()), + : masm_(jit::MacroAssembler::AsmJSToken()), usecBefore_(PRMJ_Now()) {} @@ -1106,41 +1101,6 @@ class ModuleCompileResults jit::Label& onDetachedLabel() { return onDetachedLabel_; } jit::Label& onConversionErrorLabel() { return onConversionErrorLabel_; } int64_t usecBefore() { return usecBefore_; } - - SlowFunctionVector& slowFunctions() { return slowFunctions_; } - - size_t numFunctionEntries() const { return functionEntries_.length(); } - jit::Label* functionEntry(unsigned i) { return functionEntries_[i]; } - - bool getOrCreateFunctionEntry(unsigned i, jit::Label** label) { - if (i == UINT32_MAX) - return false; - while (functionEntries_.length() <= i) { - jit::Label* newEntry = lifo_.new_(); - if (!newEntry || !functionEntries_.append(newEntry)) - return false; - } - *label = functionEntries_[i]; - return true; - } - - size_t numCodeRanges() const { return codeRanges_.length(); } - bool addCodeRange(AsmJSModule::FunctionCodeRange range) { return codeRanges_.append(range); } - AsmJSModule::FunctionCodeRange& codeRange(unsigned i) { return codeRanges_[i]; } - - size_t numFunctionCounts() const { return functionCounts_.length(); } - bool addFunctionCounts(jit::IonScriptCounts* counts) { return functionCounts_.append(counts); } - jit::IonScriptCounts* functionCount(unsigned i) { return functionCounts_[i]; } - -#if defined(MOZ_VTUNE) || defined(JS_ION_PERF) - size_t numProfiledFunctions() const { return profiledFunctions_.length(); } - bool addProfiledFunction(AsmJSModule::ProfiledFunction func) { - return profiledFunctions_.append(func); - } - AsmJSModule::ProfiledFunction& profiledFunction(unsigned i) { - return profiledFunctions_[i]; - } -#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF) }; } // namespace js diff --git a/js/src/asmjs/AsmJSLink.cpp b/js/src/asmjs/AsmJSLink.cpp index 83c9b14da4..89e9ba0641 100644 --- a/js/src/asmjs/AsmJSLink.cpp +++ b/js/src/asmjs/AsmJSLink.cpp @@ -230,7 +230,7 @@ ValidateArrayView(JSContext* cx, AsmJSModule::Global& global, HandleValue global bool tac = IsTypedArrayConstructor(v, global.viewType()); bool stac = IsSharedTypedArrayConstructor(v, global.viewType()); - if (!((tac || stac) && stac == isShared)) + if (!(tac || (stac && isShared))) return LinkFail(cx, "bad typed array constructor"); return true; diff --git a/js/src/asmjs/AsmJSModule.cpp b/js/src/asmjs/AsmJSModule.cpp index b2d479f772..544f433e1a 100644 --- a/js/src/asmjs/AsmJSModule.cpp +++ b/js/src/asmjs/AsmJSModule.cpp @@ -322,7 +322,9 @@ AsmJSModule::finish(ExclusiveContext* cx, TokenStream& tokenStream, MacroAssembl heapAccesses_ = masm.extractAsmJSHeapAccesses(); // Call-site metadata used for stack unwinding. - callSites_ = masm.extractCallSites(); + const CallSiteAndTargetVector& callSites = masm.callSites(); + if (!callSites_.appendAll(callSites)) + return false; MOZ_ASSERT(pod.functionBytes_ % AsmJSPageSize == 0); @@ -1330,19 +1332,19 @@ AsmJSModule::CodeRange::CodeRange(uint32_t nameIndex, uint32_t lineNumber, const AsmJSFunctionLabels& l) : nameIndex_(nameIndex), lineNumber_(lineNumber), - begin_(l.begin.offset()), + begin_(l.profilingEntry.offset()), profilingReturn_(l.profilingReturn.offset()), - end_(l.end.offset()) + end_(l.endAfterOOL.offset()) { PodZero(&u); // zero padding for Valgrind u.kind_ = Function; - setDeltas(l.entry.offset(), l.profilingJump.offset(), l.profilingEpilogue.offset()); + setDeltas(l.nonProfilingEntry.offset(), l.profilingJump.offset(), l.profilingEpilogue.offset()); - MOZ_ASSERT(l.begin.offset() < l.entry.offset()); - MOZ_ASSERT(l.entry.offset() < l.profilingJump.offset()); + MOZ_ASSERT(l.profilingEntry.offset() < l.nonProfilingEntry.offset()); + MOZ_ASSERT(l.nonProfilingEntry.offset() < l.profilingJump.offset()); MOZ_ASSERT(l.profilingJump.offset() < l.profilingEpilogue.offset()); MOZ_ASSERT(l.profilingEpilogue.offset() < l.profilingReturn.offset()); - MOZ_ASSERT(l.profilingReturn.offset() < l.end.offset()); + MOZ_ASSERT(l.profilingReturn.offset() < l.endAfterOOL.offset()); } void diff --git a/js/src/asmjs/AsmJSModule.h b/js/src/asmjs/AsmJSModule.h index 5927faf09b..6197df89f5 100644 --- a/js/src/asmjs/AsmJSModule.h +++ b/js/src/asmjs/AsmJSModule.h @@ -102,16 +102,20 @@ enum AsmJSSimdOperation struct MOZ_STACK_CLASS AsmJSFunctionLabels { AsmJSFunctionLabels(jit::Label& entry, jit::Label& overflowExit) - : entry(entry), overflowExit(overflowExit) {} + : nonProfilingEntry(entry), overflowExit(overflowExit) {} - jit::Label begin; - jit::Label& entry; - jit::Label profilingJump; - jit::Label profilingEpilogue; - jit::Label profilingReturn; - jit::Label end; + jit::Label profilingEntry; + jit::Label& nonProfilingEntry; + jit::Label profilingJump; + jit::Label profilingEpilogue; + jit::Label profilingReturn; + jit::Label endAfterOOL; mozilla::Maybe overflowThunk; jit::Label& overflowExit; + + private: + AsmJSFunctionLabels(const AsmJSFunctionLabels&) = delete; + AsmJSFunctionLabels& operator=(const AsmJSFunctionLabels&) = delete; }; // Represents the type and value of an asm.js numeric literal. @@ -314,6 +318,10 @@ class AsmJSModule MOZ_ASSERT(pod.which_ == ArrayView || pod.which_ == SharedArrayView || pod.which_ == ArrayViewCtor); return pod.u.viewType_; } + void makeViewShared() { + MOZ_ASSERT(pod.which_ == ArrayView); + pod.which_ = SharedArrayView; + } PropertyName* mathName() const { MOZ_ASSERT(pod.which_ == MathBuiltinFunction); return name_; @@ -627,6 +635,10 @@ class AsmJSModule PropertyName* name_; public: + FunctionCodeRange() + : CodeRange(), name_(nullptr) + {} + FunctionCodeRange(PropertyName* name, uint32_t lineNumber, const AsmJSFunctionLabels& l) : CodeRange(UINT32_MAX, lineNumber, l), name_(name) {} @@ -1097,6 +1109,15 @@ class AsmJSModule return pod.isSharedView_ == shared; return !pod.isSharedView_ || shared; } + void setViewsAreShared() { + if (pod.hasArrayView_) + pod.isSharedView_ = true; + for (size_t i=0 ; i < globals_.length() ; i++) { + Global& g = globals_[i]; + if (g.which() == Global::ArrayView) + g.makeViewShared(); + } + } /*************************************************************************/ @@ -1146,14 +1167,14 @@ class AsmJSModule bool addCodeRange(CodeRange::Kind kind, uint32_t begin, uint32_t pret, uint32_t end) { return codeRanges_.append(CodeRange(kind, begin, pret, end)); } - bool addFunctionCodeRange(PropertyName* name, FunctionCodeRange&& codeRange) + bool addFunctionCodeRange(PropertyName* name, FunctionCodeRange codeRange) { MOZ_ASSERT(!isFinished()); MOZ_ASSERT(name->isTenured()); if (names_.length() >= UINT32_MAX) return false; codeRange.initNameIndex(names_.length()); - return names_.append(name) && codeRanges_.append(Move(codeRange)); + return names_.append(name) && codeRanges_.append(codeRange); } bool addBuiltinThunkCodeRange(AsmJSExit::BuiltinKind builtin, uint32_t begin, uint32_t profilingReturn, uint32_t end) diff --git a/js/src/asmjs/AsmJSValidate.cpp b/js/src/asmjs/AsmJSValidate.cpp index b77d2be311..75dd4aa6bd 100644 --- a/js/src/asmjs/AsmJSValidate.cpp +++ b/js/src/asmjs/AsmJSValidate.cpp @@ -584,6 +584,8 @@ TypedArrayLoadType(Scalar::Type viewType) MOZ_CRASH("Unexpected array type"); } +const size_t LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12; + namespace { // The ModuleValidator encapsulates the entire validation of an asm.js module. @@ -773,6 +775,10 @@ class MOZ_STACK_CLASS ModuleValidator MOZ_ASSERT(isAnyArrayView()); return u.viewInfo.isSharedView_; } + void setViewIsSharedView() { + MOZ_ASSERT(isAnyArrayView()); + u.viewInfo.isSharedView_ = true; + } bool isMathFunction() const { return which_ == MathBuiltinFunction; } @@ -881,12 +887,25 @@ class MOZ_STACK_CLASS ModuleValidator } }; + struct SlowFunction + { + SlowFunction(PropertyName* name, unsigned ms, unsigned line, unsigned column) + : name(name), ms(ms), line(line), column(column) + {} + + PropertyName* name; + unsigned ms; + unsigned line; + unsigned column; + }; + private: typedef HashMap GlobalMap; typedef HashMap MathNameMap; typedef HashMap AtomicsNameMap; typedef HashMap SimdOperationNameMap; typedef Vector ArrayViewVector; + typedef Vector SlowFunctionVector; public: typedef HashMap ExitMap; @@ -918,6 +937,10 @@ class MOZ_STACK_CLASS ModuleValidator bool canValidateChangeHeap_; bool hasChangeHeap_; bool supportsSimd_; + bool atomicsPresent_; + + Vector functionEntryOffsets_; + SlowFunctionVector slowFunctions_; ScopedJSDeletePtr compileResults_; DebugOnly finishedFunctionBodies_; @@ -943,6 +966,9 @@ class MOZ_STACK_CLASS ModuleValidator canValidateChangeHeap_(false), hasChangeHeap_(false), supportsSimd_(cx->jitSupportsSimd()), + atomicsPresent_(false), + functionEntryOffsets_(cx), + slowFunctions_(cx), compileResults_(nullptr), finishedFunctionBodies_(false) { @@ -1155,6 +1181,7 @@ class MOZ_STACK_CLASS ModuleValidator Global* global = moduleLifo_.new_(Global::AtomicsBuiltinFunction); if (!global) return false; + atomicsPresent_ = true; global->u.atomicsBuiltinFunc_ = func; return globals_.putNew(varName, global); } @@ -1392,6 +1419,9 @@ class MOZ_STACK_CLASS ModuleValidator FuncPtrTable& funcPtrTable(unsigned i) const { return *funcPtrTables_[i]; } + uint32_t functionEntryOffset(unsigned i) { + return functionEntryOffsets_[i]; + } const Global* lookupGlobal(PropertyName* name) const { if (GlobalMap::Ptr p = globals_.lookup(name)) @@ -1438,9 +1468,43 @@ class MOZ_STACK_CLASS ModuleValidator Label& onDetachedLabel() { return compileResults_->onDetachedLabel(); } Label& onOutOfBoundsLabel() { return compileResults_->onOutOfBoundsLabel(); } Label& onConversionErrorLabel() { return compileResults_->onConversionErrorLabel(); } - Label* functionEntry(unsigned i) { return compileResults_->functionEntry(i); } ExitMap::Range allExits() const { return exits_.all(); } + bool finishGeneratingFunction(FunctionCompileResults& results) { + const AsmFunction& func = results.func(); + unsigned i = func.funcIndex(); + if (functionEntryOffsets_.length() <= i && !functionEntryOffsets_.resize(i + 1)) + return false; + + AsmJSModule::FunctionCodeRange codeRange = results.codeRange(); + functionEntryOffsets_[i] = codeRange.entry(); + + PropertyName* funcName = func.name(); + unsigned line = func.lineno(); + unsigned column = func.column(); + + // These must be done before the module is done with function bodies. + if (results.counts() && !module().addFunctionCounts(results.counts())) + return false; + if (!module().addFunctionCodeRange(codeRange.name(), codeRange)) + return false; + + unsigned compileTime = func.compileTime(); + if (compileTime >= 250) { + if (!slowFunctions_.append(SlowFunction(funcName, compileTime, line, column))) + return false; + } + +#if defined(MOZ_VTUNE) || defined(JS_ION_PERF) + // Perf and profiling information + AsmJSModule::ProfiledFunction pf(funcName, codeRange.entry(), codeRange.end(), line, column); + if (!module().addProfiledFunction(Move(pf))) + return false; +#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF) + + return true; + } + bool finishGeneratingEntry(unsigned exportIndex, Label* begin) { MOZ_ASSERT(finishedFunctionBodies_); module_->exportedFunction(exportIndex).initCodeOffset(begin->offset()); @@ -1497,8 +1561,7 @@ class MOZ_STACK_CLASS ModuleValidator for (unsigned elemIndex = 0; elemIndex < table.numElems(); elemIndex++) { AsmJSModule::RelativeLink link(AsmJSModule::RelativeLink::RawPointer); link.patchAtOffset = tableBaseOffset + elemIndex * sizeof(uint8_t*); - Label* entry = functionEntry(table.elem(elemIndex).funcIndex()); - link.targetOffset = entry->offset(); + link.targetOffset = functionEntryOffset(table.elem(elemIndex).funcIndex()); if (!module_->addRelativeLink(link)) return false; } @@ -1509,29 +1572,28 @@ class MOZ_STACK_CLASS ModuleValidator } void startFunctionBodies() { + if (atomicsPresent_) { + for (GlobalMap::Range r = globals_.all() ; !r.empty() ; r.popFront()) { + Global* g = r.front().value(); + if (g->isAnyArrayView()) + g->setViewIsSharedView(); + } + module_->setViewsAreShared(); + } module_->startFunctionBodies(); } bool finishFunctionBodies(ScopedJSDeletePtr* compileResults) { // Take ownership of compilation results compileResults_ = compileResults->forget(); - // These must be done before the module is done with function bodies. - for (size_t i = 0; i < compileResults_->numFunctionCounts(); ++i) { - if (!module().addFunctionCounts(compileResults_->functionCount(i))) - return false; - } -#if defined(MOZ_VTUNE) || defined(JS_ION_PERF) - for (size_t i = 0; i < compileResults_->numProfiledFunctions(); ++i) { - if (!module().addProfiledFunction(Move(compileResults_->profiledFunction(i)))) - return false; - } -#endif // defined(MOZ_VTUNE) || defined(JS_ION_PERF) - - // Hand in code ranges, script counts and perf profiling data to the AsmJSModule - for (size_t i = 0; i < compileResults_->numCodeRanges(); ++i) { - AsmJSModule::FunctionCodeRange& codeRange = compileResults_->codeRange(i); - if (!module().addFunctionCodeRange(codeRange.name(), Move(codeRange))) - return false; + // Patch internal calls to their final positions + for (auto& cs : masm().callSites()) { + if (!cs.isInternal()) + continue; + MOZ_ASSERT(cs.kind() == CallSiteDesc::Relative); + uint32_t callerOffset = cs.returnAddressOffset(); + uint32_t calleeOffset = functionEntryOffset(cs.targetIndex()); + masm().patchCall(callerOffset, calleeOffset); } // When an interrupt is triggered, all function code is mprotected and, @@ -1553,19 +1615,18 @@ class MOZ_STACK_CLASS ModuleValidator ScopedJSFreePtr slowFuns; int64_t usecAfter = PRMJ_Now(); int msTotal = (usecAfter - compileResults_->usecBefore()) / PRMJ_USEC_PER_MSEC; - ModuleCompileResults::SlowFunctionVector& slowFunctions = compileResults_->slowFunctions(); - if (!slowFunctions.empty()) { - slowFuns.reset(JS_smprintf("; %d functions compiled slowly: ", slowFunctions.length())); + if (!slowFunctions_.empty()) { + slowFuns.reset(JS_smprintf("; %d functions compiled slowly: ", slowFunctions_.length())); if (!slowFuns) return; - for (unsigned i = 0; i < slowFunctions.length(); i++) { - ModuleCompileResults::SlowFunction& func = slowFunctions[i]; + for (unsigned i = 0; i < slowFunctions_.length(); i++) { + ModuleValidator::SlowFunction& func = slowFunctions_[i]; JSAutoByteString name; if (!AtomToPrintableString(cx_, func.name, &name)) return; slowFuns.reset(JS_smprintf("%s%s:%u:%u (%ums)%s", slowFuns.get(), name.ptr(), func.line, func.column, func.ms, - i+1 < slowFunctions.length() ? ", " : "")); + i+1 < slowFunctions_.length() ? ", " : "")); if (!slowFuns) return; } @@ -6520,7 +6581,10 @@ CheckFunctionsSequential(ModuleValidator& m, ScopedJSDeletePtraccumulateCompileTime((PRMJ_Now() - before) / PRMJ_USEC_PER_MSEC); } - if (!GenerateAsmFunctionCode(mc, *func, *mir, *lir)) + FunctionCompileResults results; + if (!GenerateAsmFunctionCode(mc, *func, *mir, *lir, &results)) + return false; + if (!m.finishGeneratingFunction(results)) return false; } @@ -6601,10 +6665,10 @@ GetFinishedCompilation(ModuleCompiler& m, ParallelGroupState& group) } static bool -GetUsedTask(ModuleCompiler& m, ParallelGroupState& group, AsmJSParallelTask** outTask) +GetUsedTask(ModuleValidator& m, ModuleCompiler& mc, ParallelGroupState& group, AsmJSParallelTask** outTask) { // Block until a used LifoAlloc becomes available. - AsmJSParallelTask* task = GetFinishedCompilation(m, group); + AsmJSParallelTask* task = GetFinishedCompilation(mc, group); if (!task) return false; @@ -6612,7 +6676,10 @@ GetUsedTask(ModuleCompiler& m, ParallelGroupState& group, AsmJSParallelTask** ou func.accumulateCompileTime(task->compileTime); // Perform code generation on the main thread. - if (!GenerateAsmFunctionCode(m, func, *task->mir, *task->lir)) + FunctionCompileResults results; + if (!GenerateAsmFunctionCode(mc, func, *task->mir, *task->lir, &results)) + return false; + if (!m.finishGeneratingFunction(results)) return false; group.compiledJobs++; @@ -6664,7 +6731,7 @@ CheckFunctionsParallel(ModuleValidator& m, ParallelGroupState& group, if (tk != TOK_FUNCTION) break; - if (!task && !GetUnusedTask(group, i, &task) && !GetUsedTask(mc, group, &task)) + if (!task && !GetUnusedTask(group, i, &task) && !GetUsedTask(m, mc, group, &task)) return false; AsmFunction* func; @@ -6692,7 +6759,7 @@ CheckFunctionsParallel(ModuleValidator& m, ParallelGroupState& group, // Block for all outstanding helpers to complete. while (group.outstandingJobs > 0) { AsmJSParallelTask* ignored = nullptr; - if (!GetUsedTask(mc, group, &ignored)) + if (!GetUsedTask(m, mc, group, &ignored)) return false; } @@ -7162,7 +7229,9 @@ GenerateEntry(ModuleValidator& m, unsigned exportIndex) // Call into the real function. masm.assertStackAlignment(AsmJSStackAlignment); - masm.call(CallSiteDesc(CallSiteDesc::Relative), m.functionEntry(func.funcIndex())); + Label funcLabel; + funcLabel.bind(m.functionEntryOffset(func.funcIndex())); + masm.call(CallSiteDesc(CallSiteDesc::Relative), &funcLabel); // Recover the stack pointer value before dynamic alignment. masm.loadAsmJSActivation(scratch); diff --git a/js/src/builtin/Module.js b/js/src/builtin/Module.js index 02ec01e800..c462135d7c 100644 --- a/js/src/builtin/Module.js +++ b/js/src/builtin/Module.js @@ -234,6 +234,9 @@ function ModuleDeclarationInstantiation() CreateImportBinding(env, imp.localName, resolution.module, resolution.bindingName); } } + + // Step 16.iv + InstantiateModuleFunctionDeclarations(module); } // 15.2.1.16.5 ModuleEvaluation() diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp index c8b659c8d8..86f1ddc165 100644 --- a/js/src/builtin/ModuleObject.cpp +++ b/js/src/builtin/ModuleObject.cpp @@ -490,6 +490,19 @@ ModuleNamespaceObject::ProxyHandler::ownPropertyKeys(JSContext* cx, HandleObject return true; } +/////////////////////////////////////////////////////////////////////////// +// FunctionDeclaration + +FunctionDeclaration::FunctionDeclaration(HandleAtom name, HandleFunction fun) + : name(name), fun(fun) +{} + +void FunctionDeclaration::trace(JSTracer* trc) +{ + TraceEdge(trc, &name, "FunctionDeclaration name"); + TraceEdge(trc, &fun, "FunctionDeclaration fun"); +} + /////////////////////////////////////////////////////////////////////////// // ModuleObject @@ -548,6 +561,12 @@ ModuleObject::create(ExclusiveContext* cx, HandleObject enclosingStaticScope) } self->initReservedSlot(ImportBindingsSlot, PrivateValue(bindings)); + + FunctionDeclarationVector* funDecls = cx->new_(cx); + if (!funDecls) + return nullptr; + + self->initReservedSlot(FunctionDeclarationsSlot, PrivateValue(funDecls)); return self; } @@ -559,6 +578,8 @@ ModuleObject::finalize(js::FreeOp* fop, JSObject* obj) fop->delete_(&self->importBindings()); if (IndirectBindingMap* bindings = self->namespaceBindings()) fop->delete_(bindings); + if (FunctionDeclarationVector* funDecls = self->functionDeclarations()) + fop->delete_(funDecls); } ModuleEnvironmentObject* @@ -606,6 +627,16 @@ ModuleObject::namespace_() return &value.toObject().as(); } +FunctionDeclarationVector* +ModuleObject::functionDeclarations() +{ + Value value = getReservedSlot(FunctionDeclarationsSlot); + if (value.isUndefined()) + return nullptr; + + return static_cast(value.toPrivate()); +} + void ModuleObject::init(HandleScript script) { @@ -691,6 +722,9 @@ ModuleObject::trace(JSTracer* trc, JSObject* obj) TraceBindings(trc, module.importBindings()); if (IndirectBindingMap* bindings = module.namespaceBindings()) TraceBindings(trc, *bindings); + + if (FunctionDeclarationVector* funDecls = module.functionDeclarations()) + funDecls->trace(trc); } void @@ -703,6 +737,42 @@ ModuleObject::createEnvironment() setReservedSlot(EnvironmentSlot, getReservedSlot(InitialEnvironmentSlot)); } +bool +ModuleObject::noteFunctionDeclaration(ExclusiveContext* cx, HandleAtom name, HandleFunction fun) +{ + FunctionDeclarationVector* funDecls = functionDeclarations(); + return funDecls->emplaceBack(name, fun); +} + +/* static */ bool +ModuleObject::instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject self) +{ + FunctionDeclarationVector* funDecls = self->functionDeclarations(); + if (!funDecls) { + JS_ReportError(cx, "Module function declarations have already been instantiated"); + return false; + } + + RootedModuleEnvironmentObject env(cx, &self->initialEnvironment()); + RootedFunction fun(cx); + RootedValue value(cx); + + for (const auto& funDecl : *funDecls) { + fun = funDecl.fun; + RootedObject obj(cx, Lambda(cx, fun, env)); + if (!obj) + return false; + + value = ObjectValue(*fun); + if (!SetProperty(cx, env, funDecl.name->asPropertyName(), value)) + return false; + } + + js_delete(funDecls); + self->setReservedSlot(FunctionDeclarationsSlot, UndefinedValue()); + return true; +} + void ModuleObject::setEvaluated() { diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h index ebfc84a99d..9b5fe511a1 100644 --- a/js/src/builtin/ModuleObject.h +++ b/js/src/builtin/ModuleObject.h @@ -145,6 +145,17 @@ class ModuleNamespaceObject : public ProxyObject typedef Rooted RootedModuleNamespaceObject; typedef Handle HandleModuleNamespaceObject; +struct FunctionDeclaration +{ + FunctionDeclaration(HandleAtom name, HandleFunction fun); + void trace(JSTracer* trc); + + RelocatablePtrAtom name; + RelocatablePtrFunction fun; +}; + +using FunctionDeclarationVector = TraceableVector; + class ModuleObject : public NativeObject { public: @@ -164,6 +175,7 @@ class ModuleObject : public NativeObject ImportBindingsSlot, NamespaceExportsSlot, NamespaceBindingsSlot, + FunctionDeclarationsSlot, SlotCount }; @@ -197,17 +209,21 @@ class ModuleObject : public NativeObject void createEnvironment(); + bool noteFunctionDeclaration(ExclusiveContext* cx, HandleAtom name, HandleFunction fun); + static bool instantiateFunctionDeclarations(JSContext* cx, HandleModuleObject self); + void setEvaluated(); static bool evaluate(JSContext* cx, HandleModuleObject self, MutableHandleValue rval); static ModuleNamespaceObject* createNamespace(JSContext* cx, HandleModuleObject self, - HandleArrayObject exports); + HandleArrayObject exports); private: static void trace(JSTracer* trc, JSObject* obj); static void finalize(js::FreeOp* fop, JSObject* obj); bool hasScript() const; + FunctionDeclarationVector* functionDeclarations(); }; // Process a module's parse tree to collate the import and export data used when diff --git a/js/src/builtin/ReflectParse.cpp b/js/src/builtin/ReflectParse.cpp index b50f8906b1..da397a5cb5 100644 --- a/js/src/builtin/ReflectParse.cpp +++ b/js/src/builtin/ReflectParse.cpp @@ -2491,8 +2491,9 @@ ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst) return true; } - return (pn->isKind(PNK_VAR)) - ? variableDeclaration(pn, false, dst) + bool lexical = pn->isKind(PNK_LET) || pn->isKind(PNK_CONST); + return (lexical || pn->isKind(PNK_VAR)) + ? variableDeclaration(pn, lexical, dst) : expression(pn, dst); } @@ -2641,32 +2642,31 @@ ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst) if (!statement(pn->pn_right, &stmt)) return false; - if (head->isKind(PNK_FORIN)) { + if (head->isKind(PNK_FORIN) || head->isKind(PNK_FOROF)) { RootedValue var(cx); - return (!head->pn_kid1 - ? pattern(head->pn_kid2, &var) - : head->pn_kid1->isKind(PNK_LEXICALSCOPE) - ? variableDeclaration(head->pn_kid1->pn_expr, true, &var) - : variableDeclaration(head->pn_kid1, false, &var)) && - forIn(pn, head, var, stmt, dst); - } - - if (head->isKind(PNK_FOROF)) { - RootedValue var(cx); - return (!head->pn_kid1 - ? pattern(head->pn_kid2, &var) - : head->pn_kid1->isKind(PNK_LEXICALSCOPE) - ? variableDeclaration(head->pn_kid1->pn_expr, true, &var) - : variableDeclaration(head->pn_kid1, false, &var)) && - forOf(pn, head, var, stmt, dst); + if (!head->pn_kid1) { + if (!pattern(head->pn_kid2, &var)) + return false; + } else if (head->pn_kid1->isKind(PNK_LEXICALSCOPE)) { + if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var)) + return false; + } else { + if (!variableDeclaration(head->pn_kid1, + head->pn_kid1->isKind(PNK_LET) || + head->pn_kid1->isKind(PNK_CONST), + &var)) + { + return false; + } + } + if (head->isKind(PNK_FORIN)) + return forIn(pn, head, var, stmt, dst); + return forOf(pn, head, var, stmt, dst); } RootedValue init(cx), test(cx), update(cx); - return forInit(head->pn_kid1 && !head->pn_kid1->isKind(PNK_FRESHENBLOCK) - ? head->pn_kid1 - : nullptr, - &init) && + return forInit(head->pn_kid1, &init) && optExpression(head->pn_kid2, &test) && optExpression(head->pn_kid3, &update) && builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 70279e63cb..31b85792d4 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2323,7 +2323,6 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) case PNK_FORIN: // by PNK_FOR case PNK_FOROF: // by PNK_FOR case PNK_FORHEAD: // by PNK_FOR - case PNK_FRESHENBLOCK: // by PNK_FOR case PNK_CLASSMETHOD: // by PNK_CLASS case PNK_CLASSNAMES: // by PNK_CLASS case PNK_CLASSMETHODLIST: // by PNK_CLASS @@ -4972,6 +4971,15 @@ BytecodeEmitter::emitTry(ParseNode* pn) for (ParseNode* pn3 = catchList->pn_head; pn3; pn3 = pn3->pn_next) { MOZ_ASSERT(this->stackDepth == depth); + // Clear the frame's return value that might have been set by the + // try block: + // + // eval("try { 1; throw 2 } catch(e) {}"); // undefined, not 1 + if (!emit1(JSOP_UNDEFINED)) + return false; + if (!emit1(JSOP_SETRVAL)) + return false; + // Emit the lexical scope and catch body. MOZ_ASSERT(pn3->isKind(PNK_LEXICALSCOPE)); if (!emitTree(pn3)) @@ -5022,14 +5030,26 @@ BytecodeEmitter::emitTry(ParseNode* pn) stmtInfo.type = StmtType::SUBROUTINE; if (!updateSourceCoordNotes(pn->pn_kid3->pn_pos.begin)) return false; - if (!emit1(JSOP_FINALLY) || - !emit1(JSOP_GETRVAL) || - !emitTree(pn->pn_kid3) || - !emit1(JSOP_SETRVAL) || - !emit1(JSOP_RETSUB)) - { + if (!emit1(JSOP_FINALLY)) + return false; + if (!emit1(JSOP_GETRVAL)) + return false; + + // Clear the frame's return value to make break/continue return + // correct value even if there's no other statement before them: + // + // eval("x: try { 1 } finally { break x; }"); // undefined, not 1 + if (!emit1(JSOP_UNDEFINED)) + return false; + if (!emit1(JSOP_SETRVAL)) + return false; + + if (!emitTree(pn->pn_kid3)) + return false; + if (!emit1(JSOP_SETRVAL)) + return false; + if (!emit1(JSOP_RETSUB)) return false; - } hasTryFinally = true; MOZ_ASSERT(this->stackDepth == depth); } @@ -5268,6 +5288,15 @@ BytecodeEmitter::emitIterator() bool BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn, bool* letDecl) { + // ES6 specifies that loop variables get a fresh binding in each iteration. + // This is currently implemented for C-style for(;;) loops, but not + // for-in/of loops, though a similar approach should work. See bug 449811. + // + // In `for (let x in/of EXPR)`, ES6 specifies that EXPR is evaluated in a + // scope containing an uninitialized `x`. If EXPR accesses `x`, we should + // get a ReferenceError due to the TDZ violation. This is not yet + // implemented. See bug 1069480. + *letDecl = pn->isKind(PNK_LEXICALSCOPE); MOZ_ASSERT_IF(*letDecl, pn->isLexical()); @@ -5297,7 +5326,6 @@ BytecodeEmitter::emitForInOrOfVariables(ParseNode* pn, bool* letDecl) return true; } - bool BytecodeEmitter::emitForOf(StmtType type, ParseNode* pn, ptrdiff_t top) { @@ -5570,8 +5598,9 @@ BytecodeEmitter::emitForIn(ParseNode* pn, ptrdiff_t top) return true; } +/* C-style `for (init; cond; update) ...` loop. */ bool -BytecodeEmitter::emitNormalFor(ParseNode* pn, ptrdiff_t top) +BytecodeEmitter::emitCStyleFor(ParseNode* pn, ptrdiff_t top) { LoopStmtInfo stmtInfo(cx); pushLoopStatement(&stmtInfo, StmtType::FOR_LOOP, top); @@ -5579,28 +5608,48 @@ BytecodeEmitter::emitNormalFor(ParseNode* pn, ptrdiff_t top) ParseNode* forHead = pn->pn_left; ParseNode* forBody = pn->pn_right; - /* C-style for (init; cond; update) ... loop. */ + // If the head of this for-loop declared any lexical variables, the parser + // wrapped this PNK_FOR node in a PNK_LEXICALSCOPE representing the + // implicit scope of those variables. By the time we get here, we have + // already entered that scope. So far, so good. + // + // ### Scope freshening + // + // Each iteration of a `for (let V...)` loop creates a fresh loop variable + // binding for V, even if the loop is a C-style `for(;;)` loop: + // + // var funcs = []; + // for (let i = 0; i < 2; i++) + // funcs.push(function() { return i; }); + // assertEq(funcs[0](), 0); // the two closures capture... + // assertEq(funcs[1](), 1); // ...two different `i` bindings + // + // This is implemented by "freshening" the implicit block -- changing the + // scope chain to a fresh clone of the instantaneous block object -- each + // iteration, just before evaluating the "update" in for(;;) loops. + // + // No freshening occurs in `for (const ...;;)` as there's no point: you + // can't reassign consts. This is observable through the Debugger API. (The + // ES6 spec also skips cloning the environment in this case.) bool forLoopRequiresFreshening = false; if (ParseNode* init = forHead->pn_kid1) { - if (init->isKind(PNK_FRESHENBLOCK)) { - // The loop's init declaration was hoisted into an enclosing lexical - // scope node. Note that the block scope must be freshened each - // iteration. - forLoopRequiresFreshening = true; - } else { - emittingForInit = true; - if (!updateSourceCoordNotes(init->pn_pos.begin)) - return false; - if (!emitTree(init)) - return false; - emittingForInit = false; + forLoopRequiresFreshening = init->isKind(PNK_LET); - if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) { - // 'init' is an expression, not a declaration. emitTree left - // its value on the stack. - if (!emit1(JSOP_POP)) - return false; - } + // Emit the `init` clause, whether it's an expression or a variable + // declaration. (The loop variables were hoisted into an enclosing + // scope, but we still need to emit code for the initializers.) + emittingForInit = true; + if (!updateSourceCoordNotes(init->pn_pos.begin)) + return false; + if (!emitTree(init)) + return false; + emittingForInit = false; + + if (!init->isKind(PNK_VAR) && !init->isKind(PNK_LET) && !init->isKind(PNK_CONST)) { + // 'init' is an expression, not a declaration. emitTree left its + // value on the stack. + if (!emit1(JSOP_POP)) + return false; } } @@ -5728,7 +5777,7 @@ BytecodeEmitter::emitFor(ParseNode* pn, ptrdiff_t top) return emitForOf(StmtType::FOR_OF_LOOP, pn, top); MOZ_ASSERT(pn->pn_left->isKind(PNK_FORHEAD)); - return emitNormalFor(pn, top); + return emitCStyleFor(pn, top); } MOZ_NEVER_INLINE bool @@ -5846,13 +5895,16 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) MOZ_ASSERT(!needsProto); /* - * For a script we emit the code as we parse. Thus the bytecode for - * top-level functions should go in the prologue to predefine their - * names in the variable object before the already-generated main code - * is executed. This extra work for top-level scripts is not necessary - * when we emit the code for a function. It is fully parsed prior to - * invocation of the emitter and calls to emitTree for function - * definitions can be scheduled before generating the rest of code. + * For scripts we put the bytecode for top-level functions in the prologue + * to predefine their names in the variable object before the main code is + * executed. + * + * Functions are fully parsed prior to invocation of the emitter and calls + * to emitTree for function definitions are scheduled before generating + * the rest of code. + * + * For modules, we record the function and instantiate the binding during + * ModuleDeclarationInstantiation(), before the script is run. */ if (sc->isGlobalContext()) { MOZ_ASSERT(pn->pn_scopecoord.isFree()); @@ -5864,7 +5916,7 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) if (!updateSourceCoordNotes(pn->pn_pos.begin)) return false; switchToMain(); - } else { + } else if (sc->isFunctionBox()) { #ifdef DEBUG BindingIter bi(script); while (bi->name() != fun->atom()) @@ -5881,6 +5933,11 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) return false; if (!emit1(JSOP_POP)) return false; + } else { + RootedModuleObject module(cx, sc->asModuleBox()->module()); + RootedAtom name(cx, fun->atom()); + if (!module->noteFunctionDeclaration(cx, name, fun)) + return false; } return true; @@ -7041,12 +7098,25 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, return false; } + // Class methods are not enumerable. + if (type == ClassBody) { + switch (op) { + case JSOP_INITPROP: op = JSOP_INITHIDDENPROP; break; + case JSOP_INITPROP_GETTER: op = JSOP_INITHIDDENPROP_GETTER; break; + case JSOP_INITPROP_SETTER: op = JSOP_INITHIDDENPROP_SETTER; break; + default: MOZ_CRASH("Invalid op"); + } + } + if (isIndex) { objp.set(nullptr); switch (op) { - case JSOP_INITPROP: op = JSOP_INITELEM; break; - case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; - case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; + case JSOP_INITPROP: op = JSOP_INITELEM; break; + case JSOP_INITHIDDENPROP: op = JSOP_INITHIDDENELEM; break; + case JSOP_INITPROP_GETTER: op = JSOP_INITELEM_GETTER; break; + case JSOP_INITHIDDENPROP_GETTER: op = JSOP_INITHIDDENELEM_GETTER; break; + case JSOP_INITPROP_SETTER: op = JSOP_INITELEM_SETTER; break; + case JSOP_INITHIDDENPROP_SETTER: op = JSOP_INITHIDDENELEM_SETTER; break; default: MOZ_CRASH("Invalid op"); } if (!emit1(op)) @@ -7059,6 +7129,8 @@ BytecodeEmitter::emitPropertyList(ParseNode* pn, MutableHandlePlainObject objp, return false; if (objp) { + MOZ_ASSERT(type == ObjectLiteral); + MOZ_ASSERT(!IsHiddenInitOp(op)); MOZ_ASSERT(!objp->inDictionaryMode()); Rooted id(cx, AtomToId(key->pn_atom)); RootedValue undefinedValue(cx, UndefinedValue()); diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index 2ce95dbb5b..1c2cff9177 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -586,7 +586,7 @@ struct BytecodeEmitter bool emitFor(ParseNode* pn, ptrdiff_t top); bool emitForIn(ParseNode* pn, ptrdiff_t top); bool emitForInOrOfVariables(ParseNode* pn, bool* letDecl); - bool emitNormalFor(ParseNode* pn, ptrdiff_t top); + bool emitCStyleFor(ParseNode* pn, ptrdiff_t top); bool emitWhile(ParseNode* pn, ptrdiff_t top); bool emitBreak(PropertyName* label); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index accde97bc7..edeb85f570 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -408,7 +408,6 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) case PNK_FORIN: case PNK_FOROF: case PNK_FORHEAD: - case PNK_FRESHENBLOCK: case PNK_CLASSMETHOD: case PNK_CLASSMETHODLIST: case PNK_CLASSNAMES: @@ -1710,7 +1709,6 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_GENERATOR: case PNK_EXPORT_BATCH_SPEC: case PNK_OBJECT_PROPERTY_NAME: - case PNK_FRESHENBLOCK: case PNK_POSHOLDER: MOZ_ASSERT(pn->isArity(PN_NULLARY)); return true; diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index bd045e5597..d6dd032efe 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -571,10 +571,6 @@ class FullParseHandler return new_(kind, JSOP_NOP, pn1, pn2, pn3, pos); } - ParseNode* newFreshenBlock(const TokenPos& pos) { - return new_(PNK_FRESHENBLOCK, pos); - } - ParseNode* newSwitchStatement(uint32_t begin, ParseNode* discriminant, ParseNode* caseList) { TokenPos pos(begin, caseList->pn_pos.end); return new_(PNK_SWITCH, JSOP_NOP, pos, discriminant, caseList); diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index c226578e86..667afaa271 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -374,7 +374,6 @@ class NameResolver case PNK_CONTINUE: case PNK_DEBUGGER: case PNK_EXPORT_BATCH_SPEC: - case PNK_FRESHENBLOCK: case PNK_OBJECT_PROPERTY_NAME: case PNK_POSHOLDER: MOZ_ASSERT(cur->isArity(PN_NULLARY)); diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index 694600a62a..cb35d11ed6 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -214,7 +214,6 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) case PNK_DEBUGGER: case PNK_EXPORT_BATCH_SPEC: case PNK_OBJECT_PROPERTY_NAME: - case PNK_FRESHENBLOCK: case PNK_POSHOLDER: MOZ_ASSERT(pn->isArity(PN_NULLARY)); MOZ_ASSERT(!pn->isUsed(), "handle non-trivial cases separately"); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 2785f3e2a2..5d7771cd5d 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -165,7 +165,6 @@ class PackedScopeCoordinate F(FORIN) \ F(FOROF) \ F(FORHEAD) \ - F(FRESHENBLOCK) \ F(ARGSBODY) \ F(SPREAD) \ F(MUTATEPROTO) \ diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index e05e68499e..ddb6160f16 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -4080,7 +4080,8 @@ Parser::pushLetScope(HandleStaticBlockObject blockObj, AutoPus template <> SyntaxParseHandler::Node -Parser::pushLetScope(HandleStaticBlockObject blockObj, AutoPushStmtInfoPC& stmt) +Parser::pushLetScope(HandleStaticBlockObject blockObj, + AutoPushStmtInfoPC& stmt) { JS_ALWAYS_FALSE(abortIfSyntaxParser()); return SyntaxParseHandler::NodeFailure; @@ -5203,19 +5204,37 @@ Parser::forStatement(YieldHandling yieldHandling) MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR); - /* - * True if we have 'for (var/let/const ...)'. - */ + // True if we have 'for (var/let/const ...)'. bool isForDecl = false; + // The next three variables are used to implement `for (let/const ...)`. + // + // We generate an implicit block, wrapping the whole loop, to store loop + // variables declared this way. Note that if the loop uses `for (var...)` + // instead, those variables go on some existing enclosing scope, so no + // implicit block scope is created. + // + // All three variables remain null/none if the loop is any other form. + // + // blockObj is the static block object for the implicit block scope. + RootedStaticBlockObject blockObj(context); + + // letStmt is the BLOCK StmtInfo for the implicit block. + // + // Caution: `letStmt.emplace()` creates some Rooted objects. Rooteds must + // be created/destroyed in FIFO order. Therefore adding a Rooted in this + // function, between this point and the .emplace() call below, would trip + // assertions. + Maybe letStmt; + + // The PNK_LEXICALSCOPE node containing blockObj's ObjectBox. + ParseNode* forLetImpliedBlock = nullptr; + // True if a 'let' token at the head is parsed as an identifier instead of // as starting a declaration. bool letIsIdentifier = false; - /* Non-null when isForDecl is true for a 'for (let ...)' statement. */ - RootedStaticBlockObject blockObj(context); - - /* Set to 'x' in 'for (x ;... ;...)' or 'for (x in ...)'. */ + // Set to 'x' in 'for (x; ...; ...)' or 'for (x in ...)'. ParseNode* pn1; TokenStream::Modifier modifier = TokenStream::Operand; @@ -5265,9 +5284,19 @@ Parser::forStatement(YieldHandling yieldHandling) // Initialize the enclosing scope manually for the call to // |variables| below. + blockObj = StaticBlockObject::create(context); + if (!blockObj) + return null(); blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope()); + letStmt.emplace(*this, StmtType::BLOCK); + forLetImpliedBlock = pushLetScope(blockObj, *letStmt); + if (!forLetImpliedBlock) + return null(); + (*letStmt)->isForLetBlock = true; + + MOZ_ASSERT(CurrentLexicalStaticBlock(pc) == blockObj); pn1 = variables(yieldHandling, constDecl ? PNK_CONST : PNK_LET, InForInit, - nullptr, blockObj, DontHoistVars); + nullptr, blockObj, HoistVars); } else { pn1 = expr(InProhibited, yieldHandling, TripledotProhibited); } @@ -5285,60 +5314,10 @@ Parser::forStatement(YieldHandling yieldHandling) } MOZ_ASSERT_IF(isForDecl, pn1->isArity(PN_LIST)); - MOZ_ASSERT(!!blockObj == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST)))); - - // If the head of a for-loop declares any lexical variables, we generate an - // implicit block to store them. We implement this by desugaring. These: - // - // for (let/const ; ; ) - // for (let in ) - // for (let of ) - // - // transform into roughly the same parse trees as these (using deprecated - // let-block syntax): - // - // let () { for (; ; ) } - // let () { for ( in ) } - // let () { for ( of ) } - // - // This desugaring is not ES6 compliant. Initializers in the head of a - // let-block are evaluated *outside* the scope of the variables being - // initialized. ES6 mandates that they be evaluated in the same scope, - // triggering used-before-initialization temporal dead zone errors as - // necessary. See bug 1216623 on scoping and bug 1069480 on TDZ. - // - // Additionally, in ES6, each iteration of a for-loop creates a fresh - // binding of the loop variables. For example: - // - // var funcs = []; - // for (let i = 0; i < 2; i++) - // funcs.push(function() { return i; }); - // assertEq(funcs[0](), 0); // the two closures capture... - // assertEq(funcs[1](), 1); // ...two different `i` bindings - // - // These semantics are implemented by "freshening" the implicit block -- - // changing the scope chain to a fresh clone of the instantaneous block - // object -- each iteration, just before evaluating the "update" in - // for(;;) loops. We don't implement this freshening for for-in/of loops - // yet: bug 449811. - // - // No freshening occurs in `for (const ...;;)` as there's no point: you - // can't reassign consts. This is observable through the Debugger API. (The - // ES6 spec also skips cloning the environment in this case.) - // - // If the for-loop head includes a lexical declaration, then we create an - // implicit block scope, and: - // - // * forLetImpliedBlock is the node for the implicit block scope. - // * forLetDecl is the node for the decl 'let/const '. - // - // Otherwise both are null. - ParseNode* forLetImpliedBlock = nullptr; - ParseNode* forLetDecl = nullptr; + MOZ_ASSERT(letStmt.isSome() == (isForDecl && (pn1->isOp(JSOP_DEFLET) || pn1->isOp(JSOP_DEFCONST)))); // If there's an |in| keyword here, it's a for-in loop, by dint of careful // parsing of |pn1|. - Maybe letStmt; /* used if blockObj != nullptr. */ ParseNode* pn2; /* forHead->pn_kid2 */ ParseNode* pn3; /* forHead->pn_kid3 */ ParseNodeKind headKind = PNK_FORHEAD; @@ -5409,7 +5388,7 @@ Parser::forStatement(YieldHandling yieldHandling) } } else { /* Not a declaration. */ - MOZ_ASSERT(!blockObj); + MOZ_ASSERT(!letStmt); pn2 = pn1; pn1 = nullptr; @@ -5424,27 +5403,10 @@ Parser::forStatement(YieldHandling yieldHandling) return null(); modifier = TokenStream::None; - if (blockObj) { - /* - * Now that the pn3 has been parsed, push the let scope. To hold - * the blockObj for the emitter, wrap the PNK_LEXICALSCOPE node - * created by pushLetScope around the for's initializer. This also - * serves to indicate the let-decl to the emitter. - */ - letStmt.emplace(*this, StmtType::BLOCK); - ParseNode* block = pushLetScope(blockObj, *letStmt); - if (!block) - return null(); - (*letStmt)->isForLetBlock = true; - block->pn_expr = pn1; - block->pn_pos = pn1->pn_pos; - pn1 = block; - } - if (isForDecl) { /* * pn2 is part of a declaration. Make a copy that can be passed to - * EmitAssignment. Take care to do this after pushLetScope. + * BytecodeEmitter::emitAssignment. */ pn2 = cloneLeftHandSide(pn2); if (!pn2) @@ -5466,47 +5428,13 @@ Parser::forStatement(YieldHandling yieldHandling) MOZ_ASSERT(headKind == PNK_FORHEAD); - if (blockObj) { + if (letStmt) { // Ensure here that the previously-unchecked assignment mandate for // const declarations holds. if (!checkForHeadConstInitializers(pn1)) { report(ParseError, false, nullptr, JSMSG_BAD_CONST_DECL); return null(); } - - // Desugar - // - // for (let INIT; TEST; UPDATE) STMT - // - // into - // - // let (INIT) { for (; TEST; UPDATE) STMT } - // - // to provide a block scope for INIT. - letStmt.emplace(*this, StmtType::BLOCK); - forLetImpliedBlock = pushLetScope(blockObj, *letStmt); - if (!forLetImpliedBlock) - return null(); - (*letStmt)->isForLetBlock = true; - - forLetDecl = pn1; - - // The above transformation isn't enough to implement |INIT| - // scoping, because each loop iteration must see separate bindings - // of |INIT|. We handle this by replacing the block on the scope - // chain with a new block, copying the old one's contents, each - // iteration. We supply a special PNK_FRESHENBLOCK node as the - // |let INIT| node for |for(let INIT;;)| loop heads to distinguish - // such nodes from *actual*, non-desugared use of the above syntax. - // (We don't do this for PNK_CONST nodes because the spec says no - // freshening happens -- observable with the Debugger API.) - if (pn1->isKind(PNK_CONST)) { - pn1 = nullptr; - } else { - pn1 = handler.newFreshenBlock(pn1->pn_pos); - if (!pn1) - return null(); - } } /* Parse the loop condition or null into pn2. */ @@ -5558,7 +5486,7 @@ Parser::forStatement(YieldHandling yieldHandling) if (forLetImpliedBlock) { forLetImpliedBlock->pn_expr = forLoop; forLetImpliedBlock->pn_pos = forLoop->pn_pos; - return handler.newLetBlock(forLetDecl, forLetImpliedBlock, forLoop->pn_pos); + return forLetImpliedBlock; } return forLoop; } @@ -6675,9 +6603,6 @@ template bool Parser::shouldParseLetDeclaration(bool* parseDeclOut) { - // 'let' is a reserved keyword in strict mode and we shouldn't get here. - MOZ_ASSERT(!pc->sc->strict()); - TokenKind tt; *parseDeclOut = false; @@ -6692,18 +6617,10 @@ Parser::shouldParseLetDeclaration(bool* parseDeclOut) case TOK_LC: case TOK_LB: - // A following name is always a declaration. - // // |let {| and |let [| are destructuring declarations. *parseDeclOut = true; break; - case TOK_LP: - // Only parse let blocks for 1.7 and 1.8. Do not expose deprecated let - // blocks to content. - *parseDeclOut = versionNumber() == JSVERSION_1_7 || versionNumber() == JSVERSION_1_8; - break; - default: break; } @@ -6716,6 +6633,9 @@ bool Parser::peekShouldParseLetDeclaration(bool* parseDeclOut, TokenStream::Modifier modifier) { + // 'let' is a reserved keyword in strict mode and we shouldn't get here. + MOZ_ASSERT(!pc->sc->strict()); + *parseDeclOut = false; #ifdef DEBUG @@ -6802,7 +6722,12 @@ Parser::statement(YieldHandling yieldHandling, bool canHaveDirecti case TOK_NAME: { // 'let' is a contextual keyword in sloppy node. In strict mode, it is - // always lexed as TOK_LET. + // always lexed as TOK_LET except following case: + // + // "use strict" + // let a = 1; + // + // There 'let' is lexed as TOK_NAME before parsing directive. if (tokenStream.currentName() == context->names().let) { bool parseDecl; if (!shouldParseLetDeclaration(&parseDecl)) @@ -8307,6 +8232,7 @@ Parser::comprehensionFor(GeneratorKind comprehensionKind) RootedStaticBlockObject blockObj(context, StaticBlockObject::create(context)); if (!blockObj) return null(); + // Initialize the enclosing scope manually for the call to |bind| // below, which is before the call to |pushLetScope|. blockObj->initEnclosingScopeFromParser(pc->innermostStaticScope()); diff --git a/js/src/gdb/gdb-tests.cpp b/js/src/gdb/gdb-tests.cpp index 9ab81825c7..c9f74a3719 100644 --- a/js/src/gdb/gdb-tests.cpp +++ b/js/src/gdb/gdb-tests.cpp @@ -9,6 +9,7 @@ #include "gdb-tests.h" #include "jsapi.h" #include "jsfriendapi.h" +#include "js/Initialization.h" using namespace JS; diff --git a/js/src/jit-test/tests/asm.js/sta-transition.js b/js/src/jit-test/tests/asm.js/sta-transition.js new file mode 100644 index 0000000000..46c15368de --- /dev/null +++ b/js/src/jit-test/tests/asm.js/sta-transition.js @@ -0,0 +1,116 @@ +// Transitional test cases, useful while Odin accepts both +// "Int32Array" and "SharedInt32Array" to construct a view onto shared +// memory but the former only when an atomic operation is referenced, +// as per spec. Eventually it will stop accepting "SharedInt32Array", +// because that name is going away. +// +// These should not run with --no-asmjs. + +////////////////////////////////////////////////////////////////////// +// +// Int8Array can be used on SharedArrayBuffer, if atomics are present + +function m1(stdlib, ffi, heap) { + "use asm"; + + var i8 = new stdlib.Int8Array(heap); + var add = stdlib.Atomics.add; + + function f() { + add(i8, 0, 1); + return 37; + } + + return { f:f } +} + +assertEq(isAsmJSModule(m1), true); + +var { f } = m1(this, {}, new SharedArrayBuffer(65536)); +assertEq(f(), 37); + +////////////////////////////////////////////////////////////////////// +// +// SharedInt8Array can still be used on SharedArrayBuffer. +// (SharedInt8Array will eventually disappear, and this +// test case with it.) + +function m2(stdlib, ffi, heap) { + "use asm"; + + var i8 = new stdlib.SharedInt8Array(heap); + var add = stdlib.Atomics.add; + + function g() { + add(i8, 0, 1); + return 42; + } + + return { g:g } +} + +assertEq(isAsmJSModule(m2), true); + +var { g } = m2(this, {}, new SharedArrayBuffer(65536)); +assertEq(g(), 42); + +////////////////////////////////////////////////////////////////////// +// +// SharedInt8Array still cannot be used on ArrayBuffer, even without +// atomics present. +// (SharedInt8Array will eventually disappear, and this +// test case with it.) + +function m3(stdlib, ffi, heap) { + "use asm"; + + var i8 = new stdlib.SharedInt8Array(heap); + + function h() { + return i8[0]|0; + } + + return { h:h } +} + +// Running the shell with -w you should see an error here. + +assertEq(isAsmJSModule(m3), true); +try { + var wasThrown = false; + m3(this, {}, new ArrayBuffer(65536)); +} +catch (e) { + wasThrown = true; +} +assertEq(wasThrown, true); + +////////////////////////////////////////////////////////////////////// +// +// Int8Array cannot be used on SharedArrayBuffer if atomics are not imported. +// One argument for the restriction is that there are some optimizations +// that are legal if the memory is known not to be shared that are illegal +// when it is shared. + +function m4(stdlib, ffi, heap) { + "use asm"; + + var i8 = new stdlib.Int8Array(heap); + + function i() { + return i8[0]|0; + } + + return { i:i } +} + +assertEq(isAsmJSModule(m4), true); + +// An error is not actually thrown because the link failure drops us +// back to JS execution and then the Int8Array constructor will copy data +// from the SharedArrayBuffer. +// +// Running the shell with -w you should see an error here. + +var { i } = m4(this, {}, new SharedArrayBuffer(65536)); +assertEq(isAsmJSFunction(i), false); diff --git a/js/src/jit-test/tests/asm.js/testAtomics.js b/js/src/jit-test/tests/asm.js/testAtomics.js index 4f89a3bbc5..931e4c0082 100644 --- a/js/src/jit-test/tests/asm.js/testAtomics.js +++ b/js/src/jit-test/tests/asm.js/testAtomics.js @@ -1,5 +1,5 @@ // |jit-test| test-also-noasmjs -if (!this.SharedArrayBuffer || !this.SharedInt32Array || !this.Atomics) +if (!this.SharedArrayBuffer || !this.Atomics) quit(); // The code duplication below is very far from elegant but provides @@ -8,6 +8,27 @@ if (!this.SharedArrayBuffer || !this.SharedInt32Array || !this.Atomics) load(libdir + "asm.js"); load(libdir + "asserts.js"); +// This hack allows the test cases to run with --no-asmjs: the field values +// are basically ignored in asm.js mode, and the correct (Firefox-specific) +// field values are used in non-asm.js mode. If run in a non-Firefox +// browser that does not have the parallel type hierarchy this should also +// work. +// +// This hack will be removed when the parallel type hierarchy is removed +// from Firefox, bug 1176214. + +const atomicStdlib = { + Atomics: Atomics, + Int8Array: this.SharedInt8Array ? SharedInt8Array : Int8Array, + Uint8Array: this.SharedUint8Array ? SharedUint8Array : Uint8Array, + Int16Array: this.SharedInt16Array ? SharedInt16Array : Int16Array, + Uint16Array: this.SharedUint16Array ? SharedUint16Array : Uint16Array, + Int32Array: this.SharedInt32Array ? SharedInt32Array : Int32Array, + Uint32Array: this.SharedUint32Array ? SharedUint32Array : Uint32Array, + Float32Array: this.SharedFloat32Array ? SharedFloat32Array : Float32Array, + Float64Array: this.SharedFloat64Array ? SharedFloat64Array : Float64Array +}; + var loadModule_int32_code = USE_ASM + ` var atomic_fence = stdlib.Atomics.fence; @@ -21,7 +42,7 @@ var loadModule_int32_code = var atomic_or = stdlib.Atomics.or; var atomic_xor = stdlib.Atomics.xor; - var i32a = new stdlib.SharedInt32Array(heap); + var i32a = new stdlib.Int32Array(heap); function do_fence() { atomic_fence(); @@ -233,7 +254,7 @@ var loadModule_int32 = asmCompile('stdlib', 'foreign', 'heap', loadModule_int32_ function test_int32(heap) { var i32a = new SharedInt32Array(heap); - var i32m = asmLink(loadModule_int32, this, {}, heap); + var i32m = asmLink(loadModule_int32, atomicStdlib, {}, heap); var size = SharedInt32Array.BYTES_PER_ELEMENT; @@ -338,7 +359,7 @@ var loadModule_uint32_code = var atomic_or = stdlib.Atomics.or; var atomic_xor = stdlib.Atomics.xor; - var i32a = new stdlib.SharedUint32Array(heap); + var i32a = new stdlib.Uint32Array(heap); // Load element 0 function do_load() { @@ -516,7 +537,7 @@ var loadModule_uint32 = asmCompile('stdlib', 'foreign', 'heap', loadModule_uint3 function test_uint32(heap) { var i32a = new SharedUint32Array(heap); - var i32m = loadModule_uint32(this, {}, heap); + var i32m = loadModule_uint32(atomicStdlib, {}, heap); var size = SharedUint32Array.BYTES_PER_ELEMENT; @@ -619,7 +640,7 @@ var loadModule_int16_code = var atomic_or = stdlib.Atomics.or; var atomic_xor = stdlib.Atomics.xor; - var i16a = new stdlib.SharedInt16Array(heap); + var i16a = new stdlib.Int16Array(heap); function do_fence() { atomic_fence(); @@ -802,7 +823,7 @@ var loadModule_int16 = asmCompile('stdlib', 'foreign', 'heap', loadModule_int16_ function test_int16(heap) { var i16a = new SharedInt16Array(heap); - var i16m = loadModule_int16(this, {}, heap); + var i16m = loadModule_int16(atomicStdlib, {}, heap); var size = SharedInt16Array.BYTES_PER_ELEMENT; @@ -914,7 +935,7 @@ var loadModule_uint16_code = var atomic_or = stdlib.Atomics.or; var atomic_xor = stdlib.Atomics.xor; - var i16a = new stdlib.SharedUint16Array(heap); + var i16a = new stdlib.Uint16Array(heap); // Load element 0 function do_load() { @@ -1092,7 +1113,7 @@ var loadModule_uint16 = asmCompile('stdlib', 'foreign', 'heap', loadModule_uint1 function test_uint16(heap) { var i16a = new SharedUint16Array(heap); - var i16m = loadModule_uint16(this, {}, heap); + var i16m = loadModule_uint16(atomicStdlib, {}, heap); var size = SharedUint16Array.BYTES_PER_ELEMENT; @@ -1202,7 +1223,7 @@ var loadModule_int8_code = var atomic_or = stdlib.Atomics.or; var atomic_xor = stdlib.Atomics.xor; - var i8a = new stdlib.SharedInt8Array(heap); + var i8a = new stdlib.Int8Array(heap); // Load element 0 function do_load() { @@ -1380,7 +1401,7 @@ var loadModule_int8 = asmCompile('stdlib', 'foreign', 'heap', loadModule_int8_co function test_int8(heap) { var i8a = new SharedInt8Array(heap); - var i8m = loadModule_int8(this, {}, heap); + var i8m = loadModule_int8(atomicStdlib, {}, heap); for ( var i=0 ; i < i8a.length ; i++ ) i8a[i] = 0; @@ -1483,7 +1504,7 @@ var loadModule_uint8_code = var atomic_or = stdlib.Atomics.or; var atomic_xor = stdlib.Atomics.xor; - var i8a = new stdlib.SharedUint8Array(heap); + var i8a = new stdlib.Uint8Array(heap); // Load element 0 function do_load() { @@ -1661,7 +1682,7 @@ var loadModule_uint8 = asmCompile('stdlib', 'foreign', 'heap', loadModule_uint8_ function test_uint8(heap) { var i8a = new SharedUint8Array(heap); - var i8m = loadModule_uint8(this, {}, heap); + var i8m = loadModule_uint8(atomicStdlib, {}, heap); for ( var i=0 ; i < i8a.length ; i++ ) i8a[i] = 0; @@ -1816,7 +1837,7 @@ var loadModule_misc_code = var loadModule_misc = asmCompile('stdlib', 'foreign', 'heap', loadModule_misc_code); function test_misc(heap) { - var misc = loadModule_misc(this, {}, heap); + var misc = loadModule_misc(atomicStdlib, {}, heap); assertEq(misc.ilf1(), 1); assertEq(misc.ilf2(), 1); @@ -1848,7 +1869,7 @@ setARMHwCapFlags('vfp'); asmCompile('stdlib', 'ffi', 'heap', USE_ASM + ` var atomic_exchange = stdlib.Atomics.exchange; - var i8a = new stdlib.SharedInt8Array(heap); + var i8a = new stdlib.Int8Array(heap); function do_xchg() { var v = 0; diff --git a/js/src/jit-test/tests/baseline/bug1216140.js b/js/src/jit-test/tests/baseline/bug1216140.js new file mode 100644 index 0000000000..bdce8ab164 --- /dev/null +++ b/js/src/jit-test/tests/baseline/bug1216140.js @@ -0,0 +1,6 @@ +function newFunc(x) Function(x)() +newFunc(` + var BUGNUMBER = 8[ anonymous = true ]--; + () => BUGNUMBER; +`); + diff --git a/js/src/jit-test/tests/basic/bug646968-3.js b/js/src/jit-test/tests/basic/bug646968-3.js index ee4ed9e850..a02cb3f682 100644 --- a/js/src/jit-test/tests/basic/bug646968-3.js +++ b/js/src/jit-test/tests/basic/bug646968-3.js @@ -1,16 +1,16 @@ -var s, x = 0; +var s, v = "NOPE"; s = ''; -for (let x = x; x < 3; x++) +for (let v = 0, x = v; x < 3; x++) s += x; assertEq(s, '012'); s = ''; -for (let x = eval('x'); x < 3; x++) +for (let v = 0, x = eval('v'); x < 3; x++) s += x; assertEq(s, '012'); s = '' -for (let x = function () { with ({}) return x; }(); x < 3; x++) +for (let v = 0, x = function () { with ({}) return v; }(); x < 3; x++) s += x; assertEq(s, '012'); diff --git a/js/src/jit-test/tests/basic/bug646968-4.js b/js/src/jit-test/tests/basic/bug646968-4.js index ffb7ffed08..4d0e2c15fb 100644 --- a/js/src/jit-test/tests/basic/bug646968-4.js +++ b/js/src/jit-test/tests/basic/bug646968-4.js @@ -1,4 +1,8 @@ -var s = '', x = {a: 1, b: 2, c: 3}; +// Scoping: `x` in the head of a `for (let x...)` loop refers to the loop variable. + +// For now, this means it evaluates to undefined. It ought to throw a +// ReferenceError instead, but the TDZ isn't implemented here (bug 1069480). +var s = "", x = {a: 1, b: 2, c: 3}; for (let x in eval('x')) s += x; -assertEq(s, 'abc'); +assertEq(s, ""); diff --git a/js/src/jit-test/tests/basic/bug646968-6.js b/js/src/jit-test/tests/basic/bug646968-6.js new file mode 100644 index 0000000000..11b1fa1c18 --- /dev/null +++ b/js/src/jit-test/tests/basic/bug646968-6.js @@ -0,0 +1,16 @@ +// In `for (let x = EXPR; ;)`, if `x` appears within EXPR, it refers to the +// loop variable. Actually doing this is typically a TDZ error. + +load(libdir + "asserts.js"); + +assertThrowsInstanceOf(() => { + for (let x = x; null.foo; null.foo++) {} +}, ReferenceError); + +assertThrowsInstanceOf(() => { + for (let x = eval('x'); null.foo; null.foo++) {} +}, ReferenceError); + +assertThrowsInstanceOf(() => { + for (let x = function () { with ({}) return x; }(); null.foo; null.foo++) {} +}, ReferenceError); diff --git a/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js b/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js deleted file mode 100644 index d1c0764dc5..0000000000 --- a/js/src/jit-test/tests/basic/letLegacyForOfOrInScope.js +++ /dev/null @@ -1,5 +0,0 @@ -var x = "foobar"; -{ for (let x of x) assertEq(x.length, 1, "second x refers to outer x"); } - -var x = "foobar"; -{ for (let x in x) assertEq(x.length, 1, "second x refers to outer x"); } diff --git a/js/src/jit-test/tests/basic/testLet.js b/js/src/jit-test/tests/basic/testLet.js index a79c8a521e..da00dec2dd 100644 --- a/js/src/jit-test/tests/basic/testLet.js +++ b/js/src/jit-test/tests/basic/testLet.js @@ -3,7 +3,8 @@ var otherGlobal = newGlobal(); function test(str, arg, result) { arg = arg || 'ponies'; - result = result || 'ponies'; + if (arguments.length < 3) + result = 'ponies'; var fun = new Function('x', str); @@ -136,11 +137,7 @@ test('for (let y = 1;; ++y) {return x;}'); test('for (let y = 1; ++y;) {return x;}'); test('for (let [[a, [b, c]]] = [[x, []]];;) {return a;}'); test('var sum = 0;for (let y = x; y < 4; ++y) {sum += y;}return sum;', 1, 6); -test('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;', 1, 6); -test('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;', 1, 1); -test('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;', 1, 6); -test('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); -test('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); +test('var sum = 0;for (let x = 1; eval("x") < 4; ++x) {sum += eval("x");}return sum;', 1, 6); test('for (var y = 1;;) {return x;}'); test('for (var y = 1;; ++y) {return x;}'); test('for (var y = 1; ++y;) {return x;}'); @@ -151,8 +148,6 @@ test('var sum = 0;for (var X = x; X < 4; ++X) {sum += X;}return x;', 1, 1); test('var sum = 0;for (var X = eval("x"); X < 4; ++X) {sum += X;}return sum;', 1, 6); test('var sum = 0;for (var X = x; eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6); test('var sum = 0;for (var X = eval("x"); eval("X") < 4; ++X) {sum += eval("X");}return sum;', 1, 6); -test('try {for (let x = eval("throw x");;) {}} catch (e) {return e;}'); -test('try {for (let x = x + "s"; eval("throw x");) {}} catch (e) {return e;}', 'ponie'); test('for (let y = x;;) {let x;return y;}'); test('for (let y = x;;) {let y;return x;}'); test('for (let y;;) {let y;return x;}'); @@ -170,28 +165,31 @@ test('for (let i in x) {return x;}'); test('for (let i in x) {let y;return x;}'); test('for each (let [a, b] in x) {let y;return x;}'); test('for (let i in x) {let i = x;return i;}'); -test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']]); -test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']]); test('var s = "";for (let a in x) {for (let b in x) {s += a + b;}}return s;', [1,2], '00011011'); test('var res = "";for (let i in x) {res += x[i];}return res;'); test('var res = "";for (var i in x) {res += x[i];}return res;'); -test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}'); -test('for (let x in eval("x")) {return x;}', {ponies:true}); -test('for (let x in x) {return eval("x");}', {ponies:true}); -test('for (let x in eval("x")) {return eval("x");}', {ponies:true}); +isParseError('for ((let (x = {y: true}) x).y in eval("x")) {return eval("x");}'); test('for (let i in x) {break;}return x;'); test('for (let i in x) {break;}return eval("x");'); -test('for (let x in x) {break;}return x;'); -test('for (let x in x) {break;}return eval("x");'); test('a:for (let i in x) {for (let j in x) {break a;}}return x;'); test('a:for (let i in x) {for (let j in x) {break a;}}return eval("x");'); test('var j;for (let i in x) {j = i;break;}return j;', {ponies:true}); -test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}'); -test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies']); isParseError('for (let [x, x] in o) {}'); isParseError('for (let [x, y, x] in o) {}'); isParseError('for (let [x, [y, [x]]] in o) {}'); +// for(let ... in ...) scoping bugs (bug 1069480) +test('for each (let [x, y] in x) {return x + y;}', [['ponies', '']], undefined); +test('for each (let [{0: x, 1: y}, z] in x) {return x + y + z;}', [[['po','nies'], '']], undefined); +test('for (let x in eval("x")) {return x;}', {ponies:true}, undefined); +test('for (let x in x) {return eval("x");}', {ponies:true}, undefined); +test('for (let x in eval("x")) {return eval("x");}', {ponies:true}, undefined); +test('for (let x in x) {break;}return x;'); +test('for (let x in x) {break;}return eval("x");'); +test('try {for (let x in eval("throw x")) {}} catch (e) {return e;}', undefined, undefined); +test('try {for each (let x in x) {eval("throw x");}} catch (e) {return e;}', ['ponies'], undefined); +test('for each (let {x: y, y: x} in [{x: x, y: x}]) {return y;}', undefined, undefined); + // genexps test('return (i for (i in x)).next();', {ponies:true}); test('return (eval("i") for (i in x)).next();', {ponies:true}); @@ -222,6 +220,13 @@ isReferenceError('let {x} = {x:x};'); isReferenceError('switch (x) {case 3:let x;break;default:if (x === undefined) {return "ponies";}}'); isReferenceError('let x = function() {} ? x() : function() {}'); isReferenceError('(function() { let x = (function() { return x }()); }())'); +isReferenceError('var sum = 0;for (let x = x, y = 10; x < 4; ++x) {sum += x;}return sum;'); +isReferenceError('var sum = 0;for (let x = x; x < 4; ++x) {sum += x;}return x;'); +isReferenceError('var sum = 0;for (let x = eval("x"); x < 4; ++x) {sum += x;}return sum;'); +isReferenceError('var sum = 0;for (let x = x; eval("x") < 4; ++x) {sum += eval("x");}return sum;'); +isReferenceError('var sum = 0;for (let x = eval("x"); eval("x") < 4; ++x) {sum += eval("x");}return sum;'); +isReferenceError('for (let x = eval("throw x");;) {}'); +isReferenceError('for (let x = x + "s"; eval("throw x");) {}'); // redecl with function statements isParseError('let a; function a() {}'); diff --git a/js/src/jit-test/tests/modules/module-declaration-instantiation.js b/js/src/jit-test/tests/modules/module-declaration-instantiation.js index 49e3e485df..21f2ed9b20 100644 --- a/js/src/jit-test/tests/modules/module-declaration-instantiation.js +++ b/js/src/jit-test/tests/modules/module-declaration-instantiation.js @@ -1,7 +1,7 @@ // Exercise ModuleDeclarationInstantiation() operation. function testModuleEnvironment(module, expected) { - var actual = getModuleEnvironmentNames(module); + var actual = getModuleEnvironmentNames(module).sort(); assertEq(actual.length, expected.length); for (var i = 0; i < actual.length; i++) { assertEq(actual[i], expected[i]); @@ -28,3 +28,17 @@ b.declarationInstantiation(); testModuleEnvironment(a, ['x']); testModuleEnvironment(b, ['y']); + +// Function bindings are initialized as well as instantiated. +let c = parseModule(`function a(x) { return x; } + function b(x) { return x + 1; } + function c(x) { return x + 2; } + function d(x) { return x + 3; }`); +const names = ['a', 'b', 'c', 'd']; +testModuleEnvironment(c, names); +names.forEach((n) => assertEq(typeof getModuleEnvironmentValue(c, n), "undefined")); +c.declarationInstantiation(); +for (let i = 0; i < names.length; i++) { + let f = getModuleEnvironmentValue(c, names[i]); + assertEq(f(21), 21 + i); +} diff --git a/js/src/jit-test/tests/parser/let-after-directive.js b/js/src/jit-test/tests/parser/let-after-directive.js new file mode 100644 index 0000000000..58864ab341 --- /dev/null +++ b/js/src/jit-test/tests/parser/let-after-directive.js @@ -0,0 +1,6 @@ +// 'let' after "use strict" directive without semicolon is lexed as TOK_NAME +// before parsing the directive. 'let' with TOK_NAME should be handled +// correctly in strict mode. + +"use strict" +let a = 1; diff --git a/js/src/jit/AtomicOperations.h b/js/src/jit/AtomicOperations.h index 000310b1fa..2ab32f7b30 100644 --- a/js/src/jit/AtomicOperations.h +++ b/js/src/jit/AtomicOperations.h @@ -298,7 +298,7 @@ AtomicOperations::isLockfree(int32_t size) # include "jit/arm/AtomicOperations-arm.h" #elif defined(JS_CODEGEN_ARM64) # include "jit/arm64/AtomicOperations-arm64.h" -#elif defined(JS_CODEGEN_MIPS32) +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) # include "jit/mips-shared/AtomicOperations-mips-shared.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/AtomicOperations-none.h" diff --git a/js/src/jit/BaselineBailouts.cpp b/js/src/jit/BaselineBailouts.cpp index fa106a2c66..9e3c4e47b2 100644 --- a/js/src/jit/BaselineBailouts.cpp +++ b/js/src/jit/BaselineBailouts.cpp @@ -15,6 +15,7 @@ #include "jit/CompileInfo.h" #include "jit/JitSpewer.h" #include "jit/mips32/Simulator-mips32.h" +#include "jit/mips64/Simulator-mips64.h" #include "jit/Recover.h" #include "jit/RematerializedFrame.h" @@ -374,7 +375,8 @@ struct BaselineStackBuilder priorOffset -= sizeof(void*); return virtualPointerAtStackOffset(priorOffset); #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ - defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_MIPS32) + defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) || \ + defined(JS_CODEGEN_X64) // On X64, ARM, ARM64, and MIPS, the frame pointer save location depends on // the caller of the rectifier frame. BufferPointer priorFrame = diff --git a/js/src/jit/BaselineCompiler.cpp b/js/src/jit/BaselineCompiler.cpp index 3237adb265..d5212b05ff 100644 --- a/js/src/jit/BaselineCompiler.cpp +++ b/js/src/jit/BaselineCompiler.cpp @@ -484,7 +484,7 @@ BaselineCompiler::emitOutOfLinePostBarrierSlot() // On ARM, save the link register before calling. It contains the return // address. The |masm.ret()| later will pop this into |pc| to return. masm.push(lr); -#elif defined(JS_CODEGEN_MIPS32) +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) masm.push(ra); #endif masm.pushValue(R0); @@ -1964,6 +1964,12 @@ BaselineCompiler::emit_JSOP_INITELEM() return true; } +bool +BaselineCompiler::emit_JSOP_INITHIDDENELEM() +{ + return emit_JSOP_INITELEM(); +} + typedef bool (*MutateProtoFn)(JSContext* cx, HandlePlainObject obj, HandleValue newProto); static const VMFunction MutateProtoInfo = FunctionInfo(MutatePrototype); @@ -2436,7 +2442,10 @@ BaselineCompiler::emit_JSOP_BINDNAME() { frame.syncStack(0); - masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); + if (*pc == JSOP_BINDGNAME && !script->hasNonSyntacticScope()) + masm.movePtr(ImmGCPtr(&script->global().lexicalScope()), R0.scratchReg()); + else + masm.loadPtr(frame.addressOfScopeChain(), R0.scratchReg()); // Call IC. ICBindName_Fallback::Compiler stubCompiler(cx); @@ -2566,7 +2575,9 @@ bool BaselineCompiler::emitInitPropGetterSetter() { MOZ_ASSERT(JSOp(*pc) == JSOP_INITPROP_GETTER || - JSOp(*pc) == JSOP_INITPROP_SETTER); + JSOp(*pc) == JSOP_INITHIDDENPROP_GETTER || + JSOp(*pc) == JSOP_INITPROP_SETTER || + JSOp(*pc) == JSOP_INITHIDDENPROP_SETTER); // Keep values on the stack for the decompiler. frame.syncStack(0); @@ -2594,12 +2605,24 @@ BaselineCompiler::emit_JSOP_INITPROP_GETTER() return emitInitPropGetterSetter(); } +bool +BaselineCompiler::emit_JSOP_INITHIDDENPROP_GETTER() +{ + return emitInitPropGetterSetter(); +} + bool BaselineCompiler::emit_JSOP_INITPROP_SETTER() { return emitInitPropGetterSetter(); } +bool +BaselineCompiler::emit_JSOP_INITHIDDENPROP_SETTER() +{ + return emitInitPropGetterSetter(); +} + typedef bool (*InitElemGetterSetterFn)(JSContext*, jsbytecode*, HandleObject, HandleValue, HandleObject); static const VMFunction InitElemGetterSetterInfo = @@ -2609,7 +2632,9 @@ bool BaselineCompiler::emitInitElemGetterSetter() { MOZ_ASSERT(JSOp(*pc) == JSOP_INITELEM_GETTER || - JSOp(*pc) == JSOP_INITELEM_SETTER); + JSOp(*pc) == JSOP_INITHIDDENELEM_GETTER || + JSOp(*pc) == JSOP_INITELEM_SETTER || + JSOp(*pc) == JSOP_INITHIDDENELEM_SETTER); // Load index and value in R0 and R1, but keep values on the stack for the // decompiler. @@ -2638,12 +2663,24 @@ BaselineCompiler::emit_JSOP_INITELEM_GETTER() return emitInitElemGetterSetter(); } +bool +BaselineCompiler::emit_JSOP_INITHIDDENELEM_GETTER() +{ + return emitInitElemGetterSetter(); +} + bool BaselineCompiler::emit_JSOP_INITELEM_SETTER() { return emitInitElemGetterSetter(); } +bool +BaselineCompiler::emit_JSOP_INITHIDDENELEM_SETTER() +{ + return emitInitElemGetterSetter(); +} + bool BaselineCompiler::emit_JSOP_INITELEM_INC() { diff --git a/js/src/jit/BaselineCompiler.h b/js/src/jit/BaselineCompiler.h index b172152999..ff0dd3e32f 100644 --- a/js/src/jit/BaselineCompiler.h +++ b/js/src/jit/BaselineCompiler.h @@ -18,6 +18,8 @@ # include "jit/arm64/BaselineCompiler-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/BaselineCompiler-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/BaselineCompiler-mips64.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/BaselineCompiler-none.h" #else @@ -206,7 +208,12 @@ namespace jit { _(JSOP_SUPERCALL) \ _(JSOP_SPREADSUPERCALL) \ _(JSOP_THROWSETCONST) \ - _(JSOP_THROWSETALIASEDCONST) + _(JSOP_THROWSETALIASEDCONST) \ + _(JSOP_INITHIDDENPROP_GETTER) \ + _(JSOP_INITHIDDENPROP_SETTER) \ + _(JSOP_INITHIDDENELEM) \ + _(JSOP_INITHIDDENELEM_GETTER) \ + _(JSOP_INITHIDDENELEM_SETTER) class BaselineCompiler : public BaselineCompilerSpecific { diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 09f721ef0a..d89210b32a 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -3494,6 +3494,7 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_ MOZ_ASSERT(op == JSOP_SETELEM || op == JSOP_STRICTSETELEM || op == JSOP_INITELEM || + op == JSOP_INITHIDDENELEM || op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC); @@ -3511,8 +3512,8 @@ DoSetElemFallback(JSContext* cx, BaselineFrame* frame, ICSetElem_Fallback* stub_ oldInitLength = GetAnyBoxedOrUnboxedInitializedLength(obj); } - if (op == JSOP_INITELEM) { - if (!InitElemOperation(cx, obj, index, rhs)) + if (op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM) { + if (!InitElemOperation(cx, pc, obj, index, rhs)) return false; } else if (op == JSOP_INITELEM_ARRAY) { MOZ_ASSERT(uint32_t(index.toInt32()) <= INT32_MAX, @@ -3863,8 +3864,9 @@ ICSetElem_DenseOrUnboxedArray::Compiler::generateStubCode(MacroAssembler& masm) EmitReturnFromIC(masm); if (failurePopR0.used()) { + // Failure case: restore the value of R0 masm.bind(&failurePopR0); - masm.Pop(R0); + masm.popValue(R0); } // Failure case - jump to next stub @@ -4099,8 +4101,9 @@ ICSetElemDenseOrUnboxedArrayAddCompiler::generateStubCode(MacroAssembler& masm) EmitReturnFromIC(masm); if (failurePopR0.used()) { + // Failure case: restore the value of R0 masm.bind(&failurePopR0); - masm.Pop(R0); + masm.popValue(R0); masm.jump(&failure); } diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index 910925c807..8ef1ebcff0 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -1166,6 +1166,11 @@ MarkActiveBaselineScripts(JSRuntime* rt, const JitActivationIterator& activation case JitFrame_BaselineJS: iter.script()->baselineScript()->setActive(); break; + case JitFrame_LazyLink: { + LazyLinkExitFrameLayout* ll = iter.exitFrame()->as(); + ScriptFromCalleeToken(ll->jsFrame()->calleeToken())->baselineScript()->setActive(); + break; + } case JitFrame_Bailout: case JitFrame_IonJS: { // Keep the baseline script around, since bailouts from the ion diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 3161e39e7e..4cc3d6257f 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -4842,7 +4842,7 @@ CodeGenerator::visitNewStringObject(LNewStringObject* lir) masm.bind(ool->rejoin()); } -typedef bool(*InitElemFn)(JSContext* cx, HandleObject obj, +typedef bool(*InitElemFn)(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleValue id, HandleValue value); static const VMFunction InitElemInfo = FunctionInfo(InitElemOperation); @@ -4855,6 +4855,7 @@ CodeGenerator::visitInitElem(LInitElem* lir) pushArg(ToValue(lir, LInitElem::ValueIndex)); pushArg(ToValue(lir, LInitElem::IdIndex)); pushArg(objReg); + pushArg(ImmPtr(lir->mir()->resumePoint()->pc())); callVM(InitElemInfo, lir); } @@ -5277,7 +5278,7 @@ CodeGenerator::visitAbsI(LAbsI* ins) masm.branchTest32(Assembler::NotSigned, input, input, &positive); masm.neg32(input); LSnapshot* snapshot = ins->snapshot(); -#ifdef JS_CODEGEN_MIPS32 +#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) if (snapshot) bailoutCmp32(Assembler::Equal, input, Imm32(INT32_MIN), snapshot); #else @@ -6945,7 +6946,7 @@ CodeGenerator::visitOutOfLineStoreElementHole(OutOfLineStoreElementHole* ool) // If index > initializedLength, call a stub. Note that this relies on the // condition flags sticking from the incoming branch. Label callStub; -#ifdef JS_CODEGEN_MIPS32 +#if defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) // Had to reimplement for MIPS because there are no flags. if (unboxedType == JSVAL_TYPE_MAGIC) { Address initLength(elements, ObjectElements::offsetOfInitializedLength()); @@ -7798,7 +7799,7 @@ CodeGenerator::generateAsmJS(AsmJSFunctionLabels* labels) if (!generateOutOfLineCode()) return false; - masm.bind(&labels->end); + masm.bind(&labels->endAfterOOL); // The only remaining work needed to compile this function is to patch the // switch-statement jump tables (the entries of the table need the absolute @@ -9290,49 +9291,6 @@ CodeGenerator::visitAtomicIsLockFree(LAtomicIsLockFree* lir) masm.bind(&Ldone); } -void -CodeGenerator::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) -{ - Register elements = ToRegister(lir->elements()); - AnyRegister output = ToAnyRegister(lir->output()); - Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); - - Register oldval = ToRegister(lir->oldval()); - Register newval = ToRegister(lir->newval()); - - Scalar::Type arrayType = lir->mir()->arrayType(); - int width = Scalar::byteSize(arrayType); - - if (lir->index()->isConstant()) { - Address dest(elements, ToInt32(lir->index()) * width); - masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); - } else { - BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); - masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); - } -} - -void -CodeGenerator::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) -{ - Register elements = ToRegister(lir->elements()); - AnyRegister output = ToAnyRegister(lir->output()); - Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); - - Register value = ToRegister(lir->value()); - - Scalar::Type arrayType = lir->mir()->arrayType(); - int width = Scalar::byteSize(arrayType); - - if (lir->index()->isConstant()) { - Address dest(elements, ToInt32(lir->index()) * width); - masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); - } else { - BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); - masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); - } -} - void CodeGenerator::visitClampIToUint8(LClampIToUint8* lir) { diff --git a/js/src/jit/CodeGenerator.h b/js/src/jit/CodeGenerator.h index e1efd640c6..a76f8342ac 100644 --- a/js/src/jit/CodeGenerator.h +++ b/js/src/jit/CodeGenerator.h @@ -22,6 +22,8 @@ # include "jit/arm64/CodeGenerator-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/CodeGenerator-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/CodeGenerator-mips64.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/CodeGenerator-none.h" #else @@ -286,8 +288,6 @@ class CodeGenerator : public CodeGeneratorSpecific void visitStoreUnboxedScalar(LStoreUnboxedScalar* lir); void visitStoreTypedArrayElementHole(LStoreTypedArrayElementHole* lir); void visitAtomicIsLockFree(LAtomicIsLockFree* lir); - void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); - void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitClampIToUint8(LClampIToUint8* lir); void visitClampDToUint8(LClampDToUint8* lir); void visitClampVToUint8(LClampVToUint8* lir); diff --git a/js/src/jit/ExecutableAllocator.h b/js/src/jit/ExecutableAllocator.h index c0aff7d8c9..5277ed46fb 100644 --- a/js/src/jit/ExecutableAllocator.h +++ b/js/src/jit/ExecutableAllocator.h @@ -35,6 +35,7 @@ #include "jit/arm/Simulator-arm.h" #include "jit/mips32/Simulator-mips32.h" +#include "jit/mips64/Simulator-mips64.h" #include "js/GCAPI.h" #include "js/HashTable.h" #include "js/Vector.h" @@ -55,7 +56,9 @@ extern "C" void sync_instruction_memory(caddr_t v, u_int len); #endif #endif -#if defined(JS_CODEGEN_MIPS32) && defined(__linux__) && !defined(JS_SIMULATOR_MIPS32) +#if defined(__linux__) && \ + (defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)) && \ + (!defined(JS_SIMULATOR_MIPS32) && !defined(JS_SIMULATOR_MIPS64)) #include #endif @@ -394,12 +397,12 @@ class ExecutableAllocator static void cacheFlush(void*, size_t) { } -#elif defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) +#elif defined(JS_SIMULATOR_ARM) || defined(JS_SIMULATOR_MIPS32) || defined(JS_SIMULATOR_MIPS64) static void cacheFlush(void* code, size_t size) { js::jit::Simulator::FlushICache(code, size); } -#elif defined(JS_CODEGEN_MIPS32) +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) static void cacheFlush(void* code, size_t size) { #if defined(__GNUC__) diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index e300353c5e..bbe078396e 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -3210,7 +3210,7 @@ PerThreadData::setAutoFlushICache(AutoFlushICache* afc) void AutoFlushICache::setRange(uintptr_t start, size_t len) { -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) AutoFlushICache* afc = TlsPerThreadData.get()->PerThreadData::autoFlushICache(); MOZ_ASSERT(afc); MOZ_ASSERT(!afc->start_); @@ -3243,13 +3243,13 @@ AutoFlushICache::setRange(uintptr_t start, size_t len) void AutoFlushICache::flush(uintptr_t start, size_t len) { -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) PerThreadData* pt = TlsPerThreadData.get(); AutoFlushICache* afc = pt ? pt->PerThreadData::autoFlushICache() : nullptr; if (!afc) { JitSpewCont(JitSpew_CacheFlush, "#"); ExecutableAllocator::cacheFlush((void*)start, len); - MOZ_ASSERT(len <= 16); + MOZ_ASSERT(len <= 32); return; } @@ -3270,7 +3270,7 @@ AutoFlushICache::flush(uintptr_t start, size_t len) void AutoFlushICache::setInhibit() { -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) AutoFlushICache* afc = TlsPerThreadData.get()->PerThreadData::autoFlushICache(); MOZ_ASSERT(afc); MOZ_ASSERT(afc->start_); @@ -3297,14 +3297,14 @@ AutoFlushICache::setInhibit() // the respective AutoFlushICache dynamic context. // AutoFlushICache::AutoFlushICache(const char* nonce, bool inhibit) -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) : start_(0), stop_(0), name_(nonce), inhibit_(inhibit) #endif { -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) PerThreadData* pt = TlsPerThreadData.get(); AutoFlushICache* afc = pt->PerThreadData::autoFlushICache(); if (afc) @@ -3319,7 +3319,7 @@ AutoFlushICache::AutoFlushICache(const char* nonce, bool inhibit) AutoFlushICache::~AutoFlushICache() { -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) PerThreadData* pt = TlsPerThreadData.get(); MOZ_ASSERT(pt->PerThreadData::autoFlushICache() == this); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 6c8dfc83cb..5cf44b879a 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -1843,6 +1843,7 @@ IonBuilder::inspectOpcode(JSOp op) return jsop_newobject(); case JSOP_INITELEM: + case JSOP_INITHIDDENELEM: return jsop_initelem(); case JSOP_INITELEM_ARRAY: @@ -1862,13 +1863,17 @@ IonBuilder::inspectOpcode(JSOp op) } case JSOP_INITPROP_GETTER: - case JSOP_INITPROP_SETTER: { + case JSOP_INITHIDDENPROP_GETTER: + case JSOP_INITPROP_SETTER: + case JSOP_INITHIDDENPROP_SETTER: { PropertyName* name = info().getAtom(pc)->asPropertyName(); return jsop_initprop_getter_setter(name); } case JSOP_INITELEM_GETTER: + case JSOP_INITHIDDENELEM_GETTER: case JSOP_INITELEM_SETTER: + case JSOP_INITHIDDENELEM_SETTER: return jsop_initelem_getter_setter(); case JSOP_FUNCALL: diff --git a/js/src/jit/IonCaches.h b/js/src/jit/IonCaches.h index 20a357b5ec..8542b5d825 100644 --- a/js/src/jit/IonCaches.h +++ b/js/src/jit/IonCaches.h @@ -13,6 +13,8 @@ # include "jit/arm64/Assembler-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/Assembler-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/Assembler-mips64.h" #endif #include "jit/JitCompartment.h" #include "jit/Registers.h" diff --git a/js/src/jit/IonCode.h b/js/src/jit/IonCode.h index 2995c883bb..00e1031250 100644 --- a/js/src/jit/IonCode.h +++ b/js/src/jit/IonCode.h @@ -743,7 +743,7 @@ struct VMFunction; struct AutoFlushICache { private: -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) uintptr_t start_; uintptr_t stop_; const char* name_; diff --git a/js/src/jit/JitCommon.h b/js/src/jit/JitCommon.h index 3ea06d735d..7e3f71837f 100644 --- a/js/src/jit/JitCommon.h +++ b/js/src/jit/JitCommon.h @@ -15,6 +15,8 @@ # include "jit/arm64/vixl/Simulator-vixl.h" #elif defined(JS_SIMULATOR_MIPS32) #include "jit/mips32/Simulator-mips32.h" +#elif defined(JS_SIMULATOR_MIPS64) +#include "jit/mips64/Simulator-mips64.h" #endif #ifdef JS_SIMULATOR diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 52581dc92b..277f6d64f9 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -2578,6 +2578,11 @@ MachineState::FromBailout(RegisterDump::GPRArray& regs, RegisterDump::FPUArray& machine.setRegisterLocation(FloatRegister::FromIndex(i, FloatRegister::Single), (double*)&fbase[i]); } +#elif defined(JS_CODEGEN_MIPS64) + for (unsigned i = 0; i < FloatRegisters::TotalPhys; i++) { + machine.setRegisterLocation(FloatRegister(i, FloatRegisters::Double), &fpregs[i]); + machine.setRegisterLocation(FloatRegister(i, FloatRegisters::Single), &fpregs[i]); + } #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) for (unsigned i = 0; i < FloatRegisters::TotalPhys; i++) { machine.setRegisterLocation(FloatRegister(i, FloatRegisters::Single), &fpregs[i]); diff --git a/js/src/jit/LIR.h b/js/src/jit/LIR.h index e88accdbd5..83d663a4eb 100644 --- a/js/src/jit/LIR.h +++ b/js/src/jit/LIR.h @@ -1827,9 +1827,11 @@ LAllocation::toRegister() const # include "jit/arm/LIR-arm.h" #elif defined(JS_CODEGEN_ARM64) # include "jit/arm64/LIR-arm64.h" -#elif defined(JS_CODEGEN_MIPS32) +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) # if defined(JS_CODEGEN_MIPS32) # include "jit/mips32/LIR-mips32.h" +# elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/LIR-mips64.h" # endif # include "jit/mips-shared/LIR-mips-shared.h" #elif defined(JS_CODEGEN_NONE) diff --git a/js/src/jit/LOpcodes.h b/js/src/jit/LOpcodes.h index 7d6485de35..271a92c1ff 100644 --- a/js/src/jit/LOpcodes.h +++ b/js/src/jit/LOpcodes.h @@ -17,6 +17,8 @@ # include "jit/arm64/LOpcodes-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/LOpcodes-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/LOpcodes-mips64.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/LOpcodes-none.h" #else diff --git a/js/src/jit/Lowering.h b/js/src/jit/Lowering.h index 27d3a83e19..138f312388 100644 --- a/js/src/jit/Lowering.h +++ b/js/src/jit/Lowering.h @@ -21,6 +21,8 @@ # include "jit/arm64/Lowering-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/Lowering-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/Lowering-mips64.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/Lowering-none.h" #else diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index 073fd51d06..c590d9b9b9 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -13696,17 +13696,17 @@ class MAsmJSCall final private: Which which_; union { - Label* internal_; + AsmJSInternalCallee internal_; MDefinition* dynamic_; AsmJSImmKind builtin_; } u; public: Callee() {} - explicit Callee(Label* callee) : which_(Internal) { u.internal_ = callee; } + explicit Callee(AsmJSInternalCallee callee) : which_(Internal) { u.internal_ = callee; } explicit Callee(MDefinition* callee) : which_(Dynamic) { u.dynamic_ = callee; } explicit Callee(AsmJSImmKind callee) : which_(Builtin) { u.builtin_ = callee; } Which which() const { return which_; } - Label* internal() const { MOZ_ASSERT(which_ == Internal); return u.internal_; } + AsmJSInternalCallee internal() const { MOZ_ASSERT(which_ == Internal); return u.internal_; } MDefinition* dynamic() const { MOZ_ASSERT(which_ == Dynamic); return u.dynamic_; } AsmJSImmKind builtin() const { MOZ_ASSERT(which_ == Builtin); return u.builtin_; } }; diff --git a/js/src/jit/MIRGraph.cpp b/js/src/jit/MIRGraph.cpp index ffc973f2bf..a36aaf8f39 100644 --- a/js/src/jit/MIRGraph.cpp +++ b/js/src/jit/MIRGraph.cpp @@ -345,17 +345,28 @@ MBasicBlock::NewAsmJS(MIRGraph& graph, CompileInfo& info, MBasicBlock* pred, Kin if (block->kind_ == PENDING_LOOP_HEADER) { size_t nphis = block->stackPosition_; + size_t nfree = graph.phiFreeListLength(); + TempAllocator& alloc = graph.alloc(); - MPhi* phis = alloc.allocateArray(nphis); - if (!phis) - return nullptr; + MPhi* phis = nullptr; + if (nphis > nfree) { + phis = alloc.allocateArray(nphis - nfree); + if (!phis) + return nullptr; + } // Note: Phis are inserted in the same order as the slots. for (size_t i = 0; i < nphis; i++) { MDefinition* predSlot = pred->getSlot(i); MOZ_ASSERT(predSlot->type() != MIRType_Value); - MPhi* phi = new(phis + i) MPhi(alloc, predSlot->type()); + + MPhi* phi; + if (i < nfree) + phi = graph.takePhiFromFreeList(); + else + phi = phis + (i - nfree); + new(phi) MPhi(alloc, predSlot->type()); phi->addInput(predSlot); diff --git a/js/src/jit/MIRGraph.h b/js/src/jit/MIRGraph.h index 7516161d40..8f5786665b 100644 --- a/js/src/jit/MIRGraph.h +++ b/js/src/jit/MIRGraph.h @@ -693,6 +693,9 @@ class MIRGraph size_t numBlocks_; bool hasTryBlock_; + InlineList phiFreeList_; + size_t phiFreeListLength_; + public: explicit MIRGraph(TempAllocator* alloc) : alloc_(alloc), @@ -701,7 +704,8 @@ class MIRGraph idGen_(0), osrBlock_(nullptr), numBlocks_(0), - hasTryBlock_(false) + hasTryBlock_(false), + phiFreeListLength_(0) { } TempAllocator& alloc() const { @@ -812,6 +816,19 @@ class MIRGraph void dump(GenericPrinter& out); void dump(); + + void addPhiToFreeList(MPhi* phi) { + phiFreeList_.pushBack(phi); + phiFreeListLength_++; + } + size_t phiFreeListLength() const { + return phiFreeListLength_; + } + MPhi* takePhiFromFreeList() { + MOZ_ASSERT(phiFreeListLength_ > 0); + phiFreeListLength_--; + return phiFreeList_.popBack(); + } }; class MDefinitionIterator diff --git a/js/src/jit/MacroAssembler-inl.h b/js/src/jit/MacroAssembler-inl.h index 5e1a7164f9..2d674cabc8 100644 --- a/js/src/jit/MacroAssembler-inl.h +++ b/js/src/jit/MacroAssembler-inl.h @@ -19,6 +19,8 @@ # include "jit/arm64/MacroAssembler-arm64-inl.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/MacroAssembler-mips32-inl.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/MacroAssembler-mips64-inl.h" #elif !defined(JS_CODEGEN_NONE) # error "Unknown architecture!" #endif @@ -79,15 +81,22 @@ MacroAssembler::PushWithPatch(ImmPtr imm) void MacroAssembler::call(const CallSiteDesc& desc, const Register reg) { - call(reg); - append(desc, currentOffset(), framePushed()); + CodeOffsetLabel l = call(reg); + append(desc, l, framePushed()); } void MacroAssembler::call(const CallSiteDesc& desc, Label* label) { - call(label); - append(desc, currentOffset(), framePushed()); + CodeOffsetLabel l = call(label); + append(desc, l, framePushed()); +} + +void +MacroAssembler::call(const CallSiteDesc& desc, AsmJSInternalCallee callee) +{ + CodeOffsetLabel l = callWithPatch(); + append(desc, l, framePushed(), callee.index); } // =============================================================== diff --git a/js/src/jit/MacroAssembler.cpp b/js/src/jit/MacroAssembler.cpp index 29541853c1..2a182e4c9e 100644 --- a/js/src/jit/MacroAssembler.cpp +++ b/js/src/jit/MacroAssembler.cpp @@ -446,95 +446,6 @@ template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const A template void MacroAssembler::loadFromTypedArray(Scalar::Type arrayType, const BaseIndex& src, const ValueOperand& dest, bool allowDouble, Register temp, Label* fail); -template -void -MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, - Register oldval, Register newval, - Register temp, AnyRegister output) -{ - switch (arrayType) { - case Scalar::Int8: - compareExchange8SignExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint8: - compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint8Clamped: - compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Int16: - compareExchange16SignExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint16: - compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); - break; - case Scalar::Int32: - compareExchange32(mem, oldval, newval, output.gpr()); - break; - case Scalar::Uint32: - // At the moment, the code in MCallOptimize.cpp requires the output - // type to be double for uint32 arrays. See bug 1077305. - MOZ_ASSERT(output.isFloat()); - compareExchange32(mem, oldval, newval, temp); - convertUInt32ToDouble(temp, output.fpu()); - break; - default: - MOZ_CRASH("Invalid typed array type"); - } -} - -template void -MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, - Register oldval, Register newval, Register temp, - AnyRegister output); -template void -MacroAssembler::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, - Register oldval, Register newval, Register temp, - AnyRegister output); - -template -void -MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, - Register value, Register temp, AnyRegister output) -{ - switch (arrayType) { - case Scalar::Int8: - atomicExchange8SignExtend(mem, value, output.gpr()); - break; - case Scalar::Uint8: - atomicExchange8ZeroExtend(mem, value, output.gpr()); - break; - case Scalar::Uint8Clamped: - atomicExchange8ZeroExtend(mem, value, output.gpr()); - break; - case Scalar::Int16: - atomicExchange16SignExtend(mem, value, output.gpr()); - break; - case Scalar::Uint16: - atomicExchange16ZeroExtend(mem, value, output.gpr()); - break; - case Scalar::Int32: - atomicExchange32(mem, value, output.gpr()); - break; - case Scalar::Uint32: - // At the moment, the code in MCallOptimize.cpp requires the output - // type to be double for uint32 arrays. See bug 1077305. - MOZ_ASSERT(output.isFloat()); - atomicExchange32(mem, value, temp); - convertUInt32ToDouble(temp, output.fpu()); - break; - default: - MOZ_CRASH("Invalid typed array type"); - } -} - -template void -MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, - Register value, Register temp, AnyRegister output); -template void -MacroAssembler::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, - Register value, Register temp, AnyRegister output); - template void MacroAssembler::loadUnboxedProperty(T address, JSValueType type, TypedOrValueRegister output) diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index d937813080..180cb242d4 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -22,6 +22,8 @@ # include "jit/arm64/MacroAssembler-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/MacroAssembler-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/MacroAssembler-mips64.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/MacroAssembler-none.h" #else @@ -62,8 +64,8 @@ // architectures on each method declaration, such as PER_ARCH and // PER_SHARED_ARCH. -# define ALL_ARCH mips32, arm, arm64, x86, x64 -# define ALL_SHARED_ARCH mips32, arm, arm64, x86_shared +# define ALL_ARCH mips32, mips64, arm, arm64, x86, x64 +# define ALL_SHARED_ARCH arm, arm64, x86_shared, mips_shared // * How this macro works: // @@ -107,6 +109,8 @@ # define DEFINED_ON_arm # define DEFINED_ON_arm64 # define DEFINED_ON_mips32 +# define DEFINED_ON_mips64 +# define DEFINED_ON_mips_shared # define DEFINED_ON_none // Specialize for each architecture. @@ -129,6 +133,13 @@ #elif defined(JS_CODEGEN_MIPS32) # undef DEFINED_ON_mips32 # define DEFINED_ON_mips32 define +# undef DEFINED_ON_mips_shared +# define DEFINED_ON_mips_shared define +#elif defined(JS_CODEGEN_MIPS64) +# undef DEFINED_ON_mips64 +# define DEFINED_ON_mips64 define +# undef DEFINED_ON_mips_shared +# define DEFINED_ON_mips_shared define #elif defined(JS_CODEGEN_NONE) # undef DEFINED_ON_none # define DEFINED_ON_none crash @@ -417,12 +428,14 @@ class MacroAssembler : public MacroAssemblerSpecific // =============================================================== // Stack manipulation functions. - void PushRegsInMask(LiveRegisterSet set) PER_SHARED_ARCH; + void PushRegsInMask(LiveRegisterSet set) + DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); void PushRegsInMask(LiveGeneralRegisterSet set); void PopRegsInMask(LiveRegisterSet set); void PopRegsInMask(LiveGeneralRegisterSet set); - void PopRegsInMaskIgnore(LiveRegisterSet set, LiveRegisterSet ignore) PER_SHARED_ARCH; + void PopRegsInMaskIgnore(LiveRegisterSet set, LiveRegisterSet ignore) + DEFINED_ON(arm, arm64, mips32, mips64, x86_shared); void Push(const Operand op) DEFINED_ON(x86_shared); void Push(Register reg) PER_SHARED_ARCH; @@ -471,9 +484,9 @@ class MacroAssembler : public MacroAssemblerSpecific // =============================================================== // Simple call functions. - void call(Register reg) PER_SHARED_ARCH; + CodeOffsetLabel call(Register reg) PER_SHARED_ARCH; + CodeOffsetLabel call(Label* label) PER_SHARED_ARCH; void call(const Address& addr) DEFINED_ON(x86_shared); - void call(Label* label) PER_SHARED_ARCH; void call(ImmWord imm) PER_SHARED_ARCH; // Call a target native function, which is neither traceable nor movable. void call(ImmPtr imm) PER_SHARED_ARCH; @@ -483,12 +496,16 @@ class MacroAssembler : public MacroAssemblerSpecific inline void call(const CallSiteDesc& desc, const Register reg); inline void call(const CallSiteDesc& desc, Label* label); + inline void call(const CallSiteDesc& desc, AsmJSInternalCallee callee); + + CodeOffsetLabel callWithPatch() PER_SHARED_ARCH; + void patchCall(uint32_t callerOffset, uint32_t calleeOffset) PER_SHARED_ARCH; // Push the return address and make a call. On platforms where this function // is not defined, push the link register (pushReturnAddress) at the entry // point of the callee. - void callAndPushReturnAddress(Register reg) DEFINED_ON(mips32, x86_shared); - void callAndPushReturnAddress(Label* label) DEFINED_ON(mips32, x86_shared); + void callAndPushReturnAddress(Register reg) DEFINED_ON(mips_shared, x86_shared); + void callAndPushReturnAddress(Label* label) DEFINED_ON(mips_shared, x86_shared); void pushReturnAddress() DEFINED_ON(arm, arm64); @@ -1069,14 +1086,6 @@ class MacroAssembler : public MacroAssemblerSpecific } } - template - void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, - Register temp, AnyRegister output); - - template - void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, - Register temp, AnyRegister output); - void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const BaseIndex& dest, unsigned numElems = 0); void storeToTypedFloatArray(Scalar::Type arrayType, FloatRegister value, const Address& dest, diff --git a/js/src/jit/MoveEmitter.h b/js/src/jit/MoveEmitter.h index 6c686bb138..133ba5a74d 100644 --- a/js/src/jit/MoveEmitter.h +++ b/js/src/jit/MoveEmitter.h @@ -15,6 +15,8 @@ # include "jit/arm64/MoveEmitter-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/MoveEmitter-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/MoveEmitter-mips64.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/MoveEmitter-none.h" #else diff --git a/js/src/jit/RegisterAllocator.h b/js/src/jit/RegisterAllocator.h index f47036b4fc..1001335024 100644 --- a/js/src/jit/RegisterAllocator.h +++ b/js/src/jit/RegisterAllocator.h @@ -279,7 +279,7 @@ class RegisterAllocator if (mir->compilingAsmJS()) { #if defined(JS_CODEGEN_X64) allRegisters_.take(AnyRegister(HeapReg)); -#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) +#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) allRegisters_.take(AnyRegister(HeapReg)); allRegisters_.take(AnyRegister(GlobalReg)); #elif defined(JS_CODEGEN_ARM64) diff --git a/js/src/jit/RegisterSets.h b/js/src/jit/RegisterSets.h index 1c1310a2a6..1443b0820c 100644 --- a/js/src/jit/RegisterSets.h +++ b/js/src/jit/RegisterSets.h @@ -1309,7 +1309,7 @@ SavedNonVolatileRegisters(AllocatableGeneralRegisterSet unused) result.add(Register::FromCode(Registers::lr)); #elif defined(JS_CODEGEN_ARM64) result.add(Register::FromCode(Registers::lr)); -#elif defined(JS_CODEGEN_MIPS32) +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) result.add(Register::FromCode(Registers::ra)); #endif diff --git a/js/src/jit/Registers.h b/js/src/jit/Registers.h index 0bde25884d..18ca85b2bf 100644 --- a/js/src/jit/Registers.h +++ b/js/src/jit/Registers.h @@ -18,6 +18,8 @@ # include "jit/arm64/Architecture-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/Architecture-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/Architecture-mips64.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/Architecture-none.h" #else diff --git a/js/src/jit/SharedIC.cpp b/js/src/jit/SharedIC.cpp index c27150b03e..5afa433036 100644 --- a/js/src/jit/SharedIC.cpp +++ b/js/src/jit/SharedIC.cpp @@ -852,7 +852,7 @@ ICStubCompiler::emitPostWriteBarrierSlot(MacroAssembler& masm, Register obj, Val masm.branchValueIsNurseryObject(Assembler::NotEqual, val, scratch, &skipBarrier); // void PostWriteBarrier(JSRuntime* rt, JSObject* obj); -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) saveRegs.add(ICTailCallReg); #endif saveRegs.set() = GeneralRegisterSet::Intersect(saveRegs.set(), GeneralRegisterSet::Volatile()); diff --git a/js/src/jit/SharedIC.h b/js/src/jit/SharedIC.h index a6cc228a52..2707a4ce82 100644 --- a/js/src/jit/SharedIC.h +++ b/js/src/jit/SharedIC.h @@ -1026,7 +1026,7 @@ class ICStubCompiler MOZ_ASSERT(!regs.has(BaselineStackReg)); MOZ_ASSERT(!regs.has(ICTailCallReg)); regs.take(BaselineSecondScratchReg); -#elif defined(JS_CODEGEN_MIPS32) +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) MOZ_ASSERT(!regs.has(BaselineStackReg)); MOZ_ASSERT(!regs.has(ICTailCallReg)); MOZ_ASSERT(!regs.has(BaselineSecondScratchReg)); diff --git a/js/src/jit/SharedICHelpers.h b/js/src/jit/SharedICHelpers.h index 09c21fbfdd..032347da0b 100644 --- a/js/src/jit/SharedICHelpers.h +++ b/js/src/jit/SharedICHelpers.h @@ -17,6 +17,8 @@ # include "jit/arm64/SharedICHelpers-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/SharedICHelpers-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/SharedICHelpers-mips64.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/SharedICHelpers-none.h" #else diff --git a/js/src/jit/SharedICRegisters.h b/js/src/jit/SharedICRegisters.h index 6f01608785..9923385ffa 100644 --- a/js/src/jit/SharedICRegisters.h +++ b/js/src/jit/SharedICRegisters.h @@ -17,6 +17,8 @@ # include "jit/arm64/SharedICRegisters-arm64.h" #elif defined(JS_CODEGEN_MIPS32) # include "jit/mips32/SharedICRegisters-mips32.h" +#elif defined(JS_CODEGEN_MIPS64) +# include "jit/mips64/SharedICRegisters-mips64.h" #elif defined(JS_CODEGEN_NONE) # include "jit/none/SharedICRegisters-none.h" #else diff --git a/js/src/jit/VMFunctions.cpp b/js/src/jit/VMFunctions.cpp index 72a53f5762..3412fc4fcf 100644 --- a/js/src/jit/VMFunctions.cpp +++ b/js/src/jit/VMFunctions.cpp @@ -13,6 +13,7 @@ #include "jit/JitCompartment.h" #include "jit/JitFrames.h" #include "jit/mips32/Simulator-mips32.h" +#include "jit/mips64/Simulator-mips64.h" #include "vm/ArrayObject.h" #include "vm/Debugger.h" #include "vm/Interpreter.h" diff --git a/js/src/jit/arm/CodeGenerator-arm.cpp b/js/src/jit/arm/CodeGenerator-arm.cpp index b77431ea77..80353da705 100644 --- a/js/src/jit/arm/CodeGenerator-arm.cpp +++ b/js/src/jit/arm/CodeGenerator-arm.cpp @@ -1689,6 +1689,48 @@ CodeGeneratorARM::visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStati MOZ_CRASH("NYI"); } +void +CodeGeneratorARM::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } +} + +void +CodeGeneratorARM::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register value = ToRegister(lir->value()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } +} template void diff --git a/js/src/jit/arm/CodeGenerator-arm.h b/js/src/jit/arm/CodeGenerator-arm.h index 8f1520bdbe..f03c3999a5 100644 --- a/js/src/jit/arm/CodeGenerator-arm.h +++ b/js/src/jit/arm/CodeGenerator-arm.h @@ -196,6 +196,8 @@ class CodeGeneratorARM : public CodeGeneratorShared void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins); void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir); void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir); + void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); + void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitAsmJSCall(LAsmJSCall* ins); void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins); void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins); diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index 6bb73ef160..bf2dfd41ed 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -4786,6 +4786,95 @@ template void js::jit::MacroAssemblerARMCompat::atomicEffectOp(int nbytes, AtomicOp op, const Register& value, const BaseIndex& mem, Register flagTemp); +template +void +MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register oldval, Register newval, + Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + compareExchange8SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8Clamped: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int16: + compareExchange16SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint16: + compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int32: + compareExchange32(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + compareExchange32(mem, oldval, newval, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); +template void +MacroAssemblerARMCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); + +template +void +MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register value, Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + atomicExchange8SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8Clamped: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int16: + atomicExchange16SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint16: + atomicExchange16ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int32: + atomicExchange32(mem, value, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + atomicExchange32(mem, value, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register value, Register temp, AnyRegister output); +template void +MacroAssemblerARMCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register value, Register temp, AnyRegister output); + void MacroAssemblerARMCompat::profilerEnterFrame(Register framePtr, Register scratch) { @@ -4968,17 +5057,19 @@ MacroAssembler::reserveStack(uint32_t amount) // =============================================================== // Simple call functions. -void +CodeOffsetLabel MacroAssembler::call(Register reg) { as_blx(reg); + return CodeOffsetLabel(currentOffset()); } -void +CodeOffsetLabel MacroAssembler::call(Label* label) { - // For now, assume that it'll be nearby? + // For now, assume that it'll be nearby. as_bl(label, Always); + return CodeOffsetLabel(currentOffset()); } void @@ -5018,6 +5109,20 @@ MacroAssembler::call(JitCode* c) callJitNoProfiler(scratch); } +CodeOffsetLabel +MacroAssembler::callWithPatch() +{ + // For now, assume that it'll be nearby. + as_bl(BOffImm(), Always, /* documentation */ nullptr); + return CodeOffsetLabel(currentOffset()); +} +void +MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset) +{ + BufferOffset inst(callerOffset - 4); + as_bl(BufferOffset(calleeOffset).diffB(inst), Always, inst); +} + void MacroAssembler::pushReturnAddress() { diff --git a/js/src/jit/arm/MacroAssembler-arm.h b/js/src/jit/arm/MacroAssembler-arm.h index 09a9a1a399..08375dc577 100644 --- a/js/src/jit/arm/MacroAssembler-arm.h +++ b/js/src/jit/arm/MacroAssembler-arm.h @@ -1617,6 +1617,14 @@ class MacroAssemblerARMCompat : public MacroAssemblerARM atomicEffectOp(4, AtomicFetchXorOp, value, mem, flagTemp); } + template + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + void clampIntToUint8(Register reg) { // Look at (reg >> 8) if it is 0, then reg shouldn't be clamped if it is // <0, then we want to clamp to 0, otherwise, we wish to clamp to 255 diff --git a/js/src/jit/arm64/CodeGenerator-arm64.cpp b/js/src/jit/arm64/CodeGenerator-arm64.cpp index 0b2ad6916a..dd746d5bf3 100644 --- a/js/src/jit/arm64/CodeGenerator-arm64.cpp +++ b/js/src/jit/arm64/CodeGenerator-arm64.cpp @@ -737,3 +737,47 @@ CodeGeneratorARM64::setReturnDoubleRegs(LiveRegisterSet* regs) regs->add(s1); regs->add(ReturnDoubleReg); } + +void +CodeGeneratorARM64::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } +} + +void +CodeGeneratorARM64::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register value = ToRegister(lir->value()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } +} + diff --git a/js/src/jit/arm64/CodeGenerator-arm64.h b/js/src/jit/arm64/CodeGenerator-arm64.h index 313e1fbf85..da817fd10f 100644 --- a/js/src/jit/arm64/CodeGenerator-arm64.h +++ b/js/src/jit/arm64/CodeGenerator-arm64.h @@ -207,6 +207,8 @@ class CodeGeneratorARM64 : public CodeGeneratorShared void visitNegF(LNegF* lir); void visitLoadTypedArrayElementStatic(LLoadTypedArrayElementStatic* ins); void visitStoreTypedArrayElementStatic(LStoreTypedArrayElementStatic* ins); + void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); + void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitAsmJSCall(LAsmJSCall* ins); void visitAsmJSLoadHeap(LAsmJSLoadHeap* ins); void visitAsmJSStoreHeap(LAsmJSStoreHeap* ins); diff --git a/js/src/jit/arm64/MacroAssembler-arm64.cpp b/js/src/jit/arm64/MacroAssembler-arm64.cpp index 859ace6409..43057e645f 100644 --- a/js/src/jit/arm64/MacroAssembler-arm64.cpp +++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp @@ -255,6 +255,95 @@ MacroAssemblerCompat::breakpoint() Brk((code++) & 0xffff); } +template +void +MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register oldval, Register newval, + Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + compareExchange8SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8Clamped: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int16: + compareExchange16SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint16: + compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int32: + compareExchange32(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + compareExchange32(mem, oldval, newval, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); +template void +MacroAssemblerCompat::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); + +template +void +MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register value, Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + atomicExchange8SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8Clamped: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int16: + atomicExchange16SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint16: + atomicExchange16ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int32: + atomicExchange32(mem, value, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + atomicExchange32(mem, value, temp); + convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register value, Register temp, AnyRegister output); +template void +MacroAssemblerCompat::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register value, Register temp, AnyRegister output); + //{{{ check_macroassembler_style // =============================================================== // Stack manipulation functions. @@ -408,18 +497,20 @@ MacroAssembler::reserveStack(uint32_t amount) // =============================================================== // Simple call functions. -void +CodeOffsetLabel MacroAssembler::call(Register reg) { syncStackPtr(); Blr(ARMRegister(reg, 64)); + return CodeOffsetLabel(currentOffset()); } -void +CodeOffsetLabel MacroAssembler::call(Label* label) { syncStackPtr(); Bl(label); + return CodeOffsetLabel(currentOffset()); } void @@ -457,6 +548,18 @@ MacroAssembler::call(JitCode* c) blr(scratch64); } +CodeOffsetLabel +MacroAssembler::callWithPatch() +{ + MOZ_CRASH("NYI"); + return CodeOffsetLabel(); +} +void +MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset) +{ + MOZ_CRASH("NYI"); +} + void MacroAssembler::pushReturnAddress() { diff --git a/js/src/jit/arm64/MacroAssembler-arm64.h b/js/src/jit/arm64/MacroAssembler-arm64.h index df615d57af..1604a73037 100644 --- a/js/src/jit/arm64/MacroAssembler-arm64.h +++ b/js/src/jit/arm64/MacroAssembler-arm64.h @@ -2863,6 +2863,14 @@ class MacroAssemblerCompat : public vixl::MacroAssembler atomicEffectOp(4, AtomicFetchXorOp, value, mem); } + template + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + // Emit a BLR or NOP instruction. ToggleCall can be used to patch // this instruction. CodeOffsetLabel toggledCall(JitCode* target, bool enabled) { diff --git a/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h new file mode 100644 index 0000000000..b310ea95ea --- /dev/null +++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips_shared_MacroAssembler_mips_shared_inl_h +#define jit_mips_shared_MacroAssembler_mips_shared_inl_h + +#include "jit/mips-shared/MacroAssembler-mips-shared.h" + +namespace js { +namespace jit { + +//{{{ check_macroassembler_style +// =============================================================== +// Logical instructions + +void +MacroAssembler::not32(Register reg) +{ + ma_not(reg, reg); +} + +void +MacroAssembler::and32(Register src, Register dest) +{ + as_and(dest, dest, src); +} + +void +MacroAssembler::and32(Imm32 imm, Register dest) +{ + ma_and(dest, imm); +} + +void +MacroAssembler::and32(Imm32 imm, const Address& dest) +{ + load32(dest, SecondScratchReg); + ma_and(SecondScratchReg, imm); + store32(SecondScratchReg, dest); +} + +void +MacroAssembler::and32(const Address& src, Register dest) +{ + load32(src, SecondScratchReg); + ma_and(dest, SecondScratchReg); +} + +void +MacroAssembler::or32(Register src, Register dest) +{ + ma_or(dest, src); +} + +void +MacroAssembler::or32(Imm32 imm, Register dest) +{ + ma_or(dest, imm); +} + +void +MacroAssembler::or32(Imm32 imm, const Address& dest) +{ + load32(dest, SecondScratchReg); + ma_or(SecondScratchReg, imm); + store32(SecondScratchReg, dest); +} + +void +MacroAssembler::xor32(Imm32 imm, Register dest) +{ + ma_xor(dest, imm); +} + +//}}} check_macroassembler_style +// =============================================================== + +} // namespace jit +} // namespace js + +#endif /* jit_mips_shared_MacroAssembler_mips_shared_inl_h */ diff --git a/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp new file mode 100644 index 0000000000..b408e7c7a0 --- /dev/null +++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp @@ -0,0 +1,949 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jit/mips-shared/MacroAssembler-mips-shared.h" + +using namespace js; +using namespace jit; + +void +MacroAssemblerMIPSShared::ma_move(Register rd, Register rs) +{ + as_or(rd, rs, zero); +} + +void +MacroAssemblerMIPSShared::ma_li(Register dest, ImmGCPtr ptr) +{ + writeDataRelocation(ptr); + asMasm().ma_liPatchable(dest, ImmPtr(ptr.value)); +} + +void +MacroAssemblerMIPSShared::ma_li(Register dest, Imm32 imm) +{ + if (Imm16::IsInSignedRange(imm.value)) { + as_addiu(dest, zero, imm.value); + } else if (Imm16::IsInUnsignedRange(imm.value)) { + as_ori(dest, zero, Imm16::Lower(imm).encode()); + } else if (Imm16::Lower(imm).encode() == 0) { + as_lui(dest, Imm16::Upper(imm).encode()); + } else { + as_lui(dest, Imm16::Upper(imm).encode()); + as_ori(dest, dest, Imm16::Lower(imm).encode()); + } +} + +// Shifts +void +MacroAssemblerMIPSShared::ma_sll(Register rd, Register rt, Imm32 shift) +{ + as_sll(rd, rt, shift.value % 32); +} +void +MacroAssemblerMIPSShared::ma_srl(Register rd, Register rt, Imm32 shift) +{ + as_srl(rd, rt, shift.value % 32); +} + +void +MacroAssemblerMIPSShared::ma_sra(Register rd, Register rt, Imm32 shift) +{ + as_sra(rd, rt, shift.value % 32); +} + +void +MacroAssemblerMIPSShared::ma_ror(Register rd, Register rt, Imm32 shift) +{ + as_rotr(rd, rt, shift.value % 32); +} + +void +MacroAssemblerMIPSShared::ma_rol(Register rd, Register rt, Imm32 shift) +{ + as_rotr(rd, rt, 32 - (shift.value % 32)); +} + +void +MacroAssemblerMIPSShared::ma_sll(Register rd, Register rt, Register shift) +{ + as_sllv(rd, rt, shift); +} + +void +MacroAssemblerMIPSShared::ma_srl(Register rd, Register rt, Register shift) +{ + as_srlv(rd, rt, shift); +} + +void +MacroAssemblerMIPSShared::ma_sra(Register rd, Register rt, Register shift) +{ + as_srav(rd, rt, shift); +} + +void +MacroAssemblerMIPSShared::ma_ror(Register rd, Register rt, Register shift) +{ + as_rotrv(rd, rt, shift); +} + +void +MacroAssemblerMIPSShared::ma_rol(Register rd, Register rt, Register shift) +{ + ma_negu(ScratchRegister, shift); + as_rotrv(rd, rt, ScratchRegister); +} + +void +MacroAssemblerMIPSShared::ma_negu(Register rd, Register rs) +{ + as_subu(rd, zero, rs); +} + +void +MacroAssemblerMIPSShared::ma_not(Register rd, Register rs) +{ + as_nor(rd, rs, zero); +} + +// And. +void +MacroAssemblerMIPSShared::ma_and(Register rd, Register rs) +{ + as_and(rd, rd, rs); +} + +void +MacroAssemblerMIPSShared::ma_and(Register rd, Imm32 imm) +{ + ma_and(rd, rd, imm); +} + +void +MacroAssemblerMIPSShared::ma_and(Register rd, Register rs, Imm32 imm) +{ + if (Imm16::IsInUnsignedRange(imm.value)) { + as_andi(rd, rs, imm.value); + } else { + ma_li(ScratchRegister, imm); + as_and(rd, rs, ScratchRegister); + } +} + +// Or. +void +MacroAssemblerMIPSShared::ma_or(Register rd, Register rs) +{ + as_or(rd, rd, rs); +} + +void +MacroAssemblerMIPSShared::ma_or(Register rd, Imm32 imm) +{ + ma_or(rd, rd, imm); +} + +void +MacroAssemblerMIPSShared::ma_or(Register rd, Register rs, Imm32 imm) +{ + if (Imm16::IsInUnsignedRange(imm.value)) { + as_ori(rd, rs, imm.value); + } else { + ma_li(ScratchRegister, imm); + as_or(rd, rs, ScratchRegister); + } +} + +// xor +void +MacroAssemblerMIPSShared::ma_xor(Register rd, Register rs) +{ + as_xor(rd, rd, rs); +} + +void +MacroAssemblerMIPSShared::ma_xor(Register rd, Imm32 imm) +{ + ma_xor(rd, rd, imm); +} + +void +MacroAssemblerMIPSShared::ma_xor(Register rd, Register rs, Imm32 imm) +{ + if (Imm16::IsInUnsignedRange(imm.value)) { + as_xori(rd, rs, imm.value); + } else { + ma_li(ScratchRegister, imm); + as_xor(rd, rs, ScratchRegister); + } +} + +// Arithmetic-based ops. + +// Add. +void +MacroAssemblerMIPSShared::ma_addu(Register rd, Register rs, Imm32 imm) +{ + if (Imm16::IsInSignedRange(imm.value)) { + as_addiu(rd, rs, imm.value); + } else { + ma_li(ScratchRegister, imm); + as_addu(rd, rs, ScratchRegister); + } +} + +void +MacroAssemblerMIPSShared::ma_addu(Register rd, Register rs) +{ + as_addu(rd, rd, rs); +} + +void +MacroAssemblerMIPSShared::ma_addu(Register rd, Imm32 imm) +{ + ma_addu(rd, rd, imm); +} + +// Subtract. +void +MacroAssemblerMIPSShared::ma_subu(Register rd, Register rs, Imm32 imm) +{ + if (Imm16::IsInSignedRange(-imm.value)) { + as_addiu(rd, rs, -imm.value); + } else { + ma_li(ScratchRegister, imm); + as_subu(rd, rs, ScratchRegister); + } +} + +void +MacroAssemblerMIPSShared::ma_subu(Register rd, Imm32 imm) +{ + ma_subu(rd, rd, imm); +} + +void +MacroAssemblerMIPSShared::ma_subTestOverflow(Register rd, Register rs, Imm32 imm, Label* overflow) +{ + if (imm.value != INT32_MIN) { + asMasm().ma_addTestOverflow(rd, rs, Imm32(-imm.value), overflow); + } else { + ma_li(ScratchRegister, Imm32(imm.value)); + asMasm().ma_subTestOverflow(rd, rs, ScratchRegister, overflow); + } +} + +void +MacroAssemblerMIPSShared::ma_mult(Register rs, Imm32 imm) +{ + ma_li(ScratchRegister, imm); + as_mult(rs, ScratchRegister); +} + +void +MacroAssemblerMIPSShared::ma_mul_branch_overflow(Register rd, Register rs, Register rt, Label* overflow) +{ + as_mult(rs, rt); + as_mflo(rd); + as_sra(ScratchRegister, rd, 31); + as_mfhi(SecondScratchReg); + ma_b(ScratchRegister, SecondScratchReg, overflow, Assembler::NotEqual); +} + +void +MacroAssemblerMIPSShared::ma_mul_branch_overflow(Register rd, Register rs, Imm32 imm, Label* overflow) +{ + ma_li(ScratchRegister, imm); + ma_mul_branch_overflow(rd, rs, ScratchRegister, overflow); +} + +void +MacroAssemblerMIPSShared::ma_div_branch_overflow(Register rd, Register rs, Register rt, Label* overflow) +{ + as_div(rs, rt); + as_mflo(rd); + as_mfhi(ScratchRegister); + ma_b(ScratchRegister, ScratchRegister, overflow, Assembler::NonZero); +} + +void +MacroAssemblerMIPSShared::ma_div_branch_overflow(Register rd, Register rs, Imm32 imm, Label* overflow) +{ + ma_li(ScratchRegister, imm); + ma_div_branch_overflow(rd, rs, ScratchRegister, overflow); +} + +void +MacroAssemblerMIPSShared::ma_mod_mask(Register src, Register dest, Register hold, Register remain, + int32_t shift, Label* negZero) +{ + // MATH: + // We wish to compute x % (1< 0, store sum - C back into sum, thus performing a + // modulus. + ma_b(SecondScratchReg, SecondScratchReg, &sumSigned, Signed, ShortJump); + ma_move(dest, SecondScratchReg); + bind(&sumSigned); + // Get rid of the bits that we extracted before. + as_srl(remain, remain, shift); + // If the shift produced zero, finish, otherwise, continue in the loop. + ma_b(remain, remain, &head, NonZero, ShortJump); + // Check the hold to see if we need to negate the result. + ma_b(hold, hold, &done, NotSigned, ShortJump); + + // If the hold was non-zero, negate the result to be in line with + // what JS wants + if (negZero != nullptr) { + // Jump out in case of negative zero. + ma_b(hold, hold, negZero, Zero); + ma_negu(dest, dest); + } else { + ma_negu(dest, dest); + } + + bind(&done); +} + +// Memory. + +void +MacroAssemblerMIPSShared::ma_load(Register dest, const BaseIndex& src, + LoadStoreSize size, LoadStoreExtension extension) +{ + asMasm().computeScaledAddress(src, SecondScratchReg); + asMasm().ma_load(dest, Address(SecondScratchReg, src.offset), size, extension); +} + +void +MacroAssemblerMIPSShared::ma_store(Register data, const BaseIndex& dest, + LoadStoreSize size, LoadStoreExtension extension) +{ + asMasm().computeScaledAddress(dest, SecondScratchReg); + asMasm().ma_store(data, Address(SecondScratchReg, dest.offset), size, extension); +} + +void +MacroAssemblerMIPSShared::ma_store(Imm32 imm, const BaseIndex& dest, + LoadStoreSize size, LoadStoreExtension extension) +{ + // Make sure that SecondScratchReg contains absolute address so that + // offset is 0. + asMasm().computeEffectiveAddress(dest, SecondScratchReg); + + // Scrach register is free now, use it for loading imm value + ma_li(ScratchRegister, imm); + + // with offset=0 ScratchRegister will not be used in ma_store() + // so we can use it as a parameter here + asMasm().ma_store(ScratchRegister, Address(SecondScratchReg, 0), size, extension); +} + +// Branches when done from within mips-specific code. +void +MacroAssemblerMIPSShared::ma_b(Register lhs, Register rhs, Label* label, Condition c, JumpKind jumpKind) +{ + switch (c) { + case Equal : + case NotEqual: + asMasm().branchWithCode(getBranchCode(lhs, rhs, c), label, jumpKind); + break; + case Always: + ma_b(label, jumpKind); + break; + case Zero: + case NonZero: + case Signed: + case NotSigned: + MOZ_ASSERT(lhs == rhs); + asMasm().branchWithCode(getBranchCode(lhs, c), label, jumpKind); + break; + default: + Condition cond = ma_cmp(ScratchRegister, lhs, rhs, c); + asMasm().branchWithCode(getBranchCode(ScratchRegister, cond), label, jumpKind); + break; + } +} + +void +MacroAssemblerMIPSShared::ma_b(Register lhs, Imm32 imm, Label* label, Condition c, JumpKind jumpKind) +{ + MOZ_ASSERT(c != Overflow); + if (imm.value == 0) { + if (c == Always || c == AboveOrEqual) + ma_b(label, jumpKind); + else if (c == Below) + ; // This condition is always false. No branch required. + else + asMasm().branchWithCode(getBranchCode(lhs, c), label, jumpKind); + } else { + MOZ_ASSERT(lhs != ScratchRegister); + ma_li(ScratchRegister, imm); + ma_b(lhs, ScratchRegister, label, c, jumpKind); + } +} + +void +MacroAssemblerMIPSShared::ma_b(Register lhs, ImmPtr imm, Label* l, Condition c, JumpKind jumpKind) +{ + asMasm().ma_b(lhs, ImmWord(uintptr_t(imm.value)), l, c, jumpKind); +} + +void +MacroAssemblerMIPSShared::ma_b(Label* label, JumpKind jumpKind) +{ + asMasm().branchWithCode(getBranchCode(BranchIsJump), label, jumpKind); +} + +Assembler::Condition +MacroAssemblerMIPSShared::ma_cmp(Register scratch, Register lhs, Register rhs, Condition c) +{ + switch (c) { + case Above: + // bgtu s,t,label => + // sltu at,t,s + // bne at,$zero,offs + as_sltu(scratch, rhs, lhs); + return NotEqual; + case AboveOrEqual: + // bgeu s,t,label => + // sltu at,s,t + // beq at,$zero,offs + as_sltu(scratch, lhs, rhs); + return Equal; + case Below: + // bltu s,t,label => + // sltu at,s,t + // bne at,$zero,offs + as_sltu(scratch, lhs, rhs); + return NotEqual; + case BelowOrEqual: + // bleu s,t,label => + // sltu at,t,s + // beq at,$zero,offs + as_sltu(scratch, rhs, lhs); + return Equal; + case GreaterThan: + // bgt s,t,label => + // slt at,t,s + // bne at,$zero,offs + as_slt(scratch, rhs, lhs); + return NotEqual; + case GreaterThanOrEqual: + // bge s,t,label => + // slt at,s,t + // beq at,$zero,offs + as_slt(scratch, lhs, rhs); + return Equal; + case LessThan: + // blt s,t,label => + // slt at,s,t + // bne at,$zero,offs + as_slt(scratch, lhs, rhs); + return NotEqual; + case LessThanOrEqual: + // ble s,t,label => + // slt at,t,s + // beq at,$zero,offs + as_slt(scratch, rhs, lhs); + return Equal; + case Equal : + case NotEqual: + case Zero: + case NonZero: + case Always: + case Signed: + case NotSigned: + MOZ_CRASH("There is a better way to compare for equality."); + break; + case Overflow: + MOZ_CRASH("Overflow condition not supported for MIPS."); + break; + default: + MOZ_CRASH("Invalid condition for branch."); + } + return Always; +} + +void +MacroAssemblerMIPSShared::ma_cmp_set(Register rd, Register rs, Register rt, Condition c) +{ + switch (c) { + case Equal : + // seq d,s,t => + // xor d,s,t + // sltiu d,d,1 + as_xor(rd, rs, rt); + as_sltiu(rd, rd, 1); + break; + case NotEqual: + // sne d,s,t => + // xor d,s,t + // sltu d,$zero,d + as_xor(rd, rs, rt); + as_sltu(rd, zero, rd); + break; + case Above: + // sgtu d,s,t => + // sltu d,t,s + as_sltu(rd, rt, rs); + break; + case AboveOrEqual: + // sgeu d,s,t => + // sltu d,s,t + // xori d,d,1 + as_sltu(rd, rs, rt); + as_xori(rd, rd, 1); + break; + case Below: + // sltu d,s,t + as_sltu(rd, rs, rt); + break; + case BelowOrEqual: + // sleu d,s,t => + // sltu d,t,s + // xori d,d,1 + as_sltu(rd, rt, rs); + as_xori(rd, rd, 1); + break; + case GreaterThan: + // sgt d,s,t => + // slt d,t,s + as_slt(rd, rt, rs); + break; + case GreaterThanOrEqual: + // sge d,s,t => + // slt d,s,t + // xori d,d,1 + as_slt(rd, rs, rt); + as_xori(rd, rd, 1); + break; + case LessThan: + // slt d,s,t + as_slt(rd, rs, rt); + break; + case LessThanOrEqual: + // sle d,s,t => + // slt d,t,s + // xori d,d,1 + as_slt(rd, rt, rs); + as_xori(rd, rd, 1); + break; + case Zero: + MOZ_ASSERT(rs == rt); + // seq d,s,$zero => + // xor d,s,$zero + // sltiu d,d,1 + as_xor(rd, rs, zero); + as_sltiu(rd, rd, 1); + break; + case NonZero: + // sne d,s,$zero => + // xor d,s,$zero + // sltu d,$zero,d + as_xor(rd, rs, zero); + as_sltu(rd, zero, rd); + break; + case Signed: + as_slt(rd, rs, zero); + break; + case NotSigned: + // sge d,s,$zero => + // slt d,s,$zero + // xori d,d,1 + as_slt(rd, rs, zero); + as_xori(rd, rd, 1); + break; + default: + MOZ_CRASH("Invalid condition for ma_cmp_set."); + } +} + +void +MacroAssemblerMIPSShared::compareFloatingPoint(FloatFormat fmt, FloatRegister lhs, FloatRegister rhs, + DoubleCondition c, FloatTestKind* testKind, + FPConditionBit fcc) +{ + switch (c) { + case DoubleOrdered: + as_cun(fmt, lhs, rhs, fcc); + *testKind = TestForFalse; + break; + case DoubleEqual: + as_ceq(fmt, lhs, rhs, fcc); + *testKind = TestForTrue; + break; + case DoubleNotEqual: + as_cueq(fmt, lhs, rhs, fcc); + *testKind = TestForFalse; + break; + case DoubleGreaterThan: + as_colt(fmt, rhs, lhs, fcc); + *testKind = TestForTrue; + break; + case DoubleGreaterThanOrEqual: + as_cole(fmt, rhs, lhs, fcc); + *testKind = TestForTrue; + break; + case DoubleLessThan: + as_colt(fmt, lhs, rhs, fcc); + *testKind = TestForTrue; + break; + case DoubleLessThanOrEqual: + as_cole(fmt, lhs, rhs, fcc); + *testKind = TestForTrue; + break; + case DoubleUnordered: + as_cun(fmt, lhs, rhs, fcc); + *testKind = TestForTrue; + break; + case DoubleEqualOrUnordered: + as_cueq(fmt, lhs, rhs, fcc); + *testKind = TestForTrue; + break; + case DoubleNotEqualOrUnordered: + as_ceq(fmt, lhs, rhs, fcc); + *testKind = TestForFalse; + break; + case DoubleGreaterThanOrUnordered: + as_cult(fmt, rhs, lhs, fcc); + *testKind = TestForTrue; + break; + case DoubleGreaterThanOrEqualOrUnordered: + as_cule(fmt, rhs, lhs, fcc); + *testKind = TestForTrue; + break; + case DoubleLessThanOrUnordered: + as_cult(fmt, lhs, rhs, fcc); + *testKind = TestForTrue; + break; + case DoubleLessThanOrEqualOrUnordered: + as_cule(fmt, lhs, rhs, fcc); + *testKind = TestForTrue; + break; + default: + MOZ_CRASH("Invalid DoubleCondition."); + } +} + +void +MacroAssemblerMIPSShared::ma_cmp_set_double(Register dest, FloatRegister lhs, FloatRegister rhs, + DoubleCondition c) +{ + ma_li(dest, Imm32(0)); + ma_li(ScratchRegister, Imm32(1)); + + FloatTestKind moveCondition; + compareFloatingPoint(DoubleFloat, lhs, rhs, c, &moveCondition); + + if (moveCondition == TestForTrue) + as_movt(dest, ScratchRegister); + else + as_movf(dest, ScratchRegister); +} + +void +MacroAssemblerMIPSShared::ma_cmp_set_float32(Register dest, FloatRegister lhs, FloatRegister rhs, + DoubleCondition c) +{ + ma_li(dest, Imm32(0)); + ma_li(ScratchRegister, Imm32(1)); + + FloatTestKind moveCondition; + compareFloatingPoint(SingleFloat, lhs, rhs, c, &moveCondition); + + if (moveCondition == TestForTrue) + as_movt(dest, ScratchRegister); + else + as_movf(dest, ScratchRegister); +} + +void +MacroAssemblerMIPSShared::ma_cmp_set(Register rd, Register rs, Imm32 imm, Condition c) +{ + ma_li(ScratchRegister, imm); + ma_cmp_set(rd, rs, ScratchRegister, c); +} + +// fp instructions +void +MacroAssemblerMIPSShared::ma_lis(FloatRegister dest, float value) +{ + Imm32 imm(mozilla::BitwiseCast(value)); + + ma_li(ScratchRegister, imm); + moveToFloat32(ScratchRegister, dest); +} + +void +MacroAssemblerMIPSShared::ma_liNegZero(FloatRegister dest) +{ + moveToDoubleLo(zero, dest); + ma_li(ScratchRegister, Imm32(INT_MIN)); + asMasm().moveToDoubleHi(ScratchRegister, dest); +} + +void +MacroAssemblerMIPSShared::ma_sd(FloatRegister ft, BaseIndex address) +{ + asMasm().computeScaledAddress(address, SecondScratchReg); + asMasm().ma_sd(ft, Address(SecondScratchReg, address.offset)); +} + +void +MacroAssemblerMIPSShared::ma_ss(FloatRegister ft, BaseIndex address) +{ + asMasm().computeScaledAddress(address, SecondScratchReg); + asMasm().ma_ss(ft, Address(SecondScratchReg, address.offset)); +} + +void +MacroAssemblerMIPSShared::ma_bc1s(FloatRegister lhs, FloatRegister rhs, Label* label, + DoubleCondition c, JumpKind jumpKind, FPConditionBit fcc) +{ + FloatTestKind testKind; + compareFloatingPoint(SingleFloat, lhs, rhs, c, &testKind, fcc); + asMasm().branchWithCode(getBranchCode(testKind, fcc), label, jumpKind); +} + +void +MacroAssemblerMIPSShared::ma_bc1d(FloatRegister lhs, FloatRegister rhs, Label* label, + DoubleCondition c, JumpKind jumpKind, FPConditionBit fcc) +{ + FloatTestKind testKind; + compareFloatingPoint(DoubleFloat, lhs, rhs, c, &testKind, fcc); + asMasm().branchWithCode(getBranchCode(testKind, fcc), label, jumpKind); +} + +void +MacroAssemblerMIPSShared::ma_call(ImmPtr dest) +{ + asMasm().ma_liPatchable(CallReg, dest); + as_jalr(CallReg); + as_nop(); +} + +void +MacroAssemblerMIPSShared::ma_jump(ImmPtr dest) +{ + asMasm().ma_liPatchable(ScratchRegister, dest); + as_jr(ScratchRegister); + as_nop(); +} + +MacroAssembler& +MacroAssemblerMIPSShared::asMasm() +{ + return *static_cast(this); +} + +const MacroAssembler& +MacroAssemblerMIPSShared::asMasm() const +{ + return *static_cast(this); +} + +//{{{ check_macroassembler_style +// =============================================================== +// Stack manipulation functions. + +void +MacroAssembler::Push(Register reg) +{ + ma_push(reg); + adjustFrame(sizeof(intptr_t)); +} + +void +MacroAssembler::Push(const Imm32 imm) +{ + ma_li(ScratchRegister, imm); + ma_push(ScratchRegister); + adjustFrame(sizeof(intptr_t)); +} + +void +MacroAssembler::Push(const ImmWord imm) +{ + ma_li(ScratchRegister, imm); + ma_push(ScratchRegister); + adjustFrame(sizeof(intptr_t)); +} + +void +MacroAssembler::Push(const ImmPtr imm) +{ + Push(ImmWord(uintptr_t(imm.value))); +} + +void +MacroAssembler::Push(const ImmGCPtr ptr) +{ + ma_li(ScratchRegister, ptr); + ma_push(ScratchRegister); + adjustFrame(sizeof(intptr_t)); +} + +void +MacroAssembler::Push(FloatRegister f) +{ + ma_push(f); + adjustFrame(sizeof(double)); +} + +void +MacroAssembler::Pop(Register reg) +{ + ma_pop(reg); + adjustFrame(-sizeof(intptr_t)); +} + +void +MacroAssembler::Pop(const ValueOperand& val) +{ + popValue(val); + framePushed_ -= sizeof(Value); +} + + +// =============================================================== +// Simple call functions. + +CodeOffsetLabel +MacroAssembler::call(Register reg) +{ + as_jalr(reg); + as_nop(); + return CodeOffsetLabel(currentOffset()); +} + +CodeOffsetLabel +MacroAssembler::call(Label* label) +{ + ma_bal(label); + return CodeOffsetLabel(currentOffset()); +} + +CodeOffsetLabel +MacroAssembler::callWithPatch() +{ + MOZ_CRASH("NYI"); +} +void +MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset) +{ + MOZ_CRASH("NYI"); +} + +void +MacroAssembler::call(AsmJSImmPtr target) +{ + movePtr(target, CallReg); + call(CallReg); +} + +void +MacroAssembler::call(ImmWord target) +{ + call(ImmPtr((void*)target.value)); +} + +void +MacroAssembler::call(ImmPtr target) +{ + BufferOffset bo = m_buffer.nextOffset(); + addPendingJump(bo, target, Relocation::HARDCODED); + ma_call(target); +} + +void +MacroAssembler::call(JitCode* c) +{ + BufferOffset bo = m_buffer.nextOffset(); + addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE); + ma_liPatchable(ScratchRegister, ImmPtr(c->raw())); + callJitNoProfiler(ScratchRegister); +} + +void +MacroAssembler::callAndPushReturnAddress(Register callee) +{ + // Push return address during jalr delay slot. + subPtr(Imm32(sizeof(intptr_t)), StackPointer); + as_jalr(callee); + storePtr(ra, Address(StackPointer, 0)); +} + +void +MacroAssembler::callAndPushReturnAddress(Label* label) +{ + // Push return address during bal delay slot. + subPtr(Imm32(sizeof(intptr_t)), StackPointer); + ma_bal(label, DontFillDelaySlot); + storePtr(ra, Address(StackPointer, 0)); +} + +// =============================================================== +// Jit Frames. + +uint32_t +MacroAssembler::pushFakeReturnAddress(Register scratch) +{ + CodeLabel cl; + + ma_li(scratch, cl.dest()); + Push(scratch); + bind(cl.src()); + uint32_t retAddr = currentOffset(); + + addCodeLabel(cl); + return retAddr; +} + +//}}} check_macroassembler_style diff --git a/js/src/jit/mips-shared/MacroAssembler-mips-shared.h b/js/src/jit/mips-shared/MacroAssembler-mips-shared.h new file mode 100644 index 0000000000..359b247cd5 --- /dev/null +++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared.h @@ -0,0 +1,185 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips_shared_MacroAssembler_mips_shared_h +#define jit_mips_shared_MacroAssembler_mips_shared_h + +#if defined(JS_CODEGEN_MIPS32) +# include "jit/mips32/Assembler-mips32.h" +#endif + +namespace js { +namespace jit { + +enum LoadStoreSize +{ + SizeByte = 8, + SizeHalfWord = 16, + SizeWord = 32, + SizeDouble = 64 +}; + +enum LoadStoreExtension +{ + ZeroExtend = 0, + SignExtend = 1 +}; + +enum JumpKind +{ + LongJump = 0, + ShortJump = 1 +}; + +enum DelaySlotFill +{ + DontFillDelaySlot = 0, + FillDelaySlot = 1 +}; + +static Register CallReg = t9; + +class MacroAssemblerMIPSShared : public Assembler +{ + protected: + // Perform a downcast. Should be removed by Bug 996602. + MacroAssembler& asMasm(); + const MacroAssembler& asMasm() const; + + Condition ma_cmp(Register rd, Register lhs, Register rhs, Condition c); + + void compareFloatingPoint(FloatFormat fmt, FloatRegister lhs, FloatRegister rhs, + DoubleCondition c, FloatTestKind* testKind, + FPConditionBit fcc = FCC0); + + public: + void ma_move(Register rd, Register rs); + + void ma_li(Register dest, ImmGCPtr ptr); + + void ma_li(Register dest, Imm32 imm); + + // Shift operations + void ma_sll(Register rd, Register rt, Imm32 shift); + void ma_srl(Register rd, Register rt, Imm32 shift); + void ma_sra(Register rd, Register rt, Imm32 shift); + void ma_ror(Register rd, Register rt, Imm32 shift); + void ma_rol(Register rd, Register rt, Imm32 shift); + + void ma_sll(Register rd, Register rt, Register shift); + void ma_srl(Register rd, Register rt, Register shift); + void ma_sra(Register rd, Register rt, Register shift); + void ma_ror(Register rd, Register rt, Register shift); + void ma_rol(Register rd, Register rt, Register shift); + + // Negate + void ma_negu(Register rd, Register rs); + + void ma_not(Register rd, Register rs); + + // and + void ma_and(Register rd, Register rs); + void ma_and(Register rd, Imm32 imm); + void ma_and(Register rd, Register rs, Imm32 imm); + + // or + void ma_or(Register rd, Register rs); + void ma_or(Register rd, Imm32 imm); + void ma_or(Register rd, Register rs, Imm32 imm); + + // xor + void ma_xor(Register rd, Register rs); + void ma_xor(Register rd, Imm32 imm); + void ma_xor(Register rd, Register rs, Imm32 imm); + + // load + void ma_load(Register dest, const BaseIndex& src, LoadStoreSize size = SizeWord, + LoadStoreExtension extension = SignExtend); + + // store + void ma_store(Register data, const BaseIndex& dest, LoadStoreSize size = SizeWord, + LoadStoreExtension extension = SignExtend); + void ma_store(Imm32 imm, const BaseIndex& dest, LoadStoreSize size = SizeWord, + LoadStoreExtension extension = SignExtend); + + // arithmetic based ops + // add + void ma_addu(Register rd, Register rs, Imm32 imm); + void ma_addu(Register rd, Register rs); + void ma_addu(Register rd, Imm32 imm); + + // subtract + void ma_subu(Register rd, Register rs, Imm32 imm); + void ma_subu(Register rd, Imm32 imm); + void ma_subTestOverflow(Register rd, Register rs, Imm32 imm, Label* overflow); + + // multiplies. For now, there are only few that we care about. + void ma_mult(Register rs, Imm32 imm); + void ma_mul_branch_overflow(Register rd, Register rs, Register rt, Label* overflow); + void ma_mul_branch_overflow(Register rd, Register rs, Imm32 imm, Label* overflow); + + // divisions + void ma_div_branch_overflow(Register rd, Register rs, Register rt, Label* overflow); + void ma_div_branch_overflow(Register rd, Register rs, Imm32 imm, Label* overflow); + + // fast mod, uses scratch registers, and thus needs to be in the assembler + // implicitly assumes that we can overwrite dest at the beginning of the sequence + void ma_mod_mask(Register src, Register dest, Register hold, Register remain, + int32_t shift, Label* negZero = nullptr); + + // branches when done from within mips-specific code + void ma_b(Register lhs, Register rhs, Label* l, Condition c, JumpKind jumpKind = LongJump); + void ma_b(Register lhs, Imm32 imm, Label* l, Condition c, JumpKind jumpKind = LongJump); + void ma_b(Register lhs, ImmPtr imm, Label* l, Condition c, JumpKind jumpKind = LongJump); + void ma_b(Register lhs, ImmGCPtr imm, Label* l, Condition c, JumpKind jumpKind = LongJump) { + MOZ_ASSERT(lhs != ScratchRegister); + ma_li(ScratchRegister, imm); + ma_b(lhs, ScratchRegister, l, c, jumpKind); + } + + void ma_b(Label* l, JumpKind jumpKind = LongJump); + + // fp instructions + void ma_lis(FloatRegister dest, float value); + void ma_liNegZero(FloatRegister dest); + + void ma_sd(FloatRegister fd, BaseIndex address); + void ma_ss(FloatRegister fd, BaseIndex address); + + //FP branches + void ma_bc1s(FloatRegister lhs, FloatRegister rhs, Label* label, DoubleCondition c, + JumpKind jumpKind = LongJump, FPConditionBit fcc = FCC0); + void ma_bc1d(FloatRegister lhs, FloatRegister rhs, Label* label, DoubleCondition c, + JumpKind jumpKind = LongJump, FPConditionBit fcc = FCC0); + + void ma_call(ImmPtr dest); + + void ma_jump(ImmPtr dest); + + void ma_cmp_set(Register dst, Register lhs, Register rhs, Condition c); + void ma_cmp_set(Register dst, Register lhs, Imm32 imm, Condition c); + void ma_cmp_set_double(Register dst, FloatRegister lhs, FloatRegister rhs, DoubleCondition c); + void ma_cmp_set_float32(Register dst, FloatRegister lhs, FloatRegister rhs, DoubleCondition c); + + void moveToDoubleLo(Register src, FloatRegister dest) { + as_mtc1(src, dest); + } + void moveFromDoubleLo(FloatRegister src, Register dest) { + as_mfc1(dest, src); + } + + void moveToFloat32(Register src, FloatRegister dest) { + as_mtc1(src, dest); + } + void moveFromFloat32(FloatRegister src, Register dest) { + as_mfc1(dest, src); + } +}; + +} // namespace jit +} // namespace js + +#endif /* jit_mips_shared_MacroAssembler_mips_shared_h */ diff --git a/js/src/jit/mips-shared/MoveEmitter-mips-shared.h b/js/src/jit/mips-shared/MoveEmitter-mips-shared.h index 0b32da549e..7535b087f1 100644 --- a/js/src/jit/mips-shared/MoveEmitter-mips-shared.h +++ b/js/src/jit/mips-shared/MoveEmitter-mips-shared.h @@ -37,7 +37,7 @@ class MoveEmitterMIPSShared void assertDone(); Register tempReg(); FloatRegister tempFloatReg(); - Address cycleSlot(uint32_t slot, uint32_t subslot) const; + Address cycleSlot(uint32_t slot, uint32_t subslot = 0) const; int32_t getAdjustedOffset(const MoveOperand& operand); Address getAdjustedAddress(const MoveOperand& operand); diff --git a/js/src/jit/mips32/MacroAssembler-mips32-inl.h b/js/src/jit/mips32/MacroAssembler-mips32-inl.h index e2faa4a518..2b1030d893 100644 --- a/js/src/jit/mips32/MacroAssembler-mips32-inl.h +++ b/js/src/jit/mips32/MacroAssembler-mips32-inl.h @@ -9,6 +9,8 @@ #include "jit/mips32/MacroAssembler-mips32.h" +#include "jit/mips-shared/MacroAssembler-mips-shared-inl.h" + namespace js { namespace jit { @@ -16,39 +18,6 @@ namespace jit { // =============================================================== // Logical instructions -void -MacroAssembler::not32(Register reg) -{ - ma_not(reg, reg); -} - -void -MacroAssembler::and32(Register src, Register dest) -{ - as_and(dest, dest, src); -} - -void -MacroAssembler::and32(Imm32 imm, Register dest) -{ - ma_and(dest, imm); -} - -void -MacroAssembler::and32(Imm32 imm, const Address& dest) -{ - load32(dest, SecondScratchReg); - ma_and(SecondScratchReg, imm); - store32(SecondScratchReg, dest); -} - -void -MacroAssembler::and32(const Address& src, Register dest) -{ - load32(src, SecondScratchReg); - ma_and(dest, SecondScratchReg); -} - void MacroAssembler::andPtr(Register src, Register dest) { @@ -68,26 +37,6 @@ MacroAssembler::and64(Imm64 imm, Register64 dest) and32(Imm32((imm.value >> 32) & LOW_32_MASK), dest.high); } -void -MacroAssembler::or32(Register src, Register dest) -{ - ma_or(dest, src); -} - -void -MacroAssembler::or32(Imm32 imm, Register dest) -{ - ma_or(dest, imm); -} - -void -MacroAssembler::or32(Imm32 imm, const Address& dest) -{ - load32(dest, SecondScratchReg); - ma_or(SecondScratchReg, imm); - store32(SecondScratchReg, dest); -} - void MacroAssembler::orPtr(Register src, Register dest) { @@ -107,12 +56,6 @@ MacroAssembler::or64(Register64 src, Register64 dest) or32(src.high, dest.high); } -void -MacroAssembler::xor32(Imm32 imm, Register dest) -{ - ma_xor(dest, imm); -} - void MacroAssembler::xorPtr(Register src, Register dest) { diff --git a/js/src/jit/mips32/MacroAssembler-mips32.cpp b/js/src/jit/mips32/MacroAssembler-mips32.cpp index f83f8d3879..9ffee05b3a 100644 --- a/js/src/jit/mips32/MacroAssembler-mips32.cpp +++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp @@ -311,19 +311,6 @@ MacroAssemblerMIPSCompat::inc64(AbsoluteAddress dest) as_sw(SecondScratchReg, ScratchRegister, 4); } -void -MacroAssemblerMIPS::ma_move(Register rd, Register rs) -{ - as_or(rd, rs, zero); -} - -void -MacroAssemblerMIPS::ma_li(Register dest, ImmGCPtr ptr) -{ - writeDataRelocation(ptr); - ma_liPatchable(dest, ImmPtr(ptr.value)); -} - void MacroAssemblerMIPS::ma_li(Register dest, AbsoluteLabel* label) { @@ -335,21 +322,6 @@ MacroAssemblerMIPS::ma_li(Register dest, AbsoluteLabel* label) label->setPrev(bo.getOffset()); } -void -MacroAssemblerMIPS::ma_li(Register dest, Imm32 imm) -{ - if (Imm16::IsInSignedRange(imm.value)) { - as_addiu(dest, zero, imm.value); - } else if (Imm16::IsInUnsignedRange(imm.value)) { - as_ori(dest, zero, Imm16::Lower(imm).encode()); - } else if (Imm16::Lower(imm).encode() == 0) { - as_lui(dest, Imm16::Upper(imm).encode()); - } else { - as_lui(dest, Imm16::Upper(imm).encode()); - as_ori(dest, dest, Imm16::Lower(imm).encode()); - } -} - void MacroAssemblerMIPS::ma_li(Register dest, ImmWord imm) { @@ -379,177 +351,9 @@ MacroAssemblerMIPS::ma_liPatchable(Register dest, ImmWord imm) ma_liPatchable(dest, Imm32(int32_t(imm.value))); } -// Shifts -void -MacroAssemblerMIPS::ma_sll(Register rd, Register rt, Imm32 shift) -{ - as_sll(rd, rt, shift.value % 32); -} -void -MacroAssemblerMIPS::ma_srl(Register rd, Register rt, Imm32 shift) -{ - as_srl(rd, rt, shift.value % 32); -} - -void -MacroAssemblerMIPS::ma_sra(Register rd, Register rt, Imm32 shift) -{ - as_sra(rd, rt, shift.value % 32); -} - -void -MacroAssemblerMIPS::ma_ror(Register rd, Register rt, Imm32 shift) -{ - as_rotr(rd, rt, shift.value % 32); -} - -void -MacroAssemblerMIPS::ma_rol(Register rd, Register rt, Imm32 shift) -{ - as_rotr(rd, rt, 32 - (shift.value % 32)); -} - -void -MacroAssemblerMIPS::ma_sll(Register rd, Register rt, Register shift) -{ - as_sllv(rd, rt, shift); -} - -void -MacroAssemblerMIPS::ma_srl(Register rd, Register rt, Register shift) -{ - as_srlv(rd, rt, shift); -} - -void -MacroAssemblerMIPS::ma_sra(Register rd, Register rt, Register shift) -{ - as_srav(rd, rt, shift); -} - -void -MacroAssemblerMIPS::ma_ror(Register rd, Register rt, Register shift) -{ - as_rotrv(rd, rt, shift); -} - -void -MacroAssemblerMIPS::ma_rol(Register rd, Register rt, Register shift) -{ - ma_negu(ScratchRegister, shift); - as_rotrv(rd, rt, ScratchRegister); -} - -void -MacroAssemblerMIPS::ma_negu(Register rd, Register rs) -{ - as_subu(rd, zero, rs); -} - -void -MacroAssemblerMIPS::ma_not(Register rd, Register rs) -{ - as_nor(rd, rs, zero); -} - -// And. -void -MacroAssemblerMIPS::ma_and(Register rd, Register rs) -{ - as_and(rd, rd, rs); -} - -void -MacroAssemblerMIPS::ma_and(Register rd, Imm32 imm) -{ - ma_and(rd, rd, imm); -} - -void -MacroAssemblerMIPS::ma_and(Register rd, Register rs, Imm32 imm) -{ - if (Imm16::IsInUnsignedRange(imm.value)) { - as_andi(rd, rs, imm.value); - } else { - ma_li(ScratchRegister, imm); - as_and(rd, rs, ScratchRegister); - } -} - -// Or. -void -MacroAssemblerMIPS::ma_or(Register rd, Register rs) -{ - as_or(rd, rd, rs); -} - -void -MacroAssemblerMIPS::ma_or(Register rd, Imm32 imm) -{ - ma_or(rd, rd, imm); -} - -void -MacroAssemblerMIPS::ma_or(Register rd, Register rs, Imm32 imm) -{ - if (Imm16::IsInUnsignedRange(imm.value)) { - as_ori(rd, rs, imm.value); - } else { - ma_li(ScratchRegister, imm); - as_or(rd, rs, ScratchRegister); - } -} - -// xor -void -MacroAssemblerMIPS::ma_xor(Register rd, Register rs) -{ - as_xor(rd, rd, rs); -} - -void -MacroAssemblerMIPS::ma_xor(Register rd, Imm32 imm) -{ - ma_xor(rd, rd, imm); -} - -void -MacroAssemblerMIPS::ma_xor(Register rd, Register rs, Imm32 imm) -{ - if (Imm16::IsInUnsignedRange(imm.value)) { - as_xori(rd, rs, imm.value); - } else { - ma_li(ScratchRegister, imm); - as_xor(rd, rs, ScratchRegister); - } -} - // Arithmetic-based ops. // Add. -void -MacroAssemblerMIPS::ma_addu(Register rd, Register rs, Imm32 imm) -{ - if (Imm16::IsInSignedRange(imm.value)) { - as_addiu(rd, rs, imm.value); - } else { - ma_li(ScratchRegister, imm); - as_addu(rd, rs, ScratchRegister); - } -} - -void -MacroAssemblerMIPS::ma_addu(Register rd, Register rs) -{ - as_addu(rd, rd, rs); -} - -void -MacroAssemblerMIPS::ma_addu(Register rd, Imm32 imm) -{ - ma_addu(rd, rd, imm); -} - void MacroAssemblerMIPS::ma_addTestOverflow(Register rd, Register rs, Register rt, Label* overflow) { @@ -591,23 +395,6 @@ MacroAssemblerMIPS::ma_addTestOverflow(Register rd, Register rs, Imm32 imm, Labe } // Subtract. -void -MacroAssemblerMIPS::ma_subu(Register rd, Register rs, Imm32 imm) -{ - if (Imm16::IsInSignedRange(-imm.value)) { - as_addiu(rd, rs, -imm.value); - } else { - ma_li(ScratchRegister, imm); - as_subu(rd, rs, ScratchRegister); - } -} - -void -MacroAssemblerMIPS::ma_subu(Register rd, Imm32 imm) -{ - ma_subu(rd, rd, imm); -} - void MacroAssemblerMIPS::ma_subTestOverflow(Register rd, Register rs, Register rt, Label* overflow) { @@ -626,131 +413,6 @@ MacroAssemblerMIPS::ma_subTestOverflow(Register rd, Register rs, Register rt, La bind(&goodSubtraction); } -void -MacroAssemblerMIPS::ma_subTestOverflow(Register rd, Register rs, Imm32 imm, Label* overflow) -{ - if (imm.value != INT32_MIN) { - ma_addTestOverflow(rd, rs, Imm32(-imm.value), overflow); - } else { - ma_li(ScratchRegister, Imm32(imm.value)); - ma_subTestOverflow(rd, rs, ScratchRegister, overflow); - } -} - -void -MacroAssemblerMIPS::ma_mult(Register rs, Imm32 imm) -{ - ma_li(ScratchRegister, imm); - as_mult(rs, ScratchRegister); -} - -void -MacroAssemblerMIPS::ma_mul_branch_overflow(Register rd, Register rs, Register rt, Label* overflow) -{ - as_mult(rs, rt); - as_mflo(rd); - as_sra(ScratchRegister, rd, 31); - as_mfhi(SecondScratchReg); - ma_b(ScratchRegister, SecondScratchReg, overflow, Assembler::NotEqual); -} - -void -MacroAssemblerMIPS::ma_mul_branch_overflow(Register rd, Register rs, Imm32 imm, Label* overflow) -{ - ma_li(ScratchRegister, imm); - ma_mul_branch_overflow(rd, rs, ScratchRegister, overflow); -} - -void -MacroAssemblerMIPS::ma_div_branch_overflow(Register rd, Register rs, Register rt, Label* overflow) -{ - as_div(rs, rt); - as_mflo(rd); - as_mfhi(ScratchRegister); - ma_b(ScratchRegister, ScratchRegister, overflow, Assembler::NonZero); -} - -void -MacroAssemblerMIPS::ma_div_branch_overflow(Register rd, Register rs, Imm32 imm, Label* overflow) -{ - ma_li(ScratchRegister, imm); - ma_div_branch_overflow(rd, rs, ScratchRegister, overflow); -} - -void -MacroAssemblerMIPS::ma_mod_mask(Register src, Register dest, Register hold, Register remain, - int32_t shift, Label* negZero) -{ - // MATH: - // We wish to compute x % (1< 0, store sum - C back into sum, thus performing a - // modulus. - ma_b(SecondScratchReg, SecondScratchReg, &sumSigned, Signed, ShortJump); - ma_move(dest, SecondScratchReg); - bind(&sumSigned); - // Get rid of the bits that we extracted before. - as_srl(remain, remain, shift); - // If the shift produced zero, finish, otherwise, continue in the loop. - ma_b(remain, remain, &head, NonZero, ShortJump); - // Check the hold to see if we need to negate the result. - ma_b(hold, hold, &done, NotSigned, ShortJump); - - // If the hold was non-zero, negate the result to be in line with - // what JS wants - if (negZero != nullptr) { - // Jump out in case of negative zero. - ma_b(hold, hold, negZero, Zero); - ma_negu(dest, dest); - } else { - ma_negu(dest, dest); - } - - bind(&done); -} - // Memory. void @@ -790,14 +452,6 @@ MacroAssemblerMIPS::ma_load(Register dest, Address address, } } -void -MacroAssemblerMIPS::ma_load(Register dest, const BaseIndex& src, - LoadStoreSize size, LoadStoreExtension extension) -{ - asMasm().computeScaledAddress(src, SecondScratchReg); - ma_load(dest, Address(SecondScratchReg, src.offset), size, extension); -} - void MacroAssemblerMIPS::ma_store(Register data, Address address, LoadStoreSize size, LoadStoreExtension extension) @@ -829,30 +483,6 @@ MacroAssemblerMIPS::ma_store(Register data, Address address, LoadStoreSize size, } } -void -MacroAssemblerMIPS::ma_store(Register data, const BaseIndex& dest, - LoadStoreSize size, LoadStoreExtension extension) -{ - asMasm().computeScaledAddress(dest, SecondScratchReg); - ma_store(data, Address(SecondScratchReg, dest.offset), size, extension); -} - -void -MacroAssemblerMIPS::ma_store(Imm32 imm, const BaseIndex& dest, - LoadStoreSize size, LoadStoreExtension extension) -{ - // Make sure that SecondScratchReg contains absolute address so that - // offset is 0. - asMasm().computeEffectiveAddress(dest, SecondScratchReg); - - // Scrach register is free now, use it for loading imm value - ma_li(ScratchRegister, imm); - - // with offset=0 ScratchRegister will not be used in ma_store() - // so we can use it as a parameter here - ma_store(ScratchRegister, Address(SecondScratchReg, 0), size, extension); -} - void MacroAssemblerMIPSCompat::computeScaledAddress(const BaseIndex& address, Register dest) { @@ -922,49 +552,6 @@ MacroAssemblerMIPS::ma_push(Register r) } // Branches when done from within mips-specific code. -void -MacroAssemblerMIPS::ma_b(Register lhs, Register rhs, Label* label, Condition c, JumpKind jumpKind) -{ - switch (c) { - case Equal : - case NotEqual: - branchWithCode(getBranchCode(lhs, rhs, c), label, jumpKind); - break; - case Always: - ma_b(label, jumpKind); - break; - case Zero: - case NonZero: - case Signed: - case NotSigned: - MOZ_ASSERT(lhs == rhs); - branchWithCode(getBranchCode(lhs, c), label, jumpKind); - break; - default: - Condition cond = ma_cmp(ScratchRegister, lhs, rhs, c); - branchWithCode(getBranchCode(ScratchRegister, cond), label, jumpKind); - break; - } -} - -void -MacroAssemblerMIPS::ma_b(Register lhs, Imm32 imm, Label* label, Condition c, JumpKind jumpKind) -{ - MOZ_ASSERT(c != Overflow); - if (imm.value == 0) { - if (c == Always || c == AboveOrEqual) - ma_b(label, jumpKind); - else if (c == Below) - ; // This condition is always false. No branch required. - else - branchWithCode(getBranchCode(lhs, c), label, jumpKind); - } else { - MOZ_ASSERT(lhs != ScratchRegister); - ma_li(ScratchRegister, imm); - ma_b(lhs, ScratchRegister, label, c, jumpKind); - } -} - void MacroAssemblerMIPS::ma_b(Register lhs, Address addr, Label* label, Condition c, JumpKind jumpKind) { @@ -987,12 +574,6 @@ MacroAssemblerMIPS::ma_b(Address addr, ImmGCPtr imm, Label* label, Condition c, ma_b(SecondScratchReg, imm, label, c, jumpKind); } -void -MacroAssemblerMIPS::ma_b(Label* label, JumpKind jumpKind) -{ - branchWithCode(getBranchCode(BranchIsJump), label, jumpKind); -} - void MacroAssemblerMIPS::ma_bal(Label* label, DelaySlotFill delaySlotFill) { @@ -1093,276 +674,6 @@ MacroAssemblerMIPS::branchWithCode(InstImm code, Label* label, JumpKind jumpKind as_nop(); } -Assembler::Condition -MacroAssemblerMIPS::ma_cmp(Register scratch, Register lhs, Register rhs, Condition c) -{ - switch (c) { - case Above: - // bgtu s,t,label => - // sltu at,t,s - // bne at,$zero,offs - as_sltu(scratch, rhs, lhs); - return NotEqual; - case AboveOrEqual: - // bgeu s,t,label => - // sltu at,s,t - // beq at,$zero,offs - as_sltu(scratch, lhs, rhs); - return Equal; - case Below: - // bltu s,t,label => - // sltu at,s,t - // bne at,$zero,offs - as_sltu(scratch, lhs, rhs); - return NotEqual; - case BelowOrEqual: - // bleu s,t,label => - // sltu at,t,s - // beq at,$zero,offs - as_sltu(scratch, rhs, lhs); - return Equal; - case GreaterThan: - // bgt s,t,label => - // slt at,t,s - // bne at,$zero,offs - as_slt(scratch, rhs, lhs); - return NotEqual; - case GreaterThanOrEqual: - // bge s,t,label => - // slt at,s,t - // beq at,$zero,offs - as_slt(scratch, lhs, rhs); - return Equal; - case LessThan: - // blt s,t,label => - // slt at,s,t - // bne at,$zero,offs - as_slt(scratch, lhs, rhs); - return NotEqual; - case LessThanOrEqual: - // ble s,t,label => - // slt at,t,s - // beq at,$zero,offs - as_slt(scratch, rhs, lhs); - return Equal; - case Equal : - case NotEqual: - case Zero: - case NonZero: - case Always: - case Signed: - case NotSigned: - MOZ_CRASH("There is a better way to compare for equality."); - break; - case Overflow: - MOZ_CRASH("Overflow condition not supported for MIPS."); - break; - default: - MOZ_CRASH("Invalid condition for branch."); - } - return Always; -} - -void -MacroAssemblerMIPS::ma_cmp_set(Register rd, Register rs, Register rt, Condition c) -{ - switch (c) { - case Equal : - // seq d,s,t => - // xor d,s,t - // sltiu d,d,1 - as_xor(rd, rs, rt); - as_sltiu(rd, rd, 1); - break; - case NotEqual: - // sne d,s,t => - // xor d,s,t - // sltu d,$zero,d - as_xor(rd, rs, rt); - as_sltu(rd, zero, rd); - break; - case Above: - // sgtu d,s,t => - // sltu d,t,s - as_sltu(rd, rt, rs); - break; - case AboveOrEqual: - // sgeu d,s,t => - // sltu d,s,t - // xori d,d,1 - as_sltu(rd, rs, rt); - as_xori(rd, rd, 1); - break; - case Below: - // sltu d,s,t - as_sltu(rd, rs, rt); - break; - case BelowOrEqual: - // sleu d,s,t => - // sltu d,t,s - // xori d,d,1 - as_sltu(rd, rt, rs); - as_xori(rd, rd, 1); - break; - case GreaterThan: - // sgt d,s,t => - // slt d,t,s - as_slt(rd, rt, rs); - break; - case GreaterThanOrEqual: - // sge d,s,t => - // slt d,s,t - // xori d,d,1 - as_slt(rd, rs, rt); - as_xori(rd, rd, 1); - break; - case LessThan: - // slt d,s,t - as_slt(rd, rs, rt); - break; - case LessThanOrEqual: - // sle d,s,t => - // slt d,t,s - // xori d,d,1 - as_slt(rd, rt, rs); - as_xori(rd, rd, 1); - break; - case Zero: - MOZ_ASSERT(rs == rt); - // seq d,s,$zero => - // xor d,s,$zero - // sltiu d,d,1 - as_xor(rd, rs, zero); - as_sltiu(rd, rd, 1); - break; - case NonZero: - // sne d,s,$zero => - // xor d,s,$zero - // sltu d,$zero,d - as_xor(rd, rs, zero); - as_sltu(rd, zero, rd); - break; - case Signed: - as_slt(rd, rs, zero); - break; - case NotSigned: - // sge d,s,$zero => - // slt d,s,$zero - // xori d,d,1 - as_slt(rd, rs, zero); - as_xori(rd, rd, 1); - break; - default: - MOZ_CRASH("Invalid condition for ma_cmp_set."); - } -} - -void -MacroAssemblerMIPS::compareFloatingPoint(FloatFormat fmt, FloatRegister lhs, FloatRegister rhs, - DoubleCondition c, FloatTestKind* testKind, - FPConditionBit fcc) -{ - switch (c) { - case DoubleOrdered: - as_cun(fmt, lhs, rhs, fcc); - *testKind = TestForFalse; - break; - case DoubleEqual: - as_ceq(fmt, lhs, rhs, fcc); - *testKind = TestForTrue; - break; - case DoubleNotEqual: - as_cueq(fmt, lhs, rhs, fcc); - *testKind = TestForFalse; - break; - case DoubleGreaterThan: - as_colt(fmt, rhs, lhs, fcc); - *testKind = TestForTrue; - break; - case DoubleGreaterThanOrEqual: - as_cole(fmt, rhs, lhs, fcc); - *testKind = TestForTrue; - break; - case DoubleLessThan: - as_colt(fmt, lhs, rhs, fcc); - *testKind = TestForTrue; - break; - case DoubleLessThanOrEqual: - as_cole(fmt, lhs, rhs, fcc); - *testKind = TestForTrue; - break; - case DoubleUnordered: - as_cun(fmt, lhs, rhs, fcc); - *testKind = TestForTrue; - break; - case DoubleEqualOrUnordered: - as_cueq(fmt, lhs, rhs, fcc); - *testKind = TestForTrue; - break; - case DoubleNotEqualOrUnordered: - as_ceq(fmt, lhs, rhs, fcc); - *testKind = TestForFalse; - break; - case DoubleGreaterThanOrUnordered: - as_cult(fmt, rhs, lhs, fcc); - *testKind = TestForTrue; - break; - case DoubleGreaterThanOrEqualOrUnordered: - as_cule(fmt, rhs, lhs, fcc); - *testKind = TestForTrue; - break; - case DoubleLessThanOrUnordered: - as_cult(fmt, lhs, rhs, fcc); - *testKind = TestForTrue; - break; - case DoubleLessThanOrEqualOrUnordered: - as_cule(fmt, lhs, rhs, fcc); - *testKind = TestForTrue; - break; - default: - MOZ_CRASH("Invalid DoubleCondition."); - } -} - -void -MacroAssemblerMIPS::ma_cmp_set_double(Register dest, FloatRegister lhs, FloatRegister rhs, - DoubleCondition c) -{ - ma_li(dest, Imm32(0)); - ma_li(ScratchRegister, Imm32(1)); - - FloatTestKind moveCondition; - compareFloatingPoint(DoubleFloat, lhs, rhs, c, &moveCondition); - - if (moveCondition == TestForTrue) - as_movt(dest, ScratchRegister); - else - as_movf(dest, ScratchRegister); -} - -void -MacroAssemblerMIPS::ma_cmp_set_float32(Register dest, FloatRegister lhs, FloatRegister rhs, - DoubleCondition c) -{ - ma_li(dest, Imm32(0)); - ma_li(ScratchRegister, Imm32(1)); - - FloatTestKind moveCondition; - compareFloatingPoint(SingleFloat, lhs, rhs, c, &moveCondition); - - if (moveCondition == TestForTrue) - as_movt(dest, ScratchRegister); - else - as_movf(dest, ScratchRegister); -} - -void -MacroAssemblerMIPS::ma_cmp_set(Register rd, Register rs, Imm32 imm, Condition c) -{ - ma_li(ScratchRegister, imm); - ma_cmp_set(rd, rs, ScratchRegister, c); -} - void MacroAssemblerMIPS::ma_cmp_set(Register rd, Register rs, Address addr, Condition c) { @@ -1378,15 +689,6 @@ MacroAssemblerMIPS::ma_cmp_set(Register dst, Address lhs, Register rhs, Conditio } // fp instructions -void -MacroAssemblerMIPS::ma_lis(FloatRegister dest, float value) -{ - Imm32 imm(mozilla::BitwiseCast(value)); - - ma_li(ScratchRegister, imm); - moveToFloat32(ScratchRegister, dest); -} - void MacroAssemblerMIPS::ma_lid(FloatRegister dest, double value) { @@ -1413,14 +715,6 @@ MacroAssemblerMIPS::ma_lid(FloatRegister dest, double value) } } -void -MacroAssemblerMIPS::ma_liNegZero(FloatRegister dest) -{ - moveToDoubleLo(zero, dest); - ma_li(ScratchRegister, Imm32(INT_MIN)); - moveToDoubleHi(ScratchRegister, dest); -} - void MacroAssemblerMIPS::ma_mv(FloatRegister src, ValueOperand dest) { @@ -1481,13 +775,6 @@ MacroAssemblerMIPS::ma_sd(FloatRegister ft, Address address) } } -void -MacroAssemblerMIPS::ma_sd(FloatRegister ft, BaseIndex address) -{ - asMasm().computeScaledAddress(address, SecondScratchReg); - ma_sd(ft, Address(SecondScratchReg, address.offset)); -} - void MacroAssemblerMIPS::ma_ss(FloatRegister ft, Address address) { @@ -1500,13 +787,6 @@ MacroAssemblerMIPS::ma_ss(FloatRegister ft, Address address) } } -void -MacroAssemblerMIPS::ma_ss(FloatRegister ft, BaseIndex address) -{ - asMasm().computeScaledAddress(address, SecondScratchReg); - ma_ss(ft, Address(SecondScratchReg, address.offset)); -} - void MacroAssemblerMIPS::ma_pop(FloatRegister fs) { @@ -1521,24 +801,6 @@ MacroAssemblerMIPS::ma_push(FloatRegister fs) ma_sd(fs.doubleOverlay(0), Address(StackPointer, 0)); } -void -MacroAssemblerMIPS::ma_bc1s(FloatRegister lhs, FloatRegister rhs, Label* label, - DoubleCondition c, JumpKind jumpKind, FPConditionBit fcc) -{ - FloatTestKind testKind; - compareFloatingPoint(SingleFloat, lhs, rhs, c, &testKind, fcc); - branchWithCode(getBranchCode(testKind, fcc), label, jumpKind); -} - -void -MacroAssemblerMIPS::ma_bc1d(FloatRegister lhs, FloatRegister rhs, Label* label, - DoubleCondition c, JumpKind jumpKind, FPConditionBit fcc) -{ - FloatTestKind testKind; - compareFloatingPoint(DoubleFloat, lhs, rhs, c, &testKind, fcc); - branchWithCode(getBranchCode(testKind, fcc), label, jumpKind); -} - bool MacroAssemblerMIPSCompat::buildOOLFakeExitFrame(void* fakeReturnAddr) { @@ -2996,22 +2258,6 @@ MacroAssemblerMIPSCompat::storeTypeTag(ImmTag tag, const BaseIndex& dest) as_sw(ScratchRegister, SecondScratchReg, TAG_OFFSET); } -void -MacroAssemblerMIPS::ma_call(ImmPtr dest) -{ - ma_liPatchable(CallReg, dest); - as_jalr(CallReg); - as_nop(); -} - -void -MacroAssemblerMIPS::ma_jump(ImmPtr dest) -{ - ma_liPatchable(ScratchRegister, dest); - as_jr(ScratchRegister); - as_nop(); -} - void MacroAssemblerMIPSCompat::breakpoint() { @@ -3247,18 +2493,6 @@ MacroAssemblerMIPSCompat::profilerExitFrame() branch(GetJitContext()->runtime->jitRuntime()->getProfilerExitFrameTail()); } -MacroAssembler& -MacroAssemblerMIPS::asMasm() -{ - return *static_cast(this); -} - -const MacroAssembler& -MacroAssemblerMIPS::asMasm() const -{ - return *static_cast(this); -} - //{{{ check_macroassembler_style // =============================================================== // Stack manipulation functions. @@ -3320,64 +2554,6 @@ MacroAssembler::PopRegsInMaskIgnore(LiveRegisterSet set, LiveRegisterSet ignore) MOZ_ASSERT(diffG == 0); } -void -MacroAssembler::Push(Register reg) -{ - ma_push(reg); - adjustFrame(sizeof(intptr_t)); -} - -void -MacroAssembler::Push(const Imm32 imm) -{ - ma_li(ScratchRegister, imm); - ma_push(ScratchRegister); - adjustFrame(sizeof(intptr_t)); -} - -void -MacroAssembler::Push(const ImmWord imm) -{ - ma_li(ScratchRegister, imm); - ma_push(ScratchRegister); - adjustFrame(sizeof(intptr_t)); -} - -void -MacroAssembler::Push(const ImmPtr imm) -{ - Push(ImmWord(uintptr_t(imm.value))); -} - -void -MacroAssembler::Push(const ImmGCPtr ptr) -{ - ma_li(ScratchRegister, ptr); - ma_push(ScratchRegister); - adjustFrame(sizeof(intptr_t)); -} - -void -MacroAssembler::Push(FloatRegister f) -{ - ma_push(f); - adjustFrame(sizeof(double)); -} - -void -MacroAssembler::Pop(Register reg) -{ - ma_pop(reg); - adjustFrame(-sizeof(intptr_t)); -} - -void -MacroAssembler::Pop(const ValueOperand& val) -{ - popValue(val); - framePushed_ -= sizeof(Value); -} - void MacroAssembler::reserveStack(uint32_t amount) { @@ -3386,70 +2562,6 @@ MacroAssembler::reserveStack(uint32_t amount) adjustFrame(amount); } -// =============================================================== -// Simple call functions. - -void -MacroAssembler::call(Register reg) -{ - as_jalr(reg); - as_nop(); -} - -void -MacroAssembler::call(Label* label) -{ - ma_bal(label); -} - -void -MacroAssembler::call(AsmJSImmPtr target) -{ - movePtr(target, CallReg); - call(CallReg); -} - -void -MacroAssembler::call(ImmWord target) -{ - call(ImmPtr((void*)target.value)); -} - -void -MacroAssembler::call(ImmPtr target) -{ - BufferOffset bo = m_buffer.nextOffset(); - addPendingJump(bo, target, Relocation::HARDCODED); - ma_call(target); -} - -void -MacroAssembler::call(JitCode* c) -{ - BufferOffset bo = m_buffer.nextOffset(); - addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE); - ma_liPatchable(ScratchRegister, ImmPtr(c->raw())); - callJitNoProfiler(ScratchRegister); -} - -void -MacroAssembler::callAndPushReturnAddress(Register callee) -{ - // Push return address during jalr delay slot. - subPtr(Imm32(sizeof(intptr_t)), StackPointer); - as_jalr(callee); - storePtr(ra, Address(StackPointer, 0)); -} - -void -MacroAssembler::callAndPushReturnAddress(Label* label) -{ - // Push return address during bal delay slot. - subPtr(Imm32(sizeof(intptr_t)), StackPointer); - ma_bal(label, DontFillDelaySlot); - storePtr(ra, Address(StackPointer, 0)); -} - // =============================================================== // ABI function calls. @@ -3551,21 +2663,4 @@ MacroAssembler::callWithABINoProfiler(const Address& fun, MoveOp::Type result) callWithABIPost(stackAdjust, result); } -// =============================================================== -// Jit Frames. - -uint32_t -MacroAssembler::pushFakeReturnAddress(Register scratch) -{ - CodeLabel cl; - - ma_li(scratch, cl.dest()); - Push(scratch); - bind(cl.src()); - uint32_t retAddr = currentOffset(); - - addCodeLabel(cl); - return retAddr; -} - //}}} check_macroassembler_style diff --git a/js/src/jit/mips32/MacroAssembler-mips32.h b/js/src/jit/mips32/MacroAssembler-mips32.h index 7bc35741bf..7428699599 100644 --- a/js/src/jit/mips32/MacroAssembler-mips32.h +++ b/js/src/jit/mips32/MacroAssembler-mips32.h @@ -12,38 +12,12 @@ #include "jit/AtomicOp.h" #include "jit/IonCaches.h" #include "jit/JitFrames.h" -#include "jit/mips32/Assembler-mips32.h" +#include "jit/mips-shared/MacroAssembler-mips-shared.h" #include "jit/MoveResolver.h" namespace js { namespace jit { -enum LoadStoreSize -{ - SizeByte = 8, - SizeHalfWord = 16, - SizeWord = 32, - SizeDouble = 64 -}; - -enum LoadStoreExtension -{ - ZeroExtend = 0, - SignExtend = 1 -}; - -enum JumpKind -{ - LongJump = 0, - ShortJump = 1 -}; - -enum DelaySlotFill -{ - DontFillDelaySlot = 0, - FillDelaySlot = 1 -}; - struct ImmTag : public Imm32 { ImmTag(JSValueTag mask) @@ -61,7 +35,6 @@ struct ImmType : public ImmTag static const ValueOperand JSReturnOperand = ValueOperand(JSReturnReg_Type, JSReturnReg_Data); static const ValueOperand softfpReturnOperand = ValueOperand(v1, v0); -static Register CallReg = t9; static const int defaultShift = 3; static_assert(1 << defaultShift == sizeof(JS::Value), "The defaultShift is wrong"); @@ -69,107 +42,40 @@ static const uint32_t LOW_32_MASK = (1LL << 32) - 1; static const int32_t LOW_32_OFFSET = 0; static const int32_t HIGH_32_OFFSET = 4; -class MacroAssemblerMIPS : public Assembler +class MacroAssemblerMIPS : public MacroAssemblerMIPSShared { - protected: - // Perform a downcast. Should be removed by Bug 996602. - MacroAssembler& asMasm(); - const MacroAssembler& asMasm() const; - - void branchWithCode(InstImm code, Label* label, JumpKind jumpKind); - Condition ma_cmp(Register rd, Register lhs, Register rhs, Condition c); - - void compareFloatingPoint(FloatFormat fmt, FloatRegister lhs, FloatRegister rhs, - DoubleCondition c, FloatTestKind* testKind, - FPConditionBit fcc = FCC0); - public: - void ma_move(Register rd, Register rs); - - void ma_li(Register dest, ImmGCPtr ptr); + using MacroAssemblerMIPSShared::ma_b; + using MacroAssemblerMIPSShared::ma_li; + using MacroAssemblerMIPSShared::ma_ss; + using MacroAssemblerMIPSShared::ma_sd; + using MacroAssemblerMIPSShared::ma_load; + using MacroAssemblerMIPSShared::ma_store; + using MacroAssemblerMIPSShared::ma_cmp_set; + using MacroAssemblerMIPSShared::ma_subTestOverflow; void ma_li(Register dest, AbsoluteLabel* label); - void ma_li(Register dest, Imm32 imm); void ma_liPatchable(Register dest, Imm32 imm); void ma_li(Register dest, ImmWord imm); void ma_liPatchable(Register dest, ImmPtr imm); void ma_liPatchable(Register dest, ImmWord imm); - // Shift operations - void ma_sll(Register rd, Register rt, Imm32 shift); - void ma_srl(Register rd, Register rt, Imm32 shift); - void ma_sra(Register rd, Register rt, Imm32 shift); - void ma_ror(Register rd, Register rt, Imm32 shift); - void ma_rol(Register rd, Register rt, Imm32 shift); - - void ma_sll(Register rd, Register rt, Register shift); - void ma_srl(Register rd, Register rt, Register shift); - void ma_sra(Register rd, Register rt, Register shift); - void ma_ror(Register rd, Register rt, Register shift); - void ma_rol(Register rd, Register rt, Register shift); - - // Negate - void ma_negu(Register rd, Register rs); - - void ma_not(Register rd, Register rs); - - // and - void ma_and(Register rd, Register rs); - void ma_and(Register rd, Imm32 imm); - void ma_and(Register rd, Register rs, Imm32 imm); - - // or - void ma_or(Register rd, Register rs); - void ma_or(Register rd, Imm32 imm); - void ma_or(Register rd, Register rs, Imm32 imm); - - // xor - void ma_xor(Register rd, Register rs); - void ma_xor(Register rd, Imm32 imm); - void ma_xor(Register rd, Register rs, Imm32 imm); - // load void ma_load(Register dest, Address address, LoadStoreSize size = SizeWord, LoadStoreExtension extension = SignExtend); - void ma_load(Register dest, const BaseIndex& src, LoadStoreSize size = SizeWord, - LoadStoreExtension extension = SignExtend); // store void ma_store(Register data, Address address, LoadStoreSize size = SizeWord, LoadStoreExtension extension = SignExtend); - void ma_store(Register data, const BaseIndex& dest, LoadStoreSize size = SizeWord, - LoadStoreExtension extension = SignExtend); - void ma_store(Imm32 imm, const BaseIndex& dest, LoadStoreSize size = SizeWord, - LoadStoreExtension extension = SignExtend); // arithmetic based ops // add - void ma_addu(Register rd, Register rs, Imm32 imm); - void ma_addu(Register rd, Register rs); - void ma_addu(Register rd, Imm32 imm); void ma_addTestOverflow(Register rd, Register rs, Register rt, Label* overflow); void ma_addTestOverflow(Register rd, Register rs, Imm32 imm, Label* overflow); // subtract - void ma_subu(Register rd, Register rs, Imm32 imm); - void ma_subu(Register rd, Imm32 imm); void ma_subTestOverflow(Register rd, Register rs, Register rt, Label* overflow); - void ma_subTestOverflow(Register rd, Register rs, Imm32 imm, Label* overflow); - - // multiplies. For now, there are only few that we care about. - void ma_mult(Register rs, Imm32 imm); - void ma_mul_branch_overflow(Register rd, Register rs, Register rt, Label* overflow); - void ma_mul_branch_overflow(Register rd, Register rs, Imm32 imm, Label* overflow); - - // divisions - void ma_div_branch_overflow(Register rd, Register rs, Register rt, Label* overflow); - void ma_div_branch_overflow(Register rd, Register rs, Imm32 imm, Label* overflow); - - // fast mod, uses scratch registers, and thus needs to be in the assembler - // implicitly assumes that we can overwrite dest at the beginning of the sequence - void ma_mod_mask(Register src, Register dest, Register hold, Register remain, - int32_t shift, Label* negZero = nullptr); // memory // shortcut for when we know we're transferring 32 bits of data @@ -182,17 +88,8 @@ class MacroAssemblerMIPS : public Assembler void ma_pop(Register r); void ma_push(Register r); + void branchWithCode(InstImm code, Label* label, JumpKind jumpKind); // branches when done from within mips-specific code - void ma_b(Register lhs, Register rhs, Label* l, Condition c, JumpKind jumpKind = LongJump); - void ma_b(Register lhs, Imm32 imm, Label* l, Condition c, JumpKind jumpKind = LongJump); - void ma_b(Register lhs, ImmPtr imm, Label* l, Condition c, JumpKind jumpKind = LongJump) { - ma_b(lhs, ImmWord(uintptr_t(imm.value)), l, c, jumpKind); - } - void ma_b(Register lhs, ImmGCPtr imm, Label* l, Condition c, JumpKind jumpKind = LongJump) { - MOZ_ASSERT(lhs != ScratchRegister); - ma_li(ScratchRegister, imm); - ma_b(lhs, ScratchRegister, l, c, jumpKind); - } void ma_b(Register lhs, ImmWord imm, Label* l, Condition c, JumpKind jumpKind = LongJump) { ma_b(lhs, Imm32(uint32_t(imm.value)), l, c, jumpKind); @@ -211,13 +108,10 @@ class MacroAssemblerMIPS : public Assembler ma_b(ScratchRegister, rhs, l, c, jumpKind); } - void ma_b(Label* l, JumpKind jumpKind = LongJump); void ma_bal(Label* l, DelaySlotFill delaySlotFill = FillDelaySlot); // fp instructions - void ma_lis(FloatRegister dest, float value); void ma_lid(FloatRegister dest, double value); - void ma_liNegZero(FloatRegister dest); void ma_mv(FloatRegister src, ValueOperand dest); void ma_mv(ValueOperand src, FloatRegister dest); @@ -225,25 +119,11 @@ class MacroAssemblerMIPS : public Assembler void ma_ls(FloatRegister fd, Address address); void ma_ld(FloatRegister fd, Address address); void ma_sd(FloatRegister fd, Address address); - void ma_sd(FloatRegister fd, BaseIndex address); void ma_ss(FloatRegister fd, Address address); - void ma_ss(FloatRegister fd, BaseIndex address); void ma_pop(FloatRegister fs); void ma_push(FloatRegister fs); - //FP branches - void ma_bc1s(FloatRegister lhs, FloatRegister rhs, Label* label, DoubleCondition c, - JumpKind jumpKind = LongJump, FPConditionBit fcc = FCC0); - void ma_bc1d(FloatRegister lhs, FloatRegister rhs, Label* label, DoubleCondition c, - JumpKind jumpKind = LongJump, FPConditionBit fcc = FCC0); - - void ma_call(ImmPtr dest); - - void ma_jump(ImmPtr dest); - - void ma_cmp_set(Register dst, Register lhs, Register rhs, Condition c); - void ma_cmp_set(Register dst, Register lhs, Imm32 imm, Condition c); void ma_cmp_set(Register dst, Register lhs, ImmPtr imm, Condition c) { ma_cmp_set(dst, lhs, Imm32(uint32_t(imm.value)), c); } @@ -254,8 +134,6 @@ class MacroAssemblerMIPS : public Assembler ma_li(SecondScratchReg, Imm32(uint32_t(imm.value))); ma_cmp_set(dst, ScratchRegister, SecondScratchReg, c); } - void ma_cmp_set_double(Register dst, FloatRegister lhs, FloatRegister rhs, DoubleCondition c); - void ma_cmp_set_float32(Register dst, FloatRegister lhs, FloatRegister rhs, DoubleCondition c); // These fuctions abstract the access to high part of the double precision // float register. It is intended to work on both 32 bit and 64 bit @@ -267,20 +145,6 @@ class MacroAssemblerMIPS : public Assembler void moveFromDoubleHi(FloatRegister src, Register dest) { as_mfc1(dest, getOddPair(src)); } - - void moveToDoubleLo(Register src, FloatRegister dest) { - as_mtc1(src, dest); - } - void moveFromDoubleLo(FloatRegister src, Register dest) { - as_mfc1(dest, src); - } - - void moveToFloat32(Register src, FloatRegister dest) { - as_mtc1(src, dest); - } - void moveFromFloat32(FloatRegister src, Register dest) { - as_mfc1(dest, src); - } }; class MacroAssembler; diff --git a/js/src/jit/mips64/Bailouts-mips64.cpp b/js/src/jit/mips64/Bailouts-mips64.cpp new file mode 100644 index 0000000000..022560a442 --- /dev/null +++ b/js/src/jit/mips64/Bailouts-mips64.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jscntxt.h" +#include "jscompartment.h" + +#include "jit/mips-shared/Bailouts-mips-shared.h" + +using namespace js; +using namespace js::jit; + +BailoutFrameInfo::BailoutFrameInfo(const JitActivationIterator& activations, + BailoutStack* bailout) + : machine_(bailout->machine()) +{ + uint8_t* sp = bailout->parentStackPointer(); + framePointer_ = sp + bailout->frameSize(); + topFrameSize_ = framePointer_ - sp; + + JSScript* script = ScriptFromCalleeToken(((JitFrameLayout*) framePointer_)->calleeToken()); + topIonScript_ = script->ionScript(); + + attachOnJitActivation(activations); + snapshotOffset_ = bailout->snapshotOffset(); +} diff --git a/js/src/jit/mips64/BaselineCompiler-mips64.cpp b/js/src/jit/mips64/BaselineCompiler-mips64.cpp new file mode 100644 index 0000000000..72535bf1e9 --- /dev/null +++ b/js/src/jit/mips64/BaselineCompiler-mips64.cpp @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jit/mips64/BaselineCompiler-mips64.h" + +using namespace js; +using namespace js::jit; + +BaselineCompilerMIPS64::BaselineCompilerMIPS64(JSContext* cx, TempAllocator& alloc, + JSScript* script) + : BaselineCompilerMIPSShared(cx, alloc, script) +{ +} diff --git a/js/src/jit/mips64/BaselineCompiler-mips64.h b/js/src/jit/mips64/BaselineCompiler-mips64.h new file mode 100644 index 0000000000..b06fdbf7ac --- /dev/null +++ b/js/src/jit/mips64/BaselineCompiler-mips64.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_BaselineCompiler_mips64_h +#define jit_mips64_BaselineCompiler_mips64_h + +#include "jit/mips-shared/BaselineCompiler-mips-shared.h" + +namespace js { +namespace jit { + +class BaselineCompilerMIPS64 : public BaselineCompilerMIPSShared +{ + protected: + BaselineCompilerMIPS64(JSContext* cx, TempAllocator& alloc, JSScript* script); +}; + +typedef BaselineCompilerMIPS64 BaselineCompilerSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_mips64_BaselineCompiler_mips64_h */ diff --git a/js/src/jit/mips64/BaselineIC-mips64.cpp b/js/src/jit/mips64/BaselineIC-mips64.cpp new file mode 100644 index 0000000000..5c0e6d0b71 --- /dev/null +++ b/js/src/jit/mips64/BaselineIC-mips64.cpp @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jit/BaselineCompiler.h" +#include "jit/BaselineIC.h" +#include "jit/BaselineJIT.h" +#include "jit/Linker.h" +#include "jit/SharedICHelpers.h" + +using namespace js; +using namespace js::jit; + +namespace js { +namespace jit { + +// ICCompare_Int32 + +bool +ICCompare_Int32::Compiler::generateStubCode(MacroAssembler& masm) +{ + // Guard that R0 is an integer and R1 is an integer. + Label failure; + Label conditionTrue; + masm.branchTestInt32(Assembler::NotEqual, R0, &failure); + masm.branchTestInt32(Assembler::NotEqual, R1, &failure); + + // Compare payload regs of R0 and R1. + masm.unboxInt32(R0, ExtractTemp0); + masm.unboxInt32(R1, ExtractTemp1); + Assembler::Condition cond = JSOpToCondition(op, /* signed = */true); + masm.ma_cmp_set(R0.valueReg(), ExtractTemp0, ExtractTemp1, cond); + + masm.tagValue(JSVAL_TYPE_BOOLEAN, R0.valueReg(), R0); + EmitReturnFromIC(masm); + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + + return true; +} + +} // namespace jit +} // namespace js diff --git a/js/src/jit/mips64/CodeGenerator-mips64.cpp b/js/src/jit/mips64/CodeGenerator-mips64.cpp new file mode 100644 index 0000000000..5efe8572da --- /dev/null +++ b/js/src/jit/mips64/CodeGenerator-mips64.cpp @@ -0,0 +1,282 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jit/mips64/CodeGenerator-mips64.h" + +#include "mozilla/MathAlgorithms.h" + +#include "jit/CodeGenerator.h" +#include "jit/JitCompartment.h" +#include "jit/JitFrames.h" +#include "jit/MIR.h" +#include "jit/MIRGraph.h" +#include "js/Conversions.h" +#include "vm/Shape.h" +#include "vm/TraceLogging.h" + +#include "jit/MacroAssembler-inl.h" +#include "jit/shared/CodeGenerator-shared-inl.h" + +using namespace js; +using namespace js::jit; + +class js::jit::OutOfLineTableSwitch : public OutOfLineCodeBase +{ + MTableSwitch* mir_; + CodeLabel jumpLabel_; + + void accept(CodeGeneratorMIPS64* codegen) { + codegen->visitOutOfLineTableSwitch(this); + } + + public: + OutOfLineTableSwitch(MTableSwitch* mir) + : mir_(mir) + {} + + MTableSwitch* mir() const { + return mir_; + } + + CodeLabel* jumpLabel() { + return &jumpLabel_; + } +}; + +void +CodeGeneratorMIPS64::visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool) +{ + MTableSwitch* mir = ool->mir(); + + masm.haltingAlign(sizeof(void*)); + masm.bind(ool->jumpLabel()->src()); + masm.addCodeLabel(*ool->jumpLabel()); + + for (size_t i = 0; i < mir->numCases(); i++) { + LBlock* caseblock = skipTrivialBlocks(mir->getCase(i))->lir(); + Label* caseheader = caseblock->label(); + uint32_t caseoffset = caseheader->offset(); + + // The entries of the jump table need to be absolute addresses and thus + // must be patched after codegen is finished. Each table entry uses 8 + // instructions (4 for load address, 2 for branch, and 2 padding). + CodeLabel cl; + masm.ma_li(ScratchRegister, cl.dest()); + masm.branch(ScratchRegister); + masm.as_nop(); + masm.as_nop(); + cl.src()->bind(caseoffset); + masm.addCodeLabel(cl); + } +} + +void +CodeGeneratorMIPS64::emitTableSwitchDispatch(MTableSwitch* mir, Register index, + Register address) +{ + Label* defaultcase = skipTrivialBlocks(mir->getDefault())->lir()->label(); + + // Lower value with low value + if (mir->low() != 0) + masm.subPtr(Imm32(mir->low()), index); + + // Jump to default case if input is out of range + int32_t cases = mir->numCases(); + masm.branch32(Assembler::AboveOrEqual, index, Imm32(cases), defaultcase); + + // To fill in the CodeLabels for the case entries, we need to first + // generate the case entries (we don't yet know their offsets in the + // instruction stream). + OutOfLineTableSwitch* ool = new(alloc()) OutOfLineTableSwitch(mir); + addOutOfLineCode(ool, mir); + + // Compute the position where a pointer to the right case stands. + masm.ma_li(address, ool->jumpLabel()->dest()); + // index = size of table entry * index. + // See CodeGeneratorMIPS64::visitOutOfLineTableSwitch + masm.lshiftPtr(Imm32(5), index); + masm.addPtr(index, address); + + masm.branch(address); +} + +FrameSizeClass +FrameSizeClass::FromDepth(uint32_t frameDepth) +{ + return FrameSizeClass::None(); +} + +FrameSizeClass +FrameSizeClass::ClassLimit() +{ + return FrameSizeClass(0); +} + +uint32_t +FrameSizeClass::frameSize() const +{ + MOZ_CRASH("MIPS64 does not use frame size classes"); +} + +ValueOperand +CodeGeneratorMIPS64::ToValue(LInstruction* ins, size_t pos) +{ + return ValueOperand(ToRegister(ins->getOperand(pos))); +} + +ValueOperand +CodeGeneratorMIPS64::ToOutValue(LInstruction* ins) +{ + return ValueOperand(ToRegister(ins->getDef(0))); +} + +ValueOperand +CodeGeneratorMIPS64::ToTempValue(LInstruction* ins, size_t pos) +{ + return ValueOperand(ToRegister(ins->getTemp(pos))); +} + +void +CodeGeneratorMIPS64::visitBox(LBox* box) +{ + const LAllocation* in = box->getOperand(0); + const LDefinition* result = box->getDef(0); + + if (IsFloatingPointType(box->type())) { + FloatRegister reg = ToFloatRegister(in); + if (box->type() == MIRType_Float32) { + masm.convertFloat32ToDouble(reg, ScratchDoubleReg); + reg = ScratchDoubleReg; + } + masm.moveFromDouble(reg, ToRegister(result)); + } else { + masm.boxValue(ValueTypeFromMIRType(box->type()), ToRegister(in), ToRegister(result)); + } +} + +void +CodeGeneratorMIPS64::visitUnbox(LUnbox* unbox) +{ + MUnbox* mir = unbox->mir(); + + if (mir->fallible()) { + const ValueOperand value = ToValue(unbox, LUnbox::Input); + masm.splitTag(value, SecondScratchReg); + bailoutCmp32(Assembler::NotEqual, SecondScratchReg, Imm32(MIRTypeToTag(mir->type())), + unbox->snapshot()); + } + + Operand input = ToOperand(unbox->getOperand(LUnbox::Input)); + Register result = ToRegister(unbox->output()); + switch (mir->type()) { + case MIRType_Int32: + masm.unboxInt32(input, result); + break; + case MIRType_Boolean: + masm.unboxBoolean(input, result); + break; + case MIRType_Object: + masm.unboxObject(input, result); + break; + case MIRType_String: + masm.unboxString(input, result); + break; + case MIRType_Symbol: + masm.unboxSymbol(input, result); + break; + default: + MOZ_CRASH("Given MIRType cannot be unboxed."); + } +} + +Register +CodeGeneratorMIPS64::splitTagForTest(const ValueOperand& value) +{ + MOZ_ASSERT(value.valueReg() != SecondScratchReg); + masm.splitTag(value.valueReg(), SecondScratchReg); + return SecondScratchReg; +} + +void +CodeGeneratorMIPS64::visitCompareB(LCompareB* lir) +{ + MCompare* mir = lir->mir(); + + const ValueOperand lhs = ToValue(lir, LCompareB::Lhs); + const LAllocation* rhs = lir->rhs(); + const Register output = ToRegister(lir->output()); + + MOZ_ASSERT(mir->jsop() == JSOP_STRICTEQ || mir->jsop() == JSOP_STRICTNE); + Assembler::Condition cond = JSOpToCondition(mir->compareType(), mir->jsop()); + + // Load boxed boolean in ScratchRegister. + if (rhs->isConstant()) + masm.moveValue(*rhs->toConstant(), ScratchRegister); + else + masm.boxValue(JSVAL_TYPE_BOOLEAN, ToRegister(rhs), ScratchRegister); + + // Perform the comparison. + masm.cmpPtrSet(cond, lhs.valueReg(), ScratchRegister, output); +} + +void +CodeGeneratorMIPS64::visitCompareBAndBranch(LCompareBAndBranch* lir) +{ + MCompare* mir = lir->cmpMir(); + const ValueOperand lhs = ToValue(lir, LCompareBAndBranch::Lhs); + const LAllocation* rhs = lir->rhs(); + + MOZ_ASSERT(mir->jsop() == JSOP_STRICTEQ || mir->jsop() == JSOP_STRICTNE); + + // Load boxed boolean in ScratchRegister. + if (rhs->isConstant()) + masm.moveValue(*rhs->toConstant(), ScratchRegister); + else + masm.boxValue(JSVAL_TYPE_BOOLEAN, ToRegister(rhs), ScratchRegister); + + // Perform the comparison. + Assembler::Condition cond = JSOpToCondition(mir->compareType(), mir->jsop()); + emitBranch(lhs.valueReg(), ScratchRegister, cond, lir->ifTrue(), lir->ifFalse()); +} + +void +CodeGeneratorMIPS64::visitCompareBitwise(LCompareBitwise* lir) +{ + MCompare* mir = lir->mir(); + Assembler::Condition cond = JSOpToCondition(mir->compareType(), mir->jsop()); + const ValueOperand lhs = ToValue(lir, LCompareBitwise::LhsInput); + const ValueOperand rhs = ToValue(lir, LCompareBitwise::RhsInput); + const Register output = ToRegister(lir->output()); + + MOZ_ASSERT(IsEqualityOp(mir->jsop())); + + masm.cmpPtrSet(cond, lhs.valueReg(), rhs.valueReg(), output); +} + +void +CodeGeneratorMIPS64::visitCompareBitwiseAndBranch(LCompareBitwiseAndBranch* lir) +{ + MCompare* mir = lir->cmpMir(); + Assembler::Condition cond = JSOpToCondition(mir->compareType(), mir->jsop()); + const ValueOperand lhs = ToValue(lir, LCompareBitwiseAndBranch::LhsInput); + const ValueOperand rhs = ToValue(lir, LCompareBitwiseAndBranch::RhsInput); + + MOZ_ASSERT(mir->jsop() == JSOP_EQ || mir->jsop() == JSOP_STRICTEQ || + mir->jsop() == JSOP_NE || mir->jsop() == JSOP_STRICTNE); + + emitBranch(lhs.valueReg(), rhs.valueReg(), cond, lir->ifTrue(), lir->ifFalse()); +} + +void +CodeGeneratorMIPS64::setReturnDoubleRegs(LiveRegisterSet* regs) +{ + MOZ_ASSERT(ReturnFloat32Reg.reg_ == FloatRegisters::f0); + MOZ_ASSERT(ReturnDoubleReg.reg_ == FloatRegisters::f0); + FloatRegister f1 = { FloatRegisters::f1, FloatRegisters::Single }; + regs->add(ReturnFloat32Reg); + regs->add(f1); + regs->add(ReturnDoubleReg); +} diff --git a/js/src/jit/mips64/CodeGenerator-mips64.h b/js/src/jit/mips64/CodeGenerator-mips64.h new file mode 100644 index 0000000000..e08fc2ca18 --- /dev/null +++ b/js/src/jit/mips64/CodeGenerator-mips64.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_CodeGenerator_mips64_h +#define jit_mips64_CodeGenerator_mips64_h + +#include "jit/mips-shared/CodeGenerator-mips-shared.h" + +namespace js { +namespace jit { + +class CodeGeneratorMIPS64 : public CodeGeneratorMIPSShared +{ + protected: + void testNullEmitBranch(Assembler::Condition cond, const ValueOperand& value, + MBasicBlock* ifTrue, MBasicBlock* ifFalse) + { + MOZ_ASSERT(value.valueReg() != SecondScratchReg); + masm.splitTag(value.valueReg(), SecondScratchReg); + emitBranch(SecondScratchReg, ImmTag(JSVAL_TAG_NULL), cond, ifTrue, ifFalse); + } + void testUndefinedEmitBranch(Assembler::Condition cond, const ValueOperand& value, + MBasicBlock* ifTrue, MBasicBlock* ifFalse) + { + MOZ_ASSERT(value.valueReg() != SecondScratchReg); + masm.splitTag(value.valueReg(), SecondScratchReg); + emitBranch(SecondScratchReg, ImmTag(JSVAL_TAG_UNDEFINED), cond, ifTrue, ifFalse); + } + void testObjectEmitBranch(Assembler::Condition cond, const ValueOperand& value, + MBasicBlock* ifTrue, MBasicBlock* ifFalse) + { + MOZ_ASSERT(value.valueReg() != SecondScratchReg); + masm.splitTag(value.valueReg(), SecondScratchReg); + emitBranch(SecondScratchReg, ImmTag(JSVAL_TAG_OBJECT), cond, ifTrue, ifFalse); + } + + void emitTableSwitchDispatch(MTableSwitch* mir, Register index, Register base); + + public: + virtual void visitCompareB(LCompareB* lir); + virtual void visitCompareBAndBranch(LCompareBAndBranch* lir); + virtual void visitCompareBitwise(LCompareBitwise* lir); + virtual void visitCompareBitwiseAndBranch(LCompareBitwiseAndBranch* lir); + + // Out of line visitors. + void visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool); + protected: + ValueOperand ToValue(LInstruction* ins, size_t pos); + ValueOperand ToOutValue(LInstruction* ins); + ValueOperand ToTempValue(LInstruction* ins, size_t pos); + + // Functions for LTestVAndBranch. + Register splitTagForTest(const ValueOperand& value); + + public: + CodeGeneratorMIPS64(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm) + : CodeGeneratorMIPSShared(gen, graph, masm) + { } + + public: + void visitBox(LBox* box); + void visitUnbox(LUnbox* unbox); + + void setReturnDoubleRegs(LiveRegisterSet* regs); +}; + +typedef CodeGeneratorMIPS64 CodeGeneratorSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_mips64_CodeGenerator_mips64_h */ diff --git a/js/src/jit/mips64/LIR-mips64.h b/js/src/jit/mips64/LIR-mips64.h new file mode 100644 index 0000000000..ffc4d45cc1 --- /dev/null +++ b/js/src/jit/mips64/LIR-mips64.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_LIR_mips64_h +#define jit_mips64_LIR_mips64_h + +namespace js { +namespace jit { + +class LUnbox : public LInstructionHelper<1, 1, 0> +{ + public: + LIR_HEADER(Unbox); + + explicit LUnbox(const LAllocation& input) { + setOperand(0, input); + } + + static const size_t Input = 0; + + MUnbox* mir() const { + return mir_->toUnbox(); + } + const char* extraName() const { + return StringFromMIRType(mir()->type()); + } +}; + +class LUnboxFloatingPoint : public LUnbox +{ + MIRType type_; + + public: + LIR_HEADER(UnboxFloatingPoint); + + LUnboxFloatingPoint(const LAllocation& input, MIRType type) + : LUnbox(input), + type_(type) + { } + + MIRType type() const { + return type_; + } +}; + +} // namespace jit +} // namespace js + +#endif /* jit_mips64_LIR_mips64_h */ diff --git a/js/src/jit/mips64/LOpcodes-mips64.h b/js/src/jit/mips64/LOpcodes-mips64.h new file mode 100644 index 0000000000..0a873e7f24 --- /dev/null +++ b/js/src/jit/mips64/LOpcodes-mips64.h @@ -0,0 +1,16 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_LOpcodes_mips64_h__ +#define jit_mips64_LOpcodes_mips64_h__ + +#include "jit/shared/LOpcodes-shared.h" + +#define LIR_CPU_OPCODE_LIST(_) \ + _(ModMaskI) \ + _(UDivOrMod) + +#endif // jit_mips64_LOpcodes_mips64_h__ diff --git a/js/src/jit/mips64/Lowering-mips64.cpp b/js/src/jit/mips64/Lowering-mips64.cpp new file mode 100644 index 0000000000..f407bfbee7 --- /dev/null +++ b/js/src/jit/mips64/Lowering-mips64.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jit/mips64/Lowering-mips64.h" + +#include "jit/mips64/Assembler-mips64.h" + +#include "jit/MIR.h" + +#include "jit/shared/Lowering-shared-inl.h" + +using namespace js; +using namespace js::jit; + +void +LIRGeneratorMIPS64::useBoxFixed(LInstruction* lir, size_t n, MDefinition* mir, Register reg1, + Register reg2, bool useAtStart) +{ + MOZ_ASSERT(mir->type() == MIRType_Value); + + ensureDefined(mir); + lir->setOperand(n, LUse(reg1, mir->virtualRegister(), useAtStart)); +} + +void +LIRGeneratorMIPS64::visitBox(MBox* box) +{ + MDefinition* opd = box->getOperand(0); + + // If the operand is a constant, emit near its uses. + if (opd->isConstant() && box->canEmitAtUses()) { + emitAtUses(box); + return; + } + + if (opd->isConstant()) { + define(new(alloc()) LValue(opd->toConstant()->value()), box, LDefinition(LDefinition::BOX)); + } else { + LBox* ins = new(alloc()) LBox(useRegister(opd), opd->type()); + define(ins, box, LDefinition(LDefinition::BOX)); + } +} + +void +LIRGeneratorMIPS64::visitUnbox(MUnbox* unbox) +{ + MDefinition* box = unbox->getOperand(0); + + if (box->type() == MIRType_ObjectOrNull) { + LUnboxObjectOrNull* lir = new(alloc()) LUnboxObjectOrNull(useRegisterAtStart(box)); + if (unbox->fallible()) + assignSnapshot(lir, unbox->bailoutKind()); + defineReuseInput(lir, unbox, 0); + return; + } + + MOZ_ASSERT(box->type() == MIRType_Value); + + LUnbox* lir; + if (IsFloatingPointType(unbox->type())) { + lir = new(alloc()) LUnboxFloatingPoint(useRegisterAtStart(box), unbox->type()); + } else if (unbox->fallible()) { + // If the unbox is fallible, load the Value in a register first to + // avoid multiple loads. + lir = new(alloc()) LUnbox(useRegisterAtStart(box)); + } else { + lir = new(alloc()) LUnbox(useAtStart(box)); + } + + if (unbox->fallible()) + assignSnapshot(lir, unbox->bailoutKind()); + + define(lir, unbox); +} + +void +LIRGeneratorMIPS64::visitReturn(MReturn* ret) +{ + MDefinition* opd = ret->getOperand(0); + MOZ_ASSERT(opd->type() == MIRType_Value); + + LReturn* ins = new(alloc()) LReturn; + ins->setOperand(0, useFixed(opd, JSReturnReg)); + add(ins); +} + +void +LIRGeneratorMIPS64::defineUntypedPhi(MPhi* phi, size_t lirIndex) +{ + defineTypedPhi(phi, lirIndex); +} + +void +LIRGeneratorMIPS64::lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, + LBlock* block, size_t lirIndex) +{ + lowerTypedPhiInput(phi, inputPosition, block, lirIndex); +} + +void +LIRGeneratorMIPS64::lowerTruncateDToInt32(MTruncateToInt32* ins) +{ + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType_Double); + + define(new(alloc()) + LTruncateDToInt32(useRegister(opd), tempDouble()), ins); +} + +void +LIRGeneratorMIPS64::lowerTruncateFToInt32(MTruncateToInt32* ins) +{ + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType_Float32); + + define(new(alloc()) + LTruncateFToInt32(useRegister(opd), tempFloat32()), ins); +} + +void +LIRGeneratorMIPS64::visitRandom(MRandom* ins) +{ + LRandom *lir = new(alloc()) LRandom(temp(), temp(), temp()); + defineFixed(lir, ins, LFloatReg(ReturnDoubleReg)); +} diff --git a/js/src/jit/mips64/Lowering-mips64.h b/js/src/jit/mips64/Lowering-mips64.h new file mode 100644 index 0000000000..77eba96ab1 --- /dev/null +++ b/js/src/jit/mips64/Lowering-mips64.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_Lowering_mips64_h +#define jit_mips64_Lowering_mips64_h + +#include "jit/mips-shared/Lowering-mips-shared.h" + +namespace js { +namespace jit { + +class LIRGeneratorMIPS64 : public LIRGeneratorMIPSShared +{ + protected: + LIRGeneratorMIPS64(MIRGenerator* gen, MIRGraph& graph, LIRGraph& lirGraph) + : LIRGeneratorMIPSShared(gen, graph, lirGraph) + { } + + protected: + // Adds a box input to an instruction, setting operand |n| to the type and + // |n+1| to the payload. + void useBoxFixed(LInstruction* lir, size_t n, MDefinition* mir, Register reg1, Register reg2, + bool useAtStart = false); + + inline LDefinition tempToUnbox() { + return temp(); + } + + void lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, size_t lirIndex); + void defineUntypedPhi(MPhi* phi, size_t lirIndex); + + void lowerTruncateDToInt32(MTruncateToInt32* ins); + void lowerTruncateFToInt32(MTruncateToInt32* ins); + + public: + void visitBox(MBox* box); + void visitUnbox(MUnbox* unbox); + void visitReturn(MReturn* ret); + void visitRandom(MRandom* ins); +}; + +typedef LIRGeneratorMIPS64 LIRGeneratorSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_mips64_Lowering_mips64_h */ diff --git a/js/src/jit/mips64/MacroAssembler-mips64-inl.h b/js/src/jit/mips64/MacroAssembler-mips64-inl.h new file mode 100644 index 0000000000..166e25a3da --- /dev/null +++ b/js/src/jit/mips64/MacroAssembler-mips64-inl.h @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_MacroAssembler_mips64_inl_h +#define jit_mips64_MacroAssembler_mips64_inl_h + +#include "jit/mips64/MacroAssembler-mips64.h" + +#include "jit/mips-shared/MacroAssembler-mips-shared-inl.h" + +namespace js { +namespace jit { + +//{{{ check_macroassembler_style +// =============================================================== +// Logical instructions + +void +MacroAssembler::andPtr(Register src, Register dest) +{ + ma_and(dest, src); +} + +void +MacroAssembler::andPtr(Imm32 imm, Register dest) +{ + ma_and(dest, imm); +} + +void +MacroAssembler::and64(Imm64 imm, Register64 dest) +{ + ma_li(ScratchRegister, ImmWord(imm.value)); + ma_and(dest.reg, ScratchRegister); +} + +void +MacroAssembler::orPtr(Register src, Register dest) +{ + ma_or(dest, src); +} + +void +MacroAssembler::orPtr(Imm32 imm, Register dest) +{ + ma_or(dest, imm); +} + +void +MacroAssembler::or64(Register64 src, Register64 dest) +{ + ma_or(dest.reg, src.reg); +} + +void +MacroAssembler::xorPtr(Register src, Register dest) +{ + ma_xor(dest, src); +} + +void +MacroAssembler::xorPtr(Imm32 imm, Register dest) +{ + ma_xor(dest, imm); +} + +// =============================================================== +// Shift functions + +void +MacroAssembler::lshiftPtr(Imm32 imm, Register dest) +{ + ma_dsll(dest, dest, imm); +} + +void +MacroAssembler::lshift64(Imm32 imm, Register64 dest) +{ + ma_dsll(dest.reg, dest.reg, imm); +} + +void +MacroAssembler::rshiftPtr(Imm32 imm, Register dest) +{ + ma_dsrl(dest, dest, imm); +} + +void +MacroAssembler::rshiftPtrArithmetic(Imm32 imm, Register dest) +{ + ma_dsra(dest, dest, imm); +} + +void +MacroAssembler::rshift64(Imm32 imm, Register64 dest) +{ + ma_dsrl(dest.reg, dest.reg, imm); +} + +//}}} check_macroassembler_style +// =============================================================== + +} // namespace jit +} // namespace js + +#endif /* jit_mips64_MacroAssembler_mips64_inl_h */ diff --git a/js/src/jit/mips64/MacroAssembler-mips64.cpp b/js/src/jit/mips64/MacroAssembler-mips64.cpp new file mode 100644 index 0000000000..e52b56e310 --- /dev/null +++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp @@ -0,0 +1,2754 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jit/mips64/MacroAssembler-mips64.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/MathAlgorithms.h" + +#include "jit/Bailouts.h" +#include "jit/BaselineFrame.h" +#include "jit/JitFrames.h" +#include "jit/MacroAssembler.h" +#include "jit/mips64/Simulator-mips64.h" +#include "jit/MoveEmitter.h" +#include "jit/SharedICRegisters.h" + +#include "jit/MacroAssembler-inl.h" + +using namespace js; +using namespace jit; + +using mozilla::Abs; + +static_assert(sizeof(intptr_t) == 8, "Not 32-bit clean."); + +void +MacroAssemblerMIPS64Compat::convertBoolToInt32(Register src, Register dest) +{ + // Note that C++ bool is only 1 byte, so zero extend it to clear the + // higher-order bits. + ma_and(dest, src, Imm32(0xff)); +} + +void +MacroAssemblerMIPS64Compat::convertInt32ToDouble(Register src, FloatRegister dest) +{ + as_mtc1(src, dest); + as_cvtdw(dest, dest); +} + +void +MacroAssemblerMIPS64Compat::convertInt32ToDouble(const Address& src, FloatRegister dest) +{ + ma_ls(dest, src); + as_cvtdw(dest, dest); +} + +void +MacroAssemblerMIPS64Compat::convertInt32ToDouble(const BaseIndex& src, FloatRegister dest) +{ + computeScaledAddress(src, ScratchRegister); + convertInt32ToDouble(Address(ScratchRegister, src.offset), dest); +} + +void +MacroAssemblerMIPS64Compat::convertUInt32ToDouble(Register src, FloatRegister dest) +{ + // We use SecondScratchDoubleReg because MacroAssembler::loadFromTypedArray + // calls with ScratchDoubleReg as dest. + MOZ_ASSERT(dest != SecondScratchDoubleReg); + + // Subtract INT32_MIN to get a positive number + ma_subu(ScratchRegister, src, Imm32(INT32_MIN)); + + // Convert value + as_mtc1(ScratchRegister, dest); + as_cvtdw(dest, dest); + + // Add unsigned value of INT32_MIN + ma_lid(SecondScratchDoubleReg, 2147483648.0); + as_addd(dest, dest, SecondScratchDoubleReg); +} + +void +MacroAssemblerMIPS64Compat::convertUInt64ToDouble(Register64 src, Register temp, FloatRegister dest) +{ + Label positive, done; + ma_b(src.reg, src.reg, &positive, NotSigned, ShortJump); + + MOZ_ASSERT(src.reg != ScratchRegister); + MOZ_ASSERT(src.reg != SecondScratchReg); + + ma_and(ScratchRegister, src.reg, Imm32(1)); + ma_dsrl(SecondScratchReg, src.reg, Imm32(1)); + ma_or(ScratchRegister, SecondScratchReg); + as_dmtc1(ScratchRegister, dest); + as_cvtdl(dest, dest); + addDouble(dest, dest); + ma_b(&done, ShortJump); + + bind(&positive); + as_dmtc1(src.reg, dest); + as_cvtdl(dest, dest); + + bind(&done); +} + +void +MacroAssemblerMIPS64Compat::convertUInt32ToFloat32(Register src, FloatRegister dest) +{ + Label positive, done; + ma_b(src, src, &positive, NotSigned, ShortJump); + + // We cannot do the same as convertUInt32ToDouble because float32 doesn't + // have enough precision. + convertUInt32ToDouble(src, dest); + convertDoubleToFloat32(dest, dest); + ma_b(&done, ShortJump); + + bind(&positive); + convertInt32ToFloat32(src, dest); + + bind(&done); +} + +void +MacroAssemblerMIPS64Compat::convertDoubleToFloat32(FloatRegister src, FloatRegister dest) +{ + as_cvtsd(dest, src); +} + +// Convert the floating point value to an integer, if it did not fit, then it +// was clamped to INT32_MIN/INT32_MAX, and we can test it. +// NOTE: if the value really was supposed to be INT32_MAX / INT32_MIN then it +// will be wrong. +void +MacroAssemblerMIPS64Compat::branchTruncateDouble(FloatRegister src, Register dest, + Label* fail) +{ + Label test, success; + as_truncwd(ScratchDoubleReg, src); + as_mfc1(dest, ScratchDoubleReg); + + ma_b(dest, Imm32(INT32_MAX), fail, Assembler::Equal); + ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal); +} + +// Checks whether a double is representable as a 32-bit integer. If so, the +// integer is written to the output register. Otherwise, a bailout is taken to +// the given snapshot. This function overwrites the scratch float register. +void +MacroAssemblerMIPS64Compat::convertDoubleToInt32(FloatRegister src, Register dest, + Label* fail, bool negativeZeroCheck) +{ + // Convert double to int, then convert back and check if we have the + // same number. + as_cvtwd(ScratchDoubleReg, src); + as_mfc1(dest, ScratchDoubleReg); + as_cvtdw(ScratchDoubleReg, ScratchDoubleReg); + ma_bc1d(src, ScratchDoubleReg, fail, Assembler::DoubleNotEqualOrUnordered); + + if (negativeZeroCheck) { + Label notZero; + ma_b(dest, Imm32(0), ¬Zero, Assembler::NotEqual, ShortJump); + // Test and bail for -0.0, when integer result is 0 + // Move the top word of the double into the output reg, if it is + // non-zero, then the original value was -0.0 + moveFromDoubleHi(src, dest); + ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal); + bind(¬Zero); + } +} + +// Checks whether a float32 is representable as a 32-bit integer. If so, the +// integer is written to the output register. Otherwise, a bailout is taken to +// the given snapshot. This function overwrites the scratch float register. +void +MacroAssemblerMIPS64Compat::convertFloat32ToInt32(FloatRegister src, Register dest, + Label* fail, bool negativeZeroCheck) +{ + // Converting the floating point value to an integer and then converting it + // back to a float32 would not work, as float to int32 conversions are + // clamping (e.g. float(INT32_MAX + 1) would get converted into INT32_MAX + // and then back to float(INT32_MAX + 1)). If this ever happens, we just + // bail out. + as_cvtws(ScratchFloat32Reg, src); + as_mfc1(dest, ScratchFloat32Reg); + as_cvtsw(ScratchFloat32Reg, ScratchFloat32Reg); + ma_bc1s(src, ScratchFloat32Reg, fail, Assembler::DoubleNotEqualOrUnordered); + + // Bail out in the clamped cases. + ma_b(dest, Imm32(INT32_MAX), fail, Assembler::Equal); + + if (negativeZeroCheck) { + Label notZero; + ma_b(dest, Imm32(0), ¬Zero, Assembler::NotEqual, ShortJump); + // Test and bail for -0.0, when integer result is 0 + // Move the top word of the double into the output reg, + // if it is non-zero, then the original value was -0.0 + moveFromDoubleHi(src, dest); + ma_b(dest, Imm32(INT32_MIN), fail, Assembler::Equal); + bind(¬Zero); + } +} + +void +MacroAssemblerMIPS64Compat::convertFloat32ToDouble(FloatRegister src, FloatRegister dest) +{ + as_cvtds(dest, src); +} + +void +MacroAssemblerMIPS64Compat::branchTruncateFloat32(FloatRegister src, Register dest, + Label* fail) +{ + Label test, success; + as_truncws(ScratchFloat32Reg, src); + as_mfc1(dest, ScratchFloat32Reg); + + ma_b(dest, Imm32(INT32_MAX), fail, Assembler::Equal); +} + +void +MacroAssemblerMIPS64Compat::convertInt32ToFloat32(Register src, FloatRegister dest) +{ + as_mtc1(src, dest); + as_cvtsw(dest, dest); +} + +void +MacroAssemblerMIPS64Compat::convertInt32ToFloat32(const Address& src, FloatRegister dest) +{ + ma_ls(dest, src); + as_cvtsw(dest, dest); +} + +void +MacroAssemblerMIPS64Compat::addDouble(FloatRegister src, FloatRegister dest) +{ + as_addd(dest, dest, src); +} + +void +MacroAssemblerMIPS64Compat::subDouble(FloatRegister src, FloatRegister dest) +{ + as_subd(dest, dest, src); +} + +void +MacroAssemblerMIPS64Compat::mulDouble(FloatRegister src, FloatRegister dest) +{ + as_muld(dest, dest, src); +} + +void +MacroAssemblerMIPS64Compat::divDouble(FloatRegister src, FloatRegister dest) +{ + as_divd(dest, dest, src); +} + +void +MacroAssemblerMIPS64Compat::negateDouble(FloatRegister reg) +{ + as_negd(reg, reg); +} + +void +MacroAssemblerMIPS64Compat::inc64(AbsoluteAddress dest) +{ + ma_li(ScratchRegister, ImmWord(uintptr_t(dest.addr))); + as_ld(SecondScratchReg, ScratchRegister, 0); + as_daddiu(SecondScratchReg, SecondScratchReg, 1); + as_sd(SecondScratchReg, ScratchRegister, 0); +} + +void +MacroAssemblerMIPS64Compat::movq(Register rs, Register rd) +{ + ma_move(rd, rs); +} + +void +MacroAssemblerMIPS64::ma_li(Register dest, AbsoluteLabel* label) +{ + MOZ_ASSERT(!label->bound()); + // Thread the patch list through the unpatched address word in the + // instruction stream. + BufferOffset bo = m_buffer.nextOffset(); + ma_liPatchable(dest, ImmWord(label->prev())); + label->setPrev(bo.getOffset()); +} + +void +MacroAssemblerMIPS64::ma_li(Register dest, ImmWord imm) +{ + if ((int64_t)imm.value >= INT16_MIN && (int64_t)imm.value <= INT16_MAX) { + as_addiu(dest, zero, imm.value); + } else if (imm.value <= UINT16_MAX) { + as_ori(dest, zero, Imm16::Lower(Imm32(imm.value)).encode()); + } else if (0 == (imm.value & 0xffff) && 0 == (imm.value >> 32)) { + as_lui(dest, Imm16::Upper(Imm32(imm.value)).encode()); + } else if (imm.value <= UINT32_MAX) { + as_lui(dest, Imm16::Upper(Imm32(imm.value)).encode()); + as_ori(dest, dest, Imm16::Lower(Imm32(imm.value)).encode()); + } else { + if (imm.value >> 48) { + as_lui(dest, Imm16::Upper(Imm32(imm.value >> 32)).encode()); + if ((imm.value >> 32) & 0xffff) + as_ori(dest, dest, Imm16::Lower(Imm32(imm.value >> 32)).encode()); + } else { + as_ori(dest, zero, Imm16::Lower(Imm32(imm.value >> 32)).encode()); + } + as_dsll(dest, dest, 16); + if ((imm.value >> 16) & 0xffff) + as_ori(dest, dest, Imm16::Upper(Imm32(imm.value)).encode()); + as_dsll(dest, dest, 16); + if (imm.value & 0xffff) + as_ori(dest, dest, Imm16::Lower(Imm32(imm.value)).encode()); + } +} + +// This method generates lui, dsll and ori instruction block that can be modified +// by UpdateLoad64Value, either during compilation (eg. Assembler::bind), or +// during execution (eg. jit::PatchJump). +void +MacroAssemblerMIPS64::ma_liPatchable(Register dest, ImmPtr imm) +{ + return ma_liPatchable(dest, ImmWord(uintptr_t(imm.value))); +} + +void +MacroAssemblerMIPS64::ma_liPatchable(Register dest, ImmWord imm, LiFlags flags) +{ + if (Li64 == flags) { + m_buffer.ensureSpace(6 * sizeof(uint32_t)); + as_lui(dest, Imm16::Upper(Imm32(imm.value >> 32)).encode()); + as_ori(dest, dest, Imm16::Lower(Imm32(imm.value >> 32)).encode()); + as_dsll(dest, dest, 16); + as_ori(dest, dest, Imm16::Upper(Imm32(imm.value)).encode()); + as_dsll(dest, dest, 16); + as_ori(dest, dest, Imm16::Lower(Imm32(imm.value)).encode()); + } else { + m_buffer.ensureSpace(4 * sizeof(uint32_t)); + as_lui(dest, Imm16::Lower(Imm32(imm.value >> 32)).encode()); + as_ori(dest, dest, Imm16::Upper(Imm32(imm.value)).encode()); + as_drotr32(dest, dest, 48); + as_ori(dest, dest, Imm16::Lower(Imm32(imm.value)).encode()); + } +} + +// Shifts +void +MacroAssemblerMIPS64::ma_dsll(Register rd, Register rt, Imm32 shift) +{ + if (31 < shift.value) + as_dsll32(rd, rt, shift.value); + else + as_dsll(rd, rt, shift.value); +} + +void +MacroAssemblerMIPS64::ma_dsrl(Register rd, Register rt, Imm32 shift) +{ + if (31 < shift.value) + as_dsrl32(rd, rt, shift.value); + else + as_dsrl(rd, rt, shift.value); +} + +void +MacroAssemblerMIPS64::ma_dsra(Register rd, Register rt, Imm32 shift) +{ + if (31 < shift.value) + as_dsra32(rd, rt, shift.value); + else + as_dsra(rd, rt, shift.value); +} + +void +MacroAssemblerMIPS64::ma_dror(Register rd, Register rt, Imm32 shift) +{ + if (31 < shift.value) + as_drotr32(rd, rt, shift.value); + else + as_drotr(rd, rt, shift.value); +} + +void +MacroAssemblerMIPS64::ma_drol(Register rd, Register rt, Imm32 shift) +{ + uint32_t s = 64 - shift.value; + + if (31 < s) + as_drotr32(rd, rt, s); + else + as_drotr(rd, rt, s); +} + +void +MacroAssemblerMIPS64::ma_dsll(Register rd, Register rt, Register shift) +{ + as_dsllv(rd, rt, shift); +} + +void +MacroAssemblerMIPS64::ma_dsrl(Register rd, Register rt, Register shift) +{ + as_dsrlv(rd, rt, shift); +} + +void +MacroAssemblerMIPS64::ma_dsra(Register rd, Register rt, Register shift) +{ + as_dsrav(rd, rt, shift); +} + +void +MacroAssemblerMIPS64::ma_dror(Register rd, Register rt, Register shift) +{ + as_drotrv(rd, rt, shift); +} + +void +MacroAssemblerMIPS64::ma_drol(Register rd, Register rt, Register shift) +{ + ma_negu(ScratchRegister, shift); + as_drotrv(rd, rt, ScratchRegister); +} + +void +MacroAssemblerMIPS64::ma_dins(Register rt, Register rs, Imm32 pos, Imm32 size) +{ + if (pos.value >= 0 && pos.value < 32) { + if (size.value >= 2) + as_dinsm(rt, rs, pos.value, size.value); + else + as_dins(rt, rs, pos.value, size.value); + } else { + as_dinsu(rt, rs, pos.value, size.value); + } +} + +void +MacroAssemblerMIPS64::ma_dext(Register rt, Register rs, Imm32 pos, Imm32 size) +{ + if (pos.value >= 0 && pos.value < 32) { + if (size.value > 32) + as_dextm(rt, rs, pos.value, size.value); + else + as_dext(rt, rs, pos.value, size.value); + } else { + as_dextu(rt, rs, pos.value, size.value); + } +} + +// Arithmetic-based ops. + +// Add. +void +MacroAssemblerMIPS64::ma_daddu(Register rd, Register rs, Imm32 imm) +{ + if (Imm16::IsInSignedRange(imm.value)) { + as_daddiu(rd, rs, imm.value); + } else { + ma_li(ScratchRegister, imm); + as_daddu(rd, rs, ScratchRegister); + } +} + +void +MacroAssemblerMIPS64::ma_daddu(Register rd, Register rs) +{ + as_daddu(rd, rd, rs); +} + +void +MacroAssemblerMIPS64::ma_daddu(Register rd, Imm32 imm) +{ + ma_daddu(rd, rd, imm); +} + +void +MacroAssemblerMIPS64::ma_addTestOverflow(Register rd, Register rs, Register rt, Label* overflow) +{ + as_addu(rd, rs, rt); + as_daddu(ScratchRegister, rs, rt); + ma_b(rd, ScratchRegister, overflow, Assembler::NotEqual); +} + +void +MacroAssemblerMIPS64::ma_addTestOverflow(Register rd, Register rs, Imm32 imm, Label* overflow) +{ + // Check for signed range because of as_daddiu + if (Imm16::IsInSignedRange(imm.value) && Imm16::IsInUnsignedRange(imm.value)) { + as_addiu(rd, rs, imm.value); + as_daddiu(ScratchRegister, rs, imm.value); + ma_b(rd, ScratchRegister, overflow, Assembler::NotEqual); + } else { + ma_li(ScratchRegister, imm); + ma_addTestOverflow(rd, rs, ScratchRegister, overflow); + } +} + +// Subtract. +void +MacroAssemblerMIPS64::ma_dsubu(Register rd, Register rs, Imm32 imm) +{ + if (Imm16::IsInSignedRange(-imm.value)) { + as_daddiu(rd, rs, -imm.value); + } else { + ma_li(ScratchRegister, imm); + as_dsubu(rd, rs, ScratchRegister); + } +} + +void +MacroAssemblerMIPS64::ma_dsubu(Register rd, Imm32 imm) +{ + ma_dsubu(rd, rd, imm); +} + +void +MacroAssemblerMIPS64::ma_subTestOverflow(Register rd, Register rs, Register rt, Label* overflow) +{ + as_subu(rd, rs, rt); + as_dsubu(ScratchRegister, rs, rt); + ma_b(rd, ScratchRegister, overflow, Assembler::NotEqual); +} + +void +MacroAssemblerMIPS64::ma_dmult(Register rs, Imm32 imm) +{ + ma_li(ScratchRegister, imm); + as_dmult(rs, ScratchRegister); +} + +// Memory. + +void +MacroAssemblerMIPS64::ma_load(Register dest, Address address, + LoadStoreSize size, LoadStoreExtension extension) +{ + int16_t encodedOffset; + Register base; + if (!Imm16::IsInSignedRange(address.offset)) { + ma_li(ScratchRegister, Imm32(address.offset)); + as_daddu(ScratchRegister, address.base, ScratchRegister); + base = ScratchRegister; + encodedOffset = Imm16(0).encode(); + } else { + encodedOffset = Imm16(address.offset).encode(); + base = address.base; + } + + switch (size) { + case SizeByte: + if (ZeroExtend == extension) + as_lbu(dest, base, encodedOffset); + else + as_lb(dest, base, encodedOffset); + break; + case SizeHalfWord: + if (ZeroExtend == extension) + as_lhu(dest, base, encodedOffset); + else + as_lh(dest, base, encodedOffset); + break; + case SizeWord: + if (ZeroExtend == extension) + as_lwu(dest, base, encodedOffset); + else + as_lw(dest, base, encodedOffset); + break; + case SizeDouble: + as_ld(dest, base, encodedOffset); + break; + default: + MOZ_CRASH("Invalid argument for ma_load"); + } +} + +void +MacroAssemblerMIPS64::ma_store(Register data, Address address, LoadStoreSize size, + LoadStoreExtension extension) +{ + int16_t encodedOffset; + Register base; + if (!Imm16::IsInSignedRange(address.offset)) { + ma_li(ScratchRegister, Imm32(address.offset)); + as_daddu(ScratchRegister, address.base, ScratchRegister); + base = ScratchRegister; + encodedOffset = Imm16(0).encode(); + } else { + encodedOffset = Imm16(address.offset).encode(); + base = address.base; + } + + switch (size) { + case SizeByte: + as_sb(data, base, encodedOffset); + break; + case SizeHalfWord: + as_sh(data, base, encodedOffset); + break; + case SizeWord: + as_sw(data, base, encodedOffset); + break; + case SizeDouble: + as_sd(data, base, encodedOffset); + break; + default: + MOZ_CRASH("Invalid argument for ma_store"); + } +} + +void +MacroAssemblerMIPS64Compat::computeScaledAddress(const BaseIndex& address, Register dest) +{ + int32_t shift = Imm32::ShiftOf(address.scale).value; + if (shift) { + ma_dsll(ScratchRegister, address.index, Imm32(shift)); + as_daddu(dest, address.base, ScratchRegister); + } else { + as_daddu(dest, address.base, address.index); + } +} + +// Shortcut for when we know we're transferring 32 bits of data. +void +MacroAssemblerMIPS64::ma_pop(Register r) +{ + as_ld(r, StackPointer, 0); + as_daddiu(StackPointer, StackPointer, sizeof(intptr_t)); +} + +void +MacroAssemblerMIPS64::ma_push(Register r) +{ + if (r == sp) { + // Pushing sp requires one more instruction. + ma_move(ScratchRegister, sp); + r = ScratchRegister; + } + + as_daddiu(StackPointer, StackPointer, (int32_t)-sizeof(intptr_t)); + as_sd(r, StackPointer, 0); +} + +// Branches when done from within mips-specific code. +void +MacroAssemblerMIPS64::ma_b(Register lhs, ImmWord imm, Label* label, Condition c, JumpKind jumpKind) +{ + MOZ_ASSERT(c != Overflow); + if (imm.value == 0) { + if (c == Always || c == AboveOrEqual) + ma_b(label, jumpKind); + else if (c == Below) + ; // This condition is always false. No branch required. + else + branchWithCode(getBranchCode(lhs, c), label, jumpKind); + } else { + MOZ_ASSERT(lhs != ScratchRegister); + ma_li(ScratchRegister, imm); + ma_b(lhs, ScratchRegister, label, c, jumpKind); + } +} + +void +MacroAssemblerMIPS64::ma_b(Register lhs, Address addr, Label* label, Condition c, JumpKind jumpKind) +{ + MOZ_ASSERT(lhs != ScratchRegister); + ma_load(ScratchRegister, addr, SizeDouble); + ma_b(lhs, ScratchRegister, label, c, jumpKind); +} + +void +MacroAssemblerMIPS64::ma_b(Address addr, Imm32 imm, Label* label, Condition c, JumpKind jumpKind) +{ + ma_load(SecondScratchReg, addr, SizeDouble); + ma_b(SecondScratchReg, imm, label, c, jumpKind); +} + +void +MacroAssemblerMIPS64::ma_b(Address addr, ImmGCPtr imm, Label* label, Condition c, JumpKind jumpKind) +{ + ma_load(SecondScratchReg, addr, SizeDouble); + ma_b(SecondScratchReg, imm, label, c, jumpKind); +} + +void +MacroAssemblerMIPS64::ma_bal(Label* label, DelaySlotFill delaySlotFill) +{ + if (label->bound()) { + // Generate the long jump for calls because return address has to be + // the address after the reserved block. + addLongJump(nextOffset()); + ma_liPatchable(ScratchRegister, ImmWord(label->offset())); + as_jalr(ScratchRegister); + if (delaySlotFill == FillDelaySlot) + as_nop(); + return; + } + + // Second word holds a pointer to the next branch in label's chain. + uint32_t nextInChain = label->used() ? label->offset() : LabelBase::INVALID_OFFSET; + + // Make the whole branch continous in the buffer. The '6' + // instructions are writing at below (contain delay slot). + m_buffer.ensureSpace(6 * sizeof(uint32_t)); + + BufferOffset bo = writeInst(getBranchCode(BranchIsCall).encode()); + writeInst(nextInChain); + label->use(bo.getOffset()); + // Leave space for long jump. + as_nop(); + as_nop(); + as_nop(); + if (delaySlotFill == FillDelaySlot) + as_nop(); +} + +void +MacroAssemblerMIPS64::branchWithCode(InstImm code, Label* label, JumpKind jumpKind) +{ + MOZ_ASSERT(code.encode() != InstImm(op_regimm, zero, rt_bgezal, BOffImm16(0)).encode()); + InstImm inst_beq = InstImm(op_beq, zero, zero, BOffImm16(0)); + + if (label->bound()) { + int32_t offset = label->offset() - m_buffer.nextOffset().getOffset(); + + if (BOffImm16::IsInRange(offset)) + jumpKind = ShortJump; + + if (jumpKind == ShortJump) { + MOZ_ASSERT(BOffImm16::IsInRange(offset)); + code.setBOffImm16(BOffImm16(offset)); + writeInst(code.encode()); + as_nop(); + return; + } + + if (code.encode() == inst_beq.encode()) { + // Handle long jump + addLongJump(nextOffset()); + ma_liPatchable(ScratchRegister, ImmWord(label->offset())); + as_jr(ScratchRegister); + as_nop(); + return; + } + + // Handle long conditional branch, the target offset is based on self, + // point to next instruction of nop at below. + writeInst(invertBranch(code, BOffImm16(7 * sizeof(uint32_t))).encode()); + // No need for a "nop" here because we can clobber scratch. + addLongJump(nextOffset()); + ma_liPatchable(ScratchRegister, ImmWord(label->offset())); + as_jr(ScratchRegister); + as_nop(); + return; + } + + // Generate open jump and link it to a label. + + // Second word holds a pointer to the next branch in label's chain. + uint32_t nextInChain = label->used() ? label->offset() : LabelBase::INVALID_OFFSET; + + if (jumpKind == ShortJump) { + // Make the whole branch continous in the buffer. + m_buffer.ensureSpace(2 * sizeof(uint32_t)); + + // Indicate that this is short jump with offset 4. + code.setBOffImm16(BOffImm16(4)); + BufferOffset bo = writeInst(code.encode()); + writeInst(nextInChain); + label->use(bo.getOffset()); + return; + } + + bool conditional = code.encode() != inst_beq.encode(); + + // Make the whole branch continous in the buffer. The '7' + // instructions are writing at below (contain conditional nop). + m_buffer.ensureSpace(7 * sizeof(uint32_t)); + + BufferOffset bo = writeInst(code.encode()); + writeInst(nextInChain); + label->use(bo.getOffset()); + // Leave space for potential long jump. + as_nop(); + as_nop(); + as_nop(); + as_nop(); + if (conditional) + as_nop(); +} + +void +MacroAssemblerMIPS64::ma_cmp_set(Register rd, Register rs, ImmWord imm, Condition c) +{ + ma_li(ScratchRegister, imm); + ma_cmp_set(rd, rs, ScratchRegister, c); +} + +void +MacroAssemblerMIPS64::ma_cmp_set(Register rd, Register rs, ImmPtr imm, Condition c) +{ + ma_li(ScratchRegister, ImmWord(uintptr_t(imm.value))); + ma_cmp_set(rd, rs, ScratchRegister, c); +} + +// fp instructions +void +MacroAssemblerMIPS64::ma_lid(FloatRegister dest, double value) +{ + ImmWord imm(mozilla::BitwiseCast(value)); + + ma_li(ScratchRegister, imm); + moveToDouble(ScratchRegister, dest); +} + +void +MacroAssemblerMIPS64::ma_mv(FloatRegister src, ValueOperand dest) +{ + as_dmfc1(dest.valueReg(), src); +} + +void +MacroAssemblerMIPS64::ma_mv(ValueOperand src, FloatRegister dest) +{ + as_dmtc1(src.valueReg(), dest); +} + +void +MacroAssemblerMIPS64::ma_ls(FloatRegister ft, Address address) +{ + if (Imm16::IsInSignedRange(address.offset)) { + as_ls(ft, address.base, address.offset); + } else { + MOZ_ASSERT(address.base != ScratchRegister); + ma_li(ScratchRegister, Imm32(address.offset)); + as_daddu(ScratchRegister, address.base, ScratchRegister); + as_ls(ft, ScratchRegister, 0); + } +} + +void +MacroAssemblerMIPS64::ma_ld(FloatRegister ft, Address address) +{ + if (Imm16::IsInSignedRange(address.offset)) { + as_ld(ft, address.base, address.offset); + } else { + ma_li(ScratchRegister, Imm32(address.offset)); + as_daddu(ScratchRegister, address.base, ScratchRegister); + as_ld(ft, ScratchRegister, 0); + } +} + +void +MacroAssemblerMIPS64::ma_sd(FloatRegister ft, Address address) +{ + if (Imm16::IsInSignedRange(address.offset)) { + as_sd(ft, address.base, address.offset); + } else { + ma_li(ScratchRegister, Imm32(address.offset)); + as_daddu(ScratchRegister, address.base, ScratchRegister); + as_sd(ft, ScratchRegister, 0); + } +} + +void +MacroAssemblerMIPS64::ma_ss(FloatRegister ft, Address address) +{ + if (Imm16::IsInSignedRange(address.offset)) { + as_ss(ft, address.base, address.offset); + } else { + ma_li(ScratchRegister, Imm32(address.offset)); + as_daddu(ScratchRegister, address.base, ScratchRegister); + as_ss(ft, ScratchRegister, 0); + } +} + +void +MacroAssemblerMIPS64::ma_pop(FloatRegister fs) +{ + ma_ld(fs, Address(StackPointer, 0)); + as_daddiu(StackPointer, StackPointer, sizeof(double)); +} + +void +MacroAssemblerMIPS64::ma_push(FloatRegister fs) +{ + as_daddiu(StackPointer, StackPointer, (int32_t)-sizeof(double)); + ma_sd(fs, Address(StackPointer, 0)); +} + +bool +MacroAssemblerMIPS64Compat::buildOOLFakeExitFrame(void* fakeReturnAddr) +{ + uint32_t descriptor = MakeFrameDescriptor(asMasm().framePushed(), JitFrame_IonJS); + + asMasm().Push(Imm32(descriptor)); // descriptor_ + asMasm().Push(ImmPtr(fakeReturnAddr)); + + return true; +} + +void +MacroAssemblerMIPS64Compat::add32(Register src, Register dest) +{ + as_addu(dest, dest, src); +} + +void +MacroAssemblerMIPS64Compat::add32(Imm32 imm, Register dest) +{ + ma_addu(dest, dest, imm); +} + +void + +MacroAssemblerMIPS64Compat::add32(Imm32 imm, const Address& dest) +{ + load32(dest, SecondScratchReg); + ma_addu(SecondScratchReg, imm); + store32(SecondScratchReg, dest); +} + +void +MacroAssemblerMIPS64Compat::sub32(Imm32 imm, Register dest) +{ + ma_subu(dest, dest, imm); +} + +void +MacroAssemblerMIPS64Compat::sub32(Register src, Register dest) +{ + as_subu(dest, dest, src); +} + +void +MacroAssemblerMIPS64Compat::addPtr(Register src, Register dest) +{ + ma_daddu(dest, src); +} + +void +MacroAssemblerMIPS64Compat::addPtr(const Address& src, Register dest) +{ + loadPtr(src, ScratchRegister); + ma_daddu(dest, ScratchRegister); +} + +void +MacroAssemblerMIPS64Compat::subPtr(Register src, Register dest) +{ + as_dsubu(dest, dest, src); +} + +void +MacroAssemblerMIPS64Compat::move32(Imm32 imm, Register dest) +{ + ma_li(dest, imm); +} + +void +MacroAssemblerMIPS64Compat::move32(Register src, Register dest) +{ + ma_move(dest, src); +} + +void +MacroAssemblerMIPS64Compat::movePtr(Register src, Register dest) +{ + ma_move(dest, src); +} +void +MacroAssemblerMIPS64Compat::movePtr(ImmWord imm, Register dest) +{ + ma_li(dest, imm); +} + +void +MacroAssemblerMIPS64Compat::movePtr(ImmGCPtr imm, Register dest) +{ + ma_li(dest, imm); +} + +void +MacroAssemblerMIPS64Compat::movePtr(ImmPtr imm, Register dest) +{ + movePtr(ImmWord(uintptr_t(imm.value)), dest); +} +void +MacroAssemblerMIPS64Compat::movePtr(AsmJSImmPtr imm, Register dest) +{ + append(AsmJSAbsoluteLink(CodeOffsetLabel(nextOffset().getOffset()), imm.kind())); + ma_liPatchable(dest, ImmWord(-1)); +} + +void +MacroAssemblerMIPS64Compat::load8ZeroExtend(const Address& address, Register dest) +{ + ma_load(dest, address, SizeByte, ZeroExtend); +} + +void +MacroAssemblerMIPS64Compat::load8ZeroExtend(const BaseIndex& src, Register dest) +{ + ma_load(dest, src, SizeByte, ZeroExtend); +} + +void +MacroAssemblerMIPS64Compat::load8SignExtend(const Address& address, Register dest) +{ + ma_load(dest, address, SizeByte, SignExtend); +} + +void +MacroAssemblerMIPS64Compat::load8SignExtend(const BaseIndex& src, Register dest) +{ + ma_load(dest, src, SizeByte, SignExtend); +} + +void +MacroAssemblerMIPS64Compat::load16ZeroExtend(const Address& address, Register dest) +{ + ma_load(dest, address, SizeHalfWord, ZeroExtend); +} + +void +MacroAssemblerMIPS64Compat::load16ZeroExtend(const BaseIndex& src, Register dest) +{ + ma_load(dest, src, SizeHalfWord, ZeroExtend); +} + +void +MacroAssemblerMIPS64Compat::load16SignExtend(const Address& address, Register dest) +{ + ma_load(dest, address, SizeHalfWord, SignExtend); +} + +void +MacroAssemblerMIPS64Compat::load16SignExtend(const BaseIndex& src, Register dest) +{ + ma_load(dest, src, SizeHalfWord, SignExtend); +} + +void +MacroAssemblerMIPS64Compat::load32(const Address& address, Register dest) +{ + ma_load(dest, address, SizeWord); +} + +void +MacroAssemblerMIPS64Compat::load32(const BaseIndex& address, Register dest) +{ + ma_load(dest, address, SizeWord); +} + +void +MacroAssemblerMIPS64Compat::load32(AbsoluteAddress address, Register dest) +{ + movePtr(ImmPtr(address.addr), ScratchRegister); + load32(Address(ScratchRegister, 0), dest); +} + +void +MacroAssemblerMIPS64Compat::load32(AsmJSAbsoluteAddress address, Register dest) +{ + movePtr(AsmJSImmPtr(address.kind()), ScratchRegister); + load32(Address(ScratchRegister, 0), dest); +} + +void +MacroAssemblerMIPS64Compat::loadPtr(const Address& address, Register dest) +{ + ma_load(dest, address, SizeDouble); +} + +void +MacroAssemblerMIPS64Compat::loadPtr(const BaseIndex& src, Register dest) +{ + ma_load(dest, src, SizeDouble); +} + +void +MacroAssemblerMIPS64Compat::loadPtr(AbsoluteAddress address, Register dest) +{ + movePtr(ImmPtr(address.addr), ScratchRegister); + loadPtr(Address(ScratchRegister, 0), dest); +} + +void +MacroAssemblerMIPS64Compat::loadPtr(AsmJSAbsoluteAddress address, Register dest) +{ + movePtr(AsmJSImmPtr(address.kind()), ScratchRegister); + loadPtr(Address(ScratchRegister, 0), dest); +} + +void +MacroAssemblerMIPS64Compat::loadPrivate(const Address& address, Register dest) +{ + loadPtr(address, dest); + ma_dsll(dest, dest, Imm32(1)); +} + +void +MacroAssemblerMIPS64Compat::loadDouble(const Address& address, FloatRegister dest) +{ + ma_ld(dest, address); +} + +void +MacroAssemblerMIPS64Compat::loadDouble(const BaseIndex& src, FloatRegister dest) +{ + computeScaledAddress(src, SecondScratchReg); + ma_ld(dest, Address(SecondScratchReg, src.offset)); +} + +void +MacroAssemblerMIPS64Compat::loadFloatAsDouble(const Address& address, FloatRegister dest) +{ + ma_ls(dest, address); + as_cvtds(dest, dest); +} + +void +MacroAssemblerMIPS64Compat::loadFloatAsDouble(const BaseIndex& src, FloatRegister dest) +{ + loadFloat32(src, dest); + as_cvtds(dest, dest); +} + +void +MacroAssemblerMIPS64Compat::loadFloat32(const Address& address, FloatRegister dest) +{ + ma_ls(dest, address); +} + +void +MacroAssemblerMIPS64Compat::loadFloat32(const BaseIndex& src, FloatRegister dest) +{ + computeScaledAddress(src, SecondScratchReg); + ma_ls(dest, Address(SecondScratchReg, src.offset)); +} + +void +MacroAssemblerMIPS64Compat::store8(Imm32 imm, const Address& address) +{ + ma_li(SecondScratchReg, imm); + ma_store(SecondScratchReg, address, SizeByte); +} + +void +MacroAssemblerMIPS64Compat::store8(Register src, const Address& address) +{ + ma_store(src, address, SizeByte); +} + +void +MacroAssemblerMIPS64Compat::store8(Imm32 imm, const BaseIndex& dest) +{ + ma_store(imm, dest, SizeByte); +} + +void +MacroAssemblerMIPS64Compat::store8(Register src, const BaseIndex& dest) +{ + ma_store(src, dest, SizeByte); +} + +void +MacroAssemblerMIPS64Compat::store16(Imm32 imm, const Address& address) +{ + ma_li(SecondScratchReg, imm); + ma_store(SecondScratchReg, address, SizeHalfWord); +} + +void +MacroAssemblerMIPS64Compat::store16(Register src, const Address& address) +{ + ma_store(src, address, SizeHalfWord); +} + +void +MacroAssemblerMIPS64Compat::store16(Imm32 imm, const BaseIndex& dest) +{ + ma_store(imm, dest, SizeHalfWord); +} + +void +MacroAssemblerMIPS64Compat::store16(Register src, const BaseIndex& address) +{ + ma_store(src, address, SizeHalfWord); +} + +void +MacroAssemblerMIPS64Compat::store32(Register src, AbsoluteAddress address) +{ + movePtr(ImmPtr(address.addr), ScratchRegister); + store32(src, Address(ScratchRegister, 0)); +} + +void +MacroAssemblerMIPS64Compat::store32(Register src, const Address& address) +{ + ma_store(src, address, SizeWord); +} + +void +MacroAssemblerMIPS64Compat::store32(Imm32 src, const Address& address) +{ + move32(src, SecondScratchReg); + ma_store(SecondScratchReg, address, SizeWord); +} + +void +MacroAssemblerMIPS64Compat::store32(Imm32 imm, const BaseIndex& dest) +{ + ma_store(imm, dest, SizeWord); +} + +void +MacroAssemblerMIPS64Compat::store32(Register src, const BaseIndex& dest) +{ + ma_store(src, dest, SizeWord); +} + +template +void +MacroAssemblerMIPS64Compat::storePtr(ImmWord imm, T address) +{ + ma_li(SecondScratchReg, imm); + ma_store(SecondScratchReg, address, SizeDouble); +} + +template void MacroAssemblerMIPS64Compat::storePtr
(ImmWord imm, Address address); +template void MacroAssemblerMIPS64Compat::storePtr(ImmWord imm, BaseIndex address); + +template +void +MacroAssemblerMIPS64Compat::storePtr(ImmPtr imm, T address) +{ + storePtr(ImmWord(uintptr_t(imm.value)), address); +} + +template void MacroAssemblerMIPS64Compat::storePtr
(ImmPtr imm, Address address); +template void MacroAssemblerMIPS64Compat::storePtr(ImmPtr imm, BaseIndex address); + +template +void +MacroAssemblerMIPS64Compat::storePtr(ImmGCPtr imm, T address) +{ + storePtr(ImmWord(uintptr_t(imm.value)), address); +} + +template void MacroAssemblerMIPS64Compat::storePtr
(ImmGCPtr imm, Address address); +template void MacroAssemblerMIPS64Compat::storePtr(ImmGCPtr imm, BaseIndex address); + +void +MacroAssemblerMIPS64Compat::storePtr(Register src, const Address& address) +{ + ma_store(src, address, SizeDouble); +} + +void +MacroAssemblerMIPS64Compat::storePtr(Register src, const BaseIndex& address) +{ + ma_store(src, address, SizeDouble); +} + +void +MacroAssemblerMIPS64Compat::storePtr(Register src, AbsoluteAddress dest) +{ + movePtr(ImmPtr(dest.addr), ScratchRegister); + storePtr(src, Address(ScratchRegister, 0)); +} + +void +MacroAssemblerMIPS64Compat::clampIntToUint8(Register reg) +{ + // look at (reg >> 8) if it is 0, then src shouldn't be clamped + // if it is <0, then we want to clamp to 0, + // otherwise, we wish to clamp to 255 + Label done; + ma_move(ScratchRegister, reg); + asMasm().rshiftPtrArithmetic(Imm32(8), ScratchRegister); + ma_b(ScratchRegister, ScratchRegister, &done, Assembler::Zero, ShortJump); + { + Label negative; + ma_b(ScratchRegister, ScratchRegister, &negative, Assembler::Signed, ShortJump); + { + ma_li(reg, Imm32(255)); + ma_b(&done, ShortJump); + } + bind(&negative); + { + ma_move(reg, zero); + } + } + bind(&done); +} + +// Note: this function clobbers the input register. +void +MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output) +{ + MOZ_ASSERT(input != ScratchDoubleReg); + Label positive, done; + + // <= 0 or NaN --> 0 + zeroDouble(ScratchDoubleReg); + branchDouble(DoubleGreaterThan, input, ScratchDoubleReg, &positive); + { + move32(Imm32(0), output); + jump(&done); + } + + bind(&positive); + + // Add 0.5 and truncate. + loadConstantDouble(0.5, ScratchDoubleReg); + addDouble(ScratchDoubleReg, input); + + Label outOfRange; + + branchTruncateDouble(input, output, &outOfRange); + branch32(Assembler::Above, output, Imm32(255), &outOfRange); + { + // Check if we had a tie. + convertInt32ToDouble(output, ScratchDoubleReg); + branchDouble(DoubleNotEqual, input, ScratchDoubleReg, &done); + + // It was a tie. Mask out the ones bit to get an even value. + // See also js_TypedArray_uint8_clamp_double. + and32(Imm32(~1), output); + jump(&done); + } + + // > 255 --> 255 + bind(&outOfRange); + { + move32(Imm32(255), output); + } + + bind(&done); +} + +void +MacroAssemblerMIPS64Compat::subPtr(Imm32 imm, const Register dest) +{ + ma_dsubu(dest, dest, imm); +} + +void +MacroAssemblerMIPS64Compat::subPtr(const Address& addr, const Register dest) +{ + loadPtr(addr, SecondScratchReg); + subPtr(SecondScratchReg, dest); +} + +void +MacroAssemblerMIPS64Compat::subPtr(Register src, const Address& dest) +{ + loadPtr(dest, SecondScratchReg); + subPtr(src, SecondScratchReg); + storePtr(SecondScratchReg, dest); +} + +void +MacroAssemblerMIPS64Compat::addPtr(Imm32 imm, const Register dest) +{ + ma_daddu(dest, imm); +} + +void +MacroAssemblerMIPS64Compat::addPtr(Imm32 imm, const Address& dest) +{ + loadPtr(dest, ScratchRegister); + addPtr(imm, ScratchRegister); + storePtr(ScratchRegister, dest); +} + +void +MacroAssemblerMIPS64Compat::branchDouble(DoubleCondition cond, FloatRegister lhs, + FloatRegister rhs, Label* label) +{ + ma_bc1d(lhs, rhs, label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchFloat(DoubleCondition cond, FloatRegister lhs, + FloatRegister rhs, Label* label) +{ + ma_bc1s(lhs, rhs, label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestGCThing(Condition cond, const Address& address, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET), label, + (cond == Equal) ? AboveOrEqual : Below); +} +void +MacroAssemblerMIPS64Compat::branchTestGCThing(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_LOWER_INCL_TAG_OF_GCTHING_SET), label, + (cond == Equal) ? AboveOrEqual : Below); +} + +void +MacroAssemblerMIPS64Compat::branchTestPrimitive(Condition cond, const ValueOperand& value, + Label* label) +{ + splitTag(value, SecondScratchReg); + branchTestPrimitive(cond, SecondScratchReg, label); +} +void +MacroAssemblerMIPS64Compat::branchTestPrimitive(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_b(tag, ImmTag(JSVAL_UPPER_EXCL_TAG_OF_PRIMITIVE_SET), label, + (cond == Equal) ? Below : AboveOrEqual); +} + +void +MacroAssemblerMIPS64Compat::branchTestInt32(Condition cond, const ValueOperand& value, Label* label) +{ + MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); + splitTag(value, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_INT32), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestInt32(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_b(tag, ImmTag(JSVAL_TAG_INT32), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestInt32(Condition cond, const Address& address, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_INT32), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestInt32(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_INT32), label, cond); +} + +void +MacroAssemblerMIPS64Compat:: branchTestBoolean(Condition cond, const ValueOperand& value, + Label* label) +{ + MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); + splitTag(value, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_BOOLEAN), label, cond); +} + +void +MacroAssemblerMIPS64Compat:: branchTestBoolean(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); + ma_b(tag, ImmTag(JSVAL_TAG_BOOLEAN), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestBoolean(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_BOOLEAN), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestDouble(Condition cond, const ValueOperand& value, Label* label) +{ + MOZ_ASSERT(cond == Assembler::Equal || cond == NotEqual); + splitTag(value, SecondScratchReg); + branchTestDouble(cond, SecondScratchReg, label); +} + +void +MacroAssemblerMIPS64Compat::branchTestDouble(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Assembler::Equal || cond == NotEqual); + Condition actual = (cond == Equal) ? BelowOrEqual : Above; + ma_b(tag, ImmTag(JSVAL_TAG_MAX_DOUBLE), label, actual); +} + +void +MacroAssemblerMIPS64Compat::branchTestDouble(Condition cond, const Address& address, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(address, SecondScratchReg); + branchTestDouble(cond, SecondScratchReg, label); +} + +void +MacroAssemblerMIPS64Compat::branchTestDouble(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + branchTestDouble(cond, SecondScratchReg, label); +} + +void +MacroAssemblerMIPS64Compat::branchTestNull(Condition cond, const ValueOperand& value, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + splitTag(value, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_NULL), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestNull(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_b(tag, ImmTag(JSVAL_TAG_NULL), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestNull(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_NULL), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestNull(Condition cond, const Address& address, Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_NULL), label, cond); +} + +void +MacroAssemblerMIPS64Compat::testNullSet(Condition cond, const ValueOperand& value, Register dest) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + splitTag(value, SecondScratchReg); + ma_cmp_set(dest, SecondScratchReg, ImmTag(JSVAL_TAG_NULL), cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestObject(Condition cond, const ValueOperand& value, Label* label) +{ + splitTag(value, SecondScratchReg); + branchTestObject(cond, SecondScratchReg, label); +} + +void +MacroAssemblerMIPS64Compat::branchTestObject(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_b(tag, ImmTag(JSVAL_TAG_OBJECT), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestObject(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_OBJECT), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestObject(Condition cond, const Address& address, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_OBJECT), label, cond); +} + +void +MacroAssemblerMIPS64Compat::testObjectSet(Condition cond, const ValueOperand& value, Register dest) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + splitTag(value, SecondScratchReg); + ma_cmp_set(dest, SecondScratchReg, ImmTag(JSVAL_TAG_OBJECT), cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestString(Condition cond, const ValueOperand& value, Label* label) +{ + splitTag(value, SecondScratchReg); + branchTestString(cond, SecondScratchReg, label); +} + +void +MacroAssemblerMIPS64Compat::branchTestString(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_b(tag, ImmTag(JSVAL_TAG_STRING), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestString(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_STRING), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestSymbol(Condition cond, const ValueOperand& value, Label* label) +{ + splitTag(value, SecondScratchReg); + branchTestSymbol(cond, SecondScratchReg, label); +} + +void +MacroAssemblerMIPS64Compat::branchTestSymbol(Condition cond, const Register& tag, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_b(tag, ImmTag(JSVAL_TAG_SYMBOL), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestSymbol(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_SYMBOL), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestUndefined(Condition cond, const ValueOperand& value, + Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + splitTag(value, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_UNDEFINED), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestUndefined(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_b(tag, ImmTag(JSVAL_TAG_UNDEFINED), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestUndefined(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_UNDEFINED), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestUndefined(Condition cond, const Address& address, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_UNDEFINED), label, cond); +} + +void +MacroAssemblerMIPS64Compat::testUndefinedSet(Condition cond, const ValueOperand& value, Register dest) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + splitTag(value, SecondScratchReg); + ma_cmp_set(dest, SecondScratchReg, ImmTag(JSVAL_TAG_UNDEFINED), cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestNumber(Condition cond, const ValueOperand& value, Label* label) +{ + splitTag(value, SecondScratchReg); + branchTestNumber(cond, SecondScratchReg, label); +} + +void +MacroAssemblerMIPS64Compat::branchTestNumber(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_b(tag, ImmTag(JSVAL_UPPER_INCL_TAG_OF_NUMBER_SET), label, + cond == Equal ? BelowOrEqual : Above); +} + +void +MacroAssemblerMIPS64Compat::branchTestMagic(Condition cond, const ValueOperand& value, Label* label) +{ + splitTag(value, SecondScratchReg); + branchTestMagic(cond, SecondScratchReg, label); +} + +void +MacroAssemblerMIPS64Compat::branchTestMagic(Condition cond, Register tag, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_b(tag, ImmTag(JSVAL_TAG_MAGIC), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestMagic(Condition cond, const Address& address, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(address, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_MAGIC), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestMagic(Condition cond, const BaseIndex& src, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + extractTag(src, SecondScratchReg); + ma_b(SecondScratchReg, ImmTag(JSVAL_TAG_MAGIC), label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestValue(Condition cond, const ValueOperand& value, + const Value& v, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + moveValue(v, ScratchRegister); + ma_b(value.valueReg(), ScratchRegister, label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestValue(Condition cond, const Address& valaddr, + const ValueOperand& value, Label* label) +{ + MOZ_ASSERT(cond == Equal || cond == NotEqual); + loadPtr(Address(valaddr.base, valaddr.offset), ScratchRegister); + ma_b(value.valueReg(), ScratchRegister, label, cond); +} + +// unboxing code +void +MacroAssemblerMIPS64Compat::unboxNonDouble(const ValueOperand& operand, Register dest) +{ + ma_dext(dest, operand.valueReg(), Imm32(0), Imm32(JSVAL_TAG_SHIFT)); +} + +void +MacroAssemblerMIPS64Compat::unboxNonDouble(const Address& src, Register dest) +{ + loadPtr(Address(src.base, src.offset), dest); + ma_dext(dest, dest, Imm32(0), Imm32(JSVAL_TAG_SHIFT)); +} + +void +MacroAssemblerMIPS64Compat::unboxNonDouble(const BaseIndex& src, Register dest) +{ + computeScaledAddress(src, SecondScratchReg); + loadPtr(Address(SecondScratchReg, src.offset), dest); + ma_dext(dest, dest, Imm32(0), Imm32(JSVAL_TAG_SHIFT)); +} + +void +MacroAssemblerMIPS64Compat::unboxInt32(const ValueOperand& operand, Register dest) +{ + ma_dsll(dest, operand.valueReg(), Imm32(32)); + ma_dsra(dest, dest, Imm32(32)); +} + +void +MacroAssemblerMIPS64Compat::unboxInt32(const Operand& operand, Register dest) +{ + switch(operand.getTag()) { + case Operand::REG: + ma_dsll(dest, operand.toReg(), Imm32(32)); + ma_dsra(dest, dest, Imm32(32)); + break; + case Operand::MEM: + unboxInt32(operand.toAddress(), dest); + break; + case Operand::FREG: + default: + MOZ_CRASH("unexpected operand kind"); + break; + } +} + +void +MacroAssemblerMIPS64Compat::unboxInt32(const Address& src, Register dest) +{ + load32(Address(src.base, src.offset), dest); +} + +void +MacroAssemblerMIPS64Compat::unboxInt32(const BaseIndex& src, Register dest) +{ + computeScaledAddress(src, SecondScratchReg); + load32(Address(SecondScratchReg, src.offset), dest); +} + +void +MacroAssemblerMIPS64Compat::unboxBoolean(const ValueOperand& operand, Register dest) +{ + ma_dext(dest, operand.valueReg(), Imm32(0), Imm32(32)); +} + +void +MacroAssemblerMIPS64Compat::unboxBoolean(const Operand& operand, Register dest) +{ + switch(operand.getTag()) { + case Operand::REG: + ma_dext(dest, operand.toReg(), Imm32(0), Imm32(32)); + break; + case Operand::MEM: + unboxBoolean(operand.toAddress(), dest); + break; + case Operand::FREG: + default: + MOZ_CRASH("unexpected operand kind"); + break; + } +} + +void +MacroAssemblerMIPS64Compat::unboxBoolean(const Address& src, Register dest) +{ + ma_load(dest, Address(src.base, src.offset), SizeWord, ZeroExtend); +} + +void +MacroAssemblerMIPS64Compat::unboxBoolean(const BaseIndex& src, Register dest) +{ + computeScaledAddress(src, SecondScratchReg); + ma_load(dest, Address(SecondScratchReg, src.offset), SizeWord, ZeroExtend); +} + +void +MacroAssemblerMIPS64Compat::unboxDouble(const ValueOperand& operand, FloatRegister dest) +{ + as_dmtc1(operand.valueReg(), dest); +} + +void +MacroAssemblerMIPS64Compat::unboxDouble(const Address& src, FloatRegister dest) +{ + ma_ld(dest, Address(src.base, src.offset)); +} + +void +MacroAssemblerMIPS64Compat::unboxString(const ValueOperand& operand, Register dest) +{ + unboxNonDouble(operand, dest); +} + +void +MacroAssemblerMIPS64Compat::unboxString(const Operand& operand, Register dest) +{ + switch(operand.getTag()) { + case Operand::REG: + ma_dext(dest, operand.toReg(), Imm32(0), Imm32(JSVAL_TAG_SHIFT)); + break; + case Operand::MEM: + unboxNonDouble(operand.toAddress(), dest); + break; + case Operand::FREG: + default: + MOZ_CRASH("unexpected operand kind"); + break; + } +} + +void +MacroAssemblerMIPS64Compat::unboxString(const Address& src, Register dest) +{ + unboxNonDouble(src, dest); +} + +void +MacroAssemblerMIPS64Compat::unboxSymbol(const Operand& operand, Register dest) +{ + switch(operand.getTag()) { + case Operand::REG: + ma_dext(dest, operand.toReg(), Imm32(0), Imm32(JSVAL_TAG_SHIFT)); + break; + case Operand::MEM: + unboxNonDouble(operand.toAddress(), dest); + break; + case Operand::FREG: + default: + MOZ_CRASH("unexpected operand kind"); + break; + } +} + +void +MacroAssemblerMIPS64Compat::unboxSymbol(const Address& src, Register dest) +{ + unboxNonDouble(src, dest); +} + +void +MacroAssemblerMIPS64Compat::unboxObject(const ValueOperand& src, Register dest) +{ + unboxNonDouble(src, dest); +} + +void +MacroAssemblerMIPS64Compat::unboxObject(const Operand& src, Register dest) +{ + switch(src.getTag()) { + case Operand::REG: + ma_dext(dest, src.toReg(), Imm32(0), Imm32(JSVAL_TAG_SHIFT)); + break; + case Operand::MEM: + unboxNonDouble(src.toAddress(), dest); + break; + case Operand::FREG: + default: + MOZ_CRASH("unexpected operand kind"); + break; + } +} + +void +MacroAssemblerMIPS64Compat::unboxObject(const Address& src, Register dest) +{ + unboxNonDouble(src, dest); +} + +void +MacroAssemblerMIPS64Compat::unboxValue(const ValueOperand& src, AnyRegister dest) +{ + if (dest.isFloat()) { + Label notInt32, end; + branchTestInt32(Assembler::NotEqual, src, ¬Int32); + convertInt32ToDouble(src.valueReg(), dest.fpu()); + ma_b(&end, ShortJump); + bind(¬Int32); + unboxDouble(src, dest.fpu()); + bind(&end); + } else { + unboxNonDouble(src, dest.gpr()); + } +} + +void +MacroAssemblerMIPS64Compat::unboxPrivate(const ValueOperand& src, Register dest) +{ + ma_dsrl(dest, src.valueReg(), Imm32(1)); +} + +void +MacroAssemblerMIPS64Compat::boxDouble(FloatRegister src, const ValueOperand& dest) +{ + as_dmfc1(dest.valueReg(), src); +} + +void +MacroAssemblerMIPS64Compat::boxNonDouble(JSValueType type, Register src, + const ValueOperand& dest) +{ + MOZ_ASSERT(src != dest.valueReg()); + boxValue(type, src, dest.valueReg()); +} + +void +MacroAssemblerMIPS64Compat::boolValueToDouble(const ValueOperand& operand, FloatRegister dest) +{ + convertBoolToInt32(operand.valueReg(), ScratchRegister); + convertInt32ToDouble(ScratchRegister, dest); +} + +void +MacroAssemblerMIPS64Compat::int32ValueToDouble(const ValueOperand& operand, + FloatRegister dest) +{ + convertInt32ToDouble(operand.valueReg(), dest); +} + +void +MacroAssemblerMIPS64Compat::boolValueToFloat32(const ValueOperand& operand, + FloatRegister dest) +{ + + convertBoolToInt32(operand.valueReg(), ScratchRegister); + convertInt32ToFloat32(ScratchRegister, dest); +} + +void +MacroAssemblerMIPS64Compat::int32ValueToFloat32(const ValueOperand& operand, + FloatRegister dest) +{ + convertInt32ToFloat32(operand.valueReg(), dest); +} + +void +MacroAssemblerMIPS64Compat::loadConstantFloat32(float f, FloatRegister dest) +{ + ma_lis(dest, f); +} + +void +MacroAssemblerMIPS64Compat::loadInt32OrDouble(const Address& src, FloatRegister dest) +{ + Label notInt32, end; + // If it's an int, convert it to double. + loadPtr(Address(src.base, src.offset), ScratchRegister); + ma_dsrl(SecondScratchReg, ScratchRegister, Imm32(JSVAL_TAG_SHIFT)); + branchTestInt32(Assembler::NotEqual, SecondScratchReg, ¬Int32); + loadPtr(Address(src.base, src.offset), SecondScratchReg); + convertInt32ToDouble(SecondScratchReg, dest); + ma_b(&end, ShortJump); + + // Not an int, just load as double. + bind(¬Int32); + ma_ld(dest, src); + bind(&end); +} + +void +MacroAssemblerMIPS64Compat::loadInt32OrDouble(const BaseIndex& addr, FloatRegister dest) +{ + Label notInt32, end; + + // If it's an int, convert it to double. + computeScaledAddress(addr, SecondScratchReg); + // Since we only have one scratch, we need to stomp over it with the tag. + loadPtr(Address(SecondScratchReg, 0), ScratchRegister); + ma_dsrl(SecondScratchReg, ScratchRegister, Imm32(JSVAL_TAG_SHIFT)); + branchTestInt32(Assembler::NotEqual, SecondScratchReg, ¬Int32); + + computeScaledAddress(addr, SecondScratchReg); + loadPtr(Address(SecondScratchReg, 0), SecondScratchReg); + convertInt32ToDouble(SecondScratchReg, dest); + ma_b(&end, ShortJump); + + // Not an int, just load as double. + bind(¬Int32); + // First, recompute the offset that had been stored in the scratch register + // since the scratch register was overwritten loading in the type. + computeScaledAddress(addr, SecondScratchReg); + loadDouble(Address(SecondScratchReg, 0), dest); + bind(&end); +} + +void +MacroAssemblerMIPS64Compat::loadConstantDouble(double dp, FloatRegister dest) +{ + ma_lid(dest, dp); +} + +void +MacroAssemblerMIPS64Compat::branchTestInt32Truthy(bool b, const ValueOperand& value, Label* label) +{ + ma_dext(ScratchRegister, value.valueReg(), Imm32(0), Imm32(32)); + ma_b(ScratchRegister, ScratchRegister, label, b ? NonZero : Zero); +} + +void +MacroAssemblerMIPS64Compat::branchTestStringTruthy(bool b, const ValueOperand& value, Label* label) +{ + unboxString(value, SecondScratchReg); + load32(Address(SecondScratchReg, JSString::offsetOfLength()), SecondScratchReg); + ma_b(SecondScratchReg, Imm32(0), label, b ? NotEqual : Equal); +} + +void +MacroAssemblerMIPS64Compat::branchTestDoubleTruthy(bool b, FloatRegister value, Label* label) +{ + ma_lid(ScratchDoubleReg, 0.0); + DoubleCondition cond = b ? DoubleNotEqual : DoubleEqualOrUnordered; + ma_bc1d(value, ScratchDoubleReg, label, cond); +} + +void +MacroAssemblerMIPS64Compat::branchTestBooleanTruthy(bool b, const ValueOperand& operand, + Label* label) +{ + unboxBoolean(operand, SecondScratchReg); + ma_b(SecondScratchReg, SecondScratchReg, label, b ? NonZero : Zero); +} + +Register +MacroAssemblerMIPS64Compat::extractObject(const Address& address, Register scratch) +{ + loadPtr(Address(address.base, address.offset), scratch); + ma_dext(scratch, scratch, Imm32(0), Imm32(JSVAL_TAG_SHIFT)); + return scratch; +} + +Register +MacroAssemblerMIPS64Compat::extractTag(const Address& address, Register scratch) +{ + loadPtr(Address(address.base, address.offset), scratch); + ma_dext(scratch, scratch, Imm32(JSVAL_TAG_SHIFT), Imm32(64 - JSVAL_TAG_SHIFT)); + return scratch; +} + +Register +MacroAssemblerMIPS64Compat::extractTag(const BaseIndex& address, Register scratch) +{ + computeScaledAddress(address, scratch); + return extractTag(Address(scratch, address.offset), scratch); +} + +template +void +MacroAssemblerMIPS64Compat::storeUnboxedValue(ConstantOrRegister value, MIRType valueType, const T& dest, + MIRType slotType) +{ + if (valueType == MIRType_Double) { + storeDouble(value.reg().typedReg().fpu(), dest); + return; + } + + // For known integers and booleans, we can just store the unboxed value if + // the slot has the same type. + if ((valueType == MIRType_Int32 || valueType == MIRType_Boolean) && slotType == valueType) { + if (value.constant()) { + Value val = value.value(); + if (valueType == MIRType_Int32) + store32(Imm32(val.toInt32()), dest); + else + store32(Imm32(val.toBoolean() ? 1 : 0), dest); + } else { + store32(value.reg().typedReg().gpr(), dest); + } + return; + } + + if (value.constant()) + storeValue(value.value(), dest); + else + storeValue(ValueTypeFromMIRType(valueType), value.reg().typedReg().gpr(), dest); +} + +template void +MacroAssemblerMIPS64Compat::storeUnboxedValue(ConstantOrRegister value, MIRType valueType, const Address& dest, + MIRType slotType); + +template void +MacroAssemblerMIPS64Compat::storeUnboxedValue(ConstantOrRegister value, MIRType valueType, const BaseIndex& dest, + MIRType slotType); + +void +MacroAssemblerMIPS64Compat::moveValue(const Value& val, Register dest) +{ + jsval_layout jv = JSVAL_TO_IMPL(val); + writeDataRelocation(val); + movWithPatch(ImmWord(jv.asBits), dest); +} + +void +MacroAssemblerMIPS64Compat::moveValue(const Value& val, const ValueOperand& dest) +{ + moveValue(val, dest.valueReg()); +} + +/* There are 3 paths trough backedge jump. They are listed here in the order + * in which instructions are executed. + * - The short jump is simple: + * b offset # Jumps directly to target. + * lui at, addr1_hl # In delay slot. Don't care about 'at' here. + * + * - The long jump to loop header: + * b label1 + * lui at, addr1_hl # In delay slot. We use the value in 'at' later. + * label1: + * ori at, addr1_lh + * drotr32 at, at, 48 + * ori at, addr1_ll + * jr at + * lui at, addr2_hl # In delay slot. Don't care about 'at' here. + * + * - The long jump to interrupt loop: + * b label2 + * ... + * jr at + * label2: + * lui at, addr2_hl # In delay slot. Don't care about 'at' here. + * ori at, addr2_lh + * drotr32 at, at, 48 + * ori at, addr2_ll + * jr at + * nop # In delay slot. + * + * The backedge is done this way to avoid patching lui+ori pair while it is + * being executed. Look also at jit::PatchBackedge(). + */ +CodeOffsetJump +MacroAssemblerMIPS64Compat::backedgeJump(RepatchLabel* label, Label* documentation) +{ + // Only one branch per label. + MOZ_ASSERT(!label->used()); + uint32_t dest = label->bound() ? label->offset() : LabelBase::INVALID_OFFSET; + BufferOffset bo = nextOffset(); + label->use(bo.getOffset()); + + // Backedges are short jumps when bound, but can become long when patched. + m_buffer.ensureSpace(16 * sizeof(uint32_t)); + if (label->bound()) { + int32_t offset = label->offset() - bo.getOffset(); + MOZ_ASSERT(BOffImm16::IsInRange(offset)); + as_b(BOffImm16(offset)); + } else { + // Jump to "label1" by default to jump to the loop header. + as_b(BOffImm16(2 * sizeof(uint32_t))); + } + // No need for nop here. We can safely put next instruction in delay slot. + ma_liPatchable(ScratchRegister, ImmWord(dest)); + MOZ_ASSERT(nextOffset().getOffset() - bo.getOffset() == 5 * sizeof(uint32_t)); + as_jr(ScratchRegister); + // No need for nop here. We can safely put next instruction in delay slot. + ma_liPatchable(ScratchRegister, ImmWord(dest)); + as_jr(ScratchRegister); + as_nop(); + MOZ_ASSERT(nextOffset().getOffset() - bo.getOffset() == 12 * sizeof(uint32_t)); + return CodeOffsetJump(bo.getOffset()); +} + +CodeOffsetJump +MacroAssemblerMIPS64Compat::jumpWithPatch(RepatchLabel* label, Label* documentation) +{ + // Only one branch per label. + MOZ_ASSERT(!label->used()); + uint32_t dest = label->bound() ? label->offset() : LabelBase::INVALID_OFFSET; + + BufferOffset bo = nextOffset(); + label->use(bo.getOffset()); + addLongJump(bo); + ma_liPatchable(ScratchRegister, ImmWord(dest)); + as_jr(ScratchRegister); + as_nop(); + return CodeOffsetJump(bo.getOffset()); +} + +///////////////////////////////////////////////////////////////// +// X86/X64-common/ARM/MIPS interface. +///////////////////////////////////////////////////////////////// +void +MacroAssemblerMIPS64Compat::storeValue(ValueOperand val, Operand dst) +{ + storeValue(val, Address(Register::FromCode(dst.base()), dst.disp())); +} + +void +MacroAssemblerMIPS64Compat::storeValue(ValueOperand val, const BaseIndex& dest) +{ + computeScaledAddress(dest, SecondScratchReg); + storeValue(val, Address(SecondScratchReg, dest.offset)); +} + +void +MacroAssemblerMIPS64Compat::storeValue(JSValueType type, Register reg, BaseIndex dest) +{ + computeScaledAddress(dest, ScratchRegister); + + int32_t offset = dest.offset; + if (!Imm16::IsInSignedRange(offset)) { + ma_li(SecondScratchReg, Imm32(offset)); + as_daddu(ScratchRegister, ScratchRegister, SecondScratchReg); + offset = 0; + } + + storeValue(type, reg, Address(ScratchRegister, offset)); +} + +void +MacroAssemblerMIPS64Compat::storeValue(ValueOperand val, const Address& dest) +{ + storePtr(val.valueReg(), Address(dest.base, dest.offset)); +} + +void +MacroAssemblerMIPS64Compat::storeValue(JSValueType type, Register reg, Address dest) +{ + MOZ_ASSERT(dest.base != SecondScratchReg); + + ma_li(SecondScratchReg, ImmTag(JSVAL_TYPE_TO_TAG(type))); + ma_dsll(SecondScratchReg, SecondScratchReg, Imm32(JSVAL_TAG_SHIFT)); + ma_dins(SecondScratchReg, reg, Imm32(0), Imm32(JSVAL_TAG_SHIFT)); + storePtr(SecondScratchReg, Address(dest.base, dest.offset)); +} + +void +MacroAssemblerMIPS64Compat::storeValue(const Value& val, Address dest) +{ + jsval_layout jv = JSVAL_TO_IMPL(val); + if (val.isMarkable()) { + writeDataRelocation(val); + movWithPatch(ImmWord(jv.asBits), SecondScratchReg); + } else { + ma_li(SecondScratchReg, ImmWord(jv.asBits)); + } + storePtr(SecondScratchReg, Address(dest.base, dest.offset)); +} + +void +MacroAssemblerMIPS64Compat::storeValue(const Value& val, BaseIndex dest) +{ + computeScaledAddress(dest, ScratchRegister); + + int32_t offset = dest.offset; + if (!Imm16::IsInSignedRange(offset)) { + ma_li(SecondScratchReg, Imm32(offset)); + as_daddu(ScratchRegister, ScratchRegister, SecondScratchReg); + offset = 0; + } + storeValue(val, Address(ScratchRegister, offset)); +} + +void +MacroAssemblerMIPS64Compat::loadValue(const BaseIndex& addr, ValueOperand val) +{ + computeScaledAddress(addr, SecondScratchReg); + loadValue(Address(SecondScratchReg, addr.offset), val); +} + +void +MacroAssemblerMIPS64Compat::loadValue(Address src, ValueOperand val) +{ + loadPtr(Address(src.base, src.offset), val.valueReg()); +} + +void +MacroAssemblerMIPS64Compat::tagValue(JSValueType type, Register payload, ValueOperand dest) +{ + MOZ_ASSERT(dest.valueReg() != ScratchRegister); + if (payload != dest.valueReg()) + ma_move(dest.valueReg(), payload); + ma_li(ScratchRegister, ImmTag(JSVAL_TYPE_TO_TAG(type))); + ma_dins(dest.valueReg(), ScratchRegister, Imm32(JSVAL_TAG_SHIFT), Imm32(64 - JSVAL_TAG_SHIFT)); +} + +void +MacroAssemblerMIPS64Compat::pushValue(ValueOperand val) +{ + // Allocate stack slots for Value. One for each. + subPtr(Imm32(sizeof(Value)), StackPointer); + // Store Value + storeValue(val, Address(StackPointer, 0)); +} + +void +MacroAssemblerMIPS64Compat::pushValue(const Address& addr) +{ + // Load value before allocate stack, addr.base may be is sp. + loadPtr(Address(addr.base, addr.offset), ScratchRegister); + ma_dsubu(StackPointer, StackPointer, Imm32(sizeof(Value))); + storePtr(ScratchRegister, Address(StackPointer, 0)); +} + +void +MacroAssemblerMIPS64Compat::popValue(ValueOperand val) +{ + as_ld(val.valueReg(), StackPointer, 0); + as_daddiu(StackPointer, StackPointer, sizeof(Value)); +} + +void +MacroAssemblerMIPS64Compat::breakpoint() +{ + as_break(0); +} + +void +MacroAssemblerMIPS64Compat::ensureDouble(const ValueOperand& source, FloatRegister dest, + Label* failure) +{ + Label isDouble, done; + Register tag = splitTagForTest(source); + branchTestDouble(Assembler::Equal, tag, &isDouble); + branchTestInt32(Assembler::NotEqual, tag, failure); + + unboxInt32(source, ScratchRegister); + convertInt32ToDouble(ScratchRegister, dest); + jump(&done); + + bind(&isDouble); + unboxDouble(source, dest); + + bind(&done); +} + +void +MacroAssemblerMIPS64Compat::cmpPtrSet(Assembler::Condition cond, Address lhs, ImmPtr rhs, + Register dest) +{ + loadPtr(lhs, ScratchRegister); + movePtr(rhs, SecondScratchReg); + cmpPtrSet(cond, ScratchRegister, SecondScratchReg, dest); +} + +void +MacroAssemblerMIPS64Compat::cmpPtrSet(Assembler::Condition cond, Register lhs, Address rhs, + Register dest) +{ + loadPtr(rhs, ScratchRegister); + cmpPtrSet(cond, lhs, ScratchRegister, dest); +} + +void +MacroAssemblerMIPS64Compat::cmp32Set(Assembler::Condition cond, Register lhs, Address rhs, + Register dest) +{ + load32(rhs, ScratchRegister); + cmp32Set(cond, lhs, ScratchRegister, dest); +} + +void +MacroAssemblerMIPS64Compat::checkStackAlignment() +{ +#ifdef DEBUG + Label aligned; + as_andi(ScratchRegister, sp, ABIStackAlignment - 1); + ma_b(ScratchRegister, zero, &aligned, Equal, ShortJump); + as_break(BREAK_STACK_UNALIGNED); + bind(&aligned); +#endif +} + +void +MacroAssembler::alignFrameForICArguments(AfterICSaveLive& aic) +{ + if (framePushed() % ABIStackAlignment != 0) { + aic.alignmentPadding = ABIStackAlignment - (framePushed() % ABIStackAlignment); + reserveStack(aic.alignmentPadding); + } else { + aic.alignmentPadding = 0; + } + MOZ_ASSERT(framePushed() % ABIStackAlignment == 0); + checkStackAlignment(); +} + +void +MacroAssembler::restoreFrameAlignmentForICArguments(AfterICSaveLive& aic) +{ + if (aic.alignmentPadding != 0) + freeStack(aic.alignmentPadding); +} + +void +MacroAssemblerMIPS64Compat::handleFailureWithHandlerTail(void* handler) +{ + // Reserve space for exception information. + int size = (sizeof(ResumeFromException) + ABIStackAlignment) & ~(ABIStackAlignment - 1); + subPtr(Imm32(size), StackPointer); + ma_move(a0, StackPointer); // Use a0 since it is a first function argument + + // Call the handler. + asMasm().setupUnalignedABICall(a1); + asMasm().passABIArg(a0); + asMasm().callWithABI(handler); + + Label entryFrame; + Label catch_; + Label finally; + Label return_; + Label bailout; + + // Already clobbered a0, so use it... + load32(Address(StackPointer, offsetof(ResumeFromException, kind)), a0); + branch32(Assembler::Equal, a0, Imm32(ResumeFromException::RESUME_ENTRY_FRAME), &entryFrame); + branch32(Assembler::Equal, a0, Imm32(ResumeFromException::RESUME_CATCH), &catch_); + branch32(Assembler::Equal, a0, Imm32(ResumeFromException::RESUME_FINALLY), &finally); + branch32(Assembler::Equal, a0, Imm32(ResumeFromException::RESUME_FORCED_RETURN), &return_); + branch32(Assembler::Equal, a0, Imm32(ResumeFromException::RESUME_BAILOUT), &bailout); + + breakpoint(); // Invalid kind. + + // No exception handler. Load the error value, load the new stack pointer + // and return from the entry frame. + bind(&entryFrame); + moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand); + loadPtr(Address(StackPointer, offsetof(ResumeFromException, stackPointer)), StackPointer); + + // We're going to be returning by the ion calling convention + ma_pop(ra); + as_jr(ra); + as_nop(); + + // If we found a catch handler, this must be a baseline frame. Restore + // state and jump to the catch block. + bind(&catch_); + loadPtr(Address(StackPointer, offsetof(ResumeFromException, target)), a0); + loadPtr(Address(StackPointer, offsetof(ResumeFromException, framePointer)), BaselineFrameReg); + loadPtr(Address(StackPointer, offsetof(ResumeFromException, stackPointer)), StackPointer); + jump(a0); + + // If we found a finally block, this must be a baseline frame. Push + // two values expected by JSOP_RETSUB: BooleanValue(true) and the + // exception. + bind(&finally); + ValueOperand exception = ValueOperand(a1); + loadValue(Address(sp, offsetof(ResumeFromException, exception)), exception); + + loadPtr(Address(sp, offsetof(ResumeFromException, target)), a0); + loadPtr(Address(sp, offsetof(ResumeFromException, framePointer)), BaselineFrameReg); + loadPtr(Address(sp, offsetof(ResumeFromException, stackPointer)), sp); + + pushValue(BooleanValue(true)); + pushValue(exception); + jump(a0); + + // Only used in debug mode. Return BaselineFrame->returnValue() to the + // caller. + bind(&return_); + loadPtr(Address(StackPointer, offsetof(ResumeFromException, framePointer)), BaselineFrameReg); + loadPtr(Address(StackPointer, offsetof(ResumeFromException, stackPointer)), StackPointer); + loadValue(Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfReturnValue()), + JSReturnOperand); + ma_move(StackPointer, BaselineFrameReg); + pop(BaselineFrameReg); + + // If profiling is enabled, then update the lastProfilingFrame to refer to caller + // frame before returning. + { + Label skipProfilingInstrumentation; + // Test if profiler enabled. + AbsoluteAddress addressOfEnabled(GetJitContext()->runtime->spsProfiler().addressOfEnabled()); + branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + profilerExitFrame(); + bind(&skipProfilingInstrumentation); + } + + ret(); + + // If we are bailing out to baseline to handle an exception, jump to + // the bailout tail stub. + bind(&bailout); + loadPtr(Address(sp, offsetof(ResumeFromException, bailoutInfo)), a2); + ma_li(ReturnReg, Imm32(BAILOUT_RETURN_OK)); + loadPtr(Address(sp, offsetof(ResumeFromException, target)), a1); + jump(a1); +} + +CodeOffsetLabel +MacroAssemblerMIPS64Compat::toggledJump(Label* label) +{ + CodeOffsetLabel ret(nextOffset().getOffset()); + ma_b(label); + return ret; +} + +CodeOffsetLabel +MacroAssemblerMIPS64Compat::toggledCall(JitCode* target, bool enabled) +{ + BufferOffset bo = nextOffset(); + CodeOffsetLabel offset(bo.getOffset()); + addPendingJump(bo, ImmPtr(target->raw()), Relocation::JITCODE); + ma_liPatchable(ScratchRegister, ImmPtr(target->raw())); + if (enabled) { + as_jalr(ScratchRegister); + as_nop(); + } else { + as_nop(); + as_nop(); + } + MOZ_ASSERT_IF(!oom(), nextOffset().getOffset() - offset.offset() == ToggledCallSize(nullptr)); + return offset; +} + +void +MacroAssemblerMIPS64Compat::branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, + Label* label) +{ + MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); + MOZ_ASSERT(ptr != temp); + MOZ_ASSERT(ptr != SecondScratchReg); + + const Nursery& nursery = GetJitContext()->runtime->gcNursery(); + movePtr(ImmWord(-ptrdiff_t(nursery.start())), SecondScratchReg); + addPtr(ptr, SecondScratchReg); + branchPtr(cond == Assembler::Equal ? Assembler::Below : Assembler::AboveOrEqual, + SecondScratchReg, Imm32(nursery.nurserySize()), label); +} + +void +MacroAssemblerMIPS64Compat::branchValueIsNurseryObject(Condition cond, ValueOperand value, + Register temp, Label* label) +{ + MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); + + // 'Value' representing the start of the nursery tagged as a JSObject + const Nursery& nursery = GetJitContext()->runtime->gcNursery(); + Value start = ObjectValue(*reinterpret_cast(nursery.start())); + + movePtr(ImmWord(-ptrdiff_t(start.asRawBits())), SecondScratchReg); + addPtr(value.valueReg(), SecondScratchReg); + branchPtr(cond == Assembler::Equal ? Assembler::Below : Assembler::AboveOrEqual, + SecondScratchReg, Imm32(nursery.nurserySize()), label); +} + +void +MacroAssemblerMIPS64Compat::profilerEnterFrame(Register framePtr, Register scratch) +{ + AbsoluteAddress activation(GetJitContext()->runtime->addressOfProfilingActivation()); + loadPtr(activation, scratch); + storePtr(framePtr, Address(scratch, JitActivation::offsetOfLastProfilingFrame())); + storePtr(ImmPtr(nullptr), Address(scratch, JitActivation::offsetOfLastProfilingCallSite())); +} + +void +MacroAssemblerMIPS64Compat::profilerExitFrame() +{ + branch(GetJitContext()->runtime->jitRuntime()->getProfilerExitFrameTail()); +} + +//{{{ check_macroassembler_style +// =============================================================== +// Stack manipulation functions. + +void +MacroAssembler::PushRegsInMask(LiveRegisterSet set) +{ + int32_t diff = set.gprs().size() * sizeof(intptr_t) + + set.fpus().getPushSizeInBytes(); + const int32_t reserved = diff; + + reserveStack(reserved); + for (GeneralRegisterBackwardIterator iter(set.gprs()); iter.more(); iter++) { + diff -= sizeof(intptr_t); + storePtr(*iter, Address(StackPointer, diff)); + } + for (FloatRegisterBackwardIterator iter(set.fpus().reduceSetForPush()); iter.more(); iter++) { + diff -= sizeof(double); + storeDouble(*iter, Address(StackPointer, diff)); + } + MOZ_ASSERT(diff == 0); +} + +void +MacroAssembler::PopRegsInMaskIgnore(LiveRegisterSet set, LiveRegisterSet ignore) +{ + int32_t diff = set.gprs().size() * sizeof(intptr_t) + + set.fpus().getPushSizeInBytes(); + const int32_t reserved = diff; + + for (GeneralRegisterBackwardIterator iter(set.gprs()); iter.more(); iter++) { + diff -= sizeof(intptr_t); + if (!ignore.has(*iter)) + loadPtr(Address(StackPointer, diff), *iter); + } + for (FloatRegisterBackwardIterator iter(set.fpus().reduceSetForPush()); iter.more(); iter++) { + diff -= sizeof(double); + if (!ignore.has(*iter)) + loadDouble(Address(StackPointer, diff), *iter); + } + MOZ_ASSERT(diff == 0); + freeStack(reserved); +} + +void +MacroAssembler::reserveStack(uint32_t amount) +{ + if (amount) + subPtr(Imm32(amount), StackPointer); + adjustFrame(amount); +} + +// =============================================================== +// ABI function calls. + +void +MacroAssembler::setupUnalignedABICall(Register scratch) +{ + setupABICall(); + dynamicAlignment_ = true; + + ma_move(scratch, StackPointer); + + // Force sp to be aligned + subPtr(Imm32(sizeof(uintptr_t)), StackPointer); + ma_and(StackPointer, StackPointer, Imm32(~(ABIStackAlignment - 1))); + storePtr(scratch, Address(StackPointer, 0)); +} + +void +MacroAssembler::callWithABIPre(uint32_t* stackAdjust, bool callFromAsmJS) +{ + MOZ_ASSERT(inCall_); + uint32_t stackForCall = abiArgs_.stackBytesConsumedSoFar(); + + // Reserve place for $ra. + stackForCall += sizeof(intptr_t); + + if (dynamicAlignment_) { + stackForCall += ComputeByteAlignment(stackForCall, ABIStackAlignment); + } else { + uint32_t alignmentAtPrologue = callFromAsmJS ? sizeof(AsmJSFrame) : 0; + stackForCall += ComputeByteAlignment(stackForCall + framePushed() + alignmentAtPrologue, + ABIStackAlignment); + } + + *stackAdjust = stackForCall; + reserveStack(stackForCall); + + // Save $ra because call is going to clobber it. Restore it in + // callWithABIPost. NOTE: This is needed for calls from SharedIC. + // Maybe we can do this differently. + storePtr(ra, Address(StackPointer, stackForCall - sizeof(intptr_t))); + + // Position all arguments. + { + enoughMemory_ = enoughMemory_ && moveResolver_.resolve(); + if (!enoughMemory_) + return; + + MoveEmitter emitter(*this); + emitter.emit(moveResolver_); + emitter.finish(); + } + + assertStackAlignment(ABIStackAlignment); +} + +void +MacroAssembler::callWithABIPost(uint32_t stackAdjust, MoveOp::Type result) +{ + // Restore ra value (as stored in callWithABIPre()). + loadPtr(Address(StackPointer, stackAdjust - sizeof(intptr_t)), ra); + + if (dynamicAlignment_) { + // Restore sp value from stack (as stored in setupUnalignedABICall()). + loadPtr(Address(StackPointer, stackAdjust), StackPointer); + // Use adjustFrame instead of freeStack because we already restored sp. + adjustFrame(-stackAdjust); + } else { + freeStack(stackAdjust); + } + +#ifdef DEBUG + MOZ_ASSERT(inCall_); + inCall_ = false; +#endif +} + +void +MacroAssembler::callWithABINoProfiler(Register fun, MoveOp::Type result) +{ + // Load the callee in t9, no instruction between the lw and call + // should clobber it. Note that we can't use fun.base because it may + // be one of the IntArg registers clobbered before the call. + ma_move(t9, fun); + uint32_t stackAdjust; + callWithABIPre(&stackAdjust); + call(t9); + callWithABIPost(stackAdjust, result); +} + +void +MacroAssembler::callWithABINoProfiler(const Address& fun, MoveOp::Type result) +{ + // Load the callee in t9, as above. + loadPtr(Address(fun.base, fun.offset), t9); + uint32_t stackAdjust; + callWithABIPre(&stackAdjust); + call(t9); + callWithABIPost(stackAdjust, result); +} + +//}}} check_macroassembler_style diff --git a/js/src/jit/mips64/MacroAssembler-mips64.h b/js/src/jit/mips64/MacroAssembler-mips64.h new file mode 100644 index 0000000000..4422070f4b --- /dev/null +++ b/js/src/jit/mips64/MacroAssembler-mips64.h @@ -0,0 +1,1283 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_MacroAssembler_mips64_h +#define jit_mips64_MacroAssembler_mips64_h + +#include "jsopcode.h" + +#include "jit/AtomicOp.h" +#include "jit/IonCaches.h" +#include "jit/JitFrames.h" +#include "jit/mips-shared/MacroAssembler-mips-shared.h" +#include "jit/MoveResolver.h" + +namespace js { +namespace jit { + +enum LiFlags +{ + Li64 = 0, + Li48 = 1, +}; + +struct ImmShiftedTag : public ImmWord +{ + explicit ImmShiftedTag(JSValueShiftedTag shtag) + : ImmWord((uintptr_t)shtag) + { } + + explicit ImmShiftedTag(JSValueType type) + : ImmWord(uintptr_t(JSValueShiftedTag(JSVAL_TYPE_TO_SHIFTED_TAG(type)))) + { } +}; + +struct ImmTag : public Imm32 +{ + ImmTag(JSValueTag mask) + : Imm32(int32_t(mask)) + { } +}; + +static const ValueOperand JSReturnOperand = ValueOperand(JSReturnReg); + +static const int defaultShift = 3; +static_assert(1 << defaultShift == sizeof(JS::Value), "The defaultShift is wrong"); + +class MacroAssemblerMIPS64 : public MacroAssemblerMIPSShared +{ + public: + using MacroAssemblerMIPSShared::ma_b; + using MacroAssemblerMIPSShared::ma_li; + using MacroAssemblerMIPSShared::ma_ss; + using MacroAssemblerMIPSShared::ma_sd; + using MacroAssemblerMIPSShared::ma_load; + using MacroAssemblerMIPSShared::ma_store; + using MacroAssemblerMIPSShared::ma_cmp_set; + using MacroAssemblerMIPSShared::ma_subTestOverflow; + + void ma_li(Register dest, AbsoluteLabel* label); + void ma_li(Register dest, ImmWord imm); + void ma_liPatchable(Register dest, ImmPtr imm); + void ma_liPatchable(Register dest, ImmWord imm, LiFlags flags = Li48); + + // Shift operations + void ma_dsll(Register rd, Register rt, Imm32 shift); + void ma_dsrl(Register rd, Register rt, Imm32 shift); + void ma_dsra(Register rd, Register rt, Imm32 shift); + void ma_dror(Register rd, Register rt, Imm32 shift); + void ma_drol(Register rd, Register rt, Imm32 shift); + + void ma_dsll(Register rd, Register rt, Register shift); + void ma_dsrl(Register rd, Register rt, Register shift); + void ma_dsra(Register rd, Register rt, Register shift); + void ma_dror(Register rd, Register rt, Register shift); + void ma_drol(Register rd, Register rt, Register shift); + + void ma_dins(Register rt, Register rs, Imm32 pos, Imm32 size); + void ma_dext(Register rt, Register rs, Imm32 pos, Imm32 size); + + // load + void ma_load(Register dest, Address address, LoadStoreSize size = SizeWord, + LoadStoreExtension extension = SignExtend); + + // store + void ma_store(Register data, Address address, LoadStoreSize size = SizeWord, + LoadStoreExtension extension = SignExtend); + + // arithmetic based ops + // add + void ma_daddu(Register rd, Register rs, Imm32 imm); + void ma_daddu(Register rd, Register rs); + void ma_daddu(Register rd, Imm32 imm); + void ma_addTestOverflow(Register rd, Register rs, Register rt, Label* overflow); + void ma_addTestOverflow(Register rd, Register rs, Imm32 imm, Label* overflow); + + // subtract + void ma_dsubu(Register rd, Register rs, Imm32 imm); + void ma_dsubu(Register rd, Imm32 imm); + void ma_subTestOverflow(Register rd, Register rs, Register rt, Label* overflow); + + // multiplies. For now, there are only few that we care about. + void ma_dmult(Register rs, Imm32 imm); + + // stack + void ma_pop(Register r); + void ma_push(Register r); + + void branchWithCode(InstImm code, Label* label, JumpKind jumpKind); + // branches when done from within mips-specific code + void ma_b(Register lhs, ImmWord imm, Label* l, Condition c, JumpKind jumpKind = LongJump); + void ma_b(Register lhs, Address addr, Label* l, Condition c, JumpKind jumpKind = LongJump); + void ma_b(Address addr, Imm32 imm, Label* l, Condition c, JumpKind jumpKind = LongJump); + void ma_b(Address addr, ImmGCPtr imm, Label* l, Condition c, JumpKind jumpKind = LongJump); + void ma_b(Address addr, Register rhs, Label* l, Condition c, JumpKind jumpKind = LongJump) { + MOZ_ASSERT(rhs != ScratchRegister); + ma_load(ScratchRegister, addr, SizeDouble); + ma_b(ScratchRegister, rhs, l, c, jumpKind); + } + + void ma_bal(Label* l, DelaySlotFill delaySlotFill = FillDelaySlot); + + // fp instructions + void ma_lid(FloatRegister dest, double value); + + void ma_mv(FloatRegister src, ValueOperand dest); + void ma_mv(ValueOperand src, FloatRegister dest); + + void ma_ls(FloatRegister fd, Address address); + void ma_ld(FloatRegister fd, Address address); + void ma_sd(FloatRegister fd, Address address); + void ma_ss(FloatRegister fd, Address address); + + void ma_pop(FloatRegister fs); + void ma_push(FloatRegister fs); + + void ma_cmp_set(Register dst, Register lhs, ImmWord imm, Condition c); + void ma_cmp_set(Register dst, Register lhs, ImmPtr imm, Condition c); + + // These functions abstract the access to high part of the double precision + // float register. They are intended to work on both 32 bit and 64 bit + // floating point coprocessor. + void moveToDoubleHi(Register src, FloatRegister dest) { + as_mthc1(src, dest); + } + void moveFromDoubleHi(FloatRegister src, Register dest) { + as_mfhc1(dest, src); + } + + void moveToDouble(Register src, FloatRegister dest) { + as_dmtc1(src, dest); + } + void moveFromDouble(FloatRegister src, Register dest) { + as_dmfc1(dest, src); + } +}; + +class MacroAssembler; + +class MacroAssemblerMIPS64Compat : public MacroAssemblerMIPS64 +{ + public: + using MacroAssemblerMIPS64::call; + + MacroAssemblerMIPS64Compat() + { } + + void convertBoolToInt32(Register source, Register dest); + void convertInt32ToDouble(Register src, FloatRegister dest); + void convertInt32ToDouble(const Address& src, FloatRegister dest); + void convertInt32ToDouble(const BaseIndex& src, FloatRegister dest); + void convertUInt32ToDouble(Register src, FloatRegister dest); + void convertUInt32ToFloat32(Register src, FloatRegister dest); + void convertDoubleToFloat32(FloatRegister src, FloatRegister dest); + void branchTruncateDouble(FloatRegister src, Register dest, Label* fail); + void convertDoubleToInt32(FloatRegister src, Register dest, Label* fail, + bool negativeZeroCheck = true); + void convertFloat32ToInt32(FloatRegister src, Register dest, Label* fail, + bool negativeZeroCheck = true); + + void convertFloat32ToDouble(FloatRegister src, FloatRegister dest); + void branchTruncateFloat32(FloatRegister src, Register dest, Label* fail); + void convertInt32ToFloat32(Register src, FloatRegister dest); + void convertInt32ToFloat32(const Address& src, FloatRegister dest); + + void addDouble(FloatRegister src, FloatRegister dest); + void subDouble(FloatRegister src, FloatRegister dest); + void mulDouble(FloatRegister src, FloatRegister dest); + void divDouble(FloatRegister src, FloatRegister dest); + + void negateDouble(FloatRegister reg); + void inc64(AbsoluteAddress dest); + + void movq(Register rs, Register rd); + + void computeScaledAddress(const BaseIndex& address, Register dest); + + void computeEffectiveAddress(const Address& address, Register dest) { + ma_daddu(dest, address.base, Imm32(address.offset)); + } + + void computeEffectiveAddress(const BaseIndex& address, Register dest) { + computeScaledAddress(address, dest); + if (address.offset) { + addPtr(Imm32(address.offset), dest); + } + } + + void j(Label* dest) { + ma_b(dest); + } + + void mov(Register src, Register dest) { + as_ori(dest, src, 0); + } + void mov(ImmWord imm, Register dest) { + ma_li(dest, imm); + } + void mov(ImmPtr imm, Register dest) { + mov(ImmWord(uintptr_t(imm.value)), dest); + } + void mov(Register src, Address dest) { + MOZ_CRASH("NYI-IC"); + } + void mov(Address src, Register dest) { + MOZ_CRASH("NYI-IC"); + } + + void writeDataRelocation(const Value& val) { + if (val.isMarkable()) { + gc::Cell* cell = reinterpret_cast(val.toGCThing()); + if (cell && gc::IsInsideNursery(cell)) + embedsNurseryPointers_ = true; + dataRelocations_.writeUnsigned(currentOffset()); + } + } + + void branch(JitCode* c) { + BufferOffset bo = m_buffer.nextOffset(); + addPendingJump(bo, ImmPtr(c->raw()), Relocation::JITCODE); + ma_liPatchable(ScratchRegister, ImmPtr(c->raw())); + as_jr(ScratchRegister); + as_nop(); + } + void branch(const Register reg) { + as_jr(reg); + as_nop(); + } + void nop() { + as_nop(); + } + void ret() { + ma_pop(ra); + as_jr(ra); + as_nop(); + } + void retn(Imm32 n) { + // pc <- [sp]; sp += n + loadPtr(Address(StackPointer, 0), ra); + addPtr(n, StackPointer); + as_jr(ra); + as_nop(); + } + void push(Imm32 imm) { + ma_li(ScratchRegister, imm); + ma_push(ScratchRegister); + } + void push(ImmWord imm) { + ma_li(ScratchRegister, imm); + ma_push(ScratchRegister); + } + void push(ImmGCPtr imm) { + ma_li(ScratchRegister, imm); + ma_push(ScratchRegister); + } + void push(const Address& address) { + loadPtr(address, ScratchRegister); + ma_push(ScratchRegister); + } + void push(Register reg) { + ma_push(reg); + } + void push(FloatRegister reg) { + ma_push(reg); + } + void pop(Register reg) { + ma_pop(reg); + } + void pop(FloatRegister reg) { + ma_pop(reg); + } + + // Emit a branch that can be toggled to a non-operation. On MIPS64 we use + // "andi" instruction to toggle the branch. + // See ToggleToJmp(), ToggleToCmp(). + CodeOffsetLabel toggledJump(Label* label); + + // Emit a "jalr" or "nop" instruction. ToggleCall can be used to patch + // this instruction. + CodeOffsetLabel toggledCall(JitCode* target, bool enabled); + + static size_t ToggledCallSize(uint8_t* code) { + // Six instructions used in: MacroAssemblerMIPS64Compat::toggledCall + return 6 * sizeof(uint32_t); + } + + CodeOffsetLabel pushWithPatch(ImmWord imm) { + CodeOffsetLabel label = movWithPatch(imm, ScratchRegister); + ma_push(ScratchRegister); + return label; + } + + CodeOffsetLabel movWithPatch(ImmWord imm, Register dest) { + CodeOffsetLabel label = CodeOffsetLabel(currentOffset()); + ma_liPatchable(dest, imm, Li64); + return label; + } + CodeOffsetLabel movWithPatch(ImmPtr imm, Register dest) { + CodeOffsetLabel label = CodeOffsetLabel(currentOffset()); + ma_liPatchable(dest, imm); + return label; + } + + void jump(Label* label) { + ma_b(label); + } + void jump(Register reg) { + as_jr(reg); + as_nop(); + } + void jump(const Address& address) { + loadPtr(address, ScratchRegister); + as_jr(ScratchRegister); + as_nop(); + } + + void jump(JitCode* code) { + branch(code); + } + + void neg32(Register reg) { + ma_negu(reg, reg); + } + + void splitTag(Register src, Register dest) { + ma_dsrl(dest, src, Imm32(JSVAL_TAG_SHIFT)); + } + + void splitTag(const ValueOperand& operand, Register dest) { + splitTag(operand.valueReg(), dest); + } + + // Returns the register containing the type tag. + Register splitTagForTest(const ValueOperand& value) { + splitTag(value, SecondScratchReg); + return SecondScratchReg; + } + + void branchTestGCThing(Condition cond, const Address& address, Label* label); + void branchTestGCThing(Condition cond, const BaseIndex& src, Label* label); + + void branchTestPrimitive(Condition cond, const ValueOperand& value, Label* label); + void branchTestPrimitive(Condition cond, Register tag, Label* label); + + void branchTestValue(Condition cond, const ValueOperand& value, const Value& v, Label* label); + void branchTestValue(Condition cond, const Address& valaddr, const ValueOperand& value, + Label* label); + + // unboxing code + void unboxNonDouble(const ValueOperand& operand, Register dest); + void unboxNonDouble(const Address& src, Register dest); + void unboxNonDouble(const BaseIndex& src, Register dest); + void unboxInt32(const ValueOperand& operand, Register dest); + void unboxInt32(const Operand& operand, Register dest); + void unboxInt32(const Address& src, Register dest); + void unboxInt32(const BaseIndex& src, Register dest); + void unboxBoolean(const ValueOperand& operand, Register dest); + void unboxBoolean(const Operand& operand, Register dest); + void unboxBoolean(const Address& src, Register dest); + void unboxBoolean(const BaseIndex& src, Register dest); + void unboxDouble(const ValueOperand& operand, FloatRegister dest); + void unboxDouble(const Address& src, FloatRegister dest); + void unboxString(const ValueOperand& operand, Register dest); + void unboxString(const Operand& operand, Register dest); + void unboxString(const Address& src, Register dest); + void unboxSymbol(const ValueOperand& src, Register dest); + void unboxSymbol(const Operand& src, Register dest); + void unboxSymbol(const Address& src, Register dest); + void unboxObject(const ValueOperand& src, Register dest); + void unboxObject(const Operand& src, Register dest); + void unboxObject(const Address& src, Register dest); + void unboxObject(const BaseIndex& src, Register dest) { unboxNonDouble(src, dest); } + void unboxValue(const ValueOperand& src, AnyRegister dest); + void unboxPrivate(const ValueOperand& src, Register dest); + + void notBoolean(const ValueOperand& val) { + as_xori(val.valueReg(), val.valueReg(), 1); + } + + // boxing code + void boxDouble(FloatRegister src, const ValueOperand& dest); + void boxNonDouble(JSValueType type, Register src, const ValueOperand& dest); + + // Extended unboxing API. If the payload is already in a register, returns + // that register. Otherwise, provides a move to the given scratch register, + // and returns that. + Register extractObject(const Address& address, Register scratch); + Register extractObject(const ValueOperand& value, Register scratch) { + unboxObject(value, scratch); + return scratch; + } + Register extractInt32(const ValueOperand& value, Register scratch) { + unboxInt32(value, scratch); + return scratch; + } + Register extractBoolean(const ValueOperand& value, Register scratch) { + unboxBoolean(value, scratch); + return scratch; + } + Register extractTag(const Address& address, Register scratch); + Register extractTag(const BaseIndex& address, Register scratch); + Register extractTag(const ValueOperand& value, Register scratch) { + MOZ_ASSERT(scratch != ScratchRegister); + splitTag(value, scratch); + return scratch; + } + + void boolValueToDouble(const ValueOperand& operand, FloatRegister dest); + void int32ValueToDouble(const ValueOperand& operand, FloatRegister dest); + void loadInt32OrDouble(const Address& src, FloatRegister dest); + void loadInt32OrDouble(const BaseIndex& addr, FloatRegister dest); + void loadConstantDouble(double dp, FloatRegister dest); + + void boolValueToFloat32(const ValueOperand& operand, FloatRegister dest); + void int32ValueToFloat32(const ValueOperand& operand, FloatRegister dest); + void loadConstantFloat32(float f, FloatRegister dest); + + void branchTestInt32(Condition cond, const ValueOperand& value, Label* label); + void branchTestInt32(Condition cond, Register tag, Label* label); + void branchTestInt32(Condition cond, const Address& address, Label* label); + void branchTestInt32(Condition cond, const BaseIndex& src, Label* label); + + void branchTestBoolean(Condition cond, const ValueOperand& value, Label* label); + void branchTestBoolean(Condition cond, Register tag, Label* label); + void branchTestBoolean(Condition cond, const BaseIndex& src, Label* label); + + void branch32(Condition cond, Register lhs, Register rhs, Label* label) { + ma_b(lhs, rhs, label, cond); + } + void branch32(Condition cond, Register lhs, Imm32 imm, Label* label) { + ma_b(lhs, imm, label, cond); + } + void branch32(Condition cond, const Operand& lhs, Register rhs, Label* label) { + if (lhs.getTag() == Operand::REG) { + ma_b(lhs.toReg(), rhs, label, cond); + } else { + branch32(cond, lhs.toAddress(), rhs, label); + } + } + void branch32(Condition cond, const Operand& lhs, Imm32 rhs, Label* label) { + if (lhs.getTag() == Operand::REG) { + ma_b(lhs.toReg(), rhs, label, cond); + } else { + branch32(cond, lhs.toAddress(), rhs, label); + } + } + void branch32(Condition cond, const Address& lhs, Register rhs, Label* label) { + load32(lhs, SecondScratchReg); + ma_b(SecondScratchReg, rhs, label, cond); + } + void branch32(Condition cond, const Address& lhs, Imm32 rhs, Label* label) { + load32(lhs, SecondScratchReg); + ma_b(SecondScratchReg, rhs, label, cond); + } + void branch32(Condition cond, const BaseIndex& lhs, Imm32 rhs, Label* label) { + load32(lhs, SecondScratchReg); + ma_b(SecondScratchReg, rhs, label, cond); + } + void branchPtr(Condition cond, const Address& lhs, Register rhs, Label* label) { + loadPtr(lhs, SecondScratchReg); + ma_b(SecondScratchReg, rhs, label, cond); + } + + void branchPrivatePtr(Condition cond, const Address& lhs, ImmPtr ptr, Label* label) { + branchPtr(cond, lhs, ptr, label); + } + + void branchPrivatePtr(Condition cond, const Address& lhs, Register ptr, Label* label) { + branchPtr(cond, lhs, ptr, label); + } + + void branchPrivatePtr(Condition cond, Register lhs, ImmWord ptr, Label* label) { + branchPtr(cond, lhs, ptr, label); + } + + void branchTestDouble(Condition cond, const ValueOperand& value, Label* label); + void branchTestDouble(Condition cond, Register tag, Label* label); + void branchTestDouble(Condition cond, const Address& address, Label* label); + void branchTestDouble(Condition cond, const BaseIndex& src, Label* label); + + void branchTestNull(Condition cond, const ValueOperand& value, Label* label); + void branchTestNull(Condition cond, Register tag, Label* label); + void branchTestNull(Condition cond, const BaseIndex& src, Label* label); + void branchTestNull(Condition cond, const Address& address, Label* label); + void testNullSet(Condition cond, const ValueOperand& value, Register dest); + + void branchTestObject(Condition cond, const ValueOperand& value, Label* label); + void branchTestObject(Condition cond, Register tag, Label* label); + void branchTestObject(Condition cond, const BaseIndex& src, Label* label); + void branchTestObject(Condition cond, const Address& src, Label* label); + void testObjectSet(Condition cond, const ValueOperand& value, Register dest); + + void branchTestString(Condition cond, const ValueOperand& value, Label* label); + void branchTestString(Condition cond, Register tag, Label* label); + void branchTestString(Condition cond, const BaseIndex& src, Label* label); + + void branchTestSymbol(Condition cond, const ValueOperand& value, Label* label); + void branchTestSymbol(Condition cond, const Register& tag, Label* label); + void branchTestSymbol(Condition cond, const BaseIndex& src, Label* label); + + void branchTestUndefined(Condition cond, const ValueOperand& value, Label* label); + void branchTestUndefined(Condition cond, Register tag, Label* label); + void branchTestUndefined(Condition cond, const BaseIndex& src, Label* label); + void branchTestUndefined(Condition cond, const Address& address, Label* label); + void testUndefinedSet(Condition cond, const ValueOperand& value, Register dest); + + void branchTestNumber(Condition cond, const ValueOperand& value, Label* label); + void branchTestNumber(Condition cond, Register tag, Label* label); + + void branchTestMagic(Condition cond, const ValueOperand& value, Label* label); + void branchTestMagic(Condition cond, Register tag, Label* label); + void branchTestMagic(Condition cond, const Address& address, Label* label); + void branchTestMagic(Condition cond, const BaseIndex& src, Label* label); + + void branchTestMagicValue(Condition cond, const ValueOperand& val, JSWhyMagic why, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + branchTestValue(cond, val, MagicValue(why), label); + } + + void branchTestInt32Truthy(bool b, const ValueOperand& value, Label* label); + + void branchTestStringTruthy(bool b, const ValueOperand& value, Label* label); + + void branchTestDoubleTruthy(bool b, FloatRegister value, Label* label); + + void branchTestBooleanTruthy(bool b, const ValueOperand& operand, Label* label); + + void branchTest32(Condition cond, Register lhs, Register rhs, Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || cond == NotSigned); + if (lhs == rhs) { + ma_b(lhs, rhs, label, cond); + } else { + as_and(ScratchRegister, lhs, rhs); + ma_b(ScratchRegister, ScratchRegister, label, cond); + } + } + void branchTest32(Condition cond, Register lhs, Imm32 imm, Label* label) { + ma_li(ScratchRegister, imm); + branchTest32(cond, lhs, ScratchRegister, label); + } + void branchTest32(Condition cond, const Address& address, Imm32 imm, Label* label) { + load32(address, SecondScratchReg); + branchTest32(cond, SecondScratchReg, imm, label); + } + void branchTest32(Condition cond, AbsoluteAddress address, Imm32 imm, Label* label) { + load32(address, ScratchRegister); + branchTest32(cond, ScratchRegister, imm, label); + } + void branchTestPtr(Condition cond, Register lhs, Register rhs, Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || cond == NotSigned); + if (lhs == rhs) { + ma_b(lhs, rhs, label, cond); + } else { + as_and(ScratchRegister, lhs, rhs); + ma_b(ScratchRegister, ScratchRegister, label, cond); + } + } + void branchTestPtr(Condition cond, Register lhs, const Imm32 rhs, Label* label) { + ma_li(ScratchRegister, rhs); + branchTestPtr(cond, lhs, ScratchRegister, label); + } + void branchTestPtr(Condition cond, const Address& lhs, Imm32 imm, Label* label) { + loadPtr(lhs, SecondScratchReg); + branchTestPtr(cond, SecondScratchReg, imm, label); + } + void branchTest64(Condition cond, Register64 lhs, Register64 rhs, Register temp, + Label* label) { + branchTestPtr(cond, lhs.reg, rhs.reg, label); + } + void branchPtr(Condition cond, Register lhs, Register rhs, Label* label) { + ma_b(lhs, rhs, label, cond); + } + void branchPtr(Condition cond, Register lhs, ImmGCPtr ptr, Label* label) { + ma_b(lhs, ptr, label, cond); + } + void branchPtr(Condition cond, Register lhs, ImmWord imm, Label* label) { + ma_b(lhs, imm, label, cond); + } + void branchPtr(Condition cond, Register lhs, ImmPtr imm, Label* label) { + ma_b(lhs, imm, label, cond); + } + void branchPtr(Condition cond, Register lhs, AsmJSImmPtr imm, Label* label) { + movePtr(imm, SecondScratchReg); + ma_b(lhs, SecondScratchReg, label, cond); + } + void branchPtr(Condition cond, Register lhs, Imm32 imm, Label* label) { + ma_b(lhs, imm, label, cond); + } + void decBranchPtr(Condition cond, Register lhs, Imm32 imm, Label* label) { + subPtr(imm, lhs); + branchPtr(cond, lhs, Imm32(0), label); + } + + // higher level tag testing code + Address ToPayload(Address value) { + return value; + } + + void moveValue(const Value& val, Register dest); + + CodeOffsetJump backedgeJump(RepatchLabel* label, Label* documentation = nullptr); + CodeOffsetJump jumpWithPatch(RepatchLabel* label, Label* documentation = nullptr); + + template + CodeOffsetJump branchPtrWithPatch(Condition cond, Register reg, T ptr, RepatchLabel* label) { + movePtr(ptr, ScratchRegister); + Label skipJump; + ma_b(reg, ScratchRegister, &skipJump, InvertCondition(cond), ShortJump); + CodeOffsetJump off = jumpWithPatch(label); + bind(&skipJump); + return off; + } + + template + CodeOffsetJump branchPtrWithPatch(Condition cond, Address addr, T ptr, RepatchLabel* label) { + loadPtr(addr, SecondScratchReg); + movePtr(ptr, ScratchRegister); + Label skipJump; + ma_b(SecondScratchReg, ScratchRegister, &skipJump, InvertCondition(cond), ShortJump); + CodeOffsetJump off = jumpWithPatch(label); + bind(&skipJump); + return off; + } + void branchPtr(Condition cond, Address addr, ImmGCPtr ptr, Label* label) { + loadPtr(addr, SecondScratchReg); + ma_b(SecondScratchReg, ptr, label, cond); + } + + void branchPtr(Condition cond, Address addr, ImmWord ptr, Label* label) { + loadPtr(addr, SecondScratchReg); + ma_b(SecondScratchReg, ptr, label, cond); + } + void branchPtr(Condition cond, Address addr, ImmPtr ptr, Label* label) { + loadPtr(addr, SecondScratchReg); + ma_b(SecondScratchReg, ptr, label, cond); + } + void branchPtr(Condition cond, AbsoluteAddress addr, Register ptr, Label* label) { + loadPtr(addr, SecondScratchReg); + ma_b(SecondScratchReg, ptr, label, cond); + } + void branchPtr(Condition cond, AbsoluteAddress addr, ImmWord ptr, Label* label) { + loadPtr(addr, SecondScratchReg); + ma_b(SecondScratchReg, ptr, label, cond); + } + void branchPtr(Condition cond, AsmJSAbsoluteAddress addr, Register ptr, Label* label) { + loadPtr(addr, SecondScratchReg); + ma_b(SecondScratchReg, ptr, label, cond); + } + void branch32(Condition cond, AbsoluteAddress lhs, Imm32 rhs, Label* label) { + load32(lhs, SecondScratchReg); + ma_b(SecondScratchReg, rhs, label, cond); + } + void branch32(Condition cond, AbsoluteAddress lhs, Register rhs, Label* label) { + load32(lhs, SecondScratchReg); + ma_b(SecondScratchReg, rhs, label, cond); + } + void branch32(Condition cond, AsmJSAbsoluteAddress addr, Imm32 imm, Label* label) { + load32(addr, SecondScratchReg); + ma_b(SecondScratchReg, imm, label, cond); + } + + template + void loadUnboxedValue(const T& address, MIRType type, AnyRegister dest) { + if (dest.isFloat()) + loadInt32OrDouble(address, dest.fpu()); + else if (type == MIRType_Int32) + unboxInt32(address, dest.gpr()); + else if (type == MIRType_Boolean) + unboxBoolean(address, dest.gpr()); + else + unboxNonDouble(address, dest.gpr()); + } + + template + void storeUnboxedValue(ConstantOrRegister value, MIRType valueType, const T& dest, + MIRType slotType); + + template + void storeUnboxedPayload(ValueOperand value, T address, size_t nbytes) { + switch (nbytes) { + case 8: + unboxNonDouble(value, ScratchRegister); + storePtr(ScratchRegister, address); + return; + case 4: + store32(value.valueReg(), address); + return; + case 1: + store8(value.valueReg(), address); + return; + default: MOZ_CRASH("Bad payload width"); + } + } + + void moveValue(const Value& val, const ValueOperand& dest); + + void moveValue(const ValueOperand& src, const ValueOperand& dest) { + if (src.valueReg() != dest.valueReg()) + ma_move(dest.valueReg(), src.valueReg()); + } + void boxValue(JSValueType type, Register src, Register dest) { + MOZ_ASSERT(src != dest); + + JSValueTag tag = (JSValueTag)JSVAL_TYPE_TO_TAG(type); + ma_li(dest, Imm32(tag)); + ma_dsll(dest, dest, Imm32(JSVAL_TAG_SHIFT)); + ma_dins(dest, src, Imm32(0), Imm32(JSVAL_TAG_SHIFT)); + } + + void storeValue(ValueOperand val, Operand dst); + void storeValue(ValueOperand val, const BaseIndex& dest); + void storeValue(JSValueType type, Register reg, BaseIndex dest); + void storeValue(ValueOperand val, const Address& dest); + void storeValue(JSValueType type, Register reg, Address dest); + void storeValue(const Value& val, Address dest); + void storeValue(const Value& val, BaseIndex dest); + + void loadValue(Address src, ValueOperand val); + void loadValue(Operand dest, ValueOperand val) { + loadValue(dest.toAddress(), val); + } + void loadValue(const BaseIndex& addr, ValueOperand val); + void tagValue(JSValueType type, Register payload, ValueOperand dest); + + void pushValue(ValueOperand val); + void popValue(ValueOperand val); + void pushValue(const Value& val) { + jsval_layout jv = JSVAL_TO_IMPL(val); + if (val.isMarkable()) { + writeDataRelocation(val); + movWithPatch(ImmWord(jv.asBits), ScratchRegister); + push(ScratchRegister); + } else { + push(ImmWord(jv.asBits)); + } + } + void pushValue(JSValueType type, Register reg) { + boxValue(type, reg, ScratchRegister); + push(ScratchRegister); + } + void pushValue(const Address& addr); + + void handleFailureWithHandlerTail(void* handler); + + ///////////////////////////////////////////////////////////////// + // Common interface. + ///////////////////////////////////////////////////////////////// + public: + // The following functions are exposed for use in platform-shared code. + + template + void compareExchange8SignExtend(const T& mem, Register oldval, Register newval, Register output) + { + MOZ_CRASH("NYI"); + } + template + void compareExchange8ZeroExtend(const T& mem, Register oldval, Register newval, Register output) + { + MOZ_CRASH("NYI"); + } + template + void compareExchange16SignExtend(const T& mem, Register oldval, Register newval, Register output) + { + MOZ_CRASH("NYI"); + } + template + void compareExchange16ZeroExtend(const T& mem, Register oldval, Register newval, Register output) + { + MOZ_CRASH("NYI"); + } + template + void compareExchange32(const T& mem, Register oldval, Register newval, Register output) + { + MOZ_CRASH("NYI"); + } + + template + void atomicExchange8SignExtend(const T& mem, Register value, Register output) + { + MOZ_CRASH("NYI"); + } + template + void atomicExchange8ZeroExtend(const T& mem, Register value, Register output) + { + MOZ_CRASH("NYI"); + } + template + void atomicExchange16SignExtend(const T& mem, Register value, Register output) + { + MOZ_CRASH("NYI"); + } + template + void atomicExchange16ZeroExtend(const T& mem, Register value, Register output) + { + MOZ_CRASH("NYI"); + } + template + void atomicExchange32(const T& mem, Register value, Register output) + { + MOZ_CRASH("NYI"); + } + + template + void atomicFetchAdd8SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchAdd8ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchAdd16SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchAdd16ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchAdd32(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicAdd8(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template + void atomicAdd16(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template + void atomicAdd32(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + + template + void atomicFetchSub8SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchSub8ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchSub16SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchSub16ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchSub32(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template void atomicSub8(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template void atomicSub16(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template void atomicSub32(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + + template + void atomicFetchAnd8SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchAnd8ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchAnd16SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchAnd16ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchAnd32(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicAnd8(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template + void atomicAnd16(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template + void atomicAnd32(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + + template + void atomicFetchOr8SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchOr8ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchOr16SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchOr16ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchOr32(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicOr8(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template + void atomicOr16(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template + void atomicOr32(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + + template + void atomicFetchXor8SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchXor8ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchXor16SignExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchXor16ZeroExtend(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicFetchXor32(const S& value, const T& mem, Register temp, Register output) { + MOZ_CRASH("NYI"); + } + template + void atomicXor8(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template + void atomicXor16(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + template + void atomicXor32(const T& value, const S& mem) { + MOZ_CRASH("NYI"); + } + + void add32(Register src, Register dest); + void add32(Imm32 imm, Register dest); + void add32(Imm32 imm, const Address& dest); + void add64(Imm32 imm, Register64 dest) { + ma_daddu(dest.reg, imm); + } + void sub32(Imm32 imm, Register dest); + void sub32(Register src, Register dest); + + void incrementInt32Value(const Address& addr) { + add32(Imm32(1), addr); + } + + template + void branchAdd32(Condition cond, T src, Register dest, Label* overflow) { + switch (cond) { + case Overflow: + ma_addTestOverflow(dest, dest, src, overflow); + break; + default: + MOZ_CRASH("NYI"); + } + } + template + void branchSub32(Condition cond, T src, Register dest, Label* overflow) { + switch (cond) { + case Overflow: + ma_subTestOverflow(dest, dest, src, overflow); + break; + case NonZero: + case Zero: + sub32(src, dest); + ma_b(dest, dest, overflow, cond); + break; + default: + MOZ_CRASH("NYI"); + } + } + + void addPtr(Register src, Register dest); + void subPtr(Register src, Register dest); + void addPtr(const Address& src, Register dest); + + void move32(Imm32 imm, Register dest); + void move32(Register src, Register dest); + void move64(Register64 src, Register64 dest) { + movePtr(src.reg, dest.reg); + } + + void movePtr(Register src, Register dest); + void movePtr(ImmWord imm, Register dest); + void movePtr(ImmPtr imm, Register dest); + void movePtr(AsmJSImmPtr imm, Register dest); + void movePtr(ImmGCPtr imm, Register dest); + + void load8SignExtend(const Address& address, Register dest); + void load8SignExtend(const BaseIndex& src, Register dest); + + void load8ZeroExtend(const Address& address, Register dest); + void load8ZeroExtend(const BaseIndex& src, Register dest); + + void load16SignExtend(const Address& address, Register dest); + void load16SignExtend(const BaseIndex& src, Register dest); + + void load16ZeroExtend(const Address& address, Register dest); + void load16ZeroExtend(const BaseIndex& src, Register dest); + + void load32(const Address& address, Register dest); + void load32(const BaseIndex& address, Register dest); + void load32(AbsoluteAddress address, Register dest); + void load32(AsmJSAbsoluteAddress address, Register dest); + void load64(const Address& address, Register64 dest) { + loadPtr(address, dest.reg); + } + + void loadPtr(const Address& address, Register dest); + void loadPtr(const BaseIndex& src, Register dest); + void loadPtr(AbsoluteAddress address, Register dest); + void loadPtr(AsmJSAbsoluteAddress address, Register dest); + + void loadPrivate(const Address& address, Register dest); + + void loadInt32x1(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x1(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x2(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadInt32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x1(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x2(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeInt32x3(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void loadAlignedInt32x4(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedInt32x4(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void loadUnalignedInt32x4(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedInt32x4(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedInt32x4(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void storeUnalignedInt32x4(FloatRegister src, BaseIndex addr) { MOZ_CRASH("NYI"); } + + void loadFloat32x3(const Address& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadFloat32x3(const BaseIndex& src, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeFloat32x3(FloatRegister src, const Address& dest) { MOZ_CRASH("NYI"); } + void storeFloat32x3(FloatRegister src, const BaseIndex& dest) { MOZ_CRASH("NYI"); } + void loadAlignedFloat32x4(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeAlignedFloat32x4(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void loadUnalignedFloat32x4(const Address& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void loadUnalignedFloat32x4(const BaseIndex& addr, FloatRegister dest) { MOZ_CRASH("NYI"); } + void storeUnalignedFloat32x4(FloatRegister src, Address addr) { MOZ_CRASH("NYI"); } + void storeUnalignedFloat32x4(FloatRegister src, BaseIndex addr) { MOZ_CRASH("NYI"); } + + void loadDouble(const Address& addr, FloatRegister dest); + void loadDouble(const BaseIndex& src, FloatRegister dest); + + // Load a float value into a register, then expand it to a double. + void loadFloatAsDouble(const Address& addr, FloatRegister dest); + void loadFloatAsDouble(const BaseIndex& src, FloatRegister dest); + + void loadFloat32(const Address& addr, FloatRegister dest); + void loadFloat32(const BaseIndex& src, FloatRegister dest); + + void store8(Register src, const Address& address); + void store8(Imm32 imm, const Address& address); + void store8(Register src, const BaseIndex& address); + void store8(Imm32 imm, const BaseIndex& address); + + void store16(Register src, const Address& address); + void store16(Imm32 imm, const Address& address); + void store16(Register src, const BaseIndex& address); + void store16(Imm32 imm, const BaseIndex& address); + + void store32(Register src, AbsoluteAddress address); + void store32(Register src, const Address& address); + void store32(Register src, const BaseIndex& address); + void store32(Imm32 src, const Address& address); + void store32(Imm32 src, const BaseIndex& address); + + // NOTE: This will use second scratch on MIPS64. Only ARM needs the + // implementation without second scratch. + void store32_NoSecondScratch(Imm32 src, const Address& address) { + store32(src, address); + } + + void store64(Register64 src, Address address) { + storePtr(src.reg, address); + } + + template void storePtr(ImmWord imm, T address); + template void storePtr(ImmPtr imm, T address); + template void storePtr(ImmGCPtr imm, T address); + void storePtr(Register src, const Address& address); + void storePtr(Register src, const BaseIndex& address); + void storePtr(Register src, AbsoluteAddress dest); + void storeDouble(FloatRegister src, Address addr) { + ma_sd(src, addr); + } + void storeDouble(FloatRegister src, BaseIndex addr) { + MOZ_ASSERT(addr.offset == 0); + ma_sd(src, addr); + } + void moveDouble(FloatRegister src, FloatRegister dest) { + as_movd(dest, src); + } + + void storeFloat32(FloatRegister src, Address addr) { + ma_ss(src, addr); + } + void storeFloat32(FloatRegister src, BaseIndex addr) { + MOZ_ASSERT(addr.offset == 0); + ma_ss(src, addr); + } + + void zeroDouble(FloatRegister reg) { + moveToDouble(zero, reg); + } + + void clampIntToUint8(Register reg); + + void subPtr(Imm32 imm, const Register dest); + void subPtr(const Address& addr, const Register dest); + void subPtr(Register src, const Address& dest); + void addPtr(Imm32 imm, const Register dest); + void addPtr(Imm32 imm, const Address& dest); + void addPtr(ImmWord imm, const Register dest) { + movePtr(imm, ScratchRegister); + addPtr(ScratchRegister, dest); + } + void addPtr(ImmPtr imm, const Register dest) { + addPtr(ImmWord(uintptr_t(imm.value)), dest); + } + void mulBy3(const Register& src, const Register& dest) { + as_daddu(dest, src, src); + as_daddu(dest, dest, src); + } + + void mul64(Imm64 imm, const Register64& dest) { + MOZ_ASSERT(dest.reg != ScratchRegister); + mov(ImmWord(imm.value), ScratchRegister); + as_dmultu(dest.reg, ScratchRegister); + as_mflo(dest.reg); + } + + void convertUInt64ToDouble(Register64 src, Register temp, FloatRegister dest); + void mulDoublePtr(ImmPtr imm, Register temp, FloatRegister dest) { + movePtr(imm, ScratchRegister); + loadDouble(Address(ScratchRegister, 0), ScratchDoubleReg); + mulDouble(ScratchDoubleReg, dest); + } + + void breakpoint(); + + void branchDouble(DoubleCondition cond, FloatRegister lhs, FloatRegister rhs, + Label* label); + + void branchFloat(DoubleCondition cond, FloatRegister lhs, FloatRegister rhs, + Label* label); + + void checkStackAlignment(); + + static void calculateAlignedStackPointer(void** stackPointer); + + // If source is a double, load it into dest. If source is int32, + // convert it to double. Else, branch to failure. + void ensureDouble(const ValueOperand& source, FloatRegister dest, Label* failure); + + template + void cmpPtrSet(Assembler::Condition cond, T1 lhs, T2 rhs, Register dest) + { + ma_cmp_set(dest, lhs, rhs, cond); + } + void cmpPtrSet(Assembler::Condition cond, Address lhs, ImmPtr rhs, Register dest); + void cmpPtrSet(Assembler::Condition cond, Register lhs, Address rhs, Register dest); + + template + void cmp32Set(Assembler::Condition cond, T1 lhs, T2 rhs, Register dest) + { + ma_cmp_set(dest, lhs, rhs, cond); + } + void cmp32Set(Assembler::Condition cond, Register lhs, Address rhs, Register dest); + + protected: + bool buildOOLFakeExitFrame(void* fakeReturnAddr); + + public: + CodeOffsetLabel labelForPatch() { + return CodeOffsetLabel(nextOffset().getOffset()); + } + + void memIntToValue(Address Source, Address Dest) { + load32(Source, ScratchRegister); + storeValue(JSVAL_TYPE_INT32, ScratchRegister, Dest); + } + + void lea(Operand addr, Register dest) { + ma_daddu(dest, addr.baseReg(), Imm32(addr.disp())); + } + + void abiret() { + as_jr(ra); + as_nop(); + } + + BufferOffset ma_BoundsCheck(Register bounded) { + BufferOffset bo = m_buffer.nextOffset(); + ma_liPatchable(bounded, ImmWord(0)); + return bo; + } + + void moveFloat32(FloatRegister src, FloatRegister dest) { + as_movs(dest, src); + } + + void branchPtrInNurseryRange(Condition cond, Register ptr, Register temp, Label* label); + void branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp, + Label* label); + + void loadAsmJSActivation(Register dest) { + loadPtr(Address(GlobalReg, AsmJSActivationGlobalDataOffset - AsmJSGlobalRegBias), dest); + } + void loadAsmJSHeapRegisterFromGlobalData() { + MOZ_ASSERT(Imm16::IsInSignedRange(AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias)); + loadPtr(Address(GlobalReg, AsmJSHeapGlobalDataOffset - AsmJSGlobalRegBias), HeapReg); + } + + // Instrumentation for entering and leaving the profiler. + void profilerEnterFrame(Register framePtr, Register scratch); + void profilerExitFrame(); +}; + +typedef MacroAssemblerMIPS64Compat MacroAssemblerSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_mips64_MacroAssembler_mips64_h */ diff --git a/js/src/jit/mips64/MoveEmitter-mips64.cpp b/js/src/jit/mips64/MoveEmitter-mips64.cpp new file mode 100644 index 0000000000..d208b83a7b --- /dev/null +++ b/js/src/jit/mips64/MoveEmitter-mips64.cpp @@ -0,0 +1,155 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jit/mips64/MoveEmitter-mips64.h" + +#include "jit/MacroAssembler-inl.h" + +using namespace js; +using namespace js::jit; + +void +MoveEmitterMIPS64::breakCycle(const MoveOperand& from, const MoveOperand& to, + MoveOp::Type type, uint32_t slotId) +{ + // There is some pattern: + // (A -> B) + // (B -> A) + // + // This case handles (A -> B), which we reach first. We save B, then allow + // the original move to continue. + switch (type) { + case MoveOp::FLOAT32: + if (to.isMemory()) { + FloatRegister temp = ScratchFloat32Reg; + masm.loadFloat32(getAdjustedAddress(to), temp); + masm.storeFloat32(temp, cycleSlot(slotId)); + } else { + masm.storeFloat32(to.floatReg(), cycleSlot(slotId)); + } + break; + case MoveOp::DOUBLE: + if (to.isMemory()) { + FloatRegister temp = ScratchDoubleReg; + masm.loadDouble(getAdjustedAddress(to), temp); + masm.storeDouble(temp, cycleSlot(slotId)); + } else { + masm.storeDouble(to.floatReg(), cycleSlot(slotId)); + } + break; + case MoveOp::INT32: + if (to.isMemory()) { + Register temp = tempReg(); + masm.load32(getAdjustedAddress(to), temp); + masm.store32(temp, cycleSlot(0)); + } else { + // Second scratch register should not be moved by MoveEmitter. + MOZ_ASSERT(to.reg() != spilledReg_); + masm.store32(to.reg(), cycleSlot(0)); + } + break; + case MoveOp::GENERAL: + if (to.isMemory()) { + Register temp = tempReg(); + masm.loadPtr(getAdjustedAddress(to), temp); + masm.storePtr(temp, cycleSlot(0)); + } else { + // Second scratch register should not be moved by MoveEmitter. + MOZ_ASSERT(to.reg() != spilledReg_); + masm.storePtr(to.reg(), cycleSlot(0)); + } + break; + default: + MOZ_CRASH("Unexpected move type"); + } +} + +void +MoveEmitterMIPS64::completeCycle(const MoveOperand& from, const MoveOperand& to, + MoveOp::Type type, uint32_t slotId) +{ + // There is some pattern: + // (A -> B) + // (B -> A) + // + // This case handles (B -> A), which we reach last. We emit a move from the + // saved value of B, to A. + switch (type) { + case MoveOp::FLOAT32: + if (to.isMemory()) { + FloatRegister temp = ScratchFloat32Reg; + masm.loadFloat32(cycleSlot(slotId), temp); + masm.storeFloat32(temp, getAdjustedAddress(to)); + } else { + masm.loadFloat32(cycleSlot(slotId), to.floatReg()); + } + break; + case MoveOp::DOUBLE: + if (to.isMemory()) { + FloatRegister temp = ScratchDoubleReg; + masm.loadDouble(cycleSlot(slotId), temp); + masm.storeDouble(temp, getAdjustedAddress(to)); + } else { + masm.loadDouble(cycleSlot(slotId), to.floatReg()); + } + break; + case MoveOp::INT32: + MOZ_ASSERT(slotId == 0); + if (to.isMemory()) { + Register temp = tempReg(); + masm.load32(cycleSlot(0), temp); + masm.store32(temp, getAdjustedAddress(to)); + } else { + // Second scratch register should not be moved by MoveEmitter. + MOZ_ASSERT(to.reg() != spilledReg_); + masm.load32(cycleSlot(0), to.reg()); + } + break; + case MoveOp::GENERAL: + MOZ_ASSERT(slotId == 0); + if (to.isMemory()) { + Register temp = tempReg(); + masm.loadPtr(cycleSlot(0), temp); + masm.storePtr(temp, getAdjustedAddress(to)); + } else { + // Second scratch register should not be moved by MoveEmitter. + MOZ_ASSERT(to.reg() != spilledReg_); + masm.loadPtr(cycleSlot(0), to.reg()); + } + break; + default: + MOZ_CRASH("Unexpected move type"); + } +} + +void +MoveEmitterMIPS64::emitDoubleMove(const MoveOperand& from, const MoveOperand& to) +{ + // Ensure that we can use ScratchDoubleReg in memory move. + MOZ_ASSERT_IF(from.isFloatReg(), from.floatReg() != ScratchDoubleReg); + MOZ_ASSERT_IF(to.isFloatReg(), to.floatReg() != ScratchDoubleReg); + + if (from.isFloatReg()) { + if (to.isFloatReg()) { + masm.moveDouble(from.floatReg(), to.floatReg()); + } else if (to.isGeneralReg()) { + masm.moveFromDouble(from.floatReg(), to.reg()); + } else { + MOZ_ASSERT(to.isMemory()); + masm.storeDouble(from.floatReg(), getAdjustedAddress(to)); + } + } else if (to.isFloatReg()) { + if (from.isMemory()) + masm.loadDouble(getAdjustedAddress(from), to.floatReg()); + else + masm.moveToDouble(from.reg(), to.floatReg()); + } else { + MOZ_ASSERT(from.isMemory()); + MOZ_ASSERT(to.isMemory()); + masm.loadDouble(getAdjustedAddress(from), ScratchDoubleReg); + masm.storeDouble(ScratchDoubleReg, getAdjustedAddress(to)); + } +} diff --git a/js/src/jit/mips64/MoveEmitter-mips64.h b/js/src/jit/mips64/MoveEmitter-mips64.h new file mode 100644 index 0000000000..77e412fb4a --- /dev/null +++ b/js/src/jit/mips64/MoveEmitter-mips64.h @@ -0,0 +1,34 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_MoveEmitter_mips64_h +#define jit_mips64_MoveEmitter_mips64_h + +#include "jit/mips-shared/MoveEmitter-mips-shared.h" + +namespace js { +namespace jit { + +class MoveEmitterMIPS64 : public MoveEmitterMIPSShared +{ + void emitDoubleMove(const MoveOperand& from, const MoveOperand& to); + void breakCycle(const MoveOperand& from, const MoveOperand& to, + MoveOp::Type type, uint32_t slot); + void completeCycle(const MoveOperand& from, const MoveOperand& to, + MoveOp::Type type, uint32_t slot); + + public: + MoveEmitterMIPS64(MacroAssembler& masm) + : MoveEmitterMIPSShared(masm) + { } +}; + +typedef MoveEmitterMIPS64 MoveEmitter; + +} // namespace jit +} // namespace js + +#endif /* jit_mips64_MoveEmitter_mips64_h */ diff --git a/js/src/jit/mips64/SharedIC-mips64.cpp b/js/src/jit/mips64/SharedIC-mips64.cpp new file mode 100644 index 0000000000..ee325277f9 --- /dev/null +++ b/js/src/jit/mips64/SharedIC-mips64.cpp @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "jsiter.h" + +#include "jit/BaselineCompiler.h" +#include "jit/BaselineIC.h" +#include "jit/BaselineJIT.h" +#include "jit/Linker.h" +#include "jit/SharedICHelpers.h" + +#include "jsboolinlines.h" + +using namespace js; +using namespace js::jit; + +namespace js { +namespace jit { + +// ICBinaryArith_Int32 + +bool +ICBinaryArith_Int32::Compiler::generateStubCode(MacroAssembler& masm) +{ + // Guard that R0 is an integer and R1 is an integer. + Label failure; + masm.branchTestInt32(Assembler::NotEqual, R0, &failure); + masm.branchTestInt32(Assembler::NotEqual, R1, &failure); + + // Add R0 and R1. Don't need to explicitly unbox, just use R2's valueReg. + Register scratchReg = R2.valueReg(); + + Label goodMul, divTest1, divTest2; + switch(op_) { + case JSOP_ADD: + masm.unboxInt32(R0, ExtractTemp0); + masm.unboxInt32(R1, ExtractTemp1); + masm.ma_addTestOverflow(scratchReg, ExtractTemp0, ExtractTemp1, &failure); + masm.boxValue(JSVAL_TYPE_INT32, scratchReg, R0.valueReg()); + break; + case JSOP_SUB: + masm.unboxInt32(R0, ExtractTemp0); + masm.unboxInt32(R1, ExtractTemp1); + masm.ma_subTestOverflow(scratchReg, ExtractTemp0, ExtractTemp1, &failure); + masm.boxValue(JSVAL_TYPE_INT32, scratchReg, R0.valueReg()); + break; + case JSOP_MUL: { + masm.unboxInt32(R0, ExtractTemp0); + masm.unboxInt32(R1, ExtractTemp1); + masm.ma_mul_branch_overflow(scratchReg, ExtractTemp0, ExtractTemp1, &failure); + + masm.ma_b(scratchReg, Imm32(0), &goodMul, Assembler::NotEqual, ShortJump); + + // Result is -0 if operands have different signs. + masm.as_xor(t8, ExtractTemp0, ExtractTemp1); + masm.ma_b(t8, Imm32(0), &failure, Assembler::LessThan, ShortJump); + + masm.bind(&goodMul); + masm.boxValue(JSVAL_TYPE_INT32, scratchReg, R0.valueReg()); + break; + } + case JSOP_DIV: + case JSOP_MOD: { + masm.unboxInt32(R0, ExtractTemp0); + masm.unboxInt32(R1, ExtractTemp1); + // Check for INT_MIN / -1, it results in a double. + masm.ma_b(ExtractTemp0, Imm32(INT_MIN), &divTest1, Assembler::NotEqual, ShortJump); + masm.ma_b(ExtractTemp1, Imm32(-1), &failure, Assembler::Equal, ShortJump); + masm.bind(&divTest1); + + // Check for division by zero + masm.ma_b(ExtractTemp1, Imm32(0), &failure, Assembler::Equal, ShortJump); + + // Check for 0 / X with X < 0 (results in -0). + masm.ma_b(ExtractTemp0, Imm32(0), &divTest2, Assembler::NotEqual, ShortJump); + masm.ma_b(ExtractTemp1, Imm32(0), &failure, Assembler::LessThan, ShortJump); + masm.bind(&divTest2); + + masm.as_div(ExtractTemp0, ExtractTemp1); + + if (op_ == JSOP_DIV) { + // Result is a double if the remainder != 0. + masm.as_mfhi(scratchReg); + masm.ma_b(scratchReg, Imm32(0), &failure, Assembler::NotEqual, ShortJump); + masm.as_mflo(scratchReg); + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + } else { + Label done; + // If X % Y == 0 and X < 0, the result is -0. + masm.as_mfhi(scratchReg); + masm.ma_b(scratchReg, Imm32(0), &done, Assembler::NotEqual, ShortJump); + masm.ma_b(ExtractTemp0, Imm32(0), &failure, Assembler::LessThan, ShortJump); + masm.bind(&done); + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + } + break; + } + case JSOP_BITOR: + masm.as_or(R0.valueReg() , R0.valueReg(), R1.valueReg()); + break; + case JSOP_BITXOR: + masm.as_xor(scratchReg, R0.valueReg(), R1.valueReg()); + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + break; + case JSOP_BITAND: + masm.as_and(R0.valueReg() , R0.valueReg(), R1.valueReg()); + break; + case JSOP_LSH: + masm.unboxInt32(R0, ExtractTemp0); + masm.unboxInt32(R1, ExtractTemp1); + // MIPS will only use 5 lowest bits in R1 as shift offset. + masm.ma_sll(scratchReg, ExtractTemp0, ExtractTemp1); + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + break; + case JSOP_RSH: + masm.unboxInt32(R0, ExtractTemp0); + masm.unboxInt32(R1, ExtractTemp1); + masm.ma_sra(scratchReg, ExtractTemp0, ExtractTemp1); + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + break; + case JSOP_URSH: + masm.unboxInt32(R0, ExtractTemp0); + masm.unboxInt32(R1, ExtractTemp1); + masm.ma_srl(scratchReg, ExtractTemp0, ExtractTemp1); + if (allowDouble_) { + Label toUint; + masm.ma_b(scratchReg, Imm32(0), &toUint, Assembler::LessThan, ShortJump); + + // Move result and box for return. + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + EmitReturnFromIC(masm); + + masm.bind(&toUint); + masm.convertUInt32ToDouble(scratchReg, FloatReg1); + masm.boxDouble(FloatReg1, R0); + } else { + masm.ma_b(scratchReg, Imm32(0), &failure, Assembler::LessThan, ShortJump); + // Move result for return. + masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0); + } + break; + default: + MOZ_CRASH("Unhandled op for BinaryArith_Int32."); + } + + EmitReturnFromIC(masm); + + // Failure case - jump to next stub + masm.bind(&failure); + EmitStubGuardFailure(masm); + + return true; +} + +bool +ICUnaryArith_Int32::Compiler::generateStubCode(MacroAssembler& masm) +{ + Label failure; + masm.branchTestInt32(Assembler::NotEqual, R0, &failure); + + switch (op) { + case JSOP_BITNOT: + masm.not32(R0.valueReg()); + masm.tagValue(JSVAL_TYPE_INT32, R0.valueReg(), R0); + break; + case JSOP_NEG: + masm.unboxInt32(R0, ExtractTemp0); + // Guard against 0 and MIN_INT, both result in a double. + masm.branchTest32(Assembler::Zero, ExtractTemp0, Imm32(INT32_MAX), &failure); + + masm.neg32(ExtractTemp0); + masm.tagValue(JSVAL_TYPE_INT32, ExtractTemp0, R0); + break; + default: + MOZ_CRASH("Unexpected op"); + return false; + } + + EmitReturnFromIC(masm); + + masm.bind(&failure); + EmitStubGuardFailure(masm); + return true; +} + + +} // namespace jit +} // namespace js diff --git a/js/src/jit/mips64/SharedICHelpers-mips64.h b/js/src/jit/mips64/SharedICHelpers-mips64.h new file mode 100644 index 0000000000..3da4a5193b --- /dev/null +++ b/js/src/jit/mips64/SharedICHelpers-mips64.h @@ -0,0 +1,350 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_SharedICHelpers_mips64_h +#define jit_mips64_SharedICHelpers_mips64_h + +#include "jit/BaselineFrame.h" +#include "jit/BaselineIC.h" +#include "jit/MacroAssembler.h" +#include "jit/SharedICRegisters.h" + +namespace js { +namespace jit { + +// Distance from sp to the top Value inside an IC stub (no return address on +// the stack on MIPS). +static const size_t ICStackValueOffset = 0; + +inline void +EmitRestoreTailCallReg(MacroAssembler& masm) +{ + // No-op on MIPS because ra register is always holding the return address. +} + +inline void +EmitRepushTailCallReg(MacroAssembler& masm) +{ + // No-op on MIPS because ra register is always holding the return address. +} + +inline void +EmitCallIC(CodeOffsetLabel* patchOffset, MacroAssembler& masm) +{ + // Move ICEntry offset into ICStubReg. + CodeOffsetLabel offset = masm.movWithPatch(ImmWord(-1), ICStubReg); + *patchOffset = offset; + + // Load stub pointer into ICStubReg. + masm.loadPtr(Address(ICStubReg, ICEntry::offsetOfFirstStub()), ICStubReg); + + // Load stubcode pointer from BaselineStubEntry. + // R2 won't be active when we call ICs, so we can use it as scratch. + masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), R2.scratchReg()); + + // Call the stubcode via a direct jump-and-link + masm.call(R2.scratchReg()); +} + +inline void +EmitEnterTypeMonitorIC(MacroAssembler& masm, + size_t monitorStubOffset = ICMonitoredStub::offsetOfFirstMonitorStub()) +{ + // This is expected to be called from within an IC, when ICStubReg + // is properly initialized to point to the stub. + masm.loadPtr(Address(ICStubReg, (uint32_t) monitorStubOffset), ICStubReg); + + // Load stubcode pointer from BaselineStubEntry. + // R2 won't be active when we call ICs, so we can use it. + masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), R2.scratchReg()); + + // Jump to the stubcode. + masm.branch(R2.scratchReg()); +} + +inline void +EmitReturnFromIC(MacroAssembler& masm) +{ + masm.branch(ra); +} + +inline void +EmitChangeICReturnAddress(MacroAssembler& masm, Register reg) +{ + masm.movePtr(reg, ra); +} + +inline void +EmitBaselineTailCallVM(JitCode* target, MacroAssembler& masm, uint32_t argSize) +{ + // We assume during this that R0 and R1 have been pushed, and that R2 is + // unused. + MOZ_ASSERT(R2 == ValueOperand(a6)); + + // Compute frame size. + masm.movePtr(BaselineFrameReg, a6); + masm.addPtr(Imm32(BaselineFrame::FramePointerOffset), a6); + masm.subPtr(BaselineStackReg, a6); + + // Store frame size without VMFunction arguments for GC marking. + masm.ma_dsubu(a7, a6, Imm32(argSize)); + masm.store32(a7, Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFrameSize())); + + // Push frame descriptor and perform the tail call. + // ICTailCallReg (ra) already contains the return address (as we + // keep it there through the stub calls), but the VMWrapper code being + // called expects the return address to also be pushed on the stack. + MOZ_ASSERT(ICTailCallReg == ra); + masm.makeFrameDescriptor(a6, JitFrame_BaselineJS); + masm.subPtr(Imm32(sizeof(CommonFrameLayout)), StackPointer); + masm.storePtr(a6, Address(StackPointer, CommonFrameLayout::offsetOfDescriptor())); + masm.storePtr(ra, Address(StackPointer, CommonFrameLayout::offsetOfReturnAddress())); + + masm.branch(target); +} + +inline void +EmitIonTailCallVM(JitCode* target, MacroAssembler& masm, uint32_t stackSize) +{ + MOZ_CRASH("Not implemented yet."); +} + +inline void +EmitBaselineCreateStubFrameDescriptor(MacroAssembler& masm, Register reg) +{ + // Compute stub frame size. We have to add two pointers: the stub reg and + // previous frame pointer pushed by EmitEnterStubFrame. + masm.movePtr(BaselineFrameReg, reg); + masm.addPtr(Imm32(sizeof(intptr_t) * 2), reg); + masm.subPtr(BaselineStackReg, reg); + + masm.makeFrameDescriptor(reg, JitFrame_BaselineStub); +} + +inline void +EmitBaselineCallVM(JitCode* target, MacroAssembler& masm) +{ + EmitBaselineCreateStubFrameDescriptor(masm, a6); + masm.push(a6); + masm.call(target); +} + +inline void +EmitIonCallVM(JitCode* target, size_t stackSlots, MacroAssembler& masm) +{ + MOZ_CRASH("Not implemented yet."); +} + +struct BaselineStubFrame { + uintptr_t savedFrame; + uintptr_t savedStub; + uintptr_t returnAddress; + uintptr_t descriptor; +}; + +static const uint32_t STUB_FRAME_SIZE = sizeof(BaselineStubFrame); +static const uint32_t STUB_FRAME_SAVED_STUB_OFFSET = offsetof(BaselineStubFrame, savedStub); + +inline void +EmitBaselineEnterStubFrame(MacroAssembler& masm, Register scratch) +{ + MOZ_ASSERT(scratch != ICTailCallReg); + + // Compute frame size. + masm.movePtr(BaselineFrameReg, scratch); + masm.addPtr(Imm32(BaselineFrame::FramePointerOffset), scratch); + masm.subPtr(BaselineStackReg, scratch); + + masm.store32(scratch, Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFrameSize())); + + // Note: when making changes here, don't forget to update + // BaselineStubFrame if needed. + + // Push frame descriptor and return address. + masm.makeFrameDescriptor(scratch, JitFrame_BaselineJS); + masm.subPtr(Imm32(STUB_FRAME_SIZE), StackPointer); + masm.storePtr(scratch, Address(StackPointer, offsetof(BaselineStubFrame, descriptor))); + masm.storePtr(ICTailCallReg, Address(StackPointer, + offsetof(BaselineStubFrame, returnAddress))); + + // Save old frame pointer, stack pointer and stub reg. + masm.storePtr(ICStubReg, Address(StackPointer, + offsetof(BaselineStubFrame, savedStub))); + masm.storePtr(BaselineFrameReg, Address(StackPointer, + offsetof(BaselineStubFrame, savedFrame))); + masm.movePtr(BaselineStackReg, BaselineFrameReg); +} + +inline void +EmitIonEnterStubFrame(MacroAssembler& masm, Register scratch) +{ + MOZ_CRASH("Not implemented yet."); +} + +inline void +EmitBaselineLeaveStubFrame(MacroAssembler& masm, bool calledIntoIon = false) +{ + // Ion frames do not save and restore the frame pointer. If we called + // into Ion, we have to restore the stack pointer from the frame descriptor. + // If we performed a VM call, the descriptor has been popped already so + // in that case we use the frame pointer. + if (calledIntoIon) { + masm.pop(ScratchRegister); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), ScratchRegister); + masm.addPtr(ScratchRegister, BaselineStackReg); + } else { + masm.movePtr(BaselineFrameReg, BaselineStackReg); + } + + masm.loadPtr(Address(StackPointer, offsetof(BaselineStubFrame, savedFrame)), + BaselineFrameReg); + masm.loadPtr(Address(StackPointer, offsetof(BaselineStubFrame, savedStub)), + ICStubReg); + + // Load the return address. + masm.loadPtr(Address(StackPointer, offsetof(BaselineStubFrame, returnAddress)), + ICTailCallReg); + + // Discard the frame descriptor. + masm.loadPtr(Address(StackPointer, offsetof(BaselineStubFrame, descriptor)), ScratchRegister); + masm.addPtr(Imm32(STUB_FRAME_SIZE), StackPointer); +} + +inline void +EmitIonLeaveStubFrame(MacroAssembler& masm) +{ + MOZ_CRASH("Not implemented yet."); +} + +inline void +EmitStowICValues(MacroAssembler& masm, int values) +{ + MOZ_ASSERT(values >= 0 && values <= 2); + switch(values) { + case 1: + // Stow R0 + masm.pushValue(R0); + break; + case 2: + // Stow R0 and R1 + masm.pushValue(R0); + masm.pushValue(R1); + break; + } +} + +inline void +EmitUnstowICValues(MacroAssembler& masm, int values, bool discard = false) +{ + MOZ_ASSERT(values >= 0 && values <= 2); + switch(values) { + case 1: + // Unstow R0. + if (discard) + masm.addPtr(Imm32(sizeof(Value)), BaselineStackReg); + else + masm.popValue(R0); + break; + case 2: + // Unstow R0 and R1. + if (discard) { + masm.addPtr(Imm32(sizeof(Value) * 2), BaselineStackReg); + } else { + masm.popValue(R1); + masm.popValue(R0); + } + break; + } +} + +inline void +EmitCallTypeUpdateIC(MacroAssembler& masm, JitCode* code, uint32_t objectOffset) +{ + // R0 contains the value that needs to be typechecked. + // The object we're updating is a boxed Value on the stack, at offset + // objectOffset from $sp, excluding the return address. + + // Save the current ICStubReg to stack, as well as the TailCallReg, + // since on mips, the $ra is live. + masm.subPtr(Imm32(2 * sizeof(intptr_t)), StackPointer); + masm.storePtr(ICStubReg, Address(StackPointer, sizeof(intptr_t))); + masm.storePtr(ICTailCallReg, Address(StackPointer, 0)); + + // This is expected to be called from within an IC, when ICStubReg + // is properly initialized to point to the stub. + masm.loadPtr(Address(ICStubReg, ICUpdatedStub::offsetOfFirstUpdateStub()), + ICStubReg); + + // Load stubcode pointer from ICStubReg into ICTailCallReg. + masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), R2.scratchReg()); + + // Call the stubcode. + masm.call(R2.scratchReg()); + + // Restore the old stub reg and tailcall reg. + masm.loadPtr(Address(StackPointer, 0), ICTailCallReg); + masm.loadPtr(Address(StackPointer, sizeof(intptr_t)), ICStubReg); + masm.addPtr(Imm32(2 * sizeof(intptr_t)), StackPointer); + + // The update IC will store 0 or 1 in R1.scratchReg() reflecting if the + // value in R0 type-checked properly or not. + Label success; + masm.ma_b(R1.scratchReg(), Imm32(1), &success, Assembler::Equal, ShortJump); + + // If the IC failed, then call the update fallback function. + EmitBaselineEnterStubFrame(masm, R1.scratchReg()); + + masm.loadValue(Address(BaselineStackReg, STUB_FRAME_SIZE + objectOffset), R1); + + masm.pushValue(R0); + masm.pushValue(R1); + masm.push(ICStubReg); + + // Load previous frame pointer, push BaselineFrame*. + masm.loadPtr(Address(BaselineFrameReg, 0), R0.scratchReg()); + masm.pushBaselineFramePtr(R0.scratchReg(), R0.scratchReg()); + + EmitBaselineCallVM(code, masm); + EmitBaselineLeaveStubFrame(masm); + + // Success at end. + masm.bind(&success); +} + +template +inline void +EmitPreBarrier(MacroAssembler& masm, const AddrType& addr, MIRType type) +{ + // On MIPS, $ra is clobbered by patchableCallPreBarrier. Save it first. + masm.push(ra); + masm.patchableCallPreBarrier(addr, type); + masm.pop(ra); +} + +inline void +EmitStubGuardFailure(MacroAssembler& masm) +{ + // NOTE: This routine assumes that the stub guard code left the stack in + // the same state it was in when it was entered. + + // BaselineStubEntry points to the current stub. + + // Load next stub into ICStubReg + masm.loadPtr(Address(ICStubReg, ICStub::offsetOfNext()), ICStubReg); + + // Load stubcode pointer from BaselineStubEntry into scratch register. + masm.loadPtr(Address(ICStubReg, ICStub::offsetOfStubCode()), R2.scratchReg()); + + // Return address is already loaded, just jump to the next stubcode. + MOZ_ASSERT(ICTailCallReg == ra); + masm.branch(R2.scratchReg()); +} + + +} // namespace jit +} // namespace js + +#endif /* jit_mips64_SharedICHelpers_mips64_h */ diff --git a/js/src/jit/mips64/SharedICRegisters-mips64.h b/js/src/jit/mips64/SharedICRegisters-mips64.h new file mode 100644 index 0000000000..ba3a7f727e --- /dev/null +++ b/js/src/jit/mips64/SharedICRegisters-mips64.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 jit_mips64_SharedICRegisters_mips64_h +#define jit_mips64_SharedICRegisters_mips64_h + +#include "jit/MacroAssembler.h" + +namespace js { +namespace jit { + +static MOZ_CONSTEXPR_VAR Register BaselineFrameReg = s5; +static MOZ_CONSTEXPR_VAR Register BaselineStackReg = sp; + +// ValueOperands R0, R1, and R2. +// R0 == JSReturnReg, and R2 uses registers not preserved across calls. R1 value +// should be preserved across calls. +static MOZ_CONSTEXPR_VAR ValueOperand R0(v1); +static MOZ_CONSTEXPR_VAR ValueOperand R1(s4); +static MOZ_CONSTEXPR_VAR ValueOperand R2(a6); + +// ICTailCallReg and ICStubReg +// These use registers that are not preserved across calls. +static MOZ_CONSTEXPR_VAR Register ICTailCallReg = ra; +static MOZ_CONSTEXPR_VAR Register ICStubReg = a5; + +static MOZ_CONSTEXPR_VAR Register ExtractTemp0 = s6; +static MOZ_CONSTEXPR_VAR Register ExtractTemp1 = s7; + +// Register used internally by MacroAssemblerMIPS. +static MOZ_CONSTEXPR_VAR Register BaselineSecondScratchReg = SecondScratchReg; + +// Note that ICTailCallReg is actually just the link register. +// In MIPS code emission, we do not clobber ICTailCallReg since we keep +// the return address for calls there. + +// FloatReg0 must be equal to ReturnFloatReg. +static MOZ_CONSTEXPR_VAR FloatRegister FloatReg0 = f0; +static MOZ_CONSTEXPR_VAR FloatRegister FloatReg1 = f2; + +} // namespace jit +} // namespace js + +#endif /* jit_mips64_SharedICRegisters_mips64_h */ diff --git a/js/src/jit/mips64/Simulator-mips64.cpp b/js/src/jit/mips64/Simulator-mips64.cpp new file mode 100644 index 0000000000..2728af20fa --- /dev/null +++ b/js/src/jit/mips64/Simulator-mips64.cpp @@ -0,0 +1,3878 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ +// Copyright 2011 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. + +#include "jit/mips64/Simulator-mips64.h" + +#include "mozilla/Casting.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" + +#include + +#include "asmjs/AsmJSValidate.h" +#include "jit/mips64/Assembler-mips64.h" +#include "vm/Runtime.h" + +#define I8(v) static_cast(v) +#define I16(v) static_cast(v) +#define U16(v) static_cast(v) +#define I32(v) static_cast(v) +#define U32(v) static_cast(v) +#define I64(v) static_cast(v) +#define U64(v) static_cast(v) +#define I128(v) static_cast<__int128_t>(v) +#define U128(v) static_cast(v) + +namespace js { +namespace jit { + +static const Instr kCallRedirInstr = op_special | MAX_BREAK_CODE << FunctionBits | ff_break; + +// Utils functions. +static uint32_t +GetFCSRConditionBit(uint32_t cc) +{ + if (cc == 0) + return 23; + return 24 + cc; +} + +// ----------------------------------------------------------------------------- +// MIPS assembly various constants. + +class SimInstruction +{ + public: + enum { + kInstrSize = 4, + // On MIPS PC cannot actually be directly accessed. We behave as if PC was + // always the value of the current instruction being executed. + kPCReadOffset = 0 + }; + + // Get the raw instruction bits. + inline Instr instructionBits() const { + return *reinterpret_cast(this); + } + + // Set the raw instruction bits to value. + inline void setInstructionBits(Instr value) { + *reinterpret_cast(this) = value; + } + + // Read one particular bit out of the instruction bits. + inline int bit(int nr) const { + return (instructionBits() >> nr) & 1; + } + + // Read a bit field out of the instruction bits. + inline int bits(int hi, int lo) const { + return (instructionBits() >> lo) & ((2 << (hi - lo)) - 1); + } + + // Instruction type. + enum Type { + kRegisterType, + kImmediateType, + kJumpType, + kUnsupported = -1 + }; + + // Get the encoding type of the instruction. + Type instructionType() const; + + + // Accessors for the different named fields used in the MIPS encoding. + inline Opcode opcodeValue() const { + return static_cast(bits(OpcodeShift + OpcodeBits - 1, OpcodeShift)); + } + + inline int rsValue() const { + MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); + return bits(RSShift + RSBits - 1, RSShift); + } + + inline int rtValue() const { + MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); + return bits(RTShift + RTBits - 1, RTShift); + } + + inline int rdValue() const { + MOZ_ASSERT(instructionType() == kRegisterType); + return bits(RDShift + RDBits - 1, RDShift); + } + + inline int saValue() const { + MOZ_ASSERT(instructionType() == kRegisterType); + return bits(SAShift + SABits - 1, SAShift); + } + + inline int functionValue() const { + MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); + return bits(FunctionShift + FunctionBits - 1, FunctionShift); + } + + inline int fdValue() const { + return bits(FDShift + FDBits - 1, FDShift); + } + + inline int fsValue() const { + return bits(FSShift + FSBits - 1, FSShift); + } + + inline int ftValue() const { + return bits(FTShift + FTBits - 1, FTShift); + } + + inline int frValue() const { + return bits(FRShift + FRBits - 1, FRShift); + } + + // Float Compare condition code instruction bits. + inline int fcccValue() const { + return bits(FCccShift + FCccBits - 1, FCccShift); + } + + // Float Branch condition code instruction bits. + inline int fbccValue() const { + return bits(FBccShift + FBccBits - 1, FBccShift); + } + + // Float Branch true/false instruction bit. + inline int fbtrueValue() const { + return bits(FBtrueShift + FBtrueBits - 1, FBtrueShift); + } + + // Return the fields at their original place in the instruction encoding. + inline Opcode opcodeFieldRaw() const { + return static_cast(instructionBits() & OpcodeMask); + } + + inline int rsFieldRaw() const { + MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); + return instructionBits() & RSMask; + } + + // Same as above function, but safe to call within instructionType(). + inline int rsFieldRawNoAssert() const { + return instructionBits() & RSMask; + } + + inline int rtFieldRaw() const { + MOZ_ASSERT(instructionType() == kRegisterType || instructionType() == kImmediateType); + return instructionBits() & RTMask; + } + + inline int rdFieldRaw() const { + MOZ_ASSERT(instructionType() == kRegisterType); + return instructionBits() & RDMask; + } + + inline int saFieldRaw() const { + MOZ_ASSERT(instructionType() == kRegisterType); + return instructionBits() & SAMask; + } + + inline int functionFieldRaw() const { + return instructionBits() & FunctionMask; + } + + // Get the secondary field according to the opcode. + inline int secondaryValue() const { + Opcode op = opcodeFieldRaw(); + switch (op) { + case op_special: + case op_special2: + return functionValue(); + case op_cop1: + return rsValue(); + case op_regimm: + return rtValue(); + default: + return ff_null; + } + } + + inline int32_t imm16Value() const { + MOZ_ASSERT(instructionType() == kImmediateType); + return bits(Imm16Shift + Imm16Bits - 1, Imm16Shift); + } + + inline int32_t imm26Value() const { + MOZ_ASSERT(instructionType() == kJumpType); + return bits(Imm26Shift + Imm26Bits - 1, Imm26Shift); + } + + // Say if the instruction should not be used in a branch delay slot. + bool isForbiddenInBranchDelay() const; + // Say if the instruction 'links'. e.g. jal, bal. + bool isLinkingInstruction() const; + // Say if the instruction is a break or a trap. + bool isTrap() const; + + private: + + SimInstruction() = delete; + SimInstruction(const SimInstruction& other) = delete; + void operator=(const SimInstruction& other) = delete; +}; + +bool +SimInstruction::isForbiddenInBranchDelay() const +{ + const int op = opcodeFieldRaw(); + switch (op) { + case op_j: + case op_jal: + case op_beq: + case op_bne: + case op_blez: + case op_bgtz: + case op_beql: + case op_bnel: + case op_blezl: + case op_bgtzl: + return true; + case op_regimm: + switch (rtFieldRaw()) { + case rt_bltz: + case rt_bgez: + case rt_bltzal: + case rt_bgezal: + return true; + default: + return false; + }; + break; + case op_special: + switch (functionFieldRaw()) { + case ff_jr: + case ff_jalr: + return true; + default: + return false; + }; + break; + default: + return false; + }; +} + +bool +SimInstruction::isLinkingInstruction() const +{ + const int op = opcodeFieldRaw(); + switch (op) { + case op_jal: + return true; + case op_regimm: + switch (rtFieldRaw()) { + case rt_bgezal: + case rt_bltzal: + return true; + default: + return false; + }; + case op_special: + switch (functionFieldRaw()) { + case ff_jalr: + return true; + default: + return false; + }; + default: + return false; + }; +} + +bool +SimInstruction::isTrap() const +{ + if (opcodeFieldRaw() != op_special) { + return false; + } else { + switch (functionFieldRaw()) { + case ff_break: + case ff_tge: + case ff_tgeu: + case ff_tlt: + case ff_tltu: + case ff_teq: + case ff_tne: + return true; + default: + return false; + }; + } +} + +SimInstruction::Type +SimInstruction::instructionType() const +{ + switch (opcodeFieldRaw()) { + case op_special: + switch (functionFieldRaw()) { + case ff_jr: + case ff_jalr: + case ff_sync: + case ff_break: + case ff_sll: + case ff_dsll: + case ff_dsll32: + case ff_srl: + case ff_dsrl: + case ff_dsrl32: + case ff_sra: + case ff_dsra: + case ff_dsra32: + case ff_sllv: + case ff_dsllv: + case ff_srlv: + case ff_dsrlv: + case ff_srav: + case ff_dsrav: + case ff_mfhi: + case ff_mflo: + case ff_mult: + case ff_dmult: + case ff_multu: + case ff_dmultu: + case ff_div: + case ff_ddiv: + case ff_divu: + case ff_ddivu: + case ff_add: + case ff_dadd: + case ff_addu: + case ff_daddu: + case ff_sub: + case ff_dsub: + case ff_subu: + case ff_dsubu: + case ff_and: + case ff_or: + case ff_xor: + case ff_nor: + case ff_slt: + case ff_sltu: + case ff_tge: + case ff_tgeu: + case ff_tlt: + case ff_tltu: + case ff_teq: + case ff_tne: + case ff_movz: + case ff_movn: + case ff_movci: + return kRegisterType; + default: + return kUnsupported; + }; + break; + case op_special2: + switch (functionFieldRaw()) { + case ff_mul: + case ff_clz: + case ff_dclz: + return kRegisterType; + default: + return kUnsupported; + }; + break; + case op_special3: + switch (functionFieldRaw()) { + case ff_ins: + case ff_dins: + case ff_dinsm: + case ff_dinsu: + case ff_ext: + case ff_dext: + case ff_dextm: + case ff_dextu: + case ff_bshfl: + return kRegisterType; + default: + return kUnsupported; + }; + break; + case op_cop1: // Coprocessor instructions. + switch (rsFieldRawNoAssert()) { + case rs_bc1: // Branch on coprocessor condition. + return kImmediateType; + default: + return kRegisterType; + }; + break; + case op_cop1x: + return kRegisterType; + // 16 bits Immediate type instructions. e.g.: addi dest, src, imm16. + case op_regimm: + case op_beq: + case op_bne: + case op_blez: + case op_bgtz: + case op_addi: + case op_daddi: + case op_addiu: + case op_daddiu: + case op_slti: + case op_sltiu: + case op_andi: + case op_ori: + case op_xori: + case op_lui: + case op_beql: + case op_bnel: + case op_blezl: + case op_bgtzl: + case op_lb: + case op_lbu: + case op_lh: + case op_lhu: + case op_lw: + case op_lwu: + case op_lwl: + case op_lwr: + case op_ll: + case op_ld: + case op_ldl: + case op_ldr: + case op_sb: + case op_sh: + case op_sw: + case op_swl: + case op_swr: + case op_sc: + case op_sd: + case op_sdl: + case op_sdr: + case op_lwc1: + case op_ldc1: + case op_swc1: + case op_sdc1: + return kImmediateType; + // 26 bits immediate type instructions. e.g.: j imm26. + case op_j: + case op_jal: + return kJumpType; + default: + return kUnsupported; + }; + return kUnsupported; +} + +// C/C++ argument slots size. +const int kCArgSlotCount = 0; +const int kCArgsSlotsSize = kCArgSlotCount * sizeof(uintptr_t); +const int kBranchReturnOffset = 2 * SimInstruction::kInstrSize; + +class CachePage { + public: + static const int LINE_VALID = 0; + static const int LINE_INVALID = 1; + + static const int kPageShift = 12; + static const int kPageSize = 1 << kPageShift; + static const int kPageMask = kPageSize - 1; + static const int kLineShift = 2; // The cache line is only 4 bytes right now. + static const int kLineLength = 1 << kLineShift; + static const int kLineMask = kLineLength - 1; + + CachePage() { + memset(&validity_map_, LINE_INVALID, sizeof(validity_map_)); + } + + char* validityByte(int offset) { + return &validity_map_[offset >> kLineShift]; + } + + char* cachedData(int offset) { + return &data_[offset]; + } + + private: + char data_[kPageSize]; // The cached data. + static const int kValidityMapSize = kPageSize >> kLineShift; + char validity_map_[kValidityMapSize]; // One byte per line. +}; + +// Protects the icache() and redirection() properties of the +// Simulator. +class AutoLockSimulatorCache +{ + public: + explicit AutoLockSimulatorCache(Simulator* sim) : sim_(sim) { + PR_Lock(sim_->cacheLock_); + MOZ_ASSERT(!sim_->cacheLockHolder_); +#ifdef DEBUG + sim_->cacheLockHolder_ = PR_GetCurrentThread(); +#endif + } + + ~AutoLockSimulatorCache() { + MOZ_ASSERT(sim_->cacheLockHolder_); +#ifdef DEBUG + sim_->cacheLockHolder_ = nullptr; +#endif + PR_Unlock(sim_->cacheLock_); + } + + private: + Simulator* const sim_; +}; + +bool Simulator::ICacheCheckingEnabled = false; + +int64_t Simulator::StopSimAt = -1; + +Simulator * +Simulator::Create() +{ + Simulator* sim = js_new(); + if (!sim) + return nullptr; + + if (!sim->init()) { + js_delete(sim); + return nullptr; + } + + if (getenv("MIPS_SIM_ICACHE_CHECKS")) + Simulator::ICacheCheckingEnabled = true; + + int64_t stopAt; + char* stopAtStr = getenv("MIPS_SIM_STOP_AT"); + if (stopAtStr && sscanf(stopAtStr, "%ld", &stopAt) == 1) { + fprintf(stderr, "\nStopping simulation at icount %ld\n", stopAt); + Simulator::StopSimAt = stopAt; + } + + return sim; +} + +void +Simulator::Destroy(Simulator* sim) +{ + js_delete(sim); +} + +// The MipsDebugger class is used by the simulator while debugging simulated +// code. +class MipsDebugger +{ + public: + explicit MipsDebugger(Simulator* sim) : sim_(sim) { } + + void stop(SimInstruction* instr); + void debug(); + // Print all registers with a nice formatting. + void printAllRegs(); + void printAllRegsIncludingFPU(); + + private: + // We set the breakpoint code to 0xfffff to easily recognize it. + static const Instr kBreakpointInstr = op_special | ff_break | 0xfffff << 6; + static const Instr kNopInstr = op_special | ff_sll; + + Simulator* sim_; + + int64_t getRegisterValue(int regnum); + int64_t getFPURegisterValueLong(int regnum); + float getFPURegisterValueFloat(int regnum); + double getFPURegisterValueDouble(int regnum); + bool getValue(const char* desc, int64_t* value); + + // Set or delete a breakpoint. Returns true if successful. + bool setBreakpoint(SimInstruction* breakpc); + bool deleteBreakpoint(SimInstruction* breakpc); + + // Undo and redo all breakpoints. This is needed to bracket disassembly and + // execution to skip past breakpoints when run from the debugger. + void undoBreakpoints(); + void redoBreakpoints(); +}; + +static void +UNSUPPORTED() +{ + printf("Unsupported instruction.\n"); + MOZ_CRASH(); +} + +void +MipsDebugger::stop(SimInstruction* instr) +{ + // Get the stop code. + uint32_t code = instr->bits(25, 6); + // Retrieve the encoded address, which comes just after this stop. + char* msg = *reinterpret_cast(sim_->get_pc() + + SimInstruction::kInstrSize); + // Update this stop description. + if (!sim_->watchedStops_[code].desc_) + sim_->watchedStops_[code].desc_ = msg; + // Print the stop message and code if it is not the default code. + if (code != kMaxStopCode) + printf("Simulator hit stop %u: %s\n", code, msg); + else + printf("Simulator hit %s\n", msg); + sim_->set_pc(sim_->get_pc() + 2 * SimInstruction::kInstrSize); + debug(); +} + +int64_t +MipsDebugger::getRegisterValue(int regnum) +{ + if (regnum == kPCRegister) + return sim_->get_pc(); + return sim_->getRegister(regnum); +} + +int64_t +MipsDebugger::getFPURegisterValueLong(int regnum) +{ + return sim_->getFpuRegister(regnum); +} + +float +MipsDebugger::getFPURegisterValueFloat(int regnum) +{ + return sim_->getFpuRegisterFloat(regnum); +} + +double +MipsDebugger::getFPURegisterValueDouble(int regnum) +{ + return sim_->getFpuRegisterDouble(regnum); +} + +bool +MipsDebugger::getValue(const char* desc, int64_t* value) +{ + Register reg = Register::FromName(desc); + if (reg != InvalidReg) { + *value = getRegisterValue(reg.code()); + return true; + } + + if (strncmp(desc, "0x", 2) == 0) + return sscanf(desc, "%lx", reinterpret_cast(value)) == 1; + return sscanf(desc, "%li", value) == 1; +} + +bool +MipsDebugger::setBreakpoint(SimInstruction* breakpc) +{ + // Check if a breakpoint can be set. If not return without any side-effects. + if (sim_->break_pc_ != nullptr) + return false; + + // Set the breakpoint. + sim_->break_pc_ = breakpc; + sim_->break_instr_ = breakpc->instructionBits(); + // Not setting the breakpoint instruction in the code itself. It will be set + // when the debugger shell continues. + return true; + +} + +bool +MipsDebugger::deleteBreakpoint(SimInstruction* breakpc) +{ + if (sim_->break_pc_ != nullptr) + sim_->break_pc_->setInstructionBits(sim_->break_instr_); + + sim_->break_pc_ = nullptr; + sim_->break_instr_ = 0; + return true; +} + +void +MipsDebugger::undoBreakpoints() +{ + if (sim_->break_pc_) + sim_->break_pc_->setInstructionBits(sim_->break_instr_); +} + +void +MipsDebugger::redoBreakpoints() +{ + if (sim_->break_pc_) + sim_->break_pc_->setInstructionBits(kBreakpointInstr); +} + +void +MipsDebugger::printAllRegs() +{ + int64_t value; + for (uint32_t i = 0; i < Registers::Total; i++) { + value = getRegisterValue(i); + printf("%3s: 0x%016lx %20ld ", Registers::GetName(i), value, value); + + if (i % 2) + printf("\n"); + } + printf("\n"); + + value = getRegisterValue(Simulator::LO); + printf(" LO: 0x%016lx %20ld ", value, value); + value = getRegisterValue(Simulator::HI); + printf(" HI: 0x%016lx %20ld\n", value, value); + value = getRegisterValue(Simulator::pc); + printf(" pc: 0x%016lx\n", value); +} + +void +MipsDebugger::printAllRegsIncludingFPU() +{ + printAllRegs(); + + printf("\n\n"); + // f0, f1, f2, ... f31. + for (uint32_t i = 0; i < FloatRegisters::TotalPhys; i++) { + printf("%3s: 0x%016lx\tflt: %-8.4g\tdbl: %-16.4g\n", + FloatRegisters::GetName(i), + getFPURegisterValueLong(i), + getFPURegisterValueFloat(i), + getFPURegisterValueDouble(i)); + } +} + +static char* +ReadLine(const char* prompt) +{ + char* result = nullptr; + char lineBuf[256]; + int offset = 0; + bool keepGoing = true; + fprintf(stdout, "%s", prompt); + fflush(stdout); + while (keepGoing) { + if (fgets(lineBuf, sizeof(lineBuf), stdin) == nullptr) { + // fgets got an error. Just give up. + if (result) + js_delete(result); + return nullptr; + } + int len = strlen(lineBuf); + if (len > 0 && lineBuf[len - 1] == '\n') { + // Since we read a new line we are done reading the line. This + // will exit the loop after copying this buffer into the result. + keepGoing = false; + } + if (!result) { + // Allocate the initial result and make room for the terminating '\0' + result = (char*)js_malloc(len + 1); + if (!result) + return nullptr; + } else { + // Allocate a new result with enough room for the new addition. + int new_len = offset + len + 1; + char* new_result = (char*)js_malloc(new_len); + if (!new_result) + return nullptr; + // Copy the existing input into the new array and set the new + // array as the result. + memcpy(new_result, result, offset * sizeof(char)); + js_free(result); + result = new_result; + } + // Copy the newly read line into the result. + memcpy(result + offset, lineBuf, len * sizeof(char)); + offset += len; + } + + MOZ_ASSERT(result); + result[offset] = '\0'; + return result; +} + +static void +DisassembleInstruction(uint64_t pc) +{ + uint8_t* bytes = reinterpret_cast(pc); + char hexbytes[256]; + sprintf(hexbytes, "0x%x 0x%x 0x%x 0x%x", bytes[0], bytes[1], bytes[2], bytes[3]); + char llvmcmd[1024]; + sprintf(llvmcmd, "bash -c \"echo -n '%p'; echo '%s' | " + "llvm-mc -disassemble -arch=mips64el -mcpu=mips64r2 | " + "grep -v pure_instructions | grep -v .text\"", static_cast(bytes), hexbytes); + if (system(llvmcmd)) + printf("Cannot disassemble instruction.\n"); +} + +void +MipsDebugger::debug() +{ + intptr_t lastPC = -1; + bool done = false; + +#define COMMAND_SIZE 63 +#define ARG_SIZE 255 + +#define STR(a) #a +#define XSTR(a) STR(a) + + char cmd[COMMAND_SIZE + 1]; + char arg1[ARG_SIZE + 1]; + char arg2[ARG_SIZE + 1]; + char* argv[3] = { cmd, arg1, arg2 }; + + // Make sure to have a proper terminating character if reaching the limit. + cmd[COMMAND_SIZE] = 0; + arg1[ARG_SIZE] = 0; + arg2[ARG_SIZE] = 0; + + // Undo all set breakpoints while running in the debugger shell. This will + // make them invisible to all commands. + undoBreakpoints(); + + while (!done && (sim_->get_pc() != Simulator::end_sim_pc)) { + if (lastPC != sim_->get_pc()) { + DisassembleInstruction(sim_->get_pc()); + lastPC = sim_->get_pc(); + } + char* line = ReadLine("sim> "); + if (line == nullptr) { + break; + } else { + char* last_input = sim_->lastDebuggerInput(); + if (strcmp(line, "\n") == 0 && last_input != nullptr) { + line = last_input; + } else { + // Ownership is transferred to sim_; + sim_->setLastDebuggerInput(line); + } + // Use sscanf to parse the individual parts of the command line. At the + // moment no command expects more than two parameters. + int argc = sscanf(line, + "%" XSTR(COMMAND_SIZE) "s " + "%" XSTR(ARG_SIZE) "s " + "%" XSTR(ARG_SIZE) "s", + cmd, arg1, arg2); + if ((strcmp(cmd, "si") == 0) || (strcmp(cmd, "stepi") == 0)) { + SimInstruction* instr = reinterpret_cast(sim_->get_pc()); + if (!(instr->isTrap()) || + instr->instructionBits() == kCallRedirInstr) { + sim_->instructionDecode( + reinterpret_cast(sim_->get_pc())); + } else { + // Allow si to jump over generated breakpoints. + printf("/!\\ Jumping over generated breakpoint.\n"); + sim_->set_pc(sim_->get_pc() + SimInstruction::kInstrSize); + } + } else if ((strcmp(cmd, "c") == 0) || (strcmp(cmd, "cont") == 0)) { + // Execute the one instruction we broke at with breakpoints disabled. + sim_->instructionDecode(reinterpret_cast(sim_->get_pc())); + // Leave the debugger shell. + done = true; + } else if ((strcmp(cmd, "p") == 0) || (strcmp(cmd, "print") == 0)) { + if (argc == 2) { + int64_t value; + if (strcmp(arg1, "all") == 0) { + printAllRegs(); + } else if (strcmp(arg1, "allf") == 0) { + printAllRegsIncludingFPU(); + } else { + Register reg = Register::FromName(arg1); + FloatRegisters::Encoding fReg = FloatRegisters::FromName(arg1); + if (reg != InvalidReg) { + value = getRegisterValue(reg.code()); + printf("%s: 0x%016lx %20ld \n", arg1, value, value); + } else if (fReg != FloatRegisters::Invalid) { + printf("%3s: 0x%016lx\tflt: %-8.4g\tdbl: %-16.4g\n", + FloatRegisters::GetName(fReg), + getFPURegisterValueLong(fReg), + getFPURegisterValueFloat(fReg), + getFPURegisterValueDouble(fReg)); + } else { + printf("%s unrecognized\n", arg1); + } + } + } else { + printf("print or print single\n"); + } + } else if (strcmp(cmd, "stack") == 0 || strcmp(cmd, "mem") == 0) { + int64_t* cur = nullptr; + int64_t* end = nullptr; + int next_arg = 1; + + if (strcmp(cmd, "stack") == 0) { + cur = reinterpret_cast(sim_->getRegister(Simulator::sp)); + } else { // Command "mem". + int64_t value; + if (!getValue(arg1, &value)) { + printf("%s unrecognized\n", arg1); + continue; + } + cur = reinterpret_cast(value); + next_arg++; + } + + int64_t words; + if (argc == next_arg) { + words = 10; + } else { + if (!getValue(argv[next_arg], &words)) { + words = 10; + } + } + end = cur + words; + + while (cur < end) { + printf(" %p: 0x%016lx %20ld", cur, *cur, *cur); + printf("\n"); + cur++; + } + + } else if ((strcmp(cmd, "disasm") == 0) || + (strcmp(cmd, "dpc") == 0) || + (strcmp(cmd, "di") == 0)) { + uint8_t* cur = nullptr; + uint8_t* end = nullptr; + + if (argc == 1) { + cur = reinterpret_cast(sim_->get_pc()); + end = cur + (10 * SimInstruction::kInstrSize); + } else if (argc == 2) { + Register reg = Register::FromName(arg1); + if (reg != InvalidReg || strncmp(arg1, "0x", 2) == 0) { + // The argument is an address or a register name. + int64_t value; + if (getValue(arg1, &value)) { + cur = reinterpret_cast(value); + // Disassemble 10 instructions at . + end = cur + (10 * SimInstruction::kInstrSize); + } + } else { + // The argument is the number of instructions. + int64_t value; + if (getValue(arg1, &value)) { + cur = reinterpret_cast(sim_->get_pc()); + // Disassemble instructions. + end = cur + (value * SimInstruction::kInstrSize); + } + } + } else { + int64_t value1; + int64_t value2; + if (getValue(arg1, &value1) && getValue(arg2, &value2)) { + cur = reinterpret_cast(value1); + end = cur + (value2 * SimInstruction::kInstrSize); + } + } + + while (cur < end) { + DisassembleInstruction(uint64_t(cur)); + cur += SimInstruction::kInstrSize; + } + } else if (strcmp(cmd, "gdb") == 0) { + printf("relinquishing control to gdb\n"); + asm("int $3"); + printf("regaining control from gdb\n"); + } else if (strcmp(cmd, "break") == 0) { + if (argc == 2) { + int64_t value; + if (getValue(arg1, &value)) { + if (!setBreakpoint(reinterpret_cast(value))) + printf("setting breakpoint failed\n"); + } else { + printf("%s unrecognized\n", arg1); + } + } else { + printf("break
\n"); + } + } else if (strcmp(cmd, "del") == 0) { + if (!deleteBreakpoint(nullptr)) { + printf("deleting breakpoint failed\n"); + } + } else if (strcmp(cmd, "flags") == 0) { + printf("No flags on MIPS !\n"); + } else if (strcmp(cmd, "stop") == 0) { + int64_t value; + intptr_t stop_pc = sim_->get_pc() - + 2 * SimInstruction::kInstrSize; + SimInstruction* stop_instr = reinterpret_cast(stop_pc); + SimInstruction* msg_address = + reinterpret_cast(stop_pc + + SimInstruction::kInstrSize); + if ((argc == 2) && (strcmp(arg1, "unstop") == 0)) { + // Remove the current stop. + if (sim_->isStopInstruction(stop_instr)) { + stop_instr->setInstructionBits(kNopInstr); + msg_address->setInstructionBits(kNopInstr); + } else { + printf("Not at debugger stop.\n"); + } + } else if (argc == 3) { + // Print information about all/the specified breakpoint(s). + if (strcmp(arg1, "info") == 0) { + if (strcmp(arg2, "all") == 0) { + printf("Stop information:\n"); + for (uint32_t i = kMaxWatchpointCode + 1; + i <= kMaxStopCode; + i++) { + sim_->printStopInfo(i); + } + } else if (getValue(arg2, &value)) { + sim_->printStopInfo(value); + } else { + printf("Unrecognized argument.\n"); + } + } else if (strcmp(arg1, "enable") == 0) { + // Enable all/the specified breakpoint(s). + if (strcmp(arg2, "all") == 0) { + for (uint32_t i = kMaxWatchpointCode + 1; + i <= kMaxStopCode; + i++) { + sim_->enableStop(i); + } + } else if (getValue(arg2, &value)) { + sim_->enableStop(value); + } else { + printf("Unrecognized argument.\n"); + } + } else if (strcmp(arg1, "disable") == 0) { + // Disable all/the specified breakpoint(s). + if (strcmp(arg2, "all") == 0) { + for (uint32_t i = kMaxWatchpointCode + 1; + i <= kMaxStopCode; + i++) { + sim_->disableStop(i); + } + } else if (getValue(arg2, &value)) { + sim_->disableStop(value); + } else { + printf("Unrecognized argument.\n"); + } + } + } else { + printf("Wrong usage. Use help command for more information.\n"); + } + } else if ((strcmp(cmd, "h") == 0) || (strcmp(cmd, "help") == 0)) { + printf("cont\n"); + printf(" continue execution (alias 'c')\n"); + printf("stepi\n"); + printf(" step one instruction (alias 'si')\n"); + printf("print \n"); + printf(" print register content (alias 'p')\n"); + printf(" use register name 'all' to print all registers\n"); + printf("printobject \n"); + printf(" print an object from a register (alias 'po')\n"); + printf("stack []\n"); + printf(" dump stack content, default dump 10 words)\n"); + printf("mem
[]\n"); + printf(" dump memory content, default dump 10 words)\n"); + printf("flags\n"); + printf(" print flags\n"); + printf("disasm []\n"); + printf("disasm [
]\n"); + printf("disasm [[
] ]\n"); + printf(" disassemble code, default is 10 instructions\n"); + printf(" from pc (alias 'di')\n"); + printf("gdb\n"); + printf(" enter gdb\n"); + printf("break
\n"); + printf(" set a break point on the address\n"); + printf("del\n"); + printf(" delete the breakpoint\n"); + printf("stop feature:\n"); + printf(" Description:\n"); + printf(" Stops are debug instructions inserted by\n"); + printf(" the Assembler::stop() function.\n"); + printf(" When hitting a stop, the Simulator will\n"); + printf(" stop and and give control to the Debugger.\n"); + printf(" All stop codes are watched:\n"); + printf(" - They can be enabled / disabled: the Simulator\n"); + printf(" will / won't stop when hitting them.\n"); + printf(" - The Simulator keeps track of how many times they \n"); + printf(" are met. (See the info command.) Going over a\n"); + printf(" disabled stop still increases its counter. \n"); + printf(" Commands:\n"); + printf(" stop info all/ : print infos about number \n"); + printf(" or all stop(s).\n"); + printf(" stop enable/disable all/ : enables / disables\n"); + printf(" all or number stop(s)\n"); + printf(" stop unstop\n"); + printf(" ignore the stop instruction at the current location\n"); + printf(" from now on\n"); + } else { + printf("Unknown command: %s\n", cmd); + } + } + } + + // Add all the breakpoints back to stop execution and enter the debugger + // shell when hit. + redoBreakpoints(); + +#undef COMMAND_SIZE +#undef ARG_SIZE + +#undef STR +#undef XSTR +} + +static bool +AllOnOnePage(uintptr_t start, int size) +{ + intptr_t start_page = (start & ~CachePage::kPageMask); + intptr_t end_page = ((start + size) & ~CachePage::kPageMask); + return start_page == end_page; +} + +void +Simulator::setLastDebuggerInput(char* input) +{ + js_free(lastDebuggerInput_); + lastDebuggerInput_ = input; +} + +static CachePage* +GetCachePageLocked(Simulator::ICacheMap& i_cache, void* page) +{ + Simulator::ICacheMap::AddPtr p = i_cache.lookupForAdd(page); + if (p) + return p->value(); + + CachePage* new_page = js_new(); + if (!i_cache.add(p, page, new_page)) + return nullptr; + return new_page; +} + +// Flush from start up to and not including start + size. +static void +FlushOnePageLocked(Simulator::ICacheMap& i_cache, intptr_t start, int size) +{ + MOZ_ASSERT(size <= CachePage::kPageSize); + MOZ_ASSERT(AllOnOnePage(start, size - 1)); + MOZ_ASSERT((start & CachePage::kLineMask) == 0); + MOZ_ASSERT((size & CachePage::kLineMask) == 0); + void* page = reinterpret_cast(start & (~CachePage::kPageMask)); + int offset = (start & CachePage::kPageMask); + CachePage* cache_page = GetCachePageLocked(i_cache, page); + char* valid_bytemap = cache_page->validityByte(offset); + memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift); +} + +static void +FlushICacheLocked(Simulator::ICacheMap& i_cache, void* start_addr, size_t size) +{ + intptr_t start = reinterpret_cast(start_addr); + int intra_line = (start & CachePage::kLineMask); + start -= intra_line; + size += intra_line; + size = ((size - 1) | CachePage::kLineMask) + 1; + int offset = (start & CachePage::kPageMask); + while (!AllOnOnePage(start, size - 1)) { + int bytes_to_flush = CachePage::kPageSize - offset; + FlushOnePageLocked(i_cache, start, bytes_to_flush); + start += bytes_to_flush; + size -= bytes_to_flush; + MOZ_ASSERT((start & CachePage::kPageMask) == 0); + offset = 0; + } + if (size != 0) + FlushOnePageLocked(i_cache, start, size); +} + +static void +CheckICacheLocked(Simulator::ICacheMap& i_cache, SimInstruction* instr) +{ + intptr_t address = reinterpret_cast(instr); + void* page = reinterpret_cast(address & (~CachePage::kPageMask)); + void* line = reinterpret_cast(address & (~CachePage::kLineMask)); + int offset = (address & CachePage::kPageMask); + CachePage* cache_page = GetCachePageLocked(i_cache, page); + char* cache_valid_byte = cache_page->validityByte(offset); + bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID); + char* cached_line = cache_page->cachedData(offset & ~CachePage::kLineMask); + if (cache_hit) { + // Check that the data in memory matches the contents of the I-cache. + MOZ_ASSERT(memcmp(reinterpret_cast(instr), + cache_page->cachedData(offset), + SimInstruction::kInstrSize) == 0); + } else { + // Cache miss. Load memory into the cache. + memcpy(cached_line, line, CachePage::kLineLength); + *cache_valid_byte = CachePage::LINE_VALID; + } +} + +HashNumber +Simulator::ICacheHasher::hash(const Lookup& l) +{ + return U32(reinterpret_cast(l)) >> 2; +} + +bool +Simulator::ICacheHasher::match(const Key& k, const Lookup& l) +{ + MOZ_ASSERT((reinterpret_cast(k) & CachePage::kPageMask) == 0); + MOZ_ASSERT((reinterpret_cast(l) & CachePage::kPageMask) == 0); + return k == l; +} + +void +Simulator::FlushICache(void* start_addr, size_t size) +{ + if (Simulator::ICacheCheckingEnabled) { + Simulator* sim = Simulator::Current(); + AutoLockSimulatorCache als(sim); + js::jit::FlushICacheLocked(sim->icache(), start_addr, size); + } +} + +Simulator::Simulator() +{ + // Set up simulator support first. Some of this information is needed to + // setup the architecture state. + + // Note, allocation and anything that depends on allocated memory is + // deferred until init(), in order to handle OOM properly. + + stack_ = nullptr; + stackLimit_ = 0; + pc_modified_ = false; + icount_ = 0; + break_count_ = 0; + resume_pc_ = 0; + break_pc_ = nullptr; + break_instr_ = 0; + single_stepping_ = false; + single_step_callback_ = nullptr; + single_step_callback_arg_ = nullptr; + + // Set up architecture state. + // All registers are initialized to zero to start with. + for (int i = 0; i < Register::kNumSimuRegisters; i++) + registers_[i] = 0; + for (int i = 0; i < Simulator::FPURegister::kNumFPURegisters; i++) + FPUregisters_[i] = 0; + FCSR_ = 0; + + // The ra and pc are initialized to a known bad value that will cause an + // access violation if the simulator ever tries to execute it. + registers_[pc] = bad_ra; + registers_[ra] = bad_ra; + + for (int i = 0; i < kNumExceptions; i++) + exceptions[i] = 0; + + lastDebuggerInput_ = nullptr; + + cacheLock_ = nullptr; +#ifdef DEBUG + cacheLockHolder_ = nullptr; +#endif + redirection_ = nullptr; +} + +bool +Simulator::init() +{ + cacheLock_ = PR_NewLock(); + if (!cacheLock_) + return false; + + if (!icache_.init()) + return false; + + // Allocate 2MB for the stack. Note that we will only use 1MB, see below. + static const size_t stackSize = 2 * 1024 * 1024; + stack_ = static_cast(js_malloc(stackSize)); + if (!stack_) + return false; + + // Leave a safety margin of 1MB to prevent overrunning the stack when + // pushing values (total stack size is 2MB). + stackLimit_ = reinterpret_cast(stack_) + 1024 * 1024; + + // The sp is initialized to point to the bottom (high address) of the + // allocated stack area. To be safe in potential stack underflows we leave + // some buffer below. + registers_[sp] = reinterpret_cast(stack_) + stackSize - 64; + + return true; +} + +// When the generated code calls an external reference we need to catch that in +// the simulator. The external reference will be a function compiled for the +// host architecture. We need to call that function instead of trying to +// execute it with the simulator. We do that by redirecting the external +// reference to a swi (software-interrupt) instruction that is handled by +// the simulator. We write the original destination of the jump just at a known +// offset from the swi instruction so the simulator knows what to call. +class Redirection +{ + friend class Simulator; + + // sim's lock must already be held. + Redirection(void* nativeFunction, ABIFunctionType type, Simulator* sim) + : nativeFunction_(nativeFunction), + swiInstruction_(kCallRedirInstr), + type_(type), + next_(nullptr) + { + next_ = sim->redirection(); + if (Simulator::ICacheCheckingEnabled) + FlushICacheLocked(sim->icache(), addressOfSwiInstruction(), SimInstruction::kInstrSize); + sim->setRedirection(this); + } + + public: + void* addressOfSwiInstruction() { return &swiInstruction_; } + void* nativeFunction() const { return nativeFunction_; } + ABIFunctionType type() const { return type_; } + + static Redirection* Get(void* nativeFunction, ABIFunctionType type) { + Simulator* sim = Simulator::Current(); + + AutoLockSimulatorCache als(sim); + + Redirection* current = sim->redirection(); + for (; current != nullptr; current = current->next_) { + if (current->nativeFunction_ == nativeFunction) { + MOZ_ASSERT(current->type() == type); + return current; + } + } + + Redirection* redir = (Redirection*)js_malloc(sizeof(Redirection)); + if (!redir) { + MOZ_ReportAssertionFailure("[unhandlable oom] Simulator redirection", + __FILE__, __LINE__); + MOZ_CRASH(); + } + new(redir) Redirection(nativeFunction, type, sim); + return redir; + } + + static Redirection* FromSwiInstruction(SimInstruction* swiInstruction) { + uint8_t* addrOfSwi = reinterpret_cast(swiInstruction); + uint8_t* addrOfRedirection = addrOfSwi - offsetof(Redirection, swiInstruction_); + return reinterpret_cast(addrOfRedirection); + } + + private: + void* nativeFunction_; + uint32_t swiInstruction_; + ABIFunctionType type_; + Redirection* next_; +}; + +Simulator::~Simulator() +{ + js_free(stack_); + PR_DestroyLock(cacheLock_); + Redirection* r = redirection_; + while (r) { + Redirection* next = r->next_; + js_delete(r); + r = next; + } +} + +/* static */ void* +Simulator::RedirectNativeFunction(void* nativeFunction, ABIFunctionType type) +{ + Redirection* redirection = Redirection::Get(nativeFunction, type); + return redirection->addressOfSwiInstruction(); +} + +// Get the active Simulator for the current thread. +Simulator* +Simulator::Current() +{ + return TlsPerThreadData.get()->simulator(); +} + +// Sets the register in the architecture state. It will also deal with updating +// Simulator internal state for special registers such as PC. +void +Simulator::setRegister(int reg, int64_t value) +{ + MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters)); + if (reg == pc) + pc_modified_ = true; + + // Zero register always holds 0. + registers_[reg] = (reg == 0) ? 0 : value; +} + +void +Simulator::setFpuRegister(int fpureg, int64_t value) +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + FPUregisters_[fpureg] = value; +} + +void +Simulator::setFpuRegisterLo(int fpureg, int32_t value) +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + *mozilla::BitwiseCast(&FPUregisters_[fpureg]) = value; +} + +void +Simulator::setFpuRegisterHi(int fpureg, int32_t value) +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + *((mozilla::BitwiseCast(&FPUregisters_[fpureg])) + 1) = value; +} + +void +Simulator::setFpuRegisterFloat(int fpureg, float value) +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + *mozilla::BitwiseCast(&FPUregisters_[fpureg]) = value; +} + +void +Simulator::setFpuRegisterDouble(int fpureg, double value) +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + *mozilla::BitwiseCast(&FPUregisters_[fpureg]) = value; +} + +// Get the register from the architecture state. This function does handle +// the special case of accessing the PC register. +int64_t +Simulator::getRegister(int reg) const +{ + MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters)); + if (reg == 0) + return 0; + return registers_[reg] + ((reg == pc) ? SimInstruction::kPCReadOffset : 0); +} + +int64_t +Simulator::getFpuRegister(int fpureg) const +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + return FPUregisters_[fpureg]; +} + +int32_t +Simulator::getFpuRegisterLo(int fpureg) const +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + return *mozilla::BitwiseCast(&FPUregisters_[fpureg]); +} + +int32_t +Simulator::getFpuRegisterHi(int fpureg) const +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + return *((mozilla::BitwiseCast(&FPUregisters_[fpureg])) + 1); +} + +float +Simulator::getFpuRegisterFloat(int fpureg) const +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + return *mozilla::BitwiseCast(&FPUregisters_[fpureg]); +} + +double +Simulator::getFpuRegisterDouble(int fpureg) const +{ + MOZ_ASSERT((fpureg >= 0) && (fpureg < Simulator::FPURegister::kNumFPURegisters)); + return *mozilla::BitwiseCast(&FPUregisters_[fpureg]); +} + +void +Simulator::setCallResultDouble(double result) +{ + setFpuRegisterDouble(f0, result); +} + +void +Simulator::setCallResultFloat(float result) +{ + setFpuRegisterFloat(f0, result); +} + +void +Simulator::setCallResult(int64_t res) +{ + setRegister(v0, res); +} + +void +Simulator::setCallResult(__int128_t res) +{ + setRegister(v0, I64(res)); + setRegister(v1, I64(res >> 64)); +} + +// Helper functions for setting and testing the FCSR register's bits. +void +Simulator::setFCSRBit(uint32_t cc, bool value) +{ + if (value) + FCSR_ |= (1 << cc); + else + FCSR_ &= ~(1 << cc); +} + +bool +Simulator::testFCSRBit(uint32_t cc) +{ + return FCSR_ & (1 << cc); +} + +// Sets the rounding error codes in FCSR based on the result of the rounding. +// Returns true if the operation was invalid. +bool +Simulator::setFCSRRoundError(double original, double rounded) +{ + bool ret = false; + + if (!std::isfinite(original) || !std::isfinite(rounded)) { + setFCSRBit(kFCSRInvalidOpFlagBit, true); + ret = true; + } + + if (original != rounded) + setFCSRBit(kFCSRInexactFlagBit, true); + + if (rounded < DBL_MIN && rounded > -DBL_MIN && rounded != 0) { + setFCSRBit(kFCSRUnderflowFlagBit, true); + ret = true; + } + + if (rounded > INT_MAX || rounded < INT_MIN) { + setFCSRBit(kFCSROverflowFlagBit, true); + // The reference is not really clear but it seems this is required: + setFCSRBit(kFCSRInvalidOpFlagBit, true); + ret = true; + } + + return ret; +} + +// Raw access to the PC register. +void +Simulator::set_pc(int64_t value) +{ + pc_modified_ = true; + registers_[pc] = value; +} + +bool +Simulator::has_bad_pc() const +{ + return ((registers_[pc] == bad_ra) || (registers_[pc] == end_sim_pc)); +} + +// Raw access to the PC register without the special adjustment when reading. +int64_t +Simulator::get_pc() const +{ + return registers_[pc]; +} + +// The MIPS cannot do unaligned reads and writes. On some MIPS platforms an +// interrupt is caused. On others it does a funky rotation thing. For now we +// simply disallow unaligned reads, but at some point we may want to move to +// emulating the rotate behaviour. Note that simulator runs have the runtime +// system running directly on the host system and only generated code is +// executed in the simulator. Since the host is typically IA32 we will not +// get the correct MIPS-like behaviour on unaligned accesses. + +uint8_t +Simulator::readBU(uint64_t addr, SimInstruction* instr) +{ + uint8_t* ptr = reinterpret_cast(addr); + return* ptr; +} + +int8_t +Simulator::readB(uint64_t addr, SimInstruction* instr) +{ + int8_t* ptr = reinterpret_cast(addr); + return* ptr; +} + +void +Simulator::writeB(uint64_t addr, uint8_t value, SimInstruction* instr) +{ + uint8_t* ptr = reinterpret_cast(addr); + *ptr = value; +} + +void +Simulator::writeB(uint64_t addr, int8_t value, SimInstruction* instr) +{ + int8_t* ptr = reinterpret_cast(addr); + *ptr = value; +} + +uint16_t +Simulator::readHU(uint64_t addr, SimInstruction* instr) +{ + if ((addr & 1) == 0) { + uint16_t* ptr = reinterpret_cast(addr); + return *ptr; + } + printf("Unaligned unsigned halfword read at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +int16_t +Simulator::readH(uint64_t addr, SimInstruction* instr) +{ + if ((addr & 1) == 0) { + int16_t* ptr = reinterpret_cast(addr); + return *ptr; + } + printf("Unaligned signed halfword read at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +void +Simulator::writeH(uint64_t addr, uint16_t value, SimInstruction* instr) +{ + if ((addr & 1) == 0) { + uint16_t* ptr = reinterpret_cast(addr); + *ptr = value; + return; + } + printf("Unaligned unsigned halfword write at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); +} + +void +Simulator::writeH(uint64_t addr, int16_t value, SimInstruction* instr) +{ + if ((addr & 1) == 0) { + int16_t* ptr = reinterpret_cast(addr); + *ptr = value; + return; + } + printf("Unaligned halfword write at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); +} + +uint32_t +Simulator::readWU(uint64_t addr, SimInstruction* instr) +{ + if (addr < 0x400) { + // This has to be a NULL-dereference, drop into debugger. + printf("Memory read from bad address: 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + } + if ((addr & 3) == 0) { + uint32_t* ptr = reinterpret_cast(addr); + return *ptr; + } + printf("Unaligned read at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +int32_t +Simulator::readW(uint64_t addr, SimInstruction* instr) +{ + if (addr < 0x400) { + // This has to be a NULL-dereference, drop into debugger. + printf("Memory read from bad address: 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + } + if ((addr & 3) == 0) { + int32_t* ptr = reinterpret_cast(addr); + return *ptr; + } + printf("Unaligned read at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +void +Simulator::writeW(uint64_t addr, uint32_t value, SimInstruction* instr) +{ + if (addr < 0x400) { + // This has to be a NULL-dereference, drop into debugger. + printf("Memory write to bad address: 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + } + if ((addr & 3) == 0) { + uint32_t* ptr = reinterpret_cast(addr); + *ptr = value; + return; + } + printf("Unaligned write at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); +} + +void +Simulator::writeW(uint64_t addr, int32_t value, SimInstruction* instr) +{ + if (addr < 0x400) { + // This has to be a NULL-dereference, drop into debugger. + printf("Memory write to bad address: 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + } + if ((addr & 3) == 0) { + int32_t* ptr = reinterpret_cast(addr); + *ptr = value; + return; + } + printf("Unaligned write at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); +} + +int64_t +Simulator::readDW(uint64_t addr, SimInstruction* instr) +{ + if (addr < 0x400) { + // This has to be a NULL-dereference, drop into debugger. + printf("Memory read from bad address: 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + } + if ((addr & kPointerAlignmentMask) == 0) { + int64_t* ptr = reinterpret_cast(addr); + return* ptr; + } + printf("Unaligned read at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +void +Simulator::writeDW(uint64_t addr, int64_t value, SimInstruction* instr) +{ + if (addr < 0x400) { + // This has to be a NULL-dereference, drop into debugger. + printf("Memory write to bad address: 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + } + if ((addr & kPointerAlignmentMask) == 0) { + int64_t* ptr = reinterpret_cast(addr); + *ptr = value; + return; + } + printf("Unaligned write at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); +} + +double +Simulator::readD(uint64_t addr, SimInstruction* instr) +{ + if ((addr & kDoubleAlignmentMask) == 0) { + double* ptr = reinterpret_cast(addr); + return *ptr; + } + printf("Unaligned (double) read at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +void +Simulator::writeD(uint64_t addr, double value, SimInstruction* instr) +{ + if ((addr & kDoubleAlignmentMask) == 0) { + double* ptr = reinterpret_cast(addr); + *ptr = value; + return; + } + printf("Unaligned (double) write at 0x%016lx, pc=0x%016lx\n", + addr, reinterpret_cast(instr)); + MOZ_CRASH(); +} + +uintptr_t +Simulator::stackLimit() const +{ + return stackLimit_; +} + +uintptr_t* +Simulator::addressOfStackLimit() +{ + return &stackLimit_; +} + +bool +Simulator::overRecursed(uintptr_t newsp) const +{ + if (newsp == 0) + newsp = getRegister(sp); + return newsp <= stackLimit(); +} + +bool +Simulator::overRecursedWithExtra(uint32_t extra) const +{ + uintptr_t newsp = getRegister(sp) - extra; + return newsp <= stackLimit(); +} + +// Unsupported instructions use format to print an error and stop execution. +void +Simulator::format(SimInstruction* instr, const char* format) +{ + printf("Simulator found unsupported instruction:\n 0x%016lx: %s\n", + reinterpret_cast(instr), format); + MOZ_CRASH(); +} + +// Note: With the code below we assume that all runtime calls return a 64 bits +// result. If they don't, the v1 result register contains a bogus value, which +// is fine because it is caller-saved. +typedef int64_t (*Prototype_General0)(); +typedef int64_t (*Prototype_General1)(int64_t arg0); +typedef int64_t (*Prototype_General2)(int64_t arg0, int64_t arg1); +typedef int64_t (*Prototype_General3)(int64_t arg0, int64_t arg1, int64_t arg2); +typedef int64_t (*Prototype_General4)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3); +typedef int64_t (*Prototype_General5)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, + int64_t arg4); +typedef int64_t (*Prototype_General6)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, + int64_t arg4, int64_t arg5); +typedef int64_t (*Prototype_General7)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, + int64_t arg4, int64_t arg5, int64_t arg6); +typedef int64_t (*Prototype_General8)(int64_t arg0, int64_t arg1, int64_t arg2, int64_t arg3, + int64_t arg4, int64_t arg5, int64_t arg6, int64_t arg7); + +typedef double (*Prototype_Double_None)(); +typedef double (*Prototype_Double_Double)(double arg0); +typedef double (*Prototype_Double_Int)(int64_t arg0); +typedef int64_t (*Prototype_Int_Double)(double arg0); +typedef int64_t (*Prototype_Int_DoubleIntInt)(double arg0, int64_t arg1, int64_t arg2); +typedef int64_t (*Prototype_Int_IntDoubleIntInt)(int64_t arg0, double arg1, int64_t arg2, + int64_t arg3); +typedef float (*Prototype_Float32_Float32)(float arg0); + +typedef double (*Prototype_DoubleInt)(double arg0, int64_t arg1); +typedef double (*Prototype_Double_IntDouble)(int64_t arg0, double arg1); +typedef double (*Prototype_Double_DoubleDouble)(double arg0, double arg1); +typedef int64_t (*Prototype_Int_IntDouble)(int64_t arg0, double arg1); + +typedef double (*Prototype_Double_DoubleDoubleDouble)(double arg0, double arg1, double arg2); +typedef double (*Prototype_Double_DoubleDoubleDoubleDouble)(double arg0, double arg1, + double arg2, double arg3); + +// Software interrupt instructions are used by the simulator to call into C++. +void +Simulator::softwareInterrupt(SimInstruction* instr) +{ + int32_t func = instr->functionFieldRaw(); + uint32_t code = (func == ff_break) ? instr->bits(25, 6) : -1; + + // We first check if we met a call_rt_redirected. + if (instr->instructionBits() == kCallRedirInstr) { +#if !defined(USES_N64_ABI) + MOZ_CRASH("Only N64 ABI supported."); +#else + Redirection* redirection = Redirection::FromSwiInstruction(instr); + int64_t arg0 = getRegister(a0); + int64_t arg1 = getRegister(a1); + int64_t arg2 = getRegister(a2); + int64_t arg3 = getRegister(a3); + int64_t arg4 = getRegister(a4); + int64_t arg5 = getRegister(a5); + + // This is dodgy but it works because the C entry stubs are never moved. + // See comment in codegen-arm.cc and bug 1242173. + int64_t saved_ra = getRegister(ra); + + intptr_t external = reinterpret_cast(redirection->nativeFunction()); + + bool stack_aligned = (getRegister(sp) & (ABIStackAlignment - 1)) == 0; + if (!stack_aligned) { + fprintf(stderr, "Runtime call with unaligned stack!\n"); + MOZ_CRASH(); + } + + if (single_stepping_) + single_step_callback_(single_step_callback_arg_, this, nullptr); + + switch (redirection->type()) { + case Args_General0: { + Prototype_General0 target = reinterpret_cast(external); + int64_t result = target(); + setCallResult(result); + break; + } + case Args_General1: { + Prototype_General1 target = reinterpret_cast(external); + int64_t result = target(arg0); + setCallResult(result); + break; + } + case Args_General2: { + Prototype_General2 target = reinterpret_cast(external); + int64_t result = target(arg0, arg1); + setCallResult(result); + break; + } + case Args_General3: { + Prototype_General3 target = reinterpret_cast(external); + int64_t result = target(arg0, arg1, arg2); + setCallResult(result); + break; + } + case Args_General4: { + Prototype_General4 target = reinterpret_cast(external); + int64_t result = target(arg0, arg1, arg2, arg3); + setCallResult(result); + break; + } + case Args_General5: { + Prototype_General5 target = reinterpret_cast(external); + int64_t result = target(arg0, arg1, arg2, arg3, arg4); + setCallResult(result); + break; + } + case Args_General6: { + Prototype_General6 target = reinterpret_cast(external); + int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5); + setCallResult(result); + break; + } + case Args_General7: { + Prototype_General7 target = reinterpret_cast(external); + int64_t arg6 = getRegister(a6); + int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6); + setCallResult(result); + break; + } + case Args_General8: { + Prototype_General8 target = reinterpret_cast(external); + int64_t arg6 = getRegister(a6); + int64_t arg7 = getRegister(a7); + int64_t result = target(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); + setCallResult(result); + break; + } + case Args_Double_None: { + Prototype_Double_None target = reinterpret_cast(external); + double dresult = target(); + setCallResultDouble(dresult); + break; + } + case Args_Int_Double: { + double dval0 = getFpuRegisterDouble(12); + Prototype_Int_Double target = reinterpret_cast(external); + int64_t res = target(dval0); + setRegister(v0, res); + break; + } + case Args_Int_DoubleIntInt: { + double dval = getFpuRegisterDouble(12); + Prototype_Int_DoubleIntInt target = reinterpret_cast(external); + int64_t res = target(dval, arg1, arg2); + setRegister(v0, res); + break; + } + case Args_Int_IntDoubleIntInt: { + double dval = getFpuRegisterDouble(13); + Prototype_Int_IntDoubleIntInt target = reinterpret_cast(external); + int64_t res = target(arg0, dval, arg2, arg3); + setRegister(v0, res); + break; + } + case Args_Double_Double: { + double dval0 = getFpuRegisterDouble(12); + Prototype_Double_Double target = reinterpret_cast(external); + double dresult = target(dval0); + setCallResultDouble(dresult); + break; + } + case Args_Float32_Float32: { + float fval0; + fval0 = getFpuRegisterFloat(12); + Prototype_Float32_Float32 target = reinterpret_cast(external); + float fresult = target(fval0); + setCallResultFloat(fresult); + break; + } + case Args_Double_Int: { + Prototype_Double_Int target = reinterpret_cast(external); + double dresult = target(arg0); + setCallResultDouble(dresult); + break; + } + case Args_Double_DoubleInt: { + double dval0 = getFpuRegisterDouble(12); + Prototype_DoubleInt target = reinterpret_cast(external); + double dresult = target(dval0, arg1); + setCallResultDouble(dresult); + break; + } + case Args_Double_DoubleDouble: { + double dval0 = getFpuRegisterDouble(12); + double dval1 = getFpuRegisterDouble(13); + Prototype_Double_DoubleDouble target = reinterpret_cast(external); + double dresult = target(dval0, dval1); + setCallResultDouble(dresult); + break; + } + case Args_Double_IntDouble: { + double dval1 = getFpuRegisterDouble(13); + Prototype_Double_IntDouble target = reinterpret_cast(external); + double dresult = target(arg0, dval1); + setCallResultDouble(dresult); + break; + } + case Args_Int_IntDouble: { + double dval1 = getFpuRegisterDouble(13); + Prototype_Int_IntDouble target = reinterpret_cast(external); + int64_t result = target(arg0, dval1); + setRegister(v0, result); + break; + } + case Args_Double_DoubleDoubleDouble: { + double dval0 = getFpuRegisterDouble(12); + double dval1 = getFpuRegisterDouble(13); + double dval2 = getFpuRegisterDouble(14); + Prototype_Double_DoubleDoubleDouble target = + reinterpret_cast(external); + double dresult = target(dval0, dval1, dval2); + setCallResultDouble(dresult); + break; + } + case Args_Double_DoubleDoubleDoubleDouble: { + double dval0 = getFpuRegisterDouble(12); + double dval1 = getFpuRegisterDouble(13); + double dval2 = getFpuRegisterDouble(14); + double dval3 = getFpuRegisterDouble(15); + Prototype_Double_DoubleDoubleDoubleDouble target = + reinterpret_cast(external); + double dresult = target(dval0, dval1, dval2, dval3); + setCallResultDouble(dresult); + break; + } + default: + MOZ_CRASH("call"); + } + + if (single_stepping_) + single_step_callback_(single_step_callback_arg_, this, nullptr); + + setRegister(ra, saved_ra); + set_pc(getRegister(ra)); +#endif + } else if (func == ff_break && code <= kMaxStopCode) { + if (isWatchpoint(code)) { + printWatchpoint(code); + } else { + increaseStopCounter(code); + handleStop(code, instr); + } + } else { + // All remaining break_ codes, and all traps are handled here. + MipsDebugger dbg(this); + dbg.debug(); + } +} + +// Stop helper functions. +bool +Simulator::isWatchpoint(uint32_t code) +{ + return (code <= kMaxWatchpointCode); +} + +void +Simulator::printWatchpoint(uint32_t code) +{ + MipsDebugger dbg(this); + ++break_count_; + printf("\n---- break %d marker: %20ld (instr count: %20ld) ----\n", + code, break_count_, icount_); + dbg.printAllRegs(); // Print registers and continue running. +} + +void +Simulator::handleStop(uint32_t code, SimInstruction* instr) +{ + // Stop if it is enabled, otherwise go on jumping over the stop + // and the message address. + if (isEnabledStop(code)) { + MipsDebugger dbg(this); + dbg.stop(instr); + } else { + set_pc(get_pc() + 2 * SimInstruction::kInstrSize); + } +} + +bool +Simulator::isStopInstruction(SimInstruction* instr) +{ + int32_t func = instr->functionFieldRaw(); + uint32_t code = U32(instr->bits(25, 6)); + return (func == ff_break) && code > kMaxWatchpointCode && code <= kMaxStopCode; +} + +bool +Simulator::isEnabledStop(uint32_t code) +{ + MOZ_ASSERT(code <= kMaxStopCode); + MOZ_ASSERT(code > kMaxWatchpointCode); + return !(watchedStops_[code].count_ & kStopDisabledBit); +} + +void +Simulator::enableStop(uint32_t code) +{ + if (!isEnabledStop(code)) + watchedStops_[code].count_ &= ~kStopDisabledBit; +} + +void +Simulator::disableStop(uint32_t code) +{ + if (isEnabledStop(code)) + watchedStops_[code].count_ |= kStopDisabledBit; +} + +void +Simulator::increaseStopCounter(uint32_t code) +{ + MOZ_ASSERT(code <= kMaxStopCode); + if ((watchedStops_[code].count_ & ~(1 << 31)) == 0x7fffffff) { + printf("Stop counter for code %i has overflowed.\n" + "Enabling this code and reseting the counter to 0.\n", code); + watchedStops_[code].count_ = 0; + enableStop(code); + } else { + watchedStops_[code].count_++; + } +} + +// Print a stop status. +void +Simulator::printStopInfo(uint32_t code) +{ + if (code <= kMaxWatchpointCode) { + printf("That is a watchpoint, not a stop.\n"); + return; + } else if (code > kMaxStopCode) { + printf("Code too large, only %u stops can be used\n", kMaxStopCode + 1); + return; + } + const char* state = isEnabledStop(code) ? "Enabled" : "Disabled"; + int32_t count = watchedStops_[code].count_ & ~kStopDisabledBit; + // Don't print the state of unused breakpoints. + if (count != 0) { + if (watchedStops_[code].desc_) { + printf("stop %i - 0x%x: \t%s, \tcounter = %i, \t%s\n", + code, code, state, count, watchedStops_[code].desc_); + } else { + printf("stop %i - 0x%x: \t%s, \tcounter = %i\n", + code, code, state, count); + } + } +} + +void +Simulator::signalExceptions() +{ + for (int i = 1; i < kNumExceptions; i++) { + if (exceptions[i] != 0) + MOZ_CRASH("Error: Exception raised."); + } +} + +// Helper function for decodeTypeRegister. +void +Simulator::configureTypeRegister(SimInstruction* instr, + int64_t& alu_out, + __int128& i128hilo, + unsigned __int128& u128hilo, + int64_t& next_pc, + int32_t& return_addr_reg, + bool& do_interrupt) +{ + // Every local variable declared here needs to be const. + // This is to make sure that changed values are sent back to + // decodeTypeRegister correctly. + + // Instruction fields. + const Opcode op = instr->opcodeFieldRaw(); + const int32_t rs_reg = instr->rsValue(); + const int64_t rs = getRegister(rs_reg); + const int32_t rt_reg = instr->rtValue(); + const int64_t rt = getRegister(rt_reg); + const int32_t rd_reg = instr->rdValue(); + const uint32_t sa = instr->saValue(); + + const int32_t fs_reg = instr->fsValue(); + __int128 temp; + + + // ---------- Configuration. + switch (op) { + case op_cop1: // Coprocessor instructions. + switch (instr->rsFieldRaw()) { + case rs_bc1: // Handled in DecodeTypeImmed, should never come here. + MOZ_CRASH(); + break; + case rs_cfc1: + // At the moment only FCSR is supported. + MOZ_ASSERT(fs_reg == kFCSRRegister); + alu_out = FCSR_; + break; + case rs_mfc1: + alu_out = getFpuRegisterLo(fs_reg); + break; + case rs_dmfc1: + alu_out = getFpuRegister(fs_reg); + break; + case rs_mfhc1: + alu_out = getFpuRegisterHi(fs_reg); + break; + case rs_ctc1: + case rs_mtc1: + case rs_dmtc1: + case rs_mthc1: + // Do the store in the execution step. + break; + case rs_s: + case rs_d: + case rs_w: + case rs_l: + case rs_ps: + // Do everything in the execution step. + break; + default: + MOZ_CRASH(); + }; + break; + case op_cop1x: + break; + case op_special: + switch (instr->functionFieldRaw()) { + case ff_jr: + case ff_jalr: + next_pc = getRegister(instr->rsValue()); + return_addr_reg = instr->rdValue(); + break; + case ff_sll: + alu_out = I32(rt) << sa; + break; + case ff_dsll: + alu_out = rt << sa; + break; + case ff_dsll32: + alu_out = rt << (sa + 32); + break; + case ff_srl: + if (rs_reg == 0) { + // Regular logical right shift of a word by a fixed number of + // bits instruction. RS field is always equal to 0. + alu_out = I32(U32(rt) >> sa); + } else { + // Logical right-rotate of a word by a fixed number of bits. This + // is special case of SRL instruction, added in MIPS32 Release 2. + // RS field is equal to 00001. + alu_out = I32((U32(rt) >> sa) | (U32(rt) << (32 - sa))); + } + break; + case ff_dsrl: + if (rs_reg == 0) { + // Regular logical right shift of a double word by a fixed number of + // bits instruction. RS field is always equal to 0. + alu_out = U64(rt) >> sa; + } else { + // Logical right-rotate of a word by a fixed number of bits. This + // is special case of DSRL instruction, added in MIPS64 Release 2. + // RS field is equal to 00001. + alu_out = (U64(rt) >> sa) | (U64(rt) << (64 - sa)); + } + break; + case ff_dsrl32: + if (rs_reg == 0) { + // Regular logical right shift of a double word by a fixed number of + // bits instruction. RS field is always equal to 0. + alu_out = U64(rt) >> (sa + 32); + } else { + // Logical right-rotate of a double word by a fixed number of bits. This + // is special case of DSRL instruction, added in MIPS64 Release 2. + // RS field is equal to 00001. + alu_out = (U64(rt) >> (sa + 32)) | (U64(rt) << (64 - (sa + 32))); + } + break; + case ff_sra: + alu_out = I32(rt) >> sa; + break; + case ff_dsra: + alu_out = rt >> sa; + break; + case ff_dsra32: + alu_out = rt >> (sa + 32); + break; + case ff_sllv: + alu_out = I32(rt) << rs; + break; + case ff_dsllv: + alu_out = rt << rs; + break; + case ff_srlv: + if (sa == 0) { + // Regular logical right-shift of a word by a variable number of + // bits instruction. SA field is always equal to 0. + alu_out = I32(U32(rt) >> rs); + } else { + // Logical right-rotate of a word by a variable number of bits. + // This is special case od SRLV instruction, added in MIPS32 + // Release 2. SA field is equal to 00001. + alu_out = I32((U32(rt) >> rs) | (U32(rt) << (32 - rs))); + } + break; + case ff_dsrlv: + if (sa == 0) { + // Regular logical right-shift of a double word by a variable number of + // bits instruction. SA field is always equal to 0. + alu_out = U64(rt) >> rs; + } else { + // Logical right-rotate of a double word by a variable number of bits. + // This is special case od DSRLV instruction, added in MIPS64 + // Release 2. SA field is equal to 00001. + alu_out = (U64(rt) >> rs) | (U64(rt) << (64 - rs)); + } + break; + case ff_srav: + alu_out = I32(rt) >> rs; + break; + case ff_dsrav: + alu_out = rt >> rs; + break; + case ff_mfhi: + alu_out = getRegister(HI); + break; + case ff_mflo: + alu_out = getRegister(LO); + break; + case ff_mult: + i128hilo = I32(rs) * I32(rt); + break; + case ff_dmult: + i128hilo = I128(rs) * I128(rt); + break; + case ff_multu: + u128hilo = U32(rs) * U32(rt); + break; + case ff_dmultu: + u128hilo = U128(rs) * U128(rt); + break; + case ff_add: + alu_out = I32(rs) + I32(rt); + if ((alu_out << 32) != (alu_out << 31)) + exceptions[kIntegerOverflow] = 1; + alu_out = I32(alu_out); + break; + case ff_dadd: + temp = I128(rs) + I128(rt); + if ((temp << 64) != (temp << 63)) + exceptions[kIntegerOverflow] = 1; + alu_out = I64(temp); + break; + case ff_addu: + alu_out = I32(U32(rs) + U32(rt)); + break; + case ff_daddu: + alu_out = rs + rt; + break; + case ff_sub: + alu_out = I32(rs) - I32(rt); + if ((alu_out << 32) != (alu_out << 31)) + exceptions[kIntegerUnderflow] = 1; + alu_out = I32(alu_out); + break; + case ff_dsub: + temp = I128(rs) - I128(rt); + if ((temp << 64) != (temp << 63)) + exceptions[kIntegerUnderflow] = 1; + alu_out = I64(temp); + break; + case ff_subu: + alu_out = I32(U32(rs) - U32(rt)); + break; + case ff_dsubu: + alu_out = rs - rt; + break; + case ff_and: + alu_out = rs & rt; + break; + case ff_or: + alu_out = rs | rt; + break; + case ff_xor: + alu_out = rs ^ rt; + break; + case ff_nor: + alu_out = ~(rs | rt); + break; + case ff_slt: + alu_out = rs < rt ? 1 : 0; + break; + case ff_sltu: + alu_out = U64(rs) < U64(rt) ? 1 : 0; + break; + case ff_sync: + break; + // Break and trap instructions. + case ff_break: + do_interrupt = true; + break; + case ff_tge: + do_interrupt = rs >= rt; + break; + case ff_tgeu: + do_interrupt = U64(rs) >= U64(rt); + break; + case ff_tlt: + do_interrupt = rs < rt; + break; + case ff_tltu: + do_interrupt = U64(rs) < U64(rt); + break; + case ff_teq: + do_interrupt = rs == rt; + break; + case ff_tne: + do_interrupt = rs != rt; + break; + case ff_movn: + case ff_movz: + case ff_movci: + // No action taken on decode. + break; + case ff_div: + if (I32(rs) == INT_MIN && I32(rt) == -1) { + i128hilo = U32(INT_MIN); + } else { + uint32_t div = I32(rs) / I32(rt); + uint32_t mod = I32(rs) % I32(rt); + i128hilo = (I64(mod) << 32) | div; + } + break; + case ff_ddiv: + if (I32(rs) == INT_MIN && I32(rt) == -1) { + i128hilo = U64(INT64_MIN); + } else { + uint64_t div = rs / rt; + uint64_t mod = rs % rt; + i128hilo = (I128(mod) << 64) | div; + } + break; + case ff_divu: { + uint32_t div = U32(rs) / U32(rt); + uint32_t mod = U32(rs) % U32(rt); + i128hilo = (U64(mod) << 32) | div; + } + break; + case ff_ddivu: + if (0 == rt) { + i128hilo = (I128(Unpredictable) << 64) | I64(Unpredictable); + } else { + uint64_t div = U64(rs) / U64(rt); + uint64_t mod = U64(rs) % U64(rt); + i128hilo = (I128(mod) << 64) | div; + } + break; + default: + MOZ_CRASH(); + }; + break; + case op_special2: + switch (instr->functionFieldRaw()) { + case ff_mul: + alu_out = I32(I32(rs) * I32(rt)); // Only the lower 32 bits are kept. + break; + case ff_clz: + alu_out = U32(rs) ? __builtin_clz(U32(rs)) : 32; + break; + case ff_dclz: + alu_out = U64(rs) ? __builtin_clzl(U64(rs)) : 64; + break; + default: + MOZ_CRASH(); + }; + break; + case op_special3: + switch (instr->functionFieldRaw()) { + case ff_ins: { // Mips64r2 instruction. + // Interpret rd field as 5-bit msb of insert. + uint16_t msb = rd_reg; + // Interpret sa field as 5-bit lsb of insert. + uint16_t lsb = sa; + uint16_t size = msb - lsb + 1; + uint32_t mask = (1 << size) - 1; + if (lsb > msb) + alu_out = Unpredictable; + else + alu_out = (U32(rt) & ~(mask << lsb)) | ((U32(rs) & mask) << lsb); + break; + } + case ff_dins: { // Mips64r2 instruction. + // Interpret rd field as 5-bit msb of insert. + uint16_t msb = rd_reg; + // Interpret sa field as 5-bit lsb of insert. + uint16_t lsb = sa; + uint16_t size = msb - lsb + 1; + uint64_t mask = (1ul << size) - 1; + if (lsb > msb) + alu_out = Unpredictable; + else + alu_out = (U64(rt) & ~(mask << lsb)) | ((U64(rs) & mask) << lsb); + break; + } + case ff_dinsm: { // Mips64r2 instruction. + // Interpret rd field as 5-bit msb of insert. + uint16_t msb = rd_reg; + // Interpret sa field as 5-bit lsb of insert. + uint16_t lsb = sa; + uint16_t size = msb - lsb + 33; + uint64_t mask = (1ul << size) - 1; + alu_out = (U64(rt) & ~(mask << lsb)) | ((U64(rs) & mask) << lsb); + break; + } + case ff_dinsu: { // Mips64r2 instruction. + // Interpret rd field as 5-bit msb of insert. + uint16_t msb = rd_reg; + // Interpret sa field as 5-bit lsb of insert. + uint16_t lsb = sa + 32; + uint16_t size = msb - lsb + 33; + uint64_t mask = (1ul << size) - 1; + if (sa > msb) + alu_out = Unpredictable; + else + alu_out = (U64(rt) & ~(mask << lsb)) | ((U64(rs) & mask) << lsb); + break; + } + case ff_ext: { // Mips64r2 instruction. + // Interpret rd field as 5-bit msb of extract. + uint16_t msb = rd_reg; + // Interpret sa field as 5-bit lsb of extract. + uint16_t lsb = sa; + uint16_t size = msb + 1; + uint32_t mask = (1 << size) - 1; + if ((lsb + msb) > 31) + alu_out = Unpredictable; + else + alu_out = (U32(rs) & (mask << lsb)) >> lsb; + break; + } + case ff_dext: { // Mips64r2 instruction. + // Interpret rd field as 5-bit msb of extract. + uint16_t msb = rd_reg; + // Interpret sa field as 5-bit lsb of extract. + uint16_t lsb = sa; + uint16_t size = msb + 1; + uint64_t mask = (1ul << size) - 1; + alu_out = (U64(rs) & (mask << lsb)) >> lsb; + break; + } + case ff_dextm: { // Mips64r2 instruction. + // Interpret rd field as 5-bit msb of extract. + uint16_t msb = rd_reg; + // Interpret sa field as 5-bit lsb of extract. + uint16_t lsb = sa; + uint16_t size = msb + 33; + uint64_t mask = (1ul << size) - 1; + if ((lsb + msb + 32 + 1) > 64) + alu_out = Unpredictable; + else + alu_out = (U64(rs) & (mask << lsb)) >> lsb; + break; + } + case ff_dextu: { // Mips64r2 instruction. + // Interpret rd field as 5-bit msb of extract. + uint16_t msb = rd_reg; + // Interpret sa field as 5-bit lsb of extract. + uint16_t lsb = sa + 32; + uint16_t size = msb + 1; + uint64_t mask = (1ul << size) - 1; + if ((lsb + msb + 1) > 64) + alu_out = Unpredictable; + else + alu_out = (U64(rs) & (mask << lsb)) >> lsb; + break; + } + case ff_bshfl: { // Mips32r2 instruction. + if (16 == sa) // seb + alu_out = I64(I8(rt)); + else if (24 == sa) // seh + alu_out = I64(I16(rt)); + break; + } + default: + MOZ_CRASH(); + }; + break; + default: + MOZ_CRASH(); + }; +} + +// Handle execution based on instruction types. +void +Simulator::decodeTypeRegister(SimInstruction* instr) +{ + // Instruction fields. + const Opcode op = instr->opcodeFieldRaw(); + const int32_t rs_reg = instr->rsValue(); + const int64_t rs = getRegister(rs_reg); + const int32_t rt_reg = instr->rtValue(); + const int64_t rt = getRegister(rt_reg); + const int32_t rd_reg = instr->rdValue(); + + const int32_t fr_reg = instr->frValue(); + const int32_t fs_reg = instr->fsValue(); + const int32_t ft_reg = instr->ftValue(); + const int32_t fd_reg = instr->fdValue(); + __int128 i128hilo = 0; + unsigned __int128 u128hilo = 0; + + // ALU output. + // It should not be used as is. Instructions using it should always + // initialize it first. + int64_t alu_out = 0x12345678; + + // For break and trap instructions. + bool do_interrupt = false; + + // For jr and jalr. + // Get current pc. + int64_t current_pc = get_pc(); + // Next pc + int64_t next_pc = 0; + int32_t return_addr_reg = 31; + + // Set up the variables if needed before executing the instruction. + configureTypeRegister(instr, + alu_out, + i128hilo, + u128hilo, + next_pc, + return_addr_reg, + do_interrupt); + + // ---------- Raise exceptions triggered. + signalExceptions(); + + // ---------- Execution. + switch (op) { + case op_cop1: + switch (instr->rsFieldRaw()) { + case rs_bc1: // Branch on coprocessor condition. + MOZ_CRASH(); + break; + case rs_cfc1: + setRegister(rt_reg, alu_out); + case rs_mfc1: + setRegister(rt_reg, alu_out); + break; + case rs_dmfc1: + setRegister(rt_reg, alu_out); + break; + case rs_mfhc1: + setRegister(rt_reg, alu_out); + break; + case rs_ctc1: + // At the moment only FCSR is supported. + MOZ_ASSERT(fs_reg == kFCSRRegister); + FCSR_ = registers_[rt_reg]; + break; + case rs_mtc1: + setFpuRegisterLo(fs_reg, registers_[rt_reg]); + break; + case rs_dmtc1: + setFpuRegister(fs_reg, registers_[rt_reg]); + break; + case rs_mthc1: + setFpuRegisterHi(fs_reg, registers_[rt_reg]); + break; + case rs_s: + float f, ft_value, fs_value; + uint32_t cc, fcsr_cc; + int64_t i64; + fs_value = getFpuRegisterFloat(fs_reg); + ft_value = getFpuRegisterFloat(ft_reg); + cc = instr->fcccValue(); + fcsr_cc = GetFCSRConditionBit(cc); + switch (instr->functionFieldRaw()) { + case ff_add_fmt: + setFpuRegisterFloat(fd_reg, fs_value + ft_value); + break; + case ff_sub_fmt: + setFpuRegisterFloat(fd_reg, fs_value - ft_value); + break; + case ff_mul_fmt: + setFpuRegisterFloat(fd_reg, fs_value * ft_value); + break; + case ff_div_fmt: + setFpuRegisterFloat(fd_reg, fs_value / ft_value); + break; + case ff_abs_fmt: + setFpuRegisterFloat(fd_reg, fabsf(fs_value)); + break; + case ff_mov_fmt: + setFpuRegisterFloat(fd_reg, fs_value); + break; + case ff_neg_fmt: + setFpuRegisterFloat(fd_reg, -fs_value); + break; + case ff_sqrt_fmt: + setFpuRegisterFloat(fd_reg, sqrtf(fs_value)); + break; + case ff_c_un_fmt: + setFCSRBit(fcsr_cc, mozilla::IsNaN(fs_value) || mozilla::IsNaN(ft_value)); + break; + case ff_c_eq_fmt: + setFCSRBit(fcsr_cc, (fs_value == ft_value)); + break; + case ff_c_ueq_fmt: + setFCSRBit(fcsr_cc, + (fs_value == ft_value) || (mozilla::IsNaN(fs_value) || mozilla::IsNaN(ft_value))); + break; + case ff_c_olt_fmt: + setFCSRBit(fcsr_cc, (fs_value < ft_value)); + break; + case ff_c_ult_fmt: + setFCSRBit(fcsr_cc, + (fs_value < ft_value) || (mozilla::IsNaN(fs_value) || mozilla::IsNaN(ft_value))); + break; + case ff_c_ole_fmt: + setFCSRBit(fcsr_cc, (fs_value <= ft_value)); + break; + case ff_c_ule_fmt: + setFCSRBit(fcsr_cc, + (fs_value <= ft_value) || (mozilla::IsNaN(fs_value) || mozilla::IsNaN(ft_value))); + break; + case ff_cvt_d_fmt: + f = getFpuRegisterFloat(fs_reg); + setFpuRegisterDouble(fd_reg, static_cast(f)); + break; + case ff_cvt_w_fmt: // Convert float to word. + // Rounding modes are not yet supported. + MOZ_ASSERT((FCSR_ & 3) == 0); + // In rounding mode 0 it should behave like ROUND. + case ff_round_w_fmt: { // Round double to word (round half to even). + float rounded = std::floor(fs_value + 0.5); + int32_t result = I32(rounded); + if ((result & 1) != 0 && result - fs_value == 0.5) { + // If the number is halfway between two integers, + // round to the even one. + result--; + } + setFpuRegisterLo(fd_reg, result); + if (setFCSRRoundError(fs_value, rounded)) { + setFpuRegisterLo(fd_reg, kFPUInvalidResult); + } + break; + } + case ff_trunc_w_fmt: { // Truncate float to word (round towards 0). + float rounded = truncf(fs_value); + int32_t result = I32(rounded); + setFpuRegisterLo(fd_reg, result); + if (setFCSRRoundError(fs_value, rounded)) { + setFpuRegisterLo(fd_reg, kFPUInvalidResult); + } + break; + } + case ff_floor_w_fmt: { // Round float to word towards negative infinity. + float rounded = std::floor(fs_value); + int32_t result = I32(rounded); + setFpuRegisterLo(fd_reg, result); + if (setFCSRRoundError(fs_value, rounded)) { + setFpuRegisterLo(fd_reg, kFPUInvalidResult); + } + break; + } + case ff_ceil_w_fmt: { // Round double to word towards positive infinity. + float rounded = std::ceil(fs_value); + int32_t result = I32(rounded); + setFpuRegisterLo(fd_reg, result); + if (setFCSRRoundError(fs_value, rounded)) { + setFpuRegisterLo(fd_reg, kFPUInvalidResult); + } + break; + } + case ff_cvt_l_fmt: { // Mips64r2: Truncate float to 64-bit long-word. + float rounded = truncf(fs_value); + i64 = I64(rounded); + setFpuRegister(fd_reg, i64); + break; + } + case ff_round_l_fmt: { // Mips64r2 instruction. + float rounded = + fs_value > 0 ? std::floor(fs_value + 0.5) : std::ceil(fs_value - 0.5); + i64 = I64(rounded); + setFpuRegister(fd_reg, i64); + break; + } + case ff_trunc_l_fmt: { // Mips64r2 instruction. + float rounded = truncf(fs_value); + i64 = I64(rounded); + setFpuRegister(fd_reg, i64); + break; + } + case ff_floor_l_fmt: // Mips64r2 instruction. + i64 = I64(std::floor(fs_value)); + setFpuRegister(fd_reg, i64); + break; + case ff_ceil_l_fmt: // Mips64r2 instruction. + i64 = I64(std::ceil(fs_value)); + setFpuRegister(fd_reg, i64); + break; + case ff_cvt_ps_s: + case ff_c_f_fmt: + MOZ_CRASH(); + break; + default: + MOZ_CRASH(); + } + break; + case rs_d: + double dt_value, ds_value; + ds_value = getFpuRegisterDouble(fs_reg); + dt_value = getFpuRegisterDouble(ft_reg); + cc = instr->fcccValue(); + fcsr_cc = GetFCSRConditionBit(cc); + switch (instr->functionFieldRaw()) { + case ff_add_fmt: + setFpuRegisterDouble(fd_reg, ds_value + dt_value); + break; + case ff_sub_fmt: + setFpuRegisterDouble(fd_reg, ds_value - dt_value); + break; + case ff_mul_fmt: + setFpuRegisterDouble(fd_reg, ds_value * dt_value); + break; + case ff_div_fmt: + setFpuRegisterDouble(fd_reg, ds_value / dt_value); + break; + case ff_abs_fmt: + setFpuRegisterDouble(fd_reg, fabs(ds_value)); + break; + case ff_mov_fmt: + setFpuRegisterDouble(fd_reg, ds_value); + break; + case ff_neg_fmt: + setFpuRegisterDouble(fd_reg, -ds_value); + break; + case ff_sqrt_fmt: + setFpuRegisterDouble(fd_reg, sqrt(ds_value)); + break; + case ff_c_un_fmt: + setFCSRBit(fcsr_cc, mozilla::IsNaN(ds_value) || mozilla::IsNaN(dt_value)); + break; + case ff_c_eq_fmt: + setFCSRBit(fcsr_cc, (ds_value == dt_value)); + break; + case ff_c_ueq_fmt: + setFCSRBit(fcsr_cc, + (ds_value == dt_value) || (mozilla::IsNaN(ds_value) || mozilla::IsNaN(dt_value))); + break; + case ff_c_olt_fmt: + setFCSRBit(fcsr_cc, (ds_value < dt_value)); + break; + case ff_c_ult_fmt: + setFCSRBit(fcsr_cc, + (ds_value < dt_value) || (mozilla::IsNaN(ds_value) || mozilla::IsNaN(dt_value))); + break; + case ff_c_ole_fmt: + setFCSRBit(fcsr_cc, (ds_value <= dt_value)); + break; + case ff_c_ule_fmt: + setFCSRBit(fcsr_cc, + (ds_value <= dt_value) || (mozilla::IsNaN(ds_value) || mozilla::IsNaN(dt_value))); + break; + case ff_cvt_w_fmt: // Convert double to word. + // Rounding modes are not yet supported. + MOZ_ASSERT((FCSR_ & 3) == 0); + // In rounding mode 0 it should behave like ROUND. + case ff_round_w_fmt: { // Round double to word (round half to even). + double rounded = std::floor(ds_value + 0.5); + int32_t result = I32(rounded); + if ((result & 1) != 0 && result - ds_value == 0.5) { + // If the number is halfway between two integers, + // round to the even one. + result--; + } + setFpuRegisterLo(fd_reg, result); + if (setFCSRRoundError(ds_value, rounded)) + setFpuRegisterLo(fd_reg, kFPUInvalidResult); + break; + } + case ff_trunc_w_fmt: { // Truncate double to word (round towards 0). + double rounded = trunc(ds_value); + int32_t result = I32(rounded); + setFpuRegisterLo(fd_reg, result); + if (setFCSRRoundError(ds_value, rounded)) + setFpuRegisterLo(fd_reg, kFPUInvalidResult); + break; + } + case ff_floor_w_fmt: { // Round double to word towards negative infinity. + double rounded = std::floor(ds_value); + int32_t result = I32(rounded); + setFpuRegisterLo(fd_reg, result); + if (setFCSRRoundError(ds_value, rounded)) + setFpuRegisterLo(fd_reg, kFPUInvalidResult); + break; + } + case ff_ceil_w_fmt: { // Round double to word towards positive infinity. + double rounded = std::ceil(ds_value); + int32_t result = I32(rounded); + setFpuRegisterLo(fd_reg, result); + if (setFCSRRoundError(ds_value, rounded)) + setFpuRegisterLo(fd_reg, kFPUInvalidResult); + break; + } + case ff_cvt_s_fmt: // Convert double to float (single). + setFpuRegisterFloat(fd_reg, static_cast(ds_value)); + break; + case ff_cvt_l_fmt: { // Mips64r2: Truncate double to 64-bit long-word. + double rounded = trunc(ds_value); + i64 = I64(rounded); + setFpuRegister(fd_reg, i64); + break; + } + case ff_trunc_l_fmt: { // Mips64r2 instruction. + double rounded = trunc(ds_value); + i64 = I64(rounded); + setFpuRegister(fd_reg, i64); + break; + } + case ff_round_l_fmt: { // Mips64r2 instruction. + double rounded = + ds_value > 0 ? std::floor(ds_value + 0.5) : std::ceil(ds_value - 0.5); + i64 = I64(rounded); + setFpuRegister(fd_reg, i64); + break; + } + case ff_floor_l_fmt: // Mips64r2 instruction. + i64 = I64(std::floor(ds_value)); + setFpuRegister(fd_reg, i64); + break; + case ff_ceil_l_fmt: // Mips64r2 instruction. + i64 = I64(std::ceil(ds_value)); + setFpuRegister(fd_reg, i64); + break; + case ff_c_f_fmt: + MOZ_CRASH(); + break; + default: + MOZ_CRASH(); + } + break; + case rs_w: + switch (instr->functionFieldRaw()) { + case ff_cvt_s_fmt: // Convert word to float (single). + i64 = getFpuRegisterLo(fs_reg); + setFpuRegisterFloat(fd_reg, static_cast(i64)); + break; + case ff_cvt_d_fmt: // Convert word to double. + i64 = getFpuRegisterLo(fs_reg); + setFpuRegisterDouble(fd_reg, static_cast(i64)); + break; + default: + MOZ_CRASH(); + }; + break; + case rs_l: + switch (instr->functionFieldRaw()) { + case ff_cvt_d_fmt: // Mips64r2 instruction. + i64 = getFpuRegister(fs_reg); + setFpuRegisterDouble(fd_reg, static_cast(i64)); + break; + case ff_cvt_s_fmt: + MOZ_CRASH(); + break; + default: + MOZ_CRASH(); + } + break; + case rs_ps: + break; + default: + MOZ_CRASH(); + }; + break; + case op_cop1x: + switch (instr->functionFieldRaw()) { + case ff_madd_s: + float fr, ft, fs; + fr = getFpuRegisterFloat(fr_reg); + fs = getFpuRegisterFloat(fs_reg); + ft = getFpuRegisterFloat(ft_reg); + setFpuRegisterFloat(fd_reg, fs * ft + fr); + break; + case ff_madd_d: + double dr, dt, ds; + dr = getFpuRegisterDouble(fr_reg); + ds = getFpuRegisterDouble(fs_reg); + dt = getFpuRegisterDouble(ft_reg); + setFpuRegisterDouble(fd_reg, ds * dt + dr); + break; + default: + MOZ_CRASH(); + }; + break; + case op_special: + switch (instr->functionFieldRaw()) { + case ff_jr: { + SimInstruction* branch_delay_instr = reinterpret_cast( + current_pc + SimInstruction::kInstrSize); + branchDelayInstructionDecode(branch_delay_instr); + set_pc(next_pc); + pc_modified_ = true; + break; + } + case ff_jalr: { + SimInstruction* branch_delay_instr = reinterpret_cast( + current_pc + SimInstruction::kInstrSize); + setRegister(return_addr_reg, current_pc + 2 * SimInstruction::kInstrSize); + branchDelayInstructionDecode(branch_delay_instr); + set_pc(next_pc); + pc_modified_ = true; + break; + } + // Instructions using HI and LO registers. + case ff_mult: + setRegister(LO, I32(i128hilo & 0xffffffff)); + setRegister(HI, I32(i128hilo >> 32)); + break; + case ff_dmult: + setRegister(LO, I64(i128hilo & 0xfffffffffffffffful)); + setRegister(HI, I64(i128hilo >> 64)); + break; + case ff_multu: + setRegister(LO, I32(u128hilo & 0xffffffff)); + setRegister(HI, I32(u128hilo >> 32)); + break; + case ff_dmultu: + setRegister(LO, I64(u128hilo & 0xfffffffffffffffful)); + setRegister(HI, I64(u128hilo >> 64)); + break; + case ff_div: + case ff_divu: + // Divide by zero and overflow was not checked in the configuration + // step - div and divu do not raise exceptions. On division by 0 + // the result will be UNPREDICTABLE. On overflow (INT_MIN/-1), + // return INT_MIN which is what the hardware does. + setRegister(LO, I32(i128hilo & 0xffffffff)); + setRegister(HI, I32(i128hilo >> 32)); + break; + case ff_ddiv: + case ff_ddivu: + // Divide by zero and overflow was not checked in the configuration + // step - div and divu do not raise exceptions. On division by 0 + // the result will be UNPREDICTABLE. On overflow (INT_MIN/-1), + // return INT_MIN which is what the hardware does. + setRegister(LO, I64(i128hilo & 0xfffffffffffffffful)); + setRegister(HI, I64(i128hilo >> 64)); + break; + case ff_sync: + break; + // Break and trap instructions. + case ff_break: + case ff_tge: + case ff_tgeu: + case ff_tlt: + case ff_tltu: + case ff_teq: + case ff_tne: + if (do_interrupt) { + softwareInterrupt(instr); + } + break; + // Conditional moves. + case ff_movn: + if (rt) + setRegister(rd_reg, rs); + break; + case ff_movci: { + uint32_t cc = instr->fbccValue(); + uint32_t fcsr_cc = GetFCSRConditionBit(cc); + if (instr->bit(16)) { // Read Tf bit. + if (testFCSRBit(fcsr_cc)) + setRegister(rd_reg, rs); + } else { + if (!testFCSRBit(fcsr_cc)) + setRegister(rd_reg, rs); + } + break; + } + case ff_movz: + if (!rt) + setRegister(rd_reg, rs); + break; + default: // For other special opcodes we do the default operation. + setRegister(rd_reg, alu_out); + }; + break; + case op_special2: + switch (instr->functionFieldRaw()) { + case ff_mul: + setRegister(rd_reg, alu_out); + // HI and LO are UNPREDICTABLE after the operation. + setRegister(LO, Unpredictable); + setRegister(HI, Unpredictable); + break; + default: // For other special2 opcodes we do the default operation. + setRegister(rd_reg, alu_out); + } + break; + case op_special3: + switch (instr->functionFieldRaw()) { + case ff_ins: + case ff_dins: + case ff_dinsm: + case ff_dinsu: + // Ins instr leaves result in Rt, rather than Rd. + setRegister(rt_reg, alu_out); + break; + case ff_ext: + case ff_dext: + case ff_dextm: + case ff_dextu: + // Ext instr leaves result in Rt, rather than Rd. + setRegister(rt_reg, alu_out); + break; + case ff_bshfl: + setRegister(rd_reg, alu_out); + break; + default: + MOZ_CRASH(); + }; + break; + // Unimplemented opcodes raised an error in the configuration step before, + // so we can use the default here to set the destination register in common + // cases. + default: + setRegister(rd_reg, alu_out); + }; +} + +// Type 2: instructions using a 16 bits immediate. (e.g. addi, beq). +void +Simulator::decodeTypeImmediate(SimInstruction* instr) +{ + // Instruction fields. + Opcode op = instr->opcodeFieldRaw(); + int64_t rs = getRegister(instr->rsValue()); + int32_t rt_reg = instr->rtValue(); // Destination register. + int64_t rt = getRegister(rt_reg); + int16_t imm16 = instr->imm16Value(); + + int32_t ft_reg = instr->ftValue(); // Destination register. + + // Zero extended immediate. + uint32_t oe_imm16 = 0xffff & imm16; + // Sign extended immediate. + int32_t se_imm16 = imm16; + + // Get current pc. + int64_t current_pc = get_pc(); + // Next pc. + int64_t next_pc = bad_ra; + + // Used for conditional branch instructions. + bool do_branch = false; + bool execute_branch_delay_instruction = false; + + // Used for arithmetic instructions. + int64_t alu_out = 0; + // Floating point. + double fp_out = 0.0; + uint32_t cc, cc_value, fcsr_cc; + + // Used for memory instructions. + uint64_t addr = 0x0; + // Value to be written in memory. + uint64_t mem_value = 0x0; + __int128 temp; + + // ---------- Configuration (and execution for op_regimm). + switch (op) { + // ------------- op_cop1. Coprocessor instructions. + case op_cop1: + switch (instr->rsFieldRaw()) { + case rs_bc1: // Branch on coprocessor condition. + cc = instr->fbccValue(); + fcsr_cc = GetFCSRConditionBit(cc); + cc_value = testFCSRBit(fcsr_cc); + do_branch = (instr->fbtrueValue()) ? cc_value : !cc_value; + execute_branch_delay_instruction = true; + // Set next_pc. + if (do_branch) + next_pc = current_pc + (imm16 << 2) + SimInstruction::kInstrSize; + else + next_pc = current_pc + kBranchReturnOffset; + break; + default: + MOZ_CRASH(); + }; + break; + // ------------- op_regimm class. + case op_regimm: + switch (instr->rtFieldRaw()) { + case rt_bltz: + do_branch = (rs < 0); + break; + case rt_bltzal: + do_branch = rs < 0; + break; + case rt_bgez: + do_branch = rs >= 0; + break; + case rt_bgezal: + do_branch = rs >= 0; + break; + default: + MOZ_CRASH(); + }; + switch (instr->rtFieldRaw()) { + case rt_bltz: + case rt_bltzal: + case rt_bgez: + case rt_bgezal: + // Branch instructions common part. + execute_branch_delay_instruction = true; + // Set next_pc. + if (do_branch) { + next_pc = current_pc + (imm16 << 2) + SimInstruction::kInstrSize; + if (instr->isLinkingInstruction()) + setRegister(31, current_pc + kBranchReturnOffset); + } else { + next_pc = current_pc + kBranchReturnOffset; + } + default: + break; + }; + break; // case op_regimm. + // ------------- Branch instructions. + // When comparing to zero, the encoding of rt field is always 0, so we don't + // need to replace rt with zero. + case op_beq: + do_branch = (rs == rt); + break; + case op_bne: + do_branch = rs != rt; + break; + case op_blez: + do_branch = rs <= 0; + break; + case op_bgtz: + do_branch = rs > 0; + break; + // ------------- Arithmetic instructions. + case op_addi: + alu_out = I32(rs) + se_imm16; + if ((alu_out << 32) != (alu_out << 31)) + exceptions[kIntegerOverflow] = 1; + alu_out = I32(alu_out); + break; + case op_daddi: + temp = alu_out = rs + se_imm16; + if ((temp << 64) != (temp << 63)) + exceptions[kIntegerOverflow] = 1; + alu_out = I64(temp); + break; + case op_addiu: + alu_out = I32(I32(rs) + se_imm16); + break; + case op_daddiu: + alu_out = rs + se_imm16; + break; + case op_slti: + alu_out = (rs < se_imm16) ? 1 : 0; + break; + case op_sltiu: + alu_out = (U64(rs) < U64(se_imm16)) ? 1 : 0; + break; + case op_andi: + alu_out = rs & oe_imm16; + break; + case op_ori: + alu_out = rs | oe_imm16; + break; + case op_xori: + alu_out = rs ^ oe_imm16; + break; + case op_lui: + alu_out = (se_imm16 << 16); + break; + // ------------- Memory instructions. + case op_lbu: + addr = rs + se_imm16; + alu_out = readBU(addr, instr); + break; + case op_lb: + addr = rs + se_imm16; + alu_out = readB(addr, instr); + break; + case op_lhu: + addr = rs + se_imm16; + alu_out = readHU(addr, instr); + break; + case op_lh: + addr = rs + se_imm16; + alu_out = readH(addr, instr); + break; + case op_lwu: + addr = rs + se_imm16; + alu_out = readWU(addr, instr); + break; + case op_lw: + addr = rs + se_imm16; + alu_out = readW(addr, instr); + break; + case op_lwl: { + // al_offset is offset of the effective address within an aligned word. + uint8_t al_offset = (rs + se_imm16) & 3; + uint8_t byte_shift = 3 - al_offset; + uint32_t mask = (1 << byte_shift * 8) - 1; + addr = rs + se_imm16 - al_offset; + alu_out = readW(addr, instr); + alu_out <<= byte_shift * 8; + alu_out |= rt & mask; + break; + } + case op_lwr: { + // al_offset is offset of the effective address within an aligned word. + uint8_t al_offset = (rs + se_imm16) & 3; + uint8_t byte_shift = 3 - al_offset; + uint32_t mask = al_offset ? (~0 << (byte_shift + 1) * 8) : 0; + addr = rs + se_imm16 - al_offset; + alu_out = readW(addr, instr); + alu_out = U32(alu_out) >> al_offset * 8; + alu_out |= rt & mask; + break; + } + case op_ll: + addr = rs + se_imm16; + alu_out = readW(addr, instr); + break; + case op_ld: + addr = rs + se_imm16; + alu_out = readDW(addr, instr); + break; + case op_ldl: { + // al_offset is offset of the effective address within an aligned word. + uint8_t al_offset = (rs + se_imm16) & 7; + uint8_t byte_shift = 7 - al_offset; + uint64_t mask = (1ul << byte_shift * 8) - 1; + addr = rs + se_imm16 - al_offset; + alu_out = readDW(addr, instr); + alu_out <<= byte_shift * 8; + alu_out |= rt & mask; + break; + } + case op_ldr: { + // al_offset is offset of the effective address within an aligned word. + uint8_t al_offset = (rs + se_imm16) & 7; + uint8_t byte_shift = 7 - al_offset; + uint64_t mask = al_offset ? (~0ul << (byte_shift + 1) * 8) : 0; + addr = rs + se_imm16 - al_offset; + alu_out = readDW(addr, instr); + alu_out = U64(alu_out) >> al_offset * 8; + alu_out |= rt & mask; + break; + } + case op_sb: + addr = rs + se_imm16; + break; + case op_sh: + addr = rs + se_imm16; + break; + case op_sw: + addr = rs + se_imm16; + break; + case op_swl: { + uint8_t al_offset = (rs + se_imm16) & 3; + uint8_t byte_shift = 3 - al_offset; + uint32_t mask = byte_shift ? (~0 << (al_offset + 1) * 8) : 0; + addr = rs + se_imm16 - al_offset; + mem_value = readW(addr, instr) & mask; + mem_value |= U32(rt) >> byte_shift * 8; + break; + } + case op_swr: { + uint8_t al_offset = (rs + se_imm16) & 3; + uint32_t mask = (1 << al_offset * 8) - 1; + addr = rs + se_imm16 - al_offset; + mem_value = readW(addr, instr); + mem_value = (rt << al_offset * 8) | (mem_value & mask); + break; + } + case op_sc: + addr = rs + se_imm16; + break; + case op_sd: + addr = rs + se_imm16; + break; + case op_sdl: { + uint8_t al_offset = (rs + se_imm16) & 7; + uint8_t byte_shift = 7 - al_offset; + uint64_t mask = byte_shift ? (~0ul << (al_offset + 1) * 8) : 0; + addr = rs + se_imm16 - al_offset; + mem_value = readW(addr, instr) & mask; + mem_value |= U64(rt) >> byte_shift * 8; + break; + } + case op_sdr: { + uint8_t al_offset = (rs + se_imm16) & 7; + uint64_t mask = (1ul << al_offset * 8) - 1; + addr = rs + se_imm16 - al_offset; + mem_value = readW(addr, instr); + mem_value = (rt << al_offset * 8) | (mem_value & mask); + break; + } + case op_lwc1: + addr = rs + se_imm16; + alu_out = readW(addr, instr); + break; + case op_ldc1: + addr = rs + se_imm16; + fp_out = readD(addr, instr); + break; + case op_swc1: + case op_sdc1: + addr = rs + se_imm16; + break; + default: + MOZ_CRASH(); + }; + + // ---------- Raise exceptions triggered. + signalExceptions(); + + // ---------- Execution. + switch (op) { + // ------------- Branch instructions. + case op_beq: + case op_bne: + case op_blez: + case op_bgtz: + // Branch instructions common part. + execute_branch_delay_instruction = true; + // Set next_pc. + if (do_branch) { + next_pc = current_pc + (imm16 << 2) + SimInstruction::kInstrSize; + if (instr->isLinkingInstruction()) { + setRegister(31, current_pc + 2 * SimInstruction::kInstrSize); + } + } else { + next_pc = current_pc + 2 * SimInstruction::kInstrSize; + } + break; + // ------------- Arithmetic instructions. + case op_addi: + case op_daddi: + case op_addiu: + case op_daddiu: + case op_slti: + case op_sltiu: + case op_andi: + case op_ori: + case op_xori: + case op_lui: + setRegister(rt_reg, alu_out); + break; + // ------------- Memory instructions. + case op_lbu: + case op_lb: + case op_lhu: + case op_lh: + case op_lwu: + case op_lw: + case op_lwl: + case op_lwr: + case op_ll: + case op_ld: + case op_ldl: + case op_ldr: + setRegister(rt_reg, alu_out); + break; + case op_sb: + writeB(addr, I8(rt), instr); + break; + case op_sh: + writeH(addr, U16(rt), instr); + break; + case op_sw: + writeW(addr, I32(rt), instr); + break; + case op_swl: + writeW(addr, I32(mem_value), instr); + break; + case op_swr: + writeW(addr, I32(mem_value), instr); + break; + case op_sc: + writeW(addr, I32(rt), instr); + setRegister(rt_reg, 1); + break; + case op_sd: + writeDW(addr, rt, instr); + break; + case op_sdl: + writeDW(addr, mem_value, instr); + break; + case op_sdr: + writeDW(addr, mem_value, instr); + break; + case op_lwc1: + setFpuRegisterLo(ft_reg, alu_out); + break; + case op_ldc1: + setFpuRegisterDouble(ft_reg, fp_out); + break; + case op_swc1: + writeW(addr, getFpuRegisterLo(ft_reg), instr); + break; + case op_sdc1: + writeD(addr, getFpuRegisterDouble(ft_reg), instr); + break; + default: + break; + }; + + + if (execute_branch_delay_instruction) { + // Execute branch delay slot + // We don't check for end_sim_pc. First it should not be met as the current + // pc is valid. Secondly a jump should always execute its branch delay slot. + SimInstruction* branch_delay_instr = + reinterpret_cast(current_pc + SimInstruction::kInstrSize); + branchDelayInstructionDecode(branch_delay_instr); + } + + // If needed update pc after the branch delay execution. + if (next_pc != bad_ra) + set_pc(next_pc); +} + +// Type 3: instructions using a 26 bits immediate. (e.g. j, jal). +void +Simulator::decodeTypeJump(SimInstruction* instr) +{ + // Get current pc. + int64_t current_pc = get_pc(); + // Get unchanged bits of pc. + int64_t pc_high_bits = current_pc & 0xfffffffff0000000ul; + // Next pc. + int64_t next_pc = pc_high_bits | (instr->imm26Value() << 2); + + // Execute branch delay slot. + // We don't check for end_sim_pc. First it should not be met as the current pc + // is valid. Secondly a jump should always execute its branch delay slot. + SimInstruction* branch_delay_instr = + reinterpret_cast(current_pc + SimInstruction::kInstrSize); + branchDelayInstructionDecode(branch_delay_instr); + + // Update pc and ra if necessary. + // Do this after the branch delay execution. + if (instr->isLinkingInstruction()) + setRegister(31, current_pc + 2 * SimInstruction::kInstrSize); + set_pc(next_pc); + pc_modified_ = true; +} + +// Executes the current instruction. +void +Simulator::instructionDecode(SimInstruction* instr) +{ + if (Simulator::ICacheCheckingEnabled) { + AutoLockSimulatorCache als(this); + CheckICacheLocked(icache(), instr); + } + pc_modified_ = false; + + switch (instr->instructionType()) { + case SimInstruction::kRegisterType: + decodeTypeRegister(instr); + break; + case SimInstruction::kImmediateType: + decodeTypeImmediate(instr); + break; + case SimInstruction::kJumpType: + decodeTypeJump(instr); + break; + default: + UNSUPPORTED(); + } + if (!pc_modified_) + setRegister(pc, reinterpret_cast(instr) + SimInstruction::kInstrSize); +} + +void +Simulator::branchDelayInstructionDecode(SimInstruction* instr) +{ + if (single_stepping_) + single_step_callback_(single_step_callback_arg_, this, (void*)instr); + + if (instr->instructionBits() == NopInst) { + // Short-cut generic nop instructions. They are always valid and they + // never change the simulator state. + return; + } + + if (instr->isForbiddenInBranchDelay()) { + MOZ_CRASH("Eror:Unexpected opcode in a branch delay slot."); + } + instructionDecode(instr); +} + +void +Simulator::enable_single_stepping(SingleStepCallback cb, void* arg) +{ + single_stepping_ = true; + single_step_callback_ = cb; + single_step_callback_arg_ = arg; + single_step_callback_(single_step_callback_arg_, this, (void*)get_pc()); +} + +void +Simulator::disable_single_stepping() +{ + if (!single_stepping_) + return; + single_step_callback_(single_step_callback_arg_, this, (void*)get_pc()); + single_stepping_ = false; + single_step_callback_ = nullptr; + single_step_callback_arg_ = nullptr; +} + +template +void +Simulator::execute() +{ + if (single_stepping_) + single_step_callback_(single_step_callback_arg_, this, nullptr); + + // Get the PC to simulate. Cannot use the accessor here as we need the + // raw PC value and not the one used as input to arithmetic instructions. + int64_t program_counter = get_pc(); + AsmJSActivation* activation = TlsPerThreadData.get()->runtimeFromMainThread()->asmJSActivationStack(); + + while (program_counter != end_sim_pc) { + if (enableStopSimAt && (icount_ == Simulator::StopSimAt)) { + MipsDebugger dbg(this); + dbg.debug(); + } else { + if (single_stepping_) + single_step_callback_(single_step_callback_arg_, this, (void*)program_counter); + SimInstruction* instr = reinterpret_cast(program_counter); + instructionDecode(instr); + icount_++; + + int64_t rpc = resume_pc_; + if (MOZ_UNLIKELY(rpc != 0)) { + // AsmJS signal handler ran and we have to adjust the pc. + activation->setResumePC((void*)get_pc()); + set_pc(rpc); + resume_pc_ = 0; + } + } + program_counter = get_pc(); + } + + if (single_stepping_) + single_step_callback_(single_step_callback_arg_, this, nullptr); +} + +void +Simulator::callInternal(uint8_t* entry) +{ + // Prepare to execute the code at entry. + setRegister(pc, reinterpret_cast(entry)); + // Put down marker for end of simulation. The simulator will stop simulation + // when the PC reaches this value. By saving the "end simulation" value into + // the LR the simulation stops when returning to this call point. + setRegister(ra, end_sim_pc); + + // Remember the values of callee-saved registers. + // The code below assumes that r9 is not used as sb (static base) in + // simulator code and therefore is regarded as a callee-saved register. + int64_t s0_val = getRegister(s0); + int64_t s1_val = getRegister(s1); + int64_t s2_val = getRegister(s2); + int64_t s3_val = getRegister(s3); + int64_t s4_val = getRegister(s4); + int64_t s5_val = getRegister(s5); + int64_t s6_val = getRegister(s6); + int64_t s7_val = getRegister(s7); + int64_t gp_val = getRegister(gp); + int64_t sp_val = getRegister(sp); + int64_t fp_val = getRegister(fp); + + // Set up the callee-saved registers with a known value. To be able to check + // that they are preserved properly across JS execution. + int64_t callee_saved_value = icount_; + setRegister(s0, callee_saved_value); + setRegister(s1, callee_saved_value); + setRegister(s2, callee_saved_value); + setRegister(s3, callee_saved_value); + setRegister(s4, callee_saved_value); + setRegister(s5, callee_saved_value); + setRegister(s6, callee_saved_value); + setRegister(s7, callee_saved_value); + setRegister(gp, callee_saved_value); + setRegister(fp, callee_saved_value); + + // Start the simulation. + if (Simulator::StopSimAt != -1) + execute(); + else + execute(); + + // Check that the callee-saved registers have been preserved. + MOZ_ASSERT(callee_saved_value == getRegister(s0)); + MOZ_ASSERT(callee_saved_value == getRegister(s1)); + MOZ_ASSERT(callee_saved_value == getRegister(s2)); + MOZ_ASSERT(callee_saved_value == getRegister(s3)); + MOZ_ASSERT(callee_saved_value == getRegister(s4)); + MOZ_ASSERT(callee_saved_value == getRegister(s5)); + MOZ_ASSERT(callee_saved_value == getRegister(s6)); + MOZ_ASSERT(callee_saved_value == getRegister(s7)); + MOZ_ASSERT(callee_saved_value == getRegister(gp)); + MOZ_ASSERT(callee_saved_value == getRegister(fp)); + + // Restore callee-saved registers with the original value. + setRegister(s0, s0_val); + setRegister(s1, s1_val); + setRegister(s2, s2_val); + setRegister(s3, s3_val); + setRegister(s4, s4_val); + setRegister(s5, s5_val); + setRegister(s6, s6_val); + setRegister(s7, s7_val); + setRegister(gp, gp_val); + setRegister(sp, sp_val); + setRegister(fp, fp_val); +} + +int64_t +Simulator::call(uint8_t* entry, int argument_count, ...) +{ + va_list parameters; + va_start(parameters, argument_count); + + int64_t original_stack = getRegister(sp); + // Compute position of stack on entry to generated code. + int64_t entry_stack = original_stack; + if (argument_count > kCArgSlotCount) + entry_stack = entry_stack - argument_count * sizeof(int64_t); + else + entry_stack = entry_stack - kCArgsSlotsSize; + + entry_stack &= ~U64(ABIStackAlignment - 1); + + intptr_t* stack_argument = reinterpret_cast(entry_stack); + + // Setup the arguments. + for (int i = 0; i < argument_count; i++) { + js::jit::Register argReg; + if (GetIntArgReg(i, &argReg)) + setRegister(argReg.code(), va_arg(parameters, int64_t)); + else + stack_argument[i] = va_arg(parameters, int64_t); + } + + va_end(parameters); + setRegister(sp, entry_stack); + + callInternal(entry); + + // Pop stack passed arguments. + MOZ_ASSERT(entry_stack == getRegister(sp)); + setRegister(sp, original_stack); + + int64_t result = getRegister(v0); + return result; +} + +uintptr_t +Simulator::pushAddress(uintptr_t address) +{ + int new_sp = getRegister(sp) - sizeof(uintptr_t); + uintptr_t* stack_slot = reinterpret_cast(new_sp); + *stack_slot = address; + setRegister(sp, new_sp); + return new_sp; +} + +uintptr_t +Simulator::popAddress() +{ + int current_sp = getRegister(sp); + uintptr_t* stack_slot = reinterpret_cast(current_sp); + uintptr_t address = *stack_slot; + setRegister(sp, current_sp + sizeof(uintptr_t)); + return address; +} + +} // namespace jit +} // namespace js + +js::jit::Simulator* +JSRuntime::simulator() const +{ + return simulator_; +} + +js::jit::Simulator* +js::PerThreadData::simulator() const +{ + return runtime_->simulator(); +} + +uintptr_t* +JSRuntime::addressOfSimulatorStackLimit() +{ + return simulator_->addressOfStackLimit(); +} diff --git a/js/src/jit/mips64/Simulator-mips64.h b/js/src/jit/mips64/Simulator-mips64.h new file mode 100644 index 0000000000..7b032c4b13 --- /dev/null +++ b/js/src/jit/mips64/Simulator-mips64.h @@ -0,0 +1,440 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: */ +// Copyright 2011 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. + +#ifndef jit_mips64_Simulator_mips64_h +#define jit_mips64_Simulator_mips64_h + +#ifdef JS_SIMULATOR_MIPS64 + +#include "jslock.h" + +#include "jit/IonTypes.h" + +namespace js { +namespace jit { + +class Simulator; +class Redirection; +class CachePage; +class AutoLockSimulator; + +// When the SingleStepCallback is called, the simulator is about to execute +// sim->get_pc() and the current machine state represents the completed +// execution of the previous pc. +typedef void (*SingleStepCallback)(void* arg, Simulator* sim, void* pc); + +const intptr_t kPointerAlignment = 8; +const intptr_t kPointerAlignmentMask = kPointerAlignment - 1; + +const intptr_t kDoubleAlignment = 8; +const intptr_t kDoubleAlignmentMask = kDoubleAlignment - 1; + + +// Number of general purpose registers. +const int kNumRegisters = 32; + +// In the simulator, the PC register is simulated as the 34th register. +const int kPCRegister = 34; + +// Number coprocessor registers. +const int kNumFPURegisters = 32; + +// FPU (coprocessor 1) control registers. Currently only FCSR is implemented. +const int kFCSRRegister = 31; +const int kInvalidFPUControlRegister = -1; +const uint32_t kFPUInvalidResult = static_cast(1 << 31) - 1; + +// FCSR constants. +const uint32_t kFCSRInexactFlagBit = 2; +const uint32_t kFCSRUnderflowFlagBit = 3; +const uint32_t kFCSROverflowFlagBit = 4; +const uint32_t kFCSRDivideByZeroFlagBit = 5; +const uint32_t kFCSRInvalidOpFlagBit = 6; + +const uint32_t kFCSRInexactFlagMask = 1 << kFCSRInexactFlagBit; +const uint32_t kFCSRUnderflowFlagMask = 1 << kFCSRUnderflowFlagBit; +const uint32_t kFCSROverflowFlagMask = 1 << kFCSROverflowFlagBit; +const uint32_t kFCSRDivideByZeroFlagMask = 1 << kFCSRDivideByZeroFlagBit; +const uint32_t kFCSRInvalidOpFlagMask = 1 << kFCSRInvalidOpFlagBit; + +const uint32_t kFCSRFlagMask = + kFCSRInexactFlagMask | + kFCSRUnderflowFlagMask | + kFCSROverflowFlagMask | + kFCSRDivideByZeroFlagMask | + kFCSRInvalidOpFlagMask; + +const uint32_t kFCSRExceptionFlagMask = kFCSRFlagMask ^ kFCSRInexactFlagMask; + +// On MIPS64 Simulator breakpoints can have different codes: +// - Breaks between 0 and kMaxWatchpointCode are treated as simple watchpoints, +// the simulator will run through them and print the registers. +// - Breaks between kMaxWatchpointCode and kMaxStopCode are treated as stop() +// instructions (see Assembler::stop()). +// - Breaks larger than kMaxStopCode are simple breaks, dropping you into the +// debugger. +const uint32_t kMaxWatchpointCode = 31; +const uint32_t kMaxStopCode = 127; + +// ----------------------------------------------------------------------------- +// Utility functions + +typedef uint32_t Instr; +class SimInstruction; + +class Simulator { + friend class Redirection; + friend class MipsDebugger; + friend class AutoLockSimulatorCache; + public: + + // Registers are declared in order. See "See MIPS Run Linux" chapter 2. + enum Register { + no_reg = -1, + zero_reg = 0, + at, + v0, v1, + a0, a1, a2, a3, a4, a5, a6, a7, + t0, t1, t2, t3, + s0, s1, s2, s3, s4, s5, s6, s7, + t8, t9, + k0, k1, + gp, + sp, + s8, + ra, + // LO, HI, and pc. + LO, + HI, + pc, // pc must be the last register. + kNumSimuRegisters, + // aliases + fp = s8 + }; + + // Coprocessor registers. + enum FPURegister { + f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11, + f12, f13, f14, f15, f16, f17, f18, f19, f20, f21, + f22, f23, f24, f25, f26, f27, f28, f29, f30, f31, + kNumFPURegisters + }; + + // Returns nullptr on OOM. + static Simulator* Create(); + + static void Destroy(Simulator* simulator); + + // Constructor/destructor are for internal use only; use the static methods above. + Simulator(); + ~Simulator(); + + // The currently executing Simulator instance. Potentially there can be one + // for each native thread. + static Simulator* Current(); + + static inline uintptr_t StackLimit() { + return Simulator::Current()->stackLimit(); + } + + uintptr_t* addressOfStackLimit(); + + // Accessors for register state. Reading the pc value adheres to the MIPS + // architecture specification and is off by a 8 from the currently executing + // instruction. + void setRegister(int reg, int64_t value); + int64_t getRegister(int reg) const; + // Same for FPURegisters. + void setFpuRegister(int fpureg, int64_t value); + void setFpuRegisterLo(int fpureg, int32_t value); + void setFpuRegisterHi(int fpureg, int32_t value); + void setFpuRegisterFloat(int fpureg, float value); + void setFpuRegisterDouble(int fpureg, double value); + int64_t getFpuRegister(int fpureg) const; + int32_t getFpuRegisterLo(int fpureg) const; + int32_t getFpuRegisterHi(int fpureg) const; + float getFpuRegisterFloat(int fpureg) const; + double getFpuRegisterDouble(int fpureg) const; + void setFCSRBit(uint32_t cc, bool value); + bool testFCSRBit(uint32_t cc); + bool setFCSRRoundError(double original, double rounded); + + // Special case of set_register and get_register to access the raw PC value. + void set_pc(int64_t value); + int64_t get_pc() const; + + template + T get_pc_as() const { return reinterpret_cast(get_pc()); } + + void set_resume_pc(void* value) { + resume_pc_ = int64_t(value); + } + + void enable_single_stepping(SingleStepCallback cb, void* arg); + void disable_single_stepping(); + + // Accessor to the internal simulator stack area. + uintptr_t stackLimit() const; + bool overRecursed(uintptr_t newsp = 0) const; + bool overRecursedWithExtra(uint32_t extra) const; + + // Executes MIPS instructions until the PC reaches end_sim_pc. + template + void execute(); + + // Sets up the simulator state and grabs the result on return. + int64_t call(uint8_t* entry, int argument_count, ...); + + // Push an address onto the JS stack. + uintptr_t pushAddress(uintptr_t address); + + // Pop an address from the JS stack. + uintptr_t popAddress(); + + // Debugger input. + void setLastDebuggerInput(char* input); + char* lastDebuggerInput() { return lastDebuggerInput_; } + // ICache checking. + static void FlushICache(void* start, size_t size); + + // Returns true if pc register contains one of the 'SpecialValues' defined + // below (bad_ra, end_sim_pc). + bool has_bad_pc() const; + + private: + enum SpecialValues { + // Known bad pc value to ensure that the simulator does not execute + // without being properly setup. + bad_ra = -1, + // A pc value used to signal the simulator to stop execution. Generally + // the ra is set to this value on transition from native C code to + // simulated execution, so that the simulator can "return" to the native + // C code. + end_sim_pc = -2, + // Unpredictable value. + Unpredictable = 0xbadbeaf + }; + + bool init(); + + // Unsupported instructions use Format to print an error and stop execution. + void format(SimInstruction* instr, const char* format); + + // Read and write memory. + inline uint8_t readBU(uint64_t addr, SimInstruction* instr); + inline int8_t readB(uint64_t addr, SimInstruction* instr); + inline void writeB(uint64_t addr, uint8_t value, SimInstruction* instr); + inline void writeB(uint64_t addr, int8_t value, SimInstruction* instr); + + inline uint16_t readHU(uint64_t addr, SimInstruction* instr); + inline int16_t readH(uint64_t addr, SimInstruction* instr); + inline void writeH(uint64_t addr, uint16_t value, SimInstruction* instr); + inline void writeH(uint64_t addr, int16_t value, SimInstruction* instr); + + inline uint32_t readWU(uint64_t addr, SimInstruction* instr); + inline int32_t readW(uint64_t addr, SimInstruction* instr); + inline void writeW(uint64_t addr, uint32_t value, SimInstruction* instr); + inline void writeW(uint64_t addr, int32_t value, SimInstruction* instr); + + inline int64_t readDW(uint64_t addr, SimInstruction* instr); + inline int64_t readDWL(uint64_t addr, SimInstruction* instr); + inline int64_t readDWR(uint64_t addr, SimInstruction* instr); + inline void writeDW(uint64_t addr, int64_t value, SimInstruction* instr); + + inline double readD(uint64_t addr, SimInstruction* instr); + inline void writeD(uint64_t addr, double value, SimInstruction* instr); + + // Helper function for decodeTypeRegister. + void configureTypeRegister(SimInstruction* instr, + int64_t& alu_out, + __int128& i128hilo, + unsigned __int128& u128hilo, + int64_t& next_pc, + int32_t& return_addr_reg, + bool& do_interrupt); + + // Executing is handled based on the instruction type. + void decodeTypeRegister(SimInstruction* instr); + void decodeTypeImmediate(SimInstruction* instr); + void decodeTypeJump(SimInstruction* instr); + + // Used for breakpoints and traps. + void softwareInterrupt(SimInstruction* instr); + + // Stop helper functions. + bool isWatchpoint(uint32_t code); + void printWatchpoint(uint32_t code); + void handleStop(uint32_t code, SimInstruction* instr); + bool isStopInstruction(SimInstruction* instr); + bool isEnabledStop(uint32_t code); + void enableStop(uint32_t code); + void disableStop(uint32_t code); + void increaseStopCounter(uint32_t code); + void printStopInfo(uint32_t code); + + + // Executes one instruction. + void instructionDecode(SimInstruction* instr); + // Execute one instruction placed in a branch delay slot. + void branchDelayInstructionDecode(SimInstruction* instr); + + public: + static bool ICacheCheckingEnabled; + + static int64_t StopSimAt; + + // Runtime call support. + static void* RedirectNativeFunction(void* nativeFunction, ABIFunctionType type); + + private: + enum Exception { + kNone, + kIntegerOverflow, + kIntegerUnderflow, + kDivideByZero, + kNumExceptions + }; + int16_t exceptions[kNumExceptions]; + + // Exceptions. + void signalExceptions(); + + // Handle return value for runtime FP functions. + void setCallResultDouble(double result); + void setCallResultFloat(float result); + void setCallResult(int64_t res); + void setCallResult(__int128 res); + + void callInternal(uint8_t* entry); + + // Architecture state. + // Registers. + int64_t registers_[kNumSimuRegisters]; + // Coprocessor Registers. + int64_t FPUregisters_[kNumFPURegisters]; + // FPU control register. + uint32_t FCSR_; + + // Simulator support. + char* stack_; + uintptr_t stackLimit_; + bool pc_modified_; + int64_t icount_; + int64_t break_count_; + + int64_t resume_pc_; + + // Debugger input. + char* lastDebuggerInput_; + + // Registered breakpoints. + SimInstruction* break_pc_; + Instr break_instr_; + + // Single-stepping support + bool single_stepping_; + SingleStepCallback single_step_callback_; + void* single_step_callback_arg_; + + // A stop is watched if its code is less than kNumOfWatchedStops. + // Only watched stops support enabling/disabling and the counter feature. + static const uint32_t kNumOfWatchedStops = 256; + + + // Stop is disabled if bit 31 is set. + static const uint32_t kStopDisabledBit = 1U << 31; + + // A stop is enabled, meaning the simulator will stop when meeting the + // instruction, if bit 31 of watchedStops_[code].count is unset. + // The value watchedStops_[code].count & ~(1 << 31) indicates how many times + // the breakpoint was hit or gone through. + struct StopCountAndDesc { + uint32_t count_; + char* desc_; + }; + StopCountAndDesc watchedStops_[kNumOfWatchedStops]; + + private: + // ICache checking. + struct ICacheHasher { + typedef void* Key; + typedef void* Lookup; + static HashNumber hash(const Lookup& l); + static bool match(const Key& k, const Lookup& l); + }; + + public: + typedef HashMap ICacheMap; + + private: + // This lock creates a critical section around 'redirection_' and + // 'icache_', which are referenced both by the execution engine + // and by the off-thread compiler (see Redirection::Get in the cpp file). + PRLock* cacheLock_; +#ifdef DEBUG + PRThread* cacheLockHolder_; +#endif + + Redirection* redirection_; + ICacheMap icache_; + + public: + ICacheMap& icache() { + // Technically we need the lock to access the innards of the + // icache, not to take its address, but the latter condition + // serves as a useful complement to the former. + MOZ_ASSERT(cacheLockHolder_); + return icache_; + } + + Redirection* redirection() const { + MOZ_ASSERT(cacheLockHolder_); + return redirection_; + } + + void setRedirection(js::jit::Redirection* redirection) { + MOZ_ASSERT(cacheLockHolder_); + redirection_ = redirection; + } +}; + +#define JS_CHECK_SIMULATOR_RECURSION_WITH_EXTRA(cx, extra, onerror) \ + JS_BEGIN_MACRO \ + if (cx->mainThread().simulator()->overRecursedWithExtra(extra)) { \ + js::ReportOverRecursed(cx); \ + onerror; \ + } \ + JS_END_MACRO + +} // namespace jit +} // namespace js + +#endif /* JS_SIMULATOR_MIPS64 */ + +#endif /* jit_mips64_Simulator_mips64_h */ diff --git a/js/src/jit/mips64/Trampoline-mips64.cpp b/js/src/jit/mips64/Trampoline-mips64.cpp new file mode 100644 index 0000000000..80e56e95b1 --- /dev/null +++ b/js/src/jit/mips64/Trampoline-mips64.cpp @@ -0,0 +1,1319 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 "mozilla/DebugOnly.h" + +#include "jscompartment.h" + +#include "jit/Bailouts.h" +#include "jit/JitCompartment.h" +#include "jit/JitFrames.h" +#include "jit/JitSpewer.h" +#include "jit/Linker.h" +#include "jit/mips-shared/Bailouts-mips-shared.h" +#include "jit/mips64/SharedICHelpers-mips64.h" +#ifdef JS_ION_PERF +# include "jit/PerfSpewer.h" +#endif +#include "jit/VMFunctions.h" + +#include "jit/MacroAssembler-inl.h" + +using namespace js; +using namespace js::jit; + +// All registers to save and restore. This includes the stack pointer, since we +// use the ability to reference register values on the stack by index. +static const LiveRegisterSet AllRegs = + LiveRegisterSet(GeneralRegisterSet(Registers::AllMask), + FloatRegisterSet(FloatRegisters::AllMask)); + +static_assert(sizeof(uintptr_t) == sizeof(uint64_t), "Not 32-bit clean."); + +struct EnterJITRegs +{ + double f31; + double f30; + double f29; + double f28; + double f27; + double f26; + double f25; + double f24; + + // non-volatile registers. + uint64_t ra; + uint64_t s7; + uint64_t s6; + uint64_t s5; + uint64_t s4; + uint64_t s3; + uint64_t s2; + uint64_t s1; + uint64_t s0; + // Save reg_vp(a7) on stack, use it after call jit code. + uint64_t a7; +}; + +static void +GenerateReturn(MacroAssembler& masm, int returnCode) +{ + MOZ_ASSERT(masm.framePushed() == sizeof(EnterJITRegs)); + + // Restore non-volatile registers + masm.as_ld(s0, StackPointer, offsetof(EnterJITRegs, s0)); + masm.as_ld(s1, StackPointer, offsetof(EnterJITRegs, s1)); + masm.as_ld(s2, StackPointer, offsetof(EnterJITRegs, s2)); + masm.as_ld(s3, StackPointer, offsetof(EnterJITRegs, s3)); + masm.as_ld(s4, StackPointer, offsetof(EnterJITRegs, s4)); + masm.as_ld(s5, StackPointer, offsetof(EnterJITRegs, s5)); + masm.as_ld(s6, StackPointer, offsetof(EnterJITRegs, s6)); + masm.as_ld(s7, StackPointer, offsetof(EnterJITRegs, s7)); + masm.as_ld(ra, StackPointer, offsetof(EnterJITRegs, ra)); + + // Restore non-volatile floating point registers + masm.as_ld(f24, StackPointer, offsetof(EnterJITRegs, f24)); + masm.as_ld(f25, StackPointer, offsetof(EnterJITRegs, f25)); + masm.as_ld(f26, StackPointer, offsetof(EnterJITRegs, f26)); + masm.as_ld(f27, StackPointer, offsetof(EnterJITRegs, f27)); + masm.as_ld(f28, StackPointer, offsetof(EnterJITRegs, f28)); + masm.as_ld(f29, StackPointer, offsetof(EnterJITRegs, f29)); + masm.as_ld(f30, StackPointer, offsetof(EnterJITRegs, f30)); + masm.as_ld(f31, StackPointer, offsetof(EnterJITRegs, f31)); + + masm.freeStack(sizeof(EnterJITRegs)); + + masm.branch(ra); +} + +static void +GeneratePrologue(MacroAssembler& masm) +{ + masm.reserveStack(sizeof(EnterJITRegs)); + + masm.as_sd(s0, StackPointer, offsetof(EnterJITRegs, s0)); + masm.as_sd(s1, StackPointer, offsetof(EnterJITRegs, s1)); + masm.as_sd(s2, StackPointer, offsetof(EnterJITRegs, s2)); + masm.as_sd(s3, StackPointer, offsetof(EnterJITRegs, s3)); + masm.as_sd(s4, StackPointer, offsetof(EnterJITRegs, s4)); + masm.as_sd(s5, StackPointer, offsetof(EnterJITRegs, s5)); + masm.as_sd(s6, StackPointer, offsetof(EnterJITRegs, s6)); + masm.as_sd(s7, StackPointer, offsetof(EnterJITRegs, s7)); + masm.as_sd(ra, StackPointer, offsetof(EnterJITRegs, ra)); + masm.as_sd(a7, StackPointer, offsetof(EnterJITRegs, a7)); + + masm.as_sd(f24, StackPointer, offsetof(EnterJITRegs, f24)); + masm.as_sd(f25, StackPointer, offsetof(EnterJITRegs, f25)); + masm.as_sd(f26, StackPointer, offsetof(EnterJITRegs, f26)); + masm.as_sd(f27, StackPointer, offsetof(EnterJITRegs, f27)); + masm.as_sd(f28, StackPointer, offsetof(EnterJITRegs, f28)); + masm.as_sd(f29, StackPointer, offsetof(EnterJITRegs, f29)); + masm.as_sd(f30, StackPointer, offsetof(EnterJITRegs, f30)); + masm.as_sd(f31, StackPointer, offsetof(EnterJITRegs, f31)); +} + + +// Generates a trampoline for calling Jit compiled code from a C++ function. +// The trampoline use the EnterJitCode signature, with the standard x64 fastcall +// calling convention. +JitCode * +JitRuntime::generateEnterJIT(JSContext* cx, EnterJitType type) +{ + const Register reg_code = IntArgReg0; + const Register reg_argc = IntArgReg1; + const Register reg_argv = IntArgReg2; + const mozilla::DebugOnly reg_frame = IntArgReg3; + const Register reg_token = IntArgReg4; + const Register reg_chain = IntArgReg5; + const Register reg_values = IntArgReg6; + const Register reg_vp = IntArgReg7; + MacroAssembler masm(cx); + + MOZ_ASSERT(OsrFrameReg == reg_frame); + + GeneratePrologue(masm); + + // Save stack pointer into s4 + masm.movePtr(StackPointer, s4); + + // Save stack pointer as baseline frame. + if (type == EnterJitBaseline) + masm.movePtr(StackPointer, BaselineFrameReg); + + // Load the number of actual arguments into s3. + masm.unboxInt32(Address(reg_vp, 0), s3); + + /*************************************************************** + Loop over argv vector, push arguments onto stack in reverse order + ***************************************************************/ + + // if we are constructing, that also needs to include newTarget + { + Label noNewTarget; + masm.branchTest32(Assembler::Zero, reg_token, Imm32(CalleeToken_FunctionConstructing), + &noNewTarget); + + masm.add32(Imm32(1), reg_argc); + + masm.bind(&noNewTarget); + } + + // Make stack algined + masm.ma_and(s0, reg_argc, Imm32(1)); + masm.ma_dsubu(s1, StackPointer, Imm32(sizeof(Value))); + masm.as_movn(StackPointer, s1, s0); + + masm.as_dsll(s0, reg_argc, 3); // Value* argv + masm.addPtr(reg_argv, s0); // s0 = &argv[argc] + + // Loop over arguments, copying them from an unknown buffer onto the Ion + // stack so they can be accessed from JIT'ed code. + Label header, footer; + // If there aren't any arguments, don't do anything + masm.ma_b(s0, reg_argv, &footer, Assembler::BelowOrEqual, ShortJump); + { + masm.bind(&header); + + masm.subPtr(Imm32(sizeof(Value)), s0); + masm.subPtr(Imm32(sizeof(Value)), StackPointer); + + ValueOperand value = ValueOperand(s6); + masm.loadValue(Address(s0, 0), value); + masm.storeValue(value, Address(StackPointer, 0)); + + masm.ma_b(s0, reg_argv, &header, Assembler::Above, ShortJump); + } + masm.bind(&footer); + + masm.subPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer); + masm.storePtr(s3, Address(StackPointer, sizeof(uintptr_t))); // actual arguments + masm.storePtr(reg_token, Address(StackPointer, 0)); // callee token + + masm.subPtr(StackPointer, s4); + masm.makeFrameDescriptor(s4, JitFrame_Entry); + masm.push(s4); // descriptor + + CodeLabel returnLabel; + if (type == EnterJitBaseline) { + // Handle OSR. + AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); + regs.take(OsrFrameReg); + regs.take(BaselineFrameReg); + regs.take(reg_code); + regs.take(ReturnReg); + regs.take(JSReturnOperand); + + Label notOsr; + masm.ma_b(OsrFrameReg, OsrFrameReg, ¬Osr, Assembler::Zero, ShortJump); + + Register numStackValues = reg_values; + regs.take(numStackValues); + Register scratch = regs.takeAny(); + + // Push return address. + masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer); + masm.ma_li(scratch, returnLabel.dest()); + masm.storePtr(scratch, Address(StackPointer, 0)); + + // Push previous frame pointer. + masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer); + masm.storePtr(BaselineFrameReg, Address(StackPointer, 0)); + + // Reserve frame. + Register framePtr = BaselineFrameReg; + masm.subPtr(Imm32(BaselineFrame::Size()), StackPointer); + masm.movePtr(StackPointer, framePtr); + + // Reserve space for locals and stack values. + masm.ma_dsll(scratch, numStackValues, Imm32(3)); + masm.subPtr(scratch, StackPointer); + + // Enter exit frame. + masm.addPtr(Imm32(BaselineFrame::Size() + BaselineFrame::FramePointerOffset), scratch); + masm.makeFrameDescriptor(scratch, JitFrame_BaselineJS); + + // Push frame descriptor and fake return address. + masm.reserveStack(2 * sizeof(uintptr_t)); + masm.storePtr(scratch, Address(StackPointer, sizeof(uintptr_t))); // Frame descriptor + masm.storePtr(zero, Address(StackPointer, 0)); // fake return address + + // No GC things to mark, push a bare token. + masm.enterFakeExitFrame(ExitFrameLayoutBareToken); + + masm.reserveStack(2 * sizeof(uintptr_t)); + masm.storePtr(framePtr, Address(StackPointer, sizeof(uintptr_t))); // BaselineFrame + masm.storePtr(reg_code, Address(StackPointer, 0)); // jitcode + + masm.setupUnalignedABICall(scratch); + masm.passABIArg(BaselineFrameReg); // BaselineFrame + masm.passABIArg(OsrFrameReg); // InterpreterFrame + masm.passABIArg(numStackValues); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, jit::InitBaselineFrameForOsr)); + + regs.add(OsrFrameReg); + Register jitcode = regs.takeAny(); + masm.loadPtr(Address(StackPointer, 0), jitcode); + masm.loadPtr(Address(StackPointer, sizeof(uintptr_t)), framePtr); + masm.freeStack(2 * sizeof(uintptr_t)); + + Label error; + masm.freeStack(ExitFrameLayout::SizeWithFooter()); + masm.addPtr(Imm32(BaselineFrame::Size()), framePtr); + masm.branchIfFalseBool(ReturnReg, &error); + + // If OSR-ing, then emit instrumentation for setting lastProfilerFrame + // if profiler instrumentation is enabled. + { + Label skipProfilingInstrumentation; + Register realFramePtr = numStackValues; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), + &skipProfilingInstrumentation); + masm.ma_daddu(realFramePtr, framePtr, Imm32(sizeof(void*))); + masm.profilerEnterFrame(realFramePtr, scratch); + masm.bind(&skipProfilingInstrumentation); + } + + masm.jump(jitcode); + + // OOM: load error value, discard return address and previous frame + // pointer and return. + masm.bind(&error); + masm.movePtr(framePtr, StackPointer); + masm.addPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer); + masm.moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand); + masm.ma_li(scratch, returnLabel.dest()); + masm.jump(scratch); + + masm.bind(¬Osr); + // Load the scope chain in R1. + MOZ_ASSERT(R1.scratchReg() != reg_code); + masm.ma_move(R1.scratchReg(), reg_chain); + } + + // The call will push the return address on the stack, thus we check that + // the stack would be aligned once the call is complete. + masm.assertStackAlignment(JitStackAlignment, sizeof(uintptr_t)); + + // Call the function with pushing return address to stack. + masm.callJitNoProfiler(reg_code); + + if (type == EnterJitBaseline) { + // Baseline OSR will return here. + masm.bind(returnLabel.src()); + masm.addCodeLabel(returnLabel); + } + + // Pop arguments off the stack. + // s0 <- 8*argc (size of all arguments we pushed on the stack) + masm.pop(s0); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), s0); + masm.addPtr(s0, StackPointer); + + // Store the returned value into the vp + masm.as_ld(reg_vp, StackPointer, offsetof(EnterJITRegs, a7)); + masm.storeValue(JSReturnOperand, Address(reg_vp, 0)); + + // Restore non-volatile registers and return. + GenerateReturn(masm, ShortJump); + + Linker linker(masm); + AutoFlushICache afc("GenerateEnterJIT"); + JitCode* code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "EnterJIT"); +#endif + + return code; +} + +JitCode* +JitRuntime::generateInvalidator(JSContext* cx) +{ + MacroAssembler masm(cx); + + // Stack has to be alligned here. If not, we will have to fix it. + masm.checkStackAlignment(); + + // Push registers such that we can access them from [base + code]. + masm.PushRegsInMask(AllRegs); + + // Pass pointer to InvalidationBailoutStack structure. + masm.movePtr(StackPointer, a0); + + // Reserve place for return value and BailoutInfo pointer + masm.subPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer); + // Pass pointer to return value. + masm.ma_daddu(a1, StackPointer, Imm32(sizeof(uintptr_t))); + // Pass pointer to BailoutInfo + masm.movePtr(StackPointer, a2); + + masm.setupAlignedABICall(); + masm.passABIArg(a0); + masm.passABIArg(a1); + masm.passABIArg(a2); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, InvalidationBailout)); + + masm.loadPtr(Address(StackPointer, 0), a2); + masm.loadPtr(Address(StackPointer, sizeof(uintptr_t)), a1); + // Remove the return address, the IonScript, the register state + // (InvaliationBailoutStack) and the space that was allocated for the + // return value. + masm.addPtr(Imm32(sizeof(InvalidationBailoutStack) + 2 * sizeof(uintptr_t)), StackPointer); + // remove the space that this frame was using before the bailout + // (computed by InvalidationBailout) + masm.addPtr(a1, StackPointer); + + // Jump to shared bailout tail. The BailoutInfo pointer has to be in r2. + JitCode* bailoutTail = cx->runtime()->jitRuntime()->getBailoutTail(); + masm.branch(bailoutTail); + + Linker linker(masm); + AutoFlushICache afc("Invalidator"); + JitCode* code = linker.newCode(cx, OTHER_CODE); + JitSpew(JitSpew_IonInvalidate, " invalidation thunk created at %p", (void*) code->raw()); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "Invalidator"); +#endif + + return code; +} + +JitCode* +JitRuntime::generateArgumentsRectifier(JSContext* cx, void** returnAddrOut) +{ + // Do not erase the frame pointer in this function. + + MacroAssembler masm(cx); + // Caller: + // [arg2] [arg1] [this] [[argc] [callee] [descr] [raddr]] <- sp + // '--- s3 ---' + + // ArgumentsRectifierReg contains the |nargs| pushed onto the current + // frame. Including |this|, there are (|nargs| + 1) arguments to copy. + MOZ_ASSERT(ArgumentsRectifierReg == s3); + + // Add |this|, in the counter of known arguments. + masm.addPtr(Imm32(1), ArgumentsRectifierReg); + + Register numActArgsReg = a6; + Register calleeTokenReg = a7; + Register numArgsReg = a5; + + // Load |nformals| into numArgsReg. + masm.loadPtr(Address(StackPointer, RectifierFrameLayout::offsetOfCalleeToken()), + calleeTokenReg); + masm.mov(calleeTokenReg, numArgsReg); + masm.andPtr(Imm32(uint32_t(CalleeTokenMask)), numArgsReg); + masm.load16ZeroExtend(Address(numArgsReg, JSFunction::offsetOfNargs()), numArgsReg); + + // Stash another copy in t3, since we are going to do destructive operations + // on numArgsReg + masm.mov(numArgsReg, t3); + + static_assert(CalleeToken_FunctionConstructing == 1, + "Ensure that we can use the constructing bit to count the value"); + masm.mov(calleeTokenReg, t2); + masm.ma_and(t2, Imm32(uint32_t(CalleeToken_FunctionConstructing))); + + // Including |this|, and |new.target|, there are (|nformals| + 1 + isConstructing) + // arguments to push to the stack. Then we push a JitFrameLayout. We + // compute the padding expressed in the number of extra |undefined| values + // to push on the stack. + static_assert(sizeof(JitFrameLayout) % JitStackAlignment == 0, + "No need to consider the JitFrameLayout for aligning the stack"); + static_assert(JitStackAlignment % sizeof(Value) == 0, + "Ensure that we can pad the stack by pushing extra UndefinedValue"); + + MOZ_ASSERT(IsPowerOfTwo(JitStackValueAlignment)); + masm.add32(Imm32(JitStackValueAlignment - 1 /* for padding */ + 1 /* for |this| */), numArgsReg); + masm.add32(t2, numArgsReg); + masm.and32(Imm32(~(JitStackValueAlignment - 1)), numArgsReg); + + // Load the number of |undefined|s to push into t1. + masm.as_dsubu(t1, numArgsReg, s3); + + // Caller: + // [arg2] [arg1] [this] [[argc] [callee] [descr] [raddr]] <- sp <- t2 + // '------ s3 -------' + // + // Rectifier frame: + // [undef] [undef] [undef] [arg2] [arg1] [this] [[argc] [callee] [descr] [raddr]] + // '-------- t1 ---------' '------- s3 -------' + + // Copy number of actual arguments into numActArgsReg + masm.loadPtr(Address(StackPointer, RectifierFrameLayout::offsetOfNumActualArgs()), + numActArgsReg); + + + masm.moveValue(UndefinedValue(), ValueOperand(t0)); + + masm.movePtr(StackPointer, t2); // Save %sp. + + // Push undefined. (including the padding) + { + Label undefLoopTop; + + masm.bind(&undefLoopTop); + masm.sub32(Imm32(1), t1); + masm.subPtr(Imm32(sizeof(Value)), StackPointer); + masm.storeValue(ValueOperand(t0), Address(StackPointer, 0)); + + masm.ma_b(t1, t1, &undefLoopTop, Assembler::NonZero, ShortJump); + } + + // Get the topmost argument. + static_assert(sizeof(Value) == 8, "TimesEight is used to skip arguments"); + + // | - sizeof(Value)| is used to put rcx such that we can read the last + // argument, and not the value which is after. + masm.ma_dsll(t0, s3, Imm32(3)); // t0 <- nargs * 8 + masm.as_daddu(t1, t2, t0); // t1 <- t2(saved sp) + nargs * 8 + masm.addPtr(Imm32(sizeof(RectifierFrameLayout) - sizeof(Value)), t1); + + // Copy & Push arguments, |nargs| + 1 times (to include |this|). + { + Label copyLoopTop; + + masm.bind(©LoopTop); + masm.sub32(Imm32(1), s3); + masm.subPtr(Imm32(sizeof(Value)), StackPointer); + masm.loadValue(Address(t1, 0), ValueOperand(t0)); + masm.storeValue(ValueOperand(t0), Address(StackPointer, 0)); + masm.subPtr(Imm32(sizeof(Value)), t1); + + masm.ma_b(s3, s3, ©LoopTop, Assembler::NonZero, ShortJump); + } + + // if constructing, copy newTarget + { + Label notConstructing; + + masm.branchTest32(Assembler::Zero, calleeTokenReg, Imm32(CalleeToken_FunctionConstructing), + ¬Constructing); + + // thisFrame[numFormals] = prevFrame[argc] + ValueOperand newTarget(t0); + + // +1 for |this|. We want vp[argc], so don't subtract 1 + BaseIndex newTargetSrc(t2, numActArgsReg, TimesEight, sizeof(RectifierFrameLayout) + sizeof(Value)); + masm.loadValue(newTargetSrc, newTarget); + + // Again, 1 for |this| + BaseIndex newTargetDest(StackPointer, t3, TimesEight, sizeof(Value)); + masm.storeValue(newTarget, newTargetDest); + + masm.bind(¬Constructing); + } + + // Caller: + // [arg2] [arg1] [this] [[argc] [callee] [descr] [raddr]] <- t2 + // + // + // Rectifier frame: + // [undef] [undef] [undef] [arg2] [arg1] [this] <- sp [[argc] [callee] [descr] [raddr]] + + // Construct sizeDescriptor. + masm.subPtr(StackPointer, t2); + masm.makeFrameDescriptor(t2, JitFrame_Rectifier); + + // Construct JitFrameLayout. + masm.subPtr(Imm32(3 * sizeof(uintptr_t)), StackPointer); + // Push actual arguments. + masm.storePtr(numActArgsReg, Address(StackPointer, 2 * sizeof(uintptr_t))); + // Push callee token. + masm.storePtr(calleeTokenReg, Address(StackPointer, sizeof(uintptr_t))); + // Push frame descriptor. + masm.storePtr(t2, Address(StackPointer, 0)); + + // Call the target function. + // Note that this code assumes the function is JITted. + masm.andPtr(Imm32(uint32_t(CalleeTokenMask)), calleeTokenReg); + masm.loadPtr(Address(calleeTokenReg, JSFunction::offsetOfNativeOrScript()), t1); + masm.loadBaselineOrIonRaw(t1, t1, nullptr); + uint32_t returnOffset = masm.callJitNoProfiler(t1); + + // Remove the rectifier frame. + // t2 <- descriptor with FrameType. + masm.loadPtr(Address(StackPointer, 0), t2); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), t2); // t2 <- descriptor. + + // Discard descriptor, calleeToken and number of actual arguments. + masm.addPtr(Imm32(3 * sizeof(uintptr_t)), StackPointer); + + // Discard pushed arguments. + masm.addPtr(t2, StackPointer); + + masm.ret(); + Linker linker(masm); + AutoFlushICache afc("ArgumentsRectifier"); + JitCode* code = linker.newCode(cx, OTHER_CODE); + + CodeOffsetLabel returnLabel(returnOffset); + returnLabel.fixup(&masm); + if (returnAddrOut) + *returnAddrOut = (void*) (code->raw() + returnLabel.offset()); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "ArgumentsRectifier"); +#endif + + return code; +} + +/* - When bailout is done via out of line code (lazy bailout). + * Frame size is stored in $ra (look at + * CodeGeneratorMIPS64::generateOutOfLineCode()) and thunk code should save it + * on stack. Other difference is that members snapshotOffset_ and padding_ are + * pushed to the stack by CodeGeneratorMIPS64::visitOutOfLineBailout(). Field + * frameClassId_ is forced to be NO_FRAME_SIZE_CLASS_ID + * (See: JitRuntime::generateBailoutHandler). + */ +static void +PushBailoutFrame(MacroAssembler& masm, uint32_t frameClass, Register spArg) +{ + // Make sure that alignment is proper. + masm.checkStackAlignment(); + + // Push registers such that we can access them from [base + code]. + masm.PushRegsInMask(AllRegs); + + // Push the frameSize_ or tableOffset_ stored in ra + // See: CodeGeneratorMIPS64::generateOutOfLineCode() + masm.push(ra); + + // Push frame class to stack + masm.push(ImmWord(frameClass)); + + // Put pointer to BailoutStack as first argument to the Bailout() + masm.movePtr(StackPointer, spArg); +} + +static void +GenerateBailoutThunk(JSContext* cx, MacroAssembler& masm, uint32_t frameClass) +{ + PushBailoutFrame(masm, frameClass, a0); + + // Put pointer to BailoutInfo + static const uint32_t sizeOfBailoutInfo = sizeof(uintptr_t) * 2; + masm.subPtr(Imm32(sizeOfBailoutInfo), StackPointer); + masm.storePtr(ImmPtr(nullptr), Address(StackPointer, 0)); + masm.movePtr(StackPointer, a1); + + masm.setupAlignedABICall(); + masm.passABIArg(a0); + masm.passABIArg(a1); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, Bailout)); + + // Get BailoutInfo pointer + masm.loadPtr(Address(StackPointer, 0), a2); + + // Remove both the bailout frame and the topmost Ion frame's stack. + // Load frameSize from stack + masm.loadPtr(Address(StackPointer, + sizeOfBailoutInfo + BailoutStack::offsetOfFrameSize()), a1); + // Remove complete BailoutStack class and data after it + masm.addPtr(Imm32(sizeof(BailoutStack) + sizeOfBailoutInfo), StackPointer); + // Remove frame size srom stack + masm.addPtr(a1, StackPointer); + + // Jump to shared bailout tail. The BailoutInfo pointer has to be in a2. + JitCode* bailoutTail = cx->runtime()->jitRuntime()->getBailoutTail(); + masm.branch(bailoutTail); +} + +JitCode* +JitRuntime::generateBailoutTable(JSContext* cx, uint32_t frameClass) +{ + MOZ_CRASH("MIPS64 does not use bailout tables"); +} + +JitCode* +JitRuntime::generateBailoutHandler(JSContext* cx) +{ + MacroAssembler masm(cx); + GenerateBailoutThunk(cx, masm, NO_FRAME_SIZE_CLASS_ID); + + Linker linker(masm); + AutoFlushICache afc("BailoutHandler"); + JitCode* code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "BailoutHandler"); +#endif + + return code; +} + +JitCode* +JitRuntime::generateVMWrapper(JSContext* cx, const VMFunction& f) +{ + MOZ_ASSERT(functionWrappers_); + MOZ_ASSERT(functionWrappers_->initialized()); + VMWrapperMap::AddPtr p = functionWrappers_->lookupForAdd(&f); + if (p) + return p->value(); + + MacroAssembler masm(cx); + + AllocatableGeneralRegisterSet regs(Register::Codes::WrapperMask); + + static_assert((Register::Codes::VolatileMask & ~Register::Codes::WrapperMask) == 0, + "Wrapper register set should be a superset of Volatile register set."); + + // The context is the first argument; a0 is the first argument register. + Register cxreg = a0; + regs.take(cxreg); + + // We're aligned to an exit frame, so link it up. + masm.enterExitFrame(&f); + masm.loadJSContext(cxreg); + + // Save the base of the argument set stored on the stack. + Register argsBase = InvalidReg; + if (f.explicitArgs) { + argsBase = t1; // Use temporary register. + regs.take(argsBase); + masm.ma_daddu(argsBase, StackPointer, Imm32(ExitFrameLayout::SizeWithFooter())); + } + + // Reserve space for the outparameter. + Register outReg = InvalidReg; + switch (f.outParam) { + case Type_Value: + outReg = regs.takeAny(); + masm.reserveStack(sizeof(Value)); + masm.movePtr(StackPointer, outReg); + break; + + case Type_Handle: + outReg = regs.takeAny(); + masm.PushEmptyRooted(f.outParamRootType); + masm.movePtr(StackPointer, outReg); + break; + + case Type_Bool: + case Type_Int32: + outReg = regs.takeAny(); + // Reserve 4-byte space to make stack aligned to 8-byte. + masm.reserveStack(2 * sizeof(int32_t)); + masm.movePtr(StackPointer, outReg); + break; + + case Type_Pointer: + outReg = regs.takeAny(); + masm.reserveStack(sizeof(uintptr_t)); + masm.movePtr(StackPointer, outReg); + break; + + case Type_Double: + outReg = regs.takeAny(); + masm.reserveStack(sizeof(double)); + masm.movePtr(StackPointer, outReg); + break; + + default: + MOZ_ASSERT(f.outParam == Type_Void); + break; + } + + masm.setupUnalignedABICall(regs.getAny()); + masm.passABIArg(cxreg); + + size_t argDisp = 0; + + // Copy any arguments. + for (uint32_t explicitArg = 0; explicitArg < f.explicitArgs; explicitArg++) { + MoveOperand from; + switch (f.argProperties(explicitArg)) { + case VMFunction::WordByValue: + if (f.argPassedInFloatReg(explicitArg)) + masm.passABIArg(MoveOperand(argsBase, argDisp), MoveOp::DOUBLE); + else + masm.passABIArg(MoveOperand(argsBase, argDisp), MoveOp::GENERAL); + argDisp += sizeof(void*); + break; + case VMFunction::WordByRef: + masm.passABIArg(MoveOperand(argsBase, argDisp, MoveOperand::EFFECTIVE_ADDRESS), + MoveOp::GENERAL); + argDisp += sizeof(void*); + break; + case VMFunction::DoubleByValue: + case VMFunction::DoubleByRef: + MOZ_CRASH("NYI: MIPS64 callVM should not be used with 128bits values."); + break; + } + } + + // Copy the implicit outparam, if any. + if (InvalidReg != outReg) + masm.passABIArg(outReg); + + masm.callWithABI(f.wrapped); + + // Test for failure. + switch (f.failType()) { + case Type_Object: + masm.branchTestPtr(Assembler::Zero, v0, v0, masm.failureLabel()); + break; + case Type_Bool: + // Called functions return bools, which are 0/false and non-zero/true + masm.branchIfFalseBool(v0, masm.failureLabel()); + break; + default: + MOZ_CRASH("unknown failure kind"); + } + + // Load the outparam and free any allocated stack. + switch (f.outParam) { + case Type_Handle: + masm.popRooted(f.outParamRootType, ReturnReg, JSReturnOperand); + break; + + case Type_Value: + masm.loadValue(Address(StackPointer, 0), JSReturnOperand); + masm.freeStack(sizeof(Value)); + break; + + case Type_Int32: + masm.load32(Address(StackPointer, 0), ReturnReg); + masm.freeStack(2 * sizeof(int32_t)); + break; + + case Type_Pointer: + masm.loadPtr(Address(StackPointer, 0), ReturnReg); + masm.freeStack(sizeof(uintptr_t)); + break; + + case Type_Bool: + masm.load8ZeroExtend(Address(StackPointer, 0), ReturnReg); + masm.freeStack(2 * sizeof(int32_t)); + break; + + case Type_Double: + if (cx->runtime()->jitSupportsFloatingPoint) { + masm.as_ld(ReturnDoubleReg, StackPointer, 0); + } else { + masm.assumeUnreachable("Unable to load into float reg, with no FP support."); + } + masm.freeStack(sizeof(double)); + break; + + default: + MOZ_ASSERT(f.outParam == Type_Void); + break; + } + + masm.leaveExitFrame(); + masm.retn(Imm32(sizeof(ExitFrameLayout) + + f.explicitStackSlots() * sizeof(void*) + + f.extraValuesToPop * sizeof(Value))); + + Linker linker(masm); + AutoFlushICache afc("VMWrapper"); + JitCode* wrapper = linker.newCode(cx, OTHER_CODE); + if (!wrapper) + return nullptr; + + // linker.newCode may trigger a GC and sweep functionWrappers_ so we have + // to use relookupOrAdd instead of add. + if (!functionWrappers_->relookupOrAdd(p, &f, wrapper)) + return nullptr; + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(wrapper, "VMWrapper"); +#endif + + return wrapper; +} + +JitCode* +JitRuntime::generatePreBarrier(JSContext* cx, MIRType type) +{ + MacroAssembler masm(cx); + + LiveRegisterSet save; + if (cx->runtime()->jitSupportsFloatingPoint) { + save.set() = RegisterSet(GeneralRegisterSet(Registers::VolatileMask), + FloatRegisterSet(FloatRegisters::VolatileMask)); + } else { + save.set() = RegisterSet(GeneralRegisterSet(Registers::VolatileMask), + FloatRegisterSet()); + } + masm.PushRegsInMask(save); + + MOZ_ASSERT(PreBarrierReg == a1); + masm.movePtr(ImmPtr(cx->runtime()), a0); + + masm.setupUnalignedABICall(a2); + masm.passABIArg(a0); + masm.passABIArg(a1); + masm.callWithABI(IonMarkFunction(type)); + + masm.PopRegsInMask(save); + masm.ret(); + + Linker linker(masm); + AutoFlushICache afc("PreBarrier"); + JitCode* code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "PreBarrier"); +#endif + + return code; +} + +typedef bool (*HandleDebugTrapFn)(JSContext*, BaselineFrame*, uint8_t*, bool*); +static const VMFunction HandleDebugTrapInfo = FunctionInfo(HandleDebugTrap); + +JitCode* +JitRuntime::generateDebugTrapHandler(JSContext* cx) +{ + MacroAssembler masm(cx); + + Register scratch1 = t0; + Register scratch2 = t1; + + // Load BaselineFrame pointer in scratch1. + masm.movePtr(s5, scratch1); + masm.subPtr(Imm32(BaselineFrame::Size()), scratch1); + + // Enter a stub frame and call the HandleDebugTrap VM function. Ensure + // the stub frame has a nullptr ICStub pointer, since this pointer is + // marked during GC. + masm.movePtr(ImmPtr(nullptr), ICStubReg); + EmitBaselineEnterStubFrame(masm, scratch2); + + JitCode* code = cx->runtime()->jitRuntime()->getVMWrapper(HandleDebugTrapInfo); + if (!code) + return nullptr; + + masm.subPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer); + masm.storePtr(ra, Address(StackPointer, sizeof(uintptr_t))); + masm.storePtr(scratch1, Address(StackPointer, 0)); + + EmitBaselineCallVM(code, masm); + + EmitBaselineLeaveStubFrame(masm); + + // If the stub returns |true|, we have to perform a forced return + // (return from the JS frame). If the stub returns |false|, just return + // from the trap stub so that execution continues at the current pc. + Label forcedReturn; + masm.branchTest32(Assembler::NonZero, ReturnReg, ReturnReg, &forcedReturn); + + // ra was restored by EmitLeaveStubFrame + masm.branch(ra); + + masm.bind(&forcedReturn); + masm.loadValue(Address(s5, BaselineFrame::reverseOffsetOfReturnValue()), + JSReturnOperand); + masm.movePtr(s5, StackPointer); + masm.pop(s5); + + // Before returning, if profiling is turned on, make sure that lastProfilingFrame + // is set to the correct caller frame. + { + Label skipProfilingInstrumentation; + AbsoluteAddress addressOfEnabled(cx->runtime()->spsProfiler.addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), &skipProfilingInstrumentation); + masm.profilerExitFrame(); + masm.bind(&skipProfilingInstrumentation); + } + + masm.ret(); + + Linker linker(masm); + AutoFlushICache afc("DebugTrapHandler"); + JitCode* codeDbg = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(codeDbg, "DebugTrapHandler"); +#endif + + return codeDbg; +} + + +JitCode* +JitRuntime::generateExceptionTailStub(JSContext* cx, void* handler) +{ + MacroAssembler masm; + + masm.handleFailureWithHandlerTail(handler); + + Linker linker(masm); + AutoFlushICache afc("ExceptionTailStub"); + JitCode* code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "ExceptionTailStub"); +#endif + + return code; +} + +JitCode* +JitRuntime::generateBailoutTailStub(JSContext* cx) +{ + MacroAssembler masm; + + masm.generateBailoutTail(a1, a2); + + Linker linker(masm); + AutoFlushICache afc("BailoutTailStub"); + JitCode* code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "BailoutTailStub"); +#endif + + return code; +} + +JitCode* +JitRuntime::generateProfilerExitFrameTailStub(JSContext* cx) +{ + MacroAssembler masm; + + Register scratch1 = t0; + Register scratch2 = t1; + Register scratch3 = t2; + Register scratch4 = t3; + + // + // The code generated below expects that the current stack pointer points + // to an Ion or Baseline frame, at the state it would be immediately + // before a ret(). Thus, after this stub's business is done, it executes + // a ret() and returns directly to the caller script, on behalf of the + // callee script that jumped to this code. + // + // Thus the expected stack is: + // + // StackPointer ----+ + // v + // ..., ActualArgc, CalleeToken, Descriptor, ReturnAddr + // MEM-HI MEM-LOW + // + // + // The generated jitcode is responsible for overwriting the + // jitActivation->lastProfilingFrame field with a pointer to the previous + // Ion or Baseline jit-frame that was pushed before this one. It is also + // responsible for overwriting jitActivation->lastProfilingCallSite with + // the return address into that frame. The frame could either be an + // immediate "caller" frame, or it could be a frame in a previous + // JitActivation (if the current frame was entered from C++, and the C++ + // was entered by some caller jit-frame further down the stack). + // + // So this jitcode is responsible for "walking up" the jit stack, finding + // the previous Ion or Baseline JS frame, and storing its address and the + // return address into the appropriate fields on the current jitActivation. + // + // There are a fixed number of different path types that can lead to the + // current frame, which is either a baseline or ion frame: + // + // + // ^ + // | + // ^--- Ion + // | + // ^--- Baseline Stub <---- Baseline + // | + // ^--- Argument Rectifier + // | ^ + // | | + // | ^--- Ion + // | | + // | ^--- Baseline Stub <---- Baseline + // | + // ^--- Entry Frame (From C++) + // + Register actReg = scratch4; + AbsoluteAddress activationAddr(GetJitContext()->runtime->addressOfProfilingActivation()); + masm.loadPtr(activationAddr, actReg); + + Address lastProfilingFrame(actReg, JitActivation::offsetOfLastProfilingFrame()); + Address lastProfilingCallSite(actReg, JitActivation::offsetOfLastProfilingCallSite()); + +#ifdef DEBUG + // Ensure that frame we are exiting is current lastProfilingFrame + { + masm.loadPtr(lastProfilingFrame, scratch1); + Label checkOk; + masm.branchPtr(Assembler::Equal, scratch1, ImmWord(0), &checkOk); + masm.branchPtr(Assembler::Equal, StackPointer, scratch1, &checkOk); + masm.assumeUnreachable( + "Mismatch between stored lastProfilingFrame and current stack pointer."); + masm.bind(&checkOk); + } +#endif + + // Load the frame descriptor into |scratch1|, figure out what to do depending on its type. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfDescriptor()), scratch1); + + // Going into the conditionals, we will have: + // FrameDescriptor.size in scratch1 + // FrameDescriptor.type in scratch2 + masm.ma_and(scratch2, scratch1, Imm32((1 << FRAMETYPE_BITS) - 1)); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch1); + + // Handling of each case is dependent on FrameDescriptor.type + Label handle_IonJS; + Label handle_BaselineStub; + Label handle_Rectifier; + Label handle_IonAccessorIC; + Label handle_Entry; + Label end; + + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineJS), &handle_IonJS); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_BaselineStub), &handle_BaselineStub); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Rectifier), &handle_Rectifier); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_IonAccessorIC), &handle_IonAccessorIC); + masm.branch32(Assembler::Equal, scratch2, Imm32(JitFrame_Entry), &handle_Entry); + + masm.assumeUnreachable("Invalid caller frame type when exiting from Ion frame."); + + // + // JitFrame_IonJS + // + // Stack layout: + // ... + // Ion-Descriptor + // Prev-FP ---> Ion-ReturnAddr + // ... previous frame data ... |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_IonJS); + { + // |scratch1| contains Descriptor.size + + // returning directly to an IonJS frame. Store return addr to frame + // in lastProfilingCallSite. + masm.loadPtr(Address(StackPointer, JitFrameLayout::offsetOfReturnAddress()), scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + // Store return frame in lastProfilingFrame. + // scratch2 := StackPointer + Descriptor.size*1 + JitFrameLayout::Size(); + masm.as_daddu(scratch2, StackPointer, scratch1); + masm.ma_daddu(scratch2, scratch2, Imm32(JitFrameLayout::Size())); + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_BaselineStub + // + // Look past the stub and store the frame pointer to + // the baselineJS frame prior to it. + // + // Stack layout: + // ... + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-PrevFramePointer + // | ... BL-FrameData ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Descriptor.Size + // ... arguments ... | + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + // We take advantage of the fact that the stub frame saves the frame + // pointer pointing to the baseline frame, so a bunch of calculation can + // be avoided. + // + masm.bind(&handle_BaselineStub); + { + masm.as_daddu(scratch3, StackPointer, scratch1); + Address stubFrameReturnAddr(scratch3, + JitFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + Address stubFrameSavedFramePtr(scratch3, + JitFrameLayout::Size() - (2 * sizeof(void*))); + masm.loadPtr(stubFrameSavedFramePtr, scratch2); + masm.addPtr(Imm32(sizeof(void*)), scratch2); // Skip past BL-PrevFramePtr + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + + // + // JitFrame_Rectifier + // + // The rectifier frame can be preceded by either an IonJS or a + // BaselineStub frame. + // + // Stack layout if caller of rectifier was Ion: + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- Rect-Descriptor.Size + // < COMMON LAYOUT > + // + // Stack layout if caller of rectifier was Baseline: + // + // BL-Descriptor + // Prev-FP ---> BL-ReturnAddr + // +-----> BL-SavedFramePointer + // | ... baseline frame data ... + // | BLStub-Descriptor + // | BLStub-ReturnAddr + // | BLStub-StubPointer | + // +------ BLStub-SavedFramePointer |- Rect-Descriptor.Size + // ... args to rectifier ... | + // < COMMON LAYOUT > + // + // Common stack layout: + // + // ActualArgc | + // CalleeToken |- IonRectitiferFrameLayout::Size() + // Rect-Descriptor | + // Rect-ReturnAddr | + // ... rectifier data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + // + masm.bind(&handle_Rectifier); + { + // scratch2 := StackPointer + Descriptor.size*1 + JitFrameLayout::Size(); + masm.as_daddu(scratch2, StackPointer, scratch1); + masm.add32(Imm32(JitFrameLayout::Size()), scratch2); + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfDescriptor()), scratch3); + masm.ma_dsrl(scratch1, scratch3, Imm32(FRAMESIZE_SHIFT)); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch3); + + // Now |scratch1| contains Rect-Descriptor.Size + // and |scratch2| points to Rectifier frame + // and |scratch3| contains Rect-Descriptor.Type + + // Check for either Ion or BaselineStub frame. + Label handle_Rectifier_BaselineStub; + masm.branch32(Assembler::NotEqual, scratch3, Imm32(JitFrame_IonJS), + &handle_Rectifier_BaselineStub); + + // Handle Rectifier <- IonJS + // scratch3 := RectFrame[ReturnAddr] + masm.loadPtr(Address(scratch2, RectifierFrameLayout::offsetOfReturnAddress()), scratch3); + masm.storePtr(scratch3, lastProfilingCallSite); + + // scratch3 := RectFrame + Rect-Descriptor.Size + RectifierFrameLayout::Size() + masm.as_daddu(scratch3, scratch2, scratch1); + masm.add32(Imm32(RectifierFrameLayout::Size()), scratch3); + masm.storePtr(scratch3, lastProfilingFrame); + masm.ret(); + + // Handle Rectifier <- BaselineStub <- BaselineJS + masm.bind(&handle_Rectifier_BaselineStub); +#ifdef DEBUG + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch3, Imm32(JitFrame_BaselineStub), &checkOk); + masm.assumeUnreachable("Unrecognized frame preceding baselineStub."); + masm.bind(&checkOk); + } +#endif + masm.as_daddu(scratch3, scratch2, scratch1); + Address stubFrameReturnAddr(scratch3, RectifierFrameLayout::Size() + + BaselineStubFrameLayout::offsetOfReturnAddress()); + masm.loadPtr(stubFrameReturnAddr, scratch2); + masm.storePtr(scratch2, lastProfilingCallSite); + + Address stubFrameSavedFramePtr(scratch3, + RectifierFrameLayout::Size() - (2 * sizeof(void*))); + masm.loadPtr(stubFrameSavedFramePtr, scratch2); + masm.addPtr(Imm32(sizeof(void*)), scratch2); + masm.storePtr(scratch2, lastProfilingFrame); + masm.ret(); + } + + // JitFrame_IonAccessorIC + // + // The caller is always an IonJS frame. + // + // Ion-Descriptor + // Ion-ReturnAddr + // ... ion frame data ... |- AccFrame-Descriptor.Size + // StubCode | + // AccFrame-Descriptor |- IonAccessorICFrameLayout::Size() + // AccFrame-ReturnAddr | + // ... accessor frame data & args ... |- Descriptor.Size + // ActualArgc | + // CalleeToken |- JitFrameLayout::Size() + // Descriptor | + // FP -----> ReturnAddr | + masm.bind(&handle_IonAccessorIC); + { + // scratch2 := StackPointer + Descriptor.size + JitFrameLayout::Size() + masm.as_daddu(scratch2, StackPointer, scratch1); + masm.addPtr(Imm32(JitFrameLayout::Size()), scratch2); + + // scratch3 := AccFrame-Descriptor.Size + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfDescriptor()), scratch3); +#ifdef DEBUG + // Assert previous frame is an IonJS frame. + masm.movePtr(scratch3, scratch1); + masm.and32(Imm32((1 << FRAMETYPE_BITS) - 1), scratch1); + { + Label checkOk; + masm.branch32(Assembler::Equal, scratch1, Imm32(JitFrame_IonJS), &checkOk); + masm.assumeUnreachable("IonAccessorIC frame must be preceded by IonJS frame"); + masm.bind(&checkOk); + } +#endif + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch3); + + // lastProfilingCallSite := AccFrame-ReturnAddr + masm.loadPtr(Address(scratch2, IonAccessorICFrameLayout::offsetOfReturnAddress()), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + + // lastProfilingFrame := AccessorFrame + AccFrame-Descriptor.Size + + // IonAccessorICFrameLayout::Size() + masm.as_daddu(scratch1, scratch2, scratch3); + masm.addPtr(Imm32(IonAccessorICFrameLayout::Size()), scratch1); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + + // + // JitFrame_Entry + // + // If at an entry frame, store null into both fields. + // + masm.bind(&handle_Entry); + { + masm.movePtr(ImmPtr(nullptr), scratch1); + masm.storePtr(scratch1, lastProfilingCallSite); + masm.storePtr(scratch1, lastProfilingFrame); + masm.ret(); + } + + Linker linker(masm); + AutoFlushICache afc("ProfilerExitFrameTailStub"); + JitCode* code = linker.newCode(cx, OTHER_CODE); + +#ifdef JS_ION_PERF + writePerfSpewerJitCodeProfile(code, "ProfilerExitFrameStub"); +#endif + + return code; +} diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 2703c4ce27..6327e099ee 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -659,7 +659,7 @@ class CallSiteDesc CallSiteDesc(uint32_t line, uint32_t column, Kind kind) : line_(line), column_(column), kind_(kind) { - MOZ_ASSERT(column <= INT32_MAX); + MOZ_ASSERT(column_ == column, "column must fit in 31 bits"); } uint32_t line() const { return line_; } uint32_t column() const { return column_; } @@ -694,6 +694,23 @@ class CallSite : public CallSiteDesc typedef Vector CallSiteVector; +class CallSiteAndTarget : public CallSite +{ + uint32_t targetIndex_; + + public: + explicit CallSiteAndTarget(CallSite cs, uint32_t targetIndex) + : CallSite(cs), targetIndex_(targetIndex) + { } + + static const uint32_t NOT_INTERNAL = UINT32_MAX; + + bool isInternal() const { return targetIndex_ != NOT_INTERNAL; } + uint32_t targetIndex() const { MOZ_ASSERT(isInternal()); return targetIndex_; } +}; + +typedef Vector CallSiteAndTargetVector; + // As an invariant across architectures, within asm.js code: // $sp % AsmJSStackAlignment = (sizeof(AsmJSFrame) + masm.framePushed) % AsmJSStackAlignment // Thus, AsmJSFrame represents the bytes pushed after the call (which occurred @@ -915,10 +932,24 @@ struct AsmJSAbsoluteLink AsmJSImmKind target; }; +// Represents a call from an asm.js function to another asm.js function, +// represented by the index of the callee in the Module Validator +struct AsmJSInternalCallee +{ + uint32_t index; + + // Provide a default constructor for embedding it in unions + AsmJSInternalCallee() = default; + + explicit AsmJSInternalCallee(uint32_t calleeIndex) + : index(calleeIndex) + {} +}; + // The base class of all Assemblers for all archs. class AssemblerShared { - Vector callsites_; + CallSiteAndTargetVector callsites_; Vector asmJSHeapAccesses_; Vector asmJSGlobalAccesses_; Vector asmJSAbsoluteLinks_; @@ -951,13 +982,15 @@ class AssemblerShared return embedsNurseryPointers_; } - void append(const CallSiteDesc& desc, size_t currentOffset, size_t framePushed) { + void append(const CallSiteDesc& desc, CodeOffsetLabel label, size_t framePushed, + uint32_t targetIndex = CallSiteAndTarget::NOT_INTERNAL) + { // framePushed does not include sizeof(AsmJSFrame), so add it in here (see // CallSite::stackDepth). - CallSite callsite(desc, currentOffset, framePushed + sizeof(AsmJSFrame)); - enoughMemory_ &= callsites_.append(callsite); + CallSite callsite(desc, label.offset(), framePushed + sizeof(AsmJSFrame)); + enoughMemory_ &= callsites_.append(CallSiteAndTarget(callsite, targetIndex)); } - CallSiteVector&& extractCallSites() { return Move(callsites_); } + const CallSiteAndTargetVector& callSites() const { return callsites_; } void append(AsmJSHeapAccess access) { enoughMemory_ &= asmJSHeapAccesses_.append(access); } AsmJSHeapAccessVector&& extractAsmJSHeapAccesses() { return Move(asmJSHeapAccesses_); } diff --git a/js/src/jit/x86-shared/Assembler-x86-shared.h b/js/src/jit/x86-shared/Assembler-x86-shared.h index 057aab2a86..b695151a22 100644 --- a/js/src/jit/x86-shared/Assembler-x86-shared.h +++ b/js/src/jit/x86-shared/Assembler-x86-shared.h @@ -990,7 +990,7 @@ class AssemblerX86Shared : public AssemblerShared // Remove the size of the return address which is included in the frame. masm.ret_i(n.value - sizeof(void*)); } - void call(Label* label) { + CodeOffsetLabel call(Label* label) { if (label->bound()) { masm.linkJump(masm.call(), JmpDst(label->offset())); } else { @@ -998,9 +998,11 @@ class AssemblerX86Shared : public AssemblerShared JmpSrc prev = JmpSrc(label->use(j.offset())); masm.setNextJump(j, prev); } + return CodeOffsetLabel(masm.currentOffset()); } - void call(Register reg) { + CodeOffsetLabel call(Register reg) { masm.call_r(reg.encoding()); + return CodeOffsetLabel(masm.currentOffset()); } void call(const Operand& op) { switch (op.kind()) { @@ -1015,6 +1017,14 @@ class AssemblerX86Shared : public AssemblerShared } } + CodeOffsetLabel callWithPatch() { + return CodeOffsetLabel(masm.call().offset()); + } + void patchCall(uint32_t callerOffset, uint32_t calleeOffset) { + unsigned char* code = masm.data(); + X86Encoding::SetRel32(code + callerOffset, code + calleeOffset); + } + void breakpoint() { masm.int3(); } diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp index 40cff83398..c49042a18a 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp @@ -3320,6 +3320,49 @@ CodeGeneratorX86Shared::visitSimdSelect(LSimdSelect* ins) masm.bitwiseOrX4(Operand(temp), output); } +void +CodeGeneratorX86Shared::visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.compareExchangeToTypedIntArray(arrayType, dest, oldval, newval, temp, output); + } +} + +void +CodeGeneratorX86Shared::visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir) +{ + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register temp = lir->temp()->isBogusTemp() ? InvalidReg : ToRegister(lir->temp()); + + Register value = ToRegister(lir->value()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + int width = Scalar::byteSize(arrayType); + + if (lir->index()->isConstant()) { + Address dest(elements, ToInt32(lir->index()) * width); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } else { + BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width)); + masm.atomicExchangeToTypedIntArray(arrayType, dest, value, temp, output); + } +} + template void CodeGeneratorX86Shared::atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type arrayType, const S& value, diff --git a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h index 2200d9152a..1672916e7b 100644 --- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h +++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h @@ -243,6 +243,8 @@ class CodeGeneratorX86Shared : public CodeGeneratorShared virtual void visitMemoryBarrier(LMemoryBarrier* ins); virtual void visitAtomicTypedArrayElementBinop(LAtomicTypedArrayElementBinop* lir); virtual void visitAtomicTypedArrayElementBinopForEffect(LAtomicTypedArrayElementBinopForEffect* lir); + virtual void visitCompareExchangeTypedArrayElement(LCompareExchangeTypedArrayElement* lir); + virtual void visitAtomicExchangeTypedArrayElement(LAtomicExchangeTypedArrayElement* lir); void visitOutOfLineLoadTypedArrayOutOfBounds(OutOfLineLoadTypedArrayOutOfBounds* ool); void visitOffsetBoundsCheck(OffsetBoundsCheck* oolCheck); diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp index d1906fb3d8..ab32a44884 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp @@ -143,6 +143,96 @@ MacroAssemblerX86Shared::asMasm() const return *static_cast(this); } +template +void +MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register oldval, Register newval, + Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + compareExchange8SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint8Clamped: + compareExchange8ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int16: + compareExchange16SignExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint16: + compareExchange16ZeroExtend(mem, oldval, newval, output.gpr()); + break; + case Scalar::Int32: + compareExchange32(mem, oldval, newval, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + compareExchange32(mem, oldval, newval, temp); + asMasm().convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); +template void +MacroAssemblerX86Shared::compareExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register oldval, Register newval, Register temp, + AnyRegister output); + +template +void +MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, + Register value, Register temp, AnyRegister output) +{ + switch (arrayType) { + case Scalar::Int8: + atomicExchange8SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Uint8Clamped: + atomicExchange8ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int16: + atomicExchange16SignExtend(mem, value, output.gpr()); + break; + case Scalar::Uint16: + atomicExchange16ZeroExtend(mem, value, output.gpr()); + break; + case Scalar::Int32: + atomicExchange32(mem, value, output.gpr()); + break; + case Scalar::Uint32: + // At the moment, the code in MCallOptimize.cpp requires the output + // type to be double for uint32 arrays. See bug 1077305. + MOZ_ASSERT(output.isFloat()); + atomicExchange32(mem, value, temp); + asMasm().convertUInt32ToDouble(temp, output.fpu()); + break; + default: + MOZ_CRASH("Invalid typed array type"); + } +} + +template void +MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const Address& mem, + Register value, Register temp, AnyRegister output); +template void +MacroAssemblerX86Shared::atomicExchangeToTypedIntArray(Scalar::Type arrayType, const BaseIndex& mem, + Register value, Register temp, AnyRegister output); + + //{{{ check_macroassembler_style // =============================================================== // Stack manipulation functions. @@ -321,16 +411,16 @@ MacroAssembler::Pop(const ValueOperand& val) // =============================================================== // Simple call functions. -void +CodeOffsetLabel MacroAssembler::call(Register reg) { - Assembler::call(reg); + return Assembler::call(reg); } -void +CodeOffsetLabel MacroAssembler::call(Label* label) { - Assembler::call(label); + return Assembler::call(label); } void @@ -365,6 +455,17 @@ MacroAssembler::call(JitCode* target) Assembler::call(target); } +CodeOffsetLabel +MacroAssembler::callWithPatch() +{ + return Assembler::callWithPatch(); +} +void +MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset) +{ + Assembler::patchCall(callerOffset, calleeOffset); +} + void MacroAssembler::callAndPushReturnAddress(Register reg) { diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h index 406ee1171a..15dec9c96c 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h @@ -1455,6 +1455,14 @@ class MacroAssemblerX86Shared : public Assembler ret(); } + template + void compareExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register oldval, Register newval, + Register temp, AnyRegister output); + + template + void atomicExchangeToTypedIntArray(Scalar::Type arrayType, const T& mem, Register value, + Register temp, AnyRegister output); + protected: bool buildOOLFakeExitFrame(void* fakeReturnAddr); }; diff --git a/js/src/jsapi-tests/tests.cpp b/js/src/jsapi-tests/tests.cpp index 3ed4bf03ef..a40c99b738 100644 --- a/js/src/jsapi-tests/tests.cpp +++ b/js/src/jsapi-tests/tests.cpp @@ -8,6 +8,7 @@ #include +#include "js/Initialization.h" #include "js/RootingAPI.h" JSAPITest* JSAPITest::list; diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 1e41b603d8..1f387e6dbe 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -62,14 +62,10 @@ #include "js/CharacterEncoding.h" #include "js/Conversions.h" #include "js/Date.h" +#include "js/Initialization.h" #include "js/Proxy.h" #include "js/SliceBudget.h" #include "js/StructuredClone.h" -#if ENABLE_INTL_API -#include "unicode/timezone.h" -#include "unicode/uclean.h" -#include "unicode/utypes.h" -#endif // ENABLE_INTL_API #include "vm/DateObject.h" #include "vm/Debugger.h" #include "vm/ErrorObject.h" @@ -445,129 +441,6 @@ JS_IsBuiltinFunctionConstructor(JSFunction* fun) /************************************************************************/ -/* - * SpiderMonkey's initialization status is tracked here, and it controls things - * that should happen only once across all runtimes. It's an API requirement - * that JS_Init (and JS_ShutDown, if called) be called in a thread-aware - * manner, so this variable doesn't need to be atomic. - * - * The only reason at present for the restriction that you can't call - * JS_Init/stuff/JS_ShutDown multiple times is the Windows PRMJ NowInit - * initialization code, which uses PR_CallOnce to initialize the PRMJ_Now - * subsystem. (For reinitialization to be permitted, we'd need to "reset" the - * called-once status -- doable, but more trouble than it's worth now.) - * Initializing that subsystem from JS_Init eliminates the problem, but - * initialization can take a comparatively long time (15ms or so), so we - * really don't want to do it in JS_Init, and we really do want to do it only - * when PRMJ_Now is eventually called. - */ -enum InitState { Uninitialized, Running, ShutDown }; -static InitState jsInitState = Uninitialized; - -#ifdef DEBUG -static unsigned -MessageParameterCount(const char* format) -{ - unsigned numfmtspecs = 0; - for (const char* fmt = format; *fmt != '\0'; fmt++) { - if (*fmt == '{' && isdigit(fmt[1])) - ++numfmtspecs; - } - return numfmtspecs; -} - -static void -CheckMessageParameterCounts() -{ - // Assert that each message format has the correct number of braced - // parameters. -# define MSG_DEF(name, count, exception, format) \ - MOZ_ASSERT(MessageParameterCount(format) == count); -# include "js.msg" -# undef MSG_DEF -} -#endif /* DEBUG */ - -JS_PUBLIC_API(bool) -JS_Init(void) -{ - MOZ_ASSERT(jsInitState == Uninitialized, - "must call JS_Init once before any JSAPI operation except " - "JS_SetICUMemoryFunctions"); - MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(), - "how do we have live runtimes before JS_Init?"); - - PRMJ_NowInit(); - -#ifdef DEBUG - CheckMessageParameterCounts(); -#endif - - using js::TlsPerThreadData; - if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init()) - return false; - -#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) - if (!js::oom::InitThreadType()) - return false; - js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN); -#endif - - jit::ExecutableAllocator::initStatic(); - - if (!jit::InitializeIon()) - return false; - -#if EXPOSE_INTL_API - UErrorCode err = U_ZERO_ERROR; - u_init(&err); - if (U_FAILURE(err)) - return false; -#endif // EXPOSE_INTL_API - - if (!CreateHelperThreadsState()) - return false; - - if (!FutexRuntime::initialize()) - return false; - - jsInitState = Running; - return true; -} - -JS_PUBLIC_API(void) -JS_ShutDown(void) -{ - MOZ_ASSERT(jsInitState == Running, - "JS_ShutDown must only be called after JS_Init and can't race with it"); -#ifdef DEBUG - if (JSRuntime::hasLiveRuntimes()) { - // Gecko is too buggy to assert this just yet. - fprintf(stderr, - "WARNING: YOU ARE LEAKING THE WORLD (at least one JSRuntime " - "and everything alive inside it, that is) AT JS_ShutDown " - "TIME. FIX THIS!\n"); - } -#endif - - FutexRuntime::destroy(); - - DestroyHelperThreadsState(); - -#ifdef JS_TRACE_LOGGING - DestroyTraceLoggerThreadState(); - DestroyTraceLoggerGraphState(); -#endif - - PRMJ_NowShutdown(); - -#if EXPOSE_INTL_API - u_cleanup(); -#endif // EXPOSE_INTL_API - - jsInitState = ShutDown; -} - #ifdef DEBUG JS_FRIEND_API(bool) JS::isGCEnabled() @@ -581,7 +454,7 @@ JS_FRIEND_API(bool) JS::isGCEnabled() { return true; } JS_PUBLIC_API(JSRuntime*) JS_NewRuntime(uint32_t maxbytes, uint32_t maxNurseryBytes, JSRuntime* parentRuntime) { - MOZ_ASSERT(jsInitState == Running, + MOZ_ASSERT(JS::detail::libraryInitState == JS::detail::InitState::Running, "must call JS_Init prior to creating any JSRuntimes"); // Make sure that all parent runtimes are the topmost parent. @@ -606,22 +479,6 @@ JS_DestroyRuntime(JSRuntime* rt) js_delete(rt); } -JS_PUBLIC_API(bool) -JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn) -{ - MOZ_ASSERT(jsInitState == Uninitialized, - "must call JS_SetICUMemoryFunctions before any other JSAPI " - "operation (including JS_Init)"); - -#if EXPOSE_INTL_API - UErrorCode status = U_ZERO_ERROR; - u_setMemoryFunctions(/* context = */ nullptr, allocFn, reallocFn, freeFn, &status); - return U_SUCCESS(status); -#else - return true; -#endif -} - static JS_CurrentEmbedderTimeFunction currentEmbedderTimeFunction; JS_PUBLIC_API(void) @@ -5590,14 +5447,6 @@ JS_ObjectIsDate(JSContext* cx, HandleObject obj, bool* isDate) return true; } -JS_PUBLIC_API(void) -JS_ClearDateCaches(JSContext* cx) -{ - AssertHeapIsIdle(cx); - CHECK_REQUEST(cx); - cx->runtime()->dateTimeInfo.updateTimeZoneAdjustment(); -} - /************************************************************************/ /* @@ -6418,12 +6267,3 @@ JS::GetObjectZone(JSObject* obj) { return obj->zone(); } - -JS_PUBLIC_API(void) -JS::ResetTimeZone() -{ -#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) - icu::TimeZone::recreateDefault(); -#endif -} - diff --git a/js/src/jsapi.h b/js/src/jsapi.h index cf49bea76b..d0663d5465 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -40,9 +40,6 @@ namespace JS { -extern JS_PUBLIC_API(void) -ResetTimeZone(); - class TwoByteChars; #ifdef JS_DEBUG @@ -945,49 +942,14 @@ JS_IsBuiltinFunctionConstructor(JSFunction* fun); /************************************************************************/ /* - * Initialization, locking, contexts, and memory allocation. + * Locking, contexts, and memory allocation. * - * It is important that the first runtime and first context be created in a - * single-threaded fashion, otherwise the behavior of the library is undefined. + * It is important that SpiderMonkey be initialized, and the first runtime and + * first context be created, in a single-threaded fashion. Otherwise the + * behavior of the library is undefined. * See: http://developer.mozilla.org/en/docs/Category:JSAPI_Reference */ -/** - * Initialize SpiderMonkey, returning true only if initialization succeeded. - * Once this method has succeeded, it is safe to call JS_NewRuntime and other - * JSAPI methods. - * - * This method must be called before any other JSAPI method is used on any - * thread. Once it has been used, it is safe to call any JSAPI method, and it - * remains safe to do so until JS_ShutDown is correctly called. - * - * It is currently not possible to initialize SpiderMonkey multiple times (that - * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so - * again). This restriction may eventually be lifted. - */ -extern JS_PUBLIC_API(bool) -JS_Init(void); - -/** - * Destroy free-standing resources allocated by SpiderMonkey, not associated - * with any runtime, context, or other structure. - * - * This method should be called after all other JSAPI data has been properly - * cleaned up: every new runtime must have been destroyed, every new context - * must have been destroyed, and so on. Calling this method before all other - * resources have been destroyed has undefined behavior. - * - * Failure to call this method, at present, has no adverse effects other than - * leaking memory. This may not always be the case; it's recommended that all - * embedders call this method when all other JSAPI operations have completed. - * - * It is currently not possible to initialize SpiderMonkey multiple times (that - * is, calling JS_Init/JSAPI methods/JS_ShutDown in that order, then doing so - * again). This restriction may eventually be lifted. - */ -extern JS_PUBLIC_API(void) -JS_ShutDown(void); - extern JS_PUBLIC_API(JSRuntime*) JS_NewRuntime(uint32_t maxbytes, uint32_t maxNurseryBytes = JS::DefaultNurseryBytes, @@ -996,20 +958,6 @@ JS_NewRuntime(uint32_t maxbytes, extern JS_PUBLIC_API(void) JS_DestroyRuntime(JSRuntime* rt); -// These are equivalent to ICU's |UMemAllocFn|, |UMemReallocFn|, and -// |UMemFreeFn| types. The first argument (called |context| in the ICU docs) -// will always be nullptr, and should be ignored. -typedef void* (*JS_ICUAllocFn)(const void*, size_t size); -typedef void* (*JS_ICUReallocFn)(const void*, void* p, size_t size); -typedef void (*JS_ICUFreeFn)(const void*, void* p); - -/** - * This function can be used to track memory used by ICU. - * Do not use it unless you know what you are doing! - */ -extern JS_PUBLIC_API(bool) -JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn); - typedef double (*JS_CurrentEmbedderTimeFunction)(); /** @@ -4799,13 +4747,6 @@ JS_NewDateObject(JSContext* cx, int year, int mon, int mday, int hour, int min, extern JS_PUBLIC_API(bool) JS_ObjectIsDate(JSContext* cx, JS::HandleObject obj, bool* isDate); -/** - * Clears the cache of calculated local time from each Date object. - * Call to propagate a system timezone change. - */ -extern JS_PUBLIC_API(void) -JS_ClearDateCaches(JSContext* cx); - /************************************************************************/ /* diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 2838044692..7e3b8bdfb9 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -18,6 +18,7 @@ #include "gc/Marking.h" #include "jit/JitCompartment.h" +#include "js/Date.h" #include "js/Proxy.h" #include "js/RootingAPI.h" #include "proxy/DeadObjectProxy.h" @@ -116,11 +117,10 @@ JSCompartment::init(JSContext* maybecx) * * As a hack, we clear our timezone cache every time we create a new * compartment. This ensures that the cache is always relatively fresh, but - * shouldn't interfere with benchmarks which create tons of date objects + * shouldn't interfere with benchmarks that create tons of date objects * (unless they also create tons of iframes, which seems unlikely). */ - if (maybecx) - maybecx->runtime()->dateTimeInfo.updateTimeZoneAdjustment(); + JS::ResetTimeZone(); if (!crossCompartmentWrappers.init(0)) { if (maybecx) diff --git a/js/src/jsdate.cpp b/js/src/jsdate.cpp index 9dfebfd362..cae65370ab 100644 --- a/js/src/jsdate.cpp +++ b/js/src/jsdate.cpp @@ -407,7 +407,7 @@ EquivalentYearForDST(int year) /* ES5 15.9.1.8. */ static double -DaylightSavingTA(double t, DateTimeInfo* dtInfo) +DaylightSavingTA(double t) { if (!IsFinite(t)) return GenericNaN(); @@ -423,29 +423,30 @@ DaylightSavingTA(double t, DateTimeInfo* dtInfo) } int64_t utcMilliseconds = static_cast(t); - int64_t offsetMilliseconds = dtInfo->getDSTOffsetMilliseconds(utcMilliseconds); + int64_t offsetMilliseconds = DateTimeInfo::getDSTOffsetMilliseconds(utcMilliseconds); return static_cast(offsetMilliseconds); } static double -AdjustTime(double date, DateTimeInfo* dtInfo) +AdjustTime(double date) { - double t = DaylightSavingTA(date, dtInfo) + dtInfo->localTZA(); - t = (dtInfo->localTZA() >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay); + double localTZA = DateTimeInfo::localTZA(); + double t = DaylightSavingTA(date) + localTZA; + t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay); return t; } /* ES5 15.9.1.9. */ static double -LocalTime(double t, DateTimeInfo* dtInfo) +LocalTime(double t) { - return t + AdjustTime(t, dtInfo); + return t + AdjustTime(t); } static double -UTC(double t, DateTimeInfo* dtInfo) +UTC(double t) { - return t - AdjustTime(t - dtInfo->localTZA(), dtInfo); + return t - AdjustTime(t - DateTimeInfo::localTZA()); } /* ES5 15.9.1.10. */ @@ -764,7 +765,7 @@ DaysInMonth(int year, int month) */ template static bool -ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo) +ParseISODate(const CharT* s, size_t length, ClippedTime* result) { size_t i = 0; int tzMul = 1; @@ -864,7 +865,7 @@ ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* d MakeTime(hour, min, sec, frac * 1000.0)); if (isLocalTime) - msec = UTC(msec, dtInfo); + msec = UTC(msec); else msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute); @@ -879,9 +880,9 @@ ParseISODate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* d template static bool -ParseDate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtInfo) +ParseDate(const CharT* s, size_t length, ClippedTime* result) { - if (ParseISODate(s, length, result, dtInfo)) + if (ParseISODate(s, length, result)) return true; if (length == 0) @@ -1143,7 +1144,7 @@ ParseDate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtIn double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0)); if (tzOffset == -1) /* no time zone specified, have to use local */ - msec = UTC(msec, dtInfo); + msec = UTC(msec); else msec += tzOffset * msPerMinute; @@ -1152,12 +1153,12 @@ ParseDate(const CharT* s, size_t length, ClippedTime* result, DateTimeInfo* dtIn } static bool -ParseDate(JSLinearString* s, ClippedTime* result, DateTimeInfo* dtInfo) +ParseDate(JSLinearString* s, ClippedTime* result) { AutoCheckCannotGC nogc; return s->hasLatin1Chars() - ? ParseDate(s->latin1Chars(nogc), s->length(), result, dtInfo) - : ParseDate(s->twoByteChars(nogc), s->length(), result, dtInfo); + ? ParseDate(s->latin1Chars(nogc), s->length(), result) + : ParseDate(s->twoByteChars(nogc), s->length(), result); } static bool @@ -1178,7 +1179,7 @@ date_parse(JSContext* cx, unsigned argc, Value* vp) return false; ClippedTime result; - if (!ParseDate(linearStr, &result, &cx->runtime()->dateTimeInfo)) { + if (!ParseDate(linearStr, &result)) { args.rval().setNaN(); return true; } @@ -1218,17 +1219,17 @@ DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp) } void -DateObject::fillLocalTimeSlots(DateTimeInfo* dtInfo) +DateObject::fillLocalTimeSlots() { /* Check if the cache is already populated. */ if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() && - getReservedSlot(TZA_SLOT).toDouble() == dtInfo->localTZA()) + getReservedSlot(TZA_SLOT).toDouble() == DateTimeInfo::localTZA()) { return; } /* Remember timezone used to generate the local cache. */ - setReservedSlot(TZA_SLOT, DoubleValue(dtInfo->localTZA())); + setReservedSlot(TZA_SLOT, DoubleValue(DateTimeInfo::localTZA())); double utcTime = UTCTime().toNumber(); @@ -1238,7 +1239,7 @@ DateObject::fillLocalTimeSlots(DateTimeInfo* dtInfo) return; } - double localTime = LocalTime(utcTime, dtInfo); + double localTime = LocalTime(utcTime); setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime)); @@ -1348,9 +1349,9 @@ DateObject::fillLocalTimeSlots(DateTimeInfo* dtInfo) } inline double -DateObject::cachedLocalTime(DateTimeInfo* dtInfo) +DateObject::cachedLocalTime() { - fillLocalTimeSlots(dtInfo); + fillLocalTimeSlots(); return getReservedSlot(LOCAL_TIME_SLOT).toDouble(); } @@ -1381,7 +1382,7 @@ date_getTime(JSContext* cx, unsigned argc, Value* vp) DateObject::getYear_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); Value yearVal = dateObj->getReservedSlot(LOCAL_YEAR_SLOT); if (yearVal.isInt32()) { @@ -1406,7 +1407,7 @@ date_getYear(JSContext* cx, unsigned argc, Value* vp) DateObject::getFullYear_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_YEAR_SLOT)); return true; @@ -1441,7 +1442,7 @@ date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp) DateObject::getMonth_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_MONTH_SLOT)); return true; @@ -1473,7 +1474,7 @@ date_getUTCMonth(JSContext* cx, unsigned argc, Value* vp) DateObject::getDate_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_DATE_SLOT)); return true; @@ -1508,7 +1509,7 @@ date_getUTCDate(JSContext* cx, unsigned argc, Value* vp) DateObject::getDay_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_DAY_SLOT)); return true; @@ -1543,7 +1544,7 @@ date_getUTCDay(JSContext* cx, unsigned argc, Value* vp) DateObject::getHours_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_HOURS_SLOT)); return true; @@ -1578,7 +1579,7 @@ date_getUTCHours(JSContext* cx, unsigned argc, Value* vp) DateObject::getMinutes_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_MINUTES_SLOT)); return true; @@ -1615,7 +1616,7 @@ date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp) DateObject::getUTCSeconds_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); - dateObj->fillLocalTimeSlots(&cx->runtime()->dateTimeInfo); + dateObj->fillLocalTimeSlots(); args.rval().set(dateObj->getReservedSlot(LOCAL_SECONDS_SLOT)); return true; @@ -1653,7 +1654,7 @@ DateObject::getTimezoneOffset_impl(JSContext* cx, const CallArgs& args) { DateObject* dateObj = &args.thisv().toObject().as(); double utctime = dateObj->UTCTime().toNumber(); - double localtime = dateObj->cachedLocalTime(&cx->runtime()->dateTimeInfo); + double localtime = dateObj->cachedLocalTime(); /* * Return the time zone offset in minutes for the current locale that is @@ -1726,25 +1727,27 @@ GetMinsOrDefault(JSContext* cx, const CallArgs& args, unsigned i, double t, doub return ToNumber(cx, args[i], mins); } -/* ES5 15.9.5.28. */ +/* ES6 20.3.4.23. */ MOZ_ALWAYS_INLINE bool date_setMilliseconds_impl(JSContext* cx, const CallArgs& args) { Rooted dateObj(cx, &args.thisv().toObject().as()); - /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); - /* Step 2. */ - double milli; - if (!ToNumber(cx, args.get(0), &milli)) + // Steps 3-4. + double ms; + if (!ToNumber(cx, args.get(0), &ms)) return false; - double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), milli); - /* Step 3. */ - ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time), &cx->runtime()->dateTimeInfo)); + // Step 5. + double time = MakeTime(HourFromTime(t), MinFromTime(t), SecFromTime(t), ms); - /* Steps 4-5. */ + // Step 6. + ClippedTime u = TimeClip(UTC(MakeDate(Day(t), time))); + + // Steps 7-8. dateObj->setUTCTime(u, args.rval()); return true; } @@ -1792,31 +1795,31 @@ date_setSeconds_impl(JSContext* cx, const CallArgs& args) { Rooted dateObj(cx, &args.thisv().toObject().as()); - /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); - /* Step 2. */ + // Steps 3-4. double s; if (!ToNumber(cx, args.get(0), &s)) return false; - /* Step 3. */ + // Steps 5-6. double milli; if (!GetMsecsOrDefault(cx, args, 1, t, &milli)) return false; - /* Step 4. */ + // Step 7. double date = MakeDate(Day(t), MakeTime(HourFromTime(t), MinFromTime(t), s, milli)); - /* Step 5. */ - ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo)); + // Step 8. + ClippedTime u = TimeClip(UTC(date)); - /* Steps 6-7. */ + // Step 9. dateObj->setUTCTime(u, args.rval()); return true; } -/* ES5 15.9.5.31. */ +/* ES6 20.3.4.26. */ static bool date_setSeconds(JSContext* cx, unsigned argc, Value* vp) { @@ -1866,39 +1869,40 @@ date_setMinutes_impl(JSContext* cx, const CallArgs& args) { Rooted dateObj(cx, &args.thisv().toObject().as()); - /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); - /* Step 2. */ + // Steps 3-4. double m; if (!ToNumber(cx, args.get(0), &m)) return false; - /* Step 3. */ + // Steps 5-6. double s; if (!GetSecsOrDefault(cx, args, 1, t, &s)) return false; - /* Step 4. */ + // Steps 7-8. double milli; if (!GetMsecsOrDefault(cx, args, 2, t, &milli)) return false; - /* Step 5. */ + // Step 9. double date = MakeDate(Day(t), MakeTime(HourFromTime(t), m, s, milli)); - /* Step 6. */ - ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo)); + // Step 10. + ClippedTime u = TimeClip(UTC(date)); - /* Steps 7-8. */ + // Steps 11-12. dateObj->setUTCTime(u, args.rval()); return true; } -/* ES5 15.9.5.33. */ +/* ES6 20.3.4.24. */ static bool date_setMinutes(JSContext* cx, unsigned argc, Value* vp) { + // Steps 1-2 (the effectful parts). CallArgs args = CallArgsFromVp(argc, vp); return CallNonGenericMethod(cx, args); } @@ -1950,36 +1954,36 @@ date_setHours_impl(JSContext* cx, const CallArgs& args) { Rooted dateObj(cx, &args.thisv().toObject().as()); - /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + // Steps 1-2. + double t = LocalTime(dateObj->UTCTime().toNumber()); - /* Step 2. */ + // Steps 3-4. double h; if (!ToNumber(cx, args.get(0), &h)) return false; - /* Step 3. */ + // Steps 5-6. double m; if (!GetMinsOrDefault(cx, args, 1, t, &m)) return false; - /* Step 4. */ + // Steps 7-8. double s; if (!GetSecsOrDefault(cx, args, 2, t, &s)) return false; - /* Step 5. */ + // Steps 9-10. double milli; if (!GetMsecsOrDefault(cx, args, 3, t, &milli)) return false; - /* Step 6. */ + // Step 11. double date = MakeDate(Day(t), MakeTime(h, m, s, milli)); - /* Step 6. */ - ClippedTime u = TimeClip(UTC(date, &cx->runtime()->dateTimeInfo)); + // Step 12. + ClippedTime u = TimeClip(UTC(date)); - /* Steps 7-8. */ + // Steps 13-14. dateObj->setUTCTime(u, args.rval()); return true; } @@ -2045,7 +2049,7 @@ date_setDate_impl(JSContext* cx, const CallArgs& args) Rooted dateObj(cx, &args.thisv().toObject().as()); /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + double t = LocalTime(dateObj->UTCTime().toNumber()); /* Step 2. */ double date; @@ -2056,7 +2060,7 @@ date_setDate_impl(JSContext* cx, const CallArgs& args) double newDate = MakeDate(MakeDay(YearFromTime(t), MonthFromTime(t), date), TimeWithinDay(t)); /* Step 4. */ - ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo)); + ClippedTime u = TimeClip(UTC(newDate)); /* Steps 5-6. */ dateObj->setUTCTime(u, args.rval()); @@ -2129,7 +2133,7 @@ date_setMonth_impl(JSContext* cx, const CallArgs& args) Rooted dateObj(cx, &args.thisv().toObject().as()); /* Step 1. */ - double t = LocalTime(dateObj->UTCTime().toNumber(), &cx->runtime()->dateTimeInfo); + double t = LocalTime(dateObj->UTCTime().toNumber()); /* Step 2. */ double m; @@ -2145,7 +2149,7 @@ date_setMonth_impl(JSContext* cx, const CallArgs& args) double newDate = MakeDate(MakeDay(YearFromTime(t), m, date), TimeWithinDay(t)); /* Step 5. */ - ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo)); + ClippedTime u = TimeClip(UTC(newDate)); /* Steps 6-7. */ dateObj->setUTCTime(u, args.rval()); @@ -2197,12 +2201,12 @@ date_setUTCMonth(JSContext* cx, unsigned argc, Value* vp) } static double -ThisLocalTimeOrZero(Handle dateObj, DateTimeInfo* dtInfo) +ThisLocalTimeOrZero(Handle dateObj) { double t = dateObj->UTCTime().toNumber(); if (IsNaN(t)) return +0; - return LocalTime(t, dtInfo); + return LocalTime(t); } static double @@ -2219,7 +2223,7 @@ date_setFullYear_impl(JSContext* cx, const CallArgs& args) Rooted dateObj(cx, &args.thisv().toObject().as()); /* Step 1. */ - double t = ThisLocalTimeOrZero(dateObj, &cx->runtime()->dateTimeInfo); + double t = ThisLocalTimeOrZero(dateObj); /* Step 2. */ double y; @@ -2240,7 +2244,7 @@ date_setFullYear_impl(JSContext* cx, const CallArgs& args) double newDate = MakeDate(MakeDay(y, m, date), TimeWithinDay(t)); /* Step 6. */ - ClippedTime u = TimeClip(UTC(newDate, &cx->runtime()->dateTimeInfo)); + ClippedTime u = TimeClip(UTC(newDate)); /* Steps 7-8. */ dateObj->setUTCTime(u, args.rval()); @@ -2303,7 +2307,7 @@ date_setYear_impl(JSContext* cx, const CallArgs& args) Rooted dateObj(cx, &args.thisv().toObject().as()); /* Step 1. */ - double t = ThisLocalTimeOrZero(dateObj, &cx->runtime()->dateTimeInfo); + double t = ThisLocalTimeOrZero(dateObj); /* Step 2. */ double y; @@ -2325,7 +2329,7 @@ date_setYear_impl(JSContext* cx, const CallArgs& args) double day = MakeDay(yint, MonthFromTime(t), DateFromTime(t)); /* Step 6. */ - double u = UTC(MakeDate(day, TimeWithinDay(t)), &cx->runtime()->dateTimeInfo); + double u = UTC(MakeDate(day, TimeWithinDay(t))); /* Steps 7-8. */ dateObj->setUTCTime(TimeClip(u), args.rval()); @@ -2504,7 +2508,7 @@ date_toJSON(JSContext* cx, unsigned argc, Value* vp) /* for Date.toLocaleFormat; interface to PRMJTime date struct. */ static void -new_explode(double timeval, PRMJTime* split, DateTimeInfo* dtInfo) +new_explode(double timeval, PRMJTime* split) { double year = YearFromTime(timeval); @@ -2520,7 +2524,7 @@ new_explode(double timeval, PRMJTime* split, DateTimeInfo* dtInfo) /* not sure how this affects things, but it doesn't seem to matter. */ - split->tm_isdst = (DaylightSavingTA(timeval, dtInfo) != 0); + split->tm_isdst = (DaylightSavingTA(timeval) != 0); } typedef enum formatspec { @@ -2542,11 +2546,11 @@ date_format(JSContext* cx, double date, formatspec format, MutableHandleValue rv } else { MOZ_ASSERT(NumbersAreIdentical(TimeClip(date).toDouble(), date)); - double local = LocalTime(date, &cx->runtime()->dateTimeInfo); + double local = LocalTime(date); /* offset from GMT in minutes. The offset includes daylight savings, if it applies. */ - int minutes = (int) floor(AdjustTime(date, &cx->runtime()->dateTimeInfo) / msPerMinute); + int minutes = (int) floor(AdjustTime(date) / msPerMinute); /* map 510 minutes to 0830 hours */ int offset = (minutes / 60) * 100 + minutes % 60; @@ -2562,7 +2566,7 @@ date_format(JSContext* cx, double date, formatspec format, MutableHandleValue rv /* get a timezone string from the OS to include as a comment. */ - new_explode(date, &split, &cx->runtime()->dateTimeInfo); + new_explode(date, &split); if (PRMJ_FormatTime(tzbuf, sizeof tzbuf, "(%Z)", &split) != 0) { /* Decide whether to use the resulting timezone string. @@ -2652,9 +2656,9 @@ ToLocaleFormatHelper(JSContext* cx, HandleObject obj, const char* format, Mutabl JS_snprintf(buf, sizeof buf, js_NaN_date_str); } else { int result_len; - double local = LocalTime(utctime, &cx->runtime()->dateTimeInfo); + double local = LocalTime(utctime); PRMJTime split; - new_explode(local, &split, &cx->runtime()->dateTimeInfo); + new_explode(local, &split); /* Let PRMJTime format it. */ result_len = PRMJ_FormatTime(buf, sizeof buf, format, &split); @@ -2672,7 +2676,7 @@ ToLocaleFormatHelper(JSContext* cx, HandleObject obj, const char* format, Mutabl /* ...but not if starts with 4-digit year, like 2022/3/11. */ !(isdigit(buf[0]) && isdigit(buf[1]) && isdigit(buf[2]) && isdigit(buf[3]))) { - double localtime = obj->as().cachedLocalTime(&cx->runtime()->dateTimeInfo); + double localtime = obj->as().cachedLocalTime(); int year = IsNaN(localtime) ? 0 : (int) YearFromTime(localtime); JS_snprintf(buf + (result_len - 2), (sizeof buf) - (result_len - 2), "%d", year); @@ -3039,7 +3043,7 @@ DateOneArgument(JSContext* cx, const CallArgs& args) if (!linearStr) return false; - if (!ParseDate(linearStr, &t, &cx->runtime()->dateTimeInfo)) + if (!ParseDate(linearStr, &t)) t = ClippedTime::invalid(); } else { double d; @@ -3128,7 +3132,7 @@ DateMultipleArguments(JSContext* cx, const CallArgs& args) double finalDate = MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)); // Steps 3q-t. - return NewDateObject(cx, args, TimeClip(UTC(finalDate, &cx->runtime()->dateTimeInfo))); + return NewDateObject(cx, args, TimeClip(UTC(finalDate))); } return ToDateString(cx, args, NowAsMillis()); @@ -3240,7 +3244,7 @@ js::NewDateObject(JSContext* cx, int year, int mon, int mday, { MOZ_ASSERT(mon < 12); double msec_time = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0.0)); - return NewDateObjectMsec(cx, TimeClip(UTC(msec_time, &cx->runtime()->dateTimeInfo))); + return NewDateObjectMsec(cx, TimeClip(UTC(msec_time))); } JS_FRIEND_API(bool) diff --git a/js/src/jsopcode.h b/js/src/jsopcode.h index 7350837d1c..bb22fabd03 100644 --- a/js/src/jsopcode.h +++ b/js/src/jsopcode.h @@ -705,6 +705,14 @@ IsGetPropPC(jsbytecode* pc) return op == JSOP_LENGTH || op == JSOP_GETPROP || op == JSOP_CALLPROP; } +inline bool +IsHiddenInitOp(JSOp op) +{ + return op == JSOP_INITHIDDENPROP || op == JSOP_INITHIDDENELEM || + op == JSOP_INITHIDDENPROP_GETTER || op == JSOP_INITHIDDENELEM_GETTER || + op == JSOP_INITHIDDENPROP_SETTER || op == JSOP_INITHIDDENELEM_SETTER; +} + inline bool IsStrictSetPC(jsbytecode* pc) { diff --git a/js/src/moz.build b/js/src/moz.build index 6c453e8a1c..1beaa267a9 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -119,6 +119,7 @@ EXPORTS.js += [ '../public/HashTable.h', '../public/HeapAPI.h', '../public/Id.h', + '../public/Initialization.h', '../public/LegacyIntTypes.h', '../public/MemoryMetrics.h', '../public/Principals.h', @@ -367,6 +368,7 @@ SOURCES += [ 'jsatom.cpp', 'jsmath.cpp', 'jsutil.cpp', + 'vm/Initialization.cpp', ] if CONFIG['JS_POSIX_NSPR']: @@ -492,6 +494,7 @@ elif CONFIG['JS_CODEGEN_MIPS32'] or CONFIG['JS_CODEGEN_MIPS64']: 'jit/mips-shared/BaselineIC-mips-shared.cpp', 'jit/mips-shared/CodeGenerator-mips-shared.cpp', 'jit/mips-shared/Lowering-mips-shared.cpp', + 'jit/mips-shared/MacroAssembler-mips-shared.cpp', 'jit/mips-shared/MoveEmitter-mips-shared.cpp', ] if CONFIG['JS_CODEGEN_MIPS32']: @@ -516,7 +519,20 @@ elif CONFIG['JS_CODEGEN_MIPS32'] or CONFIG['JS_CODEGEN_MIPS64']: UNIFIED_SOURCES += [ 'jit/mips64/Architecture-mips64.cpp', 'jit/mips64/Assembler-mips64.cpp', + 'jit/mips64/Bailouts-mips64.cpp', + 'jit/mips64/BaselineCompiler-mips64.cpp', + 'jit/mips64/BaselineIC-mips64.cpp', + 'jit/mips64/CodeGenerator-mips64.cpp', + 'jit/mips64/Lowering-mips64.cpp', + 'jit/mips64/MacroAssembler-mips64.cpp', + 'jit/mips64/MoveEmitter-mips64.cpp', + 'jit/mips64/SharedIC-mips64.cpp', + 'jit/mips64/Trampoline-mips64.cpp', ] + if CONFIG['JS_SIMULATOR_MIPS64']: + UNIFIED_SOURCES += [ + 'jit/mips64/Simulator-mips64.cpp' + ] if CONFIG['OS_ARCH'] == 'WINNT': SOURCES += [ diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 1d0bdd908f..5ec1114978 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -66,6 +66,7 @@ #include "jit/OptimizationTracking.h" #include "js/Debug.h" #include "js/GCAPI.h" +#include "js/Initialization.h" #include "js/StructuredClone.h" #include "js/TrackedOptimizationInfo.h" #include "perf/jsperf.h" diff --git a/js/src/tests/ecma_6/Class/methodInstallation.js b/js/src/tests/ecma_6/Class/methodInstallation.js index dad5803df4..cceb0bfbc7 100644 --- a/js/src/tests/ecma_6/Class/methodInstallation.js +++ b/js/src/tests/ecma_6/Class/methodInstallation.js @@ -52,20 +52,20 @@ for (let a of [testClass, var aMethDesc = Object.getOwnPropertyDescriptor(a.prototype, \"__proto__\"); assertEq(aMethDesc.writable, true); assertEq(aMethDesc.configurable, true); - assertEq(aMethDesc.enumerable, true); + assertEq(aMethDesc.enumerable, false); aMethDesc.value(); assertEq(methodCalled, true); var aGetDesc = Object.getOwnPropertyDescriptor(a.prototype, \"getter\"); assertEq(aGetDesc.configurable, true); - assertEq(aGetDesc.enumerable, true); + assertEq(aGetDesc.enumerable, false); aGetDesc.get(); assertThrowsInstanceOf(() => new aGetDesc.get, TypeError); assertEq(getterCalled, true); var aSetDesc = Object.getOwnPropertyDescriptor(a.prototype, \"setter\"); assertEq(aSetDesc.configurable, true); - assertEq(aSetDesc.enumerable, true); + assertEq(aSetDesc.enumerable, false); aSetDesc.set(); assertThrowsInstanceOf(() => new aSetDesc.set, TypeError); assertEq(setterCalled, true); @@ -74,7 +74,7 @@ for (let a of [testClass, assertEq(Object.getOwnPropertyDescriptor(new a(), \"staticMethod\"), undefined); var aStaticMethDesc = Object.getOwnPropertyDescriptor(a, \"staticMethod\"); assertEq(aStaticMethDesc.configurable, true); - assertEq(aStaticMethDesc.enumerable, true); + assertEq(aStaticMethDesc.enumerable, false); assertEq(aStaticMethDesc.writable, true); aStaticMethDesc.value(); assertThrowsInstanceOf(() => new aStaticMethDesc.value, TypeError); @@ -83,7 +83,7 @@ for (let a of [testClass, assertEq(Object.getOwnPropertyDescriptor(new a(), \"staticGetter\"), undefined); var aStaticGetDesc = Object.getOwnPropertyDescriptor(a, \"staticGetter\"); assertEq(aStaticGetDesc.configurable, true); - assertEq(aStaticGetDesc.enumerable, true); + assertEq(aStaticGetDesc.enumerable, false); aStaticGetDesc.get(); assertThrowsInstanceOf(() => new aStaticGetDesc.get, TypeError); assertEq(staticGetterCalled, true); @@ -91,7 +91,7 @@ for (let a of [testClass, assertEq(Object.getOwnPropertyDescriptor(new a(), \"staticSetter\"), undefined); var aStaticSetDesc = Object.getOwnPropertyDescriptor(a, \"staticSetter\"); assertEq(aStaticSetDesc.configurable, true); - assertEq(aStaticSetDesc.enumerable, true); + assertEq(aStaticSetDesc.enumerable, false); aStaticSetDesc.set(); assertThrowsInstanceOf(() => new aStaticSetDesc.set, TypeError); assertEq(staticSetterCalled, true); diff --git a/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js b/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js new file mode 100644 index 0000000000..cbdbe9722e --- /dev/null +++ b/js/src/tests/ecma_6/LexicalEnvironment/bug-1216623.js @@ -0,0 +1,19 @@ +// Scoping in the head of for(let;;) statements. + +let x = 0; +for (let i = 0, a = () => i; i < 4; i++) { + assertEq(i, x++); + assertEq(a(), 0); +} +assertEq(x, 4); + +x = 11; +let q = 0; +for (let {[++q]: r} = [0, 11, 22], s = () => r; r < 13; r++) { + assertEq(r, x++); + assertEq(s(), 11); +} +assertEq(x, 13); +assertEq(q, 1); + +reportCompare(0, 0); diff --git a/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js b/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js index db36323488..8db10a4e0a 100644 --- a/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js +++ b/js/src/tests/ecma_6/LexicalEnvironment/for-loop.js @@ -78,11 +78,7 @@ var outer = "OUTER V IGNORE"; var save; for (let outer = (save = function() { return outer; }); ; ) break; -assertEq(save(), "OUTER V IGNORE", - "this is actually a bug: fix for(;;) loops to evaluate init RHSes " + - "in the block scope containing all the LHS bindings!"); - - +assertEq(save(), save); var funcs = []; function t(i, name, expect) diff --git a/js/src/tests/ecma_6/Statements/try-completion.js b/js/src/tests/ecma_6/Statements/try-completion.js new file mode 100644 index 0000000000..f9bd1e1496 --- /dev/null +++ b/js/src/tests/ecma_6/Statements/try-completion.js @@ -0,0 +1,482 @@ +var BUGNUMBER = 819125; +var summary = "try block should return try value if finally returned normally"; + +print(BUGNUMBER + ": " + summary); + +function expectTryValue(code, isUndefined) { + assertEq(eval(code), isUndefined ? undefined : 'try'); +} + +function expectCatchValue(code, isUndefined) { + assertEq(eval(code), isUndefined ? undefined : 'catch'); +} + +function expectFinallyValue(code, isUndefined) { + assertEq(eval(code), isUndefined ? undefined : 'finally'); +} + +// ==== finally: normal ==== + +// try: normal +// finally: normal +expectTryValue(` +try { + 'try'; +} finally { + 'finally'; +} +`); + +// try: normal without value +// finally: normal +expectTryValue(` +try { +} finally { + 'finally'; +} +`, true); + +// try: break +// finally: normal +expectTryValue(` +while (true) { + try { + 'try'; + break; + } finally { + 'finally'; + } +} +`); + +// try: break without value +// finally: normal +expectTryValue(` +while (true) { + try { + break; + } finally { + 'finally'; + } +} +`, true); + +// try: continue +// finally: normal +expectTryValue(` +do { + try { + 'try'; + continue; + } finally { + 'finally'; + } +} while (false); +`); + +// try: continue without value +// finally: normal +expectTryValue(` +do { + try { + continue; + } finally { + 'finally'; + } +} while (false); +`, true); + +// try: throw +// catch: normal +// finally: normal +expectCatchValue(` +try { + 'try'; + throw 'exception'; +} catch (e) { + 'catch'; +} finally { + 'finally'; +} +`); + +// try: throw +// catch: normal +// finally: normal +expectCatchValue(` +try { + 'try'; + throw 'exception'; +} catch (e) { + 'catch'; +} finally { + 'finally'; +} +`); + +// try: throw +// catch: normal without value +// finally: normal +expectCatchValue(` +try { + 'try'; + throw 'exception'; +} catch (e) { +} finally { + 'finally'; +} +`, true); + +// try: throw +// catch: normal without value +// finally: normal +expectCatchValue(` +try { + 'try'; + throw 'exception'; +} catch (e) { +} finally { + 'finally'; +} +`, true); + +// try: throw +// catch: break +// finally: normal +expectCatchValue(` +while (true) { + try { + 'try'; + throw 'exception'; + } catch (e) { + 'catch'; + break; + } finally { + 'finally'; + } +} +`); + +// try: throw +// catch: break without value +// finally: normal +expectCatchValue(` +while (true) { + try { + 'try'; + throw 'exception'; + } catch (e) { + break; + } finally { + 'finally'; + } +} +`, true); + +// try: throw +// catch: continue +// finally: normal +expectCatchValue(` +do { + try { + 'try'; + throw 'exception'; + } catch (e) { + 'catch'; + continue; + } finally { + 'finally'; + } +} while (false); +`); + +// try: throw +// catch: continue without value +// finally: normal +expectCatchValue(` +do { + try { + 'try'; + throw 'exception'; + } catch (e) { + continue; + } finally { + 'finally'; + } +} while (false); +`, true); + +// ==== finally: break ==== + +// try: normal +// finally: break +expectFinallyValue(` +while (true) { + try { + 'try'; + } finally { + 'finally'; + break; + } +} +`); + +// try: normal +// finally: break without value +expectFinallyValue(` +while (true) { + try { + 'try'; + } finally { + break; + } +} +`, true); + +// try: break +// finally: break +expectFinallyValue(` +while (true) { + try { + 'try'; + break; + } finally { + 'finally'; + break; + } +} +`); + +// try: break +// finally: break without value +expectFinallyValue(` +while (true) { + try { + 'try'; + break; + } finally { + break; + } +} +`, true); + +// try: continue +// finally: break +expectFinallyValue(` +do { + try { + 'try'; + continue; + } finally { + 'finally'; + break; + } +} while (false); +`); + +// try: continue +// finally: break without value +expectFinallyValue(` +do { + try { + 'try'; + continue; + } finally { + break; + } +} while (false); +`, true); + +// try: throw +// catch: normal +// finally: break +expectFinallyValue(` +while (true) { + try { + 'try'; + throw 'exception'; + } catch (e) { + 'catch'; + } finally { + 'finally'; + break; + } +} +`, false); + +// try: throw +// catch: normal +// finally: break without value +expectFinallyValue(` +while (true) { + try { + 'try'; + throw 'exception'; + } catch (e) { + 'catch'; + } finally { + break; + } +} +`, true); + +// ==== finally: continue ==== + +// try: normal +// finally: continue +expectFinallyValue(` +do { + try { + 'try'; + } finally { + 'finally'; + continue; + } +} while (false); +`); + +// try: normal +// finally: continue without value +expectFinallyValue(` +do { + try { + 'try'; + } finally { + continue; + } +} while (false); +`, true); + +// try: break +// finally: continue +expectFinallyValue(` +do { + try { + 'try'; + break; + } finally { + 'finally'; + continue; + } +} while (false); +`); + +// try: break +// finally: continue without value +expectFinallyValue(` +do { + try { + 'try'; + break; + } finally { + continue; + } +} while (false); +`, true); + +// try: continue +// finally: continue +expectFinallyValue(` +do { + try { + 'try'; + continue; + } finally { + 'finally'; + continue; + } +} while (false); +`); + +// try: continue +// finally: continue without value +expectFinallyValue(` +do { + try { + 'try'; + continue; + } finally { + continue; + } +} while (false); +`, true); + +// ==== without finally ==== + +// try: throw +// catch: normal +expectCatchValue(` +try { + 'try'; + throw 'exception'; +} catch (e) { + 'catch'; +} +`); + +// try: throw +// catch: normal without value +expectCatchValue(` +try { + 'try'; + throw 'exception'; +} catch (e) { +} +`, true); + +// try: throw +// catch: break +expectCatchValue(` +while (true) { + try { + 'try'; + throw 'exception'; + } catch (e) { + 'catch'; + break; + } +} +`); + +// try: throw +// catch: break without value +expectCatchValue(` +while (true) { + try { + 'try'; + throw 'exception'; + } catch (e) { + break; + } +} +`, true); + +// try: throw +// catch: continue +expectCatchValue(` +do { + try { + 'try'; + throw 'exception'; + } catch (e) { + 'catch'; + continue; + } +} while (false); +`); + +// try: throw +// catch: continue without value +expectCatchValue(` +do { + try { + 'try'; + throw 'exception'; + } catch (e) { + continue; + } +} while (false); +`, true); + +if (typeof reportCompare === "function") + reportCompare(true, true); diff --git a/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js b/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js index 01f896d578..4df8682cb6 100644 --- a/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js +++ b/js/src/tests/js1_8_5/reflect-parse/destructuring-variable-declarations.js @@ -23,9 +23,9 @@ function testVarPatternCombinations(makePattSrc, makePattPatt) { assertStmt("for (var " + pattSrcs[i].join(",") + "; foo; bar);", forStmt(varDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); assertStmt("for (let " + pattSrcs[i].join(",") + "; foo; bar);", - letStmt(pattPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt))); + forStmt(letDecl(pattPatts[i]), ident("foo"), ident("bar"), emptyStmt)); assertStmt("for (const " + constSrcs[i].join(",") + "; foo; bar);", - letStmt(constPatts[i], forStmt(null, ident("foo"), ident("bar"), emptyStmt))); + forStmt(constDecl(constPatts[i]), ident("foo"), ident("bar"), emptyStmt)); } } diff --git a/js/src/vm/DateObject.h b/js/src/vm/DateObject.h index 7b28b9db33..b0f43d46e3 100644 --- a/js/src/vm/DateObject.h +++ b/js/src/vm/DateObject.h @@ -14,8 +14,6 @@ namespace js { -class DateTimeInfo; - class DateObject : public NativeObject { static const uint32_t UTC_TIME_SLOT = 0; @@ -58,12 +56,12 @@ class DateObject : public NativeObject void setUTCTime(JS::ClippedTime t); void setUTCTime(JS::ClippedTime t, MutableHandleValue vp); - inline double cachedLocalTime(DateTimeInfo* dtInfo); + inline double cachedLocalTime(); // Cache the local time, year, month, and so forth of the object. // If UTC time is not finite (e.g., NaN), the local time // slots will be set to the UTC time without conversion. - void fillLocalTimeSlots(DateTimeInfo* dtInfo); + void fillLocalTimeSlots(); static MOZ_ALWAYS_INLINE bool getTime_impl(JSContext* cx, const CallArgs& args); static MOZ_ALWAYS_INLINE bool getYear_impl(JSContext* cx, const CallArgs& args); diff --git a/js/src/vm/DateTime.cpp b/js/src/vm/DateTime.cpp index a044dc1276..4dd6d408e5 100644 --- a/js/src/vm/DateTime.cpp +++ b/js/src/vm/DateTime.cpp @@ -10,8 +10,19 @@ #include "jsutil.h" +#include "js/Date.h" +#if ENABLE_INTL_API +#include "unicode/timezone.h" +#endif + using mozilla::UnspecifiedNaN; +/* static */ js::DateTimeInfo +js::DateTimeInfo::instance; + +/* static */ mozilla::Atomic +js::DateTimeInfo::AcquireLock::spinLock; + static bool ComputeLocalTime(time_t local, struct tm* ptm) { @@ -131,7 +142,7 @@ UTCToLocalStandardOffsetSeconds() } void -js::DateTimeInfo::updateTimeZoneAdjustment() +js::DateTimeInfo::internalUpdateTimeZoneAdjustment() { /* * The difference between local standard time and UTC will never change for @@ -164,12 +175,19 @@ js::DateTimeInfo::updateTimeZoneAdjustment() * negative numbers to ensure the first computation is always a cache miss and * doesn't return a bogus offset. */ -js::DateTimeInfo::DateTimeInfo() +/* static */ void +js::DateTimeInfo::init() { + DateTimeInfo* dtInfo = &DateTimeInfo::instance; + + MOZ_ASSERT(dtInfo->localTZA_ == 0, + "we should be initializing only once, and the static instance " + "should have started out zeroed"); + // Set to a totally impossible TZA so that the comparison above will fail // and all fields will be properly initialized. - localTZA_ = UnspecifiedNaN(); - updateTimeZoneAdjustment(); + dtInfo->localTZA_ = UnspecifiedNaN(); + dtInfo->internalUpdateTimeZoneAdjustment(); } int64_t @@ -201,7 +219,7 @@ js::DateTimeInfo::computeDSTOffsetMilliseconds(int64_t utcSeconds) } int64_t -js::DateTimeInfo::getDSTOffsetMilliseconds(int64_t utcMilliseconds) +js::DateTimeInfo::internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds) { sanityCheck(); @@ -288,3 +306,13 @@ js::DateTimeInfo::sanityCheck() MOZ_ASSERT_IF(rangeStartSeconds != INT64_MIN, rangeStartSeconds <= MaxUnixTimeT && rangeEndSeconds <= MaxUnixTimeT); } + +JS_PUBLIC_API(void) +JS::ResetTimeZone() +{ + js::DateTimeInfo::updateTimeZoneAdjustment(); + +#if ENABLE_INTL_API && defined(ICU_TZ_HAS_RECREATE_DEFAULT) + icu::TimeZone::recreateDefault(); +#endif +} diff --git a/js/src/vm/DateTime.h b/js/src/vm/DateTime.h index 4ec477b3ee..5c68ca13d0 100644 --- a/js/src/vm/DateTime.h +++ b/js/src/vm/DateTime.h @@ -7,12 +7,17 @@ #ifndef vm_DateTime_h #define vm_DateTime_h +#include "mozilla/Assertions.h" +#include "mozilla/Atomics.h" +#include "mozilla/Attributes.h" #include "mozilla/FloatingPoint.h" #include "mozilla/MathAlgorithms.h" #include #include "js/Conversions.h" +#include "js/Date.h" +#include "js/Initialization.h" #include "js/Value.h" namespace js { @@ -88,23 +93,71 @@ const double EndOfTime = 8.64e15; */ class DateTimeInfo { - public: - DateTimeInfo(); + static DateTimeInfo instance; + // Date/time info is shared across all threads in DateTimeInfo::instance, + // for consistency with ICU's handling of its default time zone. Thus we + // need something to protect concurrent accesses. + // + // The spec implicitly assumes DST and time zone adjustment information + // never change in the course of a function -- sometimes even across + // reentrancy. So make critical sections as narrow as possible, and use a + // bog-standard spinlock with busy-waiting in case of contention for + // simplicity. + class MOZ_RAII AcquireLock + { + static mozilla::Atomic spinLock; + + public: + AcquireLock() { + while (!spinLock.compareExchange(false, true)) + continue; + } + ~AcquireLock() { + MOZ_ASSERT(spinLock, "spinlock should have been acquired"); + spinLock = false; + } + }; + + friend bool ::JS_Init(); + + // Initialize global date/time tracking state. This operation occurs + // during, and is restricted to, SpiderMonkey initialization. + static void init(); + + public: /* * Get the DST offset in milliseconds at a UTC time. This is usually * either 0 or |msPerSecond * SecondsPerHour|, but at least one exotic time * zone (Lord Howe Island, Australia) has a fractional-hour offset, just to * keep things interesting. */ - int64_t getDSTOffsetMilliseconds(int64_t utcMilliseconds); + static int64_t getDSTOffsetMilliseconds(int64_t utcMilliseconds) { + AcquireLock lock; - void updateTimeZoneAdjustment(); + return DateTimeInfo::instance.internalGetDSTOffsetMilliseconds(utcMilliseconds); + } /* ES5 15.9.1.7. */ - double localTZA() { return localTZA_; } + static double localTZA() { + AcquireLock lock; + + return DateTimeInfo::instance.localTZA_; + } private: + // We don't want anyone accidentally calling *only* + // DateTimeInfo::updateTimeZoneAdjustment() to respond to a system time + // zone change (missing the necessary poking of ICU as well), so ensure + // only JS::ResetTimeZone() can call this via access restrictions. + friend void JS::ResetTimeZone(); + + static void updateTimeZoneAdjustment() { + AcquireLock lock; + + DateTimeInfo::instance.internalUpdateTimeZoneAdjustment(); + } + /* * The current local time zone adjustment, cached because retrieving this * dynamically is Slow, and a certain venerable benchmark which shall not @@ -113,7 +166,7 @@ class DateTimeInfo * SpiderMonkey occasionally and arbitrarily updates this value from the * system time zone to attempt to keep this reasonably up-to-date. If * temporary inaccuracy can't be tolerated, JSAPI clients may call - * JS_ClearDateCaches to forcibly sync this with the system time zone. + * JS::ResetTimeZone to forcibly sync this with the system time zone. */ double localTZA_; @@ -141,6 +194,9 @@ class DateTimeInfo static const int64_t RangeExpansionAmount = 30 * SecondsPerDay; + int64_t internalGetDSTOffsetMilliseconds(int64_t utcMilliseconds); + void internalUpdateTimeZoneAdjustment(); + void sanityCheck(); }; diff --git a/js/src/vm/Initialization.cpp b/js/src/vm/Initialization.cpp new file mode 100644 index 0000000000..cf5d21aa23 --- /dev/null +++ b/js/src/vm/Initialization.cpp @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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/. */ + +/* SpiderMonkey initialization and shutdown code. */ + +#include "js/Initialization.h" + +#include "mozilla/Assertions.h" + +#include + +#include "jstypes.h" + +#include "builtin/AtomicsObject.h" +#include "jit/ExecutableAllocator.h" +#include "jit/Ion.h" +#include "js/Utility.h" +#if ENABLE_INTL_API +#include "unicode/uclean.h" +#include "unicode/utypes.h" +#endif // ENABLE_INTL_API +#include "vm/DateTime.h" +#include "vm/HelperThreads.h" +#include "vm/Runtime.h" +#include "vm/Time.h" +#include "vm/TraceLogging.h" + +using JS::detail::InitState; +using JS::detail::libraryInitState; +using js::FutexRuntime; + +InitState JS::detail::libraryInitState; + +#ifdef DEBUG +static unsigned +MessageParameterCount(const char* format) +{ + unsigned numfmtspecs = 0; + for (const char* fmt = format; *fmt != '\0'; fmt++) { + if (*fmt == '{' && isdigit(fmt[1])) + ++numfmtspecs; + } + return numfmtspecs; +} + +static void +CheckMessageParameterCounts() +{ + // Assert that each message format has the correct number of braced + // parameters. +# define MSG_DEF(name, count, exception, format) \ + MOZ_ASSERT(MessageParameterCount(format) == count); +# include "js.msg" +# undef MSG_DEF +} +#endif /* DEBUG */ + +JS_PUBLIC_API(bool) +JS_Init(void) +{ + MOZ_ASSERT(libraryInitState == InitState::Uninitialized, + "must call JS_Init once before any JSAPI operation except " + "JS_SetICUMemoryFunctions"); + MOZ_ASSERT(!JSRuntime::hasLiveRuntimes(), + "how do we have live runtimes before JS_Init?"); + + PRMJ_NowInit(); + +#ifdef DEBUG + CheckMessageParameterCounts(); +#endif + + using js::TlsPerThreadData; + if (!TlsPerThreadData.initialized() && !TlsPerThreadData.init()) + return false; + +#if defined(DEBUG) || defined(JS_OOM_BREAKPOINT) + if (!js::oom::InitThreadType()) + return false; + js::oom::SetThreadType(js::oom::THREAD_TYPE_MAIN); +#endif + + js::jit::ExecutableAllocator::initStatic(); + + if (!js::jit::InitializeIon()) + return false; + + js::DateTimeInfo::init(); + +#if EXPOSE_INTL_API + UErrorCode err = U_ZERO_ERROR; + u_init(&err); + if (U_FAILURE(err)) + return false; +#endif // EXPOSE_INTL_API + + if (!js::CreateHelperThreadsState()) + return false; + + if (!FutexRuntime::initialize()) + return false; + + libraryInitState = InitState::Running; + return true; +} + +JS_PUBLIC_API(void) +JS_ShutDown(void) +{ + MOZ_ASSERT(libraryInitState == InitState::Running, + "JS_ShutDown must only be called after JS_Init and can't race with it"); +#ifdef DEBUG + if (JSRuntime::hasLiveRuntimes()) { + // Gecko is too buggy to assert this just yet. + fprintf(stderr, + "WARNING: YOU ARE LEAKING THE WORLD (at least one JSRuntime " + "and everything alive inside it, that is) AT JS_ShutDown " + "TIME. FIX THIS!\n"); + } +#endif + + FutexRuntime::destroy(); + + js::DestroyHelperThreadsState(); + +#ifdef JS_TRACE_LOGGING + js::DestroyTraceLoggerThreadState(); + js::DestroyTraceLoggerGraphState(); +#endif + + // The only difficult-to-address reason for the restriction that you can't + // call JS_Init/stuff/JS_ShutDown multiple times is the Windows PRMJ + // NowInit initialization code, which uses PR_CallOnce to initialize the + // PRMJ_Now subsystem. (For reinitialization to be permitted, we'd need to + // "reset" the called-once status -- doable, but more trouble than it's + // worth now.) Initializing that subsystem from JS_Init eliminates the + // problem, but initialization can take a comparatively long time (15ms or + // so), so we really don't want to do it in JS_Init, and we really do want + // to do it only when PRMJ_Now is eventually called. + PRMJ_NowShutdown(); + +#if EXPOSE_INTL_API + u_cleanup(); +#endif // EXPOSE_INTL_API + + libraryInitState = InitState::ShutDown; +} + +JS_PUBLIC_API(bool) +JS_SetICUMemoryFunctions(JS_ICUAllocFn allocFn, JS_ICUReallocFn reallocFn, JS_ICUFreeFn freeFn) +{ + MOZ_ASSERT(libraryInitState == InitState::Uninitialized, + "must call JS_SetICUMemoryFunctions before any other JSAPI " + "operation (including JS_Init)"); + +#if EXPOSE_INTL_API + UErrorCode status = U_ZERO_ERROR; + u_setMemoryFunctions(/* context = */ nullptr, allocFn, reallocFn, freeFn, &status); + return U_SUCCESS(status); +#else + return true; +#endif +} diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h index 7b9abb9b4e..5b45b877f1 100644 --- a/js/src/vm/Interpreter-inl.h +++ b/js/src/vm/Interpreter-inl.h @@ -748,7 +748,7 @@ TypeOfObjectOperation(JSObject* obj, JSRuntime* rt) } static MOZ_ALWAYS_INLINE bool -InitElemOperation(JSContext* cx, HandleObject obj, HandleValue idval, HandleValue val) +InitElemOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, HandleValue idval, HandleValue val) { MOZ_ASSERT(!val.isMagic(JS_ELEMENTS_HOLE)); MOZ_ASSERT(!obj->getClass()->getProperty); @@ -758,7 +758,8 @@ InitElemOperation(JSContext* cx, HandleObject obj, HandleValue idval, HandleValu if (!ToPropertyKey(cx, idval, &id)) return false; - return DefineProperty(cx, obj, id, val, nullptr, nullptr, JSPROP_ENUMERATE); + unsigned flags = JSOp(*pc) == JSOP_INITHIDDENELEM ? 0 : JSPROP_ENUMERATE; + return DefineProperty(cx, obj, id, val, nullptr, nullptr, flags); } static MOZ_ALWAYS_INLINE bool diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 8024c269f5..edcf49890a 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -1728,11 +1728,6 @@ CASE(JSOP_NOP) CASE(JSOP_UNUSED14) CASE(JSOP_BACKPATCH) CASE(JSOP_UNUSED145) -CASE(JSOP_UNUSED171) -CASE(JSOP_UNUSED172) -CASE(JSOP_UNUSED173) -CASE(JSOP_UNUSED174) -CASE(JSOP_UNUSED175) CASE(JSOP_UNUSED176) CASE(JSOP_UNUSED177) CASE(JSOP_UNUSED178) @@ -3242,7 +3237,9 @@ CASE(JSOP_CALLEE) END_CASE(JSOP_CALLEE) CASE(JSOP_INITPROP_GETTER) +CASE(JSOP_INITHIDDENPROP_GETTER) CASE(JSOP_INITPROP_SETTER) +CASE(JSOP_INITHIDDENPROP_SETTER) { MOZ_ASSERT(REGS.stackDepth() >= 2); @@ -3258,7 +3255,9 @@ CASE(JSOP_INITPROP_SETTER) END_CASE(JSOP_INITPROP_GETTER) CASE(JSOP_INITELEM_GETTER) +CASE(JSOP_INITHIDDENELEM_GETTER) CASE(JSOP_INITELEM_SETTER) +CASE(JSOP_INITHIDDENELEM_SETTER) { MOZ_ASSERT(REGS.stackDepth() >= 3); @@ -3373,6 +3372,7 @@ CASE(JSOP_INITHIDDENPROP) END_CASE(JSOP_INITPROP) CASE(JSOP_INITELEM) +CASE(JSOP_INITHIDDENELEM) { MOZ_ASSERT(REGS.stackDepth() >= 3); HandleValue val = REGS.stackHandleAt(-1); @@ -3380,7 +3380,7 @@ CASE(JSOP_INITELEM) ReservedRooted obj(&rootObject0, ®S.sp[-3].toObject()); - if (!InitElemOperation(cx, obj, id, val)) + if (!InitElemOperation(cx, REGS.pc, obj, id, val)) goto error; REGS.sp -= 2; @@ -4373,16 +4373,22 @@ js::InitGetterSetterOperation(JSContext* cx, jsbytecode* pc, HandleObject obj, H MOZ_ASSERT(val->isCallable()); GetterOp getter; SetterOp setter; - unsigned attrs = JSPROP_ENUMERATE | JSPROP_SHARED; + unsigned attrs = JSPROP_SHARED; JSOp op = JSOp(*pc); - if (op == JSOP_INITPROP_GETTER || op == JSOP_INITELEM_GETTER) { + if (!IsHiddenInitOp(op)) + attrs |= JSPROP_ENUMERATE; + + if (op == JSOP_INITPROP_GETTER || op == JSOP_INITELEM_GETTER || + op == JSOP_INITHIDDENPROP_GETTER || op == JSOP_INITHIDDENELEM_GETTER) + { getter = CastAsGetterOp(val); setter = nullptr; attrs |= JSPROP_GETTER; } else { - MOZ_ASSERT(op == JSOP_INITPROP_SETTER || op == JSOP_INITELEM_SETTER); + MOZ_ASSERT(op == JSOP_INITPROP_SETTER || op == JSOP_INITELEM_SETTER || + op == JSOP_INITHIDDENPROP_SETTER || op == JSOP_INITHIDDENELEM_SETTER); getter = nullptr; setter = CastAsSetterOp(val); attrs |= JSPROP_SETTER; diff --git a/js/src/vm/Opcodes.h b/js/src/vm/Opcodes.h index b457a79ab6..1f2524019e 100644 --- a/js/src/vm/Opcodes.h +++ b/js/src/vm/Opcodes.h @@ -1744,11 +1744,63 @@ * Stack: => */ \ macro(JSOP_THROWSETALIASEDCONST, 170, "throwsetaliasedconst", NULL, 5, 1, 1, JOF_SCOPECOORD|JOF_NAME|JOF_SET|JOF_DETECTING) \ - macro(JSOP_UNUSED171, 171,"unused171", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED172, 172,"unused172", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED173, 173,"unused173", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED174, 174,"unused174", NULL, 1, 0, 0, JOF_BYTE) \ - macro(JSOP_UNUSED175, 175,"unused175", NULL, 1, 0, 0, JOF_BYTE) \ + /* + * Initialize a non-enumerable getter in an object literal. + * + * Pops the top two values on the stack as 'val' and 'obj', defines + * getter of 'obj' as 'val', pushes 'obj' onto the stack. + * Category: Literals + * Type: Object + * Operands: uint32_t nameIndex + * Stack: obj, val => obj + */ \ + macro(JSOP_INITHIDDENPROP_GETTER, 171, "inithiddenprop_getter", NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) \ + /* + * Initialize a non-enumerable setter in an object literal. + * + * Pops the top two values on the stack as 'val' and 'obj', defines + * setter of 'obj' as 'val', pushes 'obj' onto the stack. + * Category: Literals + * Type: Object + * Operands: uint32_t nameIndex + * Stack: obj, val => obj + */ \ + macro(JSOP_INITHIDDENPROP_SETTER, 172, "inithiddenprop_setter", NULL, 5, 2, 1, JOF_ATOM|JOF_PROP|JOF_SET|JOF_DETECTING) \ + /* + * Initialize a non-enumerable numeric getter in an object literal like + * '{get 2() {}}'. + * + * Pops the top three values on the stack as 'val', 'id' and 'obj', defines + * 'id' getter of 'obj' as 'val', pushes 'obj' onto the stack. + * Category: Literals + * Type: Object + * Operands: + * Stack: obj, id, val => obj + */ \ + macro(JSOP_INITHIDDENELEM_GETTER, 173, "inithiddenelem_getter", NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_SET|JOF_DETECTING) \ + /* + * Initialize a non-enumerable numeric setter in an object literal like + * '{set 2(v) {}}'. + * + * Pops the top three values on the stack as 'val', 'id' and 'obj', defines + * 'id' setter of 'obj' as 'val', pushes 'obj' onto the stack. + * Category: Literals + * Type: Object + * Operands: + * Stack: obj, id, val => obj + */ \ + macro(JSOP_INITHIDDENELEM_SETTER, 174, "inithiddenelem_setter", NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_SET|JOF_DETECTING) \ + /* + * Initialize a non-enumerable numeric property in an object literal, like '{1: x}'. + * + * Pops the top three values on the stack as 'val', 'id' and 'obj', defines + * 'id' property of 'obj' as 'val', pushes 'obj' onto the stack. + * Category: Literals + * Type: Object + * Operands: + * Stack: obj, id, val => obj + */ \ + macro(JSOP_INITHIDDENELEM, 175, "inithiddenelem", NULL, 1, 3, 1, JOF_BYTE|JOF_ELEM|JOF_SET|JOF_DETECTING) \ macro(JSOP_UNUSED176, 176,"unused176", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED177, 177,"unused177", NULL, 1, 0, 0, JOF_BYTE) \ macro(JSOP_UNUSED178, 178,"unused178", NULL, 1, 0, 0, JOF_BYTE) \ diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index ea2263510b..c3c40b6bbf 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -45,6 +45,7 @@ #include "jit/JitCompartment.h" #include "jit/mips32/Simulator-mips32.h" #include "jit/PcScriptCache.h" +#include "js/Date.h" #include "js/MemoryMetrics.h" #include "js/SliceBudget.h" #include "vm/Debugger.h" @@ -334,7 +335,7 @@ JSRuntime::init(uint32_t maxbytes, uint32_t maxNurseryBytes) if (!InitRuntimeNumberState(this)) return false; - dateTimeInfo.updateTimeZoneAdjustment(); + JS::ResetTimeZone(); #ifdef JS_SIMULATOR simulator_ = js::jit::Simulator::Create(); diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index af6daf2b4f..862c8fbd62 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -1203,7 +1203,6 @@ struct JSRuntime : public JS::shadow::Runtime, js::LazyScriptCache lazyScriptCache; js::CompressedSourceSet compressedSourceSet; - js::DateTimeInfo dateTimeInfo; // Pool of maps used during parse/emit. This may be modified by threads // with an ExclusiveContext and requires a lock. Active compilations diff --git a/js/src/vm/SelfHosting.cpp b/js/src/vm/SelfHosting.cpp index d37b12556b..a00f8158bb 100644 --- a/js/src/vm/SelfHosting.cpp +++ b/js/src/vm/SelfHosting.cpp @@ -1320,7 +1320,7 @@ intrinsic_LocalTZA(JSContext* cx, unsigned argc, Value* vp) CallArgs args = CallArgsFromVp(argc, vp); MOZ_ASSERT(args.length() == 0, "the LocalTZA intrinsic takes no arguments"); - args.rval().setDouble(cx->runtime()->dateTimeInfo.localTZA()); + args.rval().setDouble(DateTimeInfo::localTZA()); return true; } @@ -1452,6 +1452,16 @@ intrinsic_CreateNamespaceBinding(JSContext* cx, unsigned argc, Value* vp) return true; } +static bool +intrinsic_InstantiateModuleFunctionDeclarations(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + MOZ_ASSERT(args.length() == 1); + RootedModuleObject module(cx, &args[0].toObject().as()); + args.rval().setUndefined(); + return ModuleObject::instantiateFunctionDeclarations(cx, module); +} + static bool intrinsic_SetModuleEvaluated(JSContext* cx, unsigned argc, Value* vp) { @@ -1783,6 +1793,8 @@ static const JSFunctionSpec intrinsic_functions[] = { JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 1, 0), JS_FN("CreateImportBinding", intrinsic_CreateImportBinding, 4, 0), JS_FN("CreateNamespaceBinding", intrinsic_CreateNamespaceBinding, 3, 0), + JS_FN("InstantiateModuleFunctionDeclarations", + intrinsic_InstantiateModuleFunctionDeclarations, 1, 0), JS_FN("SetModuleEvaluated", intrinsic_SetModuleEvaluated, 1, 0), JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0), JS_FN("IsModuleNamespace", intrinsic_IsModuleNamespace, 1, 0), diff --git a/js/src/vm/UnboxedObject.cpp b/js/src/vm/UnboxedObject.cpp index 2931bd3d9c..ca955f1942 100644 --- a/js/src/vm/UnboxedObject.cpp +++ b/js/src/vm/UnboxedObject.cpp @@ -1082,6 +1082,7 @@ UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32 res = NewObjectWithGroup(cx, group, allocKind, newKind); if (!res) return nullptr; + res->setInitializedLengthNoBarrier(0); res->setInlineElements(); size_t actualCapacity = (GetGCKindBytes(allocKind) - offsetOfInlineElements()) / elementSize; @@ -1091,6 +1092,7 @@ UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32 res = NewObjectWithGroup(cx, group, gc::AllocKind::OBJECT0, newKind); if (!res) return nullptr; + res->setInitializedLengthNoBarrier(0); uint32_t capacityIndex = (capacity == length) ? CapacityMatchesLengthIndex @@ -1101,7 +1103,6 @@ UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32 if (!res->elements_) { // Make the object safe for GC. res->setInlineElements(); - res->setInitializedLength(0); return nullptr; } @@ -1109,7 +1110,6 @@ UnboxedArrayObject::create(ExclusiveContext* cx, HandleObjectGroup group, uint32 } res->setLength(cx, length); - res->setInitializedLength(0); return res; } diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 5bd752a4f8..eb2602d4a1 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,7 +29,7 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 308; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 309; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); diff --git a/python/mozbuild/mozbuild/artifacts.py b/python/mozbuild/mozbuild/artifacts.py index fb0e2bce8c..d6b24f5663 100644 --- a/python/mozbuild/mozbuild/artifacts.py +++ b/python/mozbuild/mozbuild/artifacts.py @@ -37,6 +37,7 @@ consumers will need to arrange this themselves. from __future__ import absolute_import, print_function, unicode_literals import functools +import hashlib import logging import operator import os @@ -54,6 +55,14 @@ from mozbuild.util import ( ensureParentDir, FileAvoidWrite, ) +import mozpack.path as mozpath +from mozversion import mozversion +from mozregression.download_manager import ( + DownloadManager, +) +from mozregression.persist_limit import ( + PersistLimit, +) MAX_CACHED_PARENTS = 100 # Number of parent changesets to cache candidate pushheads for. NUM_PUSHHEADS_TO_QUERY_PER_PARENT = 50 # Number of candidate pushheads to cache per parent changeset. @@ -69,7 +78,8 @@ MAX_CACHED_ARTIFACTS = 6 # Keep the keys of this map in sync with the |mach artifact| --job options. JOB_DETAILS = { # 'android-api-9': {'re': re.compile('public/build/fennec-(.*)\.android-arm\.apk')}, - 'android-api-11': {'re': re.compile('public/build/geckolibs-(.*)\.aar')}, + 'android-api-11': {'re': re.compile('public/build/fennec-(.*)\.android-arm\.apk')}, + 'android-x86': {'re': re.compile('public/build/fennec-(.*)\.android-i386\.apk')}, # 'linux': {'re': re.compile('public/build/firefox-(.*)\.linux-i686\.tar\.bz2')}, # 'linux64': {'re': re.compile('public/build/firefox-(.*)\.linux-x86_64\.tar\.bz2')}, # 'macosx64': {'re': re.compile('public/build/firefox-(.*)\.mac\.dmg')}, @@ -108,7 +118,7 @@ class CacheManager(object): def __init__(self, cache_dir, cache_name, cache_size, cache_callback=None, log=None): self._cache = pylru.lrucache(cache_size, callback=cache_callback) - self._cache_filename = os.path.join(cache_dir, cache_name + '-cache.pickle') + self._cache_filename = mozpath.join(cache_dir, cache_name + '-cache.pickle') self._log = log def log(self, *args, **kwargs): @@ -206,9 +216,7 @@ class TaskCache(CacheManager): 'Unknown job {job}') raise KeyError("Unknown job") - # Bug 1175655: it appears that the Task Cluster index only takes - # 12-char hex hashes. - key = '{rev}.{tree}.{job}'.format(rev=rev[:12], tree=tree, job=job) + key = '{rev}.{tree}.{job}'.format(rev=rev, tree=tree, job=job) try: namespace = 'buildbot.revisions.{key}'.format(key=key) task = self._index.findTask(namespace) @@ -229,7 +237,10 @@ class TaskCache(CacheManager): # TODO: Handle multiple artifacts, taking the latest one. for name in names(): - # We can easily extract the task ID and the build ID from the URL. + # We can easily extract the task ID from the URL. We can't easily + # extract the build ID; we use the .ini files embedded in the + # downloaded artifact for this. We could also use the uploaded + # public/build/buildprops.json for this purpose. url = self._queue.buildUrl('getLatestArtifact', taskId, name) return url raise ValueError('Task for {key} existed, but no artifacts found!'.format(key=key)) @@ -248,6 +259,10 @@ class ArtifactCache(CacheManager): # TODO: instead of storing N artifact packages, store M megabytes. CacheManager.__init__(self, cache_dir, 'fetch', MAX_CACHED_ARTIFACTS, cache_callback=self.delete_file, log=log) self._cache_dir = cache_dir + size_limit = 1024 * 1024 * 1024 # 1Gb in bytes. + file_limit = 4 # But always keep at least 4 old artifacts around. + persist_limit = PersistLimit(size_limit, file_limit) + self._download_manager = DownloadManager(self._cache_dir, persist_limit=persist_limit) def delete_file(self, key, value): try: @@ -260,27 +275,34 @@ class ArtifactCache(CacheManager): @cachedmethod(operator.attrgetter('_cache')) def fetch(self, url, force=False): - args = ['wget', url] - - if not force: - args[1:1] = ['--timestamping'] - - proc = subprocess.Popen(args, cwd=self._cache_dir) - status = None - # Leave it to the subprocess to handle Ctrl+C. If it terminates as - # a result of Ctrl+C, proc.wait() will return a status code, and, - # we get out of the loop. If it doesn't, like e.g. gdb, we continue - # waiting. - while status is None: - try: - status = proc.wait() - except KeyboardInterrupt: - pass - - if status != 0: - raise Exception('Process executed with non-0 exit code: %s' % args) - - return os.path.abspath(os.path.join(self._cache_dir, os.path.basename(url))) + # We download to a temporary name like HASH[:16]-basename to + # differentiate among URLs with the same basenames. We then extract the + # build ID from the downloaded artifact and use it to make a human + # readable unique name. + hash = hashlib.sha256(url).hexdigest()[:16] + fname = hash + '-' + os.path.basename(url) + self.log(logging.INFO, 'artifact', + {'path': os.path.abspath(mozpath.join(self._cache_dir, fname))}, + 'Downloading to temporary location {path}') + try: + dl = self._download_manager.download(url, fname) + if dl: + dl.wait() + # Version information is extracted from {application,platform}.ini + # in the package itself. + info = mozversion.get_version(mozpath.join(self._cache_dir, fname)) + buildid = info['platform_buildid'] or info['application_buildid'] + if not buildid: + raise ValueError('Artifact for {url} existed, but no build ID could be extracted!'.format(url=url)) + newname = buildid + '-' + os.path.basename(url) + os.rename(mozpath.join(self._cache_dir, fname), mozpath.join(self._cache_dir, newname)) + self.log(logging.INFO, 'artifact', + {'path': os.path.abspath(mozpath.join(self._cache_dir, newname))}, + 'Downloaded artifact to {path}') + return os.path.abspath(mozpath.join(self._cache_dir, newname)) + finally: + # Cancel any background downloads in progress. + self._download_manager.cancel() def print_last_item(self, args, sorted_kwargs, result): url, = args @@ -325,7 +347,6 @@ class Artifacts(object): n = os.path.join(distdir, 'bin', os.path.basename(info.filename)) fh = FileAvoidWrite(n, mode='r') shutil.copyfileobj(zf.open(info), fh) - fh.write(zf.open(info).read()) file_existed, file_updated = fh.close() self.log(logging.INFO, 'artifact', {'updating': 'Updating' if file_updated else 'Not updating', 'filename': n}, @@ -356,6 +377,9 @@ class Artifacts(object): with self._task_cache as task_cache, self._pushhead_cache as pushhead_cache: # with blocks handle handle persistence. for pushhead in pushhead_cache.pushheads(self._tree, revset): + self.log(logging.DEBUG, 'artifact', + {'pushhead': pushhead}, + 'Trying to find artifacts for pushhead {pushhead}.') try: url = task_cache.artifact_url(self._tree, self._job, pushhead) break @@ -363,6 +387,9 @@ class Artifacts(object): pass if url: return self.install_from_url(url, distdir) + self.log(logging.ERROR, 'artifact', + {'revset': revset}, + 'No built artifacts for {revset} found.') return 1 def install_from(self, source, distdir): diff --git a/python/mozbuild/mozpack/copier.py b/python/mozbuild/mozpack/copier.py index 5010e6d11a..b1fd57d225 100644 --- a/python/mozbuild/mozpack/copier.py +++ b/python/mozbuild/mozpack/copier.py @@ -35,17 +35,23 @@ class FileRegistry(object): def __init__(self): self._files = OrderedDict() self._required_directories = Counter() + self._partial_paths_cache = {} def _partial_paths(self, path): ''' Turn "foo/bar/baz/zot" into ["foo/bar/baz", "foo/bar", "foo"]. ''' - partial_paths = [] - partial_path = path - while partial_path: - partial_path = mozpath.dirname(partial_path) - if partial_path: - partial_paths.append(partial_path) + dir_name = path.rpartition('/')[0] + if not dir_name: + return [] + + partial_paths = self._partial_paths_cache.get(dir_name) + if partial_paths: + return partial_paths + + partial_paths = [dir_name] + self._partial_paths(dir_name) + + self._partial_paths_cache[dir_name] = partial_paths return partial_paths def add(self, path, content): diff --git a/python/mozbuild/mozpack/executables.py b/python/mozbuild/mozpack/executables.py index c31337c6ea..c943564fae 100644 --- a/python/mozbuild/mozpack/executables.py +++ b/python/mozbuild/mozpack/executables.py @@ -107,8 +107,9 @@ def may_elfhack(path): # elfhack only supports libraries. We should check the ELF header for # the right flag, but checking the file extension works too. from buildconfig import substs - return 'USE_ELF_HACK' in substs and substs['USE_ELF_HACK'] and \ - path.endswith(substs['DLL_SUFFIX']) + return ('USE_ELF_HACK' in substs and substs['USE_ELF_HACK'] and + path.endswith(substs['DLL_SUFFIX']) and + 'COMPILE_ENVIRONMENT' in substs and substs['COMPILE_ENVIRONMENT']) def elfhack(path): diff --git a/python/mozbuild/mozpack/mozjar.py b/python/mozbuild/mozpack/mozjar.py index f0904bcda9..8cbd142edd 100644 --- a/python/mozbuild/mozpack/mozjar.py +++ b/python/mozbuild/mozpack/mozjar.py @@ -458,19 +458,23 @@ class JarWriter(object): archives as well as jar archives optimized for Gecko. See the documentation for the close() member function for a description of both layouts. ''' - def __init__(self, file=None, fileobj=None, compress=True, optimize=True): + def __init__(self, file=None, fileobj=None, compress=True, optimize=True, + compress_level=9): ''' Initialize a Jar archive in the given file. Use the given file-like object if one is given instead of opening the given file name. The compress option determines the default behavior for storing data in the jar archive. The optimize options determines whether the jar - archive should be optimized for Gecko or not. + archive should be optimized for Gecko or not. ``compress_level`` + defines the zlib compression level. It must be a value between 0 and 9 + and defaults to 9, the highest and slowest level of compression. ''' if fileobj: self._data = fileobj else: self._data = open(file, 'wb') self._compress = compress + self._compress_level = compress_level self._contents = OrderedDict() self._last_preloaded = None self._optimize = optimize @@ -592,7 +596,7 @@ class JarWriter(object): or (isinstance(data, Deflater) and data.compress == compress): deflater = data else: - deflater = Deflater(compress) + deflater = Deflater(compress, compress_level=self._compress_level) if isinstance(data, basestring): deflater.write(data) elif hasattr(data, 'read'): @@ -650,7 +654,7 @@ class Deflater(object): compressed unless the compressed form is smaller than the uncompressed data. ''' - def __init__(self, compress=True): + def __init__(self, compress=True, compress_level=9): ''' Initialize a Deflater. The compress argument determines whether to try to compress at all. @@ -658,7 +662,8 @@ class Deflater(object): self._data = BytesIO() self.compress = compress if compress: - self._deflater = zlib.compressobj(9, zlib.DEFLATED, -MAX_WBITS) + self._deflater = zlib.compressobj(compress_level, zlib.DEFLATED, + -MAX_WBITS) self._deflated = BytesIO() else: self._deflater = None diff --git a/python/mozbuild/mozpack/test/test_copier.py b/python/mozbuild/mozpack/test/test_copier.py index 2098d4a067..cd3212beb4 100644 --- a/python/mozbuild/mozpack/test/test_copier.py +++ b/python/mozbuild/mozpack/test/test_copier.py @@ -38,6 +38,16 @@ class TestFileRegistry(MatchTestTemplate, unittest.TestCase): self.assertFalse(self.registry.contains(pattern)) self.assertEqual(self.registry.match(pattern), result) + def test_partial_paths(self): + cases = { + 'foo/bar/baz/zot': ['foo/bar/baz', 'foo/bar', 'foo'], + 'foo/bar': ['foo'], + 'bar': [], + } + reg = FileRegistry() + for path, parts in cases.iteritems(): + self.assertEqual(reg._partial_paths(path), parts) + def test_file_registry(self): self.registry = FileRegistry() self.registry.add('foo', GeneratedFile('foo')) diff --git a/python/mozbuild/mozpack/test/test_unify.py b/python/mozbuild/mozpack/test/test_unify.py index 95ae9088f1..a2bbb44702 100644 --- a/python/mozbuild/mozpack/test/test_unify.py +++ b/python/mozbuild/mozpack/test/test_unify.py @@ -63,9 +63,19 @@ class TestUnifiedFinder(TestUnified): class TestUnifiedBuildFinder(TestUnified): def test_unified_build_finder(self): + finder = UnifiedBuildFinder(FileFinder(self.tmppath('a')), + FileFinder(self.tmppath('b'))) + + # Test chrome.manifest unification self.create_both('chrome.manifest', 'a\nb\nc\n') self.create_one('a', 'chrome/chrome.manifest', 'a\nb\nc\n') self.create_one('b', 'chrome/chrome.manifest', 'b\nc\na\n') + self.assertEqual(sorted([(f, c.open().read()) for f, c in + finder.find('**/chrome.manifest')]), + [('chrome.manifest', 'a\nb\nc\n'), + ('chrome/chrome.manifest', 'a\nb\nc\n')]) + + # Test buildconfig.html unification self.create_one('a', 'chrome/browser/foo/buildconfig.html', '\n'.join([ '', @@ -84,13 +94,6 @@ class TestUnifiedBuildFinder(TestUnified): '', '', ])) - finder = UnifiedBuildFinder(FileFinder(self.tmppath('a')), - FileFinder(self.tmppath('b'))) - self.assertEqual(sorted([(f, c.open().read()) for f, c in - finder.find('**/chrome.manifest')]), - [('chrome.manifest', 'a\nb\nc\n'), - ('chrome/chrome.manifest', 'a\nb\nc\n')]) - self.assertEqual(sorted([(f, c.open().read()) for f, c in finder.find('**/buildconfig.html')]), [('chrome/browser/foo/buildconfig.html', '\n'.join([ @@ -104,6 +107,7 @@ class TestUnifiedBuildFinder(TestUnified): '', ]))]) + # Test xpi file unification xpi = MockDest() with JarWriter(fileobj=xpi, compress=True) as jar: jar.add('foo', 'foo') @@ -123,6 +127,73 @@ class TestUnifiedBuildFinder(TestUnified): [('foo.xpi', foo_xpi)]) errors.out = sys.stderr + # Test install.rdf unification + x86_64 = 'Darwin_x86_64-gcc3' + x86 = 'Darwin_x86-gcc3' + target_tag = '<{em}targetPlatform>{platform}' + target_attr = '{em}targetPlatform="{platform}" ' + + rdf_tag = ''.join([ + '<{RDF}Description {em}bar="bar" {em}qux="qux">', + '<{em}foo>foo', + '{targets}', + '<{em}baz>baz', + '' + ]) + rdf_attr = ''.join([ + '<{RDF}Description {em}bar="bar" {attr}{em}qux="qux">', + '{targets}', + '<{em}foo>foo<{em}baz>baz', + '' + ]) + + for descr_ns, target_ns in (('RDF:', ''), ('', 'em:'), ('RDF:', 'em:')): + # First we need to infuse the above strings with our namespaces and + # platform values. + ns = { 'RDF': descr_ns, 'em': target_ns } + target_tag_x86_64 = target_tag.format(platform=x86_64, **ns) + target_tag_x86 = target_tag.format(platform=x86, **ns) + target_attr_x86_64 = target_attr.format(platform=x86_64, **ns) + target_attr_x86 = target_attr.format(platform=x86, **ns) + + tag_x86_64 = rdf_tag.format(targets=target_tag_x86_64, **ns) + tag_x86 = rdf_tag.format(targets=target_tag_x86, **ns) + tag_merged = rdf_tag.format(targets=target_tag_x86_64 + target_tag_x86, **ns) + tag_empty = rdf_tag.format(targets="", **ns) + + attr_x86_64 = rdf_attr.format(attr=target_attr_x86_64, targets="", **ns) + attr_x86 = rdf_attr.format(attr=target_attr_x86, targets="", **ns) + attr_merged = rdf_attr.format(attr="", targets=target_tag_x86_64 + target_tag_x86, **ns) + + # This table defines the test cases, columns "a" and "b" being the + # contents of the install.rdf of the respective platform and + # "result" the exepected merged content after unification. + testcases = ( + #_____a_____ _____b_____ ___result___# + (tag_x86_64, tag_x86, tag_merged ), + (tag_x86_64, tag_empty, tag_empty ), + (tag_empty, tag_x86, tag_empty ), + (tag_empty, tag_empty, tag_empty ), + + (attr_x86_64, attr_x86, attr_merged ), + (tag_x86_64, attr_x86, tag_merged ), + (attr_x86_64, tag_x86, attr_merged ), + + (attr_x86_64, tag_empty, tag_empty ), + (tag_empty, attr_x86, tag_empty ) + ) + + # Now create the files from the above table and compare + results = [] + for emid, (rdf_a, rdf_b, result) in enumerate(testcases): + filename = 'ext/id{0}/install.rdf'.format(emid) + self.create_one('a', filename, rdf_a) + self.create_one('b', filename, rdf_b) + results.append((filename, result)) + + self.assertEqual(sorted([(f, c.open().read()) for f, c in + finder.find('**/install.rdf')]), results) + if __name__ == '__main__': mozunit.main() diff --git a/python/mozbuild/mozpack/unify.py b/python/mozbuild/mozpack/unify.py index 53e1859285..2519beb9ce 100644 --- a/python/mozbuild/mozpack/unify.py +++ b/python/mozbuild/mozpack/unify.py @@ -20,9 +20,23 @@ from tempfile import mkstemp import mozpack.path as mozpath import struct import os +import re import subprocess from collections import OrderedDict +# Regular expressions for unifying install.rdf +FIND_TARGET_PLATFORM = re.compile(r""" + <(?P[-._0-9A-Za-z]+:)?targetPlatform> # The targetPlatform tag, with any namespace + (?P[^<]*) # The actual platform value + # The closing tag + """, re.X) +FIND_TARGET_PLATFORM_ATTR = re.compile(r""" + (?P<(?:[-._0-9A-Za-z]+:)?Description) # The opening part of the tag + (?P[^>]*?)\s+ # The initial attributes + (?P[-._0-9A-Za-z]+:)?targetPlatform= # The targetPlatform attribute, with any namespace + [\'"](?P[^\'"]+)[\'"] # The actual platform value + (?P[^>]*?>) # The remaining attributes and closing angle bracket + """, re.X) def may_unify_binary(file): ''' @@ -167,9 +181,11 @@ class UnifiedBuildFinder(UnifiedFinder): def unify_file(self, path, file1, file2): ''' - Unify buildconfig.html contents, or defer to UnifiedFinder.unify_file. + Unify files taking Mozilla application special cases into account. + Otherwise defer to UnifiedFinder.unify_file. ''' - if mozpath.basename(path) == 'buildconfig.html': + basename = mozpath.basename(path) + if basename == 'buildconfig.html': content1 = file1.open().readlines() content2 = file2.open().readlines() # Copy everything from the first file up to the end of its , @@ -180,7 +196,26 @@ class UnifiedBuildFinder(UnifiedFinder): ['
\n'] + content2[content2.index('

about:buildconfig

\n') + 1:] )) - if path.endswith('.xpi'): + elif basename == 'install.rdf': + # install.rdf files often have em:targetPlatform (either as + # attribute or as tag) that will differ between platforms. The + # unified install.rdf should contain both em:targetPlatforms if + # they exist, or strip them if only one file has a target platform. + content1, content2 = ( + FIND_TARGET_PLATFORM_ATTR.sub(lambda m: \ + m.group('tag') + m.group('attrs') + m.group('otherattrs') + + '<%stargetPlatform>%s' % \ + (m.group('ns') or "", m.group('platform'), m.group('ns') or ""), + f.open().read() + ) for f in (file1, file2) + ) + + platform2 = FIND_TARGET_PLATFORM.search(content2) + return GeneratedFile(FIND_TARGET_PLATFORM.sub( + lambda m: m.group(0) + platform2.group(0) if platform2 else '', + content1 + )) + elif path.endswith('.xpi'): finder1 = JarFinder(os.path.join(self._finder1.base, path), JarReader(fileobj=file1.open())) finder2 = JarFinder(os.path.join(self._finder2.base, path), diff --git a/xpcom/build/XPCOMInit.cpp b/xpcom/build/XPCOMInit.cpp index 20701681e1..59db86551c 100644 --- a/xpcom/build/XPCOMInit.cpp +++ b/xpcom/build/XPCOMInit.cpp @@ -154,6 +154,7 @@ extern nsresult nsStringInputStreamConstructor(nsISupports*, REFNSIID, void**); #include "GeckoProfiler.h" #include "jsapi.h" +#include "js/Initialization.h" #include "gfxPlatform.h"