Issue #3049 - Extend LoongArch minimal JIT value handling

This commit is contained in:
Basilisk-Dev 2026-04-22 23:33:46 -04:00 committed by wuggy
commit ff29f1f22c
2 changed files with 397 additions and 48 deletions

View file

@ -24,6 +24,80 @@ function negativeResult(a) {
return a - 300;
}
function lessThan(a, b) {
return a < b;
}
function lessEqual(a, b) {
return a <= b;
}
function greaterThan(a, b) {
return a > b;
}
function greaterEqual(a, b) {
return a >= b;
}
function notLess(a, b) {
return !(a < b);
}
function boolStrictEq(a, b) {
return (a < b) === (b > a);
}
function boolLooseEq(a, b) {
return (a < b) == 1;
}
function boolLooseNe(a, b) {
return (a < b) != 1;
}
function bitMix(a, b) {
return (a & b) ^ (a | 8);
}
function invertBits(a) {
return ~a;
}
function assignArg(a, b) {
a = a < b;
return a;
}
function boolToNumeric(a, b) {
var flag = a < b;
return +flag;
}
function nullValue() {
return null;
}
function voidValue(a) {
return void a;
}
function unsetLocal() {
var x;
return x;
}
function unsetLooseEqNull() {
var x;
return x == null;
}
function conditional(a, b) {
if (a < b)
return true;
return false;
}
for (var i = 0; i < 100; i++) {
assertEq(add(i, i + 1), (i + i + 1));
assertEq(withLocal(i, 7), i + 7);
@ -33,6 +107,34 @@ assertEq(overflow(2147483647, 1), 2147483648);
assertEq(unsupported(2, 3), 5.5);
assertEq(largeResult(7), 307);
assertEq(negativeResult(7), -293);
assertEq(lessThan(2, 3), true);
assertEq(lessThan(3, 2), false);
assertEq(lessEqual(3, 3), true);
assertEq(lessEqual(4, 3), false);
assertEq(greaterThan(4, 3), true);
assertEq(greaterThan(2, 3), false);
assertEq(greaterEqual(4, 4), true);
assertEq(greaterEqual(2, 3), false);
assertEq(notLess(2, 3), false);
assertEq(notLess(3, 2), true);
assertEq(boolStrictEq(2, 3), true);
assertEq(boolStrictEq(3, 2), true);
assertEq(boolLooseEq(2, 3), true);
assertEq(boolLooseEq(3, 2), false);
assertEq(boolLooseNe(2, 3), false);
assertEq(boolLooseNe(3, 2), true);
assertEq(bitMix(6, 3), (6 & 3) ^ (6 | 8));
assertEq(invertBits(6), ~6);
assertEq(assignArg(2, 3), true);
assertEq(assignArg(3, 2), false);
assertEq(boolToNumeric(2, 3), 1);
assertEq(boolToNumeric(3, 2), 0);
assertEq(nullValue(), null);
assertEq(voidValue(7), undefined);
assertEq(unsetLocal(), undefined);
assertEq(unsetLooseEqNull(), true);
assertEq(conditional(2, 3), true);
assertEq(conditional(3, 2), false);
(function testPropertyWrappers() {
function getX(obj) { return obj.x; }

View file

@ -54,7 +54,29 @@ static constexpr LoongArchReg StackRegs[] = { t0, t1, t2, t3, t4, t5, t6 };
static constexpr LoongArchReg WideScratch = t7;
static constexpr LoongArchReg NarrowScratch = t8;
using TinyLoongArchJitCode = bool (*)(int32_t arg0, int32_t arg1, int32_t* out);
enum class MinimalValueKind : uint32_t {
Int32 = 0,
Boolean = 1,
Undefined = 2,
Null = 3
};
struct MinimalJitResult
{
uint32_t payload;
MinimalValueKind kind;
};
static_assert(sizeof(MinimalJitResult) == 8,
"Minimal JIT results must stay ABI-stable");
static inline bool
IsNumericKind(MinimalValueKind kind)
{
return kind == MinimalValueKind::Int32 || kind == MinimalValueKind::Boolean;
}
using TinyLoongArchJitCode = bool (*)(int32_t arg0, int32_t arg1, MinimalJitResult* out);
enum MinimalFastPathKind : uint8_t {
MinimalFastPathUninitialized = 0,
@ -159,12 +181,20 @@ struct BranchPatch
class MinimalLoongArchCompiler
{
struct StackValue
{
LoongArchReg reg;
MinimalValueKind kind;
};
JSScript* script_;
uint32_t words_[512];
size_t wordCount_;
size_t failPatches_[128];
size_t failPatchCount_;
LoongArchReg stack_[16];
StackValue stack_[mozilla::ArrayLength(StackRegs)];
MinimalValueKind argKinds_[mozilla::ArrayLength(ArgRegs)];
MinimalValueKind localKinds_[mozilla::ArrayLength(LocalRegs)];
size_t stackDepth_;
bool sawReturn_;
Vector<int32_t, 0, SystemAllocPolicy> pcToWord_;
@ -205,6 +235,14 @@ class MinimalLoongArchCompiler
return emit(EncodeUnsignedImm12(0x03800000, dst, dst, low12));
}
bool emitXori(LoongArchReg dst, LoongArchReg src, uint32_t imm) {
return emit(EncodeUnsignedImm12(0x03c00000, dst, src, imm));
}
bool emitSltui(LoongArchReg dst, LoongArchReg src, uint32_t imm) {
return emit(EncodeUnsignedImm12(0x02400000, dst, src, imm));
}
bool emitFailureBranch() {
if (failPatchCount_ >= mozilla::ArrayLength(failPatches_))
return false;
@ -239,8 +277,45 @@ class MinimalLoongArchCompiler
return emitMove(srcDst, NarrowScratch);
}
bool emitCompareLessThan(LoongArchReg lhs, LoongArchReg rhs, LoongArchReg out) {
return emit(EncodeThreeReg(0x00120000, out, lhs, rhs));
bool emitCompare(JSOp op, LoongArchReg lhs, LoongArchReg rhs, LoongArchReg out) {
switch (op) {
case JSOP_LT:
return emit(EncodeThreeReg(0x00120000, out, lhs, rhs));
case JSOP_LE:
if (!emit(EncodeThreeReg(0x00120000, out, rhs, lhs)))
return false;
return emitXori(out, out, 1);
case JSOP_GT:
return emit(EncodeThreeReg(0x00120000, out, rhs, lhs));
case JSOP_GE:
if (!emit(EncodeThreeReg(0x00120000, out, lhs, rhs)))
return false;
return emitXori(out, out, 1);
default:
break;
}
MOZ_CRASH("bad comparison opcode");
}
bool emitEq(LoongArchReg lhs, LoongArchReg rhs, LoongArchReg out, bool invert) {
if (!emit(EncodeThreeReg(0x00158000, NarrowScratch, lhs, rhs)))
return false;
if (invert)
return emit(EncodeThreeReg(0x00128000, out, zero, NarrowScratch));
return emitSltui(out, NarrowScratch, 1);
}
bool emitBitBinary(uint32_t opcode, LoongArchReg lhs, LoongArchReg rhs, LoongArchReg out) {
return emit(EncodeThreeReg(opcode, out, lhs, rhs));
}
bool emitBitNot(LoongArchReg src, LoongArchReg out) {
return emit(EncodeThreeReg(0x00140000, out, src, zero));
}
bool emitNot(LoongArchReg src, LoongArchReg out) {
return emitSltui(out, src, 1);
}
bool emitBranch(BranchKind kind, LoongArchReg reg, uint32_t targetPcOffset) {
@ -260,9 +335,12 @@ class MinimalLoongArchCompiler
MOZ_CRASH("bad branch kind");
}
bool emitReturn(LoongArchReg result) {
// Write the full int32 result back to the out-parameter.
if (!emit(EncodeUnsignedImm12(0x29800000, result, a2, 0)))
bool emitReturn(const StackValue& result) {
if (!emit(EncodeUnsignedImm12(0x29800000, result.reg, a2, 0)))
return false;
if (!emitLoadImm32(NarrowScratch, int32_t(result.kind)))
return false;
if (!emit(EncodeUnsignedImm12(0x29800000, NarrowScratch, a2, sizeof(uint32_t))))
return false;
if (!emit(EncodeImm12(0x02800000, a0, zero, 1)))
return false;
@ -273,47 +351,60 @@ class MinimalLoongArchCompiler
}
LoongArchReg allocStackReg() {
MOZ_ASSERT(stackDepth_ < mozilla::ArrayLength(stack_));
MOZ_ASSERT(stackDepth_ < mozilla::ArrayLength(StackRegs));
return StackRegs[stackDepth_];
}
bool pushTempFromReg(LoongArchReg src) {
bool pushTempFromReg(LoongArchReg src, MinimalValueKind kind) {
if (stackDepth_ >= mozilla::ArrayLength(StackRegs))
return false;
LoongArchReg dst = allocStackReg();
if (!emitMove(dst, src))
return false;
stack_[stackDepth_++] = dst;
stack_[stackDepth_++] = { dst, kind };
return true;
}
bool pushTempFromImm(int32_t imm) {
bool pushTempFromImm(int32_t imm, MinimalValueKind kind) {
if (stackDepth_ >= mozilla::ArrayLength(StackRegs))
return false;
LoongArchReg dst = allocStackReg();
if (!emitLoadImm32(dst, imm))
return false;
stack_[stackDepth_++] = dst;
stack_[stackDepth_++] = { dst, kind };
return true;
}
bool duplicateTop() {
if (!stackDepth_)
return false;
return pushTempFromReg(stack_[stackDepth_ - 1]);
const StackValue& top = stack_[stackDepth_ - 1];
return pushTempFromReg(top.reg, top.kind);
}
bool pop(LoongArchReg* reg) {
bool pop(StackValue* value) {
if (!stackDepth_)
return false;
*reg = stack_[--stackDepth_];
*value = stack_[--stackDepth_];
return true;
}
bool emitStoreLocal(uint32_t slot) {
if (slot >= script_->nfixed() || !stackDepth_)
return false;
return emitMove(LocalRegs[slot], stack_[stackDepth_ - 1]);
if (!emitMove(LocalRegs[slot], stack_[stackDepth_ - 1].reg))
return false;
localKinds_[slot] = stack_[stackDepth_ - 1].kind;
return true;
}
bool emitStoreArg(uint32_t slot) {
if (slot >= script_->numArgs() || !stackDepth_)
return false;
if (!emitMove(ArgRegs[slot], stack_[stackDepth_ - 1].reg))
return false;
argKinds_[slot] = stack_[stackDepth_ - 1].kind;
return true;
}
bool supportedScriptShape() const {
@ -366,7 +457,12 @@ class MinimalLoongArchCompiler
failPatchCount_(0),
stackDepth_(0),
sawReturn_(false)
{ }
{
for (size_t i = 0; i < mozilla::ArrayLength(argKinds_); i++)
argKinds_[i] = MinimalValueKind::Int32;
for (size_t i = 0; i < mozilla::ArrayLength(localKinds_); i++)
localKinds_[i] = MinimalValueKind::Undefined;
}
bool compile() {
if (!supportedScriptShape())
@ -375,6 +471,11 @@ class MinimalLoongArchCompiler
if (!pcToWord_.appendN(-1, script_->length() + 1))
return false;
for (size_t i = 0; i < script_->nfixed(); i++) {
if (!emitLoadImm32(LocalRegs[i], 0))
return false;
}
jsbytecode* pc = script_->code();
while (pc < script_->codeEnd()) {
if (!markBytecode(pc))
@ -383,37 +484,61 @@ class MinimalLoongArchCompiler
uint32_t pcOffset = uint32_t(pc - script_->code());
JSOp op = JSOp(*pc);
switch (op) {
case JSOP_GETARG:
if (GET_ARGNO(pc) >= script_->numArgs())
case JSOP_GETARG: {
uint32_t arg = GET_ARGNO(pc);
if (arg >= script_->numArgs())
return false;
if (!pushTempFromReg(ArgRegs[GET_ARGNO(pc)]))
if (!pushTempFromReg(ArgRegs[arg], argKinds_[arg]))
return false;
break;
case JSOP_GETLOCAL:
if (GET_LOCALNO(pc) >= script_->nfixed())
return false;
if (!pushTempFromReg(LocalRegs[GET_LOCALNO(pc)]))
}
case JSOP_SETARG:
if (!emitStoreArg(GET_ARGNO(pc)))
return false;
break;
case JSOP_GETLOCAL: {
uint32_t local = GET_LOCALNO(pc);
if (local >= script_->nfixed())
return false;
if (!pushTempFromReg(LocalRegs[local], localKinds_[local]))
return false;
break;
}
case JSOP_SETLOCAL:
case JSOP_INITLEXICAL:
if (!emitStoreLocal(GET_LOCALNO(pc)))
return false;
break;
case JSOP_ZERO:
if (!pushTempFromImm(0))
if (!pushTempFromImm(0, MinimalValueKind::Int32))
return false;
break;
case JSOP_ONE:
if (!pushTempFromImm(1))
if (!pushTempFromImm(1, MinimalValueKind::Int32))
return false;
break;
case JSOP_FALSE:
if (!pushTempFromImm(0, MinimalValueKind::Boolean))
return false;
break;
case JSOP_TRUE:
if (!pushTempFromImm(1, MinimalValueKind::Boolean))
return false;
break;
case JSOP_NULL:
if (!pushTempFromImm(0, MinimalValueKind::Null))
return false;
break;
case JSOP_UNDEFINED:
if (!pushTempFromImm(0, MinimalValueKind::Undefined))
return false;
break;
case JSOP_INT8:
if (!pushTempFromImm(GET_INT8(pc)))
if (!pushTempFromImm(GET_INT8(pc), MinimalValueKind::Int32))
return false;
break;
case JSOP_INT32:
if (!pushTempFromImm(GET_INT32(pc)))
if (!pushTempFromImm(GET_INT32(pc), MinimalValueKind::Int32))
return false;
break;
case JSOP_POP:
@ -428,12 +553,24 @@ class MinimalLoongArchCompiler
case JSOP_SWAP: {
if (stackDepth_ < 2)
return false;
LoongArchReg tmp = stack_[stackDepth_ - 1];
StackValue tmp = stack_[stackDepth_ - 1];
stack_[stackDepth_ - 1] = stack_[stackDepth_ - 2];
stack_[stackDepth_ - 2] = tmp;
break;
}
case JSOP_TONUMERIC:
case JSOP_POS:
if (!stackDepth_ || !IsNumericKind(stack_[stackDepth_ - 1].kind))
return false;
stack_[stackDepth_ - 1].kind = MinimalValueKind::Int32;
break;
case JSOP_VOID:
if (!stackDepth_)
return false;
if (!emitLoadImm32(stack_[stackDepth_ - 1].reg, 0))
return false;
stack_[stackDepth_ - 1].kind = MinimalValueKind::Undefined;
break;
case JSOP_CHECKLEXICAL:
case JSOP_LOOPENTRY:
case JSOP_JUMPTARGET:
@ -441,46 +578,141 @@ class MinimalLoongArchCompiler
break;
case JSOP_ADD:
case JSOP_SUB: {
LoongArchReg rhs;
LoongArchReg lhs;
StackValue rhs;
StackValue lhs;
if (!pop(&rhs) || !pop(&lhs))
return false;
LoongArchReg out = allocStackReg();
if (!emitCheckedBinary(op, lhs, rhs, out))
if (!IsNumericKind(lhs.kind) || !IsNumericKind(rhs.kind))
return false;
stack_[stackDepth_++] = out;
LoongArchReg out = allocStackReg();
if (!emitCheckedBinary(op, lhs.reg, rhs.reg, out))
return false;
stack_[stackDepth_++] = { out, MinimalValueKind::Int32 };
break;
}
case JSOP_INC:
case JSOP_DEC:
if (!stackDepth_)
if (!stackDepth_ || !IsNumericKind(stack_[stackDepth_ - 1].kind))
return false;
if (!emitCheckedIncDec(op, stack_[stackDepth_ - 1]))
if (!emitCheckedIncDec(op, stack_[stackDepth_ - 1].reg))
return false;
stack_[stackDepth_ - 1].kind = MinimalValueKind::Int32;
break;
case JSOP_LT: {
LoongArchReg rhs;
LoongArchReg lhs;
case JSOP_LT:
case JSOP_LE:
case JSOP_GT:
case JSOP_GE: {
StackValue rhs;
StackValue lhs;
if (!pop(&rhs) || !pop(&lhs))
return false;
if (!IsNumericKind(lhs.kind) || !IsNumericKind(rhs.kind))
return false;
LoongArchReg out = allocStackReg();
if (!emitCompare(op, lhs.reg, rhs.reg, out))
return false;
stack_[stackDepth_++] = { out, MinimalValueKind::Boolean };
break;
}
case JSOP_EQ:
case JSOP_NE:
case JSOP_STRICTEQ:
case JSOP_STRICTNE: {
StackValue rhs;
StackValue lhs;
if (!pop(&rhs) || !pop(&lhs))
return false;
LoongArchReg out = allocStackReg();
if (!emitCompareLessThan(lhs, rhs, out))
return false;
stack_[stackDepth_++] = out;
bool invert = op == JSOP_NE || op == JSOP_STRICTNE;
bool strict = op == JSOP_STRICTEQ || op == JSOP_STRICTNE;
if (IsNumericKind(lhs.kind) && IsNumericKind(rhs.kind)) {
if (!emitEq(lhs.reg, rhs.reg, out, invert))
return false;
} else {
bool equal = false;
if (strict) {
equal = lhs.kind == rhs.kind &&
(lhs.kind == MinimalValueKind::Null ||
lhs.kind == MinimalValueKind::Undefined);
} else {
equal = (lhs.kind == rhs.kind &&
(lhs.kind == MinimalValueKind::Null ||
lhs.kind == MinimalValueKind::Undefined)) ||
((lhs.kind == MinimalValueKind::Null &&
rhs.kind == MinimalValueKind::Undefined) ||
(lhs.kind == MinimalValueKind::Undefined &&
rhs.kind == MinimalValueKind::Null));
}
if (!emitLoadImm32(out, invert ? !equal : equal))
return false;
}
stack_[stackDepth_++] = { out, MinimalValueKind::Boolean };
break;
}
case JSOP_GOTO:
if (!emitBranch(BranchKind::Always, zero, uint32_t(int32_t(pcOffset) + GET_JUMP_OFFSET(pc))))
case JSOP_NOT: {
StackValue input;
if (!pop(&input))
return false;
LoongArchReg out = allocStackReg();
if (!emitNot(input.reg, out))
return false;
stack_[stackDepth_++] = { out, MinimalValueKind::Boolean };
break;
}
case JSOP_BITAND:
case JSOP_BITOR:
case JSOP_BITXOR: {
StackValue rhs;
StackValue lhs;
if (!pop(&rhs) || !pop(&lhs))
return false;
if (!IsNumericKind(lhs.kind) || !IsNumericKind(rhs.kind))
return false;
uint32_t opcode = 0;
switch (op) {
case JSOP_BITAND:
opcode = 0x00148000;
break;
case JSOP_BITOR:
opcode = 0x00150000;
break;
case JSOP_BITXOR:
opcode = 0x00158000;
break;
default:
MOZ_CRASH("unexpected bitwise opcode");
}
LoongArchReg out = allocStackReg();
if (!emitBitBinary(opcode, lhs.reg, rhs.reg, out))
return false;
stack_[stackDepth_++] = { out, MinimalValueKind::Int32 };
break;
}
case JSOP_BITNOT:
if (!stackDepth_ || !IsNumericKind(stack_[stackDepth_ - 1].kind))
return false;
if (!emitBitNot(stack_[stackDepth_ - 1].reg, stack_[stackDepth_ - 1].reg))
return false;
stack_[stackDepth_ - 1].kind = MinimalValueKind::Int32;
break;
case JSOP_GOTO:
if (!emitBranch(BranchKind::Always, zero,
uint32_t(int32_t(pcOffset) + GET_JUMP_OFFSET(pc))))
{
return false;
}
break;
case JSOP_IFEQ:
case JSOP_IFNE: {
LoongArchReg cond;
StackValue cond;
if (!pop(&cond))
return false;
BranchKind kind = (op == JSOP_IFNE) ? BranchKind::IfTrue : BranchKind::IfFalse;
if (!emitBranch(kind, cond, uint32_t(int32_t(pcOffset) + GET_JUMP_OFFSET(pc))))
if (!emitBranch(kind, cond.reg,
uint32_t(int32_t(pcOffset) + GET_JUMP_OFFSET(pc))))
{
return false;
}
break;
}
case JSOP_RETURN:
@ -699,6 +931,23 @@ CanUseMinimalJit(RunState& state, InvokeState& invoke)
return CanUseMinimalJit(state.script(), invoke.args());
}
static Value
MinimalJitResultToValue(const MinimalJitResult& result)
{
switch (result.kind) {
case MinimalValueKind::Int32:
return JS::Int32Value(int32_t(result.payload));
case MinimalValueKind::Boolean:
return JS::BooleanValue(bool(result.payload));
case MinimalValueKind::Undefined:
return JS::UndefinedValue();
case MinimalValueKind::Null:
return JS::NullValue();
}
MOZ_CRASH("bad minimal JIT value kind");
}
static bool
LookupOrCompileMinimalJit(JSContext* cx, JSScript* script, TinyLoongArchJitCode* fnOut)
{
@ -746,13 +995,13 @@ TryCallLoongArchMinimalJit(JSContext* cx, HandleFunction fun, const CallArgs& ar
if (!LookupOrCompileMinimalJit(cx, script, &fn))
return true;
int32_t result = 0;
MinimalJitResult result = { 0, MinimalValueKind::Int32 };
int32_t arg0 = script->numArgs() >= 1 ? args[0].toInt32() : 0;
int32_t arg1 = script->numArgs() >= 2 ? args[1].toInt32() : 0;
if (!fn(arg0, arg1, &result))
return true;
args.rval().setInt32(result);
args.rval().set(MinimalJitResultToValue(result));
*handled = true;
return true;
}
@ -779,15 +1028,13 @@ TryEnterLoongArchMinimalJit(JSContext* cx, RunState& state)
if (!LookupOrCompileMinimalJit(cx, state.script(), &fn))
return false;
int32_t result = 0;
MinimalJitResult result = { 0, MinimalValueKind::Int32 };
int32_t arg0 = state.script()->numArgs() >= 1 ? invoke.args()[0].toInt32() : 0;
int32_t arg1 = state.script()->numArgs() >= 2 ? invoke.args()[1].toInt32() : 0;
if (!fn(arg0, arg1, &result))
return false;
Value value;
value.setInt32(result);
state.setReturnValue(value);
state.setReturnValue(MinimalJitResultToValue(result));
return true;
}