Temporary lazy builtin baseline
This commit is contained in:
parent
9ddc7dbee6
commit
217787e17a
@ -184,6 +184,7 @@ class Compiler(
|
||||
private val encodedPayloadTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||
private val objectDeclNames: MutableSet<String> = mutableSetOf()
|
||||
private val externCallableNames: MutableSet<String> = mutableSetOf()
|
||||
private val externBindingNames: MutableSet<String> = mutableSetOf()
|
||||
private val moduleDeclaredNames: MutableSet<String> = mutableSetOf()
|
||||
private var seedingSlotPlan: Boolean = false
|
||||
|
||||
@ -192,6 +193,7 @@ class Compiler(
|
||||
if (plan.slots.isEmpty()) return emptyMap()
|
||||
val result = LinkedHashMap<String, ForcedLocalSlotInfo>(plan.slots.size)
|
||||
for ((name, entry) in plan.slots) {
|
||||
if (externBindingNames.contains(name)) continue
|
||||
result[name] = ForcedLocalSlotInfo(
|
||||
index = entry.index,
|
||||
isMutable = entry.isMutable,
|
||||
@ -885,6 +887,7 @@ class Compiler(
|
||||
name = declaredName,
|
||||
isMutable = false,
|
||||
visibility = Visibility.Public,
|
||||
actualExtern = false,
|
||||
initializer = initStmt,
|
||||
isTransient = false,
|
||||
typeDecl = null,
|
||||
@ -1751,6 +1754,7 @@ class Compiler(
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
) as BytecodeStatement
|
||||
unwrapped to bytecodeStmt.bytecodeFunction()
|
||||
@ -1957,6 +1961,9 @@ class Compiler(
|
||||
val scopeIndex = slotPlanStack.indexOfLast { it.id == slotLoc.scopeId }
|
||||
if (functionIndex >= 0 && scopeIndex >= functionIndex) return null
|
||||
val modulePlan = moduleSlotPlan()
|
||||
if (modulePlan != null && slotLoc.scopeId == modulePlan.id && externBindingNames.contains(name)) {
|
||||
return null
|
||||
}
|
||||
if (useScopeSlots && modulePlan != null && slotLoc.scopeId == modulePlan.id) {
|
||||
return null
|
||||
}
|
||||
@ -2075,6 +2082,7 @@ class Compiler(
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
}
|
||||
@ -2105,6 +2113,7 @@ class Compiler(
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
}
|
||||
@ -2160,6 +2169,7 @@ class Compiler(
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
}
|
||||
@ -2343,6 +2353,7 @@ class Compiler(
|
||||
stmt.name,
|
||||
stmt.isMutable,
|
||||
stmt.visibility,
|
||||
stmt.actualExtern,
|
||||
init,
|
||||
stmt.isTransient,
|
||||
stmt.typeDecl,
|
||||
@ -8893,7 +8904,7 @@ class Compiler(
|
||||
val effectiveEqToken = if (isProperty) null else eqToken
|
||||
|
||||
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
|
||||
if (!isStatic && declaringClassNameCaptured == null) declareLocalName(name, isMutable)
|
||||
if (!isStatic && declaringClassNameCaptured == null && !actualExtern) declareLocalName(name, isMutable)
|
||||
val declKind = if (codeContexts.lastOrNull() is CodeContext.ClassBody) {
|
||||
SymbolKind.MEMBER
|
||||
} else {
|
||||
@ -8902,6 +8913,8 @@ class Compiler(
|
||||
resolutionSink?.declareSymbol(name, declKind, isMutable, nameStartPos, isOverride = isOverride)
|
||||
if (declKind == SymbolKind.MEMBER && extTypeName == null) {
|
||||
(codeContexts.lastOrNull() as? CodeContext.ClassBody)?.declaredMembers?.add(name)
|
||||
} else if (actualExtern) {
|
||||
externBindingNames.add(name)
|
||||
}
|
||||
|
||||
val isDelegate = if (isAbstract || actualExtern) {
|
||||
@ -9059,6 +9072,7 @@ class Compiler(
|
||||
name,
|
||||
isMutable,
|
||||
visibility,
|
||||
actualExtern,
|
||||
initialExpression,
|
||||
isTransient,
|
||||
declaredType,
|
||||
|
||||
@ -542,7 +542,11 @@ class Script(
|
||||
}
|
||||
addFn("lazy") {
|
||||
val builder = requireOnlyArg<Obj>()
|
||||
ObjLazyDelegate(builder, requireScope())
|
||||
ObjLazyDelegate(builder, requireScope().snapshotForClosure())
|
||||
}
|
||||
addFn("__builtinLazy") {
|
||||
val builder = requireOnlyArg<Obj>()
|
||||
ObjLazyDelegate(builder, requireScope().snapshotForClosure())
|
||||
}
|
||||
addVoidFn("delay") {
|
||||
val a = args.firstAndOnly()
|
||||
|
||||
@ -24,6 +24,7 @@ class VarDeclStatement(
|
||||
val name: String,
|
||||
val isMutable: Boolean,
|
||||
val visibility: Visibility,
|
||||
val actualExtern: Boolean,
|
||||
val initializer: Statement?,
|
||||
val isTransient: Boolean,
|
||||
val typeDecl: TypeDecl?,
|
||||
|
||||
@ -42,6 +42,7 @@ class BytecodeCompiler(
|
||||
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||
private val externCallableNames: Set<String> = emptySet(),
|
||||
private val externBindingNames: Set<String> = emptySet(),
|
||||
private val lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||
) {
|
||||
private val useScopeSlots: Boolean = allowedScopeNames != null || scopeSlotNameSet != null
|
||||
@ -4508,7 +4509,7 @@ class BytecodeCompiler(
|
||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||
return CompiledValue(slot, resolved)
|
||||
}
|
||||
if (useScopeSlots && allowedScopeNames?.contains(name) == true) {
|
||||
if (useScopeSlots && isPreparedScopeName(name)) {
|
||||
scopeSlotIndexByName[name]?.let { slot ->
|
||||
val resolved = slotTypes[slot] ?: SlotType.UNKNOWN
|
||||
return CompiledValue(slot, resolved)
|
||||
@ -8073,7 +8074,7 @@ class BytecodeCompiler(
|
||||
slotInitClassByKey[ScopeSlotKey(scopeId, slotIndex)] = cls
|
||||
}
|
||||
}
|
||||
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) {
|
||||
if (allowLocalSlots && slotIndex != null && (stmt.actualExtern || !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = false))) {
|
||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||
declaredLocalKeys.add(key)
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
@ -8100,7 +8101,7 @@ class BytecodeCompiler(
|
||||
val scopeId = stmt.spec.scopeId ?: 0
|
||||
if (slotIndex != null) {
|
||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||
if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId)) {
|
||||
if (allowLocalSlots && !shouldUseScopeSlotFor(scopeId, stmt.spec.name, isDelegated = false)) {
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
localSlotInfoMap[key] = LocalSlotInfo(stmt.spec.name, isMutable = false, isDelegated = false)
|
||||
}
|
||||
@ -8117,7 +8118,7 @@ class BytecodeCompiler(
|
||||
is DelegatedVarDeclStatement -> {
|
||||
val slotIndex = stmt.slotIndex
|
||||
val scopeId = stmt.scopeId ?: 0
|
||||
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId)) {
|
||||
if (allowLocalSlots && slotIndex != null && !shouldUseScopeSlotFor(scopeId, stmt.name, isDelegated = true)) {
|
||||
val key = ScopeSlotKey(scopeId, slotIndex)
|
||||
declaredLocalKeys.add(key)
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
@ -8283,15 +8284,26 @@ class BytecodeCompiler(
|
||||
|
||||
private fun isModuleSlot(scopeId: Int, name: String?): Boolean {
|
||||
if (moduleScopeId != null && scopeId != moduleScopeId) return false
|
||||
val scopeNames = allowedScopeNames ?: scopeSlotNameSet
|
||||
if (scopeNames == null || name == null) return false
|
||||
return scopeNames.contains(name)
|
||||
return isPreparedScopeName(name)
|
||||
}
|
||||
|
||||
private fun shouldUseScopeSlotFor(scopeId: Int): Boolean {
|
||||
return useScopeSlots && moduleScopeId != null && scopeId == moduleScopeId
|
||||
}
|
||||
|
||||
private fun shouldUseScopeSlotFor(scopeId: Int, name: String, isDelegated: Boolean): Boolean {
|
||||
if (moduleScopeId == null || scopeId != moduleScopeId) return false
|
||||
if (isDelegated) return false
|
||||
if (externBindingNames.contains(name)) return true
|
||||
return useScopeSlots && isPreparedScopeName(name)
|
||||
}
|
||||
|
||||
private fun isPreparedScopeName(name: String?): Boolean {
|
||||
if (name == null) return false
|
||||
if (scopeSlotNameSet?.contains(name) == true) return true
|
||||
return allowedScopeNames?.contains(name) == true
|
||||
}
|
||||
|
||||
private fun collectLoopVarNames(stmt: Statement) {
|
||||
if (stmt is BytecodeStatement) {
|
||||
collectLoopVarNames(stmt.original)
|
||||
@ -8400,8 +8412,9 @@ class BytecodeCompiler(
|
||||
captureSlotKeys.add(key)
|
||||
return
|
||||
}
|
||||
val forceScopeSlot = shouldUseScopeSlotFor(scopeId, ref.name, ref.isDelegated)
|
||||
val isModuleSlot = if (ref.isDelegated) false else isModuleSlot(scopeId, ref.name)
|
||||
if (allowLocalSlots && !isModuleSlot) {
|
||||
if (allowLocalSlots && !isModuleSlot && !forceScopeSlot) {
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
localSlotInfoMap[key] = LocalSlotInfo(ref.name, ref.isMutable, ref.isDelegated)
|
||||
}
|
||||
@ -8448,8 +8461,9 @@ class BytecodeCompiler(
|
||||
}
|
||||
captureSlotKeys.add(key)
|
||||
} else {
|
||||
val forceScopeSlot = shouldUseScopeSlotFor(scopeId, target.name, target.isDelegated)
|
||||
val isModuleSlot = if (target.isDelegated) false else isModuleSlot(scopeId, target.name)
|
||||
if (allowLocalSlots && !isModuleSlot) {
|
||||
if (allowLocalSlots && !isModuleSlot && !forceScopeSlot) {
|
||||
if (!localSlotInfoMap.containsKey(key)) {
|
||||
localSlotInfoMap[key] = LocalSlotInfo(target.name, target.isMutable, target.isDelegated)
|
||||
}
|
||||
|
||||
@ -87,6 +87,7 @@ class BytecodeStatement private constructor(
|
||||
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||
externCallableNames: Set<String> = emptySet(),
|
||||
externBindingNames: Set<String> = emptySet(),
|
||||
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||
slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
|
||||
): Statement {
|
||||
@ -122,6 +123,7 @@ class BytecodeStatement private constructor(
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||
)
|
||||
val compiled = compiler.compileStatement(nameHint, statement)
|
||||
@ -236,6 +238,7 @@ class BytecodeStatement private constructor(
|
||||
stmt.name,
|
||||
stmt.isMutable,
|
||||
stmt.visibility,
|
||||
stmt.actualExtern,
|
||||
stmt.initializer?.let { unwrapDeep(it) },
|
||||
stmt.isTransient,
|
||||
stmt.typeDecl,
|
||||
|
||||
@ -2392,8 +2392,13 @@ class CmdDeclLocal(internal val constId: Int, internal val slot: Int) : Cmd() {
|
||||
?: error("DECL_LOCAL expects LocalDecl at $constId")
|
||||
if (slot < frame.fn.scopeSlotCount) {
|
||||
val target = frame.scopeTarget(slot)
|
||||
frame.ensureScopeSlot(target, slot)
|
||||
val value = frame.slotToObj(slot).byValueCopy()
|
||||
val index = frame.ensureScopeSlot(target, slot)
|
||||
val raw = target.getSlotRecord(index).value
|
||||
val value = when (raw) {
|
||||
is FrameSlotRef -> raw.read()
|
||||
is RecordSlotRef -> raw.read()
|
||||
else -> raw
|
||||
}.byValueCopy()
|
||||
target.updateSlotFor(
|
||||
decl.name,
|
||||
ObjRecord(
|
||||
@ -4557,7 +4562,7 @@ class CmdFrame(
|
||||
return getScopeSlotValueAtAddr(addrSlot)
|
||||
}
|
||||
|
||||
fun setAddrObj(addrSlot: Int, value: Obj) {
|
||||
suspend fun setAddrObj(addrSlot: Int, value: Obj) {
|
||||
setScopeSlotValueAtAddr(addrSlot, value)
|
||||
}
|
||||
|
||||
@ -4565,7 +4570,7 @@ class CmdFrame(
|
||||
return getScopeSlotValueAtAddr(addrSlot).toLong()
|
||||
}
|
||||
|
||||
fun setAddrInt(addrSlot: Int, value: Long) {
|
||||
suspend fun setAddrInt(addrSlot: Int, value: Long) {
|
||||
setScopeSlotValueAtAddr(addrSlot, ObjInt.of(value))
|
||||
}
|
||||
|
||||
@ -4573,7 +4578,7 @@ class CmdFrame(
|
||||
return getScopeSlotValueAtAddr(addrSlot).toDouble()
|
||||
}
|
||||
|
||||
fun setAddrReal(addrSlot: Int, value: Double) {
|
||||
suspend fun setAddrReal(addrSlot: Int, value: Double) {
|
||||
setScopeSlotValueAtAddr(addrSlot, ObjReal.of(value))
|
||||
}
|
||||
|
||||
@ -4581,7 +4586,7 @@ class CmdFrame(
|
||||
return getScopeSlotValueAtAddr(addrSlot).toBool()
|
||||
}
|
||||
|
||||
fun setAddrBool(addrSlot: Int, value: Boolean) {
|
||||
suspend fun setAddrBool(addrSlot: Int, value: Boolean) {
|
||||
setScopeSlotValueAtAddr(addrSlot, if (value) ObjTrue else ObjFalse)
|
||||
}
|
||||
|
||||
@ -4870,9 +4875,16 @@ class CmdFrame(
|
||||
return resolved.value
|
||||
}
|
||||
|
||||
private fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
||||
private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
||||
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
|
||||
val index = addrIndices[addrSlot]
|
||||
val record = target.getSlotRecord(index)
|
||||
val slotId = addrScopeSlots[addrSlot]
|
||||
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
||||
target.assign(record, name, value)
|
||||
return
|
||||
}
|
||||
target.setSlotValue(index, value)
|
||||
}
|
||||
|
||||
|
||||
@ -18,9 +18,11 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.BytecodeBodyProvider
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.bytecode.BytecodeStatement
|
||||
import net.sergeych.lyng.Visibility
|
||||
import net.sergeych.lyng.executeBytecodeWithSeed
|
||||
|
||||
@ -43,13 +45,25 @@ class ObjLazyDelegate(
|
||||
onNotFoundResult: (suspend () -> Obj?)?,
|
||||
): Obj {
|
||||
return when (name) {
|
||||
"bind" -> {
|
||||
val access = args.getOrNull(1)?.toString() ?: ""
|
||||
if (!access.endsWith("Val")) {
|
||||
scope.raiseIllegalArgument("lazy delegate can only be used with 'val'")
|
||||
}
|
||||
this
|
||||
}
|
||||
"getValue" -> {
|
||||
if (!calculated) {
|
||||
val callScope = capturedScope.createChildScope(capturedScope.pos, args = Arguments.EMPTY)
|
||||
cachedValue = if (builder is Statement) {
|
||||
executeBytecodeWithSeed(callScope, builder, "lazy delegate")
|
||||
val receiver = args.getOrNull(0) ?: ObjNull
|
||||
val callScope = scope.createChildScope(
|
||||
scope.pos,
|
||||
args = Arguments.EMPTY,
|
||||
newThisObj = receiver
|
||||
)
|
||||
cachedValue = if (builder is BytecodeStatement || builder is BytecodeBodyProvider) {
|
||||
executeBytecodeWithSeed(callScope, builder as Statement, "lazy delegate")
|
||||
} else {
|
||||
builder.callOn(callScope)
|
||||
builder.invoke(callScope, receiver, Arguments.EMPTY)
|
||||
}
|
||||
calculated = true
|
||||
}
|
||||
|
||||
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* 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 kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.bridge.bindGlobalVar
|
||||
import net.sergeych.lyng.bridge.globalBinder
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class GlobalPropertyCaptureRegressionTest {
|
||||
@Test
|
||||
fun externGlobalVarAssignmentInsideFunctionShouldCallBoundSetter() = runTest {
|
||||
val scope = Script.newScope()
|
||||
var x = 1.0
|
||||
|
||||
scope.eval(
|
||||
"""
|
||||
extern var X: Real
|
||||
|
||||
fun main() {
|
||||
X = X + 1.0
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
scope.globalBinder().bindGlobalVar(
|
||||
name = "X",
|
||||
get = { x },
|
||||
set = { x = it }
|
||||
)
|
||||
|
||||
scope.eval("main()")
|
||||
|
||||
assertEquals(2.0, x, "bound extern var should stay live inside function bodies")
|
||||
}
|
||||
}
|
||||
@ -415,24 +415,26 @@ fun with<T,R>(self: T, block: T.()->R): R {
|
||||
block(self)
|
||||
}
|
||||
|
||||
extern fun __builtinLazy(creator: Object): Object
|
||||
|
||||
/*
|
||||
Standard implementation of a lazy-initialized property delegate.
|
||||
The provided creator lambda is called once on the first access to compute the value.
|
||||
Can only be used with 'val' properties.
|
||||
*/
|
||||
class lazy<T,ThisRefType=Object>(creatorParam: ThisRefType.()->T) : Delegate<T,ThisRefType> {
|
||||
private val creator: ThisRefType.()->T = creatorParam
|
||||
private var value = Unset
|
||||
private val delegate: Delegate<T,ThisRefType> = __builtinLazy(creatorParam) as Delegate<T,ThisRefType>
|
||||
|
||||
override fun bind(name: String, access: DelegateAccess, thisRef: ThisRefType): Object {
|
||||
if (access != DelegateAccess.Val) throw "lazy delegate can only be used with 'val'"
|
||||
this
|
||||
delegate.bind(name, access, thisRef)
|
||||
}
|
||||
|
||||
override fun getValue(thisRef: ThisRefType, name: String): T {
|
||||
if (value == Unset)
|
||||
value = with(thisRef,creator)
|
||||
value as T
|
||||
delegate.getValue(thisRef, name) as T
|
||||
}
|
||||
|
||||
override fun setValue(thisRef: ThisRefType, name: String, newValue: T): void {
|
||||
delegate.setValue(thisRef, name, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user