Compare commits
No commits in common. "7f2f99524f87c7985b4f331ec0bfc72b994de916" and "fd2da1efd3405856a4d241ce8a35139df844a5f2" have entirely different histories.
7f2f99524f
...
fd2da1efd3
@ -86,7 +86,6 @@ class BytecodeCompiler(
|
|||||||
private val loopVarSlots = HashSet<Int>()
|
private val loopVarSlots = HashSet<Int>()
|
||||||
private val loopStack = ArrayDeque<LoopContext>()
|
private val loopStack = ArrayDeque<LoopContext>()
|
||||||
private var currentPos: Pos? = null
|
private var currentPos: Pos? = null
|
||||||
private var cachedVoidSlot: Int? = null
|
|
||||||
|
|
||||||
private data class LoopContext(
|
private data class LoopContext(
|
||||||
val label: String?,
|
val label: String?,
|
||||||
@ -1375,6 +1374,23 @@ class BytecodeCompiler(
|
|||||||
val rightRef = binaryRight(ref)
|
val rightRef = binaryRight(ref)
|
||||||
var a = compileRefWithFallback(leftRef, null, refPos(ref)) ?: return null
|
var a = compileRefWithFallback(leftRef, null, refPos(ref)) ?: return null
|
||||||
var b = compileRefWithFallback(rightRef, null, refPos(ref)) ?: return null
|
var b = compileRefWithFallback(rightRef, null, refPos(ref)) ?: return null
|
||||||
|
if (op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)) {
|
||||||
|
val leftNeedsObj = a.type == SlotType.INT && b.type == SlotType.REAL
|
||||||
|
val rightNeedsObj = b.type == SlotType.INT && a.type == SlotType.REAL
|
||||||
|
if (leftNeedsObj || rightNeedsObj) {
|
||||||
|
val leftObj = if (leftNeedsObj) {
|
||||||
|
compileScopeSlotObj(leftRef) ?: a
|
||||||
|
} else {
|
||||||
|
a
|
||||||
|
}
|
||||||
|
val rightObj = if (rightNeedsObj) {
|
||||||
|
compileScopeSlotObj(rightRef) ?: b
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
return compileObjBinaryOp(leftRef, leftObj, rightObj, op, refPos(ref))
|
||||||
|
}
|
||||||
|
}
|
||||||
val intOps = setOf(
|
val intOps = setOf(
|
||||||
BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
|
BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
|
||||||
BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
|
BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
|
||||||
@ -1397,11 +1413,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
val typesMismatch = a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN
|
val typesMismatch = a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN
|
||||||
val allowMixedNumeric = op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH)
|
val allowMixedNumeric = op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH)
|
||||||
val isMixedNumeric = (a.type == SlotType.INT && b.type == SlotType.REAL) ||
|
if (typesMismatch && op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT)) {
|
||||||
(a.type == SlotType.REAL && b.type == SlotType.INT)
|
|
||||||
if (typesMismatch && op in setOf(BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT) &&
|
|
||||||
!(allowMixedNumeric && isMixedNumeric)
|
|
||||||
) {
|
|
||||||
return compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
return compileObjBinaryOp(leftRef, a, b, op, refPos(ref))
|
||||||
}
|
}
|
||||||
if ((a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) &&
|
if ((a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) &&
|
||||||
@ -1671,12 +1683,12 @@ class BytecodeCompiler(
|
|||||||
val left = ensureObjSlot(a)
|
val left = ensureObjSlot(a)
|
||||||
val right = ensureObjSlot(b)
|
val right = ensureObjSlot(b)
|
||||||
val opcode = when {
|
val opcode = when {
|
||||||
isExactNonNullSlotClassOrTemp(left.slot, ObjString.type) &&
|
isExactNonNullSlotClass(left.slot, ObjString.type) &&
|
||||||
isExactNonNullSlotClassOrTemp(right.slot, ObjString.type) -> stringOp
|
isExactNonNullSlotClass(right.slot, ObjString.type) -> stringOp
|
||||||
isExactNonNullSlotClassOrTemp(left.slot, ObjInt.type) &&
|
isExactNonNullSlotClass(left.slot, ObjInt.type) &&
|
||||||
isExactNonNullSlotClassOrTemp(right.slot, ObjInt.type) -> intOp
|
isExactNonNullSlotClass(right.slot, ObjInt.type) -> intOp
|
||||||
isExactNonNullSlotClassOrTemp(left.slot, ObjReal.type) &&
|
isExactNonNullSlotClass(left.slot, ObjReal.type) &&
|
||||||
isExactNonNullSlotClassOrTemp(right.slot, ObjReal.type) -> realOp
|
isExactNonNullSlotClass(right.slot, ObjReal.type) -> realOp
|
||||||
else -> objOp
|
else -> objOp
|
||||||
}
|
}
|
||||||
builder.emit(opcode, left.slot, right.slot, out)
|
builder.emit(opcode, left.slot, right.slot, out)
|
||||||
@ -2340,36 +2352,60 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun compileLogicalAnd(ref: LogicalAndRef): CompiledValue? {
|
private fun compileLogicalAnd(ref: LogicalAndRef): CompiledValue? {
|
||||||
val leftValue = compileRefWithFallback(ref.left(), SlotType.BOOL, Pos.builtIn) ?: return null
|
val leftValue = compileRefWithFallback(ref.left(), SlotType.BOOL, Pos.builtIn) ?: return null
|
||||||
if (leftValue.type != SlotType.BOOL) return null
|
val leftBool = if (leftValue.type == SlotType.BOOL) {
|
||||||
|
leftValue
|
||||||
|
} else {
|
||||||
|
val slot = allocSlot()
|
||||||
|
builder.emit(Opcode.OBJ_TO_BOOL, leftValue.slot, slot)
|
||||||
|
CompiledValue(slot, SlotType.BOOL)
|
||||||
|
}
|
||||||
val resultSlot = allocSlot()
|
val resultSlot = allocSlot()
|
||||||
val falseId = builder.addConst(BytecodeConst.Bool(false))
|
val falseId = builder.addConst(BytecodeConst.Bool(false))
|
||||||
builder.emit(Opcode.CONST_BOOL, falseId, resultSlot)
|
builder.emit(Opcode.CONST_BOOL, falseId, resultSlot)
|
||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_FALSE,
|
Opcode.JMP_IF_FALSE,
|
||||||
listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(endLabel))
|
listOf(CmdBuilder.Operand.IntVal(leftBool.slot), CmdBuilder.Operand.LabelRef(endLabel))
|
||||||
)
|
)
|
||||||
val rightValue = compileRefWithFallback(ref.right(), SlotType.BOOL, Pos.builtIn) ?: return null
|
val rightValue = compileRefWithFallback(ref.right(), SlotType.BOOL, Pos.builtIn) ?: return null
|
||||||
if (rightValue.type != SlotType.BOOL) return null
|
val rightBool = if (rightValue.type == SlotType.BOOL) {
|
||||||
builder.emit(Opcode.MOVE_BOOL, rightValue.slot, resultSlot)
|
rightValue
|
||||||
|
} else {
|
||||||
|
val slot = allocSlot()
|
||||||
|
builder.emit(Opcode.OBJ_TO_BOOL, rightValue.slot, slot)
|
||||||
|
CompiledValue(slot, SlotType.BOOL)
|
||||||
|
}
|
||||||
|
builder.emit(Opcode.MOVE_BOOL, rightBool.slot, resultSlot)
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
return CompiledValue(resultSlot, SlotType.BOOL)
|
return CompiledValue(resultSlot, SlotType.BOOL)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileLogicalOr(ref: LogicalOrRef): CompiledValue? {
|
private fun compileLogicalOr(ref: LogicalOrRef): CompiledValue? {
|
||||||
val leftValue = compileRefWithFallback(ref.left(), SlotType.BOOL, Pos.builtIn) ?: return null
|
val leftValue = compileRefWithFallback(ref.left(), SlotType.BOOL, Pos.builtIn) ?: return null
|
||||||
if (leftValue.type != SlotType.BOOL) return null
|
val leftBool = if (leftValue.type == SlotType.BOOL) {
|
||||||
|
leftValue
|
||||||
|
} else {
|
||||||
|
val slot = allocSlot()
|
||||||
|
builder.emit(Opcode.OBJ_TO_BOOL, leftValue.slot, slot)
|
||||||
|
CompiledValue(slot, SlotType.BOOL)
|
||||||
|
}
|
||||||
val resultSlot = allocSlot()
|
val resultSlot = allocSlot()
|
||||||
val trueId = builder.addConst(BytecodeConst.Bool(true))
|
val trueId = builder.addConst(BytecodeConst.Bool(true))
|
||||||
builder.emit(Opcode.CONST_BOOL, trueId, resultSlot)
|
builder.emit(Opcode.CONST_BOOL, trueId, resultSlot)
|
||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_TRUE,
|
Opcode.JMP_IF_TRUE,
|
||||||
listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(endLabel))
|
listOf(CmdBuilder.Operand.IntVal(leftBool.slot), CmdBuilder.Operand.LabelRef(endLabel))
|
||||||
)
|
)
|
||||||
val rightValue = compileRefWithFallback(ref.right(), SlotType.BOOL, Pos.builtIn) ?: return null
|
val rightValue = compileRefWithFallback(ref.right(), SlotType.BOOL, Pos.builtIn) ?: return null
|
||||||
if (rightValue.type != SlotType.BOOL) return null
|
val rightBool = if (rightValue.type == SlotType.BOOL) {
|
||||||
builder.emit(Opcode.MOVE_BOOL, rightValue.slot, resultSlot)
|
rightValue
|
||||||
|
} else {
|
||||||
|
val slot = allocSlot()
|
||||||
|
builder.emit(Opcode.OBJ_TO_BOOL, rightValue.slot, slot)
|
||||||
|
CompiledValue(slot, SlotType.BOOL)
|
||||||
|
}
|
||||||
|
builder.emit(Opcode.MOVE_BOOL, rightBool.slot, resultSlot)
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
return CompiledValue(resultSlot, SlotType.BOOL)
|
return CompiledValue(resultSlot, SlotType.BOOL)
|
||||||
}
|
}
|
||||||
@ -3533,11 +3569,6 @@ class BytecodeCompiler(
|
|||||||
val elementClass = listElementClassBySlot[receiver.slot] ?: listElementClassFromReceiverRef(ref.targetRef)
|
val elementClass = listElementClassBySlot[receiver.slot] ?: listElementClassFromReceiverRef(ref.targetRef)
|
||||||
if (elementClass != null) {
|
if (elementClass != null) {
|
||||||
slotObjClass[dst] = elementClass
|
slotObjClass[dst] = elementClass
|
||||||
if (elementClass == ObjString.type && elementClass.isClosed) {
|
|
||||||
stableObjSlots.add(dst)
|
|
||||||
} else {
|
|
||||||
stableObjSlots.remove(dst)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
@ -3604,12 +3635,6 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
SlotType.OBJ -> {
|
SlotType.OBJ -> {
|
||||||
if (objOp == null) return null
|
if (objOp == null) return null
|
||||||
if (isExactNonNullSlotClassOrTemp(rhs.slot, ObjInt.type)) {
|
|
||||||
val right = allocSlot()
|
|
||||||
builder.emit(Opcode.UNBOX_INT_OBJ, rhs.slot, right)
|
|
||||||
builder.emit(intOp, out, right, out)
|
|
||||||
return CompiledValue(out, SlotType.INT)
|
|
||||||
}
|
|
||||||
val leftObj = allocSlot()
|
val leftObj = allocSlot()
|
||||||
builder.emit(Opcode.BOX_OBJ, out, leftObj)
|
builder.emit(Opcode.BOX_OBJ, out, leftObj)
|
||||||
updateSlotType(leftObj, SlotType.OBJ)
|
updateSlotType(leftObj, SlotType.OBJ)
|
||||||
@ -3635,12 +3660,6 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
SlotType.OBJ -> {
|
SlotType.OBJ -> {
|
||||||
if (objOp == null) return null
|
if (objOp == null) return null
|
||||||
if (isExactNonNullSlotClassOrTemp(rhs.slot, ObjReal.type)) {
|
|
||||||
val right = allocSlot()
|
|
||||||
builder.emit(Opcode.UNBOX_REAL_OBJ, rhs.slot, right)
|
|
||||||
builder.emit(realOp, out, right, out)
|
|
||||||
return CompiledValue(out, SlotType.REAL)
|
|
||||||
}
|
|
||||||
val leftObj = allocSlot()
|
val leftObj = allocSlot()
|
||||||
builder.emit(Opcode.BOX_OBJ, out, leftObj)
|
builder.emit(Opcode.BOX_OBJ, out, leftObj)
|
||||||
updateSlotType(leftObj, SlotType.OBJ)
|
updateSlotType(leftObj, SlotType.OBJ)
|
||||||
@ -5641,17 +5660,6 @@ class BytecodeCompiler(
|
|||||||
private fun emitInlineBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? =
|
private fun emitInlineBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? =
|
||||||
emitInlineStatements(stmt.statements(), needResult)
|
emitInlineStatements(stmt.statements(), needResult)
|
||||||
|
|
||||||
private fun ensureVoidSlot(): Int {
|
|
||||||
val existing = cachedVoidSlot
|
|
||||||
if (existing != null) return existing
|
|
||||||
val slot = allocSlot()
|
|
||||||
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
|
||||||
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
|
||||||
updateSlotType(slot, SlotType.OBJ)
|
|
||||||
cachedVoidSlot = slot
|
|
||||||
return slot
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun shouldInlineBlock(stmt: BlockStatement): Boolean {
|
private fun shouldInlineBlock(stmt: BlockStatement): Boolean {
|
||||||
return allowLocalSlots
|
return allowLocalSlots
|
||||||
}
|
}
|
||||||
@ -5994,11 +6002,6 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val needsBreakFlag = stmt.canBreak || stmt.elseStatement != null
|
val needsBreakFlag = stmt.canBreak || stmt.elseStatement != null
|
||||||
val realWidenSlots = collectLoopRealWidenSlots(stmt.body)
|
|
||||||
val hasRealWiden = realWidenSlots.isNotEmpty()
|
|
||||||
if (hasRealWiden) {
|
|
||||||
applySlotTypes(realWidenSlots, SlotType.REAL)
|
|
||||||
}
|
|
||||||
val breakFlagSlot = allocSlot()
|
val breakFlagSlot = allocSlot()
|
||||||
if (range == null && rangeRef == null && typedRangeLocal == null) {
|
if (range == null && rangeRef == null && typedRangeLocal == null) {
|
||||||
val sourceValue = compileStatementValueOrFallback(stmt.source) ?: return null
|
val sourceValue = compileStatementValueOrFallback(stmt.source) ?: return null
|
||||||
@ -6072,18 +6075,12 @@ class BytecodeCompiler(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
|
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
|
||||||
if (hasRealWiden) {
|
|
||||||
applySlotTypes(realWidenSlots, SlotType.UNKNOWN)
|
|
||||||
}
|
|
||||||
loopStack.removeLast()
|
loopStack.removeLast()
|
||||||
if (wantResult) {
|
if (wantResult) {
|
||||||
val bodyObj = ensureObjSlot(bodyValue)
|
val bodyObj = ensureObjSlot(bodyValue)
|
||||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
|
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
|
||||||
}
|
}
|
||||||
builder.mark(continueLabel)
|
builder.mark(continueLabel)
|
||||||
if (hasRealWiden) {
|
|
||||||
emitLoopRealCoercions(realWidenSlots)
|
|
||||||
}
|
|
||||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
||||||
|
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
@ -6191,9 +6188,6 @@ class BytecodeCompiler(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
|
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
|
||||||
if (hasRealWiden) {
|
|
||||||
applySlotTypes(realWidenSlots, SlotType.UNKNOWN)
|
|
||||||
}
|
|
||||||
loopStack.removeLast()
|
loopStack.removeLast()
|
||||||
if (wantResult) {
|
if (wantResult) {
|
||||||
val bodyObj = ensureObjSlot(bodyValue)
|
val bodyObj = ensureObjSlot(bodyValue)
|
||||||
@ -6201,9 +6195,6 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
builder.mark(continueLabel)
|
builder.mark(continueLabel)
|
||||||
builder.emit(Opcode.INC_INT, iSlot)
|
builder.emit(Opcode.INC_INT, iSlot)
|
||||||
if (hasRealWiden) {
|
|
||||||
emitLoopRealCoercions(realWidenSlots)
|
|
||||||
}
|
|
||||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
||||||
|
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
@ -6274,9 +6265,6 @@ class BytecodeCompiler(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
|
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
|
||||||
if (hasRealWiden) {
|
|
||||||
applySlotTypes(realWidenSlots, SlotType.UNKNOWN)
|
|
||||||
}
|
|
||||||
loopStack.removeLast()
|
loopStack.removeLast()
|
||||||
if (wantResult) {
|
if (wantResult) {
|
||||||
val bodyObj = ensureObjSlot(bodyValue)
|
val bodyObj = ensureObjSlot(bodyValue)
|
||||||
@ -6284,9 +6272,6 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
builder.mark(continueLabel)
|
builder.mark(continueLabel)
|
||||||
builder.emit(Opcode.INC_INT, iSlot)
|
builder.emit(Opcode.INC_INT, iSlot)
|
||||||
if (hasRealWiden) {
|
|
||||||
emitLoopRealCoercions(realWidenSlots)
|
|
||||||
}
|
|
||||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
||||||
|
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
@ -6346,9 +6331,6 @@ class BytecodeCompiler(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
|
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
|
||||||
if (hasRealWiden) {
|
|
||||||
applySlotTypes(realWidenSlots, SlotType.UNKNOWN)
|
|
||||||
}
|
|
||||||
loopStack.removeLast()
|
loopStack.removeLast()
|
||||||
if (wantResult) {
|
if (wantResult) {
|
||||||
val bodyObj = ensureObjSlot(bodyValue)
|
val bodyObj = ensureObjSlot(bodyValue)
|
||||||
@ -6478,7 +6460,10 @@ class BytecodeCompiler(
|
|||||||
restoreFlowTypeOverride(elseRestore)
|
restoreFlowTypeOverride(elseRestore)
|
||||||
}
|
}
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
return CompiledValue(ensureVoidSlot(), SlotType.OBJ)
|
val slot = allocSlot()
|
||||||
|
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||||
|
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
||||||
|
return CompiledValue(slot, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSlotTypeByName(name: String, type: SlotType) {
|
private fun updateSlotTypeByName(name: String, type: SlotType) {
|
||||||
|
|||||||
@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import net.sergeych.lyng.Benchmarks
|
|
||||||
import net.sergeych.lyng.Script
|
|
||||||
import net.sergeych.lyng.obj.ObjInt
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.time.TimeSource
|
|
||||||
|
|
||||||
class MixedCompareBenchmarkTest {
|
|
||||||
@Test
|
|
||||||
fun benchmarkMixedCompareOps() = runTest {
|
|
||||||
if (!Benchmarks.enabled) return@runTest
|
|
||||||
val iterations = 200000
|
|
||||||
val script = """
|
|
||||||
fun mixedCompareBench(n) {
|
|
||||||
var acc = 0
|
|
||||||
var r = 0.0
|
|
||||||
val strs = ["a","b","aa","bb","abc","abd","zzz",""]
|
|
||||||
var i = 0
|
|
||||||
while(i < n) {
|
|
||||||
val si = strs[i % 8]
|
|
||||||
if( si == "a" ) acc += 1 else acc -= 1
|
|
||||||
if( si != "zzz" ) acc += 2
|
|
||||||
if( si == "" ) acc += 3
|
|
||||||
|
|
||||||
if( i < (i % 5) ) acc += 1 else acc -= 1
|
|
||||||
if( (i % 3) == 0 ) acc += 2
|
|
||||||
|
|
||||||
val r1 = i + 0.5
|
|
||||||
if( r1 > i ) acc += 1
|
|
||||||
if( i < r1 ) acc += 1
|
|
||||||
r += r1 * 0.25
|
|
||||||
if( r > 1000.0 ) acc += 1
|
|
||||||
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
acc
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(script)
|
|
||||||
val expected = expectedValue(iterations)
|
|
||||||
|
|
||||||
val start = TimeSource.Monotonic.markNow()
|
|
||||||
val result = scope.eval("mixedCompareBench($iterations)") as ObjInt
|
|
||||||
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
|
||||||
println("[DEBUG_LOG] [BENCH] mixed-compare elapsed=${elapsedMs} ms")
|
|
||||||
assertEquals(expected, result.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun expectedValue(iterations: Int): Long {
|
|
||||||
val strs = arrayOf("a", "b", "aa", "bb", "abc", "abd", "zzz", "")
|
|
||||||
var acc = 0L
|
|
||||||
var r = 0.0
|
|
||||||
var i = 0
|
|
||||||
while (i < iterations) {
|
|
||||||
val si = strs[i % 8]
|
|
||||||
if (si == "a") acc += 1 else acc -= 1
|
|
||||||
if (si != "zzz") acc += 2
|
|
||||||
if (si == "") acc += 3
|
|
||||||
|
|
||||||
if (i < (i % 5)) acc += 1 else acc -= 1
|
|
||||||
if ((i % 3) == 0) acc += 2
|
|
||||||
|
|
||||||
val r1 = i + 0.5
|
|
||||||
if (r1 > i) acc += 1
|
|
||||||
if (i < r1) acc += 1
|
|
||||||
r += r1 * 0.25
|
|
||||||
if (r > 1000.0) acc += 1
|
|
||||||
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1258,28 +1258,6 @@ class ScriptTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testForLoopRealWidenDisasm() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun widenFor() {
|
|
||||||
var acc = 0
|
|
||||||
for(i in 0..3) {
|
|
||||||
if (i == 1) acc = 0.5
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("widenFor")
|
|
||||||
println("[DEBUG_LOG] widenFor disasm:\n$disasm")
|
|
||||||
val incIndex = disasm.indexOf("INC_INT")
|
|
||||||
assertTrue(incIndex >= 0, "expected INC_INT in for-loop disasm")
|
|
||||||
val convIndex = disasm.indexOf("INT_TO_REAL")
|
|
||||||
assertTrue(convIndex >= 0, "expected INT_TO_REAL in for-loop disasm")
|
|
||||||
assertTrue(convIndex > incIndex, "INT_TO_REAL should appear after INC_INT")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIntClosedRangeInclusive() = runTest {
|
fun testIntClosedRangeInclusive() = runTest {
|
||||||
eval(
|
eval(
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
# Fast Ops Optimizations Plan (Draft)
|
|
||||||
|
|
||||||
Baseline
|
|
||||||
- See `notes/nested_range_baseline.md`
|
|
||||||
|
|
||||||
Candidates (not started)
|
|
||||||
1) Primitive comparisons (done)
|
|
||||||
- Emit fast CMP variants for known ObjString/ObjInt/ObjReal using temp/stable slots.
|
|
||||||
- MixedCompareBenchmarkTest: 374 ms -> 347 ms.
|
|
||||||
2) Mixed numeric ops (done)
|
|
||||||
- Allow INT+REAL arithmetic to use primitive REAL ops (no obj fallback).
|
|
||||||
- MixedCompareBenchmarkTest: 347 ms -> 275 ms.
|
|
||||||
3) Boolean conversion (done; do not revert without review)
|
|
||||||
- Skip redundant OBJ_TO_BOOL in logical AND/OR when compiler already emits BOOL.
|
|
||||||
- MixedCompareBenchmarkTest: 275 ms -> 249 ms.
|
|
||||||
4) Range/loop hot path (done)
|
|
||||||
- Reuse a cached ObjVoid slot for if-statements in statement context (avoids per-iteration CONST_OBJ).
|
|
||||||
- MixedCompareBenchmarkTest: 249 ms -> 247 ms.
|
|
||||||
5) String ops (done)
|
|
||||||
- Mark GET_INDEX results as stable only for closed ObjString elements to enable fast compares.
|
|
||||||
- MixedCompareBenchmarkTest: 247 ms -> 240 ms.
|
|
||||||
6) Box/unbox audit (done)
|
|
||||||
- Unbox ObjInt/ObjReal in assign-op when target is INT/REAL to avoid boxing + obj ops.
|
|
||||||
- MixedCompareBenchmarkTest: 240 ms -> 234 ms.
|
|
||||||
7) Mixed compare coverage
|
|
||||||
- Emit CMP_*_REAL when one operand is known ObjReal in more expression forms (not just assign-op).
|
|
||||||
- Verify with disassembly that fast cmp opcodes are emitted.
|
|
||||||
8) Range-loop invariant hoist
|
|
||||||
- Cache range end/step into temps once per loop; avoid repeated slot reads/boxing in body.
|
|
||||||
- Confirm no extra CONST_OBJ in hot path.
|
|
||||||
9) Boxing elision pass
|
|
||||||
- Remove redundant BOX_OBJ when value feeds only primitive ops afterward (local liveness).
|
|
||||||
- Ensure no impact on closures/escaping values.
|
|
||||||
10) Closed-type fast paths expansion
|
|
||||||
- Apply closed-type trust for ObjBool/ObjInt/ObjReal/ObjString in ternaries and conditional chains.
|
|
||||||
- Guard with exact non-null temp/slot checks only.
|
|
||||||
11) VM hot op micro-optimizations
|
|
||||||
- Reduce frame reads/writes in ADD_INT, MUL_REAL, CMP_*_INT/REAL when operands are temps.
|
|
||||||
- Compare against baseline; revert if regression after 10-run median.
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
# Mixed Compare Benchmark Baseline
|
|
||||||
|
|
||||||
Date: 2026-02-16
|
|
||||||
|
|
||||||
Benchmark:
|
|
||||||
- MixedCompareBenchmarkTest.benchmarkMixedCompareOps
|
|
||||||
|
|
||||||
Command:
|
|
||||||
`BENCHMARKS=true timeout 20s ./gradlew :lynglib:jvmTest --tests MixedCompareBenchmarkTest --rerun-tasks`
|
|
||||||
|
|
||||||
Result:
|
|
||||||
- mixed-compare elapsed=374 ms
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
# Nested Range Benchmark Baseline
|
|
||||||
|
|
||||||
Date: 2026-02-16
|
|
||||||
|
|
||||||
Command:
|
|
||||||
`BENCHMARKS=true timeout 20s ./gradlew :lynglib:jvmTest --tests NestedRangeBenchmarkTest --rerun-tasks`
|
|
||||||
|
|
||||||
Result:
|
|
||||||
- nested-happy elapsed=56 ms
|
|
||||||
Loading…
x
Reference in New Issue
Block a user