From 460765fc800f7c9730e9f8d90b9edad14419958e Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Thu, 23 Apr 2026 03:26:35 -0400 Subject: [PATCH] Issue #3049 - Track LoongArch minimal JIT control flow state --- .../tests/basic/loongarch-minimal-jit.js | 48 +++++++ js/src/jit/LoongArchMinimalJit.cpp | 128 ++++++++++++++++-- 2 files changed, 165 insertions(+), 11 deletions(-) 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 71e3ff9ca0..5b07c7bb93 100644 --- a/js/src/jit-test/tests/basic/loongarch-minimal-jit.js +++ b/js/src/jit-test/tests/basic/loongarch-minimal-jit.js @@ -158,6 +158,40 @@ function conditional(a, b) { return false; } +function logicalOr(a, b) { + return a || b; +} + +function logicalAnd(a, b) { + return a && b; +} + +function logicalBoolOr(a, b) { + return (a < b) || (b < a); +} + +function logicalBoolAnd(a, b) { + return (a < b) && (b < 10); +} + +function minValue(a, b) { + return a < b ? a : b; +} + +function chooseOrder(a, b) { + return a < b ? true : false; +} + +function sumRange(n) { + var i = 0; + var total = 0; + while (i < n) { + total = total + i; + i = i + 1; + } + return total; +} + for (var i = 0; i < 100; i++) { assertEq(add(i, i + 1), (i + i + 1)); assertEq(withLocal(i, 7), i + 7); @@ -224,6 +258,20 @@ assertEq(unsetLocal(), undefined); assertEq(unsetLooseEqNull(), true); assertEq(conditional(2, 3), true); assertEq(conditional(3, 2), false); +assertEq(logicalOr(0, 7), 7); +assertEq(logicalOr(5, 7), 5); +assertEq(logicalAnd(0, 7), 0); +assertEq(logicalAnd(5, 7), 7); +assertEq(logicalBoolOr(2, 3), true); +assertEq(logicalBoolOr(3, 3), false); +assertEq(logicalBoolAnd(2, 3), true); +assertEq(logicalBoolAnd(3, 2), false); +assertEq(minValue(2, 3), 2); +assertEq(minValue(5, 1), 1); +assertEq(chooseOrder(2, 3), true); +assertEq(chooseOrder(3, 2), false); +assertEq(sumRange(0), 0); +assertEq(sumRange(5), 10); (function testPropertyWrappers() { function getX(obj) { return obj.x; } diff --git a/js/src/jit/LoongArchMinimalJit.cpp b/js/src/jit/LoongArchMinimalJit.cpp index ff8d1ef904..98975d6c14 100644 --- a/js/src/jit/LoongArchMinimalJit.cpp +++ b/js/src/jit/LoongArchMinimalJit.cpp @@ -179,6 +179,21 @@ struct BranchPatch LoongArchReg reg; }; +struct ControlFlowState +{ + bool initialized; + size_t stackDepth; + LoongArchReg stackRegs[mozilla::ArrayLength(StackRegs)]; + MinimalValueKind stackKinds[mozilla::ArrayLength(StackRegs)]; + MinimalValueKind argKinds[mozilla::ArrayLength(ArgRegs)]; + MinimalValueKind localKinds[mozilla::ArrayLength(LocalRegs)]; + + ControlFlowState() + : initialized(false), + stackDepth(0) + { } +}; + class MinimalLoongArchCompiler { struct StackValue @@ -196,8 +211,10 @@ class MinimalLoongArchCompiler MinimalValueKind argKinds_[mozilla::ArrayLength(ArgRegs)]; MinimalValueKind localKinds_[mozilla::ArrayLength(LocalRegs)]; size_t stackDepth_; + bool reachable_; bool sawReturn_; Vector pcToWord_; + Vector pcStates_; Vector branchPatches_; bool emit(uint32_t word) { @@ -207,11 +224,78 @@ class MinimalLoongArchCompiler return true; } + bool restoreState(const ControlFlowState& state) { + stackDepth_ = state.stackDepth; + for (size_t i = 0; i < stackDepth_; i++) { + stack_[i].reg = state.stackRegs[i]; + stack_[i].kind = state.stackKinds[i]; + } + for (size_t i = 0; i < mozilla::ArrayLength(argKinds_); i++) + argKinds_[i] = state.argKinds[i]; + for (size_t i = 0; i < mozilla::ArrayLength(localKinds_); i++) + localKinds_[i] = state.localKinds[i]; + return true; + } + + bool stateMatches(const ControlFlowState& state) const { + if (stackDepth_ != state.stackDepth) + return false; + for (size_t i = 0; i < stackDepth_; i++) { + if (stack_[i].reg != state.stackRegs[i] || + stack_[i].kind != state.stackKinds[i]) + { + return false; + } + } + for (size_t i = 0; i < mozilla::ArrayLength(argKinds_); i++) { + if (argKinds_[i] != state.argKinds[i]) + return false; + } + for (size_t i = 0; i < mozilla::ArrayLength(localKinds_); i++) { + if (localKinds_[i] != state.localKinds[i]) + return false; + } + return true; + } + + bool captureState(ControlFlowState* state) const { + state->initialized = true; + state->stackDepth = stackDepth_; + for (size_t i = 0; i < stackDepth_; i++) { + state->stackRegs[i] = stack_[i].reg; + state->stackKinds[i] = stack_[i].kind; + } + for (size_t i = 0; i < mozilla::ArrayLength(argKinds_); i++) + state->argKinds[i] = argKinds_[i]; + for (size_t i = 0; i < mozilla::ArrayLength(localKinds_); i++) + state->localKinds[i] = localKinds_[i]; + return true; + } + + bool recordTargetState(uint32_t targetPcOffset) { + if (targetPcOffset >= pcStates_.length()) + return false; + + ControlFlowState& state = pcStates_[targetPcOffset]; + if (state.initialized) + return stateMatches(state); + return captureState(&state); + } + bool markBytecode(jsbytecode* pc) { uint32_t offset = uint32_t(pc - script_->code()); MOZ_ASSERT(offset < pcToWord_.length()); pcToWord_[offset] = int32_t(wordCount_); - return true; + + ControlFlowState& state = pcStates_[offset]; + if (!state.initialized) + return true; + + if (reachable_) + return stateMatches(state); + + reachable_ = true; + return restoreState(state); } bool emitMove(LoongArchReg dst, LoongArchReg src) { @@ -614,6 +698,7 @@ class MinimalLoongArchCompiler wordCount_(0), failPatchCount_(0), stackDepth_(0), + reachable_(true), sawReturn_(false) { for (size_t i = 0; i < mozilla::ArrayLength(argKinds_); i++) @@ -628,6 +713,8 @@ class MinimalLoongArchCompiler if (!pcToWord_.appendN(-1, script_->length() + 1)) return false; + if (!pcStates_.appendN(ControlFlowState(), script_->length() + 1)) + return false; for (size_t i = 0; i < script_->nfixed(); i++) { if (!emitLoadImm32(LocalRegs[i], 0)) @@ -640,6 +727,11 @@ class MinimalLoongArchCompiler return false; uint32_t pcOffset = uint32_t(pc - script_->code()); + if (!reachable_) { + pc += GetBytecodeLength(pc); + continue; + } + JSOp op = JSOp(*pc); switch (op) { case JSOP_GETARG: { @@ -763,6 +855,18 @@ class MinimalLoongArchCompiler case JSOP_JUMPTARGET: case JSOP_NOP: break; + case JSOP_OR: + case JSOP_AND: { + if (!stackDepth_) + return false; + uint32_t target = uint32_t(int32_t(pcOffset) + GET_JUMP_OFFSET(pc)); + if (!recordTargetState(target)) + return false; + BranchKind kind = (op == JSOP_OR) ? BranchKind::IfTrue : BranchKind::IfFalse; + if (!emitBranch(kind, stack_[stackDepth_ - 1].reg, target)) + return false; + break; + } case JSOP_ADD: case JSOP_SUB: case JSOP_MUL: @@ -927,24 +1031,26 @@ class MinimalLoongArchCompiler 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)))) - { + case JSOP_GOTO: { + uint32_t target = uint32_t(int32_t(pcOffset) + GET_JUMP_OFFSET(pc)); + if (!recordTargetState(target)) return false; - } + if (!emitBranch(BranchKind::Always, zero, target)) + return false; + reachable_ = false; break; + } case JSOP_IFEQ: case JSOP_IFNE: { StackValue cond; if (!pop(&cond)) return false; - BranchKind kind = (op == JSOP_IFNE) ? BranchKind::IfTrue : BranchKind::IfFalse; - if (!emitBranch(kind, cond.reg, - uint32_t(int32_t(pcOffset) + GET_JUMP_OFFSET(pc)))) - { + uint32_t target = uint32_t(int32_t(pcOffset) + GET_JUMP_OFFSET(pc)); + if (!recordTargetState(target)) + return false; + BranchKind kind = (op == JSOP_IFNE) ? BranchKind::IfTrue : BranchKind::IfFalse; + if (!emitBranch(kind, cond.reg, target)) return false; - } break; } case JSOP_RETURN: