Expand bytecode expression support and add disassembler
This commit is contained in:
parent
c8a8b12dfc
commit
3d9170d677
@ -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)
|
||||||
|
|||||||
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user