Add a SPARC64 (SPARC V9) JIT backend to SpiderMonkey Ported from the ppc64be backend. SPARC V9 is big-endian LP64 with a sliding register window; this backend treats it as a flat register machine, executing a single `save` in EnterJIT and never again, so every Ion/Baseline frame shares one window. That keeps SpiderMonkey's own frame layout, bailout machinery and safepoint encoding working unchanged. New backend in js/src/jit/sparc64/; the remaining changes teach shared code about JS_CODEGEN_SPARC64 and fix places where it assumed properties SPARC does not have. Architecture-specific points that differ from every other backend: - %sp carries the SPARC 2047-byte stack bias. The 176-byte register-window save area, which the kernel may spill into asynchronously on any window overflow deeper in the call chain, is folded into the stack access base (SP_BASE = 2223) so the reserve holds at all times rather than only across ABI calls. The bias is an address-space conversion: both the source and the destination of an operation decide whether it applies. - FramePointer is %l7, not %i6. After `save`, %i6 is physically the caller's %o6; writing it would corrupt the caller's stack pointer and misdirect its window spill. - Every control transfer has a delay slot, which the assembler fills. - Unaligned access traps, so the Baseline Interpreter's bytecode operand reads and the character-wise string helpers are synthesized from byte loads (CopyStringChars and branchIfNotStringCharsEquals are gated on Assembler::SupportsUnalignedAccesses()). - The FP register index is written verbatim into the V9 5-bit double field, which decodes as %d((f & 0x1E) | ((f & 1) << 5)). Single index i is %f(i), the high word of Double (i & 0x1E), so odd Single indices are non-allocatable and a FLOAT32 definition may not be satisfied by a Double-kind register. - Small aggregates passed by value are left-justified in the argument register, and the whole argument list shares one slot array: slot n is %o(n) or %d(2n). - The hardware NaN is 0x7FFFFFFFFFFFFFFF, which once negated is not a valid JS::Value double encoding, so double -> Value conversions canonicalize. wasm SIMD is not supported: VIS is a 64-bit partitioned-arithmetic unit, not a 128-bit lane machine. VIS3 movxtod/movdtox are used for GPR<->FPR moves behind a HasVIS3() runtime gate, with a memory round-trip fallback. Tested under qemu-sparc64 (T2 sysroot). jit-test basic at the default flag matrix, at --blinterp-eager --no-baseline --no-ion, and at --ion-eager --no-threads each show only basic/segmenter-atomref.js, which needs Intl and fails identically on an interpreter-only reference build. Octane pdf.js scores 229 / 133 / 106 by tier against an interpreter-only reference of 13.1. Signed-off-by: René Rebe --- firefox-153.0/config/check_macroassembler_style.py 2026-07-30 07:43:05.718624861 +0200 +++ firefox-153.0/config/check_macroassembler_style.py 2026-07-29 19:53:28.850585405 +0200 @@ -34,6 +34,7 @@ all_architecture_names = set([ "loong64", "riscv64", "ppc64", + "sparc64", "wasm32", ]) all_shared_architecture_names = set([ @@ -43,6 +44,7 @@ all_shared_architecture_names = set([ "loong64", "riscv64", "ppc64", + "sparc64", "wasm32", ]) --- firefox-153.0/js/moz.configure 2026-07-30 07:43:05.718709935 +0200 +++ firefox-153.0/js/moz.configure 2026-07-29 19:43:10.074342346 +0200 @@ -245,6 +245,7 @@ def jit_default(target, enable_portable_ "loongarch64", "ppc64", "riscv64", + "sparc64", ): return True return False @@ -323,6 +324,8 @@ def jit_codegen(jit_enabled, simulator, return namespace(ppc64=True) elif target.cpu == "riscv64": return namespace(riscv64=True) + elif target.cpu == "sparc64": + return namespace(sparc64=True) return namespace(**{str(target.cpu): True}) @@ -334,6 +337,7 @@ set_config("JS_CODEGEN_MIPS64", jit_code set_config("JS_CODEGEN_LOONG64", jit_codegen.loong64) set_config("JS_CODEGEN_PPC64", jit_codegen.ppc64) set_config("JS_CODEGEN_RISCV64", jit_codegen.riscv64) +set_config("JS_CODEGEN_SPARC64", jit_codegen.sparc64) set_config("JS_CODEGEN_X86", jit_codegen.x86) set_config("JS_CODEGEN_X64", jit_codegen.x64) set_config("JS_CODEGEN_WASM32", jit_codegen.wasm32) @@ -345,6 +349,7 @@ set_define("JS_CODEGEN_MIPS64", jit_code set_define("JS_CODEGEN_LOONG64", jit_codegen.loong64) set_define("JS_CODEGEN_PPC64", jit_codegen.ppc64) set_define("JS_CODEGEN_RISCV64", jit_codegen.riscv64) +set_define("JS_CODEGEN_SPARC64", jit_codegen.sparc64) set_define("JS_CODEGEN_X86", jit_codegen.x86) set_define("JS_CODEGEN_X64", jit_codegen.x64) set_define("JS_CODEGEN_WASM32", jit_codegen.wasm32) @@ -1393,8 +1398,8 @@ set_config( @depends("--enable-simulator", target) def wasm_has_heapreg(simulator, target): - #if target.cpu != "x86": - return True + if target.cpu != "x86": + return True if simulator and simulator[0] == "arm": return True --- firefox-153.0/js/src/builtin/TestingFunctions.cpp 2026-07-30 07:43:05.745441548 +0200 +++ firefox-153.0/js/src/builtin/TestingFunctions.cpp 2026-07-29 19:48:48.292791355 +0200 @@ -502,6 +502,15 @@ static bool GetBuildConfiguration(JSCont return false; } +#ifdef JS_CODEGEN_SPARC64 + value = BooleanValue(true); +#else + value = BooleanValue(false); +#endif + if (!JS_SetProperty(cx, info, "sparc64", value)) { + return false; + } + #ifdef MOZ_ASAN value = BooleanValue(true); #else --- firefox-153.0/js/src/irregexp/RegExpAPI.cpp 2026-07-30 07:43:05.745879668 +0200 +++ firefox-153.0/js/src/irregexp/RegExpAPI.cpp 2026-07-29 19:48:54.033883133 +0200 @@ -495,8 +495,8 @@ class RegExpDepthCheck final : public v8 // This size is picked to be comfortably larger than any // RegExp*::ToNode stack frame. -#if defined(__powerpc64__) - // PPC64 ELFv2 has larger minimum stack frames. +#if defined(__powerpc64__) || (defined(__sparc__) && defined(__arch64__)) + // PPC64 ELFv2 and SPARC V9 have larger minimum stack frames. static const size_t FRAME_PADDING = 256 * 4; #elif !defined(DEBUG) && !defined(MOZ_CODE_COVERAGE) static const size_t FRAME_PADDING = 256; --- firefox-153.0/js/src/jit/Assembler.h 2026-07-30 07:43:05.720274516 +0200 +++ firefox-153.0/js/src/jit/Assembler.h 2026-07-29 19:44:13.700207889 +0200 @@ -21,6 +21,8 @@ # include "jit/riscv64/Assembler-riscv64.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/Assembler-ppc64.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/Assembler-sparc64.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/Assembler-wasm32.h" #elif defined(JS_CODEGEN_NONE) --- firefox-153.0/js/src/jit/BaselineCodeGen.cpp 2026-07-30 07:43:05.748021436 +0200 +++ firefox-153.0/js/src/jit/BaselineCodeGen.cpp 2026-07-29 22:54:53.459933838 +0200 @@ -495,7 +495,7 @@ static void LoadUint8Operand(MacroAssemb static void LoadUint16Operand(MacroAssembler& masm, Register dest) { Register pc = LoadBytecodePC(masm, dest); - masm.load16ZeroExtend(Address(pc, sizeof(jsbytecode)), dest); + masm.loadUnaligned16ZeroExtend(Address(pc, sizeof(jsbytecode)), dest); SwapBytecodeOperandToNative16(masm, dest); } @@ -514,7 +514,7 @@ static void LoadConstantCompareOperand(M static void LoadInt32Operand(MacroAssembler& masm, Register dest) { Register pc = LoadBytecodePC(masm, dest); - masm.load32(Address(pc, sizeof(jsbytecode)), dest); + masm.loadUnaligned32(Address(pc, sizeof(jsbytecode)), dest); SwapBytecodeOperandToNative32(masm, dest); } @@ -523,10 +523,10 @@ static void LoadInt32OperandSignExtendTo #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ // The operand is little-endian; byteSwap32 byte-reverses and sign-extends the // low word to 64 bits, i.e. exactly the sign-extend-to-ptr we want. - masm.load32(Address(pc, sizeof(jsbytecode)), dest); + masm.loadUnaligned32(Address(pc, sizeof(jsbytecode)), dest); masm.byteSwap32(dest); #else - masm.load32SignExtendToPtr(Address(pc, sizeof(jsbytecode)), dest); + masm.loadUnaligned32SignExtendToPtr(Address(pc, sizeof(jsbytecode)), dest); #endif } @@ -534,7 +534,7 @@ static void LoadUint24Operand(MacroAssem Register dest) { // Load the opcode and operand, then left shift to discard the opcode. Register pc = LoadBytecodePC(masm, dest); - masm.load32(Address(pc, offset), dest); + masm.loadUnaligned32(Address(pc, offset), dest); // Swap to native first so the opcode byte ends up in the low 8 bits, then // shift it out, leaving the 24-bit operand. SwapBytecodeOperandToNative32(masm, dest); @@ -542,9 +542,8 @@ static void LoadUint24Operand(MacroAssem } static void LoadInlineValueOperand(MacroAssembler& masm, ValueOperand dest) { - // Note: the Value might be unaligned but as above we rely on all our - // platforms having appropriate support for unaligned accesses (except for - // floating point instructions on ARM). + // Note: the Value might be unaligned, so this goes through the + // alignment-safe load (a plain load except where the hardware traps). Register pc = LoadBytecodePC(masm, dest.scratchReg()); masm.loadUnalignedValue(Address(pc, sizeof(jsbytecode)), dest); #if defined(JS_PUNBOX64) && defined(__BYTE_ORDER__) && \ @@ -4326,6 +4325,11 @@ bool BaselineCodeGen::emit_Glob prepareVMCall(); loadInt32LengthBytecodeOperand(R0.scratchReg()); +#ifdef JS_CODEGEN_SPARC64 + // GCThingIndex is a 4-byte aggregate passed by value, and the SPARC V9 ABI + // passes such aggregates left-justified in the argument register. + masm.lshiftPtr(Imm32(32), R0.scratchReg()); +#endif pushArg(R0.scratchReg()); pushScriptArg(); masm.loadPtr(frame.addressOfEnvironmentChain(), R0.scratchReg()); @@ -5828,7 +5832,7 @@ void BaselineInterpreterCodeGen::emitGet // Jump to default if val > high. The low/high operands are little-endian in // the bytecode, so byte-swap to native on big-endian before comparing. #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ - masm.load32(highAddr, scratch2); + masm.loadUnaligned32(highAddr, scratch2); masm.byteSwap32(scratch2); masm.branch32(Assembler::LessThan, scratch2, dest, &jumpToDefault); #else @@ -5836,7 +5840,7 @@ void BaselineInterpreterCodeGen::emitGet #endif // Jump to default if val < low. - masm.load32(lowAddr, scratch2); + masm.loadUnaligned32(lowAddr, scratch2); SwapBytecodeOperandToNative32(masm, scratch2); masm.branch32(Assembler::GreaterThan, scratch2, dest, &jumpToDefault); --- firefox-153.0/js/src/jit/CacheIRCompiler.cpp 2026-07-30 07:43:05.749394310 +0200 +++ firefox-153.0/js/src/jit/CacheIRCompiler.cpp 2026-07-30 00:33:33.489194546 +0200 @@ -6417,7 +6417,7 @@ void CacheIRCompiler::emitActivateIterat #endif // Mark iterator as active. - Address iterFlagsAddr(nativeIter, NativeIterator::offsetOfFlags()); + Address iterFlagsAddr(nativeIter, NativeIterator::offsetOfFlagsForJit32()); masm.storePtr(objBeingIterated, iterObjAddr); masm.or32(Imm32(NativeIterator::flagForJit32(NativeIterator::Flags::Active)), iterFlagsAddr); @@ -10319,6 +10319,12 @@ bool CacheIRCompiler::emitConcatStringsR #ifdef JS_USE_LINK_REGISTER liveRegs.add(ICTailCallReg); #endif +#ifdef JS_CODEGEN_SPARC64 + // On SPARC the link register is %o7, which ICTailCallReg cannot be (see + // SharedICRegisters-sparc64.h). The inner call clobbers it, and both + // EmitReturnFromIC and EmitRestoreTailCallReg read it afterwards. + liveRegs.add(LinkRegister); +#endif liveRegs.takeUnchecked(output.valueReg()); #ifdef JS_CODEGEN_PPC64 --- firefox-153.0/js/src/jit/CodeGenerator.cpp 2026-07-30 07:43:05.750271130 +0200 +++ firefox-153.0/js/src/jit/CodeGenerator.cpp 2026-07-29 23:22:38.632463353 +0200 @@ -13587,7 +13587,9 @@ static void CopyStringChars(MacroAssembl toEncoding == CharEncoding::Latin1 ? sizeof(char) : sizeof(char16_t); // Try to copy multiple characters at once when both encoding are equal. - if (fromEncoding == toEncoding) { + // This reads and writes at character granularity, so it is only valid where + // unaligned accesses are; SPARC traps on them. + if (fromEncoding == toEncoding && Assembler::SupportsUnalignedAccesses()) { constexpr size_t ptrWidth = sizeof(uintptr_t); // Copy |width| bytes and then adjust |from| and |to|. @@ -18340,7 +18342,7 @@ void CodeGenerator::visitObjectToIterato Address(iterObj, PropertyIteratorObject::offsetOfIteratorSlot()), nativeIter); - Address iterFlagsAddr(nativeIter, NativeIterator::offsetOfFlags()); + Address iterFlagsAddr(nativeIter, NativeIterator::offsetOfFlagsForJit32()); if (lir->mir()->wantsIndices()) { // At least one consumer of the output of this iterator has been optimized // to use iterator indices. If the cached iterator doesn't include indices, @@ -18405,7 +18407,7 @@ void CodeGenerator::emitIteratorHasIndic PropertyIteratorObject::offsetOfIteratorSlot()); masm.loadPrivate(nativeIterAddr, temp); masm.branchTest32(Assembler::Zero, - Address(temp, NativeIterator::offsetOfFlags()), + Address(temp, NativeIterator::offsetOfFlagsForJit32()), Imm32(NativeIterator::flagForJit32( NativeIterator::Flags::IndicesAvailable)), ifFalse); --- firefox-153.0/js/src/jit/CodeGenerator.h 2026-07-30 07:43:05.722405664 +0200 +++ firefox-153.0/js/src/jit/CodeGenerator.h 2026-07-29 19:44:16.078249536 +0200 @@ -25,6 +25,8 @@ # include "jit/riscv64/CodeGenerator-riscv64.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/CodeGenerator-ppc64.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/CodeGenerator-sparc64.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/CodeGenerator-wasm32.h" #elif defined(JS_CODEGEN_NONE) --- firefox-153.0/js/src/jit/EffectiveAddressAnalysis.cpp 2026-07-30 07:43:05.722437185 +0200 +++ firefox-153.0/js/src/jit/EffectiveAddressAnalysis.cpp 2026-07-29 19:48:06.843124539 +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_PPC64) || \ + defined(JS_CODEGEN_SPARC64) return imm >= -0xFFF && imm <= 0xFFF; #elif defined(JS_CODEGEN_WASM32) || defined(JS_CODEGEN_NONE) return true; --- firefox-153.0/js/src/jit/ExecutableAllocator.cpp 2026-07-30 07:43:05.722473537 +0200 +++ firefox-153.0/js/src/jit/ExecutableAllocator.cpp 2026-07-29 19:48:38.704637768 +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 - // execution when code regions are reused after sweeping. + // incoherent ICache (PPC64, SPARC64), 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_PPC64) || defined(JS_CODEGEN_SPARC64) reprotectPool(rt, pool, ProtectionSetting::Executable, MustFlushICache::Yes); #else --- firefox-153.0/js/src/jit/FlushICache.h 2026-07-30 07:43:05.722515749 +0200 +++ firefox-153.0/js/src/jit/FlushICache.h 2026-07-29 19:45:02.820060155 +0200 @@ -19,9 +19,10 @@ namespace jit { inline void FlushICache(void* code, size_t size) { // No-op. Code and data caches are coherent on x86 and x64. } -#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) +#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_SPARC64) // 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 @@ -37,9 +38,11 @@ inline void FlushICache(void* code, size # error "Unknown architecture!" #endif -#if (defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)) || \ +// SPARC V9 `flush` synchronizes the issuing processor's instruction pipeline, +// so FlushICache alone is sufficient there. +#if (defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)) || \ defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_RISCV64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_SPARC64) inline void FlushExecutionContext() { // No-op. Execution context is coherent with instruction cache. --- firefox-153.0/js/src/jit/JitContext.cpp 2026-07-30 07:43:05.722587302 +0200 +++ firefox-153.0/js/src/jit/JitContext.cpp 2026-07-29 19:48:09.159161998 +0200 @@ -125,6 +125,10 @@ bool jit::InitializeJit() { PPC64Flags::Init(); #endif +#ifdef JS_CODEGEN_SPARC64 + SPARC64Flags::Init(); +#endif + #ifndef JS_CODEGEN_NONE MOZ_ASSERT(js::jit::CPUFlagsHaveBeenComputed()); #endif --- firefox-153.0/js/src/jit/JitFrames.cpp 2026-07-30 07:43:05.751161810 +0200 +++ firefox-153.0/js/src/jit/JitFrames.cpp 2026-07-29 19:51:08.956003794 +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_PPC64) || defined(JS_CODEGEN_RISCV64) || \ + defined(JS_CODEGEN_SPARC64) 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_PPC64) || defined(JS_CODEGEN_RISCV64) || \ + defined(JS_CODEGEN_SPARC64) + // PPC64/RISCV64/SPARC64 always store FloatRegisters as 64-bit doubles. MOZ_RELEASE_ASSERT(reg.size() >= sizeof(T)); #else MOZ_RELEASE_ASSERT(reg.size() == sizeof(T)); --- firefox-153.0/js/src/jit/JitFrames.h 2026-07-30 07:43:05.751250944 +0200 +++ firefox-153.0/js/src/jit/JitFrames.h 2026-07-29 19:51:02.034896560 +0200 @@ -331,6 +331,11 @@ struct ResumeFromException { void* _ppc_lr_; void* _ppc_toc_; #endif +#if defined(JS_CODEGEN_SPARC64) + // Same reasoning as PPC64: a called ABI-compliant routine may spill its + // register window into the 176-byte minimum frame area on top of the stack. + void* _sparc_reserved_[22]; +#endif uint8_t* framePointer; uint8_t* stackPointer; @@ -383,7 +388,8 @@ struct ResumeFromException { } }; -#if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) +#if defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_SPARC64) static_assert(sizeof(ResumeFromException) % 16 == 0, "ResumeFromException should be aligned"); #endif --- firefox-153.0/js/src/jit/LIR.h 2026-07-30 07:43:05.722933988 +0200 +++ firefox-153.0/js/src/jit/LIR.h 2026-07-30 01:51:17.689519389 +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_PPC64) || \ + defined(JS_CODEGEN_SPARC64) static const uint32_t REG_BITS = 7; #else static const uint32_t REG_BITS = 6; @@ -622,6 +623,9 @@ class LDefinition { Type type() const { return (Type)((bits_ >> TYPE_SHIFT) & TYPE_MASK); } static bool isFloatRegCompatible(Type type, FloatRegister reg) { + // Not sparc64: there Single i is the high word of Double i, so the two + // kinds are different physical storage and holding a float32 in a + // Double-kind register makes isDouble() lie to shared code. #if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_RISCV64) if (type == FLOAT32 || type == DOUBLE) { return reg.isSingle() || reg.isDouble(); @@ -2303,6 +2307,8 @@ AnyRegister LAllocation::toAnyRegister() # include "jit/riscv64/LIR-riscv64.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/LIR-ppc64.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/LIR-sparc64.h" #elif defined(JS_CODEGEN_MIPS64) # include "jit/mips-shared/LIR-mips-shared.h" # include "jit/mips64/LIR-mips64.h" --- firefox-153.0/js/src/jit/LIROps.yaml 2026-07-30 07:43:05.723103195 +0200 +++ firefox-153.0/js/src/jit/LIROps.yaml 2026-07-29 19:49:38.966976565 +0200 @@ -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_PPC64) || defined(JS_CODEGEN_SPARC64) 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_PPC64) || defined(JS_CODEGEN_SPARC64) 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_PPC64) || defined(JS_CODEGEN_SPARC64) 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_PPC64) || defined(JS_CODEGEN_SPARC64) 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_PPC64) || defined(JS_CODEGEN_SPARC64) 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_PPC64) || defined(JS_CODEGEN_SPARC64) 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_PPC64) || defined(JS_CODEGEN_SPARC64) 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_PPC64) || defined(JS_CODEGEN_SPARC64) 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_PPC64) || defined(JS_CODEGEN_SPARC64) # 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_PPC64) || defined(JS_CODEGEN_SPARC64) # 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_PPC64) || defined(JS_CODEGEN_SPARC64) # 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_PPC64) || defined(JS_CODEGEN_SPARC64) # 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) @@ -4422,6 +4422,64 @@ - name: DivOrModI64 gen_boilerplate: false +- name: UDivOrMod + gen_boilerplate: false + +- name: UDivOrModI64 + gen_boilerplate: false + +- name: ModMaskI + result_type: WordSized + operands: + input: WordSized + arguments: + shift: int32_t + num_temps: 2 + mir_op: Mod + +- name: WasmTruncateToInt64 + result_type: Int64 + operands: + input: WordSized + mir_op: true + +- name: Int64ToFloatingPoint + result_type: WordSized + operands: + input: Int64 + mir_op: true + +- name: WasmCompareExchangeI64 + result_type: Int64 + operands: + ptr: WordSized + oldValue: Int64 + newValue: Int64 + memoryBase: WordSized + mir_op: WasmCompareExchangeHeap + +- name: WasmAtomicBinopI64 + result_type: Int64 + operands: + ptr: WordSized + value: Int64 + memoryBase: WordSized + num_temps64: 1 + mir_op: WasmAtomicBinopHeap + +- name: WasmAtomicExchangeI64 + result_type: Int64 + operands: + ptr: WordSized + value: Int64 + memoryBase: WordSized + mir_op: WasmAtomicExchangeHeap +#endif + +#ifdef JS_CODEGEN_SPARC64 +- name: DivOrModI64 + gen_boilerplate: false + - name: UDivOrMod gen_boilerplate: false --- firefox-153.0/js/src/jit/Label.h 2026-07-30 07:43:05.723196990 +0200 +++ firefox-153.0/js/src/jit/Label.h 2026-07-29 19:44:55.757938625 +0200 @@ -22,8 +22,9 @@ struct LabelBase { // incoming uses and needs to be bound. uint32_t offset_ : 31; -#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_SPARC64) public: #endif static const uint32_t INVALID_OFFSET = 0x7fffffff; // UINT31_MAX. --- firefox-153.0/js/src/jit/Lowering.h 2026-07-30 07:43:05.723688693 +0200 +++ firefox-153.0/js/src/jit/Lowering.h 2026-07-29 19:44:17.947282240 +0200 @@ -25,6 +25,8 @@ # include "jit/riscv64/Lowering-riscv64.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/Lowering-ppc64.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/Lowering-sparc64.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/Lowering-wasm32.h" #elif defined(JS_CODEGEN_NONE) --- firefox-153.0/js/src/jit/MacroAssembler-inl.h 2026-07-30 07:43:05.751437252 +0200 +++ firefox-153.0/js/src/jit/MacroAssembler-inl.h 2026-07-29 23:42:24.570934558 +0200 @@ -41,6 +41,8 @@ # include "jit/riscv64/MacroAssembler-riscv64-inl.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/MacroAssembler-ppc64-inl.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/MacroAssembler-sparc64-inl.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/MacroAssembler-wasm32-inl.h" #elif !defined(JS_CODEGEN_NONE) @@ -392,6 +394,36 @@ void MacroAssembler::moveValue(const Con } // =============================================================== +// Load instructions + +void MacroAssembler::loadUnaligned16ZeroExtend(const Address& src, + Register dest) { +#ifdef JS_CODEGEN_SPARC64 + load16UnalignedZeroExtend(src, dest); +#else + load16ZeroExtend(src, dest); +#endif +} + +void MacroAssembler::loadUnaligned32(const Address& src, Register dest) { +#ifdef JS_CODEGEN_SPARC64 + // sparc64's load32 is |ldsw|, so the byte-wise form must sign-extend too. + load32Unaligned(src, dest); +#else + load32(src, dest); +#endif +} + +void MacroAssembler::loadUnaligned32SignExtendToPtr(const Address& src, + Register dest) { +#ifdef JS_CODEGEN_SPARC64 + load32Unaligned(src, dest); +#else + load32SignExtendToPtr(src, dest); +#endif +} + +// =============================================================== // Copy instructions void MacroAssembler::copy64(const Address& src, const Address& dest, @@ -943,7 +975,16 @@ void MacroAssembler::canonicalizeValueZe template void MacroAssembler::boxDouble(FloatRegister src, const T& dest) { +#ifdef JS_CODEGEN_SPARC64 + // SPARC's hardware NaN is not the canonical one, so raw bits must not reach + // a Value slot; go through the canonicalizing register form. + UseScratchRegisterScope temps(this); + Register scratch = temps.Acquire(); + boxDouble(src, ValueOperand(scratch), src); + storePtr(scratch, dest); +#else storeDouble(src, dest); +#endif } template @@ -1013,17 +1054,40 @@ void MacroAssembler::ensureDouble(const template void MacroAssembler::branchTestStackPtr(Condition cond, T t, Label* label) { +#ifdef JS_CODEGEN_SPARC64 + // %sp is biased; the comparand is a real address, so compare against the + // logical stack pointer. + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + moveStackPtrTo(scratch); + branchTestPtr(cond, scratch, t, label); +#else branchTestPtr(cond, getStackPointer(), t, label); +#endif } template void MacroAssembler::branchStackPtr(Condition cond, T rhs, Label* label) { +#ifdef JS_CODEGEN_SPARC64 + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + moveStackPtrTo(scratch); + branchPtr(cond, scratch, rhs, label); +#else branchPtr(cond, getStackPointer(), rhs, label); +#endif } template void MacroAssembler::branchStackPtrRhs(Condition cond, T lhs, Label* label) { +#ifdef JS_CODEGEN_SPARC64 + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + moveStackPtrTo(scratch); + branchPtr(cond, lhs, scratch, label); +#else branchPtr(cond, lhs, getStackPointer(), label); +#endif } template @@ -1034,6 +1098,11 @@ void MacroAssembler::addToStackPtr(T t) template void MacroAssembler::addStackPtrTo(T t) { addPtr(getStackPointer(), t); +#ifdef JS_CODEGEN_SPARC64 + // getStackPointer() is the biased register; callers mean the logical stack + // pointer, which is SP_BASE higher. + addPtr(Imm32(int32_t(js::jit::SP_BASE)), t); +#endif } void MacroAssembler::reserveStack(uint32_t amount) { --- firefox-153.0/js/src/jit/MacroAssembler.cpp 2026-07-30 07:43:05.751795909 +0200 +++ firefox-153.0/js/src/jit/MacroAssembler.cpp 2026-07-30 01:25:06.787613490 +0200 @@ -1222,8 +1222,16 @@ void MacroAssembler::branchIfNotStringCh : sizeof(char16_t); size_t byteLength = StringCharsByteLength(str); + // Strides wider than one character read at character granularity, which is + // only valid where unaligned accesses are; SPARC traps on them. + size_t maxStride = + Assembler::SupportsUnalignedAccesses() ? 8 : encodingSize; + size_t pos = 0; for (size_t stride : {8, 4, 2, 1}) { + if (stride > maxStride) { + continue; + } while (byteLength >= stride) { Address addr(stringChars, pos * encodingSize); switch (stride) { @@ -3260,8 +3268,10 @@ void MacroAssembler::extractCurrentIndex // Assert the Active flag is set. Label iterActive; branchTest32(Assembler::NonZero, - Address(outIndex, NativeIterator::offsetOfFlags()), - Imm32(NativeIterator::Flags::Active), &iterActive); + Address(outIndex, NativeIterator::offsetOfFlagsForJit32()), + Imm32(NativeIterator::flagForJit32( + NativeIterator::Flags::Active)), + &iterActive); assumeUnreachable("iterator-index fast path on an inactive iterator"); bind(&iterActive); #endif @@ -6163,7 +6173,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_PPC64) || \ + defined(JS_CODEGEN_SPARC64) UseScratchRegisterScope temps(masm); Register scratch = temps.Acquire(); #elif !defined(JS_CODEGEN_NONE) @@ -8580,7 +8591,8 @@ void MacroAssembler::debugAssertCanonica breakpoint(); bind(&ok); # 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_SPARC64) Label ok; UseScratchRegisterScope temps(*this); Register scratch = temps.Acquire(); @@ -9767,7 +9779,7 @@ void MacroAssembler::branchIfResizableAr void MacroAssembler::branchIfNativeIteratorNotReusable(Register ni, Label* notReusable) { // See NativeIterator::isReusable. - Address flagsAddr(ni, NativeIterator::offsetOfFlags()); + Address flagsAddr(ni, NativeIterator::offsetOfFlagsForJit32()); #ifdef DEBUG Label niIsInitialized; @@ -9862,7 +9874,7 @@ void MacroAssembler::maybeLoadIteratorFr load32(Address(nativeIterator, NativeIterator::offsetOfPropertyCount()), temp3); branchTest32(Assembler::Zero, - Address(nativeIterator, NativeIterator::offsetOfFlags()), + Address(nativeIterator, NativeIterator::offsetOfFlagsForJit32()), Imm32(NativeIterator::flagForJit32( NativeIterator::Flags::IndicesAllocated)), &skipIndices); @@ -9995,7 +10007,7 @@ void MacroAssembler::iteratorClose(Regis Register temp3) { LoadNativeIterator(*this, obj, temp1); - Address flagsAddr(temp1, NativeIterator::offsetOfFlags()); + Address flagsAddr(temp1, NativeIterator::offsetOfFlagsForJit32()); // The shared iterator used for for-in with null/undefined is immutable and // unlinked. See NativeIterator::isEmptyIteratorSingleton. --- firefox-153.0/js/src/jit/MacroAssembler.h 2026-07-30 07:43:05.752205988 +0200 +++ firefox-153.0/js/src/jit/MacroAssembler.h 2026-07-29 23:40:11.689308590 +0200 @@ -25,6 +25,8 @@ # include "jit/riscv64/MacroAssembler-riscv64.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/MacroAssembler-ppc64.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/MacroAssembler-sparc64.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/MacroAssembler-wasm32.h" #elif defined(JS_CODEGEN_NONE) @@ -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, ppc64, sparc64, wasm32 #define ALL_SHARED_ARCH \ - arm, arm64, loong64, mips64, riscv64, ppc64, x86_shared, wasm32 + arm, arm64, loong64, mips64, riscv64, ppc64, sparc64, x86_shared, wasm32 // * How this macro works: // @@ -144,6 +147,7 @@ #define DEFINED_ON_loong64 #define DEFINED_ON_riscv64 #define DEFINED_ON_ppc64 +#define DEFINED_ON_sparc64 #define DEFINED_ON_wasm32 #define DEFINED_ON_none @@ -176,6 +180,9 @@ #elif defined(JS_CODEGEN_PPC64) # undef DEFINED_ON_ppc64 # define DEFINED_ON_ppc64 define +#elif defined(JS_CODEGEN_SPARC64) +# undef DEFINED_ON_sparc64 +# define DEFINED_ON_sparc64 define #elif defined(JS_CODEGEN_WASM32) # undef DEFINED_ON_wasm32 # define DEFINED_ON_wasm32 define @@ -569,7 +576,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, ppc64, sparc64, wasm32); // Move the stack pointer based on the requested amount. void adjustStack(int amount); @@ -627,9 +634,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, ppc64, sparc64, wasm32); void popReturnAddress() - DEFINED_ON(mips64, arm, arm64, loong64, riscv64, ppc64, wasm32); + DEFINED_ON(mips64, arm, arm64, loong64, riscv64, ppc64, sparc64, wasm32); // Useful for dealing with two-valued returns. void moveRegPair(Register src0, Register src1, Register dst0, Register dst1, @@ -648,7 +655,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, ppc64, sparc64); // Emit a nop that can be patched to and from a nop and a call with int32 // relative displacement. @@ -674,9 +681,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, ppc64, sparc64); 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, ppc64, sparc64); public: // =============================================================== @@ -1040,6 +1047,13 @@ class MacroAssembler : public MacroAssem inline void load32SignExtendToPtr(const Address& src, Register dest) PER_ARCH; + // Alignment-safe loads, for data such as bytecode operands that is packed + // without padding. Plain loads everywhere except on targets whose hardware + // traps on unaligned accesses; the value produced is identical. + inline void loadUnaligned16ZeroExtend(const Address& src, Register dest); + inline void loadUnaligned32(const Address& src, Register dest); + inline void loadUnaligned32SignExtendToPtr(const Address& src, Register dest); + inline void loadAbiReturnAddress(Register dest) PER_SHARED_ARCH; // =============================================================== @@ -1186,13 +1200,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, ppc64, sparc64); 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, ppc64, sparc64); 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 +1228,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, ppc64, sparc64); inline void quotient64(Register lhs, Register rhs, Register dest, bool isUnsigned) - DEFINED_ON(arm64, loong64, mips64, riscv64, ppc64); + DEFINED_ON(arm64, loong64, mips64, riscv64, ppc64, sparc64); // 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 +1245,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, ppc64, sparc64); inline void remainder64(Register lhs, Register rhs, Register dest, bool isUnsigned) - DEFINED_ON(arm64, loong64, mips64, riscv64, ppc64); + DEFINED_ON(arm64, loong64, mips64, riscv64, ppc64, sparc64); // As above, but lhs and dest must be eax and tempEdx must be edx. inline void remainder32(Register lhs, Register rhs, Register dest, @@ -2092,7 +2106,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, ppc64, sparc64); template inline void branchTestUndefinedImpl(Condition cond, const T& t, Label* label) @@ -2257,7 +2271,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, ppc64, sparc64); // 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 +2284,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, ppc64, sparc64); // ======================================================================== // Canonicalization primitives. @@ -2369,68 +2383,68 @@ class MacroAssembler : public MacroAssem // Moves inline void moveSimd128(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Constants inline void loadConstantSimd128(const SimdConstant& v, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Splat inline void splatX16(Register src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); inline void splatX4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void splatX2(Register64 src, FloatRegister dest) - DEFINED_ON(x86, x64, arm64, ppc64); + DEFINED_ON(x86, x64, arm64, ppc64, sparc64); inline void splatX2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // 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, ppc64, sparc64); inline void unsignedExtractLaneInt8x16(uint32_t lane, FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void extractLaneInt16x8(uint32_t lane, FloatRegister src, - Register dest) DEFINED_ON(x86_shared, arm64, ppc64); + Register dest) DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedExtractLaneInt16x8(uint32_t lane, FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void extractLaneInt32x4(uint32_t lane, FloatRegister src, - Register dest) DEFINED_ON(x86_shared, arm64, ppc64); + Register dest) DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void extractLaneInt64x2(uint32_t lane, FloatRegister src, - Register64 dest) DEFINED_ON(x86, x64, arm64, ppc64); + Register64 dest) DEFINED_ON(x86, x64, arm64, ppc64, sparc64); inline void extractLaneFloat32x4(uint32_t lane, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void extractLaneFloat64x2(uint32_t lane, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Replace lane value @@ -2439,21 +2453,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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); inline void replaceLaneInt64x2(unsigned lane, FloatRegister lhs, Register64 rhs, FloatRegister dest) @@ -2461,7 +2475,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, ppc64, sparc64); inline void replaceLaneFloat32x4(unsigned lane, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) @@ -2469,7 +2483,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, ppc64, sparc64); inline void replaceLaneFloat64x2(unsigned lane, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) @@ -2477,7 +2491,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, ppc64, sparc64); // Shuffle - blend and permute with immediate indices, and its many // specializations. Lane values other than those mentioned are illegal. @@ -2485,11 +2499,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, ppc64, sparc64); 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, ppc64, sparc64); // 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 +2530,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, ppc64, sparc64); inline void interleaveHighInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void interleaveHighInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void interleaveHighInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void interleaveHighInt64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void interleaveLowInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void interleaveLowInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void interleaveLowInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void interleaveLowInt64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Permute - permute with immediate indices. @@ -2558,7 +2572,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, ppc64, sparc64); // lane values 0..3 [sic]. inline void permuteHighInt16x8(const uint16_t lanes[4], FloatRegister src, @@ -2576,80 +2590,80 @@ 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, ppc64, sparc64); // 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, ppc64, sparc64); // 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, ppc64, sparc64); inline void rightShiftSimd128(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Zero extend int values. inline void zeroExtend8x16To16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void zeroExtend8x16To32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void zeroExtend8x16To64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void zeroExtend16x8To32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void zeroExtend16x8To64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void zeroExtend32x4To64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Reverse bytes in lanes. inline void reverseInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void reverseInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void reverseInt64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // 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, ppc64, sparc64); inline void swizzleInt8x16Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Integer Add inline void addInt8x16(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); inline void addInt64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2657,13 +2671,13 @@ 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, ppc64, sparc64); 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, ppc64, sparc64); inline void subInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2672,24 +2686,24 @@ 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, ppc64, sparc64); 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, ppc64, sparc64); // Integer Multiply inline void mulInt16x8(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void mulInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2705,100 +2719,100 @@ 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, ppc64, sparc64); // 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, ppc64, sparc64); inline void extMulHighInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedExtMulLowInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedExtMulHighInt8x16(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void extMulLowInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void extMulHighInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedExtMulLowInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedExtMulHighInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void extMulLowInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void extMulHighInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedExtMulLowInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedExtMulHighInt32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void q15MulrSatInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Integer Negate inline void negInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void negInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void negInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void negInt64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); inline void unsignedAddSatInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2806,27 +2820,27 @@ 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); inline void unsignedSubSatInt16x8(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2834,40 +2848,40 @@ 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); inline void unsignedMinInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2875,40 +2889,40 @@ 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); inline void unsignedMaxInt32x4(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -2917,25 +2931,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, ppc64, sparc64); inline void unsignedAverageInt16x8(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Lane-wise integer absolute value inline void absInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void absInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void absInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void absInt64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // 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 +2958,41 @@ 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, ppc64, sparc64); inline void leftShiftInt8x16(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void leftShiftInt16x8(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void leftShiftInt32x4(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void leftShiftInt64x2(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // 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 +3001,82 @@ 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, ppc64, sparc64); inline void rightShiftInt8x16(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void unsignedRightShiftInt8x16(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void rightShiftInt16x8(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void unsignedRightShiftInt16x8(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void rightShiftInt32x4(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void unsignedRightShiftInt32x4(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void rightShiftInt64x2(FloatRegister lhs, Register rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void unsignedRightShiftInt64x2(Imm32 count, FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Sign replication operation @@ -3081,47 +3095,47 @@ 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, ppc64, sparc64); inline void bitwiseAndSimd128(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void bitwiseOrSimd128(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void bitwiseXorSimd128(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); // 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, ppc64, sparc64); // 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, ppc64, sparc64); inline void bitwiseNotAndSimd128(FloatRegister lhs, FloatRegister rhs, FloatRegister lhsDest) @@ -3134,34 +3148,34 @@ 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, ppc64, sparc64); // Population count inline void popcntInt8x16(FloatRegister src, FloatRegister dest, - FloatRegister temp) DEFINED_ON(x86_shared, ppc64); + FloatRegister temp) DEFINED_ON(x86_shared, ppc64, sparc64); inline void popcntInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc64, sparc64); // 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, ppc64, sparc64); // All lanes true inline void allTrueInt8x16(FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void allTrueInt16x8(FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void allTrueInt32x4(FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void allTrueInt64x2(FloatRegister src, Register dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Bitmask, ie extract and compress high bits of all lanes @@ -3169,31 +3183,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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); // 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, ppc64, sparc64); // On x86_shared, limited to !=, ==, <=, > inline void compareInt8x16(Assembler::Condition cond, FloatRegister lhs, @@ -3203,15 +3217,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, ppc64, sparc64); inline void compareInt16x8(Assembler::Condition cond, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void compareInt16x8(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // On x86_shared, limited to !=, ==, <=, > inline void compareInt16x8(Assembler::Condition cond, FloatRegister lhs, @@ -3221,7 +3235,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, ppc64, sparc64); inline void compareInt32x4(Assembler::Condition cond, FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) @@ -3230,7 +3244,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, ppc64, sparc64); inline void compareForEqualityInt64x2(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, @@ -3244,15 +3258,15 @@ 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, ppc64, sparc64); inline void compareInt64x2(Assembler::Condition cond, FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc64, sparc64); inline void compareFloat32x4(Assembler::Condition cond, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // On x86_shared, limited to ==, !=, <, <= inline void compareFloat32x4(Assembler::Condition cond, FloatRegister lhs, @@ -3263,11 +3277,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, ppc64, sparc64); inline void compareFloat64x2(Assembler::Condition cond, FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // On x86_shared, limited to ==, !=, <, <= inline void compareFloat64x2(Assembler::Condition cond, FloatRegister lhs, @@ -3278,7 +3292,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, ppc64, sparc64); // Load @@ -3287,92 +3301,92 @@ class MacroAssembler : public MacroAssem inline FaultingCodeOffset loadUnalignedSimd128(const Address& src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline FaultingCodeOffset loadUnalignedSimd128(const BaseIndex& src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Store inline FaultingCodeOffset storeUnalignedSimd128(FloatRegister src, const Address& dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline FaultingCodeOffset storeUnalignedSimd128(FloatRegister src, const BaseIndex& dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Floating point negation inline void negFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void negFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Floating point absolute value inline void absFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void absFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // 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, ppc64, sparc64); inline void minFloat32x4(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc64, sparc64); inline void minFloat32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc64, sparc64); inline void minFloat64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, FloatRegister temp1, - FloatRegister temp2) DEFINED_ON(x86_shared, ppc64); + FloatRegister temp2) DEFINED_ON(x86_shared, ppc64, sparc64); inline void minFloat64x2(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc64, sparc64); inline void minFloat64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc64, sparc64); // 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, ppc64, sparc64); inline void maxFloat32x4(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc64, sparc64); inline void maxFloat32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc64, sparc64); inline void maxFloat64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, FloatRegister temp1, - FloatRegister temp2) DEFINED_ON(x86_shared, ppc64); + FloatRegister temp2) DEFINED_ON(x86_shared, ppc64, sparc64); inline void maxFloat64x2(FloatRegister rhs, FloatRegister lhsDest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc64, sparc64); inline void maxFloat64x2(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(arm64, ppc64); + FloatRegister dest) DEFINED_ON(arm64, ppc64, sparc64); // Floating add inline void addFloat32x4(FloatRegister lhs, FloatRegister rhs, - FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister dest) DEFINED_ON(x86_shared, arm64, ppc64, sparc64); 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, ppc64, sparc64); inline void addFloat64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -3380,13 +3394,13 @@ 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, ppc64, sparc64); 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, ppc64, sparc64); inline void subFloat64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -3394,13 +3408,13 @@ 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, ppc64, sparc64); 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, ppc64, sparc64); inline void divFloat64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -3408,13 +3422,13 @@ 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, ppc64, sparc64); 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, ppc64, sparc64); inline void mulFloat64x2(FloatRegister lhs, const SimdConstant& rhs, FloatRegister dest) DEFINED_ON(x86_shared); @@ -3422,91 +3436,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, ppc64, sparc64); inline void unsignedExtAddPairwiseInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void extAddPairwiseInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedExtAddPairwiseInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Floating square root inline void sqrtFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void sqrtFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Integer to floating point with rounding inline void convertInt32x4ToFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedConvertInt32x4ToFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void convertInt32x4ToFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedConvertInt32x4ToFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Floating point to integer with saturation inline void truncSatFloat32x4ToInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedTruncSatFloat32x4ToInt32x4(FloatRegister src, FloatRegister dest, FloatRegister temp) - DEFINED_ON(x86_shared, ppc64); + DEFINED_ON(x86_shared, ppc64, sparc64); inline void unsignedTruncSatFloat32x4ToInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc64, sparc64); inline void truncSatFloat64x2ToInt32x4(FloatRegister src, FloatRegister dest, FloatRegister temp) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedTruncSatFloat64x2ToInt32x4(FloatRegister src, FloatRegister dest, FloatRegister temp) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void truncFloat32x4ToInt32x4Relaxed(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedTruncFloat32x4ToInt32x4Relaxed(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void truncFloat64x2ToInt32x4Relaxed(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedTruncFloat64x2ToInt32x4Relaxed(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Floating point narrowing inline void convertFloat64x2ToFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Floating point widening inline void convertFloat32x4ToFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Integer to integer narrowing @@ -3514,65 +3528,65 @@ 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); // Integer to integer widening inline void widenLowInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void widenHighInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedWidenLowInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedWidenHighInt8x16(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void widenLowInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void widenHighInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedWidenLowInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedWidenHighInt16x8(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void widenLowInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedWidenLowInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void widenHighInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void unsignedWidenHighInt32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // Compare-based minimum/maximum // @@ -3584,47 +3598,47 @@ class MacroAssembler : public MacroAssem inline void pseudoMinFloat32x4(FloatRegister rhsOrRhsDest, FloatRegister lhsOrLhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void pseudoMinFloat32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void pseudoMinFloat64x2(FloatRegister rhsOrRhsDest, FloatRegister lhsOrLhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void pseudoMinFloat64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void pseudoMaxFloat32x4(FloatRegister rhsOrRhsDest, FloatRegister lhsOrLhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void pseudoMaxFloat32x4(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void pseudoMaxFloat64x2(FloatRegister rhsOrRhsDest, FloatRegister lhsOrLhsDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void pseudoMaxFloat64x2(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // 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, ppc64, sparc64); 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, ppc64, sparc64); inline void dotInt8x16Int7x16ThenAdd(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) @@ -3632,81 +3646,81 @@ class MacroAssembler : public MacroAssem inline void dotInt8x16Int7x16ThenAdd(FloatRegister lhs, FloatRegister rhs, FloatRegister dest, FloatRegister temp) - DEFINED_ON(arm64, ppc64); + DEFINED_ON(arm64, ppc64, sparc64); // Floating point rounding inline void ceilFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void ceilFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void floorFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void floorFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void truncFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void truncFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void nearestFloat32x4(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void nearestFloat64x2(FloatRegister src, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); // 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, ppc64, sparc64); inline void fnmaFloat32x4(FloatRegister src1, FloatRegister src2, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void fmaFloat64x2(FloatRegister src1, FloatRegister src2, - FloatRegister srcDest) DEFINED_ON(x86_shared, arm64, ppc64); + FloatRegister srcDest) DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void fnmaFloat64x2(FloatRegister src1, FloatRegister src2, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void minFloat32x4Relaxed(FloatRegister src, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void minFloat32x4Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void maxFloat32x4Relaxed(FloatRegister src, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void maxFloat32x4Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void minFloat64x2Relaxed(FloatRegister src, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void minFloat64x2Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void maxFloat64x2Relaxed(FloatRegister src, FloatRegister srcDest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void maxFloat64x2Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); inline void q15MulrInt16x8Relaxed(FloatRegister lhs, FloatRegister rhs, FloatRegister dest) - DEFINED_ON(x86_shared, arm64, ppc64); + DEFINED_ON(x86_shared, arm64, ppc64, sparc64); public: // ======================================================================== @@ -3731,10 +3745,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, ppc64, sparc64, 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, ppc64, sparc64, riscv64, wasm32, x64, x86); bool convertUInt64ToDoubleNeedsTemp() PER_ARCH; @@ -3815,16 +3829,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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); 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, ppc64, sparc64); // 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 +3918,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, ppc64, sparc64); 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, ppc64, sparc64); void oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64 output, TruncFlags flags, const wasm::TrapSiteDesc& trapSiteDesc, @@ -3917,11 +3931,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, ppc64, sparc64); 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, ppc64, sparc64); void oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64 output, TruncFlags flags, const wasm::TrapSiteDesc& trapSiteDesc, @@ -4242,7 +4256,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, ppc64, sparc64); // As enterFakeExitFrame(), but using register conventions appropriate for // wasm stubs. @@ -4309,13 +4323,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, ppc64, sparc64); 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, ppc64, sparc64); // x86: `expected` and `output` must be edx:eax; `replacement` is ecx:ebx. // x64: `output` must be rax. @@ -4325,12 +4339,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, ppc64, sparc64); 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, ppc64, sparc64); // Exchange with memory. Return the value initially in memory. // MIPS: `valueTemp`, `offsetTemp` and `maskTemp` must be defined for 8-bit @@ -4347,12 +4361,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, ppc64, sparc64); 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, ppc64, sparc64); // x86: `value` must be ecx:ebx; `output` must be edx:eax. // ARM: `value` and `output` must be distinct and (even,odd) pairs. @@ -4360,11 +4374,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, ppc64, sparc64); 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, ppc64, sparc64); // Read-modify-write with memory. Return the value in memory before the // operation. @@ -4398,12 +4412,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, ppc64, sparc64); 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, ppc64, sparc64); // x86: // `temp` must be ecx:ebx; `output` must be edx:eax. @@ -4417,7 +4431,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, ppc64, sparc64); void atomicFetchOp64(Synchronization sync, AtomicOp op, const Address& value, const Address& mem, Register64 temp, Register64 output) @@ -4425,7 +4439,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, ppc64, sparc64); void atomicFetchOp64(Synchronization sync, AtomicOp op, const Address& value, const BaseIndex& mem, Register64 temp, Register64 output) @@ -4443,14 +4457,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, ppc64, sparc64); 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, ppc64, sparc64); // 64-bit atomic load. On 64-bit systems, use regular load with // Synchronization::Load, not this method. @@ -4503,14 +4517,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, ppc64, sparc64); 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, ppc64, sparc64); void wasmAtomicExchange(const wasm::MemoryAccessDesc& access, const Address& mem, Register value, Register output) @@ -4524,13 +4538,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, ppc64, sparc64); 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, ppc64, sparc64); void wasmAtomicFetchOp(const wasm::MemoryAccessDesc& access, AtomicOp op, Register value, const Address& mem, Register temp, @@ -4552,13 +4566,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, ppc64, sparc64); 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, ppc64, sparc64); // Read-modify-write with memory. Return no value. // @@ -4585,13 +4599,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, ppc64, sparc64); 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, ppc64, sparc64); // 64-bit wide operations. @@ -4649,12 +4663,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, ppc64, sparc64, 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, ppc64, sparc64, x64); void wasmAtomicFetchOp64(const wasm::MemoryAccessDesc& access, AtomicOp op, const Address& value, const Address& mem, @@ -4707,14 +4721,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, ppc64, sparc64); 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, ppc64, sparc64); void atomicExchangeJS(Scalar::Type arrayType, Synchronization sync, const Address& mem, Register value, Register temp, @@ -4728,13 +4742,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, ppc64, sparc64); 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, ppc64, sparc64); void atomicFetchOpJS(Scalar::Type arrayType, Synchronization sync, AtomicOp op, Register value, const Address& mem, @@ -4760,13 +4774,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, ppc64, sparc64); 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, ppc64, sparc64); void atomicEffectOpJS(Scalar::Type arrayType, Synchronization sync, AtomicOp op, Register value, const Address& mem, @@ -4788,13 +4802,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, ppc64, sparc64); 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, ppc64, sparc64); void atomicIsLockFreeJS(Register value, Register output); @@ -5953,17 +5967,30 @@ 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, ppc64, sparc64, wasm32, arm, x86, x64); void subFromStackPtr(Register reg); template void subStackPtrFrom(T t) { subPtr(getStackPointer(), t); +#ifdef JS_CODEGEN_SPARC64 + // getStackPointer() is the biased register; callers mean the logical + // stack pointer, which is SP_BASE higher. + subPtr(Imm32(int32_t(js::jit::SP_BASE)), t); +#endif } template void andToStackPtr(T t) { +#ifdef JS_CODEGEN_SPARC64 + // %sp is biased, so alignment has to be applied to the logical stack + // pointer; masking the raw register would leave it misaligned by SP_BASE. + addPtr(Imm32(int32_t(js::jit::SP_BASE)), getStackPointer()); andPtr(t, getStackPointer()); + subPtr(Imm32(int32_t(js::jit::SP_BASE)), getStackPointer()); +#else + andPtr(t, getStackPointer()); +#endif } template @@ -5977,11 +6004,27 @@ class MacroAssembler : public MacroAssem template void loadStackPtr(T t) { +#ifdef JS_CODEGEN_SPARC64 + // The stored value is a real address (C++ produced or consumes it), while + // %sp is biased, so the load has to convert between the two. + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + loadPtr(t, scratch); + moveToStackPtr(scratch); +#else loadPtr(t, getStackPointer()); +#endif } template void storeStackPtr(T t) { +#ifdef JS_CODEGEN_SPARC64 + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + moveStackPtrTo(scratch); + storePtr(scratch, t); +#else storePtr(getStackPointer(), t); +#endif } template --- firefox-153.0/js/src/jit/MoveEmitter.h 2026-07-30 07:43:05.724992292 +0200 +++ firefox-153.0/js/src/jit/MoveEmitter.h 2026-07-29 19:44:20.349324232 +0200 @@ -19,6 +19,8 @@ # include "jit/riscv64/MoveEmitter-riscv64.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/MoveEmitter-ppc64.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/MoveEmitter-sparc64.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/MoveEmitter-wasm32.h" #elif defined(JS_CODEGEN_NONE) --- firefox-153.0/js/src/jit/MoveResolver.cpp 2026-07-30 07:43:05.725021533 +0200 +++ firefox-153.0/js/src/jit/MoveResolver.cpp 2026-07-30 02:14:43.034901080 +0200 @@ -57,18 +57,17 @@ 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 +#if defined(JS_CODEGEN_PPC64) // PPC64 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 - // (from == to) assert above, and would otherwise trip the + // values but share one physical register *in the same format*. 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 (from == to) assert above, and would otherwise trip the // !from().aliases(to()) invariant the resolver relies on later. Drop them. // - // This would be correct for any backend whose FloatRegister has multiple - // kinds aliasing one physical register, and could be un-gated if another - // such backend needs it, but it is scoped to PPC64 so move resolution on - // tier-1 platforms is left unchanged. + // Not sparc64: there Single i is the high word of Double i, so such a move + // is a real format conversion and must not be dropped. LDefinition's strict + // kind compatibility keeps sparc64 from producing one. if (from.aliases(to)) { return true; } --- firefox-153.0/js/src/jit/RegisterAllocator.h 2026-07-30 07:43:05.725053636 +0200 +++ firefox-153.0/js/src/jit/RegisterAllocator.h 2026-07-29 19:45:05.319103082 +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_PPC64) || defined(JS_CODEGEN_SPARC64) regs.take(HeapReg); #endif MOZ_ASSERT(!regs.has(FramePointer)); --- firefox-153.0/js/src/jit/RegisterSets.h 2026-07-15 22:44:59.000000000 +0200 +++ firefox-153.0/js/src/jit/RegisterSets.h 2026-07-29 19:53:56.479557357 +0200 @@ -1407,6 +1407,8 @@ inline LiveGeneralRegisterSet SavedNonVo #elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ defined(JS_CODEGEN_RISCV64) result.add(Register::FromCode(Registers::ra)); +#elif defined(JS_CODEGEN_SPARC64) + result.add(Register::FromCode(Registers::lr)); #endif return result; --- firefox-153.0/js/src/jit/Registers.h 2026-07-30 07:43:05.725084907 +0200 +++ firefox-153.0/js/src/jit/Registers.h 2026-07-29 19:44:22.651364439 +0200 @@ -22,6 +22,8 @@ # include "jit/riscv64/Architecture-riscv64.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/Architecture-ppc64.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/Architecture-sparc64.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/Architecture-wasm32.h" #elif defined(JS_CODEGEN_NONE) --- firefox-153.0/js/src/jit/Safepoints.cpp 2026-07-30 07:43:05.725127679 +0200 +++ firefox-153.0/js/src/jit/Safepoints.cpp 2026-07-29 19:48:34.942577400 +0200 @@ -63,7 +63,7 @@ static void WriteFloatRegisterMask(Compa stream.writeUnsigned64(bits.low()); stream.writeUnsigned64(bits.high()); break; -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) case 16: stream.writeUnsigned64(static_cast(bits)); stream.writeUnsigned64(static_cast(bits >> 64)); @@ -93,7 +93,7 @@ static FloatRegisters::SetType ReadFloat uint64_t high = stream.readUnsigned64(); return Bitset128(high, low); } -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) case 16: { uint64_t low = stream.readUnsigned64(); uint64_t high = stream.readUnsigned64(); --- firefox-153.0/js/src/jit/SharedICHelpers-inl.h 2026-07-30 07:43:05.725154100 +0200 +++ firefox-153.0/js/src/jit/SharedICHelpers-inl.h 2026-07-29 19:44:25.003405482 +0200 @@ -21,6 +21,8 @@ # include "jit/riscv64/SharedICHelpers-riscv64-inl.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/SharedICHelpers-ppc64-inl.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/SharedICHelpers-sparc64-inl.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/SharedICHelpers-wasm32-inl.h" #elif defined(JS_CODEGEN_NONE) --- firefox-153.0/js/src/jit/SharedICHelpers.h 2026-07-30 07:43:05.725185631 +0200 +++ firefox-153.0/js/src/jit/SharedICHelpers.h 2026-07-29 19:44:26.887438329 +0200 @@ -21,6 +21,8 @@ # include "jit/riscv64/SharedICHelpers-riscv64.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/SharedICHelpers-ppc64.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/SharedICHelpers-sparc64.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/SharedICHelpers-wasm32.h" #elif defined(JS_CODEGEN_NONE) --- firefox-153.0/js/src/jit/SharedICRegisters.h 2026-07-30 07:43:05.725211913 +0200 +++ firefox-153.0/js/src/jit/SharedICRegisters.h 2026-07-29 19:44:29.250479493 +0200 @@ -21,6 +21,8 @@ # include "jit/riscv64/SharedICRegisters-riscv64.h" #elif defined(JS_CODEGEN_PPC64) # include "jit/ppc64/SharedICRegisters-ppc64.h" +#elif defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/SharedICRegisters-sparc64.h" #elif defined(JS_CODEGEN_WASM32) # include "jit/wasm32/SharedICRegisters-wasm32.h" #elif defined(JS_CODEGEN_NONE) --- firefox-153.0/js/src/jit/moz.build 2026-07-30 07:43:05.725253214 +0200 +++ firefox-153.0/js/src/jit/moz.build 2026-07-29 19:43:33.660500444 +0200 @@ -240,6 +240,16 @@ elif CONFIG["JS_CODEGEN_PPC64"]: ] if CONFIG["JS_SIMULATOR_PPC64"]: UNIFIED_SOURCES += ["ppc64/Simulator-ppc64.cpp"] +elif CONFIG["JS_CODEGEN_SPARC64"]: + UNIFIED_SOURCES += [ + "sparc64/Architecture-sparc64.cpp", + "sparc64/Assembler-sparc64.cpp", + "sparc64/CodeGenerator-sparc64.cpp", + "sparc64/Lowering-sparc64.cpp", + "sparc64/MacroAssembler-sparc64.cpp", + "sparc64/MoveEmitter-sparc64.cpp", + "sparc64/Trampoline-sparc64.cpp", + ] elif CONFIG["JS_CODEGEN_RISCV64"]: UNIFIED_SOURCES += [ "riscv64/Architecture-riscv64.cpp", --- firefox-153.0/js/src/jit/shared/Assembler-shared.h 2026-07-30 07:43:05.728932742 +0200 +++ firefox-153.0/js/src/jit/shared/Assembler-shared.h 2026-07-29 19:48:05.061095701 +0200 @@ -31,14 +31,15 @@ #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_PPC64) || defined(JS_CODEGEN_SPARC64) // 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_PPC64) || \ + defined(JS_CODEGEN_SPARC64) // JS_CODELABEL_LINKMODE gives labels additional metadata // describing how Bind() should patch them. # define JS_CODELABEL_LINKMODE --- firefox-153.0/js/src/jit/shared/CodeGenerator-shared.cpp 2026-07-30 07:43:05.729029947 +0200 +++ firefox-153.0/js/src/jit/shared/CodeGenerator-shared.cpp 2026-07-29 19:51:11.774047413 +0200 @@ -1074,7 +1074,8 @@ 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) + !defined(JS_CODEGEN_RISCV64) && !defined(JS_CODEGEN_PPC64) && \ + !defined(JS_CODEGEN_SPARC64) void CodeGeneratorShared::jumpToBlock(MBasicBlock* mir, Assembler::Condition cond) { // Skip past trivial blocks. --- firefox-153.0/js/src/jit/shared/Lowering-shared-inl.h 2026-07-30 07:43:05.729089509 +0200 +++ firefox-153.0/js/src/jit/shared/Lowering-shared-inl.h 2026-07-29 19:51:14.594091037 +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_SPARC64) LAllocation LIRGeneratorShared::useAnyOrConstant(MDefinition* mir) { return useRegisterOrConstant(mir); } --- firefox-153.0/js/src/jsapi-tests/testJitABIcalls.cpp 2026-07-30 07:43:05.729222346 +0200 +++ firefox-153.0/js/src/jsapi-tests/testJitABIcalls.cpp 2026-07-29 19:53:58.550588476 +0200 @@ -721,6 +721,9 @@ class JitABICall final : public jsapites #elif defined(JS_CODEGEN_PPC64) Register base = r11; regs.take(base); +#elif defined(JS_CODEGEN_SPARC64) + Register base = l7; + regs.take(base); #else # error "Unknown architecture!" #endif --- firefox-153.0/js/src/jsapi-tests/testWasmReturnCalls.cpp 2026-07-30 07:43:05.729261877 +0200 +++ firefox-153.0/js/src/jsapi-tests/testWasmReturnCalls.cpp 2026-07-29 20:03:20.237506757 +0200 @@ -35,6 +35,8 @@ BEGIN_TEST(testWasmCheckSlowCallMarkerHi # if defined(JS_CODEGEN_PPC64) static constexpr Register ra = ABINonArgReg3; masm.xs_mflr(ra); +# elif defined(JS_CODEGEN_SPARC64) + static constexpr Register ra = o7; # elif !defined(JS_CODEGEN_LOONG64) && !defined(JS_CODEGEN_MIPS64) && \ !defined(JS_CODEGEN_RISCV64) static constexpr Register ra = lr; @@ -76,6 +78,8 @@ BEGIN_TEST(testWasmCheckSlowCallMarkerMi # if defined(JS_CODEGEN_PPC64) static constexpr Register ra = ABINonArgReg3; masm.xs_mflr(ra); +# elif defined(JS_CODEGEN_SPARC64) + static constexpr Register ra = o7; # elif !defined(JS_CODEGEN_LOONG64) && !defined(JS_CODEGEN_MIPS64) && \ !defined(JS_CODEGEN_RISCV64) static constexpr Register ra = lr; --- firefox-153.0/js/src/jsapi-tests/testsJit.cpp 2026-07-30 07:43:05.729281319 +0200 +++ firefox-153.0/js/src/jsapi-tests/testsJit.cpp 2026-07-29 20:03:20.237456155 +0200 @@ -25,6 +25,8 @@ void PrepareJit(js::jit::MacroAssembler& #if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ defined(JS_CODEGEN_RISCV64) save.add(js::jit::ra); +#elif defined(JS_CODEGEN_SPARC64) + save.add(js::jit::o7); #elif defined(JS_CODEGEN_PPC64) // LR on PPC64 isn't a GPR; save it to the stack manually. { @@ -52,6 +54,8 @@ bool ExecuteJit(JSContext* cx, js::jit:: #if defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ defined(JS_CODEGEN_RISCV64) restore.add(js::jit::ra); +#elif defined(JS_CODEGEN_SPARC64) + restore.add(js::jit::o7); #elif defined(JS_CODEGEN_PPC64) // LR will be restored manually after PopRegsInMask. #elif defined(JS_USE_LINK_REGISTER) --- firefox-153.0/js/src/util/Poison.h 2026-07-30 07:43:05.730111667 +0200 +++ firefox-153.0/js/src/util/Poison.h 2026-07-29 19:51:17.287132673 +0200 @@ -94,6 +94,8 @@ const uint8_t JS_SCOPE_DATA_TRAILING_NAM 0x29 // illegal sb instruction, crashes in user mode. #elif defined(JS_CODEGEN_PPC64) # define JS_SWEPT_CODE_PATTERN 0x00 // illegal instruction (all zeros) +#elif defined(JS_CODEGEN_SPARC64) +# define JS_SWEPT_CODE_PATTERN 0x00 // illtrap/unimp (all zeros) #else # error "JS_SWEPT_CODE_PATTERN not defined for this platform" #endif --- firefox-153.0/js/src/vm/Initialization.cpp 2026-07-15 22:44:59.000000000 +0200 +++ firefox-153.0/js/src/vm/Initialization.cpp 2026-07-29 20:28:14.456546453 +0200 @@ -79,9 +79,12 @@ static void SetupCanonicalNaN() { // with our canonical NaN, we must canonicalize every double. This // is implemented for C++ code in Value::bitsFromDouble, but is not // implemented for JIT code. -# if !defined(JS_CODEGEN_NONE) +# if !defined(JS_CODEGEN_NONE) && !defined(JS_CODEGEN_SPARC64) # error "No JIT support for non-canonical hardware NaN" # endif +// SPARC64 is exempt: MacroAssemblerSPARC64Compat::boxDouble canonicalizes NaN +// on every double -> Value conversion, so JIT-produced Values match the ones +// Value::bitsFromDouble produces in C++. (void)hardwareNaNBits; #elif defined(JS_RUNTIME_CANONICAL_NAN) --- firefox-153.0/js/src/vm/Iteration.h 2026-07-30 07:43:05.754586356 +0200 +++ firefox-153.0/js/src/vm/Iteration.h 2026-07-29 23:19:52.658454676 +0200 @@ -634,11 +634,22 @@ struct NativeIterator : public NativeIte } // flags_ is a uint8_t, but JIT code accesses it with 32-bit operations - // (branchTest32/or32 on offsetOfFlags()). The byte is the low part of that - // 32-bit value on little-endian but the high part on big-endian, so flag - // constants used in such JIT accesses must be shifted accordingly. + // (branchTest32/or32). offsetOfFlagsForJit32() gives the address such an + // access must use and flagForJit32() places the flag within the loaded word. + // sparc64 traps on unaligned loads, so there the 32-bit access addresses the + // aligned word containing flags_ rather than flags_ itself. + static constexpr size_t offsetOfFlagsForJit32() { +#ifdef JS_CODEGEN_SPARC64 + return offsetof(NativeIterator, protoShapeCount_); +#else + return offsetof(NativeIterator, flags_); +#endif + } + static constexpr uint32_t flagForJit32(uint32_t flag) { -#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#ifdef JS_CODEGEN_SPARC64 + return flag << 8; +#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ return flag << 24; #else return flag; @@ -655,6 +666,13 @@ struct NativeIterator : public NativeIte } }; +#ifdef JS_CODEGEN_SPARC64 +static_assert(NativeIterator::offsetOfFlagsForJit32() % 4 == 0 && + NativeIterator::offsetOfFlags() == + NativeIterator::offsetOfFlagsForJit32() + 2, + "flagForJit32 assumes flags_ is byte 2 of an aligned word"); +#endif + class PropertyIteratorObject : public NativeObject { static const JSClassOps classOps_; --- firefox-153.0/js/src/wasm/WasmAnyRef.h 2026-07-30 07:43:05.730145348 +0200 +++ firefox-153.0/js/src/wasm/WasmAnyRef.h 2026-07-29 19:57:33.076145634 +0200 @@ -209,7 +209,8 @@ class AnyRef { // Truncate the value to the 31-bit value size. uintptr_t wideValue = uintptr_t(value & 0x7FFFFFFF); #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_SPARC64) // Sign extend the value to the native pointer size. uintptr_t wideValue = uintptr_t(int64_t((uint64_t(value) << 33)) >> 33); #elif !defined(JS_64BIT) @@ -235,7 +236,8 @@ class AnyRef { # if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_ARM64) MOZ_ASSERT(value <= UINT32_MAX); # 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_SPARC64) // On sign-extending platforms, a canonical i32 must be the sign // extension of its low 32 bits. MOZ_ASSERT(value == uintptr_t(int64_t(int32_t(value)))); --- firefox-153.0/js/src/wasm/WasmBCDefs.h 2026-07-30 07:43:05.730174309 +0200 +++ firefox-153.0/js/src/wasm/WasmBCDefs.h 2026-07-29 19:57:33.076307791 +0200 @@ -47,6 +47,9 @@ #if defined(JS_CODEGEN_PPC64) # include "jit/ppc64/Assembler-ppc64.h" #endif +#if defined(JS_CODEGEN_SPARC64) +# include "jit/sparc64/Assembler-sparc64.h" +#endif #include "js/ScalarType.h" #include "util/Memory.h" #include "wasm/WasmCodegenTypes.h" @@ -158,6 +161,10 @@ enum class RhsDestOp { True = true }; # define RABALDR_PIN_INSTANCE #endif +#ifdef JS_CODEGEN_SPARC64 +# define RABALDR_PIN_INSTANCE +#endif + // Max number of pushes onto the value stack for any opcode or emitter that // does not push a variable, unbounded amount (anything with multiple // results). This includes also intermediate pushes such as values pushed as --- firefox-153.0/js/src/wasm/WasmBCMemory.cpp 2026-07-30 07:43:05.755102630 +0200 +++ firefox-153.0/js/src/wasm/WasmBCMemory.cpp 2026-07-29 19:57:52.117788051 +0200 @@ -375,7 +375,8 @@ void BaseCompiler::boundsCheckBelow4GBAc // Make sure the ptr could be used as an index register. static inline void ToValidIndex(MacroAssembler& masm, RegI32 ptr) { #if 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_SPARC64) // When ptr is used as an index, it will be added to a 64-bit register. // So we should explicitly promote ptr to 64-bit. Since now ptr holds a // unsigned 32-bit value, we zero-extend it to 64-bit here. @@ -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_SPARC64) MOZ_ASSERT(temp.isInvalid()); if (dest.tag == AnyReg::I64) { masm.wasmLoadI64(*access, memoryBase, ptr, ptr, dest.i64()); @@ -686,8 +687,9 @@ void BaseCompiler::load(MemoryAccessDesc return executeLoad(access, check, instance, memoryBase, RegI32(ptr.reg), dest, maybeFromI64(temp)); #elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_PPC64) - // On mips64, loongarch64, and ppc64, the 'prepareMemoryAccess' function will + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) + // On mips64, loongarch64, ppc64, and sparc64, the 'prepareMemoryAccess' + // function will // make sure that ptr holds a valid 64-bit index value. Thus the code // generated in 'executeLoad' is the same for the 64-bit and the 32-bit case. return executeLoad(access, check, instance, memoryBase, RegI32(ptr.reg), dest, @@ -799,7 +801,7 @@ void BaseCompiler::executeStore(MemoryAc } else { masm.wasmStore(*access, src.any(), memoryBase, ptr); } -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) MOZ_ASSERT(temp.isInvalid()); if (access->type() == Scalar::Int64) { masm.wasmStoreI64(*access, src.i64(), memoryBase, ptr, ptr); @@ -830,7 +832,8 @@ void BaseCompiler::store(MemoryAccessDes maybeFromI64(temp)); #elif defined(JS_CODEGEN_X64) || 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_SPARC64) return executeStore(access, check, instance, memoryBase, RegI32(ptr.reg), src, maybeFromI64(temp)); #else @@ -1314,7 +1317,7 @@ static void Deallocate(BaseCompiler* bc, } #elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) struct Temps { RegI32 t0, t1, t2; @@ -1522,8 +1525,9 @@ static void Deallocate(BaseCompiler* bc, bc->freeI64(temp); } -#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ + defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_SPARC64) static void PopAndAllocate(BaseCompiler* bc, AtomicOp op, RegI64* rd, RegI64* rv, RegI64* temp) { @@ -1698,7 +1702,7 @@ static void Deallocate(BaseCompiler* bc, } #elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) struct Temps { RegI32 t0, t1, t2; @@ -1863,8 +1867,9 @@ static void Deallocate(BaseCompiler* bc, bc->freeI32(bc->specific_.ecx); } -#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ + defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_SPARC64) static void PopAndAllocate(BaseCompiler* bc, RegI64* rd, RegI64* rv) { *rv = bc->popI64(); @@ -2038,7 +2043,7 @@ static void Deallocate(BaseCompiler* bc, } #elif defined(JS_CODEGEN_MIPS64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) struct Temps { RegI32 t0, t1, t2; @@ -2307,8 +2312,9 @@ static void Deallocate(BaseCompiler* bc, bc->freeI64(rnew); } -#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ - defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_MIPS64) || \ + defined(JS_CODEGEN_LOONG64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_SPARC64) template static void PopAndAllocate(BaseCompiler* bc, RegI64* rexpect, RegI64* rnew, --- firefox-153.0/js/src/wasm/WasmBCRegDefs.h 2026-07-30 07:43:05.730382049 +0200 +++ firefox-153.0/js/src/wasm/WasmBCRegDefs.h 2026-07-29 20:29:57.819669122 +0200 @@ -125,6 +125,13 @@ static constexpr Register RabaldrScratch static constexpr Register RabaldrScratchI32 = r25; #endif +#ifdef JS_CODEGEN_SPARC64 +# define RABALDR_SCRATCH_I32 +// A window register: callee-saved, not an argument register, and not used by +// any wasm infrastructure. +static constexpr Register RabaldrScratchI32 = l6; +#endif + #ifdef RABALDR_SCRATCH_F32_ALIASES_F64 # if !defined(RABALDR_SCRATCH_F32) || !defined(RABALDR_SCRATCH_F64) # error "Bad configuration" @@ -395,7 +402,7 @@ struct SpecificRegs { }; #elif 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_SPARC64) struct SpecificRegs { // Required by gcc. SpecificRegs() {} --- firefox-153.0/js/src/wasm/WasmBaselineCompile.cpp 2026-07-30 07:43:05.755271190 +0200 +++ firefox-153.0/js/src/wasm/WasmBaselineCompile.cpp 2026-07-29 19:58:17.546904665 +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_SPARC64) ScratchI32 scratch(*this); CodeLabel tableCl; -# if defined(JS_CODEGEN_PPC64) +# if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) 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_SPARC64) 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_SPARC64) { ScratchPtr scratch(*this); @@ -1411,7 +1414,8 @@ void BaseCompiler::popStackResults(ABIRe switch (v.kind()) { case Stk::ConstI32: #if 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_SPARC64) fr.storeImmediatePtrToStack(v.i32val_, resultHeight, temp); #else fr.storeImmediatePtrToStack(uint32_t(v.i32val_), resultHeight, temp); @@ -2396,7 +2400,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_SPARC64) return RegI32::Invalid(); #else MOZ_CRASH("BaseCompiler platform hook: needRotate64Temp"); @@ -2455,7 +2459,7 @@ void BaseCompiler::popAndAllocateForMulI pop2xI64(r0, r1); #elif defined(JS_CODEGEN_RISCV64) pop2xI64(r0, r1); -#elif defined(JS_CODEGEN_PPC64) +#elif defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) pop2xI64(r0, r1); #else MOZ_CRASH("BaseCompiler porting interface: popAndAllocateForMulI64"); @@ -2888,7 +2892,7 @@ 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_SPARC64) return bc.needI32(); #elif defined(JS_CODEGEN_PPC64) // PPC64 has native popcntd/popcntw; no temp register needed. @@ -12773,7 +12777,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_SPARC64) return true; #else return false; --- firefox-153.0/js/src/wasm/WasmCompile.cpp 2026-07-30 07:43:05.731130114 +0200 +++ firefox-153.0/js/src/wasm/WasmCompile.cpp 2026-07-29 20:24:01.913599876 +0200 @@ -74,8 +74,9 @@ uint32_t wasm::ObservedCPUFeatures() { LOONG64 = 0x7, RISCV64 = 0x8, PPC64 = 0x9, + SPARC64_ARCH = 0xa, - LAST = PPC64, + LAST = SPARC64_ARCH, ARCH_BITS = 4 }; @@ -107,6 +108,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_SPARC64) + MOZ_ASSERT(jit::GetSPARC64Flags() <= (UINT32_MAX >> ARCH_BITS)); + return SPARC64_ARCH | (jit::GetSPARC64Flags() << ARCH_BITS); #elif defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32) return 0; #else --- firefox-153.0/js/src/wasm/WasmFrameIter.cpp 2026-07-30 07:43:05.731245588 +0200 +++ firefox-153.0/js/src/wasm/WasmFrameIter.cpp 2026-07-29 20:02:59.073824155 +0200 @@ -635,6 +635,15 @@ static const unsigned SetFP = 16; // post-poppedFP window (single-step profiling fires every instruction). static const unsigned PoppedFP = 8; static const unsigned PoppedFPJitEntry = 8; +#elif defined(JS_CODEGEN_SPARC64) +// SPARC64 has no store-with-update, so each push is sub+store (8 bytes) and +// moveStackPtrTo is a single mov. +static const unsigned PushedRetAddr = 8; +static const unsigned PushedFP = 16; +static const unsigned SetFP = 20; +// The epilogues restore %o7 and FP first, then a single add pops the Frame. +static const unsigned PoppedFP = 4; +static const unsigned PoppedFPJitEntry = 4; #elif defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_WASM32) // Synthetic values to satisfy asserts and avoid compiler warnings. static const unsigned PushedRetAddr = 0; @@ -726,7 +735,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_SPARC64) { *entry = masm.currentOffset(); @@ -865,6 +874,17 @@ static void GenerateCallableEpilogue(Mac masm.leaveNoPool(); masm.as_blr(); +#elif defined(JS_CODEGEN_SPARC64) + + masm.loadPtr(Address(StackPointer, Frame::returnAddressOffset()), + js::jit::o7); + masm.loadPtr(Address(StackPointer, Frame::callerFPOffset()), FramePointer); + poppedFP = masm.currentOffset(); + + masm.addToStackPtr(Imm32(sizeof(Frame))); + *ret = masm.currentOffset(); + masm.abiret(); + #elif defined(JS_CODEGEN_ARM64) // See comment at equivalent place in |GenerateCallablePrologue| above. @@ -1553,7 +1573,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_SPARC64) offsets->begin = masm.currentOffset(); masm.pushReturnAddress(); #elif defined(JS_CODEGEN_ARM64) @@ -1621,6 +1641,15 @@ void wasm::GenerateJitEntryEpilogue(Macr masm.addToStackPtr(Imm32(sizeof(Frame))); offsets->ret = masm.currentOffset(); masm.as_blr(); +#elif defined(JS_CODEGEN_SPARC64) + masm.loadPtr(Address(StackPointer, Frame::returnAddressOffset()), + js::jit::o7); + masm.loadPtr(Address(StackPointer, Frame::callerFPOffset()), FramePointer); + poppedFP = masm.currentOffset(); + + masm.addToStackPtr(Imm32(sizeof(Frame))); + offsets->ret = masm.currentOffset(); + masm.abiret(); #else // Forbid pools for the same reason as described in GenerateCallablePrologue. # if defined(JS_CODEGEN_ARM) @@ -2028,6 +2057,22 @@ bool js::wasm::StartUnwinding(const Regi fixedFP = fp; AssertMatchesCallSite(fixedPC, fixedFP); } else +#elif defined(JS_CODEGEN_SPARC64) + if (codeRange->isThunk()) { + // The FarJumpIsland sequence temporarily scrambles %o7. + fixedPC = pc; + fixedFP = fp; + *unwoundCaller = false; + AssertMatchesCallSite( + Frame::fromUntaggedWasmExitFP(fp)->returnAddress(), + Frame::fromUntaggedWasmExitFP(fp)->rawCaller()); + } else if (offsetFromEntry < PushedFP) { + // The return address is in %o7 (registers.lr) until + // pushReturnAddress() saves it to the stack. + fixedPC = (uint8_t*)registers.lr; + fixedFP = fp; + AssertMatchesCallSite(fixedPC, fixedFP); + } else #elif defined(JS_CODEGEN_ARM64) if (offsetFromEntry < SetFP || codeRange->isThunk()) { // On ARM64 we rely on register state instead of state saved on @@ -2108,7 +2153,8 @@ bool js::wasm::StartUnwinding(const Regi fixedPC = (uint8_t*)registers.lr; fixedFP = fp; AssertMatchesCallSite(fixedPC, fixedFP); -#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_LOONG64) +#elif defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_LOONG64) || \ + defined(JS_CODEGEN_SPARC64) // The stack pointer does not move until all values have // been restored so several cases can be coalesced here. } else if (offsetInCode >= codeRange->ret() - PoppedFP && --- firefox-153.0/js/src/wasm/WasmIonCompile.cpp 2026-07-30 07:43:05.756271203 +0200 +++ firefox-153.0/js/src/wasm/WasmIonCompile.cpp 2026-07-29 19:57:33.077066078 +0200 @@ -11591,7 +11591,8 @@ bool js::wasm::IonPlatformSupport() { #if defined(JS_CODEGEN_X64) || defined(JS_CODEGEN_X86) || \ defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_MIPS64) || \ defined(JS_CODEGEN_ARM64) || defined(JS_CODEGEN_LOONG64) || \ - defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) + defined(JS_CODEGEN_RISCV64) || defined(JS_CODEGEN_PPC64) || \ + defined(JS_CODEGEN_SPARC64) return true; #else return false; --- firefox-153.0/js/src/wasm/WasmStubs.cpp 2026-07-30 07:43:05.756829779 +0200 +++ firefox-153.0/js/src/wasm/WasmStubs.cpp 2026-07-29 20:03:04.421543469 +0200 @@ -694,7 +694,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_SPARC64) masm.pushReturnAddress(); # elif defined(JS_CODEGEN_ARM64) // WasmPush updates framePushed() unlike pushReturnAddress(), but that's @@ -2179,7 +2179,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_SPARC64) MOZ_ASSERT(NonVolatileRegs.has(HeapReg)); #endif @@ -2626,11 +2626,12 @@ bool wasm::GenerateBuiltinThunk(MacroAss } } #endif -#ifdef JS_CODEGEN_PPC64 - // PPC64 32-bit operations do not zero-extend to 64 bits (unlike - // x86-64/ARM64/LA64). The ELFv2 ABI requires callers to zero/sign-extend - // narrow args. Wasm i32 values may have garbage upper bits in 64-bit - // registers, so zero-extend them before calling C++ builtins. +#if defined(JS_CODEGEN_PPC64) || defined(JS_CODEGEN_SPARC64) + // PPC64 and SPARC64 32-bit operations do not zero-extend to 64 bits + // (unlike x86-64/ARM64/LA64). Their ABIs require callers to + // zero/sign-extend narrow args. Wasm i32 values may have garbage upper + // bits in 64-bit registers, so zero-extend them before calling C++ + // builtins. if (selfArgs.mirType() == MIRType::Int32) { masm.move32ZeroExtendToPtr(selfArgs->gpr(), selfArgs->gpr()); } @@ -2755,6 +2756,19 @@ static const LiveRegisterSet RegsToPrese # else FloatRegisterSet(FloatRegisters::AllDoubleMask)); # endif +#elif defined(JS_CODEGEN_SPARC64) +// Exclude %g0 (hardwired zero), %g6/%g7 (reserved for the system), %o6 (sp) +// and %i6 (fp). +static const LiveRegisterSet RegsToPreserve( + GeneralRegisterSet(Registers::AllMask & ~((uint32_t(1) << Registers::g0) | + (uint32_t(1) << Registers::g6) | + (uint32_t(1) << Registers::g7) | + (uint32_t(1) << Registers::sp) | + (uint32_t(1) << Registers::fp))), + FloatRegisterSet(FloatRegisters::AllDoubleMask)); +# ifdef ENABLE_WASM_SIMD +# error "high lanes of SIMD registers need to be saved too." +# endif #elif defined(JS_CODEGEN_ARM64) // We assume that traps do not happen while lr is live. This both ensures that // the size of RegsToPreserve is a multiple of 2 (preserving WasmStackAlignment) --- firefox-153.0/js/src/wasm/WasmSummarizeInsn.cpp 2026-07-30 07:43:05.756981646 +0200 +++ firefox-153.0/js/src/wasm/WasmSummarizeInsn.cpp 2026-07-29 19:59:00.291035359 +0200 @@ -1898,6 +1898,16 @@ Maybe SummarizeTrapInst return Nothing(); } +// =============================================================== sparc64 ==== + +// Not implemented; the trapsite/instruction cross-check in WasmCodegenTypes +// does not run on sparc64. +# elif defined(JS_CODEGEN_SPARC64) + +Maybe SummarizeTrapInstruction(const uint8_t* insnAddr) { + return Nothing(); +} + // ================================================================== none ==== # elif defined(JS_CODEGEN_NONE) --- firefox-153.0/js/src/wasm/WasmValue.cpp 2026-07-30 07:43:05.732591439 +0200 +++ firefox-153.0/js/src/wasm/WasmValue.cpp 2026-07-29 19:57:33.076447018 +0200 @@ -430,7 +430,8 @@ bool ToWebAssemblyValue_i32(JSContext* c bool ok = ToInt32(cx, val, loc); if (ok && mustWrite64) { #if 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_SPARC64) loc[1] = loc[0] >> 31; #else loc[1] = 0; --- firefox-153.0/python/mozbuild/mozbuild/mozinfo.py 2026-07-15 22:45:00.000000000 +0200 +++ firefox-153.0/python/mozbuild/mozbuild/mozinfo.py 2026-07-29 19:53:28.850758054 +0200 @@ -62,7 +62,7 @@ def build_dict(config, env=os.environ): p = "x86" d["processor"] = p # hardcoded list of 64-bit CPUs - if p in ["x86_64", "ppc64", "aarch64"]: + if p in ["x86_64", "ppc64", "sparc64", "aarch64"]: d["bits"] = 64 # hardcoded list of known 32-bit CPUs elif p in ["x86", "arm", "ppc"]: diff -Naurp empty/js/src/jit/sparc64/Architecture-sparc64.cpp firefox-153.0/js/src/jit/sparc64/Architecture-sparc64.cpp --- firefox-153.0/js/src/jit/sparc64/Architecture-sparc64.cpp 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/Architecture-sparc64.cpp 2026-07-29 19:51:47.955605256 +0200 @@ -0,0 +1,198 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#include "jit/sparc64/Architecture-sparc64.h" + +#include +#include +#include + +#include "jit/FlushICache.h" // js::jit::FlushICache +#include "jit/RegisterSets.h" + +// Present in glibc's for sparc, but define locally so the build +// does not depend on the sysroot version. +#ifndef HWCAP_SPARC_VIS +# define HWCAP_SPARC_VIS 0x00002000 +#endif +#ifndef HWCAP_SPARC_VIS2 +# define HWCAP_SPARC_VIS2 0x00004000 +#endif +#ifndef HWCAP_SPARC_VIS3 +# define HWCAP_SPARC_VIS3 0x00020000 +#endif + +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 and Double of the same encoding are the same physical register, so + // push them once as a Double (8-byte slot). Simd128 is a disjoint pool here + // (16-byte slot) and is never actually allocated. + 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. + SetType set64 = singleSet | doubleSet; + + uint32_t count64 = std::popcount(static_cast(set64)); + uint32_t count128 = std::popcount(static_cast(simd128Set)); + + return count64 * sizeof(double) + count128 * 16; +} + +uint32_t FloatRegister::getRegisterDumpOffsetInBytes() { + return encoding() * sizeof(FloatRegisters::RegisterContent); +} + +static bool sVISDetected = false; +static bool sVIS2Detected = false; +static bool sVIS3Detected = false; +static bool sCPUFlagsComputed = false; + +void SPARC64Flags::Init() { + if (sCPUFlagsComputed) { + return; + } + unsigned long hwcap = getauxval(AT_HWCAP); + sVISDetected = (hwcap & HWCAP_SPARC_VIS) != 0; + sVIS2Detected = (hwcap & HWCAP_SPARC_VIS2) != 0; + sVIS3Detected = (hwcap & HWCAP_SPARC_VIS3) != 0; + + // qemu-user does not report a SPARC AT_HWCAP, so allow opting into the VIS + // code paths for testing. Each level implies the ones below it. + const char* forceVIS = getenv("MOZ_SPARC64_FORCE_VIS"); + if (forceVIS && forceVIS[0] == '1') { + sVISDetected = true; + } + const char* forceVIS2 = getenv("MOZ_SPARC64_FORCE_VIS2"); + if (forceVIS2 && forceVIS2[0] == '1') { + sVISDetected = true; + sVIS2Detected = true; + } + const char* forceVIS3 = getenv("MOZ_SPARC64_FORCE_VIS3"); + if (forceVIS3 && forceVIS3[0] == '1') { + sVISDetected = true; + sVIS2Detected = true; + sVIS3Detected = true; + } + // Exercise the memory round-trip fallbacks on VIS-capable hardware. + const char* forceNoVIS = getenv("MOZ_SPARC64_FORCE_NOVIS"); + if (forceNoVIS && forceNoVIS[0] == '1') { + sVISDetected = false; + sVIS2Detected = false; + sVIS3Detected = false; + } + if (sVIS3Detected || sVIS2Detected) { + sVISDetected = true; + } + sCPUFlagsComputed = true; +} + +bool HasVIS() { + MOZ_ASSERT(sCPUFlagsComputed); + return sVISDetected; +} + +bool HasVIS2() { + MOZ_ASSERT(sCPUFlagsComputed); + return sVIS2Detected; +} + +bool HasVIS3() { + MOZ_ASSERT(sCPUFlagsComputed); + return sVIS3Detected; +} + +bool CPUFlagsHaveBeenComputed() { return sCPUFlagsComputed; } + +// Per-bit feature flags packed into the wasm code signature. The value is +// 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 here. +uint32_t GetSPARC64Flags() { + uint32_t flags = 0; + if (sVISDetected) { + flags |= SPARC64Flag_VIS; + } + if (sVIS2Detected) { + flags |= SPARC64Flag_VIS2; + } + if (sVIS3Detected) { + flags |= SPARC64Flag_VIS3; + } + return flags; +} + +void FlushICache(void* code, size_t size) { + // SPARC V9 has incoherent I/D caches. FLUSH makes the doubleword at the + // given address coherent and flushes the pipeline; the ABI requires one + // FLUSH per doubleword of modified code. + if (!size) { + return; + } + + uintptr_t start = reinterpret_cast(code); + uintptr_t end = start + size; + + for (uintptr_t addr = start & ~uintptr_t(7); addr < end; addr += 8) { + asm volatile("flush %0" : : "r"(addr) : "memory"); + } + // FLUSH also synchronizes the issuing processor's instruction pipeline, so + // FlushICache.h keeps FlushExecutionContext a no-op on SPARC. +} + +} // namespace jit +} // namespace js diff -Naurp empty/js/src/jit/sparc64/Architecture-sparc64.h firefox-153.0/js/src/jit/sparc64/Architecture-sparc64.h --- firefox-153.0/js/src/jit/sparc64/Architecture-sparc64.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/Architecture-sparc64.h 2026-07-30 00:11:55.203493327 +0200 @@ -0,0 +1,616 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_Architecture_sparc64_h +#define jit_sparc64_Architecture_sparc64_h + +#include +#include + +#include "jit/shared/Architecture-shared.h" + +#include "js/Utility.h" + +namespace js { +namespace jit { + +// SPARC V9 exposes 32 integer registers through a sliding register window: +// %g0-%g7 (global), %o0-%o7 (out), %l0-%l7 (local), %i0-%i7 (in). +// +// The JIT never executes save/restore: all generated code runs in the single +// window established by EnterJIT, so this is a flat 32-register machine and +// %l0-%l7/%i0-%i5 are preserved across calls to C for free. +// +// SPARC V9 LP64 convention as seen by the JIT: +// %g0 Hardwired zero +// %g1-%g3 Scratch (ScratchRegister, SecondScratchReg, addressTempRegister) +// %g4,%g5 Volatile, allocatable +// %g6,%g7 System / TLS, never written +// %o0-%o5 Outgoing arguments 1-6, %o0 is also the C return value +// %o6 Stack pointer (biased by STACK_BIAS) +// %o7 Link register written by call +// %l0-%l7 Window-local, non-volatile from the JIT's point of view +// %i0-%i5 Window-in, non-volatile from the JIT's point of view +// %i6 Frame pointer of the single JIT window +// %i7 Return address of the JIT window's caller + +// %sp and %fp hold the real stack address minus this bias; every memory +// reference through them has to add it back. +static constexpr uint32_t STACK_BIAS = 2047; + +class Registers { + public: + enum RegisterID { + g0 = 0, + g1, + g2, + g3, + g4, + g5, + g6, + g7, + o0, + o1, + o2, + o3, + o4, + o5, + o6, + o7, + l0, + l1, + l2, + l3, + l4, + l5, + l6, + l7, + i0, + i1, + i2, + i3, + i4, + i5, + i6, + i7, + invalid_reg, + sp = o6, + fp = i6, + lr = o7, + }; + 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[] = { + "g0", "g1", "g2", "g3", "g4", "g5", "g6", "g7", + "o0", "o1", "o2", "o3", "o4", "o5", "sp", "o7", + "l0", "l1", "l2", "l3", "l4", "l5", "l6", "l7", + "i0", "i1", "i2", "i3", "i4", "i5", "fp", "i7"}; + 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 = 21; + + static const SetType AllMask = 0xFFFFFFFF; + static const SetType NoneMask = 0x0; + + static const SetType ArgRegMask = + (1U << Registers::o0) | (1U << Registers::o1) | (1U << Registers::o2) | + (1U << Registers::o3) | (1U << Registers::o4) | (1U << Registers::o5); + + // g1-g3 and o7 are also clobbered by a call, but are non-allocatable and + // handled separately. + static const SetType VolatileMask = + ArgRegMask | (1U << Registers::g4) | (1U << Registers::g5); + + // Preserved across a call because the callee's own `save` gives it a + // different register window (see the flat-window model above). + static const SetType NonVolatileMask = + (1U << Registers::l0) | (1U << Registers::l1) | (1U << Registers::l2) | + (1U << Registers::l3) | (1U << Registers::l4) | (1U << Registers::l5) | + (1U << Registers::l6) | (1U << Registers::i0) | + (1U << Registers::i1) | (1U << Registers::i2) | (1U << Registers::i3) | + (1U << Registers::i4) | (1U << Registers::i5); + + static const SetType NonAllocatableMask = + (1U << Registers::g0) | // Hardwired zero. + (1U << Registers::g1) | // ScratchRegister. + (1U << Registers::g2) | // SecondScratchReg. + (1U << Registers::g3) | // addressTempRegister. + (1U << Registers::l7) | // FramePointer. + (1U << Registers::g6) | // Reserved for the system. + (1U << Registers::g7) | // Thread pointer (TLS). + (1U << Registers::sp) | // Stack pointer (%o6). + (1U << Registers::o7) | // Link register. + (1U << Registers::i6) | // Caller's %sp: %i6 aliases the caller's %o6. + (1U << Registers::i7); // Caller's return address. + + static const SetType WrapperMask = VolatileMask; + + // Registers returned from a JS -> JS call. + static const SetType JSCallMask = (1U << Registers::o2); + + // Registers returned from a JS -> C call. + static const SetType CallMask = (1U << Registers::o0); + + static const SetType AllocatableMask = AllMask & ~NonAllocatableMask; + + static_assert(AllocatableMask == (VolatileMask | NonVolatileMask)); + static_assert(Allocatable == std::popcount(AllocatableMask)); +}; + +typedef uint32_t PackedRegisterMask; + +template +class TypedRegisterSet; + +class FloatRegisters { + public: + // The hardware maps a 5-bit double field f to architectural register + // %d((f & 0x1E) | ((f & 1) << 5)), so an index used verbatim as that field + // enumerates all 32 doubles, just not in numerical order: index i is + // %d(i) for even i and %d(i + 31) for odd i. The dN enum names below are + // therefore index labels, not architectural names -- GetName() prints the + // real register. + // + // A Single index goes into the plain 5-bit single field, so Single index i + // is %f(i), the upper half of double %d(i & ~1) -- that is, of Double index + // (i & 0x1E). Single i and Double i are the same physical register only for + // even i, so odd Single indices are non-allocatable (see + // NonAllocatableMask); the whole aliasing model below assumes it. + enum FPRegisterID { + d0 = 0, + d2, + d4, + d6, + d8, + d10, + d12, + d14, + d16, + d18, + d20, + d22, + d24, + d26, + d28, + d30, + d32, + d34, + d36, + d38, + d40, + d42, + d44, + d46, + d48, + d50, + d52, + d54, + d56, + d58, + d60, + d62, + }; + + // Eight bits: (invalid << 7) | (kind << 5) | encoding + typedef uint8_t Code; + typedef FPRegisterID Encoding; + // 3 kinds × 32 regs = 96 bits needed. Use __uint128_t. + typedef __uint128_t SetType; + + enum Kind : uint8_t { Double, Single, Simd128, NumTypes }; + + static constexpr Code Invalid = 0x80; + + static const char* GetName(uint32_t code) { + // Indexed by encoding, so the order follows the field-to-register mapping + // described above rather than the numerical one. + static const char* const DoubleNames[] = { + "d0", "d32", "d2", "d34", "d4", "d36", "d6", "d38", + "d8", "d40", "d10", "d42", "d12", "d44", "d14", "d46", + "d16", "d48", "d18", "d50", "d20", "d52", "d22", "d54", + "d24", "d56", "d26", "d58", "d28", "d60", "d30", "d62"}; + static const char* const SingleNames[] = { + "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(DoubleNames), + "Table is the correct size"); + static_assert(TotalPhys == std::size(SingleNames), + "Table is the correct size"); + if (code >= Total) { + return "invalid"; + } + if (kind(Code(code)) == Single) { + return SingleNames[encoding(Code(code))]; + } + return DoubleNames[encoding(Code(code))]; + } + + 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 index 30, the scratch. + + static_assert(sizeof(SetType) * 8 >= Total, + "SetType should be large enough to enumerate all registers."); + + static const SetType SpreadSingle = SetType(1) + << (uint32_t(Single) * TotalPhys); + static const SetType SpreadDouble = SetType(1) + << (uint32_t(Double) * TotalPhys); + static const SetType SpreadSimd128 = SetType(1) + << (uint32_t(Simd128) * TotalPhys); + static const SetType Spread = SpreadSingle | SpreadDouble | SpreadSimd128; + + static const SetType AllPhysMask = ((SetType(1) << TotalPhys) - 1); + static const SetType AllMask = AllPhysMask * Spread; + static const SetType AllSingleMask = AllPhysMask * SpreadSingle; + static const SetType AllDoubleMask = AllPhysMask * SpreadDouble; + static const SetType AllSimd128Mask = AllPhysMask * SpreadSimd128; + static const SetType NoneMask = SetType(0); + + // The SPARC V9 ABI has no callee-saved floating point registers. + static const SetType NonVolatileMask = NoneMask; + + static const SetType VolatileMask = AllMask & ~NonVolatileMask; + + static const SetType WrapperMask = VolatileMask; + + static const SetType OddPhysMask = AllPhysMask & SetType(0xAAAAAAAA); + + // Index 30 is the scratch register in all three views. It is the only even + // index whose single and double views genuinely alias and that is far enough + // from index 0 to stay clear of the return value; it is also FP argument slot + // 15, which no call the JIT emits ever reaches. + // + // Odd single indices are excluded because Single i only aliases Double i for + // even i (see the encoding note at the top of the class). Allocating them + // would let a double write silently destroy a live float32. + static const SetType NonAllocatableMask = + ((SetType(1) << FloatRegisters::d60) * Spread) | + (OddPhysMask * SpreadSingle); + + static const SetType AllocatableMask = AllMask & ~NonAllocatableMask; + + static_assert(Allocatable == std::popcount(static_cast( + AllocatableMask & AllPhysMask))); + + union RegisterContent { + float s; + double d; + // No v128 here: Simd128 is never allocated (SupportsWasmSimd() is false) + // and must not widen the RegisterDump FPU slot stride, which + // PushRegsInMask and addressOfRegister both walk at 8 bytes. + }; + + 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); + } +}; + +// Must fit the widest register class (Simd128 = 16 bytes); cannot be derived +// from sizeof(FloatRegisters::RegisterContent), which is sized for scalars. +// Consumed only by MoveEmitter, not part of the JIT frame layout. +static const uint32_t SpillSlotSize = 16; + +// The SPARC V9 minimum stack frame measured from %sp + STACK_BIAS: 128 bytes +// of register window save area plus a 48-byte spill area for arguments 1-6. +// The kernel spills our window into that area asynchronously, on any window +// overflow deeper in the call chain, so the reserve has to hold at all times +// and not just across ABI calls. It is therefore folded into the stack access +// base below rather than into ShadowStackSpace, which stays zero. +static constexpr uint32_t SPARCFrameReserve = 176; + +// Base added to every [StackPointer + k] reference, so that the reserve always +// sits below the JIT's logical stack top. %o6 + SP_BASE is the logical stack +// pointer, and is 16-byte aligned exactly when %sp is ABI-correct. +static constexpr uint32_t SP_BASE = STACK_BIAS + SPARCFrameReserve; +static_assert((SP_BASE + 1) % 16 == 0); + +// Zero because SPARCFrameReserve already accounts for the callee's save area: +// outgoing stack argument 7 lands at [sp + 0] == [%o6 + STACK_BIAS + 176]. +static constexpr uint32_t ShadowStackSpace = 0; +static const uint32_t SizeOfReturnAddressAfterCall = 0; + +// BPcc/FBPfcc have a 19-bit signed word displacement (+/- 1MB). Reduced to +// leave room for jump island insertion. +static constexpr uint32_t JumpImmediateRange = (1 << 20) - 32; + +// Size of each bailout table entry: a call plus its delay-slot nop. +static const uint32_t BAILOUT_TABLE_ENTRY_SIZE = 8; + +// Condition code selectors. The values are the 3-bit cc2:cc1:cc0 field of +// MOVcc/FMOVcc; the 2-bit cc1:cc0 field of BPcc/FBPfcc is the low two bits +// (BPcc implies cc2 = 1, FBPfcc implies cc2 = 0). +enum CCRegisterID { + fcc0 = 0, + fcc1 = 1, + fcc2 = 2, + fcc3 = 3, + icc = 4, + xcc = 6, + invalid_ccreg = 7 +}; + +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 (Single, Double, Simd128) down to physical mask. + SetType phys = + (x & FloatRegisters::AllPhysMask) | + ((x >> FloatRegisters::TotalPhys) & FloatRegisters::AllPhysMask) | + ((x >> (2 * FloatRegisters::TotalPhys)) & FloatRegisters::AllPhysMask); + return std::popcount(static_cast(phys)); + } + + // __uint128_t helpers for FirstBit/LastBit. + static uint32_t FirstBit(SetType x) { + MOZ_ASSERT(x); + uint64_t lo = static_cast(x); + if (lo) { + return std::countr_zero(lo); + } + return 64 + std::countr_zero(static_cast(x >> 64)); + } + static uint32_t LastBit(SetType x) { + MOZ_ASSERT(x); + uint64_t hi = static_cast(x >> 64); + if (hi) { + return 64 + (std::bit_width(hi) - 1); + } + return std::bit_width(static_cast(x)) - 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_); + } + + // SPARC has a single floating point register file, so the raw encoding is + // the same for every kind. + constexpr Encoding encoding() const { + MOZ_ASSERT(!invalid_); + return Encoding(encoding_); + } + + 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 { + // {Single, Double} of the same encoding share a physical register. Simd128 + // is treated as a disjoint pool: it is never allocated, and keeping it + // disjoint keeps the push/dump slot arithmetic single-kind. + 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; } + +// SPARC64 feature bits packed into the value GetSPARC64Flags() 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 must +// remain backward-compatible: an older signature has to keep meaning the same +// set of features. +enum SPARC64FeatureFlags : uint32_t { + SPARC64Flag_VIS = 1u << 0, + SPARC64Flag_VIS2 = 1u << 1, + // VIS3 also provides movxtod/movdtox, the direct GPR<->FPR moves. Without + // it those go through a memory round trip. + SPARC64Flag_VIS3 = 1u << 2, +}; + +uint32_t GetSPARC64Flags(); + +class SPARC64Flags final { + public: + SPARC64Flags() = delete; + + // Called from the JitContext constructor to read the hardware capabilities + // via getauxval(AT_HWCAP). Must run exactly once, before HasVIS() and + // friends are used. + static void Init(); +}; + +bool HasVIS(); +bool HasVIS2(); + +// True when the CPU implements VIS 3.0 (UltraSPARC T4 / SPARC64 X and later). +// VIS3 adds movxtod/movdtox; where it is absent MacroAssemblerSPARC64 moves +// bits between GPRs and FPRs through a scratch slot in the JIT frame. +bool HasVIS3(); + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_Architecture_sparc64_h */ diff -Naurp empty/js/src/jit/sparc64/Assembler-sparc64.cpp firefox-153.0/js/src/jit/sparc64/Assembler-sparc64.cpp --- firefox-153.0/js/src/jit/sparc64/Assembler-sparc64.cpp 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/Assembler-sparc64.cpp 2026-07-30 00:25:58.072659483 +0200 @@ -0,0 +1,952 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#include "jit/sparc64/Assembler-sparc64.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; + +// SPARC V9 maps the argument list onto one array of extended-word slots. +// Slot n is %o(n) for an integer/pointer argument and %d(2n) for a prototyped +// double, so the two register files share a single positional counter -- +// f(void*, double) puts the double in %d2, not %d0. Slots 0-5 are the only +// ones with an integer register, but a double keeps a register up to slot 15. +// A slot past the sixth consumes stack space either way; stackOffset_ already +// includes ShadowStackSpace. +ABIArg ABIArgGenerator::next(MIRType type) { + const unsigned slot = slotIndex_++; + const uint32_t slotOffset = stackOffset_; + if (slot >= NumIntArgRegs) { + stackOffset_ += sizeof(uintptr_t); + } + + switch (type) { + case MIRType::Int32: + case MIRType::Int64: + case MIRType::Pointer: + case MIRType::WasmAnyRef: + case MIRType::WasmArrayData: + case MIRType::StackResults: { + current_ = slot >= NumIntArgRegs + ? ABIArg(slotOffset) + : ABIArg(Register::FromCode(Registers::o0 + slot)); + break; + } + case MIRType::Float32: + case MIRType::Double: { + if (slot >= NumFloatArgRegs) { + current_ = ABIArg(slotOffset); + break; + } + // Slot n is architectural %d(2n) for a double, and %f(2n+1) for a float + // (right-justified in the 8-byte slot, so the second word on big-endian). + // Double index 2n is %d(2n) and Single index 2n+1 is %f(2n+1). + current_ = ABIArg( + type == MIRType::Double + ? FloatRegister(FloatRegisters::Encoding(2 * slot), + FloatRegisters::Double) + : FloatRegister(FloatRegisters::Encoding(2 * slot + 1), + FloatRegisters::Single)); + break; + } + case MIRType::Simd128: + MOZ_CRASH("NYI: SIMD arguments (SupportsWasmSimd() is false)"); + default: + MOZ_CRASH("Unexpected argument type"); + } + return current_; +} + +// The 4-bit cond field of Bicc/BPcc and of FBPfcc are both laid out so that +// bit 3 selects the complement of the remaining test. +Assembler::Condition Assembler::InvertCondition(Condition cond) { + static_assert((Equal ^ 8) == NotEqual && (LessThan ^ 8) == GreaterThanOrEqual && + (LessThanOrEqual ^ 8) == GreaterThan && + (Below ^ 8) == AboveOrEqual && (BelowOrEqual ^ 8) == Above && + (Signed ^ 8) == NotSigned && (Overflow ^ 8) == NotOverflow && + (Never ^ 8) == Always, + "SPARC integer condition inversion is cond ^ 8"); + return Condition(uint32_t(cond) ^ 8); +} + +Assembler::DoubleCondition Assembler::InvertCondition(DoubleCondition cond) { + static_assert( + (DoubleEqual ^ 8) == DoubleNotEqualOrUnordered && + (DoubleNotEqual ^ 8) == DoubleEqualOrUnordered && + (DoubleLessThan ^ 8) == DoubleGreaterThanOrEqualOrUnordered && + (DoubleLessThanOrEqual ^ 8) == DoubleGreaterThanOrUnordered && + (DoubleGreaterThan ^ 8) == DoubleLessThanOrEqualOrUnordered && + (DoubleGreaterThanOrEqual ^ 8) == DoubleLessThanOrUnordered && + (DoubleOrdered ^ 8) == DoubleUnordered && + (DoubleNever ^ 8) == DoubleAlways, + "SPARC float condition inversion is cond ^ 8"); + return DoubleCondition(uint32_t(cond) ^ 8); +} + +// --------------------------------------------------------------------------- +// Raw encoders used by the static patchers, which have no Assembler instance. + +static const uint32_t OP3_FLUSHW = 0x2b; + +static uint32_t SethiWord(Register rd, uint32_t imm22) { + return FMT2 | Rd(rd) | (uint32_t(OP2_SETHI) << Op2Shift) | (imm22 & Imm22Mask); +} + +static uint32_t OrImmWord(Register rd, Register rs1, int32_t imm) { + return FMT3A | Rd(rd) | (uint32_t(OP3_OR) << Op3Shift) | Rs1(rs1) | + Simm13(imm); +} + +static uint32_t OrRegWord(Register rd, Register rs1, Register rs2) { + return FMT3A | Rd(rd) | (uint32_t(OP3_OR) << Op3Shift) | Rs1(rs1) | Rs2(rs2); +} + +static uint32_t SllxImmWord(Register rd, Register rs1, uint32_t sh) { + return FMT3A | Rd(rd) | (uint32_t(OP3_SLL) << Op3Shift) | Rs1(rs1) | + (1u << ImmBitShift) | (1u << ShiftXShift) | sh; +} + +static uint32_t JmplWord(Register rd, Register rs1, int32_t off) { + return FMT3A | Rd(rd) | (uint32_t(OP3_JMPL) << Op3Shift) | Rs1(rs1) | + Simm13(off); +} + +static uint32_t CallWord(int32_t off) { + return FMT1 | JOffImm30(off).encode(); +} + +static uint32_t BranchWord(uint32_t op2, uint32_t cond, uint32_t cc, + uint32_t annul, uint32_t predict, uint32_t disp19) { + return FMT2 | (annul << AnnulShift) | (cond << CondShift) | + (op2 << Op2Shift) | (cc << CCShift) | (predict << PredictShift) | + (disp19 & Disp19Mask); +} + +// An unconditional, predicted-taken BPcc against %xcc; %xcc is irrelevant for +// cond == Always but has to be encoded as something. +static uint32_t BranchAlwaysWord(int32_t offset) { + return BranchWord(OP2_BPcc, Assembler::Always, Assembler::CC_XCC, + Assembler::NoAnnul, Assembler::Taken, + BOffImm19(offset).encode()); +} + +static void SetBranchDisp(Instruction* inst, int32_t offset) { + inst->setData((inst->encode() & ~Disp19Mask) | BOffImm19(offset).encode()); +} + +static bool IsTaggedTrap(const Instruction* inst) { + return inst->extractOp() == 2 && inst->extractOp3() == OP3_Tcc; +} + +static uint32_t TrapTagOf(const Instruction* inst) { + return inst->encode() & 0x7f; +} + +// The sethi/or/sllx/sethi/or/or shape WriteLoad64Instructions emits. +static bool IsLoad64Stanza(Instruction* i0) { + return i0[0].isSethi() && i0[3].isSethi() && i0[2].extractOp() == 2 && + i0[2].extractOp3() == OP3_SLL; +} + +static void NopRange(Instruction* first, uint32_t count) { + for (uint32_t i = 0; i < count; i++) { + first[i].makeNop(); + } +} + +// Every control transfer emits its delay slot in the same buffer transaction, +// so a constant pool can never be dumped between the two. +static BufferOffset EmitWithDelaySlot(SPARCBufferWithExecutableCopy& buffer, + uint32_t inst) { + uint32_t pair[2] = {inst, NopInstValue}; + return buffer.allocEntry(2, 0, reinterpret_cast(pair), nullptr); +} + +// --------------------------------------------------------------------------- +// Instruction accessors. + +Instruction* Instruction::skipPool() { + if (extractOp() != 0 || extractOp2() != OP2_BPcc || + extractCond() != Assembler::Always) { + return this; + } + int32_t disp = int32_t(data << 13) >> 11; + if (disp <= 0) { + return this; + } + // The pool header sits after the guard branch and its delay slot; bit 15 + // clear marks an artificial (assembler-inserted) guard. + Instruction* header = this + 2; + if ((header->encode() & 0xffff8000) != 0xffff0000) { + return this; + } + return reinterpret_cast(reinterpret_cast(this) + disp); +} + +InstReg* Instruction::asReg() { + MOZ_ASSERT(extractOp() >= 2 && !hasImmBit()); + return static_cast(this); +} + +InstImm* Instruction::asImm() { + MOZ_ASSERT(extractOp() >= 2 && hasImmBit()); + return static_cast(this); +} + +// --------------------------------------------------------------------------- +// 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) { 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 the pool is empty. + 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(); +} + +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 and primitive emitters. + +BufferOffset Assembler::haltingAlign(int alignment) { + BufferOffset ret; + MOZ_ASSERT(m_buffer.isAligned(4)); + MOZ_ASSERT((alignment & (alignment - 1)) == 0); + while (size() & (alignment - 1)) { + BufferOffset tmp = as_illtrap(0); + if (!ret.assigned()) { + ret = tmp; + } + } + return ret; +} + +BufferOffset Assembler::nopAlign(int alignment) { + BufferOffset ret; + MOZ_ASSERT(m_buffer.isAligned(4)); + MOZ_ASSERT((alignment & (alignment - 1)) == 0); + while (size() & (alignment - 1)) { + BufferOffset tmp = as_nop(); + if (!ret.assigned()) { + ret = tmp; + } + } + return ret; +} + +BufferOffset Assembler::as_nop() { + spew("nop"); + return writeInst(NopInstValue); +} + +// --------------------------------------------------------------------------- +// Control transfer. Each of these emits its instruction plus a delay-slot nop. + +BufferOffset Assembler::as_bpcc(Condition cond, ConditionCode cc, BOffImm19 off, + Annul a, BranchPredict p) { + spew("b%d,%s\t%s,%d", int(cond), p == Taken ? "pt" : "pn", + cc == CC_XCC ? "%xcc" : "%icc", off.isInvalid() ? 0 : off.decode()); + uint32_t disp = off.isInvalid() ? 0 : off.encode(); + return EmitWithDelaySlot(m_buffer, + BranchWord(OP2_BPcc, cond, cc, a, p, disp)); +} + +BufferOffset Assembler::as_fbpfcc(DoubleCondition cond, FloatConditionCode fcc, + BOffImm19 off, Annul a, BranchPredict p) { + spew("fb%d,%s\t%%fcc%d,%d", int(cond), p == Taken ? "pt" : "pn", int(fcc), + off.isInvalid() ? 0 : off.decode()); + uint32_t disp = off.isInvalid() ? 0 : off.encode(); + return EmitWithDelaySlot(m_buffer, + BranchWord(OP2_FBPfcc, cond, fcc, a, p, disp)); +} + +BufferOffset Assembler::as_call(JOffImm30 off) { + spew("call\t%d", off.isInvalid() ? 0 : off.decode()); + uint32_t disp = off.isInvalid() ? 0 : off.encode(); + return EmitWithDelaySlot(m_buffer, FMT1 | (disp & Disp30Mask)); +} + +BufferOffset Assembler::as_jmpl(Register rd, Register rs1, int32_t off) { + MOZ_ASSERT(IsInSimm13Range(off)); + spew("jmpl\t[%s+%d],%s", rs1.name(), off, rd.name()); + return EmitWithDelaySlot(m_buffer, JmplWord(rd, rs1, off)); +} + +BufferOffset Assembler::as_jmpl(Register rd, Register rs1, Register rs2) { + spew("jmpl\t[%s+%s],%s", rs1.name(), rs2.name(), rd.name()); + return EmitWithDelaySlot(m_buffer, + FMT3A | Rd(rd) | (uint32_t(OP3_JMPL) << Op3Shift) | + Rs1(rs1) | Rs2(rs2)); +} + +BufferOffset Assembler::as_retl() { + spew("retl"); + return EmitWithDelaySlot(m_buffer, JmplWord(g0, o7, 8)); +} + +BufferOffset Assembler::as_save(Register rd, Register rs1, int32_t imm) { + MOZ_ASSERT(IsInSimm13Range(imm)); + spew("save\t%s,%d,%s", rs1.name(), imm, rd.name()); + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_SAVE) << Op3Shift) | + Rs1(rs1) | Simm13(imm)); +} + +BufferOffset Assembler::as_restore(Register rd, Register rs1, Register rs2) { + spew("restore\t%s,%s,%s", rs1.name(), rs2.name(), rd.name()); + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_RESTORE) << Op3Shift) | + Rs1(rs1) | Rs2(rs2)); +} + +BufferOffset Assembler::as_ta(uint32_t trap) { + MOZ_ASSERT(trap < 0x80); + spew("ta\t%d", trap); + // Tcc with cond = Always against %icc. + return writeInst(FMT3A | (uint32_t(Always) << RdShift) | + (uint32_t(OP3_Tcc) << Op3Shift) | Rs1(g0) | + (1u << ImmBitShift) | trap); +} + +BufferOffset Assembler::as_illtrap(uint32_t payload) { + spew("illtrap\t%d", payload); + return writeInst(FMT2 | (uint32_t(OP2_ILLTRAP) << Op2Shift) | + (payload & Imm22Mask)); +} + +BufferOffset Assembler::as_flushw() { + spew("flushw"); + return writeInst(FMT3A | (OP3_FLUSHW << Op3Shift)); +} + +// MOVcc has its own field layout: cc2 at bit 18, cond at 17:14, cc1:cc0 at +// 12:11, and an 11-bit immediate. Integer condition codes always set cc2. +BufferOffset Assembler::as_movcc(Condition cond, ConditionCode cc, Register rd, + Register rs2) { + spew("mov%d\t%s,%s", int(cond), rs2.name(), rd.name()); + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_MOVcc) << Op3Shift) | + (1u << 18) | (uint32_t(cond) << 14) | + ((uint32_t(cc) & 0x3) << 11) | Rs2(rs2)); +} + +BufferOffset Assembler::as_movcc(Condition cond, ConditionCode cc, Register rd, + int32_t imm) { + MOZ_ASSERT(imm >= -1024 && imm <= 1023); + spew("mov%d\t%d,%s", int(cond), imm, rd.name()); + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_MOVcc) << Op3Shift) | + (1u << 18) | (uint32_t(cond) << 14) | (1u << ImmBitShift) | + ((uint32_t(cc) & 0x3) << 11) | (uint32_t(imm) & 0x7ff)); +} + +// --------------------------------------------------------------------------- +// Label binding, retarget and code label processing. +// +// A forward reference to an unbound label reserves a stanza of +// PatchableBranchInstructionCount words whose first word is a tagged `ta` and +// whose second word carries the offset of the next pending branch in the +// label's use chain (or LabelBase::INVALID_OFFSET). bind() below turns the +// stanza into its final shape: +// +// BTag short: ba disp19; nop; nops +// long: load64 %g2; jmpl %g2,%g0; nop +// CallTag short: nops; call disp30; nop (return address = stanza end) +// long: load64 %g2; jmpl %g2,%o7; nop +// BCTag the conditional BPcc and its delay slot precede the stanza at +// [-2] and [-1]; it was emitted with the inverted condition and a +// displacement that skips the whole stanza. +// short: rewrite [-2] with the true condition and the real target, +// nop the stanza out. +// long: leave [-2] alone; load64 %g2; jmpl %g2,%g0; nop + +static const uint32_t StanzaWords = Assembler::PatchableBranchInstructionCount; +static const uint32_t StanzaJmplSlot = Assembler::Load64InstructionCount; + +void Assembler::bind(InstImm* inst, uintptr_t branch, uintptr_t target) { + intptr_t offset = target - branch; + Instruction* i0 = (Instruction*)inst; + + if (IsLoad64Stanza(i0)) { + addLongJump(BufferOffset(branch), BufferOffset(target)); + return; + } + + if (IsTaggedTrap(i0)) { + switch (TrapTag(TrapTagOf(i0))) { + case BTag: { + if (BOffImm19::IsInRange(offset)) { + i0->setData(BranchAlwaysWord(int32_t(offset))); + NopRange(i0 + 1, StanzaWords - 1); + return; + } + addLongJump(BufferOffset(branch), BufferOffset(target)); + WriteLoad64Instructions(i0, SecondScratchReg, + LabelBase::INVALID_OFFSET); + i0[StanzaJmplSlot].setData(JmplWord(g0, SecondScratchReg, 0)); + i0[StanzaJmplSlot + 1].makeNop(); + return; + } + case CallTag: { + // The call sits at the second-to-last slot so that the return address + // its delay slot establishes is the end of the stanza. + intptr_t callOffset = offset - intptr_t(StanzaJmplSlot) * 4; + if (JOffImm30::IsInRange(callOffset)) { + NopRange(i0, StanzaJmplSlot); + i0[StanzaJmplSlot].setData(CallWord(int32_t(callOffset))); + i0[StanzaJmplSlot + 1].makeNop(); + return; + } + addLongJump(BufferOffset(branch), BufferOffset(target)); + WriteLoad64Instructions(i0, SecondScratchReg, + LabelBase::INVALID_OFFSET); + i0[StanzaJmplSlot].setData(JmplWord(o7, SecondScratchReg, 0)); + i0[StanzaJmplSlot + 1].makeNop(); + return; + } + case BCTag: { + Instruction* bp = i0 - 2; + MOZ_ASSERT(bp->isBranch()); + // The BPcc is two words before the stanza, so its own displacement to + // the target is 8 bytes longer. + if (BOffImm19::IsInRange(offset + 8)) { + bp->setData(bp->encode() ^ (8u << CondShift)); + SetBranchDisp(bp, int32_t(offset + 8)); + NopRange(i0, StanzaWords); + return; + } + addLongJump(BufferOffset(branch), BufferOffset(target)); + WriteLoad64Instructions(i0, SecondScratchReg, + LabelBase::INVALID_OFFSET); + i0[StanzaJmplSlot].setData(JmplWord(g0, SecondScratchReg, 0)); + i0[StanzaJmplSlot + 1].makeNop(); + return; + } + default: + MOZ_CRASH("Unexpected TrapTag"); + } + } + + if (i0->isBranch()) { + MOZ_ASSERT(BOffImm19::IsInRange(offset)); + SetBranchDisp(i0, int32_t(offset)); + i0->next()->makeNop(); + return; + } + + if (i0->isCall()) { + MOZ_ASSERT(JOffImm30::IsInRange(offset)); + i0->setData(CallWord(int32_t(offset))); + 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()) { + 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 { + i1->setData(target->offset()); + break; + } + b = next; + } while (true); + } + if (!target->bound()) { + target->use(label->offset()); + } + } + label->reset(); +} + +void Assembler::processCodeLabels(uint8_t* rawCode) { + for (const CodeLabel& label : codeLabels_) { + Bind(rawCode, label); + } +} + +void Assembler::call(Label* label) { + if (label->bound()) { + int32_t offset = label->offset() - currentOffset(); + MOZ_ASSERT(JOffImm30::IsInRange(offset)); + as_call(JOffImm30(offset)); + return; + } + + m_buffer.enterNoPool(StanzaWords); + BufferOffset bo = as_ta(CallTag); + writeInst(label->used() ? label->offset() : LabelBase::INVALID_OFFSET); + for (uint32_t i = 2; i < StanzaWords; i++) { + writeInst(NopInstValue); + } + m_buffer.leaveNoPool(); + if (!oom()) { + label->use(bo.getOffset()); + } +} + +void Assembler::call(void* target) { + m_buffer.enterNoPool(StanzaWords); + BufferOffset bo = writeInst(NopInstValue); + for (uint32_t i = 1; i < StanzaWords; i++) { + writeInst(NopInstValue); + } + m_buffer.leaveNoPool(); + if (oom()) { + return; + } + + Instruction* inst = editSrc(bo); + WriteLoad64Instructions(inst, SecondScratchReg, uint64_t(uintptr_t(target))); + inst[StanzaJmplSlot].setData(JmplWord(o7, SecondScratchReg, 0)); + inst[StanzaJmplSlot + 1].makeNop(); + addPendingJump(bo, ImmPtr(target), RelocationKind::HARDCODED); +} + +// --------------------------------------------------------------------------- +// Constant pool callbacks. + +/* static */ +void Assembler::InsertIndexIntoTag(uint8_t* load, uint32_t index) { + uint32_t* inst = (uint32_t*)load; + *inst = (*inst & 0xFFFF0000) | (index & 0xFFFF); +} + +/* static */ +bool Assembler::PatchConstantPoolLoad(void* loadAddr, void* constPoolAddr) { + // Hint word layout, written by the MacroAssembler's loadFromPool* helpers: + // bits 0-15: pool entry index + // bits 16-20: destination FPR encoding + // bits 21-22: load type + // 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; + + if (loadType != PoolLoadFPR64 && loadType != PoolLoadFPR32) { + MOZ_CRASH("NYI: PatchConstantPoolLoad for Simd128 pool entries"); + } + + // SPARC V9 has no PC-relative addressing; `call .+8` is the only way to + // materialise the PC, and it lands in %o7. Save and restore %o7 around it. + // [0] mov %o7, %g1 + // [1] call .+8 (%o7 := address of [1]) + // [2] nop (delay slot) + // [3] lddf/ldf [%o7 + disp], %fD + // [4] mov %g1, %o7 + int32_t displacement = + (int32_t)((uint8_t*)constPoolAddr + index * 4 - ((uint8_t*)loadAddr + 4)); + MOZ_ASSERT(IsInSimm13Range(displacement)); + + Register base = LinkRegister; + uint32_t op3 = (loadType == PoolLoadFPR64) ? OP3_LDDF : OP3_LDF; + + inst[0] = OrRegWord(SavedScratchRegister, g0, LinkRegister); + inst[1] = FMT1 | (2u & Disp30Mask); + inst[2] = NopInstValue; + inst[3] = FMT3B | ((destReg & RegMask) << RdShift) | (op3 << Op3Shift) | + Rs1(base) | Simm13(displacement); + inst[4] = OrRegWord(LinkRegister, g0, SavedScratchRegister); + + return false; +} + +/* static */ +void Assembler::WritePoolGuard(BufferOffset branch, Instruction* inst, + BufferOffset dest) { + int32_t offset = dest.getOffset() - branch.getOffset(); + MOZ_ASSERT(BOffImm19::IsInRange(offset)); + inst[0].setData(BranchAlwaysWord(offset)); + inst[1].makeNop(); +} + +/* static */ +void Assembler::WritePoolHeader(uint8_t* start, Pool* p, bool isNatural) { + uint32_t poolSize = p->getPoolSize(); + uint32_t sizeInWords = (poolSize + 4 + 3) >> 2; + MOZ_ASSERT(sizeInWords < (1 << 15)); + uint32_t header = + (sizeInWords & 0x7FFF) | (isNatural ? (1 << 15) : 0) | 0xFFFF0000; + *(uint32_t*)start = header; +} + +/* static */ +void Assembler::PatchShortRangeBranchToVeneer(SPARCBuffer*, unsigned rangeIdx, + BufferOffset deadline, + BufferOffset veneer) { + // numShortBranchRanges is 0, so the buffer never calls this. + MOZ_CRASH("PatchShortRangeBranchToVeneer: should not be called"); +} + +// --------------------------------------------------------------------------- +// The fixed-length 64-bit immediate sequence. +// +// [0] sethi %hi(hi32), rd rd := (v >> 42) << 10 +// [1] or rd, %lo(hi32), rd rd := v >> 32 (zero extended) +// [2] sllx rd, 32, rd rd := (v >> 32) << 32 +// [3] sethi %hi(lo32), tmp tmp := (v >> 10) & ~0x3ff +// [4] or tmp, %lo(lo32), tmp tmp := v & 0xffffffff +// [5] or rd, tmp, rd rd := v +// +// %hi is bits 31..10 of the half and %lo is bits 9..0, so the `or` immediates +// are always non-negative and their simm13 sign extension is harmless, and +// sethi zeroes bits 63..32 of its destination. tmp is %g3 unless rd is %g3, in +// which case it is %g1: %g3 is outside the {%g1,%g2} scratch pool, so a stanza +// emitted into one scratch never destroys the other one its caller is holding. +// This matches ma_li's choice of temp. + +static Register Load64Temp(Register reg) { + return (reg == g3) ? SavedScratchRegister : g3; +} + +/* static */ +void Assembler::WriteLoad64Instructions(Instruction* inst0, Register reg, + uint64_t value) { + Register tmp = Load64Temp(reg); + uint32_t hi32 = uint32_t(value >> 32); + uint32_t lo32 = uint32_t(value); + + inst0[0].setData(SethiWord(reg, hi32 >> 10)); + inst0[1].setData(OrImmWord(reg, reg, int32_t(hi32 & 0x3ff))); + inst0[2].setData(SllxImmWord(reg, reg, 32)); + inst0[3].setData(SethiWord(tmp, lo32 >> 10)); + inst0[4].setData(OrImmWord(tmp, tmp, int32_t(lo32 & 0x3ff))); + inst0[5].setData(OrRegWord(reg, reg, tmp)); +} + +/* static */ +uint64_t Assembler::ExtractLoad64Value(Instruction* inst0) { + MOZ_ASSERT(IsLoad64Stanza(inst0)); + uint32_t hi32 = (inst0[0].extractImm22() << 10) | + (uint32_t(inst0[1].extractSimm13()) & 0x3ff); + uint32_t lo32 = (inst0[3].extractImm22() << 10) | + (uint32_t(inst0[4].extractSimm13()) & 0x3ff); + return (uint64_t(hi32) << 32) | uint64_t(lo32); +} + +/* static */ +void Assembler::UpdateLoad64Value(Instruction* inst0, uint64_t value) { + MOZ_ASSERT(IsLoad64Stanza(inst0)); + uint32_t hi32 = uint32_t(value >> 32); + uint32_t lo32 = uint32_t(value); + + static_cast(&inst0[0])->setImm22(hi32 >> 10); + static_cast(&inst0[1])->setSimm13(int32_t(hi32 & 0x3ff)); + static_cast(&inst0[3])->setImm22(lo32 >> 10); + static_cast(&inst0[4])->setSimm13(int32_t(lo32 & 0x3ff)); +} + +// --------------------------------------------------------------------------- +// Patching and toggling. + +/* static */ +uint32_t Assembler::PatchWrite_NearCallSize() { + return StanzaWords * sizeof(uint32_t); +} + +/* static */ +void Assembler::PatchWrite_NearCall(CodeLocationLabel start, + CodeLocationLabel toCall) { + Instruction* inst = (Instruction*)start.raw(); + uint8_t* dest = toCall.raw(); + + Assembler::WriteLoad64Instructions(inst, SecondScratchReg, (uint64_t)dest); + inst[StanzaJmplSlot].setData(JmplWord(o7, SecondScratchReg, 0)); + inst[StanzaJmplSlot + 1].makeNop(); + FlushICache(inst, StanzaWords * 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::ExtractLoad64Value(inst); + MOZ_ASSERT(value == uint64_t(expectedValue.value)); + + Assembler::UpdateLoad64Value(inst, uint64_t(newValue.value)); + FlushICache(inst, Load64InstructionCount * sizeof(Instruction)); +} + +// A toggled call is a load64 stanza followed by `jmpl rD,%o7` and its delay +// slot; disabling it replaces the jmpl with a nop. Idempotent in either +// direction, which the debugger relies on. +/* static */ +void Assembler::ToggleCall(CodeLocationLabel inst_, bool enabled) { + Instruction* i0 = (Instruction*)inst_.raw(); + MOZ_ASSERT(IsLoad64Stanza(i0)); + + Register scratch = Register::FromCode(i0->extractRd()); + Instruction* jmpl = i0 + StanzaJmplSlot; + uint32_t enabledWord = JmplWord(o7, scratch, 0); + + MOZ_ASSERT(jmpl->encode() == NopInstValue || jmpl->encode() == enabledWord); + jmpl->setData(enabled ? enabledWord : NopInstValue); + FlushICache(jmpl, sizeof(Instruction)); +} + +// toggledJump binds to `ba,pt disp19`. Toggling it off flips the condition +// field to Never, which preserves the displacement and executes nothing. +/* static */ +void Assembler::ToggleToJmp(CodeLocationLabel inst_) { + Instruction* inst = (Instruction*)inst_.raw(); + MOZ_ASSERT(inst->extractOp() == 0 && inst->extractOp2() == OP2_BPcc); + MOZ_ASSERT(inst->extractCond() == Never); + inst->setData((inst->encode() & ~(0xfu << CondShift)) | + (uint32_t(Always) << CondShift)); + FlushICache(inst, sizeof(Instruction)); +} + +/* static */ +void Assembler::ToggleToCmp(CodeLocationLabel inst_) { + Instruction* inst = (Instruction*)inst_.raw(); + MOZ_ASSERT(inst->extractOp() == 0 && inst->extractOp2() == OP2_BPcc); + MOZ_ASSERT(inst->extractCond() == Always); + inst->setData((inst->encode() & ~(0xfu << CondShift)) | + (uint32_t(Never) << CondShift)); + 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::UpdateLoad64Value(inst, (uint64_t)(rawCode + target)); + } + } +} + +uintptr_t Assembler::GetPointer(uint8_t* instPtr) { + Instruction* inst = (Instruction*)instPtr; + return Assembler::ExtractLoad64Value(inst); +} + +static JitCode* CodeFromJump(Instruction* jump) { + uint8_t* target = (uint8_t*)Assembler::ExtractLoad64Value(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) { + void* ptr = (void*)Assembler::ExtractLoad64Value(inst); + void* prior = ptr; + + uintptr_t word = reinterpret_cast(ptr); + if (word >> JSVAL_TAG_SHIFT) { + Value v = Value::fromRawBits(word); + TraceManuallyBarrieredEdge(trc, &v, "jit-masm-value"); + ptr = (void*)v.bitsAsPunboxPointer(); + } else { + TraceManuallyBarrieredGenericPointerEdge( + trc, reinterpret_cast(&ptr), "jit-masm-ptr"); + } + + if (ptr != prior) { + if (awjc.isNothing()) { + awjc.emplace(code); + } + Assembler::UpdateLoad64Value(inst, uint64_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::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 -Naurp empty/js/src/jit/sparc64/Assembler-sparc64.h firefox-153.0/js/src/jit/sparc64/Assembler-sparc64.h --- firefox-153.0/js/src/jit/sparc64/Assembler-sparc64.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/Assembler-sparc64.h 2026-07-31 10:15:46.144345127 +0200 @@ -0,0 +1,1354 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_Assembler_sparc64_h +#define jit_sparc64_Assembler_sparc64_h + +#include "jit/CompactBuffer.h" +#include "jit/JitCode.h" +#include "jit/JitSpewer.h" +#include "jit/shared/Assembler-shared.h" +#include "jit/shared/Disassembler-shared.h" +#include "jit/shared/IonAssemblerBuffer.h" +#include "jit/shared/IonAssemblerBufferWithConstantPools.h" +#include "jit/sparc64/Architecture-sparc64.h" +#include "wasm/WasmTypeDecls.h" + +namespace js { +namespace jit { + +// GPR constants. SPARC V9 windowed register file: globals, outs, locals, ins. +static constexpr Register g0{Registers::g0}; +static constexpr Register g1{Registers::g1}; +static constexpr Register g2{Registers::g2}; +static constexpr Register g3{Registers::g3}; +static constexpr Register g4{Registers::g4}; +static constexpr Register g5{Registers::g5}; +static constexpr Register g6{Registers::g6}; +static constexpr Register g7{Registers::g7}; +static constexpr Register o0{Registers::o0}; +static constexpr Register o1{Registers::o1}; +static constexpr Register o2{Registers::o2}; +static constexpr Register o3{Registers::o3}; +static constexpr Register o4{Registers::o4}; +static constexpr Register o5{Registers::o5}; +static constexpr Register o6{Registers::o6}; +static constexpr Register o7{Registers::o7}; +static constexpr Register l0{Registers::l0}; +static constexpr Register l1{Registers::l1}; +static constexpr Register l2{Registers::l2}; +static constexpr Register l3{Registers::l3}; +static constexpr Register l4{Registers::l4}; +static constexpr Register l5{Registers::l5}; +static constexpr Register l6{Registers::l6}; +static constexpr Register l7{Registers::l7}; +static constexpr Register i0{Registers::i0}; +static constexpr Register i1{Registers::i1}; +static constexpr Register i2{Registers::i2}; +static constexpr Register i3{Registers::i3}; +static constexpr Register i4{Registers::i4}; +static constexpr Register i5{Registers::i5}; +static constexpr Register i6{Registers::i6}; +static constexpr Register i7{Registers::i7}; + +// FPR constants. The enumerator value is the dense encoding index (d0 -> 0, +// d2 -> 1, ... d62 -> 31); the dN spelling is that index's label, not the +// architectural register it selects. See the encoding note in +// Architecture-sparc64.h. +static constexpr FloatRegister d0{FloatRegisters::d0, FloatRegisters::Double}; +static constexpr FloatRegister d2{FloatRegisters::d2, FloatRegisters::Double}; +static constexpr FloatRegister d4{FloatRegisters::d4, FloatRegisters::Double}; +static constexpr FloatRegister d6{FloatRegisters::d6, FloatRegisters::Double}; +static constexpr FloatRegister d8{FloatRegisters::d8, FloatRegisters::Double}; +static constexpr FloatRegister d10{FloatRegisters::d10, FloatRegisters::Double}; +static constexpr FloatRegister d12{FloatRegisters::d12, FloatRegisters::Double}; +static constexpr FloatRegister d14{FloatRegisters::d14, FloatRegisters::Double}; +static constexpr FloatRegister d16{FloatRegisters::d16, FloatRegisters::Double}; +static constexpr FloatRegister d18{FloatRegisters::d18, FloatRegisters::Double}; +static constexpr FloatRegister d20{FloatRegisters::d20, FloatRegisters::Double}; +static constexpr FloatRegister d22{FloatRegisters::d22, FloatRegisters::Double}; +static constexpr FloatRegister d24{FloatRegisters::d24, FloatRegisters::Double}; +static constexpr FloatRegister d26{FloatRegisters::d26, FloatRegisters::Double}; +static constexpr FloatRegister d28{FloatRegisters::d28, FloatRegisters::Double}; +static constexpr FloatRegister d30{FloatRegisters::d30, FloatRegisters::Double}; +static constexpr FloatRegister d32{FloatRegisters::d32, FloatRegisters::Double}; +static constexpr FloatRegister d34{FloatRegisters::d34, FloatRegisters::Double}; +static constexpr FloatRegister d36{FloatRegisters::d36, FloatRegisters::Double}; +static constexpr FloatRegister d38{FloatRegisters::d38, FloatRegisters::Double}; +static constexpr FloatRegister d40{FloatRegisters::d40, FloatRegisters::Double}; +static constexpr FloatRegister d42{FloatRegisters::d42, FloatRegisters::Double}; +static constexpr FloatRegister d44{FloatRegisters::d44, FloatRegisters::Double}; +static constexpr FloatRegister d46{FloatRegisters::d46, FloatRegisters::Double}; +static constexpr FloatRegister d48{FloatRegisters::d48, FloatRegisters::Double}; +static constexpr FloatRegister d50{FloatRegisters::d50, FloatRegisters::Double}; +static constexpr FloatRegister d52{FloatRegisters::d52, FloatRegisters::Double}; +static constexpr FloatRegister d54{FloatRegisters::d54, FloatRegisters::Double}; +static constexpr FloatRegister d56{FloatRegisters::d56, FloatRegisters::Double}; +static constexpr FloatRegister d58{FloatRegisters::d58, FloatRegisters::Double}; +static constexpr FloatRegister d60{FloatRegisters::d60, FloatRegisters::Double}; +static constexpr FloatRegister d62{FloatRegisters::d62, FloatRegisters::Double}; + +static constexpr Register InvalidReg{Registers::Invalid}; +static constexpr FloatRegister InvalidFloatReg; + +static constexpr Register StackPointer = o6; +// NOT %i6: after `save`, %i6 is physically the caller's %o6, so writing it +// would corrupt the caller's %sp and misdirect its window spill. %l7 belongs +// to the JIT's own window. +static constexpr Register FramePointer = l7; +static constexpr Register ReturnReg = o0; +static constexpr Register64 ReturnReg64(ReturnReg); +static constexpr FloatRegister ReturnFloat32Reg{FloatRegisters::d0, + FloatRegisters::Single}; +static constexpr FloatRegister ReturnDoubleReg = d0; +static constexpr FloatRegister ReturnSimd128Reg{FloatRegisters::d0, + FloatRegisters::Simd128}; + +// The link register written by `call`. Volatile, never allocated. +static constexpr Register LinkRegister = o7; + +static constexpr Register ScratchRegister = g1; +static constexpr Register SecondScratchReg = g2; +// Only used by the static patch helpers, which run where %g1 is dead. +static constexpr Register SavedScratchRegister = g1; + +// %d0 is the ABI double return register, so the scratch lives at the far end +// of the file where no argument or return value can land. Index 30 rather than +// 31: the single and double views of index 31 are %f31 and %f62, two different +// physical registers, while index 30 is %f30 and %d30 and they truly alias. +static constexpr FloatRegister ScratchFloat32Reg{FloatRegisters::d60, + FloatRegisters::Single}; +static constexpr FloatRegister ScratchDoubleReg = d60; +static constexpr FloatRegister ScratchSimd128Reg{FloatRegisters::d60, + FloatRegisters::Simd128}; + +struct ScratchFloat32Scope : public AutoFloatRegisterScope { + explicit ScratchFloat32Scope(MacroAssembler& masm) + : AutoFloatRegisterScope(masm, ScratchFloat32Reg) {} +}; + +struct ScratchDoubleScope : public AutoFloatRegisterScope { + explicit ScratchDoubleScope(MacroAssembler& masm) + : AutoFloatRegisterScope(masm, ScratchDoubleReg) {} +}; + +// Not a scoped acquire: the register is never allocatable, and SIMD helpers +// nest, which would trip AutoFloatRegisterScope's double-acquire assert. +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 = o3; +static constexpr Register PreBarrierReg = o1; +static constexpr Register InterpreterPCReg = l0; + +static constexpr Register CallTempReg0 = o1; +static constexpr Register CallTempReg1 = o4; +static constexpr Register CallTempReg2 = o5; +static constexpr Register CallTempReg3 = l5; +// Must not alias JSReturnReg: LMegamorphicLoadSlotPermissive holds a saved +// object in tempFixed(CallTempReg4) while defining into JSReturnOperand. +static constexpr Register CallTempReg4 = l6; +static constexpr Register CallTempReg5 = o3; + +// SPARC V9 passes only six integer arguments in registers; the window +// registers are the natural overflow temps since they survive a call. +static constexpr Register CallTempNonArgRegs[] = {l1, l2}; +static const uint32_t NumCallTempNonArgRegs = std::size(CallTempNonArgRegs); + +static constexpr Register IntArgReg0 = o0; +static constexpr Register IntArgReg1 = o1; +static constexpr Register IntArgReg2 = o2; +static constexpr Register IntArgReg3 = o3; +static constexpr Register IntArgReg4 = o4; +static constexpr Register IntArgReg5 = o5; + +// 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; + +static constexpr Register JSReturnReg_Type = o3; +static constexpr Register JSReturnReg_Data = o2; +static constexpr Register JSReturnReg = o2; +static constexpr ValueOperand JSReturnOperand = ValueOperand(JSReturnReg); + +static constexpr Register ABINonArgReg0 = l3; +static constexpr Register ABINonArgReg1 = l4; +static constexpr Register ABINonArgReg2 = i0; +static constexpr Register ABINonArgReg3 = i1; +static constexpr Register ABINonArgReturnReg0 = i2; +static constexpr Register ABINonArgReturnReg1 = i3; +static constexpr Register ABINonVolatileReg = l1; +static constexpr Register ABINonArgReturnVolatileReg = g4; + +static constexpr FloatRegister ABINonArgDoubleReg{FloatRegisters::d32, + FloatRegisters::Double}; + +// Wasm instance pointer register. Preserved across wasm function calls. +static constexpr Register InstanceReg = i4; +static constexpr Register HeapReg = i5; +static constexpr Register GlobalReg = i5; + +// 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; + +// Must not be ABINonArgReg0: shared tail-call code parks the callee address +// there while CollapseWasmFrame* overwrites tempForRA. %o7 is architecturally +// the link register but is volatile and non-allocatable, so like PPC64 we use +// a callee-saved register not touched during call setup. +static constexpr Register WasmTailCallInstanceScratchReg = ABINonArgReg1; +static constexpr Register WasmTailCallRAScratchReg = ABINonVolatileReg; +static constexpr Register WasmTailCallFPScratchReg = ABINonArgReg3; + +// Scratch along the return path in the fast js -> wasm stub. Must not overlap +// ReturnReg, JSReturnOperand or InstanceReg, and must be volatile. +static constexpr Register WasmJitEntryReturnScratch = o5; + +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; +// `ta 0x10` — a single-word unconditional trap. +static const uint32_t WasmTrapInstructionLength = 4; + +static constexpr uint32_t WasmCheckedCallEntryOffset = 0u; +static constexpr uint32_t WasmCheckedTailEntryOffset = 32u; + +static constexpr Scale ScalePointer = TimesEight; + +class ABIArgGenerator : public ABIArgGeneratorShared { + public: + explicit ABIArgGenerator(ABIKind kind) + : ABIArgGeneratorShared(kind), slotIndex_(0), current_() { + // ShadowStackSpace is 0: the 176-byte register-window save area is folded + // into the stack access base (SP_BASE = STACK_BIAS + 176), so outgoing + // stack argument 7 is at logical offset 0. See SPARC64-JIT-DESIGN.md 2.1. + } + + ABIArg next(MIRType argType); + ABIArg& current() { return current_; } + + protected: + // The SPARC V9 argument list is a single array of extended-word slots. Slot + // n is %o(n) for an integer argument and %d(2n) for a floating-point one, so + // one shared counter drives both files. + unsigned slotIndex_; + ABIArg current_; +}; + +static constexpr uint32_t NumIntArgRegs = 6; +// %d0,%d2,...,%d30 carry the first 16 prototyped FP arguments, i.e. Double +// indices 0,2,...,30. Slot 15 is the scratch register, but no call the JIT +// emits comes close to sixteen FP arguments. +static constexpr uint32_t NumFloatArgRegs = 16; + +static inline bool GetIntArgReg(uint32_t usedIntArgs, Register* out) { + if (usedIntArgs < NumIntArgRegs) { + *out = Register::FromCode(o0.code() + usedIntArgs); + return true; + } + return false; +} + +static inline bool GetFloatArgReg(uint32_t usedFloatArgs, FloatRegister* out) { + if (usedFloatArgs < NumFloatArgRegs) { + *out = FloatRegister::FromCode(d0.code() + 2 * 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; +} + +// --------------------------------------------------------------------------- +// SPARC V9 instruction encoding. +// +// Format 1 (op=1): disp30 CALL +// Format 2 (op=0): rd/cond | op2 | imm22/disp22/disp19 SETHI, branches +// Format 3 (op=2,3): rd | op3 | rs1 | i | (rs2 | simm13) everything else + +static const uint32_t OpShift = 30; +static const uint32_t RdShift = 25; +static const uint32_t Op2Shift = 22; +static const uint32_t Op3Shift = 19; +static const uint32_t Rs1Shift = 14; +static const uint32_t ImmBitShift = 13; +static const uint32_t OpfShift = 5; +static const uint32_t CondShift = 25; +static const uint32_t AnnulShift = 29; +static const uint32_t CCShift = 20; // cc1:cc0 for BPcc/FBPfcc/MOVcc +static const uint32_t PredictShift = 19; +static const uint32_t AsiShift = 5; +static const uint32_t ShiftXShift = 12; // `x` bit selecting 64-bit shifts + +static const uint32_t RegMask = 0x1f; +static const uint32_t Simm13Mask = 0x1fff; +static const uint32_t Imm22Mask = 0x3fffff; +static const uint32_t Disp19Mask = 0x7ffff; +static const uint32_t Disp22Mask = 0x3fffff; +static const uint32_t Disp30Mask = 0x3fffffff; + +// Top-level formats. +static const uint32_t FMT1 = 1u << OpShift; +static const uint32_t FMT2 = 0u << OpShift; +static const uint32_t FMT3A = 2u << OpShift; // arithmetic / logical / FP +static const uint32_t FMT3B = 3u << OpShift; // loads and stores + +// Format 2 op2 selectors. +enum Op2 : uint32_t { + OP2_ILLTRAP = 0, + OP2_BPcc = 1, + OP2_Bicc = 2, + OP2_BPr = 3, + OP2_SETHI = 4, + OP2_FBPfcc = 5, + OP2_FBfcc = 6, +}; + +// Format 3 op3 selectors, op=2. +enum Op3A : uint32_t { + OP3_ADD = 0x00, OP3_AND = 0x01, OP3_OR = 0x02, OP3_XOR = 0x03, + OP3_SUB = 0x04, OP3_ANDN = 0x05, OP3_ORN = 0x06, OP3_XNOR = 0x07, + OP3_ADDC = 0x08, OP3_MULX = 0x09, OP3_UMUL = 0x0a, OP3_SMUL = 0x0b, + OP3_SUBC = 0x0c, OP3_UDIVX = 0x0d, OP3_UDIV = 0x0e, OP3_SDIV = 0x0f, + OP3_ADDcc = 0x10, OP3_ANDcc = 0x11, OP3_ORcc = 0x12, OP3_XORcc = 0x13, + OP3_SUBcc = 0x14, OP3_ANDNcc = 0x15, OP3_ORNcc = 0x16, OP3_XNORcc = 0x17, + OP3_ADDCcc = 0x18, OP3_UMULcc = 0x1a, OP3_SMULcc = 0x1b, + OP3_SUBCcc = 0x1c, OP3_UDIVcc = 0x1e, OP3_SDIVcc = 0x1f, + OP3_TADDcc = 0x20, OP3_TSUBcc = 0x21, + OP3_SLL = 0x25, OP3_SRL = 0x26, OP3_SRA = 0x27, + OP3_RD = 0x28, OP3_MOVcc = 0x2c, OP3_SDIVX = 0x2d, OP3_POPC = 0x2e, + OP3_MOVr = 0x2f, OP3_WR = 0x30, + OP3_FPop1 = 0x34, OP3_FPop2 = 0x35, OP3_IMPDEP1 = 0x36, + OP3_JMPL = 0x38, OP3_RETURN = 0x39, OP3_Tcc = 0x3a, OP3_FLUSH = 0x3b, + OP3_SAVE = 0x3c, OP3_RESTORE = 0x3d, +}; + +// Format 3 op3 selectors, op=3 (loads and stores). +enum Op3B : uint32_t { + OP3_LDUW = 0x00, OP3_LDUB = 0x01, OP3_LDUH = 0x02, OP3_LDD = 0x03, + OP3_STW = 0x04, OP3_STB = 0x05, OP3_STH = 0x06, OP3_STD = 0x07, + OP3_LDSW = 0x08, OP3_LDSB = 0x09, OP3_LDSH = 0x0a, OP3_LDX = 0x0b, + OP3_LDSTUB = 0x0d, OP3_STX = 0x0e, OP3_SWAP = 0x0f, + OP3_LDF = 0x20, OP3_LDFSR = 0x21, OP3_LDQF = 0x22, OP3_LDDF = 0x23, + OP3_STF = 0x24, OP3_STFSR = 0x25, OP3_STQF = 0x26, OP3_STDF = 0x27, + OP3_PREFETCH = 0x2d, + OP3_CASA = 0x3c, OP3_CASXA = 0x3e, +}; + +// FPop1 opf selectors. +enum Opf : uint32_t { + OPF_FMOVs = 0x001, OPF_FMOVd = 0x002, + OPF_FNEGs = 0x005, OPF_FNEGd = 0x006, + OPF_FABSs = 0x009, OPF_FABSd = 0x00a, + OPF_FSQRTs = 0x029, OPF_FSQRTd = 0x02a, + OPF_FADDs = 0x041, OPF_FADDd = 0x042, + OPF_FSUBs = 0x045, OPF_FSUBd = 0x046, + OPF_FMULs = 0x049, OPF_FMULd = 0x04a, + OPF_FDIVs = 0x04d, OPF_FDIVd = 0x04e, + OPF_FSMULd = 0x069, + OPF_FSTOX = 0x081, OPF_FDTOX = 0x082, + OPF_FXTOS = 0x084, OPF_FXTOD = 0x088, + OPF_FITOS = 0x0c4, OPF_FDTOS = 0x0c6, OPF_FITOD = 0x0c8, OPF_FSTOD = 0x0c9, + OPF_FSTOI = 0x0d1, OPF_FDTOI = 0x0d2, + // FPop2. + OPF_FCMPs = 0x051, OPF_FCMPd = 0x052, + OPF_FCMPEs = 0x055, OPF_FCMPEd = 0x056, + // VIS1 / VIS2 / VIS3 (IMPDEP1). + OPF_ALIGNADDR = 0x018, OPF_BMASK = 0x019, + OPF_FALIGNDATA = 0x048, OPF_BSHUFFLE = 0x04c, + OPF_FPMERGE = 0x04b, + OPF_FPADD16 = 0x050, OPF_FPADD32 = 0x052, + OPF_FPSUB16 = 0x054, OPF_FPSUB32 = 0x056, + OPF_FZERO = 0x060, OPF_FONE = 0x07e, OPF_FSRC1 = 0x074, OPF_FSRC2 = 0x078, + OPF_FNOT1 = 0x06a, OPF_FNOT2 = 0x066, + OPF_FOR = 0x07c, OPF_FAND = 0x070, OPF_FXOR = 0x06c, + OPF_FORNOT1 = 0x07a, OPF_FORNOT2 = 0x076, + OPF_FANDNOT1 = 0x068, OPF_FANDNOT2 = 0x064, + // VIS3 GPR<->FPR moves; the direct analogue of PPC's mtvsrd/mfvsrd. + // Only the 64-bit pair is used; the 32-bit forms are here for completeness + // and are unverified on hardware. Values checked against GNU as -Av9v. + OPF_MOVdTOx = 0x110, OPF_MOVsTOuw = 0x111, OPF_MOVsTOsw = 0x113, + OPF_MOVwTOs = 0x119, OPF_MOVxTOd = 0x118, +}; + +// Address space identifiers used by the load/store-alternate forms. +enum ASI : uint32_t { + ASI_PRIMARY = 0x80, + ASI_PRIMARY_LITTLE = 0x88, +}; + +static inline uint32_t Rd(Register r) { + return (uint32_t(r.code()) & RegMask) << RdShift; +} +static inline uint32_t Rd(FloatRegister r) { + return (uint32_t(r.encoding()) & RegMask) << RdShift; +} +static inline uint32_t Rs1(Register r) { + return (uint32_t(r.code()) & RegMask) << Rs1Shift; +} +static inline uint32_t Rs1(FloatRegister r) { + return (uint32_t(r.encoding()) & RegMask) << Rs1Shift; +} +static inline uint32_t Rs2(Register r) { return uint32_t(r.code()) & RegMask; } +static inline uint32_t Rs2(FloatRegister r) { + return uint32_t(r.encoding()) & RegMask; +} +static inline uint32_t Simm13(int32_t v) { + return (1u << ImmBitShift) | (uint32_t(v) & Simm13Mask); +} + +static inline bool IsInSimm13Range(int64_t v) { + return v >= -4096 && v <= 4095; +} + +// --------------------------------------------------------------------------- + +class Instruction; +class InstReg; +class InstImm; +class BOffImm19; +class JOffImm30; + +static const uint32_t NopInstValue = 0x01000000; // sethi %hi(0), %g0 + +class Instruction { + protected: + uint32_t data; + + public: + Instruction() : data(NopInstValue) {} + explicit Instruction(uint32_t data_) : data(data_) {} + + uint32_t encode() const { return data; } + void setData(uint32_t d) { data = d; } + const Instruction& operator=(const Instruction& src) { + data = src.data; + return *this; + } + + uint32_t extractOp() const { return (data >> OpShift) & 0x3; } + uint32_t extractOp2() const { return (data >> Op2Shift) & 0x7; } + uint32_t extractOp3() const { return (data >> Op3Shift) & 0x3f; } + uint32_t extractRd() const { return (data >> RdShift) & RegMask; } + uint32_t extractRs1() const { return (data >> Rs1Shift) & RegMask; } + uint32_t extractRs2() const { return data & RegMask; } + uint32_t extractCond() const { return (data >> CondShift) & 0xf; } + bool hasImmBit() const { return (data >> ImmBitShift) & 1; } + int32_t extractSimm13() const { + return int32_t(data << 19) >> 19; + } + uint32_t extractImm22() const { return data & Imm22Mask; } + + bool isSethi() const { + return extractOp() == 0 && extractOp2() == OP2_SETHI; + } + bool isCall() const { return extractOp() == 1; } + bool isNop() const { return data == NopInstValue; } + bool isBranch() const { + if (extractOp() != 0) { + return false; + } + uint32_t op2 = extractOp2(); + return op2 == OP2_BPcc || op2 == OP2_Bicc || op2 == OP2_BPr || + op2 == OP2_FBPfcc || op2 == OP2_FBfcc; + } + + void makeNop() { data = NopInstValue; } + + Instruction* next() { return this + 1; } + Instruction* skipPool(); + + InstReg* asReg(); + InstImm* asImm(); +}; + +class InstReg : public Instruction { + public: + explicit InstReg(uint32_t data) : Instruction(data) {} + InstReg(uint32_t fmt, uint32_t op3, Register rd, Register rs1, Register rs2) + : Instruction(fmt | Rd(rd) | (op3 << Op3Shift) | Rs1(rs1) | Rs2(rs2)) {} +}; + +class InstImm : public Instruction { + public: + explicit InstImm(uint32_t data) : Instruction(data) {} + InstImm(uint32_t fmt, uint32_t op3, Register rd, Register rs1, int32_t imm) + : Instruction(fmt | Rd(rd) | (op3 << Op3Shift) | Rs1(rs1) | + Simm13(imm)) {} + + void setSimm13(int32_t imm) { + MOZ_ASSERT(IsInSimm13Range(imm)); + data = (data & ~Simm13Mask) | (uint32_t(imm) & Simm13Mask); + } + void setImm22(uint32_t imm) { + data = (data & ~Imm22Mask) | (imm & Imm22Mask); + } +}; + +// Displacement of a BPcc/FBPfcc branch: 19 signed words, +/-1 MiB. +class BOffImm19 { + uint32_t data; + + public: + explicit BOffImm19(int32_t offset) : data((offset >> 2) & Disp19Mask) { + MOZ_ASSERT((offset & 0x3) == 0); + MOZ_ASSERT(IsInRange(offset)); + } + explicit BOffImm19() : data(INVALID) {} + + static const uint32_t INVALID = 0x00080000; + + uint32_t encode() const { + MOZ_ASSERT(!isInvalid()); + return data; + } + int32_t decode() const { + MOZ_ASSERT(!isInvalid()); + return int32_t(data << 13) >> 11; + } + bool isInvalid() const { return data == INVALID; } + + static bool IsInRange(int64_t offset) { + return offset >= -(1 << 20) && offset < (1 << 20); + } +}; + +// Displacement of a CALL: 30 signed words, +/-2 GiB. +class JOffImm30 { + uint32_t data; + + public: + explicit JOffImm30(int32_t offset) : data((offset >> 2) & Disp30Mask) { + MOZ_ASSERT((offset & 0x3) == 0); + MOZ_ASSERT(IsInRange(offset)); + } + explicit JOffImm30() : data(INVALID) {} + + static const uint32_t INVALID = 0x40000000; + + uint32_t encode() const { + MOZ_ASSERT(!isInvalid()); + return data; + } + int32_t decode() const { + MOZ_ASSERT(!isInvalid()); + return int32_t(data << 2); + } + bool isInvalid() const { return data == INVALID; } + + static bool IsInRange(int64_t offset) { + return offset >= -(int64_t(1) << 31) && offset < (int64_t(1) << 31); + } +}; + +class Imm16 { + uint16_t value; + + public: + explicit Imm16(uint32_t imm) : value(imm) {} + Imm16() : value(0) {} + uint32_t encode() const { return value; } + int32_t decodeSigned() const { return int16_t(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); + } +}; + +static const uint32_t NopInst = NopInstValue; + +using SPARCBuffer = js::jit::AssemblerBufferWithConstantPools< + Instruction, Assembler, + js::jit::AssemblerBufferSettings{ + .instSize = 4, + .guardSize = 2, // branch + delay slot + .headerSize = 1, + .pcBias = 0, + .alignFillInst = NopInst, + .nopFillInst = NopInst, + }>; + +class SPARCBufferWithExecutableCopy : public SPARCBuffer { + public: + SPARCBufferWithExecutableCopy(size_t poolMaxOffset, unsigned nopFill) + : SPARCBuffer(poolMaxOffset, nopFill) {} +}; + +class Assembler : public AssemblerShared { + public: + // Tags encoded in the trap word of a `ta` used as a patch placeholder. + enum TrapTag { + BTag = 2, + BCTag = 4, + CallTag = 6, + DebugTag0 = 10, + DebugTag1 = 12, + DebugTag2 = 14 + }; + + enum PoolLoadType { + PoolLoadFPR64 = 1, + PoolLoadSimd128 = 2, + PoolLoadFPR32 = 3, + }; + + // The 4-bit cond field of Bicc/BPcc. Signed and unsigned variants differ + // only in which flags they inspect, so they are distinct encodings here. + enum Condition { + Never = 0x0, + Equal = 0x1, + LessThanOrEqual = 0x2, + LessThan = 0x3, + BelowOrEqual = 0x4, + Below = 0x5, + Signed = 0x6, + Overflow = 0x7, + Always = 0x8, + NotEqual = 0x9, + GreaterThan = 0xa, + GreaterThanOrEqual = 0xb, + Above = 0xc, + AboveOrEqual = 0xd, + NotSigned = 0xe, + NotOverflow = 0xf, + + Zero = Equal, + NonZero = NotEqual, + CarrySet = Below, + CarryClear = AboveOrEqual, + }; + + // The 4-bit cond field of FBPfcc. %fcc encodes 0=equal, 1=less, + // 2=greater, 3=unordered, so every SpiderMonkey ordering maps directly. + enum DoubleCondition { + DoubleNever = 0x0, + DoubleNotEqualOrUnordered = 0x1, + DoubleLessThanOrGreaterThan = 0x2, + DoubleLessThanOrUnordered = 0x3, + DoubleLessThan = 0x4, + DoubleGreaterThanOrUnordered = 0x5, + DoubleGreaterThan = 0x6, + DoubleUnordered = 0x7, + DoubleAlways = 0x8, + DoubleEqual = 0x9, + DoubleEqualOrUnordered = 0xa, + DoubleGreaterThanOrEqual = 0xb, + DoubleGreaterThanOrEqualOrUnordered = 0xc, + DoubleLessThanOrEqual = 0xd, + DoubleLessThanOrEqualOrUnordered = 0xe, + DoubleOrdered = 0xf, + + DoubleNotEqual = DoubleLessThanOrGreaterThan, + }; + + // cc1:cc0 selector for BPcc/MOVcc/Tcc. + enum ConditionCode { + CC_ICC = 0, // 32-bit condition codes + CC_XCC = 2, // 64-bit condition codes + }; + + // cc1:cc0 selector for FBPfcc. + enum FloatConditionCode { + CC_FCC0 = 0, + CC_FCC1 = 1, + CC_FCC2 = 2, + CC_FCC3 = 3, + }; + + enum BranchPredict { + NotTaken = 0, + Taken = 1, + }; + + enum Annul { + NoAnnul = 0, + DoAnnul = 1, + }; + + enum LinkBit { DontLinkB = 0, LinkB = 1 }; + enum FloatFormat { SingleFloat, DoubleFloat }; + enum FloatTestKind { TestForTrue, TestForFalse }; + + BufferOffset nextOffset() { return m_buffer.nextOffset(); } + + protected: + Instruction* editSrc(BufferOffset bo) { + if (!bo.assigned()) { + 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_; + + SPARCBufferWithExecutableCopy m_buffer; + +#ifdef JS_JITSPEW + Sprinter* printer; +#endif + + public: + // A pool load is `lddf/ldf [rbase + simm13], fd`, so the furthest reachable + // entry is 4095 bytes past the base; the base is the second word of the + // load stanza, and the margin covers the guard, header and rounding. + Assembler() + : m_buffer(/* poolMaxOffset */ 4000, /* nopFill */ 0), +#ifdef JS_JITSPEW + printer(nullptr), +#endif + isFinished(false), + scratch_register_list_((1 << Registers::g1) | (1 << Registers::g2)) { + } + + void setUnlimitedBuffer() { m_buffer.setUnlimited(); } + + 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(SPARCBuffer*, 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(); + } + 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(); + + // --- Format 3 arithmetic and logical. + BufferOffset as_alu(Op3A op3, Register rd, Register rs1, Register rs2) { + return writeInst(FMT3A | Rd(rd) | (uint32_t(op3) << Op3Shift) | Rs1(rs1) | + Rs2(rs2)); + } + BufferOffset as_alu(Op3A op3, Register rd, Register rs1, int32_t imm) { + MOZ_ASSERT(IsInSimm13Range(imm)); + return writeInst(FMT3A | Rd(rd) | (uint32_t(op3) << Op3Shift) | Rs1(rs1) | + Simm13(imm)); + } + + BufferOffset as_add(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_ADD, rd, rs1, rs2); + } + BufferOffset as_add(Register rd, Register rs1, int32_t imm) { + return as_alu(OP3_ADD, rd, rs1, imm); + } + BufferOffset as_addcc(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_ADDcc, rd, rs1, rs2); + } + BufferOffset as_addcc(Register rd, Register rs1, int32_t imm) { + return as_alu(OP3_ADDcc, rd, rs1, imm); + } + BufferOffset as_sub(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_SUB, rd, rs1, rs2); + } + BufferOffset as_sub(Register rd, Register rs1, int32_t imm) { + return as_alu(OP3_SUB, rd, rs1, imm); + } + BufferOffset as_subcc(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_SUBcc, rd, rs1, rs2); + } + BufferOffset as_subcc(Register rd, Register rs1, int32_t imm) { + return as_alu(OP3_SUBcc, rd, rs1, imm); + } + BufferOffset as_and(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_AND, rd, rs1, rs2); + } + BufferOffset as_and(Register rd, Register rs1, int32_t imm) { + return as_alu(OP3_AND, rd, rs1, imm); + } + BufferOffset as_andcc(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_ANDcc, rd, rs1, rs2); + } + BufferOffset as_andcc(Register rd, Register rs1, int32_t imm) { + return as_alu(OP3_ANDcc, rd, rs1, imm); + } + BufferOffset as_andn(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_ANDN, rd, rs1, rs2); + } + BufferOffset as_or(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_OR, rd, rs1, rs2); + } + BufferOffset as_or(Register rd, Register rs1, int32_t imm) { + return as_alu(OP3_OR, rd, rs1, imm); + } + BufferOffset as_orcc(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_ORcc, rd, rs1, rs2); + } + BufferOffset as_orn(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_ORN, rd, rs1, rs2); + } + BufferOffset as_xor(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_XOR, rd, rs1, rs2); + } + BufferOffset as_xor(Register rd, Register rs1, int32_t imm) { + return as_alu(OP3_XOR, rd, rs1, imm); + } + BufferOffset as_xnor(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_XNOR, rd, rs1, rs2); + } + BufferOffset as_mulx(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_MULX, rd, rs1, rs2); + } + BufferOffset as_mulx(Register rd, Register rs1, int32_t imm) { + return as_alu(OP3_MULX, rd, rs1, imm); + } + BufferOffset as_sdivx(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_SDIVX, rd, rs1, rs2); + } + BufferOffset as_udivx(Register rd, Register rs1, Register rs2) { + return as_alu(OP3_UDIVX, rd, rs1, rs2); + } + BufferOffset as_popc(Register rd, Register rs2) { + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_POPC) << Op3Shift) | + Rs2(rs2)); + } + + // Shifts. The `x` bit selects the 64-bit form; shift counts are 6-bit. + BufferOffset as_shift(Op3A op3, Register rd, Register rs1, Register rs2, + bool isX) { + return writeInst(FMT3A | Rd(rd) | (uint32_t(op3) << Op3Shift) | Rs1(rs1) | + (uint32_t(isX) << ShiftXShift) | Rs2(rs2)); + } + BufferOffset as_shift(Op3A op3, Register rd, Register rs1, uint32_t sh, + bool isX) { + MOZ_ASSERT(sh < (isX ? 64u : 32u)); + return writeInst(FMT3A | Rd(rd) | (uint32_t(op3) << Op3Shift) | Rs1(rs1) | + (1u << ImmBitShift) | (uint32_t(isX) << ShiftXShift) | sh); + } + BufferOffset as_sll(Register rd, Register rs1, Register rs2) { + return as_shift(OP3_SLL, rd, rs1, rs2, false); + } + BufferOffset as_sll(Register rd, Register rs1, uint32_t sh) { + return as_shift(OP3_SLL, rd, rs1, sh, false); + } + BufferOffset as_srl(Register rd, Register rs1, Register rs2) { + return as_shift(OP3_SRL, rd, rs1, rs2, false); + } + BufferOffset as_srl(Register rd, Register rs1, uint32_t sh) { + return as_shift(OP3_SRL, rd, rs1, sh, false); + } + BufferOffset as_sra(Register rd, Register rs1, Register rs2) { + return as_shift(OP3_SRA, rd, rs1, rs2, false); + } + BufferOffset as_sra(Register rd, Register rs1, uint32_t sh) { + return as_shift(OP3_SRA, rd, rs1, sh, false); + } + BufferOffset as_sllx(Register rd, Register rs1, Register rs2) { + return as_shift(OP3_SLL, rd, rs1, rs2, true); + } + BufferOffset as_sllx(Register rd, Register rs1, uint32_t sh) { + return as_shift(OP3_SLL, rd, rs1, sh, true); + } + BufferOffset as_srlx(Register rd, Register rs1, Register rs2) { + return as_shift(OP3_SRL, rd, rs1, rs2, true); + } + BufferOffset as_srlx(Register rd, Register rs1, uint32_t sh) { + return as_shift(OP3_SRL, rd, rs1, sh, true); + } + BufferOffset as_srax(Register rd, Register rs1, Register rs2) { + return as_shift(OP3_SRA, rd, rs1, rs2, true); + } + BufferOffset as_srax(Register rd, Register rs1, uint32_t sh) { + return as_shift(OP3_SRA, rd, rs1, sh, true); + } + + BufferOffset as_sethi(Register rd, uint32_t imm22) { + return writeInst(FMT2 | Rd(rd) | (uint32_t(OP2_SETHI) << Op2Shift) | + (imm22 & Imm22Mask)); + } + + // --- Loads and stores. SPARC addressing is [rs1 + rs2] or [rs1 + simm13]. + BufferOffset as_load(Op3B op3, Register rd, Register base, int32_t off) { + MOZ_ASSERT(IsInSimm13Range(off)); + return writeInst(FMT3B | Rd(rd) | (uint32_t(op3) << Op3Shift) | Rs1(base) | + Simm13(off)); + } + BufferOffset as_load(Op3B op3, Register rd, Register base, Register index) { + return writeInst(FMT3B | Rd(rd) | (uint32_t(op3) << Op3Shift) | Rs1(base) | + Rs2(index)); + } + BufferOffset as_loadf(Op3B op3, FloatRegister rd, Register base, + int32_t off) { + MOZ_ASSERT(IsInSimm13Range(off)); + return writeInst(FMT3B | Rd(rd) | (uint32_t(op3) << Op3Shift) | Rs1(base) | + Simm13(off)); + } + BufferOffset as_loadf(Op3B op3, FloatRegister rd, Register base, + Register index) { + return writeInst(FMT3B | Rd(rd) | (uint32_t(op3) << Op3Shift) | Rs1(base) | + Rs2(index)); + } + + BufferOffset as_ldx(Register rd, Register base, int32_t off) { + return as_load(OP3_LDX, rd, base, off); + } + BufferOffset as_ldx(Register rd, Register base, Register index) { + return as_load(OP3_LDX, rd, base, index); + } + BufferOffset as_lduw(Register rd, Register base, int32_t off) { + return as_load(OP3_LDUW, rd, base, off); + } + BufferOffset as_ldsw(Register rd, Register base, int32_t off) { + return as_load(OP3_LDSW, rd, base, off); + } + BufferOffset as_lduh(Register rd, Register base, int32_t off) { + return as_load(OP3_LDUH, rd, base, off); + } + BufferOffset as_ldsh(Register rd, Register base, int32_t off) { + return as_load(OP3_LDSH, rd, base, off); + } + BufferOffset as_ldub(Register rd, Register base, int32_t off) { + return as_load(OP3_LDUB, rd, base, off); + } + BufferOffset as_ldsb(Register rd, Register base, int32_t off) { + return as_load(OP3_LDSB, rd, base, off); + } + BufferOffset as_stx(Register rd, Register base, int32_t off) { + return as_load(OP3_STX, rd, base, off); + } + BufferOffset as_stx(Register rd, Register base, Register index) { + return as_load(OP3_STX, rd, base, index); + } + BufferOffset as_stw(Register rd, Register base, int32_t off) { + return as_load(OP3_STW, rd, base, off); + } + BufferOffset as_sth(Register rd, Register base, int32_t off) { + return as_load(OP3_STH, rd, base, off); + } + BufferOffset as_stb(Register rd, Register base, int32_t off) { + return as_load(OP3_STB, rd, base, off); + } + BufferOffset as_lddf(FloatRegister rd, Register base, int32_t off) { + return as_loadf(OP3_LDDF, rd, base, off); + } + BufferOffset as_lddf(FloatRegister rd, Register base, Register index) { + return as_loadf(OP3_LDDF, rd, base, index); + } + BufferOffset as_ldf(FloatRegister rd, Register base, int32_t off) { + return as_loadf(OP3_LDF, rd, base, off); + } + BufferOffset as_stdf(FloatRegister rd, Register base, int32_t off) { + return as_loadf(OP3_STDF, rd, base, off); + } + BufferOffset as_stdf(FloatRegister rd, Register base, Register index) { + return as_loadf(OP3_STDF, rd, base, index); + } + BufferOffset as_stf(FloatRegister rd, Register base, int32_t off) { + return as_loadf(OP3_STF, rd, base, off); + } + + // Compare-and-swap, the basis of every atomic op. + BufferOffset as_casa(Register rd, Register base, Register rs2, uint32_t asi) { + return writeInst(FMT3B | Rd(rd) | (uint32_t(OP3_CASA) << Op3Shift) | + Rs1(base) | (asi << AsiShift) | Rs2(rs2)); + } + BufferOffset as_casxa(Register rd, Register base, Register rs2, + uint32_t asi) { + return writeInst(FMT3B | Rd(rd) | (uint32_t(OP3_CASXA) << Op3Shift) | + Rs1(base) | (asi << AsiShift) | Rs2(rs2)); + } + + // --- Floating point. + BufferOffset as_fpop1(Opf opf, FloatRegister rd, FloatRegister rs1, + FloatRegister rs2) { + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_FPop1) << Op3Shift) | + Rs1(rs1) | (uint32_t(opf) << OpfShift) | Rs2(rs2)); + } + BufferOffset as_fpop1(Opf opf, FloatRegister rd, FloatRegister rs2) { + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_FPop1) << Op3Shift) | + (uint32_t(opf) << OpfShift) | Rs2(rs2)); + } + BufferOffset as_fcmp(Opf opf, FloatConditionCode fcc, FloatRegister rs1, + FloatRegister rs2) { + return writeInst(FMT3A | (uint32_t(fcc) << RdShift) | + (uint32_t(OP3_FPop2) << Op3Shift) | Rs1(rs1) | + (uint32_t(opf) << OpfShift) | Rs2(rs2)); + } + // VIS / IMPDEP1, including the VIS3 GPR<->FPR moves. + BufferOffset as_vis(Opf opf, FloatRegister rd, FloatRegister rs1, + FloatRegister rs2) { + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_IMPDEP1) << Op3Shift) | + Rs1(rs1) | (uint32_t(opf) << OpfShift) | Rs2(rs2)); + } + BufferOffset as_movxtod(FloatRegister rd, Register rs2) { + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_IMPDEP1) << Op3Shift) | + (uint32_t(OPF_MOVxTOd) << OpfShift) | Rs2(rs2)); + } + BufferOffset as_movdtox(Register rd, FloatRegister rs2) { + return writeInst(FMT3A | Rd(rd) | (uint32_t(OP3_IMPDEP1) << Op3Shift) | + (uint32_t(OPF_MOVdTOx) << OpfShift) | Rs2(rs2)); + } + + // --- Control transfer. Every one of these emits two words: the transfer + // itself and a nop in its delay slot, in a single buffer transaction so a + // constant pool can never be dumped between them. Callers must not emit + // the delay slot themselves. + BufferOffset as_bpcc(Condition cond, ConditionCode cc, BOffImm19 off, + Annul a = NoAnnul, BranchPredict p = Taken); + BufferOffset as_fbpfcc(DoubleCondition cond, FloatConditionCode fcc, + BOffImm19 off, Annul a = NoAnnul, + BranchPredict p = Taken); + BufferOffset as_call(JOffImm30 off); + BufferOffset as_jmpl(Register rd, Register rs1, int32_t off); + BufferOffset as_jmpl(Register rd, Register rs1, Register rs2); + BufferOffset as_retl(); + BufferOffset as_save(Register rd, Register rs1, int32_t imm); + BufferOffset as_restore(Register rd, Register rs1, Register rs2); + BufferOffset as_ta(uint32_t trap); + BufferOffset as_illtrap(uint32_t payload); + BufferOffset as_flushw(); + + // Conditional move; SPARC's answer to a branchless select. + BufferOffset as_movcc(Condition cond, ConditionCode cc, Register rd, + Register rs2); + BufferOffset as_movcc(Condition cond, ConditionCode cc, Register rd, + int32_t imm); + + 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 bool SupportsFloatingPoint() { return true; } + // VIS is a 64-bit partitioned-arithmetic unit, not a 128-bit lane machine; + // there is no reasonable v128 lowering. See SPARC64-JIT-DESIGN.md section 4. + static bool SupportsWasmSimd() { return false; } + static bool SupportsUnalignedAccesses() { return false; } + static bool SupportsFastUnalignedFPAccesses() { return false; } + static bool SupportsFloat64To16() { return false; } + static bool SupportsFloat32To16() { return false; } + static bool HasRoundInstruction(RoundingMode mode) { return false; } + + void addPendingJump(BufferOffset src, ImmPtr target, RelocationKind kind) { + enoughMemory_ &= jumps_.append(RelativePatch(src, target.value, kind)); + if (kind == RelocationKind::JITCODE) { + jumpRelocations_.writeUnsigned(src.getOffset()); + } + } + + 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)); + } + + void flushBuffer() { m_buffer.flushPool(); } + void comment(const char* msg) { spew("; %s", msg); } + static uint32_t NopSize() { return 4; } + + // The fixed-length sequence ma_liPatchable emits, so a 64-bit constant can + // be rewritten in place: sethi/or/sllx/sethi/or/or. + static const uint32_t Load64InstructionCount = 6; + + // Footprint of a patchable branch/call stanza, and of the placeholder a + // forward reference to an unbound label reserves: Load64InstructionCount + // followed by `jmpl` and its delay-slot nop. See Assembler-sparc64.cpp for + // the stanza shapes bind() recognises. + static const uint32_t PatchableBranchInstructionCount = + Load64InstructionCount + 2; + + static uint64_t ExtractLoad64Value(Instruction* inst0); + static void UpdateLoad64Value(Instruction* inst0, uint64_t value); + static void WriteLoad64Instructions(Instruction* inst0, Register reg, + uint64_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& heapAccess) { + } + + 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); + + GeneralRegisterSet* GetScratchRegisterList() { + return &scratch_register_list_; + } + + protected: + GeneralRegisterSet scratch_register_list_; +}; + +inline bool IsUnaligned(const wasm::MemoryAccessDesc& access) { + if (!access.align()) { + return false; + } + return access.align() < access.byteSize(); +} + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_Assembler_sparc64_h */ diff -Naurp empty/js/src/jit/sparc64/CodeGenerator-sparc64.cpp firefox-153.0/js/src/jit/sparc64/CodeGenerator-sparc64.cpp --- firefox-153.0/js/src/jit/sparc64/CodeGenerator-sparc64.cpp 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/CodeGenerator-sparc64.cpp 2026-07-30 00:55:40.565971819 +0200 @@ -0,0 +1,2430 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#include "jit/sparc64/CodeGenerator-sparc64.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 { + +CodeGeneratorSPARC64::CodeGeneratorSPARC64(MIRGenerator* gen, LIRGraph* graph, + MacroAssembler* masm, + const wasm::CodeMetadata* codeMeta) + : CodeGeneratorShared(gen, graph, masm, codeMeta) {} + +MoveOperand CodeGeneratorSPARC64::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 CodeGeneratorSPARC64::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) { + masm.subPtr(Imm32(sizeof(Value)), 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 CodeGeneratorSPARC64::bailout(LSnapshot* snapshot) { + Label label; + masm.jump(&label); + bailoutFrom(&label, snapshot); +} + +void CodeGeneratorSPARC64::bailoutIfFalseBool(Register lhs, + LSnapshot* snapshot) { + Label bail; + masm.branchTest32(Assembler::Zero, lhs, Imm32(0xFF), &bail); + bailoutFrom(&bail, snapshot); +} + +bool CodeGeneratorSPARC64::generateOutOfLineCode() { + if (!CodeGeneratorShared::generateOutOfLineCode()) { + return false; + } + + if (deoptLabel_.used()) { + masm.bind(&deoptLabel_); + + // The frame size is handed to GenerateBailoutThunk in the link register, + // which PushBailoutFrame pushes as the return address slot. + masm.movePtr(ImmWord(frameSize()), LinkRegister); + + TrampolinePtr handler = gen->jitRuntime()->getGenericBailoutHandler(); + masm.jump(handler); + } + + return !masm.oom(); +} + +void CodeGeneratorSPARC64::branchToBlock(MBasicBlock* block) { + Label* label = skipTrivialBlocks(block)->lir()->label(); + masm.jump(label); +} + +void CodeGeneratorSPARC64::branchToBlock(Assembler::DoubleCondition cond, + FloatRegister lhs, FloatRegister rhs, + MBasicBlock* mir) { + Label* label = skipTrivialBlocks(mir)->lir()->label(); + masm.branchDouble(cond, lhs, rhs, label); +} + +void CodeGeneratorSPARC64::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(CodeGeneratorSPARC64* codegen) { + codegen->visitOutOfLineTableSwitch(this); + } + + public: + explicit OutOfLineTableSwitch(MTableSwitch* mir) : mir_(mir) {} + + MTableSwitch* mir() const { return mir_; } + CodeLabel* jumpLabel() { return &jumpLabel_; } +}; + +void CodeGeneratorSPARC64::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 CodeGeneratorSPARC64::generateInvalidateEpilogue() { + // Pad 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_); + + masm.pushReturnAddress(); + + invalidateEpilogueData_ = masm.pushWithPatch(ImmWord(uintptr_t(-1))); + + TrampolinePtr thunk = gen->jitRuntime()->getInvalidationThunk(); + masm.jump(thunk); +} + +void CodeGeneratorSPARC64::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 CodeGeneratorSPARC64::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 CodeGeneratorSPARC64::emitBigIntPtrDiv(LBigIntPtrDiv* ins, + Register dividend, Register divisor, + Register output) { + masm.as_sdivx(output, dividend, divisor); +} + +void CodeGeneratorSPARC64::emitBigIntPtrMod(LBigIntPtrMod* ins, + Register dividend, Register divisor, + Register output) { + // SPARC V9 has no remainder instruction: r = a - (a / b) * b. + masm.as_sdivx(output, dividend, divisor); + masm.as_mulx(output, output, divisor); + masm.as_sub(output, dividend, output); +} + +// =============================================================== +// Visitors: Box/Unbox + +void CodeGenerator::visitBox(LBox* box) { + const LAllocation* in = box->getOperand(0); + ValueOperand result = ToOutValue(box); + + masm.moveValue(TypedOrValueRegister(box->type(), ToAnyRegister(in)), result); +} + +void CodeGenerator::visitUnbox(LUnbox* unbox) { + MUnbox* mir = unbox->mir(); + + Register result = ToRegister(unbox->output()); + + if (mir->fallible()) { + ValueOperand value = ToValue(unbox->input()); + Label bail; + switch (mir->type()) { + case MIRType::Int32: + masm.fallibleUnboxInt32(value, result, &bail); + break; + case MIRType::Boolean: + masm.fallibleUnboxBoolean(value, result, &bail); + break; + case MIRType::Object: + masm.fallibleUnboxObject(value, result, &bail); + break; + case MIRType::String: + masm.fallibleUnboxString(value, result, &bail); + break; + case MIRType::Symbol: + masm.fallibleUnboxSymbol(value, result, &bail); + break; + case MIRType::BigInt: + masm.fallibleUnboxBigInt(value, result, &bail); + break; + default: + MOZ_CRASH("Given MIRType cannot be unboxed."); + } + bailoutFrom(&bail, unbox->snapshot()); + return; + } + + LAllocation* input = unbox->getOperand(LUnbox::Input); + if (input->isGeneralReg()) { + Register inputReg = ToRegister(input); + switch (mir->type()) { + case MIRType::Int32: + masm.unboxInt32(ValueOperand(inputReg), result); + break; + case MIRType::Boolean: + masm.unboxBoolean(ValueOperand(inputReg), result); + break; + case MIRType::Object: + masm.unboxObject(ValueOperand(inputReg), result); + break; + case MIRType::String: + masm.unboxString(ValueOperand(inputReg), result); + break; + case MIRType::Symbol: + masm.unboxSymbol(ValueOperand(inputReg), result); + break; + case MIRType::BigInt: + masm.unboxBigInt(ValueOperand(inputReg), result); + break; + default: + MOZ_CRASH("Given MIRType cannot be unboxed."); + } + return; + } + + Address inputAddr = ToAddress(input); + switch (mir->type()) { + case MIRType::Int32: + masm.unboxInt32(inputAddr, result); + break; + case MIRType::Boolean: + masm.unboxBoolean(inputAddr, result); + break; + case MIRType::Object: + masm.unboxObject(inputAddr, result); + break; + case MIRType::String: + masm.unboxString(inputAddr, result); + break; + case MIRType::Symbol: + masm.unboxSymbol(inputAddr, result); + break; + case MIRType::BigInt: + masm.unboxBigInt(inputAddr, result); + break; + default: + MOZ_CRASH("Given MIRType cannot be unboxed."); + } +} + +// =============================================================== +// Visitors: Integer Arithmetic +// +// Int32 values live sign-extended in a 64-bit GPR, so every 32-bit result is +// re-canonicalised with `sra rd, 0` and a 32-bit overflow is detected by +// comparing the 64-bit result against its own sign-extension. + +void CodeGenerator::visitAddI(LAddI* ins) { + LAllocation* lhs = ins->getOperand(0); + 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); + } + } else if (ins->snapshot()) { + // Use the hardware %icc overflow bit rather than comparing the 64-bit sum + // against its own sign extension: an operand may arrive zero-extended + // (branchTruncateDoubleMaybeModUint32 does that), which makes the + // comparison report overflow for any negative result. + masm.as_addcc(dest, ToRegister(lhs), ToRegister(rhs)); + masm.as_sra(dest, dest, 0); + Label overflow; + masm.ma_bc(Assembler::Overflow, Assembler::CC_ICC, &overflow); + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.as_add(dest, ToRegister(lhs), ToRegister(rhs)); + masm.as_sra(dest, dest, 0); + } +} + +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) { + Register dest = ToRegister(lir->getDef(0)); + Register lhs = ToRegister(lir->getOperand(0)); + const LAllocation* rhs = lir->getOperand(1); + + if (rhs->isConstant()) { + if (lhs != dest) { + masm.movePtr(lhs, dest); + } + masm.addPtr(ImmWord(ToInt64(rhs)), dest); + } else { + masm.as_add(dest, lhs, ToRegister(rhs)); + } +} + +void CodeGenerator::visitSubI(LSubI* ins) { + LAllocation* lhs = ins->getOperand(0); + 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); + } + } else if (ins->snapshot()) { + // See visitAddI: %icc is immune to a non-sign-extended operand. + masm.as_subcc(dest, ToRegister(lhs), ToRegister(rhs)); + masm.as_sra(dest, dest, 0); + Label overflow; + masm.ma_bc(Assembler::Overflow, Assembler::CC_ICC, &overflow); + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.as_sub(dest, ToRegister(lhs), ToRegister(rhs)); + masm.as_sra(dest, dest, 0); + } +} + +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(ToIntPtr(rhs)), dest); + } else { + masm.as_sub(dest, lhs, ToRegister(rhs)); + } +} + +void CodeGenerator::visitSubI64(LSubI64* lir) { + Register dest = ToRegister(lir->getDef(0)); + Register lhs = ToRegister(lir->getOperand(0)); + const LAllocation* rhs = lir->getOperand(1); + + if (rhs->isConstant()) { + if (lhs != dest) { + masm.movePtr(lhs, dest); + } + masm.sub64(Imm64(ToInt64(rhs)), Register64(dest)); + } else { + masm.as_sub(dest, lhs, ToRegister(rhs)); + } +} + +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); + Register src = lhs; + + // 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, src, Imm32(0), ins->snapshot()); + } + + switch (constant) { + case -1: + if (mul->canOverflow()) { + Label ok; + masm.branch32(Assembler::NotEqual, lhs, Imm32(INT32_MIN), &ok); + bailout(ins->snapshot()); + masm.bind(&ok); + } + masm.as_sub(dest, g0, src); + masm.as_sra(dest, dest, 0); + return; + case 0: + masm.move32(Imm32(0), dest); + return; + case 1: + masm.move32(src, dest); + return; + case 2: + if (mul->canOverflow()) { + masm.move32(src, dest); + Label overflow; + masm.branchAdd32(Assembler::Overflow, dest, dest, &overflow); + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.move32(src, dest); + masm.add32(dest, dest); + } + return; + default: + break; + } + + uint32_t absCst = mozilla::Abs(constant); + if (absCst > 0 && (absCst & (absCst - 1)) == 0 && !mul->canOverflow()) { + uint32_t shift = mozilla::FloorLog2(absCst); + masm.lshift32(Imm32(shift), src, dest); + if (constant < 0) { + masm.as_sub(dest, g0, dest); + } + masm.as_sra(dest, dest, 0); + return; + } + + if (mul->canOverflow()) { + masm.move32(src, dest); + Label overflow; + masm.branchMul32(Assembler::Overflow, Imm32(constant), dest, &overflow); + bailoutFrom(&overflow, ins->snapshot()); + } else { + masm.move32(src, dest); + masm.mul32(Imm32(constant), dest); + } + + if (mul->canBeNegativeZero() && constant < 0) { + Label ok; + masm.branch32(Assembler::NotEqual, dest, Imm32(0), &ok); + bailoutTest32(Assembler::Signed, src, src, ins->snapshot()); + masm.bind(&ok); + } + return; + } + + Register rhsReg = ToRegister(rhs); + + // mulx gives the exact 64-bit product of two sign-extended int32 operands, + // so a 32-bit overflow is exactly "the product is not its own sign + // extension". + masm.as_mulx(dest, lhs, rhsReg); + if (mul->canOverflow()) { + masm.as_sra(SecondScratchReg, dest, 0); + bailoutCmpPtr(Assembler::NotEqual, dest, SecondScratchReg, ins->snapshot()); + } else { + masm.as_sra(dest, dest, 0); + } + + if (mul->canBeNegativeZero()) { + Label done; + masm.branch32(Assembler::NotEqual, dest, Imm32(0), &done); + masm.as_or(SecondScratchReg, lhs, rhsReg); + bailoutTest32(Assembler::Signed, SecondScratchReg, SecondScratchReg, + ins->snapshot()); + masm.bind(&done); + } +} + +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_mulx(dest, lhs, ToRegister(rhs)); + } +} + +void CodeGenerator::visitMulI64(LMulI64* lir) { + Register dest = ToRegister(lir->getDef(0)); + Register lhs = ToRegister(lir->getOperand(0)); + const LAllocation* rhs = lir->getOperand(1); + + if (rhs->isConstant()) { + if (lhs != dest) { + masm.movePtr(lhs, dest); + } + masm.mulPtr(ImmWord(ToInt64(rhs)), dest); + } else { + masm.as_mulx(dest, lhs, ToRegister(rhs)); + } +} + +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()); + } + } + + 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); + } + + // Both operands are sign-extended int32, so the 64-bit divide yields the + // 32-bit quotient (INT32_MIN / -1 is filtered out above). + masm.as_sdivx(dest, lhs, rhs); + masm.as_sra(dest, dest, 0); + + if (!mir->canTruncateRemainder()) { + masm.as_mulx(temp, dest, rhs); + masm.as_sub(temp, lhs, temp); + 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()); + Register tmp = SecondScratchReg; + int32_t shift = ins->shift(); + + if (shift == 0) { + masm.move32(lhs, dest); + return; + } + + MDiv* mir = ins->mir(); + + if (!mir->isTruncated()) { + // Bail out if any of the low `shift` bits is set. + masm.as_sll(tmp, lhs, 32 - shift); + bailoutCmp32(Assembler::NotEqual, tmp, Imm32(0), ins->snapshot()); + } + + if (!mir->canBeNegativeDividend()) { + masm.as_sra(dest, lhs, shift); + } else { + // Round toward zero: add (1 << shift) - 1 when the dividend is negative. + masm.as_srax(tmp, lhs, 63); + masm.as_srlx(tmp, tmp, 64 - shift); + masm.as_add(tmp, tmp, lhs); + masm.as_sra(dest, tmp, shift); + } +} + +void CodeGenerator::visitModI(LModI* ins) { + Register lhs = ToRegister(ins->lhs()); + Register rhs = ToRegister(ins->rhs()); + Register dest = ToRegister(ins->output()); + Register temp = SecondScratchReg; + 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 { + 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()); + } + } + + // sdivx of INT32_MIN by -1 is representable in 64 bits, but the wasm spec + // (and JS) define rem_s(MIN, -1) as 0; take the shortcut explicitly. + 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_sdivx(temp, lhs, rhs); + masm.as_mulx(temp, temp, rhs); + masm.as_sub(dest, lhs, temp); + masm.as_sra(dest, dest, 0); + + 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; + + if (mir->canBeNegativeDividend() && !mir->isTruncated()) { + Label nonNeg; + masm.branch32(Assembler::NotEqual, in, Imm32(0), &nonNeg); + bailoutTest32(Assembler::Signed, in, in, ins->snapshot()); + masm.bind(&nonNeg); + } + + Label negative, done; + masm.branchTest32(Assembler::Signed, in, in, &negative); + + masm.and32(Imm32(mask), in, out); + masm.jump(&done); + + masm.bind(&negative); + masm.as_sub(out, g0, in); + masm.and32(Imm32(mask), out); + masm.as_sub(out, g0, out); + masm.as_sra(out, out, 0); + + if (!mir->isTruncated() && mir->canBeNegativeDividend()) { + Label ok; + masm.branch32(Assembler::NotEqual, out, Imm32(0), &ok); + bailout(ins->snapshot()); + masm.bind(&ok); + } + + 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_sub(output, g0, input); + masm.as_sra(output, output, 0); +} + +void CodeGenerator::visitNegI64(LNegI64* ins) { + Register input = ToRegister64(ins->input()).reg; + Register output = ToOutRegister64(ins).reg; + masm.as_sub(output, g0, input); +} + +void CodeGenerator::visitUDivOrMod(LUDivOrMod* ins) { + Register lhs = ToRegister(ins->lhs()); + Register rhs = ToRegister(ins->rhs()); + Register output = ToRegister(ins->output()); + Register temp = SecondScratchReg; + 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()); + } + } + + // Zero-extend both operands in place, then use the 64-bit unsigned divide. + // The lowering uses useRegisterAtStart precisely so this is legal. + masm.as_srl(lhs, lhs, 0); + masm.as_srl(rhs, rhs, 0); + + // The output may share a register with an input, so compute anything that + // still needs the inputs before writing it. + if (ins->mir()->isDiv()) { + if (!ins->mir()->toDiv()->canTruncateRemainder()) { + masm.as_udivx(temp, lhs, rhs); + masm.as_mulx(temp, temp, rhs); + masm.as_sub(temp, lhs, temp); + bailoutCmp32(Assembler::NotEqual, temp, Imm32(0), ins->snapshot()); + } + masm.as_udivx(output, lhs, rhs); + } else { + masm.as_udivx(temp, lhs, rhs); + masm.as_mulx(temp, temp, rhs); + masm.as_sub(output, lhs, temp); + } + + masm.as_sra(output, output, 0); + + if (!ins->mir()->isTruncated()) { + bailoutCmp32(Assembler::LessThan, output, Imm32(0), ins->snapshot()); + } + + masm.bind(&done); +} + +void CodeGenerator::visitDivOrModI64(LDivOrModI64* lir) { + Register lhs = ToRegister(lir->getOperand(0)); + Register rhs = ToRegister(lir->getOperand(1)); + Register output = ToRegister(lir->output()); + + Label done; + + if (lir->canBeDivideByZero()) { + Label nonZero; + masm.branchPtr(Assembler::NotEqual, rhs, ImmWord(0), &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, lir->trapSiteDesc()); + masm.bind(&nonZero); + } + + if (lir->canBeNegativeOverflow()) { + Label notMinInt; + masm.branchPtr(Assembler::NotEqual, lhs, ImmWord(INT64_MIN), ¬MinInt); + masm.branchPtr(Assembler::NotEqual, rhs, ImmWord(-1), ¬MinInt); + if (lir->mir()->isDiv()) { + masm.wasmTrap(wasm::Trap::IntegerOverflow, lir->trapSiteDesc()); + } else { + masm.movePtr(ImmWord(0), output); + masm.jump(&done); + } + masm.bind(¬MinInt); + } + + if (lir->mir()->isDiv()) { + masm.as_sdivx(output, lhs, rhs); + } else { + masm.as_sdivx(SecondScratchReg, lhs, rhs); + masm.as_mulx(SecondScratchReg, SecondScratchReg, rhs); + masm.as_sub(output, lhs, SecondScratchReg); + } + + masm.bind(&done); +} + +void CodeGenerator::visitUDivOrModI64(LUDivOrModI64* lir) { + Register lhs = ToRegister(lir->getOperand(0)); + Register rhs = ToRegister(lir->getOperand(1)); + Register output = ToRegister(lir->output()); + + if (lir->canBeDivideByZero()) { + Label nonZero; + masm.branchPtr(Assembler::NotEqual, rhs, ImmWord(0), &nonZero); + masm.wasmTrap(wasm::Trap::IntegerDivideByZero, lir->trapSiteDesc()); + masm.bind(&nonZero); + } + + if (lir->mir()->isDiv()) { + masm.as_udivx(output, lhs, rhs); + } else { + masm.as_udivx(SecondScratchReg, lhs, rhs); + masm.as_mulx(SecondScratchReg, SecondScratchReg, rhs); + masm.as_sub(output, lhs, SecondScratchReg); + } +} + +// =============================================================== +// Visitors: Bitwise + +void CodeGenerator::visitBitNotI(LBitNotI* ins) { + Register input = ToRegister(ins->input()); + Register dest = ToRegister(ins->output()); + masm.as_xnor(dest, input, g0); + masm.as_sra(dest, dest, 0); +} + +void CodeGenerator::visitBitNotI64(LBitNotI64* ins) { + Register input = ToRegister64(ins->input()).reg; + Register dest = ToOutRegister64(ins).reg; + masm.as_xnor(dest, input, g0); +} + +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)); + masm.as_sra(dest, dest, 0); + } + break; + case JSOp::BitXor: + if (rhs->isConstant()) { + masm.xor32(Imm32(ToInt32(rhs)), lhs, dest); + } else { + masm.as_xor(dest, lhs, ToRegister(rhs)); + masm.as_sra(dest, dest, 0); + } + break; + case JSOp::BitAnd: + if (rhs->isConstant()) { + masm.and32(Imm32(ToInt32(rhs)), lhs, dest); + } else { + masm.as_and(dest, lhs, ToRegister(rhs)); + masm.as_sra(dest, dest, 0); + } + break; + default: + MOZ_CRASH("unexpected binary opcode"); + } +} + +void CodeGenerator::visitBitOpI64(LBitOpI64* lir) { + Register dest = ToRegister(lir->getDef(0)); + Register lhs = ToRegister(lir->getOperand(0)); + const LAllocation* rhs = lir->getOperand(1); + + switch (lir->bitop()) { + case JSOp::BitOr: + if (rhs->isConstant()) { + if (lhs != dest) { + masm.movePtr(lhs, dest); + } + masm.or64(Imm64(ToInt64(rhs)), Register64(dest)); + } else { + masm.as_or(dest, lhs, ToRegister(rhs)); + } + break; + case JSOp::BitXor: + if (rhs->isConstant()) { + if (lhs != dest) { + masm.movePtr(lhs, dest); + } + masm.xor64(Imm64(ToInt64(rhs)), Register64(dest)); + } else { + masm.as_xor(dest, lhs, ToRegister(rhs)); + } + break; + case JSOp::BitAnd: + if (rhs->isConstant()) { + if (lhs != dest) { + masm.movePtr(lhs, dest); + } + masm.and64(Imm64(ToInt64(rhs)), Register64(dest)); + } else { + masm.as_and(dest, lhs, ToRegister(rhs)); + } + 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()) { + bailoutCmp32(Assembler::LessThan, dest, Imm32(0), ins->snapshot()); + } + break; + default: + MOZ_CRASH("unexpected shift opcode"); + } + } else { + // sll/srl/sra take the shift count from rs2{4:0}, which is exactly the + // mod-32 the language requires; srl/sra additionally read only the low + // 32 bits of rs1. + Register shiftReg = ToRegister(rhs); + switch (ins->bitop()) { + case JSOp::Lsh: + masm.as_sll(dest, lhs, shiftReg); + masm.as_sra(dest, dest, 0); + break; + case JSOp::Rsh: + masm.as_sra(dest, lhs, shiftReg); + break; + case JSOp::Ursh: + masm.as_srl(dest, lhs, shiftReg); + masm.as_sra(dest, dest, 0); + 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())) & 0x3f; + 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"); + } + } else { + // sllx/srlx/srax take the count from rs2{5:0}: mod-64 for free. + Register shiftReg = ToRegister(ins->rhs()); + switch (ins->bitop()) { + case JSOp::Lsh: + masm.as_sllx(dest, lhs, shiftReg); + break; + case JSOp::Rsh: + masm.as_srax(dest, lhs, shiftReg); + break; + case JSOp::Ursh: + masm.as_srlx(dest, lhs, shiftReg); + break; + default: + MOZ_CRASH("unexpected shift opcode"); + } + } +} + +void CodeGenerator::visitShiftI64(LShiftI64* lir) { + Register lhs = ToRegister64(lir->lhs()).reg; + Register dest = ToOutRegister64(lir).reg; + const LAllocation* rhs = lir->rhs(); + + if (rhs->isConstant()) { + int32_t shift = int32_t(rhs->toConstant()->toInt64()) & 0x3f; + switch (lir->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"); + } + } else { + Register shiftReg = ToRegister(rhs); + switch (lir->bitop()) { + case JSOp::Lsh: + masm.as_sllx(dest, lhs, shiftReg); + break; + case JSOp::Rsh: + masm.as_srax(dest, lhs, shiftReg); + break; + case JSOp::Ursh: + masm.as_srlx(dest, lhs, shiftReg); + break; + default: + MOZ_CRASH("unexpected shift opcode"); + } + } +} + +void CodeGenerator::visitUrshD(LUrshD* ins) { + Register lhs = ToRegister(ins->lhs()); + const LAllocation* rhs = ins->rhs(); + FloatRegister dest = ToFloatRegister(ins->output()); + + Register temp = ToRegister(ins->temp0()); + + if (rhs->isConstant()) { + int32_t shift = ToInt32(rhs) & 0x1f; + if (shift) { + masm.rshift32(Imm32(shift), lhs, temp); + } else { + masm.move32(lhs, temp); + } + } else { + masm.move32(lhs, temp); + masm.rshift32(ToRegister(rhs), temp); + } + + masm.convertUInt32ToDouble(temp, dest); +} + +// =============================================================== +// 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_fpop1(OPF_FADDd, dest, lhs, rhs); + break; + case JSOp::Sub: + masm.as_fpop1(OPF_FSUBd, dest, lhs, rhs); + break; + case JSOp::Mul: + masm.as_fpop1(OPF_FMULd, dest, lhs, rhs); + break; + case JSOp::Div: + masm.as_fpop1(OPF_FDIVd, 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_fpop1(OPF_FADDs, dest, lhs, rhs); + break; + case JSOp::Sub: + masm.as_fpop1(OPF_FSUBs, dest, lhs, rhs); + break; + case JSOp::Mul: + masm.as_fpop1(OPF_FMULs, dest, lhs, rhs); + break; + case JSOp::Div: + masm.as_fpop1(OPF_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_fpop1(OPF_FNEGd, output, input); +} + +void CodeGenerator::visitNegF(LNegF* ins) { + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + masm.as_fpop1(OPF_FNEGs, output, input); +} + +void CodeGenerator::visitPowHalfD(LPowHalfD* ins) { + FloatRegister input = ToFloatRegister(ins->input()); + FloatRegister output = ToFloatRegister(ins->output()); + + Label done, skip; + + masm.loadConstantDouble(NegativeInfinity(), ScratchDoubleReg); + masm.branchDouble(Assembler::DoubleNotEqualOrUnordered, input, + ScratchDoubleReg, &skip); + masm.loadConstantDouble(std::numeric_limits::infinity(), output); + masm.jump(&done); + + masm.bind(&skip); + // Adding 0.0 turns -0 into +0. + masm.loadConstantDouble(0.0, ScratchDoubleReg); + masm.as_fpop1(OPF_FADDd, output, input, ScratchDoubleReg); + masm.as_fpop1(OPF_FSQRTd, 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.ma_fcmp(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.ma_fcmps(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.ma_fcmp(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.ma_fcmps(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) { + Register input = ToRegister(lir->input()); + Register output = ToRegister(lir->output()); + + if (lir->mir()->isUnsigned()) { + masm.as_srl(output, input, 0); + } else { + masm.as_sra(output, input, 0); + } +} + +void CodeGenerator::visitWrapInt64ToInt32(LWrapInt64ToInt32* lir) { + const LInt64Allocation input = lir->input(); + Register output = ToRegister(lir->output()); + + if (lir->mir()->bottomHalf()) { + if (input.value().isMemory()) { + Address addr = ToAddress(input); + // Big-endian: the low word of a spilled 64-bit value is at +4. + addr = Address(addr.base, addr.offset + sizeof(int32_t)); + masm.load32(addr, output); + } else { + masm.move64To32(ToRegister64(input), output); + } + } else { + // Only the GPR-pair argument splitter (JS_CODEGEN_REGISTER_PAIR, 32-bit + // ARM) produces bottomHalf=false. + MOZ_CRASH("Not implemented."); + } +} + +void CodeGenerator::visitSignExtendInt64(LSignExtendInt64* lir) { + Register64 input = ToRegister64(lir->input()); + Register64 output = ToOutRegister64(lir); + + switch (lir->mir()->mode()) { + case MSignExtendInt64::Byte: + masm.as_sllx(output.reg, input.reg, 56); + masm.as_srax(output.reg, output.reg, 56); + break; + case MSignExtendInt64::Half: + masm.as_sllx(output.reg, input.reg, 48); + masm.as_srax(output.reg, output.reg, 48); + break; + case MSignExtendInt64::Word: + masm.as_sra(output.reg, input.reg, 0); + break; + } +} + +void CodeGenerator::visitWasmExtendU32Index(LWasmExtendU32Index* lir) { + Register input = ToRegister(lir->input()); + Register output = ToRegister(lir->output()); + masm.as_srl(output, input, 0); +} + +void CodeGenerator::visitWasmWrapU32Index(LWasmWrapU32Index* lir) { + Register input = ToRegister(lir->input()); + Register output = ToRegister(lir->output()); + masm.move32(input, 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 { + masm.wasmTruncateFloat32ToUInt32(input, output, mir->isSaturating(), + oolEntry); + } + + masm.bind(ool->rejoin()); + return; + } + + if (fromType == MIRType::Double) { + masm.wasmTruncateDoubleToInt32(input, output, mir->isSaturating(), + oolEntry); + } else { + masm.wasmTruncateFloat32ToInt32(input, output, mir->isSaturating(), + oolEntry); + } + + 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(); + + if (fromType == MIRType::Double) { + if (mir->isUnsigned()) { + masm.wasmTruncateDoubleToUInt64(input, output, isSaturating, oolEntry, + oolRejoin, InvalidFloatReg); + } else { + masm.wasmTruncateDoubleToInt64(input, output, isSaturating, oolEntry, + oolRejoin, InvalidFloatReg); + } + } else { + if (mir->isUnsigned()) { + masm.wasmTruncateFloat32ToUInt64(input, output, isSaturating, oolEntry, + oolRejoin, InvalidFloatReg); + } else { + masm.wasmTruncateFloat32ToInt64(input, output, isSaturating, oolEntry, + oolRejoin, InvalidFloatReg); + } + } +} + +void CodeGenerator::visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir) { + Register64 input = ToRegister64(lir->input()); + FloatRegister output = ToFloatRegister(lir->output()); + MIRType outputType = lir->mir()->type(); + + if (outputType == MIRType::Double) { + if (lir->mir()->isUnsigned()) { + masm.convertUInt64ToDouble(input, output, Register::Invalid()); + } else { + masm.convertInt64ToDouble(input, output); + } + } else { + if (lir->mir()->isUnsigned()) { + masm.convertUInt64ToFloat32(input, output, Register::Invalid()); + } 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 + +template +void CodeGeneratorSPARC64::emitWasmLoad(T* lir) { + const MWasmLoad* mir = lir->mir(); + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + + Register memoryBase = ToRegister(lir->memoryBase()); + Register ptr = ToRegister(lir->ptr()); + Register ptrScratch = ToTempRegisterOrInvalid(lir->temp0()); + + if (mir->base()->type() == MIRType::Int32) { + masm.as_srl(scratch, ptr, 0); + ptr = scratch; + ptrScratch = ptrScratch != InvalidReg ? scratch : InvalidReg; + } + + masm.wasmLoad(mir->access(), memoryBase, ptr, ptrScratch, + ToAnyRegister(lir->output())); +} + +template +void CodeGeneratorSPARC64::emitWasmStore(T* lir) { + const MWasmStore* mir = lir->mir(); + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + + Register memoryBase = ToRegister(lir->memoryBase()); + Register ptr = ToRegister(lir->ptr()); + Register ptrScratch = ToTempRegisterOrInvalid(lir->temp0()); + + if (mir->base()->type() == MIRType::Int32) { + masm.as_srl(scratch, ptr, 0); + ptr = scratch; + ptrScratch = ptrScratch != InvalidReg ? scratch : InvalidReg; + } + + masm.wasmStore(mir->access(), ToAnyRegister(lir->value()), memoryBase, ptr, + ptrScratch); +} + +void CodeGenerator::visitWasmLoad(LWasmLoad* lir) { emitWasmLoad(lir); } + +void CodeGenerator::visitWasmStore(LWasmStore* lir) { emitWasmStore(lir); } + +void CodeGenerator::visitWasmLoadI64(LWasmLoadI64* lir) { + const MWasmLoad* mir = lir->mir(); + + Register memoryBase = ToRegister(lir->memoryBase()); + Register ptrScratch = ToTempRegisterOrInvalid(lir->temp0()); + + Register ptrReg = ToRegister(lir->ptr()); + if (mir->base()->type() == MIRType::Int32) { + masm.move32ZeroExtendToPtr(ptrReg, ptrReg); + } + + masm.wasmLoadI64(mir->access(), memoryBase, ptrReg, ptrScratch, + ToOutRegister64(lir)); +} + +void CodeGenerator::visitWasmStoreI64(LWasmStoreI64* lir) { + const MWasmStore* mir = lir->mir(); + + Register memoryBase = ToRegister(lir->memoryBase()); + Register ptrScratch = ToTempRegisterOrInvalid(lir->temp0()); + + Register ptrReg = ToRegister(lir->ptr()); + if (mir->base()->type() == MIRType::Int32) { + masm.move32ZeroExtendToPtr(ptrReg, ptrReg); + } + + masm.wasmStoreI64(mir->access(), ToRegister64(lir->value()), memoryBase, + ptrReg, ptrScratch); +} + +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); + } + + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.as_srl(scratch, ptrReg, 0); + BaseIndex addr(HeapReg, scratch, TimesOne); + + switch (accessType) { + case Scalar::Int8: + masm.load8SignExtend(addr, ToRegister(output)); + break; + case Scalar::Uint8: + masm.load8ZeroExtend(addr, ToRegister(output)); + break; + case Scalar::Int16: + masm.load16SignExtend(addr, ToRegister(output)); + break; + case Scalar::Uint16: + masm.load16ZeroExtend(addr, ToRegister(output)); + break; + case Scalar::Int32: + case Scalar::Uint32: + masm.load32(addr, ToRegister(output)); + break; + case Scalar::Float64: + masm.loadDouble(addr, ToFloatRegister(output)); + break; + case Scalar::Float32: + masm.loadFloat32(addr, ToFloatRegister(output)); + 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); + } + + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.as_srl(scratch, ptrReg, 0); + BaseIndex addr(HeapReg, scratch, TimesOne); + + switch (mir->accessType()) { + case Scalar::Int8: + case Scalar::Uint8: + masm.store8(ToRegister(value), addr); + break; + case Scalar::Int16: + case Scalar::Uint16: + masm.store16(ToRegister(value), addr); + break; + case Scalar::Int32: + case Scalar::Uint32: + masm.store32(ToRegister(value), addr); + break; + case Scalar::Float64: + masm.storeDouble(ToFloatRegister(value), addr); + break; + case Scalar::Float32: + masm.storeFloat32(ToFloatRegister(value), addr); + 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()) { + // An i32 stack argument must be stored as 32 bits: on big-endian a 64-bit + // store would leave the value in the high-address half of the slot, while + // the callee reads the low-address word. + if (mir->input()->type() == MIRType::Int32) { + masm.store32(Imm32(ToInt32(ins->arg())), + Address(StackPointer, mir->spOffset())); + } else { + masm.storePtr(ImmWord(ToInt32(ins->arg())), + Address(StackPointer, mir->spOffset())); + } + } else { + if (ins->arg()->isGeneralReg()) { + if (mir->input()->type() == MIRType::Int32) { + masm.store32(ToRegister(ins->arg()), + Address(StackPointer, mir->spOffset())); + } else { + masm.storePtr(ToRegister(ins->arg()), + Address(StackPointer, mir->spOffset())); + } + } else if (mir->input()->type() == MIRType::Double) { + masm.storeDouble(ToFloatRegister(ins->arg()), + Address(StackPointer, mir->spOffset())); + } else if (mir->input()->type() == MIRType::Simd128) { + MOZ_CRASH("SIMD unsupported on sparc64"); + } 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); + return; + } + // The condition is a 32-bit value; test 32 bits so that high-bit garbage + // cannot make a zero condition read as non-zero. + Label done; + masm.branchTest32(Assembler::NonZero, cond, cond, &done); + if (mirType == MIRType::Int32) { + masm.load32(ToAddress(falseExpr), out); + } else { + masm.loadPtr(ToAddress(falseExpr), out); + } + masm.bind(&done); + return; + } + + FloatRegister out = ToFloatRegister(ins->output()); + MOZ_ASSERT(ToFloatRegister(ins->trueExpr()) == out, + "true expr input is reused for output"); + + Label done; + masm.branchTest32(Assembler::NonZero, cond, cond, &done); + + if (falseExpr->isFloatReg()) { + if (mirType == MIRType::Float32) { + masm.moveFloat32(ToFloatRegister(falseExpr), out); + } else if (mirType == MIRType::Double) { + masm.moveDouble(ToFloatRegister(falseExpr), out); + } else { + MOZ_CRASH("unhandled type in visitWasmSelect!"); + } + } else { + if (mirType == MIRType::Float32) { + masm.loadFloat32(ToAddress(falseExpr), out); + } else if (mirType == MIRType::Double) { + masm.loadDouble(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()); + LInt64Allocation falseExpr = lir->falseExpr(); + + Register64 out = ToOutRegister64(lir); + MOZ_ASSERT(ToRegister64(lir->trueExpr()) == out, + "true expr is reused for input"); + + if (falseExpr.value().isGeneralReg()) { + masm.moveIfZero(out.reg, ToRegister(falseExpr.value()), cond); + return; + } + + Label done; + masm.branchTest32(Assembler::NonZero, cond, cond, &done); + masm.loadPtr(ToAddress(falseExpr.value()), out.reg); + masm.bind(&done); +} + +void CodeGenerator::visitWasmCompareAndSelect(LWasmCompareAndSelect* ins) { + MCompare::CompareType compTy = ins->compareType(); + MIRType insTy = ins->mir()->type(); + const bool cmpIs32 = compTy == MCompare::Compare_Int32 || + compTy == MCompare::Compare_UInt32; + const bool cmpIs64 = compTy == MCompare::Compare_Int64 || + compTy == MCompare::Compare_UInt64; + const bool selIsInt = insTy == MIRType::Int32 || insTy == MIRType::Int64; + + MOZ_RELEASE_ASSERT( + (cmpIs32 || cmpIs64) && selIsInt, + "CodeGenerator::visitWasmCompareAndSelect: unexpected types"); + + Register trueExprAndDest = ToRegister(ins->output()); + MOZ_ASSERT(ToRegister(ins->ifTrueExpr()) == trueExprAndDest, + "true expr input is reused for output"); + + Assembler::Condition cond = + Assembler::InvertCondition(JSOpToCondition(compTy, ins->jsop())); + Register lhs = ToRegister(ins->leftExpr()); + Register rhs = ToRegister(ins->rightExpr()); + Register falseExpr = ToRegister(ins->ifFalseExpr()); + + // movcc moves the whole 64-bit GPR; only the compare width (icc vs xcc) + // differs between the two cases. + if (cmpIs32) { + masm.cmp32Move32(cond, lhs, rhs, falseExpr, trueExprAndDest); + } else { + masm.cmpPtrMovePtr(cond, lhs, rhs, falseExpr, trueExprAndDest); + } +} + +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; + masm.ma_addPtrTestCarry(Assembler::CarryClear, out.reg, base.reg, + ImmWord(mir->offset()), &ok); + 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()); + + masm.movePtr(ImmWord(0), output); + BaseIndex addr(output, ToRegister(ins->index()), mir->scale(), + mir->displacement()); + masm.computeEffectiveAddress(addr, output); + masm.as_sra(output, output, 0); +} + +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); + masm.as_sra(output, output, 0); +} + +void CodeGenerator::visitWasmMulI64WideHI64(LWasmMulI64WideHI64* ins) { + Register lhs = ToRegister(ins->lhs()); + Register rhs = ToRegister(ins->rhs()); + Register output = ToRegister(ins->output()); + + // SPARC V9 has no widening multiply (umulxhi is VIS3-only), so build the + // high half from four 32x32 partial products. + Register t0 = ScratchRegister; + Register t1 = SecondScratchReg; + Register t2 = g3; + + masm.as_srl(t0, lhs, 0); // u0 + masm.as_srl(t1, rhs, 0); // v0 + masm.as_mulx(output, t0, t1); // w0 = u0 * v0 + masm.as_srlx(output, output, 32); + masm.as_srlx(t2, lhs, 32); // u1 + masm.as_mulx(t1, t2, t1); // u1 * v0 + masm.as_add(output, t1, output); // t = u1 * v0 + (w0 >> 32) + masm.as_srlx(t1, output, 32); // w2 + masm.as_srl(output, output, 0); // w1 + masm.as_srlx(t2, rhs, 32); // v1 + masm.as_mulx(t0, t0, t2); // u0 * v1 + masm.as_add(output, output, t0); + masm.as_srlx(output, output, 32); + masm.as_add(output, output, t1); + masm.as_srlx(t0, lhs, 32); // u1 + masm.as_mulx(t0, t0, t2); // u1 * v1 + masm.as_add(output, output, t0); + + if (ins->isSigned()) { + // mulhs(u, v) = mulhu(u, v) - (u < 0 ? v : 0) - (v < 0 ? u : 0) + masm.as_srax(t0, lhs, 63); + masm.as_and(t0, t0, rhs); + masm.as_sub(output, output, t0); + masm.as_srax(t0, rhs, 63); + masm.as_and(t0, t0, lhs); + masm.as_sub(output, output, t0); + } +} + +// =============================================================== +// 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); +} + +// =============================================================== +// Visitors: 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. Assembler::SupportsWasmSimd() is false, so the lowering never emits +// any of these. See SPARC64-JIT-DESIGN.md section 4. + +void CodeGenerator::visitSimd128(LSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmTernarySimd128(LWasmTernarySimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmBinarySimd128(LWasmBinarySimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmBinarySimd128WithConstant( + LWasmBinarySimd128WithConstant* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmVariableShiftSimd128( + LWasmVariableShiftSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmConstantShiftSimd128( + LWasmConstantShiftSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmSignReplicationSimd128( + LWasmSignReplicationSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmShuffleSimd128(LWasmShuffleSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmPermuteSimd128(LWasmPermuteSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmReplaceLaneSimd128(LWasmReplaceLaneSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmReplaceInt64LaneSimd128( + LWasmReplaceInt64LaneSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmScalarToSimd128(LWasmScalarToSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmInt64ToSimd128(LWasmInt64ToSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmUnarySimd128(LWasmUnarySimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmReduceSimd128(LWasmReduceSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmReduceAndBranchSimd128( + LWasmReduceAndBranchSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmReduceSimd128ToInt64( + LWasmReduceSimd128ToInt64* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmLoadLaneSimd128(LWasmLoadLaneSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void CodeGenerator::visitWasmStoreLaneSimd128(LWasmStoreLaneSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +} // namespace jit +} // namespace js diff -Naurp empty/js/src/jit/sparc64/CodeGenerator-sparc64.h firefox-153.0/js/src/jit/sparc64/CodeGenerator-sparc64.h --- firefox-153.0/js/src/jit/sparc64/CodeGenerator-sparc64.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/CodeGenerator-sparc64.h 2026-07-29 20:00:51.439640006 +0200 @@ -0,0 +1,100 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_CodeGenerator_sparc64_h +#define jit_sparc64_CodeGenerator_sparc64_h + +#include "jit/shared/CodeGenerator-shared.h" +#include "jit/sparc64/Assembler-sparc64.h" + +namespace js { +namespace jit { + +class CodeGeneratorSPARC64; +class OutOfLineTableSwitch; + +using OutOfLineWasmTruncateCheck = + OutOfLineWasmTruncateCheckBase; + +class CodeGeneratorSPARC64 : public CodeGeneratorShared { + friend class MoveResolverSPARC64; + + protected: + CodeGeneratorSPARC64(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 CodeGeneratorSPARC64 CodeGeneratorSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_CodeGenerator_sparc64_h */ diff -Naurp empty/js/src/jit/sparc64/LIR-sparc64.h firefox-153.0/js/src/jit/sparc64/LIR-sparc64.h --- firefox-153.0/js/src/jit/sparc64/LIR-sparc64.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/LIR-sparc64.h 2026-07-29 20:00:13.678096404 +0200 @@ -0,0 +1,136 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_LIR_sparc64_h +#define jit_sparc64_LIR_sparc64_h + +namespace js { +namespace jit { + +class LUnbox : public LInstructionHelper<1, BOX_PIECES, 0> { + public: + LIR_HEADER(Unbox); + + explicit LUnbox(const LAllocation& input) : LInstructionHelper(classOpcode) { + setOperand(0, input); + } + + static const size_t Input = 0; + + LBoxAllocation input() const { return getBoxOperand(Input); } + + MUnbox* mir() const { return mir_->toUnbox(); } + 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(); + } +}; + +class LDivOrModI64 : public LBinaryMath<0> { + public: + LIR_HEADER(DivOrModI64); + + LDivOrModI64(const LAllocation& lhs, const LAllocation& rhs) + : LBinaryMath(classOpcode) { + setOperand(0, lhs); + setOperand(1, rhs); + } + + 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 canBeNegativeOverflow() const { + if (mir_->isMod()) { + return mir_->toMod()->canBeNegativeDividend(); + } + return mir_->toDiv()->canBeNegativeOverflow(); + } + wasm::TrapSiteDesc trapSiteDesc() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + if (mir_->isMod()) { + return mir_->toMod()->trapSiteDesc(); + } + return mir_->toDiv()->trapSiteDesc(); + } +}; + +class LUDivOrModI64 : public LBinaryMath<0> { + public: + LIR_HEADER(UDivOrModI64); + + LUDivOrModI64(const LAllocation& lhs, const LAllocation& rhs) + : LBinaryMath(classOpcode) { + setOperand(0, lhs); + setOperand(1, rhs); + } + + const char* extraName() const { + return mir()->isTruncated() ? "Truncated" : nullptr; + } + + 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(); + } + wasm::TrapSiteDesc trapSiteDesc() const { + MOZ_ASSERT(mir_->isDiv() || mir_->isMod()); + if (mir_->isMod()) { + return mir_->toMod()->trapSiteDesc(); + } + return mir_->toDiv()->trapSiteDesc(); + } +}; + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_LIR_sparc64_h */ diff -Naurp empty/js/src/jit/sparc64/Lowering-sparc64.cpp firefox-153.0/js/src/jit/sparc64/Lowering-sparc64.cpp --- firefox-153.0/js/src/jit/sparc64/Lowering-sparc64.cpp 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/Lowering-sparc64.cpp 2026-07-29 20:10:52.799438879 +0200 @@ -0,0 +1,961 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#include "jit/sparc64/Lowering-sparc64.h" + +#include "mozilla/MathAlgorithms.h" + +#include "jit/Lowering.h" +#include "jit/MIR-wasm.h" +#include "jit/MIR.h" +#include "jit/sparc64/Assembler-sparc64.h" + +#include "jit/shared/Lowering-shared-inl.h" + +using namespace js; +using namespace js::jit; + +using mozilla::FloorLog2; + +namespace js { +namespace jit { + +LTableSwitch* LIRGeneratorSPARC64::newLTableSwitch( + const LAllocation& in, const LDefinition& inputCopy) { + return new (alloc()) LTableSwitch(in, inputCopy, temp()); +} + +LTableSwitchV* LIRGeneratorSPARC64::newLTableSwitchV(const LBoxAllocation& in) { + return new (alloc()) LTableSwitchV(in, temp(), tempDouble(), temp()); +} + +void LIRGeneratorSPARC64::lowerForShift(LInstructionHelper<1, 2, 0>* ins, + MDefinition* mir, MDefinition* lhs, + MDefinition* rhs) { + lowerForALU(ins, mir, lhs, rhs); +} + +template +void LIRGeneratorSPARC64::lowerForShiftInt64(LInstr* ins, MDefinition* mir, + MDefinition* lhs, + MDefinition* rhs) { + if constexpr (std::is_same_v) { + ins->setLhs(useInt64RegisterAtStart(lhs)); + ins->setRhs(useRegisterOrConstantAtStart(rhs)); + } else { + ins->setInput(useInt64RegisterAtStart(lhs)); + ins->setCount(useRegisterOrConstantAtStart(rhs)); + } + defineInt64(ins, mir); +} + +template void LIRGeneratorSPARC64::lowerForShiftInt64(LShiftI64* ins, + MDefinition* mir, + MDefinition* lhs, + MDefinition* rhs); +template void LIRGeneratorSPARC64::lowerForShiftInt64(LRotateI64* ins, + MDefinition* mir, + MDefinition* lhs, + MDefinition* rhs); + +void LIRGeneratorSPARC64::lowerForALU(LInstructionHelper<1, 1, 0>* ins, + MDefinition* mir, MDefinition* input) { + ins->setOperand(0, useRegisterAtStart(input)); + define(ins, mir); +} + +void LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::lowerForALUInt64( + LInstructionHelper* ins, MDefinition* mir, + MDefinition* input) { + ins->setInt64Operand(0, useInt64RegisterAtStart(input)); + defineInt64(ins, mir); +} + +void LIRGeneratorSPARC64::lowerForALUInt64( + LInstructionHelper* ins, + MDefinition* mir, MDefinition* lhs, MDefinition* rhs) { + ins->setInt64Operand(0, useInt64RegisterAtStart(lhs)); + ins->setInt64Operand(INT64_PIECES, useInt64RegisterOrConstantAtStart(rhs)); + defineInt64(ins, mir); +} + +void LIRGeneratorSPARC64::lowerForMulInt64(LMulI64* ins, MMul* mir, + MDefinition* lhs, MDefinition* rhs) { + lowerForALUInt64(ins, mir, lhs, rhs); +} + +void LIRGeneratorSPARC64::lowerForFPU(LInstructionHelper<1, 1, 0>* ins, + MDefinition* mir, MDefinition* input) { + ins->setOperand(0, useRegisterAtStart(input)); + define(ins, mir); +} + +void LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::useBoxFixed(MDefinition* mir, Register reg1, + Register reg2, + bool useAtStart) { + MOZ_ASSERT(mir->type() == MIRType::Value); + + ensureDefined(mir); + return LBoxAllocation(LUse(reg1, mir->virtualRegister(), useAtStart)); +} + +LAllocation LIRGeneratorSPARC64::useByteOpRegister(MDefinition* mir) { + return useRegister(mir); +} + +LAllocation LIRGeneratorSPARC64::useByteOpRegisterAtStart(MDefinition* mir) { + return useRegisterAtStart(mir); +} + +LAllocation LIRGeneratorSPARC64::useByteOpRegisterOrNonDoubleConstant( + MDefinition* mir) { + return useRegisterOrNonDoubleConstant(mir); +} + +LDefinition LIRGeneratorSPARC64::tempByteOpRegister() { return temp(); } + +LDefinition LIRGeneratorSPARC64::tempToUnbox() { return temp(); } + +void LIRGeneratorSPARC64::lowerUntypedPhiInput(MPhi* phi, + uint32_t inputPosition, + LBlock* block, + size_t lirIndex) { + lowerTypedPhiInput(phi, inputPosition, block, lirIndex); +} + +void LIRGeneratorSPARC64::lowerInt64PhiInput(MPhi* phi, uint32_t inputPosition, + LBlock* block, size_t lirIndex) { + lowerTypedPhiInput(phi, inputPosition, block, lirIndex); +} + +void LIRGeneratorSPARC64::defineInt64Phi(MPhi* phi, size_t lirIndex) { + defineTypedPhi(phi, lirIndex); +} + +void LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::lowerDivI64(MDiv* div) { + auto* lir = new (alloc()) + LDivOrModI64(useRegister(div->lhs()), useRegister(div->rhs())); + defineInt64(lir, div); +} + +void LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::lowerModI64(MMod* mod) { + auto* lir = new (alloc()) + LDivOrModI64(useRegister(mod->lhs()), useRegister(mod->rhs())); + defineInt64(lir, mod); +} + +void LIRGeneratorSPARC64::lowerUDiv(MDiv* div) { + MDefinition* lhs = div->getOperand(0); + MDefinition* rhs = div->getOperand(1); + LUDivOrMod* lir = new (alloc()) LUDivOrMod; + // visitUDivOrMod zero-extends lhs/rhs in place before udivx, so the inputs + // must not be required live after the LIR op begins. + lir->setOperand(0, useRegisterAtStart(lhs)); + lir->setOperand(1, useRegisterAtStart(rhs)); + if (div->fallible()) { + assignSnapshot(lir, div->bailoutKind()); + } + define(lir, div); +} + +void LIRGeneratorSPARC64::lowerUDivI64(MDiv* div) { + auto* lir = new (alloc()) + LUDivOrModI64(useRegister(div->lhs()), useRegister(div->rhs())); + defineInt64(lir, div); +} + +void LIRGeneratorSPARC64::lowerUMod(MMod* mod) { + MDefinition* lhs = mod->getOperand(0); + MDefinition* rhs = mod->getOperand(1); + LUDivOrMod* lir = new (alloc()) LUDivOrMod; + // See lowerUDiv above for why useRegisterAtStart is required here. + lir->setOperand(0, useRegisterAtStart(lhs)); + lir->setOperand(1, useRegisterAtStart(rhs)); + if (mod->fallible()) { + assignSnapshot(lir, mod->bailoutKind()); + } + define(lir, mod); +} + +void LIRGeneratorSPARC64::lowerUModI64(MMod* mod) { + auto* lir = new (alloc()) + LUDivOrModI64(useRegister(mod->lhs()), useRegister(mod->rhs())); + defineInt64(lir, mod); +} + +void LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::lowerBigIntPtrLsh(MBigIntPtrLsh* ins) { + auto* lir = new (alloc()) LBigIntPtrLsh( + useRegister(ins->lhs()), useRegister(ins->rhs()), temp(), temp()); + assignSnapshot(lir, ins->bailoutKind()); + define(lir, ins); +} + +void LIRGeneratorSPARC64::lowerBigIntPtrRsh(MBigIntPtrRsh* ins) { + auto* lir = new (alloc()) LBigIntPtrRsh( + useRegister(ins->lhs()), useRegister(ins->rhs()), temp(), temp()); + assignSnapshot(lir, ins->bailoutKind()); + define(lir, ins); +} + +void LIRGeneratorSPARC64::lowerTruncateDToInt32(MTruncateToInt32* ins) { + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType::Double); + define(new (alloc()) LTruncateDToInt32(useRegister(opd), tempDouble()), ins); +} + +void LIRGeneratorSPARC64::lowerTruncateFToInt32(MTruncateToInt32* ins) { + MDefinition* opd = ins->input(); + MOZ_ASSERT(opd->type() == MIRType::Float32); + define(new (alloc()) LTruncateFToInt32(useRegister(opd), tempFloat32()), ins); +} + +void LIRGeneratorSPARC64::lowerBuiltinInt64ToFloatingPoint( + MBuiltinInt64ToFloatingPoint* ins) { + MOZ_CRASH("We don't use it for this architecture"); +} + +void LIRGeneratorSPARC64::lowerWasmSelectI(MWasmSelect* select) { + auto* lir = new (alloc()) + LWasmSelect(useRegisterAtStart(select->trueExpr()), + useAny(select->falseExpr()), useRegister(select->condExpr())); + defineReuseInput(lir, select, LWasmSelect::TrueExprIndex); +} + +void LIRGeneratorSPARC64::lowerWasmSelectI64(MWasmSelect* select) { + auto* lir = new (alloc()) LWasmSelectI64( + useInt64RegisterAtStart(select->trueExpr()), + useInt64(select->falseExpr()), useRegister(select->condExpr())); + defineInt64ReuseInput(lir, select, LWasmSelectI64::TrueExprIndex); +} + +void LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::lowerWasmBuiltinTruncateToInt64( + MWasmBuiltinTruncateToInt64* ins) { + MOZ_CRASH("We don't use it for this architecture"); +} + +void LIRGeneratorSPARC64::lowerWasmBuiltinDivI64(MWasmBuiltinDivI64* div) { + MOZ_CRASH("We don't use runtime div for this architecture"); +} + +void LIRGeneratorSPARC64::lowerWasmBuiltinModI64(MWasmBuiltinModI64* mod) { + MOZ_CRASH("We don't use runtime mod for this architecture"); +} + +void LIRGeneratorSPARC64::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 LIRGeneratorSPARC64::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* opd = box->getOperand(0); + + if (opd->isConstant() && box->canEmitAtUses()) { + emitAtUses(box); + return; + } + + if (opd->isConstant()) { + define(new (alloc()) LValue(opd->toConstant()->toJSValue()), box, + LDefinition(LDefinition::BOX)); + } else { + LBox* ins = new (alloc()) LBox(useRegisterAtStart(opd), opd->type()); + define(ins, box, LDefinition(LDefinition::BOX)); + } +} + +void LIRGenerator::visitUnbox(MUnbox* unbox) { + MDefinition* box = unbox->getOperand(0); + MOZ_ASSERT(box->type() == MIRType::Value); + + LInstructionHelper<1, BOX_PIECES, 0>* lir; + if (IsFloatingPointType(unbox->type())) { + MOZ_ASSERT(unbox->type() == MIRType::Double); + lir = new (alloc()) LUnboxFloatingPoint(useBoxAtStart(box)); + } else if (unbox->fallible()) { + lir = new (alloc()) LUnbox(useRegisterAtStart(box)); + } else { + lir = new (alloc()) LUnbox(useAtStart(box)); + } + + if (unbox->fallible()) { + assignSnapshot(lir, unbox->bailoutKind()); + } + + define(lir, unbox); +} + +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) { + defineInt64( + new (alloc()) LExtendInt32ToInt64(useRegisterAtStart(ins->input())), ins); +} + +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, useFixed(opd, JSReturnReg)); + 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(); + } + + // SPARC V9 has only word and doubleword CAS (casa/casxa), so a sub-word + // compare-exchange is a word CAS on the containing aligned word plus a + // shift/mask dance: offsetTemp holds the byte offset within the word and + // maskTemp the in-word field mask, with valueTemp holding the merged word. + 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(); + } + + 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(); + + 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); + + LAllocation memoryBase = + ins->hasMemoryBase() ? LAllocation(useRegisterAtStart(ins->memoryBase())) + : LGeneralReg(HeapReg); + + LAllocation ptr = useRegisterAtStart(base); + + LDefinition ptrCopy = LDefinition::BogusTemp(); + if (ins->access().offset32()) { + ptrCopy = tempCopy(base, 0); + } + + if (ins->type() == MIRType::Int64) { + auto* lir = new (alloc()) LWasmLoadI64(ptr, memoryBase, ptrCopy); + defineInt64(lir, ins); + return; + } + + auto* lir = new (alloc()) LWasmLoad(ptr, 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); + + LDefinition ptrCopy = LDefinition::BogusTemp(); + if (ins->access().offset32()) { + ptrCopy = tempCopy(base, 0); + } + + if (ins->access().type() == Scalar::Int64) { + LInt64Allocation valueAlloc = useInt64RegisterAtStart(value); + auto* lir = + new (alloc()) LWasmStoreI64(baseAlloc, valueAlloc, memoryBase, ptrCopy); + add(lir, ins); + return; + } + + LAllocation valueAlloc = 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(); + + // Sub-word CAS is emulated on top of the word-wide casa; see + // visitCompareExchangeTypedArrayElement above. + 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(); + + 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(); + + 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. Assembler::SupportsWasmSimd() is false on SPARC64: VIS is a +// 64-bit partitioned-arithmetic unit, not a 128-bit lane machine, so no v128 +// LIR is ever produced. See SPARC64-JIT-DESIGN.md section 4. + +void LIRGenerator::visitWasmTernarySimd128(MWasmTernarySimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmBinarySimd128(MWasmBinarySimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmBinarySimd128WithConstant( + MWasmBinarySimd128WithConstant* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmShiftSimd128(MWasmShiftSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmShuffleSimd128(MWasmShuffleSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmReplaceLaneSimd128(MWasmReplaceLaneSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmScalarToSimd128(MWasmScalarToSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmUnarySimd128(MWasmUnarySimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmReduceSimd128(MWasmReduceSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmLoadLaneSimd128(MWasmLoadLaneSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void LIRGenerator::visitWasmStoreLaneSimd128(MWasmStoreLaneSimd128* ins) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +// SPARC V9 has MOVcc, so a compare feeding a select folds into +// subcc + movcc for {U,}Int32 / {U,}Int64 compares with an integer result. +// FP is not specialized: there is no FMOVcc-on-integer-condition form that +// beats the generic branch-based path. +bool LIRGeneratorShared::canSpecializeWasmCompareAndSelect( + MCompare::CompareType compTy, MIRType insTy) { + const bool insOk = insTy == MIRType::Int32 || insTy == MIRType::Int64; + const bool cmpOk = compTy == MCompare::Compare_Int32 || + compTy == MCompare::Compare_UInt32 || + compTy == MCompare::Compare_Int64 || + compTy == MCompare::Compare_UInt64; + return insOk && cmpOk; +} + +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; +} + +bool MWasmTernarySimd128::canRelaxBitselect() { return false; } + +bool MWasmBinarySimd128::canPmaddubsw() { return false; } + +bool MWasmBinarySimd128::specializeForConstantRhs() { return false; } +#endif + +} // namespace jit +} // namespace js diff -Naurp empty/js/src/jit/sparc64/Lowering-sparc64.h firefox-153.0/js/src/jit/sparc64/Lowering-sparc64.h --- firefox-153.0/js/src/jit/sparc64/Lowering-sparc64.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/Lowering-sparc64.h 2026-07-29 20:00:34.958402935 +0200 @@ -0,0 +1,101 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_Lowering_sparc64_h +#define jit_sparc64_Lowering_sparc64_h + +#include "jit/shared/Lowering-shared.h" + +namespace js { +namespace jit { + +class LIRGeneratorSPARC64 : public LIRGeneratorShared { + protected: + LIRGeneratorSPARC64(MIRGenerator* gen, MIRGraph& graph, LIRGraph& lirGraph) + : LIRGeneratorShared(gen, graph, lirGraph) {} + + LTableSwitch* newLTableSwitch(const LAllocation& in, + const LDefinition& inputCopy); + LTableSwitchV* newLTableSwitchV(const LBoxAllocation& in); + + void lowerForShift(LInstructionHelper<1, 2, 0>* ins, MDefinition* mir, + MDefinition* lhs, MDefinition* rhs); + template + void lowerForShiftInt64(LInstr* ins, MDefinition* mir, MDefinition* lhs, + MDefinition* rhs); + 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); + void lowerForFPU(LInstructionHelper<1, 1, 0>* ins, MDefinition* mir, + MDefinition* input); + void lowerForFPU(LInstructionHelper<1, 2, 0>* ins, MDefinition* mir, + MDefinition* lhs, MDefinition* rhs); + + template + void lowerForCompareI64(LInstructionHelper<1, 2 * INT64_PIECES, Temps>* lir, + MDefinition* mir, MDefinition* lhs, MDefinition* rhs); + + LBoxAllocation useBoxFixed(MDefinition* mir, Register reg1, Register reg2, + bool useAtStart = false); + + LAllocation useByteOpRegister(MDefinition* mir); + LAllocation useByteOpRegisterAtStart(MDefinition* mir); + LAllocation useByteOpRegisterOrNonDoubleConstant(MDefinition* mir); + LDefinition tempByteOpRegister(); + + LDefinition tempToUnbox(); + + 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 lowerMulI(MMul* mul, MDefinition* lhs, MDefinition* rhs); + void lowerDivI(MDiv* div); + void lowerDivI64(MDiv* div); + void lowerModI(MMod* mod); + void lowerModI64(MMod* mod); + void lowerUDiv(MDiv* div); + void lowerUDivI64(MDiv* div); + void lowerUMod(MMod* mod); + void lowerUModI64(MMod* mod); + void lowerUrshD(MUrsh* mir); + void lowerPowOfTwoI(MPow* mir); + void lowerBigIntPtrDiv(MBigIntPtrDiv* ins); + void lowerBigIntPtrMod(MBigIntPtrMod* ins); + void lowerBigIntPtrLsh(MBigIntPtrLsh* ins); + void lowerBigIntPtrRsh(MBigIntPtrRsh* ins); + void lowerTruncateDToInt32(MTruncateToInt32* ins); + void lowerTruncateFToInt32(MTruncateToInt32* ins); + void lowerBuiltinInt64ToFloatingPoint(MBuiltinInt64ToFloatingPoint* ins); + void lowerWasmSelectI(MWasmSelect* select); + void lowerWasmSelectI64(MWasmSelect* select); + void lowerWasmBuiltinTruncateToInt64(MWasmBuiltinTruncateToInt64* ins); + void lowerWasmBuiltinTruncateToInt32(MWasmBuiltinTruncateToInt32* ins); + void lowerWasmBuiltinDivI64(MWasmBuiltinDivI64* div); + void lowerWasmBuiltinModI64(MWasmBuiltinModI64* mod); + void lowerAtomicLoad64(MLoadUnboxedScalar* ins); + void lowerAtomicStore64(MStoreUnboxedScalar* ins); +}; + +typedef LIRGeneratorSPARC64 LIRGeneratorSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_Lowering_sparc64_h */ diff -Naurp empty/js/src/jit/sparc64/MacroAssembler-sparc64-inl.h firefox-153.0/js/src/jit/sparc64/MacroAssembler-sparc64-inl.h --- firefox-153.0/js/src/jit/sparc64/MacroAssembler-sparc64-inl.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/MacroAssembler-sparc64-inl.h 2026-07-29 20:24:11.774967609 +0200 @@ -0,0 +1,4060 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_MacroAssembler_sparc64_inl_h +#define jit_sparc64_MacroAssembler_sparc64_inl_h + +#include "jit/sparc64/MacroAssembler-sparc64.h" + +namespace js { +namespace jit { + +// 32-bit values live sign-extended in the full 64-bit register, as on ppc64. +// `sra rd,rs,0` re-canonicalises after any 64-bit-wide ALU op; `srl rd,rs,0` +// zero-extends where an unsigned 32-bit value is wanted instead. +// +// Condition codes: 32-bit compares/tests go through ma_cmp32/ma_test32 (%icc), +// 64-bit, pointer and Value ones through ma_cmp64/ma_test64 (%xcc); ma_b then +// reads the recorded bank. ma_bc names the bank explicitly after a raw +// addcc/subcc. + +// membar, encoded as `rd %asr15, %g0` with the mmask in the simm13 field. +static constexpr uint32_t MembarInstBase = 0x8143E000; +static constexpr uint32_t MembarStoreLoad = 0x2; + +static inline bool IsUnsignedCondition(Assembler::Condition cond) { + switch (cond) { + case Assembler::Below: + case Assembler::BelowOrEqual: + case Assembler::Above: + case Assembler::AboveOrEqual: + return true; + default: + return false; + } +} + +// Materialise a template operand into a register usable as an ALU rs2. +static inline Register SparcOperand32(MacroAssembler& masm, + UseScratchRegisterScope& temps, + Register src) { + return src; +} +static inline Register SparcOperand32(MacroAssembler& masm, + UseScratchRegisterScope& temps, + Imm32 src) { + Register scratch = temps.Acquire(); + masm.move32(src, scratch); + return scratch; +} +static inline Register SparcOperandPtr(MacroAssembler& masm, + UseScratchRegisterScope& temps, + Register src) { + return src; +} +static inline Register SparcOperandPtr(MacroAssembler& masm, + UseScratchRegisterScope& temps, + Imm32 src) { + Register scratch = temps.Acquire(); + masm.movePtr(ImmWord(int64_t(src.value)), scratch); + return scratch; +} +static inline Register SparcOperandPtr(MacroAssembler& masm, + UseScratchRegisterScope& temps, + ImmWord src) { + Register scratch = temps.Acquire(); + masm.movePtr(src, scratch); + return scratch; +} + +// dest = byte-reversed low 32 bits of src, zero-extended. dest, src and tmp +// must be three distinct registers. +static inline void SparcByteSwap32(MacroAssembler& masm, Register dest, + Register src, Register tmp) { + masm.as_srl(dest, src, 24u); + masm.as_srl(tmp, src, 16u); + masm.as_and(tmp, tmp, 0xff); + masm.as_sll(tmp, tmp, 8u); + masm.as_or(dest, dest, tmp); + masm.as_srl(tmp, src, 8u); + masm.as_and(tmp, tmp, 0xff); + masm.as_sll(tmp, tmp, 16u); + masm.as_or(dest, dest, tmp); + masm.as_and(tmp, src, 0xff); + masm.as_sll(tmp, tmp, 24u); + masm.as_or(dest, dest, tmp); +} + +//{{{ check_macroassembler_style + +// =============================================================== +// Move instructions + +void MacroAssembler::move64(Register64 src, Register64 dest) { + movePtr(src.reg, dest.reg); +} + +void MacroAssembler::move64(Imm64 imm, Register64 dest) { + movePtr(ImmWord(imm.value), dest.reg); +} + +void MacroAssembler::moveDoubleToGPR64(FloatRegister src, Register64 dest) { + moveFPR64ToGPR(src, dest.reg); +} + +void MacroAssembler::moveGPR64ToDouble(Register64 src, FloatRegister dest) { + moveGPRToFPR64(src.reg, dest); +} + +void MacroAssembler::moveLowDoubleToGPR(FloatRegister src, Register dest) { + MOZ_CRASH("Not supported for this target"); +} + +void MacroAssembler::move64To32(Register64 src, Register dest) { + as_sra(dest, src.reg, 0u); +} + +void MacroAssembler::move32To64ZeroExtend(Register src, Register64 dest) { + as_srl(dest.reg, src, 0u); +} + +void MacroAssembler::move8To64SignExtend(Register src, Register64 dest) { + as_sll(dest.reg, src, 24u); + as_sra(dest.reg, dest.reg, 24u); +} + +void MacroAssembler::move16To64SignExtend(Register src, Register64 dest) { + as_sll(dest.reg, src, 16u); + as_sra(dest.reg, dest.reg, 16u); +} + +void MacroAssembler::move32To64SignExtend(Register src, Register64 dest) { + as_sra(dest.reg, src, 0u); +} + +void MacroAssembler::moveFloat32ToGPR(FloatRegister src, Register dest) { + moveFPR32ToGPR(src, dest); +} + +void MacroAssembler::moveGPRToFloat32(Register src, FloatRegister dest) { + moveGPRToFPR32(src, dest); +} + +void MacroAssembler::moveFloat16ToGPR(FloatRegister src, Register dest) { + MOZ_CRASH("NYI: float16 unsupported on sparc64"); +} + +void MacroAssembler::moveGPRToFloat16(Register src, FloatRegister dest) { + MOZ_CRASH("NYI: float16 unsupported on sparc64"); +} + +void MacroAssembler::move8ZeroExtend(Register src, Register dest) { + as_and(dest, src, 0xff); +} + +void MacroAssembler::move8SignExtend(Register src, Register dest) { + as_sll(dest, src, 24u); + as_sra(dest, dest, 24u); +} + +void MacroAssembler::move16SignExtend(Register src, Register dest) { + as_sll(dest, src, 16u); + as_sra(dest, dest, 16u); +} + +void MacroAssembler::move8SignExtendToPtr(Register src, Register dest) { + move8SignExtend(src, dest); +} + +void MacroAssembler::move16SignExtendToPtr(Register src, Register dest) { + move16SignExtend(src, dest); +} + +void MacroAssembler::move32SignExtendToPtr(Register src, Register dest) { + as_sra(dest, src, 0u); +} + +void MacroAssembler::move32ZeroExtendToPtr(Register src, Register dest) { + as_srl(dest, src, 0u); +} + +// =============================================================== +// Load instructions + +void MacroAssembler::load32SignExtendToPtr(const Address& src, Register dest) { + load32(src, dest); +} + +void MacroAssembler::loadAbiReturnAddress(Register dest) { + movePtr(LinkRegister, dest); +} + +// =============================================================== +// Logical instructions + +void MacroAssembler::not32(Register reg) { + as_xnor(reg, reg, g0); + as_sra(reg, reg, 0u); +} + +void MacroAssembler::notPtr(Register reg) { as_xnor(reg, reg, g0); } + +void MacroAssembler::andPtr(Register src, Register dest) { + as_and(dest, dest, src); +} + +void MacroAssembler::andPtr(Imm32 imm, Register dest) { + ma_alu(OP3_AND, dest, dest, int64_t(int64_t(imm.value))); +} + +void MacroAssembler::andPtr(Imm32 imm, Register src, Register dest) { + ma_alu(OP3_AND, dest, src, int64_t(int64_t(imm.value))); +} + +void MacroAssembler::and64(Imm64 imm, Register64 dest) { + ma_alu(OP3_AND, dest.reg, dest.reg, int64_t(imm.value)); +} + +void MacroAssembler::and64(Register64 src, Register64 dest) { + as_and(dest.reg, dest.reg, src.reg); +} + +void MacroAssembler::and32(Register src, Register dest) { + as_and(dest, dest, src); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::and32(Imm32 imm, Register dest) { + ma_alu(OP3_AND, dest, dest, int64_t(imm.value)); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::and32(Imm32 imm, Register src, Register dest) { + ma_alu(OP3_AND, dest, src, int64_t(imm.value)); + as_sra(dest, dest, 0u); +} + +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); + and32(scratch, dest); +} + +void MacroAssembler::or64(Imm64 imm, Register64 dest) { + ma_alu(OP3_OR, dest.reg, dest.reg, int64_t(imm.value)); +} + +void MacroAssembler::or32(Register src, Register dest) { + as_or(dest, dest, src); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::or32(Imm32 imm, Register dest) { + ma_alu(OP3_OR, dest, dest, int64_t(imm.value)); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::or32(Imm32 imm, Register src, Register dest) { + ma_alu(OP3_OR, dest, src, int64_t(imm.value)); + as_sra(dest, dest, 0u); +} + +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) { + ma_alu(OP3_XOR, dest.reg, dest.reg, int64_t(imm.value)); +} + +void MacroAssembler::orPtr(Register src, Register dest) { + as_or(dest, dest, src); +} + +void MacroAssembler::orPtr(Imm32 imm, Register dest) { + ma_alu(OP3_OR, dest, dest, int64_t(int64_t(imm.value))); +} + +void MacroAssembler::orPtr(Imm32 imm, Register src, Register dest) { + ma_alu(OP3_OR, dest, src, int64_t(int64_t(imm.value))); +} + +void MacroAssembler::or64(Register64 src, Register64 dest) { + as_or(dest.reg, dest.reg, src.reg); +} + +void MacroAssembler::xor64(Register64 src, Register64 dest) { + as_xor(dest.reg, dest.reg, src.reg); +} + +void MacroAssembler::xorPtr(Register src, Register dest) { + as_xor(dest, dest, src); +} + +void MacroAssembler::xorPtr(Imm32 imm, Register dest) { + ma_alu(OP3_XOR, dest, dest, int64_t(int64_t(imm.value))); +} + +void MacroAssembler::xorPtr(Imm32 imm, Register src, Register dest) { + ma_alu(OP3_XOR, dest, src, int64_t(int64_t(imm.value))); +} + +void MacroAssembler::xor32(Register src, Register dest) { + as_xor(dest, dest, src); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::xor32(Imm32 imm, Register dest) { + ma_alu(OP3_XOR, dest, dest, int64_t(imm.value)); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::xor32(Imm32 imm, Register src, Register dest) { + ma_alu(OP3_XOR, dest, src, int64_t(imm.value)); + as_sra(dest, dest, 0u); +} + +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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_srl(scratch, reg, 8u); + as_and(scratch, scratch, 0xff); + as_and(reg, reg, 0xff); + as_sll(reg, reg, 24u); + as_sra(reg, reg, 16u); + as_or(reg, reg, scratch); +} + +void MacroAssembler::byteSwap16ZeroExtend(Register reg) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_srl(scratch, reg, 8u); + as_and(scratch, scratch, 0xff); + as_and(reg, reg, 0xff); + as_sll(reg, reg, 8u); + as_or(reg, reg, scratch); +} + +void MacroAssembler::byteSwap32(Register reg) { + UseScratchRegisterScope temps(asMasm()); + Register result = temps.Acquire(); + Register tmp = temps.Acquire(); + SparcByteSwap32(asMasm(), result, reg, tmp); + as_sra(reg, result, 0u); +} + +void MacroAssembler::byteSwap64(Register64 reg64) { + Register reg = reg64.reg; + UseScratchRegisterScope temps(asMasm()); + Register lo = temps.Acquire(); + Register tmp = temps.Acquire(); + // g3 is the non-allocatable address temp; nothing here forms an address. + SparcByteSwap32(asMasm(), lo, reg, tmp); + as_srlx(g3, reg, 32u); + SparcByteSwap32(asMasm(), reg, g3, tmp); + as_sllx(lo, lo, 32u); + as_or(reg, reg, lo); +} + +// =============================================================== +// Arithmetic functions + +void MacroAssembler::addPtr(Register src, Register dest) { + as_add(dest, dest, src); +} + +void MacroAssembler::addPtr(Imm32 imm, Register dest) { + ma_alu(OP3_ADD, dest, dest, int64_t(int64_t(imm.value))); +} + +void MacroAssembler::addPtr(ImmWord imm, Register dest) { + ma_alu(OP3_ADD, dest, dest, int64_t(imm.value)); +} + +void MacroAssembler::add64(Register64 src, Register64 dest) { + as_add(dest.reg, dest.reg, src.reg); +} + +void MacroAssembler::add64(Imm32 imm, Register64 dest) { + addPtr(imm, dest.reg); +} + +void MacroAssembler::add64(Imm64 imm, Register64 dest) { + ma_alu(OP3_ADD, dest.reg, dest.reg, int64_t(imm.value)); +} + +void MacroAssembler::add32(Register src, Register dest) { + as_add(dest, dest, src); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::add32(Imm32 imm, Register dest) { + ma_alu(OP3_ADD, dest, dest, int64_t(imm.value)); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::add32(Imm32 imm, Register src, Register dest) { + ma_alu(OP3_ADD, dest, src, int64_t(imm.value)); + as_sra(dest, dest, 0u); +} + +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); + add32(scratch, dest); +} + +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_fpop1(OPF_FADDd, dest, dest, src); +} + +void MacroAssembler::addFloat32(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FADDs, dest, dest, src); +} + +CodeOffset MacroAssembler::sub32FromStackPtrWithPatch(Register dest) { + CodeOffset offset = movWithPatch(ImmWord(0), dest); + as_sub(dest, StackPointer, dest); + return offset; +} + +void MacroAssembler::patchSub32FromStackPtr(CodeOffset offset, Imm32 imm) { + Instruction* inst = (Instruction*)editSrc(BufferOffset(offset.offset())); + Assembler::UpdateLoad64Value(inst, uint64_t(int64_t(imm.value))); +} + +void MacroAssembler::subPtr(Register src, Register dest) { + as_sub(dest, dest, src); +} + +void MacroAssembler::subPtr(Imm32 imm, Register dest) { + ma_alu(OP3_SUB, dest, dest, int64_t(int64_t(imm.value))); +} + +void MacroAssembler::sub64(Register64 src, Register64 dest) { + as_sub(dest.reg, dest.reg, src.reg); +} + +void MacroAssembler::sub64(Imm64 imm, Register64 dest) { + ma_alu(OP3_SUB, dest.reg, dest.reg, int64_t(imm.value)); +} + +void MacroAssembler::sub32(Register src, Register dest) { + as_sub(dest, dest, src); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::sub32(Imm32 imm, Register dest) { + ma_alu(OP3_SUB, dest, dest, int64_t(imm.value)); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::sub32(const Address& src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(src, scratch); + sub32(scratch, dest); +} + +void MacroAssembler::subPtr(Register src, const Address& dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(dest, scratch); + as_sub(scratch, scratch, src); + storePtr(scratch, dest); +} + +void MacroAssembler::subPtr(const Address& addr, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(addr, scratch); + as_sub(dest, dest, scratch); +} + +void MacroAssembler::subDouble(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FSUBd, dest, dest, src); +} + +void MacroAssembler::subFloat32(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FSUBs, dest, dest, src); +} + +void MacroAssembler::mul64(const Register64& rhs, const Register64& srcDest) { + as_mulx(srcDest.reg, srcDest.reg, rhs.reg); +} + +void MacroAssembler::mul64(Imm64 imm, const Register64& dest) { + ma_alu(OP3_MULX, dest.reg, dest.reg, int64_t(imm.value)); +} + +void MacroAssembler::mul64(Imm64 imm, const Register64& dest, + const Register temp) { + MOZ_ASSERT(temp == Register::Invalid()); + mul64(imm, dest); +} + +void MacroAssembler::mul64(const Register64& src, const Register64& dest, + const Register temp) { + MOZ_ASSERT(temp == Register::Invalid()); + as_mulx(dest.reg, dest.reg, src.reg); +} + +void MacroAssembler::mulPtr(Register rhs, Register srcDest) { + as_mulx(srcDest, srcDest, rhs); +} + +void MacroAssembler::mulPtr(ImmWord rhs, Register srcDest) { + ma_alu(OP3_MULX, srcDest, srcDest, int64_t(rhs.value)); +} + +void MacroAssembler::mulBy3(Register src, Register dest) { + as_mulx(dest, src, 3); +} + +void MacroAssembler::mul32(Register rhs, Register srcDest) { + as_mulx(srcDest, srcDest, rhs); + as_sra(srcDest, srcDest, 0u); +} + +void MacroAssembler::mul32(Imm32 imm, Register srcDest) { + ma_alu(OP3_MULX, srcDest, srcDest, int64_t(imm.value)); + as_sra(srcDest, srcDest, 0u); +} + +void MacroAssembler::mulHighUnsigned32(Imm32 imm, Register src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_srl(scratch, src, 0u); + move32(imm, dest); + as_srl(dest, dest, 0u); + as_mulx(dest, scratch, dest); + as_srlx(dest, dest, 32u); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::mulFloat32(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FMULs, dest, dest, src); +} + +void MacroAssembler::mulDouble(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FMULd, dest, dest, src); +} + +void MacroAssembler::mulDoublePtr(ImmPtr imm, Register temp, + FloatRegister dest) { + movePtr(imm, temp); + ma_loadf(OP3_LDDF, ScratchDoubleReg, Address(temp, 0)); + as_fpop1(OPF_FMULd, 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); + ma_load(OP3_LDX, scratch, Address(addrReg, 0)); + as_add(scratch, scratch, 1); + ma_store(OP3_STX, scratch, Address(addrReg, 0)); +} + +void MacroAssembler::divFloat32(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FDIVs, dest, dest, src); +} + +void MacroAssembler::divDouble(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FDIVd, dest, dest, src); +} + +void MacroAssembler::quotient32(Register lhs, Register rhs, Register dest, + bool isUnsigned) { + UseScratchRegisterScope temps(asMasm()); + Register l = temps.Acquire(); + Register r = temps.Acquire(); + if (isUnsigned) { + as_srl(l, lhs, 0u); + as_srl(r, rhs, 0u); + as_udivx(dest, l, r); + } else { + as_sra(l, lhs, 0u); + as_sra(r, rhs, 0u); + as_sdivx(dest, l, r); + } + as_sra(dest, dest, 0u); +} + +void MacroAssembler::quotient64(Register lhs, Register rhs, Register dest, + bool isUnsigned) { + if (isUnsigned) { + as_udivx(dest, lhs, rhs); + } else { + as_sdivx(dest, lhs, rhs); + } +} + +void MacroAssembler::remainder32(Register lhs, Register rhs, Register dest, + bool isUnsigned) { + UseScratchRegisterScope temps(asMasm()); + Register l = temps.Acquire(); + Register r = temps.Acquire(); + if (isUnsigned) { + as_srl(l, lhs, 0u); + as_srl(r, rhs, 0u); + as_udivx(l, l, r); + } else { + as_sra(l, lhs, 0u); + as_sra(r, rhs, 0u); + as_sdivx(l, l, r); + } + as_mulx(l, l, r); + as_sub(dest, lhs, l); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::remainder64(Register lhs, Register rhs, Register dest, + bool isUnsigned) { + UseScratchRegisterScope temps(asMasm()); + Register q = temps.Acquire(); + if (isUnsigned) { + as_udivx(q, lhs, rhs); + } else { + as_sdivx(q, lhs, rhs); + } + as_mulx(q, q, rhs); + as_sub(dest, lhs, q); +} + +void MacroAssembler::neg64(Register64 reg) { as_sub(reg.reg, g0, reg.reg); } + +void MacroAssembler::negPtr(Register reg) { as_sub(reg, g0, reg); } + +void MacroAssembler::neg32(Register reg) { + as_sub(reg, g0, reg); + as_sra(reg, reg, 0u); +} + +void MacroAssembler::negateDouble(FloatRegister reg) { + as_fpop1(OPF_FNEGd, reg, reg); +} + +void MacroAssembler::negateFloat(FloatRegister reg) { + as_fpop1(OPF_FNEGs, reg, reg); +} + +void MacroAssembler::abs32(Register src, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register mask = temps.Acquire(); + as_sra(mask, src, 31u); + as_xor(dest, src, mask); + as_sub(dest, dest, mask); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::absFloat32(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FABSs, dest, src); +} + +void MacroAssembler::absDouble(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FABSd, dest, src); +} + +void MacroAssembler::sqrtFloat32(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FSQRTs, dest, src); +} + +void MacroAssembler::sqrtDouble(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FSQRTd, dest, src); +} + +void MacroAssembler::min32(Register lhs, Register rhs, Register dest) { + ma_cmp32(lhs, rhs); + if (dest == rhs) { + as_movcc(Assembler::LessThan, Assembler::CC_ICC, dest, lhs); + return; + } + movePtr(lhs, dest); + as_movcc(Assembler::GreaterThan, Assembler::CC_ICC, dest, rhs); +} + +void MacroAssembler::min32(Register lhs, Imm32 rhs, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(rhs, scratch); + min32(lhs, scratch, dest); +} + +void MacroAssembler::max32(Register lhs, Register rhs, Register dest) { + ma_cmp32(lhs, rhs); + if (dest == rhs) { + as_movcc(Assembler::GreaterThan, Assembler::CC_ICC, dest, lhs); + return; + } + movePtr(lhs, dest); + as_movcc(Assembler::LessThan, Assembler::CC_ICC, dest, rhs); +} + +void MacroAssembler::max32(Register lhs, Imm32 rhs, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + move32(rhs, scratch); + max32(lhs, scratch, dest); +} + +void MacroAssembler::minPtr(Register lhs, Register rhs, Register dest) { + ma_cmp64(lhs, rhs); + if (dest == rhs) { + as_movcc(Assembler::LessThan, Assembler::CC_XCC, dest, lhs); + return; + } + movePtr(lhs, dest); + as_movcc(Assembler::GreaterThan, Assembler::CC_XCC, dest, rhs); +} + +void MacroAssembler::minPtr(Register lhs, ImmWord rhs, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(rhs, scratch); + minPtr(lhs, scratch, dest); +} + +void MacroAssembler::maxPtr(Register lhs, Register rhs, Register dest) { + ma_cmp64(lhs, rhs); + if (dest == rhs) { + as_movcc(Assembler::GreaterThan, Assembler::CC_XCC, dest, lhs); + return; + } + movePtr(lhs, dest); + as_movcc(Assembler::LessThan, Assembler::CC_XCC, dest, rhs); +} + +void MacroAssembler::maxPtr(Register lhs, ImmWord rhs, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(rhs, scratch); + maxPtr(lhs, scratch, dest); +} + +void MacroAssembler::minFloat32(FloatRegister other, FloatRegister srcDest, + bool handleNaN) { + Label done, nan, equal; + ma_fcmps(srcDest, other); + if (handleNaN) { + ma_b(Assembler::DoubleUnordered, &nan); + } + ma_b(Assembler::DoubleEqual, &equal); + ma_b(Assembler::DoubleLessThan, &done); + moveFloat32(other, srcDest); + jump(&done); + + bind(&equal); + // Both are zero of some sign: -((-srcDest) - other) yields -0 if either is. + loadConstantFloat32(0.0f, ScratchFloat32Reg); + ma_fcmps(srcDest, ScratchFloat32Reg); + ma_b(Assembler::DoubleNotEqual, &done); + as_fpop1(OPF_FNEGs, ScratchFloat32Reg, srcDest); + as_fpop1(OPF_FSUBs, ScratchFloat32Reg, ScratchFloat32Reg, other); + as_fpop1(OPF_FNEGs, srcDest, ScratchFloat32Reg); + jump(&done); + + if (handleNaN) { + bind(&nan); + as_fpop1(OPF_FADDs, srcDest, srcDest, other); + } + bind(&done); +} + +void MacroAssembler::minDouble(FloatRegister other, FloatRegister srcDest, + bool handleNaN) { + Label done, nan, equal; + ma_fcmp(srcDest, other); + if (handleNaN) { + ma_b(Assembler::DoubleUnordered, &nan); + } + ma_b(Assembler::DoubleEqual, &equal); + ma_b(Assembler::DoubleLessThan, &done); + moveDouble(other, srcDest); + jump(&done); + + bind(&equal); + loadConstantDouble(0.0, ScratchDoubleReg); + ma_fcmp(srcDest, ScratchDoubleReg); + ma_b(Assembler::DoubleNotEqual, &done); + as_fpop1(OPF_FNEGd, ScratchDoubleReg, srcDest); + as_fpop1(OPF_FSUBd, ScratchDoubleReg, ScratchDoubleReg, other); + as_fpop1(OPF_FNEGd, srcDest, ScratchDoubleReg); + jump(&done); + + if (handleNaN) { + bind(&nan); + as_fpop1(OPF_FADDd, srcDest, srcDest, other); + } + bind(&done); +} + +void MacroAssembler::maxFloat32(FloatRegister other, FloatRegister srcDest, + bool handleNaN) { + Label done, nan, equal; + ma_fcmps(srcDest, other); + if (handleNaN) { + ma_b(Assembler::DoubleUnordered, &nan); + } + ma_b(Assembler::DoubleEqual, &equal); + ma_b(Assembler::DoubleGreaterThan, &done); + moveFloat32(other, srcDest); + jump(&done); + + bind(&equal); + loadConstantFloat32(0.0f, ScratchFloat32Reg); + ma_fcmps(srcDest, ScratchFloat32Reg); + ma_b(Assembler::DoubleNotEqual, &done); + as_fpop1(OPF_FADDs, srcDest, srcDest, other); + jump(&done); + + if (handleNaN) { + bind(&nan); + as_fpop1(OPF_FADDs, srcDest, srcDest, other); + } + bind(&done); +} + +void MacroAssembler::maxDouble(FloatRegister other, FloatRegister srcDest, + bool handleNaN) { + Label done, nan, equal; + ma_fcmp(srcDest, other); + if (handleNaN) { + ma_b(Assembler::DoubleUnordered, &nan); + } + ma_b(Assembler::DoubleEqual, &equal); + ma_b(Assembler::DoubleGreaterThan, &done); + moveDouble(other, srcDest); + jump(&done); + + bind(&equal); + loadConstantDouble(0.0, ScratchDoubleReg); + ma_fcmp(srcDest, ScratchDoubleReg); + ma_b(Assembler::DoubleNotEqual, &done); + as_fpop1(OPF_FADDd, srcDest, srcDest, other); + jump(&done); + + if (handleNaN) { + bind(&nan); + as_fpop1(OPF_FADDd, srcDest, srcDest, other); + } + bind(&done); +} + +// =============================================================== +// Shift functions +// +// SPARC takes the shift count from rs2[4:0] for the 32-bit forms and rs2[5:0] +// for the 64-bit forms, so no explicit masking is ever needed. + +void MacroAssembler::lshift32(Register src, Register dest) { + as_sll(dest, dest, src); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::lshift32(Imm32 imm, Register dest) { + lshift32(imm, dest, dest); +} + +void MacroAssembler::lshift32(Imm32 imm, Register src, Register dest) { + as_sll(dest, src, uint32_t(imm.value) & 0x1f); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::flexibleLshift32(Register src, Register dest) { + lshift32(src, dest); +} + +void MacroAssembler::lshift64(Register shift, Register64 dest) { + as_sllx(dest.reg, dest.reg, shift); +} + +void MacroAssembler::lshift64(Imm32 imm, Register64 dest) { + MOZ_ASSERT(0 <= imm.value && imm.value < 64); + as_sllx(dest.reg, dest.reg, uint32_t(imm.value)); +} + +void MacroAssembler::lshiftPtr(Register shift, Register dest) { + as_sllx(dest, dest, shift); +} + +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 < 64); + as_sllx(dest, src, uint32_t(imm.value)); +} + +void MacroAssembler::flexibleLshiftPtr(Register shift, Register srcDest) { + lshiftPtr(shift, srcDest); +} + +void MacroAssembler::rshift32(Register src, Register dest) { + as_srl(dest, dest, src); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::rshift32(Imm32 imm, Register dest) { + rshift32(imm, dest, dest); +} + +void MacroAssembler::rshift32(Imm32 imm, Register src, Register dest) { + as_srl(dest, src, uint32_t(imm.value) & 0x1f); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::flexibleRshift32(Register src, Register dest) { + rshift32(src, dest); +} + +void MacroAssembler::rshift32Arithmetic(Register src, Register dest) { + as_sra(dest, dest, src); +} + +void MacroAssembler::rshift32Arithmetic(Imm32 imm, Register dest) { + rshift32Arithmetic(imm, dest, dest); +} + +void MacroAssembler::rshift32Arithmetic(Imm32 imm, Register src, + Register dest) { + as_sra(dest, src, uint32_t(imm.value) & 0x1f); +} + +void MacroAssembler::flexibleRshift32Arithmetic(Register src, Register dest) { + rshift32Arithmetic(src, dest); +} + +void MacroAssembler::rshift64(Register shift, Register64 dest) { + as_srlx(dest.reg, dest.reg, shift); +} + +void MacroAssembler::rshift64(Imm32 imm, Register64 dest) { + MOZ_ASSERT(0 <= imm.value && imm.value < 64); + as_srlx(dest.reg, dest.reg, uint32_t(imm.value)); +} + +void MacroAssembler::rshift64Arithmetic(Imm32 imm, Register64 dest) { + MOZ_ASSERT(0 <= imm.value && imm.value < 64); + as_srax(dest.reg, dest.reg, uint32_t(imm.value)); +} + +void MacroAssembler::rshift64Arithmetic(Register shift, Register64 dest) { + as_srax(dest.reg, dest.reg, shift); +} + +void MacroAssembler::rshiftPtr(Register shift, Register dest) { + as_srlx(dest, dest, shift); +} + +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 < 64); + as_srlx(dest, src, uint32_t(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 < 64); + as_srax(dest, src, uint32_t(imm.value)); +} + +void MacroAssembler::rshiftPtrArithmetic(Register shift, Register dest) { + as_srax(dest, dest, shift); +} + +void MacroAssembler::flexibleRshiftPtrArithmetic(Register shift, + Register srcDest) { + rshiftPtrArithmetic(shift, srcDest); +} + +// =============================================================== +// Rotation functions + +void MacroAssembler::rotateLeft(Register count, Register input, Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register left = temps.Acquire(); + Register right = temps.Acquire(); + as_and(left, count, 31); + as_sub(right, g0, left); + as_and(right, right, 31); + as_sll(left, input, left); + as_srl(right, input, right); + as_or(dest, left, right); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::rotateLeft(Imm32 count, Register input, Register dest) { + uint32_t c = uint32_t(count.value) & 31; + if (c == 0) { + move32(input, dest); + return; + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sll(scratch, input, c); + as_srl(dest, input, 32 - c); + as_or(dest, dest, scratch); + as_sra(dest, dest, 0u); +} + +void MacroAssembler::rotateLeft64(Register count, Register64 src, + Register64 dest, Register temp) { + MOZ_ASSERT(temp == Register::Invalid()); + UseScratchRegisterScope temps(asMasm()); + Register left = temps.Acquire(); + Register right = temps.Acquire(); + as_and(left, count, 63); + as_sub(right, g0, left); + as_and(right, right, 63); + as_sllx(left, src.reg, left); + as_srlx(right, src.reg, right); + as_or(dest.reg, left, right); +} + +void MacroAssembler::rotateLeft64(Imm32 count, Register64 src, Register64 dest, + Register temp) { + MOZ_ASSERT(temp == Register::Invalid()); + uint32_t c = uint32_t(count.value) & 63; + if (c == 0) { + movePtr(src.reg, dest.reg); + return; + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sllx(scratch, src.reg, c); + as_srlx(dest.reg, src.reg, 64 - c); + as_or(dest.reg, dest.reg, scratch); +} + +void MacroAssembler::rotateRight(Register count, Register input, + Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register left = temps.Acquire(); + Register right = temps.Acquire(); + as_and(right, count, 31); + as_sub(left, g0, right); + as_and(left, left, 31); + as_srl(right, input, right); + as_sll(left, input, left); + as_or(dest, left, right); + as_sra(dest, dest, 0u); +} + +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) { + MOZ_ASSERT(temp == Register::Invalid()); + UseScratchRegisterScope temps(asMasm()); + Register left = temps.Acquire(); + Register right = temps.Acquire(); + as_and(right, count, 63); + as_sub(left, g0, right); + as_and(left, left, 63); + as_srlx(right, src.reg, right); + as_sllx(left, src.reg, left); + as_or(dest.reg, left, right); +} + +void MacroAssembler::rotateRight64(Imm32 count, Register64 src, Register64 dest, + Register temp) { + MOZ_ASSERT(temp == Register::Invalid()); + rotateLeft64(Imm32((64 - count.value) & 63), src, dest, temp); +} + +// =============================================================== +// Bit counting functions +// +// SPARC V9 has popc but no clz/ctz: clz smears the value right and counts, +// ctz counts the bits below the lowest set bit. + +void MacroAssembler::clz64(Register64 src, Register64 dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_srlx(scratch, src.reg, 1u); + as_or(dest.reg, src.reg, scratch); + as_srlx(scratch, dest.reg, 2u); + as_or(dest.reg, dest.reg, scratch); + as_srlx(scratch, dest.reg, 4u); + as_or(dest.reg, dest.reg, scratch); + as_srlx(scratch, dest.reg, 8u); + as_or(dest.reg, dest.reg, scratch); + as_srlx(scratch, dest.reg, 16u); + as_or(dest.reg, dest.reg, scratch); + as_srlx(scratch, dest.reg, 32u); + as_or(dest.reg, dest.reg, scratch); + as_popc(dest.reg, dest.reg); + as_sub(dest.reg, g0, dest.reg); + as_add(dest.reg, dest.reg, 64); +} + +void MacroAssembler::ctz64(Register64 src, Register64 dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sub(scratch, g0, src.reg); + as_and(scratch, src.reg, scratch); + as_sub(scratch, scratch, 1); + as_popc(dest.reg, scratch); +} + +void MacroAssembler::popcnt64(Register64 input, Register64 output, + Register tmp) { + as_popc(output.reg, input.reg); +} + +void MacroAssembler::clz32(Register src, Register dest, bool knownNotZero) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_srl(dest, src, 0u); + as_srlx(scratch, dest, 1u); + as_or(dest, dest, scratch); + as_srlx(scratch, dest, 2u); + as_or(dest, dest, scratch); + as_srlx(scratch, dest, 4u); + as_or(dest, dest, scratch); + as_srlx(scratch, dest, 8u); + as_or(dest, dest, scratch); + as_srlx(scratch, dest, 16u); + as_or(dest, dest, scratch); + as_popc(dest, dest); + as_sub(dest, g0, dest); + as_add(dest, dest, 32); +} + +void MacroAssembler::ctz32(Register src, Register dest, bool knownNotZero) { + UseScratchRegisterScope temps(asMasm()); + Register value = temps.Acquire(); + Register scratch = temps.Acquire(); + as_srl(value, src, 0u); + as_sub(scratch, g0, value); + as_and(value, value, scratch); + as_sub(value, value, 1); + // Keeping only the low 32 bits turns the src == 0 case into 32, not 64. + as_srl(value, value, 0u); + as_popc(dest, value); +} + +void MacroAssembler::popcnt32(Register input, Register output, Register tmp) { + as_srl(output, input, 0u); + as_popc(output, output); +} + +// =============================================================== +// 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); + if (IsUnsignedCondition(cond) || cond == Equal || cond == NotEqual) { + 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); + if (IsUnsignedCondition(cond) || cond == Equal || cond == NotEqual) { + 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); +} + +void MacroAssembler::cmp64Set(Condition cond, Register64 lhs, Register64 rhs, + Register dest) { + Condition c = ma_cmp(lhs.reg, rhs.reg, cond); + ma_cmp_set(dest, c); +} + +void MacroAssembler::cmp64Set(Condition cond, Register64 lhs, Imm64 rhs, + Register dest) { + Condition c = ma_cmp(lhs.reg, ImmWord(uint64_t(rhs.value)), cond); + ma_cmp_set(dest, c); +} + +void MacroAssembler::cmp64Set(Condition cond, Address lhs, Register64 rhs, + Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, rhs.reg, cond); + ma_cmp_set(dest, c); +} + +void MacroAssembler::cmp64Set(Condition cond, Address lhs, Imm64 rhs, + Register dest) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, ImmWord(uint64_t(rhs.value)), cond); + ma_cmp_set(dest, c); +} + +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(); + Imm32 narrowed(0); + if (IsUnsignedCondition(cond) || cond == Equal || cond == NotEqual) { + 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(); + Imm32 narrowed(0); + if (IsUnsignedCondition(cond) || cond == Equal || cond == NotEqual) { + 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) { + Condition c = ma_cmp(lhs.reg, ImmWord(uint64_t(val.value)), cond); + ma_b(c, success); + if (fail) { + jump(fail); + } +} + +void MacroAssembler::branch64(Condition cond, Register64 lhs, Register64 rhs, + Label* success, Label* fail) { + Condition c = ma_cmp(lhs.reg, rhs.reg, cond); + ma_b(c, success); + if (fail) { + jump(fail); + } +} + +void MacroAssembler::branch64(Condition cond, const Address& lhs, Imm64 val, + Label* success, Label* fail) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, ImmWord(uint64_t(val.value)), cond); + ma_b(c, success); + if (fail) { + jump(fail); + } +} + +void MacroAssembler::branch64(Condition cond, const Address& lhs, + Register64 rhs, Label* success, Label* fail) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, rhs.reg, cond); + ma_b(c, success); + if (fail) { + jump(fail); + } +} + +void MacroAssembler::branch64(Condition cond, const Address& lhs, + const Address& rhs, Register scratch, + Label* label) { + loadPtr(rhs, scratch); + branch64(cond, lhs, Register64(scratch), label, nullptr); +} + +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(int64_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) { + ma_fcmps(lhs, rhs); + ma_b(cond, label); +} + +void MacroAssembler::branchTruncateFloat32MaybeModUint32(FloatRegister src, + Register dest, + Label* fail) { + as_fpop1(OPF_FSTOX, ScratchDoubleReg, src); + moveFPR64ToGPR(ScratchDoubleReg, dest); + // A conversion that cannot be represented delivers the maximum-magnitude + // integer; both saturations are out of the int32 range we accept. + asMasm().branchPtr(Assembler::Equal, dest, ImmWord(uint64_t(INT64_MIN)), + fail); + asMasm().branchPtr(Assembler::Equal, dest, ImmWord(uint64_t(INT64_MAX)), + fail); + as_srl(dest, dest, 0u); +} + +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) { + ma_fcmp(lhs, rhs); + ma_b(cond, label); +} + +void MacroAssembler::branchTruncateDoubleMaybeModUint32(FloatRegister src, + Register dest, + Label* fail) { + as_fpop1(OPF_FDTOX, ScratchDoubleReg, src); + moveFPR64ToGPR(ScratchDoubleReg, dest); + asMasm().branchPtr(Assembler::Equal, dest, ImmWord(uint64_t(INT64_MIN)), + fail); + asMasm().branchPtr(Assembler::Equal, dest, ImmWord(uint64_t(INT64_MAX)), + fail); + as_srl(dest, dest, 0u); +} + +void MacroAssembler::branchTruncateDoubleToInt32(FloatRegister src, + Register dest, Label* fail) { + convertDoubleToInt32(src, dest, fail, false); +} + +void MacroAssembler::branchInt64NotInPtrRange(Register64 src, Label* label) { + // Always in range on a 64-bit target. +} + +void MacroAssembler::branchUInt64NotInPtrRange(Register64 src, Label* label) { + ma_cmp64(src.reg, int64_t(0)); + ma_b(Assembler::LessThan, label); +} + +template +void MacroAssembler::branchAdd32(Condition cond, T src, Register dest, + Label* overflow) { + UseScratchRegisterScope temps(asMasm()); + Register rhs = SparcOperand32(asMasm(), temps, src); + as_addcc(dest, dest, rhs); + as_sra(dest, dest, 0u); + switch (cond) { + case Overflow: + case Zero: + case NonZero: + case Signed: + case NotSigned: + case CarrySet: + case CarryClear: + ma_bc(cond, Assembler::CC_ICC, overflow); + break; + default: + MOZ_CRASH("NYI: branchAdd32 condition"); + } +} + +template +void MacroAssembler::branchSub32(Condition cond, T src, Register dest, + Label* overflow) { + UseScratchRegisterScope temps(asMasm()); + Register rhs = SparcOperand32(asMasm(), temps, src); + as_subcc(dest, dest, rhs); + as_sra(dest, dest, 0u); + switch (cond) { + case Overflow: + case Zero: + case NonZero: + case Signed: + case NotSigned: + case CarrySet: + case CarryClear: + ma_bc(cond, Assembler::CC_ICC, overflow); + break; + default: + MOZ_CRASH("NYI: branchSub32 condition"); + } +} + +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); + as_mulx(dest, dest, scratch); + as_sra(scratch, dest, 0u); + as_subcc(g0, dest, scratch); + as_sra(dest, dest, 0u); + ma_bc(NotEqual, Assembler::CC_XCC, 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) { + UseScratchRegisterScope temps(asMasm()); + Register rhs = SparcOperandPtr(asMasm(), temps, src); + as_addcc(dest, dest, rhs); + switch (cond) { + case Overflow: + case Zero: + case NonZero: + case Signed: + case NotSigned: + case CarrySet: + case CarryClear: + ma_bc(cond, Assembler::CC_XCC, label); + break; + default: + MOZ_CRASH("NYI: branchAddPtr condition"); + } +} + +template +void MacroAssembler::branchSubPtr(Condition cond, T src, Register dest, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register rhs = SparcOperandPtr(asMasm(), temps, src); + as_subcc(dest, dest, rhs); + switch (cond) { + case Overflow: + case Zero: + case NonZero: + case Signed: + case NotSigned: + case CarrySet: + case CarryClear: + ma_bc(cond, Assembler::CC_XCC, label); + break; + default: + MOZ_CRASH("NYI: branchSubPtr condition"); + } +} + +void MacroAssembler::branchMulPtr(Condition cond, Register src, Register dest, + Label* label) { + MOZ_ASSERT(cond == Assembler::Overflow); + // No 64-bit multiply-with-overflow: verify by dividing the product back, + // with an explicit test for INT64_MIN * -1, which sdivx cannot distinguish. + UseScratchRegisterScope temps(asMasm()); + Register lhs = temps.Acquire(); + Register check = temps.Acquire(); + Label done; + movePtr(dest, lhs); + as_mulx(dest, dest, src); + as_subcc(g0, src, 0); + ma_bc(Assembler::Equal, Assembler::CC_XCC, &done); + as_sdivx(check, dest, src); + as_subcc(g0, check, lhs); + ma_bc(Assembler::NotEqual, Assembler::CC_XCC, label); + as_subcc(g0, src, -1); + ma_bc(Assembler::NotEqual, Assembler::CC_XCC, &done); + movePtr(ImmWord(uint64_t(INT64_MIN)), check); + as_subcc(g0, lhs, check); + ma_bc(Assembler::Equal, Assembler::CC_XCC, label); + bind(&done); +} + +void MacroAssembler::branchNegPtr(Condition cond, Register reg, Label* label) { + MOZ_ASSERT(cond == Overflow); + negPtr(reg); + branchPtr(Assembler::Equal, reg, ImmWord(uintptr_t(INTPTR_MIN)), 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); + ma_test32(lhs, rhs); + ma_b(cond, label); +} + +void MacroAssembler::branchTest32(Condition cond, Register lhs, Imm32 rhs, + Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + ma_test32(lhs, int64_t(rhs.value)); + ma_b(cond, 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); + branchTest32(cond, scratch, rhs, 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); + branchTest32(cond, scratch, rhs, label); +} + +void MacroAssembler::branchTestPtr(Condition cond, Register lhs, Register rhs, + Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + ma_test64(lhs, rhs); + ma_b(cond, label); +} + +void MacroAssembler::branchTestPtr(Condition cond, Register lhs, Imm32 rhs, + Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + ma_test64(lhs, int64_t(rhs.value)); + ma_b(cond, label); +} + +void MacroAssembler::branchTestPtr(Condition cond, Register lhs, ImmWord rhs, + Label* label) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + ma_test64(lhs, int64_t(rhs.value)); + ma_b(cond, label); +} + +void MacroAssembler::branchTestPtr(Condition cond, const Address& lhs, + Imm32 rhs, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + branchTestPtr(cond, scratch, rhs, label); +} + +void MacroAssembler::branchTest64(Condition cond, Register64 lhs, + Register64 rhs, Register temp, Label* success, + Label* fail) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + ma_test64(lhs.reg, rhs.reg); + ma_b(cond, success); + if (fail) { + jump(fail); + } +} + +void MacroAssembler::branchTest64(Condition cond, Register64 lhs, Imm64 rhs, + Label* success, Label* fail) { + MOZ_ASSERT(cond == Zero || cond == NonZero || cond == Signed || + cond == NotSigned); + ma_test64(lhs.reg, int64_t(rhs.value)); + ma_b(cond, success); + if (fail) { + 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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestUndefined(cond, scratch, 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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestInt32(cond, scratch, 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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + unboxInt32(value, scratch); + ma_cmp32(scratch, int64_t(0)); + ma_b(b ? NotEqual : Equal, label); +} + +void MacroAssembler::branchTestDouble(Condition cond, Register tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Condition actual = (cond == Equal) ? BelowOrEqual : Above; + Condition c = ma_cmp(tag, ImmTag(JSVAL_TAG_MAX_DOUBLE), actual); + ma_b(c, label); +} + +void MacroAssembler::branchTestDouble(Condition cond, const ValueOperand& value, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestDouble(cond, scratch, 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) { + zeroDouble(ScratchDoubleReg); + ma_fcmp(value, ScratchDoubleReg); + ma_b(b ? DoubleNotEqual : DoubleEqualOrUnordered, 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, Imm32(JS::detail::ValueUpperInclNumberTag), actual); + ma_b(c, label); +} + +void MacroAssembler::branchTestNumber(Condition cond, const ValueOperand& value, + Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestNumber(cond, scratch, 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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestBoolean(cond, scratch, 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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + unboxBoolean(value, scratch); + ma_cmp32(scratch, int64_t(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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestString(cond, scratch, 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(); + unboxString(value, scratch); + load32(Address(scratch, JSString::offsetOfLength()), scratch); + ma_cmp32(scratch, int64_t(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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestSymbol(cond, scratch, 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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestBigInt(cond, scratch, 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(); + unboxBigInt(value, scratch); + load32(Address(scratch, BigInt::offsetOfDigitLength()), scratch); + ma_cmp32(scratch, int64_t(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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestNull(cond, scratch, 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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestObject(cond, scratch, 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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestPrimitive(cond, scratch, 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, Imm32(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, Imm32(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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(value, scratch); + branchTestMagic(cond, scratch, label); +} + +void MacroAssembler::branchTestMagic(Condition cond, const Address& valaddr, + JSWhyMagic why, Label* label) { + uint64_t magic = MagicValue(why).asRawBits(); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(valaddr, scratch); + Condition c = ma_cmp(scratch, ImmWord(magic), cond); + ma_b(c, label); +} + +void MacroAssembler::branchTestMagic(Condition cond, const BaseIndex& valaddr, + JSWhyMagic why, Label* label) { + uint64_t magic = MagicValue(why).asRawBits(); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(valaddr, scratch); + Condition c = ma_cmp(scratch, ImmWord(magic), cond); + ma_b(c, label); +} + +template +void MacroAssembler::branchTestValue(Condition cond, const T& lhs, + const ValueOperand& rhs, Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(lhs, scratch); + Condition c = ma_cmp(scratch, rhs.valueReg(), cond); + ma_b(c, label); +} + +// =============================================================== +// 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, Imm32(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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(rhs, scratch); + Condition 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, ImmWord(int64_t(rhs.value)), cond); + 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) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(rhs, scratch); + Condition 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(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(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(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(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); + ma_test32(scratch, int64_t(mask.value)); + loadPtr(src, scratch); + ma_cmp_move(dest, scratch, cond); +} + +void MacroAssembler::test32MovePtr(Condition cond, Register operand, Imm32 mask, + Register src, Register dest) { + MOZ_ASSERT(cond == Zero || cond == NonZero); + ma_test32(operand, int64_t(mask.value)); + ma_cmp_move(dest, src, cond); +} + +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); + test32MovePtr(cond, scratch, mask, src, dest); +} + +// =============================================================== +// Spectre mitigations +// +// These read the flags of a compare the caller has already emitted. Every +// such caller compares pointer-width values, so they select %xcc. + +void MacroAssembler::spectreMovePtr(Condition cond, Register src, + Register dest) { + ma_cmp_move(dest, src, cond); +} + +void MacroAssembler::spectreZeroRegister(Condition cond, Register scratch, + Register dest) { + movePtr(ImmWord(0), scratch); + ma_cmp_move(dest, scratch, cond); +} + +void MacroAssembler::spectreBoundsCheck32(Register index, Register length, + Register maybeScratch, + Label* failure) { + Condition c = ma_cmp(index, length, Below, true); + if (failure) { + ma_b(Assembler::InvertCondition(c), failure); + } + if (maybeScratch != InvalidReg) { + movePtr(ImmWord(0), maybeScratch); + ma_cmp_move(index, maybeScratch, Assembler::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) { + Condition c = ma_cmp(index, length, Below); + if (failure) { + ma_b(Assembler::InvertCondition(c), failure); + } + if (maybeScratch != InvalidReg) { + movePtr(ImmWord(0), maybeScratch); + ma_cmp_move(index, maybeScratch, Assembler::InvertCondition(c)); + } +} + +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) { + return ma_storef(OP3_STF, src, addr); +} + +FaultingCodeOffset MacroAssembler::storeFloat32(FloatRegister src, + const BaseIndex& addr) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + computeEffectiveAddress(addr, scratch); + return ma_storef(OP3_STF, src, Address(scratch, 0)); +} + +FaultingCodeOffset MacroAssembler::storeDouble(FloatRegister src, + const Address& addr) { + return ma_storef(OP3_STDF, src, addr); +} + +FaultingCodeOffset MacroAssembler::storeDouble(FloatRegister src, + const BaseIndex& addr) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + computeEffectiveAddress(addr, scratch); + return ma_storef(OP3_STDF, src, Address(scratch, 0)); +} + +FaultingCodeOffset MacroAssembler::storeFloat16(FloatRegister src, + const Address& dest, + Register temp) { + MOZ_CRASH("NYI: float16 unsupported on sparc64"); +} + +FaultingCodeOffset MacroAssembler::storeFloat16(FloatRegister src, + const BaseIndex& dest, + Register temp) { + MOZ_CRASH("NYI: float16 unsupported on sparc64"); +} + +void MacroAssembler::memoryBarrier(MemoryBarrier barrier) { + if (barrier.isNone()) { + return; + } + // SPARC V9 runs in TSO, so only StoreLoad ever needs a membar. + if (barrier.hasStoreLoad() || barrier.hasSync()) { + writeInst(MembarInstBase | MembarStoreLoad); + } +} + +// =============================================================== +// Clamping functions + +void MacroAssembler::clampIntToUint8(Register reg) { + Label done, positive; + ma_cmp32(reg, int64_t(255)); + ma_b(LessThanOrEqual, &done); + move32(Imm32(255), reg); + bind(&done); + ma_cmp32(reg, int64_t(0)); + ma_b(GreaterThanOrEqual, &positive); + move32(Imm32(0), reg); + bind(&positive); +} + +// =============================================================== +// Unboxing + +void MacroAssembler::fallibleUnboxPtr(const ValueOperand& src, Register dest, + JSValueType type, Label* fail) { + MOZ_ASSERT(type == JSVAL_TYPE_OBJECT || type == JSVAL_TYPE_STRING || + type == JSVAL_TYPE_SYMBOL || type == JSVAL_TYPE_BIGINT); + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + splitTag(src, scratch); + Condition c = ma_cmp(scratch, ImmTag(JSVAL_TYPE_TO_TAG(type)), NotEqual); + ma_b(c, fail); + } + unboxNonDouble(src, dest, type); +} + +void MacroAssembler::fallibleUnboxPtr(const Address& src, Register dest, + JSValueType type, Label* fail) { + loadValue(src, ValueOperand(dest)); + fallibleUnboxPtr(ValueOperand(dest), dest, type, fail); +} + +void MacroAssembler::fallibleUnboxPtr(const BaseIndex& src, Register dest, + JSValueType type, Label* fail) { + loadValue(src, ValueOperand(dest)); + fallibleUnboxPtr(ValueOperand(dest), dest, type, fail); +} + +void MacroAssembler::wasmAddSubI128HI64(Register lhsLo, Register lhsHi, + Register rhsLo, Register rhsHi, + Register output, bool isAdd) { + MOZ_RELEASE_ASSERT(output != lhsLo && output != lhsHi && output != rhsLo && + output != rhsHi); + // SPARC's ADDC/SUBC consume %icc.C, so the 64-bit carry is materialised by + // a MOVcc off %xcc instead. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + if (isAdd) { + as_addcc(output, lhsLo, rhsLo); + as_add(output, lhsHi, rhsHi); + as_add(scratch, output, 1); + } else { + as_subcc(output, lhsLo, rhsLo); + as_sub(output, lhsHi, rhsHi); + as_sub(scratch, output, 1); + } + as_movcc(Assembler::CarrySet, Assembler::CC_XCC, output, scratch); +} + +void MacroAssembler::wasmMulI64WideHI64(Register lhs, Register rhs, + Register output, bool isSigned) { + MOZ_CRASH("NYI: wasmMulI64WideHI64 needs VIS3 umulxhi on sparc64"); +} + +//}}} check_macroassembler_style + +void MacroAssemblerSPARC64Compat::incrementInt32Value(const Address& addr) { + asMasm().add32(Imm32(1), valuePayload(addr)); +} + +void MacroAssemblerSPARC64Compat::retn(Imm32 n) { + // The return address is at the top of stack; read it before moving %sp. + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + asMasm().loadPtr(Address(StackPointer, 0), scratch); + if (n.value != 0) { + asMasm().addPtr(Imm32(n.value), StackPointer); + } + as_jmpl(g0, scratch, 0); +} + +// =============================================================== +// 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 + +// =============================================================== +// Wasm SIMD +// +// Assembler::SupportsWasmSimd() is false and no v128 LIR is ever produced +// (SPARC64-JIT-DESIGN.md section 4): VIS is a 64-bit partitioned-arithmetic +// unit, not a 128-bit lane machine. These exist only so shared code links. + +FaultingCodeOffset MacroAssembler::loadUnalignedSimd128(const Address& src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +FaultingCodeOffset MacroAssembler::loadUnalignedSimd128(const BaseIndex& src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +FaultingCodeOffset MacroAssembler::storeUnalignedSimd128(FloatRegister src, + const Address& dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +FaultingCodeOffset MacroAssembler::storeUnalignedSimd128( + FloatRegister src, const BaseIndex& dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::moveSimd128(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::loadConstantSimd128(const SimdConstant& v, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::splatX16(Register src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::splatX8(Register src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::splatX4(Register src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::splatX4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::splatX2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtractLaneInt8x16(uint32_t lane, + FloatRegister src, + Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtractLaneInt16x8(uint32_t lane, + FloatRegister src, + Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extractLaneFloat32x4(uint32_t lane, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extractLaneFloat64x2(uint32_t lane, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::replaceLaneInt8x16(unsigned lane, Register rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::replaceLaneInt16x8(unsigned lane, Register rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::replaceLaneInt32x4(unsigned lane, Register rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::replaceLaneFloat32x4(unsigned lane, FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::replaceLaneFloat64x2(unsigned lane, FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::shuffleInt8x16(const uint8_t lanes[16], FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::shuffleInt8x16(const uint8_t lanes[16], FloatRegister lhs, + FloatRegister rhs, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::laneSelectSimd128(FloatRegister mask, FloatRegister lhs, + FloatRegister rhs, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::interleaveHighInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::interleaveHighInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::interleaveHighInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::interleaveHighInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::interleaveLowInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::interleaveLowInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::interleaveLowInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::interleaveLowInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::concatAndRightShiftSimd128(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest, + uint32_t shift) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::leftShiftSimd128(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rightShiftSimd128(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::zeroExtend8x16To16x8(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::zeroExtend8x16To32x4(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::zeroExtend8x16To64x2(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::zeroExtend16x8To32x4(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::zeroExtend16x8To64x2(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::zeroExtend32x4To64x2(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::reverseInt16x8(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::reverseInt32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::reverseInt64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::swizzleInt8x16Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extMulLowInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extMulHighInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtMulLowInt8x16(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtMulHighInt8x16(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extMulLowInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extMulHighInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtMulLowInt16x8(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtMulHighInt16x8(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extMulLowInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extMulHighInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtMulLowInt32x4(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtMulHighInt32x4(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::q15MulrSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::negInt8x16(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::negInt16x8(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::negInt32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::negInt64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedAddSatInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedAddSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedSubSatInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedSubSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedMinInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedMinInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedMinInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedMaxInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedMaxInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedMaxInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedAverageInt8x16(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedAverageInt16x8(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::absInt8x16(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::absInt16x8(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::absInt32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::absInt64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::leftShiftInt8x16(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::leftShiftInt16x8(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::leftShiftInt32x4(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::leftShiftInt64x2(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rightShiftInt8x16(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedRightShiftInt8x16(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rightShiftInt16x8(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedRightShiftInt16x8(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rightShiftInt32x4(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedRightShiftInt32x4(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rightShiftInt64x2(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedRightShiftInt64x2(Imm32 count, FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseAndSimd128(FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseAndSimd128(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseOrSimd128(FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseOrSimd128(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseXorSimd128(FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseXorSimd128(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseNotSimd128(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseNotAndSimd128(FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::anyTrueSimd128(FloatRegister src, Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::allTrueInt8x16(FloatRegister src, Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::allTrueInt16x8(FloatRegister src, Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::allTrueInt32x4(FloatRegister src, Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::allTrueInt64x2(FloatRegister src, Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareInt8x16(Assembler::Condition cond, + FloatRegister rhs, FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareInt8x16(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareInt16x8(Assembler::Condition cond, + FloatRegister rhs, FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareInt16x8(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareInt32x4(Assembler::Condition cond, + FloatRegister rhs, FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareInt32x4(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareFloat32x4(Assembler::Condition cond, + FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareFloat32x4(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareFloat64x2(Assembler::Condition cond, + FloatRegister rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareFloat64x2(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::negFloat32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::negFloat64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::absFloat32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::absFloat64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extAddPairwiseInt8x16(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtAddPairwiseInt8x16(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extAddPairwiseInt16x8(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedExtAddPairwiseInt16x8(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::sqrtFloat32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::sqrtFloat64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::convertInt32x4ToFloat32x4(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedConvertInt32x4ToFloat32x4(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::convertInt32x4ToFloat64x2(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedConvertInt32x4ToFloat64x2(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::truncSatFloat32x4ToInt32x4(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::truncSatFloat64x2ToInt32x4(FloatRegister src, + FloatRegister dest, + FloatRegister temp) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedTruncSatFloat64x2ToInt32x4(FloatRegister src, + FloatRegister dest, + FloatRegister temp) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::truncFloat32x4ToInt32x4Relaxed(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedTruncFloat32x4ToInt32x4Relaxed( + FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::truncFloat64x2ToInt32x4Relaxed(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedTruncFloat64x2ToInt32x4Relaxed( + FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::convertFloat64x2ToFloat32x4(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::convertFloat32x4ToFloat64x2(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedNarrowInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedNarrowInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::widenLowInt8x16(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::widenHighInt8x16(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedWidenLowInt8x16(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedWidenHighInt8x16(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::widenLowInt16x8(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::widenHighInt16x8(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedWidenLowInt16x8(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedWidenHighInt16x8(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::widenLowInt32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedWidenLowInt32x4(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::widenHighInt32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedWidenHighInt32x4(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::pseudoMinFloat32x4(FloatRegister rhsOrRhsDest, + FloatRegister lhsOrLhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::pseudoMinFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::pseudoMinFloat64x2(FloatRegister rhsOrRhsDest, + FloatRegister lhsOrLhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::pseudoMinFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::pseudoMaxFloat32x4(FloatRegister rhsOrRhsDest, + FloatRegister lhsOrLhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::pseudoMaxFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::pseudoMaxFloat64x2(FloatRegister rhsOrRhsDest, + FloatRegister lhsOrLhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::pseudoMaxFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::dotInt8x16Int7x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::ceilFloat32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::ceilFloat64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::floorFloat32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::floorFloat64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::truncFloat32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::truncFloat64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::nearestFloat32x4(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::nearestFloat64x2(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::fnmaFloat32x4(FloatRegister src1, FloatRegister src2, + FloatRegister srcDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::fnmaFloat64x2(FloatRegister src1, FloatRegister src2, + FloatRegister srcDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat32x4Relaxed(FloatRegister src, + FloatRegister srcDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat32x4Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat32x4Relaxed(FloatRegister src, + FloatRegister srcDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat32x4Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat64x2Relaxed(FloatRegister src, + FloatRegister srcDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat64x2Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat64x2Relaxed(FloatRegister src, + FloatRegister srcDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat64x2Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::q15MulrInt16x8Relaxed(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::popcntInt8x16(FloatRegister src, FloatRegister dest, + FloatRegister temp) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedTruncSatFloat32x4ToInt32x4(FloatRegister src, + FloatRegister dest, + FloatRegister temp) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::dotInt8x16Int7x16ThenAdd(FloatRegister lhs, + FloatRegister rhs, + FloatRegister dest, + FloatRegister temp) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::permuteInt16x8(const uint16_t lanes[8], FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rotateRightSimd128(FloatRegister src, FloatRegister dest, + uint32_t shift) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::mulInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseAndNotSimd128(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitwiseSelectSimd128(FloatRegister onTrue, + FloatRegister onFalse, + FloatRegister maskDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::popcntInt8x16(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitmaskInt8x16(FloatRegister src, Register dest, + FloatRegister temp) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitmaskInt16x8(FloatRegister src, Register dest, + FloatRegister temp) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitmaskInt32x4(FloatRegister src, Register dest, + FloatRegister temp) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::bitmaskInt64x2(FloatRegister src, Register dest, + FloatRegister temp) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareInt64x2(Assembler::Condition cond, + FloatRegister rhs, FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::compareInt64x2(Assembler::Condition cond, + FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat32x4(FloatRegister rhs, FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat64x2(FloatRegister rhs, FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat32x4(FloatRegister rhs, FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat64x2(FloatRegister rhs, FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest, FloatRegister temp1, + FloatRegister temp2) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedTruncSatFloat32x4ToInt32x4(FloatRegister src, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extractLaneInt64x2(uint32_t lane, FloatRegister src, + Register64 dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::replaceLaneInt64x2(unsigned lane, Register64 rhs, + FloatRegister lhsDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::addFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::addFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::addInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::addInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::divFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extractLaneInt16x8(uint32_t lane, FloatRegister src, + Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extractLaneInt32x4(uint32_t lane, FloatRegister src, + Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::extractLaneInt8x16(uint32_t lane, FloatRegister src, + Register dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::maxInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::mulInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::narrowInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::splatX2(Register64 src, FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::subInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::swizzleInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::addInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::addInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::addSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::addSatInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::divFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::minInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::mulFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::mulFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::mulInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::narrowInt32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::subFloat32x4(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::subFloat64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::subInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::subInt64x2(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::subInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::subSatInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::subSatInt8x16(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::widenDotInt16x8(FloatRegister lhs, FloatRegister rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::leftShiftInt8x16(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rightShiftInt8x16(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedRightShiftInt8x16(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::leftShiftInt16x8(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rightShiftInt16x8(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedRightShiftInt16x8(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::leftShiftInt32x4(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::leftShiftInt64x2(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rightShiftInt32x4(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::rightShiftInt64x2(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedRightShiftInt32x4(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::unsignedRightShiftInt64x2(FloatRegister lhs, Register rhs, + FloatRegister dest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::fmaFloat32x4(FloatRegister src1, FloatRegister src2, + FloatRegister srcDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +void MacroAssembler::fmaFloat64x2(FloatRegister src1, FloatRegister src2, + FloatRegister srcDest) { + MOZ_CRASH("SIMD unsupported on sparc64"); +} + +//}}} check_macroassembler_style + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_MacroAssembler_sparc64_inl_h */ diff -Naurp empty/js/src/jit/sparc64/MacroAssembler-sparc64.cpp firefox-153.0/js/src/jit/sparc64/MacroAssembler-sparc64.cpp --- firefox-153.0/js/src/jit/sparc64/MacroAssembler-sparc64.cpp 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/MacroAssembler-sparc64.cpp 2026-07-30 01:08:37.804416427 +0200 @@ -0,0 +1,3674 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#include "jit/sparc64/MacroAssembler-sparc64.h" + +#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/sparc64/SharedICRegisters-sparc64.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& MacroAssemblerSPARC64::asMasm() { + return *static_cast(this); +} + +const MacroAssembler& MacroAssemblerSPARC64::asMasm() const { + return *static_cast(this); +} + +// =============================================================== +// Raw encodings the in-place patchers write. + +static uint32_t EncodeCall(int32_t disp) { + return FMT1 | (uint32_t(disp >> 2) & Disp30Mask); +} + +static uint32_t EncodeJmpl(Register rd, Register rs1, int32_t imm) { + return FMT3A | Rd(rd) | (uint32_t(OP3_JMPL) << Op3Shift) | Rs1(rs1) | + Simm13(imm); +} + +// =============================================================== +// Constant materialisation. + +void MacroAssemblerSPARC64::ma_li(Register dest, ImmWord imm) { + int64_t v = int64_t(imm.value); + + if (IsInSimm13Range(v)) { + as_or(dest, g0, int32_t(v)); + return; + } + if (v > 0 && v <= int64_t(UINT32_MAX)) { + uint32_t u = uint32_t(v); + as_sethi(dest, u >> 10); + if (u & 0x3ff) { + as_or(dest, dest, int32_t(u & 0x3ff)); + } + return; + } + if (v < 0 && v >= int64_t(INT32_MIN)) { + // sethi %hi(~v); xor rd, %lo(v) | 0x1c00 — the canonical negative-word + // form. The immediate is written as its signed 13-bit value. + uint32_t nv = ~uint32_t(v); + as_sethi(dest, nv >> 10); + as_xor(dest, dest, int32_t(uint32_t(v) & 0x3ff) - 1024); + return; + } + + // Full 64-bit. %g3 is neither allocatable nor part of the scratch pool, so + // this never disturbs a scratch a caller is holding. + Register tmp = (dest != g3) ? g3 : SecondScratchReg; + uint32_t hi = uint32_t(uint64_t(v) >> 32); + uint32_t lo = uint32_t(uint64_t(v)); + as_sethi(dest, hi >> 10); + as_or(dest, dest, int32_t(hi & 0x3ff)); + as_sllx(dest, dest, 32); + as_sethi(tmp, lo >> 10); + as_or(tmp, tmp, int32_t(lo & 0x3ff)); + as_or(dest, dest, tmp); +} + +void MacroAssemblerSPARC64::ma_li(Register dest, Imm32 imm) { + ma_li(dest, ImmWord(uint64_t(int64_t(imm.value)))); +} + +void MacroAssemblerSPARC64::ma_li(Register dest, ImmPtr imm) { + ma_li(dest, ImmWord(uintptr_t(imm.value))); +} + +void MacroAssemblerSPARC64::ma_li(Register dest, ImmGCPtr imm) { + BufferOffset bo = emitLoad64Stanza(dest, (uint64_t)uintptr_t(imm.value)); + Assembler::writeDataRelocation(bo, imm); +} + +// Emits exactly Load64InstructionCount NOPs and lets the assembler write the +// canonical sequence into them, so the value can be rewritten in place later. +// Clobbers %g3 (or %g1 when dest is %g3) as the sequence's temp. +BufferOffset MacroAssemblerSPARC64::emitLoad64Stanza(Register dest, + uint64_t value) { + m_buffer.enterNoPool(kNoPoolLoad64StanzaInsns); + BufferOffset bo = writeInst(NopInst); + for (size_t i = 1; i < kNoPoolLoad64StanzaInsns; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + // Only part of the stanza is in the buffer if any writeInst hit OOM; + // WriteLoad64Instructions writes all six words unconditionally. + if (m_buffer.oom()) { + return bo; + } + WriteLoad64Instructions((Instruction*)editSrc(bo), dest, value); + return bo; +} + +BufferOffset MacroAssemblerSPARC64::ma_liPatchable(Register dest, ImmWord imm) { + return emitLoad64Stanza(dest, imm.value); +} + +BufferOffset MacroAssemblerSPARC64::ma_liPatchable(Register dest, ImmPtr imm) { + return ma_liPatchable(dest, ImmWord(uintptr_t(imm.value))); +} + +// =============================================================== +// Memory. + +FaultingCodeOffset MacroAssemblerSPARC64::ma_load(Op3B op, Register rd, + Register base, int32_t off) { + int64_t o = biasedOffset(base, off); + if (IsInSimm13Range(o)) { + return FaultingCodeOffset(as_load(op, rd, base, int32_t(o)).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register index = temps.Acquire(); + ma_li(index, ImmWord(uint64_t(o))); + return FaultingCodeOffset(as_load(op, rd, base, index).getOffset()); +} + +FaultingCodeOffset MacroAssemblerSPARC64::ma_loadf(Op3B op, FloatRegister rd, + Register base, int32_t off) { + int64_t o = biasedOffset(base, off); + if (IsInSimm13Range(o)) { + return FaultingCodeOffset(as_loadf(op, rd, base, int32_t(o)).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register index = temps.Acquire(); + ma_li(index, ImmWord(uint64_t(o))); + return FaultingCodeOffset(as_loadf(op, rd, base, index).getOffset()); +} + +FaultingCodeOffset MacroAssemblerSPARC64::ma_store(Op3B op, Register rs, + Register base, int32_t off) { + int64_t o = biasedOffset(base, off); + if (IsInSimm13Range(o)) { + return FaultingCodeOffset(as_load(op, rs, base, int32_t(o)).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register index = temps.Acquire(); + MOZ_ASSERT(index != rs); + ma_li(index, ImmWord(uint64_t(o))); + return FaultingCodeOffset(as_load(op, rs, base, index).getOffset()); +} + +FaultingCodeOffset MacroAssemblerSPARC64::ma_storef(Op3B op, FloatRegister rs, + Register base, + int32_t off) { + int64_t o = biasedOffset(base, off); + if (IsInSimm13Range(o)) { + return FaultingCodeOffset(as_loadf(op, rs, base, int32_t(o)).getOffset()); + } + UseScratchRegisterScope temps(*this); + Register index = temps.Acquire(); + ma_li(index, ImmWord(uint64_t(o))); + return FaultingCodeOffset(as_loadf(op, rs, base, index).getOffset()); +} + +// =============================================================== +// simm13-safe ALU and comparison. + +void MacroAssemblerSPARC64::ma_alu(Op3A op, Register rd, Register rs, + int64_t imm) { + if (IsInSimm13Range(imm)) { + as_alu(op, rd, rs, int32_t(imm)); + return; + } + UseScratchRegisterScope temps(*this); + Register tmp = temps.Acquire(); + MOZ_ASSERT(tmp != rs); + ma_li(tmp, ImmWord(uint64_t(imm))); + as_alu(op, rd, rs, tmp); +} + +void MacroAssemblerSPARC64::ma_cmp64(Register lhs, int64_t imm) { + if (IsInSimm13Range(imm)) { + as_subcc(g0, lhs, int32_t(imm)); + } else { + UseScratchRegisterScope temps(*this); + Register tmp = temps.Acquire(); + ma_li(tmp, ImmWord(uint64_t(imm))); + as_subcc(g0, lhs, tmp); + } + lastCC_ = CC_XCC; +} + +void MacroAssemblerSPARC64::ma_cmp32(Register lhs, int64_t imm) { + if (IsInSimm13Range(imm)) { + as_subcc(g0, lhs, int32_t(imm)); + } else { + UseScratchRegisterScope temps(*this); + Register tmp = temps.Acquire(); + ma_li(tmp, ImmWord(uint64_t(imm))); + as_subcc(g0, lhs, tmp); + } + lastCC_ = CC_ICC; +} + +void MacroAssemblerSPARC64::ma_test64(Register lhs, int64_t imm) { + if (IsInSimm13Range(imm)) { + as_andcc(g0, lhs, int32_t(imm)); + } else { + UseScratchRegisterScope temps(*this); + Register tmp = temps.Acquire(); + ma_li(tmp, ImmWord(uint64_t(imm))); + as_andcc(g0, lhs, tmp); + } + lastCC_ = CC_XCC; +} + +void MacroAssemblerSPARC64::ma_test32(Register lhs, int64_t imm) { + if (IsInSimm13Range(imm)) { + as_andcc(g0, lhs, int32_t(imm)); + } else { + UseScratchRegisterScope temps(*this); + Register tmp = temps.Acquire(); + ma_li(tmp, ImmWord(uint64_t(imm))); + as_andcc(g0, lhs, tmp); + } + lastCC_ = CC_ICC; +} + +// =============================================================== +// Branches. Every control-transfer emitter writes its own delay slot, so a +// branch costs two words and nothing here emits a trailing nop. + +void MacroAssemblerSPARC64::jump(Label* label) { + if (label->bound()) { + m_buffer.enterNoPool(2); + int32_t offset = label->offset() - currentOffset(); + if (BOffImm19::IsInRange(offset)) { + as_bpcc(Always, CC_XCC, BOffImm19(offset)); + m_buffer.leaveNoPool(); + return; + } + m_buffer.leaveNoPool(); + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + BufferOffset bo = + emitLoad64Stanza(SecondScratchReg, LabelBase::INVALID_OFFSET); + as_jmpl(g0, SecondScratchReg, 0); + m_buffer.leaveNoPool(); + addLongJump(bo, BufferOffset(label->offset())); + return; + } + + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + BufferOffset bo = as_ta(BTag); + writeInst(label->used() ? label->offset() : LabelBase::INVALID_OFFSET); + for (size_t i = 0; i < kNoPoolLoad64StanzaInsns; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + if (!oom()) { + label->use(bo.getOffset()); + } +} + +void MacroAssemblerSPARC64::ma_bc(Condition cond, ConditionCode cc, + Label* label) { + if (label->bound()) { + m_buffer.enterNoPool(2); + int32_t offset = label->offset() - currentOffset(); + if (BOffImm19::IsInRange(offset)) { + as_bpcc(cond, cc, BOffImm19(offset)); + m_buffer.leaveNoPool(); + return; + } + m_buffer.leaveNoPool(); + m_buffer.enterNoPool(kNoPoolCondLongBranchInsns); + as_bpcc(InvertCondition(cond), cc, BOffImm19(kCondLongBranchSkip)); + BufferOffset bo = + emitLoad64Stanza(SecondScratchReg, LabelBase::INVALID_OFFSET); + as_jmpl(g0, SecondScratchReg, 0); + m_buffer.leaveNoPool(); + addLongJump(bo, BufferOffset(label->offset())); + return; + } + + m_buffer.enterNoPool(kNoPoolCondLongBranchInsns); + as_bpcc(InvertCondition(cond), cc, BOffImm19(kCondLongBranchSkip)); + BufferOffset bo = as_ta(BCTag); + writeInst(label->used() ? label->offset() : LabelBase::INVALID_OFFSET); + for (size_t i = 0; i < kNoPoolLoad64StanzaInsns; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + if (!oom()) { + label->use(bo.getOffset()); + } +} + +void MacroAssemblerSPARC64::ma_bc(DoubleCondition cond, Label* label) { + if (label->bound()) { + m_buffer.enterNoPool(2); + int32_t offset = label->offset() - currentOffset(); + if (BOffImm19::IsInRange(offset)) { + as_fbpfcc(cond, CC_FCC0, BOffImm19(offset)); + m_buffer.leaveNoPool(); + return; + } + m_buffer.leaveNoPool(); + m_buffer.enterNoPool(kNoPoolCondLongBranchInsns); + as_fbpfcc(InvertCondition(cond), CC_FCC0, BOffImm19(kCondLongBranchSkip)); + BufferOffset bo = + emitLoad64Stanza(SecondScratchReg, LabelBase::INVALID_OFFSET); + as_jmpl(g0, SecondScratchReg, 0); + m_buffer.leaveNoPool(); + addLongJump(bo, BufferOffset(label->offset())); + return; + } + + m_buffer.enterNoPool(kNoPoolCondLongBranchInsns); + as_fbpfcc(InvertCondition(cond), CC_FCC0, BOffImm19(kCondLongBranchSkip)); + BufferOffset bo = as_ta(BCTag); + writeInst(label->used() ? label->offset() : LabelBase::INVALID_OFFSET); + for (size_t i = 0; i < kNoPoolLoad64StanzaInsns; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + if (!oom()) { + label->use(bo.getOffset()); + } +} + +// =============================================================== +// GPR<->FPR moves. + +void MacroAssemblerSPARC64::moveGPRToFPR64(Register src, FloatRegister dest) { + if (HasVIS3()) { + as_movxtod(dest, src); + return; + } + as_sub(StackPointer, StackPointer, 16); + ma_store(OP3_STX, src, StackPointer, 0); + ma_loadf(OP3_LDDF, dest, StackPointer, 0); + as_add(StackPointer, StackPointer, 16); +} + +void MacroAssemblerSPARC64::moveFPR64ToGPR(FloatRegister src, Register dest) { + if (HasVIS3()) { + as_movdtox(dest, src); + return; + } + as_sub(StackPointer, StackPointer, 16); + ma_storef(OP3_STDF, src, StackPointer, 0); + ma_load(OP3_LDX, dest, StackPointer, 0); + as_add(StackPointer, StackPointer, 16); +} + +// The single-precision view of encoding i is the high word of %d(2i), which is +// exactly what stf/ldf transfer, so a raw 32-bit round trip needs no shifting. +void MacroAssemblerSPARC64::moveGPRToFPR32(Register src, FloatRegister dest) { + as_sub(StackPointer, StackPointer, 16); + ma_store(OP3_STW, src, StackPointer, 0); + ma_loadf(OP3_LDF, dest.asSingle(), StackPointer, 0); + as_add(StackPointer, StackPointer, 16); +} + +void MacroAssemblerSPARC64::moveFPR32ToGPR(FloatRegister src, Register dest) { + as_sub(StackPointer, StackPointer, 16); + ma_storef(OP3_STF, src.asSingle(), StackPointer, 0); + ma_load(OP3_LDUW, dest, StackPointer, 0); + as_add(StackPointer, StackPointer, 16); +} + +void MacroAssemblerSPARC64::moveGPRWordZeroToFPR(Register src, + FloatRegister dest) { + UseScratchRegisterScope temps(asMasm()); + Register tmp = temps.Acquire(); + as_srl(tmp, src, 0); + moveGPRToFPR64(tmp, dest); +} + +void MacroAssemblerSPARC64::emitCopySign(FloatRegister dest, + FloatRegister signSrc, + FloatRegister magSrc) { + UseScratchRegisterScope temps(asMasm()); + Register mag = temps.Acquire(); + Register sign = temps.Acquire(); + moveFPR64ToGPR(magSrc, mag); + moveFPR64ToGPR(signSrc, sign); + as_sllx(mag, mag, 1); + as_srlx(mag, mag, 1); + as_srlx(sign, sign, 63); + as_sllx(sign, sign, 63); + as_or(mag, mag, sign); + moveGPRToFPR64(mag, dest); +} + +void MacroAssemblerSPARC64::roundToInt64FPR(FloatRegister fpDest, + FloatRegister src, FpIntRound m) { + // SPARC's convert-to-integer always truncates toward zero, so compute the + // truncation, convert it back and correct by one when it moved the wrong + // way. NaN and out-of-range inputs leave the (saturated) truncation alone; + // every caller range-checks the result afterwards. + // src is read after fpDest is written, so the two must be distinct. + MOZ_ASSERT(fpDest != src); + UseScratchRegisterScope temps(asMasm()); + Register ival = temps.Acquire(); + Label done; + + as_fpop1(OPF_FDTOX, fpDest, src); + moveFPR64ToGPR(fpDest, ival); + as_fpop1(OPF_FXTOD, fpDest, fpDest); + ma_fcmp(fpDest, src); + ma_bc(m == RoundDown ? DoubleLessThanOrEqualOrUnordered + : DoubleGreaterThanOrEqualOrUnordered, + &done); + as_alu(m == RoundDown ? OP3_SUB : OP3_ADD, ival, ival, 1); + bind(&done); + moveGPRToFPR64(ival, fpDest); +} + +void MacroAssemblerSPARC64::roundFloat32ToInt64GPR(Register dest, + FloatRegister src, + FpIntRound m) { + // Same truncate-and-correct shape as roundToInt64FPR, but the comparison + // stays in single precision so src is never overwritten: the double scratch + // holds the truncation and its single view holds the value converted back. + // Correct for every float32: above 2^24 the input is already integral and + // compares equal, below it the truncation is exact in single precision. + ScratchDoubleScope fpscratch(asMasm()); + FloatRegister back = fpscratch.asSingle(); + Label done; + + as_fpop1(OPF_FSTOX, fpscratch, src); + moveFPR64ToGPR(fpscratch, dest); + as_fpop1(OPF_FXTOS, back, fpscratch); + ma_fcmps(back, src); + ma_bc(m == RoundDown ? DoubleLessThanOrEqualOrUnordered + : DoubleGreaterThanOrEqualOrUnordered, + &done); + as_alu(m == RoundDown ? OP3_SUB : OP3_ADD, dest, dest, 1); + bind(&done); +} + +// =============================================================== +// Out-of-line fake exit frame + +bool MacroAssemblerSPARC64Compat::buildOOLFakeExitFrame(void* fakeReturnAddr) { + asMasm().Push(FrameDescriptor(FrameType::IonJS)); + asMasm().Push(ImmPtr(fakeReturnAddr)); + asMasm().Push(FramePointer); + return true; +} + +// =============================================================== +// FP constants + +void MacroAssemblerSPARC64Compat::loadConstantDouble(double dp, + FloatRegister dest) { + union { + double d; + uint64_t u; + } u; + u.d = dp; + if (u.u == 0) { + moveGPRToFPR64(g0, dest); + return; + } + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + ma_li(scratch, ImmWord(u.u)); + moveGPRToFPR64(scratch, dest); +} + +void MacroAssemblerSPARC64Compat::loadConstantFloat32(float f, + FloatRegister dest) { + union { + float f; + uint32_t u; + } u; + u.f = f; + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + ma_li(scratch, ImmWord(uint64_t(u.u))); + moveGPRToFPR32(scratch, dest); +} + +// =============================================================== +// Load int32 or double from memory + +void MacroAssemblerSPARC64Compat::loadInt32OrDouble(const Address& src, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + Label end; + + loadPtr(src, scratch); + moveGPRToFPR64(scratch, dest); + as_srlx(scratch, scratch, JSVAL_TAG_SHIFT); + asMasm().branchTestInt32(Assembler::NotEqual, scratch, &end); + moveFPR64ToGPR(dest, scratch); + as_sra(scratch, scratch, 0); + moveGPRToFPR64(scratch, dest); + as_fpop1(OPF_FXTOD, dest, dest); + + bind(&end); +} + +void MacroAssemblerSPARC64Compat::loadInt32OrDouble(const BaseIndex& addr, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(addr, scratch); + loadInt32OrDouble(Address(scratch, addr.offset), dest); +} + +// =============================================================== +// Conversion functions + +void MacroAssemblerSPARC64Compat::convertUInt32ToDouble(Register src, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_srl(scratch, src, 0); + moveGPRToFPR64(scratch, dest); + as_fpop1(OPF_FXTOD, dest, dest); +} + +void MacroAssemblerSPARC64Compat::convertUInt32ToFloat32(Register src, + FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_srl(scratch, src, 0); + moveGPRToFPR64(scratch, ScratchDoubleReg); + as_fpop1(OPF_FXTOS, dest, ScratchDoubleReg); +} + +// Precondition: `dest` holds the integer round-trip result. If it is zero the +// input was +0.0 or -0.0; tell them apart by the sign bit of `src`. +static void EmitNegativeZeroCheck(MacroAssemblerSPARC64Compat& masm, + FloatRegister src, Register dest, + Label* fail) { + Label notZero; + masm.ma_cmp64(dest, int64_t(0)); + masm.ma_b(Assembler::NotEqual, ¬Zero); + UseScratchRegisterScope temps(masm); + Register scratch = temps.Acquire(); + masm.moveFPR64ToGPR(src, scratch); + masm.ma_cmp64(scratch, int64_t(0)); + masm.ma_b(Assembler::LessThan, fail); + masm.bind(¬Zero); +} + +void MacroAssemblerSPARC64Compat::convertDoubleToInt32(FloatRegister src, + Register dest, + Label* fail, + bool negativeZeroCheck) { + ScratchDoubleScope fpscratch(asMasm()); + as_fpop1(OPF_FDTOX, fpscratch, src); + moveFPR64ToGPR(fpscratch, dest); + { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(Assembler::NotEqual, fail); + } + moveGPRToFPR64(dest, fpscratch); + as_fpop1(OPF_FXTOD, fpscratch, fpscratch); + ma_fcmp(fpscratch, src); + ma_b(Assembler::DoubleNotEqualOrUnordered, fail); + + if (negativeZeroCheck) { + EmitNegativeZeroCheck(*this, src, dest, fail); + } +} + +void MacroAssemblerSPARC64Compat::convertDoubleToPtr(FloatRegister src, + Register dest, Label* fail, + bool negativeZeroCheck) { + ScratchDoubleScope fpscratch(asMasm()); + as_fpop1(OPF_FDTOX, fpscratch, src); + moveFPR64ToGPR(fpscratch, dest); + as_fpop1(OPF_FXTOD, fpscratch, fpscratch); + ma_fcmp(fpscratch, src); + ma_b(Assembler::DoubleNotEqualOrUnordered, fail); + + if (negativeZeroCheck) { + EmitNegativeZeroCheck(*this, src, dest, fail); + } +} + +void MacroAssemblerSPARC64Compat::convertFloat32ToInt32( + FloatRegister src, Register dest, Label* fail, bool negativeZeroCheck) { + ScratchDoubleScope fpscratch(asMasm()); + as_fpop1(OPF_FSTOX, fpscratch, src); + moveFPR64ToGPR(fpscratch, dest); + { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(Assembler::NotEqual, fail); + } + moveGPRToFPR64(dest, fpscratch); + as_fpop1(OPF_FXTOS, ScratchFloat32Reg, fpscratch); + ma_fcmps(ScratchFloat32Reg, src); + ma_b(Assembler::DoubleNotEqualOrUnordered, fail); + + if (negativeZeroCheck) { + EmitNegativeZeroCheck(*this, src, dest, fail); + } +} + +// =============================================================== +// Unaligned accesses. SupportsUnalignedAccesses() is false, so these are +// synthesized from byte accesses. Big-endian byte order throughout. + +// The byte at the lowest address is the most significant one, so the result is +// bit-for-bit what an (illegal) aligned load of the same width would have +// produced on this big-endian target. Callers that byte-swap afterwards must +// not be changed. +void MacroAssemblerSPARC64Compat::loadUnalignedBytes(Op3B firstOp, + Register base, + int32_t offset, int nbytes, + Register dest) { + UseScratchRegisterScope temps(*this); + Register acc = temps.Acquire(); + // dest may alias base (the bytecode pc is often the destination's scratch), + // so accumulate away from both and write dest only at the end. %g3 is the + // third non-allocatable scratch: g1/g2 alone cannot cover the accumulator, + // the byte temp, and the index ma_load materialises for a wide offset. + Register byteTemp = g3; + MOZ_ASSERT(acc != base && acc != byteTemp && base != byteTemp); + ma_load(firstOp, acc, base, offset); + for (int i = 1; i < nbytes; i++) { + ma_load(OP3_LDUB, byteTemp, base, offset + i); + as_sllx(acc, acc, 8); + as_or(acc, acc, byteTemp); + } + if (dest != acc) { + as_or(dest, g0, acc); + } +} + +void MacroAssemblerSPARC64Compat::load16UnalignedZeroExtend(const Address& src, + Register dest) { + loadUnalignedBytes(OP3_LDUB, src.base, src.offset, 2, dest); +} + +void MacroAssemblerSPARC64Compat::load16UnalignedZeroExtend( + const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register base = temps.Acquire(); + computeScaledAddress(src, base); + load16UnalignedZeroExtend(Address(base, src.offset), dest); +} + +void MacroAssemblerSPARC64Compat::load16UnalignedSignExtend(const Address& src, + Register dest) { + loadUnalignedBytes(OP3_LDSB, src.base, src.offset, 2, dest); +} + +void MacroAssemblerSPARC64Compat::load16UnalignedSignExtend( + const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register base = temps.Acquire(); + computeScaledAddress(src, base); + load16UnalignedSignExtend(Address(base, src.offset), dest); +} + +void MacroAssemblerSPARC64Compat::load32Unaligned(const Address& src, + Register dest) { + loadUnalignedBytes(OP3_LDSB, src.base, src.offset, 4, dest); +} + +void MacroAssemblerSPARC64Compat::load32Unaligned(const BaseIndex& src, + Register dest) { + UseScratchRegisterScope temps(*this); + Register base = temps.Acquire(); + computeScaledAddress(src, base); + load32Unaligned(Address(base, src.offset), dest); +} + +void MacroAssemblerSPARC64Compat::load32UnalignedZeroExtend(const Address& src, + Register dest) { + loadUnalignedBytes(OP3_LDUB, src.base, src.offset, 4, dest); +} + +void MacroAssemblerSPARC64Compat::load64Unaligned(const Address& src, + Register64 dest) { + loadUnalignedBytes(OP3_LDUB, src.base, src.offset, 8, dest.reg); +} + +void MacroAssemblerSPARC64Compat::load64Unaligned(const BaseIndex& src, + Register64 dest) { + UseScratchRegisterScope temps(*this); + Register base = temps.Acquire(); + computeScaledAddress(src, base); + load64Unaligned(Address(base, src.offset), dest); +} + +void MacroAssemblerSPARC64Compat::store16Unaligned(Register src, + const Address& dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_srlx(scratch, src, 8); + ma_store(OP3_STB, scratch, dest.base, dest.offset); + ma_store(OP3_STB, src, dest.base, dest.offset + 1); +} + +void MacroAssemblerSPARC64Compat::store16Unaligned(Register src, + const BaseIndex& dest) { + UseScratchRegisterScope temps(*this); + Register base = temps.Acquire(); + computeScaledAddress(dest, base); + store16Unaligned(src, Address(base, dest.offset)); +} + +void MacroAssemblerSPARC64Compat::store32Unaligned(Register src, + const Address& dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + for (int i = 0; i < 3; i++) { + as_srlx(scratch, src, 8 * (3 - i)); + ma_store(OP3_STB, scratch, dest.base, dest.offset + i); + } + ma_store(OP3_STB, src, dest.base, dest.offset + 3); +} + +void MacroAssemblerSPARC64Compat::store32Unaligned(Register src, + const BaseIndex& dest) { + UseScratchRegisterScope temps(*this); + Register base = temps.Acquire(); + computeScaledAddress(dest, base); + store32Unaligned(src, Address(base, dest.offset)); +} + +void MacroAssemblerSPARC64Compat::store64Unaligned(Register64 src, + const Address& dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + for (int i = 0; i < 7; i++) { + as_srlx(scratch, src.reg, 8 * (7 - i)); + ma_store(OP3_STB, scratch, dest.base, dest.offset + i); + } + ma_store(OP3_STB, src.reg, dest.base, dest.offset + 7); +} + +void MacroAssemblerSPARC64Compat::store64Unaligned(Register64 src, + const BaseIndex& dest) { + UseScratchRegisterScope temps(*this); + Register base = temps.Acquire(); + computeScaledAddress(dest, base); + store64Unaligned(src, Address(base, dest.offset)); +} + +// =============================================================== +// Toggled call + +CodeOffset MacroAssemblerSPARC64Compat::toggledCall(JitCode* target, + bool enabled) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + BufferOffset boLoad = + emitLoad64Stanza(scratch, (uint64_t)uintptr_t(target->raw())); + CodeOffset offset(boLoad.getOffset()); + addPendingJump(boLoad, ImmPtr(target->raw()), RelocationKind::JITCODE); + if (enabled) { + as_jmpl(LinkRegister, scratch, 0); + } else { + writeInst(NopInst); + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + MOZ_ASSERT_IF(!oom(), nextOffset().getOffset() - offset.offset() == + ToggledCallSize(nullptr)); + return offset; +} + +// =============================================================== +// Exception handling + +void MacroAssemblerSPARC64Compat::handleFailureWithHandlerTail( + Label* profilerExitTail, Label* bailoutTail, + uint32_t* returnValueCheckOffset) { + int size = (sizeof(ResumeFromException) + ABIStackAlignment - 1) & + ~(ABIStackAlignment - 1); + asMasm().subPtr(Imm32(size), StackPointer); + asMasm().moveStackPtrTo(o0); + + using Fn = void (*)(ResumeFromException* rfe); + asMasm().setupUnalignedABICall(o1); + asMasm().passABIArg(o0); + 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()), o0); + asMasm().branch32(Assembler::Equal, o0, + Imm32(ExceptionResumeKind::EntryFrame), &entryFrame); + asMasm().branch32(Assembler::Equal, o0, Imm32(ExceptionResumeKind::Catch), + &catch_); + asMasm().branch32(Assembler::Equal, o0, Imm32(ExceptionResumeKind::Finally), + &finally); + asMasm().branch32(Assembler::Equal, o0, + Imm32(ExceptionResumeKind::ForcedReturnBaseline), + &returnBaseline); + asMasm().branch32(Assembler::Equal, o0, + Imm32(ExceptionResumeKind::ForcedReturnIon), &returnIon); + asMasm().branch32(Assembler::Equal, o0, Imm32(ExceptionResumeKind::Bailout), + &bailout); + asMasm().branch32(Assembler::Equal, o0, + Imm32(ExceptionResumeKind::WasmInterpEntry), + &wasmInterpEntry); + asMasm().branch32(Assembler::Equal, o0, 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); + asMasm().loadStackPtr( + Address(StackPointer, ResumeFromException::offsetOfStackPointer())); + ret(); + + // Catch handler. + bind(&catch_); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfTarget()), o0); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + asMasm().loadStackPtr( + Address(StackPointer, ResumeFromException::offsetOfStackPointer())); + jump(o0); + + // Finally block. + bind(&finally); + ValueOperand exception = ValueOperand(o1); + loadValue(Address(StackPointer, ResumeFromException::offsetOfException()), + exception); + + ValueOperand exceptionStack = ValueOperand(o4); + loadValue( + Address(StackPointer, ResumeFromException::offsetOfExceptionStack()), + exceptionStack); + + loadPtr(Address(StackPointer, ResumeFromException::offsetOfTarget()), o0); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + asMasm().loadStackPtr( + Address(StackPointer, ResumeFromException::offsetOfStackPointer())); + + pushValue(exception); + pushValue(exceptionStack); + pushValue(BooleanValue(true)); + jump(o0); + + // Forced return from baseline. + Label profilingInstrumentation; + bind(&returnBaseline); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + asMasm().loadStackPtr( + Address(StackPointer, ResumeFromException::offsetOfStackPointer())); + 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); + asMasm().loadStackPtr( + Address(StackPointer, ResumeFromException::offsetOfStackPointer())); + + bind(&profilingInstrumentation); + { + Label skipProfilingInstrumentation; + AbsoluteAddress addressOfEnabled( + asMasm().runtime()->geckoProfiler().addressOfEnabled()); + asMasm().branch32(Assembler::Equal, addressOfEnabled, Imm32(0), + &skipProfilingInstrumentation); + jump(profilerExitTail); + bind(&skipProfilingInstrumentation); + } + + asMasm().moveToStackPtr(FramePointer); + loadPtr(Address(StackPointer, 0), FramePointer); + asMasm().addPtr(Imm32(sizeof(void*)), StackPointer); + ret(); + + // Bailout. + bind(&bailout); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfBailoutInfo()), + IntArgReg2); + asMasm().loadStackPtr( + Address(StackPointer, ResumeFromException::offsetOfStackPointer())); + ma_li(ReturnReg, Imm32(1)); + jump(bailoutTail); + + // Wasm interp entry. + bind(&wasmInterpEntry); + loadPtr(Address(StackPointer, ResumeFromException::offsetOfFramePointer()), + FramePointer); + asMasm().loadStackPtr( + Address(StackPointer, ResumeFromException::offsetOfStackPointer())); + movePtr(ImmWord(wasm::InterpFailInstanceReg), InstanceReg); + ret(); + + // Wasm catch. + bind(&wasmCatch); + wasm::GenerateJumpToCatchHandler(asMasm(), StackPointer, o0, o1, o4); +} + +void MacroAssembler::clampDoubleToUint8(FloatRegister input, Register output) { + ScratchDoubleScope fpscratch(asMasm()); + Label positive, below255, roundUp, 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); + // 0 < input < 255. SPARC only truncates, so round half-to-even by hand: + // compare the fractional part's bit pattern against that of 0.5 (valid as an + // unsigned integer compare because the fraction is non-negative). + as_fpop1(OPF_FDTOX, fpscratch, input); + moveFPR64ToGPR(fpscratch, output); + as_fpop1(OPF_FXTOD, fpscratch, fpscratch); + as_fpop1(OPF_FSUBd, fpscratch, input, fpscratch); + { + UseScratchRegisterScope temps(asMasm()); + Register bits = temps.Acquire(); + Register tmp = temps.Acquire(); + moveFPR64ToGPR(fpscratch, bits); + as_srlx(tmp, bits, 52); + ma_cmp64(tmp, int64_t(0x3fe)); + ma_b(Assembler::Below, &done); + as_sllx(tmp, bits, 12); + ma_cmp64(tmp, int64_t(0)); + ma_b(Assembler::NotEqual, &roundUp); + as_and(tmp, output, 1); + ma_cmp64(tmp, int64_t(0)); + ma_b(Assembler::Equal, &done); + } + bind(&roundUp); + as_add(output, output, 1); + bind(&done); +} + +void MacroAssembler::subFromStackPtr(Imm32 imm32) { + if (imm32.value) { + asMasm().subPtr(imm32, StackPointer); + } +} + +//{{{ check_macroassembler_style + +void MacroAssembler::widenInt32(Register r) { + move32To64SignExtend(r, Register64(r)); +} + +// 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)); + } + + for (FloatRegisterBackwardIterator iter(set.fpus().reduceSetForPush()); + iter.more(); ++iter) { + FloatRegister reg = *iter; + diff -= reg.size(); + // Simd128 is never allocated (SupportsWasmSimd() is false); the mask only + // ever names it statically, so the reserved slot is left unwritten. + if (!reg.isSimd128()) { + 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) && !reg.isSimd128()) { + loadDouble(Address(StackPointer, diff), reg.asDouble()); + } + } + MOZ_ASSERT(diff == 0); + freeStack(reserved); +} + +// Call operations. +CodeOffset MacroAssembler::call(Register reg) { + as_jmpl(LinkRegister, reg, 0); + return CodeOffset(currentOffset()); +} + +CodeOffset MacroAssembler::call(Label* label) { + if (label->bound()) { + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + int32_t offset = label->offset() - currentOffset(); + // The call goes at inst[6] of the 8-word stanza. + int32_t callOffset = + offset - int32_t(kNoPoolLoad64StanzaInsns) * int32_t(sizeof(uint32_t)); + if (JOffImm30::IsInRange(callOffset)) { + for (size_t i = 0; i < kNoPoolLoad64StanzaInsns; i++) { + writeInst(NopInst); + } + as_call(JOffImm30(callOffset)); + m_buffer.leaveNoPool(); + return CodeOffset(currentOffset()); + } + BufferOffset bo = + emitLoad64Stanza(SecondScratchReg, LabelBase::INVALID_OFFSET); + as_jmpl(LinkRegister, SecondScratchReg, 0); + m_buffer.leaveNoPool(); + addLongJump(bo, BufferOffset(label->offset())); + return CodeOffset(currentOffset()); + } + + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + BufferOffset bo = as_ta(CallTag); + writeInst(label->used() ? label->offset() : LabelBase::INVALID_OFFSET); + for (size_t i = 0; i < kNoPoolLoad64StanzaInsns; 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(kNoPoolPatchableBranchInsns); + BufferOffset bo = emitLoad64Stanza(CallReg, (uint64_t)uintptr_t(target.value)); + addPendingJump(bo, target, RelocationKind::HARDCODED); + as_jmpl(LinkRegister, CallReg, 0); + m_buffer.leaveNoPool(); +} + +CodeOffset MacroAssembler::call(wasm::SymbolicAddress target) { + movePtr(target, CallReg); + return call(CallReg); +} + +void MacroAssembler::call(ImmWord imm) { call(ImmPtr((void*)imm.value)); } + +void MacroAssembler::call(JitCode* c) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + BufferOffset bo = emitLoad64Stanza(scratch, (uint64_t)uintptr_t(c->raw())); + addPendingJump(bo, ImmPtr(c->raw()), RelocationKind::JITCODE); + callJitNoProfiler(scratch); +} + +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_); + uint32_t stackForCall = abiArgs_.stackBytesConsumedSoFar(); + + // Slot for the %o7 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); + + storePtr(LinkRegister, + Address(StackPointer, stackForCall - sizeof(intptr_t))); + + { + enoughMemory_ &= moveResolver_.resolve(); + if (!enoughMemory_) { + return; + } + + MoveEmitter emitter(*this); + emitter.emit(moveResolver_); + emitter.finish(); + } + + assertStackAlignment(ABIStackAlignment); +} + +void MacroAssembler::callWithABIPost(uint32_t stackAdjust, ABIType result) { + loadPtr(Address(StackPointer, stackAdjust - sizeof(intptr_t)), LinkRegister); + + if (dynamicAlignment_) { + loadPtr(Address(StackPointer, stackAdjust), StackPointer); + adjustFrame(-stackAdjust); + } else { + freeStack(stackAdjust); + } + +#ifdef DEBUG + MOZ_ASSERT(inCall_); + inCall_ = false; +#endif +} + +void MacroAssembler::callWithABINoProfiler(Register fun, ABIType result) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + movePtr(fun, scratch); + + uint32_t stackAdjust; + callWithABIPre(&stackAdjust); + call(scratch); + callWithABIPost(stackAdjust, result); +} + +// Value operations. +void MacroAssembler::moveValue(const ValueOperand& src, + const ValueOperand& dest) { + if (src.valueReg() != dest.valueReg()) { + movePtr(src.valueReg(), dest.valueReg()); + } +} + +void MacroAssembler::moveValue(const Value& src, const ValueOperand& dest) { + if (!src.isGCThing()) { + movePtr(ImmWord(src.asRawBits()), dest.valueReg()); + return; + } + CodeOffset off = movWithPatch(ImmWord(src.asRawBits()), dest.valueReg()); + writeDataRelocation(off, src); +} + +// 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()); + + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + MOZ_ASSERT(lhs.valueReg() != scratch); + if (!rhs.isGCThing()) { + movePtr(ImmWord(rhs.asRawBits()), scratch); + } else { + moveValue(rhs, ValueOperand(scratch)); + } + branchPtr(cond, lhs.valueReg(), scratch, label); +} + +void MacroAssembler::branchTestNaNValue(Condition cond, const ValueOperand& val, + Register temp, Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + MOZ_ASSERT(val.valueReg() != scratch); + + // Strip the IEEE sign bit from both sides before comparing. + as_sllx(temp, val.valueReg(), 1); + as_srlx(temp, temp, 1); + + static_assert(JS::detail::CanonicalizedNaNSignBit == 0); + moveValue(DoubleValue(JS::GenericNaN()), ValueOperand(scratch)); + as_sllx(scratch, scratch, 1); + as_srlx(scratch, scratch, 1); + + branchPtr(cond, temp, scratch, label); +} + +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); +} + +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); +} + +// Patching / near address operations. +CodeOffset MacroAssembler::nopPatchableToCall() { + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + for (size_t i = 0; i < kNoPoolPatchableBranchInsns; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + return CodeOffset(currentOffset()); +} + +CodeOffset MacroAssembler::moveNearAddressWithPatch(Register dest) { + CodeOffset offset(currentOffset()); + emitLoad64Stanza(dest, 0); + return offset; +} + +// static +void MacroAssembler::patchNearAddressMove(CodeLocationLabel loc, + CodeLocationLabel target) { + Instruction* inst = (Instruction*)loc.raw(); + UpdateLoad64Value(inst, (uint64_t)target.raw()); +} + +// Return address operations. +// +// `call` leaves %o7 pointing at the call instruction itself, but every JIT +// frame must hold a real code address so JSJitFrameIter and the bailout +// machinery can use it directly. Normalise on the way in and out. +void MacroAssembler::pushReturnAddress() { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_add(scratch, LinkRegister, 8); + push(scratch); +} + +void MacroAssembler::popReturnAddress() { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + pop(scratch); + as_sub(LinkRegister, scratch, 8); +} + +// ABI setup. +void MacroAssembler::setupUnalignedABICall(Register scratch) { + MOZ_ASSERT(!IsCompilingWasm(), "wasm should only use aligned ABI calls"); + setupNativeABICall(); + dynamicAlignment_ = true; + + // Save the raw %o6: callWithABIPost restores it with a plain loadPtr, so the + // biased register value has to round-trip, not the logical stack pointer. + as_or(scratch, g0, StackPointer); + + // Align the *logical* stack pointer (%o6 + SP_BASE); the raw register is + // SP_BASE below it, so convert, mask and convert back. + ma_add(StackPointer, StackPointer, int64_t(SP_BASE) - int64_t(sizeof(uintptr_t))); + ma_and(StackPointer, StackPointer, ~int64_t(ABIStackAlignment - 1)); + ma_add(StackPointer, StackPointer, -int64_t(SP_BASE)); + 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"); + + // SPARC V9 has no 32-bit divide worth using; widen to 64 bits and use + // sdivx/udivx, then compute the remainder as lhs - quot * rhs. + UseScratchRegisterScope temps(asMasm()); + Register a = temps.Acquire(); + Register b = temps.Acquire(); + if (isUnsigned) { + as_srl(a, lhs, 0); + as_srl(b, rhs, 0); + as_udivx(divOutput, a, b); + } else { + as_sra(a, lhs, 0); + as_sra(b, rhs, 0); + as_sdivx(divOutput, a, b); + } + as_mulx(remOutput, divOutput, b); + as_sub(remOutput, a, remOutput); + as_sra(divOutput, divOutput, 0); + as_sra(remOutput, remOutput, 0); +} + +void MacroAssembler::flexibleQuotient32( + Register lhs, Register rhs, Register dest, bool isUnsigned, + const LiveRegisterSet& volatileLiveRegs) { + UseScratchRegisterScope temps(asMasm()); + Register a = temps.Acquire(); + Register b = temps.Acquire(); + if (isUnsigned) { + as_srl(a, lhs, 0); + as_srl(b, rhs, 0); + as_udivx(dest, a, b); + } else { + as_sra(a, lhs, 0); + as_sra(b, rhs, 0); + as_sdivx(dest, a, b); + } + as_sra(dest, dest, 0); +} + +void MacroAssembler::flexibleRemainder32(Register lhs, Register rhs, + Register dest, bool isUnsigned, + const LiveRegisterSet&) { + UseScratchRegisterScope temps(asMasm()); + Register a = temps.Acquire(); + Register b = temps.Acquire(); + if (isUnsigned) { + as_srl(a, lhs, 0); + as_srl(b, rhs, 0); + as_udivx(dest, a, b); + } else { + as_sra(a, lhs, 0); + as_sra(b, rhs, 0); + as_sdivx(dest, a, b); + } + as_mulx(dest, dest, b); + as_sub(dest, a, dest); + as_sra(dest, dest, 0); +} + +void MacroAssembler::flexibleQuotientPtr(Register lhs, Register rhs, + Register dest, bool isUnsigned, + const LiveRegisterSet&) { + Label done; + if (!isUnsigned) { + // sdivx(INT64_MIN, -1) overflows; match the ARM64/LoongArch64 result. + Label notMinOverflow; + branchPtr(Assembler::NotEqual, lhs, ImmWord(INT64_MIN), ¬MinOverflow); + branchPtr(Assembler::NotEqual, rhs, ImmWord(-1), ¬MinOverflow); + movePtr(ImmWord(INT64_MIN), dest); + jump(&done); + bind(¬MinOverflow); + } + if (isUnsigned) { + as_udivx(dest, lhs, rhs); + } else { + as_sdivx(dest, lhs, rhs); + } + bind(&done); +} + +void MacroAssembler::flexibleRemainderPtr(Register lhs, Register rhs, + Register dest, bool isUnsigned, + const LiveRegisterSet&) { + Label done; + if (!isUnsigned) { + Label notMinOverflow; + branchPtr(Assembler::NotEqual, lhs, ImmWord(INT64_MIN), ¬MinOverflow); + branchPtr(Assembler::NotEqual, rhs, ImmWord(-1), ¬MinOverflow); + movePtr(ImmWord(0), dest); + jump(&done); + bind(¬MinOverflow); + } + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + if (isUnsigned) { + as_udivx(scratch, lhs, rhs); + } else { + as_sdivx(scratch, lhs, rhs); + } + as_mulx(scratch, scratch, rhs); + as_sub(dest, lhs, scratch); + 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); +} + +// =============================================================== +// Integer <-> FP conversion. + +void MacroAssembler::convertInt64ToDouble(Register64 src, FloatRegister dest) { + moveGPRToFPR64(src.reg, dest); + as_fpop1(OPF_FXTOD, dest, dest); +} + +void MacroAssembler::convertInt64ToFloat32(Register64 src, FloatRegister dest) { + moveGPRToFPR64(src.reg, ScratchDoubleReg); + as_fpop1(OPF_FXTOS, dest, ScratchDoubleReg); +} + +bool MacroAssembler::convertUInt64ToDoubleNeedsTemp() { return false; } + +// SPARC has no unsigned integer -> FP conversion. Values below 2^63 go through +// the signed path; above that, halve with round-to-odd (which keeps the result +// correctly rounded and correctly re-roundable to single) and double back. +static void EmitConvertU64ToDouble(MacroAssembler& masm, Register64 src, + FloatRegister dest) { + Label bigVal, done; + UseScratchRegisterScope temps(masm); + Register t = temps.Acquire(); + Register lo = temps.Acquire(); + + masm.ma_cmp64(src.reg, int64_t(0)); + masm.ma_b(Assembler::LessThan, &bigVal); + masm.moveGPRToFPR64(src.reg, dest); + masm.as_fpop1(OPF_FXTOD, dest, dest); + masm.jump(&done); + + masm.bind(&bigVal); + masm.as_srlx(t, src.reg, 1); + masm.as_and(lo, src.reg, 1); + masm.as_or(t, t, lo); + masm.moveGPRToFPR64(t, dest); + masm.as_fpop1(OPF_FXTOD, dest, dest); + masm.as_fpop1(OPF_FADDd, dest, dest, dest); + masm.bind(&done); +} + +void MacroAssembler::convertUInt64ToDouble(Register64 src, FloatRegister dest, + Register temp) { + MOZ_ASSERT(temp == Register::Invalid()); + EmitConvertU64ToDouble(*this, src, dest); +} + +void MacroAssembler::convertUInt64ToFloat32(Register64 src, FloatRegister dest, + Register temp) { + MOZ_ASSERT(temp == Register::Invalid()); + EmitConvertU64ToDouble(*this, src, ScratchDoubleReg); + as_fpop1(OPF_FDTOS, dest, ScratchDoubleReg); +} + +void MacroAssembler::convertIntPtrToDouble(Register src, FloatRegister dest) { + convertInt64ToDouble(Register64(src), dest); +} + +void MacroAssembler::copySignDouble(FloatRegister lhs, FloatRegister rhs, + FloatRegister output) { + emitCopySign(output, rhs, lhs); +} + +void MacroAssembler::copySignFloat32(FloatRegister lhs, FloatRegister rhs, + FloatRegister output) { + emitCopySign(output, rhs, lhs); +} + +// =============================================================== +// Rounding. + +void MacroAssembler::nearbyIntDouble(RoundingMode mode, FloatRegister src, + FloatRegister dest) { + MOZ_ASSERT(src != ScratchDoubleReg && dest != ScratchDoubleReg); + Label done; + moveDouble(src, ScratchDoubleReg); + if (src != dest) { + moveDouble(src, dest); + } + { + // A biased exponent >= 1075 means |val| >= 2^52, so the value is already + // integral; this also catches Inf and NaN. + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + moveFPR64ToGPR(ScratchDoubleReg, scratch); + as_srlx(scratch, scratch, 52); + as_and(scratch, scratch, 0x7ff); + ma_cmp64(scratch, int64_t(1075)); + ma_b(Assembler::GreaterThanOrEqual, &done); + } + + switch (mode) { + case RoundingMode::TowardsZero: + as_fpop1(OPF_FDTOX, dest, ScratchDoubleReg); + break; + case RoundingMode::Up: + roundToInt64FPR(dest, ScratchDoubleReg, RoundUp); + break; + case RoundingMode::Down: + roundToInt64FPR(dest, ScratchDoubleReg, RoundDown); + break; + case RoundingMode::NearestTiesToEven: { + // floor, then inspect the fraction. Its bit pattern orders like its + // value because it is non-negative, so the comparison against 0.5 is + // done on the exponent/mantissa fields directly. + roundToInt64FPR(dest, ScratchDoubleReg, RoundDown); + UseScratchRegisterScope temps(asMasm()); + Register n = temps.Acquire(); + Register bits = temps.Acquire(); + Label roundUp, keep; + moveFPR64ToGPR(dest, n); + as_fpop1(OPF_FXTOD, dest, dest); + as_fpop1(OPF_FSUBd, dest, ScratchDoubleReg, dest); + moveFPR64ToGPR(dest, bits); + // The fraction is in [0, 1), so an exponent below 0x3fe means < 0.5 and + // 0x3fe with a zero mantissa is exactly 0.5. + as_srlx(g3, bits, 52); + ma_cmp64(g3, int64_t(0x3fe)); + ma_b(Assembler::Below, &keep); + as_sllx(bits, bits, 12); + ma_cmp64(bits, int64_t(0)); + ma_b(Assembler::NotEqual, &roundUp); + as_and(bits, n, 1); + ma_cmp64(bits, int64_t(0)); + ma_b(Assembler::Equal, &keep); + bind(&roundUp); + as_add(n, n, 1); + bind(&keep); + moveGPRToFPR64(n, dest); + break; + } + default: + MOZ_CRASH("Unexpected rounding mode"); + } + as_fpop1(OPF_FXTOD, dest, dest); + emitCopySign(dest, ScratchDoubleReg, dest); + bind(&done); +} + +void MacroAssembler::nearbyIntFloat32(RoundingMode mode, FloatRegister src, + FloatRegister dest) { + // A float32 widened to double is exact, and every integral result of the + // rounding is exactly representable back in float32. + FloatRegister wide = dest.asDouble(); + MOZ_ASSERT(wide != ScratchDoubleReg); + as_fpop1(OPF_FSTOD, wide, src); + nearbyIntDouble(mode, wide, wide); + as_fpop1(OPF_FDTOS, dest, wide); +} + +void MacroAssembler::floorDoubleToInt32(FloatRegister src, Register dest, + Label* fail) { + ScratchDoubleScope fpscratch(asMasm()); + roundToInt64FPR(fpscratch, src, RoundDown); + moveFPR64ToGPR(fpscratch, dest); + + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(NotEqual, fail); + } + + Label notZero; + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, ¬Zero); + { + // Only +0.0 may reach here with a zero result; -0.0 and NaN must fail. + moveFPR64ToGPR(src, dest); + as_srlx(dest, dest, 62); + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, fail); + } + bind(¬Zero); +} + +void MacroAssembler::floorFloat32ToInt32(FloatRegister src, Register dest, + Label* fail) { + roundFloat32ToInt64GPR(dest, src, RoundDown); + + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(NotEqual, fail); + } + + Label notZero; + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, ¬Zero); + { + moveFPR64ToGPR(src, dest); + as_srlx(dest, dest, 62); + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, fail); + } + bind(¬Zero); +} + +void MacroAssembler::ceilDoubleToInt32(FloatRegister src, Register dest, + Label* fail) { + ScratchDoubleScope fpscratch(asMasm()); + roundToInt64FPR(fpscratch, src, RoundUp); + moveFPR64ToGPR(fpscratch, dest); + + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(NotEqual, fail); + } + + Label notZero; + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, ¬Zero); + { + moveFPR64ToGPR(src, dest); + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, fail); + } + bind(¬Zero); +} + +void MacroAssembler::ceilFloat32ToInt32(FloatRegister src, Register dest, + Label* fail) { + roundFloat32ToInt64GPR(dest, src, RoundUp); + + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(NotEqual, fail); + } + + Label notZero; + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, ¬Zero); + { + moveFPR64ToGPR(src, dest); + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, fail); + } + bind(¬Zero); +} + +void MacroAssembler::truncDoubleToInt32(FloatRegister src, Register dest, + Label* fail) { + ScratchDoubleScope fpscratch(asMasm()); + as_fpop1(OPF_FDTOX, fpscratch, src); + moveFPR64ToGPR(fpscratch, dest); + + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(NotEqual, fail); + } + + Label notZero; + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, ¬Zero); + { + moveFPR64ToGPR(src, dest); + as_srlx(dest, dest, 62); + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, fail); + } + bind(¬Zero); +} + +void MacroAssembler::truncFloat32ToInt32(FloatRegister src, Register dest, + Label* fail) { + ScratchDoubleScope fpscratch(asMasm()); + as_fpop1(OPF_FSTOX, fpscratch, src); + moveFPR64ToGPR(fpscratch, dest); + + { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(NotEqual, fail); + } + + Label notZero; + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, ¬Zero); + { + moveFPR64ToGPR(src, dest); + as_srlx(dest, dest, 62); + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, fail); + } + bind(¬Zero); +} + +void MacroAssembler::roundDoubleToInt32(FloatRegister src, Register dest, + FloatRegister temp, Label* fail) { + ScratchDoubleScope fpscratch(asMasm()); + Label performRound, notZero; + + zeroDouble(fpscratch); + branchDouble(DoubleGreaterThanOrEqual, src, fpscratch, &performRound); + + loadConstantDouble(-0.5, fpscratch); + branchDouble(DoubleGreaterThanOrEqual, src, fpscratch, fail); + + bind(&performRound); + { + // roundToInt64FPR reads its source after writing its destination, so the + // biased value has to live somewhere other than the destination. + loadConstantDouble(GetBiggestNumberLessThan(0.5), fpscratch); + as_fpop1(OPF_FADDd, temp, src, fpscratch); + roundToInt64FPR(fpscratch, temp, RoundDown); + moveFPR64ToGPR(fpscratch, dest); + + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(NotEqual, fail); + } + + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, ¬Zero); + { + moveFPR64ToGPR(src, dest); + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, fail); + } + bind(¬Zero); +} + +void MacroAssembler::roundFloat32ToInt32(FloatRegister src, Register dest, + FloatRegister temp, Label* fail) { + ScratchDoubleScope fpscratch(asMasm()); + Label performRound, notZero; + + loadConstantFloat32(0.0f, fpscratch); + branchFloat(DoubleGreaterThanOrEqual, src, fpscratch, &performRound); + + loadConstantFloat32(-0.5f, fpscratch); + branchFloat(DoubleGreaterThanOrEqual, src, fpscratch, fail); + + bind(&performRound); + { + // roundToInt64FPR reads its source after writing its destination, so the + // biased value has to live somewhere other than the destination. + loadConstantFloat32(float(GetBiggestNumberLessThan(0.5)), fpscratch); + as_fpop1(OPF_FADDs, temp, src, fpscratch); + as_fpop1(OPF_FSTOD, temp.asDouble(), temp); + roundToInt64FPR(fpscratch, temp.asDouble(), RoundDown); + moveFPR64ToGPR(fpscratch, dest); + + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + as_sra(scratch, dest, 0); + ma_cmp64(dest, scratch); + ma_b(NotEqual, fail); + } + + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, ¬Zero); + { + moveFPR64ToGPR(src, dest); + ma_cmp64(dest, int64_t(0)); + ma_b(NotEqual, fail); + } + bind(¬Zero); +} + +// =============================================================== +// GC / nursery helpers. + +void MacroAssembler::loadStoreBuffer(Register ptr, Register buffer) { + andPtr(Imm32(int32_t(~gc::ChunkMask)), ptr, buffer); + loadPtr(Address(buffer, gc::ChunkStoreBufferOffset), buffer); +} + +// =============================================================== +// Template instantiations. + +template +void MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, + MIRType valueType, const T& dest) { + MOZ_ASSERT(valueType < MIRType::Value); + + if (valueType == MIRType::Double) { + boxDouble(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); + +// =============================================================== +// Far jumps and patching. + +CodeOffset MacroAssembler::farJumpWithPatch() { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + + CodeOffset loadOffset(currentOffset()); + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + emitLoad64Stanza(scratch, 0); + as_jmpl(g0, scratch, 0); + m_buffer.leaveNoPool(); + + return loadOffset; +} + +void MacroAssembler::patchFarJump(CodeOffset farJump, uint32_t targetOffset) { + Instruction* inst = + (Instruction*)m_buffer.getInst(BufferOffset(farJump.offset())); + // The stanza always begins with `sethi %hi(...), rd`. + Register reg = Register::FromCode(inst[0].extractRd()); + WriteLoad64Instructions(inst, reg, LabelBase::INVALID_OFFSET); + addLongJump(BufferOffset(farJump.offset()), BufferOffset(targetOffset)); +} + +// static +void MacroAssembler::patchFarJump(uint8_t* farJump, uint8_t* target) { + UpdateLoad64Value((Instruction*)farJump, (uint64_t)(uintptr_t)target); + FlushICache(farJump, Assembler::Load64InstructionCount * sizeof(Instruction)); +} + +CodeOffset MacroAssembler::callWithPatch() { + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + for (size_t i = 0; i < kNoPoolPatchableBranchInsns; i++) { + writeInst(NopInst); + } + m_buffer.leaveNoPool(); + return CodeOffset(currentOffset()); +} + +void MacroAssembler::patchCall(uint32_t callerOffset, uint32_t calleeOffset) { + // callerOffset points just past the 8-word stanza; the call goes at inst[6]. + uint32_t stanzaStart = + callerOffset - kNoPoolPatchableBranchInsns * sizeof(uint32_t); + Instruction* i0 = (Instruction*)(m_buffer.getInst(BufferOffset(stanzaStart))); + intptr_t callAddr = + (intptr_t)stanzaStart + + intptr_t(kNoPoolLoad64StanzaInsns) * (intptr_t)sizeof(uint32_t); + intptr_t callOffset = (intptr_t)calleeOffset - callAddr; + if (JOffImm30::IsInRange(callOffset)) { + for (size_t i = 0; i < kNoPoolLoad64StanzaInsns; i++) { + i0[i].makeNop(); + } + i0[kNoPoolLoad64StanzaInsns].setData(EncodeCall(int32_t(callOffset))); + i0[kNoPoolLoad64StanzaInsns + 1].makeNop(); + } else { + addLongJump(BufferOffset(stanzaStart), BufferOffset(calleeOffset)); + WriteLoad64Instructions(i0, SecondScratchReg, LabelBase::INVALID_OFFSET); + i0[kNoPoolLoad64StanzaInsns].setData( + EncodeJmpl(LinkRegister, SecondScratchReg, 0)); + i0[kNoPoolLoad64StanzaInsns + 1].makeNop(); + } +} + +// static +void MacroAssembler::patchNopToCall(uint8_t* callsite, uint8_t* target) { + Instruction* inst = (Instruction*)callsite - kNoPoolPatchableBranchInsns; + WriteLoad64Instructions(inst, SecondScratchReg, (uint64_t)(uintptr_t)target); + inst[kNoPoolLoad64StanzaInsns].setData( + EncodeJmpl(LinkRegister, SecondScratchReg, 0)); + inst[kNoPoolLoad64StanzaInsns + 1].makeNop(); + FlushICache(inst, kNoPoolPatchableBranchInsns * sizeof(Instruction)); +} + +// static +void MacroAssembler::patchCallToNop(uint8_t* callsite) { + Instruction* inst = (Instruction*)callsite - kNoPoolPatchableBranchInsns; + for (size_t i = 0; i < kNoPoolPatchableBranchInsns; i++) { + inst[i].makeNop(); + } + FlushICache(inst, kNoPoolPatchableBranchInsns * sizeof(Instruction)); +} + +CodeOffset MacroAssembler::move32WithPatch(Register dest) { + CodeOffset offset(currentOffset()); + emitLoad64Stanza(dest, 0); + return offset; +} + +void MacroAssembler::patchMove32(CodeOffset offset, Imm32 n) { + Instruction* inst = + (Instruction*)m_buffer.getInst(BufferOffset(offset.offset())); + UpdateLoad64Value(inst, uint64_t(int64_t(n.value))); +} + +CodeOffset MacroAssembler::sub32FromMemAndBranchIfNegativeWithPatch( + Address address, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != address.base); + load32(address, scratch); + // Placeholder immediate; patchSub32FromMemAndBranchIfNegative rewrites the + // simm13 field of this add to the negated decrement. + as_add(scratch, scratch, 128); + CodeOffset patchPoint = CodeOffset(currentOffset()); + store32(scratch, address); + ma_cmp32(scratch, int64_t(0)); + ma_b(LessThan, label); + return patchPoint; +} + +void MacroAssembler::patchSub32FromMemAndBranchIfNegative(CodeOffset offset, + Imm32 imm) { + int32_t val = imm.value; + MOZ_RELEASE_ASSERT(val >= 1 && val <= 127); + InstImm* inst = + (InstImm*)m_buffer.getInst(BufferOffset(offset.offset() - 4)); + inst->setSimm13(-val); +} + +uint32_t MacroAssembler::pushFakeReturnAddress(Register scratch) { + CodeLabel cl; + + // Must be the full patchable stanza: processCodeLabels rewrites it in place. + mov(&cl, scratch); + Push(scratch); + + bind(&cl); + uint32_t retAddr = currentOffset(); + + addCodeLabel(cl); + return retAddr; +} + +// =============================================================== +void MacroAssembler::flush() { Assembler::flush(); } + +// Wasm support. + +FaultingCodeOffset MacroAssembler::wasmTrapInstruction() { + m_buffer.flushPool(); + FaultingCodeOffset fco = FaultingCodeOffset(currentOffset()); + as_ta(0x10); + return fco; +} + +// `or %g3, 0, %g3` — architecturally harmless and never emitted by ordinary +// code generation. +static const int32_t SlowCallMarker = + int32_t(FMT3A | (uint32_t(Registers::g3) << RdShift) | + (uint32_t(OP3_OR) << Op3Shift) | + (uint32_t(Registers::g3) << Rs1Shift) | (1u << ImmBitShift)); + +void MacroAssembler::wasmMarkCallAsSlow() { as_or(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))); +} + +void MacroAssembler::PopStackPtr() { + // push(StackPointer) stores the logical (unbiased) pointer, so restoring it + // has to convert back rather than load %sp raw. + loadStackPtr(Address(StackPointer, 0)); + adjustFrame(-int32_t(sizeof(intptr_t))); +} + +// 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()) { + storeDouble(reg.asDouble(), dest); + } + } + MOZ_ASSERT(diffF == 0); +} + +void MacroAssembler::freeStackTo(uint32_t framePushed) { + MOZ_ASSERT(framePushed <= framePushed_); + // FramePointer is unbiased, so convert it back into the raw %sp encoding. + ma_sub(StackPointer, FramePointer, int64_t(SP_BASE) + int64_t(framePushed)); + framePushed_ = framePushed; +} + +// =============================================================== +// Misc. + +void MacroAssembler::comment(const char* msg) {} + +void MacroAssembler::speculationBarrier() { + // The assembler exposes no membar encoder; a Spectre-v1 barrier would be + // `membar #Sync` here. Reachable only under + // JitOptions.spectreJitToCxxCalls, which we leave off on sparc64. + as_nop(); +} + +void MacroAssembler::atomicPause() { as_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) { + ma_cmp64(index, boundsCheckLimit); + ma_b(cond, label); +} + +void MacroAssembler::wasmBoundsCheck32(Condition cond, Register index, + Address boundsCheckLimit, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + load32(boundsCheckLimit, scratch); + ma_cmp64(index, scratch); + ma_b(cond, label); +} + +void MacroAssembler::wasmBoundsCheck64(Condition cond, Register64 index, + Register64 boundsCheckLimit, + Label* label) { + ma_cmp64(index.reg, boundsCheckLimit.reg); + ma_b(cond, label); +} + +void MacroAssembler::wasmBoundsCheck64(Condition cond, Register64 index, + Address boundsCheckLimit, Label* label) { + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + loadPtr(boundsCheckLimit, scratch); + ma_cmp64(index.reg, scratch); + ma_b(cond, label); +} + +// =============================================================== +// Wasm truncation. + +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 MacroAssemblerSPARC64Compat::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) { + 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 { + Label notNaN; + 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::DoubleGreaterThanOrEqual, input, + fpscratch, rejoin); + } else { + asMasm().branchFloat(Assembler::DoubleGreaterThanOrEqual, input, + fpscratch, rejoin); + } + 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 MacroAssemblerSPARC64Compat::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()); + Register output = output_.reg; + + 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().movePtr(ImmWord(0), output); + asMasm().jump(rejoin); + asMasm().bind(¬NegOrNaN); + asMasm().movePtr(ImmWord(UINT64_MAX), output); + } else { + Label notNaN; + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleOrdered, input, input, ¬NaN); + } else { + asMasm().branchFloat(Assembler::DoubleOrdered, input, input, ¬NaN); + } + asMasm().movePtr(ImmWord(0), output); + asMasm().jump(rejoin); + + asMasm().bind(¬NaN); + if (fromType == MIRType::Double) { + asMasm().branchDouble(Assembler::DoubleGreaterThanOrEqual, input, + fpscratch, rejoin); + } else { + asMasm().branchFloat(Assembler::DoubleGreaterThanOrEqual, input, + fpscratch, rejoin); + } + asMasm().movePtr(ImmWord(INT64_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); +} + +// The convert-to-integer instructions saturate out-of-range inputs and produce +// a defined value for NaN, so every wasm truncation is a convert followed by a +// range test rather than an FSR exception probe. + +void MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, + Register output, + bool isSaturating, + Label* oolEntry) { + ScratchDoubleScope fpscratch(asMasm()); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + ma_fcmp(input, input); + ma_b(DoubleUnordered, oolEntry); + as_fpop1(OPF_FDTOX, fpscratch, input); + moveFPR64ToGPR(fpscratch, output); + as_sra(scratch, output, 0); + ma_cmp64(output, scratch); + ma_b(NotEqual, oolEntry); +} + +void MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, + Register output, + bool isSaturating, + Label* oolEntry) { + ScratchDoubleScope fpscratch(asMasm()); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + ma_fcmp(input, input); + ma_b(DoubleUnordered, oolEntry); + as_fpop1(OPF_FDTOX, fpscratch, input); + moveFPR64ToGPR(fpscratch, output); + as_srlx(scratch, output, 32); + ma_cmp64(scratch, int64_t(0)); + ma_b(NotEqual, oolEntry); + as_sra(output, output, 0); +} + +void MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, + Register output, + bool isSaturating, + Label* oolEntry) { + ScratchDoubleScope fpscratch(asMasm()); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + ma_fcmps(input, input); + ma_b(DoubleUnordered, oolEntry); + as_fpop1(OPF_FSTOX, fpscratch, input); + moveFPR64ToGPR(fpscratch, output); + as_sra(scratch, output, 0); + ma_cmp64(output, scratch); + ma_b(NotEqual, oolEntry); +} + +void MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, + Register output, + bool isSaturating, + Label* oolEntry) { + ScratchDoubleScope fpscratch(asMasm()); + UseScratchRegisterScope temps(asMasm()); + Register scratch = temps.Acquire(); + ma_fcmps(input, input); + ma_b(DoubleUnordered, oolEntry); + as_fpop1(OPF_FSTOX, fpscratch, input); + moveFPR64ToGPR(fpscratch, output); + as_srlx(scratch, output, 32); + ma_cmp64(scratch, int64_t(0)); + ma_b(NotEqual, oolEntry); + as_sra(output, output, 0); +} + +// The conversion truncates and saturates, so range-check the input against +// +/-2^63 up front rather than trying to recognise a saturated result. +static void EmitTruncateToInt64(MacroAssembler& masm, FloatRegister input, + Register64 output, bool isFloat32, + Label* oolEntry) { + ScratchDoubleScope fpscratch(masm); + + auto compare = [&](FloatRegister rhs) { + if (isFloat32) { + masm.ma_fcmps(input, rhs); + } else { + masm.ma_fcmp(input, rhs); + } + }; + auto loadBound = [&](double v) { + if (isFloat32) { + masm.loadConstantFloat32(float(v), fpscratch); + } else { + masm.loadConstantDouble(v, fpscratch); + } + }; + + compare(input); + masm.ma_b(Assembler::DoubleUnordered, oolEntry); + loadBound(9223372036854775808.0); // 2^63 + compare(fpscratch); + masm.ma_b(Assembler::DoubleGreaterThanOrEqual, oolEntry); + loadBound(-9223372036854775808.0); // -2^63, exactly representable + compare(fpscratch); + masm.ma_b(Assembler::DoubleLessThan, oolEntry); + + masm.as_fpop1(isFloat32 ? OPF_FSTOX : OPF_FDTOX, fpscratch, input); + masm.moveFPR64ToGPR(fpscratch, output.reg); +} + +void MacroAssembler::wasmTruncateDoubleToInt64( + FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, + Label* oolRejoin, FloatRegister tempDouble) { + MOZ_ASSERT(tempDouble.isInvalid()); + EmitTruncateToInt64(*this, input, output, /* isFloat32 */ false, oolEntry); + if (isSaturating) { + bind(oolRejoin); + } +} + +void MacroAssembler::wasmTruncateFloat32ToInt64( + FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, + Label* oolRejoin, FloatRegister tempFloat) { + MOZ_ASSERT(tempFloat.isInvalid()); + EmitTruncateToInt64(*this, input, output, /* isFloat32 */ true, oolEntry); + if (isSaturating) { + bind(oolRejoin); + } +} + +// SPARC has no unsigned convert-to-integer. Values in [0, 2^63) go through the +// signed path; [2^63, 2^64) are biased down by 2^63, converted, and have bit +// 63 restored. +static void EmitTruncateToUInt64(MacroAssembler& masm, FloatRegister input, + Register64 output, bool isFloat32, + bool isSaturating, Label* oolEntry, + Label* oolRejoin) { + Label small, joined; + ScratchDoubleScope fpscratch(masm); + + auto compare = [&](FloatRegister rhs) { + if (isFloat32) { + masm.ma_fcmps(input, rhs); + } else { + masm.ma_fcmp(input, rhs); + } + }; + auto loadBound = [&](double v) { + if (isFloat32) { + masm.loadConstantFloat32(float(v), fpscratch); + } else { + masm.loadConstantDouble(v, fpscratch); + } + }; + + compare(input); + masm.ma_b(Assembler::DoubleUnordered, oolEntry); + + // wasm accepts (-1, 0) and truncates it to zero; anything at or below -1, or + // at or above 2^64, is out of range. + loadBound(-1.0); + compare(fpscratch); + masm.ma_b(Assembler::DoubleLessThanOrEqual, oolEntry); + loadBound(18446744073709551616.0); // 2^64 + compare(fpscratch); + masm.ma_b(Assembler::DoubleGreaterThanOrEqual, oolEntry); + + loadBound(9223372036854775808.0); // 2^63 + compare(fpscratch); + masm.ma_b(Assembler::DoubleLessThan, &small); + { + masm.as_fpop1(isFloat32 ? OPF_FSUBs : OPF_FSUBd, fpscratch, input, + fpscratch); + masm.as_fpop1(isFloat32 ? OPF_FSTOX : OPF_FDTOX, fpscratch, fpscratch); + masm.moveFPR64ToGPR(fpscratch, output.reg); + UseScratchRegisterScope temps(masm); + Register bit = temps.Acquire(); + masm.ma_li(bit, ImmWord(uint64_t(1) << 63)); + masm.as_or(output.reg, output.reg, bit); + masm.jump(&joined); + } + masm.bind(&small); + { + masm.as_fpop1(isFloat32 ? OPF_FSTOX : OPF_FDTOX, fpscratch, input); + masm.moveFPR64ToGPR(fpscratch, output.reg); + } + masm.bind(&joined); + if (isSaturating) { + masm.bind(oolRejoin); + } +} + +void MacroAssembler::wasmTruncateDoubleToUInt64( + FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, + Label* oolRejoin, FloatRegister tempDouble) { + MOZ_ASSERT(tempDouble.isInvalid()); + EmitTruncateToUInt64(*this, input, output, /* isFloat32 */ false, + isSaturating, oolEntry, oolRejoin); +} + +void MacroAssembler::wasmTruncateFloat32ToUInt64( + FloatRegister input, Register64 output, bool isSaturating, Label* oolEntry, + Label* oolRejoin, FloatRegister tempFloat) { + MOZ_ASSERT(tempFloat.isInvalid()); + EmitTruncateToUInt64(*this, input, output, /* isFloat32 */ true, isSaturating, + oolEntry, oolRejoin); +} + +// =============================================================== +// Profiler. + +void MacroAssemblerSPARC64Compat::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 MacroAssemblerSPARC64Compat::profilerExitFrame() { + jump(asMasm().runtime()->jitRuntime()->getProfilerExitFrameTail()); +} + +// =============================================================== +// Modulo by a mask. + +void MacroAssemblerSPARC64Compat::ma_mod_mask(Register src, Register dest, + Register hold, Register remain, + int32_t shift, Label* negZero) { + // 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) { + if (access) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); + } + + bool signExtend = Scalar::isSignedIntType(type); + unsigned nbytes = Scalar::byteSize(type); + + UseScratchRegisterScope temps(masm); + Register addr = temps.Acquire(); + Register work = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + + masm.memoryBarrierBefore(sync); + + if (nbytes >= 4) { + masm.movePtr(newval, output); + masm.as_casa(output, addr, oldval, ASI_PRIMARY); + masm.as_sra(output, output, 0); + masm.memoryBarrierAfter(sync); + return; + } + + Label again, end; + Register cur = valueTemp; + Register shift = offsetTemp; + Register mask = maskTemp; + SubWordSetup(masm, addr, nbytes, shift, mask); + + masm.bind(&again); + masm.as_lduw(cur, addr, 0); + masm.as_and(output, cur, mask); + masm.as_srl(output, output, shift); + NarrowSubWord(masm, output, nbytes, signExtend); + + masm.movePtr(oldval, work); + NarrowSubWord(masm, work, nbytes, signExtend); + masm.ma_cmp32(output, work); + masm.ma_b(Assembler::NotEqual, &end); + + // newWord = cur ^ ((cur ^ (newval << shift)) & mask) + masm.as_sll(work, newval, shift); + masm.as_xor(work, work, cur); + masm.as_and(work, work, mask); + masm.as_xor(work, work, cur); + masm.as_casa(work, addr, cur, ASI_PRIMARY); + masm.ma_cmp32(work, cur); + masm.ma_b(Assembler::NotEqual, &again); + + masm.bind(&end); + masm.memoryBarrierAfter(sync); +} + +template +static void CompareExchange64(MacroAssembler& masm, + const wasm::MemoryAccessDesc* access, + Synchronization sync, const T& mem, + Register64 expect, Register64 replace, + Register64 output) { + if (access) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); + } + MOZ_ASSERT(expect != output && replace != output); + + UseScratchRegisterScope temps(masm); + Register addr = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + + masm.memoryBarrierBefore(sync); + masm.movePtr(replace.reg, output.reg); + masm.as_casxa(output.reg, addr, expect.reg, ASI_PRIMARY); + masm.memoryBarrierAfter(sync); +} + +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) { + if (access) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); + } + + bool signExtend = Scalar::isSignedIntType(type); + unsigned nbytes = Scalar::byteSize(type); + + UseScratchRegisterScope temps(masm); + Register addr = temps.Acquire(); + Register work = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + + masm.memoryBarrierBefore(sync); + + Label again; + if (nbytes >= 4) { + masm.bind(&again); + masm.as_lduw(output, addr, 0); + masm.movePtr(value, work); + masm.as_casa(work, addr, output, ASI_PRIMARY); + masm.ma_cmp32(work, output); + masm.ma_b(Assembler::NotEqual, &again); + masm.as_sra(output, output, 0); + masm.memoryBarrierAfter(sync); + return; + } + + Register cur = valueTemp; + Register shift = offsetTemp; + Register mask = maskTemp; + SubWordSetup(masm, addr, nbytes, shift, mask); + + masm.bind(&again); + masm.as_lduw(cur, addr, 0); + masm.as_and(output, cur, mask); + masm.as_srl(output, output, shift); + NarrowSubWord(masm, output, nbytes, signExtend); + + masm.as_sll(work, value, shift); + masm.as_xor(work, work, cur); + masm.as_and(work, work, mask); + masm.as_xor(work, work, cur); + masm.as_casa(work, addr, cur, ASI_PRIMARY); + masm.ma_cmp32(work, cur); + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); +} + +template +static void AtomicExchange64(MacroAssembler& masm, + const wasm::MemoryAccessDesc* access, + Synchronization sync, const T& mem, + Register64 value, Register64 output) { + if (access) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); + } + MOZ_ASSERT(value != output); + + UseScratchRegisterScope temps(masm); + Register addr = temps.Acquire(); + Register work = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + + Label again; + masm.memoryBarrierBefore(sync); + masm.bind(&again); + masm.as_ldx(output.reg, addr, 0); + masm.movePtr(value.reg, work); + masm.as_casxa(work, addr, output.reg, ASI_PRIMARY); + masm.ma_cmp64(work, output.reg); + masm.ma_b(Assembler::NotEqual, &again); + masm.memoryBarrierAfter(sync); +} + +static void EmitAtomicOp(MacroAssembler& masm, AtomicOp op, Register rd, + Register lhs, Register rhs) { + switch (op) { + case AtomicOp::Add: + masm.as_add(rd, lhs, rhs); + break; + case AtomicOp::Sub: + masm.as_sub(rd, lhs, rhs); + break; + case AtomicOp::And: + masm.as_and(rd, lhs, rhs); + break; + case AtomicOp::Or: + masm.as_or(rd, lhs, rhs); + break; + case AtomicOp::Xor: + masm.as_xor(rd, lhs, rhs); + 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) { + if (access) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); + } + + bool signExtend = Scalar::isSignedIntType(type); + unsigned nbytes = Scalar::byteSize(type); + + UseScratchRegisterScope temps(masm); + Register addr = temps.Acquire(); + Register work = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + + masm.memoryBarrierBefore(sync); + + Label again; + if (nbytes >= 4) { + masm.bind(&again); + masm.as_lduw(output, addr, 0); + EmitAtomicOp(masm, op, work, output, value); + masm.as_casa(work, addr, output, ASI_PRIMARY); + masm.ma_cmp32(work, output); + masm.ma_b(Assembler::NotEqual, &again); + masm.as_sra(output, output, 0); + masm.memoryBarrierAfter(sync); + return; + } + + Register cur = valueTemp; + Register shift = offsetTemp; + Register mask = maskTemp; + SubWordSetup(masm, addr, nbytes, shift, mask); + + masm.bind(&again); + masm.as_lduw(cur, addr, 0); + masm.as_and(output, cur, mask); + masm.as_srl(output, output, shift); + NarrowSubWord(masm, output, nbytes, signExtend); + + EmitAtomicOp(masm, op, work, output, value); + masm.as_sll(work, work, shift); + masm.as_xor(work, work, cur); + masm.as_and(work, work, mask); + masm.as_xor(work, work, cur); + masm.as_casa(work, addr, cur, ASI_PRIMARY); + masm.ma_cmp32(work, cur); + masm.ma_b(Assembler::NotEqual, &again); + + masm.memoryBarrierAfter(sync); +} + +template +static void AtomicFetchOp64(MacroAssembler& masm, + const wasm::MemoryAccessDesc* access, + Synchronization sync, AtomicOp op, Register64 value, + const T& mem, Register64 temp, Register64 output) { + if (access) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); + } + MOZ_ASSERT(value != output); + MOZ_ASSERT(value != temp); + + UseScratchRegisterScope temps(masm); + Register addr = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + + Label again; + masm.memoryBarrierBefore(sync); + masm.bind(&again); + masm.as_ldx(output.reg, addr, 0); + EmitAtomicOp(masm, op, temp.reg, output.reg, value.reg); + masm.as_casxa(temp.reg, addr, output.reg, ASI_PRIMARY); + masm.ma_cmp64(temp.reg, output.reg); + 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) { + if (access) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); + } + + unsigned nbytes = Scalar::byteSize(type); + + UseScratchRegisterScope temps(masm); + Register addr = temps.Acquire(); + Register work = temps.Acquire(); + masm.computeEffectiveAddress(mem, addr); + + masm.memoryBarrierBefore(sync); + + Label again; + if (nbytes >= 4) { + // The retry needs both the loaded word and the new word live at once, one + // more than the scratch pool holds, so the new value is built in %g3 + // (neither allocatable nor part of the pool). + masm.bind(&again); + masm.as_lduw(work, addr, 0); + EmitAtomicOp(masm, op, g3, work, value); + masm.as_casa(g3, addr, work, ASI_PRIMARY); + masm.ma_cmp32(g3, work); + masm.ma_b(Assembler::NotEqual, &again); + masm.memoryBarrierAfter(sync); + return; + } + + Register cur = valueTemp; + Register shift = offsetTemp; + Register mask = maskTemp; + SubWordSetup(masm, addr, nbytes, shift, mask); + + masm.bind(&again); + masm.as_lduw(cur, addr, 0); + masm.as_and(work, cur, mask); + masm.as_srl(work, work, shift); + EmitAtomicOp(masm, op, work, work, value); + masm.as_sll(work, work, shift); + masm.as_xor(work, work, cur); + masm.as_and(work, work, mask); + masm.as_xor(work, work, cur); + masm.as_casa(work, addr, cur, ASI_PRIMARY); + masm.ma_cmp32(work, cur); + 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); +} + +void MacroAssembler::compareExchange64(Synchronization sync, const Address& mem, + Register64 expect, Register64 replace, + Register64 output) { + CompareExchange64(*this, nullptr, sync, mem, expect, replace, output); +} + +void MacroAssembler::compareExchange64(Synchronization sync, + const BaseIndex& mem, Register64 expect, + Register64 replace, Register64 output) { + CompareExchange64(*this, nullptr, sync, mem, expect, replace, output); +} + +void MacroAssembler::wasmCompareExchange(const wasm::MemoryAccessDesc& access, + const Address& mem, Register oldval, + Register newval, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmCompareExchange(const wasm::MemoryAccessDesc& access, + const BaseIndex& mem, Register oldval, + Register newval, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmCompareExchange64(const wasm::MemoryAccessDesc& access, + const Address& mem, + Register64 expect, + Register64 replace, + Register64 output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmCompareExchange64(const wasm::MemoryAccessDesc& access, + const BaseIndex& mem, + Register64 expect, + Register64 replace, + Register64 output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +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) { + AtomicExchange64(*this, nullptr, sync, mem, value, output); +} + +void MacroAssembler::atomicExchange64(Synchronization sync, + const BaseIndex& mem, Register64 value, + Register64 output) { + AtomicExchange64(*this, nullptr, sync, mem, value, output); +} + +void MacroAssembler::wasmAtomicExchange(const wasm::MemoryAccessDesc& access, + const Address& mem, Register value, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmAtomicExchange(const wasm::MemoryAccessDesc& access, + const BaseIndex& mem, Register value, + Register valueTemp, Register offsetTemp, + Register maskTemp, Register output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmAtomicExchange64(const wasm::MemoryAccessDesc& access, + const Address& mem, Register64 src, + Register64 output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmAtomicExchange64(const wasm::MemoryAccessDesc& access, + const BaseIndex& mem, Register64 src, + Register64 output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +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) { + AtomicFetchOp64(*this, nullptr, sync, op, value, mem, temp, output); +} + +void MacroAssembler::atomicFetchOp64(Synchronization sync, AtomicOp op, + Register64 value, const BaseIndex& mem, + Register64 temp, Register64 output) { + AtomicFetchOp64(*this, nullptr, sync, op, value, mem, temp, output); +} + +void MacroAssembler::atomicEffectOp64(Synchronization sync, AtomicOp op, + Register64 value, const Address& mem, + Register64 temp) { + AtomicFetchOp64(*this, nullptr, sync, op, value, mem, temp, temp); +} + +void MacroAssembler::atomicEffectOp64(Synchronization sync, AtomicOp op, + Register64 value, const BaseIndex& mem, + Register64 temp) { + AtomicFetchOp64(*this, nullptr, sync, op, value, mem, temp, temp); +} + +void MacroAssembler::wasmAtomicFetchOp(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register value, + const Address& mem, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmAtomicFetchOp(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register value, + const BaseIndex& mem, Register valueTemp, + Register offsetTemp, Register maskTemp, + Register output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmAtomicFetchOp64(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register64 value, + const Address& mem, Register64 temp, + Register64 output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmAtomicFetchOp64(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register64 value, + const BaseIndex& mem, Register64 temp, + Register64 output) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmAtomicEffectOp(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register value, + const Address& mem, Register valueTemp, + Register offsetTemp, + Register maskTemp) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +void MacroAssembler::wasmAtomicEffectOp(const wasm::MemoryAccessDesc& access, + AtomicOp op, Register value, + const BaseIndex& mem, + Register valueTemp, Register offsetTemp, + Register maskTemp) { + MOZ_CRASH("NYI: sparc64 wasm atomics"); +} + +// ======================================================================== +// 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 load/store. +// +// wasm linear memory is little-endian; this target is big-endian, so every +// multi-byte access is byte-reversed in a register. SPARC also traps on +// unaligned accesses, which the kernel fixes up; a wasm module that performs +// genuinely unaligned accesses is therefore correct but slow. + +void MacroAssemblerSPARC64Compat::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; + } + + asMasm().memoryBarrierBefore(access.sync()); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForLoad(Scalar::byteSize(access.type())), + FaultingCodeOffset(currentOffset())); + + switch (access.type()) { + case Scalar::Int8: + as_load(OP3_LDSB, output.gpr(), memoryBase, ptr); + break; + case Scalar::Uint8: + as_load(OP3_LDUB, output.gpr(), memoryBase, ptr); + break; + case Scalar::Int16: + as_load(OP3_LDUH, output.gpr(), memoryBase, ptr); + asMasm().byteSwap16SignExtend(output.gpr()); + break; + case Scalar::Uint16: + as_load(OP3_LDUH, output.gpr(), memoryBase, ptr); + asMasm().byteSwap16ZeroExtend(output.gpr()); + break; + case Scalar::Int32: + case Scalar::Uint32: + as_load(OP3_LDUW, output.gpr(), memoryBase, ptr); + asMasm().byteSwap32(output.gpr()); + break; + case Scalar::Float64: { + UseScratchRegisterScope temps(asMasm()); + Register tmp = temps.Acquire(); + as_load(OP3_LDX, tmp, memoryBase, ptr); + asMasm().byteSwap64(Register64(tmp)); + moveGPRToFPR64(tmp, output.fpu()); + break; + } + case Scalar::Float32: { + UseScratchRegisterScope temps(asMasm()); + Register tmp = temps.Acquire(); + as_load(OP3_LDUW, tmp, memoryBase, ptr); + asMasm().byteSwap32(tmp); + moveGPRToFPR32(tmp, output.fpu()); + break; + } + case Scalar::Simd128: + MOZ_CRASH("SIMD unsupported on sparc64"); + default: + MOZ_CRASH("unexpected array type"); + } + + asMasm().memoryBarrierAfter(access.sync()); +} + +void MacroAssemblerSPARC64Compat::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; + } + + asMasm().memoryBarrierBefore(access.sync()); + + // The byte reversal happens before the faulting store, so the trap site is + // recorded just before the memory instruction in every case. + UseScratchRegisterScope temps(asMasm()); + Register tmp = temps.Acquire(); + + switch (access.type()) { + case Scalar::Int8: + case Scalar::Uint8: + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(1), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STB, value.gpr(), memoryBase, ptr); + break; + case Scalar::Int16: + case Scalar::Uint16: + movePtr(value.gpr(), tmp); + asMasm().byteSwap16ZeroExtend(tmp); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(2), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STH, tmp, memoryBase, ptr); + break; + case Scalar::Int32: + case Scalar::Uint32: + movePtr(value.gpr(), tmp); + asMasm().byteSwap32(tmp); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(4), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STW, tmp, memoryBase, ptr); + break; + case Scalar::Int64: + movePtr(value.gpr(), tmp); + asMasm().byteSwap64(Register64(tmp)); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(8), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STX, tmp, memoryBase, ptr); + break; + case Scalar::Float64: + moveFPR64ToGPR(value.fpu(), tmp); + asMasm().byteSwap64(Register64(tmp)); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(8), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STX, tmp, memoryBase, ptr); + break; + case Scalar::Float32: + moveFPR32ToGPR(value.fpu(), tmp); + asMasm().byteSwap32(tmp); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(4), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STW, tmp, memoryBase, ptr); + break; + case Scalar::Simd128: + MOZ_CRASH("SIMD unsupported on sparc64"); + default: + MOZ_CRASH("unexpected array type"); + } + + asMasm().memoryBarrierAfter(access.sync()); +} + +void MacroAssemblerSPARC64Compat::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; + } + + asMasm().memoryBarrierBefore(access.sync()); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForLoad(Scalar::byteSize(access.type())), + FaultingCodeOffset(currentOffset())); + + switch (access.type()) { + case Scalar::Int8: + as_load(OP3_LDSB, output.reg, memoryBase, ptr); + break; + case Scalar::Uint8: + as_load(OP3_LDUB, output.reg, memoryBase, ptr); + break; + case Scalar::Int16: + as_load(OP3_LDUH, output.reg, memoryBase, ptr); + asMasm().byteSwap16SignExtend(output.reg); + break; + case Scalar::Uint16: + as_load(OP3_LDUH, output.reg, memoryBase, ptr); + asMasm().byteSwap16ZeroExtend(output.reg); + break; + case Scalar::Int32: + as_load(OP3_LDUW, output.reg, memoryBase, ptr); + asMasm().byteSwap32(output.reg); + as_sra(output.reg, output.reg, 0); + break; + case Scalar::Uint32: + as_load(OP3_LDUW, output.reg, memoryBase, ptr); + asMasm().byteSwap32(output.reg); + as_srl(output.reg, output.reg, 0); + break; + case Scalar::Int64: + as_load(OP3_LDX, output.reg, memoryBase, ptr); + asMasm().byteSwap64(output); + break; + default: + MOZ_CRASH("unexpected array type"); + } + + asMasm().memoryBarrierAfter(access.sync()); +} + +void MacroAssemblerSPARC64Compat::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; + } + + asMasm().memoryBarrierBefore(access.sync()); + + UseScratchRegisterScope temps(asMasm()); + Register tmp = temps.Acquire(); + movePtr(value.reg, tmp); + + switch (access.type()) { + case Scalar::Int8: + case Scalar::Uint8: + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(1), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STB, tmp, memoryBase, ptr); + break; + case Scalar::Int16: + case Scalar::Uint16: + asMasm().byteSwap16ZeroExtend(tmp); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(2), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STH, tmp, memoryBase, ptr); + break; + case Scalar::Int32: + case Scalar::Uint32: + asMasm().byteSwap32(tmp); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(4), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STW, tmp, memoryBase, ptr); + break; + case Scalar::Int64: + asMasm().byteSwap64(Register64(tmp)); + m_buffer.flushPool(); + append(access, wasm::TrapMachineInsnForStore(8), + FaultingCodeOffset(currentOffset())); + as_load(OP3_STX, tmp, memoryBase, ptr); + 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 -Naurp empty/js/src/jit/sparc64/MacroAssembler-sparc64.h firefox-153.0/js/src/jit/sparc64/MacroAssembler-sparc64.h --- firefox-153.0/js/src/jit/sparc64/MacroAssembler-sparc64.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/MacroAssembler-sparc64.h 2026-07-30 00:27:37.531867091 +0200 @@ -0,0 +1,1486 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_MacroAssembler_sparc64_h +#define jit_sparc64_MacroAssembler_sparc64_h + +#include + +#include "jit/MoveResolver.h" +#include "jit/sparc64/Assembler-sparc64.h" +#include "wasm/WasmBuiltins.h" + +namespace js { +namespace jit { + +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); +} + +// enterNoPool() guard sizes. Every patchable stanza has a fixed instruction +// count so the in-place patchers can find their slots. +// +// kNoPoolLoad64StanzaInsns (6): the sethi/or/sllx/sethi/or/or sequence +// WriteLoad64Instructions rewrites in place. Equals +// Assembler::Load64InstructionCount. +// +// kNoPoolPatchableBranchInsns (8): patchable far call / jump. Three shapes +// share the budget: +// - load64 stanza (6) + jmpl + delay nop = 8 (bound call/jump) +// - 6 nops + call + delay nop = 8 (short bound call) +// - ta(TAG) + chain word + 6 nops = 8 (forward reference) +// +// kNoPoolCondLongBranchInsns (10): BPcc + delay nop over an 8-slot stanza. +static constexpr size_t kNoPoolLoad64StanzaInsns = 6; +static constexpr size_t kNoPoolPatchableBranchInsns = 8; +static constexpr size_t kNoPoolCondLongBranchInsns = 10; + +// Displacement a conditional branch uses to skip the 8-slot stanza that +// follows its own delay slot. +static constexpr int32_t kCondLongBranchSkip = 40; + +// Indirect-call target register. %g3 is the third scratch: non-allocatable, +// volatile, and never live across a call. +static constexpr Register CallReg = g3; + +struct ImmShiftedTag : public ImmWord { + explicit ImmShiftedTag(JSValueShiftedTag shtag) : ImmWord((uintptr_t)shtag) {} + explicit ImmShiftedTag(JSValueType type) + : ImmWord(((uintptr_t)JSVAL_TYPE_TO_SHIFTED_TAG(type))) {} +}; + +struct ImmTag : public Imm32 { + explicit ImmTag(JSValueTag tag) : Imm32(tag) {} +}; + +class ScratchTagScope { + UseScratchRegisterScope temps_; + Register scratch_; + bool owned_; + mozilla::DebugOnly released_; + + public: + ScratchTagScope(Assembler& masm, const ValueOperand&) + : temps_(masm), owned_(true), released_(false) { + scratch_ = temps_.Acquire(); + } + + operator Register() { + MOZ_ASSERT(!released_); + return scratch_; + } + + void release() { + MOZ_ASSERT(!released_); + released_ = true; + if (owned_) { + temps_.Release(scratch_); + owned_ = false; + } + } + + void reacquire() { + MOZ_ASSERT(released_); + released_ = false; + if (!owned_) { + scratch_ = temps_.Acquire(); + owned_ = true; + } + } +}; + +class ScratchTagScopeRelease { + ScratchTagScope* ts_; + + public: + explicit ScratchTagScopeRelease(ScratchTagScope* ts) : ts_(ts) { + ts_->release(); + } + ~ScratchTagScopeRelease() { ts_->reacquire(); } +}; + +class MacroAssemblerSPARC64 : public Assembler { + protected: + MacroAssembler& asMasm(); + const MacroAssembler& asMasm() const; + + // SPARC puts the width selection in the *branch*, not the compare: `subcc` + // writes both %icc (low 32 bits) and %xcc (all 64). Every helper that emits + // a flag-setting instruction records which bank its consumer must read, and + // ma_b / ma_cmp_set / ma_cmp_move branch on it. This is the exact analogue + // of PPC's implicit cr0 and carries the same "no unrelated compare in + // between" rule. + ConditionCode lastCC_ = CC_XCC; + + public: + void ma_setCC(ConditionCode cc) { lastCC_ = cc; } + ConditionCode ma_currentCC() const { return lastCC_; } + + // =============================================================== + // Stack bias. See SPARC64-JIT-DESIGN.md sections 2.1 and 2.2. + // + // %o6 holds (logical stack pointer - SP_BASE). SP_BASE folds the 2047-byte + // SPARC bias together with the 176-byte frame the kernel may asynchronously + // spill our register window into, so the JIT's own data always starts above + // it and ShadowStackSpace is zero. %i6 (FramePointer) is deliberately *not* + // biased: SpiderMonkey writes it into JIT frames and C++ dereferences those + // values directly. + // + // These are the only sanctioned way to form a %sp-relative offset; + // hand-written as_ldx(..., StackPointer, off) is a bug. + + static constexpr int32_t SP_BASE = int32_t(STACK_BIAS) + 176; + + static bool isBiasedBase(Register base) { return base == StackPointer; } + static int32_t stackBias(Register base) { + return isBiasedBase(base) ? SP_BASE : 0; + } + static int32_t spOffset(int32_t off) { return off + SP_BASE; } + static int32_t fpOffset(int32_t off) { return off; } + static int64_t biasedOffset(Register base, int64_t off) { + return off + stackBias(base); + } + // SP_BASE converts between %sp-relative and real addresses. Reading through + // %sp adds it; writing the result back into %sp takes it off again, so an + // sp-to-sp computation must not shift the register at all. + static int32_t biasDelta(Register base, Register dest) { + return stackBias(base) - stackBias(dest); + } + + // =============================================================== + // Constant materialisation. + + // Shortest of: or %g0,simm13,rd / sethi(+or) / the 6-instruction 64-bit + // sequence. The 64-bit path clobbers SecondScratchReg (ScratchRegister when + // dest is SecondScratchReg). + void ma_li(Register dest, ImmWord imm); + void ma_li(Register dest, Imm32 imm); + void ma_li(Register dest, ImmPtr imm); + void ma_li(Register dest, ImmGCPtr imm); + + // Always the fixed Load64InstructionCount form, so the value can be + // rewritten in place by UpdateLoad64Value. + BufferOffset ma_liPatchable(Register dest, ImmWord imm); + BufferOffset ma_liPatchable(Register dest, ImmPtr imm); + + BufferOffset emitLoad64Stanza(Register dest, uint64_t value); + + // =============================================================== + // Memory. These add STACK_BIAS for %sp/%fp bases and fall back to an index + // register when the biased offset leaves simm13; callers never range-check. + + FaultingCodeOffset ma_load(Op3B op, Register rd, Register base, int32_t off); + FaultingCodeOffset ma_load(Op3B op, Register rd, const Address& addr) { + return ma_load(op, rd, addr.base, addr.offset); + } + FaultingCodeOffset ma_loadf(Op3B op, FloatRegister rd, Register base, + int32_t off); + FaultingCodeOffset ma_loadf(Op3B op, FloatRegister rd, const Address& addr) { + return ma_loadf(op, rd, addr.base, addr.offset); + } + FaultingCodeOffset ma_store(Op3B op, Register rs, Register base, int32_t off); + FaultingCodeOffset ma_store(Op3B op, Register rs, const Address& addr) { + return ma_store(op, rs, addr.base, addr.offset); + } + FaultingCodeOffset ma_storef(Op3B op, FloatRegister rs, Register base, + int32_t off); + FaultingCodeOffset ma_storef(Op3B op, FloatRegister rs, const Address& addr) { + return ma_storef(op, rs, addr.base, addr.offset); + } + + // =============================================================== + // simm13-safe ALU with a wide immediate. + + void ma_alu(Op3A op, Register rd, Register rs, int64_t imm); + void ma_add(Register rd, Register rs, int64_t imm) { + ma_alu(OP3_ADD, rd, rs, imm); + } + void ma_sub(Register rd, Register rs, int64_t imm) { + ma_alu(OP3_SUB, rd, rs, imm); + } + void ma_and(Register rd, Register rs, int64_t imm) { + ma_alu(OP3_AND, rd, rs, imm); + } + void ma_or(Register rd, Register rs, int64_t imm) { + ma_alu(OP3_OR, rd, rs, imm); + } + void ma_xor(Register rd, Register rs, int64_t imm) { + ma_alu(OP3_XOR, rd, rs, imm); + } + + // =============================================================== + // Flag setting. Each records the bank ma_b must branch on. + + void ma_cmp64(Register lhs, Register rhs) { + as_subcc(g0, lhs, rhs); + lastCC_ = CC_XCC; + } + void ma_cmp64(Register lhs, int64_t imm); + void ma_cmp32(Register lhs, Register rhs) { + as_subcc(g0, lhs, rhs); + lastCC_ = CC_ICC; + } + void ma_cmp32(Register lhs, int64_t imm); + + void ma_test64(Register lhs, Register rhs) { + as_andcc(g0, lhs, rhs); + lastCC_ = CC_XCC; + } + void ma_test64(Register lhs, int64_t imm); + void ma_test32(Register lhs, Register rhs) { + as_andcc(g0, lhs, rhs); + lastCC_ = CC_ICC; + } + void ma_test32(Register lhs, int64_t imm); + + // fcmpd/fcmps into %fcc0. + void ma_fcmp(FloatRegister lhs, FloatRegister rhs) { + as_fcmp(OPF_FCMPd, CC_FCC0, lhs, rhs); + } + void ma_fcmps(FloatRegister lhs, FloatRegister rhs) { + as_fcmp(OPF_FCMPs, CC_FCC0, lhs, rhs); + } + + // =============================================================== + // Branches. + + void jump(Label* label); + // Conditional branch on an explicit condition-code bank. + void ma_bc(Condition cond, ConditionCode cc, Label* label); + // Conditional branch on %fcc0. + void ma_bc(DoubleCondition cond, Label* label); + + // Branch on the bank the most recent ma_cmp_*/ma_test_* wrote. + template + void ma_b(CondT cond, Label* label) { + if constexpr (std::is_same_v) { + if (cond == Always) { + jump(label); + return; + } + ma_bc(cond, lastCC_, label); + } else { + ma_bc(cond, label); + } + } + + // =============================================================== + // GPR<->FPR moves. + // + // VIS3 hardware has movxtod/movdtox; everything older (including the qemu + // target we bring up on, whose AT_HWCAP reports VIS/VIS2 but not VIS3) + // round-trips through a freshly allocated 16-byte stack slot above SP_BASE. + // The memory path is the one our tests actually exercise. + + void moveGPRToFPR64(Register src, FloatRegister dest); + void moveFPR64ToGPR(FloatRegister src, Register dest); + // Raw 32-bit move to/from the single-precision half (the high word of the + // double register on this big-endian target). + void moveGPRToFPR32(Register src, FloatRegister dest); + void moveFPR32ToGPR(FloatRegister src, Register dest); + // Low word of src zero-extended into the FPR doubleword. + void moveGPRWordZeroToFPR(Register src, FloatRegister dest); + + // dest = magnitude(magSrc) with the sign bit of signSrc. + void emitCopySign(FloatRegister dest, FloatRegister signSrc, + FloatRegister magSrc); + + // Round src to an integral value converted to int64 in fpDest (the bit + // pattern an fdtox leaves in the FPR). SPARC's convert-to-integer always + // truncates toward zero, so floor/ceil are synthesized with a correction + // step. RoundDown = toward -inf, RoundUp = toward +inf. + enum FpIntRound { RoundDown, RoundUp }; + void roundToInt64FPR(FloatRegister fpDest, FloatRegister src, FpIntRound m); + // Single-precision form, leaving the rounded value in a GPR. Needed because + // the float32 callers have no spare FP register to keep src out of fpDest. + void roundFloat32ToInt64GPR(Register dest, FloatRegister src, FpIntRound m); +}; + +class MacroAssemblerSPARC64Compat : public MacroAssemblerSPARC64 { + public: + using MacroAssemblerSPARC64::MacroAssemblerSPARC64; + + MacroAssemblerSPARC64Compat() {} + + bool buildOOLFakeExitFrame(void* fakeReturnAddr); + + // =============================================================== + // Conversion functions + + void convertBoolToInt32(Register src, Register dest) { + as_and(dest, src, 1); + } + void convertInt32ToDouble(Register src, FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_sra(scratch, src, 0); + moveGPRToFPR64(scratch, dest); + as_fpop1(OPF_FXTOD, dest, dest); + } + void convertInt32ToDouble(const Address& srcArg, FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + load32(valuePayload(srcArg), scratch); + moveGPRToFPR64(scratch, dest); + as_fpop1(OPF_FXTOD, dest, dest); + } + void convertInt32ToDouble(const BaseIndex& src, FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + convertInt32ToDouble(Address(scratch, src.offset), dest); + } + void convertUInt32ToDouble(Register src, FloatRegister dest); + void convertUInt32ToFloat32(Register src, FloatRegister dest); + void convertDoubleToFloat32(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FDTOS, dest, src); + } + void convertDoubleToFloat16(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("NYI: float16 on sparc64"); + } + void convertFloat16ToDouble(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("NYI: float16 on sparc64"); + } + void convertFloat32ToFloat16(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("NYI: float16 on sparc64"); + } + void convertFloat16ToFloat32(FloatRegister src, FloatRegister dest) { + MOZ_CRASH("NYI: float16 on sparc64"); + } + void convertInt32ToFloat16(Register src, FloatRegister dest) { + MOZ_CRASH("NYI: float16 on sparc64"); + } + void convertDoubleToInt32(FloatRegister src, Register dest, Label* fail, + bool negativeZeroCheck = true); + void convertDoubleToPtr(FloatRegister src, Register dest, Label* fail, + bool negativeZeroCheck = true); + void convertFloat32ToInt32(FloatRegister src, Register dest, Label* fail, + bool negativeZeroCheck = true); + void convertFloat32ToDouble(FloatRegister src, FloatRegister dest) { + as_fpop1(OPF_FSTOD, dest, src); + } + void convertInt32ToFloat32(Register src, FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_sra(scratch, src, 0); + moveGPRToFPR64(scratch, ScratchDoubleReg); + as_fpop1(OPF_FXTOS, dest, ScratchDoubleReg); + } + void convertInt32ToFloat32(const Address& src, FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + load32(src, scratch); + convertInt32ToFloat32(scratch, dest); + } + + FaultingCodeOffset loadFloat16(const Address& addr, FloatRegister dest, + Register temp) { + MOZ_CRASH("NYI: float16 on sparc64"); + } + FaultingCodeOffset loadFloat16(const BaseIndex& src, FloatRegister dest, + Register temp) { + MOZ_CRASH("NYI: float16 on sparc64"); + } + + // =============================================================== + // Effective address computation + + void computeScaledAddress(const BaseIndex& address, Register dest) { + int32_t bias = biasDelta(address.base, dest); + if (address.scale == TimesOne) { + as_add(dest, address.base, address.index); + } else if (dest != address.base) { + as_sllx(dest, address.index, uint32_t(address.scale)); + as_add(dest, address.base, dest); + } else { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_sllx(scratch, address.index, uint32_t(address.scale)); + as_add(dest, address.base, scratch); + } + if (bias) { + as_add(dest, dest, bias); + } + } + + void computeEffectiveAddress(const Address& address, Register dest) { + int64_t off = int64_t(address.offset) + biasDelta(address.base, dest); + if (off == 0) { + if (dest != address.base) { + as_or(dest, g0, address.base); + } + return; + } + ma_add(dest, address.base, off); + } + void computeEffectiveAddress(const BaseIndex& address, Register dest) { + computeScaledAddress(address, dest); + if (address.offset) { + ma_add(dest, dest, address.offset); + } + } + + // =============================================================== + // Move instructions + + void mov(Register src, Register dest) { as_or(dest, g0, src); } + void mov(ImmWord imm, Register dest) { movePtr(imm, dest); } + void mov(ImmPtr imm, Register dest) { + mov(ImmWord(uintptr_t(imm.value)), dest); + } + void mov(CodeLabel* label, Register dest) { + BufferOffset bo = emitLoad64Stanza(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) { ma_li(dest, imm); } + void move32(Register src, Register dest) { as_sra(dest, src, 0); } + + void movePtr(Register src, Register dest) { + // Converting to or from %sp crosses the bias: shared code's + // moveStackPtrTo/moveToStackPtr are defined in terms of this, and they + // mean the logical stack pointer. + if (src == StackPointer && dest != StackPointer) { + as_add(dest, StackPointer, SP_BASE); + } else if (dest == StackPointer && src != StackPointer) { + as_sub(StackPointer, src, SP_BASE); + } else if (src != dest) { + as_or(dest, g0, src); + } + } + void movePtr(ImmWord imm, Register dest) { ma_li(dest, imm); } + void movePtr(ImmPtr imm, Register dest) { + movePtr(ImmWord(uintptr_t(imm.value)), dest); + } + void movePtr(wasm::SymbolicAddress imm, Register dest) { + BufferOffset bo = emitLoad64Stanza(dest, (uint64_t)-1); + append(wasm::SymbolicAccess(CodeOffset(bo.getOffset()), imm)); + } + void movePtr(ImmGCPtr imm, Register dest) { + BufferOffset bo = emitLoad64Stanza(dest, (uint64_t)uintptr_t(imm.value)); + Assembler::writeDataRelocation(bo, imm); + } + + void moveFloat32(FloatRegister src, FloatRegister dest) { + if (src != dest) { + as_fpop1(OPF_FMOVs, dest, src); + } + } + void moveDouble(FloatRegister src, FloatRegister dest) { + if (src != dest) { + as_fpop1(OPF_FMOVd, dest, src); + } + } + + // =============================================================== + // Branch functions + + void branch(JitCode* c) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + BufferOffset bo = emitLoad64Stanza(scratch, (uint64_t)uintptr_t(c->raw())); + addPendingJump(bo, ImmPtr(c->raw()), RelocationKind::JITCODE); + as_jmpl(g0, scratch, 0); + m_buffer.leaveNoPool(); + } + void branch(const Register reg) { as_jmpl(g0, reg, 0); } + + using MacroAssemblerSPARC64::jump; + void jump(Register reg) { as_jmpl(g0, reg, 0); } + void jump(const Address& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + loadPtr(address, scratch); + as_jmpl(g0, scratch, 0); + } + void jump(JitCode* code) { branch(code); } + void jump(ImmPtr ptr) { + m_buffer.enterNoPool(kNoPoolPatchableBranchInsns); + BufferOffset bo = + emitLoad64Stanza(SecondScratchReg, (uint64_t)uintptr_t(ptr.value)); + addPendingJump(bo, ptr, RelocationKind::HARDCODED); + as_jmpl(g0, SecondScratchReg, 0); + m_buffer.leaveNoPool(); + } + void jump(TrampolinePtr code) { jump(ImmPtr(code.value)); } + + // Explicit-bank forms, for callers that know the operand width instead of + // relying on the bank the last compare recorded. + using MacroAssemblerSPARC64::ma_b; + void ma_b(Condition cond, ConditionCode cc, Label* label) { + if (cond == Always) { + jump(label); + return; + } + ma_bc(cond, cc, label); + } + + void ma_cmp_set(Register dest, Condition cond, ConditionCode cc) { + as_or(dest, g0, 0); + as_movcc(cond, cc, dest, 1); + } + + void ma_cmp_move(Register dest, Register src, Condition cond, + ConditionCode cc) { + as_movcc(cond, cc, dest, src); + } + + // dest = 1 if the recorded condition-code bank satisfies cond, else 0. + void ma_cmp_set(Register dest, Condition cond) { + as_or(dest, g0, 0); + as_movcc(cond, lastCC_, dest, 1); + } + + void ma_cmp_set_dbl(Register dest, DoubleCondition cond) { + // MOVcc can select %fcc0, but the Assembler's ConditionCode only names + // %icc/%xcc, so materialise the boolean with a branch instead. + Label done; + as_or(dest, g0, 0); + ma_bc(Assembler::InvertCondition(cond), &done); + as_or(dest, g0, 1); + bind(&done); + } + + // If the recorded bank satisfies cond, dest = src. + void ma_cmp_move(Register dest, Register src, Condition cond) { + as_movcc(cond, lastCC_, dest, src); + } + + // wasm select: the condition is a 32-bit value, so test it on %icc. + void moveIfZero(Register dst, Register src, Register cond) { + ma_cmp32(cond, int64_t(0)); + as_movcc(Equal, CC_ICC, dst, src); + } + + 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); + + // Emit the compare for the given condition and operand sizes, recording the + // condition-code bank ma_b must read. Returns the condition to use, which on + // SPARC is always the one passed in: the signed/unsigned distinction lives + // in the branch encoding, not in the compare. + Condition ma_cmp(Register lhs, Register rhs, Condition cond, + bool is32bit = false) { + if (is32bit) { + ma_cmp32(lhs, rhs); + } else { + ma_cmp64(lhs, rhs); + } + return cond; + } + + Condition ma_cmp(Register lhs, Imm32 rhs, Condition cond, + bool is32bit = false) { + if (is32bit) { + ma_cmp32(lhs, int64_t(rhs.value)); + } else { + ma_cmp64(lhs, int64_t(rhs.value)); + } + return cond; + } + + Condition ma_cmp(Register lhs, ImmWord rhs, Condition cond) { + ma_cmp64(lhs, int64_t(rhs.value)); + return 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) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + ma_li(scratch, Imm32(rhs.value)); + ma_cmp32(lhs, scratch); + return cond; + } + + // Compare a tag register against an ImmTag constant and branch. Tags are + // 17-bit, so the constant needs a register; the caller must leave one of the + // two scratches free. + void branchTestTag(Condition cond, Register tagReg, ImmTag tag, + Label* label) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + Register tmp = + (tagReg == SecondScratchReg) ? ScratchRegister : SecondScratchReg; + ma_li(tmp, Imm32(tag.value)); + ma_cmp32(tagReg, tmp); + ma_b(cond, label); + } + + void ma_mod_mask(Register src, Register dest, Register hold, Register remain, + int32_t shift, Label* negZero = nullptr); + + void nop() { as_nop(); } + void breakpoint(uint32_t value = 0) { as_ta(0x7f); } + + 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); + } + void push(Register reg) { + if (reg == StackPointer) { + // Shared code pushes the stack pointer to hand C++ a Value* (the vp/argv + // arguments of the call fallbacks). That has to be the logical pointer, + // not the biased register. + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + as_add(scratch, StackPointer, SP_BASE); + as_sub(StackPointer, StackPointer, 8); + ma_store(OP3_STX, scratch, StackPointer, 0); + return; + } + as_sub(StackPointer, StackPointer, 8); + ma_store(OP3_STX, reg, StackPointer, 0); + } + void push(FloatRegister reg) { + as_sub(StackPointer, StackPointer, 8); + if (reg.isSingle()) { + ma_storef(OP3_STF, reg, StackPointer, 0); + } else { + ma_storef(OP3_STDF, reg, StackPointer, 0); + } + } + void pop(Register reg) { + ma_load(OP3_LDX, reg, StackPointer, 0); + as_add(StackPointer, StackPointer, 8); + } + void pop(FloatRegister reg) { + if (reg.isSingle()) { + ma_loadf(OP3_LDF, reg, StackPointer, 0); + } else { + ma_loadf(OP3_LDDF, reg, StackPointer, 0); + } + as_add(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 = emitLoad64Stanza(dest, (uint64_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(Register src, Register dest) { + as_srlx(dest, src, JSVAL_TAG_SHIFT); + } + void splitTag(const ValueOperand& operand, Register dest) { + splitTag(operand.valueReg(), dest); + } + void splitTagForTest(const ValueOperand& value, ScratchTagScope& tag) { + splitTag(value, tag); + } + + // PUNBOX64: the int32/boolean payload is the low 32 bits of the Value, which + // on this big-endian target sits at byte +4 of the 8-byte slot. + static Address valuePayload(const Address& a) { + return Address(a.base, a.offset + 4); + } + static BaseIndex valuePayload(const BaseIndex& a) { + return BaseIndex(a.base, a.index, a.scale, a.offset + 4); + } + + void unboxNonDouble(const ValueOperand& operand, Register dest, + JSValueType type) { + unboxNonDouble(operand.valueReg(), dest, type); + } + template + void unboxNonDouble(T src, Register dest, JSValueType type) { + MOZ_ASSERT(type != JSVAL_TYPE_DOUBLE); + if (type == JSVAL_TYPE_INT32 || type == JSVAL_TYPE_BOOLEAN) { + load32(valuePayload(src), dest); + return; + } + loadPtr(src, dest); + unboxNonDouble(dest, dest, type); + } + void unboxNonDouble(Register src, Register dest, JSValueType type) { + MOZ_ASSERT(type != JSVAL_TYPE_DOUBLE); + if (type == JSVAL_TYPE_INT32 || type == JSVAL_TYPE_BOOLEAN) { + as_sra(dest, src, 0); + return; + } + as_sllx(dest, src, 64 - JSVAL_TAG_SHIFT); + as_srlx(dest, dest, 64 - JSVAL_TAG_SHIFT); + } + void unboxGCThingForGCBarrier(const Address& src, Register dest) { + loadPtr(src, dest); + as_sllx(dest, dest, 64 - JSVAL_TAG_SHIFT); + as_srlx(dest, dest, 64 - JSVAL_TAG_SHIFT); + } + void unboxGCThingForGCBarrier(const ValueOperand& src, Register dest) { + as_sllx(dest, src.valueReg(), 64 - JSVAL_TAG_SHIFT); + as_srlx(dest, dest, 64 - JSVAL_TAG_SHIFT); + } + void unboxWasmAnyRefGCThingForGCBarrier(const Address& src, Register dest) { + static_assert(wasm::AnyRef::TagShift == 2); + loadPtr(src, dest); + as_and(dest, dest, -4); + } + void getGCThingValueChunk(const Address& src, Register dest) { + loadPtr(src, dest); + getGCThingValueChunk(ValueOperand(dest), dest); + } + void getGCThingValueChunk(const ValueOperand& src, Register dest) { + static_assert(js::gc::ChunkShift == 20); + as_sllx(dest, src.valueReg(), 64 - JSVAL_TAG_SHIFT); + as_srlx(dest, dest, (64 - JSVAL_TAG_SHIFT) + js::gc::ChunkShift); + as_sllx(dest, dest, js::gc::ChunkShift); + } + + // SPARC's hardware NaN is 0x7FFFFFFFFFFFFFFF, and 0xFFFFFFFFFFFFFFFF once + // negated, which JS::detail::ValueIsDouble rejects; rewrite every NaN to + // CanonicalizedNaNBits. %g3 is neither allocatable nor in the scratch pool. + void boxDouble(FloatRegister src, const ValueOperand& dest, FloatRegister) { + Register out = dest.valueReg(); + moveFPR64ToGPR(src, out); + UseScratchRegisterScope temps(*this); + temps.Exclude(GeneralRegisterSet(1 << out.code())); + Register tmp = temps.Acquire(); + as_sllx(tmp, out, 1); + as_srlx(tmp, tmp, 1); + as_or(g3, g0, 0x7ff); + as_sllx(g3, g3, 52); + ma_cmp64(tmp, g3); + as_or(tmp, g0, 0xfff); + as_sllx(tmp, tmp, 51); + as_movcc(Above, CC_XCC, out, tmp); + } + void boxNonDouble(JSValueType type, Register src, const ValueOperand& dest) { + boxValue(type, src, dest.valueReg()); + } + void boxNonDouble(Register type, Register src, const ValueOperand& dest) { + boxValue(type, src, dest.valueReg()); + } + void unboxInt32(const ValueOperand& operand, Register dest) { + as_sra(dest, operand.valueReg(), 0); + } + void unboxInt32(const Address& src, Register dest) { + load32(valuePayload(src), dest); + } + void unboxInt32(const BaseIndex& src, Register dest) { + load32(valuePayload(src), dest); + } + void unboxBoolean(const ValueOperand& operand, Register dest) { + as_sra(dest, operand.valueReg(), 0); + } + void unboxBoolean(const Address& src, Register dest) { + load32(valuePayload(src), dest); + } + void unboxBoolean(const BaseIndex& src, Register dest) { + load32(valuePayload(src), dest); + } + void unboxDouble(const ValueOperand& operand, FloatRegister dest) { + moveGPRToFPR64(operand.valueReg(), 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) { + if (dest.isFloat()) { + unboxDouble(src, dest.fpu()); + } else { + unboxNonDouble(src, dest.gpr(), type); + } + } + void unboxObjectOrNull(const Address& src, Register dest) { + loadPtr(src, dest); + as_sllx(dest, dest, 64 - JSVAL_TAG_SHIFT); + as_srlx(dest, dest, 64 - JSVAL_TAG_SHIFT); + } + + void tagValue(JSValueType type, Register payload, ValueOperand dest) { + MOZ_ASSERT(type != JSVAL_TYPE_UNDEFINED && type != JSVAL_TYPE_NULL); + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != payload && scratch != dest.valueReg()); + tagValueWithScratch(type, payload, dest, scratch); + } + void tagValueWithScratch(JSValueType type, Register payload, + ValueOperand dest, Register scratch) { + movePtr(ImmShiftedTag(type), scratch); + if (type == JSVAL_TYPE_INT32 || type == JSVAL_TYPE_BOOLEAN || + type == JSVAL_TYPE_MAGIC) { + as_srl(dest.valueReg(), payload, 0); + } else if (payload != dest.valueReg()) { + movePtr(payload, dest.valueReg()); + } + as_or(dest.valueReg(), dest.valueReg(), scratch); + } + void boxValue(JSValueType type, Register src, Register dest) { + MOZ_ASSERT(src != dest); + MOZ_ASSERT(type != JSVAL_TYPE_UNDEFINED && type != JSVAL_TYPE_NULL); + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + boxValueWithScratch(type, src, dest, scratch); + } + void boxValueWithScratch(JSValueType type, Register src, Register dest, + Register scratch) { + if (type == JSVAL_TYPE_INT32 || type == JSVAL_TYPE_BOOLEAN || + type == JSVAL_TYPE_MAGIC) { + as_srl(dest, src, 0); + } else { + movePtr(src, dest); + } + movePtr(ImmShiftedTag(type), scratch); + as_or(dest, dest, scratch); + } + void boxValue(Register type, Register src, Register dest) { + MOZ_ASSERT(src != dest); + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(scratch != dest && scratch != src && scratch != type); + ma_li(scratch, Imm32(JSVAL_TAG_MAX_DOUBLE)); + as_or(scratch, scratch, type); + as_sllx(scratch, scratch, JSVAL_TAG_SHIFT); + as_srl(dest, src, 0); + as_or(dest, dest, scratch); + } + + // =============================================================== + // Value store/load/push/pop + + void storeValue(ValueOperand val, const Address& dest) { + storePtr(val.valueReg(), dest); + } + void storeValue(ValueOperand val, const BaseIndex& dest) { + storePtr(val.valueReg(), dest); + } + void storeValue(JSValueType type, Register reg, Address dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(dest.base != scratch); + boxValue(type, reg, scratch); + storePtr(scratch, dest); + } + void storeValue(JSValueType type, Register reg, BaseIndex dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(dest.base != scratch); + boxValue(type, reg, scratch); + storePtr(scratch, dest); + } + void storeValue(const Value& val, Address dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(dest.base != scratch); + if (val.isGCThing()) { + CodeOffset off = movWithPatch(ImmWord(val.asRawBits()), scratch); + writeDataRelocation(off, val); + } else { + movePtr(ImmWord(val.asRawBits()), scratch); + } + storePtr(scratch, dest); + } + void storeValue(const Value& val, BaseIndex dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + MOZ_ASSERT(dest.base != scratch); + if (val.isGCThing()) { + CodeOffset off = movWithPatch(ImmWord(val.asRawBits()), scratch); + writeDataRelocation(off, val); + } else { + movePtr(ImmWord(val.asRawBits()), scratch); + } + storePtr(scratch, dest); + } + void storeValue(const Address& src, const Address& dest, Register temp) { + loadPtr(src, temp); + storePtr(temp, dest); + } + + void storePrivateValue(Register src, const Address& dest) { + storePtr(src, dest); + } + void storePrivateValue(ImmGCPtr imm, const Address& dest) { + storePtr(imm, dest); + } + + void loadValue(Address src, ValueOperand val) { + loadPtr(src, val.valueReg()); + } + void loadValue(const BaseIndex& src, ValueOperand val) { + loadPtr(src, val.valueReg()); + } + void loadUnalignedValue(const Address& src, ValueOperand dest) { + load64Unaligned(src, Register64(dest.valueReg())); + } + + void pushValue(ValueOperand val) { push(val.valueReg()); } + void popValue(ValueOperand val) { pop(val.valueReg()); } + void pushValue(const Value& val) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + if (val.isGCThing()) { + CodeOffset off = movWithPatch(ImmWord(val.asRawBits()), scratch); + writeDataRelocation(off, val); + } else { + movePtr(ImmWord(val.asRawBits()), scratch); + } + push(scratch); + } + void pushValue(JSValueType type, Register reg) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + boxValue(type, reg, scratch); + push(scratch); + } + void pushValue(const Address& addr) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + loadPtr(addr, scratch); + push(scratch); + } + void pushValue(const BaseIndex& addr, Register scratch) { + loadPtr(addr, scratch); + push(scratch); + } + + // =============================================================== + // Load instructions + + FaultingCodeOffset load8SignExtend(const Address& address, Register dest) { + return ma_load(OP3_LDSB, dest, address); + } + FaultingCodeOffset load8SignExtend(const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + return ma_load(OP3_LDSB, dest, scratch, src.offset); + } + FaultingCodeOffset load8ZeroExtend(const Address& address, Register dest) { + return ma_load(OP3_LDUB, dest, address); + } + FaultingCodeOffset load8ZeroExtend(const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + return ma_load(OP3_LDUB, dest, scratch, src.offset); + } + FaultingCodeOffset load16SignExtend(const Address& address, Register dest) { + return ma_load(OP3_LDSH, dest, address); + } + FaultingCodeOffset load16SignExtend(const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + return ma_load(OP3_LDSH, dest, scratch, src.offset); + } + // SPARC traps on unaligned accesses, so the "unaligned" forms are + // synthesized from bytes. |firstOp| is the load used for the byte at the + // lowest (most significant) address and selects sign- or zero-extension. + void loadUnalignedBytes(Op3B firstOp, Register base, int32_t offset, + int nbytes, Register dest); + void load16UnalignedSignExtend(const Address& src, Register dest); + void load16UnalignedSignExtend(const BaseIndex& src, Register dest); + FaultingCodeOffset load16ZeroExtend(const Address& address, Register dest) { + return ma_load(OP3_LDUH, dest, address); + } + FaultingCodeOffset load16ZeroExtend(const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + return ma_load(OP3_LDUH, dest, scratch, src.offset); + } + void load16UnalignedZeroExtend(const Address& src, Register dest); + void load16UnalignedZeroExtend(const BaseIndex& src, Register dest); + + FaultingCodeOffset load32(const Address& address, Register dest) { + return ma_load(OP3_LDSW, dest, address); + } + FaultingCodeOffset load32(const BaseIndex& address, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(address, scratch); + return ma_load(OP3_LDSW, dest, scratch, address.offset); + } + void load32(AbsoluteAddress address, Register dest) { + movePtr(ImmWord((uintptr_t)address.addr), dest); + as_ldsw(dest, dest, 0); + } + void load32(wasm::SymbolicAddress address, Register dest) { + movePtr(address, dest); + as_ldsw(dest, dest, 0); + } + void load32Unaligned(const Address& src, Register dest); + void load32Unaligned(const BaseIndex& src, Register dest); + void load32UnalignedZeroExtend(const Address& src, Register dest); + + FaultingCodeOffset load64(const Address& address, Register64 dest) { + return loadPtr(address, dest.reg); + } + FaultingCodeOffset load64(const BaseIndex& address, Register64 dest) { + return loadPtr(address, dest.reg); + } + void load64Unaligned(const Address& src, Register64 dest); + void load64Unaligned(const BaseIndex& src, Register64 dest); + + FaultingCodeOffset loadPtr(const Address& address, Register dest) { + return ma_load(OP3_LDX, dest, address); + } + FaultingCodeOffset loadPtr(const BaseIndex& src, Register dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + return ma_load(OP3_LDX, dest, scratch, src.offset); + } + void loadPtr(AbsoluteAddress address, Register dest) { + movePtr(ImmWord((uintptr_t)address.addr), dest); + as_ldx(dest, dest, 0); + } + void loadPtr(wasm::SymbolicAddress address, Register dest) { + movePtr(address, dest); + as_ldx(dest, dest, 0); + } + + void loadPrivate(const Address& address, Register dest) { + loadPtr(address, dest); + } + + FaultingCodeOffset loadDouble(const Address& addr, FloatRegister dest) { + return ma_loadf(OP3_LDDF, dest, addr); + } + FaultingCodeOffset loadDouble(const BaseIndex& src, FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + return ma_loadf(OP3_LDDF, dest, scratch, src.offset); + } + FaultingCodeOffset loadFloat32(const Address& addr, FloatRegister dest) { + return ma_loadf(OP3_LDF, dest, addr); + } + FaultingCodeOffset loadFloat32(const BaseIndex& src, FloatRegister dest) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(src, scratch); + return ma_loadf(OP3_LDF, dest, scratch, src.offset); + } + + void loadConstantDouble(double dp, FloatRegister dest); + void loadConstantFloat32(float f, FloatRegister dest); + + void notBoolean(const ValueOperand& val) { + as_xor(val.valueReg(), val.valueReg(), 1); + } + + [[nodiscard]] Register extractTag(const Address& address, Register scratch) { + loadPtr(address, scratch); + as_srlx(scratch, scratch, JSVAL_TAG_SHIFT); + return scratch; + } + [[nodiscard]] Register extractTag(const BaseIndex& address, + Register scratch) { + computeScaledAddress(address, scratch); + loadPtr(Address(scratch, address.offset), scratch); + as_srlx(scratch, scratch, JSVAL_TAG_SHIFT); + return scratch; + } + [[nodiscard]] Register extractTag(const ValueOperand& value, + Register scratch) { + splitTag(value, scratch); + return scratch; + } + + [[nodiscard]] Register extractObject(const Address& address, + Register scratch) { + unboxObject(address, scratch); + return scratch; + } + [[nodiscard]] Register extractObject(const ValueOperand& value, + Register scratch) { + unboxObject(value, scratch); + return scratch; + } + [[nodiscard]] Register extractInt32(const ValueOperand& value, + Register scratch) { + unboxInt32(value, scratch); + return scratch; + } + [[nodiscard]] Register extractString(const ValueOperand& value, + Register scratch) { + unboxString(value, scratch); + return scratch; + } + [[nodiscard]] Register extractSymbol(const ValueOperand& value, + Register scratch) { + unboxSymbol(value, scratch); + return scratch; + } + [[nodiscard]] Register extractBoolean(const ValueOperand& value, + Register scratch) { + unboxBoolean(value, scratch); + return scratch; + } + + void testObjectSet(Condition cond, const ValueOperand& value, Register dest) { + testTagSet(cond, value, JSVAL_TAG_OBJECT, dest); + } + void testUndefinedSet(Condition cond, const ValueOperand& value, + Register dest) { + testTagSet(cond, value, JSVAL_TAG_UNDEFINED, dest); + } + void testNullSet(Condition cond, const ValueOperand& value, Register dest) { + testTagSet(cond, value, JSVAL_TAG_NULL, dest); + } + + // Return to the absolute return address the JIT pushed. `call` leaves %o7 + // pointing at the call instruction itself, so pushReturnAddress stores + // %o7 + 8 and every JIT frame therefore holds a real code address. + BufferOffset ret() { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + ma_load(OP3_LDX, scratch, StackPointer, 0); + as_add(StackPointer, StackPointer, 8); + return as_jmpl(g0, scratch, 0); + } + + void j(Label* dest) { jump(dest); } + + void getWasmAnyRefGCThingChunk(Register anyref, Register dest) { + static_assert(js::gc::ChunkShift == 20); + as_srlx(dest, anyref, js::gc::ChunkShift); + as_sllx(dest, dest, js::gc::ChunkShift); + } + + template + void loadUnboxedValue(const T& address, MIRType type, AnyRegister dest) { + if (dest.isFloat()) { + loadInt32OrDouble(address, dest.fpu()); + } else { + unboxNonDouble(address, dest.gpr(), ValueTypeFromMIRType(type)); + } + } + + void loadInt32OrDouble(const Address& src, FloatRegister dest); + void loadInt32OrDouble(const BaseIndex& addr, FloatRegister dest); + + // =============================================================== + // Store instructions + + FaultingCodeOffset store8(Register src, const Address& address) { + return ma_store(OP3_STB, src, address); + } + FaultingCodeOffset store8(Register src, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(address, scratch); + return ma_store(OP3_STB, src, scratch, address.offset); + } + 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) { + return ma_store(OP3_STH, src, address); + } + FaultingCodeOffset store16(Register src, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(address, scratch); + return ma_store(OP3_STH, src, scratch, address.offset); + } + 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); + } + void store16Unaligned(Register src, const Address& dest); + void store16Unaligned(Register src, const BaseIndex& dest); + + FaultingCodeOffset store32(Register src, const Address& address) { + return ma_store(OP3_STW, src, address); + } + FaultingCodeOffset store32(Register src, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(address, scratch); + return ma_store(OP3_STW, src, scratch, address.offset); + } + 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); + } + void store32Unaligned(Register src, const Address& dest); + void store32Unaligned(Register src, const BaseIndex& dest); + + void store64(Imm64 imm, Address address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(ImmWord(imm.value), scratch); + storePtr(scratch, address); + } + void store64(Imm64 imm, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(ImmWord(imm.value), scratch); + storePtr(scratch, address); + } + FaultingCodeOffset store64(Register64 src, Address address) { + return storePtr(src.reg, address); + } + FaultingCodeOffset store64(Register64 src, const BaseIndex& address) { + return storePtr(src.reg, address); + } + void store64Unaligned(Register64 src, const Address& dest); + void store64Unaligned(Register64 src, const BaseIndex& 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) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + movePtr(ImmWord((uintptr_t)dest.addr), scratch); + as_stx(src, scratch, 0); + } + FaultingCodeOffset storePtr(Register src, const Address& address) { + return ma_store(OP3_STX, src, address); + } + FaultingCodeOffset storePtr(Register src, const BaseIndex& address) { + UseScratchRegisterScope temps(*this); + Register scratch = temps.Acquire(); + computeScaledAddress(address, scratch); + return ma_store(OP3_STX, src, scratch, address.offset); + } + + // =============================================================== + // Misc + + void handleFailureWithHandlerTail(Label* profilerExitTail, Label* bailoutTail, + uint32_t* returnValueCheckOffset); + + inline void incrementInt32Value(const Address& addr); + + void zeroDouble(FloatRegister reg) { moveGPRToFPR64(g0, reg); } + + void writeCodePointer(CodeLabel* label) { + label->patchAt()->bind(currentOffset()); + label->setLinkMode(CodeLabel::RawPointer); + m_buffer.ensureSpace(sizeof(void*)); + writeInst(-1); + 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); + // load64 stanza (6) + jmpl + delay nop. + static size_t ToggledCallSize(uint8_t* code) { + return kNoPoolPatchableBranchInsns * 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("SPARC64: lea not supported; use computeEffectiveAddress"); + } + + // Return using the SPARC link-register convention (%o7 holds the address of + // the call instruction), for code entered by a real `call`. + void abiret() { as_retl(); } + + 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); + + private: + void testTagSet(Condition cond, const ValueOperand& value, JSValueTag tag, + Register dest) { + MOZ_ASSERT(cond == Equal || cond == NotEqual); + { + UseScratchRegisterScope temps(*this); + Register tagReg = temps.Acquire(); + splitTag(value, tagReg); + Register tmp = + (tagReg == SecondScratchReg) ? ScratchRegister : SecondScratchReg; + ma_li(tmp, Imm32(tag)); + ma_cmp32(tagReg, tmp); + } + ma_cmp_set(dest, cond); + } +}; + +typedef MacroAssemblerSPARC64Compat MacroAssemblerSpecific; + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_MacroAssembler_sparc64_h */ diff -Naurp empty/js/src/jit/sparc64/MoveEmitter-sparc64.cpp firefox-153.0/js/src/jit/sparc64/MoveEmitter-sparc64.cpp --- firefox-153.0/js/src/jit/sparc64/MoveEmitter-sparc64.cpp 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/MoveEmitter-sparc64.cpp 2026-07-29 20:12:34.157015448 +0200 @@ -0,0 +1,328 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#include "jit/sparc64/MoveEmitter-sparc64.h" + +#include "jit/MacroAssembler-inl.h" + +using namespace js; +using namespace js::jit; + +void MoveEmitterSPARC64::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: + MOZ_CRASH("NYI: SPARC64 has no 128-bit SIMD"); + break; + default: + MOZ_CRASH("Unexpected move type"); + } +} + +void MoveEmitterSPARC64::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: + MOZ_CRASH("NYI: SPARC64 has no 128-bit SIMD"); + break; + default: + MOZ_CRASH("Unexpected move type"); + } +} + +void MoveEmitterSPARC64::emit(const MoveResolver& moves) { + if (moves.numCycles()) { + // The stride below has to match SpillSlotSize. + 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)); + } +} + +// The +STACK_BIAS+176 that turns this into a machine address is applied by +// ma_load/ma_store when the base register is StackPointer. +Address MoveEmitterSPARC64::cycleSlot(uint32_t slot, uint32_t subslot) const { + int32_t offset = masm.framePushed() - pushedAtCycle_; + return Address(StackPointer, offset + slot * SpillSlotSize + subslot); +} + +int32_t MoveEmitterSPARC64::getAdjustedOffset(const MoveOperand& operand) { + MOZ_ASSERT(operand.isMemoryOrEffectiveAddress()); + if (operand.base() != StackPointer) { + return operand.disp(); + } + + return operand.disp() + masm.framePushed() - pushedAtStart_; +} + +Address MoveEmitterSPARC64::getAdjustedAddress(const MoveOperand& operand) { + return Address(operand.base(), getAdjustedOffset(operand)); +} + +void MoveEmitterSPARC64::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 MoveEmitterSPARC64::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 MoveEmitterSPARC64::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 MoveEmitterSPARC64::emitDoubleMove(const MoveOperand& from, + const MoveOperand& to) { + if (from.isFloatReg()) { + if (to.isFloatReg()) { + masm.moveDouble(from.floatReg(), to.floatReg()); + } else if (to.isGeneralReg()) { + // movdtox with VIS3, otherwise a round trip through a stack slot. + masm.moveDoubleToGPR64(from.floatReg(), Register64(to.reg())); + } else { + MOZ_ASSERT(to.isMemory()); + masm.storeDouble(from.floatReg(), getAdjustedAddress(to)); + } + } else if (to.isFloatReg()) { + if (from.isMemory()) { + masm.loadDouble(getAdjustedAddress(from), to.floatReg()); + } else { + // movxtod with VIS3, otherwise a round trip through a stack slot. + masm.moveGPR64ToDouble(Register64(from.reg()), to.floatReg()); + } + } else { + MOZ_ASSERT(from.isMemory()); + MOZ_ASSERT(to.isMemory()); + ScratchDoubleScope fpscratch64(masm); + masm.loadDouble(getAdjustedAddress(from), fpscratch64); + masm.storeDouble(fpscratch64, getAdjustedAddress(to)); + } +} + +void MoveEmitterSPARC64::emitSimd128Move(const MoveOperand& from, + const MoveOperand& to) { + MOZ_CRASH("NYI: SPARC64 has no 128-bit SIMD"); +} + +void MoveEmitterSPARC64::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 MoveEmitterSPARC64::assertDone() { MOZ_ASSERT(inCycle_ == 0); } + +void MoveEmitterSPARC64::finish() { + assertDone(); + + masm.freeStack(masm.framePushed() - pushedAtStart_); +} diff -Naurp empty/js/src/jit/sparc64/MoveEmitter-sparc64.h firefox-153.0/js/src/jit/sparc64/MoveEmitter-sparc64.h --- firefox-153.0/js/src/jit/sparc64/MoveEmitter-sparc64.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/MoveEmitter-sparc64.h 2026-07-29 20:11:47.954735595 +0200 @@ -0,0 +1,65 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_MoveEmitter_sparc64_h +#define jit_sparc64_MoveEmitter_sparc64_h + +#include "jit/MacroAssembler.h" +#include "jit/MoveResolver.h" + +namespace js { +namespace jit { + +class MoveEmitterSPARC64 { + 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 MoveEmitterSPARC64(MacroAssembler& masm) + : inCycle_(0), + masm(masm), + pushedAtStart_(masm.framePushed()), + pushedAtCycle_(-1) {} + + ~MoveEmitterSPARC64() { 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 MoveEmitterSPARC64 MoveEmitter; + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_MoveEmitter_sparc64_h */ diff -Naurp empty/js/src/jit/sparc64/SharedICHelpers-sparc64-inl.h firefox-153.0/js/src/jit/sparc64/SharedICHelpers-sparc64-inl.h --- firefox-153.0/js/src/jit/sparc64/SharedICHelpers-sparc64-inl.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/SharedICHelpers-sparc64-inl.h 2026-07-29 20:11:35.039662328 +0200 @@ -0,0 +1,89 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_SharedICHelpers_sparc64_inl_h +#define jit_sparc64_SharedICHelpers_sparc64_inl_h + +#include "jit/BaselineFrame.h" +#include "jit/SharedICHelpers.h" + +#include "jit/MacroAssembler-inl.h" + +namespace js { +namespace jit { + +#ifdef DEBUG +// FramePointer is unbiased while StackPointer is biased, so the frame size is +// FramePointer minus the *unbiased* stack pointer. +inline void EmitComputeBaselineFrameSize(MacroAssembler& masm, + Register dest) { + UseScratchRegisterScope temps(masm); + Register unbiasedSp = temps.Acquire(); + masm.moveStackPtrTo(unbiasedSp); + masm.movePtr(FramePointer, dest); + masm.subPtr(unbiasedSp, dest); +} +#endif + +inline void EmitBaselineTailCallVM(TrampolinePtr target, MacroAssembler& masm, + uint32_t argSize) { +#ifdef DEBUG + Register scratch = R2.scratchReg(); + EmitComputeBaselineFrameSize(masm, 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. The return address stays + // in %o7; the VM wrapper pushes it via pushReturnAddress(). + masm.push(FrameDescriptor(FrameType::BaselineJS)); + + 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 + EmitComputeBaselineFrameSize(masm, 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. + masm.Push(FrameDescriptor(FrameType::BaselineJS)); + EmitRestoreTailCallReg(masm); + masm.Push(ICTailCallReg); + + // Save old frame pointer, stack pointer and stub reg. + masm.Push(FramePointer); + masm.moveStackPtrTo(FramePointer); + masm.Push(ICStubReg); + + // Stack should remain aligned. + masm.assertStackAlignment(sizeof(Value), 0); +} + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_SharedICHelpers_sparc64_inl_h */ diff -Naurp empty/js/src/jit/sparc64/SharedICHelpers-sparc64.h firefox-153.0/js/src/jit/sparc64/SharedICHelpers-sparc64.h --- firefox-153.0/js/src/jit/sparc64/SharedICHelpers-sparc64.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/SharedICHelpers-sparc64.h 2026-07-29 20:11:11.212533163 +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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_SharedICHelpers_sparc64_h +#define jit_sparc64_SharedICHelpers_sparc64_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. The return address +// lives in %o7, not on the stack. +static const size_t ICStackValueOffset = 0; + +// `call` and `jmpl` write the address of the transfer instruction itself into +// the link register; the resume address is that plus the transfer and its +// delay slot. +static constexpr int32_t LinkRegisterToReturnAddress = 8; + +struct BaselineStubFrame { + uintptr_t savedFrame; + uintptr_t savedStub; + uintptr_t returnAddress; + uintptr_t descriptor; +}; + +inline void EmitRestoreTailCallReg(MacroAssembler& masm) { + masm.computeEffectiveAddress( + Address(LinkRegister, LinkRegisterToReturnAddress), ICTailCallReg); +} + +inline void EmitRepushTailCallReg(MacroAssembler& masm) { + // Nothing to undo: EmitRestoreTailCallReg only derived a register value. +} + +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()); + + masm.call(R2.scratchReg()); + *callOffset = CodeOffset(masm.currentOffset()); +} + +inline void EmitReturnFromIC(MacroAssembler& masm) { masm.abiret(); } + +inline void EmitBaselineLeaveStubFrame(MacroAssembler& masm) { + masm.loadPtr( + Address(FramePointer, BaselineStubFrameLayout::ICStubOffsetFromFP), + ICStubReg); + + masm.moveToStackPtr(FramePointer); + masm.Pop(FramePointer); + + // Reinstate the link register from the saved return address. + masm.Pop(ICTailCallReg); + masm.computeEffectiveAddress( + Address(ICTailCallReg, -LinkRegisterToReturnAddress), LinkRegister); + + // 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) { + // %o7 is clobbered by guardedCallPreBarrier. Save it first. + masm.push(LinkRegister); + masm.guardedCallPreBarrier(addr, type); + masm.pop(LinkRegister); +} + +inline void EmitStubGuardFailure(MacroAssembler& masm) { + // Load next stub into ICStubReg. + masm.loadPtr(Address(ICStubReg, ICCacheIRStub::offsetOfNext()), ICStubReg); + + // %o7 still holds the link for the original call; jump to the next stubcode. + masm.jump(Address(ICStubReg, ICStub::offsetOfStubCode())); +} + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_SharedICHelpers_sparc64_h */ diff -Naurp empty/js/src/jit/sparc64/SharedICRegisters-sparc64.h firefox-153.0/js/src/jit/sparc64/SharedICRegisters-sparc64.h --- firefox-153.0/js/src/jit/sparc64/SharedICRegisters-sparc64.h 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/SharedICRegisters-sparc64.h 2026-07-29 20:10:53.197440865 +0200 @@ -0,0 +1,47 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#ifndef jit_sparc64_SharedICRegisters_sparc64_h +#define jit_sparc64_SharedICRegisters_sparc64_h + +#include "jit/Registers.h" +#include "jit/RegisterSets.h" +#include "jit/sparc64/Assembler-sparc64.h" + +namespace js { +namespace jit { + +// ValueOperands R0, R1, and R2. +// R0 == JSReturnReg, R2 uses a register that is not preserved across calls, +// R1 must survive calls and so lives in the register window. +static constexpr ValueOperand R0(o2); +static constexpr ValueOperand R1(l2); +static constexpr ValueOperand R2(o1); + +static_assert(R0 == ValueOperand(JSReturnReg)); + +// ICTailCallReg holds a *real* return address, so it cannot be %o7: `call` +// leaves the address of the call instruction itself there, eight bytes short +// of the resume address, and shared code (BaselineCacheIRCompiler's native +// exit frame) stores ICTailCallReg straight into a frame's return address +// slot. Like PPC64 with its LR we therefore use window registers, which are +// preserved across calls under the flat-window model. Both are allocatable, +// so BaselineICAvailableGeneralRegs() has to take them. +static constexpr Register ICTailCallReg = i3; +static constexpr Register ICStubReg = i2; + +// %d0 is both the SPARC V9 double return register and ScratchDoubleReg, so +// unlike other platforms FloatReg0 cannot be ReturnDoubleReg. +static constexpr FloatRegister FloatReg0 = d2; +static constexpr FloatRegister FloatReg1 = d4; +static constexpr FloatRegister FloatReg2 = d6; +static constexpr FloatRegister FloatReg3 = d8; + +} // namespace jit +} // namespace js + +#endif /* jit_sparc64_SharedICRegisters_sparc64_h */ diff -Naurp empty/js/src/jit/sparc64/Trampoline-sparc64.cpp firefox-153.0/js/src/jit/sparc64/Trampoline-sparc64.cpp --- firefox-153.0/js/src/jit/sparc64/Trampoline-sparc64.cpp 1970-01-01 01:00:00.000000000 +0100 +++ firefox-153.0/js/src/jit/sparc64/Trampoline-sparc64.cpp 2026-07-29 20:15:45.889429715 +0200 @@ -0,0 +1,492 @@ +/* -*- 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/. */ +/* Copyright (C) 2026 René Rebe */ + +#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/sparc64/SharedICHelpers-sparc64.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 is excluded: SupportsWasmSimd() +// is false, so no v128 value is ever live at bailout / invalidator / +// preBarrier entry, and including it would force the bailout frame's FPUArray +// to hold slots nothing ever writes. +static const LiveRegisterSet AllRegs = LiveRegisterSet( + GeneralRegisterSet(Registers::AllMask), + FloatRegisterSet(FloatRegisters::AllSingleMask | + FloatRegisters::AllDoubleMask)); + +static_assert(sizeof(uintptr_t) == sizeof(uint64_t), "Not 64-bit clean."); + +// %i6 is the register window's frame pointer: it is physically the same +// register as the C caller's %o6, so it holds the caller's stack pointer and +// is the address the kernel spills that window to. Overwriting it corrupts +// the caller's stack pointer and makes any window overflow write 128 bytes to +// a garbage address. SpiderMonkey's FramePointer, which the JIT rewrites +// constantly, must therefore live somewhere else. +static_assert(FramePointer != i6, + "FramePointer must not alias the register window frame pointer"); + +// SPARC V9 minimum frame: 16 extended words of register window save area plus +// six extended words of argument spill area. Stack-passed argument n > 6 of +// an incoming C call sits at [caller %sp + STACK_BIAS + 176 + 8*(n-7)]. +static constexpr uint32_t SparcMinFrameSize = 176; +static_assert(SparcMinFrameSize % ABIStackAlignment == 0); + +// Slots for the two EnterJitCode arguments that arrive on the stack. They are +// copied into this trampoline's own frame so they survive the JIT call. +struct EnterJITScratch { + uintptr_t numStackValues; + uintptr_t vp; +}; +static_assert(sizeof(EnterJITScratch) % ABIStackAlignment == 0); + +void JitRuntime::generateEnterJIT(JSContext* cx, MacroAssembler& masm) { + AutoCreatedBy acb(masm, "JitRuntime::generateEnterJIT"); + + enterJITOffset_ = startTrampolineCode(masm); + + // This is the boundary between the C world and the JIT's flat register + // machine. The single `save` below rotates a fresh window in: the C + // arguments that arrived in %o0-%o5 are now readable as %i0-%i5, and + // %l0-%l7 / %i0-%i5 become the JIT's own registers, preserved across calls + // to C for free because the C callee's own `save` gives it a different + // window. Every Ion, Baseline and trampoline frame shares this one window; + // no other generated code anywhere in the backend may execute save or + // restore. The matching `restore` at the end of this function is the only + // one in the backend. + masm.as_save(StackPointer, StackPointer, -int32_t(SparcMinFrameSize)); + masm.setFramePushed(0); + masm.assertStackAlignment(ABIStackAlignment, 0); + + // EnterJitCode signature: (void* code, unsigned argc, Value* argv, + // InterpreterFrame* fp, CalleeToken calleeToken, + // JSObject* envChain, size_t numStackValues, + // Value* vp) + const Register reg_code = i0; + const Register reg_argc = i1; + const Register reg_argv = i2; + const Register reg_frame = i3; + const Register reg_token = i4; + const Register reg_chain = i5; + + // Only six integer arguments arrive in registers; numStackValues and vp are + // in the caller's frame. Read them through the raw window frame pointer + // (which still holds the caller's biased %sp) and stash them in ours. + masm.reserveStack(sizeof(EnterJITScratch)); + masm.as_ldx(SecondScratchReg, i6, STACK_BIAS + SparcMinFrameSize); + masm.storePtr(SecondScratchReg, + Address(StackPointer, + offsetof(EnterJITScratch, numStackValues))); + masm.as_ldx(SecondScratchReg, i6, STACK_BIAS + SparcMinFrameSize + 8); + masm.storePtr(SecondScratchReg, + Address(StackPointer, offsetof(EnterJITScratch, vp))); + + // FramePointer is unbiased, so this is `add %sp, SP_BASE, FramePointer`, + // not a register move. + masm.moveStackPtrTo(FramePointer); + + // Window registers, so they survive the calls generateEnterJitShared makes. + generateEnterJitShared(masm, reg_argc, reg_argv, reg_token, l1, l3, l4); + + // Push the descriptor. + masm.loadPtr(Address(FramePointer, offsetof(EnterJITScratch, vp)), l1); + masm.unboxInt32(Address(l1, 0), l1); + masm.pushFrameDescriptorForJitCall(FrameType::CppToJSJit, l1, l1); + + CodeLabel returnLabel; + Label oomReturnLabel; + { + // Handle Interpreter -> Baseline OSR. + AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); + MOZ_ASSERT(!regs.has(FramePointer)); + regs.take(OsrFrameReg); + regs.take(reg_code); + + masm.movePtr(reg_frame, OsrFrameReg); + + Label notOsr; + masm.branchTestPtr(Assembler::Zero, OsrFrameReg, OsrFrameReg, ¬Osr); + + Register numStackValues = regs.takeAny(); + masm.loadPtr( + Address(FramePointer, offsetof(EnterJITScratch, numStackValues)), + numStackValues); + Register scratch = regs.takeAny(); + + // Push return address. + masm.subFromStackPtr(Imm32(sizeof(uintptr_t))); + masm.mov(&returnLabel, scratch); + masm.storePtr(scratch, Address(StackPointer, 0)); + + // Push previous frame pointer. + masm.subFromStackPtr(Imm32(sizeof(uintptr_t))); + masm.storePtr(FramePointer, Address(StackPointer, 0)); + + // Reserve frame. + Register framePtr = FramePointer; + masm.moveStackPtrTo(framePtr); + masm.subFromStackPtr(Imm32(BaselineFrame::Size())); + + Register framePtrScratch = regs.takeAny(); + masm.moveStackPtrTo(framePtrScratch); + + // Reserve space for locals and stack values. + masm.lshiftPtr(Imm32(3), numStackValues, scratch); + masm.subFromStackPtr(scratch); + + // 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)); + + masm.callJitNoProfiler(reg_code); + + { + // Interpreter -> Baseline OSR will return here. + masm.bind(&returnLabel); + masm.addCodeLabel(returnLabel); + masm.bind(&oomReturnLabel); + } + + // Discard arguments and padding: put the stack pointer back on the + // EnterJITScratch slots. + masm.moveToStackPtr(FramePointer); + masm.setFramePushed(sizeof(EnterJITScratch)); + + // Store the returned value into the vp. + const Register reg_vp = o1; + MOZ_ASSERT(reg_vp != JSReturnReg); + masm.loadPtr(Address(StackPointer, offsetof(EnterJITScratch, vp)), reg_vp); + masm.storeValue(JSReturnOperand, Address(reg_vp, 0)); + + masm.freeStack(sizeof(EnterJITScratch)); + + // Our return address is in %i7, which `restore` rotates away, so park it in + // a global (globals are not windowed) first. This `restore` is the only one + // in the backend; it hands the C caller back its own window and its %sp. + masm.as_or(ScratchRegister, i7, g0); + masm.as_restore(g0, g0, g0); + masm.as_jmpl(g0, ScratchRegister, 8); +} + +// static +mozilla::Maybe<::JS::ProfilingFrameIterator::RegisterState> +JitRuntime::getCppEntryRegisters(JitFrameLayout* frameStackAddress) { + // The C caller's registers are in its register window, not in memory, and + // materialising them would need a flushw in generateEnterJIT. + 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. The C callee wants a + // real address, so unbias. + masm.moveStackPtrTo(IntArgReg0); + + // Reserve place for BailoutInfo pointer. Two words to ensure alignment for + // setupAlignedABICall. + masm.subFromStackPtr(Imm32(2 * sizeof(uintptr_t))); + masm.moveStackPtrTo(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. + masm.jump(bailoutTail); +} + +// When bailout is done via out of line code (lazy bailout), the frame size is +// left in %o7 by CodeGeneratorSPARC64::generateOutOfLineCode() and the thunk +// pushes it. It is a plain integer, not a return address, so it is pushed +// verbatim rather than through pushReturnAddress(), which would add the +// call/delay-slot displacement. +static void PushBailoutFrame(MacroAssembler& masm, Register spArg) { + masm.push(LinkRegister); + + // 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.moveStackPtrTo(spArg); +} + +static void GenerateBailoutThunk(MacroAssembler& masm, Label* bailoutTail) { + PushBailoutFrame(masm, IntArgReg0); + + // Make space for Bailout's bailoutInfo outparam. + masm.reserveStack(sizeof(void*)); + masm.moveStackPtrTo(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. + 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."); + + // The context is the first argument. + 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 the link register 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); + + masm.setupUnalignedABICallDontSaveRestoreSP(); + masm.passABIArg(cxreg); + + size_t argDisp = ExitFrameLayout::Size(); + + // Copy any arguments. + for (uint32_t explicitArg = 0; explicitArg < f.explicitArgs; explicitArg++) { + switch (f.argProperties(explicitArg)) { + case VMFunctionData::WordByValue: + if (f.argPassedInFloatReg(explicitArg)) { + masm.passABIArg(MoveOperand(FramePointer, argDisp), ABIType::Float64); + } else { + masm.passABIArg(MoveOperand(FramePointer, argDisp), ABIType::General); + } + argDisp += sizeof(void*); + break; + case VMFunctionData::WordByRef: + masm.passABIArg(MoveOperand(FramePointer, argDisp, + MoveOperand::Kind::EffectiveAddress), + ABIType::General); + argDisp += sizeof(void*); + break; + case VMFunctionData::DoubleByValue: + case VMFunctionData::DoubleByRef: + MOZ_CRASH("NYI: SPARC64 callVM should not be used with 128bit values."); + break; + } + } + + // Copy the implicit outparam, if any. + const int32_t outParamOffset = + -int32_t(ExitFooterFrame::Size()) - f.sizeOfOutParamStackSlot(); + if (f.outParam != Type_Void) { + 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, ReturnReg, ReturnReg, + masm.failureLabel()); + break; + case Type_Bool: + masm.branchIfFalseBool(ReturnReg, 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); + Register temp1 = IntArgReg0; + Register temp2 = IntArgReg2; + Register temp3 = IntArgReg3; + 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)); + // %o7 is clobbered by the call below, so turn it into a real return address + // on the stack first. + 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); +}