diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/moz.configure /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/moz.configure --- a/js/moz.configure 2026-07-28 16:38:22.629291810 +0200 +++ b/js/moz.configure 2026-07-28 16:51:42.693857969 +0200 @@ -243,6 +243,7 @@ def jit_default(target, enable_portable_ "aarch64", "mips64", "loongarch64", + "ppc", "ppc64", "riscv64", ): @@ -265,7 +266,7 @@ def report_deprecated(value): # ======================================================= option( "--enable-simulator", - choices=("arm", "arm64", "mips64", "loong64", "riscv64", "ppc64"), + choices=("arm", "arm64", "mips64", "loong64", "riscv64", "ppc", "ppc64"), nargs=1, help="Enable a JIT code simulator for the specified architecture", ) @@ -278,7 +279,7 @@ def simulator(jit_enabled, simulator_ena sim_cpu = simulator_enabled[0] - if sim_cpu in ("arm"): + if sim_cpu in ("arm", "ppc"): if target.cpu != "x86": die("The %s simulator only works on x86." % sim_cpu) @@ -295,6 +296,7 @@ set_config("JS_SIMULATOR_ARM64", simulat set_config("JS_SIMULATOR_MIPS64", simulator.mips64) set_config("JS_SIMULATOR_LOONG64", simulator.loong64) set_config("JS_SIMULATOR_RISCV64", simulator.riscv64) +set_config("JS_SIMULATOR_PPC", simulator.ppc) set_config("JS_SIMULATOR_PPC64", simulator.ppc64) set_define("JS_SIMULATOR", depends_if(simulator)(lambda x: True)) set_define("JS_SIMULATOR_ARM", simulator.arm) @@ -302,6 +304,7 @@ set_define("JS_SIMULATOR_ARM64", simulat set_define("JS_SIMULATOR_MIPS64", simulator.mips64) set_define("JS_SIMULATOR_LOONG64", simulator.loong64) set_define("JS_SIMULATOR_RISCV64", simulator.riscv64) +set_define("JS_SIMULATOR_PPC", simulator.ppc) set_define("JS_SIMULATOR_PPC64", simulator.ppc64) @@ -319,6 +322,8 @@ def jit_codegen(jit_enabled, simulator, return namespace(x64=True) elif target.cpu == "loongarch64": return namespace(loong64=True) + elif target.cpu == "ppc": + return namespace(ppc=True) elif target.cpu == "ppc64": return namespace(ppc64=True) elif target.cpu == "riscv64": @@ -332,6 +337,7 @@ set_config("JS_CODEGEN_ARM", jit_codegen set_config("JS_CODEGEN_ARM64", jit_codegen.arm64) set_config("JS_CODEGEN_MIPS64", jit_codegen.mips64) set_config("JS_CODEGEN_LOONG64", jit_codegen.loong64) +set_config("JS_CODEGEN_PPC", jit_codegen.ppc) set_config("JS_CODEGEN_PPC64", jit_codegen.ppc64) set_config("JS_CODEGEN_RISCV64", jit_codegen.riscv64) set_config("JS_CODEGEN_X86", jit_codegen.x86) @@ -343,6 +349,7 @@ set_define("JS_CODEGEN_ARM", jit_codegen set_define("JS_CODEGEN_ARM64", jit_codegen.arm64) set_define("JS_CODEGEN_MIPS64", jit_codegen.mips64) set_define("JS_CODEGEN_LOONG64", jit_codegen.loong64) +set_define("JS_CODEGEN_PPC", jit_codegen.ppc) set_define("JS_CODEGEN_PPC64", jit_codegen.ppc64) set_define("JS_CODEGEN_RISCV64", jit_codegen.riscv64) set_define("JS_CODEGEN_X86", jit_codegen.x86) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/build-tf.log /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/build-tf.log --- a/js/src/build-tf.log 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/build-tf.log 2026-07-29 11:43:49.088951651 +0200 @@ -0,0 +1 @@ +scripts/Build-Pkg: line 23: ./jsbuild.sh: No such file or directory diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/builtin/TestingFunctions.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/builtin/TestingFunctions.cpp --- a/js/src/builtin/TestingFunctions.cpp 2026-07-28 16:38:22.649440867 +0200 +++ b/js/src/builtin/TestingFunctions.cpp 2026-07-29 11:44:30.690589076 +0200 @@ -57,6 +57,7 @@ #include "gc/GCEnum.h" #include "gc/GCLock.h" #include "gc/Zone.h" +#include "jit/AtomicOperations.h" #include "jit/BaselineJIT.h" #include "jit/CacheIRSpewer.h" #include "jit/Disassemble.h" @@ -448,6 +449,15 @@ static bool GetBuildConfiguration(JSCont return false; } +#ifdef JS_CODEGEN_PPC + value = BooleanValue(true); +#else + value = BooleanValue(false); +#endif + if (!JS_SetProperty(cx, info, "ppc", value)) { + return false; + } + #ifdef JS_CODEGEN_PPC64 value = BooleanValue(true); #else @@ -493,6 +503,15 @@ static bool GetBuildConfiguration(JSCont return false; } +#ifdef JS_SIMULATOR_PPC + value = BooleanValue(true); +#else + value = BooleanValue(false); +#endif + if (!JS_SetProperty(cx, info, "ppc-simulator", value)) { + return false; + } + #ifdef JS_SIMULATOR_PPC64 value = BooleanValue(true); #else @@ -1103,7 +1122,9 @@ static bool GetWasmSupportedFeatures(JSC return false; } - value.setBoolean(true); + // Threads need lock-free 8-byte atomics, which not every target has; the + // 32-bit PowerPC 603 has lwarx/stwcx. but no ldarx/stdcx. + value.setBoolean(jit::AtomicOperations::isLockfree8()); if (!JS_SetProperty(cx, features, "threads", value)) { return false; } diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/irregexp/RegExpNativeMacroAssembler.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/irregexp/RegExpNativeMacroAssembler.cpp --- a/js/src/irregexp/RegExpNativeMacroAssembler.cpp 2026-07-28 16:38:22.649987822 +0200 +++ b/js/src/irregexp/RegExpNativeMacroAssembler.cpp 2026-07-28 16:57:40.905337060 +0200 @@ -1014,6 +1014,8 @@ void SMRegExpMacroAssembler::CheckBacktr // calling or the regexp's return address will be clobbered. masm_.xs_mflr(temp1_); masm_.as_stdu(temp1_, masm_.getStackPointer(), -8); +#elif defined(JS_CODEGEN_PPC) + masm_.pushReturnAddress(); #endif masm_.call(&stack_overflow_label_); @@ -1022,6 +1024,8 @@ void SMRegExpMacroAssembler::CheckBacktr masm_.as_ld(temp1_, masm_.getStackPointer(), 0); masm_.xs_mtlr(temp1_); masm_.as_addi(masm_.getStackPointer(), masm_.getStackPointer(), 8); +#elif defined(JS_CODEGEN_PPC) + masm_.popReturnAddress(); #endif // Exit with an exception if the call failed @@ -1112,8 +1116,8 @@ void SMRegExpMacroAssembler::createStack masm_.initPseudoStackPtr(); #endif -#ifdef JS_CODEGEN_PPC64 - // PPC64's link register is an SPR, not a GPR, so it cannot be included in +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) + // PowerPC's link register is an SPR, not a GPR, so it cannot be included in // SavedNonVolatileRegisters. Save it explicitly before the frame pointer // so that abiret()'s blr can return to the caller after we restore it. masm_.pushReturnAddress(); @@ -1347,7 +1351,7 @@ void SMRegExpMacroAssembler::exitHandler // Perform a plain Ret(), as abiret() will move SP <- PSP and that is wrong. masm_.Ret(vixl::lr); #else -# ifdef JS_CODEGEN_PPC64 +# if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) masm_.popReturnAddress(); # endif masm_.abiret(); @@ -1393,7 +1397,7 @@ void SMRegExpMacroAssembler::stackOverfl // Adjust for the return address on the stack. size_t frameOffset = sizeof(void*); -#ifdef JS_CODEGEN_PPC64 +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) // CheckBacktrackStackLimit pushes LR before calling us, so there's a // second return address on the stack. frameOffset += sizeof(void*); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ABIFunctionList-inl.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ABIFunctionList-inl.h --- a/js/src/jit/ABIFunctionList-inl.h 2026-07-15 22:44:59.000000000 +0200 +++ b/js/src/jit/ABIFunctionList-inl.h 2026-07-28 23:52:17.035495381 +0200 @@ -158,6 +158,7 @@ namespace jit { _(js::jit::GetInt32FromStringPure) \ _(js::jit::GetNativeDataPropertyPure) \ _(js::jit::GetNativeDataPropertyPureWithCacheLookup) \ + _(js::jit::GetNativeDataPropertyPureRaw) \ _(js::jit::GetNativeDataPropertyByValuePure) \ _(js::jit::GlobalHasLiveOnDebuggerStatement) \ _(js::jit::HandleCodeCoverageAtPC) \ @@ -172,6 +173,7 @@ namespace jit { _(js::jit::LazyLinkTopActivation) \ _(js::jit::LinearizeForCharAccessPure) \ _(js::jit::ObjectHasGetterSetterPure) \ + _(js::jit::ObjectHasGetterSetterPureRaw) \ _(js::jit::ObjectIsCallable) \ _(js::jit::ObjectIsConstructor) \ _(js::jit::PostGlobalWriteBarrier) \ @@ -180,11 +182,13 @@ namespace jit { _(js::jit::PreserveWrapper) \ _(js::jit::Printf0) \ _(js::jit::Printf1) \ + _(js::jit::ProxyGetPropertyRaw) \ _(js::jit::StringFromCharCodeNoGC) \ _(js::jit::StringTrimEndIndex) \ _(js::jit::StringTrimStartIndex) \ _(js::jit::TypeOfNameObject) \ _(js::jit::TypeOfEqObject) \ + _(js::jit::TypeOfEqObjectRaw) \ _(js::jit::WeakMapValueReadBarrier) \ _(js::jit::WrapObjectPure) \ ABIFUNCTION_FUZZILLI_LIST(_) \ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ABIFunctions.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ABIFunctions.h --- a/js/src/jit/ABIFunctions.h 2026-07-15 22:09:44.000000000 +0200 +++ b/js/src/jit/ABIFunctions.h 2026-07-29 08:09:14.699632331 +0200 @@ -5,8 +5,15 @@ #ifndef jit_ABIFunctions_h #define jit_ABIFunctions_h +#include + #include "jstypes.h" // JS_FUNC_TO_DATA_PTR +#ifdef JS_CODEGEN_PPC +// JSJitGetterOp, JSJitSetterOp, JSJitMethodOp +# include "js/experimental/JitInfo.h" +#endif + struct JS_PUBLIC_API JSContext; namespace JS { @@ -16,6 +23,40 @@ class JS_PUBLIC_API Value; namespace js { namespace jit { +#ifdef JS_CODEGEN_PPC +// The 32-bit PowerPC SysV ABI passes class, struct and union arguments by +// invisible reference: the callee dereferences the argument register. JIT code +// has no caller copy to point at, so such a signature cannot be called with +// callWithABI(). Reject it at build time rather than crashing in the callee. +template +struct ABIArgsAreScalar : std::false_type {}; + +template +struct ABIArgsAreScalar + : std::bool_constant<(... && + !(std::is_class_v || std::is_union_v))> {}; + +// Opt-out for the few signatures whose aggregate arguments the caller +// materializes on the stack and passes by explicit reference itself. The DOM +// accessor and method ops are handled that way in +// CodeGenerator::visitGetDOMProperty(), visitSetDOMProperty() and +// visitCallDOMNative(). +template +struct ABIArgsPassedByExplicitReference : std::false_type {}; + +template <> +struct ABIArgsPassedByExplicitReference : std::true_type {}; +template <> +struct ABIArgsPassedByExplicitReference : std::true_type {}; +template <> +struct ABIArgsPassedByExplicitReference : std::true_type {}; + +template +struct ABIArgsAreCallable + : std::bool_constant::value || + ABIArgsPassedByExplicitReference::value> {}; +#endif + // This class is used to ensure that all known targets of callWithABI are // registered here. Otherwise, this would raise a static assertion at compile // time. @@ -28,6 +69,12 @@ template struct ABIFunction { void* address() const { return JS_FUNC_TO_DATA_PTR(void*, fun); } +#ifdef JS_CODEGEN_PPC + static_assert(ABIArgsAreScalar::value, + "ppc32 SysV passes aggregates by invisible reference, so " + "callWithABI() cannot hand this callee one by value."); +#endif + // If this assertion fails, you are likely in the context of a // `callWithABI()` call. This error indicates that ABIFunction has // not been specialized for `` by the time of this call. @@ -49,6 +96,12 @@ template struct ABIFunctionSignature { void* address(Sig fun) const { return JS_FUNC_TO_DATA_PTR(void*, fun); } +#ifdef JS_CODEGEN_PPC + static_assert(ABIArgsAreCallable::value, + "ppc32 SysV passes aggregates by invisible reference, so " + "callWithABI() cannot hand this callee one by value."); +#endif + // If this assertion fails, you are likely in the context of a // `DynamicFunction(fn)` call. This error indicates that // ABIFunctionSignature has not been specialized for `Sig` by the time of this diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/Assembler.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/Assembler.h --- a/js/src/jit/Assembler.h 2026-07-28 16:38:22.631630827 +0200 +++ b/js/src/jit/Assembler.h 2026-07-28 16:52:29.010991302 +0200 @@ -19,6 +19,8 @@ # include "jit/loong64/Assembler-loong64.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/Assembler-riscv64.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/Assembler-ppc.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/Assembler-ppc64.h" #elif defined(JS_CODEGEN_WASM32) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/BaselineCacheIRCompiler.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/BaselineCacheIRCompiler.cpp --- a/js/src/jit/BaselineCacheIRCompiler.cpp 2026-07-28 16:38:22.652102338 +0200 +++ b/js/src/jit/BaselineCacheIRCompiler.cpp 2026-07-28 21:09:52.384203770 +0200 @@ -186,12 +186,15 @@ Address BaselineCacheIRCompiler::stubAdd return Address(ICStubReg, stubDataOffset_ + offset); } -// RawInt32 stub fields are word-sized (stored via asWord), so their 32-bit -// payload occupies the high-addressed half of the uintptr_t slot on big-endian. -// A 32-bit load of such a field must therefore skip the tag word. No-op on LE. +// RawInt32 stub fields are word-sized (stored via asWord), so on a 64-bit +// big-endian target their 32-bit payload occupies the high-addressed half of +// the uintptr_t slot and a 32-bit load has to skip the other half. On 32-bit +// the slot is exactly the value, and on little-endian this is a no-op. static Address Int32StubFieldAddr(Address addr) { #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - addr.offset += sizeof(int32_t); + if constexpr (sizeof(uintptr_t) > sizeof(int32_t)) { + addr.offset += sizeof(uintptr_t) - sizeof(int32_t); + } #endif return addr; } @@ -3005,7 +3008,9 @@ bool BaselineCacheIRCompiler::emitCallNa } else { // This depends on the native function pointer being stored unchanged as // a PrivateValue. - masm.callWithABI(Address(calleeReg, JSFunction::offsetOfNativeOrEnv())); + masm.callWithABI(Address( + calleeReg, + JSFunction::offsetOfNativeOrEnv() + PrivateValuePayloadOffset)); } #endif } break; diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/BaselineCodeGen.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/BaselineCodeGen.cpp --- a/js/src/jit/BaselineCodeGen.cpp 2026-07-28 16:38:22.652454985 +0200 +++ b/js/src/jit/BaselineCodeGen.cpp 2026-07-28 20:36:26.313187974 +0200 @@ -546,12 +546,24 @@ static void LoadInlineValueOperand(Macro // platforms having appropriate support for unaligned accesses (except for // floating point instructions on ARM). Register pc = LoadBytecodePC(masm, dest.scratchReg()); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // The inline Value is the raw 64-bit representation stored little-endian in + // the bytecode (see GET_INLINE_VALUE), so byte-reverse it to native order. +# ifdef JS_PUNBOX64 masm.loadUnalignedValue(Address(pc, sizeof(jsbytecode)), dest); -#if defined(JS_PUNBOX64) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - // The inline Value is stored little-endian in the bytecode (see - // GET_INLINE_VALUE), so byte-reverse it to native order on big-endian. masm.byteSwap64(Register64(dest.valueReg())); +# else + // Under nunbox32 the low half of those raw bits is the payload and the high + // half is the tag, so the two words also swap places. Load the type first: + // the payload register may alias |pc|. + masm.load32(Address(pc, sizeof(jsbytecode) + sizeof(uint32_t)), + dest.typeReg()); + masm.load32(Address(pc, sizeof(jsbytecode)), dest.payloadReg()); + masm.byteSwap32(dest.typeReg()); + masm.byteSwap32(dest.payloadReg()); +# endif +#else + masm.loadUnalignedValue(Address(pc, sizeof(jsbytecode)), dest); #endif } diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/BaselineIC.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/BaselineIC.cpp --- a/js/src/jit/BaselineIC.cpp 2026-07-28 16:38:22.631746412 +0200 +++ b/js/src/jit/BaselineIC.cpp 2026-07-28 16:57:40.905830342 +0200 @@ -120,7 +120,7 @@ AllocatableGeneralRegisterSet BaselineIC MOZ_ASSERT(!regs.has(PseudoStackPointer)); MOZ_ASSERT(!regs.has(RealStackPointer)); MOZ_ASSERT(!regs.has(ICTailCallReg)); -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) regs.take(ICTailCallReg); #endif regs.take(ICStubReg); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/CacheIR.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/CacheIR.cpp --- a/js/src/jit/CacheIR.cpp 2026-07-28 16:38:22.653354936 +0200 +++ b/js/src/jit/CacheIR.cpp 2026-07-29 07:42:12.185622952 +0200 @@ -20,6 +20,7 @@ #include "builtin/WeakMapObject.h" #include "builtin/WeakSetObject.h" #include "gc/GC.h" +#include "jit/AtomicOperations.h" #include "jit/BaselineIC.h" #include "jit/CacheIRCloner.h" #include "jit/CacheIRCompiler.h" @@ -9613,8 +9614,16 @@ static bool AtomicsMeetsPreconditions(Ty case Scalar::Uint16: case Scalar::Int32: case Scalar::Uint32: + break; + case Scalar::BigInt64: case Scalar::BigUint64: + // Inlined 64-bit atomics are only compatible with the C++ ones if the + // latter are lock-free; otherwise every 8-byte access has to take the + // same lock, so leave these to the VM. + if (!AtomicOperations::isLockfree8()) { + return false; + } break; case Scalar::Float16: diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/CacheIRCompiler.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/CacheIRCompiler.cpp --- a/js/src/jit/CacheIRCompiler.cpp 2026-07-28 16:38:22.654109450 +0200 +++ b/js/src/jit/CacheIRCompiler.cpp 2026-07-29 08:06:01.766253136 +0200 @@ -4562,7 +4562,7 @@ bool CacheIRCompiler::emitGuardBoundFunc return false; } - Address flagsSlot(obj, BoundFunctionObject::offsetOfFlagsSlot()); + Address flagsSlot(obj, BoundFunctionObject::offsetOfFlagsSlotForJit32()); masm.branchTest32(Assembler::Zero, flagsSlot, Imm32(BoundFunctionObject::IsConstructorFlag), failure->label()); @@ -7674,7 +7674,7 @@ bool CacheIRCompiler::emitStoreTypedArra handleOOB ? &done : failure->label()); // Load the elements vector. - masm.loadPtr(Address(obj, ArrayBufferViewObject::dataOffset()), scratch1); + masm.loadPrivate(Address(obj, ArrayBufferViewObject::dataOffset()), scratch1); BaseIndex dest(scratch1, index, ScaleFromScalarType(elementType)); @@ -7807,7 +7807,7 @@ bool CacheIRCompiler::emitLoadTypedArray } // Load the elements vector. - masm.loadPtr(Address(obj, ArrayBufferViewObject::dataOffset()), scratch1); + masm.loadPrivate(Address(obj, ArrayBufferViewObject::dataOffset()), scratch1); // Load the value. BaseIndex source(scratch1, index, ScaleFromScalarType(elementType)); @@ -7945,7 +7945,7 @@ bool CacheIRCompiler::emitLoadDataViewVa emitDataViewBoundsCheck(viewKind, byteSize, obj, offset, outputScratch, scratch2, failure->label()); - masm.loadPtr(Address(obj, DataViewObject::dataOffset()), outputScratch); + masm.loadPrivate(Address(obj, DataViewObject::dataOffset()), outputScratch); // Load the value. BaseIndex source(outputScratch, offset, TimesOne); @@ -8192,7 +8192,7 @@ bool CacheIRCompiler::emitStoreDataViewV emitDataViewBoundsCheck(viewKind, byteSize, obj, offset, scratch1, boundsCheckScratch, failure->label()); - masm.loadPtr(Address(obj, DataViewObject::dataOffset()), scratch1); + masm.loadPrivate(Address(obj, DataViewObject::dataOffset()), scratch1); BaseIndex dest(scratch1, offset, TimesOne); if (byteSize == 1) { @@ -8515,12 +8515,21 @@ bool CacheIRCompiler::emitLoadTypeOfEqOb save.takeUnchecked(scratch); masm.PushRegsInMask(save); +#ifdef JS_CODEGEN_PPC + // ppc32 SysV passes aggregates by invisible reference; see VMFunctions.h. + using Fn = bool (*)(JSObject* obj, uint32_t operand); +#else using Fn = bool (*)(JSObject* obj, TypeofEqOperand operand); +#endif masm.setupUnalignedABICall(scratch); masm.passABIArg(obj); masm.move32(Imm32(TypeofEqOperand(type, compareOp).rawValue()), scratch); masm.passABIArg(scratch); +#ifdef JS_CODEGEN_PPC + masm.callWithABI(); +#else masm.callWithABI(); +#endif masm.storeCallBoolResult(scratch); masm.PopRegsInMask(save); @@ -9633,10 +9642,12 @@ void CacheIRCompiler::emitLoadStubField( break; case StubField::Type::RawInt32: // A RawInt32 occupies the low 32 bits of a uintptr_t-sized stub-data - // slot; on big-endian those bytes are at offset +4 (the high/zero half - // is at +0). Loading +0 there would read zero. No-op on little-endian. + // slot. On a 64-bit big-endian target those bytes are at offset +4; + // on 32-bit the slot is exactly the value. No-op on little-endian. #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - masm.load32(Address(load.base, load.offset + 4), dest); + masm.load32(Address(load.base, load.offset + int32_t(sizeof(uintptr_t) - + sizeof(int32_t))), + dest); #else masm.load32(load, dest); #endif @@ -9767,8 +9778,6 @@ bool CacheIRCompiler::emitMegamorphicLoa volatileRegs.takeUnchecked(idReg); masm.PushRegsInMask(volatileRegs); - using Fn = bool (*)(JSContext* cx, JSObject* obj, PropertyKey id, - MegamorphicCache::Entry* cacheEntry, Value* vp); masm.setupUnalignedABICall(scratch1); masm.loadJSContext(scratch1); masm.passABIArg(scratch1); @@ -9779,8 +9788,17 @@ bool CacheIRCompiler::emitMegamorphicLoa masm.passABIArg(idReg); #ifdef JS_CODEGEN_X86 + using Fn = bool (*)(JSContext* cx, JSObject* obj, PropertyKey id, + MegamorphicCache::Entry* cacheEntry, Value* vp); masm.callWithABI(); +#elif defined(JS_CODEGEN_PPC) + // ppc32 SysV passes a PropertyKey by invisible reference; pass the raw bits. + using Fn = bool (*)(JSContext* cx, JSObject* obj, uintptr_t idBits, + MegamorphicCache::Entry* cacheEntry, Value* vp); + masm.callWithABI(); #else + using Fn = bool (*)(JSContext* cx, JSObject* obj, PropertyKey id, + MegamorphicCache::Entry* cacheEntry, Value* vp); masm.callWithABI(); #endif @@ -9927,8 +9945,6 @@ bool CacheIRCompiler::emitGuardHasGetter masm.unboxNonDouble(scratch3.get(), scratch3.get().scratchReg(), JSVAL_TYPE_PRIVATE_GCTHING); - using Fn = bool (*)(JSContext* cx, JSObject* obj, jsid id, - GetterSetter* getterSetter); masm.setupUnalignedABICall(scratch1); masm.loadJSContext(scratch1); masm.passABIArg(scratch1); @@ -9936,7 +9952,16 @@ bool CacheIRCompiler::emitGuardHasGetter emitLoadStubField(id, scratch2); masm.passABIArg(scratch2); masm.passABIArg(scratch3.get().scratchReg()); +#ifdef JS_CODEGEN_PPC + // ppc32 SysV passes a jsid by invisible reference; pass the raw bits. + using Fn = bool (*)(JSContext* cx, JSObject* obj, uintptr_t idBits, + GetterSetter* getterSetter); + masm.callWithABI(); +#else + using Fn = bool (*)(JSContext* cx, JSObject* obj, jsid id, + GetterSetter* getterSetter); masm.callWithABI(); +#endif masm.storeCallPointerResult(scratch1); masm.PopRegsInMask(volatileRegs); @@ -10321,8 +10346,8 @@ bool CacheIRCompiler::emitConcatStringsR #endif liveRegs.takeUnchecked(output.valueReg()); -#ifdef JS_CODEGEN_PPC64 - // On PPC64, LR is an SPR, not a GPR, so ICTailCallReg is a regular +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) + // On PowerPC, LR is an SPR, not a GPR, so ICTailCallReg is a regular // GPR that does not shadow LR. The inner bctrl will clobber LR, so // save/restore it explicitly. masm.xs_mflr(r0); @@ -10348,7 +10373,7 @@ bool CacheIRCompiler::emitConcatStringsR masm.branchTestPtr(Assembler::Zero, CallTempReg5, CallTempReg5, &vmCall); masm.tagValue(JSVAL_TYPE_STRING, CallTempReg5, output.valueReg()); masm.PopRegsInMask(liveRegs); -#ifdef JS_CODEGEN_PPC64 +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) masm.pop(r0); masm.xs_mtlr(r0); #endif @@ -10357,7 +10382,7 @@ bool CacheIRCompiler::emitConcatStringsR masm.bind(&vmCall); masm.setFramePushed(framePushed); masm.PopRegsInMask(liveRegs); -#ifdef JS_CODEGEN_PPC64 +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) masm.pop(r0); masm.xs_mtlr(r0); #endif @@ -10974,7 +10999,7 @@ bool CacheIRCompiler::emitAtomicsLoadRes } // Load the elements vector. - masm.loadPtr(Address(obj, ArrayBufferViewObject::dataOffset()), scratch); + masm.loadPrivate(Address(obj, ArrayBufferViewObject::dataOffset()), scratch); // Load the value. BaseIndex source(scratch, index, ScaleFromScalarType(elementType)); @@ -11031,7 +11056,7 @@ bool CacheIRCompiler::emitAtomicsStoreRe if (!Scalar::isBigIntType(elementType)) { // Load the elements vector. - masm.loadPtr(Address(obj, ArrayBufferViewObject::dataOffset()), scratch); + masm.loadPrivate(Address(obj, ArrayBufferViewObject::dataOffset()), scratch); // Store the value. BaseIndex dest(scratch, index, ScaleFromScalarType(elementType)); @@ -12005,7 +12030,7 @@ bool CacheIRCompiler::emitAssertProperty masm.passABIArg(obj); masm.passABIArg(id); masm.passABIArg(slot); - using Fn = void (*)(NativeObject*, PropertyKey, uint32_t); + using Fn = void (*)(NativeObject*, uintptr_t, uint32_t); masm.callWithABI(); masm.PopRegsInMask(save); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/CodeGenerator.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/CodeGenerator.cpp --- a/js/src/jit/CodeGenerator.cpp 2026-07-28 16:38:22.655308025 +0200 +++ b/js/src/jit/CodeGenerator.cpp 2026-07-29 08:06:56.819932043 +0200 @@ -2521,14 +2521,14 @@ static bool PrepareAndExecuteRegExp(Macr masm.computeEffectiveAddress(Address(FramePointer, ioOffset), temp2); masm.PushRegsInMask(volatileRegs); masm.setupUnalignedABICall(temp3); -#if defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) // temp1 aliases argregs on this platform, so we need to reuse temp3 // or we'll stomp on the code pointer when we pass the first ABI argument. masm.movePtr(codePointer, temp3); codePointer = temp3; #endif masm.passABIArg(temp2); -#if defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) // The regexp code pointer is a raw JIT entry, not an ELFv1 function // descriptor, so it must not be called through the descriptor path. masm.callWithABIJitCode(codePointer); @@ -2964,8 +2964,9 @@ static JitCode* GenerateRegExpMatchStubS ImmWord(0), &oolEntry); // Similarly, if the |hasIndices| flag is set, fall back to the OOL stub. - masm.branchTest32(Assembler::NonZero, - Address(shared, RegExpShared::offsetOfFlags()), + // RegExpShared::flags is a single byte, so it must be loaded as such. + masm.load8ZeroExtend(Address(shared, RegExpShared::offsetOfFlags()), temp3); + masm.branchTest32(Assembler::NonZero, temp3, Imm32(int32_t(JS::RegExpFlag::HasIndices)), &oolEntry); Address pairCountAddress = RegExpPairCountAddress(); @@ -4866,8 +4867,14 @@ void CodeGenerator::visitMegamorphicLoad masm.Push(UndefinedValue()); masm.moveStackPtrTo(temp3); +#ifdef JS_CODEGEN_PPC + // ppc32 SysV passes a PropertyKey by invisible reference; pass the raw bits. + using Fn = bool (*)(JSContext* cx, JSObject* obj, uintptr_t idBits, + MegamorphicCache::Entry* cacheEntry, Value* vp); +#else using Fn = bool (*)(JSContext* cx, JSObject* obj, PropertyKey id, MegamorphicCache::Entry* cacheEntry, Value* vp); +#endif masm.setupAlignedABICall(); masm.loadJSContext(temp0); masm.passABIArg(temp0); @@ -4877,7 +4884,11 @@ void CodeGenerator::visitMegamorphicLoad masm.passABIArg(temp2); masm.passABIArg(temp3); +#ifdef JS_CODEGEN_PPC + masm.callWithABI(); +#else masm.callWithABI(); +#endif MOZ_ASSERT(!output.aliases(ReturnReg)); masm.Pop(output); @@ -5700,7 +5711,9 @@ void CodeGenerator::visitGuardFunctionKi void CodeGenerator::visitGuardFunctionScript(LGuardFunctionScript* lir) { Register function = ToRegister(lir->function()); - Address scriptAddr(function, JSFunction::offsetOfJitInfoOrScript()); + // The slot holds a PrivateValue, so read the payload half. + Address scriptAddr(function, JSFunction::offsetOfJitInfoOrScript() + + PrivateValuePayloadOffset); bailoutCmpPtr(Assembler::NotEqual, scriptAddr, ImmGCPtr(lir->mir()->expected()), lir->snapshot()); } @@ -6351,10 +6364,12 @@ void CodeGenerator::visitCallDOMNative(L // Push argc from the call instruction into what will become the IonExitFrame. // This word is read both as a uintptr_t (IonDOMMethodExitFrameLayout::argc(), // for GC tracing) and as a uint32 (JSJitMethodCallArgs::argc_, by the native). - // On big-endian a uint32 read of a word slot sees the high half, so store argc - // in the high 32 bits; argc() reads it back shifted. + // On big-endian a uint32 read of a word slot sees the high-addressed half, so + // store argc there; argc() reads it back shifted. The shift is zero when a + // word is already 32 bits. #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - masm.Push(ImmWord(uintptr_t(call->numActualArgs()) << 32)); + masm.Push(ImmWord(uintptr_t(call->numActualArgs()) + << (8 * (sizeof(uintptr_t) - sizeof(uint32_t))))); #else masm.Push(Imm32(call->numActualArgs())); #endif @@ -6392,6 +6407,16 @@ void CodeGenerator::visitCallDOMNative(L markSafepointAt(safepointOffset, call); +#ifdef JS_CODEGEN_PPC + // ppc32 SysV passes Handle by invisible reference: the callee + // expects a pointer to the Handle, not the Handle's contents. Spill a + // scratch copy below the exit frame so the sp-relative result addresses + // stay unchanged. + masm.reserveStack(ABIStackAlignment); + masm.storePtr(argObj, Address(masm.getStackPointer(), 0)); + masm.moveStackPtrTo(argObj); +#endif + // Construct and execute call. masm.setupAlignedABICall(); masm.loadJSContext(argJSContext); @@ -6404,6 +6429,10 @@ void CodeGenerator::visitCallDOMNative(L ABIType::General, CheckUnsafeCallWithABI::DontCheckHasExitFrame); +#ifdef JS_CODEGEN_PPC + masm.freeStack(ABIStackAlignment); +#endif + if (target->jitInfo()->isInfallible) { masm.loadValue(Address(masm.getStackPointer(), IonDOMMethodExitFrameLayout::offsetOfResult()), @@ -6904,7 +6933,8 @@ void JitRuntime::generateIonGenericCallF Label notFunCall; masm.branchPtr(Assembler::NotEqual, - Address(calleeReg, JSFunction::offsetOfNativeOrEnv()), + Address(calleeReg, JSFunction::offsetOfNativeOrEnv() + + PrivateValuePayloadOffset), ImmPtr(js::fun_call), ¬FunCall); // In general, we can implement fun_call by replacing calleeReg with @@ -6965,7 +6995,8 @@ void JitRuntime::generateIonGenericCallB vmCall); Address targetSlot(calleeReg, BoundFunctionObject::offsetOfTargetSlot()); - Address flagsSlot(calleeReg, BoundFunctionObject::offsetOfFlagsSlot()); + Address flagsSlot(calleeReg, + BoundFunctionObject::offsetOfFlagsSlotForJit32()); Address thisSlot(calleeReg, BoundFunctionObject::offsetOfBoundThisSlot()); Address firstInlineArgSlot( calleeReg, BoundFunctionObject::offsetOfFirstInlineBoundArg()); @@ -9902,7 +9933,7 @@ void CodeGenerator::visitGuardBoundFunct Register obj = ToRegister(lir->object()); Label bail; - Address flagsSlot(obj, BoundFunctionObject::offsetOfFlagsSlot()); + Address flagsSlot(obj, BoundFunctionObject::offsetOfFlagsSlotForJit32()); masm.branchTest32(Assembler::Zero, flagsSlot, Imm32(BoundFunctionObject::IsConstructorFlag), &bail); bailoutFrom(&bail, lir->snapshot()); @@ -10843,8 +10874,8 @@ void CodeGenerator::visitWasmLoadSlot(LW if (type == MIRType::Simd128) { MOZ_ASSERT(wideningOp == MWideningOp::None); FaultingCodeOffset fco = masm.loadUnalignedSimd128(addr, dst.fpu()); -# if defined(JS_CODEGEN_PPC64) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# if (defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64)) && \ + defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ // A wasm global cell holds the little-endian image; byte-reverse to the // canonical SIMD register order. masm.byteReverseSimd128(dst.fpu(), dst.fpu()); @@ -10892,8 +10923,8 @@ void CodeGenerator::visitWasmStoreSlot(L #ifdef ENABLE_WASM_SIMD if (type == MIRType::Simd128) { FloatRegister v = src.fpu(); -# if defined(JS_CODEGEN_PPC64) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# if (defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64)) && \ + defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ // A wasm global cell holds the little-endian image; byte-reverse the // canonical register into a scratch (the input may be live) before storing. ScratchSimd128Scope scratch(masm); @@ -11265,7 +11296,7 @@ void CodeGenerator::visitArrayBufferView LArrayBufferViewElements* lir) { Register obj = ToRegister(lir->object()); Register out = ToRegister(lir->output()); - masm.loadPtr(Address(obj, ArrayBufferViewObject::dataOffset()), out); + masm.loadPrivate(Address(obj, ArrayBufferViewObject::dataOffset()), out); } void CodeGenerator::visitTypedArrayElementSize(LTypedArrayElementSize* lir) { @@ -19987,6 +20018,19 @@ void CodeGenerator::visitGetDOMProperty( markSafepointAt(safepointOffset, ins); +#ifdef JS_CODEGEN_PPC + // ppc32 SysV passes Handle and JSJitGetterCallArgs by invisible + // reference: the callee expects pointers to them, not their contents. Spill + // scratch copies below the exit frame so the sp-relative result address + // stays unchanged. + masm.reserveStack(ABIStackAlignment); + masm.storePtr(ObjectReg, Address(masm.getStackPointer(), 0)); + masm.storePtr(ValueReg, Address(masm.getStackPointer(), sizeof(uintptr_t))); + masm.moveStackPtrTo(ObjectReg); + masm.computeEffectiveAddress( + Address(masm.getStackPointer(), sizeof(uintptr_t)), ValueReg); +#endif + masm.setupAlignedABICall(); masm.loadJSContext(JSContextReg); masm.passABIArg(JSContextReg); @@ -19998,6 +20042,10 @@ void CodeGenerator::visitGetDOMProperty( ABIType::General, CheckUnsafeCallWithABI::DontCheckHasExitFrame); +#ifdef JS_CODEGEN_PPC + masm.freeStack(ABIStackAlignment); +#endif + if (ins->mir()->isInfallible()) { masm.loadValue(Address(masm.getStackPointer(), IonDOMExitFrameLayout::offsetOfResult()), @@ -20108,6 +20156,17 @@ void CodeGenerator::visitSetDOMProperty( markSafepointAt(safepointOffset, ins); +#ifdef JS_CODEGEN_PPC + // ppc32 SysV passes Handle and JSJitSetterCallArgs by invisible + // reference: the callee expects pointers to them, not their contents. + masm.reserveStack(ABIStackAlignment); + masm.storePtr(ObjectReg, Address(masm.getStackPointer(), 0)); + masm.storePtr(ValueReg, Address(masm.getStackPointer(), sizeof(uintptr_t))); + masm.moveStackPtrTo(ObjectReg); + masm.computeEffectiveAddress( + Address(masm.getStackPointer(), sizeof(uintptr_t)), ValueReg); +#endif + masm.setupAlignedABICall(); masm.loadJSContext(JSContextReg); masm.passABIArg(JSContextReg); @@ -20119,6 +20178,10 @@ void CodeGenerator::visitSetDOMProperty( ABIType::General, CheckUnsafeCallWithABI::DontCheckHasExitFrame); +#ifdef JS_CODEGEN_PPC + masm.freeStack(ABIStackAlignment); +#endif + masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); // Switch back to the current realm if needed. Note: if the setter threw an @@ -21975,15 +22038,25 @@ void CodeGenerator::visitGuardHasGetterS masm.unboxNonDouble(valueAddr, temp2, JSVAL_TYPE_PRIVATE_GCTHING); } +#ifdef JS_CODEGEN_PPC + // ppc32 SysV passes a jsid by invisible reference; pass the raw bits. + using Fn = bool (*)(JSContext* cx, JSObject* obj, uintptr_t idBits, + GetterSetter* getterSetter); +#else using Fn = bool (*)(JSContext* cx, JSObject* obj, jsid id, GetterSetter* getterSetter); +#endif masm.setupAlignedABICall(); masm.loadJSContext(temp0); masm.passABIArg(temp0); masm.passABIArg(object); masm.passABIArg(temp1); masm.passABIArg(temp2); +#ifdef JS_CODEGEN_PPC + masm.callWithABI(); +#else masm.callWithABI(); +#endif bailoutIfFalseBool(ReturnReg, lir->snapshot()); } diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/CodeGenerator.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/CodeGenerator.h --- a/js/src/jit/CodeGenerator.h 2026-07-28 16:38:22.634385682 +0200 +++ b/js/src/jit/CodeGenerator.h 2026-07-28 16:52:29.011063556 +0200 @@ -23,6 +23,8 @@ # include "jit/loong64/CodeGenerator-loong64.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/CodeGenerator-riscv64.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/CodeGenerator-ppc.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/CodeGenerator-ppc64.h" #elif defined(JS_CODEGEN_WASM32) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/EffectiveAddressAnalysis.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/EffectiveAddressAnalysis.cpp --- a/js/src/jit/EffectiveAddressAnalysis.cpp 2026-07-28 16:38:22.634447565 +0200 +++ b/js/src/jit/EffectiveAddressAnalysis.cpp 2026-07-28 16:58:27.252771609 +0200 @@ -60,7 +60,8 @@ static bool OffsetIsSmallEnough(int32_t // `movn #imm`. arm32 is similar. return imm >= -0xFFFF && imm <= 0xFFFF; #elif defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_PPC) || \ + defined(JS_CODEGEN_PPC64) return imm >= -0xFFF && imm <= 0xFFF; #elif defined(JS_CODEGEN_WASM32) || defined(JS_CODEGEN_NONE) return true; diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ExecutableAllocator.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ExecutableAllocator.cpp --- a/js/src/jit/ExecutableAllocator.cpp 2026-07-28 16:38:22.634561130 +0200 +++ b/js/src/jit/ExecutableAllocator.cpp 2026-07-29 14:21:23.704309968 +0200 @@ -307,12 +307,12 @@ void ExecutableAllocator::poisonCode(JSR } // Make the pools executable again and drop references. On architectures with - // incoherent ICache (PPC64), we must flush to prevent stale instruction + // incoherent ICache (PowerPC), we must flush to prevent stale instruction // execution when code regions are reused after sweeping. for (size_t i = 0; i < ranges.length(); i++) { ExecutablePool* pool = ranges[i].pool; if (pool->isMarked()) { -#ifdef JS_CODEGEN_PPC64 +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) reprotectPool(rt, pool, ProtectionSetting::Executable, MustFlushICache::Yes); #else diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/FlushICache.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/FlushICache.cpp --- a/js/src/jit/FlushICache.cpp 2026-07-28 16:38:22.634624253 +0200 +++ b/js/src/jit/FlushICache.cpp 2026-07-28 16:57:40.908142757 +0200 @@ -14,7 +14,7 @@ #endif #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) # ifdef __linux__ # include diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/FlushICache.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/FlushICache.h --- a/js/src/jit/FlushICache.h 2026-07-28 16:38:22.634658744 +0200 +++ b/js/src/jit/FlushICache.h 2026-07-28 16:57:40.908213581 +0200 @@ -21,7 +21,8 @@ inline void FlushICache(void* code, size } #elif (defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)) || \ defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || \ + defined(JS_CODEGEN_PPC64) // Invalidate the given code range from the icache. This will also flush the // execution context for this core. If this code is to be executed on another @@ -56,9 +57,9 @@ inline bool CanFlushExecutionContextForA inline void FlushExecutionContextForAllThreads() { MOZ_CRASH(); } #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) -// ARM, ARM64, and PPC64 must flush the instruction pipeline of the current +// ARM, ARM64, and PowerPC must flush the instruction pipeline of the current // core before executing newly JIT'ed code. This will remove any stale data from // the pipeline that may have referenced invalidated instructions. // diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/IonCacheIRCompiler.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/IonCacheIRCompiler.cpp --- a/js/src/jit/IonCacheIRCompiler.cpp 2026-07-15 22:44:59.000000000 +0200 +++ b/js/src/jit/IonCacheIRCompiler.cpp 2026-07-28 23:53:42.475262962 +0200 @@ -1339,15 +1339,27 @@ bool IonCacheIRCompiler::emitProxyGetRes masm.enterFakeExitFrame(argJSContext, scratch, ExitFrameType::IonOOLProxy); // Make the call. +#ifdef JS_CODEGEN_PPC + // ppc32 SysV passes a Handle (a class) by invisible reference, adding a + // second level of indirection on top of the Handle's own. Pass the rooted + // slot addresses and rebuild the Handles in the shim. + using Fn = bool (*)(JSContext* cx, JSObject** proxy, jsid* id, Value* vp); +#else using Fn = bool (*)(JSContext* cx, HandleObject proxy, HandleId id, MutableHandleValue vp); +#endif masm.setupUnalignedABICall(scratch); masm.passABIArg(argJSContext); masm.passABIArg(argProxy); masm.passABIArg(argId); masm.passABIArg(argVp); +#ifdef JS_CODEGEN_PPC + masm.callWithABI( + ABIType::General, CheckUnsafeCallWithABI::DontCheckHasExitFrame); +#else masm.callWithABI( ABIType::General, CheckUnsafeCallWithABI::DontCheckHasExitFrame); +#endif // Test for failure. masm.branchIfFalseBool(ReturnReg, masm.exceptionLabel()); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/JitContext.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/JitContext.cpp --- a/js/src/jit/JitContext.cpp 2026-07-28 16:38:22.634765880 +0200 +++ b/js/src/jit/JitContext.cpp 2026-07-28 16:57:40.908239081 +0200 @@ -121,6 +121,10 @@ bool jit::InitializeJit() { RVFlags::Init(); #endif +#ifdef JS_CODEGEN_PPC + PPCFlags::Init(); +#endif + #ifdef JS_CODEGEN_PPC64 PPC64Flags::Init(); #endif diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/JitFrames.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/JitFrames.cpp --- a/js/src/jit/JitFrames.cpp 2026-07-28 16:38:22.656074350 +0200 +++ b/js/src/jit/JitFrames.cpp 2026-07-28 17:02:08.868185196 +0200 @@ -1816,7 +1816,8 @@ Value SnapshotIterator::allocationValue( return DoubleValue(fromRegister(alloc.fpuReg())); case RValueAllocation::FLOAT32_REG: -#if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_RISCV64) +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_RISCV64) return Float32Value( float(fromRegister(alloc.fpuReg().asDouble()))); #else @@ -2622,8 +2623,9 @@ uintptr_t MachineState::read(Register re template T MachineState::read(FloatRegister reg) const { -#if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_RISCV64) - // PPC64/RISCV64 always store FloatRegisters as 64-bit doubles. +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_RISCV64) + // PowerPC/RISCV64 always store FloatRegisters as 64-bit doubles. MOZ_RELEASE_ASSERT(reg.size() >= sizeof(T)); #else MOZ_RELEASE_ASSERT(reg.size() == sizeof(T)); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/JitFrames.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/JitFrames.h --- a/js/src/jit/JitFrames.h 2026-07-28 16:38:22.656172013 +0200 +++ b/js/src/jit/JitFrames.h 2026-07-28 20:36:07.067072260 +0200 @@ -322,7 +322,14 @@ enum class ExceptionResumeKind : int32_t // Data needed to recover from an exception. struct ResumeFromException { -#if defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_PPC) + // As for PPC64 below, but the SysV linkage area is four words and has no + // TOC slot. + void* _ppc_sp_; + void* _ppc_cr_; + void* _ppc_lr_; + void* _ppc_pad_; +#elif defined(JS_CODEGEN_PPC64) // This struct is built on the stack as part of exception returns. Because // it goes right on top of the stack, an ABI-compliant routine can wreck // it, so we implement a minimum Power ISA linkage area (four doublewords). @@ -383,7 +390,8 @@ struct ResumeFromException { } }; -#if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC) || \ + defined(JS_CODEGEN_PPC64) static_assert(sizeof(ResumeFromException) % 16 == 0, "ResumeFromException should be aligned"); #endif @@ -821,9 +829,10 @@ class IonDOMMethodExitFrameLayout { inline JSObject** thisObjAddress() { return &thisObj_; } inline uintptr_t argc() { #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - // visitCallDOMNative stores argc in the high 32 bits of this word so the - // native's uint32 JSJitMethodCallArgs::argc_ overlay reads it on big-endian. - return argc_ >> 32; + // visitCallDOMNative stores argc in the high-addressed half of this word so + // the native's uint32 JSJitMethodCallArgs::argc_ overlay reads it on + // big-endian. The shift is zero when a word is already 32 bits. + return argc_ >> (8 * (sizeof(uintptr_t) - sizeof(uint32_t))); #else return argc_; #endif diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/LIR.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/LIR.cpp --- a/js/src/jit/LIR.cpp 2026-07-28 16:38:22.635063423 +0200 +++ b/js/src/jit/LIR.cpp 2026-07-28 17:02:08.868539341 +0200 @@ -777,9 +777,10 @@ bool LMoveGroup::add(LAllocation from, L // Alignment is not currently required for SIMD on x86/x64/arm64. See also // CodeGeneratorShared::CodeGeneratorShared and in general everywhere // SimdMemoryAignment is used. Likely, alignment requirements will return. -# if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || \ - defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) - // No need for any check on x86/x64/arm64/ppc64. +# if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || \ + defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC) || \ + defined(JS_CODEGEN_PPC64) + // No need for any check on x86/x64/arm64/ppc/ppc64. # else # error "Need to consider SIMD alignment on this target." // The following code may be of use if we need alignment checks on diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/LIR.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/LIR.h --- a/js/src/jit/LIR.h 2026-07-28 16:38:22.635171648 +0200 +++ b/js/src/jit/LIR.h 2026-07-28 16:52:55.345749228 +0200 @@ -200,7 +200,8 @@ class LUse : public LAllocation { static const uint32_t POLICY_BITS = 3; static const uint32_t POLICY_SHIFT = 0; static const uint32_t POLICY_MASK = (1 << POLICY_BITS) - 1; -#if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC) || \ + defined(JS_CODEGEN_PPC64) static const uint32_t REG_BITS = 7; #else static const uint32_t REG_BITS = 6; @@ -622,7 +623,8 @@ class LDefinition { Type type() const { return (Type)((bits_ >> TYPE_SHIFT) & TYPE_MASK); } static bool isFloatRegCompatible(Type type, FloatRegister reg) { -#if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_RISCV64) +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_RISCV64) if (type == FLOAT32 || type == DOUBLE) { return reg.isSingle() || reg.isDouble(); } @@ -2301,6 +2303,8 @@ AnyRegister LAllocation::toAnyRegister() # include "jit/loong64/LIR-loong64.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/LIR-riscv64.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/LIR-ppc.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/LIR-ppc64.h" #elif defined(JS_CODEGEN_MIPS64) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/LIROps.yaml /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/LIROps.yaml --- a/js/src/jit/LIROps.yaml 2026-07-28 16:38:22.635365327 +0200 +++ b/js/src/jit/LIROps.yaml 2026-07-28 17:01:21.003917839 +0200 @@ -1271,7 +1271,7 @@ operands: lhs: Int64 rhs: Int64 -#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM) +#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_PPC) num_temps: 1 #endif defer_init: true @@ -2204,7 +2204,7 @@ oldval: WordSized newval: WordSized # Needs additional temps on LL/SC platforms to extract/insert bits of word. -#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) num_temps: 4 #else num_temps: 1 @@ -2218,7 +2218,7 @@ index: WordSized value: WordSized # Needs additional temps on LL/SC platforms to extract/insert bits of word. -#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) num_temps: 4 #else num_temps: 1 @@ -2232,7 +2232,7 @@ index: WordSized value: WordSized # Needs additional temps on LL/SC platforms to extract/insert bits of word. -#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) num_temps: 4 #else num_temps: 2 @@ -2249,7 +2249,7 @@ # Needs additional temps on LL/SC platforms to extract/insert bits of word. #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) num_temps: 1 -#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) num_temps: 3 #endif mir_op: AtomicTypedArrayElementBinop @@ -3060,7 +3060,7 @@ operands: ptr: WordSized memoryBase: WordSized -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) num_temps: 1 #endif mir_op: true @@ -3072,7 +3072,7 @@ memoryBase: WordSized #ifdef JS_CODEGEN_ARM num_temps: 2 -#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) num_temps: 1 #endif mir_op: WasmLoad @@ -3082,7 +3082,7 @@ ptr: WordSized value: WordSized memoryBase: WordSized -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) num_temps: 1 #endif mir_op: true @@ -3092,7 +3092,7 @@ ptr: WordSized value: Int64 memoryBase: WordSized -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) num_temps: 1 #endif mir_op: WasmStore @@ -3122,7 +3122,7 @@ memoryBase: WordSized #ifdef JS_CODEGEN_X86 num_temps: 1 -#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) # Temp that may be used on LL/SC platforms for extract/insert bits of word. num_temps: 3 #endif @@ -3136,7 +3136,7 @@ memoryBase: WordSized #ifdef JS_CODEGEN_X86 num_temps: 1 -#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) # Temp that may be used on LL/SC platforms for extract/insert bits of word. num_temps: 3 #endif @@ -3148,7 +3148,7 @@ ptr: WordSized value: WordSized memoryBase: WordSized -#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) # Temp that may be used on LL/SC platforms for extract/insert bits of word. num_temps: 3 #elifdef JS_CODEGEN_X86 @@ -3165,7 +3165,7 @@ ptr: WordSized value: WordSized memoryBase: WordSized -#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) # Temp that may be used on LL/SC platforms for extract/insert bits of word. num_temps: 3 #elif defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) @@ -4418,7 +4418,18 @@ mir_op: WasmAtomicExchangeHeap #endif -#ifdef JS_CODEGEN_PPC64 +#ifdef JS_CODEGEN_PPC +- name: BoxFloatingPoint + result_type: BoxedValue + operands: + input: WordSized + arguments: + type: MIRType + num_temps: 1 + extra_name: true +#endif + +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) - name: DivOrModI64 gen_boilerplate: false diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/Label.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/Label.h --- a/js/src/jit/Label.h 2026-07-28 16:38:22.635488542 +0200 +++ b/js/src/jit/Label.h 2026-07-28 17:02:08.868670347 +0200 @@ -23,7 +23,8 @@ struct LabelBase { uint32_t offset_ : 31; #if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || \ + defined(JS_CODEGEN_PPC64) public: #endif static const uint32_t INVALID_OFFSET = 0x7fffffff; // UINT31_MAX. diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/Lowering.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/Lowering.cpp --- a/js/src/jit/Lowering.cpp 2026-07-28 16:38:22.635818357 +0200 +++ b/js/src/jit/Lowering.cpp 2026-07-28 17:02:08.868824644 +0200 @@ -1172,9 +1172,10 @@ void LIRGenerator::visitTest(MTest* test } } -#if defined(ENABLE_WASM_SIMD) && \ - (defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || \ - defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64)) +#if defined(ENABLE_WASM_SIMD) && \ + (defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || \ + defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC) || \ + defined(JS_CODEGEN_PPC64)) // Check if the operand for this test is an any_true/all_true SIMD operation. // If it is, we want to emit an LWasmReduceAndBranchSimd128 node to avoid // generating an intermediate boolean result. diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/Lowering.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/Lowering.h --- a/js/src/jit/Lowering.h 2026-07-28 16:38:22.636012165 +0200 +++ b/js/src/jit/Lowering.h 2026-07-28 16:52:29.011799519 +0200 @@ -23,6 +23,8 @@ # include "jit/loong64/Lowering-loong64.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/Lowering-riscv64.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/Lowering-ppc.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/Lowering-ppc64.h" #elif defined(JS_CODEGEN_WASM32) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/MacroAssembler-inl.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/MacroAssembler-inl.h --- a/js/src/jit/MacroAssembler-inl.h 2026-07-28 16:38:22.656380164 +0200 +++ b/js/src/jit/MacroAssembler-inl.h 2026-07-28 16:52:29.011871362 +0200 @@ -39,6 +39,8 @@ # include "jit/loong64/MacroAssembler-loong64-inl.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/MacroAssembler-riscv64-inl.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/MacroAssembler-ppc-inl.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/MacroAssembler-ppc64-inl.h" #elif defined(JS_CODEGEN_WASM32) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/MacroAssembler.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/MacroAssembler.cpp --- a/js/src/jit/MacroAssembler.cpp 2026-07-28 16:38:22.656758390 +0200 +++ b/js/src/jit/MacroAssembler.cpp 2026-07-29 10:21:26.126630946 +0200 @@ -2290,7 +2290,16 @@ void MacroAssembler::initializeBigInt64( "BigInt inline storage can store at least two digits"); #endif +#if !defined(JS_PUNBOX64) && defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // The digits are a uintptr_t array, so digit 0 is at offset 0 whatever the + // endianness, but store64 places the high word first on a big-endian target. + store32(val.low, Address(bigInt, js::BigInt::offsetOfInlineDigits())); + store32(val.high, Address(bigInt, js::BigInt::offsetOfInlineDigits() + + int32_t(sizeof(BigInt::Digit)))); +#else store64(val, Address(bigInt, js::BigInt::offsetOfInlineDigits())); +#endif bind(&done); } @@ -2799,9 +2808,10 @@ void MacroAssembler::setIsCrossRealmArra branchTestObjIsFunction(Assembler::NotEqual, obj, output, obj, &isFalse); // The function must be the ArrayConstructor native. - branchPtr(Assembler::NotEqual, - Address(obj, JSFunction::offsetOfNativeOrEnv()), - ImmPtr(js::ArrayConstructor), &isFalse); + branchPtr( + Assembler::NotEqual, + Address(obj, JSFunction::offsetOfNativeOrEnv() + PrivateValuePayloadOffset), + ImmPtr(js::ArrayConstructor), &isFalse); move32(Imm32(1), output); jump(&done); @@ -2829,7 +2839,7 @@ void MacroAssembler::setIsDefinitelyType branchTestObjIsFunction(Assembler::NotEqual, obj, output, obj, &isFalse); // Load the native into |output|. - loadPtr(Address(obj, JSFunction::offsetOfNativeOrEnv()), output); + loadPrivate(Address(obj, JSFunction::offsetOfNativeOrEnv()), output); auto branchIsTypedArrayCtor = [&](Scalar::Type type) { // The function must be a TypedArrayConstructor native (from any realm). @@ -3261,7 +3271,9 @@ void MacroAssembler::extractCurrentIndex Label iterActive; branchTest32(Assembler::NonZero, Address(outIndex, NativeIterator::offsetOfFlags()), - Imm32(NativeIterator::Flags::Active), &iterActive); + Imm32(NativeIterator::flagForJit32( + NativeIterator::Flags::Active)), + &iterActive); assumeUnreachable("iterator-index fast path on an inactive iterator"); bind(&iterActive); #endif @@ -6163,7 +6175,8 @@ static void MoveDataBlock(MacroAssembler static constexpr Register scratch = ABINonArgReg0; masm.push(scratch); #elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) || \ + defined(JS_CODEGEN_PPC64) UseScratchRegisterScope temps(masm); Register scratch = temps.Acquire(); #elif !defined(JS_CODEGEN_NONE) @@ -6349,9 +6362,9 @@ static void CollapseWasmFrameFast(MacroA #ifdef JS_USE_LINK_REGISTER // RA is already in its place, just move stack. -# ifdef JS_CODEGEN_PPC64 - // PPC64's LR is not a GPR, so WasmTailCallRAScratchReg is a normal GPR - // (r14). We must explicitly move it to LR so the callee's prologue +# if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) + // PowerPC's LR is not a GPR, so WasmTailCallRAScratchReg is a normal GPR. + // We must explicitly move it to LR so the callee's prologue // (pushReturnAddress) saves the correct return address. masm.xs_mtlr(tempForRA); # endif @@ -6503,9 +6516,9 @@ static void CollapseWasmFrameSlow(MacroA #ifdef JS_USE_LINK_REGISTER masm.freeStack(reserved); // RA is already in its place, just move stack. -# ifdef JS_CODEGEN_PPC64 - // PPC64's LR is not a GPR, so WasmTailCallRAScratchReg is a normal GPR - // (r14). We must explicitly move the trampoline address to LR so the +# if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) + // PowerPC's LR is not a GPR, so WasmTailCallRAScratchReg is a normal GPR. + // We must explicitly move the trampoline address to LR so the // callee returns to the trampoline. masm.xs_mtlr(tempForRA); # endif @@ -7007,8 +7020,9 @@ void MacroAssembler::wasmCallRef(const w size_t instanceSlotOffset = FunctionExtended::offsetOfExtendedSlot( FunctionExtended::WASM_INSTANCE_SLOT); static_assert(FunctionExtended::WASM_INSTANCE_SLOT < wasm::NullPtrGuardSize); - FaultingCodeOffset fco = - loadPtr(Address(calleeFnObj, instanceSlotOffset), newInstanceTemp); + FaultingCodeOffset fco = loadPtr( + Address(calleeFnObj, instanceSlotOffset + PrivateValuePayloadOffset), + newInstanceTemp); append(wasm::Trap::NullPointerDereference, wasm::TrapMachineInsnForLoadWord(), fco.get(), desc.toTrapSiteDesc()); branchPtr(Assembler::Equal, InstanceReg, newInstanceTemp, &fastCall); @@ -7027,7 +7041,9 @@ void MacroAssembler::wasmCallRef(const w // WASM_FUNC_UNCHECKED_ENTRY_SLOT extended slot. size_t uncheckedEntrySlotOffset = FunctionExtended::offsetOfExtendedSlot( FunctionExtended::WASM_FUNC_UNCHECKED_ENTRY_SLOT); - loadPtr(Address(calleeFnObj, uncheckedEntrySlotOffset), calleeScratch); + loadPtr(Address(calleeFnObj, + uncheckedEntrySlotOffset + PrivateValuePayloadOffset), + calleeScratch); *slowCallOffset = wasmMarkedSlowCall(desc, calleeScratch); @@ -7043,7 +7059,9 @@ void MacroAssembler::wasmCallRef(const w bind(&fastCall); - loadPtr(Address(calleeFnObj, uncheckedEntrySlotOffset), calleeScratch); + loadPtr(Address(calleeFnObj, + uncheckedEntrySlotOffset + PrivateValuePayloadOffset), + calleeScratch); // We use a different type of call site for the fast call since the instance // slots in the frame do not have valid values. @@ -7072,8 +7090,9 @@ void MacroAssembler::wasmReturnCallRef( size_t instanceSlotOffset = FunctionExtended::offsetOfExtendedSlot( FunctionExtended::WASM_INSTANCE_SLOT); static_assert(FunctionExtended::WASM_INSTANCE_SLOT < wasm::NullPtrGuardSize); - FaultingCodeOffset fco = - loadPtr(Address(calleeFnObj, instanceSlotOffset), newInstanceTemp); + FaultingCodeOffset fco = loadPtr( + Address(calleeFnObj, instanceSlotOffset + PrivateValuePayloadOffset), + newInstanceTemp); append(wasm::Trap::NullPointerDereference, wasm::TrapMachineInsnForLoadWord(), fco.get(), desc.toTrapSiteDesc()); branchPtr(Assembler::Equal, InstanceReg, newInstanceTemp, &fastCall); @@ -7092,7 +7111,9 @@ void MacroAssembler::wasmReturnCallRef( // WASM_FUNC_UNCHECKED_ENTRY_SLOT extended slot. size_t uncheckedEntrySlotOffset = FunctionExtended::offsetOfExtendedSlot( FunctionExtended::WASM_FUNC_UNCHECKED_ENTRY_SLOT); - loadPtr(Address(calleeFnObj, uncheckedEntrySlotOffset), calleeScratch); + loadPtr(Address(calleeFnObj, + uncheckedEntrySlotOffset + PrivateValuePayloadOffset), + calleeScratch); wasm::CallSiteDesc stubDesc(desc.lineOrBytecode(), wasm::CallSiteKind::ReturnStub); @@ -7105,7 +7126,9 @@ void MacroAssembler::wasmReturnCallRef( bind(&fastCall); - loadPtr(Address(calleeFnObj, uncheckedEntrySlotOffset), calleeScratch); + loadPtr(Address(calleeFnObj, + uncheckedEntrySlotOffset + PrivateValuePayloadOffset), + calleeScratch); wasmCollapseFrameFast(retCallInfo); jump(calleeScratch); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/MacroAssembler.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/MacroAssembler.h --- a/js/src/jit/MacroAssembler.h 2026-07-28 16:38:22.657203940 +0200 +++ b/js/src/jit/MacroAssembler.h 2026-07-28 23:39:28.834568273 +0200 @@ -23,6 +23,8 @@ # include "jit/loong64/MacroAssembler-loong64.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/MacroAssembler-riscv64.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/MacroAssembler-ppc.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/MacroAssembler-ppc64.h" #elif defined(JS_CODEGEN_WASM32) @@ -95,9 +97,10 @@ // } // ////}}} check_macroassembler_style -#define ALL_ARCH mips64, arm, arm64, x86, x64, loong64, riscv64, ppc64, wasm32 +#define ALL_ARCH \ + mips64, arm, arm64, x86, x64, loong64, riscv64, ppc, ppc64, wasm32 #define ALL_SHARED_ARCH \ - arm, arm64, loong64, mips64, riscv64, ppc64, x86_shared, wasm32 + arm, arm64, loong64, mips64, riscv64, ppc, ppc64, x86_shared, wasm32 // * How this macro works: // @@ -143,6 +146,7 @@ #define DEFINED_ON_mips64 #define DEFINED_ON_loong64 #define DEFINED_ON_riscv64 +#define DEFINED_ON_ppc #define DEFINED_ON_ppc64 #define DEFINED_ON_wasm32 #define DEFINED_ON_none @@ -173,6 +177,9 @@ #elif defined(JS_CODEGEN_RISCV64) # undef DEFINED_ON_riscv64 # define DEFINED_ON_riscv64 define +#elif defined(JS_CODEGEN_PPC) +# undef DEFINED_ON_ppc +# define DEFINED_ON_ppc define #elif defined(JS_CODEGEN_PPC64) # undef DEFINED_ON_ppc64 # define DEFINED_ON_ppc64 define @@ -243,6 +250,16 @@ namespace jit { class FrameDescriptor; enum class ExitFrameType : uint8_t; +// Offset from the start of an in-memory Value to the word holding a +// PrivateValue's payload. Zero everywhere except big-endian nunbox32, where the +// type tag occupies the first word. Code reading a private pointer out of a +// slot with loadPtr/branchPtr rather than loadPrivate has to add this. +#ifdef JS_NUNBOX32 +static constexpr int32_t PrivateValuePayloadOffset = NUNBOX32_PAYLOAD_OFFSET; +#else +static constexpr int32_t PrivateValuePayloadOffset = 0; +#endif + class AutoSaveLiveRegisters; class CompileZone; class TemplateNativeObject; @@ -569,7 +586,7 @@ class MacroAssembler : public MacroAssem void Pop(const Register64 reg); void PopFlags() DEFINED_ON(x86_shared); void PopStackPtr() - DEFINED_ON(arm, mips64, x86_shared, loong64, riscv64, ppc64, wasm32); + DEFINED_ON(arm, mips64, x86_shared, loong64, riscv64, ppc, ppc64, wasm32); // Move the stack pointer based on the requested amount. void adjustStack(int amount); @@ -627,9 +644,9 @@ class MacroAssembler : public MacroAssem // These do not adjust framePushed(). void pushReturnAddress() - DEFINED_ON(mips64, arm, arm64, loong64, riscv64, ppc64, wasm32); + DEFINED_ON(mips64, arm, arm64, loong64, riscv64, ppc, ppc64, wasm32); void popReturnAddress() - DEFINED_ON(mips64, arm, arm64, loong64, riscv64, ppc64, wasm32); + DEFINED_ON(mips64, arm, arm64, loong64, riscv64, ppc, ppc64, wasm32); // Useful for dealing with two-valued returns. void moveRegPair(Register src0, Register src1, Register dst0, Register dst1, @@ -648,7 +665,7 @@ class MacroAssembler : public MacroAssem CodeOffset farJumpWithPatch() PER_SHARED_ARCH; void patchFarJump(CodeOffset farJump, uint32_t targetOffset) PER_SHARED_ARCH; static void patchFarJump(uint8_t* farJump, uint8_t* target) - DEFINED_ON(arm, arm64, x86_shared, loong64, mips64, riscv64, ppc64); + DEFINED_ON(arm, arm64, x86_shared, loong64, mips64, riscv64, ppc, ppc64); // Emit a nop that can be patched to and from a nop and a call with int32 // relative displacement. @@ -674,9 +691,9 @@ class MacroAssembler : public MacroAssem // target behaviour is only provided for `n` in the range 0 .. 2^31-1 // inclusive. CodeOffset move32WithPatch(Register dest) - DEFINED_ON(x86_shared, arm, arm64, loong64, mips64, riscv64, ppc64); + DEFINED_ON(x86_shared, arm, arm64, loong64, mips64, riscv64, ppc, ppc64); void patchMove32(CodeOffset offset, Imm32 n) - DEFINED_ON(x86_shared, arm, arm64, loong64, mips64, riscv64, ppc64); + DEFINED_ON(x86_shared, arm, arm64, loong64, mips64, riscv64, ppc, ppc64); public: // =============================================================== @@ -812,7 +829,7 @@ class MacroAssembler : public MacroAssem // the C ABI rather than a C++ function, so on ELFv1 it must not be // dereferenced as a function descriptor. void callWithABIJitCode(Register fun, ABIType result = ABIType::General) - DEFINED_ON(ppc64); + DEFINED_ON(ppc, ppc64); CodeOffset callWithABI(wasm::BytecodeOffset offset, wasm::SymbolicAddress fun, mozilla::Maybe instanceOffset, @@ -1186,13 +1203,13 @@ class MacroAssembler : public MacroAssem inline void mulPtr(ImmWord rhs, Register srcDest) PER_ARCH; inline void mul64(const Register64& rhs, const Register64& srcDest) - DEFINED_ON(x64, arm64, mips64, loong64, riscv64, ppc64); + DEFINED_ON(x64, arm64, mips64, loong64, riscv64, ppc, ppc64); inline void mul64(const Operand& src, const Register64& dest) DEFINED_ON(x64); inline void mul64(const Operand& src, const Register64& dest, const Register temp) DEFINED_ON(x64); inline void mul64(Imm64 imm, const Register64& dest) PER_ARCH; inline void mul64(Imm64 imm, const Register64& dest, const Register temp) - DEFINED_ON(x86, x64, arm, mips64, loong64, riscv64, ppc64); + DEFINED_ON(x86, x64, arm, mips64, loong64, riscv64, ppc, ppc64); inline void mul64(const Register64& src, const Register64& dest, const Register temp) PER_ARCH; inline void mul64(const Register64& src1, const Register64& src2, @@ -1214,11 +1231,11 @@ class MacroAssembler : public MacroAssem // On ARM, the chip must have hardware division instructions. inline void quotient32(Register lhs, Register rhs, Register dest, bool isUnsigned) - DEFINED_ON(mips64, arm, arm64, loong64, riscv64, wasm32, ppc64); + DEFINED_ON(mips64, arm, arm64, loong64, riscv64, wasm32, ppc, ppc64); inline void quotient64(Register lhs, Register rhs, Register dest, bool isUnsigned) - DEFINED_ON(arm64, loong64, mips64, riscv64, ppc64); + DEFINED_ON(arm64, loong64, mips64, riscv64, ppc, ppc64); // As above, but lhs and dest must be eax and tempEdx must be edx. inline void quotient32(Register lhs, Register rhs, Register dest, @@ -1231,11 +1248,11 @@ class MacroAssembler : public MacroAssem // On ARM, the chip must have hardware division instructions. inline void remainder32(Register lhs, Register rhs, Register dest, bool isUnsigned) - DEFINED_ON(mips64, arm, arm64, loong64, riscv64, wasm32, ppc64); + DEFINED_ON(mips64, arm, arm64, loong64, riscv64, wasm32, ppc, ppc64); inline void remainder64(Register lhs, Register rhs, Register dest, bool isUnsigned) - DEFINED_ON(arm64, loong64, mips64, riscv64, ppc64); + DEFINED_ON(arm64, loong64, mips64, riscv64, ppc, ppc64); // As above, but lhs and dest must be eax and tempEdx must be edx. inline void remainder32(Register lhs, Register rhs, Register dest, @@ -1712,7 +1729,7 @@ class MacroAssembler : public MacroAssem Label* label) PER_SHARED_ARCH; inline void branchAdd64(Condition cond, Imm64 imm, Register64 dest, - Label* label) DEFINED_ON(x86, arm, wasm32); + Label* label) DEFINED_ON(x86, arm, ppc, wasm32); template inline void branchAddPtr(Condition cond, T src, Register dest, @@ -2092,7 +2109,7 @@ class MacroAssembler : public MacroAssem template void branchValueIsNurseryCellImpl(Condition cond, const T& value, Register temp, Label* label) - DEFINED_ON(arm64, x64, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm64, x64, mips64, loong64, riscv64, ppc, ppc64); template inline void branchTestUndefinedImpl(Condition cond, const T& t, Label* label) @@ -2257,7 +2274,7 @@ class MacroAssembler : public MacroAssem // from all the other registers, on all supported targets. inline void wasmAddSubI128HI64(Register lhsLo, Register lhsHi, Register rhsLo, Register rhsHi, Register output, bool isAdd) - DEFINED_ON(x64, arm64, riscv64, loong64, mips64, ppc64); + DEFINED_ON(x64, arm64, riscv64, loong64, mips64, ppc, ppc64); // Produces the top 64 bits of the 128-bit value `lhs *widen rhs`. Only used // on 64-bit targets. On x64, `lhs` must be RAX, `rhs` must be RDX, and all @@ -2270,7 +2287,7 @@ class MacroAssembler : public MacroAssem // what the registers may be. inline void wasmMulI64WideHI64(Register lhs, Register rhs, Register output, bool isSigned) - DEFINED_ON(arm64, riscv64, loong64, mips64, ppc64); + DEFINED_ON(arm64, riscv64, loong64, mips64, ppc, ppc64); // ======================================================================== // Canonicalization primitives. @@ -2369,68 +2386,72 @@ class MacroAssembler : public MacroAssem // Moves inline void moveSimd128(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Constants inline void loadConstantSimd128(const SimdConstant& v, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Splat inline void splatX16(Register src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void splatX16(uint32_t srcLane, FloatRegister src, FloatRegister dest) DEFINED_ON(arm64); inline void splatX8(Register src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void splatX8(uint32_t srcLane, FloatRegister src, FloatRegister dest) DEFINED_ON(arm64); inline void splatX4(Register src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void splatX4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void splatX2(Register64 src, FloatRegister dest) - DEFINED_ON(x86, x64, arm64, ppc64); + DEFINED_ON(x86, x64, arm64, ppc, ppc64); inline void splatX2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Extract lane as scalar. Float extraction does not canonicalize the value. inline void extractLaneInt8x16(uint32_t lane, FloatRegister src, - Register dest) DEFINED_ON(x86_shared, arm64, ppc64); + Register dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtractLaneInt8x16(uint32_t lane, FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extractLaneInt16x8(uint32_t lane, FloatRegister src, - Register dest) DEFINED_ON(x86_shared, arm64, ppc64); + Register dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtractLaneInt16x8(uint32_t lane, FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extractLaneInt32x4(uint32_t lane, FloatRegister src, - Register dest) DEFINED_ON(x86_shared, arm64, ppc64); + Register dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extractLaneInt64x2(uint32_t lane, FloatRegister src, - Register64 dest) DEFINED_ON(x86, x64, arm64, ppc64); + Register64 dest) + DEFINED_ON(x86, x64, arm64, ppc, ppc64); inline void extractLaneFloat32x4(uint32_t lane, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extractLaneFloat64x2(uint32_t lane, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Replace lane value @@ -2439,21 +2460,21 @@ class MacroAssembler : public MacroAssem inline void replaceLaneInt8x16(unsigned lane, Register rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void replaceLaneInt16x8(unsigned lane, FloatRegister lhs, Register rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void replaceLaneInt16x8(unsigned lane, Register rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void replaceLaneInt32x4(unsigned lane, FloatRegister lhs, Register rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void replaceLaneInt32x4(unsigned lane, Register rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void replaceLaneInt64x2(unsigned lane, FloatRegister lhs, Register64 rhs, FloatRegister dest) @@ -2461,7 +2482,7 @@ class MacroAssembler : public MacroAssem inline void replaceLaneInt64x2(unsigned lane, Register64 rhs, FloatRegister lhsDest) - DEFINED_ON(x86, x64, arm64, ppc64); + DEFINED_ON(x86, x64, arm64, ppc, ppc64); inline void replaceLaneFloat32x4(unsigned lane, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) @@ -2469,7 +2490,7 @@ class MacroAssembler : public MacroAssem inline void replaceLaneFloat32x4(unsigned lane, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void replaceLaneFloat64x2(unsigned lane, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) @@ -2477,7 +2498,7 @@ class MacroAssembler : public MacroAssem inline void replaceLaneFloat64x2(unsigned lane, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Shuffle - blend and permute with immediate indices, and its many // specializations. Lane values other than those mentioned are illegal. @@ -2485,11 +2506,11 @@ class MacroAssembler : public MacroAssem // lane values 0..31 inline void shuffleInt8x16(const uint8_t lanes[16], FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void shuffleInt8x16(const uint8_t lanes[16], FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Lane values must be 0 (select from lhs) or FF (select from rhs). // The behavior is undefined for lane values that are neither 0 nor FF. @@ -2516,39 +2537,39 @@ class MacroAssembler : public MacroAssem // The implementation works effectively for I8x16, I16x8, I32x4, and I64x2. inline void laneSelectSimd128(FloatRegister mask, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void interleaveHighInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void interleaveHighInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void interleaveHighInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void interleaveHighInt64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void interleaveLowInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void interleaveLowInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void interleaveLowInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void interleaveLowInt64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Permute - permute with immediate indices. @@ -2558,7 +2579,7 @@ class MacroAssembler : public MacroAssem // lane values 0..7 inline void permuteInt16x8(const uint16_t lanes[8], FloatRegister src, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc, ppc64); // lane values 0..3 [sic]. inline void permuteHighInt16x8(const uint16_t lanes[4], FloatRegister src, @@ -2576,80 +2597,85 @@ class MacroAssembler : public MacroAssem // low_16_bytes_of((lhs ++ rhs) >> shift*8), shift must be < 16 inline void concatAndRightShiftSimd128(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, uint32_t shift) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Rotate right by immediate count: // low_16_bytes_of((src ++ src) >> shift*8), shift must be < 16 inline void rotateRightSimd128(FloatRegister src, FloatRegister dest, - uint32_t shift) DEFINED_ON(arm64, ppc64); + uint32_t shift) DEFINED_ON(arm64, ppc, ppc64); // Shift bytes with immediate count, shifting in zeroes. Shift count 0..15. inline void leftShiftSimd128(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void rightShiftSimd128(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Zero extend int values. inline void zeroExtend8x16To16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void zeroExtend8x16To32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void zeroExtend8x16To64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void zeroExtend16x8To32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void zeroExtend16x8To64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void zeroExtend32x4To64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Reverse bytes in lanes. inline void reverseInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void reverseInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void reverseInt64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Swizzle - permute with variable indices. `rhs` holds the lanes parameter. inline void swizzleInt8x16(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void swizzleInt8x16Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Integer Add inline void addInt8x16(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void addInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void addInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void addInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void addInt32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void addInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void addInt64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void addInt64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2657,13 +2683,15 @@ class MacroAssembler : public MacroAssem // Integer Subtract inline void subInt8x16(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void subInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void subInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void subInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2672,24 +2700,28 @@ class MacroAssembler : public MacroAssem FloatRegister dest) DEFINED_ON(x86_shared); inline void subInt32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void subInt64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void subInt64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Integer Multiply inline void mulInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void mulInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void mulInt32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void mulInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2705,100 +2737,102 @@ class MacroAssembler : public MacroAssem inline void mulInt64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, FloatRegister temp1, - FloatRegister temp2) DEFINED_ON(arm64, ppc64); + FloatRegister temp2) DEFINED_ON(arm64, ppc, ppc64); // Note for the extMul opcodes, the NxM designation is for the input lanes; // the output lanes are twice as wide. inline void extMulLowInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extMulHighInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtMulLowInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtMulHighInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extMulLowInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extMulHighInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtMulLowInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtMulHighInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extMulLowInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extMulHighInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtMulLowInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtMulHighInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void q15MulrSatInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Integer Negate inline void negInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void negInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void negInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void negInt64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Saturating integer add inline void addSatInt8x16(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void addSatInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedAddSatInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedAddSatInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void addSatInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void addSatInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedAddSatInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedAddSatInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2806,27 +2840,29 @@ class MacroAssembler : public MacroAssem // Saturating integer subtract inline void subSatInt8x16(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void subSatInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedSubSatInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedSubSatInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void subSatInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void subSatInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedSubSatInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedSubSatInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2834,40 +2870,43 @@ class MacroAssembler : public MacroAssem // Lane-wise integer minimum inline void minInt8x16(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void minInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedMinInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedMinInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void minInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void minInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedMinInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedMinInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void minInt32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void minInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedMinInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedMinInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2875,40 +2914,43 @@ class MacroAssembler : public MacroAssem // Lane-wise integer maximum inline void maxInt8x16(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void maxInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedMaxInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedMaxInt8x16(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void maxInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void maxInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedMaxInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedMaxInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void maxInt32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void maxInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedMaxInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedMaxInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2917,25 +2959,25 @@ class MacroAssembler : public MacroAssem inline void unsignedAverageInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedAverageInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Lane-wise integer absolute value inline void absInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void absInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void absInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void absInt64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Left shift by scalar. Immediates and variable shifts must have been // masked; shifts of zero will work but may or may not generate code. @@ -2944,41 +2986,45 @@ class MacroAssembler : public MacroAssem FloatRegister temp) DEFINED_ON(x86_shared); inline void leftShiftInt8x16(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void leftShiftInt8x16(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void leftShiftInt16x8(Register rhs, FloatRegister lhsDest) DEFINED_ON(x86_shared); inline void leftShiftInt16x8(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void leftShiftInt16x8(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void leftShiftInt32x4(Register rhs, FloatRegister lhsDest) DEFINED_ON(x86_shared); inline void leftShiftInt32x4(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void leftShiftInt32x4(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void leftShiftInt64x2(Register rhs, FloatRegister lhsDest) DEFINED_ON(x86_shared); inline void leftShiftInt64x2(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void leftShiftInt64x2(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Right shift by scalar. Immediates and variable shifts must have been // masked; shifts of zero will work but may or may not generate code. @@ -2987,82 +3033,90 @@ class MacroAssembler : public MacroAssem FloatRegister temp) DEFINED_ON(x86_shared); inline void rightShiftInt8x16(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void rightShiftInt8x16(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedRightShiftInt8x16(Register rhs, FloatRegister lhsDest, FloatRegister temp) DEFINED_ON(x86_shared); inline void unsignedRightShiftInt8x16(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void unsignedRightShiftInt8x16(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void rightShiftInt16x8(Register rhs, FloatRegister lhsDest) DEFINED_ON(x86_shared); inline void rightShiftInt16x8(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void rightShiftInt16x8(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedRightShiftInt16x8(Register rhs, FloatRegister lhsDest) DEFINED_ON(x86_shared); inline void unsignedRightShiftInt16x8(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void unsignedRightShiftInt16x8(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void rightShiftInt32x4(Register rhs, FloatRegister lhsDest) DEFINED_ON(x86_shared); inline void rightShiftInt32x4(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void rightShiftInt32x4(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedRightShiftInt32x4(Register rhs, FloatRegister lhsDest) DEFINED_ON(x86_shared); inline void unsignedRightShiftInt32x4(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void unsignedRightShiftInt32x4(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void rightShiftInt64x2(Register rhs, FloatRegister lhsDest, FloatRegister temp) DEFINED_ON(x86_shared); inline void rightShiftInt64x2(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void rightShiftInt64x2(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void unsignedRightShiftInt64x2(Register rhs, FloatRegister lhsDest) DEFINED_ON(x86_shared); inline void unsignedRightShiftInt64x2(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) + DEFINED_ON(arm64, ppc, ppc64); inline void unsignedRightShiftInt64x2(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Sign replication operation @@ -3081,47 +3135,48 @@ class MacroAssembler : public MacroAssem // Bitwise and, or, xor, not inline void bitwiseAndSimd128(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void bitwiseAndSimd128(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void bitwiseAndSimd128(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void bitwiseOrSimd128(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void bitwiseOrSimd128(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void bitwiseOrSimd128(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void bitwiseXorSimd128(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void bitwiseXorSimd128(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void bitwiseXorSimd128(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void bitwiseNotSimd128(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Bitwise AND with compliment: dest = lhs & ~rhs, note only arm64 can do it. inline void bitwiseAndNotSimd128(FloatRegister lhs, FloatRegister rhs, - FloatRegister lhsDest) DEFINED_ON(arm64, ppc64); + FloatRegister lhsDest) + DEFINED_ON(arm64, ppc, ppc64); // Bitwise AND with complement: dest = ~lhs & rhs, note this is not what Wasm // wants but what the x86 hardware offers. Hence the name. inline void bitwiseNotAndSimd128(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void bitwiseNotAndSimd128(FloatRegister lhs, FloatRegister rhs, FloatRegister lhsDest) @@ -3134,34 +3189,36 @@ class MacroAssembler : public MacroAssem FloatRegister temp) DEFINED_ON(x86_shared); inline void bitwiseSelectSimd128(FloatRegister onTrue, FloatRegister onFalse, - FloatRegister maskDest) DEFINED_ON(arm64, ppc64); + FloatRegister maskDest) + DEFINED_ON(arm64, ppc, ppc64); // Population count inline void popcntInt8x16(FloatRegister src, FloatRegister dest, - FloatRegister temp) DEFINED_ON(x86_shared, ppc64); + FloatRegister temp) + DEFINED_ON(x86_shared, ppc, ppc64); inline void popcntInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc, ppc64); // Any lane true, ie, any bit set inline void anyTrueSimd128(FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // All lanes true inline void allTrueInt8x16(FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void allTrueInt16x8(FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void allTrueInt32x4(FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void allTrueInt64x2(FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Bitmask, ie extract and compress high bits of all lanes @@ -3169,31 +3226,31 @@ class MacroAssembler : public MacroAssem DEFINED_ON(x86_shared); inline void bitmaskInt8x16(FloatRegister src, Register dest, - FloatRegister temp) DEFINED_ON(arm64, ppc64); + FloatRegister temp) DEFINED_ON(arm64, ppc, ppc64); inline void bitmaskInt16x8(FloatRegister src, Register dest) DEFINED_ON(x86_shared); inline void bitmaskInt16x8(FloatRegister src, Register dest, - FloatRegister temp) DEFINED_ON(arm64, ppc64); + FloatRegister temp) DEFINED_ON(arm64, ppc, ppc64); inline void bitmaskInt32x4(FloatRegister src, Register dest) DEFINED_ON(x86_shared); inline void bitmaskInt32x4(FloatRegister src, Register dest, - FloatRegister temp) DEFINED_ON(arm64, ppc64); + FloatRegister temp) DEFINED_ON(arm64, ppc, ppc64); inline void bitmaskInt64x2(FloatRegister src, Register dest) DEFINED_ON(x86_shared); inline void bitmaskInt64x2(FloatRegister src, Register dest, - FloatRegister temp) DEFINED_ON(arm64, ppc64); + FloatRegister temp) DEFINED_ON(arm64, ppc, ppc64); // Comparisons (integer and floating-point) inline void compareInt8x16(Assembler::Condition cond, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // On x86_shared, limited to !=, ==, <=, > inline void compareInt8x16(Assembler::Condition cond, FloatRegister lhs, @@ -3203,15 +3260,15 @@ class MacroAssembler : public MacroAssem // On arm64, use any integer comparison condition. inline void compareInt8x16(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void compareInt16x8(Assembler::Condition cond, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void compareInt16x8(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // On x86_shared, limited to !=, ==, <=, > inline void compareInt16x8(Assembler::Condition cond, FloatRegister lhs, @@ -3221,7 +3278,7 @@ class MacroAssembler : public MacroAssem // On x86_shared, limited to !=, ==, <=, > inline void compareInt32x4(Assembler::Condition cond, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void compareInt32x4(Assembler::Condition cond, FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) @@ -3230,7 +3287,7 @@ class MacroAssembler : public MacroAssem // On arm64, use any integer comparison condition. inline void compareInt32x4(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void compareForEqualityInt64x2(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, @@ -3244,15 +3301,16 @@ class MacroAssembler : public MacroAssem DEFINED_ON(x86_shared); inline void compareInt64x2(Assembler::Condition cond, FloatRegister rhs, - FloatRegister lhsDest) DEFINED_ON(arm64, ppc64); + FloatRegister lhsDest) + DEFINED_ON(arm64, ppc, ppc64); inline void compareInt64x2(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc, ppc64); inline void compareFloat32x4(Assembler::Condition cond, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // On x86_shared, limited to ==, !=, <, <= inline void compareFloat32x4(Assembler::Condition cond, FloatRegister lhs, @@ -3263,11 +3321,11 @@ class MacroAssembler : public MacroAssem // On arm64, use any float-point comparison condition. inline void compareFloat32x4(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void compareFloat64x2(Assembler::Condition cond, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // On x86_shared, limited to ==, !=, <, <= inline void compareFloat64x2(Assembler::Condition cond, FloatRegister lhs, @@ -3278,7 +3336,7 @@ class MacroAssembler : public MacroAssem // On arm64, use any float-point comparison condition. inline void compareFloat64x2(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Load @@ -3287,92 +3345,98 @@ class MacroAssembler : public MacroAssem inline FaultingCodeOffset loadUnalignedSimd128(const Address& src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline FaultingCodeOffset loadUnalignedSimd128(const BaseIndex& src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Store inline FaultingCodeOffset storeUnalignedSimd128(FloatRegister src, const Address& dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline FaultingCodeOffset storeUnalignedSimd128(FloatRegister src, const BaseIndex& dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Floating point negation inline void negFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void negFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Floating point absolute value inline void absFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void absFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // NaN-propagating minimum inline void minFloat32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, FloatRegister temp1, - FloatRegister temp2) DEFINED_ON(x86_shared, ppc64); + FloatRegister temp2) + DEFINED_ON(x86_shared, ppc, ppc64); inline void minFloat32x4(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc, ppc64); inline void minFloat32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc, ppc64); inline void minFloat64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, FloatRegister temp1, - FloatRegister temp2) DEFINED_ON(x86_shared, ppc64); + FloatRegister temp2) + DEFINED_ON(x86_shared, ppc, ppc64); inline void minFloat64x2(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc, ppc64); inline void minFloat64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc, ppc64); // NaN-propagating maximum inline void maxFloat32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, FloatRegister temp1, - FloatRegister temp2) DEFINED_ON(x86_shared, ppc64); + FloatRegister temp2) + DEFINED_ON(x86_shared, ppc, ppc64); inline void maxFloat32x4(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc, ppc64); inline void maxFloat32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc, ppc64); inline void maxFloat64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, FloatRegister temp1, - FloatRegister temp2) DEFINED_ON(x86_shared, ppc64); + FloatRegister temp2) + DEFINED_ON(x86_shared, ppc, ppc64); inline void maxFloat64x2(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc, ppc64); inline void maxFloat64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc, ppc64); // Floating add inline void addFloat32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void addFloat32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void addFloat64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void addFloat64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -3380,13 +3444,15 @@ class MacroAssembler : public MacroAssem // Floating subtract inline void subFloat32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void subFloat32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void subFloat64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void subFloat64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -3394,13 +3460,15 @@ class MacroAssembler : public MacroAssem // Floating division inline void divFloat32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void divFloat32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void divFloat64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void divFloat64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -3408,13 +3476,15 @@ class MacroAssembler : public MacroAssem // Floating Multiply inline void mulFloat32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void mulFloat32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void mulFloat64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void mulFloat64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -3422,91 +3492,91 @@ class MacroAssembler : public MacroAssem // Pairwise add inline void extAddPairwiseInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtAddPairwiseInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void extAddPairwiseInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedExtAddPairwiseInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Floating square root inline void sqrtFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void sqrtFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Integer to floating point with rounding inline void convertInt32x4ToFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedConvertInt32x4ToFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void convertInt32x4ToFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedConvertInt32x4ToFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Floating point to integer with saturation inline void truncSatFloat32x4ToInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedTruncSatFloat32x4ToInt32x4(FloatRegister src, FloatRegister dest, FloatRegister temp) - DEFINED_ON(x86_shared, ppc64); + DEFINED_ON(x86_shared, ppc, ppc64); inline void unsignedTruncSatFloat32x4ToInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc, ppc64); inline void truncSatFloat64x2ToInt32x4(FloatRegister src, FloatRegister dest, FloatRegister temp) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedTruncSatFloat64x2ToInt32x4(FloatRegister src, FloatRegister dest, FloatRegister temp) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void truncFloat32x4ToInt32x4Relaxed(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedTruncFloat32x4ToInt32x4Relaxed(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void truncFloat64x2ToInt32x4Relaxed(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedTruncFloat64x2ToInt32x4Relaxed(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Floating point narrowing inline void convertFloat64x2ToFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Floating point widening inline void convertFloat32x4ToFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Integer to integer narrowing @@ -3514,65 +3584,67 @@ class MacroAssembler : public MacroAssem FloatRegister dest) DEFINED_ON(x86_shared); inline void narrowInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedNarrowInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedNarrowInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void narrowInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void narrowInt32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedNarrowInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void unsignedNarrowInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Integer to integer widening inline void widenLowInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void widenHighInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedWidenLowInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedWidenHighInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void widenLowInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void widenHighInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedWidenLowInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedWidenHighInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void widenLowInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedWidenLowInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void widenHighInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void unsignedWidenHighInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Compare-based minimum/maximum // @@ -3584,47 +3656,48 @@ class MacroAssembler : public MacroAssem inline void pseudoMinFloat32x4(FloatRegister rhsOrRhsDest, FloatRegister lhsOrLhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void pseudoMinFloat32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void pseudoMinFloat64x2(FloatRegister rhsOrRhsDest, FloatRegister lhsOrLhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void pseudoMinFloat64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void pseudoMaxFloat32x4(FloatRegister rhsOrRhsDest, FloatRegister lhsOrLhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void pseudoMaxFloat32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void pseudoMaxFloat64x2(FloatRegister rhsOrRhsDest, FloatRegister lhsOrLhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void pseudoMaxFloat64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Widening/pairwise integer dot product inline void widenDotInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void widenDotInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); inline void dotInt8x16Int7x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void dotInt8x16Int7x16ThenAdd(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) @@ -3632,81 +3705,83 @@ class MacroAssembler : public MacroAssem inline void dotInt8x16Int7x16ThenAdd(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, FloatRegister temp) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc, ppc64); // Floating point rounding inline void ceilFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void ceilFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void floorFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void floorFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void truncFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void truncFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void nearestFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void nearestFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); // Floating multiply-accumulate: srcDest [+-]= src1 * src2 inline void fmaFloat32x4(FloatRegister src1, FloatRegister src2, - FloatRegister srcDest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister srcDest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void fnmaFloat32x4(FloatRegister src1, FloatRegister src2, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void fmaFloat64x2(FloatRegister src1, FloatRegister src2, - FloatRegister srcDest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister srcDest) + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void fnmaFloat64x2(FloatRegister src1, FloatRegister src2, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void minFloat32x4Relaxed(FloatRegister src, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void minFloat32x4Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void maxFloat32x4Relaxed(FloatRegister src, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void maxFloat32x4Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void minFloat64x2Relaxed(FloatRegister src, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void minFloat64x2Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void maxFloat64x2Relaxed(FloatRegister src, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void maxFloat64x2Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); inline void q15MulrInt16x8Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc, ppc64); public: // ======================================================================== @@ -3731,10 +3806,10 @@ class MacroAssembler : public MacroAssem // temp required on x86 and x64; must be undefined on mips64 and loong64. void convertUInt64ToFloat32(Register64 src, FloatRegister dest, Register temp) - DEFINED_ON(arm64, mips64, loong64, ppc64, riscv64, wasm32, x64, x86); + DEFINED_ON(arm64, mips64, loong64, ppc, ppc64, riscv64, wasm32, x64, x86); void convertInt64ToFloat32(Register64 src, FloatRegister dest) - DEFINED_ON(arm64, mips64, loong64, ppc64, riscv64, wasm32, x64, x86); + DEFINED_ON(arm64, mips64, loong64, ppc, ppc64, riscv64, wasm32, x64, x86); bool convertUInt64ToDoubleNeedsTemp() PER_ARCH; @@ -3815,16 +3890,16 @@ class MacroAssembler : public MacroAssem // Scalar::Int64. void wasmLoad(const wasm::MemoryAccessDesc& access, Register memoryBase, Register ptr, Register ptrScratch, AnyRegister output) - DEFINED_ON(arm, loong64, mips64, ppc64); + DEFINED_ON(arm, loong64, mips64, ppc, ppc64); void wasmLoadI64(const wasm::MemoryAccessDesc& access, Register memoryBase, Register ptr, Register ptrScratch, Register64 output) - DEFINED_ON(arm, mips64, loong64, ppc64); + DEFINED_ON(arm, mips64, loong64, ppc, ppc64); void wasmStore(const wasm::MemoryAccessDesc& access, AnyRegister value, Register memoryBase, Register ptr, Register ptrScratch) - DEFINED_ON(arm, loong64, mips64, ppc64); + DEFINED_ON(arm, loong64, mips64, ppc, ppc64); void wasmStoreI64(const wasm::MemoryAccessDesc& access, Register64 value, Register memoryBase, Register ptr, Register ptrScratch) - DEFINED_ON(arm, mips64, loong64, ppc64); + DEFINED_ON(arm, mips64, loong64, ppc, ppc64); // These accept general memoryBase + ptr + offset (in `access`); the offset is // always smaller than the guard region. They will insert an additional add @@ -3904,11 +3979,11 @@ class MacroAssembler : public MacroAssem void wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, Label* oolRejoin, FloatRegister tempDouble) - DEFINED_ON(arm64, x86, x64, mips64, loong64, riscv64, wasm32, ppc64); + DEFINED_ON(arm64, x86, x64, mips64, loong64, riscv64, wasm32, ppc, ppc64); void wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, Label* oolRejoin, FloatRegister tempDouble) - DEFINED_ON(arm64, x86, x64, mips64, loong64, riscv64, wasm32, ppc64); + DEFINED_ON(arm64, x86, x64, mips64, loong64, riscv64, wasm32, ppc, ppc64); void oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64 output, TruncFlags flags, const wasm::TrapSiteDesc& trapSiteDesc, @@ -3917,11 +3992,11 @@ class MacroAssembler : public MacroAssem void wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, Label* oolRejoin, FloatRegister tempDouble) - DEFINED_ON(arm64, x86, x64, mips64, loong64, riscv64, wasm32, ppc64); + DEFINED_ON(arm64, x86, x64, mips64, loong64, riscv64, wasm32, ppc, ppc64); void wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, Label* oolRejoin, FloatRegister tempDouble) - DEFINED_ON(arm64, x86, x64, mips64, loong64, riscv64, wasm32, ppc64); + DEFINED_ON(arm64, x86, x64, mips64, loong64, riscv64, wasm32, ppc, ppc64); void oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64 output, TruncFlags flags, const wasm::TrapSiteDesc& trapSiteDesc, @@ -4242,7 +4317,7 @@ class MacroAssembler : public MacroAssem // that the 32-bit value will be zero-extended or sign-extended to 64 bits as // appropriate for the platform. void widenInt32(Register r) - DEFINED_ON(arm64, x64, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm64, x64, mips64, loong64, riscv64, ppc, ppc64); // As enterFakeExitFrame(), but using register conventions appropriate for // wasm stubs. @@ -4309,13 +4384,13 @@ class MacroAssembler : public MacroAssem const Address& mem, Register expected, Register replacement, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void compareExchange(Scalar::Type type, Synchronization sync, const BaseIndex& mem, Register expected, Register replacement, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); // x86: `expected` and `output` must be edx:eax; `replacement` is ecx:ebx. // x64: `output` must be rax. @@ -4325,12 +4400,12 @@ class MacroAssembler : public MacroAssem void compareExchange64(Synchronization sync, const Address& mem, Register64 expected, Register64 replacement, Register64 output) - DEFINED_ON(arm, arm64, x64, x86, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm, arm64, x64, x86, mips64, loong64, riscv64, ppc, ppc64); void compareExchange64(Synchronization sync, const BaseIndex& mem, Register64 expected, Register64 replacement, Register64 output) - DEFINED_ON(arm, arm64, x64, x86, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm, arm64, x64, x86, mips64, loong64, riscv64, ppc, ppc64); // Exchange with memory. Return the value initially in memory. // MIPS: `valueTemp`, `offsetTemp` and `maskTemp` must be defined for 8-bit @@ -4347,12 +4422,12 @@ class MacroAssembler : public MacroAssem void atomicExchange(Scalar::Type type, Synchronization sync, const Address& mem, Register value, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void atomicExchange(Scalar::Type type, Synchronization sync, const BaseIndex& mem, Register value, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); // x86: `value` must be ecx:ebx; `output` must be edx:eax. // ARM: `value` and `output` must be distinct and (even,odd) pairs. @@ -4360,11 +4435,11 @@ class MacroAssembler : public MacroAssem void atomicExchange64(Synchronization sync, const Address& mem, Register64 value, Register64 output) - DEFINED_ON(arm, arm64, x64, x86, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm, arm64, x64, x86, mips64, loong64, riscv64, ppc, ppc64); void atomicExchange64(Synchronization sync, const BaseIndex& mem, Register64 value, Register64 output) - DEFINED_ON(arm, arm64, x64, x86, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm, arm64, x64, x86, mips64, loong64, riscv64, ppc, ppc64); // Read-modify-write with memory. Return the value in memory before the // operation. @@ -4398,12 +4473,12 @@ class MacroAssembler : public MacroAssem void atomicFetchOp(Scalar::Type type, Synchronization sync, AtomicOp op, Register value, const Address& mem, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void atomicFetchOp(Scalar::Type type, Synchronization sync, AtomicOp op, Register value, const BaseIndex& mem, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); // x86: // `temp` must be ecx:ebx; `output` must be edx:eax. @@ -4417,7 +4492,7 @@ class MacroAssembler : public MacroAssem void atomicFetchOp64(Synchronization sync, AtomicOp op, Register64 value, const Address& mem, Register64 temp, Register64 output) - DEFINED_ON(arm, arm64, x64, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm, arm64, x64, mips64, loong64, riscv64, ppc, ppc64); void atomicFetchOp64(Synchronization sync, AtomicOp op, const Address& value, const Address& mem, Register64 temp, Register64 output) @@ -4425,7 +4500,7 @@ class MacroAssembler : public MacroAssem void atomicFetchOp64(Synchronization sync, AtomicOp op, Register64 value, const BaseIndex& mem, Register64 temp, Register64 output) - DEFINED_ON(arm, arm64, x64, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm, arm64, x64, mips64, loong64, riscv64, ppc, ppc64); void atomicFetchOp64(Synchronization sync, AtomicOp op, const Address& value, const BaseIndex& mem, Register64 temp, Register64 output) @@ -4443,14 +4518,14 @@ class MacroAssembler : public MacroAssem void atomicEffectOp64(Synchronization sync, AtomicOp op, Register64 value, const Address& mem, Register64 temp) - DEFINED_ON(arm, arm64, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm, arm64, mips64, loong64, riscv64, ppc, ppc64); void atomicEffectOp64(Synchronization sync, AtomicOp op, Register64 value, const BaseIndex& mem) DEFINED_ON(x64); void atomicEffectOp64(Synchronization sync, AtomicOp op, Register64 value, const BaseIndex& mem, Register64 temp) - DEFINED_ON(arm, arm64, mips64, loong64, riscv64, ppc64); + DEFINED_ON(arm, arm64, mips64, loong64, riscv64, ppc, ppc64); // 64-bit atomic load. On 64-bit systems, use regular load with // Synchronization::Load, not this method. @@ -4465,10 +4540,10 @@ class MacroAssembler : public MacroAssem Register64 output) DEFINED_ON(x86); void atomicLoad64(Synchronization sync, const Address& mem, Register64 output) - DEFINED_ON(arm); + DEFINED_ON(arm, ppc); void atomicLoad64(Synchronization sync, const BaseIndex& mem, - Register64 output) DEFINED_ON(arm); + Register64 output) DEFINED_ON(arm, ppc); // 64-bit atomic store. On 64-bit systems, use regular store with // Synchronization::Store, not this method. @@ -4477,10 +4552,10 @@ class MacroAssembler : public MacroAssem // ARM: `value` and `temp` must be (even,odd) pairs. void atomicStore64(Synchronization sync, const Address& mem, Register64 value, - Register64 temp) DEFINED_ON(x86, arm); + Register64 temp) DEFINED_ON(x86, arm, ppc); void atomicStore64(Synchronization sync, const BaseIndex& mem, - Register64 value, Register64 temp) DEFINED_ON(x86, arm); + Register64 value, Register64 temp) DEFINED_ON(x86, arm, ppc); // ======================================================================== // Wasm atomic operations. @@ -4503,14 +4578,14 @@ class MacroAssembler : public MacroAssem Register replacement, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void wasmCompareExchange(const wasm::MemoryAccessDesc& access, const BaseIndex& mem, Register expected, Register replacement, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void wasmAtomicExchange(const wasm::MemoryAccessDesc& access, const Address& mem, Register value, Register output) @@ -4524,13 +4599,13 @@ class MacroAssembler : public MacroAssem const Address& mem, Register value, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void wasmAtomicExchange(const wasm::MemoryAccessDesc& access, const BaseIndex& mem, Register value, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void wasmAtomicFetchOp(const wasm::MemoryAccessDesc& access, AtomicOp op, Register value, const Address& mem, Register temp, @@ -4552,13 +4627,13 @@ class MacroAssembler : public MacroAssem Register value, const Address& mem, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void wasmAtomicFetchOp(const wasm::MemoryAccessDesc& access, AtomicOp op, Register value, const BaseIndex& mem, Register valueTemp, Register offsetTemp, Register maskTemp, Register output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); // Read-modify-write with memory. Return no value. // @@ -4585,13 +4660,13 @@ class MacroAssembler : public MacroAssem Register value, const Address& mem, Register valueTemp, Register offsetTemp, Register maskTemp) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void wasmAtomicEffectOp(const wasm::MemoryAccessDesc& access, AtomicOp op, Register value, const BaseIndex& mem, Register valueTemp, Register offsetTemp, Register maskTemp) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); // 64-bit wide operations. @@ -4603,11 +4678,11 @@ class MacroAssembler : public MacroAssem void wasmAtomicLoad64(const wasm::MemoryAccessDesc& access, const Address& mem, Register64 temp, Register64 output) - DEFINED_ON(arm, x86, wasm32); + DEFINED_ON(arm, x86, ppc, wasm32); void wasmAtomicLoad64(const wasm::MemoryAccessDesc& access, const BaseIndex& mem, Register64 temp, - Register64 output) DEFINED_ON(arm, x86, wasm32); + Register64 output) DEFINED_ON(arm, x86, ppc, wasm32); // x86: `expected` must be the same as `output`, and must be edx:eax. // x86: `replacement` must be ecx:ebx. @@ -4649,12 +4724,12 @@ class MacroAssembler : public MacroAssem void wasmAtomicFetchOp64(const wasm::MemoryAccessDesc& access, AtomicOp op, Register64 value, const Address& mem, Register64 temp, Register64 output) - DEFINED_ON(arm, arm64, mips64, loong64, riscv64, ppc64, x64); + DEFINED_ON(arm, arm64, mips64, loong64, riscv64, ppc, ppc64, x64); void wasmAtomicFetchOp64(const wasm::MemoryAccessDesc& access, AtomicOp op, Register64 value, const BaseIndex& mem, Register64 temp, Register64 output) - DEFINED_ON(arm, arm64, mips64, loong64, riscv64, ppc64, x64); + DEFINED_ON(arm, arm64, mips64, loong64, riscv64, ppc, ppc64, x64); void wasmAtomicFetchOp64(const wasm::MemoryAccessDesc& access, AtomicOp op, const Address& value, const Address& mem, @@ -4707,14 +4782,14 @@ class MacroAssembler : public MacroAssem Register replacement, Register valueTemp, Register offsetTemp, Register maskTemp, Register temp, AnyRegister output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void compareExchangeJS(Scalar::Type arrayType, Synchronization sync, const BaseIndex& mem, Register expected, Register replacement, Register valueTemp, Register offsetTemp, Register maskTemp, Register temp, AnyRegister output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void atomicExchangeJS(Scalar::Type arrayType, Synchronization sync, const Address& mem, Register value, Register temp, @@ -4728,13 +4803,13 @@ class MacroAssembler : public MacroAssem const Address& mem, Register value, Register valueTemp, Register offsetTemp, Register maskTemp, Register temp, AnyRegister output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void atomicExchangeJS(Scalar::Type arrayType, Synchronization sync, const BaseIndex& mem, Register value, Register valueTemp, Register offsetTemp, Register maskTemp, Register temp, AnyRegister output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void atomicFetchOpJS(Scalar::Type arrayType, Synchronization sync, AtomicOp op, Register value, const Address& mem, @@ -4760,13 +4835,13 @@ class MacroAssembler : public MacroAssem AtomicOp op, Register value, const Address& mem, Register valueTemp, Register offsetTemp, Register maskTemp, Register temp, AnyRegister output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void atomicFetchOpJS(Scalar::Type arrayType, Synchronization sync, AtomicOp op, Register value, const BaseIndex& mem, Register valueTemp, Register offsetTemp, Register maskTemp, Register temp, AnyRegister output) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void atomicEffectOpJS(Scalar::Type arrayType, Synchronization sync, AtomicOp op, Register value, const Address& mem, @@ -4788,13 +4863,13 @@ class MacroAssembler : public MacroAssem AtomicOp op, Register value, const Address& mem, Register valueTemp, Register offsetTemp, Register maskTemp) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void atomicEffectOpJS(Scalar::Type arrayType, Synchronization sync, AtomicOp op, Register value, const BaseIndex& mem, Register valueTemp, Register offsetTemp, Register maskTemp) - DEFINED_ON(mips64, loong64, riscv64, ppc64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64); void atomicIsLockFreeJS(Register value, Register output); @@ -5663,7 +5738,13 @@ class MacroAssembler : public MacroAssem branchTestDouble(Assembler::Equal, source, &isDouble); branchTestInt32(Assembler::NotEqual, source, failure); +#ifdef JS_CODEGEN_PPC + // Big-endian nunbox32 keeps the payload in the second word, so the Value's + // own address is the tag, not the int32. + convertInt32ToDouble(ToPayload(source), dest); +#else convertInt32ToDouble(source, dest); +#endif jump(&done); bind(&isDouble); @@ -5953,7 +6034,7 @@ class MacroAssembler : public MacroAssem inline void addStackPtrTo(T t); void subFromStackPtr(Imm32 imm32) - DEFINED_ON(mips64, loong64, riscv64, ppc64, wasm32, arm, x86, x64); + DEFINED_ON(mips64, loong64, riscv64, ppc, ppc64, wasm32, arm, x86, x64); void subFromStackPtr(Register reg); template diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/MoveEmitter.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/MoveEmitter.h --- a/js/src/jit/MoveEmitter.h 2026-07-28 16:38:22.637390009 +0200 +++ b/js/src/jit/MoveEmitter.h 2026-07-28 16:52:29.012030259 +0200 @@ -17,6 +17,8 @@ # include "jit/loong64/MoveEmitter-loong64.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/MoveEmitter-riscv64.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/MoveEmitter-ppc.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/MoveEmitter-ppc64.h" #elif defined(JS_CODEGEN_WASM32) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/MoveResolver.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/MoveResolver.cpp --- a/js/src/jit/MoveResolver.cpp 2026-07-28 16:38:22.637425520 +0200 +++ b/js/src/jit/MoveResolver.cpp 2026-07-29 09:57:01.973763367 +0200 @@ -21,9 +21,8 @@ MoveOperand::MoveOperand(MacroAssembler& #ifdef JS_CODEGEN_REGISTER_PAIR case ABIArg::GPR_PAIR: kind_ = Kind::RegPair; - code_ = arg.evenGpr().code(); - MOZ_ASSERT(code_ % 2 == 0); - MOZ_ASSERT(code_ + 1 == arg.oddGpr().code()); + // Store the lower-numbered code of the pair; see ABIArg::evenGpr(). + code_ = std::min(arg.evenGpr().code(), arg.oddGpr().code()); break; #endif case ABIArg::FPU: @@ -57,8 +56,8 @@ bool MoveResolver::addMove(const MoveOpe MoveOp::Type type) { // Assert that we're not doing no-op moves. MOZ_ASSERT(!(from == to)); -#ifdef JS_CODEGEN_PPC64 - // PPC64 FloatRegisters expose Single/Double kinds that have distinct code() +#if defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) + // PowerPC FloatRegisters expose Single/Double kinds that have distinct code() // values but share one physical register. The register allocator can emit a // move between two such kind-views of the same FPR (e.g. f2-Double to // f2-Single); these are no-ops on the hardware, are not caught by the diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/MoveResolver.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/MoveResolver.h --- a/js/src/jit/MoveResolver.h 2026-07-15 22:09:45.000000000 +0200 +++ b/js/src/jit/MoveResolver.h 2026-07-29 09:56:52.476716422 +0200 @@ -86,11 +86,11 @@ class MoveOperand { } Register evenReg() const { MOZ_ASSERT(isGeneralRegPair()); - return Register::FromCode(code_); + return Register::FromCode(code_ % 2 == 0 ? code_ : code_ + 1); } Register oddReg() const { MOZ_ASSERT(isGeneralRegPair()); - return Register::FromCode(code_ + 1); + return Register::FromCode(code_ % 2 == 0 ? code_ + 1 : code_); } FloatRegister floatReg() const { MOZ_ASSERT(isFloatReg()); @@ -119,15 +119,9 @@ class MoveOperand { // have to check any other register, or register pair. if (isGeneralRegPair() || other.isGeneralRegPair()) { if (isGeneralRegPair() && other.isGeneralRegPair()) { - // Assume that register pairs are aligned on even registers. - MOZ_ASSERT(!evenReg().aliases(other.oddReg())); - MOZ_ASSERT(!oddReg().aliases(other.evenReg())); - // Pair of registers are composed of consecutive registers, thus - // if the first registers are aliased, then the second registers - // are aliased too. - MOZ_ASSERT(evenReg().aliases(other.evenReg()) == - oddReg().aliases(other.oddReg())); - return evenReg().aliases(other.evenReg()); + // Pairs are two consecutive registers drawn from one aligned family, + // so two pairs either coincide entirely or are disjoint. + return code_ == other.code_; } else if (other.isGeneralReg()) { MOZ_ASSERT(isGeneralRegPair()); return evenReg().aliases(other.reg()) || oddReg().aliases(other.reg()); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ProcessExecutableMemory.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ProcessExecutableMemory.cpp --- a/js/src/jit/ProcessExecutableMemory.cpp 2026-07-15 22:09:44.000000000 +0200 +++ b/js/src/jit/ProcessExecutableMemory.cpp 2026-07-29 14:21:23.704052206 +0200 @@ -788,8 +788,18 @@ void* ProcessExecutableMemory::allocate( MOZ_ASSERT(bytes <= MaxCodeBytesPerProcess); - // Maybe skip a page to make allocations less predictable. - size_t page = cursor_ + (rng_.ref().next() % 2); + // Maybe skip a page to make allocations less predictable. The skipped page + // is stranded until the cursor wraps, and it can then only be reused by a + // single-page allocation. On a 32-bit target the arena is only + // MaxCodeBytesPerProcess = 140MB, so the arena fills before any code is + // reclaimed and what is left is a sea of isolated one-page holes: every + // allocation larger than ExecutableCodePageSize then fails even though + // tens of MB are free. Only randomize where the arena is large enough for + // the stranding to be irrelevant. + size_t page = cursor_; + if (JS_BITS_PER_WORD == 64) { + page += rng_.ref().next() % 2; + } for (size_t i = 0; i < MaxCodePages; i++) { // Make sure page + numPages - 1 is a valid index. diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/RegisterAllocator.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/RegisterAllocator.h --- a/js/src/jit/RegisterAllocator.h 2026-07-28 16:38:22.637467312 +0200 +++ b/js/src/jit/RegisterAllocator.h 2026-07-28 17:02:08.871865291 +0200 @@ -265,7 +265,7 @@ class RegisterAllocator { #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || \ defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) regs.take(HeapReg); #endif MOZ_ASSERT(!regs.has(FramePointer)); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/RegisterSets.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/RegisterSets.h --- a/js/src/jit/RegisterSets.h 2026-07-15 22:44:59.000000000 +0200 +++ b/js/src/jit/RegisterSets.h 2026-07-29 09:56:39.151652574 +0200 @@ -1309,9 +1309,14 @@ class ABIArg { #else MOZ_CRASH("Unsupported type of ABI argument."); #endif - u.gpr_ = gprLow.code(); - MOZ_ASSERT(u.gpr_ % 2 == 0); - MOZ_ASSERT(u.gpr_ + 1 == gprHigh.code()); + // A pair is two consecutive registers. Store the lower-numbered code; + // which half holds the low word is platform-specific (ARM softfp pairs + // start on an even register and hold the low word there, SysV ppc32 pairs + // start on an odd register and hold the high word there). + Register::Code lo = gprLow.code(); + Register::Code hi = gprHigh.code(); + u.gpr_ = lo < hi ? lo : hi; + MOZ_ASSERT(uint32_t(lo < hi ? hi : lo) == uint32_t(u.gpr_) + 1); } explicit ABIArg(FloatRegister fpu) : kind_(FPU) { u.fpu_ = fpu.code(); } explicit ABIArg(uint32_t offset) : kind_(Stack) { u.offset_ = offset; } @@ -1339,11 +1344,11 @@ class ABIArg { } Register evenGpr() const { MOZ_ASSERT(isGeneralRegPair()); - return Register::FromCode(u.gpr_); + return Register::FromCode(u.gpr_ % 2 == 0 ? u.gpr_ : u.gpr_ + 1); } Register oddGpr() const { MOZ_ASSERT(isGeneralRegPair()); - return Register::FromCode(u.gpr_ + 1); + return Register::FromCode(u.gpr_ % 2 == 0 ? u.gpr_ + 1 : u.gpr_); } FloatRegister fpu() const { MOZ_ASSERT(kind() == FPU); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/Registers.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/Registers.h --- a/js/src/jit/Registers.h 2026-07-28 16:38:22.637503184 +0200 +++ b/js/src/jit/Registers.h 2026-07-28 19:40:41.352733395 +0200 @@ -20,6 +20,8 @@ # include "jit/loong64/Architecture-loong64.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/Architecture-riscv64.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/Architecture-ppc.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/Architecture-ppc64.h" #elif defined(JS_CODEGEN_WASM32) @@ -175,9 +177,15 @@ inline Register::SetType Register::Alloc } #if JS_BITS_PER_WORD == 32 -// Note, some platform code depends on INT64LOW_OFFSET being zero. +// Note, some platform code depends on INT64LOW_OFFSET being zero. That code is +// x86-only, so keeping the big-endian case separate is safe. +# if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +static const uint32_t INT64LOW_OFFSET = 1 * sizeof(int32_t); +static const uint32_t INT64HIGH_OFFSET = 0 * sizeof(int32_t); +# else static const uint32_t INT64LOW_OFFSET = 0 * sizeof(int32_t); static const uint32_t INT64HIGH_OFFSET = 1 * sizeof(int32_t); +# endif #endif struct Register64 { diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/Safepoints.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/Safepoints.cpp --- a/js/src/jit/Safepoints.cpp 2026-07-28 16:38:22.637548787 +0200 +++ b/js/src/jit/Safepoints.cpp 2026-07-28 17:02:08.871961986 +0200 @@ -58,7 +58,7 @@ static PackedRegisterMask ReadRegisterMa static void WriteFloatRegisterMask(CompactBufferWriter& stream, FloatRegisters::SetType bits) { switch (sizeof(FloatRegisters::SetType)) { -#ifdef JS_CODEGEN_ARM64 +#if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC) case 16: stream.writeUnsigned64(bits.low()); stream.writeUnsigned64(bits.high()); @@ -87,7 +87,7 @@ static void WriteFloatRegisterMask(Compa static FloatRegisters::SetType ReadFloatRegisterMask( CompactBufferReader& stream) { switch (sizeof(FloatRegisters::SetType)) { -#ifdef JS_CODEGEN_ARM64 +#if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC) case 16: { uint64_t low = stream.readUnsigned64(); uint64_t high = stream.readUnsigned64(); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/SharedICHelpers-inl.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/SharedICHelpers-inl.h --- a/js/src/jit/SharedICHelpers-inl.h 2026-07-28 16:38:22.637593448 +0200 +++ b/js/src/jit/SharedICHelpers-inl.h 2026-07-28 16:52:29.012164515 +0200 @@ -19,6 +19,8 @@ # include "jit/loong64/SharedICHelpers-loong64-inl.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/SharedICHelpers-riscv64-inl.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/SharedICHelpers-ppc-inl.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/SharedICHelpers-ppc64-inl.h" #elif defined(JS_CODEGEN_WASM32) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/SharedICHelpers.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/SharedICHelpers.h --- a/js/src/jit/SharedICHelpers.h 2026-07-28 16:38:22.637627849 +0200 +++ b/js/src/jit/SharedICHelpers.h 2026-07-28 16:52:29.012141514 +0200 @@ -19,6 +19,8 @@ # include "jit/loong64/SharedICHelpers-loong64.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/SharedICHelpers-riscv64.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/SharedICHelpers-ppc.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/SharedICHelpers-ppc64.h" #elif defined(JS_CODEGEN_WASM32) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/SharedICRegisters.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/SharedICRegisters.h --- a/js/src/jit/SharedICRegisters.h 2026-07-28 16:38:22.637646951 +0200 +++ b/js/src/jit/SharedICRegisters.h 2026-07-28 16:52:29.012186345 +0200 @@ -19,6 +19,8 @@ # include "jit/loong64/SharedICRegisters-loong64.h" #elif defined(JS_CODEGEN_RISCV64) # include "jit/riscv64/SharedICRegisters-riscv64.h" +#elif defined(JS_CODEGEN_PPC) +# include "jit/ppc/SharedICRegisters-ppc.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/SharedICRegisters-ppc64.h" #elif defined(JS_CODEGEN_WASM32) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/Simulator.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/Simulator.h --- a/js/src/jit/Simulator.h 2026-07-28 16:38:22.637664032 +0200 +++ b/js/src/jit/Simulator.h 2026-07-28 16:52:29.012209467 +0200 @@ -15,6 +15,8 @@ # include "jit/loong64/Simulator-loong64.h" #elif defined(JS_SIMULATOR_RISCV64) # include "jit/riscv64/Simulator-riscv64.h" +#elif defined(JS_SIMULATOR_PPC) +# include "jit/ppc/Simulator-ppc.h" #elif defined(JS_SIMULATOR_PPC64) # include "jit/ppc64/Simulator-ppc64.h" #elif defined(JS_SIMULATOR) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/VMFunctions.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/VMFunctions.cpp --- a/js/src/jit/VMFunctions.cpp 2026-07-15 22:44:59.000000000 +0200 +++ b/js/src/jit/VMFunctions.cpp 2026-07-28 23:51:39.127167051 +0200 @@ -6,6 +6,8 @@ #include "mozilla/FloatingPoint.h" +#include + #include "builtin/Date.h" #include "builtin/MapObject.h" #include "builtin/String.h" @@ -24,6 +26,7 @@ #include "js/friend/WindowProxy.h" // js::IsWindow #include "js/Printf.h" #include "js/TraceKind.h" +#include "proxy/Proxy.h" // js::ProxyGetProperty #include "proxy/ScriptedProxyHandler.h" #include "util/Unicode.h" #include "vm/ArrayObject.h" @@ -131,6 +134,16 @@ struct TypeToArgProperties> { // Fail for Handle types that aren't specialized above. }; +// Convert argument type to whether the C++ type is an aggregate. The 32-bit +// PowerPC SysV ABI passes those by invisible reference, so its VM wrapper has +// to synthesize the pointer the callee expects. References are not aggregates: +// they are already passed as a pointer. +template +struct TypeToIsAggregate { + static const uint32_t result = + (std::is_class_v || std::is_union_v) ? 1 : 0; +}; + // Convert argument type to whether or not it should be passed in a float // register on platforms that have them, like x64. template @@ -339,20 +352,24 @@ struct VMFunctionDataHelper::result; } + static constexpr uint32_t argumentIsAggregate() { + return BitMask::result; + } static constexpr uint64_t argumentRootTypes() { return BitMask::result; } constexpr explicit VMFunctionDataHelper(const char* name) : VMFunctionData(name, explicitArgs(), argumentProperties(), - argumentPassedInFloatRegs(), argumentRootTypes(), - outParam(), outParamRootType(), returnType(), + argumentPassedInFloatRegs(), argumentIsAggregate(), + argumentRootTypes(), outParam(), outParamRootType(), + returnType(), /* extraValuesToPop = */ 0) {} constexpr explicit VMFunctionDataHelper(const char* name, PopValues extraValuesToPop) : VMFunctionData(name, explicitArgs(), argumentProperties(), - argumentPassedInFloatRegs(), argumentRootTypes(), - outParam(), outParamRootType(), returnType(), - extraValuesToPop.numValues) {} + argumentPassedInFloatRegs(), argumentIsAggregate(), + argumentRootTypes(), outParam(), outParamRootType(), + returnType(), extraValuesToPop.numValues) {} }; // GCC warns when the signature does not have matching attributes (for example @@ -3432,10 +3449,11 @@ void AssertMapObjectHash(JSContext* cx, MOZ_ASSERT(actualHash == HashValue(cx, obj, value)); } -void AssertPropertyLookup(NativeObject* obj, PropertyKey id, uint32_t slot) { +void AssertPropertyLookup(NativeObject* obj, uintptr_t idBits, uint32_t slot) { AutoUnsafeCallWithABI unsafe; #ifdef DEBUG - mozilla::Maybe prop = obj->lookupPure(id); + mozilla::Maybe prop = + obj->lookupPure(PropertyKey::fromRawBits(idBits)); MOZ_ASSERT(prop.isSome()); MOZ_ASSERT(prop->slot() == slot); #else @@ -3443,6 +3461,33 @@ void AssertPropertyLookup(NativeObject* #endif } +// callWithABI shims for callees taking an aggregate by value; see the comment +// in VMFunctions.h. The wrapped callee keeps its own AutoUnsafeCallWithABI, so +// these add none of their own. +bool GetNativeDataPropertyPureRaw(JSContext* cx, JSObject* obj, + uintptr_t idBits, MegamorphicCacheEntry* entry, + Value* vp) { + return GetNativeDataPropertyPure(cx, obj, PropertyKey::fromRawBits(idBits), + entry, vp); +} + +bool ObjectHasGetterSetterPureRaw(JSContext* cx, JSObject* objArg, + uintptr_t idBits, + GetterSetter* getterSetter) { + return ObjectHasGetterSetterPure(cx, objArg, PropertyKey::fromRawBits(idBits), + getterSetter); +} + +bool TypeOfEqObjectRaw(JSObject* obj, uint32_t operand) { + return TypeOfEqObject(obj, TypeofEqOperand::fromRawValue(uint8_t(operand))); +} + +bool ProxyGetPropertyRaw(JSContext* cx, JSObject** proxy, jsid* id, Value* vp) { + return js::ProxyGetProperty(cx, HandleObject::fromMarkedLocation(proxy), + HandleId::fromMarkedLocation(id), + MutableHandleValue::fromMarkedLocation(vp)); +} + // This is a specialized version of WeakMap::valueReadBarrier. void WeakMapValueReadBarrier(js::gc::TenuredCell* cell, Zone* mapZone) { diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/VMFunctions.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/VMFunctions.h --- a/js/src/jit/VMFunctions.h 2026-07-15 22:44:59.000000000 +0200 +++ b/js/src/jit/VMFunctions.h 2026-07-28 23:50:43.045696849 +0200 @@ -185,6 +185,12 @@ struct VMFunctionData { // have them. uint32_t argumentPassedInFloatRegs; + // One bit per argument, set when the argument's C++ type is a class or a + // union. The outparam's bit is at index |explicitArgs|. ABIs that pass + // aggregates by invisible reference (32-bit PowerPC SysV) need this to know + // when to hand the callee a pointer instead of the value itself. + uint32_t argumentIsAggregate; + // Number of arguments expected, excluding JSContext * as an implicit // first argument and an outparam as a possible implicit final argument. uint8_t explicitArgs; @@ -236,6 +242,11 @@ struct VMFunctionData { return ((argumentPassedInFloatRegs >> explicitArg) & 1) == 1; } + // |arg| may also be |explicitArgs|, which is the outparam. + bool argIsAggregate(uint32_t arg) const { + return ((argumentIsAggregate >> arg) & 1) == 1; + } + #if defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_ION_PERF) const char* name() const { return name_; } #endif @@ -308,9 +319,9 @@ struct VMFunctionData { constexpr VMFunctionData(const char* name, uint32_t explicitArgs, uint32_t argumentProperties, uint32_t argumentPassedInFloatRegs, - uint64_t argRootTypes, DataType outParam, - RootType outParamRootType, DataType returnType, - uint8_t extraValuesToPop = 0) + uint32_t argumentIsAggregate, uint64_t argRootTypes, + DataType outParam, RootType outParamRootType, + DataType returnType, uint8_t extraValuesToPop = 0) : #if defined(DEBUG) || defined(JS_JITSPEW) || defined(JS_ION_PERF) name_(name), @@ -318,6 +329,7 @@ struct VMFunctionData { argumentRootTypes(argRootTypes), argumentProperties(argumentProperties), argumentPassedInFloatRegs(argumentPassedInFloatRegs), + argumentIsAggregate(argumentIsAggregate), explicitArgs(explicitArgs), outParamRootType(outParamRootType), outParam(outParam), @@ -739,10 +751,29 @@ void AssertSetObjectHash(JSContext* cx, void AssertMapObjectHash(JSContext* cx, MapObject* obj, const Value* value, mozilla::HashNumber actualHash); -void AssertPropertyLookup(NativeObject* obj, PropertyKey id, uint32_t slot); +// |idBits| is a PropertyKey's raw bits rather than the key itself: this is +// called directly through callWithABI, and ABIs that pass aggregates by +// invisible reference (32-bit PowerPC SysV) cannot take one in a register. +void AssertPropertyLookup(NativeObject* obj, uintptr_t idBits, uint32_t slot); void WeakMapValueReadBarrier(gc::TenuredCell* cell, Zone* mapZone); +// callWithABI shims for callees that take an aggregate (class/union) by value. +// The 32-bit PowerPC SysV ABI passes those by invisible reference, which +// callWithABI cannot express: ABIType has no aggregate category, so class-ness +// is already erased by the time passABIArg sees the argument. These wrappers +// take the raw scalar payload (or, for Handles, the address of the rooted slot) +// and rebuild the aggregate on the C++ side. Only JS_CODEGEN_PPC calls them, +// but they are defined unconditionally because ABIFUNCTION_LIST cannot contain +// preprocessor conditionals. +bool GetNativeDataPropertyPureRaw(JSContext* cx, JSObject* obj, + uintptr_t idBits, MegamorphicCacheEntry* entry, + Value* vp); +bool ObjectHasGetterSetterPureRaw(JSContext* cx, JSObject* objArg, + uintptr_t idBits, GetterSetter* getterSetter); +bool TypeOfEqObjectRaw(JSObject* obj, uint32_t operand); +bool ProxyGetPropertyRaw(JSContext* cx, JSObject** proxy, jsid* id, Value* vp); + // Functions used when JS_MASM_VERBOSE is enabled. void AssumeUnreachable(const char* output); void Printf0(const char* output); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/moz.build /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/moz.build --- a/js/src/jit/moz.build 2026-07-28 16:38:22.637693673 +0200 +++ b/js/src/jit/moz.build 2026-07-28 16:52:01.399059969 +0200 @@ -228,6 +228,18 @@ elif CONFIG["JS_CODEGEN_LOONG64"]: ] if CONFIG["JS_SIMULATOR_LOONG64"]: UNIFIED_SOURCES += ["loong64/Simulator-loong64.cpp"] +elif CONFIG["JS_CODEGEN_PPC"]: + UNIFIED_SOURCES += [ + "ppc/Architecture-ppc.cpp", + "ppc/Assembler-ppc.cpp", + "ppc/CodeGenerator-ppc.cpp", + "ppc/Lowering-ppc.cpp", + "ppc/MacroAssembler-ppc.cpp", + "ppc/MoveEmitter-ppc.cpp", + "ppc/Trampoline-ppc.cpp", + ] + if CONFIG["JS_SIMULATOR_PPC"]: + UNIFIED_SOURCES += ["ppc/Simulator-ppc.cpp"] elif CONFIG["JS_CODEGEN_PPC64"]: UNIFIED_SOURCES += [ "ppc64/Architecture-ppc64.cpp", diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/Architecture-ppc.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/Architecture-ppc.cpp --- a/js/src/jit/ppc/Architecture-ppc.cpp 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/Architecture-ppc.cpp 2026-07-28 16:57:19.548590562 +0200 @@ -0,0 +1,193 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/ppc/Architecture-ppc.h" + +#ifndef JS_SIMULATOR +# include +#endif + +#include "jit/FlushICache.h" // js::jit::FlushICache +#include "jit/RegisterSets.h" + +namespace js { +namespace jit { + +Registers::Code Registers::FromName(const char* name) { + for (size_t i = 0; i < Total; i++) { + if (strcmp(GetName(i), name) == 0) { + return Code(i); + } + } + + return Invalid; +} + +FloatRegisters::Code FloatRegisters::FromName(const char* name) { + for (size_t i = 0; i < Total; i++) { + if (strcmp(GetName(i), name) == 0) { + return Code(i); + } + } + + return Invalid; +} + +FloatRegisterSet FloatRegister::ReduceSetForPush(const FloatRegisterSet& s) { + SetType all = s.bits(); + SetType simd128Set = + (all >> (uint32_t(FloatRegisters::Simd128) * FloatRegisters::TotalPhys)) & + FloatRegisters::AllPhysMask; + SetType doubleSet = + (all >> (uint32_t(FloatRegisters::Double) * FloatRegisters::TotalPhys)) & + FloatRegisters::AllPhysMask; + SetType singleSet = + (all >> (uint32_t(FloatRegisters::Single) * FloatRegisters::TotalPhys)) & + FloatRegisters::AllPhysMask; + + // Single+Double share physical FPRs (push as Double, 8-byte slot); + // Simd128 lives in its own physical VRs (push as Simd128, 16-byte + // slot). Different physical pools — no dedup. Note that + // sizeof(FloatRegisters::RegisterContent) is 8 bytes (no v128 in the + // union), so RegisterDump::FPUArray is 32 × 8 = 256 bytes, matching + // the Float-only layout PushRegsInMask produces. + SetType set64 = singleSet | doubleSet; + + SetType reduced = + (simd128Set << (uint32_t(FloatRegisters::Simd128) * + FloatRegisters::TotalPhys)) | + (set64 << (uint32_t(FloatRegisters::Double) * FloatRegisters::TotalPhys)); + return FloatRegisterSet(reduced); +} + +uint32_t FloatRegister::GetPushSizeInBytes(const FloatRegisterSet& s) { + SetType all = s.bits(); + SetType simd128Set = + (all >> (uint32_t(FloatRegisters::Simd128) * FloatRegisters::TotalPhys)) & + FloatRegisters::AllPhysMask; + SetType doubleSet = + (all >> (uint32_t(FloatRegisters::Double) * FloatRegisters::TotalPhys)) & + FloatRegisters::AllPhysMask; + SetType singleSet = + (all >> (uint32_t(FloatRegisters::Single) * FloatRegisters::TotalPhys)) & + FloatRegisters::AllPhysMask; + + // Natural per-kind slot sizes. See ReduceSetForPush comment. + SetType set64 = singleSet | doubleSet; + + // SetType is Bitset128 and both operands are masked down to AllPhysMask, + // so the population count lives entirely in the low word. + uint32_t count64 = std::popcount(set64.low()); + uint32_t count128 = std::popcount(simd128Set.low()); + + return count64 * sizeof(double) + count128 * 16; +} + +uint32_t FloatRegister::getRegisterDumpOffsetInBytes() { + // Simd128 encoding is 32-63 — mask back to 0-31 for the FPUArray- + // relative offset. (FPUArray has 32 slots; Simd128 should never be in + // a SafepointState/BailoutState anyway.) + return (encoding() & 31) * sizeof(FloatRegisters::RegisterContent); +} + +static bool sVMXDetected = false; +static bool sCPUFlagsComputed = false; + +#ifndef JS_SIMULATOR +// Cache line sizes, detected at startup from ELF auxiliary vector. +// Fallback to 32 bytes (safe minimum per LuaJIT/LLVM compiler-rt). +static size_t sDCacheLineSize = 0; +static size_t sICacheLineSize = 0; +#endif + +void PPCFlags::Init() { + if (sCPUFlagsComputed) { + return; + } +#ifndef JS_SIMULATOR + // PPC_FEATURE_HAS_ALTIVEC = 0x10000000. The 603 baseline has no vector + // unit, so this is the only feature bit that varies at run time. + sVMXDetected = (getauxval(AT_HWCAP) & 0x10000000) != 0; + + size_t dcache = getauxval(AT_DCACHEBSIZE); + size_t icache = getauxval(AT_ICACHEBSIZE); + sDCacheLineSize = dcache ? dcache : 32; + sICacheLineSize = icache ? icache : 32; +#endif + // Force the scalar fallback paths for testing on AltiVec hardware. + const char* forceNoVMX = getenv("MOZ_PPC_FORCE_NOVMX"); + if (forceNoVMX && forceNoVMX[0] == '1') { + sVMXDetected = false; + } + sCPUFlagsComputed = true; +} + +bool HasVMX() { + MOZ_ASSERT(sCPUFlagsComputed); + return sVMXDetected; +} + +bool CPUFlagsHaveBeenComputed() { return sCPUFlagsComputed; } + +// Per-bit feature flags packed into the wasm code signature. The value is +// also assert-checked into a fixed-width field in js/src/wasm/WasmCompile.cpp +// — if that field ever overflows, widen it there before landing more bits. +uint32_t GetPPCFlags() { + uint32_t flags = 0; + if (sVMXDetected) { + flags |= PPCFlag_VMX; + } + return flags; +} + +void FlushICache(void* code, size_t size) { +#if defined(JS_SIMULATOR) + js::jit::SimulatorProcess::FlushICache(code, size); +#else + // PPC has incoherent I/D caches. GCC's __builtin___clear_cache is a + // no-op on PPC Linux, so we implement the flush explicitly. + // This follows the same approach as QEMU (util/cacheflush.c) and the + // Linux kernel (arch/powerpc/mm/cacheflush.c): + // dcbst loop -> sync -> icbi loop -> sync -> isync + if (!size) { + return; + } + MOZ_ASSERT(sCPUFlagsComputed, + "PPCFlags::Init must run before any FlushICache call"); + + uintptr_t start = reinterpret_cast(code); + uintptr_t end = start + size; + + // Step 1: Write back data cache to memory. + for (uintptr_t addr = start & ~(sDCacheLineSize - 1); addr < end; + addr += sDCacheLineSize) { + asm volatile("dcbst 0, %0" : : "r"(addr) : "memory"); + } + asm volatile("sync" ::: "memory"); + + // Step 2: Invalidate instruction cache. + for (uintptr_t addr = start & ~(sICacheLineSize - 1); addr < end; + addr += sICacheLineSize) { + asm volatile("icbi 0, %0" : : "r"(addr) : "memory"); + } + // The extra sync before isync matches the Linux kernel and QEMU. + // It ensures all icbi operations complete before the pipeline flush. + asm volatile("sync" ::: "memory"); + asm volatile("isync" ::: "memory"); +#endif +} + +void FlushExecutionContext() { +#if !defined(JS_SIMULATOR) + // PPC's isync flushes the instruction pipeline on the current core, + // ensuring any previously invalidated icache entries are discarded and + // instructions are re-fetched from coherent memory. + asm volatile("isync" ::: "memory"); +#endif +} + +} // namespace jit +} // namespace js diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/Architecture-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/Architecture-ppc.h --- a/js/src/jit/ppc/Architecture-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/Architecture-ppc.h 2026-07-29 09:57:20.396857769 +0200 @@ -0,0 +1,714 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_Architecture_ppc_h +#define jit_ppc_Architecture_ppc_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" + +#include +#include + +#include "jit/shared/Architecture-shared.h" + +#include "js/Utility.h" + +namespace js { +namespace jit { + +// PPC has 32 32-bit general purpose registers, r0 through r31. +// The program counter is not directly accessible as a register. +// The link register (LR) and count register (CTR) are SPRs. + +// PowerPC SysV (32-bit Linux) GPR Convention: +// Name Usage +// r0 Volatile, reads as literal 0 when used as a base register +// r1 Stack pointer (callee-saved) +// r2 Thread pointer / TLS (reserved, never written) +// r3 Return value / first argument (r3:r4 for 64-bit values) +// r4-r10 Arguments 2-8 +// r11 Environment pointer / scratch +// r12 Branch target / scratch +// r13 Small-data-area base (reserved, never written) +// r14-r31 Callee-saved +// +// There is no TOC and no function descriptors: a function pointer is a +// plain code address, and calls neither save nor restore r2. + +// PowerPC SysV (32-bit Linux) FPR Convention: +// f0 Scratch +// f1-f8 Arguments 1-8 / volatile +// f9-f13 Volatile +// f14-f31 Callee-saved + +// AltiVec/VMX VR Convention (a register file distinct from the FPRs): +// v0-v19 Volatile +// v20-v31 Callee-saved + +class Registers { + public: + enum RegisterID { + r0 = 0, + r1, + r2, + r3, + r4, + r5, + r6, + r7, + r8, + r9, + r10, + r11, + r12, + r13, + r14, + r15, + r16, + r17, + r18, + r19, + r20, + r21, + r22, + r23, + r24, + r25, + r26, + r27, + r28, + r29, + r30, + r31, + sp = r1, + invalid_reg, + }; + typedef uint8_t Code; + typedef RegisterID Encoding; + typedef uint32_t SetType; + + static const Encoding StackPointer = sp; + static const Encoding Invalid = invalid_reg; + + union RegisterContent { + uintptr_t r; + }; + + static uint32_t SetSize(SetType x) { return std::popcount(x); } + static uint32_t FirstBit(SetType x) { + MOZ_ASSERT(x); + return std::countr_zero(x); + } + static uint32_t LastBit(SetType x) { + MOZ_ASSERT(x); + return std::bit_width(x) - 1; + } + + static const char* GetName(uint32_t code) { + static const char* const Names[] = { + "r0", "sp", "r2", "r3", "r4", "r5", "r6", "r7", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", + "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", + "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31"}; + static_assert(Total == std::size(Names), "Table is the correct size"); + if (code >= Total) { + return "invalid"; + } + return Names[code]; + } + + static Code FromName(const char* name); + + static const uint32_t Total = 32; + static const uint32_t TotalPhys = 32; + static const uint32_t Allocatable = 24; + + static const SetType AllMask = 0xFFFFFFFF; + static const SetType NoneMask = 0x0; + + static const SetType ArgRegMask = + (1U << Registers::r3) | (1U << Registers::r4) | (1U << Registers::r5) | + (1U << Registers::r6) | (1U << Registers::r7) | (1U << Registers::r8) | + (1U << Registers::r9) | (1U << Registers::r10); + + // SysV volatile GPRs are r0 and r3-r12. + static const SetType VolatileMask = ArgRegMask | (1U << Registers::r0) | + (1U << Registers::r11) | + (1U << Registers::r12); + + // SysV callee-saved GPRs are r14..r31. r2 (thread pointer) and r13 + // (small-data-area base) are dedicated registers, NOT general + // callee-saved: they must NEVER be written, so listing them here would + // only make `PushRegsInMask(NonVolatileMask)` waste 8 bytes per + // wasm-stub frame. Both are NonAllocatable and no JIT-emitted code + // assigns to them, so they are preserved across the JIT body for free. + static const SetType NonVolatileMask = + (1U << Registers::r14) | (1U << Registers::r15) | (1U << Registers::r16) | + (1U << Registers::r17) | (1U << Registers::r18) | (1U << Registers::r19) | + (1U << Registers::r20) | (1U << Registers::r21) | (1U << Registers::r22) | + (1U << Registers::r23) | (1U << Registers::r24) | (1U << Registers::r25) | + (1U << Registers::r26) | (1U << Registers::r27) | (1U << Registers::r28) | + (1U << Registers::r29) | (1U << Registers::r30) | (1U << Registers::r31); + + static const SetType NonAllocatableMask = + (1U << Registers::r0) | // Cannot be base in load/store. + (1U << Registers::sp) | // Stack pointer. + (1U << Registers::r2) | // Thread pointer (TLS). + (1U << Registers::r11) | // Third scratch. + (1U << Registers::r12) | // Second scratch / addressTempRegister. + (1U << Registers::r13) | // Small-data-area base. + (1U << Registers::r16) | // Saved scratch register. + (1U << Registers::r31); // Frame pointer. + + static const SetType WrapperMask = VolatileMask; + + // Registers returned from a JS -> JS call. + static const SetType JSCallMask = (1U << Registers::r5); + + // Registers returned from a JS -> C call. + static const SetType CallMask = (1U << Registers::r3); + + static const SetType AllocatableMask = AllMask & ~NonAllocatableMask; + + static_assert(Allocatable == std::popcount(AllocatableMask), + "Allocatable must match AllocatableMask"); +}; + +typedef uint32_t PackedRegisterMask; + +template +class TypedRegisterSet; + +// 128-bit bitset for FloatRegisters::SetType. The three register kinds +// (Double, Single, Simd128) need 3 x 32 = 96 bits, and GCC provides no +// __int128 on 32-bit PowerPC. Same approach as jit/arm64. +class Bitset128 { + // The order (hi, lo) looks best in the debugger. + uint64_t hi, lo; + + public: + MOZ_IMPLICIT constexpr Bitset128(uint64_t initial) : hi(0), lo(initial) {} + MOZ_IMPLICIT constexpr Bitset128(const Bitset128& that) + : hi(that.hi), lo(that.lo) {} + + constexpr Bitset128(uint64_t hi, uint64_t lo) : hi(hi), lo(lo) {} + + constexpr uint64_t high() const { return hi; } + + constexpr uint64_t low() const { return lo; } + + constexpr Bitset128 operator|(Bitset128 that) const { + return Bitset128(hi | that.hi, lo | that.lo); + } + + constexpr Bitset128 operator&(Bitset128 that) const { + return Bitset128(hi & that.hi, lo & that.lo); + } + + constexpr Bitset128 operator^(Bitset128 that) const { + return Bitset128(hi ^ that.hi, lo ^ that.lo); + } + + constexpr Bitset128 operator~() const { return Bitset128(~hi, ~lo); } + + // Shifting by the word width must be handled separately. + + constexpr Bitset128 operator<<(size_t shift) const { + if (shift == 0) { + return *this; + } + if (shift < 64) { + return Bitset128((hi << shift) | (lo >> (64 - shift)), lo << shift); + } + if (shift == 64) { + return Bitset128(lo, 0); + } + return Bitset128(lo << (shift - 64), 0); + } + + constexpr Bitset128 operator>>(size_t shift) const { + if (shift == 0) { + return *this; + } + if (shift < 64) { + return Bitset128(hi >> shift, (lo >> shift) | (hi << (64 - shift))); + } + if (shift == 64) { + return Bitset128(0, hi); + } + return Bitset128(0, hi >> (shift - 64)); + } + + constexpr bool operator==(Bitset128 that) const { + return lo == that.lo && hi == that.hi; + } + + constexpr bool operator!=(Bitset128 that) const { + return lo != that.lo || hi != that.hi; + } + + constexpr bool operator!() const { return (hi | lo) == 0; } + + Bitset128& operator|=(const Bitset128& that) { + hi |= that.hi; + lo |= that.lo; + return *this; + } + + Bitset128& operator&=(const Bitset128& that) { + hi &= that.hi; + lo &= that.lo; + return *this; + } + + uint32_t size() const { return std::popcount(hi) + std::popcount(lo); } + + uint32_t countTrailingZeroes() const { + if (lo) { + return std::countr_zero(lo); + } + return std::countr_zero(hi) + 64; + } + + uint32_t countLeadingZeroes() const { + if (hi) { + return std::countl_zero(hi); + } + return std::countl_zero(lo) + 64; + } + + uint32_t bitWidth() const { return 128 - countLeadingZeroes(); } +}; + +class FloatRegisters { + public: + enum FPRegisterID { + f0 = 0, + 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, + }; + + // Eight bits: (invalid << 7) | (kind << 5) | encoding + typedef uint8_t Code; + typedef FPRegisterID Encoding; + // 3 kinds x 32 regs = 96 bits needed. + typedef Bitset128 SetType; + + enum Kind : uint8_t { Double, Single, Simd128, NumTypes }; + + static constexpr Code Invalid = 0x80; + + static const char* GetName(uint32_t code) { + static const char* const Names[] = { + "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"}; + static_assert(TotalPhys == std::size(Names), "Table is the correct size"); + if (code >= Total) { + return "invalid"; + } + return Names[code % TotalPhys]; + } + + static Code FromName(const char* name); + + static const uint32_t TotalPhys = 32; + static const uint32_t Total = TotalPhys * NumTypes; + static const uint32_t Allocatable = 31; // Without f0, the scratch register. + + static_assert(sizeof(SetType) * 8 >= Total, + "SetType should be large enough to enumerate all registers."); + + static constexpr unsigned ShiftDouble = uint32_t(Double) * TotalPhys; + static constexpr unsigned ShiftSingle = uint32_t(Single) * TotalPhys; + static constexpr unsigned ShiftSimd128 = uint32_t(Simd128) * TotalPhys; + + static constexpr SetType SpreadSingle = SetType(1) << ShiftSingle; + static constexpr SetType SpreadDouble = SetType(1) << ShiftDouble; + static constexpr SetType SpreadSimd128 = SetType(1) << ShiftSimd128; + static constexpr SetType Spread = SpreadSingle | SpreadDouble | SpreadSimd128; + + static constexpr SetType AllPhysMask = ~(~SetType(0) << TotalPhys); + static constexpr SetType AllSingleMask = AllPhysMask << ShiftSingle; + static constexpr SetType AllDoubleMask = AllPhysMask << ShiftDouble; + static constexpr SetType AllSimd128Mask = AllPhysMask << ShiftSimd128; + static constexpr SetType AllMask = + AllSingleMask | AllDoubleMask | AllSimd128Mask; + static constexpr SetType NoneMask = SetType(0); + + // SysV: f14-f31 are non-volatile (callee-saved) for scalar FP. + static constexpr SetType NonVolatilePhysMask = + SetType((1U << FloatRegisters::f14) | (1U << FloatRegisters::f15) | + (1U << FloatRegisters::f16) | (1U << FloatRegisters::f17) | + (1U << FloatRegisters::f18) | (1U << FloatRegisters::f19) | + (1U << FloatRegisters::f20) | (1U << FloatRegisters::f21) | + (1U << FloatRegisters::f22) | (1U << FloatRegisters::f23) | + (1U << FloatRegisters::f24) | (1U << FloatRegisters::f25) | + (1U << FloatRegisters::f26) | (1U << FloatRegisters::f27) | + (1U << FloatRegisters::f28) | (1U << FloatRegisters::f29) | + (1U << FloatRegisters::f30) | (1U << FloatRegisters::f31)); + // Simd128 lives in the AltiVec/VMX register file VR0-VR31, which is + // physically separate from FPR0-FPR31. Under SysV VR20-VR31 are + // non-volatile (callee-saved). Encoding storage is 20-31 with + // kind=Simd128. + static constexpr SetType SimdNonVolatilePhysMask = + SetType((1U << 20) | (1U << 21) | (1U << 22) | (1U << 23) | (1U << 24) | + (1U << 25) | (1U << 26) | (1U << 27) | (1U << 28) | (1U << 29) | + (1U << 30) | (1U << 31)); + static constexpr SetType NonVolatileMask = + (NonVolatilePhysMask << ShiftSingle) | + (NonVolatilePhysMask << ShiftDouble) | + (SimdNonVolatilePhysMask << ShiftSimd128); + + static constexpr SetType VolatileMask = AllMask & ~NonVolatileMask; + + static constexpr SetType WrapperMask = VolatileMask; + + // f0/v0 is the scratch register (all three views: single, double, simd128). + static constexpr SetType NonAllocatableMask = + (SetType(1) << (ShiftSingle + FloatRegisters::f0)) | + (SetType(1) << (ShiftDouble + FloatRegisters::f0)) | + (SetType(1) << (ShiftSimd128 + FloatRegisters::f0)); + + static constexpr SetType AllocatableMask = AllMask & ~NonAllocatableMask; + + union RegisterContent { + float s; + double d; + // No v128 here. Simd128 lives in the physically-distinct VRs and + // never reaches RegisterDump (asserted by SafepointState; bailout + // AllRegs excludes Simd128). With v128 in the union, sizeof was 16, + // forcing PushRegsInMask to a 16-byte stride that mismatched + // addressOfRegister's 8-byte walk via (*iter).size(). + }; + + static constexpr Encoding encoding(Code c) { return Encoding(c & 31); } + + static constexpr Kind kind(Code c) { return Kind((c >> 5) & 3); } + + static constexpr Code fromParts(uint32_t encoding, uint32_t kind, + uint32_t invalid) { + return Code((invalid << 7) | (kind << 5) | encoding); + } +}; + +// SpillSlotSize must fit the widest register class (Simd128 = 16 bytes). +// We can't derive from sizeof(FloatRegisters::RegisterContent) — that +// union is sized for FPRs only (8 bytes since v128 lives in the distinct +// VMX register file, not in the FPR union), so deriving would +// under-reserve for Simd128 cycle breaks. SpillSlotSize is consumed only +// by MoveEmitter and is not part of the JIT frame layout. +// Big endian: a nunbox32 JS::Value keeps the tag in the high 32 bits, so the +// tag sits at offset 0 and the payload at offset 4 -- the mirror of every +// little-endian nunbox32 backend. +static const int32_t NUNBOX32_TYPE_OFFSET = 0; +static const int32_t NUNBOX32_PAYLOAD_OFFSET = 4; + +static const uint32_t SpillSlotSize = 16; + +// PowerPC SysV ABI: the caller leaves a linkage area at the bottom of its +// frame holding the back chain at 0(sp) and the LR save slot at 4(sp), which +// the callee writes. SysV needs no parameter save area for register-passed +// arguments, so the stack arguments start right above the linkage area. +static constexpr uint32_t LinkageAreaSize = 8; + +// Callers reserve this much before a call. It is only ever used as a whole +// reservation (wasm's stubs rely on it not breaking stack alignment), so it is +// the linkage area rounded up to ABIStackAlignment, not the offset of the first +// stack argument -- see ABIArgGenerator, which uses LinkageAreaSize for the +// system ABI and this for the wasm ABI. +static constexpr uint32_t ShadowStackSpace = 16; +static const uint32_t SizeOfReturnAddressAfterCall = 0; + +// PPC branch instructions have a 26-bit signed offset field, giving a +// range of +/- 32MB. We reduce this to leave room for jump island insertion. +static constexpr uint32_t JumpImmediateRange = (32 * 1024 * 1024) - 32; + +// Size of each bailout table entry (a single bl instruction). +static const uint32_t BAILOUT_TABLE_ENTRY_SIZE = 4; + +// PPC special purpose registers (not exposed to the allocator). +enum SPRegisterID { + spr_xer = 1, + spr_lr = 8, + spr_ctr = 9, + spr_vrsave = 256, + invalid_spreg +}; + +// PPC condition registers. +enum CRegisterID { cr0 = 0, cr1, cr5 = 5, cr6, cr7, invalid_creg }; + +struct FloatRegister { + typedef FloatRegisters Codes; + typedef size_t Code; + typedef Codes::Encoding Encoding; + typedef Codes::SetType SetType; + + static uint32_t SetSize(SetType x) { + // Fold all 3 kinds (Double, Single, Simd128) down to physical mask. + x |= x >> FloatRegisters::TotalPhys; + x |= x >> FloatRegisters::TotalPhys; + x &= FloatRegisters::AllPhysMask; + MOZ_ASSERT(x.high() == 0); + return std::popcount(x.low()); + } + + static uint32_t FirstBit(SetType x) { + MOZ_ASSERT(x); + return x.countTrailingZeroes(); + } + static uint32_t LastBit(SetType x) { + MOZ_ASSERT(x); + return x.bitWidth() - 1; + } + + private: + uint8_t encoding_; + uint8_t kind_; + bool invalid_; + + typedef Codes::Kind Kind; + + public: + constexpr FloatRegister(Encoding encoding, Kind kind) + : encoding_(encoding), kind_(kind), invalid_(false) {} + + constexpr FloatRegister() + : encoding_(0), kind_(FloatRegisters::Double), invalid_(true) {} + + static FloatRegister FromCode(uint32_t i) { + MOZ_ASSERT(i < Codes::Total); + return FloatRegister(FloatRegisters::encoding(i), FloatRegisters::kind(i)); + } + + bool isSingle() const { + MOZ_ASSERT(!invalid_); + return kind_ == FloatRegisters::Single; + } + bool isDouble() const { + MOZ_ASSERT(!invalid_); + return kind_ == FloatRegisters::Double; + } + bool isSimd128() const { + MOZ_ASSERT(!invalid_); + return kind_ == FloatRegisters::Simd128; + } + bool isInvalid() const { return invalid_; } + + FloatRegister asSingle() const { + MOZ_ASSERT(!invalid_); + return FloatRegister(Encoding(encoding_), FloatRegisters::Single); + } + FloatRegister asDouble() const { + MOZ_ASSERT(!invalid_); + return FloatRegister(Encoding(encoding_), FloatRegisters::Double); + } + FloatRegister asSimd128() const { + MOZ_ASSERT(!invalid_); + return FloatRegister(Encoding(encoding_), FloatRegisters::Simd128); + } + + constexpr uint32_t size() const { + MOZ_ASSERT(!invalid_); + if (kind_ == FloatRegisters::Double) { + return sizeof(double); + } + if (kind_ == FloatRegisters::Single) { + return sizeof(float); + } + MOZ_ASSERT(kind_ == FloatRegisters::Simd128); + return 16; + } + + constexpr Code code() const { + return Codes::fromParts(encoding_, kind_, invalid_); + } + + constexpr Encoding encoding() const { + MOZ_ASSERT(!invalid_); + // VMX VRs and the FPRs are separate register files, so Simd128 + // encodings are biased by 32 to keep them distinct from Single/Double + // at the same physical number. VMX FloatRegister-taking encoders mask + // with `& 31` to recover the raw 5-bit VR field. + return Encoding(encoding_ + (kind_ == FloatRegisters::Simd128 ? 32 : 0)); + } + + const char* name() const { return FloatRegisters::GetName(code()); } + bool volatile_() const { + MOZ_ASSERT(!invalid_); + return !!((SetType(1) << code()) & FloatRegisters::VolatileMask); + } + constexpr bool operator!=(FloatRegister other) const { + return code() != other.code(); + } + constexpr bool operator==(FloatRegister other) const { + return code() == other.code(); + } + + bool aliases(FloatRegister other) const { + // Register-class partition: {Single, Double} share the FPRs; Simd128 + // lives in the VMX VRs. FPR f5 (Single/Double encoding 5) and VR v5 + // (Simd128 encoding 5) are distinct physical registers. + if (encoding_ != other.encoding_) return false; + bool selfSimd = (kind_ == FloatRegisters::Simd128); + bool otherSimd = (other.kind_ == FloatRegisters::Simd128); + return selfSimd == otherSimd; + } + bool equiv(FloatRegister other) const { + MOZ_ASSERT(!invalid_); + return kind_ == other.kind_; + } + + uint32_t numAliased() const { + return (kind_ == FloatRegisters::Simd128) ? 1 : 2; + } + uint32_t numAlignedAliased() { return numAliased(); } + + FloatRegister aliased(uint32_t aliasIdx) { + MOZ_ASSERT(!invalid_); + MOZ_ASSERT(aliasIdx < numAliased()); + if (kind_ == FloatRegisters::Simd128) { + return *this; + } + Kind otherKind = (kind_ == FloatRegisters::Single) ? FloatRegisters::Double + : FloatRegisters::Single; + Kind selectedKind = (aliasIdx == 0) ? Kind(kind_) : otherKind; + return FloatRegister(Encoding(encoding_), selectedKind); + } + FloatRegister alignedAliased(uint32_t aliasIdx) { + MOZ_ASSERT(aliasIdx < numAliased()); + return aliased(aliasIdx); + } + SetType alignedOrDominatedAliasedSet() const { + if (kind_ == FloatRegisters::Simd128) { + return SetType(1) << ((uint32_t(FloatRegisters::Simd128) * + FloatRegisters::TotalPhys) + + encoding_); + } + return (Codes::SpreadSingle | Codes::SpreadDouble) << encoding_; + } + + static constexpr RegTypeName DefaultType = RegTypeName::Float64; + + template + static SetType LiveAsIndexableSet(SetType s) { + return SetType(0); + } + + template + static SetType AllocatableAsIndexableSet(SetType s) { + static_assert(Name != RegTypeName::Any, "Allocatable set are not iterable"); + return LiveAsIndexableSet(s); + } + + static TypedRegisterSet ReduceSetForPush( + const TypedRegisterSet& s); + static uint32_t GetPushSizeInBytes(const TypedRegisterSet& s); + uint32_t getRegisterDumpOffsetInBytes(); +}; + +template <> +inline FloatRegister::SetType +FloatRegister::LiveAsIndexableSet(SetType set) { + return set & FloatRegisters::AllSingleMask; +} + +template <> +inline FloatRegister::SetType +FloatRegister::LiveAsIndexableSet(SetType set) { + return set & FloatRegisters::AllDoubleMask; +} + +template <> +inline FloatRegister::SetType +FloatRegister::LiveAsIndexableSet(SetType set) { + return set & FloatRegisters::AllSimd128Mask; +} + +template <> +inline FloatRegister::SetType +FloatRegister::LiveAsIndexableSet(SetType set) { + return set; +} + +inline bool hasUnaliasedDouble() { return false; } +inline bool hasMultiAlias() { return false; } + +// PPC feature bits packed into the value GetPPCFlags() returns, +// which feeds wasm/WasmCompile.cpp's per-architecture code signature. +// Defined as enum constants (not enum class) so callers can OR/AND +// freely. New bits should remain backward-compatible — older signatures +// must keep meaning the same set of features. +enum PPCFeatureFlags : uint32_t { + // AltiVec/VMX. The baseline target (PowerPC 603) has no vector unit, so + // this is the only bit that can vary at runtime. + PPCFlag_VMX = 1u << 0, +}; + +uint32_t GetPPCFlags(); + +class PPCFlags final { + public: + PPCFlags() = delete; + + // PPCFlags::Init is called from the JitContext constructor to read the + // hardware capabilities (via getauxval(AT_HWCAP)). It must be called + // exactly once, before HasVMX() is used. + static void Init(); +}; + +// True when the CPU implements AltiVec/VMX. False on the 603 baseline, in +// which case wasm SIMD is unavailable and the scalar FPR fallbacks are used. +bool HasVMX(); + +// The 32-bit backend targets a classic PowerPC (603) with an FPU and no +// vector unit, so these are always false. They are retained because the +// rest of js/src/jit/ppc/ still gates ISA 2.02+ code paths on them; every +// such site takes the fallback branch. +inline bool HasPOWER9() { return false; } +inline bool HasPOWER10() { return false; } +inline bool HasVSX() { return false; } + +// SysV ppc32 passes a 64-bit integer argument in an odd/even GPR pair, so +// ABIArg must be able to express a pair of general purpose registers. +#define JS_CODEGEN_REGISTER_PAIR 1 + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_Architecture_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/Assembler-ppc.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/Assembler-ppc.cpp --- a/js/src/jit/ppc/Assembler-ppc.cpp 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/Assembler-ppc.cpp 2026-07-29 10:42:30.265668026 +0200 @@ -0,0 +1,2484 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/ppc/Assembler-ppc.h" + +#include "mozilla/DebugOnly.h" +#include "mozilla/Maybe.h" + +#include "gc/Marking.h" +#include "jit/AutoWritableJitCode.h" +#include "jit/ExecutableAllocator.h" +#include "jit/FlushICache.h" + +using mozilla::DebugOnly; + +using namespace js; +using namespace js::jit; + +// SysV ppc32: 8 GPRs (r3-r10) and 8 FPRs (f1-f8). Unlike ELFv2, an FP +// argument does not shadow a GPR slot, and there is no parameter save area +// for register-passed arguments. +ABIArg ABIArgGenerator::next(MIRType type) { + switch (type) { + case MIRType::Int32: + case MIRType::Pointer: + case MIRType::WasmAnyRef: + case MIRType::WasmArrayData: + case MIRType::StackResults: { + if (intRegIndex_ >= NumIntArgRegs) { + current_ = ABIArg(stackOffset_); + stackOffset_ += sizeof(uintptr_t); + break; + } + current_ = ABIArg(Register::FromCode(Registers::r3 + intRegIndex_)); + intRegIndex_++; + break; + } + case MIRType::Int64: { + // SysV places this in an odd/even GPR pair (r3:r4, r5:r6, ...) with the + // high word in the lower-numbered register. The Wasm ABI follows suit: + // wasm::GenerateBuiltinThunk requires both ABIs to place arguments + // identically. + intRegIndex_ += intRegIndex_ & 1; // pairs start at r3, r5, r7, r9 + if (intRegIndex_ + 1 < NumIntArgRegs) { + Register high = Register::FromCode(Registers::r3 + intRegIndex_); + Register low = Register::FromCode(Registers::r3 + intRegIndex_ + 1); + current_ = ABIArg(low, high); + intRegIndex_ += 2; + break; + } + intRegIndex_ = NumIntArgRegs; + stackOffset_ = AlignBytes(stackOffset_, sizeof(uint64_t)); + current_ = ABIArg(stackOffset_); + stackOffset_ += sizeof(uint64_t); + break; + } + case MIRType::Float32: + case MIRType::Double: { + if (floatRegIndex_ == NumFloatArgRegs) { + stackOffset_ = AlignBytes(stackOffset_, sizeof(double)); + current_ = ABIArg(stackOffset_); + stackOffset_ += sizeof(double); + break; + } + current_ = ABIArg(FloatRegister( + FloatRegisters::Encoding(FloatRegisters::f1 + floatRegIndex_), + type == MIRType::Double ? FloatRegisters::Double + : FloatRegisters::Single)); + floatRegIndex_++; + break; + } + case MIRType::Simd128: { + // VMX vectors are not part of the SysV argument convention; wasm-to-wasm + // calls are the only producer, so pass them on the stack. + stackOffset_ = AlignBytes(stackOffset_, SimdMemoryAlignment); + current_ = ABIArg(stackOffset_); + stackOffset_ += 16; + break; + } + default: + MOZ_CRASH("Unexpected argument type"); + } + return current_; +} + +// Condition inversion tables. +Assembler::Condition Assembler::InvertCondition(Condition cond) { + switch (cond) { + case Equal: + return NotEqual; + case NotEqual: + return Equal; + case LessThan: + return GreaterThanOrEqual; + case LessThanOrEqual: + return GreaterThan; + case GreaterThan: + return LessThanOrEqual; + case GreaterThanOrEqual: + return LessThan; + case Above: + return BelowOrEqual; + case AboveOrEqual: + return Below; + case Below: + return AboveOrEqual; + case BelowOrEqual: + return Above; + case Zero: + return NonZero; + case NonZero: + return Zero; + case Signed: + return NotSigned; + case NotSigned: + return Signed; + case SOBit: + return NSOBit; + case NSOBit: + return SOBit; + case Overflow: + return NotOverflow; + case NotOverflow: + return Overflow; + case CarrySet: + return CarryClear; + case CarryClear: + return CarrySet; + default: + MOZ_CRASH("unexpected condition"); + } +} + +Assembler::DoubleCondition Assembler::InvertCondition(DoubleCondition cond) { + switch (cond) { + case DoubleOrdered: + return DoubleUnordered; + case DoubleEqual: + return DoubleNotEqualOrUnordered; + case DoubleNotEqual: + return DoubleEqualOrUnordered; + case DoubleGreaterThan: + return DoubleLessThanOrEqualOrUnordered; + case DoubleGreaterThanOrEqual: + return DoubleLessThanOrUnordered; + case DoubleLessThan: + return DoubleGreaterThanOrEqualOrUnordered; + case DoubleLessThanOrEqual: + return DoubleGreaterThanOrUnordered; + case DoubleUnordered: + return DoubleOrdered; + case DoubleEqualOrUnordered: + return DoubleNotEqual; + case DoubleNotEqualOrUnordered: + return DoubleEqual; + case DoubleGreaterThanOrUnordered: + return DoubleLessThanOrEqual; + case DoubleGreaterThanOrEqualOrUnordered: + return DoubleLessThan; + case DoubleLessThanOrUnordered: + return DoubleGreaterThanOrEqual; + case DoubleLessThanOrEqualOrUnordered: + return DoubleGreaterThan; + default: + MOZ_CRASH("unexpected condition"); + } +} + +// InstImm helper. +uint8_t InstImm::traptag() { + uint8_t r = ((data & 0x001f0000) >> 16); + MOZ_ASSERT(isOpcode(PPC_tw)); + MOZ_ASSERT(r == ((data & 0x0000f800) >> 11)); + return r & 0xfe; +} + +BOffImm16::BOffImm16(InstImm inst) : data(inst.extractImm16Value() & 0xFFFC) { + // Sign-extend the 16-bit field. + if (data & 0x8000) { + data |= ~0xFFFF; + } +} + +Instruction* BOffImm16::getDest(Instruction* src) const { + return (Instruction*)((uint8_t*)src + data); +} + +Instruction* JOffImm26::getDest(Instruction* src) const { + return (Instruction*)((uint8_t*)src + data); +} + +Imm16::Imm16() : value(0) {} + +// Buffer management. +bool Assembler::oom() const { + return AssemblerShared::oom() || m_buffer.oom() || jumpRelocations_.oom() || + dataRelocations_.oom(); +} + +void Assembler::finish() { + MOZ_ASSERT(!isFinished); + isFinished = true; + m_buffer.flushPool(); +} + +bool Assembler::appendRawCode(const uint8_t* code, size_t numBytes) { + return m_buffer.appendRawCode(code, numBytes); +} + +bool Assembler::reserve(size_t size) { + // Fixed-size chunk buffer; no point in reserving now vs. on-demand. + return !oom(); +} + +bool Assembler::swapBuffer(wasm::Bytes& bytes) { + MOZ_ASSERT(bytes.empty()); + if (!bytes.resize(bytesNeeded())) { + return false; + } + m_buffer.executableCopy(bytes.begin()); + return true; +} + +void Assembler::copyJumpRelocationTable(uint8_t* dest) { + if (jumpRelocations_.length()) { + memcpy(dest, jumpRelocations_.buffer(), jumpRelocations_.length()); + } +} + +void Assembler::copyDataRelocationTable(uint8_t* dest) { + if (dataRelocations_.length()) { + memcpy(dest, dataRelocations_.buffer(), dataRelocations_.length()); + } +} + +void Assembler::executableCopy(void* buffer) { + MOZ_ASSERT(isFinished); + m_buffer.executableCopy(static_cast(buffer)); +} + +void Assembler::executableCopy(uint8_t* buffer) { + MOZ_ASSERT(isFinished); + m_buffer.executableCopy(buffer); +} + +size_t Assembler::size() const { + // AssemblerBufferWithConstantPools::size() asserts pool is empty. + // Flush pending pool entries first. + const_cast(m_buffer).flushPool(); + return m_buffer.size(); +} + +size_t Assembler::jumpRelocationTableBytes() const { + return jumpRelocations_.length(); +} + +size_t Assembler::dataRelocationTableBytes() const { + return dataRelocations_.length(); +} + +size_t Assembler::bytesNeeded() const { + return size() + jumpRelocationTableBytes() + dataRelocationTableBytes(); +} + +// Write an instruction into the buffer or to an external destination. +BufferOffset Assembler::writeInst(uint32_t x, uint32_t* dest) { + MOZ_ASSERT(hasCreator()); + if (dest == nullptr) { + return m_buffer.putInt(x); + } + + WriteInstStatic(x, dest); + return BufferOffset(); +} + +void Assembler::WriteInstStatic(uint32_t x, uint32_t* dest) { + MOZ_ASSERT(dest != nullptr); + *dest = x; +} + +// Alignment. +BufferOffset Assembler::haltingAlign(int alignment) { + BufferOffset ret; + MOZ_ASSERT(m_buffer.isAligned(4)); + if (alignment == 8) { + if (!m_buffer.isAligned(alignment)) { + BufferOffset tmp = xs_trap(); + if (!ret.assigned()) { + ret = tmp; + } + } + } else { + MOZ_ASSERT((alignment & (alignment - 1)) == 0); + while (size() & (alignment - 1)) { + BufferOffset tmp = xs_trap(); + if (!ret.assigned()) { + ret = tmp; + } + } + } + return ret; +} + +BufferOffset Assembler::nopAlign(int alignment) { + BufferOffset ret; + MOZ_ASSERT(m_buffer.isAligned(4)); + if (alignment == 8) { + if (!m_buffer.isAligned(alignment)) { + BufferOffset tmp = as_nop(); + if (!ret.assigned()) { + ret = tmp; + } + } + } else { + MOZ_ASSERT((alignment & (alignment - 1)) == 0); + while (size() & (alignment - 1)) { + BufferOffset tmp = as_nop(); + if (!ret.assigned()) { + ret = tmp; + } + } + } + return ret; +} + +// Primitive instructions. +BufferOffset Assembler::as_nop() { + spew("nop"); + return writeInst(PPC_nop); +} + +BufferOffset Assembler::as_lwsync() { + spew("lwsync"); + return writeInst(PPC_lwsync); +} + +BufferOffset Assembler::as_sync() { + spew("sync"); + return writeInst(PPC_sync); +} + +BufferOffset Assembler::as_isync() { + spew("isync"); + return writeInst(PPC_isync); +} + +// Branch and jump instructions. +BufferOffset Assembler::as_b(JOffImm26 off, BranchAddressType bat, LinkBit lb) { + return as_b(off.encode(), bat, lb); +} + +BufferOffset Assembler::as_b(int32_t off, BranchAddressType bat, LinkBit lb) { + spew("b%s%s\t%x", bat == AbsoluteBranch ? "a" : "", lb ? "l" : "", off); + MOZ_ASSERT(!(off & 0x03)); + return writeInst(PPC_b | ((uint32_t)off & 0x3fffffc) | bat | lb); +} + +BufferOffset Assembler::as_blr(LinkBit lb) { + spew("blr%s", lb ? "l" : ""); + return writeInst(uint32_t(PPC_blr) | uint32_t(lb)); +} + +BufferOffset Assembler::as_bctr(LinkBit lb) { + spew("bctr%s", lb ? "l" : ""); + return writeInst(uint32_t(PPC_bctr) | uint32_t(lb)); +} + +// Conditional branches. +BufferOffset Assembler::as_bc(BOffImm16 off, Condition cond, CRegisterID cr, + LikelyBit lkb, LinkBit lb) { + return as_bc(off.encode(), cond, cr, lkb, lb); +} + +BufferOffset Assembler::as_bc(int16_t off, Condition cond, CRegisterID cr, + LikelyBit lkb, LinkBit lb) { + return as_bc(off, computeConditionCode(cond, cr), lkb, lb); +} + +BufferOffset Assembler::as_bc(BOffImm16 off, DoubleCondition cond, + CRegisterID cr, LikelyBit lkb, LinkBit lb) { + return as_bc(off.encode(), cond, cr, lkb, lb); +} + +BufferOffset Assembler::as_bc(int16_t off, DoubleCondition cond, CRegisterID cr, + LikelyBit lkb, LinkBit lb) { + return as_bc(off, computeConditionCode(cond, cr), lkb, lb); +} + +BufferOffset Assembler::as_bcctr(Condition cond, CRegisterID cr, LikelyBit lkb, + LinkBit lb) { + return as_bcctr(computeConditionCode(cond, cr), lkb, lb); +} + +BufferOffset Assembler::as_bcctr(DoubleCondition cond, CRegisterID cr, + LikelyBit lkb, LinkBit lb) { + return as_bcctr(computeConditionCode(cond, cr), lkb, lb); +} + +// Condition code computation: turn DoubleCondition + CR into BO|BI. +// May emit CR logic instructions for synthetic conditions involving FU bit. +uint16_t Assembler::computeConditionCode(DoubleCondition op, CRegisterID cr) { + const uint8_t condBit = crBit(cr, op); + const uint8_t fuBit = crBit(cr, DoubleUnordered); + uint32_t newop = (uint32_t)op & 255; + + if (op & DoubleConditionUnordered) { + if ((uint32_t(op) & BranchOptionMask) == BranchOnClear) { + as_crorc(condBit, fuBit, condBit); + newop |= BranchOnSet; + } else { + if (condBit != fuBit) { + as_cror(condBit, fuBit, condBit); + } + } + } else { + if ((uint32_t(op) & BranchOptionMask) == BranchOnClear) { + if (condBit != fuBit) { + as_cror(condBit, fuBit, condBit); + } + } else { + if (condBit != fuBit) { + as_crandc(condBit, condBit, fuBit); + } + } + } + + return (newop + ((uint8_t)cr << 6)); +} + +// Condition code computation: turn Condition + CR into BO|BI. +// May emit mcrxrx for XER-mediated conditions. +uint16_t Assembler::computeConditionCode(Condition op, CRegisterID cr) { + uint32_t newop = (uint32_t)op & 255; + + if (op & ConditionOnlyXER) { + MOZ_ASSERT(op == Overflow || op == NotOverflow); + // There is no mcrxrx, so read XER and place OV into the GT position of + // the target CR field: the Overflow condition (0x1c = GreaterThan) + // tests the GT bit. XER layout in the GPR (IBM bit numbering): + // bit 0 = SO, 1 = OV, 2 = CA; GT sits at IBM bit 4*cr+1. + xs_mfxer(r0); + int gtBit = 4 * (int)cr + 1; // GT position in CR field + int sh = (1 - gtBit) & 31; // rotate OV from bit 1 to gtBit + as_rlwinm(r0, r0, sh, gtBit, gtBit); // isolate OV at GT only + as_mtcrf(1 << (7 - (int)cr), r0); + newop = (uint32_t)op & 255; + } + + return (newop + ((uint8_t)cr << 6)); +} + +// Given BO|BI in a 16-bit quantity, split into bit fields for instruction. +static uint32_t makeOpMask(uint16_t op) { + MOZ_ASSERT(!(op & 0xfc00)); + return ((op & 0x0f) << 21) | ((op & 0xfff0) << 12); +} + +BufferOffset Assembler::as_bc(int16_t off, uint16_t op, LikelyBit lkb, + LinkBit lb) { + spew("bc%s%s\tBO_BI=0x%04x,%d", lb ? "l" : "", lkb ? "+" : "", op, off); + MOZ_ASSERT(!(off & 0x03)); + return writeInst(Instruction(PPC_bc | makeOpMask(op) | lkb << 21 | + ((uint16_t)off & 0xfffc) | lb) + .encode()); +} + +BufferOffset Assembler::as_bcctr(uint16_t op, LikelyBit lkb, LinkBit lb) { + spew("bcctr%s%s", lb ? "l" : "", lkb ? "+" : ""); + return writeInst(PPC_bcctr | makeOpMask(op) | lkb << 21 | lb); +} + +// SPR operations. +BufferOffset Assembler::as_mtspr(SPRegisterID spr, Register ra) { + spew("mtspr\t%d,%3s", spr, ra.name()); + return writeInst(PPC_mtspr | ra.code() << 21 | PPC_SPR(spr)); +} + +BufferOffset Assembler::as_mfspr(Register rd, SPRegisterID spr) { + spew("mfspr\t%3s,%d", rd.name(), spr); + return writeInst(PPC_mfspr | rd.code() << 21 | PPC_SPR(spr)); +} + +// CR operations. +#define DEF_CRCR(op) \ + BufferOffset Assembler::as_##op(uint8_t t, uint8_t a, uint8_t b) { \ + spew(#op "\t%d,%d,%d", t, a, b); \ + return writeInst(PPC_##op | t << 21 | a << 16 | b << 11); \ + } +DEF_CRCR(crandc) +DEF_CRCR(cror) +DEF_CRCR(crorc) +#undef DEF_CRCR + +BufferOffset Assembler::as_mtcrf(uint32_t mask, Register rs) { + spew("mtcrf\t%d,%3s", mask, rs.name()); + return writeInst(PPC_mtcrf | rs.code() << 21 | mask << 12); +} + +BufferOffset Assembler::as_mfocrf(Register rd, CRegisterID crfs) { + spew("mfocrf\t%3s,cr%d", rd.name(), crfs); + // FXM is a one-hot 8-bit mask at bits 12-19. Bit (7-crfs) selects the CR. + return writeInst(PPC_mfocrf | rd.code() << 21 | (1 << (7 - crfs)) << 12); +} + +BufferOffset Assembler::as_mcrxrx(CRegisterID cr) { + spew("mcrxrx\tcr%d", cr); + return writeInst(PPC_mcrxrx | cr << 23); +} + +// GPR neg. +BufferOffset Assembler::as_neg(Register rd, Register rs) { + spew("neg\t%3s,%3s", rd.name(), rs.name()); + return writeInst(InstReg(PPC_neg, rd, rs, r0).encode()); +} + +// Compare instructions. The doubleword spellings emit the word form: the L +// bit is always 0 on this target, and a pointer-sized compare is 32 bits. +BufferOffset Assembler::as_cmpd(CRegisterID cr, Register ra, Register rb) { + return as_cmpw(cr, ra, rb); +} + +BufferOffset Assembler::as_cmpdi(CRegisterID cr, Register ra, int16_t im) { + return as_cmpwi(cr, ra, im); +} + +BufferOffset Assembler::as_cmpld(CRegisterID cr, Register ra, Register rb) { + return as_cmplw(cr, ra, rb); +} + +BufferOffset Assembler::as_cmpldi(CRegisterID cr, Register ra, int16_t im) { + return as_cmplwi(cr, ra, im); +} + +BufferOffset Assembler::as_cmpw(CRegisterID cr, Register ra, Register rb) { + spew("cmpw\tcr%d,%3s,%3s", cr, ra.name(), rb.name()); + return writeInst(PPC_cmpw | cr << 23 | ra.code() << 16 | rb.code() << 11); +} + +BufferOffset Assembler::as_cmpwi(CRegisterID cr, Register ra, int16_t im) { + spew("cmpwi\tcr%d,%3s,%d", cr, ra.name(), im); + return writeInst(PPC_cmpwi | cr << 23 | ra.code() << 16 | + ((uint16_t)im & 0xffff)); +} + +BufferOffset Assembler::as_cmplw(CRegisterID cr, Register ra, Register rb) { + spew("cmplw\tcr%d,%3s,%3s", cr, ra.name(), rb.name()); + return writeInst(PPC_cmplw | cr << 23 | ra.code() << 16 | rb.code() << 11); +} + +BufferOffset Assembler::as_cmplwi(CRegisterID cr, Register ra, int16_t im) { + spew("cmplwi\tcr%d,%3s,%d", cr, ra.name(), im); + return writeInst(PPC_cmplwi | cr << 23 | ra.code() << 16 | + ((uint16_t)im & 0xffff)); +} + +// Compare instructions (cr0 implicit). +BufferOffset Assembler::as_cmpd(Register ra, Register rb) { + return as_cmpw(ra, rb); +} + +BufferOffset Assembler::as_cmpdi(Register ra, int16_t im) { + return as_cmpwi(ra, im); +} + +BufferOffset Assembler::as_cmpld(Register ra, Register rb) { + return as_cmplw(ra, rb); +} + +BufferOffset Assembler::as_cmpldi(Register ra, int16_t im) { + return as_cmplwi(ra, im); +} + +BufferOffset Assembler::as_cmpw(Register ra, Register rb) { + spew("cmpw\t%3s,%3s", ra.name(), rb.name()); + return writeInst(PPC_cmpw | ra.code() << 16 | rb.code() << 11); +} + +BufferOffset Assembler::as_cmpwi(Register ra, int16_t im) { + spew("cmpwi\t%3s,%d", ra.name(), im); + return writeInst(PPC_cmpwi | ra.code() << 16 | ((uint16_t)im & 0xffff)); +} + +BufferOffset Assembler::as_cmplw(Register ra, Register rb) { + spew("cmplw\t%3s,%3s", ra.name(), rb.name()); + return writeInst(PPC_cmplw | ra.code() << 16 | rb.code() << 11); +} + +BufferOffset Assembler::as_cmplwi(Register ra, int16_t im) { + spew("cmplwi\t%3s,%d", ra.name(), im); + return writeInst(PPC_cmplwi | ra.code() << 16 | ((uint16_t)im & 0xffff)); +} + +// FP encoding helpers. +static uint32_t AForm(uint32_t op, FloatRegister frt, FloatRegister fra, + FloatRegister frb, FloatRegister frc, bool rc) { + return (op | (frt.encoding() << 21) | (fra.encoding() << 16) | + (frb.encoding() << 11) | (frc.encoding() << 6) | rc); +} + +static uint32_t XForm(uint32_t op, FloatRegister frt, FloatRegister fra, + FloatRegister frb, bool rc) { + return (op | (frt.encoding() << 21) | (fra.encoding() << 16) | + (frb.encoding() << 11) | rc); +} + +static uint32_t XForm(uint32_t op, FloatRegister frt, Register ra, Register rb, + bool rc) { + return (op | (frt.encoding() << 21) | (ra.code() << 16) | (rb.code() << 11) | + rc); +} + +static uint32_t DForm(uint32_t op, FloatRegister frt, Register ra, + int16_t imm) { + return (op | (frt.encoding() << 21) | (ra.code() << 16) | + ((uint16_t)imm & 0xffff)); +} + +// VMX encoding helpers. A Simd128 FloatRegister names one of VR0-VR31; its +// encoding() is masked to the 5-bit VR field. VR0-VR31 are a register file +// of their own, distinct from FPR0-FPR31. +static uint32_t VR(FloatRegister v) { return uint32_t(v.encoding()) & 31; } + +// VX-form: (4<<26) | VRT<<21 | VRA<<16 | VRB<<11 | XO +static uint32_t VXForm(uint32_t op, FloatRegister vrt, FloatRegister vra, + FloatRegister vrb) { + return op | (VR(vrt) << 21) | (VR(vra) << 16) | (VR(vrb) << 11); +} + +// VX-form unary: the VRA field is unused (or carries a UIM immediate). +static uint32_t VXForm2(uint32_t op, FloatRegister vrt, FloatRegister vrb, + uint32_t bits16to20 = 0) { + return op | (VR(vrt) << 21) | ((bits16to20 & 31) << 16) | (VR(vrb) << 11); +} + +// VA-form: (4<<26) | VRT<<21 | VRA<<16 | VRB<<11 | VRC<<6 | XO(6-bit) +static uint32_t VAForm(uint32_t op, FloatRegister vrt, FloatRegister vra, + FloatRegister vrb, FloatRegister vrc) { + return op | (VR(vrt) << 21) | (VR(vra) << 16) | (VR(vrb) << 11) | + (VR(vrc) << 6); +} + +// --- Macro-defined instruction emitters --- + +// X-form: rd in bits 21-25, ra in 16-20, rb in 11-15. +#define DEF_XFORM(op) \ + BufferOffset Assembler::as_##op(Register rd, Register ra, Register rb) { \ + spew(#op "\t%3s,%3s,%3s", rd.name(), ra.name(), rb.name()); \ + return writeInst(InstReg(PPC_##op, rd, ra, rb).encode()); \ + } + +#define DEF_XFORM_RC(op) \ + BufferOffset Assembler::as_##op##_rc(Register rd, Register ra, \ + Register rb) { \ + spew(#op ".\t%3s,%3s,%3s", rd.name(), ra.name(), rb.name()); \ + return writeInst(InstReg(PPC_##op, rd, ra, rb).encode() | 0x1); \ + } + +// X-form with swapped RS/RA encoding: rs in bits 21-25, ra in 16-20. +#define DEF_XFORMS(op) \ + BufferOffset Assembler::as_##op(Register rd, Register ra, Register rb) { \ + spew(#op "\t%3s,%3s,%3s", rd.name(), ra.name(), rb.name()); \ + return writeInst(InstReg(PPC_##op, ra, rd, rb).encode()); \ + } + +#define DEF_XFORMS_RC(op) \ + BufferOffset Assembler::as_##op##_rc(Register rd, Register ra, \ + Register rb) { \ + spew(#op ".\t%3s,%3s,%3s", rd.name(), ra.name(), rb.name()); \ + return writeInst(InstReg(PPC_##op, ra, rd, rb).encode() | 0x1); \ + } + +// X-form shift immediate with swapped encoding. +#define DEF_XFORMS_I(op) \ + BufferOffset Assembler::as_##op(Register rd, Register ra, uint8_t sh) { \ + spew(#op "\t%3s,%3s,%d", rd.name(), ra.name(), sh); \ + MOZ_ASSERT(sh < 32); \ + return writeInst(PPC_##op | ra.code() << 21 | rd.code() << 16 | sh << 11); \ + } + +// 2-reg X-form: rd in bits 21-25, ra in 16-20, rb=r0. +#define DEF_XFORM2(op) \ + BufferOffset Assembler::as_##op(Register rd, Register ra) { \ + spew(#op "\t%3s,%3s", rd.name(), ra.name()); \ + return writeInst(InstReg(PPC_##op, rd, ra, r0).encode()); \ + } + +#define DEF_XFORM2_RC(op) \ + BufferOffset Assembler::as_##op##_rc(Register rd, Register ra) { \ + spew(#op ".\t%3s,%3s", rd.name(), ra.name()); \ + return writeInst(InstReg(PPC_##op, rd, ra, r0).encode() | 0x1); \ + } + +// 2-reg X-form swapped: ra in bits 21-25, rd in 16-20. +#define DEF_XFORM2S(op) \ + BufferOffset Assembler::as_##op(Register rd, Register ra) { \ + spew(#op "\t%3s,%3s", rd.name(), ra.name()); \ + return writeInst(InstReg(PPC_##op, ra, rd, r0).encode()); \ + } + +#define DEF_XFORM2S_RC(op) \ + BufferOffset Assembler::as_##op##_rc(Register rd, Register ra) { \ + spew(#op ".\t%3s,%3s", rd.name(), ra.name()); \ + return writeInst(InstReg(PPC_##op, ra, rd, r0).encode() | 0x1); \ + } + +// D-form load/store: rd=RT, rb=RA (base register), off=displacement. +// r0 cannot be used as base register for D-form loads/stores. +#define DEF_DFORM(op) \ + BufferOffset Assembler::as_##op(Register rd, Register rb, int16_t off) { \ + spew(#op "\t%3s,%d(%3s)", rd.name(), off, rb.name()); \ + MOZ_ASSERT(rb != r0); \ + return writeInst(InstImm(PPC_##op, rd, rb, off).encode()); \ + } + +// D-form with swapped RS/RA encoding for logical immediates. +#define DEF_DFORMS(op) \ + BufferOffset Assembler::as_##op(Register rd, Register ra, uint16_t im) { \ + spew(#op "\t%3s,%3s,%d", rd.name(), ra.name(), im); \ + return writeInst(InstImm(PPC_##op, ra, rd, im).encode()); \ + } + +// M-form: rotate with 3 registers + mb + me. +#define DEF_MFORM(op) \ + BufferOffset Assembler::as_##op(Register rd, Register rs, Register rb, \ + uint8_t mb, uint8_t me) { \ + spew(#op "\t%3s,%3s,%3s,%d,%d", rd.name(), rs.name(), rb.name(), mb, me); \ + MOZ_ASSERT(mb < 32); \ + MOZ_ASSERT(me < 32); \ + return writeInst(PPC_##op | rs.code() << 21 | rd.code() << 16 | \ + rb.code() << 11 | mb << 6 | me << 1); \ + } + +// M-form with immediate shift. +#define DEF_MFORM_I(op) \ + BufferOffset Assembler::as_##op(Register rd, Register rs, uint8_t sh, \ + uint8_t mb, uint8_t me) { \ + spew(#op "\t%3s,%3s,%d,%d,%d", rd.name(), rs.name(), sh, mb, me); \ + MOZ_ASSERT(sh < 32); \ + MOZ_ASSERT(mb < 32); \ + MOZ_ASSERT(me < 32); \ + return writeInst(PPC_##op | rs.code() << 21 | rd.code() << 16 | sh << 11 | \ + mb << 6 | me << 1); \ + } + +#define DEF_MFORM_I_RC(op) \ + BufferOffset Assembler::as_##op##_rc(Register rd, Register rs, uint8_t sh, \ + uint8_t mb, uint8_t me) { \ + spew(#op ".\t%3s,%3s,%d,%d,%d", rd.name(), rs.name(), sh, mb, me); \ + MOZ_ASSERT(sh < 32); \ + MOZ_ASSERT(mb < 32); \ + MOZ_ASSERT(me < 32); \ + return writeInst(PPC_##op | rs.code() << 21 | rd.code() << 16 | sh << 11 | \ + mb << 6 | me << 1 | 1); \ + } + +// MDS-form: rotate with register + mb (64-bit). +#define DEF_MDSFORM(op) \ + BufferOffset Assembler::as_##op(Register ra, Register rs, Register rb, \ + uint8_t mb) { \ + spew(#op "\t%3s,%3s,%3s,%d", ra.name(), rs.name(), rb.name(), mb); \ + MOZ_ASSERT(mb < 64); \ + return writeInst(PPC_##op | rs.code() << 21 | ra.code() << 16 | \ + rb.code() << 11 | ((mb & 0x1f) << 6) | (mb & 0x20)); \ + } + +#define DEF_MDSFORM_RC(op) \ + BufferOffset Assembler::as_##op##_rc(Register ra, Register rs, Register rb, \ + uint8_t mb) { \ + spew(#op ".\t%3s,%3s,%3s,%d", ra.name(), rs.name(), rb.name(), mb); \ + MOZ_ASSERT(mb < 64); \ + return writeInst(PPC_##op | rs.code() << 21 | ra.code() << 16 | \ + rb.code() << 11 | ((mb & 0x1f) << 6) | (mb & 0x20) | 1); \ + } + +// MD-form: rotate/shift with immediate sh + mb (64-bit). +// sh and mb are 6-bit fields split across the instruction word. +#define DEF_MDFORM(op) \ + BufferOffset Assembler::as_##op(Register ra, Register rs, uint8_t sh, \ + uint8_t mb) { \ + spew(#op "\t%3s,%3s,%d,%d", ra.name(), rs.name(), sh, mb); \ + MOZ_ASSERT(sh < 64); \ + MOZ_ASSERT(mb < 64); \ + return writeInst(PPC_##op | rs.code() << 21 | ra.code() << 16 | \ + ((sh & 0x1f) << 11) | ((mb & 0x1f) << 6) | (mb & 0x20) | \ + ((sh & 0x20) >> 4)); \ + } + +#define DEF_MDFORM_RC(op) \ + BufferOffset Assembler::as_##op##_rc(Register ra, Register rs, uint8_t sh, \ + uint8_t mb) { \ + spew(#op ".\t%3s,%3s,%d,%d", ra.name(), rs.name(), sh, mb); \ + MOZ_ASSERT(sh < 64); \ + MOZ_ASSERT(mb < 64); \ + return writeInst(PPC_##op | rs.code() << 21 | ra.code() << 16 | \ + ((sh & 0x1f) << 11) | ((mb & 0x1f) << 6) | (mb & 0x20) | \ + ((sh & 0x20) >> 4) | 0x01); \ + } + +// FP 2-reg X-form: frt in bits 21-25, fra=f0, frb in 11-15. +#define DEF_XFORM2_F(op) \ + BufferOffset Assembler::as_##op(FloatRegister rd, FloatRegister ra) { \ + spew(#op "\t%3s,%3s", rd.name(), ra.name()); \ + return writeInst(XForm(PPC_##op, rd, f0, ra, false)); \ + } + +#define DEF_XFORM2_F_RC(op) \ + BufferOffset Assembler::as_##op##_rc(FloatRegister rd, FloatRegister ra) { \ + spew(#op ".\t%3s,%3s", rd.name(), ra.name()); \ + return writeInst(XForm(PPC_##op, rd, f0, ra, true)); \ + } + +// FP A-form with frc (fmul-type): frt, fra, frc; frb=f0. +#define DEF_AFORM_C(op) \ + BufferOffset Assembler::as_##op(FloatRegister rd, FloatRegister ra, \ + FloatRegister rc) { \ + spew(#op "\t%3s,%3s,%3s", rd.name(), ra.name(), rc.name()); \ + return writeInst(AForm(PPC_##op, rd, ra, f0, rc, false)); \ + } + +#define DEF_AFORM_C_RC(op) \ + BufferOffset Assembler::as_##op##_rc(FloatRegister rd, FloatRegister ra, \ + FloatRegister rc) { \ + spew(#op ".\t%3s,%3s,%3s", rd.name(), ra.name(), rc.name()); \ + return writeInst(AForm(PPC_##op, rd, ra, f0, rc, true)); \ + } + +// FP A-form with frb (fadd-type): frt, fra, frb; frc=f0. +#define DEF_AFORM_B(op) \ + BufferOffset Assembler::as_##op(FloatRegister rd, FloatRegister ra, \ + FloatRegister rb) { \ + spew(#op "\t%3s,%3s,%3s", rd.name(), ra.name(), rb.name()); \ + return writeInst(AForm(PPC_##op, rd, ra, rb, f0, false)); \ + } + +#define DEF_AFORM_B_RC(op) \ + BufferOffset Assembler::as_##op##_rc(FloatRegister rd, FloatRegister ra, \ + FloatRegister rb) { \ + spew(#op ".\t%3s,%3s,%3s", rd.name(), ra.name(), rb.name()); \ + return writeInst(AForm(PPC_##op, rd, ra, rb, f0, true)); \ + } + +// Full FP A-form: frt, fra, frc, frb (fmadd-type). +#define DEF_AFORM(op) \ + BufferOffset Assembler::as_##op(FloatRegister rd, FloatRegister ra, \ + FloatRegister rc, FloatRegister rb) { \ + spew(#op "\t%3s,%3s,%3s,%3s", rd.name(), ra.name(), rc.name(), rb.name()); \ + return writeInst(AForm(PPC_##op, rd, ra, rb, rc, false)); \ + } + +#define DEF_AFORM_RC(op) \ + BufferOffset Assembler::as_##op##_rc(FloatRegister rd, FloatRegister ra, \ + FloatRegister rc, FloatRegister rb) { \ + spew(#op ".\t%3s,%3s,%3s,%3s", rd.name(), ra.name(), rc.name(), \ + rb.name()); \ + return writeInst(AForm(PPC_##op, rd, ra, rb, rc, true)); \ + } + +// FP D-form load/store. +#define DEF_DFORM_F(op) \ + BufferOffset Assembler::as_##op(FloatRegister rd, Register rb, \ + int16_t off) { \ + spew(#op "\t%3s,%d(%3s)", rd.name(), off, rb.name()); \ + MOZ_ASSERT(rb != r0); \ + return writeInst(DForm(PPC_##op, rd, rb, off)); \ + } + +// FP X-form indexed load/store. +#define DEF_FMEMx(op) \ + BufferOffset Assembler::as_##op(FloatRegister rd, Register ra, \ + Register rb) { \ + spew(#op "\t%3s,%3s,%3s", rd.name(), ra.name(), rb.name()); \ + return writeInst(XForm(PPC_##op, rd, ra, rb, false)); \ + } + +// --- Rotate/shift instructions --- + +DEF_MFORM(rlwnm) +DEF_MFORM_I(rlwinm) +DEF_MFORM_I_RC(rlwinm) +DEF_MFORM_I(rlwimi) +DEF_XFORMS_I(srawi) + +DEF_MDSFORM(rldcl) +DEF_MDFORM(rldicl) +DEF_MDFORM_RC(rldicl) +DEF_MDFORM(rldicr) +DEF_MDFORM_RC(rldicr) +DEF_MDFORM(rldimi) + +BufferOffset Assembler::as_sradi(Register rd, Register rs, int sh) { + spew("sradi\t%3s,%3s,%d", rd.name(), rs.name(), sh); + MOZ_ASSERT(sh >= 0 && sh < 64); + return writeInst(PPC_sradi | rd.code() << 16 | rs.code() << 21 | + (sh & 0x1f) << 11 | (sh & 0x20) >> 4); +} + +// --- ALU three-register --- + +#define DEF_ALU2(op) DEF_XFORM(op) + +DEF_ALU2(add) +DEF_ALU2(addc) +DEF_ALU2(adde) +DEF_ALU2(subf) +DEF_ALU2(subfc) +DEF_ALU2(subfe) +DEF_ALU2(divd) +DEF_ALU2(divdu) +DEF_ALU2(divw) +DEF_ALU2(divwu) +// POWER9 modulo (XO-form, same encoding pattern as div). +DEF_XFORM(modsd) +DEF_XFORM(modsw) +DEF_XFORM(modud) +DEF_XFORM(moduw) +DEF_ALU2(mulld) +DEF_ALU2(mulhd) +DEF_ALU2(mulhdu) +DEF_ALU2(mulldo) +DEF_ALU2(mullw) +DEF_ALU2(mulhwu) +#undef DEF_ALU2 + +// --- ALU immediate --- + +// D-form ALU-immediate ops have no Rc bit at instruction LSB (that bit +// is part of the 16-bit immediate). The only valid record-form variant +// in this group is `addic.`, which is a separate primary opcode (13) +// hand-written below; subfic and mulli have no record form at all. +#define DEF_ALUI(op) \ + BufferOffset Assembler::as_##op(Register rd, Register ra, int16_t im) { \ + spew(#op "\t%3s,%3s,%d", rd.name(), ra.name(), im); \ + return writeInst(InstImm(PPC_##op, rd, ra, im).encode()); \ + } + +BufferOffset Assembler::as_addi(Register rd, Register ra, int16_t im, + bool actually_li) { +#ifdef DEBUG + if (actually_li) { + spew("li\t%3s,%d", rd.name(), im); + } else { + MOZ_ASSERT(ra != r0); + spew("addi\t%3s,%3s,%d", rd.name(), ra.name(), im); + } +#endif + return writeInst(InstImm(PPC_addi, rd, ra, im).encode()); +} + +BufferOffset Assembler::as_addis(Register rd, Register ra, int16_t im, + bool actually_lis) { +#ifdef DEBUG + if (actually_lis) { + spew("lis\t%3s,%d", rd.name(), im); + } else { + MOZ_ASSERT(ra != r0); + spew("addis\t%3s,%3s,%d", rd.name(), ra.name(), im); + } +#endif + return writeInst(InstImm(PPC_addis, rd, ra, im).encode()); +} + +DEF_ALUI(mulli) +DEF_ALUI(subfic) +#undef DEF_ALUI + +// --- ALU unary/extended --- + + +#define DEF_ALUE_S(op) DEF_XFORM2S(op) +DEF_ALUE_S(cntlzw) +DEF_ALUE_S(cntlzd) +DEF_ALUE_S(cnttzd) +DEF_ALUE_S(cnttzw) +#undef DEF_ALUE_S + +DEF_XFORM2S(popcntd) +DEF_XFORM2S(popcntw) +DEF_XFORM2S(brd) // POWER10 +DEF_XFORM2S(brh) // POWER10 +DEF_XFORM2S(brw) // POWER10 + +// --- Bitwise logical (three-register) --- + +#define DEF_BITALU2(op) DEF_XFORMS(op) +DEF_BITALU2(nor) +DEF_BITALU2(slw) +DEF_BITALU2(srw) +DEF_BITALU2(sraw) +DEF_BITALU2(sld) +DEF_BITALU2(srd) +DEF_BITALU2(srad) +#undef DEF_BITALU2 + +// and_, or_, xor_ are manually defined (trailing underscore to avoid C++ +// keyword conflicts). xs_mr delegates to as_or_ so we must not assert +// rd==rs==rb in as_or_ (which would be a valid mr). +BufferOffset Assembler::as_or_(Register rd, Register rs, Register rb) { + spew("or\t%3s,%3s,%3s", rd.name(), rs.name(), rb.name()); + return writeInst(InstReg(PPC_or_, rs, rd, rb).encode()); +} + +BufferOffset Assembler::as_xor_(Register rd, Register rs, Register rb) { + spew("xor\t%3s,%3s,%3s", rd.name(), rs.name(), rb.name()); + return writeInst(InstReg(PPC_xor_, rs, rd, rb).encode()); +} + +BufferOffset Assembler::as_and_(Register rd, Register rs, Register rb) { + spew("and\t%3s,%3s,%3s", rd.name(), rs.name(), rb.name()); + return writeInst(InstReg(PPC_and_, rs, rd, rb).encode()); +} + +BufferOffset Assembler::as_and__rc(Register rd, Register rs, Register rb) { + spew("and.\t%3s,%3s,%3s", rd.name(), rs.name(), rb.name()); + return writeInst(InstReg(PPC_and_, rs, rd, rb).encode() | 0x1); +} + +// --- Bitwise logical (immediate) --- + +DEF_DFORMS(ori) +DEF_DFORMS(oris) +DEF_DFORMS(xori) +DEF_DFORMS(xoris) + +BufferOffset Assembler::as_andi_rc(Register rd, Register ra, uint16_t im) { + spew("andi.\t%3s,%3s,%d", rd.name(), ra.name(), im); + return writeInst(InstImm(PPC_andi_dot, ra, rd, im).encode()); +} + +// --- Sign extension --- + +#define DEF_ALUEXT(op) DEF_XFORM2S(op) DEF_XFORM2S_RC(op) +DEF_XFORM2S(extsb) +DEF_XFORM2S(extsh) +DEF_ALUEXT(extsw) +#undef DEF_ALUEXT + +// --- Integer loads (D-form) --- + +DEF_DFORM(lbz) +DEF_DFORM(lha) +DEF_DFORM(lhz) + +BufferOffset Assembler::as_lwa(Register rd, Register rb, int16_t off) { + spew("lwa\t%3s,%d(%3s)", rd.name(), off, rb.name()); + MOZ_ASSERT(rb != r0); + MOZ_ASSERT(!(off & 0x03)); + return writeInst(InstImm(PPC_lwa, rd, rb, off).encode()); +} + +DEF_DFORM(lwz) + +BufferOffset Assembler::as_ld(Register rd, Register rb, int16_t off) { + spew("ld\t%3s,%d(%3s)", rd.name(), off, rb.name()); + MOZ_ASSERT(rb != r0); + MOZ_ASSERT(!(off & 0x03)); + return writeInst(InstImm(PPC_ld, rd, rb, off).encode()); +} + +// --- Integer stores (D-form) --- + +DEF_DFORM(stb) +DEF_DFORM(sth) +DEF_DFORM(stw) +DEF_DFORM(stwu) + +BufferOffset Assembler::as_std(Register rd, Register rb, int16_t off) { + spew("std\t%3s,%d(%3s)", rd.name(), off, rb.name()); + MOZ_ASSERT(rb != r0); + MOZ_ASSERT(!(off & 0x03)); + return writeInst(InstImm(PPC_std, rd, rb, off).encode()); +} + +// stdu is DS-form: the displacement is a 14-bit field and its low two bits +// must be zero, so it needs the alignment guard the generic macro omits. +BufferOffset Assembler::as_stdu(Register rd, Register rb, int16_t off) { + spew("stdu\t%3s,%d(%3s)", rd.name(), off, rb.name()); + MOZ_ASSERT(rb != r0); + MOZ_ASSERT(!(off & 0x03)); + return writeInst(InstImm(PPC_stdu, rd, rb, off).encode()); +} + +#undef DEF_DFORM +#undef DEF_DFORMS + +// --- Integer loads/stores (X-form, indexed) --- + +#define DEF_MEMx(op) DEF_XFORM(op) +DEF_MEMx(lbzx) DEF_MEMx(lhax) DEF_MEMx(lhzx) DEF_MEMx(lwax) + DEF_MEMx(lwzx) DEF_MEMx(lwarx) DEF_MEMx(lbarx) + DEF_MEMx(lharx) DEF_MEMx(ldx) DEF_MEMx(ldarx) DEF_MEMx(stbx) + DEF_MEMx(stbcx) DEF_MEMx(stwx) DEF_MEMx(stwbrx) DEF_MEMx(sthx) + DEF_MEMx(sthcx) DEF_MEMx(stdx) DEF_MEMx(stdcx) + DEF_MEMx(stwcx) DEF_MEMx(lhbrx) DEF_MEMx(lwbrx) + DEF_MEMx(ldbrx) DEF_MEMx(sthbrx) DEF_MEMx(stdbrx) +#undef DEF_MEMx + +// --- Integer select --- + +BufferOffset Assembler::as_isel(Register rt, Register ra, Register rb, + uint16_t bc, CRegisterID cr) { + MOZ_ASSERT(ra != r0); + return as_isel0(rt, ra, rb, bc, cr); +} + +BufferOffset Assembler::as_isel0(Register rt, Register ra, Register rb, + uint16_t bc, CRegisterID cr) { + spew("isel\t%3s,%3s,%3s,cr%d:0x%02x", rt.name(), ra.name(), rb.name(), cr, + bc); + MOZ_ASSERT((bc < 0x40) && ((bc & 0x0f) == 0x0c)); + uint16_t nbc = (bc >> 4) + (cr << 2); + return writeInst(PPC_isel | rt.code() << 21 | ra.code() << 16 | + rb.code() << 11 | nbc << 6); +} + +BufferOffset Assembler::as_setbc(Register rt, uint16_t bc, CRegisterID cr) { + spew("setbc\t%3s,cr%d:0x%02x", rt.name(), cr, bc); + MOZ_ASSERT((bc < 0x40) && ((bc & 0x0f) == 0x0c)); + uint16_t nbc = (bc >> 4) + (cr << 2); + return writeInst(PPC_setbc | (rt.code() << 21) | (nbc << 16)); +} + +BufferOffset Assembler::as_setbcr(Register rt, uint16_t bc, CRegisterID cr) { + spew("setbcr\t%3s,cr%d:0x%02x", rt.name(), cr, bc); + MOZ_ASSERT((bc < 0x40) && ((bc & 0x0f) == 0x0c)); + uint16_t nbc = (bc >> 4) + (cr << 2); + return writeInst(PPC_setbcr | (rt.code() << 21) | (nbc << 16)); +} + +// --- FP compare --- + +BufferOffset Assembler::as_fcmpu(CRegisterID cr, FloatRegister ra, + FloatRegister rb) { + spew("fcmpu\tcr%d,%3s,%3s", cr, ra.name(), rb.name()); + return writeInst(PPC_fcmpu | cr << 23 | ra.encoding() << 16 | + rb.encoding() << 11); +} + +BufferOffset Assembler::as_fcmpu(FloatRegister ra, FloatRegister rb) { + return as_fcmpu(cr0, ra, rb); +} + +// --- FP arithmetic --- + +#define DEF_FPUAC(op) DEF_AFORM_C(op) +DEF_FPUAC(fmul) +DEF_FPUAC(fmuls) +#undef DEF_FPUAC + +#define DEF_FPUAB(op) DEF_AFORM_B(op) +DEF_FPUAB(fadd) +DEF_FPUAB(fdiv) +DEF_FPUAB(fsub) +DEF_FPUAB(fadds) +DEF_FPUAB(fdivs) +DEF_FPUAB(fsubs) +DEF_FPUAB(fcpsgn) +#undef DEF_FPUAB + +// --- FP unary/conversion/rounding --- + +#define DEF_FPUDS(op) DEF_XFORM2_F(op) +DEF_FPUDS(fabs) +DEF_FPUDS(fneg) +DEF_FPUDS(fmr) +DEF_FPUDS(fcfid) +DEF_FPUDS(fcfids) +DEF_FPUDS(fcfidu) +DEF_FPUDS(fcfidus) +DEF_FPUDS(fctid) +DEF_FPUDS(fctidz) +DEF_FPUDS(fctiduz) +DEF_FPUDS(fctiw) +DEF_FPUDS(fctiwz) +DEF_FPUDS(frim) +DEF_FPUDS(frip) +DEF_FPUDS(friz) +DEF_FPUDS(frsp) +DEF_FPUDS(fsqrt) +DEF_FPUDS(fsqrts) +#undef DEF_FPUDS + +// --- FP loads/stores (D-form) --- + +DEF_DFORM_F(lfd) +DEF_DFORM_F(lfs) +DEF_DFORM_F(stfd) +DEF_DFORM_F(stfs) +DEF_DFORM_F(stfdu) +DEF_DFORM_F(stfsu) + +// --- FP loads/stores (X-form, indexed) --- + +DEF_FMEMx(lfdx) DEF_FMEMx(lfsx) DEF_FMEMx(lfiwax) + DEF_FMEMx(stfdx) DEF_FMEMx(stfsx) +// Clean up macros. +#undef DEF_XFORM +#undef DEF_XFORM_RC +#undef DEF_XFORMS +#undef DEF_XFORMS_RC +#undef DEF_XFORMS_I +#undef DEF_XFORM2 +#undef DEF_XFORM2_RC +#undef DEF_XFORM2S +#undef DEF_XFORM2S_RC +#undef DEF_XFORM2_F +#undef DEF_XFORM2_F_RC +#undef DEF_MFORM +#undef DEF_MFORM_I +#undef DEF_MFORM_I_RC +#undef DEF_MDSFORM +#undef DEF_MDSFORM_RC +#undef DEF_MDFORM +#undef DEF_MDFORM_RC +#undef DEF_DFORM_F +#undef DEF_FMEMx +#undef DEF_AFORM_C +#undef DEF_AFORM_C_RC +#undef DEF_AFORM_B +#undef DEF_AFORM_B_RC +#undef DEF_AFORM +#undef DEF_AFORM_RC + + // --- FPSCR operations --- + + BufferOffset Assembler::as_mtfsb0(uint8_t bt) { + spew("mtfsb0\t%d", bt); + return writeInst(PPC_mtfsb0 | (uint32_t)bt << 21); +} + +BufferOffset Assembler::as_mtfsb1(uint8_t bt) { + spew("mtfsb1\t%d", bt); + return writeInst(PPC_mtfsb1 | (uint32_t)bt << 21); +} + +BufferOffset Assembler::as_mcrfs(CRegisterID bf, uint8_t bfa) { + spew("mcrfs\tcr%d,%d", bf, bfa); + return writeInst(PPC_mcrfs | (uint32_t)bf << 23 | (uint32_t)bfa << 18); +} + +// --- VMX SIMD load/store --- + +// X-form: opcode | VRT << 21 | RA << 16 | RB << 11 | XO. lvx/stvx ignore the +// low four bits of the effective address, so the caller must align the slot. +BufferOffset Assembler::as_lvx(uint8_t vrt, Register ra, Register rb) { + MOZ_ASSERT(vrt < 32); + spew("lvx\tvr%d,%3s,%3s", vrt, ra.name(), rb.name()); + return writeInst(PPC_lvx | uint32_t(vrt) << 21 | ra.code() << 16 | + rb.code() << 11); +} + +BufferOffset Assembler::as_stvx(uint8_t vrs, Register ra, Register rb) { + MOZ_ASSERT(vrs < 32); + spew("stvx\tvr%d,%3s,%3s", vrs, ra.name(), rb.name()); + return writeInst(PPC_stvx | uint32_t(vrs) << 21 | ra.code() << 16 | + rb.code() << 11); +} + +BufferOffset Assembler::as_lvx(FloatRegister vrt, Register ra, Register rb) { + return as_lvx(uint8_t(VR(vrt)), ra, rb); +} + +BufferOffset Assembler::as_stvx(FloatRegister vrs, Register ra, Register rb) { + return as_stvx(uint8_t(VR(vrs)), ra, rb); +} + +#define DEF_VMX_LVS(op) \ + BufferOffset Assembler::as_##op(FloatRegister vrt, Register ra, \ + Register rb) { \ + spew(#op "\tvr%u,%3s,%3s", VR(vrt), ra.name(), rb.name()); \ + return writeInst(PPC_##op | (VR(vrt) << 21) | (ra.code() << 16) | \ + (rb.code() << 11)); \ + } +DEF_VMX_LVS(lvsl) +DEF_VMX_LVS(lvsr) +#undef DEF_VMX_LVS + +BufferOffset Assembler::as_mfvscr(FloatRegister vrt) { + spew("mfvscr\tvr%u", VR(vrt)); + return writeInst(PPC_mfvscr | (VR(vrt) << 21)); +} + +BufferOffset Assembler::as_mtvscr(FloatRegister vrb) { + spew("mtvscr\tvr%u", VR(vrb)); + return writeInst(PPC_mtvscr | (VR(vrb) << 11)); +} + +// --- VMX register operations (FloatRegister-typed) --- + +#define DEF_VMX_VX3(op) \ + BufferOffset Assembler::as_##op(FloatRegister vrt, FloatRegister vra, \ + FloatRegister vrb) { \ + spew(#op "\t%3s,%3s,%3s", vrt.name(), vra.name(), vrb.name()); \ + return writeInst(VXForm(PPC_##op, vrt, vra, vrb)); \ + } +DEF_VMX_VX3(vand) +DEF_VMX_VX3(vandc) +DEF_VMX_VX3(vor) +DEF_VMX_VX3(vxor) +DEF_VMX_VX3(vnor) +DEF_VMX_VX3(vsl) +DEF_VMX_VX3(vsr) +DEF_VMX_VX3(vaddfp) +DEF_VMX_VX3(vsubfp) +DEF_VMX_VX3(vmaxfp) +DEF_VMX_VX3(vminfp) +#undef DEF_VMX_VX3 + +// VC-form float compares; the record form ORs in Rc (bit 10) and sets CR6. +#define DEF_VMX_VCMP(op) \ + BufferOffset Assembler::as_##op(FloatRegister vrt, FloatRegister vra, \ + FloatRegister vrb) { \ + spew(#op "\t%3s,%3s,%3s", vrt.name(), vra.name(), vrb.name()); \ + return writeInst(VXForm(PPC_##op, vrt, vra, vrb)); \ + } \ + BufferOffset Assembler::as_##op##_rc(FloatRegister vrt, FloatRegister vra, \ + FloatRegister vrb) { \ + spew(#op ".\t%3s,%3s,%3s", vrt.name(), vra.name(), vrb.name()); \ + return writeInst(VXForm(PPC_##op, vrt, vra, vrb) | 0x400); \ + } +DEF_VMX_VCMP(vcmpeqfp) +DEF_VMX_VCMP(vcmpgefp) +DEF_VMX_VCMP(vcmpgtfp) +#undef DEF_VMX_VCMP + +#define DEF_VMX_VX2(op) \ + BufferOffset Assembler::as_##op(FloatRegister vrt, FloatRegister vrb) { \ + spew(#op "\t%3s,%3s", vrt.name(), vrb.name()); \ + return writeInst(VXForm2(PPC_##op, vrt, vrb)); \ + } +DEF_VMX_VX2(vrefp) +DEF_VMX_VX2(vrsqrtefp) +DEF_VMX_VX2(vexptefp) +DEF_VMX_VX2(vlogefp) +DEF_VMX_VX2(vrfin) +DEF_VMX_VX2(vrfiz) +DEF_VMX_VX2(vrfip) +DEF_VMX_VX2(vrfim) +#undef DEF_VMX_VX2 + +// Int <-> float with a 5-bit power-of-two scale in the UIM field. +#define DEF_VMX_VCONV(op) \ + BufferOffset Assembler::as_##op(FloatRegister vrt, FloatRegister vrb, \ + uint8_t uim) { \ + MOZ_ASSERT(uim < 32); \ + spew(#op "\t%3s,%3s,%u", vrt.name(), vrb.name(), uim); \ + return writeInst(VXForm2(PPC_##op, vrt, vrb, uim)); \ + } +DEF_VMX_VCONV(vcfsx) +DEF_VMX_VCONV(vcfux) +DEF_VMX_VCONV(vctsxs) +DEF_VMX_VCONV(vctuxs) +#undef DEF_VMX_VCONV + +BufferOffset Assembler::as_vsel(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb, FloatRegister vrc) { + spew("vsel\t%3s,%3s,%3s,%3s", vrt.name(), vra.name(), vrb.name(), + vrc.name()); + return writeInst(VAForm(PPC_vsel, vrt, vra, vrb, vrc)); +} + +BufferOffset Assembler::as_vmaddfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrc, FloatRegister vrb) { + spew("vmaddfp\t%3s,%3s,%3s,%3s", vrt.name(), vra.name(), vrc.name(), + vrb.name()); + return writeInst(VAForm(PPC_vmaddfp, vrt, vra, vrb, vrc)); +} + +BufferOffset Assembler::as_vnmsubfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrc, FloatRegister vrb) { + spew("vnmsubfp\t%3s,%3s,%3s,%3s", vrt.name(), vra.name(), vrc.name(), + vrb.name()); + return writeInst(VAForm(PPC_vnmsubfp, vrt, vra, vrb, vrc)); +} + +BufferOffset Assembler::as_vspltw(FloatRegister vrt, FloatRegister vrb, + uint8_t uim) { + MOZ_ASSERT(uim < 4); + spew("vspltw\t%3s,%3s,%d", vrt.name(), vrb.name(), uim); + return writeInst(VXForm2(PPC_vspltw, vrt, vrb, uim)); +} + +BufferOffset Assembler::as_vperm(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb, FloatRegister vrc) { + spew("vperm\t%3s,%3s,%3s,%3s", vrt.name(), vra.name(), vrb.name(), + vrc.name()); + return writeInst(VAForm(PPC_vperm, vrt, vra, vrb, vrc)); +} + +// --- VMX instructions --- + +BufferOffset Assembler::as_vspltb(FloatRegister vrt, FloatRegister vrb, + uint8_t uim) { + MOZ_ASSERT(uim < 16); + spew("vspltb\t%3s,%3s,%d", vrt.name(), vrb.name(), uim); + return writeInst(VXForm2(PPC_vspltb, vrt, vrb, uim)); +} + +BufferOffset Assembler::as_vsplth(FloatRegister vrt, FloatRegister vrb, + uint8_t uim) { + MOZ_ASSERT(uim < 8); + spew("vsplth\t%3s,%3s,%d", vrt.name(), vrb.name(), uim); + return writeInst(VXForm2(PPC_vsplth, vrt, vrb, uim)); +} + +// VA-form with a shift-byte immediate in the VRC field. +BufferOffset Assembler::as_vsldoi(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb, uint8_t shb) { + MOZ_ASSERT(shb < 16); + spew("vsldoi\t%3s,%3s,%3s,%d", vrt.name(), vra.name(), vrb.name(), shb); + return writeInst(VXForm(PPC_vsldoi, vrt, vra, vrb) | (uint32_t)shb << 6); +} + +// --- VMX integer arithmetic (VR registers only) --- + +// VX-form: (4<<26) | VRT<<21 | VRA<<16 | VRB<<11 | XO +// The macro takes raw VR numbers (0-31). +#define DEF_VMX_VVV(op) \ + BufferOffset Assembler::as_##op(uint8_t vrt, uint8_t vra, uint8_t vrb) { \ + MOZ_ASSERT(vrt < 32 && vra < 32 && vrb < 32); \ + spew(#op "\tvr%d,vr%d,vr%d", vrt, vra, vrb); \ + return writeInst(PPC_##op | vrt << 21 | vra << 16 | vrb << 11); \ + } + +DEF_VMX_VVV(vaddubm) +DEF_VMX_VVV(vadduhm) +DEF_VMX_VVV(vadduwm) +DEF_VMX_VVV(vaddudm) +DEF_VMX_VVV(vsububm) +DEF_VMX_VVV(vsubuhm) +DEF_VMX_VVV(vsubuwm) +DEF_VMX_VVV(vsubudm) +DEF_VMX_VVV(vaddsbs) +DEF_VMX_VVV(vaddshs) +DEF_VMX_VVV(vaddubs) +DEF_VMX_VVV(vadduhs) +DEF_VMX_VVV(vsubsbs) +DEF_VMX_VVV(vsubshs) +DEF_VMX_VVV(vsububs) +DEF_VMX_VVV(vsubuhs) +DEF_VMX_VVV(vminsb) +DEF_VMX_VVV(vminsh) +DEF_VMX_VVV(vminsw) +DEF_VMX_VVV(vmaxsb) +DEF_VMX_VVV(vmaxsh) +DEF_VMX_VVV(vmaxsw) +DEF_VMX_VVV(vmaxsd) +DEF_VMX_VVV(vminub) +DEF_VMX_VVV(vminuh) +DEF_VMX_VVV(vminuw) +DEF_VMX_VVV(vmaxub) +DEF_VMX_VVV(vmaxuh) +DEF_VMX_VVV(vmaxuw) +DEF_VMX_VVV(vavgub) +DEF_VMX_VVV(vavguh) +DEF_VMX_VVV(vmuluwm) +DEF_VMX_VVV(vmulld) + +DEF_VMX_VVV(vslb) +DEF_VMX_VVV(vslh) +DEF_VMX_VVV(vslw) +DEF_VMX_VVV(vsld) +DEF_VMX_VVV(vsrb) +DEF_VMX_VVV(vsrh) +DEF_VMX_VVV(vsrw) +DEF_VMX_VVV(vsrd) +DEF_VMX_VVV(vsrab) +DEF_VMX_VVV(vsrah) +DEF_VMX_VVV(vsraw) +DEF_VMX_VVV(vsrad) +DEF_VMX_VVV(vslo) +DEF_VMX_VVV(vsro) +DEF_VMX_VVV(vcmpequb) +DEF_VMX_VVV(vcmpequh) +DEF_VMX_VVV(vcmpequw) +DEF_VMX_VVV(vcmpequd) +DEF_VMX_VVV(vcmpgtsb) +DEF_VMX_VVV(vcmpgtsh) +DEF_VMX_VVV(vcmpgtsw) +DEF_VMX_VVV(vcmpgtsd) +DEF_VMX_VVV(vcmpgtub) +DEF_VMX_VVV(vcmpgtuh) +DEF_VMX_VVV(vcmpgtuw) +DEF_VMX_VVV(vcmpgtud) +// POWER9 (ISA 3.0). NotEqual compare; saves the xxlnor that vcmpequX needs. +DEF_VMX_VVV(vcmpneb) +DEF_VMX_VVV(vcmpneh) +DEF_VMX_VVV(vcmpnew) + +// POWER8+ (ISA 2.07). vbpermq RT,RA,RB: bit-permute quadword. +DEF_VMX_VVV(vbpermq) + +#undef DEF_VMX_VVV + +// VC-form record forms: same as VX-form above with Rc bit (bit 10 LSB) set. +// vcmpXXX. sets CR6: LT = all-true, EQ = none-true. +#define DEF_VMX_VVV_RC(op) \ + BufferOffset Assembler::as_##op##_rc(uint8_t vrt, uint8_t vra, \ + uint8_t vrb) { \ + MOZ_ASSERT(vrt < 32 && vra < 32 && vrb < 32); \ + spew(#op ".\tvr%d,vr%d,vr%d", vrt, vra, vrb); \ + return writeInst(PPC_##op | vrt << 21 | vra << 16 | vrb << 11 | 0x400); \ + } + +DEF_VMX_VVV_RC(vcmpequb) +DEF_VMX_VVV_RC(vcmpequh) +DEF_VMX_VVV_RC(vcmpequw) +DEF_VMX_VVV_RC(vcmpequd) + +#undef DEF_VMX_VVV_RC + +// VMX unary VX-form: (4<<26) | VRT<<21 | 0<<16 | VRB<<11 | XO +#define DEF_VMX_UNARY(op) \ + BufferOffset Assembler::as_##op(uint8_t vrt, uint8_t vrb) { \ + MOZ_ASSERT(vrt < 32 && vrb < 32); \ + spew(#op "\tvr%d,vr%d", vrt, vrb); \ + return writeInst(PPC_##op | vrt << 21 | vrb << 11); \ + } +DEF_VMX_UNARY(vupkhsb) +DEF_VMX_UNARY(vupklsb) +DEF_VMX_UNARY(vupkhsh) +DEF_VMX_UNARY(vupklsh) +DEF_VMX_UNARY(vupkhsw) +DEF_VMX_UNARY(vupklsw) +// POWER9 per-lane integer negate. The VRA field holds the subop code +// (6 for vnegw, 7 for vnegd) which is already baked into PPC_vneg{w,d}. +DEF_VMX_UNARY(vnegw) +DEF_VMX_UNARY(vnegd) +DEF_VMX_UNARY(vpopcntb) +#undef DEF_VMX_UNARY + +// ----------------------------------------------------------------------------- +// Power ISA v3.1 (POWER10) prefixed instructions. NOT IMPLEMENTED BY THIS +// TARGET; retained only until the MacroAssembler call sites are removed. +// +// Layout: +// +// Prefix word (BE bit numbering from the manual; LE bits in parentheses): +// [0..5] primary opcode = 1 (LE 31..26) +// [6..7] Type: 00 = 8LS, 10 = MLS (LE 25..24) +// [8..10] reserved = 0 (LE 23..21) +// [11] R: 1 = PC-relative (RA must be r0) (LE 20) +// [12..13] reserved = 0 (LE 19..18) +// [14..31] d0: high 18 bits of 34-bit signed immediate (LE 17..0) +// +// Suffix (paddi/pld, GPR target): +// [0..5] suffix opcode (paddi=14, pld=57) (LE 31..26) +// [6..10] RT (LE 25..21) +// [11..15] RA (LE 20..16) +// [16..31] d1: low 16 bits of immediate (LE 15..0) +// +// The prefix and suffix of a prefixed instruction must lie in the same +// 64-byte aligned block at **runtime**. The JitCode allocator only +// guarantees 16-byte alignment, so the buffer-relative offset and the +// runtime address can differ by 0/16/32/48 mod 64. A buffer-only check +// `(currentOffset() & 63) == 60` is correct when the allocator base is +// 64-aligned but misses three of the four 16-aligned base classes — pad +// whenever `(currentOffset() & 15) == 12`, which catches all four. The +// enterNoPool guard prevents the constant-pool flusher from inserting +// bodies between the (optional) nop, prefix, and suffix. + +static uint32_t EncodePower10Prefix(uint32_t type, bool R, uint32_t d0) { + MOZ_ASSERT(type == 0 || type == 2); // 8LS=0, MLS=2 + MOZ_ASSERT(d0 < (1u << 18)); + return (1u << 26) | (type << 24) | (uint32_t(R ? 1 : 0) << 20) | + (d0 & 0x3FFFFu); +} + +static void SplitImm34(int64_t imm34, uint32_t* d0, uint32_t* d1) { + MOZ_ASSERT(imm34 >= -(int64_t(1) << 33)); + MOZ_ASSERT(imm34 < (int64_t(1) << 33)); + uint64_t u = uint64_t(imm34) & 0x3FFFFFFFFull; // low 34 bits + *d0 = uint32_t(u >> 16) & 0x3FFFFu; // 18 bits + *d1 = uint32_t(u) & 0xFFFFu; // 16 bits +} + +void Assembler::ensurePrefixedAlignment() { + if ((currentOffset() & 15) == 12) { + as_nop(); + } +} + +// Reservation = nop (worst case) + prefix + suffix. +BufferOffset Assembler::emitPrefixedInst(uint32_t prefix, uint32_t suffix) { + m_buffer.enterNoPool(3); + ensurePrefixedAlignment(); + BufferOffset bo = writeInst(prefix); + writeInst(suffix); + m_buffer.leaveNoPool(); + return bo; +} + +// paddi RT, RA, SI, R (MLS, suffix opcode 14 = addi) +// R=0: RT = (RA==0 ? 0 : RA) + sign_extend(SI, 34) +// R=1: RT = CIA(prefix) + sign_extend(SI, 34) (RA must be r0) +BufferOffset Assembler::as_paddi(Register rt, Register ra, int64_t imm34, + bool R) { + MOZ_ASSERT_IF(R, ra == r0); + spew("paddi\t%s,%s,%lld,%d", rt.name(), ra.name(), (long long)imm34, + R ? 1 : 0); + uint32_t d0, d1; + SplitImm34(imm34, &d0, &d1); + uint32_t prefix = EncodePower10Prefix(/*type=MLS*/ 2, R, d0); + uint32_t suffix = (14u << 26) | (uint32_t(rt.code()) << 21) | + (uint32_t(ra.code()) << 16) | d1; + return emitPrefixedInst(prefix, suffix); +} + +// pld RT, D(RA), R (8LS, suffix opcode 57) +BufferOffset Assembler::as_pld(Register rt, Register ra, int64_t imm34, + bool R) { + MOZ_ASSERT_IF(R, ra == r0); + spew("pld\t%s,%lld(%s),%d", rt.name(), (long long)imm34, ra.name(), + R ? 1 : 0); + uint32_t d0, d1; + SplitImm34(imm34, &d0, &d1); + uint32_t prefix = EncodePower10Prefix(/*type=8LS*/ 0, R, d0); + uint32_t suffix = (57u << 26) | (uint32_t(rt.code()) << 21) | + (uint32_t(ra.code()) << 16) | d1; + return emitPrefixedInst(prefix, suffix); +} + + +// plfd FRT, D(RA), R (MLS, suffix opcode 50; D-form-like FPR load) +BufferOffset Assembler::as_plfd(FloatRegister frt, Register ra, int64_t imm34, + bool R) { + MOZ_ASSERT_IF(R, ra == r0); + spew("plfd\tf%u,%lld(%s),%d", uint32_t(frt.encoding()), + (long long)imm34, ra.name(), R ? 1 : 0); + uint32_t d0, d1; + SplitImm34(imm34, &d0, &d1); + uint32_t prefix = EncodePower10Prefix(/*type=MLS*/ 2, R, d0); + uint32_t suffix = (50u << 26) | (uint32_t(frt.encoding()) << 21) | + (uint32_t(ra.code()) << 16) | d1; + return emitPrefixedInst(prefix, suffix); +} + +// plfs FRT, D(RA), R (MLS, suffix opcode 48; widens single → double in FPR) +BufferOffset Assembler::as_plfs(FloatRegister frt, Register ra, int64_t imm34, + bool R) { + MOZ_ASSERT_IF(R, ra == r0); + spew("plfs\tf%u,%lld(%s),%d", uint32_t(frt.encoding()), + (long long)imm34, ra.name(), R ? 1 : 0); + uint32_t d0, d1; + SplitImm34(imm34, &d0, &d1); + uint32_t prefix = EncodePower10Prefix(/*type=MLS*/ 2, R, d0); + uint32_t suffix = (48u << 26) | (uint32_t(frt.encoding()) << 21) | + (uint32_t(ra.code()) << 16) | d1; + return emitPrefixedInst(prefix, suffix); +} + +// pstd RS, D(RA), R (8LS, suffix opcode 61 = std D-form) +BufferOffset Assembler::as_pstd(Register rs, Register ra, int64_t imm34, + bool R) { + MOZ_ASSERT_IF(R, ra == r0); + spew("pstd\t%s,%lld(%s),%d", rs.name(), (long long)imm34, ra.name(), + R ? 1 : 0); + uint32_t d0, d1; + SplitImm34(imm34, &d0, &d1); + uint32_t prefix = EncodePower10Prefix(/*type=8LS*/ 0, R, d0); + uint32_t suffix = (61u << 26) | (uint32_t(rs.code()) << 21) | + (uint32_t(ra.code()) << 16) | d1; + return emitPrefixedInst(prefix, suffix); +} + + +// pstfd FRS, D(RA), R (MLS, suffix opcode 54 = stfd) +BufferOffset Assembler::as_pstfd(FloatRegister frs, Register ra, int64_t imm34, + bool R) { + MOZ_ASSERT_IF(R, ra == r0); + spew("pstfd\tf%u,%lld(%s),%d", uint32_t(frs.encoding()), + (long long)imm34, ra.name(), R ? 1 : 0); + uint32_t d0, d1; + SplitImm34(imm34, &d0, &d1); + uint32_t prefix = EncodePower10Prefix(/*type=MLS*/ 2, R, d0); + uint32_t suffix = (54u << 26) | (uint32_t(frs.encoding()) << 21) | + (uint32_t(ra.code()) << 16) | d1; + return emitPrefixedInst(prefix, suffix); +} + +// pstfs FRS, D(RA), R (MLS, suffix opcode 52 = stfs) +BufferOffset Assembler::as_pstfs(FloatRegister frs, Register ra, int64_t imm34, + bool R) { + MOZ_ASSERT_IF(R, ra == r0); + spew("pstfs\tf%u,%lld(%s),%d", uint32_t(frs.encoding()), + (long long)imm34, ra.name(), R ? 1 : 0); + uint32_t d0, d1; + SplitImm34(imm34, &d0, &d1); + uint32_t prefix = EncodePower10Prefix(/*type=MLS*/ 2, R, d0); + uint32_t suffix = (52u << 26) | (uint32_t(frs.encoding()) << 21) | + (uint32_t(ra.code()) << 16) | d1; + return emitPrefixedInst(prefix, suffix); +} + +// POWER10 (ISA 3.1) Vector Extract Mask. RT (GPR) gets the wasm-spec +// bitmask (one bit per lane MSB) directly in low 16/8/4/2 bits. UIM +// is baked into PPC_vextract{b,h,w,d}m (8/9/10/11). Caller must have +// verified HasPOWER10(). +#define DEF_VEXTRACT_M(op) \ + BufferOffset Assembler::as_##op(Register rt, FloatRegister vrb) { \ + spew(#op "\t%s,vr%u", rt.name(), uint32_t(vrb.encoding() & 31)); \ + return writeInst(PPC_##op | (uint32_t(rt.code()) << 21) | \ + ((uint32_t(vrb.encoding()) & 31) << 11)); \ + } +DEF_VEXTRACT_M(vextractbm) +DEF_VEXTRACT_M(vextracthm) +DEF_VEXTRACT_M(vextractwm) +DEF_VEXTRACT_M(vextractdm) +#undef DEF_VEXTRACT_M + +// POWER10 (ISA 3.1) Vector Insert Word/Doubleword from GPR. VX-form: +// VRT at bits 21..25, UIM at bits 16..20, RB at bits 11..15. +#define DEF_VINS(op, max_uim) \ + BufferOffset Assembler::as_##op(FloatRegister vrt, Register rb, \ + uint8_t uim) { \ + MOZ_ASSERT(uim <= (max_uim)); \ + spew(#op "\tvr%u,%s,%u", uint32_t(vrt.encoding() & 31), rb.name(), \ + uint32_t(uim)); \ + return writeInst(PPC_##op | \ + ((uint32_t(vrt.encoding()) & 31) << 21) | \ + (uint32_t(uim) << 16) | \ + (uint32_t(rb.code()) << 11)); \ + } +DEF_VINS(vinsw, 12) +DEF_VINS(vinsd, 8) +#undef DEF_VINS + +// POWER10 (ISA 3.1) Vector Insert byte/halfword from GPR with +// register-supplied byte position. VX-form: VRT at bits 21..25, +// RA at bits 16..20, RB at bits 11..15. "rx" is right-indexed +// (LE-natural — index 0 = LSB byte). +#define DEF_VINS_RX(op) \ + BufferOffset Assembler::as_##op(FloatRegister vrt, Register ra, \ + Register rb) { \ + spew(#op "\tvr%u,%s,%s", uint32_t(vrt.encoding() & 31), ra.name(), \ + rb.name()); \ + return writeInst(PPC_##op | \ + ((uint32_t(vrt.encoding()) & 31) << 21) | \ + (uint32_t(ra.code()) << 16) | \ + (uint32_t(rb.code()) << 11)); \ + } +DEF_VINS_RX(vinsbrx) +DEF_VINS_RX(vinshrx) +#undef DEF_VINS_RX + +// POWER9 (ISA 3.0) V-form 3-operand instructions with VRT, UIM, VRB at +// bits 21..25, 16..20, 11..15 respectively (vinsert{b,h}, vextract{ub,uh}). +// Simd128 names a VR, so mask VRT and VRB to the 5-bit VR field. +#define DEF_VRT_UIM_VRB(op, max_uim, uim_step) \ + BufferOffset Assembler::as_##op(FloatRegister vrt, FloatRegister vrb, \ + uint8_t uim) { \ + MOZ_ASSERT(uim <= (max_uim)); \ + MOZ_ASSERT((uim) % (uim_step) == 0); \ + spew(#op "\tvr%u,vr%u,%u", uint32_t(vrt.encoding() & 31), \ + uint32_t(vrb.encoding() & 31), uint32_t(uim)); \ + return writeInst(PPC_##op | \ + ((uint32_t(vrt.encoding()) & 31) << 21) | \ + (uint32_t(uim) << 16) | \ + ((uint32_t(vrb.encoding()) & 31) << 11)); \ + } +DEF_VRT_UIM_VRB(vinsertb, 15, 1) +DEF_VRT_UIM_VRB(vinserth, 14, 2) +DEF_VRT_UIM_VRB(vextractub, 15, 1) +DEF_VRT_UIM_VRB(vextractuh, 14, 2) +#undef DEF_VRT_UIM_VRB + +// VMX binary VX-form pack/merge (re-use DEF_VMX_VVV pattern). +#define DEF_VMX_VVV(op) \ + BufferOffset Assembler::as_##op(uint8_t vrt, uint8_t vra, uint8_t vrb) { \ + MOZ_ASSERT(vrt < 32 && vra < 32 && vrb < 32); \ + spew(#op "\tvr%d,vr%d,vr%d", vrt, vra, vrb); \ + return writeInst(PPC_##op | vrt << 21 | vra << 16 | vrb << 11); \ + } +DEF_VMX_VVV(vpkuhum) +DEF_VMX_VVV(vpkuwum) +DEF_VMX_VVV(vpkshss) +DEF_VMX_VVV(vpkswss) DEF_VMX_VVV(vpkshus) DEF_VMX_VVV(vpkswus) + DEF_VMX_VVV(vmrghb) + DEF_VMX_VVV(vmrghh) DEF_VMX_VVV(vmrghw) DEF_VMX_VVV(vmrglb) + DEF_VMX_VVV(vmrglh) DEF_VMX_VVV(vmrglw) DEF_VMX_VVV(vmulesb) + DEF_VMX_VVV(vmulosb) DEF_VMX_VVV(vmuleub) DEF_VMX_VVV(vmuloub) + DEF_VMX_VVV(vmulesh) DEF_VMX_VVV(vmulosh) + DEF_VMX_VVV(vmuleuh) DEF_VMX_VVV(vmulouh) + DEF_VMX_VVV(vmulesw) DEF_VMX_VVV(vmulosw) + DEF_VMX_VVV(vmuleuw) DEF_VMX_VVV(vmulouw) +#undef DEF_VMX_VVV + + // vperm VA-form: (4<<26) | VRT<<21 | VRA<<16 | VRB<<11 | VRC<<6 | XO + BufferOffset Assembler::as_vperm(uint8_t vrt, uint8_t vra, uint8_t vrb, + uint8_t vrc) { + MOZ_ASSERT(vrt < 32 && vra < 32 && vrb < 32 && vrc < 32); + spew("vperm\tvr%d,vr%d,vr%d,vr%d", vrt, vra, vrb, vrc); + return writeInst(PPC_vperm | vrt << 21 | vra << 16 | vrb << 11 | vrc << 6); +} + +// VA-form ternary VMX: (4<<26) | VRT<<21 | VRA<<16 | VRB<<11 | VRC<<6 | +// XO(6-bit) +BufferOffset Assembler::as_vmladduhm(uint8_t vrt, uint8_t vra, uint8_t vrb, + uint8_t vrc) { + MOZ_ASSERT(vrt < 32 && vra < 32 && vrb < 32 && vrc < 32); + spew("vmladduhm\tvr%d,vr%d,vr%d,vr%d", vrt, vra, vrb, vrc); + return writeInst(PPC_vmladduhm | vrt << 21 | vra << 16 | vrb << 11 | + vrc << 6); +} + +BufferOffset Assembler::as_vmhraddshs(uint8_t vrt, uint8_t vra, uint8_t vrb, + uint8_t vrc) { + MOZ_ASSERT(vrt < 32 && vra < 32 && vrb < 32 && vrc < 32); + spew("vmhraddshs\tvr%d,vr%d,vr%d,vr%d", vrt, vra, vrb, vrc); + return writeInst(PPC_vmhraddshs | vrt << 21 | vra << 16 | vrb << 11 | + vrc << 6); +} + +BufferOffset Assembler::as_vmsumshm(uint8_t vrt, uint8_t vra, uint8_t vrb, + uint8_t vrc) { + MOZ_ASSERT(vrt < 32 && vra < 32 && vrb < 32 && vrc < 32); + spew("vmsumshm\tvr%d,vr%d,vr%d,vr%d", vrt, vra, vrb, vrc); + return writeInst(PPC_vmsumshm | vrt << 21 | vra << 16 | vrb << 11 | + vrc << 6); +} + +BufferOffset Assembler::as_vmsumuhm(uint8_t vrt, uint8_t vra, uint8_t vrb, + uint8_t vrc) { + MOZ_ASSERT(vrt < 32 && vra < 32 && vrb < 32 && vrc < 32); + spew("vmsumuhm\tvr%d,vr%d,vr%d,vr%d", vrt, vra, vrb, vrc); + return writeInst(PPC_vmsumuhm | vrt << 21 | vra << 16 | vrb << 11 | + vrc << 6); +} + +BufferOffset Assembler::as_vspltisb(uint8_t vrt, int8_t simm5) { + MOZ_ASSERT(vrt < 32); + MOZ_ASSERT(simm5 >= -16 && simm5 <= 15); + spew("vspltisb\tvr%d,%d", vrt, simm5); + return writeInst(PPC_vspltisb | uint32_t(vrt) << 21 | + (uint32_t(simm5) & 0x1F) << 16); +} + +BufferOffset Assembler::as_vspltish(uint8_t vrt, int8_t simm5) { + MOZ_ASSERT(vrt < 32); + MOZ_ASSERT(simm5 >= -16 && simm5 <= 15); + spew("vspltish\tvr%d,%d", vrt, simm5); + return writeInst(PPC_vspltish | uint32_t(vrt) << 21 | + (uint32_t(simm5) & 0x1F) << 16); +} + +BufferOffset Assembler::as_vspltisw(uint8_t vrt, int8_t simm5) { + MOZ_ASSERT(vrt < 32); + MOZ_ASSERT(simm5 >= -16 && simm5 <= 15); + spew("vspltisw\tvr%d,%d", vrt, simm5); + return writeInst(PPC_vspltisw | uint32_t(vrt) << 21 | + (uint32_t(simm5) & 0x1F) << 16); +} + +// --- Convenience pseudo-instructions --- + +BufferOffset Assembler::xs_trap() { + spew("trap @ %08x", currentOffset()); + return writeInst(PPC_trap); +} + +BufferOffset Assembler::xs_trap_tagged(TrapTag tag) { + uint32_t tv = PPC_trap | ((uint8_t)tag << 16) | ((uint8_t)tag << 11); + spew("trap @ %08x ; MARK %d %08x", currentOffset(), (uint8_t)tag, tv); + return writeInst(tv); +} + +BufferOffset Assembler::xs_mr(Register rd, Register ra) { + return as_or_(rd, ra, ra); +} + +BufferOffset Assembler::xs_mtctr(Register ra) { + return as_mtspr((SPRegisterID)spr_ctr, ra); +} + +BufferOffset Assembler::xs_mtlr(Register ra) { + return as_mtspr((SPRegisterID)spr_lr, ra); +} + +BufferOffset Assembler::xs_mflr(Register rd) { + return as_mfspr(rd, (SPRegisterID)spr_lr); +} + +BufferOffset Assembler::xs_mfxer(Register ra) { + return as_mfspr(ra, (SPRegisterID)spr_xer); +} + +BufferOffset Assembler::xs_li(Register rd, int16_t im) { + return as_addi(rd, r0, im, true); +} + +BufferOffset Assembler::xs_lis(Register rd, int16_t im) { + return as_addis(rd, r0, im, true); +} + +BufferOffset Assembler::x_subi(Register rd, Register ra, int16_t im) { + return as_addi(rd, ra, -im); +} + +BufferOffset Assembler::x_not(Register rd, Register ra) { + return as_nor(rd, ra, ra); +} + +BufferOffset Assembler::x_slwi(Register rd, Register rs, int n) { + MOZ_ASSERT(n >= 0 && n < 32); + return as_rlwinm(rd, rs, n, 0, 31 - n); +} + +// x_sldi/x_srdi build on the 64-bit MD-form rotates and therefore only work +// on a 64-bit implementation; the MacroAssembler must replace them. +BufferOffset Assembler::x_sldi(Register rd, Register rs, int n) { + return as_rldicr(rd, rs, n, 63 - n); +} + +BufferOffset Assembler::x_srwi(Register rd, Register rs, int n) { + MOZ_ASSERT(n >= 0 && n < 32); + if (n == 0) { + return as_rlwinm(rd, rs, 0, 0, 31); + } + return as_rlwinm(rd, rs, 32 - n, n, 31); +} + +BufferOffset Assembler::x_srdi(Register rd, Register rs, int n) { + MOZ_ASSERT(n >= 0 && n < 64); + if (n == 0) { + return as_or_(rd, rs, rs); + } + return as_rldicl(rd, rs, 64 - n, n); +} + +// ======================================================================== +// Label binding, retarget, and code label processing. +// ======================================================================== + +InstImm Assembler::invertBranch(InstImm branch, BOffImm16 skipOffset) { + // Flip the BO condition-true/condition-false bit (bit 24). + uint32_t data = branch.encode(); + data = (data ^ 0x01000000) & 0xFFFF0003; + data |= skipOffset.encode(); + branch.setData(data); + return branch; +} + +void Assembler::bind(InstImm* inst, uintptr_t branch, uintptr_t target) { + intptr_t offset = target - branch; + Instruction* i0 = (Instruction*)inst; + + if (IsLoadPtrStanza(i0)) { + // Already a long stanza; just register the long jump. The lis/ori pair + // is repointed later via UpdateLoadPtrValue. + addLongJump(BufferOffset(branch), BufferOffset(target)); + return; + } + + if (i0->isOpcode((uint32_t)PPC_tw)) { + // Tagged trap stanza of LongBranchInstructions words: the trap word + // itself is slot [0], the pointer materialisation covers slots + // [0 .. PointerImmInstructions-1], then mtctr and bctr(l). + TrapTag tag = (TrapTag)inst->traptag(); + Instruction* iCtr = i0 + PointerImmInstructions; + Instruction* iBr = iCtr + 1; + + auto nopOutStanza = [&]() { + for (uint32_t i = 0; i < LongBranchInstructions; i++) { + i0[i].makeNop(); + } + }; + + switch (tag) { + case BCTag: { + // inst[-1] is the original bc instruction. + Instruction* bc = i0 - 1; + // Try short bc (offset + 4 because bc is one instruction before tw). + if (BOffImm16::IsInRange(offset + (intptr_t)sizeof(uint32_t))) { + bc->setData(((bc->encode() ^ 0x01000000) & 0xFFFF0003) | + BOffImm16(offset + sizeof(uint32_t)).encode()); + nopOutStanza(); + return; + } + // Try short b (unconditional). + if (JOffImm26::IsInRange(offset)) { + nopOutStanza(); + i0->setData(PPC_b | JOffImm26(offset).encode()); + return; + } + addLongJump(BufferOffset(branch), BufferOffset(target)); + WriteLoadPtrInstructions(i0, SecondScratchReg, + LabelBase::INVALID_OFFSET); + iCtr->makeOp_mtctr(SecondScratchReg); + iBr->makeOp_bctr(); + break; + } + case CallTag: { + // For calls the branch sits in the last slot, so the return address + // is right after the stanza. + intptr_t callOffset = + offset - (LongBranchInstructions - 1) * (intptr_t)sizeof(uint32_t); + if (JOffImm26::IsInRange(callOffset)) { + nopOutStanza(); + iBr->setData(PPC_b | JOffImm26(callOffset).encode() | LinkB); + return; + } + addLongJump(BufferOffset(branch), BufferOffset(target)); + WriteLoadPtrInstructions(i0, SecondScratchReg, + LabelBase::INVALID_OFFSET); + iCtr->makeOp_mtctr(SecondScratchReg); + iBr->makeOp_bctr(LinkB); + break; + } + case BTag: { + if (JOffImm26::IsInRange(offset)) { + nopOutStanza(); + i0->setData(PPC_b | JOffImm26(offset).encode()); + return; + } + addLongJump(BufferOffset(branch), BufferOffset(target)); + WriteLoadPtrInstructions(i0, SecondScratchReg, + LabelBase::INVALID_OFFSET); + iCtr->makeOp_mtctr(SecondScratchReg); + iBr->makeOp_bctr(); + break; + } + default: + MOZ_CRASH("Unexpected TrapTag"); + } + return; + } + + if (i0->isOpcode(PPC_b)) { + // Short unconditional branch — set offset, nop next-in-chain slot. + MOZ_ASSERT(JOffImm26::IsInRange(offset)); + i0->setData((i0->encode() & ~0x03FFFFFC) | JOffImm26(offset).encode()); + i0->next()->makeNop(); + return; + } + + if (i0->isOpcode(PPC_bc)) { + // Short conditional branch — preserve upper 16 bits, set offset. + MOZ_ASSERT(BOffImm16::IsInRange(offset)); + i0->setData((i0->encode() & 0xFFFF0003) | BOffImm16(offset).encode()); + i0->next()->makeNop(); + return; + } + + MOZ_CRASH("Unexpected instruction in bind"); +} + +void Assembler::bind(Label* label, BufferOffset boff) { + if (label->used()) { + bool more; + BufferOffset b(label); + do { + BufferOffset next; + InstImm* inst = (InstImm*)editSrc(b); + Instruction* i1 = ((Instruction*)inst)->next(); + more = (i1->encode() != LabelBase::INVALID_OFFSET); + if (more) { + next = BufferOffset(i1->encode()); + } + bind(inst, b.getOffset(), boff.getOffset()); + b = next; + } while (more); + } + label->bind(boff.getOffset()); +} + +void Assembler::retarget(Label* label, Label* target) { + spew("retarget"); + if (label->used() && !oom()) { + if (target->bound()) { + bind(label, BufferOffset(target)); + } else if (target->used()) { + // Prepend label's use chain to target's use chain. + BufferOffset b(label); + BufferOffset next; + do { + Instruction* inst = (Instruction*)editSrc(b); + Instruction* i1 = inst->next(); + if (i1->encode() != LabelBase::INVALID_OFFSET) { + next = BufferOffset(i1->encode()); + } else { + // End of label's chain — link to target's head. + i1->setData(target->offset()); + break; + } + b = next; + } while (true); + } + // Transfer label's use list to target. + if (!target->bound()) { + target->use(label->offset()); + } + } + label->reset(); +} + +void Assembler::processCodeLabels(uint8_t* rawCode) { + for (const CodeLabel& label : codeLabels_) { + Bind(rawCode, label); + } +} + +// ======================================================================== +// Constant pool callbacks (required by AssemblerBufferWithConstantPools). +// ======================================================================== + +/* static */ +void Assembler::InsertIndexIntoTag(uint8_t* load, uint32_t index) { + // Stash the pool entry index in the hint word's low 16 bits; the high + // bits carry the dest reg and load type, consumed by + // PatchConstantPoolLoad when the pool is resolved. + uint32_t* inst = (uint32_t*)load; + *inst = (*inst & 0xFFFF0000) | (index & 0xFFFF); +} + +/* static */ +bool Assembler::PatchConstantPoolLoad(void* loadAddr, void* constPoolAddr) { + // Rewrite placeholder instructions with a pool load sequence. + // Hint word layout (set by loadFromPoolFloat64 / loadFromPoolFloat32 / + // loadFromPoolSimd128): + // bits 0-15: pool entry index + // bits 16-20: destination register (FPR encoding) + // bits 21-22: load type (PoolLoadFPR64, PoolLoadSimd128, PoolLoadFPR32) + // bits 28-31: sentinel 0xF + + uint32_t* inst = (uint32_t*)loadAddr; + + uint32_t hint = inst[0]; + uint32_t index = hint & 0xFFFF; + uint32_t destReg = (hint >> 16) & 0x1F; + uint32_t loadType = (hint >> 21) & 0x3; + + // Displacement of the pool entry relative to inst[1], which is where the + // bcl leaves LR. + int32_t displacement = + (int32_t)((uint8_t*)constPoolAddr + index * 4 - ((uint8_t*)loadAddr + 4)); + MOZ_ASSERT(displacement >= -32768 && displacement < 32768); + + // There is no addpcis on this target, so every path seeds the base register + // from LR. r16 (SavedScratchRegister) is non-volatile and non-allocatable. + // The bcl/mflr pair does perturb the return address stack, which is the + // price of PC-relative addressing here. + uint32_t baseReg = SavedScratchRegister.code(); + + if (loadType == PoolLoadFPR64 || loadType == PoolLoadFPR32) { + // [0] bcl 20,0,$+4 + // [1] mflr r16 + // [2] lfd/lfs fD, displacement(r16) (lfs widens single -> double) + uint32_t loadOp = (loadType == PoolLoadFPR64) ? PPC_lfd : PPC_lfs; + inst[0] = PPC_bcl_always_plus4; + inst[1] = PPC_mfspr | (baseReg << 21) | PPC_SPR(spr_lr); + inst[2] = + loadOp | (destReg << 21) | (baseReg << 16) | (displacement & 0xFFFF); + } else if (loadType == PoolLoadSimd128) { + // [0] bcl 20,0,$+4 + // [1] mflr r16 + // [2] addi r16, r16, displacement + // [3] lvx vD, 0, r16 + // [4] nop + // lvx clears the low four bits of the effective address, so the pool + // entry itself must be 16-byte aligned (loadFromPoolSimd128's job). + MOZ_ASSERT((((uintptr_t)constPoolAddr + index * 4) & 15) == 0, + "Simd128 pool entries must be 16-byte aligned for lvx"); + inst[0] = PPC_bcl_always_plus4; + inst[1] = PPC_mfspr | (baseReg << 21) | PPC_SPR(spr_lr); + inst[2] = + PPC_addi | (baseReg << 21) | (baseReg << 16) | (displacement & 0xFFFF); + inst[3] = PPC_lvx | (destReg << 21) | (baseReg << 11); + inst[4] = NopInst; + } else { + MOZ_CRASH("PatchConstantPoolLoad: unsupported load type"); + } + + return false; +} + +/* static */ +void Assembler::WritePoolGuard(BufferOffset branch, Instruction* inst, + BufferOffset dest) { + // Emit an unconditional branch over the pool data. + int32_t offset = dest.getOffset() - branch.getOffset(); + MOZ_ASSERT(JOffImm26::IsInRange(offset)); + inst->setData(PPC_b | (offset & 0x03FFFFFC)); +} + +/* static */ +void Assembler::WritePoolHeader(uint8_t* start, Pool* p, bool isNatural) { + // Write pool identification header. + // Encode pool size and isNatural flag in a single 32-bit word. + uint32_t poolSize = p->getPoolSize(); + uint32_t sizeInWords = (poolSize + 4 + 3) >> 2; // header + data, in words + MOZ_ASSERT(sizeInWords < (1 << 15)); + uint32_t header = (sizeInWords & 0x7FFF) | (isNatural ? (1 << 15) : 0) | + 0xFFFF0000; // sentinel + *(uint32_t*)start = header; +} + +/* static */ +void Assembler::PatchShortRangeBranchToVeneer(PPCBuffer*, unsigned rangeIdx, + BufferOffset deadline, + BufferOffset veneer) { + // PPC does not use short-range branch tracking (NumShortBranchRanges = 0). + MOZ_CRASH("PatchShortRangeBranchToVeneer: should not be called"); +} + +// Pointer materialisation stanza (PointerImmInstructions words): +// [0] lis rD, hi16 +// [1] ori rD, rD, lo16 +// Both halves are plain D-form immediates, so the patchers rewrite the two +// 16-bit fields in place without moving any code. +static constexpr uint32_t LisOpcodeBits = uint32_t(PPC_addis); +static constexpr uint32_t OriOpcodeBits = uint32_t(PPC_ori); + +/* static */ +bool Assembler::IsLoadPtrStanza(const Instruction* inst0) { + // lis rD, imm is addis rD, r0, imm: opcode 15 with RA == 0. + uint32_t enc0 = inst0[0].encode(); + uint32_t enc1 = inst0[1].encode(); + if ((enc0 & PPC_MAJOR_OPCODE_MASK) != LisOpcodeBits || + (enc0 & RAMask) != 0) { + return false; + } + // ori rD, rD, imm with the same register in RS and RA. + return (enc1 & PPC_MAJOR_OPCODE_MASK) == OriOpcodeBits && + ((enc1 >> RSShift) & RegMask) == ((enc0 >> RTShift) & RegMask) && + ((enc1 >> RAShift) & RegMask) == ((enc0 >> RTShift) & RegMask); +} + +// Destination register of a pointer materialisation stanza (RT of the lis). +static uint8_t LoadPtrStanzaDestReg(Instruction* inst0) { + return (inst0[0].encode() >> RTShift) & RegMask; +} + +/* static */ +void Assembler::WriteLoadPtrInstructions(Instruction* inst0, Register reg, + uintptr_t value) { + uint32_t rd = uint32_t(reg.code()); + inst0[0].setData(LisOpcodeBits | (rd << RTShift) | + ((uint32_t(value) >> 16) & Imm16Mask)); + inst0[1].setData(OriOpcodeBits | (rd << RSShift) | (rd << RAShift) | + (uint32_t(value) & Imm16Mask)); +} + +/* static */ +uintptr_t Assembler::ExtractLoadPtrValue(Instruction* inst0) { + MOZ_ASSERT(IsLoadPtrStanza(inst0)); + return (uintptr_t(inst0[0].encode() & Imm16Mask) << 16) | + uintptr_t(inst0[1].encode() & Imm16Mask); +} + +/* static */ +void Assembler::UpdateLoadPtrValue(Instruction* inst0, uintptr_t value) { + MOZ_ASSERT(IsLoadPtrStanza(inst0), + "UpdateLoadPtrValue: inst0 is not a pointer stanza"); + inst0[0].setData((inst0[0].encode() & ~Imm16Mask) | + ((uint32_t(value) >> 16) & Imm16Mask)); + inst0[1].setData((inst0[1].encode() & ~Imm16Mask) | + (uint32_t(value) & Imm16Mask)); +} + +// ======================================================================== +// Patching and toggle operations. +// ======================================================================== + +/* static */ +uint32_t Assembler::PatchWrite_NearCallSize() { + return LongBranchInstructions * sizeof(uint32_t); +} + +/* static */ +void Assembler::PatchWrite_NearCall(CodeLocationLabel start, + CodeLocationLabel toCall) { + Instruction* inst = (Instruction*)start.raw(); + uint8_t* dest = toCall.raw(); + + Assembler::WriteLoadPtrInstructions(inst, SavedScratchRegister, + (uintptr_t)dest); + inst[PointerImmInstructions].makeOp_mtctr(SavedScratchRegister); + inst[PointerImmInstructions + 1].makeOp_bctr(LinkB); + FlushICache(inst, LongBranchInstructions * sizeof(Instruction)); +} + +/* static */ +void Assembler::PatchWrite_Imm32(CodeLocationLabel label, Imm32 imm) { + uint32_t* l = (uint32_t*)label.raw(); + *(l - 1) = imm.value; + FlushICache(l - 1, sizeof(uint32_t)); +} + +void Assembler::PatchDataWithValueCheck(CodeLocationLabel label, + ImmPtr newValue, ImmPtr expectedValue) { + PatchDataWithValueCheck(label, PatchedImmPtr(newValue.value), + PatchedImmPtr(expectedValue.value)); +} + +void Assembler::PatchDataWithValueCheck(CodeLocationLabel label, + PatchedImmPtr newValue, + PatchedImmPtr expectedValue) { + Instruction* inst = (Instruction*)label.raw(); + + DebugOnly value = Assembler::ExtractLoadPtrValue(inst); + MOZ_ASSERT(value == uintptr_t(expectedValue.value)); + + Assembler::UpdateLoadPtrValue(inst, uintptr_t(newValue.value)); + FlushICache(inst, PointerImmInstructions * sizeof(Instruction)); +} + +// ToggleCall toggles the call portion of a toggledCall stanza, whose layout +// is the pointer materialisation followed by mtctr + bctrl. Only the last +// two words change; toggling twice in the same direction is a no-op, which +// the debugger relies on (a breakpoint and a frame.onStep on one script both +// touch the same site). + +/* static */ +void Assembler::ToggleCall(CodeLocationLabel inst_, bool enabled) { + Instruction* i0 = (Instruction*)inst_.raw(); + Instruction* iCtr = i0 + PointerImmInstructions; + Instruction* iBr = iCtr + 1; + + MOZ_ASSERT(IsLoadPtrStanza(i0)); + + Register scratch = Register::FromCode(LoadPtrStanzaDestReg(i0)); + uint32_t mtctr = PPC_mtspr | (scratch.code() << 21) | PPC_SPR(spr_ctr); + uint32_t bctrl = (uint32_t)PPC_bctr | (uint32_t)LinkB; + MOZ_ASSERT(iCtr->encode() == NopInst || iCtr->encode() == mtctr); + MOZ_ASSERT(iBr->encode() == NopInst || iBr->encode() == bctrl); + if (enabled) { + iCtr->setData(mtctr); + iBr->setData(bctrl); + } else { + iCtr->setData(NopInst); + iBr->setData(NopInst); + } + FlushICache(iCtr, 2 * sizeof(Instruction)); +} + +// toggledJump emits a trap stanza via jump(label). After binding, the first +// instruction becomes "b offset" (short branch). We toggle between b and ori: +// b offset: [010010][LI:24][0][0] +// ori r0,r0,imm: [011000][00000][00000][UI:16] +// For short forward jumps (offset < 64KB), bits 25:16 of LI are 0, so +// swapping the opcode preserves the offset in the lower 16 bits. +// ori r0,r0,X is effectively a nop (writes to r0). + +/* static */ +void Assembler::ToggleToJmp(CodeLocationLabel inst_) { + Instruction* inst = (Instruction*)inst_.raw(); + MOZ_ASSERT(inst->isOpcode(PPC_ori)); + // Verify RS=0 and RA=0 (r0). + MOZ_ASSERT((inst->encode() & 0x03E00000) == 0); + MOZ_ASSERT((inst->encode() & 0x001F0000) == 0); + // Swap opcode from ori (011000) to b (010010). + uint32_t encoding = inst->encode(); + encoding = (encoding & 0x03FFFFFF) | (uint32_t)PPC_b; + inst->setData(encoding); + FlushICache(inst, sizeof(Instruction)); +} + +/* static */ +void Assembler::ToggleToCmp(CodeLocationLabel inst_) { + Instruction* inst = (Instruction*)inst_.raw(); + MOZ_ASSERT(inst->isOpcode(PPC_b)); + // Verify short forward branch: upper LI bits (25:16) are 0, AA=0, LK=0. + MOZ_ASSERT((inst->encode() & 0x03FF0003) == 0); + // Swap opcode from b (010010) to ori (011000). + uint32_t encoding = inst->encode(); + encoding = (encoding & 0x03FFFFFF) | (uint32_t)PPC_ori; + inst->setData(encoding); + FlushICache(inst, sizeof(Instruction)); +} + +// ======================================================================== +// Bind, tracing, and pointer extraction. +// ======================================================================== + +void Assembler::Bind(uint8_t* rawCode, const CodeLabel& label) { + if (label.patchAt().bound()) { + auto mode = label.linkMode(); + intptr_t offset = label.patchAt().offset(); + intptr_t target = label.target().offset(); + + if (mode == CodeLabel::RawPointer) { + *reinterpret_cast(rawCode + offset) = rawCode + target; + } else { + MOZ_ASSERT(mode == CodeLabel::MoveImmediate || + mode == CodeLabel::JumpImmediate); + Instruction* inst = (Instruction*)(rawCode + offset); + Assembler::UpdateLoadPtrValue(inst, (uintptr_t)(rawCode + target)); + } + } +} + +uintptr_t Assembler::GetPointer(uint8_t* instPtr) { + Instruction* inst = (Instruction*)instPtr; + return Assembler::ExtractLoadPtrValue(inst); +} + +static JitCode* CodeFromJump(Instruction* jump) { + uint8_t* target = (uint8_t*)Assembler::ExtractLoadPtrValue(jump); + return JitCode::FromExecutable(target); +} + +void Assembler::TraceJumpRelocations(JSTracer* trc, JitCode* code, + CompactBufferReader& reader) { + while (reader.more()) { + JitCode* child = + CodeFromJump((Instruction*)(code->raw() + reader.readUnsigned())); + TraceManuallyBarrieredEdge(trc, &child, "rel32"); + } +} + +static void TraceOneDataRelocation(JSTracer* trc, + mozilla::Maybe& awjc, + JitCode* code, Instruction* inst) { + // On nunbox32 an embedded data relocation is always a bare Cell pointer; + // a boxed Value never fits in one materialised word. + void* ptr = (void*)Assembler::ExtractLoadPtrValue(inst); + void* prior = ptr; + + TraceManuallyBarrieredGenericPointerEdge( + trc, reinterpret_cast(&ptr), "jit-masm-ptr"); + + if (ptr != prior) { + if (awjc.isNothing()) { + awjc.emplace(code); + } + Assembler::UpdateLoadPtrValue(inst, uintptr_t(ptr)); + } +} + +/* static */ +void Assembler::TraceDataRelocations(JSTracer* trc, JitCode* code, + CompactBufferReader& reader) { + mozilla::Maybe awjc; + while (reader.more()) { + size_t offset = reader.readUnsigned(); + Instruction* inst = (Instruction*)(code->raw() + offset); + TraceOneDataRelocation(trc, awjc, code, inst); + } +} + +/* static */ +uint8_t* Assembler::NextInstruction(uint8_t* instruction, uint32_t* count) { + if (count != nullptr) { + *count += sizeof(Instruction); + } + return instruction + sizeof(Instruction); +} + +// ======================================================================== +// UseScratchRegisterScope implementation. +// ======================================================================== + +UseScratchRegisterScope::UseScratchRegisterScope(Assembler& assembler) + : available_(assembler.GetScratchRegisterList()), + old_available_(*available_) {} + +UseScratchRegisterScope::UseScratchRegisterScope(Assembler* assembler) + : available_(assembler->GetScratchRegisterList()), + old_available_(*available_) {} + +UseScratchRegisterScope::~UseScratchRegisterScope() { + *available_ = old_available_; +} + +Register UseScratchRegisterScope::Acquire() { + MOZ_ASSERT(available_ != nullptr); + MOZ_ASSERT(!available_->empty()); + Register index = GeneralRegisterSet::FirstRegister(available_->bits()); + available_->takeRegisterIndex(index); + return index; +} + +void UseScratchRegisterScope::Release(const Register& reg) { + MOZ_ASSERT(available_ != nullptr); + MOZ_ASSERT(old_available_.hasRegisterIndex(reg)); + MOZ_ASSERT(!available_->hasRegisterIndex(reg)); + Include(GeneralRegisterSet(1 << reg.code())); +} + +bool UseScratchRegisterScope::hasAvailable() const { + return (available_->size()) != 0; +} diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/Assembler-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/Assembler-ppc.h --- a/js/src/jit/ppc/Assembler-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/Assembler-ppc.h 2026-07-29 07:53:30.252985538 +0200 @@ -0,0 +1,2088 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_Assembler_ppc_h +#define jit_ppc_Assembler_ppc_h + +#include "jit/CompactBuffer.h" +#include "jit/JitCode.h" +#include "jit/JitSpewer.h" +#include "jit/ppc/Architecture-ppc.h" +#include "jit/shared/Assembler-shared.h" +#include "jit/shared/Disassembler-shared.h" +#include "jit/shared/IonAssemblerBuffer.h" +#include "jit/shared/IonAssemblerBufferWithConstantPools.h" +#include "wasm/WasmTypeDecls.h" + +// 32-bit big-endian PowerPC, SysV ABI (powerpc-unknown-linux-gnu). +// +// Pointers and the machine word are 32 bits: a pointer-sized immediate is +// materialised with `lis rD, hi16 ; ori rD, rD, lo16` (see +// PointerImmInstructions below), which is also the shape every patcher in +// this backend rewrites in place. +// +// There is no TOC and no function descriptor: a function pointer is a plain +// code address and r2 is the thread pointer, never saved or restored around +// a call. r13 is the small-data-area base. Both are reserved. +// +// The scalar ISA is the classic 32-bit subset (no ld/std, no rld*, no 64-bit +// compare form, no isel, no popcnt, no fsqrt, no fcfid/fctidz). AltiVec/VMX +// is used for SIMD; VSX does not exist on this target. + +namespace js { +namespace jit { + +// GPR register constants. +static constexpr Register r0{Registers::r0}; +static constexpr Register r1{Registers::r1}; +static constexpr Register r2{Registers::r2}; +static constexpr Register r3{Registers::r3}; +static constexpr Register r4{Registers::r4}; +static constexpr Register r5{Registers::r5}; +static constexpr Register r6{Registers::r6}; +static constexpr Register r7{Registers::r7}; +static constexpr Register r8{Registers::r8}; +static constexpr Register r9{Registers::r9}; +static constexpr Register r10{Registers::r10}; +static constexpr Register r11{Registers::r11}; +static constexpr Register r12{Registers::r12}; +static constexpr Register r13{Registers::r13}; +static constexpr Register r14{Registers::r14}; +static constexpr Register r15{Registers::r15}; +static constexpr Register r16{Registers::r16}; +static constexpr Register r17{Registers::r17}; +static constexpr Register r18{Registers::r18}; +static constexpr Register r19{Registers::r19}; +static constexpr Register r20{Registers::r20}; +static constexpr Register r21{Registers::r21}; +static constexpr Register r22{Registers::r22}; +static constexpr Register r23{Registers::r23}; +static constexpr Register r24{Registers::r24}; +static constexpr Register r25{Registers::r25}; +static constexpr Register r26{Registers::r26}; +static constexpr Register r27{Registers::r27}; +static constexpr Register r28{Registers::r28}; +static constexpr Register r29{Registers::r29}; +static constexpr Register r30{Registers::r30}; +static constexpr Register r31{Registers::r31}; + +// FPR register constants. +static constexpr FloatRegister f0{FloatRegisters::f0, FloatRegisters::Double}; +static constexpr FloatRegister f1{FloatRegisters::f1, FloatRegisters::Double}; +static constexpr FloatRegister f2{FloatRegisters::f2, FloatRegisters::Double}; +static constexpr FloatRegister f3{FloatRegisters::f3, FloatRegisters::Double}; +static constexpr FloatRegister f4{FloatRegisters::f4, FloatRegisters::Double}; +static constexpr FloatRegister f5{FloatRegisters::f5, FloatRegisters::Double}; +static constexpr FloatRegister f6{FloatRegisters::f6, FloatRegisters::Double}; +static constexpr FloatRegister f7{FloatRegisters::f7, FloatRegisters::Double}; +static constexpr FloatRegister f8{FloatRegisters::f8, FloatRegisters::Double}; +static constexpr FloatRegister f9{FloatRegisters::f9, FloatRegisters::Double}; +static constexpr FloatRegister f10{FloatRegisters::f10, FloatRegisters::Double}; +static constexpr FloatRegister f11{FloatRegisters::f11, FloatRegisters::Double}; +static constexpr FloatRegister f12{FloatRegisters::f12, FloatRegisters::Double}; +static constexpr FloatRegister f13{FloatRegisters::f13, FloatRegisters::Double}; +static constexpr FloatRegister f14{FloatRegisters::f14, FloatRegisters::Double}; +static constexpr FloatRegister f15{FloatRegisters::f15, FloatRegisters::Double}; +static constexpr FloatRegister f16{FloatRegisters::f16, FloatRegisters::Double}; +static constexpr FloatRegister f17{FloatRegisters::f17, FloatRegisters::Double}; +static constexpr FloatRegister f18{FloatRegisters::f18, FloatRegisters::Double}; +static constexpr FloatRegister f19{FloatRegisters::f19, FloatRegisters::Double}; +static constexpr FloatRegister f20{FloatRegisters::f20, FloatRegisters::Double}; +static constexpr FloatRegister f21{FloatRegisters::f21, FloatRegisters::Double}; +static constexpr FloatRegister f22{FloatRegisters::f22, FloatRegisters::Double}; +static constexpr FloatRegister f23{FloatRegisters::f23, FloatRegisters::Double}; +static constexpr FloatRegister f24{FloatRegisters::f24, FloatRegisters::Double}; +static constexpr FloatRegister f25{FloatRegisters::f25, FloatRegisters::Double}; +static constexpr FloatRegister f26{FloatRegisters::f26, FloatRegisters::Double}; +static constexpr FloatRegister f27{FloatRegisters::f27, FloatRegisters::Double}; +static constexpr FloatRegister f28{FloatRegisters::f28, FloatRegisters::Double}; +static constexpr FloatRegister f29{FloatRegisters::f29, FloatRegisters::Double}; +static constexpr FloatRegister f30{FloatRegisters::f30, FloatRegisters::Double}; +static constexpr FloatRegister f31{FloatRegisters::f31, FloatRegisters::Double}; + +static constexpr Register InvalidReg{Registers::Invalid}; +static constexpr FloatRegister InvalidFloatReg; + +static constexpr Register StackPointer = r1; +static constexpr Register FramePointer = r31; +static constexpr Register ReturnReg = r3; +// SysV ppc32 returns a 64-bit integer in r3:r4, most significant word first. +static constexpr Register64 ReturnReg64(r3, r4); +static constexpr FloatRegister ReturnFloat32Reg{FloatRegisters::f1, + FloatRegisters::Single}; +static constexpr FloatRegister ReturnDoubleReg = f1; +static constexpr FloatRegister ReturnSimd128Reg{FloatRegisters::f1, + FloatRegisters::Simd128}; + +// r16 is non-volatile and non-allocatable, used as a saved scratch. +static constexpr Register SavedScratchRegister = r16; + +static constexpr Register SecondScratchReg = r12; + +static constexpr FloatRegister ScratchFloat32Reg{FloatRegisters::f0, + FloatRegisters::Single}; +static constexpr FloatRegister ScratchDoubleReg = f0; +static constexpr FloatRegister ScratchSimd128Reg{FloatRegisters::f0, + FloatRegisters::Simd128}; + +struct ScratchFloat32Scope : public AutoFloatRegisterScope { + explicit ScratchFloat32Scope(MacroAssembler& masm) + : AutoFloatRegisterScope(masm, ScratchFloat32Reg) {} +}; + +struct ScratchDoubleScope : public AutoFloatRegisterScope { + explicit ScratchDoubleScope(MacroAssembler& masm) + : AutoFloatRegisterScope(masm, ScratchDoubleReg) {} +}; + +// PPC: ScratchSimd128Scope is a simple register wrapper, NOT a scoped +// acquire/release. ScratchSimd128Reg is VMX register v0, encoded as +// {FloatRegisters::f0, Simd128}; VR0-VR31 are a physically separate file +// from FPR0-FPR31, so it does not alias ScratchDoubleReg = f0. It is +// non-allocatable and always available. Many SIMD functions call other SIMD +// functions that also need v0, creating nested "scopes". Using +// AutoFloatRegisterScope would assert on double-acquire in debug builds. +// Since v0 is never allocated by the register allocator, nesting is safe. +struct ScratchSimd128Scope : public FloatRegister { + explicit ScratchSimd128Scope(MacroAssembler&) + : FloatRegister(ScratchSimd128Reg) {} +}; + +class Assembler; + +class UseScratchRegisterScope { + public: + explicit UseScratchRegisterScope(Assembler& assembler); + explicit UseScratchRegisterScope(Assembler* assembler); + ~UseScratchRegisterScope(); + + Register Acquire(); + void Release(const Register& reg); + bool hasAvailable() const; + void Include(const GeneralRegisterSet& list) { + *available_ = GeneralRegisterSet::Union(*available_, list); + } + void Exclude(const GeneralRegisterSet& list) { + *available_ = GeneralRegisterSet::Subtract(*available_, list); + } + + private: + GeneralRegisterSet* available_; + GeneralRegisterSet old_available_; +}; + +static constexpr Register OsrFrameReg = r6; +static constexpr Register PreBarrierReg = r4; +static constexpr Register InterpreterPCReg = r17; + +static constexpr Register CallTempReg0 = r4; +static constexpr Register CallTempReg1 = r9; +static constexpr Register CallTempReg2 = r10; +static constexpr Register CallTempReg3 = r7; +// CallTempReg4 must NOT be JSReturnReg (r5): LMegamorphicLoadSlotPermissive +// uses tempFixed(CallTempReg4) for a saved obj pointer AND defineReturn +// (JSReturnOperand=r5) for output. If they alias, the megamorphic cache +// lookup clobbers the saved obj, corrupting the 'this' pointer. +static constexpr Register CallTempReg4 = r8; +static constexpr Register CallTempReg5 = r6; + +// r3-r10 are all argument registers and r11/r12 are the assembler's own +// scratch pair, so use allocatable non-volatile registers as overflow temps. +static constexpr Register CallTempNonArgRegs[] = {r14, r15}; +static const uint32_t NumCallTempNonArgRegs = std::size(CallTempNonArgRegs); + +static constexpr Register IntArgReg0 = r3; +static constexpr Register IntArgReg1 = r4; +static constexpr Register IntArgReg2 = r5; +static constexpr Register IntArgReg3 = r6; +static constexpr Register IntArgReg4 = r7; +static constexpr Register IntArgReg5 = r8; +static constexpr Register IntArgReg6 = r9; +static constexpr Register IntArgReg7 = r10; + +// Registers used by RegExpMatcher and RegExpExecMatch stubs. +static constexpr Register RegExpMatcherRegExpReg = CallTempReg0; +static constexpr Register RegExpMatcherStringReg = CallTempReg1; +static constexpr Register RegExpMatcherLastIndexReg = CallTempReg2; + +// Registers used by RegExpExecTest stub (do not use ReturnReg). +static constexpr Register RegExpExecTestRegExpReg = CallTempReg0; +static constexpr Register RegExpExecTestStringReg = CallTempReg1; + +// Registers used by RegExpSearcher stub (do not use ReturnReg). +static constexpr Register RegExpSearcherRegExpReg = CallTempReg0; +static constexpr Register RegExpSearcherStringReg = CallTempReg1; +static constexpr Register RegExpSearcherLastIndexReg = CallTempReg2; + +// nunbox32: a boxed JS::Value lives in a (type, payload) register pair. +static constexpr Register JSReturnReg_Type = r6; +static constexpr Register JSReturnReg_Data = r5; +static constexpr Register JSReturnReg = JSReturnReg_Data; +static constexpr ValueOperand JSReturnOperand{JSReturnReg_Type, + JSReturnReg_Data}; + +static constexpr Register ABINonArgReg0 = r19; +static constexpr Register ABINonArgReg1 = r20; +static constexpr Register ABINonArgReg2 = r21; +static constexpr Register ABINonArgReg3 = r22; +static constexpr Register ABINonArgReturnReg0 = r29; +static constexpr Register ABINonArgReturnReg1 = r30; +static constexpr Register ABINonVolatileReg = r14; +static constexpr Register ABINonArgReturnVolatileReg = r11; + +static constexpr FloatRegister ABINonArgDoubleReg{FloatRegisters::f14, + FloatRegisters::Double}; + +// Wasm instance pointer register. Preserved across wasm function calls. +static constexpr Register InstanceReg = r18; +static constexpr Register HeapReg = r24; +static constexpr Register GlobalReg = r23; + +// Wasm table call registers. +static constexpr Register WasmTableCallScratchReg0 = ABINonArgReg0; +static constexpr Register WasmTableCallScratchReg1 = ABINonArgReg1; +static constexpr Register WasmTableCallSigReg = ABINonArgReg2; +static constexpr Register WasmTableCallIndexReg = ABINonArgReg3; + +// Wasm ref call registers. +static constexpr Register WasmCallRefCallScratchReg0 = ABINonArgReg0; +static constexpr Register WasmCallRefCallScratchReg1 = ABINonArgReg1; +static constexpr Register WasmCallRefCallScratchReg2 = ABINonArgReg2; +static constexpr Register WasmCallRefReg = ABINonArgReg3; + +// Wasm tail call scratch registers. +// WasmTailCallRAScratchReg must NOT be ABINonArgReg0: the shared tail-call +// code (wasmReturnCallImport, wasmReturnCallIndirect, wasmReturnCallRef) +// stores the callee address in ABINonArgReg0, and CollapseWasmFrame* +// overwrites tempForRA. On architectures with a GPR link register (ARM, +// MIPS, LA64, RISC-V) this is ra/lr. PPC's LR is an SPR, so we use r14 +// (ABINonVolatileReg) which is callee-saved and not used in call setup. +static constexpr Register WasmTailCallInstanceScratchReg = ABINonArgReg1; +static constexpr Register WasmTailCallRAScratchReg = ABINonVolatileReg; +static constexpr Register WasmTailCallFPScratchReg = ABINonArgReg3; + +// Register used as a scratch along the return path in the fast js -> wasm stub +// code. Must not overlap ReturnReg, JSReturnOperand, or InstanceReg. +// Must be volatile. +static constexpr Register WasmJitEntryReturnScratch = r10; + +static constexpr uint32_t ABIStackAlignment = 16; +static constexpr uint32_t CodeAlignment = 16; +static constexpr uint32_t JitStackAlignment = 16; + +static constexpr uint32_t JitStackValueAlignment = + JitStackAlignment / sizeof(Value); +static_assert(JitStackAlignment % sizeof(Value) == 0 && + JitStackValueAlignment >= 1, + "Stack alignment should be a non-zero multiple of sizeof(Value)"); + +static constexpr uint32_t SimdMemoryAlignment = 16; +static_assert( + CodeAlignment % SimdMemoryAlignment == 0, + "Code alignment should be larger than any of the alignments " + "which are used for the constant sections of the code buffer. " + "Thus it should be larger than the alignment for SIMD constants."); + +static constexpr uint32_t WasmStackAlignment = SimdMemoryAlignment; +static const uint32_t WasmTrapInstructionLength = 4; + +static constexpr uint32_t WasmCheckedCallEntryOffset = 0u; +static constexpr uint32_t WasmCheckedTailEntryOffset = 32u; + +static constexpr Scale ScalePointer = TimesFour; + +// Instructions needed to materialise a pointer-sized immediate: +// lis rD, hi16 ; ori rD, rD, lo16 +// Every patchable pointer/jump stanza in this backend is built out of +// exactly this many words, so use this constant instead of a literal. +static constexpr uint32_t PointerImmInstructions = 2; + +// A patchable long branch or call: pointer materialisation into a scratch +// register, then mtctr + bctr/bctrl. +static constexpr uint32_t LongBranchInstructions = PointerImmInstructions + 2; + +class ABIArgGenerator : public ABIArgGeneratorShared { + public: + explicit ABIArgGenerator(ABIKind kind) + : ABIArgGeneratorShared(kind), + intRegIndex_(0), + floatRegIndex_(0), + current_() { + // System ABI: SysV ppc32 puts the first stack argument directly above the + // linkage area (back chain at 0(sp), callee-written LR save at 4(sp)), + // i.e. at 8(sp). Verified against GCC: a callee taking ten int arguments + // reads the ninth and tenth with `lwz 3,8(1)` / `lwz 9,12(1)`. + // The Wasm ABI instead places arguments above the whole FrameWithInstances + // shadow area, so that stack arguments do not overlap the caller/callee + // instance slots. + stackOffset_ += (kind == ABIKind::Wasm) ? ShadowStackSpace : LinkageAreaSize; + } + + ABIArg next(MIRType argType); + ABIArg& current() { return current_; } + + protected: + unsigned intRegIndex_; + unsigned floatRegIndex_; + ABIArg current_; +}; + +static constexpr uint32_t NumIntArgRegs = 8; +// SysV ppc32 passes FP arguments in f1-f8 (ELFv2 used f1-f13). +static constexpr uint32_t NumFloatArgRegs = 8; + +static inline bool GetIntArgReg(uint32_t usedIntArgs, Register* out) { + if (usedIntArgs < NumIntArgRegs) { + *out = Register::FromCode(r3.code() + usedIntArgs); + return true; + } + return false; +} + +static inline bool GetFloatArgReg(uint32_t usedFloatArgs, FloatRegister* out) { + if (usedFloatArgs < NumFloatArgRegs) { + *out = FloatRegister::FromCode(f1.code() + usedFloatArgs); + return true; + } + return false; +} + +static inline bool GetTempRegForIntArg(uint32_t usedIntArgs, + uint32_t usedFloatArgs, Register* out) { + MOZ_ASSERT(usedFloatArgs == 0); + + if (GetIntArgReg(usedIntArgs, out)) { + return true; + } + + usedIntArgs -= NumIntArgRegs; + if (usedIntArgs >= NumCallTempNonArgRegs) { + return false; + } + *out = CallTempNonArgRegs[usedIntArgs]; + return true; +} + +// PPC instruction field positions. +// PPC uses big-endian bit numbering (bit 0 = MSB), but we store instructions +// in a uint32_t where bit 0 = LSB. The shifts below are in LSB-0 terms. +// +// [0:5] primary opcode (OpcodeShift=26) +// [6:10] RT/RS/BF/TO (RTShift=21, 5 bits) +// [11:15] RA/BI (RAShift=16, 5 bits) +// [16:20] RB/SH (RBShift=11, 5 bits) +// [16:31] SI/UI/D (Imm16Shift=0, 16 bits) +// [21:25] subop bits (varies) +// [21:30] XO (X-form; A/M/MD/MDS narrower) +// [31] Rc bit (RcShift=0) + +static const uint32_t OpcodeShift = 26; +static const uint32_t OpcodeBits = 6; + +static const uint32_t RTShift = 21; +static const uint32_t RTBits = 5; +static const uint32_t RSShift = 21; +static const uint32_t RSBits = 5; +static const uint32_t RAShift = 16; +static const uint32_t RABits = 5; +static const uint32_t RBShift = 11; +static const uint32_t RBBits = 5; +static const uint32_t RCShift = 6; +static const uint32_t RCBits = 5; + +static const uint32_t BOShift = 21; +static const uint32_t BOBits = 5; +static const uint32_t BIShift = 16; +static const uint32_t BIBits = 5; + +static const uint32_t Imm16Shift = 0; +static const uint32_t Imm16Bits = 16; + +static const uint32_t RcShift = 0; +static const uint32_t RcBit = 1; + +static const uint32_t RTMask = ((1 << RTBits) - 1) << RTShift; +static const uint32_t RSMask = ((1 << RSBits) - 1) << RSShift; +static const uint32_t RAMask = ((1 << RABits) - 1) << RAShift; +static const uint32_t RBMask = ((1 << RBBits) - 1) << RBShift; +static const uint32_t Imm16Mask = (1 << Imm16Bits) - 1; +static const uint32_t RegMask = (1 << RTBits) - 1; + +static inline uint32_t RT(Register r) { return (uint32_t)r.code() << RTShift; } +static inline uint32_t RT(FloatRegister r) { + return (uint32_t)r.code() << RTShift; +} +static inline uint32_t RS(Register r) { return (uint32_t)r.code() << RSShift; } +static inline uint32_t RS(FloatRegister r) { + return (uint32_t)r.code() << RSShift; +} +static inline uint32_t RA(Register r) { return (uint32_t)r.code() << RAShift; } +static inline uint32_t RA(FloatRegister r) { + return (uint32_t)r.code() << RAShift; +} +static inline uint32_t RB(Register r) { return (uint32_t)r.code() << RBShift; } +static inline uint32_t RB(FloatRegister r) { + return (uint32_t)r.code() << RBShift; +} + +// SPR encoding: the SPR number is split across bits 11-15 and 16-20 in a +// swapped arrangement. PPC_SPR(x) produces the value to OR into an +// mtspr/mfspr instruction at the RB+RA position (bits 11-20). +#define PPC_SPR(x) ((((int)(x) >> 5) & 0x1f) << 11 | ((int)(x) & 0x1f) << 16) + +enum PPCOpcodes { + PPC_add = 0x7C000214, + PPC_addc = 0x7C000014, + PPC_adde = 0x7C000114, + PPC_addi = 0x38000000, + PPC_addis = 0x3C000000, + PPC_and_ = 0x7C000038, + // andi. is always record form (no non-record andi exists). + PPC_andi_dot = 0x70000000, + PPC_b = 0x48000000, + PPC_bc = 0x40000000, + // Encoded "bcl 20, lt, $+4": PC-relative branch-and-link by 4 bytes + // (land at the next instruction) with BO=20 (branch always); BI=0 + // (=lt) is don't-care because BO=20 forces the branch. There is no + // addpcis on this target, so every constant-pool load seeds LR with the + // current PC this way and then does mflr + lfd/lfs/lvx. + PPC_bcl_always_plus4 = 0x42800005, + PPC_bctr = 0x4E800420, + PPC_bcctr = 0x4C000420, + PPC_blr = 0x4E800020, + // There are no doubleword compare forms here: the L bit (0x00200000) is + // always 0, so cmp/cmpi/cmpl/cmpli only ever compare 32 bits. + PPC_cmpw = 0x7C000000, + PPC_cmpwi = 0x2C000000, + PPC_cmplw = 0x7C000040, + PPC_cmplwi = 0x28000000, + PPC_cntlzd = 0x7C000074, + PPC_cntlzw = 0x7C000034, + PPC_cnttzd = 0x7C000474, + PPC_cnttzw = 0x7C000434, + PPC_crandc = 0x4C000102, + PPC_cror = 0x4C000382, + PPC_crorc = 0x4C000342, + PPC_divd = 0x7C0003D2, + PPC_divdu = 0x7C000392, + PPC_divw = 0x7C0003D6, + PPC_divwu = 0x7C000396, + // POWER9 (ISA 3.0) modulo instructions. + PPC_modsd = 0x7C000612, + PPC_modsw = 0x7C000616, + PPC_modud = 0x7C000212, + PPC_moduw = 0x7C000216, + PPC_extsb = 0x7C000774, + PPC_extsh = 0x7C000734, + PPC_extsw = 0x7C0007B4, + PPC_fabs = 0xFC000210, + PPC_fadd = 0xFC00002A, + PPC_fadds = 0xEC00002A, + PPC_fcpsgn = 0xFC000010, + PPC_fcfid = 0xFC00069C, + PPC_fcfids = 0xEC00069C, + PPC_fcfidu = 0xFC00079C, + PPC_fcfidus = 0xEC00079C, + PPC_fcmpu = 0xFC000000, + PPC_fctid = 0xFC00065C, + PPC_fctidz = 0xFC00065E, + PPC_fctiduz = 0xFC00075E, + PPC_fctiw = 0xFC00001C, + PPC_fctiwz = 0xFC00001E, + PPC_fdiv = 0xFC000024, + PPC_fdivs = 0xEC000024, + PPC_fmr = 0xFC000090, + PPC_fmul = 0xFC000032, + PPC_fmuls = 0xEC000032, + PPC_fneg = 0xFC000050, + PPC_frim = 0xFC0003D0, + PPC_frip = 0xFC000390, + PPC_friz = 0xFC000350, + PPC_frsp = 0xFC000018, + PPC_fsub = 0xFC000028, + PPC_fsubs = 0xEC000028, + PPC_fsqrt = 0xFC00002C, + PPC_fsqrts = 0xEC00002C, + PPC_isel = 0x7C00001E, + // POWER10 (ISA 3.1). RT = (CR[BI]==1) ? 1 : 0. XO=384 at bits 21-30. + PPC_setbc = 0x7C000300, + // POWER10 (ISA 3.1). RT = (CR[BI]==0) ? 1 : 0. XO=416. + PPC_setbcr = 0x7C000340, + PPC_lbarx = 0x7C000068, + PPC_lbz = 0x88000000, + PPC_lbzx = 0x7C0000AE, + PPC_ld = 0xE8000000, + PPC_ldarx = 0x7C0000A8, + PPC_ldx = 0x7C00002A, + PPC_lfd = 0xC8000000, + PPC_lfdx = 0x7C0004AE, + PPC_lfiwax = 0x7C0006AE, + PPC_lfiwzx = 0x7C0006EE, + PPC_lfs = 0xC0000000, + PPC_lfsx = 0x7C00042E, + PPC_lha = 0xA8000000, + PPC_lharx = 0x7C0000E8, + PPC_lhax = 0x7C0002AE, + PPC_lhz = 0xA0000000, + PPC_lhzx = 0x7C00022E, + PPC_lwa = 0xE8000002, + PPC_lwarx = 0x7C000028, + PPC_lwz = 0x80000000, + // X-form sign-extending word load (opcode 31, XO=341). Single-insn + // equivalent of lwzx + extsw. + PPC_lwax = 0x7C0002AA, + PPC_lwzx = 0x7C00002E, + // Byte-reverse (little-endian) indexed load/store. Used for wasm's + // spec-mandated little-endian linear memory on big-endian hosts (opcode 31). + PPC_lhbrx = 0x7C00062C, // XO=790 + PPC_lwbrx = 0x7C00042C, // XO=534 + PPC_ldbrx = 0x7C000428, // XO=532 (POWER7+) + PPC_sthbrx = 0x7C00072C, // XO=918 + PPC_stdbrx = 0x7C000528, // XO=660 (POWER7+) + PPC_mcrxrx = 0x7C000480, + PPC_mcrfs = 0xFC000080, + PPC_mfocrf = 0x7C100026, + PPC_mffs = 0xFC00048E, + PPC_mfspr = 0x7C0002A6, + PPC_mtcrf = 0x7C000120, + PPC_mtfsb0 = 0xFC00008C, + PPC_mtfsb1 = 0xFC00004C, + PPC_mtspr = 0x7C0003A6, + PPC_mulhd = 0x7C000092, + PPC_mulhdu = 0x7C000012, + PPC_mulhwu = 0x7C000016, + PPC_mulli = 0x1C000000, + PPC_mulld = 0x7C0001D2, + PPC_mulldo = 0x7C0005D2, + PPC_mullw = 0x7C0001D6, + PPC_neg = 0x7C0000D0, + PPC_nor = 0x7C0000F8, + PPC_or_ = 0x7C000378, + PPC_ori = 0x60000000, + PPC_oris = 0x64000000, + PPC_popcntb = 0x7C0000F4, + PPC_popcntd = 0x7C0003F4, + PPC_popcntw = 0x7C0002F4, + PPC_brd = 0x7C000176, // POWER10: byte-reverse doubleword (X-form, XO=187) + PPC_brh = 0x7C0001B6, // POWER10: byte-reverse each halfword (X-form, XO=219) + PPC_brw = 0x7C000136, // POWER10: byte-reverse each word (X-form, XO=155) + PPC_rldcl = 0x78000010, + PPC_rldicl = 0x78000000, + PPC_rldcr = 0x78000012, + PPC_rldicr = 0x78000004, + PPC_rldimi = 0x7800000C, + PPC_rlwimi = 0x50000000, + PPC_rlwinm = 0x54000000, + PPC_rlwnm = 0x5C000000, + PPC_sld = 0x7C000036, + PPC_slw = 0x7C000030, + PPC_srad = 0x7C000634, + PPC_sradi = 0x7C000674, + PPC_sraw = 0x7C000630, + PPC_srawi = 0x7C000670, + PPC_srd = 0x7C000436, + PPC_srw = 0x7C000430, + PPC_stb = 0x98000000, + PPC_stbcx = 0x7C00056D, + PPC_stbx = 0x7C0001AE, + PPC_std = 0xF8000000, + PPC_stdcx = 0x7C0001AD, + PPC_stdu = 0xF8000001, + PPC_stdx = 0x7C00012A, + PPC_stfd = 0xD8000000, + PPC_stfdu = 0xDC000000, + PPC_stfdx = 0x7C0005AE, + PPC_stfs = 0xD0000000, + PPC_stfsu = 0xD4000000, + PPC_stfsx = 0x7C00052E, + PPC_sth = 0xB0000000, + PPC_sthcx = 0x7C0005AD, + PPC_sthx = 0x7C00032E, + PPC_stw = 0x90000000, + PPC_stwu = 0x94000000, + PPC_stwx = 0x7C00012E, + PPC_stwbrx = 0x7C00052C, + PPC_stwcx = 0x7C00012D, + PPC_subf = 0x7C000050, + PPC_subfc = 0x7C000010, + PPC_subfe = 0x7C000110, + PPC_subfic = 0x20000000, + PPC_sync = 0x7C0004AC, + // isync — execution synchronization. Discards prefetched instructions and + // forces a refetch+reexecute of everything past the barrier; prevents + // speculative bypass. Used for Spectre v1 mitigation in speculationBarrier. + // Encoding: bytes `2c 01 00 4c` (LE) = 0x4C00012C. + PPC_isync = 0x4C00012C, + PPC_trap = 0x7FE00008, + PPC_tw = 0x7C000008, + PPC_xor_ = 0x7C000278, + PPC_xori = 0x68000000, + PPC_xoris = 0x6C000000, + // VMX register load/store (X-form, opcode 31). lvx/stvx force the effective + // address to a 16-byte boundary; lvsl/lvsr build the vperm control vector + // used to reassemble an unaligned quadword from two aligned loads. + PPC_lvx = 0x7C0000CE, // XO=103 + PPC_stvx = 0x7C0001CE, // XO=231 + PPC_lvsl = 0x7C00000C, // XO=6 + PPC_lvsr = 0x7C00004C, // XO=38 + // VSCR access (VX-form). + PPC_mfvscr = 0x10000604, + PPC_mtvscr = 0x10000644, + PPC_vaddubm = 0x10000000, + PPC_vavgub = 0x10000402, + PPC_vavguh = 0x10000442, + PPC_vcmpequb = 0x10000006, + PPC_vcmpequh = 0x10000046, + PPC_vcmpequw = 0x10000086, + PPC_vcmpequd = 0x100000C7, + PPC_vcmpgtsb = 0x10000306, + PPC_vcmpgtsh = 0x10000346, + PPC_vcmpgtsw = 0x10000386, + PPC_vcmpgtsd = 0x100003C7, + PPC_vcmpgtub = 0x10000206, + PPC_vcmpgtuh = 0x10000246, + PPC_vcmpgtuw = 0x10000286, + PPC_vcmpgtud = 0x100002C7, + PPC_vcmpneb = 0x10000007, // POWER9 (ISA 3.0) + PPC_vcmpneh = 0x10000047, // POWER9 + PPC_vcmpnew = 0x10000087, // POWER9 + PPC_vadduhm = 0x10000040, + PPC_vadduwm = 0x10000080, + PPC_vaddudm = 0x100000C0, + PPC_vaddubs = 0x10000200, + PPC_vadduhs = 0x10000240, + PPC_vaddsbs = 0x10000300, + PPC_vaddshs = 0x10000340, + PPC_vmaxsb = 0x10000102, + PPC_vmaxsh = 0x10000142, + PPC_vmaxsw = 0x10000182, + PPC_vmaxsd = 0x100001C2, + PPC_vmaxub = 0x10000002, + PPC_vmaxuh = 0x10000042, + PPC_vmaxuw = 0x10000082, + PPC_vmhraddshs = 0x10000021, + PPC_vmrghb = 0x1000000C, + PPC_vmrghh = 0x1000004C, + PPC_vmrghw = 0x1000008C, + PPC_vmrglb = 0x1000010C, + PPC_vmrglh = 0x1000014C, + PPC_vmrglw = 0x1000018C, + PPC_vminsb = 0x10000302, + PPC_vminsh = 0x10000342, + PPC_vminsw = 0x10000382, + PPC_vminub = 0x10000202, + PPC_vminuh = 0x10000242, + PPC_vminuw = 0x10000282, + // POWER9 (ISA 3.0) per-lane integer negate. VRA field carries the subop + // code: 6 for vnegw, 7 for vnegd. Base XO is 0x602. + PPC_vnegw = 0x10060602, + PPC_vnegd = 0x10070602, + PPC_vmladduhm = 0x10000022, + PPC_vmuluwm = 0x10000089, + PPC_vmulld = 0x100001C9, // POWER10 (XO=457, vector i64x2 multiply low) + PPC_vmulesb = 0x10000308, + PPC_vmuleub = 0x10000208, + PPC_vmulesh = 0x10000348, + PPC_vmuleuh = 0x10000248, + PPC_vmulesw = 0x10000388, + PPC_vmuleuw = 0x10000288, + PPC_vmulosb = 0x10000108, + PPC_vmuloub = 0x10000008, + PPC_vmulosh = 0x10000148, + PPC_vmulouh = 0x10000048, + PPC_vmulosw = 0x10000188, + PPC_vmulouw = 0x10000088, + PPC_vmsumshm = 0x10000028, + PPC_vmsumuhm = 0x10000026, + PPC_vperm = 0x1000002B, + // VX-form, opcode 4, XO=0x54C. Per-byte bit-permute of a 128-bit value; + // result 16-bit bitmap lands in dw0 low 16 bits, recoverable via mfvsrd. + // Available on POWER8+ (ISA 2.07). + PPC_vbpermq = 0x1000054C, + // POWER10 (ISA 3.1) Vector Extract Mask. VX-form, opcode 4, XO=0x642, + // with UIM at bits 11..15 selecting lane width: 8=byte, 9=halfword, + // 10=word, 11=doubleword. RT is a GPR (low N bits = wasm bitmask). + PPC_vextractbm = 0x10080642, + PPC_vextracthm = 0x10090642, + PPC_vextractwm = 0x100A0642, + PPC_vextractdm = 0x100B0642, + // POWER10 vector insert from GPR at immediate byte offset: + // vinsw VRT, RB, UIM VRT[UIM*8:UIM*8+31] ← RB[32:63] + // vinsd VRT, RB, UIM VRT[UIM*8:UIM*8+63] ← RB[0:63] + // VX-form, opcode 4. RB at bits 16..20, UIM at bits 11..15. + PPC_vinsw = 0x100000CF, // POWER10 (XO=207) + PPC_vinsd = 0x100001CF, // POWER10 (XO=463) + // POWER10 vector insert byte/halfword from GPR with register-supplied + // (right-indexed = LE-natural) byte position: + // vinsbrx VRT, RA, RB VRT.byte[RA & 0xF] ← RB & 0xFF + // vinshrx VRT, RA, RB VRT.hword[(RA & 0xE)/2] ← RB & 0xFFFF + // VX-form, opcode 4. RA at bits 16..20, RB at bits 11..15. + PPC_vinsbrx = 0x1000030F, // POWER10 (XO=783) + PPC_vinshrx = 0x1000034F, // POWER10 (XO=847) + // POWER9 (ISA 3.0) vector insert byte/halfword from VR at immediate + // byte position: + // vinsertb VRT, VRB, UIM VRT.byte[UIM] ← VRB.byte[7] (BE) + // vinserth VRT, VRB, UIM VRT.hword[UIM..+1] ← VRB.byte[6..7] (BE) + // V-form, opcode 4. VRB at bits 11..15, UIM at bits 16..20. A Simd128 + // FloatRegister names a VR via `encoding() & 31`. + PPC_vinsertb = 0x1000030D, // POWER9 (XO=781) + PPC_vinserth = 0x1000034D, // POWER9 (XO=845) + PPC_vextractub = 0x1000020D, // POWER9 (XO=525) + PPC_vextractuh = 0x1000024D, // POWER9 (XO=589) + PPC_vspltisb = 0x1000030C, // POWER7+ (XO=780, splat 5-bit SIMM to all 16 byte lanes) + PPC_vspltish = 0x1000034C, // POWER7+ (XO=844, splat 5-bit SIMM to all 8 i16 lanes) + PPC_vspltisw = 0x1000038C, // POWER7+ (XO=908, splat 5-bit SIMM to all 4 i32 lanes) + PPC_vpopcntb = 0x10000703, + PPC_vslb = 0x10000104, + PPC_vsld = 0x100005C4, + PPC_vsldoi = 0x1000002C, + PPC_vslh = 0x10000144, + PPC_vslo = 0x1000040C, + PPC_vslw = 0x10000184, + PPC_vspltb = 0x1000020C, + PPC_vsplth = 0x1000024C, + PPC_vsrab = 0x10000304, + PPC_vsrad = 0x100003C4, + PPC_vsrah = 0x10000344, + PPC_vsraw = 0x10000384, + PPC_vsrb = 0x10000204, + PPC_vsrd = 0x100006C4, + PPC_vsrh = 0x10000244, + PPC_vsro = 0x1000044C, + PPC_vsrw = 0x10000284, + PPC_vpkshss = 0x1000018E, + PPC_vpkshus = 0x1000010E, + PPC_vpkswss = 0x100001CE, + PPC_vpkswus = 0x1000014E, + PPC_vupkhsb = 0x1000020E, + PPC_vupkhsh = 0x1000024E, + PPC_vupkhsw = 0x1000064E, + PPC_vupklsb = 0x1000028E, + PPC_vupklsh = 0x100002CE, + PPC_vupklsw = 0x100006CE, + PPC_vsububm = 0x10000400, + PPC_vsubuhm = 0x10000440, + PPC_vsubuwm = 0x10000480, + PPC_vsubudm = 0x100004C0, + PPC_vsububs = 0x10000600, + PPC_vsubuhs = 0x10000640, + PPC_vsubsbs = 0x10000700, + PPC_vsubshs = 0x10000740, + + // VMX bitwise logical (VX-form). These replace the VSX xxl* family. + PPC_vand = 0x10000404, + PPC_vandc = 0x10000444, + PPC_vor = 0x10000484, + PPC_vxor = 0x100004C4, + PPC_vnor = 0x10000504, + // VA-form bitwise select (replaces xxsel). + PPC_vsel = 0x1000002A, + // Word splat (replaces xxspltw); the byte/halfword forms are above. + PPC_vspltw = 0x1000028C, + // Modulo pack (truncating), used to narrow i32x4 -> i16x8 etc. + PPC_vpkuhum = 0x1000000E, + PPC_vpkuwum = 0x1000004E, + // Whole-vector bit shifts (as opposed to the per-lane vsl*/vsr* above). + PPC_vsl = 0x100001C4, + PPC_vsr = 0x100002C4, + // VMX single-precision float arithmetic. VMX has no double-precision + // vector arithmetic at all: f64x2 must be done with scalar FPR ops. + PPC_vaddfp = 0x1000000A, + PPC_vsubfp = 0x1000004A, + PPC_vmaxfp = 0x1000040A, + PPC_vminfp = 0x1000044A, + PPC_vmaddfp = 0x1000002E, // VA-form + PPC_vnmsubfp = 0x1000002F, // VA-form + // Reciprocal / reciprocal-square-root estimates. VMX has no vector divide + // or square root; these plus Newton-Raphson are the only option. + PPC_vrefp = 0x1000010A, + PPC_vrsqrtefp = 0x1000014A, + PPC_vexptefp = 0x1000018A, + PPC_vlogefp = 0x100001CA, + // Float rounding (nearest/trunc/ceil/floor). + PPC_vrfin = 0x1000020A, + PPC_vrfiz = 0x1000024A, + PPC_vrfip = 0x1000028A, + PPC_vrfim = 0x100002CA, + // Int <-> float conversions with a 5-bit scale immediate in the UIM field. + PPC_vcfux = 0x1000030A, + PPC_vcfsx = 0x1000034A, + PPC_vctuxs = 0x1000038A, + PPC_vctsxs = 0x100003CA, + // Float compares (VC-form; Rc bit at 0x400 sets CR6). + PPC_vcmpeqfp = 0x100000C6, + PPC_vcmpgefp = 0x100001C6, + PPC_vcmpgtfp = 0x100002C6, + PPC_vcmpbfp = 0x100003C6, + + // Simplified mnemonics. + PPC_mr = PPC_or_, + PPC_not = PPC_nor, + PPC_nop = PPC_ori, + PPC_lwsync = PPC_sync | (1 << 21), + + PPC_MAJOR_OPCODE_MASK = 0xFC000000 +}; + +static const uint32_t NopInst = (uint32_t)PPC_nop; + +class Instruction; +class InstReg; +class InstImm; +class BOffImm16; +class JOffImm26; + +// PPC base instruction type: a single 32-bit word. +class Instruction { + protected: + uint32_t data; + + public: + explicit Instruction(uint32_t data_) : data(data_) {} + explicit Instruction(PPCOpcodes op) : data((uint32_t)op) {} + + uint32_t encode() const { return data; } + + void makeNop() { data = NopInst; } + void makeOp_mtctr(Register r) { + data = PPC_mtspr | ((uint32_t)r.code()) << 21 | PPC_SPR(9); + } + void makeOp_bctr(uint32_t linkBit = 0) { data = PPC_bctr | linkBit; } + + void setData(uint32_t data) { this->data = data; } + + const Instruction& operator=(const Instruction& src) { + data = src.data; + return *this; + } + + uint32_t extractBit(uint32_t bit) const { return (encode() >> bit) & 1; } + uint32_t extractBitField(uint32_t hi, uint32_t lo) const { + return (encode() >> lo) & ((2 << (hi - lo)) - 1); + } + + uint32_t extractOpcode() const { return data & PPC_MAJOR_OPCODE_MASK; } + bool isOpcode(uint32_t op) const { + return extractOpcode() == (op & PPC_MAJOR_OPCODE_MASK); + } + + uint32_t extractRT() const { + return extractBitField(RTShift + RTBits - 1, RTShift); + } + uint32_t extractRA() const { + return extractBitField(RAShift + RABits - 1, RAShift); + } + uint32_t extractRB() const { + return extractBitField(RBShift + RBBits - 1, RBShift); + } + uint32_t extractImm16() const { return data & Imm16Mask; } + + Instruction* next() { return this + 1; } + + const uint32_t* raw() const { return &data; } + uint32_t size() const { return 4; } +}; + +static_assert(sizeof(Instruction) == 4); + +// Register-register-register instruction (X-form and XO-form). +class InstReg : public Instruction { + public: + explicit InstReg(PPCOpcodes op) : Instruction(op) {} + InstReg(PPCOpcodes op, Register rt, Register ra, Register rb) + : Instruction((uint32_t)op | RT(rt) | RA(ra) | RB(rb)) {} + InstReg(PPCOpcodes op, FloatRegister frt, FloatRegister fra, + FloatRegister frb) + : Instruction((uint32_t)op | RT(frt) | RA(fra) | RB(frb)) {} + + void setRT(Register r) { data = (data & ~RTMask) | RT(r); } + void setRA(Register r) { data = (data & ~RAMask) | RA(r); } + void setRB(Register r) { data = (data & ~RBMask) | RB(r); } + + void setImm16(uint32_t imm) { + data = (data & 0xFFFF0000) | (imm & Imm16Mask); + } + uint32_t extractImm16Value() const { return data & Imm16Mask; } +}; + +// Register-immediate instruction (D-form). +// Bits 21-25 hold RT (loads, addi) or RS (stores, ori). Both encode identically +// since RT and RS occupy the same field; the caller simply passes the right +// register. +class InstImm : public Instruction { + public: + explicit InstImm(PPCOpcodes op) : Instruction(op) {} + InstImm(PPCOpcodes op, Register rt, Register ra, uint32_t imm16) + : Instruction((uint32_t)op | RT(rt) | RA(ra) | (imm16 & Imm16Mask)) {} + + void setRT(Register r) { data = (data & ~RTMask) | RT(r); } + void setRA(Register r) { data = (data & ~RAMask) | RA(r); } + + void setImm16(uint32_t imm) { + data = (data & 0xFFFF0000) | (imm & Imm16Mask); + } + void setLowerReg(Register rl) { + data = (data & 0xFFE0FFFF) | ((uint32_t)rl.code() << 16); + } + uint32_t extractImm16Value() const { return data & Imm16Mask; } + + // Extract the TrapTag from a tagged trap instruction (tw). + // Defined in Assembler-ppc.cpp. Returns a TrapTag value as uint8_t + // because Assembler::TrapTag is not yet defined at this point in the header. + uint8_t traptag(); +}; + +// A BOffImm16 is a 16-bit signed branch offset for conditional branches +// (bc-form instructions). The offset is stored in bits 2..15 and is +// 4-byte aligned, giving a range of +/-32 KB. +class BOffImm16 { + int32_t data; + + public: + uint32_t encode() const { + MOZ_ASSERT(!isInvalid()); + return static_cast(data) & 0xFFFC; + } + int32_t decode() const { + MOZ_ASSERT(!isInvalid()); + return data; + } + + explicit BOffImm16(int offset) : data(offset) { + MOZ_ASSERT((offset & 0x3) == 0); + MOZ_ASSERT(IsInRange(offset)); + } + static bool IsInRange(int offset) { + return offset >= -32768 && offset <= 32764; + } + + static const int32_t INVALID = 0x00020000; + BOffImm16() : data(INVALID) {} + + bool isInvalid() const { return data == INVALID; } + + Instruction* getDest(Instruction* src) const; + + explicit BOffImm16(InstImm inst); +}; + +// A JOffImm26 is a 26-bit signed branch offset for unconditional branches +// (b/bl instructions). Bits 2..25 encode the offset, 4-byte aligned, +// giving a range of +/-32 MB. +class JOffImm26 { + int32_t data; + + public: + uint32_t encode() const { + MOZ_ASSERT(!isInvalid()); + return static_cast(data) & 0x03FFFFFC; + } + int32_t decode() const { + MOZ_ASSERT(!isInvalid()); + return data; + } + + explicit JOffImm26(int offset) : data(offset) { + MOZ_ASSERT((offset & 0x3) == 0); + MOZ_ASSERT(IsInRange(offset)); + } + static bool IsInRange(int offset) { + return offset >= -33554432 && offset <= 33554428; + } + + static const int32_t INVALID = 0x20000000; + JOffImm26() : data(INVALID) {} + + bool isInvalid() const { return data == INVALID; } + + Instruction* getDest(Instruction* src) const; +}; + +// A 16-bit immediate value used in D-form instructions. +class Imm16 { + int32_t value; + + public: + Imm16(); + explicit Imm16(uint32_t imm) : value(imm) {} + uint32_t encode() const { return static_cast(value) & 0xffff; } + int32_t decodeSigned() const { return value; } + uint32_t decodeUnsigned() const { return value; } + static bool IsInSignedRange(int32_t imm) { + return imm >= INT16_MIN && imm <= INT16_MAX; + } + static bool IsInUnsignedRange(uint32_t imm) { return imm <= UINT16_MAX; } + static Imm16 Lower(Imm32 imm) { return Imm16(imm.value & 0xffff); } + static Imm16 Upper(Imm32 imm) { return Imm16((imm.value >> 16) & 0xffff); } +}; + +class Operand { + public: + enum Tag { REG, FREG, MEM }; + + private: + Tag tag : 3; + uint32_t reg : 5; + int32_t offset; + + public: + MOZ_IMPLICIT Operand(Register reg_) : tag(REG), reg(reg_.code()) {} + + explicit Operand(FloatRegister freg) : tag(FREG), reg(freg.code()) {} + + Operand(Register base, Imm32 off) + : tag(MEM), reg(base.code()), offset(off.value) {} + + Operand(Register base, int32_t off) + : tag(MEM), reg(base.code()), offset(off) {} + + explicit Operand(const Address& addr) + : tag(MEM), reg(addr.base.code()), offset(addr.offset) {} + + Tag getTag() const { return tag; } + + Register toReg() const { + MOZ_ASSERT(tag == REG); + return Register::FromCode(reg); + } + + FloatRegister toFReg() const { + MOZ_ASSERT(tag == FREG); + return FloatRegister::FromCode(reg); + } + + void toAddr(Register* r, Imm32* dest) const { + MOZ_ASSERT(tag == MEM); + *r = Register::FromCode(reg); + *dest = Imm32(offset); + } + Address toAddress() const { + MOZ_ASSERT(tag == MEM); + return Address(Register::FromCode(reg), offset); + } + int32_t disp() const { + MOZ_ASSERT(tag == MEM); + return offset; + } + + int32_t base() const { + MOZ_ASSERT(tag == MEM); + return reg; + } + Register baseReg() const { + MOZ_ASSERT(tag == MEM); + return Register::FromCode(reg); + } +}; + +// Bug 2034064 collapsed the per-buffer compile-time configuration of +// AssemblerBufferWithConstantPools into AssemblerBufferSettings, and reduced +// the runtime ctor to (poolMaxOffset, nopFill). instBufferAlign and the +// NumShortBranchRanges template arg were dropped: PPC previously passed +// instBufferAlign=8 (unused on this backend; pool entries are 4-byte aligned) +// and NumShortBranchRanges=0. +using PPCBuffer = js::jit::AssemblerBufferWithConstantPools< + Instruction, Assembler, + js::jit::AssemblerBufferSettings{ + .instSize = 4, + .guardSize = 1, + .headerSize = 1, + .pcBias = 0, + .alignFillInst = NopInst, + .nopFillInst = NopInst, + }>; + +// Inherits executableCopy() and appendRawCode() from +// AssemblerBufferWithConstantPools, which assert pool is flushed. +class PPCBufferWithExecutableCopy : public PPCBuffer { + public: + PPCBufferWithExecutableCopy(size_t poolMaxOffset, unsigned nopFill) + : PPCBuffer(poolMaxOffset, nopFill) {} +}; + +class Assembler : public AssemblerShared { + public: + // Trap tags encoded in the low bits of a trap word. + // FreeBSD and others may use r1 in their trap word, so bit 0 is avoided. + enum TrapTag { + BTag = 2, + BCTag = 4, + CallTag = 6, + DebugTag0 = 10, + DebugTag1 = 12, + DebugTag2 = 14 + }; + + // Pool load types encoded in bits 21-22 of pool hint words. + // Used by InsertIndexIntoTag / PatchConstantPoolLoad. + enum PoolLoadType { + PoolLoadFPR64 = 1, // lfd fD, offset(rBase) + PoolLoadSimd128 = 2, // addi rBase, rBase, offset; lvx vD, 0, rBase + PoolLoadFPR32 = 3 // lfs fD, offset(rBase) — auto-expands to double + }; + + enum BranchBits { + BranchOnClear = 0x04, + BranchOnSet = 0x0c, + BranchOptionMask = 0x0f, + BranchOptionInvert = 0x08 + }; + + // PPC condition encoding. The top nybble is the offset to the CR field + // (the x in BIF*4+x), and the bottom is the BO field. + // Synthetic flags sit in the MSB and are masked off before use. + enum Condition { + ConditionUnsigned = 0x100, + ConditionUnsignedHandled = 0x2ff, + ConditionZero = 0x400, + ConditionOnlyXER = 0x200, + ConditionXERCA = 0x23c, + ConditionXERNCA = 0x234, + ConditionXEROV = 0x21c, + + Equal = 0x2c, + NotEqual = 0x24, + GreaterThan = 0x1c, + GreaterThanOrEqual = 0x04, + LessThan = 0x0c, + LessThanOrEqual = 0x14, + + Above = GreaterThan | ConditionUnsigned, + AboveOrEqual = GreaterThanOrEqual | ConditionUnsigned, + Below = LessThan | ConditionUnsigned, + BelowOrEqual = LessThanOrEqual | ConditionUnsigned, + + Signed = LessThan | ConditionZero, + NotSigned = GreaterThanOrEqual | ConditionZero, + Zero = Equal | ConditionZero, + NonZero = NotEqual | ConditionZero, + + Overflow = ConditionXEROV, + NotOverflow = ConditionOnlyXER | LessThanOrEqual, + CarrySet = ConditionXERCA, + CarryClear = ConditionXERNCA, + + Always = 0x1f, + SOBit = 0x3c, + NSOBit = 0x34 + }; + + enum DoubleCondition { + DoubleConditionUnordered = 0x100, + DoubleOrdered = 0x34, + DoubleEqual = 0x2c, + DoubleNotEqual = 0x24, + DoubleGreaterThan = 0x1c, + DoubleGreaterThanOrEqual = 0x04, + DoubleLessThan = 0x0c, + DoubleLessThanOrEqual = 0x14, + DoubleUnordered = 0x3c, + DoubleEqualOrUnordered = DoubleEqual | DoubleConditionUnordered, + DoubleNotEqualOrUnordered = DoubleNotEqual | DoubleConditionUnordered, + DoubleGreaterThanOrUnordered = DoubleGreaterThan | DoubleConditionUnordered, + DoubleGreaterThanOrEqualOrUnordered = + DoubleGreaterThanOrEqual | DoubleConditionUnordered, + DoubleLessThanOrUnordered = DoubleLessThan | DoubleConditionUnordered, + DoubleLessThanOrEqualOrUnordered = + DoubleLessThanOrEqual | DoubleConditionUnordered, + }; + + enum LinkBit { + DontLinkB = 0, + LinkB = 1, + }; + + enum LikelyBit { + NotLikelyB = 0, + LikelyB = 1, + }; + + enum BranchAddressType { + RelativeBranch = 0, + AbsoluteBranch = 2, + }; + + enum FloatFormat { SingleFloat, DoubleFloat }; + enum FloatTestKind { TestForTrue, TestForFalse }; + + BufferOffset nextOffset() { return m_buffer.nextOffset(); } + + protected: + Instruction* editSrc(BufferOffset bo) { + if (!bo.assigned()) { + // Under OOM, writeInst may return an unassigned BufferOffset. + // Return a dummy writable area so callers (WriteLoad64Instructions) + // can proceed harmlessly; the compilation will be discarded. + static uint32_t oomDummy_[8]; + return (Instruction*)oomDummy_; + } + return m_buffer.getInst(bo); + } + + struct RelativePatch { + BufferOffset offset; + void* target; + RelocationKind kind; + + RelativePatch(BufferOffset offset, void* target, RelocationKind kind) + : offset(offset), target(target), kind(kind) {} + }; + + js::Vector jumps_; + + CompactBufferWriter jumpRelocations_; + CompactBufferWriter dataRelocations_; + + PPCBufferWithExecutableCopy m_buffer; + +#ifdef JS_JITSPEW + Sprinter* printer; +#endif + + public: + // Which absolute bit number does a CR + Condition pair refer to? + static uint8_t crBit(CRegisterID cr, Condition cond) { + return (cr << 2) + ((cond & 0xf0) >> 4); + } + static uint8_t crBit(CRegisterID cr, DoubleCondition cond) { + return (cr << 2) + ((cond & 0xf0) >> 4); + } + + Assembler() + : m_buffer(/* poolMaxOffset */ 8192, /* nopFill */ 0), +#ifdef JS_JITSPEW + printer(nullptr), +#endif + isFinished(false), + scratch_register_list_((1 << Registers::r11) | (1 << Registers::r12)) { + } + + void setUnlimitedBuffer() { m_buffer.setUnlimited(); } + + // Constant pool callbacks required by AssemblerBufferWithConstantPools. + static void InsertIndexIntoTag(uint8_t* load, uint32_t index); + static bool PatchConstantPoolLoad(void* loadAddr, void* constPoolAddr); + static void WritePoolGuard(BufferOffset branch, Instruction* inst, + BufferOffset dest); + static void WritePoolHeader(uint8_t* start, js::jit::Pool* p, bool isNatural); + static void PatchShortRangeBranchToVeneer(PPCBuffer*, unsigned rangeIdx, + BufferOffset deadline, + BufferOffset veneer); + + static Condition InvertCondition(Condition cond); + static DoubleCondition InvertCondition(DoubleCondition cond); + + void writeRelocation(BufferOffset src) { + jumpRelocations_.writeUnsigned(src.getOffset()); + } + + void writeDataRelocation(ImmGCPtr ptr) { + if (ptr.value) { + if (gc::IsInsideNursery(ptr.value)) { + embedsNurseryPointers_ = true; + } + dataRelocations_.writeUnsigned(nextOffset().getOffset()); + } + } + void writeDataRelocation(BufferOffset bo, ImmGCPtr ptr) { + if (ptr.value) { + if (gc::IsInsideNursery(ptr.value)) { + embedsNurseryPointers_ = true; + } + dataRelocations_.writeUnsigned(bo.getOffset()); + } + } + + void assertNoGCThings() const { +#ifdef DEBUG + MOZ_ASSERT(dataRelocations_.length() == 0); + for (auto& j : jumps_) { + MOZ_ASSERT(j.kind == RelocationKind::HARDCODED); + } +#endif + } + + bool oom() const; + + void setPrinter(Sprinter* sp) { +#ifdef JS_JITSPEW + printer = sp; +#endif + } + +#ifdef JS_JITSPEW + inline void spew(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3) { + if (MOZ_UNLIKELY(printer || JitSpewEnabled(JitSpew_Codegen))) { + va_list va; + va_start(va, fmt); + spewVA(fmt, va); + va_end(va); + } + } + MOZ_COLD void spewVA(const char* fmt, va_list va) MOZ_FORMAT_PRINTF(2, 0) { + char buf[200]; + int i = VsprintfLiteral(buf, fmt, va); + if (i > -1) { + if (printer) { + printer->printf("%s\n", buf); + } + js::jit::JitSpew(js::jit::JitSpew_Codegen, "%s", buf); + } + } +#else + MOZ_ALWAYS_INLINE void spew(const char* fmt, ...) MOZ_FORMAT_PRINTF(2, 3) {} +#endif + + Register getStackPointer() const { return StackPointer; } + + protected: + bool isFinished; + + public: + static uintptr_t GetPointer(uint8_t*); + void flush() { + MOZ_ASSERT(!isFinished); + m_buffer.flushPool(); + } + // Inhibit pool flushes for the next maxInst instructions. Mirrors the + // ARM/ARM64 wrappers; lets shared code (e.g. WasmFrameIter epilogues + // that need static byte distances between currentOffset() captures) + // fence a small instruction window without reaching into m_buffer. + void enterNoPool(size_t maxInst) { m_buffer.enterNoPool(maxInst); } + void leaveNoPool() { m_buffer.leaveNoPool(); } + void finish(); + bool appendRawCode(const uint8_t* code, size_t numBytes); + bool reserve(size_t size); + bool swapBuffer(wasm::Bytes& bytes); + void executableCopy(void* buffer); + void copyJumpRelocationTable(uint8_t* dest); + void copyDataRelocationTable(uint8_t* dest); + + size_t size() const; + size_t jumpRelocationTableBytes() const; + size_t dataRelocationTableBytes() const; + size_t bytesNeeded() const; + + BufferOffset writeInst(uint32_t x, uint32_t* dest = nullptr); + static void WriteInstStatic(uint32_t x, uint32_t* dest); + + public: + BufferOffset haltingAlign(int alignment); + BufferOffset nopAlign(int alignment); + BufferOffset as_nop(); + + // --- Instruction emission + + // Branch instructions. + uint16_t computeConditionCode(Condition op, CRegisterID cr = cr0); + uint16_t computeConditionCode(DoubleCondition cond, CRegisterID cr = cr0); + BufferOffset as_b(JOffImm26 off, BranchAddressType bat = RelativeBranch, + LinkBit lb = DontLinkB); + BufferOffset as_b(int32_t off, BranchAddressType bat = RelativeBranch, + LinkBit lb = DontLinkB); + BufferOffset as_blr(LinkBit lb = DontLinkB); + BufferOffset as_bctr(LinkBit lb = DontLinkB); + BufferOffset as_bc(BOffImm16 off, Condition cond, CRegisterID cr = cr0, + LikelyBit lkb = NotLikelyB, LinkBit lb = DontLinkB); + BufferOffset as_bc(int16_t off, Condition cond, CRegisterID cr = cr0, + LikelyBit lkb = NotLikelyB, LinkBit lb = DontLinkB); + BufferOffset as_bc(BOffImm16 off, DoubleCondition cond, CRegisterID cr = cr0, + LikelyBit lkb = NotLikelyB, LinkBit lb = DontLinkB); + BufferOffset as_bc(int16_t off, DoubleCondition cond, CRegisterID cr = cr0, + LikelyBit lkb = NotLikelyB, LinkBit lb = DontLinkB); + BufferOffset as_bcctr(Condition cond, CRegisterID cr = cr0, + LikelyBit lkb = NotLikelyB, LinkBit lb = DontLinkB); + BufferOffset as_bcctr(DoubleCondition cond, CRegisterID cr = cr0, + LikelyBit lkb = NotLikelyB, LinkBit lb = DontLinkB); + BufferOffset as_bc(int16_t off, uint16_t op, LikelyBit lkb = NotLikelyB, + LinkBit lb = DontLinkB); + BufferOffset as_bcctr(uint16_t op, LikelyBit lkb = NotLikelyB, + LinkBit lb = DontLinkB); + + // SPR operations. + BufferOffset as_mtspr(SPRegisterID spr, Register ra); + BufferOffset as_mfspr(Register rd, SPRegisterID spr); + + // CR operations. + BufferOffset as_crandc(uint8_t t, uint8_t a, uint8_t b); + BufferOffset as_cror(uint8_t t, uint8_t a, uint8_t b); + BufferOffset as_crorc(uint8_t t, uint8_t a, uint8_t b); + BufferOffset as_mtcrf(uint32_t mask, Register rs); + BufferOffset as_mfocrf(Register rd, CRegisterID crfs); + BufferOffset as_mcrxrx(CRegisterID crt); + + // Compare instructions. The "d" spellings are kept for source + // compatibility with the 64-bit seed but emit the word form (L=0); a + // pointer-sized compare is a 32-bit compare here. + BufferOffset as_cmpd(CRegisterID cr, Register ra, Register rb); + BufferOffset as_cmpdi(CRegisterID cr, Register ra, int16_t im); + BufferOffset as_cmpld(CRegisterID cr, Register ra, Register rb); + BufferOffset as_cmpldi(CRegisterID cr, Register ra, int16_t im); + BufferOffset as_cmpw(CRegisterID cr, Register ra, Register rb); + BufferOffset as_cmpwi(CRegisterID cr, Register ra, int16_t im); + BufferOffset as_cmplw(CRegisterID cr, Register ra, Register rb); + BufferOffset as_cmplwi(CRegisterID cr, Register ra, int16_t im); + BufferOffset as_cmpd(Register ra, Register rb); + BufferOffset as_cmpdi(Register ra, int16_t im); + BufferOffset as_cmpld(Register ra, Register rb); + BufferOffset as_cmpldi(Register ra, int16_t im); + BufferOffset as_cmpw(Register ra, Register rb); + BufferOffset as_cmpwi(Register ra, int16_t im); + BufferOffset as_cmplw(Register ra, Register rb); + BufferOffset as_cmplwi(Register ra, int16_t im); + + // ALU (three-register). + BufferOffset as_add(Register rd, Register ra, Register rb); + BufferOffset as_addc(Register rd, Register ra, Register rb); + BufferOffset as_adde(Register rd, Register ra, Register rb); + BufferOffset as_subf(Register rd, Register ra, Register rb); + BufferOffset as_subfc(Register rd, Register ra, Register rb); + BufferOffset as_subfe(Register rd, Register ra, Register rb); + BufferOffset as_neg(Register rd, Register rs); + + // NOT IMPLEMENTED BY THIS TARGET. The emitters below are retained only + // because js/src/jit/ppc/MacroAssembler-ppc*.cpp still names them; each + // one needs a 32-bit register-pair (or helper-call) replacement there, + // after which the emitter should be deleted. + BufferOffset as_mulld(Register rd, Register ra, Register rb); + BufferOffset as_mulhd(Register rd, Register ra, Register rb); + BufferOffset as_mulhdu(Register rd, Register ra, Register rb); + BufferOffset as_mulldo(Register rd, Register ra, Register rb); + BufferOffset as_divd(Register rd, Register ra, Register rb); + BufferOffset as_divdu(Register rd, Register ra, Register rb); + + BufferOffset as_mullw(Register rd, Register ra, Register rb); + BufferOffset as_mulhwu(Register rd, Register ra, Register rb); + BufferOffset as_divw(Register rd, Register ra, Register rb); + BufferOffset as_divwu(Register rd, Register ra, Register rb); + // POWER9 modulo — NOT IMPLEMENTED BY THIS TARGET (see note above). + BufferOffset as_modsd(Register rd, Register ra, Register rb); + BufferOffset as_modsw(Register rd, Register ra, Register rb); + BufferOffset as_modud(Register rd, Register ra, Register rb); + BufferOffset as_moduw(Register rd, Register ra, Register rb); + + // ALU immediate. + BufferOffset as_addi(Register rd, Register ra, int16_t im, + bool actually_li = false); + BufferOffset as_addis(Register rd, Register ra, int16_t im, + bool actually_lis = false); + BufferOffset as_mulli(Register rd, Register ra, int16_t im); + BufferOffset as_subfic(Register rd, Register ra, int16_t im); + + // ALU unary/extended. + BufferOffset as_cntlzw(Register rd, Register ra); + // NOT IMPLEMENTED BY THIS TARGET: cntlzd is 64-bit, cnttz*/popcnt* are + // POWER9/POWER8, br{d,h,w} are POWER10. + BufferOffset as_cntlzd(Register rd, Register ra); + BufferOffset as_cnttzd(Register rd, Register ra); + BufferOffset as_cnttzw(Register rd, Register ra); + BufferOffset as_popcntd(Register ra, Register rs); + BufferOffset as_popcntw(Register ra, Register rs); + BufferOffset as_brd(Register ra, Register rs); + BufferOffset as_brh(Register ra, Register rs); + BufferOffset as_brw(Register ra, Register rs); + + // Bit operations (logical, three-register). + BufferOffset as_and_(Register rd, Register rs, Register rb); + BufferOffset as_and__rc(Register rd, Register rs, Register rb); + BufferOffset as_nor(Register rd, Register rs, Register rb); + BufferOffset as_or_(Register rd, Register rs, Register rb); + BufferOffset as_xor_(Register rd, Register rs, Register rb); + BufferOffset as_slw(Register rd, Register rs, Register rb); + BufferOffset as_srw(Register rd, Register rs, Register rb); + BufferOffset as_sraw(Register rd, Register rs, Register rb); + // NOT IMPLEMENTED BY THIS TARGET (64-bit shifts). + BufferOffset as_sld(Register rd, Register rs, Register rb); + BufferOffset as_srd(Register rd, Register rs, Register rb); + BufferOffset as_srad(Register rd, Register rs, Register rb); + + // Bit operations (logical, immediate). + BufferOffset as_ori(Register rd, Register ra, uint16_t im); + BufferOffset as_oris(Register rd, Register ra, uint16_t im); + BufferOffset as_xori(Register rd, Register ra, uint16_t im); + BufferOffset as_xoris(Register rd, Register ra, uint16_t im); + BufferOffset as_andi_rc(Register rd, Register ra, uint16_t im); + + // Sign extension. + BufferOffset as_extsb(Register rd, Register rs); + BufferOffset as_extsh(Register rd, Register rs); + // NOT IMPLEMENTED BY THIS TARGET (extsw is 64-bit; a 32-bit register + // already holds its own sign). + BufferOffset as_extsw(Register rd, Register rs); + BufferOffset as_extsw_rc(Register rd, Register rs); + + // Shift/rotate with immediates. + BufferOffset as_srawi(Register id, Register rs, uint8_t n); + // NOT IMPLEMENTED BY THIS TARGET (MD/MDS-form 64-bit rotates). + BufferOffset as_sradi(Register rd, Register rs, int n); + BufferOffset as_rldcl(Register ra, Register rs, Register rb, uint8_t mb); + BufferOffset as_rldicl(Register ra, Register rs, uint8_t sh, uint8_t mb); + BufferOffset as_rldicl_rc(Register ra, Register rs, uint8_t sh, uint8_t mb); + BufferOffset as_rldicr(Register ra, Register rs, uint8_t sh, uint8_t mb); + BufferOffset as_rldicr_rc(Register ra, Register rs, uint8_t sh, uint8_t mb); + BufferOffset as_rldimi(Register rd, Register rs, uint8_t sh, uint8_t mb); + + BufferOffset as_rlwinm(Register rd, Register rs, uint8_t sh, uint8_t mb, + uint8_t me); + BufferOffset as_rlwinm_rc(Register rd, Register rs, uint8_t sh, uint8_t mb, + uint8_t me); + BufferOffset as_rlwimi(Register rd, Register rs, uint8_t sh, uint8_t mb, + uint8_t me); + BufferOffset as_rlwnm(Register rd, Register rs, Register rb, uint8_t mb, + uint8_t me); + + // Integer loads (D-form). + BufferOffset as_lbz(Register rd, Register rb, int16_t off); + BufferOffset as_lha(Register rd, Register rb, int16_t off); + BufferOffset as_lhz(Register rd, Register rb, int16_t off); + BufferOffset as_lwz(Register rd, Register rb, int16_t off); + // NOT IMPLEMENTED BY THIS TARGET (DS-form 64-bit load). + BufferOffset as_lwa(Register rd, Register rb, int16_t off); + BufferOffset as_ld(Register rd, Register rb, int16_t off); + + // Integer stores (D-form). + BufferOffset as_stb(Register rd, Register rb, int16_t off); + BufferOffset as_sth(Register rd, Register rb, int16_t off); + BufferOffset as_stw(Register rd, Register rb, int16_t off); + // stwu stores rd before updating rb, so pushing the stack pointer itself + // writes the value it had before the decrement. + BufferOffset as_stwu(Register rd, Register rb, int16_t off); + // NOT IMPLEMENTED BY THIS TARGET (DS-form 64-bit store). + BufferOffset as_std(Register rd, Register rb, int16_t off); + BufferOffset as_stdu(Register rd, Register rb, int16_t off); + + // Integer loads/stores (X-form, indexed). + BufferOffset as_lbzx(Register rd, Register ra, Register rb); + BufferOffset as_lhax(Register rd, Register ra, Register rb); + BufferOffset as_lhzx(Register rd, Register ra, Register rb); + BufferOffset as_lwzx(Register rd, Register ra, Register rb); + BufferOffset as_lwarx(Register rd, Register ra, Register rb); + BufferOffset as_stbx(Register rd, Register ra, Register rb); + BufferOffset as_stwx(Register rd, Register ra, Register rb); + BufferOffset as_stwbrx(Register rd, Register ra, Register rb); + BufferOffset as_lhbrx(Register rd, Register ra, Register rb); + BufferOffset as_lwbrx(Register rd, Register ra, Register rb); + BufferOffset as_sthbrx(Register rd, Register ra, Register rb); + BufferOffset as_sthx(Register rd, Register ra, Register rb); + BufferOffset as_stwcx(Register rd, Register ra, Register rb); + // NOT IMPLEMENTED BY THIS TARGET: the 64-bit indexed forms, the POWER8 + // byte/halfword reservations, and the POWER7 byte-reversed doubleword + // accesses. + BufferOffset as_lwax(Register rd, Register ra, Register rb); + BufferOffset as_lbarx(Register rd, Register ra, Register rb); + BufferOffset as_lharx(Register rd, Register ra, Register rb); + BufferOffset as_ldx(Register rd, Register ra, Register rb); + BufferOffset as_ldarx(Register rd, Register ra, Register rb); + BufferOffset as_ldbrx(Register rd, Register ra, Register rb); + BufferOffset as_stdbrx(Register rd, Register ra, Register rb); + BufferOffset as_stbcx(Register rd, Register ra, Register rb); + BufferOffset as_sthcx(Register rd, Register ra, Register rb); + BufferOffset as_stdx(Register rd, Register ra, Register rb); + BufferOffset as_stdcx(Register rd, Register ra, Register rb); + + // Integer select. NOT IMPLEMENTED BY THIS TARGET: isel is POWER6+ and + // setbc/setbcr are POWER10; use a branch or the subfe/and/or mask trick. + BufferOffset as_setbc(Register rt, uint16_t bc, CRegisterID cr); + BufferOffset as_setbcr(Register rt, uint16_t bc, CRegisterID cr); + BufferOffset as_isel(Register rt, Register ra, Register rb, uint16_t rc, + CRegisterID cr = cr0); + BufferOffset as_isel0(Register rt, Register ra, Register rb, uint16_t rc, + CRegisterID cr = cr0); + + // FP compare. + BufferOffset as_fcmpu(CRegisterID cr, FloatRegister ra, FloatRegister rb); + BufferOffset as_fcmpu(FloatRegister ra, FloatRegister rb); + + // FP arithmetic (two-source). + BufferOffset as_fadd(FloatRegister rd, FloatRegister ra, FloatRegister rc); + BufferOffset as_fadds(FloatRegister rd, FloatRegister ra, FloatRegister rc); + BufferOffset as_fsub(FloatRegister rd, FloatRegister ra, FloatRegister rc); + BufferOffset as_fsubs(FloatRegister rd, FloatRegister ra, FloatRegister rc); + BufferOffset as_fdiv(FloatRegister rd, FloatRegister ra, FloatRegister rc); + BufferOffset as_fdivs(FloatRegister rd, FloatRegister ra, FloatRegister rc); + BufferOffset as_fmul(FloatRegister rd, FloatRegister ra, FloatRegister rc); + BufferOffset as_fmuls(FloatRegister rd, FloatRegister ra, FloatRegister rc); + BufferOffset as_fcpsgn(FloatRegister rd, FloatRegister ra, FloatRegister rc); + // FP unary. + BufferOffset as_fabs(FloatRegister rd, FloatRegister rs); + BufferOffset as_fneg(FloatRegister rd, FloatRegister rs); + BufferOffset as_fmr(FloatRegister rd, FloatRegister rs); + BufferOffset as_frsp(FloatRegister rd, FloatRegister rs); + // NOT IMPLEMENTED BY THIS TARGET: the 603 has no fsqrt/fsqrts. Call out + // to sqrt() or use a Newton-Raphson refinement. + BufferOffset as_fsqrt(FloatRegister rd, FloatRegister rs); + BufferOffset as_fsqrts(FloatRegister rd, FloatRegister rs); + + // FP conversions. + BufferOffset as_fctiw(FloatRegister rd, FloatRegister rs); + BufferOffset as_fctiwz(FloatRegister rd, FloatRegister rs); + // NOT IMPLEMENTED BY THIS TARGET: the float <-> int64 conversions are + // 64-bit instructions. Use fctiwz plus software for the 64-bit cases. + BufferOffset as_fcfid(FloatRegister rd, FloatRegister rs); + BufferOffset as_fcfids(FloatRegister rd, FloatRegister rs); + BufferOffset as_fcfidu(FloatRegister rd, FloatRegister rs); + BufferOffset as_fcfidus(FloatRegister rd, FloatRegister rs); + BufferOffset as_fctid(FloatRegister rd, FloatRegister rs); + BufferOffset as_fctidz(FloatRegister rd, FloatRegister rs); + BufferOffset as_fctiduz(FloatRegister rd, FloatRegister rs); + + // FP rounding. NOT IMPLEMENTED BY THIS TARGET (POWER5+); see + // HasRoundInstruction, which reports false. + BufferOffset as_frim(FloatRegister rd, FloatRegister rs); + BufferOffset as_frip(FloatRegister rd, FloatRegister rs); + BufferOffset as_friz(FloatRegister rd, FloatRegister rs); + + // FP loads (D-form). + BufferOffset as_lfd(FloatRegister rd, Register rb, int16_t off); + BufferOffset as_lfs(FloatRegister rd, Register rb, int16_t off); + + // FP stores (D-form). + BufferOffset as_stfd(FloatRegister rd, Register rb, int16_t off); + BufferOffset as_stfs(FloatRegister rd, Register rb, int16_t off); + BufferOffset as_stfdu(FloatRegister rd, Register rb, int16_t off); + BufferOffset as_stfsu(FloatRegister rd, Register rb, int16_t off); + + // FP loads/stores (X-form, indexed). + BufferOffset as_lfdx(FloatRegister rd, Register ra, Register rb); + BufferOffset as_lfsx(FloatRegister rd, Register ra, Register rb); + BufferOffset as_stfdx(FloatRegister rd, Register ra, Register rb); + BufferOffset as_stfsx(FloatRegister rd, Register ra, Register rb); + // NOT IMPLEMENTED BY THIS TARGET (ISA 2.05, part of the int64 <-> FP path). + BufferOffset as_lfiwax(FloatRegister rd, Register ra, Register rb); + + // FPSCR operations. + BufferOffset as_mtfsb0(uint8_t bt); + BufferOffset as_mtfsb1(uint8_t bt); + BufferOffset as_mcrfs(CRegisterID bf, uint8_t bfa); + + // VMX SIMD load/store (X-form, indexed). VR0-VR31 are a register file of + // their own; a Simd128 FloatRegister names one of them and the emitters + // mask its encoding down to the 5-bit VR field. The uint8_t overloads take + // a raw VR number, which the trampoline needs for the callee-saved + // VR20-VR31 spills. lvx/stvx force the effective address to a 16-byte + // boundary, so the caller must align the slot; lvsl/lvsr produce the vperm + // control vector for reassembling an unaligned quadword. + BufferOffset as_lvx(uint8_t vrt, Register ra, Register rb); + BufferOffset as_stvx(uint8_t vrs, Register ra, Register rb); + BufferOffset as_lvx(FloatRegister vrt, Register ra, Register rb); + BufferOffset as_stvx(FloatRegister vrs, Register ra, Register rb); + BufferOffset as_lvsl(FloatRegister vrt, Register ra, Register rb); + BufferOffset as_lvsr(FloatRegister vrt, Register ra, Register rb); + + // VSCR access. + BufferOffset as_mfvscr(FloatRegister vrt); + BufferOffset as_mtvscr(FloatRegister vrb); + + // VMX bitwise operations (VX-form) and the VA-form bitwise select. + BufferOffset as_vand(FloatRegister vrt, FloatRegister vra, FloatRegister vrb); + BufferOffset as_vandc(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + BufferOffset as_vor(FloatRegister vrt, FloatRegister vra, FloatRegister vrb); + BufferOffset as_vxor(FloatRegister vrt, FloatRegister vra, FloatRegister vrb); + BufferOffset as_vnor(FloatRegister vrt, FloatRegister vra, FloatRegister vrb); + BufferOffset as_vsel(FloatRegister vrt, FloatRegister vra, FloatRegister vrb, + FloatRegister vrc); + + // Whole-vector (not per-lane) bit shift; the shift amount is replicated in + // every byte of vrb. + BufferOffset as_vsl(FloatRegister vrt, FloatRegister vra, FloatRegister vrb); + BufferOffset as_vsr(FloatRegister vrt, FloatRegister vra, FloatRegister vrb); + + // VMX single-precision float arithmetic. There is no double-precision + // vector arithmetic on VMX: f64x2 must be lowered to two scalar FPR ops. + BufferOffset as_vaddfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + BufferOffset as_vsubfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + BufferOffset as_vmaxfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + BufferOffset as_vminfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + // vrt = vra * vrc + vrb (VA-form). + BufferOffset as_vmaddfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrc, FloatRegister vrb); + // vrt = -(vra * vrc - vrb) (VA-form). + BufferOffset as_vnmsubfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrc, FloatRegister vrb); + // Estimates; VMX has neither a vector divide nor a vector square root. + BufferOffset as_vrefp(FloatRegister vrt, FloatRegister vrb); + BufferOffset as_vrsqrtefp(FloatRegister vrt, FloatRegister vrb); + BufferOffset as_vexptefp(FloatRegister vrt, FloatRegister vrb); + BufferOffset as_vlogefp(FloatRegister vrt, FloatRegister vrb); + // Round to nearest / toward zero / up / down. + BufferOffset as_vrfin(FloatRegister vrt, FloatRegister vrb); + BufferOffset as_vrfiz(FloatRegister vrt, FloatRegister vrb); + BufferOffset as_vrfip(FloatRegister vrt, FloatRegister vrb); + BufferOffset as_vrfim(FloatRegister vrt, FloatRegister vrb); + // Int <-> float with a 5-bit power-of-two scale (uim = 0 for a plain + // conversion). + BufferOffset as_vcfsx(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + BufferOffset as_vcfux(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + BufferOffset as_vctsxs(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + BufferOffset as_vctuxs(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + // Float compares; the record forms set CR6 (LT = all true, EQ = none true). + BufferOffset as_vcmpeqfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + BufferOffset as_vcmpgefp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + BufferOffset as_vcmpgtfp(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + BufferOffset as_vcmpeqfp_rc(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + BufferOffset as_vcmpgefp_rc(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + BufferOffset as_vcmpgtfp_rc(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb); + + // Word splat and truncating (modulo) pack. + BufferOffset as_vspltw(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + BufferOffset as_vpkuhum(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vpkuwum(uint8_t vrt, uint8_t vra, uint8_t vrb); + + // VMX integer arithmetic (raw VR numbers 0-31). + // Callers must ensure operands are in VR space. + BufferOffset as_vaddubm(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vadduhm(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vadduwm(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vaddudm(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsububm(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsubuhm(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsubuwm(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsubudm(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vaddsbs(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vaddshs(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vaddubs(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vadduhs(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsubsbs(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsubshs(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsububs(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsubuhs(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vminsb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vminsh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vminsw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmaxsb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmaxsh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmaxsw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmaxsd(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vminub(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vminuh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vminuw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmaxub(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmaxuh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmaxuw(uint8_t vrt, uint8_t vra, uint8_t vrb); + // POWER9 (ISA 3.0): per-lane integer negate. + BufferOffset as_vnegw(uint8_t vrt, uint8_t vrb); + BufferOffset as_vnegd(uint8_t vrt, uint8_t vrb); + // POWER10 (ISA 3.1) prefixed instructions. Each emits 8 bytes (prefix + + // suffix) with a single nop inserted before iff the prefix would + // straddle a 64-byte block. Caller must guarantee HasPOWER10(). + // imm34 is signed 34-bit; R=true selects PC-relative form (RA must be r0). + // Returns the offset of the prefix word. + BufferOffset as_paddi(Register rt, Register ra, int64_t imm34, bool R); + BufferOffset as_pld(Register rt, Register ra, int64_t imm34, bool R); + // FP-target prefixed loads: plfd/plfs are MLS (Type=2) with suffix + // opcodes 50 and 48. plfs widens single → double in the FPR + // (matches non-prefixed lfs semantics). + BufferOffset as_plfd(FloatRegister frt, Register ra, int64_t imm34, + bool R); + BufferOffset as_plfs(FloatRegister frt, Register ra, int64_t imm34, + bool R); + // Prefixed-store counterparts. Same prefix shape; suffix opcodes are + // the D-form variants of std/stfd/stfs (61, 54, 52). + BufferOffset as_pstd(Register rs, Register ra, int64_t imm34, bool R); + BufferOffset as_pstfd(FloatRegister frs, Register ra, int64_t imm34, + bool R); + BufferOffset as_pstfs(FloatRegister frs, Register ra, int64_t imm34, + bool R); + + private: + // Emit a nop before a prefixed instruction iff the prefix would otherwise + // start at offset 12 (mod 16), which would split the prefix and suffix + // across a 16-byte boundary (a POWER10 alignment requirement). + void ensurePrefixedAlignment(); + + // Emit an already-encoded prefix+suffix word pair as a single, unsplittable + // prefixed instruction: reserve the pool, align, write both words, and + // return the offset of the prefix. + BufferOffset emitPrefixedInst(uint32_t prefix, uint32_t suffix); + + public: + BufferOffset as_vavgub(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vavguh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmuluwm(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmulld(uint8_t vrt, uint8_t vra, uint8_t vrb); + // VMX shift (VR0-31 only). + BufferOffset as_vslb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vslh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vslw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsld(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsrb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsrh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsrw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsrd(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsrab(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsrah(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsraw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsrad(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vslo(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vsro(uint8_t vrt, uint8_t vra, uint8_t vrb); + + // VMX integer compare (VR0-31 only). + BufferOffset as_vcmpequb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpequh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpequw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpequd(uint8_t vrt, uint8_t vra, uint8_t vrb); + // Record forms set CR6: LT = all-true, EQ = none-true. + BufferOffset as_vcmpequb_rc(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpequh_rc(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpequw_rc(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpequd_rc(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpgtsb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpgtsh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpgtsw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpgtsd(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpgtub(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpgtuh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpgtuw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpgtud(uint8_t vrt, uint8_t vra, uint8_t vrb); + // POWER9 (ISA 3.0). NotEqual compare; no doubleword variant. + BufferOffset as_vcmpneb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpneh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vcmpnew(uint8_t vrt, uint8_t vra, uint8_t vrb); + + + // VMX widen/narrow/merge/pack (VR0-31 only). + BufferOffset as_vupkhsb(uint8_t vrt, uint8_t vrb); + BufferOffset as_vupklsb(uint8_t vrt, uint8_t vrb); + BufferOffset as_vupkhsh(uint8_t vrt, uint8_t vrb); + BufferOffset as_vupklsh(uint8_t vrt, uint8_t vrb); + BufferOffset as_vupkhsw(uint8_t vrt, uint8_t vrb); + BufferOffset as_vupklsw(uint8_t vrt, uint8_t vrb); + BufferOffset as_vpkshss(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vpkswss(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vpkshus(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vpkswus(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmrghb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmrghh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmrghw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmrglb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmrglh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmrglw(uint8_t vrt, uint8_t vra, uint8_t vrb); + + // VMX extended multiply (VR0-31 only). + BufferOffset as_vmulesb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmulosb(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmuleub(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmuloub(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmulesh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmulosh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmuleuh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmulouh(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmulesw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmulosw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmuleuw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vmulouw(uint8_t vrt, uint8_t vra, uint8_t vrb); + BufferOffset as_vpopcntb(uint8_t vrt, uint8_t vrb); + BufferOffset as_vperm(uint8_t vrt, uint8_t vra, uint8_t vrb, uint8_t vrc); + // Everything from here to the end of the VMX section post-dates the + // original AltiVec definition (POWER8/POWER9/POWER10 additions) and is + // NOT IMPLEMENTED BY THIS TARGET; the emitters are retained only until + // their MacroAssembler call sites are rewritten. + // POWER8+ (ISA 2.07). VX-form bit-permute. See PPC_vbpermq comment. + BufferOffset as_vbpermq(uint8_t vrt, uint8_t vra, uint8_t vrb); + // POWER10 (ISA 3.1) Vector Extract Mask. RT is a GPR. + BufferOffset as_vextractbm(Register rt, FloatRegister vrb); + BufferOffset as_vextracthm(Register rt, FloatRegister vrb); + BufferOffset as_vextractwm(Register rt, FloatRegister vrb); + BufferOffset as_vextractdm(Register rt, FloatRegister vrb); + // POWER10 (ISA 3.1) Vector Insert from GPR at immediate byte offset. + // UIM range: vinsw 0..12, vinsd 0..8 (caller must enforce). + BufferOffset as_vinsw(FloatRegister vrt, Register rb, uint8_t uim); + BufferOffset as_vinsd(FloatRegister vrt, Register rb, uint8_t uim); + // POWER10 (ISA 3.1) Vector Insert byte / halfword from GPR with the + // byte position supplied by another GPR (RA & 0xF for vinsbrx, + // RA & 0xE for vinshrx). "rx" = right-indexed = LE-natural. + BufferOffset as_vinsbrx(FloatRegister vrt, Register ra, Register rb); + BufferOffset as_vinshrx(FloatRegister vrt, Register ra, Register rb); + // POWER9 (ISA 3.0) Vector Insert byte / halfword from VR at immediate + // byte position. UIM range: vinsertb 0..15, vinserth 0..14 + // (caller must enforce; vinserth UIM is in bytes, even-aligned). + BufferOffset as_vinsertb(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + BufferOffset as_vinserth(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + // POWER9 (ISA 3.0) Vector Extract byte / halfword from VR at immediate + // BE byte position. UIM range: vextractub 0..15, vextractuh 0..14 + // (caller must enforce; vextractuh UIM is in bytes, even-aligned). The + // extracted byte/halfword lands at BE byte 7 of VRT with the rest zeroed. + BufferOffset as_vextractub(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + BufferOffset as_vextractuh(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + // VX-form with 5-bit signed immediate splat: each lane of VRT is + // set to sign_extend(SIMM5) (range [-16, 15]) at byte/halfword/word granularity. + BufferOffset as_vspltisb(uint8_t vrt, int8_t simm5); + BufferOffset as_vspltish(uint8_t vrt, int8_t simm5); + BufferOffset as_vspltisw(uint8_t vrt, int8_t simm5); + + // VA-form ternary VMX instructions. + BufferOffset as_vmladduhm(uint8_t vrt, uint8_t vra, uint8_t vrb, uint8_t vrc); + BufferOffset as_vmhraddshs(uint8_t vrt, uint8_t vra, uint8_t vrb, + uint8_t vrc); + BufferOffset as_vmsumshm(uint8_t vrt, uint8_t vra, uint8_t vrb, uint8_t vrc); + BufferOffset as_vmsumuhm(uint8_t vrt, uint8_t vra, uint8_t vrb, uint8_t vrc); + + // VMX vector operations. + BufferOffset as_vspltb(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + BufferOffset as_vsplth(FloatRegister vrt, FloatRegister vrb, uint8_t uim); + BufferOffset as_vsldoi(FloatRegister vrt, FloatRegister vra, + FloatRegister vrb, uint8_t shb); + BufferOffset as_vperm(FloatRegister vrt, FloatRegister vra, FloatRegister vrb, + FloatRegister vrc); + + // Barrier and sync instructions. + BufferOffset as_lwsync(); + BufferOffset as_sync(); + BufferOffset as_isync(); + + // Convenience pseudo-instructions. + BufferOffset xs_trap(); + BufferOffset xs_trap_tagged(TrapTag tag); + BufferOffset xs_mr(Register rd, Register ra); + BufferOffset xs_mtctr(Register ra); + BufferOffset xs_mtlr(Register ra); + BufferOffset xs_mflr(Register rd); + BufferOffset xs_mfxer(Register ra); + BufferOffset xs_li(Register rd, int16_t im); + BufferOffset xs_lis(Register rd, int16_t im); + BufferOffset x_subi(Register rd, Register ra, int16_t im); + BufferOffset x_not(Register rd, Register ra); + BufferOffset x_slwi(Register rd, Register rs, int n); + BufferOffset x_sldi(Register rd, Register rs, int n); + BufferOffset x_srwi(Register rd, Register rs, int n); + BufferOffset x_srdi(Register rd, Register rs, int n); + + // --- Label operations. + void bind(Label* label) { bind(label, nextOffset()); } + void bind(Label* label, BufferOffset boff); + void bind(InstImm* inst, uintptr_t branch, uintptr_t target); + void bind(CodeLabel* label) { label->target()->bind(currentOffset()); } + uint32_t currentOffset() { return nextOffset().getOffset(); } + void retarget(Label* label, Label* target); + void call(Label* label); + void call(void* target); + + // --- Static capability queries. + static bool SupportsFloatingPoint() { return true; } + // The wasm SIMD (v128) lowering is backed by AltiVec/VMX, which the 603 + // baseline does not have. + static bool SupportsWasmSimd() { return HasVMX(); } + static bool SupportsUnalignedAccesses() { return true; } + static bool SupportsFastUnalignedFPAccesses() { return true; } + // FP16 conversion is a VSX (POWER9) feature; there is no hardware for it. + static bool SupportsFloat64To16() { return false; } + static bool SupportsFloat32To16() { return false; } + static bool HasRoundInstruction(RoundingMode mode) { + // frin/friz/frip/frim are POWER5+; this target has none of them, so + // rounding is always done in software. + return false; + } + + protected: + InstImm invertBranch(InstImm branch, BOffImm16 skipOffset); + void addPendingJump(BufferOffset src, ImmPtr target, RelocationKind kind) { + enoughMemory_ &= jumps_.append(RelativePatch(src, target.value, kind)); + if (kind == RelocationKind::JITCODE) { + writeRelocation(src); + } + } + void addLongJump(BufferOffset src, BufferOffset dst) { + CodeLabel cl; + cl.patchAt()->bind(src.getOffset()); + cl.target()->bind(dst.getOffset()); + cl.setLinkMode(CodeLabel::JumpImmediate); + addCodeLabel(std::move(cl)); + } + + public: + void flushBuffer() { m_buffer.flushPool(); } + void comment(const char* msg) { spew("; %s", msg); } + static uint32_t NopSize() { return 4; } + + // --- Static patching API. + // A patchable pointer materialisation is PointerImmInstructions words: + // [0] lis rD, hi16 + // [1] ori rD, rD, lo16 + static bool IsLoadPtrStanza(const Instruction* inst0); + static uintptr_t ExtractLoadPtrValue(Instruction* inst0); + static void UpdateLoadPtrValue(Instruction* inst0, uintptr_t value); + static void WriteLoadPtrInstructions(Instruction* inst0, Register reg, + uintptr_t value); + + static void PatchWrite_Imm32(CodeLocationLabel label, Imm32 imm); + static uint8_t* NextInstruction(uint8_t* instruction, + uint32_t* count = nullptr); + static void ToggleToJmp(CodeLocationLabel inst_); + static void ToggleToCmp(CodeLocationLabel inst_); + + void verifyHeapAccessDisassembly(uint32_t begin, uint32_t end, + const Disassembler::HeapAccess& ha) {} + + // --- Public patching API (required by shared code). + static void Bind(uint8_t* rawCode, const CodeLabel& label); + void processCodeLabels(uint8_t* rawCode); + + static void TraceJumpRelocations(JSTracer* trc, JitCode* code, + CompactBufferReader& reader); + static void TraceDataRelocations(JSTracer* trc, JitCode* code, + CompactBufferReader& reader); + + void executableCopy(uint8_t* buffer); + + static uint32_t PatchWrite_NearCallSize(); + static void PatchWrite_NearCall(CodeLocationLabel start, + CodeLocationLabel toCall); + static void PatchDataWithValueCheck(CodeLocationLabel label, ImmPtr newValue, + ImmPtr expectedValue); + static void PatchDataWithValueCheck(CodeLocationLabel label, + PatchedImmPtr newValue, + PatchedImmPtr expectedValue); + static void ToggleCall(CodeLocationLabel inst_, bool enabled); + + private: + GeneralRegisterSet scratch_register_list_; + + public: + GeneralRegisterSet* GetScratchRegisterList() { + return &scratch_register_list_; + } +}; // Assembler + +inline bool IsUnaligned(const wasm::MemoryAccessDesc& access) { + if (!access.align()) { + return false; + } + return access.align() < access.byteSize(); +} + +} // namespace jit +} // namespace js + +// Whether an Imm32 fits in an unsigned 16-bit immediate. +#define PPC_IMM_OK_U(x) (MOZ_LIKELY(((x).value & 0xffff0000) == 0)) + +// Whether an Imm32 fits in a signed 16-bit immediate. +#define PPC_IMM_OK_S(x) \ + (MOZ_LIKELY(((x).value & 0xffff8000) == 0 || \ + ((x).value & 0xffff8000) == 0xffff8000)) + +// Whether the offset part of an Address fits in a signed 16-bit immediate. +#define PPC_OFFS_OK(x) \ + (MOZ_LIKELY(((x).offset & 0xffff8000) == 0 || \ + ((x).offset & 0xffff8000) == 0xffff8000)) + +// Same test but checking a bit ahead (for paired loads). +#define PPC_OFFS_INCR_OK(x, incr) \ + (MOZ_LIKELY((((x).offset + (incr)) & 0xffff8000) == 0 || \ + (((x).offset + (incr)) & 0xffff8000) == 0xffff8000)) + +#endif /* jit_ppc_Assembler_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/CodeGenerator-ppc.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/CodeGenerator-ppc.cpp --- a/js/src/jit/ppc/CodeGenerator-ppc.cpp 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/CodeGenerator-ppc.cpp 2026-07-29 08:23:47.131824696 +0200 @@ -0,0 +1,3506 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/ppc/CodeGenerator-ppc.h" + +#include "mozilla/MathAlgorithms.h" + +#include + +#include "builtin/Number.h" +#include "jit/CodeGenerator.h" +#include "jit/InlineScriptTree.h" +#include "jit/JitRuntime.h" +#include "jit/MIR-wasm.h" +#include "jit/MIR.h" +#include "jit/MIRGraph.h" +#include "vm/JSContext.h" +#include "vm/Realm.h" +#include "vm/Shape.h" + +#include "jit/shared/CodeGenerator-shared-inl.h" +#include "vm/JSScript-inl.h" + +using namespace js; +using namespace js::jit; + +using JS::GenericNaN; +using mozilla::NegativeInfinity; + +namespace js { +namespace jit { + +CodeGeneratorPPC::CodeGeneratorPPC(MIRGenerator* gen, LIRGraph* graph, + MacroAssembler* masm, + const wasm::CodeMetadata* codeMeta) + : CodeGeneratorShared(gen, graph, masm, codeMeta) {} + +MoveOperand CodeGeneratorPPC::toMoveOperand(LAllocation a) const { + if (a.isGeneralReg()) { + return MoveOperand(ToRegister(a)); + } + if (a.isFloatReg()) { + return MoveOperand(ToFloatRegister(a)); + } + MoveOperand::Kind kind = a.isStackArea() ? MoveOperand::Kind::EffectiveAddress + : MoveOperand::Kind::Memory; + Address address = ToAddress(a); + MOZ_ASSERT((address.offset & 3) == 0); + return MoveOperand(address, kind); +} + +void CodeGeneratorPPC::bailoutFrom(Label* label, LSnapshot* snapshot) { + MOZ_ASSERT_IF(!masm.oom(), label->used()); + MOZ_ASSERT_IF(!masm.oom(), !label->bound()); + + encode(snapshot); + + InlineScriptTree* tree = snapshot->mir()->block()->trackedTree(); + auto* ool = new (alloc()) LambdaOutOfLineCode([=, this](OutOfLineCode& ool) { + // Push snapshotOffset: exactly one machine word, matching the trailing + // snapshotOffset_ field of BailoutStack. + masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer); + masm.storePtr(ImmWord(snapshot->snapshotOffset()), + Address(StackPointer, 0)); + masm.jump(&deoptLabel_); + }); + addOutOfLineCode(ool, + new (alloc()) BytecodeSite(tree, tree->script()->code())); + + masm.retarget(label, ool->entry()); +} + +void CodeGeneratorPPC::bailout(LSnapshot* snapshot) { + Label label; + masm.jump(&label); + bailoutFrom(&label, snapshot); +} + +void CodeGeneratorPPC::bailoutIfFalseBool(Register lhs, LSnapshot* snapshot) { + Label bail; + masm.branchTest32(Assembler::Zero, lhs, Imm32(0xFF), &bail); + bailoutFrom(&bail, snapshot); +} + +bool CodeGeneratorPPC::generateOutOfLineCode() { + if (!CodeGeneratorShared::generateOutOfLineCode()) { + return false; + } + + if (deoptLabel_.used()) { + masm.bind(&deoptLabel_); + + // Frame size is stored in LR and pushed by GenerateBailoutThunk + // (via PushBailoutFrame -> pushReturnAddress -> mflr). + { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.movePtr(ImmWord(frameSize()), scratch); + masm.xs_mtlr(scratch); + } + + TrampolinePtr handler = gen->jitRuntime()->getGenericBailoutHandler(); + masm.jump(handler); + } + + return !masm.oom(); +} + +void CodeGeneratorPPC::branchToBlock(MBasicBlock* block) { + Label* label = skipTrivialBlocks(block)->lir()->label(); + masm.jump(label); +} + +void CodeGeneratorPPC::branchToBlock(Assembler::DoubleCondition cond, + FloatRegister lhs, FloatRegister rhs, + MBasicBlock* mir) { + Label* label = skipTrivialBlocks(mir)->lir()->label(); + masm.branchDouble(cond, lhs, rhs, label); +} + +void CodeGeneratorPPC::branchToBlock(Assembler::FloatFormat fmt, + Assembler::DoubleCondition cond, + FloatRegister lhs, FloatRegister rhs, + MBasicBlock* mir) { + Label* label = skipTrivialBlocks(mir)->lir()->label(); + if (fmt == Assembler::DoubleFloat) { + masm.branchDouble(cond, lhs, rhs, label); + } else { + masm.branchFloat(cond, lhs, rhs, label); + } +} + +class OutOfLineTableSwitch : public OutOfLineCodeBase { + MTableSwitch* mir_; + CodeLabel jumpLabel_; + + void accept(CodeGeneratorPPC* codegen) { + codegen->visitOutOfLineTableSwitch(this); + } + + public: + explicit OutOfLineTableSwitch(MTableSwitch* mir) : mir_(mir) {} + + MTableSwitch* mir() const { return mir_; } + CodeLabel* jumpLabel() { return &jumpLabel_; } +}; + +void CodeGeneratorPPC::emitTableSwitchDispatch(MTableSwitch* mir, + Register index, Register base) { + Label* defaultcase = skipTrivialBlocks(mir->getDefault())->lir()->label(); + + if (mir->low() != 0) { + masm.subPtr(Imm32(mir->low()), index); + } + + int32_t cases = mir->numCases(); + masm.branchPtr(Assembler::AboveOrEqual, index, ImmWord(cases), defaultcase); + + OutOfLineTableSwitch* ool = new (alloc()) OutOfLineTableSwitch(mir); + addOutOfLineCode(ool, mir); + + masm.mov(ool->jumpLabel(), base); + + BaseIndex pointer(base, index, ScalePointer); + masm.branchToComputedAddress(pointer); +} + +void CodeGeneratorPPC::generateInvalidateEpilogue() { + // Pad with enough nops so that PatchWrite_NearCall on the last OSI point + // cannot overlap the invalidation epilogue. + for (size_t i = 0; i < Assembler::PatchWrite_NearCallSize(); + i += Assembler::NopSize()) { + masm.nop(); + } + + masm.bind(&invalidate_); + + // Push the return address (LR) onto the stack. + masm.pushReturnAddress(); + + invalidateEpilogueData_ = masm.pushWithPatch(ImmWord(uintptr_t(-1))); + + TrampolinePtr thunk = gen->jitRuntime()->getInvalidationThunk(); + masm.jump(thunk); +} + +void CodeGeneratorPPC::visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool) { + MTableSwitch* mir = ool->mir(); + + masm.haltingAlign(sizeof(void*)); + masm.bind(ool->jumpLabel()); + 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(); + + CodeLabel cl; + masm.writeCodePointer(&cl); + cl.target()->bind(caseoffset); + masm.addCodeLabel(cl); + } +} + +void CodeGeneratorPPC::visitOutOfLineWasmTruncateCheck( + OutOfLineWasmTruncateCheck* ool) { + if (ool->toType() == MIRType::Int32) { + masm.outOfLineWasmTruncateToInt32Check(ool->input(), ool->output(), + ool->fromType(), ool->flags(), + ool->rejoin(), ool->trapSiteDesc()); + } else { + MOZ_ASSERT(ool->toType() == MIRType::Int64); + masm.outOfLineWasmTruncateToInt64Check(ool->input(), ool->output64(), + ool->fromType(), ool->flags(), + ool->rejoin(), ool->trapSiteDesc()); + } +} + +void CodeGeneratorPPC::emitBigIntPtrDiv(LBigIntPtrDiv* ins, Register dividend, + Register divisor, Register output) { + masm.as_divw(output, dividend, divisor); +} + +void CodeGeneratorPPC::emitBigIntPtrMod(LBigIntPtrMod* ins, Register dividend, + Register divisor, Register output) { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.as_divw(scratch, dividend, divisor); + masm.as_mullw(scratch, scratch, divisor); + masm.as_subf(output, scratch, dividend); +} + +// =============================================================== +// Visitors: Box/Unbox + +void CodeGenerator::visitBox(LBox* box) { + const LDefinition* type = box->getDef(TYPE_INDEX); + + MOZ_ASSERT(!box->payload()->isConstant()); + + // LIRGenerator::visitBox gives the input and the payload definition the same + // virtual register, so only the type tag has to be materialised. + masm.move32(Imm32(MIRTypeToTag(box->type())), ToRegister(type)); +} + +void CodeGenerator::visitBoxFloatingPoint(LBoxFloatingPoint* box) { + FloatRegister in = ToFloatRegister(box->input()); + ValueOperand out = ToOutValue(box); + + if (box->type() == MIRType::Float32) { + masm.convertFloat32ToDouble(in, ScratchDoubleReg); + in = ScratchDoubleReg; + } + + // There is no direct FPR->GPR move on this target, so spill the double and + // reload its two halves. Big endian: the high word, which is where a boxed + // double keeps its tag bits, is at the lower address. + masm.reserveStack(sizeof(double)); + masm.storeDouble(in, Address(StackPointer, 0)); + masm.load32(Address(StackPointer, 0), out.typeReg()); + masm.load32(Address(StackPointer, sizeof(int32_t)), out.payloadReg()); + masm.freeStack(sizeof(double)); +} + +void CodeGenerator::visitUnbox(LUnbox* unbox) { + // Note that for unbox, the type and payload indexes are switched on the + // inputs. + MUnbox* mir = unbox->mir(); + Register type = ToRegister(unbox->type()); + Register payload = ToRegister(unbox->payload()); + Register output = ToRegister(unbox->output()); + + JSValueTag tag = MIRTypeToTag(mir->type()); + if (mir->fallible()) { + bailoutCmp32(Assembler::NotEqual, type, Imm32(tag), unbox->snapshot()); + } else { +#ifdef DEBUG + Label ok; + masm.branch32(Assembler::Equal, type, Imm32(tag), &ok); + masm.assumeUnreachable("Infallible unbox type mismatch"); + masm.bind(&ok); +#endif + } + + masm.unboxNonDouble(ValueOperand(type, payload), output, + ValueTypeFromMIRType(mir->type())); +} + +// =============================================================== +// Visitors: Integer Arithmetic + +void CodeGenerator::visitAddI(LAddI* ins) { + const LAllocation* lhs = ins->getOperand(0); + const LAllocation* rhs = ins->getOperand(1); + Register dest = ToRegister(ins->getDef(0)); + + if (rhs->isConstant()) { + Imm32 imm(ToInt32(rhs)); + if (ins->snapshot()) { + masm.move32(ToRegister(lhs), dest); + Label overflow; + masm.branchAdd32(Assembler::Overflow, imm, dest, &overflow); + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.add32(imm, ToRegister(lhs), dest); + } + return; + } + + Register lhsReg = ToRegister(lhs); + Register rhsReg = ToRegister(rhs); + + if (ins->snapshot()) { + Label overflow; + // Addition is commutative, so if dest aliases rhs accumulate lhs instead + // of clobbering rhs with a move. + if (dest == rhsReg) { + masm.branchAdd32(Assembler::Overflow, lhsReg, dest, &overflow); + } else { + masm.move32(lhsReg, dest); + masm.branchAdd32(Assembler::Overflow, rhsReg, dest, &overflow); + } + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.as_add(dest, lhsReg, rhsReg); + } +} + +void CodeGenerator::visitAddIntPtr(LAddIntPtr* ins) { + Register dest = ToRegister(ins->getDef(0)); + Register lhs = ToRegister(ins->getOperand(0)); + const LAllocation* rhs = ins->getOperand(1); + + if (rhs->isConstant()) { + if (lhs != dest) { + masm.movePtr(lhs, dest); + } + masm.addPtr(ImmWord(ToIntPtr(rhs)), dest); + } else { + masm.as_add(dest, lhs, ToRegister(rhs)); + } +} + +void CodeGenerator::visitAddI64(LAddI64* lir) { + LInt64Allocation lhs = lir->lhs(); + LInt64Allocation rhs = lir->rhs(); + + MOZ_ASSERT(ToOutRegister64(lir) == ToRegister64(lhs)); + + if (IsConstant(rhs)) { + masm.add64(Imm64(ToInt64(rhs)), ToRegister64(lhs)); + return; + } + + masm.add64(ToRegister64(rhs), ToRegister64(lhs)); +} + +void CodeGenerator::visitSubI(LSubI* ins) { + const LAllocation* lhs = ins->getOperand(0); + const LAllocation* rhs = ins->getOperand(1); + Register dest = ToRegister(ins->getDef(0)); + + if (rhs->isConstant()) { + Imm32 imm(ToInt32(rhs)); + if (ins->snapshot()) { + masm.move32(ToRegister(lhs), dest); + Label overflow; + masm.branchSub32(Assembler::Overflow, imm, dest, &overflow); + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.move32(ToRegister(lhs), dest); + masm.sub32(imm, dest); + } + return; + } + + Register lhsReg = ToRegister(lhs); + Register rhsReg = ToRegister(rhs); + + if (ins->snapshot()) { + Label overflow; + if (dest == rhsReg) { + // as_subf reads both operands before writing, so check overflow inline + // rather than staging through a scratch: branchSub32 needs both itself. + UseScratchRegisterScope temps(masm); + Register t0 = temps.Acquire(); + Register t1 = temps.Acquire(); + masm.as_xor_(t0, lhsReg, rhsReg); + masm.as_subf(dest, rhsReg, lhsReg); + masm.as_xor_(t1, lhsReg, dest); + masm.as_and__rc(t0, t0, t1); + masm.ma_b(Assembler::LessThan, &overflow); + } else { + masm.move32(lhsReg, dest); + masm.branchSub32(Assembler::Overflow, rhsReg, dest, &overflow); + } + bailoutFrom(&overflow, ins->snapshot()); + } else { + // as_subf(d, a, b) computes d = b - a. + masm.as_subf(dest, rhsReg, lhsReg); + } +} + +void CodeGenerator::visitSubIntPtr(LSubIntPtr* ins) { + Register dest = ToRegister(ins->getDef(0)); + Register lhs = ToRegister(ins->getOperand(0)); + const LAllocation* rhs = ins->getOperand(1); + + if (rhs->isConstant()) { + if (lhs != dest) { + masm.movePtr(lhs, dest); + } + masm.subPtr(Imm32(int32_t(ToIntPtr(rhs))), dest); + } else { + masm.as_subf(dest, ToRegister(rhs), lhs); + } +} + +void CodeGenerator::visitSubI64(LSubI64* lir) { + LInt64Allocation lhs = lir->lhs(); + LInt64Allocation rhs = lir->rhs(); + + MOZ_ASSERT(ToOutRegister64(lir) == ToRegister64(lhs)); + + if (IsConstant(rhs)) { + masm.sub64(Imm64(ToInt64(rhs)), ToRegister64(lhs)); + return; + } + + masm.sub64(ToRegister64(rhs), ToRegister64(lhs)); +} + +void CodeGenerator::visitMulI(LMulI* ins) { + Register dest = ToRegister(ins->getDef(0)); + Register lhs = ToRegister(ins->getOperand(0)); + const LAllocation* rhs = ins->getOperand(1); + MMul* mul = ins->mir(); + + if (rhs->isConstant()) { + int32_t constant = ToInt32(rhs); + + // Bail out on -0.0 before the special cases below, which return early. + if (mul->canBeNegativeZero() && constant <= 0) { + Assembler::Condition cond = + (constant == 0) ? Assembler::LessThan : Assembler::Equal; + bailoutCmp32(cond, lhs, Imm32(0), ins->snapshot()); + } + + switch (constant) { + case -1: + if (mul->canOverflow()) { + masm.move32(lhs, dest); + Label overflow; + masm.branchNeg32(Assembler::Overflow, dest, &overflow); + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.as_neg(dest, lhs); + } + return; + case 0: + masm.move32(Imm32(0), dest); + return; + case 1: + masm.move32(lhs, dest); + return; + case 2: + masm.move32(lhs, dest); + if (mul->canOverflow()) { + Label overflow; + masm.branchAdd32(Assembler::Overflow, dest, dest, &overflow); + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.add32(dest, dest); + } + return; + default: + break; + } + + uint32_t absCst = + constant < 0 ? uint32_t(-int64_t(constant)) : uint32_t(constant); + if (!mul->canOverflow() && (absCst & (absCst - 1)) == 0) { + masm.x_slwi(dest, lhs, mozilla::FloorLog2(absCst)); + if (constant < 0) { + masm.as_neg(dest, dest); + } + return; + } + + masm.move32(lhs, dest); + if (mul->canOverflow()) { + Label overflow; + masm.branchMul32(Assembler::Overflow, Imm32(constant), dest, &overflow); + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.mul32(Imm32(constant), dest); + } + return; + } + + Register rhsReg = ToRegister(rhs); + + if (mul->canBeNegativeZero()) { + // The product is -0 exactly when one operand is zero and the other is + // negative. Test that before the multiply, which may clobber an input. + Label lhsNonZero, checked; + masm.branch32(Assembler::NotEqual, lhs, Imm32(0), &lhsNonZero); + bailoutCmp32(Assembler::LessThan, rhsReg, Imm32(0), ins->snapshot()); + masm.jump(&checked); + masm.bind(&lhsNonZero); + masm.branch32(Assembler::NotEqual, rhsReg, Imm32(0), &checked); + bailoutCmp32(Assembler::LessThan, lhs, Imm32(0), ins->snapshot()); + masm.bind(&checked); + } + + if (mul->canOverflow()) { + Label overflow; + if (dest == rhsReg) { + masm.branchMul32(Assembler::Overflow, lhs, dest, &overflow); + } else { + masm.move32(lhs, dest); + masm.branchMul32(Assembler::Overflow, rhsReg, dest, &overflow); + } + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.as_mullw(dest, lhs, rhsReg); + } +} + +void CodeGenerator::visitMulIntPtr(LMulIntPtr* ins) { + Register dest = ToRegister(ins->getDef(0)); + Register lhs = ToRegister(ins->getOperand(0)); + const LAllocation* rhs = ins->getOperand(1); + + if (rhs->isConstant()) { + if (lhs != dest) { + masm.movePtr(lhs, dest); + } + masm.mulPtr(ImmWord(ToIntPtr(rhs)), dest); + } else { + masm.as_mullw(dest, lhs, ToRegister(rhs)); + } +} + +void CodeGenerator::visitMulI64(LMulI64* lir) { + LInt64Allocation lhs = lir->lhs(); + LInt64Allocation rhs = lir->rhs(); + + MOZ_ASSERT(ToOutRegister64(lir) == ToRegister64(lhs)); + + if (IsConstant(rhs)) { + int64_t constant = ToInt64(rhs); + // These cases must match the ones lowerForMulInt64 assumes need no temp. + switch (constant) { + case -1: + masm.neg64(ToRegister64(lhs)); + return; + case 0: + masm.move64(Imm64(0), ToRegister64(lhs)); + return; + case 1: + return; + case 2: + masm.add64(ToRegister64(lhs), ToRegister64(lhs)); + return; + default: + break; + } + if (constant > 0) { + int32_t shift = mozilla::FloorLog2(uint64_t(constant)); + if (int64_t(1) << shift == constant) { + masm.lshift64(Imm32(shift), ToRegister64(lhs)); + return; + } + } + masm.mul64(Imm64(constant), ToRegister64(lhs), ToRegister(lir->temp0())); + return; + } + + masm.mul64(ToRegister64(rhs), ToRegister64(lhs), ToRegister(lir->temp0())); +} + +void CodeGenerator::visitDivI(LDivI* ins) { + Register lhs = ToRegister(ins->lhs()); + Register rhs = ToRegister(ins->rhs()); + Register dest = ToRegister(ins->output()); + Register temp = ToRegister(ins->temp0()); + MDiv* mir = ins->mir(); + + Label done; + + if (mir->canBeDivideByZero()) { + if (mir->trapOnError()) { + Label nonZero; + masm.branch32(Assembler::NotEqual, rhs, Imm32(0), &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); + masm.bind(&nonZero); + } else if (mir->canTruncateInfinities()) { + Label nonZero; + masm.branch32(Assembler::NotEqual, rhs, Imm32(0), &nonZero); + masm.move32(Imm32(0), dest); + masm.jump(&done); + masm.bind(&nonZero); + } else { + MOZ_ASSERT(mir->fallible()); + bailoutCmp32(Assembler::Equal, rhs, Imm32(0), ins->snapshot()); + } + } + + // divw is undefined for INT32_MIN / -1. + if (mir->canBeNegativeOverflow()) { + Label notMinInt; + masm.branch32(Assembler::NotEqual, lhs, Imm32(INT32_MIN), ¬MinInt); + masm.branch32(Assembler::NotEqual, rhs, Imm32(-1), ¬MinInt); + + if (mir->trapOnError()) { + masm.wasmTrap(wasm::Trap::IntegerOverflow, mir->trapSiteDesc()); + } else if (mir->canTruncateOverflow()) { + masm.move32(Imm32(INT32_MIN), dest); + masm.jump(&done); + } else { + MOZ_ASSERT(mir->fallible()); + bailout(ins->snapshot()); + } + masm.bind(¬MinInt); + } + + if (!mir->canTruncateNegativeZero() && mir->canBeNegativeZero()) { + Label ok; + masm.branch32(Assembler::NotEqual, lhs, Imm32(0), &ok); + bailoutCmp32(Assembler::LessThan, rhs, Imm32(0), ins->snapshot()); + masm.bind(&ok); + } + + masm.as_divw(dest, lhs, rhs); + + if (!mir->canTruncateRemainder()) { + masm.as_mullw(temp, dest, rhs); + masm.as_subf(temp, temp, lhs); + bailoutCmp32(Assembler::NotEqual, temp, Imm32(0), ins->snapshot()); + } + + masm.bind(&done); +} + +void CodeGenerator::visitDivPowTwoI(LDivPowTwoI* ins) { + Register lhs = ToRegister(ins->numerator()); + Register dest = ToRegister(ins->output()); + int32_t shift = ins->shift(); + + if (shift == 0) { + masm.move32(lhs, dest); + return; + } + + MDiv* mir = ins->mir(); + UseScratchRegisterScope temps(masm); + Register tmp = temps.Acquire(); + + if (!mir->isTruncated()) { + // If the remainder is non-zero, bail out. + masm.x_slwi(tmp, lhs, 32 - shift); + bailoutCmp32(Assembler::NotEqual, tmp, Imm32(0), ins->snapshot()); + } + + if (!mir->canBeNegativeDividend()) { + masm.as_srawi(dest, lhs, shift); + return; + } + + // Round towards zero for a negative dividend by adding (1 << shift) - 1. + if (shift > 1) { + masm.as_srawi(tmp, lhs, 31); + masm.as_rlwinm(tmp, tmp, 0, 32 - shift, 31); + } else { + masm.as_rlwinm(tmp, lhs, 1, 31, 31); + } + masm.add32(lhs, tmp); + masm.as_srawi(dest, tmp, shift); +} + +void CodeGenerator::visitModI(LModI* ins) { + Register lhs = ToRegister(ins->lhs()); + Register rhs = ToRegister(ins->rhs()); + Register dest = ToRegister(ins->output()); + UseScratchRegisterScope temps(masm); + Register temp = temps.Acquire(); + MMod* mir = ins->mir(); + Label done; + + if (mir->canBeDivideByZero()) { + if (mir->isTruncated()) { + if (mir->trapOnError()) { + Label nonZero; + masm.branch32(Assembler::NotEqual, rhs, Imm32(0), &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, mir->trapSiteDesc()); + masm.bind(&nonZero); + } else { + // Truncated division by zero yields integer zero. + masm.move32(rhs, dest); + Label nonZero; + masm.branch32(Assembler::NotEqual, rhs, Imm32(0), &nonZero); + masm.jump(&done); + masm.bind(&nonZero); + } + } else { + MOZ_ASSERT(mir->fallible()); + bailoutCmp32(Assembler::Equal, rhs, Imm32(0), ins->snapshot()); + } + } + + // divw is undefined for INT32_MIN / -1, so return 0 explicitly. The wasm + // spec also defines rem_s(INT32_MIN, -1) = 0. + if (!mir->isUnsigned()) { + Label notMinOverflow; + masm.branch32(Assembler::NotEqual, lhs, Imm32(INT32_MIN), ¬MinOverflow); + masm.branch32(Assembler::NotEqual, rhs, Imm32(-1), ¬MinOverflow); + masm.move32(Imm32(0), dest); + masm.jump(&done); + masm.bind(¬MinOverflow); + } + + masm.as_divw(temp, lhs, rhs); + masm.as_mullw(temp, temp, rhs); + masm.as_subf(dest, temp, lhs); + + // If X%Y == 0 and X < 0, the result is -0 and we need to bail out. + if (mir->canBeNegativeDividend() && !mir->isTruncated()) { + MOZ_ASSERT(mir->fallible()); + Label ok; + masm.branch32(Assembler::NotEqual, dest, Imm32(0), &ok); + bailoutCmp32(Assembler::LessThan, lhs, Imm32(0), ins->snapshot()); + masm.bind(&ok); + } + + masm.bind(&done); +} + +void CodeGenerator::visitModPowTwoI(LModPowTwoI* ins) { + Register in = ToRegister(ins->getOperand(0)); + Register out = ToRegister(ins->getDef(0)); + MMod* mir = ins->mir(); + int32_t shift = ins->shift(); + uint32_t mask = (uint32_t(1) << shift) - 1; + + Label negative, done; + masm.branch32(Assembler::LessThan, in, Imm32(0), &negative); + + masm.and32(Imm32(mask), in, out); + masm.jump(&done); + + // Negative case: negate, mask, negate back. + masm.bind(&negative); + masm.as_neg(out, in); + masm.and32(Imm32(mask), out); + masm.as_neg(out, out); + + if (mir->canBeNegativeDividend() && !mir->isTruncated()) { + MOZ_ASSERT(mir->fallible()); + bailoutCmp32(Assembler::Equal, out, Imm32(0), ins->snapshot()); + } + + masm.bind(&done); +} + +void CodeGenerator::visitModMaskI(LModMaskI* ins) { + Register src = ToRegister(ins->input()); + Register dest = ToRegister(ins->output()); + Register tmp0 = ToRegister(ins->temp0()); + Register tmp1 = ToRegister(ins->temp1()); + MMod* mir = ins->mir(); + + if (!mir->isTruncated() && mir->canBeNegativeDividend()) { + MOZ_ASSERT(mir->fallible()); + + Label bail; + masm.ma_mod_mask(src, dest, tmp0, tmp1, ins->shift(), &bail); + bailoutFrom(&bail, ins->snapshot()); + } else { + masm.ma_mod_mask(src, dest, tmp0, tmp1, ins->shift(), nullptr); + } +} + +void CodeGenerator::visitNegI(LNegI* ins) { + Register input = ToRegister(ins->input()); + Register output = ToRegister(ins->output()); + masm.as_neg(output, input); +} + +void CodeGenerator::visitNegI64(LNegI64* ins) { + Register64 input = ToRegister64(ins->input()); + MOZ_ASSERT(input == ToOutRegister64(ins)); + masm.neg64(input); +} + +void CodeGenerator::visitUDivOrMod(LUDivOrMod* ins) { + Register lhs = ToRegister(ins->lhs()); + Register rhs = ToRegister(ins->rhs()); + Register output = ToRegister(ins->output()); + UseScratchRegisterScope temps(masm); + Register temp = temps.Acquire(); + Label done; + + if (ins->canBeDivideByZero()) { + if (ins->mir()->isTruncated()) { + if (ins->trapOnError()) { + Label nonZero; + masm.branch32(Assembler::NotEqual, rhs, Imm32(0), &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, ins->trapSiteDesc()); + masm.bind(&nonZero); + } else { + Label nonZero; + masm.branch32(Assembler::NotEqual, rhs, Imm32(0), &nonZero); + masm.move32(Imm32(0), output); + masm.jump(&done); + masm.bind(&nonZero); + } + } else { + bailoutCmp32(Assembler::Equal, rhs, Imm32(0), ins->snapshot()); + } + } + + if (ins->mir()->isDiv()) { + if (!ins->mir()->toDiv()->canTruncateRemainder()) { + masm.as_divwu(temp, lhs, rhs); + masm.as_mullw(temp, temp, rhs); + masm.as_subf(temp, temp, lhs); + bailoutCmp32(Assembler::NotEqual, temp, Imm32(0), ins->snapshot()); + } + masm.as_divwu(output, lhs, rhs); + } else { + masm.as_divwu(temp, lhs, rhs); + masm.as_mullw(temp, temp, rhs); + masm.as_subf(output, temp, lhs); + } + + // A large unsigned result does not fit in an int32 and must become a double. + if (!ins->mir()->isTruncated()) { + bailoutCmp32(Assembler::LessThan, output, Imm32(0), ins->snapshot()); + } + + masm.bind(&done); +} + +// 32-bit PowerPC has no divd/divdu, so 64-bit division and modulo are calls +// to the wasm builtins, as on ARM. +void CodeGenerator::visitDivOrModI64(LDivOrModI64* lir) { + MOZ_ASSERT(gen->compilingWasm()); + MOZ_ASSERT(ToRegister(lir->instance()) == InstanceReg); + masm.Push(InstanceReg); + int32_t framePushedAfterInstance = masm.framePushed(); + + Register64 lhs = ToRegister64(lir->lhs()); + Register64 rhs = ToRegister64(lir->rhs()); + Register64 output = ToOutRegister64(lir); + + MOZ_ASSERT(output == ReturnReg64); + + Label done; + + if (lir->canBeDivideByZero()) { + Label nonZero; + // InstanceReg was preserved above, so it is free as a temp. + masm.branchTest64(Assembler::NonZero, rhs, rhs, InstanceReg, &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, lir->trapSiteDesc()); + masm.bind(&nonZero); + } + + auto* mir = lir->mir(); + + // Handle the INT64_MIN / -1 overflow case. + if (lir->canBeNegativeOverflow()) { + Label notmin; + masm.branch64(Assembler::NotEqual, lhs, Imm64(INT64_MIN), ¬min); + masm.branch64(Assembler::NotEqual, rhs, Imm64(-1), ¬min); + if (mir->isWasmBuiltinModI64()) { + masm.xor64(output, output); + } else { + masm.wasmTrap(wasm::Trap::IntegerOverflow, lir->trapSiteDesc()); + } + masm.jump(&done); + masm.bind(¬min); + } + + wasm::SymbolicAddress callee = mir->isWasmBuiltinModI64() + ? wasm::SymbolicAddress::ModI64 + : wasm::SymbolicAddress::DivI64; + masm.setupWasmABICall(callee); + masm.passABIArg(lhs.high); + masm.passABIArg(lhs.low); + masm.passABIArg(rhs.high); + masm.passABIArg(rhs.low); + + int32_t instanceOffset = masm.framePushed() - framePushedAfterInstance; + masm.callWithABI(lir->trapSiteDesc().bytecodeOffset, callee, + mozilla::Some(instanceOffset)); + + masm.bind(&done); + masm.Pop(InstanceReg); +} + +void CodeGenerator::visitUDivOrModI64(LUDivOrModI64* lir) { + MOZ_ASSERT(gen->compilingWasm()); + MOZ_ASSERT(ToRegister(lir->instance()) == InstanceReg); + masm.Push(InstanceReg); + int32_t framePushedAfterInstance = masm.framePushed(); + + Register64 lhs = ToRegister64(lir->lhs()); + Register64 rhs = ToRegister64(lir->rhs()); + + MOZ_ASSERT(ToOutRegister64(lir) == ReturnReg64); + + if (lir->canBeDivideByZero()) { + Label nonZero; + masm.branchTest64(Assembler::NonZero, rhs, rhs, InstanceReg, &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, lir->trapSiteDesc()); + masm.bind(&nonZero); + } + + MDefinition* mir = lir->mir(); + wasm::SymbolicAddress callee = mir->isWasmBuiltinModI64() + ? wasm::SymbolicAddress::UModI64 + : wasm::SymbolicAddress::UDivI64; + masm.setupWasmABICall(callee); + masm.passABIArg(lhs.high); + masm.passABIArg(lhs.low); + masm.passABIArg(rhs.high); + masm.passABIArg(rhs.low); + + int32_t instanceOffset = masm.framePushed() - framePushedAfterInstance; + masm.callWithABI(lir->trapSiteDesc().bytecodeOffset, callee, + mozilla::Some(instanceOffset)); + masm.Pop(InstanceReg); +} + +// =============================================================== +// Visitors: Bitwise + +void CodeGenerator::visitBitNotI(LBitNotI* ins) { + Register input = ToRegister(ins->input()); + Register dest = ToRegister(ins->output()); + masm.as_nor(dest, input, input); +} + +void CodeGenerator::visitBitNotI64(LBitNotI64* ins) { + Register64 input = ToRegister64(ins->input()); + MOZ_ASSERT(input == ToOutRegister64(ins)); + masm.as_nor(input.high, input.high, input.high); + masm.as_nor(input.low, input.low, input.low); +} + +void CodeGenerator::visitBitOpI(LBitOpI* ins) { + Register dest = ToRegister(ins->getDef(0)); + Register lhs = ToRegister(ins->getOperand(0)); + const LAllocation* rhs = ins->getOperand(1); + + switch (ins->bitop()) { + case JSOp::BitOr: + if (rhs->isConstant()) { + masm.or32(Imm32(ToInt32(rhs)), lhs, dest); + } else { + masm.as_or_(dest, lhs, ToRegister(rhs)); + } + break; + case JSOp::BitXor: + if (rhs->isConstant()) { + masm.xor32(Imm32(ToInt32(rhs)), lhs, dest); + } else { + masm.as_xor_(dest, lhs, ToRegister(rhs)); + } + break; + case JSOp::BitAnd: + if (rhs->isConstant()) { + masm.and32(Imm32(ToInt32(rhs)), lhs, dest); + } else { + masm.as_and_(dest, lhs, ToRegister(rhs)); + } + break; + default: + MOZ_CRASH("unexpected binary opcode"); + } +} + +void CodeGenerator::visitBitOpI64(LBitOpI64* lir) { + LInt64Allocation lhs = lir->lhs(); + LInt64Allocation rhs = lir->rhs(); + + MOZ_ASSERT(ToOutRegister64(lir) == ToRegister64(lhs)); + + switch (lir->bitop()) { + case JSOp::BitOr: + if (IsConstant(rhs)) { + masm.or64(Imm64(ToInt64(rhs)), ToRegister64(lhs)); + } else { + masm.or64(ToRegister64(rhs), ToRegister64(lhs)); + } + break; + case JSOp::BitXor: + if (IsConstant(rhs)) { + masm.xor64(Imm64(ToInt64(rhs)), ToRegister64(lhs)); + } else { + masm.xor64(ToRegister64(rhs), ToRegister64(lhs)); + } + break; + case JSOp::BitAnd: + if (IsConstant(rhs)) { + masm.and64(Imm64(ToInt64(rhs)), ToRegister64(lhs)); + } else { + masm.and64(ToRegister64(rhs), ToRegister64(lhs)); + } + break; + default: + MOZ_CRASH("unexpected binary opcode"); + } +} + +void CodeGenerator::visitShiftI(LShiftI* ins) { + Register lhs = ToRegister(ins->lhs()); + const LAllocation* rhs = ins->rhs(); + Register dest = ToRegister(ins->output()); + + if (rhs->isConstant()) { + int32_t shift = ToInt32(rhs) & 0x1f; + switch (ins->bitop()) { + case JSOp::Lsh: + if (shift) { + masm.lshift32(Imm32(shift), lhs, dest); + } else { + masm.move32(lhs, dest); + } + break; + case JSOp::Rsh: + if (shift) { + masm.rshift32Arithmetic(Imm32(shift), lhs, dest); + } else { + masm.move32(lhs, dest); + } + break; + case JSOp::Ursh: + if (shift) { + masm.rshift32(Imm32(shift), lhs, dest); + } else { + masm.move32(lhs, dest); + } + if (ins->mir()->toUrsh()->fallible()) { + // x >>> 0 can produce values that don't fit in an int32. + bailoutCmp32(Assembler::LessThan, dest, Imm32(0), ins->snapshot()); + } + break; + default: + MOZ_CRASH("unexpected shift opcode"); + } + return; + } + + // slw/srw/sraw consume six bits of shift amount; JS requires mod 32. + Register shiftReg = ToRegister(rhs); + UseScratchRegisterScope temps(masm); + Register masked = temps.Acquire(); + masm.as_rlwinm(masked, shiftReg, 0, 27, 31); + switch (ins->bitop()) { + case JSOp::Lsh: + masm.as_slw(dest, lhs, masked); + break; + case JSOp::Rsh: + masm.as_sraw(dest, lhs, masked); + break; + case JSOp::Ursh: + masm.as_srw(dest, lhs, masked); + if (ins->mir()->toUrsh()->fallible()) { + bailoutCmp32(Assembler::LessThan, dest, Imm32(0), ins->snapshot()); + } + break; + default: + MOZ_CRASH("unexpected shift opcode"); + } +} + +void CodeGenerator::visitShiftIntPtr(LShiftIntPtr* ins) { + Register lhs = ToRegister(ins->lhs()); + Register dest = ToRegister(ins->output()); + + if (ins->rhs()->isConstant()) { + int32_t shift = int32_t(ToIntPtr(ins->rhs())) & 0x1f; + switch (ins->bitop()) { + case JSOp::Lsh: + if (shift) { + masm.lshiftPtr(Imm32(shift), lhs, dest); + } else { + masm.movePtr(lhs, dest); + } + break; + case JSOp::Rsh: + if (shift) { + masm.rshiftPtrArithmetic(Imm32(shift), lhs, dest); + } else { + masm.movePtr(lhs, dest); + } + break; + case JSOp::Ursh: + if (shift) { + masm.rshiftPtr(Imm32(shift), lhs, dest); + } else { + masm.movePtr(lhs, dest); + } + break; + default: + MOZ_CRASH("unexpected shift opcode"); + } + return; + } + + Register shiftReg = ToRegister(ins->rhs()); + UseScratchRegisterScope temps(masm); + Register masked = temps.Acquire(); + masm.as_rlwinm(masked, shiftReg, 0, 27, 31); + switch (ins->bitop()) { + case JSOp::Lsh: + masm.as_slw(dest, lhs, masked); + break; + case JSOp::Rsh: + masm.as_sraw(dest, lhs, masked); + break; + case JSOp::Ursh: + masm.as_srw(dest, lhs, masked); + break; + default: + MOZ_CRASH("unexpected shift opcode"); + } +} + +void CodeGenerator::visitShiftI64(LShiftI64* lir) { + LInt64Allocation lhs = lir->lhs(); + const LAllocation* rhs = lir->rhs(); + + MOZ_ASSERT(ToOutRegister64(lir) == ToRegister64(lhs)); + + if (rhs->isConstant()) { + int32_t shift = int32_t(rhs->toConstant()->toInt64() & 0x3f); + if (shift == 0) { + return; + } + switch (lir->bitop()) { + case JSOp::Lsh: + masm.lshift64(Imm32(shift), ToRegister64(lhs)); + break; + case JSOp::Rsh: + masm.rshift64Arithmetic(Imm32(shift), ToRegister64(lhs)); + break; + case JSOp::Ursh: + masm.rshift64(Imm32(shift), ToRegister64(lhs)); + break; + default: + MOZ_CRASH("unexpected shift opcode"); + } + return; + } + + switch (lir->bitop()) { + case JSOp::Lsh: + masm.lshift64(ToRegister(rhs), ToRegister64(lhs)); + break; + case JSOp::Rsh: + masm.rshift64Arithmetic(ToRegister(rhs), ToRegister64(lhs)); + break; + case JSOp::Ursh: + masm.rshift64(ToRegister(rhs), ToRegister64(lhs)); + break; + default: + MOZ_CRASH("unexpected shift opcode"); + } +} + +// =============================================================== +// Visitors: Floating-point arithmetic + +void CodeGenerator::visitMathD(LMathD* math) { + FloatRegister lhs = ToFloatRegister(math->lhs()); + FloatRegister rhs = ToFloatRegister(math->rhs()); + FloatRegister dest = ToFloatRegister(math->output()); + + switch (math->jsop()) { + case JSOp::Add: + masm.as_fadd(dest, lhs, rhs); + break; + case JSOp::Sub: + masm.as_fsub(dest, lhs, rhs); + break; + case JSOp::Mul: + masm.as_fmul(dest, lhs, rhs); + break; + case JSOp::Div: + masm.as_fdiv(dest, lhs, rhs); + break; + default: + MOZ_CRASH("unexpected double opcode"); + } +} + +void CodeGenerator::visitMathF(LMathF* math) { + FloatRegister lhs = ToFloatRegister(math->lhs()); + FloatRegister rhs = ToFloatRegister(math->rhs()); + FloatRegister dest = ToFloatRegister(math->output()); + + switch (math->jsop()) { + case JSOp::Add: + masm.as_fadds(dest, lhs, rhs); + break; + case JSOp::Sub: + masm.as_fsubs(dest, lhs, rhs); + break; + case JSOp::Mul: + masm.as_fmuls(dest, lhs, rhs); + break; + case JSOp::Div: + masm.as_fdivs(dest, lhs, rhs); + break; + default: + MOZ_CRASH("unexpected float32 opcode"); + } +} + +void CodeGenerator::visitMinMaxD(LMinMaxD* ins) { + FloatRegister first = ToFloatRegister(ins->first()); + FloatRegister second = ToFloatRegister(ins->second()); + mozilla::DebugOnly output = ToFloatRegister(ins->output()); + + MOZ_ASSERT(first == output); + if (ins->mir()->isMax()) { + masm.maxDouble(second, first, /* handleNaN = */ true); + } else { + masm.minDouble(second, first, /* handleNaN = */ true); + } +} + +void CodeGenerator::visitMinMaxF(LMinMaxF* ins) { + FloatRegister first = ToFloatRegister(ins->first()); + FloatRegister second = ToFloatRegister(ins->second()); + mozilla::DebugOnly output = ToFloatRegister(ins->output()); + + MOZ_ASSERT(first == output); + if (ins->mir()->isMax()) { + masm.maxFloat32(second, first, /* handleNaN = */ true); + } else { + masm.minFloat32(second, first, /* handleNaN = */ true); + } +} + +void CodeGenerator::visitNegD(LNegD* ins) { + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + masm.as_fneg(output, input); +} + +void CodeGenerator::visitNegF(LNegF* ins) { + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + masm.as_fneg(output, input); +} + +void CodeGenerator::visitUrshD(LUrshD* ins) { + Register lhs = ToRegister(ins->lhs()); + Register temp = ToRegister(ins->temp0()); + const LAllocation* rhs = ins->rhs(); + FloatRegister out = ToFloatRegister(ins->output()); + + if (rhs->isConstant()) { + int32_t shift = ToInt32(rhs) & 0x1F; + if (shift) { + masm.x_srwi(temp, lhs, shift); + } else { + masm.move32(lhs, temp); + } + } else { + masm.as_andi_rc(temp, ToRegister(rhs), 0x1F); + masm.as_srw(temp, lhs, temp); + } + + masm.convertUInt32ToDouble(temp, out); +} + +void CodeGenerator::visitPowHalfD(LPowHalfD* ins) { + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + + Label done, skip; + + // Check for -Infinity. + masm.loadConstantDouble(NegativeInfinity(), ScratchDoubleReg); + masm.branchDouble(Assembler::DoubleNotEqualOrUnordered, input, + ScratchDoubleReg, &skip); + masm.loadConstantDouble(std::numeric_limits::infinity(), output); + masm.jump(&done); + + masm.bind(&skip); + // Add 0.0 to handle -0. There is no fsqrt on this target, so sqrtDouble is + // a macro-assembler routine rather than a single instruction. + masm.loadConstantDouble(0.0, ScratchDoubleReg); + masm.as_fadd(output, input, ScratchDoubleReg); + masm.sqrtDouble(output, output); + + masm.bind(&done); +} + +void CodeGenerator::visitNotD(LNotD* ins) { + FloatRegister input = ToFloatRegister(ins->input()); + Register dest = ToRegister(ins->output()); + + masm.loadConstantDouble(0.0, ScratchDoubleReg); + masm.as_fcmpu(input, ScratchDoubleReg); + masm.ma_cmp_set_dbl(dest, Assembler::DoubleEqualOrUnordered); +} + +void CodeGenerator::visitNotF(LNotF* ins) { + FloatRegister input = ToFloatRegister(ins->input()); + Register dest = ToRegister(ins->output()); + + masm.loadConstantFloat32(0.0f, ScratchFloat32Reg); + masm.as_fcmpu(input, ScratchFloat32Reg); + masm.ma_cmp_set_dbl(dest, Assembler::DoubleEqualOrUnordered); +} + +// =============================================================== +// Visitors: FP comparisons and branches + +void CodeGenerator::visitCompareD(LCompareD* comp) { + FloatRegister lhs = ToFloatRegister(comp->left()); + FloatRegister rhs = ToFloatRegister(comp->right()); + Register dest = ToRegister(comp->output()); + Assembler::DoubleCondition cond = + comp->mir()->jsop() == JSOp::StrictEq ? Assembler::DoubleEqual + : comp->mir()->jsop() == JSOp::StrictNe + ? Assembler::DoubleNotEqualOrUnordered + : JSOpToDoubleCondition(comp->mir()->jsop()); + + masm.as_fcmpu(lhs, rhs); + masm.ma_cmp_set_dbl(dest, cond); +} + +void CodeGenerator::visitCompareF(LCompareF* comp) { + FloatRegister lhs = ToFloatRegister(comp->left()); + FloatRegister rhs = ToFloatRegister(comp->right()); + Register dest = ToRegister(comp->output()); + Assembler::DoubleCondition cond = + comp->mir()->jsop() == JSOp::StrictEq ? Assembler::DoubleEqual + : comp->mir()->jsop() == JSOp::StrictNe + ? Assembler::DoubleNotEqualOrUnordered + : JSOpToDoubleCondition(comp->mir()->jsop()); + + masm.as_fcmpu(lhs, rhs); + masm.ma_cmp_set_dbl(dest, cond); +} + +void CodeGenerator::visitCompareDAndBranch(LCompareDAndBranch* comp) { + FloatRegister lhs = ToFloatRegister(comp->left()); + FloatRegister rhs = ToFloatRegister(comp->right()); + + Assembler::DoubleCondition cond = + JSOpToDoubleCondition(comp->cmpMir()->jsop()); + MBasicBlock* ifTrue = comp->ifTrue(); + MBasicBlock* ifFalse = comp->ifFalse(); + + if (isNextBlock(ifFalse->lir())) { + branchToBlock(Assembler::DoubleFloat, cond, lhs, rhs, ifTrue); + } else { + branchToBlock(Assembler::DoubleFloat, Assembler::InvertCondition(cond), lhs, + rhs, ifFalse); + jumpToBlock(ifTrue); + } +} + +void CodeGenerator::visitCompareFAndBranch(LCompareFAndBranch* comp) { + FloatRegister lhs = ToFloatRegister(comp->left()); + FloatRegister rhs = ToFloatRegister(comp->right()); + + Assembler::DoubleCondition cond = + JSOpToDoubleCondition(comp->cmpMir()->jsop()); + MBasicBlock* ifTrue = comp->ifTrue(); + MBasicBlock* ifFalse = comp->ifFalse(); + + if (isNextBlock(ifFalse->lir())) { + branchToBlock(Assembler::SingleFloat, cond, lhs, rhs, ifTrue); + } else { + branchToBlock(Assembler::SingleFloat, Assembler::InvertCondition(cond), lhs, + rhs, ifFalse); + jumpToBlock(ifTrue); + } +} + +void CodeGenerator::visitTestDAndBranch(LTestDAndBranch* test) { + FloatRegister input = ToFloatRegister(test->input()); + + MBasicBlock* ifTrue = test->ifTrue(); + MBasicBlock* ifFalse = test->ifFalse(); + + masm.loadConstantDouble(0.0, ScratchDoubleReg); + + if (isNextBlock(ifFalse->lir())) { + branchToBlock(Assembler::DoubleFloat, Assembler::DoubleNotEqual, input, + ScratchDoubleReg, ifTrue); + } else { + branchToBlock(Assembler::DoubleFloat, Assembler::DoubleEqualOrUnordered, + input, ScratchDoubleReg, ifFalse); + jumpToBlock(ifTrue); + } +} + +void CodeGenerator::visitTestFAndBranch(LTestFAndBranch* test) { + FloatRegister input = ToFloatRegister(test->input()); + + MBasicBlock* ifTrue = test->ifTrue(); + MBasicBlock* ifFalse = test->ifFalse(); + + masm.loadConstantFloat32(0.0f, ScratchFloat32Reg); + + if (isNextBlock(ifFalse->lir())) { + branchToBlock(Assembler::SingleFloat, Assembler::DoubleNotEqual, input, + ScratchFloat32Reg, ifTrue); + } else { + branchToBlock(Assembler::SingleFloat, Assembler::DoubleEqualOrUnordered, + input, ScratchFloat32Reg, ifFalse); + jumpToBlock(ifTrue); + } +} + +// =============================================================== +// Visitors: Truncation + +void CodeGenerator::visitTruncateDToInt32(LTruncateDToInt32* ins) { + emitTruncateDouble(ToFloatRegister(ins->input()), ToRegister(ins->output()), + ins->mir()); +} + +void CodeGenerator::visitTruncateFToInt32(LTruncateFToInt32* ins) { + emitTruncateFloat32(ToFloatRegister(ins->input()), ToRegister(ins->output()), + ins->mir()); +} + +// =============================================================== +// Visitors: Int64 / Wasm type conversions + +void CodeGenerator::visitExtendInt32ToInt64(LExtendInt32ToInt64* lir) { + Register64 output = ToOutRegister64(lir); + // LIRGenerator::visitExtendInt32ToInt64 reuses the input for the low half. + MOZ_ASSERT(ToRegister(lir->input()) == output.low); + + if (lir->mir()->isUnsigned()) { + masm.move32(Imm32(0), output.high); + } else { + masm.as_srawi(output.high, output.low, 31); + } +} + +void CodeGenerator::visitWrapInt64ToInt32(LWrapInt64ToInt32* lir) { + LInt64Allocation input = lir->input(); + Register output = ToRegister(lir->output()); + + if (lir->mir()->bottomHalf()) { + masm.move32(ToRegister(input.low()), output); + } else { + masm.move32(ToRegister(input.high()), output); + } +} + +void CodeGenerator::visitSignExtendInt64(LSignExtendInt64* lir) { + Register64 input = ToRegister64(lir->input()); + Register64 output = ToOutRegister64(lir); + + switch (lir->mir()->mode()) { + case MSignExtendInt64::Byte: + masm.as_extsb(output.low, input.low); + break; + case MSignExtendInt64::Half: + masm.as_extsh(output.low, input.low); + break; + case MSignExtendInt64::Word: + masm.move32(input.low, output.low); + break; + } + masm.as_srawi(output.high, output.low, 31); +} + +void CodeGenerator::visitWasmExtendU32Index(LWasmExtendU32Index*) { + MOZ_CRASH("64-bit only"); +} + +void CodeGenerator::visitWasmWrapU32Index(LWasmWrapU32Index* lir) { + // No code: the result is just the low word of the input pair. + MOZ_ASSERT(ToRegister(lir->input()) == ToRegister(lir->output())); +} + +void CodeGenerator::visitWasmTruncateToInt32(LWasmTruncateToInt32* lir) { + auto input = ToFloatRegister(lir->input()); + auto output = ToRegister(lir->output()); + + MWasmTruncateToInt32* mir = lir->mir(); + MIRType fromType = mir->input()->type(); + + MOZ_ASSERT(fromType == MIRType::Double || fromType == MIRType::Float32); + + auto* ool = new (alloc()) OutOfLineWasmTruncateCheck(mir, input, output); + addOutOfLineCode(ool, mir); + + Label* oolEntry = ool->entry(); + if (mir->isUnsigned()) { + if (fromType == MIRType::Double) { + masm.wasmTruncateDoubleToUInt32(input, output, mir->isSaturating(), + oolEntry); + } else if (fromType == MIRType::Float32) { + masm.wasmTruncateFloat32ToUInt32(input, output, mir->isSaturating(), + oolEntry); + } else { + MOZ_CRASH("unexpected type"); + } + + masm.bind(ool->rejoin()); + return; + } + + if (fromType == MIRType::Double) { + masm.wasmTruncateDoubleToInt32(input, output, mir->isSaturating(), + oolEntry); + } else if (fromType == MIRType::Float32) { + masm.wasmTruncateFloat32ToInt32(input, output, mir->isSaturating(), + oolEntry); + } else { + MOZ_CRASH("unexpected type"); + } + + masm.bind(ool->rejoin()); +} + +void CodeGenerator::visitWasmTruncateToInt64(LWasmTruncateToInt64* lir) { + FloatRegister input = ToFloatRegister(lir->input()); + Register64 output = ToOutRegister64(lir); + + MWasmTruncateToInt64* mir = lir->mir(); + MIRType fromType = mir->input()->type(); + + MOZ_ASSERT(fromType == MIRType::Double || fromType == MIRType::Float32); + + auto* ool = new (alloc()) OutOfLineWasmTruncateCheck(mir, input, output); + addOutOfLineCode(ool, mir); + + Label* oolEntry = ool->entry(); + Label* oolRejoin = ool->rejoin(); + bool isSaturating = mir->isSaturating(); + + // There is no fctidz here, so the macro assembler does the conversion in + // software. Lowering allocates no temp, so hand it ScratchDoubleReg. + if (fromType == MIRType::Double) { + if (mir->isUnsigned()) { + masm.wasmTruncateDoubleToUInt64(input, output, isSaturating, oolEntry, + oolRejoin, ScratchDoubleReg); + } else { + masm.wasmTruncateDoubleToInt64(input, output, isSaturating, oolEntry, + oolRejoin, ScratchDoubleReg); + } + } else { + if (mir->isUnsigned()) { + masm.wasmTruncateFloat32ToUInt64(input, output, isSaturating, oolEntry, + oolRejoin, ScratchDoubleReg); + } else { + masm.wasmTruncateFloat32ToInt64(input, output, isSaturating, oolEntry, + oolRejoin, ScratchDoubleReg); + } + } +} + +void CodeGenerator::visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir) { + Register64 input = ToRegister64(lir->input()); + FloatRegister output = ToFloatRegister(lir->output()); + MIRType outputType = lir->mir()->type(); + + // Software conversion (no fcfid), and Lowering allocates no temp, so pull + // the scratch GPR the unsigned paths need from the assembler's own pool. + UseScratchRegisterScope temps(masm); + Register temp = + masm.convertUInt64ToDoubleNeedsTemp() ? temps.Acquire() : InvalidReg; + + if (outputType == MIRType::Double) { + if (lir->mir()->isUnsigned()) { + masm.convertUInt64ToDouble(input, output, temp); + } else { + masm.convertInt64ToDouble(input, output); + } + } else { + if (lir->mir()->isUnsigned()) { + masm.convertUInt64ToFloat32(input, output, temp); + } else { + masm.convertInt64ToFloat32(input, output); + } + } +} + +void CodeGenerator::visitWasmUint32ToDouble(LWasmUint32ToDouble* lir) { + Register input = ToRegister(lir->input()); + FloatRegister output = ToFloatRegister(lir->output()); + masm.convertUInt32ToDouble(input, output); +} + +void CodeGenerator::visitWasmUint32ToFloat32(LWasmUint32ToFloat32* lir) { + Register input = ToRegister(lir->input()); + FloatRegister output = ToFloatRegister(lir->output()); + masm.convertUInt32ToFloat32(input, output); +} + +void CodeGenerator::visitWasmBuiltinTruncateDToInt32( + LWasmBuiltinTruncateDToInt32* lir) { + emitTruncateDouble(ToFloatRegister(lir->getOperand(0)), + ToRegister(lir->getDef(0)), lir->mir()); +} + +void CodeGenerator::visitWasmBuiltinTruncateFToInt32( + LWasmBuiltinTruncateFToInt32* lir) { + emitTruncateFloat32(ToFloatRegister(lir->getOperand(0)), + ToRegister(lir->getDef(0)), lir->mir()); +} + +// =============================================================== +// Visitors: Wasm load/store + +// The wasm index is already a 32-bit value in a 32-bit register, so none of +// these need the zero-extension step the 64-bit backends perform. +template +void CodeGeneratorPPC::emitWasmLoad(T* lir) { + const MWasmLoad* mir = lir->mir(); + + masm.wasmLoad(mir->access(), ToRegister(lir->memoryBase()), + ToRegister(lir->ptr()), ToTempRegisterOrInvalid(lir->temp0()), + ToAnyRegister(lir->output())); +} + +template +void CodeGeneratorPPC::emitWasmStore(T* lir) { + const MWasmStore* mir = lir->mir(); + + masm.wasmStore(mir->access(), ToAnyRegister(lir->value()), + ToRegister(lir->memoryBase()), ToRegister(lir->ptr()), + ToTempRegisterOrInvalid(lir->temp0())); +} + +void CodeGenerator::visitWasmLoad(LWasmLoad* lir) { emitWasmLoad(lir); } + +void CodeGenerator::visitWasmStore(LWasmStore* lir) { emitWasmStore(lir); } + +void CodeGenerator::visitWasmLoadI64(LWasmLoadI64* lir) { + const MWasmLoad* mir = lir->mir(); + + masm.wasmLoadI64(mir->access(), ToRegister(lir->memoryBase()), + ToRegister(lir->ptr()), + ToTempRegisterOrInvalid(lir->temp0()), ToOutRegister64(lir)); +} + +void CodeGenerator::visitWasmStoreI64(LWasmStoreI64* lir) { + const MWasmStore* mir = lir->mir(); + + masm.wasmStoreI64(mir->access(), ToRegister64(lir->value()), + ToRegister(lir->memoryBase()), ToRegister(lir->ptr()), + ToTempRegisterOrInvalid(lir->temp0())); +} + +void CodeGenerator::visitAsmJSLoadHeap(LAsmJSLoadHeap* ins) { + const MAsmJSLoadHeap* mir = ins->mir(); + MOZ_ASSERT(!mir->hasMemoryBase()); + + const LAllocation* ptr = ins->ptr(); + const LDefinition* output = ins->output(); + const LAllocation* boundsCheckLimit = ins->boundsCheckLimit(); + + Register ptrReg = ToRegister(ptr); + Scalar::Type accessType = mir->accessType(); + bool isFloat = accessType == Scalar::Float32 || accessType == Scalar::Float64; + Label done; + + if (mir->needsBoundsCheck()) { + Label boundsCheckPassed; + Register boundsCheckLimitReg = ToRegister(boundsCheckLimit); + masm.wasmBoundsCheck32(Assembler::Below, ptrReg, boundsCheckLimitReg, + &boundsCheckPassed); + if (isFloat) { + if (accessType == Scalar::Float32) { + masm.loadConstantFloat32(GenericNaN(), ToFloatRegister(output)); + } else { + masm.loadConstantDouble(GenericNaN(), ToFloatRegister(output)); + } + } else { + masm.movePtr(ImmWord(0), ToRegister(output)); + } + masm.jump(&done); + masm.bind(&boundsCheckPassed); + } + + switch (accessType) { + case Scalar::Int8: + masm.as_lbzx(ToRegister(output), HeapReg, ptrReg); + masm.as_extsb(ToRegister(output), ToRegister(output)); + break; + case Scalar::Uint8: + masm.as_lbzx(ToRegister(output), HeapReg, ptrReg); + break; + case Scalar::Int16: + masm.as_lhax(ToRegister(output), HeapReg, ptrReg); + break; + case Scalar::Uint16: + masm.as_lhzx(ToRegister(output), HeapReg, ptrReg); + break; + case Scalar::Int32: + case Scalar::Uint32: + masm.as_lwzx(ToRegister(output), HeapReg, ptrReg); + break; + case Scalar::Float64: + masm.as_lfdx(ToFloatRegister(output), HeapReg, ptrReg); + break; + case Scalar::Float32: + masm.as_lfsx(ToFloatRegister(output), HeapReg, ptrReg); + break; + default: + MOZ_CRASH("unexpected array type"); + } + + if (done.used()) { + masm.bind(&done); + } +} + +void CodeGenerator::visitAsmJSStoreHeap(LAsmJSStoreHeap* ins) { + const MAsmJSStoreHeap* mir = ins->mir(); + MOZ_ASSERT(!mir->hasMemoryBase()); + + const LAllocation* value = ins->value(); + const LAllocation* ptr = ins->ptr(); + const LAllocation* boundsCheckLimit = ins->boundsCheckLimit(); + + Register ptrReg = ToRegister(ptr); + + Label done; + if (mir->needsBoundsCheck()) { + Register boundsCheckLimitReg = ToRegister(boundsCheckLimit); + masm.wasmBoundsCheck32(Assembler::AboveOrEqual, ptrReg, boundsCheckLimitReg, + &done); + } + + switch (mir->accessType()) { + case Scalar::Int8: + case Scalar::Uint8: + masm.as_stbx(ToRegister(value), HeapReg, ptrReg); + break; + case Scalar::Int16: + case Scalar::Uint16: + masm.as_sthx(ToRegister(value), HeapReg, ptrReg); + break; + case Scalar::Int32: + case Scalar::Uint32: + masm.as_stwx(ToRegister(value), HeapReg, ptrReg); + break; + case Scalar::Float64: + masm.as_stfdx(ToFloatRegister(value), HeapReg, ptrReg); + break; + case Scalar::Float32: + masm.as_stfsx(ToFloatRegister(value), HeapReg, ptrReg); + break; + default: + MOZ_CRASH("unexpected array type"); + } + + if (done.used()) { + masm.bind(&done); + } +} + +void CodeGenerator::visitWasmStackArg(LWasmStackArg* ins) { + const MWasmStackArg* mir = ins->mir(); + if (ins->arg()->isConstant()) { + masm.store32(Imm32(ToInt32(ins->arg())), + Address(StackPointer, mir->spOffset())); + } else { + if (ins->arg()->isGeneralReg()) { + masm.store32(ToRegister(ins->arg()), + Address(StackPointer, mir->spOffset())); + } else if (mir->input()->type() == MIRType::Double) { + masm.storeDouble(ToFloatRegister(ins->arg()), + Address(StackPointer, mir->spOffset())); +#ifdef ENABLE_WASM_SIMD + } else if (mir->input()->type() == MIRType::Simd128) { + masm.storeUnalignedSimd128(ToFloatRegister(ins->arg()), + Address(StackPointer, mir->spOffset())); +#endif + } else { + masm.storeFloat32(ToFloatRegister(ins->arg()), + Address(StackPointer, mir->spOffset())); + } + } +} + +void CodeGenerator::visitWasmStackArgI64(LWasmStackArgI64* ins) { + const MWasmStackArg* mir = ins->mir(); + Address dst(StackPointer, mir->spOffset()); + if (IsConstant(ins->arg())) { + masm.store64(Imm64(ToInt64(ins->arg())), dst); + } else { + masm.store64(ToRegister64(ins->arg()), dst); + } +} + +void CodeGenerator::visitWasmSelect(LWasmSelect* ins) { + MIRType mirType = ins->mir()->type(); + + Register cond = ToRegister(ins->condExpr()); + const LAllocation* falseExpr = ins->falseExpr(); + + if (mirType == MIRType::Int32 || mirType == MIRType::WasmAnyRef) { + Register out = ToRegister(ins->output()); + MOZ_ASSERT(ToRegister(ins->trueExpr()) == out, + "true expr input is reused for output"); + if (falseExpr->isGeneralReg()) { + masm.moveIfZero(out, ToRegister(falseExpr), cond); + } else { + masm.cmp32Load32(Assembler::Zero, cond, cond, ToAddress(falseExpr), out); + } + return; + } + + FloatRegister out = ToFloatRegister(ins->output()); + MOZ_ASSERT(ToFloatRegister(ins->trueExpr()) == out, + "true expr input is reused for output"); + + if (falseExpr->isFloatReg()) { + Label done; + // The select condition is a 32-bit value; test 32 bits so high-bit garbage + // does not make a zero condition read as non-zero. + masm.branchTest32(Assembler::NonZero, cond, cond, &done); + if (mirType == MIRType::Float32) { + masm.moveFloat32(ToFloatRegister(falseExpr), out); + } else if (mirType == MIRType::Double) { + masm.moveDouble(ToFloatRegister(falseExpr), out); + } else if (mirType == MIRType::Simd128) { + masm.moveSimd128(ToFloatRegister(falseExpr), out); + } else { + MOZ_CRASH("unhandled type in visitWasmSelect!"); + } + masm.bind(&done); + } else { + Label done; + // The select condition is a 32-bit value; test 32 bits so high-bit garbage + // does not make a zero condition read as non-zero. + masm.branchTest32(Assembler::NonZero, cond, cond, &done); + + if (mirType == MIRType::Float32) { + masm.loadFloat32(ToAddress(falseExpr), out); + } else if (mirType == MIRType::Double) { + masm.loadDouble(ToAddress(falseExpr), out); + } else if (mirType == MIRType::Simd128) { + masm.loadUnalignedSimd128(ToAddress(falseExpr), out); + } else { + MOZ_CRASH("unhandled type in visitWasmSelect!"); + } + + masm.bind(&done); + } +} + +void CodeGenerator::visitWasmSelectI64(LWasmSelectI64* lir) { + MOZ_ASSERT(lir->mir()->type() == MIRType::Int64); + + Register cond = ToRegister(lir->condExpr()); + Register64 falseExpr = ToRegister64(lir->falseExpr()); + Register64 out = ToOutRegister64(lir); + MOZ_ASSERT(ToRegister64(lir->trueExpr()) == out, + "true expr is reused for input"); + + // The select condition is a 32-bit value; test 32 bits so high-bit garbage + // does not make a zero condition read as non-zero. + Label done; + masm.branchTest32(Assembler::NonZero, cond, cond, &done); + masm.move64(falseExpr, out); + masm.bind(&done); +} + +void CodeGenerator::visitWasmCompareAndSelect(LWasmCompareAndSelect* ins) { + MCompare::CompareType compTy = ins->compareType(); + + // canSpecializeWasmCompareAndSelect only accepts an Int32 select on an + // {U,}Int32 compare. + MOZ_RELEASE_ASSERT(compTy == MCompare::Compare_Int32 || + compTy == MCompare::Compare_UInt32); + MOZ_RELEASE_ASSERT(ins->mir()->type() == MIRType::Int32); + + Register trueExprAndDest = ToRegister(ins->output()); + MOZ_ASSERT(ToRegister(ins->ifTrueExpr()) == trueExprAndDest, + "true expr input is reused for output"); + + Assembler::Condition cond = JSOpToCondition(compTy, ins->jsop()); + Register lhs = ToRegister(ins->leftExpr()); + Register rhs = ToRegister(ins->rightExpr()); + Register falseExpr = ToRegister(ins->ifFalseExpr()); + + // No isel on this target: branch over the move of the false value. + Label done; + masm.branch32(cond, lhs, rhs, &done); + masm.move32(falseExpr, trueExprAndDest); + masm.bind(&done); +} + +void CodeGenerator::visitWasmAddOffset(LWasmAddOffset* lir) { + MWasmAddOffset* mir = lir->mir(); + Register base = ToRegister(lir->base()); + Register out = ToRegister(lir->output()); + + Label ok; + masm.ma_add32TestCarry(Assembler::CarryClear, out, base, Imm32(mir->offset()), + &ok); + masm.wasmTrap(wasm::Trap::OutOfBounds, mir->trapSiteDesc()); + masm.bind(&ok); +} + +void CodeGenerator::visitWasmAddOffset64(LWasmAddOffset64* lir) { + MWasmAddOffset* mir = lir->mir(); + Register64 base = ToRegister64(lir->base()); + Register64 out = ToOutRegister64(lir); + + Label ok, trap; + { + // The output pair may alias the input pair, so keep a copy of the original + // value to detect the 64-bit unsigned wrap-around. + UseScratchRegisterScope temps(masm); + Register origLow = temps.Acquire(); + Register origHigh = temps.Acquire(); + masm.move32(base.low, origLow); + masm.move32(base.high, origHigh); + + masm.move64(base, out); + masm.add64(Imm64(mir->offset()), out); + + masm.branch32(Assembler::Above, out.high, origHigh, &ok); + masm.branch32(Assembler::Below, out.high, origHigh, &trap); + masm.branch32(Assembler::AboveOrEqual, out.low, origLow, &ok); + } + masm.bind(&trap); + masm.wasmTrap(wasm::Trap::OutOfBounds, mir->trapSiteDesc()); + masm.bind(&ok); +} + +// =============================================================== +// Visitors: Effective Address + +void CodeGenerator::visitEffectiveAddress2(LEffectiveAddress2* ins) { + const MEffectiveAddress2* mir = ins->mir(); + Register output = ToRegister(ins->output()); + + // EA = index * scale + displacement (no base register) + masm.movePtr(ImmWord(0), output); + BaseIndex addr(output, ToRegister(ins->index()), mir->scale(), + mir->displacement()); + masm.computeEffectiveAddress(addr, output); +} + +void CodeGenerator::visitEffectiveAddress3(LEffectiveAddress3* ins) { + const MEffectiveAddress3* mir = ins->mir(); + Register output = ToRegister(ins->output()); + + BaseIndex addr(ToRegister(ins->base()), ToRegister(ins->index()), + mir->scale(), mir->displacement()); + masm.computeEffectiveAddress(addr, output); +} + +// =============================================================== +// Visitors: Typed Array Atomics + +void CodeGenerator::visitCompareExchangeTypedArrayElement( + LCompareExchangeTypedArrayElement* lir) { + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register outTemp = ToTempRegisterOrInvalid(lir->temp0()); + + Register oldval = ToRegister(lir->oldval()); + Register newval = ToRegister(lir->newval()); + Register valueTemp = ToTempRegisterOrInvalid(lir->temp1()); + Register offsetTemp = ToTempRegisterOrInvalid(lir->temp2()); + Register maskTemp = ToTempRegisterOrInvalid(lir->temp3()); + Scalar::Type arrayType = lir->mir()->arrayType(); + + auto dest = ToAddressOrBaseIndex(elements, lir->index(), arrayType); + + dest.match([&](const auto& dest) { + masm.compareExchangeJS(arrayType, Synchronization::Full(), dest, oldval, + newval, valueTemp, offsetTemp, maskTemp, outTemp, + output); + }); +} + +void CodeGenerator::visitAtomicExchangeTypedArrayElement( + LAtomicExchangeTypedArrayElement* lir) { + Register elements = ToRegister(lir->elements()); + AnyRegister output = ToAnyRegister(lir->output()); + Register outTemp = ToTempRegisterOrInvalid(lir->temp0()); + + Register value = ToRegister(lir->value()); + Register valueTemp = ToTempRegisterOrInvalid(lir->temp1()); + Register offsetTemp = ToTempRegisterOrInvalid(lir->temp2()); + Register maskTemp = ToTempRegisterOrInvalid(lir->temp3()); + Scalar::Type arrayType = lir->mir()->arrayType(); + + auto dest = ToAddressOrBaseIndex(elements, lir->index(), arrayType); + + dest.match([&](const auto& dest) { + masm.atomicExchangeJS(arrayType, Synchronization::Full(), dest, value, + valueTemp, offsetTemp, maskTemp, outTemp, output); + }); +} + +void CodeGenerator::visitAtomicTypedArrayElementBinop( + LAtomicTypedArrayElementBinop* lir) { + MOZ_ASSERT(!lir->mir()->isForEffect()); + + AnyRegister output = ToAnyRegister(lir->output()); + Register elements = ToRegister(lir->elements()); + Register outTemp = ToTempRegisterOrInvalid(lir->temp0()); + Register valueTemp = ToTempRegisterOrInvalid(lir->temp1()); + Register offsetTemp = ToTempRegisterOrInvalid(lir->temp2()); + Register maskTemp = ToTempRegisterOrInvalid(lir->temp3()); + Register value = ToRegister(lir->value()); + Scalar::Type arrayType = lir->mir()->arrayType(); + + auto mem = ToAddressOrBaseIndex(elements, lir->index(), arrayType); + + mem.match([&](const auto& mem) { + masm.atomicFetchOpJS(arrayType, Synchronization::Full(), + lir->mir()->operation(), value, mem, valueTemp, + offsetTemp, maskTemp, outTemp, output); + }); +} + +void CodeGenerator::visitAtomicTypedArrayElementBinopForEffect( + LAtomicTypedArrayElementBinopForEffect* lir) { + MOZ_ASSERT(lir->mir()->isForEffect()); + + Register elements = ToRegister(lir->elements()); + Register valueTemp = ToTempRegisterOrInvalid(lir->temp0()); + Register offsetTemp = ToTempRegisterOrInvalid(lir->temp1()); + Register maskTemp = ToTempRegisterOrInvalid(lir->temp2()); + Register value = ToRegister(lir->value()); + Scalar::Type arrayType = lir->mir()->arrayType(); + + auto mem = ToAddressOrBaseIndex(elements, lir->index(), arrayType); + + mem.match([&](const auto& mem) { + masm.atomicEffectOpJS(arrayType, Synchronization::Full(), + lir->mir()->operation(), value, mem, valueTemp, + offsetTemp, maskTemp); + }); +} + +void CodeGenerator::visitCompareExchangeTypedArrayElement64( + LCompareExchangeTypedArrayElement64* lir) { + Register elements = ToRegister(lir->elements()); + Register64 oldval = ToRegister64(lir->oldval()); + Register64 newval = ToRegister64(lir->newval()); + Register64 out = ToOutRegister64(lir); + Scalar::Type arrayType = lir->mir()->arrayType(); + + auto dest = ToAddressOrBaseIndex(elements, lir->index(), arrayType); + + dest.match([&](const auto& dest) { + masm.compareExchange64(Synchronization::Full(), dest, oldval, newval, out); + }); +} + +void CodeGenerator::visitAtomicExchangeTypedArrayElement64( + LAtomicExchangeTypedArrayElement64* lir) { + Register elements = ToRegister(lir->elements()); + Register64 value = ToRegister64(lir->value()); + Register64 out = ToOutRegister64(lir); + Scalar::Type arrayType = lir->mir()->arrayType(); + + auto dest = ToAddressOrBaseIndex(elements, lir->index(), arrayType); + + dest.match([&](const auto& dest) { + masm.atomicExchange64(Synchronization::Full(), dest, value, out); + }); +} + +void CodeGenerator::visitAtomicTypedArrayElementBinop64( + LAtomicTypedArrayElementBinop64* lir) { + MOZ_ASSERT(lir->mir()->hasUses()); + + Register elements = ToRegister(lir->elements()); + Register64 value = ToRegister64(lir->value()); + Register64 temp = ToRegister64(lir->temp0()); + Register64 out = ToOutRegister64(lir); + + Scalar::Type arrayType = lir->mir()->arrayType(); + AtomicOp atomicOp = lir->mir()->operation(); + + auto dest = ToAddressOrBaseIndex(elements, lir->index(), arrayType); + + dest.match([&](const auto& dest) { + masm.atomicFetchOp64(Synchronization::Full(), atomicOp, value, dest, temp, + out); + }); +} + +void CodeGenerator::visitAtomicTypedArrayElementBinopForEffect64( + LAtomicTypedArrayElementBinopForEffect64* lir) { + MOZ_ASSERT(!lir->mir()->hasUses()); + + Register elements = ToRegister(lir->elements()); + Register64 value = ToRegister64(lir->value()); + Register64 temp = ToRegister64(lir->temp0()); + + Scalar::Type arrayType = lir->mir()->arrayType(); + AtomicOp atomicOp = lir->mir()->operation(); + + auto dest = ToAddressOrBaseIndex(elements, lir->index(), arrayType); + + dest.match([&](const auto& dest) { + masm.atomicEffectOp64(Synchronization::Full(), atomicOp, value, dest, temp); + }); +} + +void CodeGenerator::visitAtomicLoad64(LAtomicLoad64* lir) { + Register elements = ToRegister(lir->elements()); + Register64 out = ToOutRegister64(lir); + Scalar::Type storageType = lir->mir()->storageType(); + + auto source = ToAddressOrBaseIndex(elements, lir->index(), storageType); + + auto sync = Synchronization::Load(); + masm.memoryBarrierBefore(sync); + source.match([&](const auto& source) { masm.load64(source, out); }); + masm.memoryBarrierAfter(sync); +} + +void CodeGenerator::visitAtomicStore64(LAtomicStore64* lir) { + Register elements = ToRegister(lir->elements()); + Register64 value = ToRegister64(lir->value()); + Scalar::Type writeType = lir->mir()->writeType(); + + auto dest = ToAddressOrBaseIndex(elements, lir->index(), writeType); + + auto sync = Synchronization::Store(); + masm.memoryBarrierBefore(sync); + dest.match([&](const auto& dest) { masm.store64(value, dest); }); + masm.memoryBarrierAfter(sync); +} + +// Wasm Atomics +void CodeGenerator::visitWasmCompareExchangeHeap( + LWasmCompareExchangeHeap* ins) { + MWasmCompareExchangeHeap* mir = ins->mir(); + Register memoryBase = ToRegister(ins->memoryBase()); + Register ptrReg = ToRegister(ins->ptr()); + BaseIndex srcAddr(memoryBase, ptrReg, TimesOne, mir->access().offset32()); + + Register oldval = ToRegister(ins->oldValue()); + Register newval = ToRegister(ins->newValue()); + Register valueTemp = ToTempRegisterOrInvalid(ins->temp0()); + Register offsetTemp = ToTempRegisterOrInvalid(ins->temp1()); + Register maskTemp = ToTempRegisterOrInvalid(ins->temp2()); + + masm.wasmCompareExchange(mir->access(), srcAddr, oldval, newval, valueTemp, + offsetTemp, maskTemp, ToRegister(ins->output())); +} + +void CodeGenerator::visitWasmAtomicExchangeHeap(LWasmAtomicExchangeHeap* ins) { + MWasmAtomicExchangeHeap* mir = ins->mir(); + Register memoryBase = ToRegister(ins->memoryBase()); + Register ptrReg = ToRegister(ins->ptr()); + Register value = ToRegister(ins->value()); + BaseIndex srcAddr(memoryBase, ptrReg, TimesOne, mir->access().offset32()); + + Register valueTemp = ToTempRegisterOrInvalid(ins->temp0()); + Register offsetTemp = ToTempRegisterOrInvalid(ins->temp1()); + Register maskTemp = ToTempRegisterOrInvalid(ins->temp2()); + + masm.wasmAtomicExchange(mir->access(), srcAddr, value, valueTemp, offsetTemp, + maskTemp, ToRegister(ins->output())); +} + +void CodeGenerator::visitWasmAtomicBinopHeap(LWasmAtomicBinopHeap* ins) { + MOZ_ASSERT(ins->mir()->hasUses()); + + MWasmAtomicBinopHeap* mir = ins->mir(); + Register memoryBase = ToRegister(ins->memoryBase()); + Register ptrReg = ToRegister(ins->ptr()); + Register valueTemp = ToTempRegisterOrInvalid(ins->temp0()); + Register offsetTemp = ToTempRegisterOrInvalid(ins->temp1()); + Register maskTemp = ToTempRegisterOrInvalid(ins->temp2()); + + BaseIndex srcAddr(memoryBase, ptrReg, TimesOne, mir->access().offset32()); + + masm.wasmAtomicFetchOp(mir->access(), mir->operation(), + ToRegister(ins->value()), srcAddr, valueTemp, + offsetTemp, maskTemp, ToRegister(ins->output())); +} + +void CodeGenerator::visitWasmAtomicBinopHeapForEffect( + LWasmAtomicBinopHeapForEffect* ins) { + MOZ_ASSERT(!ins->mir()->hasUses()); + + MWasmAtomicBinopHeap* mir = ins->mir(); + Register memoryBase = ToRegister(ins->memoryBase()); + Register ptrReg = ToRegister(ins->ptr()); + Register valueTemp = ToTempRegisterOrInvalid(ins->temp0()); + Register offsetTemp = ToTempRegisterOrInvalid(ins->temp1()); + Register maskTemp = ToTempRegisterOrInvalid(ins->temp2()); + + BaseIndex srcAddr(memoryBase, ptrReg, TimesOne, mir->access().offset32()); + masm.wasmAtomicEffectOp(mir->access(), mir->operation(), + ToRegister(ins->value()), srcAddr, valueTemp, + offsetTemp, maskTemp); +} + +void CodeGenerator::visitWasmCompareExchangeI64(LWasmCompareExchangeI64* lir) { + Register memoryBase = ToRegister(lir->memoryBase()); + Register ptr = ToRegister(lir->ptr()); + Register64 oldValue = ToRegister64(lir->oldValue()); + Register64 newValue = ToRegister64(lir->newValue()); + Register64 output = ToOutRegister64(lir); + uint32_t offset = lir->mir()->access().offset32(); + + BaseIndex addr(memoryBase, ptr, TimesOne, offset); + masm.wasmCompareExchange64(lir->mir()->access(), addr, oldValue, newValue, + output); +} + +void CodeGenerator::visitWasmAtomicExchangeI64(LWasmAtomicExchangeI64* lir) { + Register memoryBase = ToRegister(lir->memoryBase()); + Register ptr = ToRegister(lir->ptr()); + Register64 value = ToRegister64(lir->value()); + Register64 output = ToOutRegister64(lir); + uint32_t offset = lir->mir()->access().offset32(); + + BaseIndex addr(memoryBase, ptr, TimesOne, offset); + masm.wasmAtomicExchange64(lir->mir()->access(), addr, value, output); +} + +void CodeGenerator::visitWasmAtomicBinopI64(LWasmAtomicBinopI64* lir) { + Register memoryBase = ToRegister(lir->memoryBase()); + Register ptr = ToRegister(lir->ptr()); + Register64 value = ToRegister64(lir->value()); + Register64 output = ToOutRegister64(lir); + Register64 temp = ToRegister64(lir->temp0()); + uint32_t offset = lir->mir()->access().offset32(); + + BaseIndex addr(memoryBase, ptr, TimesOne, offset); + + masm.wasmAtomicFetchOp64(lir->mir()->access(), lir->mir()->operation(), value, + addr, temp, output); +} + +// SIMD code generators. +void CodeGenerator::visitSimd128(LSimd128* ins) { + FloatRegister dest = ToFloatRegister(ins->output()); + masm.loadConstantSimd128(ins->simd128(), dest); +} +void CodeGenerator::visitWasmTernarySimd128(LWasmTernarySimd128* ins) { + FloatRegister v0 = ToFloatRegister(ins->v0()); + FloatRegister v1 = ToFloatRegister(ins->v1()); + FloatRegister v2 = ToFloatRegister(ins->v2()); + FloatRegister dest = ToFloatRegister(ins->output()); + switch (ins->simdOp()) { + case wasm::SimdOp::V128Bitselect: + // bitselect(v0, v1, v2): result = (v0 & v2) | (v1 & ~v2) + // vsel: vD = (vA & ~vC) | (vB & vC), so vA=v1, vB=v0, vC=v2. + masm.as_vsel(dest, v1, v0, v2); + break; + case wasm::SimdOp::I8x16RelaxedLaneSelect: + case wasm::SimdOp::I16x8RelaxedLaneSelect: + case wasm::SimdOp::I32x4RelaxedLaneSelect: + case wasm::SimdOp::I64x2RelaxedLaneSelect: + // relaxed laneSelect(v0, v1, mask=v2): same as bitselect + masm.as_vsel(dest, v1, v0, v2); + break; + // Lowering uses defineReuseInput on V2Index for ternary ops — the + // allocator is required to place `dest` in v2's slot. Assert that + // here; the FMA/dot helpers write their result through v2 in-place, + // so dest == v2 makes the trailing moveSimd128 unnecessary. + case wasm::SimdOp::I32x4RelaxedDotI8x16I7x16AddS: + MOZ_ASSERT(dest == v2); + masm.dotInt8x16Int7x16ThenAdd(v0, v1, v2, + ToFloatRegister(ins->temp0())); + break; + case wasm::SimdOp::F32x4RelaxedMadd: + MOZ_ASSERT(dest == v2); + masm.fmaFloat32x4(v0, v1, v2); + break; + case wasm::SimdOp::F64x2RelaxedMadd: + MOZ_ASSERT(dest == v2); + masm.fmaFloat64x2(v0, v1, v2); + break; + case wasm::SimdOp::F32x4RelaxedNmadd: + MOZ_ASSERT(dest == v2); + masm.fnmaFloat32x4(v0, v1, v2); + break; + case wasm::SimdOp::F64x2RelaxedNmadd: + MOZ_ASSERT(dest == v2); + masm.fnmaFloat64x2(v0, v1, v2); + break; + default: + MOZ_CRASH("PPC: NYI SIMD ternary op"); + } +} +void CodeGenerator::visitWasmBinarySimd128(LWasmBinarySimd128* ins) { + FloatRegister lhs = ToFloatRegister(ins->lhs()); + FloatRegister rhs = ToFloatRegister(ins->rhs()); + FloatRegister dest = ToFloatRegister(ins->output()); + switch (ins->simdOp()) { + // Bitwise + case wasm::SimdOp::V128And: + masm.bitwiseAndSimd128(lhs, rhs, dest); + break; + case wasm::SimdOp::V128Or: + masm.bitwiseOrSimd128(lhs, rhs, dest); + break; + case wasm::SimdOp::V128Xor: + masm.bitwiseXorSimd128(lhs, rhs, dest); + break; + case wasm::SimdOp::V128AndNot: + masm.bitwiseAndNotSimd128(lhs, rhs, dest); + break; + // Integer add + case wasm::SimdOp::I8x16Add: + masm.addInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8Add: + masm.addInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4Add: + masm.addInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2Add: + masm.addInt64x2(lhs, rhs, dest); + break; + // Integer sub + case wasm::SimdOp::I8x16Sub: + masm.subInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8Sub: + masm.subInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4Sub: + masm.subInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2Sub: + masm.subInt64x2(lhs, rhs, dest); + break; + // Saturating add + case wasm::SimdOp::I8x16AddSatS: + masm.addSatInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16AddSatU: + masm.unsignedAddSatInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8AddSatS: + masm.addSatInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8AddSatU: + masm.unsignedAddSatInt16x8(lhs, rhs, dest); + break; + // Saturating sub + case wasm::SimdOp::I8x16SubSatS: + masm.subSatInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16SubSatU: + masm.unsignedSubSatInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8SubSatS: + masm.subSatInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8SubSatU: + masm.unsignedSubSatInt16x8(lhs, rhs, dest); + break; + // Integer multiply + case wasm::SimdOp::I16x8Mul: + masm.mulInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4Mul: + masm.mulInt32x4(lhs, rhs, dest); + break; + // Integer min/max signed + case wasm::SimdOp::I8x16MinS: + masm.minInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16MaxS: + masm.maxInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8MinS: + masm.minInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8MaxS: + masm.maxInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4MinS: + masm.minInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4MaxS: + masm.maxInt32x4(lhs, rhs, dest); + break; + // Integer min/max unsigned + case wasm::SimdOp::I8x16MinU: + masm.unsignedMinInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16MaxU: + masm.unsignedMaxInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8MinU: + masm.unsignedMinInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8MaxU: + masm.unsignedMaxInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4MinU: + masm.unsignedMinInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4MaxU: + masm.unsignedMaxInt32x4(lhs, rhs, dest); + break; + // Average unsigned + case wasm::SimdOp::I8x16AvgrU: + masm.unsignedAverageInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8AvgrU: + masm.unsignedAverageInt16x8(lhs, rhs, dest); + break; + // Q15 multiply + case wasm::SimdOp::I16x8Q15MulrSatS: + masm.q15MulrSatInt16x8(lhs, rhs, dest); + break; + // Integer compare + case wasm::SimdOp::I8x16Eq: + masm.compareInt8x16(Assembler::Equal, lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16Ne: + masm.compareInt8x16(Assembler::NotEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16LtS: + masm.compareInt8x16(Assembler::LessThan, lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16GtS: + masm.compareInt8x16(Assembler::GreaterThan, lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16LeS: + masm.compareInt8x16(Assembler::LessThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16GeS: + masm.compareInt8x16(Assembler::GreaterThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16LtU: + masm.compareInt8x16(Assembler::Below, lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16GtU: + masm.compareInt8x16(Assembler::Above, lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16LeU: + masm.compareInt8x16(Assembler::BelowOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16GeU: + masm.compareInt8x16(Assembler::AboveOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8Eq: + masm.compareInt16x8(Assembler::Equal, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8Ne: + masm.compareInt16x8(Assembler::NotEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8LtS: + masm.compareInt16x8(Assembler::LessThan, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8GtS: + masm.compareInt16x8(Assembler::GreaterThan, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8LeS: + masm.compareInt16x8(Assembler::LessThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8GeS: + masm.compareInt16x8(Assembler::GreaterThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8LtU: + masm.compareInt16x8(Assembler::Below, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8GtU: + masm.compareInt16x8(Assembler::Above, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8LeU: + masm.compareInt16x8(Assembler::BelowOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8GeU: + masm.compareInt16x8(Assembler::AboveOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4Eq: + masm.compareInt32x4(Assembler::Equal, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4Ne: + masm.compareInt32x4(Assembler::NotEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4LtS: + masm.compareInt32x4(Assembler::LessThan, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4GtS: + masm.compareInt32x4(Assembler::GreaterThan, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4LeS: + masm.compareInt32x4(Assembler::LessThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4GeS: + masm.compareInt32x4(Assembler::GreaterThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4LtU: + masm.compareInt32x4(Assembler::Below, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4GtU: + masm.compareInt32x4(Assembler::Above, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4LeU: + masm.compareInt32x4(Assembler::BelowOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4GeU: + masm.compareInt32x4(Assembler::AboveOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2Eq: + masm.compareInt64x2(Assembler::Equal, lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2Ne: + masm.compareInt64x2(Assembler::NotEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2LtS: + masm.compareInt64x2(Assembler::LessThan, lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2GtS: + masm.compareInt64x2(Assembler::GreaterThan, lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2LeS: + masm.compareInt64x2(Assembler::LessThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2GeS: + masm.compareInt64x2(Assembler::GreaterThanOrEqual, lhs, rhs, dest); + break; + // Float compare + case wasm::SimdOp::F32x4Eq: + masm.compareFloat32x4(Assembler::Equal, lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4Ne: + masm.compareFloat32x4(Assembler::NotEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4Lt: + masm.compareFloat32x4(Assembler::LessThan, lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4Gt: + masm.compareFloat32x4(Assembler::GreaterThan, lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4Le: + masm.compareFloat32x4(Assembler::LessThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4Ge: + masm.compareFloat32x4(Assembler::GreaterThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Eq: + masm.compareFloat64x2(Assembler::Equal, lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Ne: + masm.compareFloat64x2(Assembler::NotEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Lt: + masm.compareFloat64x2(Assembler::LessThan, lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Gt: + masm.compareFloat64x2(Assembler::GreaterThan, lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Le: + masm.compareFloat64x2(Assembler::LessThanOrEqual, lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Ge: + masm.compareFloat64x2(Assembler::GreaterThanOrEqual, lhs, rhs, dest); + break; + // Float arithmetic + case wasm::SimdOp::F32x4Add: + masm.addFloat32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4Sub: + masm.subFloat32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4Mul: + masm.mulFloat32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4Div: + masm.divFloat32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4Min: + masm.minFloat32x4(lhs, rhs, dest, ToFloatRegister(ins->getTemp(0)), + ToFloatRegister(ins->getTemp(1))); + break; + case wasm::SimdOp::F32x4Max: + masm.maxFloat32x4(lhs, rhs, dest, ToFloatRegister(ins->getTemp(0)), + ToFloatRegister(ins->getTemp(1))); + break; + case wasm::SimdOp::F32x4PMin: + masm.pseudoMinFloat32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::F32x4PMax: + masm.pseudoMaxFloat32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Add: + masm.addFloat64x2(lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Sub: + masm.subFloat64x2(lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Mul: + masm.mulFloat64x2(lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Div: + masm.divFloat64x2(lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2Min: + masm.minFloat64x2(lhs, rhs, dest, ToFloatRegister(ins->getTemp(0)), + ToFloatRegister(ins->getTemp(1))); + break; + case wasm::SimdOp::F64x2Max: + masm.maxFloat64x2(lhs, rhs, dest, ToFloatRegister(ins->getTemp(0)), + ToFloatRegister(ins->getTemp(1))); + break; + case wasm::SimdOp::F64x2PMin: + masm.pseudoMinFloat64x2(lhs, rhs, dest); + break; + case wasm::SimdOp::F64x2PMax: + masm.pseudoMaxFloat64x2(lhs, rhs, dest); + break; + // Narrow + case wasm::SimdOp::I8x16NarrowI16x8S: + masm.narrowInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16NarrowI16x8U: + masm.unsignedNarrowInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8NarrowI32x4S: + masm.narrowInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8NarrowI32x4U: + masm.unsignedNarrowInt32x4(lhs, rhs, dest); + break; + // i64 multiply + case wasm::SimdOp::I64x2Mul: { + FloatRegister temp0 = ToTempFloatRegisterOrInvalid(ins->temp0()); + FloatRegister temp1f = ToTempFloatRegisterOrInvalid(ins->temp1()); + masm.mulInt64x2(lhs, rhs, dest, temp0, temp1f); + break; + } + // Extended multiply + case wasm::SimdOp::I16x8ExtmulLowI8x16S: + masm.extMulLowInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8ExtmulHighI8x16S: + masm.extMulHighInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8ExtmulLowI8x16U: + masm.unsignedExtMulLowInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8ExtmulHighI8x16U: + masm.unsignedExtMulHighInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4ExtmulLowI16x8S: + masm.extMulLowInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4ExtmulHighI16x8S: + masm.extMulHighInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4ExtmulLowI16x8U: + masm.unsignedExtMulLowInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4ExtmulHighI16x8U: + masm.unsignedExtMulHighInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2ExtmulLowI32x4S: + masm.extMulLowInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2ExtmulHighI32x4S: + masm.extMulHighInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2ExtmulLowI32x4U: + masm.unsignedExtMulLowInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2ExtmulHighI32x4U: + masm.unsignedExtMulHighInt32x4(lhs, rhs, dest); + break; + // Dot product + case wasm::SimdOp::I32x4DotI16x8S: + masm.widenDotInt16x8(lhs, rhs, dest); + break; + // Relaxed binary ops + case wasm::SimdOp::F32x4RelaxedMin: + masm.minFloat32x4Relaxed(rhs, lhs); + if (dest != lhs) masm.moveSimd128(lhs, dest); + break; + case wasm::SimdOp::F32x4RelaxedMax: + masm.maxFloat32x4Relaxed(rhs, lhs); + if (dest != lhs) masm.moveSimd128(lhs, dest); + break; + case wasm::SimdOp::F64x2RelaxedMin: + masm.minFloat64x2Relaxed(rhs, lhs); + if (dest != lhs) masm.moveSimd128(lhs, dest); + break; + case wasm::SimdOp::F64x2RelaxedMax: + masm.maxFloat64x2Relaxed(rhs, lhs); + if (dest != lhs) masm.moveSimd128(lhs, dest); + break; + case wasm::SimdOp::I8x16RelaxedSwizzle: + masm.swizzleInt8x16Relaxed(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8RelaxedQ15MulrS: + masm.q15MulrInt16x8Relaxed(lhs, rhs, dest); + break; + // Swizzle + case wasm::SimdOp::I8x16Swizzle: + masm.swizzleInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8RelaxedDotI8x16I7x16S: + masm.dotInt8x16Int7x16(lhs, rhs, dest); + break; + default: + MOZ_CRASH("PPC: NYI SIMD binary op"); + } +} +void CodeGenerator::visitWasmBinarySimd128WithConstant( + LWasmBinarySimd128WithConstant* ins) { + FloatRegister lhs = ToFloatRegister(ins->lhs()); + FloatRegister dest = ToFloatRegister(ins->output()); + SimdConstant rhs = ins->rhs(); + // Load the constant into scratch, then use the binary op. + ScratchSimd128Scope scratch(masm); + masm.loadConstantSimd128(rhs, scratch); + switch (ins->mir()->simdOp()) { + // Bitwise + case wasm::SimdOp::V128And: + masm.bitwiseAndSimd128(lhs, scratch, dest); + break; + case wasm::SimdOp::V128Or: + masm.bitwiseOrSimd128(lhs, scratch, dest); + break; + case wasm::SimdOp::V128Xor: + masm.bitwiseXorSimd128(lhs, scratch, dest); + break; + case wasm::SimdOp::V128AndNot: + masm.bitwiseAndNotSimd128(lhs, scratch, dest); + break; + // Integer add + case wasm::SimdOp::I8x16Add: + masm.addInt8x16(lhs, scratch, dest); + break; + case wasm::SimdOp::I16x8Add: + masm.addInt16x8(lhs, scratch, dest); + break; + case wasm::SimdOp::I32x4Add: + masm.addInt32x4(lhs, scratch, dest); + break; + case wasm::SimdOp::I64x2Add: + masm.addInt64x2(lhs, scratch, dest); + break; + // Integer sub + case wasm::SimdOp::I8x16Sub: + masm.subInt8x16(lhs, scratch, dest); + break; + case wasm::SimdOp::I16x8Sub: + masm.subInt16x8(lhs, scratch, dest); + break; + case wasm::SimdOp::I32x4Sub: + masm.subInt32x4(lhs, scratch, dest); + break; + case wasm::SimdOp::I64x2Sub: + masm.subInt64x2(lhs, scratch, dest); + break; + // Integer multiply (16-/32-bit lanes; I64x2 unreachable, see below) + case wasm::SimdOp::I16x8Mul: + masm.mulInt16x8(lhs, scratch, dest); + break; + case wasm::SimdOp::I32x4Mul: + masm.mulInt32x4(lhs, scratch, dest); + break; + case wasm::SimdOp::I64x2Mul: + // Unreachable on PPC: MWasmBinarySimd128::specializeForConstantRhs + // returns false in Lowering-ppc.cpp, so MIR with a constant rhs + // to I64x2Mul is never created on this backend. + // + // The previous in-place implementation was broken in three ways: + // hard-coded VR0/VR1 staging assumed an ordering that didn't match + // the surrounding code; a dead `mfvsrd(a, f0)` clobbered `a` + // immediately before the next mfvsrd; and the trailing + // `xxpermdi(dest, scratch, dest, 0)` with DM=0 placed lane-0 in the + // wrong half. Rather than ship dead-but-broken code, crash loudly + // if reachability ever changes — the future enabler must write a + // correct lowering (e.g. via masm.mulInt64x2 with explicit temps). + MOZ_CRASH("PPC: I64x2Mul with constant rhs unimplemented " + "(specializeForConstantRhs returns false)"); + // Compare + case wasm::SimdOp::I8x16Eq: + masm.compareInt8x16(Assembler::Equal, lhs, scratch, dest); + break; + case wasm::SimdOp::I8x16Ne: + masm.compareInt8x16(Assembler::NotEqual, lhs, scratch, dest); + break; + default: + MOZ_CRASH("PPC: NYI SIMD binary-with-constant op"); + } +} +void CodeGenerator::visitWasmVariableShiftSimd128( + LWasmVariableShiftSimd128* ins) { + FloatRegister lhs = ToFloatRegister(ins->lhs()); + Register rhs = ToRegister(ins->rhs()); + FloatRegister dest = ToFloatRegister(ins->output()); + switch (ins->mir()->simdOp()) { + case wasm::SimdOp::I8x16Shl: + masm.leftShiftInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16ShrS: + masm.rightShiftInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I8x16ShrU: + masm.unsignedRightShiftInt8x16(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8Shl: + masm.leftShiftInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8ShrS: + masm.rightShiftInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I16x8ShrU: + masm.unsignedRightShiftInt16x8(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4Shl: + masm.leftShiftInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4ShrS: + masm.rightShiftInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I32x4ShrU: + masm.unsignedRightShiftInt32x4(lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2Shl: + masm.leftShiftInt64x2(lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2ShrS: + masm.rightShiftInt64x2(lhs, rhs, dest); + break; + case wasm::SimdOp::I64x2ShrU: + masm.unsignedRightShiftInt64x2(lhs, rhs, dest); + break; + default: + MOZ_CRASH("PPC: NYI SIMD variable shift op"); + } +} +void CodeGenerator::visitWasmConstantShiftSimd128( + LWasmConstantShiftSimd128* ins) { + FloatRegister src = ToFloatRegister(ins->src()); + FloatRegister dest = ToFloatRegister(ins->output()); + int32_t shift = ins->shift(); + switch (ins->mir()->simdOp()) { + case wasm::SimdOp::I8x16Shl: + masm.leftShiftInt8x16(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I8x16ShrS: + masm.rightShiftInt8x16(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I8x16ShrU: + masm.unsignedRightShiftInt8x16(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I16x8Shl: + masm.leftShiftInt16x8(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I16x8ShrS: + masm.rightShiftInt16x8(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I16x8ShrU: + masm.unsignedRightShiftInt16x8(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I32x4Shl: + masm.leftShiftInt32x4(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I32x4ShrS: + masm.rightShiftInt32x4(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I32x4ShrU: + masm.unsignedRightShiftInt32x4(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I64x2Shl: + masm.leftShiftInt64x2(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I64x2ShrS: + masm.rightShiftInt64x2(Imm32(shift), src, dest); + break; + case wasm::SimdOp::I64x2ShrU: + masm.unsignedRightShiftInt64x2(Imm32(shift), src, dest); + break; + default: + MOZ_CRASH("PPC: NYI SIMD constant shift op"); + } +} +void CodeGenerator::visitWasmSignReplicationSimd128( + LWasmSignReplicationSimd128* ins) { + FloatRegister src = ToFloatRegister(ins->src()); + FloatRegister dest = ToFloatRegister(ins->output()); + // Sign replication = arithmetic right shift by max amount (all sign bits). + switch (ins->mir()->simdOp()) { + case wasm::SimdOp::I8x16ShrS: + masm.rightShiftInt8x16(Imm32(7), src, dest); + break; + case wasm::SimdOp::I16x8ShrS: + masm.rightShiftInt16x8(Imm32(15), src, dest); + break; + case wasm::SimdOp::I32x4ShrS: + masm.rightShiftInt32x4(Imm32(31), src, dest); + break; + case wasm::SimdOp::I64x2ShrS: + masm.rightShiftInt64x2(Imm32(63), src, dest); + break; + default: + MOZ_CRASH("Unexpected sign replication op"); + } +} +void CodeGenerator::visitWasmShuffleSimd128(LWasmShuffleSimd128* ins) { + FloatRegister lhs = ToFloatRegister(ins->lhs()); + FloatRegister rhs = ToFloatRegister(ins->rhs()); + FloatRegister dest = ToFloatRegister(ins->output()); + SimdConstant ctrl = ins->control(); + const uint8_t* lanes = reinterpret_cast(ctrl.bytes()); + masm.shuffleInt8x16(lanes, lhs, rhs, dest); +} +void CodeGenerator::visitWasmPermuteSimd128(LWasmPermuteSimd128* ins) { + FloatRegister src = ToFloatRegister(ins->src()); + FloatRegister dest = ToFloatRegister(ins->output()); + // PPC: the shuffle analysis transforms control bytes into specialized + // formats. Reconstruct raw Wasm byte indices for our vperm implementation. + SimdConstant ctrl = ins->control(); + uint8_t rawLanes[16]; + switch (ins->op()) { + case SimdPermuteOp::MOVE: + masm.moveSimd128(src, dest); + return; + case SimdPermuteOp::PERMUTE_32x4: { + const int32_t* words = reinterpret_cast(ctrl.bytes()); + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + rawLanes[i * 4 + j] = words[i] * 4 + j; + break; + } + case SimdPermuteOp::PERMUTE_16x8: { + // control has int16 halfword indices. High byte of halfs[0] may have + // platform-specific flags (Perm16x8Action). Mask to get the index only. + const int16_t* halfs = reinterpret_cast(ctrl.bytes()); + for (int i = 0; i < 8; i++) { + int hwIdx = halfs[i] & 0x7; + rawLanes[i * 2] = hwIdx * 2; + rawLanes[i * 2 + 1] = hwIdx * 2 + 1; + } + break; + } + case SimdPermuteOp::BROADCAST_8x16: { + uint8_t lane = reinterpret_cast(ctrl.bytes())[0]; + for (int i = 0; i < 16; i++) rawLanes[i] = lane; + break; + } + case SimdPermuteOp::BROADCAST_16x8: { + // control has int16 halfword indices; a byte-sized read of element 0 + // would pick up the wrong half on big-endian. + uint8_t lane = uint8_t(ctrl.asInt16x8()[0] & 0x7); + for (int i = 0; i < 8; i++) { + rawLanes[i * 2] = lane * 2; + rawLanes[i * 2 + 1] = lane * 2 + 1; + } + break; + } + case SimdPermuteOp::ROTATE_RIGHT_8x16: { + uint8_t shift = reinterpret_cast(ctrl.bytes())[0]; + for (int i = 0; i < 16; i++) rawLanes[i] = (i + shift) % 16; + break; + } + case SimdPermuteOp::SHIFT_LEFT_8x16: { + // Shifted-out positions must be zero. Use index 16+ to pick from zero. + uint8_t shift = reinterpret_cast(ctrl.bytes())[0]; + for (int i = 0; i < 16; i++) + rawLanes[i] = (i >= shift) ? (i - shift) : (16 + i); + goto needsZeroRhs; + } + case SimdPermuteOp::SHIFT_RIGHT_8x16: { + uint8_t shift = reinterpret_cast(ctrl.bytes())[0]; + for (int i = 0; i < 16; i++) + rawLanes[i] = (i + shift < 16) ? (i + shift) : (16 + i); + goto needsZeroRhs; + } + case SimdPermuteOp::REVERSE_16x8: { + // Reverse bytes within each 16-bit lane: [1,0,3,2,5,4,...] + for (int i = 0; i < 8; i++) { + rawLanes[i * 2] = i * 2 + 1; + rawLanes[i * 2 + 1] = i * 2; + } + break; + } + case SimdPermuteOp::REVERSE_32x4: { + // Reverse bytes within each 32-bit lane: [3,2,1,0,7,6,5,4,...] + for (int i = 0; i < 4; i++) + for (int j = 0; j < 4; j++) + rawLanes[i * 4 + j] = i * 4 + (3 - j); + break; + } + case SimdPermuteOp::REVERSE_64x2: { + // Reverse bytes within each 64-bit lane: [7,6,5,4,3,2,1,0,15,...] + for (int i = 0; i < 2; i++) + for (int j = 0; j < 8; j++) + rawLanes[i * 8 + j] = i * 8 + (7 - j); + break; + } + case SimdPermuteOp::ZERO_EXTEND_8x16_TO_16x8: + case SimdPermuteOp::ZERO_EXTEND_8x16_TO_32x4: + case SimdPermuteOp::ZERO_EXTEND_8x16_TO_64x2: + case SimdPermuteOp::ZERO_EXTEND_16x8_TO_32x4: + case SimdPermuteOp::ZERO_EXTEND_16x8_TO_64x2: + case SimdPermuteOp::ZERO_EXTEND_32x4_TO_64x2: { + const int8_t* bytes = reinterpret_cast(ctrl.bytes()); + for (int i = 0; i < 16; i++) rawLanes[i] = bytes[i]; + goto needsZeroRhs; + } + default: { + // PERMUTE_8x16 and others: control has raw byte indices. + const int8_t* bytes = reinterpret_cast(ctrl.bytes()); + for (int i = 0; i < 16; i++) rawLanes[i] = bytes[i]; + break; + } + } + masm.shuffleInt8x16(rawLanes, src, src, dest); + return; + + needsZeroRhs: { + // Wasm convention: rawLanes[i] in 0..15 selects src.LE_byte[idx], and + // rawLanes[i] >= 16 means "zero". Without spilling, we can't satisfy + // vperm's three-input constraint AND keep src alive when dest == src. + // Strategy: vperm src with itself (any valid byte for the "zero" + // positions, bytes get masked out below), then AND with a mask that + // zeros those positions. + int8_t ctrl[16], mask[16]; + for (unsigned i = 0; i < 16; i++) { + uint8_t idx = rawLanes[i]; + if (idx < 16) { + ctrl[i] = 15 - idx; + mask[i] = -1; + } else { + ctrl[i] = 0; + mask[i] = 0; + } + } + ScratchSimd128Scope scratch(masm); + masm.loadConstantSimd128(SimdConstant::CreateX16(ctrl), scratch); + masm.as_vperm(dest.encoding() & 31, + src.encoding() & 31, + src.encoding() & 31, + scratch.encoding() & 31); + masm.loadConstantSimd128(SimdConstant::CreateX16(mask), scratch); + masm.as_vand(dest, dest, scratch); + return; + } +} +void CodeGenerator::visitWasmReplaceLaneSimd128(LWasmReplaceLaneSimd128* ins) { + FloatRegister lhsDest = ToFloatRegister(ins->output()); + MOZ_ASSERT(ToFloatRegister(ins->lhs()) == lhsDest); + uint32_t lane = ins->mir()->laneIndex(); + switch (ins->mir()->simdOp()) { + case wasm::SimdOp::I8x16ReplaceLane: + masm.replaceLaneInt8x16(lane, ToRegister(ins->rhs()), lhsDest); + break; + case wasm::SimdOp::I16x8ReplaceLane: + masm.replaceLaneInt16x8(lane, ToRegister(ins->rhs()), lhsDest); + break; + case wasm::SimdOp::I32x4ReplaceLane: + masm.replaceLaneInt32x4(lane, ToRegister(ins->rhs()), lhsDest); + break; + case wasm::SimdOp::F32x4ReplaceLane: + masm.replaceLaneFloat32x4(lane, ToFloatRegister(ins->rhs()), lhsDest); + break; + case wasm::SimdOp::F64x2ReplaceLane: + masm.replaceLaneFloat64x2(lane, ToFloatRegister(ins->rhs()), lhsDest); + break; + default: + MOZ_CRASH("PPC: NYI SIMD replace lane op"); + } +} +void CodeGenerator::visitWasmReplaceInt64LaneSimd128( + LWasmReplaceInt64LaneSimd128* ins) { + MOZ_ASSERT(ins->mir()->simdOp() == wasm::SimdOp::I64x2ReplaceLane); + FloatRegister lhsDest = ToFloatRegister(ins->output()); + MOZ_ASSERT(ToFloatRegister(ins->lhs()) == lhsDest); + masm.replaceLaneInt64x2(ins->mir()->laneIndex(), + ToRegister64(ins->rhs()), lhsDest); +} +void CodeGenerator::visitWasmScalarToSimd128(LWasmScalarToSimd128* ins) { + FloatRegister dest = ToFloatRegister(ins->output()); + switch (ins->mir()->simdOp()) { + case wasm::SimdOp::I8x16Splat: + masm.splatX16(ToRegister(ins->src()), dest); + break; + case wasm::SimdOp::I16x8Splat: + masm.splatX8(ToRegister(ins->src()), dest); + break; + case wasm::SimdOp::I32x4Splat: + masm.splatX4(ToRegister(ins->src()), dest); + break; + case wasm::SimdOp::F32x4Splat: + masm.splatX4(ToFloatRegister(ins->src()), dest); + break; + case wasm::SimdOp::F64x2Splat: + masm.splatX2(ToFloatRegister(ins->src()), dest); + break; + default: + MOZ_CRASH("PPC: NYI SIMD scalar-to-simd op"); + } +} +void CodeGenerator::visitWasmInt64ToSimd128(LWasmInt64ToSimd128* ins) { + FloatRegister dest = ToFloatRegister(ins->output()); + switch (ins->mir()->simdOp()) { + case wasm::SimdOp::I64x2Splat: + masm.splatX2(ToRegister64(ins->src()), dest); + break; + default: + MOZ_CRASH("PPC: NYI SIMD int64-to-simd op"); + } +} +void CodeGenerator::visitWasmUnarySimd128(LWasmUnarySimd128* ins) { + FloatRegister src = ToFloatRegister(ins->src()); + FloatRegister dest = ToFloatRegister(ins->output()); + switch (ins->mir()->simdOp()) { + case wasm::SimdOp::I8x16Neg: + masm.negInt8x16(src, dest); + break; + case wasm::SimdOp::I16x8Neg: + masm.negInt16x8(src, dest); + break; + case wasm::SimdOp::I32x4Neg: + masm.negInt32x4(src, dest); + break; + case wasm::SimdOp::I64x2Neg: + masm.negInt64x2(src, dest); + break; + case wasm::SimdOp::I8x16Abs: + masm.absInt8x16(src, dest); + break; + case wasm::SimdOp::I16x8Abs: + masm.absInt16x8(src, dest); + break; + case wasm::SimdOp::I32x4Abs: + masm.absInt32x4(src, dest); + break; + case wasm::SimdOp::I64x2Abs: + masm.absInt64x2(src, dest); + break; + case wasm::SimdOp::V128Not: + masm.bitwiseNotSimd128(src, dest); + break; + case wasm::SimdOp::F32x4Neg: + masm.negFloat32x4(src, dest); + break; + case wasm::SimdOp::F64x2Neg: + masm.negFloat64x2(src, dest); + break; + case wasm::SimdOp::F32x4Abs: + masm.absFloat32x4(src, dest); + break; + case wasm::SimdOp::F64x2Abs: + masm.absFloat64x2(src, dest); + break; + case wasm::SimdOp::F32x4Sqrt: + masm.sqrtFloat32x4(src, dest); + break; + case wasm::SimdOp::F64x2Sqrt: + masm.sqrtFloat64x2(src, dest); + break; + case wasm::SimdOp::F32x4Ceil: + masm.ceilFloat32x4(src, dest); + break; + case wasm::SimdOp::F64x2Ceil: + masm.ceilFloat64x2(src, dest); + break; + case wasm::SimdOp::F32x4Floor: + masm.floorFloat32x4(src, dest); + break; + case wasm::SimdOp::F64x2Floor: + masm.floorFloat64x2(src, dest); + break; + case wasm::SimdOp::F32x4Trunc: + masm.truncFloat32x4(src, dest); + break; + case wasm::SimdOp::F64x2Trunc: + masm.truncFloat64x2(src, dest); + break; + case wasm::SimdOp::F32x4Nearest: + masm.nearestFloat32x4(src, dest); + break; + case wasm::SimdOp::F64x2Nearest: + masm.nearestFloat64x2(src, dest); + break; + // Conversions + case wasm::SimdOp::F32x4ConvertI32x4S: + masm.convertInt32x4ToFloat32x4(src, dest); + break; + case wasm::SimdOp::F32x4ConvertI32x4U: + masm.unsignedConvertInt32x4ToFloat32x4(src, dest); + break; + case wasm::SimdOp::I32x4TruncSatF32x4S: + masm.truncSatFloat32x4ToInt32x4(src, dest); + break; + case wasm::SimdOp::I32x4TruncSatF32x4U: + masm.unsignedTruncSatFloat32x4ToInt32x4(src, dest); + break; + case wasm::SimdOp::F64x2ConvertLowI32x4S: + masm.convertInt32x4ToFloat64x2(src, dest); + break; + case wasm::SimdOp::F64x2ConvertLowI32x4U: + masm.unsignedConvertInt32x4ToFloat64x2(src, dest); + break; + case wasm::SimdOp::F32x4DemoteF64x2Zero: + masm.convertFloat64x2ToFloat32x4(src, dest); + break; + case wasm::SimdOp::F64x2PromoteLowF32x4: + masm.convertFloat32x4ToFloat64x2(src, dest); + break; + case wasm::SimdOp::I32x4TruncSatF64x2SZero: + masm.truncSatFloat64x2ToInt32x4(src, dest, ScratchSimd128Reg); + break; + case wasm::SimdOp::I32x4TruncSatF64x2UZero: + masm.unsignedTruncSatFloat64x2ToInt32x4(src, dest, ScratchSimd128Reg); + break; + // Widen + case wasm::SimdOp::I16x8ExtendLowI8x16S: + masm.widenLowInt8x16(src, dest); + break; + case wasm::SimdOp::I16x8ExtendHighI8x16S: + masm.widenHighInt8x16(src, dest); + break; + case wasm::SimdOp::I16x8ExtendLowI8x16U: + masm.unsignedWidenLowInt8x16(src, dest); + break; + case wasm::SimdOp::I16x8ExtendHighI8x16U: + masm.unsignedWidenHighInt8x16(src, dest); + break; + case wasm::SimdOp::I32x4ExtendLowI16x8S: + masm.widenLowInt16x8(src, dest); + break; + case wasm::SimdOp::I32x4ExtendHighI16x8S: + masm.widenHighInt16x8(src, dest); + break; + case wasm::SimdOp::I32x4ExtendLowI16x8U: + masm.unsignedWidenLowInt16x8(src, dest); + break; + case wasm::SimdOp::I32x4ExtendHighI16x8U: + masm.unsignedWidenHighInt16x8(src, dest); + break; + case wasm::SimdOp::I64x2ExtendLowI32x4S: + masm.widenLowInt32x4(src, dest); + break; + case wasm::SimdOp::I64x2ExtendHighI32x4S: + masm.widenHighInt32x4(src, dest); + break; + case wasm::SimdOp::I64x2ExtendLowI32x4U: + masm.unsignedWidenLowInt32x4(src, dest); + break; + case wasm::SimdOp::I64x2ExtendHighI32x4U: + masm.unsignedWidenHighInt32x4(src, dest); + break; + // Extended add pairwise + case wasm::SimdOp::I16x8ExtaddPairwiseI8x16S: + masm.extAddPairwiseInt8x16(src, dest); + break; + case wasm::SimdOp::I16x8ExtaddPairwiseI8x16U: + masm.unsignedExtAddPairwiseInt8x16(src, dest); + break; + case wasm::SimdOp::I32x4ExtaddPairwiseI16x8S: + masm.extAddPairwiseInt16x8(src, dest); + break; + case wasm::SimdOp::I32x4ExtaddPairwiseI16x8U: + masm.unsignedExtAddPairwiseInt16x8(src, dest); + break; + // Relaxed truncation + case wasm::SimdOp::I32x4RelaxedTruncF32x4S: + masm.truncFloat32x4ToInt32x4Relaxed(src, dest); + break; + case wasm::SimdOp::I32x4RelaxedTruncF32x4U: + masm.unsignedTruncFloat32x4ToInt32x4Relaxed(src, dest); + break; + case wasm::SimdOp::I32x4RelaxedTruncF64x2SZero: + masm.truncFloat64x2ToInt32x4Relaxed(src, dest); + break; + case wasm::SimdOp::I32x4RelaxedTruncF64x2UZero: + masm.unsignedTruncFloat64x2ToInt32x4Relaxed(src, dest); + break; + // Popcnt + case wasm::SimdOp::I8x16Popcnt: + masm.popcntInt8x16(src, dest); + break; + default: + MOZ_CRASH("PPC: NYI SIMD unary op"); + } +} +void CodeGenerator::visitWasmReduceSimd128(LWasmReduceSimd128* ins) { + FloatRegister src = ToFloatRegister(ins->src()); + uint32_t imm = ins->mir()->imm(); + switch (ins->mir()->simdOp()) { + case wasm::SimdOp::I8x16ExtractLaneS: + masm.extractLaneInt8x16(imm, src, ToRegister(ins->output())); + break; + case wasm::SimdOp::I8x16ExtractLaneU: + masm.unsignedExtractLaneInt8x16(imm, src, ToRegister(ins->output())); + break; + case wasm::SimdOp::I16x8ExtractLaneS: + masm.extractLaneInt16x8(imm, src, ToRegister(ins->output())); + break; + case wasm::SimdOp::I16x8ExtractLaneU: + masm.unsignedExtractLaneInt16x8(imm, src, ToRegister(ins->output())); + break; + case wasm::SimdOp::I32x4ExtractLane: + masm.extractLaneInt32x4(imm, src, ToRegister(ins->output())); + break; + case wasm::SimdOp::F32x4ExtractLane: + masm.extractLaneFloat32x4(imm, src, ToFloatRegister(ins->output())); + break; + case wasm::SimdOp::F64x2ExtractLane: + masm.extractLaneFloat64x2(imm, src, ToFloatRegister(ins->output())); + break; + case wasm::SimdOp::V128AnyTrue: + masm.anyTrueSimd128(src, ToRegister(ins->output())); + break; + case wasm::SimdOp::I8x16AllTrue: + masm.allTrueInt8x16(src, ToRegister(ins->output())); + break; + case wasm::SimdOp::I16x8AllTrue: + masm.allTrueInt16x8(src, ToRegister(ins->output())); + break; + case wasm::SimdOp::I32x4AllTrue: + masm.allTrueInt32x4(src, ToRegister(ins->output())); + break; + case wasm::SimdOp::I64x2AllTrue: + masm.allTrueInt64x2(src, ToRegister(ins->output())); + break; + case wasm::SimdOp::I8x16Bitmask: + masm.bitmaskInt8x16(src, ToRegister(ins->output()), ScratchSimd128Reg); + break; + case wasm::SimdOp::I16x8Bitmask: + masm.bitmaskInt16x8(src, ToRegister(ins->output()), ScratchSimd128Reg); + break; + case wasm::SimdOp::I32x4Bitmask: + masm.bitmaskInt32x4(src, ToRegister(ins->output()), ScratchSimd128Reg); + break; + case wasm::SimdOp::I64x2Bitmask: + masm.bitmaskInt64x2(src, ToRegister(ins->output()), ScratchSimd128Reg); + break; + default: + MOZ_CRASH("PPC: NYI SIMD reduce op"); + } +} +void CodeGenerator::visitWasmReduceAndBranchSimd128( + LWasmReduceAndBranchSimd128* ins) { + FloatRegister src = ToFloatRegister(ins->src()); + UseScratchRegisterScope temps(masm); + Register tmp = temps.Acquire(); + switch (ins->simdOp()) { + case wasm::SimdOp::V128AnyTrue: + masm.anyTrueSimd128(src, tmp); + break; + case wasm::SimdOp::I8x16AllTrue: + masm.allTrueInt8x16(src, tmp); + break; + case wasm::SimdOp::I16x8AllTrue: + masm.allTrueInt16x8(src, tmp); + break; + case wasm::SimdOp::I32x4AllTrue: + masm.allTrueInt32x4(src, tmp); + break; + case wasm::SimdOp::I64x2AllTrue: + masm.allTrueInt64x2(src, tmp); + break; + case wasm::SimdOp::I8x16Bitmask: + masm.bitmaskInt8x16(src, tmp, ScratchSimd128Reg); + break; + case wasm::SimdOp::I16x8Bitmask: + masm.bitmaskInt16x8(src, tmp, ScratchSimd128Reg); + break; + case wasm::SimdOp::I32x4Bitmask: + masm.bitmaskInt32x4(src, tmp, ScratchSimd128Reg); + break; + case wasm::SimdOp::I64x2Bitmask: + masm.bitmaskInt64x2(src, tmp, ScratchSimd128Reg); + break; + default: + MOZ_CRASH("PPC: NYI SIMD reduce-and-branch op"); + } + masm.as_cmpwi(tmp, 0); + // Branch to ifTrue if nonzero, fall through to ifFalse. + Label* ifTrue = skipTrivialBlocks(ins->ifTrue())->lir()->label(); + Label* ifFalse = skipTrivialBlocks(ins->ifFalse())->lir()->label(); + masm.ma_b(Assembler::NotEqual, ifTrue); + masm.jump(ifFalse); +} +void CodeGenerator::visitWasmReduceSimd128ToInt64( + LWasmReduceSimd128ToInt64* ins) { + FloatRegister src = ToFloatRegister(ins->src()); + Register64 dest = ToOutRegister64(ins); + switch (ins->mir()->simdOp()) { + case wasm::SimdOp::I64x2ExtractLane: + masm.extractLaneInt64x2(ins->mir()->imm(), src, dest); + break; + default: + MOZ_CRASH("PPC: NYI SIMD reduce-to-int64 op"); + } +} +static inline wasm::MemoryAccessDesc DeriveMemoryAccessDesc( + const wasm::MemoryAccessDesc& access, Scalar::Type type, + uint32_t extraOffset = 0) { + return wasm::MemoryAccessDesc(access.memoryIndex(), type, access.align(), + access.offset32() + extraOffset, + access.trapDesc(), access.isHugeMemory()); +} + +void CodeGenerator::visitWasmLoadLaneSimd128(LWasmLoadLaneSimd128* ins) { + const MWasmLoadLaneSimd128* mir = ins->mir(); + Register memoryBase = ToRegister(ins->memoryBase()); + Register ptr = ToRegister(ins->ptr()); + FloatRegister src = ToFloatRegister(ins->src()); + FloatRegister dest = ToFloatRegister(ins->output()); + UseScratchRegisterScope temps(masm); + Register tmp = temps.Acquire(); + masm.moveSimd128(src, dest); + switch (mir->laneSize()) { + case 1: + masm.wasmLoad(DeriveMemoryAccessDesc(mir->access(), Scalar::Int8), + memoryBase, ptr, ptr, AnyRegister(tmp)); + masm.replaceLaneInt8x16(mir->laneIndex(), tmp, dest); + break; + case 2: + masm.wasmLoad(DeriveMemoryAccessDesc(mir->access(), Scalar::Int16), + memoryBase, ptr, ptr, AnyRegister(tmp)); + masm.replaceLaneInt16x8(mir->laneIndex(), tmp, dest); + break; + case 4: + masm.wasmLoad(DeriveMemoryAccessDesc(mir->access(), Scalar::Int32), + memoryBase, ptr, ptr, AnyRegister(tmp)); + masm.replaceLaneInt32x4(mir->laneIndex(), tmp, dest); + break; + case 8: { + // Only one scratch GPR is available here, so do the i64 lane as its two + // i32 halves. Wasm lane numbering puts the low word first. + uint32_t lane = mir->laneIndex() * 2; + masm.wasmLoad(DeriveMemoryAccessDesc(mir->access(), Scalar::Int32), + memoryBase, ptr, ptr, AnyRegister(tmp)); + masm.replaceLaneInt32x4(lane, tmp, dest); + masm.wasmLoad( + DeriveMemoryAccessDesc(mir->access(), Scalar::Int32, sizeof(int32_t)), + memoryBase, ptr, ptr, AnyRegister(tmp)); + masm.replaceLaneInt32x4(lane + 1, tmp, dest); + break; + } + default: + MOZ_CRASH("Unexpected lane size"); + } +} +void CodeGenerator::visitWasmStoreLaneSimd128(LWasmStoreLaneSimd128* ins) { + const MWasmStoreLaneSimd128* mir = ins->mir(); + Register memoryBase = ToRegister(ins->memoryBase()); + Register ptr = ToRegister(ins->ptr()); + FloatRegister src = ToFloatRegister(ins->src()); + UseScratchRegisterScope temps(masm); + Register tmp = temps.Acquire(); + switch (mir->laneSize()) { + case 1: + masm.unsignedExtractLaneInt8x16(mir->laneIndex(), src, tmp); + masm.wasmStore(DeriveMemoryAccessDesc(mir->access(), Scalar::Int8), + AnyRegister(tmp), memoryBase, ptr, ptr); + break; + case 2: + masm.unsignedExtractLaneInt16x8(mir->laneIndex(), src, tmp); + masm.wasmStore(DeriveMemoryAccessDesc(mir->access(), Scalar::Int16), + AnyRegister(tmp), memoryBase, ptr, ptr); + break; + case 4: + masm.extractLaneInt32x4(mir->laneIndex(), src, tmp); + masm.wasmStore(DeriveMemoryAccessDesc(mir->access(), Scalar::Int32), + AnyRegister(tmp), memoryBase, ptr, ptr); + break; + case 8: { + // As in visitWasmLoadLaneSimd128: one scratch GPR, so store the i64 lane + // as its two i32 halves, low word first. + uint32_t lane = mir->laneIndex() * 2; + masm.extractLaneInt32x4(lane, src, tmp); + masm.wasmStore(DeriveMemoryAccessDesc(mir->access(), Scalar::Int32), + AnyRegister(tmp), memoryBase, ptr, ptr); + masm.extractLaneInt32x4(lane + 1, src, tmp); + masm.wasmStore( + DeriveMemoryAccessDesc(mir->access(), Scalar::Int32, sizeof(int32_t)), + AnyRegister(tmp), memoryBase, ptr, ptr); + break; + } + default: + MOZ_CRASH("Unexpected lane size"); + } +} + +} // namespace jit +} // namespace js diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/CodeGenerator-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/CodeGenerator-ppc.h --- a/js/src/jit/ppc/CodeGenerator-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/CodeGenerator-ppc.h 2026-07-28 19:22:05.765411173 +0200 @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_CodeGenerator_ppc_h +#define jit_ppc_CodeGenerator_ppc_h + +#include "jit/ppc/Assembler-ppc.h" +#include "jit/shared/CodeGenerator-shared.h" + +namespace js { +namespace jit { + +class CodeGeneratorPPC; +class OutOfLineTableSwitch; + +using OutOfLineWasmTruncateCheck = + OutOfLineWasmTruncateCheckBase; + +class CodeGeneratorPPC : public CodeGeneratorShared { + friend class MoveResolverPPC; + + protected: + CodeGeneratorPPC(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm, + const wasm::CodeMetadata* codeMeta); + + NonAssertingLabel deoptLabel_; + + MoveOperand toMoveOperand(LAllocation a) const; + + template + void bailoutCmp32(Assembler::Condition c, T1 lhs, T2 rhs, + LSnapshot* snapshot) { + Label bail; + masm.branch32(c, lhs, rhs, &bail); + bailoutFrom(&bail, snapshot); + } + template + void bailoutCmpPtr(Assembler::Condition c, T1 lhs, T2 rhs, + LSnapshot* snapshot) { + Label bail; + masm.branchPtr(c, lhs, rhs, &bail); + bailoutFrom(&bail, snapshot); + } + template + void bailoutTest32(Assembler::Condition c, T1 lhs, T2 rhs, + LSnapshot* snapshot) { + Label bail; + masm.branchTest32(c, lhs, rhs, &bail); + bailoutFrom(&bail, snapshot); + } + void bailoutIfFalseBool(Register lhs, LSnapshot* snapshot); + void bailoutFrom(Label* label, LSnapshot* snapshot); + void bailout(LSnapshot* snapshot); + + protected: + bool generateOutOfLineCode(); + void branchToBlock(MBasicBlock* block); + + template + void branchToBlock(Assembler::Condition cond, Register lhs, T rhs, + MBasicBlock* mir) { + Label* label = skipTrivialBlocks(mir)->lir()->label(); + masm.branch32(cond, lhs, rhs, label); + } + void branchToBlock(Assembler::DoubleCondition cond, FloatRegister lhs, + FloatRegister rhs, MBasicBlock* mir); + void branchToBlock(Assembler::FloatFormat fmt, + Assembler::DoubleCondition cond, FloatRegister lhs, + FloatRegister rhs, MBasicBlock* mir); + + void emitTableSwitchDispatch(MTableSwitch* mir, Register index, + Register base); + + void emitBigIntPtrDiv(LBigIntPtrDiv* ins, Register dividend, Register divisor, + Register output); + void emitBigIntPtrMod(LBigIntPtrMod* ins, Register dividend, Register divisor, + Register output); + + void generateInvalidateEpilogue(); + + template + void emitWasmLoad(T* lir); + template + void emitWasmStore(T* lir); + + public: + void visitOutOfLineTableSwitch(OutOfLineTableSwitch* ool); + void visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* ool); +}; + +typedef CodeGeneratorPPC CodeGeneratorSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_CodeGenerator_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/LIR-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/LIR-ppc.h --- a/js/src/jit/ppc/LIR-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/LIR-ppc.h 2026-07-28 17:01:17.479849361 +0200 @@ -0,0 +1,166 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_LIR_ppc_h +#define jit_ppc_LIR_ppc_h + +namespace js { +namespace jit { + +// The payload is operand 0 and the type is operand 1 so that the result can +// reuse the payload register. +class LUnbox : public LInstructionHelper<1, 2, 0> { + public: + LIR_HEADER(Unbox); + + LUnbox() : LInstructionHelper(classOpcode) {} + + MUnbox* mir() const { return mir_->toUnbox(); } + const LAllocation* payload() { return getOperand(0); } + const LAllocation* type() { return getOperand(1); } + const char* extraName() const { return StringFromMIRType(mir()->type()); } +}; + +class LUDivOrMod : public LBinaryMath<0> { + public: + LIR_HEADER(UDivOrMod); + + LUDivOrMod() : LBinaryMath(classOpcode) {} + + MBinaryArithInstruction* mir() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + return static_cast(mir_); + } + + bool canBeDivideByZero() const { + if (mir_->isMod()) { + return mir_->toMod()->canBeDivideByZero(); + } + return mir_->toDiv()->canBeDivideByZero(); + } + + bool trapOnError() const { + if (mir_->isMod()) { + return mir_->toMod()->trapOnError(); + } + return mir_->toDiv()->trapOnError(); + } + + wasm::TrapSiteDesc trapSiteDesc() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + if (mir_->isMod()) { + return mir_->toMod()->trapSiteDesc(); + } + return mir_->toDiv()->trapSiteDesc(); + } +}; + +// 32-bit PowerPC has no 64-bit divide, so wasm i64 div/mod are call-outs to +// the C++ builtins and carry the instance pointer as their last operand. +class LDivOrModI64 + : public LCallInstructionHelper { + public: + LIR_HEADER(DivOrModI64) + + static const size_t Lhs = 0; + static const size_t Rhs = INT64_PIECES; + static const size_t Instance = 2 * INT64_PIECES; + + LDivOrModI64(const LInt64Allocation& lhs, const LInt64Allocation& rhs, + const LAllocation& instance) + : LCallInstructionHelper(classOpcode) { + setInt64Operand(Lhs, lhs); + setInt64Operand(Rhs, rhs); + setOperand(Instance, instance); + } + + LInt64Allocation lhs() const { return getInt64Operand(Lhs); } + LInt64Allocation rhs() const { return getInt64Operand(Rhs); } + const LAllocation* instance() const { return getOperand(Instance); } + + MDefinition* mir() const { + MOZ_ASSERT(mir_->isWasmBuiltinDivI64() || mir_->isWasmBuiltinModI64()); + return mir_; + } + bool canBeDivideByZero() const { + if (mir_->isWasmBuiltinModI64()) { + return mir_->toWasmBuiltinModI64()->canBeDivideByZero(); + } + return mir_->toWasmBuiltinDivI64()->canBeDivideByZero(); + } + bool canBeNegativeOverflow() const { + if (mir_->isWasmBuiltinModI64()) { + return mir_->toWasmBuiltinModI64()->canBeNegativeDividend(); + } + return mir_->toWasmBuiltinDivI64()->canBeNegativeOverflow(); + } + const wasm::TrapSiteDesc& trapSiteDesc() const { + MOZ_ASSERT(mir_->isWasmBuiltinDivI64() || mir_->isWasmBuiltinModI64()); + if (mir_->isWasmBuiltinModI64()) { + return mir_->toWasmBuiltinModI64()->trapSiteDesc(); + } + return mir_->toWasmBuiltinDivI64()->trapSiteDesc(); + } +}; + +class LUDivOrModI64 + : public LCallInstructionHelper { + public: + LIR_HEADER(UDivOrModI64) + + static const size_t Lhs = 0; + static const size_t Rhs = INT64_PIECES; + static const size_t Instance = 2 * INT64_PIECES; + + LUDivOrModI64(const LInt64Allocation& lhs, const LInt64Allocation& rhs, + const LAllocation& instance) + : LCallInstructionHelper(classOpcode) { + setInt64Operand(Lhs, lhs); + setInt64Operand(Rhs, rhs); + setOperand(Instance, instance); + } + + LInt64Allocation lhs() const { return getInt64Operand(Lhs); } + LInt64Allocation rhs() const { return getInt64Operand(Rhs); } + const LAllocation* instance() const { return getOperand(Instance); } + + MDefinition* mir() const { + MOZ_ASSERT(mir_->isWasmBuiltinDivI64() || mir_->isWasmBuiltinModI64()); + return mir_; + } + bool canBeDivideByZero() const { + if (mir_->isWasmBuiltinModI64()) { + return mir_->toWasmBuiltinModI64()->canBeDivideByZero(); + } + return mir_->toWasmBuiltinDivI64()->canBeDivideByZero(); + } + bool canBeNegativeOverflow() const { + if (mir_->isWasmBuiltinModI64()) { + return mir_->toWasmBuiltinModI64()->canBeNegativeDividend(); + } + return mir_->toWasmBuiltinDivI64()->canBeNegativeOverflow(); + } + const wasm::TrapSiteDesc& trapSiteDesc() const { + MOZ_ASSERT(mir_->isWasmBuiltinDivI64() || mir_->isWasmBuiltinModI64()); + if (mir_->isWasmBuiltinModI64()) { + return mir_->toWasmBuiltinModI64()->trapSiteDesc(); + } + return mir_->toWasmBuiltinDivI64()->trapSiteDesc(); + } +}; + +// Definitions for `extraName` methods of generated LIR instructions. + +#ifdef JS_JITSPEW +const char* LBoxFloatingPoint::extraName() const { + return StringFromMIRType(type_); +} +#endif + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_LIR_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/Lowering-ppc.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/Lowering-ppc.cpp --- a/js/src/jit/ppc/Lowering-ppc.cpp 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/Lowering-ppc.cpp 2026-07-28 16:47:50.986541976 +0200 @@ -0,0 +1,1408 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// LIR generation for 32-bit PowerPC. Values are (type, payload) register pairs +// and Register64 is a (high, low) register pair, so everything Int64- or +// Value-shaped below works on two virtual registers. 64-bit divide and modulo +// have no instruction on this target and are lowered as builtin calls. + +#include "jit/ppc/Lowering-ppc.h" + +#include "mozilla/MathAlgorithms.h" + +#include "jit/Lowering.h" +#include "jit/MIR-wasm.h" +#include "jit/MIR.h" +#include "jit/ppc/Assembler-ppc.h" +#include "wasm/WasmFeatures.h" // for wasm::ReportSimdAnalysis + +#include "jit/shared/Lowering-shared-inl.h" + +using namespace js; +using namespace js::jit; + +using mozilla::FloorLog2; + +namespace js { +namespace jit { + +LTableSwitch* LIRGeneratorPPC::newLTableSwitch(const LAllocation& in, + const LDefinition& inputCopy) { + return new (alloc()) LTableSwitch(in, inputCopy, temp()); +} + +LTableSwitchV* LIRGeneratorPPC::newLTableSwitchV(const LBoxAllocation& in) { + return new (alloc()) LTableSwitchV(in, temp(), tempDouble(), temp()); +} + +void LIRGeneratorPPC::lowerForShift(LInstructionHelper<1, 2, 0>* ins, + MDefinition* mir, MDefinition* lhs, + MDefinition* rhs) { + lowerForALU(ins, mir, lhs, rhs); +} + +template +void LIRGeneratorPPC::lowerForShiftInt64(LInstr* ins, MDefinition* mir, + MDefinition* lhs, MDefinition* rhs) { + LAllocation rhsAlloc; + if (rhs->isConstant()) { + rhsAlloc = useOrConstant(rhs); + } else { + // The operands are int64, but only the low 32 bits of the RHS are used as + // the shift count; the high word is discarded. + rhsAlloc = useLowWordRegister(rhs); + } + + if constexpr (std::is_same_v) { + ins->setLhs(useInt64RegisterAtStart(lhs)); + ins->setRhs(rhsAlloc); + defineInt64ReuseInput(ins, mir, LShiftI64::LhsIndex); + } else { + ins->setInput(useInt64RegisterAtStart(lhs)); + ins->setCount(rhsAlloc); + if (!rhs->isConstant()) { + ins->setTemp0(temp()); + } + defineInt64ReuseInput(ins, mir, LRotateI64::InputIndex); + } +} + +template void LIRGeneratorPPC::lowerForShiftInt64(LShiftI64* ins, + MDefinition* mir, + MDefinition* lhs, + MDefinition* rhs); +template void LIRGeneratorPPC::lowerForShiftInt64(LRotateI64* ins, + MDefinition* mir, + MDefinition* lhs, + MDefinition* rhs); + +void LIRGeneratorPPC::lowerForALU(LInstructionHelper<1, 1, 0>* ins, + MDefinition* mir, MDefinition* input) { + ins->setOperand(0, useRegisterAtStart(input)); + define(ins, mir); +} + +void LIRGeneratorPPC::lowerForALU(LInstructionHelper<1, 2, 0>* ins, + MDefinition* mir, MDefinition* lhs, + MDefinition* rhs) { + ins->setOperand(0, useRegisterAtStart(lhs)); + ins->setOperand(1, useRegisterOrConstantAtStart(rhs)); + define(ins, mir); +} + +void LIRGeneratorPPC::lowerForALUInt64( + LInstructionHelper* ins, MDefinition* mir, + MDefinition* input) { + // Reuse the input pair: define + use-at-start would let the output pair use + // the same two registers in the opposite order. + ins->setInt64Operand(0, useInt64RegisterAtStart(input)); + defineInt64ReuseInput(ins, mir, 0); +} + +void LIRGeneratorPPC::lowerForALUInt64( + LInstructionHelper* ins, + MDefinition* mir, MDefinition* lhs, MDefinition* rhs) { + // The RHS pair is read after the first half of the output pair is written + // (addc/adde, subfc/subfe), so it cannot be an at-start use. + ins->setInt64Operand(0, useInt64RegisterAtStart(lhs)); + ins->setInt64Operand(INT64_PIECES, useInt64RegisterOrConstant(rhs)); + defineInt64ReuseInput(ins, mir, 0); +} + +void LIRGeneratorPPC::lowerForMulInt64(LMulI64* ins, MMul* mir, + MDefinition* lhs, MDefinition* rhs) { + // 64x64 multiply is mullw/mulhwu plus two cross products, which needs a + // scratch to accumulate the high word. + bool needsTemp = true; + + if (rhs->isConstant()) { + int64_t constant = rhs->toConstant()->toInt64(); + int32_t shift = mozilla::FloorLog2(uint64_t(constant)); + // See the special cases in CodeGeneratorPPC::visitMulI64. + if (constant >= -1 && constant <= 2) { + needsTemp = false; + } + if (constant > 0 && int64_t(1) << shift == constant) { + needsTemp = false; + } + } + + ins->setLhs(useInt64RegisterAtStart(lhs)); + ins->setRhs(useInt64RegisterOrConstant(rhs)); + if (needsTemp) { + ins->setTemp0(temp()); + } + + defineInt64ReuseInput(ins, mir, 0); +} + +void LIRGeneratorPPC::lowerForFPU(LInstructionHelper<1, 1, 0>* ins, + MDefinition* mir, MDefinition* input) { + ins->setOperand(0, useRegisterAtStart(input)); + define(ins, mir); +} + +void LIRGeneratorPPC::lowerForFPU(LInstructionHelper<1, 2, 0>* ins, + MDefinition* mir, MDefinition* lhs, + MDefinition* rhs) { + ins->setOperand(0, useRegisterAtStart(lhs)); + ins->setOperand(1, useRegisterAtStart(rhs)); + define(ins, mir); +} + +LBoxAllocation LIRGeneratorPPC::useBoxFixed(MDefinition* mir, Register reg1, + Register reg2, bool useAtStart) { + MOZ_ASSERT(mir->type() == MIRType::Value); + MOZ_ASSERT(reg1 != reg2); + + ensureDefined(mir); + return LBoxAllocation(LUse(reg1, mir->virtualRegister(), useAtStart), + LUse(reg2, VirtualRegisterOfPayload(mir), useAtStart)); +} + +LAllocation LIRGeneratorPPC::useByteOpRegister(MDefinition* mir) { + return useRegister(mir); +} + +LAllocation LIRGeneratorPPC::useByteOpRegisterAtStart(MDefinition* mir) { + return useRegisterAtStart(mir); +} + +LAllocation LIRGeneratorPPC::useByteOpRegisterOrNonDoubleConstant( + MDefinition* mir) { + return useRegisterOrNonDoubleConstant(mir); +} + +LDefinition LIRGeneratorPPC::tempByteOpRegister() { return temp(); } + +void LIRGeneratorPPC::lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, + LBlock* block, size_t lirIndex) { + MDefinition* operand = phi->getOperand(inputPosition); + LPhi* type = block->getPhi(lirIndex + VREG_TYPE_OFFSET); + LPhi* payload = block->getPhi(lirIndex + VREG_DATA_OFFSET); + type->setOperand( + inputPosition, + LUse(operand->virtualRegister() + VREG_TYPE_OFFSET, LUse::ANY)); + payload->setOperand(inputPosition, + LUse(VirtualRegisterOfPayload(operand), LUse::ANY)); +} + +void LIRGeneratorPPC::lowerInt64PhiInput(MPhi* phi, uint32_t inputPosition, + LBlock* block, size_t lirIndex) { + MDefinition* operand = phi->getOperand(inputPosition); + LPhi* low = block->getPhi(lirIndex + INT64LOW_INDEX); + LPhi* high = block->getPhi(lirIndex + INT64HIGH_INDEX); + low->setOperand(inputPosition, + LUse(operand->virtualRegister() + INT64LOW_INDEX, LUse::ANY)); + high->setOperand( + inputPosition, + LUse(operand->virtualRegister() + INT64HIGH_INDEX, LUse::ANY)); +} + +void LIRGeneratorPPC::defineInt64Phi(MPhi* phi, size_t lirIndex) { + LPhi* low = current->getPhi(lirIndex + INT64LOW_INDEX); + LPhi* high = current->getPhi(lirIndex + INT64HIGH_INDEX); + + uint32_t lowVreg = getVirtualRegister(); + + phi->setVirtualRegister(lowVreg); + + uint32_t highVreg = getVirtualRegister(); + MOZ_ASSERT(lowVreg + INT64HIGH_INDEX == highVreg + INT64LOW_INDEX); + + low->setDef(0, LDefinition(lowVreg, LDefinition::INT32)); + high->setDef(0, LDefinition(highVreg, LDefinition::INT32)); + annotate(high); + annotate(low); +} + +void LIRGeneratorPPC::lowerMulI(MMul* mul, MDefinition* lhs, + MDefinition* rhs) { + LMulI* lir = new (alloc()) LMulI; + if (mul->fallible()) { + assignSnapshot(lir, mul->bailoutKind()); + } + if (mul->canBeNegativeZero() && !rhs->isConstant()) { + lir->setOperand(0, useRegister(lhs)); + lir->setOperand(1, useRegister(rhs)); + define(lir, mul); + return; + } + lowerForALU(lir, mul, lhs, rhs); +} + +void LIRGeneratorPPC::lowerDivI(MDiv* div) { + if (div->rhs()->isConstant()) { + int32_t rhs = div->rhs()->toConstant()->toInt32(); + int32_t shift = FloorLog2(uint32_t(rhs)); + if (rhs > 0 && 1 << shift == rhs) { + LDivPowTwoI* lir = + new (alloc()) LDivPowTwoI(useRegister(div->lhs()), shift); + if (div->fallible()) { + assignSnapshot(lir, div->bailoutKind()); + } + define(lir, div); + return; + } + } + LDivI* lir = new (alloc()) + LDivI(useRegister(div->lhs()), useRegister(div->rhs()), temp()); + if (div->fallible()) { + assignSnapshot(lir, div->bailoutKind()); + } + define(lir, div); +} + +void LIRGeneratorPPC::lowerDivI64(MDiv* div) { + MOZ_CRASH("We use MWasmBuiltinDivI64 instead."); +} + +void LIRGeneratorPPC::lowerWasmBuiltinDivI64(MWasmBuiltinDivI64* div) { + MOZ_ASSERT(div->type() == MIRType::Int64); + + if (div->isUnsigned()) { + auto* lir = new (alloc()) + LUDivOrModI64(useInt64RegisterAtStart(div->lhs()), + useInt64RegisterAtStart(div->rhs()), + useFixedAtStart(div->instance(), InstanceReg)); + defineReturn(lir, div); + return; + } + + auto* lir = new (alloc()) LDivOrModI64( + useInt64RegisterAtStart(div->lhs()), useInt64RegisterAtStart(div->rhs()), + useFixedAtStart(div->instance(), InstanceReg)); + defineReturn(lir, div); +} + +void LIRGeneratorPPC::lowerModI(MMod* mod) { + if (mod->rhs()->isConstant()) { + int32_t rhs = mod->rhs()->toConstant()->toInt32(); + int32_t shift = FloorLog2(uint32_t(rhs)); + if (rhs > 0 && 1 << shift == rhs) { + LModPowTwoI* lir = + new (alloc()) LModPowTwoI(useRegister(mod->lhs()), shift); + if (mod->fallible()) { + assignSnapshot(lir, mod->bailoutKind()); + } + define(lir, mod); + return; + } else if (shift < 31 && (1 << (shift + 1)) - 1 == rhs) { + LModMaskI* lir = new (alloc()) + LModMaskI(useRegister(mod->lhs()), temp(), temp(), shift + 1); + if (mod->fallible()) { + assignSnapshot(lir, mod->bailoutKind()); + } + define(lir, mod); + return; + } + } + auto* lir = + new (alloc()) LModI(useRegister(mod->lhs()), useRegister(mod->rhs())); + if (mod->fallible()) { + assignSnapshot(lir, mod->bailoutKind()); + } + define(lir, mod); +} + +void LIRGeneratorPPC::lowerModI64(MMod* mod) { + MOZ_CRASH("We use MWasmBuiltinModI64 instead."); +} + +void LIRGeneratorPPC::lowerWasmBuiltinModI64(MWasmBuiltinModI64* mod) { + MOZ_ASSERT(mod->type() == MIRType::Int64); + + if (mod->isUnsigned()) { + auto* lir = new (alloc()) + LUDivOrModI64(useInt64RegisterAtStart(mod->lhs()), + useInt64RegisterAtStart(mod->rhs()), + useFixedAtStart(mod->instance(), InstanceReg)); + defineReturn(lir, mod); + return; + } + + auto* lir = new (alloc()) LDivOrModI64( + useInt64RegisterAtStart(mod->lhs()), useInt64RegisterAtStart(mod->rhs()), + useFixedAtStart(mod->instance(), InstanceReg)); + defineReturn(lir, mod); +} + +void LIRGeneratorPPC::lowerUDiv(MDiv* div) { + MDefinition* lhs = div->getOperand(0); + MDefinition* rhs = div->getOperand(1); + LUDivOrMod* lir = new (alloc()) LUDivOrMod; + lir->setOperand(0, useRegister(lhs)); + lir->setOperand(1, useRegister(rhs)); + if (div->fallible()) { + assignSnapshot(lir, div->bailoutKind()); + } + define(lir, div); +} + +void LIRGeneratorPPC::lowerUDivI64(MDiv* div) { + MOZ_CRASH("We use MWasmBuiltinDivI64 instead."); +} + +void LIRGeneratorPPC::lowerUMod(MMod* mod) { + MDefinition* lhs = mod->getOperand(0); + MDefinition* rhs = mod->getOperand(1); + LUDivOrMod* lir = new (alloc()) LUDivOrMod; + lir->setOperand(0, useRegister(lhs)); + lir->setOperand(1, useRegister(rhs)); + if (mod->fallible()) { + assignSnapshot(lir, mod->bailoutKind()); + } + define(lir, mod); +} + +void LIRGeneratorPPC::lowerUModI64(MMod* mod) { + MOZ_CRASH("We use MWasmBuiltinModI64 instead."); +} + +void LIRGeneratorPPC::lowerUrshD(MUrsh* mir) { + MDefinition* lhs = mir->lhs(); + MDefinition* rhs = mir->rhs(); + MOZ_ASSERT(lhs->type() == MIRType::Int32); + MOZ_ASSERT(rhs->type() == MIRType::Int32); + auto* lir = new (alloc()) LUrshD(useRegisterAtStart(lhs), + useRegisterOrConstantAtStart(rhs), temp()); + define(lir, mir); +} + +void LIRGeneratorPPC::lowerPowOfTwoI(MPow* mir) { + int32_t base = mir->input()->toConstant()->toInt32(); + MDefinition* power = mir->power(); + auto* lir = new (alloc()) LPowOfTwoI(useRegister(power), base); + assignSnapshot(lir, mir->bailoutKind()); + define(lir, mir); +} + +void LIRGeneratorPPC::lowerBigIntPtrDiv(MBigIntPtrDiv* ins) { + auto* lir = new (alloc()) + LBigIntPtrDiv(useRegister(ins->lhs()), useRegister(ins->rhs()), + LDefinition::BogusTemp(), LDefinition::BogusTemp()); + assignSnapshot(lir, ins->bailoutKind()); + define(lir, ins); +} + +void LIRGeneratorPPC::lowerBigIntPtrMod(MBigIntPtrMod* ins) { + auto* lir = new (alloc()) + LBigIntPtrMod(useRegister(ins->lhs()), useRegister(ins->rhs()), temp(), + LDefinition::BogusTemp()); + if (ins->canBeDivideByZero()) { + assignSnapshot(lir, ins->bailoutKind()); + } + define(lir, ins); +} + +void LIRGeneratorPPC::lowerBigIntPtrLsh(MBigIntPtrLsh* ins) { + auto* lir = new (alloc()) LBigIntPtrLsh( + useRegister(ins->lhs()), useRegister(ins->rhs()), temp(), temp()); + assignSnapshot(lir, ins->bailoutKind()); + define(lir, ins); +} + +void LIRGeneratorPPC::lowerBigIntPtrRsh(MBigIntPtrRsh* ins) { + auto* lir = new (alloc()) LBigIntPtrRsh( + useRegister(ins->lhs()), useRegister(ins->rhs()), temp(), temp()); + assignSnapshot(lir, ins->bailoutKind()); + define(lir, ins); +} + +// fctiwz produces a 32-bit result and is available on this target, so both +// truncations stay inline. The conversion goes through ScratchDoubleReg and a +// stack slot in the macro assembler, so no allocatable temp is needed. +void LIRGeneratorPPC::lowerTruncateDToInt32(MTruncateToInt32* ins) { + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType::Double); + define(new (alloc()) + LTruncateDToInt32(useRegister(opd), LDefinition::BogusTemp()), + ins); +} + +void LIRGeneratorPPC::lowerTruncateFToInt32(MTruncateToInt32* ins) { + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType::Float32); + define(new (alloc()) + LTruncateFToInt32(useRegister(opd), LDefinition::BogusTemp()), + ins); +} + +void LIRGeneratorPPC::lowerBuiltinInt64ToFloatingPoint( + MBuiltinInt64ToFloatingPoint* ins) { + MOZ_CRASH("We don't use it for this architecture"); +} + +void LIRGeneratorPPC::lowerWasmSelectI(MWasmSelect* select) { + auto* lir = new (alloc()) + LWasmSelect(useRegisterAtStart(select->trueExpr()), + useAny(select->falseExpr()), useRegister(select->condExpr())); + defineReuseInput(lir, select, LWasmSelect::TrueExprIndex); +} + +void LIRGeneratorPPC::lowerWasmSelectI64(MWasmSelect* select) { + auto* lir = new (alloc()) LWasmSelectI64( + useInt64RegisterAtStart(select->trueExpr()), + useInt64Register(select->falseExpr()), useRegister(select->condExpr())); + defineInt64ReuseInput(lir, select, LWasmSelectI64::TrueExprIndex); +} + +void LIRGeneratorPPC::lowerWasmBuiltinTruncateToInt32( + MWasmBuiltinTruncateToInt32* ins) { + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType::Double || opd->type() == MIRType::Float32); + + if (opd->type() == MIRType::Double) { + define(new (alloc()) LWasmBuiltinTruncateDToInt32( + useRegister(opd), useFixed(ins->instance(), InstanceReg), + LDefinition::BogusTemp()), + ins); + return; + } + + define(new (alloc()) LWasmBuiltinTruncateFToInt32( + useRegister(opd), useFixed(ins->instance(), InstanceReg), + LDefinition::BogusTemp()), + ins); +} + +void LIRGeneratorPPC::lowerWasmBuiltinTruncateToInt64( + MWasmBuiltinTruncateToInt64* ins) { + MOZ_CRASH("We don't use it for this architecture"); +} + +void LIRGeneratorPPC::lowerAtomicLoad64(MLoadUnboxedScalar* ins) { + const LUse elements = useRegister(ins->elements()); + const LAllocation index = + useRegisterOrIndexConstant(ins->index(), ins->storageType()); + auto* lir = new (alloc()) LAtomicLoad64(elements, index); + defineInt64(lir, ins); +} + +void LIRGeneratorPPC::lowerAtomicStore64(MStoreUnboxedScalar* ins) { + LUse elements = useRegister(ins->elements()); + LAllocation index = + useRegisterOrIndexConstant(ins->index(), ins->writeType()); + LInt64Allocation value = useInt64Register(ins->value()); + add(new (alloc()) LAtomicStore64(elements, index, value), ins); +} + +// =============================================================== +// LIRGenerator::visit* implementations + +void LIRGenerator::visitBox(MBox* box) { + MDefinition* inner = box->getOperand(0); + + // Boxing a double means moving it out of the FPU into the type/payload GPR + // pair, which goes through memory on PowerPC and needs a scratch register. + if (IsFloatingPointType(inner->type())) { + defineBox(new (alloc()) LBoxFloatingPoint(useRegisterAtStart(inner), temp(), + inner->type()), + box); + return; + } + + if (box->canEmitAtUses()) { + emitAtUses(box); + return; + } + + if (inner->isConstant()) { + defineBox(new (alloc()) LValue(inner->toConstant()->toJSValue()), box); + return; + } + + LBox* lir = new (alloc()) LBox(use(inner), inner->type()); + + // Otherwise, we should not define a new register for the payload portion + // of the output, so bypass defineBox(). + uint32_t vreg = getVirtualRegister(); + + // Note that because we're using BogusTemp(), we do not change the type of + // the definition. We also do not define the first output as "TYPE", + // because it has no corresponding payload at (vreg + 1). Also note that + // although we copy the input's original type for the payload half of the + // definition, this is only for clarity. BogusTemp() definitions are + // ignored. + lir->setDef(0, LDefinition(vreg, LDefinition::GENERAL)); + lir->setDef(1, LDefinition::BogusTemp()); + box->setVirtualRegister(vreg); + addUnchecked(lir); +} + +void LIRGenerator::visitUnbox(MUnbox* unbox) { + MDefinition* inner = unbox->getOperand(0); + + // An unbox reads a type tag and a payload. Unlike most instructions + // consuming a box, we ask for the type second, so that the result can re-use + // the first input. + MOZ_ASSERT(inner->type() == MIRType::Value); + + ensureDefined(inner); + + if (IsFloatingPointType(unbox->type())) { + MOZ_ASSERT(unbox->type() == MIRType::Double); + auto* lir = new (alloc()) LUnboxFloatingPoint(useBox(inner)); + if (unbox->fallible()) { + assignSnapshot(lir, unbox->bailoutKind()); + } + define(lir, unbox); + return; + } + + // Swap the order we use the box pieces so we can re-use the payload register. + LUnbox* lir = new (alloc()) LUnbox; + lir->setOperand(0, usePayloadInRegisterAtStart(inner)); + lir->setOperand(1, useType(inner, LUse::REGISTER)); + + if (unbox->fallible()) { + assignSnapshot(lir, unbox->bailoutKind()); + } + + // Types and payloads form two separate intervals. If the type becomes dead + // before the payload, it could be used as a Value without the type being + // recoverable. Unbox's purpose is to eagerly kill the definition of a type + // tag, so keeping both alive (for the purpose of gcmaps) is unappealing. + // Instead, we create a new virtual register. + defineReuseInput(lir, unbox, 0); +} + +void LIRGenerator::visitCopySign(MCopySign* ins) { + MDefinition* lhs = ins->lhs(); + MDefinition* rhs = ins->rhs(); + + MOZ_ASSERT(IsFloatingPointType(lhs->type())); + MOZ_ASSERT(lhs->type() == rhs->type()); + MOZ_ASSERT(lhs->type() == ins->type()); + + LInstructionHelper<1, 2, 0>* lir; + if (lhs->type() == MIRType::Double) { + lir = new (alloc()) LCopySignD(); + } else { + lir = new (alloc()) LCopySignF(); + } + + lowerForFPU(lir, ins, lhs, rhs); +} + +void LIRGenerator::visitExtendInt32ToInt64(MExtendInt32ToInt64* ins) { + auto* lir = + new (alloc()) LExtendInt32ToInt64(useRegisterAtStart(ins->input())); + defineInt64(lir, ins); + + // The low half of the result is the input, so reuse its register. + LDefinition def(LDefinition::GENERAL, LDefinition::MUST_REUSE_INPUT); + def.setReusedInput(0); + def.setVirtualRegister(ins->virtualRegister()); + + lir->setDef(INT64LOW_INDEX, def); +} + +void LIRGenerator::visitSignExtendInt64(MSignExtendInt64* ins) { + defineInt64(new (alloc()) + LSignExtendInt64(useInt64RegisterAtStart(ins->input())), + ins); +} + +void LIRGenerator::visitInt64ToFloatingPoint(MInt64ToFloatingPoint* ins) { + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType::Int64); + MOZ_ASSERT(IsFloatingPointType(ins->type())); + define(new (alloc()) LInt64ToFloatingPoint(useInt64Register(opd)), ins); +} + +void LIRGenerator::visitSubstr(MSubstr* ins) { + LSubstr* lir = new (alloc()) + LSubstr(useRegister(ins->string()), useRegister(ins->begin()), + useRegister(ins->length()), temp(), temp(), temp()); + define(lir, ins); + assignSafepoint(lir, ins); +} + +void LIRGenerator::visitReturnImpl(MDefinition* opd, bool isGenerator) { + MOZ_ASSERT(opd->type() == MIRType::Value); + + LReturn* ins = new (alloc()) LReturn(isGenerator); + ins->setOperand(0, LUse(JSReturnReg_Type)); + ins->setOperand(1, LUse(JSReturnReg_Data)); + fillBoxUses(ins, 0, opd); + add(ins); +} + +void LIRGenerator::visitCompareExchangeTypedArrayElement( + MCompareExchangeTypedArrayElement* ins) { + MOZ_ASSERT(!Scalar::isFloatingType(ins->arrayType())); + MOZ_ASSERT(ins->elements()->type() == MIRType::Elements); + MOZ_ASSERT(ins->index()->type() == MIRType::IntPtr); + + const LUse elements = useRegister(ins->elements()); + const LAllocation index = + useRegisterOrIndexConstant(ins->index(), ins->arrayType()); + + if (Scalar::isBigIntType(ins->arrayType())) { + LInt64Allocation oldval = useInt64Register(ins->oldval()); + LInt64Allocation newval = useInt64Register(ins->newval()); + + auto* lir = new (alloc()) + LCompareExchangeTypedArrayElement64(elements, index, oldval, newval); + defineInt64(lir, ins); + return; + } + + const LAllocation oldval = useRegister(ins->oldval()); + const LAllocation newval = useRegister(ins->newval()); + + LDefinition outTemp = LDefinition::BogusTemp(); + LDefinition valueTemp = LDefinition::BogusTemp(); + LDefinition offsetTemp = LDefinition::BogusTemp(); + LDefinition maskTemp = LDefinition::BogusTemp(); + + if (ins->arrayType() == Scalar::Uint32 && IsFloatingPointType(ins->type())) { + outTemp = temp(); + } + + // This target has only word-granularity lwarx/stwcx., so a sub-word CAS + // needs the round-down + shift + mask sequence and all three temps. + if (Scalar::byteSize(ins->arrayType()) < 4) { + valueTemp = temp(); + offsetTemp = temp(); + maskTemp = temp(); + } + + LCompareExchangeTypedArrayElement* lir = new (alloc()) + LCompareExchangeTypedArrayElement(elements, index, oldval, newval, + outTemp, valueTemp, offsetTemp, + maskTemp); + + define(lir, ins); +} + +void LIRGenerator::visitAtomicExchangeTypedArrayElement( + MAtomicExchangeTypedArrayElement* ins) { + MOZ_ASSERT(ins->elements()->type() == MIRType::Elements); + MOZ_ASSERT(ins->index()->type() == MIRType::IntPtr); + + const LUse elements = useRegister(ins->elements()); + const LAllocation index = + useRegisterOrIndexConstant(ins->index(), ins->arrayType()); + + if (Scalar::isBigIntType(ins->arrayType())) { + LInt64Allocation value = useInt64Register(ins->value()); + + auto* lir = new (alloc()) + LAtomicExchangeTypedArrayElement64(elements, index, value); + defineInt64(lir, ins); + return; + } + + MOZ_ASSERT(ins->arrayType() <= Scalar::Uint32); + + const LAllocation value = useRegister(ins->value()); + + LDefinition outTemp = LDefinition::BogusTemp(); + LDefinition valueTemp = LDefinition::BogusTemp(); + LDefinition offsetTemp = LDefinition::BogusTemp(); + LDefinition maskTemp = LDefinition::BogusTemp(); + + if (ins->arrayType() == Scalar::Uint32) { + MOZ_ASSERT(ins->type() == MIRType::Double); + outTemp = temp(); + } + + // Word-granularity lwarx/stwcx. only: sub-word exchange needs all three + // temps for the round-down + shift + mask sequence. + if (Scalar::byteSize(ins->arrayType()) < 4) { + valueTemp = temp(); + offsetTemp = temp(); + maskTemp = temp(); + } + + LAtomicExchangeTypedArrayElement* lir = + new (alloc()) LAtomicExchangeTypedArrayElement( + elements, index, value, outTemp, valueTemp, offsetTemp, maskTemp); + + define(lir, ins); +} + +void LIRGenerator::visitAtomicTypedArrayElementBinop( + MAtomicTypedArrayElementBinop* ins) { + MOZ_ASSERT(ins->arrayType() != Scalar::Uint8Clamped); + MOZ_ASSERT(!Scalar::isFloatingType(ins->arrayType())); + MOZ_ASSERT(ins->elements()->type() == MIRType::Elements); + MOZ_ASSERT(ins->index()->type() == MIRType::IntPtr); + + const LUse elements = useRegister(ins->elements()); + const LAllocation index = + useRegisterOrIndexConstant(ins->index(), ins->arrayType()); + + if (Scalar::isBigIntType(ins->arrayType())) { + LInt64Allocation value = useInt64Register(ins->value()); + LInt64Definition temp = tempInt64(); + + if (ins->isForEffect()) { + auto* lir = new (alloc()) LAtomicTypedArrayElementBinopForEffect64( + elements, index, value, temp); + add(lir, ins); + return; + } + + auto* lir = new (alloc()) + LAtomicTypedArrayElementBinop64(elements, index, value, temp); + defineInt64(lir, ins); + return; + } + + LAllocation value = useRegister(ins->value()); + LDefinition valueTemp = LDefinition::BogusTemp(); + LDefinition offsetTemp = LDefinition::BogusTemp(); + LDefinition maskTemp = LDefinition::BogusTemp(); + + // Word-granularity lwarx/stwcx. only: sub-word binops need all three temps + // for the round-down + shift + mask sequence, in both variants. + if (Scalar::byteSize(ins->arrayType()) < 4) { + valueTemp = temp(); + offsetTemp = temp(); + maskTemp = temp(); + } + + if (ins->isForEffect()) { + LAtomicTypedArrayElementBinopForEffect* lir = + new (alloc()) LAtomicTypedArrayElementBinopForEffect( + elements, index, value, valueTemp, offsetTemp, maskTemp); + add(lir, ins); + return; + } + + LDefinition outTemp = LDefinition::BogusTemp(); + + if (ins->arrayType() == Scalar::Uint32 && IsFloatingPointType(ins->type())) { + outTemp = temp(); + } + + LAtomicTypedArrayElementBinop* lir = + new (alloc()) LAtomicTypedArrayElementBinop( + elements, index, value, outTemp, valueTemp, offsetTemp, maskTemp); + define(lir, ins); +} +void LIRGenerator::visitAsmJSLoadHeap(MAsmJSLoadHeap* ins) { + MDefinition* base = ins->base(); + MOZ_ASSERT(base->type() == MIRType::Int32); + + MDefinition* boundsCheckLimit = ins->boundsCheckLimit(); + MOZ_ASSERT_IF(ins->needsBoundsCheck(), + boundsCheckLimit->type() == MIRType::Int32); + + LAllocation baseAlloc = useRegisterAtStart(base); + + LAllocation limitAlloc = ins->needsBoundsCheck() + ? useRegisterAtStart(boundsCheckLimit) + : LAllocation(); + + MOZ_ASSERT(!ins->hasMemoryBase()); + auto* lir = + new (alloc()) LAsmJSLoadHeap(baseAlloc, limitAlloc, LAllocation()); + define(lir, ins); +} +void LIRGenerator::visitAsmJSStoreHeap(MAsmJSStoreHeap* ins) { + MDefinition* base = ins->base(); + MOZ_ASSERT(base->type() == MIRType::Int32); + + MDefinition* boundsCheckLimit = ins->boundsCheckLimit(); + MOZ_ASSERT_IF(ins->needsBoundsCheck(), + boundsCheckLimit->type() == MIRType::Int32); + + LAllocation baseAlloc = useRegisterAtStart(base); + + LAllocation limitAlloc = ins->needsBoundsCheck() + ? useRegisterAtStart(boundsCheckLimit) + : LAllocation(); + + MOZ_ASSERT(!ins->hasMemoryBase()); + add(new (alloc()) LAsmJSStoreHeap(baseAlloc, useRegisterAtStart(ins->value()), + limitAlloc, LAllocation()), + ins); +} +void LIRGenerator::visitWasmLoad(MWasmLoad* ins) { + MDefinition* base = ins->base(); + MOZ_ASSERT(base->type() == MIRType::Int32 || base->type() == MIRType::Int64); + + if (ins->type() == MIRType::Int64) { + // The output is a register pair which may be assigned over an at-start + // input, so keep the memory base live and always copy the pointer: the + // high word is loaded from ptr + 4. + LAllocation memoryBase = ins->hasMemoryBase() + ? LAllocation(useRegister(ins->memoryBase())) + : LGeneralReg(HeapReg); + auto* lir = new (alloc()) LWasmLoadI64(useRegisterAtStart(base), memoryBase, + tempCopy(base, 0)); + defineInt64(lir, ins); + return; + } + + LAllocation memoryBase = + ins->hasMemoryBase() ? LAllocation(useRegisterAtStart(ins->memoryBase())) + : LGeneralReg(HeapReg); + + LDefinition ptrCopy = LDefinition::BogusTemp(); + if (ins->access().offset32()) { + ptrCopy = tempCopy(base, 0); + } + + auto* lir = + new (alloc()) LWasmLoad(useRegisterAtStart(base), memoryBase, ptrCopy); + define(lir, ins); +} +void LIRGenerator::visitWasmStore(MWasmStore* ins) { + MDefinition* base = ins->base(); + MOZ_ASSERT(base->type() == MIRType::Int32 || base->type() == MIRType::Int64); + + MDefinition* value = ins->value(); + LAllocation memoryBase = + ins->hasMemoryBase() ? LAllocation(useRegisterAtStart(ins->memoryBase())) + : LGeneralReg(HeapReg); + + LAllocation baseAlloc = useRegisterAtStart(base); + + if (ins->access().type() == Scalar::Int64) { + // The high word is stored at ptr + 4, so the pointer always needs a copy. + LInt64Allocation valueAlloc = useInt64RegisterAtStart(value); + auto* lir = new (alloc()) + LWasmStoreI64(baseAlloc, valueAlloc, memoryBase, tempCopy(base, 0)); + add(lir, ins); + return; + } + + LDefinition ptrCopy = LDefinition::BogusTemp(); + if (ins->access().offset32()) { + ptrCopy = tempCopy(base, 0); + } + + // A narrowing store of an i64 (i64.store8/16/32) only writes the low word. + LAllocation valueAlloc = value->type() == MIRType::Int64 + ? LAllocation(useLowWordRegisterAtStart(value)) + : useRegisterAtStart(value); + auto* lir = + new (alloc()) LWasmStore(baseAlloc, valueAlloc, memoryBase, ptrCopy); + add(lir, ins); +} +void LIRGenerator::visitWasmTruncateToInt64(MWasmTruncateToInt64* ins) { + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType::Double || opd->type() == MIRType::Float32); + + defineInt64(new (alloc()) LWasmTruncateToInt64(useRegister(opd)), ins); +} +void LIRGenerator::visitWasmUnsignedToDouble(MWasmUnsignedToDouble* ins) { + MOZ_ASSERT(ins->input()->type() == MIRType::Int32); + LWasmUint32ToDouble* lir = + new (alloc()) LWasmUint32ToDouble(useRegisterAtStart(ins->input())); + define(lir, ins); +} +void LIRGenerator::visitWasmUnsignedToFloat32(MWasmUnsignedToFloat32* ins) { + MOZ_ASSERT(ins->input()->type() == MIRType::Int32); + LWasmUint32ToFloat32* lir = + new (alloc()) LWasmUint32ToFloat32(useRegisterAtStart(ins->input())); + define(lir, ins); +} +void LIRGenerator::visitWasmCompareExchangeHeap(MWasmCompareExchangeHeap* ins) { + MDefinition* base = ins->base(); + MOZ_ASSERT(base->type() == MIRType::Int32 || base->type() == MIRType::Int64); + LAllocation memoryBase = ins->hasMemoryBase() + ? LAllocation(useRegister(ins->memoryBase())) + : LGeneralReg(HeapReg); + + if (ins->access().type() == Scalar::Int64) { + auto* lir = new (alloc()) LWasmCompareExchangeI64( + useRegister(base), useInt64Register(ins->oldValue()), + useInt64Register(ins->newValue()), memoryBase); + defineInt64(lir, ins); + return; + } + + LDefinition valueTemp = LDefinition::BogusTemp(); + LDefinition offsetTemp = LDefinition::BogusTemp(); + LDefinition maskTemp = LDefinition::BogusTemp(); + + // Word-granularity lwarx/stwcx. only, so sub-word wasm CAS needs all three + // temps for the round-down + shift + mask sequence. + if (ins->access().byteSize() < 4) { + valueTemp = temp(); + offsetTemp = temp(); + maskTemp = temp(); + } + + auto* lir = new (alloc()) + LWasmCompareExchangeHeap(useRegister(base), useRegister(ins->oldValue()), + useRegister(ins->newValue()), memoryBase, + valueTemp, offsetTemp, maskTemp); + + define(lir, ins); +} +void LIRGenerator::visitWasmAtomicExchangeHeap(MWasmAtomicExchangeHeap* ins) { + MDefinition* base = ins->base(); + MOZ_ASSERT(base->type() == MIRType::Int32 || base->type() == MIRType::Int64); + LAllocation memoryBase = ins->hasMemoryBase() + ? LAllocation(useRegister(ins->memoryBase())) + : LGeneralReg(HeapReg); + + if (ins->access().type() == Scalar::Int64) { + auto* lir = new (alloc()) LWasmAtomicExchangeI64( + useRegister(base), useInt64Register(ins->value()), memoryBase); + defineInt64(lir, ins); + return; + } + + LDefinition valueTemp = LDefinition::BogusTemp(); + LDefinition offsetTemp = LDefinition::BogusTemp(); + LDefinition maskTemp = LDefinition::BogusTemp(); + + // Word-granularity lwarx/stwcx. only, so sub-word wasm exchange needs all + // three temps for the round-down + shift + mask sequence. + if (ins->access().byteSize() < 4) { + valueTemp = temp(); + offsetTemp = temp(); + maskTemp = temp(); + } + + auto* lir = new (alloc()) + LWasmAtomicExchangeHeap(useRegister(base), useRegister(ins->value()), + memoryBase, valueTemp, offsetTemp, maskTemp); + define(lir, ins); +} +void LIRGenerator::visitWasmAtomicBinopHeap(MWasmAtomicBinopHeap* ins) { + MDefinition* base = ins->base(); + MOZ_ASSERT(base->type() == MIRType::Int32 || base->type() == MIRType::Int64); + LAllocation memoryBase = ins->hasMemoryBase() + ? LAllocation(useRegister(ins->memoryBase())) + : LGeneralReg(HeapReg); + + if (ins->access().type() == Scalar::Int64) { + auto* lir = new (alloc()) + LWasmAtomicBinopI64(useRegister(base), useInt64Register(ins->value()), + memoryBase, tempInt64()); + defineInt64(lir, ins); + return; + } + + LDefinition valueTemp = LDefinition::BogusTemp(); + LDefinition offsetTemp = LDefinition::BogusTemp(); + LDefinition maskTemp = LDefinition::BogusTemp(); + + // Word-granularity lwarx/stwcx. only, so sub-word wasm binops need all three + // temps for the round-down + shift + mask sequence, in both variants. + if (ins->access().byteSize() < 4) { + valueTemp = temp(); + offsetTemp = temp(); + maskTemp = temp(); + } + + if (!ins->hasUses()) { + LWasmAtomicBinopHeapForEffect* lir = new (alloc()) + LWasmAtomicBinopHeapForEffect(useRegister(base), + useRegister(ins->value()), memoryBase, + valueTemp, offsetTemp, maskTemp); + add(lir, ins); + return; + } + + auto* lir = new (alloc()) + LWasmAtomicBinopHeap(useRegister(base), useRegister(ins->value()), + memoryBase, valueTemp, offsetTemp, maskTemp); + + define(lir, ins); +} + +// SIMD lowering +void LIRGenerator::visitWasmTernarySimd128(MWasmTernarySimd128* ins) { +#ifdef ENABLE_WASM_SIMD + MOZ_ASSERT(ins->type() == MIRType::Simd128); + // useRegister for v0/v1 and useRegisterAtStart only for v2 — matches + // ARM64's V128Bitselect policy. defineReuseInput requires the reused + // input to be useRegisterAtStart and the others to remain alive + // (useRegister); reusing all three policies as useRegisterAtStart + // trips the allocator's "*def->output() != alloc" assertion because + // v0/v1 may then share the slot with the output. + LDefinition temp0 = LDefinition::BogusTemp(); + if (ins->simdOp() == wasm::SimdOp::I32x4RelaxedDotI8x16I7x16AddS) { + temp0 = tempSimd128(); + } + auto* lir = new (alloc()) LWasmTernarySimd128( + useRegister(ins->v0()), useRegister(ins->v1()), + useRegisterAtStart(ins->v2()), temp0, + ins->simdOp()); + // The PPC visitor (CodeGenerator-ppc.cpp:visitWasmTernarySimd128) + // emits the FMA / DOT_THEN_ADD chain with v2 as the implicit + // accumulator. defineReuseInput tells the allocator to put `dest` + // in v2's slot, eliminating the previous conditional moveSimd128. + defineReuseInput(lir, ins, LWasmTernarySimd128::V2Index); +#else + MOZ_CRASH("No SIMD"); +#endif +} +void LIRGenerator::visitWasmBinarySimd128(MWasmBinarySimd128* ins) { +#ifdef ENABLE_WASM_SIMD + MOZ_ASSERT(ins->type() == MIRType::Simd128); + LDefinition temp0 = LDefinition::BogusTemp(); + LDefinition temp1 = LDefinition::BogusTemp(); + // Only FP min/max need SIMD temps, for the wasm NaN-canonicalisation dance. + // VMX has no double-precision vector arithmetic, so the f64x2 cases and + // i64x2.mul are emitted as scalar FPR/GPR sequences using internal + // scratches rather than the temps below. + if (ins->simdOp() == wasm::SimdOp::F32x4Min || + ins->simdOp() == wasm::SimdOp::F32x4Max || + ins->simdOp() == wasm::SimdOp::F64x2Min || + ins->simdOp() == wasm::SimdOp::F64x2Max) { + temp0 = tempSimd128(); + temp1 = tempSimd128(); + } + auto* lir = new (alloc()) LWasmBinarySimd128( + useRegisterAtStart(ins->lhs()), useRegisterAtStart(ins->rhs()), + temp0, temp1, ins->simdOp()); + define(lir, ins); +#else + MOZ_CRASH("No SIMD"); +#endif +} +void LIRGenerator::visitWasmBinarySimd128WithConstant( + MWasmBinarySimd128WithConstant* ins) { +#ifdef ENABLE_WASM_SIMD + MOZ_ASSERT(ins->type() == MIRType::Simd128); + auto* lir = new (alloc()) LWasmBinarySimd128WithConstant( + useRegisterAtStart(ins->lhs()), LDefinition::BogusTemp(), ins->rhs()); + define(lir, ins); +#else + MOZ_CRASH("No SIMD"); +#endif +} +void LIRGenerator::visitWasmShiftSimd128(MWasmShiftSimd128* ins) { +#ifdef ENABLE_WASM_SIMD + MOZ_ASSERT(ins->type() == MIRType::Simd128); + MOZ_ASSERT(ins->rhs()->type() == MIRType::Int32); + + if (ins->rhs()->isConstant()) { + int32_t shiftCountMask; + switch (ins->simdOp()) { + case wasm::SimdOp::I8x16Shl: + case wasm::SimdOp::I8x16ShrU: + case wasm::SimdOp::I8x16ShrS: + shiftCountMask = 7; + break; + case wasm::SimdOp::I16x8Shl: + case wasm::SimdOp::I16x8ShrU: + case wasm::SimdOp::I16x8ShrS: + shiftCountMask = 15; + break; + case wasm::SimdOp::I32x4Shl: + case wasm::SimdOp::I32x4ShrU: + case wasm::SimdOp::I32x4ShrS: + shiftCountMask = 31; + break; + case wasm::SimdOp::I64x2Shl: + case wasm::SimdOp::I64x2ShrU: + case wasm::SimdOp::I64x2ShrS: + shiftCountMask = 63; + break; + default: + MOZ_CRASH("Unexpected shift operation"); + } + int32_t shiftCount = ins->rhs()->toConstant()->toInt32() & shiftCountMask; +#ifdef DEBUG + js::wasm::ReportSimdAnalysis("shift -> constant shift"); +#endif + auto* lir = new (alloc()) + LWasmConstantShiftSimd128(useRegisterAtStart(ins->lhs()), shiftCount); + define(lir, ins); + } else { +#ifdef DEBUG + js::wasm::ReportSimdAnalysis("shift -> variable shift"); +#endif + auto* lir = new (alloc()) LWasmVariableShiftSimd128( + useRegisterAtStart(ins->lhs()), useRegisterAtStart(ins->rhs())); + define(lir, ins); + } +#else + MOZ_CRASH("No SIMD"); +#endif +} +#ifdef ENABLE_WASM_SIMD +// Helper: reconstruct raw Wasm byte lane indices from analyzed SimdShuffle. +static SimdConstant ReconstructShuffleBytes(const SimdShuffle& s) { + int8_t bytes[16]; + // Handle SimdShuffleOp (two-operand patterns). + if (s.shuffleOp) { + switch (*s.shuffleOp) { + case SimdShuffleOp::CONCAT_RIGHT_SHIFT_8x16: { + // control[0] = suffix length. ARM64 uses 16-count as the EXT shift. + // Reconstruct raw byte indices: EXT(rhs, lhs, 16-count) = + // take (16-count) bytes from rhs end, then count bytes from lhs start. + uint8_t count = 16 - reinterpret_cast(s.control.bytes())[0]; + for (int i = 0; i < 16; i++) { + int idx = i + count; + bytes[i] = (idx < 16) ? (idx + 16) : (idx - 16); + } + return SimdConstant::CreateX16(bytes); + } + case SimdShuffleOp::BLEND_8x16: { + // control has 0 (lhs) or -1 (rhs) per byte. + const int8_t* mask = reinterpret_cast(s.control.bytes()); + for (int i = 0; i < 16; i++) + bytes[i] = mask[i] ? (i + 16) : i; + return SimdConstant::CreateX16(bytes); + } + case SimdShuffleOp::BLEND_16x8: { + const int16_t* mask = reinterpret_cast(s.control.bytes()); + for (int i = 0; i < 8; i++) { + int base = mask[i] ? (i * 2 + 16) : (i * 2); + bytes[i * 2] = base; + bytes[i * 2 + 1] = base + 1; + } + return SimdConstant::CreateX16(bytes); + } +#define INTERLEAVE(name, width, low_start, count) \ + case SimdShuffleOp::name: { \ + for (int i = 0; i < count; i++) { \ + int lhsIdx = low_start + i * width; \ + int rhsIdx = lhsIdx + 16; \ + for (int j = 0; j < width; j++) { \ + bytes[(i * 2) * width + j] = lhsIdx + j; \ + bytes[(i * 2 + 1) * width + j] = rhsIdx + j; \ + } \ + } \ + return SimdConstant::CreateX16(bytes); \ + } + INTERLEAVE(INTERLEAVE_LOW_8x16, 1, 0, 8) + INTERLEAVE(INTERLEAVE_HIGH_8x16, 1, 8, 8) + INTERLEAVE(INTERLEAVE_LOW_16x8, 2, 0, 4) + INTERLEAVE(INTERLEAVE_HIGH_16x8, 2, 8, 4) + INTERLEAVE(INTERLEAVE_LOW_32x4, 4, 0, 2) + INTERLEAVE(INTERLEAVE_HIGH_32x4, 4, 8, 2) + INTERLEAVE(INTERLEAVE_LOW_64x2, 8, 0, 1) + INTERLEAVE(INTERLEAVE_HIGH_64x2, 8, 8, 1) +#undef INTERLEAVE + default: + break; + } + } + // PERMUTE_8x16, SHUFFLE_BLEND_8x16, etc: control should have raw byte indices. + // Force to Int8x16 type to avoid assertions from mismatched types. + if (s.control.type() == SimdConstant::Int8x16) { + return s.control; + } + // Fallback: re-create as Int8x16 from raw bytes. + memcpy(bytes, s.control.bytes(), 16); + return SimdConstant::CreateX16(bytes); +} + +#endif // ENABLE_WASM_SIMD + +void LIRGenerator::visitWasmShuffleSimd128(MWasmShuffleSimd128* ins) { +#ifdef ENABLE_WASM_SIMD + MOZ_ASSERT(ins->type() == MIRType::Simd128); + SimdShuffle s = ins->shuffle(); + switch (s.opd) { + case SimdShuffle::Operand::LEFT: + case SimdShuffle::Operand::RIGHT: { + // Single-operand permute: the analysis has identified that only one + // input matters (the other is zero or unused). + LAllocation src; + if (s.opd == SimdShuffle::Operand::LEFT) { + src = useRegisterAtStart(ins->lhs()); + } else { + src = useRegisterAtStart(ins->rhs()); + } + auto* lir = + new (alloc()) LWasmPermuteSimd128(src, *s.permuteOp, s.control); + define(lir, ins); + break; + } + case SimdShuffle::Operand::BOTH: + case SimdShuffle::Operand::BOTH_SWAPPED: { + SimdConstant ctrl = ReconstructShuffleBytes(s); + LAllocation lhs, rhs; + if (s.opd == SimdShuffle::Operand::BOTH_SWAPPED) { + lhs = useRegisterAtStart(ins->rhs()); + rhs = useRegisterAtStart(ins->lhs()); + } else { + lhs = useRegisterAtStart(ins->lhs()); + rhs = useRegisterAtStart(ins->rhs()); + } + auto* lir = new (alloc()) LWasmShuffleSimd128( + lhs, rhs, *s.shuffleOp, ctrl); + define(lir, ins); + break; + } + } +#else + MOZ_CRASH("No SIMD"); +#endif +} +void LIRGenerator::visitWasmReplaceLaneSimd128(MWasmReplaceLaneSimd128* ins) { +#ifdef ENABLE_WASM_SIMD + MOZ_ASSERT(ins->type() == MIRType::Simd128); + if (ins->rhs()->type() == MIRType::Int64) { + auto* lir = new (alloc()) LWasmReplaceInt64LaneSimd128( + useRegisterAtStart(ins->lhs()), useInt64Register(ins->rhs())); + defineReuseInput(lir, ins, LWasmReplaceInt64LaneSimd128::LhsIndex); + } else { + auto* lir = new (alloc()) LWasmReplaceLaneSimd128( + useRegisterAtStart(ins->lhs()), useRegister(ins->rhs())); + defineReuseInput(lir, ins, LWasmReplaceLaneSimd128::LhsIndex); + } +#else + MOZ_CRASH("No SIMD"); +#endif +} +void LIRGenerator::visitWasmScalarToSimd128(MWasmScalarToSimd128* ins) { +#ifdef ENABLE_WASM_SIMD + MOZ_ASSERT(ins->type() == MIRType::Simd128); + if (ins->input()->type() == MIRType::Int64) { + auto* lir = + new (alloc()) LWasmInt64ToSimd128(useInt64RegisterAtStart(ins->input())); + define(lir, ins); + } else { + auto* lir = + new (alloc()) LWasmScalarToSimd128(useRegisterAtStart(ins->input())); + define(lir, ins); + } +#else + MOZ_CRASH("No SIMD"); +#endif +} +void LIRGenerator::visitWasmUnarySimd128(MWasmUnarySimd128* ins) { +#ifdef ENABLE_WASM_SIMD + MOZ_ASSERT(ins->type() == MIRType::Simd128); + auto* lir = new (alloc()) + LWasmUnarySimd128(useRegisterAtStart(ins->input()), + LDefinition::BogusTemp()); + define(lir, ins); +#else + MOZ_CRASH("No SIMD"); +#endif +} +#ifdef ENABLE_WASM_SIMD +bool LIRGeneratorPPC::canFoldReduceSimd128AndBranch(wasm::SimdOp op) { + switch (op) { + case wasm::SimdOp::V128AnyTrue: + case wasm::SimdOp::I8x16AllTrue: + case wasm::SimdOp::I16x8AllTrue: + case wasm::SimdOp::I32x4AllTrue: + case wasm::SimdOp::I64x2AllTrue: + return true; + default: + return false; + } +} + +bool LIRGeneratorPPC::canEmitWasmReduceSimd128AtUses( + MWasmReduceSimd128* ins) { + if (!ins->canEmitAtUses()) { + return false; + } + if (ins->type() != MIRType::Int32) { + return false; + } + if (!canFoldReduceSimd128AndBranch(ins->simdOp())) { + return false; + } + MUseIterator iter(ins->usesBegin()); + if (iter == ins->usesEnd()) { + return true; + } + MNode* node = iter->consumer(); + if (!node->isDefinition() || !node->toDefinition()->isTest()) { + return false; + } + iter++; + return iter == ins->usesEnd(); +} +#endif // ENABLE_WASM_SIMD + +void LIRGenerator::visitWasmReduceSimd128(MWasmReduceSimd128* ins) { +#ifdef ENABLE_WASM_SIMD + if (canEmitWasmReduceSimd128AtUses(ins)) { + emitAtUses(ins); + return; + } + if (ins->type() == MIRType::Int64) { + auto* lir = new (alloc()) + LWasmReduceSimd128ToInt64(useRegisterAtStart(ins->input())); + defineInt64(lir, ins); + } else { + auto* lir = + new (alloc()) LWasmReduceSimd128(useRegisterAtStart(ins->input())); + define(lir, ins); + } +#else + MOZ_CRASH("No SIMD"); +#endif +} +void LIRGenerator::visitWasmLoadLaneSimd128(MWasmLoadLaneSimd128* ins) { +#ifdef ENABLE_WASM_SIMD + LUse base = useRegisterAtStart(ins->base()); + LUse inputUse = useRegisterAtStart(ins->value()); + LAllocation memoryBase = + ins->hasMemoryBase() ? LAllocation(useRegisterAtStart(ins->memoryBase())) + : LGeneralReg(HeapReg); + auto* lir = new (alloc()) LWasmLoadLaneSimd128(base, inputUse, memoryBase); + define(lir, ins); +#else + MOZ_CRASH("No SIMD"); +#endif +} +void LIRGenerator::visitWasmStoreLaneSimd128(MWasmStoreLaneSimd128* ins) { +#ifdef ENABLE_WASM_SIMD + LUse base = useRegisterAtStart(ins->base()); + LUse input = useRegisterAtStart(ins->value()); + LAllocation memoryBase = + ins->hasMemoryBase() ? LAllocation(useRegisterAtStart(ins->memoryBase())) + : LGeneralReg(HeapReg); + auto* lir = new (alloc()) LWasmStoreLaneSimd128(base, input, memoryBase); + add(lir, ins); +#else + MOZ_CRASH("No SIMD"); +#endif +} + +// Only {U,}Int32 compare with an Int32 result is specialized: an Int64 +// compare would be a two-word sequence, and the 603 has no isel, so the +// CodeGen visitor emits cmpw/cmplw plus a short branch. +bool LIRGeneratorShared::canSpecializeWasmCompareAndSelect( + MCompare::CompareType compTy, MIRType insTy) { + return insTy == MIRType::Int32 && (compTy == MCompare::Compare_Int32 || + compTy == MCompare::Compare_UInt32); +} + +void LIRGeneratorShared::lowerWasmCompareAndSelect(MWasmSelect* ins, + MDefinition* lhs, + MDefinition* rhs, + MCompare::CompareType compTy, + JSOp jsop) { + MOZ_ASSERT(canSpecializeWasmCompareAndSelect(compTy, ins->type())); + auto* lir = new (alloc()) LWasmCompareAndSelect( + useRegister(lhs), useRegister(rhs), useRegisterAtStart(ins->trueExpr()), + useRegister(ins->falseExpr()), compTy, jsop); + defineReuseInput(lir, ins, LWasmCompareAndSelect::IfTrueExprIndex); +} + +// MIR helpers needed by the linker +#ifdef ENABLE_WASM_SIMD +bool MWasmTernarySimd128::specializeBitselectConstantMaskAsShuffle( + int8_t shuffle[16]) { + return false; +} +#endif + +bool MWasmBinarySimd128::specializeForConstantRhs() { return false; } + +#ifdef ENABLE_WASM_SIMD +bool MWasmTernarySimd128::canRelaxBitselect() { return false; } +#endif + +#ifdef ENABLE_WASM_SIMD +bool MWasmBinarySimd128::canPmaddubsw() { return false; } +#endif + +} // namespace jit +} // namespace js diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/Lowering-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/Lowering-ppc.h --- a/js/src/jit/ppc/Lowering-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/Lowering-ppc.h 2026-07-28 16:59:40.357195320 +0200 @@ -0,0 +1,110 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_Lowering_ppc_h +#define jit_ppc_Lowering_ppc_h + +#include "jit/shared/Lowering-shared.h" + +namespace js { +namespace jit { + +class LIRGeneratorPPC : public LIRGeneratorShared { + protected: + LIRGeneratorPPC(MIRGenerator* gen, MIRGraph& graph, LIRGraph& lirGraph) + : LIRGeneratorShared(gen, graph, lirGraph) {} + + // Returns a box allocation with type set to reg1 and payload set to reg2. + LBoxAllocation useBoxFixed(MDefinition* mir, Register reg1, Register reg2, + bool useAtStart = false); + + // Unlike x86, any register can be the source of a byte store or the + // destination of a byte load. + LAllocation useByteOpRegister(MDefinition* mir); + LAllocation useByteOpRegisterAtStart(MDefinition* mir); + LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition* mir); + LDefinition tempByteOpRegister(); + + inline LDefinition tempToUnbox() { return LDefinition::BogusTemp(); } + + bool needTempForPostBarrier() { return true; } + + void lowerUntypedPhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, + size_t lirIndex); + void lowerInt64PhiInput(MPhi* phi, uint32_t inputPosition, LBlock* block, + size_t lirIndex); + void defineInt64Phi(MPhi* phi, size_t lirIndex); + + void lowerForShift(LInstructionHelper<1, 2, 0>* ins, MDefinition* mir, + MDefinition* lhs, MDefinition* rhs); + void lowerUrshD(MUrsh* mir); + + void lowerPowOfTwoI(MPow* mir); + + void lowerForALU(LInstructionHelper<1, 1, 0>* ins, MDefinition* mir, + MDefinition* input); + void lowerForALU(LInstructionHelper<1, 2, 0>* ins, MDefinition* mir, + MDefinition* lhs, MDefinition* rhs); + + void lowerForALUInt64(LInstructionHelper* ins, + MDefinition* mir, MDefinition* input); + void lowerForALUInt64( + LInstructionHelper* ins, + MDefinition* mir, MDefinition* lhs, MDefinition* rhs); + void lowerForMulInt64(LMulI64* ins, MMul* mir, MDefinition* lhs, + MDefinition* rhs); + template + void lowerForShiftInt64(LInstr* ins, MDefinition* mir, MDefinition* lhs, + MDefinition* rhs); + + void lowerForFPU(LInstructionHelper<1, 1, 0>* ins, MDefinition* mir, + MDefinition* input); + void lowerForFPU(LInstructionHelper<1, 2, 0>* ins, MDefinition* mir, + MDefinition* lhs, MDefinition* rhs); + + void lowerBuiltinInt64ToFloatingPoint(MBuiltinInt64ToFloatingPoint* ins); + void lowerWasmBuiltinTruncateToInt64(MWasmBuiltinTruncateToInt64* ins); + void lowerWasmBuiltinTruncateToInt32(MWasmBuiltinTruncateToInt32* ins); + void lowerTruncateDToInt32(MTruncateToInt32* ins); + void lowerTruncateFToInt32(MTruncateToInt32* ins); + void lowerDivI(MDiv* div); + void lowerModI(MMod* mod); + void lowerDivI64(MDiv* div); + void lowerWasmBuiltinDivI64(MWasmBuiltinDivI64* div); + void lowerModI64(MMod* mod); + void lowerWasmBuiltinModI64(MWasmBuiltinModI64* mod); + void lowerUDivI64(MDiv* div); + void lowerUModI64(MMod* mod); + void lowerMulI(MMul* mul, MDefinition* lhs, MDefinition* rhs); + void lowerUDiv(MDiv* div); + void lowerUMod(MMod* mod); + void lowerWasmSelectI(MWasmSelect* select); + void lowerWasmSelectI64(MWasmSelect* select); + + void lowerBigIntPtrLsh(MBigIntPtrLsh* ins); + void lowerBigIntPtrRsh(MBigIntPtrRsh* ins); + void lowerBigIntPtrDiv(MBigIntPtrDiv* ins); + void lowerBigIntPtrMod(MBigIntPtrMod* ins); + + void lowerAtomicLoad64(MLoadUnboxedScalar* ins); + void lowerAtomicStore64(MStoreUnboxedScalar* ins); + + LTableSwitch* newLTableSwitch(const LAllocation& in, + const LDefinition& inputCopy); + LTableSwitchV* newLTableSwitchV(const LBoxAllocation& in); + +#ifdef ENABLE_WASM_SIMD + bool canFoldReduceSimd128AndBranch(wasm::SimdOp op); + bool canEmitWasmReduceSimd128AtUses(MWasmReduceSimd128* ins); +#endif +}; + +using LIRGeneratorSpecific = LIRGeneratorPPC; + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_Lowering_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/MacroAssembler-ppc-inl.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/MacroAssembler-ppc-inl.h --- a/js/src/jit/ppc/MacroAssembler-ppc-inl.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/MacroAssembler-ppc-inl.h 2026-07-29 08:24:05.944062544 +0200 @@ -0,0 +1,5943 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_MacroAssembler_ppc_inl_h +#define jit_ppc_MacroAssembler_ppc_inl_h + +#include "jit/ppc/MacroAssembler-ppc.h" + +namespace js { +namespace jit { + +// This target has no instruction that moves bits between a GPR and an FPR, so +// every such transfer goes through the reserved stack scratch frame; see +// MacroAssemblerPPC::allocStackScratch. + +// Strip the "or equal" part of a relational condition, keeping signedness. +static inline Assembler::Condition ConditionWithoutEqual( + Assembler::Condition cond) { + switch (cond) { + case Assembler::LessThan: + case Assembler::LessThanOrEqual: + return Assembler::LessThan; + case Assembler::GreaterThan: + case Assembler::GreaterThanOrEqual: + return Assembler::GreaterThan; + case Assembler::Below: + case Assembler::BelowOrEqual: + return Assembler::Below; + case Assembler::Above: + case Assembler::AboveOrEqual: + return Assembler::Above; + default: + MOZ_CRASH("unexpected condition"); + } +} + +// Map a relational condition onto its unsigned counterpart. +static inline Assembler::Condition UnsignedCondition( + Assembler::Condition cond) { + switch (cond) { + case Assembler::LessThan: + case Assembler::Below: + return Assembler::Below; + case Assembler::LessThanOrEqual: + case Assembler::BelowOrEqual: + return Assembler::BelowOrEqual; + case Assembler::GreaterThan: + case Assembler::Above: + return Assembler::Above; + case Assembler::GreaterThanOrEqual: + case Assembler::AboveOrEqual: + return Assembler::AboveOrEqual; + default: + MOZ_CRASH("unexpected condition"); + } +} + +//{{{ check_macroassembler_style + +// =============================================================== +// Move instructions + +void MacroAssembler::move64(Register64 src, Register64 dest) { + move32(src.low, dest.low); + move32(src.high, dest.high); +} + +void MacroAssembler::move64(Imm64 imm, Register64 dest) { + move32(imm.low(), dest.low); + move32(imm.hi(), dest.high); +} + +void MacroAssembler::moveDoubleToGPR64(FloatRegister src, Register64 dest) { + allocStackScratch(); + as_stfd(src, StackPointer, StackScratchSlot); + // Big endian: the high word is at the lower address. + as_lwz(dest.high, StackPointer, StackScratchSlot); + as_lwz(dest.low, StackPointer, StackScratchSlot + 4); + freeStackScratch(); +} + +void MacroAssembler::moveGPR64ToDouble(Register64 src, FloatRegister dest) { + allocStackScratch(); + as_stw(src.high, StackPointer, StackScratchSlot); + as_stw(src.low, StackPointer, StackScratchSlot + 4); + as_lfd(dest, StackPointer, StackScratchSlot); + freeStackScratch(); +} + +void MacroAssembler::moveLowDoubleToGPR(FloatRegister src, Register dest) { + allocStackScratch(); + as_stfd(src, StackPointer, StackScratchSlot); + as_lwz(dest, StackPointer, StackScratchSlot + 4); + freeStackScratch(); +} + +void MacroAssembler::move64To32(Register64 src, Register dest) { + move32(src.low, dest); +} + +void MacroAssembler::move32To64ZeroExtend(Register src, Register64 dest) { + move32(src, dest.low); + move32(Imm32(0), dest.high); +} + +void MacroAssembler::move8To64SignExtend(Register src, Register64 dest) { + as_extsb(dest.low, src); + as_srawi(dest.high, dest.low, 31); +} + +void MacroAssembler::move16To64SignExtend(Register src, Register64 dest) { + as_extsh(dest.low, src); + as_srawi(dest.high, dest.low, 31); +} + +void MacroAssembler::move32To64SignExtend(Register src, Register64 dest) { + move32(src, dest.low); + as_srawi(dest.high, dest.low, 31); +} + +void MacroAssembler::moveFloat32ToGPR(FloatRegister src, Register dest) { + // stfs rounds the double-format FPR value back to its single pattern in + // memory; reload it as a plain word. + allocStackScratch(); + as_stfs(src, StackPointer, StackScratchSlot); + as_lwz(dest, StackPointer, StackScratchSlot); + freeStackScratch(); +} + +void MacroAssembler::moveGPRToFloat32(Register src, FloatRegister dest) { + // lfs widens the raw single bits to the double-format value the FPR holds. + allocStackScratch(); + as_stw(src, StackPointer, StackScratchSlot); + as_lfs(dest, StackPointer, StackScratchSlot); + freeStackScratch(); +} + +void MacroAssembler::moveFloat16ToGPR(FloatRegister src, Register dest) { + // The FPR is used as a bit container: the FP16 pattern lives in bits 48:63 + // of the doubleword, i.e. at byte offset 6 on this big-endian target. + allocStackScratch(); + as_stfd(src, StackPointer, StackScratchSlot); + as_lhz(dest, StackPointer, StackScratchSlot + 6); + freeStackScratch(); +} + +void MacroAssembler::moveGPRToFloat16(Register src, FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + allocStackScratch(); + xs_li(scratch, 0); + as_stw(scratch, StackPointer, StackScratchSlot); + as_stw(scratch, StackPointer, StackScratchSlot + 4); + as_sth(src, StackPointer, StackScratchSlot + 6); + as_lfd(dest, StackPointer, StackScratchSlot); + freeStackScratch(); +} + +void MacroAssembler::move8ZeroExtend(Register src, Register dest) { + // rlwinm dest, src, 0, 24, 31 — mask to low 8 bits. + as_rlwinm(dest, src, 0, 24, 31); +} + +void MacroAssembler::move8SignExtend(Register src, Register dest) { + as_extsb(dest, src); +} + +void MacroAssembler::move16SignExtend(Register src, Register dest) { + as_extsh(dest, src); +} + +void MacroAssembler::move8SignExtendToPtr(Register src, Register dest) { + as_extsb(dest, src); +} + +void MacroAssembler::move16SignExtendToPtr(Register src, Register dest) { + as_extsh(dest, src); +} + +void MacroAssembler::move32SignExtendToPtr(Register src, Register dest) { + move32(src, dest); +} + +void MacroAssembler::move32ZeroExtendToPtr(Register src, Register dest) { + move32(src, dest); +} + +// =============================================================== +// Load instructions + +void MacroAssembler::load32SignExtendToPtr(const Address& src, Register dest) { + load32(src, dest); +} + +void MacroAssembler::loadAbiReturnAddress(Register dest) { xs_mflr(dest); } + +// =============================================================== +// Logical instructions + +void MacroAssembler::not32(Register reg) { x_not(reg, reg); } + +void MacroAssembler::notPtr(Register reg) { x_not(reg, reg); } + +void MacroAssembler::andPtr(Register src, Register dest) { + as_and_(dest, dest, src); +} + +// If `mask` is a non-zero, non-all-ones contiguous run of 1-bits in a +// 32-bit value (LSB-numbering), set MB/ME to the BE bit positions +// (PPC convention: bit 0 = MSB) needed by `rlwinm SH=0` and return true. +// Otherwise return false. Run-time cost is at JIT emit time only. +static inline bool IsContigMask32(uint32_t mask, unsigned& mb, unsigned& me) { + if (mask == 0 || mask == 0xFFFFFFFFu) return false; + unsigned tz = (unsigned)__builtin_ctz(mask); + uint32_t shifted = mask >> tz; + if ((shifted & (shifted + 1)) != 0) return false; // Has a 0 between 1s. + unsigned width = 32 - (unsigned)__builtin_clz(shifted); + // LSB bits set: [tz, tz+width-1]. BE bits: [32-tz-width, 31-tz]. + mb = 32 - tz - width; + me = 31 - tz; + return true; +} + +void MacroAssembler::andPtr(Imm32 imm, Register dest) { and32(imm, dest); } + +void MacroAssembler::andPtr(Imm32 imm, Register src, Register dest) { + and32(imm, src, dest); +} + +void MacroAssembler::and64(Imm64 imm, Register64 dest) { + and32(imm.low(), dest.low); + and32(imm.hi(), dest.high); +} + +void MacroAssembler::and64(Register64 src, Register64 dest) { + as_and_(dest.low, dest.low, src.low); + as_and_(dest.high, dest.high, src.high); +} + +void MacroAssembler::and32(Register src, Register dest) { + as_and_(dest, dest, src); +} + +void MacroAssembler::and32(Imm32 imm, Register dest) { + uint32_t uimm = uint32_t(imm.value); + if (is_uintN(uimm, 16)) { + as_andi_rc(dest, dest, uimm); + return; + } + unsigned mb, me; + if (IsContigMask32(uimm, mb, me)) { + // rlwinm SH=0 ANDs with the contiguous mask; the record form sets CR0 + // to match the side-effect of the andi. fast path above. + as_rlwinm_rc(dest, dest, 0, mb, me); + return; + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(imm, scratch); + as_and_(dest, dest, scratch); +} + +void MacroAssembler::and32(Imm32 imm, Register src, Register dest) { + if (src != dest) { + xs_mr(dest, src); + } + and32(imm, dest); +} + +void MacroAssembler::and32(Imm32 imm, const Address& dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(dest, scratch); + and32(imm, scratch); + store32(scratch, dest); +} + +void MacroAssembler::and32(const Address& src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(src, scratch); + as_and_(dest, dest, scratch); +} + +void MacroAssembler::or64(Imm64 imm, Register64 dest) { + or32(imm.low(), dest.low); + or32(imm.hi(), dest.high); +} + +void MacroAssembler::or32(Register src, Register dest) { + as_or_(dest, dest, src); +} + +void MacroAssembler::or32(Imm32 imm, Register dest) { + uint32_t uimm = uint32_t(imm.value); + uint16_t lo = uimm & 0xFFFF; + uint16_t hi = (uimm >> 16) & 0xFFFF; + // ori/oris zero-extend their immediates and touch no other bits, so the + // pair covers any 32-bit immediate in at most 2 insns with no scratch. + if (hi == 0) { + as_ori(dest, dest, lo); + } else if (lo == 0) { + as_oris(dest, dest, hi); + } else { + as_ori(dest, dest, lo); + as_oris(dest, dest, hi); + } +} + +void MacroAssembler::or32(Imm32 imm, Register src, Register dest) { + if (src != dest) { + xs_mr(dest, src); + } + or32(imm, dest); +} + +void MacroAssembler::or32(Imm32 imm, const Address& dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(dest, scratch); + or32(imm, scratch); + store32(scratch, dest); +} + +void MacroAssembler::xor64(Imm64 imm, Register64 dest) { + xor32(imm.low(), dest.low); + xor32(imm.hi(), dest.high); +} + +void MacroAssembler::orPtr(Register src, Register dest) { + as_or_(dest, dest, src); +} + +void MacroAssembler::orPtr(Imm32 imm, Register dest) { or32(imm, dest); } + +void MacroAssembler::orPtr(Imm32 imm, Register src, Register dest) { + or32(imm, src, dest); +} + +void MacroAssembler::or64(Register64 src, Register64 dest) { + as_or_(dest.low, dest.low, src.low); + as_or_(dest.high, dest.high, src.high); +} + +void MacroAssembler::xor64(Register64 src, Register64 dest) { + as_xor_(dest.low, dest.low, src.low); + as_xor_(dest.high, dest.high, src.high); +} + +void MacroAssembler::xorPtr(Register src, Register dest) { + as_xor_(dest, dest, src); +} + +void MacroAssembler::xorPtr(Imm32 imm, Register dest) { xor32(imm, dest); } + +void MacroAssembler::xorPtr(Imm32 imm, Register src, Register dest) { + xor32(imm, src, dest); +} + +void MacroAssembler::xor32(Register src, Register dest) { + as_xor_(dest, dest, src); +} + +void MacroAssembler::xor32(Imm32 imm, Register dest) { + uint32_t uimm = uint32_t(imm.value); + uint16_t lo = uimm & 0xFFFF; + uint16_t hi = (uimm >> 16) & 0xFFFF; + if (hi == 0) { + as_xori(dest, dest, lo); + } else if (lo == 0) { + as_xoris(dest, dest, hi); + } else { + as_xori(dest, dest, lo); + as_xoris(dest, dest, hi); + } +} + +void MacroAssembler::xor32(Imm32 imm, Register src, Register dest) { + if (src != dest) { + xs_mr(dest, src); + } + xor32(imm, dest); +} + +void MacroAssembler::xor32(Imm32 imm, const Address& dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(dest, scratch); + xor32(imm, scratch); + store32(scratch, dest); +} + +void MacroAssembler::xor32(const Address& src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(src, scratch); + xor32(scratch, dest); +} + +// =============================================================== +// Swap instructions + +void MacroAssembler::byteSwap16SignExtend(Register reg) { + // Swap the bytes of the low halfword via (reg<<8)&0xFF00 | (reg>>8)&0xFF. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_rlwinm(scratch, reg, 8, 16, 23); // scratch = (reg<<8) & 0xFF00 + as_rlwinm(reg, reg, 24, 24, 31); // reg = (reg>>8) & 0xFF + as_or_(reg, reg, scratch); + as_extsh(reg, reg); +} + +void MacroAssembler::byteSwap16ZeroExtend(Register reg) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_rlwinm(scratch, reg, 8, 16, 23); + as_rlwinm(reg, reg, 24, 24, 31); + as_or_(reg, reg, scratch); +} + +// Rotate-with-insert byte-reversal of |src| into |dst|, which must differ from +// |src|. Usable inside a lwarx/stwcx. reservation window, unlike a memory +// round-trip through lwbrx/stwbrx. +static void ByteReverse32(MacroAssembler& masm, Register dst, Register src) { + masm.as_rlwinm(dst, src, 8, 0, 31); // dst = rotl32(src, 8) + masm.as_rlwimi(dst, src, 24, 0, 7); // insert src byte 0 -> dst byte 3 + masm.as_rlwimi(dst, src, 24, 16, 23); // insert src byte 2 -> dst byte 1 +} + +void MacroAssembler::byteSwap32(Register reg) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + ByteReverse32(*this, scratch, reg); + xs_mr(reg, scratch); +} + +void MacroAssembler::byteSwap64(Register64 reg64) { + // bswap64(high||low) = bswap32(low) || bswap32(high). + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + ByteReverse32(*this, scratch, reg64.low); + ByteReverse32(*this, reg64.low, reg64.high); + xs_mr(reg64.high, scratch); +} + +// =============================================================== +// Arithmetic functions + +void MacroAssembler::addPtr(Register src, Register dest) { + as_add(dest, dest, src); +} + +void MacroAssembler::addPtr(Imm32 imm, Register dest) { + int32_t val = imm.value; + if (is_intN(val, 16)) { + as_addi(dest, dest, val); + return; + } + // addis adds the high half; the low half is a signed addi, so bias the + // high half by one when the low half is negative. + int32_t lo = int16_t(val); + int32_t hi = (val - lo) >> 16; + as_addis(dest, dest, hi); + if (lo != 0) { + as_addi(dest, dest, lo); + } +} + +void MacroAssembler::addPtr(ImmWord imm, Register dest) { + addPtr(Imm32(int32_t(imm.value)), dest); +} + +void MacroAssembler::add64(Register64 src, Register64 dest) { + as_addc(dest.low, dest.low, src.low); + as_adde(dest.high, dest.high, src.high); +} + +void MacroAssembler::add64(Imm32 imm, Register64 dest) { + // Like ARM, the immediate extends as unsigned: the high word only takes the + // carry. There is no addic/addze here, so the constant and the zero addend + // both go through registers; neither move disturbs XER[CA]. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(imm, scratch); + as_addc(dest.low, dest.low, scratch); + xs_li(scratch, 0); + as_adde(dest.high, dest.high, scratch); +} + +void MacroAssembler::add64(Imm64 imm, Register64 dest) { + if (imm.low().value == 0) { + add32(imm.hi(), dest.high); + return; + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + // move32 emits addi/addis/ori, none of which disturb XER[CA]. + move32(imm.low(), scratch); + as_addc(dest.low, dest.low, scratch); + move32(imm.hi(), scratch); + as_adde(dest.high, dest.high, scratch); +} + +void MacroAssembler::add32(Register src, Register dest) { + as_add(dest, dest, src); +} + +void MacroAssembler::add32(Imm32 imm, Register dest) { addPtr(imm, dest); } + +void MacroAssembler::add32(Imm32 imm, Register src, Register dest) { + move32(src, dest); + add32(imm, dest); +} + +void MacroAssembler::add32(Imm32 imm, const Address& dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(dest, scratch); + add32(imm, scratch); + store32(scratch, dest); +} + +void MacroAssembler::add32(const Address& src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(src, scratch); + as_add(dest, dest, scratch); +} + +void MacroAssembler::addPtr(Imm32 imm, const Address& dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(dest, scratch); + addPtr(imm, scratch); + storePtr(scratch, dest); +} + +void MacroAssembler::addPtr(const Address& src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(src, scratch); + addPtr(scratch, dest); +} + +void MacroAssembler::addDouble(FloatRegister src, FloatRegister dest) { + as_fadd(dest, dest, src); +} + +void MacroAssembler::addFloat32(FloatRegister src, FloatRegister dest) { + as_fadds(dest, dest, src); +} + +CodeOffset MacroAssembler::sub32FromStackPtrWithPatch(Register dest) { + CodeOffset offset = CodeOffset(currentOffset()); + emitLoadPtrStanza(dest, 0); + as_subf(dest, dest, StackPointer); + return offset; +} + +void MacroAssembler::patchSub32FromStackPtr(CodeOffset offset, Imm32 imm) { + Instruction* inst = (Instruction*)editSrc(BufferOffset(offset.offset())); + UpdateLoadPtrValue(inst, uintptr_t(imm.value)); +} + +void MacroAssembler::subPtr(Register src, Register dest) { + as_subf(dest, src, dest); +} + +void MacroAssembler::subPtr(Imm32 imm, Register dest) { + addPtr(Imm32(int32_t(0u - uint32_t(imm.value))), dest); +} + +void MacroAssembler::sub64(Register64 src, Register64 dest) { + // subfc/subfe compute rB - rA with a borrow chain. + as_subfc(dest.low, src.low, dest.low); + as_subfe(dest.high, src.high, dest.high); +} + +void MacroAssembler::sub64(Imm64 imm, Register64 dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(imm.low(), scratch); + as_subfc(dest.low, scratch, dest.low); + move32(imm.hi(), scratch); + as_subfe(dest.high, scratch, dest.high); +} + +void MacroAssembler::sub32(Register src, Register dest) { + as_subf(dest, src, dest); +} + +void MacroAssembler::sub32(Imm32 imm, Register dest) { + addPtr(Imm32(int32_t(0u - uint32_t(imm.value))), dest); +} + +void MacroAssembler::sub32(const Address& src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(src, scratch); + as_subf(dest, scratch, dest); +} + +void MacroAssembler::subPtr(Register src, const Address& dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(dest, scratch); + as_subf(scratch, src, scratch); + storePtr(scratch, dest); +} + +void MacroAssembler::subPtr(const Address& addr, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(addr, scratch); + as_subf(dest, scratch, dest); +} + +void MacroAssembler::subDouble(FloatRegister src, FloatRegister dest) { + as_fsub(dest, dest, src); +} + +void MacroAssembler::subFloat32(FloatRegister src, FloatRegister dest) { + as_fsubs(dest, dest, src); +} + +// 64-bit multiply on a register pair: +// low = lo(dest.low * src.low) +// high = hi_u(dest.low * src.low) +// + lo(dest.high * src.low) + lo(dest.low * src.high) +// |temp| must be distinct from every operand register. +static void Mul64(MacroAssembler& masm, Register srcLow, Register srcHigh, + Register64 dest, Register temp) { + MOZ_ASSERT(temp != dest.low && temp != dest.high); + MOZ_ASSERT(temp != srcLow && temp != srcHigh); + masm.as_mullw(temp, dest.low, srcHigh); + masm.as_mullw(dest.high, dest.high, srcLow); + masm.as_add(temp, temp, dest.high); + masm.as_mulhwu(dest.high, dest.low, srcLow); + masm.as_add(dest.high, dest.high, temp); + masm.as_mullw(dest.low, dest.low, srcLow); +} + +void MacroAssembler::mul64(const Register64& rhs, const Register64& srcDest) { + UseScratchRegisterScope temps(asMasm()); + Register temp = temps.Acquire(); + Mul64(*this, rhs.low, rhs.high, srcDest, temp); +} + +void MacroAssembler::mul64(Imm64 imm, const Register64& dest) { + UseScratchRegisterScope temps(asMasm()); + Register srcLow = temps.Acquire(); + Register srcHigh = temps.Acquire(); + move32(imm.low(), srcLow); + move32(imm.hi(), srcHigh); + // No third scratch is left, so fold the partial products by hand. + as_mullw(srcHigh, dest.low, srcHigh); // lo(dest.low * imm.hi) + as_mullw(dest.high, dest.high, srcLow); // lo(dest.high * imm.low) + as_add(srcHigh, srcHigh, dest.high); + as_mulhwu(dest.high, dest.low, srcLow); + as_add(dest.high, dest.high, srcHigh); + as_mullw(dest.low, dest.low, srcLow); +} + +void MacroAssembler::mul64(Imm64 imm, const Register64& dest, + const Register temp) { + if (temp == Register::Invalid()) { + mul64(imm, dest); + return; + } + UseScratchRegisterScope temps(asMasm()); + Register srcLow = temps.Acquire(); + Register srcHigh = temps.Acquire(); + move32(imm.low(), srcLow); + move32(imm.hi(), srcHigh); + Mul64(*this, srcLow, srcHigh, dest, temp); +} + +void MacroAssembler::mul64(const Register64& src, const Register64& dest, + const Register temp) { + if (temp == Register::Invalid()) { + mul64(src, dest); + return; + } + Mul64(*this, src.low, src.high, dest, temp); +} + +void MacroAssembler::mulPtr(Register rhs, Register srcDest) { + as_mullw(srcDest, srcDest, rhs); +} + +void MacroAssembler::mulPtr(ImmWord rhs, Register srcDest) { + if (is_intN(int32_t(rhs.value), 16)) { + as_mulli(srcDest, srcDest, int16_t(rhs.value)); + return; + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + MOZ_ASSERT(srcDest != scratch); + movePtr(rhs, scratch); + mulPtr(scratch, srcDest); +} + +void MacroAssembler::mulBy3(Register src, Register dest) { + as_mulli(dest, src, 3); +} + +void MacroAssembler::mul32(Register rhs, Register srcDest) { + as_mullw(srcDest, srcDest, rhs); +} + +void MacroAssembler::mul32(Imm32 imm, Register srcDest) { + if (is_intN(imm.value, 16)) { + as_mulli(srcDest, srcDest, imm.value); + return; + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(imm, scratch); + as_mullw(srcDest, srcDest, scratch); +} + +void MacroAssembler::mulHighUnsigned32(Imm32 imm, Register src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + MOZ_ASSERT(src != scratch); + move32(imm, scratch); + as_mulhwu(dest, src, scratch); +} + +void MacroAssembler::mulFloat32(FloatRegister src, FloatRegister dest) { + as_fmuls(dest, dest, src); +} + +void MacroAssembler::mulDouble(FloatRegister src, FloatRegister dest) { + as_fmul(dest, dest, src); +} + +void MacroAssembler::mulDoublePtr(ImmPtr imm, Register temp, + FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(imm, scratch); + as_lfd(ScratchDoubleReg, scratch, 0); + as_fmul(dest, dest, ScratchDoubleReg); +} + +void MacroAssembler::inc64(AbsoluteAddress dest) { + UseScratchRegisterScope temps(asMasm()); + Register addrReg = temps.Acquire(); + Register scratch = temps.Acquire(); + movePtr(ImmWord(uintptr_t(dest.addr)), addrReg); + // Big endian: the high word is at the lower address. + as_lwz(scratch, addrReg, 4); + xs_li(r0, 1); + as_addc(scratch, scratch, r0); + as_stw(scratch, addrReg, 4); + as_lwz(scratch, addrReg, 0); + xs_li(r0, 0); + as_adde(scratch, scratch, r0); + as_stw(scratch, addrReg, 0); +} + +void MacroAssembler::divFloat32(FloatRegister src, FloatRegister dest) { + as_fdivs(dest, dest, src); +} + +void MacroAssembler::divDouble(FloatRegister src, FloatRegister dest) { + as_fdiv(dest, dest, src); +} + +void MacroAssembler::quotient32(Register lhs, Register rhs, Register dest, + bool isUnsigned) { + if (isUnsigned) { + as_divwu(dest, lhs, rhs); + } else { + as_divw(dest, lhs, rhs); + } +} + +void MacroAssembler::quotient64(Register lhs, Register rhs, Register dest, + bool isUnsigned) { + MOZ_CRASH("NYI: quotient64 (no 64-bit divide on 32-bit PowerPC)"); +} + +void MacroAssembler::remainder32(Register lhs, Register rhs, Register dest, + bool isUnsigned) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + if (isUnsigned) { + as_divwu(scratch, lhs, rhs); + } else { + as_divw(scratch, lhs, rhs); + } + as_mullw(scratch, scratch, rhs); + as_subf(dest, scratch, lhs); +} + +void MacroAssembler::remainder64(Register lhs, Register rhs, Register dest, + bool isUnsigned) { + MOZ_CRASH("NYI: remainder64 (no 64-bit divide on 32-bit PowerPC)"); +} + +void MacroAssembler::neg64(Register64 reg) { + // subfe with a zero addend is subfze, which this assembler does not have. + as_subfic(reg.low, reg.low, 0); + xs_li(r0, 0); + as_subfe(reg.high, reg.high, r0); +} + +void MacroAssembler::negPtr(Register reg) { as_neg(reg, reg); } + +void MacroAssembler::neg32(Register reg) { as_neg(reg, reg); } + +void MacroAssembler::negateDouble(FloatRegister reg) { as_fneg(reg, reg); } + +void MacroAssembler::negateFloat(FloatRegister reg) { as_fneg(reg, reg); } + +void MacroAssembler::abs32(Register src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_srawi(scratch, src, 31); + as_xor_(dest, src, scratch); + as_subf(dest, scratch, dest); +} + +void MacroAssembler::absFloat32(FloatRegister src, FloatRegister dest) { + as_fabs(dest, src); +} + +void MacroAssembler::absDouble(FloatRegister src, FloatRegister dest) { + as_fabs(dest, src); +} + +// This target has no isel, so the min/max selects are done with a branch. +static void MinMax32(MacroAssembler& masm, Register lhs, Register rhs, + Register dest, bool isMax) { + Assembler::Condition keep = + isMax ? Assembler::GreaterThanOrEqual : Assembler::LessThanOrEqual; + Label done; + if (dest == lhs) { + masm.branch32(keep, lhs, rhs, &done); + masm.move32(rhs, dest); + } else { + if (dest != rhs) { + masm.move32(rhs, dest); + } + masm.branch32(Assembler::InvertCondition(keep), lhs, rhs, &done); + masm.move32(lhs, dest); + } + masm.bind(&done); +} + +void MacroAssembler::min32(Register lhs, Register rhs, Register dest) { + MinMax32(*this, lhs, rhs, dest, /* isMax = */ false); +} + +void MacroAssembler::min32(Register lhs, Imm32 rhs, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(rhs, scratch); + MinMax32(*this, lhs, scratch, dest, /* isMax = */ false); +} + +void MacroAssembler::max32(Register lhs, Register rhs, Register dest) { + MinMax32(*this, lhs, rhs, dest, /* isMax = */ true); +} + +void MacroAssembler::max32(Register lhs, Imm32 rhs, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(rhs, scratch); + MinMax32(*this, lhs, scratch, dest, /* isMax = */ true); +} + +void MacroAssembler::minPtr(Register lhs, Register rhs, Register dest) { + MinMax32(*this, lhs, rhs, dest, /* isMax = */ false); +} + +void MacroAssembler::minPtr(Register lhs, ImmWord rhs, Register dest) { + min32(lhs, Imm32(int32_t(rhs.value)), dest); +} + +void MacroAssembler::maxPtr(Register lhs, Register rhs, Register dest) { + MinMax32(*this, lhs, rhs, dest, /* isMax = */ true); +} + +void MacroAssembler::maxPtr(Register lhs, ImmWord rhs, Register dest) { + max32(lhs, Imm32(int32_t(rhs.value)), dest); +} + +void MacroAssembler::minFloat32(FloatRegister other, FloatRegister srcDest, + bool handleNaN) { + Label done, nan, equal; + as_fcmpu(srcDest, other); + if (handleNaN) { + ma_b(Assembler::DoubleUnordered, &nan); + } + // Handle +0 vs -0. + ma_b(Assembler::DoubleEqual, &equal); + ma_b(Assembler::DoubleLessThan, &done); + as_fmr(srcDest, other); + jump(&done); + + bind(&equal); + // Both operands are equal. Check if they're zero. + loadConstantFloat32(0.0f, ScratchFloat32Reg); + as_fcmpu(srcDest, ScratchFloat32Reg); + // If not zero, they're identical; keep srcDest. + ma_b(Assembler::DoubleNotEqual, &done); + // Both are some combination of +0/-0. For min, result should be -0 + // if either is -0: -((-srcDest) - other) gives -0 when either is -0. + as_fneg(ScratchFloat32Reg, srcDest); + as_fsubs(ScratchFloat32Reg, ScratchFloat32Reg, other); + as_fneg(srcDest, ScratchFloat32Reg); + jump(&done); + + if (handleNaN) { + bind(&nan); + as_fadds(srcDest, srcDest, other); + } + bind(&done); +} + +void MacroAssembler::minDouble(FloatRegister other, FloatRegister srcDest, + bool handleNaN) { + Label done, nan, equal; + as_fcmpu(srcDest, other); + if (handleNaN) { + ma_b(Assembler::DoubleUnordered, &nan); + } + // Handle +0 vs -0. + ma_b(Assembler::DoubleEqual, &equal); + ma_b(Assembler::DoubleLessThan, &done); + as_fmr(srcDest, other); + jump(&done); + + bind(&equal); + loadConstantDouble(0.0, ScratchDoubleReg); + as_fcmpu(srcDest, ScratchDoubleReg); + ma_b(Assembler::DoubleNotEqual, &done); + // -((-srcDest) - other) gives -0 when either is -0. + as_fneg(ScratchDoubleReg, srcDest); + as_fsub(ScratchDoubleReg, ScratchDoubleReg, other); + as_fneg(srcDest, ScratchDoubleReg); + jump(&done); + + if (handleNaN) { + bind(&nan); + as_fadd(srcDest, srcDest, other); + } + bind(&done); +} + +void MacroAssembler::maxFloat32(FloatRegister other, FloatRegister srcDest, + bool handleNaN) { + Label done, nan, equal; + as_fcmpu(srcDest, other); + if (handleNaN) { + ma_b(Assembler::DoubleUnordered, &nan); + } + // Handle +0 vs -0. + ma_b(Assembler::DoubleEqual, &equal); + ma_b(Assembler::DoubleGreaterThan, &done); + as_fmr(srcDest, other); + jump(&done); + + bind(&equal); + loadConstantFloat32(0.0f, ScratchFloat32Reg); + as_fcmpu(srcDest, ScratchFloat32Reg); + ma_b(Assembler::DoubleNotEqual, &done); + // -0 + -0 = -0 and -0 + 0 = +0. + as_fadds(srcDest, srcDest, other); + jump(&done); + + if (handleNaN) { + bind(&nan); + as_fadds(srcDest, srcDest, other); + } + bind(&done); +} + +void MacroAssembler::maxDouble(FloatRegister other, FloatRegister srcDest, + bool handleNaN) { + Label done, nan, equal; + as_fcmpu(srcDest, other); + if (handleNaN) { + ma_b(Assembler::DoubleUnordered, &nan); + } + // Handle +0 vs -0. + ma_b(Assembler::DoubleEqual, &equal); + ma_b(Assembler::DoubleGreaterThan, &done); + as_fmr(srcDest, other); + jump(&done); + + bind(&equal); + loadConstantDouble(0.0, ScratchDoubleReg); + as_fcmpu(srcDest, ScratchDoubleReg); + ma_b(Assembler::DoubleNotEqual, &done); + // -0 + -0 = -0 and -0 + 0 = +0. + as_fadd(srcDest, srcDest, other); + jump(&done); + + if (handleNaN) { + bind(&nan); + as_fadd(srcDest, srcDest, other); + } + bind(&done); +} + +// =============================================================== +// Shift functions + +void MacroAssembler::lshift32(Register src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register masked = temps.Acquire(); + as_rlwinm(masked, src, 0, 27, 31); + as_slw(dest, dest, masked); +} + +void MacroAssembler::lshift32(Imm32 imm, Register dest) { + lshift32(imm, dest, dest); +} + +void MacroAssembler::lshift32(Imm32 imm, Register src, Register dest) { + x_slwi(dest, src, imm.value & 0x1f); +} + +void MacroAssembler::flexibleLshift32(Register src, Register dest) { + lshift32(src, dest); +} + +void MacroAssembler::lshift64(Register unmaskedShift, Register64 dest) { + // slw/srw take the shift count modulo 64 and produce zero for counts >= 32, + // which makes the whole 64-bit shift branchless: the (shift - 32) term is + // zero while shift < 32 and the (32 - shift) term is zero once shift >= 32. + UseScratchRegisterScope temps(asMasm()); + Register shift = temps.Acquire(); + Register tmp = temps.Acquire(); + as_rlwinm(shift, unmaskedShift, 0, 26, 31); // shift &= 63 + as_subfic(tmp, shift, 32); + as_slw(dest.high, dest.high, shift); + as_srw(tmp, dest.low, tmp); + as_or_(dest.high, dest.high, tmp); + as_addi(tmp, shift, -32); + as_slw(tmp, dest.low, tmp); + as_or_(dest.high, dest.high, tmp); + as_slw(dest.low, dest.low, shift); +} + +void MacroAssembler::lshift64(Imm32 imm, Register64 dest) { + MOZ_ASSERT(0 <= imm.value && imm.value < 64); + int32_t c = imm.value; + if (c == 0) { + return; + } + if (c < 32) { + as_rlwinm(dest.high, dest.high, c, 0, 31 - c); // high <<= c + as_rlwimi(dest.high, dest.low, c, 32 - c, 31); // high |= low >>> (32 - c) + as_rlwinm(dest.low, dest.low, c, 0, 31 - c); + } else { + x_slwi(dest.high, dest.low, c - 32); + move32(Imm32(0), dest.low); + } +} + +void MacroAssembler::lshiftPtr(Register shift, Register dest) { + lshift32(shift, dest); +} + +void MacroAssembler::lshiftPtr(Imm32 imm, Register dest) { + lshiftPtr(imm, dest, dest); +} + +void MacroAssembler::lshiftPtr(Imm32 imm, Register src, Register dest) { + MOZ_ASSERT(0 <= imm.value && imm.value < 32); + x_slwi(dest, src, imm.value); +} + +void MacroAssembler::flexibleLshiftPtr(Register shift, Register srcDest) { + lshiftPtr(shift, srcDest); +} + +void MacroAssembler::rshift32(Register src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register masked = temps.Acquire(); + as_rlwinm(masked, src, 0, 27, 31); + as_srw(dest, dest, masked); +} + +void MacroAssembler::rshift32(Imm32 imm, Register dest) { + rshift32(imm, dest, dest); +} + +void MacroAssembler::rshift32(Imm32 imm, Register src, Register dest) { + x_srwi(dest, src, imm.value & 0x1f); +} + +void MacroAssembler::flexibleRshift32(Register src, Register dest) { + rshift32(src, dest); +} + +void MacroAssembler::rshift32Arithmetic(Register src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register masked = temps.Acquire(); + as_rlwinm(masked, src, 0, 27, 31); + as_sraw(dest, dest, masked); +} + +void MacroAssembler::rshift32Arithmetic(Imm32 imm, Register dest) { + rshift32Arithmetic(imm, dest, dest); +} + +void MacroAssembler::rshift32Arithmetic(Imm32 imm, Register src, + Register dest) { + as_srawi(dest, src, imm.value & 0x1f); +} + +void MacroAssembler::flexibleRshift32Arithmetic(Register src, Register dest) { + rshift32Arithmetic(src, dest); +} + +void MacroAssembler::rshift64(Register unmaskedShift, Register64 dest) { + // Mirror image of lshift64; see the note there about srw/slw producing zero + // for shift counts >= 32. + UseScratchRegisterScope temps(asMasm()); + Register shift = temps.Acquire(); + Register tmp = temps.Acquire(); + as_rlwinm(shift, unmaskedShift, 0, 26, 31); + as_subfic(tmp, shift, 32); + as_srw(dest.low, dest.low, shift); + as_slw(tmp, dest.high, tmp); + as_or_(dest.low, dest.low, tmp); + as_addi(tmp, shift, -32); + as_srw(tmp, dest.high, tmp); + as_or_(dest.low, dest.low, tmp); + as_srw(dest.high, dest.high, shift); +} + +void MacroAssembler::rshift64(Imm32 imm, Register64 dest) { + MOZ_ASSERT(0 <= imm.value && imm.value < 64); + int32_t c = imm.value; + if (c == 0) { + return; + } + if (c < 32) { + as_rlwinm(dest.low, dest.low, 32 - c, c, 31); // low >>>= c + as_rlwimi(dest.low, dest.high, 32 - c, 0, c - 1); // low |= high << (32-c) + as_rlwinm(dest.high, dest.high, 32 - c, c, 31); + } else if (c == 32) { + move32(dest.high, dest.low); + move32(Imm32(0), dest.high); + } else { + x_srwi(dest.low, dest.high, c - 32); + move32(Imm32(0), dest.high); + } +} + +void MacroAssembler::rshift64Arithmetic(Imm32 imm, Register64 dest) { + MOZ_ASSERT(0 <= imm.value && imm.value < 64); + int32_t c = imm.value; + if (c == 0) { + return; + } + if (c < 32) { + as_rlwinm(dest.low, dest.low, 32 - c, c, 31); + as_rlwimi(dest.low, dest.high, 32 - c, 0, c - 1); + as_srawi(dest.high, dest.high, c); + } else if (c == 32) { + move32(dest.high, dest.low); + as_srawi(dest.high, dest.high, 31); + } else { + as_srawi(dest.low, dest.high, c - 32); + as_srawi(dest.high, dest.high, 31); + } +} + +void MacroAssembler::rshift64Arithmetic(Register unmaskedShift, + Register64 dest) { + // sraw replicates the sign bit for counts >= 32, so unlike the logical + // shifts the (shift - 32) term cannot simply be OR-ed in; take a branch. + UseScratchRegisterScope temps(asMasm()); + Register shift = temps.Acquire(); + Register tmp = temps.Acquire(); + Label lessThan32; + as_rlwinm(shift, unmaskedShift, 0, 26, 31); + as_subfic(tmp, shift, 32); + as_srw(dest.low, dest.low, shift); + as_slw(tmp, dest.high, tmp); + as_or_(dest.low, dest.low, tmp); + as_cmpwi(shift, 32); + ma_b(Assembler::LessThan, &lessThan32); + as_addi(tmp, shift, -32); + as_sraw(dest.low, dest.high, tmp); + bind(&lessThan32); + as_sraw(dest.high, dest.high, shift); +} + +void MacroAssembler::rshiftPtr(Register shift, Register dest) { + rshift32(shift, dest); +} + +void MacroAssembler::rshiftPtr(Imm32 imm, Register dest) { + rshiftPtr(imm, dest, dest); +} + +void MacroAssembler::rshiftPtr(Imm32 imm, Register src, Register dest) { + MOZ_ASSERT(0 <= imm.value && imm.value < 32); + x_srwi(dest, src, imm.value); +} + +void MacroAssembler::flexibleRshiftPtr(Register shift, Register srcDest) { + rshiftPtr(shift, srcDest); +} + +void MacroAssembler::rshiftPtrArithmetic(Imm32 imm, Register dest) { + rshiftPtrArithmetic(imm, dest, dest); +} + +void MacroAssembler::rshiftPtrArithmetic(Imm32 imm, Register src, + Register dest) { + MOZ_ASSERT(0 <= imm.value && imm.value < 32); + as_srawi(dest, src, imm.value); +} + +void MacroAssembler::rshiftPtrArithmetic(Register shift, Register dest) { + rshift32Arithmetic(shift, dest); +} + +void MacroAssembler::flexibleRshiftPtrArithmetic(Register shift, + Register srcDest) { + rshiftPtrArithmetic(shift, srcDest); +} + +// =============================================================== +// Rotation functions + +void MacroAssembler::rotateLeft(Register count, Register input, Register dest) { + // rotlw is rlwnm with a full mask. + as_rlwnm(dest, input, count, 0, 31); +} + +void MacroAssembler::rotateLeft(Imm32 count, Register input, Register dest) { + as_rlwinm(dest, input, count.value & 31, 0, 31); +} + +// Constant 64-bit rotate left of a register pair by 0 < amount < 32. +static void RotateLeft64Small(MacroAssembler& masm, Register64 src, + Register64 dest, int32_t amount) { + MOZ_ASSERT(0 < amount && amount < 32); + MOZ_ASSERT(src.low != dest.high && src.high != dest.low); + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.xs_mr(scratch, src.high); + // dest.high = (src.high << a) | (src.low >>> (32 - a)) + masm.as_rlwinm(dest.high, scratch, amount, 0, 31 - amount); + masm.as_rlwimi(dest.high, src.low, amount, 32 - amount, 31); + // dest.low = (src.low << a) | (old src.high >>> (32 - a)) + masm.as_rlwinm(dest.low, src.low, amount, 0, 31 - amount); + masm.as_rlwimi(dest.low, scratch, amount, 32 - amount, 31); +} + +void MacroAssembler::rotateLeft64(Imm32 count, Register64 src, Register64 dest, + Register temp) { + MOZ_ASSERT(src.low != dest.high && src.high != dest.low); + int32_t amount = count.value & 0x3f; + if (amount == 0) { + move64(src, dest); + } else if (amount == 32) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + xs_mr(scratch, src.low); + xs_mr(dest.low, src.high); + xs_mr(dest.high, scratch); + } else if (amount < 32) { + RotateLeft64Small(*this, src, dest, amount); + } else { + // A rotate by more than 32 is a word swap plus a rotate by amount - 32. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + xs_mr(scratch, src.low); + xs_mr(dest.low, src.high); + xs_mr(dest.high, scratch); + RotateLeft64Small(*this, dest, dest, amount - 32); + } +} + +// Variable 64-bit rotate of a register pair, in place. A right rotate by n is +// a left rotate by (-n) & 63. +// +// After reducing to a rotate by n' < 32, each word is rotated in isolation and +// the low n' bits are then exchanged between the two words: with +// x = rotl32(high) ^ rotl32(low) masked to its low n' bits, the answers are +// rotl32(high) ^ x and rotl32(low) ^ x. That keeps the whole sequence within +// the two available scratch registers. +static void RotateVariable64(MacroAssembler& masm, Register count, + Register64 dest, bool isLeft) { + UseScratchRegisterScope temps(masm); + Register n = temps.Acquire(); + Register x = temps.Acquire(); + Label noSwap; + + if (isLeft) { + masm.as_rlwinm(n, count, 0, 26, 31); // n = count & 63 + } else { + masm.as_neg(n, count); + masm.as_rlwinm(n, n, 0, 26, 31); // n = (-count) & 63 + } + + masm.as_cmpwi(n, 32); + masm.ma_b(Assembler::LessThan, &noSwap); + // Rotating by 32 or more exchanges the two words. + masm.as_xor_(x, dest.high, dest.low); + masm.as_xor_(dest.high, dest.high, x); + masm.as_xor_(dest.low, dest.low, x); + masm.bind(&noSwap); + + masm.as_rlwinm(n, n, 0, 27, 31); // n &= 31 + masm.as_rlwnm(dest.high, dest.high, n, 0, 31); + masm.as_rlwnm(dest.low, dest.low, n, 0, 31); + masm.as_subfic(n, n, 32); // n = 32 - n' + masm.as_xor_(x, dest.high, dest.low); + // Keep only the low n' bits of x; for n' == 0 both shifts are by 32 and + // produce zero, leaving both words untouched. + masm.as_slw(x, x, n); + masm.as_srw(x, x, n); + masm.as_xor_(dest.high, dest.high, x); + masm.as_xor_(dest.low, dest.low, x); +} + +void MacroAssembler::rotateLeft64(Register count, Register64 src, + Register64 dest, Register temp) { + if (src != dest) { + move64(src, dest); + } + RotateVariable64(*this, count, dest, /* isLeft = */ true); +} + +void MacroAssembler::rotateRight(Register count, Register input, + Register dest) { + // rotateRight(n) = rotateLeft(32 - n). When dest != input the negated + // count can land directly in dest, dropping the scratch. + if (dest != input) { + as_subfic(dest, count, 32); + as_rlwnm(dest, input, dest, 0, 31); + return; + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_subfic(scratch, count, 32); + as_rlwnm(dest, input, scratch, 0, 31); +} + +void MacroAssembler::rotateRight(Imm32 count, Register input, Register dest) { + rotateLeft(Imm32((32 - count.value) & 31), input, dest); +} + +void MacroAssembler::rotateRight64(Register count, Register64 src, + Register64 dest, Register temp) { + if (src != dest) { + move64(src, dest); + } + RotateVariable64(*this, count, dest, /* isLeft = */ false); +} + +void MacroAssembler::rotateRight64(Imm32 count, Register64 src, Register64 dest, + Register temp) { + rotateLeft64(Imm32((64 - count.value) & 63), src, dest, temp); +} + +// =============================================================== +// Bit counting functions + +void MacroAssembler::clz64(Register64 src, Register64 dest) { + // If the high word is zero the answer is 32 + clz32(low). + Label highNonZero, done; + branch32(Assembler::NotEqual, src.high, Imm32(0), &highNonZero); + as_cntlzw(dest.low, src.low); + as_addi(dest.low, dest.low, 32); + jump(&done); + bind(&highNonZero); + as_cntlzw(dest.low, src.high); + bind(&done); + move32(Imm32(0), dest.high); +} + +void MacroAssembler::ctz64(Register64 src, Register64 dest) { + Label useHigh, done; + branch32(Assembler::Equal, src.low, Imm32(0), &useHigh); + ctz32(src.low, dest.low, /* knownNotZero = */ true); + jump(&done); + bind(&useHigh); + ctz32(src.high, dest.low, /* knownNotZero = */ false); + as_addi(dest.low, dest.low, 32); + bind(&done); + move32(Imm32(0), dest.high); +} + +void MacroAssembler::popcnt64(Register64 input, Register64 output, + Register tmp) { + MOZ_ASSERT(output.low != output.high); + // The source and destination may overlap, so pick an order that does not + // clobber a word before it is counted. + if (output.low != input.high) { + popcnt32(input.low, output.low, tmp); + popcnt32(input.high, output.high, tmp); + } else { + MOZ_ASSERT(output.high != input.high); + popcnt32(input.low, output.high, tmp); + popcnt32(input.high, output.low, tmp); + } + as_add(output.low, output.low, output.high); + move32(Imm32(0), output.high); +} + +void MacroAssembler::clz32(Register src, Register dest, bool knownNotZero) { + as_cntlzw(dest, src); +} + +void MacroAssembler::ctz32(Register src, Register dest, bool knownNotZero) { + // ctz(x) = 32 - clz(~x & (x - 1)); this also yields 32 for x == 0, so the + // knownNotZero case needs no special handling. + UseScratchRegisterScope temps(asMasm()); + Register tmp = temps.Acquire(); + as_addi(tmp, src, -1); + x_not(dest, src); + as_and_(dest, dest, tmp); + as_cntlzw(dest, dest); + as_subfic(dest, dest, 32); +} + +void MacroAssembler::popcnt32(Register input, Register output, Register tmp) { + // Classic SWAR population count: this target has no popcntb/popcntw. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register t = (tmp != Register::Invalid()) ? tmp : temps.Acquire(); + + if (input != output) { + move32(input, output); + } + + x_srwi(t, output, 1); + move32(Imm32(0x55555555), scratch); + as_and_(t, t, scratch); + as_subf(output, t, output); + + x_srwi(t, output, 2); + move32(Imm32(0x33333333), scratch); + as_and_(output, output, scratch); + as_and_(t, t, scratch); + as_add(output, output, t); + + x_srwi(t, output, 4); + as_add(output, output, t); + move32(Imm32(0x0F0F0F0F), scratch); + as_and_(output, output, scratch); + + move32(Imm32(0x01010101), scratch); + as_mullw(output, output, scratch); + x_srwi(output, output, 24); +} + +// =============================================================== +// Condition functions + +void MacroAssembler::cmp8Set(Condition cond, Address lhs, Imm32 rhs, + Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != lhs.base); + bool isUnsigned = (cond & Assembler::ConditionUnsigned) != 0; + if (isUnsigned) { + load8ZeroExtend(lhs, scratch); + Condition c = ma_cmp(scratch, Imm32(uint8_t(rhs.value)), cond, true); + ma_cmp_set(dest, c); + } else { + load8SignExtend(lhs, scratch); + Condition c = ma_cmp(scratch, Imm32(int8_t(rhs.value)), cond, true); + ma_cmp_set(dest, c); + } +} + +void MacroAssembler::cmp16Set(Condition cond, Address lhs, Imm32 rhs, + Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != lhs.base); + bool isUnsigned = (cond & Assembler::ConditionUnsigned) != 0; + if (isUnsigned) { + load16ZeroExtend(lhs, scratch); + Condition c = ma_cmp(scratch, Imm32(uint16_t(rhs.value)), cond, true); + ma_cmp_set(dest, c); + } else { + load16SignExtend(lhs, scratch); + Condition c = ma_cmp(scratch, Imm32(int16_t(rhs.value)), cond, true); + ma_cmp_set(dest, c); + } +} + +template +void MacroAssembler::cmp32Set(Condition cond, T1 lhs, T2 rhs, Register dest) { + Condition c = ma_cmp(lhs, rhs, cond, true); + ma_cmp_set(dest, c); +} + +// A 64-bit comparison compares the high words first and only falls through to +// the (always unsigned) low-word comparison when the high words are equal. +template +static void Cmp64AndBranch(MacroAssembler& masm, Assembler::Condition cond, + LhsH lhsHigh, LhsL lhsLow, RhsH rhsHigh, RhsL rhsLow, + Label* success, Label* fail) { + bool fallthrough = false; + Label fallthroughLabel; + if (!fail) { + fail = &fallthroughLabel; + fallthrough = true; + } + + switch (cond) { + case Assembler::Equal: + masm.branch32(Assembler::NotEqual, lhsLow, rhsLow, fail); + masm.branch32(Assembler::Equal, lhsHigh, rhsHigh, success); + break; + case Assembler::NotEqual: + masm.branch32(Assembler::NotEqual, lhsLow, rhsLow, success); + masm.branch32(Assembler::NotEqual, lhsHigh, rhsHigh, success); + break; + case Assembler::LessThan: + case Assembler::LessThanOrEqual: + case Assembler::GreaterThan: + case Assembler::GreaterThanOrEqual: + case Assembler::Below: + case Assembler::BelowOrEqual: + case Assembler::Above: + case Assembler::AboveOrEqual: { + Assembler::Condition cond1 = ConditionWithoutEqual(cond); + Assembler::Condition cond2 = + ConditionWithoutEqual(Assembler::InvertCondition(cond)); + Assembler::Condition cond3 = UnsignedCondition(cond); + masm.branch32(cond1, lhsHigh, rhsHigh, success); + masm.branch32(cond2, lhsHigh, rhsHigh, fail); + masm.branch32(cond3, lhsLow, rhsLow, success); + break; + } + default: + MOZ_CRASH("Condition code not supported"); + } + + if (fallthrough) { + masm.bind(fail); + } else { + masm.jump(fail); + } +} + +void MacroAssembler::cmp64Set(Condition cond, Register64 lhs, Register64 rhs, + Register dest) { + Label success, done; + branch64(cond, lhs, rhs, &success, nullptr); + move32(Imm32(0), dest); + jump(&done); + bind(&success); + move32(Imm32(1), dest); + bind(&done); +} + +void MacroAssembler::cmp64Set(Condition cond, Register64 lhs, Imm64 rhs, + Register dest) { + Label success, done; + branch64(cond, lhs, rhs, &success, nullptr); + move32(Imm32(0), dest); + jump(&done); + bind(&success); + move32(Imm32(1), dest); + bind(&done); +} + +void MacroAssembler::cmp64Set(Condition cond, Address lhs, Register64 rhs, + Register dest) { + Label success, done; + branch64(cond, lhs, rhs, &success, nullptr); + move32(Imm32(0), dest); + jump(&done); + bind(&success); + move32(Imm32(1), dest); + bind(&done); +} + +void MacroAssembler::cmp64Set(Condition cond, Address lhs, Imm64 rhs, + Register dest) { + Label success, done; + branch64(cond, lhs, rhs, &success, nullptr); + move32(Imm32(0), dest); + jump(&done); + bind(&success); + move32(Imm32(1), dest); + bind(&done); +} + +template +void MacroAssembler::cmpPtrSet(Condition cond, T1 lhs, T2 rhs, Register dest) { + Condition c = ma_cmp(lhs, rhs, cond); + ma_cmp_set(dest, c); +} + +// =============================================================== +// Branch functions + +void MacroAssembler::branch8(Condition cond, const Address& lhs, Imm32 rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + // Narrow the immediate to the 8-bit memory operand width so both sides of + // the compare have matching bit patterns. uint8 for equality / unsigned, + // int8 for signed relational. + bool isEqOrNe = (cond == Assembler::Equal) || (cond == Assembler::NotEqual); + bool isUnsigned = (cond & Assembler::ConditionUnsigned) != 0; + Imm32 narrowed(0); + if (isEqOrNe || isUnsigned) { + load8ZeroExtend(lhs, scratch); + narrowed = Imm32(uint8_t(rhs.value)); + } else { + load8SignExtend(lhs, scratch); + narrowed = Imm32(int8_t(rhs.value)); + } + Condition c = ma_cmp(scratch, narrowed, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch8(Condition cond, const BaseIndex& lhs, Register rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load8ZeroExtend(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch16(Condition cond, const Address& lhs, Imm32 rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + // See branch8. + bool isEqOrNe = (cond == Assembler::Equal) || (cond == Assembler::NotEqual); + bool isUnsigned = (cond & Assembler::ConditionUnsigned) != 0; + Imm32 narrowed(0); + if (isEqOrNe || isUnsigned) { + load16ZeroExtend(lhs, scratch); + narrowed = Imm32(uint16_t(rhs.value)); + } else { + load16SignExtend(lhs, scratch); + narrowed = Imm32(int16_t(rhs.value)); + } + Condition c = ma_cmp(scratch, narrowed, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch32(Condition cond, Register lhs, Register rhs, + Label* label) { + Condition c = ma_cmp(lhs, rhs, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch32(Condition cond, Register lhs, Imm32 imm, + Label* label) { + Condition c = ma_cmp(lhs, imm, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch32(Condition cond, const Address& lhs, Register rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch32(Condition cond, const Address& lhs, Imm32 rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch32(Condition cond, const AbsoluteAddress& lhs, + Register rhs, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(ImmWord((uintptr_t)lhs.addr), scratch); + load32(Address(scratch, 0), scratch); + Condition c = ma_cmp(scratch, rhs, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch32(Condition cond, const AbsoluteAddress& lhs, + Imm32 rhs, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(ImmWord((uintptr_t)lhs.addr), scratch); + load32(Address(scratch, 0), scratch); + Condition c = ma_cmp(scratch, rhs, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch32(Condition cond, const BaseIndex& lhs, Imm32 rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch32(Condition cond, wasm::SymbolicAddress addr, + Imm32 imm, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(addr, scratch); + load32(Address(scratch, 0), scratch); + Condition c = ma_cmp(scratch, imm, cond, true); + ma_b(c, label); +} + +void MacroAssembler::branch64(Condition cond, Register64 lhs, Imm64 val, + Label* success, Label* fail) { + Cmp64AndBranch(*this, cond, lhs.high, lhs.low, val.hi(), val.low(), success, + fail); +} + +void MacroAssembler::branch64(Condition cond, Register64 lhs, Register64 rhs, + Label* success, Label* fail) { + Cmp64AndBranch(*this, cond, lhs.high, lhs.low, rhs.high, rhs.low, success, + fail); +} + +void MacroAssembler::branch64(Condition cond, const Address& lhs, Imm64 val, + Label* success, Label* fail) { + Cmp64AndBranch(*this, cond, HighWord(lhs), LowWord(lhs), val.hi(), val.low(), + success, fail); +} + +void MacroAssembler::branch64(Condition cond, const Address& lhs, + Register64 rhs, Label* success, Label* fail) { + Cmp64AndBranch(*this, cond, HighWord(lhs), LowWord(lhs), rhs.high, rhs.low, + success, fail); +} + +void MacroAssembler::branch64(Condition cond, const Address& lhs, + const Address& rhs, Register scratch, + Label* label) { + MOZ_ASSERT(cond == Assembler::NotEqual || cond == Assembler::Equal, + "other condition codes not supported"); + MOZ_ASSERT(lhs.base != scratch); + MOZ_ASSERT(rhs.base != scratch); + + Label done; + load32(LowWord(rhs), scratch); + if (cond == Assembler::Equal) { + branch32(Assembler::NotEqual, LowWord(lhs), scratch, &done); + } else { + branch32(Assembler::NotEqual, LowWord(lhs), scratch, label); + } + load32(HighWord(rhs), scratch); + branch32(cond, HighWord(lhs), scratch, label); + bind(&done); +} + +void MacroAssembler::branchPtr(Condition cond, Register lhs, Register rhs, + Label* label) { + Condition c = ma_cmp(lhs, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, Register lhs, Imm32 rhs, + Label* label) { + Condition c = ma_cmp(lhs, ImmWord(uintptr_t(intptr_t(rhs.value))), cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, Register lhs, ImmPtr rhs, + Label* label) { + Condition c = ma_cmp(lhs, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, Register lhs, ImmGCPtr rhs, + Label* label) { + Condition c = ma_cmp(lhs, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, Register lhs, ImmWord rhs, + Label* label) { + Condition c = ma_cmp(lhs, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, const Address& lhs, Register rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, const Address& lhs, ImmPtr rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, const Address& lhs, ImmGCPtr rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, const Address& lhs, ImmWord rhs, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, const AbsoluteAddress& lhs, + Register rhs, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(ImmWord((uintptr_t)lhs.addr), scratch); + loadPtr(Address(scratch, 0), scratch); + Condition c = ma_cmp(scratch, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, const AbsoluteAddress& lhs, + ImmWord rhs, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(ImmWord((uintptr_t)lhs.addr), scratch); + loadPtr(Address(scratch, 0), scratch); + Condition c = ma_cmp(scratch, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, wasm::SymbolicAddress lhs, + Register rhs, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(lhs, scratch); + loadPtr(Address(scratch, 0), scratch); + Condition c = ma_cmp(scratch, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, const BaseIndex& lhs, + Register rhs, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPtr(Condition cond, const BaseIndex& lhs, + ImmWord rhs, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond); + ma_b(c, label); +} + +void MacroAssembler::branchPrivatePtr(Condition cond, const Address& lhs, + Register rhs, Label* label) { + branchPtr(cond, lhs, rhs, label); +} + +void MacroAssembler::branchFloat(DoubleCondition cond, FloatRegister lhs, + FloatRegister rhs, Label* label) { + as_fcmpu(lhs, rhs); + ma_b(cond, label); +} + +void MacroAssembler::branchTruncateFloat32MaybeModUint32(FloatRegister src, + Register dest, + Label* fail) { + // No fctidz on this target, so there is no wider intermediate to take the + // low 32 bits of; the plain 32-bit truncation is the best we can do. + branchTruncateFloat32ToInt32(src, dest, fail); +} + +void MacroAssembler::branchTruncateFloat32ToInt32(FloatRegister src, + Register dest, Label* fail) { + convertFloat32ToInt32(src, dest, fail, false); +} + +void MacroAssembler::branchDouble(DoubleCondition cond, FloatRegister lhs, + FloatRegister rhs, Label* label) { + as_fcmpu(lhs, rhs); + ma_b(cond, label); +} + +void MacroAssembler::branchTruncateDoubleMaybeModUint32(FloatRegister src, + Register dest, + Label* fail) { + branchTruncateDoubleToInt32(src, dest, fail); +} + +void MacroAssembler::branchTruncateDoubleToInt32(FloatRegister src, + Register dest, Label* fail) { + convertDoubleToInt32(src, dest, fail, false); +} + +void MacroAssembler::branchInt64NotInPtrRange(Register64 src, Label* label) { + // The high word must be the sign extension of the low word. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_srawi(scratch, src.low, 31); + as_cmpw(src.high, scratch); + ma_b(Assembler::NotEqual, label); +} + +void MacroAssembler::branchUInt64NotInPtrRange(Register64 src, Label* label) { + // The high word and the low word's sign bit must all be zero. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + x_srwi(scratch, src.low, 31); + as_or_(scratch, scratch, src.high); + as_cmpwi(scratch, 0); + ma_b(Assembler::NotEqual, label); +} + +void MacroAssembler::branchAdd64(Condition cond, Imm64 imm, Register64 dest, + Label* label) { + MOZ_ASSERT(cond == CarrySet || cond == CarryClear); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + // Unsigned carry out of the 64-bit add means the result wrapped below the + // original value. XER[CA] is not usable here: add64 short-circuits to a + // plain add32 when the low half of the immediate is zero. + move32(dest.high, scratch); + move32(dest.low, SecondScratchReg); + add64(imm, dest); + + Label done; + Label* carry = (cond == CarrySet) ? label : &done; + Label* noCarry = (cond == CarryClear) ? label : &done; + as_cmplw(dest.high, scratch); + ma_b(LessThan, carry); + ma_b(GreaterThan, noCarry); + as_cmplw(dest.low, SecondScratchReg); + ma_b(LessThan, carry); + jump(noCarry); + bind(&done); +} + +template +void MacroAssembler::branchAdd32(Condition cond, T src, Register dest, + Label* overflow) { + switch (cond) { + case Overflow: { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(dest, scratch); // scratch = old dest + add32(src, dest); + // Second temp from the scope: SecondScratchReg may already be held. + Register scratch2 = temps.Acquire(); + // Signed overflow iff (old ^ result) & (rhs ^ result) has bit 31 set. + as_xor_(scratch2, dest, scratch); + as_subf(scratch, scratch, dest); // scratch = rhs value + as_xor_(scratch, scratch, dest); + as_and__rc(scratch, scratch, scratch2); + ma_b(LessThan, overflow); + break; + } + case NonZero: + case Zero: + add32(src, dest); + as_cmpwi(dest, 0); + ma_b(cond == NonZero ? NotEqual : Equal, overflow); + break; + case Signed: + case NotSigned: + add32(src, dest); + as_cmpwi(dest, 0); + ma_b(cond == Signed ? LessThan : GreaterThanOrEqual, overflow); + break; + case CarryClear: + case CarrySet: { + // Unsigned carry: compare the result against the original operand. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(dest, scratch); + add32(src, dest); + as_cmplw(dest, scratch); + ma_b(cond == CarrySet ? LessThan : GreaterThanOrEqual, overflow); + break; + } + default: + MOZ_CRASH("NYI"); + } +} + +template +void MacroAssembler::branchSub32(Condition cond, T src, Register dest, + Label* overflow) { + switch (cond) { + case Overflow: { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(dest, scratch); // scratch = old dest + sub32(src, dest); + // Second temp from the scope: SecondScratchReg may already be held. + Register scratch2 = temps.Acquire(); + // Signed overflow iff (old ^ rhs) & (old ^ result) has bit 31 set. + as_subf(scratch2, dest, scratch); // scratch2 = rhs value + as_xor_(scratch2, scratch, scratch2); + as_xor_(scratch, scratch, dest); + as_and__rc(scratch, scratch, scratch2); + ma_b(LessThan, overflow); + break; + } + case NonZero: + case Zero: + sub32(src, dest); + as_cmpwi(dest, 0); + ma_b(cond == NonZero ? NotEqual : Equal, overflow); + break; + case Signed: + case NotSigned: + sub32(src, dest); + as_cmpwi(dest, 0); + ma_b(cond == Signed ? LessThan : GreaterThanOrEqual, overflow); + break; + default: + MOZ_CRASH("NYI"); + } +} + +// There is no mullwo and no signed mulhw here, so the 64-bit product is built +// from mulhwu plus the two sign corrections and compared against the sign +// extension of the low word. +template +void MacroAssembler::branchMul32(Condition cond, T src, Register dest, + Label* overflow) { + MOZ_ASSERT(cond == Overflow); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(src, scratch); + Register scratch2 = temps.Acquire(); + as_mulhwu(scratch2, dest, scratch); + as_srawi(r0, dest, 31); + as_and_(r0, r0, scratch); + as_subf(scratch2, r0, scratch2); + as_srawi(r0, scratch, 31); + as_and_(r0, r0, dest); + as_subf(scratch2, r0, scratch2); + as_mullw(dest, dest, scratch); + as_srawi(r0, dest, 31); + as_cmpw(scratch2, r0); + ma_b(NotEqual, overflow); +} + +template +void MacroAssembler::branchRshift32(Condition cond, T src, Register dest, + Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero); + rshift32(src, dest); + branch32(cond == Zero ? Equal : NotEqual, dest, Imm32(0), label); +} + +void MacroAssembler::branchNeg32(Condition cond, Register reg, Label* label) { + MOZ_ASSERT(cond == Overflow); + neg32(reg); + branch32(Equal, reg, Imm32(INT32_MIN), label); +} + +template +void MacroAssembler::branchAddPtr(Condition cond, T src, Register dest, + Label* label) { + branchAdd32(cond, src, dest, label); +} + +template +void MacroAssembler::branchSubPtr(Condition cond, T src, Register dest, + Label* label) { + branchSub32(cond, src, dest, label); +} + +void MacroAssembler::branchMulPtr(Condition cond, Register src, Register dest, + Label* label) { + branchMul32(cond, src, dest, label); +} + +void MacroAssembler::branchNegPtr(Condition cond, Register reg, Label* label) { + branchNeg32(cond, reg, label); +} + +void MacroAssembler::decBranchPtr(Condition cond, Register lhs, Imm32 rhs, + Label* label) { + subPtr(rhs, lhs); + branchPtr(cond, lhs, Imm32(0), label); +} + +void MacroAssembler::branchTest32(Condition cond, Register lhs, Register rhs, + Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + if (lhs == rhs) { + as_cmpwi(lhs, 0); + } else { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_and__rc(scratch, lhs, rhs); // record form folds the cmpwi + } + Condition base = static_cast(cond & ~Assembler::ConditionZero); + ma_b(base, label); +} + +void MacroAssembler::branchTest32(Condition cond, Register lhs, Imm32 rhs, + Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + if (is_uintN(rhs.value, 16)) { + // andi. sets CR0 on a value whose sign bit is necessarily clear, so for + // Signed/NotSigned the answer is fixed; Zero/NonZero read CR0[eq]. + as_andi_rc(scratch, lhs, rhs.value); + } else { + move32(rhs, scratch); + as_and__rc(scratch, lhs, scratch); + } + Condition base = static_cast(cond & ~Assembler::ConditionZero); + ma_b(base, label); +} + +void MacroAssembler::branchTest32(Condition cond, const Address& lhs, Imm32 rhs, + Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(lhs, scratch); + and32(rhs, scratch); + as_cmpwi(scratch, 0); + Condition base = static_cast(cond & ~Assembler::ConditionZero); + ma_b(base, label); +} + +void MacroAssembler::branchTest32(Condition cond, const AbsoluteAddress& lhs, + Imm32 rhs, Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(ImmWord((uintptr_t)lhs.addr), scratch); + load32(Address(scratch, 0), scratch); + and32(rhs, scratch); + as_cmpwi(scratch, 0); + Condition base = static_cast(cond & ~Assembler::ConditionZero); + ma_b(base, label); +} + +void MacroAssembler::branchTestPtr(Condition cond, Register lhs, Register rhs, + Label* label) { + branchTest32(cond, lhs, rhs, label); +} + +void MacroAssembler::branchTestPtr(Condition cond, Register lhs, Imm32 rhs, + Label* label) { + branchTest32(cond, lhs, rhs, label); +} + +void MacroAssembler::branchTestPtr(Condition cond, Register lhs, ImmWord rhs, + Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(rhs, scratch); + as_and__rc(scratch, lhs, scratch); + Condition base = static_cast(cond & ~Assembler::ConditionZero); + ma_b(base, label); +} + +void MacroAssembler::branchTestPtr(Condition cond, const Address& lhs, + Imm32 rhs, Label* label) { + branchTest32(cond, lhs, rhs, label); +} + +void MacroAssembler::branchTest64(Condition cond, Register64 lhs, + Register64 rhs, Register temp, Label* success, + Label* fail) { + bool fallthrough = false; + Label fallthroughLabel; + if (!fail) { + fail = &fallthroughLabel; + fallthrough = true; + } + + if (cond == Assembler::Zero || cond == Assembler::NonZero) { + if (lhs == rhs && temp != Register::Invalid()) { + as_or_(temp, lhs.low, lhs.high); + branchTest32(cond, temp, temp, success); + } else if (cond == Assembler::Zero) { + branchTest32(Assembler::NonZero, lhs.low, rhs.low, fail); + branchTest32(Assembler::Zero, lhs.high, rhs.high, success); + } else { + branchTest32(Assembler::NonZero, lhs.low, rhs.low, success); + branchTest32(Assembler::NonZero, lhs.high, rhs.high, success); + } + } else if (cond == Assembler::Signed || cond == Assembler::NotSigned) { + branchTest32(cond, lhs.high, rhs.high, success); + } else { + MOZ_CRASH("Unsupported condition"); + } + + if (fallthrough) { + bind(fail); + } else { + jump(fail); + } +} + +void MacroAssembler::branchTest64(Condition cond, Register64 lhs, Imm64 rhs, + Label* success, Label* fail) { + bool fallthrough = false; + Label fallthroughLabel; + if (!fail) { + fail = &fallthroughLabel; + fallthrough = true; + } + + if (cond == Assembler::Zero || cond == Assembler::NonZero) { + if (rhs.hi().value == 0) { + branchTest32(cond, lhs.low, rhs.low(), success); + } else if (rhs.low().value == 0) { + branchTest32(cond, lhs.high, rhs.hi(), success); + } else if (cond == Assembler::Zero) { + branchTest32(Assembler::NonZero, lhs.low, rhs.low(), fail); + branchTest32(Assembler::Zero, lhs.high, rhs.hi(), success); + } else { + branchTest32(Assembler::NonZero, lhs.low, rhs.low(), success); + branchTest32(Assembler::NonZero, lhs.high, rhs.hi(), success); + } + } else if (cond == Assembler::Signed || cond == Assembler::NotSigned) { + branchTest32(cond, lhs.high, rhs.hi(), success); + } else { + MOZ_CRASH("Unsupported condition"); + } + + if (fallthrough) { + bind(fail); + } else { + jump(fail); + } +} + +// =============================================================== +// Value-type branch functions + +void MacroAssembler::branchTestUndefined(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_UNDEFINED), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestUndefined(Condition cond, + const ValueOperand& value, + Label* label) { + branchTestUndefined(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestUndefined(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestUndefined(cond, tag, label); +} + +void MacroAssembler::branchTestUndefined(Condition cond, + const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestUndefined(cond, tag, label); +} + +void MacroAssembler::branchTestInt32(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_INT32), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestInt32(Condition cond, const ValueOperand& value, + Label* label) { + branchTestInt32(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestInt32(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestInt32(cond, tag, label); +} + +void MacroAssembler::branchTestInt32(Condition cond, const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestInt32(cond, tag, label); +} + +void MacroAssembler::branchTestInt32Truthy(bool b, const ValueOperand& value, + Label* label) { + as_cmpwi(value.payloadReg(), 0); + ma_b(b ? NotEqual : Equal, label); +} + +void MacroAssembler::branchTestDouble(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + // nunbox32: every double tag is below JSVAL_TAG_CLEAR. + Condition actual = (cond == Equal) ? Below : AboveOrEqual; + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_CLEAR), actual); + ma_b(c, label); +} + +void MacroAssembler::branchTestDouble(Condition cond, const ValueOperand& value, + Label* label) { + branchTestDouble(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestDouble(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestDouble(cond, tag, label); +} + +void MacroAssembler::branchTestDouble(Condition cond, const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestDouble(cond, tag, label); +} + +void MacroAssembler::branchTestDoubleTruthy(bool b, FloatRegister value, + Label* label) { + loadConstantDouble(0.0, ScratchDoubleReg); + as_fcmpu(value, ScratchDoubleReg); + DoubleCondition cond = b ? DoubleNotEqual : DoubleEqualOrUnordered; + ma_b(cond, label); +} + +void MacroAssembler::branchTestNumber(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition actual = (cond == Equal) ? BelowOrEqual : Above; + Condition c = + ma_cmp(tag, ImmTag(JS::detail::ValueUpperInclNumberTag), actual); + ma_b(c, label); +} + +void MacroAssembler::branchTestNumber(Condition cond, const ValueOperand& value, + Label* label) { + branchTestNumber(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestBoolean(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_BOOLEAN), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestBoolean(Condition cond, + const ValueOperand& value, + Label* label) { + branchTestBoolean(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestBoolean(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestBoolean(cond, tag, label); +} + +void MacroAssembler::branchTestBoolean(Condition cond, const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestBoolean(cond, tag, label); +} + +void MacroAssembler::branchTestBooleanTruthy(bool b, const ValueOperand& value, + Label* label) { + as_cmpwi(value.payloadReg(), 0); + ma_b(b ? NotEqual : Equal, label); +} + +void MacroAssembler::branchTestString(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_STRING), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestString(Condition cond, const ValueOperand& value, + Label* label) { + branchTestString(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestString(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestString(cond, tag, label); +} + +void MacroAssembler::branchTestString(Condition cond, const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestString(cond, tag, label); +} + +void MacroAssembler::branchTestStringTruthy(bool b, const ValueOperand& value, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(Address(value.payloadReg(), JSString::offsetOfLength()), scratch); + as_cmpwi(scratch, 0); + ma_b(b ? NotEqual : Equal, label); +} + +void MacroAssembler::branchTestSymbol(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_SYMBOL), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestSymbol(Condition cond, const ValueOperand& value, + Label* label) { + branchTestSymbol(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestSymbol(Condition cond, const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestSymbol(cond, tag, label); +} + +void MacroAssembler::branchTestSymbol(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestSymbol(cond, tag, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_BIGINT), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const ValueOperand& value, + Label* label) { + branchTestBigInt(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestBigInt(cond, tag, label); +} + +void MacroAssembler::branchTestBigInt(Condition cond, const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestBigInt(cond, tag, label); +} + +void MacroAssembler::branchTestBigIntTruthy(bool b, const ValueOperand& value, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(Address(value.payloadReg(), BigInt::offsetOfDigitLength()), scratch); + as_cmpwi(scratch, 0); + ma_b(b ? NotEqual : Equal, label); +} + +void MacroAssembler::branchTestNull(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_NULL), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestNull(Condition cond, const ValueOperand& value, + Label* label) { + branchTestNull(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestNull(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestNull(cond, tag, label); +} + +void MacroAssembler::branchTestNull(Condition cond, const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestNull(cond, tag, label); +} + +void MacroAssembler::branchTestObject(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_OBJECT), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestObject(Condition cond, const ValueOperand& value, + Label* label) { + branchTestObject(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestObject(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestObject(cond, tag, label); +} + +void MacroAssembler::branchTestObject(Condition cond, const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestObject(cond, tag, label); +} + +void MacroAssembler::branchTestPrimitive(Condition cond, + const ValueOperand& value, + Label* label) { + branchTestPrimitive(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestGCThing(Condition cond, const Address& address, + Label* label) { + branchTestGCThingImpl(cond, address, label); +} + +void MacroAssembler::branchTestGCThing(Condition cond, const BaseIndex& address, + Label* label) { + branchTestGCThingImpl(cond, address, label); +} + +void MacroAssembler::branchTestGCThing(Condition cond, + const ValueOperand& address, + Label* label) { + branchTestGCThingImpl(cond, address, label); +} + +template +void MacroAssembler::branchTestGCThingImpl(Condition cond, const T& address, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + Condition actual = (cond == Equal) ? AboveOrEqual : Below; + Condition c = + ma_cmp(tag, ImmTag(JS::detail::ValueLowerInclGCThingTag), actual); + ma_b(c, label); +} + +void MacroAssembler::branchTestPrimitive(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition actual = (cond == Equal) ? Below : AboveOrEqual; + Condition c = + ma_cmp(tag, ImmTag(JS::detail::ValueUpperExclPrimitiveTag), actual); + ma_b(c, label); +} + +void MacroAssembler::branchTestMagic(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_MAGIC), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestMagic(Condition cond, const Address& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestMagic(cond, tag, label); +} + +void MacroAssembler::branchTestMagic(Condition cond, const BaseIndex& address, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(address, scratch); + branchTestMagic(cond, tag, label); +} + +void MacroAssembler::branchTestMagic(Condition cond, const ValueOperand& value, + Label* label) { + branchTestMagic(cond, value.typeReg(), label); +} + +void MacroAssembler::branchTestMagic(Condition cond, const Address& valaddr, + JSWhyMagic why, Label* label) { + MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); + Label notMagic; + if (cond == Assembler::Equal) { + branchTestMagic(Assembler::NotEqual, valaddr, ¬Magic); + } else { + branchTestMagic(Assembler::NotEqual, valaddr, label); + } + branch32(cond, valuePayload(valaddr), Imm32(why), label); + bind(¬Magic); +} + +void MacroAssembler::branchTestMagic(Condition cond, const BaseIndex& valaddr, + JSWhyMagic why, Label* label) { + MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); + Label notMagic; + if (cond == Assembler::Equal) { + branchTestMagic(Assembler::NotEqual, valaddr, ¬Magic); + } else { + branchTestMagic(Assembler::NotEqual, valaddr, label); + } + branch32(cond, valuePayload(valaddr), Imm32(why), label); + bind(¬Magic); +} + +template +void MacroAssembler::branchTestValue(Condition cond, const T& lhs, + const ValueOperand& rhs, Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + // Big-endian nunbox32: the type word is at offset 0 and the payload at +4, + // which is what valuePayload() encodes. + Label notSameValue; + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(lhs, scratch); + Condition tagNe = ma_cmp(scratch, rhs.typeReg(), Assembler::NotEqual, true); + ma_b(tagNe, (cond == Assembler::Equal) ? ¬SameValue : label); + load32(valuePayload(lhs), scratch); + Condition c = ma_cmp(scratch, rhs.payloadReg(), cond, true); + ma_b(c, label); + bind(¬SameValue); +} + +// =============================================================== +// Test-set functions + +template +void MacroAssembler::testNumberSet(Condition cond, const T& src, + Register dest) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(src, scratch); + Condition actual = (cond == Equal) ? BelowOrEqual : Above; + Condition c = + ma_cmp(tag, ImmTag(JS::detail::ValueUpperInclNumberTag), actual); + ma_cmp_set(dest, c); +} + +template +void MacroAssembler::testBooleanSet(Condition cond, const T& src, + Register dest) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(src, scratch); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_BOOLEAN), cond); + ma_cmp_set(dest, c); +} + +template +void MacroAssembler::testStringSet(Condition cond, const T& src, + Register dest) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(src, scratch); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_STRING), cond); + ma_cmp_set(dest, c); +} + +template +void MacroAssembler::testSymbolSet(Condition cond, const T& src, + Register dest) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(src, scratch); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_SYMBOL), cond); + ma_cmp_set(dest, c); +} + +template +void MacroAssembler::testBigIntSet(Condition cond, const T& src, + Register dest) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + Register tag = extractTag(src, scratch); + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_BIGINT), cond); + ma_cmp_set(dest, c); +} + +// =============================================================== +// Computed address / conditional move / conditional load + +void MacroAssembler::branchToComputedAddress(const BaseIndex& addr) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(addr, scratch); + branch(scratch); +} + +void MacroAssembler::cmp32Move32(Condition cond, Register lhs, Imm32 rhs, + Register src, Register dest) { + Condition c = ma_cmp(lhs, rhs, cond, true); + ma_cmp_move(dest, src, c); +} + +void MacroAssembler::cmp32Move32(Condition cond, Register lhs, Register rhs, + Register src, Register dest) { + Condition c = ma_cmp(lhs, rhs, cond, true); + ma_cmp_move(dest, src, c); +} + +void MacroAssembler::cmp32Move32(Condition cond, Register lhs, + const Address& rhs, Register src, + Register dest) { + Condition c; + { + // Release the scratch before ma_cmp_move, which needs both of them. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(rhs, scratch); + c = ma_cmp(lhs, scratch, cond, true); + } + ma_cmp_move(dest, src, c); +} + +void MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, + Register src, Register dest) { + Condition c = ma_cmp(lhs, rhs, cond, true); + ma_cmp_move(dest, src, c); +} + +void MacroAssembler::cmpPtrMovePtr(Condition cond, Register lhs, Imm32 rhs, + Register src, Register dest) { + Condition c = ma_cmp(lhs, rhs, cond, true); + ma_cmp_move(dest, src, c); +} + +void MacroAssembler::cmpPtrMovePtr(Condition cond, Register lhs, Register rhs, + Register src, Register dest) { + Condition c = ma_cmp(lhs, rhs, cond); + ma_cmp_move(dest, src, c); +} + +void MacroAssembler::cmpPtrMovePtr(Condition cond, Register lhs, + const Address& rhs, Register src, + Register dest) { + Condition c; + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(rhs, scratch); + c = ma_cmp(lhs, scratch, cond); + } + ma_cmp_move(dest, src, c); +} + +void MacroAssembler::cmp32Load32(Condition cond, Register lhs, + const Address& rhs, const Address& src, + Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(rhs, scratch); + Condition c = ma_cmp(lhs, scratch, cond, true); + load32(src, scratch); + ma_cmp_move_clobber_src(dest, scratch, c); +} + +void MacroAssembler::cmp32Load32(Condition cond, Register lhs, Register rhs, + const Address& src, Register dest) { + Condition c = ma_cmp(lhs, rhs, cond, true); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(src, scratch); + ma_cmp_move_clobber_src(dest, scratch, c); +} + +void MacroAssembler::cmp32Load32(Condition cond, Register lhs, Imm32 rhs, + const Address& src, Register dest) { + Condition c = ma_cmp(lhs, rhs, cond, true); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(src, scratch); + ma_cmp_move_clobber_src(dest, scratch, c); +} + +void MacroAssembler::cmp32LoadPtr(Condition cond, const Address& lhs, Imm32 rhs, + const Address& src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(lhs, scratch); + Condition c = ma_cmp(scratch, rhs, cond, true); + loadPtr(src, scratch); + ma_cmp_move_clobber_src(dest, scratch, c); +} + +void MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, + Imm32 mask, const Address& src, + Register dest) { + MOZ_ASSERT(cond == Zero || cond == NonZero); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(addr, scratch); + if (is_uintN(mask.value, 16)) { + as_andi_rc(scratch, scratch, mask.value); + } else { + // Use a nested scope so scratch2 is released before loadPtr below. + UseScratchRegisterScope temps2(asMasm()); + Register scratch2 = temps2.Acquire(); + move32(mask, scratch2); + as_and__rc(scratch, scratch, scratch2); + } + Condition base = static_cast(cond & ~Assembler::ConditionZero); + loadPtr(src, scratch); + ma_cmp_move_clobber_src(dest, scratch, base); +} + +void MacroAssembler::test32MovePtr(Condition cond, Register operand, Imm32 mask, + Register src, Register dest) { + MOZ_ASSERT(cond == Zero || cond == NonZero); + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + if (is_uintN(mask.value, 16)) { + as_andi_rc(scratch, operand, mask.value); + } else { + move32(mask, scratch); + as_and__rc(scratch, operand, scratch); + } + } + Condition base = static_cast(cond & ~Assembler::ConditionZero); + ma_cmp_move(dest, src, base); +} + +void MacroAssembler::test32MovePtr(Condition cond, const Address& addr, + Imm32 mask, Register src, Register dest) { + MOZ_ASSERT(cond == Zero || cond == NonZero); + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(addr, scratch); + and32(mask, scratch); + as_cmpwi(scratch, 0); + } + Condition base = static_cast(cond & ~Assembler::ConditionZero); + ma_cmp_move(dest, src, base); +} + +// =============================================================== +// Spectre mitigations + +void MacroAssembler::spectreMovePtr(Condition cond, Register src, + Register dest) { + // Assumes compare already issued. + Condition base = static_cast( + cond & ~(Assembler::ConditionUnsigned | Assembler::ConditionZero)); + ma_cmp_move(dest, src, base); +} + +void MacroAssembler::spectreZeroRegister(Condition cond, Register scratch, + Register dest) { + // Assumes compare already issued. Zero dest if the condition is true. + Condition origBase = static_cast( + cond & ~(Assembler::ConditionUnsigned | Assembler::ConditionZero)); + xs_li(scratch, 0); + ma_cmp_move_clobber_src(dest, scratch, origBase); +} + +void MacroAssembler::spectreBoundsCheck32(Register index, Register length, + Register maybeScratch, + Label* failure) { + Condition c = ma_cmp(index, length, Below, true); + if (failure) { + ma_b(InvertCondition(c), failure); + } + if (maybeScratch != InvalidReg) { + xs_li(maybeScratch, 0); + ma_cmp_move_clobber_src(index, maybeScratch, InvertCondition(c)); + } +} + +void MacroAssembler::spectreBoundsCheck32(Register index, const Address& length, + Register maybeScratch, + Label* failure) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(length, scratch); + spectreBoundsCheck32(index, scratch, maybeScratch, failure); +} + +void MacroAssembler::spectreBoundsCheckPtr(Register index, Register length, + Register maybeScratch, + Label* failure) { + spectreBoundsCheck32(index, length, maybeScratch, failure); +} + +void MacroAssembler::spectreBoundsCheckPtr(Register index, + const Address& length, + Register maybeScratch, + Label* failure) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(length, scratch); + spectreBoundsCheckPtr(index, scratch, maybeScratch, failure); +} + +// =============================================================== +// Memory access primitives + +FaultingCodeOffset MacroAssembler::storeFloat32(FloatRegister src, + const Address& addr) { + MOZ_ASSERT(addr.base != r0); + if (is_intN(addr.offset, 16)) { + return FaultingCodeOffset(as_stfs(src, addr.base, addr.offset).getOffset()); + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(ImmWord(uintptr_t(intptr_t(addr.offset))), scratch); + return FaultingCodeOffset(as_stfsx(src, addr.base, scratch).getOffset()); +} + +FaultingCodeOffset MacroAssembler::storeFloat32(FloatRegister src, + const BaseIndex& addr) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + computeEffectiveAddress(addr, scratch); + return FaultingCodeOffset(as_stfs(src, scratch, 0).getOffset()); +} + +FaultingCodeOffset MacroAssembler::storeDouble(FloatRegister src, + const Address& addr) { + MOZ_ASSERT(addr.base != r0); + if (is_intN(addr.offset, 16)) { + return FaultingCodeOffset(as_stfd(src, addr.base, addr.offset).getOffset()); + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(ImmWord(uintptr_t(intptr_t(addr.offset))), scratch); + return FaultingCodeOffset(as_stfdx(src, addr.base, scratch).getOffset()); +} + +FaultingCodeOffset MacroAssembler::storeDouble(FloatRegister src, + const BaseIndex& addr) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + computeEffectiveAddress(addr, scratch); + return FaultingCodeOffset(as_stfd(src, scratch, 0).getOffset()); +} + +FaultingCodeOffset MacroAssembler::storeFloat16(FloatRegister src, + const Address& dest, + Register temp) { + // No half-word FPR store on this target; go through a GPR. + moveFloat16ToGPR(src, temp); + return store16(temp, dest); +} + +FaultingCodeOffset MacroAssembler::storeFloat16(FloatRegister src, + const BaseIndex& dest, + Register temp) { + moveFloat16ToGPR(src, temp); + return store16(temp, dest); +} + +void MacroAssembler::memoryBarrier(MemoryBarrier barrier) { + if (barrier.isNone()) { + return; + } + if (barrier.hasStoreLoad() || barrier.hasSync()) { + as_sync(); + } else { + as_lwsync(); + } +} + +// =============================================================== +// Clamping functions + +void MacroAssembler::clampIntToUint8(Register reg) { + // Clamp to [0, 255]. + Label done; + as_cmpwi(reg, 255); + ma_b(LessThanOrEqual, &done); + move32(Imm32(255), reg); + bind(&done); + Label positive; + as_cmpwi(reg, 0); + ma_b(GreaterThanOrEqual, &positive); + move32(Imm32(0), reg); + bind(&positive); +} + +// =============================================================== +// Unboxing + +template +static void FallibleUnboxPtr(MacroAssembler& masm, const T& src, Register dest, + JSValueType type, Label* fail) { + switch (type) { + case JSVAL_TYPE_OBJECT: + masm.branchTestObject(Assembler::NotEqual, src, fail); + break; + case JSVAL_TYPE_STRING: + masm.branchTestString(Assembler::NotEqual, src, fail); + break; + case JSVAL_TYPE_SYMBOL: + masm.branchTestSymbol(Assembler::NotEqual, src, fail); + break; + case JSVAL_TYPE_BIGINT: + masm.branchTestBigInt(Assembler::NotEqual, src, fail); + break; + default: + MOZ_CRASH("Unexpected type"); + } + masm.unboxNonDouble(src, dest, type); +} + +void MacroAssembler::fallibleUnboxPtr(const ValueOperand& src, Register dest, + JSValueType type, Label* fail) { + FallibleUnboxPtr(*this, src, dest, type, fail); +} + +void MacroAssembler::fallibleUnboxPtr(const Address& src, Register dest, + JSValueType type, Label* fail) { + FallibleUnboxPtr(*this, src, dest, type, fail); +} + +void MacroAssembler::fallibleUnboxPtr(const BaseIndex& src, Register dest, + JSValueType type, Label* fail) { + FallibleUnboxPtr(*this, src, dest, type, fail); +} + +void MacroAssembler::wasmAddSubI128HI64(Register lhsLo, Register lhsHi, + Register rhsLo, Register rhsHi, + Register output, bool isAdd) { + MOZ_CRASH("NYI: wasmAddSubI128HI64 is a 64-bit-target-only primitive"); +} + +void MacroAssembler::wasmMulI64WideHI64(Register lhs, Register rhs, + Register output, bool isSigned) { + MOZ_CRASH("NYI: wasmMulI64WideHI64 is a 64-bit-target-only primitive"); +} + +//}}} check_macroassembler_style + +void MacroAssemblerPPCCompat::incrementInt32Value(const Address& addr) { + // Increment the int32 payload, which on big endian is at +4 in the Value. + asMasm().add32(Imm32(1), valuePayload(addr)); +} + +void MacroAssemblerPPCCompat::retn(Imm32 n) { + // Load the return address from [SP,0] before adjusting SP, since it sits at + // the current top of stack rather than at SP+n. + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_lwz(scratch, StackPointer, 0); + if (n.value != 0) { + asMasm().addPtr(Imm32(n.value), StackPointer); + } + xs_mtlr(scratch); + as_blr(); +} + +// =============================================================== +// Template specializations (outside check_macroassembler_style) + +template <> +inline void MacroAssembler::cmpPtrSet(Assembler::Condition cond, Address lhs, + ImmPtr rhs, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Assembler::Condition c = ma_cmp(scratch, rhs, cond); + ma_cmp_set(dest, c); +} + +template <> +inline void MacroAssembler::cmpPtrSet(Assembler::Condition cond, Register lhs, + Address rhs, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + loadPtr(rhs, scratch); + Assembler::Condition c = ma_cmp(lhs, scratch, cond); + ma_cmp_set(dest, c); +} + +template <> +inline void MacroAssembler::cmpPtrSet(Assembler::Condition cond, Address lhs, + Register rhs, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Assembler::Condition c = ma_cmp(scratch, rhs, cond); + ma_cmp_set(dest, c); +} + +template <> +inline void MacroAssembler::cmp32Set(Assembler::Condition cond, Register lhs, + Address rhs, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + load32(rhs, scratch); + Assembler::Condition c = ma_cmp(lhs, scratch, cond, true); + ma_cmp_set(dest, c); +} + +template <> +inline void MacroAssembler::cmp32Set(Assembler::Condition cond, Address lhs, + Register rhs, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + load32(lhs, scratch); + Assembler::Condition c = ma_cmp(scratch, rhs, cond, true); + ma_cmp_set(dest, c); +} + +template <> +inline void MacroAssembler::cmp32Set(Assembler::Condition cond, Address lhs, + Imm32 rhs, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + load32(lhs, scratch); + Assembler::Condition c = ma_cmp(scratch, rhs, cond, true); + ma_cmp_set(dest, c); +} + +//{{{ check_macroassembler_style +// =============================================================== +// SIMD load/store (128-bit) +// +// VMX has no unaligned quadword access and no element-granular memory +// operation, so an unaligned 128-bit access is reassembled a word at a time +// through a 16-byte-aligned stack slot. The JIT keeps SP aligned to +// JitStackAlignment (16), which is what lvx/stvx require: they force the +// effective address down to a 16-byte boundary. + +static constexpr int32_t SimdSlotSize = 16; + +// Store/load a vector at `off` bytes above SP; `off` must be 16-aligned. +// lvx/stvx are indexed-only. r0 is not allocatable and reads as a literal +// zero in the base position, so it serves as both the "no base" marker and +// the index register without consuming a scratch. +static void SimdSpill(MacroAssembler& masm, FloatRegister src, int32_t off) { + MOZ_ASSERT(off % SimdSlotSize == 0); + if (off == 0) { + masm.as_stvx(src, r0, StackPointer); + return; + } + masm.xs_li(r0, int16_t(off)); + masm.as_stvx(src, StackPointer, r0); +} + +static void SimdFill(MacroAssembler& masm, FloatRegister dest, int32_t off) { + MOZ_ASSERT(off % SimdSlotSize == 0); + if (off == 0) { + masm.as_lvx(dest, r0, StackPointer); + return; + } + masm.xs_li(r0, int16_t(off)); + masm.as_lvx(dest, StackPointer, r0); +} + +FaultingCodeOffset MacroAssembler::loadUnalignedSimd128(const Address& src, + FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register addr = temps.Acquire(); + computeEffectiveAddress(src, addr); + Register data = temps.Acquire(); + reserveStack(SimdSlotSize); + FaultingCodeOffset fco(as_lwz(data, addr, 0).getOffset()); + as_stw(data, StackPointer, 0); + for (int32_t i = 4; i < 16; i += 4) { + as_lwz(data, addr, i); + as_stw(data, StackPointer, i); + } + SimdFill(*this, dest, 0); + freeStack(SimdSlotSize); + return fco; +} + +FaultingCodeOffset MacroAssembler::loadUnalignedSimd128(const BaseIndex& src, + FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register addr = temps.Acquire(); + computeEffectiveAddress(src, addr); + Register data = temps.Acquire(); + reserveStack(SimdSlotSize); + FaultingCodeOffset fco(as_lwz(data, addr, 0).getOffset()); + as_stw(data, StackPointer, 0); + for (int32_t i = 4; i < 16; i += 4) { + as_lwz(data, addr, i); + as_stw(data, StackPointer, i); + } + SimdFill(*this, dest, 0); + freeStack(SimdSlotSize); + return fco; +} + +FaultingCodeOffset MacroAssembler::storeUnalignedSimd128(FloatRegister src, + const Address& dest) { + UseScratchRegisterScope temps(asMasm()); + Register addr = temps.Acquire(); + computeEffectiveAddress(dest, addr); + Register data = temps.Acquire(); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + as_lwz(data, StackPointer, 0); + FaultingCodeOffset fco(as_stw(data, addr, 0).getOffset()); + for (int32_t i = 4; i < 16; i += 4) { + as_lwz(data, StackPointer, i); + as_stw(data, addr, i); + } + freeStack(SimdSlotSize); + return fco; +} + +FaultingCodeOffset MacroAssembler::storeUnalignedSimd128( + FloatRegister src, const BaseIndex& dest) { + UseScratchRegisterScope temps(asMasm()); + Register addr = temps.Acquire(); + computeEffectiveAddress(dest, addr); + Register data = temps.Acquire(); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + as_lwz(data, StackPointer, 0); + FaultingCodeOffset fco(as_stw(data, addr, 0).getOffset()); + for (int32_t i = 4; i < 16; i += 4) { + as_lwz(data, StackPointer, i); + as_stw(data, addr, i); + } + freeStack(SimdSlotSize); + return fco; +} + +// =============================================================== +// SIMD operations +// +// Scratch register conventions for SIMD helpers (read this before writing +// a new one): +// +// 1. `ScratchSimd128Scope scratch(*this)` — names v0 (VR0), which is +// non-allocatable. Default temp; one per helper, and it is a plain +// wrapper, so nesting two of them aliases rather than asserting. +// +// 2. **Do NOT** write to VR1..VR31 without a Lowering temp: they are +// allocatable and a live wasm v128 may be sitting in any of them. +// +// 3. When a helper needs more than one vector temp, reserve a frame with +// `reserveStack(n * SimdSlotSize)` and use SimdSpill/SimdFill. SysV +// ppc32 has no red zone, so writing below SP is not an option. +// +// 4. Nothing moves bits between a GPR/FPR and a VR, and there is no +// element-granular VMX memory access, so all lane extraction and +// insertion goes through a 16-byte-aligned stack slot. +// +// 5. VMX has no double-precision vector arithmetic, so every f64x2 +// operation is done as two scalar FPR operations over such a slot. + +typedef void (*VmxBinaryFn)(Assembler&, uint8_t, uint8_t, uint8_t); + +static void EmitVmxBinary(MacroAssembler& masm, VmxBinaryFn vmxOp, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + vmxOp(static_cast(masm), dest.encoding() & 31, + lhs.encoding() & 31, rhs.encoding() & 31); +} + +// Macro for defining VMX binary wrappers. +#define VMX_BINARY_WRAPPER(vmxInst) \ + [](Assembler& a, uint8_t vrt, uint8_t vra, uint8_t vrb) { \ + a.as_##vmxInst(vrt, vra, vrb); \ + } + +// Emit op directly on Simd128 dest, then complement in place. +template +static void EmitVmxBinaryNot(MacroAssembler& masm, VmxBinaryFnT vmxOp, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + vmxOp(static_cast(masm), dest.encoding() & 31, + lhs.encoding() & 31, rhs.encoding() & 31); + masm.as_vnor(dest, dest, dest); +} + +// Integer SIMD compare helper. VMX compare instructions produce all-ones +// for true, all-zeros for false per element. +// Available VMX compares: vcmpequ* (eq), vcmpgts* (signed gt), vcmpgtu* +// (unsigned gt). Other conditions derived by swapping operands or +// complementing. +template +static void EmitVmxCompare(MacroAssembler& masm, Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, EqFn eqFn, GtsFn gtsFn, + GtuFn gtuFn) { + switch (cond) { + case Assembler::Equal: + EmitVmxBinary(masm, eqFn, lhs, rhs, dest); + break; + case Assembler::NotEqual: + EmitVmxBinaryNot(masm, eqFn, lhs, rhs, dest); + break; + case Assembler::GreaterThan: + EmitVmxBinary(masm, gtsFn, lhs, rhs, dest); + break; + case Assembler::GreaterThanOrEqual: + // !(rhs > lhs) + EmitVmxBinaryNot(masm, gtsFn, rhs, lhs, dest); + break; + case Assembler::LessThan: + // rhs > lhs (swap) + EmitVmxBinary(masm, gtsFn, rhs, lhs, dest); + break; + case Assembler::LessThanOrEqual: + // !(lhs > rhs) + EmitVmxBinaryNot(masm, gtsFn, lhs, rhs, dest); + break; + case Assembler::Above: + EmitVmxBinary(masm, gtuFn, lhs, rhs, dest); + break; + case Assembler::AboveOrEqual: + EmitVmxBinaryNot(masm, gtuFn, rhs, lhs, dest); + break; + case Assembler::Below: + EmitVmxBinary(masm, gtuFn, rhs, lhs, dest); + break; + case Assembler::BelowOrEqual: + EmitVmxBinaryNot(masm, gtuFn, lhs, rhs, dest); + break; + default: + MOZ_CRASH("Unexpected SIMD integer condition"); + } +} + +// Emit ternary VMX op directly on Simd128 regs, no staging. +typedef void (*VmxTernaryFn)(Assembler&, uint8_t, uint8_t, uint8_t, uint8_t); + +static void EmitVmxTernary(MacroAssembler& masm, VmxTernaryFn vmxOp, + FloatRegister a, FloatRegister b, FloatRegister c, + FloatRegister dest) { + vmxOp(static_cast(masm), dest.encoding() & 31, a.encoding() & 31, + b.encoding() & 31, c.encoding() & 31); +} + +// Emit unary VMX op directly on Simd128 regs, no staging. +typedef void (*VmxUnaryFn)(Assembler&, uint8_t, uint8_t); + +static void EmitVmxUnary(MacroAssembler& masm, VmxUnaryFn vmxOp, + FloatRegister src, FloatRegister dest) { + vmxOp(static_cast(masm), dest.encoding() & 31, + src.encoding() & 31); +} + +static void ZeroSimd128(MacroAssembler& masm, FloatRegister dest) { + masm.as_vxor(dest, dest, dest); +} + +// Select doublewords out of a pair of vectors the way VSX xxpermdi did: +// dm = 0 -> [a.dw0, b.dw0] dm = 1 -> [a.dw0, b.dw1] +// dm = 2 -> [a.dw1, b.dw0] dm = 3 -> [a.dw1, b.dw1] +// vsldoi(x, y, 8) yields [x.dw1, y.dw0], so each case needs at most three of +// them plus ScratchSimd128Reg. `dest` may alias `a` or `b`. +static void PermuteDwords(MacroAssembler& masm, FloatRegister a, + FloatRegister b, uint8_t dm, FloatRegister dest) { + ScratchSimd128Scope scratch(masm); + switch (dm) { + case 0: + masm.as_vsldoi(scratch, a, a, 8); + masm.as_vsldoi(dest, scratch, b, 8); + break; + case 1: + if (a == b) { + masm.moveSimd128(a, dest); + break; + } + masm.as_vsldoi(scratch, a, a, 8); + masm.as_vsldoi(dest, b, b, 8); + masm.as_vsldoi(dest, scratch, dest, 8); + break; + case 2: + masm.as_vsldoi(dest, a, b, 8); + break; + case 3: + masm.as_vsldoi(scratch, b, b, 8); + masm.as_vsldoi(dest, a, scratch, 8); + break; + default: + MOZ_CRASH("bad doubleword selector"); + } +} + +void MacroAssembler::moveSimd128(FloatRegister src, FloatRegister dest) { + if (src != dest) { + as_vor(dest, src, src); + } +} + +void MacroAssembler::loadConstantSimd128(const SimdConstant& v, + FloatRegister dest) { + // Load a 128-bit constant from the inline constant pool. On big endian the + // pool bytes are stored pre-reversed (see loadFromPoolSimd128), so the raw + // load already yields the canonical register layout the lane ops expect. + // The stanza's bcl clobbers LR; preserve it as loadConstantDouble does. + xs_mflr(r0); + loadFromPoolSimd128(dest, v); + xs_mtlr(r0); +} + +// Lane mapping (unchanged from the little-endian backend: on this big-endian +// target CodeGenerator byte-reverses a v128 at every memory boundary, so the +// in-register image is the one the ppc64le lowering produced): +// Wasm lane K = register byte (15 - K). +// For VMX byte ops, BE byte index = 15 - wasm_lane. +// For VMX halfword ops, BE halfword index = 7 - wasm_halfword. +// For VMX word ops (vspltw), BE word index = 3 - wasm_word. +// For doubleword ops, BE dword index = 1 - wasm_dword. +// +// stvx puts BE byte i at memory offset i, so the scalar value of wasm lane L +// of width W bytes is the plain big-endian value at slot offset +// 16 - W - W*L. + +static int32_t SimdLaneOffset(unsigned lane, unsigned widthBytes) { + return int32_t(16 - widthBytes - widthBytes * lane); +} + +void MacroAssembler::splatX16(Register src, FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register word = temps.Acquire(); + // Replicate the low byte through a word, then fill the slot with it. + as_rlwinm(word, src, 0, 24, 31); + as_rlwimi(word, word, 8, 16, 23); + as_rlwimi(word, word, 16, 0, 15); + reserveStack(SimdSlotSize); + for (int32_t i = 0; i < 16; i += 4) { + as_stw(word, StackPointer, i); + } + SimdFill(*this, dest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::splatX8(Register src, FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register word = temps.Acquire(); + as_rlwinm(word, src, 0, 16, 31); + as_rlwimi(word, word, 16, 0, 15); + reserveStack(SimdSlotSize); + for (int32_t i = 0; i < 16; i += 4) { + as_stw(word, StackPointer, i); + } + SimdFill(*this, dest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::splatX4(Register src, FloatRegister dest) { + reserveStack(SimdSlotSize); + for (int32_t i = 0; i < 16; i += 4) { + as_stw(src, StackPointer, i); + } + SimdFill(*this, dest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::splatX4(FloatRegister src, FloatRegister dest) { + // src is an FPR holding a float value in double format; stfs rounds it + // back to its single pattern. + reserveStack(SimdSlotSize); + for (int32_t i = 0; i < 16; i += 4) { + as_stfs(src, StackPointer, i); + } + SimdFill(*this, dest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::splatX2(FloatRegister src, FloatRegister dest) { + reserveStack(SimdSlotSize); + as_stfd(src, StackPointer, 0); + as_stfd(src, StackPointer, 8); + SimdFill(*this, dest, 0); + freeStack(SimdSlotSize); +} + +// Helpers: splat Imm32 into SIMD register at various element widths. +// VMX shift instructions read the shift count from EACH element +// independently, so the count must be replicated to every byte/halfword/word +// as appropriate. vspltis{b,h,w} covers [-16, 15] in one instruction; wider +// values go through the GPR splat above, which keeps the count in +// architectural layout (loadConstantSimd128 byte-reverses for wasm's +// little-endian constants and would put the count in the wrong bytes). +static void SplatImm8(MacroAssembler& masm, Imm32 imm, FloatRegister dest) { + int8_t val = (int8_t)imm.value; + if (val >= -16 && val <= 15) { + masm.as_vspltisb(dest.encoding() & 31, val); + return; + } + UseScratchRegisterScope temps(masm); + Register tmp = temps.Acquire(); + masm.xs_li(tmp, val); + masm.splatX16(tmp, dest); +} + +static void SplatImm16(MacroAssembler& masm, Imm32 imm, FloatRegister dest) { + int16_t val = (int16_t)imm.value; + if (val >= -16 && val <= 15) { + masm.as_vspltish(dest.encoding() & 31, (int8_t)val); + return; + } + UseScratchRegisterScope temps(masm); + Register tmp = temps.Acquire(); + masm.xs_li(tmp, val); + masm.splatX8(tmp, dest); +} + +static void SplatImm32(MacroAssembler& masm, Imm32 imm, FloatRegister dest) { + int32_t val = imm.value; + if (val >= -16 && val <= 15) { + masm.as_vspltisw(dest.encoding() & 31, (int8_t)val); + return; + } + UseScratchRegisterScope temps(masm); + Register tmp = temps.Acquire(); + masm.move32(Imm32(val), tmp); + masm.splatX4(tmp, dest); +} + +// =============================================================== +// Extract lane + +void MacroAssembler::unsignedExtractLaneInt8x16(uint32_t lane, + FloatRegister src, + Register dest) { + MOZ_ASSERT(lane < 16); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + as_lbz(dest, StackPointer, SimdLaneOffset(lane, 1)); + freeStack(SimdSlotSize); +} + +void MacroAssembler::unsignedExtractLaneInt16x8(uint32_t lane, + FloatRegister src, + Register dest) { + MOZ_ASSERT(lane < 8); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + as_lhz(dest, StackPointer, SimdLaneOffset(lane, 2)); + freeStack(SimdSlotSize); +} + +void MacroAssembler::extractLaneFloat32x4(uint32_t lane, FloatRegister src, + FloatRegister dest) { + MOZ_ASSERT(lane < 4); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + as_lfs(dest, StackPointer, SimdLaneOffset(lane, 4)); + freeStack(SimdSlotSize); +} + +void MacroAssembler::extractLaneFloat64x2(uint32_t lane, FloatRegister src, + FloatRegister dest) { + MOZ_ASSERT(lane < 2); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + as_lfd(dest, StackPointer, SimdLaneOffset(lane, 8)); + freeStack(SimdSlotSize); +} + +// =============================================================== +// Replace lane + +void MacroAssembler::replaceLaneInt8x16(unsigned lane, Register rhs, + FloatRegister lhsDest) { + MOZ_ASSERT(lane < 16); + reserveStack(SimdSlotSize); + SimdSpill(*this, lhsDest, 0); + as_stb(rhs, StackPointer, SimdLaneOffset(lane, 1)); + SimdFill(*this, lhsDest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::replaceLaneInt16x8(unsigned lane, Register rhs, + FloatRegister lhsDest) { + MOZ_ASSERT(lane < 8); + reserveStack(SimdSlotSize); + SimdSpill(*this, lhsDest, 0); + as_sth(rhs, StackPointer, SimdLaneOffset(lane, 2)); + SimdFill(*this, lhsDest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::replaceLaneInt32x4(unsigned lane, Register rhs, + FloatRegister lhsDest) { + MOZ_ASSERT(lane < 4); + reserveStack(SimdSlotSize); + SimdSpill(*this, lhsDest, 0); + as_stw(rhs, StackPointer, SimdLaneOffset(lane, 4)); + SimdFill(*this, lhsDest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::replaceLaneFloat32x4(unsigned lane, FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_ASSERT(lane < 4); + reserveStack(SimdSlotSize); + SimdSpill(*this, lhsDest, 0); + as_stfs(rhs, StackPointer, SimdLaneOffset(lane, 4)); + SimdFill(*this, lhsDest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::replaceLaneFloat64x2(unsigned lane, FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_ASSERT(lane < 2); + reserveStack(SimdSlotSize); + SimdSpill(*this, lhsDest, 0); + as_stfd(rhs, StackPointer, SimdLaneOffset(lane, 8)); + SimdFill(*this, lhsDest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::shuffleInt8x16(const uint8_t lanes[16], FloatRegister rhs, + FloatRegister lhsDest) { + shuffleInt8x16(lanes, lhsDest, rhs, lhsDest); +} + +void MacroAssembler::shuffleInt8x16(const uint8_t lanes[16], FloatRegister lhs, + FloatRegister rhs, FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + // vperm uses BE byte indices: VRA[0]=MSB, VRA[15]=LSB, VRB[16..31]. + // Convert wasm lane indices to vperm control: lhs lane N = BE index + // (15-N), rhs lane N = BE index (31-N) = (47 - (N+16)). + int8_t ctrl[16]; + for (unsigned i = 0; i < 16; i++) { + uint8_t src = lanes[i]; + if (src < 16) { + ctrl[i] = 15 - src; + } else { + ctrl[i] = 47 - src; + } + } + loadConstantSimd128(SimdConstant::CreateX16(ctrl), scratch); + as_vperm(dest.encoding() & 31, lhs.encoding() & 31, rhs.encoding() & 31, + scratch.encoding() & 31); +} + +void MacroAssembler::laneSelectSimd128(FloatRegister mask, FloatRegister lhs, + FloatRegister rhs, FloatRegister dest) { + // vsel: VRT = (VRA & ~VRC) | (VRB & VRC). + // laneSelect: dest = (lhs & mask) | (rhs & ~mask). + as_vsel(dest, rhs, lhs, mask); +} + +void MacroAssembler::interleaveHighInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmrghb), rhs, lhs, dest); +} + +void MacroAssembler::interleaveHighInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmrghh), rhs, lhs, dest); +} + +void MacroAssembler::interleaveHighInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmrghw), rhs, lhs, dest); +} + +void MacroAssembler::interleaveHighInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + // Merge the high (BE dword 0) halves. + PermuteDwords(*this, rhs, lhs, 0, dest); +} + +void MacroAssembler::interleaveLowInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmrglb), rhs, lhs, dest); +} + +void MacroAssembler::interleaveLowInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmrglh), rhs, lhs, dest); +} + +void MacroAssembler::interleaveLowInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmrglw), rhs, lhs, dest); +} + +void MacroAssembler::interleaveLowInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + // Merge the low (BE dword 1) halves. + PermuteDwords(*this, rhs, lhs, 3, dest); +} + +void MacroAssembler::concatAndRightShiftSimd128(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest, + uint32_t shift) { + // vsldoi(VRA, VRB, SH) extracts 16 bytes starting at byte SH of the + // big-endian concatenation VRA||VRB, which in the canonical register + // layout is exactly the wasm right-shift-concat. + MOZ_ASSERT(shift < 16); + if (shift == 0) { + moveSimd128(rhs, dest); + return; + } + as_vsldoi(dest, lhs, rhs, shift); +} + +void MacroAssembler::leftShiftSimd128(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_ASSERT(count.value < 16); + if (count.value == 0) { + moveSimd128(src, dest); + return; + } + // vslo shifts the whole vector left by whole octets; the count lives in + // bits 1-4 of byte 15 of VRB, so splat count*8 into every word. + ScratchSimd128Scope scratch(*this); + SplatImm32(*this, Imm32(count.value * 8), scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vslo), src, scratch, dest); +} + +void MacroAssembler::rightShiftSimd128(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_ASSERT(count.value < 16); + if (count.value == 0) { + moveSimd128(src, dest); + return; + } + ScratchSimd128Scope scratch(*this); + SplatImm32(*this, Imm32(count.value * 8), scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsro), src, scratch, dest); +} + +void MacroAssembler::zeroExtend8x16To16x8(FloatRegister src, + FloatRegister dest) { + // Unsigned widen low: interleave the low bytes with zero bytes. Order + // matters: the merge reads src before dest is written, and dest may alias + // src. + ScratchSimd128Scope zero(*this); + ZeroSimd128(*this, zero); + as_vmrglb(dest.encoding() & 31, zero.encoding() & 31, src.encoding() & 31); +} + +void MacroAssembler::zeroExtend8x16To32x4(FloatRegister src, + FloatRegister dest) { + zeroExtend8x16To16x8(src, dest); + zeroExtend16x8To32x4(dest, dest); +} + +void MacroAssembler::zeroExtend8x16To64x2(FloatRegister src, + FloatRegister dest) { + zeroExtend8x16To32x4(src, dest); + zeroExtend32x4To64x2(dest, dest); +} + +void MacroAssembler::zeroExtend16x8To32x4(FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope zero(*this); + ZeroSimd128(*this, zero); + as_vmrglh(dest.encoding() & 31, zero.encoding() & 31, src.encoding() & 31); +} + +void MacroAssembler::zeroExtend16x8To64x2(FloatRegister src, + FloatRegister dest) { + zeroExtend16x8To32x4(src, dest); + zeroExtend32x4To64x2(dest, dest); +} + +void MacroAssembler::zeroExtend32x4To64x2(FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope zero(*this); + ZeroSimd128(*this, zero); + as_vmrglw(dest.encoding() & 31, zero.encoding() & 31, src.encoding() & 31); +} + +void MacroAssembler::reverseInt16x8(FloatRegister src, FloatRegister dest) { + const uint8_t lanes[] = {14, 15, 12, 13, 10, 11, 8, 9, + 6, 7, 4, 5, 2, 3, 0, 1}; + shuffleInt8x16(lanes, src, src, dest); +} + +void MacroAssembler::reverseInt32x4(FloatRegister src, FloatRegister dest) { + const uint8_t lanes[] = {12, 13, 14, 15, 8, 9, 10, 11, + 4, 5, 6, 7, 0, 1, 2, 3}; + shuffleInt8x16(lanes, src, src, dest); +} + +void MacroAssembler::reverseInt64x2(FloatRegister src, FloatRegister dest) { + as_vsldoi(dest, src, src, 8); +} + +void MacroAssembler::swizzleInt8x16Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + swizzleInt8x16(lhs, rhs, dest); +} + +// extMul{Low,High}Int{8x16,16x8} use the widening even/odd multiplies +// (vmul{e,o}{s,u}{b,h}) plus a halfword/word merge to map the BE-indexed +// even/odd products into wasm lane order. +// +// Aliasing safety: vmul* reads both operands before writing, so +// `dest = vmulo* lhs, rhs` is safe even when dest aliases lhs/rhs. +// One scratch holds the even-product half because vmrgl{h,w} reads dest +// after the odd multiply. + +void MacroAssembler::extMulLowInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31, r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31, s = scratch.encoding() & 31; + as_vmulesb(s, l, r); + as_vmulosb(d, l, r); + as_vmrglh(d, s, d); +} + +void MacroAssembler::extMulHighInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31, r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31, s = scratch.encoding() & 31; + as_vmulesb(s, l, r); + as_vmulosb(d, l, r); + as_vmrghh(d, s, d); +} + +void MacroAssembler::unsignedExtMulLowInt8x16(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31, r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31, s = scratch.encoding() & 31; + as_vmuleub(s, l, r); + as_vmuloub(d, l, r); + as_vmrglh(d, s, d); +} + +void MacroAssembler::unsignedExtMulHighInt8x16(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31, r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31, s = scratch.encoding() & 31; + as_vmuleub(s, l, r); + as_vmuloub(d, l, r); + as_vmrghh(d, s, d); +} + +void MacroAssembler::extMulLowInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31, r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31, s = scratch.encoding() & 31; + as_vmulesh(s, l, r); + as_vmulosh(d, l, r); + as_vmrglw(d, s, d); +} + +void MacroAssembler::extMulHighInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31, r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31, s = scratch.encoding() & 31; + as_vmulesh(s, l, r); + as_vmulosh(d, l, r); + as_vmrghw(d, s, d); +} + +void MacroAssembler::unsignedExtMulLowInt16x8(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31, r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31, s = scratch.encoding() & 31; + as_vmuleuh(s, l, r); + as_vmulouh(d, l, r); + as_vmrglw(d, s, d); +} + +void MacroAssembler::unsignedExtMulHighInt16x8(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31, r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31, s = scratch.encoding() & 31; + as_vmuleuh(s, l, r); + as_vmulouh(d, l, r); + as_vmrghw(d, s, d); +} + +// extMul{Low,High}Int32x4 use the word even/odd multiplies plus a doubleword +// permute to combine the two i64 partial products into wasm lane order. +// The multiplies read both operands before writing, and the permute reads +// both inputs before writing, so aliasing is safe. + +static void EmitExtMulInt32x4( + MacroAssembler& masm, FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, void (*mulEven)(Assembler&, uint8_t, uint8_t, uint8_t), + void (*mulOdd)(Assembler&, uint8_t, uint8_t, uint8_t), bool isLow) { + ScratchSimd128Scope even(masm); + uint8_t l = lhs.encoding() & 31; + uint8_t r = rhs.encoding() & 31; + mulEven(static_cast(masm), even.encoding() & 31, l, r); + mulOdd(static_cast(masm), dest.encoding() & 31, l, r); + // vsldoi(x, y, 8) = [x.dw1, y.dw0]; two of them pick either the low or the + // high doubleword of each product without a third vector register. + if (isLow) { + masm.as_vsldoi(dest, dest, dest, 8); + masm.as_vsldoi(dest, even, dest, 8); + } else { + masm.as_vsldoi(even, even, even, 8); + masm.as_vsldoi(dest, even, dest, 8); + } +} + +void MacroAssembler::extMulLowInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitExtMulInt32x4( + *this, lhs, rhs, dest, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulesw(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulosw(t, x, y); + }, + /* isLow = */ true); +} + +void MacroAssembler::extMulHighInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitExtMulInt32x4( + *this, lhs, rhs, dest, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulesw(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulosw(t, x, y); + }, + /* isLow = */ false); +} + +void MacroAssembler::unsignedExtMulLowInt32x4(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + EmitExtMulInt32x4( + *this, lhs, rhs, dest, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmuleuw(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulouw(t, x, y); + }, + /* isLow = */ true); +} + +void MacroAssembler::unsignedExtMulHighInt32x4(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + EmitExtMulInt32x4( + *this, lhs, rhs, dest, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmuleuw(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulouw(t, x, y); + }, + /* isLow = */ false); +} + +void MacroAssembler::q15MulrSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + // Q15 multiply-round-saturate: vmhraddshs(a, b, zero) computes + // saturate((a[i]*b[i] + 0x4000) >> 15) for each halfword. + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + EmitVmxTernary( + *this, + [](Assembler& a, uint8_t vrt, uint8_t vra, uint8_t vrb, uint8_t vrc) { + a.as_vmhraddshs(vrt, vra, vrb, vrc); + }, + lhs, rhs, scratch, dest); +} + +// neg = 0 - src, with ScratchSimd128Reg holding the zero so the register +// allocator sees no clobbered VRs. There is no vneg at any width in VMX. +void MacroAssembler::negInt8x16(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vsububm(dest.encoding() & 31, scratch.encoding() & 31, + src.encoding() & 31); +} + +void MacroAssembler::negInt16x8(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vsubuhm(dest.encoding() & 31, scratch.encoding() & 31, + src.encoding() & 31); +} + +void MacroAssembler::negInt32x4(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vsubuwm(dest.encoding() & 31, scratch.encoding() & 31, + src.encoding() & 31); +} + +void MacroAssembler::negInt64x2(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vsubudm(dest.encoding() & 31, scratch.encoding() & 31, + src.encoding() & 31); +} + +void MacroAssembler::unsignedAddSatInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vaddubs), lhs, rhs, dest); +} + +void MacroAssembler::unsignedAddSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vadduhs), lhs, rhs, dest); +} + +void MacroAssembler::unsignedSubSatInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsububs), lhs, rhs, dest); +} + +void MacroAssembler::unsignedSubSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsubuhs), lhs, rhs, dest); +} + +void MacroAssembler::unsignedMinInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vminub), lhs, rhs, dest); +} + +void MacroAssembler::unsignedMinInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vminuh), lhs, rhs, dest); +} + +void MacroAssembler::unsignedMinInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vminuw), lhs, rhs, dest); +} + +void MacroAssembler::unsignedMaxInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmaxub), lhs, rhs, dest); +} + +void MacroAssembler::unsignedMaxInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmaxuh), lhs, rhs, dest); +} + +void MacroAssembler::unsignedMaxInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmaxuw), lhs, rhs, dest); +} + +void MacroAssembler::unsignedAverageInt8x16(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vavgub), lhs, rhs, dest); +} + +void MacroAssembler::unsignedAverageInt16x8(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vavguh), lhs, rhs, dest); +} + +// abs(x) = max(x, -x) per signed lane; VMX has no vabs at any width. Compute +// -src into ScratchSimd128Reg first, then max into dest, which is safe even +// when dest == src because vmaxs* reads both operands before writing. + +void MacroAssembler::absInt8x16(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope tmp(*this); + ZeroSimd128(*this, tmp); + as_vsububm(tmp.encoding() & 31, tmp.encoding() & 31, src.encoding() & 31); + as_vmaxsb(dest.encoding() & 31, src.encoding() & 31, tmp.encoding() & 31); +} + +void MacroAssembler::absInt16x8(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope tmp(*this); + ZeroSimd128(*this, tmp); + as_vsubuhm(tmp.encoding() & 31, tmp.encoding() & 31, src.encoding() & 31); + as_vmaxsh(dest.encoding() & 31, src.encoding() & 31, tmp.encoding() & 31); +} + +void MacroAssembler::absInt32x4(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope tmp(*this); + ZeroSimd128(*this, tmp); + as_vsubuwm(tmp.encoding() & 31, tmp.encoding() & 31, src.encoding() & 31); + as_vmaxsw(dest.encoding() & 31, src.encoding() & 31, tmp.encoding() & 31); +} + +void MacroAssembler::absInt64x2(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope tmp(*this); + ZeroSimd128(*this, tmp); + as_vsubudm(tmp.encoding() & 31, tmp.encoding() & 31, src.encoding() & 31); + as_vmaxsd(dest.encoding() & 31, src.encoding() & 31, tmp.encoding() & 31); +} + +void MacroAssembler::leftShiftInt8x16(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm8(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vslb), src, scratch, dest); +} + +void MacroAssembler::leftShiftInt16x8(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm16(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vslh), src, scratch, dest); +} + +void MacroAssembler::leftShiftInt32x4(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm32(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vslw), src, scratch, dest); +} + +void MacroAssembler::leftShiftInt64x2(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm32(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsld), src, scratch, dest); +} + +void MacroAssembler::rightShiftInt8x16(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm8(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrab), src, scratch, dest); +} + +void MacroAssembler::unsignedRightShiftInt8x16(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm8(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrb), src, scratch, dest); +} + +void MacroAssembler::rightShiftInt16x8(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm16(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrah), src, scratch, dest); +} + +void MacroAssembler::unsignedRightShiftInt16x8(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm16(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrh), src, scratch, dest); +} + +void MacroAssembler::rightShiftInt32x4(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm32(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsraw), src, scratch, dest); +} + +void MacroAssembler::unsignedRightShiftInt32x4(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm32(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrw), src, scratch, dest); +} + +void MacroAssembler::rightShiftInt64x2(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm32(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrad), src, scratch, dest); +} + +void MacroAssembler::unsignedRightShiftInt64x2(Imm32 count, FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + SplatImm32(*this, count, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrd), src, scratch, dest); +} + +void MacroAssembler::bitwiseAndSimd128(FloatRegister rhs, + FloatRegister lhsDest) { + as_vand(lhsDest, lhsDest, rhs); +} + +void MacroAssembler::bitwiseAndSimd128(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + as_vand(dest, lhs, rhs); +} + +void MacroAssembler::bitwiseOrSimd128(FloatRegister rhs, + FloatRegister lhsDest) { + as_vor(lhsDest, lhsDest, rhs); +} + +void MacroAssembler::bitwiseOrSimd128(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + as_vor(dest, lhs, rhs); +} + +void MacroAssembler::bitwiseXorSimd128(FloatRegister rhs, + FloatRegister lhsDest) { + as_vxor(lhsDest, lhsDest, rhs); +} + +void MacroAssembler::bitwiseXorSimd128(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + as_vxor(dest, lhs, rhs); +} + +void MacroAssembler::bitwiseNotSimd128(FloatRegister src, FloatRegister dest) { + as_vnor(dest, src, src); +} + +void MacroAssembler::bitwiseNotAndSimd128(FloatRegister rhs, + FloatRegister lhsDest) { + // notand(lhs, rhs) = ~lhs & rhs = vandc(rhs, lhs) + as_vandc(lhsDest, rhs, lhsDest); +} + +void MacroAssembler::anyTrueSimd128(FloatRegister src, Register dest) { + // vcmpequw. against zero sets CR6: LT = every word of src was zero, + // EQ = no word was zero. any-true = !all-zero = !CR6.LT. + ScratchSimd128Scope scratch(*this); + uint8_t s = scratch.encoding() & 31; + ZeroSimd128(*this, scratch); + as_vcmpequw_rc(s, src.encoding() & 31, s); + as_mfocrf(dest, cr6); + // CR6.LT sits at BE bit 24; rotating left 25 brings it to bit 31. + as_rlwinm(dest, dest, 25, 31, 31); + as_xori(dest, dest, 1); +} + +// vcmpequX. against zero sets CR6: LT = all input lanes were zero, +// EQ = no input lane was zero. The latter is exactly "all-true". +// mfocrf places CR6 at bits 24-27 (LT = 24, EQ = 26); rotating left 27 +// brings CR6.EQ to bit 31. +template +static void EmitAllTrueInt(MacroAssembler& masm, FloatRegister src, + Register dest, VmxCmpRcFn vmxCmpRc) { + ScratchSimd128Scope scratch(masm); + ZeroSimd128(masm, scratch); + uint8_t s = scratch.encoding() & 31; + vmxCmpRc(static_cast(masm), s, src.encoding() & 31, s); + masm.as_mfocrf(dest, cr6); + masm.as_rlwinm(dest, dest, 27, 31, 31); +} + +void MacroAssembler::allTrueInt8x16(FloatRegister src, Register dest) { + EmitAllTrueInt(*this, src, dest, + [](Assembler& a, uint8_t t, uint8_t r, uint8_t b) { + a.as_vcmpequb_rc(t, r, b); + }); +} + +void MacroAssembler::allTrueInt16x8(FloatRegister src, Register dest) { + EmitAllTrueInt(*this, src, dest, + [](Assembler& a, uint8_t t, uint8_t r, uint8_t b) { + a.as_vcmpequh_rc(t, r, b); + }); +} + +void MacroAssembler::allTrueInt32x4(FloatRegister src, Register dest) { + EmitAllTrueInt(*this, src, dest, + [](Assembler& a, uint8_t t, uint8_t r, uint8_t b) { + a.as_vcmpequw_rc(t, r, b); + }); +} + +void MacroAssembler::allTrueInt64x2(FloatRegister src, Register dest) { + EmitAllTrueInt(*this, src, dest, + [](Assembler& a, uint8_t t, uint8_t r, uint8_t b) { + a.as_vcmpequd_rc(t, r, b); + }); +} + +void MacroAssembler::compareInt8x16(Assembler::Condition cond, + FloatRegister rhs, FloatRegister lhsDest) { + compareInt8x16(cond, lhsDest, rhs, lhsDest); +} + +void MacroAssembler::compareInt8x16(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxCompare(*this, cond, lhs, rhs, dest, VMX_BINARY_WRAPPER(vcmpequb), + VMX_BINARY_WRAPPER(vcmpgtsb), VMX_BINARY_WRAPPER(vcmpgtub)); +} + +void MacroAssembler::compareInt16x8(Assembler::Condition cond, + FloatRegister rhs, FloatRegister lhsDest) { + compareInt16x8(cond, lhsDest, rhs, lhsDest); +} + +void MacroAssembler::compareInt16x8(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxCompare(*this, cond, lhs, rhs, dest, VMX_BINARY_WRAPPER(vcmpequh), + VMX_BINARY_WRAPPER(vcmpgtsh), VMX_BINARY_WRAPPER(vcmpgtuh)); +} + +void MacroAssembler::compareInt32x4(Assembler::Condition cond, + FloatRegister rhs, FloatRegister lhsDest) { + compareInt32x4(cond, lhsDest, rhs, lhsDest); +} + +void MacroAssembler::compareInt32x4(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxCompare(*this, cond, lhs, rhs, dest, VMX_BINARY_WRAPPER(vcmpequw), + VMX_BINARY_WRAPPER(vcmpgtsw), VMX_BINARY_WRAPPER(vcmpgtuw)); +} + +void MacroAssembler::compareFloat32x4(Assembler::Condition cond, + FloatRegister rhs, + FloatRegister lhsDest) { + compareFloat32x4(cond, lhsDest, rhs, lhsDest); +} + +void MacroAssembler::compareFloat32x4(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + switch (cond) { + case Assembler::Equal: + as_vcmpeqfp(dest, lhs, rhs); + break; + case Assembler::NotEqual: + as_vcmpeqfp(dest, lhs, rhs); + bitwiseNotSimd128(dest, dest); + break; + case Assembler::GreaterThan: + as_vcmpgtfp(dest, lhs, rhs); + break; + case Assembler::GreaterThanOrEqual: + as_vcmpgefp(dest, lhs, rhs); + break; + case Assembler::LessThan: + as_vcmpgtfp(dest, rhs, lhs); + break; + case Assembler::LessThanOrEqual: + as_vcmpgefp(dest, rhs, lhs); + break; + default: + MOZ_CRASH("Unexpected SIMD float condition"); + } +} + +// =============================================================== +// Scalar-lane fallbacks +// +// VMX has no double-precision vector arithmetic, no vector divide and no +// square root at any width, so those operations run lane by lane on the FPRs +// over a stack frame: +// 0..15 lhs image | 16..31 rhs image | 32..47 result | 48..63 saved FPRs. +// Only f0 is non-allocatable, so the two working FPRs are borrowed from the +// allocatable file and restored before the frame is released; f0 stays free +// for any helper called from inside the loop. + +static constexpr int32_t SimdScalarFrame = 64; +static constexpr int32_t kScalarLhs = 0; +static constexpr int32_t kScalarRhs = 16; +static constexpr int32_t kScalarRes = 32; +static constexpr int32_t kScalarSave0 = 48; +static constexpr int32_t kScalarSave1 = 56; + +static constexpr FloatRegister ScalarLaneReg0{FloatRegisters::f1, + FloatRegisters::Double}; +static constexpr FloatRegister ScalarLaneReg1{FloatRegisters::f2, + FloatRegisters::Double}; + +static void ScalarLaneLoad(MacroAssembler& masm, FloatRegister r, int32_t off, + unsigned width) { + if (width == 8) { + masm.as_lfd(r, StackPointer, off); + } else { + masm.as_lfs(r, StackPointer, off); + } +} + +static void ScalarLaneStore(MacroAssembler& masm, FloatRegister r, int32_t off, + unsigned width) { + if (width == 8) { + masm.as_stfd(r, StackPointer, off); + } else { + masm.as_stfs(r, StackPointer, off); + } +} + +// `op(masm, a, b)` leaves its result in `a`. +template +static void EmitScalarLaneBinary(MacroAssembler& masm, FloatRegister lhs, + FloatRegister rhs, FloatRegister dest, + unsigned width, Fn op) { + FloatRegister a = ScalarLaneReg0; + FloatRegister b = ScalarLaneReg1; + masm.reserveStack(SimdScalarFrame); + SimdSpill(masm, lhs, kScalarLhs); + SimdSpill(masm, rhs, kScalarRhs); + masm.as_stfd(a, StackPointer, kScalarSave0); + masm.as_stfd(b, StackPointer, kScalarSave1); + for (int32_t i = 0; i < 16; i += int32_t(width)) { + ScalarLaneLoad(masm, a, kScalarLhs + i, width); + ScalarLaneLoad(masm, b, kScalarRhs + i, width); + op(masm, a, b); + ScalarLaneStore(masm, a, kScalarRes + i, width); + } + masm.as_lfd(a, StackPointer, kScalarSave0); + masm.as_lfd(b, StackPointer, kScalarSave1); + SimdFill(masm, dest, kScalarRes); + masm.freeStack(SimdScalarFrame); +} + +// `op(masm, a)` leaves its result in `a`. +template +static void EmitScalarLaneUnary(MacroAssembler& masm, FloatRegister src, + FloatRegister dest, unsigned width, Fn op) { + FloatRegister a = ScalarLaneReg0; + masm.reserveStack(SimdScalarFrame); + SimdSpill(masm, src, kScalarLhs); + masm.as_stfd(a, StackPointer, kScalarSave0); + for (int32_t i = 0; i < 16; i += int32_t(width)) { + ScalarLaneLoad(masm, a, kScalarLhs + i, width); + op(masm, a); + ScalarLaneStore(masm, a, kScalarRes + i, width); + } + masm.as_lfd(a, StackPointer, kScalarSave0); + SimdFill(masm, dest, kScalarRes); + masm.freeStack(SimdScalarFrame); +} + +// Per-lane compare producing the usual all-ones/all-zeros lane mask. +static void EmitScalarLaneCompare(MacroAssembler& masm, + Assembler::DoubleCondition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, unsigned width) { + FloatRegister a = ScalarLaneReg0; + FloatRegister b = ScalarLaneReg1; + UseScratchRegisterScope temps(masm); + Register bits = temps.Acquire(); + masm.reserveStack(SimdScalarFrame); + SimdSpill(masm, lhs, kScalarLhs); + SimdSpill(masm, rhs, kScalarRhs); + masm.as_stfd(a, StackPointer, kScalarSave0); + masm.as_stfd(b, StackPointer, kScalarSave1); + for (int32_t i = 0; i < 16; i += int32_t(width)) { + Label setTrue, done; + ScalarLaneLoad(masm, a, kScalarLhs + i, width); + ScalarLaneLoad(masm, b, kScalarRhs + i, width); + masm.as_fcmpu(a, b); + masm.ma_b(cond, &setTrue); + masm.xs_li(bits, 0); + masm.jump(&done); + masm.bind(&setTrue); + masm.xs_li(bits, -1); + masm.bind(&done); + for (int32_t j = 0; j < int32_t(width); j += 4) { + masm.as_stw(bits, StackPointer, kScalarRes + i + j); + } + } + masm.as_lfd(a, StackPointer, kScalarSave0); + masm.as_lfd(b, StackPointer, kScalarSave1); + SimdFill(masm, dest, kScalarRes); + masm.freeStack(SimdScalarFrame); +} + +// f32x4 sign-bit mask (0x80000000 in every word), built without a pool entry: +// vspltisw gives all-ones, and vslw takes its per-word count from the low 5 +// bits of the same register (31), so one shift lands the sign bit. +static void SignMaskFloat32x4(MacroAssembler& masm, FloatRegister dest) { + uint8_t d = dest.encoding() & 31; + masm.as_vspltisw(d, -1); + masm.as_vslw(d, d, d); +} + +void MacroAssembler::compareFloat64x2(Assembler::Condition cond, + FloatRegister rhs, + FloatRegister lhsDest) { + compareFloat64x2(cond, lhsDest, rhs, lhsDest); +} + +void MacroAssembler::compareFloat64x2(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + Assembler::DoubleCondition dc; + switch (cond) { + case Assembler::Equal: + dc = Assembler::DoubleEqual; + break; + case Assembler::NotEqual: + dc = Assembler::DoubleNotEqualOrUnordered; + break; + case Assembler::GreaterThan: + dc = Assembler::DoubleGreaterThan; + break; + case Assembler::GreaterThanOrEqual: + dc = Assembler::DoubleGreaterThanOrEqual; + break; + case Assembler::LessThan: + dc = Assembler::DoubleLessThan; + break; + case Assembler::LessThanOrEqual: + dc = Assembler::DoubleLessThanOrEqual; + break; + default: + MOZ_CRASH("Unexpected SIMD float condition"); + } + EmitScalarLaneCompare(*this, dc, lhs, rhs, dest, 8); +} + +void MacroAssembler::negFloat32x4(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope mask(*this); + SignMaskFloat32x4(*this, mask); + as_vxor(dest, src, mask); +} + +void MacroAssembler::negFloat64x2(FloatRegister src, FloatRegister dest) { + EmitScalarLaneUnary( + *this, src, dest, 8, + [](MacroAssembler& m, FloatRegister a) { m.as_fneg(a, a); }); +} + +void MacroAssembler::absFloat32x4(FloatRegister src, FloatRegister dest) { + ScratchSimd128Scope mask(*this); + SignMaskFloat32x4(*this, mask); + as_vandc(dest, src, mask); +} + +void MacroAssembler::absFloat64x2(FloatRegister src, FloatRegister dest) { + EmitScalarLaneUnary( + *this, src, dest, 8, + [](MacroAssembler& m, FloatRegister a) { m.as_fabs(a, a); }); +} + +// extAddPairwise: result[k] = widen(src[2k]) + widen(src[2k+1]). VMX has no +// pairwise add, so it is emulated with the even/odd widening multiplies +// against a splat of 1. Both multiplies need `src` and the splat live at the +// same time; with only ScratchSimd128Reg available, the intermediate goes to +// a reserved stack slot (SysV ppc32 has no red zone). +template +static void EmitExtAddPairwise(MacroAssembler& masm, FloatRegister src, + FloatRegister dest, SplatFn splat, + MulEvenFn mulEven, MulOddFn mulOdd, AddFn add) { + ScratchSimd128Scope scratch(masm); + Assembler& a = static_cast(masm); + uint8_t s = scratch.encoding() & 31; + uint8_t srcEnc = src.encoding() & 31; + uint8_t destEnc = dest.encoding() & 31; + masm.reserveStack(2 * SimdSlotSize); + SimdSpill(masm, src, 0); + splat(a, s); + mulEven(a, destEnc, srcEnc, s); + SimdSpill(masm, dest, SimdSlotSize); + SimdFill(masm, scratch, 0); + splat(a, destEnc); + mulOdd(a, destEnc, s, destEnc); + SimdFill(masm, scratch, SimdSlotSize); + add(a, destEnc, destEnc, s); + masm.freeStack(2 * SimdSlotSize); +} + +void MacroAssembler::extAddPairwiseInt8x16(FloatRegister src, + FloatRegister dest) { + EmitExtAddPairwise( + *this, src, dest, [](Assembler& a, uint8_t t) { a.as_vspltisb(t, 1); }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulesb(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulosb(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vadduhm(t, x, y); + }); +} + +void MacroAssembler::unsignedExtAddPairwiseInt8x16(FloatRegister src, + FloatRegister dest) { + EmitExtAddPairwise( + *this, src, dest, [](Assembler& a, uint8_t t) { a.as_vspltisb(t, 1); }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmuleub(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmuloub(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vadduhm(t, x, y); + }); +} + +// vmsumshm/vmsumuhm collapse the i16x8 -> i32x4 pairwise add into a single +// multiply-sum with VRB = splat(1) and VRC = 0. That needs three live +// vectors, which only works when dest and src are distinct. +void MacroAssembler::extAddPairwiseInt16x8(FloatRegister src, + FloatRegister dest) { + if (dest != src) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vspltish(dest.encoding() & 31, 1); + as_vmsumshm(dest.encoding() & 31, src.encoding() & 31, dest.encoding() & 31, + scratch.encoding() & 31); + return; + } + EmitExtAddPairwise( + *this, src, dest, [](Assembler& a, uint8_t t) { a.as_vspltish(t, 1); }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulesh(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulosh(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vadduwm(t, x, y); + }); +} + +void MacroAssembler::unsignedExtAddPairwiseInt16x8(FloatRegister src, + FloatRegister dest) { + if (dest != src) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vspltish(dest.encoding() & 31, 1); + as_vmsumuhm(dest.encoding() & 31, src.encoding() & 31, dest.encoding() & 31, + scratch.encoding() & 31); + return; + } + EmitExtAddPairwise( + *this, src, dest, [](Assembler& a, uint8_t t) { a.as_vspltish(t, 1); }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmuleuh(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vmulouh(t, x, y); + }, + [](Assembler& a, uint8_t t, uint8_t x, uint8_t y) { + a.as_vadduwm(t, x, y); + }); +} + +static double SqrtD(double x) { + AutoUnsafeCallWithABI unsafe; + return std::sqrt(x); +} + +void MacroAssembler::sqrtDouble(FloatRegister src, FloatRegister dest) { + // The 603 implements neither fsqrt nor fsqrts, so this is a libm call. + // Callers treat it as a plain instruction, so save the volatile set around + // it; callWithABIPre/Post take care of LR. + LiveRegisterSet save(GeneralRegisterSet(Registers::VolatileMask), + FloatRegisterSet(FloatRegisters::VolatileMask)); + PushRegsInMask(save); + + setupUnalignedABICall(r3); + passABIArg(src, ABIType::Float64); + // SqrtD is file-local, so it cannot go in ABIFUNCTION_LIST; use the dynamic + // form, as the other libm callouts in MacroAssembler.cpp do. + callWithABI(DynamicFunction(SqrtD), ABIType::Float64); + moveDouble(ReturnDoubleReg, dest); + + // dest commonly aliases src, so it must be excluded from the restore or the + // result is overwritten with the argument. + LiveRegisterSet ignore; + ignore.add(dest); + PopRegsInMaskIgnore(save, ignore); +} + +void MacroAssembler::sqrtFloat32(FloatRegister src, FloatRegister dest) { + // A double square root rounded once to single is the correctly rounded + // single-precision result: 53 mantissa bits exceed the 2*24+2 needed. + sqrtDouble(src.asDouble(), dest.asDouble()); + as_frsp(dest, dest); +} + +void MacroAssembler::sqrtFloat32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("NYI: sqrtFloat32x4 needs an ABI call to sqrtf (no fsqrt)"); +} + +void MacroAssembler::sqrtFloat64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("NYI: sqrtFloat64x2 needs an ABI call to sqrt (no fsqrt)"); +} + +void MacroAssembler::convertInt32x4ToFloat32x4(FloatRegister src, + FloatRegister dest) { + as_vcfsx(dest, src, 0); +} + +void MacroAssembler::unsignedConvertInt32x4ToFloat32x4(FloatRegister src, + FloatRegister dest) { + as_vcfux(dest, src, 0); +} + +// i32x4 (low 2 lanes) -> f64x2. Output lane L is at slot offset 8 - 8L, input +// lane L at 12 - 4L. +void MacroAssembler::convertInt32x4ToFloat64x2(FloatRegister src, + FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register word = temps.Acquire(); + FloatRegister a = ScalarLaneReg0; + reserveStack(SimdScalarFrame); + SimdSpill(*this, src, kScalarLhs); + as_stfd(a, StackPointer, kScalarSave0); + for (unsigned lane = 0; lane < 2; lane++) { + as_lwz(word, StackPointer, kScalarLhs + SimdLaneOffset(lane, 4)); + convertInt32ToDouble(word, a); + as_stfd(a, StackPointer, kScalarRes + SimdLaneOffset(lane, 8)); + } + as_lfd(a, StackPointer, kScalarSave0); + SimdFill(*this, dest, kScalarRes); + freeStack(SimdScalarFrame); +} + +void MacroAssembler::unsignedConvertInt32x4ToFloat64x2(FloatRegister src, + FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register word = temps.Acquire(); + FloatRegister a = ScalarLaneReg0; + reserveStack(SimdScalarFrame); + SimdSpill(*this, src, kScalarLhs); + as_stfd(a, StackPointer, kScalarSave0); + for (unsigned lane = 0; lane < 2; lane++) { + as_lwz(word, StackPointer, kScalarLhs + SimdLaneOffset(lane, 4)); + convertUInt32ToDouble(word, a); + as_stfd(a, StackPointer, kScalarRes + SimdLaneOffset(lane, 8)); + } + as_lfd(a, StackPointer, kScalarSave0); + SimdFill(*this, dest, kScalarRes); + freeStack(SimdScalarFrame); +} + +void MacroAssembler::truncSatFloat32x4ToInt32x4(FloatRegister src, + FloatRegister dest) { + // vctsxs saturates and, per the AltiVec definition, yields 0 for a NaN + // source, which is exactly the wasm contract. + as_vctsxs(dest, src, 0); +} + +void MacroAssembler::unsignedTruncSatFloat32x4ToInt32x4(FloatRegister src, + FloatRegister dest) { + as_vctuxs(dest, src, 0); +} + +// f64x2 -> i32x4 (wasm `i32x4.trunc_sat_f64x2_s_zero`). fctiwz already +// saturates to INT32_MIN/INT32_MAX; only the NaN case needs fixing up, and +// there is no stfiwx here so the integer is read back out of the low word of +// a stored double. +void MacroAssembler::truncSatFloat64x2ToInt32x4(FloatRegister src, + FloatRegister dest, + FloatRegister temp) { + UseScratchRegisterScope temps(asMasm()); + Register word = temps.Acquire(); + FloatRegister a = ScalarLaneReg0; + reserveStack(SimdScalarFrame); + SimdSpill(*this, src, kScalarLhs); + as_stfd(a, StackPointer, kScalarSave0); + xs_li(word, 0); + as_stw(word, StackPointer, kScalarRes + 0); + as_stw(word, StackPointer, kScalarRes + 4); + for (unsigned lane = 0; lane < 2; lane++) { + Label isNaN, done; + as_lfd(a, StackPointer, kScalarLhs + SimdLaneOffset(lane, 8)); + as_fcmpu(a, a); + ma_b(Assembler::DoubleUnordered, &isNaN); + as_fctiwz(a, a); + as_stfd(a, StackPointer, kScalarRhs); + as_lwz(word, StackPointer, kScalarRhs + 4); + jump(&done); + bind(&isNaN); + xs_li(word, 0); + bind(&done); + as_stw(word, StackPointer, kScalarRes + SimdLaneOffset(lane, 4)); + } + as_lfd(a, StackPointer, kScalarSave0); + SimdFill(*this, dest, kScalarRes); + freeStack(SimdScalarFrame); +} + +// Unsigned counterpart. There is no unsigned convert-to-integer instruction, +// so the range is split at 2^31 and the high half is biased back. +void MacroAssembler::unsignedTruncSatFloat64x2ToInt32x4(FloatRegister src, + FloatRegister dest, + FloatRegister temp) { + UseScratchRegisterScope temps(asMasm()); + Register word = temps.Acquire(); + FloatRegister a = ScalarLaneReg0; + FloatRegister b = ScalarLaneReg1; + reserveStack(SimdScalarFrame); + SimdSpill(*this, src, kScalarLhs); + as_stfd(a, StackPointer, kScalarSave0); + as_stfd(b, StackPointer, kScalarSave1); + xs_li(word, 0); + as_stw(word, StackPointer, kScalarRes + 0); + as_stw(word, StackPointer, kScalarRes + 4); + for (unsigned lane = 0; lane < 2; lane++) { + Label zero, big, high, done; + as_lfd(a, StackPointer, kScalarLhs + SimdLaneOffset(lane, 8)); + loadConstantDouble(0.0, b); + as_fcmpu(a, b); + ma_b(Assembler::DoubleLessThanOrEqualOrUnordered, &zero); + loadConstantDouble(4294967295.0, b); + as_fcmpu(a, b); + ma_b(Assembler::DoubleGreaterThanOrEqual, &big); + loadConstantDouble(2147483648.0, b); + as_fcmpu(a, b); + ma_b(Assembler::DoubleGreaterThanOrEqual, &high); + as_fctiwz(a, a); + as_stfd(a, StackPointer, kScalarRhs); + as_lwz(word, StackPointer, kScalarRhs + 4); + jump(&done); + bind(&high); + as_fsub(a, a, b); + as_fctiwz(a, a); + as_stfd(a, StackPointer, kScalarRhs); + as_lwz(word, StackPointer, kScalarRhs + 4); + as_xoris(word, word, 0x8000); + jump(&done); + bind(&big); + xs_li(word, -1); + jump(&done); + bind(&zero); + xs_li(word, 0); + bind(&done); + as_stw(word, StackPointer, kScalarRes + SimdLaneOffset(lane, 4)); + } + as_lfd(a, StackPointer, kScalarSave0); + as_lfd(b, StackPointer, kScalarSave1); + SimdFill(*this, dest, kScalarRes); + freeStack(SimdScalarFrame); +} + +void MacroAssembler::truncFloat32x4ToInt32x4Relaxed(FloatRegister src, + FloatRegister dest) { + truncSatFloat32x4ToInt32x4(src, dest); +} + +void MacroAssembler::unsignedTruncFloat32x4ToInt32x4Relaxed( + FloatRegister src, FloatRegister dest) { + unsignedTruncSatFloat32x4ToInt32x4(src, dest); +} + +void MacroAssembler::truncFloat64x2ToInt32x4Relaxed(FloatRegister src, + FloatRegister dest) { + truncSatFloat64x2ToInt32x4(src, dest, ScratchSimd128Reg); +} + +void MacroAssembler::unsignedTruncFloat64x2ToInt32x4Relaxed( + FloatRegister src, FloatRegister dest) { + unsignedTruncSatFloat64x2ToInt32x4(src, dest, ScratchSimd128Reg); +} + +// f64x2 -> f32x4 (wasm `f32x4.demote_f64x2_zero`): lanes 0 and 1 are rounded +// to single, lanes 2 and 3 are zero. +void MacroAssembler::convertFloat64x2ToFloat32x4(FloatRegister src, + FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register zero = temps.Acquire(); + FloatRegister a = ScalarLaneReg0; + reserveStack(SimdScalarFrame); + SimdSpill(*this, src, kScalarLhs); + as_stfd(a, StackPointer, kScalarSave0); + xs_li(zero, 0); + as_stw(zero, StackPointer, kScalarRes + 0); + as_stw(zero, StackPointer, kScalarRes + 4); + for (unsigned lane = 0; lane < 2; lane++) { + as_lfd(a, StackPointer, kScalarLhs + SimdLaneOffset(lane, 8)); + as_frsp(a, a); + as_stfs(a, StackPointer, kScalarRes + SimdLaneOffset(lane, 4)); + } + as_lfd(a, StackPointer, kScalarSave0); + SimdFill(*this, dest, kScalarRes); + freeStack(SimdScalarFrame); +} + +// f32x4 (low 2 lanes) -> f64x2, wasm `f64x2.promote_low_f32x4`. +void MacroAssembler::convertFloat32x4ToFloat64x2(FloatRegister src, + FloatRegister dest) { + FloatRegister a = ScalarLaneReg0; + reserveStack(SimdScalarFrame); + SimdSpill(*this, src, kScalarLhs); + as_stfd(a, StackPointer, kScalarSave0); + for (unsigned lane = 0; lane < 2; lane++) { + as_lfs(a, StackPointer, kScalarLhs + SimdLaneOffset(lane, 4)); + as_stfd(a, StackPointer, kScalarRes + SimdLaneOffset(lane, 8)); + } + as_lfd(a, StackPointer, kScalarSave0); + SimdFill(*this, dest, kScalarRes); + freeStack(SimdScalarFrame); +} + +void MacroAssembler::unsignedNarrowInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vpkshus), rhs, lhs, dest); +} + +void MacroAssembler::unsignedNarrowInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vpkswus), rhs, lhs, dest); +} + +void MacroAssembler::widenLowInt8x16(FloatRegister src, FloatRegister dest) { + // In the canonical register layout the wasm-low lanes sit in the BE-low + // half, so vupkls* is the "widen low" of the pair. + EmitVmxUnary( + *this, + [](Assembler& a, uint8_t vrt, uint8_t vrb) { a.as_vupklsb(vrt, vrb); }, + src, dest); +} + +void MacroAssembler::widenHighInt8x16(FloatRegister src, FloatRegister dest) { + EmitVmxUnary( + *this, + [](Assembler& a, uint8_t vrt, uint8_t vrb) { a.as_vupkhsb(vrt, vrb); }, + src, dest); +} + +void MacroAssembler::unsignedWidenLowInt8x16(FloatRegister src, + FloatRegister dest) { + zeroExtend8x16To16x8(src, dest); +} + +void MacroAssembler::unsignedWidenHighInt8x16(FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vmrghb(dest.encoding() & 31, scratch.encoding() & 31, src.encoding() & 31); +} + +void MacroAssembler::widenLowInt16x8(FloatRegister src, FloatRegister dest) { + EmitVmxUnary( + *this, + [](Assembler& a, uint8_t vrt, uint8_t vrb) { a.as_vupklsh(vrt, vrb); }, + src, dest); +} + +void MacroAssembler::widenHighInt16x8(FloatRegister src, FloatRegister dest) { + EmitVmxUnary( + *this, + [](Assembler& a, uint8_t vrt, uint8_t vrb) { a.as_vupkhsh(vrt, vrb); }, + src, dest); +} + +void MacroAssembler::unsignedWidenLowInt16x8(FloatRegister src, + FloatRegister dest) { + zeroExtend16x8To32x4(src, dest); +} + +void MacroAssembler::unsignedWidenHighInt16x8(FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vmrghh(dest.encoding() & 31, scratch.encoding() & 31, src.encoding() & 31); +} + +void MacroAssembler::widenLowInt32x4(FloatRegister src, FloatRegister dest) { + EmitVmxUnary( + *this, + [](Assembler& a, uint8_t vrt, uint8_t vrb) { a.as_vupklsw(vrt, vrb); }, + src, dest); +} + +void MacroAssembler::unsignedWidenLowInt32x4(FloatRegister src, + FloatRegister dest) { + zeroExtend32x4To64x2(src, dest); +} + +void MacroAssembler::widenHighInt32x4(FloatRegister src, FloatRegister dest) { + EmitVmxUnary( + *this, + [](Assembler& a, uint8_t vrt, uint8_t vrb) { a.as_vupkhsw(vrt, vrb); }, + src, dest); +} + +void MacroAssembler::unsignedWidenHighInt32x4(FloatRegister src, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vmrghw(dest.encoding() & 31, scratch.encoding() & 31, src.encoding() & 31); +} + +void MacroAssembler::pseudoMinFloat32x4(FloatRegister rhsOrRhsDest, + FloatRegister lhsOrLhsDest) { + pseudoMinFloat32x4(lhsOrLhsDest, rhsOrRhsDest, lhsOrLhsDest); +} + +void MacroAssembler::pseudoMinFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + // pmin(lhs, rhs) = rhs < lhs ? rhs : lhs. + ScratchSimd128Scope scratch(*this); + as_vcmpgtfp(scratch, lhs, rhs); + as_vsel(dest, lhs, rhs, scratch); +} + +// pmin/pmax on f64x2 keep the same "select the second operand only when the +// comparison holds" shape, done lane by lane. +void MacroAssembler::pseudoMinFloat64x2(FloatRegister rhsOrRhsDest, + FloatRegister lhsOrLhsDest) { + pseudoMinFloat64x2(lhsOrLhsDest, rhsOrRhsDest, lhsOrLhsDest); +} + +void MacroAssembler::pseudoMinFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + Label done; + m.as_fcmpu(b, a); + m.ma_b(Assembler::DoubleGreaterThanOrEqualOrUnordered, + &done); + m.as_fmr(a, b); + m.bind(&done); + }); +} + +void MacroAssembler::pseudoMaxFloat32x4(FloatRegister rhsOrRhsDest, + FloatRegister lhsOrLhsDest) { + pseudoMaxFloat32x4(lhsOrLhsDest, rhsOrRhsDest, lhsOrLhsDest); +} + +void MacroAssembler::pseudoMaxFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + // pmax(lhs, rhs) = lhs < rhs ? rhs : lhs. + ScratchSimd128Scope scratch(*this); + as_vcmpgtfp(scratch, rhs, lhs); + as_vsel(dest, lhs, rhs, scratch); +} + +void MacroAssembler::pseudoMaxFloat64x2(FloatRegister rhsOrRhsDest, + FloatRegister lhsOrLhsDest) { + pseudoMaxFloat64x2(lhsOrLhsDest, rhsOrRhsDest, lhsOrLhsDest); +} + +void MacroAssembler::pseudoMaxFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + Label done; + m.as_fcmpu(b, a); + m.ma_b(Assembler::DoubleLessThanOrEqualOrUnordered, + &done); + m.as_fmr(a, b); + m.bind(&done); + }); +} + +void MacroAssembler::dotInt8x16Int7x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + // result[k] = lhs[2k]*rhs[2k] + lhs[2k+1]*rhs[2k+1] for k = 0..7. + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31; + uint8_t r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31; + uint8_t s = scratch.encoding() & 31; + as_vmulesb(s, l, r); + as_vmulosb(d, l, r); + as_vadduhm(d, s, d); +} + +void MacroAssembler::ceilFloat32x4(FloatRegister src, FloatRegister dest) { + as_vrfip(dest, src); +} + +void MacroAssembler::ceilFloat64x2(FloatRegister src, FloatRegister dest) { + EmitScalarLaneUnary(*this, src, dest, 8, + [](MacroAssembler& m, FloatRegister a) { + m.nearbyIntDouble(RoundingMode::Up, a, a); + }); +} + +void MacroAssembler::floorFloat32x4(FloatRegister src, FloatRegister dest) { + as_vrfim(dest, src); +} + +void MacroAssembler::floorFloat64x2(FloatRegister src, FloatRegister dest) { + EmitScalarLaneUnary(*this, src, dest, 8, + [](MacroAssembler& m, FloatRegister a) { + m.nearbyIntDouble(RoundingMode::Down, a, a); + }); +} + +void MacroAssembler::truncFloat32x4(FloatRegister src, FloatRegister dest) { + as_vrfiz(dest, src); +} + +void MacroAssembler::truncFloat64x2(FloatRegister src, FloatRegister dest) { + EmitScalarLaneUnary(*this, src, dest, 8, + [](MacroAssembler& m, FloatRegister a) { + m.nearbyIntDouble(RoundingMode::TowardsZero, a, a); + }); +} + +void MacroAssembler::nearestFloat32x4(FloatRegister src, FloatRegister dest) { + as_vrfin(dest, src); +} + +void MacroAssembler::nearestFloat64x2(FloatRegister src, FloatRegister dest) { + EmitScalarLaneUnary( + *this, src, dest, 8, [](MacroAssembler& m, FloatRegister a) { + m.nearbyIntDouble(RoundingMode::NearestTiesToEven, a, a); + }); +} + +// srcDest = srcDest - src1 * src2. vnmsubfp computes -(vra*vrc - vrb), which +// is exactly that. +void MacroAssembler::fnmaFloat32x4(FloatRegister src1, FloatRegister src2, + FloatRegister srcDest) { + as_vnmsubfp(srcDest, src1, src2, srcDest); +} + +// Three-operand scalar lane loop for the f64x2 fused-multiply forms; the +// accumulator doubles as the destination. +static void EmitScalarFmaFloat64x2(MacroAssembler& masm, FloatRegister src1, + FloatRegister src2, FloatRegister srcDest, + bool negate) { + FloatRegister a = ScalarLaneReg0; + FloatRegister b = ScalarLaneReg1; + masm.reserveStack(2 * SimdScalarFrame); + SimdSpill(masm, src1, kScalarLhs); + SimdSpill(masm, src2, kScalarRhs); + SimdSpill(masm, srcDest, SimdScalarFrame); + masm.as_stfd(a, StackPointer, kScalarSave0); + masm.as_stfd(b, StackPointer, kScalarSave1); + for (int32_t i = 0; i < 16; i += 8) { + masm.as_lfd(a, StackPointer, kScalarLhs + i); + masm.as_lfd(b, StackPointer, kScalarRhs + i); + masm.as_fmul(a, a, b); + masm.as_lfd(b, StackPointer, SimdScalarFrame + i); + if (negate) { + masm.as_fsub(a, b, a); + } else { + masm.as_fadd(a, a, b); + } + masm.as_stfd(a, StackPointer, kScalarRes + i); + } + masm.as_lfd(a, StackPointer, kScalarSave0); + masm.as_lfd(b, StackPointer, kScalarSave1); + SimdFill(masm, srcDest, kScalarRes); + masm.freeStack(2 * SimdScalarFrame); +} + +void MacroAssembler::fnmaFloat64x2(FloatRegister src1, FloatRegister src2, + FloatRegister srcDest) { + EmitScalarFmaFloat64x2(*this, src1, src2, srcDest, /* negate = */ true); +} + +void MacroAssembler::fmaFloat32x4(FloatRegister src1, FloatRegister src2, + FloatRegister srcDest) { + as_vmaddfp(srcDest, src1, src2, srcDest); +} + +void MacroAssembler::fmaFloat64x2(FloatRegister src1, FloatRegister src2, + FloatRegister srcDest) { + EmitScalarFmaFloat64x2(*this, src1, src2, srcDest, /* negate = */ false); +} + +void MacroAssembler::minFloat32x4Relaxed(FloatRegister src, + FloatRegister srcDest) { + as_vminfp(srcDest, srcDest, src); +} + +void MacroAssembler::minFloat32x4Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + as_vminfp(dest, lhs, rhs); +} + +void MacroAssembler::maxFloat32x4Relaxed(FloatRegister src, + FloatRegister srcDest) { + as_vmaxfp(srcDest, srcDest, src); +} + +void MacroAssembler::maxFloat32x4Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + as_vmaxfp(dest, lhs, rhs); +} + +void MacroAssembler::minFloat64x2Relaxed(FloatRegister src, + FloatRegister srcDest) { + pseudoMinFloat64x2(srcDest, src, srcDest); +} + +void MacroAssembler::minFloat64x2Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + pseudoMinFloat64x2(lhs, rhs, dest); +} + +void MacroAssembler::maxFloat64x2Relaxed(FloatRegister src, + FloatRegister srcDest) { + pseudoMaxFloat64x2(srcDest, src, srcDest); +} + +void MacroAssembler::maxFloat64x2Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + pseudoMaxFloat64x2(lhs, rhs, dest); +} + +void MacroAssembler::q15MulrInt16x8Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + q15MulrSatInt16x8(lhs, rhs, dest); +} + +void MacroAssembler::popcntInt8x16(FloatRegister src, FloatRegister dest, + FloatRegister temp) { + popcntInt8x16(src, dest); +} + +void MacroAssembler::unsignedTruncSatFloat32x4ToInt32x4(FloatRegister src, + FloatRegister dest, + FloatRegister temp) { + unsignedTruncSatFloat32x4ToInt32x4(src, dest); +} + +void MacroAssembler::dotInt8x16Int7x16ThenAdd(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest, + FloatRegister temp) { + // dest += pairwise_widen_i16_to_i32(dot_i8x16(lhs, rhs)). + ScratchSimd128Scope scratch(*this); + uint8_t l = lhs.encoding() & 31; + uint8_t r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31; + uint8_t s = scratch.encoding() & 31; + uint8_t t = temp.encoding() & 31; + + as_vmulesb(s, l, r); + as_vmulosb(t, l, r); + as_vadduhm(t, s, t); + as_vspltish(s, 1); + as_vmsumshm(d, t, s, d); +} + +void MacroAssembler::permuteInt16x8(const uint16_t lanes[8], FloatRegister src, + FloatRegister dest) { + uint8_t shuffleLanes[16]; + for (unsigned i = 0; i < 8; i++) { + shuffleLanes[i * 2] = lanes[i] * 2; + shuffleLanes[i * 2 + 1] = lanes[i] * 2 + 1; + } + shuffleInt8x16(shuffleLanes, src, src, dest); +} + +void MacroAssembler::rotateRightSimd128(FloatRegister src, FloatRegister dest, + uint32_t shift) { + MOZ_ASSERT(shift < 16); + if (shift == 0) { + moveSimd128(src, dest); + return; + } + as_vsldoi(dest, src, src, 16 - shift); +} + +// i64x2 multiply, done lane by lane from 32-bit partial products: +// lo = a.lo * b.lo +// hi = mulhwu(a.lo, b.lo) + a.lo * b.hi + a.hi * b.lo +// Only r11, r12 and r0 are needed, which is all this target has spare. +void MacroAssembler::mulInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + UseScratchRegisterScope temps(asMasm()); + Register x = temps.Acquire(); + Register y = temps.Acquire(); + reserveStack(SimdScalarFrame); + SimdSpill(*this, lhs, kScalarLhs); + SimdSpill(*this, rhs, kScalarRhs); + for (unsigned lane = 0; lane < 2; lane++) { + int32_t off = SimdLaneOffset(lane, 8); + int32_t hi = off; // big endian: high word at the lower address + int32_t lo = off + 4; + as_lwz(x, StackPointer, kScalarLhs + lo); + as_lwz(y, StackPointer, kScalarRhs + lo); + as_mullw(r0, x, y); + as_stw(r0, StackPointer, kScalarRes + lo); + as_mulhwu(r0, x, y); + as_lwz(y, StackPointer, kScalarRhs + hi); + as_mullw(y, x, y); + as_add(r0, r0, y); + as_lwz(x, StackPointer, kScalarLhs + hi); + as_lwz(y, StackPointer, kScalarRhs + lo); + as_mullw(y, x, y); + as_add(r0, r0, y); + as_stw(r0, StackPointer, kScalarRes + hi); + } + SimdFill(*this, dest, kScalarRes); + freeStack(SimdScalarFrame); +} + +void MacroAssembler::bitwiseAndNotSimd128(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + // andnot(lhs, rhs) = lhs & ~rhs + as_vandc(dest, lhs, rhs); +} + +void MacroAssembler::bitwiseSelectSimd128(FloatRegister onTrue, + FloatRegister onFalse, + FloatRegister maskDest) { + // result = (onTrue & mask) | (onFalse & ~mask); vsel selects VRB where the + // control bit is set. + as_vsel(maskDest, onFalse, onTrue, maskDest); +} + +// No vpopcntb on this target: use the classic nibble lookup through vperm. +// vperm indexes the concatenation VRA||VRB by BE byte number, so a table +// whose BE byte i holds popcount(i) turns a nibble vector into a count +// vector. loadConstantSimd128 byte-reverses, hence the reversed literal. +void MacroAssembler::popcntInt8x16(FloatRegister src, FloatRegister dest) { + static const int8_t kNibblePopcount[16] = {4, 3, 3, 2, 3, 2, 2, 1, + 3, 2, 2, 1, 2, 1, 1, 0}; + ScratchSimd128Scope scratch(*this); + uint8_t s = scratch.encoding() & 31; + uint8_t d = dest.encoding() & 31; + // Two slots: one keeps a copy of src (dest may alias it), one the partial + // count. + reserveStack(2 * SimdSlotSize); + SimdSpill(*this, src, SimdSlotSize); + as_vspltisb(s, 4); + as_vsrb(d, src.encoding() & 31, s); + loadConstantSimd128(SimdConstant::CreateX16(kNibblePopcount), scratch); + as_vperm(d, s, s, d); + SimdSpill(*this, dest, 0); + SimdFill(*this, scratch, SimdSlotSize); + moveSimd128(scratch, dest); + as_vspltisb(s, 15); + as_vand(dest, dest, scratch); + loadConstantSimd128(SimdConstant::CreateX16(kNibblePopcount), scratch); + as_vperm(d, s, s, d); + SimdFill(*this, scratch, 0); + as_vaddubm(d, d, s); + freeStack(2 * SimdSlotSize); +} + +// bitmask: gather the sign bit of every lane into the low bits of a GPR. +// vbpermq is POWER8, so the lanes are read back out of a stack slot and the +// bits are spliced with rlwimi. `rotl32(x, k + 1 - width)` brings the lane's +// sign bit to result bit k, and the MB/ME pair keeps only that bit. +static void EmitSimdBitmask(MacroAssembler& masm, FloatRegister src, + Register dest, unsigned lanes, unsigned width) { + masm.reserveStack(SimdSlotSize); + SimdSpill(masm, src, 0); + UseScratchRegisterScope temps(masm); + Register lane = temps.Acquire(); + for (unsigned j = 0; j < lanes; j++) { + int32_t off = SimdLaneOffset(j, width); + unsigned signBit; // value-bit position of the lane's sign bit + switch (width) { + case 1: + masm.as_lbz(lane, StackPointer, off); + signBit = 7; + break; + case 2: + masm.as_lhz(lane, StackPointer, off); + signBit = 15; + break; + default: + // For i64x2 the sign bit is bit 31 of the lane's high word, which on + // big endian is the word at the lane's own offset. + masm.as_lwz(lane, StackPointer, off); + signBit = 31; + break; + } + uint8_t sh = uint8_t((j + 32 - signBit) % 32); + uint8_t mb = uint8_t(31 - j); + if (j == 0) { + masm.as_rlwinm(dest, lane, sh, mb, mb); + } else { + masm.as_rlwimi(dest, lane, sh, mb, mb); + } + } + masm.freeStack(SimdSlotSize); +} + +void MacroAssembler::bitmaskInt8x16(FloatRegister src, Register dest, + FloatRegister temp) { + EmitSimdBitmask(*this, src, dest, 16, 1); +} + +void MacroAssembler::bitmaskInt16x8(FloatRegister src, Register dest, + FloatRegister temp) { + EmitSimdBitmask(*this, src, dest, 8, 2); +} + +void MacroAssembler::bitmaskInt32x4(FloatRegister src, Register dest, + FloatRegister temp) { + EmitSimdBitmask(*this, src, dest, 4, 4); +} + +void MacroAssembler::bitmaskInt64x2(FloatRegister src, Register dest, + FloatRegister temp) { + EmitSimdBitmask(*this, src, dest, 2, 8); +} + +void MacroAssembler::compareInt64x2(Assembler::Condition cond, + FloatRegister rhs, FloatRegister lhsDest) { + compareInt64x2(cond, lhsDest, rhs, lhsDest); +} + +void MacroAssembler::compareInt64x2(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxCompare(*this, cond, lhs, rhs, dest, VMX_BINARY_WRAPPER(vcmpequd), + VMX_BINARY_WRAPPER(vcmpgtsd), VMX_BINARY_WRAPPER(vcmpgtud)); +} + +void MacroAssembler::minFloat32x4(FloatRegister rhs, FloatRegister lhsDest) { + minFloat32x4(lhsDest, rhs, lhsDest); +} + +void MacroAssembler::minFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + as_vminfp(dest, lhs, rhs); +} + +void MacroAssembler::minFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + // Wasm min with NaN propagation. The mask and the NaN-producing add must be + // computed before the min, which may clobber lhs through dest. + as_vcmpeqfp(temp1, lhs, lhs); + as_vcmpeqfp(temp2, rhs, rhs); + as_vand(temp1, temp1, temp2); + as_vaddfp(temp2, lhs, rhs); + as_vminfp(dest, lhs, rhs); + as_vsel(dest, temp2, dest, temp1); +} + +void MacroAssembler::minFloat64x2(FloatRegister rhs, FloatRegister lhsDest) { + minFloat64x2(lhsDest, rhs, lhsDest); +} + +void MacroAssembler::minFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + m.minDouble(b, a, /* handleNaN = */ false); + }); +} + +void MacroAssembler::minFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + m.minDouble(b, a, /* handleNaN = */ true); + }); +} + +void MacroAssembler::maxFloat32x4(FloatRegister rhs, FloatRegister lhsDest) { + maxFloat32x4(lhsDest, rhs, lhsDest); +} + +void MacroAssembler::maxFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + as_vmaxfp(dest, lhs, rhs); +} + +void MacroAssembler::maxFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + as_vcmpeqfp(temp1, lhs, lhs); + as_vcmpeqfp(temp2, rhs, rhs); + as_vand(temp1, temp1, temp2); + as_vaddfp(temp2, lhs, rhs); + as_vmaxfp(dest, lhs, rhs); + as_vsel(dest, temp2, dest, temp1); +} + +void MacroAssembler::maxFloat64x2(FloatRegister rhs, FloatRegister lhsDest) { + maxFloat64x2(lhsDest, rhs, lhsDest); +} + +void MacroAssembler::maxFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + m.maxDouble(b, a, /* handleNaN = */ false); + }); +} + +void MacroAssembler::maxFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + m.maxDouble(b, a, /* handleNaN = */ true); + }); +} + +void MacroAssembler::extractLaneInt64x2(uint32_t lane, FloatRegister src, + Register64 dest) { + MOZ_ASSERT(lane < 2); + int32_t off = SimdLaneOffset(lane, 8); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + // Big endian: the high word is at the lower address. + as_lwz(dest.high, StackPointer, off); + as_lwz(dest.low, StackPointer, off + 4); + freeStack(SimdSlotSize); +} + +void MacroAssembler::replaceLaneInt64x2(unsigned lane, Register64 rhs, + FloatRegister lhsDest) { + MOZ_ASSERT(lane < 2); + int32_t off = SimdLaneOffset(lane, 8); + reserveStack(SimdSlotSize); + SimdSpill(*this, lhsDest, 0); + as_stw(rhs.high, StackPointer, off); + as_stw(rhs.low, StackPointer, off + 4); + SimdFill(*this, lhsDest, 0); + freeStack(SimdSlotSize); +} + +void MacroAssembler::splatX2(Register64 src, FloatRegister dest) { + reserveStack(SimdSlotSize); + as_stw(src.high, StackPointer, 0); + as_stw(src.low, StackPointer, 4); + as_stw(src.high, StackPointer, 8); + as_stw(src.low, StackPointer, 12); + SimdFill(*this, dest, 0); + freeStack(SimdSlotSize); +} + +// SIMD 3-operand arithmetic (x86_shared-style signatures). + +void MacroAssembler::addFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + as_vaddfp(dest, lhs, rhs); +} + +void MacroAssembler::addFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + m.as_fadd(a, a, b); + }); +} + +void MacroAssembler::addInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vadduhm), lhs, rhs, dest); +} + +void MacroAssembler::addInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vaddubm), lhs, rhs, dest); +} + +// VMX has no vector divide at any width, so f32x4 division also runs lane by +// lane; the reciprocal estimate vrefp is not accurate enough for wasm. +void MacroAssembler::divFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 4, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + m.as_fdivs(a, a, b); + }); +} + +void MacroAssembler::extractLaneInt16x8(uint32_t lane, FloatRegister src, + Register dest) { + MOZ_ASSERT(lane < 8); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + as_lha(dest, StackPointer, SimdLaneOffset(lane, 2)); + freeStack(SimdSlotSize); +} + +void MacroAssembler::extractLaneInt32x4(uint32_t lane, FloatRegister src, + Register dest) { + MOZ_ASSERT(lane < 4); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + as_lwz(dest, StackPointer, SimdLaneOffset(lane, 4)); + freeStack(SimdSlotSize); +} + +void MacroAssembler::extractLaneInt8x16(uint32_t lane, FloatRegister src, + Register dest) { + MOZ_ASSERT(lane < 16); + reserveStack(SimdSlotSize); + SimdSpill(*this, src, 0); + as_lbz(dest, StackPointer, SimdLaneOffset(lane, 1)); + freeStack(SimdSlotSize); + as_extsb(dest, dest); +} + +void MacroAssembler::maxInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmaxsh), lhs, rhs, dest); +} + +void MacroAssembler::maxInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmaxsw), lhs, rhs, dest); +} + +void MacroAssembler::maxInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmaxsb), lhs, rhs, dest); +} + +void MacroAssembler::minInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vminsb), lhs, rhs, dest); +} + +void MacroAssembler::mulInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vmuluwm), lhs, rhs, dest); +} + +void MacroAssembler::narrowInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vpkshss), rhs, lhs, dest); +} + +void MacroAssembler::subInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsubuwm), lhs, rhs, dest); +} + +void MacroAssembler::swizzleInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + // Wasm i8x16.swizzle: result[i] = (rhs[i] < 16) ? lhs[rhs[i]] : 0. + // vsububs(splat(15), rhs) builds the vperm control and saturates + // out-of-range indices to 0; those lanes are then masked off with the + // "rhs > 15" mask. When dest aliases rhs the permute would destroy rhs + // before the mask could be built, so the mask goes to a stack slot first. + ScratchSimd128Scope scratch(*this); + uint8_t s = scratch.encoding() & 31; + uint8_t l = lhs.encoding() & 31; + uint8_t r = rhs.encoding() & 31; + uint8_t d = dest.encoding() & 31; + + if (dest != rhs) { + as_vspltisb(s, 15); + as_vsububs(s, s, r); + as_vperm(d, l, l, s); + as_vspltisb(s, 15); + as_vcmpgtub(s, r, s); + as_vandc(dest, dest, scratch); + return; + } + + reserveStack(SimdSlotSize); + as_vspltisb(s, 15); + as_vcmpgtub(s, r, s); + SimdSpill(*this, scratch, 0); + as_vspltisb(s, 15); + as_vsububs(s, s, r); + as_vperm(d, l, l, s); + SimdFill(*this, scratch, 0); + as_vandc(dest, dest, scratch); + freeStack(SimdSlotSize); +} + +// SIMD 3-operand arithmetic (continued). + +void MacroAssembler::addInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vadduwm), lhs, rhs, dest); +} + +void MacroAssembler::addInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vaddudm), lhs, rhs, dest); +} + +void MacroAssembler::addSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vaddshs), lhs, rhs, dest); +} + +void MacroAssembler::addSatInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vaddsbs), lhs, rhs, dest); +} + +void MacroAssembler::divFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + m.as_fdiv(a, a, b); + }); +} + +void MacroAssembler::minInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vminsh), lhs, rhs, dest); +} + +void MacroAssembler::minInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vminsw), lhs, rhs, dest); +} + +// VMX has no vector float multiply; vmaddfp with a -0.0 addend rounds once +// and keeps the signed-zero rules. +void MacroAssembler::mulFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope negZero(*this); + SignMaskFloat32x4(*this, negZero); + as_vmaddfp(dest, lhs, rhs, negZero); +} + +void MacroAssembler::mulFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + m.as_fmul(a, a, b); + }); +} + +void MacroAssembler::mulInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + EmitVmxTernary( + *this, + [](Assembler& a, uint8_t vrt, uint8_t vra, uint8_t vrb, uint8_t vrc) { + a.as_vmladduhm(vrt, vra, vrb, vrc); + }, + lhs, rhs, scratch, dest); +} + +void MacroAssembler::narrowInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vpkswss), rhs, lhs, dest); +} + +void MacroAssembler::subFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + as_vsubfp(dest, lhs, rhs); +} + +void MacroAssembler::subFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitScalarLaneBinary(*this, lhs, rhs, dest, 8, + [](MacroAssembler& m, FloatRegister a, FloatRegister b) { + m.as_fsub(a, a, b); + }); +} + +void MacroAssembler::subInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsubuhm), lhs, rhs, dest); +} + +void MacroAssembler::subInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsubudm), lhs, rhs, dest); +} + +void MacroAssembler::subInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsububm), lhs, rhs, dest); +} + +void MacroAssembler::subSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsubshs), lhs, rhs, dest); +} + +void MacroAssembler::subSatInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsubsbs), lhs, rhs, dest); +} + +void MacroAssembler::widenDotInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + // i32x4.dot_i16x8_s: vmsumshm with a zero addend is the whole operation. + ScratchSimd128Scope scratch(*this); + ZeroSimd128(*this, scratch); + as_vmsumshm(dest.encoding() & 31, lhs.encoding() & 31, rhs.encoding() & 31, + scratch.encoding() & 31); +} + +// SIMD variable-shift helpers: splat the GPR shift count across all lanes of +// a scratch VR, then issue the vector shift. vsl*/vsr*/vsra* take the count +// modulo the lane width, matching wasm. + +void MacroAssembler::leftShiftInt8x16(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX16(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vslb), lhs, scratch, dest); +} + +void MacroAssembler::rightShiftInt8x16(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX16(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrab), lhs, scratch, dest); +} + +void MacroAssembler::unsignedRightShiftInt8x16(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX16(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrb), lhs, scratch, dest); +} + +void MacroAssembler::leftShiftInt16x8(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX8(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vslh), lhs, scratch, dest); +} + +void MacroAssembler::rightShiftInt16x8(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX8(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrah), lhs, scratch, dest); +} + +void MacroAssembler::unsignedRightShiftInt16x8(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX8(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrh), lhs, scratch, dest); +} + +void MacroAssembler::leftShiftInt32x4(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX4(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vslw), lhs, scratch, dest); +} + +void MacroAssembler::leftShiftInt64x2(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX4(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsld), lhs, scratch, dest); +} + +void MacroAssembler::rightShiftInt32x4(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX4(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsraw), lhs, scratch, dest); +} + +void MacroAssembler::rightShiftInt64x2(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX4(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrad), lhs, scratch, dest); +} + +void MacroAssembler::unsignedRightShiftInt32x4(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX4(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrw), lhs, scratch, dest); +} + +void MacroAssembler::unsignedRightShiftInt64x2(FloatRegister lhs, Register rhs, + FloatRegister dest) { + ScratchSimd128Scope scratch(*this); + splatX4(rhs, scratch); + EmitVmxBinary(*this, VMX_BINARY_WRAPPER(vsrd), lhs, scratch, dest); +} + +//}}} check_macroassembler_style + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_MacroAssembler_ppc_inl_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/MacroAssembler-ppc.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/MacroAssembler-ppc.cpp --- a/js/src/jit/ppc/MacroAssembler-ppc.cpp 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/MacroAssembler-ppc.cpp 2026-07-29 11:31:52.754859409 +0200 @@ -0,0 +1,3618 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/ppc/MacroAssembler-ppc.h" + +#include + +#include "jit/Bailouts.h" +#include "jit/BaselineFrame.h" +#include "jit/FlushICache.h" +#include "jit/JitFrames.h" +#include "jit/JitRuntime.h" +#include "jit/MacroAssembler.h" +#include "jit/MoveEmitter.h" +#include "jit/ppc/SharedICRegisters-ppc.h" +#include "vm/JitActivation.h" +#include "vm/JSContext.h" +#include "wasm/WasmBuiltins.h" +#include "wasm/WasmStubs.h" + +#include "jit/MacroAssembler-inl.h" + +namespace js { +namespace jit { + +MacroAssembler& MacroAssemblerPPC::asMasm() { + return *static_cast(this); +} + +const MacroAssembler& MacroAssemblerPPC::asMasm() const { + return *static_cast(this); +} + +// =============================================================== +// GPR pair <-> FPR transfers and FP primitives. + +// FPSCR rounding-control bits, in mtfsb{0,1} bit numbering (the 0-31 view of +// the 32-bit FPSCR). RN occupies bits 30:31. +static constexpr uint8_t kFPSCR_RN_hi = 30; +static constexpr uint8_t kFPSCR_RN_lo = 31; + +// The double bit pattern 0x4330000000000000 == 2^52; adding an unsigned 32-bit +// value into its low word yields 2^52 + u exactly. +static constexpr int16_t kMagicDoubleHi = 0x4330; +static constexpr double kMagicUnsigned = 4503599627370496.0; // 2^52 +static constexpr double kMagicSigned = 4503601774854144.0; // 2^52 + 2^31 + +void MacroAssemblerPPC::moveGPRPairToDouble(Register hi, Register lo, + FloatRegister dest) { + allocStackScratch(); + as_stw(hi, StackPointer, StackScratchSlot); + as_stw(lo, StackPointer, StackScratchSlot + 4); + as_lfd(dest, StackPointer, StackScratchSlot); + freeStackScratch(); +} + +void MacroAssemblerPPC::moveDoubleToGPRPair(FloatRegister src, Register hi, + Register lo) { + allocStackScratch(); + as_stfd(src, StackPointer, StackScratchSlot); + as_lwz(hi, StackPointer, StackScratchSlot); + as_lwz(lo, StackPointer, StackScratchSlot + 4); + freeStackScratch(); +} + +void MacroAssemblerPPC::emitCopySign(FloatRegister dest, FloatRegister signSrc, + FloatRegister magSrc) { + // No fcpsgn on this target: splice the sign bit through the high words. + UseScratchRegisterScope temps(asMasm()); + Register mag = temps.Acquire(); + Register sign = temps.Acquire(); + allocStackScratch(StackScratchFrame2); + as_stfd(magSrc, StackPointer, StackScratchSlot); + as_stfd(signSrc, StackPointer, StackScratchSlot2); + as_lwz(mag, StackPointer, StackScratchSlot); + as_lwz(sign, StackPointer, StackScratchSlot2); + as_rlwinm(mag, mag, 0, 1, 31); // drop the sign bit + as_rlwinm(sign, sign, 0, 0, 0); // keep only the sign bit + as_or_(mag, mag, sign); + as_stw(mag, StackPointer, StackScratchSlot); + as_lfd(dest, StackPointer, StackScratchSlot); + freeStackScratch(StackScratchFrame2); +} + +void MacroAssemblerPPC::roundToInt32FPR(FloatRegister fpDest, FloatRegister src, + FpIntRound m) { + if (m == RoundZero) { + as_fctiwz(fpDest, src); + return; + } + if (m == RoundNearest) { + emitFctiw(fpDest, src); + return; + } + // fctiw honours the FPSCR rounding mode; set it, convert, restore + // round-to-nearest-even. + as_mtfsb1(kFPSCR_RN_hi); + if (m == RoundDown) { + as_mtfsb1(kFPSCR_RN_lo); // RN = 0b11, toward -inf + } else { + as_mtfsb0(kFPSCR_RN_lo); // RN = 0b10, toward +inf + } + emitFctiw(fpDest, src); + as_mtfsb0(kFPSCR_RN_hi); + as_mtfsb0(kFPSCR_RN_lo); +} + +// =============================================================== +// Out-of-line fake exit frame + +bool MacroAssemblerPPCCompat::buildOOLFakeExitFrame(void* fakeReturnAddr) { + asMasm().Push(FrameDescriptor(FrameType::IonJS)); + asMasm().Push(ImmPtr(fakeReturnAddr)); + asMasm().Push(FramePointer); + return true; +} + +// =============================================================== +// Integer <-> floating point conversion + +// The int-to-double bias trick: [0x43300000, biased] read as a double is +// exactly 2^52 + biased, so subtracting the matching magic constant leaves the +// original value. It needs one GPR and clobbers ScratchDoubleReg, which is why +// no caller may pass ScratchDoubleReg as |dest|. +// Subtract the conversion bias from |dest|. The constant normally lives in +// ScratchDoubleReg, but the allocator may hand us ScratchDoubleReg as |dest| +// and f0 is the only scratch FPR, so borrow a volatile one in that case. +void MacroAssemblerPPCCompat::subtractDoubleBias(FloatRegister dest, + double bias) { + if (dest != ScratchDoubleReg) { + loadConstantDouble(bias, ScratchDoubleReg); + as_fsub(dest, dest, ScratchDoubleReg); + return; + } + allocStackScratch(); + as_stfd(f1, StackPointer, StackScratchSlot); + loadConstantDouble(bias, f1); + as_fsub(dest, dest, f1); + as_lfd(f1, StackPointer, StackScratchSlot); + freeStackScratch(); +} + +void MacroAssemblerPPCCompat::convertInt32ToDouble(Register src, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register t = temps.Acquire(); + as_xoris(t, src, 0x8000); + allocStackScratch(); + as_stw(t, StackPointer, StackScratchSlot + 4); + xs_lis(t, kMagicDoubleHi); + as_stw(t, StackPointer, StackScratchSlot); + as_lfd(dest, StackPointer, StackScratchSlot); + freeStackScratch(); + subtractDoubleBias(dest, kMagicSigned); +} + +void MacroAssemblerPPCCompat::convertInt32ToDouble(const Address& src, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register value = temps.Acquire(); + load32(src, value); + temps.Release(value); + convertInt32ToDouble(value, dest); +} + +void MacroAssemblerPPCCompat::convertInt32ToDouble(const BaseIndex& src, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register value = temps.Acquire(); + load32(src, value); + temps.Release(value); + convertInt32ToDouble(value, dest); +} + +void MacroAssemblerPPCCompat::convertUInt32ToDouble(Register src, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register t = temps.Acquire(); + allocStackScratch(); + as_stw(src, StackPointer, StackScratchSlot + 4); + xs_lis(t, kMagicDoubleHi); + as_stw(t, StackPointer, StackScratchSlot); + as_lfd(dest, StackPointer, StackScratchSlot); + freeStackScratch(); + subtractDoubleBias(dest, kMagicUnsigned); +} + +void MacroAssemblerPPCCompat::convertUInt32ToFloat32(Register src, + FloatRegister dest) { + // A uint32 is exact in a double, so rounding once to single afterwards + // matches a direct uint32 -> float32 conversion. + convertUInt32ToDouble(src, dest); + as_frsp(dest, dest); +} + +void MacroAssemblerPPCCompat::convertInt32ToFloat32(Register src, + FloatRegister dest) { + convertInt32ToDouble(src, dest); + as_frsp(dest, dest); +} + +void MacroAssemblerPPCCompat::convertInt32ToFloat32(const Address& src, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register value = temps.Acquire(); + load32(src, value); + temps.Release(value); + convertInt32ToFloat32(value, dest); +} + +// Bail out when the rounded result is zero but the input was not +0.0: only +// -0.0 (and NaN, already rejected) has a non-zero high word here. +static void EmitRejectNegativeZero(MacroAssembler& masm, FloatRegister src, + Register dest, Label* fail) { + Label notZero; + masm.as_cmpwi(dest, 0); + masm.ma_b(Assembler::NotEqual, ¬Zero); + { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.allocStackScratch(); + masm.as_stfd(src, StackPointer, MacroAssemblerPPC::StackScratchSlot); + masm.as_lwz(scratch, StackPointer, MacroAssemblerPPC::StackScratchSlot); + masm.freeStackScratch(); + masm.as_cmpwi(scratch, 0); + masm.ma_b(Assembler::NotEqual, fail); + } + masm.bind(¬Zero); +} + +// Truncate toward zero into |dest|, bailing out to |fail| when fctiwz reports +// an invalid conversion (NaN, infinity, or out of int32 range) or an inexact +// one (the input had a fractional part). This replaces the round-trip compare +// the 64-bit backend used, which would need a second scratch FPR. +static void EmitExactFPToInt32(MacroAssembler& masm, FloatRegister src, + Register dest, Label* fail) { + masm.as_mtfsb0(23); // VXCVI + masm.as_mtfsb0(6); // XX + masm.as_fctiwz(ScratchDoubleReg, src); + // FPSCR field 5 is {reserved, VXSOFT, VXSQRT, VXCVI}: VXCVI lands in SO. + masm.as_mcrfs(cr0, 5); + masm.ma_b(Assembler::SOBit, fail); + // FPSCR field 1 is {UX, ZX, XX, VXSNAN}: XX lands in EQ. + masm.as_mcrfs(cr0, 1); + masm.ma_b(Assembler::Equal, fail); + masm.allocStackScratch(); + masm.as_stfd(ScratchDoubleReg, StackPointer, + MacroAssemblerPPC::StackScratchSlot); + masm.as_lwz(dest, StackPointer, MacroAssemblerPPC::StackScratchSlot + 4); + masm.freeStackScratch(); +} + +void MacroAssemblerPPCCompat::convertDoubleToInt32(FloatRegister src, + Register dest, Label* fail, + bool negativeZeroCheck) { + EmitExactFPToInt32(asMasm(), src, dest, fail); + if (negativeZeroCheck) { + EmitRejectNegativeZero(asMasm(), src, dest, fail); + } +} + +void MacroAssemblerPPCCompat::convertFloat32ToInt32(FloatRegister src, + Register dest, Label* fail, + bool negativeZeroCheck) { + EmitExactFPToInt32(asMasm(), src, dest, fail); + if (negativeZeroCheck) { + EmitRejectNegativeZero(asMasm(), src, dest, fail); + } +} + +FaultingCodeOffset MacroAssemblerPPCCompat::loadFloat16(const Address& addr, + FloatRegister dest, + Register temp) { + FaultingCodeOffset fco = load16ZeroExtend(addr, temp); + asMasm().moveGPRToFloat16(temp, dest); + return fco; +} + +FaultingCodeOffset MacroAssemblerPPCCompat::loadFloat16(const BaseIndex& src, + FloatRegister dest, + Register temp) { + FaultingCodeOffset fco = load16ZeroExtend(src, temp); + asMasm().moveGPRToFloat16(temp, dest); + return fco; +} + +// =============================================================== +// Condition materialisation + +void MacroAssemblerPPCCompat::ma_cmp_set_dbl(Register dest, + DoubleCondition cond) { + bool hasUnorderedFlag = uint32_t(cond) & DoubleConditionUnordered; + uint8_t bit = crBit(cr0, cond); + UseScratchRegisterScope temps(*this); + Register cr = temps.Acquire(); + + as_mfocrf(cr, cr0); + as_rlwinm(dest, cr, bit + 1, 31, 31); + if ((uint32_t(cond) & BranchOptionMask) != BranchOnSet) { + as_xori(dest, dest, 1); + } + if (hasUnorderedFlag) { + // NaN makes the condition true: OR in the SO bit (CR0 bit 3). + as_rlwinm(cr, cr, 4, 31, 31); + as_or_(dest, dest, cr); + } else if ((uint32_t(cond) & BranchOptionMask) != BranchOnSet && + cond != DoubleOrdered) { + // An ordered comparison that negates a CR bit reads as true for NaN, + // because NaN leaves LT/GT/EQ all clear. Force the result to 0 then. + as_rlwinm(cr, cr, 4, 31, 31); + as_xori(cr, cr, 1); + as_and_(dest, dest, cr); + } +} + +// =============================================================== +// Tag tests against memory + +#define DEFINE_TEST_ADDRESS(Name) \ + Assembler::Condition MacroAssemblerPPCCompat::test##Name( \ + Condition cond, const Address& address) { \ + UseScratchRegisterScope temps(*this); \ + Register scratch = temps.Acquire(); \ + return test##Name(cond, extractTag(address, scratch)); \ + } \ + Assembler::Condition MacroAssemblerPPCCompat::test##Name( \ + Condition cond, const BaseIndex& src) { \ + UseScratchRegisterScope temps(*this); \ + Register scratch = temps.Acquire(); \ + return test##Name(cond, extractTag(src, scratch)); \ + } + +DEFINE_TEST_ADDRESS(Int32) +DEFINE_TEST_ADDRESS(Boolean) +DEFINE_TEST_ADDRESS(Double) +DEFINE_TEST_ADDRESS(Null) +DEFINE_TEST_ADDRESS(Undefined) +DEFINE_TEST_ADDRESS(String) +DEFINE_TEST_ADDRESS(Symbol) +DEFINE_TEST_ADDRESS(BigInt) +DEFINE_TEST_ADDRESS(Object) +DEFINE_TEST_ADDRESS(Magic) +DEFINE_TEST_ADDRESS(GCThing) + +#undef DEFINE_TEST_ADDRESS + +Assembler::Condition MacroAssemblerPPCCompat::testNumber( + Condition cond, const Address& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + return testNumber(cond, extractTag(address, scratch)); +} + +Assembler::DoubleCondition MacroAssemblerPPCCompat::testDoubleTruthy( + bool truthy, FloatRegister reg) { + ScratchDoubleScope fpscratch(asMasm()); + asMasm().zeroDouble(fpscratch); + as_fcmpu(reg, fpscratch); + return truthy ? Assembler::DoubleNotEqual : Assembler::DoubleEqualOrUnordered; +} + +Assembler::Condition MacroAssemblerPPCCompat::testStringTruthy( + bool truthy, const ValueOperand& value) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + load32(Address(value.payloadReg(), JSString::offsetOfLength()), scratch); + as_cmpwi(scratch, 0); + return truthy ? Assembler::NotEqual : Assembler::Equal; +} + +Assembler::Condition MacroAssemblerPPCCompat::testBigIntTruthy( + bool truthy, const ValueOperand& value) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + load32(Address(value.payloadReg(), BigInt::offsetOfDigitLength()), scratch); + as_cmpwi(scratch, 0); + return truthy ? Assembler::NotEqual : Assembler::Equal; +} + +// =============================================================== +// Boxing + +void MacroAssemblerPPCCompat::boxNonDouble(JSValueType type, Register src, + const ValueOperand& dest) { + MOZ_ASSERT(type != JSVAL_TYPE_UNDEFINED && type != JSVAL_TYPE_NULL); + MOZ_ASSERT(dest.typeReg() != dest.payloadReg()); + +#ifdef DEBUG + if (type == JSVAL_TYPE_BOOLEAN) { + Label upperBitsZeroed; + as_cmplwi(src, 1); + ma_b(Assembler::BelowOrEqual, &upperBitsZeroed); + breakpoint(); + bind(&upperBitsZeroed); + } +#endif + + if (src != dest.payloadReg()) { + xs_mr(dest.payloadReg(), src); + } + move32(ImmType(type), dest.typeReg()); +} + +void MacroAssemblerPPCCompat::boxNonDouble(Register type, Register src, + const ValueOperand& dest) { + MOZ_ASSERT(type != dest.payloadReg() && src != dest.typeReg()); + +#ifdef DEBUG + Label ok, isNullOrUndefined, isBoolean; + Condition c; + c = ma_cmp(type, Imm32(JSVAL_TYPE_NULL), Equal); + ma_b(c, &isNullOrUndefined); + c = ma_cmp(type, Imm32(JSVAL_TYPE_UNDEFINED), Equal); + ma_b(c, &isNullOrUndefined); + c = ma_cmp(type, Imm32(JSVAL_TYPE_BOOLEAN), Equal); + ma_b(c, &isBoolean); + c = ma_cmp(type, Imm32(JSVAL_TYPE_INT32), Equal); + ma_b(c, &ok); + c = ma_cmp(type, Imm32(JSVAL_TYPE_MAGIC), Equal); + ma_b(c, &ok); + c = ma_cmp(type, Imm32(JSVAL_TYPE_STRING), Equal); + ma_b(c, &ok); + c = ma_cmp(type, Imm32(JSVAL_TYPE_SYMBOL), Equal); + ma_b(c, &ok); + c = ma_cmp(type, Imm32(JSVAL_TYPE_PRIVATE_GCTHING), Equal); + ma_b(c, &ok); + c = ma_cmp(type, Imm32(JSVAL_TYPE_BIGINT), Equal); + ma_b(c, &ok); + c = ma_cmp(type, Imm32(JSVAL_TYPE_OBJECT), Equal); + ma_b(c, &ok); + breakpoint(); + { + bind(&isNullOrUndefined); + c = ma_cmp(src, Imm32(0), Equal); + ma_b(c, &ok); + breakpoint(); + } + { + bind(&isBoolean); + c = ma_cmp(src, Imm32(1), BelowOrEqual); + ma_b(c, &ok); + breakpoint(); + } + bind(&ok); +#endif + + if (src != dest.payloadReg()) { + xs_mr(dest.payloadReg(), src); + } + // The nunbox32 tag is JSVAL_TAG_CLEAR | type. + as_oris(dest.typeReg(), type, uint16_t(JSVAL_TAG_CLEAR >> 16)); + as_ori(dest.typeReg(), dest.typeReg(), uint16_t(JSVAL_TAG_CLEAR)); +} + +void MacroAssemblerPPCCompat::unboxValue(const ValueOperand& src, + AnyRegister dest, JSValueType type) { + if (dest.isFloat()) { + Label notInt32, end; + asMasm().branchTestInt32(Assembler::NotEqual, src, ¬Int32); + convertInt32ToDouble(src.payloadReg(), dest.fpu()); + ma_b(Assembler::Always, &end); + bind(¬Int32); + unboxDouble(src, dest.fpu()); + bind(&end); + } else { + unboxNonDouble(src, dest.gpr(), type); + } +} + +// =============================================================== +// Load int32 or double from memory + +void MacroAssemblerPPCCompat::loadInt32OrDouble(const Address& src, + FloatRegister dest) { + Label notInt32, end; + { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + load32(ToType(src), scratch); + asMasm().branchTestInt32(Assembler::NotEqual, scratch, ¬Int32); + load32(ToPayload(src), scratch); + convertInt32ToDouble(scratch, dest); + } + ma_b(Assembler::Always, &end); + bind(¬Int32); + loadDouble(src, dest); + bind(&end); +} + +void MacroAssemblerPPCCompat::loadInt32OrDouble(const BaseIndex& addr, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register base = temps.Acquire(); + computeScaledAddress(addr, base); + loadInt32OrDouble(Address(base, addr.offset), dest); +} + +// =============================================================== +// Toggled call + +CodeOffset MacroAssemblerPPCCompat::toggledCall(JitCode* target, bool enabled) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + m_buffer.enterNoPool(LongBranchInstructions); + BufferOffset boLoad = emitLoadPtrStanza(scratch, uintptr_t(target->raw())); + CodeOffset offset(boLoad.getOffset()); + addPendingJump(boLoad, ImmPtr(target->raw()), RelocationKind::JITCODE); + if (enabled) { + xs_mtctr(scratch); + as_bctr(LinkBit::LinkB); + } else { + writeInst(NopInst); + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + MOZ_ASSERT_IF(!oom(), nextOffset().getOffset() - offset.offset() == + ToggledCallSize(nullptr)); + return offset; +} + +// =============================================================== +// Exception handling + +void MacroAssemblerPPCCompat::handleFailureWithHandlerTail( + Label* profilerExitTail, Label* bailoutTail, + uint32_t* returnValueCheckOffset) { + int size = (sizeof(ResumeFromException) + ABIStackAlignment - 1) & + ~(ABIStackAlignment - 1); + asMasm().subPtr(Imm32(size), StackPointer); + mov(StackPointer, r3); + + using Fn = void (*)(ResumeFromException* rfe); + asMasm().setupUnalignedABICall(r4); + asMasm().passABIArg(r3); + asMasm().callWithABI( + ABIType::General, CheckUnsafeCallWithABI::DontCheckHasExitFrame); + + *returnValueCheckOffset = asMasm().currentOffset(); + + Label entryFrame; + Label catch_; + Label finally; + Label returnBaseline; + Label returnIon; + Label bailout; + Label wasmInterpEntry; + Label wasmCatch; + + load32(Address(StackPointer, ResumeFromException::offsetOfKind()), r3); + asMasm().branch32(Assembler::Equal, r3, + Imm32(ExceptionResumeKind::EntryFrame), &entryFrame); + asMasm().branch32(Assembler::Equal, r3, Imm32(ExceptionResumeKind::Catch), + &catch_); + asMasm().branch32(Assembler::Equal, r3, Imm32(ExceptionResumeKind::Finally), + &finally); + asMasm().branch32(Assembler::Equal, r3, + Imm32(ExceptionResumeKind::ForcedReturnBaseline), + &returnBaseline); + asMasm().branch32(Assembler::Equal, r3, + Imm32(ExceptionResumeKind::ForcedReturnIon), &returnIon); + asMasm().branch32(Assembler::Equal, r3, Imm32(ExceptionResumeKind::Bailout), + &bailout); + asMasm().branch32(Assembler::Equal, r3, + Imm32(ExceptionResumeKind::WasmInterpEntry), + &wasmInterpEntry); + asMasm().branch32(Assembler::Equal, r3, Imm32(ExceptionResumeKind::WasmCatch), + &wasmCatch); + + breakpoint(); // Invalid kind. + + // No exception handler. Return error from entry frame. + bind(&entryFrame); + asMasm().moveValue(MagicValue(JS_ION_ERROR), JSReturnOperand); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfStackPointer()), + StackPointer); + ret(); + + // Catch handler. + bind(&catch_); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfTarget()), r3); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfStackPointer()), + StackPointer); + jump(r3); + + // Finally block. + bind(&finally); + ValueOperand exception = ValueOperand(r5, r4); + loadValue(Address(StackPointer, ResumeFromException::offsetOfException()), + exception); + + ValueOperand exceptionStack = ValueOperand(r7, r6); + loadValue( + Address(StackPointer, ResumeFromException::offsetOfExceptionStack()), + exceptionStack); + + loadPtr(Address(StackPointer, ResumeFromException::offsetOfTarget()), r3); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfStackPointer()), + StackPointer); + + pushValue(exception); + pushValue(exceptionStack); + pushValue(BooleanValue(true)); + jump(r3); + + // Forced return from baseline. + Label profilingInstrumentation; + bind(&returnBaseline); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfStackPointer()), + StackPointer); + loadValue(Address(FramePointer, BaselineFrame::reverseOffsetOfReturnValue()), + JSReturnOperand); + jump(&profilingInstrumentation); + + // Forced return from Ion. + bind(&returnIon); + loadValue(Address(StackPointer, ResumeFromException::offsetOfException()), + JSReturnOperand); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfStackPointer()), + StackPointer); + + bind(&profilingInstrumentation); + { + Label skipProfilingInstrumentation; + AbsoluteAddress addressOfEnabled( + asMasm().runtime()->geckoProfiler().addressOfEnabled()); + asMasm().branch32(Assembler::Equal, addressOfEnabled, Imm32(0), + &skipProfilingInstrumentation); + jump(profilerExitTail); + bind(&skipProfilingInstrumentation); + } + + xs_mr(StackPointer, FramePointer); + // Pop FP from stack, then return (pop LR + blr). + loadPtr(Address(StackPointer, 0), FramePointer); + asMasm().addPtr(Imm32(sizeof(void*)), StackPointer); + ret(); + + // Bailout. + bind(&bailout); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfBailoutInfo()), + r5); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfStackPointer()), + StackPointer); + xs_li(ReturnReg, 1); + jump(bailoutTail); + + // Wasm interp entry. + bind(&wasmInterpEntry); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfStackPointer()), + StackPointer); + movePtr(ImmWord(wasm::InterpFailInstanceReg), InstanceReg); + ret(); + + // Wasm catch. + bind(&wasmCatch); + wasm::GenerateJumpToCatchHandler(asMasm(), StackPointer, r4, r5, r6); +} + +void MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output) { + ScratchDoubleScope fpscratch(asMasm()); + + // NaN and everything <= 0 clamps to 0; everything >= 255 clamps to 255. + // fctiw rounds half-to-even per the default FPSCR mode, which is what + // Uint8ClampedArray wants. + Label positive, below255, done; + zeroDouble(fpscratch); + branchDouble(DoubleGreaterThan, input, fpscratch, &positive); + { + move32(Imm32(0), output); + jump(&done); + } + + bind(&positive); + + loadConstantDouble(255.0, fpscratch); + branchDouble(DoubleLessThan, input, fpscratch, &below255); + { + move32(Imm32(255), output); + jump(&done); + } + + bind(&below255); + + emitFctiw(fpscratch, input); + allocStackScratch(); + as_stfd(fpscratch, StackPointer, StackScratchSlot); + as_lwz(output, StackPointer, StackScratchSlot + 4); + freeStackScratch(); + bind(&done); +} + +void MacroAssembler::subFromStackPtr(Imm32 imm32) { + if (!imm32.value) { + return; + } + // SysV ppc32 requires a valid back chain at 0(sp). stwu reads the source + // register before writing the base back, so this stores the old SP. + if (is_intN(-imm32.value, 16)) { + as_stwu(StackPointer, StackPointer, int16_t(-imm32.value)); + return; + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + xs_mr(scratch, StackPointer); + asMasm().subPtr(imm32, StackPointer); + as_stw(scratch, StackPointer, 0); +} + +//{{{ check_macroassembler_style + +void MacroAssembler::widenInt32(Register r) { + // A GPR is already a full machine word on this target. +} + +// Stack operations. +void MacroAssembler::Push(Register reg) { + push(reg); + adjustFrame(int32_t(sizeof(intptr_t))); +} +void MacroAssembler::Push(const Imm32 imm) { + push(imm); + adjustFrame(int32_t(sizeof(intptr_t))); +} + +void MacroAssembler::Push(const ImmWord imm) { + push(imm); + adjustFrame(int32_t(sizeof(intptr_t))); +} + +void MacroAssembler::Push(const ImmPtr imm) { + Push(ImmWord(uintptr_t(imm.value))); +} + +void MacroAssembler::Push(const ImmGCPtr ptr) { + push(ptr); + adjustFrame(int32_t(sizeof(intptr_t))); +} + +void MacroAssembler::PushBoxed(FloatRegister reg) { + subFromStackPtr(Imm32(sizeof(double))); + boxDouble(reg, Address(getStackPointer(), 0)); + adjustFrame(sizeof(double)); +} + +void MacroAssembler::Pop(Register reg) { + pop(reg); + adjustFrame(-int32_t(sizeof(intptr_t))); +} +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)); + } + + // Natural per-kind slot: 8 bytes for Single/Double via stfd, 16 bytes for + // Simd128 via stvx. + for (FloatRegisterBackwardIterator iter(set.fpus().reduceSetForPush()); + iter.more(); ++iter) { + FloatRegister reg = *iter; + diff -= reg.size(); + if (reg.isSimd128()) { + if (HasVMX()) { + storeUnalignedSimd128(reg, Address(StackPointer, diff)); + } + } else { + storeDouble(reg.asDouble(), 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) { + FloatRegister reg = *iter; + diff -= reg.size(); + if (!ignore.has(reg)) { + if (reg.isSimd128()) { + if (HasVMX()) { + loadUnalignedSimd128(Address(StackPointer, diff), reg); + } + } else { + loadDouble(Address(StackPointer, diff), reg.asDouble()); + } + } + } + MOZ_ASSERT(diff == 0); + freeStack(reserved); +} + +// Call operations. +CodeOffset MacroAssembler::call(Register reg) { + xs_mtctr(reg); + as_bctr(LinkB); + return CodeOffset(currentOffset()); +} +CodeOffset MacroAssembler::call(Label* label) { + if (label->bound()) { + // Open the no-pool window BEFORE computing the displacement: + // enterNoPool() can itself flush a pending pool and advance + // currentOffset(). + m_buffer.enterNoPool(LongBranchInstructions); + int32_t offset = label->offset() - currentOffset(); + // The call goes in the last slot of the stanza. + int32_t callOffset = + offset - (LongBranchInstructions - 1) * (int32_t)sizeof(uint32_t); + if (JOffImm26::IsInRange(callOffset)) { + for (uint32_t i = 1; i < LongBranchInstructions; i++) { + writeInst(NopInst); + } + as_b(JOffImm26(callOffset), RelativeBranch, LinkB); + m_buffer.leaveNoPool(); + return CodeOffset(currentOffset()); + } + BufferOffset bo = + emitLoadPtrStanza(SecondScratchReg, LabelBase::INVALID_OFFSET); + xs_mtctr(SecondScratchReg); + as_bctr(LinkB); + m_buffer.leaveNoPool(); + addLongJump(bo, BufferOffset(label->offset())); + return CodeOffset(currentOffset()); + } + // Forward reference: CallTag stanza. + m_buffer.enterNoPool(LongBranchInstructions); + BufferOffset bo = xs_trap_tagged(CallTag); + writeInst(label->used() ? label->offset() : LabelBase::INVALID_OFFSET); + for (uint32_t i = 2; i < LongBranchInstructions; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + if (!oom()) { + label->use(bo.getOffset()); + } + return CodeOffset(currentOffset()); +} +CodeOffset MacroAssembler::call(const Address& addr) { + loadPtr(addr, CallReg); + return call(CallReg); +} + +void MacroAssembler::call(ImmPtr target) { + m_buffer.enterNoPool(LongBranchInstructions); + BufferOffset bo = emitLoadPtrStanza(CallReg, uintptr_t(target.value)); + addPendingJump(bo, target, RelocationKind::HARDCODED); + xs_mtctr(CallReg); + as_bctr(LinkB); + m_buffer.leaveNoPool(); +} + +CodeOffset MacroAssembler::call(wasm::SymbolicAddress target) { + movePtr(target, CallReg); + return call(CallReg); +} + +// A SymbolicAddress is a plain code address on SysV ppc32; there are no +// function descriptors and no TOC to restore. +CodeOffset MacroAssemblerPPCCompat::callWasmSymbolic( + wasm::SymbolicAddress imm) { + asMasm().movePtr(imm, CallReg); + return asMasm().call(CallReg); +} + +void MacroAssembler::callWithABINoProfiler(const Address& fun, ABIType result) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(fun, scratch); + + uint32_t stackAdjust; + callWithABIPre(&stackAdjust); + call(scratch); + callWithABIPost(stackAdjust, result); +} + +void MacroAssembler::callWithABIPre(uint32_t* stackAdjust, bool callFromWasm) { + MOZ_ASSERT(inCall_); + // ABIArgGenerator already accounts for the SysV linkage area via + // ShadowStackSpace. + uint32_t stackForCall = abiArgs_.stackBytesConsumedSoFar(); + + // Reserve a slot for our own LR save. + stackForCall += sizeof(intptr_t); + + if (dynamicAlignment_) { + stackForCall += ComputeByteAlignment(stackForCall, ABIStackAlignment); + } else { + uint32_t alignmentAtPrologue = callFromWasm ? sizeof(wasm::Frame) : 0; + stackForCall += ComputeByteAlignment( + stackForCall + framePushed() + alignmentAtPrologue, ABIStackAlignment); + } + + *stackAdjust = stackForCall; + reserveStack(stackForCall); + + // Save LR. Restored in callWithABIPost. + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + xs_mflr(scratch); + storePtr(scratch, Address(StackPointer, stackForCall - sizeof(intptr_t))); + } + + // Position all arguments. + { + enoughMemory_ &= moveResolver_.resolve(); + if (!enoughMemory_) { + return; + } + + MoveEmitter emitter(*this); + emitter.emit(moveResolver_); + emitter.finish(); + } + + assertStackAlignment(ABIStackAlignment); +} + +void MacroAssembler::callWithABIPost(uint32_t stackAdjust, ABIType result) { + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(Address(StackPointer, stackAdjust - sizeof(intptr_t)), scratch); + xs_mtlr(scratch); + } + + if (dynamicAlignment_) { + // Restore SP from stack (as stored in setupUnalignedABICall). + loadPtr(Address(StackPointer, stackAdjust), StackPointer); + adjustFrame(-stackAdjust); + } else { + freeStack(stackAdjust); + } + +#ifdef DEBUG + MOZ_ASSERT(inCall_); + inCall_ = false; +#endif +} + +// Value operations. +void MacroAssembler::moveValue(const ValueOperand& src, + const ValueOperand& dest) { + Register s0 = src.typeReg(); + Register s1 = src.payloadReg(); + Register d0 = dest.typeReg(); + Register d1 = dest.payloadReg(); + + if (s1 == d0) { + if (s0 == d1) { + // A pure swap of the two registers. + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(d0 != scratch && d1 != scratch); + xs_mr(scratch, d1); + xs_mr(d1, d0); + xs_mr(d0, scratch); + return; + } + std::swap(s0, s1); + std::swap(d0, d1); + } + + if (s0 != d0) { + xs_mr(d0, s0); + } + if (s1 != d1) { + xs_mr(d1, s1); + } +} +void MacroAssembler::moveValue(const Value& src, const ValueOperand& dest) { + move32(Imm32(src.toNunboxTag()), dest.typeReg()); + if (src.isGCThing()) { + movePtr(ImmGCPtr(src.toGCThing()), dest.payloadReg()); + } else { + move32(Imm32(src.toNunboxPayload()), dest.payloadReg()); + } +} + +// Branch operations. +void MacroAssembler::branchTestValue(Condition cond, const ValueOperand& lhs, + const Value& rhs, Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + MOZ_ASSERT(!rhs.isNaN()); + + Label done; + Label* payloadMismatch = (cond == Equal) ? &done : label; + + if (rhs.isGCThing()) { + branchPtr(Assembler::NotEqual, lhs.payloadReg(), ImmGCPtr(rhs.toGCThing()), + payloadMismatch); + } else { + branch32(Assembler::NotEqual, lhs.payloadReg(), + Imm32(rhs.toNunboxPayload()), payloadMismatch); + } + branch32(cond, lhs.typeReg(), Imm32(rhs.toNunboxTag()), label); + bind(&done); +} +void MacroAssembler::branchTestNaNValue(Condition cond, const ValueOperand& val, + Register temp, Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + + // Ignore the sign bit when testing for NaN. + static_assert(JS::detail::CanonicalizedNaNSignBit == 0); + as_rlwinm(temp, val.typeReg(), 0, 1, 31); + + Value expected = DoubleValue(JS::GenericNaN()); + Label done; + Label* payloadMismatch = (cond == Equal) ? &done : label; + branch32(Assembler::NotEqual, val.payloadReg(), + Imm32(expected.toNunboxPayload()), payloadMismatch); + branch32(cond, temp, Imm32(expected.toNunboxTag()), label); + bind(&done); +} + +void MacroAssembler::branchPtrInNurseryChunk(Condition cond, Register ptr, + Register temp, Label* label) { + MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); + MOZ_ASSERT(ptr != temp); + MOZ_ASSERT(temp != InvalidReg); + + andPtr(Imm32(int32_t(~gc::ChunkMask)), ptr, temp); + branchPtr(InvertCondition(cond), Address(temp, gc::ChunkStoreBufferOffset), + ImmWord(0), label); +} +void MacroAssembler::branchValueIsNurseryCell(Condition cond, + ValueOperand value, Register temp, + Label* label) { + branchValueIsNurseryCellImpl(cond, value, temp, label); +} + +// Patching / near address operations. +CodeOffset MacroAssembler::nopPatchableToCall() { + m_buffer.enterNoPool(LongBranchInstructions); + for (uint32_t i = 0; i < LongBranchInstructions; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + // The returned offset is the return address (just past the stanza). + return CodeOffset(currentOffset()); +} +CodeOffset MacroAssembler::moveNearAddressWithPatch(Register dest) { + CodeOffset offset(currentOffset()); + emitLoadPtrStanza(dest, 0); + return offset; +} +// static +void MacroAssembler::patchNearAddressMove(CodeLocationLabel loc, + CodeLocationLabel target) { + Instruction* inst = (Instruction*)loc.raw(); + UpdateLoadPtrValue(inst, (uintptr_t)target.raw()); +} + +// Return address operations (link register architectures). +// +// These MUST decrement SP by exactly one machine word: wasm::Frame is +// {callerFP_, returnAddress_} and GenerateCallablePrologue pairs this with +// push(FramePointer) to match that layout exactly. +void MacroAssembler::pushReturnAddress() { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + xs_mflr(scratch); + push(scratch); +} +void MacroAssembler::popReturnAddress() { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + pop(scratch); + xs_mtlr(scratch); +} + +// ABI setup. +void MacroAssembler::setupUnalignedABICall(Register scratch) { + MOZ_ASSERT(!IsCompilingWasm(), "wasm should only use aligned ABI calls"); + setupNativeABICall(); + dynamicAlignment_ = true; + + movePtr(StackPointer, scratch); + + // Force sp to be aligned. + subPtr(Imm32(sizeof(uintptr_t)), StackPointer); + andPtr(Imm32(~(ABIStackAlignment - 1)), StackPointer); + storePtr(scratch, Address(StackPointer, 0)); +} + +// =============================================================== +// Arithmetic helpers. + +void MacroAssembler::flexibleDivMod32(Register lhs, Register rhs, + Register divOutput, Register remOutput, + bool isUnsigned, const LiveRegisterSet&) { + MOZ_ASSERT(lhs != divOutput && lhs != remOutput, "lhs is preserved"); + MOZ_ASSERT(rhs != divOutput && rhs != remOutput, "rhs is preserved"); + + // PPC has no modulus instruction: rem = lhs - (lhs/rhs)*rhs. + // divw(INT32_MIN, -1) is undefined; define it as quotient INT32_MIN, + // remainder 0. + Label done; + if (!isUnsigned) { + Label notMinOverflow; + branch32(Assembler::NotEqual, lhs, Imm32(INT32_MIN), ¬MinOverflow); + branch32(Assembler::NotEqual, rhs, Imm32(-1), ¬MinOverflow); + move32(Imm32(INT32_MIN), divOutput); + move32(Imm32(0), remOutput); + jump(&done); + bind(¬MinOverflow); + } + if (isUnsigned) { + as_divwu(divOutput, lhs, rhs); + } else { + as_divw(divOutput, lhs, rhs); + } + as_mullw(remOutput, divOutput, rhs); + as_subf(remOutput, remOutput, lhs); + bind(&done); +} + +void MacroAssembler::shiftIndex32AndAdd(Register indexTemp32, int shift, + Register pointer) { + if (IsShiftInScaleRange(shift)) { + computeEffectiveAddress( + BaseIndex(pointer, indexTemp32, ShiftToScale(shift)), pointer); + return; + } + lshift32(Imm32(shift), indexTemp32); + addPtr(indexTemp32, pointer); +} + +void MacroAssembler::convertInt64ToDouble(Register64 src, FloatRegister dest) { + // dest = (double)(int32)hi * 2^32 + (double)(uint32)lo. Both products are + // exact, so the single fadd rounds the exact int64 value correctly. The + // partial result is parked in the scratch frame because ScratchDoubleReg is + // consumed by the int-to-double bias trick. + convertUInt32ToDouble(src.low, dest); + allocStackScratch(); + as_stfd(dest, StackPointer, StackScratchSlot); + convertInt32ToDouble(src.high, dest); + loadConstantDouble(4294967296.0, ScratchDoubleReg); + as_fmul(dest, dest, ScratchDoubleReg); + as_lfd(ScratchDoubleReg, StackPointer, StackScratchSlot); + freeStackScratch(); + as_fadd(dest, dest, ScratchDoubleReg); +} + +void MacroAssembler::nearbyIntDouble(RoundingMode mode, FloatRegister src, + FloatRegister dest) { + // There is no frin/friz/frip/frim here and no fctid either, so the rounding + // goes through the 32-bit converters. A magnitude at or above 2^31 is + // already integral (or NaN/Inf) and passes through unchanged, which also + // keeps the fctiw below in range. + MOZ_ASSERT(dest != ScratchDoubleReg); + constexpr int16_t kParked = MacroAssemblerPPC::StackScratchSlot2; + Label release; + + // The scratch frame spans the whole sequence, so every exit goes through + // |release|; the nested conversions balance their own frames inside it. + allocStackScratch(StackScratchFrame2); + + // src is needed after the conversion (for its sign) but ScratchDoubleReg is + // consumed by convertInt32ToDouble, so park it. + as_stfd(src, StackPointer, kParked); + + { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_lwz(scratch, StackPointer, kParked); + // Biased exponent >= 1054 (= 1023 + 31) means |val| >= 2^31. + as_rlwinm(scratch, scratch, 12, 21, 31); + ma_cmp(scratch, Imm32(1054), Assembler::GreaterThanOrEqual); + } + if (src != dest) { + moveDouble(src, dest); + } + ma_b(Assembler::GreaterThanOrEqual, &release); + + MacroAssemblerPPC::FpIntRound fpMode; + switch (mode) { + case RoundingMode::NearestTiesToEven: + fpMode = MacroAssemblerPPC::RoundNearest; + break; + case RoundingMode::TowardsZero: + fpMode = MacroAssemblerPPC::RoundZero; + break; + case RoundingMode::Up: + fpMode = MacroAssemblerPPC::RoundUp; + break; + case RoundingMode::Down: + fpMode = MacroAssemblerPPC::RoundDown; + break; + default: + MOZ_CRASH("Unexpected rounding mode"); + } + roundToInt32FPR(dest, src, fpMode); + { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_stfd(dest, StackPointer, MacroAssemblerPPC::StackScratchSlot); + as_lwz(scratch, StackPointer, MacroAssemblerPPC::StackScratchSlot + 4); + temps.Release(scratch); + convertInt32ToDouble(scratch, dest); + } + // Restore the sign so that e.g. ceil(-0.5) stays -0.0. + as_lfd(ScratchDoubleReg, StackPointer, kParked); + emitCopySign(dest, ScratchDoubleReg, dest); + bind(&release); + freeStackScratch(StackScratchFrame2); +} + +void MacroAssembler::nearbyIntFloat32(RoundingMode mode, FloatRegister src, + FloatRegister dest) { + // PPC FP rounding operates on double precision; round then narrow back. + nearbyIntDouble(mode, src, dest); + as_frsp(dest, dest); +} + +// =============================================================== +// Far jump support. + +CodeOffset MacroAssembler::farJumpWithPatch() { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + + CodeOffset loadOffset(currentOffset()); + m_buffer.enterNoPool(LongBranchInstructions); + emitLoadPtrStanza(scratch, 0); + xs_mtctr(scratch); + as_bctr(); + m_buffer.leaveNoPool(); + + return loadOffset; +} + +// =============================================================== +void MacroAssembler::flush() { Assembler::flush(); } + +// Wasm support. + +FaultingCodeOffset MacroAssembler::wasmTrapInstruction() { + m_buffer.flushPool(); // see comment in wasmLoadImpl + FaultingCodeOffset fco = FaultingCodeOffset(currentOffset()); + xs_trap(); + return fco; +} + +// PPC SlowCallMarker: `ori r12, r12, 0` -- a NOP-like instruction that will +// not appear in normal code generation (plain nop is ori r0, r0, 0). +static const int32_t SlowCallMarker = 0x618C0000; + +void MacroAssembler::wasmMarkCallAsSlow() { as_ori(CallReg, CallReg, 0); } + +void MacroAssembler::wasmCheckSlowCallsite(Register ra_, Label* notSlow, + Register temp1, Register temp2) { + MOZ_ASSERT(ra_ != temp2); + load32(Address(ra_, 0), temp2); + branch32(Assembler::NotEqual, temp2, Imm32(SlowCallMarker), notSlow); +} + +CodeOffset MacroAssembler::wasmMarkedSlowCall(const wasm::CallSiteDesc& desc, + const Register reg) { + CodeOffset offset = call(desc, reg); + wasmMarkCallAsSlow(); + return offset; +} + +// =============================================================== +// Additional stack operations. + +void MacroAssembler::Push(FloatRegister f) { + push(f); + adjustFrame(int32_t(sizeof(double))); +} +void MacroAssembler::Pop(FloatRegister f) { + pop(f); + adjustFrame(-int32_t(sizeof(double))); +} +void MacroAssembler::Pop(const ValueOperand& val) { + popValue(val); + adjustFrame(-int32_t(sizeof(Value))); +} + +// static +size_t MacroAssembler::PushRegsInMaskSizeInBytes(LiveRegisterSet set) { + return set.gprs().size() * sizeof(intptr_t) + set.fpus().getPushSizeInBytes(); +} + +void MacroAssembler::storeRegsInMask(LiveRegisterSet set, Address dest, + Register scratch) { + FloatRegisterSet fpuSet(set.fpus().reduceSetForPush()); + mozilla::DebugOnly numFpu = fpuSet.size(); + mozilla::DebugOnly diffF = fpuSet.getPushSizeInBytes(); + mozilla::DebugOnly diffG = set.gprs().size() * sizeof(intptr_t); + + MOZ_ASSERT(dest.offset >= diffG + diffF); + + for (GeneralRegisterBackwardIterator iter(set.gprs()); iter.more(); ++iter) { + diffG -= sizeof(intptr_t); + dest.offset -= sizeof(intptr_t); + storePtr(*iter, dest); + } + MOZ_ASSERT(diffG == 0); + + for (FloatRegisterBackwardIterator iter(fpuSet); iter.more(); ++iter) { + FloatRegister reg = *iter; + diffF -= reg.size(); + numFpu -= 1; + dest.offset -= reg.size(); + if (reg.isSimd128()) { + if (HasVMX()) { + storeUnalignedSimd128(reg, dest); + } + } else { + storeDouble(reg.asDouble(), dest); + } + } + MOZ_ASSERT(diffF == 0); +} + +void MacroAssembler::freeStackTo(uint32_t framePushed) { + MOZ_ASSERT(framePushed <= framePushed_); + movePtr(FramePointer, StackPointer); + if (framePushed) { + subPtr(Imm32(framePushed), StackPointer); + } + framePushed_ = framePushed; +} + +// =============================================================== +// Additional call / patch operations. + +void MacroAssembler::call(JitCode* c) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + + BufferOffset bo = emitLoadPtrStanza(scratch, uintptr_t(c->raw())); + addPendingJump(bo, ImmPtr(c->raw()), RelocationKind::JITCODE); + + callJitNoProfiler(scratch); +} + +CodeOffset MacroAssembler::callWithPatch() { + // A stanza of nops that patchCall() rewrites. The returned offset is the + // return address (just past the stanza). + m_buffer.enterNoPool(LongBranchInstructions); + for (uint32_t i = 0; i < LongBranchInstructions; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + return CodeOffset(currentOffset()); +} + +void MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset) { + uint32_t stanzaStart = + callerOffset - LongBranchInstructions * sizeof(uint32_t); + Instruction* i0 = (Instruction*)(m_buffer.getInst(BufferOffset(stanzaStart))); + intptr_t blAddr = (intptr_t)stanzaStart + + (LongBranchInstructions - 1) * (intptr_t)sizeof(uint32_t); + intptr_t callOffset = (intptr_t)calleeOffset - blAddr; + if (JOffImm26::IsInRange(callOffset)) { + for (uint32_t i = 0; i < LongBranchInstructions - 1; i++) { + i0[i].makeNop(); + } + i0[LongBranchInstructions - 1].setData( + PPC_b | JOffImm26(callOffset).encode() | LinkB); + } else { + addLongJump(BufferOffset(stanzaStart), BufferOffset(calleeOffset)); + WriteLoadPtrInstructions(i0, SecondScratchReg, LabelBase::INVALID_OFFSET); + i0[PointerImmInstructions].makeOp_mtctr(SecondScratchReg); + i0[PointerImmInstructions + 1].makeOp_bctr(LinkB); + } +} + +void MacroAssembler::patchFarJump(CodeOffset farJump, uint32_t targetOffset) { + Instruction* inst = + (Instruction*)m_buffer.getInst(BufferOffset(farJump.offset())); + // The destination register is the RT field of the leading lis. + uint32_t regCode = (inst[0].encode() >> 21) & 0x1f; + Register reg = Register::FromCode(regCode); + WriteLoadPtrInstructions(inst, reg, LabelBase::INVALID_OFFSET); + addLongJump(BufferOffset(farJump.offset()), BufferOffset(targetOffset)); +} + +// static +void MacroAssembler::patchFarJump(uint8_t* farJump, uint8_t* target) { + UpdateLoadPtrValue((Instruction*)farJump, (uintptr_t)target); + FlushICache(farJump, PointerImmInstructions * sizeof(Instruction)); +} + +// static +void MacroAssembler::patchNopToCall(uint8_t* callsite, uint8_t* target) { + // callsite points just past the stanza. + Instruction* inst = (Instruction*)callsite - LongBranchInstructions; + WriteLoadPtrInstructions(inst, SecondScratchReg, (uintptr_t)target); + inst[PointerImmInstructions].makeOp_mtctr(SecondScratchReg); + inst[PointerImmInstructions + 1].makeOp_bctr(LinkB); + FlushICache(inst, LongBranchInstructions * sizeof(Instruction)); +} + +// static +void MacroAssembler::patchCallToNop(uint8_t* callsite) { + Instruction* inst = (Instruction*)callsite - LongBranchInstructions; + for (uint32_t i = 0; i < LongBranchInstructions; i++) { + inst[i].makeNop(); + } + FlushICache(inst, LongBranchInstructions * sizeof(Instruction)); +} + +void MacroAssembler::patchMove32(CodeOffset offset, Imm32 n) { + Instruction* inst = + (Instruction*)m_buffer.getInst(BufferOffset(offset.offset())); + UpdateLoadPtrValue(inst, uintptr_t(uint32_t(n.value))); +} + +uint32_t MacroAssembler::pushFakeReturnAddress(Register scratch) { + CodeLabel cl; + + // mov(CodeLabel*, Register) always emits the full patchable stanza, which + // is what processCodeLabels -> UpdateLoadPtrValue expects to find. + mov(&cl, scratch); + + Push(scratch); + + bind(&cl); + uint32_t retAddr = currentOffset(); + + addCodeLabel(cl); + return retAddr; +} + +void MacroAssembler::callWithABINoProfiler(Register fun, ABIType result) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + // fun might be clobbered by callWithABIPre's argument shuffling. + movePtr(fun, scratch); + + uint32_t stackAdjust; + callWithABIPre(&stackAdjust); + call(scratch); + callWithABIPost(stackAdjust, result); +} + +void MacroAssembler::callWithABIJitCode(Register fun, ABIType result) { + AutoProfilerCallInstrumentation profiler(*this); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(fun, scratch); + + uint32_t stackAdjust; + callWithABIPre(&stackAdjust); + call(scratch); + callWithABIPost(stackAdjust, result); +} + +// =============================================================== +// Additional arithmetic helpers. + +void MacroAssembler::flexibleRemainder32(Register lhs, Register rhs, + Register dest, bool isUnsigned, + const LiveRegisterSet&) { + // rem = lhs - (lhs/rhs)*rhs; divw(INT32_MIN, -1) is undefined, define the + // remainder as 0. + Label done; + if (!isUnsigned) { + Label notMinOverflow; + branch32(Assembler::NotEqual, lhs, Imm32(INT32_MIN), ¬MinOverflow); + branch32(Assembler::NotEqual, rhs, Imm32(-1), ¬MinOverflow); + move32(Imm32(0), dest); + jump(&done); + bind(¬MinOverflow); + } + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + if (isUnsigned) { + as_divwu(scratch, lhs, rhs); + } else { + as_divw(scratch, lhs, rhs); + } + as_mullw(scratch, scratch, rhs); + as_subf(dest, scratch, lhs); + } + bind(&done); +} + +void MacroAssembler::flexibleQuotientPtr( + Register lhs, Register rhs, Register dest, bool isUnsigned, + const LiveRegisterSet& volatileLiveRegs) { + flexibleQuotient32(lhs, rhs, dest, isUnsigned, volatileLiveRegs); +} + +void MacroAssembler::flexibleRemainderPtr( + Register lhs, Register rhs, Register dest, bool isUnsigned, + const LiveRegisterSet& volatileLiveRegs) { + flexibleRemainder32(lhs, rhs, dest, isUnsigned, volatileLiveRegs); +} + +// =============================================================== +// Rounding helpers. + +// Shared tail for the floor/ceil/trunc/round-to-int32 helpers: convert under +// |mode|, bail out if fctiw signalled an invalid conversion (NaN, Inf, or out +// of int32 range), and extract the result word. +static void EmitFPToInt32(MacroAssembler& masm, FloatRegister src, + Register dest, MacroAssemblerPPC::FpIntRound mode, + Label* fail) { + ScratchDoubleScope fpscratch(masm); + masm.clearFPConversionException(); + masm.roundToInt32FPR(fpscratch, src, mode); + masm.testFPConversionException(); + masm.ma_b(Assembler::SOBit, fail); + masm.allocStackScratch(); + masm.as_stfd(fpscratch, StackPointer, MacroAssemblerPPC::StackScratchSlot); + masm.as_lwz(dest, StackPointer, MacroAssemblerPPC::StackScratchSlot + 4); + masm.freeStackScratch(); +} + +void MacroAssembler::floorDoubleToInt32(FloatRegister src, Register dest, + Label* fail) { + EmitFPToInt32(*this, src, dest, MacroAssemblerPPC::RoundDown, fail); + EmitRejectNegativeZero(*this, src, dest, fail); +} + +void MacroAssembler::floorFloat32ToInt32(FloatRegister src, Register dest, + Label* fail) { + EmitFPToInt32(*this, src, dest, MacroAssemblerPPC::RoundDown, fail); + EmitRejectNegativeZero(*this, src, dest, fail); +} + +void MacroAssembler::ceilDoubleToInt32(FloatRegister src, Register dest, + Label* fail) { + EmitFPToInt32(*this, src, dest, MacroAssemblerPPC::RoundUp, fail); + EmitRejectNegativeZero(*this, src, dest, fail); +} + +void MacroAssembler::ceilFloat32ToInt32(FloatRegister src, Register dest, + Label* fail) { + EmitFPToInt32(*this, src, dest, MacroAssemblerPPC::RoundUp, fail); + EmitRejectNegativeZero(*this, src, dest, fail); +} + +void MacroAssembler::truncDoubleToInt32(FloatRegister src, Register dest, + Label* fail) { + EmitFPToInt32(*this, src, dest, MacroAssemblerPPC::RoundZero, fail); + EmitRejectNegativeZero(*this, src, dest, fail); +} + +void MacroAssembler::truncFloat32ToInt32(FloatRegister src, Register dest, + Label* fail) { + EmitFPToInt32(*this, src, dest, MacroAssemblerPPC::RoundZero, fail); + EmitRejectNegativeZero(*this, src, dest, fail); +} + +void MacroAssembler::roundDoubleToInt32(FloatRegister src, Register dest, + FloatRegister temp, Label* fail) { + Label performRound; + + { + ScratchDoubleScope fpscratch(asMasm()); + zeroDouble(fpscratch); + branchDouble(DoubleGreaterThanOrEqual, src, fpscratch, &performRound); + + // Input is negative: anything in [-0.5, -0.0) rounds to -0. + loadConstantDouble(-0.5, fpscratch); + branchDouble(DoubleGreaterThanOrEqual, src, fpscratch, fail); + } + + bind(&performRound); + { + ScratchDoubleScope fpscratch(asMasm()); + loadConstantDouble(GetBiggestNumberLessThan(0.5), temp); + as_fadd(fpscratch, src, temp); + clearFPConversionException(); + roundToInt32FPR(fpscratch, fpscratch, MacroAssemblerPPC::RoundDown); + testFPConversionException(); + ma_b(Assembler::SOBit, fail); + allocStackScratch(); + as_stfd(fpscratch, StackPointer, MacroAssemblerPPC::StackScratchSlot); + as_lwz(dest, StackPointer, MacroAssemblerPPC::StackScratchSlot + 4); + freeStackScratch(); + } + + EmitRejectNegativeZero(*this, src, dest, fail); +} + +void MacroAssembler::roundFloat32ToInt32(FloatRegister src, Register dest, + FloatRegister temp, Label* fail) { + Label performRound; + + { + ScratchDoubleScope fpscratch(asMasm()); + loadConstantFloat32(0.0f, fpscratch); + branchFloat(DoubleGreaterThanOrEqual, src, fpscratch, &performRound); + + loadConstantFloat32(-0.5f, fpscratch); + branchFloat(DoubleGreaterThanOrEqual, src, fpscratch, fail); + } + + bind(&performRound); + { + ScratchDoubleScope fpscratch(asMasm()); + loadConstantFloat32(float(GetBiggestNumberLessThan(0.5)), temp); + as_fadds(fpscratch, src, temp); + clearFPConversionException(); + roundToInt32FPR(fpscratch, fpscratch, MacroAssemblerPPC::RoundDown); + testFPConversionException(); + ma_b(Assembler::SOBit, fail); + allocStackScratch(); + as_stfd(fpscratch, StackPointer, MacroAssemblerPPC::StackScratchSlot); + as_lwz(dest, StackPointer, MacroAssemblerPPC::StackScratchSlot + 4); + freeStackScratch(); + } + + EmitRejectNegativeZero(*this, src, dest, fail); +} + +// =============================================================== +// FP conversion / copy-sign. + +void MacroAssembler::convertIntPtrToDouble(Register src, FloatRegister dest) { + convertInt32ToDouble(src, dest); +} + +void MacroAssembler::copySignDouble(FloatRegister lhs, FloatRegister rhs, + FloatRegister output) { + // lhs is the magnitude source, rhs the sign source. + emitCopySign(output, rhs, lhs); +} + +void MacroAssembler::copySignFloat32(FloatRegister lhs, FloatRegister rhs, + FloatRegister output) { + emitCopySign(output, rhs, lhs); +} + +// =============================================================== +// GC / nursery helpers. + +void MacroAssembler::loadStoreBuffer(Register ptr, Register buffer) { + andPtr(Imm32(int32_t(~gc::ChunkMask)), ptr, buffer); + loadPtr(Address(buffer, gc::ChunkStoreBufferOffset), buffer); +} + +void MacroAssembler::branchValueIsNurseryCell(Condition cond, + const Address& address, + Register temp, Label* label) { + branchValueIsNurseryCellImpl(cond, address, temp, label); +} + +template +void MacroAssembler::branchValueIsNurseryCellImpl(Condition cond, + const T& value, Register temp, + Label* label) { + MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual); + MOZ_ASSERT(temp != InvalidReg); + Label done; + branchTestGCThing(Assembler::NotEqual, value, + cond == Assembler::Equal ? &done : label); + + getGCThingValueChunk(value, temp); + loadPtr(Address(temp, gc::ChunkStoreBufferOffset), temp); + branchPtr(InvertCondition(cond), temp, ImmWord(0), label); + + bind(&done); +} + +// =============================================================== +// Template instantiations. + +template +void MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, + MIRType valueType, const T& dest) { + MOZ_ASSERT(valueType < MIRType::Value); + + if (valueType == MIRType::Double) { + storeDouble(value.reg().typedReg().fpu(), dest); + return; + } + + if (value.constant()) { + storeValue(value.value(), dest); + } else { + storeValue(ValueTypeFromMIRType(valueType), value.reg().typedReg().gpr(), + dest); + } +} + +template void MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, + MIRType valueType, + const Address& dest); +template void MacroAssembler::storeUnboxedValue( + const ConstantOrRegister& value, MIRType valueType, + const BaseObjectElementIndex& dest); + +// =============================================================== +// Misc stubs. + +void MacroAssembler::comment(const char* msg) {} + +void MacroAssembler::speculationBarrier() { + // isync discards prefetched instructions and forces a refetch, blocking + // speculative bypass past this point. + as_isync(); +} + +void MacroAssembler::atomicPause() { nop(); } + +void MacroAssembler::enterFakeExitFrameForWasm(Register cxreg, Register scratch, + ExitFrameType type) { + enterFakeExitFrame(cxreg, scratch, type); +} + +void MacroAssembler::wasmBoundsCheck32(Condition cond, Register index, + Register boundsCheckLimit, + Label* label) { + Condition c = ma_cmp(index, boundsCheckLimit, cond); + ma_b(c, label); +} + +void MacroAssembler::wasmBoundsCheck32(Condition cond, Register index, + Address boundsCheckLimit, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(boundsCheckLimit, scratch); + Condition c = ma_cmp(index, scratch, cond); + ma_b(c, label); +} + +void MacroAssembler::wasmBoundsCheck64(Condition cond, Register64 index, + Register64 boundsCheckLimit, + Label* label) { + MOZ_ASSERT(cond == Assembler::Below || cond == Assembler::AboveOrEqual); + // The limit is a uintptr_t, i.e. 32 bits here, so a non-zero high word is + // always out of bounds. + Label rejoin; + Label* failLabel = cond == Assembler::AboveOrEqual ? label : &rejoin; + branch32(Assembler::NotEqual, index.high, Imm32(0), failLabel); + wasmBoundsCheck32(cond, index.low, boundsCheckLimit.low, label); + bind(&rejoin); +} + +void MacroAssembler::wasmBoundsCheck64(Condition cond, Register64 index, + Address boundsCheckLimit, Label* label) { + MOZ_ASSERT(cond == Assembler::Below || cond == Assembler::AboveOrEqual); + Label rejoin; + Label* failLabel = cond == Assembler::AboveOrEqual ? label : &rejoin; + branch32(Assembler::NotEqual, index.high, Imm32(0), failLabel); + wasmBoundsCheck32(cond, index.low, boundsCheckLimit, label); + bind(&rejoin); +} + +CodeOffset MacroAssembler::move32WithPatch(Register dest) { + CodeOffset offset(currentOffset()); + emitLoadPtrStanza(dest, 0); + return offset; +} + +CodeOffset MacroAssembler::sub32FromMemAndBranchIfNegativeWithPatch( + Address address, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != address.base); + load32(address, scratch); + // Positive placeholder, later patched to the real negative immediate. + as_addi(scratch, scratch, 128); + CodeOffset patchPoint = CodeOffset(currentOffset()); + store32(scratch, address); + as_cmpwi(scratch, 0); + ma_b(LessThan, label); + return patchPoint; +} + +bool MacroAssembler::convertUInt64ToDoubleNeedsTemp() { return false; } + +void MacroAssembler::call(ImmWord imm) { call(ImmPtr((void*)imm.value)); } + +void MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, + Register temp) { + MOZ_ASSERT(temp == Register::Invalid()); + convertUInt32ToDouble(src.low, dest); + allocStackScratch(); + as_stfd(dest, StackPointer, StackScratchSlot); + convertUInt32ToDouble(src.high, dest); + loadConstantDouble(4294967296.0, ScratchDoubleReg); + as_fmul(dest, dest, ScratchDoubleReg); + as_lfd(ScratchDoubleReg, StackPointer, StackScratchSlot); + freeStackScratch(); + as_fadd(dest, dest, ScratchDoubleReg); +} + +void MacroAssembler::convertInt64ToFloat32(Register64 src, FloatRegister dest) { + // Double rounding can differ from a direct i64 -> f32 conversion by at most + // one ULP, for magnitudes above 2^24 that land exactly on a single-precision + // rounding boundary. + convertInt64ToDouble(src, dest); + as_frsp(dest, dest); +} + +void MacroAssembler::convertUInt64ToFloat32(Register64 src, FloatRegister dest, + Register temp) { + MOZ_ASSERT(temp == Register::Invalid()); + convertUInt64ToDouble(src, dest, temp); + as_frsp(dest, dest); +} + +void MacroAssembler::flexibleQuotient32( + Register lhs, Register rhs, Register dest, bool isUnsigned, + const LiveRegisterSet& volatileLiveRegs) { + // divw(INT32_MIN, -1) is undefined; return INT32_MIN to match the other + // hardware-divide backends. + Label done; + if (!isUnsigned) { + Label notMinOverflow; + branch32(Assembler::NotEqual, lhs, Imm32(INT32_MIN), ¬MinOverflow); + branch32(Assembler::NotEqual, rhs, Imm32(-1), ¬MinOverflow); + move32(Imm32(INT32_MIN), dest); + jump(&done); + bind(¬MinOverflow); + } + if (isUnsigned) { + as_divwu(dest, lhs, rhs); + } else { + as_divw(dest, lhs, rhs); + } + bind(&done); +} + +void MacroAssembler::oolWasmTruncateCheckF32ToI32( + FloatRegister input, Register output, TruncFlags flags, + const wasm::TrapSiteDesc& trapSiteDesc, Label* rejoin) { + outOfLineWasmTruncateToInt32Check(input, output, MIRType::Float32, flags, + rejoin, trapSiteDesc); +} + +void MacroAssembler::oolWasmTruncateCheckF32ToI64( + FloatRegister input, Register64 output, TruncFlags flags, + const wasm::TrapSiteDesc& trapSiteDesc, Label* rejoin) { + outOfLineWasmTruncateToInt64Check(input, output, MIRType::Float32, flags, + rejoin, trapSiteDesc); +} + +void MacroAssembler::oolWasmTruncateCheckF64ToI32( + FloatRegister input, Register output, TruncFlags flags, + const wasm::TrapSiteDesc& trapSiteDesc, Label* rejoin) { + outOfLineWasmTruncateToInt32Check(input, output, MIRType::Double, flags, + rejoin, trapSiteDesc); +} + +void MacroAssembler::oolWasmTruncateCheckF64ToI64( + FloatRegister input, Register64 output, TruncFlags flags, + const wasm::TrapSiteDesc& trapSiteDesc, Label* rejoin) { + outOfLineWasmTruncateToInt64Check(input, output, MIRType::Double, flags, + rejoin, trapSiteDesc); +} + +void MacroAssemblerPPCCompat::outOfLineWasmTruncateToInt32Check( + FloatRegister input, Register output, MIRType fromType, TruncFlags flags, + Label* rejoin, const wasm::TrapSiteDesc& trapSiteDesc) { + bool isUnsigned = flags & TRUNC_UNSIGNED; + bool isSaturating = flags & TRUNC_SATURATING; + + if (isSaturating) { + ScratchDoubleScope fpscratch(asMasm()); + if (fromType == MIRType::Double) { + asMasm().loadConstantDouble(0.0, fpscratch); + } else { + asMasm().loadConstantFloat32(0.0f, fpscratch); + } + + if (isUnsigned) { + // If input < 0 or NaN, output = 0; else output = UINT32_MAX. + Label notNegOrNaN; + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleGreaterThanOrEqual, input, + fpscratch, ¬NegOrNaN); + } else { + asMasm().branchFloat(Assembler::DoubleGreaterThanOrEqual, input, + fpscratch, ¬NegOrNaN); + } + asMasm().move32(Imm32(0), output); + asMasm().jump(rejoin); + asMasm().bind(¬NegOrNaN); + asMasm().move32(Imm32(UINT32_MAX), output); + } else { + // Signed: NaN -> 0, negative overflow -> INT32_MIN, positive overflow -> + // INT32_MAX. The inline path branches here on the fctiwz exception + // before it stores anything, so every arm must write output. + Label notNaN, negative; + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleOrdered, input, input, ¬NaN); + } else { + asMasm().branchFloat(Assembler::DoubleOrdered, input, input, ¬NaN); + } + asMasm().move32(Imm32(0), output); + asMasm().jump(rejoin); + + asMasm().bind(¬NaN); + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleLessThan, input, fpscratch, + &negative); + } else { + asMasm().branchFloat(Assembler::DoubleLessThan, input, fpscratch, + &negative); + } + asMasm().move32(Imm32(INT32_MAX), output); + asMasm().jump(rejoin); + + asMasm().bind(&negative); + asMasm().move32(Imm32(INT32_MIN), output); + } + + MOZ_ASSERT(rejoin->bound()); + asMasm().jump(rejoin); + return; + } + + Label inputIsNaN; + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleUnordered, input, input, + &inputIsNaN); + } else { + asMasm().branchFloat(Assembler::DoubleUnordered, input, input, &inputIsNaN); + } + + asMasm().wasmTrap(wasm::Trap::IntegerOverflow, trapSiteDesc); + asMasm().bind(&inputIsNaN); + asMasm().wasmTrap(wasm::Trap::InvalidConversionToInteger, trapSiteDesc); +} + +void MacroAssemblerPPCCompat::outOfLineWasmTruncateToInt64Check( + FloatRegister input, Register64 output_, MIRType fromType, TruncFlags flags, + Label* rejoin, const wasm::TrapSiteDesc& trapSiteDesc) { + bool isUnsigned = flags & TRUNC_UNSIGNED; + bool isSaturating = flags & TRUNC_SATURATING; + + if (isSaturating) { + ScratchDoubleScope fpscratch(asMasm()); + + if (fromType == MIRType::Double) { + asMasm().loadConstantDouble(0.0, fpscratch); + } else { + asMasm().loadConstantFloat32(0.0f, fpscratch); + } + + if (isUnsigned) { + Label notNegOrNaN; + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleGreaterThanOrEqual, input, + fpscratch, ¬NegOrNaN); + } else { + asMasm().branchFloat(Assembler::DoubleGreaterThanOrEqual, input, + fpscratch, ¬NegOrNaN); + } + asMasm().move32(Imm32(0), output_.high); + asMasm().move32(Imm32(0), output_.low); + asMasm().jump(rejoin); + asMasm().bind(¬NegOrNaN); + asMasm().move32(Imm32(-1), output_.high); + asMasm().move32(Imm32(-1), output_.low); + } else { + // NaN -> 0, negative overflow -> INT64_MIN, positive overflow -> + // INT64_MAX. Every arm must write output; nothing pre-saturates it. + Label notNaN, negative; + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleOrdered, input, input, ¬NaN); + } else { + asMasm().branchFloat(Assembler::DoubleOrdered, input, input, ¬NaN); + } + asMasm().move32(Imm32(0), output_.high); + asMasm().move32(Imm32(0), output_.low); + asMasm().jump(rejoin); + + asMasm().bind(¬NaN); + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleLessThan, input, fpscratch, + &negative); + } else { + asMasm().branchFloat(Assembler::DoubleLessThan, input, fpscratch, + &negative); + } + asMasm().move32(Imm32(INT32_MAX), output_.high); + asMasm().move32(Imm32(-1), output_.low); + asMasm().jump(rejoin); + + asMasm().bind(&negative); + asMasm().move32(Imm32(INT32_MIN), output_.high); + asMasm().move32(Imm32(0), output_.low); + } + + MOZ_ASSERT(rejoin->bound()); + asMasm().jump(rejoin); + return; + } + + // There is no fctidz here, so the truncation is a builtin callout that + // returns 0x8000000000000000 both on overflow and for a legitimate INT64_MIN + // result. Re-test the input against the representable range and rejoin when + // the value was in fact in range. + Label inputIsNaN; + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleUnordered, input, input, + &inputIsNaN); + } else { + asMasm().branchFloat(Assembler::DoubleUnordered, input, input, &inputIsNaN); + } + + double minValue, maxValue; + Assembler::DoubleCondition minCond = Assembler::DoubleLessThanOrEqual; + Assembler::DoubleCondition maxCond = Assembler::DoubleGreaterThanOrEqual; + if (isUnsigned) { + minValue = -1; + maxValue = double(UINT64_MAX) + 1.0; + } else { + // No float or double value exists between INT64_MIN and INT64_MIN - 1.0, + // so INT64_MIN itself is the exclusive lower bound. + minValue = double(INT64_MIN); + minCond = Assembler::DoubleLessThan; + maxValue = double(INT64_MAX) + 1.0; + } + + Label fail; + { + ScratchDoubleScope fpscratch(asMasm()); + if (fromType == MIRType::Double) { + asMasm().loadConstantDouble(minValue, fpscratch); + asMasm().branchDouble(minCond, input, fpscratch, &fail); + asMasm().loadConstantDouble(maxValue, fpscratch); + asMasm().branchDouble(maxCond, input, fpscratch, &fail); + } else { + asMasm().loadConstantFloat32(float(minValue), fpscratch); + asMasm().branchFloat(minCond, input, fpscratch, &fail); + asMasm().loadConstantFloat32(float(maxValue), fpscratch); + asMasm().branchFloat(maxCond, input, fpscratch, &fail); + } + } + + // The input was in range, so the callout's result is the correct one. + asMasm().jump(rejoin); + + asMasm().bind(&fail); + asMasm().wasmTrap(wasm::Trap::IntegerOverflow, trapSiteDesc); + asMasm().bind(&inputIsNaN); + asMasm().wasmTrap(wasm::Trap::InvalidConversionToInteger, trapSiteDesc); +} + +void MacroAssembler::PopStackPtr() { + loadPtr(Address(StackPointer, 0), StackPointer); + adjustFrame(-int32_t(sizeof(intptr_t))); +} + +void MacroAssembler::patchSub32FromMemAndBranchIfNegative(CodeOffset offset, + Imm32 imm) { + int32_t val = imm.value; + MOZ_RELEASE_ASSERT(val >= 1 && val <= 127); + // The addi sits one instruction before the recorded offset. + Instruction* inst = + (Instruction*)m_buffer.getInst(BufferOffset(offset.offset() - 4)); + uint32_t instWord = inst->encode(); + uint32_t base = instWord & 0xffff0000; + inst->setData(base | (uint16_t)(-val & 0xffff)); +} + +void MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, + Register output, + bool isSaturating, + Label* oolEntry) { + ScratchDoubleScope fpscratch(asMasm()); + clearFPConversionException(); + as_fctiwz(fpscratch, input); + testFPConversionException(); + ma_b(SOBit, oolEntry); + allocStackScratch(); + as_stfd(fpscratch, StackPointer, StackScratchSlot); + as_lwz(output, StackPointer, StackScratchSlot + 4); + freeStackScratch(); +} + +void MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, + Register output, + bool isSaturating, + Label* oolEntry) { + // fctiwz only produces a signed int32, so split at 2^31: below that the + // signed conversion is exact, above it convert (input - 2^31) and add the + // bias back in the integer domain. + ScratchDoubleScope fpscratch(asMasm()); + Label small, done; + + as_fcmpu(input, input); + ma_b(DoubleUnordered, oolEntry); + loadConstantDouble(2147483648.0, fpscratch); + as_fcmpu(input, fpscratch); + ma_b(DoubleLessThan, &small); + { + as_fsub(fpscratch, input, fpscratch); + clearFPConversionException(); + as_fctiwz(fpscratch, fpscratch); + testFPConversionException(); + ma_b(SOBit, oolEntry); + allocStackScratch(); + as_stfd(fpscratch, StackPointer, StackScratchSlot); + as_lwz(output, StackPointer, StackScratchSlot + 4); + freeStackScratch(); + as_xoris(output, output, 0x8000); + jump(&done); + } + bind(&small); + { + clearFPConversionException(); + as_fctiwz(fpscratch, input); + testFPConversionException(); + ma_b(SOBit, oolEntry); + allocStackScratch(); + as_stfd(fpscratch, StackPointer, StackScratchSlot); + as_lwz(output, StackPointer, StackScratchSlot + 4); + freeStackScratch(); + as_cmpwi(output, 0); + ma_b(LessThan, oolEntry); + } + bind(&done); +} + +void MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, + Register output, + bool isSaturating, + Label* oolEntry) { + wasmTruncateDoubleToInt32(input, output, isSaturating, oolEntry); +} + +void MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, + Register output, + bool isSaturating, + Label* oolEntry) { + wasmTruncateDoubleToUInt32(input, output, isSaturating, oolEntry); +} + +void MacroAssembler::wasmTruncateDoubleToInt64( + FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, + Label* oolRejoin, FloatRegister tempDouble) { + MOZ_CRASH("NYI: wasm f64 -> i64 truncation (no fctidz on this target)"); +} + +void MacroAssembler::wasmTruncateFloat32ToInt64( + FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, + Label* oolRejoin, FloatRegister tempFloat) { + MOZ_CRASH("NYI: wasm f32 -> i64 truncation (no fctidz on this target)"); +} + +void MacroAssembler::wasmTruncateDoubleToUInt64( + FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, + Label* oolRejoin, FloatRegister tempDouble) { + MOZ_CRASH("NYI: wasm f64 -> u64 truncation (no fctiduz on this target)"); +} + +void MacroAssembler::wasmTruncateFloat32ToUInt64( + FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, + Label* oolRejoin, FloatRegister tempFloat) { + MOZ_CRASH("NYI: wasm f32 -> u64 truncation (no fctiduz on this target)"); +} + +void MacroAssemblerPPCCompat::profilerEnterFrame(Register framePtr, + Register scratch) { + asMasm().loadJSContext(scratch); + loadPtr(Address(scratch, offsetof(JSContext, profilingActivation_)), scratch); + storePtr(framePtr, + Address(scratch, JitActivation::offsetOfLastProfilingFrame())); + storePtr(ImmPtr(nullptr), + Address(scratch, JitActivation::offsetOfLastProfilingCallSite())); +} + +void MacroAssemblerPPCCompat::profilerExitFrame() { + jump(asMasm().runtime()->jitRuntime()->getProfilerExitFrameTail()); +} + +void MacroAssemblerPPCCompat::ma_mod_mask(Register src, Register dest, + Register hold, Register remain, + int32_t shift, Label* negZero) { + // Compute x % ((1< +static void CompareExchange(MacroAssembler& masm, + const wasm::MemoryAccessDesc* access, + Scalar::Type type, Synchronization sync, + const T& mem, Register oldval, Register newval, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register output) { + UseScratchRegisterScope temps(masm); + bool signExtend = Scalar::isSignedIntType(type); + unsigned nbytes = Scalar::byteSize(type); + + switch (nbytes) { + case 1: + case 2: + break; + case 4: + MOZ_ASSERT(valueTemp == InvalidReg); + MOZ_ASSERT(offsetTemp == InvalidReg); + MOZ_ASSERT(maskTemp == InvalidReg); + break; + default: + MOZ_CRASH(); + } + + Label again, end; + + Register addr = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + + if (nbytes == 4) { + Register scratch = temps.Acquire(); + masm.memoryBarrierBefore(sync); + + masm.bind(&again); + + if (access) { + masm.flushBuffer(); // see comment in wasmLoadImpl + masm.append(*access, wasm::TrapMachineInsn::Atomic, + FaultingCodeOffset(masm.currentOffset())); + } + + masm.as_lwarx(output, r0, addr); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // wasm atomic memory is little-endian; JS atomics are native order. + if (access) { + ReverseWord32(masm, scratch, output); + masm.xs_mr(output, scratch); + } +#endif + masm.ma_cmp(output, oldval, Assembler::NotEqual); + masm.ma_b(Assembler::NotEqual, &end); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access) { + ReverseWord32(masm, scratch, newval); + masm.as_stwcx(scratch, r0, addr); + } else { + masm.as_stwcx(newval, r0, addr); + } +#else + masm.as_stwcx(newval, r0, addr); +#endif + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); + masm.bind(&end); + return; + } + + // Sub-word: word-granularity reservation with shift and mask. + Register word = temps.Acquire(); + SetupSubWordAccess(masm, addr, nbytes, offsetTemp, maskTemp); + + masm.memoryBarrierBefore(sync); + masm.bind(&again); + + if (access) { + masm.flushBuffer(); // see comment in wasmLoadImpl + masm.append(*access, wasm::TrapMachineInsn::Atomic, + FaultingCodeOffset(masm.currentOffset())); + } + + masm.as_lwarx(word, r0, addr); + ExtractSubWord(masm, output, word, offsetTemp, nbytes, signExtend); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // A wasm halfword is stored little-endian; swap it into logical order. + if (access && nbytes == 2) { + ReverseHalf16(masm, valueTemp, output); + if (signExtend) { + masm.as_extsh(output, valueTemp); + } else { + masm.xs_mr(output, valueTemp); + } + } +#endif + masm.ma_cmp(output, oldval, Assembler::NotEqual); + masm.ma_b(Assembler::NotEqual, &end); + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access && nbytes == 2) { + ReverseHalf16(masm, valueTemp, newval); + } else { + masm.xs_mr(valueTemp, newval); + } +#else + masm.xs_mr(valueTemp, newval); +#endif + // r0 is safe as the insert scratch: it is non-allocatable and here only ever + // appears as the hardwired-zero RA operand of the reservation pair. + InsertSubWord(masm, word, valueTemp, offsetTemp, maskTemp, r0); + masm.as_stwcx(word, r0, addr); + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); + masm.bind(&end); +} + +template +static void AtomicExchange(MacroAssembler& masm, + const wasm::MemoryAccessDesc* access, + Scalar::Type type, Synchronization sync, + const T& mem, Register value, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + UseScratchRegisterScope temps(masm); + bool signExtend = Scalar::isSignedIntType(type); + unsigned nbytes = Scalar::byteSize(type); + + switch (nbytes) { + case 1: + case 2: + break; + case 4: + MOZ_ASSERT(valueTemp == InvalidReg); + MOZ_ASSERT(offsetTemp == InvalidReg); + MOZ_ASSERT(maskTemp == InvalidReg); + break; + default: + MOZ_CRASH(); + } + + Label again; + + Register addr = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + + if (nbytes == 4) { + Register scratch = temps.Acquire(); + masm.memoryBarrierBefore(sync); + masm.bind(&again); + + if (access) { + masm.flushBuffer(); // see comment in wasmLoadImpl + masm.append(*access, wasm::TrapMachineInsn::Atomic, + FaultingCodeOffset(masm.currentOffset())); + } + + masm.as_lwarx(output, r0, addr); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access) { + ReverseWord32(masm, scratch, value); + masm.as_stwcx(scratch, r0, addr); + } else { + masm.as_stwcx(value, r0, addr); + } +#else + masm.as_stwcx(value, r0, addr); +#endif + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access) { + ReverseWord32(masm, scratch, output); + masm.xs_mr(output, scratch); + } +#endif + return; + } + + Register word = temps.Acquire(); + SetupSubWordAccess(masm, addr, nbytes, offsetTemp, maskTemp); + + masm.memoryBarrierBefore(sync); + masm.bind(&again); + + if (access) { + masm.flushBuffer(); // see comment in wasmLoadImpl + masm.append(*access, wasm::TrapMachineInsn::Atomic, + FaultingCodeOffset(masm.currentOffset())); + } + + masm.as_lwarx(word, r0, addr); + ExtractSubWord(masm, output, word, offsetTemp, nbytes, signExtend); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access && nbytes == 2) { + ReverseHalf16(masm, valueTemp, output); + if (signExtend) { + masm.as_extsh(output, valueTemp); + } else { + masm.xs_mr(output, valueTemp); + } + ReverseHalf16(masm, valueTemp, value); + } else { + masm.xs_mr(valueTemp, value); + } +#else + masm.xs_mr(valueTemp, value); +#endif + // r0 is safe as the insert scratch: it is non-allocatable and here only ever + // appears as the hardwired-zero RA operand of the reservation pair. + InsertSubWord(masm, word, valueTemp, offsetTemp, maskTemp, r0); + masm.as_stwcx(word, r0, addr); + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); +} + +// Apply |op| to (a, b) leaving the result in |dest|. +static void EmitAtomicOp(MacroAssembler& masm, AtomicOp op, Register dest, + Register a, Register b) { + switch (op) { + case AtomicOp::Add: + masm.as_add(dest, a, b); + break; + case AtomicOp::Sub: + masm.as_subf(dest, b, a); + break; + case AtomicOp::And: + masm.as_and_(dest, a, b); + break; + case AtomicOp::Or: + masm.as_or_(dest, a, b); + break; + case AtomicOp::Xor: + masm.as_xor_(dest, a, b); + break; + default: + MOZ_CRASH(); + } +} + +template +static void AtomicFetchOp(MacroAssembler& masm, + const wasm::MemoryAccessDesc* access, + Scalar::Type type, Synchronization sync, AtomicOp op, + const T& mem, Register value, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + UseScratchRegisterScope temps(masm); + bool signExtend = Scalar::isSignedIntType(type); + unsigned nbytes = Scalar::byteSize(type); + + switch (nbytes) { + case 1: + case 2: + break; + case 4: + MOZ_ASSERT(valueTemp == InvalidReg); + MOZ_ASSERT(offsetTemp == InvalidReg); + MOZ_ASSERT(maskTemp == InvalidReg); + break; + default: + MOZ_CRASH(); + } + + Label again; + + Register addr = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + Register scratch = temps.Acquire(); + + if (nbytes == 4) { + masm.memoryBarrierBefore(sync); + masm.bind(&again); + + if (access) { + masm.flushBuffer(); // see comment in wasmLoadImpl + masm.append(*access, wasm::TrapMachineInsn::Atomic, + FaultingCodeOffset(masm.currentOffset())); + } + + masm.as_lwarx(output, r0, addr); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // wasm atomic memory is little-endian, but lwarx read it natively. + if (access) { + ReverseWord32(masm, scratch, output); + masm.xs_mr(output, scratch); + } +#endif + EmitAtomicOp(masm, op, scratch, output, value); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access) { + // r0 is not live here: at this call site it only ever appears as the + // hardwired-zero RA operand of the reservation pair. + ReverseWord32(masm, r0, scratch); + masm.xs_mr(scratch, r0); + } +#endif + masm.as_stwcx(scratch, r0, addr); + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); + return; + } + + SetupSubWordAccess(masm, addr, nbytes, offsetTemp, maskTemp); + + masm.memoryBarrierBefore(sync); + masm.bind(&again); + + if (access) { + masm.flushBuffer(); // see comment in wasmLoadImpl + masm.append(*access, wasm::TrapMachineInsn::Atomic, + FaultingCodeOffset(masm.currentOffset())); + } + + masm.as_lwarx(scratch, r0, addr); + ExtractSubWord(masm, output, scratch, offsetTemp, nbytes, signExtend); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access && nbytes == 2) { + ReverseHalf16(masm, valueTemp, output); + if (signExtend) { + masm.as_extsh(output, valueTemp); + } else { + masm.xs_mr(output, valueTemp); + } + } +#endif + EmitAtomicOp(masm, op, valueTemp, output, value); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access && nbytes == 2) { + ReverseHalf16(masm, r0, valueTemp); + masm.xs_mr(valueTemp, r0); + } +#endif + InsertSubWord(masm, scratch, valueTemp, offsetTemp, maskTemp, r0); + masm.as_stwcx(scratch, r0, addr); + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); +} + +template +static void AtomicEffectOp(MacroAssembler& masm, + const wasm::MemoryAccessDesc* access, + Scalar::Type type, Synchronization sync, AtomicOp op, + const T& mem, Register value, Register valueTemp, + Register offsetTemp, Register maskTemp) { + UseScratchRegisterScope temps(masm); + unsigned nbytes = Scalar::byteSize(type); + + switch (nbytes) { + case 1: + case 2: + break; + case 4: + MOZ_ASSERT(valueTemp == InvalidReg); + MOZ_ASSERT(offsetTemp == InvalidReg); + MOZ_ASSERT(maskTemp == InvalidReg); + break; + default: + MOZ_CRASH(); + } + + Label again; + + Register addr = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + Register word = temps.Acquire(); + + if (nbytes == 4) { + masm.memoryBarrierBefore(sync); + masm.bind(&again); + + if (access) { + masm.flushBuffer(); // see comment in wasmLoadImpl + masm.append(*access, wasm::TrapMachineInsn::Atomic, + FaultingCodeOffset(masm.currentOffset())); + } + + masm.as_lwarx(word, r0, addr); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // wasm atomic memory is little-endian; JS atomics are native order. r0 is + // safe as a working register between the reservation pair: it holds no + // live state here and the byte reversal touches no memory. + if (access) { + ReverseWord32(masm, r0, word); + masm.xs_mr(word, r0); + } +#endif + EmitAtomicOp(masm, op, word, word, value); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access) { + ReverseWord32(masm, r0, word); + masm.xs_mr(word, r0); + } +#endif + masm.as_stwcx(word, r0, addr); + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); + return; + } + + SetupSubWordAccess(masm, addr, nbytes, offsetTemp, maskTemp); + + masm.memoryBarrierBefore(sync); + masm.bind(&again); + + if (access) { + masm.flushBuffer(); // see comment in wasmLoadImpl + masm.append(*access, wasm::TrapMachineInsn::Atomic, + FaultingCodeOffset(masm.currentOffset())); + } + + masm.as_lwarx(word, r0, addr); + ExtractSubWord(masm, valueTemp, word, offsetTemp, nbytes, false); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access && nbytes == 2) { + ReverseHalf16(masm, r0, valueTemp); + masm.xs_mr(valueTemp, r0); + } +#endif + EmitAtomicOp(masm, op, valueTemp, valueTemp, value); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + if (access && nbytes == 2) { + ReverseHalf16(masm, r0, valueTemp); + masm.xs_mr(valueTemp, r0); + } +#endif + InsertSubWord(masm, word, valueTemp, offsetTemp, maskTemp, r0); + masm.as_stwcx(word, r0, addr); + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); +} + +// Public MacroAssembler methods. + +void MacroAssembler::compareExchange(Scalar::Type type, Synchronization sync, + const Address& mem, Register oldval, + Register newval, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + CompareExchange(*this, nullptr, type, sync, mem, oldval, newval, valueTemp, + offsetTemp, maskTemp, output); +} + +void MacroAssembler::compareExchange(Scalar::Type type, Synchronization sync, + const BaseIndex& mem, Register oldval, + Register newval, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + CompareExchange(*this, nullptr, type, sync, mem, oldval, newval, valueTemp, + offsetTemp, maskTemp, output); +} + +// 64-bit atomics need a doubleword reservation (ldarx/stdcx.), which this +// target does not have; a two-word lwarx/stwcx. sequence would not be atomic. +// AtomicOperations::isLockfree8() is therefore false, which makes +// AtomicsMeetsPreconditions() reject BigInt64 arrays -- those go to the VM and +// libatomic instead -- and disables wasm threads, so these stay unreachable. +void MacroAssembler::compareExchange64(Synchronization sync, const Address& mem, + Register64 expect, Register64 replace, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::compareExchange64(Synchronization sync, + const BaseIndex& mem, Register64 expect, + Register64 replace, Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::wasmCompareExchange(const wasm::MemoryAccessDesc& access, + const Address& mem, Register oldval, + Register newval, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + CompareExchange(*this, &access, access.type(), access.sync(), mem, oldval, + newval, valueTemp, offsetTemp, maskTemp, output); +} + +void MacroAssembler::wasmCompareExchange(const wasm::MemoryAccessDesc& access, + const BaseIndex& mem, Register oldval, + Register newval, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + CompareExchange(*this, &access, access.type(), access.sync(), mem, oldval, + newval, valueTemp, offsetTemp, maskTemp, output); +} + +void MacroAssembler::wasmCompareExchange64(const wasm::MemoryAccessDesc& access, + const Address& mem, + Register64 expect, + Register64 replace, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::wasmCompareExchange64(const wasm::MemoryAccessDesc& access, + const BaseIndex& mem, + Register64 expect, + Register64 replace, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicExchange(Scalar::Type type, Synchronization sync, + const Address& mem, Register value, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register output) { + AtomicExchange(*this, nullptr, type, sync, mem, value, valueTemp, offsetTemp, + maskTemp, output); +} + +void MacroAssembler::atomicExchange(Scalar::Type type, Synchronization sync, + const BaseIndex& mem, Register value, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register output) { + AtomicExchange(*this, nullptr, type, sync, mem, value, valueTemp, offsetTemp, + maskTemp, output); +} + +void MacroAssembler::atomicExchange64(Synchronization sync, const Address& mem, + Register64 value, Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicExchange64(Synchronization sync, + const BaseIndex& mem, Register64 value, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicLoad64(Synchronization sync, const Address& mem, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicLoad64(Synchronization sync, const BaseIndex& mem, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicStore64(Synchronization sync, const Address& mem, + Register64 value, Register64 temp) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicStore64(Synchronization sync, const BaseIndex& mem, + Register64 value, Register64 temp) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::wasmAtomicLoad64(const wasm::MemoryAccessDesc& access, + const Address& mem, Register64 temp, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::wasmAtomicLoad64(const wasm::MemoryAccessDesc& access, + const BaseIndex& mem, Register64 temp, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::wasmAtomicExchange(const wasm::MemoryAccessDesc& access, + const Address& mem, Register value, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register output) { + AtomicExchange(*this, &access, access.type(), access.sync(), mem, value, + valueTemp, offsetTemp, maskTemp, output); +} + +void MacroAssembler::wasmAtomicExchange(const wasm::MemoryAccessDesc& access, + const BaseIndex& mem, Register value, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register output) { + AtomicExchange(*this, &access, access.type(), access.sync(), mem, value, + valueTemp, offsetTemp, maskTemp, output); +} + +void MacroAssembler::wasmAtomicExchange64(const wasm::MemoryAccessDesc& access, + const Address& mem, Register64 src, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::wasmAtomicExchange64(const wasm::MemoryAccessDesc& access, + const BaseIndex& mem, Register64 src, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicFetchOp(Scalar::Type type, Synchronization sync, + AtomicOp op, Register value, + const Address& mem, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + AtomicFetchOp(*this, nullptr, type, sync, op, mem, value, valueTemp, + offsetTemp, maskTemp, output); +} + +void MacroAssembler::atomicFetchOp(Scalar::Type type, Synchronization sync, + AtomicOp op, Register value, + const BaseIndex& mem, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + AtomicFetchOp(*this, nullptr, type, sync, op, mem, value, valueTemp, + offsetTemp, maskTemp, output); +} + +void MacroAssembler::atomicFetchOp64(Synchronization sync, AtomicOp op, + Register64 value, const Address& mem, + Register64 temp, Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicFetchOp64(Synchronization sync, AtomicOp op, + Register64 value, const BaseIndex& mem, + Register64 temp, Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicEffectOp64(Synchronization sync, AtomicOp op, + Register64 value, const Address& mem, + Register64 temp) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::atomicEffectOp64(Synchronization sync, AtomicOp op, + Register64 value, const BaseIndex& mem, + Register64 temp) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::wasmAtomicFetchOp(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register value, + const Address& mem, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + AtomicFetchOp(*this, &access, access.type(), access.sync(), op, mem, value, + valueTemp, offsetTemp, maskTemp, output); +} + +void MacroAssembler::wasmAtomicFetchOp(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register value, + const BaseIndex& mem, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + AtomicFetchOp(*this, &access, access.type(), access.sync(), op, mem, value, + valueTemp, offsetTemp, maskTemp, output); +} + +void MacroAssembler::wasmAtomicFetchOp64(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register64 value, + const Address& mem, Register64 temp, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::wasmAtomicFetchOp64(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register64 value, + const BaseIndex& mem, Register64 temp, + Register64 output) { + MOZ_CRASH("NYI: 64-bit atomics (no ldarx/stdcx. on this target)"); +} + +void MacroAssembler::wasmAtomicEffectOp(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register value, + const Address& mem, Register valueTemp, + Register offsetTemp, + Register maskTemp) { + AtomicEffectOp(*this, &access, access.type(), access.sync(), op, mem, value, + valueTemp, offsetTemp, maskTemp); +} + +void MacroAssembler::wasmAtomicEffectOp(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register value, + const BaseIndex& mem, + Register valueTemp, Register offsetTemp, + Register maskTemp) { + AtomicEffectOp(*this, &access, access.type(), access.sync(), op, mem, value, + valueTemp, offsetTemp, maskTemp); +} + +// ======================================================================== +// JS atomic operations. + +template +static void CompareExchangeJS(MacroAssembler& masm, Scalar::Type arrayType, + Synchronization sync, const T& mem, + Register oldval, Register newval, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register temp, + AnyRegister output) { + if (arrayType == Scalar::Uint32) { + masm.compareExchange(arrayType, sync, mem, oldval, newval, valueTemp, + offsetTemp, maskTemp, temp); + masm.convertUInt32ToDouble(temp, output.fpu()); + } else { + masm.compareExchange(arrayType, sync, mem, oldval, newval, valueTemp, + offsetTemp, maskTemp, output.gpr()); + } +} + +template +static void AtomicExchangeJS(MacroAssembler& masm, Scalar::Type arrayType, + Synchronization sync, const T& mem, Register value, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register temp, + AnyRegister output) { + if (arrayType == Scalar::Uint32) { + masm.atomicExchange(arrayType, sync, mem, value, valueTemp, offsetTemp, + maskTemp, temp); + masm.convertUInt32ToDouble(temp, output.fpu()); + } else { + masm.atomicExchange(arrayType, sync, mem, value, valueTemp, offsetTemp, + maskTemp, output.gpr()); + } +} + +template +static void AtomicFetchOpJS(MacroAssembler& masm, Scalar::Type arrayType, + Synchronization sync, AtomicOp op, Register value, + const T& mem, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register temp, AnyRegister output) { + if (arrayType == Scalar::Uint32) { + masm.atomicFetchOp(arrayType, sync, op, value, mem, valueTemp, offsetTemp, + maskTemp, temp); + masm.convertUInt32ToDouble(temp, output.fpu()); + } else { + masm.atomicFetchOp(arrayType, sync, op, value, mem, valueTemp, offsetTemp, + maskTemp, output.gpr()); + } +} + +void MacroAssembler::compareExchangeJS(Scalar::Type arrayType, + Synchronization sync, const Address& mem, + Register oldval, Register newval, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register temp, + AnyRegister output) { + CompareExchangeJS(*this, arrayType, sync, mem, oldval, newval, valueTemp, + offsetTemp, maskTemp, temp, output); +} + +void MacroAssembler::compareExchangeJS(Scalar::Type arrayType, + Synchronization sync, + const BaseIndex& mem, Register oldval, + Register newval, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register temp, AnyRegister output) { + CompareExchangeJS(*this, arrayType, sync, mem, oldval, newval, valueTemp, + offsetTemp, maskTemp, temp, output); +} + +void MacroAssembler::atomicExchangeJS(Scalar::Type arrayType, + Synchronization sync, const Address& mem, + Register value, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register temp, AnyRegister output) { + AtomicExchangeJS(*this, arrayType, sync, mem, value, valueTemp, offsetTemp, + maskTemp, temp, output); +} + +void MacroAssembler::atomicExchangeJS(Scalar::Type arrayType, + Synchronization sync, + const BaseIndex& mem, Register value, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register temp, + AnyRegister output) { + AtomicExchangeJS(*this, arrayType, sync, mem, value, valueTemp, offsetTemp, + maskTemp, temp, output); +} + +void MacroAssembler::atomicFetchOpJS(Scalar::Type arrayType, + Synchronization sync, AtomicOp op, + Register value, const Address& mem, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register temp, + AnyRegister output) { + AtomicFetchOpJS(*this, arrayType, sync, op, value, mem, valueTemp, offsetTemp, + maskTemp, temp, output); +} + +void MacroAssembler::atomicFetchOpJS(Scalar::Type arrayType, + Synchronization sync, AtomicOp op, + Register value, const BaseIndex& mem, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register temp, + AnyRegister output) { + AtomicFetchOpJS(*this, arrayType, sync, op, value, mem, valueTemp, offsetTemp, + maskTemp, temp, output); +} + +void MacroAssembler::atomicEffectOpJS(Scalar::Type arrayType, + Synchronization sync, AtomicOp op, + Register value, const BaseIndex& mem, + Register valueTemp, Register offsetTemp, + Register maskTemp) { + AtomicEffectOp(*this, nullptr, arrayType, sync, op, mem, value, valueTemp, + offsetTemp, maskTemp); +} + +void MacroAssembler::atomicEffectOpJS(Scalar::Type arrayType, + Synchronization sync, AtomicOp op, + Register value, const Address& mem, + Register valueTemp, Register offsetTemp, + Register maskTemp) { + AtomicEffectOp(*this, nullptr, arrayType, sync, op, mem, value, valueTemp, + offsetTemp, maskTemp); +} + +// ======================================================================== +// Wasm address offset carry tests. + +void MacroAssemblerPPCCompat::ma_add32TestCarry(Condition cond, Register rd, + Register rs, Imm32 imm, + Label* overflow) { + MOZ_ASSERT(cond == Assembler::CarrySet || cond == Assembler::CarryClear); + if (rd != rs) { + asMasm().move32(rs, rd); + asMasm().add32(imm, rd); + as_cmplw(rd, rs); + } else { + // visitWasmAddOffset uses useRegisterAtStart, so the allocator may have + // collapsed rd onto rs; save rs before it is clobbered. + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + asMasm().move32(rs, scratch); + asMasm().add32(imm, rd); + as_cmplw(rd, scratch); + } + ma_b(cond == Assembler::CarrySet ? LessThan : GreaterThanOrEqual, overflow); +} + +void MacroAssemblerPPCCompat::ma_addPtrTestCarry(Condition cond, Register rd, + Register rs, ImmWord imm, + Label* overflow) { + ma_add32TestCarry(cond, rd, rs, Imm32(int32_t(uintptr_t(imm.value))), + overflow); +} + +// ======================================================================== +// Wasm load/store helpers. + +void MacroAssemblerPPCCompat::wasmProbeLastByte( + const wasm::MemoryAccessDesc& access, Register memoryBase, Register ptr) { + const unsigned size = Scalar::byteSize(access.type()); + if (size <= 1) { + return; + } + UseScratchRegisterScope temps(asMasm()); + Register probeAddr = temps.Acquire(); + // size is at most 16 (Simd128), well within the int16_t range of as_addi. + as_addi(probeAddr, ptr, int16_t(size - 1)); + // Record the probe as a wasm trap site so its SIGSEGV dispatches through + // the wasm signal handler the same way the real access would. + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsn::Load8, + FaultingCodeOffset(currentOffset())); + as_lbzx(probeAddr, memoryBase, probeAddr); +} + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +void MacroAssemblerPPCCompat::byteReverseSimd128(FloatRegister src, + FloatRegister dest) { + MOZ_ASSERT(src != ScratchSimd128Reg, "control in v0 would clobber src"); + UseScratchRegisterScope temps(*this); + Register zero = temps.Acquire(); + ScratchSimd128Scope ctl(asMasm()); + // lvsl with a 16-byte-aligned effective address yields {0,1,...,15}; its + // complement is {0xFF,0xFE,...,0xF0}, whose low five bits are 31..16 and so + // select VRB bytes 15..0 -- exactly the reversal. vperm reads all inputs + // before writing, so dest may alias src or the control vector. + xs_li(zero, 0); + as_lvsl(ctl, r0, zero); + as_vnor(ctl, ctl, ctl); + as_vperm(dest, src, src, ctl); +} +#endif + +void MacroAssemblerPPCCompat::wasmLoadImpl( + const wasm::MemoryAccessDesc& access, Register memoryBase, Register ptr, + Register ptrScratch, AnyRegister output) { + access.assertOffsetInGuardPages(); + uint32_t offset = access.offset32(); + MOZ_ASSERT_IF(offset, ptrScratch != InvalidReg); + + if (offset) { + asMasm().addPtr(ImmWord(offset), ptrScratch); + ptr = ptrScratch; + } + + wasmProbeLastByte(access, memoryBase, ptr); + + asMasm().memoryBarrierBefore(access.sync()); + + if (access.type() == Scalar::Simd128) { + MOZ_CRASH("NYI: wasm v128 load"); + } + + // Flush any pending constant pool entries before recording the trap site, + // otherwise a pool body inserted between the recorded offset and the + // emitted load shifts the load and leaves the pool guard branch at the + // recorded offset (SummarizeTrapInstruction then rejects the trap site). + // + // FP loads go through a GPR (wasm memory is little-endian and there is no + // byte-reversing FP load), so their faulting instruction is not the first + // one emitted; those record their trap site themselves. + bool deferTrapSite = + access.type() == Scalar::Float64 || access.type() == Scalar::Float32; + if (!deferTrapSite) { + m_buffer.flushPool(); + append(access, + wasm::TrapMachineInsnForLoad(Scalar::byteSize(access.type())), + FaultingCodeOffset(currentOffset())); + } + + switch (access.type()) { + case Scalar::Int8: + as_lbzx(output.gpr(), memoryBase, ptr); + as_extsb(output.gpr(), output.gpr()); + break; + case Scalar::Uint8: + as_lbzx(output.gpr(), memoryBase, ptr); + break; + case Scalar::Int16: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // wasm memory is little-endian; load byte-reversed then sign-extend. + as_lhbrx(output.gpr(), memoryBase, ptr); + as_extsh(output.gpr(), output.gpr()); +#else + as_lhax(output.gpr(), memoryBase, ptr); +#endif + break; + case Scalar::Uint16: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_lhbrx(output.gpr(), memoryBase, ptr); +#else + as_lhzx(output.gpr(), memoryBase, ptr); +#endif + break; + case Scalar::Int32: + case Scalar::Uint32: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_lwbrx(output.gpr(), memoryBase, ptr); +#else + as_lwzx(output.gpr(), memoryBase, ptr); +#endif + break; + case Scalar::Float64: { + UseScratchRegisterScope temps(asMasm()); + Register lo = temps.Acquire(); + Register hi = temps.Acquire(); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // wasm memory is little-endian: the first four bytes hold the low word. + // The access is two 32-bit loads, so record a 32-bit trap insn for the + // first half only; the explicit bounds check covers the whole access. + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForLoad(4), + FaultingCodeOffset(currentOffset())); + as_lwbrx(lo, memoryBase, ptr); + as_addi(hi, ptr, 4); + as_lwbrx(hi, memoryBase, hi); +#else + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForLoad(4), + FaultingCodeOffset(currentOffset())); + as_lwzx(hi, memoryBase, ptr); + as_addi(lo, ptr, 4); + as_lwzx(lo, memoryBase, lo); +#endif + moveGPRPairToDouble(hi, lo, output.fpu()); + break; + } + case Scalar::Float32: { + UseScratchRegisterScope temps(asMasm()); + Register bits = temps.Acquire(); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForLoad(4), + FaultingCodeOffset(currentOffset())); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_lwbrx(bits, memoryBase, ptr); +#else + as_lwzx(bits, memoryBase, ptr); +#endif + allocStackScratch(); + as_stw(bits, StackPointer, StackScratchSlot); + as_lfs(output.fpu(), StackPointer, StackScratchSlot); + freeStackScratch(); + break; + } + default: + MOZ_CRASH("unexpected array type"); + } + + asMasm().memoryBarrierAfter(access.sync()); +} + +void MacroAssemblerPPCCompat::wasmStoreImpl( + const wasm::MemoryAccessDesc& access, AnyRegister value, + Register memoryBase, Register ptr, Register ptrScratch) { + access.assertOffsetInGuardPages(); + uint32_t offset = access.offset32(); + MOZ_ASSERT_IF(offset, ptrScratch != InvalidReg); + + if (offset) { + asMasm().addPtr(ImmWord(offset), ptrScratch); + ptr = ptrScratch; + } + + wasmProbeLastByte(access, memoryBase, ptr); + + asMasm().memoryBarrierBefore(access.sync()); + + if (access.type() == Scalar::Simd128) { + MOZ_CRASH("NYI: wasm v128 store"); + } + + // See wasmLoadImpl for why the FP paths record their own trap site. + bool deferTrapSite = + access.type() == Scalar::Float64 || access.type() == Scalar::Float32; + if (!deferTrapSite) { + m_buffer.flushPool(); + append(access, + wasm::TrapMachineInsnForStore(Scalar::byteSize(access.type())), + FaultingCodeOffset(currentOffset())); + } + + switch (access.type()) { + case Scalar::Int8: + case Scalar::Uint8: + as_stbx(value.gpr(), memoryBase, ptr); + break; + case Scalar::Int16: + case Scalar::Uint16: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_sthbrx(value.gpr(), memoryBase, ptr); +#else + as_sthx(value.gpr(), memoryBase, ptr); +#endif + break; + case Scalar::Int32: + case Scalar::Uint32: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_stwbrx(value.gpr(), memoryBase, ptr); +#else + as_stwx(value.gpr(), memoryBase, ptr); +#endif + break; + case Scalar::Float64: { + UseScratchRegisterScope temps(asMasm()); + Register lo = temps.Acquire(); + Register hi = temps.Acquire(); + moveDoubleToGPRPair(value.fpu(), hi, lo); + // Two 32-bit stores: record a 32-bit trap insn for the first half only, + // which the explicit bounds check already covers for the whole access. + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(4), + FaultingCodeOffset(currentOffset())); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_stwbrx(lo, memoryBase, ptr); + as_addi(lo, ptr, 4); + as_stwbrx(hi, memoryBase, lo); +#else + as_stwx(hi, memoryBase, ptr); + as_addi(hi, ptr, 4); + as_stwx(lo, memoryBase, hi); +#endif + break; + } + case Scalar::Float32: { + UseScratchRegisterScope temps(asMasm()); + Register bits = temps.Acquire(); + allocStackScratch(); + as_stfs(value.fpu(), StackPointer, StackScratchSlot); + as_lwz(bits, StackPointer, StackScratchSlot); + freeStackScratch(); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(4), + FaultingCodeOffset(currentOffset())); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_stwbrx(bits, memoryBase, ptr); +#else + as_stwx(bits, memoryBase, ptr); +#endif + break; + } + default: + MOZ_CRASH("unexpected array type"); + } + + asMasm().memoryBarrierAfter(access.sync()); +} + +void MacroAssemblerPPCCompat::wasmLoadI64Impl( + const wasm::MemoryAccessDesc& access, Register memoryBase, Register ptr, + Register ptrScratch, Register64 output) { + uint32_t offset = access.offset32(); + MOZ_ASSERT_IF(offset, ptrScratch != InvalidReg); + + if (offset) { + asMasm().addPtr(ImmWord(offset), ptrScratch); + ptr = ptrScratch; + } + + wasmProbeLastByte(access, memoryBase, ptr); + + asMasm().memoryBarrierBefore(access.sync()); + m_buffer.flushPool(); // see comment in wasmLoadImpl + // An Int64 access is emitted as two 32-bit loads, so the recorded (first) + // instruction is a 32-bit one; the explicit bounds check covers the rest. + int loadBytes = int(Scalar::byteSize(access.type())); + if (loadBytes > 4) { + loadBytes = 4; + } + append(access, wasm::TrapMachineInsnForLoad(loadBytes), + FaultingCodeOffset(currentOffset())); + + switch (access.type()) { + case Scalar::Int8: + as_lbzx(output.low, memoryBase, ptr); + as_extsb(output.low, output.low); + as_srawi(output.high, output.low, 31); + break; + case Scalar::Uint8: + as_lbzx(output.low, memoryBase, ptr); + xs_li(output.high, 0); + break; + case Scalar::Int16: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_lhbrx(output.low, memoryBase, ptr); +#else + as_lhzx(output.low, memoryBase, ptr); +#endif + as_extsh(output.low, output.low); + as_srawi(output.high, output.low, 31); + break; + case Scalar::Uint16: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_lhbrx(output.low, memoryBase, ptr); +#else + as_lhzx(output.low, memoryBase, ptr); +#endif + xs_li(output.high, 0); + break; + case Scalar::Int32: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_lwbrx(output.low, memoryBase, ptr); +#else + as_lwzx(output.low, memoryBase, ptr); +#endif + as_srawi(output.high, output.low, 31); + break; + case Scalar::Uint32: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_lwbrx(output.low, memoryBase, ptr); +#else + as_lwzx(output.low, memoryBase, ptr); +#endif + xs_li(output.high, 0); + break; + case Scalar::Int64: { + UseScratchRegisterScope temps(asMasm()); + Register hiAddr = temps.Acquire(); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // wasm memory is little-endian: the first four bytes are the low word. + as_lwbrx(output.low, memoryBase, ptr); + as_addi(hiAddr, ptr, 4); + as_lwbrx(output.high, memoryBase, hiAddr); +#else + as_lwzx(output.low, memoryBase, ptr); + as_addi(hiAddr, ptr, 4); + as_lwzx(output.high, memoryBase, hiAddr); +#endif + break; + } + default: + MOZ_CRASH("unexpected array type"); + } + + asMasm().memoryBarrierAfter(access.sync()); +} + +void MacroAssemblerPPCCompat::wasmStoreI64Impl( + const wasm::MemoryAccessDesc& access, Register64 value, Register memoryBase, + Register ptr, Register ptrScratch) { + uint32_t offset = access.offset32(); + MOZ_ASSERT_IF(offset, ptrScratch != InvalidReg); + + if (offset) { + asMasm().addPtr(ImmWord(offset), ptrScratch); + ptr = ptrScratch; + } + + wasmProbeLastByte(access, memoryBase, ptr); + + asMasm().memoryBarrierBefore(access.sync()); + m_buffer.flushPool(); // see comment in wasmLoadImpl + // An Int64 access is emitted as two 32-bit stores; see wasmLoadI64Impl. + int storeBytes = int(Scalar::byteSize(access.type())); + if (storeBytes > 4) { + storeBytes = 4; + } + append(access, wasm::TrapMachineInsnForStore(storeBytes), + FaultingCodeOffset(currentOffset())); + + switch (access.type()) { + case Scalar::Int8: + case Scalar::Uint8: + as_stbx(value.low, memoryBase, ptr); + break; + case Scalar::Int16: + case Scalar::Uint16: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_sthbrx(value.low, memoryBase, ptr); +#else + as_sthx(value.low, memoryBase, ptr); +#endif + break; + case Scalar::Int32: + case Scalar::Uint32: +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_stwbrx(value.low, memoryBase, ptr); +#else + as_stwx(value.low, memoryBase, ptr); +#endif + break; + case Scalar::Int64: { + UseScratchRegisterScope temps(asMasm()); + Register hiAddr = temps.Acquire(); +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + as_stwbrx(value.low, memoryBase, ptr); + as_addi(hiAddr, ptr, 4); + as_stwbrx(value.high, memoryBase, hiAddr); +#else + as_stwx(value.low, memoryBase, ptr); + as_addi(hiAddr, ptr, 4); + as_stwx(value.high, memoryBase, hiAddr); +#endif + break; + } + default: + MOZ_CRASH("unexpected array type"); + } + + asMasm().memoryBarrierAfter(access.sync()); +} + +void MacroAssembler::wasmLoad(const wasm::MemoryAccessDesc& access, + Register memoryBase, Register ptr, + Register ptrScratch, AnyRegister output) { + wasmLoadImpl(access, memoryBase, ptr, ptrScratch, output); +} + +void MacroAssembler::wasmLoadI64(const wasm::MemoryAccessDesc& access, + Register memoryBase, Register ptr, + Register ptrScratch, Register64 output) { + wasmLoadI64Impl(access, memoryBase, ptr, ptrScratch, output); +} + +void MacroAssembler::wasmStore(const wasm::MemoryAccessDesc& access, + AnyRegister value, Register memoryBase, + Register ptr, Register ptrScratch) { + wasmStoreImpl(access, value, memoryBase, ptr, ptrScratch); +} + +void MacroAssembler::wasmStoreI64(const wasm::MemoryAccessDesc& access, + Register64 value, Register memoryBase, + Register ptr, Register ptrScratch) { + wasmStoreI64Impl(access, value, memoryBase, ptr, ptrScratch); +} + +//}}} check_macroassembler_style + +} // namespace jit +} // namespace js + +#ifdef ENABLE_WASM_SIMD +// static +bool MacroAssembler::MustMaskShiftCountSimd128(wasm::SimdOp op, int32_t* mask) { + return false; +} +#endif diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/MacroAssembler-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/MacroAssembler-ppc.h --- a/js/src/jit/ppc/MacroAssembler-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/MacroAssembler-ppc.h 2026-07-29 10:22:43.193502711 +0200 @@ -0,0 +1,1788 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_MacroAssembler_ppc_h +#define jit_ppc_MacroAssembler_ppc_h + +#include "jit/MoveResolver.h" +#include "jit/ppc/Assembler-ppc.h" +#include "wasm/WasmBuiltins.h" + +namespace js { +namespace jit { + +using js::wasm::FaultingCodeOffsetPair; + +inline bool is_intN(int64_t x, unsigned n) { + MOZ_ASSERT((0 < n) && (n < 64)); + int64_t limit = static_cast(1) << (n - 1); + return (-limit <= x) && (x < limit); +} + +inline bool is_uintN(uint64_t x, unsigned n) { + MOZ_ASSERT((0 < n) && (n < 64)); + return !(x >> n); +} + +// nunbox32 memory layout on a big-endian target: the tag word is at byte +// offset 0 of a JS::Value and the payload word at offset 4. This is the +// mirror image of the little-endian nunbox32 backends. + +// Worst case instruction count for the conditional long branch emitted by +// ma_b(): the three-instruction XER inspection an Overflow condition needs, +// the bc itself, and then the long branch stanza. +static constexpr size_t kNoPoolCondLongBranchInsns = + 3 + 1 + LongBranchInstructions; + +static constexpr Register CallReg = r12; + +struct ImmTag : public Imm32 { + explicit ImmTag(JSValueTag tag) : Imm32(tag) {} +}; + +struct ImmType : public ImmTag { + explicit ImmType(JSValueType type) : ImmTag(JSVAL_TYPE_TO_TAG(type)) {} +}; + +// On nunbox32 the tag already lives in its own register, so the "scratch tag" +// is just the ValueOperand's type register. See the documentation in +// MacroAssembler.h. +class ScratchTagScope { + const ValueOperand& v_; + + public: + ScratchTagScope(MacroAssembler&, const ValueOperand& v) : v_(v) {} + operator Register() { return v_.typeReg(); } + void release() {} + void reacquire() {} +}; + +class ScratchTagScopeRelease { + public: + explicit ScratchTagScopeRelease(ScratchTagScope*) {} +}; + +class MacroAssemblerPPC : public Assembler { + protected: + MacroAssembler& asMasm(); + const MacroAssembler& asMasm() const; + + public: + // Staging area for GPR <-> FPR transfers. The 32-bit SysV ABI has no red + // zone, so this is a frame reserved above SP with reserveStack(): word 0 is + // the back chain subFromStackPtr writes, so the payload starts at 8. + // StackScratchFrame2 is the two-doubleword form, for the few sequences that + // need StackScratchSlot and StackScratchSlot2 live at the same time. + static constexpr int32_t StackScratchFrame = 16; + static constexpr int32_t StackScratchFrame2 = 32; + static constexpr int16_t StackScratchSlot = 8; + static constexpr int16_t StackScratchSlot2 = 16; + + // GPR pair <-> FPR doubleword transfers. There is no mtvsrd/mfvsrd here and + // no 64-bit GPR, so the value round-trips through the scratch slot. Big + // endian: the high word is at the lower address. + void moveGPRPairToDouble(Register hi, Register lo, FloatRegister dest); + void moveDoubleToGPRPair(FloatRegister src, Register hi, Register lo); + + // dest = magnitude(magSrc) with the sign bit of signSrc. There is no + // fcpsgn on this target, so the sign is spliced in through the high words. + void emitCopySign(FloatRegister dest, FloatRegister signSrc, + FloatRegister magSrc); + + // Round src to an integral value converted to int32 in the low word of + // fpDest (the bit pattern fctiw* leaves in the FPR). There is no fctid, so + // everything that used to go through int64 now goes through fctiw under a + // temporarily-set FPSCR rounding mode. + enum FpIntRound { RoundNearest, RoundDown, RoundUp, RoundZero }; + void roundToInt32FPR(FloatRegister fpDest, FloatRegister src, FpIntRound m); + + // fctiw (X-form, opcode 63, XO 14) rounds per FPSCR[RN] rather than + // truncating. Assembler-ppc.h only exposes the fctiwz form, so encode it + // here; see the interface request in the port notes. + BufferOffset emitFctiw(FloatRegister rd, FloatRegister rs) { + return writeInst(0xFC000000u | + (uint32_t(rd.encoding() & 31) << 21) | + (uint32_t(rs.encoding() & 31) << 11) | (14u << 1)); + } + + // Reserve / release the staging frame above. These are raw SP adjustments + // rather than reserveStack()/freeStack(): they are always balanced inside a + // single instruction sequence, so framePushed() is deliberately left alone + // and the sequences stay usable where frame accounting is fixed. stwu keeps + // the SysV back chain valid at 0(sp). + void allocStackScratch(int32_t size = StackScratchFrame) { + as_stwu(StackPointer, StackPointer, int16_t(-size)); + } + void freeStackScratch(int32_t size = StackScratchFrame) { + as_addi(StackPointer, StackPointer, int16_t(size)); + } + + // Clear FPSCR[VXCVI] before a conversion, then test it afterwards; a set + // bit means the input was NaN or out of range. + void clearFPConversionException() { as_mtfsb0(23); } + void testFPConversionException() { as_mcrfs(cr0, 5); } +}; + +class MacroAssemblerPPCCompat : public MacroAssemblerPPC { + public: + using MacroAssemblerPPC::MacroAssemblerPPC; + + MacroAssemblerPPCCompat() {} + + bool buildOOLFakeExitFrame(void* fakeReturnAddr); + + // A wasm SymbolicAddress is a plain code address on this ABI (no function + // descriptors), so this is just an indirect call. + CodeOffset callWasmSymbolic(wasm::SymbolicAddress imm); + +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // Byte-reverse the 16 bytes of |src| into |dest|, converting between the + // raw native image of little-endian v128 memory and the canonical LE + // register layout. VMX only: lvsl builds {0..15}, vnor turns it into the + // descending selector {0xFF..0xF0} whose low five bits address VRB bytes + // 15..0. vperm reads all inputs before writing, so |dest| may alias |src| + // or the control register; |src| must not be v0. + void byteReverseSimd128(FloatRegister src, FloatRegister dest); +#endif + + // =============================================================== + // nunbox32 address helpers. Big endian: tag at +0, payload at +4. + + static Address ToPayload(const Address& a) { + return Address(a.base, a.offset + NUNBOX32_PAYLOAD_OFFSET); + } + static BaseIndex ToPayload(const BaseIndex& a) { + return BaseIndex(a.base, a.index, a.scale, + a.offset + NUNBOX32_PAYLOAD_OFFSET); + } + static Address ToType(const Address& a) { + return Address(a.base, a.offset + NUNBOX32_TYPE_OFFSET); + } + static BaseIndex ToType(const BaseIndex& a) { + return BaseIndex(a.base, a.index, a.scale, a.offset + NUNBOX32_TYPE_OFFSET); + } + // pushValue() pushes the payload first, so a subsequent SP-relative access + // to the tag has to step over the word already pushed. + static Address ToTypeAfterStackPush(const Address& a) { + if (a.base == StackPointer) { + return Address(a.base, a.offset + sizeof(void*) + NUNBOX32_TYPE_OFFSET); + } + return ToType(a); + } + // Retained spelling used elsewhere in the backend. + static Address valuePayload(const Address& a) { return ToPayload(a); } + static BaseIndex valuePayload(const BaseIndex& a) { return ToPayload(a); } + + // A 64-bit integer in memory: high word at the lower address. + static Address HighWordBE(const Address& a) { + return Address(a.base, a.offset); + } + static Address LowWordBE(const Address& a) { + return Address(a.base, a.offset + 4); + } + static BaseIndex HighWordBE(const BaseIndex& a) { + return BaseIndex(a.base, a.index, a.scale, a.offset); + } + static BaseIndex LowWordBE(const BaseIndex& a) { + return BaseIndex(a.base, a.index, a.scale, a.offset + 4); + } + + // =============================================================== + // Conversion functions + + void convertBoolToInt32(Register src, Register dest) { + as_rlwinm(dest, src, 0, 31, 31); + } + void subtractDoubleBias(FloatRegister dest, double bias); + 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) { + as_frsp(dest, src); + } + // No hardware FP16; SupportsFloat{64,32}To16() report false. + void convertDoubleToFloat16(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("Not supported for this target"); + } + void convertFloat16ToDouble(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("Not supported for this target"); + } + void convertFloat32ToFloat16(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("Not supported for this target"); + } + void convertFloat16ToFloat32(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("Not supported for this target"); + } + void convertInt32ToFloat16(Register src, FloatRegister dest) { + MOZ_CRASH("Not supported for this target"); + } + void convertDoubleToInt32(FloatRegister src, Register dest, Label* fail, + bool negativeZeroCheck = true); + void convertDoubleToPtr(FloatRegister src, Register dest, Label* fail, + bool negativeZeroCheck = true) { + convertDoubleToInt32(src, dest, fail, negativeZeroCheck); + } + void convertFloat32ToInt32(FloatRegister src, Register dest, Label* fail, + bool negativeZeroCheck = true); + void convertFloat32ToDouble(FloatRegister src, FloatRegister dest) { + // A PPC FPR always holds the FP64-equivalent representation of an FP32 + // value, so f64.promote_f32 only has to quiet a signalling NaN, which + // frsp does as a side effect of its (here identity) rounding. + as_frsp(dest, src); + } + void convertInt32ToFloat32(Register src, FloatRegister dest); + void convertInt32ToFloat32(const Address& src, FloatRegister dest); + + FaultingCodeOffset loadFloat16(const Address& addr, FloatRegister dest, + Register temp); + FaultingCodeOffset loadFloat16(const BaseIndex& src, FloatRegister dest, + Register temp); + + // =============================================================== + // Effective address computation + + void computeScaledAddress(const BaseIndex& address, Register dest) { + if (address.scale == TimesOne) { + as_add(dest, address.base, address.index); + } else if (dest != address.base && dest != address.index) { + x_slwi(dest, address.index, address.scale); + as_add(dest, address.base, dest); + } else { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + x_slwi(scratch, address.index, address.scale); + as_add(dest, address.base, scratch); + } + } + + void computeEffectiveAddress(const Address& address, Register dest) { + if (address.offset == 0) { + if (dest != address.base) { + xs_mr(dest, address.base); + } + } else if (is_intN(address.offset, 16)) { + as_addi(dest, address.base, address.offset); + } else { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(address.offset), scratch); + as_add(dest, address.base, scratch); + } + } + void computeEffectiveAddress(const BaseIndex& address, Register dest) { + computeScaledAddress(address, dest); + if (address.offset) { + if (is_intN(address.offset, 16)) { + as_addi(dest, dest, address.offset); + } else { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(address.offset), scratch); + as_add(dest, dest, scratch); + } + } + } + + // =============================================================== + // Move instructions + + void mov(Register src, Register dest) { xs_mr(dest, src); } + void mov(ImmWord imm, Register dest) { movePtr(imm, dest); } + void mov(ImmPtr imm, Register dest) { + mov(ImmWord(uintptr_t(imm.value)), dest); + } + + // Emit the fixed-size, patchable pointer materialisation (lis + ori) that + // every patcher in this backend rewrites in place. Pool flushes are + // inhibited so no pool body can be inserted mid-stanza. + BufferOffset emitLoadPtrStanza(Register dest, uintptr_t value) { + m_buffer.enterNoPool(PointerImmInstructions); + BufferOffset bo = writeInst(NopInst); + for (uint32_t i = 1; i < PointerImmInstructions; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + // On OOM only part of the stanza was reserved; writing it anyway would + // run past the buffer's backing store. + if (m_buffer.oom()) { + return bo; + } + WriteLoadPtrInstructions((Instruction*)editSrc(bo), dest, value); + return bo; + } + + // lis + ori, unconditionally: a fixed two-instruction Imm32 load for the + // places that need a known encoding rather than the shortest sequence. + void ma_li(Register dest, Imm32 imm) { + xs_lis(dest, (int16_t)((uint32_t)imm.value >> 16)); + as_ori(dest, dest, (uint16_t)imm.value); + } + + void mov(CodeLabel* label, Register dest) { + BufferOffset bo = emitLoadPtrStanza(dest, LabelBase::INVALID_OFFSET); + label->patchAt()->bind(bo.getOffset()); + label->setLinkMode(CodeLabel::MoveImmediate); + } + void mov(Register src, Address dest) { storePtr(src, dest); } + void mov(Address src, Register dest) { loadPtr(src, dest); } + + void move32(Imm32 imm, Register dest) { + if (is_intN(imm.value, 16)) { + xs_li(dest, (int16_t)imm.value); + } else if (is_uintN((uint32_t)imm.value, 16)) { + xs_li(dest, 0); + as_ori(dest, dest, (uint16_t)imm.value); + } else { + xs_lis(dest, (int16_t)((uint32_t)imm.value >> 16)); + if (imm.value & 0xffff) { + as_ori(dest, dest, (uint16_t)imm.value); + } + } + } + void move32(Register src, Register dest) { + if (src != dest) { + xs_mr(dest, src); + } + } + + void movePtr(Register src, Register dest) { + if (src != dest) { + xs_mr(dest, src); + } + } + void movePtr(ImmWord imm, Register dest) { + move32(Imm32(int32_t(uintptr_t(imm.value))), dest); + } + void movePtr(ImmPtr imm, Register dest) { + movePtr(ImmWord(uintptr_t(imm.value)), dest); + } + + // Load a 64-bit FPR constant from the inline constant pool. The patcher + // expands this to bcl/mflr/lfd, so it clobbers LR and SavedScratchRegister + // and must not be used inside an enterNoPool region. + BufferOffset loadFromPoolFloat64(FloatRegister dest, double value) { + uint32_t hint = (uint32_t(dest.encoding()) << 16) | + (uint32_t(PoolLoadFPR64) << 21) | 0xF0000000; + uint32_t inst[3] = {hint, NopInst, NopInst}; + return m_buffer.allocEntry(3, 2, (uint8_t*)inst, (uint8_t*)&value); + } + // Same shape as loadFromPoolFloat64; lfs widens the single to double in + // the FPR, so no follow-up conversion is needed. + BufferOffset loadFromPoolFloat32(FloatRegister dest, float value) { + uint32_t hint = (uint32_t(dest.encoding()) << 16) | + (uint32_t(PoolLoadFPR32) << 21) | 0xF0000000; + uint32_t inst[3] = {hint, NopInst, NopInst}; + return m_buffer.allocEntry(3, 1, (uint8_t*)inst, (uint8_t*)&value); + } + // Load a 128-bit SIMD constant from the inline constant pool: five slots + // for bcl/mflr/addi/lvx/nop. lvx clears the low four bits of the effective + // address, so the pool entry has to be 16-byte aligned; the entry payload + // is padded to a 16-byte multiple and PatchConstantPoolLoad asserts the + // final alignment. + BufferOffset loadFromPoolSimd128(FloatRegister dest, const SimdConstant& v) { + uint32_t hint = ((uint32_t(dest.encoding()) & 0x1F) << 16) | + (uint32_t(PoolLoadSimd128) << 21) | 0xF0000000; + uint32_t inst[5] = {hint, NopInst, NopInst, NopInst, NopInst}; +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // Store the constant byte-reversed so a raw lvx yields the canonical + // little-endian register layout with no runtime reversal. + uint8_t reversed[16]; + const uint8_t* raw = reinterpret_cast(v.bytes()); + for (size_t i = 0; i < 16; i++) { + reversed[i] = raw[15 - i]; + } + return m_buffer.allocEntry(5, 4, (uint8_t*)inst, reversed); +#else + return m_buffer.allocEntry(5, 4, (uint8_t*)inst, (uint8_t*)v.bytes()); +#endif + } + void movePtr(wasm::SymbolicAddress imm, Register dest) { + BufferOffset bo = emitLoadPtrStanza(dest, uintptr_t(-1)); + append(wasm::SymbolicAccess(CodeOffset(bo.getOffset()), imm)); + } + void movePtr(ImmGCPtr imm, Register dest) { + BufferOffset bo = emitLoadPtrStanza(dest, uintptr_t(imm.value)); + Assembler::writeDataRelocation(bo, imm); + } + + void moveFloat32(FloatRegister src, FloatRegister dest) { + if (src != dest) { + as_fmr(dest, src); + } + } + void moveDouble(FloatRegister src, FloatRegister dest) { + if (src != dest) { + as_fmr(dest, src); + } + } + + // =============================================================== + // Branch functions + + void branch(JitCode* c) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + m_buffer.enterNoPool(LongBranchInstructions); + BufferOffset bo = emitLoadPtrStanza(scratch, uintptr_t(c->raw())); + addPendingJump(bo, ImmPtr(c->raw()), RelocationKind::JITCODE); + xs_mtctr(scratch); + as_bctr(); + m_buffer.leaveNoPool(); + } + void branch(const Register reg) { + xs_mtctr(reg); + as_bctr(); + } + + void jump(Label* label) { + if (label->bound()) { + // Open the no-pool window BEFORE computing the displacement: + // enterNoPool() can itself flush a pending pool and advance + // currentOffset(), which would leave the branch short by the pool size. + m_buffer.enterNoPool(2); + int32_t offset = label->offset() - currentOffset(); + if (JOffImm26::IsInRange(offset)) { + as_b(offset); + writeInst(NopInst); + m_buffer.leaveNoPool(); + return; + } + m_buffer.leaveNoPool(); + m_buffer.enterNoPool(LongBranchInstructions); + BufferOffset bo = + emitLoadPtrStanza(SecondScratchReg, LabelBase::INVALID_OFFSET); + xs_mtctr(SecondScratchReg); + as_bctr(); + m_buffer.leaveNoPool(); + addLongJump(bo, BufferOffset(label->offset())); + return; + } + // Unbound label: trap-tagged stanza of LongBranchInstructions words. + m_buffer.enterNoPool(LongBranchInstructions); + BufferOffset bo = xs_trap_tagged(BTag); + writeInst(label->used() ? label->offset() : LabelBase::INVALID_OFFSET); + for (uint32_t i = 2; i < LongBranchInstructions; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + if (!oom()) { + label->use(bo.getOffset()); + } + } + void jump(Register reg) { + xs_mtctr(reg); + as_bctr(); + } + void jump(const Address& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + loadPtr(address, scratch); + xs_mtctr(scratch); + as_bctr(); + } + void jump(JitCode* code) { branch(code); } + void jump(ImmPtr ptr) { + m_buffer.enterNoPool(LongBranchInstructions); + BufferOffset bo = + emitLoadPtrStanza(SecondScratchReg, uintptr_t(ptr.value)); + addPendingJump(bo, ptr, RelocationKind::HARDCODED); + xs_mtctr(SecondScratchReg); + as_bctr(); + m_buffer.leaveNoPool(); + } + void jump(TrampolinePtr code) { jump(ImmPtr(code.value)); } + + // Conditional branch to label. Assumes a compare instruction has already + // been emitted that sets CR0. + template + void ma_b(CondT cond, Label* label) { + if constexpr (std::is_same_v) { + if (cond == Always) { + jump(label); + return; + } + } + if (label->bound()) { + // Same pool hazard as jump(). Budget: XER inspection (3) + cror (1) + + // bc (1) + nop (1). + m_buffer.enterNoPool(6); + // For DoubleCondition, as_bc emits a cror/crandc before the bc, which + // advances currentOffset() by one instruction. + int32_t crAdjust = 0; + if constexpr (std::is_same_v) { + crAdjust = -(int32_t)sizeof(uint32_t); + } + int32_t offset = label->offset() - currentOffset() + crAdjust; + if (BOffImm16::IsInRange(offset)) { + as_bc((int16_t)offset, cond); + writeInst(NopInst); + m_buffer.leaveNoPool(); + return; + } + m_buffer.leaveNoPool(); + m_buffer.enterNoPool(kNoPoolCondLongBranchInsns); + as_bc((int16_t)(LongBranchInstructions * sizeof(uint32_t) + 4), + InvertCondition(cond)); + BufferOffset boLoad = + emitLoadPtrStanza(SecondScratchReg, LabelBase::INVALID_OFFSET); + xs_mtctr(SecondScratchReg); + as_bctr(); + m_buffer.leaveNoPool(); + addLongJump(boLoad, BufferOffset(label->offset())); + return; + } + // Forward reference: bc over a BCTag stanza. + m_buffer.enterNoPool(kNoPoolCondLongBranchInsns); + as_bc((int16_t)(LongBranchInstructions * sizeof(uint32_t) + 4), + InvertCondition(cond)); + BufferOffset bo = xs_trap_tagged(BCTag); + writeInst(label->used() ? label->offset() : LabelBase::INVALID_OFFSET); + for (uint32_t i = 2; i < LongBranchInstructions; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + if (!oom()) { + label->use(bo.getOffset()); + } + } + + // Set dest = 1 if CR0 satisfies cond, else dest = 0. There is no isel or + // setbc on this target, so read CR0 and rotate the interesting bit down. + void ma_cmp_set(Register dest, Condition cond) { + uint8_t bit = crBit(cr0, cond); + as_mfocrf(dest, cr0); + as_rlwinm(dest, dest, bit + 1, 31, 31); + if ((uint32_t(cond) & BranchOptionMask) != BranchOnSet) { + as_xori(dest, dest, 1); + } + } + + void ma_cmp_set_dbl(Register dest, DoubleCondition cond); + + // Conditional move: if CR0 satisfies cond, dest = src. Branchless via a + // 0/-1 mask, since there is no isel. This uses both scratch registers, so a + // caller already holding one must use ma_cmp_move_clobber_src instead. + void ma_cmp_move(Register dest, Register src, Condition cond) { + UseScratchRegisterScope temps(*this); + Register mask = temps.Acquire(); + Register tmp = temps.Acquire(); + ma_cmp_set(mask, cond); + as_neg(mask, mask); + as_xor_(tmp, dest, src); + as_and_(tmp, tmp, mask); + as_xor_(dest, dest, tmp); + } + + // As above, but |src| is a dead temporary and is used as the second working + // register, so only one scratch is needed. + void ma_cmp_move_clobber_src(Register dest, Register src, Condition cond) { + UseScratchRegisterScope temps(*this); + Register mask = temps.Acquire(); + MOZ_ASSERT(mask != src && mask != dest); + ma_cmp_set(mask, cond); + as_neg(mask, mask); + as_xor_(src, dest, src); + as_and_(src, src, mask); + as_xor_(dest, dest, src); + } + + // If cond == 0, move src to dst; otherwise dst is unchanged. The only + // callers are wasm select, whose condition is a 32-bit value. + void moveIfZero(Register dst, Register src, Register cond) { + as_cmpwi(cond, 0); + ma_cmp_move(dst, src, Equal); + } + + void ma_add32TestCarry(Condition cond, Register rd, Register rs, Imm32 imm, + Label* overflow); + void ma_addPtrTestCarry(Condition cond, Register rd, Register rs, ImmWord imm, + Label* overflow); + + // Issue the correct compare instruction for the given condition. Returns + // the condition to use with ma_b or ma_cmp_set. |is32bit| is accepted for + // source compatibility; every compare is a word compare here. + Condition ma_cmp(Register lhs, Register rhs, Condition cond, + bool is32bit = true) { + Condition base = + static_cast(cond & ~(ConditionUnsigned | ConditionZero)); + bool isUnsigned = (cond & ConditionUnsigned) != 0; + // ConditionZero-flagged conditions (Signed, NotSigned, Zero, NonZero) + // test a single register against zero. + if ((cond & ConditionZero) != 0) { + as_cmpwi(lhs, 0); + return base; + } + if (isUnsigned) { + as_cmplw(lhs, rhs); + } else { + as_cmpw(lhs, rhs); + } + return base; + } + + Condition ma_cmp(Register lhs, Imm32 rhs, Condition cond, + bool is32bit = true) { + Condition base = + static_cast(cond & ~(ConditionUnsigned | ConditionZero)); + bool isUnsigned = (cond & ConditionUnsigned) != 0; + if (isUnsigned) { + if (is_uintN((uint32_t)rhs.value, 16)) { + as_cmplwi(lhs, rhs.value); + return base; + } + } else { + if (is_intN(rhs.value, 16)) { + as_cmpwi(lhs, rhs.value); + return base; + } + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + move32(rhs, scratch); + return ma_cmp(lhs, scratch, cond); + } + + Condition ma_cmp(Register lhs, ImmWord rhs, Condition cond) { + return ma_cmp(lhs, Imm32(int32_t(uintptr_t(rhs.value))), cond); + } + + Condition ma_cmp(Register lhs, ImmPtr rhs, Condition cond) { + return ma_cmp(lhs, ImmWord(uintptr_t(rhs.value)), cond); + } + + Condition ma_cmp(Register lhs, ImmGCPtr rhs, Condition cond) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(rhs, scratch); + return ma_cmp(lhs, scratch, cond); + } + + Condition ma_cmp(Register lhs, ImmTag rhs, Condition cond) { + return ma_cmp(lhs, Imm32(rhs.value), cond); + } + + // Compare a tag register against an ImmTag constant and branch, WITHOUT + // acquiring a scratch register. Uses xoris+cmplwi, which MODIFIES tagReg, + // so it is only safe when tagReg is owned by the caller. + void branchTestTag(Condition cond, Register tagReg, ImmTag tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + uint32_t t = tag.value; + as_xoris(tagReg, tagReg, t >> 16); + as_cmplwi(tagReg, t & 0xFFFF); + ma_b(cond, label); + } + + void ma_mod_mask(Register src, Register dest, Register hold, Register remain, + int32_t shift, Label* negZero = nullptr); + + void nop() { writeInst(NopInst); } + void breakpoint(uint32_t value = 0) { xs_trap(); } + + inline void retn(Imm32 n); + + // =============================================================== + // Stack operations + + void push(Imm32 imm) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + move32(imm, scratch); + push(scratch); + } + void push(ImmWord imm) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(imm, scratch); + push(scratch); + } + void push(ImmGCPtr imm) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(imm, scratch); + push(scratch); + } + void push(const Address& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + loadPtr(address, scratch); + push(scratch); + } + // stwu reads the source register before writing the base back, so this also + // does the right thing for push(StackPointer): the word stored is the value + // the stack pointer had before the decrement, which is what shared code + // doing masm.push(masm.getStackPointer()) expects. + void push(Register reg) { as_stwu(reg, StackPointer, -4); } + void push(FloatRegister reg) { + // stfdu/stfsu fuse the SP decrement with the FP store. + if (reg.isSingle()) { + as_stfsu(reg, StackPointer, -8); + } else { + as_stfdu(reg, StackPointer, -8); + } + } + void pop(Register reg) { + as_lwz(reg, StackPointer, 0); + as_addi(StackPointer, StackPointer, 4); + } + void pop(FloatRegister reg) { + if (reg.isSingle()) { + as_lfs(reg, StackPointer, 0); + } else { + as_lfd(reg, StackPointer, 0); + } + as_addi(StackPointer, StackPointer, 8); + } + + CodeOffset pushWithPatch(ImmWord imm) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + CodeOffset offset = movWithPatch(imm, scratch); + push(scratch); + return offset; + } + CodeOffset movWithPatch(ImmWord imm, Register dest) { + BufferOffset bo = emitLoadPtrStanza(dest, uintptr_t(imm.value)); + return CodeOffset(bo.getOffset()); + } + CodeOffset movWithPatch(ImmPtr imm, Register dest) { + return movWithPatch(ImmWord(uintptr_t(imm.value)), dest); + } + + // =============================================================== + // Tag/unbox operations + + void splitTag(const ValueOperand& value, Register dest) { + if (dest != value.typeReg()) { + xs_mr(dest, value.typeReg()); + } + } + void splitTagForTest(const ValueOperand& value, ScratchTagScope& tag) { + MOZ_ASSERT(value.typeReg() == Register(tag)); + } + + // =============================================================== + // Tag tests + + Condition testInt32(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + return ma_cmp(tag, ImmTag(JSVAL_TAG_INT32), cond); + } + Condition testBoolean(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + return ma_cmp(tag, ImmTag(JSVAL_TAG_BOOLEAN), cond); + } + Condition testNull(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + return ma_cmp(tag, ImmTag(JSVAL_TAG_NULL), cond); + } + Condition testUndefined(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + return ma_cmp(tag, ImmTag(JSVAL_TAG_UNDEFINED), cond); + } + Condition testString(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + return ma_cmp(tag, ImmTag(JSVAL_TAG_STRING), cond); + } + Condition testSymbol(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + return ma_cmp(tag, ImmTag(JSVAL_TAG_SYMBOL), cond); + } + Condition testBigInt(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + return ma_cmp(tag, ImmTag(JSVAL_TAG_BIGINT), cond); + } + Condition testObject(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + return ma_cmp(tag, ImmTag(JSVAL_TAG_OBJECT), cond); + } + Condition testMagic(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + return ma_cmp(tag, ImmTag(JSVAL_TAG_MAGIC), cond); + } + // A double is any tag below JSVAL_TAG_CLEAR, so this is an unsigned + // compare whose result condition is Below / AboveOrEqual. + Condition testDouble(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_cmp(tag, ImmTag(JSVAL_TAG_CLEAR), Above); + return cond == Equal ? Below : AboveOrEqual; + } + Condition testNumber(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_cmp(tag, ImmTag(JS::detail::ValueUpperInclNumberTag), Above); + return cond == Equal ? BelowOrEqual : Above; + } + Condition testPrimitive(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_cmp(tag, ImmTag(JS::detail::ValueUpperExclPrimitiveTag), Above); + return cond == Equal ? Below : AboveOrEqual; + } + Condition testGCThing(Condition cond, Register tag) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + ma_cmp(tag, ImmTag(JS::detail::ValueLowerInclGCThingTag), Above); + return cond == Equal ? AboveOrEqual : Below; + } + Condition testError(Condition cond, Register tag) { + return testMagic(cond, tag); + } + + Condition testInt32(Condition cond, const ValueOperand& value) { + return testInt32(cond, value.typeReg()); + } + Condition testBoolean(Condition cond, const ValueOperand& value) { + return testBoolean(cond, value.typeReg()); + } + Condition testDouble(Condition cond, const ValueOperand& value) { + return testDouble(cond, value.typeReg()); + } + Condition testNull(Condition cond, const ValueOperand& value) { + return testNull(cond, value.typeReg()); + } + Condition testUndefined(Condition cond, const ValueOperand& value) { + return testUndefined(cond, value.typeReg()); + } + Condition testString(Condition cond, const ValueOperand& value) { + return testString(cond, value.typeReg()); + } + Condition testSymbol(Condition cond, const ValueOperand& value) { + return testSymbol(cond, value.typeReg()); + } + Condition testBigInt(Condition cond, const ValueOperand& value) { + return testBigInt(cond, value.typeReg()); + } + Condition testObject(Condition cond, const ValueOperand& value) { + return testObject(cond, value.typeReg()); + } + Condition testNumber(Condition cond, const ValueOperand& value) { + return testNumber(cond, value.typeReg()); + } + Condition testMagic(Condition cond, const ValueOperand& value) { + return testMagic(cond, value.typeReg()); + } + Condition testPrimitive(Condition cond, const ValueOperand& value) { + return testPrimitive(cond, value.typeReg()); + } + Condition testGCThing(Condition cond, const ValueOperand& value) { + return testGCThing(cond, value.typeReg()); + } + + template + Condition testInt32Impl(Condition cond, const T& t) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + return testInt32(cond, extractTag(t, scratch)); + } + + Condition testInt32(Condition cond, const Address& address); + Condition testBoolean(Condition cond, const Address& address); + Condition testDouble(Condition cond, const Address& address); + Condition testNull(Condition cond, const Address& address); + Condition testUndefined(Condition cond, const Address& address); + Condition testString(Condition cond, const Address& address); + Condition testSymbol(Condition cond, const Address& address); + Condition testBigInt(Condition cond, const Address& address); + Condition testObject(Condition cond, const Address& address); + Condition testNumber(Condition cond, const Address& address); + Condition testMagic(Condition cond, const Address& address); + Condition testGCThing(Condition cond, const Address& address); + + Condition testInt32(Condition cond, const BaseIndex& src); + Condition testBoolean(Condition cond, const BaseIndex& src); + Condition testDouble(Condition cond, const BaseIndex& src); + Condition testNull(Condition cond, const BaseIndex& src); + Condition testUndefined(Condition cond, const BaseIndex& src); + Condition testString(Condition cond, const BaseIndex& src); + Condition testSymbol(Condition cond, const BaseIndex& src); + Condition testBigInt(Condition cond, const BaseIndex& src); + Condition testObject(Condition cond, const BaseIndex& src); + Condition testMagic(Condition cond, const BaseIndex& src); + Condition testGCThing(Condition cond, const BaseIndex& src); + + // Treat the value as a boolean and set CR0 accordingly. + Condition testInt32Truthy(bool truthy, const ValueOperand& operand) { + as_cmpwi(operand.payloadReg(), 0); + return truthy ? NotEqual : Equal; + } + Condition testBooleanTruthy(bool truthy, const ValueOperand& operand) { + as_cmpwi(operand.payloadReg(), 0); + return truthy ? NotEqual : Equal; + } + DoubleCondition testDoubleTruthy(bool truthy, FloatRegister reg); + Condition testStringTruthy(bool truthy, const ValueOperand& value); + Condition testBigIntTruthy(bool truthy, const ValueOperand& value); + + // =============================================================== + // Unboxing + + void unboxNonDouble(const ValueOperand& operand, Register dest, + JSValueType type) { + MOZ_ASSERT(type != JSVAL_TYPE_DOUBLE); + if (operand.payloadReg() != dest) { + xs_mr(dest, operand.payloadReg()); + } + } + void unboxNonDouble(const Address& src, Register dest, JSValueType type) { + MOZ_ASSERT(type != JSVAL_TYPE_DOUBLE); + load32(ToPayload(src), dest); + } + void unboxNonDouble(const BaseIndex& src, Register dest, JSValueType type) { + MOZ_ASSERT(type != JSVAL_TYPE_DOUBLE); + load32(ToPayload(src), dest); + } + void unboxGCThingForGCBarrier(const Address& src, Register dest) { + load32(ToPayload(src), dest); + } + void unboxGCThingForGCBarrier(const ValueOperand& src, Register dest) { + if (src.payloadReg() != dest) { + xs_mr(dest, src.payloadReg()); + } + } + void unboxWasmAnyRefGCThingForGCBarrier(const Address& src, Register dest) { + static_assert(wasm::AnyRef::TagShift == 2); + // src is a raw wasm::AnyRef word, not a Value: no payload offset. + load32(src, dest); + as_rlwinm(dest, dest, 0, 0, 31 - wasm::AnyRef::TagShift); + } + void getGCThingValueChunk(const Address& src, Register dest) { + static_assert(js::gc::ChunkShift == 20); + load32(ToPayload(src), dest); + as_rlwinm(dest, dest, 0, 0, 31 - js::gc::ChunkShift); + } + void getGCThingValueChunk(const ValueOperand& src, Register dest) { + static_assert(js::gc::ChunkShift == 20); + as_rlwinm(dest, src.payloadReg(), 0, 0, 31 - js::gc::ChunkShift); + } + + void boxDouble(FloatRegister src, const ValueOperand& dest, FloatRegister) { + moveDoubleToGPRPair(src, dest.typeReg(), dest.payloadReg()); + } + void boxNonDouble(JSValueType type, Register src, const ValueOperand& dest); + void boxNonDouble(Register type, Register src, const ValueOperand& dest); + + void unboxInt32(const ValueOperand& operand, Register dest) { + unboxNonDouble(operand, dest, JSVAL_TYPE_INT32); + } + void unboxInt32(const Address& src, Register dest) { + load32(ToPayload(src), dest); + } + void unboxInt32(const BaseIndex& src, Register dest) { + load32(ToPayload(src), dest); + } + void unboxBoolean(const ValueOperand& operand, Register dest) { + unboxNonDouble(operand, dest, JSVAL_TYPE_BOOLEAN); + } + void unboxBoolean(const Address& src, Register dest) { + load32(ToPayload(src), dest); + } + void unboxBoolean(const BaseIndex& src, Register dest) { + load32(ToPayload(src), dest); + } + void unboxDouble(const ValueOperand& operand, FloatRegister dest) { + moveGPRPairToDouble(operand.typeReg(), operand.payloadReg(), dest); + } + void unboxDouble(const Address& src, FloatRegister dest) { + loadDouble(src, dest); + } + void unboxDouble(const BaseIndex& src, FloatRegister dest) { + loadDouble(src, dest); + } + void unboxString(const ValueOperand& operand, Register dest) { + unboxNonDouble(operand, dest, JSVAL_TYPE_STRING); + } + void unboxString(const Address& src, Register dest) { + unboxNonDouble(src, dest, JSVAL_TYPE_STRING); + } + void unboxSymbol(const ValueOperand& operand, Register dest) { + unboxNonDouble(operand, dest, JSVAL_TYPE_SYMBOL); + } + void unboxSymbol(const Address& src, Register dest) { + unboxNonDouble(src, dest, JSVAL_TYPE_SYMBOL); + } + void unboxBigInt(const ValueOperand& operand, Register dest) { + unboxNonDouble(operand, dest, JSVAL_TYPE_BIGINT); + } + void unboxBigInt(const Address& src, Register dest) { + unboxNonDouble(src, dest, JSVAL_TYPE_BIGINT); + } + void unboxObject(const ValueOperand& src, Register dest) { + unboxNonDouble(src, dest, JSVAL_TYPE_OBJECT); + } + void unboxObject(const Address& src, Register dest) { + unboxNonDouble(src, dest, JSVAL_TYPE_OBJECT); + } + void unboxObject(const BaseIndex& src, Register dest) { + unboxNonDouble(src, dest, JSVAL_TYPE_OBJECT); + } + void unboxValue(const ValueOperand& src, AnyRegister dest, JSValueType type); + void unboxObjectOrNull(const Address& src, Register dest) { + // null carries a zero payload, so the payload word is already either a + // valid pointer or zero. + load32(ToPayload(src), dest); + } + + void tagValue(JSValueType type, Register payload, ValueOperand dest) { + boxNonDouble(type, payload, dest); + } + + // =============================================================== + // Value store/load/push/pop + + void storeValue(ValueOperand val, const Address& dest) { + store32(val.payloadReg(), ToPayload(dest)); + store32(val.typeReg(), ToType(dest)); + } + void storeValue(ValueOperand val, const BaseIndex& dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(dest, scratch); + storeValue(val, Address(scratch, dest.offset)); + } + void storeValue(JSValueType type, Register reg, Address dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + store32(reg, ToPayload(dest)); + move32(ImmTag(JSVAL_TYPE_TO_TAG(type)), scratch); + store32(scratch, ToType(dest)); + } + void storeValue(JSValueType type, Register reg, BaseIndex dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(dest, scratch); + storeValue(type, reg, Address(scratch, dest.offset)); + } + void storeValue(const Value& val, Address dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(dest.base != scratch); + if (val.isGCThing()) { + movePtr(ImmGCPtr(val.toGCThing()), scratch); + } else { + move32(Imm32(val.toNunboxPayload()), scratch); + } + store32(scratch, ToPayload(dest)); + move32(Imm32(val.toNunboxTag()), scratch); + store32(scratch, ToType(dest)); + } + void storeValue(const Value& val, BaseIndex dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(dest, scratch); + storeValue(val, Address(scratch, dest.offset)); + } + void storeValue(const Address& src, const Address& dest, Register temp) { + load32(ToType(src), temp); + store32(temp, ToType(dest)); + load32(ToPayload(src), temp); + store32(temp, ToPayload(dest)); + } + + void storePrivateValue(Register src, const Address& dest) { + store32(Imm32(0), ToType(dest)); + store32(src, ToPayload(dest)); + } + void storePrivateValue(ImmGCPtr imm, const Address& dest) { + store32(Imm32(0), ToType(dest)); + storePtr(imm, ToPayload(dest)); + } + + void loadValue(const Address& src, ValueOperand val) { + // Do not clobber the base register before it has been used. + if (src.base == val.typeReg()) { + load32(ToPayload(src), val.payloadReg()); + load32(ToType(src), val.typeReg()); + } else { + load32(ToType(src), val.typeReg()); + load32(ToPayload(src), val.payloadReg()); + } + } + void loadValue(const BaseIndex& src, ValueOperand val) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + loadValue(Address(scratch, src.offset), val); + } + void loadUnalignedValue(const Address& src, ValueOperand dest) { + loadValue(src, dest); + } + + // Big endian: the tag has to end up at the lower address, so the payload + // is pushed first. + void pushValue(ValueOperand val) { + push(val.payloadReg()); + push(val.typeReg()); + } + void popValue(ValueOperand val) { + pop(val.typeReg()); + pop(val.payloadReg()); + } + void pushValue(const Value& val) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + if (val.isGCThing()) { + movePtr(ImmGCPtr(val.toGCThing()), scratch); + } else { + move32(Imm32(val.toNunboxPayload()), scratch); + } + push(scratch); + move32(Imm32(val.toNunboxTag()), scratch); + push(scratch); + } + void pushValue(JSValueType type, Register reg) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + push(reg); + move32(ImmTag(JSVAL_TYPE_TO_TAG(type)), scratch); + push(scratch); + } + void pushValue(const Address& addr) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + load32(ToPayload(addr), scratch); + push(scratch); + load32(ToTypeAfterStackPush(addr), scratch); + push(scratch); + } + void pushValue(const BaseIndex& addr, Register scratch) { + computeEffectiveAddress(addr, scratch); + pushValue(Address(scratch, 0)); + } + + // =============================================================== + // Load instructions + + FaultingCodeOffset load8SignExtend(const Address& address, Register dest) { + FaultingCodeOffset fco; + if (is_intN(address.offset, 16)) { + fco = FaultingCodeOffset( + as_lbz(dest, address.base, address.offset).getOffset()); + } else { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(address.offset), scratch); + fco = + FaultingCodeOffset(as_lbzx(dest, address.base, scratch).getOffset()); + } + as_extsb(dest, dest); + return fco; + } + FaultingCodeOffset load8SignExtend(const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + FaultingCodeOffset fco; + if (is_intN(src.offset, 16)) { + fco = FaultingCodeOffset(as_lbz(dest, scratch, src.offset).getOffset()); + } else { + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(src.offset), dest); + fco = FaultingCodeOffset(as_lbzx(dest, scratch, dest).getOffset()); + } + as_extsb(dest, dest); + return fco; + } + FaultingCodeOffset load8ZeroExtend(const Address& address, Register dest) { + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_lbz(dest, address.base, address.offset).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(address.offset), scratch); + return FaultingCodeOffset(as_lbzx(dest, address.base, scratch).getOffset()); + } + FaultingCodeOffset load8ZeroExtend(const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + if (is_intN(src.offset, 16)) { + return FaultingCodeOffset(as_lbz(dest, scratch, src.offset).getOffset()); + } + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(src.offset), dest); + return FaultingCodeOffset(as_lbzx(dest, scratch, dest).getOffset()); + } + FaultingCodeOffset load16SignExtend(const Address& address, Register dest) { + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_lha(dest, address.base, address.offset).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(address.offset), scratch); + return FaultingCodeOffset(as_lhax(dest, address.base, scratch).getOffset()); + } + FaultingCodeOffset load16SignExtend(const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + if (is_intN(src.offset, 16)) { + return FaultingCodeOffset(as_lha(dest, scratch, src.offset).getOffset()); + } + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(src.offset), dest); + return FaultingCodeOffset(as_lhax(dest, scratch, dest).getOffset()); + } + template + void load16UnalignedSignExtend(const S& src, Register dest) { + load16SignExtend(src, dest); + } + FaultingCodeOffset load16ZeroExtend(const Address& address, Register dest) { + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_lhz(dest, address.base, address.offset).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(address.offset), scratch); + return FaultingCodeOffset(as_lhzx(dest, address.base, scratch).getOffset()); + } + FaultingCodeOffset load16ZeroExtend(const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + if (is_intN(src.offset, 16)) { + return FaultingCodeOffset(as_lhz(dest, scratch, src.offset).getOffset()); + } + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(src.offset), dest); + return FaultingCodeOffset(as_lhzx(dest, scratch, dest).getOffset()); + } + template + void load16UnalignedZeroExtend(const S& src, Register dest) { + load16ZeroExtend(src, dest); + } + + FaultingCodeOffset load32(const Address& address, Register dest) { + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_lwz(dest, address.base, address.offset).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(address.offset), scratch); + return FaultingCodeOffset(as_lwzx(dest, address.base, scratch).getOffset()); + } + FaultingCodeOffset load32(const BaseIndex& address, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(address, scratch); + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_lwz(dest, scratch, address.offset).getOffset()); + } + MOZ_ASSERT(scratch != dest); + movePtr(ImmWord(address.offset), dest); + return FaultingCodeOffset(as_lwzx(dest, scratch, dest).getOffset()); + } + void load32(AbsoluteAddress address, Register dest) { + movePtr(ImmWord((uintptr_t)address.addr), dest); + as_lwz(dest, dest, 0); + } + void load32(wasm::SymbolicAddress address, Register dest) { + movePtr(address, dest); + as_lwz(dest, dest, 0); + } + template + void load32Unaligned(const S& src, Register dest) { + load32(src, dest); + } + + FaultingCodeOffsetPair load64(const Address& address, Register64 dest) { + FaultingCodeOffset fco1, fco2; + if (address.base == dest.high) { + fco1 = load32(LowWordBE(address), dest.low); + fco2 = load32(HighWordBE(address), dest.high); + } else { + fco1 = load32(HighWordBE(address), dest.high); + fco2 = load32(LowWordBE(address), dest.low); + } + return FaultingCodeOffsetPair(fco1, fco2); + } + FaultingCodeOffsetPair load64(const BaseIndex& address, Register64 dest) { + // If you run into this, relax your register allocation constraints. + MOZ_RELEASE_ASSERT( + !((address.base == dest.low || address.base == dest.high) && + (address.index == dest.low || address.index == dest.high))); + FaultingCodeOffset fco1, fco2; + if (address.base == dest.high || address.index == dest.high) { + fco1 = load32(LowWordBE(address), dest.low); + fco2 = load32(HighWordBE(address), dest.high); + } else { + fco1 = load32(HighWordBE(address), dest.high); + fco2 = load32(LowWordBE(address), dest.low); + } + return FaultingCodeOffsetPair(fco1, fco2); + } + template + void load64Unaligned(const S& src, Register64 dest) { + load64(src, dest); + } + + FaultingCodeOffset loadPtr(const Address& address, Register dest) { + return load32(address, dest); + } + FaultingCodeOffset loadPtr(const BaseIndex& src, Register dest) { + return load32(src, dest); + } + void loadPtr(AbsoluteAddress address, Register dest) { + load32(address, dest); + } + void loadPtr(wasm::SymbolicAddress address, Register dest) { + load32(address, dest); + } + + void loadPrivate(const Address& address, Register dest) { + loadPtr(ToPayload(address), dest); + } + + FaultingCodeOffset loadDouble(const Address& addr, FloatRegister dest) { + if (is_intN(addr.offset, 16)) { + return FaultingCodeOffset( + as_lfd(dest, addr.base, addr.offset).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(ImmWord(addr.offset), scratch); + return FaultingCodeOffset(as_lfdx(dest, addr.base, scratch).getOffset()); + } + FaultingCodeOffset loadDouble(const BaseIndex& src, FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + if (is_intN(src.offset, 16)) { + return FaultingCodeOffset(as_lfd(dest, scratch, src.offset).getOffset()); + } + Register scratch2 = temps.Acquire(); + movePtr(ImmWord(src.offset), scratch2); + return FaultingCodeOffset(as_lfdx(dest, scratch, scratch2).getOffset()); + } + FaultingCodeOffset loadFloat32(const Address& addr, FloatRegister dest) { + if (is_intN(addr.offset, 16)) { + return FaultingCodeOffset( + as_lfs(dest, addr.base, addr.offset).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(ImmWord(addr.offset), scratch); + return FaultingCodeOffset(as_lfsx(dest, addr.base, scratch).getOffset()); + } + FaultingCodeOffset loadFloat32(const BaseIndex& src, FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + if (is_intN(src.offset, 16)) { + return FaultingCodeOffset(as_lfs(dest, scratch, src.offset).getOffset()); + } + Register scratch2 = temps.Acquire(); + movePtr(ImmWord(src.offset), scratch2); + return FaultingCodeOffset(as_lfsx(dest, scratch, scratch2).getOffset()); + } + + // FP constants come from the inline constant pool. The stanza seeds its base + // register from LR with bcl/mflr, so LR has to be preserved: IC stubs and + // any other code reached with a live return address return through blr, and + // would otherwise branch back into the stanza and spin forever. + void loadConstantDouble(double dp, FloatRegister dest) { + xs_mflr(r0); + loadFromPoolFloat64(dest, dp); + xs_mtlr(r0); + } + void loadConstantFloat32(float f, FloatRegister dest) { + xs_mflr(r0); + loadFromPoolFloat32(dest, f); + xs_mtlr(r0); + } + + void notBoolean(const ValueOperand& val) { + as_xori(val.payloadReg(), val.payloadReg(), 1); + } + + [[nodiscard]] Register extractTag(const Address& address, Register scratch) { + load32(ToType(address), scratch); + return scratch; + } + [[nodiscard]] Register extractTag(const BaseIndex& address, + Register scratch) { + computeScaledAddress(address, scratch); + return extractTag(Address(scratch, address.offset), scratch); + } + [[nodiscard]] Register extractTag(const ValueOperand& value, + Register scratch) { + return value.typeReg(); + } + + [[nodiscard]] Register extractObject(const Address& address, + Register scratch) { + load32(ToPayload(address), scratch); + return scratch; + } + [[nodiscard]] Register extractObject(const ValueOperand& value, + Register scratch) { + return value.payloadReg(); + } + [[nodiscard]] Register extractInt32(const ValueOperand& value, + Register scratch) { + return value.payloadReg(); + } + [[nodiscard]] Register extractString(const ValueOperand& value, + Register scratch) { + return value.payloadReg(); + } + [[nodiscard]] Register extractSymbol(const ValueOperand& value, + Register scratch) { + return value.payloadReg(); + } + [[nodiscard]] Register extractBoolean(const ValueOperand& value, + Register scratch) { + return value.payloadReg(); + } + + void testObjectSet(Condition cond, const ValueOperand& value, Register dest) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = testObject(cond, value); + ma_cmp_set(dest, c); + } + void testUndefinedSet(Condition cond, const ValueOperand& value, + Register dest) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = testUndefined(cond, value); + ma_cmp_set(dest, c); + } + void testNullSet(Condition cond, const ValueOperand& value, Register dest) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition c = testNull(cond, value); + ma_cmp_set(dest, c); + } + + BufferOffset ret() { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_lwz(scratch, StackPointer, 0); + as_addi(StackPointer, StackPointer, 4); + xs_mtlr(scratch); + return as_blr(); + } + + void j(Label* dest) { jump(dest); } + + void getWasmAnyRefGCThingChunk(Register anyref, Register dest) { + static_assert(js::gc::ChunkShift == 20); + as_rlwinm(dest, anyref, 0, 0, 31 - js::gc::ChunkShift); + } + + void loadUnboxedValue(const Address& address, MIRType type, + AnyRegister dest) { + if (dest.isFloat()) { + loadInt32OrDouble(address, dest.fpu()); + } else { + load32(ToPayload(address), dest.gpr()); + } + } + void loadUnboxedValue(const BaseIndex& address, MIRType type, + AnyRegister dest) { + if (dest.isFloat()) { + loadInt32OrDouble(address, dest.fpu()); + } else { + load32(ToPayload(address), dest.gpr()); + } + } + + void loadInt32OrDouble(const Address& src, FloatRegister dest); + void loadInt32OrDouble(const BaseIndex& addr, FloatRegister dest); + + // =============================================================== + // Store instructions + + FaultingCodeOffset store8(Register src, const Address& address) { + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_stb(src, address.base, address.offset).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(ImmWord(address.offset), scratch); + return FaultingCodeOffset(as_stbx(src, address.base, scratch).getOffset()); + } + FaultingCodeOffset store8(Register src, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(address, scratch); + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_stb(src, scratch, address.offset).getOffset()); + } + Register scratch2 = temps.Acquire(); + movePtr(ImmWord(address.offset), scratch2); + return FaultingCodeOffset(as_stbx(src, scratch, scratch2).getOffset()); + } + void store8(Imm32 imm, const Address& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + move32(imm, scratch); + store8(scratch, address); + } + void store8(Imm32 imm, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + move32(imm, scratch); + store8(scratch, address); + } + + FaultingCodeOffset store16(Register src, const Address& address) { + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_sth(src, address.base, address.offset).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(ImmWord(address.offset), scratch); + return FaultingCodeOffset(as_sthx(src, address.base, scratch).getOffset()); + } + FaultingCodeOffset store16(Register src, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(address, scratch); + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_sth(src, scratch, address.offset).getOffset()); + } + Register scratch2 = temps.Acquire(); + movePtr(ImmWord(address.offset), scratch2); + return FaultingCodeOffset(as_sthx(src, scratch, scratch2).getOffset()); + } + void store16(Imm32 imm, const Address& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + move32(imm, scratch); + store16(scratch, address); + } + void store16(Imm32 imm, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + move32(imm, scratch); + store16(scratch, address); + } + template + void store16Unaligned(Register src, const T& dest) { + store16(src, dest); + } + + FaultingCodeOffset store32(Register src, const Address& address) { + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_stw(src, address.base, address.offset).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(ImmWord(address.offset), scratch); + return FaultingCodeOffset(as_stwx(src, address.base, scratch).getOffset()); + } + FaultingCodeOffset store32(Register src, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(address, scratch); + if (is_intN(address.offset, 16)) { + return FaultingCodeOffset( + as_stw(src, scratch, address.offset).getOffset()); + } + Register scratch2 = temps.Acquire(); + movePtr(ImmWord(address.offset), scratch2); + return FaultingCodeOffset(as_stwx(src, scratch, scratch2).getOffset()); + } + void store32(Register src, AbsoluteAddress address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(ImmWord((uintptr_t)address.addr), scratch); + as_stw(src, scratch, 0); + } + void store32(Imm32 src, const Address& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + move32(src, scratch); + store32(scratch, address); + } + void store32(Imm32 src, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + move32(src, scratch); + store32(scratch, address); + } + template + void store32Unaligned(Register src, const T& dest) { + store32(src, dest); + } + + void store64(Imm64 imm, Address address) { + store32(imm.hi(), HighWordBE(address)); + store32(imm.low(), LowWordBE(address)); + } + void store64(Imm64 imm, const BaseIndex& address) { + store32(imm.hi(), HighWordBE(address)); + store32(imm.low(), LowWordBE(address)); + } + FaultingCodeOffsetPair store64(Register64 src, Address address) { + FaultingCodeOffset fco1 = store32(src.high, HighWordBE(address)); + FaultingCodeOffset fco2 = store32(src.low, LowWordBE(address)); + return FaultingCodeOffsetPair(fco1, fco2); + } + FaultingCodeOffsetPair store64(Register64 src, const BaseIndex& address) { + FaultingCodeOffset fco1 = store32(src.high, HighWordBE(address)); + FaultingCodeOffset fco2 = store32(src.low, LowWordBE(address)); + return FaultingCodeOffsetPair(fco1, fco2); + } + template + void store64Unaligned(Register64 src, const T& dest) { + store64(src, dest); + } + + template + void storePtr(ImmWord imm, T address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(imm, scratch); + storePtr(scratch, address); + } + template + void storePtr(ImmPtr imm, T address) { + storePtr(ImmWord(uintptr_t(imm.value)), address); + } + template + void storePtr(ImmGCPtr imm, T address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(imm, scratch); + storePtr(scratch, address); + } + void storePtr(Register src, AbsoluteAddress dest) { + store32(src, dest); + } + FaultingCodeOffset storePtr(Register src, const Address& address) { + return store32(src, address); + } + FaultingCodeOffset storePtr(Register src, const BaseIndex& address) { + return store32(src, address); + } + + // =============================================================== + // Misc + + void handleFailureWithHandlerTail(Label* profilerExitTail, Label* bailoutTail, + uint32_t* returnValueCheckOffset); + + inline void incrementInt32Value(const Address& addr); + + void zeroDouble(FloatRegister reg) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + xs_li(scratch, 0); + allocStackScratch(); + as_stw(scratch, StackPointer, StackScratchSlot); + as_stw(scratch, StackPointer, StackScratchSlot + 4); + as_lfd(reg, StackPointer, StackScratchSlot); + freeStackScratch(); + } + + void writeCodePointer(CodeLabel* label) { + label->patchAt()->bind(currentOffset()); + label->setLinkMode(CodeLabel::RawPointer); + m_buffer.ensureSpace(sizeof(void*)); + writeInst(-1); + } + void writeDataRelocation(const Value& val) { + if (val.isGCThing()) { + gc::Cell* cell = val.toGCThing(); + if (cell && gc::IsInsideNursery(cell)) { + embedsNurseryPointers_ = true; + } + dataRelocations_.writeUnsigned(currentOffset()); + } + } + void writeDataRelocation(CodeOffset off, const Value& val) { + if (val.isGCThing()) { + gc::Cell* cell = val.toGCThing(); + if (cell && gc::IsInsideNursery(cell)) { + embedsNurseryPointers_ = true; + } + dataRelocations_.writeUnsigned(off.offset()); + } + } + + CodeOffset toggledJump(Label* label) { + CodeOffset ret(nextOffset().getOffset()); + jump(label); + return ret; + } + CodeOffset toggledCall(JitCode* target, bool enabled); + static size_t ToggledCallSize(uint8_t* code) { + return LongBranchInstructions * sizeof(uint32_t); + } + + void checkStackAlignment() {} + + static void calculateAlignedStackPointer(void** stackPointer) { + *stackPointer = reinterpret_cast((uintptr_t(*stackPointer)) & + ~(ABIStackAlignment - 1)); + } + + void lea(Operand addr, Register dest) { + MOZ_CRASH("PPC: lea not supported; use computeEffectiveAddress"); + } + + void abiret() { as_blr(); } + + void profilerEnterFrame(Register framePtr, Register scratch); + void profilerExitFrame(); + + void outOfLineWasmTruncateToInt32Check( + FloatRegister input, Register output, MIRType fromType, TruncFlags flags, + Label* rejoin, const wasm::TrapSiteDesc& trapSiteDesc); + void outOfLineWasmTruncateToInt64Check( + FloatRegister input, Register64 output, MIRType fromType, + TruncFlags flags, Label* rejoin, const wasm::TrapSiteDesc& trapSiteDesc); + + void wasmLoadImpl(const wasm::MemoryAccessDesc& access, Register memoryBase, + Register ptr, Register ptrScratch, AnyRegister output); + void wasmStoreImpl(const wasm::MemoryAccessDesc& access, AnyRegister value, + Register memoryBase, Register ptr, Register ptrScratch); + void wasmLoadI64Impl(const wasm::MemoryAccessDesc& access, + Register memoryBase, Register ptr, Register ptrScratch, + Register64 output); + void wasmStoreI64Impl(const wasm::MemoryAccessDesc& access, Register64 value, + Register memoryBase, Register ptr, Register ptrScratch); + + // Last-byte probing load to enforce wasm-spec atomicity for multi-byte + // wasm accesses. POWER permits an unaligned page-spanning access to commit + // one half before the other half takes a DSI, but wasm requires atomicity. + // Touching the last byte of the upcoming access first turns that into a + // SIGSEGV (and hence a wasm trap) before the real access executes; POWER's + // precise-interrupt model guarantees the access never architecturally runs + // if the probe faults. + // + // Wasm linear memory is one contiguous mapped region followed by an + // mprotect'd guard, so last-byte-mapped implies all-bytes-mapped. + // + // Skipped for single-byte accesses, and never called on the atomic path: + // atomics are naturally aligned per wasm spec and ISA-enforced lwarx + // alignment, so they cannot span pages. + void wasmProbeLastByte(const wasm::MemoryAccessDesc& access, + Register memoryBase, Register ptr); +}; + +typedef MacroAssemblerPPCCompat MacroAssemblerSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_MacroAssembler_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/MoveEmitter-ppc.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/MoveEmitter-ppc.cpp --- a/js/src/jit/ppc/MoveEmitter-ppc.cpp 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/MoveEmitter-ppc.cpp 2026-07-28 19:28:57.231056300 +0200 @@ -0,0 +1,355 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/ppc/MoveEmitter-ppc.h" + +#include "jit/MacroAssembler-inl.h" + +using namespace js; +using namespace js::jit; + +void MoveEmitterPPC::breakCycle(const MoveOperand& from, + const MoveOperand& to, MoveOp::Type type, + uint32_t slotId) { + switch (type) { + case MoveOp::FLOAT32: + if (to.isMemory()) { + ScratchFloat32Scope fpscratch32(masm); + masm.loadFloat32(getAdjustedAddress(to), fpscratch32); + masm.storeFloat32(fpscratch32, cycleSlot(slotId)); + } else { + masm.storeFloat32(to.floatReg(), cycleSlot(slotId)); + } + break; + case MoveOp::DOUBLE: + if (to.isMemory()) { + ScratchDoubleScope fpscratch64(masm); + masm.loadDouble(getAdjustedAddress(to), fpscratch64); + masm.storeDouble(fpscratch64, cycleSlot(slotId)); + } else { + masm.storeDouble(to.floatReg(), cycleSlot(slotId)); + } + break; + case MoveOp::INT32: + if (to.isMemory()) { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.load32(getAdjustedAddress(to), scratch); + masm.store32(scratch, cycleSlot(0)); + } else { + masm.store32(to.reg(), cycleSlot(0)); + } + break; + case MoveOp::GENERAL: + if (to.isMemory()) { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.loadPtr(getAdjustedAddress(to), scratch); + masm.storePtr(scratch, cycleSlot(0)); + } else { + masm.storePtr(to.reg(), cycleSlot(0)); + } + break; + case MoveOp::SIMD128: + if (to.isMemory()) { + ScratchSimd128Scope scratch(masm); + masm.loadUnalignedSimd128(getAdjustedAddress(to), scratch); + masm.storeUnalignedSimd128(scratch, cycleSlot(slotId)); + } else { + masm.storeUnalignedSimd128(to.floatReg(), cycleSlot(slotId)); + } + break; + default: + MOZ_CRASH("Unexpected move type"); + } +} + +void MoveEmitterPPC::completeCycle(const MoveOperand& from, + const MoveOperand& to, MoveOp::Type type, + uint32_t slotId) { + switch (type) { + case MoveOp::FLOAT32: + if (to.isMemory()) { + ScratchFloat32Scope fpscratch32(masm); + masm.loadFloat32(cycleSlot(slotId), fpscratch32); + masm.storeFloat32(fpscratch32, getAdjustedAddress(to)); + } else { + masm.loadFloat32(cycleSlot(slotId), to.floatReg()); + } + break; + case MoveOp::DOUBLE: + if (to.isMemory()) { + ScratchDoubleScope fpscratch64(masm); + masm.loadDouble(cycleSlot(slotId), fpscratch64); + masm.storeDouble(fpscratch64, getAdjustedAddress(to)); + } else { + masm.loadDouble(cycleSlot(slotId), to.floatReg()); + } + break; + case MoveOp::INT32: + MOZ_ASSERT(slotId == 0); + if (to.isMemory()) { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.load32(cycleSlot(0), scratch); + masm.store32(scratch, getAdjustedAddress(to)); + } else { + masm.load32(cycleSlot(0), to.reg()); + } + break; + case MoveOp::GENERAL: + MOZ_ASSERT(slotId == 0); + if (to.isMemory()) { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.loadPtr(cycleSlot(0), scratch); + masm.storePtr(scratch, getAdjustedAddress(to)); + } else { + masm.loadPtr(cycleSlot(0), to.reg()); + } + break; + case MoveOp::SIMD128: + if (to.isMemory()) { + ScratchSimd128Scope scratch(masm); + masm.loadUnalignedSimd128(cycleSlot(slotId), scratch); + masm.storeUnalignedSimd128(scratch, getAdjustedAddress(to)); + } else { + masm.loadUnalignedSimd128(cycleSlot(slotId), to.floatReg()); + } + break; + default: + MOZ_CRASH("Unexpected move type"); + } +} + +void MoveEmitterPPC::emit(const MoveResolver& moves) { + if (moves.numCycles()) { + // SpillSlotSize must be wide enough for the widest cycled value + // (SIMD128 = 16 bytes). The stride below assumes the same. See + // Architecture-ppc.h for the rationale. + static_assert(SpillSlotSize == 16); + masm.reserveStack(moves.numCycles() * SpillSlotSize); + pushedAtCycle_ = masm.framePushed(); + } + + for (size_t i = 0; i < moves.numMoves(); i++) { + emit(moves.getMove(i)); + } +} + +Address MoveEmitterPPC::cycleSlot(uint32_t slot, uint32_t subslot) const { + int32_t offset = masm.framePushed() - pushedAtCycle_; + // Stride must match the per-cycle reservation in emit(); using a + // narrower stride causes adjacent SIMD128 slots to overlap. + return Address(StackPointer, offset + slot * SpillSlotSize + subslot); +} + +int32_t MoveEmitterPPC::getAdjustedOffset(const MoveOperand& operand) { + MOZ_ASSERT(operand.isMemoryOrEffectiveAddress()); + if (operand.base() != StackPointer) { + return operand.disp(); + } + + return operand.disp() + masm.framePushed() - pushedAtStart_; +} + +Address MoveEmitterPPC::getAdjustedAddress(const MoveOperand& operand) { + return Address(operand.base(), getAdjustedOffset(operand)); +} + +void MoveEmitterPPC::emitMove(const MoveOperand& from, + const MoveOperand& to) { + if (from.isGeneralReg()) { + if (to.isGeneralReg()) { + masm.movePtr(from.reg(), to.reg()); + } else if (to.isMemory()) { + masm.storePtr(from.reg(), getAdjustedAddress(to)); + } else { + MOZ_CRASH("Invalid emitMove arguments."); + } + } else if (from.isMemory()) { + if (to.isGeneralReg()) { + masm.loadPtr(getAdjustedAddress(from), to.reg()); + } else if (to.isMemory()) { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.loadPtr(getAdjustedAddress(from), scratch); + masm.storePtr(scratch, getAdjustedAddress(to)); + } else { + MOZ_CRASH("Invalid emitMove arguments."); + } + } else if (from.isEffectiveAddress()) { + if (to.isGeneralReg()) { + masm.computeEffectiveAddress(getAdjustedAddress(from), to.reg()); + } else if (to.isMemory()) { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.computeEffectiveAddress(getAdjustedAddress(from), scratch); + masm.storePtr(scratch, getAdjustedAddress(to)); + } else { + MOZ_CRASH("Invalid emitMove arguments."); + } + } else { + MOZ_CRASH("Invalid emitMove arguments."); + } +} + +void MoveEmitterPPC::emitInt32Move(const MoveOperand& from, + const MoveOperand& to) { + if (from.isGeneralReg()) { + if (to.isGeneralReg()) { + masm.move32(from.reg(), to.reg()); + } else if (to.isMemory()) { + masm.store32(from.reg(), getAdjustedAddress(to)); + } else { + MOZ_CRASH("Invalid emitInt32Move arguments."); + } + } else if (from.isMemory()) { + if (to.isGeneralReg()) { + masm.load32(getAdjustedAddress(from), to.reg()); + } else if (to.isMemory()) { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.load32(getAdjustedAddress(from), scratch); + masm.store32(scratch, getAdjustedAddress(to)); + } else { + MOZ_CRASH("Invalid emitInt32Move arguments."); + } + } else if (from.isEffectiveAddress()) { + if (to.isGeneralReg()) { + masm.computeEffectiveAddress(getAdjustedAddress(from), to.reg()); + } else if (to.isMemory()) { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.computeEffectiveAddress(getAdjustedAddress(from), scratch); + masm.store32(scratch, getAdjustedAddress(to)); + } else { + MOZ_CRASH("Invalid emitInt32Move arguments."); + } + } else { + MOZ_CRASH("Invalid emitInt32Move arguments."); + } +} + +void MoveEmitterPPC::emitFloat32Move(const MoveOperand& from, + const MoveOperand& to) { + if (from.isFloatReg()) { + if (to.isFloatReg()) { + masm.moveFloat32(from.floatReg(), to.floatReg()); + } else { + MOZ_ASSERT(to.isMemory()); + masm.storeFloat32(from.floatReg(), getAdjustedAddress(to)); + } + } else if (to.isFloatReg()) { + MOZ_ASSERT(from.isMemory()); + masm.loadFloat32(getAdjustedAddress(from), to.floatReg()); + } else { + MOZ_ASSERT(from.isMemory()); + MOZ_ASSERT(to.isMemory()); + ScratchFloat32Scope fpscratch32(masm); + masm.loadFloat32(getAdjustedAddress(from), fpscratch32); + masm.storeFloat32(fpscratch32, getAdjustedAddress(to)); + } +} + +void MoveEmitterPPC::emitDoubleMove(const MoveOperand& from, + const MoveOperand& to) { + if (from.isFloatReg()) { + if (to.isFloatReg()) { + masm.moveDouble(from.floatReg(), to.floatReg()); + } else if (to.isGeneralReg()) { + MOZ_CRASH("PPC: doubles are never moved into a 32-bit GPR"); + } 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 { + MOZ_CRASH("PPC: doubles are never moved out of a 32-bit GPR"); + } + } else { + MOZ_ASSERT(from.isMemory()); + MOZ_ASSERT(to.isMemory()); + ScratchDoubleScope fpscratch64(masm); + masm.loadDouble(getAdjustedAddress(from), fpscratch64); + masm.storeDouble(fpscratch64, getAdjustedAddress(to)); + } +} + +void MoveEmitterPPC::emitSimd128Move(const MoveOperand& from, + const MoveOperand& to) { + if (from.isFloatReg()) { + if (to.isFloatReg()) { + masm.moveSimd128(from.floatReg(), to.floatReg()); + } else { + MOZ_ASSERT(to.isMemory()); + masm.storeUnalignedSimd128(from.floatReg(), getAdjustedAddress(to)); + } + } else if (to.isFloatReg()) { + MOZ_ASSERT(from.isMemory()); + masm.loadUnalignedSimd128(getAdjustedAddress(from), to.floatReg()); + } else { + MOZ_ASSERT(from.isMemory()); + MOZ_ASSERT(to.isMemory()); + ScratchSimd128Scope scratch(masm); + masm.loadUnalignedSimd128(getAdjustedAddress(from), scratch); + masm.storeUnalignedSimd128(scratch, getAdjustedAddress(to)); + } +} + +void MoveEmitterPPC::emit(const MoveOp& move) { + const MoveOperand& from = move.from(); + const MoveOperand& to = move.to(); + + if (move.isCycleEnd() && move.isCycleBegin()) { + breakCycle(from, to, move.endCycleType(), move.cycleBeginSlot()); + completeCycle(from, to, move.type(), move.cycleEndSlot()); + return; + } + + if (move.isCycleEnd()) { + MOZ_ASSERT(inCycle_); + completeCycle(from, to, move.type(), move.cycleEndSlot()); + MOZ_ASSERT(inCycle_ > 0); + inCycle_--; + return; + } + + if (move.isCycleBegin()) { + breakCycle(from, to, move.endCycleType(), move.cycleBeginSlot()); + inCycle_++; + } + + switch (move.type()) { + case MoveOp::FLOAT32: + emitFloat32Move(from, to); + break; + case MoveOp::DOUBLE: + emitDoubleMove(from, to); + break; + case MoveOp::SIMD128: + emitSimd128Move(from, to); + break; + case MoveOp::INT32: + emitInt32Move(from, to); + break; + case MoveOp::GENERAL: + emitMove(from, to); + break; + default: + MOZ_CRASH("Unexpected move type"); + } +} + +void MoveEmitterPPC::assertDone() { MOZ_ASSERT(inCycle_ == 0); } + +void MoveEmitterPPC::finish() { + assertDone(); + + masm.freeStack(masm.framePushed() - pushedAtStart_); +} diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/MoveEmitter-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/MoveEmitter-ppc.h --- a/js/src/jit/ppc/MoveEmitter-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/MoveEmitter-ppc.h 2026-07-28 16:47:36.792011574 +0200 @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_MoveEmitter_ppc_h +#define jit_ppc_MoveEmitter_ppc_h + +#include "jit/MacroAssembler.h" +#include "jit/MoveResolver.h" + +namespace js { +namespace jit { + +class MoveEmitterPPC { + void emitDoubleMove(const MoveOperand& from, const MoveOperand& to); + void emitSimd128Move(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); + + protected: + uint32_t inCycle_; + MacroAssembler& masm; + + uint32_t pushedAtStart_; + + int32_t pushedAtCycle_; + + void assertDone(); + Address cycleSlot(uint32_t slot, uint32_t subslot = 0) const; + int32_t getAdjustedOffset(const MoveOperand& operand); + Address getAdjustedAddress(const MoveOperand& operand); + + void emitMove(const MoveOperand& from, const MoveOperand& to); + void emitInt32Move(const MoveOperand& from, const MoveOperand& to); + void emitFloat32Move(const MoveOperand& from, const MoveOperand& to); + void emit(const MoveOp& move); + + public: + explicit MoveEmitterPPC(MacroAssembler& masm) + : inCycle_(0), + masm(masm), + pushedAtStart_(masm.framePushed()), + pushedAtCycle_(-1) {} + + ~MoveEmitterPPC() { assertDone(); } + + void emit(const MoveResolver& moves); + void finish(); + // setScratchRegister is part of the cross-arch MoveEmitter interface + // but we never spill, so there's no scratch to set. No-op kept for + // shared-code compatibility. + void setScratchRegister(Register reg) {} +}; + +typedef MoveEmitterPPC MoveEmitter; + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_MoveEmitter_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/PORT-SPEC.md /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/PORT-SPEC.md --- a/js/src/jit/ppc/PORT-SPEC.md 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/PORT-SPEC.md 2026-07-29 08:23:49.172372497 +0200 @@ -0,0 +1,341 @@ +# SpiderMonkey JIT backend for 32-bit big-endian PowerPC (`JS_CODEGEN_PPC`) + +`js/src/jit/ppc/` was seeded by copying `js/src/jit/ppc64/` (the 64-bit +OpenPOWER/ELFv2 backend) and renaming `ppc64`->`ppc` / `PPC64`->`PPC`. It must +now be converted to 32-bit PowerPC. This file is the single source of truth so +the pieces fit together. + +## Target + +* Triple `powerpc-t2-linux-gnu`, T2 SDE `ppc-desktop`, `-mcpu=603`. +* Big endian, 32-bit pointers, `sizeof(void*) == 4`. +* **AltiVec/VMX is kept** (VR0-VR31, `lvx`/`stvx`/`vperm`/..., `vrsave`). + SIMD lowering stays alive and is backed by VMX. +* **VSX is removed** — POWER7+, far too new. Every `lxv*`/`stxv*`/`xx*`/`xs*`/ + `xv*` must become a VMX/FPR equivalent or go away. VMX has **no + double-precision vector arithmetic**, so any `f64x2` operation must be done + as two scalar FPR operations. +* No 64-bit instructions, no `popcnt*`, no `isel`, no `fsqrt`, no + `fctidz`/`fcfid`, no `frin`/`friz`/`frip`/`frim`, no `lbarx`/`lharx`/ + `stbcx.`/`sthcx.` (ISA 2.06+), no `ldarx`/`stdcx.`. `lwarx`/`stwcx.` exist. + +## Value representation + +* `JS_NUNBOX32` (configure sets it automatically for a 32-bit target). +* `ValueOperand` is a `(type, payload)` register pair; `Register64` is a + `(high, low)` pair. Big endian: **the high word is at the lower address**. +* Reference backends: `js/src/jit/arm/` for *shape* (32-bit, RISC, nunbox32, + register pairs), `js/src/jit/x86/` as the second nunbox32 opinion. Keep ppc64 + only for instruction encoding. + +## ABI: 32-bit PowerPC SysV (NOT ELFv2) + +| Register | Role | +| --- | --- | +| `r0` | volatile scratch; reads as literal 0 in base-register position | +| `r1` | stack pointer | +| `r2` | **TLS thread pointer — reserved, never write** | +| `r3`-`r10` | volatile; integer args 1-8; `r3` / `r3:r4` returns | +| `r11`, `r12` | volatile scratch | +| `r13` | small-data-area base — reserved, never write | +| `r14`-`r31` | callee-saved | +| `f0` | volatile scratch | +| `f1`-`f8` | FP arguments (**8**, not 13) | +| `f9`-`f13` | volatile | +| `f14`-`f31` | callee-saved | + +* No TOC, no `r2` save/restore, no function descriptors. +* `ABIStackAlignment` 16. `ShadowStackSpace` **16** (SysV linkage area: back + chain at `0(sp)`, LR save slot at `4(sp)`, rounded to alignment). + `SizeOfReturnAddressAfterCall` stays 0. +* A 64-bit integer argument goes in an **odd/even** pair — `r3:r4`, `r5:r6`, + `r7:r8`, `r9:r10` — with the **high word in the lower-numbered register**. + See "Known open issues" below: `ABIArg` cannot represent this yet. + +## Settled Architecture values (verified with the cross compiler) + +`Allocatable` 24; `Registers::SetType` `uint32_t`; +`VolatileMask` = r0 + r3-r12 (`0x00001FF9`); `NonVolatileMask` = r14-r31 +(`0xFFFFC000`); `NonAllocatableMask` = r0, r1, r2, r11, r12, r13, r16, r31 +(`0x80013807`); `AllocatableMask` `0x7FFEC7F8`; `SpillSlotSize` 16; +`ShadowStackSpace` 16; `FloatRegisters::Kind` `{Double, Single, Simd128}`; +FPR `Total`/`TotalPhys`/`Allocatable` = 96/32/31. + +**`__uint128_t` does not exist on 32-bit PowerPC** — GCC 16.1 rejects it. So +`FloatRegisters::SetType` is a local `Bitset128` class (copied from +`js/src/jit/arm64/Architecture-arm64.h`, which needs the same workaround on +MSVC) holding `{uint64_t hi, lo}` with `.low()`, `countTrailingZeroes()`, +`bitWidth()`. It is **not integral**: `static_cast(x)` will not +compile, and the FPR masks use arm64's `AllPhysMask << ShiftX` shift form. + +Register assignments: `ReturnReg` r3, `ReturnReg64` (r3, r4), +`JSReturnReg_Type` r6 / `JSReturnReg_Data` r5, `FramePointer` r31, +`SecondScratchReg` r12, `SavedScratchRegister` r16, `InterpreterPCReg` r17, +`CallTempReg0..5` = r4, r9, r10, r7, r8, r6, `ABINonArgReg0..3` r19-r22, +`ABINonArgReturnReg0/1` r29/r30, `ABINonVolatileReg` r14, `InstanceReg` r18, +`HeapReg` r24, `GlobalReg` r23. SharedICRegisters: R0 (r6, r5), R1 (r15, r14), +R2 (r4, r3), `ICTailCallReg` r27, `ICStubReg` r26. + +## Feature predicates + +`HasVMX()` is the AltiVec predicate. `HasVSX()`, `HasPOWER9()`, `HasPOWER10()` +survive only as hardcoded `return false` stubs so the tree links while call +sites are migrated. Code gated on them is dead and must be **deleted**, not +left behind a constant false — several are `MOZ_ASSERT(HasPOWER9())`. + +## Constant materialisation + +`PointerImmInstructions = 2` (`lis` + `ori`); `LongBranchInstructions = +PointerImmInstructions + 2`. `PatchWrite_NearCallSize()` is 16 bytes. The +patch API is `ExtractLoadPtrValue` / `UpdateLoadPtrValue` / +`WriteLoadPtrInstructions` / `IsLoadPtrStanza` (all `uintptr_t`). Stanza +placeholders must be a real `lis/ori` pair, not NOPs. + +## `PPC` is a predefined macro + +The 32-bit PowerPC compiler predefines `PPC` as `1` (also `__PPC__`, +`powerpc`). **Never use a bare `PPC` identifier** — it silently rewrites +enumerators and variables. The wasm `ObservedCPUFeatures` arch enum therefore +uses `PPC32 = 0xa`, not `PPC`. + +## Out of scope / disabled + +* **`Simulator-ppc.{h,cpp}`** are the copied 64-bit interpreter. Not built, + not ported, not in `moz.build`. We run real 32-bit code under `qemu-ppc`. +* **wasm JSPI** disabled for ppc: with it `ResumeFromException` is 72 bytes + and cannot satisfy `sizeof % 16 == 0`; without it exactly 64. `ppc` removed + from both JSPI lists in `js/moz.configure`; `JS_CODEGEN_PPC` added to the + assert in `jit/JitFrames.h`. + +## Known open issues + +1. **Int64 ABI arguments.** `ABIArg`'s `GPR_PAIR` stores one register code and + asserts `code % 2 == 0` with the other half at `+1` — the little-endian + convention. SysV ppc32 needs odd-first with the high word first, so it + fails on both parity and ordering. Only ARM defines + `JS_CODEGEN_REGISTER_PAIR`. Int64 args are currently passed on the stack, + which is ABI-incorrect for direct C calls. Mitigating factor: the wasm i64 + builtins (`DivI64`, …) take split `uint32_t` hi/lo args, and the int64 + return in r3:r4 is already correct. +2. **64-bit wasm atomics** currently follow ppc64's `ldarx`/`stdcx.` path, + which does not exist here. Compiles, cannot work. +3. **Sub-word atomics** need the word-granularity `lwarx`/`stwcx.` + round-down + shift + mask sequence (MIPS/LOONG64 model) with + valueTemp/offsetTemp/maskTemp, because `lbarx`/`lharx` do not exist. +4. `byteReverseSimd128` was VSX (`xxbrd`/`xxbrq`); needs a VMX `vperm` + implementation — `jit/CodeGenerator.cpp` calls it on big-endian targets. +5. `WasmBCMemory.cpp` `LoadLaneAndWiden` used VSX `xxpermdi`; needs a VMX + sequence. +6. **The VMX baseline is unsettled and currently too new.** The i64x2/i32x4 + lowering still uses ISA 2.07 (POWER8-era) VMX: `vaddudm`, `vsubudm`, + `vcmpequd`, `vcmpgtsd`/`vcmpgtud`, `vsld`/`vsrd`/`vsrad`, `vmaxsd`, + `vmul{e,o}{s,u}w`, `vmuluwm`, `vupk{h,l}sw`. **None of these exist on a + 7400/7450 G4**, which is the realistic AltiVec target for 32-bit PowerPC. + They are all behind `HasVMX()`, which is false on the `-mcpu=603` build + target, so they are inert today and cannot mis-execute there — but on a G4 + `HasVMX()` would be true and they would trap. Either add a + `HasVMX207()`-style predicate and a classic-AltiVec fallback for the ~20 + call sites, or decide the baseline is POWER8 VMX and say so. Affected: + `addInt64x2`, `subInt64x2`, `compareInt64x2`, `allTrueInt64x2`, the i64x2 + shifts, `absInt64x2`, `extMul*Int32x4`, `widen{Low,High}Int32x4`, + `mulInt32x4`. +7. `loadUnalignedSimd128`/`storeUnalignedSimd128` emit four word accesses but + report only the first as the `FaultingCodeOffset`, while + `CodeGenerator::visitWasmLoadSlot` registers a single + `TrapMachineInsn::Load128` site — a signal-handler null check would only + fire on the first word. + +## Current status (integration) + +The tree **builds clean** and the JIT-enabled `js` shell **links and runs** under +`qemu-ppc`: the C++ interpreter path is correct (`--no-blinterp --no-baseline +--no-ion` computes a 200k-iteration loop correctly). Any path that enters +JIT-generated code still crashes, including an empty function under +`--blinterp-eager`, so the remaining bug is in the shared JIT entry path +(enterJIT trampoline / baseline-interpreter prologue / VM-call marshalling), +not in individual opcodes. + +Fixed so far during bring-up: +* `generateEnterJIT` saved/restored VR20-VR31 unconditionally. `stvx` is an + illegal instruction without AltiVec (the 603 has none, and qemu's default CPU + model has none), so this was the first SIGILL. Now gated on `HasVMX()`. + **Any other unconditional VMX emission has the same problem.** +* `Trampoline-ppc.cpp` `MOZ_CRASH`ed on `VMFunctionData::DoubleByValue` / + `DoubleByRef`. Those are unreachable only on 64-bit; on a 32-bit target a + two-word argument is routine. Implemented following ARM. +* `INT64LOW_OFFSET`/`INT64HIGH_OFFSET` in `jit/Registers.h` were hardcoded + little-endian. On big endian the **high** word is at offset 0. Now selected + on `__BYTE_ORDER__` (this tree has no `MOZ_BIG_ENDIAN`; `__BYTE_ORDER__` is + the local idiom). Shared `LowWord()`/`HighWord()` in + `jit/shared/Assembler-shared.h` depend on these. +* `js/src/util/Poison.h` had no ppc `JS_SWEPT_CODE_PATTERN` (the chain ends in + `#error`); `wasm/WasmDebugFrame.h` needed ppc in the 32-bit padding list. +* Added the missing `as_fctiw` emitter; added `visitUrshD` and `branchAdd64`. + +Debugging recipe that works: `qemu-ppc -g -L $SYSROOT ...` then +`powerpc-t2-linux-gnu-gdb -ex 'target remote :' -ex continue -ex 'bt' -ex +'x/6i $pc-12'`. This gives real symbols for C++ frames and disassembles +JIT-generated code at the fault. + +## The ppc32 SysV aggregate-argument rule (important) + +**The 32-bit PowerPC SysV ABI passes aggregates by invisible reference.** Proven +with the cross compiler: for `uint32_t f(void*, void*, SmallStruct)` GCC emits +`lwz 3,0(5)` — it *dereferences* the third argument register. This holds even +for a trivially-copyable 4-byte struct, and it is **not** what x86/ARM/x64 do, +so it is invisible to every other backend. + +Consequence: SpiderMonkey's `TypeToArgProperties` in `js/src/jit/VMFunctions.cpp` +classifies anything of word size or smaller as pass-by-value, which is wrong +here for class/union types (`GCThingIndex`, `Value`, ...). Fixed by OR-ing in +`VMFunctionData::ByRef` for `std::is_class_v` / `std::is_union_v` under +`JS_CODEGEN_PPC`; the wrapper's ByRef path already passes the effective address +of the exit-frame slot, which is exactly the pointer-to-caller's-copy the ABI +wants. Scalars are unaffected. **Any new VM function taking an aggregate by +value depends on this.** + +## Status + +The backend is healthy. jit-test `basic` is PASSED ALL (1491 tests, full +jitflags); a wider sweep of ion/warp/cacheir/gc/arguments/collections/proxy/ +arrays (~2700) is down to a handful; Octane pdf.js scores 137. +**WebAssembly is enabled and running** (baseline compiler only): traps are +correct and `f32/f64.sqrt` are bit-exact against `Math.sqrt`. + +### Closed since this file was first written + +* Byte-swapping wasm linear memory is **fully implemented** (`lhbrx`/`lwbrx`/ + `sthbrx`/`stwbrx`, i64/f64 split into two swapped halves, low word at the + lower address). The old note claiming ppc64 was the only big-endian target + with this is obsolete. +* Sub-word atomics: done (`lwarx`/`stwcx.` round-down + shift + mask, with the + BE half-word reversal gated on wasm-vs-JS). +* `byteReverseSimd128`: done in pure VMX (`lvsl` + `vnor` + `vperm`). +* ppc32 wasm signal handling: done. Note the 32-bit glibc `ucontext_t` keeps + registers behind `uc_mcontext.uc_regs->gregs[]`, not inline as on ppc64; + `kWasmTrapSignal` must be `SIGTRAP` because `wasmTrap()` emits `tw 31,r0,r0`. +* `f32/f64.sqrt` route through `SymbolicAddress::SqrtF/SqrtD`. They cannot use + `masm.sqrtDouble` directly: on the 603 that is a hidden libm call and + `setupUnalignedABICall` asserts `!IsCompilingWasm()`. + +### Deliberately deferred, with reasons + +* **wasm Ion** — baseline only. `IonPlatformSupport()` stays off. +* **wasm SIMD** — `ENABLE_WASM_SIMD` is not set for ppc. VMX has no + double-precision vector arithmetic, and the lowering still uses ISA 2.07 + (POWER8) VMX that a 7400/G4 lacks — and `SupportsWasmSimd()` is exactly + `HasVMX()`, which is true under `qemu -cpu G4`, so enabling it would emit + instructions the target cannot run. +* **wasm threads / atomics** — the 603 has `lwarx`/`stwcx.` but no + `ldarx`/`stdcx.`, so lock-free 8-byte atomics are impossible. Rather than + fake it, the `isLockfree8()` requirement moved from + `wasm::HasPlatformSupport()` into `wasm::ThreadsAvailable()`. The validator + then rejects every `0xFE` opcode and all shared memories, making the ~20 + `MOZ_CRASH` i64-atomic sites statically unreachable. +* **memory64 / multi-memory `memory.copy`** — need real `int64_t` C arguments, + so they depend on the Int64 ABI gap below. + +### Still open + +1. **Int64 ABI arguments.** `ABIArg`'s `GPR_PAIR` stores one code, asserts it is + even, and derives the other at +1 — the little-endian convention. SysV ppc32 + pairs are odd-first (`r3:r4`, `r5:r6`, ...) with the **high** word in the + lower-numbered register, so it fails on both parity and ordering. + `ABIArgGenerator` therefore puts Int64 on the stack. The only non-wasm + `passABIArg(Register64)` site in the tree is + `CodeGenerator::visitTypedArrayFill64`, which is consequently broken. + Note `MacroAssembler::passABIArg(Register64)` decomposes a pair into two + plain `MoveOp::GENERAL` moves, so `MoveEmitter` never sees a `RegPair` — + the work is in `jit/RegisterSets.h`, `jit/MoveResolver.*`, + `JS_CODEGEN_REGISTER_PAIR`, and `ABIArgGenerator::next`. +2. **`wasmBoundsCheck32`** discards the `Condition` returned by `ma_cmp` and + branches on the original flagged one, so every wasm memory access traps + `OutOfBounds` — even index 0. + +## The ppc32 SysV aggregate-argument rule (important) + +**The 32-bit PowerPC SysV ABI passes aggregates by invisible reference.** Proven +with the cross compiler: for `uint32_t f(void*, void*, SmallStruct)` GCC emits +`lwz 3,0(5)` — it *dereferences* the third argument register. This holds even +for a trivially-copyable 4-byte struct, and it is **not** what x86/ARM/x64 do, +so it is invisible to every other backend. + +Consequence: SpiderMonkey's `TypeToArgProperties` in `js/src/jit/VMFunctions.cpp` +classifies anything of word size or smaller as pass-by-value, which is wrong +here for class/union types (`GCThingIndex`, `Value`, ...). Fixed by OR-ing in +`VMFunctionData::ByRef` for `std::is_class_v` / `std::is_union_v` under +`JS_CODEGEN_PPC`; the wrapper's ByRef path already passes the effective address +of the exit-frame slot, which is exactly the pointer-to-caller's-copy the ABI +wants. Scalars are unaffected. **Any new VM function taking an aggregate by +value depends on this.** + +## Where the bring-up currently stands (next thing to debug) + +`--blinterp-eager --no-baseline --no-ion -e 'print(42)'` — the simplest possible +script — fails with `Assertion failure: flags() == 0` at `gc/Cell.h:762` +(`TenuredCellWithNonGCPointer::headerPtr()`), i.e. a GC pointer whose low bits +are polluted. Backtrace: + + headerPtr() <- BaseShape::clasp() <- Shape::getObjectClass() + <- JSObject::getClass() <- JSObject::is() + <- JSObject::as() + <- GetNameIRGenerator::tryAttachGlobalNameValue() (jit/CacheIR.cpp:3371) + +So the **environment-chain object read out of the baseline frame is corrupt**. +That also explains the earlier `GlobalOrEvalDeclInstantiation` failure, whose +first argument is the env chain. Polluted *low* bits point at reading the wrong +half of a Value or a wrong frame offset, not at a wild pointer. + +Prime suspects, in order: +1. `BaselineFrame` env-chain slot handling in the blinterp prologue, and + `generateEnterJIT`'s initial frame setup in `Trampoline-ppc.cpp`. +2. Any place a raw pointer slot is read with a Value-payload offset (or vice + versa). On big endian `NUNBOX32_TYPE_OFFSET` is 0 and + `NUNBOX32_PAYLOAD_OFFSET` is 4, the mirror of every other backend, so an + off-by-one-half bug reads adjacent data rather than obviously crashing. + +Already ruled out, with evidence, so do not re-investigate: +* VM-call argument marshalling: at the callee's entry the argument registers are + consecutive exit-frame slot addresses holding sensible values. +* Those slots are not clobbered (watched across a whole call in one run) and are + ~450 bytes above SP, so the callee's frame cannot overwrite them. +* r2 (TLS) and r13 (SDA base) are intact inside VM calls, so JIT code is not + clobbering the reserved registers. Note C++ reaches globals through `r2`, so a + clobber would look like garbage appearing from nowhere. +* The wrapper's argument base is `ExitFrameLayout::Size()`, identical to ARM. +* AltiVec is not involved: the same failure occurs under `qemu-ppc -cpu G4`. + +## Test-suite status and failure classification + +**jit-test** (`js/src/jit-test/`, 9226 tests): `basic` (1491) PASSED ALL with full +jitflags. A wider sweep of ion/warp/cacheir/gc/arguments/collections/proxy/arrays +(~2700) leaves 12 failures + 1 timeout, dominated by a BigInt64/intptr cluster of 6. + +**jstests** (`js/src/tests/`, 59661 files). The harness needs a qemu wrapper +*inside* the objdir — `jstests.py` finds `config/autoconf.mk` by walking up from +the shell path — so use `$OBJDIR/dist/bin/js-qemu`, not the one in this workdir. +`non262` gives 19 regressions, and **the JIT is responsible for only 4 of them**. +Always re-run a suspected failure with `--args="--no-blinterp --no-baseline +--no-ion"` before treating it as a codegen bug: + +* **14 are environment, not us** — 12 `Intl/DateTimeFormat`, `Temporal/Intl/ + consistent-dates`, `Date/15.9.5.7`. All fail identically with the JIT fully + disabled. Cause is the system ICU (78.3, via `--with-system-icu`) and its + timezone data differing from what the tests pin. Do not "fix" these in the + backend. +* **1 is a runtime gap, not codegen** — `TypedArray/sort_modifications_concurrent` + hits `MOZ_CRASH(No 64-bit atomics)` at + `jit/shared/AtomicOperations-feeling-lucky-gcc.h:168` with the JIT off. The + sysroot does ship `libatomic.so.1` and the shell already links it, so + `__atomic_*` builtins are a plausible route to real 8-byte atomics here, which + would also unblock wasm i64 atomics. +* **4 are genuine JIT bugs** — `Math/pow-approx`, `Math/pow-approx-pow10`, + `Array/sort_basics`, `extensions/clone-errors`. + +## Build / test harness + +Scripts live in `/srv/t2/tmp/ppc-jit-work/` (outside the T2 tree, which T2 +deletes between builds): `jsenv.sh`, `jsconf.sh`, `jsbuild.sh`, `js-qemu`, +`snapshot-patch.sh`, plus `baseline/` (pristine T2-patched snapshot for +diffing) and `salvage/`. Run tests with +`js/src/jit-test/jit_test.py <...>/js-qemu`. diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/SharedICHelpers-ppc-inl.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/SharedICHelpers-ppc-inl.h --- a/js/src/jit/ppc/SharedICHelpers-ppc-inl.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/SharedICHelpers-ppc-inl.h 2026-07-28 16:47:36.792052545 +0200 @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_SharedICHelpers_ppc_inl_h +#define jit_ppc_SharedICHelpers_ppc_inl_h + +#include "jit/BaselineFrame.h" +#include "jit/SharedICHelpers.h" + +#include "jit/MacroAssembler-inl.h" + +namespace js { +namespace jit { + +inline void EmitBaselineTailCallVM(TrampolinePtr target, MacroAssembler& masm, + uint32_t argSize) { +#ifdef DEBUG + Register scratch = R2.scratchReg(); + + // Compute frame size. + masm.movePtr(FramePointer, scratch); + masm.subPtr(StackPointer, scratch); + + // Store frame size without VMFunction arguments for debug assertions. + masm.subPtr(Imm32(argSize), scratch); + Address frameSizeAddr(FramePointer, + BaselineFrame::reverseOffsetOfDebugFrameSize()); + masm.store32(scratch, frameSizeAddr); + masm.addPtr(Imm32(argSize), scratch); +#endif + + // Push frame descriptor and perform the tail call. + masm.push(FrameDescriptor(FrameType::BaselineJS)); + + // The return address is in LR (set by the original bl/bctrl call). + // The VMWrapper code will push it via pushReturnAddress(). + + masm.jump(target); +} + +inline void EmitBaselineCallVM(TrampolinePtr target, MacroAssembler& masm) { + masm.push(FrameDescriptor(FrameType::BaselineStub)); + masm.call(target); +} + +inline void EmitBaselineEnterStubFrame(MacroAssembler& masm, Register scratch) { + MOZ_ASSERT(scratch != ICTailCallReg); + +#ifdef DEBUG + // Compute frame size. + masm.movePtr(FramePointer, scratch); + masm.subPtr(StackPointer, scratch); + + Address frameSizeAddr(FramePointer, + BaselineFrame::reverseOffsetOfDebugFrameSize()); + masm.store32(scratch, frameSizeAddr); +#endif + + // Note: when making changes here, don't forget to update + // BaselineStubFrame if needed. + + // Push frame descriptor and return address. + // LR holds the return address; read it into ICTailCallReg to push. + masm.Push(FrameDescriptor(FrameType::BaselineJS)); + masm.xs_mflr(ICTailCallReg); + masm.Push(ICTailCallReg); + + // Save old frame pointer, stack pointer and stub reg. + masm.Push(FramePointer); + masm.movePtr(StackPointer, FramePointer); + masm.Push(ICStubReg); + + // Stack should remain aligned. + masm.assertStackAlignment(sizeof(Value), 0); +} + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_SharedICHelpers_ppc_inl_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/SharedICHelpers-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/SharedICHelpers-ppc.h --- a/js/src/jit/ppc/SharedICHelpers-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/SharedICHelpers-ppc.h 2026-07-28 16:47:36.792093638 +0200 @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_SharedICHelpers_ppc_h +#define jit_ppc_SharedICHelpers_ppc_h + +#include "jit/BaselineIC.h" +#include "jit/JitFrames.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 PPC). +static const size_t ICStackValueOffset = 0; + +struct BaselineStubFrame { + uintptr_t savedFrame; + uintptr_t savedStub; + uintptr_t returnAddress; + uintptr_t descriptor; +}; + +inline void EmitRestoreTailCallReg(MacroAssembler& masm) { + // On PPC, LR always holds the return address after a bl/bctrl call. + // No-op: LR is the hardware link register, not a GPR on the stack. +} + +inline void EmitRepushTailCallReg(MacroAssembler& masm) { + // No-op: LR already holds the return address. +} + +inline void EmitCallIC(MacroAssembler& masm, CodeOffset* callOffset) { + // The stub pointer must already be in ICStubReg. + // Load stubcode pointer from the ICStub. + // 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. On PPC call(Register) emits mtctr + bctrl, + // which sets LR to the address after bctrl. + masm.call(R2.scratchReg()); + *callOffset = CodeOffset(masm.currentOffset()); +} + +inline void EmitReturnFromIC(MacroAssembler& masm) { + // Return via hardware LR (set by the original bl/bctrl call). + masm.as_blr(); +} + +inline void EmitBaselineLeaveStubFrame(MacroAssembler& masm) { + masm.loadPtr( + Address(FramePointer, BaselineStubFrameLayout::ICStubOffsetFromFP), + ICStubReg); + + masm.movePtr(FramePointer, StackPointer); + masm.Pop(FramePointer); + + // Load the return address and restore it to LR. + masm.Pop(ICTailCallReg); + masm.xs_mtlr(ICTailCallReg); + + // Discard the frame descriptor. + { + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.Pop(scratch); + } +} + +template +inline void EmitPreBarrier(MacroAssembler& masm, const AddrType& addr, + MIRType type) { + // On PPC, LR is clobbered by guardedCallPreBarrier. Save it first. + masm.xs_mflr(r0); + masm.push(r0); + masm.guardedCallPreBarrier(addr, type); + masm.pop(r0); + masm.xs_mtlr(r0); +} + +inline void EmitStubGuardFailure(MacroAssembler& masm) { + // Load next stub into ICStubReg. + masm.loadPtr(Address(ICStubReg, ICCacheIRStub::offsetOfNext()), ICStubReg); + + // Return address is in LR. Jump to the next stubcode. + masm.jump(Address(ICStubReg, ICStub::offsetOfStubCode())); +} + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_SharedICHelpers_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/SharedICRegisters-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/SharedICRegisters-ppc.h --- a/js/src/jit/ppc/SharedICRegisters-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/SharedICRegisters-ppc.h 2026-07-28 16:49:01.260411919 +0200 @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_SharedICRegisters_ppc_h +#define jit_ppc_SharedICRegisters_ppc_h + +#include "jit/ppc/Assembler-ppc.h" +#include "jit/Registers.h" +#include "jit/RegisterSets.h" + +namespace js { +namespace jit { + +// ValueOperands R0, R1, and R2, each a (type, payload) pair under nunbox32. +// R0 == JSReturnOperand, and R2 uses registers not preserved across calls. R1 +// value should be preserved across calls. +static constexpr ValueOperand R0(r6, r5); +static constexpr ValueOperand R1(r15, r14); +static constexpr ValueOperand R2(r4, r3); + +// ICTailCallReg and ICStubReg. +// On PPC, LR is not a GPR, so ICTailCallReg must be a normal GPR. +// r3-r10 are all argument registers, so using one risks clobbering by ABI +// calls with enough arguments, and r11/r12 are the scratch pair. We use +// callee-saved registers instead, matching the MIPS64/RISC-V strategy. +// These are excluded from BaselineICAvailableGeneralRegs. +static constexpr Register ICTailCallReg = r27; +static constexpr Register ICStubReg = r26; + +// FloatReg0 must be equal to ReturnFloatReg. +static constexpr FloatRegister FloatReg0 = {FloatRegisters::f1, + FloatRegisters::Double}; +static constexpr FloatRegister FloatReg1 = {FloatRegisters::f2, + FloatRegisters::Double}; +static constexpr FloatRegister FloatReg2 = {FloatRegisters::f3, + FloatRegisters::Double}; +static constexpr FloatRegister FloatReg3 = {FloatRegisters::f4, + FloatRegisters::Double}; + +} // namespace jit +} // namespace js + +#endif /* jit_ppc_SharedICRegisters_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/Simulator-ppc.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/Simulator-ppc.cpp --- a/js/src/jit/ppc/Simulator-ppc.cpp 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/Simulator-ppc.cpp 2026-07-28 16:47:36.796087374 +0200 @@ -0,0 +1,7273 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/ppc/Simulator-ppc.h" + +#include +#include +#include +#include +#include + +#include "jit/AtomicOperations.h" +#include "jit/ppc/Assembler-ppc.h" +#include "js/Conversions.h" +#include "threading/LockGuard.h" +#include "vm/Float16.h" +#include "vm/JSContext.h" +#include "vm/Runtime.h" +#include "wasm/WasmInstance.h" +#include "wasm/WasmSignalHandlers.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<__uint128_t>(v) + +namespace js { +namespace jit { + +static int64_t MultiplyHighSigned(int64_t u, int64_t v) { + uint64_t u0, v0, w0; + int64_t u1, v1, w1, w2, t; + + u0 = u & 0xFFFFFFFFL; + u1 = u >> 32; + v0 = v & 0xFFFFFFFFL; + v1 = v >> 32; + + w0 = u0 * v0; + t = u1 * v0 + (w0 >> 32); + w1 = t & 0xFFFFFFFFL; + w2 = t >> 32; + w1 = u0 * v1 + w1; + + return u1 * v1 + w2 + (w1 >> 32); +} + +static uint64_t MultiplyHighUnsigned(uint64_t u, uint64_t v) { + uint64_t u0, v0, w0; + uint64_t u1, v1, w1, w2, t; + + u0 = u & 0xFFFFFFFFL; + u1 = u >> 32; + v0 = v & 0xFFFFFFFFL; + v1 = v >> 32; + + w0 = u0 * v0; + t = u1 * v0 + (w0 >> 32); + w1 = t & 0xFFFFFFFFL; + w2 = t >> 32; + w1 = u0 * v1 + w1; + + return u1 * v1 + w2 + (w1 >> 32); +} + +inline constexpr uint32_t RotateLeft32(uint32_t value, uint32_t shift) { + return (value << shift) | (value >> ((32 - shift) & 31)); +} + +inline constexpr uint64_t RotateLeft64(uint64_t value, uint64_t shift) { + return (value << shift) | (value >> ((64 - shift) & 63)); +} + +// Generate a 64-bit mask with bits mb..me set (PPC numbering: 0 = MSB = bit +// 63 in C). When mb <= me, a contiguous range is set; when mb > me, the +// mask wraps around (bits 0..me and mb..63 are set). +static inline uint64_t MASK64(unsigned mb, unsigned me) { + MOZ_ASSERT(mb < 64 && me < 64); + uint64_t mask_begin = ~0ULL >> mb; + uint64_t mask_end = ~0ULL << (63 - me); + if (mb <= me) { + return mask_begin & mask_end; + } + return mask_begin | mask_end; +} + +static inline uint32_t MASK32(unsigned mb, unsigned me) { + MOZ_ASSERT(mb < 32 && me < 32); + uint32_t mask_begin = ~0U >> mb; + uint32_t mask_end = ~0U << (31 - me); + if (mb <= me) { + return mask_begin & mask_end; + } + return mask_begin | mask_end; +} + +// Count leading zeros. +static inline int CountLeadingZeros64(uint64_t value) { + if (value == 0) return 64; + return __builtin_clzll(value); +} + +static inline int CountLeadingZeros32(uint32_t value) { + if (value == 0) return 32; + return __builtin_clz(value); +} + +static inline int CountTrailingZeros64(uint64_t value) { + if (value == 0) return 64; + return __builtin_ctzll(value); +} + +static inline int CountTrailingZeros32(uint32_t value) { + if (value == 0) return 32; + return __builtin_ctz(value); +} + +static inline int PopCount64(uint64_t value) { + return __builtin_popcountll(value); +} + +static inline int PopCount32(uint32_t value) { + return __builtin_popcount(value); +} + +static inline uint64_t PopCountPerByte(uint64_t value) { + uint64_t result = 0; + for (int i = 0; i < 8; i++) { + uint8_t byte = (value >> (i * 8)) & 0xFF; + result |= (uint64_t)__builtin_popcount(byte) << (i * 8); + } + return result; +} + +// PPC C argument slots: PPC ELFv2 ABI does not require C argument +// slots on the stack for register-passed arguments, but we reserve the +// link area (32 bytes). +const int kCArgSlotCount = 0; +const int kCArgsSlotsSize = kCArgSlotCount * sizeof(uintptr_t); + +// ----------------------------------------------------------------------------- +// PPC SimInstruction. + +class SimInstruction { + public: + enum { + kInstrSize = 4, + kPCReadOffset = 0 + }; + + inline Instr instructionBits() const { + return *reinterpret_cast(this); + } + + inline void setInstructionBits(Instr value) { + *reinterpret_cast(this) = value; + } + + inline int bit(int nr) const { return (instructionBits() >> nr) & 1; } + + inline uint32_t bits(int hi, int lo) const { + return (instructionBits() >> lo) & ((2U << (hi - lo)) - 1); + } + + inline uint32_t opcode() const { return bits(31, 26); } + + inline uint32_t rtValue() const { return bits(25, 21); } + inline uint32_t rsValue() const { return bits(25, 21); } + inline uint32_t raValue() const { return bits(20, 16); } + inline uint32_t rbValue() const { return bits(15, 11); } + inline uint32_t rcValue() const { return bits(10, 6); } + + inline uint32_t boValue() const { return bits(25, 21); } + inline uint32_t biValue() const { return bits(20, 16); } + + // D-form 16-bit immediate (sign-extend to get signed value). + inline int16_t imm16Value() const { return I16(bits(15, 0)); } + inline uint16_t uimm16Value() const { return U16(bits(15, 0)); } + + // DS-form 14-bit displacement (bits 2..15, 4-byte aligned). + inline int16_t ds14Value() const { + return I16(bits(15, 2) << 2); + } + + // B-form 14-bit branch displacement (bits 2..15, 4-byte aligned). + inline int32_t bd16Value() const { + int16_t raw = I16(bits(15, 2) << 2); + return (int32_t)raw; + } + + // I-form 24-bit branch offset (bits 2..25, sign-extended, 4-byte aligned). + inline int32_t li26Value() const { + int32_t raw = I32(bits(25, 2) << 2); + // Sign-extend from 26 bits. + return (raw << 6) >> 6; + } + + // Extended opcode for X-form / XO-form (bits 1..10). + inline uint32_t xoValue() const { return bits(10, 1); } + + // Extended opcode for XL-form (bits 1..10). + inline uint32_t xlValue() const { return bits(10, 1); } + + // MD-form SH field: sh[0:4] in instruction bits 15:11, sh[5] in bit 1. + // Assembler encodes: ((sh & 0x1f) << 11) | ((sh & 0x20) >> 4). + inline uint32_t mdSHValue() const { + return bits(15, 11) | (bit(1) << 5); + } + // mb/me for MD-form (rldicl/rldicr/rldic/rldimi): 6-bit field split as + // mb[0:4] in instruction bits 10:6 and mb[5] in bit 5. + inline uint32_t mdMBValue() const { + return bits(10, 6) | (bit(5) << 5); + } + inline uint32_t mdMEValue() const { return mdMBValue(); } + + // MD-form XO (bits 2..4). + inline uint32_t mdXOValue() const { return bits(4, 2); } + + // MDS-form (rldcl, rldcr): mb[0:4] in bits 10:6, mb[5] in bit 5. + inline uint32_t mdsMBValue() const { + return bits(10, 6) | (bit(5) << 5); + } + + // M-form fields (32-bit rotate/mask). + inline uint32_t mSHValue() const { return bits(15, 11); } + inline uint32_t mMBValue() const { return bits(10, 6); } + inline uint32_t mMEValue() const { return bits(5, 1); } + + // Rc bit. + inline bool rcBit() const { return bit(0); } + + // AA bit for branch instructions. + inline bool aaBit() const { return bit(1); } + + // LK bit for branch instructions. + inline bool lkBit() const { return bit(0); } + + // OE bit for XO-form arithmetic. + inline bool oeBit() const { return bit(10); } + + // L bit for compare instructions (bit 21). + inline bool lBit() const { return bit(21); } + + // BF field (bits 23..25) for compares. + inline uint32_t bfValue() const { return bits(25, 23); } + + bool isTrap() const { + uint32_t instr = instructionBits(); + // PPC_trap = 0x7FE00008 (tw 31,0,0). + // Don't treat the call-redirection instruction or wasm trap as a + // debugger trap. + if (instr == kCallRedirInstr) return false; + if (instr == 0x7FE00008) return false; + // Any other tw instruction with TO=31 is a trap. + if (opcode() == 31 && (xoValue() == 4)) return true; + return false; + } + + private: + SimInstruction() = delete; + SimInstruction(const SimInstruction& other) = delete; + void operator=(const SimInstruction& other) = delete; +}; + +// ----------------------------------------------------------------------------- +// ICache. + +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; + 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]; + static const int kValidityMapSize = kPageSize >> kLineShift; + char validity_map_[kValidityMapSize]; +}; + +class AutoLockSimulatorCache : public LockGuard { + using Base = LockGuard; + + public: + explicit AutoLockSimulatorCache() + : Base(SimulatorProcess::singleton_->cacheLock_) {} +}; + +mozilla::Atomic + SimulatorProcess::ICacheCheckingDisableCount(1); +SimulatorProcess* SimulatorProcess::singleton_ = nullptr; + +int64_t Simulator::StopSimAt = -1; + +// ----------------------------------------------------------------------------- +// Simulator Create / Destroy. + +Simulator* Simulator::Create() { + auto sim = MakeUnique(); + if (!sim) { + return nullptr; + } + + if (!sim->init()) { + return nullptr; + } + + int64_t stopAt; + char* stopAtStr = getenv("PPC_SIM_STOP_AT"); + if (stopAtStr && sscanf(stopAtStr, "%" PRIi64, &stopAt) == 1) { + fprintf(stderr, "\nStopping simulation at icount %" PRIi64 "\n", stopAt); + Simulator::StopSimAt = stopAt; + } + + return sim.release(); +} + +void Simulator::Destroy(Simulator* sim) { js_delete(sim); } + +// ----------------------------------------------------------------------------- +// Debugger. + +class ppcDebugger { + public: + explicit ppcDebugger(Simulator* sim) : sim_(sim) {} + + void stop(SimInstruction* instr); + void debug(); + void printAllRegs(); + void printAllRegsIncludingFPU(); + + private: + static const Instr kBreakpointInstr = 0x7FE00008; // PPC_trap + static const Instr kNopInstr = 0x60000000; // PPC_nop + + 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); + + bool setBreakpoint(SimInstruction* breakpc); + bool deleteBreakpoint(SimInstruction* breakpc); + + void undoBreakpoints(); + void redoBreakpoints(); +}; + +void ppcDebugger::stop(SimInstruction* instr) { + uint32_t code = 0; + char* msg = *reinterpret_cast(sim_->get_pc() + + SimInstruction::kInstrSize); + if (!sim_->watchedStops_[code].desc_) { + sim_->watchedStops_[code].desc_ = msg; + } + 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 ppcDebugger::getRegisterValue(int regnum) { + if (regnum == kPCRegister) { + return sim_->get_pc(); + } + return sim_->getRegister(regnum); +} + +int64_t ppcDebugger::getFPURegisterValueLong(int regnum) { + return sim_->getFpuRegister(regnum); +} + +float ppcDebugger::getFPURegisterValueFloat(int regnum) { + return sim_->getFpuRegisterFloat(regnum); +} + +double ppcDebugger::getFPURegisterValueDouble(int regnum) { + return sim_->getFpuRegisterDouble(regnum); +} + +bool ppcDebugger::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 + 2, "%" PRIx64, reinterpret_cast(value)) == + 1; + } + return sscanf(desc, "%" PRIu64, reinterpret_cast(value)) == 1; +} + +bool ppcDebugger::setBreakpoint(SimInstruction* breakpc) { + if (sim_->break_pc_ != nullptr) { + return false; + } + + sim_->break_pc_ = breakpc; + sim_->break_instr_ = breakpc->instructionBits(); + return true; +} + +bool ppcDebugger::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 ppcDebugger::undoBreakpoints() { + if (sim_->break_pc_) { + sim_->break_pc_->setInstructionBits(sim_->break_instr_); + } +} + +void ppcDebugger::redoBreakpoints() { + if (sim_->break_pc_) { + sim_->break_pc_->setInstructionBits(kBreakpointInstr); + } +} + +void ppcDebugger::printAllRegs() { + int64_t value; + for (uint32_t i = 0; i < Registers::Total; i++) { + value = getRegisterValue(i); + printf("%3s: 0x%016" PRIx64 " %20" PRIi64 " ", Registers::GetName(i), + value, value); + + if (i % 2) { + printf("\n"); + } + } + printf("\n"); + + value = getRegisterValue(Simulator::pc); + printf(" pc: 0x%016" PRIx64 "\n", value); + printf(" lr: 0x%016" PRIx64 "\n", sim_->getLR()); + printf(" ctr: 0x%016" PRIx64 "\n", sim_->getCTR()); + printf(" cr: 0x%08x\n", sim_->getCR()); + printf(" xer: 0x%016" PRIx64 "\n", sim_->getXER()); +} + +void ppcDebugger::printAllRegsIncludingFPU() { + printAllRegs(); + + printf("\n\n"); + for (uint32_t i = 0; i < FloatRegisters::TotalPhys; i++) { + printf("%3s: 0x%016" PRIx64 "\tflt: %-8.4g\tdbl: %-16.4g\n", + FloatRegisters::GetName(i), getFPURegisterValueLong(i), + getFPURegisterValueFloat(i), getFPURegisterValueDouble(i)); + } +} + +static char* ReadLine(const char* prompt) { + UniqueChars result; + char lineBuf[256]; + int offset = 0; + bool keepGoing = true; + fprintf(stdout, "%s", prompt); + fflush(stdout); + while (keepGoing) { + if (fgets(lineBuf, sizeof(lineBuf), stdin) == nullptr) { + return nullptr; + } + int len = strlen(lineBuf); + if (len > 0 && lineBuf[len - 1] == '\n') { + keepGoing = false; + } + if (!result) { + result.reset(js_pod_malloc(len + 1)); + if (!result) { + return nullptr; + } + } else { + int new_len = offset + len + 1; + char* new_result = js_pod_malloc(new_len); + if (!new_result) { + return nullptr; + } + memcpy(new_result, result.get(), offset * sizeof(char)); + result.reset(new_result); + } + memcpy(result.get() + offset, lineBuf, len * sizeof(char)); + offset += len; + } + + MOZ_ASSERT(result); + result[offset] = '\0'; + return result.release(); +} + +static void DisassembleInstruction(uint64_t pc) { + printf(" 0x%016" PRIx64 ": %08x\n", pc, + *reinterpret_cast(pc)); +} + +void ppcDebugger::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}; + + cmd[COMMAND_SIZE] = 0; + arg1[ARG_SIZE] = 0; + arg2[ARG_SIZE] = 0; + + 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 { + sim_->setLastDebuggerInput(line); + } + 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()) { + sim_->instructionDecode(instr); + } else { + printf("/!\\ Jumping over generated breakpoint.\n"); + sim_->set_pc(sim_->get_pc() + SimInstruction::kInstrSize); + } + sim_->icount_++; + } else if ((strcmp(cmd, "c") == 0) || (strcmp(cmd, "cont") == 0)) { + sim_->instructionDecode( + reinterpret_cast(sim_->get_pc())); + sim_->icount_++; + 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::Code fReg = FloatRegisters::FromName(arg1); + if (reg != InvalidReg) { + value = getRegisterValue(reg.code()); + printf("%s: 0x%016" PRIx64 " %20" PRIi64 " \n", arg1, value, + value); + } else if (fReg != FloatRegisters::Invalid) { + printf("%3s: 0x%016" PRIx64 "\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 { + 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%016" PRIx64 " %20" PRIi64, 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) { + int64_t value; + if (getValue(arg1, &value)) { + cur = reinterpret_cast(value); + end = cur + (10 * SimInstruction::kInstrSize); + } + } else { + int64_t value; + if (getValue(arg1, &value)) { + cur = reinterpret_cast(sim_->get_pc()); + 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"); +#if defined(__x86_64__) + asm("int $3"); +#elif defined(__aarch64__) + asm("brk #0xf000"); +#endif + 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("CR: 0x%08x XER: 0x%016" PRIx64 "\n", sim_->getCR(), + sim_->getXER()); + } 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)) { + if (sim_->isStopInstruction(stop_instr)) { + stop_instr->setInstructionBits(kNopInstr); + msg_address->setInstructionBits(kNopInstr); + } else { + printf("Not at debugger stop.\n"); + } + } else if (argc == 3) { + 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) { + 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) { + 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("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 CR and XER\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"); + } else { + printf("Unknown command: %s\n", cmd); + } + } + } + + redoBreakpoints(); + +#undef COMMAND_SIZE +#undef ARG_SIZE + +#undef STR +#undef XSTR +} + +// ----------------------------------------------------------------------------- +// ICache helpers. + +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(SimulatorProcess::ICacheMap& i_cache, + void* page) { + SimulatorProcess::ICacheMap::AddPtr p = i_cache.lookupForAdd(page); + if (p) { + return p->value(); + } + AutoEnterOOMUnsafeRegion oomUnsafe; + CachePage* new_page = js_new(); + if (!new_page || !i_cache.add(p, page, new_page)) { + oomUnsafe.crash("Simulator CachePage"); + } + return new_page; +} + +static void FlushOnePageLocked(SimulatorProcess::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(SimulatorProcess::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 SimulatorProcess::checkICacheLocked(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(icache(), 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) { + mozilla::DebugOnly cmpret = + memcmp(reinterpret_cast(instr), cache_page->cachedData(offset), + SimInstruction::kInstrSize); + MOZ_ASSERT(cmpret == 0); + } else { + memcpy(cached_line, line, CachePage::kLineLength); + *cache_valid_byte = CachePage::LINE_VALID; + } +} + +HashNumber SimulatorProcess::ICacheHasher::hash(const Lookup& l) { + return U32(reinterpret_cast(l)) >> 2; +} + +bool SimulatorProcess::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; +} + +/* static */ +void SimulatorProcess::FlushICache(void* start_addr, size_t size) { + if (!ICacheCheckingDisableCount) { + AutoLockSimulatorCache als; + js::jit::FlushICacheLocked(icache(), start_addr, size); + } +} + +// ----------------------------------------------------------------------------- +// Redirection. + +class Redirection { + friend class SimulatorProcess; + + Redirection(void* nativeFunction, ABIFunctionType type) + : nativeFunction_(nativeFunction), + swiInstruction_(kCallRedirInstr), + type_(type), + next_(nullptr) { + next_ = SimulatorProcess::redirection(); + if (!SimulatorProcess::ICacheCheckingDisableCount) { + FlushICacheLocked(SimulatorProcess::icache(), addressOfSwiInstruction(), + SimInstruction::kInstrSize); + } + SimulatorProcess::setRedirection(this); + } + + public: + void* addressOfSwiInstruction() { return &swiInstruction_; } + void* nativeFunction() const { return nativeFunction_; } + ABIFunctionType type() const { return type_; } + + static Redirection* Get(void* nativeFunction, ABIFunctionType type) { + AutoLockSimulatorCache als; + + Redirection* current = SimulatorProcess::redirection(); + for (; current != nullptr; current = current->next_) { + if (current->nativeFunction_ == nativeFunction) { + MOZ_ASSERT(current->type() == type); + return current; + } + } + + AutoEnterOOMUnsafeRegion oomUnsafe; + Redirection* redir = js_pod_malloc(1); + if (!redir) { + oomUnsafe.crash("Simulator redirection"); + } + new (redir) Redirection(nativeFunction, type); + 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 constructor / destructor / init. + +Simulator::Simulator() { + stack_ = nullptr; + stackLimit_ = 0; + pc_modified_ = false; + icount_ = 0; + break_count_ = 0; + break_pc_ = nullptr; + break_instr_ = 0; + single_stepping_ = false; + single_step_callback_ = nullptr; + single_step_callback_arg_ = nullptr; + + for (int i = 0; i < Register::kNumSimuRegisters; i++) { + registers_[i] = 0; + } + for (int i = 0; i < Simulator::FPURegister::kNumFPURegisters; i++) { + FPUregisters_[i] = 0; + } + + LR_ = 0; + CTR_ = 0; + CR_ = 0; + XER_ = 0; + FPSCR_ = 0; + LLBit_ = false; + LLAddr_ = 0; + lastLLValue_ = 0; + + // Initialize PC and LR to a known bad value that will cause an + // access violation if the simulator ever tries to execute it. + registers_[pc] = bad_ra; + LR_ = bad_ra; + + lastDebuggerInput_ = nullptr; +} + +bool Simulator::init() { + static const size_t stackSize = 2 * 1024 * 1024; + stack_ = js_pod_malloc(stackSize); + if (!stack_) { + return false; + } + + // Leave a safety margin of 1MB to prevent overrunning the stack. + stackLimit_ = reinterpret_cast(stack_) + 1024 * 1024; + + // The sp is initialized to point to the bottom (high address) of the + // allocated stack area. + registers_[sp] = reinterpret_cast(stack_) + stackSize - 64; + + // Zero-initialize VR namespace. Simulated PPC does not guarantee any + // value in VRs at entry, but zeroing avoids uninitialized-read false + // positives in tools and makes regression traces deterministic. + memset(VRregisters_, 0, sizeof(VRregisters_)); + + return true; +} + +Simulator::~Simulator() { js_free(stack_); } + +SimulatorProcess::SimulatorProcess() + : cacheLock_(mutexid::SimulatorCacheLock), redirection_(nullptr) { + if (getenv("PPC_SIM_ICACHE_CHECKS")) { + ICacheCheckingDisableCount = 0; + } +} + +SimulatorProcess::~SimulatorProcess() { + 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(); +} + +Simulator* Simulator::Current() { + JSContext* cx = TlsContext.get(); + MOZ_ASSERT(CurrentThreadCanAccessRuntime(cx->runtime())); + return cx->simulator(); +} + +// ----------------------------------------------------------------------------- +// Register accessors. + +void Simulator::setRegister(int reg, int64_t value) { + MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters)); + if (reg == pc) { + pc_modified_ = true; + } + registers_[reg] = value; +} + +int64_t Simulator::getRegister(int reg) const { + MOZ_ASSERT((reg >= 0) && (reg < Register::kNumSimuRegisters)); + return registers_[reg] + ((reg == pc) ? SimInstruction::kPCReadOffset : 0); +} + +void Simulator::setFpuRegister(int fpureg, int64_t value) { + MOZ_ASSERT((fpureg >= 0) && + (fpureg < Simulator::FPURegister::kNumFPURegisters)); + FPUregisters_[fpureg] = value; +} + +void Simulator::setFpuRegisterWord(int fpureg, int32_t value) { + MOZ_ASSERT((fpureg >= 0) && + (fpureg < Simulator::FPURegister::kNumFPURegisters)); + int32_t* pword; + pword = reinterpret_cast(&FPUregisters_[fpureg]); + *pword = value; +} + +// Promote f32 → f64 preserving NaN payload, like PPC's `lfs` and +// `xscvspdpn`. The plain C cast `(double)f32_nan` is permitted by the +// standard to quiet a signaling NaN, which on x86/ARM hosts visibly +// transforms 0x7FA00000 (sNaN) into a qNaN such as 0x7FE00000 — breaking +// every wasm test that loads a constant sNaN bit pattern. Manually +// reconstruct the f64 NaN with the same sign + payload (payload shifted +// left by 29 to fill the wider mantissa). +static double promoteFloatPreservingNaN(float f) { + uint32_t bits; + memcpy(&bits, &f, sizeof(bits)); + if ((bits & 0x7F800000u) == 0x7F800000u && (bits & 0x007FFFFFu) != 0u) { + uint64_t sign = uint64_t(bits >> 31) & 1u; + uint64_t payload = uint64_t(bits & 0x007FFFFFu); + uint64_t dbits = (sign << 63) | (uint64_t(0x7FFu) << 52) | (payload << 29); + double d; + memcpy(&d, &dbits, sizeof(d)); + return d; + } + return (double)f; +} + +// Demote f64 → f32 preserving NaN payload (non-signaling: matches PPC +// `stfs` / `xscvdpspn`, and wasm `lfs`-equivalent stores). Truncates the +// lower 29 bits of the f64 payload (those bits cannot be represented in +// the narrower f32 mantissa); if the truncation would yield a payload of +// zero (which would degrade the NaN to an Infinity), force the LSB so +// the result is still a NaN. This intentionally does NOT set the quiet +// bit — that's the job of the explicit-quieting op `xscvdpsp` and +// f32.demote_f64's wasm-level lowering. +static float demoteDoublePreservingNaN(double d) { + uint64_t bits; + memcpy(&bits, &d, sizeof(bits)); + if ((bits & 0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && + (bits & 0x000FFFFFFFFFFFFFULL) != 0) { + uint32_t sign = uint32_t(bits >> 63) & 1u; + uint32_t payload = uint32_t((bits >> 29) & 0x007FFFFFu); + if (payload == 0) payload = 1; + uint32_t fbits = (sign << 31) | 0x7F800000u | payload; + float f; + memcpy(&f, &fbits, sizeof(f)); + return f; + } + return (float)d; +} + +void Simulator::setFpuRegisterFloat(int fpureg, float value) { + MOZ_ASSERT((fpureg >= 0) && + (fpureg < Simulator::FPURegister::kNumFPURegisters)); + // ELFv2 ABI: single-precision values in FPRs are stored as their + // double-precision representation. Promote and store the full 8 bytes, + // not just the low 4. (Otherwise the upper 4 bytes are stale, matching + // the layout that fctid/fcfid/lfd would read but NOT what the JIT and + // the C ABI expect for a 'float' parameter.) Use the NaN-preserving + // helper so a signaling-NaN return value isn't quieted into a qNaN. + double promoted = promoteFloatPreservingNaN(value); + memcpy(&FPUregisters_[fpureg], &promoted, sizeof(promoted)); +} + +void Simulator::setFpuRegisterDouble(int fpureg, double value) { + MOZ_ASSERT((fpureg >= 0) && + (fpureg < Simulator::FPURegister::kNumFPURegisters)); + *mozilla::BitwiseCast(&FPUregisters_[fpureg]) = value; +} + +int64_t Simulator::getFpuRegister(int fpureg) const { + MOZ_ASSERT((fpureg >= 0) && + (fpureg < Simulator::FPURegister::kNumFPURegisters)); + return FPUregisters_[fpureg]; +} + +int32_t Simulator::getFpuRegisterWord(int fpureg) const { + MOZ_ASSERT((fpureg >= 0) && + (fpureg < Simulator::FPURegister::kNumFPURegisters)); + return *mozilla::BitwiseCast(&FPUregisters_[fpureg]); +} + +int32_t Simulator::getFpuRegisterSignedWord(int fpureg) const { + MOZ_ASSERT((fpureg >= 0) && + (fpureg < Simulator::FPURegister::kNumFPURegisters)); + return *mozilla::BitwiseCast(&FPUregisters_[fpureg]); +} + +float Simulator::getFpuRegisterFloat(int fpureg) const { + MOZ_ASSERT((fpureg >= 0) && + (fpureg < Simulator::FPURegister::kNumFPURegisters)); + // ELFv2 ABI: single-precision values are passed/returned in FPRs as their + // double-precision representation. Read the full 8 bytes as double, then + // narrow to float — matching the `frsp` the C callee would do, and matching + // what real PPC hardware sees when the FPR was loaded via `lfs`. Use the + // NaN-preserving helper so a signaling-NaN parameter isn't quieted. + double promoted; + memcpy(&promoted, &FPUregisters_[fpureg], sizeof(promoted)); + return demoteDoublePreservingNaN(promoted); +} + +double Simulator::getFpuRegisterDouble(int fpureg) const { + MOZ_ASSERT((fpureg >= 0) && + (fpureg < Simulator::FPURegister::kNumFPURegisters)); + return *mozilla::BitwiseCast(&FPUregisters_[fpureg]); +} + +void Simulator::setVRBytes(int vreg, const uint8_t bytes[16]) { + MOZ_ASSERT((vreg >= 0) && (vreg < kNumVRRegisters)); + memcpy(VRregisters_[vreg], bytes, 16); +} + +void Simulator::getVRBytes(int vreg, uint8_t bytes[16]) const { + MOZ_ASSERT((vreg >= 0) && (vreg < kNumVRRegisters)); + memcpy(bytes, VRregisters_[vreg], 16); +} + +void Simulator::getVSR128(int vsr, uint8_t bytes[16]) const { + MOZ_ASSERT((vsr >= 0) && (vsr < kNumFPURegisters + kNumVRRegisters)); + if (vsr < kNumFPURegisters) { + // VSR 0-31: FPR view. The FPR scalar lives in BE DW0 of the VSR, + // which on PPCLE register storage maps to LE bytes 8-15. + // DW1 is undefined per ISA; we model it as zero. + // `lfd f0,(mem); xxlor ,f0,f0; stxvx ,...` writes the + // double's 8 bytes to the HIGH half of the 16-byte store (LE + // bytes 8-15). + int64_t val = FPUregisters_[vsr]; + memset(bytes, 0, 8); + memcpy(bytes + 8, &val, 8); + } else { + memcpy(bytes, VRregisters_[vsr - kNumFPURegisters], 16); + } +} + +void Simulator::setVSR128(int vsr, const uint8_t bytes[16]) { + MOZ_ASSERT((vsr >= 0) && (vsr < kNumFPURegisters + kNumVRRegisters)); + if (vsr < kNumFPURegisters) { + // FPR scalar at BE DW0 = LE bytes 8-15. DW1 is architecturally + // discarded on VSR-to-FPR writes. + int64_t val; + memcpy(&val, bytes + 8, 8); + FPUregisters_[vsr] = val; + } else { + memcpy(VRregisters_[vsr - kNumFPURegisters], bytes, 16); + } +} + +void Simulator::setCallResultDouble(double result) { + setFpuRegisterDouble(Simulator::f1, result); +} + +void Simulator::setCallResultFloat(float result) { + setFpuRegisterFloat(Simulator::f1, result); +} + +void Simulator::setCallResult(int64_t res) { setRegister(r3, res); } + +#ifdef XP_DARWIN +void Simulator::setCallResult(intptr_t res) { + setRegister(r3, I64(res)); +} +#endif + +void Simulator::setCallResult(__int128 res) { + setRegister(r3, I64(res)); + setRegister(r4, I64(res >> 64)); +} + +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)); +} + +int64_t Simulator::get_pc() const { return registers_[pc]; } + +JS::ProfilingFrameIterator::RegisterState Simulator::registerState() { + wasm::RegisterState state; + state.pc = (void*)get_pc(); + state.fp = (void*)getRegister(fp); + state.sp = (void*)getRegister(sp); + state.lr = (void*)getLR(); + return state; +} + +// ----------------------------------------------------------------------------- +// Memory access helpers. + +uint8_t Simulator::readBU(uint64_t addr) { + if (handleWasmSegFault(addr, 1)) { + return 0xff; + } + uint8_t* ptr = reinterpret_cast(addr); + return *ptr; +} + +int8_t Simulator::readB(uint64_t addr) { + if (handleWasmSegFault(addr, 1)) { + return -1; + } + int8_t* ptr = reinterpret_cast(addr); + return *ptr; +} + +void Simulator::writeB(uint64_t addr, uint8_t value) { + if (handleWasmSegFault(addr, 1)) { + return; + } + uint8_t* ptr = reinterpret_cast(addr); + *ptr = value; +} + +void Simulator::writeB(uint64_t addr, int8_t value) { + if (handleWasmSegFault(addr, 1)) { + return; + } + int8_t* ptr = reinterpret_cast(addr); + *ptr = value; +} + +uint16_t Simulator::readHU(uint64_t addr, SimInstruction* instr) { + if (handleWasmSegFault(addr, 2)) { + return 0xffff; + } + uint16_t* ptr = reinterpret_cast(addr); + return *ptr; +} + +int16_t Simulator::readH(uint64_t addr, SimInstruction* instr) { + if (handleWasmSegFault(addr, 2)) { + return -1; + } + int16_t* ptr = reinterpret_cast(addr); + return *ptr; +} + +void Simulator::writeH(uint64_t addr, uint16_t value, SimInstruction* instr) { + if (handleWasmSegFault(addr, 2)) { + return; + } + uint16_t* ptr = reinterpret_cast(addr); + LLBit_ = false; + *ptr = value; +} + +void Simulator::writeH(uint64_t addr, int16_t value, SimInstruction* instr) { + if (handleWasmSegFault(addr, 2)) { + return; + } + int16_t* ptr = reinterpret_cast(addr); + LLBit_ = false; + *ptr = value; +} + +uint32_t Simulator::readWU(uint64_t addr, SimInstruction* instr) { + if (handleWasmSegFault(addr, 4)) { + return -1; + } + uint32_t* ptr = reinterpret_cast(addr); + return *ptr; +} + +int32_t Simulator::readW(uint64_t addr, SimInstruction* instr) { + if (handleWasmSegFault(addr, 4)) { + return -1; + } + int32_t* ptr = reinterpret_cast(addr); + return *ptr; +} + +void Simulator::writeW(uint64_t addr, uint32_t value, SimInstruction* instr) { + if (handleWasmSegFault(addr, 4)) { + return; + } + uint32_t* ptr = reinterpret_cast(addr); + LLBit_ = false; + *ptr = value; +} + +void Simulator::writeW(uint64_t addr, int32_t value, SimInstruction* instr) { + if (handleWasmSegFault(addr, 4)) { + return; + } + int32_t* ptr = reinterpret_cast(addr); + LLBit_ = false; + *ptr = value; +} + +int64_t Simulator::readDW(uint64_t addr, SimInstruction* instr) { + if (handleWasmSegFault(addr, 8)) { + return -1; + } + int64_t* ptr = reinterpret_cast(addr); + return *ptr; +} + +void Simulator::writeDW(uint64_t addr, int64_t value, SimInstruction* instr) { + if (handleWasmSegFault(addr, 8)) { + return; + } + int64_t* ptr = reinterpret_cast(addr); + LLBit_ = false; + *ptr = value; +} + +double Simulator::readD(uint64_t addr, SimInstruction* instr) { + if (handleWasmSegFault(addr, 8)) { + return NAN; + } + double* ptr = reinterpret_cast(addr); + return *ptr; +} + +void Simulator::writeD(uint64_t addr, double value, SimInstruction* instr) { + if (handleWasmSegFault(addr, 8)) { + return; + } + double* ptr = reinterpret_cast(addr); + LLBit_ = false; + *ptr = value; +} + +// Byte-wide load-reserve / store-conditional (lbarx / stbcx.). +// Byte accesses have no alignment requirement. +uint8_t Simulator::loadLinkedB(uint64_t addr, SimInstruction* instr) { + if (handleWasmSegFault(addr, 1)) { + return 0; + } + volatile uint8_t* ptr = reinterpret_cast(addr); + uint8_t value = *ptr; + lastLLValue_ = value; + LLAddr_ = addr; + LLBit_ = true; + return value; +} + +int Simulator::storeConditionalB(uint64_t addr, uint8_t value, + SimInstruction* instr) { + if (addr != LLAddr_) { + printf("stbcx. to bad address: 0x%016" PRIx64 ", pc=0x%016" PRIxPTR + ", expected: 0x%016" PRIxPTR "\n", + addr, reinterpret_cast(instr), LLAddr_); + MOZ_CRASH(); + } + SharedMem ptr = + SharedMem::shared(reinterpret_cast(addr)); + if (!LLBit_) { + return 0; + } + LLBit_ = false; + LLAddr_ = 0; + uint8_t expected = uint8_t(lastLLValue_); + uint8_t old = + AtomicOperations::compareExchangeSeqCst(ptr, expected, value); + return (old == expected) ? 1 : 0; +} + +// Halfword-wide load-reserve / store-conditional (lharx / sthcx.). +// 2-byte aligned per ISA. +uint16_t Simulator::loadLinkedH(uint64_t addr, SimInstruction* instr) { + if ((addr & 1) == 0) { + if (handleWasmSegFault(addr, 2)) { + return 0; + } + volatile uint16_t* ptr = reinterpret_cast(addr); + uint16_t value = *ptr; + lastLLValue_ = value; + LLAddr_ = addr; + LLBit_ = true; + return value; + } + printf("Unaligned lharx at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, + reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +int Simulator::storeConditionalH(uint64_t addr, uint16_t value, + SimInstruction* instr) { + if (addr != LLAddr_) { + printf("sthcx. to bad address: 0x%016" PRIx64 ", pc=0x%016" PRIxPTR + ", expected: 0x%016" PRIxPTR "\n", + addr, reinterpret_cast(instr), LLAddr_); + MOZ_CRASH(); + } + if ((addr & 1) == 0) { + SharedMem ptr = + SharedMem::shared(reinterpret_cast(addr)); + if (!LLBit_) { + return 0; + } + LLBit_ = false; + LLAddr_ = 0; + uint16_t expected = uint16_t(lastLLValue_); + uint16_t old = + AtomicOperations::compareExchangeSeqCst(ptr, expected, value); + return (old == expected) ? 1 : 0; + } + printf("Unaligned sthcx. at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, + reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +int32_t Simulator::loadLinkedW(uint64_t addr, SimInstruction* instr) { + if ((addr & 3) == 0) { + if (handleWasmSegFault(addr, 4)) { + return -1; + } + + volatile int32_t* ptr = reinterpret_cast(addr); + int32_t value = *ptr; + lastLLValue_ = value; + LLAddr_ = addr; + LLBit_ = true; + return value; + } + printf("Unaligned lwarx at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, + reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +int Simulator::storeConditionalW(uint64_t addr, int32_t value, + SimInstruction* instr) { + if (addr != LLAddr_) { + printf("stwcx. to bad address: 0x%016" PRIx64 ", pc=0x%016" PRIxPTR + ", expected: 0x%016" PRIxPTR "\n", + addr, reinterpret_cast(instr), LLAddr_); + MOZ_CRASH(); + } + + if ((addr & 3) == 0) { + SharedMem ptr = + SharedMem::shared(reinterpret_cast(addr)); + + if (!LLBit_) { + return 0; + } + + LLBit_ = false; + LLAddr_ = 0; + int32_t expected = int32_t(lastLLValue_); + int32_t old = + AtomicOperations::compareExchangeSeqCst(ptr, expected, value); + return (old == expected) ? 1 : 0; + } + printf("Unaligned stwcx. at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, + reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +int64_t Simulator::loadLinkedD(uint64_t addr, SimInstruction* instr) { + if ((addr & kPointerAlignmentMask) == 0) { + if (handleWasmSegFault(addr, 8)) { + return -1; + } + + volatile int64_t* ptr = reinterpret_cast(addr); + int64_t value = *ptr; + lastLLValue_ = value; + LLAddr_ = addr; + LLBit_ = true; + return value; + } + printf("Unaligned ldarx at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, + reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +int Simulator::storeConditionalD(uint64_t addr, int64_t value, + SimInstruction* instr) { + if (addr != LLAddr_) { + printf("stdcx. to bad address: 0x%016" PRIx64 ", pc=0x%016" PRIxPTR + ", expected: 0x%016" PRIxPTR "\n", + addr, reinterpret_cast(instr), LLAddr_); + MOZ_CRASH(); + } + + if ((addr & kPointerAlignmentMask) == 0) { + SharedMem ptr = + SharedMem::shared(reinterpret_cast(addr)); + + if (!LLBit_) { + return 0; + } + + LLBit_ = false; + LLAddr_ = 0; + int64_t expected = lastLLValue_; + int64_t old = + AtomicOperations::compareExchangeSeqCst(ptr, expected, value); + return (old == expected) ? 1 : 0; + } + printf("Unaligned stdcx. at 0x%016" PRIx64 ", pc=0x%016" PRIxPTR "\n", addr, + reinterpret_cast(instr)); + MOZ_CRASH(); + return 0; +} + +// ----------------------------------------------------------------------------- +// Stack limit / recursion helpers. + +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(); +} + +void Simulator::format(SimInstruction* instr, const char* format) { + printf("Simulator found unsupported instruction:\n 0x%016" PRIxPTR + ": %08x %s\n", + reinterpret_cast(instr), instr->instructionBits(), format); + MOZ_CRASH(); +} + +// ----------------------------------------------------------------------------- +// softwareInterrupt - handle kCallRedirInstr (PPC_stop) and PPC_trap. + +ABI_FUNCTION_TYPE_SIM_PROTOTYPES + +void Simulator::softwareInterrupt(SimInstruction* instr) { + uint32_t instrBits = instr->instructionBits(); + + if (instrBits == kCallRedirInstr) { + Redirection* redirection = Redirection::FromSwiInstruction(instr); + uintptr_t nativeFn = + reinterpret_cast(redirection->nativeFunction()); + + // Get the SP for reading stack arguments. + int64_t* sp_ = reinterpret_cast(getRegister(sp)); + // Skip past the PPC ELFv2 link area (4 doublewords = 32 bytes). + sp_ = reinterpret_cast(reinterpret_cast(sp_) + 32); + + // PPC ELFv2: integer args in r3-r10, FP args in f1-f13. + int64_t a0_ = getRegister(r3); + int64_t a1_ = getRegister(r4); + int64_t a2_ = getRegister(r5); + int64_t a3_ = getRegister(r6); + int64_t a4_ = getRegister(r7); + int64_t a5_ = getRegister(r8); + int64_t a6_ = getRegister(r9); + int64_t a7_ = getRegister(r10); + // PPC ELFv2: FP args in f1-f13, mapped to f0_s..f12_s and f0_d..f12_d. + float f0_s = getFpuRegisterFloat(Simulator::f1); + float f1_s = getFpuRegisterFloat(Simulator::f2); + float f2_s = getFpuRegisterFloat(Simulator::f3); + float f3_s = getFpuRegisterFloat(Simulator::f4); + float f4_s = getFpuRegisterFloat(Simulator::f5); + float f5_s = getFpuRegisterFloat(Simulator::f6); + float f6_s = getFpuRegisterFloat(Simulator::f7); + float f7_s = getFpuRegisterFloat(Simulator::f8); + float f8_s = getFpuRegisterFloat(Simulator::f9); + float f9_s = getFpuRegisterFloat(Simulator::f10); + float f10_s = getFpuRegisterFloat(Simulator::f11); + float f11_s = getFpuRegisterFloat(Simulator::f12); + float f12_s = getFpuRegisterFloat(Simulator::f13); + double f0_d = getFpuRegisterDouble(Simulator::f1); + double f1_d = getFpuRegisterDouble(Simulator::f2); + double f2_d = getFpuRegisterDouble(Simulator::f3); + double f3_d = getFpuRegisterDouble(Simulator::f4); + double f4_d = getFpuRegisterDouble(Simulator::f5); + double f5_d = getFpuRegisterDouble(Simulator::f6); + double f6_d = getFpuRegisterDouble(Simulator::f7); + double f7_d = getFpuRegisterDouble(Simulator::f8); + double f8_d = getFpuRegisterDouble(Simulator::f9); + double f9_d = getFpuRegisterDouble(Simulator::f10); + double f10_d = getFpuRegisterDouble(Simulator::f11); + double f11_d = getFpuRegisterDouble(Simulator::f12); + double f12_d = getFpuRegisterDouble(Simulator::f13); + + // Suppress unused-variable warnings for higher FP arg registers. + // They exist for ABI completeness but few function types use >5 FP args. + (void)f4_s; (void)f5_s; (void)f6_s; (void)f7_s; (void)f8_s; (void)f9_s; + (void)f10_s; (void)f11_s; (void)f12_s; + (void)f4_d; (void)f5_d; (void)f6_d; (void)f7_d; (void)f8_d; (void)f9_d; + (void)f10_d; (void)f11_d; (void)f12_d; + + int64_t saved_lr = getLR(); + + 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()) { + ABI_FUNCTION_TYPE_PPC_SIM_DISPATCH + + default: + MOZ_CRASH("Unknown function type."); + } + + if (single_stepping_) { + single_step_callback_(single_step_callback_arg_, this, nullptr); + } + + setLR(saved_lr); + set_pc(getLR()); + } else if (instrBits == 0x7FE00008) { + // PPC_trap: used for wasm traps. + uint8_t* newPC; + if (wasm::HandleIllegalInstruction(registerState(), &newPC)) { + set_pc(int64_t(newPC)); + return; + } + MOZ_CRASH("Unexpected trap instruction"); + } else { + // Other trap-like instructions: enter debugger. + ppcDebugger dbg(this); + dbg.debug(); + } +} + +// ----------------------------------------------------------------------------- +// Stop/breakpoint helpers. + +bool Simulator::isWatchpoint(uint32_t code) { + return (code <= kMaxWatchpointCode); +} + +void Simulator::printWatchpoint(uint32_t code) { + ppcDebugger dbg(this); + ++break_count_; + printf("\n---- break %d marker: %20" PRIi64 " (instr count: %20" PRIi64 + ") ----\n", + code, break_count_, icount_); + dbg.printAllRegs(); +} + +void Simulator::handleStop(uint32_t code, SimInstruction* instr) { + if (isEnabledStop(code)) { + ppcDebugger dbg(this); + dbg.stop(instr); + } else { + set_pc(get_pc() + SimInstruction::kInstrSize); + } +} + +bool Simulator::isStopInstruction(SimInstruction* instr) { + return instr->instructionBits() == kCallRedirInstr; +} + +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_++; + } +} + +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; + 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); + } + } +} + +// ============================================================================= +// Instruction decoders. +// ============================================================================= + +// Compute effective address for D-form instructions. +// If RA==0, the base is 0 (not GPR[0]). +static inline int64_t DFormEA(Simulator* sim, SimInstruction* instr, + int16_t offset) { + uint32_t ra = instr->raValue(); + int64_t base = (ra == 0) ? 0 : sim->getRegister(ra); + return base + offset; +} + +// Compute effective address for DS-form instructions. +static inline int64_t DSFormEA(Simulator* sim, SimInstruction* instr, + int16_t offset) { + uint32_t ra = instr->raValue(); + int64_t base = (ra == 0) ? 0 : sim->getRegister(ra); + return base + offset; +} + +// Compute effective address for X-form indexed instructions. +// If RA==0, base is 0 (not GPR[0]). +static inline int64_t XFormEA(Simulator* sim, SimInstruction* instr) { + uint32_t ra = instr->raValue(); + uint32_t rb = instr->rbValue(); + int64_t base = (ra == 0) ? 0 : sim->getRegister(ra); + return base + sim->getRegister(rb); +} + +// Compute effective address for X-form indexed updates (RA != 0 required). +static inline int64_t XFormEAUpdate(Simulator* sim, SimInstruction* instr) { + uint32_t ra = instr->raValue(); + uint32_t rb = instr->rbValue(); + return sim->getRegister(ra) + sim->getRegister(rb); +} + +// ----------------------------------------------------------------------------- +// decodeDFormALU: addi, addis, ori, oris, xori, xoris, andi., andis., +// cmpi, cmpli, subfic, addic, addic., mulli, twi + +void Simulator::decodeDFormALU(SimInstruction* instr) { + uint32_t opcode = instr->opcode(); + uint32_t rt = instr->rtValue(); + uint32_t ra = instr->raValue(); + int16_t si = instr->imm16Value(); + uint16_t ui = instr->uimm16Value(); + + switch (opcode) { + case 14: { + // addi: RT = (RA|0) + SI + int64_t base = (ra == 0) ? 0 : getRegister(ra); + setRegister(rt, base + (int64_t)si); + break; + } + case 15: { + // addis: RT = (RA|0) + (SI << 16) + int64_t base = (ra == 0) ? 0 : getRegister(ra); + setRegister(rt, base + ((int64_t)si << 16)); + break; + } + case 24: { + // ori: RA = RS | UI + setRegister(ra, getRegister(rt) | (uint64_t)ui); + break; + } + case 25: { + // oris: RA = RS | (UI << 16) + setRegister(ra, getRegister(rt) | ((uint64_t)ui << 16)); + break; + } + case 26: { + // xori: RA = RS ^ UI + setRegister(ra, getRegister(rt) ^ (uint64_t)ui); + break; + } + case 27: { + // xoris: RA = RS ^ (UI << 16) + setRegister(ra, getRegister(rt) ^ ((uint64_t)ui << 16)); + break; + } + case 28: { + // andi.: RA = RS & UI, update CR0 + int64_t result = getRegister(rt) & (uint64_t)ui; + setRegister(ra, result); + updateCR0(result); + break; + } + case 29: { + // andis.: RA = RS & (UI << 16), update CR0 + int64_t result = getRegister(rt) & ((uint64_t)ui << 16); + setRegister(ra, result); + updateCR0(result); + break; + } + case 11: { + // cmpi: compare RA with SI, signed + uint32_t bf = instr->bfValue(); + bool l = instr->lBit(); + if (l) { + // 64-bit compare + setCRFieldCmp(bf, getRegister(ra), (int64_t)si); + } else { + // 32-bit compare + int32_t ra32 = I32(getRegister(ra)); + setCRFieldCmp(bf, (int64_t)ra32, (int64_t)(int32_t)si); + } + break; + } + case 10: { + // cmpli: compare RA with UI, unsigned + uint32_t bf = instr->bfValue(); + bool l = instr->lBit(); + if (l) { + // 64-bit unsigned compare + setCRFieldCmpU(bf, U64(getRegister(ra)), (uint64_t)ui); + } else { + // 32-bit unsigned compare + uint32_t ra32 = U32(getRegister(ra)); + setCRFieldCmpU(bf, (uint64_t)ra32, (uint64_t)ui); + } + break; + } + case 8: { + // subfic: RT = SI - RA, set CA + uint64_t ra_val = U64(getRegister(ra)); + uint64_t imm = U64((int64_t)si); + uint64_t result = imm + ~ra_val + 1; + setRegister(rt, I64(result)); + // CA is set if there is a carry out of the addition (~RA + IMM + 1). + // Equivalently, CA = (IMM >= RA) for unsigned interpretation of the + // full 64-bit subtraction. + bool carry = (imm >= ra_val) || (imm == 0 && ra_val == 0); + // More precise: carry = (~ra_val + imm) would overflow, or adding 1 + // overflows. + uint64_t tmp = ~ra_val + imm; + carry = (tmp < ~ra_val) || (tmp < imm) || (result < tmp); + // Simplify: CA if no borrow. + carry = (U64((int64_t)si) >= ra_val); + if (ra_val == 0) carry = true; + // Actually, subfic CA: carry out of ~RA + IMM + 1. + // CA = (IMM > RA - 1) when RA != 0, CA = 1 when RA == 0. + // Or just: the unsigned result of (SI - RA) is valid (no borrow). + // Let's compute it correctly: + { + __uint128_t wide = (__uint128_t)(~ra_val) + (__uint128_t)imm + 1; + carry = (wide >> 64) != 0; + } + setXERCA(carry); + break; + } + case 12: { + // addic: RT = RA + SI, set CA + uint64_t ra_val = U64(getRegister(ra)); + uint64_t imm = U64((int64_t)si); + uint64_t result = ra_val + imm; + setRegister(rt, I64(result)); + setXERCA(result < ra_val); + break; + } + case 13: { + // addic.: RT = RA + SI, set CA, update CR0 + uint64_t ra_val = U64(getRegister(ra)); + uint64_t imm = U64((int64_t)si); + uint64_t result = ra_val + imm; + setRegister(rt, I64(result)); + setXERCA(result < ra_val); + updateCR0(I64(result)); + break; + } + case 7: { + // mulli: RT = RA * SI (low 64 bits) + int64_t result = getRegister(ra) * (int64_t)si; + setRegister(rt, result); + break; + } + case 3: { + // twi: Trap Word Immediate. We don't implement trapping in the + // simulator; just continue. + break; + } + default: + MOZ_CRASH_UNSAFE_PRINTF("decodeDFormALU: unhandled opcode %u", opcode); + } +} + +// ----------------------------------------------------------------------------- +// decodeDFormLoad: lwz(32), lbz(34), lhz(40), lha(42), lfs(48), lfd(50) +// and update variants + +void Simulator::decodeDFormLoad(SimInstruction* instr) { + uint32_t opcode = instr->opcode(); + uint32_t rt = instr->rtValue(); + int16_t si = instr->imm16Value(); + uint64_t ea = DFormEA(this, instr, si); + + switch (opcode) { + case 32: + // lwz + setRegister(rt, U64(readWU(ea, instr))); + break; + case 33: { + // lwzu: RA != 0, load and update RA + setRegister(rt, U64(readWU(ea, instr))); + setRegister(instr->raValue(), ea); + break; + } + case 34: + // lbz + setRegister(rt, U64(readBU(ea))); + break; + case 35: { + // lbzu + setRegister(rt, U64(readBU(ea))); + setRegister(instr->raValue(), ea); + break; + } + case 40: + // lhz + setRegister(rt, U64(readHU(ea, instr))); + break; + case 41: { + // lhzu + setRegister(rt, U64(readHU(ea, instr))); + setRegister(instr->raValue(), ea); + break; + } + case 42: + // lha (half-word, sign-extended) + setRegister(rt, (int64_t)readH(ea, instr)); + break; + case 43: { + // lhau + setRegister(rt, (int64_t)readH(ea, instr)); + setRegister(instr->raValue(), ea); + break; + } + case 48: { + // lfs: load float single, widen to double in FPR (NaN-preserving; + // matches Power ISA `lfs` which uses xscvspdpn semantics) + if (handleWasmSegFault(ea, 4)) break; + float val = *reinterpret_cast(ea); + setFpuRegisterDouble(rt, promoteFloatPreservingNaN(val)); + break; + } + case 49: { + // lfsu + if (handleWasmSegFault(ea, 4)) break; + float val = *reinterpret_cast(ea); + setFpuRegisterDouble(rt, promoteFloatPreservingNaN(val)); + setRegister(instr->raValue(), ea); + break; + } + case 50: { + // lfd: load float double + double val = readD(ea, instr); + setFpuRegisterDouble(rt, val); + break; + } + case 51: { + // lfdu + double val = readD(ea, instr); + setFpuRegisterDouble(rt, val); + setRegister(instr->raValue(), ea); + break; + } + default: + MOZ_CRASH_UNSAFE_PRINTF("decodeDFormLoad: unhandled opcode %u", opcode); + } +} + +// ----------------------------------------------------------------------------- +// decodeDFormStore: stw(36), stwu(37), stb(38), sth(44), stfs(52), stfd(54) +// and update variants + +void Simulator::decodeDFormStore(SimInstruction* instr) { + uint32_t opcode = instr->opcode(); + uint32_t rs = instr->rsValue(); + int16_t si = instr->imm16Value(); + + // For stores, the effective address calculation differs for update forms: + // - Non-update: EA = (RA|0) + D + // - Update: EA = RA + D (RA must not be 0) + bool isUpdate = false; + switch (opcode) { + case 37: case 39: case 45: case 53: case 55: + isUpdate = true; + break; + } + + uint64_t ea; + if (isUpdate) { + ea = getRegister(instr->raValue()) + (int64_t)si; + } else { + ea = DFormEA(this, instr, si); + } + + switch (opcode) { + case 36: + // stw + writeW(ea, I32(getRegister(rs)), instr); + break; + case 38: + // stb + writeB(ea, (uint8_t)(getRegister(rs) & 0xFF)); + break; + case 39: + // stbu + writeB(ea, (uint8_t)(getRegister(rs) & 0xFF)); + setRegister(instr->raValue(), ea); + break; + case 44: + // sth + writeH(ea, U16(getRegister(rs)), instr); + break; + case 45: + // sthu + writeH(ea, U16(getRegister(rs)), instr); + setRegister(instr->raValue(), ea); + break; + case 52: { + // stfs: convert double in FPR to single and store (NaN-preserving; + // matches Power ISA `stfs` which uses xscvdpspn semantics) + double dval = getFpuRegisterDouble(rs); + float fval = demoteDoublePreservingNaN(dval); + if (handleWasmSegFault(ea, 4)) break; + *reinterpret_cast(ea) = fval; + LLBit_ = false; + break; + } + case 53: { + // stfsu + double dval = getFpuRegisterDouble(rs); + float fval = demoteDoublePreservingNaN(dval); + if (handleWasmSegFault(ea, 4)) break; + *reinterpret_cast(ea) = fval; + LLBit_ = false; + setRegister(instr->raValue(), ea); + break; + } + case 54: + // stfd + writeD(ea, getFpuRegisterDouble(rs), instr); + break; + case 55: + // stfdu + writeD(ea, getFpuRegisterDouble(rs), instr); + setRegister(instr->raValue(), ea); + break; + default: + MOZ_CRASH_UNSAFE_PRINTF("decodeDFormStore: unhandled opcode %u", opcode); + } +} + +// ----------------------------------------------------------------------------- +// decodeDSForm: ld(58/0), lwa(58/2), std(62/0), stdu(62/1) + +void Simulator::decodeDSForm(SimInstruction* instr) { + uint32_t opcode = instr->opcode(); + uint32_t rt = instr->rtValue(); + int16_t ds = instr->ds14Value(); + uint32_t xo = instr->bits(1, 0); + + if (opcode == 58) { + uint64_t ea = DSFormEA(this, instr, ds); + switch (xo) { + case 0: + // ld + setRegister(rt, readDW(ea, instr)); + break; + case 1: { + // ldu + setRegister(rt, readDW(ea, instr)); + setRegister(instr->raValue(), ea); + break; + } + case 2: + // lwa (load word algebraic, sign-extended to 64) + setRegister(rt, (int64_t)readW(ea, instr)); + break; + default: + MOZ_CRASH_UNSAFE_PRINTF("decodeDSForm: opcode 58, xo=%u", xo); + } + } else if (opcode == 62) { + // For std/stdu, EA uses RA directly (no RA|0 rule). + uint64_t ea; + if (xo == 1) { + // stdu: update form + ea = getRegister(instr->raValue()) + (int64_t)ds; + } else { + ea = DSFormEA(this, instr, ds); + } + switch (xo) { + case 0: + // std + writeDW(ea, getRegister(rt), instr); + break; + case 1: + // stdu + writeDW(ea, getRegister(rt), instr); + setRegister(instr->raValue(), ea); + break; + default: + MOZ_CRASH_UNSAFE_PRINTF("decodeDSForm: opcode 62, xo=%u", xo); + } + } else { + MOZ_CRASH_UNSAFE_PRINTF("decodeDSForm: unhandled opcode %u", opcode); + } +} + +// ----------------------------------------------------------------------------- +// decodeXForm: Major opcode 31 (X-form, XO-form, etc.) +// This is the largest decoder covering most ALU, indexed load/store, SPR, +// and atomic instructions. + +void Simulator::decodeXForm(SimInstruction* instr) { + uint32_t xo = instr->xoValue(); + uint32_t rt = instr->rtValue(); + uint32_t ra = instr->raValue(); + uint32_t rb = instr->rbValue(); + bool rc = instr->rcBit(); + + // Many instructions share major opcode 31. Switch on extended opcode. + // For XO-form with OE=1, the xoValue() includes bit 10, so + // addo (266 | 512 = 778) etc. are separate cases. + + // First check for isel which uses bits 1-5 = 15 (XO = 15 in bits 1..5). + if ((xo & 0x1F) == 15) { + // isel: if CR[BC] then RT=RA else RT=RB + // BC is in bits 6..10 (the rc field position). + uint32_t bc = instr->rcValue(); + uint32_t crField = bc / 4; + uint32_t crBit = bc % 4; + uint8_t crFieldVal = getCRField(crField); + // PPC CR field bits: bit3=LT(8), bit2=GT(4), bit1=EQ(2), bit0=SO(1) + // Bit numbering within field: 0=LT, 1=GT, 2=EQ, 3=SO + bool bitSet; + switch (crBit) { + case 0: bitSet = (crFieldVal & kCRFieldLT) != 0; break; + case 1: bitSet = (crFieldVal & kCRFieldGT) != 0; break; + case 2: bitSet = (crFieldVal & kCRFieldEQ) != 0; break; + case 3: bitSet = (crFieldVal & kCRFieldSO) != 0; break; + default: bitSet = false; break; + } + int64_t raVal = (ra == 0) ? 0 : getRegister(ra); + int64_t rbVal = getRegister(rb); + setRegister(rt, bitSet ? raVal : rbVal); + return; + } + + switch (xo) { + // --- Arithmetic --- + case 266: { + // add + int64_t result = getRegister(ra) + getRegister(rb); + setRegister(rt, result); + if (rc) updateCR0(result); + break; + } + case 778: { + // addo + int64_t ra_val = getRegister(ra); + int64_t rb_val = getRegister(rb); + int64_t result = ra_val + rb_val; + setRegister(rt, result); + // Overflow if signs of inputs are same but result sign differs. + bool ov = ((ra_val ^ result) & (rb_val ^ result)) < 0; + setXEROV(ov); + if (rc) updateCR0(result); + break; + } + case 10: { + // addc + uint64_t ra_val = U64(getRegister(ra)); + uint64_t rb_val = U64(getRegister(rb)); + uint64_t result = ra_val + rb_val; + setRegister(rt, I64(result)); + setXERCA(result < ra_val); + if (rc) updateCR0(I64(result)); + break; + } + case 138: { + // adde: RT = RA + RB + CA + uint64_t ra_val = U64(getRegister(ra)); + uint64_t rb_val = U64(getRegister(rb)); + uint64_t ca = getXERCA() ? 1ULL : 0ULL; + uint64_t result = ra_val + rb_val + ca; + setRegister(rt, I64(result)); + // Carry-out: when ca==0, only the ra+rb wrap matters; when ca==1, + // an additional wrap occurs iff result <= ra_val. + bool newCA = ca ? (result <= ra_val) : (result < ra_val); + setXERCA(newCA); + if (rc) updateCR0(I64(result)); + break; + } + case 234: { + // addme: RT = RA + CA - 1 + uint64_t ra_val = U64(getRegister(ra)); + uint64_t ca = getXERCA() ? 1ULL : 0ULL; + uint64_t result = ra_val + ca + ~0ULL; // + CA + (-1) + setRegister(rt, I64(result)); + // CA if carry out of (RA + CA + 0xFFFFFFFFFFFFFFFF) + bool newCA = (ra_val != 0) || (ca != 0); + setXERCA(newCA); + if (rc) updateCR0(I64(result)); + break; + } + case 202: { + // addze: RT = RA + CA + uint64_t ra_val = U64(getRegister(ra)); + uint64_t ca = getXERCA() ? 1ULL : 0ULL; + uint64_t result = ra_val + ca; + setRegister(rt, I64(result)); + setXERCA(result < ra_val); + if (rc) updateCR0(I64(result)); + break; + } + case 40: { + // subf: RT = RB - RA + int64_t result = getRegister(rb) - getRegister(ra); + setRegister(rt, result); + if (rc) updateCR0(result); + break; + } + case 552: { + // subfo: RT = RB - RA, set OV + int64_t ra_val = getRegister(ra); + int64_t rb_val = getRegister(rb); + int64_t result = rb_val - ra_val; + setRegister(rt, result); + bool ov = ((rb_val ^ ra_val) & (rb_val ^ result)) < 0; + setXEROV(ov); + if (rc) updateCR0(result); + break; + } + case 8: { + // subfc: RT = ~RA + RB + 1 + uint64_t ra_val = U64(getRegister(ra)); + uint64_t rb_val = U64(getRegister(rb)); + uint64_t result = ~ra_val + rb_val + 1; + setRegister(rt, I64(result)); + // CA = no borrow = (RB >= RA unsigned) + setXERCA(rb_val >= ra_val); + if (rc) updateCR0(I64(result)); + break; + } + case 136: { + // subfe: RT = ~RA + RB + CA + uint64_t ra_val = U64(getRegister(ra)); + uint64_t rb_val = U64(getRegister(rb)); + uint64_t ca = getXERCA() ? 1ULL : 0ULL; + uint64_t result = ~ra_val + rb_val + ca; + setRegister(rt, I64(result)); + __uint128_t wide = (__uint128_t)(~ra_val) + (__uint128_t)rb_val + ca; + setXERCA((wide >> 64) != 0); + if (rc) updateCR0(I64(result)); + break; + } + case 232: { + // subfze: RT = ~RA + CA + uint64_t ra_val = U64(getRegister(ra)); + uint64_t ca = getXERCA() ? 1ULL : 0ULL; + uint64_t result = ~ra_val + ca; + setRegister(rt, I64(result)); + setXERCA(ca > ra_val); // CA if ~RA + CA overflows + if (rc) updateCR0(I64(result)); + break; + } + case 104: { + // neg: RT = -RA + int64_t result = -getRegister(ra); + setRegister(rt, result); + if (rc) updateCR0(result); + break; + } + + // --- Multiply --- + case 233: { + // mulld: RT = RA * RB (low 64 bits) + int64_t result = getRegister(ra) * getRegister(rb); + setRegister(rt, result); + if (rc) updateCR0(result); + break; + } + case 745: { + // mulldo: RT = RA * RB, set OV + int64_t ra_val = getRegister(ra); + int64_t rb_val = getRegister(rb); + int64_t result = ra_val * rb_val; + setRegister(rt, result); + // OV if high part of full 128-bit product is not all-sign. + int64_t hi = MultiplyHighSigned(ra_val, rb_val); + bool ov = (hi != (result >> 63)); + setXEROV(ov); + if (rc) updateCR0(result); + break; + } + case 235: { + // mullw: RT = sign_ext(RA[32:63] * RB[32:63]) + int64_t result = (int64_t)I32(getRegister(ra)) * + (int64_t)I32(getRegister(rb)); + setRegister(rt, result); + if (rc) updateCR0(result); + break; + } + case 747: { + // mullwo + int64_t ra_val = I32(getRegister(ra)); + int64_t rb_val = I32(getRegister(rb)); + int64_t result = ra_val * rb_val; + setRegister(rt, result); + bool ov = (result != (int64_t)I32(result)); + setXEROV(ov); + if (rc) updateCR0(result); + break; + } + case 73: { + // mulhd: RT = high 64 bits of RA * RB (signed) + setRegister(rt, MultiplyHighSigned(getRegister(ra), getRegister(rb))); + if (rc) updateCR0(getRegister(rt)); + break; + } + case 9: { + // mulhdu: RT = high 64 bits of RA * RB (unsigned) + setRegister(rt, I64(MultiplyHighUnsigned(U64(getRegister(ra)), + U64(getRegister(rb))))); + if (rc) updateCR0(getRegister(rt)); + break; + } + case 75: { + // mulhw: RT = high 32 bits of (RA[32:63] * RB[32:63]), signed + int64_t result = + (int64_t)I32(getRegister(ra)) * (int64_t)I32(getRegister(rb)); + setRegister(rt, result >> 32); + if (rc) updateCR0(getRegister(rt)); + break; + } + case 11: { + // mulhwu: RT = high 32 bits, unsigned + uint64_t result = + (uint64_t)U32(getRegister(ra)) * (uint64_t)U32(getRegister(rb)); + setRegister(rt, I64(result >> 32)); + if (rc) updateCR0(getRegister(rt)); + break; + } + + // --- Divide --- + case 489: { + // divd: RT = RA / RB (signed, 64-bit) + int64_t ra_val = getRegister(ra); + int64_t rb_val = getRegister(rb); + if (rb_val == 0 || (ra_val == INT64_MIN && rb_val == -1)) { + setRegister(rt, 0); + } else { + setRegister(rt, ra_val / rb_val); + } + if (rc) updateCR0(getRegister(rt)); + break; + } + case 1001: { + // divdo + int64_t ra_val = getRegister(ra); + int64_t rb_val = getRegister(rb); + bool ov = (rb_val == 0) || (ra_val == INT64_MIN && rb_val == -1); + if (ov) { + setRegister(rt, 0); + } else { + setRegister(rt, ra_val / rb_val); + } + setXEROV(ov); + if (rc) updateCR0(getRegister(rt)); + break; + } + case 457: { + // divdu: unsigned 64-bit divide + uint64_t ra_val = U64(getRegister(ra)); + uint64_t rb_val = U64(getRegister(rb)); + if (rb_val == 0) { + setRegister(rt, 0); + } else { + setRegister(rt, I64(ra_val / rb_val)); + } + if (rc) updateCR0(getRegister(rt)); + break; + } + case 969: { + // divduo + uint64_t ra_val = U64(getRegister(ra)); + uint64_t rb_val = U64(getRegister(rb)); + bool ov = (rb_val == 0); + if (ov) { + setRegister(rt, 0); + } else { + setRegister(rt, I64(ra_val / rb_val)); + } + setXEROV(ov); + if (rc) updateCR0(getRegister(rt)); + break; + } + case 491: { + // divw: signed 32-bit divide + int32_t ra_val = I32(getRegister(ra)); + int32_t rb_val = I32(getRegister(rb)); + if (rb_val == 0 || (ra_val == INT32_MIN && rb_val == -1)) { + setRegister(rt, 0); + } else { + setRegister(rt, (int64_t)(ra_val / rb_val)); + } + if (rc) updateCR0(getRegister(rt)); + break; + } + case 1003: { + // divwo + int32_t ra_val = I32(getRegister(ra)); + int32_t rb_val = I32(getRegister(rb)); + bool ov = (rb_val == 0) || (ra_val == INT32_MIN && rb_val == -1); + if (ov) { + setRegister(rt, 0); + } else { + setRegister(rt, (int64_t)(ra_val / rb_val)); + } + setXEROV(ov); + if (rc) updateCR0(getRegister(rt)); + break; + } + case 459: { + // divwu: unsigned 32-bit divide + uint32_t ra_val = U32(getRegister(ra)); + uint32_t rb_val = U32(getRegister(rb)); + if (rb_val == 0) { + setRegister(rt, 0); + } else { + setRegister(rt, (int64_t)(ra_val / rb_val)); + } + if (rc) updateCR0(getRegister(rt)); + break; + } + case 971: { + // divwuo + uint32_t ra_val = U32(getRegister(ra)); + uint32_t rb_val = U32(getRegister(rb)); + bool ov = (rb_val == 0); + if (ov) { + setRegister(rt, 0); + } else { + setRegister(rt, (int64_t)(ra_val / rb_val)); + } + setXEROV(ov); + if (rc) updateCR0(getRegister(rt)); + break; + } + + // --- POWER9 modulo (ISA 3.0) --- + // Result of "undefined" division (rb_val == 0, or signed INT_MIN / -1) + // is implementation-defined per Power ISA; matching the divX behaviour + // above, we yield 0 in those cases. Rc has no encoding for these ops. + case 779: { + // modsw: RT = RA % RB (signed, 32-bit) + int32_t ra_val = I32(getRegister(ra)); + int32_t rb_val = I32(getRegister(rb)); + if (rb_val == 0 || (ra_val == INT32_MIN && rb_val == -1)) { + setRegister(rt, 0); + } else { + setRegister(rt, (int64_t)(ra_val % rb_val)); + } + break; + } + case 267: { + // moduw: RT = RA % RB (unsigned, 32-bit) + uint32_t ra_val = U32(getRegister(ra)); + uint32_t rb_val = U32(getRegister(rb)); + if (rb_val == 0) { + setRegister(rt, 0); + } else { + setRegister(rt, (int64_t)(ra_val % rb_val)); + } + break; + } + case 777: { + // modsd: RT = RA % RB (signed, 64-bit) + int64_t ra_val = getRegister(ra); + int64_t rb_val = getRegister(rb); + if (rb_val == 0 || (ra_val == INT64_MIN && rb_val == -1)) { + setRegister(rt, 0); + } else { + setRegister(rt, ra_val % rb_val); + } + break; + } + case 265: { + // modud: RT = RA % RB (unsigned, 64-bit) + uint64_t ra_val = U64(getRegister(ra)); + uint64_t rb_val = U64(getRegister(rb)); + if (rb_val == 0) { + setRegister(rt, 0); + } else { + setRegister(rt, I64(ra_val % rb_val)); + } + break; + } + + // --- Logical --- + case 28: { + // and: RA = RS & RB + int64_t result = getRegister(rt) & getRegister(rb); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 60: { + // andc: RA = RS & ~RB + int64_t result = getRegister(rt) & ~getRegister(rb); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 444: { + // or: RA = RS | RB + int64_t result = getRegister(rt) | getRegister(rb); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 412: { + // orc: RA = RS | ~RB + int64_t result = getRegister(rt) | ~getRegister(rb); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 316: { + // xor: RA = RS ^ RB + int64_t result = getRegister(rt) ^ getRegister(rb); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 476: { + // nand: RA = ~(RS & RB) + int64_t result = ~(getRegister(rt) & getRegister(rb)); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 124: { + // nor: RA = ~(RS | RB) + int64_t result = ~(getRegister(rt) | getRegister(rb)); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 284: { + // eqv: RA = ~(RS ^ RB) + int64_t result = ~(getRegister(rt) ^ getRegister(rb)); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + + // --- Shifts --- + case 27: { + // sld: RA = RS << RB[58:63] if RB[57]==0, else RA=0 + uint64_t shift = U64(getRegister(rb)); + uint64_t rs_val = U64(getRegister(rt)); + int64_t result; + if (shift & 0x40) { + result = 0; + } else { + result = I64(rs_val << (shift & 0x3F)); + } + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 24: { + // slw: RA = RS[32:63] << RB[59:63] if RB[58]==0, else RA=0 (32-bit) + uint32_t shift = U32(getRegister(rb)); + uint32_t rs_val = U32(getRegister(rt)); + uint32_t result; + if (shift & 0x20) { + result = 0; + } else { + result = rs_val << (shift & 0x1F); + } + setRegister(ra, (int64_t)(int32_t)result); + if (rc) updateCR0(getRegister(ra)); + break; + } + case 539: { + // srd: RA = RS >> RB[58:63] if RB[57]==0, else RA=0 (logical) + uint64_t shift = U64(getRegister(rb)); + uint64_t rs_val = U64(getRegister(rt)); + int64_t result; + if (shift & 0x40) { + result = 0; + } else { + result = I64(rs_val >> (shift & 0x3F)); + } + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 536: { + // srw: RA = RS[32:63] >> RB[59:63] logical (32-bit) + uint32_t shift = U32(getRegister(rb)); + uint32_t rs_val = U32(getRegister(rt)); + uint32_t result; + if (shift & 0x20) { + result = 0; + } else { + result = rs_val >> (shift & 0x1F); + } + setRegister(ra, (int64_t)(int32_t)result); + if (rc) updateCR0(getRegister(ra)); + break; + } + case 794: { + // srad: RA = RS >> RB[58:63] arithmetic (64-bit), set CA + uint64_t shift = U64(getRegister(rb)); + int64_t rs_val = getRegister(rt); + int64_t result; + bool carry; + if (shift & 0x40) { + result = rs_val >> 63; // all sign bits + carry = (rs_val < 0); + } else { + uint32_t sh = shift & 0x3F; + result = rs_val >> sh; + // CA = 1 if RS is negative and any 1-bits were shifted out. + carry = (rs_val < 0) && ((rs_val & ((1ULL << sh) - 1)) != 0); + } + setRegister(ra, result); + setXERCA(carry); + if (rc) updateCR0(result); + break; + } + case 792: { + // sraw: RA = RS[32:63] >> RB[59:63] arithmetic (32-bit), set CA + uint32_t shift = U32(getRegister(rb)); + int32_t rs_val = I32(getRegister(rt)); + int32_t result; + bool carry; + if (shift & 0x20) { + result = rs_val >> 31; + carry = (rs_val < 0); + } else { + uint32_t sh = shift & 0x1F; + result = rs_val >> sh; + carry = (rs_val < 0) && ((rs_val & ((1U << sh) - 1)) != 0); + } + setRegister(ra, (int64_t)result); + setXERCA(carry); + if (rc) updateCR0(getRegister(ra)); + break; + } + case 826: + case 827: { + // sradi RA, RS, SH: RA = EXTS(RS) >> sh arithmetic (64-bit), set CA. + // XS-form, XO=413 (9-bit, bits 21-29), sh[5] at bit 30, Rc at bit 31. + // Our xoValue() extracts bits 10:1 (10 bits) + // which yields 413*2 + sh[5] = 826 (sh[5]=0) or 827 (sh[5]=1). + // sh[0:4] at instruction bits 15:11 (= raValue field position, but + // for this XS-form they're the SH[0:4] subfield). + uint32_t sh = instr->bits(15, 11) | (instr->bit(1) << 5); + int64_t rs_val = getRegister(rt); + int64_t result = (sh == 0) ? rs_val : (rs_val >> sh); + // CA := rs_val < 0 && any bits shifted out are 1. + bool carry = (rs_val < 0) && sh > 0 && + ((U64(rs_val) & ((1ULL << sh) - 1)) != 0); + setRegister(ra, result); + setXERCA(carry); + if (rc) updateCR0(result); + break; + } + case 824: { + // srawi: RA = RS[32:63] >> SH arithmetic (32-bit), set CA + uint32_t sh = instr->bits(15, 11); + int32_t rs_val = I32(getRegister(rt)); + int32_t result = rs_val >> sh; + bool carry = (rs_val < 0) && sh > 0 && + ((U32(rs_val) & ((1U << sh) - 1)) != 0); + setRegister(ra, (int64_t)result); + setXERCA(carry); + if (rc) updateCR0(getRegister(ra)); + break; + } + + // --- Extend / count --- + case 954: { + // extsb: RA = sign_ext(RS[56:63]) + int64_t result = (int64_t)(int8_t)(getRegister(rt) & 0xFF); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 922: { + // extsh: RA = sign_ext(RS[48:63]) + int64_t result = (int64_t)(int16_t)(getRegister(rt) & 0xFFFF); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 986: { + // extsw: RA = sign_ext(RS[32:63]) + int64_t result = (int64_t)(int32_t)(getRegister(rt) & 0xFFFFFFFF); + setRegister(ra, result); + if (rc) updateCR0(result); + break; + } + case 58: { + // cntlzd: RA = count leading zeros of RS (64-bit) + setRegister(ra, CountLeadingZeros64(U64(getRegister(rt)))); + if (rc) updateCR0(getRegister(ra)); + break; + } + case 26: { + // cntlzw: RA = count leading zeros of RS[32:63] (32-bit) + setRegister(ra, CountLeadingZeros32(U32(getRegister(rt)))); + if (rc) updateCR0(getRegister(ra)); + break; + } + case 570: { + // cnttzd + setRegister(ra, CountTrailingZeros64(U64(getRegister(rt)))); + if (rc) updateCR0(getRegister(ra)); + break; + } + case 538: { + // cnttzw + setRegister(ra, CountTrailingZeros32(U32(getRegister(rt)))); + if (rc) updateCR0(getRegister(ra)); + break; + } + case 506: { + // popcntd + setRegister(ra, PopCount64(U64(getRegister(rt)))); + break; + } + case 378: { + // popcntw: popcount each 32-bit half independently, sum in each half + uint64_t val = U64(getRegister(rt)); + uint32_t lo = PopCount32(U32(val)); + uint32_t hi = PopCount32(U32(val >> 32)); + setRegister(ra, I64(((uint64_t)hi << 32) | lo)); + break; + } + case 122: { + // popcntb: popcount each byte independently + setRegister(ra, I64(PopCountPerByte(U64(getRegister(rt))))); + break; + } + case 187: { + // brd (POWER10): RA = byte-reverse(RS) full 64-bit doubleword. + setRegister(ra, I64(__builtin_bswap64(U64(getRegister(rt))))); + break; + } + case 219: { + // brh (POWER10): byte-reverse each of the 4 halfwords in RS. + uint64_t v = U64(getRegister(rt)); + uint64_t out = ((v & 0xFF00FF00FF00FF00ULL) >> 8) | + ((v & 0x00FF00FF00FF00FFULL) << 8); + setRegister(ra, I64(out)); + break; + } + case 155: { + // brw (POWER10): byte-reverse each of the 2 words in RS. + uint64_t v = U64(getRegister(rt)); + uint64_t out = ((uint64_t)__builtin_bswap32((uint32_t)(v >> 32)) << 32) | + (uint64_t)__builtin_bswap32((uint32_t)v); + setRegister(ra, I64(out)); + break; + } + + // --- Compare (X-form) --- + case 0: { + // cmp (cmpw/cmpd): signed compare + uint32_t bf = instr->bfValue(); + bool l = instr->lBit(); + if (l) { + setCRFieldCmp(bf, getRegister(ra), getRegister(rb)); + } else { + setCRFieldCmp(bf, (int64_t)I32(getRegister(ra)), + (int64_t)I32(getRegister(rb))); + } + break; + } + case 32: { + // cmpl (cmplw/cmpld): unsigned compare + uint32_t bf = instr->bfValue(); + bool l = instr->lBit(); + if (l) { + setCRFieldCmpU(bf, U64(getRegister(ra)), U64(getRegister(rb))); + } else { + setCRFieldCmpU(bf, (uint64_t)U32(getRegister(ra)), + (uint64_t)U32(getRegister(rb))); + } + break; + } + + // --- Trap --- + case 4: { + // tw: Trap Word. The JIT uses this for debugging / tagging. + // In the simulator we just treat it as a NOP (the JIT uses tagged + // trap words that are never actually reached during normal execution, + // they serve as metadata for the patcher). + break; + } + + // --- SPR --- + case 339: { + // mfspr: RT = SPR + // SPR encoding: spr[4:0] at bits 16..20, spr[9:5] at bits 11..15 + uint32_t spr_lo = instr->raValue(); // bits 16..20 + uint32_t spr_hi = instr->rbValue(); // bits 11..15 + uint32_t spr = (spr_lo) | (spr_hi << 5); + switch (spr) { + case 8: // LR + setRegister(rt, getLR()); + break; + case 9: // CTR + setRegister(rt, getCTR()); + break; + case 1: // XER + setRegister(rt, I64(getXER())); + break; + default: + MOZ_CRASH_UNSAFE_PRINTF("mfspr: unhandled SPR %u", spr); + } + break; + } + case 467: { + // mtspr: SPR = RS + uint32_t spr_lo = instr->raValue(); + uint32_t spr_hi = instr->rbValue(); + uint32_t spr = (spr_lo) | (spr_hi << 5); + int64_t val = getRegister(rt); + switch (spr) { + case 8: // LR + setLR(val); + break; + case 9: // CTR + setCTR(val); + break; + case 1: // XER + setXER(U64(val)); + break; + default: + MOZ_CRASH_UNSAFE_PRINTF("mtspr: unhandled SPR %u", spr); + } + break; + } + case 19: { + // mfocrf: read one CR field selected by the FXM bitmask into RT. + // (Plain mfcr shares this XO with FXM=0; we model both by reading + // the full CR — the JIT only emits mfocrf and the bits outside the + // selected field are spec'd "undefined", so reading the full CR is + // a valid implementation.) + setRegister(rt, (int64_t)getCR()); + break; + } + case 144: { + // mtcrf: move to CR fields + // FXM field is in bits 12..19. + uint32_t fxm = instr->bits(19, 12); + uint32_t rs_val = U32(getRegister(rt)); + uint32_t cr = getCR(); + for (int i = 0; i < 8; i++) { + if (fxm & (0x80 >> i)) { + uint32_t shift = 4 * (7 - i); + cr = (cr & ~(0xFu << shift)) | (rs_val & (0xFu << shift)); + } + } + setCR(cr); + break; + } + case 576: { + // mcrxrx: move XER[OV,OV32,CA,CA32] to CR field BF + uint32_t bf = instr->bfValue(); + uint8_t field = 0; + if (getXEROV()) field |= 0x8; + // OV32 at bit 19 of XER + if ((getXER() >> kXEROV32Bit) & 1) field |= 0x4; + if (getXERCA()) field |= 0x2; + if ((getXER() >> kXERCA32Bit) & 1) field |= 0x1; + setCRField(bf, field); + break; + } + case 384: + case 416: { + // POWER10 setbc/setbcr: RT = (CR[BI]==N) ? 1 : 0 + // BI at bits 11..15; xo=384 (setbc, N=1), xo=416 (setbcr, N=0). + uint32_t bi = instr->raValue(); + uint32_t crField = bi / 4; + uint32_t crBit = bi % 4; + uint8_t crFieldVal = getCRField(crField); + bool bitSet; + switch (crBit) { + case 0: bitSet = (crFieldVal & kCRFieldLT) != 0; break; + case 1: bitSet = (crFieldVal & kCRFieldGT) != 0; break; + case 2: bitSet = (crFieldVal & kCRFieldEQ) != 0; break; + case 3: bitSet = (crFieldVal & kCRFieldSO) != 0; break; + default: bitSet = false; break; + } + bool want = (xo == 384) ? bitSet : !bitSet; + setRegister(rt, want ? 1 : 0); + break; + } + + // --- Indexed loads --- + case 21: { + // ldx: RT = [RA|0 + RB], 8 bytes + uint64_t ea = XFormEA(this, instr); + setRegister(rt, readDW(ea, instr)); + break; + } + case 53: { + // ldux: RT = [RA + RB], update RA + uint64_t ea = XFormEAUpdate(this, instr); + setRegister(rt, readDW(ea, instr)); + setRegister(ra, ea); + break; + } + case 23: { + // lwzx: RT = zero_ext([RA|0 + RB], 4 bytes) + uint64_t ea = XFormEA(this, instr); + setRegister(rt, U64(readWU(ea, instr))); + break; + } + case 341: { + // lwax: RT = sign_ext([RA|0 + RB], 4 bytes) + uint64_t ea = XFormEA(this, instr); + setRegister(rt, (int64_t)readW(ea, instr)); + break; + } + case 87: { + // lbzx + uint64_t ea = XFormEA(this, instr); + setRegister(rt, U64(readBU(ea))); + break; + } + case 279: { + // lhzx + uint64_t ea = XFormEA(this, instr); + setRegister(rt, U64(readHU(ea, instr))); + break; + } + case 343: { + // lhax + uint64_t ea = XFormEA(this, instr); + setRegister(rt, (int64_t)readH(ea, instr)); + break; + } + case 535: { + // lfsx: load float single indexed, widen to double (NaN-preserving) + uint64_t ea = XFormEA(this, instr); + if (!handleWasmSegFault(ea, 4)) { + float val = *reinterpret_cast(ea); + setFpuRegisterDouble(rt, promoteFloatPreservingNaN(val)); + } + break; + } + case 599: { + // lfdx: load float double indexed + uint64_t ea = XFormEA(this, instr); + setFpuRegisterDouble(rt, readD(ea, instr)); + break; + } + case 855: { + // lfiwax: load float as integer word algebraic + uint64_t ea = XFormEA(this, instr); + int32_t val = readW(ea, instr); + setFpuRegister(rt, (int64_t)val); + break; + } + case 887: { + // lfiwzx: load float as integer word zero + uint64_t ea = XFormEA(this, instr); + uint32_t val = readWU(ea, instr); + setFpuRegister(rt, (int64_t)(uint64_t)val); + break; + } + + // --- Indexed stores --- + case 149: { + // stdx + uint64_t ea = XFormEA(this, instr); + writeDW(ea, getRegister(rt), instr); + break; + } + case 151: { + // stwx + uint64_t ea = XFormEA(this, instr); + writeW(ea, I32(getRegister(rt)), instr); + break; + } + case 215: { + // stbx + uint64_t ea = XFormEA(this, instr); + writeB(ea, (uint8_t)(getRegister(rt) & 0xFF)); + break; + } + case 407: { + // sthx + uint64_t ea = XFormEA(this, instr); + writeH(ea, U16(getRegister(rt)), instr); + break; + } + case 663: { + // stfsx: store float single indexed (NaN-preserving) + uint64_t ea = XFormEA(this, instr); + if (!handleWasmSegFault(ea, 4)) { + float fval = demoteDoublePreservingNaN(getFpuRegisterDouble(rt)); + *reinterpret_cast(ea) = fval; + LLBit_ = false; + } + break; + } + case 727: { + // stfdx: store float double indexed + uint64_t ea = XFormEA(this, instr); + writeD(ea, getFpuRegisterDouble(rt), instr); + break; + } + + // --- Byte-reversed stores --- + case 662: { + // stwbrx + uint64_t ea = XFormEA(this, instr); + uint32_t val = U32(getRegister(rt)); + writeW(ea, (int32_t)__builtin_bswap32(val), instr); + break; + } + + // --- Atomic load/store --- + // + // Load-reserve and store-conditional. Sub-word variants + // (lbarx/lharx/stbcx./sthcx.) were added in ISA v2.06 (POWER7+). + // Word/doubleword variants (lwarx/stwcx./ldarx/stdcx.) go back + // to the base ISA. + case 52: { + // lbarx RT, RA, RB, EH + uint64_t ea = XFormEA(this, instr); + uint8_t val = loadLinkedB(ea, instr); + setRegister(rt, (int64_t)val); + break; + } + case 116: { + // lharx RT, RA, RB, EH + uint64_t ea = XFormEA(this, instr); + uint16_t val = loadLinkedH(ea, instr); + setRegister(rt, (int64_t)val); + break; + } + case 694: { + // stbcx. RS, RA, RB: always Rc=1. + uint64_t ea = XFormEA(this, instr); + uint8_t val = uint8_t(getRegister(rt)); + int result = storeConditionalB(ea, val, instr); + if (result) { + setCRField(0, kCRFieldEQ | (kCRFieldSO * getXERSO())); + } else { + setCRField(0, kCRFieldSO * getXERSO()); + } + break; + } + case 726: { + // sthcx. RS, RA, RB: always Rc=1. + uint64_t ea = XFormEA(this, instr); + uint16_t val = uint16_t(getRegister(rt)); + int result = storeConditionalH(ea, val, instr); + if (result) { + setCRField(0, kCRFieldEQ | (kCRFieldSO * getXERSO())); + } else { + setCRField(0, kCRFieldSO * getXERSO()); + } + break; + } + case 20: { + // lwarx + uint64_t ea = XFormEA(this, instr); + int32_t val = loadLinkedW(ea, instr); + setRegister(rt, (int64_t)val); + break; + } + case 150: { + // stwcx. + uint64_t ea = XFormEA(this, instr); + int32_t val = I32(getRegister(rt)); + int result = storeConditionalW(ea, val, instr); + // stwcx. always updates CR0: EQ if store succeeded, else clear. + if (result) { + setCRField(0, kCRFieldEQ | (kCRFieldSO * getXERSO())); + } else { + setCRField(0, kCRFieldSO * getXERSO()); + } + break; + } + case 84: { + // ldarx + uint64_t ea = XFormEA(this, instr); + int64_t val = loadLinkedD(ea, instr); + setRegister(rt, val); + break; + } + case 214: { + // stdcx. + uint64_t ea = XFormEA(this, instr); + int64_t val = getRegister(rt); + int result = storeConditionalD(ea, val, instr); + if (result) { + setCRField(0, kCRFieldEQ | (kCRFieldSO * getXERSO())); + } else { + setCRField(0, kCRFieldSO * getXERSO()); + } + break; + } + + // --- Synchronization --- + case 598: + // sync / lwsync / ptesync: no-op in simulator + break; + case 854: + // eieio: no-op in simulator + break; + + // --- GPR <-> VSR move (major opcode 31, XX1-form) --- + // + // Two sub-encodings: + // mtvsr* XT,RA{,RB}: XX1Form — XT at bits 25:21 (5) + TX at bit 0 (1); + // RA at bits 20:16; RB (if any) at bits 15:11. + // mfvsr* RA,XS: XX1FormMfvsr — XS at bits 25:21 (5) + SX at bit 0 (1); + // RA (GPR dest) at bits 20:16. + // + // The original decoder treated "rsValue()" (bits 25:21 = VSR field) as a + // GPR index — doubly wrong: the GPR side lives at bits 20:16 (= raValue()) + // and the VSR side is 6 bits (5-bit field + extension bit at bit 0). Fixed + // here and extended for the full VSR namespace (0-63). + // The ISA names each field in BE. "XT.DW0" is the BE doubleword which on + // PPCLE register storage lives at LE bytes 8-15 (our bytes[] is LE-natural: + // bytes[0] = lowest address). With `mtvsrd / mfvsrd / mtvsrdd / mfvsrld + // / stxvx`: mtvsrd of 0x1122334455667788 produces `00 00 00 00 00 00 00 00 + // 88 77 66 55 44 33 22 11` in memory (LE bytes 8-15 hold the GPR bits with + // LSB at byte 8). Matching semantics here means the sim respects + // the full Power ISA, not a self-consistent LE-reversed + // convention. + case 51: { + // mfvsrd RA, XS: GPR[RA] = XS.DW0 = LE bytes 8..15. + int xs = int(instr->rtValue() | (instr->bit(0) << 5)); // T + SX(TX) + uint8_t bytes[16]; + getVSR128(xs, bytes); + int64_t val; + memcpy(&val, bytes + 8, 8); + setRegister(instr->raValue(), val); + break; + } + case 211: { + // mtvsrwa XT, RA: XT.DW0 = sign_ext_64(RA[32:63]); XT.DW1 = 0. + // POWER8+ (ISA 2.07). Combines extsw + mtvsrd. LE layout: bytes + // 8-15 ← sign-extended low 32 of RA; bytes 0-7 ← 0. + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t bytes[16]; + int64_t val = (int64_t)(int32_t)getRegister(instr->raValue()); + memset(bytes, 0, 8); + memcpy(bytes + 8, &val, 8); + setVSR128(xt, bytes); + break; + } + case 179: { + // mtvsrd XT, RA: XT.DW0 = RA; XT.DW1 = 0. + // LE layout: bytes 8-15 ← RA, bytes 0-7 ← 0. + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t bytes[16]; + int64_t val = getRegister(instr->raValue()); + memset(bytes, 0, 8); + memcpy(bytes + 8, &val, 8); + setVSR128(xt, bytes); + break; + } + case 243: { + // mtvsrwz XT, RA: XT.DW0 = zero_ext(RA[32:63]); XT.DW1 = 0. + // The 32-bit value lives in the low 32 bits of DW0 = BE word 1, + // which on LE storage is LE bytes 8..11 (LE word 2); LE bytes + // 12..15 = 0 (upper half of DW0 = BE word 0 = zero-extended). + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t bytes[16]; + uint32_t lo = U32(getRegister(instr->raValue())); + memset(bytes, 0, 16); + bytes[8] = (uint8_t)(lo); + bytes[9] = (uint8_t)(lo >> 8); + bytes[10] = (uint8_t)(lo >> 16); + bytes[11] = (uint8_t)(lo >> 24); + setVSR128(xt, bytes); + break; + } + case 307: { + // mfvsrld RA, XS: GPR[RA] = XS.DW1 = LE bytes 0..7. + // POWER9. + int xs = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t bytes[16]; + getVSR128(xs, bytes); + int64_t val; + memcpy(&val, bytes, 8); + setRegister(instr->raValue(), val); + break; + } + case 403: { + // mtvsrws XT, RA (POWER9): splat low 32 bits of RA into all four + // word elements of XT. The same 32-bit value appears in lanes 0..3, + // so the byte layout is identical in LE and BE — + // bytes 0..15 = lo | lo | lo | lo. + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t bytes[16]; + uint32_t lo = U32(getRegister(instr->raValue())); + uint64_t val = ((uint64_t)lo << 32) | lo; + memcpy(bytes, &val, 8); + memcpy(bytes + 8, &val, 8); + setVSR128(xt, bytes); + break; + } + case 435: { + // mtvsrdd XT, RA, RB: XT.DW0 = RA; XT.DW1 = RB. POWER9. + // LE: bytes 8-15 ← RA, bytes 0-7 ← RB. + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t bytes[16]; + int64_t dw0 = getRegister(instr->raValue()); + int64_t dw1 = getRegister(instr->rbValue()); + memcpy(bytes, &dw1, 8); + memcpy(bytes + 8, &dw0, 8); + setVSR128(xt, bytes); + break; + } + + // --- VMX vector memory (major opcode 31) --- + // + // lvx / stvx / lvxl / stvxl. + // EA = (RA|0) + RB; EA = EA & ~0xF (alignment) + // lvx: VRT[0:127] <- MEM(EA, 16) bytes[0] = *(EA+0) + // stvx: MEM(EA, 16) <- VRS[0:127] *(EA+0) = bytes[0] + // lvxl / stvxl are identical in effect to lvx / stvx (the "l" form + // hints "least recently used"; semantically indistinguishable). + case 103: { + // lvx: VRT = MEM(EA & ~0xF, 16 bytes) + uint64_t ea = XFormEA(this, instr) & ~uint64_t(0xF); + if (handleWasmSegFault(ea, 16)) break; + memcpy(VRregisters_[rt], reinterpret_cast(ea), 16); + break; + } + case 231: { + // stvx: MEM(EA & ~0xF, 16 bytes) = VRS + uint64_t ea = XFormEA(this, instr) & ~uint64_t(0xF); + if (handleWasmSegFault(ea, 16)) break; + memcpy(reinterpret_cast(ea), VRregisters_[rt], 16); + break; + } + case 359: { + // lvxl: semantically identical to lvx + uint64_t ea = XFormEA(this, instr) & ~uint64_t(0xF); + if (handleWasmSegFault(ea, 16)) break; + memcpy(VRregisters_[rt], reinterpret_cast(ea), 16); + break; + } + case 487: { + // stvxl: semantically identical to stvx + uint64_t ea = XFormEA(this, instr) & ~uint64_t(0xF); + if (handleWasmSegFault(ea, 16)) break; + memcpy(reinterpret_cast(ea), VRregisters_[rt], 16); + break; + } + + // --- VSX vector memory indexed (major opcode 31) --- + // + // These ops take a 6-bit VSR register, + // encoded as 5-bit T/S + 1-bit TX/SX extension at instruction LSB + // bit 0 (= our instr->bit(0)). EA = (RA|0) + RB. 16-byte access, + // not forced-aligned (hardware may handle misaligned via sub-access + // or alignment interrupt per impl). + // + // Byte-order note: lxvx/stxvx perform a natural 16-byte LE + // memcpy. lxvd2x/stxvd2x on real PPC LE hardware load/store + // doublewords in BE-pair order — i.e. lxvd2x places memory bytes + // 0-7 in the register's BE-DW0 (= LE bytes 8-15) and bytes 8-15 + // in BE-DW1 (= LE bytes 0-7). The JIT brackets every wasm SIMD + // load/store with a compensating xxpermdi DM=2 so the net effect + // is a natural LE byte order. The constant pool emits the same + // lxvd2x + xxpermdi sequence (per PatchConstantPoolLoad) but + // assumes the hardware semantics, not a plain memcpy. So the sim + // must match real-hardware lxvd2x/stxvd2x semantics including the + // BE-DW byte order — otherwise the post-load xxpermdi unswaps + // bytes that were never swapped, and constant-pool Simd128 loads + // (e.g. shuffle masks) come out with halves transposed. + case 268: { + // lxvx: XT = MEM((RA|0)+RB, 16) + uint64_t ea = XFormEA(this, instr); + if (handleWasmSegFault(ea, 16)) break; + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t buf[16]; + memcpy(buf, reinterpret_cast(ea), 16); + setVSR128(xt, buf); + break; + } + case 396: { + // stxvx: MEM((RA|0)+RB, 16) = XS + uint64_t ea = XFormEA(this, instr); + if (handleWasmSegFault(ea, 16)) break; + int xs = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t buf[16]; + getVSR128(xs, buf); + memcpy(reinterpret_cast(ea), buf, 16); + break; + } + case 813: { + // lxsihzx XT, RA, RB: P9 (ISA 3.0). Load halfword to VSR & zero, + // indexed. MEM(EA, 2) (LE-natural halfword) is placed in dw[0] + // low 16 bits; the rest of the VSR is zeroed. In sim LE-byte + // storage, that is bytes[8..9] (low byte at bytes[8]). + uint64_t ea = XFormEA(this, instr); + if (handleWasmSegFault(ea, 2)) break; + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint16_t halfword = readH(ea, instr); + uint8_t buf[16]; + memset(buf, 0, 16); + buf[8] = (uint8_t)(halfword & 0xFF); + buf[9] = (uint8_t)((halfword >> 8) & 0xFF); + setVSR128(xt, buf); + break; + } + case 941: { + // stxsihx XS, RA, RB: P9 (ISA 3.0). Store halfword from VSR, + // indexed. dw[0] low 16 bits (sim bytes[8..9] in host-LE order) + // are written as a halfword at MEM(EA, 2). + uint64_t ea = XFormEA(this, instr); + if (handleWasmSegFault(ea, 2)) break; + int xs = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t buf[16]; + getVSR128(xs, buf); + uint16_t halfword = + (uint16_t)buf[8] | ((uint16_t)buf[9] << 8); + writeH(ea, halfword, instr); + break; + } + case 844: { + // lxvd2x: XT = MEM((RA|0)+RB, 16) with BE-DW byte ordering. + // Memory bytes 0-7 land in BE-DW0 (= LE bytes 8-15); memory + // bytes 8-15 land in BE-DW1 (= LE bytes 0-7). + uint64_t ea = XFormEA(this, instr); + if (handleWasmSegFault(ea, 16)) break; + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t mem[16], buf[16]; + memcpy(mem, reinterpret_cast(ea), 16); + memcpy(buf, mem + 8, 8); + memcpy(buf + 8, mem, 8); + setVSR128(xt, buf); + break; + } + case 972: { + // stxvd2x: MEM((RA|0)+RB, 16) = XS with BE-DW byte ordering. + // Inverse of lxvd2x: register LE bytes 0-7 → memory bytes 8-15; + // LE bytes 8-15 → memory bytes 0-7. + uint64_t ea = XFormEA(this, instr); + if (handleWasmSegFault(ea, 16)) break; + int xs = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t buf[16], mem[16]; + getVSR128(xs, buf); + memcpy(mem, buf + 8, 8); + memcpy(mem + 8, buf, 8); + memcpy(reinterpret_cast(ea), mem, 16); + break; + } + + default: + MOZ_CRASH_UNSAFE_PRINTF( + "decodeXForm: unimplemented XO=%u (instruction 0x%08x)", xo, + instr->instructionBits()); + } +} + +// ----------------------------------------------------------------------------- +// decodeRotateMask: rlwinm(21), rlwnm(23), rlwimi(20), +// rldicl(30), rldicr(30), rldic(30), rldimi(30), rldcl(30), rldcr(30) + +void Simulator::decodeRotateMask(SimInstruction* instr) { + uint32_t opcode = instr->opcode(); + + if (opcode == 21) { + // rlwinm: RA = ROTL32(RS,SH) & MASK(MB,ME), Rc + uint32_t rs_val = U32(getRegister(instr->rsValue())); + uint32_t sh = instr->mSHValue(); + uint32_t mb = instr->mMBValue(); + uint32_t me = instr->mMEValue(); + uint32_t rotated = RotateLeft32(rs_val, sh); + uint32_t mask = MASK32(mb, me); + int64_t result = (int64_t)(uint64_t)(rotated & mask); + setRegister(instr->raValue(), result); + if (instr->rcBit()) updateCR0(result); + } else if (opcode == 23) { + // rlwnm: RA = ROTL32(RS,RB[27:31]) & MASK(MB,ME), Rc + uint32_t rs_val = U32(getRegister(instr->rsValue())); + uint32_t sh = U32(getRegister(instr->rbValue())) & 0x1F; + uint32_t mb = instr->mMBValue(); + uint32_t me = instr->mMEValue(); + uint32_t rotated = RotateLeft32(rs_val, sh); + uint32_t mask = MASK32(mb, me); + int64_t result = (int64_t)(uint64_t)(rotated & mask); + setRegister(instr->raValue(), result); + if (instr->rcBit()) updateCR0(result); + } else if (opcode == 20) { + // rlwimi: RA = (ROTL32(RS,SH) & MASK) | (RA & ~MASK), Rc + uint32_t rs_val = U32(getRegister(instr->rsValue())); + uint32_t sh = instr->mSHValue(); + uint32_t mb = instr->mMBValue(); + uint32_t me = instr->mMEValue(); + uint32_t rotated = RotateLeft32(rs_val, sh); + uint32_t mask = MASK32(mb, me); + uint32_t ra_val = U32(getRegister(instr->raValue())); + int64_t result = (int64_t)(uint64_t)((rotated & mask) | (ra_val & ~mask)); + setRegister(instr->raValue(), result); + if (instr->rcBit()) updateCR0(result); + } else if (opcode == 30) { + // MD-form / MDS-form: 64-bit rotate/mask + uint32_t rs = instr->rsValue(); + uint64_t rs_val = U64(getRegister(rs)); + uint32_t ra_reg = instr->raValue(); + + // Determine which sub-opcode: bits 2..4 for MD-form, bit 4 for MDS. + // MD: bits 2..4 + // MDS: bit 4 (rldcl has bit4=0, bit3..2=00 with bit1=1; rldcr has + // bit4=0, bit3..2=01 with bit1=1). Actually: + // rldicl: 30 | MD-XO=0 (bits 2..4 = 000), bit1=0 + // rldicr: 30 | MD-XO=1 (bits 2..4 = 001), bit1=0 + // rldic: 30 | MD-XO=2 (bits 2..4 = 010), bit1=0 + // rldimi: 30 | MD-XO=3 (bits 2..4 = 011), bit1=0 + // rldcl: 30 | MDS, bit4=0, bit3..1=000, bit0=Rc => bits 1..4=1000 + // Actually rldcl: bits 1..4 = 1000, i.e. bit(4)=1,bit(3)=0, + // bit(2)=0,bit(1)=0 + // rldcr: 30 | MDS, bits 1..4 = 1001 + // + // Let's check bit 4 first: if bit(4)==1, it's MDS-form (rldcl/rldcr). + if (instr->bit(4)) { + // MDS-form: shift amount from RB register + uint32_t sh = U32(getRegister(instr->rbValue())) & 0x3F; + uint64_t rotated = RotateLeft64(rs_val, sh); + uint32_t mb = instr->mdsMBValue(); + + if (!instr->bit(1)) { + // rldcl: RA = ROTL64(RS, RB[58:63]) & MASK(mb, 63) + uint64_t mask = MASK64(mb, 63); + int64_t result = I64(rotated & mask); + setRegister(ra_reg, result); + if (instr->rcBit()) updateCR0(result); + } else { + // rldcr: RA = ROTL64(RS, RB[58:63]) & MASK(0, me) + uint32_t me = instr->mdsMBValue(); + uint64_t mask = MASK64(0, me); + int64_t result = I64(rotated & mask); + setRegister(ra_reg, result); + if (instr->rcBit()) updateCR0(result); + } + } else { + // MD-form + uint32_t sh = instr->mdSHValue(); + uint64_t rotated = RotateLeft64(rs_val, sh); + uint32_t xo_md = instr->bits(3, 2); + + switch (xo_md) { + case 0: { + // rldicl: RA = ROTL64(RS, SH) & MASK(mb, 63) + uint32_t mb = instr->mdMBValue(); + uint64_t mask = MASK64(mb, 63); + int64_t result = I64(rotated & mask); + setRegister(ra_reg, result); + if (instr->rcBit()) updateCR0(result); + break; + } + case 1: { + // rldicr: RA = ROTL64(RS, SH) & MASK(0, me) + uint32_t me = instr->mdMEValue(); + uint64_t mask = MASK64(0, me); + int64_t result = I64(rotated & mask); + setRegister(ra_reg, result); + if (instr->rcBit()) updateCR0(result); + break; + } + case 2: { + // rldic: RA = ROTL64(RS, SH) & MASK(mb, ~SH) + // Actually: MASK(mb, 63-SH) + uint32_t mb = instr->mdMBValue(); + uint64_t mask = MASK64(mb, 63 - sh); + int64_t result = I64(rotated & mask); + setRegister(ra_reg, result); + if (instr->rcBit()) updateCR0(result); + break; + } + case 3: { + // rldimi: RA = (ROTL64(RS,SH) & MASK) | (RA & ~MASK) + uint32_t mb = instr->mdMBValue(); + uint64_t mask = MASK64(mb, 63 - sh); + uint64_t ra_val = U64(getRegister(ra_reg)); + int64_t result = I64((rotated & mask) | (ra_val & ~mask)); + setRegister(ra_reg, result); + if (instr->rcBit()) updateCR0(result); + break; + } + default: + MOZ_CRASH_UNSAFE_PRINTF("decodeRotateMask: MD xo=%u", xo_md); + } + } + } else { + MOZ_CRASH_UNSAFE_PRINTF("decodeRotateMask: opcode=%u", opcode); + } +} + +// ----------------------------------------------------------------------------- +// CR-bit accessors used by the XL-form CR-logic ops (crand, crandc, cror, +// crorc, crxor, creqv). Bit index is in BIF*4+x form: field=b/4, bit=b%4 +// where 0=LT, 1=GT, 2=EQ, 3=SO. +static inline uint8_t CRBitMask(uint32_t bitInField) { + switch (bitInField) { + case 0: return kCRFieldLT; + case 1: return kCRFieldGT; + case 2: return kCRFieldEQ; + case 3: return kCRFieldSO; + } + return 0; +} + +static inline bool GetCRBit(Simulator& s, uint32_t b) { + return (s.getCRField(b / 4) & CRBitMask(b % 4)) != 0; +} + +static inline void SetCRBit(Simulator& s, uint32_t b, bool val) { + uint8_t fv = s.getCRField(b / 4); + uint8_t mask = CRBitMask(b % 4); + s.setCRField(b / 4, val ? (fv | mask) : (fv & ~mask)); +} + +// ----------------------------------------------------------------------------- +// decodeBranch: b(18), bc(16), XL-form(19) + +void Simulator::decodeBranch(SimInstruction* instr) { + uint32_t opcode = instr->opcode(); + + if (opcode == 18) { + // b / bl: I-form unconditional branch + int32_t offset = instr->li26Value(); + bool lk = instr->lkBit(); + bool aa = instr->aaBit(); + + int64_t target; + if (aa) { + target = (int64_t)offset; + } else { + target = get_pc() + (int64_t)offset; + } + + if (lk) { + setLR(get_pc() + SimInstruction::kInstrSize); + } + + set_pc(target); + return; + } + + if (opcode == 16) { + // bc / bcl: B-form conditional branch + uint32_t bo = instr->boValue(); + uint32_t bi = instr->biValue(); + int32_t bd = instr->bd16Value(); + bool lk = instr->lkBit(); + bool aa = instr->aaBit(); + + // Decrement CTR if BO[2] (bit 2 of BO, which is bo & 0x04) is clear. + if (!(bo & 0x04)) { + setCTR(getCTR() - 1); + } + + // Evaluate CTR condition. + bool ctr_ok = (bo & 0x04) || + ((getCTR() != 0) ^ ((bo & 0x02) != 0)); + + // Evaluate CR condition. + uint32_t crField = bi / 4; + uint32_t crBit = bi % 4; + uint8_t crFieldVal = getCRField(crField); + bool crBitSet; + switch (crBit) { + case 0: crBitSet = (crFieldVal & kCRFieldLT) != 0; break; + case 1: crBitSet = (crFieldVal & kCRFieldGT) != 0; break; + case 2: crBitSet = (crFieldVal & kCRFieldEQ) != 0; break; + case 3: crBitSet = (crFieldVal & kCRFieldSO) != 0; break; + default: crBitSet = false; break; + } + bool cond_ok = (bo & 0x10) || (crBitSet == ((bo & 0x08) != 0)); + + if (ctr_ok && cond_ok) { + int64_t target; + if (aa) { + target = (int64_t)bd; + } else { + target = get_pc() + (int64_t)bd; + } + if (lk) { + setLR(get_pc() + SimInstruction::kInstrSize); + } + set_pc(target); + } else { + // Branch not taken. + set_pc(get_pc() + SimInstruction::kInstrSize); + } + return; + } + + if (opcode == 19) { + // XL-form: bclr, bcctr, crand, crandc, cror, crorc, crxor, creqv, + // mcrf, isync + uint32_t xl = instr->xlValue(); + + switch (xl) { + case 16: { + // bclr: conditional branch to LR + uint32_t bo = instr->boValue(); + uint32_t bi = instr->biValue(); + bool lk = instr->lkBit(); + + if (!(bo & 0x04)) { + setCTR(getCTR() - 1); + } + + bool ctr_ok = (bo & 0x04) || + ((getCTR() != 0) ^ ((bo & 0x02) != 0)); + + uint32_t crField = bi / 4; + uint32_t crBit = bi % 4; + uint8_t crFieldVal = getCRField(crField); + bool crBitSet; + switch (crBit) { + case 0: crBitSet = (crFieldVal & kCRFieldLT) != 0; break; + case 1: crBitSet = (crFieldVal & kCRFieldGT) != 0; break; + case 2: crBitSet = (crFieldVal & kCRFieldEQ) != 0; break; + case 3: crBitSet = (crFieldVal & kCRFieldSO) != 0; break; + default: crBitSet = false; break; + } + bool cond_ok = (bo & 0x10) || (crBitSet == ((bo & 0x08) != 0)); + + if (ctr_ok && cond_ok) { + int64_t target = getLR() & ~3LL; + if (lk) { + setLR(get_pc() + SimInstruction::kInstrSize); + } + set_pc(target); + } else { + set_pc(get_pc() + SimInstruction::kInstrSize); + } + break; + } + case 528: { + // bcctr: conditional branch to CTR + uint32_t bo = instr->boValue(); + uint32_t bi = instr->biValue(); + bool lk = instr->lkBit(); + + // CTR is not decremented for bcctr. + uint32_t crField = bi / 4; + uint32_t crBit = bi % 4; + uint8_t crFieldVal = getCRField(crField); + bool crBitSet; + switch (crBit) { + case 0: crBitSet = (crFieldVal & kCRFieldLT) != 0; break; + case 1: crBitSet = (crFieldVal & kCRFieldGT) != 0; break; + case 2: crBitSet = (crFieldVal & kCRFieldEQ) != 0; break; + case 3: crBitSet = (crFieldVal & kCRFieldSO) != 0; break; + default: crBitSet = false; break; + } + bool cond_ok = (bo & 0x10) || (crBitSet == ((bo & 0x08) != 0)); + + if (cond_ok) { + int64_t target = getCTR() & ~3LL; + if (lk) { + setLR(get_pc() + SimInstruction::kInstrSize); + } + set_pc(target); + } else { + set_pc(get_pc() + SimInstruction::kInstrSize); + } + break; + } + case 257: { + // crand: CR[BT] = CR[BA] & CR[BB] + uint32_t bt = instr->rtValue(); + uint32_t ba = instr->raValue(); + uint32_t bb = instr->rbValue(); + SetCRBit(*this, bt, GetCRBit(*this, ba) && GetCRBit(*this, bb)); + break; + } + case 129: { + // crandc: CR[BT] = CR[BA] & ~CR[BB] + uint32_t bt = instr->rtValue(); + uint32_t ba = instr->raValue(); + uint32_t bb = instr->rbValue(); + SetCRBit(*this, bt, GetCRBit(*this, ba) && !GetCRBit(*this, bb)); + break; + } + case 449: { + // cror: CR[BT] = CR[BA] | CR[BB] + uint32_t bt = instr->rtValue(); + uint32_t ba = instr->raValue(); + uint32_t bb = instr->rbValue(); + SetCRBit(*this, bt, GetCRBit(*this, ba) || GetCRBit(*this, bb)); + break; + } + case 417: { + // crorc: CR[BT] = CR[BA] | ~CR[BB] + uint32_t bt = instr->rtValue(); + uint32_t ba = instr->raValue(); + uint32_t bb = instr->rbValue(); + SetCRBit(*this, bt, GetCRBit(*this, ba) || !GetCRBit(*this, bb)); + break; + } + case 193: { + // crxor: CR[BT] = CR[BA] ^ CR[BB] + uint32_t bt = instr->rtValue(); + uint32_t ba = instr->raValue(); + uint32_t bb = instr->rbValue(); + SetCRBit(*this, bt, GetCRBit(*this, ba) ^ GetCRBit(*this, bb)); + break; + } + case 289: { + // creqv: CR[BT] = ~(CR[BA] ^ CR[BB]) + uint32_t bt = instr->rtValue(); + uint32_t ba = instr->raValue(); + uint32_t bb = instr->rbValue(); + SetCRBit(*this, bt, !(GetCRBit(*this, ba) ^ GetCRBit(*this, bb))); + break; + } + case 150: { + // isync: no-op in simulator + break; + } + case 370: { + // PPC_stop (0x4C0002E4) decoded as XL-form opcode 19, XL=370. + // This is our kCallRedirInstr. Handle via softwareInterrupt. + softwareInterrupt(instr); + break; + } + case 2: { + // POWER9 addpcis rT, D (DX-form). Computes rT = (CIA + 4) + + // (sext16(D) << 16). The 16-bit signed displacement D is split + // across three sub-fields: + // d0 = bits LE 6..15 (10 bits) — D[15:6] + // d1 = bits LE 16..20 (5 bits) — D[5:1] + // d2 = bit LE 0 (1 bit) — D[0] + // (Mirrors the encoder in Assembler-ppc.cpp:as_addpcis.) + uint32_t rt = instr->rtValue(); + uint32_t d0 = instr->bits(15, 6); + uint32_t d1 = instr->bits(20, 16); + uint32_t d2 = instr->bit(0); + int16_t D = (int16_t)((d0 << 6) | (d1 << 1) | d2); + int64_t cia = reinterpret_cast(instr); + setRegister(rt, cia + SimInstruction::kInstrSize + + (static_cast(D) << 16)); + break; + } + default: + MOZ_CRASH_UNSAFE_PRINTF("decodeBranch: XL opcode 19, xl=%u", xl); + } + return; + } + + MOZ_CRASH_UNSAFE_PRINTF("decodeBranch: opcode=%u", opcode); +} + +// ----------------------------------------------------------------------------- +// decodeFP: Major opcodes 59 (A-form single) and 63 (X-form / A-form double) + +void Simulator::decodeFP(SimInstruction* instr) { + uint32_t opcode = instr->opcode(); + uint32_t rt = instr->rtValue(); // FRT + uint32_t ra = instr->raValue(); // FRA + uint32_t rb = instr->rbValue(); // FRB + uint32_t rc_reg = instr->rcValue(); // FRC (A-form) + + if (opcode == 63) { + // X-form and A-form double-precision instructions. + // For A-form, the sub-opcode is in bits 1..5. + // For X-form, the sub-opcode is in bits 1..10. + uint32_t xo_a = instr->bits(5, 1); // A-form sub-opcode + uint32_t xo_x = instr->bits(10, 1); // X-form sub-opcode + + // Try A-form first (5-bit sub-opcode in bits 1..5). + switch (xo_a) { + case 21: { + // fadd + double result = getFpuRegisterDouble(ra) + getFpuRegisterDouble(rb); + setFpuRegisterDouble(rt, result); + return; + } + case 20: { + // fsub + double result = getFpuRegisterDouble(ra) - getFpuRegisterDouble(rb); + setFpuRegisterDouble(rt, result); + return; + } + case 25: { + // fmul: FRT = FRA * FRC (note: FRC, not FRB!) + double result = getFpuRegisterDouble(ra) * getFpuRegisterDouble(rc_reg); + setFpuRegisterDouble(rt, result); + return; + } + case 18: { + // fdiv + double result = getFpuRegisterDouble(ra) / getFpuRegisterDouble(rb); + setFpuRegisterDouble(rt, result); + return; + } + case 22: { + // fsqrt + double result = sqrt(getFpuRegisterDouble(rb)); + setFpuRegisterDouble(rt, result); + return; + } + case 29: { + // fmadd: FRT = FRA * FRC + FRB + double result = std::fma(getFpuRegisterDouble(ra), + getFpuRegisterDouble(rc_reg), + getFpuRegisterDouble(rb)); + setFpuRegisterDouble(rt, result); + return; + } + case 30: { + // fnmsub: FRT = -(FRA * FRC - FRB) + double result = -(std::fma(getFpuRegisterDouble(ra), + getFpuRegisterDouble(rc_reg), + -getFpuRegisterDouble(rb))); + setFpuRegisterDouble(rt, result); + return; + } + case 28: { + // fmsub: FRT = FRA * FRC - FRB + double result = std::fma(getFpuRegisterDouble(ra), + getFpuRegisterDouble(rc_reg), + -getFpuRegisterDouble(rb)); + setFpuRegisterDouble(rt, result); + return; + } + case 31: { + // fnmadd: FRT = -(FRA * FRC + FRB) + double result = -(std::fma(getFpuRegisterDouble(ra), + getFpuRegisterDouble(rc_reg), + getFpuRegisterDouble(rb))); + setFpuRegisterDouble(rt, result); + return; + } + case 23: { + // fsel: FRT = (FRA >= 0) ? FRC : FRB + double fra = getFpuRegisterDouble(ra); + setFpuRegisterDouble(rt, (fra >= 0.0) ? getFpuRegisterDouble(rc_reg) + : getFpuRegisterDouble(rb)); + return; + } + case 26: { + // frsqrte: FRT = 1.0 / sqrt(FRB) (estimate) + double result = 1.0 / sqrt(getFpuRegisterDouble(rb)); + setFpuRegisterDouble(rt, result); + return; + } + } + + // X-form (10-bit sub-opcode). + switch (xo_x) { + case 72: { + // fmr: FRT = FRB + setFpuRegisterDouble(rt, getFpuRegisterDouble(rb)); + break; + } + case 40: { + // fneg: FRT = -FRB + setFpuRegisterDouble(rt, -getFpuRegisterDouble(rb)); + break; + } + case 264: { + // fabs: FRT = |FRB| + setFpuRegisterDouble(rt, fabs(getFpuRegisterDouble(rb))); + break; + } + case 136: { + // fnabs: FRT = -|FRB| + setFpuRegisterDouble(rt, -fabs(getFpuRegisterDouble(rb))); + break; + } + case 8: { + // fcpsgn: FRT = sign(FRA) || magnitude(FRB) + double fra = getFpuRegisterDouble(ra); + double frb = getFpuRegisterDouble(rb); + setFpuRegisterDouble(rt, std::copysign(frb, fra)); + break; + } + case 0: { + // fcmpu: compare FRA, FRB unordered + uint32_t bf = instr->bfValue(); + double fra = getFpuRegisterDouble(ra); + double frb = getFpuRegisterDouble(rb); + uint8_t field = 0; + if (std::isnan(fra) || std::isnan(frb)) { + field = kCRFieldSO; + } else if (fra < frb) { + field = kCRFieldLT; + } else if (fra > frb) { + field = kCRFieldGT; + } else { + field = kCRFieldEQ; + } + setCRField(bf, field); + break; + } + case 32: { + // fcmpo: compare FRA, FRB ordered + uint32_t bf = instr->bfValue(); + double fra = getFpuRegisterDouble(ra); + double frb = getFpuRegisterDouble(rb); + uint8_t field = 0; + if (std::isnan(fra) || std::isnan(frb)) { + field = kCRFieldSO; + } else if (fra < frb) { + field = kCRFieldLT; + } else if (fra > frb) { + field = kCRFieldGT; + } else { + field = kCRFieldEQ; + } + setCRField(bf, field); + break; + } + // For fctid* and fctiw* the ISA specifies that bit 23 of FPSCR (VXCVI, + // "invalid op for integer convert") is set when the source is NaN, +Inf, + // -Inf, or out of the destination's range. Wasm's out-of-range trap + // sequence is `mtfsb0 23; fctidz; mfvsrd; mcrfs cr0,5; bt SOBit,trap`, + // so the simulator MUST update VXCVI here for the trap to fire. With + // FPSCR_ in the low-half PPC layout (PPC bit N → int64 bit (31-N)), + // VXCVI lives at int64 bit (31-23) = 8. + case 814: { + // fctid: convert double to int64 (current rounding) + double frb = getFpuRegisterDouble(rb); + int64_t result; + bool invalid = false; + if (std::isnan(frb)) { + result = INT64_MIN; + invalid = true; + } else if (frb >= -(double)INT64_MIN || frb < (double)INT64_MIN) { + result = (frb < 0) ? INT64_MIN : INT64_MAX; + invalid = true; + } else { + switch (FPSCR_ & kFPSCRRNMask) { + case RN: result = (int64_t)llrint(frb); break; + case RZ: result = (int64_t)frb; break; + case RP: result = (int64_t)ceil(frb); break; + case RM: result = (int64_t)floor(frb); break; + default: result = (int64_t)frb; break; + } + } + if (invalid) FPSCR_ |= (1ULL << 8); /* VXCVI: PPC bit 23 in low-half layout */ + setFpuRegister(rt, result); + break; + } + case 815: { + // fctidz: convert double to int64 (round toward zero) + double frb = getFpuRegisterDouble(rb); + int64_t result; + bool invalid = false; + if (std::isnan(frb)) { + result = INT64_MIN; + invalid = true; + } else if (frb >= -(double)INT64_MIN) { + result = INT64_MAX; + invalid = true; + } else if (frb < (double)INT64_MIN) { + result = INT64_MIN; + invalid = true; + } else { + result = (int64_t)frb; + } + if (invalid) FPSCR_ |= (1ULL << 8); /* VXCVI: PPC bit 23 in low-half layout */ + setFpuRegister(rt, result); + break; + } + case 942: { + // fctidu: convert double to uint64 (current rounding). + // VXCVI is signaled when source is NaN, ±Inf, or the rounded value + // is outside [0, 2^64-1]. Notably, + // a negative source whose rounded value is 0 (e.g. -0.4 in RN, or + // any value in (-1, 0) in RZ) is NOT invalid. + double frb = getFpuRegisterDouble(rb); + uint64_t result; + bool invalid = false; + if (std::isnan(frb)) { + result = 0; + invalid = true; + } else if (frb >= -2.0 * (double)INT64_MIN /* 2^64 */) { + result = UINT64_MAX; + invalid = true; + } else { + double rounded; + switch (FPSCR_ & kFPSCRRNMask) { + case RN: rounded = nearbyint(frb); break; + case RZ: rounded = trunc(frb); break; + case RP: rounded = ceil(frb); break; + case RM: rounded = floor(frb); break; + default: rounded = trunc(frb); break; + } + if (rounded < 0.0) { + result = 0; + invalid = true; + } else { + result = (uint64_t)rounded; + } + } + if (invalid) FPSCR_ |= (1ULL << 8); /* VXCVI: PPC bit 23 in low-half layout */ + setFpuRegister(rt, I64(result)); + break; + } + case 943: { + // fctiduz: convert double to uint64 (round toward zero). + // Same VXCVI rule as fctidu but rounding is fixed to truncate + // toward zero. Source in (-1, 0) truncates to 0 — VALID. + double frb = getFpuRegisterDouble(rb); + uint64_t result; + bool invalid = false; + if (std::isnan(frb)) { + result = 0; + invalid = true; + } else if (frb >= -2.0 * (double)INT64_MIN /* 2^64 */) { + result = UINT64_MAX; + invalid = true; + } else if (frb <= -1.0) { + // Truncated value is negative — invalid for unsigned. + result = 0; + invalid = true; + } else { + // Source is in (-1, 2^64); truncation toward zero yields a value + // in [0, 2^64). + result = (uint64_t)trunc(frb); + } + if (invalid) FPSCR_ |= (1ULL << 8); /* VXCVI: PPC bit 23 in low-half layout */ + setFpuRegister(rt, I64(result)); + break; + } + case 14: { + // fctiw: convert double to int32 (current rounding). + // Invalid range: rounded value < INT32_MIN or > INT32_MAX. The + // double-precision boundary on the negative side is INT32_MIN-1 = + // -2^31-1 = -2147483649.0 (exactly representable; doubles in + // (-2^31-1, -2^31) all round-to-nearest to -2^31 which is valid). + double frb = getFpuRegisterDouble(rb); + int32_t result; + bool invalid = false; + if (std::isnan(frb)) { + result = INT32_MIN; + invalid = true; + } else { + double rounded; + switch (FPSCR_ & kFPSCRRNMask) { + case RN: rounded = nearbyint(frb); break; + case RZ: rounded = trunc(frb); break; + case RP: rounded = ceil(frb); break; + case RM: rounded = floor(frb); break; + default: rounded = trunc(frb); break; + } + if (rounded > (double)INT32_MAX) { + result = INT32_MAX; + invalid = true; + } else if (rounded < (double)INT32_MIN) { + result = INT32_MIN; + invalid = true; + } else { + result = (int32_t)rounded; + } + } + if (invalid) FPSCR_ |= (1ULL << 8); /* VXCVI: PPC bit 23 in low-half layout */ + setFpuRegister(rt, (int64_t)result); + break; + } + case 15: { + // fctiwz: convert double to int32 (round toward zero). + // Truncation of a value in (-2^31-1, INT32_MIN) toward zero gives + // INT32_MIN — valid. Only `frb <= -2^31-1` (i.e. `frb < INT32_MIN-1+1` + // = `frb < -2147483648` ... wait, simplest: check truncated value in + // range AFTER truncation.) + double frb = getFpuRegisterDouble(rb); + int32_t result; + bool invalid = false; + if (std::isnan(frb)) { + result = INT32_MIN; + invalid = true; + } else { + double truncated = trunc(frb); + if (truncated > (double)INT32_MAX) { + result = INT32_MAX; + invalid = true; + } else if (truncated < (double)INT32_MIN) { + result = INT32_MIN; + invalid = true; + } else { + result = (int32_t)truncated; + } + } + if (invalid) FPSCR_ |= (1ULL << 8); /* VXCVI: PPC bit 23 in low-half layout */ + setFpuRegister(rt, (int64_t)result); + break; + } + case 142: { + // fctiwu: convert double to uint32 (current rounding). The check is + // on the ROUNDED value: VXCVI iff rounded < 0 or rounded > UINT32_MAX. + double frb = getFpuRegisterDouble(rb); + uint32_t result; + bool invalid = false; + if (std::isnan(frb)) { + result = 0; + invalid = true; + } else { + double rounded; + switch (FPSCR_ & kFPSCRRNMask) { + case RN: rounded = nearbyint(frb); break; + case RZ: rounded = trunc(frb); break; + case RP: rounded = ceil(frb); break; + case RM: rounded = floor(frb); break; + default: rounded = trunc(frb); break; + } + if (rounded < 0.0) { + result = 0; + invalid = true; + } else if (rounded > (double)UINT32_MAX) { + result = UINT32_MAX; + invalid = true; + } else { + result = (uint32_t)rounded; + } + } + if (invalid) FPSCR_ |= (1ULL << 8); /* VXCVI: PPC bit 23 in low-half layout */ + setFpuRegister(rt, (int64_t)(uint64_t)result); + break; + } + case 143: { + // fctiwuz: convert double to uint32 (round toward zero). + // Source in (-1, 0) truncates to 0 — VALID. + double frb = getFpuRegisterDouble(rb); + uint32_t result; + bool invalid = false; + if (std::isnan(frb)) { + result = 0; + invalid = true; + } else { + double truncated = trunc(frb); + if (truncated > (double)UINT32_MAX) { + result = UINT32_MAX; + invalid = true; + } else if (truncated < 0.0) { + result = 0; + invalid = true; + } else { + result = (uint32_t)truncated; + } + } + if (invalid) FPSCR_ |= (1ULL << 8); /* VXCVI: PPC bit 23 in low-half layout */ + setFpuRegister(rt, (int64_t)(uint64_t)result); + break; + } + case 846: { + // fcfid: convert int64 in FPR to double + int64_t val = getFpuRegister(rb); + setFpuRegisterDouble(rt, (double)val); + break; + } + case 974: { + // fcfidu: convert uint64 in FPR to double + uint64_t val = U64(getFpuRegister(rb)); + setFpuRegisterDouble(rt, (double)val); + break; + } + case 12: { + // frsp: round double to single precision (then re-extend in FPR). + // sNaN inputs are quieted (the result payload MSB is set). + // wasm f32.demote_f64 lowers to this op when + // not using xscvdpsp directly. + double frb = getFpuRegisterDouble(rb); + float result = demoteDoublePreservingNaN(frb); + uint32_t fbits; + memcpy(&fbits, &result, sizeof(fbits)); + if ((fbits & 0x7F800000u) == 0x7F800000u && + (fbits & 0x007FFFFFu) != 0) { + fbits |= 0x00400000u; + memcpy(&result, &fbits, sizeof(result)); + } + setFpuRegisterDouble(rt, promoteFloatPreservingNaN(result)); + break; + } + case 392: { + // frin: round to nearest integer (ties away from zero) + double frb = getFpuRegisterDouble(rb); + setFpuRegisterDouble(rt, round(frb)); + break; + } + case 424: { + // friz: round toward zero + double frb = getFpuRegisterDouble(rb); + setFpuRegisterDouble(rt, trunc(frb)); + break; + } + case 456: { + // frip: round toward +infinity (ceil). XO=456. + double frb = getFpuRegisterDouble(rb); + setFpuRegisterDouble(rt, ceil(frb)); + break; + } + case 488: { + // frim: round toward -infinity (floor). XO=488. + double frb = getFpuRegisterDouble(rb); + setFpuRegisterDouble(rt, floor(frb)); + break; + } + case 583: { + // mffs: FRT = FPSCR (as double bit pattern) + setFpuRegister(rt, I64(FPSCR_)); + break; + } + // FPSCR is treated as a 32-bit register stored in the low 32 bits of + // FPSCR_ (uint64_t), with PPC bit numbering: PPC bit N (where bit 0 is + // the MSB) lives at int64 bit (31-N). Field F (4 bits) covers PPC bits + // 4F..4F+3 → int64 bit-LSB (28-4F) to bit-MSB (31-4F). This matches + // mcrfs, mtfsfi, kFPSCRRNMask (which checks bits 30-31 PPC = int64 bits + // 0-1), and mffs (which copies FPSCR into FPR bits 32..63 PPC = int64 + // bits 0..31). Earlier mtfsb0/mtfsb1 used (63-bt) which placed bits in + // the high half of FPSCR_ where mcrfs etc. would never see them — so + // the wasm trap sequence `mtfsb0 23; fctidz; mcrfs cr0,5; bt SO,oolEntry` + // could not detect VXCVI. + case 38: { + // mtfsb1: set FPSCR bit. XO=38. Same PPC bit numbering as mtfsb0 + // (PPC bit N → int64 bit (31-N)); used by the pre-VSX 970 rounding + // helper to set the FPSCR RN field (PPC bits 30-31) around fctid. + uint32_t bt = instr->rtValue(); + FPSCR_ |= (1ULL << (31 - bt)); + break; + } + case 70: { + // mtfsb0: clear FPSCR bit. XO=70. + // (Cases 38 and 70 had the labels swapped, so wasm's + // `mtfsb0 23; fctidz; mcrfs cr0,5; bt SO,trap` sequence accidentally + // SET VXCVI before the convert ran, causing every fctid* to trap.) + uint32_t bt = instr->rtValue(); + FPSCR_ &= ~(1ULL << (31 - bt)); + break; + } + case 64: { + // mcrfs: copy FPSCR field to CR field + uint32_t bf = instr->bfValue(); + uint32_t bfa = instr->bits(20, 18); + uint32_t shift = 4 * (7 - bfa); + uint8_t val = (FPSCR_ >> shift) & 0xF; + setCRField(bf, val); + break; + } + default: + MOZ_CRASH_UNSAFE_PRINTF( + "decodeFP: opcode 63, xo_x=%u (instruction 0x%08x)", xo_x, + instr->instructionBits()); + } + } else if (opcode == 59) { + // A-form single-precision instructions. + uint32_t xo_a = instr->bits(5, 1); + + switch (xo_a) { + case 21: { + // fadds + double result = (double)((float)(getFpuRegisterDouble(ra) + + getFpuRegisterDouble(rb))); + setFpuRegisterDouble(rt, result); + break; + } + case 20: { + // fsubs + double result = (double)((float)(getFpuRegisterDouble(ra) - + getFpuRegisterDouble(rb))); + setFpuRegisterDouble(rt, result); + break; + } + case 25: { + // fmuls: FRT = (float)(FRA * FRC) + double result = (double)((float)(getFpuRegisterDouble(ra) * + getFpuRegisterDouble(rc_reg))); + setFpuRegisterDouble(rt, result); + break; + } + case 18: { + // fdivs + double result = (double)((float)(getFpuRegisterDouble(ra) / + getFpuRegisterDouble(rb))); + setFpuRegisterDouble(rt, result); + break; + } + case 22: { + // fsqrts + double result = (double)sqrtf((float)getFpuRegisterDouble(rb)); + setFpuRegisterDouble(rt, result); + break; + } + case 29: { + // fmadds + double result = (double)((float)std::fma(getFpuRegisterDouble(ra), + getFpuRegisterDouble(rc_reg), + getFpuRegisterDouble(rb))); + setFpuRegisterDouble(rt, result); + break; + } + case 30: { + // fnmsubs + double result = (double)(-(float)std::fma(getFpuRegisterDouble(ra), + getFpuRegisterDouble(rc_reg), + -getFpuRegisterDouble(rb))); + setFpuRegisterDouble(rt, result); + break; + } + case 28: { + // fmsubs + double result = (double)((float)std::fma(getFpuRegisterDouble(ra), + getFpuRegisterDouble(rc_reg), + -getFpuRegisterDouble(rb))); + setFpuRegisterDouble(rt, result); + break; + } + case 31: { + // fnmadds + double result = (double)(-(float)std::fma(getFpuRegisterDouble(ra), + getFpuRegisterDouble(rc_reg), + getFpuRegisterDouble(rb))); + setFpuRegisterDouble(rt, result); + break; + } + default: { + // Try X-form sub-opcodes for opcode 59 (e.g., fcfids, fcfidus). + uint32_t xo_x = instr->bits(10, 1); + switch (xo_x) { + case 846: { + // fcfids: convert int64 to float single (result stored as double) + int64_t val = getFpuRegister(rb); + setFpuRegisterDouble(rt, (double)(float)val); + break; + } + case 974: { + // fcfidus: convert uint64 to float single + uint64_t val = U64(getFpuRegister(rb)); + setFpuRegisterDouble(rt, (double)(float)val); + break; + } + default: + MOZ_CRASH_UNSAFE_PRINTF( + "decodeFP: opcode 59, xo_a=%u xo_x=%u", xo_a, xo_x); + } + break; + } + } + } else { + MOZ_CRASH_UNSAFE_PRINTF("decodeFP: opcode=%u", opcode); + } +} + +// ----------------------------------------------------------------------------- +// decodeVMX: Major opcode 4 (AltiVec/VMX vector ops on VR0-VR31). +// +// VR-form (VX-form): bits 0-5 = primary opcode (4), bits 6-10 = VRT, +// bits 11-15 = VRA, bits 16-20 = VRB, bits 21-31 = XO (11 bits). +// XO extracted via `instructionBits() & 0x7FF`. +// +// Helpers below pack/unpack each VR via the VRregisters_ byte storage +// (16 bytes, big-endian PPC numbering: bytes[0] is the most-significant +// byte of the architectural register, but on PPC LE wasm the lane +// ordering is what the JIT expects). All ops here use byte-level +// accessors for consistency with the existing VMX memory ops. + +void Simulator::decodeVMX(SimInstruction* instr) { + uint32_t xo = instr->instructionBits() & 0x7FFu; + uint32_t vrt = instr->rtValue(); // bits 6..10 + uint32_t vra = instr->raValue(); // bits 11..15 + uint32_t vrb = instr->rbValue(); // bits 16..20 + uint32_t uimm = instr->raValue(); // VA-form: 5-bit immediate at bits 11..15 + + uint8_t a[16], b[16], r[16]; + getVRBytes(vra, a); + getVRBytes(vrb, b); + + // Helpers for treating the byte storage as typed lane arrays. + // The PPCLE wasm SIMD lowering stores each lane's bytes in + // little-endian order, so lane i of an N-byte element occupies bytes + // (i*N) .. (i*N + N - 1) with the LSB at byte (i*N). For example, + // a v128.const i32x4 0x12345678 has bytes [78 56 34 12 …]. + #define LANE_U8(buf, i) ((uint8_t)(buf)[(i)]) + #define LANE_S8(buf, i) ((int8_t)(buf)[(i)]) + #define LANE_U16(buf, i) \ + ((uint16_t)((uint16_t)(buf)[(i) * 2] | \ + ((uint16_t)(buf)[(i) * 2 + 1] << 8))) + #define LANE_S16(buf, i) ((int16_t)LANE_U16(buf, i)) + #define LANE_U32(buf, i) \ + ((uint32_t)((uint32_t)(buf)[(i) * 4] | \ + ((uint32_t)(buf)[(i) * 4 + 1] << 8) | \ + ((uint32_t)(buf)[(i) * 4 + 2] << 16) | \ + ((uint32_t)(buf)[(i) * 4 + 3] << 24))) + #define LANE_S32(buf, i) ((int32_t)LANE_U32(buf, i)) + #define LANE_U64(buf, i) \ + ((uint64_t)((uint64_t)(buf)[(i) * 8] | \ + ((uint64_t)(buf)[(i) * 8 + 1] << 8) | \ + ((uint64_t)(buf)[(i) * 8 + 2] << 16) | \ + ((uint64_t)(buf)[(i) * 8 + 3] << 24) | \ + ((uint64_t)(buf)[(i) * 8 + 4] << 32) | \ + ((uint64_t)(buf)[(i) * 8 + 5] << 40) | \ + ((uint64_t)(buf)[(i) * 8 + 6] << 48) | \ + ((uint64_t)(buf)[(i) * 8 + 7] << 56))) + #define LANE_S64(buf, i) ((int64_t)LANE_U64(buf, i)) + #define SET_LANE_U8(buf, i, v) do { (buf)[(i)] = (uint8_t)(v); } while (0) + #define SET_LANE_U16(buf, i, v) do { \ + (buf)[(i) * 2] = (uint8_t)((uint16_t)(v) & 0xFF); \ + (buf)[(i) * 2 + 1] = (uint8_t)(((uint16_t)(v) >> 8) & 0xFF); \ + } while (0) + #define SET_LANE_U32(buf, i, v) do { \ + (buf)[(i) * 4] = (uint8_t)((uint32_t)(v) & 0xFF); \ + (buf)[(i) * 4 + 1] = (uint8_t)(((uint32_t)(v) >> 8) & 0xFF); \ + (buf)[(i) * 4 + 2] = (uint8_t)(((uint32_t)(v) >> 16) & 0xFF); \ + (buf)[(i) * 4 + 3] = (uint8_t)(((uint32_t)(v) >> 24) & 0xFF); \ + } while (0) + #define SET_LANE_U64(buf, i, v) do { \ + (buf)[(i) * 8] = (uint8_t)((uint64_t)(v) & 0xFF); \ + (buf)[(i) * 8 + 1] = (uint8_t)(((uint64_t)(v) >> 8) & 0xFF); \ + (buf)[(i) * 8 + 2] = (uint8_t)(((uint64_t)(v) >> 16) & 0xFF); \ + (buf)[(i) * 8 + 3] = (uint8_t)(((uint64_t)(v) >> 24) & 0xFF); \ + (buf)[(i) * 8 + 4] = (uint8_t)(((uint64_t)(v) >> 32) & 0xFF); \ + (buf)[(i) * 8 + 5] = (uint8_t)(((uint64_t)(v) >> 40) & 0xFF); \ + (buf)[(i) * 8 + 6] = (uint8_t)(((uint64_t)(v) >> 48) & 0xFF); \ + (buf)[(i) * 8 + 7] = (uint8_t)(((uint64_t)(v) >> 56) & 0xFF); \ + } while (0) + + // --- VA-form pre-dispatch --- + // + // VA-form has a 6-bit XO at bits 26-31 and a 5-bit VRC at bits 21-25. + // decodeVMX's 11-bit XO mask conflates VRC with + // XO, so a plain `switch (xo)` over 11-bit values only matches when + // VRC == 0. Peel off the three VA-form ops actually used by the JIT + // (vmladduhm, vsel, vperm) before the main switch so any VRC value + // works. vsldoi (XO=44) is VX-form with SH at bits 22-25, not VA — + // handled in the switch below. + { + uint32_t va_xo = xo & 0x3Fu; + if (va_xo == 32 || va_xo == 33 || va_xo == 34 || va_xo == 38 || + va_xo == 40 || va_xo == 42 || va_xo == 43) { + uint32_t vrc = (instr->instructionBits() >> 6) & 0x1F; + uint8_t cv[16]; + getVRBytes(vrc, cv); + if (va_xo == 32) { + // vmhaddshs VT,VA,VB,VC : VT[i] = sat_s16( + // (s32)VA.h[i] * (s32)VB.h[i] >> 15 + (s32)VC.h[i]) + // (no rounding term — use vmhraddshs for the rounded form). + for (int i = 0; i < 8; i++) { + int32_t prod = (int32_t)LANE_S16(a, i) * (int32_t)LANE_S16(b, i); + int32_t sum = (prod >> 15) + (int32_t)LANE_S16(cv, i); + if (sum > INT16_MAX) sum = INT16_MAX; + if (sum < INT16_MIN) sum = INT16_MIN; + SET_LANE_U16(r, i, (uint16_t)(int16_t)sum); + } + } else if (va_xo == 33) { + // vmhraddshs VT,VA,VB,VC : rounded Q15 multiply-add-saturate. + // VT[i] = sat_s16(((s32)VA.h[i] * (s32)VB.h[i] + 0x4000) + // >> 15 + (s32)VC.h[i]) + // Used by wasm i16x8.q15mulr_sat_s (VC is zero). + for (int i = 0; i < 8; i++) { + int32_t prod = (int32_t)LANE_S16(a, i) * (int32_t)LANE_S16(b, i); + int32_t sum = ((prod + 0x4000) >> 15) + (int32_t)LANE_S16(cv, i); + if (sum > INT16_MAX) sum = INT16_MAX; + if (sum < INT16_MIN) sum = INT16_MIN; + SET_LANE_U16(r, i, (uint16_t)(int16_t)sum); + } + } else if (va_xo == 34) { + // vmladduhm VT,VA,VB,VC : VT = low16(VA*VB + VC) + for (int i = 0; i < 8; i++) { + uint16_t prod = LANE_U16(a, i) * LANE_U16(b, i); + SET_LANE_U16(r, i, prod + LANE_U16(cv, i)); + } + } else if (va_xo == 40) { + // vmsumshm VT,VA,VB,VC : pairwise multiply-sum of signed halfwords + // into i32 lanes, modulo i32 wrap. + // VT.i32[k] = VC.i32[k] + VA.i16[2k]*VB.i16[2k] + // + VA.i16[2k+1]*VB.i16[2k+1] + // Used by wasm i32x4.dot_i16x8_s with VC = 0, and by + // i32x4.extadd_pairwise_i16x8_s with VB = splat(1) and VC = 0. + for (int k = 0; k < 4; k++) { + int32_t a0 = (int32_t)LANE_S16(a, 2 * k); + int32_t a1 = (int32_t)LANE_S16(a, 2 * k + 1); + int32_t b0 = (int32_t)LANE_S16(b, 2 * k); + int32_t b1 = (int32_t)LANE_S16(b, 2 * k + 1); + int32_t c = LANE_S32(cv, k); + int32_t result = (int32_t)((uint32_t)c + (uint32_t)(a0 * b0) + + (uint32_t)(a1 * b1)); + SET_LANE_U32(r, k, (uint32_t)result); + } + } else if (va_xo == 38) { + // vmsumuhm VT,VA,VB,VC : same as vmsumshm but unsigned halfwords. + // VT.u32[k] = VC.u32[k] + VA.u16[2k]*VB.u16[2k] + // + VA.u16[2k+1]*VB.u16[2k+1] + // Used by wasm i32x4.extadd_pairwise_i16x8_u with VB = splat(1) + // and VC = 0. + for (int k = 0; k < 4; k++) { + uint32_t a0 = (uint32_t)LANE_U16(a, 2 * k); + uint32_t a1 = (uint32_t)LANE_U16(a, 2 * k + 1); + uint32_t b0 = (uint32_t)LANE_U16(b, 2 * k); + uint32_t b1 = (uint32_t)LANE_U16(b, 2 * k + 1); + uint32_t c = LANE_U32(cv, k); + uint32_t result = c + a0 * b0 + a1 * b1; + SET_LANE_U32(r, k, result); + } + } else if (va_xo == 42) { + // vsel VT,VA,VB,VC : VT[i] = (VC[i] & VB[i]) | (~VC[i] & VA[i]) + for (int i = 0; i < 16; i++) { + r[i] = (uint8_t)((cv[i] & b[i]) | (~cv[i] & a[i])); + } + } else { + // vperm VT,VA,VB,VC; empirical LE: + // r[LE_i] = (VC[LE_i] < 16) ? VA[LE_(15-VC[i])] + // : VB[LE_(31-VC[i])] + for (int i = 0; i < 16; i++) { + uint8_t idx = cv[i] & 0x1F; + r[i] = (idx < 16) ? a[15 - idx] : b[31 - idx]; + } + } + setVRBytes(vrt, r); + goto vmx_done; + } + } + + switch (xo) { + // === Integer add (modulo) === + case 0: // vaddubm + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, LANE_U8(a, i) + LANE_U8(b, i)); + } + setVRBytes(vrt, r); break; + case 64: // vadduhm + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, LANE_U16(a, i) + LANE_U16(b, i)); + } + setVRBytes(vrt, r); break; + case 128: // vadduwm + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, LANE_U32(a, i) + LANE_U32(b, i)); + } + setVRBytes(vrt, r); break; + case 192: // vaddudm + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, LANE_U64(a, i) + LANE_U64(b, i)); + } + setVRBytes(vrt, r); break; + + // === Integer sub (modulo) === + case 1024: // vsububm + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, LANE_U8(a, i) - LANE_U8(b, i)); + } + setVRBytes(vrt, r); break; + case 1088: // vsubuhm + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, LANE_U16(a, i) - LANE_U16(b, i)); + } + setVRBytes(vrt, r); break; + case 1152: // vsubuwm + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, LANE_U32(a, i) - LANE_U32(b, i)); + } + setVRBytes(vrt, r); break; + case 1216: // vsubudm + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, LANE_U64(a, i) - LANE_U64(b, i)); + } + setVRBytes(vrt, r); break; + + // === Integer add (saturating, signed) === + case 768: // vaddsbs + for (int i = 0; i < 16; i++) { + int s = (int)LANE_S8(a, i) + (int)LANE_S8(b, i); + if (s > INT8_MAX) s = INT8_MAX; + if (s < INT8_MIN) s = INT8_MIN; + SET_LANE_U8(r, i, (uint8_t)s); + } + setVRBytes(vrt, r); break; + case 832: // vaddshs + for (int i = 0; i < 8; i++) { + int s = (int)LANE_S16(a, i) + (int)LANE_S16(b, i); + if (s > INT16_MAX) s = INT16_MAX; + if (s < INT16_MIN) s = INT16_MIN; + SET_LANE_U16(r, i, (uint16_t)s); + } + setVRBytes(vrt, r); break; + case 896: // vaddsws + for (int i = 0; i < 4; i++) { + int64_t s = (int64_t)LANE_S32(a, i) + (int64_t)LANE_S32(b, i); + if (s > INT32_MAX) s = INT32_MAX; + if (s < INT32_MIN) s = INT32_MIN; + SET_LANE_U32(r, i, (uint32_t)s); + } + setVRBytes(vrt, r); break; + + // === Integer add (saturating, unsigned) === + case 512: // vaddubs + for (int i = 0; i < 16; i++) { + unsigned s = (unsigned)LANE_U8(a, i) + (unsigned)LANE_U8(b, i); + if (s > UINT8_MAX) s = UINT8_MAX; + SET_LANE_U8(r, i, (uint8_t)s); + } + setVRBytes(vrt, r); break; + case 576: // vadduhs + for (int i = 0; i < 8; i++) { + unsigned s = (unsigned)LANE_U16(a, i) + (unsigned)LANE_U16(b, i); + if (s > UINT16_MAX) s = UINT16_MAX; + SET_LANE_U16(r, i, (uint16_t)s); + } + setVRBytes(vrt, r); break; + case 640: // vadduws + for (int i = 0; i < 4; i++) { + uint64_t s = (uint64_t)LANE_U32(a, i) + (uint64_t)LANE_U32(b, i); + if (s > UINT32_MAX) s = UINT32_MAX; + SET_LANE_U32(r, i, (uint32_t)s); + } + setVRBytes(vrt, r); break; + + // === Integer sub (saturating, signed) === + case 1792: // vsubsbs + for (int i = 0; i < 16; i++) { + int s = (int)LANE_S8(a, i) - (int)LANE_S8(b, i); + if (s > INT8_MAX) s = INT8_MAX; + if (s < INT8_MIN) s = INT8_MIN; + SET_LANE_U8(r, i, (uint8_t)s); + } + setVRBytes(vrt, r); break; + case 1856: // vsubshs + for (int i = 0; i < 8; i++) { + int s = (int)LANE_S16(a, i) - (int)LANE_S16(b, i); + if (s > INT16_MAX) s = INT16_MAX; + if (s < INT16_MIN) s = INT16_MIN; + SET_LANE_U16(r, i, (uint16_t)s); + } + setVRBytes(vrt, r); break; + + // === Integer sub (saturating, unsigned) === + case 1536: // vsububs + for (int i = 0; i < 16; i++) { + int s = (int)LANE_U8(a, i) - (int)LANE_U8(b, i); + if (s < 0) s = 0; + SET_LANE_U8(r, i, (uint8_t)s); + } + setVRBytes(vrt, r); break; + case 1600: // vsubuhs + for (int i = 0; i < 8; i++) { + int s = (int)LANE_U16(a, i) - (int)LANE_U16(b, i); + if (s < 0) s = 0; + SET_LANE_U16(r, i, (uint16_t)s); + } + setVRBytes(vrt, r); break; + + // === Average unsigned (rounded: (a+b+1)>>1) === + case 1026: // vavgub + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, + ((unsigned)LANE_U8(a, i) + LANE_U8(b, i) + 1) >> 1); + } + setVRBytes(vrt, r); break; + case 1090: // vavguh + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, + ((unsigned)LANE_U16(a, i) + LANE_U16(b, i) + 1) >> 1); + } + setVRBytes(vrt, r); break; + + // === Vector multiply per-lane (i32x4.mul) === + case 137: { // vmuluwm: per-lane i32 multiply (low 32 bits) + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, LANE_U32(a, i) * LANE_U32(b, i)); + } + setVRBytes(vrt, r); break; + } + + // === POWER10 vmulld: per-lane i64 multiply (low 64 bits) === + case 457: { + for (int i = 0; i < 2; i++) { + uint64_t av = 0, bv = 0; + for (int j = 0; j < 8; j++) { + av |= ((uint64_t)a[i * 8 + j]) << (j * 8); + bv |= ((uint64_t)b[i * 8 + j]) << (j * 8); + } + uint64_t prod = av * bv; // low 64 bits, modulo wrap + for (int j = 0; j < 8; j++) { + r[i * 8 + j] = (uint8_t)(prod >> (j * 8)); + } + } + setVRBytes(vrt, r); break; + } + + // === vmule/vmulo* (multiply even/odd lanes, widening) === + // + // All XO values below were verified by disassembling the + // PPC_vmule*/PPC_vmulo* constants from Assembler-ppc.h with + // `as -mppc -mlittle` + `objdump -Mpower9 -d`. The previous + // version had all 12 XO labels swapped with each other's semantic + // pair (so the JIT's vmulesb was decoded as vmulosb and vice + // versa), causing i8x16→i16x8 extmul to produce wrong halfwords. + // + // PPC_vmuloub = 0x10000008 → XO=8 vmuloub (LE even-byte pairs) + // PPC_vmulouh = 0x10000048 → XO=72 vmulouh + // PPC_vmulouw = 0x10000088 → XO=136 vmulouw + // PPC_vmulosb = 0x10000108 → XO=264 vmulosb + // PPC_vmulosh = 0x10000148 → XO=328 vmulosh + // PPC_vmulosw = 0x10000188 → XO=392 vmulosw + // PPC_vmuleub = 0x10000208 → XO=520 vmuleub (LE odd-byte pairs) + // PPC_vmuleuh = 0x10000248 → XO=584 vmuleuh + // PPC_vmuleuw = 0x10000288 → XO=648 vmuleuw + // PPC_vmulesb = 0x10000308 → XO=776 vmulesb + // PPC_vmulesh = 0x10000348 → XO=840 vmulesh + // PPC_vmulesw = 0x10000388 → XO=904 vmulesw + // + // Lane indexing on LE storage: "BE-even byte i" is stored at LE + // byte index (15 - 2i); since our LANE_S8 uses LE byte index, the + // "BE-even" = "LE-odd" mapping gives `2*i + 1` for vmule, `2*i` + // for vmulo. The JIT's extmul helpers emit `vmulesb + vmulosb + + // vmrglh` to pack both halves; getting the semantics swapped here + // produces the right result register but with the halves in the + // wrong merge order, breaking extmul. + case 776: { // vmulesb: signed BE-even byte → halfword (8 results) + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, + (int16_t)LANE_S8(a, 2 * i + 1) * + (int16_t)LANE_S8(b, 2 * i + 1)); + } + setVRBytes(vrt, r); break; + } + case 520: { // vmuleub: unsigned BE-even byte → halfword + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, + (uint16_t)LANE_U8(a, 2 * i + 1) * + (uint16_t)LANE_U8(b, 2 * i + 1)); + } + setVRBytes(vrt, r); break; + } + case 840: { // vmulesh: signed BE-even halfword → word + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, + (int32_t)LANE_S16(a, 2 * i + 1) * + (int32_t)LANE_S16(b, 2 * i + 1)); + } + setVRBytes(vrt, r); break; + } + case 584: { // vmuleuh: unsigned BE-even halfword → word + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, + (uint32_t)LANE_U16(a, 2 * i + 1) * + (uint32_t)LANE_U16(b, 2 * i + 1)); + } + setVRBytes(vrt, r); break; + } + case 904: { // vmulesw: signed BE-even word → dword (POWER8) + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, + (int64_t)LANE_S32(a, 2 * i + 1) * + (int64_t)LANE_S32(b, 2 * i + 1)); + } + setVRBytes(vrt, r); break; + } + case 648: { // vmuleuw: unsigned BE-even word → dword (POWER8) + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, + (uint64_t)LANE_U32(a, 2 * i + 1) * + (uint64_t)LANE_U32(b, 2 * i + 1)); + } + setVRBytes(vrt, r); break; + } + case 264: { // vmulosb: signed BE-odd byte → halfword + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, + (int16_t)LANE_S8(a, 2 * i) * + (int16_t)LANE_S8(b, 2 * i)); + } + setVRBytes(vrt, r); break; + } + case 8: { // vmuloub: unsigned BE-odd byte + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, + (uint16_t)LANE_U8(a, 2 * i) * + (uint16_t)LANE_U8(b, 2 * i)); + } + setVRBytes(vrt, r); break; + } + case 328: { // vmulosh: signed BE-odd halfword → word + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, + (int32_t)LANE_S16(a, 2 * i) * + (int32_t)LANE_S16(b, 2 * i)); + } + setVRBytes(vrt, r); break; + } + case 72: { // vmulouh: unsigned BE-odd halfword → word + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, + (uint32_t)LANE_U16(a, 2 * i) * + (uint32_t)LANE_U16(b, 2 * i)); + } + setVRBytes(vrt, r); break; + } + case 392: { // vmulosw: signed BE-odd word + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, + (int64_t)LANE_S32(a, 2 * i) * + (int64_t)LANE_S32(b, 2 * i)); + } + setVRBytes(vrt, r); break; + } + case 136: { // vmulouw: unsigned BE-odd word + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, + (uint64_t)LANE_U32(a, 2 * i) * + (uint64_t)LANE_U32(b, 2 * i)); + } + setVRBytes(vrt, r); break; + } + + // === Per-lane rotate left (vrl{b,h,w,d}) === + case 4: // vrlb + for (int i = 0; i < 16; i++) { + uint8_t v = LANE_U8(a, i); + uint32_t s = LANE_U8(b, i) & 7; + SET_LANE_U8(r, i, (uint8_t)((v << s) | (v >> ((8 - s) & 7)))); + } + setVRBytes(vrt, r); break; + case 68: // vrlh + for (int i = 0; i < 8; i++) { + uint16_t v = LANE_U16(a, i); + uint32_t s = LANE_U16(b, i) & 15; + SET_LANE_U16(r, i, (uint16_t)((v << s) | (v >> ((16 - s) & 15)))); + } + setVRBytes(vrt, r); break; + case 132: // vrlw + for (int i = 0; i < 4; i++) { + uint32_t v = LANE_U32(a, i); + uint32_t s = LANE_U32(b, i) & 31; + SET_LANE_U32(r, i, (v << s) | (v >> ((32 - s) & 31))); + } + setVRBytes(vrt, r); break; + case 196: // vrld + for (int i = 0; i < 2; i++) { + uint64_t v = LANE_U64(a, i); + uint32_t s = LANE_U64(b, i) & 63; + SET_LANE_U64(r, i, (v << s) | (v >> ((64 - s) & 63))); + } + setVRBytes(vrt, r); break; + + // === Min / Max signed === + case 258: // vmaxsb + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, std::max(LANE_S8(a, i), LANE_S8(b, i))); + } + setVRBytes(vrt, r); break; + case 322: // vmaxsh + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, std::max(LANE_S16(a, i), LANE_S16(b, i))); + } + setVRBytes(vrt, r); break; + case 386: // vmaxsw + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, std::max(LANE_S32(a, i), LANE_S32(b, i))); + } + setVRBytes(vrt, r); break; + case 450: // vmaxsd + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, std::max(LANE_S64(a, i), LANE_S64(b, i))); + } + setVRBytes(vrt, r); break; + case 770: // vminsb + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, std::min(LANE_S8(a, i), LANE_S8(b, i))); + } + setVRBytes(vrt, r); break; + case 834: // vminsh + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, std::min(LANE_S16(a, i), LANE_S16(b, i))); + } + setVRBytes(vrt, r); break; + case 898: // vminsw + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, std::min(LANE_S32(a, i), LANE_S32(b, i))); + } + setVRBytes(vrt, r); break; + case 962: // vminsd + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, std::min(LANE_S64(a, i), LANE_S64(b, i))); + } + setVRBytes(vrt, r); break; + + // === Min / Max unsigned === + case 2: // vmaxub + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, std::max(LANE_U8(a, i), LANE_U8(b, i))); + } + setVRBytes(vrt, r); break; + case 66: // vmaxuh + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, std::max(LANE_U16(a, i), LANE_U16(b, i))); + } + setVRBytes(vrt, r); break; + case 130: // vmaxuw + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, std::max(LANE_U32(a, i), LANE_U32(b, i))); + } + setVRBytes(vrt, r); break; + case 194: // vmaxud + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, std::max(LANE_U64(a, i), LANE_U64(b, i))); + } + setVRBytes(vrt, r); break; + case 514: // vminub + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, std::min(LANE_U8(a, i), LANE_U8(b, i))); + } + setVRBytes(vrt, r); break; + case 578: // vminuh + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, std::min(LANE_U16(a, i), LANE_U16(b, i))); + } + setVRBytes(vrt, r); break; + case 642: // vminuw + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, std::min(LANE_U32(a, i), LANE_U32(b, i))); + } + setVRBytes(vrt, r); break; + case 706: // vminud + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, std::min(LANE_U64(a, i), LANE_U64(b, i))); + } + setVRBytes(vrt, r); break; + + // === Vector compare (eq, gt signed, gt unsigned, ne POWER9) === + // + // All vcmp* ops set per-lane all-1s on true, all-0s on false. The + // record form (Rc=1, XO MSB bit set; XO_rec = XO_base + 1024) must + // additionally write CR6: + // CR6.LT = 1 iff ALL lanes are true; + // CR6.GT = 0 (always); + // CR6.EQ = 1 iff NO lane is true; + // CR6.SO = 0 (always). + // `i8x16.all_true` etc. in wasm rely on CR6.EQ via `mfocrf cr6`; the + // previous simulator implementation left CR6 untouched, so the + // predicate was always wrong. + // + // Helper: count true lanes by looking at byte 0 of each lane (all + // bytes within a "true" lane are 0xFF so byte 0 is a sound proxy). + #define VCMP_DONE(lanes_, lane_bytes_) \ + do { \ + setVRBytes(vrt, r); \ + if (xo >= 1024) { \ + int numTrue_ = 0; \ + for (int i_ = 0; i_ < (lanes_); i_++) { \ + if (r[i_ * (lane_bytes_)] == 0xFF) numTrue_++; \ + } \ + uint8_t field_ = 0; \ + if (numTrue_ == (lanes_)) field_ |= kCRFieldLT; \ + if (numTrue_ == 0) field_ |= kCRFieldEQ; \ + setCRField(6, field_); \ + } \ + } while (0) + + case 6: // vcmpequb (Rc=0) + case 1030: // vcmpequb. (record, CR6 updated) + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, LANE_U8(a, i) == LANE_U8(b, i) ? 0xFF : 0); + } + VCMP_DONE(16, 1); break; + case 70: // vcmpequh + case 1094: // vcmpequh. + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, LANE_U16(a, i) == LANE_U16(b, i) ? 0xFFFF : 0); + } + VCMP_DONE(8, 2); break; + case 134: // vcmpequw + case 1158: // vcmpequw. + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, + LANE_U32(a, i) == LANE_U32(b, i) ? 0xFFFFFFFFu : 0); + } + VCMP_DONE(4, 4); break; + case 199: // vcmpequd + case 1223: // vcmpequd. + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, + LANE_U64(a, i) == LANE_U64(b, i) + ? UINT64_MAX + : 0); + } + VCMP_DONE(2, 8); break; + + // === Compare greater-than signed === + case 774: // vcmpgtsb + case 1798: // vcmpgtsb. + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, LANE_S8(a, i) > LANE_S8(b, i) ? 0xFF : 0); + } + VCMP_DONE(16, 1); break; + case 838: // vcmpgtsh + case 1862: // vcmpgtsh. + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, LANE_S16(a, i) > LANE_S16(b, i) ? 0xFFFF : 0); + } + VCMP_DONE(8, 2); break; + case 902: // vcmpgtsw + case 1926: // vcmpgtsw. + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, + LANE_S32(a, i) > LANE_S32(b, i) ? 0xFFFFFFFFu : 0); + } + VCMP_DONE(4, 4); break; + case 967: // vcmpgtsd + case 1991: // vcmpgtsd. + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, + LANE_S64(a, i) > LANE_S64(b, i) ? UINT64_MAX : 0); + } + VCMP_DONE(2, 8); break; + + // === Compare greater-than unsigned === + case 518: // vcmpgtub + case 1542: // vcmpgtub. + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, LANE_U8(a, i) > LANE_U8(b, i) ? 0xFF : 0); + } + VCMP_DONE(16, 1); break; + case 582: // vcmpgtuh + case 1606: // vcmpgtuh. + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, LANE_U16(a, i) > LANE_U16(b, i) ? 0xFFFF : 0); + } + VCMP_DONE(8, 2); break; + case 646: // vcmpgtuw + case 1670: // vcmpgtuw. + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, + LANE_U32(a, i) > LANE_U32(b, i) ? 0xFFFFFFFFu : 0); + } + VCMP_DONE(4, 4); break; + case 711: // vcmpgtud + case 1735: // vcmpgtud. + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, + LANE_U64(a, i) > LANE_U64(b, i) ? UINT64_MAX : 0); + } + VCMP_DONE(2, 8); break; + + // === Splat from immediate (5-bit signed splat into all lanes) === + // ISA defines UIM in BE element numbering. For LE storage, BE element i = LE element (N-1-i). + case 524: // vspltb: VRT[*] = VRB[BE-byte-UIM]; uimm from VRA field (bits 11..15) + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, LANE_U8(b, 15 - (uimm & 0xF))); + } + setVRBytes(vrt, r); break; + case 588: // vsplth + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, LANE_U16(b, 7 - (uimm & 0x7))); + } + setVRBytes(vrt, r); break; + case 652: // vspltw + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, LANE_U32(b, 3 - (uimm & 0x3))); + } + setVRBytes(vrt, r); break; + + // === Splat 5-bit signed immediate to all byte lanes === + case 780: { // vspltisb VRT, SIMM5 + int32_t simm5 = (int32_t)((instr->instructionBits() >> 16) & 0x1F); + if (simm5 & 0x10) simm5 |= ~0x1F; + uint8_t b = (uint8_t)(int8_t)simm5; + memset(r, b, 16); + setVRBytes(vrt, r); break; + } + + // === Splat 5-bit signed immediate to all halfword lanes === + case 844: { // vspltish VRT, SIMM5 + // SIMM5 occupies bits 11..15 of the instruction (VRA field). It + // is sign-extended to 16 bits and replicated across all 8 halfword + // lanes of VRT. Range: [-16, 15]. + int32_t simm5 = (int32_t)((instr->instructionBits() >> 16) & 0x1F); + if (simm5 & 0x10) simm5 |= ~0x1F; // sign-extend bit 4 + int16_t hw = (int16_t)simm5; + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, (uint16_t)hw); + } + setVRBytes(vrt, r); break; + } + + // === Splat 5-bit signed immediate to all word lanes === + case 908: { // vspltisw VRT, SIMM5 + int32_t simm5 = (int32_t)((instr->instructionBits() >> 16) & 0x1F); + if (simm5 & 0x10) simm5 |= ~0x1F; + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, (uint32_t)simm5); + } + setVRBytes(vrt, r); break; + } + + // === Merge (interleave) === + // + // The ISA defines vmrgh* / vmrgl* in BE numbering; the + // empirical LE storage behaviour is: + // vmrgh* VT,VA,VB: for i in 0..N/2-1, + // VT.lane_LE[2i] = VB.lane_LE[(N/2) + i] + // VT.lane_LE[2i+1] = VA.lane_LE[(N/2) + i] + // vmrgl* VT,VA,VB: for i in 0..N/2-1, + // VT.lane_LE[2i] = VB.lane_LE[i] + // VT.lane_LE[2i+1] = VA.lane_LE[i] + // i.e. the VB operand goes to the even result positions (reversed + // from what a naïve BE reading would suggest) and the "high" form + // selects the upper-half of LE storage. + case 12: // vmrghb + for (int i = 0; i < 8; i++) { + SET_LANE_U8(r, 2 * i, LANE_U8(b, 8 + i)); + SET_LANE_U8(r, 2 * i + 1, LANE_U8(a, 8 + i)); + } + setVRBytes(vrt, r); break; + case 76: // vmrghh + for (int i = 0; i < 4; i++) { + SET_LANE_U16(r, 2 * i, LANE_U16(b, 4 + i)); + SET_LANE_U16(r, 2 * i + 1, LANE_U16(a, 4 + i)); + } + setVRBytes(vrt, r); break; + case 140: // vmrghw + for (int i = 0; i < 2; i++) { + SET_LANE_U32(r, 2 * i, LANE_U32(b, 2 + i)); + SET_LANE_U32(r, 2 * i + 1, LANE_U32(a, 2 + i)); + } + setVRBytes(vrt, r); break; + case 268: // vmrglb + for (int i = 0; i < 8; i++) { + SET_LANE_U8(r, 2 * i, LANE_U8(b, i)); + SET_LANE_U8(r, 2 * i + 1, LANE_U8(a, i)); + } + setVRBytes(vrt, r); break; + case 332: // vmrglh + for (int i = 0; i < 4; i++) { + SET_LANE_U16(r, 2 * i, LANE_U16(b, i)); + SET_LANE_U16(r, 2 * i + 1, LANE_U16(a, i)); + } + setVRBytes(vrt, r); break; + case 396: // vmrglw + for (int i = 0; i < 2; i++) { + SET_LANE_U32(r, 2 * i, LANE_U32(b, i)); + SET_LANE_U32(r, 2 * i + 1, LANE_U32(a, i)); + } + setVRBytes(vrt, r); break; + + // === Per-lane shift left (count from VRB, low N bits per element) === + case 260: // vslb + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, LANE_U8(a, i) << (LANE_U8(b, i) & 7)); + } + setVRBytes(vrt, r); break; + case 324: // vslh + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, LANE_U16(a, i) << (LANE_U16(b, i) & 15)); + } + setVRBytes(vrt, r); break; + case 388: // vslw + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, LANE_U32(a, i) << (LANE_U32(b, i) & 31)); + } + setVRBytes(vrt, r); break; + case 1476: // vsld + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, LANE_U64(a, i) << (LANE_U64(b, i) & 63)); + } + setVRBytes(vrt, r); break; + + // === Per-lane shift right unsigned === + case 516: // vsrb + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, LANE_U8(a, i) >> (LANE_U8(b, i) & 7)); + } + setVRBytes(vrt, r); break; + case 580: // vsrh + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, LANE_U16(a, i) >> (LANE_U16(b, i) & 15)); + } + setVRBytes(vrt, r); break; + case 644: // vsrw + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, LANE_U32(a, i) >> (LANE_U32(b, i) & 31)); + } + setVRBytes(vrt, r); break; + case 1732: // vsrd + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, LANE_U64(a, i) >> (LANE_U64(b, i) & 63)); + } + setVRBytes(vrt, r); break; + + // === Per-lane shift right algebraic (signed) === + case 772: // vsrab + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, + (uint8_t)(LANE_S8(a, i) >> (LANE_U8(b, i) & 7))); + } + setVRBytes(vrt, r); break; + case 836: // vsrah + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, + (uint16_t)(LANE_S16(a, i) >> (LANE_U16(b, i) & 15))); + } + setVRBytes(vrt, r); break; + case 900: // vsraw + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, + (uint32_t)(LANE_S32(a, i) >> (LANE_U32(b, i) & 31))); + } + setVRBytes(vrt, r); break; + case 964: // vsrad + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, + (uint64_t)(LANE_S64(a, i) >> (LANE_U64(b, i) & 63))); + } + setVRBytes(vrt, r); break; + + // === POWER9 per-lane integer negate (subop in VRA field) === + // PPC_vnegw = 0x10060602 → XO=0x602=1538, VRA=6 + // PPC_vnegd = 0x10070602 → XO=0x602=1538, VRA=7 + case 1538: + if (vra == 6) { // vnegw + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, (uint32_t)(-LANE_S32(b, i))); + } + } else if (vra == 7) { // vnegd + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, (uint64_t)(-LANE_S64(b, i))); + } + } else { + MOZ_CRASH_UNSAFE_PRINTF("decodeVMX XO=1538: unknown subop %u", vra); + } + setVRBytes(vrt, r); break; + + // === POWER10 vextract{b,h,w,d}m (XO=1602=0x642) === + // RT (GPR) gets the wasm-spec bitmask in low 16/8/4/2 bits. UIM at + // bits 11..15 (= sim `vra`) selects lane width: 8=byte, 9=halfword, + // 10=word, 11=doubleword. + case 1602: { + uint64_t result = 0; + switch (vra) { + case 8: // vextractbm: 16 byte lanes + for (int i = 0; i < 16; i++) { + if (b[i] & 0x80) result |= (1ULL << i); + } + break; + case 9: // vextracthm: 8 halfword lanes; MSB lives at byte 2i+1 + for (int i = 0; i < 8; i++) { + if (b[2 * i + 1] & 0x80) result |= (1ULL << i); + } + break; + case 10: // vextractwm: 4 word lanes; MSB at byte 4i+3 + for (int i = 0; i < 4; i++) { + if (b[4 * i + 3] & 0x80) result |= (1ULL << i); + } + break; + case 11: // vextractdm: 2 dword lanes; MSB at byte 8i+7 + for (int i = 0; i < 2; i++) { + if (b[8 * i + 7] & 0x80) result |= (1ULL << i); + } + break; + default: + MOZ_CRASH_UNSAFE_PRINTF("decodeVMX XO=1602: unknown UIM %u", vra); + } + // vrt is the GPR target (RT field at bits 6..10). + setRegister(int(vrt), int64_t(result)); + goto vmx_done; // Skip the trailing setVRBytes used by VR-targeting ops. + } + + // === POWER9 vinsertb (XO=781) / vinserth (XO=845) === + // Insert byte/halfword from a VR (NOT a GPR) at an immediate byte + // position UIM (BE). + // vinsertb: VRT.byte[UIM] (BE) ← VRB.byte[7] (BE) + // vinserth: VRT.byte[UIM] (BE) ← VRB.byte[6] (BE) + // VRT.byte[UIM+1] (BE) ← VRB.byte[7] (BE) + // BE byte i ↔ LE byte (15-i). So VRB.byte[6] (BE) = LE byte 9 of + // VRB, VRB.byte[7] (BE) = LE byte 8. (Byte-pair order matters.) + case 781: // vinsertb + case 845: { // vinserth + getVRBytes(vrt, r); // start from current VRT + if (xo == 845) { + // vinserth: copy 2-byte halfword (BE bytes 6..7 of VRB). + r[15 - uimm] = b[9]; // BE byte UIM ← VRB BE byte 6 + r[14 - uimm] = b[8]; // BE byte UIM+1 ← VRB BE byte 7 + } else { + // vinsertb: copy a single byte (BE byte 7 of VRB). + r[15 - uimm] = b[8]; // BE byte UIM ← VRB BE byte 7 + } + setVRBytes(vrt, r); break; + } + + // === POWER9 vextractub (XO=525) / vextractuh (XO=589) === + // Extract one byte/halfword from VRB at immediate BE position UIM + // and place it at BE byte 7 of VRT, with all other bytes of VRT + // zeroed. Companion to vinsertb/h; chooses an immediate BE position + // and lands the result at the low byte of VRT (= low byte of mfvsrd). + // vextractub: VRT.byte[7] (BE) ← VRB.byte[UIM] (BE), rest = 0 + // vextractuh: VRT.byte[6] (BE) ← VRB.byte[UIM] (BE) + // VRT.byte[7] (BE) ← VRB.byte[UIM+1] (BE), rest = 0 + case 525: // vextractub + case 589: { // vextractuh + memset(r, 0, sizeof(r)); + if (xo == 589) { + r[9] = b[15 - uimm]; // VRT BE byte 6 ← VRB BE byte UIM + r[8] = b[14 - uimm]; // VRT BE byte 7 ← VRB BE byte UIM+1 + } else { + r[8] = b[15 - uimm]; // VRT BE byte 7 ← VRB BE byte UIM + } + setVRBytes(vrt, r); break; + } + + // === POWER10 vinsbrx (XO=783) / vinshrx (XO=847) === + // Right-indexed (LE-natural) byte/halfword insert from GPR. RA's + // low 4 bits supply the byte position (mod 16); for vinshrx the + // position is also masked to even (& 0xE) so the halfword is + // 2-byte aligned. RB's low 8 / 16 bits are inserted; other bytes + // of VRT are unchanged. RA and RB are GPRs (NOT VRs) — sim's + // pre-fetched `a` and `b` from getVRBytes are unused here. + case 783: // vinsbrx + case 847: { // vinshrx + uint64_t ra_val = U64(getRegister(int(vra))); + uint64_t rb_val = U64(getRegister(int(vrb))); + getVRBytes(vrt, r); // start from current VRT + const bool isHalf = (xo == 847); + const uint32_t pos = isHalf ? uint32_t(ra_val & 0xEULL) + : uint32_t(ra_val & 0xFULL); + r[pos] = (uint8_t)(rb_val & 0xFFULL); + if (isHalf) { + r[pos + 1] = (uint8_t)((rb_val >> 8) & 0xFFULL); + } + setVRBytes(vrt, r); break; + } + + // === POWER10 vinsw (XO=207) / vinsd (XO=463) === + // VRT[UIM*8:UIM*8+N-1] (BE bits) ← RB low N bits, where N = 32 or 64. + // RB is a GPR (the `vrb` field at sim bits 15..11). UIM is at sim + // bits 20..16 (= the `uimm` / `vra` decode). Other bytes of VRT are + // unchanged, so we read VRT first then patch UIM..UIM+(N/8-1). + case 207: // vinsw + case 463: { // vinsd + uint64_t rb_val = U64(getRegister(int(vrb))); + getVRBytes(vrt, r); // start from current VRT + const int width = (xo == 463) ? 8 : 4; // bytes + // BE byte UIM+i of VRT = LE byte (15 - UIM - i). + // For vinsd, RB.dword[0] (BE) = bits 56..63 of rb_val (host LSB end + // of the GPR — recall U64() puts the canonical 64-bit value in a + // host uint64_t with bit 63 = MSB). + // For vinsw, source is RB[32:63] = low 32 bits of rb_val. + uint64_t src = (width == 8) ? rb_val : (rb_val & 0xFFFFFFFFULL); + const int srcMsbShift = (width * 8) - 8; // 56 or 24 + for (int i = 0; i < width; i++) { + r[15 - uimm - i] = (uint8_t)(src >> (srcMsbShift - 8 * i)); + } + setVRBytes(vrt, r); break; + } + + // === POWER8+ vbpermq (XO=1356=0x54C): per-byte bit permute === + // For each i in 0..15, take VRB BE-byte i (= sim b[15-i]); if its + // high bit is set, perm[i]=0; else perm[i] = bit at BE position + // (low 7 bits) of VRA. ISA says perm[0..15] go into VRT.dw[1] low + // 16 bits, but on real LE silicon the bitmap is observable in dw[0] + // low 16 bits — i.e., recoverable via mfvsrd. Match that observable + // behaviour: write the bitmap into sim bytes[8..9] (where mfvsrd + // reads dw[0] from), zero the rest. + case 1356: { + uint8_t perm[16]; + for (int k = 0; k < 16; k++) { + uint8_t ctl = b[15 - k]; + if (ctl & 0x80) { + perm[k] = 0; + } else { + int p = ctl & 0x7F; + int le_idx = 15 - (p / 8); + int bit_in_byte = 7 - (p % 8); + perm[k] = (a[le_idx] >> bit_in_byte) & 1; + } + } + uint8_t lo = 0, hi = 0; + for (int k = 0; k < 8; k++) hi = (hi << 1) | perm[k]; + for (int k = 8; k < 16; k++) lo = (lo << 1) | perm[k]; + for (int i = 0; i < 16; i++) r[i] = 0; + r[8] = lo; + r[9] = hi; + setVRBytes(vrt, r); break; + } + + // VA-form ops vmladduhm (XO=34), vsel (XO=42), vperm (XO=43) are + // peeled off in the pre-dispatch above (see "VA-form pre-dispatch" + // comment near the top of this function), since the 11-bit XO + // mask conflates VRC into the case label. + + // === Unpack high signed (BE-numbering = LE indices 8..15) === + // vupkhsb: VRT[i] = sign_extend_to_16(VRA[i+0..7]). On LE storage with + // BE-named "high" being the low-indexed bytes, vupkhsb sign-extends the + // low 8 bytes of VRA into 8 halfwords. PPCLE wasm calls these the + // "high" lanes per PPC convention; the JIT compensates internally via + // the vupklsb/vupkhsb swap documented in MacroAssembler-ppc-inl.h. + case 526: // vupkhsb (high signed byte → halfword) + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, (uint16_t)(int16_t)LANE_S8(b, 8 + i)); + } + setVRBytes(vrt, r); break; + case 590: // vupkhsh (high signed halfword → word) + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, (uint32_t)(int32_t)LANE_S16(b, 4 + i)); + } + setVRBytes(vrt, r); break; + case 1614: // vupkhsw (high signed word → dword) POWER8+ + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, (uint64_t)(int64_t)LANE_S32(b, 2 + i)); + } + setVRBytes(vrt, r); break; + case 654: // vupklsb (low signed byte → halfword) — PPC LE: takes high lanes + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, (uint16_t)(int16_t)LANE_S8(b, i)); + } + setVRBytes(vrt, r); break; + case 718: // vupklsh (low signed halfword → word) + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, (uint32_t)(int32_t)LANE_S16(b, i)); + } + setVRBytes(vrt, r); break; + case 1742: // vupklsw (low signed word → dword) + for (int i = 0; i < 2; i++) { + SET_LANE_U64(r, i, (uint64_t)(int64_t)LANE_S32(b, i)); + } + setVRBytes(vrt, r); break; + + // === Pack (saturate or modulo) === + // + // vpk* definitions are BE-specified: + // VT.byte[0..7] = saturate(VA.halfword[0..7]), VT.byte[8..15] = + // saturate(VB.halfword[0..7]) (BE-numbered throughout). On + // PPCLE register storage that inverts to: LE bytes 0-7 = VB's + // saturated halfwords, LE bytes 8-15 = VA's. + // + // vpkshus = XO 270 (s16 → u8 sat) + // vpkshss = XO 398 (s16 → s8 sat) + // vpkswus = XO 334 (s32 → u16 sat) + // vpkswss = XO 462 (s32 → s16 sat) + // The sim previously had three of these four labels rotated + // (270=vpkshss, 334=vpkshus, 398=vpkswus) so every i8x16/i16x8 + // narrow_* call silently used the wrong saturation kind or + // lane width — vpkshss was completely absent. + case 398: { // vpkshss (signed halfword → signed byte) + for (int i = 0; i < 8; i++) { + int v = LANE_S16(b, i); + if (v > INT8_MAX) v = INT8_MAX; + if (v < INT8_MIN) v = INT8_MIN; + SET_LANE_U8(r, i, (uint8_t)(int8_t)v); + } + for (int i = 0; i < 8; i++) { + int v = LANE_S16(a, i); + if (v > INT8_MAX) v = INT8_MAX; + if (v < INT8_MIN) v = INT8_MIN; + SET_LANE_U8(r, 8 + i, (uint8_t)(int8_t)v); + } + setVRBytes(vrt, r); break; + } + case 462: { // vpkswss (signed word → signed halfword) + for (int i = 0; i < 4; i++) { + int64_t v = LANE_S32(b, i); + if (v > INT16_MAX) v = INT16_MAX; + if (v < INT16_MIN) v = INT16_MIN; + SET_LANE_U16(r, i, (uint16_t)(int16_t)v); + } + for (int i = 0; i < 4; i++) { + int64_t v = LANE_S32(a, i); + if (v > INT16_MAX) v = INT16_MAX; + if (v < INT16_MIN) v = INT16_MIN; + SET_LANE_U16(r, 4 + i, (uint16_t)(int16_t)v); + } + setVRBytes(vrt, r); break; + } + case 270: { // vpkshus (signed halfword → unsigned byte, sat) + for (int i = 0; i < 8; i++) { + int v = LANE_S16(b, i); + if (v > UINT8_MAX) v = UINT8_MAX; + if (v < 0) v = 0; + SET_LANE_U8(r, i, (uint8_t)v); + } + for (int i = 0; i < 8; i++) { + int v = LANE_S16(a, i); + if (v > UINT8_MAX) v = UINT8_MAX; + if (v < 0) v = 0; + SET_LANE_U8(r, 8 + i, (uint8_t)v); + } + setVRBytes(vrt, r); break; + } + case 334: { // vpkswus (signed word → unsigned halfword, sat) + for (int i = 0; i < 4; i++) { + int64_t v = LANE_S32(b, i); + if (v > UINT16_MAX) v = UINT16_MAX; + if (v < 0) v = 0; + SET_LANE_U16(r, i, (uint16_t)v); + } + for (int i = 0; i < 4; i++) { + int64_t v = LANE_S32(a, i); + if (v > UINT16_MAX) v = UINT16_MAX; + if (v < 0) v = 0; + SET_LANE_U16(r, 4 + i, (uint16_t)v); + } + setVRBytes(vrt, r); break; + } + + // === POWER9 compare not-equal (vcmpne{b,h,w}) — Rc=0 and Rc=1 === + case 7: // vcmpneb + case 1031: // vcmpneb. + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, LANE_U8(a, i) != LANE_U8(b, i) ? 0xFF : 0); + } + VCMP_DONE(16, 1); break; + case 71: // vcmpneh + case 1095: // vcmpneh. + for (int i = 0; i < 8; i++) { + SET_LANE_U16(r, i, LANE_U16(a, i) != LANE_U16(b, i) ? 0xFFFF : 0); + } + VCMP_DONE(8, 2); break; + case 135: // vcmpnew + case 1159: // vcmpnew. + for (int i = 0; i < 4; i++) { + SET_LANE_U32(r, i, + LANE_U32(a, i) != LANE_U32(b, i) ? 0xFFFFFFFFu : 0); + } + VCMP_DONE(4, 4); break; + #undef VCMP_DONE + + // === Population count per byte (POWER8) === + case 1795: { // vpopcntb (XO 0x703 = 1795). VRA field unused. + for (int i = 0; i < 16; i++) { + SET_LANE_U8(r, i, (uint8_t)__builtin_popcount(LANE_U8(b, i))); + } + setVRBytes(vrt, r); break; + } + + // === vsldoi: VRT = (VRA || VRB) shifted left by SH bytes (SH at bits 22..25) === + case 44: case 45: case 46: case 47: { + // SH is at bits 22..25 (PPC) → LSB bits 6..9 of the instruction → + // (instructionBits >> 6) & 0xF. Our XO mask already bottoms-out at + // bit 0, so extract from the raw instruction. + uint32_t sh = (instr->instructionBits() >> 6) & 0xF; + uint8_t cat[32]; + memcpy(cat, a, 16); + memcpy(cat + 16, b, 16); + for (int i = 0; i < 16; i++) { + r[i] = cat[sh + i]; + } + setVRBytes(vrt, r); break; + } + + + default: + MOZ_CRASH_UNSAFE_PRINTF( + "decodeVMX: unimplemented XO=%u (instruction 0x%08x)", xo, + instr->instructionBits()); + } + +vmx_done: + #undef LANE_U8 + #undef LANE_S8 + #undef LANE_U16 + #undef LANE_S16 + #undef LANE_U32 + #undef LANE_S32 + #undef LANE_U64 + #undef LANE_S64 + #undef SET_LANE_U8 + #undef SET_LANE_U16 + #undef SET_LANE_U32 + #undef SET_LANE_U64 + ; // empty stmt for label +} + +// ----------------------------------------------------------------------------- +// decodeVSX: Major opcode 60 (XX1-form, XX2-form) +// mfvsrd, mtvsrd, mtvsrwz, mtvsrws, xscvdpsp, xscvdpspn, xscvspdp, +// xscvspdpn, xxbrd + +void Simulator::decodeVSX(SimInstruction* instr) { + // VSX major opcode 60 covers XX1/XX2/XX3/XX4 forms. We dispatch XX4 + // (xxsel) first because its XO is only 2 bits (at ISA 26-27 = sim + // bits 5-4), and the XC register field at ISA 21-25 would otherwise + // produce 32 different 9-bit XO values to enumerate in the switch. + // Peel off any instruction with XX4 XO=3 (xxsel). No XX2/XX3 op currently + // emitted by the JIT has sim bits (5,4) == 3. + if (instr->bits(5, 4) == 3) { + // xxsel XT,XA,XB,XC (VA-like XX4-form). + // XT[i] = (XA[i] & ~XC[i]) | (XB[i] & XC[i]) + // Register fields: XA/XB/XT per-byte; XC at ISA bits 21-25 (sim + // bits 10-6) with CX extension at ISA bit 28 (sim bit 3). + int xa = int(instr->raValue() | (instr->bit(2) << 5)); + int xb = int(instr->rbValue() | (instr->bit(1) << 5)); + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + int xc = int(instr->bits(10, 6) | (instr->bit(3) << 5)); + uint8_t ab[16], bb[16], cb[16], result[16]; + getVSR128(xa, ab); + getVSR128(xb, bb); + getVSR128(xc, cb); + for (int i = 0; i < 16; i++) { + result[i] = (uint8_t)((ab[i] & ~cb[i]) | (bb[i] & cb[i])); + } + setVSR128(xt, result); + return; + } + + // The remaining forms (XX1/XX2/XX3) share a 9-bit XO at ISA bits + // 21-29 (sim bits 10-2). For XX3 this is (8-bit XO << 1) | AX; for + // XX2 the full 9 bits are the XO (no AX field). + uint32_t xo = instr->bits(10, 2); + uint32_t rt = instr->rtValue(); + uint32_t rb = instr->rbValue(); + + switch (xo) { + // xscvdpsp / xscvdpspn / xscvspdp / xscvspdpn / xxbrd are + // XX2-form: XT/XB are each 6-bit (5-bit field + TX/BX extension at + // sim bits 0/1). Post-Phase-2 the JIT emits these with Simd128 + // targets (encoding 32-63), which require the extension bit to + // select VR-space instead of FPR-space. The previous code used + // only the 5-bit field, so any VR-space target silently clobbered + // FPR 0..31 and the post-splat fbits in splatX4 never reached the + // vector lanes. + case 265: { + // xscvdpsp: double→single with sNaN quieting. The ISA says + // result lands at XT[0:31] (BE word 0 = LE bytes 12..15) and + // XT[32:127] is "undefined". Real POWER9 silicon actually + // duplicates the result into BE word 1 as well, so the bytes + // at LE 8..11 hold the same single. The JIT's + // replaceLaneFloat32x4 lowering depends on this: it follows + // xscvdpspn with `xxinsertw …, 12`, which reads XB.word[1] + // (LE bytes 8..11). Zeroing those bytes here would silently + // lose the single under sim. Mirror HW. + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16]; + getVSR128(xb, bb); + // Source double at BE DW0 = LE bytes 8..15 of xb. + uint64_t dbits = 0; + for (int i = 0; i < 8; i++) dbits |= ((uint64_t)bb[8 + i]) << (i * 8); + double frb; + memcpy(&frb, &dbits, sizeof(frb)); + float result = demoteDoublePreservingNaN(frb); + uint32_t fbits; + memcpy(&fbits, &result, sizeof(fbits)); + if ((fbits & 0x7F800000u) == 0x7F800000u && (fbits & 0x007FFFFFu) != 0) { + fbits |= 0x00400000u; + } + uint8_t out[16]; + memset(out, 0, 8); + // BE word 1 (LE 8..11) and BE word 0 (LE 12..15) both = fbits. + for (int off : {8, 12}) { + out[off] = (uint8_t)(fbits); + out[off + 1] = (uint8_t)(fbits >> 8); + out[off + 2] = (uint8_t)(fbits >> 16); + out[off + 3] = (uint8_t)(fbits >> 24); + } + setVSR128(xt, out); + break; + } + case 267: { + // xscvdpspn: same as xscvdpsp but non-signaling. Same HW-observed + // word-1 duplication (see xscvdpsp comment above). + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16]; + getVSR128(xb, bb); + uint64_t dbits = 0; + for (int i = 0; i < 8; i++) dbits |= ((uint64_t)bb[8 + i]) << (i * 8); + double frb; + memcpy(&frb, &dbits, sizeof(frb)); + float result = demoteDoublePreservingNaN(frb); + uint32_t fbits; + memcpy(&fbits, &result, sizeof(fbits)); + uint8_t out[16]; + memset(out, 0, 8); + for (int off : {8, 12}) { + out[off] = (uint8_t)(fbits); + out[off + 1] = (uint8_t)(fbits >> 8); + out[off + 2] = (uint8_t)(fbits >> 16); + out[off + 3] = (uint8_t)(fbits >> 24); + } + setVSR128(xt, out); + break; + } + case 393: { + // xvcvdpsp: convert two doubles to two singles, replicating each + // result across its dword. BE words = [s(BE_dw0), s(BE_dw0), + // s(BE_dw1), s(BE_dw1)]. SIGNALING form per ISA: sNaN inputs are + // quieted (high-order fraction bit set in result). + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16], out[16]; + getVSR128(xb, bb); + uint32_t fbits[2]; + // BE_dw0 = LE bytes 8..15, BE_dw1 = LE bytes 0..7. + for (int dw = 0; dw < 2; dw++) { + int leOff = (dw == 0) ? 8 : 0; + uint64_t dbits = 0; + for (int i = 0; i < 8; i++) { + dbits |= ((uint64_t)bb[leOff + i]) << (i * 8); + } + double frb; + memcpy(&frb, &dbits, sizeof(frb)); + float result = demoteDoublePreservingNaN(frb); + memcpy(&fbits[dw], &result, sizeof(uint32_t)); + if ((fbits[dw] & 0x7F800000u) == 0x7F800000u && + (fbits[dw] & 0x007FFFFFu) != 0) { + fbits[dw] |= 0x00400000u; // quiet sNaN result + } + } + // LE words: [s(dw1), s(dw1), s(dw0), s(dw0)] + // (LE word 0 = BE word 3 = s(dw1); LE word 3 = BE word 0 = s(dw0)). + uint32_t leWords[4] = {fbits[1], fbits[1], fbits[0], fbits[0]}; + for (int w = 0; w < 4; w++) { + out[w * 4] = (uint8_t)leWords[w]; + out[w * 4 + 1] = (uint8_t)(leWords[w] >> 8); + out[w * 4 + 2] = (uint8_t)(leWords[w] >> 16); + out[w * 4 + 3] = (uint8_t)(leWords[w] >> 24); + } + setVSR128(xt, out); + break; + } + case 216: // xvcvdpsxws: double → signed word, saturating, RTZ (vector) + case 200: { // xvcvdpuxws: double → unsigned word, saturating, RTZ (vector) + // src1 := XB.dword_BE[0]; src2 := XB.dword_BE[1] + // r1 := ConvertDPtoSat(src1); r2 := ConvertDPtoSat(src2) + // XT.word_BE[0] := r1; XT.word_BE[1] := r1 (replicated) + // XT.word_BE[2] := r2; XT.word_BE[3] := r2 (replicated) + // Saturation: signed clamps to [INT32_MIN, INT32_MAX] with NaN→INT32_MIN; + // unsigned clamps to [0, UINT32_MAX] with NaN→0 and neg→0. + // BE_dw0 = LE bytes 8..15; BE_dw1 = LE bytes 0..7. + bool isSigned = (xo == 216); + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16], out[16]; + getVSR128(xb, bb); + const int srcOffsets[2] = {8, 0}; // BE_dw0 (LE 8..15), BE_dw1 (LE 0..7) + uint32_t results[2]; + for (int lane = 0; lane < 2; lane++) { + uint64_t dbits = 0; + for (int j = 0; j < 8; j++) { + dbits |= ((uint64_t)bb[srcOffsets[lane] + j]) << (j * 8); + } + double dval; + memcpy(&dval, &dbits, sizeof(dval)); + if (std::isnan(dval)) { + results[lane] = isSigned ? 0x80000000u : 0u; + } else if (isSigned) { + if (dval >= 2147483647.0) { + results[lane] = 0x7FFFFFFFu; + } else if (dval <= -2147483648.0) { + results[lane] = 0x80000000u; + } else { + results[lane] = (uint32_t)(int32_t)dval; // RTZ + } + } else { // unsigned + if (dval <= 0.0) { + results[lane] = 0u; + } else if (dval >= 4294967295.0) { + results[lane] = 0xFFFFFFFFu; + } else { + results[lane] = (uint32_t)dval; // RTZ + } + } + } + // Replicated layout: BE words [r1, r1, r2, r2]; in LE bytes + // [r2, r2, r1, r1] (LE word 0 = BE word 3 = r2, LE word 3 = BE word 0 = r1). + uint32_t leWords[4] = {results[1], results[1], results[0], results[0]}; + for (int w = 0; w < 4; w++) { + out[w * 4] = (uint8_t)leWords[w]; + out[w * 4 + 1] = (uint8_t)(leWords[w] >> 8); + out[w * 4 + 2] = (uint8_t)(leWords[w] >> 16); + out[w * 4 + 3] = (uint8_t)(leWords[w] >> 24); + } + setVSR128(xt, out); + break; + } + case 248: // xvcvsxwdp: signed word → double (vector) + case 232: { // xvcvuxwdp: unsigned word → double (vector) + // src1 := XB.word_BE[0]; src2 := XB.word_BE[2] + // XT.dword_BE[0] := Convert(src1); XT.dword_BE[1] := Convert(src2) + // BE word 0 = LE bytes 12..15; BE word 2 = LE bytes 4..7. + // Output BE dword 0 = LE bytes 8..15; BE dword 1 = LE bytes 0..7. + // No NaN handling needed (integer source). + bool isSigned = (xo == 248); + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16], out[16]; + getVSR128(xb, bb); + const int srcOffsets[2] = {12, 4}; + const int dstOffsets[2] = {8, 0}; + for (int lane = 0; lane < 2; lane++) { + uint32_t bits = (uint32_t)bb[srcOffsets[lane]] | + ((uint32_t)bb[srcOffsets[lane] + 1] << 8) | + ((uint32_t)bb[srcOffsets[lane] + 2] << 16) | + ((uint32_t)bb[srcOffsets[lane] + 3] << 24); + double dval = isSigned ? (double)(int32_t)bits : (double)bits; + uint64_t dbits; + memcpy(&dbits, &dval, sizeof(dbits)); + for (int i = 0; i < 8; i++) { + out[dstOffsets[lane] + i] = (uint8_t)(dbits >> (i * 8)); + } + } + setVSR128(xt, out); + break; + } + case 457: { + // xvcvspdp: convert two singles to two doubles. SIGNALING form + // per ISA: sNaN inputs are quieted in the result (bit 51 set). + // src1 := XB.word_BE[0]; src2 := XB.word_BE[2] + // XT.dword_BE[0] := ConvertSPtoDP(src1) + // XT.dword_BE[1] := ConvertSPtoDP(src2) + // BE word 0 = LE bytes 12..15; BE word 2 = LE bytes 4..7. + // Output BE dword 0 = LE bytes 8..15; BE dword 1 = LE bytes 0..7. + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16], out[16]; + getVSR128(xb, bb); + // src1 from BE word 0 (LE 12..15), output dword at LE 8..15. + // src2 from BE word 2 (LE 4..7), output dword at LE 0..7. + const int srcOffsets[2] = {12, 4}; // LE byte offsets of word_BE[0], word_BE[2] + const int dstOffsets[2] = {8, 0}; // LE byte offsets of dword_BE[0], dword_BE[1] + for (int lane = 0; lane < 2; lane++) { + uint32_t fbits = (uint32_t)bb[srcOffsets[lane]] | + ((uint32_t)bb[srcOffsets[lane] + 1] << 8) | + ((uint32_t)bb[srcOffsets[lane] + 2] << 16) | + ((uint32_t)bb[srcOffsets[lane] + 3] << 24); + float fval; + memcpy(&fval, &fbits, sizeof(fval)); + double dval = promoteFloatPreservingNaN(fval); + uint64_t dbits; + memcpy(&dbits, &dval, sizeof(dbits)); + if ((dbits & 0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && + (dbits & 0x000FFFFFFFFFFFFFULL) != 0) { + dbits |= 0x0008000000000000ULL; // quiet sNaN result + } + for (int i = 0; i < 8; i++) { + out[dstOffsets[lane] + i] = (uint8_t)(dbits >> (i * 8)); + } + } + setVSR128(xt, out); + break; + } + case 329: { + // xscvspdp: single→double from BE word 0 of XB. SIGNALING form; + // an sNaN input yields a qNaN result with the high-order + // fraction bit (quiet bit) set. + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16]; + getVSR128(xb, bb); + // BE word 0 = LE bytes 12..15 of xb. + uint32_t fbits = (uint32_t)bb[12] | + ((uint32_t)bb[13] << 8) | + ((uint32_t)bb[14] << 16) | + ((uint32_t)bb[15] << 24); + float fval; + memcpy(&fval, &fbits, sizeof(fval)); + double dval = promoteFloatPreservingNaN(fval); + uint64_t dbits; + memcpy(&dbits, &dval, sizeof(dbits)); + // Quiet any NaN result (signaling form): set bit 51 of mantissa. + if ((dbits & 0x7FF0000000000000ULL) == 0x7FF0000000000000ULL && + (dbits & 0x000FFFFFFFFFFFFFULL) != 0) { + dbits |= 0x0008000000000000ULL; + } + uint8_t out[16]; + memset(out, 0, 8); + for (int i = 0; i < 8; i++) out[8 + i] = (uint8_t)(dbits >> (i * 8)); + setVSR128(xt, out); + break; + } + case 331: { + // xscvspdpn: non-signaling variant of xscvspdp. + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16]; + getVSR128(xb, bb); + uint32_t fbits = (uint32_t)bb[12] | + ((uint32_t)bb[13] << 8) | + ((uint32_t)bb[14] << 16) | + ((uint32_t)bb[15] << 24); + float fval; + memcpy(&fval, &fbits, sizeof(fval)); + double dval = promoteFloatPreservingNaN(fval); + uint64_t dbits; + memcpy(&dbits, &dval, sizeof(dbits)); + uint8_t out[16]; + memset(out, 0, 8); + for (int i = 0; i < 8; i++) out[8 + i] = (uint8_t)(dbits >> (i * 8)); + setVSR128(xt, out); + break; + } + case 347: { + // POWER9 XX2-form ops sharing XO=347; disambiguated by the 5-bit + // A immediate (sim bits 20..16): + // A=0 -> xsxexpdp (extract biased exponent into 11 LSBs of XT.dw0) + // A=16 -> xscvhpdp (FP16 -> FP64) + // A=17 -> xscvdphp (FP64 -> FP16) + // Half placement: the FP16 value lives at LE bytes 8..9 of + // the VSR (= BE bits 48..63 of + // dword[0]), with the rest of dword[0] zeroed. This matches the + // lxsihzx layout already used by the JIT. + uint32_t aImm = (instr->instructionBits() >> 16) & 0x1F; + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16], out[16]; + getVSR128(xb, bb); + memset(out, 0, 16); + if (aImm == 17) { + // xscvdphp: read FP64 from BE 0..63 of XB (LE bytes 8..15), + // convert to FP16, place at LE bytes 8..9 of XT. + double d; + memcpy(&d, bb + 8, 8); + uint16_t h = js::float16(d).toRawBits(); + out[8] = (uint8_t)(h & 0xFF); + out[9] = (uint8_t)((h >> 8) & 0xFF); + } else if (aImm == 16) { + // xscvhpdp: read FP16 from LE bytes 8..9 of XB, convert to FP64, + // place at LE bytes 8..15 of XT. + uint16_t h = (uint16_t)bb[8] | ((uint16_t)bb[9] << 8); + double d = static_cast(js::float16::fromRawBits(h)); + memcpy(out + 8, &d, 8); + } else if (aImm == 0) { + // xsxexpdp: read FP64 from LE bytes 8..15 of XB, extract biased + // exponent (bits 1..11 of the IEEE-754 double = bits 52..62 of + // the 64-bit pattern), place into XT.dw0 with rest zeroed. + uint64_t bits = 0; + for (int i = 0; i < 8; i++) bits |= uint64_t(bb[8 + i]) << (i * 8); + uint64_t exp = (bits >> 52) & 0x7FF; + for (int i = 0; i < 8; i++) out[8 + i] = (uint8_t)(exp >> (i * 8)); + } else { + MOZ_CRASH_UNSAFE_PRINTF( + "decodeVSX XO=347 with unexpected A=%u (instr 0x%08x)", + aImm, instr->instructionBits()); + } + setVSR128(xt, out); + break; + } + case 475: { + // xxbrd: byte-reverse each doubleword. + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16], out[16]; + getVSR128(xb, bb); + for (int i = 0; i < 8; i++) out[i] = bb[7 - i]; + for (int i = 0; i < 8; i++) out[8 + i] = bb[15 - i]; + setVSR128(xt, out); + break; + } + + // === XX3-form scalar: xsmaxjdp / xsminjdp (POWER9) === + // + // xs{max,min}jdp XT, XA, XB. Scalar inputs at BE bits 0..63 of + // XA / XB (= LE bytes 8..15); result lands at BE 0..63 of XT + // (upper bits "undefined" per ISA). + // + // Semantics match ECMA-262 Math.{max,min} / wasm f64.{max,min}: + // - NaN: if A is NaN return A; else if B is NaN return B. sNaN + // payload preserved bit-for-bit (NOT quieted). + // - ±0 tie: signed-zero ordering. xsmaxjdp returns +0 for any + // mix of (-0, +0); xsminjdp returns -0. + // - Otherwise: standard IEEE max / min. + case 288: case 289: // xsmaxjdp (XO8=144 → 9-bit 288/289) + case 304: case 305: { // xsminjdp (XO8=152 → 9-bit 304/305) + int xa = int(instr->raValue() | (instr->bit(2) << 5)); + int xb = int(instr->rbValue() | (instr->bit(1) << 5)); + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t ab[16], bb[16], out[16]; + getVSR128(xa, ab); + getVSR128(xb, bb); + double a, b; + memcpy(&a, ab + 8, 8); + memcpy(&b, bb + 8, 8); + bool isMax = (xo >> 1) == 144; + double r; + if (std::isnan(a)) { + r = a; + } else if (std::isnan(b)) { + r = b; + } else if (a == 0.0 && b == 0.0) { + // Signed-zero ordering: max picks +0, min picks -0. + if (isMax) { + r = std::signbit(a) ? b : a; + } else { + r = std::signbit(a) ? a : b; + } + } else { + r = isMax ? std::max(a, b) : std::min(a, b); + } + memset(out, 0, 8); + memcpy(out + 8, &r, 8); + setVSR128(xt, out); + break; + } + + // --- VSX XX3-form: xxpermdi --- + // + // xxpermdi XT, XA, XB, DM: + // XT.DW0 = XA.DW(DM[0]) + // XT.DW1 = XB.DW(DM[1]) + // In BE, DW0 is MSB-side, DW1 is LSB-side. On PPCLE register + // storage, DW0 = LE bytes 8-15 and DW1 = LE bytes 0-7. The sim's + // previous implementation used the reversed "DW0 = LE 0-7" + // convention which cancelled for self-swap round-trips but + // produced wrong halves when chained with ISA-correct ops + // (mtvsrd, xxspltw, mfvsrd). + case 20: case 21: // xxpermdi DM=0 + case 84: case 85: // xxpermdi DM=1 + case 148: case 149: // xxpermdi DM=2 (= xxswapd when XA==XB) + case 212: case 213: { // xxpermdi DM=3 + uint8_t dm_hi = (xo >> 7) & 1; // DM[0] + uint8_t dm_lo = (xo >> 6) & 1; // DM[1] + int xa = int(instr->raValue() | (instr->bit(2) << 5)); + int xb = int(instr->rbValue() | (instr->bit(1) << 5)); + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t xa_bytes[16], xb_bytes[16], result[16]; + getVSR128(xa, xa_bytes); + getVSR128(xb, xb_bytes); + // DW0 in LE storage is bytes 8-15; DW1 is bytes 0-7. + // XT.DW0 (result[8..15]) = XA.DW(dm_hi) + // XT.DW1 (result[0..7]) = XB.DW(dm_lo) + // DW(0) is at LE 8, DW(1) is at LE 0. + memcpy(result + 8, xa_bytes + (dm_hi ? 0 : 8), 8); + memcpy(result, xb_bytes + (dm_lo ? 0 : 8), 8); + setVSR128(xt, result); + break; + } + + // --- VSX logical (XX3-form, primary opcode 60) --- + // + // Each takes two 6-bit VSR sources XA/XB and writes 6-bit VSR + // destination XT. 8-bit ISA XO at bits 21-28; our + // 9-bit XO extraction (bits 10:2) includes the AX bit at position 0, + // so each op appears as two consecutive values (AX=0 and AX=1). + // + // xxland XT,XA,XB XO=130 (9-bit: 260, 261) XT = XA & XB + // xxlandc XT,XA,XB XO=138 (276, 277) XT = XA & ~XB + // xxlor XT,XA,XB XO=146 (292, 293) XT = XA | XB + // xxlxor XT,XA,XB XO=154 (308, 309) XT = XA ^ XB + // xxlnor XT,XA,XB XO=162 (324, 325) XT = ~(XA | XB) + // xxlorc XT,XA,XB XO=170 (340, 341) XT = XA | ~XB + // xxlnand XT,XA,XB XO=178 (356, 357) XT = ~(XA & XB) + // xxleqv XT,XA,XB XO=186 (372, 373) XT = ~(XA ^ XB) + // + // The encoding constants in Assembler-ppc.h match: PPC_xxlor=0xF0000490 + // has bits 4,7,10 set in its base (XO=146 in the 8-bit field), which + // under the simulator's 9-bit extraction gives 2*146=292 (AX=0 default). + case 260: case 261: // xxland + case 276: case 277: // xxlandc + case 292: case 293: // xxlor + case 308: case 309: // xxlxor + case 324: case 325: // xxlnor + case 340: case 341: // xxlorc + case 356: case 357: // xxlnand + case 372: case 373: // xxleqv + { + int xa = int(instr->raValue() | (instr->bit(2) << 5)); + int xb = int(instr->rbValue() | (instr->bit(1) << 5)); + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t a_bytes[16], b_bytes[16], result[16]; + getVSR128(xa, a_bytes); + getVSR128(xb, b_bytes); + // Dispatch on the 8-bit ISA XO (ignoring AX bit at position 0). + uint32_t xo8 = xo >> 1; + for (int i = 0; i < 16; i++) { + uint8_t a = a_bytes[i], b = b_bytes[i]; + switch (xo8) { + case 130: result[i] = a & b; break; // xxland + case 138: result[i] = a & ~b; break; // xxlandc + case 146: result[i] = a | b; break; // xxlor + case 154: result[i] = a ^ b; break; // xxlxor + case 162: result[i] = (uint8_t)~(a | b); break; // xxlnor + case 170: result[i] = a | (uint8_t)~b; break; // xxlorc + case 178: result[i] = (uint8_t)~(a & b); break; // xxlnand + case 186: result[i] = (uint8_t)~(a ^ b); break; // xxleqv + } + } + setVSR128(xt, result); + break; + } + + // === XX2-form: xxspltw (splat word from VRB[UIM] to all 4 lanes) === + // + // xxspltw: UIM selects one of four words in BE numbering. UIM=0 + // → BE word 0 (MSB side of the 128 bits). On PPCLE register + // storage that maps to LE word (3 - UIM). With the input + // {0x11111111, 0x22222222, 0x33333333, 0x44444444}: UIM=0 + // splats 0x44444444 (= LE word 3), UIM=3 splats 0x11111111 + // (= LE word 0). The JIT emits xxspltw UIM=1 after mtvsrd on the + // POWER8 splatX4 path — mtvsrd puts the GPR's low 32 bits in BE + // word 1 (= LE word 2 on HW), so xxspltw UIM=1 picks up exactly + // that word and splats it to every lane. + case 164: { // xxspltw + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint32_t uim = (instr->instructionBits() >> 16) & 0x3; + uint32_t leIdx = 3 - uim; // BE word UIM → LE word (3-UIM) + uint8_t bb[16], result[16]; + getVSR128(xb, bb); + uint32_t word = (uint32_t)bb[leIdx * 4] | + ((uint32_t)bb[leIdx * 4 + 1] << 8) | + ((uint32_t)bb[leIdx * 4 + 2] << 16) | + ((uint32_t)bb[leIdx * 4 + 3] << 24); + for (int i = 0; i < 4; i++) { + result[i * 4] = (uint8_t)(word & 0xFF); + result[i * 4 + 1] = (uint8_t)((word >> 8) & 0xFF); + result[i * 4 + 2] = (uint8_t)((word >> 16) & 0xFF); + result[i * 4 + 3] = (uint8_t)((word >> 24) & 0xFF); + } + setVSR128(xt, result); + break; + } + + // === XX2-form: xxextractuw (extract word at BE byte UIM, place at BE word 1) === + // + // xxextractuw XT, XB, UIM: + // Bytes [4:7] of XT receive bytes [UIM:UIM+3] of XB. Bytes [0:3] + // and [8:15] of XT are set to zero. + // UIM ∈ {0, 4, 8, 12} (caller responsible for alignment). + // BE byte i ↔ LE byte (15-i), so the word at XB BE bytes UIM..UIM+3 + // sits at XB LE bytes (12-UIM)..(15-UIM), and lands at XT LE bytes + // 8..11 (= XT BE word 1). + case 165: { // xxextractuw + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint32_t uim = (instr->instructionBits() >> 16) & 0xF; + uint8_t bb[16], result[16]; + getVSR128(xb, bb); + memset(result, 0, sizeof(result)); + // result.LE[8..11] = XB.LE[(12-UIM)..(15-UIM)] (preserves byte order). + memcpy(result + 8, bb + (12 - uim), 4); + setVSR128(xt, result); + break; + } + + case 180: { + // xxspltib XT, IMM8 (POWER9, ISA 3.0): splat 8-bit immediate to + // all 16 bytes of XT. The encoder writes `imm8 << 11`, so IMM8 + // occupies LE bits 11..18; TX bit at LE bit 0 selects upper VSR. + uint32_t imm8 = instr->bits(18, 11); + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + uint8_t xt_bytes[16]; + memset(xt_bytes, (uint8_t)imm8, 16); + setVSR128(xt, xt_bytes); + break; + } + case 181: { + // xxinsertw XT, XB, UIM (POWER9, ISA 3.0): copy XB[32..63] (the + // low 32 bits of XB's BE doubleword 0, which lives at LE bytes + // 8-11 of XB) into XT at BE byte position UIM. UIM ∈ {0,4,8,12}; + // dest occupies XT LE bytes (12-UIM)..(15-UIM). Other bytes of + // XT are preserved. UIM at PPC bits 11-15 = LE bits 16-20; TX/BX + // at LE bits 0/1. + uint32_t uim = instr->bits(20, 16); + int xt = int(instr->rtValue() | (instr->bit(0) << 5)); + int xb = int(instr->rbValue() | (instr->bit(1) << 5)); + uint8_t xb_bytes[16], xt_bytes[16]; + getVSR128(xb, xb_bytes); + getVSR128(xt, xt_bytes); + memcpy(xt_bytes + (12 - uim), xb_bytes + 8, 4); + setVSR128(xt, xt_bytes); + break; + } + + // === XX2-form: xvabssp / xvabsdp (vector absolute value) === + case 408: case 409: case 410: case 411: { // xvabssp + AX/BX bits + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16], result[16]; + getVSR128(xb, bb); + for (int i = 0; i < 4; i++) { + uint32_t bits = (uint32_t)bb[i * 4] | + ((uint32_t)bb[i * 4 + 1] << 8) | + ((uint32_t)bb[i * 4 + 2] << 16) | + ((uint32_t)bb[i * 4 + 3] << 24); + bits &= 0x7FFFFFFFu; // clear sign bit + result[i * 4] = (uint8_t)(bits & 0xFF); + result[i * 4 + 1] = (uint8_t)((bits >> 8) & 0xFF); + result[i * 4 + 2] = (uint8_t)((bits >> 16) & 0xFF); + result[i * 4 + 3] = (uint8_t)((bits >> 24) & 0xFF); + } + setVSR128(xt, result); + break; + } + case 472: case 473: case 474: { // xvabsdp (475 used by xxbrd) + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16], result[16]; + getVSR128(xb, bb); + for (int i = 0; i < 2; i++) { + uint64_t bits = 0; + for (int k = 0; k < 8; k++) bits |= ((uint64_t)bb[i * 8 + k]) << (k * 8); + bits &= 0x7FFFFFFFFFFFFFFFULL; + for (int k = 0; k < 8; k++) result[i * 8 + k] = (uint8_t)((bits >> (k * 8)) & 0xFF); + } + setVSR128(xt, result); + break; + } + + // === XX2-form unary vector float ops (single XB operand, no AX) === + // + // Encoding: opcode 60, bits 6-10=XT, 11-15 reserved, 16-20=XB, + // 21-29 = 9-bit XO (full field), 30=BX, 31=TX. Extraction gives us + // xo = XO9 directly (no AX bit). Every op below has a unique XO9. + // + // xvsqrtsp XO9=139 PPC_xvsqrtsp=0xF000022C + // xvsqrtdp XO9=203 PPC_xvsqrtdp=0xF000032C + // xvnegsp XO9=441 PPC_xvnegsp=0xF00006E4 + // xvnegdp XO9=505 PPC_xvnegdp=0xF00007E4 + // xvrspip XO9=169 PPC_xvrspip=0xF00002A4 (round +inf = ceil) + // xvrspiz XO9=153 PPC_xvrspiz=0xF0000264 (round toward 0 = trunc) + // xvrspim XO9=185 PPC_xvrspim=0xF00002E4 (round -inf = floor) + // xvrspic XO9=171 PPC_xvrspic=0xF00002AC (round per FPSCR) + // xvrdpip XO9=233 PPC_xvrdpip=0xF00003A4 + // xvrdpiz XO9=217 PPC_xvrdpiz=0xF0000364 + // xvrdpim XO9=249 PPC_xvrdpim=0xF00003E4 + // xvrdpic XO9=235 PPC_xvrdpic=0xF00003AC + // xvcvspsxws XO9=152 PPC_xvcvspsxws=0xF0000260 (f32 → s32, sat) + // xvcvspuxws XO9=136 PPC_xvcvspuxws=0xF0000220 (f32 → u32, sat) + // xvcvsxwsp XO9=184 PPC_xvcvsxwsp=0xF00002E0 (s32 → f32) + // xvcvuxwsp XO9=168 PPC_xvcvuxwsp=0xF00002A0 (u32 → f32) + case 139: case 203: // xvsqrtsp / xvsqrtdp + case 441: case 505: // xvnegsp / xvnegdp + case 169: case 233: // xvrspip / xvrdpip (ceil) + case 153: case 217: // xvrspiz / xvrdpiz (trunc) + case 185: case 249: // xvrspim / xvrdpim (floor) + case 171: case 235: // xvrspic / xvrdpic (round-to-nearest) + case 136: case 152: // xvcvspuxws / xvcvspsxws + case 168: case 184: { // xvcvuxwsp / xvcvsxwsp + int xt = int(rt | (instr->bit(0) << 5)); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t bb[16], result[16]; + getVSR128(xb, bb); + bool isSp = (xo == 139 || xo == 441 || xo == 169 || xo == 153 || + xo == 185 || xo == 171 || xo == 136 || xo == 152 || + xo == 168 || xo == 184); + auto getF32 = [](uint8_t* buf, int i) -> float { + uint32_t b = (uint32_t)buf[i * 4] | + ((uint32_t)buf[i * 4 + 1] << 8) | + ((uint32_t)buf[i * 4 + 2] << 16) | + ((uint32_t)buf[i * 4 + 3] << 24); + float f; memcpy(&f, &b, sizeof(f)); return f; + }; + auto setF32 = [](uint8_t* buf, int i, float f) { + uint32_t b; memcpy(&b, &f, sizeof(b)); + buf[i*4]=(uint8_t)b; buf[i*4+1]=(uint8_t)(b>>8); + buf[i*4+2]=(uint8_t)(b>>16); buf[i*4+3]=(uint8_t)(b>>24); + }; + auto getF64 = [](uint8_t* buf, int i) -> double { + uint64_t b = 0; + for (int k=0;k<8;k++) b |= ((uint64_t)buf[i*8+k])<<(k*8); + double d; memcpy(&d, &b, sizeof(d)); return d; + }; + auto setF64 = [](uint8_t* buf, int i, double d) { + uint64_t b; memcpy(&b, &d, sizeof(b)); + for (int k=0;k<8;k++) buf[i*8+k]=(uint8_t)(b>>(k*8)); + }; + // Integer lane read/write (used by conversion ops). + auto setU32 = [](uint8_t* buf, int i, uint32_t v) { + buf[i*4]=(uint8_t)v; buf[i*4+1]=(uint8_t)(v>>8); + buf[i*4+2]=(uint8_t)(v>>16); buf[i*4+3]=(uint8_t)(v>>24); + }; + // Saturated float→int conversion per Power ISA v3.0B: input NaN maps + // to 0; out-of-range saturates to the extreme of the destination type. + auto fp2sxw = [](double f) -> uint32_t { + if (std::isnan(f)) return 0; + if (f >= (double)INT32_MAX) return (uint32_t)INT32_MAX; + if (f <= (double)INT32_MIN) return (uint32_t)INT32_MIN; + return (uint32_t)(int32_t)std::trunc(f); + }; + auto fp2uxw = [](double f) -> uint32_t { + if (std::isnan(f)) return 0; + if (f >= (double)UINT32_MAX) return UINT32_MAX; + if (f <= 0.0) return 0; + return (uint32_t)std::trunc(f); + }; + + if (isSp) { + for (int i = 0; i < 4; i++) { + float v = getF32(bb, i); + float out = 0.0f; + uint32_t iout = 0; + bool isInt = false; + switch (xo) { + case 139: out = std::sqrt(v); break; // xvsqrtsp + case 441: out = -v; break; // xvnegsp + case 169: out = std::ceil(v); break; // xvrspip + case 153: out = std::trunc(v); break; // xvrspiz + case 185: out = std::floor(v); break; // xvrspim + case 171: out = std::nearbyint(v); break; // xvrspic + case 152: iout = fp2sxw(v); isInt = true; break; // xvcvspsxws + case 136: iout = fp2uxw(v); isInt = true; break; // xvcvspuxws + case 184: { // xvcvsxwsp + uint32_t bits = (uint32_t)bb[i*4] | + ((uint32_t)bb[i*4+1]<<8) | + ((uint32_t)bb[i*4+2]<<16) | + ((uint32_t)bb[i*4+3]<<24); + out = (float)(int32_t)bits; + break; + } + case 168: { // xvcvuxwsp + uint32_t bits = (uint32_t)bb[i*4] | + ((uint32_t)bb[i*4+1]<<8) | + ((uint32_t)bb[i*4+2]<<16) | + ((uint32_t)bb[i*4+3]<<24); + out = (float)(uint32_t)bits; + break; + } + } + if (isInt) setU32(result, i, iout); + else setF32(result, i, out); + } + } else { + for (int i = 0; i < 2; i++) { + double v = getF64(bb, i); + double out = 0.0; + switch (xo) { + case 203: out = std::sqrt(v); break; // xvsqrtdp + case 505: out = -v; break; // xvnegdp + case 233: out = std::ceil(v); break; // xvrdpip + case 217: out = std::trunc(v); break; // xvrdpiz + case 249: out = std::floor(v); break; // xvrdpim + case 235: out = std::nearbyint(v); break; // xvrdpic + } + setF64(result, i, out); + } + } + setVSR128(xt, result); + break; + } + + // === XX3-form vector float compare (eq, gt, ge) === + // The wasm SIMD compares emit these and use the result as a bitmask. + // Per Power ISA: result is all-1s for true lanes, all-0s for false + // (for the non-recording form; bit 0 of XO selects record form which + // we don't model — wasm doesn't read CR6 here). + // Encodings: + // 0xF0000218 xvcmpeqsp (XO8=67) → XO9 = 134/135 (+AX). + // 0xF0000258 xvcmpgtsp (XO8=75) → XO9 = 150/151. + // 0xF0000298 xvcmpgesp (XO8=83) → XO9 = 166/167. + // 0xF0000318 xvcmpeqdp (XO8=99) → XO9 = 198/199. + // 0xF0000358 xvcmpgtdp (XO8=107) → XO9 = 214/215. + // 0xF0000398 xvcmpgedp (XO8=115) → XO9 = 230/231. + // Rc=1 record form flips ISA bit 21 (sim bit 10), yielding XO9+256 + // (not adjacent to the Rc=0 slot). wasm never emits the record form. + case 134: case 135: // xvcmpeqsp (XO8=67) + case 198: case 199: // xvcmpeqdp (XO8=99) + case 150: case 151: // xvcmpgtsp (XO8=75) + case 214: case 215: // xvcmpgtdp (XO8=107) + case 166: case 167: // xvcmpgesp (XO8=83) + case 230: case 231: { // xvcmpgedp (XO8=115) + int xt = int(rt | (instr->bit(0) << 5)); + uint32_t ra = instr->raValue(); + int xa = int(ra | ((instr->instructionBits() >> 2) & 1) << 5); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t ab[16], bb[16], result[16]; + getVSR128(xa, ab); + getVSR128(xb, bb); + uint32_t op8 = xo >> 1; // canonical 8-bit XO + bool isF32 = (op8 == 67 || op8 == 75 || op8 == 83); + bool isEq = (op8 == 67 || op8 == 99); + bool isGt = (op8 == 75 || op8 == 107); + bool isGe = (op8 == 83 || op8 == 115); + (void)isGe; + auto cmpF32 = [&](int i) -> bool { + uint32_t aBits = (uint32_t)ab[i * 4] | + ((uint32_t)ab[i * 4 + 1] << 8) | + ((uint32_t)ab[i * 4 + 2] << 16) | + ((uint32_t)ab[i * 4 + 3] << 24); + uint32_t bBits = (uint32_t)bb[i * 4] | + ((uint32_t)bb[i * 4 + 1] << 8) | + ((uint32_t)bb[i * 4 + 2] << 16) | + ((uint32_t)bb[i * 4 + 3] << 24); + float fa, fb; + memcpy(&fa, &aBits, sizeof(fa)); + memcpy(&fb, &bBits, sizeof(fb)); + if (isEq) return fa == fb; + if (isGt) return fa > fb; + return fa >= fb; + }; + auto cmpF64 = [&](int i) -> bool { + uint64_t aBits = 0, bBits = 0; + for (int k = 0; k < 8; k++) aBits |= ((uint64_t)ab[i * 8 + k]) << (k * 8); + for (int k = 0; k < 8; k++) bBits |= ((uint64_t)bb[i * 8 + k]) << (k * 8); + double fa, fb; + memcpy(&fa, &aBits, sizeof(fa)); + memcpy(&fb, &bBits, sizeof(fb)); + if (isEq) return fa == fb; + if (isGt) return fa > fb; + return fa >= fb; + }; + if (isF32) { + for (int i = 0; i < 4; i++) { + uint32_t mask = cmpF32(i) ? 0xFFFFFFFFu : 0; + for (int k = 0; k < 4; k++) { + result[i * 4 + k] = (uint8_t)((mask >> (k * 8)) & 0xFF); + } + } + } else { + for (int i = 0; i < 2; i++) { + uint64_t mask = cmpF64(i) ? UINT64_MAX : 0; + for (int k = 0; k < 8; k++) { + result[i * 8 + k] = (uint8_t)((mask >> (k * 8)) & 0xFF); + } + } + } + setVSR128(xt, result); + break; + } + + // === XX3-form vector float arithmetic === + // Encoding: bits 6-10=XT, 11-15=XA, 16-20=XB, 21-28=XO (8 bits), 29=AX, + // 30=BX, 31=TX. We dispatched above using `bits(10, 2)` which is bits + // 21-29 (9 bits) — that includes the AX register-extension bit, which + // changes for every XA in {0..31} vs {32..63}. To match all 4 + // (AX,BX) combinations of an XX3 op we use `case xo3 | 0|1|2|3` where + // xo3 = (8-bit XO) << 1 (because XO occupies bits 1..8 of our 9-bit + // extraction). Helper macro: each case covers four labels. + #define XX3_CASE_BASE(name) \ + case ((name) | 0): case ((name) | 1): + case 128: case 129: // xvaddsp: 4 × f32 add (XO=64 → bits 1..8 = 128) + case 192: case 193: // xvadddp + case 144: case 145: // xvsubsp + case 208: case 209: // xvsubdp + case 160: case 161: // xvmulsp + case 224: case 225: // xvmuldp + case 176: case 177: // xvdivsp + case 240: case 241: // xvdivdp + case 384: case 385: // xvmaxsp + case 448: case 449: // xvmaxdp + case 400: case 401: // xvminsp + case 464: case 465: // xvmindp + { + // Re-extract the canonical 8-bit XX3 XO. + uint32_t xo3 = (xo >> 1); + (void)xo3; + int xt = int(rt | (instr->bit(0) << 5)); + uint32_t ra = instr->raValue(); + int xa = int(ra | ((instr->instructionBits() >> 2) & 1) << 5); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t ab[16], bb[16], rb_bytes[16]; + getVSR128(xa, ab); + getVSR128(xb, bb); + + auto getF32 = [](uint8_t* buf, int i) -> float { + uint32_t bits = (uint32_t)buf[i * 4] | + ((uint32_t)buf[i * 4 + 1] << 8) | + ((uint32_t)buf[i * 4 + 2] << 16) | + ((uint32_t)buf[i * 4 + 3] << 24); + float f; + memcpy(&f, &bits, sizeof(f)); + return f; + }; + auto setF32 = [](uint8_t* buf, int i, float f) { + uint32_t bits; + memcpy(&bits, &f, sizeof(bits)); + buf[i * 4] = (uint8_t)(bits & 0xFF); + buf[i * 4 + 1] = (uint8_t)((bits >> 8) & 0xFF); + buf[i * 4 + 2] = (uint8_t)((bits >> 16) & 0xFF); + buf[i * 4 + 3] = (uint8_t)((bits >> 24) & 0xFF); + }; + auto getF64 = [](uint8_t* buf, int i) -> double { + uint64_t bits = 0; + for (int k = 0; k < 8; k++) bits |= ((uint64_t)buf[i * 8 + k]) << (k * 8); + double d; + memcpy(&d, &bits, sizeof(d)); + return d; + }; + auto setF64 = [](uint8_t* buf, int i, double d) { + uint64_t bits; + memcpy(&bits, &d, sizeof(bits)); + for (int k = 0; k < 8; k++) buf[i * 8 + k] = (uint8_t)((bits >> (k * 8)) & 0xFF); + }; + + // Dispatch on the canonical 8-bit XX3 XO (bits 21..28 PPC = xo>>1). + switch (xo3) { + case 64: for (int i = 0; i < 4; i++) setF32(rb_bytes, i, getF32(ab, i) + getF32(bb, i)); break; // xvaddsp + case 96: for (int i = 0; i < 2; i++) setF64(rb_bytes, i, getF64(ab, i) + getF64(bb, i)); break; // xvadddp + case 72: for (int i = 0; i < 4; i++) setF32(rb_bytes, i, getF32(ab, i) - getF32(bb, i)); break; // xvsubsp + case 104: for (int i = 0; i < 2; i++) setF64(rb_bytes, i, getF64(ab, i) - getF64(bb, i)); break; // xvsubdp + case 80: for (int i = 0; i < 4; i++) setF32(rb_bytes, i, getF32(ab, i) * getF32(bb, i)); break; // xvmulsp + case 112: for (int i = 0; i < 2; i++) setF64(rb_bytes, i, getF64(ab, i) * getF64(bb, i)); break; // xvmuldp + case 88: for (int i = 0; i < 4; i++) setF32(rb_bytes, i, getF32(ab, i) / getF32(bb, i)); break; // xvdivsp + case 120: for (int i = 0; i < 2; i++) setF64(rb_bytes, i, getF64(ab, i) / getF64(bb, i)); break; // xvdivdp + // xvmin{sp,dp} / xvmax{sp,dp}: + // If both operands are NaN, result is the NaN from XA. + // If exactly one operand is NaN, result is the NON-NaN operand. + // For 0 / -0, treat -0 < +0 (signed-zero ordering): xvminsp(+0,-0) + // = -0, xvmaxsp(+0,-0) = +0, in either operand order. + // Otherwise, result is IEEE min/max(a, b). + // This differs from IEEE 754 (which propagates NaN) and is + // relied upon by wasm relaxed_min/max (bug1946618.js) and by + // wasm f32x4.min(0,-0) returning -0 (simd_f32x4.wast.js). + #define XV_MAX(T, a, b) [](T a_, T b_) -> T { \ + bool an = std::isnan(a_), bn = std::isnan(b_); \ + if (an && bn) return a_; \ + if (an) return b_; \ + if (bn) return a_; \ + if (a_ == 0.0 && b_ == 0.0) { \ + /* -0 is smaller than +0; max picks +0. */ \ + return std::signbit(a_) ? b_ : a_; \ + } \ + return std::max(a_, b_); \ + }(a, b) + #define XV_MIN(T, a, b) [](T a_, T b_) -> T { \ + bool an = std::isnan(a_), bn = std::isnan(b_); \ + if (an && bn) return a_; \ + if (an) return b_; \ + if (bn) return a_; \ + if (a_ == 0.0 && b_ == 0.0) { \ + /* -0 is smaller than +0; min picks -0. */ \ + return std::signbit(a_) ? a_ : b_; \ + } \ + return std::min(a_, b_); \ + }(a, b) + case 192: for (int i = 0; i < 4; i++) { // xvmaxsp + float a = getF32(ab, i), b = getF32(bb, i); + setF32(rb_bytes, i, XV_MAX(float, a, b)); + } break; + case 224: for (int i = 0; i < 2; i++) { // xvmaxdp + double a = getF64(ab, i), b = getF64(bb, i); + setF64(rb_bytes, i, XV_MAX(double, a, b)); + } break; + case 200: for (int i = 0; i < 4; i++) { // xvminsp + float a = getF32(ab, i), b = getF32(bb, i); + setF32(rb_bytes, i, XV_MIN(float, a, b)); + } break; + case 232: for (int i = 0; i < 2; i++) { // xvmindp + double a = getF64(ab, i), b = getF64(bb, i); + setF64(rb_bytes, i, XV_MIN(double, a, b)); + } break; + #undef XV_MAX + #undef XV_MIN + default: + MOZ_CRASH_UNSAFE_PRINTF( + "xv float dispatch missing 8-bit XO=%u (instr 0x%08x)", + xo3, instr->instructionBits()); + } + setVSR128(xt, rb_bytes); + break; + } + + // === XX3-form fused multiply-add (3-source: XT is also input) === + // + // xvmaddasp XT,XA,XB: XT = (XA * XB) + XT (fused madd) + // xvmaddadp XT,XA,XB: same for f64 + // xvnmsubasp XT,XA,XB: XT = -((XA * XB) - XT) = XT - (XA * XB) + // xvnmsubadp XT,XA,XB: same for f64 + // + // Encodings (each +AX): XO8 → XO9 pairs + // xvmaddasp PPC_xvmaddasp=0xF0000208 XO8=65 → XO9 130/131 + // xvmaddadp PPC_xvmaddadp=0xF0000308 XO8=97 → XO9 194/195 + // xvnmsubasp PPC_xvnmsubasp=0xF0000688 XO8=209 → XO9 418/419 + // xvnmsubadp PPC_xvnmsubadp=0xF0000788 XO8=241 → XO9 482/483 + // std::fma gives IEEE-correct single-rounding behaviour matching the + // Power ISA definition of these fused forms. + case 130: case 131: // xvmaddasp + case 194: case 195: // xvmaddadp + case 418: case 419: // xvnmsubasp + case 482: case 483: { // xvnmsubadp + int xt = int(rt | (instr->bit(0) << 5)); + uint32_t ra = instr->raValue(); + int xa = int(ra | ((instr->instructionBits() >> 2) & 1) << 5); + int xb = int(rb | ((instr->instructionBits() >> 1) & 1) << 5); + uint8_t ab[16], bb[16], tb[16]; + getVSR128(xa, ab); + getVSR128(xb, bb); + getVSR128(xt, tb); // XT is also an input (accumulator). + bool isSp = (xo == 130 || xo == 131 || xo == 418 || xo == 419); + bool isNmsub = (xo == 418 || xo == 419 || xo == 482 || xo == 483); + auto rdF32 = [](uint8_t* buf, int i) -> float { + uint32_t b = (uint32_t)buf[i * 4] | + ((uint32_t)buf[i * 4 + 1] << 8) | + ((uint32_t)buf[i * 4 + 2] << 16) | + ((uint32_t)buf[i * 4 + 3] << 24); + float f; memcpy(&f, &b, sizeof(f)); return f; + }; + auto wrF32 = [](uint8_t* buf, int i, float f) { + uint32_t b; memcpy(&b, &f, sizeof(b)); + buf[i*4]=(uint8_t)b; buf[i*4+1]=(uint8_t)(b>>8); + buf[i*4+2]=(uint8_t)(b>>16); buf[i*4+3]=(uint8_t)(b>>24); + }; + auto rdF64 = [](uint8_t* buf, int i) -> double { + uint64_t b = 0; + for (int k=0;k<8;k++) b |= ((uint64_t)buf[i*8+k])<<(k*8); + double d; memcpy(&d, &b, sizeof(d)); return d; + }; + auto wrF64 = [](uint8_t* buf, int i, double d) { + uint64_t b; memcpy(&b, &d, sizeof(b)); + for (int k=0;k<8;k++) buf[i*8+k]=(uint8_t)(b>>(k*8)); + }; + uint8_t result[16]; + if (isSp) { + for (int i = 0; i < 4; i++) { + float a = rdF32(ab, i), b = rdF32(bb, i), t = rdF32(tb, i); + // madd: t + a*b ; nmsub: -(a*b - t) = t - a*b = std::fma(a,b,-t) negated. + float out = isNmsub ? -std::fma(a, b, -t) + : std::fma(a, b, t); + wrF32(result, i, out); + } + } else { + for (int i = 0; i < 2; i++) { + double a = rdF64(ab, i), b = rdF64(bb, i), t = rdF64(tb, i); + double out = isNmsub ? -std::fma(a, b, -t) + : std::fma(a, b, t); + wrF64(result, i, out); + } + } + setVSR128(xt, result); + break; + } + + default: + MOZ_CRASH_UNSAFE_PRINTF( + "decodeVSX: unimplemented XO=%u (instruction 0x%08x)", xo, + instr->instructionBits()); + } +} + +// ============================================================================= +// Power ISA v3.1 prefixed instructions (POWER10). +// ============================================================================= +// +// A prefixed instruction is 8 bytes: a 4-byte prefix word (primary opcode 1) +// followed by a 4-byte suffix word. Prefix and suffix must lie in the same +// 64-byte aligned block — the JIT must guarantee this when emitting; the sim +// asserts. +// +// Prefix word layout (BE bit numbering): +// [0..5] primary opcode = 1 +// [6..7] Type (00 = 8LS, 10 = MLS — only forms we implement) +// [8..10] reserved (must be 0) +// [11] R (1 = PC-relative; RA must be 0) +// [12..13] reserved (must be 0) +// [14..31] d0 (high 18 bits of the 34-bit signed immediate) +// +// Suffix word (MLS/8LS form, GPR-target instructions like paddi/pld): +// [0..5] suffix primary opcode (selects the actual instruction) +// [6..10] RT (or RS for stores) +// [11..15] RA +// [16..31] d1 (low 16 bits of immediate) +// +// Suffix word (8LS plxv quirk): the suffix opcode field is only 5 bits +// wide and bit [5] holds TX, the high bit of the 6-bit XT VSR number: +// [0..4] plxv suffix opcode = 11001 (= 25) +// [5] TX +// [6..10] T +// [11..15] RA +// [16..31] d1 +// Combined: XT = (TX << 5) | T. (Equivalent: full 6-bit field at [0..5] +// is 0b11001(TX) — values 50 or 51 in our LE bits 31..26.) +// +// Combined immediate: SI = sign_extend((d0 << 16) | d1, 34). +// EA when R=1: address-of-prefix + SI. (RA must be 0.) +// EA when R=0: (RA == 0 ? 0 : GPR[RA]) + SI. +// +// Suffix opcodes implemented here: +// MLS (Type 2) / suffix=14 paddi +// MLS (Type 2) / suffix=48 plfs (load FP single, widens to double) +// MLS (Type 2) / suffix=50 plfd (load FP double) +// 8LS (Type 0) / suffix=57 pld +// 8LS (Type 0) / 5-bit suffix=25, bit 26 = TX plxv +// +// Verification recipe when adding more: assemble with `gcc -mcpu=power10 +// -c` (or clang) and compare the emitted bytes against the encoder; encode +// in a small inline-asm program and step through under this sim. + +void Simulator::decodePrefixed(SimInstruction* prefix) { + // Prefix and suffix must reside in the same 64-byte block. + uint64_t prefixAddr = reinterpret_cast(prefix); + MOZ_ASSERT((prefixAddr & 63) <= 56, + "POWER10 prefixed instruction crosses 64-byte boundary"); + + SimInstruction* suffix = reinterpret_cast( + reinterpret_cast(prefix) + SimInstruction::kInstrSize); + + uint32_t type = prefix->bits(25, 24); + uint32_t R = prefix->bit(20); + uint32_t d0 = prefix->bits(17, 0); // 18 bits + uint32_t suffixOp6 = suffix->bits(31, 26); // 6-bit form (paddi, pld) + uint32_t suffixOp5 = suffix->bits(31, 27); // 5-bit form (plxv) + uint32_t plxvTX = suffix->bit(26); + uint32_t rt = suffix->rtValue(); + uint32_t ra = suffix->raValue(); + uint32_t d1 = suffix->uimm16Value(); + + // Reassemble 34-bit signed displacement. + int64_t imm34 = (static_cast(d0) << 16) | d1; + imm34 = (imm34 << 30) >> 30; // sign-extend from bit 33 + + // R=1 forms require RA=0 per the ISA. + MOZ_ASSERT(!R || ra == 0, + "POWER10 prefixed R=1 form requires RA=0"); + + // Type 2 = MLS, Type 0 = 8LS. Other types are reserved here. + if (type == 2 && suffixOp6 == 14) { + // paddi RT, RA, SI, R (MLS) + int64_t base = R ? static_cast(prefixAddr) + : (ra == 0 ? 0 : getRegister(ra)); + setRegister(rt, base + imm34); + } else if (type == 0 && suffixOp6 == 57) { + // pld RT, D(RA), R (8LS) + uint64_t ea = R ? prefixAddr + static_cast(imm34) + : (ra == 0 ? 0 : getRegister(ra)) + + static_cast(imm34); + if (!handleWasmSegFault(ea, 8)) { + setRegister(rt, readDW(ea, prefix)); + } + } else if (type == 2 && suffixOp6 == 50) { + // plfd FRT, D(RA), R (MLS) — load 8-byte double into FPR. + uint64_t ea = R ? prefixAddr + static_cast(imm34) + : (ra == 0 ? 0 : getRegister(ra)) + + static_cast(imm34); + if (!handleWasmSegFault(ea, 8)) { + setFpuRegisterDouble(rt, readD(ea, prefix)); + } + } else if (type == 2 && suffixOp6 == 48) { + // plfs FRT, D(RA), R (MLS) — load 4-byte single, widen NaN-preserving. + uint64_t ea = R ? prefixAddr + static_cast(imm34) + : (ra == 0 ? 0 : getRegister(ra)) + + static_cast(imm34); + if (!handleWasmSegFault(ea, 4)) { + float val = *reinterpret_cast(ea); + setFpuRegisterDouble(rt, promoteFloatPreservingNaN(val)); + } + } else if (type == 0 && suffixOp5 == 25) { + // plxv XT, D(RA), R (8LS) — XT = (TX << 5) | T, TX at suffix bit 26. + int xt = static_cast(rt | (plxvTX << 5)); + uint64_t ea = R ? prefixAddr + static_cast(imm34) + : (ra == 0 ? 0 : getRegister(ra)) + + static_cast(imm34); + if (!handleWasmSegFault(ea, 16)) { + uint8_t buf[16]; + memcpy(buf, reinterpret_cast(ea), 16); + setVSR128(xt, buf); + } + } else if (type == 0 && suffixOp6 == 61) { + // pstd RS, D(RA), R (8LS) — store doubleword. + uint64_t ea = R ? prefixAddr + static_cast(imm34) + : (ra == 0 ? 0 : getRegister(ra)) + + static_cast(imm34); + if (!handleWasmSegFault(ea, 8)) { + writeDW(ea, getRegister(rt), prefix); + } + } else if (type == 2 && suffixOp6 == 54) { + // pstfd FRS, D(RA), R (MLS) — store double. + uint64_t ea = R ? prefixAddr + static_cast(imm34) + : (ra == 0 ? 0 : getRegister(ra)) + + static_cast(imm34); + if (!handleWasmSegFault(ea, 8)) { + writeD(ea, getFpuRegisterDouble(rt), prefix); + } + } else if (type == 2 && suffixOp6 == 52) { + // pstfs FRS, D(RA), R (MLS) — store single (narrow from double in FPR). + uint64_t ea = R ? prefixAddr + static_cast(imm34) + : (ra == 0 ? 0 : getRegister(ra)) + + static_cast(imm34); + if (!handleWasmSegFault(ea, 4)) { + double dval = getFpuRegisterDouble(rt); + *reinterpret_cast(ea) = demoteDoublePreservingNaN(dval); + } + } else if (type == 0 && suffixOp5 == 27) { + // pstxv XS, D(RA), R (8LS) — XS = (SX << 5) | S, SX at suffix bit 26. + int xs = static_cast(rt | (plxvTX << 5)); + uint64_t ea = R ? prefixAddr + static_cast(imm34) + : (ra == 0 ? 0 : getRegister(ra)) + + static_cast(imm34); + if (!handleWasmSegFault(ea, 16)) { + uint8_t buf[16]; + getVSR128(xs, buf); + memcpy(reinterpret_cast(ea), buf, 16); + } + } else { + MOZ_CRASH_UNSAFE_PRINTF( + "decodePrefixed: unimplemented type=%u " + "(prefix 0x%08x, suffix 0x%08x)", + type, prefix->instructionBits(), suffix->instructionBits()); + } + + // Advance past the full 8-byte prefixed instruction unless a handler + // already redirected the PC. The caller (instructionDecode) returns + // immediately after us, so its 4-byte trailing advance is skipped. + if (!pc_modified_) { + set_pc(static_cast(prefixAddr) + 2 * SimInstruction::kInstrSize); + } +} + +// ============================================================================= +// Top-level instruction decoder. +// ============================================================================= + +void Simulator::instructionDecode(SimInstruction* instr) { + if (!SimulatorProcess::ICacheCheckingDisableCount) { + AutoLockSimulatorCache als; + SimulatorProcess::checkICacheLocked(instr); + } + pc_modified_ = false; + + uint32_t instrBits = instr->instructionBits(); + + // Check for kCallRedirInstr first (PPC_stop = 0x4C0002E4). + if (instrBits == kCallRedirInstr) { + softwareInterrupt(instr); + if (!pc_modified_) { + set_pc(reinterpret_cast(instr) + SimInstruction::kInstrSize); + } + return; + } + + // Check for PPC_trap (0x7FE00008). + if (instrBits == 0x7FE00008) { + softwareInterrupt(instr); + if (!pc_modified_) { + set_pc(reinterpret_cast(instr) + SimInstruction::kInstrSize); + } + return; + } + + uint32_t opcode = instr->opcode(); + + // Power ISA v3.1 prefixed instructions: primary opcode 1 marks a + // 4-byte prefix word followed by a 4-byte suffix word. decodePrefixed + // advances the PC by the full 8 bytes (or leaves it modified for + // PC-relative side-effects). + if (opcode == 1) { + decodePrefixed(instr); + return; + } + + switch (opcode) { + // D-form ALU + case 3: // twi + case 7: // mulli + case 8: // subfic + case 10: // cmpli + case 11: // cmpi + case 12: // addic + case 13: // addic. + case 14: // addi + case 15: // addis + case 24: // ori + case 25: // oris + case 26: // xori + case 27: // xoris + case 28: // andi. + case 29: // andis. + decodeDFormALU(instr); + break; + + // D-form loads + case 32: // lwz + case 33: // lwzu + case 34: // lbz + case 35: // lbzu + case 40: // lhz + case 41: // lhzu + case 42: // lha + case 43: // lhau + case 48: // lfs + case 49: // lfsu + case 50: // lfd + case 51: // lfdu + decodeDFormLoad(instr); + break; + + // D-form stores + case 36: // stw + case 38: // stb + case 39: // stbu + case 44: // sth + case 45: // sthu + case 52: // stfs + case 53: // stfsu + case 54: // stfd + case 55: // stfdu + decodeDFormStore(instr); + break; + + // DS-form + case 58: // ld, ldu, lwa + case 62: // std, stdu + decodeDSForm(instr); + break; + + // B-form conditional branch + case 16: + decodeBranch(instr); + break; + + // SC (system call) - unused in JIT + case 17: + MOZ_CRASH("Simulator: sc instruction not supported"); + break; + + // I-form unconditional branch + case 18: + decodeBranch(instr); + break; + + // XL-form (branch to LR/CTR, CR operations) + case 19: + decodeBranch(instr); + break; + + // M-form / MD-form rotate/mask + case 20: // rlwimi + case 21: // rlwinm + case 23: // rlwnm + case 30: // rldicl, rldicr, rldic, rldimi, rldcl, rldcr + decodeRotateMask(instr); + break; + + // VMX (AltiVec) — primary opcode 4. Vector arithmetic / compare / shift / + // splat / merge / pack / unpack on VR0-VR31. The wasm SIMD lowering + // emits these directly (Simd128 lives in the VR namespace). + case 4: + decodeVMX(instr); + break; + + // X-form / XO-form + case 31: + decodeXForm(instr); + break; + + // FP single (A-form) + case 59: + decodeFP(instr); + break; + + // VSX (XX1-form) + case 60: + decodeVSX(instr); + break; + + // FP double (X-form / A-form) + case 63: + decodeFP(instr); + break; + + default: + MOZ_CRASH_UNSAFE_PRINTF( + "instructionDecode: unsupported opcode %u (instruction 0x%08x)", + opcode, instrBits); + } + + if (!pc_modified_) { + set_pc(reinterpret_cast(instr) + SimInstruction::kInstrSize); + } +} + +// ============================================================================= +// Single-stepping / execute loop. +// ============================================================================= + +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); + } + + int64_t program_counter = get_pc(); + + while (program_counter != end_sim_pc) { + if (enableStopSimAt && (icount_ == Simulator::StopSimAt)) { + ppcDebugger 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_++; + } + program_counter = get_pc(); + } + + if (single_stepping_) { + single_step_callback_(single_step_callback_arg_, this, nullptr); + } +} + +// ============================================================================= +// callInternal / call. +// ============================================================================= + +void Simulator::callInternal(uint8_t* entry) { + // Prepare to execute the code at entry. + setRegister(pc, reinterpret_cast(entry)); + // The simulation stops when returning to this call point (LR == end_sim_pc). + setLR(end_sim_pc); + + // Remember the values of callee-saved registers (r14-r31 in ELFv2). + int64_t r14_val = getRegister(r14); + int64_t r15_val = getRegister(r15); + int64_t r16_val = getRegister(r16); + int64_t r17_val = getRegister(r17); + int64_t r18_val = getRegister(r18); + int64_t r19_val = getRegister(r19); + int64_t r20_val = getRegister(r20); + int64_t r21_val = getRegister(r21); + int64_t r22_val = getRegister(r22); + int64_t r23_val = getRegister(r23); + int64_t r24_val = getRegister(r24); + int64_t r25_val = getRegister(r25); + int64_t r26_val = getRegister(r26); + int64_t r27_val = getRegister(r27); + int64_t r28_val = getRegister(r28); + int64_t r29_val = getRegister(r29); + int64_t r30_val = getRegister(r30); + int64_t r31_val = getRegister(r31); + int64_t sp_val = getRegister(sp); + +#ifdef DEBUG + // Set up callee-saved registers with a known value to detect clobbers. + // DEBUG-only: in release this would silently corrupt every JS-jit-entry + // stub frame, since the stub saves r14-r31 to its stack early on. Any + // single-step-profiling sample taken later (or any unwind through the + // stub's saved CSR area) then dereferences `icount_` as a frame + // pointer and crashes — see e.g. wasm/profiling.js, ion-error-*.js, + // ion-lazy-tables.js, ion-callerfp-tag.js, return-call-profiling.js, + // externref-global-postbarrier.js, builtin-modules/i8vecmul.js, + // asm.js/testBug1357053.js (all single-step-profiling tests). In + // debug builds the value collides with the same callsites but the + // MOZ_ASSERTs below catch any actual ABI violation, which is the + // entire point. + int64_t callee_saved_value = icount_; + setRegister(r14, callee_saved_value); + setRegister(r15, callee_saved_value); + setRegister(r16, callee_saved_value); + setRegister(r17, callee_saved_value); + setRegister(r18, callee_saved_value); + setRegister(r19, callee_saved_value); + setRegister(r20, callee_saved_value); + setRegister(r21, callee_saved_value); + setRegister(r22, callee_saved_value); + setRegister(r23, callee_saved_value); + setRegister(r24, callee_saved_value); + setRegister(r25, callee_saved_value); + setRegister(r26, callee_saved_value); + setRegister(r27, callee_saved_value); + setRegister(r28, callee_saved_value); + setRegister(r29, callee_saved_value); + setRegister(r30, callee_saved_value); + setRegister(r31, callee_saved_value); +#endif + + // Start the simulation. + if (Simulator::StopSimAt != -1) { + execute(); + } else { + execute(); + } + +#ifdef DEBUG + // Check that the callee-saved registers have been preserved. + MOZ_ASSERT(callee_saved_value == getRegister(r14)); + MOZ_ASSERT(callee_saved_value == getRegister(r15)); + MOZ_ASSERT(callee_saved_value == getRegister(r16)); + MOZ_ASSERT(callee_saved_value == getRegister(r17)); + MOZ_ASSERT(callee_saved_value == getRegister(r18)); + MOZ_ASSERT(callee_saved_value == getRegister(r19)); + MOZ_ASSERT(callee_saved_value == getRegister(r20)); + MOZ_ASSERT(callee_saved_value == getRegister(r21)); + MOZ_ASSERT(callee_saved_value == getRegister(r22)); + MOZ_ASSERT(callee_saved_value == getRegister(r23)); + MOZ_ASSERT(callee_saved_value == getRegister(r24)); + MOZ_ASSERT(callee_saved_value == getRegister(r25)); + MOZ_ASSERT(callee_saved_value == getRegister(r26)); + MOZ_ASSERT(callee_saved_value == getRegister(r27)); + MOZ_ASSERT(callee_saved_value == getRegister(r28)); + MOZ_ASSERT(callee_saved_value == getRegister(r29)); + MOZ_ASSERT(callee_saved_value == getRegister(r30)); + MOZ_ASSERT(callee_saved_value == getRegister(r31)); +#endif + + // Restore callee-saved registers. + setRegister(r14, r14_val); + setRegister(r15, r15_val); + setRegister(r16, r16_val); + setRegister(r17, r17_val); + setRegister(r18, r18_val); + setRegister(r19, r19_val); + setRegister(r20, r20_val); + setRegister(r21, r21_val); + setRegister(r22, r22_val); + setRegister(r23, r23_val); + setRegister(r24, r24_val); + setRegister(r25, r25_val); + setRegister(r26, r26_val); + setRegister(r27, r27_val); + setRegister(r28, r28_val); + setRegister(r29, r29_val); + setRegister(r30, r30_val); + setRegister(r31, r31_val); + setRegister(sp, sp_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); + + // PPC ELFv2: first 8 integer args go in r3-r10. + 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); + + MOZ_ASSERT(entry_stack == getRegister(sp)); + setRegister(sp, original_stack); + + int64_t result = getRegister(r3); + return result; +} + +uintptr_t Simulator::pushAddress(uintptr_t address) { + int64_t 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() { + int64_t 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* JSContext::simulator() const { return simulator_; } diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/Simulator-ppc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/Simulator-ppc.h --- a/js/src/jit/ppc/Simulator-ppc.h 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/Simulator-ppc.h 2026-07-28 16:47:36.792223563 +0200 @@ -0,0 +1,556 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef jit_ppc_Simulator_ppc_h +#define jit_ppc_Simulator_ppc_h + +#ifdef JS_SIMULATOR_PPC + +# include "mozilla/Atomics.h" + +# include "jit/IonTypes.h" +# include "js/ProfilingFrameIterator.h" +# include "threading/Thread.h" +# include "vm/MutexIDs.h" +# include "wasm/WasmSignalHandlers.h" + +namespace js { +namespace jit { + +class JitActivation; +class Simulator; +class Redirection; +class CachePage; +class AutoLockSimulator; + +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; + +const int kNumGPRegisters = 32; +const int kPCRegister = 32; +const int kNumFPURegisters = 32; +const int kNumVRRegisters = 32; // VR0-VR31 (Altivec/VMX; = VSR32-63 in VSX) + +// PPC Condition Register: 8 fields of 4 bits each. +// Each field: bit3=LT, bit2=GT, bit1=EQ, bit0=SO (in PPC big-endian numbering +// within a field, but stored in little-endian nibble order in our uint32_t). +const int kNumCRFields = 8; + +// CR field bit positions (within a 4-bit field). +const uint8_t kCRFieldLT = 0x8; +const uint8_t kCRFieldGT = 0x4; +const uint8_t kCRFieldEQ = 0x2; +const uint8_t kCRFieldSO = 0x1; + +// XER register bit positions. +const int kXERSOBit = 31; +const int kXEROVBit = 30; +const int kXERCABit = 29; +const int kXEROV32Bit = 19; +const int kXERCA32Bit = 18; + +// FPSCR rounding mode bits (bits 62:63, stored in low bits of our uint64_t). +const uint64_t kFPSCRRNMask = 0x3; + +// FPU rounding modes matching PPC FPSCR RN field. +enum FPURoundingMode { + RN = 0, // Round to Nearest (ties to even) + RZ = 1, // Round toward Zero + RP = 2, // Round toward +Infinity + RM = 3, // Round toward -Infinity +}; + +// FPU invalid result constants. +const uint32_t kFPUInvalidResult = static_cast(1 << 31) - 1; +const int32_t kFPUInvalidResultNegative = static_cast(1u << 31); +const uint64_t kFPU64InvalidResult = + static_cast(static_cast(1) << 63) - 1; +const int64_t kFPU64InvalidResultNegative = + static_cast(static_cast(1) << 63); + +// Breakpoint/stop code ranges. +const uint32_t kMaxWatchpointCode = 31; +const uint32_t kMaxStopCode = 127; +const uint32_t kWasmTrapCode = 6; + +// Redirection instruction: PPC_stop (0x4C0002E4). +// Distinct from PPC_trap (0x7FE00008) used for wasm traps. +const uint32_t kCallRedirInstr = 0x4C0002E4; + +typedef uint32_t Instr; +class SimInstruction; + +class Simulator { + friend class ppcDebugger; + + public: + enum Register { + no_reg = -1, + r0 = 0, + r1, + r2, + r3, + r4, + r5, + r6, + r7, + r8, + r9, + r10, + r11, + r12, + r13, + r14, + r15, + r16, + r17, + r18, + r19, + r20, + r21, + r22, + r23, + r24, + r25, + r26, + r27, + r28, + r29, + r30, + r31, + pc, + kNumSimuRegisters, + // Aliases + sp = r1, + fp = r31, + }; + + enum FPURegister { + f0 = 0, + 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 + }; + + static Simulator* Create(); + static void Destroy(Simulator* simulator); + + Simulator(); + ~Simulator(); + + static Simulator* Current(); + + static inline uintptr_t StackLimit() { + return Simulator::Current()->stackLimit(); + } + + uintptr_t* addressOfStackLimit(); + + // GPR accessors. + void setRegister(int reg, int64_t value); + int64_t getRegister(int reg) const; + + // FPR accessors. + void setFpuRegister(int fpureg, int64_t value); + void setFpuRegisterWord(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 getFpuRegisterWord(int fpureg) const; + int32_t getFpuRegisterSignedWord(int fpureg) const; + float getFpuRegisterFloat(int fpureg) const; + double getFpuRegisterDouble(int fpureg) const; + + // VR accessors (Altivec/VMX registers VR0-VR31). The bytes array is the + // ground truth: bytes[0] is the most-significant-byte on PPC big-endian + // numbering, i.e., VSR[MSB..LSB] mapped as bytes[0..15]. Callers that want + // typed views (lane 0 etc.) should extract from the bytes array according + // to the ISA's lane numbering for that instruction. + void setVRBytes(int vreg, const uint8_t bytes[16]); + void getVRBytes(int vreg, uint8_t bytes[16]) const; + + // VSR (Vector-Scalar Register) accessors: unified 64-register namespace + // where VSR 0-31 aliases FPR 0-31 (DW0 is the FPR value, DW1 is + // architecturally undefined — we model it as zero on read, ignored on + // write) and VSR 32-63 aliases VR 0-31. Used by VSX instructions + // (xxpermdi, xxlor, xxlxor, mtvsrd, mfvsrd, ...). + void getVSR128(int vsr, uint8_t bytes[16]) const; + void setVSR128(int vsr, const uint8_t bytes[16]); + + // SPR accessors. + int64_t getLR() const { return LR_; } + void setLR(int64_t value) { LR_ = value; } + int64_t getCTR() const { return CTR_; } + void setCTR(int64_t value) { CTR_ = value; } + uint32_t getCR() const { return CR_; } + void setCR(uint32_t value) { CR_ = value; } + uint64_t getXER() const { return XER_; } + void setXER(uint64_t value) { XER_ = value; } + uint64_t getFPSCR() const { return FPSCR_; } + void setFPSCR(uint64_t value) { FPSCR_ = value; } + + // CR field accessors: field 0 is the most significant nibble (bits 31:28). + uint8_t getCRField(int field) const { + return (CR_ >> (4 * (7 - field))) & 0xF; + } + void setCRField(int field, uint8_t val) { + uint32_t shift = 4 * (7 - field); + CR_ = (CR_ & ~(0xFu << shift)) | ((val & 0xFu) << shift); + } + + // XER bit accessors. + bool getXERSO() const { return (XER_ >> kXERSOBit) & 1; } + void setXERSO(bool v) { + XER_ = (XER_ & ~(1ull << kXERSOBit)) | ((uint64_t)v << kXERSOBit); + } + bool getXEROV() const { return (XER_ >> kXEROVBit) & 1; } + void setXEROV(bool v) { + XER_ = (XER_ & ~(1ull << kXEROVBit)) | ((uint64_t)v << kXEROVBit); + // Mirror to OV32. Real POWER9 silicon sets OV32 == OV for both 32-bit + // and 64-bit overflow ops: mulldo(2, 2^62) produces OV=OV32=1; + // mulldo(2^30, 4) produces OV=OV32=0. The JIT's + // POWER9 Overflow path is `mulldo + mcrxrx + bc Overflow`, where + // mcrxrx places OV32 in the GT slot and the Overflow condition tests + // GT — so OV32 must be live or no-overflow is reported even when + // OV=1. Without this mirror, BigInt fast-path mul silently wraps. + XER_ = (XER_ & ~(1ull << kXEROV32Bit)) | ((uint64_t)v << kXEROV32Bit); + if (v) setXERSO(true); + } + bool getXERCA() const { return (XER_ >> kXERCABit) & 1; } + void setXERCA(bool v) { + XER_ = (XER_ & ~(1ull << kXERCABit)) | ((uint64_t)v << kXERCABit); + } + + // PC accessors. + void set_pc(int64_t value); + int64_t get_pc() const; + + template + T get_pc_as() const { + return reinterpret_cast(get_pc()); + } + + void enable_single_stepping(SingleStepCallback cb, void* arg); + void disable_single_stepping(); + + uintptr_t stackLimit() const; + bool overRecursed(uintptr_t newsp = 0) const; + bool overRecursedWithExtra(uint32_t extra) const; + + template + void execute(); + + int64_t call(uint8_t* entry, int argument_count, ...); + + uintptr_t pushAddress(uintptr_t address); + uintptr_t popAddress(); + + void setLastDebuggerInput(char* input); + char* lastDebuggerInput() { return lastDebuggerInput_; } + + bool has_bad_pc() const; + + // Update CR field 0 from a 64-bit result. + void updateCR0(int64_t result) { + uint8_t field = kCRFieldSO * getXERSO(); + if (result < 0) + field |= kCRFieldLT; + else if (result > 0) + field |= kCRFieldGT; + else + field |= kCRFieldEQ; + setCRField(0, field); + } + + // Update CR field 0 from a 32-bit result (sign-extended comparison). + void updateCR0_32(int32_t result) { + uint8_t field = kCRFieldSO * getXERSO(); + if (result < 0) + field |= kCRFieldLT; + else if (result > 0) + field |= kCRFieldGT; + else + field |= kCRFieldEQ; + setCRField(0, field); + } + + // Compare and set an arbitrary CR field. + void setCRFieldCmp(int field, int64_t lhs, int64_t rhs) { + uint8_t val = kCRFieldSO * getXERSO(); + if (lhs < rhs) + val |= kCRFieldLT; + else if (lhs > rhs) + val |= kCRFieldGT; + else + val |= kCRFieldEQ; + setCRField(field, val); + } + + void setCRFieldCmpU(int field, uint64_t lhs, uint64_t rhs) { + uint8_t val = kCRFieldSO * getXERSO(); + if (lhs < rhs) + val |= kCRFieldLT; + else if (lhs > rhs) + val |= kCRFieldGT; + else + val |= kCRFieldEQ; + setCRField(field, val); + } + + private: + enum SpecialValues { + // PPC masks the low 2 bits of branch targets, so these must be + // 4-byte aligned to survive the & ~3 mask in blr/bcctr. + bad_ra = -4, + end_sim_pc = -8, + Unpredictable = 0xbadbeaf + }; + + bool init(); + + void format(SimInstruction* instr, const char* format); + + // Memory access. + inline uint8_t readBU(uint64_t addr); + inline int8_t readB(uint64_t addr); + inline void writeB(uint64_t addr, uint8_t value); + inline void writeB(uint64_t addr, int8_t value); + + 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 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); + + inline uint8_t loadLinkedB(uint64_t addr, SimInstruction* instr); + inline int storeConditionalB(uint64_t addr, uint8_t value, + SimInstruction* instr); + inline uint16_t loadLinkedH(uint64_t addr, SimInstruction* instr); + inline int storeConditionalH(uint64_t addr, uint16_t value, + SimInstruction* instr); + inline int32_t loadLinkedW(uint64_t addr, SimInstruction* instr); + inline int storeConditionalW(uint64_t addr, int32_t value, + SimInstruction* instr); + inline int64_t loadLinkedD(uint64_t addr, SimInstruction* instr); + inline int storeConditionalD(uint64_t addr, int64_t value, + SimInstruction* instr); + + // Instruction decoders. + void decodeDFormALU(SimInstruction* instr); + void decodeDFormLoad(SimInstruction* instr); + void decodeDFormStore(SimInstruction* instr); + void decodeDSForm(SimInstruction* instr); + void decodeXForm(SimInstruction* instr); + void decodeRotateMask(SimInstruction* instr); + void decodeBranch(SimInstruction* instr); + void decodeFP(SimInstruction* instr); + void decodeVSX(SimInstruction* instr); + void decodeVMX(SimInstruction* instr); + // Power ISA v3.1 prefixed instructions. `prefix` points at the + // 4-byte prefix word; the suffix is read from `prefix + 4`. + void decodePrefixed(SimInstruction* prefix); + + void softwareInterrupt(SimInstruction* instr); + + // Stop/breakpoint helpers. + 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); + + JS::ProfilingFrameIterator::RegisterState registerState(); + + bool MOZ_ALWAYS_INLINE handleWasmSegFault(uint64_t addr, unsigned numBytes) { + if (MOZ_LIKELY(!js::wasm::CodeExists)) { + return false; + } + uint8_t* newPC; + if (!js::wasm::MemoryAccessTraps(registerState(), (uint8_t*)addr, numBytes, + &newPC)) { + return false; + } + LLBit_ = false; + set_pc(int64_t(newPC)); + return true; + } + + void instructionDecode(SimInstruction* instr); + + public: + static int64_t StopSimAt; + + static void* RedirectNativeFunction(void* nativeFunction, + ABIFunctionType type); + + private: + void setCallResultDouble(double result); + void setCallResultFloat(float result); + void setCallResult(int64_t res); +# ifdef XP_DARWIN + void setCallResult(intptr_t res); +# endif + void setCallResult(__int128 res); + + void callInternal(uint8_t* entry); + + // Architecture state. + int64_t registers_[kNumSimuRegisters]; + int64_t FPUregisters_[kNumFPURegisters]; + // VR namespace (Altivec/VMX registers VR0-VR31 == VSR32-63). Stored as + // 16 raw bytes per register to preserve exact architectural byte order + // independent of host endianness. Accessors defined below; the bytes + // array is the ground truth. + uint8_t VRregisters_[kNumVRRegisters][16]; + + // PPC Special Purpose Registers. + int64_t LR_; + int64_t CTR_; + uint32_t CR_; + uint64_t XER_; + uint64_t FPSCR_; + + // Atomics. + bool LLBit_; + uintptr_t LLAddr_; + int64_t lastLLValue_; + + // Simulator support. + char* stack_; + uintptr_t stackLimit_; + bool pc_modified_; + int64_t icount_; + int64_t break_count_; + + char* lastDebuggerInput_; + + SimInstruction* break_pc_; + Instr break_instr_; + + bool single_stepping_; + SingleStepCallback single_step_callback_; + void* single_step_callback_arg_; + + static const uint32_t kNumOfWatchedStops = 256; + static const uint32_t kStopDisabledBit = 1U << 31; + + struct StopCountAndDesc { + uint32_t count_; + char* desc_; + }; + StopCountAndDesc watchedStops_[kNumOfWatchedStops]; +}; + +// Process-wide simulator state. +class SimulatorProcess { + friend class Redirection; + friend class AutoLockSimulatorCache; + + private: + 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; + + static mozilla::Atomic + ICacheCheckingDisableCount; + static void FlushICache(void* start, size_t size); + static void checkICacheLocked(SimInstruction* instr); + + static bool initialize() { + singleton_ = js_new(); + return singleton_; + } + static void destroy() { + js_delete(singleton_); + singleton_ = nullptr; + } + + SimulatorProcess(); + ~SimulatorProcess(); + + private: + static SimulatorProcess* singleton_; + + Mutex cacheLock_; + Redirection* redirection_; + ICacheMap icache_; + + public: + static ICacheMap& icache() { + singleton_->cacheLock_.assertOwnedByCurrentThread(); + return singleton_->icache_; + } + + static Redirection* redirection() { + singleton_->cacheLock_.assertOwnedByCurrentThread(); + return singleton_->redirection_; + } + + static void setRedirection(js::jit::Redirection* redirection) { + singleton_->cacheLock_.assertOwnedByCurrentThread(); + singleton_->redirection_ = redirection; + } +}; + +} // namespace jit +} // namespace js + +#endif /* JS_SIMULATOR_PPC */ + +#endif /* jit_ppc_Simulator_ppc_h */ diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/Trampoline-ppc.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/Trampoline-ppc.cpp --- a/js/src/jit/ppc/Trampoline-ppc.cpp 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/Trampoline-ppc.cpp 2026-07-28 20:18:48.105364653 +0200 @@ -0,0 +1,697 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "jit/Bailouts.h" +#include "jit/BaselineFrame.h" +#include "jit/CalleeToken.h" +#include "jit/JitFrames.h" +#include "jit/JitRuntime.h" +#include "jit/PerfSpewer.h" +#include "jit/ppc/SharedICHelpers-ppc.h" +#include "jit/VMFunctions.h" +#include "vm/JitActivation.h" +#include "vm/JSContext.h" + +#include "jit/MacroAssembler-inl.h" + +using namespace js; +using namespace js::jit; + +// Float (Single+Double) and all GPRs. Simd128 excluded — Ion compiles JS +// (no v128 type), so SIMD regs are never live at bailout / invalidator / +// preBarrier entry. Including them would force the bailout frame's +// FPUArray to hold v128 slots that Ion never writes. +static const LiveRegisterSet AllRegs = LiveRegisterSet( + GeneralRegisterSet(Registers::AllMask), + FloatRegisterSet(FloatRegisters::AllSingleMask | + FloatRegisters::AllDoubleMask)); + +static_assert(sizeof(uintptr_t) == sizeof(uint32_t), "Not 32-bit clean."); + +// SysV ppc32 callee-saved: GPRs r14-r31, FPRs f14-f31, VRs VR20-VR31, LR. +// r2 (TLS pointer) and r13 (small-data base) are reserved and never touched. +// We also save reg_vp (r10 / IntArgReg7) so we can use it after the JIT call. +// +// Layout is alignas(16) so that after `reserveStack(sizeof(EnterJITRegs))` +// the SP-relative offset of every VR slot is 16-byte aligned, satisfying +// stvx's preferred alignment, and so sizeof stays a multiple of 16 and SP +// remains quadword-aligned. +struct alignas(16) EnterJITRegs { + // VR20-VR31 first so their SP-relative offsets are 0, 16, 32, ... — all + // 16-byte aligned regardless of what follows. + uint8_t vr20[16]; + uint8_t vr21[16]; + uint8_t vr22[16]; + uint8_t vr23[16]; + uint8_t vr24[16]; + uint8_t vr25[16]; + uint8_t vr26[16]; + uint8_t vr27[16]; + uint8_t vr28[16]; + uint8_t vr29[16]; + uint8_t vr30[16]; + uint8_t vr31[16]; + + double f31; + double f30; + double f29; + double f28; + double f27; + double f26; + double f25; + double f24; + double f23; + double f22; + double f21; + double f20; + double f19; + double f18; + double f17; + double f16; + double f15; + double f14; + + uint32_t r31; // FramePointer + uint32_t r30; + uint32_t r29; + uint32_t r28; + uint32_t r27; + uint32_t r26; + uint32_t r25; + uint32_t r24; + uint32_t r23; + uint32_t r22; + uint32_t r21; + uint32_t r20; + uint32_t r19; + uint32_t r18; + uint32_t r17; + uint32_t r16; + uint32_t r15; + uint32_t r14; + uint32_t lr; + // Save reg_vp (r10) on stack so we can use it after the JIT call returns. + uint32_t r10; +}; +// 192 bytes of VR slots + 144 bytes of FPR slots + 20 GPR/LR words = 416, +// already a multiple of 16. +static_assert((sizeof(EnterJITRegs) % 16) == 0, + "EnterJITRegs must be 16-byte aligned to keep SP aligned"); + +static void GenerateReturn(MacroAssembler& masm) { + MOZ_ASSERT(masm.framePushed() == sizeof(EnterJITRegs)); + + // Restore non-volatile GPRs. + masm.as_lwz(r14, StackPointer, offsetof(EnterJITRegs, r14)); + masm.as_lwz(r15, StackPointer, offsetof(EnterJITRegs, r15)); + masm.as_lwz(r16, StackPointer, offsetof(EnterJITRegs, r16)); + masm.as_lwz(r17, StackPointer, offsetof(EnterJITRegs, r17)); + masm.as_lwz(r18, StackPointer, offsetof(EnterJITRegs, r18)); + masm.as_lwz(r19, StackPointer, offsetof(EnterJITRegs, r19)); + masm.as_lwz(r20, StackPointer, offsetof(EnterJITRegs, r20)); + masm.as_lwz(r21, StackPointer, offsetof(EnterJITRegs, r21)); + masm.as_lwz(r22, StackPointer, offsetof(EnterJITRegs, r22)); + masm.as_lwz(r23, StackPointer, offsetof(EnterJITRegs, r23)); + masm.as_lwz(r24, StackPointer, offsetof(EnterJITRegs, r24)); + masm.as_lwz(r25, StackPointer, offsetof(EnterJITRegs, r25)); + masm.as_lwz(r26, StackPointer, offsetof(EnterJITRegs, r26)); + masm.as_lwz(r27, StackPointer, offsetof(EnterJITRegs, r27)); + masm.as_lwz(r28, StackPointer, offsetof(EnterJITRegs, r28)); + masm.as_lwz(r29, StackPointer, offsetof(EnterJITRegs, r29)); + masm.as_lwz(r30, StackPointer, offsetof(EnterJITRegs, r30)); + masm.as_lwz(r31, StackPointer, offsetof(EnterJITRegs, r31)); + + // Restore LR. + masm.as_lwz(r0, StackPointer, offsetof(EnterJITRegs, lr)); + masm.xs_mtlr(r0); + + // Restore non-volatile FPRs. + masm.as_lfd(f14, StackPointer, offsetof(EnterJITRegs, f14)); + masm.as_lfd(f15, StackPointer, offsetof(EnterJITRegs, f15)); + masm.as_lfd(f16, StackPointer, offsetof(EnterJITRegs, f16)); + masm.as_lfd(f17, StackPointer, offsetof(EnterJITRegs, f17)); + masm.as_lfd(f18, StackPointer, offsetof(EnterJITRegs, f18)); + masm.as_lfd(f19, StackPointer, offsetof(EnterJITRegs, f19)); + masm.as_lfd(f20, StackPointer, offsetof(EnterJITRegs, f20)); + masm.as_lfd(f21, StackPointer, offsetof(EnterJITRegs, f21)); + masm.as_lfd(f22, StackPointer, offsetof(EnterJITRegs, f22)); + masm.as_lfd(f23, StackPointer, offsetof(EnterJITRegs, f23)); + masm.as_lfd(f24, StackPointer, offsetof(EnterJITRegs, f24)); + masm.as_lfd(f25, StackPointer, offsetof(EnterJITRegs, f25)); + masm.as_lfd(f26, StackPointer, offsetof(EnterJITRegs, f26)); + masm.as_lfd(f27, StackPointer, offsetof(EnterJITRegs, f27)); + masm.as_lfd(f28, StackPointer, offsetof(EnterJITRegs, f28)); + masm.as_lfd(f29, StackPointer, offsetof(EnterJITRegs, f29)); + masm.as_lfd(f30, StackPointer, offsetof(EnterJITRegs, f30)); + masm.as_lfd(f31, StackPointer, offsetof(EnterJITRegs, f31)); + + // Restore callee-saved VR20-VR31. lvx uses indexed addressing (RA + RB), + // and r0's value is used here as RB (RA = StackPointer is non-zero, so its + // value is added). r0 is non-allocatable. +#define RESTORE_VR(N) \ + masm.xs_li(r0, offsetof(EnterJITRegs, vr##N)); \ + masm.as_lvx(N, StackPointer, r0) + if (HasVMX()) { + RESTORE_VR(20); RESTORE_VR(21); RESTORE_VR(22); RESTORE_VR(23); + RESTORE_VR(24); RESTORE_VR(25); RESTORE_VR(26); RESTORE_VR(27); + RESTORE_VR(28); RESTORE_VR(29); RESTORE_VR(30); RESTORE_VR(31); + } +#undef RESTORE_VR + + masm.freeStack(sizeof(EnterJITRegs)); + + masm.as_blr(); +} + +static void GeneratePrologue(MacroAssembler& masm) { + // Save LR first (PPC LR is SPR, not GPR). + masm.xs_mflr(r0); + + // SysV ppc32 prologue convention: the LR save word lives at [SP+4] of the + // *caller's* frame, so store it before decrementing SP. External unwinders + // walk the stack through that slot. We keep a second copy in our own frame + // for the clean restore in GenerateReturn. + masm.as_stw(r0, StackPointer, 4); + + masm.reserveStack(sizeof(EnterJITRegs)); + + masm.as_stw(r0, StackPointer, offsetof(EnterJITRegs, lr)); + + // Save non-volatile GPRs. r2 and r13 are reserved and must not be touched. + masm.as_stw(r14, StackPointer, offsetof(EnterJITRegs, r14)); + masm.as_stw(r15, StackPointer, offsetof(EnterJITRegs, r15)); + masm.as_stw(r16, StackPointer, offsetof(EnterJITRegs, r16)); + masm.as_stw(r17, StackPointer, offsetof(EnterJITRegs, r17)); + masm.as_stw(r18, StackPointer, offsetof(EnterJITRegs, r18)); + masm.as_stw(r19, StackPointer, offsetof(EnterJITRegs, r19)); + masm.as_stw(r20, StackPointer, offsetof(EnterJITRegs, r20)); + masm.as_stw(r21, StackPointer, offsetof(EnterJITRegs, r21)); + masm.as_stw(r22, StackPointer, offsetof(EnterJITRegs, r22)); + masm.as_stw(r23, StackPointer, offsetof(EnterJITRegs, r23)); + masm.as_stw(r24, StackPointer, offsetof(EnterJITRegs, r24)); + masm.as_stw(r25, StackPointer, offsetof(EnterJITRegs, r25)); + masm.as_stw(r26, StackPointer, offsetof(EnterJITRegs, r26)); + masm.as_stw(r27, StackPointer, offsetof(EnterJITRegs, r27)); + masm.as_stw(r28, StackPointer, offsetof(EnterJITRegs, r28)); + masm.as_stw(r29, StackPointer, offsetof(EnterJITRegs, r29)); + masm.as_stw(r30, StackPointer, offsetof(EnterJITRegs, r30)); + masm.as_stw(r31, StackPointer, offsetof(EnterJITRegs, r31)); + + // Save reg_vp (r10) so we can retrieve it after the JIT call. + masm.as_stw(r10, StackPointer, offsetof(EnterJITRegs, r10)); + + // Save non-volatile FPRs. + masm.as_stfd(f14, StackPointer, offsetof(EnterJITRegs, f14)); + masm.as_stfd(f15, StackPointer, offsetof(EnterJITRegs, f15)); + masm.as_stfd(f16, StackPointer, offsetof(EnterJITRegs, f16)); + masm.as_stfd(f17, StackPointer, offsetof(EnterJITRegs, f17)); + masm.as_stfd(f18, StackPointer, offsetof(EnterJITRegs, f18)); + masm.as_stfd(f19, StackPointer, offsetof(EnterJITRegs, f19)); + masm.as_stfd(f20, StackPointer, offsetof(EnterJITRegs, f20)); + masm.as_stfd(f21, StackPointer, offsetof(EnterJITRegs, f21)); + masm.as_stfd(f22, StackPointer, offsetof(EnterJITRegs, f22)); + masm.as_stfd(f23, StackPointer, offsetof(EnterJITRegs, f23)); + masm.as_stfd(f24, StackPointer, offsetof(EnterJITRegs, f24)); + masm.as_stfd(f25, StackPointer, offsetof(EnterJITRegs, f25)); + masm.as_stfd(f26, StackPointer, offsetof(EnterJITRegs, f26)); + masm.as_stfd(f27, StackPointer, offsetof(EnterJITRegs, f27)); + masm.as_stfd(f28, StackPointer, offsetof(EnterJITRegs, f28)); + masm.as_stfd(f29, StackPointer, offsetof(EnterJITRegs, f29)); + masm.as_stfd(f30, StackPointer, offsetof(EnterJITRegs, f30)); + masm.as_stfd(f31, StackPointer, offsetof(EnterJITRegs, f31)); + + // Save callee-saved VR20-VR31. The JIT freely uses VMX registers + // via EmitVmxBinary etc.; without this save the C caller's VR20-VR31 + // contents would be trashed on return. stvx uses indexed addressing — + // r0 holds the offset (non-allocatable in JIT regalloc; safe to use as + // a free temp here). +#define SAVE_VR(N) \ + masm.xs_li(r0, offsetof(EnterJITRegs, vr##N)); \ + masm.as_stvx(N, StackPointer, r0) + if (HasVMX()) { + SAVE_VR(20); SAVE_VR(21); SAVE_VR(22); SAVE_VR(23); + SAVE_VR(24); SAVE_VR(25); SAVE_VR(26); SAVE_VR(27); + SAVE_VR(28); SAVE_VR(29); SAVE_VR(30); SAVE_VR(31); + } +#undef SAVE_VR +} + +void JitRuntime::generateEnterJIT(JSContext* cx, MacroAssembler& masm) { + AutoCreatedBy acb(masm, "JitRuntime::generateEnterJIT"); + + enterJITOffset_ = startTrampolineCode(masm); + + // EnterJitCode signature: (void* code, unsigned argc, Value* argv, + // InterpreterFrame* fp, CalleeToken calleeToken, + // JSObject* envChain, size_t numStackValues, + // Value* vp) + const Register reg_code = IntArgReg0; // r3 + const Register reg_argc = IntArgReg1; // r4 + const Register reg_argv = IntArgReg2; // r5 + const mozilla::DebugOnly reg_frame = IntArgReg3; // r6 + const Register reg_token = IntArgReg4; // r7 + const Register reg_chain = IntArgReg5; // r8 + const Register reg_values = IntArgReg6; // r9 + const Register reg_vp = IntArgReg7; // r10 + + MOZ_ASSERT(OsrFrameReg == reg_frame); + + GeneratePrologue(masm); + + // Save stack pointer as baseline frame. + masm.movePtr(StackPointer, FramePointer); + + // Use non-volatile scratch registers for generateEnterJitShared. + // r14, r15, r17 are non-volatile and not special-purpose in JIT. + generateEnterJitShared(masm, reg_argc, reg_argv, reg_token, r14, r15, r17); + + // Push the descriptor. + masm.unboxInt32(Address(reg_vp, 0), r14); + masm.pushFrameDescriptorForJitCall(FrameType::CppToJSJit, r14, r14); + + CodeLabel returnLabel; + Label oomReturnLabel; + { + // Handle Interpreter -> Baseline OSR. + AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); + MOZ_ASSERT(!regs.has(FramePointer)); + regs.take(OsrFrameReg); + regs.take(reg_code); + MOZ_ASSERT(!regs.has(ReturnReg), "ReturnReg matches reg_code"); + + Label notOsr; + masm.branchTestPtr(Assembler::Zero, OsrFrameReg, OsrFrameReg, ¬Osr); + + Register numStackValues = reg_values; + regs.take(numStackValues); + Register scratch = regs.takeAny(); + + // Push return address. + masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer); + masm.mov(&returnLabel, scratch); + masm.storePtr(scratch, Address(StackPointer, 0)); + + // Push previous frame pointer. + masm.subPtr(Imm32(sizeof(uintptr_t)), StackPointer); + masm.storePtr(FramePointer, Address(StackPointer, 0)); + + // Reserve frame. + Register framePtr = FramePointer; + masm.movePtr(StackPointer, framePtr); + masm.subPtr(Imm32(BaselineFrame::Size()), StackPointer); + + Register framePtrScratch = regs.takeAny(); + masm.movePtr(StackPointer, framePtrScratch); + + // Reserve space for locals and stack values; a Value is still 8 bytes. + masm.x_slwi(scratch, numStackValues, 3); + masm.subPtr(scratch, StackPointer); + + // Enter exit frame. + masm.reserveStack(3 * sizeof(uintptr_t)); + masm.storePtr(ImmWord(MakeFrameDescriptor(FrameType::BaselineJS)), + Address(StackPointer, 2 * sizeof(uintptr_t))); + masm.storePtr(ImmPtr(nullptr), Address(StackPointer, sizeof(uintptr_t))); + masm.storePtr(FramePointer, Address(StackPointer, 0)); + + // No GC things to mark, push a bare token. + masm.loadJSContext(scratch); + masm.enterFakeExitFrame(scratch, scratch, ExitFrameType::Bare); + + masm.reserveStack(2 * sizeof(uintptr_t)); + masm.storePtr(framePtr, Address(StackPointer, sizeof(uintptr_t))); + masm.storePtr(reg_code, Address(StackPointer, 0)); + + using Fn = void (*)(BaselineFrame* frame, InterpreterFrame* interpFrame, + uint32_t numStackValues); + masm.setupUnalignedABICall(scratch); + masm.passABIArg(framePtrScratch); + masm.passABIArg(OsrFrameReg); + masm.passABIArg(numStackValues); + masm.callWithABI( + ABIType::General, CheckUnsafeCallWithABI::DontCheckHasExitFrame); + + 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)); + + masm.freeStack(ExitFrameLayout::SizeWithFooter()); + + // If OSR-ing, then emit instrumentation for setting lastProfilerFrame + // if profiler instrumentation is enabled. + { + Label skipProfilingInstrumentation; + AbsoluteAddress addressOfEnabled( + cx->runtime()->geckoProfiler().addressOfEnabled()); + masm.branch32(Assembler::Equal, addressOfEnabled, Imm32(0), + &skipProfilingInstrumentation); + masm.profilerEnterFrame(framePtr, scratch); + masm.bind(&skipProfilingInstrumentation); + } + + masm.jump(jitcode); + + masm.bind(¬Osr); + // Load the scope chain in R1. + MOZ_ASSERT(R1.scratchReg() != reg_code); + masm.movePtr(reg_chain, R1.scratchReg()); + } + + // The call will push the return address and frame pointer on the stack, thus + // we check that the stack would be aligned once the call is complete. + masm.assertStackAlignment(JitStackAlignment, 2 * sizeof(uintptr_t)); + + // Call the function with pushing return address to stack. + masm.callJitNoProfiler(reg_code); + + { + // Interpreter -> Baseline OSR will return here. + masm.bind(&returnLabel); + masm.addCodeLabel(returnLabel); + masm.bind(&oomReturnLabel); + } + + // Discard arguments and padding. Set sp to the address of the EnterJITRegs + // on the stack. + masm.movePtr(FramePointer, StackPointer); + + // Store the returned value into the vp. + masm.as_lwz(reg_vp, StackPointer, offsetof(EnterJITRegs, r10)); + masm.storeValue(JSReturnOperand, Address(reg_vp, 0)); + + // Restore non-volatile registers and return. + GenerateReturn(masm); +} + +// static +mozilla::Maybe<::JS::ProfilingFrameIterator::RegisterState> +JitRuntime::getCppEntryRegisters(JitFrameLayout* frameStackAddress) { + return mozilla::Nothing{}; +} + +void JitRuntime::generateInvalidator(MacroAssembler& masm, Label* bailoutTail) { + AutoCreatedBy acb(masm, "JitRuntime::generateInvalidator"); + + invalidatorOffset_ = startTrampolineCode(masm); + + masm.checkStackAlignment(); + + // Push all registers so we can access them from [base + code]. + masm.PushRegsInMask(AllRegs); + + // Pass pointer to InvalidationBailoutStack structure. + masm.movePtr(StackPointer, IntArgReg0); + + // Reserve place for BailoutInfo pointer. Two words to ensure alignment for + // setupAlignedABICall. + masm.subPtr(Imm32(2 * sizeof(uintptr_t)), StackPointer); + masm.movePtr(StackPointer, IntArgReg1); + + using Fn = bool (*)(InvalidationBailoutStack* sp, BaselineBailoutInfo** info); + masm.setupAlignedABICall(); + masm.passABIArg(IntArgReg0); + masm.passABIArg(IntArgReg1); + masm.callWithABI( + ABIType::General, CheckUnsafeCallWithABI::DontCheckOther); + + masm.pop(IntArgReg2); + + // Pop the machine state and the dead frame. + masm.moveToStackPtr(FramePointer); + + // Jump to shared bailout tail. The BailoutInfo pointer has to be in + // IntArgReg2 (r5). + masm.jump(bailoutTail); +} + +// When bailout is done via out of line code (lazy bailout). +// Frame size is stored in LR (look at +// CodeGeneratorPPC::generateOutOfLineCode()) and thunk code should save it +// on stack. +static void PushBailoutFrame(MacroAssembler& masm, Register spArg) { + // Push the frameSize_ stored in LR. + // See: CodeGeneratorPPC::generateOutOfLineCode() + masm.pushReturnAddress(); + + // Push registers such that we can access them from [base + code]. + masm.PushRegsInMask(AllRegs); + + // Put pointer to BailoutStack as first argument to the Bailout(). + masm.movePtr(StackPointer, spArg); +} + +static void GenerateBailoutThunk(MacroAssembler& masm, Label* bailoutTail) { + PushBailoutFrame(masm, IntArgReg0); + + // Make space for Bailout's bailoutInfo outparam. + masm.reserveStack(sizeof(void*)); + masm.movePtr(StackPointer, IntArgReg1); + + // Call the bailout function. + using Fn = bool (*)(BailoutStack* sp, BaselineBailoutInfo** info); + masm.setupUnalignedABICall(IntArgReg2); + masm.passABIArg(IntArgReg0); + masm.passABIArg(IntArgReg1); + masm.callWithABI(ABIType::General, + CheckUnsafeCallWithABI::DontCheckOther); + + // Get the bailoutInfo outparam. + masm.pop(IntArgReg2); + + // Remove both the bailout frame and the topmost Ion frame's stack. + masm.moveToStackPtr(FramePointer); + + // Jump to shared bailout tail. The BailoutInfo pointer has to be in + // IntArgReg2 (r5). + masm.jump(bailoutTail); +} + +void JitRuntime::generateBailoutHandler(MacroAssembler& masm, + Label* bailoutTail) { + AutoCreatedBy acb(masm, "JitRuntime::generateBailoutHandler"); + + bailoutHandlerOffset_ = startTrampolineCode(masm); + + GenerateBailoutThunk(masm, bailoutTail); +} + +bool JitRuntime::generateVMWrapper(JSContext* cx, MacroAssembler& masm, + VMFunctionId id, const VMFunctionData& f, + DynFn nativeFun, uint32_t* wrapperOffset) { + AutoCreatedBy acb(masm, "JitRuntime::generateVMWrapper"); + + *wrapperOffset = startTrampolineCode(masm); + + // Avoid conflicts with argument registers while discarding the result after + // the function call. + 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."); + + // WrapperMask is VolatileMask, which under SysV ppc32 is r0 plus r3-r12. + // r0 reads as a literal zero in base-register position, and r11/r12 are + // the assembler's own scratches, so none of them may be handed out here. + regs.take(r0); + regs.take(r11); + regs.take(r12); + + // The context is the first argument; r3 is the first argument register. + Register cxreg = IntArgReg0; + regs.take(cxreg); + + // On link-register platforms, it is the responsibility of the VM *callee* to + // push the return address, while the caller must ensure that the address + // is stored in LR on entry. This allows the VM wrapper to work with both + // direct calls and tail calls. + masm.pushReturnAddress(); + + // Push the frame pointer to finish the exit frame, then link it up. + masm.Push(FramePointer); + masm.moveStackPtrTo(FramePointer); + masm.loadJSContext(cxreg); + masm.enterExitFrame(cxreg, regs.getAny(), id); + + // Reserve space for the outparameter. + masm.reserveVMFunctionOutParamSpace(f); + + const int32_t outParamOffset = + -int32_t(ExitFooterFrame::Size()) - f.sizeOfOutParamStackSlot(); + + // The 32-bit PowerPC SysV ABI passes class and union types by invisible + // reference: the callee receives a pointer to the caller's copy. An argument + // that SpiderMonkey marks ByRef and whose C++ type is such an aggregate — a + // Handle or MutableHandle, whose value *is* the address of the + // exit-frame slot — therefore needs one indirection more than on every other + // platform. Materialize that level in a block of words below the outparam. + const bool outParamIsAggregate = + f.outParam != Type_Void && f.argIsAggregate(f.explicitArgs); + size_t indirectWords = outParamIsAggregate ? 1 : 0; + for (uint32_t i = 0; i < f.explicitArgs; i++) { + if (f.argIsAggregate(i) && (f.argProperties(i) & VMFunctionData::ByRef)) { + indirectWords++; + } + } + if (indirectWords) { + masm.reserveStack(indirectWords * sizeof(uintptr_t)); + } + + Register indirectScratch = regs.takeAny(); + int32_t indirectOffset = outParamOffset; + + // Store the address of the exit-frame slot into one of those words and pass + // that word's address: the pointer-to-Handle the callee will dereference. + auto passAggregateByRef = [&](int32_t slotOffset) { + indirectOffset -= sizeof(uintptr_t); + masm.computeEffectiveAddress(Address(FramePointer, slotOffset), + indirectScratch); + masm.storePtr(indirectScratch, Address(FramePointer, indirectOffset)); + masm.passABIArg(MoveOperand(FramePointer, indirectOffset, + MoveOperand::Kind::EffectiveAddress), + ABIType::General); + }; + + masm.setupUnalignedABICallDontSaveRestoreSP(); + masm.passABIArg(cxreg); + + size_t argDisp = ExitFrameLayout::Size(); + + // Copy any arguments. + for (uint32_t explicitArg = 0; explicitArg < f.explicitArgs; explicitArg++) { + uint32_t props = uint32_t(f.argProperties(explicitArg)); + bool isDouble = (props & VMFunctionData::Double) != 0; + bool byRef = (props & VMFunctionData::ByRef) != 0; + + if (f.argIsAggregate(explicitArg)) { + if (byRef) { + passAggregateByRef(int32_t(argDisp)); + } else { + // An aggregate passed by value: the exit-frame slot already holds the + // caller's copy, so its address is what the ABI wants. + masm.passABIArg(MoveOperand(FramePointer, argDisp, + MoveOperand::Kind::EffectiveAddress), + ABIType::General); + } + } else if (byRef) { + // A C++ reference is already passed as a plain pointer. + masm.passABIArg(MoveOperand(FramePointer, argDisp, + MoveOperand::Kind::EffectiveAddress), + ABIType::General); + } else if (isDouble) { + // A non-aggregate two-word argument is a real double. + MOZ_ASSERT(f.argPassedInFloatReg(explicitArg)); + masm.passABIArg(MoveOperand(FramePointer, argDisp), ABIType::Float64); + } else if (f.argPassedInFloatReg(explicitArg)) { + masm.passABIArg(MoveOperand(FramePointer, argDisp), ABIType::Float64); + } else { + masm.passABIArg(MoveOperand(FramePointer, argDisp), ABIType::General); + } + + argDisp += isDouble ? 2 * sizeof(void*) : sizeof(void*); + } + + // Copy the implicit outparam, if any. + if (f.outParam != Type_Void) { + if (outParamIsAggregate) { + passAggregateByRef(outParamOffset); + } else { + masm.passABIArg(MoveOperand(FramePointer, outParamOffset, + MoveOperand::Kind::EffectiveAddress), + ABIType::General); + } + } + + masm.callWithABI(nativeFun, ABIType::General, + CheckUnsafeCallWithABI::DontCheckHasExitFrame); + + // Test for failure. + switch (f.failType()) { + case Type_Cell: + masm.branchTestPtr(Assembler::Zero, IntArgReg0, IntArgReg0, + masm.failureLabel()); + break; + case Type_Bool: + masm.branchIfFalseBool(IntArgReg0, masm.failureLabel()); + break; + case Type_Void: + break; + default: + MOZ_CRASH("unknown failure kind"); + } + + // Load the outparam. + masm.loadVMFunctionOutParam(f, Address(FramePointer, outParamOffset)); + + // Pop frame and restore frame pointer. + masm.moveToStackPtr(FramePointer); + masm.pop(FramePointer); + + // Return. Subtract sizeof(void*) for the frame pointer. + masm.retn(Imm32(sizeof(ExitFrameLayout) - sizeof(void*) + + f.explicitStackSlots() * sizeof(void*) + + f.extraValuesToPop * sizeof(Value))); + + return true; +} + +uint32_t JitRuntime::generatePreBarrier(JSContext* cx, MacroAssembler& masm, + MIRType type) { + AutoCreatedBy acb(masm, "JitRuntime::generatePreBarrier"); + + uint32_t offset = startTrampolineCode(masm); + + MOZ_ASSERT(PreBarrierReg == IntArgReg1); // r4 + Register temp1 = IntArgReg0; // r3 + Register temp2 = IntArgReg2; // r5 + Register temp3 = IntArgReg3; // r6 + masm.push(temp1); + masm.push(temp2); + masm.push(temp3); + + Label noBarrier; + masm.emitPreBarrierFastPath(type, temp1, temp2, temp3, &noBarrier); + + // Call into C++ to mark this GC thing. + masm.pop(temp3); + masm.pop(temp2); + masm.pop(temp1); + + LiveRegisterSet save; + save.set() = RegisterSet(GeneralRegisterSet(Registers::VolatileMask), + FloatRegisterSet(FloatRegisters::VolatileMask)); + // On PPC, save LR since we'll be making a call. + masm.pushReturnAddress(); + masm.PushRegsInMask(save); + + masm.movePtr(ImmPtr(cx->runtime()), IntArgReg0); + + masm.setupUnalignedABICall(IntArgReg2); + masm.passABIArg(IntArgReg0); + masm.passABIArg(IntArgReg1); + masm.callWithABI(JitPreWriteBarrier(type)); + + masm.PopRegsInMask(save); + masm.ret(); + + masm.bind(&noBarrier); + masm.pop(temp3); + masm.pop(temp2); + masm.pop(temp1); + masm.abiret(); + + return offset; +} + +void JitRuntime::generateBailoutTailStub(MacroAssembler& masm, + Label* bailoutTail) { + AutoCreatedBy acb(masm, "JitRuntime::generateBailoutTailStub"); + + masm.bind(bailoutTail); + masm.generateBailoutTail(IntArgReg1, IntArgReg2); +} diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/build-16.log /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/build-16.log --- a/js/src/jit/ppc/build-16.log 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/build-16.log 2026-07-28 23:04:57.201833961 +0200 @@ -0,0 +1 @@ +scripts/Build-Pkg: line 13: ./jsbuild.sh: No such file or directory diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/ppc/build-17.log /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/ppc/build-17.log --- a/js/src/jit/ppc/build-17.log 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/jit/ppc/build-17.log 2026-07-28 23:06:27.257752648 +0200 @@ -0,0 +1 @@ +scripts/Build-Pkg: line 37: ./jsbuild.sh: No such file or directory diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/shared/Assembler-shared.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/shared/Assembler-shared.h --- a/js/src/jit/shared/Assembler-shared.h 2026-07-28 16:38:22.641663183 +0200 +++ b/js/src/jit/shared/Assembler-shared.h 2026-07-28 16:57:40.908299324 +0200 @@ -31,20 +31,22 @@ #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ defined(JS_CODEGEN_WASM32) || defined(JS_CODEGEN_RISCV64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC) || defined(JS_CODEGEN_PPC64) // Push return addresses callee-side. # define JS_USE_LINK_REGISTER #endif #if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_ARM64) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || \ - defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_PPC) || \ + defined(JS_CODEGEN_PPC64) // JS_CODELABEL_LINKMODE gives labels additional metadata // describing how Bind() should patch them. # define JS_CODELABEL_LINKMODE #endif using js::wasm::FaultingCodeOffset; +using js::wasm::FaultingCodeOffsetPair; namespace js { namespace jit { diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/shared/AtomicOperations-feeling-lucky-gcc.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/shared/AtomicOperations-feeling-lucky-gcc.h --- a/js/src/jit/shared/AtomicOperations-feeling-lucky-gcc.h 2026-07-28 16:38:22.641712976 +0200 +++ b/js/src/jit/shared/AtomicOperations-feeling-lucky-gcc.h 2026-07-29 07:41:44.636283150 +0200 @@ -65,6 +65,16 @@ # define HAS_64BIT_LOCKFREE #endif +// 32-bit PowerPC has lwarx/stwcx. but no ldarx/stdcx., so an 8-byte atomic +// cannot be lock-free. GCC routes the 8-byte __atomic builtins to libatomic, +// whose lock-based implementation is correct as long as every 8-byte atomic +// access in the process goes through it. AtomicsMeetsPreconditions() in +// jit/CacheIR.cpp keeps the JIT from inlining 64-bit atomics when +// isLockfree8() is false, which is what guarantees that. +#if defined(JS_CODEGEN_PPC) +# define HAS_64BIT_ATOMICS +#endif + #if defined(__riscv) && __riscv_xlen == 64 # define HAS_64BIT_ATOMICS # define HAS_64BIT_LOCKFREE diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/shared/CodeGenerator-shared.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/shared/CodeGenerator-shared.cpp --- a/js/src/jit/shared/CodeGenerator-shared.cpp 2026-07-28 16:38:22.641784399 +0200 +++ b/js/src/jit/shared/CodeGenerator-shared.cpp 2026-07-28 18:52:47.194405397 +0200 @@ -85,9 +85,10 @@ CodeGeneratorShared::CodeGeneratorShared #endif #ifdef ENABLE_WASM_SIMD -# if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || \ - defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) - // On X64/x86, ARM64, and PPC64, we don't need alignment for Wasm SIMD at this time. +# if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || \ + defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) + // On X64/x86, ARM64, and PPC, we don't need alignment for Wasm SIMD at this time. # else # error \ "we may need padding so that local slots are SIMD-aligned and the stack must be kept SIMD-aligned too." @@ -1073,8 +1074,9 @@ Label* CodeGeneratorShared::getJumpLabel // This function is not used for MIPS64/LOONG64/RISCV64. They have // branchToBlock. -#if !defined(JS_CODEGEN_MIPS64) && !defined(JS_CODEGEN_LOONG64) && \ - !defined(JS_CODEGEN_RISCV64) && !defined(JS_CODEGEN_PPC64) +#if !defined(JS_CODEGEN_MIPS64) && !defined(JS_CODEGEN_LOONG64) && \ + !defined(JS_CODEGEN_RISCV64) && !defined(JS_CODEGEN_PPC64) && \ + !defined(JS_CODEGEN_PPC) void CodeGeneratorShared::jumpToBlock(MBasicBlock* mir, Assembler::Condition cond) { // Skip past trivial blocks. diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit/shared/Lowering-shared-inl.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit/shared/Lowering-shared-inl.h --- a/js/src/jit/shared/Lowering-shared-inl.h 2026-07-28 16:38:22.641854322 +0200 +++ b/js/src/jit/shared/Lowering-shared-inl.h 2026-07-28 18:52:47.194621576 +0200 @@ -527,7 +527,8 @@ LAllocation LIRGeneratorShared::useRegis #if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) LAllocation LIRGeneratorShared::useAnyOrConstant(MDefinition* mir) { return useRegisterOrConstant(mir); } diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/debug/Object-createSource-forceEnableAsmJS.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/debug/Object-createSource-forceEnableAsmJS.js --- a/js/src/jit-test/tests/debug/Object-createSource-forceEnableAsmJS.js 2026-07-15 22:44:59.000000000 +0200 +++ b/js/src/jit-test/tests/debug/Object-createSource-forceEnableAsmJS.js 2026-07-29 11:29:53.946982027 +0200 @@ -1,4 +1,4 @@ -// |jit-test| --asmjs; skip-if: !wasmDebuggingEnabled() +// |jit-test| --asmjs; skip-if: !wasmDebuggingEnabled() || !isAsmJSCompilationAvailable() gczeal(0); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/ion/mod-constant-pow2-minus-one.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/ion/mod-constant-pow2-minus-one.js --- a/js/src/jit-test/tests/ion/mod-constant-pow2-minus-one.js 2026-07-28 16:38:22.630661042 +0200 +++ b/js/src/jit-test/tests/ion/mod-constant-pow2-minus-one.js 2026-07-29 15:05:46.519117671 +0200 @@ -45,8 +45,10 @@ for (let i = 0; i < 64; i++) { inputs.push(0, 1, -1, 65534, 65535, 65536, 0x7fffffff, -0x80000000); // Warm up through the tiers, then assert each constant-divisor result -// matches the divide-based reference. -for (let iter = 0; iter < 2000; iter++) { +// matches the divide-based reference. 200 iterations is ~53k calls per +// constant-divisor function, far past the Ion warm-up threshold; more only +// costs emulator time. +for (let iter = 0; iter < 200; iter++) { for (const [fn, d] of cases) { for (const x of inputs) { assertEq(fn(x), refmod(x, d)); @@ -71,7 +73,7 @@ const buf = new Int32Array(64); for (let i = 0; i < buf.length; i++) { buf[i] = Math.imul(i, 2654435761) | 0; } -for (let iter = 0; iter < 5000; iter++) { +for (let iter = 0; iter < 500; iter++) { for (let i = 0; i < 64; i++) { assertEq(pressure(buf, i), refmod(buf[i & 63], 65535)); } diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/bug2008301.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/bug2008301.js --- a/js/src/jit-test/tests/wasm/bug2008301.js 2026-07-15 22:09:45.000000000 +0200 +++ b/js/src/jit-test/tests/wasm/bug2008301.js 2026-07-29 11:29:39.982760213 +0200 @@ -1,4 +1,4 @@ -// |jit-test| --fast-warmup +// |jit-test| --fast-warmup; skip-if: !wasmThreadsEnabled() let v0 = new WebAssembly.Memory({ initial: 7, maximum: 10449, shared: true, address: 'i32' }); // WasmModule Code: diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/bug2018381.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/bug2018381.js --- a/js/src/jit-test/tests/wasm/bug2018381.js 2026-07-15 22:09:45.000000000 +0200 +++ b/js/src/jit-test/tests/wasm/bug2018381.js 2026-07-29 11:29:42.017792555 +0200 @@ -1,4 +1,4 @@ -// |jit-test| --no-threads +// |jit-test| --no-threads; skip-if: !wasmThreadsEnabled() const code = ` (module diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/bug2032943.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/bug2032943.js --- a/js/src/jit-test/tests/wasm/bug2032943.js 2026-07-15 22:09:45.000000000 +0200 +++ b/js/src/jit-test/tests/wasm/bug2032943.js 2026-07-29 11:29:44.320829149 +0200 @@ -1,4 +1,4 @@ -// |jit-test| --fast-warmup +// |jit-test| --fast-warmup; skip-if: !wasmThreadsEnabled() const code = ` (module diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/directiveless/features.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/directiveless/features.js --- a/js/src/jit-test/tests/wasm/directiveless/features.js 2026-07-15 22:09:45.000000000 +0200 +++ b/js/src/jit-test/tests/wasm/directiveless/features.js 2026-07-29 11:36:15.218958177 +0200 @@ -1,6 +1,12 @@ -// |jit-test| include:wasm.js; +// |jit-test| include:wasm.js; skip-if: !wasmCompilersPresent().includes("ion") || !wasmThreadsEnabled() // Do not manually change the default features with a jit-test directive. The // logic below relies on this. +// +// The skip-if above is not a feature change: it only opts out targets that +// cannot satisfy the test's two hardcoded platform assumptions, namely that +// the optimizing compiler is built in ("baseline+ion" below) and that the +// 'threads' feature, which getWasmSupportedFeatures() reports unconditionally, +// is actually usable. // Test that all features are either: // 1. 'disabled' - not enabled by the default flags. diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/limits.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/limits.js --- a/js/src/jit-test/tests/wasm/limits.js 2026-07-15 22:09:44.000000000 +0200 +++ b/js/src/jit-test/tests/wasm/limits.js 2026-07-29 11:31:16.644914673 +0200 @@ -4,11 +4,14 @@ const PageSize = PageSizeInBytes; const MemoryMaxValid = 65536; const MemoryMaxRuntime = MaxPagesIn32BitMemory; +const SharedMemoryInModule = wasmThreadsEnabled(); + const TableMaxValid = 0xffff_ffff; const TableMaxRuntime = 10_000_000; // Test that a memory type is valid within a module function testMemoryValidate(initial, maximum, shared) { + if (shared && !SharedMemoryInModule) return; wasmValidateText(`(module (memory ${initial} ${maximum || ''} ${shared ? 'shared' : ''}) )`); @@ -26,6 +29,7 @@ testMemoryValidate(MemoryMaxValid, Memor // Test that a memory type is not valid within a module function testMemoryFailValidate(initial, maximum, shared, pattern) { + if (shared && !SharedMemoryInModule) return; wasmFailValidateText(`(module (memory ${initial} ${maximum || ''} ${shared ? 'shared' : ''}) )`, pattern); @@ -57,12 +61,14 @@ testMemoryFailConstruct(0, MemoryMaxVali // with a WebAssembly.Memory function testMemoryCreate(initial, maximum, shared) { // May OOM, but must not fail to validate - try { - wasmEvalText(`(module - (memory ${initial} ${maximum || ''} ${shared ? 'shared' : ''}) - )`); - } catch (e) { - assertEq(String(e).indexOf("out of memory") !== -1, true, `${e}`); + if (!shared || SharedMemoryInModule) { + try { + wasmEvalText(`(module + (memory ${initial} ${maximum || ''} ${shared ? 'shared' : ''}) + )`); + } catch (e) { + assertEq(String(e).indexOf("out of memory") !== -1, true, `${e}`); + } } try { new WebAssembly.Memory({initial, maximum, shared}); @@ -86,6 +92,7 @@ testMemoryCreate(MemoryMaxRuntime, Memor if (MemoryMaxRuntime < 65536) { let testMemoryFailCreate = function(initial, maximum, shared) { + if (shared && !SharedMemoryInModule) return; assertErrorMessage(() => wasmEvalText(`(module (memory ${initial} ${maximum || ''} ${shared ? 'shared' : ''}) )`), WebAssembly.RuntimeError, /too many memory pages/); @@ -101,6 +108,7 @@ if (MemoryMaxRuntime < 65536) { testMemoryFailCreate(MemoryMaxRuntime + 1, MemoryMaxValid, true); } else { let testMemoryFailCreate = function(initial, maximum, shared, jsError, jsMsg) { + if (shared && !SharedMemoryInModule) return; assertErrorMessage(() => wasmEvalText(`(module (memory ${initial} ${maximum || ''} ${shared ? 'shared' : ''}) )`), WebAssembly.CompileError, /(initial memory size too big)|(memory size minimum must not be greater than maximum)/); @@ -120,6 +128,7 @@ if (MemoryMaxRuntime < 65536) { // Test that a memory type cannot be grown from initial to a target due to an // implementation limit function testMemoryFailGrow(initial, maximum, target, shared) { + if (shared && !SharedMemoryInModule) return; let {run} = wasmEvalText(`(module (memory ${initial} ${maximum || ''} ${shared ? 'shared' : ''}) (func (export "run") (result i32) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/memory64/basic.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/memory64/basic.js --- a/js/src/jit-test/tests/wasm/memory64/basic.js 2026-07-28 16:38:22.650839731 +0200 +++ b/js/src/jit-test/tests/wasm/memory64/basic.js 2026-07-29 11:33:30.948957670 +0200 @@ -17,18 +17,24 @@ function memoryTypeDescriptor(shared, in }; } function validAndInstantiableMemoryType(shared, initial, max) { - wasmValidateText(memoryTypeModuleText(shared, initial, max)); - wasmEvalText(memoryTypeModuleText(shared, initial, max)); + if (!shared || wasmThreadsEnabled()) { + wasmValidateText(memoryTypeModuleText(shared, initial, max)); + wasmEvalText(memoryTypeModuleText(shared, initial, max)); + } new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max)); } function validButNotInstantiableMemoryType(shared, initial, max, errorMessage) { - wasmValidateText(memoryTypeModuleText(shared, initial, max)); - assertErrorMessage(() => wasmEvalText(memoryTypeModuleText(shared, initial, max)), WebAssembly.RuntimeError, errorMessage); + if (!shared || wasmThreadsEnabled()) { + wasmValidateText(memoryTypeModuleText(shared, initial, max)); + assertErrorMessage(() => wasmEvalText(memoryTypeModuleText(shared, initial, max)), WebAssembly.RuntimeError, errorMessage); + } assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max)), WebAssembly.RuntimeError, errorMessage); } function invalidMemoryType(shared, initial, max, compileMessage, jsMessage) { - wasmFailValidateText(memoryTypeModuleText(shared, initial, max), compileMessage); - assertErrorMessage(() => wasmEvalText(memoryTypeModuleText(shared, initial, max)), WebAssembly.CompileError, compileMessage); + if (!shared || wasmThreadsEnabled()) { + wasmFailValidateText(memoryTypeModuleText(shared, initial, max), compileMessage); + assertErrorMessage(() => wasmEvalText(memoryTypeModuleText(shared, initial, max)), WebAssembly.CompileError, compileMessage); + } assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max)), Error, jsMessage); } @@ -537,142 +543,8 @@ function makeTest(LOC, INITIAL, MAXIMUM, (v128.store32_lane offset=${SMALL} 2 (local.get $p) (call $unstash))) `; - const ins = wasmEvalText(` -(module - (memory (export "mem") i64 ${INITIAL} ${MAXIMUM} ${SHARED}) - - ;; About the test cases: there are various optimizations in the engine - ;; for different shapes of a pointer+offset. Constant pointers are - ;; resolved early; large offsets are folded early using explicit code - ;; with an overflow check (but "large" depends on 32-bit vs 64-bit); - ;; wait/notify fold offsets early regardless; zero offsets lead to - ;; tighter code with variable pointers; and don't get me started on - ;; alignment checks. These test cases are not exhaustive but aim - ;; to test at least some things. - - ;; TODO: more sizes for all operations, though this is not critical - ;; TODO: sign extending loads, again not critical - - ${wasmSimdEnabled() ? v128Prefix : ""} - - ;; Read i32 - (func (export "readi32@0") (param $p i64) (result i32) - (i32.load (local.get $p))) - - (func (export "readi32@small") (param $p i64) (result i32) - (i32.load offset=${SMALL} (local.get $p))) - - (func (export "readi32@big") (param $p i64) (result i32) - (i32.load offset=${BIG} (local.get $p))) - - (func (export "readi32@huge") (param $p i64) (result i32) - (i32.load offset=${HUGE} (local.get $p))) - - (func (export "readi32@vast") (param $p i64) (result i32) - (i32.load offset=${VAST} (local.get $p))) - - (func (export "readi32/const@0") (result i32) - (i32.load (i64.const ${LOC}))) - - (func (export "readi32/const@small") (result i32) - (i32.load offset=${SMALL} (i64.const ${LOC}))) - - (func (export "readi32/const@big") (result i32) - (i32.load offset=${BIG} (i64.const ${LOC}))) - - (func (export "readi32/const@vast") (result i32) - (i32.load offset=${VAST} (i64.const ${LOC}))) - - ;; Read i64 - (func (export "readi64@0") (param $p i64) (result i64) - (i64.load (local.get $p))) - - (func (export "readi64@small") (param $p i64) (result i64) - (i64.load offset=${SMALL} (local.get $p))) - - (func (export "readi64@big") (param $p i64) (result i64) - (i64.load offset=${BIG} (local.get $p))) - - (func (export "readi64@huge") (param $p i64) (result i64) - (i64.load offset=${HUGE} (local.get $p))) - - (func (export "readi64@vast") (param $p i64) (result i64) - (i64.load offset=${VAST} (local.get $p))) - - (func (export "readi64/const@0") (result i64) - (i64.load (i64.const ${LOC}))) - - (func (export "readi64/const@small") (result i64) - (i64.load offset=${SMALL} (i64.const ${LOC}))) - - (func (export "readi64/const@big") (result i64) - (i64.load offset=${BIG} (i64.const ${LOC}))) - - (func (export "readi64/const@vast") (result i64) - (i64.load offset=${VAST} (i64.const ${LOC}))) - - ;; Read v128 - ${wasmSimdEnabled() ? readV128Code : ""} - - ;; write i32 - (func (export "writei32@0") (param $p i64) (param $v i32) - (i32.store (local.get $p) (local.get $v))) - - (func (export "writei32@small") (param $p i64) (param $v i32) - (i32.store offset=${SMALL} (local.get $p) (local.get $v))) - - (func (export "writei32@big") (param $p i64) (param $v i32) - (i32.store offset=${BIG} (local.get $p) (local.get $v))) - - (func (export "writei32@huge") (param $p i64) (param $v i32) - (i32.store offset=${HUGE} (local.get $p) (local.get $v))) - - (func (export "writei32@vast") (param $p i64) (param $v i32) - (i32.store offset=${VAST} (local.get $p) (local.get $v))) - - (func (export "writei32/const@0") (param $v i32) - (i32.store (i64.const ${LOC}) (local.get $v))) - - (func (export "writei32/const@small") (param $v i32) - (i32.store offset=${SMALL} (i64.const ${LOC}) (local.get $v))) - - (func (export "writei32/const@big") (param $v i32) - (i32.store offset=${BIG} (i64.const ${LOC}) (local.get $v))) - - (func (export "writei32/const@vast") (param $v i32) - (i32.store offset=${VAST} (i64.const ${LOC}) (local.get $v))) - - ;; write i64 - (func (export "writei64@0") (param $p i64) (param $v i64) - (i64.store (local.get $p) (local.get $v))) - - (func (export "writei64@small") (param $p i64) (param $v i64) - (i64.store offset=${SMALL} (local.get $p) (local.get $v))) - - (func (export "writei64@big") (param $p i64) (param $v i64) - (i64.store offset=${BIG} (local.get $p) (local.get $v))) - - (func (export "writei64@huge") (param $p i64) (param $v i64) - (i64.store offset=${HUGE} (local.get $p) (local.get $v))) - - (func (export "writei64@vast") (param $p i64) (param $v i64) - (i64.store offset=${VAST} (local.get $p) (local.get $v))) - - (func (export "writei64/const@0") (param $v i64) - (i64.store (i64.const ${LOC}) (local.get $v))) - - (func (export "writei64/const@small") (param $v i64) - (i64.store offset=${SMALL} (i64.const ${LOC}) (local.get $v))) - - (func (export "writei64/const@big") (param $v i64) - (i64.store offset=${BIG} (i64.const ${LOC}) (local.get $v))) - - (func (export "writei64/const@vast") (param $v i64) - (i64.store offset=${VAST} (i64.const ${LOC}) (local.get $v))) - - ;; Read v128 - ${wasmSimdEnabled() ? writeV128Code : ""} - + // Atomic accesses require wasm threads, which not every target supports. + const atomicCode = ` ;; Atomic read i32 (func (export "areadi32@0") (param $p i64) (result i32) @@ -1138,6 +1010,145 @@ function makeTest(LOC, INITIAL, MAXIMUM, (func (export "wake@huge") (param $p i64) (result i32) (memory.atomic.notify offset=${HUGE} (local.get $p) (i32.const 1))) +`; + + const ins = wasmEvalText(` +(module + (memory (export "mem") i64 ${INITIAL} ${MAXIMUM} ${SHARED}) + + ;; About the test cases: there are various optimizations in the engine + ;; for different shapes of a pointer+offset. Constant pointers are + ;; resolved early; large offsets are folded early using explicit code + ;; with an overflow check (but "large" depends on 32-bit vs 64-bit); + ;; wait/notify fold offsets early regardless; zero offsets lead to + ;; tighter code with variable pointers; and don't get me started on + ;; alignment checks. These test cases are not exhaustive but aim + ;; to test at least some things. + + ;; TODO: more sizes for all operations, though this is not critical + ;; TODO: sign extending loads, again not critical + + ${wasmSimdEnabled() ? v128Prefix : ""} + + ;; Read i32 + (func (export "readi32@0") (param $p i64) (result i32) + (i32.load (local.get $p))) + + (func (export "readi32@small") (param $p i64) (result i32) + (i32.load offset=${SMALL} (local.get $p))) + + (func (export "readi32@big") (param $p i64) (result i32) + (i32.load offset=${BIG} (local.get $p))) + + (func (export "readi32@huge") (param $p i64) (result i32) + (i32.load offset=${HUGE} (local.get $p))) + + (func (export "readi32@vast") (param $p i64) (result i32) + (i32.load offset=${VAST} (local.get $p))) + + (func (export "readi32/const@0") (result i32) + (i32.load (i64.const ${LOC}))) + + (func (export "readi32/const@small") (result i32) + (i32.load offset=${SMALL} (i64.const ${LOC}))) + + (func (export "readi32/const@big") (result i32) + (i32.load offset=${BIG} (i64.const ${LOC}))) + + (func (export "readi32/const@vast") (result i32) + (i32.load offset=${VAST} (i64.const ${LOC}))) + + ;; Read i64 + (func (export "readi64@0") (param $p i64) (result i64) + (i64.load (local.get $p))) + + (func (export "readi64@small") (param $p i64) (result i64) + (i64.load offset=${SMALL} (local.get $p))) + + (func (export "readi64@big") (param $p i64) (result i64) + (i64.load offset=${BIG} (local.get $p))) + + (func (export "readi64@huge") (param $p i64) (result i64) + (i64.load offset=${HUGE} (local.get $p))) + + (func (export "readi64@vast") (param $p i64) (result i64) + (i64.load offset=${VAST} (local.get $p))) + + (func (export "readi64/const@0") (result i64) + (i64.load (i64.const ${LOC}))) + + (func (export "readi64/const@small") (result i64) + (i64.load offset=${SMALL} (i64.const ${LOC}))) + + (func (export "readi64/const@big") (result i64) + (i64.load offset=${BIG} (i64.const ${LOC}))) + + (func (export "readi64/const@vast") (result i64) + (i64.load offset=${VAST} (i64.const ${LOC}))) + + ;; Read v128 + ${wasmSimdEnabled() ? readV128Code : ""} + + ;; write i32 + (func (export "writei32@0") (param $p i64) (param $v i32) + (i32.store (local.get $p) (local.get $v))) + + (func (export "writei32@small") (param $p i64) (param $v i32) + (i32.store offset=${SMALL} (local.get $p) (local.get $v))) + + (func (export "writei32@big") (param $p i64) (param $v i32) + (i32.store offset=${BIG} (local.get $p) (local.get $v))) + + (func (export "writei32@huge") (param $p i64) (param $v i32) + (i32.store offset=${HUGE} (local.get $p) (local.get $v))) + + (func (export "writei32@vast") (param $p i64) (param $v i32) + (i32.store offset=${VAST} (local.get $p) (local.get $v))) + + (func (export "writei32/const@0") (param $v i32) + (i32.store (i64.const ${LOC}) (local.get $v))) + + (func (export "writei32/const@small") (param $v i32) + (i32.store offset=${SMALL} (i64.const ${LOC}) (local.get $v))) + + (func (export "writei32/const@big") (param $v i32) + (i32.store offset=${BIG} (i64.const ${LOC}) (local.get $v))) + + (func (export "writei32/const@vast") (param $v i32) + (i32.store offset=${VAST} (i64.const ${LOC}) (local.get $v))) + + ;; write i64 + (func (export "writei64@0") (param $p i64) (param $v i64) + (i64.store (local.get $p) (local.get $v))) + + (func (export "writei64@small") (param $p i64) (param $v i64) + (i64.store offset=${SMALL} (local.get $p) (local.get $v))) + + (func (export "writei64@big") (param $p i64) (param $v i64) + (i64.store offset=${BIG} (local.get $p) (local.get $v))) + + (func (export "writei64@huge") (param $p i64) (param $v i64) + (i64.store offset=${HUGE} (local.get $p) (local.get $v))) + + (func (export "writei64@vast") (param $p i64) (param $v i64) + (i64.store offset=${VAST} (local.get $p) (local.get $v))) + + (func (export "writei64/const@0") (param $v i64) + (i64.store (i64.const ${LOC}) (local.get $v))) + + (func (export "writei64/const@small") (param $v i64) + (i64.store offset=${SMALL} (i64.const ${LOC}) (local.get $v))) + + (func (export "writei64/const@big") (param $v i64) + (i64.store offset=${BIG} (i64.const ${LOC}) (local.get $v))) + + (func (export "writei64/const@vast") (param $v i64) + (i64.store offset=${VAST} (i64.const ${LOC}) (local.get $v))) + + ;; Read v128 + ${wasmSimdEnabled() ? writeV128Code : ""} + + ${wasmThreadsEnabled() ? atomicCode : ""} ) `); return ins; @@ -1593,7 +1604,7 @@ if (getBuildConfiguration("pointer-byte- for ( let shared of ['','shared'] ) { for (let [LOC, start, max] of configs) { - if (shared != '' && max == '') { + if (shared != '' && (max == '' || !wasmThreadsEnabled())) { continue; } const ins = makeTest(LOC, start, max, shared); @@ -1613,16 +1624,18 @@ for ( let shared of ['','shared'] ) { for ( let m of [mem32, mem64] ) { testRead(ins, m, LOC, ""); testWrite(ins, m, LOC, ""); - testRead(ins, m, LOC, "a"); - testWrite(ins, m, LOC, "a"); - testAtomicRMW(ins, m, LOC, "add", (r,s) => r+s); - testAtomicRMW(ins, m, LOC, "sub", (r,s) => r-s); - testAtomicRMW(ins, m, LOC, "and", (r,s) => r&s); - testAtomicRMW(ins, m, LOC, "or", (r,s) => r|s); - testAtomicRMW(ins, m, LOC, "xor", (r,s) => r^s); - testAtomicRMW(ins, m, LOC, "xchg", (r,s) => s); - testAtomicCmpxchg(ins, m, LOC); - testAtomicWake(ins, m, LOC); + if (wasmThreadsEnabled()) { + testRead(ins, m, LOC, "a"); + testWrite(ins, m, LOC, "a"); + testAtomicRMW(ins, m, LOC, "add", (r,s) => r+s); + testAtomicRMW(ins, m, LOC, "sub", (r,s) => r-s); + testAtomicRMW(ins, m, LOC, "and", (r,s) => r&s); + testAtomicRMW(ins, m, LOC, "or", (r,s) => r|s); + testAtomicRMW(ins, m, LOC, "xor", (r,s) => r^s); + testAtomicRMW(ins, m, LOC, "xchg", (r,s) => s); + testAtomicCmpxchg(ins, m, LOC); + testAtomicWake(ins, m, LOC); + } } if (wasmSimdEnabled()) { @@ -1659,6 +1672,7 @@ function makeModule(initial, maximum, sh } for ( let shared of ['','shared'] ) { + if (shared != '' && !wasmThreadsEnabled()) continue; let ins = wasmEvalText(makeModule(1, 3, shared)); assertEq(ins.exports.size(), 1n); @@ -1698,6 +1712,7 @@ for ( let shared of ['','shared'] ) { if (getBuildConfiguration("pointer-byte-size") == 8) { for ( let shared of ['','shared'] ) { + if (shared != '' && !wasmThreadsEnabled()) continue; let limit = wasmMaxMemoryPages('i64'); let initial = 65537; let maximum = limit + 1; diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/memory64/bug1912695.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/memory64/bug1912695.js --- a/js/src/jit-test/tests/wasm/memory64/bug1912695.js 2026-07-15 22:09:44.000000000 +0200 +++ b/js/src/jit-test/tests/wasm/memory64/bug1912695.js 2026-07-29 11:29:49.247654839 +0200 @@ -1,3 +1,5 @@ +// |jit-test| skip-if: !wasmThreadsEnabled() + const m = new WebAssembly.Module(wasmTextToBinary(`(module (memory i64 1 100 shared) (func (param i64) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/passive-segs-partial-mem.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/passive-segs-partial-mem.js --- a/js/src/jit-test/tests/wasm/passive-segs-partial-mem.js 2026-07-15 22:09:44.000000000 +0200 +++ b/js/src/jit-test/tests/wasm/passive-segs-partial-mem.js 2026-07-29 11:30:13.447687385 +0200 @@ -16,7 +16,8 @@ const PAGESIZE = 65536; // should still fill up to the limit. function mem_fill(min, max, shared, backup, write=backup*2) { - if (shared == "shared" && !sharedMemoryEnabled()) + // Shared memory needs wasm threads, which some targets cannot provide. + if (shared == "shared" && !wasmThreadsEnabled()) return; let ins = wasmEvalText( `(module @@ -55,7 +56,8 @@ mem_fill(2, 4, "shared", 257, 0xFFFFFFFF const mem_init_len = 16; function mem_init(min, max, shared, backup, write) { - if (shared == "shared" && !sharedMemoryEnabled()) + // Shared memory needs wasm threads, which some targets cannot provide. + if (shared == "shared" && !wasmThreadsEnabled()) return; let ins = wasmEvalText( `(module @@ -112,7 +114,8 @@ mem_init(1, "", "", PAGESIZE, 0xFFFFFFFC // - both oob function mem_copy(min, max, shared, srcOffs, targetOffs, len) { - if (shared == "shared" && !sharedMemoryEnabled()) + // Shared memory needs wasm threads, which some targets cannot provide. + if (shared == "shared" && !wasmThreadsEnabled()) return; let ins = wasmEvalText( `(module diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/regress/bug1684861.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/regress/bug1684861.js --- a/js/src/jit-test/tests/wasm/regress/bug1684861.js 2026-07-15 22:09:45.000000000 +0200 +++ b/js/src/jit-test/tests/wasm/regress/bug1684861.js 2026-07-29 11:29:51.985950893 +0200 @@ -1,3 +1,5 @@ +// |jit-test| skip-if: !wasmThreadsEnabled() + const oob = /index out of bounds/; const unaligned = /unaligned memory access/; const RuntimeError = WebAssembly.RuntimeError; diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jit-test/tests/wasm/wasm-resizablearraybuffer-shared.js /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jit-test/tests/wasm/wasm-resizablearraybuffer-shared.js --- a/js/src/jit-test/tests/wasm/wasm-resizablearraybuffer-shared.js 2026-07-28 16:38:22.651918300 +0200 +++ b/js/src/jit-test/tests/wasm/wasm-resizablearraybuffer-shared.js 2026-07-29 11:29:46.252859843 +0200 @@ -1,4 +1,4 @@ -// |jit-test| skip-if: !('toResizableBuffer' in WebAssembly.Memory.prototype) +// |jit-test| skip-if: !wasmThreadsEnabled() || !('toResizableBuffer' in WebAssembly.Memory.prototype) let mem = new WebAssembly.Memory({initial: 20, maximum: 50, shared: true}); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/jsapi-tests/testAtomicOperations.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/jsapi-tests/testAtomicOperations.cpp --- a/js/src/jsapi-tests/testAtomicOperations.cpp 2026-07-15 22:09:45.000000000 +0200 +++ b/js/src/jsapi-tests/testAtomicOperations.cpp 2026-07-29 07:53:18.252837549 +0200 @@ -33,9 +33,9 @@ BEGIN_REUSABLE_TEST(testAtomicLockFree8) jit::AtomicOperations::hasAtomic8()); // We must have lock-free 8-byte atomics on every platform where we support - // wasm, but we don't care otherwise. + // wasm threads, but we don't care otherwise. - CHECK(!wasm::HasSupport(cx) || jit::AtomicOperations::isLockfree8()); + CHECK(!wasm::ThreadsAvailable(cx) || jit::AtomicOperations::isLockfree8()); return true; } END_TEST(testAtomicLockFree8) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/main_raw.log /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/main_raw.log --- a/js/src/main_raw.log 1970-01-01 01:00:00.000000000 +0100 +++ b/js/src/main_raw.log 2026-07-29 11:39:32.168003501 +0200 @@ -0,0 +1,52 @@ +{"action": "suite_start", "pid": 201646, "source": "jittests", "tests": [], "thread": "main", "time": 1785317957.7538614} +{"action": "test_start", "jitflags": "--fast-warmup", "pid": 201722, "source": "jittests", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317957.7567387} +{"action": "test_end", "extra": {"jitflags": "--fast-warmup", "pid": 201722}, "jitflags": "--fast-warmup", "message": "Success", "pid": 201722, "source": "jittests", "status": "PASS", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317959.9325056} +{"action": "test_start", "jitflags": "--fast-warmup --wasm-compiler=optimizing", "pid": 202295, "source": "jittests", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317959.9376569} +{"action": "test_end", "extra": {"jitflags": "--fast-warmup --wasm-compiler=optimizing", "pid": 202295}, "jitflags": "--fast-warmup --wasm-compiler=optimizing", "message": "Success", "pid": 202295, "source": "jittests", "status": "PASS", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317961.337936} +{"action": "test_start", "jitflags": "--fast-warmup -P wasm_lazy_tiering --setpref=wasm_lazy_tiering_synchronous", "pid": 202300, "source": "jittests", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317959.9446719} +{"action": "test_end", "extra": {"jitflags": "--fast-warmup -P wasm_lazy_tiering --setpref=wasm_lazy_tiering_synchronous", "pid": 202300}, "jitflags": "--fast-warmup -P wasm_lazy_tiering --setpref=wasm_lazy_tiering_synchronous", "message": "Success", "pid": 202300, "source": "jittests", "status": "PASS", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317961.338003} +{"action": "test_start", "jitflags": "--fast-warmup --test-wasm-await-tier2", "pid": 202299, "source": "jittests", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317959.9417992} +{"action": "test_end", "extra": {"jitflags": "--fast-warmup --test-wasm-await-tier2", "pid": 202299}, "jitflags": "--fast-warmup --test-wasm-await-tier2", "message": "Success", "pid": 202299, "source": "jittests", "status": "PASS", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317961.4066923} +{"action": "test_start", "jitflags": "--fast-warmup --wasm-compiler=baseline", "pid": 202297, "source": "jittests", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317959.9407432} +{"action": "test_end", "extra": {"jitflags": "--fast-warmup --wasm-compiler=baseline", "pid": 202297}, "jitflags": "--fast-warmup --wasm-compiler=baseline", "message": "Success", "pid": 202297, "source": "jittests", "status": "PASS", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317961.6655931} +{"action": "test_start", "jitflags": "--fast-warmup --setpref=wasm_test_serialization=true", "pid": 202544, "source": "jittests", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317961.3416142} +{"action": "test_end", "extra": {"jitflags": "--fast-warmup --setpref=wasm_test_serialization=true", "pid": 202544}, "jitflags": "--fast-warmup --setpref=wasm_test_serialization=true", "message": "Success", "pid": 202544, "source": "jittests", "status": "PASS", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317962.7366843} +{"action": "test_start", "jitflags": "--fast-warmup --wasm-compiler=optimizing --no-avx", "pid": 202556, "source": "jittests", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317961.409646} +{"action": "test_end", "extra": {"jitflags": "--fast-warmup --wasm-compiler=optimizing --no-avx", "pid": 202556}, "jitflags": "--fast-warmup --wasm-compiler=optimizing --no-avx", "message": "Success", "pid": 202556, "source": "jittests", "status": "PASS", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317962.799645} +{"action": "test_start", "jitflags": "--fast-warmup --disable-wasm-huge-memory", "pid": 202540, "source": "jittests", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317961.340644} +{"action": "test_end", "extra": {"jitflags": "--fast-warmup --disable-wasm-huge-memory", "pid": 202540}, "jitflags": "--fast-warmup --disable-wasm-huge-memory", "message": "Success", "pid": 202540, "source": "jittests", "status": "PASS", "test": "wasm/bug2032943.js", "thread": "main", "time": 1785317963.387579} +{"action": "test_start", "jitflags": "", "pid": 202631, "source": "jittests", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317961.6676326} +{"action": "test_end", "extra": {"jitflags": "", "pid": 202631}, "jitflags": "", "message": "Success", "pid": 202631, "source": "jittests", "status": "PASS", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317963.5815876} +{"action": "test_start", "jitflags": "--wasm-compiler=optimizing", "pid": 202751, "source": "jittests", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317962.7406578} +{"action": "test_end", "extra": {"jitflags": "--wasm-compiler=optimizing", "pid": 202751}, "jitflags": "--wasm-compiler=optimizing", "message": "Success", "pid": 202751, "source": "jittests", "status": "PASS", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317963.7965968} +{"action": "test_start", "jitflags": "--wasm-compiler=baseline", "pid": 202756, "source": "jittests", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317962.8018367} +{"action": "test_end", "extra": {"jitflags": "--wasm-compiler=baseline", "pid": 202756}, "jitflags": "--wasm-compiler=baseline", "message": "Success", "pid": 202756, "source": "jittests", "status": "PASS", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317965.0946798} +{"action": "test_start", "jitflags": "--disable-wasm-huge-memory", "pid": 202963, "source": "jittests", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317963.7996442} +{"action": "test_end", "extra": {"jitflags": "--disable-wasm-huge-memory", "pid": 202963}, "jitflags": "--disable-wasm-huge-memory", "message": "Success", "pid": 202963, "source": "jittests", "status": "PASS", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317965.1116722} +{"action": "test_start", "jitflags": "--test-wasm-await-tier2", "pid": 202860, "source": "jittests", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317963.3906202} +{"action": "test_end", "extra": {"jitflags": "--test-wasm-await-tier2", "pid": 202860}, "jitflags": "--test-wasm-await-tier2", "message": "Success", "pid": 202860, "source": "jittests", "status": "PASS", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317965.4375713} +{"action": "test_start", "jitflags": "-P wasm_lazy_tiering --setpref=wasm_lazy_tiering_synchronous", "pid": 202899, "source": "jittests", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317963.5827408} +{"action": "test_end", "extra": {"jitflags": "-P wasm_lazy_tiering --setpref=wasm_lazy_tiering_synchronous", "pid": 202899}, "jitflags": "-P wasm_lazy_tiering --setpref=wasm_lazy_tiering_synchronous", "message": "Success", "pid": 202899, "source": "jittests", "status": "PASS", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317966.2036378} +{"action": "test_start", "jitflags": "--setpref=wasm_test_serialization=true", "pid": 203125, "source": "jittests", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317965.0976276} +{"action": "test_end", "extra": {"jitflags": "--setpref=wasm_test_serialization=true", "pid": 203125}, "jitflags": "--setpref=wasm_test_serialization=true", "message": "Success", "pid": 203125, "source": "jittests", "status": "PASS", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317966.4346447} +{"action": "test_start", "jitflags": "--wasm-compiler=optimizing --no-avx", "pid": 203128, "source": "jittests", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317965.1146016} +{"action": "test_end", "extra": {"jitflags": "--wasm-compiler=optimizing --no-avx", "pid": 203128}, "jitflags": "--wasm-compiler=optimizing --no-avx", "message": "Success", "pid": 203128, "source": "jittests", "status": "PASS", "test": "wasm/limits-br-table.js", "thread": "main", "time": 1785317966.4906256} +{"action": "test_start", "jitflags": "--wasm-compiler=optimizing", "pid": 203329, "source": "jittests", "test": "wasm/limits.js", "thread": "main", "time": 1785317966.206476} +{"action": "test_end", "extra": {"jitflags": "--wasm-compiler=optimizing", "pid": 203329}, "jitflags": "--wasm-compiler=optimizing", "message": "Success", "pid": 203329, "source": "jittests", "status": "PASS", "test": "wasm/limits.js", "thread": "main", "time": 1785317967.186612} +{"action": "test_start", "jitflags": "", "pid": 203228, "source": "jittests", "test": "wasm/limits.js", "thread": "main", "time": 1785317965.4405935} +{"action": "test_end", "extra": {"jitflags": "", "pid": 203228}, "jitflags": "", "message": "Success", "pid": 203228, "source": "jittests", "status": "PASS", "test": "wasm/limits.js", "thread": "main", "time": 1785317969.1199415} +{"action": "test_start", "jitflags": "--test-wasm-await-tier2", "pid": 203409, "source": "jittests", "test": "wasm/limits.js", "thread": "main", "time": 1785317966.4936047} +{"action": "test_end", "extra": {"jitflags": "--test-wasm-await-tier2", "pid": 203409}, "jitflags": "--test-wasm-await-tier2", "message": "Success", "pid": 203409, "source": "jittests", "status": "PASS", "test": "wasm/limits.js", "thread": "main", "time": 1785317969.9896448} +{"action": "test_start", "jitflags": "--wasm-compiler=baseline", "pid": 203390, "source": "jittests", "test": "wasm/limits.js", "thread": "main", "time": 1785317966.4366024} +{"action": "test_end", "extra": {"jitflags": "--wasm-compiler=baseline", "pid": 203390}, "jitflags": "--wasm-compiler=baseline", "message": "Success", "pid": 203390, "source": "jittests", "status": "PASS", "test": "wasm/limits.js", "thread": "main", "time": 1785317970.1996124} +{"action": "test_start", "jitflags": "--disable-wasm-huge-memory", "pid": 203884, "source": "jittests", "test": "wasm/limits.js", "thread": "main", "time": 1785317969.1239529} +{"action": "test_end", "extra": {"jitflags": "--disable-wasm-huge-memory", "pid": 203884}, "jitflags": "--disable-wasm-huge-memory", "message": "Success", "pid": 203884, "source": "jittests", "status": "PASS", "test": "wasm/limits.js", "thread": "main", "time": 1785317970.8997338} +{"action": "test_start", "jitflags": "-P wasm_lazy_tiering --setpref=wasm_lazy_tiering_synchronous", "pid": 203541, "source": "jittests", "test": "wasm/limits.js", "thread": "main", "time": 1785317967.1886024} +{"action": "test_end", "extra": {"jitflags": "-P wasm_lazy_tiering --setpref=wasm_lazy_tiering_synchronous", "pid": 203541}, "jitflags": "-P wasm_lazy_tiering --setpref=wasm_lazy_tiering_synchronous", "message": "Success", "pid": 203541, "source": "jittests", "status": "PASS", "test": "wasm/limits.js", "thread": "main", "time": 1785317971.2156293} +{"action": "test_start", "jitflags": "--setpref=wasm_test_serialization=true", "pid": 204018, "source": "jittests", "test": "wasm/limits.js", "thread": "main", "time": 1785317969.994602} +{"action": "test_end", "extra": {"jitflags": "--setpref=wasm_test_serialization=true", "pid": 204018}, "jitflags": "--setpref=wasm_test_serialization=true", "message": "Success", "pid": 204018, "source": "jittests", "status": "PASS", "test": "wasm/limits.js", "thread": "main", "time": 1785317971.270587} +{"action": "test_start", "jitflags": "--wasm-compiler=optimizing --no-avx", "pid": 204078, "source": "jittests", "test": "wasm/limits.js", "thread": "main", "time": 1785317970.2016447} +{"action": "test_end", "extra": {"jitflags": "--wasm-compiler=optimizing --no-avx", "pid": 204078}, "jitflags": "--wasm-compiler=optimizing --no-avx", "message": "Success", "pid": 204078, "source": "jittests", "status": "PASS", "test": "wasm/limits.js", "thread": "main", "time": 1785317971.3085976} +{"action": "test_start", "jitflags": "", "pid": 204212, "source": "jittests", "test": "wasm/directiveless/features.js", "thread": "main", "time": 1785317970.9025993} +{"action": "test_end", "extra": {"jitflags": "", "pid": 204212}, "jitflags": "", "message": "Success", "pid": 204212, "source": "jittests", "status": "PASS", "test": "wasm/directiveless/features.js", "thread": "main", "time": 1785317972.1686172} +{"action": "suite_end", "pid": 201646, "source": "jittests", "thread": "main", "time": 1785317972.1688974} diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/util/Poison.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/util/Poison.h --- a/js/src/util/Poison.h 2026-07-28 16:38:22.643014135 +0200 +++ b/js/src/util/Poison.h 2026-07-28 18:50:20.568593042 +0200 @@ -92,7 +92,7 @@ const uint8_t JS_SCOPE_DATA_TRAILING_NAM #elif defined(JS_CODEGEN_RISCV64) # define JS_SWEPT_CODE_PATTERN \ 0x29 // illegal sb instruction, crashes in user mode. -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) # define JS_SWEPT_CODE_PATTERN 0x00 // illegal instruction (all zeros) #else # error "JS_SWEPT_CODE_PATTERN not defined for this platform" diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/vm/BoundFunctionObject.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/vm/BoundFunctionObject.h --- a/js/src/vm/BoundFunctionObject.h 2026-07-15 22:09:47.000000000 +0200 +++ b/js/src/vm/BoundFunctionObject.h 2026-07-29 08:05:38.001960082 +0200 @@ -148,6 +148,17 @@ class BoundFunctionObject : public Nativ static constexpr size_t offsetOfFlagsSlot() { return getFixedSlotOffset(FlagsSlot); } + + // The flags are a boxed Int32Value; JIT code that reads them with a 32-bit + // load must address the payload word, which is at +sizeof(int32_t) within + // the 8-byte slot on big-endian and at +0 on little-endian. + static constexpr size_t offsetOfFlagsSlotForJit32() { +#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + return offsetOfFlagsSlot() + sizeof(int32_t); +#else + return offsetOfFlagsSlot(); +#endif + } static constexpr size_t offsetOfBoundThisSlot() { return getFixedSlotOffset(BoundThisSlot); } diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmBCDefs.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmBCDefs.h --- a/js/src/wasm/WasmBCDefs.h 2026-07-28 16:38:22.643088348 +0200 +++ b/js/src/wasm/WasmBCDefs.h 2026-07-28 18:49:07.588607168 +0200 @@ -154,6 +154,14 @@ enum class RhsDestOp { True = true }; # define RABALDR_PIN_INSTANCE #endif +#ifdef JS_CODEGEN_PPC +# define RABALDR_PIN_INSTANCE +// 32-bit PowerPC has no 64-bit divide and no fcfid/fctidz. +# define RABALDR_INT_DIV_I64_CALLOUT +# define RABALDR_I64_TO_FLOAT_CALLOUT +# define RABALDR_FLOAT_TO_I64_CALLOUT +#endif + #ifdef JS_CODEGEN_PPC64 # define RABALDR_PIN_INSTANCE #endif diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmBCFrame.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmBCFrame.h --- a/js/src/wasm/WasmBCFrame.h 2026-07-28 16:38:22.660200498 +0200 +++ b/js/src/wasm/WasmBCFrame.h 2026-07-29 08:27:28.915621045 +0200 @@ -879,15 +879,16 @@ class BaseStackFrame final : public Base } void loadStackI32(int32_t offset, RegI32 dest) { - // An i32 is spilled into a pointer-sized (8-byte) stack slot by pushGPR, so - // on big-endian its value occupies the high-address low word at +4. Reading - // at +0 would pick up the always-zero high word. + // An i32 is spilled into a pointer-sized stack slot by pushGPR. On a + // big-endian target whose pointers are wider than 32 bits the value is in + // the high-address low word; where a pointer is one word the slot is the + // value itself and no adjustment applies. #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - masm.load32(Address(sp_, stackOffset(offset) + int32_t(sizeof(int32_t))), - dest); + constexpr int32_t lowWord = int32_t(StackSizeOfPtr - sizeof(int32_t)); #else - masm.load32(Address(sp_, stackOffset(offset)), dest); + constexpr int32_t lowWord = 0; #endif + masm.load32(Address(sp_, stackOffset(offset) + lowWord), dest); } void loadStackI64(int32_t offset, RegI64 dest) { diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmBCMemory.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmBCMemory.cpp --- a/js/src/wasm/WasmBCMemory.cpp 2026-07-28 16:38:22.660340853 +0200 +++ b/js/src/wasm/WasmBCMemory.cpp 2026-07-29 10:21:27.159642603 +0200 @@ -345,9 +345,10 @@ void BaseCompiler::boundsCheckBelow4GBAc // we can use a 32-bit check and avoid extension and wrapping. uint32_t offset = instanceOffsetOfBoundsCheckLimit(memoryIndex, byteSize); #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - // The bounds check limit is a pointer-sized field; the 32-bit check must - // read its low word. - offset += sizeof(uint32_t); + // The bounds check limit is a pointer-sized field; on a big-endian host with + // pointers wider than the 32-bit load, its low word is at the far end. On a + // 32-bit host the field is exactly one word, so this adjustment is zero. + offset += sizeof(uintptr_t) - sizeof(uint32_t); #endif masm.wasmBoundsCheck32(Assembler::Below, ptr, Address(instance, offset), ok); } @@ -648,7 +649,7 @@ void BaseCompiler::executeLoad(MemoryAcc } else { masm.wasmLoad(*access, memoryBase, ptr, dest.any()); } -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) MOZ_ASSERT(temp.isInvalid()); if (dest.tag == AnyReg::I64) { masm.wasmLoadI64(*access, memoryBase, ptr, ptr, dest.i64()); @@ -806,6 +807,17 @@ void BaseCompiler::executeStore(MemoryAc } else { masm.wasmStore(*access, src.any(), memoryBase, ptr, ptr); } +#elif defined(JS_CODEGEN_PPC) + MOZ_ASSERT(temp.isInvalid()); + if (access->type() == Scalar::Int64) { + masm.wasmStoreI64(*access, src.i64(), memoryBase, ptr, ptr); + } else if (src.tag == AnyReg::I64) { + // i64.store8/16/32 narrows an I64 pair; the low half holds the stored bits + // on big endian too. + masm.wasmStore(*access, AnyRegister(src.i64().low), memoryBase, ptr, ptr); + } else { + masm.wasmStore(*access, src.any(), memoryBase, ptr, ptr); + } #else MOZ_CRASH("BaseCompiler platform hook: store"); #endif @@ -1118,6 +1130,14 @@ static void Deallocate(BaseCompiler* bc, bc->freeI32(bc->specific_.ecx); } +#elif defined(JS_CODEGEN_PPC) + +static void Allocate(BaseCompiler* bc, RegI64* rd, RegI64*) { + *rd = bc->needI64(); +} + +static void Deallocate(BaseCompiler* bc, RegI64) {} + #elif defined(__wasi__) || (defined(JS_CODEGEN_NONE) && !defined(JS_64BIT)) static void Allocate(BaseCompiler*, RegI64*, RegI64*) {} @@ -1350,7 +1370,7 @@ static void Deallocate(BaseCompiler* bc, bc->maybeFree(temps.t2); } -#elif defined(JS_CODEGEN_RISCV64) +#elif defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) struct Temps { RegI32 t0, t1, t2; @@ -1523,7 +1543,8 @@ static void Deallocate(BaseCompiler* bc, } #elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) static void PopAndAllocate(BaseCompiler* bc, AtomicOp op, RegI64* rd, RegI64* rv, RegI64* temp) { @@ -1733,7 +1754,7 @@ static void Deallocate(BaseCompiler* bc, bc->maybeFree(temps.t2); } -#elif defined(JS_CODEGEN_RISCV64) +#elif defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) struct Temps { RegI32 t0, t1, t2; @@ -1864,7 +1885,8 @@ static void Deallocate(BaseCompiler* bc, } #elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) static void PopAndAllocate(BaseCompiler* bc, RegI64* rd, RegI64* rv) { *rv = bc->popI64(); @@ -2080,7 +2102,7 @@ static void Deallocate(BaseCompiler* bc, bc->maybeFree(temps.t2); } -#elif defined(JS_CODEGEN_RISCV64) +#elif defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) struct Temps { RegI32 t0, t1, t2; @@ -2308,7 +2330,8 @@ static void Deallocate(BaseCompiler* bc, } #elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) template static void PopAndAllocate(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew, diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmBCRegDefs.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmBCRegDefs.h --- a/js/src/wasm/WasmBCRegDefs.h 2026-07-28 16:38:22.643317239 +0200 +++ b/js/src/wasm/WasmBCRegDefs.h 2026-07-28 18:53:10.352943083 +0200 @@ -118,7 +118,7 @@ static constexpr Register RabaldrScratch static constexpr Register RabaldrScratchI32 = CallTempReg2; #endif -#ifdef JS_CODEGEN_PPC64 +#if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) # define RABALDR_SCRATCH_I32 // Use r25 (callee-saved, non-arg, not used by any wasm infrastructure) // instead of CallTempReg2 (r10) which is IntArgReg7. @@ -387,7 +387,7 @@ struct SpecificRegs { edx_eax(RegI64(Register64(js::jit::edx, js::jit::eax))), abiReturnRegI64(edx_eax) {} }; -#elif defined(JS_CODEGEN_ARM) +#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_PPC) struct SpecificRegs { RegI64 abiReturnRegI64; diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmBCStkMgmt-inl.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmBCStkMgmt-inl.h --- a/js/src/wasm/WasmBCStkMgmt-inl.h 2026-07-15 22:09:48.000000000 +0200 +++ b/js/src/wasm/WasmBCStkMgmt-inl.h 2026-07-29 10:20:31.686017752 +0200 @@ -379,6 +379,14 @@ void BaseCompiler::sync() { #ifdef JS_PUNBOX64 loadI64(v, fromI32(scratch)); uint32_t offs = fr.pushGPR(scratch); +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // The stack grows down, so pushing the low half first leaves the high + // half at the lower address, which is what the readers of MemI64 + // assume on big endian (INT64HIGH_OFFSET == 0). + fr.loadLocalI64Low(localFromSlot(v.slot(), MIRType::Int64), scratch); + fr.pushGPR(scratch); + fr.loadLocalI64High(localFromSlot(v.slot(), MIRType::Int64), scratch); + uint32_t offs = fr.pushGPR(scratch); #else fr.loadLocalI64High(localFromSlot(v.slot(), MIRType::Int64), scratch); fr.pushGPR(scratch); @@ -392,6 +400,10 @@ void BaseCompiler::sync() { #ifdef JS_PUNBOX64 uint32_t offs = fr.pushGPR(v.i64reg().reg); freeI64(v.i64reg()); +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + fr.pushGPR(v.i64reg().low); + uint32_t offs = fr.pushGPR(v.i64reg().high); + freeI64(v.i64reg()); #else fr.pushGPR(v.i64reg().high); uint32_t offs = fr.pushGPR(v.i64reg().low); @@ -847,6 +859,10 @@ void BaseCompiler::popI64(const Stk& v, case Stk::MemI64: #ifdef JS_PUNBOX64 fr.popGPR(dest.reg); +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // Matches the low-then-high push order used by sync(). + fr.popGPR(dest.high); + fr.popGPR(dest.low); #else fr.popGPR(dest.low); fr.popGPR(dest.high); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmBaselineCompile.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmBaselineCompile.cpp --- a/js/src/wasm/WasmBaselineCompile.cpp 2026-07-28 16:38:22.660787824 +0200 +++ b/js/src/wasm/WasmBaselineCompile.cpp 2026-07-29 10:20:56.012711172 +0200 @@ -377,11 +377,12 @@ void BaseCompiler::tableSwitch(Label* th masm.ma_ldr(DTRAddr(scratch, DtrRegImmShift(switchValue, LSL, 2)), pc, Offset, Assembler::Always); #elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) ScratchI32 scratch(*this); CodeLabel tableCl; -# if defined(JS_CODEGEN_PPC64) +# if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) masm.mov(&tableCl, scratch); # else masm.ma_li(scratch, &tableCl); @@ -903,7 +904,8 @@ void BaseCompiler::insertBreakablePoint( masm.append(CallSiteDesc(iter_.lastOpcodeOffset(), kind), CodeOffset(masm.currentOffset())); #elif defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) ScratchPtr scratch(*this); Label L; masm.loadPtr(Address(InstanceReg, Instance::offsetOfDebugStub()), scratch); @@ -979,7 +981,8 @@ void BaseCompiler::insertPerFunctionDebu masm.ma_bx(lr, Assembler::Zero); } #elif defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) { ScratchPtr scratch(*this); @@ -2200,7 +2203,9 @@ bool BaseCompiler::updateCallRefMetrics( // if (target->instance != thisInstance) goto Out-Of-Line const size_t instanceSlotOffset = FunctionExtended::offsetOfExtendedSlot( FunctionExtended::WASM_INSTANCE_SLOT); - masm.loadPtr(Address(regFuncRef, instanceSlotOffset), regScratch); + masm.loadPtr( + Address(regFuncRef, instanceSlotOffset + jit::PrivateValuePayloadOffset), + regScratch); masm.branchPtr(Assembler::NotEqual, InstanceReg, regScratch, ool->entry()); // At this point, regFuncRef = the FuncExtended*, regMetrics = the @@ -2396,7 +2401,7 @@ RegI32 BaseCompiler::needRotate64Temp() #elif defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || \ defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) return RegI32::Invalid(); #else MOZ_CRASH("BaseCompiler platform hook: needRotate64Temp"); @@ -2446,7 +2451,7 @@ void BaseCompiler::popAndAllocateForMulI *temp = needI32(); #elif defined(JS_CODEGEN_MIPS64) pop2xI64(r0, r1); -#elif defined(JS_CODEGEN_ARM) +#elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_PPC) pop2xI64(r0, r1); *temp = needI32(); #elif defined(JS_CODEGEN_ARM64) @@ -2888,7 +2893,8 @@ static RegI32 PopcntTemp(BaseCompiler& b return AssemblerX86Shared::HasPOPCNT() ? RegI32::Invalid() : bc.needI32(); #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_RISCV64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC) + // The 603 has neither popcntw nor popcntd; always give it a temp. return bc.needI32(); #elif defined(JS_CODEGEN_PPC64) // PPC64 has native popcntd/popcntw; no temp register needed. @@ -3094,9 +3100,11 @@ static void NegateF64(MacroAssembler& ma masm.negateDouble(rsd); } +#ifndef JS_CODEGEN_PPC static void SqrtF64(MacroAssembler& masm, RegF64 rsd) { masm.sqrtDouble(rsd, rsd); } +#endif static void AddF32(MacroAssembler& masm, RegF32 rs, RegF32 rsd) { masm.addFloat32(rs, rsd); @@ -3161,9 +3169,11 @@ static void NegateF32(MacroAssembler& ma masm.negateFloat(rsd); } +#ifndef JS_CODEGEN_PPC static void SqrtF32(MacroAssembler& masm, RegF32 rsd) { masm.sqrtFloat32(rsd, rsd); } +#endif #ifndef RABALDR_I64_TO_FLOAT_CALLOUT static void ConvertI64ToF32(MacroAssembler& masm, RegI64 rs, RegF32 rd) { @@ -4715,7 +4725,8 @@ bool BaseCompiler::emitTryTable() { RegPtr data = needPtr(); // Unpack the tag and jump to the block - masm.loadPtr(Address(exn, (int32_t)WasmExceptionObject::offsetOfData()), + masm.loadPtr(Address(exn, (int32_t)WasmExceptionObject::offsetOfData() + + jit::PrivateValuePayloadOffset), data); // This method can increase stk_.length() by an unbounded amount, so we need // to perform an allocation here to accomodate the variable number of @@ -4927,7 +4938,8 @@ bool BaseCompiler::emitCatch() { RegRef exn = popRef(); RegPtr data = needPtr(); - masm.loadPtr(Address(exn, (int32_t)WasmExceptionObject::offsetOfData()), + masm.loadPtr(Address(exn, (int32_t)WasmExceptionObject::offsetOfData() + + jit::PrivateValuePayloadOffset), data); // This method can increase stk_.length() by an unbounded amount, so we need @@ -5237,7 +5249,9 @@ bool BaseCompiler::emitThrow() { RegPtr data = needPtr(); freePtr(RegPtr(PreBarrierReg)); - masm.loadPtr(Address(exn, WasmExceptionObject::offsetOfData()), data); + masm.loadPtr(Address(exn, (int32_t)WasmExceptionObject::offsetOfData() + + jit::PrivateValuePayloadOffset), + data); for (int32_t i = params.length() - 1; i >= 0; i--) { uint32_t offset = offsets[i]; @@ -6188,8 +6202,8 @@ bool BaseCompiler::emitGetGlobal() { RegV128 rv = needV128(); ScratchPtr tmp(*this); masm.loadUnalignedSimd128(addressOfGlobalVar(global, tmp), rv); -#if defined(JS_CODEGEN_PPC64) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#if (defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC)) && \ + defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ // The global cell holds the little-endian image; byte-reverse to the // canonical SIMD register order. masm.byteReverseSimd128(rv, rv); @@ -6268,8 +6282,8 @@ bool BaseCompiler::emitSetGlobal() { case ValType::V128: { RegV128 rv = popV128(); ScratchPtr tmp(*this); -#if defined(JS_CODEGEN_PPC64) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#if (defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC)) && \ + defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ // The global cell holds the little-endian image; byte-reverse the // canonical SIMD register before storing. rv is dead after the store. masm.byteReverseSimd128(rv, rv); @@ -9398,7 +9412,7 @@ static void MulI64x2(MacroAssembler& mas RegV128 temp1, RegV128 temp2) { masm.mulInt64x2(rsd, rs, rsd, temp1, temp2); } -# elif defined(JS_CODEGEN_PPC64) +# elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) static void MulI64x2(MacroAssembler& masm, RegV128 rs, RegV128 rsd, RegV128 temp1, RegV128 temp2) { masm.mulInt64x2(rsd, rs, rsd, temp1, temp2); @@ -9418,7 +9432,7 @@ static void DivF64x2(MacroAssembler& mas } # if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) static void MinF32x4(MacroAssembler& masm, RegV128 rs, RegV128 rsd, RegV128 temp1, RegV128 temp2) { masm.minFloat32x4(rsd, rs, rsd, temp1, temp2); @@ -9439,8 +9453,8 @@ static void MaxF64x2(MacroAssembler& mas masm.maxFloat64x2(rsd, rs, rsd, temp1, temp2); } -# if defined(JS_CODEGEN_PPC64) -// PPC64: use non-RhsDestOp convention (first=rhs, second=lhsDest), +# if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) +// PPC: use non-RhsDestOp convention (first=rhs, second=lhsDest), // matching the pseudoMin/Max function signature. static void PMinF32x4(MacroAssembler& masm, RegV128 rs, RegV128 rsd) { masm.pseudoMinFloat32x4(rs, rsd); @@ -9870,8 +9884,8 @@ static void ShiftRightUI64x2(MacroAssemb ShiftOpMask(masm, SimdOp::I64x2ShrU, rs, temp); masm.unsignedRightShiftInt64x2(rsd, temp, rsd); } -# elif defined(JS_CODEGEN_PPC64) -// PPC64: same as ARM64 pattern (shift amount in GPR, result in vector reg) +# elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) +// PPC: same as ARM64 pattern (shift amount in GPR, result in vector reg) static void ShiftLeftI8x16(MacroAssembler& masm, RegI32 rs, RegV128 rsd, RegI32 temp) { ShiftOpMask(masm, SimdOp::I8x16Shl, rs, temp); @@ -10232,7 +10246,7 @@ static void BitmaskI64x2(MacroAssembler& RegV128 temp) { masm.bitmaskInt64x2(rs, rd, temp); } -# elif defined(JS_CODEGEN_PPC64) +# elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) static void BitmaskI8x16(MacroAssembler& masm, RegV128 rs, RegI32 rd, RegV128 temp) { masm.bitmaskInt8x16(rs, rd, temp); @@ -10320,7 +10334,7 @@ static void BitselectV128(MacroAssembler masm.bitwiseSelectSimd128(lhsDest, rhs, temp); masm.moveSimd128(temp, lhsDest); } -# elif defined(JS_CODEGEN_PPC64) +# elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) static void BitselectV128(MacroAssembler& masm, RegV128 rhs, RegV128 control, RegV128 lhsDest, RegV128 temp) { masm.moveSimd128(control, temp); @@ -10402,7 +10416,8 @@ void BaseCompiler::emitDotI8x16I7x16AddS RegV128 rsd = popV128(); RegV128 rs0, rs1; pop2xV128(&rs0, &rs1); -# if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) +# if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) RegV128 temp = needV128(); masm.dotInt8x16Int7x16ThenAdd(rs0, rs1, rsd, temp); freeV128(temp); @@ -10608,7 +10623,8 @@ bool BaseCompiler::emitVectorLaneSelect( freeV128(lhs); freeV128(mask); pushV128(rhsDest); -# elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) +# elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) RegV128 maskDest = popV128(); RegV128 rhs = popV128(); RegV128 lhs = popV128(); @@ -11202,7 +11218,14 @@ bool BaseCompiler::emitBody() { case uint16_t(Op::F32Abs): CHECK_NEXT(dispatchUnary1(AbsF32, ValType::F32)); case uint16_t(Op::F32Sqrt): +#ifdef JS_CODEGEN_PPC + // No fsqrt/fsqrts on the 603, so masm.sqrtFloat32 is an unaligned ABI + // call, which is illegal inside a wasm frame. + CHECK_NEXT( + emitUnaryMathBuiltinCall(SymbolicAddress::SqrtF, ValType::F32)); +#else CHECK_NEXT(dispatchUnary1(SqrtF32, ValType::F32)); +#endif case uint16_t(Op::F32Ceil): CHECK_NEXT( emitUnaryMathBuiltinCall(SymbolicAddress::CeilF, ValType::F32)); @@ -11278,7 +11301,12 @@ bool BaseCompiler::emitBody() { case uint16_t(Op::F64Abs): CHECK_NEXT(dispatchUnary1(AbsF64, ValType::F64)); case uint16_t(Op::F64Sqrt): +#ifdef JS_CODEGEN_PPC + CHECK_NEXT( + emitUnaryMathBuiltinCall(SymbolicAddress::SqrtD, ValType::F64)); +#else CHECK_NEXT(dispatchUnary1(SqrtF64, ValType::F64)); +#endif case uint16_t(Op::F64Ceil): CHECK_NEXT( emitUnaryMathBuiltinCall(SymbolicAddress::CeilD, ValType::F64)); @@ -12773,7 +12801,8 @@ bool js::wasm::BaselinePlatformSupport() #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || \ defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64) || \ defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_PPC) return true; #else return false; diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmBuiltins.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmBuiltins.cpp --- a/js/src/wasm/WasmBuiltins.cpp 2026-07-15 22:44:59.000000000 +0200 +++ b/js/src/wasm/WasmBuiltins.cpp 2026-07-29 07:57:59.996312038 +0200 @@ -120,6 +120,12 @@ constexpr SymbolicAddressSignature SASig SymbolicAddress::FloorD, _F64, _Infallible, _NoTrap, 1, {_F64, _END}}; constexpr SymbolicAddressSignature SASigFloorF = { SymbolicAddress::FloorF, _F32, _Infallible, _NoTrap, 1, {_F32, _END}}; +#if defined(JS_CODEGEN_PPC) +constexpr SymbolicAddressSignature SASigSqrtD = { + SymbolicAddress::SqrtD, _F64, _Infallible, _NoTrap, 1, {_F64, _END}}; +constexpr SymbolicAddressSignature SASigSqrtF = { + SymbolicAddress::SqrtF, _F32, _Infallible, _NoTrap, 1, {_F32, _END}}; +#endif constexpr SymbolicAddressSignature SASigTruncD = { SymbolicAddress::TruncD, _F64, _Infallible, _NoTrap, 1, {_F64, _END}}; constexpr SymbolicAddressSignature SASigTruncF = { @@ -1363,6 +1369,18 @@ static T Floor(T value) { return std::floor(value); } +#if defined(JS_CODEGEN_PPC) +template +static T Sqrt(T value) { + // Perform addition to ensure quiet NaNs are returned. Also try to keep the + // NaN payload intact, so don't directly return a specific quiet NaN value. + if (std::isnan(value)) { + return value + value; + } + return std::sqrt(value); +} +#endif + template static T Trunc(T value) { // Perform addition to ensure quiet NaNs are returned. Also try to keep the @@ -1583,6 +1601,14 @@ void* wasm::AddressOf(SymbolicAddress im case SymbolicAddress::FloorF: *abiType = Args_Float32_Float32; return FuncCast(Floor, *abiType); +#if defined(JS_CODEGEN_PPC) + case SymbolicAddress::SqrtD: + *abiType = Args_Double_Double; + return FuncCast(Sqrt, *abiType); + case SymbolicAddress::SqrtF: + *abiType = Args_Float32_Float32; + return FuncCast(Sqrt, *abiType); +#endif case SymbolicAddress::TruncD: *abiType = Args_Double_Double; return FuncCast(Trunc, *abiType); @@ -1975,6 +2001,10 @@ bool wasm::NeedsBuiltinThunk(SymbolicAdd case SymbolicAddress::CeilF: case SymbolicAddress::FloorD: case SymbolicAddress::FloorF: +#if defined(JS_CODEGEN_PPC) + case SymbolicAddress::SqrtD: + case SymbolicAddress::SqrtF: +#endif case SymbolicAddress::TruncD: case SymbolicAddress::TruncF: case SymbolicAddress::NearbyIntD: diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmBuiltins.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmBuiltins.h --- a/js/src/wasm/WasmBuiltins.h 2026-07-15 22:44:59.000000000 +0200 +++ b/js/src/wasm/WasmBuiltins.h 2026-07-29 07:57:01.561591422 +0200 @@ -61,6 +61,13 @@ enum class SymbolicAddress { CeilF, FloorD, FloorF, +#if defined(JS_CODEGEN_PPC) + // The 603 has no fsqrt/fsqrts, so wasm f32.sqrt/f64.sqrt must go through a + // builtin call rather than MacroAssembler::sqrt{Float32,Double}, which are + // themselves unaligned ABI calls and illegal inside a wasm frame. + SqrtD, + SqrtF, +#endif TruncD, TruncF, NearbyIntD, @@ -238,6 +245,10 @@ extern const SymbolicAddressSignature SA extern const SymbolicAddressSignature SASigCeilF; extern const SymbolicAddressSignature SASigFloorD; extern const SymbolicAddressSignature SASigFloorF; +#if defined(JS_CODEGEN_PPC) +extern const SymbolicAddressSignature SASigSqrtD; +extern const SymbolicAddressSignature SASigSqrtF; +#endif extern const SymbolicAddressSignature SASigTruncD; extern const SymbolicAddressSignature SASigTruncF; extern const SymbolicAddressSignature SASigNearbyIntD; diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmCodegenConstants.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmCodegenConstants.h --- a/js/src/wasm/WasmCodegenConstants.h 2026-07-28 16:38:22.644036122 +0200 +++ b/js/src/wasm/WasmCodegenConstants.h 2026-07-28 18:52:47.194745222 +0200 @@ -43,8 +43,8 @@ static const unsigned InterpFailInstance // The following thresholds were derived from a microbenchmark. If we begin to // ship this optimization for more platforms, we will need to extend this list. -#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64) || \ - defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64) || \ + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) static const uint32_t MaxInlineMemoryCopyLength = 64; static const uint32_t MaxInlineMemoryFillLength = 64; #elif defined(JS_CODEGEN_X86) diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmCodegenTypes.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmCodegenTypes.cpp --- a/js/src/wasm/WasmCodegenTypes.cpp 2026-07-28 16:38:22.644070314 +0200 +++ b/js/src/wasm/WasmCodegenTypes.cpp 2026-07-28 18:52:47.194784573 +0200 @@ -147,7 +147,7 @@ void TrapSitesForKind::checkInvariants(c # if (defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || \ defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_ARM) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_PPC64)) + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC)) // Check that each trapsite is associated with a plausible instruction. The // required instruction kind depends on the trapsite kind. // diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmCompile.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmCompile.cpp --- a/js/src/wasm/WasmCompile.cpp 2026-07-28 16:38:22.644137836 +0200 +++ b/js/src/wasm/WasmCompile.cpp 2026-07-28 18:49:07.592303428 +0200 @@ -74,8 +74,11 @@ uint32_t wasm::ObservedCPUFeatures() { LOONG64 = 0x7, RISCV64 = 0x8, PPC64 = 0x9, + // Not PPC: the 32-bit PowerPC compiler predefines that as an object-like + // macro, which would rewrite the enumerator here. + PPC32 = 0xa, - LAST = PPC64, + LAST = PPC32, ARCH_BITS = 4 }; @@ -107,6 +110,9 @@ uint32_t wasm::ObservedCPUFeatures() { #elif defined(JS_CODEGEN_PPC64) MOZ_ASSERT(jit::GetPPC64Flags() <= (UINT32_MAX >> ARCH_BITS)); return PPC64 | (jit::GetPPC64Flags() << ARCH_BITS); +#elif defined(JS_CODEGEN_PPC) + MOZ_ASSERT(jit::GetPPCFlags() <= (UINT32_MAX >> ARCH_BITS)); + return PPC32 | (jit::GetPPCFlags() << ARCH_BITS); #elif defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32) return 0; #else diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmDebugFrame.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmDebugFrame.h --- a/js/src/wasm/WasmDebugFrame.h 2026-07-15 22:09:47.000000000 +0200 +++ b/js/src/wasm/WasmDebugFrame.h 2026-07-28 19:28:57.230802557 +0200 @@ -102,9 +102,10 @@ class DebugFrame { // Avoid -Wunused-private-field warnings. protected: -#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_X86) || defined(__wasi__) - // See alignmentStaticAsserts(). For ARM32 and X86 DebugFrame is only - // 4-byte aligned, so we add another word to get up to 8-byte +#if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_X86) || \ + defined(JS_CODEGEN_PPC) || defined(__wasi__) + // See alignmentStaticAsserts(). For ARM32, X86 and PPC32 DebugFrame is + // only 4-byte aligned, so we add another word to get up to 8-byte // alignment. uint32_t padding_; #endif diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmFeatures.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmFeatures.cpp --- a/js/src/wasm/WasmFeatures.cpp 2026-07-28 16:38:22.661156651 +0200 +++ b/js/src/wasm/WasmFeatures.cpp 2026-07-29 07:52:57.698584069 +0200 @@ -240,14 +240,16 @@ bool wasm::SimdAvailable(JSContext* cx) } bool wasm::ThreadsAvailable(JSContext* cx) { - return WasmThreadsFlag(cx) && AnyCompilerAvailable(cx); + // Wasm threads require 8-byte lock-free atomics. + return WasmThreadsFlag(cx) && jit::AtomicOperations::isLockfree8() && + AnyCompilerAvailable(cx); } bool wasm::HasPlatformSupport() { -#ifndef JS_CODEGEN_PPC64 - // PPC64 is the only big-endian target with the byte-swapping linear-memory - // support needed for wasm's little-endian memory model; other big-endian - // targets are unsupported. +#if !defined(JS_CODEGEN_PPC64) && !defined(JS_CODEGEN_PPC) + // PPC64 and PPC are the only big-endian targets with the byte-swapping + // linear-memory support needed for wasm's little-endian memory model; other + // big-endian targets are unsupported. if constexpr (std::endian::native != std::endian::little) { return false; } @@ -269,10 +271,9 @@ bool wasm::HasPlatformSupport() { return false; } - // Wasm threads require 8-byte lock-free atomics. - if (!jit::AtomicOperations::isLockfree8()) { - return false; - } + // Note: 8-byte lock-free atomics are required for wasm threads only, and are + // checked in ThreadsAvailable() rather than here. Without them shared + // memories and every ThreadPrefix opcode are rejected during validation. // Test only whether the compilers are supported on the hardware, not whether // they are enabled. diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmFrameIter.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmFrameIter.cpp --- a/js/src/wasm/WasmFrameIter.cpp 2026-07-28 16:38:22.644262132 +0200 +++ b/js/src/wasm/WasmFrameIter.cpp 2026-07-29 07:58:09.271426418 +0200 @@ -622,7 +622,7 @@ static const unsigned PushedFP = 16; static const unsigned SetFP = 20; static const unsigned PoppedFP = 4; static const unsigned PoppedFPJitEntry = 8; -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) // pushReturnAddress = mflr(4) + stdu(4) = 8 bytes. // push(FP) = stdu(4) = 4 bytes (PPC64 stdu is a single DS-form instruction). // moveStackPtrTo = mr(4) = 4 bytes. @@ -726,7 +726,7 @@ static void GenerateCallablePrologue(Mac masm.moveStackPtrTo(FramePointer); MOZ_ASSERT_IF(!masm.oom(), SetFP == masm.currentOffset() - *entry); } -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) { *entry = masm.currentOffset(); @@ -833,7 +833,7 @@ static void GenerateCallableEpilogue(Mac masm.jalr(zero, ra, 0); masm.nop(); } -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) // Load RA and FP from the Frame while it's still on the stack. // Using r0 (js::jit::r0) for RA is safe: it's volatile, used as // RT (not base), and we're in an epilogue where it's not live. @@ -1553,7 +1553,7 @@ void wasm::GenerateJitEntryPrologue(Macr AutoForbidPoolsAndNops afp(&masm, 10); offsets->begin = masm.currentOffset(); masm.push(ra); -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) offsets->begin = masm.currentOffset(); masm.pushReturnAddress(); #elif defined(JS_CODEGEN_ARM64) @@ -1607,7 +1607,7 @@ void wasm::GenerateJitEntryEpilogue(Macr masm.Ret(ARMRegister(lr, 64)); masm.setFramePushed(0); } -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) // Load RA and FP from the frame while it's still on the stack, then // restore LR, pop the frame, and return. mtlr must precede addi so LR // holds the caller's RA across the whole post-poppedFP window (see @@ -2012,7 +2012,7 @@ bool js::wasm::StartUnwinding(const Regi fixedFP = fp; AssertMatchesCallSite(fixedPC, fixedFP); } else -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) if (codeRange->isThunk()) { // The FarJumpIsland sequence temporary scrambles the link register. fixedPC = pc; @@ -2079,7 +2079,7 @@ bool js::wasm::StartUnwinding(const Regi fixedPC = Frame::fromUntaggedWasmExitFP(sp)->returnAddress(); fixedFP = fp; AssertMatchesCallSite(fixedPC, fixedFP); -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) } else if (offsetInCode >= codeRange->ret() - PoppedFP && offsetInCode < codeRange->ret()) { // PPC64 epilogue (RA loaded into r0, FP restored, RA not yet @@ -2488,6 +2488,12 @@ const char* wasm::ThunkedNativeToDescrip return "call to native f64.floor (in wasm)"; case SymbolicAddress::FloorF: return "call to native f32.floor (in wasm)"; +#if defined(JS_CODEGEN_PPC) + case SymbolicAddress::SqrtD: + return "call to native f64.sqrt (in wasm)"; + case SymbolicAddress::SqrtF: + return "call to native f32.sqrt (in wasm)"; +#endif case SymbolicAddress::TruncD: return "call to native f64.trunc (in wasm)"; case SymbolicAddress::TruncF: diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmGC.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmGC.cpp --- a/js/src/wasm/WasmGC.cpp 2026-07-28 16:38:22.644375437 +0200 +++ b/js/src/wasm/WasmGC.cpp 2026-07-28 18:52:47.194906249 +0200 @@ -444,7 +444,7 @@ bool wasm::IsPlausibleStackMapKey(const insn[-1] == 0x00000013 /* addi zero, zero, 0 */) || // jal; nop (insn[-1] == 0x00100073 && (insn[-2] & kITypeMask) == RO_CSRRWI))); // wasm trap -# elif defined(JS_CODEGEN_PPC64) +# elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) const uint32_t* insn = reinterpret_cast(nextPC); MOZ_ASSERT((uintptr_t(insn) & 3) == 0); // xs_trap() = tw 31,r0,r0 (PPC_trap); bctrl = PPC_bctr|1; bl = I-form diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmGenerator.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmGenerator.cpp --- a/js/src/wasm/WasmGenerator.cpp 2026-07-28 16:38:22.644450981 +0200 +++ b/js/src/wasm/WasmGenerator.cpp 2026-07-28 18:53:47.583238172 +0200 @@ -931,9 +931,9 @@ bool ModuleGenerator::finishCodeBlock(Co callFarJumps_.clear(); // None of the linking or far-jump operations should emit masm metadata, - // except on PPC64 where patchFarJump uses addLongJump to create CodeLabels + // except on PPC where patchFarJump uses addLongJump to create CodeLabels // for absolute-address far jumps. Drain those into linkData_ here. -#ifdef JS_CODEGEN_PPC64 +#if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) for (const jit::CodeLabel& codeLabel : masm_->codeLabels()) { LinkData::InternalLink link; link.patchAtOffset = codeLabel.patchAt().offset(); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmInstance.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmInstance.cpp --- a/js/src/wasm/WasmInstance.cpp 2026-07-28 16:38:22.661315698 +0200 +++ b/js/src/wasm/WasmInstance.cpp 2026-07-29 10:24:35.098775192 +0200 @@ -238,11 +238,12 @@ static bool UnpackResults(JSContext* cx, bool mustWrite64 = result_size == 8; #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ // The wasm caller reads an i32 stack result from the low word of its - // pointer-sized slot, at byte offset +4. ToWebAssemblyValue's mustWrite64 - // widening assumes little-endian layout, so write the 32-bit value - // directly at the right offset instead. + // pointer-sized slot, which on big endian is at the far end of the slot. + // The shift is zero where a pointer is 4 bytes and the slot is exactly the + // i32. ToWebAssemblyValue's mustWrite64 widening assumes little-endian + // layout, so write the 32-bit value directly at the right offset instead. if (result.type().kind() == ValType::I32) { - loc += sizeof(int32_t); + loc += ABIResult::StackSizeOfInt32 - sizeof(int32_t); mustWrite64 = false; } #endif @@ -3619,10 +3620,11 @@ bool wasm::ResultsToJSValue(JSContext* c if (result.onStack()) { char* loc = stackResultsLoc.value() + result.stackOffset(); #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - // An i32 stack result occupies a pointer-sized slot and is written as a - // 64-bit store, so its value is the low word at byte offset +4. + // An i32 stack result occupies a pointer-sized slot; on big endian its + // low word is at the far end of the slot. Zero where a pointer is 4 + // bytes. if (result.type().kind() == ValType::I32) { - loc += sizeof(int32_t); + loc += ABIResult::StackSizeOfInt32 - sizeof(int32_t); } #endif if (!ToJSValue(cx, loc, result.type(), &tmp, level)) { diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmIonCompile.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmIonCompile.cpp --- a/js/src/wasm/WasmIonCompile.cpp 2026-07-28 16:38:22.661791270 +0200 +++ b/js/src/wasm/WasmIonCompile.cpp 2026-07-28 18:49:07.590360959 +0200 @@ -984,9 +984,10 @@ class FunctionCompiler { rhs = rhs2; } - // For x86 and arm we implement i64 div via c++ builtin. + // For x86, arm and ppc we implement i64 div via c++ builtin. // A call to c++ builtin requires instance pointer. -#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM) +#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM) || \ + defined(JS_CODEGEN_PPC) if (type == MIRType::Int64) { auto* ins = MWasmBuiltinDivI64::New(alloc(), lhs, rhs, instancePointer_, unsignd, trapOnError, trapSiteDesc()); @@ -1025,9 +1026,10 @@ class FunctionCompiler { rhs = rhs2; } - // For x86 and arm we implement i64 mod via c++ builtin. + // For x86, arm and ppc we implement i64 mod via c++ builtin. // A call to c++ builtin requires instance pointer. -#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM) +#if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_ARM) || \ + defined(JS_CODEGEN_PPC) if (type == MIRType::Int64) { auto* ins = MWasmBuiltinModI64::New(alloc(), lhs, rhs, instancePointer_, unsignd, trapOnError, trapSiteDesc()); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmSignalHandlers.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmSignalHandlers.cpp --- a/js/src/wasm/WasmSignalHandlers.cpp 2026-07-28 16:38:22.645146032 +0200 +++ b/js/src/wasm/WasmSignalHandlers.cpp 2026-07-29 07:54:31.891745681 +0200 @@ -163,6 +163,15 @@ using namespace js::wasm; # define R32_sig(p) ((p)->uc_mcontext.gp_regs[32]) # define R36_sig(p) ((p)->uc_mcontext.gp_regs[36]) # endif +# if defined(__linux__) && defined(__powerpc__) && !defined(__powerpc64__) +// On 32-bit PowerPC glibc the registers are not stored inline in ucontext_t: +// uc_mcontext is a union of pointers into uc_reg_space. The gregs[] index +// layout matches the 64-bit gp_regs[] one (nip == 32, link == 36). +# define R01_sig(p) ((p)->uc_mcontext.uc_regs->gregs[1]) +# define R31_sig(p) ((p)->uc_mcontext.uc_regs->gregs[31]) +# define R32_sig(p) ((p)->uc_mcontext.uc_regs->gregs[32]) +# define R36_sig(p) ((p)->uc_mcontext.uc_regs->gregs[36]) +# endif # if defined(__linux__) && defined(__loongarch__) # define EPC_sig(p) ((p)->uc_mcontext.__pc) # define RRA_sig(p) ((p)->uc_mcontext.__gregs[1]) @@ -417,7 +426,7 @@ struct macos_aarch64_context { # define SP_sig(p) RSP_sig(p) # define LR_sig(p) R31_sig(p) # elif defined(__ppc64__) || defined(__PPC64__) || defined(__ppc64le__) || \ - defined(__PPC64LE__) + defined(__PPC64LE__) || defined(__powerpc__) || defined(__PPC__) # define PC_sig(p) R32_sig(p) # define SP_sig(p) R01_sig(p) # define FP_sig(p) R31_sig(p) @@ -468,7 +477,8 @@ static uint8_t* ContextToSP(CONTEXT* con # if defined(__arm__) || defined(__aarch64__) || defined(__mips__) || \ defined(__loongarch__) || defined(__riscv) || \ - defined(__ppc64__) || defined(__PPC64__) + defined(__ppc64__) || defined(__PPC64__) || defined(__powerpc__) || \ + defined(__PPC__) static uint8_t* ContextToLR(CONTEXT* context) { # ifdef LR_sig return mozilla::BitwiseCast(LR_sig(context)); @@ -486,7 +496,8 @@ static JS::ProfilingFrameIterator::Regis state.sp = ContextToSP(context); # if defined(__arm__) || defined(__aarch64__) || defined(__mips__) || \ defined(__loongarch__) || defined(__riscv) || \ - defined(__ppc64__) || defined(__PPC64__) + defined(__ppc64__) || defined(__PPC64__) || defined(__powerpc__) || \ + defined(__PPC__) state.lr = ContextToLR(context); # else state.lr = (void*)UINTPTR_MAX; @@ -787,8 +798,9 @@ static void MachExceptionHandlerThread() # if defined(__mips__) || defined(__loongarch__) static const uint32_t kWasmTrapSignal = SIGFPE; -# elif defined(__ppc64__) || defined(__PPC64__) || \ - defined(__ppc64le__) || defined(__PPC64LE__) +# elif defined(__ppc64__) || defined(__PPC64__) || \ + defined(__ppc64le__) || defined(__PPC64LE__) || \ + defined(__powerpc__) || defined(__PPC__) static const uint32_t kWasmTrapSignal = SIGTRAP; # else static const uint32_t kWasmTrapSignal = SIGILL; diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmStubs.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmStubs.cpp --- a/js/src/wasm/WasmStubs.cpp 2026-07-28 16:38:22.662128835 +0200 +++ b/js/src/wasm/WasmStubs.cpp 2026-07-29 10:21:06.142336802 +0200 @@ -414,8 +414,8 @@ static void SetupABIArguments(MacroAssem // wasmLosslessInvoke, and is guarded against in normal JS-API // call paths. masm.loadUnalignedSimd128(src, iter->fpu()); -# if defined(JS_CODEGEN_PPC64) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# if (defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC)) && \ + defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ // The ExportArg slot holds the little-endian image; byte-reverse // the raw load into the canonical register value. masm.byteReverseSimd128(iter->fpu(), iter->fpu()); @@ -493,6 +493,18 @@ static void SetupABIArguments(MacroAssem masm.as_ldbrx(dataTmp, src.base, addrTmp); masm.storePtr(dataTmp, Address(masm.getStackPointer(), dstOff)); } +# elif defined(JS_CODEGEN_PPC) && defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + // Same reversal, but ldbrx is a 64-bit instruction that does not + // exist here, so go through the vector unit instead. + { + ScratchSimd128Scope fpscratch(masm); + masm.loadUnalignedSimd128(src, fpscratch); + masm.byteReverseSimd128(fpscratch, fpscratch); + masm.storeUnalignedSimd128( + fpscratch, + Address(masm.getStackPointer(), iter->offsetFromArgBase())); + } # else ScratchSimd128Scope fpscratch(masm); masm.loadUnalignedSimd128(src, fpscratch); @@ -540,8 +552,8 @@ static void StoreRegisterResult(MacroAss break; case ValType::V128: #ifdef ENABLE_WASM_SIMD -# if defined(JS_CODEGEN_PPC64) && defined(__BYTE_ORDER__) && \ - __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +# if (defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC)) && \ + defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ // The result slot holds the little-endian image; byte-reverse the // canonical register value before the raw store. { @@ -694,7 +706,7 @@ static bool GenerateInterpEntry(MacroAss #ifdef JS_USE_LINK_REGISTER # if defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) masm.pushReturnAddress(); # elif defined(JS_CODEGEN_ARM64) // WasmPush updates framePushed() unlike pushReturnAddress(), but that's @@ -2179,7 +2191,7 @@ static bool GenerateImportInterpExit(Mac #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM) || \ defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_RISCV64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) MOZ_ASSERT(NonVolatileRegs.has(HeapReg)); #endif @@ -2733,16 +2745,17 @@ static const LiveRegisterSet RegsToPrese # ifdef ENABLE_WASM_SIMD # error "high lanes of SIMD registers need to be saved too." # endif -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) // Exclude r0 (ScratchRegister, not allocatable, special addressing semantics), -// r1 (SP), r2 (TOC pointer, reserved), and r13 (TLS pointer, reserved). +// r1 (SP), r2 (TOC pointer on ppc64, TLS pointer on ppc32; reserved), and r13 +// (TLS pointer on ppc64, small-data-area base on ppc32; reserved). static const LiveRegisterSet RegsToPreserve( GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::r0) | (uint32_t(1) << Registers::r1) | (uint32_t(1) << Registers::r2) | (uint32_t(1) << Registers::r13))), # ifdef ENABLE_WASM_SIMD - // Unlike ARM64, where the vector registers alias the doubles, PPC64 + // Unlike ARM64, where the vector registers alias the doubles, PowerPC // doubles live in the FPRs (VSR0-31) while wasm v128 values live in the // VRs (VSR32-63) -- two disjoint physical pools, so both must be // preserved. Saving only the doubles loses the entire live v128 state: a @@ -3328,7 +3341,9 @@ static bool GenerateUpdateCallRefMetrics Label after2; const size_t offsetOfInstanceSlot = FunctionExtended::offsetOfExtendedSlot( FunctionExtended::WASM_INSTANCE_SLOT); - masm.loadPtr(Address(regFuncRef, offsetOfInstanceSlot), regScratch); + masm.loadPtr(Address(regFuncRef, + offsetOfInstanceSlot + jit::PrivateValuePayloadOffset), + regScratch); masm.branchPtr(Assembler::Equal, InstanceReg, regScratch, &after2); // const size_t offsetOfCountOther = CallRefMetrics::offsetOfCountOther(); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/js/src/wasm/WasmSummarizeInsn.cpp /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/js/src/wasm/WasmSummarizeInsn.cpp --- a/js/src/wasm/WasmSummarizeInsn.cpp 2026-07-28 16:38:22.662304434 +0200 +++ b/js/src/wasm/WasmSummarizeInsn.cpp 2026-07-28 18:52:47.195159511 +0200 @@ -1733,7 +1733,7 @@ Maybe SummarizeTrapInst // ================================================================== ppc64 ==== -# elif defined(JS_CODEGEN_PPC64) +# elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_PPC) Maybe SummarizeTrapInstruction(const uint8_t* insnAddr) { MOZ_ASSERT(0 == (uintptr_t(insnAddr) & 3)); diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/mfbt/Assertions.h /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/mfbt/Assertions.h --- a/mfbt/Assertions.h 2026-07-28 16:38:22.645763660 +0200 +++ b/mfbt/Assertions.h 2026-07-28 16:49:01.260520404 +0200 @@ -287,6 +287,11 @@ static inline void MOZ_CrashSequence(voi "std %1,0(%0);\n" // Write the line number to the crashing address : // no output registers : "r"(aAddress), "r"(aLine)); +# elif defined(__powerpc__) + asm volatile( + "stw %1,0(%0);\n" // Write the line number to the crashing address + : // no output registers + : "r"(aAddress), "r"(aLine)); # else # warning \ "Unsupported architecture, replace the code below with assembly suitable to crash the process" diff -Nurp -p '--exclude=*.orig' '--exclude=*.rej' '--exclude=*.pyc' '--exclude=__pycache__' /srv/t2/tmp/ppc-jit-work/baseline/config/check_macroassembler_style.py /srv/t2/src-firefox.ppc-desktop.260728.163810.1997595/firefox-153.0/config/check_macroassembler_style.py --- a/config/check_macroassembler_style.py 2026-07-28 16:38:22.629200706 +0200 +++ b/config/check_macroassembler_style.py 2026-07-28 16:52:11.034164314 +0200 @@ -33,6 +33,7 @@ all_architecture_names = set([ "arm64", "loong64", "riscv64", + "ppc", "ppc64", "wasm32", ]) @@ -42,6 +43,7 @@ all_shared_architecture_names = set([ "arm64", "loong64", "riscv64", + "ppc", "ppc64", "wasm32", ])