Stabilize bytecode interpreter and fallbacks
This commit is contained in:
parent
2f4462858b
commit
7de856fc62
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
class BlockStatement(
|
||||||
|
val block: Script,
|
||||||
|
val slotPlan: Map<String, Int>,
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos)
|
||||||
|
if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan)
|
||||||
|
return block.execute(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun statements(): List<Statement> = block.debugStatements()
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||||
|
|
||||||
|
interface BytecodeBodyProvider {
|
||||||
|
fun bytecodeBody(): BytecodeStatement?
|
||||||
|
}
|
||||||
@ -239,7 +239,8 @@ class Compiler(
|
|||||||
val statements = mutableListOf<Statement>()
|
val statements = mutableListOf<Statement>()
|
||||||
val start = cc.currentPos()
|
val start = cc.currentPos()
|
||||||
// Track locals at script level for fast local refs
|
// Track locals at script level for fast local refs
|
||||||
return withLocalNames(emptySet()) {
|
return try {
|
||||||
|
withLocalNames(emptySet()) {
|
||||||
// package level declarations
|
// package level declarations
|
||||||
// Notify sink about script start
|
// Notify sink about script start
|
||||||
miniSink?.onScriptStart(start)
|
miniSink?.onScriptStart(start)
|
||||||
@ -346,6 +347,8 @@ class Compiler(
|
|||||||
MiniScript(MiniRange(start, cc.currentPos()))
|
MiniScript(MiniRange(start, cc.currentPos()))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadQualifiedName(): String {
|
fun loadQualifiedName(): String {
|
||||||
@ -373,6 +376,40 @@ class Compiler(
|
|||||||
return BytecodeStatement.wrap(stmt, "stmt@${stmt.pos}", allowLocalSlots = true)
|
return BytecodeStatement.wrap(stmt, "stmt@${stmt.pos}", allowLocalSlots = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun wrapFunctionBytecode(stmt: Statement, name: String): Statement {
|
||||||
|
if (!useBytecodeStatements) return stmt
|
||||||
|
return BytecodeStatement.wrap(stmt, "fn@$name", allowLocalSlots = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unwrapBytecodeDeep(stmt: Statement): Statement {
|
||||||
|
return when (stmt) {
|
||||||
|
is BytecodeStatement -> unwrapBytecodeDeep(stmt.original)
|
||||||
|
is BlockStatement -> {
|
||||||
|
val unwrapped = stmt.statements().map { unwrapBytecodeDeep(it) }
|
||||||
|
val script = Script(stmt.block.pos, unwrapped)
|
||||||
|
BlockStatement(script, stmt.slotPlan, stmt.pos)
|
||||||
|
}
|
||||||
|
is VarDeclStatement -> {
|
||||||
|
val init = stmt.initializer?.let { unwrapBytecodeDeep(it) }
|
||||||
|
VarDeclStatement(
|
||||||
|
stmt.name,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
init,
|
||||||
|
stmt.isTransient,
|
||||||
|
stmt.pos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
is IfStatement -> {
|
||||||
|
val cond = unwrapBytecodeDeep(stmt.condition)
|
||||||
|
val ifBody = unwrapBytecodeDeep(stmt.ifBody)
|
||||||
|
val elseBody = stmt.elseBody?.let { unwrapBytecodeDeep(it) }
|
||||||
|
IfStatement(cond, ifBody, elseBody, stmt.pos)
|
||||||
|
}
|
||||||
|
else -> stmt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
|
private suspend fun parseStatement(braceMeansLambda: Boolean = false): Statement? {
|
||||||
lastAnnotation = null
|
lastAnnotation = null
|
||||||
lastLabel = null
|
lastLabel = null
|
||||||
@ -2574,12 +2611,24 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun constIntRangeOrNull(ref: ObjRef): ConstIntRange? {
|
private fun constIntRangeOrNull(ref: ObjRef): ConstIntRange? {
|
||||||
if (ref !is RangeRef) return null
|
when (ref) {
|
||||||
|
is ConstRef -> {
|
||||||
|
val range = ref.constValue as? ObjRange ?: return null
|
||||||
|
if (!range.isIntRange) return null
|
||||||
|
val start = range.start?.toLong() ?: return null
|
||||||
|
val end = range.end?.toLong() ?: return null
|
||||||
|
val endExclusive = if (range.isEndInclusive) end + 1 else end
|
||||||
|
return ConstIntRange(start, endExclusive)
|
||||||
|
}
|
||||||
|
is RangeRef -> {
|
||||||
val start = constIntValueOrNull(ref.left) ?: return null
|
val start = constIntValueOrNull(ref.left) ?: return null
|
||||||
val end = constIntValueOrNull(ref.right) ?: return null
|
val end = constIntValueOrNull(ref.right) ?: return null
|
||||||
val endExclusive = if (ref.isEndInclusive) end + 1 else end
|
val endExclusive = if (ref.isEndInclusive) end + 1 else end
|
||||||
return ConstIntRange(start, endExclusive)
|
return ConstIntRange(start, endExclusive)
|
||||||
}
|
}
|
||||||
|
else -> return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun constIntValueOrNull(ref: ObjRef?): Long? {
|
private fun constIntValueOrNull(ref: ObjRef?): Long? {
|
||||||
return when (ref) {
|
return when (ref) {
|
||||||
@ -2595,10 +2644,17 @@ class Compiler(
|
|||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
private suspend fun parseDoWhileStatement(): Statement {
|
private suspend fun parseDoWhileStatement(): Statement {
|
||||||
val label = getLabel()?.also { cc.labels += it }
|
val label = getLabel()?.also { cc.labels += it }
|
||||||
val (canBreak, body) = cc.parseLoop {
|
val loopSlotPlan = SlotPlan(mutableMapOf(), 0)
|
||||||
|
slotPlanStack.add(loopSlotPlan)
|
||||||
|
val (canBreak, parsedBody) = try {
|
||||||
|
cc.parseLoop {
|
||||||
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement")
|
parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement")
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
slotPlanStack.removeLast()
|
||||||
|
}
|
||||||
label?.also { cc.labels -= it }
|
label?.also { cc.labels -= it }
|
||||||
|
val body = unwrapBytecodeDeep(parsedBody)
|
||||||
|
|
||||||
cc.skipWsTokens()
|
cc.skipWsTokens()
|
||||||
val tWhile = cc.next()
|
val tWhile = cc.next()
|
||||||
@ -2606,12 +2662,18 @@ class Compiler(
|
|||||||
throw ScriptError(tWhile.pos, "Expected 'while' after do body")
|
throw ScriptError(tWhile.pos, "Expected 'while' after do body")
|
||||||
|
|
||||||
ensureLparen()
|
ensureLparen()
|
||||||
val condition = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected condition after 'while'")
|
slotPlanStack.add(loopSlotPlan)
|
||||||
|
val condition = try {
|
||||||
|
parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected condition after 'while'")
|
||||||
|
} finally {
|
||||||
|
slotPlanStack.removeLast()
|
||||||
|
}
|
||||||
ensureRparen()
|
ensureRparen()
|
||||||
|
|
||||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||||
val elseStatement = if (cc.next().let { it.type == Token.Type.ID && it.value == "else" }) {
|
val elseStatement = if (cc.next().let { it.type == Token.Type.ID && it.value == "else" }) {
|
||||||
parseStatement()
|
val parsedElse = parseStatement()
|
||||||
|
parsedElse?.let { unwrapBytecodeDeep(it) }
|
||||||
} else {
|
} else {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
null
|
null
|
||||||
@ -2655,15 +2717,23 @@ class Compiler(
|
|||||||
parseExpression() ?: throw ScriptError(start, "Bad while statement: expected expression")
|
parseExpression() ?: throw ScriptError(start, "Bad while statement: expected expression")
|
||||||
ensureRparen()
|
ensureRparen()
|
||||||
|
|
||||||
val (canBreak, body) = cc.parseLoop {
|
val loopSlotPlan = SlotPlan(mutableMapOf(), 0)
|
||||||
|
slotPlanStack.add(loopSlotPlan)
|
||||||
|
val (canBreak, parsedBody) = try {
|
||||||
|
cc.parseLoop {
|
||||||
if (cc.current().type == Token.Type.LBRACE) parseBlock()
|
if (cc.current().type == Token.Type.LBRACE) parseBlock()
|
||||||
else parseStatement() ?: throw ScriptError(start, "Bad while statement: expected statement")
|
else parseStatement() ?: throw ScriptError(start, "Bad while statement: expected statement")
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
slotPlanStack.removeLast()
|
||||||
|
}
|
||||||
label?.also { cc.labels -= it }
|
label?.also { cc.labels -= it }
|
||||||
|
val body = unwrapBytecodeDeep(parsedBody)
|
||||||
|
|
||||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||||
val elseStatement = if (cc.next().let { it.type == Token.Type.ID && it.value == "else" }) {
|
val elseStatement = if (cc.next().let { it.type == Token.Type.ID && it.value == "else" }) {
|
||||||
parseStatement()
|
val parsedElse = parseStatement()
|
||||||
|
parsedElse?.let { unwrapBytecodeDeep(it) }
|
||||||
} else {
|
} else {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
null
|
null
|
||||||
@ -2986,8 +3056,9 @@ class Compiler(
|
|||||||
var closure: Scope? = null
|
var closure: Scope? = null
|
||||||
|
|
||||||
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
|
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
|
||||||
val fnBody = object : Statement() {
|
val fnBody = object : Statement(), BytecodeBodyProvider {
|
||||||
override val pos: Pos = t.pos
|
override val pos: Pos = t.pos
|
||||||
|
override fun bytecodeBody(): BytecodeStatement? = fnStatements as? BytecodeStatement
|
||||||
override suspend fun execute(callerContext: Scope): Obj {
|
override suspend fun execute(callerContext: Scope): Obj {
|
||||||
callerContext.pos = start
|
callerContext.pos = start
|
||||||
|
|
||||||
@ -3082,6 +3153,7 @@ class Compiler(
|
|||||||
|
|
||||||
val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody)
|
val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody)
|
||||||
?: fnBody
|
?: fnBody
|
||||||
|
val compiledFnBody = annotatedFnBody
|
||||||
|
|
||||||
extTypeName?.let { typeName ->
|
extTypeName?.let { typeName ->
|
||||||
// class extension method
|
// class extension method
|
||||||
@ -3092,10 +3164,10 @@ class Compiler(
|
|||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
// ObjInstance has a fixed instance scope, so we need to build a closure
|
// ObjInstance has a fixed instance scope, so we need to build a closure
|
||||||
val result = (scope.thisObj as? ObjInstance)?.let { i ->
|
val result = (scope.thisObj as? ObjInstance)?.let { i ->
|
||||||
annotatedFnBody.execute(ClosureScope(scope, i.instanceScope))
|
compiledFnBody.execute(ClosureScope(scope, i.instanceScope))
|
||||||
}
|
}
|
||||||
// other classes can create one-time scope for this rare case:
|
// other classes can create one-time scope for this rare case:
|
||||||
?: annotatedFnBody.execute(scope.thisObj.autoInstanceScope(scope))
|
?: compiledFnBody.execute(scope.thisObj.autoInstanceScope(scope))
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3127,18 +3199,18 @@ class Compiler(
|
|||||||
newThisObj = i
|
newThisObj = i
|
||||||
)
|
)
|
||||||
execScope.currentClassCtx = cls
|
execScope.currentClassCtx = cls
|
||||||
annotatedFnBody.execute(execScope)
|
compiledFnBody.execute(execScope)
|
||||||
} ?: annotatedFnBody.execute(thisObj.autoInstanceScope(this))
|
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this))
|
||||||
} finally {
|
} finally {
|
||||||
this.currentClassCtx = savedCtx
|
this.currentClassCtx = savedCtx
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// also expose the symbol in the class scope for possible references
|
// also expose the symbol in the class scope for possible references
|
||||||
context.addItem(name, false, annotatedFnBody, visibility)
|
context.addItem(name, false, compiledFnBody, visibility)
|
||||||
annotatedFnBody
|
compiledFnBody
|
||||||
} else {
|
} else {
|
||||||
// top-level or nested function
|
// top-level or nested function
|
||||||
context.addItem(name, false, annotatedFnBody, visibility)
|
context.addItem(name, false, compiledFnBody, visibility)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// as the function can be called from anywhere, we have
|
// as the function can be called from anywhere, we have
|
||||||
@ -3194,15 +3266,7 @@ class Compiler(
|
|||||||
slotPlanStack.removeLast()
|
slotPlanStack.removeLast()
|
||||||
}
|
}
|
||||||
val planSnapshot = slotPlanIndices(blockSlotPlan)
|
val planSnapshot = slotPlanIndices(blockSlotPlan)
|
||||||
val stmt = object : Statement() {
|
val stmt = BlockStatement(block, planSnapshot, startPos)
|
||||||
override val pos: Pos = startPos
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
|
||||||
// block run on inner context:
|
|
||||||
val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos)
|
|
||||||
if (planSnapshot.isNotEmpty()) target.applySlotPlan(planSnapshot)
|
|
||||||
return block.execute(target)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val wrapped = wrapBytecode(stmt)
|
val wrapped = wrapBytecode(stmt)
|
||||||
return wrapped.also {
|
return wrapped.also {
|
||||||
val t1 = cc.next()
|
val t1 = cc.next()
|
||||||
@ -3456,6 +3520,17 @@ class Compiler(
|
|||||||
pendingDeclDoc = null
|
pendingDeclDoc = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (declaringClassNameCaptured == null &&
|
||||||
|
extTypeName == null &&
|
||||||
|
!isStatic &&
|
||||||
|
!isProperty &&
|
||||||
|
!isDelegate &&
|
||||||
|
!actualExtern &&
|
||||||
|
!isAbstract
|
||||||
|
) {
|
||||||
|
return VarDeclStatement(name, isMutable, visibility, initialExpression, isTransient, start)
|
||||||
|
}
|
||||||
|
|
||||||
if (isStatic) {
|
if (isStatic) {
|
||||||
// find objclass instance: this is tricky: this code executes in object initializer,
|
// find objclass instance: this is tricky: this code executes in object initializer,
|
||||||
// when creating instance, but we need to execute it in the class initializer which
|
// when creating instance, but we need to execute it in the class initializer which
|
||||||
|
|||||||
@ -18,6 +18,8 @@
|
|||||||
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.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
|
||||||
|
|
||||||
@ -410,6 +412,34 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun applySlotPlanWithSnapshot(plan: Map<String, Int>): Map<String, Int?> {
|
||||||
|
if (plan.isEmpty()) return emptyMap()
|
||||||
|
val maxIndex = plan.values.maxOrNull() ?: return emptyMap()
|
||||||
|
if (slots.size <= maxIndex) {
|
||||||
|
val targetSize = maxIndex + 1
|
||||||
|
while (slots.size < targetSize) {
|
||||||
|
slots.add(ObjRecord(ObjUnset, isMutable = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val snapshot = LinkedHashMap<String, Int?>(plan.size)
|
||||||
|
for ((name, idx) in plan) {
|
||||||
|
snapshot[name] = nameToSlot[name]
|
||||||
|
nameToSlot[name] = idx
|
||||||
|
}
|
||||||
|
return snapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restoreSlotPlan(snapshot: Map<String, Int?>) {
|
||||||
|
if (snapshot.isEmpty()) return
|
||||||
|
for ((name, idx) in snapshot) {
|
||||||
|
if (idx == null) {
|
||||||
|
nameToSlot.remove(name)
|
||||||
|
} else {
|
||||||
|
nameToSlot[name] = idx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear all references and maps to prevent memory leaks when pooled.
|
* Clear all references and maps to prevent memory leaks when pooled.
|
||||||
*/
|
*/
|
||||||
@ -615,6 +645,15 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun disassembleSymbol(name: String): String {
|
||||||
|
val record = get(name) ?: return "$name is not found"
|
||||||
|
val stmt = record.value as? Statement ?: return "$name is not a compiled body"
|
||||||
|
val bytecode = (stmt as? BytecodeStatement)?.bytecodeFunction()
|
||||||
|
?: (stmt as? BytecodeBodyProvider)?.bytecodeBody()?.bytecodeFunction()
|
||||||
|
?: return "$name is not a compiled body"
|
||||||
|
return BytecodeDisassembler.disassemble(bytecode)
|
||||||
|
}
|
||||||
|
|
||||||
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
|
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
|
||||||
val newFn = object : Statement() {
|
val newFn = object : Statement() {
|
||||||
override val pos: Pos = Pos.builtIn
|
override val pos: Pos = Pos.builtIn
|
||||||
|
|||||||
@ -43,6 +43,8 @@ class Script(
|
|||||||
return lastResult
|
return lastResult
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun debugStatements(): List<Statement> = statements
|
||||||
|
|
||||||
suspend fun execute() = execute(
|
suspend fun execute() = execute(
|
||||||
defaultImportManager.newStdScope()
|
defaultImportManager.newStdScope()
|
||||||
)
|
)
|
||||||
|
|||||||
@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
|
*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
|
class VarDeclStatement(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val initializer: Statement?,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
private val startPos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
|
override suspend fun execute(context: Scope): Obj {
|
||||||
|
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjNull
|
||||||
|
context.addItem(
|
||||||
|
name,
|
||||||
|
isMutable,
|
||||||
|
initValue,
|
||||||
|
visibility,
|
||||||
|
recordType = ObjRecord.Type.Other,
|
||||||
|
isTransient = isTransient
|
||||||
|
)
|
||||||
|
return initValue
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -129,7 +129,7 @@ class BytecodeBuilder {
|
|||||||
|
|
||||||
private fun operandKinds(op: Opcode): List<OperandKind> {
|
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||||
return when (op) {
|
return when (op) {
|
||||||
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE -> emptyList()
|
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.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.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 ->
|
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
||||||
@ -138,8 +138,10 @@ class BytecodeBuilder {
|
|||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.PUSH_SCOPE ->
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
listOf(OperandKind.CONST)
|
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_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.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.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
||||||
|
|||||||
@ -16,12 +16,14 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
|
import net.sergeych.lyng.BlockStatement
|
||||||
import net.sergeych.lyng.ExpressionStatement
|
import net.sergeych.lyng.ExpressionStatement
|
||||||
import net.sergeych.lyng.IfStatement
|
import net.sergeych.lyng.IfStatement
|
||||||
import net.sergeych.lyng.ParsedArgument
|
import net.sergeych.lyng.ParsedArgument
|
||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
import net.sergeych.lyng.ToBoolStatement
|
import net.sergeych.lyng.ToBoolStatement
|
||||||
|
import net.sergeych.lyng.VarDeclStatement
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
|
|
||||||
class BytecodeCompiler(
|
class BytecodeCompiler(
|
||||||
@ -36,6 +38,10 @@ class BytecodeCompiler(
|
|||||||
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 slotTypes = mutableMapOf<Int, SlotType>()
|
private val slotTypes = mutableMapOf<Int, SlotType>()
|
||||||
|
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): BytecodeFunction? {
|
||||||
prepareCompilation(stmt)
|
prepareCompilation(stmt)
|
||||||
@ -43,6 +49,8 @@ class BytecodeCompiler(
|
|||||||
is ExpressionStatement -> compileExpression(name, stmt)
|
is ExpressionStatement -> compileExpression(name, stmt)
|
||||||
is net.sergeych.lyng.IfStatement -> compileIf(name, stmt)
|
is net.sergeych.lyng.IfStatement -> compileIf(name, stmt)
|
||||||
is net.sergeych.lyng.ForInStatement -> compileForIn(name, stmt)
|
is net.sergeych.lyng.ForInStatement -> compileForIn(name, stmt)
|
||||||
|
is BlockStatement -> compileBlock(name, stmt)
|
||||||
|
is VarDeclStatement -> compileVarDecl(name, stmt)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -66,8 +74,14 @@ 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 mapped = scopeSlotMap[ScopeSlotKey(refDepth(ref), refSlot(ref))] ?: return null
|
val loopSlotId = loopVarSlotIdByName[ref.name]
|
||||||
CompiledValue(mapped, slotTypes[mapped] ?: SlotType.UNKNOWN)
|
val mapped = loopSlotId ?: scopeSlotMap[ScopeSlotKey(refDepth(ref), refSlot(ref))] ?: return null
|
||||||
|
val resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
|
||||||
|
if (resolved == SlotType.UNKNOWN && intLoopVarNames.contains(ref.name)) {
|
||||||
|
updateSlotType(mapped, SlotType.INT)
|
||||||
|
return CompiledValue(mapped, SlotType.INT)
|
||||||
|
}
|
||||||
|
CompiledValue(mapped, resolved)
|
||||||
}
|
}
|
||||||
is BinaryOpRef -> compileBinary(ref)
|
is BinaryOpRef -> compileBinary(ref)
|
||||||
is UnaryOpRef -> compileUnary(ref)
|
is UnaryOpRef -> compileUnary(ref)
|
||||||
@ -150,8 +164,30 @@ class BytecodeCompiler(
|
|||||||
if (op == BinOp.AND || op == BinOp.OR) {
|
if (op == BinOp.AND || op == BinOp.OR) {
|
||||||
return compileLogical(op, binaryLeft(ref), binaryRight(ref), refPos(ref))
|
return compileLogical(op, binaryLeft(ref), binaryRight(ref), refPos(ref))
|
||||||
}
|
}
|
||||||
val a = compileRef(binaryLeft(ref)) ?: return null
|
val leftRef = binaryLeft(ref)
|
||||||
val b = compileRef(binaryRight(ref)) ?: return null
|
val rightRef = binaryRight(ref)
|
||||||
|
var a = compileRef(leftRef) ?: return null
|
||||||
|
var b = compileRef(rightRef) ?: return null
|
||||||
|
val intOps = setOf(
|
||||||
|
BinOp.PLUS, BinOp.MINUS, BinOp.STAR, BinOp.SLASH, BinOp.PERCENT,
|
||||||
|
BinOp.BAND, BinOp.BOR, BinOp.BXOR, BinOp.SHL, BinOp.SHR
|
||||||
|
)
|
||||||
|
val leftIsLoopVar = (leftRef as? LocalSlotRef)?.name?.let { intLoopVarNames.contains(it) } == true
|
||||||
|
val rightIsLoopVar = (rightRef as? LocalSlotRef)?.name?.let { intLoopVarNames.contains(it) } == true
|
||||||
|
if (a.type == SlotType.UNKNOWN && b.type == SlotType.INT && op in intOps && leftIsLoopVar) {
|
||||||
|
updateSlotType(a.slot, SlotType.INT)
|
||||||
|
a = CompiledValue(a.slot, SlotType.INT)
|
||||||
|
}
|
||||||
|
if (b.type == SlotType.UNKNOWN && a.type == SlotType.INT && op in intOps && rightIsLoopVar) {
|
||||||
|
updateSlotType(b.slot, SlotType.INT)
|
||||||
|
b = CompiledValue(b.slot, SlotType.INT)
|
||||||
|
}
|
||||||
|
if (a.type == SlotType.UNKNOWN && b.type == SlotType.UNKNOWN && op in intOps && leftIsLoopVar && rightIsLoopVar) {
|
||||||
|
updateSlotType(a.slot, SlotType.INT)
|
||||||
|
updateSlotType(b.slot, SlotType.INT)
|
||||||
|
a = CompiledValue(a.slot, SlotType.INT)
|
||||||
|
b = CompiledValue(b.slot, SlotType.INT)
|
||||||
|
}
|
||||||
val typesMismatch = a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN
|
val typesMismatch = a.type != b.type && a.type != SlotType.UNKNOWN && b.type != SlotType.UNKNOWN
|
||||||
if (typesMismatch && op !in setOf(BinOp.EQ, BinOp.NEQ, BinOp.LT, BinOp.LTE, BinOp.GT, BinOp.GTE)) {
|
if (typesMismatch && op !in setOf(BinOp.EQ, BinOp.NEQ, BinOp.LT, BinOp.LTE, BinOp.GT, BinOp.GTE)) {
|
||||||
return null
|
return null
|
||||||
@ -701,9 +737,8 @@ class BytecodeCompiler(
|
|||||||
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 = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null
|
val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null
|
||||||
val slotType = slotTypes[slot] ?: return null
|
val slotType = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||||
return when (slotType) {
|
return when (slotType) {
|
||||||
SlotType.INT -> {
|
SlotType.INT -> {
|
||||||
if (ref.isPost) {
|
if (ref.isPost) {
|
||||||
@ -732,6 +767,41 @@ class BytecodeCompiler(
|
|||||||
CompiledValue(slot, SlotType.REAL)
|
CompiledValue(slot, SlotType.REAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SlotType.OBJ -> {
|
||||||
|
val oneSlot = allocSlot()
|
||||||
|
val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One))
|
||||||
|
builder.emit(Opcode.CONST_OBJ, oneId, oneSlot)
|
||||||
|
val current = allocSlot()
|
||||||
|
builder.emit(Opcode.BOX_OBJ, slot, current)
|
||||||
|
if (ref.isPost) {
|
||||||
|
val result = allocSlot()
|
||||||
|
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
|
||||||
|
builder.emit(op, current, oneSlot, result)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, result, slot)
|
||||||
|
updateSlotType(slot, SlotType.OBJ)
|
||||||
|
CompiledValue(current, SlotType.OBJ)
|
||||||
|
} else {
|
||||||
|
val result = allocSlot()
|
||||||
|
val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ
|
||||||
|
builder.emit(op, current, oneSlot, result)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, result, slot)
|
||||||
|
updateSlotType(slot, SlotType.OBJ)
|
||||||
|
CompiledValue(result, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SlotType.UNKNOWN -> {
|
||||||
|
if (ref.isPost) {
|
||||||
|
val old = allocSlot()
|
||||||
|
builder.emit(Opcode.MOVE_INT, slot, old)
|
||||||
|
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
|
||||||
|
updateSlotType(slot, SlotType.INT)
|
||||||
|
CompiledValue(old, SlotType.INT)
|
||||||
|
} else {
|
||||||
|
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
|
||||||
|
updateSlotType(slot, SlotType.INT)
|
||||||
|
CompiledValue(slot, SlotType.INT)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -794,6 +864,20 @@ class BytecodeCompiler(
|
|||||||
|
|
||||||
private fun compileCall(ref: CallRef): CompiledValue? {
|
private fun compileCall(ref: CallRef): CompiledValue? {
|
||||||
if (ref.isOptionalInvoke) return null
|
if (ref.isOptionalInvoke) return null
|
||||||
|
if (ref.target is LocalVarRef || ref.target is FastLocalVarRef || ref.target is BoundLocalVarRef) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
val fieldTarget = ref.target as? FieldRef
|
||||||
|
if (fieldTarget != null && !fieldTarget.isOptional) {
|
||||||
|
val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null
|
||||||
|
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||||
|
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||||
|
val methodId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name))
|
||||||
|
if (methodId > 0xFFFF) return null
|
||||||
|
val dst = allocSlot()
|
||||||
|
builder.emit(Opcode.CALL_VIRTUAL, receiver.slot, methodId, args.base, encodedCount, dst)
|
||||||
|
return CompiledValue(dst, SlotType.UNKNOWN)
|
||||||
|
}
|
||||||
val callee = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
|
val callee = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null
|
||||||
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null
|
||||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||||
@ -890,12 +974,112 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): BytecodeFunction? {
|
private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): BytecodeFunction? {
|
||||||
|
val resultSlot = emitForIn(stmt) ?: return null
|
||||||
|
builder.emit(Opcode.RET, resultSlot)
|
||||||
|
val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
|
||||||
|
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileBlock(name: String, stmt: BlockStatement): BytecodeFunction? {
|
||||||
|
val result = emitBlock(stmt) ?: return null
|
||||||
|
builder.emit(Opcode.RET, result.slot)
|
||||||
|
val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
|
||||||
|
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileVarDecl(name: String, stmt: VarDeclStatement): BytecodeFunction? {
|
||||||
|
val result = emitVarDecl(stmt) ?: return null
|
||||||
|
builder.emit(Opcode.RET, result.slot)
|
||||||
|
val localCount = maxOf(nextSlot, result.slot + 1) - scopeSlotCount
|
||||||
|
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileStatementValue(stmt: Statement): CompiledValue? {
|
||||||
|
return when (stmt) {
|
||||||
|
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileStatementValueOrFallback(stmt: Statement): CompiledValue? {
|
||||||
|
val target = if (stmt is BytecodeStatement) stmt.original else stmt
|
||||||
|
return when (target) {
|
||||||
|
is ExpressionStatement -> compileRefWithFallback(target.ref, null, target.pos)
|
||||||
|
is IfStatement -> compileIfExpression(target)
|
||||||
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
val resultSlot = emitForIn(target) ?: return null
|
||||||
|
updateSlotType(resultSlot, SlotType.OBJ)
|
||||||
|
CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
is BlockStatement -> emitBlock(target)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emitBlock(stmt: BlockStatement): CompiledValue? {
|
||||||
|
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.slotPlan))
|
||||||
|
builder.emit(Opcode.PUSH_SCOPE, planId)
|
||||||
|
val statements = stmt.statements()
|
||||||
|
var lastValue: CompiledValue? = null
|
||||||
|
for (statement in statements) {
|
||||||
|
lastValue = compileStatementValueOrFallback(statement) ?: return null
|
||||||
|
}
|
||||||
|
var result = lastValue ?: run {
|
||||||
|
val slot = allocSlot()
|
||||||
|
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||||
|
builder.emit(Opcode.CONST_OBJ, voidId, slot)
|
||||||
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
if (result.slot < scopeSlotCount) {
|
||||||
|
val captured = allocSlot()
|
||||||
|
emitMove(result, captured)
|
||||||
|
result = CompiledValue(captured, result.type)
|
||||||
|
}
|
||||||
|
builder.emit(Opcode.POP_SCOPE)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? {
|
||||||
|
val value = stmt.initializer?.let { compileStatementValueOrFallback(it) } ?: run {
|
||||||
|
val slot = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_NULL, slot)
|
||||||
|
updateSlotType(slot, SlotType.OBJ)
|
||||||
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
val declId = builder.addConst(
|
||||||
|
BytecodeConst.LocalDecl(
|
||||||
|
stmt.name,
|
||||||
|
stmt.isMutable,
|
||||||
|
stmt.visibility,
|
||||||
|
stmt.isTransient
|
||||||
|
)
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.DECL_LOCAL, declId, value.slot)
|
||||||
|
if (value.type != SlotType.UNKNOWN) {
|
||||||
|
updateSlotTypeByName(stmt.name, value.type)
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
private fun emitForIn(stmt: net.sergeych.lyng.ForInStatement): Int? {
|
||||||
if (stmt.canBreak) return null
|
if (stmt.canBreak) return null
|
||||||
val range = stmt.constRange ?: return null
|
val range = stmt.constRange ?: return null
|
||||||
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName] ?: return null
|
val loopSlotId = loopVarSlotIdByName[stmt.loopVarName] ?: return null
|
||||||
val loopSlot = scopeSlotMap[ScopeSlotKey(0, loopSlotIndex)] ?: return null
|
val slotIndex = loopVarSlotIndexByName[stmt.loopVarName] ?: return null
|
||||||
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.loopSlotPlan))
|
val planId = builder.addConst(BytecodeConst.SlotPlan(mapOf(stmt.loopVarName to slotIndex)))
|
||||||
|
val useLoopScope = scopeSlotMap.isEmpty()
|
||||||
|
if (useLoopScope) {
|
||||||
builder.emit(Opcode.PUSH_SCOPE, planId)
|
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()
|
||||||
@ -917,7 +1101,9 @@ class BytecodeCompiler(
|
|||||||
Opcode.JMP_IF_TRUE,
|
Opcode.JMP_IF_TRUE,
|
||||||
listOf(BytecodeBuilder.Operand.IntVal(cmpSlot), BytecodeBuilder.Operand.LabelRef(endLabel))
|
listOf(BytecodeBuilder.Operand.IntVal(cmpSlot), BytecodeBuilder.Operand.LabelRef(endLabel))
|
||||||
)
|
)
|
||||||
builder.emit(Opcode.MOVE_INT, iSlot, loopSlot)
|
builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId)
|
||||||
|
updateSlotType(loopSlotId, SlotType.INT)
|
||||||
|
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
|
||||||
val bodyValue = compileStatementValueOrFallback(stmt.body) ?: return null
|
val bodyValue = compileStatementValueOrFallback(stmt.body) ?: return null
|
||||||
val bodyObj = ensureObjSlot(bodyValue)
|
val bodyObj = ensureObjSlot(bodyValue)
|
||||||
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||||
@ -930,30 +1116,23 @@ class BytecodeCompiler(
|
|||||||
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)
|
builder.emit(Opcode.POP_SCOPE)
|
||||||
builder.emit(Opcode.RET, resultSlot)
|
} else {
|
||||||
|
builder.emit(Opcode.POP_SLOT_PLAN)
|
||||||
val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
|
}
|
||||||
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
return resultSlot
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun compileStatementValue(stmt: Statement): CompiledValue? {
|
private fun updateSlotTypeByName(name: String, type: SlotType) {
|
||||||
return when (stmt) {
|
val loopSlotId = loopVarSlotIdByName[name]
|
||||||
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
if (loopSlotId != null) {
|
||||||
else -> null
|
updateSlotType(loopSlotId, type)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
for ((key, index) in scopeSlotMap) {
|
||||||
|
if (scopeSlotNameMap[key] == name) {
|
||||||
private fun compileStatementValueOrFallback(stmt: Statement): CompiledValue? {
|
updateSlotType(index, type)
|
||||||
return when (stmt) {
|
|
||||||
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
|
||||||
is IfStatement -> compileIfExpression(stmt)
|
|
||||||
else -> {
|
|
||||||
val slot = allocSlot()
|
|
||||||
val id = builder.addFallback(stmt)
|
|
||||||
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
|
|
||||||
updateSlotType(slot, SlotType.OBJ)
|
|
||||||
CompiledValue(slot, SlotType.OBJ)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1025,8 +1204,13 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
val id = builder.addFallback(stmt)
|
val id = builder.addFallback(stmt)
|
||||||
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
|
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
|
||||||
updateSlotType(slot, forceType ?: SlotType.OBJ)
|
if (forceType == null) {
|
||||||
return CompiledValue(slot, forceType ?: SlotType.OBJ)
|
builder.emit(Opcode.BOX_OBJ, slot, slot)
|
||||||
|
updateSlotType(slot, SlotType.OBJ)
|
||||||
|
return CompiledValue(slot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
updateSlotType(slot, forceType)
|
||||||
|
return CompiledValue(slot, forceType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
private fun refSlot(ref: LocalSlotRef): Int = ref.slot
|
||||||
@ -1053,7 +1237,12 @@ class BytecodeCompiler(
|
|||||||
nextSlot = 0
|
nextSlot = 0
|
||||||
slotTypes.clear()
|
slotTypes.clear()
|
||||||
scopeSlotMap.clear()
|
scopeSlotMap.clear()
|
||||||
|
intLoopVarNames.clear()
|
||||||
|
loopVarNames.clear()
|
||||||
|
loopVarSlotIndexByName.clear()
|
||||||
|
loopVarSlotIdByName.clear()
|
||||||
if (allowLocalSlots) {
|
if (allowLocalSlots) {
|
||||||
|
collectLoopVarNames(stmt)
|
||||||
collectScopeSlots(stmt)
|
collectScopeSlots(stmt)
|
||||||
}
|
}
|
||||||
scopeSlotCount = scopeSlotMap.size
|
scopeSlotCount = scopeSlotMap.size
|
||||||
@ -1062,31 +1251,55 @@ class BytecodeCompiler(
|
|||||||
scopeSlotNames = arrayOfNulls(scopeSlotCount)
|
scopeSlotNames = arrayOfNulls(scopeSlotCount)
|
||||||
for ((key, index) in scopeSlotMap) {
|
for ((key, index) in scopeSlotMap) {
|
||||||
scopeSlotDepths[index] = key.depth
|
scopeSlotDepths[index] = key.depth
|
||||||
|
val name = scopeSlotNameMap[key]
|
||||||
scopeSlotIndices[index] = key.slot
|
scopeSlotIndices[index] = key.slot
|
||||||
scopeSlotNames[index] = scopeSlotNameMap[key]
|
scopeSlotNames[index] = name
|
||||||
|
}
|
||||||
|
if (loopVarNames.isNotEmpty()) {
|
||||||
|
var maxSlotIndex = scopeSlotMap.keys.maxOfOrNull { it.slot } ?: -1
|
||||||
|
for (name in loopVarNames) {
|
||||||
|
maxSlotIndex += 1
|
||||||
|
loopVarSlotIndexByName[name] = maxSlotIndex
|
||||||
|
}
|
||||||
|
val start = scopeSlotCount
|
||||||
|
val total = scopeSlotCount + loopVarSlotIndexByName.size
|
||||||
|
scopeSlotDepths = scopeSlotDepths.copyOf(total)
|
||||||
|
scopeSlotIndices = scopeSlotIndices.copyOf(total)
|
||||||
|
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
|
||||||
}
|
}
|
||||||
nextSlot = scopeSlotCount
|
nextSlot = scopeSlotCount
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun collectScopeSlots(stmt: Statement) {
|
private fun collectScopeSlots(stmt: Statement) {
|
||||||
|
if (stmt is BytecodeStatement) {
|
||||||
|
collectScopeSlots(stmt.original)
|
||||||
|
return
|
||||||
|
}
|
||||||
when (stmt) {
|
when (stmt) {
|
||||||
is ExpressionStatement -> collectScopeSlotsRef(stmt.ref)
|
is ExpressionStatement -> collectScopeSlotsRef(stmt.ref)
|
||||||
|
is BlockStatement -> {
|
||||||
|
for (child in stmt.statements()) {
|
||||||
|
collectScopeSlots(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is VarDeclStatement -> {
|
||||||
|
stmt.initializer?.let { collectScopeSlots(it) }
|
||||||
|
}
|
||||||
is IfStatement -> {
|
is IfStatement -> {
|
||||||
collectScopeSlots(stmt.condition)
|
collectScopeSlots(stmt.condition)
|
||||||
collectScopeSlots(stmt.ifBody)
|
collectScopeSlots(stmt.ifBody)
|
||||||
stmt.elseBody?.let { collectScopeSlots(it) }
|
stmt.elseBody?.let { collectScopeSlots(it) }
|
||||||
}
|
}
|
||||||
is net.sergeych.lyng.ForInStatement -> {
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
|
|
||||||
if (loopSlotIndex != null) {
|
|
||||||
val key = ScopeSlotKey(0, loopSlotIndex)
|
|
||||||
if (!scopeSlotMap.containsKey(key)) {
|
|
||||||
scopeSlotMap[key] = scopeSlotMap.size
|
|
||||||
}
|
|
||||||
if (!scopeSlotNameMap.containsKey(key)) {
|
|
||||||
scopeSlotNameMap[key] = stmt.loopVarName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
collectScopeSlots(stmt.source)
|
collectScopeSlots(stmt.source)
|
||||||
collectScopeSlots(stmt.body)
|
collectScopeSlots(stmt.body)
|
||||||
stmt.elseStatement?.let { collectScopeSlots(it) }
|
stmt.elseStatement?.let { collectScopeSlots(it) }
|
||||||
@ -1095,9 +1308,69 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun collectLoopVarNames(stmt: Statement) {
|
||||||
|
if (stmt is BytecodeStatement) {
|
||||||
|
collectLoopVarNames(stmt.original)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
when (stmt) {
|
||||||
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
if (stmt.constRange != null) {
|
||||||
|
intLoopVarNames.add(stmt.loopVarName)
|
||||||
|
loopVarNames.add(stmt.loopVarName)
|
||||||
|
}
|
||||||
|
collectLoopVarNames(stmt.source)
|
||||||
|
collectLoopVarNames(stmt.body)
|
||||||
|
stmt.elseStatement?.let { collectLoopVarNames(it) }
|
||||||
|
}
|
||||||
|
is BlockStatement -> {
|
||||||
|
for (child in stmt.statements()) {
|
||||||
|
collectLoopVarNames(child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is VarDeclStatement -> {
|
||||||
|
stmt.initializer?.let { collectLoopVarNames(it) }
|
||||||
|
}
|
||||||
|
is IfStatement -> {
|
||||||
|
collectLoopVarNames(stmt.condition)
|
||||||
|
collectLoopVarNames(stmt.ifBody)
|
||||||
|
stmt.elseBody?.let { collectLoopVarNames(it) }
|
||||||
|
}
|
||||||
|
is ExpressionStatement -> collectLoopVarNamesRef(stmt.ref)
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectLoopVarNamesRef(ref: ObjRef) {
|
||||||
|
when (ref) {
|
||||||
|
is BinaryOpRef -> {
|
||||||
|
collectLoopVarNamesRef(binaryLeft(ref))
|
||||||
|
collectLoopVarNamesRef(binaryRight(ref))
|
||||||
|
}
|
||||||
|
is UnaryOpRef -> collectLoopVarNamesRef(unaryOperand(ref))
|
||||||
|
is AssignRef -> collectLoopVarNamesRef(assignValue(ref))
|
||||||
|
is AssignOpRef -> {
|
||||||
|
collectLoopVarNamesRef(ref.target)
|
||||||
|
collectLoopVarNamesRef(ref.value)
|
||||||
|
}
|
||||||
|
is IncDecRef -> collectLoopVarNamesRef(ref.target)
|
||||||
|
is ConditionalRef -> {
|
||||||
|
collectLoopVarNamesRef(ref.condition)
|
||||||
|
collectLoopVarNamesRef(ref.ifTrue)
|
||||||
|
collectLoopVarNamesRef(ref.ifFalse)
|
||||||
|
}
|
||||||
|
is ElvisRef -> {
|
||||||
|
collectLoopVarNamesRef(ref.left)
|
||||||
|
collectLoopVarNamesRef(ref.right)
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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 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
|
||||||
|
|||||||
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Visibility
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
sealed class BytecodeConst {
|
sealed class BytecodeConst {
|
||||||
@ -26,6 +27,12 @@ sealed class BytecodeConst {
|
|||||||
data class StringVal(val value: String) : BytecodeConst()
|
data class StringVal(val value: String) : BytecodeConst()
|
||||||
data class ObjRef(val value: Obj) : BytecodeConst()
|
data class ObjRef(val value: Obj) : BytecodeConst()
|
||||||
data class SlotPlan(val plan: Map<String, Int>) : BytecodeConst()
|
data class SlotPlan(val plan: Map<String, Int>) : BytecodeConst()
|
||||||
|
data class LocalDecl(
|
||||||
|
val name: String,
|
||||||
|
val isMutable: Boolean,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
) : BytecodeConst()
|
||||||
data class CallArgsPlan(val tailBlock: Boolean, val specs: List<CallArgSpec>) : BytecodeConst()
|
data class CallArgsPlan(val tailBlock: Boolean, val specs: List<CallArgSpec>) : BytecodeConst()
|
||||||
data class CallArgSpec(val name: String?, val isSplat: Boolean)
|
data class CallArgSpec(val name: String?, val isSplat: Boolean)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,7 @@ object BytecodeDisassembler {
|
|||||||
|
|
||||||
private fun operandKinds(op: Opcode): List<OperandKind> {
|
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||||
return when (op) {
|
return when (op) {
|
||||||
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE -> emptyList()
|
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.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.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 ->
|
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
||||||
@ -91,8 +91,10 @@ object BytecodeDisassembler {
|
|||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.PUSH_SCOPE ->
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
listOf(OperandKind.CONST)
|
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_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.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.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
||||||
|
|||||||
@ -31,6 +31,8 @@ class BytecodeStatement private constructor(
|
|||||||
return BytecodeVm().execute(function, scope, emptyList())
|
return BytecodeVm().execute(function, scope, emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun bytecodeFunction(): BytecodeFunction = 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
|
||||||
|
|||||||
@ -19,6 +19,7 @@ package net.sergeych.lyng.bytecode
|
|||||||
import net.sergeych.lyng.Arguments
|
import net.sergeych.lyng.Arguments
|
||||||
import net.sergeych.lyng.PerfFlags
|
import net.sergeych.lyng.PerfFlags
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
|
|
||||||
class BytecodeVm {
|
class BytecodeVm {
|
||||||
@ -27,11 +28,16 @@ class BytecodeVm {
|
|||||||
private const val ARG_PLAN_MASK = 0x7FFF
|
private const val ARG_PLAN_MASK = 0x7FFF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var virtualDepth = 0
|
||||||
|
|
||||||
suspend fun execute(fn: BytecodeFunction, scope0: Scope, args: List<Obj>): Obj {
|
suspend fun execute(fn: BytecodeFunction, scope0: Scope, args: List<Obj>): Obj {
|
||||||
val scopeStack = ArrayDeque<Scope>()
|
val scopeStack = ArrayDeque<Scope>()
|
||||||
|
val scopeVirtualStack = ArrayDeque<Boolean>()
|
||||||
|
val slotPlanStack = ArrayDeque<Map<String, Int?>>()
|
||||||
var scope = scope0
|
var scope = scope0
|
||||||
val methodCallSites = BytecodeCallSiteCache.methodCallSites(fn)
|
val methodCallSites = BytecodeCallSiteCache.methodCallSites(fn)
|
||||||
val frame = BytecodeFrame(fn.localCount, args.size)
|
val frame = BytecodeFrame(fn.localCount, args.size)
|
||||||
|
virtualDepth = 0
|
||||||
for (i in args.indices) {
|
for (i in args.indices) {
|
||||||
frame.setObj(frame.argBase + i, args[i])
|
frame.setObj(frame.argBase + i, args[i])
|
||||||
}
|
}
|
||||||
@ -734,16 +740,65 @@ class BytecodeVm {
|
|||||||
ip += fn.constIdWidth
|
ip += fn.constIdWidth
|
||||||
val planConst = fn.constants[constId] as? BytecodeConst.SlotPlan
|
val planConst = fn.constants[constId] as? BytecodeConst.SlotPlan
|
||||||
?: error("PUSH_SCOPE expects SlotPlan at $constId")
|
?: error("PUSH_SCOPE expects SlotPlan at $constId")
|
||||||
|
if (scope.skipScopeCreation) {
|
||||||
|
val snapshot = scope.applySlotPlanWithSnapshot(planConst.plan)
|
||||||
|
slotPlanStack.addLast(snapshot)
|
||||||
|
virtualDepth += 1
|
||||||
scopeStack.addLast(scope)
|
scopeStack.addLast(scope)
|
||||||
|
scopeVirtualStack.addLast(true)
|
||||||
|
} else {
|
||||||
|
scopeStack.addLast(scope)
|
||||||
|
scopeVirtualStack.addLast(false)
|
||||||
scope = scope.createChildScope()
|
scope = scope.createChildScope()
|
||||||
if (planConst.plan.isNotEmpty()) {
|
if (planConst.plan.isNotEmpty()) {
|
||||||
scope.applySlotPlan(planConst.plan)
|
scope.applySlotPlan(planConst.plan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Opcode.POP_SCOPE -> {
|
Opcode.POP_SCOPE -> {
|
||||||
|
val isVirtual = scopeVirtualStack.removeLastOrNull()
|
||||||
|
?: error("Scope stack underflow in POP_SCOPE")
|
||||||
|
if (isVirtual) {
|
||||||
|
val snapshot = slotPlanStack.removeLastOrNull()
|
||||||
|
?: error("Slot plan stack underflow in POP_SCOPE")
|
||||||
|
scope.restoreSlotPlan(snapshot)
|
||||||
|
virtualDepth -= 1
|
||||||
|
}
|
||||||
scope = scopeStack.removeLastOrNull()
|
scope = scopeStack.removeLastOrNull()
|
||||||
?: error("Scope stack underflow in POP_SCOPE")
|
?: error("Scope stack underflow in POP_SCOPE")
|
||||||
}
|
}
|
||||||
|
Opcode.PUSH_SLOT_PLAN -> {
|
||||||
|
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
|
||||||
|
ip += fn.constIdWidth
|
||||||
|
val planConst = fn.constants[constId] as? BytecodeConst.SlotPlan
|
||||||
|
?: error("PUSH_SLOT_PLAN expects SlotPlan at $constId")
|
||||||
|
val snapshot = scope.applySlotPlanWithSnapshot(planConst.plan)
|
||||||
|
slotPlanStack.addLast(snapshot)
|
||||||
|
virtualDepth += 1
|
||||||
|
}
|
||||||
|
Opcode.POP_SLOT_PLAN -> {
|
||||||
|
val snapshot = slotPlanStack.removeLastOrNull()
|
||||||
|
?: error("Slot plan stack underflow in POP_SLOT_PLAN")
|
||||||
|
scope.restoreSlotPlan(snapshot)
|
||||||
|
virtualDepth -= 1
|
||||||
|
}
|
||||||
|
Opcode.DECL_LOCAL -> {
|
||||||
|
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
|
||||||
|
ip += fn.constIdWidth
|
||||||
|
val slot = decoder.readSlot(code, ip)
|
||||||
|
ip += fn.slotWidth
|
||||||
|
val decl = fn.constants[constId] as? BytecodeConst.LocalDecl
|
||||||
|
?: error("DECL_LOCAL expects LocalDecl at $constId")
|
||||||
|
val value = slotToObj(fn, frame, scope, slot).byValueCopy()
|
||||||
|
scope.addItem(
|
||||||
|
decl.name,
|
||||||
|
decl.isMutable,
|
||||||
|
value,
|
||||||
|
decl.visibility,
|
||||||
|
recordType = ObjRecord.Type.Other,
|
||||||
|
isTransient = decl.isTransient
|
||||||
|
)
|
||||||
|
}
|
||||||
Opcode.CALL_SLOT -> {
|
Opcode.CALL_SLOT -> {
|
||||||
val calleeSlot = decoder.readSlot(code, ip)
|
val calleeSlot = decoder.readSlot(code, ip)
|
||||||
ip += fn.slotWidth
|
ip += fn.slotWidth
|
||||||
@ -979,11 +1034,16 @@ class BytecodeVm {
|
|||||||
|
|
||||||
private fun resolveScope(scope: Scope, depth: Int): Scope {
|
private fun resolveScope(scope: Scope, depth: Int): Scope {
|
||||||
if (depth == 0) return scope
|
if (depth == 0) return scope
|
||||||
|
var effectiveDepth = depth
|
||||||
|
if (virtualDepth > 0) {
|
||||||
|
if (effectiveDepth <= virtualDepth) return scope
|
||||||
|
effectiveDepth -= virtualDepth
|
||||||
|
}
|
||||||
val next = when (scope) {
|
val next = when (scope) {
|
||||||
is net.sergeych.lyng.ClosureScope -> scope.closureScope
|
is net.sergeych.lyng.ClosureScope -> scope.closureScope
|
||||||
else -> scope.parent
|
else -> scope.parent
|
||||||
}
|
}
|
||||||
return next?.let { resolveScope(it, depth - 1) }
|
return next?.let { resolveScope(it, effectiveDepth - 1) }
|
||||||
?: error("Scope depth $depth is out of range")
|
?: error("Scope depth $depth is out of range")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -109,6 +109,9 @@ enum class Opcode(val code: Int) {
|
|||||||
RET_VOID(0x84),
|
RET_VOID(0x84),
|
||||||
PUSH_SCOPE(0x85),
|
PUSH_SCOPE(0x85),
|
||||||
POP_SCOPE(0x86),
|
POP_SCOPE(0x86),
|
||||||
|
PUSH_SLOT_PLAN(0x87),
|
||||||
|
POP_SLOT_PLAN(0x88),
|
||||||
|
DECL_LOCAL(0x89),
|
||||||
|
|
||||||
CALL_DIRECT(0x90),
|
CALL_DIRECT(0x90),
|
||||||
CALL_VIRTUAL(0x91),
|
CALL_VIRTUAL(0x91),
|
||||||
|
|||||||
@ -17,7 +17,12 @@
|
|||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.Benchmarks
|
import net.sergeych.lyng.Benchmarks
|
||||||
import net.sergeych.lyng.eval
|
import net.sergeych.lyng.Compiler
|
||||||
|
import net.sergeych.lyng.ForInStatement
|
||||||
|
import net.sergeych.lyng.Script
|
||||||
|
import net.sergeych.lyng.Statement
|
||||||
|
import net.sergeych.lyng.bytecode.BytecodeDisassembler
|
||||||
|
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
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
@ -27,8 +32,7 @@ class NestedRangeBenchmarkTest {
|
|||||||
@Test
|
@Test
|
||||||
fun benchmarkHappyNumbersNestedRanges() = runTest {
|
fun benchmarkHappyNumbersNestedRanges() = runTest {
|
||||||
if (!Benchmarks.enabled) return@runTest
|
if (!Benchmarks.enabled) return@runTest
|
||||||
val script = """
|
val bodyScript = """
|
||||||
fun naiveCountHappyNumbers() {
|
|
||||||
var count = 0
|
var count = 0
|
||||||
for( n1 in 0..9 )
|
for( n1 in 0..9 )
|
||||||
for( n2 in 0..9 )
|
for( n2 in 0..9 )
|
||||||
@ -38,14 +42,55 @@ class NestedRangeBenchmarkTest {
|
|||||||
for( n6 in 0..9 )
|
for( n6 in 0..9 )
|
||||||
if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
|
if( n1 + n2 + n3 == n4 + n5 + n6 ) count++
|
||||||
count
|
count
|
||||||
}
|
|
||||||
naiveCountHappyNumbers()
|
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
|
val compiled = Compiler.compile(bodyScript)
|
||||||
|
dumpNestedLoopBytecode(compiled.debugStatements())
|
||||||
|
|
||||||
|
val script = """
|
||||||
|
fun naiveCountHappyNumbers() {
|
||||||
|
$bodyScript
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
val scope = Script.newScope()
|
||||||
|
scope.eval(script)
|
||||||
|
val fnDisasm = scope.disassembleSymbol("naiveCountHappyNumbers")
|
||||||
|
println("[DEBUG_LOG] [BENCH] nested-happy function naiveCountHappyNumbers bytecode:\n$fnDisasm")
|
||||||
|
|
||||||
val start = TimeSource.Monotonic.markNow()
|
val start = TimeSource.Monotonic.markNow()
|
||||||
val result = eval(script) as ObjInt
|
val result = scope.eval("naiveCountHappyNumbers()") as ObjInt
|
||||||
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
val elapsedMs = start.elapsedNow().inWholeMilliseconds
|
||||||
println("[DEBUG_LOG] [BENCH] nested-happy elapsed=${elapsedMs} ms")
|
println("[DEBUG_LOG] [BENCH] nested-happy elapsed=${elapsedMs} ms")
|
||||||
assertEquals(55252L, result.value)
|
assertEquals(55252L, result.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun dumpNestedLoopBytecode(statements: List<Statement>) {
|
||||||
|
var current: Statement? = statements.firstOrNull { stmt ->
|
||||||
|
stmt is BytecodeStatement && stmt.original is ForInStatement
|
||||||
|
}
|
||||||
|
var depth = 1
|
||||||
|
while (current is BytecodeStatement && current.original is ForInStatement) {
|
||||||
|
val original = current.original as ForInStatement
|
||||||
|
println(
|
||||||
|
"[DEBUG_LOG] [BENCH] nested-happy loop depth=$depth " +
|
||||||
|
"constRange=${original.constRange} canBreak=${original.canBreak} " +
|
||||||
|
"loopSlotPlan=${original.loopSlotPlan}"
|
||||||
|
)
|
||||||
|
val fn = current.bytecodeFunction()
|
||||||
|
val slots = fn.scopeSlotNames.mapIndexed { idx, name ->
|
||||||
|
val slotName = name ?: "s$idx"
|
||||||
|
"$slotName@${fn.scopeSlotDepths[idx]}:${fn.scopeSlotIndices[idx]}"
|
||||||
|
}
|
||||||
|
println("[DEBUG_LOG] [BENCH] nested-happy slots depth=$depth: ${slots.joinToString(", ")}")
|
||||||
|
val disasm = BytecodeDisassembler.disassemble(fn)
|
||||||
|
println("[DEBUG_LOG] [BENCH] nested-happy bytecode depth=$depth:\n$disasm")
|
||||||
|
current = original.body
|
||||||
|
depth += 1
|
||||||
|
}
|
||||||
|
if (depth == 1) {
|
||||||
|
println("[DEBUG_LOG] [BENCH] nested-happy bytecode: <not found>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3989,6 +3989,7 @@ class ScriptTest {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testParserOverflow() = runTest {
|
fun testParserOverflow() = runTest {
|
||||||
try {
|
try {
|
||||||
@ -5044,4 +5045,3 @@ class ScriptTest {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user