Optimize int loop jumps and document loop var immutability
This commit is contained in:
parent
db9dc73da8
commit
637258581d
@ -63,6 +63,8 @@ In spite of this you can use ranges in for loops:
|
||||
>>> 3
|
||||
>>> void
|
||||
|
||||
The loop variable is read-only inside the loop body (behaves like a `val`).
|
||||
|
||||
but
|
||||
|
||||
for( i in 1..<3 )
|
||||
|
||||
@ -105,6 +105,7 @@ arguments list in almost arbitrary ways. For example:
|
||||
var result = ""
|
||||
for( a in args ) result += a
|
||||
}
|
||||
// loop variables are read-only inside the loop body
|
||||
|
||||
assertEquals(
|
||||
"4231",
|
||||
|
||||
@ -739,6 +739,7 @@ See also: [Testing and Assertions](Testing.md)
|
||||
var result = []
|
||||
for( x in iterable ) result += transform(x)
|
||||
}
|
||||
// loop variables are read-only inside the loop body
|
||||
assert( [11, 21, 31] == mapValues( [1,2,3], { it*10+1 }))
|
||||
>>> void
|
||||
|
||||
|
||||
@ -77,6 +77,8 @@ class BytecodeCompiler(
|
||||
private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>()
|
||||
private val intLoopVarNames = LinkedHashSet<String>()
|
||||
private val valueFnRefs = LinkedHashSet<ValueFnRef>()
|
||||
private val loopVarKeys = LinkedHashSet<ScopeSlotKey>()
|
||||
private val loopVarSlots = HashSet<Int>()
|
||||
private val loopStack = ArrayDeque<LoopContext>()
|
||||
private var currentPos: Pos? = null
|
||||
|
||||
@ -1950,6 +1952,10 @@ class BytecodeCompiler(
|
||||
return value
|
||||
}
|
||||
val value = compileRef(assignValue(ref)) ?: return null
|
||||
if (isLoopVarRef(localTarget)) {
|
||||
emitLoopVarReassignError(localTarget.name, localTarget.pos())
|
||||
return value
|
||||
}
|
||||
if (!localTarget.isMutable || localTarget.isDelegated) {
|
||||
val msgId = builder.addConst(BytecodeConst.StringVal("can't reassign val ${localTarget.name}"))
|
||||
val msgSlot = allocSlot()
|
||||
@ -1990,6 +1996,11 @@ class BytecodeCompiler(
|
||||
val resolved = resolveAssignableSlotByName(nameTarget) ?: return null
|
||||
val slot = resolved.first
|
||||
val isMutable = resolved.second
|
||||
if (isLoopVarSlot(slot)) {
|
||||
val pos = (ref.target as? LocalVarRef)?.pos() ?: Pos.builtIn
|
||||
emitLoopVarReassignError(nameTarget, pos)
|
||||
return value
|
||||
}
|
||||
if (!isMutable) {
|
||||
val msgId = builder.addConst(BytecodeConst.StringVal("can't reassign val $nameTarget"))
|
||||
val msgSlot = allocSlot()
|
||||
@ -2227,6 +2238,11 @@ class BytecodeCompiler(
|
||||
val localTarget = ref.target as? LocalSlotRef
|
||||
if (localTarget != null) {
|
||||
if (!allowLocalSlots) return compileEvalRef(ref)
|
||||
if (isLoopVarRef(localTarget)) {
|
||||
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
|
||||
emitLoopVarReassignError(localTarget.name, localTarget.pos())
|
||||
return rhs
|
||||
}
|
||||
if (localTarget.isDelegated) {
|
||||
val slot = resolveSlot(localTarget) ?: return null
|
||||
if (slot < scopeSlotCount) return null
|
||||
@ -2299,6 +2315,12 @@ class BytecodeCompiler(
|
||||
}
|
||||
val varTarget = ref.target as? LocalVarRef
|
||||
if (varTarget != null) {
|
||||
val resolved = resolveAssignableSlotByName(varTarget.name)
|
||||
if (resolved != null && isLoopVarSlot(resolved.first)) {
|
||||
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
|
||||
emitLoopVarReassignError(varTarget.name, varTarget.pos())
|
||||
return rhs
|
||||
}
|
||||
return compileEvalRef(ref)
|
||||
}
|
||||
val objOp = when (ref.op) {
|
||||
@ -2591,6 +2613,10 @@ class BytecodeCompiler(
|
||||
}
|
||||
is LocalSlotRef -> {
|
||||
if (!allowLocalSlots || !target.isMutable) return null
|
||||
if (isLoopVarRef(target)) {
|
||||
emitLoopVarReassignError(target.name, target.pos())
|
||||
return CompiledValue(currentObj.slot, SlotType.OBJ)
|
||||
}
|
||||
if (target.isDelegated) {
|
||||
val slot = resolveSlot(target) ?: return null
|
||||
if (slot < scopeSlotCount) return null
|
||||
@ -3178,6 +3204,10 @@ class BytecodeCompiler(
|
||||
val target = ref.target as? LocalSlotRef
|
||||
if (target != null) {
|
||||
if (!allowLocalSlots) return null
|
||||
if (isLoopVarRef(target)) {
|
||||
val errorSlot = emitLoopVarReassignError(target.name, target.pos())
|
||||
return CompiledValue(errorSlot, SlotType.OBJ)
|
||||
}
|
||||
if (!target.isMutable) return null
|
||||
if (target.isDelegated) {
|
||||
val slot = resolveSlot(target) ?: return null
|
||||
@ -3345,6 +3375,14 @@ class BytecodeCompiler(
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
val varTarget = ref.target as? LocalVarRef
|
||||
if (varTarget != null) {
|
||||
val resolved = resolveAssignableSlotByName(varTarget.name)
|
||||
if (resolved != null && isLoopVarSlot(resolved.first)) {
|
||||
val errorSlot = emitLoopVarReassignError(varTarget.name, varTarget.pos())
|
||||
return CompiledValue(errorSlot, SlotType.OBJ)
|
||||
}
|
||||
}
|
||||
|
||||
val thisFieldTarget = ref.target as? ThisFieldSlotRef
|
||||
if (thisFieldTarget != null) {
|
||||
@ -4380,18 +4418,18 @@ class BytecodeCompiler(
|
||||
} else {
|
||||
stmt.condition
|
||||
}
|
||||
val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
|
||||
val condValue = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
|
||||
if (condValue.type != SlotType.BOOL) return null
|
||||
|
||||
val resultSlot = allocSlot()
|
||||
val elseLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
|
||||
val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
|
||||
if (!emitIntCompareJump(conditionStmt.ref, jumpOnTrue = false, target = elseLabel)) {
|
||||
val condValue = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
|
||||
if (condValue.type != SlotType.BOOL) return null
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_FALSE,
|
||||
listOf(CmdBuilder.Operand.IntVal(condValue.slot), CmdBuilder.Operand.LabelRef(elseLabel))
|
||||
)
|
||||
}
|
||||
val thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null
|
||||
emitMove(thenValue, resultSlot)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||
@ -5403,6 +5441,8 @@ class BytecodeCompiler(
|
||||
}
|
||||
|
||||
try {
|
||||
val needsBreakFlag = stmt.canBreak || stmt.elseStatement != null
|
||||
val breakFlagSlot = allocSlot()
|
||||
if (range == null && rangeRef == null && typedRangeLocal == null) {
|
||||
val sourceValue = compileStatementValueOrFallback(stmt.source) ?: return null
|
||||
val sourceObj = ensureObjSlot(sourceValue)
|
||||
@ -5430,13 +5470,18 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.CALL_MEMBER_SLOT, sourceObj.slot, iteratorMethodId, 0, 0, iterSlot)
|
||||
builder.emit(Opcode.ITER_PUSH, iterSlot)
|
||||
|
||||
val breakFlagSlot = allocSlot()
|
||||
if (needsBreakFlag) {
|
||||
val falseId = builder.addConst(BytecodeConst.Bool(false))
|
||||
builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot)
|
||||
|
||||
val resultSlot = allocSlot()
|
||||
}
|
||||
val resultSlot = if (wantResult) {
|
||||
val slot = allocSlot()
|
||||
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
|
||||
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
||||
slot
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val loopLabel = builder.label()
|
||||
val continueLabel = builder.label()
|
||||
@ -5465,7 +5510,7 @@ class BytecodeCompiler(
|
||||
endLabel,
|
||||
continueLabel,
|
||||
breakFlagSlot,
|
||||
if (wantResult) resultSlot else null,
|
||||
resultSlot,
|
||||
hasIterator = true
|
||||
)
|
||||
)
|
||||
@ -5473,12 +5518,13 @@ class BytecodeCompiler(
|
||||
loopStack.removeLast()
|
||||
if (wantResult) {
|
||||
val bodyObj = ensureObjSlot(bodyValue)
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
|
||||
}
|
||||
builder.mark(continueLabel)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
||||
|
||||
builder.mark(endLabel)
|
||||
if (needsBreakFlag) {
|
||||
val afterPop = builder.label()
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
@ -5486,29 +5532,38 @@ class BytecodeCompiler(
|
||||
)
|
||||
builder.emit(Opcode.ITER_POP)
|
||||
builder.mark(afterPop)
|
||||
} else {
|
||||
builder.emit(Opcode.ITER_POP)
|
||||
}
|
||||
if (stmt.elseStatement != null) {
|
||||
val afterElse = builder.label()
|
||||
val afterElse = if (needsBreakFlag) builder.label() else null
|
||||
if (needsBreakFlag) {
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse))
|
||||
listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse!!))
|
||||
)
|
||||
}
|
||||
val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
|
||||
if (wantResult) {
|
||||
val elseObj = ensureObjSlot(elseValue)
|
||||
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
|
||||
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot!!)
|
||||
}
|
||||
builder.mark(afterElse)
|
||||
if (needsBreakFlag) {
|
||||
builder.mark(afterElse!!)
|
||||
}
|
||||
return resultSlot
|
||||
}
|
||||
return resultSlot ?: breakFlagSlot
|
||||
}
|
||||
|
||||
val iSlot = allocSlot()
|
||||
val iSlot = loopSlotId
|
||||
val endSlot = allocSlot()
|
||||
if (range != null) {
|
||||
val startId = builder.addConst(BytecodeConst.IntVal(range.start))
|
||||
val endId = builder.addConst(BytecodeConst.IntVal(range.endExclusive))
|
||||
builder.emit(Opcode.CONST_INT, startId, iSlot)
|
||||
builder.emit(Opcode.CONST_INT, endId, endSlot)
|
||||
updateSlotType(iSlot, SlotType.INT)
|
||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||
} else {
|
||||
if (rangeRef != null) {
|
||||
val left = rangeRef.left ?: return null
|
||||
@ -5521,6 +5576,8 @@ class BytecodeCompiler(
|
||||
if (rangeRef.isEndInclusive) {
|
||||
builder.emit(Opcode.INC_INT, endSlot)
|
||||
}
|
||||
updateSlotType(iSlot, SlotType.INT)
|
||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||
} else {
|
||||
val rangeLocal = typedRangeLocal ?: return null
|
||||
val rangeValue = compileRef(rangeLocal) ?: return null
|
||||
@ -5532,27 +5589,33 @@ class BytecodeCompiler(
|
||||
Opcode.JMP_IF_FALSE,
|
||||
listOf(CmdBuilder.Operand.IntVal(okSlot), CmdBuilder.Operand.LabelRef(badRangeLabel))
|
||||
)
|
||||
val breakFlagSlot = allocSlot()
|
||||
if (needsBreakFlag) {
|
||||
val falseId = builder.addConst(BytecodeConst.Bool(false))
|
||||
builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot)
|
||||
|
||||
val resultSlot = allocSlot()
|
||||
}
|
||||
val resultSlot = if (wantResult) {
|
||||
val slot = allocSlot()
|
||||
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
|
||||
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
||||
slot
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val loopLabel = builder.label()
|
||||
val continueLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
val doneLabel = builder.label()
|
||||
builder.mark(loopLabel)
|
||||
val cmpSlot = allocSlot()
|
||||
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
|
||||
Opcode.JMP_IF_GTE_INT,
|
||||
listOf(
|
||||
CmdBuilder.Operand.IntVal(iSlot),
|
||||
CmdBuilder.Operand.IntVal(endSlot),
|
||||
CmdBuilder.Operand.LabelRef(endLabel)
|
||||
)
|
||||
emitMove(CompiledValue(iSlot, SlotType.INT), loopSlotId)
|
||||
updateSlotType(loopSlotId, SlotType.INT)
|
||||
)
|
||||
updateSlotType(iSlot, SlotType.INT)
|
||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||
loopStack.addLast(
|
||||
LoopContext(
|
||||
@ -5560,7 +5623,7 @@ class BytecodeCompiler(
|
||||
endLabel,
|
||||
continueLabel,
|
||||
breakFlagSlot,
|
||||
if (wantResult) resultSlot else null,
|
||||
resultSlot,
|
||||
hasIterator = false
|
||||
)
|
||||
)
|
||||
@ -5568,7 +5631,7 @@ class BytecodeCompiler(
|
||||
loopStack.removeLast()
|
||||
if (wantResult) {
|
||||
val bodyObj = ensureObjSlot(bodyValue)
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
|
||||
}
|
||||
builder.mark(continueLabel)
|
||||
builder.emit(Opcode.INC_INT, iSlot)
|
||||
@ -5576,49 +5639,60 @@ class BytecodeCompiler(
|
||||
|
||||
builder.mark(endLabel)
|
||||
if (stmt.elseStatement != null) {
|
||||
val afterElse = builder.label()
|
||||
val afterElse = if (needsBreakFlag) builder.label() else null
|
||||
if (needsBreakFlag) {
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse))
|
||||
listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse!!))
|
||||
)
|
||||
}
|
||||
val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
|
||||
if (wantResult) {
|
||||
val elseObj = ensureObjSlot(elseValue)
|
||||
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
|
||||
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot!!)
|
||||
}
|
||||
if (needsBreakFlag) {
|
||||
builder.mark(afterElse!!)
|
||||
}
|
||||
builder.mark(afterElse)
|
||||
}
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(doneLabel)))
|
||||
builder.mark(badRangeLabel)
|
||||
val msgId = builder.addConst(BytecodeConst.StringVal("expected Int range"))
|
||||
builder.emit(Opcode.CONST_OBJ, msgId, resultSlot)
|
||||
val errorSlot = resultSlot ?: allocSlot()
|
||||
builder.emit(Opcode.CONST_OBJ, msgId, errorSlot)
|
||||
val posId = builder.addConst(BytecodeConst.PosVal(stmt.pos))
|
||||
builder.emit(Opcode.THROW, posId, resultSlot)
|
||||
builder.emit(Opcode.THROW, posId, errorSlot)
|
||||
builder.mark(doneLabel)
|
||||
return resultSlot
|
||||
return resultSlot ?: breakFlagSlot
|
||||
}
|
||||
}
|
||||
|
||||
val breakFlagSlot = allocSlot()
|
||||
if (needsBreakFlag) {
|
||||
val falseId = builder.addConst(BytecodeConst.Bool(false))
|
||||
builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot)
|
||||
|
||||
val resultSlot = allocSlot()
|
||||
}
|
||||
val resultSlot = if (wantResult) {
|
||||
val slot = allocSlot()
|
||||
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
|
||||
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
||||
slot
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val loopLabel = builder.label()
|
||||
val continueLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
builder.mark(loopLabel)
|
||||
val cmpSlot = allocSlot()
|
||||
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
|
||||
Opcode.JMP_IF_GTE_INT,
|
||||
listOf(
|
||||
CmdBuilder.Operand.IntVal(iSlot),
|
||||
CmdBuilder.Operand.IntVal(endSlot),
|
||||
CmdBuilder.Operand.LabelRef(endLabel)
|
||||
)
|
||||
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
|
||||
updateSlotType(loopSlotId, SlotType.INT)
|
||||
)
|
||||
updateSlotType(iSlot, SlotType.INT)
|
||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||
loopStack.addLast(
|
||||
LoopContext(
|
||||
@ -5626,7 +5700,7 @@ class BytecodeCompiler(
|
||||
endLabel,
|
||||
continueLabel,
|
||||
breakFlagSlot,
|
||||
if (wantResult) resultSlot else null,
|
||||
resultSlot,
|
||||
hasIterator = false
|
||||
)
|
||||
)
|
||||
@ -5634,7 +5708,7 @@ class BytecodeCompiler(
|
||||
loopStack.removeLast()
|
||||
if (wantResult) {
|
||||
val bodyObj = ensureObjSlot(bodyValue)
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot!!)
|
||||
}
|
||||
builder.mark(continueLabel)
|
||||
builder.emit(Opcode.INC_INT, iSlot)
|
||||
@ -5642,19 +5716,23 @@ class BytecodeCompiler(
|
||||
|
||||
builder.mark(endLabel)
|
||||
if (stmt.elseStatement != null) {
|
||||
val afterElse = builder.label()
|
||||
val afterElse = if (needsBreakFlag) builder.label() else null
|
||||
if (needsBreakFlag) {
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse))
|
||||
listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse!!))
|
||||
)
|
||||
}
|
||||
val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
|
||||
if (wantResult) {
|
||||
val elseObj = ensureObjSlot(elseValue)
|
||||
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
|
||||
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot!!)
|
||||
}
|
||||
builder.mark(afterElse)
|
||||
if (needsBreakFlag) {
|
||||
builder.mark(afterElse!!)
|
||||
}
|
||||
return resultSlot
|
||||
}
|
||||
return resultSlot ?: breakFlagSlot
|
||||
} finally {
|
||||
if (usedOverride) {
|
||||
loopSlotOverrides.remove(stmt.loopVarName)
|
||||
@ -5693,12 +5771,16 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||
}
|
||||
builder.mark(continueLabel)
|
||||
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
||||
val conditionTarget = if (stmt.condition is BytecodeStatement) stmt.condition.original else stmt.condition
|
||||
val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
|
||||
if (!emitIntCompareJump(conditionStmt.ref, jumpOnTrue = true, target = loopLabel)) {
|
||||
val condition = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
|
||||
if (condition.type != SlotType.BOOL) return null
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
|
||||
)
|
||||
}
|
||||
|
||||
builder.mark(endLabel)
|
||||
if (stmt.elseStatement != null) {
|
||||
@ -5748,12 +5830,16 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||
}
|
||||
builder.mark(continueLabel)
|
||||
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
||||
val conditionTarget = if (stmt.condition is BytecodeStatement) stmt.condition.original else stmt.condition
|
||||
val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
|
||||
if (!emitIntCompareJump(conditionStmt.ref, jumpOnTrue = true, target = loopLabel)) {
|
||||
val condition = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
|
||||
if (condition.type != SlotType.BOOL) return null
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel))
|
||||
)
|
||||
}
|
||||
|
||||
builder.mark(endLabel)
|
||||
if (stmt.elseStatement != null) {
|
||||
@ -5773,14 +5859,18 @@ class BytecodeCompiler(
|
||||
}
|
||||
|
||||
private fun compileIfStatement(stmt: IfStatement): CompiledValue? {
|
||||
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
||||
if (condition.type != SlotType.BOOL) return null
|
||||
val elseLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
val conditionTarget = if (stmt.condition is BytecodeStatement) stmt.condition.original else stmt.condition
|
||||
val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
|
||||
if (!emitIntCompareJump(conditionStmt.ref, jumpOnTrue = false, target = elseLabel)) {
|
||||
val condition = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
|
||||
if (condition.type != SlotType.BOOL) return null
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_FALSE,
|
||||
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel))
|
||||
)
|
||||
}
|
||||
val thenRestore = applyFlowTypeOverride(flowTypeOverrideForIf(stmt.condition, applyForThen = true))
|
||||
compileStatementValueOrFallback(stmt.ifBody, false) ?: return null
|
||||
restoreFlowTypeOverride(thenRestore)
|
||||
@ -5792,7 +5882,10 @@ class BytecodeCompiler(
|
||||
restoreFlowTypeOverride(elseRestore)
|
||||
}
|
||||
builder.mark(endLabel)
|
||||
return condition
|
||||
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) {
|
||||
@ -5809,15 +5902,19 @@ class BytecodeCompiler(
|
||||
}
|
||||
|
||||
private fun compileIfExpression(stmt: IfStatement): CompiledValue? {
|
||||
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
||||
if (condition.type != SlotType.BOOL) return null
|
||||
val resultSlot = allocSlot()
|
||||
val elseLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
val conditionTarget = if (stmt.condition is BytecodeStatement) stmt.condition.original else stmt.condition
|
||||
val conditionStmt = conditionTarget as? ExpressionStatement ?: return null
|
||||
if (!emitIntCompareJump(conditionStmt.ref, jumpOnTrue = false, target = elseLabel)) {
|
||||
val condition = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
|
||||
if (condition.type != SlotType.BOOL) return null
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_FALSE,
|
||||
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel))
|
||||
)
|
||||
}
|
||||
val thenRestore = applyFlowTypeOverride(flowTypeOverrideForIf(stmt.condition, applyForThen = true))
|
||||
val thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null
|
||||
restoreFlowTypeOverride(thenRestore)
|
||||
@ -5840,6 +5937,70 @@ class BytecodeCompiler(
|
||||
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||
}
|
||||
|
||||
private fun emitIntCompareJump(ref: ObjRef, jumpOnTrue: Boolean, target: CmdBuilder.Label): Boolean {
|
||||
setPos(refPosOrCurrent(ref))
|
||||
val binary = ref as? BinaryOpRef ?: return false
|
||||
val op = binaryOp(binary)
|
||||
if (op != BinOp.EQ && op != BinOp.NEQ && op != BinOp.LT && op != BinOp.LTE && op != BinOp.GT && op != BinOp.GTE) {
|
||||
return false
|
||||
}
|
||||
val leftRef = binaryLeft(binary)
|
||||
val rightRef = binaryRight(binary)
|
||||
if (!isSimpleIntCompareRef(leftRef) || !isSimpleIntCompareRef(rightRef)) return false
|
||||
val left = compileRef(leftRef) ?: return false
|
||||
val right = compileRef(rightRef) ?: return false
|
||||
if (left.type != SlotType.INT || right.type != SlotType.INT) return false
|
||||
val opcode = if (jumpOnTrue) {
|
||||
intCompareJumpOpcode(op)
|
||||
} else {
|
||||
intCompareJumpOpcode(invertIntCompareOp(op))
|
||||
}
|
||||
builder.emit(
|
||||
opcode,
|
||||
listOf(
|
||||
CmdBuilder.Operand.IntVal(left.slot),
|
||||
CmdBuilder.Operand.IntVal(right.slot),
|
||||
CmdBuilder.Operand.LabelRef(target)
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
private fun isSimpleIntCompareRef(ref: ObjRef): Boolean {
|
||||
return when (ref) {
|
||||
is ConstRef -> ref.constValue is ObjInt
|
||||
is LocalVarRef -> ref.name != "this"
|
||||
is FastLocalVarRef -> ref.name != "this"
|
||||
is BoundLocalVarRef -> true
|
||||
is LocalSlotRef -> !ref.isDelegated && ref.name != "this"
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun invertIntCompareOp(op: BinOp): BinOp {
|
||||
return when (op) {
|
||||
BinOp.EQ -> BinOp.NEQ
|
||||
BinOp.NEQ -> BinOp.EQ
|
||||
BinOp.LT -> BinOp.GTE
|
||||
BinOp.LTE -> BinOp.GT
|
||||
BinOp.GT -> BinOp.LTE
|
||||
BinOp.GTE -> BinOp.LT
|
||||
else -> op
|
||||
}
|
||||
}
|
||||
|
||||
private fun intCompareJumpOpcode(op: BinOp): Opcode {
|
||||
return when (op) {
|
||||
BinOp.EQ -> Opcode.JMP_IF_EQ_INT
|
||||
BinOp.NEQ -> Opcode.JMP_IF_NEQ_INT
|
||||
BinOp.LT -> Opcode.JMP_IF_LT_INT
|
||||
BinOp.LTE -> Opcode.JMP_IF_LTE_INT
|
||||
BinOp.GT -> Opcode.JMP_IF_GT_INT
|
||||
BinOp.GTE -> Opcode.JMP_IF_GTE_INT
|
||||
else -> Opcode.JMP_IF_NEQ_INT
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileCondition(stmt: Statement, pos: Pos): CompiledValue? {
|
||||
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||
return when (target) {
|
||||
@ -6325,6 +6486,21 @@ class BytecodeCompiler(
|
||||
|
||||
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
||||
private fun refScopeId(ref: LocalSlotRef): Int = ref.scopeId
|
||||
|
||||
private fun isLoopVarRef(ref: LocalSlotRef): Boolean {
|
||||
return loopVarKeys.contains(ScopeSlotKey(refScopeId(ref), refSlot(ref)))
|
||||
}
|
||||
|
||||
private fun isLoopVarSlot(slot: Int): Boolean = loopVarSlots.contains(slot)
|
||||
|
||||
private fun emitLoopVarReassignError(name: String, pos: Pos): Int {
|
||||
val msgId = builder.addConst(BytecodeConst.StringVal("can't reassign loop variable $name"))
|
||||
val msgSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_OBJ, msgId, msgSlot)
|
||||
val posId = builder.addConst(BytecodeConst.PosVal(pos))
|
||||
builder.emit(Opcode.THROW, posId, msgSlot)
|
||||
return msgSlot
|
||||
}
|
||||
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
||||
private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
|
||||
private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op
|
||||
@ -6682,6 +6858,8 @@ class BytecodeCompiler(
|
||||
declaredLocalKeys.clear()
|
||||
localRangeRefs.clear()
|
||||
intLoopVarNames.clear()
|
||||
loopVarKeys.clear()
|
||||
loopVarSlots.clear()
|
||||
valueFnRefs.clear()
|
||||
addrSlotByScopeSlot.clear()
|
||||
loopStack.clear()
|
||||
@ -6936,6 +7114,16 @@ class BytecodeCompiler(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (loopVarKeys.isNotEmpty()) {
|
||||
for (key in loopVarKeys) {
|
||||
val localIndex = localSlotIndexByKey[key]
|
||||
if (localIndex != null) {
|
||||
loopVarSlots.add(scopeSlotCount + localIndex)
|
||||
continue
|
||||
}
|
||||
scopeSlotMap[key]?.let { loopVarSlots.add(it) }
|
||||
}
|
||||
}
|
||||
nextSlot = scopeSlotCount + localSlotNames.size
|
||||
}
|
||||
|
||||
@ -7118,6 +7306,10 @@ class BytecodeCompiler(
|
||||
}
|
||||
when (stmt) {
|
||||
is net.sergeych.lyng.ForInStatement -> {
|
||||
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
|
||||
if (loopSlotIndex != null) {
|
||||
loopVarKeys.add(ScopeSlotKey(stmt.loopScopeId, loopSlotIndex))
|
||||
}
|
||||
collectLoopSlotPlans(stmt.source, scopeDepth)
|
||||
val loopDepth = scopeDepth + 1
|
||||
collectLoopSlotPlans(stmt.body, loopDepth)
|
||||
|
||||
@ -193,6 +193,10 @@ class CmdBuilder {
|
||||
listOf(OperandKind.IP)
|
||||
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||
Opcode.JMP_IF_EQ_INT, Opcode.JMP_IF_NEQ_INT,
|
||||
Opcode.JMP_IF_LT_INT, Opcode.JMP_IF_LTE_INT,
|
||||
Opcode.JMP_IF_GT_INT, Opcode.JMP_IF_GTE_INT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.IP)
|
||||
Opcode.CALL_DIRECT ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_MEMBER_SLOT ->
|
||||
@ -406,6 +410,36 @@ class CmdBuilder {
|
||||
Opcode.JMP -> CmdJmp(operands[0])
|
||||
Opcode.JMP_IF_TRUE -> CmdJmpIfTrue(operands[0], operands[1])
|
||||
Opcode.JMP_IF_FALSE -> CmdJmpIfFalse(operands[0], operands[1])
|
||||
Opcode.JMP_IF_EQ_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||
CmdJmpIfEqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||
} else {
|
||||
CmdJmpIfEqInt(operands[0], operands[1], operands[2])
|
||||
}
|
||||
Opcode.JMP_IF_NEQ_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||
CmdJmpIfNeqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||
} else {
|
||||
CmdJmpIfNeqInt(operands[0], operands[1], operands[2])
|
||||
}
|
||||
Opcode.JMP_IF_LT_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||
CmdJmpIfLtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||
} else {
|
||||
CmdJmpIfLtInt(operands[0], operands[1], operands[2])
|
||||
}
|
||||
Opcode.JMP_IF_LTE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||
CmdJmpIfLteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||
} else {
|
||||
CmdJmpIfLteInt(operands[0], operands[1], operands[2])
|
||||
}
|
||||
Opcode.JMP_IF_GT_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||
CmdJmpIfGtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||
} else {
|
||||
CmdJmpIfGtInt(operands[0], operands[1], operands[2])
|
||||
}
|
||||
Opcode.JMP_IF_GTE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||
CmdJmpIfGteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2])
|
||||
} else {
|
||||
CmdJmpIfGteInt(operands[0], operands[1], operands[2])
|
||||
}
|
||||
Opcode.RET -> CmdRet(operands[0])
|
||||
Opcode.RET_VOID -> CmdRetVoid()
|
||||
Opcode.PUSH_SCOPE -> CmdPushScope(operands[0])
|
||||
|
||||
@ -176,6 +176,42 @@ object CmdDisassembler {
|
||||
is CmdCmpGteRealInt -> Opcode.CMP_GTE_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||
is CmdCmpNeqIntReal -> Opcode.CMP_NEQ_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||
is CmdCmpNeqRealInt -> Opcode.CMP_NEQ_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||
is CmdJmpIfEqInt -> Opcode.JMP_IF_EQ_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||
is CmdJmpIfEqIntLocal -> Opcode.JMP_IF_EQ_INT to intArrayOf(
|
||||
cmd.a + fn.scopeSlotCount,
|
||||
cmd.b + fn.scopeSlotCount,
|
||||
cmd.target
|
||||
)
|
||||
is CmdJmpIfNeqInt -> Opcode.JMP_IF_NEQ_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||
is CmdJmpIfNeqIntLocal -> Opcode.JMP_IF_NEQ_INT to intArrayOf(
|
||||
cmd.a + fn.scopeSlotCount,
|
||||
cmd.b + fn.scopeSlotCount,
|
||||
cmd.target
|
||||
)
|
||||
is CmdJmpIfLtInt -> Opcode.JMP_IF_LT_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||
is CmdJmpIfLtIntLocal -> Opcode.JMP_IF_LT_INT to intArrayOf(
|
||||
cmd.a + fn.scopeSlotCount,
|
||||
cmd.b + fn.scopeSlotCount,
|
||||
cmd.target
|
||||
)
|
||||
is CmdJmpIfLteInt -> Opcode.JMP_IF_LTE_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||
is CmdJmpIfLteIntLocal -> Opcode.JMP_IF_LTE_INT to intArrayOf(
|
||||
cmd.a + fn.scopeSlotCount,
|
||||
cmd.b + fn.scopeSlotCount,
|
||||
cmd.target
|
||||
)
|
||||
is CmdJmpIfGtInt -> Opcode.JMP_IF_GT_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||
is CmdJmpIfGtIntLocal -> Opcode.JMP_IF_GT_INT to intArrayOf(
|
||||
cmd.a + fn.scopeSlotCount,
|
||||
cmd.b + fn.scopeSlotCount,
|
||||
cmd.target
|
||||
)
|
||||
is CmdJmpIfGteInt -> Opcode.JMP_IF_GTE_INT to intArrayOf(cmd.a, cmd.b, cmd.target)
|
||||
is CmdJmpIfGteIntLocal -> Opcode.JMP_IF_GTE_INT to intArrayOf(
|
||||
cmd.a + fn.scopeSlotCount,
|
||||
cmd.b + fn.scopeSlotCount,
|
||||
cmd.target
|
||||
)
|
||||
is CmdCmpEqObj -> Opcode.CMP_EQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||
is CmdCmpNeqObj -> Opcode.CMP_NEQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||
is CmdCmpRefEqObj -> Opcode.CMP_REF_EQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||
@ -329,6 +365,10 @@ object CmdDisassembler {
|
||||
listOf(OperandKind.IP)
|
||||
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||
Opcode.JMP_IF_EQ_INT, Opcode.JMP_IF_NEQ_INT,
|
||||
Opcode.JMP_IF_LT_INT, Opcode.JMP_IF_LTE_INT,
|
||||
Opcode.JMP_IF_GT_INT, Opcode.JMP_IF_GTE_INT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.IP)
|
||||
Opcode.CALL_DIRECT ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_SLOT, Opcode.CALL_BRIDGE_SLOT ->
|
||||
|
||||
@ -34,12 +34,14 @@ class CmdVm {
|
||||
frame.applyCaptureRecords()
|
||||
binder?.invoke(frame, args)
|
||||
val cmds = fn.cmds
|
||||
try {
|
||||
while (result == null) {
|
||||
try {
|
||||
while (result == null) {
|
||||
val cmd = cmds[frame.ip]
|
||||
frame.ip += 1
|
||||
try {
|
||||
cmd.perform(frame)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
if (!frame.handleException(e)) {
|
||||
frame.cancelIterators()
|
||||
@ -1303,6 +1305,114 @@ class CmdJmpIfFalse(internal val cond: Int, internal val target: Int) : Cmd() {
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfEqInt(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getInt(a) == frame.getInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfEqIntLocal(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getLocalInt(a) == frame.getLocalInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfNeqInt(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getInt(a) != frame.getInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfNeqIntLocal(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getLocalInt(a) != frame.getLocalInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfLtInt(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getInt(a) < frame.getInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfLtIntLocal(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getLocalInt(a) < frame.getLocalInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfLteInt(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getInt(a) <= frame.getInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfLteIntLocal(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getLocalInt(a) <= frame.getLocalInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfGtInt(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getInt(a) > frame.getInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfGtIntLocal(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getLocalInt(a) > frame.getLocalInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfGteInt(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getInt(a) >= frame.getInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdJmpIfGteIntLocal(internal val a: Int, internal val b: Int, internal val target: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
if (frame.getLocalInt(a) >= frame.getLocalInt(b)) {
|
||||
frame.ip = target
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdRet(internal val slot: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
frame.vm.result = frame.slotToObj(slot)
|
||||
|
||||
@ -117,6 +117,12 @@ enum class Opcode(val code: Int) {
|
||||
JMP(0x80),
|
||||
JMP_IF_TRUE(0x81),
|
||||
JMP_IF_FALSE(0x82),
|
||||
JMP_IF_EQ_INT(0xD0),
|
||||
JMP_IF_NEQ_INT(0xD1),
|
||||
JMP_IF_LT_INT(0xD2),
|
||||
JMP_IF_LTE_INT(0xD3),
|
||||
JMP_IF_GT_INT(0xD4),
|
||||
JMP_IF_GTE_INT(0xD5),
|
||||
RET(0x83),
|
||||
RET_VOID(0x84),
|
||||
RET_LABEL(0xBA),
|
||||
|
||||
@ -17,11 +17,13 @@
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.Benchmarks
|
||||
import net.sergeych.lyng.BytecodeBodyProvider
|
||||
import net.sergeych.lyng.Compiler
|
||||
import net.sergeych.lyng.ForInStatement
|
||||
import net.sergeych.lyng.Script
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.bytecode.CmdDisassembler
|
||||
import net.sergeych.lyng.bytecode.CmdFunction
|
||||
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import kotlin.time.TimeSource
|
||||
@ -57,6 +59,7 @@ class NestedRangeBenchmarkTest {
|
||||
scope.eval(script)
|
||||
val fnDisasm = scope.disassembleSymbol("naiveCountHappyNumbers")
|
||||
println("[DEBUG_LOG] [BENCH] nested-happy function naiveCountHappyNumbers cmd:\n$fnDisasm")
|
||||
dumpFunctionSlots(scope, "naiveCountHappyNumbers")
|
||||
runMode(scope)
|
||||
}
|
||||
|
||||
@ -96,4 +99,30 @@ class NestedRangeBenchmarkTest {
|
||||
}
|
||||
}
|
||||
|
||||
private fun dumpFunctionSlots(scope: net.sergeych.lyng.Scope, name: String) {
|
||||
val record = scope[name]?.value as? Statement ?: return
|
||||
val fn = bytecodeFromStatement(record) ?: return
|
||||
val scopeNames = fn.scopeSlotNames.mapIndexedNotNull { idx, slotName ->
|
||||
slotName?.let { "$it@${fn.scopeSlotIndices[idx]}" }
|
||||
}
|
||||
val localNames = fn.localSlotNames.mapIndexedNotNull { idx, slotName ->
|
||||
slotName?.let { "$it@$idx" }
|
||||
}
|
||||
val captures = fn.localSlotNames.mapIndexedNotNull { idx, slotName ->
|
||||
if (slotName != null && fn.localSlotCaptures.getOrNull(idx) == true) "$slotName@$idx" else null
|
||||
}
|
||||
println(
|
||||
"[DEBUG_LOG] [BENCH] nested-happy function $name slots: " +
|
||||
"scopeCount=${fn.scopeSlotCount} " +
|
||||
"scope=[${scopeNames.joinToString(", ")}] " +
|
||||
"locals=[${localNames.joinToString(", ")}] " +
|
||||
"captures=[${captures.joinToString(", ")}]"
|
||||
)
|
||||
}
|
||||
|
||||
private fun bytecodeFromStatement(stmt: Statement): CmdFunction? {
|
||||
return (stmt as? BytecodeStatement)?.bytecodeFunction()
|
||||
?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1937,7 +1937,6 @@ class ScriptTest {
|
||||
println("limit reached after "+n+" rounds")
|
||||
break sum
|
||||
}
|
||||
n++
|
||||
}
|
||||
else {
|
||||
println("limit not reached")
|
||||
|
||||
@ -129,7 +129,6 @@ class TypesTest {
|
||||
println("limit reached after "+n+" rounds")
|
||||
break sum
|
||||
}
|
||||
n++
|
||||
}
|
||||
else {
|
||||
println("limit not reached")
|
||||
|
||||
3
notes/bytecode_vm_notes.md
Normal file
3
notes/bytecode_vm_notes.md
Normal file
@ -0,0 +1,3 @@
|
||||
# Bytecode VM notes
|
||||
|
||||
- Opcode switch dispatch was measured slower than virtual dispatch; keep the per-op Cmd class path for now.
|
||||
43
notes/nested_loop_vm_state.md
Normal file
43
notes/nested_loop_vm_state.md
Normal file
@ -0,0 +1,43 @@
|
||||
## Nested loop performance investigation state (2026-02-15)
|
||||
|
||||
### Key findings
|
||||
- Bytecode for `NestedRangeBenchmarkTest` is fully int-local ops; no dynamic lookups or scopes in hot path.
|
||||
- Loop vars now live directly in local int slots (`n1..n6`), removing per-iteration `MOVE_INT`.
|
||||
- Per-instruction try/catch in VM was replaced with an outer try/catch loop; on JVM this improved the benchmark.
|
||||
- Native slowdown is likely dominated by suspend/virtual dispatch overhead in VM, not allocations in int ops.
|
||||
|
||||
### Current bytecode shape (naiveCountHappyNumbers)
|
||||
- Ops: `CONST_INT`, `CMP_GTE_INT`, `INC_INT`, `ADD_INT`, `CMP_EQ_INT`, `JMP*`, `RET`.
|
||||
- All are `*Local` variants hitting `BytecodeFrame` primitive arrays.
|
||||
|
||||
### Changes made
|
||||
- Loop vars are enforced read-only inside loop bodies at bytecode compile time (reassign, op=, ?=, ++/-- throw).
|
||||
- Range loops reuse the loop var slot as the counter; no per-iteration move.
|
||||
- VM loop now uses outer try/catch (no per-op try/catch).
|
||||
- VM stats instrumentation was added temporarily, then removed for MP safety.
|
||||
|
||||
### Files changed
|
||||
- `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt`
|
||||
- loop var immutability checks
|
||||
- loop var slot reuse for range loops
|
||||
- skip break/result init when not needed
|
||||
- `lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt`
|
||||
- outer try/catch VM loop (removed per-op try/catch)
|
||||
- stats instrumentation removed
|
||||
- `lynglib/src/commonTest/kotlin/NestedRangeBenchmarkTest.kt`
|
||||
- temporary VM stats debug removed
|
||||
- slot dump remains for visibility
|
||||
- `notes/bytecode_vm_notes.md`
|
||||
- note: opcode switch dispatch tested slower than virtual dispatch
|
||||
|
||||
### Benchmark snapshots (JVM)
|
||||
- Before VM loop change: ~96–110 ms.
|
||||
- With VM stats enabled: ~234–240 ms (stats overhead).
|
||||
- After VM loop change, stats disabled: ~85 ms.
|
||||
- 2026-02-15 baseline (fused int-compare jumps): 74 ms.
|
||||
- Command: `./gradlew :lynglib:jvmTest -Pbenchmarks=true --tests '*NestedRangeBenchmarkTest*'`
|
||||
- Notes: loop range checks use `JMP_IF_GTE_INT` (no CMP+bool temp).
|
||||
|
||||
### Hypothesis for Native slowdown
|
||||
- Suspend/virtual dispatch per opcode dominates on K/N, even with no allocations in int ops.
|
||||
- Next idea: a non-suspend fast path for hot opcodes, or a dual-path VM loop.
|
||||
Loading…
x
Reference in New Issue
Block a user