Optimize cmd VM with scoped slot addressing
This commit is contained in:
parent
7de856fc62
commit
bef94d3bc5
@ -18,12 +18,12 @@ package net.sergeych.lyng.bytecode
|
|||||||
|
|
||||||
import java.util.IdentityHashMap
|
import java.util.IdentityHashMap
|
||||||
|
|
||||||
internal actual object BytecodeCallSiteCache {
|
internal actual object CmdCallSiteCache {
|
||||||
private val cache = ThreadLocal.withInitial {
|
private val cache = ThreadLocal.withInitial {
|
||||||
IdentityHashMap<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
|
IdentityHashMap<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
|
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
||||||
val map = cache.get()
|
val map = cache.get()
|
||||||
return map.getOrPut(fn) { mutableMapOf() }
|
return map.getOrPut(fn) { mutableMapOf() }
|
||||||
}
|
}
|
||||||
@ -373,7 +373,8 @@ class Compiler(
|
|||||||
|
|
||||||
private fun wrapBytecode(stmt: Statement): Statement {
|
private fun wrapBytecode(stmt: Statement): Statement {
|
||||||
if (!useBytecodeStatements) return stmt
|
if (!useBytecodeStatements) return stmt
|
||||||
return BytecodeStatement.wrap(stmt, "stmt@${stmt.pos}", allowLocalSlots = true)
|
val allowLocals = codeContexts.lastOrNull() is CodeContext.Function
|
||||||
|
return BytecodeStatement.wrap(stmt, "stmt@${stmt.pos}", allowLocalSlots = allowLocals)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun wrapFunctionBytecode(stmt: Statement, name: String): Statement {
|
private fun wrapFunctionBytecode(stmt: Statement, name: String): Statement {
|
||||||
@ -381,6 +382,27 @@ class Compiler(
|
|||||||
return BytecodeStatement.wrap(stmt, "fn@$name", allowLocalSlots = true)
|
return BytecodeStatement.wrap(stmt, "fn@$name", allowLocalSlots = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun containsUnsupportedForBytecode(stmt: Statement): Boolean {
|
||||||
|
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||||
|
return when (target) {
|
||||||
|
is ExpressionStatement -> false
|
||||||
|
is IfStatement -> {
|
||||||
|
containsUnsupportedForBytecode(target.condition) ||
|
||||||
|
containsUnsupportedForBytecode(target.ifBody) ||
|
||||||
|
(target.elseBody?.let { containsUnsupportedForBytecode(it) } ?: false)
|
||||||
|
}
|
||||||
|
is ForInStatement -> {
|
||||||
|
target.constRange == null || target.canBreak ||
|
||||||
|
containsUnsupportedForBytecode(target.source) ||
|
||||||
|
containsUnsupportedForBytecode(target.body) ||
|
||||||
|
(target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false)
|
||||||
|
}
|
||||||
|
is BlockStatement -> target.statements().any { containsUnsupportedForBytecode(it) }
|
||||||
|
is VarDeclStatement -> target.initializer?.let { containsUnsupportedForBytecode(it) } ?: false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun unwrapBytecodeDeep(stmt: Statement): Statement {
|
private fun unwrapBytecodeDeep(stmt: Statement): Statement {
|
||||||
return when (stmt) {
|
return when (stmt) {
|
||||||
is BytecodeStatement -> unwrapBytecodeDeep(stmt.original)
|
is BytecodeStatement -> unwrapBytecodeDeep(stmt.original)
|
||||||
@ -397,6 +419,8 @@ class Compiler(
|
|||||||
stmt.visibility,
|
stmt.visibility,
|
||||||
init,
|
init,
|
||||||
stmt.isTransient,
|
stmt.isTransient,
|
||||||
|
stmt.slotIndex,
|
||||||
|
stmt.slotDepth,
|
||||||
stmt.pos
|
stmt.pos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -859,7 +883,7 @@ class Compiler(
|
|||||||
|
|
||||||
label?.let { cc.labels.add(it) }
|
label?.let { cc.labels.add(it) }
|
||||||
slotPlanStack.add(paramSlotPlan)
|
slotPlanStack.add(paramSlotPlan)
|
||||||
val body = try {
|
val parsedBody = try {
|
||||||
inCodeContext(CodeContext.Function("<lambda>")) {
|
inCodeContext(CodeContext.Function("<lambda>")) {
|
||||||
withLocalNames(slotParamNames.toSet()) {
|
withLocalNames(slotParamNames.toSet()) {
|
||||||
parseBlock(skipLeadingBrace = true)
|
parseBlock(skipLeadingBrace = true)
|
||||||
@ -868,6 +892,7 @@ class Compiler(
|
|||||||
} finally {
|
} finally {
|
||||||
slotPlanStack.removeLast()
|
slotPlanStack.removeLast()
|
||||||
}
|
}
|
||||||
|
val body = unwrapBytecodeDeep(parsedBody)
|
||||||
label?.let { cc.labels.remove(it) }
|
label?.let { cc.labels.remove(it) }
|
||||||
|
|
||||||
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
|
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
|
||||||
@ -1494,15 +1519,20 @@ class Compiler(
|
|||||||
val slotLoc = lookupSlotLocation(t.value)
|
val slotLoc = lookupSlotLocation(t.value)
|
||||||
val inClassCtx = codeContexts.any { it is CodeContext.ClassBody }
|
val inClassCtx = codeContexts.any { it is CodeContext.ClassBody }
|
||||||
when {
|
when {
|
||||||
slotLoc != null -> LocalSlotRef(
|
slotLoc != null -> {
|
||||||
|
val scopeDepth = slotPlanStack.size - 1 - slotLoc.depth
|
||||||
|
LocalSlotRef(
|
||||||
t.value,
|
t.value,
|
||||||
slotLoc.slot,
|
slotLoc.slot,
|
||||||
slotLoc.depth,
|
slotLoc.depth,
|
||||||
|
scopeDepth,
|
||||||
slotLoc.isMutable,
|
slotLoc.isMutable,
|
||||||
slotLoc.isDelegated,
|
slotLoc.isDelegated,
|
||||||
t.pos
|
t.pos
|
||||||
)
|
)
|
||||||
PerfFlags.EMIT_FAST_LOCAL_REFS && (currentLocalNames?.contains(t.value) == true) ->
|
}
|
||||||
|
PerfFlags.EMIT_FAST_LOCAL_REFS && !useBytecodeStatements &&
|
||||||
|
(currentLocalNames?.contains(t.value) == true) ->
|
||||||
FastLocalVarRef(t.value, t.pos)
|
FastLocalVarRef(t.value, t.pos)
|
||||||
inClassCtx -> ImplicitThisMemberRef(t.value, t.pos)
|
inClassCtx -> ImplicitThisMemberRef(t.value, t.pos)
|
||||||
else -> LocalVarRef(t.value, t.pos)
|
else -> LocalVarRef(t.value, t.pos)
|
||||||
@ -2041,7 +2071,19 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
|
|
||||||
private suspend fun parseTryStatement(): Statement {
|
private suspend fun parseTryStatement(): Statement {
|
||||||
val body = parseBlock()
|
fun withCatchSlot(block: Statement, catchName: String): Statement {
|
||||||
|
val stmt = block as? BlockStatement ?: return block
|
||||||
|
if (stmt.slotPlan.containsKey(catchName)) return stmt
|
||||||
|
val basePlan = stmt.slotPlan
|
||||||
|
val newPlan = LinkedHashMap<String, Int>(basePlan.size + 1)
|
||||||
|
newPlan[catchName] = 0
|
||||||
|
for ((name, idx) in basePlan) {
|
||||||
|
newPlan[name] = idx + 1
|
||||||
|
}
|
||||||
|
return BlockStatement(stmt.block, newPlan, stmt.pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
val body = unwrapBytecodeDeep(parseBlock())
|
||||||
val catches = mutableListOf<CatchBlockData>()
|
val catches = mutableListOf<CatchBlockData>()
|
||||||
cc.skipTokens(Token.Type.NEWLINE)
|
cc.skipTokens(Token.Type.NEWLINE)
|
||||||
var t = cc.next()
|
var t = cc.next()
|
||||||
@ -2078,7 +2120,7 @@ class Compiler(
|
|||||||
exClassNames += "Exception"
|
exClassNames += "Exception"
|
||||||
cc.skipTokenOfType(Token.Type.RPAREN)
|
cc.skipTokenOfType(Token.Type.RPAREN)
|
||||||
}
|
}
|
||||||
val block = parseBlock()
|
val block = withCatchSlot(unwrapBytecodeDeep(parseBlock()), catchVar.value)
|
||||||
catches += CatchBlockData(catchVar, exClassNames, block)
|
catches += CatchBlockData(catchVar, exClassNames, block)
|
||||||
cc.skipTokens(Token.Type.NEWLINE)
|
cc.skipTokens(Token.Type.NEWLINE)
|
||||||
t = cc.next()
|
t = cc.next()
|
||||||
@ -2087,13 +2129,13 @@ class Compiler(
|
|||||||
cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here")
|
cc.skipTokenOfType(Token.Type.LBRACE, "expected catch(...) or catch { ... } here")
|
||||||
catches += CatchBlockData(
|
catches += CatchBlockData(
|
||||||
Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"),
|
Token("it", cc.currentPos(), Token.Type.ID), listOf("Exception"),
|
||||||
parseBlock(true)
|
withCatchSlot(unwrapBytecodeDeep(parseBlock(true)), "it")
|
||||||
)
|
)
|
||||||
t = cc.next()
|
t = cc.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val finallyClause = if (t.value == "finally") {
|
val finallyClause = if (t.value == "finally") {
|
||||||
parseBlock()
|
unwrapBytecodeDeep(parseBlock())
|
||||||
} else {
|
} else {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
null
|
null
|
||||||
@ -2133,7 +2175,9 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
val catchContext = scope.createChildScope(pos = cdata.catchVar.pos)
|
val catchContext = scope.createChildScope(pos = cdata.catchVar.pos).apply {
|
||||||
|
skipScopeCreation = true
|
||||||
|
}
|
||||||
catchContext.addItem(cdata.catchVar.value, false, caughtObj)
|
catchContext.addItem(cdata.catchVar.value, false, caughtObj)
|
||||||
result = cdata.block.execute(catchContext)
|
result = cdata.block.execute(catchContext)
|
||||||
isCaught = true
|
isCaught = true
|
||||||
@ -3020,7 +3064,7 @@ class Compiler(
|
|||||||
currentLocalDeclCount
|
currentLocalDeclCount
|
||||||
localDeclCountStack.add(0)
|
localDeclCountStack.add(0)
|
||||||
slotPlanStack.add(paramSlotPlan)
|
slotPlanStack.add(paramSlotPlan)
|
||||||
val fnStatements = try {
|
val parsedFnStatements = try {
|
||||||
if (actualExtern)
|
if (actualExtern)
|
||||||
object : Statement() {
|
object : Statement() {
|
||||||
override val pos: Pos = start
|
override val pos: Pos = start
|
||||||
@ -3050,6 +3094,9 @@ class Compiler(
|
|||||||
} finally {
|
} finally {
|
||||||
slotPlanStack.removeLast()
|
slotPlanStack.removeLast()
|
||||||
}
|
}
|
||||||
|
val fnStatements = parsedFnStatements?.let {
|
||||||
|
if (containsUnsupportedForBytecode(it)) unwrapBytecodeDeep(it) else it
|
||||||
|
}
|
||||||
// Capture and pop the local declarations count for this function
|
// Capture and pop the local declarations count for this function
|
||||||
val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0
|
val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0
|
||||||
|
|
||||||
@ -3528,7 +3575,10 @@ class Compiler(
|
|||||||
!actualExtern &&
|
!actualExtern &&
|
||||||
!isAbstract
|
!isAbstract
|
||||||
) {
|
) {
|
||||||
return VarDeclStatement(name, isMutable, visibility, initialExpression, isTransient, start)
|
val slotPlan = slotPlanStack.lastOrNull()
|
||||||
|
val slotIndex = slotPlan?.slots?.get(name)?.index
|
||||||
|
val slotDepth = slotPlan?.let { slotPlanStack.size - 1 }
|
||||||
|
return VarDeclStatement(name, isMutable, visibility, initialExpression, isTransient, slotIndex, slotDepth, start)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStatic) {
|
if (isStatic) {
|
||||||
|
|||||||
@ -72,4 +72,5 @@ object PerfFlags {
|
|||||||
|
|
||||||
// Specialized non-allocating integer range iteration in hot loops
|
// Specialized non-allocating integer range iteration in hot loops
|
||||||
var RANGE_FAST_ITER: Boolean = PerfDefaults.RANGE_FAST_ITER
|
var RANGE_FAST_ITER: Boolean = PerfDefaults.RANGE_FAST_ITER
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,7 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.bytecode.BytecodeDisassembler
|
import net.sergeych.lyng.bytecode.CmdDisassembler
|
||||||
import net.sergeych.lyng.bytecode.BytecodeStatement
|
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
import net.sergeych.lyng.pacman.ImportProvider
|
import net.sergeych.lyng.pacman.ImportProvider
|
||||||
@ -339,6 +339,8 @@ open class Scope(
|
|||||||
|
|
||||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||||
|
|
||||||
|
internal fun getLocalRecordDirect(name: String): ObjRecord? = objects[name]
|
||||||
|
|
||||||
open operator fun get(name: String): ObjRecord? {
|
open operator fun get(name: String): ObjRecord? {
|
||||||
if (name == "this") return thisObj.asReadonly
|
if (name == "this") return thisObj.asReadonly
|
||||||
|
|
||||||
@ -381,6 +383,8 @@ open class Scope(
|
|||||||
fun setSlotValue(index: Int, newValue: Obj) {
|
fun setSlotValue(index: Int, newValue: Obj) {
|
||||||
slots[index].value = newValue
|
slots[index].value = newValue
|
||||||
}
|
}
|
||||||
|
val slotCount: Int
|
||||||
|
get() = slots.size
|
||||||
|
|
||||||
fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
|
fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
|
||||||
fun allocateSlotFor(name: String, record: ObjRecord): Int {
|
fun allocateSlotFor(name: String, record: ObjRecord): Int {
|
||||||
@ -440,6 +444,20 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasSlotPlanConflict(plan: Map<String, Int>): Boolean {
|
||||||
|
if (plan.isEmpty() || nameToSlot.isEmpty()) return false
|
||||||
|
val planIndexToNames = HashMap<Int, HashSet<String>>(plan.size)
|
||||||
|
for ((name, idx) in plan) {
|
||||||
|
val names = planIndexToNames.getOrPut(idx) { HashSet(2) }
|
||||||
|
names.add(name)
|
||||||
|
}
|
||||||
|
for ((existingName, existingIndex) in nameToSlot) {
|
||||||
|
val plannedNames = planIndexToNames[existingIndex] ?: continue
|
||||||
|
if (!plannedNames.contains(existingName)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all references and maps to prevent memory leaks when pooled.
|
* Clear all references and maps to prevent memory leaks when pooled.
|
||||||
*/
|
*/
|
||||||
@ -651,7 +669,7 @@ open class Scope(
|
|||||||
val bytecode = (stmt as? BytecodeStatement)?.bytecodeFunction()
|
val bytecode = (stmt as? BytecodeStatement)?.bytecodeFunction()
|
||||||
?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
|
?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
|
||||||
?: return "$name is not a compiled body"
|
?: return "$name is not a compiled body"
|
||||||
return BytecodeDisassembler.disassemble(bytecode)
|
return CmdDisassembler.disassemble(bytecode)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
|
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
|
||||||
|
|||||||
@ -26,6 +26,8 @@ class VarDeclStatement(
|
|||||||
val visibility: Visibility,
|
val visibility: Visibility,
|
||||||
val initializer: Statement?,
|
val initializer: Statement?,
|
||||||
val isTransient: Boolean,
|
val isTransient: Boolean,
|
||||||
|
val slotIndex: Int?,
|
||||||
|
val slotDepth: Int?,
|
||||||
private val startPos: Pos,
|
private val startPos: Pos,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override val pos: Pos = startPos
|
override val pos: Pos = startPos
|
||||||
|
|||||||
@ -1,222 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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,
|
|
||||||
scopeSlotDepths: IntArray = IntArray(0),
|
|
||||||
scopeSlotIndices: IntArray = IntArray(0),
|
|
||||||
scopeSlotNames: Array<String?> = emptyArray()
|
|
||||||
): BytecodeFunction {
|
|
||||||
val scopeSlotCount = scopeSlotDepths.size
|
|
||||||
require(scopeSlotIndices.size == scopeSlotCount) { "scope slot mapping size mismatch" }
|
|
||||||
require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) {
|
|
||||||
"scope slot name mapping size mismatch"
|
|
||||||
}
|
|
||||||
val totalSlots = localCount + scopeSlotCount
|
|
||||||
val slotWidth = when {
|
|
||||||
totalSlots < 256 -> 1
|
|
||||||
totalSlots < 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,
|
|
||||||
scopeSlotCount = scopeSlotCount,
|
|
||||||
scopeSlotDepths = scopeSlotDepths,
|
|
||||||
scopeSlotIndices = scopeSlotIndices,
|
|
||||||
scopeSlotNames = if (scopeSlotNames.isEmpty()) Array(scopeSlotCount) { null } else scopeSlotNames,
|
|
||||||
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, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList()
|
|
||||||
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
|
||||||
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
|
||||||
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
|
||||||
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.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
|
||||||
listOf(OperandKind.CONST)
|
|
||||||
Opcode.DECL_LOCAL ->
|
|
||||||
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.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_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_SLOT ->
|
|
||||||
listOf(OperandKind.SLOT, 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -29,21 +29,28 @@ import net.sergeych.lyng.obj.*
|
|||||||
class BytecodeCompiler(
|
class BytecodeCompiler(
|
||||||
private val allowLocalSlots: Boolean = true,
|
private val allowLocalSlots: Boolean = true,
|
||||||
) {
|
) {
|
||||||
private var builder = BytecodeBuilder()
|
private var builder = CmdBuilder()
|
||||||
private var nextSlot = 0
|
private var nextSlot = 0
|
||||||
|
private var nextAddrSlot = 0
|
||||||
private var scopeSlotCount = 0
|
private var scopeSlotCount = 0
|
||||||
private var scopeSlotDepths = IntArray(0)
|
private var scopeSlotDepths = IntArray(0)
|
||||||
private var scopeSlotIndices = IntArray(0)
|
private var scopeSlotIndices = IntArray(0)
|
||||||
private var scopeSlotNames = emptyArray<String?>()
|
private var scopeSlotNames = emptyArray<String?>()
|
||||||
private val scopeSlotMap = LinkedHashMap<ScopeSlotKey, Int>()
|
private val scopeSlotMap = LinkedHashMap<ScopeSlotKey, Int>()
|
||||||
private val scopeSlotNameMap = LinkedHashMap<ScopeSlotKey, String>()
|
private val scopeSlotNameMap = LinkedHashMap<ScopeSlotKey, String>()
|
||||||
|
private val addrSlotByScopeSlot = LinkedHashMap<Int, Int>()
|
||||||
|
private data class LocalSlotInfo(val name: String, val isMutable: Boolean, val depth: Int)
|
||||||
|
private val localSlotInfoMap = LinkedHashMap<ScopeSlotKey, LocalSlotInfo>()
|
||||||
|
private val localSlotIndexByKey = LinkedHashMap<ScopeSlotKey, Int>()
|
||||||
|
private val localSlotIndexByName = LinkedHashMap<String, Int>()
|
||||||
|
private var localSlotNames = emptyArray<String?>()
|
||||||
|
private var localSlotMutables = BooleanArray(0)
|
||||||
|
private var localSlotDepths = IntArray(0)
|
||||||
|
private val declaredLocalKeys = LinkedHashSet<ScopeSlotKey>()
|
||||||
private val slotTypes = mutableMapOf<Int, SlotType>()
|
private val slotTypes = mutableMapOf<Int, SlotType>()
|
||||||
private val intLoopVarNames = LinkedHashSet<String>()
|
private val intLoopVarNames = LinkedHashSet<String>()
|
||||||
private val loopVarNames = LinkedHashSet<String>()
|
|
||||||
private val loopVarSlotIndexByName = LinkedHashMap<String, Int>()
|
|
||||||
private val loopVarSlotIdByName = LinkedHashMap<String, Int>()
|
|
||||||
|
|
||||||
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): BytecodeFunction? {
|
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? {
|
||||||
prepareCompilation(stmt)
|
prepareCompilation(stmt)
|
||||||
return when (stmt) {
|
return when (stmt) {
|
||||||
is ExpressionStatement -> compileExpression(name, stmt)
|
is ExpressionStatement -> compileExpression(name, stmt)
|
||||||
@ -55,12 +62,22 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun compileExpression(name: String, stmt: ExpressionStatement): BytecodeFunction? {
|
fun compileExpression(name: String, stmt: ExpressionStatement): CmdFunction? {
|
||||||
prepareCompilation(stmt)
|
prepareCompilation(stmt)
|
||||||
val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null
|
val value = compileRefWithFallback(stmt.ref, null, stmt.pos) ?: return null
|
||||||
builder.emit(Opcode.RET, value.slot)
|
builder.emit(Opcode.RET, value.slot)
|
||||||
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
|
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
|
||||||
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
return builder.build(
|
||||||
|
name,
|
||||||
|
localCount,
|
||||||
|
addrCount = nextAddrSlot,
|
||||||
|
scopeSlotDepths,
|
||||||
|
scopeSlotIndices,
|
||||||
|
scopeSlotNames,
|
||||||
|
localSlotNames,
|
||||||
|
localSlotMutables,
|
||||||
|
localSlotDepths
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class CompiledValue(val slot: Int, val type: SlotType)
|
private data class CompiledValue(val slot: Int, val type: SlotType)
|
||||||
@ -74,12 +91,18 @@ class BytecodeCompiler(
|
|||||||
if (!allowLocalSlots) return null
|
if (!allowLocalSlots) return null
|
||||||
if (ref.isDelegated) return null
|
if (ref.isDelegated) return null
|
||||||
if (ref.name.isEmpty()) return null
|
if (ref.name.isEmpty()) return null
|
||||||
val loopSlotId = loopVarSlotIdByName[ref.name]
|
val mapped = resolveSlot(ref) ?: return null
|
||||||
val mapped = loopSlotId ?: scopeSlotMap[ScopeSlotKey(refDepth(ref), refSlot(ref))] ?: return null
|
var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
|
||||||
val resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
|
|
||||||
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
|
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
|
||||||
updateSlotType(mapped, SlotType.INT)
|
updateSlotType(mapped, SlotType.INT)
|
||||||
return CompiledValue(mapped, SlotType.INT)
|
resolved = SlotType.INT
|
||||||
|
}
|
||||||
|
if (mapped < scopeSlotCount && resolved != SlotType.UNKNOWN) {
|
||||||
|
val addrSlot = ensureScopeAddr(mapped)
|
||||||
|
val local = allocSlot()
|
||||||
|
emitLoadFromAddr(addrSlot, local, resolved)
|
||||||
|
updateSlotType(local, resolved)
|
||||||
|
return CompiledValue(local, resolved)
|
||||||
}
|
}
|
||||||
CompiledValue(mapped, resolved)
|
CompiledValue(mapped, resolved)
|
||||||
}
|
}
|
||||||
@ -87,7 +110,7 @@ class BytecodeCompiler(
|
|||||||
is UnaryOpRef -> compileUnary(ref)
|
is UnaryOpRef -> compileUnary(ref)
|
||||||
is AssignRef -> compileAssign(ref)
|
is AssignRef -> compileAssign(ref)
|
||||||
is AssignOpRef -> compileAssignOp(ref)
|
is AssignOpRef -> compileAssignOp(ref)
|
||||||
is IncDecRef -> compileIncDec(ref)
|
is IncDecRef -> compileIncDec(ref, true)
|
||||||
is ConditionalRef -> compileConditional(ref)
|
is ConditionalRef -> compileConditional(ref)
|
||||||
is ElvisRef -> compileElvis(ref)
|
is ElvisRef -> compileElvis(ref)
|
||||||
is CallRef -> compileCall(ref)
|
is CallRef -> compileCall(ref)
|
||||||
@ -626,17 +649,17 @@ class BytecodeCompiler(
|
|||||||
if (op == BinOp.AND) {
|
if (op == BinOp.AND) {
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_FALSE,
|
Opcode.JMP_IF_FALSE,
|
||||||
listOf(BytecodeBuilder.Operand.IntVal(leftValue.slot), BytecodeBuilder.Operand.LabelRef(shortLabel))
|
listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(shortLabel))
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_TRUE,
|
Opcode.JMP_IF_TRUE,
|
||||||
listOf(BytecodeBuilder.Operand.IntVal(leftValue.slot), BytecodeBuilder.Operand.LabelRef(shortLabel))
|
listOf(CmdBuilder.Operand.IntVal(leftValue.slot), CmdBuilder.Operand.LabelRef(shortLabel))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val rightValue = compileRefWithFallback(right, SlotType.BOOL, pos) ?: return null
|
val rightValue = compileRefWithFallback(right, SlotType.BOOL, pos) ?: return null
|
||||||
emitMove(rightValue, resultSlot)
|
emitMove(rightValue, resultSlot)
|
||||||
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
builder.mark(shortLabel)
|
builder.mark(shortLabel)
|
||||||
val constId = builder.addConst(BytecodeConst.Bool(op == BinOp.OR))
|
val constId = builder.addConst(BytecodeConst.Bool(op == BinOp.OR))
|
||||||
builder.emit(Opcode.CONST_BOOL, constId, resultSlot)
|
builder.emit(Opcode.CONST_BOOL, constId, resultSlot)
|
||||||
@ -648,27 +671,49 @@ class BytecodeCompiler(
|
|||||||
val target = assignTarget(ref) ?: return null
|
val target = assignTarget(ref) ?: return null
|
||||||
if (!allowLocalSlots) return null
|
if (!allowLocalSlots) return null
|
||||||
if (!target.isMutable || target.isDelegated) return null
|
if (!target.isMutable || target.isDelegated) return null
|
||||||
if (refDepth(target) > 0) return null
|
|
||||||
val value = compileRef(assignValue(ref)) ?: return null
|
val value = compileRef(assignValue(ref)) ?: return null
|
||||||
val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null
|
val slot = resolveSlot(target) ?: return null
|
||||||
|
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
||||||
|
val addrSlot = ensureScopeAddr(slot)
|
||||||
|
emitStoreToAddr(value.slot, addrSlot, value.type)
|
||||||
|
} else if (slot < scopeSlotCount) {
|
||||||
|
val addrSlot = ensureScopeAddr(slot)
|
||||||
|
emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ)
|
||||||
|
} else {
|
||||||
when (value.type) {
|
when (value.type) {
|
||||||
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
|
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot)
|
||||||
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot)
|
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot)
|
||||||
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot)
|
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot)
|
||||||
else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot)
|
else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
updateSlotType(slot, value.type)
|
updateSlotType(slot, value.type)
|
||||||
return CompiledValue(slot, value.type)
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileAssignOp(ref: AssignOpRef): CompiledValue? {
|
private fun compileAssignOp(ref: AssignOpRef): CompiledValue? {
|
||||||
val target = ref.target as? LocalSlotRef ?: return null
|
val target = ref.target as? LocalSlotRef ?: return null
|
||||||
if (!allowLocalSlots) return null
|
if (!allowLocalSlots) return null
|
||||||
if (!target.isMutable || target.isDelegated) return null
|
if (!target.isMutable || target.isDelegated) return null
|
||||||
if (refDepth(target) > 0) return null
|
val slot = resolveSlot(target) ?: return null
|
||||||
val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null
|
|
||||||
val targetType = slotTypes[slot] ?: return null
|
val targetType = slotTypes[slot] ?: return null
|
||||||
val rhs = compileRef(ref.value) ?: return null
|
val rhs = compileRef(ref.value) ?: return null
|
||||||
|
if (slot < scopeSlotCount) {
|
||||||
|
val addrSlot = ensureScopeAddr(slot)
|
||||||
|
val current = allocSlot()
|
||||||
|
emitLoadFromAddr(addrSlot, current, targetType)
|
||||||
|
val result = when (ref.op) {
|
||||||
|
BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ)
|
||||||
|
BinOp.MINUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.SUB_INT, Opcode.SUB_REAL, Opcode.SUB_OBJ)
|
||||||
|
BinOp.STAR -> compileAssignOpBinary(targetType, rhs, current, Opcode.MUL_INT, Opcode.MUL_REAL, Opcode.MUL_OBJ)
|
||||||
|
BinOp.SLASH -> compileAssignOpBinary(targetType, rhs, current, Opcode.DIV_INT, Opcode.DIV_REAL, Opcode.DIV_OBJ)
|
||||||
|
BinOp.PERCENT -> compileAssignOpBinary(targetType, rhs, current, Opcode.MOD_INT, null, Opcode.MOD_OBJ)
|
||||||
|
else -> null
|
||||||
|
} ?: return null
|
||||||
|
emitStoreToAddr(current, addrSlot, result.type)
|
||||||
|
updateSlotType(slot, result.type)
|
||||||
|
return CompiledValue(current, result.type)
|
||||||
|
}
|
||||||
val out = slot
|
val out = slot
|
||||||
val result = when (ref.op) {
|
val result = when (ref.op) {
|
||||||
BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ)
|
BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ)
|
||||||
@ -733,15 +778,79 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileIncDec(ref: IncDecRef): CompiledValue? {
|
private fun compileIncDec(ref: IncDecRef, wantResult: Boolean): CompiledValue? {
|
||||||
val target = ref.target as? LocalSlotRef ?: return null
|
val target = ref.target as? LocalSlotRef ?: return null
|
||||||
if (!allowLocalSlots) return null
|
if (!allowLocalSlots) return null
|
||||||
if (!target.isMutable || target.isDelegated) return null
|
if (!target.isMutable || target.isDelegated) return null
|
||||||
val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null
|
val slot = resolveSlot(target) ?: return null
|
||||||
val slotType = slotTypes[slot] ?: SlotType.UNKNOWN
|
val slotType = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||||
|
if (slot < scopeSlotCount && slotType != SlotType.UNKNOWN) {
|
||||||
|
val addrSlot = ensureScopeAddr(slot)
|
||||||
|
val current = allocSlot()
|
||||||
|
emitLoadFromAddr(addrSlot, current, slotType)
|
||||||
|
val result = when (slotType) {
|
||||||
|
SlotType.INT -> {
|
||||||
|
if (wantResult && ref.isPost) {
|
||||||
|
val old = allocSlot()
|
||||||
|
builder.emit(Opcode.MOVE_INT, current, old)
|
||||||
|
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, current)
|
||||||
|
emitStoreToAddr(current, addrSlot, SlotType.INT)
|
||||||
|
CompiledValue(old, SlotType.INT)
|
||||||
|
} else {
|
||||||
|
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, current)
|
||||||
|
emitStoreToAddr(current, addrSlot, SlotType.INT)
|
||||||
|
CompiledValue(current, SlotType.INT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SlotType.REAL -> {
|
||||||
|
val oneSlot = allocSlot()
|
||||||
|
val oneId = builder.addConst(BytecodeConst.RealVal(1.0))
|
||||||
|
builder.emit(Opcode.CONST_REAL, oneId, oneSlot)
|
||||||
|
if (wantResult && ref.isPost) {
|
||||||
|
val old = allocSlot()
|
||||||
|
builder.emit(Opcode.MOVE_REAL, current, old)
|
||||||
|
val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
|
||||||
|
builder.emit(op, current, oneSlot, current)
|
||||||
|
emitStoreToAddr(current, addrSlot, SlotType.REAL)
|
||||||
|
CompiledValue(old, SlotType.REAL)
|
||||||
|
} else {
|
||||||
|
val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
|
||||||
|
builder.emit(op, current, oneSlot, current)
|
||||||
|
emitStoreToAddr(current, addrSlot, SlotType.REAL)
|
||||||
|
CompiledValue(current, SlotType.REAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SlotType.OBJ -> {
|
||||||
|
val oneSlot = allocSlot()
|
||||||
|
val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
|
||||||
|
builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
|
||||||
|
val boxed = allocSlot()
|
||||||
|
builder.emit(Opcode.BOX_OBJ, current, boxed)
|
||||||
|
if (wantResult && ref.isPost) {
|
||||||
|
val result = allocSlot()
|
||||||
|
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
|
||||||
|
builder.emit(op, boxed, oneSlot, result)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, result, boxed)
|
||||||
|
emitStoreToAddr(boxed, addrSlot, SlotType.OBJ)
|
||||||
|
updateSlotType(slot, SlotType.OBJ)
|
||||||
|
CompiledValue(boxed, SlotType.OBJ)
|
||||||
|
} else {
|
||||||
|
val result = allocSlot()
|
||||||
|
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
|
||||||
|
builder.emit(op, boxed, oneSlot, result)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, result, boxed)
|
||||||
|
emitStoreToAddr(boxed, addrSlot, SlotType.OBJ)
|
||||||
|
updateSlotType(slot, SlotType.OBJ)
|
||||||
|
CompiledValue(result, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
if (result != null) return result
|
||||||
|
}
|
||||||
return when (slotType) {
|
return when (slotType) {
|
||||||
SlotType.INT -> {
|
SlotType.INT -> {
|
||||||
if (ref.isPost) {
|
if (wantResult && ref.isPost) {
|
||||||
val old = allocSlot()
|
val old = allocSlot()
|
||||||
builder.emit(Opcode.MOVE_INT, slot, old)
|
builder.emit(Opcode.MOVE_INT, slot, old)
|
||||||
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
|
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
|
||||||
@ -755,7 +864,7 @@ class BytecodeCompiler(
|
|||||||
val oneSlot = allocSlot()
|
val oneSlot = allocSlot()
|
||||||
val oneId = builder.addConst(BytecodeConst.RealVal(1.0))
|
val oneId = builder.addConst(BytecodeConst.RealVal(1.0))
|
||||||
builder.emit(Opcode.CONST_REAL, oneId, oneSlot)
|
builder.emit(Opcode.CONST_REAL, oneId, oneSlot)
|
||||||
if (ref.isPost) {
|
if (wantResult && ref.isPost) {
|
||||||
val old = allocSlot()
|
val old = allocSlot()
|
||||||
builder.emit(Opcode.MOVE_REAL, slot, old)
|
builder.emit(Opcode.MOVE_REAL, slot, old)
|
||||||
val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
|
val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
|
||||||
@ -773,7 +882,7 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
|
builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
|
||||||
val current = allocSlot()
|
val current = allocSlot()
|
||||||
builder.emit(Opcode.BOX_OBJ, slot, current)
|
builder.emit(Opcode.BOX_OBJ, slot, current)
|
||||||
if (ref.isPost) {
|
if (wantResult && ref.isPost) {
|
||||||
val result = allocSlot()
|
val result = allocSlot()
|
||||||
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
|
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
|
||||||
builder.emit(op, current, oneSlot, result)
|
builder.emit(op, current, oneSlot, result)
|
||||||
@ -790,7 +899,7 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
SlotType.UNKNOWN -> {
|
SlotType.UNKNOWN -> {
|
||||||
if (ref.isPost) {
|
if (wantResult && ref.isPost) {
|
||||||
val old = allocSlot()
|
val old = allocSlot()
|
||||||
builder.emit(Opcode.MOVE_INT, slot, old)
|
builder.emit(Opcode.MOVE_INT, slot, old)
|
||||||
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
|
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
|
||||||
@ -814,12 +923,12 @@ class BytecodeCompiler(
|
|||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_FALSE,
|
Opcode.JMP_IF_FALSE,
|
||||||
listOf(BytecodeBuilder.Operand.IntVal(condition.slot), BytecodeBuilder.Operand.LabelRef(elseLabel))
|
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel))
|
||||||
)
|
)
|
||||||
val thenValue = compileRefWithFallback(ref.ifTrue, null, Pos.builtIn) ?: return null
|
val thenValue = compileRefWithFallback(ref.ifTrue, null, Pos.builtIn) ?: return null
|
||||||
val thenObj = ensureObjSlot(thenValue)
|
val thenObj = ensureObjSlot(thenValue)
|
||||||
builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot)
|
builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot)
|
||||||
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
builder.mark(elseLabel)
|
builder.mark(elseLabel)
|
||||||
val elseValue = compileRefWithFallback(ref.ifFalse, null, Pos.builtIn) ?: return null
|
val elseValue = compileRefWithFallback(ref.ifFalse, null, Pos.builtIn) ?: return null
|
||||||
val elseObj = ensureObjSlot(elseValue)
|
val elseObj = ensureObjSlot(elseValue)
|
||||||
@ -841,10 +950,10 @@ class BytecodeCompiler(
|
|||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_TRUE,
|
Opcode.JMP_IF_TRUE,
|
||||||
listOf(BytecodeBuilder.Operand.IntVal(cmpSlot), BytecodeBuilder.Operand.LabelRef(rightLabel))
|
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(rightLabel))
|
||||||
)
|
)
|
||||||
builder.emit(Opcode.MOVE_OBJ, leftObj.slot, resultSlot)
|
builder.emit(Opcode.MOVE_OBJ, leftObj.slot, resultSlot)
|
||||||
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
builder.mark(rightLabel)
|
builder.mark(rightLabel)
|
||||||
val rightValue = compileRefWithFallback(ref.right, null, Pos.builtIn) ?: return null
|
val rightValue = compileRefWithFallback(ref.right, null, Pos.builtIn) ?: return null
|
||||||
val rightObj = ensureObjSlot(rightValue)
|
val rightObj = ensureObjSlot(rightValue)
|
||||||
@ -941,7 +1050,7 @@ class BytecodeCompiler(
|
|||||||
return 0x8000 or planId
|
return 0x8000 or planId
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileIf(name: String, stmt: IfStatement): BytecodeFunction? {
|
private fun compileIf(name: String, stmt: IfStatement): CmdFunction? {
|
||||||
val conditionStmt = stmt.condition as? ExpressionStatement ?: return null
|
val conditionStmt = stmt.condition as? ExpressionStatement ?: return null
|
||||||
val condValue = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
|
val condValue = compileRefWithFallback(conditionStmt.ref, SlotType.BOOL, stmt.pos) ?: return null
|
||||||
if (condValue.type != SlotType.BOOL) return null
|
if (condValue.type != SlotType.BOOL) return null
|
||||||
@ -952,11 +1061,11 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_FALSE,
|
Opcode.JMP_IF_FALSE,
|
||||||
listOf(BytecodeBuilder.Operand.IntVal(condValue.slot), BytecodeBuilder.Operand.LabelRef(elseLabel))
|
listOf(CmdBuilder.Operand.IntVal(condValue.slot), CmdBuilder.Operand.LabelRef(elseLabel))
|
||||||
)
|
)
|
||||||
val thenValue = compileStatementValue(stmt.ifBody) ?: return null
|
val thenValue = compileStatementValue(stmt.ifBody) ?: return null
|
||||||
emitMove(thenValue, resultSlot)
|
emitMove(thenValue, resultSlot)
|
||||||
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
|
||||||
builder.mark(elseLabel)
|
builder.mark(elseLabel)
|
||||||
if (stmt.elseBody != null) {
|
if (stmt.elseBody != null) {
|
||||||
@ -970,28 +1079,68 @@ class BytecodeCompiler(
|
|||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
builder.emit(Opcode.RET, resultSlot)
|
builder.emit(Opcode.RET, resultSlot)
|
||||||
val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
|
val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
|
||||||
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
return builder.build(
|
||||||
|
name,
|
||||||
|
localCount,
|
||||||
|
addrCount = nextAddrSlot,
|
||||||
|
scopeSlotDepths,
|
||||||
|
scopeSlotIndices,
|
||||||
|
scopeSlotNames,
|
||||||
|
localSlotNames,
|
||||||
|
localSlotMutables,
|
||||||
|
localSlotDepths
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): BytecodeFunction? {
|
private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): CmdFunction? {
|
||||||
val resultSlot = emitForIn(stmt) ?: return null
|
val resultSlot = emitForIn(stmt, true) ?: return null
|
||||||
builder.emit(Opcode.RET, resultSlot)
|
builder.emit(Opcode.RET, resultSlot)
|
||||||
val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
|
val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
|
||||||
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
return builder.build(
|
||||||
|
name,
|
||||||
|
localCount,
|
||||||
|
addrCount = nextAddrSlot,
|
||||||
|
scopeSlotDepths,
|
||||||
|
scopeSlotIndices,
|
||||||
|
scopeSlotNames,
|
||||||
|
localSlotNames,
|
||||||
|
localSlotMutables,
|
||||||
|
localSlotDepths
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileBlock(name: String, stmt: BlockStatement): BytecodeFunction? {
|
private fun compileBlock(name: String, stmt: BlockStatement): CmdFunction? {
|
||||||
val result = emitBlock(stmt) ?: return null
|
val result = emitBlock(stmt, true) ?: return null
|
||||||
builder.emit(Opcode.RET, result.slot)
|
builder.emit(Opcode.RET, result.slot)
|
||||||
val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
|
val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
|
||||||
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
return builder.build(
|
||||||
|
name,
|
||||||
|
localCount,
|
||||||
|
addrCount = nextAddrSlot,
|
||||||
|
scopeSlotDepths,
|
||||||
|
scopeSlotIndices,
|
||||||
|
scopeSlotNames,
|
||||||
|
localSlotNames,
|
||||||
|
localSlotMutables,
|
||||||
|
localSlotDepths
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileVarDecl(name: String, stmt: VarDeclStatement): BytecodeFunction? {
|
private fun compileVarDecl(name: String, stmt: VarDeclStatement): CmdFunction? {
|
||||||
val result = emitVarDecl(stmt) ?: return null
|
val result = emitVarDecl(stmt) ?: return null
|
||||||
builder.emit(Opcode.RET, result.slot)
|
builder.emit(Opcode.RET, result.slot)
|
||||||
val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
|
val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
|
||||||
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
return builder.build(
|
||||||
|
name,
|
||||||
|
localCount,
|
||||||
|
addrCount = nextAddrSlot,
|
||||||
|
scopeSlotDepths,
|
||||||
|
scopeSlotIndices,
|
||||||
|
scopeSlotNames,
|
||||||
|
localSlotNames,
|
||||||
|
localSlotMutables,
|
||||||
|
localSlotDepths
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileStatementValue(stmt: Statement): CompiledValue? {
|
private fun compileStatementValue(stmt: Statement): CompiledValue? {
|
||||||
@ -1001,17 +1150,44 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileStatementValueOrFallback(stmt: Statement): CompiledValue? {
|
private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? {
|
||||||
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||||
return when (target) {
|
return if (needResult) {
|
||||||
|
when (target) {
|
||||||
is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos)
|
is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos)
|
||||||
is IfStatement -> compileIfExpression(target)
|
is IfStatement -> compileIfExpression(target)
|
||||||
is net.sergeych.lyng.ForInStatement -> {
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
val resultSlot = emitForIn(target) ?: return null
|
val resultSlot = emitForIn(target, true) ?: return null
|
||||||
updateSlotType(resultSlot, SlotType.OBJ)
|
updateSlotType(resultSlot, SlotType.OBJ)
|
||||||
CompiledValue(resultSlot, SlotType.OBJ)
|
CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
is BlockStatement -> emitBlock(target)
|
is BlockStatement -> emitBlock(target, true)
|
||||||
|
is VarDeclStatement -> emitVarDecl(target)
|
||||||
|
else -> {
|
||||||
|
val slot = allocSlot()
|
||||||
|
val id = builder.addFallback(target)
|
||||||
|
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
|
||||||
|
builder.emit(Opcode.BOX_OBJ, slot, slot)
|
||||||
|
updateSlotType(slot, SlotType.OBJ)
|
||||||
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
when (target) {
|
||||||
|
is ExpressionStatement -> {
|
||||||
|
val ref = target.ref
|
||||||
|
if (ref is IncDecRef) {
|
||||||
|
compileIncDec(ref, false)
|
||||||
|
} else {
|
||||||
|
compileRefWithFallback(ref, null, target.pos)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IfStatement -> compileIfStatement(target)
|
||||||
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
val resultSlot = emitForIn(target, false) ?: return null
|
||||||
|
CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
is BlockStatement -> emitBlock(target, false)
|
||||||
is VarDeclStatement -> emitVarDecl(target)
|
is VarDeclStatement -> emitVarDecl(target)
|
||||||
else -> {
|
else -> {
|
||||||
val slot = allocSlot()
|
val slot = allocSlot()
|
||||||
@ -1023,31 +1199,78 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun emitBlock(stmt: BlockStatement): CompiledValue? {
|
private fun emitBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? {
|
||||||
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan))
|
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan))
|
||||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
builder.emit(Opcode.PUSH_SCOPE, planId)
|
||||||
|
resetAddrCache()
|
||||||
val statements = stmt.statements()
|
val statements = stmt.statements()
|
||||||
var lastValue: CompiledValue? = null
|
var lastValue: CompiledValue? = null
|
||||||
for (statement in statements) {
|
for ((index, statement) in statements.withIndex()) {
|
||||||
lastValue = compileStatementValueOrFallback(statement) ?: return null
|
val isLast = index == statements.lastIndex
|
||||||
|
val wantResult = needResult && isLast
|
||||||
|
val value = compileStatementValueOrFallback(statement, wantResult) ?: return null
|
||||||
|
if (wantResult) {
|
||||||
|
lastValue = value
|
||||||
}
|
}
|
||||||
var result = lastValue ?: run {
|
}
|
||||||
|
val result = if (needResult) {
|
||||||
|
var value = lastValue ?: run {
|
||||||
val slot = allocSlot()
|
val slot = allocSlot()
|
||||||
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||||
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
||||||
CompiledValue(slot, SlotType.OBJ)
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
if (result.slot < scopeSlotCount) {
|
if (value.slot < scopeSlotCount) {
|
||||||
val captured = allocSlot()
|
val captured = allocSlot()
|
||||||
emitMove(result, captured)
|
emitMove(value, captured)
|
||||||
result = CompiledValue(captured, result.type)
|
value = CompiledValue(captured, value.type)
|
||||||
|
}
|
||||||
|
value
|
||||||
|
} else {
|
||||||
|
lastValue ?: run {
|
||||||
|
val slot = allocSlot()
|
||||||
|
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||||
|
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
||||||
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
builder.emit(Opcode.POP_SCOPE)
|
builder.emit(Opcode.POP_SCOPE)
|
||||||
|
resetAddrCache()
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? {
|
private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? {
|
||||||
|
val localSlot = if (allowLocalSlots && stmt.slotIndex != null) {
|
||||||
|
val depth = stmt.slotDepth ?: 0
|
||||||
|
val key = ScopeSlotKey(depth, stmt.slotIndex)
|
||||||
|
val localIndex = localSlotIndexByKey[key]
|
||||||
|
localIndex?.let { scopeSlotCount + it }
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
if (localSlot != null) {
|
||||||
|
val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
|
||||||
|
builder.emit(Opcode.CONST_NULL, localSlot)
|
||||||
|
updateSlotType(localSlot, SlotType.OBJ)
|
||||||
|
CompiledValue(localSlot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
if (value.slot != localSlot) {
|
||||||
|
emitMove(value, localSlot)
|
||||||
|
}
|
||||||
|
updateSlotType(localSlot, value.type)
|
||||||
|
val declId = builder.addConst(
|
||||||
|
BytecodeConst.LocalDecl(
|
||||||
|
stmt.name,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.isTransient
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.DECL_LOCAL, declId, localSlot)
|
||||||
|
return CompiledValue(localSlot, value.type)
|
||||||
|
}
|
||||||
val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
|
val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
|
||||||
val slot = allocSlot()
|
val slot = allocSlot()
|
||||||
builder.emit(Opcode.CONST_NULL, slot)
|
builder.emit(Opcode.CONST_NULL, slot)
|
||||||
@ -1068,18 +1291,11 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
private fun emitForIn(stmt: net.sergeych.lyng.ForInStatement): Int? {
|
private fun emitForIn(stmt: net.sergeych.lyng.ForInStatement, wantResult: Boolean): Int? {
|
||||||
if (stmt.canBreak) return null
|
if (stmt.canBreak) return null
|
||||||
val range = stmt.constRange ?: return null
|
val range = stmt.constRange ?: return null
|
||||||
val loopSlotId = loopVarSlotIdByName[stmt.loopVarName] ?: return null
|
val loopLocalIndex = localSlotIndexByName[stmt.loopVarName] ?: return null
|
||||||
val slotIndex = loopVarSlotIndexByName[stmt.loopVarName] ?: return null
|
val loopSlotId = scopeSlotCount + loopLocalIndex
|
||||||
val planId = builder.addConst(BytecodeConst.SlotPlan(mapOf(stmt.loopVarName to slotIndex)))
|
|
||||||
val useLoopScope = scopeSlotMap.isEmpty()
|
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
|
||||||
} else {
|
|
||||||
builder.emit(Opcode.PUSH_SLOT_PLAN, planId)
|
|
||||||
}
|
|
||||||
|
|
||||||
val iSlot = allocSlot()
|
val iSlot = allocSlot()
|
||||||
val endSlot = allocSlot()
|
val endSlot = allocSlot()
|
||||||
@ -1089,8 +1305,10 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.CONST_INT, endId, endSlot)
|
builder.emit(Opcode.CONST_INT, endId, endSlot)
|
||||||
|
|
||||||
val resultSlot = allocSlot()
|
val resultSlot = allocSlot()
|
||||||
|
if (wantResult) {
|
||||||
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||||
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
|
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
|
||||||
|
}
|
||||||
|
|
||||||
val loopLabel = builder.label()
|
val loopLabel = builder.label()
|
||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
@ -1099,35 +1317,53 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_TRUE,
|
Opcode.JMP_IF_TRUE,
|
||||||
listOf(BytecodeBuilder.Operand.IntVal(cmpSlot), BytecodeBuilder.Operand.LabelRef(endLabel))
|
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel))
|
||||||
)
|
)
|
||||||
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
|
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
|
||||||
updateSlotType(loopSlotId, SlotType.INT)
|
updateSlotType(loopSlotId, SlotType.INT)
|
||||||
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||||
val bodyValue = compileStatementValueOrFallback(stmt.body) ?: return null
|
val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null
|
||||||
|
if (wantResult) {
|
||||||
val bodyObj = ensureObjSlot(bodyValue)
|
val bodyObj = ensureObjSlot(bodyValue)
|
||||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||||
|
}
|
||||||
builder.emit(Opcode.INC_INT, iSlot)
|
builder.emit(Opcode.INC_INT, iSlot)
|
||||||
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(loopLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
|
||||||
|
|
||||||
builder.mark(endLabel)
|
builder.mark(endLabel)
|
||||||
if (stmt.elseStatement != null) {
|
if (stmt.elseStatement != null) {
|
||||||
val elseValue = compileStatementValueOrFallback(stmt.elseStatement) ?: return null
|
val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null
|
||||||
|
if (wantResult) {
|
||||||
val elseObj = ensureObjSlot(elseValue)
|
val elseObj = ensureObjSlot(elseValue)
|
||||||
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
|
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
|
||||||
}
|
}
|
||||||
if (useLoopScope) {
|
|
||||||
builder.emit(Opcode.POP_SCOPE)
|
|
||||||
} else {
|
|
||||||
builder.emit(Opcode.POP_SLOT_PLAN)
|
|
||||||
}
|
}
|
||||||
return resultSlot
|
return resultSlot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun compileIfStatement(stmt: IfStatement): CompiledValue? {
|
||||||
|
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
||||||
|
if (condition.type != SlotType.BOOL) return null
|
||||||
|
val elseLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_FALSE,
|
||||||
|
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel))
|
||||||
|
)
|
||||||
|
compileStatementValueOrFallback(stmt.ifBody, false) ?: return null
|
||||||
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
builder.mark(elseLabel)
|
||||||
|
stmt.elseBody?.let {
|
||||||
|
compileStatementValueOrFallback(it, false) ?: return null
|
||||||
|
}
|
||||||
|
builder.mark(endLabel)
|
||||||
|
return condition
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateSlotTypeByName(name: String, type: SlotType) {
|
private fun updateSlotTypeByName(name: String, type: SlotType) {
|
||||||
val loopSlotId = loopVarSlotIdByName[name]
|
val localIndex = localSlotIndexByName[name]
|
||||||
if (loopSlotId != null) {
|
if (localIndex != null) {
|
||||||
updateSlotType(loopSlotId, type)
|
updateSlotType(scopeSlotCount + localIndex, type)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for ((key, index) in scopeSlotMap) {
|
for ((key, index) in scopeSlotMap) {
|
||||||
@ -1145,12 +1381,12 @@ class BytecodeCompiler(
|
|||||||
val endLabel = builder.label()
|
val endLabel = builder.label()
|
||||||
builder.emit(
|
builder.emit(
|
||||||
Opcode.JMP_IF_FALSE,
|
Opcode.JMP_IF_FALSE,
|
||||||
listOf(BytecodeBuilder.Operand.IntVal(condition.slot), BytecodeBuilder.Operand.LabelRef(elseLabel))
|
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(elseLabel))
|
||||||
)
|
)
|
||||||
val thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null
|
val thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null
|
||||||
val thenObj = ensureObjSlot(thenValue)
|
val thenObj = ensureObjSlot(thenValue)
|
||||||
builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot)
|
builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot)
|
||||||
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||||
builder.mark(elseLabel)
|
builder.mark(elseLabel)
|
||||||
if (stmt.elseBody != null) {
|
if (stmt.elseBody != null) {
|
||||||
val elseValue = compileStatementValueOrFallback(stmt.elseBody) ?: return null
|
val elseValue = compileStatementValueOrFallback(stmt.elseBody) ?: return null
|
||||||
@ -1178,12 +1414,60 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resetAddrCache() {
|
||||||
|
addrSlotByScopeSlot.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureScopeAddr(scopeSlot: Int): Int {
|
||||||
|
val existing = addrSlotByScopeSlot[scopeSlot]
|
||||||
|
if (existing != null) return existing
|
||||||
|
val addrSlot = nextAddrSlot++
|
||||||
|
addrSlotByScopeSlot[scopeSlot] = addrSlot
|
||||||
|
builder.emit(Opcode.RESOLVE_SCOPE_SLOT, scopeSlot, addrSlot)
|
||||||
|
return addrSlot
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emitLoadFromAddr(addrSlot: Int, dstSlot: Int, type: SlotType) {
|
||||||
|
when (type) {
|
||||||
|
SlotType.INT -> builder.emit(Opcode.LOAD_INT_ADDR, addrSlot, dstSlot)
|
||||||
|
SlotType.REAL -> builder.emit(Opcode.LOAD_REAL_ADDR, addrSlot, dstSlot)
|
||||||
|
SlotType.BOOL -> builder.emit(Opcode.LOAD_BOOL_ADDR, addrSlot, dstSlot)
|
||||||
|
SlotType.OBJ -> builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, dstSlot)
|
||||||
|
else -> builder.emit(Opcode.LOAD_OBJ_ADDR, addrSlot, dstSlot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emitStoreToAddr(srcSlot: Int, addrSlot: Int, type: SlotType) {
|
||||||
|
when (type) {
|
||||||
|
SlotType.INT -> builder.emit(Opcode.STORE_INT_ADDR, srcSlot, addrSlot)
|
||||||
|
SlotType.REAL -> builder.emit(Opcode.STORE_REAL_ADDR, srcSlot, addrSlot)
|
||||||
|
SlotType.BOOL -> builder.emit(Opcode.STORE_BOOL_ADDR, srcSlot, addrSlot)
|
||||||
|
SlotType.OBJ -> builder.emit(Opcode.STORE_OBJ_ADDR, srcSlot, addrSlot)
|
||||||
|
else -> builder.emit(Opcode.STORE_OBJ_ADDR, srcSlot, addrSlot)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun emitMove(value: CompiledValue, dstSlot: Int) {
|
private fun emitMove(value: CompiledValue, dstSlot: Int) {
|
||||||
|
val srcSlot = value.slot
|
||||||
|
val srcIsScope = srcSlot < scopeSlotCount
|
||||||
|
val dstIsScope = dstSlot < scopeSlotCount
|
||||||
|
if (value.type != SlotType.UNKNOWN) {
|
||||||
|
if (srcIsScope && !dstIsScope) {
|
||||||
|
val addrSlot = ensureScopeAddr(srcSlot)
|
||||||
|
emitLoadFromAddr(addrSlot, dstSlot, value.type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (dstIsScope) {
|
||||||
|
val addrSlot = ensureScopeAddr(dstSlot)
|
||||||
|
emitStoreToAddr(srcSlot, addrSlot, value.type)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
when (value.type) {
|
when (value.type) {
|
||||||
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, dstSlot)
|
SlotType.INT -> builder.emit(Opcode.MOVE_INT, srcSlot, dstSlot)
|
||||||
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, dstSlot)
|
SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, srcSlot, dstSlot)
|
||||||
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, dstSlot)
|
SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, srcSlot, dstSlot)
|
||||||
else -> builder.emit(Opcode.MOVE_OBJ, value.slot, dstSlot)
|
else -> builder.emit(Opcode.MOVE_OBJ, srcSlot, dstSlot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1215,6 +1499,7 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
||||||
private fun refDepth(ref: LocalSlotRef): Int = ref.depth
|
private fun refDepth(ref: LocalSlotRef): Int = ref.depth
|
||||||
|
private fun refScopeDepth(ref: LocalSlotRef): Int = ref.scopeDepth
|
||||||
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
||||||
private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
|
private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
|
||||||
private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op
|
private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op
|
||||||
@ -1224,6 +1509,16 @@ class BytecodeCompiler(
|
|||||||
private fun assignValue(ref: AssignRef): ObjRef = ref.value
|
private fun assignValue(ref: AssignRef): ObjRef = ref.value
|
||||||
private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn
|
private fun refPos(ref: BinaryOpRef): Pos = Pos.builtIn
|
||||||
|
|
||||||
|
private fun resolveSlot(ref: LocalSlotRef): Int? {
|
||||||
|
val localKey = ScopeSlotKey(refScopeDepth(ref), refSlot(ref))
|
||||||
|
val localIndex = localSlotIndexByKey[localKey]
|
||||||
|
if (localIndex != null) return scopeSlotCount + localIndex
|
||||||
|
val nameIndex = localSlotIndexByName[ref.name]
|
||||||
|
if (nameIndex != null) return scopeSlotCount + nameIndex
|
||||||
|
val scopeKey = ScopeSlotKey(refDepth(ref), refSlot(ref))
|
||||||
|
return scopeSlotMap[scopeKey]
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateSlotType(slot: Int, type: SlotType) {
|
private fun updateSlotType(slot: Int, type: SlotType) {
|
||||||
if (type == SlotType.UNKNOWN) {
|
if (type == SlotType.UNKNOWN) {
|
||||||
slotTypes.remove(slot)
|
slotTypes.remove(slot)
|
||||||
@ -1233,17 +1528,24 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareCompilation(stmt: Statement) {
|
private fun prepareCompilation(stmt: Statement) {
|
||||||
builder = BytecodeBuilder()
|
builder = CmdBuilder()
|
||||||
nextSlot = 0
|
nextSlot = 0
|
||||||
|
nextAddrSlot = 0
|
||||||
slotTypes.clear()
|
slotTypes.clear()
|
||||||
scopeSlotMap.clear()
|
scopeSlotMap.clear()
|
||||||
|
localSlotInfoMap.clear()
|
||||||
|
localSlotIndexByKey.clear()
|
||||||
|
localSlotIndexByName.clear()
|
||||||
|
localSlotNames = emptyArray()
|
||||||
|
localSlotMutables = BooleanArray(0)
|
||||||
|
localSlotDepths = IntArray(0)
|
||||||
|
declaredLocalKeys.clear()
|
||||||
intLoopVarNames.clear()
|
intLoopVarNames.clear()
|
||||||
loopVarNames.clear()
|
addrSlotByScopeSlot.clear()
|
||||||
loopVarSlotIndexByName.clear()
|
|
||||||
loopVarSlotIdByName.clear()
|
|
||||||
if (allowLocalSlots) {
|
if (allowLocalSlots) {
|
||||||
collectLoopVarNames(stmt)
|
collectLoopVarNames(stmt)
|
||||||
collectScopeSlots(stmt)
|
collectScopeSlots(stmt)
|
||||||
|
collectLoopSlotPlans(stmt, 0)
|
||||||
}
|
}
|
||||||
scopeSlotCount = scopeSlotMap.size
|
scopeSlotCount = scopeSlotMap.size
|
||||||
scopeSlotDepths = IntArray(scopeSlotCount)
|
scopeSlotDepths = IntArray(scopeSlotCount)
|
||||||
@ -1255,28 +1557,26 @@ class BytecodeCompiler(
|
|||||||
scopeSlotIndices[index] = key.slot
|
scopeSlotIndices[index] = key.slot
|
||||||
scopeSlotNames[index] = name
|
scopeSlotNames[index] = name
|
||||||
}
|
}
|
||||||
if (loopVarNames.isNotEmpty()) {
|
if (allowLocalSlots && localSlotInfoMap.isNotEmpty()) {
|
||||||
var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1
|
val names = ArrayList<String?>(localSlotInfoMap.size)
|
||||||
for (name in loopVarNames) {
|
val mutables = BooleanArray(localSlotInfoMap.size)
|
||||||
maxSlotIndex += 1
|
val depths = IntArray(localSlotInfoMap.size)
|
||||||
loopVarSlotIndexByName[name] = maxSlotIndex
|
var index = 0
|
||||||
|
for ((key, info) in localSlotInfoMap) {
|
||||||
|
localSlotIndexByKey[key] = index
|
||||||
|
if (!localSlotIndexByName.containsKey(info.name)) {
|
||||||
|
localSlotIndexByName[info.name] = index
|
||||||
}
|
}
|
||||||
val start = scopeSlotCount
|
names.add(info.name)
|
||||||
val total = scopeSlotCount + loopVarSlotIndexByName.size
|
mutables[index] = info.isMutable
|
||||||
scopeSlotDepths = scopeSlotDepths.copyOf(total)
|
depths[index] = info.depth
|
||||||
scopeSlotIndices = scopeSlotIndices.copyOf(total)
|
index += 1
|
||||||
scopeSlotNames = scopeSlotNames.copyOf(total)
|
|
||||||
var cursor = start
|
|
||||||
for ((name, slotIndex) in loopVarSlotIndexByName) {
|
|
||||||
loopVarSlotIdByName[name] = cursor
|
|
||||||
scopeSlotDepths[cursor] = 0
|
|
||||||
scopeSlotIndices[cursor] = slotIndex
|
|
||||||
scopeSlotNames[cursor] = name
|
|
||||||
cursor += 1
|
|
||||||
}
|
}
|
||||||
scopeSlotCount = total
|
localSlotNames = names.toTypedArray()
|
||||||
|
localSlotMutables = mutables
|
||||||
|
localSlotDepths = depths
|
||||||
}
|
}
|
||||||
nextSlot = scopeSlotCount
|
nextSlot = scopeSlotCount + localSlotNames.size
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectScopeSlots(stmt: Statement) {
|
private fun collectScopeSlots(stmt: Statement) {
|
||||||
@ -1292,6 +1592,11 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is VarDeclStatement -> {
|
is VarDeclStatement -> {
|
||||||
|
val slotIndex = stmt.slotIndex
|
||||||
|
val slotDepth = stmt.slotDepth
|
||||||
|
if (allowLocalSlots && slotIndex != null && slotDepth != null) {
|
||||||
|
declaredLocalKeys.add(ScopeSlotKey(slotDepth, slotIndex))
|
||||||
|
}
|
||||||
stmt.initializer?.let { collectScopeSlots(it) }
|
stmt.initializer?.let { collectScopeSlots(it) }
|
||||||
}
|
}
|
||||||
is IfStatement -> {
|
is IfStatement -> {
|
||||||
@ -1308,6 +1613,45 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun collectLoopSlotPlans(stmt: Statement, scopeDepth: Int) {
|
||||||
|
if (stmt is BytecodeStatement) {
|
||||||
|
collectLoopSlotPlans(stmt.original, scopeDepth)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
when (stmt) {
|
||||||
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
collectLoopSlotPlans(stmt.source, scopeDepth)
|
||||||
|
val loopDepth = scopeDepth + 1
|
||||||
|
for ((name, slotIndex) in stmt.loopSlotPlan) {
|
||||||
|
val key = ScopeSlotKey(loopDepth, slotIndex)
|
||||||
|
if (!localSlotInfoMap.containsKey(key)) {
|
||||||
|
localSlotInfoMap[key] = LocalSlotInfo(name, isMutable = true, depth = loopDepth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collectLoopSlotPlans(stmt.body, loopDepth)
|
||||||
|
stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) }
|
||||||
|
}
|
||||||
|
is BlockStatement -> {
|
||||||
|
val nextDepth = scopeDepth + 1
|
||||||
|
for (child in stmt.statements()) {
|
||||||
|
collectLoopSlotPlans(child, nextDepth)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is IfStatement -> {
|
||||||
|
collectLoopSlotPlans(stmt.condition, scopeDepth)
|
||||||
|
collectLoopSlotPlans(stmt.ifBody, scopeDepth)
|
||||||
|
stmt.elseBody?.let { collectLoopSlotPlans(it, scopeDepth) }
|
||||||
|
}
|
||||||
|
is VarDeclStatement -> {
|
||||||
|
stmt.initializer?.let { collectLoopSlotPlans(it, scopeDepth) }
|
||||||
|
}
|
||||||
|
is ExpressionStatement -> {
|
||||||
|
// no-op
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun collectLoopVarNames(stmt: Statement) {
|
private fun collectLoopVarNames(stmt: Statement) {
|
||||||
if (stmt is BytecodeStatement) {
|
if (stmt is BytecodeStatement) {
|
||||||
collectLoopVarNames(stmt.original)
|
collectLoopVarNames(stmt.original)
|
||||||
@ -1317,7 +1661,6 @@ class BytecodeCompiler(
|
|||||||
is net.sergeych.lyng.ForInStatement -> {
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
if (stmt.constRange != null) {
|
if (stmt.constRange != null) {
|
||||||
intLoopVarNames.add(stmt.loopVarName)
|
intLoopVarNames.add(stmt.loopVarName)
|
||||||
loopVarNames.add(stmt.loopVarName)
|
|
||||||
}
|
}
|
||||||
collectLoopVarNames(stmt.source)
|
collectLoopVarNames(stmt.source)
|
||||||
collectLoopVarNames(stmt.body)
|
collectLoopVarNames(stmt.body)
|
||||||
@ -1370,7 +1713,15 @@ class BytecodeCompiler(
|
|||||||
private fun collectScopeSlotsRef(ref: ObjRef) {
|
private fun collectScopeSlotsRef(ref: ObjRef) {
|
||||||
when (ref) {
|
when (ref) {
|
||||||
is LocalSlotRef -> {
|
is LocalSlotRef -> {
|
||||||
if (loopVarNames.contains(ref.name)) return
|
val localKey = ScopeSlotKey(refScopeDepth(ref), refSlot(ref))
|
||||||
|
val shouldLocalize = declaredLocalKeys.contains(localKey) ||
|
||||||
|
intLoopVarNames.contains(ref.name)
|
||||||
|
if (allowLocalSlots && !ref.isDelegated && shouldLocalize) {
|
||||||
|
if (!localSlotInfoMap.containsKey(localKey)) {
|
||||||
|
localSlotInfoMap[localKey] = LocalSlotInfo(ref.name, ref.isMutable, localKey.depth)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
val key = ScopeSlotKey(refDepth(ref), refSlot(ref))
|
val key = ScopeSlotKey(refDepth(ref), refSlot(ref))
|
||||||
if (!scopeSlotMap.containsKey(key)) {
|
if (!scopeSlotMap.containsKey(key)) {
|
||||||
scopeSlotMap[key] = scopeSlotMap.size
|
scopeSlotMap[key] = scopeSlotMap.size
|
||||||
@ -1387,6 +1738,14 @@ class BytecodeCompiler(
|
|||||||
is AssignRef -> {
|
is AssignRef -> {
|
||||||
val target = assignTarget(ref)
|
val target = assignTarget(ref)
|
||||||
if (target != null) {
|
if (target != null) {
|
||||||
|
val localKey = ScopeSlotKey(refScopeDepth(target), refSlot(target))
|
||||||
|
val shouldLocalize = declaredLocalKeys.contains(localKey) ||
|
||||||
|
intLoopVarNames.contains(target.name)
|
||||||
|
if (allowLocalSlots && !target.isDelegated && shouldLocalize) {
|
||||||
|
if (!localSlotInfoMap.containsKey(localKey)) {
|
||||||
|
localSlotInfoMap[localKey] = LocalSlotInfo(target.name, target.isMutable, localKey.depth)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
val key = ScopeSlotKey(refDepth(target), refSlot(target))
|
val key = ScopeSlotKey(refDepth(target), refSlot(target))
|
||||||
if (!scopeSlotMap.containsKey(key)) {
|
if (!scopeSlotMap.containsKey(key)) {
|
||||||
scopeSlotMap[key] = scopeSlotMap.size
|
scopeSlotMap[key] = scopeSlotMap.size
|
||||||
@ -1395,6 +1754,7 @@ class BytecodeCompiler(
|
|||||||
scopeSlotNameMap[key] = target.name
|
scopeSlotNameMap[key] = target.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
collectScopeSlotsRef(assignValue(ref))
|
collectScopeSlotsRef(assignValue(ref))
|
||||||
}
|
}
|
||||||
is AssignOpRef -> {
|
is AssignOpRef -> {
|
||||||
|
|||||||
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
}
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
val name = if (v < fn.scopeSlotCount) fn.scopeSlotNames[v] else null
|
|
||||||
operands += if (name != null) "s$v($name)" else "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, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList()
|
|
||||||
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
|
||||||
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
|
||||||
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
|
||||||
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.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
|
||||||
listOf(OperandKind.CONST)
|
|
||||||
Opcode.DECL_LOCAL ->
|
|
||||||
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.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_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_SLOT ->
|
|
||||||
listOf(OperandKind.SLOT, 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -23,30 +23,111 @@ import net.sergeych.lyng.obj.Obj
|
|||||||
|
|
||||||
class BytecodeStatement private constructor(
|
class BytecodeStatement private constructor(
|
||||||
val original: Statement,
|
val original: Statement,
|
||||||
private val function: BytecodeFunction,
|
private val function: CmdFunction,
|
||||||
) : Statement(original.isStaticConst, original.isConst, original.returnType) {
|
) : Statement(original.isStaticConst, original.isConst, original.returnType) {
|
||||||
override val pos: Pos = original.pos
|
override val pos: Pos = original.pos
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
return BytecodeVm().execute(function, scope, emptyList())
|
return CmdVm().execute(function, scope, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun bytecodeFunction(): BytecodeFunction = function
|
internal fun bytecodeFunction(): CmdFunction = function
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun wrap(statement: Statement, nameHint: String, allowLocalSlots: Boolean): Statement {
|
fun wrap(statement: Statement, nameHint: String, allowLocalSlots: Boolean): Statement {
|
||||||
if (statement is BytecodeStatement) return statement
|
if (statement is BytecodeStatement) return statement
|
||||||
val compiler = BytecodeCompiler(allowLocalSlots = allowLocalSlots)
|
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||||
|
if (hasUnsupported) return unwrapDeep(statement)
|
||||||
|
val safeLocals = allowLocalSlots
|
||||||
|
val compiler = BytecodeCompiler(allowLocalSlots = safeLocals)
|
||||||
val compiled = compiler.compileStatement(nameHint, statement)
|
val compiled = compiler.compileStatement(nameHint, statement)
|
||||||
val fn = compiled ?: run {
|
val fn = compiled ?: run {
|
||||||
val builder = BytecodeBuilder()
|
val builder = CmdBuilder()
|
||||||
val slot = 0
|
val slot = 0
|
||||||
val id = builder.addFallback(statement)
|
val id = builder.addFallback(statement)
|
||||||
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
|
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
|
||||||
builder.emit(Opcode.RET, slot)
|
builder.emit(Opcode.RET, slot)
|
||||||
builder.build(nameHint, localCount = 1)
|
builder.build(
|
||||||
|
nameHint,
|
||||||
|
localCount = 1,
|
||||||
|
addrCount = 0,
|
||||||
|
localSlotNames = emptyArray(),
|
||||||
|
localSlotMutables = BooleanArray(0),
|
||||||
|
localSlotDepths = IntArray(0)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return BytecodeStatement(statement, fn)
|
return BytecodeStatement(statement, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun containsUnsupportedStatement(stmt: Statement): Boolean {
|
||||||
|
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||||
|
return when (target) {
|
||||||
|
is net.sergeych.lyng.ExpressionStatement -> false
|
||||||
|
is net.sergeych.lyng.IfStatement -> {
|
||||||
|
containsUnsupportedStatement(target.condition) ||
|
||||||
|
containsUnsupportedStatement(target.ifBody) ||
|
||||||
|
(target.elseBody?.let { containsUnsupportedStatement(it) } ?: false)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
target.constRange == null || target.canBreak ||
|
||||||
|
containsUnsupportedStatement(target.source) ||
|
||||||
|
containsUnsupportedStatement(target.body) ||
|
||||||
|
(target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.BlockStatement ->
|
||||||
|
target.statements().any { containsUnsupportedStatement(it) }
|
||||||
|
is net.sergeych.lyng.VarDeclStatement ->
|
||||||
|
target.initializer?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
|
else -> true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unwrapDeep(stmt: Statement): Statement {
|
||||||
|
return when (stmt) {
|
||||||
|
is BytecodeStatement -> unwrapDeep(stmt.original)
|
||||||
|
is net.sergeych.lyng.BlockStatement -> {
|
||||||
|
val unwrapped = stmt.statements().map { unwrapDeep(it) }
|
||||||
|
net.sergeych.lyng.BlockStatement(
|
||||||
|
net.sergeych.lyng.Script(stmt.pos, unwrapped),
|
||||||
|
stmt.slotPlan,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.VarDeclStatement -> {
|
||||||
|
net.sergeych.lyng.VarDeclStatement(
|
||||||
|
stmt.name,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.initializer?.let { unwrapDeep(it) },
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.slotIndex,
|
||||||
|
stmt.slotDepth,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.IfStatement -> {
|
||||||
|
net.sergeych.lyng.IfStatement(
|
||||||
|
unwrapDeep(stmt.condition),
|
||||||
|
unwrapDeep(stmt.ifBody),
|
||||||
|
stmt.elseBody?.let { unwrapDeep(it) },
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
net.sergeych.lyng.ForInStatement(
|
||||||
|
stmt.loopVarName,
|
||||||
|
unwrapDeep(stmt.source),
|
||||||
|
stmt.constRange,
|
||||||
|
unwrapDeep(stmt.body),
|
||||||
|
stmt.elseStatement?.let { unwrapDeep(it) },
|
||||||
|
stmt.label,
|
||||||
|
stmt.canBreak,
|
||||||
|
stmt.loopSlotPlan,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> stmt
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,356 @@
|
|||||||
|
/*
|
||||||
|
* 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 CmdBuilder {
|
||||||
|
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,
|
||||||
|
addrCount: Int = 0,
|
||||||
|
scopeSlotDepths: IntArray = IntArray(0),
|
||||||
|
scopeSlotIndices: IntArray = IntArray(0),
|
||||||
|
scopeSlotNames: Array<String?> = emptyArray(),
|
||||||
|
localSlotNames: Array<String?> = emptyArray(),
|
||||||
|
localSlotMutables: BooleanArray = BooleanArray(0),
|
||||||
|
localSlotDepths: IntArray = IntArray(0)
|
||||||
|
): CmdFunction {
|
||||||
|
val scopeSlotCount = scopeSlotDepths.size
|
||||||
|
require(scopeSlotIndices.size == scopeSlotCount) { "scope slot mapping size mismatch" }
|
||||||
|
require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) {
|
||||||
|
"scope slot name mapping size mismatch"
|
||||||
|
}
|
||||||
|
require(localSlotNames.size == localSlotMutables.size) { "local slot metadata size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotDepths.size) { "local slot depth metadata size mismatch" }
|
||||||
|
val labelIps = mutableMapOf<Label, Int>()
|
||||||
|
for ((label, idx) in labelPositions) {
|
||||||
|
labelIps[label] = idx
|
||||||
|
}
|
||||||
|
val cmds = ArrayList<Cmd>(instructions.size)
|
||||||
|
for (ins in instructions) {
|
||||||
|
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}")
|
||||||
|
}
|
||||||
|
val operands = IntArray(kinds.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}")
|
||||||
|
}
|
||||||
|
operands[i] = v
|
||||||
|
}
|
||||||
|
cmds.add(createCmd(ins.op, operands, scopeSlotCount))
|
||||||
|
}
|
||||||
|
return CmdFunction(
|
||||||
|
name = name,
|
||||||
|
localCount = localCount,
|
||||||
|
addrCount = addrCount,
|
||||||
|
scopeSlotCount = scopeSlotCount,
|
||||||
|
scopeSlotDepths = scopeSlotDepths,
|
||||||
|
scopeSlotIndices = scopeSlotIndices,
|
||||||
|
scopeSlotNames = if (scopeSlotNames.isEmpty()) Array(scopeSlotCount) { null } else scopeSlotNames,
|
||||||
|
localSlotNames = localSlotNames,
|
||||||
|
localSlotMutables = localSlotMutables,
|
||||||
|
localSlotDepths = localSlotDepths,
|
||||||
|
constants = constPool.toList(),
|
||||||
|
fallbackStatements = fallbackStatements.toList(),
|
||||||
|
cmds = cmds.toTypedArray()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||||
|
return when (op) {
|
||||||
|
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList()
|
||||||
|
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
||||||
|
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
||||||
|
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.RESOLVE_SCOPE_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR ->
|
||||||
|
listOf(OperandKind.ADDR, OperandKind.SLOT)
|
||||||
|
Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.CONST_NULL ->
|
||||||
|
listOf(OperandKind.SLOT)
|
||||||
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
|
listOf(OperandKind.CONST)
|
||||||
|
Opcode.DECL_LOCAL ->
|
||||||
|
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.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_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_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, 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 enum class OperandKind {
|
||||||
|
SLOT,
|
||||||
|
ADDR,
|
||||||
|
CONST,
|
||||||
|
IP,
|
||||||
|
COUNT,
|
||||||
|
ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createCmd(op: Opcode, operands: IntArray, scopeSlotCount: Int): Cmd {
|
||||||
|
return when (op) {
|
||||||
|
Opcode.NOP -> CmdNop()
|
||||||
|
Opcode.MOVE_OBJ -> CmdMoveObj(operands[0], operands[1])
|
||||||
|
Opcode.MOVE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount) {
|
||||||
|
CmdMoveIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdMoveInt(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.MOVE_REAL -> CmdMoveReal(operands[0], operands[1])
|
||||||
|
Opcode.MOVE_BOOL -> CmdMoveBool(operands[0], operands[1])
|
||||||
|
Opcode.CONST_OBJ -> CmdConstObj(operands[0], operands[1])
|
||||||
|
Opcode.CONST_INT -> if (operands[1] >= scopeSlotCount) {
|
||||||
|
CmdConstIntLocal(operands[0], operands[1] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdConstInt(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
|
||||||
|
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
|
||||||
|
Opcode.CONST_NULL -> CmdConstNull(operands[0])
|
||||||
|
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
|
||||||
|
Opcode.RESOLVE_SCOPE_SLOT -> CmdResolveScopeSlot(operands[0], operands[1])
|
||||||
|
Opcode.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1])
|
||||||
|
Opcode.STORE_OBJ_ADDR -> CmdStoreObjAddr(operands[0], operands[1])
|
||||||
|
Opcode.LOAD_INT_ADDR -> CmdLoadIntAddr(operands[0], operands[1])
|
||||||
|
Opcode.STORE_INT_ADDR -> CmdStoreIntAddr(operands[0], operands[1])
|
||||||
|
Opcode.LOAD_REAL_ADDR -> CmdLoadRealAddr(operands[0], operands[1])
|
||||||
|
Opcode.STORE_REAL_ADDR -> CmdStoreRealAddr(operands[0], operands[1])
|
||||||
|
Opcode.LOAD_BOOL_ADDR -> CmdLoadBoolAddr(operands[0], operands[1])
|
||||||
|
Opcode.STORE_BOOL_ADDR -> CmdStoreBoolAddr(operands[0], operands[1])
|
||||||
|
Opcode.INT_TO_REAL -> CmdIntToReal(operands[0], operands[1])
|
||||||
|
Opcode.REAL_TO_INT -> CmdRealToInt(operands[0], operands[1])
|
||||||
|
Opcode.BOOL_TO_INT -> CmdBoolToInt(operands[0], operands[1])
|
||||||
|
Opcode.INT_TO_BOOL -> CmdIntToBool(operands[0], operands[1])
|
||||||
|
Opcode.ADD_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdAddIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdAddInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.SUB_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdSubIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdSubInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.MUL_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdMulIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdMulInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.DIV_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdDivIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdDivInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.MOD_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdModIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdModInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.NEG_INT -> CmdNegInt(operands[0], operands[1])
|
||||||
|
Opcode.INC_INT -> if (operands[0] >= scopeSlotCount) {
|
||||||
|
CmdIncIntLocal(operands[0] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdIncInt(operands[0])
|
||||||
|
}
|
||||||
|
Opcode.DEC_INT -> if (operands[0] >= scopeSlotCount) {
|
||||||
|
CmdDecIntLocal(operands[0] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdDecInt(operands[0])
|
||||||
|
}
|
||||||
|
Opcode.ADD_REAL -> CmdAddReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SUB_REAL -> CmdSubReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.MUL_REAL -> CmdMulReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.DIV_REAL -> CmdDivReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.NEG_REAL -> CmdNegReal(operands[0], operands[1])
|
||||||
|
Opcode.AND_INT -> CmdAndInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.OR_INT -> CmdOrInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.XOR_INT -> CmdXorInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SHL_INT -> CmdShlInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SHR_INT -> CmdShrInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.USHR_INT -> CmdUshrInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.INV_INT -> CmdInvInt(operands[0], operands[1])
|
||||||
|
Opcode.CMP_EQ_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdCmpEqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpEqInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_NEQ_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdCmpNeqIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpNeqInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LT_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdCmpLtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLtInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_LTE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdCmpLteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpLteInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GT_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdCmpGtIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGtInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_GTE_INT -> if (operands[0] >= scopeSlotCount && operands[1] >= scopeSlotCount && operands[2] >= scopeSlotCount) {
|
||||||
|
CmdCmpGteIntLocal(operands[0] - scopeSlotCount, operands[1] - scopeSlotCount, operands[2] - scopeSlotCount)
|
||||||
|
} else {
|
||||||
|
CmdCmpGteInt(operands[0], operands[1], operands[2])
|
||||||
|
}
|
||||||
|
Opcode.CMP_EQ_REAL -> CmdCmpEqReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_NEQ_REAL -> CmdCmpNeqReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_LT_REAL -> CmdCmpLtReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_LTE_REAL -> CmdCmpLteReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GT_REAL -> CmdCmpGtReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GTE_REAL -> CmdCmpGteReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_EQ_BOOL -> CmdCmpEqBool(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_NEQ_BOOL -> CmdCmpNeqBool(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_EQ_INT_REAL -> CmdCmpEqIntReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_EQ_REAL_INT -> CmdCmpEqRealInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_LT_INT_REAL -> CmdCmpLtIntReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_LT_REAL_INT -> CmdCmpLtRealInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_LTE_INT_REAL -> CmdCmpLteIntReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_LTE_REAL_INT -> CmdCmpLteRealInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GT_INT_REAL -> CmdCmpGtIntReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GT_REAL_INT -> CmdCmpGtRealInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GTE_INT_REAL -> CmdCmpGteIntReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GTE_REAL_INT -> CmdCmpGteRealInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_NEQ_INT_REAL -> CmdCmpNeqIntReal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_NEQ_REAL_INT -> CmdCmpNeqRealInt(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_EQ_OBJ -> CmdCmpEqObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_NEQ_OBJ -> CmdCmpNeqObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_REF_EQ_OBJ -> CmdCmpRefEqObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_REF_NEQ_OBJ -> CmdCmpRefNeqObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.NOT_BOOL -> CmdNotBool(operands[0], operands[1])
|
||||||
|
Opcode.AND_BOOL -> CmdAndBool(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.OR_BOOL -> CmdOrBool(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_LT_OBJ -> CmdCmpLtObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_LTE_OBJ -> CmdCmpLteObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GT_OBJ -> CmdCmpGtObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.CMP_GTE_OBJ -> CmdCmpGteObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.ADD_OBJ -> CmdAddObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SUB_OBJ -> CmdSubObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.MUL_OBJ -> CmdMulObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.DIV_OBJ -> CmdDivObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.MOD_OBJ -> CmdModObj(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.JMP -> CmdJmp(operands[0])
|
||||||
|
Opcode.JMP_IF_TRUE -> CmdJmpIfTrue(operands[0], operands[1])
|
||||||
|
Opcode.JMP_IF_FALSE -> CmdJmpIfFalse(operands[0], operands[1])
|
||||||
|
Opcode.RET -> CmdRet(operands[0])
|
||||||
|
Opcode.RET_VOID -> CmdRetVoid()
|
||||||
|
Opcode.PUSH_SCOPE -> CmdPushScope(operands[0])
|
||||||
|
Opcode.POP_SCOPE -> CmdPopScope()
|
||||||
|
Opcode.PUSH_SLOT_PLAN -> CmdPushSlotPlan(operands[0])
|
||||||
|
Opcode.POP_SLOT_PLAN -> CmdPopSlotPlan()
|
||||||
|
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
||||||
|
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.CALL_VIRTUAL -> CmdCallVirtual(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
|
Opcode.CALL_FALLBACK -> CmdCallFallback(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.EVAL_FALLBACK -> CmdEvalFallback(operands[0], operands[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,6 +16,6 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
internal expect object BytecodeCallSiteCache {
|
internal expect object CmdCallSiteCache {
|
||||||
fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite>
|
fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite>
|
||||||
}
|
}
|
||||||
@ -0,0 +1,251 @@
|
|||||||
|
/*
|
||||||
|
* 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 CmdDisassembler {
|
||||||
|
fun disassemble(fn: CmdFunction): String {
|
||||||
|
val out = StringBuilder()
|
||||||
|
val cmds = fn.cmds
|
||||||
|
for (i in cmds.indices) {
|
||||||
|
val (op, opValues) = opAndOperands(fn, cmds[i])
|
||||||
|
val kinds = operandKinds(op)
|
||||||
|
val operands = ArrayList<String>(kinds.size)
|
||||||
|
for (k in kinds.indices) {
|
||||||
|
val v = opValues.getOrElse(k) { 0 }
|
||||||
|
when (kinds[k]) {
|
||||||
|
OperandKind.SLOT -> {
|
||||||
|
val name = when {
|
||||||
|
v < fn.scopeSlotCount -> fn.scopeSlotNames[v]
|
||||||
|
else -> {
|
||||||
|
val localIndex = v - fn.scopeSlotCount
|
||||||
|
fn.localSlotNames.getOrNull(localIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
operands += if (name != null) "s$v($name)" else "s$v"
|
||||||
|
}
|
||||||
|
OperandKind.ADDR -> operands += "a$v"
|
||||||
|
OperandKind.CONST -> operands += "k$v"
|
||||||
|
OperandKind.IP -> operands += "ip$v"
|
||||||
|
OperandKind.COUNT -> operands += "n$v"
|
||||||
|
OperandKind.ID -> operands += "#$v"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.append(i).append(": ").append(op.name)
|
||||||
|
if (operands.isNotEmpty()) {
|
||||||
|
out.append(' ').append(operands.joinToString(", "))
|
||||||
|
}
|
||||||
|
out.append('\n')
|
||||||
|
}
|
||||||
|
return out.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun opAndOperands(fn: CmdFunction, cmd: Cmd): Pair<Opcode, IntArray> {
|
||||||
|
return when (cmd) {
|
||||||
|
is CmdNop -> Opcode.NOP to intArrayOf()
|
||||||
|
is CmdMoveObj -> Opcode.MOVE_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdMoveInt -> Opcode.MOVE_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdMoveIntLocal -> Opcode.MOVE_INT to intArrayOf(cmd.src + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdMoveReal -> Opcode.MOVE_REAL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdMoveBool -> Opcode.MOVE_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdConstObj -> Opcode.CONST_OBJ to intArrayOf(cmd.constId, cmd.dst)
|
||||||
|
is CmdConstInt -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst)
|
||||||
|
is CmdConstIntLocal -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdConstReal -> Opcode.CONST_REAL to intArrayOf(cmd.constId, cmd.dst)
|
||||||
|
is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst)
|
||||||
|
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
|
||||||
|
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdResolveScopeSlot -> Opcode.RESOLVE_SCOPE_SLOT to intArrayOf(cmd.scopeSlot, cmd.addrSlot)
|
||||||
|
is CmdLoadObjAddr -> Opcode.LOAD_OBJ_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
|
is CmdStoreObjAddr -> Opcode.STORE_OBJ_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||||
|
is CmdLoadIntAddr -> Opcode.LOAD_INT_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
|
is CmdStoreIntAddr -> Opcode.STORE_INT_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||||
|
is CmdLoadRealAddr -> Opcode.LOAD_REAL_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
|
is CmdStoreRealAddr -> Opcode.STORE_REAL_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||||
|
is CmdLoadBoolAddr -> Opcode.LOAD_BOOL_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
|
is CmdStoreBoolAddr -> Opcode.STORE_BOOL_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||||
|
is CmdIntToReal -> Opcode.INT_TO_REAL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdRealToInt -> Opcode.REAL_TO_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdBoolToInt -> Opcode.BOOL_TO_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdIntToBool -> Opcode.INT_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdAddInt -> Opcode.ADD_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdAddIntLocal -> Opcode.ADD_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdSubInt -> Opcode.SUB_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdSubIntLocal -> Opcode.SUB_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdMulInt -> Opcode.MUL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdMulIntLocal -> Opcode.MUL_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdDivInt -> Opcode.DIV_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdDivIntLocal -> Opcode.DIV_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdModInt -> Opcode.MOD_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdModIntLocal -> Opcode.MOD_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdNegInt -> Opcode.NEG_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdIncInt -> Opcode.INC_INT to intArrayOf(cmd.slot)
|
||||||
|
is CmdIncIntLocal -> Opcode.INC_INT to intArrayOf(cmd.slot + fn.scopeSlotCount)
|
||||||
|
is CmdDecInt -> Opcode.DEC_INT to intArrayOf(cmd.slot)
|
||||||
|
is CmdDecIntLocal -> Opcode.DEC_INT to intArrayOf(cmd.slot + fn.scopeSlotCount)
|
||||||
|
is CmdAddReal -> Opcode.ADD_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdSubReal -> Opcode.SUB_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdMulReal -> Opcode.MUL_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdDivReal -> Opcode.DIV_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdNegReal -> Opcode.NEG_REAL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdAndInt -> Opcode.AND_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdOrInt -> Opcode.OR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdXorInt -> Opcode.XOR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdShlInt -> Opcode.SHL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdShrInt -> Opcode.SHR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdUshrInt -> Opcode.USHR_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdInvInt -> Opcode.INV_INT to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdCmpEqInt -> Opcode.CMP_EQ_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqIntLocal -> Opcode.CMP_EQ_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpNeqInt -> Opcode.CMP_NEQ_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqIntLocal -> Opcode.CMP_NEQ_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLtInt -> Opcode.CMP_LT_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtIntLocal -> Opcode.CMP_LT_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpLteInt -> Opcode.CMP_LTE_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteIntLocal -> Opcode.CMP_LTE_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGtInt -> Opcode.CMP_GT_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtIntLocal -> Opcode.CMP_GT_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpGteInt -> Opcode.CMP_GTE_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteIntLocal -> Opcode.CMP_GTE_INT to intArrayOf(cmd.a + fn.scopeSlotCount, cmd.b + fn.scopeSlotCount, cmd.dst + fn.scopeSlotCount)
|
||||||
|
is CmdCmpEqReal -> Opcode.CMP_EQ_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqReal -> Opcode.CMP_NEQ_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtReal -> Opcode.CMP_LT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteReal -> Opcode.CMP_LTE_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtReal -> Opcode.CMP_GT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteReal -> Opcode.CMP_GTE_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqBool -> Opcode.CMP_EQ_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqBool -> Opcode.CMP_NEQ_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqIntReal -> Opcode.CMP_EQ_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqRealInt -> Opcode.CMP_EQ_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtIntReal -> Opcode.CMP_LT_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtRealInt -> Opcode.CMP_LT_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteIntReal -> Opcode.CMP_LTE_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteRealInt -> Opcode.CMP_LTE_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtIntReal -> Opcode.CMP_GT_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtRealInt -> Opcode.CMP_GT_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteIntReal -> Opcode.CMP_GTE_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteRealInt -> Opcode.CMP_GTE_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqIntReal -> Opcode.CMP_NEQ_INT_REAL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqRealInt -> Opcode.CMP_NEQ_REAL_INT to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpEqObj -> Opcode.CMP_EQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpNeqObj -> Opcode.CMP_NEQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpRefEqObj -> Opcode.CMP_REF_EQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpRefNeqObj -> Opcode.CMP_REF_NEQ_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdNotBool -> Opcode.NOT_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdAndBool -> Opcode.AND_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdOrBool -> Opcode.OR_BOOL to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLtObj -> Opcode.CMP_LT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpLteObj -> Opcode.CMP_LTE_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGtObj -> Opcode.CMP_GT_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdCmpGteObj -> Opcode.CMP_GTE_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdAddObj -> Opcode.ADD_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdSubObj -> Opcode.SUB_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdMulObj -> Opcode.MUL_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdDivObj -> Opcode.DIV_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdModObj -> Opcode.MOD_OBJ to intArrayOf(cmd.a, cmd.b, cmd.dst)
|
||||||
|
is CmdJmp -> Opcode.JMP to intArrayOf(cmd.target)
|
||||||
|
is CmdJmpIfTrue -> Opcode.JMP_IF_TRUE to intArrayOf(cmd.cond, cmd.target)
|
||||||
|
is CmdJmpIfFalse -> Opcode.JMP_IF_FALSE to intArrayOf(cmd.cond, cmd.target)
|
||||||
|
is CmdRet -> Opcode.RET to intArrayOf(cmd.slot)
|
||||||
|
is CmdRetVoid -> Opcode.RET_VOID to intArrayOf()
|
||||||
|
is CmdPushScope -> Opcode.PUSH_SCOPE to intArrayOf(cmd.planId)
|
||||||
|
is CmdPopScope -> Opcode.POP_SCOPE to intArrayOf()
|
||||||
|
is CmdPushSlotPlan -> Opcode.PUSH_SLOT_PLAN to intArrayOf(cmd.planId)
|
||||||
|
is CmdPopSlotPlan -> Opcode.POP_SLOT_PLAN to intArrayOf()
|
||||||
|
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdCallVirtual -> Opcode.CALL_VIRTUAL to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdCallFallback -> Opcode.CALL_FALLBACK to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst)
|
||||||
|
is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot)
|
||||||
|
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
|
||||||
|
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
|
||||||
|
is CmdEvalFallback -> Opcode.EVAL_FALLBACK to intArrayOf(cmd.id, cmd.dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class OperandKind {
|
||||||
|
SLOT,
|
||||||
|
ADDR,
|
||||||
|
CONST,
|
||||||
|
IP,
|
||||||
|
COUNT,
|
||||||
|
ID,
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||||
|
return when (op) {
|
||||||
|
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList()
|
||||||
|
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
||||||
|
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
||||||
|
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
|
Opcode.RESOLVE_SCOPE_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR ->
|
||||||
|
listOf(OperandKind.ADDR, OperandKind.SLOT)
|
||||||
|
Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.CONST_NULL ->
|
||||||
|
listOf(OperandKind.SLOT)
|
||||||
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||||
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
|
listOf(OperandKind.CONST)
|
||||||
|
Opcode.DECL_LOCAL ->
|
||||||
|
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.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_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_SLOT ->
|
||||||
|
listOf(OperandKind.SLOT, 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -16,26 +16,28 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
data class BytecodeFunction(
|
data class CmdFunction(
|
||||||
val name: String,
|
val name: String,
|
||||||
val localCount: Int,
|
val localCount: Int,
|
||||||
|
val addrCount: Int,
|
||||||
val scopeSlotCount: Int,
|
val scopeSlotCount: Int,
|
||||||
val scopeSlotDepths: IntArray,
|
val scopeSlotDepths: IntArray,
|
||||||
val scopeSlotIndices: IntArray,
|
val scopeSlotIndices: IntArray,
|
||||||
val scopeSlotNames: Array<String?>,
|
val scopeSlotNames: Array<String?>,
|
||||||
val slotWidth: Int,
|
val localSlotNames: Array<String?>,
|
||||||
val ipWidth: Int,
|
val localSlotMutables: BooleanArray,
|
||||||
val constIdWidth: Int,
|
val localSlotDepths: IntArray,
|
||||||
val constants: List<BytecodeConst>,
|
val constants: List<BytecodeConst>,
|
||||||
val fallbackStatements: List<net.sergeych.lyng.Statement>,
|
val fallbackStatements: List<net.sergeych.lyng.Statement>,
|
||||||
val code: ByteArray,
|
val cmds: Array<Cmd>,
|
||||||
) {
|
) {
|
||||||
init {
|
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" }
|
|
||||||
require(scopeSlotDepths.size == scopeSlotCount) { "scopeSlotDepths size mismatch" }
|
require(scopeSlotDepths.size == scopeSlotCount) { "scopeSlotDepths size mismatch" }
|
||||||
require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" }
|
require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" }
|
||||||
require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames size mismatch" }
|
require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotDepths.size) { "localSlot depth metadata size mismatch" }
|
||||||
|
require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" }
|
||||||
|
require(addrCount >= 0) { "addrCount must be non-negative" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@ -124,6 +124,15 @@ enum class Opcode(val code: Int) {
|
|||||||
SET_INDEX(0xA3),
|
SET_INDEX(0xA3),
|
||||||
|
|
||||||
EVAL_FALLBACK(0xB0),
|
EVAL_FALLBACK(0xB0),
|
||||||
|
RESOLVE_SCOPE_SLOT(0xB1),
|
||||||
|
LOAD_OBJ_ADDR(0xB2),
|
||||||
|
STORE_OBJ_ADDR(0xB3),
|
||||||
|
LOAD_INT_ADDR(0xB4),
|
||||||
|
STORE_INT_ADDR(0xB5),
|
||||||
|
LOAD_REAL_ADDR(0xB6),
|
||||||
|
STORE_REAL_ADDR(0xB7),
|
||||||
|
LOAD_BOOL_ADDR(0xB8),
|
||||||
|
STORE_BOOL_ADDR(0xB9),
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@ -1991,6 +1991,10 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
scope.assign(rec, name, newValue)
|
scope.assign(rec, name, newValue)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx)?.let { rec ->
|
||||||
|
scope.assign(rec, name, newValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
scope[name]?.let { stored ->
|
scope[name]?.let { stored ->
|
||||||
scope.assign(stored, name, newValue)
|
scope.assign(stored, name, newValue)
|
||||||
return
|
return
|
||||||
@ -2007,6 +2011,10 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
scope.chainLookupIgnoreClosure(name, followClosure = true, caller = scope.currentClassCtx)?.let { rec ->
|
||||||
|
scope.assign(rec, name, newValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
scope[name]?.let { stored ->
|
scope[name]?.let { stored ->
|
||||||
scope.assign(stored, name, newValue)
|
scope.assign(stored, name, newValue)
|
||||||
return
|
return
|
||||||
@ -2417,6 +2425,7 @@ class LocalSlotRef(
|
|||||||
val name: String,
|
val name: String,
|
||||||
internal val slot: Int,
|
internal val slot: Int,
|
||||||
internal val depth: Int,
|
internal val depth: Int,
|
||||||
|
internal val scopeDepth: Int,
|
||||||
internal val isMutable: Boolean,
|
internal val isMutable: Boolean,
|
||||||
internal val isDelegated: Boolean,
|
internal val isDelegated: Boolean,
|
||||||
private val atPos: Pos,
|
private val atPos: Pos,
|
||||||
|
|||||||
@ -19,10 +19,10 @@ import net.sergeych.lyng.IfStatement
|
|||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
import net.sergeych.lyng.bytecode.BytecodeBuilder
|
import net.sergeych.lyng.bytecode.CmdBuilder
|
||||||
import net.sergeych.lyng.bytecode.BytecodeCompiler
|
import net.sergeych.lyng.bytecode.BytecodeCompiler
|
||||||
import net.sergeych.lyng.bytecode.BytecodeConst
|
import net.sergeych.lyng.bytecode.BytecodeConst
|
||||||
import net.sergeych.lyng.bytecode.BytecodeVm
|
import net.sergeych.lyng.bytecode.CmdVm
|
||||||
import net.sergeych.lyng.bytecode.Opcode
|
import net.sergeych.lyng.bytecode.Opcode
|
||||||
import net.sergeych.lyng.obj.BinaryOpRef
|
import net.sergeych.lyng.obj.BinaryOpRef
|
||||||
import net.sergeych.lyng.obj.BinOp
|
import net.sergeych.lyng.obj.BinOp
|
||||||
@ -45,10 +45,10 @@ import net.sergeych.lyng.obj.toLong
|
|||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class BytecodeVmTest {
|
class CmdVmTest {
|
||||||
@Test
|
@Test
|
||||||
fun addsIntConstants() = kotlinx.coroutines.test.runTest {
|
fun addsIntConstants() = kotlinx.coroutines.test.runTest {
|
||||||
val builder = BytecodeBuilder()
|
val builder = CmdBuilder()
|
||||||
val k0 = builder.addConst(BytecodeConst.IntVal(2))
|
val k0 = builder.addConst(BytecodeConst.IntVal(2))
|
||||||
val k1 = builder.addConst(BytecodeConst.IntVal(3))
|
val k1 = builder.addConst(BytecodeConst.IntVal(3))
|
||||||
builder.emit(Opcode.CONST_INT, k0, 0)
|
builder.emit(Opcode.CONST_INT, k0, 0)
|
||||||
@ -56,7 +56,7 @@ class BytecodeVmTest {
|
|||||||
builder.emit(Opcode.ADD_INT, 0, 1, 2)
|
builder.emit(Opcode.ADD_INT, 0, 1, 2)
|
||||||
builder.emit(Opcode.RET, 2)
|
builder.emit(Opcode.RET, 2)
|
||||||
val fn = builder.build("addInts", localCount = 3)
|
val fn = builder.build("addInts", localCount = 3)
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(5, result.toInt())
|
assertEquals(5, result.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ class BytecodeVmTest {
|
|||||||
)
|
)
|
||||||
val ifStmt = IfStatement(cond, thenStmt, elseStmt, 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 fn = BytecodeCompiler().compileStatement("ifTest", ifStmt) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(10, result.toInt())
|
assertEquals(10, result.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ class BytecodeVmTest {
|
|||||||
error("bytecode compile failed for ifNoElse")
|
error("bytecode compile failed for ifNoElse")
|
||||||
}
|
}
|
||||||
}!!
|
}!!
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(ObjVoid, result)
|
assertEquals(ObjVoid, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +120,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val fn = BytecodeCompiler().compileExpression("andShort", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("andShort", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(false, result.toBool())
|
assertEquals(false, result.toBool())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val fn = BytecodeCompiler().compileExpression("orShort", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("orShort", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(true, result.toBool())
|
assertEquals(true, result.toBool())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,7 +151,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val fn = BytecodeCompiler().compileExpression("realPlus", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("realPlus", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(5.75, result.toDouble())
|
assertEquals(5.75, result.toDouble())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,7 +163,7 @@ class BytecodeVmTest {
|
|||||||
scope.args[0].toLong() + scope.args[1].toLong()
|
scope.args[0].toLong() + scope.args[1].toLong()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val builder = BytecodeBuilder()
|
val builder = CmdBuilder()
|
||||||
val fnId = builder.addConst(BytecodeConst.ObjRef(callable))
|
val fnId = builder.addConst(BytecodeConst.ObjRef(callable))
|
||||||
val arg0 = builder.addConst(BytecodeConst.IntVal(2L))
|
val arg0 = builder.addConst(BytecodeConst.IntVal(2L))
|
||||||
val arg1 = builder.addConst(BytecodeConst.IntVal(3L))
|
val arg1 = builder.addConst(BytecodeConst.IntVal(3L))
|
||||||
@ -173,7 +173,7 @@ class BytecodeVmTest {
|
|||||||
builder.emit(Opcode.CALL_SLOT, 0, 1, 2, 3)
|
builder.emit(Opcode.CALL_SLOT, 0, 1, 2, 3)
|
||||||
builder.emit(Opcode.RET, 3)
|
builder.emit(Opcode.RET, 3)
|
||||||
val fn = builder.build("callSlot", localCount = 4)
|
val fn = builder.build("callSlot", localCount = 4)
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(5, result.toInt())
|
assertEquals(5, result.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val ltFn = BytecodeCompiler().compileExpression("mixedLt", ltExpr) ?: error("bytecode compile failed")
|
val ltFn = BytecodeCompiler().compileExpression("mixedLt", ltExpr) ?: error("bytecode compile failed")
|
||||||
val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList())
|
val ltResult = CmdVm().execute(ltFn, Scope(), emptyList())
|
||||||
assertEquals(true, ltResult.toBool())
|
assertEquals(true, ltResult.toBool())
|
||||||
|
|
||||||
val eqExpr = ExpressionStatement(
|
val eqExpr = ExpressionStatement(
|
||||||
@ -200,7 +200,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val eqFn = BytecodeCompiler().compileExpression("mixedEq", eqExpr) ?: error("bytecode compile failed")
|
val eqFn = BytecodeCompiler().compileExpression("mixedEq", eqExpr) ?: error("bytecode compile failed")
|
||||||
val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList())
|
val eqResult = CmdVm().execute(eqFn, Scope(), emptyList())
|
||||||
assertEquals(true, eqResult.toBool())
|
assertEquals(true, eqResult.toBool())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -224,7 +224,7 @@ class BytecodeVmTest {
|
|||||||
)
|
)
|
||||||
val expr = ExpressionStatement(callRef, Pos.builtIn)
|
val expr = ExpressionStatement(callRef, Pos.builtIn)
|
||||||
val fn = BytecodeCompiler().compileExpression("tailBlockArgs", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("tailBlockArgs", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(true, result.toBool())
|
assertEquals(true, result.toBool())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +249,7 @@ class BytecodeVmTest {
|
|||||||
)
|
)
|
||||||
val expr = ExpressionStatement(callRef, Pos.builtIn)
|
val expr = ExpressionStatement(callRef, Pos.builtIn)
|
||||||
val fn = BytecodeCompiler().compileExpression("namedArgs", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("namedArgs", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(5, result.toInt())
|
assertEquals(5, result.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +275,7 @@ class BytecodeVmTest {
|
|||||||
)
|
)
|
||||||
val expr = ExpressionStatement(callRef, Pos.builtIn)
|
val expr = ExpressionStatement(callRef, Pos.builtIn)
|
||||||
val fn = BytecodeCompiler().compileExpression("splatArgs", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("splatArgs", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(3, result.toInt())
|
assertEquals(3, result.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,7 +290,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val fn = BytecodeCompiler().compileExpression("mixedPlus", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("mixedPlus", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(5.5, result.toDouble())
|
assertEquals(5.5, result.toDouble())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,13 +305,13 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val fn = BytecodeCompiler().compileExpression("mixedNeq", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("mixedNeq", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(true, result.toBool())
|
assertEquals(true, result.toBool())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun localSlotTypeTrackingEnablesArithmetic() = kotlinx.coroutines.test.runTest {
|
fun localSlotTypeTrackingEnablesArithmetic() = kotlinx.coroutines.test.runTest {
|
||||||
val slotRef = LocalSlotRef("a", 0, 0, true, false, net.sergeych.lyng.Pos.builtIn)
|
val slotRef = LocalSlotRef("a", 0, 0, 0, true, false, net.sergeych.lyng.Pos.builtIn)
|
||||||
val assign = AssignRef(
|
val assign = AssignRef(
|
||||||
slotRef,
|
slotRef,
|
||||||
ConstRef(ObjInt.of(2).asReadonly),
|
ConstRef(ObjInt.of(2).asReadonly),
|
||||||
@ -327,13 +327,13 @@ class BytecodeVmTest {
|
|||||||
)
|
)
|
||||||
val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("localSlotAdd", expr) ?: error("bytecode compile failed")
|
||||||
val scope = Scope().apply { applySlotPlan(mapOf("a" to 0)) }
|
val scope = Scope().apply { applySlotPlan(mapOf("a" to 0)) }
|
||||||
val result = BytecodeVm().execute(fn, scope, emptyList())
|
val result = CmdVm().execute(fn, scope, emptyList())
|
||||||
assertEquals(4, result.toInt())
|
assertEquals(4, result.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun parentScopeSlotAccessWorks() = kotlinx.coroutines.test.runTest {
|
fun parentScopeSlotAccessWorks() = kotlinx.coroutines.test.runTest {
|
||||||
val parentRef = LocalSlotRef("a", 0, 1, true, false, net.sergeych.lyng.Pos.builtIn)
|
val parentRef = LocalSlotRef("a", 0, 1, 0, true, false, net.sergeych.lyng.Pos.builtIn)
|
||||||
val expr = ExpressionStatement(
|
val expr = ExpressionStatement(
|
||||||
BinaryOpRef(
|
BinaryOpRef(
|
||||||
BinOp.PLUS,
|
BinOp.PLUS,
|
||||||
@ -348,7 +348,7 @@ class BytecodeVmTest {
|
|||||||
setSlotValue(0, ObjInt.of(3))
|
setSlotValue(0, ObjInt.of(3))
|
||||||
}
|
}
|
||||||
val child = Scope(parent)
|
val child = Scope(parent)
|
||||||
val result = BytecodeVm().execute(fn, child, emptyList())
|
val result = CmdVm().execute(fn, child, emptyList())
|
||||||
assertEquals(5, result.toInt())
|
assertEquals(5, result.toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,7 +363,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val fn = BytecodeCompiler().compileExpression("objEq", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("objEq", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals(true, result.toBool())
|
assertEquals(true, result.toBool())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,7 +379,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val eqFn = BytecodeCompiler().compileExpression("objRefEq", eqExpr) ?: error("bytecode compile failed")
|
val eqFn = BytecodeCompiler().compileExpression("objRefEq", eqExpr) ?: error("bytecode compile failed")
|
||||||
val eqResult = BytecodeVm().execute(eqFn, Scope(), emptyList())
|
val eqResult = CmdVm().execute(eqFn, Scope(), emptyList())
|
||||||
assertEquals(true, eqResult.toBool())
|
assertEquals(true, eqResult.toBool())
|
||||||
|
|
||||||
val neqExpr = ExpressionStatement(
|
val neqExpr = ExpressionStatement(
|
||||||
@ -391,7 +391,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val neqFn = BytecodeCompiler().compileExpression("objRefNeq", neqExpr) ?: error("bytecode compile failed")
|
val neqFn = BytecodeCompiler().compileExpression("objRefNeq", neqExpr) ?: error("bytecode compile failed")
|
||||||
val neqResult = BytecodeVm().execute(neqFn, Scope(), emptyList())
|
val neqResult = CmdVm().execute(neqFn, Scope(), emptyList())
|
||||||
assertEquals(true, neqResult.toBool())
|
assertEquals(true, neqResult.toBool())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,7 +406,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val ltFn = BytecodeCompiler().compileExpression("objLt", ltExpr) ?: error("bytecode compile failed")
|
val ltFn = BytecodeCompiler().compileExpression("objLt", ltExpr) ?: error("bytecode compile failed")
|
||||||
val ltResult = BytecodeVm().execute(ltFn, Scope(), emptyList())
|
val ltResult = CmdVm().execute(ltFn, Scope(), emptyList())
|
||||||
assertEquals(true, ltResult.toBool())
|
assertEquals(true, ltResult.toBool())
|
||||||
|
|
||||||
val gteExpr = ExpressionStatement(
|
val gteExpr = ExpressionStatement(
|
||||||
@ -418,7 +418,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val gteFn = BytecodeCompiler().compileExpression("objGte", gteExpr) ?: error("bytecode compile failed")
|
val gteFn = BytecodeCompiler().compileExpression("objGte", gteExpr) ?: error("bytecode compile failed")
|
||||||
val gteResult = BytecodeVm().execute(gteFn, Scope(), emptyList())
|
val gteResult = CmdVm().execute(gteFn, Scope(), emptyList())
|
||||||
assertEquals(true, gteResult.toBool())
|
assertEquals(true, gteResult.toBool())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -433,7 +433,7 @@ class BytecodeVmTest {
|
|||||||
net.sergeych.lyng.Pos.builtIn
|
net.sergeych.lyng.Pos.builtIn
|
||||||
)
|
)
|
||||||
val fn = BytecodeCompiler().compileExpression("objPlus", expr) ?: error("bytecode compile failed")
|
val fn = BytecodeCompiler().compileExpression("objPlus", expr) ?: error("bytecode compile failed")
|
||||||
val result = BytecodeVm().execute(fn, Scope(), emptyList())
|
val result = CmdVm().execute(fn, Scope(), emptyList())
|
||||||
assertEquals("ab", (result as ObjString).value)
|
assertEquals("ab", (result as ObjString).value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,7 +21,7 @@ import net.sergeych.lyng.Compiler
|
|||||||
import net.sergeych.lyng.ForInStatement
|
import net.sergeych.lyng.ForInStatement
|
||||||
import net.sergeych.lyng.Script
|
import net.sergeych.lyng.Script
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
import net.sergeych.lyng.bytecode.BytecodeDisassembler
|
import net.sergeych.lyng.bytecode.CmdDisassembler
|
||||||
import net.sergeych.lyng.bytecode.BytecodeStatement
|
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
import net.sergeych.lyng.obj.ObjInt
|
import net.sergeych.lyng.obj.ObjInt
|
||||||
import kotlin.time.TimeSource
|
import kotlin.time.TimeSource
|
||||||
@ -56,8 +56,11 @@ class NestedRangeBenchmarkTest {
|
|||||||
val scope = Script.newScope()
|
val scope = Script.newScope()
|
||||||
scope.eval(script)
|
scope.eval(script)
|
||||||
val fnDisasm = scope.disassembleSymbol("naiveCountHappyNumbers")
|
val fnDisasm = scope.disassembleSymbol("naiveCountHappyNumbers")
|
||||||
println("[DEBUG_LOG] [BENCH] nested-happy function naiveCountHappyNumbers bytecode:\n$fnDisasm")
|
println("[DEBUG_LOG] [BENCH] nested-happy function naiveCountHappyNumbers cmd:\n$fnDisasm")
|
||||||
|
runMode(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun runMode(scope: net.sergeych.lyng.Scope) {
|
||||||
val start = TimeSource.Monotonic.markNow()
|
val start = TimeSource.Monotonic.markNow()
|
||||||
val result = scope.eval("naiveCountHappyNumbers()") as ObjInt
|
val result = scope.eval("naiveCountHappyNumbers()") as ObjInt
|
||||||
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
||||||
@ -83,13 +86,13 @@ class NestedRangeBenchmarkTest {
|
|||||||
"$slotName@${fn.scopeSlotDepths[idx]}:${fn.scopeSlotIndices[idx]}"
|
"$slotName@${fn.scopeSlotDepths[idx]}:${fn.scopeSlotIndices[idx]}"
|
||||||
}
|
}
|
||||||
println("[DEBUG_LOG] [BENCH] nested-happy slots depth=$depth: ${slots.joinToString(", ")}")
|
println("[DEBUG_LOG] [BENCH] nested-happy slots depth=$depth: ${slots.joinToString(", ")}")
|
||||||
val disasm = BytecodeDisassembler.disassemble(fn)
|
val disasm = CmdDisassembler.disassemble(fn)
|
||||||
println("[DEBUG_LOG] [BENCH] nested-happy bytecode depth=$depth:\n$disasm")
|
println("[DEBUG_LOG] [BENCH] nested-happy cmd depth=$depth:\n$disasm")
|
||||||
current = original.body
|
current = original.body
|
||||||
depth += 1
|
depth += 1
|
||||||
}
|
}
|
||||||
if (depth == 1) {
|
if (depth == 1) {
|
||||||
println("[DEBUG_LOG] [BENCH] nested-happy bytecode: <not found>")
|
println("[DEBUG_LOG] [BENCH] nested-happy cmd: <not found>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2822,10 +2822,10 @@ class ScriptTest {
|
|||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
||||||
(1..100).map { launch { dosomething() } }.forEach {
|
(1..50).map { launch { dosomething() } }.forEach {
|
||||||
assertEquals(5050, it.await())
|
assertEquals(5050, it.await())
|
||||||
}
|
}
|
||||||
assertEquals( 100, ac.getCounter() )
|
assertEquals( 50, ac.getCounter() )
|
||||||
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
|
|||||||
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
internal actual object BytecodeCallSiteCache {
|
internal actual object CmdCallSiteCache {
|
||||||
private val cache = mutableMapOf<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
|
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
||||||
|
|
||||||
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
|
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
||||||
return cache.getOrPut(fn) { mutableMapOf() }
|
return cache.getOrPut(fn) { mutableMapOf() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -18,12 +18,12 @@ package net.sergeych.lyng.bytecode
|
|||||||
|
|
||||||
import java.util.IdentityHashMap
|
import java.util.IdentityHashMap
|
||||||
|
|
||||||
internal actual object BytecodeCallSiteCache {
|
internal actual object CmdCallSiteCache {
|
||||||
private val cache = ThreadLocal.withInitial {
|
private val cache = ThreadLocal.withInitial {
|
||||||
IdentityHashMap<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
|
IdentityHashMap<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
|
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
||||||
val map = cache.get()
|
val map = cache.get()
|
||||||
return map.getOrPut(fn) { mutableMapOf() }
|
return map.getOrPut(fn) { mutableMapOf() }
|
||||||
}
|
}
|
||||||
@ -17,10 +17,10 @@
|
|||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
@kotlin.native.concurrent.ThreadLocal
|
@kotlin.native.concurrent.ThreadLocal
|
||||||
internal actual object BytecodeCallSiteCache {
|
internal actual object CmdCallSiteCache {
|
||||||
private val cache = mutableMapOf<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
|
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
||||||
|
|
||||||
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
|
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
||||||
return cache.getOrPut(fn) { mutableMapOf() }
|
return cache.getOrPut(fn) { mutableMapOf() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
internal actual object BytecodeCallSiteCache {
|
internal actual object CmdCallSiteCache {
|
||||||
private val cache = mutableMapOf<BytecodeFunction, MutableMap<Int, MethodCallSite>>()
|
private val cache = mutableMapOf<CmdFunction, MutableMap<Int, MethodCallSite>>()
|
||||||
|
|
||||||
actual fun methodCallSites(fn: BytecodeFunction): MutableMap<Int, MethodCallSite> {
|
actual fun methodCallSites(fn: CmdFunction): MutableMap<Int, MethodCallSite> {
|
||||||
return cache.getOrPut(fn) { mutableMapOf() }
|
return cache.getOrPut(fn) { mutableMapOf() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user