Bytecode for loop over typed range params
This commit is contained in:
parent
2311cfc224
commit
37a8831fd7
@ -126,6 +126,18 @@ class Compiler(
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isRangeType(type: TypeDecl): Boolean {
|
||||
val name = when (type) {
|
||||
is TypeDecl.Simple -> type.name
|
||||
is TypeDecl.Generic -> type.name
|
||||
else -> return false
|
||||
}
|
||||
return name == "Range" ||
|
||||
name == "IntRange" ||
|
||||
name.endsWith(".Range") ||
|
||||
name.endsWith(".IntRange")
|
||||
}
|
||||
|
||||
var packageName: String? = null
|
||||
|
||||
class Settings(
|
||||
@ -371,6 +383,9 @@ class Compiler(
|
||||
private var lastLabel: String? = null
|
||||
private val useBytecodeStatements: Boolean = true
|
||||
private val returnLabelStack = ArrayDeque<Set<String>>()
|
||||
private val rangeParamNamesStack = mutableListOf<Set<String>>()
|
||||
private val currentRangeParamNames: Set<String>
|
||||
get() = rangeParamNamesStack.lastOrNull() ?: emptySet()
|
||||
|
||||
private fun wrapBytecode(stmt: Statement): Statement {
|
||||
if (!useBytecodeStatements) return stmt
|
||||
@ -380,7 +395,8 @@ class Compiler(
|
||||
stmt,
|
||||
"stmt@${stmt.pos}",
|
||||
allowLocalSlots = allowLocals,
|
||||
returnLabels = returnLabels
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = currentRangeParamNames
|
||||
)
|
||||
}
|
||||
|
||||
@ -391,7 +407,8 @@ class Compiler(
|
||||
stmt,
|
||||
"fn@$name",
|
||||
allowLocalSlots = true,
|
||||
returnLabels = returnLabels
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = currentRangeParamNames
|
||||
)
|
||||
}
|
||||
|
||||
@ -3041,11 +3058,16 @@ class Compiler(
|
||||
val paramNamesList = argsDeclaration.params.map { it.name }
|
||||
val paramNames: Set<String> = paramNamesList.toSet()
|
||||
val paramSlotPlan = buildParamSlotPlan(paramNamesList)
|
||||
val rangeParamNames = argsDeclaration.params
|
||||
.filter { isRangeType(it.type) }
|
||||
.map { it.name }
|
||||
.toSet()
|
||||
|
||||
// Parse function body while tracking declared locals to compute precise capacity hints
|
||||
currentLocalDeclCount
|
||||
localDeclCountStack.add(0)
|
||||
slotPlanStack.add(paramSlotPlan)
|
||||
rangeParamNamesStack.add(rangeParamNames)
|
||||
val parsedFnStatements = try {
|
||||
val returnLabels = buildSet {
|
||||
add(name)
|
||||
@ -3083,6 +3105,7 @@ class Compiler(
|
||||
returnLabelStack.removeLast()
|
||||
}
|
||||
} finally {
|
||||
rangeParamNamesStack.removeLast()
|
||||
slotPlanStack.removeLast()
|
||||
}
|
||||
val fnStatements = parsedFnStatements?.let {
|
||||
|
||||
@ -29,6 +29,7 @@ import net.sergeych.lyng.obj.*
|
||||
class BytecodeCompiler(
|
||||
private val allowLocalSlots: Boolean = true,
|
||||
private val returnLabels: Set<String> = emptySet(),
|
||||
private val rangeLocalNames: Set<String> = emptySet(),
|
||||
) {
|
||||
private var builder = CmdBuilder()
|
||||
private var nextSlot = 0
|
||||
@ -48,6 +49,7 @@ class BytecodeCompiler(
|
||||
private var localSlotMutables = BooleanArray(0)
|
||||
private var localSlotDepths = IntArray(0)
|
||||
private val declaredLocalKeys = LinkedHashSet<ScopeSlotKey>()
|
||||
private val localRangeRefs = LinkedHashMap<ScopeSlotKey, RangeRef>()
|
||||
private val slotTypes = mutableMapOf<Int, SlotType>()
|
||||
private val intLoopVarNames = LinkedHashSet<String>()
|
||||
private val loopStack = ArrayDeque<LoopContext>()
|
||||
@ -1672,8 +1674,12 @@ class BytecodeCompiler(
|
||||
}
|
||||
private fun emitForIn(stmt: net.sergeych.lyng.ForInStatement, wantResult: Boolean): Int? {
|
||||
val range = stmt.constRange
|
||||
val rangeRef = if (range == null) extractRangeRef(stmt.source) else null
|
||||
if (range == null && rangeRef == null) return null
|
||||
var rangeRef = if (range == null) extractRangeRef(stmt.source) else null
|
||||
if (range == null && rangeRef == null) {
|
||||
rangeRef = extractRangeFromLocal(stmt.source)
|
||||
}
|
||||
val typedRangeLocal = if (range == null && rangeRef == null) extractTypedRangeLocal(stmt.source) else null
|
||||
if (range == null && rangeRef == null && typedRangeLocal == null) return null
|
||||
val loopLocalIndex = localSlotIndexByName[stmt.loopVarName] ?: return null
|
||||
val loopSlotId = scopeSlotCount + loopLocalIndex
|
||||
|
||||
@ -1685,7 +1691,8 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.CONST_INT, startId, iSlot)
|
||||
builder.emit(Opcode.CONST_INT, endId, endSlot)
|
||||
} else {
|
||||
val left = rangeRef?.left ?: return null
|
||||
if (rangeRef != null) {
|
||||
val left = rangeRef.left ?: return null
|
||||
val right = rangeRef.right ?: return null
|
||||
val startValue = compileRef(left) ?: return null
|
||||
val endValue = compileRef(right) ?: return null
|
||||
@ -1695,6 +1702,73 @@ class BytecodeCompiler(
|
||||
if (rangeRef.isEndInclusive) {
|
||||
builder.emit(Opcode.INC_INT, endSlot)
|
||||
}
|
||||
} else {
|
||||
val rangeLocal = typedRangeLocal ?: return null
|
||||
val rangeValue = compileRef(rangeLocal) ?: return null
|
||||
val rangeObj = ensureObjSlot(rangeValue)
|
||||
val okSlot = allocSlot()
|
||||
builder.emit(Opcode.RANGE_INT_BOUNDS, rangeObj.slot, iSlot, endSlot, okSlot)
|
||||
val fallbackLabel = builder.label()
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_FALSE,
|
||||
listOf(CmdBuilder.Operand.IntVal(okSlot), CmdBuilder.Operand.LabelRef(fallbackLabel))
|
||||
)
|
||||
val breakFlagSlot = allocSlot()
|
||||
val falseId = builder.addConst(BytecodeConst.Bool(false))
|
||||
builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot)
|
||||
|
||||
val resultSlot = allocSlot()
|
||||
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
|
||||
|
||||
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))
|
||||
)
|
||||
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
|
||||
updateSlotType(loopSlotId, SlotType.INT)
|
||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||
loopStack.addLast(
|
||||
LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
|
||||
)
|
||||
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
|
||||
loopStack.removeLast()
|
||||
if (wantResult) {
|
||||
val bodyObj = ensureObjSlot(bodyValue)
|
||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||
}
|
||||
builder.mark(continueLabel)
|
||||
builder.emit(Opcode.INC_INT, iSlot)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
||||
|
||||
builder.mark(endLabel)
|
||||
if (stmt.elseStatement != null) {
|
||||
val afterElse = builder.label()
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
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.mark(afterElse)
|
||||
}
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(doneLabel)))
|
||||
builder.mark(fallbackLabel)
|
||||
val fallbackId = builder.addFallback(stmt)
|
||||
builder.emit(Opcode.EVAL_FALLBACK, fallbackId, resultSlot)
|
||||
builder.mark(doneLabel)
|
||||
return resultSlot
|
||||
}
|
||||
}
|
||||
|
||||
val breakFlagSlot = allocSlot()
|
||||
@ -2105,6 +2179,7 @@ class BytecodeCompiler(
|
||||
localSlotMutables = BooleanArray(0)
|
||||
localSlotDepths = IntArray(0)
|
||||
declaredLocalKeys.clear()
|
||||
localRangeRefs.clear()
|
||||
intLoopVarNames.clear()
|
||||
addrSlotByScopeSlot.clear()
|
||||
loopStack.clear()
|
||||
@ -2168,6 +2243,11 @@ class BytecodeCompiler(
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
localSlotInfoMap[key] = LocalSlotInfo(stmt.name, stmt.isMutable, slotDepth)
|
||||
}
|
||||
if (!stmt.isMutable) {
|
||||
extractDeclaredRange(stmt.initializer)?.let { range ->
|
||||
localRangeRefs[key] = range
|
||||
}
|
||||
}
|
||||
}
|
||||
stmt.initializer?.let { collectScopeSlots(it) }
|
||||
}
|
||||
@ -2527,6 +2607,30 @@ class BytecodeCompiler(
|
||||
return expr.ref as? RangeRef
|
||||
}
|
||||
|
||||
private fun extractDeclaredRange(stmt: Statement?): RangeRef? {
|
||||
if (stmt == null) return null
|
||||
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||
val expr = target as? ExpressionStatement ?: return null
|
||||
return expr.ref as? RangeRef
|
||||
}
|
||||
|
||||
private fun extractRangeFromLocal(source: Statement): RangeRef? {
|
||||
val target = if (source is BytecodeStatement) source.original else source
|
||||
val expr = target as? ExpressionStatement ?: return null
|
||||
val localRef = expr.ref as? LocalSlotRef ?: return null
|
||||
val key = ScopeSlotKey(refScopeDepth(localRef), refSlot(localRef))
|
||||
return localRangeRefs[key]
|
||||
}
|
||||
|
||||
private fun extractTypedRangeLocal(source: Statement): LocalSlotRef? {
|
||||
if (rangeLocalNames.isEmpty()) return null
|
||||
val target = if (source is BytecodeStatement) source.original else source
|
||||
val expr = target as? ExpressionStatement ?: return null
|
||||
val localRef = expr.ref as? LocalSlotRef ?: return null
|
||||
if (localRef.isDelegated) return null
|
||||
return if (rangeLocalNames.contains(localRef.name)) localRef else null
|
||||
}
|
||||
|
||||
private fun effectiveLocalDepth(depth: Int): Int {
|
||||
if (depth == 0 || virtualScopeDepths.isEmpty()) return depth
|
||||
var virtualCount = 0
|
||||
|
||||
@ -40,12 +40,17 @@ class BytecodeStatement private constructor(
|
||||
nameHint: String,
|
||||
allowLocalSlots: Boolean,
|
||||
returnLabels: Set<String> = emptySet(),
|
||||
rangeLocalNames: Set<String> = emptySet(),
|
||||
): Statement {
|
||||
if (statement is BytecodeStatement) return statement
|
||||
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||
if (hasUnsupported) return unwrapDeep(statement)
|
||||
val safeLocals = allowLocalSlots
|
||||
val compiler = BytecodeCompiler(allowLocalSlots = safeLocals, returnLabels = returnLabels)
|
||||
val compiler = BytecodeCompiler(
|
||||
allowLocalSlots = safeLocals,
|
||||
returnLabels = returnLabels,
|
||||
rangeLocalNames = rangeLocalNames
|
||||
)
|
||||
val compiled = compiler.compileStatement(nameHint, statement)
|
||||
val fn = compiled ?: run {
|
||||
val builder = CmdBuilder()
|
||||
@ -78,11 +83,15 @@ class BytecodeStatement private constructor(
|
||||
is net.sergeych.lyng.ForInStatement -> {
|
||||
val rangeSource = target.source
|
||||
val rangeRef = (rangeSource as? net.sergeych.lyng.ExpressionStatement)?.ref as? RangeRef
|
||||
val hasRange = target.constRange != null || rangeRef != null
|
||||
!hasRange ||
|
||||
val sourceRef = (rangeSource as? net.sergeych.lyng.ExpressionStatement)?.ref
|
||||
val hasRange = target.constRange != null ||
|
||||
rangeRef != null ||
|
||||
(sourceRef is net.sergeych.lyng.obj.LocalSlotRef)
|
||||
val unsupported = !hasRange ||
|
||||
containsUnsupportedStatement(target.source) ||
|
||||
containsUnsupportedStatement(target.body) ||
|
||||
(target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false)
|
||||
unsupported
|
||||
}
|
||||
is net.sergeych.lyng.WhileStatement -> {
|
||||
containsUnsupportedStatement(target.condition) ||
|
||||
|
||||
@ -122,6 +122,8 @@ class CmdBuilder {
|
||||
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
||||
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.RANGE_INT_BOUNDS ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.RET_LABEL, Opcode.THROW ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.RESOLVE_SCOPE_SLOT ->
|
||||
@ -209,6 +211,7 @@ class CmdBuilder {
|
||||
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
|
||||
Opcode.CONST_NULL -> CmdConstNull(operands[0])
|
||||
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
|
||||
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.RET_LABEL -> CmdRetLabel(operands[0], operands[1])
|
||||
Opcode.THROW -> CmdThrow(operands[0], operands[1])
|
||||
Opcode.RESOLVE_SCOPE_SLOT -> CmdResolveScopeSlot(operands[0], operands[1])
|
||||
|
||||
@ -68,6 +68,7 @@ object CmdDisassembler {
|
||||
is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst)
|
||||
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
|
||||
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||
is CmdRangeIntBounds -> Opcode.RANGE_INT_BOUNDS to intArrayOf(cmd.src, cmd.startSlot, cmd.endSlot, cmd.okSlot)
|
||||
is CmdResolveScopeSlot -> Opcode.RESOLVE_SCOPE_SLOT to intArrayOf(cmd.scopeSlot, cmd.addrSlot)
|
||||
is CmdLoadObjAddr -> Opcode.LOAD_OBJ_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||
is CmdStoreObjAddr -> Opcode.STORE_OBJ_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||
@ -196,6 +197,8 @@ object CmdDisassembler {
|
||||
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
||||
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.RANGE_INT_BOUNDS ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.RET_LABEL, Opcode.THROW ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.RESOLVE_SCOPE_SLOT ->
|
||||
|
||||
@ -154,6 +154,28 @@ class CmdBoxObj(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
}
|
||||
}
|
||||
|
||||
class CmdRangeIntBounds(
|
||||
internal val src: Int,
|
||||
internal val startSlot: Int,
|
||||
internal val endSlot: Int,
|
||||
internal val okSlot: Int,
|
||||
) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val obj = frame.slotToObj(src)
|
||||
val range = obj as? ObjRange
|
||||
if (range == null || !range.isIntRange) {
|
||||
frame.setBool(okSlot, false)
|
||||
return
|
||||
}
|
||||
val start = (range.start as ObjInt).value
|
||||
val end = (range.end as ObjInt).value
|
||||
frame.setInt(startSlot, start)
|
||||
frame.setInt(endSlot, if (range.isEndInclusive) end + 1 else end)
|
||||
frame.setBool(okSlot, true)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdResolveScopeSlot(internal val scopeSlot: Int, internal val addrSlot: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
frame.resolveScopeSlotAddr(scopeSlot, addrSlot)
|
||||
|
||||
@ -28,6 +28,7 @@ enum class Opcode(val code: Int) {
|
||||
CONST_BOOL(0x08),
|
||||
CONST_NULL(0x09),
|
||||
BOX_OBJ(0x0A),
|
||||
RANGE_INT_BOUNDS(0x0B),
|
||||
|
||||
INT_TO_REAL(0x10),
|
||||
REAL_TO_INT(0x11),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user