diff --git a/js/src/jit-test/tests/basic/loongarch-minimal-jit.js b/js/src/jit-test/tests/basic/loongarch-minimal-jit.js index 3e4a3afd7c..fd06ba3184 100644 --- a/js/src/jit-test/tests/basic/loongarch-minimal-jit.js +++ b/js/src/jit-test/tests/basic/loongarch-minimal-jit.js @@ -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; } diff --git a/js/src/jit/LoongArchMinimalJit.cpp b/js/src/jit/LoongArchMinimalJit.cpp index 32a93225b5..88a5c3192a 100644 --- a/js/src/jit/LoongArchMinimalJit.cpp +++ b/js/src/jit/LoongArchMinimalJit.cpp @@ -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 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; }