Compare commits
10 Commits
bc9e557814
...
b4598bff98
| Author | SHA1 | Date | |
|---|---|---|---|
| b4598bff98 | |||
| fd1548c86c | |||
| 9c56cf751b | |||
| 8ae6eb8d69 | |||
| 3d9170d677 | |||
| c8a8b12dfc | |||
| 6560457e3d | |||
| ea877748e5 | |||
| d8b00a805c | |||
| f42ea0a04c |
@ -149,6 +149,16 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
|
||||
- CMP_GT_REAL_INT S, S -> S
|
||||
- CMP_GTE_INT_REAL S, S -> S
|
||||
- CMP_GTE_REAL_INT S, S -> S
|
||||
- CMP_NEQ_INT_REAL S, S -> S
|
||||
- CMP_NEQ_REAL_INT S, S -> S
|
||||
- CMP_EQ_OBJ S, S -> S
|
||||
- CMP_NEQ_OBJ S, S -> S
|
||||
- CMP_REF_EQ_OBJ S, S -> S
|
||||
- CMP_REF_NEQ_OBJ S, S -> S
|
||||
- CMP_LT_OBJ S, S -> S
|
||||
- CMP_LTE_OBJ S, S -> S
|
||||
- CMP_GT_OBJ S, S -> S
|
||||
- CMP_GTE_OBJ S, S -> S
|
||||
|
||||
### Boolean ops
|
||||
- NOT_BOOL S -> S
|
||||
@ -176,7 +186,24 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
|
||||
### Fallback
|
||||
- EVAL_FALLBACK T -> S
|
||||
|
||||
## 6) Function Header (binary container)
|
||||
## 6) Const Pool Encoding (v0)
|
||||
|
||||
Each const entry is encoded as:
|
||||
[tag:U8] [payload...]
|
||||
|
||||
Tags:
|
||||
- 0x00: NULL
|
||||
- 0x01: BOOL (payload: U8 0/1)
|
||||
- 0x02: INT (payload: S64, little-endian)
|
||||
- 0x03: REAL (payload: F64, IEEE-754, little-endian)
|
||||
- 0x04: STRING (payload: U32 length + UTF-8 bytes)
|
||||
- 0x05: OBJ_REF (payload: U32 index into external Obj table)
|
||||
|
||||
Notes:
|
||||
- OBJ_REF is reserved for embedding prebuilt Obj handles if needed.
|
||||
- Strings use UTF-8; length is bytes, not chars.
|
||||
|
||||
## 7) Function Header (binary container)
|
||||
|
||||
Suggested layout for a bytecode function blob:
|
||||
- magic: U32 ("LYBC")
|
||||
@ -190,10 +217,34 @@ Suggested layout for a bytecode function blob:
|
||||
- constPool: [const entries...]
|
||||
- code: [bytecode...]
|
||||
|
||||
Const pool entries are encoded as type-tagged values (Obj/Int/Real/Bool/String)
|
||||
in a simple tagged format. This is intentionally unspecified in v0.
|
||||
Const pool entries use the encoding described in section 6.
|
||||
|
||||
## 7) Notes
|
||||
## 8) Sample Bytecode (illustrative)
|
||||
|
||||
Example Lyng:
|
||||
val x = 2
|
||||
val y = 3
|
||||
val z = x + y
|
||||
|
||||
Assume:
|
||||
- localCount = 3 (x,y,z)
|
||||
- argCount = 0
|
||||
- slot width = 1 byte
|
||||
- const pool: [INT 2, INT 3]
|
||||
|
||||
Bytecode:
|
||||
CONST_INT k0 -> s0
|
||||
CONST_INT k1 -> s1
|
||||
ADD_INT s0, s1 -> s2
|
||||
RET_VOID
|
||||
|
||||
Encoded (opcode values symbolic):
|
||||
[OP_CONST_INT][k0][s0]
|
||||
[OP_CONST_INT][k1][s1]
|
||||
[OP_ADD_INT][s0][s1][s2]
|
||||
[OP_RET_VOID]
|
||||
|
||||
## 9) Notes
|
||||
|
||||
- Mixed-mode is allowed: compiler can emit FALLBACK ops for unsupported nodes.
|
||||
- The VM must be suspendable; on suspension, store ip + minimal operand state.
|
||||
|
||||
@ -2965,25 +2965,10 @@ class Compiler(
|
||||
return if (t2.type == Token.Type.ID && t2.value == "else") {
|
||||
val elseBody =
|
||||
parseStatement() ?: throw ScriptError(pos, "Bad else statement: expected statement")
|
||||
return object : Statement() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
IfStatement(condition, ifBody, elseBody, start)
|
||||
} else {
|
||||
cc.previous()
|
||||
object : Statement() {
|
||||
override val pos: Pos = start
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
if (condition.execute(scope).toBool())
|
||||
return ifBody.execute(scope)
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
IfStatement(condition, ifBody, null, start)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,199 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
class BytecodeBuilder {
|
||||
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 constPool = mutableListOf<BytecodeConst>()
|
||||
private val labelPositions = mutableMapOf<Label, Int>()
|
||||
private var nextLabelId = 0
|
||||
private val fallbackStatements = mutableListOf<net.sergeych.lyng.Statement>()
|
||||
|
||||
fun addConst(c: BytecodeConst): Int {
|
||||
constPool += c
|
||||
return constPool.lastIndex
|
||||
}
|
||||
|
||||
fun emit(op: Opcode, vararg operands: Int) {
|
||||
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 addFallback(stmt: net.sergeych.lyng.Statement): Int {
|
||||
fallbackStatements += stmt
|
||||
return fallbackStatements.lastIndex
|
||||
}
|
||||
|
||||
fun build(name: String, localCount: Int): BytecodeFunction {
|
||||
val slotWidth = when {
|
||||
localCount < 256 -> 1
|
||||
localCount < 65536 -> 2
|
||||
else -> 4
|
||||
}
|
||||
val constIdWidth = if (constPool.size < 65536) 2 else 4
|
||||
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()
|
||||
for (ins in instructions) {
|
||||
code.writeU8(ins.op.code and 0xFF)
|
||||
val kinds = operandKinds(ins.op)
|
||||
if (kinds.size != ins.operands.size) {
|
||||
error("Operand count mismatch for ${ins.op}: expected ${kinds.size}, got ${ins.operands.size}")
|
||||
}
|
||||
for (i in kinds.indices) {
|
||||
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]) {
|
||||
OperandKind.SLOT -> code.writeUInt(v, slotWidth)
|
||||
OperandKind.CONST -> code.writeUInt(v, constIdWidth)
|
||||
OperandKind.IP -> code.writeUInt(v, ipWidth)
|
||||
OperandKind.COUNT -> code.writeUInt(v, 2)
|
||||
OperandKind.ID -> code.writeUInt(v, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
return BytecodeFunction(
|
||||
name = name,
|
||||
localCount = localCount,
|
||||
slotWidth = slotWidth,
|
||||
ipWidth = ipWidth,
|
||||
constIdWidth = constIdWidth,
|
||||
constants = constPool.toList(),
|
||||
fallbackStatements = fallbackStatements.toList(),
|
||||
code = code.toByteArray()
|
||||
)
|
||||
}
|
||||
|
||||
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||
return when (op) {
|
||||
Opcode.NOP, Opcode.RET_VOID -> emptyList()
|
||||
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL,
|
||||
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.CONST_NULL ->
|
||||
listOf(OperandKind.SLOT)
|
||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
||||
Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
||||
Opcode.CMP_EQ_INT, Opcode.CMP_NEQ_INT, Opcode.CMP_LT_INT, Opcode.CMP_LTE_INT,
|
||||
Opcode.CMP_GT_INT, Opcode.CMP_GTE_INT,
|
||||
Opcode.CMP_EQ_REAL, Opcode.CMP_NEQ_REAL, Opcode.CMP_LT_REAL, Opcode.CMP_LTE_REAL,
|
||||
Opcode.CMP_GT_REAL, Opcode.CMP_GTE_REAL,
|
||||
Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL,
|
||||
Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT,
|
||||
Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT,
|
||||
Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT,
|
||||
Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ,
|
||||
Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ,
|
||||
Opcode.AND_BOOL, Opcode.OR_BOOL ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
|
||||
listOf(OperandKind.SLOT)
|
||||
Opcode.JMP ->
|
||||
listOf(OperandKind.IP)
|
||||
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_VIRTUAL ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.GET_FIELD ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.SET_FIELD ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.GET_INDEX ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.SET_INDEX ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.EVAL_FALLBACK ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
SLOT,
|
||||
CONST,
|
||||
IP,
|
||||
COUNT,
|
||||
ID,
|
||||
}
|
||||
|
||||
private class ByteArrayOutput {
|
||||
private val data = ArrayList<Byte>(256)
|
||||
|
||||
fun writeU8(v: Int) {
|
||||
data.add((v and 0xFF).toByte())
|
||||
}
|
||||
|
||||
fun writeUInt(v: Int, width: Int) {
|
||||
var value = v
|
||||
var remaining = width
|
||||
while (remaining-- > 0) {
|
||||
writeU8(value)
|
||||
value = value ushr 8
|
||||
}
|
||||
}
|
||||
|
||||
fun toByteArray(): ByteArray = data.toByteArray()
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,623 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
import net.sergeych.lyng.ExpressionStatement
|
||||
import net.sergeych.lyng.IfStatement
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.ToBoolStatement
|
||||
import net.sergeych.lyng.obj.*
|
||||
|
||||
class BytecodeCompiler {
|
||||
private val builder = BytecodeBuilder()
|
||||
private var nextSlot = 0
|
||||
private val slotTypes = mutableMapOf<Int, SlotType>()
|
||||
|
||||
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? {
|
||||
val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null
|
||||
builder.emit(Opcode.RET, value.slot)
|
||||
val localCount = maxOf(nextSlot, value.slot + 1)
|
||||
return builder.build(name, localCount)
|
||||
}
|
||||
|
||||
private data class CompiledValue(val slot: Int, val type: SlotType)
|
||||
|
||||
private fun allocSlot(): Int = nextSlot++
|
||||
|
||||
private fun compileRef(ref: ObjRef): CompiledValue? {
|
||||
return when (ref) {
|
||||
is ConstRef -> compileConst(ref.constValue)
|
||||
is LocalSlotRef -> {
|
||||
if (ref.name.isEmpty()) return null
|
||||
if (refDepth(ref) != 0) return null
|
||||
CompiledValue(refSlot(ref), slotTypes[refSlot(ref)] ?: SlotType.UNKNOWN)
|
||||
}
|
||||
is BinaryOpRef -> compileBinary(ref)
|
||||
is UnaryOpRef -> compileUnary(ref)
|
||||
is AssignRef -> compileAssign(ref)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileConst(obj: Obj): CompiledValue? {
|
||||
val slot = allocSlot()
|
||||
when (obj) {
|
||||
is ObjInt -> {
|
||||
val id = builder.addConst(BytecodeConst.IntVal(obj.value))
|
||||
builder.emit(Opcode.CONST_INT, id, slot)
|
||||
return CompiledValue(slot, SlotType.INT)
|
||||
}
|
||||
is ObjReal -> {
|
||||
val id = builder.addConst(BytecodeConst.RealVal(obj.value))
|
||||
builder.emit(Opcode.CONST_REAL, id, slot)
|
||||
return CompiledValue(slot, SlotType.REAL)
|
||||
}
|
||||
is ObjBool -> {
|
||||
val id = builder.addConst(BytecodeConst.Bool(obj.value))
|
||||
builder.emit(Opcode.CONST_BOOL, id, slot)
|
||||
return CompiledValue(slot, SlotType.BOOL)
|
||||
}
|
||||
is ObjString -> {
|
||||
val id = builder.addConst(BytecodeConst.StringVal(obj.value))
|
||||
builder.emit(Opcode.CONST_OBJ, id, slot)
|
||||
return CompiledValue(slot, SlotType.OBJ)
|
||||
}
|
||||
ObjNull -> {
|
||||
builder.emit(Opcode.CONST_NULL, slot)
|
||||
return CompiledValue(slot, SlotType.OBJ)
|
||||
}
|
||||
else -> {
|
||||
val id = builder.addConst(BytecodeConst.ObjRef(obj))
|
||||
builder.emit(Opcode.CONST_OBJ, id, slot)
|
||||
return CompiledValue(slot, SlotType.OBJ)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileUnary(ref: UnaryOpRef): CompiledValue? {
|
||||
val a = compileRef(unaryOperand(ref)) ?: return null
|
||||
val out = allocSlot()
|
||||
return when (unaryOp(ref)) {
|
||||
UnaryOp.NEGATE -> when (a.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.NEG_INT, a.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.REAL -> {
|
||||
builder.emit(Opcode.NEG_REAL, a.slot, out)
|
||||
CompiledValue(out, SlotType.REAL)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
UnaryOp.NOT -> {
|
||||
if (a.type != SlotType.BOOL) return null
|
||||
builder.emit(Opcode.NOT_BOOL, a.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
UnaryOp.BITNOT -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
builder.emit(Opcode.INV_INT, a.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileBinary(ref: BinaryOpRef): CompiledValue? {
|
||||
val op = binaryOp(ref)
|
||||
if (op == BinOp.AND || op == BinOp.OR) {
|
||||
return compileLogical(op, binaryLeft(ref), binaryRight(ref), refPos(ref))
|
||||
}
|
||||
val a = compileRef(binaryLeft(ref)) ?: return null
|
||||
val b = compileRef(binaryRight(ref)) ?: return null
|
||||
val typesMismatch = a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN
|
||||
if (typesMismatch && op !in setOf(BinOp.EQ, BinOp.NEQ, BinOp.LT, BinOp.LTE, BinOp.GT, BinOp.GTE)) {
|
||||
return null
|
||||
}
|
||||
val out = allocSlot()
|
||||
return when (op) {
|
||||
BinOp.PLUS -> when (a.type) {
|
||||
SlotType.INT -> {
|
||||
when (b.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.ADD_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
SlotType.REAL -> {
|
||||
when (b.type) {
|
||||
SlotType.REAL -> {
|
||||
builder.emit(Opcode.ADD_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.REAL)
|
||||
}
|
||||
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.ADD_REAL, a, b, out)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
BinOp.MINUS -> when (a.type) {
|
||||
SlotType.INT -> {
|
||||
when (b.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.SUB_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
SlotType.REAL -> {
|
||||
when (b.type) {
|
||||
SlotType.REAL -> {
|
||||
builder.emit(Opcode.SUB_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.REAL)
|
||||
}
|
||||
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.SUB_REAL, a, b, out)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
BinOp.STAR -> when (a.type) {
|
||||
SlotType.INT -> {
|
||||
when (b.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.MUL_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
SlotType.REAL -> {
|
||||
when (b.type) {
|
||||
SlotType.REAL -> {
|
||||
builder.emit(Opcode.MUL_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.REAL)
|
||||
}
|
||||
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.MUL_REAL, a, b, out)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
BinOp.SLASH -> when (a.type) {
|
||||
SlotType.INT -> {
|
||||
when (b.type) {
|
||||
SlotType.INT -> {
|
||||
builder.emit(Opcode.DIV_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
SlotType.REAL -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
SlotType.REAL -> {
|
||||
when (b.type) {
|
||||
SlotType.REAL -> {
|
||||
builder.emit(Opcode.DIV_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.REAL)
|
||||
}
|
||||
SlotType.INT -> compileRealArithmeticWithCoercion(Opcode.DIV_REAL, a, b, out)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
BinOp.PERCENT -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
builder.emit(Opcode.MOD_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
BinOp.EQ -> {
|
||||
compileCompareEq(a, b, out)
|
||||
}
|
||||
BinOp.NEQ -> {
|
||||
compileCompareNeq(a, b, out)
|
||||
}
|
||||
BinOp.LT -> {
|
||||
compileCompareLt(a, b, out)
|
||||
}
|
||||
BinOp.LTE -> {
|
||||
compileCompareLte(a, b, out)
|
||||
}
|
||||
BinOp.GT -> {
|
||||
compileCompareGt(a, b, out)
|
||||
}
|
||||
BinOp.GTE -> {
|
||||
compileCompareGte(a, b, out)
|
||||
}
|
||||
BinOp.REF_EQ -> {
|
||||
if (a.type != SlotType.OBJ || b.type != SlotType.OBJ) return null
|
||||
builder.emit(Opcode.CMP_REF_EQ_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
BinOp.REF_NEQ -> {
|
||||
if (a.type != SlotType.OBJ || b.type != SlotType.OBJ) return null
|
||||
builder.emit(Opcode.CMP_REF_NEQ_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
BinOp.AND -> {
|
||||
if (a.type != SlotType.BOOL) return null
|
||||
builder.emit(Opcode.AND_BOOL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
BinOp.OR -> {
|
||||
if (a.type != SlotType.BOOL) return null
|
||||
builder.emit(Opcode.OR_BOOL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
BinOp.BAND -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
builder.emit(Opcode.AND_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
BinOp.BOR -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
builder.emit(Opcode.OR_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
BinOp.BXOR -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
builder.emit(Opcode.XOR_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
BinOp.SHL -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
builder.emit(Opcode.SHL_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
BinOp.SHR -> {
|
||||
if (a.type != SlotType.INT) return null
|
||||
builder.emit(Opcode.SHR_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.INT)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileRealArithmeticWithCoercion(
|
||||
op: Opcode,
|
||||
a: CompiledValue,
|
||||
b: CompiledValue,
|
||||
out: Int
|
||||
): CompiledValue? {
|
||||
if (a.type == SlotType.INT && b.type == SlotType.REAL) {
|
||||
val left = allocSlot()
|
||||
builder.emit(Opcode.INT_TO_REAL, a.slot, left)
|
||||
builder.emit(op, left, b.slot, out)
|
||||
return CompiledValue(out, SlotType.REAL)
|
||||
}
|
||||
if (a.type == SlotType.REAL && b.type == SlotType.INT) {
|
||||
val right = allocSlot()
|
||||
builder.emit(Opcode.INT_TO_REAL, b.slot, right)
|
||||
builder.emit(op, a.slot, right, out)
|
||||
return CompiledValue(out, SlotType.REAL)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun compileCompareEq(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
|
||||
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
|
||||
return when {
|
||||
a.type == SlotType.INT && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_EQ_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_EQ_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.BOOL && b.type == SlotType.BOOL -> {
|
||||
builder.emit(Opcode.CMP_EQ_BOOL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.INT && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_EQ_INT_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_EQ_REAL_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
|
||||
builder.emit(Opcode.CMP_EQ_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileCompareNeq(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
|
||||
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
|
||||
return when {
|
||||
a.type == SlotType.INT && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_NEQ_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_NEQ_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.BOOL && b.type == SlotType.BOOL -> {
|
||||
builder.emit(Opcode.CMP_NEQ_BOOL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.INT && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_NEQ_INT_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_NEQ_REAL_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
|
||||
builder.emit(Opcode.CMP_NEQ_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileCompareLt(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
|
||||
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
|
||||
return when {
|
||||
a.type == SlotType.INT && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_LT_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_LT_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.INT && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_LT_INT_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_LT_REAL_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
|
||||
builder.emit(Opcode.CMP_LT_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileCompareLte(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
|
||||
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
|
||||
return when {
|
||||
a.type == SlotType.INT && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_LTE_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_LTE_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.INT && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_LTE_INT_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_LTE_REAL_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
|
||||
builder.emit(Opcode.CMP_LTE_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileCompareGt(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
|
||||
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
|
||||
return when {
|
||||
a.type == SlotType.INT && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_GT_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_GT_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.INT && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_GT_INT_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_GT_REAL_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
|
||||
builder.emit(Opcode.CMP_GT_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileCompareGte(a: CompiledValue, b: CompiledValue, out: Int): CompiledValue? {
|
||||
if (a.type == SlotType.UNKNOWN || b.type == SlotType.UNKNOWN) return null
|
||||
return when {
|
||||
a.type == SlotType.INT && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_GTE_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_GTE_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.INT && b.type == SlotType.REAL -> {
|
||||
builder.emit(Opcode.CMP_GTE_INT_REAL, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.REAL && b.type == SlotType.INT -> {
|
||||
builder.emit(Opcode.CMP_GTE_REAL_INT, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
a.type == SlotType.OBJ && b.type == SlotType.OBJ -> {
|
||||
builder.emit(Opcode.CMP_GTE_OBJ, a.slot, b.slot, out)
|
||||
CompiledValue(out, SlotType.BOOL)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileLogical(op: BinOp, left: ObjRef, right: ObjRef, pos: Pos): CompiledValue? {
|
||||
val leftValue = compileRefWithFallback(left, SlotType.BOOL, pos) ?: return null
|
||||
if (leftValue.type != SlotType.BOOL) return null
|
||||
val resultSlot = allocSlot()
|
||||
val shortLabel = builder.label()
|
||||
val endLabel = builder.label()
|
||||
if (op == BinOp.AND) {
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_FALSE,
|
||||
listOf(BytecodeBuilder.Operand.IntVal(leftValue.slot), BytecodeBuilder.Operand.LabelRef(shortLabel))
|
||||
)
|
||||
} else {
|
||||
builder.emit(
|
||||
Opcode.JMP_IF_TRUE,
|
||||
listOf(BytecodeBuilder.Operand.IntVal(leftValue.slot), BytecodeBuilder.Operand.LabelRef(shortLabel))
|
||||
)
|
||||
}
|
||||
val rightValue = compileRefWithFallback(right, SlotType.BOOL, pos) ?: return null
|
||||
emitMove(rightValue, resultSlot)
|
||||
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
||||
builder.mark(shortLabel)
|
||||
val constId = builder.addConst(BytecodeConst.Bool(op == BinOp.OR))
|
||||
builder.emit(Opcode.CONST_BOOL, constId, resultSlot)
|
||||
builder.mark(endLabel)
|
||||
return CompiledValue(resultSlot, SlotType.BOOL)
|
||||
}
|
||||
|
||||
private fun compileAssign(ref: AssignRef): CompiledValue? {
|
||||
val target = assignTarget(ref) ?: return null
|
||||
if (refDepth(target) != 0) return null
|
||||
val value = compileRef(assignValue(ref)) ?: return null
|
||||
val slot = refSlot(target)
|
||||
when (value.type) {
|
||||
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
|
||||
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot)
|
||||
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot)
|
||||
else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot)
|
||||
}
|
||||
updateSlotType(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 = 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()
|
||||
|
||||
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: Statement): CompiledValue? {
|
||||
return when (stmt) {
|
||||
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
||||
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 compileRefWithFallback(ref: ObjRef, forceType: SlotType?, pos: Pos): CompiledValue? {
|
||||
val compiled = compileRef(ref)
|
||||
if (compiled != null && (forceType == null || compiled.type == forceType || compiled.type == SlotType.UNKNOWN)) {
|
||||
return if (forceType != null && compiled.type == SlotType.UNKNOWN) {
|
||||
CompiledValue(compiled.slot, forceType)
|
||||
} else compiled
|
||||
}
|
||||
val slot = allocSlot()
|
||||
val stmt = if (forceType == SlotType.BOOL) {
|
||||
ToBoolStatement(ExpressionStatement(ref, pos), pos)
|
||||
} else {
|
||||
ExpressionStatement(ref, pos)
|
||||
}
|
||||
val id = builder.addFallback(stmt)
|
||||
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
|
||||
updateSlotType(slot, forceType ?: SlotType.OBJ)
|
||||
return CompiledValue(slot, forceType ?: SlotType.OBJ)
|
||||
}
|
||||
|
||||
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
||||
private fun refDepth(ref: LocalSlotRef): Int = ref.depth
|
||||
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
||||
private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
|
||||
private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op
|
||||
private fun unaryOperand(ref: UnaryOpRef): ObjRef = ref.a
|
||||
private fun unaryOp(ref: UnaryOpRef): UnaryOp = ref.op
|
||||
private fun assignTarget(ref: AssignRef): LocalSlotRef? = ref.target as? LocalSlotRef
|
||||
private fun assignValue(ref: AssignRef): ObjRef = ref.value
|
||||
private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn
|
||||
|
||||
private fun updateSlotType(slot: Int, type: SlotType) {
|
||||
if (type == SlotType.UNKNOWN) {
|
||||
slotTypes.remove(slot)
|
||||
} else {
|
||||
slotTypes[slot] = type
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
|
||||
sealed class BytecodeConst {
|
||||
object Null : BytecodeConst()
|
||||
data class Bool(val value: Boolean) : BytecodeConst()
|
||||
data class IntVal(val value: Long) : BytecodeConst()
|
||||
data class RealVal(val value: Double) : BytecodeConst()
|
||||
data class StringVal(val value: String) : BytecodeConst()
|
||||
data class ObjRef(val value: Obj) : BytecodeConst()
|
||||
}
|
||||
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
interface BytecodeDecoder {
|
||||
fun readOpcode(code: ByteArray, ip: Int): Opcode
|
||||
fun readSlot(code: ByteArray, ip: Int): Int
|
||||
fun readConstId(code: ByteArray, ip: Int, width: Int): Int
|
||||
fun readIp(code: ByteArray, ip: Int, width: Int): Int
|
||||
}
|
||||
|
||||
object Decoder8 : BytecodeDecoder {
|
||||
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
|
||||
Opcode.fromCode(code[ip].toInt() and 0xFF) ?: error("Unknown opcode: ${code[ip]}")
|
||||
|
||||
override fun readSlot(code: ByteArray, ip: Int): Int = code[ip].toInt() and 0xFF
|
||||
|
||||
override fun readConstId(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
|
||||
override fun readIp(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
}
|
||||
|
||||
object Decoder16 : BytecodeDecoder {
|
||||
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
|
||||
Opcode.fromCode(code[ip].toInt() and 0xFF) ?: error("Unknown opcode: ${code[ip]}")
|
||||
|
||||
override fun readSlot(code: ByteArray, ip: Int): Int =
|
||||
(code[ip].toInt() and 0xFF) or ((code[ip + 1].toInt() and 0xFF) shl 8)
|
||||
|
||||
override fun readConstId(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
|
||||
override fun readIp(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
}
|
||||
|
||||
object Decoder32 : BytecodeDecoder {
|
||||
override fun readOpcode(code: ByteArray, ip: Int): Opcode =
|
||||
Opcode.fromCode(code[ip].toInt() and 0xFF) ?: error("Unknown opcode: ${code[ip]}")
|
||||
|
||||
override fun readSlot(code: ByteArray, ip: Int): Int = readUInt(code, ip, 4)
|
||||
|
||||
override fun readConstId(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
|
||||
override fun readIp(code: ByteArray, ip: Int, width: Int): Int =
|
||||
readUInt(code, ip, width)
|
||||
}
|
||||
|
||||
private fun readUInt(code: ByteArray, ip: Int, width: Int): Int {
|
||||
var result = 0
|
||||
var shift = 0
|
||||
var idx = ip
|
||||
var remaining = width
|
||||
while (remaining-- > 0) {
|
||||
result = result or ((code[idx].toInt() and 0xFF) shl shift)
|
||||
shift += 8
|
||||
idx++
|
||||
}
|
||||
return result
|
||||
}
|
||||
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
object BytecodeDisassembler {
|
||||
fun disassemble(fn: BytecodeFunction): String {
|
||||
val decoder = when (fn.slotWidth) {
|
||||
1 -> Decoder8
|
||||
2 -> Decoder16
|
||||
4 -> Decoder32
|
||||
else -> error("Unsupported slot width: ${fn.slotWidth}")
|
||||
}
|
||||
val out = StringBuilder()
|
||||
val code = fn.code
|
||||
var ip = 0
|
||||
while (ip < code.size) {
|
||||
val op = decoder.readOpcode(code, ip)
|
||||
val startIp = ip
|
||||
ip += 1
|
||||
val kinds = operandKinds(op)
|
||||
val operands = ArrayList<String>(kinds.size)
|
||||
for (kind in kinds) {
|
||||
when (kind) {
|
||||
OperandKind.SLOT -> {
|
||||
val v = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
operands += "s$v"
|
||||
}
|
||||
OperandKind.CONST -> {
|
||||
val v = decoder.readConstId(code, ip, fn.constIdWidth)
|
||||
ip += fn.constIdWidth
|
||||
operands += "k$v"
|
||||
}
|
||||
OperandKind.IP -> {
|
||||
val v = decoder.readIp(code, ip, fn.ipWidth)
|
||||
ip += fn.ipWidth
|
||||
operands += "ip$v"
|
||||
}
|
||||
OperandKind.COUNT -> {
|
||||
val v = decoder.readConstId(code, ip, 2)
|
||||
ip += 2
|
||||
operands += "n$v"
|
||||
}
|
||||
OperandKind.ID -> {
|
||||
val v = decoder.readConstId(code, ip, 2)
|
||||
ip += 2
|
||||
operands += "#$v"
|
||||
}
|
||||
}
|
||||
}
|
||||
out.append(startIp).append(": ").append(op.name)
|
||||
if (operands.isNotEmpty()) {
|
||||
out.append(' ').append(operands.joinToString(", "))
|
||||
}
|
||||
out.append('\n')
|
||||
}
|
||||
return out.toString()
|
||||
}
|
||||
|
||||
private enum class OperandKind {
|
||||
SLOT,
|
||||
CONST,
|
||||
IP,
|
||||
COUNT,
|
||||
ID,
|
||||
}
|
||||
|
||||
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||
return when (op) {
|
||||
Opcode.NOP, Opcode.RET_VOID -> emptyList()
|
||||
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL,
|
||||
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.CONST_NULL ->
|
||||
listOf(OperandKind.SLOT)
|
||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
||||
Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
||||
Opcode.CMP_EQ_INT, Opcode.CMP_NEQ_INT, Opcode.CMP_LT_INT, Opcode.CMP_LTE_INT,
|
||||
Opcode.CMP_GT_INT, Opcode.CMP_GTE_INT,
|
||||
Opcode.CMP_EQ_REAL, Opcode.CMP_NEQ_REAL, Opcode.CMP_LT_REAL, Opcode.CMP_LTE_REAL,
|
||||
Opcode.CMP_GT_REAL, Opcode.CMP_GTE_REAL,
|
||||
Opcode.CMP_EQ_BOOL, Opcode.CMP_NEQ_BOOL,
|
||||
Opcode.CMP_EQ_INT_REAL, Opcode.CMP_EQ_REAL_INT, Opcode.CMP_LT_INT_REAL, Opcode.CMP_LT_REAL_INT,
|
||||
Opcode.CMP_LTE_INT_REAL, Opcode.CMP_LTE_REAL_INT, Opcode.CMP_GT_INT_REAL, Opcode.CMP_GT_REAL_INT,
|
||||
Opcode.CMP_GTE_INT_REAL, Opcode.CMP_GTE_REAL_INT, Opcode.CMP_NEQ_INT_REAL, Opcode.CMP_NEQ_REAL_INT,
|
||||
Opcode.CMP_EQ_OBJ, Opcode.CMP_NEQ_OBJ, Opcode.CMP_REF_EQ_OBJ, Opcode.CMP_REF_NEQ_OBJ,
|
||||
Opcode.CMP_LT_OBJ, Opcode.CMP_LTE_OBJ, Opcode.CMP_GT_OBJ, Opcode.CMP_GTE_OBJ,
|
||||
Opcode.AND_BOOL, Opcode.OR_BOOL ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
|
||||
listOf(OperandKind.SLOT)
|
||||
Opcode.JMP ->
|
||||
listOf(OperandKind.IP)
|
||||
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
|
||||
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.CALL_VIRTUAL ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||
Opcode.GET_FIELD ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.SET_FIELD ->
|
||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
|
||||
Opcode.GET_INDEX ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.SET_INDEX ->
|
||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||
Opcode.EVAL_FALLBACK ->
|
||||
listOf(OperandKind.ID, OperandKind.SLOT)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjNull
|
||||
|
||||
class BytecodeFrame(
|
||||
val localCount: Int,
|
||||
val argCount: Int,
|
||||
) {
|
||||
val slotCount: Int = localCount + argCount
|
||||
val argBase: Int = localCount
|
||||
|
||||
private val slotTypes: ByteArray = ByteArray(slotCount) { SlotType.UNKNOWN.code }
|
||||
private val objSlots: Array<Obj?> = arrayOfNulls(slotCount)
|
||||
private val intSlots: LongArray = LongArray(slotCount)
|
||||
private val realSlots: DoubleArray = DoubleArray(slotCount)
|
||||
private val boolSlots: BooleanArray = BooleanArray(slotCount)
|
||||
|
||||
fun getSlotType(slot: Int): SlotType = SlotType.values().first { it.code == slotTypes[slot] }
|
||||
fun getSlotTypeCode(slot: Int): Byte = slotTypes[slot]
|
||||
fun setSlotType(slot: Int, type: SlotType) {
|
||||
slotTypes[slot] = type.code
|
||||
}
|
||||
|
||||
fun getObj(slot: Int): Obj = objSlots[slot] ?: ObjNull
|
||||
fun setObj(slot: Int, value: Obj) {
|
||||
objSlots[slot] = value
|
||||
slotTypes[slot] = SlotType.OBJ.code
|
||||
}
|
||||
|
||||
fun getInt(slot: Int): Long = intSlots[slot]
|
||||
fun setInt(slot: Int, value: Long) {
|
||||
intSlots[slot] = value
|
||||
slotTypes[slot] = SlotType.INT.code
|
||||
}
|
||||
|
||||
fun getReal(slot: Int): Double = realSlots[slot]
|
||||
fun setReal(slot: Int, value: Double) {
|
||||
realSlots[slot] = value
|
||||
slotTypes[slot] = SlotType.REAL.code
|
||||
}
|
||||
|
||||
fun getBool(slot: Int): Boolean = boolSlots[slot]
|
||||
fun setBool(slot: Int, value: Boolean) {
|
||||
boolSlots[slot] = value
|
||||
slotTypes[slot] = SlotType.BOOL.code
|
||||
}
|
||||
|
||||
fun clearSlot(slot: Int) {
|
||||
slotTypes[slot] = SlotType.UNKNOWN.code
|
||||
objSlots[slot] = null
|
||||
intSlots[slot] = 0L
|
||||
realSlots[slot] = 0.0
|
||||
boolSlots[slot] = false
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
data class BytecodeFunction(
|
||||
val name: String,
|
||||
val localCount: Int,
|
||||
val slotWidth: Int,
|
||||
val ipWidth: Int,
|
||||
val constIdWidth: Int,
|
||||
val constants: List<BytecodeConst>,
|
||||
val fallbackStatements: List<net.sergeych.lyng.Statement>,
|
||||
val code: ByteArray,
|
||||
) {
|
||||
init {
|
||||
require(slotWidth == 1 || slotWidth == 2 || slotWidth == 4) { "slotWidth must be 1,2,4" }
|
||||
require(ipWidth == 2 || ipWidth == 4) { "ipWidth must be 2 or 4" }
|
||||
require(constIdWidth == 2 || constIdWidth == 4) { "constIdWidth must be 2 or 4" }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,704 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.obj.*
|
||||
|
||||
class BytecodeVm {
|
||||
suspend fun execute(fn: BytecodeFunction, scope: Scope, args: List<Obj>): Obj {
|
||||
val frame = BytecodeFrame(fn.localCount, args.size)
|
||||
for (i in args.indices) {
|
||||
frame.setObj(frame.argBase + i, args[i])
|
||||
}
|
||||
val decoder = when (fn.slotWidth) {
|
||||
1 -> Decoder8
|
||||
2 -> Decoder16
|
||||
4 -> Decoder32
|
||||
else -> error("Unsupported slot width: ${fn.slotWidth}")
|
||||
}
|
||||
var ip = 0
|
||||
val code = fn.code
|
||||
while (ip < code.size) {
|
||||
val op = decoder.readOpcode(code, ip)
|
||||
ip += 1
|
||||
when (op) {
|
||||
Opcode.NOP -> {
|
||||
// no-op
|
||||
}
|
||||
Opcode.CONST_INT -> {
|
||||
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
|
||||
ip += fn.constIdWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val c = fn.constants[constId] as? BytecodeConst.IntVal
|
||||
?: error("CONST_INT expects IntVal at $constId")
|
||||
frame.setInt(dst, c.value)
|
||||
}
|
||||
Opcode.CONST_REAL -> {
|
||||
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
|
||||
ip += fn.constIdWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val c = fn.constants[constId] as? BytecodeConst.RealVal
|
||||
?: error("CONST_REAL expects RealVal at $constId")
|
||||
frame.setReal(dst, c.value)
|
||||
}
|
||||
Opcode.CONST_BOOL -> {
|
||||
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
|
||||
ip += fn.constIdWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val c = fn.constants[constId] as? BytecodeConst.Bool
|
||||
?: error("CONST_BOOL expects Bool at $constId")
|
||||
frame.setBool(dst, c.value)
|
||||
}
|
||||
Opcode.CONST_OBJ -> {
|
||||
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
|
||||
ip += fn.constIdWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
when (val c = fn.constants[constId]) {
|
||||
is BytecodeConst.ObjRef -> {
|
||||
val obj = c.value
|
||||
when (obj) {
|
||||
is ObjInt -> frame.setInt(dst, obj.value)
|
||||
is ObjReal -> frame.setReal(dst, obj.value)
|
||||
is ObjBool -> frame.setBool(dst, obj.value)
|
||||
else -> frame.setObj(dst, obj)
|
||||
}
|
||||
}
|
||||
is BytecodeConst.StringVal -> frame.setObj(dst, ObjString(c.value))
|
||||
else -> error("CONST_OBJ expects ObjRef/StringVal at $constId")
|
||||
}
|
||||
}
|
||||
Opcode.CONST_NULL -> {
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setObj(dst, ObjNull)
|
||||
}
|
||||
Opcode.MOVE_INT -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setInt(dst, frame.getInt(src))
|
||||
}
|
||||
Opcode.MOVE_REAL -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setReal(dst, frame.getReal(src))
|
||||
}
|
||||
Opcode.MOVE_BOOL -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setBool(dst, frame.getBool(src))
|
||||
}
|
||||
Opcode.MOVE_OBJ -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setObj(dst, frame.getObj(src))
|
||||
}
|
||||
Opcode.INT_TO_REAL -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setReal(dst, frame.getInt(src).toDouble())
|
||||
}
|
||||
Opcode.REAL_TO_INT -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setInt(dst, frame.getReal(src).toLong())
|
||||
}
|
||||
Opcode.BOOL_TO_INT -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setInt(dst, if (frame.getBool(src)) 1L else 0L)
|
||||
}
|
||||
Opcode.INT_TO_BOOL -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setBool(dst, frame.getInt(src) != 0L)
|
||||
}
|
||||
Opcode.ADD_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.setInt(dst, frame.getInt(a) + frame.getInt(b))
|
||||
}
|
||||
Opcode.SUB_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.setInt(dst, frame.getInt(a) - frame.getInt(b))
|
||||
}
|
||||
Opcode.MUL_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.setInt(dst, frame.getInt(a) * frame.getInt(b))
|
||||
}
|
||||
Opcode.DIV_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.setInt(dst, frame.getInt(a) / frame.getInt(b))
|
||||
}
|
||||
Opcode.MOD_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.setInt(dst, frame.getInt(a) % frame.getInt(b))
|
||||
}
|
||||
Opcode.NEG_INT -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setInt(dst, -frame.getInt(src))
|
||||
}
|
||||
Opcode.INC_INT -> {
|
||||
val slot = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setInt(slot, frame.getInt(slot) + 1L)
|
||||
}
|
||||
Opcode.DEC_INT -> {
|
||||
val slot = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setInt(slot, frame.getInt(slot) - 1L)
|
||||
}
|
||||
Opcode.ADD_REAL -> {
|
||||
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.setReal(dst, frame.getReal(a) + frame.getReal(b))
|
||||
}
|
||||
Opcode.SUB_REAL -> {
|
||||
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.setReal(dst, frame.getReal(a) - frame.getReal(b))
|
||||
}
|
||||
Opcode.MUL_REAL -> {
|
||||
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.setReal(dst, frame.getReal(a) * frame.getReal(b))
|
||||
}
|
||||
Opcode.DIV_REAL -> {
|
||||
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.setReal(dst, frame.getReal(a) / frame.getReal(b))
|
||||
}
|
||||
Opcode.NEG_REAL -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setReal(dst, -frame.getReal(src))
|
||||
}
|
||||
Opcode.AND_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.setInt(dst, frame.getInt(a) and frame.getInt(b))
|
||||
}
|
||||
Opcode.OR_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.setInt(dst, frame.getInt(a) or frame.getInt(b))
|
||||
}
|
||||
Opcode.XOR_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.setInt(dst, frame.getInt(a) xor frame.getInt(b))
|
||||
}
|
||||
Opcode.SHL_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.setInt(dst, frame.getInt(a) shl frame.getInt(b).toInt())
|
||||
}
|
||||
Opcode.SHR_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.setInt(dst, frame.getInt(a) shr frame.getInt(b).toInt())
|
||||
}
|
||||
Opcode.USHR_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.setInt(dst, frame.getInt(a) ushr frame.getInt(b).toInt())
|
||||
}
|
||||
Opcode.INV_INT -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setInt(dst, frame.getInt(src).inv())
|
||||
}
|
||||
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_LTE_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_GT_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_GTE_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.CMP_NEQ_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_REAL -> {
|
||||
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.getReal(a) == frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_NEQ_REAL -> {
|
||||
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.getReal(a) != frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_LT_REAL -> {
|
||||
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.getReal(a) < frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_LTE_REAL -> {
|
||||
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.getReal(a) <= frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_GT_REAL -> {
|
||||
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.getReal(a) > frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_GTE_REAL -> {
|
||||
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.getReal(a) >= frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_EQ_BOOL -> {
|
||||
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.getBool(a) == frame.getBool(b))
|
||||
}
|
||||
Opcode.CMP_NEQ_BOOL -> {
|
||||
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.getBool(a) != frame.getBool(b))
|
||||
}
|
||||
Opcode.CMP_EQ_INT_REAL -> {
|
||||
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).toDouble() == frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_EQ_REAL_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.getReal(a) == frame.getInt(b).toDouble())
|
||||
}
|
||||
Opcode.CMP_LT_INT_REAL -> {
|
||||
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).toDouble() < frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_LT_REAL_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.getReal(a) < frame.getInt(b).toDouble())
|
||||
}
|
||||
Opcode.CMP_LTE_INT_REAL -> {
|
||||
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).toDouble() <= frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_LTE_REAL_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.getReal(a) <= frame.getInt(b).toDouble())
|
||||
}
|
||||
Opcode.CMP_GT_INT_REAL -> {
|
||||
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).toDouble() > frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_GT_REAL_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.getReal(a) > frame.getInt(b).toDouble())
|
||||
}
|
||||
Opcode.CMP_GTE_INT_REAL -> {
|
||||
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).toDouble() >= frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_GTE_REAL_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.getReal(a) >= frame.getInt(b).toDouble())
|
||||
}
|
||||
Opcode.CMP_NEQ_INT_REAL -> {
|
||||
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).toDouble() != frame.getReal(b))
|
||||
}
|
||||
Opcode.CMP_NEQ_REAL_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.getReal(a) != frame.getInt(b).toDouble())
|
||||
}
|
||||
Opcode.CMP_EQ_OBJ -> {
|
||||
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.getObj(a).equals(scope, frame.getObj(b)))
|
||||
}
|
||||
Opcode.CMP_NEQ_OBJ -> {
|
||||
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.getObj(a).equals(scope, frame.getObj(b)))
|
||||
}
|
||||
Opcode.CMP_REF_EQ_OBJ -> {
|
||||
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.getObj(a) === frame.getObj(b))
|
||||
}
|
||||
Opcode.CMP_REF_NEQ_OBJ -> {
|
||||
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.getObj(a) !== frame.getObj(b))
|
||||
}
|
||||
Opcode.CMP_LT_OBJ -> {
|
||||
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.getObj(a).compareTo(scope, frame.getObj(b)) < 0)
|
||||
}
|
||||
Opcode.CMP_LTE_OBJ -> {
|
||||
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.getObj(a).compareTo(scope, frame.getObj(b)) <= 0)
|
||||
}
|
||||
Opcode.CMP_GT_OBJ -> {
|
||||
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.getObj(a).compareTo(scope, frame.getObj(b)) > 0)
|
||||
}
|
||||
Opcode.CMP_GTE_OBJ -> {
|
||||
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.getObj(a).compareTo(scope, frame.getObj(b)) >= 0)
|
||||
}
|
||||
Opcode.NOT_BOOL -> {
|
||||
val src = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
frame.setBool(dst, !frame.getBool(src))
|
||||
}
|
||||
Opcode.AND_BOOL -> {
|
||||
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.getBool(a) && frame.getBool(b))
|
||||
}
|
||||
Opcode.OR_BOOL -> {
|
||||
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.getBool(a) || frame.getBool(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.JMP_IF_TRUE -> {
|
||||
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.EVAL_FALLBACK -> {
|
||||
val id = decoder.readConstId(code, ip, 2)
|
||||
ip += 2
|
||||
val dst = decoder.readSlot(code, ip)
|
||||
ip += fn.slotWidth
|
||||
val stmt = fn.fallbackStatements.getOrNull(id)
|
||||
?: error("Fallback statement not found: $id")
|
||||
val result = stmt.execute(scope)
|
||||
when (result) {
|
||||
is ObjInt -> frame.setInt(dst, result.value)
|
||||
is ObjReal -> frame.setReal(dst, result.value)
|
||||
is ObjBool -> frame.setBool(dst, result.value)
|
||||
else -> frame.setObj(dst, result)
|
||||
}
|
||||
}
|
||||
Opcode.RET -> {
|
||||
val slot = decoder.readSlot(code, ip)
|
||||
return slotToObj(frame, slot)
|
||||
}
|
||||
Opcode.RET_VOID -> return ObjVoid
|
||||
else -> error("Opcode not implemented: $op")
|
||||
}
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
|
||||
private fun slotToObj(frame: BytecodeFrame, slot: Int): Obj {
|
||||
return when (frame.getSlotTypeCode(slot)) {
|
||||
SlotType.INT.code -> ObjInt.of(frame.getInt(slot))
|
||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(slot))
|
||||
SlotType.BOOL.code -> if (frame.getBool(slot)) ObjTrue else ObjFalse
|
||||
SlotType.OBJ.code -> frame.getObj(slot)
|
||||
else -> ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,121 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
enum class Opcode(val code: Int) {
|
||||
NOP(0x00),
|
||||
MOVE_OBJ(0x01),
|
||||
MOVE_INT(0x02),
|
||||
MOVE_REAL(0x03),
|
||||
MOVE_BOOL(0x04),
|
||||
CONST_OBJ(0x05),
|
||||
CONST_INT(0x06),
|
||||
CONST_REAL(0x07),
|
||||
CONST_BOOL(0x08),
|
||||
CONST_NULL(0x09),
|
||||
|
||||
INT_TO_REAL(0x10),
|
||||
REAL_TO_INT(0x11),
|
||||
BOOL_TO_INT(0x12),
|
||||
INT_TO_BOOL(0x13),
|
||||
|
||||
ADD_INT(0x20),
|
||||
SUB_INT(0x21),
|
||||
MUL_INT(0x22),
|
||||
DIV_INT(0x23),
|
||||
MOD_INT(0x24),
|
||||
NEG_INT(0x25),
|
||||
INC_INT(0x26),
|
||||
DEC_INT(0x27),
|
||||
|
||||
ADD_REAL(0x30),
|
||||
SUB_REAL(0x31),
|
||||
MUL_REAL(0x32),
|
||||
DIV_REAL(0x33),
|
||||
NEG_REAL(0x34),
|
||||
|
||||
AND_INT(0x40),
|
||||
OR_INT(0x41),
|
||||
XOR_INT(0x42),
|
||||
SHL_INT(0x43),
|
||||
SHR_INT(0x44),
|
||||
USHR_INT(0x45),
|
||||
INV_INT(0x46),
|
||||
|
||||
CMP_EQ_INT(0x50),
|
||||
CMP_NEQ_INT(0x51),
|
||||
CMP_LT_INT(0x52),
|
||||
CMP_LTE_INT(0x53),
|
||||
CMP_GT_INT(0x54),
|
||||
CMP_GTE_INT(0x55),
|
||||
CMP_EQ_REAL(0x56),
|
||||
CMP_NEQ_REAL(0x57),
|
||||
CMP_LT_REAL(0x58),
|
||||
CMP_LTE_REAL(0x59),
|
||||
CMP_GT_REAL(0x5A),
|
||||
CMP_GTE_REAL(0x5B),
|
||||
CMP_EQ_BOOL(0x5C),
|
||||
CMP_NEQ_BOOL(0x5D),
|
||||
|
||||
CMP_EQ_INT_REAL(0x60),
|
||||
CMP_EQ_REAL_INT(0x61),
|
||||
CMP_LT_INT_REAL(0x62),
|
||||
CMP_LT_REAL_INT(0x63),
|
||||
CMP_LTE_INT_REAL(0x64),
|
||||
CMP_LTE_REAL_INT(0x65),
|
||||
CMP_GT_INT_REAL(0x66),
|
||||
CMP_GT_REAL_INT(0x67),
|
||||
CMP_GTE_INT_REAL(0x68),
|
||||
CMP_GTE_REAL_INT(0x69),
|
||||
CMP_NEQ_INT_REAL(0x6A),
|
||||
CMP_NEQ_REAL_INT(0x6B),
|
||||
CMP_EQ_OBJ(0x6C),
|
||||
CMP_NEQ_OBJ(0x6D),
|
||||
CMP_REF_EQ_OBJ(0x6E),
|
||||
CMP_REF_NEQ_OBJ(0x6F),
|
||||
|
||||
NOT_BOOL(0x70),
|
||||
AND_BOOL(0x71),
|
||||
OR_BOOL(0x72),
|
||||
CMP_LT_OBJ(0x73),
|
||||
CMP_LTE_OBJ(0x74),
|
||||
CMP_GT_OBJ(0x75),
|
||||
CMP_GTE_OBJ(0x76),
|
||||
|
||||
JMP(0x80),
|
||||
JMP_IF_TRUE(0x81),
|
||||
JMP_IF_FALSE(0x82),
|
||||
RET(0x83),
|
||||
RET_VOID(0x84),
|
||||
|
||||
CALL_DIRECT(0x90),
|
||||
CALL_VIRTUAL(0x91),
|
||||
CALL_FALLBACK(0x92),
|
||||
|
||||
GET_FIELD(0xA0),
|
||||
SET_FIELD(0xA1),
|
||||
GET_INDEX(0xA2),
|
||||
SET_INDEX(0xA3),
|
||||
|
||||
EVAL_FALLBACK(0xB0),
|
||||
;
|
||||
|
||||
companion object {
|
||||
private val byCode: Map<Int, Opcode> = values().associateBy { it.code }
|
||||
fun fromCode(code: Int): Opcode? = byCode[code]
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.bytecode
|
||||
|
||||
enum class SlotType(val code: Byte) {
|
||||
UNKNOWN(0),
|
||||
OBJ(1),
|
||||
INT(2),
|
||||
REAL(3),
|
||||
BOOL(4),
|
||||
}
|
||||
@ -89,7 +89,7 @@ enum class BinOp {
|
||||
}
|
||||
|
||||
/** R-value reference for unary operations. */
|
||||
class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
||||
class UnaryOpRef(internal val op: UnaryOp, internal val a: ObjRef) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val v = a.evalValue(scope)
|
||||
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
@ -141,7 +141,7 @@ class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
||||
}
|
||||
|
||||
/** R-value reference for binary operations. */
|
||||
class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||
class BinaryOpRef(internal val op: BinOp, internal val left: ObjRef, internal val right: ObjRef) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
return evalValue(scope).asReadonly
|
||||
}
|
||||
@ -2403,8 +2403,8 @@ class ImplicitThisMethodCallRef(
|
||||
*/
|
||||
class LocalSlotRef(
|
||||
val name: String,
|
||||
private val slot: Int,
|
||||
private val depth: Int,
|
||||
internal val slot: Int,
|
||||
internal val depth: Int,
|
||||
private val atPos: Pos,
|
||||
) : ObjRef {
|
||||
override fun forEachVariable(block: (String) -> Unit) {
|
||||
@ -2657,8 +2657,8 @@ class AssignIfNullRef(
|
||||
|
||||
/** Simple assignment: target = value */
|
||||
class AssignRef(
|
||||
private val target: ObjRef,
|
||||
private val value: ObjRef,
|
||||
internal val target: ObjRef,
|
||||
internal val value: ObjRef,
|
||||
private val atPos: Pos,
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
|
||||
@ -20,6 +20,7 @@ package net.sergeych.lyng
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjVoid
|
||||
import net.sergeych.lyng.obj.toBool
|
||||
|
||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||
|
||||
@ -63,6 +64,30 @@ 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 ToBoolStatement(
|
||||
val expr: Statement,
|
||||
override val pos: Pos,
|
||||
) : Statement() {
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
return if (expr.execute(scope).toBool()) net.sergeych.lyng.obj.ObjTrue else net.sergeych.lyng.obj.ObjFalse
|
||||
}
|
||||
}
|
||||
|
||||
class ExpressionStatement(
|
||||
val ref: net.sergeych.lyng.obj.ObjRef,
|
||||
override val pos: Pos
|
||||
|
||||
301
lynglib/src/commonTest/kotlin/BytecodeVmTest.kt
Normal file
301
lynglib/src/commonTest/kotlin/BytecodeVmTest.kt
Normal file
@ -0,0 +1,301 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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 net.sergeych.lyng.ExpressionStatement
|
||||
import net.sergeych.lyng.IfStatement
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.bytecode.BytecodeBuilder
|
||||
import net.sergeych.lyng.bytecode.BytecodeCompiler
|
||||
import net.sergeych.lyng.bytecode.BytecodeConst
|
||||
import net.sergeych.lyng.bytecode.BytecodeVm
|
||||
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.LocalSlotRef
|
||||
import net.sergeych.lyng.obj.ObjFalse
|
||||
import net.sergeych.lyng.obj.ObjInt
|
||||
import net.sergeych.lyng.obj.ObjTrue
|
||||
import net.sergeych.lyng.obj.ObjReal
|
||||
import net.sergeych.lyng.obj.ObjString
|
||||
import net.sergeych.lyng.obj.ObjList
|
||||
import net.sergeych.lyng.obj.AssignRef
|
||||
import net.sergeych.lyng.obj.ValueFnRef
|
||||
import net.sergeych.lyng.obj.ObjVoid
|
||||
import net.sergeych.lyng.obj.toBool
|
||||
import net.sergeych.lyng.obj.toDouble
|
||||
import net.sergeych.lyng.obj.toInt
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class BytecodeVmTest {
|
||||
@Test
|
||||
fun addsIntConstants() = kotlinx.coroutines.test.runTest {
|
||||
val builder = BytecodeBuilder()
|
||||
val k0 = builder.addConst(BytecodeConst.IntVal(2))
|
||||
val k1 = builder.addConst(BytecodeConst.IntVal(3))
|
||||
builder.emit(Opcode.CONST_INT, k0, 0)
|
||||
builder.emit(Opcode.CONST_INT, k1, 1)
|
||||
builder.emit(Opcode.ADD_INT, 0, 1, 2)
|
||||
builder.emit(Opcode.RET, 2)
|
||||
val fn = builder.build("addInts", localCount = 3)
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
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())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun ifWithoutElseReturnsVoid() = kotlinx.coroutines.test.runTest {
|
||||
val cond = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.LT,
|
||||
ConstRef(ObjInt.of(2).asReadonly),
|
||||
ConstRef(ObjInt.of(1).asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val thenStmt = ExpressionStatement(
|
||||
ConstRef(ObjInt.of(10).asReadonly),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val ifStmt = IfStatement(cond, thenStmt, null, net.sergeych.lyng.Pos.builtIn)
|
||||
val fn = BytecodeCompiler().compileStatement("ifNoElse", ifStmt).also {
|
||||
if (it == null) {
|
||||
error("bytecode compile failed for ifNoElse")
|
||||
}
|
||||
}!!
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(ObjVoid, result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun andIsShortCircuit() = kotlinx.coroutines.test.runTest {
|
||||
val throwingRef = ValueFnRef { error("should not execute") }
|
||||
val expr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.AND,
|
||||
ConstRef(ObjFalse.asReadonly),
|
||||
throwingRef
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val fn = BytecodeCompiler().compileExpression("andShort", expr) ?: error("bytecode compile failed")
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(false, result.toBool())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun orIsShortCircuit() = kotlinx.coroutines.test.runTest {
|
||||
val throwingRef = ValueFnRef { error("should not execute") }
|
||||
val expr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.OR,
|
||||
ConstRef(ObjTrue.asReadonly),
|
||||
throwingRef
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val fn = BytecodeCompiler().compileExpression("orShort", expr) ?: error("bytecode compile failed")
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(true, result.toBool())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun realArithmeticUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
|
||||
val expr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.PLUS,
|
||||
ConstRef(ObjReal.of(2.5).asReadonly),
|
||||
ConstRef(ObjReal.of(3.25).asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val fn = BytecodeCompiler().compileExpression("realPlus", expr) ?: error("bytecode compile failed")
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(5.75, result.toDouble())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mixedIntRealComparisonUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
|
||||
val ltExpr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.LT,
|
||||
ConstRef(ObjInt.of(2).asReadonly),
|
||||
ConstRef(ObjReal.of(2.5).asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val ltFn = BytecodeCompiler().compileExpression("mixedLt", ltExpr) ?: error("bytecode compile failed")
|
||||
val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList())
|
||||
assertEquals(true, ltResult.toBool())
|
||||
|
||||
val eqExpr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.EQ,
|
||||
ConstRef(ObjReal.of(4.0).asReadonly),
|
||||
ConstRef(ObjInt.of(4).asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val eqFn = BytecodeCompiler().compileExpression("mixedEq", eqExpr) ?: error("bytecode compile failed")
|
||||
val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList())
|
||||
assertEquals(true, eqResult.toBool())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mixedIntRealArithmeticUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
|
||||
val expr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.PLUS,
|
||||
ConstRef(ObjInt.of(2).asReadonly),
|
||||
ConstRef(ObjReal.of(3.5).asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val fn = BytecodeCompiler().compileExpression("mixedPlus", expr) ?: error("bytecode compile failed")
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(5.5, result.toDouble())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun mixedIntRealNotEqualUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
|
||||
val expr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.NEQ,
|
||||
ConstRef(ObjInt.of(3).asReadonly),
|
||||
ConstRef(ObjReal.of(2.5).asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val fn = BytecodeCompiler().compileExpression("mixedNeq", expr) ?: error("bytecode compile failed")
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(true, result.toBool())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun localSlotTypeTrackingEnablesArithmetic() = kotlinx.coroutines.test.runTest {
|
||||
val slotRef = LocalSlotRef("a", 0, 0, net.sergeych.lyng.Pos.builtIn)
|
||||
val assign = AssignRef(
|
||||
slotRef,
|
||||
ConstRef(ObjInt.of(2).asReadonly),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val expr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.PLUS,
|
||||
assign,
|
||||
slotRef
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed")
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(4, result.toInt())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun objectEqualityUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
|
||||
val expr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.EQ,
|
||||
ConstRef(ObjString("abc").asReadonly),
|
||||
ConstRef(ObjString("abc").asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val fn = BytecodeCompiler().compileExpression("objEq", expr) ?: error("bytecode compile failed")
|
||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
||||
assertEquals(true, result.toBool())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun objectReferenceEqualityUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
|
||||
val shared = ObjList()
|
||||
val eqExpr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.REF_EQ,
|
||||
ConstRef(shared.asReadonly),
|
||||
ConstRef(shared.asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val eqFn = BytecodeCompiler().compileExpression("objRefEq", eqExpr) ?: error("bytecode compile failed")
|
||||
val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList())
|
||||
assertEquals(true, eqResult.toBool())
|
||||
|
||||
val neqExpr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.REF_NEQ,
|
||||
ConstRef(ObjList().asReadonly),
|
||||
ConstRef(ObjList().asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val neqFn = BytecodeCompiler().compileExpression("objRefNeq", neqExpr) ?: error("bytecode compile failed")
|
||||
val neqResult = BytecodeVm().execute(neqFn, Scope(), emptyList())
|
||||
assertEquals(true, neqResult.toBool())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun objectComparisonUsesBytecodeOps() = kotlinx.coroutines.test.runTest {
|
||||
val ltExpr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.LT,
|
||||
ConstRef(ObjString("a").asReadonly),
|
||||
ConstRef(ObjString("b").asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val ltFn = BytecodeCompiler().compileExpression("objLt", ltExpr) ?: error("bytecode compile failed")
|
||||
val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList())
|
||||
assertEquals(true, ltResult.toBool())
|
||||
|
||||
val gteExpr = ExpressionStatement(
|
||||
BinaryOpRef(
|
||||
BinOp.GTE,
|
||||
ConstRef(ObjString("b").asReadonly),
|
||||
ConstRef(ObjString("a").asReadonly),
|
||||
),
|
||||
net.sergeych.lyng.Pos.builtIn
|
||||
)
|
||||
val gteFn = BytecodeCompiler().compileExpression("objGte", gteExpr) ?: error("bytecode compile failed")
|
||||
val gteResult = BytecodeVm().execute(gteFn, Scope(), emptyList())
|
||||
assertEquals(true, gteResult.toBool())
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user