Enforce bytecode-only wrappers and add extern bridge opcode
This commit is contained in:
parent
05cba5b653
commit
489dae6604
@ -74,7 +74,7 @@ internal suspend fun executeClassDecl(
|
|||||||
newClass.classScope = classScope
|
newClass.classScope = classScope
|
||||||
classScope.addConst("object", newClass)
|
classScope.addConst("object", newClass)
|
||||||
|
|
||||||
spec.bodyInit?.execute(classScope)
|
spec.bodyInit?.let { requireBytecodeBody(scope, it, "object body init").execute(classScope) }
|
||||||
|
|
||||||
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
|
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
|
||||||
if (spec.declaredName != null) {
|
if (spec.declaredName != null) {
|
||||||
@ -148,16 +148,29 @@ internal suspend fun executeClassDecl(
|
|||||||
}
|
}
|
||||||
classScope.currentClassCtx = newClass
|
classScope.currentClassCtx = newClass
|
||||||
newClass.classScope = classScope
|
newClass.classScope = classScope
|
||||||
spec.bodyInit?.execute(classScope)
|
spec.bodyInit?.let { requireBytecodeBody(scope, it, "class body init").execute(classScope) }
|
||||||
if (spec.initScope.isNotEmpty()) {
|
if (spec.initScope.isNotEmpty()) {
|
||||||
for (s in spec.initScope) {
|
for (s in spec.initScope) {
|
||||||
s.execute(classScope)
|
requireBytecodeBody(scope, s, "class init").execute(classScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newClass.checkAbstractSatisfaction(spec.startPos)
|
newClass.checkAbstractSatisfaction(spec.startPos)
|
||||||
return newClass
|
return newClass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun requireBytecodeBody(
|
||||||
|
scope: Scope,
|
||||||
|
stmt: Statement,
|
||||||
|
label: String
|
||||||
|
): net.sergeych.lyng.bytecode.BytecodeStatement {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
}
|
||||||
|
|
||||||
class ClassDeclStatement(
|
class ClassDeclStatement(
|
||||||
val spec: ClassDeclSpec,
|
val spec: ClassDeclSpec,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
|
|||||||
@ -176,6 +176,7 @@ class Compiler(
|
|||||||
private val encodedPayloadTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
private val encodedPayloadTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
||||||
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||||
private val objectDeclNames: MutableSet<String> = mutableSetOf()
|
private val objectDeclNames: MutableSet<String> = mutableSetOf()
|
||||||
|
private val externCallableNames: MutableSet<String> = mutableSetOf()
|
||||||
|
|
||||||
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
|
private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) {
|
||||||
val plan = moduleSlotPlan() ?: return
|
val plan = moduleSlotPlan() ?: return
|
||||||
@ -1853,6 +1854,7 @@ class Compiler(
|
|||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
externCallableNames = externCallableNames,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1876,6 +1878,7 @@ class Compiler(
|
|||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
externCallableNames = externCallableNames,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1920,6 +1923,7 @@ class Compiler(
|
|||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
externCallableNames = externCallableNames,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -6690,6 +6694,12 @@ class Compiler(
|
|||||||
if (extensionWrapperName != null) {
|
if (extensionWrapperName != null) {
|
||||||
declareLocalName(extensionWrapperName, isMutable = false)
|
declareLocalName(extensionWrapperName, isMutable = false)
|
||||||
}
|
}
|
||||||
|
if (actualExtern && declKind != SymbolKind.MEMBER) {
|
||||||
|
externCallableNames.add(name)
|
||||||
|
}
|
||||||
|
if (actualExtern && extensionWrapperName != null) {
|
||||||
|
externCallableNames.add(extensionWrapperName)
|
||||||
|
}
|
||||||
val declSlotPlan = if (declKind != SymbolKind.MEMBER) slotPlanStack.lastOrNull() else null
|
val declSlotPlan = if (declKind != SymbolKind.MEMBER) slotPlanStack.lastOrNull() else null
|
||||||
val declSlotIndex = declSlotPlan?.slots?.get(name)?.index
|
val declSlotIndex = declSlotPlan?.slots?.get(name)?.index
|
||||||
val declScopeId = declSlotPlan?.id
|
val declScopeId = declSlotPlan?.id
|
||||||
|
|||||||
@ -18,6 +18,7 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjClass
|
import net.sergeych.lyng.obj.ObjClass
|
||||||
|
import net.sergeych.lyng.obj.ObjExternCallable
|
||||||
import net.sergeych.lyng.obj.ObjExtensionMethodCallable
|
import net.sergeych.lyng.obj.ObjExtensionMethodCallable
|
||||||
import net.sergeych.lyng.obj.ObjInstance
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
@ -66,21 +67,22 @@ internal suspend fun executeFunctionDecl(
|
|||||||
if (spec.actualExtern && spec.extTypeName == null && !spec.parentIsClassBody) {
|
if (spec.actualExtern && spec.extTypeName == null && !spec.parentIsClassBody) {
|
||||||
val existing = scope.get(spec.name)
|
val existing = scope.get(spec.name)
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
|
val value = (existing.value as? ObjExternCallable) ?: ObjExternCallable.wrap(existing.value)
|
||||||
scope.addItem(
|
scope.addItem(
|
||||||
spec.name,
|
spec.name,
|
||||||
false,
|
false,
|
||||||
existing.value,
|
value,
|
||||||
spec.visibility,
|
spec.visibility,
|
||||||
callSignature = existing.callSignature
|
callSignature = existing.callSignature
|
||||||
)
|
)
|
||||||
return existing.value
|
return value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec.isDelegated) {
|
if (spec.isDelegated) {
|
||||||
val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate")
|
val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate")
|
||||||
val accessType = ObjString("Callable")
|
val accessType = ObjString("Callable")
|
||||||
val initValue = delegateExpr.execute(scope)
|
val initValue = requireBytecodeBody(scope, delegateExpr, "delegated function").execute(scope)
|
||||||
val finalDelegate = try {
|
val finalDelegate = try {
|
||||||
initValue.invokeInstanceMethod(scope, "bind", Arguments(ObjString(spec.name), accessType, scope.thisObj))
|
initValue.invokeInstanceMethod(scope, "bind", Arguments(ObjString(spec.name), accessType, scope.thisObj))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -143,7 +145,7 @@ internal suspend fun executeFunctionDecl(
|
|||||||
)
|
)
|
||||||
val initStmt = spec.delegateInitStatement
|
val initStmt = spec.delegateInitStatement
|
||||||
?: scope.raiseIllegalState("missing delegated init statement for ${spec.name}")
|
?: scope.raiseIllegalState("missing delegated init statement for ${spec.name}")
|
||||||
cls.instanceInitializers += initStmt
|
cls.instanceInitializers += requireBytecodeBody(scope, initStmt, "delegated function init")
|
||||||
} else {
|
} else {
|
||||||
scope.addItem(
|
scope.addItem(
|
||||||
spec.name,
|
spec.name,
|
||||||
@ -225,6 +227,19 @@ internal suspend fun executeFunctionDecl(
|
|||||||
return annotatedFnBody
|
return annotatedFnBody
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun requireBytecodeBody(
|
||||||
|
scope: Scope,
|
||||||
|
stmt: Statement,
|
||||||
|
label: String
|
||||||
|
): net.sergeych.lyng.bytecode.BytecodeStatement {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
}
|
||||||
|
|
||||||
class FunctionDeclStatement(
|
class FunctionDeclStatement(
|
||||||
val spec: FunctionDeclSpec,
|
val spec: FunctionDeclSpec,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
|
|||||||
@ -28,10 +28,19 @@ class InlineBlockStatement(
|
|||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
var last: Obj = ObjVoid
|
var last: Obj = ObjVoid
|
||||||
for (stmt in statements) {
|
for (stmt in statements) {
|
||||||
last = stmt.execute(scope)
|
last = requireBytecodeBody(scope, stmt, "inline block").execute(scope)
|
||||||
}
|
}
|
||||||
return last
|
return last
|
||||||
}
|
}
|
||||||
|
|
||||||
fun statements(): List<Statement> = statements
|
fun statements(): List<Statement> = statements
|
||||||
|
|
||||||
|
private suspend fun requireBytecodeBody(scope: Scope, stmt: Statement, label: String): net.sergeych.lyng.bytecode.BytecodeStatement {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import net.sergeych.lyng.bytecode.CmdFrame
|
|||||||
import net.sergeych.lyng.bytecode.CmdVm
|
import net.sergeych.lyng.bytecode.CmdVm
|
||||||
import net.sergeych.lyng.obj.Obj
|
import net.sergeych.lyng.obj.Obj
|
||||||
import net.sergeych.lyng.obj.ObjNull
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
|
||||||
|
|
||||||
class PropertyAccessorStatement(
|
class PropertyAccessorStatement(
|
||||||
val body: Statement,
|
val body: Statement,
|
||||||
@ -29,32 +28,33 @@ class PropertyAccessorStatement(
|
|||||||
) : Statement() {
|
) : Statement() {
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
if (argName != null) {
|
if (argName != null) {
|
||||||
val value = scope.args.list.firstOrNull() ?: ObjNull
|
|
||||||
val prev = scope.skipScopeCreation
|
val prev = scope.skipScopeCreation
|
||||||
scope.skipScopeCreation = true
|
scope.skipScopeCreation = true
|
||||||
return try {
|
return try {
|
||||||
if (body is net.sergeych.lyng.bytecode.BytecodeStatement) {
|
val bytecodeStmt = requireBytecodeBody(scope, body, "property accessor")
|
||||||
val fn = body.bytecodeFunction()
|
val fn = bytecodeStmt.bytecodeFunction()
|
||||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||||
val slotPlan = fn.localSlotPlanByName()
|
val slotPlan = fn.localSlotPlanByName()
|
||||||
val slotIndex = slotPlan[argName]
|
val slotIndex = slotPlan[argName]
|
||||||
|
?: scope.raiseIllegalState("property accessor argument $argName missing from slot plan")
|
||||||
val argValue = arguments.list.firstOrNull() ?: ObjNull
|
val argValue = arguments.list.firstOrNull() ?: ObjNull
|
||||||
if (slotIndex != null) {
|
|
||||||
frame.frame.setObj(slotIndex, argValue)
|
frame.frame.setObj(slotIndex, argValue)
|
||||||
} else if (scope.getLocalRecordDirect(argName) == null) {
|
|
||||||
scope.addItem(argName, true, argValue, recordType = ObjRecord.Type.Argument)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
scope.pos = pos
|
scope.pos = pos
|
||||||
CmdVm().execute(fn, scope, scope.args, binder)
|
CmdVm().execute(fn, scope, scope.args, binder)
|
||||||
} else {
|
|
||||||
scope.addItem(argName, true, value, recordType = ObjRecord.Type.Argument)
|
|
||||||
body.execute(scope)
|
|
||||||
}
|
|
||||||
} finally {
|
} finally {
|
||||||
scope.skipScopeCreation = prev
|
scope.skipScopeCreation = prev
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return body.execute(scope)
|
return requireBytecodeBody(scope, body, "property accessor").execute(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun requireBytecodeBody(scope: Scope, stmt: Statement, label: String): net.sergeych.lyng.bytecode.BytecodeStatement {
|
||||||
|
val bytecode = when (stmt) {
|
||||||
|
is net.sergeych.lyng.bytecode.BytecodeStatement -> stmt
|
||||||
|
is BytecodeBodyProvider -> stmt.bytecodeBody()
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return bytecode ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -78,7 +78,6 @@ open class Scope(
|
|||||||
thisObj = primary
|
thisObj = primary
|
||||||
val extrasSnapshot = when {
|
val extrasSnapshot = when {
|
||||||
extras.isEmpty() -> emptyList()
|
extras.isEmpty() -> emptyList()
|
||||||
extras === thisVariants -> extras.toList()
|
|
||||||
extras is MutableList<*> -> synchronized(extras) { extras.toList() }
|
extras is MutableList<*> -> synchronized(extras) { extras.toList() }
|
||||||
else -> extras.toList()
|
else -> extras.toList()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Limited facade for Kotlin bridge callables.
|
||||||
|
* Exposes only the minimal API needed to read/write vars and invoke methods.
|
||||||
|
*/
|
||||||
|
interface ScopeFacade {
|
||||||
|
val args: Arguments
|
||||||
|
var pos: Pos
|
||||||
|
var thisObj: Obj
|
||||||
|
operator fun get(name: String): ObjRecord?
|
||||||
|
suspend fun resolve(rec: ObjRecord, name: String): Obj
|
||||||
|
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj)
|
||||||
|
fun raiseError(message: String): Nothing
|
||||||
|
fun raiseSymbolNotFound(name: String): Nothing
|
||||||
|
fun raiseIllegalState(message: String = "Illegal argument error"): Nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ScopeBridge(private val scope: Scope) : ScopeFacade {
|
||||||
|
override val args: Arguments
|
||||||
|
get() = scope.args
|
||||||
|
override var pos: Pos
|
||||||
|
get() = scope.pos
|
||||||
|
set(value) { scope.pos = value }
|
||||||
|
override var thisObj: Obj
|
||||||
|
get() = scope.thisObj
|
||||||
|
set(value) { scope.thisObj = value }
|
||||||
|
override fun get(name: String): ObjRecord? = scope[name]
|
||||||
|
override suspend fun resolve(rec: ObjRecord, name: String): Obj = scope.resolve(rec, name)
|
||||||
|
override suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) = scope.assign(rec, name, newValue)
|
||||||
|
override fun raiseError(message: String): Nothing = scope.raiseError(message)
|
||||||
|
override fun raiseSymbolNotFound(name: String): Nothing = scope.raiseSymbolNotFound(name)
|
||||||
|
override fun raiseIllegalState(message: String): Nothing = scope.raiseIllegalState(message)
|
||||||
|
}
|
||||||
@ -35,6 +35,7 @@ class BytecodeCompiler(
|
|||||||
private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||||
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||||
|
private val externCallableNames: Set<String> = emptySet(),
|
||||||
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
) {
|
) {
|
||||||
private var builder = CmdBuilder()
|
private var builder = CmdBuilder()
|
||||||
@ -3688,6 +3689,7 @@ class BytecodeCompiler(
|
|||||||
private fun compileCall(ref: CallRef): CompiledValue? {
|
private fun compileCall(ref: CallRef): CompiledValue? {
|
||||||
val callPos = callSitePos()
|
val callPos = callSitePos()
|
||||||
val localTarget = ref.target as? LocalVarRef
|
val localTarget = ref.target as? LocalVarRef
|
||||||
|
val isExternCall = localTarget != null && externCallableNames.contains(localTarget.name)
|
||||||
if (localTarget != null) {
|
if (localTarget != null) {
|
||||||
val direct = resolveDirectNameSlot(localTarget.name)
|
val direct = resolveDirectNameSlot(localTarget.name)
|
||||||
if (direct == null) {
|
if (direct == null) {
|
||||||
@ -3711,7 +3713,13 @@ class BytecodeCompiler(
|
|||||||
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
|
||||||
setPos(callPos)
|
setPos(callPos)
|
||||||
builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst)
|
builder.emit(
|
||||||
|
if (isExternCall) Opcode.CALL_BRIDGE_SLOT else Opcode.CALL_SLOT,
|
||||||
|
callee.slot,
|
||||||
|
args.base,
|
||||||
|
encodedCount,
|
||||||
|
dst
|
||||||
|
)
|
||||||
if (initClass != null) {
|
if (initClass != null) {
|
||||||
slotObjClass[dst] = initClass
|
slotObjClass[dst] = initClass
|
||||||
}
|
}
|
||||||
@ -3730,7 +3738,13 @@ class BytecodeCompiler(
|
|||||||
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
|
||||||
setPos(callPos)
|
setPos(callPos)
|
||||||
builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst)
|
builder.emit(
|
||||||
|
if (isExternCall) Opcode.CALL_BRIDGE_SLOT else Opcode.CALL_SLOT,
|
||||||
|
callee.slot,
|
||||||
|
args.base,
|
||||||
|
encodedCount,
|
||||||
|
dst
|
||||||
|
)
|
||||||
if (initClass != null) {
|
if (initClass != null) {
|
||||||
slotObjClass[dst] = initClass
|
slotObjClass[dst] = initClass
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,7 @@ class BytecodeStatement private constructor(
|
|||||||
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||||
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||||
|
externCallableNames: Set<String> = emptySet(),
|
||||||
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
): Statement {
|
): Statement {
|
||||||
if (statement is BytecodeStatement) return statement
|
if (statement is BytecodeStatement) return statement
|
||||||
@ -80,6 +81,7 @@ class BytecodeStatement private constructor(
|
|||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
externCallableNames = externCallableNames,
|
||||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
val compiled = compiler.compileStatement(nameHint, statement)
|
val compiled = compiler.compileStatement(nameHint, statement)
|
||||||
|
|||||||
@ -197,7 +197,7 @@ class CmdBuilder {
|
|||||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_MEMBER_SLOT ->
|
Opcode.CALL_MEMBER_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_SLOT ->
|
Opcode.CALL_SLOT, Opcode.CALL_BRIDGE_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_DYNAMIC_MEMBER ->
|
Opcode.CALL_DYNAMIC_MEMBER ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
@ -435,6 +435,7 @@ class CmdBuilder {
|
|||||||
Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1])
|
Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1])
|
||||||
Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4])
|
Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
|
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
Opcode.CALL_BRIDGE_SLOT -> CmdCallBridgeSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.CALL_DYNAMIC_MEMBER -> CmdCallDynamicMember(operands[0], operands[1], operands[2], operands[3], operands[4])
|
Opcode.CALL_DYNAMIC_MEMBER -> CmdCallDynamicMember(operands[0], operands[1], operands[2], operands[3], operands[4])
|
||||||
Opcode.GET_INDEX -> CmdGetIndex(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.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
|
||||||
|
|||||||
@ -229,6 +229,7 @@ object CmdDisassembler {
|
|||||||
is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
|
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
|
is CmdCallBridgeSlot -> Opcode.CALL_BRIDGE_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
is CmdCallDynamicMember -> Opcode.CALL_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.argBase, cmd.argCount, cmd.dst)
|
is CmdCallDynamicMember -> Opcode.CALL_DYNAMIC_MEMBER to intArrayOf(cmd.recvSlot, cmd.nameId, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
|
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 CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
|
||||||
@ -330,7 +331,7 @@ object CmdDisassembler {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.IP)
|
listOf(OperandKind.SLOT, OperandKind.IP)
|
||||||
Opcode.CALL_DIRECT ->
|
Opcode.CALL_DIRECT ->
|
||||||
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_SLOT ->
|
Opcode.CALL_SLOT, Opcode.CALL_BRIDGE_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.CALL_MEMBER_SLOT ->
|
Opcode.CALL_MEMBER_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
|
|||||||
@ -2023,6 +2023,40 @@ class CmdCallSlot(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CmdCallBridgeSlot(
|
||||||
|
internal val calleeSlot: Int,
|
||||||
|
internal val argBase: Int,
|
||||||
|
internal val argCount: Int,
|
||||||
|
internal val dst: Int,
|
||||||
|
) : Cmd() {
|
||||||
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
|
val callee = frame.slotToObj(calleeSlot)
|
||||||
|
if (callee === ObjUnset) {
|
||||||
|
val name = if (calleeSlot < frame.fn.scopeSlotCount) {
|
||||||
|
frame.fn.scopeSlotNames[calleeSlot]
|
||||||
|
} else {
|
||||||
|
val localIndex = calleeSlot - frame.fn.scopeSlotCount
|
||||||
|
frame.fn.localSlotNames.getOrNull(localIndex)
|
||||||
|
}
|
||||||
|
val message = name?.let { "property '$it' is unset (not initialized)" }
|
||||||
|
?: "property is unset (not initialized) in ${frame.fn.name} at slot $calleeSlot"
|
||||||
|
frame.ensureScope().raiseUnset(message)
|
||||||
|
}
|
||||||
|
if (callee !is net.sergeych.lyng.obj.ObjExternCallable) {
|
||||||
|
frame.ensureScope().raiseIllegalState("CALL_BRIDGE_SLOT expects extern callable")
|
||||||
|
}
|
||||||
|
val args = frame.buildArguments(argBase, argCount)
|
||||||
|
val result = if (PerfFlags.SCOPE_POOL) {
|
||||||
|
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
|
||||||
|
} else {
|
||||||
|
val scope = frame.ensureScope()
|
||||||
|
callee.callOn(scope.createChildScope(scope.pos, args = args))
|
||||||
|
}
|
||||||
|
frame.storeObjResult(dst, result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class CmdListLiteral(
|
class CmdListLiteral(
|
||||||
internal val planId: Int,
|
internal val planId: Int,
|
||||||
internal val baseSlot: Int,
|
internal val baseSlot: Int,
|
||||||
|
|||||||
@ -136,6 +136,7 @@ enum class Opcode(val code: Int) {
|
|||||||
ASSIGN_DESTRUCTURE(0x91),
|
ASSIGN_DESTRUCTURE(0x91),
|
||||||
CALL_MEMBER_SLOT(0x92),
|
CALL_MEMBER_SLOT(0x92),
|
||||||
CALL_SLOT(0x93),
|
CALL_SLOT(0x93),
|
||||||
|
CALL_BRIDGE_SLOT(0x94),
|
||||||
|
|
||||||
GET_INDEX(0xA2),
|
GET_INDEX(0xA2),
|
||||||
SET_INDEX(0xA3),
|
SET_INDEX(0xA3),
|
||||||
|
|||||||
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* 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.obj
|
||||||
|
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.ScopeBridge
|
||||||
|
import net.sergeych.lyng.ScopeFacade
|
||||||
|
import net.sergeych.lyng.Statement
|
||||||
|
|
||||||
|
class ObjExternCallable private constructor(
|
||||||
|
private val target: Obj?,
|
||||||
|
private val fn: (suspend ScopeFacade.() -> Obj)?
|
||||||
|
) : Obj() {
|
||||||
|
|
||||||
|
override val objClass: ObjClass
|
||||||
|
get() = Statement.type
|
||||||
|
|
||||||
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
|
val facade = ScopeBridge(scope)
|
||||||
|
return when {
|
||||||
|
fn != null -> facade.fn()
|
||||||
|
target != null -> target.callOn(scope)
|
||||||
|
else -> ObjVoid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "ExternCallable@${hashCode()}"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun wrap(target: Obj): ObjExternCallable = ObjExternCallable(target, null)
|
||||||
|
fun fromBridge(fn: suspend ScopeFacade.() -> Obj): ObjExternCallable = ObjExternCallable(null, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user