Bytecode for iterable for-in loops
This commit is contained in:
parent
37a8831fd7
commit
8dfdbaa0a0
@ -422,8 +422,7 @@ class Compiler(
|
||||
(target.elseBody?.let { containsUnsupportedForBytecode(it) } ?: false)
|
||||
}
|
||||
is ForInStatement -> {
|
||||
target.constRange == null ||
|
||||
containsUnsupportedForBytecode(target.source) ||
|
||||
containsUnsupportedForBytecode(target.source) ||
|
||||
containsUnsupportedForBytecode(target.body) ||
|
||||
(target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false)
|
||||
}
|
||||
@ -2079,8 +2078,8 @@ class Compiler(
|
||||
cc.currentPos(),
|
||||
"when else block already defined"
|
||||
)
|
||||
elseCase =
|
||||
parseStatement() ?: throw ScriptError(
|
||||
elseCase = parseStatement()?.let { unwrapBytecodeDeep(it) }
|
||||
?: throw ScriptError(
|
||||
cc.currentPos(),
|
||||
"when else block expected"
|
||||
)
|
||||
@ -2102,7 +2101,8 @@ class Compiler(
|
||||
}
|
||||
// parsed conditions?
|
||||
if (!skipParseBody) {
|
||||
val block = parseStatement() ?: throw ScriptError(cc.currentPos(), "when case block expected")
|
||||
val block = parseStatement()?.let { unwrapBytecodeDeep(it) }
|
||||
?: throw ScriptError(cc.currentPos(), "when case block expected")
|
||||
for (c in currentCondition) cases += WhenCase(c, block)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1679,10 +1679,81 @@ class BytecodeCompiler(
|
||||
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
|
||||
|
||||
if (range == null && rangeRef == null && typedRangeLocal == null) {
|
||||
val sourceValue = compileStatementValueOrFallback(stmt.source) ?: return null
|
||||
val sourceObj = ensureObjSlot(sourceValue)
|
||||
val typeId = builder.addConst(BytecodeConst.ObjRef(ObjIterable))
|
||||
val typeSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_OBJ, typeId, typeSlot)
|
||||
builder.emit(Opcode.ASSERT_IS, sourceObj.slot, typeSlot)
|
||||
|
||||
val iterSlot = allocSlot()
|
||||
val iteratorId = builder.addConst(BytecodeConst.StringVal("iterator"))
|
||||
builder.emit(Opcode.CALL_VIRTUAL, sourceObj.slot, iteratorId, 0, 0, iterSlot)
|
||||
|
||||
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()
|
||||
builder.mark(loopLabel)
|
||||
|
||||
val hasNextSlot = allocSlot()
|
||||
val hasNextId = builder.addConst(BytecodeConst.StringVal("hasNext"))
|
||||
builder.emit(Opcode.CALL_VIRTUAL, iterSlot, hasNextId, 0, 0, hasNextSlot)
|
||||
val condSlot = allocSlot()
|
||||
builder.emit(Opcode.OBJ_TO_BOOL, hasNextSlot, condSlot)
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_FALSE,
|
||||
listOf(CmdBuilder.Operand.IntVal(condSlot), CmdBuilder.Operand.LabelRef(endLabel))
|
||||
)
|
||||
|
||||
val nextSlot = allocSlot()
|
||||
val nextId = builder.addConst(BytecodeConst.StringVal("next"))
|
||||
builder.emit(Opcode.CALL_VIRTUAL, iterSlot, nextId, 0, 0, nextSlot)
|
||||
val nextObj = ensureObjSlot(CompiledValue(nextSlot, SlotType.UNKNOWN))
|
||||
builder.emit(Opcode.MOVE_OBJ, nextObj.slot, loopSlotId)
|
||||
updateSlotType(loopSlotId, SlotType.OBJ)
|
||||
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
|
||||
|
||||
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.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)
|
||||
}
|
||||
return resultSlot
|
||||
}
|
||||
|
||||
val iSlot = allocSlot()
|
||||
val endSlot = allocSlot()
|
||||
if (range != null) {
|
||||
|
||||
@ -81,14 +81,7 @@ class BytecodeStatement private constructor(
|
||||
(target.elseBody?.let { containsUnsupportedStatement(it) } ?: false)
|
||||
}
|
||||
is net.sergeych.lyng.ForInStatement -> {
|
||||
val rangeSource = target.source
|
||||
val rangeRef = (rangeSource as? net.sergeych.lyng.ExpressionStatement)?.ref as? RangeRef
|
||||
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) ||
|
||||
val unsupported = containsUnsupportedStatement(target.source) ||
|
||||
containsUnsupportedStatement(target.body) ||
|
||||
(target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false)
|
||||
unsupported
|
||||
|
||||
@ -120,8 +120,12 @@ class CmdBuilder {
|
||||
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList()
|
||||
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
||||
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 ->
|
||||
Opcode.OBJ_TO_BOOL,
|
||||
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT,
|
||||
Opcode.ASSERT_IS ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.CHECK_IS ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.RANGE_INT_BOUNDS ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.RET_LABEL, Opcode.THROW ->
|
||||
@ -211,7 +215,10 @@ 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.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
|
||||
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
|
||||
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
|
||||
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
|
||||
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,9 @@ 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 CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
||||
is CmdAssertIs -> Opcode.ASSERT_IS to intArrayOf(cmd.objSlot, cmd.typeSlot)
|
||||
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)
|
||||
@ -197,6 +200,10 @@ 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.OBJ_TO_BOOL, Opcode.ASSERT_IS ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.CHECK_IS ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.RANGE_INT_BOUNDS ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.RET_LABEL, Opcode.THROW ->
|
||||
|
||||
@ -154,6 +154,37 @@ class CmdBoxObj(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
}
|
||||
}
|
||||
|
||||
class CmdObjToBool(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
frame.setBool(dst, frame.slotToObj(src).toBool())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdCheckIs(internal val objSlot: Int, internal val typeSlot: Int, internal val dst: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val obj = frame.slotToObj(objSlot)
|
||||
val typeObj = frame.slotToObj(typeSlot)
|
||||
val clazz = typeObj as? ObjClass
|
||||
frame.setBool(dst, clazz != null && obj.isInstanceOf(clazz))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdAssertIs(internal val objSlot: Int, internal val typeSlot: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val obj = frame.slotToObj(objSlot)
|
||||
val typeObj = frame.slotToObj(typeSlot)
|
||||
val clazz = typeObj as? ObjClass ?: frame.scope.raiseClassCastError(
|
||||
"${typeObj.inspect(frame.scope)} is not the class instance"
|
||||
)
|
||||
if (!obj.isInstanceOf(clazz)) {
|
||||
frame.scope.raiseClassCastError("expected ${clazz.className}, got ${obj.objClass.className}")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
class CmdRangeIntBounds(
|
||||
internal val src: Int,
|
||||
internal val startSlot: Int,
|
||||
|
||||
@ -34,6 +34,9 @@ enum class Opcode(val code: Int) {
|
||||
REAL_TO_INT(0x11),
|
||||
BOOL_TO_INT(0x12),
|
||||
INT_TO_BOOL(0x13),
|
||||
OBJ_TO_BOOL(0x14),
|
||||
CHECK_IS(0x15),
|
||||
ASSERT_IS(0x16),
|
||||
|
||||
ADD_INT(0x20),
|
||||
SUB_INT(0x21),
|
||||
|
||||
@ -5044,4 +5044,62 @@ class ScriptTest {
|
||||
assertEquals( [1], t(1) )
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForInIterableDisasm() = runTest {
|
||||
val scope = Script.newScope()
|
||||
scope.eval("""
|
||||
fun type(x) {
|
||||
when(x) {
|
||||
"42", 42 -> "answer to the great question"
|
||||
is Real, is Int -> "number"
|
||||
is String -> {
|
||||
for( d in x ) {
|
||||
if( d !in '0'..'9' )
|
||||
break "unknown"
|
||||
}
|
||||
else "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent())
|
||||
println("[DEBUG_LOG] type disasm:\n${scope.disassembleSymbol("type")}")
|
||||
val r1 = scope.eval("""type("12%")""")
|
||||
val r2 = scope.eval("""type("153")""")
|
||||
println("[DEBUG_LOG] type(\"12%\")=${r1.inspect(scope)}")
|
||||
println("[DEBUG_LOG] type(\"153\")=${r2.inspect(scope)}")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForInIterableBytecode() = runTest {
|
||||
val result = eval("""
|
||||
fun sumAll(x) {
|
||||
var s = 0
|
||||
for (i in x) s += i
|
||||
s
|
||||
}
|
||||
sumAll([1,2,3]) + sumAll(0..3)
|
||||
""".trimIndent())
|
||||
assertEquals(ObjInt(12), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testForInIterableUnknownTypeDisasm() = runTest {
|
||||
val scope = Script.newScope()
|
||||
scope.eval("""
|
||||
fun countAll(x) {
|
||||
var c = 0
|
||||
for (i in x) c++
|
||||
c
|
||||
}
|
||||
""".trimIndent())
|
||||
val disasm = scope.disassembleSymbol("countAll")
|
||||
println("[DEBUG_LOG] countAll disasm:\n$disasm")
|
||||
assertFalse(disasm.contains("not a compiled body"))
|
||||
assertFalse(disasm.contains("EVAL_FALLBACK"))
|
||||
val r1 = scope.eval("countAll([1,2,3])")
|
||||
val r2 = scope.eval("countAll(0..3)")
|
||||
assertEquals(ObjInt(3), r1)
|
||||
assertEquals(ObjInt(4), r2)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user