Add bytecode if support and test
This commit is contained in:
parent
ea877748e5
commit
6560457e3d
@ -2965,25 +2965,10 @@ class Compiler(
|
|||||||
return if (t2.type == Token.Type.ID && t2.value == "else") {
|
return if (t2.type == Token.Type.ID && t2.value == "else") {
|
||||||
val elseBody =
|
val elseBody =
|
||||||
parseStatement() ?: throw ScriptError(pos, "Bad else statement: expected statement")
|
parseStatement() ?: throw ScriptError(pos, "Bad else statement: expected statement")
|
||||||
return object : Statement() {
|
IfStatement(condition, ifBody, elseBody, start)
|
||||||
override val pos: Pos = start
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
|
||||||
return if (condition.execute(scope).toBool())
|
|
||||||
ifBody.execute(scope)
|
|
||||||
else
|
|
||||||
elseBody.execute(scope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
object : Statement() {
|
IfStatement(condition, ifBody, null, start)
|
||||||
override val pos: Pos = start
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
|
||||||
if (condition.execute(scope).toBool())
|
|
||||||
return ifBody.execute(scope)
|
|
||||||
return ObjVoid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,10 +17,19 @@
|
|||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
class BytecodeBuilder {
|
class BytecodeBuilder {
|
||||||
data class Instr(val op: Opcode, val operands: IntArray)
|
sealed interface Operand {
|
||||||
|
data class IntVal(val value: Int) : Operand
|
||||||
|
data class LabelRef(val label: Label) : Operand
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Label(val id: Int)
|
||||||
|
|
||||||
|
data class Instr(val op: Opcode, val operands: List<Operand>)
|
||||||
|
|
||||||
private val instructions = mutableListOf<Instr>()
|
private val instructions = mutableListOf<Instr>()
|
||||||
private val constPool = mutableListOf<BytecodeConst>()
|
private val constPool = mutableListOf<BytecodeConst>()
|
||||||
|
private val labelPositions = mutableMapOf<Label, Int>()
|
||||||
|
private var nextLabelId = 0
|
||||||
|
|
||||||
fun addConst(c: BytecodeConst): Int {
|
fun addConst(c: BytecodeConst): Int {
|
||||||
constPool += c
|
constPool += c
|
||||||
@ -28,7 +37,17 @@ class BytecodeBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun emit(op: Opcode, vararg operands: Int) {
|
fun emit(op: Opcode, vararg operands: Int) {
|
||||||
instructions += Instr(op, operands.copyOf())
|
instructions += Instr(op, operands.map { Operand.IntVal(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emit(op: Opcode, operands: List<Operand>) {
|
||||||
|
instructions += Instr(op, operands)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun label(): Label = Label(nextLabelId++)
|
||||||
|
|
||||||
|
fun mark(label: Label) {
|
||||||
|
labelPositions[label] = instructions.size
|
||||||
}
|
}
|
||||||
|
|
||||||
fun build(name: String, localCount: Int): BytecodeFunction {
|
fun build(name: String, localCount: Int): BytecodeFunction {
|
||||||
@ -39,15 +58,32 @@ class BytecodeBuilder {
|
|||||||
}
|
}
|
||||||
val constIdWidth = if (constPool.size < 65536) 2 else 4
|
val constIdWidth = if (constPool.size < 65536) 2 else 4
|
||||||
val ipWidth = 2
|
val ipWidth = 2
|
||||||
|
val instrOffsets = IntArray(instructions.size)
|
||||||
|
var currentIp = 0
|
||||||
|
for (i in instructions.indices) {
|
||||||
|
instrOffsets[i] = currentIp
|
||||||
|
val kinds = operandKinds(instructions[i].op)
|
||||||
|
currentIp += 1 + kinds.sumOf { operandWidth(it, slotWidth, constIdWidth, ipWidth) }
|
||||||
|
}
|
||||||
|
val labelIps = mutableMapOf<Label, Int>()
|
||||||
|
for ((label, idx) in labelPositions) {
|
||||||
|
labelIps[label] = instrOffsets.getOrNull(idx) ?: error("Invalid label index: $idx")
|
||||||
|
}
|
||||||
|
|
||||||
val code = ByteArrayOutput()
|
val code = ByteArrayOutput()
|
||||||
for (ins in instructions) {
|
for (ins in instructions) {
|
||||||
code.writeU8(ins.op.code.toInt() and 0xFF)
|
code.writeU8(ins.op.code and 0xFF)
|
||||||
val kinds = operandKinds(ins.op)
|
val kinds = operandKinds(ins.op)
|
||||||
if (kinds.size != ins.operands.size) {
|
if (kinds.size != ins.operands.size) {
|
||||||
error("Operand count mismatch for ${ins.op}: expected ${kinds.size}, got ${ins.operands.size}")
|
error("Operand count mismatch for ${ins.op}: expected ${kinds.size}, got ${ins.operands.size}")
|
||||||
}
|
}
|
||||||
for (i in kinds.indices) {
|
for (i in kinds.indices) {
|
||||||
val v = ins.operands[i]
|
val operand = ins.operands[i]
|
||||||
|
val v = when (operand) {
|
||||||
|
is Operand.IntVal -> operand.value
|
||||||
|
is Operand.LabelRef -> labelIps[operand.label]
|
||||||
|
?: error("Unknown label ${operand.label.id} for ${ins.op}")
|
||||||
|
}
|
||||||
when (kinds[i]) {
|
when (kinds[i]) {
|
||||||
OperandKind.SLOT -> code.writeUInt(v, slotWidth)
|
OperandKind.SLOT -> code.writeUInt(v, slotWidth)
|
||||||
OperandKind.CONST -> code.writeUInt(v, constIdWidth)
|
OperandKind.CONST -> code.writeUInt(v, constIdWidth)
|
||||||
@ -115,6 +151,16 @@ class BytecodeBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun operandWidth(kind: OperandKind, slotWidth: Int, constIdWidth: Int, ipWidth: Int): Int {
|
||||||
|
return when (kind) {
|
||||||
|
OperandKind.SLOT -> slotWidth
|
||||||
|
OperandKind.CONST -> constIdWidth
|
||||||
|
OperandKind.IP -> ipWidth
|
||||||
|
OperandKind.COUNT -> 2
|
||||||
|
OperandKind.ID -> 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private enum class OperandKind {
|
private enum class OperandKind {
|
||||||
SLOT,
|
SLOT,
|
||||||
CONST,
|
CONST,
|
||||||
|
|||||||
@ -17,12 +17,21 @@
|
|||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
import net.sergeych.lyng.ExpressionStatement
|
import net.sergeych.lyng.ExpressionStatement
|
||||||
|
import net.sergeych.lyng.IfStatement
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
|
|
||||||
class BytecodeCompiler {
|
class BytecodeCompiler {
|
||||||
private val builder = BytecodeBuilder()
|
private val builder = BytecodeBuilder()
|
||||||
private var nextSlot = 0
|
private var nextSlot = 0
|
||||||
|
|
||||||
|
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): BytecodeFunction? {
|
||||||
|
return when (stmt) {
|
||||||
|
is ExpressionStatement -> compileExpression(name, stmt)
|
||||||
|
is net.sergeych.lyng.IfStatement -> compileIf(name, stmt)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun compileExpression(name: String, stmt: ExpressionStatement): BytecodeFunction? {
|
fun compileExpression(name: String, stmt: ExpressionStatement): BytecodeFunction? {
|
||||||
val value = compileRef(stmt.ref) ?: return null
|
val value = compileRef(stmt.ref) ?: return null
|
||||||
builder.emit(Opcode.RET, value.slot)
|
builder.emit(Opcode.RET, value.slot)
|
||||||
@ -241,27 +250,61 @@ class BytecodeCompiler {
|
|||||||
return CompiledValue(slot, value.type)
|
return CompiledValue(slot, value.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun compileIf(name: String, stmt: IfStatement): BytecodeFunction? {
|
||||||
|
val conditionStmt = stmt.condition as? ExpressionStatement ?: return null
|
||||||
|
val condValue = compileRef(conditionStmt.ref) ?: return null
|
||||||
|
if (condValue.type != SlotType.BOOL) return null
|
||||||
|
|
||||||
|
val resultSlot = allocSlot()
|
||||||
|
val elseLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_FALSE,
|
||||||
|
listOf(BytecodeBuilder.Operand.IntVal(condValue.slot), BytecodeBuilder.Operand.LabelRef(elseLabel))
|
||||||
|
)
|
||||||
|
val thenValue = compileStatementValue(stmt.ifBody) ?: return null
|
||||||
|
emitMove(thenValue, resultSlot)
|
||||||
|
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
|
||||||
|
builder.mark(elseLabel)
|
||||||
|
if (stmt.elseBody != null) {
|
||||||
|
val elseValue = compileStatementValue(stmt.elseBody) ?: return null
|
||||||
|
emitMove(elseValue, resultSlot)
|
||||||
|
} else {
|
||||||
|
val id = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||||
|
builder.emit(Opcode.CONST_OBJ, id, resultSlot)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.mark(endLabel)
|
||||||
|
builder.emit(Opcode.RET, resultSlot)
|
||||||
|
val localCount = maxOf(nextSlot, resultSlot + 1)
|
||||||
|
return builder.build(name, localCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileStatementValue(stmt: net.sergeych.lyng.Statement): CompiledValue? {
|
||||||
|
return when (stmt) {
|
||||||
|
is ExpressionStatement -> compileRef(stmt.ref)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emitMove(value: CompiledValue, dstSlot: Int) {
|
||||||
|
when (value.type) {
|
||||||
|
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, dstSlot)
|
||||||
|
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, dstSlot)
|
||||||
|
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, dstSlot)
|
||||||
|
else -> builder.emit(Opcode.MOVE_OBJ, value.slot, dstSlot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
||||||
|
private fun refDepth(ref: LocalSlotRef): Int = ref.depth
|
||||||
private fun refDepth(ref: LocalSlotRef): Int = refDepthAccessor(ref)
|
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
||||||
|
private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
|
||||||
private fun binaryLeft(ref: BinaryOpRef): ObjRef = binaryLeftAccessor(ref)
|
private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op
|
||||||
private fun binaryRight(ref: BinaryOpRef): ObjRef = binaryRightAccessor(ref)
|
private fun unaryOperand(ref: UnaryOpRef): ObjRef = ref.a
|
||||||
private fun binaryOp(ref: BinaryOpRef): BinOp = binaryOpAccessor(ref)
|
private fun unaryOp(ref: UnaryOpRef): UnaryOp = ref.op
|
||||||
|
private fun assignTarget(ref: AssignRef): LocalSlotRef? = ref.target as? LocalSlotRef
|
||||||
private fun unaryOperand(ref: UnaryOpRef): ObjRef = unaryOperandAccessor(ref)
|
private fun assignValue(ref: AssignRef): ObjRef = ref.value
|
||||||
private fun unaryOp(ref: UnaryOpRef): UnaryOp = unaryOpAccessor(ref)
|
|
||||||
|
|
||||||
private fun assignTarget(ref: AssignRef): LocalSlotRef? = assignTargetAccessor(ref)
|
|
||||||
private fun assignValue(ref: AssignRef): ObjRef = assignValueAccessor(ref)
|
|
||||||
|
|
||||||
// Accessor helpers to avoid exposing fields directly in ObjRef classes.
|
|
||||||
private fun refDepthAccessor(ref: LocalSlotRef): Int = ref.depth
|
|
||||||
private fun binaryLeftAccessor(ref: BinaryOpRef): ObjRef = ref.left
|
|
||||||
private fun binaryRightAccessor(ref: BinaryOpRef): ObjRef = ref.right
|
|
||||||
private fun binaryOpAccessor(ref: BinaryOpRef): BinOp = ref.op
|
|
||||||
private fun unaryOperandAccessor(ref: UnaryOpRef): ObjRef = ref.a
|
|
||||||
private fun unaryOpAccessor(ref: UnaryOpRef): UnaryOp = ref.op
|
|
||||||
private fun assignTargetAccessor(ref: AssignRef): LocalSlotRef? = ref.target as? LocalSlotRef
|
|
||||||
private fun assignValueAccessor(ref: AssignRef): ObjRef = ref.value
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,6 +109,37 @@ class BytecodeVm {
|
|||||||
ip += fn.slotWidth
|
ip += fn.slotWidth
|
||||||
frame.setInt(dst, frame.getInt(a) + frame.getInt(b))
|
frame.setInt(dst, frame.getInt(a) + frame.getInt(b))
|
||||||
}
|
}
|
||||||
|
Opcode.CMP_LT_INT -> {
|
||||||
|
val a = decoder.readSlot(code, ip)
|
||||||
|
ip += fn.slotWidth
|
||||||
|
val b = decoder.readSlot(code, ip)
|
||||||
|
ip += fn.slotWidth
|
||||||
|
val dst = decoder.readSlot(code, ip)
|
||||||
|
ip += fn.slotWidth
|
||||||
|
frame.setBool(dst, frame.getInt(a) < frame.getInt(b))
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_INT -> {
|
||||||
|
val a = decoder.readSlot(code, ip)
|
||||||
|
ip += fn.slotWidth
|
||||||
|
val b = decoder.readSlot(code, ip)
|
||||||
|
ip += fn.slotWidth
|
||||||
|
val dst = decoder.readSlot(code, ip)
|
||||||
|
ip += fn.slotWidth
|
||||||
|
frame.setBool(dst, frame.getInt(a) == frame.getInt(b))
|
||||||
|
}
|
||||||
|
Opcode.JMP -> {
|
||||||
|
val target = decoder.readIp(code, ip, fn.ipWidth)
|
||||||
|
ip = target
|
||||||
|
}
|
||||||
|
Opcode.JMP_IF_FALSE -> {
|
||||||
|
val cond = decoder.readSlot(code, ip)
|
||||||
|
ip += fn.slotWidth
|
||||||
|
val target = decoder.readIp(code, ip, fn.ipWidth)
|
||||||
|
ip += fn.ipWidth
|
||||||
|
if (!frame.getBool(cond)) {
|
||||||
|
ip = target
|
||||||
|
}
|
||||||
|
}
|
||||||
Opcode.RET -> {
|
Opcode.RET -> {
|
||||||
val slot = decoder.readSlot(code, ip)
|
val slot = decoder.readSlot(code, ip)
|
||||||
return slotToObj(frame, slot)
|
return slotToObj(frame, slot)
|
||||||
|
|||||||
@ -20,6 +20,7 @@ package net.sergeych.lyng
|
|||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
import net.sergeych.lyng.obj.toBool
|
||||||
|
|
||||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||||
|
|
||||||
@ -63,6 +64,21 @@ abstract class Statement(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class IfStatement(
|
||||||
|
val condition: Statement,
|
||||||
|
val ifBody: Statement,
|
||||||
|
val elseBody: Statement?,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
return if (condition.execute(scope).toBool()) {
|
||||||
|
ifBody.execute(scope)
|
||||||
|
} else {
|
||||||
|
elseBody?.execute(scope) ?: ObjVoid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ExpressionStatement(
|
class ExpressionStatement(
|
||||||
val ref: net.sergeych.lyng.obj.ObjRef,
|
val ref: net.sergeych.lyng.obj.ObjRef,
|
||||||
override val pos: Pos
|
override val pos: Pos
|
||||||
|
|||||||
@ -14,11 +14,18 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import net.sergeych.lyng.ExpressionStatement
|
||||||
|
import net.sergeych.lyng.IfStatement
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.bytecode.BytecodeBuilder
|
import net.sergeych.lyng.bytecode.BytecodeBuilder
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeCompiler
|
||||||
import net.sergeych.lyng.bytecode.BytecodeConst
|
import net.sergeych.lyng.bytecode.BytecodeConst
|
||||||
import net.sergeych.lyng.bytecode.BytecodeVm
|
import net.sergeych.lyng.bytecode.BytecodeVm
|
||||||
import net.sergeych.lyng.bytecode.Opcode
|
import net.sergeych.lyng.bytecode.Opcode
|
||||||
|
import net.sergeych.lyng.obj.BinaryOpRef
|
||||||
|
import net.sergeych.lyng.obj.BinOp
|
||||||
|
import net.sergeych.lyng.obj.ConstRef
|
||||||
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
import net.sergeych.lyng.obj.toInt
|
import net.sergeych.lyng.obj.toInt
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
@ -37,4 +44,28 @@ class BytecodeVmTest {
|
|||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(5, result.toInt())
|
assertEquals(5, result.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun ifExpressionReturnsThenValue() = kotlinx.coroutines.test.runTest {
|
||||||
|
val cond = ExpressionStatement(
|
||||||
|
BinaryOpRef(
|
||||||
|
BinOp.LT,
|
||||||
|
ConstRef(ObjInt.of(2).asReadonly),
|
||||||
|
ConstRef(ObjInt.of(3).asReadonly),
|
||||||
|
),
|
||||||
|
net.sergeych.lyng.Pos.builtIn
|
||||||
|
)
|
||||||
|
val thenStmt = ExpressionStatement(
|
||||||
|
ConstRef(ObjInt.of(10).asReadonly),
|
||||||
|
net.sergeych.lyng.Pos.builtIn
|
||||||
|
)
|
||||||
|
val elseStmt = ExpressionStatement(
|
||||||
|
ConstRef(ObjInt.of(20).asReadonly),
|
||||||
|
net.sergeych.lyng.Pos.builtIn
|
||||||
|
)
|
||||||
|
val ifStmt = IfStatement(cond, thenStmt, elseStmt, net.sergeych.lyng.Pos.builtIn)
|
||||||
|
val fn = BytecodeCompiler().compileStatement("ifTest", ifStmt) ?: error("bytecode compile failed")
|
||||||
|
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||||
|
assertEquals(10, result.toInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user