Issue #3049 - loongarch64: implement baseline wasm atomics

This commit is contained in:
Basilisk-Dev 2026-04-26 11:23:59 -04:00 committed by wuggy
commit 6bb198c50d

View file

@ -29,7 +29,6 @@
* - Tiered compilation (bug 1277562)
* - profiler support / devtools (bug 1286948)
* - SIMD
* - Atomics
*
* There are lots of machine dependencies here but they are pretty well isolated
* to a segment of the compiler. Many dependencies will eventually be factored
@ -3710,6 +3709,123 @@ class BaseCompiler
return true;
}
#if defined(JS_CODEGEN_LOONGARCH64)
void coerceAtomicStoreResult(Scalar::Type viewType, RegI32 value) {
switch (viewType) {
case Scalar::Int8:
masm.as_ext_w_b(value.reg, value.reg);
break;
case Scalar::Uint8:
masm.as_bstrpick_d(value.reg, value.reg, 7, 0);
break;
case Scalar::Int16:
masm.as_ext_w_h(value.reg, value.reg);
break;
case Scalar::Uint16:
masm.as_bstrpick_d(value.reg, value.reg, 15, 0);
break;
case Scalar::Int32:
case Scalar::Uint32:
break;
default:
MOZ_CRASH("Unexpected atomic array type");
}
}
void atomicBinopToTypedIntArray(AtomicOp op, Scalar::Type viewType, Register value,
const BaseIndex& addr, Register valueTemp,
Register offsetTemp, Register maskTemp,
Register output) {
switch (viewType) {
case Scalar::Int8:
masm.atomicFetchOp(1, true, op, value, addr, valueTemp, offsetTemp,
maskTemp, output);
break;
case Scalar::Uint8:
masm.atomicFetchOp(1, false, op, value, addr, valueTemp, offsetTemp,
maskTemp, output);
break;
case Scalar::Int16:
masm.atomicFetchOp(2, true, op, value, addr, valueTemp, offsetTemp,
maskTemp, output);
break;
case Scalar::Uint16:
masm.atomicFetchOp(2, false, op, value, addr, valueTemp, offsetTemp,
maskTemp, output);
break;
case Scalar::Int32:
case Scalar::Uint32:
masm.atomicFetchOp(4, false, op, value, addr, valueTemp, offsetTemp,
maskTemp, output);
break;
default:
MOZ_CRASH("Unexpected atomic array type");
}
}
void atomicCompareExchangeToTypedIntArray(Scalar::Type viewType, const BaseIndex& addr,
Register oldval, Register newval,
Register valueTemp, Register offsetTemp,
Register maskTemp, Register output) {
switch (viewType) {
case Scalar::Int8:
masm.compareExchange(1, true, addr, oldval, newval, valueTemp,
offsetTemp, maskTemp, output);
break;
case Scalar::Uint8:
masm.compareExchange(1, false, addr, oldval, newval, valueTemp,
offsetTemp, maskTemp, output);
break;
case Scalar::Int16:
masm.compareExchange(2, true, addr, oldval, newval, valueTemp,
offsetTemp, maskTemp, output);
break;
case Scalar::Uint16:
masm.compareExchange(2, false, addr, oldval, newval, valueTemp,
offsetTemp, maskTemp, output);
break;
case Scalar::Int32:
case Scalar::Uint32:
masm.compareExchange(4, false, addr, oldval, newval, valueTemp,
offsetTemp, maskTemp, output);
break;
default:
MOZ_CRASH("Unexpected atomic array type");
}
}
void atomicExchangeToTypedIntArray(Scalar::Type viewType, const BaseIndex& addr,
Register value, Register valueTemp,
Register offsetTemp, Register maskTemp,
Register output) {
switch (viewType) {
case Scalar::Int8:
masm.atomicExchange(1, true, addr, value, valueTemp, offsetTemp,
maskTemp, output);
break;
case Scalar::Uint8:
masm.atomicExchange(1, false, addr, value, valueTemp, offsetTemp,
maskTemp, output);
break;
case Scalar::Int16:
masm.atomicExchange(2, true, addr, value, valueTemp, offsetTemp,
maskTemp, output);
break;
case Scalar::Uint16:
masm.atomicExchange(2, false, addr, value, valueTemp, offsetTemp,
maskTemp, output);
break;
case Scalar::Int32:
case Scalar::Uint32:
masm.atomicExchange(4, false, addr, value, valueTemp, offsetTemp,
maskTemp, output);
break;
default:
MOZ_CRASH("Unexpected atomic array type");
}
}
#endif
#ifdef JS_CODEGEN_ARM
void
loadI32(MemoryAccessDesc access, bool isSigned, RegI32 ptr, Register rt) {
@ -4056,8 +4172,15 @@ class BaseCompiler
#endif
void emitReinterpretI32AsF32();
void emitReinterpretI64AsF64();
MOZ_MUST_USE bool emitGrowMemory();
MOZ_MUST_USE bool emitCurrentMemory();
#if defined(JS_CODEGEN_LOONGARCH64)
[[nodiscard]] bool emitAtomicLoad();
[[nodiscard]] bool emitAtomicStore();
[[nodiscard]] bool emitAtomicBinOp();
[[nodiscard]] bool emitAtomicCompareExchange();
[[nodiscard]] bool emitAtomicExchange();
#endif
[[nodiscard]] bool emitGrowMemory();
[[nodiscard]] bool emitCurrentMemory();
};
void
@ -6980,6 +7103,209 @@ BaseCompiler::emitTeeStoreWithCoercion(ValType resultType, Scalar::Type viewType
return true;
}
#if defined(JS_CODEGEN_LOONGARCH64)
bool
BaseCompiler::emitAtomicLoad()
{
LinearMemoryAddress<Nothing> addr;
Scalar::Type viewType;
if (!iter_.readAtomicLoad(&addr, &viewType))
return false;
if (deadCode_)
return true;
MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(trapOffset()), 0,
MembarBeforeLoad, MembarAfterLoad);
RegI32 rp = popI32();
checkOffset(&access, rp);
#ifndef WASM_HUGE_MEMORY
masm.wasmBoundsCheck(Assembler::AboveOrEqual, rp.reg, trap(Trap::OutOfBounds));
#endif
MOZ_ASSERT(!IsUnaligned(access));
BaseIndex srcAddr(HeapReg, rp.reg, TimesOne, access.offset());
LoadStoreSize size = static_cast<LoadStoreSize>(access.byteSize() * 8);
LoadStoreExtension ext = (viewType == Scalar::Uint8 ||
viewType == Scalar::Uint16 ||
viewType == Scalar::Uint32)
? ZeroExtend
: SignExtend;
masm.memoryBarrier(access.barrierBefore());
masm.ma_load(rp.reg, srcAddr, size, ext);
masm.append(access, masm.size() - 4, masm.framePushed());
masm.memoryBarrier(access.barrierAfter());
pushI32(rp);
return true;
}
bool
BaseCompiler::emitAtomicStore()
{
LinearMemoryAddress<Nothing> addr;
Scalar::Type viewType;
Nothing unused_value;
if (!iter_.readAtomicStore(&addr, &viewType, &unused_value))
return false;
if (deadCode_)
return true;
MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(trapOffset()), 0,
MembarBeforeStore, MembarAfterStore);
RegI32 rv = popI32();
RegI32 rp = popI32();
checkOffset(&access, rp);
#ifndef WASM_HUGE_MEMORY
masm.wasmBoundsCheck(Assembler::AboveOrEqual, rp.reg, trap(Trap::OutOfBounds));
#endif
MOZ_ASSERT(!IsUnaligned(access));
BaseIndex dstAddr(HeapReg, rp.reg, TimesOne, access.offset());
LoadStoreSize size = static_cast<LoadStoreSize>(access.byteSize() * 8);
masm.memoryBarrier(access.barrierBefore());
masm.ma_store(rv.reg, dstAddr, size, SignExtend);
masm.append(access, masm.size() - 4, masm.framePushed());
masm.memoryBarrier(access.barrierAfter());
freeI32(rp);
coerceAtomicStoreResult(viewType, rv);
pushI32(rv);
return true;
}
bool
BaseCompiler::emitAtomicBinOp()
{
LinearMemoryAddress<Nothing> addr;
Scalar::Type viewType;
AtomicOp op;
Nothing unused_value;
if (!iter_.readAtomicBinOp(&addr, &viewType, &op, &unused_value))
return false;
if (deadCode_)
return true;
MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(trapOffset()));
RegI32 rv = popI32();
RegI32 rp = popI32();
RegI32 rd = needI32();
RegI32 valueTemp = needI32();
RegI32 offsetTemp = needI32();
RegI32 maskTemp = needI32();
checkOffset(&access, rp);
#ifndef WASM_HUGE_MEMORY
masm.wasmBoundsCheck(Assembler::AboveOrEqual, rp.reg, trap(Trap::OutOfBounds));
#endif
MOZ_ASSERT(!IsUnaligned(access));
atomicBinopToTypedIntArray(op, viewType,
rv.reg, BaseIndex(HeapReg, rp.reg, TimesOne, access.offset()),
valueTemp.reg, offsetTemp.reg, maskTemp.reg, rd.reg);
freeI32(rp);
freeI32(rv);
freeI32(valueTemp);
freeI32(offsetTemp);
freeI32(maskTemp);
pushI32(rd);
return true;
}
bool
BaseCompiler::emitAtomicCompareExchange()
{
LinearMemoryAddress<Nothing> addr;
Scalar::Type viewType;
Nothing unused_oldValue, unused_newValue;
if (!iter_.readAtomicCompareExchange(&addr, &viewType, &unused_oldValue, &unused_newValue))
return false;
if (deadCode_)
return true;
MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(trapOffset()));
RegI32 newval = popI32();
RegI32 oldval = popI32();
RegI32 rp = popI32();
RegI32 rd = needI32();
RegI32 valueTemp = needI32();
RegI32 offsetTemp = needI32();
RegI32 maskTemp = needI32();
checkOffset(&access, rp);
#ifndef WASM_HUGE_MEMORY
masm.wasmBoundsCheck(Assembler::AboveOrEqual, rp.reg, trap(Trap::OutOfBounds));
#endif
MOZ_ASSERT(!IsUnaligned(access));
atomicCompareExchangeToTypedIntArray(viewType,
BaseIndex(HeapReg, rp.reg, TimesOne, access.offset()),
oldval.reg, newval.reg, valueTemp.reg,
offsetTemp.reg, maskTemp.reg, rd.reg);
freeI32(newval);
freeI32(oldval);
freeI32(rp);
freeI32(valueTemp);
freeI32(offsetTemp);
freeI32(maskTemp);
pushI32(rd);
return true;
}
bool
BaseCompiler::emitAtomicExchange()
{
LinearMemoryAddress<Nothing> addr;
Scalar::Type viewType;
Nothing unused_value;
if (!iter_.readAtomicExchange(&addr, &viewType, &unused_value))
return false;
if (deadCode_)
return true;
MemoryAccessDesc access(viewType, addr.align, addr.offset, Some(trapOffset()));
RegI32 rv = popI32();
RegI32 rp = popI32();
RegI32 rd = needI32();
RegI32 valueTemp = needI32();
RegI32 offsetTemp = needI32();
RegI32 maskTemp = needI32();
checkOffset(&access, rp);
#ifndef WASM_HUGE_MEMORY
masm.wasmBoundsCheck(Assembler::AboveOrEqual, rp.reg, trap(Trap::OutOfBounds));
#endif
MOZ_ASSERT(!IsUnaligned(access));
atomicExchangeToTypedIntArray(viewType,
BaseIndex(HeapReg, rp.reg, TimesOne, access.offset()),
rv.reg, valueTemp.reg, offsetTemp.reg, maskTemp.reg, rd.reg);
freeI32(rv);
freeI32(rp);
freeI32(valueTemp);
freeI32(offsetTemp);
freeI32(maskTemp);
pushI32(rd);
return true;
}
#endif
bool
BaseCompiler::emitGrowMemory()
{
@ -7610,12 +7936,25 @@ BaseCompiler::emitBody()
CHECK_NEXT(emitComparison(emitCompareF64, ValType::F64, JSOP_GE, MCompare::Compare_Double));
// Atomics
#if defined(JS_CODEGEN_LOONGARCH64)
case uint16_t(Op::I32AtomicsLoad):
CHECK_NEXT(emitAtomicLoad());
case uint16_t(Op::I32AtomicsStore):
CHECK_NEXT(emitAtomicStore());
case uint16_t(Op::I32AtomicsBinOp):
CHECK_NEXT(emitAtomicBinOp());
case uint16_t(Op::I32AtomicsCompareExchange):
CHECK_NEXT(emitAtomicCompareExchange());
case uint16_t(Op::I32AtomicsExchange):
CHECK_NEXT(emitAtomicExchange());
#else
case uint16_t(Op::I32AtomicsLoad):
case uint16_t(Op::I32AtomicsStore):
case uint16_t(Op::I32AtomicsBinOp):
case uint16_t(Op::I32AtomicsCompareExchange):
case uint16_t(Op::I32AtomicsExchange):
MOZ_CRASH("Unimplemented Atomics");
#endif
// Sign extensions
case uint16_t(Op::I32Extend8S):