Expand bytecode expression support and add disassembler

This commit is contained in:
Sergey Chernov 2026-01-25 19:44:37 +03:00
parent c8a8b12dfc
commit 3d9170d677
3 changed files with 256 additions and 17 deletions

View File

@ -181,35 +181,101 @@ class BytecodeCompiler {
CompiledValue(out, SlotType.INT) CompiledValue(out, SlotType.INT)
} }
BinOp.EQ -> { BinOp.EQ -> {
if (a.type != SlotType.INT) return null when (a.type) {
SlotType.INT -> {
builder.emit(Opcode.CMP_EQ_INT, a.slot, b.slot, out) builder.emit(Opcode.CMP_EQ_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL) CompiledValue(out, SlotType.BOOL)
} }
SlotType.REAL -> {
builder.emit(Opcode.CMP_EQ_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.BOOL -> {
builder.emit(Opcode.CMP_EQ_BOOL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
BinOp.NEQ -> { BinOp.NEQ -> {
if (a.type != SlotType.INT) return null when (a.type) {
SlotType.INT -> {
builder.emit(Opcode.CMP_NEQ_INT, a.slot, b.slot, out) builder.emit(Opcode.CMP_NEQ_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL) CompiledValue(out, SlotType.BOOL)
} }
SlotType.REAL -> {
builder.emit(Opcode.CMP_NEQ_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
SlotType.BOOL -> {
builder.emit(Opcode.CMP_NEQ_BOOL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
BinOp.LT -> { BinOp.LT -> {
if (a.type != SlotType.INT) return null when (a.type) {
SlotType.INT -> {
builder.emit(Opcode.CMP_LT_INT, a.slot, b.slot, out) builder.emit(Opcode.CMP_LT_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL) CompiledValue(out, SlotType.BOOL)
} }
SlotType.REAL -> {
builder.emit(Opcode.CMP_LT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
BinOp.LTE -> { BinOp.LTE -> {
if (a.type != SlotType.INT) return null when (a.type) {
SlotType.INT -> {
builder.emit(Opcode.CMP_LTE_INT, a.slot, b.slot, out) builder.emit(Opcode.CMP_LTE_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL) CompiledValue(out, SlotType.BOOL)
} }
SlotType.REAL -> {
builder.emit(Opcode.CMP_LTE_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
BinOp.GT -> { BinOp.GT -> {
if (a.type != SlotType.INT) return null when (a.type) {
SlotType.INT -> {
builder.emit(Opcode.CMP_GT_INT, a.slot, b.slot, out) builder.emit(Opcode.CMP_GT_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL) CompiledValue(out, SlotType.BOOL)
} }
SlotType.REAL -> {
builder.emit(Opcode.CMP_GT_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
BinOp.GTE -> { BinOp.GTE -> {
if (a.type != SlotType.INT) return null when (a.type) {
SlotType.INT -> {
builder.emit(Opcode.CMP_GTE_INT, a.slot, b.slot, out) builder.emit(Opcode.CMP_GTE_INT, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL) CompiledValue(out, SlotType.BOOL)
} }
SlotType.REAL -> {
builder.emit(Opcode.CMP_GTE_REAL, a.slot, b.slot, out)
CompiledValue(out, SlotType.BOOL)
}
else -> null
}
}
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 -> { BinOp.BAND -> {
if (a.type != SlotType.INT) return null if (a.type != SlotType.INT) return null
builder.emit(Opcode.AND_INT, a.slot, b.slot, out) builder.emit(Opcode.AND_INT, a.slot, b.slot, out)

View File

@ -0,0 +1,128 @@
/*
* 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.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)
}
}
}

View File

@ -142,6 +142,51 @@ class BytecodeVm {
ip += fn.slotWidth ip += fn.slotWidth
frame.setBool(dst, frame.getInt(a) == frame.getInt(b)) 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_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.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 -> { Opcode.JMP -> {
val target = decoder.readIp(code, ip, fn.ipWidth) val target = decoder.readIp(code, ip, fn.ipWidth)
ip = target ip = target