Compare commits
No commits in common. "0973a6afebd0ea61fcdd27818a753332d8640cf3" and "f72cdfdf83178eaf71809e3bed9d627c882348a9" have entirely different histories.
0973a6afeb
...
f72cdfdf83
@ -1,15 +0,0 @@
|
|||||||
import lyng.time
|
|
||||||
|
|
||||||
val n = 700_000
|
|
||||||
|
|
||||||
fun tm<T>(block: ()->T): T {
|
|
||||||
val t = Instant()
|
|
||||||
block().also {
|
|
||||||
println("tm: ${Instant() - t}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val x = tm { List.fill(n) { it * 10 + 1 } }
|
|
||||||
val y = tm { List.fill(n, n + 10) { it * 10 + 1 } }
|
|
||||||
tm { x.add(-1) }
|
|
||||||
tm { y.add(-2) }
|
|
||||||
@ -434,57 +434,6 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun supportsFastFrameBinding(arguments: Arguments): Boolean {
|
|
||||||
if (arguments.named.isNotEmpty() || arguments.tailBlockMode) return false
|
|
||||||
return params.none { it.isEllipsis || it.defaultValue != null }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun assignToFrameFast(
|
|
||||||
scope: Scope,
|
|
||||||
arguments: Arguments = scope.args,
|
|
||||||
paramSlotPlan: Map<String, Int>,
|
|
||||||
frame: FrameAccess,
|
|
||||||
slotOffset: Int = 0
|
|
||||||
) {
|
|
||||||
if (!supportsFastFrameBinding(arguments)) {
|
|
||||||
scope.raiseIllegalState("fast frame binding is not supported for this call shape")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun slotFor(name: String): Int {
|
|
||||||
val full = paramSlotPlan[name] ?: scope.raiseIllegalState("parameter slot for '$name' is missing")
|
|
||||||
val slot = full - slotOffset
|
|
||||||
if (slot < 0) scope.raiseIllegalState("parameter slot for '$name' is out of range")
|
|
||||||
return slot
|
|
||||||
}
|
|
||||||
|
|
||||||
fun assign(slot: Int, value: Obj) {
|
|
||||||
when (value) {
|
|
||||||
is net.sergeych.lyng.obj.ObjInt -> frame.setInt(slot, value.value)
|
|
||||||
is net.sergeych.lyng.obj.ObjReal -> frame.setReal(slot, value.value)
|
|
||||||
is net.sergeych.lyng.obj.ObjBool -> frame.setBool(slot, value.value)
|
|
||||||
else -> frame.setObj(slot, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arguments.list.size > params.size) {
|
|
||||||
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
|
||||||
}
|
|
||||||
if (arguments.list.size < params.size) {
|
|
||||||
for (i in arguments.list.size until params.size) {
|
|
||||||
val a = params[i]
|
|
||||||
if (!a.type.isNullable) {
|
|
||||||
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in params.indices) {
|
|
||||||
val slot = slotFor(params[i].name)
|
|
||||||
val value = if (i < arguments.list.size) arguments.list[i] else ObjNull
|
|
||||||
assign(slot, value.byValueCopy())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single argument declaration descriptor.
|
* Single argument declaration descriptor.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -16,12 +16,4 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import net.sergeych.lyng.obj.Obj
|
interface BytecodeCallable
|
||||||
|
|
||||||
interface BytecodeCallable {
|
|
||||||
fun callOnFast(scope: Scope): Obj? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
interface BytecodeArgCallable {
|
|
||||||
fun callWithArgsFast(scope: Scope, args: Arguments): Obj? = null
|
|
||||||
}
|
|
||||||
|
|||||||
@ -28,7 +28,6 @@ internal suspend fun executeBytecodeWithSeed(scope: Scope, stmt: Statement, labe
|
|||||||
else -> null
|
else -> null
|
||||||
} ?: scope.raiseIllegalState("$label requires bytecode statement")
|
} ?: scope.raiseIllegalState("$label requires bytecode statement")
|
||||||
scope.pos = bytecode.pos
|
scope.pos = bytecode.pos
|
||||||
bytecode.callOnFast(scope)?.let { return it }
|
|
||||||
return CmdVm().execute(bytecode.bytecodeFunction(), scope, scope.args) { frame, _ ->
|
return CmdVm().execute(bytecode.bytecodeFunction(), scope, scope.args) { frame, _ ->
|
||||||
seedFrameLocalsFromScope(frame, scope)
|
seedFrameLocalsFromScope(frame, scope)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,30 +20,5 @@ package net.sergeych.lyng
|
|||||||
* Compile-time call metadata for known functions. Used to select lambda receiver semantics.
|
* Compile-time call metadata for known functions. Used to select lambda receiver semantics.
|
||||||
*/
|
*/
|
||||||
data class CallSignature(
|
data class CallSignature(
|
||||||
val tailBlockReceiverType: String? = null,
|
val tailBlockReceiverType: String? = null
|
||||||
val inlineHigherOrder: HigherOrderInline? = null
|
)
|
||||||
) {
|
|
||||||
data class HigherOrderInline(
|
|
||||||
val kind: Kind,
|
|
||||||
val result: ResultMode,
|
|
||||||
val argCount: Int = 1,
|
|
||||||
val lambdaArgIndex: Int = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
enum class Kind {
|
|
||||||
UNARY_ARGUMENT,
|
|
||||||
RECEIVER,
|
|
||||||
ITERABLE,
|
|
||||||
MAP_GET_OR_PUT
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class ResultMode {
|
|
||||||
BLOCK_RESULT,
|
|
||||||
RETURN_RECEIVER,
|
|
||||||
FOR_EACH,
|
|
||||||
MAP,
|
|
||||||
FILTER,
|
|
||||||
MAP_NOT_NULL,
|
|
||||||
ASSOCIATE_BY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -187,7 +187,6 @@ class Compiler(
|
|||||||
private val callableReturnTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
private val callableReturnTypeByScopeId: MutableMap<Int, MutableMap<Int, ObjClass>> = mutableMapOf()
|
||||||
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||||
private val callableReturnTypeDeclByName: MutableMap<String, TypeDecl> = mutableMapOf()
|
private val callableReturnTypeDeclByName: MutableMap<String, TypeDecl> = mutableMapOf()
|
||||||
private val callSignatureByName: MutableMap<String, CallSignature> = mutableMapOf()
|
|
||||||
private val lambdaReturnTypeByRef: MutableMap<ObjRef, ObjClass> = mutableMapOf()
|
private val lambdaReturnTypeByRef: MutableMap<ObjRef, ObjClass> = mutableMapOf()
|
||||||
private val exactLambdaRefByScopeId: MutableMap<Int, MutableMap<Int, LambdaFnRef>> = mutableMapOf()
|
private val exactLambdaRefByScopeId: MutableMap<Int, MutableMap<Int, LambdaFnRef>> = mutableMapOf()
|
||||||
private val lambdaCaptureEntriesByRef: MutableMap<ValueFnRef, List<net.sergeych.lyng.bytecode.LambdaCaptureEntry>> =
|
private val lambdaCaptureEntriesByRef: MutableMap<ValueFnRef, List<net.sergeych.lyng.bytecode.LambdaCaptureEntry>> =
|
||||||
@ -266,11 +265,6 @@ class Compiler(
|
|||||||
if (plan.slots.containsKey(name)) continue
|
if (plan.slots.containsKey(name)) continue
|
||||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||||
scopeSeedNames.add(name)
|
scopeSeedNames.add(name)
|
||||||
record.callSignature?.let { signature ->
|
|
||||||
if (!callSignatureByName.containsKey(name)) {
|
|
||||||
callSignatureByName[name] = signature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (record.typeDecl != null && nameTypeDecl[name] == null) {
|
if (record.typeDecl != null && nameTypeDecl[name] == null) {
|
||||||
nameTypeDecl[name] = record.typeDecl
|
nameTypeDecl[name] = record.typeDecl
|
||||||
if (nameObjClass[name] == null) {
|
if (nameObjClass[name] == null) {
|
||||||
@ -297,11 +291,6 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
scopeSeedNames.add(getterName)
|
scopeSeedNames.add(getterName)
|
||||||
}
|
}
|
||||||
record.callSignature?.let { signature ->
|
|
||||||
if (!callSignatureByName.containsKey(getterName)) {
|
|
||||||
callSignatureByName[getterName] = signature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val prop = record.value as? ObjProperty
|
val prop = record.value as? ObjProperty
|
||||||
if (prop?.setter != null) {
|
if (prop?.setter != null) {
|
||||||
val setterName = extensionPropertySetterName(cls.className, name)
|
val setterName = extensionPropertySetterName(cls.className, name)
|
||||||
@ -327,11 +316,6 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
scopeSeedNames.add(callableName)
|
scopeSeedNames.add(callableName)
|
||||||
}
|
}
|
||||||
record.callSignature?.let { signature ->
|
|
||||||
if (!callSignatureByName.containsKey(callableName)) {
|
|
||||||
callSignatureByName[callableName] = signature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -363,11 +347,6 @@ class Compiler(
|
|||||||
record.type == ObjRecord.Type.Delegated
|
record.type == ObjRecord.Type.Delegated
|
||||||
)
|
)
|
||||||
scopeSeedNames.add(name)
|
scopeSeedNames.add(name)
|
||||||
record.callSignature?.let { signature ->
|
|
||||||
if (!callSignatureByName.containsKey(name)) {
|
|
||||||
callSignatureByName[name] = signature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (record.typeDecl != null && nameTypeDecl[name] == null) {
|
if (record.typeDecl != null && nameTypeDecl[name] == null) {
|
||||||
nameTypeDecl[name] = record.typeDecl
|
nameTypeDecl[name] = record.typeDecl
|
||||||
if (nameObjClass[name] == null) {
|
if (nameObjClass[name] == null) {
|
||||||
@ -783,29 +762,6 @@ class Compiler(
|
|||||||
?: importManager.rootScope.getLocalRecordDirect(name)?.callSignature
|
?: importManager.rootScope.getLocalRecordDirect(name)?.callSignature
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun declaredCallSignature(name: String, extTypeName: String?, actualExtern: Boolean): CallSignature? {
|
|
||||||
val imported = if (actualExtern) {
|
|
||||||
importManager.rootScope.getLocalRecordDirect(name)?.callSignature
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
val inferred = when {
|
|
||||||
extTypeName == "Iterable" && name == "filter" -> CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.ITERABLE,
|
|
||||||
result = CallSignature.ResultMode.FILTER
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
return when {
|
|
||||||
imported == null -> inferred
|
|
||||||
inferred == null -> imported
|
|
||||||
imported.inlineHigherOrder != null -> imported
|
|
||||||
else -> imported.copy(inlineHigherOrder = inferred.inlineHigherOrder)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal data class MemberIds(val fieldId: Int?, val methodId: Int?)
|
internal data class MemberIds(val fieldId: Int?, val methodId: Int?)
|
||||||
|
|
||||||
private fun resolveMemberIds(name: String, pos: Pos, qualifier: String? = null): MemberIds {
|
private fun resolveMemberIds(name: String, pos: Pos, qualifier: String? = null): MemberIds {
|
||||||
@ -1473,11 +1429,6 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun seedImportTypeMetadata(name: String, record: ObjRecord) {
|
private fun seedImportTypeMetadata(name: String, record: ObjRecord) {
|
||||||
record.callSignature?.let { signature ->
|
|
||||||
if (!callSignatureByName.containsKey(name)) {
|
|
||||||
callSignatureByName[name] = signature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (record.typeDecl != null && nameTypeDecl[name] == null) {
|
if (record.typeDecl != null && nameTypeDecl[name] == null) {
|
||||||
nameTypeDecl[name] = record.typeDecl
|
nameTypeDecl[name] = record.typeDecl
|
||||||
}
|
}
|
||||||
@ -1948,12 +1899,11 @@ class Compiler(
|
|||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
knownClassNames = knownClassNamesForBytecode(),
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = knownObjectNamesForBytecode(),
|
knownObjectNames = objectDeclNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
callSignatureByName = callSignatureByName,
|
|
||||||
externBindingNames = externBindingNames,
|
externBindingNames = externBindingNames,
|
||||||
preparedModuleBindingNames = importBindings.keys,
|
preparedModuleBindingNames = importBindings.keys,
|
||||||
scopeRefPosByName = moduleReferencePosByName,
|
scopeRefPosByName = moduleReferencePosByName,
|
||||||
@ -2277,23 +2227,6 @@ class Compiler(
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun knownObjectNamesForBytecode(): Set<String> {
|
|
||||||
val result = LinkedHashSet<String>()
|
|
||||||
fun addScope(scope: Scope?) {
|
|
||||||
if (scope == null) return
|
|
||||||
for ((name, rec) in scope.objects) {
|
|
||||||
if (rec.value is ObjInstance) result.add(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addScope(seedScope)
|
|
||||||
addScope(importManager.rootScope)
|
|
||||||
for (module in importedModules) {
|
|
||||||
addScope(module.scope)
|
|
||||||
}
|
|
||||||
result.addAll(objectDeclNames)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun wrapBytecode(stmt: Statement): Statement {
|
private fun wrapBytecode(stmt: Statement): Statement {
|
||||||
if (codeContexts.lastOrNull() is CodeContext.Module) return stmt
|
if (codeContexts.lastOrNull() is CodeContext.Module) return stmt
|
||||||
if (codeContexts.lastOrNull() is CodeContext.ClassBody) return stmt
|
if (codeContexts.lastOrNull() is CodeContext.ClassBody) return stmt
|
||||||
@ -2322,12 +2255,11 @@ class Compiler(
|
|||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
knownClassNames = knownClassNamesForBytecode(),
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = knownObjectNamesForBytecode(),
|
knownObjectNames = objectDeclNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
callSignatureByName = callSignatureByName,
|
|
||||||
externCallableNames = externCallableNames,
|
externCallableNames = externCallableNames,
|
||||||
externBindingNames = externBindingNames,
|
externBindingNames = externBindingNames,
|
||||||
preparedModuleBindingNames = importBindings.keys,
|
preparedModuleBindingNames = importBindings.keys,
|
||||||
@ -2357,12 +2289,11 @@ class Compiler(
|
|||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
knownClassNames = knownClassNamesForBytecode(),
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = knownObjectNamesForBytecode(),
|
knownObjectNames = objectDeclNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
callSignatureByName = callSignatureByName,
|
|
||||||
externCallableNames = externCallableNames,
|
externCallableNames = externCallableNames,
|
||||||
externBindingNames = externBindingNames,
|
externBindingNames = externBindingNames,
|
||||||
preparedModuleBindingNames = importBindings.keys,
|
preparedModuleBindingNames = importBindings.keys,
|
||||||
@ -2417,12 +2348,11 @@ class Compiler(
|
|||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownNames,
|
knownNameObjClass = knownNames,
|
||||||
knownClassNames = knownClassNamesForBytecode(),
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = knownObjectNamesForBytecode(),
|
knownObjectNames = objectDeclNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
callSignatureByName = callSignatureByName,
|
|
||||||
externCallableNames = externCallableNames,
|
externCallableNames = externCallableNames,
|
||||||
externBindingNames = externBindingNames,
|
externBindingNames = externBindingNames,
|
||||||
preparedModuleBindingNames = importBindings.keys,
|
preparedModuleBindingNames = importBindings.keys,
|
||||||
@ -3615,90 +3545,14 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
val bytecodeFn = (fnStatements as? BytecodeStatement)?.bytecodeFunction()
|
val bytecodeFn = (fnStatements as? BytecodeStatement)?.bytecodeFunction()
|
||||||
val inlineBodyRef = argsDeclaration?.let { null } ?: extractInlineLambdaBodyRef(body)
|
val inlineBodyRef = argsDeclaration?.let { null } ?: extractInlineLambdaBodyRef(body)
|
||||||
val supportsDirectInvokeFastPath = bytecodeFn != null &&
|
|
||||||
bytecodeFn.scopeSlotCount == 0 &&
|
|
||||||
expectedReceiverType == null &&
|
|
||||||
!wrapAsExtensionCallable &&
|
|
||||||
!containsDelegatedRefs(body)
|
|
||||||
val ref = LambdaFnRef(
|
val ref = LambdaFnRef(
|
||||||
valueFn = { closureScope ->
|
valueFn = { closureScope ->
|
||||||
val captureRecords = closureScope.captureRecords
|
val captureRecords = closureScope.captureRecords
|
||||||
val stmt = object : Statement(), BytecodeBodyProvider, BytecodeCallable {
|
val stmt = object : Statement(), BytecodeBodyProvider {
|
||||||
override val pos: Pos = fnStatements.pos
|
override val pos: Pos = fnStatements.pos
|
||||||
|
|
||||||
override fun bytecodeBody(): BytecodeStatement? = fnStatements as? BytecodeStatement
|
override fun bytecodeBody(): BytecodeStatement? = fnStatements as? BytecodeStatement
|
||||||
|
|
||||||
override fun callOnFast(scope: Scope): Obj? {
|
|
||||||
val context = scope.applyClosureForBytecode(closureScope, preferredThisType = expectedReceiverType).also {
|
|
||||||
it.args = scope.args
|
|
||||||
}
|
|
||||||
if (captureSlots.isNotEmpty()) {
|
|
||||||
if (captureRecords != null) {
|
|
||||||
context.captureRecords = captureRecords
|
|
||||||
context.captureNames = captureSlots.map { it.name }
|
|
||||||
} else {
|
|
||||||
val resolvedRecords = ArrayList<ObjRecord>(captureSlots.size)
|
|
||||||
val resolvedNames = ArrayList<String>(captureSlots.size)
|
|
||||||
for (capture in captureSlots) {
|
|
||||||
val rec = resolveStableCaptureRecord(
|
|
||||||
closureScope,
|
|
||||||
capture.name,
|
|
||||||
context.currentClassCtx
|
|
||||||
) ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found")
|
|
||||||
resolvedRecords.add(freezeImmutableCaptureRecord(rec))
|
|
||||||
resolvedNames.add(capture.name)
|
|
||||||
}
|
|
||||||
context.captureRecords = resolvedRecords
|
|
||||||
context.captureNames = resolvedNames
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val bytecodeBody = fnStatements as? BytecodeStatement ?: return null
|
|
||||||
val bytecodeFn = bytecodeBody.bytecodeFunction()
|
|
||||||
if (!supportsDirectInvokeFastPath || !bytecodeFn.fastOnly) return null
|
|
||||||
val fastPreboundNames = if (argsDeclaration == null) {
|
|
||||||
setOf("it")
|
|
||||||
} else {
|
|
||||||
argsDeclaration.params.mapTo(LinkedHashSet()) { it.name }
|
|
||||||
}
|
|
||||||
val declaredNames = bytecodeFn.constants
|
|
||||||
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
|
||||||
.mapTo(mutableSetOf()) { it.name }
|
|
||||||
if (!canFastSeedUndeclaredLocals(bytecodeFn, declaredNames, fastPreboundNames)) return null
|
|
||||||
if (argsDeclaration != null && !argsDeclaration.supportsFastFrameBinding(scope.args)) return null
|
|
||||||
val slotPlan = bytecodeFn.localSlotPlanByName()
|
|
||||||
val binder: (net.sergeych.lyng.bytecode.CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
|
||||||
if (argsDeclaration == null) {
|
|
||||||
val l = arguments.list
|
|
||||||
val itValue: Obj = when (l.size) {
|
|
||||||
0 -> ObjVoid
|
|
||||||
1 -> l[0]
|
|
||||||
else -> ObjList(l.toMutableList())
|
|
||||||
}
|
|
||||||
val itSlot = slotPlan["it"]
|
|
||||||
if (itSlot != null) {
|
|
||||||
when (itValue) {
|
|
||||||
is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
|
|
||||||
is ObjReal -> frame.frame.setReal(itSlot, itValue.value)
|
|
||||||
is ObjBool -> frame.frame.setBool(itSlot, itValue.value)
|
|
||||||
else -> frame.frame.setObj(itSlot, itValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
argsDeclaration.assignToFrameFast(
|
|
||||||
context,
|
|
||||||
arguments,
|
|
||||||
slotPlan,
|
|
||||||
frame.frame
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return try {
|
|
||||||
net.sergeych.lyng.bytecode.CmdVm().executeFastOnlyNoSuspend(bytecodeFn, context, scope.args, binder)
|
|
||||||
} catch (e: ReturnException) {
|
|
||||||
if (e.label == null || returnLabels.contains(e.label)) e.result else throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
val context = scope.applyClosureForBytecode(closureScope, preferredThisType = expectedReceiverType).also {
|
val context = scope.applyClosureForBytecode(closureScope, preferredThisType = expectedReceiverType).also {
|
||||||
it.args = scope.args
|
it.args = scope.args
|
||||||
@ -3774,7 +3628,6 @@ class Compiler(
|
|||||||
captureEntries = captureEntries,
|
captureEntries = captureEntries,
|
||||||
inferredReturnClass = returnClass,
|
inferredReturnClass = returnClass,
|
||||||
inlineBodyRef = inlineBodyRef,
|
inlineBodyRef = inlineBodyRef,
|
||||||
supportsDirectInvokeFastPath = supportsDirectInvokeFastPath,
|
|
||||||
preferredThisType = expectedReceiverType,
|
preferredThisType = expectedReceiverType,
|
||||||
wrapAsExtensionCallable = wrapAsExtensionCallable,
|
wrapAsExtensionCallable = wrapAsExtensionCallable,
|
||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
@ -9278,7 +9131,7 @@ class Compiler(
|
|||||||
val extensionWrapperName = extTypeName?.let { extensionCallableName(it, name) }
|
val extensionWrapperName = extTypeName?.let { extensionCallableName(it, name) }
|
||||||
val classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
val classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
||||||
var memberMethodId = if (extTypeName == null) classCtx?.memberMethodIds?.get(name) else null
|
var memberMethodId = if (extTypeName == null) classCtx?.memberMethodIds?.get(name) else null
|
||||||
val externCallSignature = declaredCallSignature(name, extTypeName, actualExtern)
|
val externCallSignature = if (actualExtern) importManager.rootScope.getLocalRecordDirect(name)?.callSignature else null
|
||||||
|
|
||||||
val declKind = if (parentContext is CodeContext.ClassBody) SymbolKind.MEMBER else SymbolKind.FUNCTION
|
val declKind = if (parentContext is CodeContext.ClassBody) SymbolKind.MEMBER else SymbolKind.FUNCTION
|
||||||
resolutionSink?.declareSymbol(name, declKind, isMutable = false, pos = nameStartPos, isOverride = isOverride)
|
resolutionSink?.declareSymbol(name, declKind, isMutable = false, pos = nameStartPos, isOverride = isOverride)
|
||||||
@ -9300,9 +9153,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
if (extensionWrapperName != null) {
|
if (extensionWrapperName != null) {
|
||||||
declareLocalName(extensionWrapperName, isMutable = false)
|
declareLocalName(extensionWrapperName, isMutable = false)
|
||||||
externCallSignature?.let { callSignatureByName[extensionWrapperName] = it }
|
|
||||||
}
|
}
|
||||||
externCallSignature?.let { callSignatureByName[name] = it }
|
|
||||||
if (actualExtern && declKind != SymbolKind.MEMBER) {
|
if (actualExtern && declKind != SymbolKind.MEMBER) {
|
||||||
externCallableNames.add(name)
|
externCallableNames.add(name)
|
||||||
}
|
}
|
||||||
@ -9585,8 +9436,9 @@ class Compiler(
|
|||||||
val fnBody = object : Statement(), BytecodeBodyProvider {
|
val fnBody = object : Statement(), BytecodeBodyProvider {
|
||||||
override val pos: Pos = start
|
override val pos: Pos = start
|
||||||
override fun bytecodeBody(): BytecodeStatement? = fnStatements as? BytecodeStatement
|
override fun bytecodeBody(): BytecodeStatement? = fnStatements as? BytecodeStatement
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
scope.pos = start
|
||||||
|
|
||||||
// restore closure where the function was defined, and making a copy of it
|
// restore closure where the function was defined, and making a copy of it
|
||||||
// for local space. If there is no closure, we are in, say, class context where
|
// for local space. If there is no closure, we are in, say, class context where
|
||||||
// the closure is in the class initialization and we needn't more:
|
// the closure is in the class initialization and we needn't more:
|
||||||
@ -9595,7 +9447,6 @@ class Compiler(
|
|||||||
it.args = scope.args
|
it.args = scope.args
|
||||||
}
|
}
|
||||||
} ?: scope
|
} ?: scope
|
||||||
context.pos = start
|
|
||||||
|
|
||||||
// Capacity hint: parameters + declared locals + small overhead
|
// Capacity hint: parameters + declared locals + small overhead
|
||||||
val capacityHint = paramNames.size + fnLocalDecls + 4
|
val capacityHint = paramNames.size + fnLocalDecls + 4
|
||||||
|
|||||||
@ -118,14 +118,7 @@ internal suspend fun executeFunctionDecl(
|
|||||||
scope.addExtension(
|
scope.addExtension(
|
||||||
type,
|
type,
|
||||||
spec.name,
|
spec.name,
|
||||||
ObjRecord(
|
ObjRecord(ObjUnset, isMutable = false, visibility = spec.visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply {
|
||||||
ObjUnset,
|
|
||||||
isMutable = false,
|
|
||||||
visibility = spec.visibility,
|
|
||||||
declaringClass = null,
|
|
||||||
type = ObjRecord.Type.Delegated,
|
|
||||||
callSignature = spec.externCallSignature
|
|
||||||
).apply {
|
|
||||||
delegate = finalDelegate
|
delegate = finalDelegate
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -142,8 +135,7 @@ internal suspend fun executeFunctionDecl(
|
|||||||
null,
|
null,
|
||||||
spec.startPos,
|
spec.startPos,
|
||||||
isTransient = spec.isTransient,
|
isTransient = spec.isTransient,
|
||||||
type = ObjRecord.Type.Delegated,
|
type = ObjRecord.Type.Delegated
|
||||||
callSignature = spec.externCallSignature
|
|
||||||
).apply {
|
).apply {
|
||||||
delegate = finalDelegate
|
delegate = finalDelegate
|
||||||
}
|
}
|
||||||
@ -212,7 +204,6 @@ internal suspend fun executeFunctionDecl(
|
|||||||
visibility = spec.visibility,
|
visibility = spec.visibility,
|
||||||
pos = spec.startPos,
|
pos = spec.startPos,
|
||||||
type = ObjRecord.Type.Fun,
|
type = ObjRecord.Type.Fun,
|
||||||
callSignature = spec.externCallSignature,
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
scope.addExtension(
|
scope.addExtension(
|
||||||
@ -224,7 +215,6 @@ internal suspend fun executeFunctionDecl(
|
|||||||
visibility = spec.visibility,
|
visibility = spec.visibility,
|
||||||
declaringClass = null,
|
declaringClass = null,
|
||||||
type = ObjRecord.Type.Fun,
|
type = ObjRecord.Type.Fun,
|
||||||
callSignature = spec.externCallSignature,
|
|
||||||
typeDecl = spec.typeDecl
|
typeDecl = spec.typeDecl
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -237,7 +227,6 @@ internal suspend fun executeFunctionDecl(
|
|||||||
wrapper,
|
wrapper,
|
||||||
spec.visibility,
|
spec.visibility,
|
||||||
recordType = ObjRecord.Type.Fun,
|
recordType = ObjRecord.Type.Fun,
|
||||||
callSignature = spec.externCallSignature,
|
|
||||||
typeDecl = spec.typeDecl
|
typeDecl = spec.typeDecl
|
||||||
)
|
)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
@ -256,8 +245,7 @@ internal suspend fun executeFunctionDecl(
|
|||||||
isOverride = spec.isOverride,
|
isOverride = spec.isOverride,
|
||||||
type = ObjRecord.Type.Fun,
|
type = ObjRecord.Type.Fun,
|
||||||
methodId = spec.memberMethodId,
|
methodId = spec.memberMethodId,
|
||||||
typeDecl = spec.typeDecl,
|
typeDecl = spec.typeDecl
|
||||||
callSignature = spec.externCallSignature
|
|
||||||
)
|
)
|
||||||
val memberValue = cls.members[spec.name]?.value ?: compiledFnBody
|
val memberValue = cls.members[spec.name]?.value ?: compiledFnBody
|
||||||
scope.addItem(
|
scope.addItem(
|
||||||
|
|||||||
@ -66,11 +66,7 @@ internal class ScopeBridge(internal val scope: Scope) : ScopeFacade {
|
|||||||
override fun raiseIllegalState(message: String): Nothing = scope.raiseIllegalState(message)
|
override fun raiseIllegalState(message: String): Nothing = scope.raiseIllegalState(message)
|
||||||
override fun raiseNotImplemented(what: String): Nothing = scope.raiseNotImplemented(what)
|
override fun raiseNotImplemented(what: String): Nothing = scope.raiseNotImplemented(what)
|
||||||
override suspend fun call(callee: Obj, args: Arguments, newThisObj: Obj?): Obj {
|
override suspend fun call(callee: Obj, args: Arguments, newThisObj: Obj?): Obj {
|
||||||
if (newThisObj == null) {
|
return callee.callOn(scope.createChildScope(scope.pos, args = args, newThisObj = newThisObj))
|
||||||
(callee as? BytecodeArgCallable)?.callWithArgsFast(scope, args)?.let { return it }
|
|
||||||
}
|
|
||||||
val child = scope.createChildScope(scope.pos, args = args, newThisObj = newThisObj)
|
|
||||||
return (callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
|
||||||
}
|
}
|
||||||
override suspend fun toStringOf(obj: Obj, forInspect: Boolean): ObjString = obj.toString(scope, forInspect)
|
override suspend fun toStringOf(obj: Obj, forInspect: Boolean): ObjString = obj.toString(scope, forInspect)
|
||||||
override suspend fun inspect(obj: Obj): String = obj.inspect(scope)
|
override suspend fun inspect(obj: Obj): String = obj.inspect(scope)
|
||||||
|
|||||||
@ -108,7 +108,11 @@ class Script(
|
|||||||
seedImportBindings(scope, seedScope)
|
seedImportBindings(scope, seedScope)
|
||||||
}
|
}
|
||||||
if (moduleSlotPlan.isNotEmpty()) {
|
if (moduleSlotPlan.isNotEmpty()) {
|
||||||
installModuleSlotPlan(scope)
|
scope.applySlotPlan(moduleSlotPlan)
|
||||||
|
for (name in moduleSlotPlan.keys) {
|
||||||
|
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
|
||||||
|
scope.updateSlotFor(name, record)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,26 +120,12 @@ class Script(
|
|||||||
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
if (importBindings.isEmpty() && importedModules.isEmpty()) return
|
||||||
seedImportBindings(scope, seedScope)
|
seedImportBindings(scope, seedScope)
|
||||||
if (moduleSlotPlan.isNotEmpty()) {
|
if (moduleSlotPlan.isNotEmpty()) {
|
||||||
installModuleSlotPlan(scope)
|
scope.applySlotPlan(moduleSlotPlan)
|
||||||
}
|
for (name in moduleSlotPlan.keys) {
|
||||||
}
|
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
|
||||||
|
scope.updateSlotFor(name, record)
|
||||||
private fun installModuleSlotPlan(scope: Scope) {
|
|
||||||
for ((name, index) in moduleSlotPlan) {
|
|
||||||
if (scope.getSlotIndexOf(name) != null) continue
|
|
||||||
if (scope.hasSlotPlanConflict(mapOf(name to index))) {
|
|
||||||
val record = scope.objects[name]
|
|
||||||
?: scope.localBindings[name]
|
|
||||||
?: ObjRecord(ObjUnset, isMutable = true)
|
|
||||||
scope.allocateSlotFor(name, record)
|
|
||||||
} else {
|
|
||||||
scope.applySlotPlan(mapOf(name to index))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (name in moduleSlotPlan.keys) {
|
|
||||||
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
|
|
||||||
scope.updateSlotFor(name, record)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun seedModuleLocals(
|
private suspend fun seedModuleLocals(
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -40,7 +40,6 @@ sealed class BytecodeConst {
|
|||||||
val captureNames: List<String>,
|
val captureNames: List<String>,
|
||||||
val paramSlotPlan: Map<String, Int>,
|
val paramSlotPlan: Map<String, Int>,
|
||||||
val argsDeclaration: ArgsDeclaration?,
|
val argsDeclaration: ArgsDeclaration?,
|
||||||
val supportsDirectInvokeFastPath: Boolean,
|
|
||||||
val preferredThisType: String?,
|
val preferredThisType: String?,
|
||||||
val wrapAsExtensionCallable: Boolean,
|
val wrapAsExtensionCallable: Boolean,
|
||||||
val returnLabels: Set<String>,
|
val returnLabels: Set<String>,
|
||||||
|
|||||||
@ -23,36 +23,19 @@ import net.sergeych.lyng.obj.*
|
|||||||
class BytecodeStatement private constructor(
|
class BytecodeStatement private constructor(
|
||||||
val original: Statement,
|
val original: Statement,
|
||||||
private val function: CmdFunction,
|
private val function: CmdFunction,
|
||||||
) : Statement(original.isStaticConst, original.isConst, original.returnType), BytecodeCallable {
|
) : Statement(original.isStaticConst, original.isConst, original.returnType) {
|
||||||
override val pos: Pos = original.pos
|
override val pos: Pos = original.pos
|
||||||
|
|
||||||
private val declaredLocalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) {
|
|
||||||
function.constants
|
|
||||||
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
|
||||||
.mapTo(mutableSetOf()) { it.name }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun callOnFast(scope: Scope): Obj? {
|
|
||||||
scope.pos = pos
|
|
||||||
if (!function.fastOnly) return null
|
|
||||||
if (!canFastSeedUndeclaredLocals(function, declaredLocalNames, emptySet())) return null
|
|
||||||
val binder: (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
|
||||||
if (!trySeedFrameLocalsFromScopeFast(frame, scope)) {
|
|
||||||
scope.raiseIllegalState("fast local seeding is not available")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return CmdVm().executeFastOnlyNoSuspend(function, scope, scope.args, binder)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
scope.pos = pos
|
scope.pos = pos
|
||||||
val fastResult = callOnFast(scope)
|
val declaredNames = function.constants
|
||||||
if (fastResult != null) return fastResult
|
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
||||||
|
.mapTo(mutableSetOf()) { it.name }
|
||||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
||||||
val localNames = frame.fn.localSlotNames
|
val localNames = frame.fn.localSlotNames
|
||||||
for (i in localNames.indices) {
|
for (i in localNames.indices) {
|
||||||
val name = localNames[i] ?: continue
|
val name = localNames[i] ?: continue
|
||||||
if (declaredLocalNames.contains(name)) continue
|
if (declaredNames.contains(name)) continue
|
||||||
val slotType = frame.getLocalSlotTypeCode(i)
|
val slotType = frame.getLocalSlotTypeCode(i)
|
||||||
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
|
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
|
||||||
continue
|
continue
|
||||||
@ -106,7 +89,6 @@ 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(),
|
||||||
callSignatureByName: Map<String, CallSignature> = emptyMap(),
|
|
||||||
externCallableNames: Set<String> = emptySet(),
|
externCallableNames: Set<String> = emptySet(),
|
||||||
externBindingNames: Set<String> = emptySet(),
|
externBindingNames: Set<String> = emptySet(),
|
||||||
preparedModuleBindingNames: Set<String> = emptySet(),
|
preparedModuleBindingNames: Set<String> = emptySet(),
|
||||||
@ -147,7 +129,6 @@ class BytecodeStatement private constructor(
|
|||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName,
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
callSignatureByName = callSignatureByName,
|
|
||||||
externCallableNames = externCallableNames,
|
externCallableNames = externCallableNames,
|
||||||
externBindingNames = externBindingNames,
|
externBindingNames = externBindingNames,
|
||||||
preparedModuleBindingNames = preparedModuleBindingNames,
|
preparedModuleBindingNames = preparedModuleBindingNames,
|
||||||
|
|||||||
@ -112,7 +112,6 @@ class CmdBuilder {
|
|||||||
}
|
}
|
||||||
cmds.add(createCmd(ins.op, operands, scopeSlotCount, localSlotCaptures))
|
cmds.add(createCmd(ins.op, operands, scopeSlotCount, localSlotCaptures))
|
||||||
}
|
}
|
||||||
val cmdArray = cmds.toTypedArray()
|
|
||||||
return CmdFunction(
|
return CmdFunction(
|
||||||
name = name,
|
name = name,
|
||||||
localCount = localCount,
|
localCount = localCount,
|
||||||
@ -129,9 +128,8 @@ class CmdBuilder {
|
|||||||
localSlotDelegated = localSlotDelegated,
|
localSlotDelegated = localSlotDelegated,
|
||||||
localSlotCaptures = localSlotCaptures,
|
localSlotCaptures = localSlotCaptures,
|
||||||
constants = constPool.toList(),
|
constants = constPool.toList(),
|
||||||
cmds = cmdArray,
|
cmds = cmds.toTypedArray(),
|
||||||
posByIp = posByInstr.toTypedArray(),
|
posByIp = posByInstr.toTypedArray()
|
||||||
fastOnly = computeFastOnlyBytecode(scopeSlotCount, cmdArray)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,10 +237,6 @@ class CmdBuilder {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.LIST_IOTA_INT ->
|
Opcode.LIST_IOTA_INT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.LIST_NEW_INT_CAP ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
|
||||||
Opcode.LIST_FILL_INT_CAP ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
|
||||||
Opcode.MAKE_RANGE ->
|
Opcode.MAKE_RANGE ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.LIST_LITERAL ->
|
Opcode.LIST_LITERAL ->
|
||||||
@ -848,8 +842,6 @@ class CmdBuilder {
|
|||||||
Opcode.LIST_NEW_INT -> CmdListNewInt(operands[0], operands[1])
|
Opcode.LIST_NEW_INT -> CmdListNewInt(operands[0], operands[1])
|
||||||
Opcode.LIST_FILL_INT -> CmdListFillInt(operands[0], operands[1], operands[2])
|
Opcode.LIST_FILL_INT -> CmdListFillInt(operands[0], operands[1], operands[2])
|
||||||
Opcode.LIST_IOTA_INT -> CmdListIotaInt(operands[0], operands[1])
|
Opcode.LIST_IOTA_INT -> CmdListIotaInt(operands[0], operands[1])
|
||||||
Opcode.LIST_NEW_INT_CAP -> CmdListNewIntCap(operands[0], operands[1], operands[2])
|
|
||||||
Opcode.LIST_FILL_INT_CAP -> CmdListFillIntCap(operands[0], operands[1], operands[2], operands[3])
|
|
||||||
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
|
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])
|
||||||
|
|||||||
@ -498,8 +498,6 @@ object CmdDisassembler {
|
|||||||
is CmdListNewInt -> Opcode.LIST_NEW_INT to intArrayOf(cmd.sizeSlot, cmd.dst)
|
is CmdListNewInt -> Opcode.LIST_NEW_INT to intArrayOf(cmd.sizeSlot, cmd.dst)
|
||||||
is CmdListFillInt -> Opcode.LIST_FILL_INT to intArrayOf(cmd.sizeSlot, cmd.callableSlot, cmd.dst)
|
is CmdListFillInt -> Opcode.LIST_FILL_INT to intArrayOf(cmd.sizeSlot, cmd.callableSlot, cmd.dst)
|
||||||
is CmdListIotaInt -> Opcode.LIST_IOTA_INT to intArrayOf(cmd.sizeSlot, cmd.dst)
|
is CmdListIotaInt -> Opcode.LIST_IOTA_INT to intArrayOf(cmd.sizeSlot, cmd.dst)
|
||||||
is CmdListNewIntCap -> Opcode.LIST_NEW_INT_CAP to intArrayOf(cmd.sizeSlot, cmd.capacitySlot, cmd.dst)
|
|
||||||
is CmdListFillIntCap -> Opcode.LIST_FILL_INT_CAP to intArrayOf(cmd.sizeSlot, cmd.capacitySlot, cmd.callableSlot, cmd.dst)
|
|
||||||
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
|
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
|
||||||
is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst)
|
is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst)
|
||||||
is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
|
is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
|
||||||
@ -629,10 +627,6 @@ object CmdDisassembler {
|
|||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.LIST_IOTA_INT ->
|
Opcode.LIST_IOTA_INT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.LIST_NEW_INT_CAP ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
|
||||||
Opcode.LIST_FILL_INT_CAP ->
|
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
|
||||||
Opcode.LIST_LITERAL ->
|
Opcode.LIST_LITERAL ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
|
||||||
Opcode.GET_MEMBER_SLOT ->
|
Opcode.GET_MEMBER_SLOT ->
|
||||||
|
|||||||
@ -35,7 +35,6 @@ data class CmdFunction(
|
|||||||
val constants: List<BytecodeConst>,
|
val constants: List<BytecodeConst>,
|
||||||
val cmds: Array<Cmd>,
|
val cmds: Array<Cmd>,
|
||||||
val posByIp: Array<net.sergeych.lyng.Pos?>,
|
val posByIp: Array<net.sergeych.lyng.Pos?>,
|
||||||
val fastOnly: Boolean = false,
|
|
||||||
) {
|
) {
|
||||||
init {
|
init {
|
||||||
require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" }
|
require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" }
|
||||||
@ -72,118 +71,3 @@ data class CmdFunction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun computeFastOnlyBytecode(scopeSlotCount: Int, cmds: Array<Cmd>): Boolean {
|
|
||||||
if (scopeSlotCount != 0) return false
|
|
||||||
return cmds.all(::supportsFastOnlyExecution)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun supportsFastOnlyExecution(cmd: Cmd): Boolean {
|
|
||||||
return when (cmd) {
|
|
||||||
is CmdMoveIntLocal,
|
|
||||||
is CmdMoveRealLocal,
|
|
||||||
is CmdMoveBoolLocal,
|
|
||||||
is CmdConstObj,
|
|
||||||
is CmdConstInt,
|
|
||||||
is CmdConstIntLocal,
|
|
||||||
is CmdConstReal,
|
|
||||||
is CmdConstBool,
|
|
||||||
is CmdConstNull,
|
|
||||||
is CmdUnboxIntObjLocal,
|
|
||||||
is CmdUnboxRealObjLocal,
|
|
||||||
is CmdIntToRealLocal,
|
|
||||||
is CmdRealToIntLocal,
|
|
||||||
is CmdBoolToIntLocal,
|
|
||||||
is CmdIntToBoolLocal,
|
|
||||||
is CmdAddIntLocal,
|
|
||||||
is CmdSubIntLocal,
|
|
||||||
is CmdMulIntLocal,
|
|
||||||
is CmdDivIntLocal,
|
|
||||||
is CmdModIntLocal,
|
|
||||||
is CmdNegIntLocal,
|
|
||||||
is CmdIncIntLocal,
|
|
||||||
is CmdDecIntLocal,
|
|
||||||
is CmdAddRealLocal,
|
|
||||||
is CmdSubRealLocal,
|
|
||||||
is CmdMulRealLocal,
|
|
||||||
is CmdDivRealLocal,
|
|
||||||
is CmdNegRealLocal,
|
|
||||||
is CmdAndIntLocal,
|
|
||||||
is CmdOrIntLocal,
|
|
||||||
is CmdXorIntLocal,
|
|
||||||
is CmdShlIntLocal,
|
|
||||||
is CmdShrIntLocal,
|
|
||||||
is CmdUshrIntLocal,
|
|
||||||
is CmdInvIntLocal,
|
|
||||||
is CmdCmpEqIntLocal,
|
|
||||||
is CmdCmpNeqIntLocal,
|
|
||||||
is CmdCmpLtIntLocal,
|
|
||||||
is CmdCmpLteIntLocal,
|
|
||||||
is CmdCmpGtIntLocal,
|
|
||||||
is CmdCmpGteIntLocal,
|
|
||||||
is CmdCmpEqRealLocal,
|
|
||||||
is CmdCmpNeqRealLocal,
|
|
||||||
is CmdCmpLtRealLocal,
|
|
||||||
is CmdCmpLteRealLocal,
|
|
||||||
is CmdCmpGtRealLocal,
|
|
||||||
is CmdCmpGteRealLocal,
|
|
||||||
is CmdCmpEqBoolLocal,
|
|
||||||
is CmdCmpNeqBoolLocal,
|
|
||||||
is CmdCmpEqIntRealLocal,
|
|
||||||
is CmdCmpEqRealIntLocal,
|
|
||||||
is CmdCmpLtIntRealLocal,
|
|
||||||
is CmdCmpLtRealIntLocal,
|
|
||||||
is CmdCmpLteIntRealLocal,
|
|
||||||
is CmdCmpLteRealIntLocal,
|
|
||||||
is CmdCmpGtIntRealLocal,
|
|
||||||
is CmdCmpGtRealIntLocal,
|
|
||||||
is CmdCmpGteIntRealLocal,
|
|
||||||
is CmdCmpGteRealIntLocal,
|
|
||||||
is CmdCmpNeqIntRealLocal,
|
|
||||||
is CmdCmpNeqRealIntLocal,
|
|
||||||
is CmdCmpEqStrLocal,
|
|
||||||
is CmdCmpNeqStrLocal,
|
|
||||||
is CmdCmpLtStrLocal,
|
|
||||||
is CmdCmpLteStrLocal,
|
|
||||||
is CmdCmpGtStrLocal,
|
|
||||||
is CmdCmpGteStrLocal,
|
|
||||||
is CmdCmpEqIntObjLocal,
|
|
||||||
is CmdCmpNeqIntObjLocal,
|
|
||||||
is CmdCmpLtIntObjLocal,
|
|
||||||
is CmdCmpLteIntObjLocal,
|
|
||||||
is CmdCmpGtIntObjLocal,
|
|
||||||
is CmdCmpGteIntObjLocal,
|
|
||||||
is CmdCmpEqRealObjLocal,
|
|
||||||
is CmdCmpNeqRealObjLocal,
|
|
||||||
is CmdCmpLtRealObjLocal,
|
|
||||||
is CmdCmpLteRealObjLocal,
|
|
||||||
is CmdCmpGtRealObjLocal,
|
|
||||||
is CmdCmpGteRealObjLocal,
|
|
||||||
is CmdAddIntObjLocal,
|
|
||||||
is CmdSubIntObjLocal,
|
|
||||||
is CmdMulIntObjLocal,
|
|
||||||
is CmdDivIntObjLocal,
|
|
||||||
is CmdModIntObjLocal,
|
|
||||||
is CmdAddRealObjLocal,
|
|
||||||
is CmdSubRealObjLocal,
|
|
||||||
is CmdMulRealObjLocal,
|
|
||||||
is CmdDivRealObjLocal,
|
|
||||||
is CmdModRealObjLocal,
|
|
||||||
is CmdNotBoolLocal,
|
|
||||||
is CmdAndBoolLocal,
|
|
||||||
is CmdOrBoolLocal,
|
|
||||||
is CmdJmp,
|
|
||||||
is CmdJmpIfTrueLocal,
|
|
||||||
is CmdJmpIfFalseLocal,
|
|
||||||
is CmdJmpIfEqIntLocal,
|
|
||||||
is CmdJmpIfNeqIntLocal,
|
|
||||||
is CmdJmpIfLtIntLocal,
|
|
||||||
is CmdJmpIfLteIntLocal,
|
|
||||||
is CmdJmpIfGtIntLocal,
|
|
||||||
is CmdJmpIfGteIntLocal,
|
|
||||||
is CmdRet,
|
|
||||||
is CmdRetVoid -> true
|
|
||||||
|
|
||||||
else -> false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -57,64 +57,6 @@ class CmdVm {
|
|||||||
suspend fun execute(fn: CmdFunction, scope0: Scope, args: List<Obj>): Obj {
|
suspend fun execute(fn: CmdFunction, scope0: Scope, args: List<Obj>): Obj {
|
||||||
return execute(fn, scope0, Arguments.from(args))
|
return execute(fn, scope0, Arguments.from(args))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun executeFastOnlyNoSuspend(
|
|
||||||
fn: CmdFunction,
|
|
||||||
scope0: Scope,
|
|
||||||
args: Arguments,
|
|
||||||
binder: ((CmdFrame, Arguments) -> Unit)? = null
|
|
||||||
): Obj {
|
|
||||||
require(fn.fastOnly) { "fast-only execution requested for non-fast function ${fn.name}" }
|
|
||||||
result = null
|
|
||||||
val frame = CmdFrame(this, fn, scope0, args.list)
|
|
||||||
frame.applyCaptureRecords()
|
|
||||||
binder?.invoke(frame, args)
|
|
||||||
val cmds = fn.cmds
|
|
||||||
try {
|
|
||||||
while (result == null) {
|
|
||||||
val cmd = cmds[frame.ip++]
|
|
||||||
if (!cmd.performFast(frame)) {
|
|
||||||
error("fast-only command not supported: ${cmd::class.simpleName}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
throw frame.normalizeThrowableFast(e)
|
|
||||||
}
|
|
||||||
return result ?: ObjVoid
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun executeFastOnly(
|
|
||||||
fn: CmdFunction,
|
|
||||||
scope0: Scope,
|
|
||||||
args: Arguments,
|
|
||||||
binder: ((CmdFrame, Arguments) -> Unit)? = null
|
|
||||||
): Obj {
|
|
||||||
require(fn.fastOnly) { "fast-only execution requested for non-fast function ${fn.name}" }
|
|
||||||
result = null
|
|
||||||
val frame = CmdFrame(this, fn, scope0, args.list)
|
|
||||||
frame.applyCaptureRecords()
|
|
||||||
binder?.invoke(frame, args)
|
|
||||||
val cmds = fn.cmds
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
while (result == null) {
|
|
||||||
val cmd = cmds[frame.ip++]
|
|
||||||
if (!cmd.performFast(frame)) {
|
|
||||||
error("fast-only command not supported: ${cmd::class.simpleName}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
val throwable = frame.normalizeThrowable(e)
|
|
||||||
if (!frame.handleException(throwable)) {
|
|
||||||
frame.cancelIterators()
|
|
||||||
throw throwable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frame.cancelIterators()
|
|
||||||
return result ?: ObjVoid
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Cmd {
|
sealed class Cmd {
|
||||||
@ -338,11 +280,6 @@ class CmdMakeRange(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CmdConstNull(internal val dst: Int) : Cmd() {
|
class CmdConstNull(internal val dst: Int) : Cmd() {
|
||||||
override fun performFast(frame: CmdFrame): Boolean {
|
|
||||||
frame.setObj(dst, ObjNull)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
frame.setObj(dst, ObjNull)
|
frame.setObj(dst, ObjNull)
|
||||||
return
|
return
|
||||||
@ -2364,11 +2301,6 @@ class CmdJmpIfGteIntLocal(internal val a: Int, internal val b: Int, internal val
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CmdRet(internal val slot: Int) : Cmd() {
|
class CmdRet(internal val slot: Int) : Cmd() {
|
||||||
override fun performFast(frame: CmdFrame): Boolean {
|
|
||||||
frame.vm.result = frame.storedSlotObj(slot)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
frame.vm.result = frame.slotToObj(slot)
|
frame.vm.result = frame.slotToObj(slot)
|
||||||
return
|
return
|
||||||
@ -2390,11 +2322,6 @@ class CmdRetLabel(internal val labelId: Int, internal val slot: Int) : Cmd() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CmdRetVoid : Cmd() {
|
class CmdRetVoid : Cmd() {
|
||||||
override fun performFast(frame: CmdFrame): Boolean {
|
|
||||||
frame.vm.result = ObjVoid
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
frame.vm.result = ObjVoid
|
frame.vm.result = ObjVoid
|
||||||
return
|
return
|
||||||
@ -3290,21 +3217,10 @@ class CmdCallDirect(
|
|||||||
frame.ensureScope().raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
|
frame.ensureScope().raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val directFastResult = (callee as? BytecodeArgCallable)?.callWithArgsFast(frame.ensureScope(), args)
|
val result = if (PerfFlags.SCOPE_POOL) {
|
||||||
val result = if (directFastResult != null) {
|
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
|
||||||
directFastResult
|
|
||||||
} else if (PerfFlags.SCOPE_POOL) {
|
|
||||||
frame.ensureScope().withChildFrame(args) { child ->
|
|
||||||
(callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
val scope = frame.ensureScope()
|
callee.callOn(frame.ensureScope().createChildScope(frame.ensureScope().pos, args = args))
|
||||||
if (callee is BytecodeLambdaCallable && callee.supportsDirectInvokeFastPath()) {
|
|
||||||
callee.invokeWithArgsFast(scope, args) ?: callee.invokeWithArgs(scope, args)
|
|
||||||
} else {
|
|
||||||
val child = scope.createChildScope(scope.pos, args = args)
|
|
||||||
(callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
frame.storeObjResult(dst, result)
|
frame.storeObjResult(dst, result)
|
||||||
return
|
return
|
||||||
@ -3331,28 +3247,18 @@ class CmdCallSlot(
|
|||||||
frame.ensureScope().raiseUnset(message)
|
frame.ensureScope().raiseUnset(message)
|
||||||
}
|
}
|
||||||
val args = frame.buildArguments(argBase, argCount)
|
val args = frame.buildArguments(argBase, argCount)
|
||||||
val scope = frame.ensureScope()
|
|
||||||
val directFastResult = (callee as? BytecodeArgCallable)?.callWithArgsFast(scope, args)
|
|
||||||
val canPool = PerfFlags.SCOPE_POOL && callee !is Statement
|
val canPool = PerfFlags.SCOPE_POOL && callee !is Statement
|
||||||
val result = if (directFastResult != null) {
|
val result = if (canPool) {
|
||||||
directFastResult
|
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
|
||||||
} else if (canPool) {
|
|
||||||
frame.ensureScope().withChildFrame(args) { child ->
|
|
||||||
(callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
val scope = frame.ensureScope()
|
||||||
if (callee is Statement) {
|
if (callee is Statement) {
|
||||||
val bytecodeBody = (callee as? BytecodeBodyProvider)?.bytecodeBody()
|
val bytecodeBody = (callee as? BytecodeBodyProvider)?.bytecodeBody()
|
||||||
if (callee !is BytecodeStatement && callee !is BytecodeCallable && bytecodeBody == null) {
|
if (callee !is BytecodeStatement && callee !is BytecodeCallable && bytecodeBody == null) {
|
||||||
scope.raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
|
scope.raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (callee is BytecodeLambdaCallable && callee.supportsDirectInvokeFastPath()) {
|
callee.callOn(scope.createChildScope(scope.pos, args = args))
|
||||||
callee.invokeWithArgsFast(scope, args) ?: callee.invokeWithArgs(scope, args)
|
|
||||||
} else {
|
|
||||||
val child = scope.createChildScope(scope.pos, args = args)
|
|
||||||
(callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
frame.storeObjResult(dst, result)
|
frame.storeObjResult(dst, result)
|
||||||
return
|
return
|
||||||
@ -3435,53 +3341,10 @@ class CmdListFillInt(
|
|||||||
val scope = frame.ensureScope()
|
val scope = frame.ensureScope()
|
||||||
val result = ObjList(LongArray(size))
|
val result = ObjList(LongArray(size))
|
||||||
for (i in 0 until size) {
|
for (i in 0 until size) {
|
||||||
val args = Arguments(ObjInt.of(i.toLong()))
|
|
||||||
val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) {
|
val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) {
|
||||||
callable.invokeImplicitIntArgFast(scope, i.toLong()) ?: callable.invokeImplicitIntArg(scope, i.toLong())
|
callable.invokeImplicitIntArg(scope, i.toLong())
|
||||||
} else if (callable is BytecodeArgCallable) {
|
|
||||||
callable.callWithArgsFast(scope, args) ?: run {
|
|
||||||
val child = scope.createChildScope(scope.pos, args = args)
|
|
||||||
(callable as? BytecodeCallable)?.callOnFast(child) ?: callable.callOn(child)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
val child = scope.createChildScope(scope.pos, args = args)
|
callable.callOn(scope.createChildScope(scope.pos, args = Arguments(ObjInt.of(i.toLong()))))
|
||||||
(callable as? BytecodeCallable)?.callOnFast(child) ?: callable.callOn(child)
|
|
||||||
}
|
|
||||||
val intValue = (value as? ObjInt)?.value ?: scope.raiseClassCastError("expected Int fill result")
|
|
||||||
result.setIntAtFast(i, intValue)
|
|
||||||
}
|
|
||||||
frame.storeObjResult(dst, result)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CmdListFillIntCap(
|
|
||||||
internal val sizeSlot: Int,
|
|
||||||
internal val capacitySlot: Int,
|
|
||||||
internal val callableSlot: Int,
|
|
||||||
internal val dst: Int,
|
|
||||||
) : Cmd() {
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
|
||||||
val size = frame.getInt(sizeSlot).toInt()
|
|
||||||
if (size < 0) frame.ensureScope().raiseIllegalArgument("list size must be non-negative")
|
|
||||||
val capacity = frame.getInt(capacitySlot).toInt()
|
|
||||||
val actualCapacity = maxOf(size, capacity)
|
|
||||||
if (actualCapacity < 0) frame.ensureScope().raiseIllegalArgument("list capacity must be non-negative")
|
|
||||||
val callable = frame.storedSlotObj(callableSlot)
|
|
||||||
val scope = frame.ensureScope()
|
|
||||||
val result = ObjList(LongArray(actualCapacity), size)
|
|
||||||
for (i in 0 until size) {
|
|
||||||
val args = Arguments(ObjInt.of(i.toLong()))
|
|
||||||
val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) {
|
|
||||||
callable.invokeImplicitIntArgFast(scope, i.toLong()) ?: callable.invokeImplicitIntArg(scope, i.toLong())
|
|
||||||
} else if (callable is BytecodeArgCallable) {
|
|
||||||
callable.callWithArgsFast(scope, args) ?: run {
|
|
||||||
val child = scope.createChildScope(scope.pos, args = args)
|
|
||||||
(callable as? BytecodeCallable)?.callOnFast(child) ?: callable.callOn(child)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val child = scope.createChildScope(scope.pos, args = args)
|
|
||||||
(callable as? BytecodeCallable)?.callOnFast(child) ?: callable.callOn(child)
|
|
||||||
}
|
}
|
||||||
val intValue = (value as? ObjInt)?.value ?: scope.raiseClassCastError("expected Int fill result")
|
val intValue = (value as? ObjInt)?.value ?: scope.raiseClassCastError("expected Int fill result")
|
||||||
result.setIntAtFast(i, intValue)
|
result.setIntAtFast(i, intValue)
|
||||||
@ -3895,22 +3758,6 @@ class CmdListNewInt(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CmdListNewIntCap(
|
|
||||||
internal val sizeSlot: Int,
|
|
||||||
internal val capacitySlot: Int,
|
|
||||||
internal val dst: Int,
|
|
||||||
) : Cmd() {
|
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
|
||||||
val size = frame.getInt(sizeSlot).toInt()
|
|
||||||
if (size < 0) frame.ensureScope().raiseIllegalArgument("list size must be non-negative")
|
|
||||||
val capacity = frame.getInt(capacitySlot).toInt()
|
|
||||||
val actualCapacity = maxOf(size, capacity)
|
|
||||||
if (actualCapacity < 0) frame.ensureScope().raiseIllegalArgument("list capacity must be non-negative")
|
|
||||||
frame.storeObjResult(dst, ObjList(LongArray(actualCapacity), size))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CmdGetIndex(
|
class CmdGetIndex(
|
||||||
internal val targetSlot: Int,
|
internal val targetSlot: Int,
|
||||||
internal val indexSlot: Int,
|
internal val indexSlot: Int,
|
||||||
@ -4015,7 +3862,6 @@ class CmdMakeLambda(internal val id: Int, internal val dst: Int) : Cmd() {
|
|||||||
captureNames = lambdaConst.captureNames,
|
captureNames = lambdaConst.captureNames,
|
||||||
paramSlotPlan = lambdaConst.paramSlotPlan,
|
paramSlotPlan = lambdaConst.paramSlotPlan,
|
||||||
argsDeclaration = lambdaConst.argsDeclaration,
|
argsDeclaration = lambdaConst.argsDeclaration,
|
||||||
supportsDirectInvokeFastPath = lambdaConst.supportsDirectInvokeFastPath,
|
|
||||||
preferredThisType = lambdaConst.preferredThisType,
|
preferredThisType = lambdaConst.preferredThisType,
|
||||||
returnLabels = lambdaConst.returnLabels,
|
returnLabels = lambdaConst.returnLabels,
|
||||||
pos = lambdaConst.pos
|
pos = lambdaConst.pos
|
||||||
@ -4037,18 +3883,10 @@ class BytecodeLambdaCallable(
|
|||||||
private val captureNames: List<String>,
|
private val captureNames: List<String>,
|
||||||
private val paramSlotPlan: Map<String, Int>,
|
private val paramSlotPlan: Map<String, Int>,
|
||||||
private val argsDeclaration: ArgsDeclaration?,
|
private val argsDeclaration: ArgsDeclaration?,
|
||||||
private val supportsDirectInvokeFastPath: Boolean,
|
|
||||||
private val preferredThisType: String?,
|
private val preferredThisType: String?,
|
||||||
private val returnLabels: Set<String>,
|
private val returnLabels: Set<String>,
|
||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
) : Statement(), BytecodeCallable, BytecodeArgCallable {
|
) : Statement(), BytecodeCallable {
|
||||||
private val slotPlanByName: Map<String, Int> by lazy(LazyThreadSafetyMode.NONE) { fn.localSlotPlanByName() }
|
|
||||||
private val declaredLocalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) {
|
|
||||||
fn.constants
|
|
||||||
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
|
||||||
.mapTo(mutableSetOf()) { it.name }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun freezeRecord(record: ObjRecord): ObjRecord {
|
private fun freezeRecord(record: ObjRecord): ObjRecord {
|
||||||
if (record.isMutable) return record
|
if (record.isMutable) return record
|
||||||
val raw = record.value as Obj?
|
val raw = record.value as Obj?
|
||||||
@ -4080,7 +3918,6 @@ class BytecodeLambdaCallable(
|
|||||||
captureNames = captureNames,
|
captureNames = captureNames,
|
||||||
paramSlotPlan = paramSlotPlan,
|
paramSlotPlan = paramSlotPlan,
|
||||||
argsDeclaration = argsDeclaration,
|
argsDeclaration = argsDeclaration,
|
||||||
supportsDirectInvokeFastPath = supportsDirectInvokeFastPath,
|
|
||||||
preferredThisType = preferredThisType,
|
preferredThisType = preferredThisType,
|
||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
pos = pos
|
pos = pos
|
||||||
@ -4097,7 +3934,6 @@ class BytecodeLambdaCallable(
|
|||||||
captureNames = captureNames,
|
captureNames = captureNames,
|
||||||
paramSlotPlan = paramSlotPlan,
|
paramSlotPlan = paramSlotPlan,
|
||||||
argsDeclaration = argsDeclaration,
|
argsDeclaration = argsDeclaration,
|
||||||
supportsDirectInvokeFastPath = supportsDirectInvokeFastPath,
|
|
||||||
preferredThisType = preferredThisType,
|
preferredThisType = preferredThisType,
|
||||||
returnLabels = returnLabels,
|
returnLabels = returnLabels,
|
||||||
pos = pos
|
pos = pos
|
||||||
@ -4106,29 +3942,9 @@ class BytecodeLambdaCallable(
|
|||||||
|
|
||||||
fun supportsImplicitIntFillFastPath(): Boolean = argsDeclaration == null
|
fun supportsImplicitIntFillFastPath(): Boolean = argsDeclaration == null
|
||||||
|
|
||||||
fun supportsDirectInvokeFastPath(): Boolean = supportsDirectInvokeFastPath
|
suspend fun invokeImplicitIntArg(scope: Scope, arg: Long): Obj {
|
||||||
|
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
|
||||||
private val fastPreboundLocalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) {
|
it.args = Arguments.EMPTY
|
||||||
if (argsDeclaration == null) {
|
|
||||||
setOf("it")
|
|
||||||
} else {
|
|
||||||
argsDeclaration.params.mapTo(LinkedHashSet()) { it.name }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val supportsFastUndeclaredLocalInit: Boolean by lazy(LazyThreadSafetyMode.NONE) {
|
|
||||||
canFastSeedUndeclaredLocals(fn, declaredLocalNames, fastPreboundLocalNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun supportsFastOnlyVm(arguments: Arguments): Boolean {
|
|
||||||
if (!supportsDirectInvokeFastPath || !fn.fastOnly) return false
|
|
||||||
if (!supportsFastUndeclaredLocalInit) return false
|
|
||||||
return argsDeclaration == null || argsDeclaration.supportsFastFrameBinding(arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun buildContext(callScope: Scope, args: Arguments): Scope {
|
|
||||||
val context = callScope.applyClosureForBytecode(closureScope, preferredThisType).also {
|
|
||||||
it.args = args
|
|
||||||
}
|
}
|
||||||
if (captureRecords != null) {
|
if (captureRecords != null) {
|
||||||
context.captureRecords = captureRecords
|
context.captureRecords = captureRecords
|
||||||
@ -4136,160 +3952,97 @@ class BytecodeLambdaCallable(
|
|||||||
} else if (captureNames.isNotEmpty()) {
|
} else if (captureNames.isNotEmpty()) {
|
||||||
closureScope.raiseIllegalState("bytecode lambda capture records missing")
|
closureScope.raiseIllegalState("bytecode lambda capture records missing")
|
||||||
}
|
}
|
||||||
return context
|
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
||||||
}
|
paramSlotPlan["it"]?.let { itSlot ->
|
||||||
|
|
||||||
private fun bindArgumentsFast(frame: CmdFrame, context: Scope, arguments: Arguments) {
|
|
||||||
if (argsDeclaration == null) {
|
|
||||||
val l = arguments.list
|
|
||||||
val itValue: Obj = when (l.size) {
|
|
||||||
0 -> ObjVoid
|
|
||||||
1 -> l[0]
|
|
||||||
else -> ObjList(l.toMutableList())
|
|
||||||
}
|
|
||||||
val itSlot = slotPlanByName["it"]
|
|
||||||
if (itSlot != null) {
|
|
||||||
when (itValue) {
|
|
||||||
is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
|
|
||||||
is ObjReal -> frame.frame.setReal(itSlot, itValue.value)
|
|
||||||
is ObjBool -> frame.frame.setBool(itSlot, itValue.value)
|
|
||||||
else -> frame.frame.setObj(itSlot, itValue)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
argsDeclaration.assignToFrameFast(
|
|
||||||
context,
|
|
||||||
arguments,
|
|
||||||
slotPlanByName,
|
|
||||||
frame.frame
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun bindArguments(frame: CmdFrame, context: Scope, arguments: Arguments) {
|
|
||||||
if (argsDeclaration == null) {
|
|
||||||
bindArgumentsFast(frame, context, arguments)
|
|
||||||
} else {
|
|
||||||
argsDeclaration.assignToFrame(
|
|
||||||
context,
|
|
||||||
arguments,
|
|
||||||
slotPlanByName,
|
|
||||||
frame.frame
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun seedUndeclaredLocals(frame: CmdFrame, context: Scope) {
|
|
||||||
val localNames = frame.fn.localSlotNames
|
|
||||||
for (i in localNames.indices) {
|
|
||||||
val name = localNames[i] ?: continue
|
|
||||||
if (declaredLocalNames.contains(name)) continue
|
|
||||||
val slotType = frame.getLocalSlotTypeCode(i)
|
|
||||||
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val record = context.getLocalRecordDirect(name)
|
|
||||||
?: context.parent?.get(name)
|
|
||||||
?: context.get(name)
|
|
||||||
?: continue
|
|
||||||
val value =
|
|
||||||
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
|
||||||
context.resolve(record, name)
|
|
||||||
} else {
|
|
||||||
when (val direct = record.value) {
|
|
||||||
is FrameSlotRef -> direct.read()
|
|
||||||
is RecordSlotRef -> direct.read(context, name)
|
|
||||||
is ScopeSlotRef -> direct.read()
|
|
||||||
else -> direct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
frame.frame.setObj(i, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun invokeImplicitIntArg(scope: Scope, arg: Long): Obj {
|
|
||||||
val context = buildContext(scope, Arguments.EMPTY)
|
|
||||||
val fastBinder: (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
|
||||||
slotPlanByName["it"]?.let { itSlot ->
|
|
||||||
frame.frame.setInt(itSlot, arg)
|
frame.frame.setInt(itSlot, arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return try {
|
return try {
|
||||||
val vm = CmdVm()
|
CmdVm().execute(fn, context, Arguments.EMPTY, binder)
|
||||||
if (supportsFastOnlyVm(Arguments.EMPTY)) {
|
|
||||||
vm.executeFastOnly(fn, context, Arguments.EMPTY, fastBinder)
|
|
||||||
} else {
|
|
||||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
|
||||||
slotPlanByName["it"]?.let { itSlot ->
|
|
||||||
frame.frame.setInt(itSlot, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
vm.execute(fn, context, Arguments.EMPTY, binder)
|
|
||||||
}
|
|
||||||
} catch (e: ReturnException) {
|
} catch (e: ReturnException) {
|
||||||
if (e.label == null || returnLabels.contains(e.label)) e.result
|
if (e.label == null || returnLabels.contains(e.label)) e.result
|
||||||
else throw e
|
else throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invokeImplicitIntArgFast(scope: Scope, arg: Long): Obj? {
|
|
||||||
if (!supportsFastOnlyVm(Arguments.EMPTY)) return null
|
|
||||||
val context = buildContext(scope, Arguments.EMPTY)
|
|
||||||
val binder: (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
|
||||||
slotPlanByName["it"]?.let { itSlot ->
|
|
||||||
frame.frame.setInt(itSlot, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return try {
|
|
||||||
CmdVm().executeFastOnlyNoSuspend(fn, context, Arguments.EMPTY, binder)
|
|
||||||
} catch (e: ReturnException) {
|
|
||||||
if (e.label == null || returnLabels.contains(e.label)) e.result else throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun invokeWithArgs(scope: Scope, args: Arguments): Obj {
|
|
||||||
val context = buildContext(scope, args)
|
|
||||||
return try {
|
|
||||||
val vm = CmdVm()
|
|
||||||
if (supportsFastOnlyVm(args)) {
|
|
||||||
val binder: (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
|
||||||
bindArgumentsFast(frame, context, arguments)
|
|
||||||
}
|
|
||||||
vm.executeFastOnly(fn, context, args, binder)
|
|
||||||
} else {
|
|
||||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
|
||||||
bindArguments(frame, context, arguments)
|
|
||||||
seedUndeclaredLocals(frame, context)
|
|
||||||
}
|
|
||||||
vm.execute(fn, context, args, binder)
|
|
||||||
}
|
|
||||||
} catch (e: ReturnException) {
|
|
||||||
if (e.label == null || returnLabels.contains(e.label)) e.result else throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun invokeWithArgsFast(scope: Scope, args: Arguments): Obj? {
|
|
||||||
if (!supportsFastOnlyVm(args)) return null
|
|
||||||
val context = buildContext(scope, args)
|
|
||||||
val binder: (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
|
||||||
bindArgumentsFast(frame, context, arguments)
|
|
||||||
}
|
|
||||||
return try {
|
|
||||||
CmdVm().executeFastOnlyNoSuspend(fn, context, args, binder)
|
|
||||||
} catch (e: ReturnException) {
|
|
||||||
if (e.label == null || returnLabels.contains(e.label)) e.result else throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun callWithArgsFast(scope: Scope, args: Arguments): Obj? = invokeWithArgsFast(scope, args)
|
|
||||||
|
|
||||||
override fun callOnFast(scope: Scope): Obj? = invokeWithArgsFast(scope, scope.args)
|
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
return invokeWithArgs(scope, scope.args)
|
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
|
||||||
|
it.args = scope.args
|
||||||
|
}
|
||||||
|
if (captureRecords != null) {
|
||||||
|
context.captureRecords = captureRecords
|
||||||
|
context.captureNames = captureNames
|
||||||
|
} else if (captureNames.isNotEmpty()) {
|
||||||
|
closureScope.raiseIllegalState("bytecode lambda capture records missing")
|
||||||
|
}
|
||||||
|
if (argsDeclaration == null) {
|
||||||
|
// Bound in the bytecode entry binder.
|
||||||
|
} else {
|
||||||
|
// args bound into frame slots in the bytecode entry binder
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
val declaredNames = fn.constants
|
||||||
|
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
||||||
|
.mapTo(mutableSetOf()) { it.name }
|
||||||
|
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||||
|
val slotPlan = fn.localSlotPlanByName()
|
||||||
|
if (argsDeclaration == null) {
|
||||||
|
val l = arguments.list
|
||||||
|
val itValue: Obj = when (l.size) {
|
||||||
|
0 -> ObjVoid
|
||||||
|
1 -> l[0]
|
||||||
|
else -> ObjList(l.toMutableList())
|
||||||
|
}
|
||||||
|
val itSlot = slotPlan["it"]
|
||||||
|
if (itSlot != null) {
|
||||||
|
when (itValue) {
|
||||||
|
is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
|
||||||
|
is ObjReal -> frame.frame.setReal(itSlot, itValue.value)
|
||||||
|
is ObjBool -> frame.frame.setBool(itSlot, itValue.value)
|
||||||
|
else -> frame.frame.setObj(itSlot, itValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
argsDeclaration.assignToFrame(
|
||||||
|
context,
|
||||||
|
arguments,
|
||||||
|
slotPlan,
|
||||||
|
frame.frame
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val localNames = frame.fn.localSlotNames
|
||||||
|
for (i in localNames.indices) {
|
||||||
|
val name = localNames[i] ?: continue
|
||||||
|
if (declaredNames.contains(name)) continue
|
||||||
|
val slotType = frame.getLocalSlotTypeCode(i)
|
||||||
|
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val record = context.getLocalRecordDirect(name)
|
||||||
|
?: context.parent?.get(name)
|
||||||
|
?: context.get(name)
|
||||||
|
?: continue
|
||||||
|
val value =
|
||||||
|
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||||
|
context.resolve(record, name)
|
||||||
|
} else {
|
||||||
|
when (val direct = record.value) {
|
||||||
|
is FrameSlotRef -> direct.read()
|
||||||
|
is RecordSlotRef -> direct.read(context, name)
|
||||||
|
is ScopeSlotRef -> direct.read()
|
||||||
|
else -> direct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame.frame.setObj(i, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CmdVm().execute(fn, context, scope.args, binder)
|
||||||
|
} catch (e: ReturnException) {
|
||||||
|
if (e.label == null || returnLabels.contains(e.label)) e.result else throw e
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4739,19 +4492,6 @@ class CmdFrame(
|
|||||||
return ExecutionError(errorObject, pos, message, t)
|
return ExecutionError(errorObject, pos, message, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun normalizeThrowableFast(t: Throwable): Throwable {
|
|
||||||
if (t is ExecutionError || t is ReturnException || t is LoopBreakContinueException) return t
|
|
||||||
val parentScope = ensureScope()
|
|
||||||
val pos = (t as? ScriptError)?.pos ?: currentErrorPos() ?: parentScope.pos
|
|
||||||
val throwScope = parentScope.createChildScope(pos = pos)
|
|
||||||
val message = when (t) {
|
|
||||||
is ScriptError -> t.errorMessage
|
|
||||||
else -> t.message ?: t.toString()
|
|
||||||
}
|
|
||||||
val errorObject = ObjUnknownException(throwScope, message)
|
|
||||||
return ExecutionError(errorObject, pos, message, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun handleException(t: Throwable): Boolean {
|
suspend fun handleException(t: Throwable): Boolean {
|
||||||
val handler = tryStack.lastOrNull() ?: return false
|
val handler = tryStack.lastOrNull() ?: return false
|
||||||
vmIterDebug {
|
vmIterDebug {
|
||||||
@ -5706,14 +5446,6 @@ class CmdFrame(
|
|||||||
if (index < target.slotCount) return index
|
if (index < target.slotCount) return index
|
||||||
return index
|
return index
|
||||||
}
|
}
|
||||||
if (target.hasSlotPlanConflict(mapOf(name to index))) {
|
|
||||||
val record = target.getLocalRecordDirect(name)
|
|
||||||
?: target.localBindings[name]
|
|
||||||
?: target.parent?.get(name)
|
|
||||||
?: target.get(name)
|
|
||||||
?: ObjRecord(ObjUnset, isMutable = true)
|
|
||||||
return target.allocateSlotFor(name, record)
|
|
||||||
}
|
|
||||||
target.applySlotPlan(mapOf(name to index))
|
target.applySlotPlan(mapOf(name to index))
|
||||||
val existing = target.getLocalRecordDirect(name) ?: target.localBindings[name]
|
val existing = target.getLocalRecordDirect(name) ?: target.localBindings[name]
|
||||||
if (existing != null) {
|
if (existing != null) {
|
||||||
|
|||||||
@ -190,8 +190,6 @@ enum class Opcode(val code: Int) {
|
|||||||
GET_DYNAMIC_MEMBER(0xAC),
|
GET_DYNAMIC_MEMBER(0xAC),
|
||||||
SET_DYNAMIC_MEMBER(0xAD),
|
SET_DYNAMIC_MEMBER(0xAD),
|
||||||
CALL_DYNAMIC_MEMBER(0xAE),
|
CALL_DYNAMIC_MEMBER(0xAE),
|
||||||
LIST_NEW_INT_CAP(0xAF),
|
|
||||||
LIST_FILL_INT_CAP(0xB0),
|
|
||||||
|
|
||||||
RESOLVE_SCOPE_SLOT(0xB1),
|
RESOLVE_SCOPE_SLOT(0xB1),
|
||||||
LOAD_OBJ_ADDR(0xB2),
|
LOAD_OBJ_ADDR(0xB2),
|
||||||
|
|||||||
@ -17,58 +17,7 @@
|
|||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.FrameSlotRef
|
|
||||||
import net.sergeych.lyng.RecordSlotRef
|
|
||||||
import net.sergeych.lyng.ScopeSlotRef
|
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
import net.sergeych.lyng.obj.ObjProperty
|
|
||||||
|
|
||||||
internal fun canFastSeedUndeclaredLocals(
|
|
||||||
fn: CmdFunction,
|
|
||||||
declaredLocalNames: Set<String>,
|
|
||||||
preboundLocalNames: Set<String>
|
|
||||||
): Boolean {
|
|
||||||
if (fn.localSlotNames.isEmpty()) return true
|
|
||||||
for (i in fn.localSlotNames.indices) {
|
|
||||||
val name = fn.localSlotNames[i] ?: continue
|
|
||||||
if (declaredLocalNames.contains(name)) continue
|
|
||||||
if (fn.localSlotCaptures.getOrNull(i) == true) continue
|
|
||||||
if (preboundLocalNames.contains(name)) continue
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun trySeedFrameLocalsFromScopeFast(frame: CmdFrame, scope: Scope): Boolean {
|
|
||||||
val localNames = frame.fn.localSlotNames
|
|
||||||
if (localNames.isEmpty()) return true
|
|
||||||
val base = frame.fn.scopeSlotCount
|
|
||||||
for (i in localNames.indices) {
|
|
||||||
val name = localNames[i] ?: continue
|
|
||||||
val slotType = frame.getLocalSlotTypeCode(i)
|
|
||||||
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) continue
|
|
||||||
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) continue
|
|
||||||
val record = scope.getLocalRecordDirect(name)
|
|
||||||
?: scope.chainLookupIgnoreClosure(name, followClosure = true)
|
|
||||||
?: continue
|
|
||||||
val value = when {
|
|
||||||
record.type == ObjRecord.Type.Delegated -> return false
|
|
||||||
record.type == ObjRecord.Type.Property -> return false
|
|
||||||
record.value is ObjProperty -> return false
|
|
||||||
else -> when (val direct = record.value) {
|
|
||||||
is FrameSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
|
|
||||||
is RecordSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
|
|
||||||
is ScopeSlotRef -> direct.resolvedCaptureValueOrNull() ?: return false
|
|
||||||
else -> direct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (value is FrameSlotRef && value.refersTo(frame.frame, i)) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
frame.setObjUnchecked(base + i, value)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
||||||
val localNames = frame.fn.localSlotNames
|
val localNames = frame.fn.localSlotNames
|
||||||
|
|||||||
@ -99,11 +99,10 @@ fun ObjClass.addFnDoc(
|
|||||||
visibility: Visibility = Visibility.Public,
|
visibility: Visibility = Visibility.Public,
|
||||||
tags: Map<String, List<String>> = emptyMap(),
|
tags: Map<String, List<String>> = emptyMap(),
|
||||||
moduleName: String? = null,
|
moduleName: String? = null,
|
||||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
|
||||||
code: suspend ScopeFacade.() -> Obj
|
code: suspend ScopeFacade.() -> Obj
|
||||||
) {
|
) {
|
||||||
// Register runtime method
|
// Register runtime method
|
||||||
addFn(name, isOpen, visibility, callSignature = callSignature, code = code)
|
addFn(name, isOpen, visibility, code = code)
|
||||||
// Register docs for the member under this class
|
// Register docs for the member under this class
|
||||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||||
classDoc(this@addFnDoc.className, doc = "") {
|
classDoc(this@addFnDoc.className, doc = "") {
|
||||||
@ -138,10 +137,9 @@ fun ObjClass.addClassFnDoc(
|
|||||||
isOpen: Boolean = false,
|
isOpen: Boolean = false,
|
||||||
tags: Map<String, List<String>> = emptyMap(),
|
tags: Map<String, List<String>> = emptyMap(),
|
||||||
moduleName: String? = null,
|
moduleName: String? = null,
|
||||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
|
||||||
code: suspend ScopeFacade.() -> Obj
|
code: suspend ScopeFacade.() -> Obj
|
||||||
) {
|
) {
|
||||||
addClassFn(name, isOpen, callSignature, code)
|
addClassFn(name, isOpen, code)
|
||||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||||
classDoc(this@addClassFnDoc.className, doc = "") {
|
classDoc(this@addClassFnDoc.className, doc = "") {
|
||||||
method(name = name, doc = doc, params = params, returns = returns, isStatic = true, tags = tags)
|
method(name = name, doc = doc, params = params, returns = returns, isStatic = true, tags = tags)
|
||||||
|
|||||||
@ -31,7 +31,6 @@ class LambdaFnRef(
|
|||||||
val captureEntries: List<LambdaCaptureEntry>,
|
val captureEntries: List<LambdaCaptureEntry>,
|
||||||
val inferredReturnClass: ObjClass?,
|
val inferredReturnClass: ObjClass?,
|
||||||
val inlineBodyRef: ObjRef?,
|
val inlineBodyRef: ObjRef?,
|
||||||
val supportsDirectInvokeFastPath: Boolean,
|
|
||||||
val preferredThisType: String?,
|
val preferredThisType: String?,
|
||||||
val wrapAsExtensionCallable: Boolean,
|
val wrapAsExtensionCallable: Boolean,
|
||||||
val returnLabels: Set<String>,
|
val returnLabels: Set<String>,
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import kotlinx.serialization.json.Json
|
|||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonNull
|
import kotlinx.serialization.json.JsonNull
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
import net.sergeych.lyng.BytecodeCallable
|
|
||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
import net.sergeych.lyng.InteropOperator
|
import net.sergeych.lyng.InteropOperator
|
||||||
import net.sergeych.lyng.OperatorInteropRegistry
|
import net.sergeych.lyng.OperatorInteropRegistry
|
||||||
@ -724,39 +723,40 @@ open class Obj {
|
|||||||
scope.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun invokeWithBoundScope(
|
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj {
|
||||||
scope: Scope,
|
val usePool = PerfFlags.SCOPE_POOL && this !is Statement
|
||||||
args: Arguments,
|
|
||||||
thisObj: Obj,
|
|
||||||
declaringClass: ObjClass? = null,
|
|
||||||
atPos: Pos = scope.pos
|
|
||||||
): Obj {
|
|
||||||
val usePool = PerfFlags.SCOPE_POOL && this !is Statement && atPos == scope.pos
|
|
||||||
return if (usePool) {
|
return if (usePool) {
|
||||||
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
||||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||||
(this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child)
|
callOn(child)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val child = scope.createChildScope(atPos, args = args, newThisObj = thisObj).also {
|
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
||||||
if (declaringClass != null) it.currentClassCtx = declaringClass
|
if (declaringClass != null) it.currentClassCtx = declaringClass
|
||||||
}
|
})
|
||||||
(this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj {
|
|
||||||
return invokeWithBoundScope(scope, args, thisObj, declaringClass)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||||
invokeWithBoundScope(scope, Arguments(args.toList()), thisObj)
|
callOn(
|
||||||
|
scope.createChildScope(
|
||||||
|
scope.pos,
|
||||||
|
args = Arguments(args.toList()),
|
||||||
|
newThisObj = thisObj
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun invoke(scope: Scope, thisObj: Obj): Obj =
|
suspend fun invoke(scope: Scope, thisObj: Obj): Obj =
|
||||||
invokeWithBoundScope(scope, Arguments.EMPTY, thisObj)
|
callOn(
|
||||||
|
scope.createChildScope(
|
||||||
|
scope.pos,
|
||||||
|
args = Arguments.EMPTY,
|
||||||
|
newThisObj = thisObj
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||||
invokeWithBoundScope(scope, args, thisObj, atPos = atPos)
|
callOn(scope.createChildScope(atPos, args = args, newThisObj = thisObj))
|
||||||
|
|
||||||
|
|
||||||
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
|
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
|
||||||
@ -828,13 +828,7 @@ open class Obj {
|
|||||||
name = "let",
|
name = "let",
|
||||||
doc = "Calls the specified function block with `this` value as its argument and returns its result.",
|
doc = "Calls the specified function block with `this` value as its argument and returns its result.",
|
||||||
params = listOf(ParamDoc("block")),
|
params = listOf(ParamDoc("block")),
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib"
|
||||||
callSignature = CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.UNARY_ARGUMENT,
|
|
||||||
result = CallSignature.ResultMode.BLOCK_RESULT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
call(args.firstAndOnly(), Arguments(thisObj))
|
call(args.firstAndOnly(), Arguments(thisObj))
|
||||||
}
|
}
|
||||||
@ -842,13 +836,7 @@ open class Obj {
|
|||||||
name = "apply",
|
name = "apply",
|
||||||
doc = "Calls the specified function block with `this` value as its receiver and returns `this` value.",
|
doc = "Calls the specified function block with `this` value as its receiver and returns `this` value.",
|
||||||
params = listOf(ParamDoc("block")),
|
params = listOf(ParamDoc("block")),
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib"
|
||||||
callSignature = CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.RECEIVER,
|
|
||||||
result = CallSignature.ResultMode.RETURN_RECEIVER
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
val body = args.firstAndOnly()
|
val body = args.firstAndOnly()
|
||||||
val scope = requireScope()
|
val scope = requireScope()
|
||||||
@ -864,13 +852,7 @@ open class Obj {
|
|||||||
name = "also",
|
name = "also",
|
||||||
doc = "Calls the specified function block with `this` value as its argument and returns `this` value.",
|
doc = "Calls the specified function block with `this` value as its argument and returns `this` value.",
|
||||||
params = listOf(ParamDoc("block")),
|
params = listOf(ParamDoc("block")),
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib"
|
||||||
callSignature = CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.UNARY_ARGUMENT,
|
|
||||||
result = CallSignature.ResultMode.RETURN_RECEIVER
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
call(args.firstAndOnly(), Arguments(thisObj))
|
call(args.firstAndOnly(), Arguments(thisObj))
|
||||||
thisObj
|
thisObj
|
||||||
@ -879,13 +861,7 @@ open class Obj {
|
|||||||
name = "run",
|
name = "run",
|
||||||
doc = "Calls the specified function block with `this` value as its receiver and returns its result.",
|
doc = "Calls the specified function block with `this` value as its receiver and returns its result.",
|
||||||
params = listOf(ParamDoc("block")),
|
params = listOf(ParamDoc("block")),
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib"
|
||||||
callSignature = CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.RECEIVER,
|
|
||||||
result = CallSignature.ResultMode.BLOCK_RESULT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
call(args.firstAndOnly())
|
call(args.firstAndOnly())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -809,8 +809,7 @@ open class ObjClass(
|
|||||||
if (initStmt is net.sergeych.lyng.Statement) {
|
if (initStmt is net.sergeych.lyng.Statement) {
|
||||||
executeBytecodeWithSeed(instance.instanceScope, initStmt, "instance init")
|
executeBytecodeWithSeed(instance.instanceScope, initStmt, "instance init")
|
||||||
} else {
|
} else {
|
||||||
(initStmt as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(instance.instanceScope)
|
initStmt.callOn(instance.instanceScope)
|
||||||
?: initStmt.callOn(instance.instanceScope)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -822,14 +821,13 @@ open class ObjClass(
|
|||||||
c.instanceConstructor?.let { ctor ->
|
c.instanceConstructor?.let { ctor ->
|
||||||
val execScope =
|
val execScope =
|
||||||
instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance)
|
instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance)
|
||||||
(ctor as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(execScope) ?: ctor.callOn(execScope)
|
ctor.callOn(execScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj {
|
suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj {
|
||||||
val child = scope.createChildScope(Arguments(*plainArgs))
|
return callOn(scope.createChildScope(Arguments(*plainArgs)))
|
||||||
return (this as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: callOn(child)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -849,7 +847,6 @@ open class ObjClass(
|
|||||||
fieldId: Int? = null,
|
fieldId: Int? = null,
|
||||||
methodId: Int? = null,
|
methodId: Int? = null,
|
||||||
typeDecl: net.sergeych.lyng.TypeDecl? = null,
|
typeDecl: net.sergeych.lyng.TypeDecl? = null,
|
||||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
|
||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
// Validation of override rules: only for non-system declarations
|
// Validation of override rules: only for non-system declarations
|
||||||
var existing: ObjRecord? = null
|
var existing: ObjRecord? = null
|
||||||
@ -950,7 +947,6 @@ open class ObjClass(
|
|||||||
isOverride = isOverride,
|
isOverride = isOverride,
|
||||||
isTransient = isTransient,
|
isTransient = isTransient,
|
||||||
type = type,
|
type = type,
|
||||||
callSignature = callSignature,
|
|
||||||
typeDecl = typeDecl,
|
typeDecl = typeDecl,
|
||||||
memberName = name,
|
memberName = name,
|
||||||
fieldId = effectiveFieldId,
|
fieldId = effectiveFieldId,
|
||||||
@ -977,8 +973,7 @@ open class ObjClass(
|
|||||||
isTransient: Boolean = false,
|
isTransient: Boolean = false,
|
||||||
type: ObjRecord.Type = ObjRecord.Type.Field,
|
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||||
fieldId: Int? = null,
|
fieldId: Int? = null,
|
||||||
methodId: Int? = null,
|
methodId: Int? = null
|
||||||
callSignature: net.sergeych.lyng.CallSignature? = null
|
|
||||||
): ObjRecord {
|
): ObjRecord {
|
||||||
initClassScope()
|
initClassScope()
|
||||||
val existing = classScope!!.objects[name]
|
val existing = classScope!!.objects[name]
|
||||||
@ -1019,7 +1014,6 @@ open class ObjClass(
|
|||||||
writeVisibility,
|
writeVisibility,
|
||||||
recordType = type,
|
recordType = type,
|
||||||
isTransient = isTransient,
|
isTransient = isTransient,
|
||||||
callSignature = callSignature,
|
|
||||||
fieldId = effectiveFieldId,
|
fieldId = effectiveFieldId,
|
||||||
methodId = effectiveMethodId
|
methodId = effectiveMethodId
|
||||||
)
|
)
|
||||||
@ -1039,7 +1033,6 @@ open class ObjClass(
|
|||||||
isOverride: Boolean = false,
|
isOverride: Boolean = false,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
methodId: Int? = null,
|
methodId: Int? = null,
|
||||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
|
||||||
code: (suspend net.sergeych.lyng.ScopeFacade.() -> Obj)? = null
|
code: (suspend net.sergeych.lyng.ScopeFacade.() -> Obj)? = null
|
||||||
) {
|
) {
|
||||||
val stmt = code?.let { ObjExternCallable.fromBridge { it() } } ?: ObjNull
|
val stmt = code?.let { ObjExternCallable.fromBridge { it() } } ?: ObjNull
|
||||||
@ -1047,8 +1040,7 @@ open class ObjClass(
|
|||||||
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
||||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||||
type = ObjRecord.Type.Fun,
|
type = ObjRecord.Type.Fun,
|
||||||
methodId = methodId,
|
methodId = methodId
|
||||||
callSignature = callSignature
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1080,19 +1072,8 @@ open class ObjClass(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
||||||
fun addClassFn(
|
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend net.sergeych.lyng.ScopeFacade.() -> Obj) {
|
||||||
name: String,
|
createClassField(name, ObjExternCallable.fromBridge { code() }, isOpen, type = ObjRecord.Type.Fun)
|
||||||
isOpen: Boolean = false,
|
|
||||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
|
||||||
code: suspend net.sergeych.lyng.ScopeFacade.() -> Obj
|
|
||||||
) {
|
|
||||||
createClassField(
|
|
||||||
name,
|
|
||||||
ObjExternCallable.fromBridge { code() },
|
|
||||||
isOpen,
|
|
||||||
type = ObjRecord.Type.Fun,
|
|
||||||
callSignature = callSignature
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -142,7 +142,7 @@ object ObjDecimalSupport {
|
|||||||
}
|
}
|
||||||
val child = requireScope().createChildScope()
|
val child = requireScope().createChildScope()
|
||||||
child.addConst(decimalContextVar, context)
|
child.addConst(decimalContextVar, context)
|
||||||
(block as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: block.callOn(child)
|
block.callOn(child)
|
||||||
}
|
}
|
||||||
registerBuiltinConversions(decimalClass)
|
registerBuiltinConversions(decimalClass)
|
||||||
registerInterop(decimalClass)
|
registerInterop(decimalClass)
|
||||||
|
|||||||
@ -64,19 +64,13 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
|
|||||||
return (callback as? BytecodeLambdaCallable)?.rebindClosure(context) ?: callback
|
return (callback as? BytecodeLambdaCallable)?.rebindClosure(context) ?: callback
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun callCallback(callback: Obj, child: Scope): Obj {
|
|
||||||
return (callback as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: callback.callOn(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use read callback to dynamically resolve the field name. Note that it does not work
|
* Use read callback to dynamically resolve the field name. Note that it does not work
|
||||||
* with method invocation which is implemented separately in [invokeInstanceMethod] below.
|
* with method invocation which is implemented separately in [invokeInstanceMethod] below.
|
||||||
*/
|
*/
|
||||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
return readCallback?.let { callback ->
|
return readCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name))))?.let {
|
||||||
callCallback(callback, execBase.createChildScope(Arguments(ObjString(name))))
|
|
||||||
}?.let {
|
|
||||||
if (writeCallback != null)
|
if (writeCallback != null)
|
||||||
it.asMutable
|
it.asMutable
|
||||||
else
|
else
|
||||||
@ -96,34 +90,26 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
|
|||||||
onNotFoundResult: (suspend () -> Obj?)?
|
onNotFoundResult: (suspend () -> Obj?)?
|
||||||
): Obj {
|
): Obj {
|
||||||
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
val over = readCallback?.let { callback ->
|
val over = readCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name))))
|
||||||
callCallback(callback, execBase.createChildScope(Arguments(ObjString(name))))
|
|
||||||
}
|
|
||||||
return over?.invoke(scope, scope.thisObj, args)
|
return over?.invoke(scope, scope.thisObj, args)
|
||||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||||
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
writeCallback?.let { callback ->
|
writeCallback?.callOn(execBase.createChildScope(Arguments(ObjString(name), newValue)))
|
||||||
callCallback(callback, execBase.createChildScope(Arguments(ObjString(name), newValue)))
|
|
||||||
}
|
|
||||||
?: super.writeField(scope, name, newValue)
|
?: super.writeField(scope, name, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
return readCallback?.let { callback ->
|
return readCallback?.callOn(execBase.createChildScope(Arguments(index)))
|
||||||
callCallback(callback, execBase.createChildScope(Arguments(index)))
|
|
||||||
}
|
|
||||||
?: super.getAt(scope, index)
|
?: super.getAt(scope, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||||
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
writeCallback?.let { callback ->
|
writeCallback?.callOn(execBase.createChildScope(Arguments(index, newValue)))
|
||||||
callCallback(callback, execBase.createChildScope(Arguments(index, newValue)))
|
|
||||||
}
|
|
||||||
?: super.putAt(scope, index, newValue)
|
?: super.putAt(scope, index, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,7 +124,7 @@ open class ObjDynamic(var readCallback: Obj? = null, var writeCallback: Obj? = n
|
|||||||
// Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames.
|
// Snapshot the caller scope to capture locals/args even if the runtime pools/reuses frames.
|
||||||
// Module scope should stay late-bound to allow extern class rebinding and similar updates.
|
// Module scope should stay late-bound to allow extern class rebinding and similar updates.
|
||||||
delegate.builderScope = if (scope is net.sergeych.lyng.ModuleScope) null else scope.snapshotForClosure()
|
delegate.builderScope = if (scope is net.sergeych.lyng.ModuleScope) null else scope.snapshotForClosure()
|
||||||
(builder as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(buildScope) ?: builder.callOn(buildScope)
|
builder.callOn(buildScope)
|
||||||
return delegate
|
return delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -395,17 +395,8 @@ suspend fun Obj.getLyngExceptionMessageWithStackTrace(scope: Scope? = null,showD
|
|||||||
var at = "unknown"
|
var at = "unknown"
|
||||||
val stack = if (!trace.list.isEmpty()) {
|
val stack = if (!trace.list.isEmpty()) {
|
||||||
val first = trace.list[0]
|
val first = trace.list[0]
|
||||||
suspend fun formatTraceEntry(entry: Obj): String {
|
at = (first.readField(s, "at").value as ObjString).value
|
||||||
return when (entry) {
|
"\n" + trace.list.map { " at " + it.toString(s).value }.joinToString("\n")
|
||||||
is ObjString -> entry.value.removePrefix("#")
|
|
||||||
else -> entry.toString(s).value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
at = when (first) {
|
|
||||||
is ObjString -> formatTraceEntry(first)
|
|
||||||
else -> (first.readField(s, "at").value as ObjString).value
|
|
||||||
}
|
|
||||||
"\n" + trace.list.map { " at " + formatTraceEntry(it) }.joinToString("\n")
|
|
||||||
} else {
|
} else {
|
||||||
val pos = s.pos
|
val pos = s.pos
|
||||||
if (pos.source.fileName.isNotEmpty() && pos.currentLine.isNotEmpty()) {
|
if (pos.source.fileName.isNotEmpty() && pos.currentLine.isNotEmpty()) {
|
||||||
|
|||||||
@ -81,7 +81,7 @@ private suspend fun createLyngFlowInput(scope: Scope, producer: Obj, ownerSessio
|
|||||||
val runProducer: suspend CoroutineScope.() -> Unit = {
|
val runProducer: suspend CoroutineScope.() -> Unit = {
|
||||||
var failure: Throwable? = null
|
var failure: Throwable? = null
|
||||||
try {
|
try {
|
||||||
(producer as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(builderScope) ?: producer.callOn(builderScope)
|
producer.callOn(builderScope)
|
||||||
} catch (x: ScriptFlowIsNoMoreCollected) {
|
} catch (x: ScriptFlowIsNoMoreCollected) {
|
||||||
// premature flow closing, OK
|
// premature flow closing, OK
|
||||||
} catch (x: Throwable) {
|
} catch (x: Throwable) {
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
import net.sergeych.lyng.Arguments
|
||||||
import net.sergeych.lyng.CallSignature
|
|
||||||
import net.sergeych.lyng.miniast.ParamDoc
|
import net.sergeych.lyng.miniast.ParamDoc
|
||||||
import net.sergeych.lyng.miniast.addFnDoc
|
import net.sergeych.lyng.miniast.addFnDoc
|
||||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||||
@ -190,13 +189,7 @@ val ObjIterable by lazy {
|
|||||||
doc = "Build a map from elements using the lambda result as key.",
|
doc = "Build a map from elements using the lambda result as key.",
|
||||||
params = listOf(ParamDoc("keySelector")),
|
params = listOf(ParamDoc("keySelector")),
|
||||||
returns = type("lyng.Map"),
|
returns = type("lyng.Map"),
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib"
|
||||||
callSignature = CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.ITERABLE,
|
|
||||||
result = CallSignature.ResultMode.ASSOCIATE_BY
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
val association = requireOnlyArg<Obj>()
|
val association = requireOnlyArg<Obj>()
|
||||||
val result = ObjMap()
|
val result = ObjMap()
|
||||||
@ -211,13 +204,7 @@ val ObjIterable by lazy {
|
|||||||
doc = "Apply the lambda to each element in iteration order.",
|
doc = "Apply the lambda to each element in iteration order.",
|
||||||
params = listOf(ParamDoc("action")),
|
params = listOf(ParamDoc("action")),
|
||||||
isOpen = true,
|
isOpen = true,
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib"
|
||||||
callSignature = CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.ITERABLE,
|
|
||||||
result = CallSignature.ResultMode.FOR_EACH
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
val scope = requireScope()
|
val scope = requireScope()
|
||||||
val it = thisObj.invokeInstanceMethod(scope, "iterator")
|
val it = thisObj.invokeInstanceMethod(scope, "iterator")
|
||||||
@ -235,13 +222,7 @@ val ObjIterable by lazy {
|
|||||||
params = listOf(ParamDoc("transform")),
|
params = listOf(ParamDoc("transform")),
|
||||||
returns = type("lyng.List"),
|
returns = type("lyng.List"),
|
||||||
isOpen = true,
|
isOpen = true,
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib"
|
||||||
callSignature = CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.ITERABLE,
|
|
||||||
result = CallSignature.ResultMode.MAP
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
val fn = requiredArg<Obj>(0)
|
val fn = requiredArg<Obj>(0)
|
||||||
val result = mutableListOf<Obj>()
|
val result = mutableListOf<Obj>()
|
||||||
@ -257,13 +238,7 @@ val ObjIterable by lazy {
|
|||||||
params = listOf(ParamDoc("transform")),
|
params = listOf(ParamDoc("transform")),
|
||||||
returns = type("lyng.List"),
|
returns = type("lyng.List"),
|
||||||
isOpen = true,
|
isOpen = true,
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib"
|
||||||
callSignature = CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.ITERABLE,
|
|
||||||
result = CallSignature.ResultMode.MAP_NOT_NULL
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
val fn = requiredArg<Obj>(0)
|
val fn = requiredArg<Obj>(0)
|
||||||
val result = mutableListOf<Obj>()
|
val result = mutableListOf<Obj>()
|
||||||
|
|||||||
@ -165,9 +165,9 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal constructor(intValues: LongArray, size: Int = intValues.size) : this(mutableListOf()) {
|
internal constructor(intValues: LongArray) : this(mutableListOf()) {
|
||||||
primitiveIntList = intValues
|
primitiveIntList = intValues
|
||||||
primitiveIntSize = size
|
primitiveIntSize = intValues.size
|
||||||
boxedList = null
|
boxedList = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -519,8 +519,8 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
doc = "Append one or more elements to the end of this list.",
|
doc = "Append one or more elements to the end of this list.",
|
||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
val l = thisAs<ObjList>()
|
val l = thisAs<ObjList>().list
|
||||||
for (a in args) l.appendFast(a)
|
for (a in args) l.add(a)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
|
|||||||
@ -19,7 +19,6 @@ package net.sergeych.lyng.obj
|
|||||||
|
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import net.sergeych.lyng.CallSignature
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lynon.LynonDecoder
|
import net.sergeych.lynon.LynonDecoder
|
||||||
@ -263,15 +262,7 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
|||||||
doc = "Get value by key or compute, store, and return the default from a lambda.",
|
doc = "Get value by key or compute, store, and return the default from a lambda.",
|
||||||
params = listOf(ParamDoc("key"), ParamDoc("default")),
|
params = listOf(ParamDoc("key"), ParamDoc("default")),
|
||||||
returns = type("lyng.Any"),
|
returns = type("lyng.Any"),
|
||||||
moduleName = "lyng.stdlib",
|
moduleName = "lyng.stdlib"
|
||||||
callSignature = CallSignature(
|
|
||||||
inlineHigherOrder = CallSignature.HigherOrderInline(
|
|
||||||
kind = CallSignature.Kind.MAP_GET_OR_PUT,
|
|
||||||
result = CallSignature.ResultMode.BLOCK_RESULT,
|
|
||||||
argCount = 2,
|
|
||||||
lambdaArgIndex = 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
val key = requiredArg<Obj>(0)
|
val key = requiredArg<Obj>(0)
|
||||||
thisAs<ObjMap>().map.getOrPut(key) {
|
thisAs<ObjMap>().map.getOrPut(key) {
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
import net.sergeych.lyng.Arguments
|
||||||
import net.sergeych.lyng.BytecodeCallable
|
|
||||||
import net.sergeych.lyng.BytecodeBodyProvider
|
import net.sergeych.lyng.BytecodeBodyProvider
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
@ -42,16 +41,14 @@ class ObjProperty(
|
|||||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
||||||
val execScope = scope.applyClosure(instanceScope).createChildScope(newThisObj = instance)
|
val execScope = scope.applyClosure(instanceScope).createChildScope(newThisObj = instance)
|
||||||
execScope.currentClassCtx = declaringClass
|
execScope.currentClassCtx = declaringClass
|
||||||
(g as? BytecodeCallable)?.callOnFast(execScope)?.let { return it }
|
|
||||||
return when (g) {
|
return when (g) {
|
||||||
is BytecodeStatement -> executeBytecodeWithSeed(execScope, g, "property getter")
|
is BytecodeStatement -> executeBytecodeWithSeed(execScope, g, "property getter")
|
||||||
is BytecodeBodyProvider -> {
|
is BytecodeBodyProvider -> {
|
||||||
val body = g.bytecodeBody()
|
val body = g.bytecodeBody()
|
||||||
if (body != null) executeBytecodeWithSeed(execScope, body, "property getter")
|
if (body != null) executeBytecodeWithSeed(execScope, body, "property getter") else g.callOn(execScope)
|
||||||
else (g as? BytecodeCallable)?.callOnFast(execScope) ?: g.callOn(execScope)
|
|
||||||
}
|
}
|
||||||
is Statement -> (g as? BytecodeCallable)?.callOnFast(execScope) ?: g.callOn(execScope)
|
is Statement -> g.callOn(execScope)
|
||||||
else -> (g as? BytecodeCallable)?.callOnFast(execScope) ?: g.callOn(execScope)
|
else -> g.callOn(execScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,16 +59,14 @@ class ObjProperty(
|
|||||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
||||||
val execScope = scope.applyClosure(instanceScope).createChildScope(args = Arguments(value), newThisObj = instance)
|
val execScope = scope.applyClosure(instanceScope).createChildScope(args = Arguments(value), newThisObj = instance)
|
||||||
execScope.currentClassCtx = declaringClass
|
execScope.currentClassCtx = declaringClass
|
||||||
(s as? BytecodeCallable)?.callOnFast(execScope)?.let { return }
|
|
||||||
when (s) {
|
when (s) {
|
||||||
is BytecodeStatement -> executeBytecodeWithSeed(execScope, s, "property setter")
|
is BytecodeStatement -> executeBytecodeWithSeed(execScope, s, "property setter")
|
||||||
is BytecodeBodyProvider -> {
|
is BytecodeBodyProvider -> {
|
||||||
val body = s.bytecodeBody()
|
val body = s.bytecodeBody()
|
||||||
if (body != null) executeBytecodeWithSeed(execScope, body, "property setter")
|
if (body != null) executeBytecodeWithSeed(execScope, body, "property setter") else s.callOn(execScope)
|
||||||
else (s as? BytecodeCallable)?.callOnFast(execScope) ?: s.callOn(execScope)
|
|
||||||
}
|
}
|
||||||
is Statement -> (s as? BytecodeCallable)?.callOnFast(execScope) ?: s.callOn(execScope)
|
is Statement -> s.callOn(execScope)
|
||||||
else -> (s as? BytecodeCallable)?.callOnFast(execScope) ?: s.callOn(execScope)
|
else -> s.callOn(execScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,26 +38,17 @@ class ImportManager(
|
|||||||
|
|
||||||
val packageNames: List<String> get() = imports.keys.toList()
|
val packageNames: List<String> get() = imports.keys.toList()
|
||||||
|
|
||||||
private class CacheCell(var scope: ModuleScope? = null)
|
|
||||||
|
|
||||||
private inner class Entry(
|
private inner class Entry(
|
||||||
val packageName: String,
|
val packageName: String,
|
||||||
val builder: suspend (ModuleScope) -> Unit,
|
val builder: suspend (ModuleScope) -> Unit,
|
||||||
val cacheCell: CacheCell = CacheCell()
|
var cachedScope: ModuleScope? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun getScope(pos: Pos): ModuleScope {
|
suspend fun getScope(pos: Pos): ModuleScope {
|
||||||
cacheCell.scope?.let { return it }
|
cachedScope?.let { return it }
|
||||||
val module = ModuleScope(inner, pos, packageName)
|
return ModuleScope(inner, pos, packageName).apply {
|
||||||
cacheCell.scope = module
|
cachedScope = this
|
||||||
return try {
|
builder(this)
|
||||||
builder(module)
|
|
||||||
module
|
|
||||||
} catch (e: Throwable) {
|
|
||||||
if (cacheCell.scope === module) {
|
|
||||||
cacheCell.scope = null
|
|
||||||
}
|
|
||||||
throw e
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -161,14 +152,14 @@ class ImportManager(
|
|||||||
op.withLock {
|
op.withLock {
|
||||||
ImportManager(rootScope, securityManager).apply {
|
ImportManager(rootScope, securityManager).apply {
|
||||||
for ((name, entry) in this@ImportManager.imports) {
|
for ((name, entry) in this@ImportManager.imports) {
|
||||||
imports[name] = Entry(entry.packageName, entry.builder, entry.cacheCell)
|
imports[name] = Entry(entry.packageName, entry.builder, entry.cachedScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invalidatePackageCache(name: String) {
|
fun invalidatePackageCache(name: String) {
|
||||||
op.withLock {
|
op.withLock {
|
||||||
imports[name]?.cacheCell?.scope = null
|
imports[name]?.cachedScope = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -58,10 +58,7 @@ abstract class Statement(
|
|||||||
val type = ObjClass("Callable")
|
val type = ObjClass("Callable")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun call(scope: Scope, vararg args: Obj): Obj {
|
suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.createChildScope(args = Arguments(*args)))
|
||||||
val child = scope.createChildScope(args = Arguments(*args))
|
|
||||||
return (this as? BytecodeCallable)?.callOnFast(child) ?: execute(child)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun bytecodeOnly(scope: Scope, label: String): Nothing {
|
protected fun bytecodeOnly(scope: Scope, label: String): Nothing {
|
||||||
return scope.raiseIllegalState("bytecode-only execution is required; $label needs compiled bytecode")
|
return scope.raiseIllegalState("bytecode-only execution is required; $label needs compiled bytecode")
|
||||||
|
|||||||
@ -213,24 +213,6 @@ class BytecodeRecentOpsTest {
|
|||||||
assertEquals(4, scope.eval("calc()").toInt())
|
assertEquals(4, scope.eval("calc()").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun listFillWithCapacityUsesPrimitiveCapacityBytecode() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc() {
|
|
||||||
val xs = List.fill(5, 12) { it * 2 }
|
|
||||||
xs.add(99)
|
|
||||||
xs[0] + xs[4] + xs[5]
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("LIST_NEW_INT_CAP"), disasm)
|
|
||||||
assertFalse(disasm.contains("LIST_FILL_INT_CAP"), disasm)
|
|
||||||
assertEquals(107, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun directLambdaLiteralCallUsesInlineBytecode() = runTest {
|
fun directLambdaLiteralCallUsesInlineBytecode() = runTest {
|
||||||
val scope = Script.newScope()
|
val scope = Script.newScope()
|
||||||
@ -247,21 +229,6 @@ class BytecodeRecentOpsTest {
|
|||||||
assertEquals(11, scope.eval("calc()").toInt())
|
assertEquals(11, scope.eval("calc()").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun capturedLambdaCanCallListFillOnCapturedClassReceiver() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
val result = scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc(n: Int) {
|
|
||||||
val xs = { List.fill(n) { it } }()
|
|
||||||
xs[4]
|
|
||||||
}
|
|
||||||
calc(5)
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
assertEquals(4, result.toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun directLambdaLiteralCallWithCaptureUsesInlineBytecode() = runTest {
|
fun directLambdaLiteralCallWithCaptureUsesInlineBytecode() = runTest {
|
||||||
val scope = Script.newScope()
|
val scope = Script.newScope()
|
||||||
@ -379,106 +346,6 @@ class BytecodeRecentOpsTest {
|
|||||||
assertEquals(12, scope.eval("calc()").toInt())
|
assertEquals(12, scope.eval("calc()").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun nestedInlineLambdaParamCallAvoidsCallSlot() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc() {
|
|
||||||
{ g -> g(10) }({ x -> x + 1 })
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(11, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun optionalExactLambdaCallUsesInlineBytecode() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
type IntFn = (Int)->Int
|
|
||||||
fun calc() {
|
|
||||||
val f: IntFn? = { x -> x + 1 }
|
|
||||||
f?(10)
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(11, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun conditionalExactLambdaCallUsesInlineBytecode() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
val base = { x -> x + 1 }
|
|
||||||
fun calc(flag: Bool) {
|
|
||||||
(if(flag) base else base)(10)
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(11, scope.eval("calc(true)").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun elvisExactLambdaCallUsesInlineBytecode() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
val base = { x -> x + 1 }
|
|
||||||
fun calc() {
|
|
||||||
(null ?: base)(10)
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(11, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun castExactLambdaCallUsesInlineBytecode() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
type IntFn = (Int)->Int
|
|
||||||
val base: IntFn = { x -> x + 1 }
|
|
||||||
fun calc() {
|
|
||||||
(base as IntFn)(10)
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(11, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun whenExactLambdaCallUsesInlineBytecode() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
val base = { x -> x + 1 }
|
|
||||||
fun calc(flag: Bool) {
|
|
||||||
(when(flag) {
|
|
||||||
true -> base
|
|
||||||
else -> base
|
|
||||||
})(10)
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(11, scope.eval("calc(true)").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun letLiteralUsesInlineBytecode() = runTest {
|
fun letLiteralUsesInlineBytecode() = runTest {
|
||||||
val scope = Script.newScope()
|
val scope = Script.newScope()
|
||||||
@ -634,166 +501,6 @@ class BytecodeRecentOpsTest {
|
|||||||
assertEquals(6, scope.eval("calc()").toInt())
|
assertEquals(6, scope.eval("calc()").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun mapLiteralUsesDirectConstructorCall() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc() {
|
|
||||||
val m = { a: 1, b: 2 }
|
|
||||||
m.size
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("CALL_DIRECT"), disasm)
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(2, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun mapEntryLiteralUsesDirectConstructorCall() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc() {
|
|
||||||
val e = "a" => 2
|
|
||||||
e.value
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("CALL_DIRECT"), disasm)
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(2, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun constructorNameUsesDirectCall() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc() {
|
|
||||||
Map().size
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("CALL_DIRECT"), disasm)
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(0, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun constructorAliasUsesDirectCall() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc() {
|
|
||||||
val ctor = Map
|
|
||||||
val m = ctor() as Map
|
|
||||||
m.size
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("CALL_DIRECT"), disasm)
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(0, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun ifExpressionConstructorAliasUsesDirectCall() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc(flag: Bool) {
|
|
||||||
val ctor = if(flag) Map else Map
|
|
||||||
val m = ctor() as Map
|
|
||||||
m.size
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("CALL_DIRECT"), disasm)
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(0, scope.eval("calc(true)").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun elvisConstructorAliasUsesDirectCall() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc() {
|
|
||||||
val ctor = null ?: Map
|
|
||||||
val m = ctor() as Map
|
|
||||||
m.size
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("CALL_DIRECT"), disasm)
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(0, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun whenConstructorAliasUsesDirectCall() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc(flag: Bool) {
|
|
||||||
val ctor = when(flag) {
|
|
||||||
true -> Map
|
|
||||||
else -> Map
|
|
||||||
}
|
|
||||||
val m = ctor() as Map
|
|
||||||
m.size
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("CALL_DIRECT"), disasm)
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(0, scope.eval("calc(true)").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun localNamedFunctionUsesDirectCall() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc() {
|
|
||||||
fun twice(x: Int) { x * 2 }
|
|
||||||
twice(3)
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("CALL_DIRECT"), disasm)
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(6, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun localNamedFunctionAliasUsesDirectCall() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
fun calc() {
|
|
||||||
fun twice(x: Int) { x * 2 }
|
|
||||||
val f = twice
|
|
||||||
f(3)
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
val disasm = scope.disassembleSymbol("calc")
|
|
||||||
assertTrue(disasm.contains("CALL_DIRECT"), disasm)
|
|
||||||
assertFalse(disasm.contains("CALL_SLOT"), disasm)
|
|
||||||
assertEquals(6, scope.eval("calc()").toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun optionalIndexPreIncSkipsOnNullReceiver() = runTest {
|
fun optionalIndexPreIncSkipsOnNullReceiver() = runTest {
|
||||||
eval(
|
eval(
|
||||||
|
|||||||
@ -16,17 +16,11 @@
|
|||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.Compiler
|
import net.sergeych.lyng.Compiler
|
||||||
import net.sergeych.lyng.Arguments
|
|
||||||
import net.sergeych.lyng.Pos
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Script
|
import net.sergeych.lyng.Script
|
||||||
import net.sergeych.lyng.ScriptError
|
import net.sergeych.lyng.ScriptError
|
||||||
import net.sergeych.lyng.Source
|
import net.sergeych.lyng.Source
|
||||||
import net.sergeych.lyng.Statement
|
|
||||||
import net.sergeych.lyng.asFacade
|
import net.sergeych.lyng.asFacade
|
||||||
import net.sergeych.lyng.obj.ObjDynamic
|
|
||||||
import net.sergeych.lyng.obj.ObjInt
|
|
||||||
import net.sergeych.lyng.obj.ObjList
|
|
||||||
import net.sergeych.lyng.obj.ObjString
|
import net.sergeych.lyng.obj.ObjString
|
||||||
import net.sergeych.lyng.obj.toInt
|
import net.sergeych.lyng.obj.toInt
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
@ -74,235 +68,6 @@ class CompilerVmReviewRegressionTest {
|
|||||||
assertContains(ex.errorMessage, "module binding 'answer'")
|
assertContains(ex.errorMessage, "module binding 'answer'")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun facadeCallUsesPreparedLambdaWithArgs() = runTest {
|
|
||||||
val lambda = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<facade-call-lambda>",
|
|
||||||
"""
|
|
||||||
val base = 2
|
|
||||||
{ x -> x + base }
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
|
|
||||||
val scope = Script.newScope()
|
|
||||||
val callable = lambda.execute(scope)
|
|
||||||
assertEquals(42, scope.asFacade().call(callable, Arguments(ObjInt.of(40))).toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun genericInvokeHelpersUsePreparedLambdaEntryPoints() = runTest {
|
|
||||||
val unaryLambda = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<generic-invoke-unary>",
|
|
||||||
"""
|
|
||||||
val delta = 2
|
|
||||||
{ x -> x + delta }
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
val nullaryLambda = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<generic-invoke-nullary>",
|
|
||||||
"""
|
|
||||||
val base = 7
|
|
||||||
{ base }
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
|
|
||||||
val unaryScope = Script.newScope()
|
|
||||||
val nullaryScope = Script.newScope()
|
|
||||||
val unaryCallable = unaryLambda.execute(unaryScope)
|
|
||||||
val nullaryCallable = nullaryLambda.execute(nullaryScope)
|
|
||||||
|
|
||||||
assertEquals(42, unaryCallable.invoke(unaryScope, ObjString("receiver"), ObjInt.of(40)).toInt())
|
|
||||||
assertEquals(7, nullaryCallable.invoke(nullaryScope, ObjString("receiver")).toInt())
|
|
||||||
assertEquals(
|
|
||||||
42,
|
|
||||||
unaryCallable.invoke(
|
|
||||||
unaryScope,
|
|
||||||
Pos(Source("<generic-invoke-pos>", ""), 0, 0),
|
|
||||||
ObjString("receiver"),
|
|
||||||
Arguments(ObjInt.of(40))
|
|
||||||
).toInt()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun statementCallUsesPreparedLambdaFastPath() = runTest {
|
|
||||||
val unaryLambda = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<statement-call-unary>",
|
|
||||||
"""
|
|
||||||
val delta = 2
|
|
||||||
{ x -> x + delta }
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
|
|
||||||
val scope = Script.newScope()
|
|
||||||
val callable = unaryLambda.execute(scope) as Statement
|
|
||||||
|
|
||||||
assertEquals(42, callable.call(scope, ObjInt.of(40)).toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun preparedLambdaKeepsImmutableModuleCaptureAcrossOtherScriptsInSameScope() = runTest {
|
|
||||||
val unaryLambda = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<cross-script-capture-unary>",
|
|
||||||
"""
|
|
||||||
val delta = 2
|
|
||||||
{ x -> x + delta }
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
val unrelatedScript = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<cross-script-capture-unrelated>",
|
|
||||||
"""
|
|
||||||
val base = 7
|
|
||||||
base
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
|
|
||||||
val scope = Script.newScope()
|
|
||||||
val callable = unaryLambda.execute(scope) as Statement
|
|
||||||
unrelatedScript.execute(scope)
|
|
||||||
|
|
||||||
assertEquals(42, callable.call(scope, ObjInt.of(40)).toInt())
|
|
||||||
assertEquals(42, scope.asFacade().call(callable, Arguments(ObjInt.of(40))).toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun dynamicCallbacksUsePreparedLambdaFastPath() = runTest {
|
|
||||||
val script = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<dynamic-fast-callbacks>",
|
|
||||||
"""
|
|
||||||
var seen = ""
|
|
||||||
dynamic {
|
|
||||||
get { name -> name + seen }
|
|
||||||
set { name, value -> seen = name + "=" + value }
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
|
|
||||||
val scope = Script.newScope()
|
|
||||||
val dynamic = script.execute(scope) as ObjDynamic
|
|
||||||
|
|
||||||
assertEquals("foo", (dynamic.readField(scope, "foo").value as ObjString).value)
|
|
||||||
dynamic.writeField(scope, "foo", ObjInt.of(7))
|
|
||||||
assertEquals("barfoo=7", (dynamic.getAt(scope, ObjString("bar")) as ObjString).value)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun higherOrderMethodInliningSupportsCapturedValues() = runTest {
|
|
||||||
val script = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<higher-order-inline-captures>",
|
|
||||||
"""
|
|
||||||
val suffix = "!"
|
|
||||||
val offset = 10
|
|
||||||
var sum = 0
|
|
||||||
|
|
||||||
val letResult = "a".let { it + suffix }
|
|
||||||
val applyResult = List<Int>().apply { add(offset); add(offset + 1) }
|
|
||||||
val mapped = [1, 2, 3].map { it + offset }
|
|
||||||
val filtered = [1, 2, 3].filter { it + offset >= 12 }
|
|
||||||
val notNull = [1, 2, 3].mapNotNull { if (it + offset >= 12) it + offset else null }
|
|
||||||
val associated = [1, 2, 3].associateBy { "k" + (it + offset) }
|
|
||||||
[1, 2, 3].forEach { sum += it + offset }
|
|
||||||
|
|
||||||
[letResult, applyResult, mapped, filtered, notNull, associated, sum]
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
|
|
||||||
val scope = Script.newScope()
|
|
||||||
val result = script.execute(scope) as ObjList
|
|
||||||
|
|
||||||
assertEquals("a!", (result.list[0] as ObjString).value)
|
|
||||||
val applied = result.list[1] as ObjList
|
|
||||||
assertEquals(listOf(10, 11), applied.list.map { it.toInt() })
|
|
||||||
|
|
||||||
val mapped = result.list[2] as ObjList
|
|
||||||
assertEquals(listOf(11, 12, 13), mapped.list.map { it.toInt() })
|
|
||||||
|
|
||||||
val filtered = result.list[3] as ObjList
|
|
||||||
assertEquals(listOf(2, 3), filtered.list.map { it.toInt() })
|
|
||||||
|
|
||||||
val notNull = result.list[4] as ObjList
|
|
||||||
assertEquals(listOf(12, 13), notNull.list.map { it.toInt() })
|
|
||||||
|
|
||||||
val associated = result.list[5].toString(scope).value
|
|
||||||
assertContains(associated, "\"k11\" => 1")
|
|
||||||
assertContains(associated, "\"k12\" => 2")
|
|
||||||
assertContains(associated, "\"k13\" => 3")
|
|
||||||
|
|
||||||
assertEquals(36, result.list[6].toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun directLambdaInliningMatchesImplicitItInvocationSemantics() = runTest {
|
|
||||||
val script = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<direct-inline-it-semantics>",
|
|
||||||
"""
|
|
||||||
val zeroFn = { if (it == void) 1 else 0 }
|
|
||||||
val multiFn = { it }
|
|
||||||
val zero = zeroFn()
|
|
||||||
val multi = multiFn(1, 2, 3)
|
|
||||||
[zero, multi]
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
|
|
||||||
val scope = Script.newScope()
|
|
||||||
val result = script.execute(scope) as ObjList
|
|
||||||
|
|
||||||
assertEquals(1, result.list[0].toInt())
|
|
||||||
val multi = result.list[1] as ObjList
|
|
||||||
assertEquals(listOf(1, 2, 3), multi.list.map { it.toInt() })
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun mapGetOrPutUsesInlineDefaultLambda() = runTest {
|
|
||||||
val script = Compiler.compile(
|
|
||||||
Source(
|
|
||||||
"<map-get-or-put-inline>",
|
|
||||||
"""
|
|
||||||
val offset = 10
|
|
||||||
val m = Map()
|
|
||||||
val first = m.getOrPut("k") { offset + 1 }
|
|
||||||
val second = m.getOrPut("k") { offset + 2 }
|
|
||||||
[first, second, m["k"]]
|
|
||||||
""".trimIndent()
|
|
||||||
),
|
|
||||||
Script.defaultImportManager
|
|
||||||
)
|
|
||||||
|
|
||||||
val scope = Script.newScope()
|
|
||||||
val result = script.execute(scope) as ObjList
|
|
||||||
|
|
||||||
assertEquals(11, result.list[0].toInt())
|
|
||||||
assertEquals(11, result.list[1].toInt())
|
|
||||||
assertEquals(11, result.list[2].toInt())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun subjectlessWhenReportsScriptError() = runTest {
|
fun subjectlessWhenReportsScriptError() = runTest {
|
||||||
val ex = assertFailsWith<ScriptError> {
|
val ex = assertFailsWith<ScriptError> {
|
||||||
|
|||||||
@ -17,12 +17,10 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.obj.toInt
|
import net.sergeych.lyng.obj.toInt
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
|
||||||
import kotlin.time.TimeSource
|
import kotlin.time.TimeSource
|
||||||
|
|
||||||
class OptTest {
|
class OptTest {
|
||||||
@ -42,11 +40,9 @@ class OptTest {
|
|||||||
repeat(3) { pass ->
|
repeat(3) { pass ->
|
||||||
val size = scope.eval("buildArray(200000)").toInt()
|
val size = scope.eval("buildArray(200000)").toInt()
|
||||||
assertEquals(200000, size, "warmup pass ${pass + 1} failed")
|
assertEquals(200000, size, "warmup pass ${pass + 1} failed")
|
||||||
delay(100)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val passes = 3
|
||||||
val passes = 4
|
|
||||||
var bestMs = Long.MAX_VALUE
|
var bestMs = Long.MAX_VALUE
|
||||||
var totalMs = 0L
|
var totalMs = 0L
|
||||||
repeat(passes) { pass ->
|
repeat(passes) { pass ->
|
||||||
@ -60,25 +56,4 @@ class OptTest {
|
|||||||
}
|
}
|
||||||
println("add-to-array best=${bestMs}ms avg=${totalMs / passes}ms after warmup")
|
println("add-to-array best=${bestMs}ms avg=${totalMs / passes}ms after warmup")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testAddToArray2() = runTest {
|
|
||||||
eval(
|
|
||||||
$$"""
|
|
||||||
import lyng.time
|
|
||||||
val n = 700_000
|
|
||||||
fun tm<T>(block: ()->T): T {
|
|
||||||
val t = Instant()
|
|
||||||
block().also {
|
|
||||||
println("tm: ${Instant() - t}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val x = tm { List.fill(n) { it * 10 + 1 } }
|
|
||||||
val y = tm { List.fill(n, n + 10) { it * 10 + 1 } }
|
|
||||||
tm { x.add(-1) }
|
|
||||||
tm { y.add(-2) }
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,11 +13,6 @@ Current focus
|
|||||||
Key recent changes
|
Key recent changes
|
||||||
- Updated AI helper docs to reflect static typing, type expressions, and compile-time-only name resolution.
|
- Updated AI helper docs to reflect static typing, type expressions, and compile-time-only name resolution.
|
||||||
- Added stdlib random API: `Random` and deterministic `SeededRandom` with `nextInt`, `nextFloat`, and generic `next(range)`.
|
- Added stdlib random API: `Random` and deterministic `SeededRandom` with `nextInt`, `nextFloat`, and generic `next(range)`.
|
||||||
- Generalized primitive list optimization for compiler-generated `List.fill`:
|
|
||||||
- `List.fill(size) { intExpr }` and `List.fill(size, capacity) { intExpr }` now both have bytecode fast paths.
|
|
||||||
- Added `LIST_NEW_INT_CAP` / `LIST_FILL_INT_CAP` for the 3-arg capacity-preserving form.
|
|
||||||
- Fixed `ObjList.add(...)` to preserve primitive-int backing storage instead of forcing boxing through `.list`.
|
|
||||||
- `OptTest.testAddToArray2` no longer shows the old 10x anomaly for `List.fill(n, n + 10)` or append-to-extended-list.
|
|
||||||
|
|
||||||
Known failing tests
|
Known failing tests
|
||||||
- None in :lynglib:jvmTest after Random/SeededRandom integration.
|
- None in :lynglib:jvmTest after Random/SeededRandom integration.
|
||||||
|
|||||||
@ -22,25 +22,18 @@ Candidates (not started)
|
|||||||
6) Box/unbox audit (done)
|
6) Box/unbox audit (done)
|
||||||
- Unbox ObjInt/ObjReal in assign-op when target is INT/REAL to avoid boxing + obj ops.
|
- Unbox ObjInt/ObjReal in assign-op when target is INT/REAL to avoid boxing + obj ops.
|
||||||
- MixedCompareBenchmarkTest: 240 ms -> 234 ms.
|
- MixedCompareBenchmarkTest: 240 ms -> 234 ms.
|
||||||
7) Primitive list fill with capacity (done)
|
7) Mixed compare coverage
|
||||||
- Extended the compiler/runtime fast path from `List.fill(size) { intExpr }` to `List.fill(size, capacity) { intExpr }`.
|
|
||||||
- Added `LIST_NEW_INT_CAP` and `LIST_FILL_INT_CAP` so the 3-arg form keeps primitive-int storage instead of falling back to generic stdlib code.
|
|
||||||
- `OptTest.testAddToArray2`: `List.fill(n, n + 10) { ... }` dropped from the prior anomaly (~10x slower than 2-arg fill) to the same range as `List.fill(n) { ... }`, roughly `56-67 ms` vs `46-75 ms` after warmup.
|
|
||||||
8) Primitive list append preservation (done)
|
|
||||||
- Fixed `ObjList.add(...)` to append through the primitive-aware fast path instead of forcing `.list` and boxing the backing storage.
|
|
||||||
- `OptTest.testAddToArray2`: appending to the pre-extended list dropped from the prior anomaly (~10x slower) to sub-millisecond / low-millisecond timings (`~0.05-0.16 ms` for the extended list path, `~1.6-4.3 ms` for the baseline path, depending on warmup).
|
|
||||||
9) Mixed compare coverage
|
|
||||||
- Emit CMP_*_REAL when one operand is known ObjReal in more expression forms (not just assign-op).
|
- Emit CMP_*_REAL when one operand is known ObjReal in more expression forms (not just assign-op).
|
||||||
- Verify with disassembly that fast cmp opcodes are emitted.
|
- Verify with disassembly that fast cmp opcodes are emitted.
|
||||||
10) Range-loop invariant hoist
|
8) Range-loop invariant hoist
|
||||||
- Cache range end/step into temps once per loop; avoid repeated slot reads/boxing in body.
|
- Cache range end/step into temps once per loop; avoid repeated slot reads/boxing in body.
|
||||||
- Confirm no extra CONST_OBJ in hot path.
|
- Confirm no extra CONST_OBJ in hot path.
|
||||||
11) Boxing elision pass
|
9) Boxing elision pass
|
||||||
- Remove redundant BOX_OBJ when value feeds only primitive ops afterward (local liveness).
|
- Remove redundant BOX_OBJ when value feeds only primitive ops afterward (local liveness).
|
||||||
- Ensure no impact on closures/escaping values.
|
- Ensure no impact on closures/escaping values.
|
||||||
12) Closed-type fast paths expansion
|
10) Closed-type fast paths expansion
|
||||||
- Apply closed-type trust for ObjBool/ObjInt/ObjReal/ObjString in ternaries and conditional chains.
|
- Apply closed-type trust for ObjBool/ObjInt/ObjReal/ObjString in ternaries and conditional chains.
|
||||||
- Guard with exact non-null temp/slot checks only.
|
- Guard with exact non-null temp/slot checks only.
|
||||||
13) VM hot op micro-optimizations
|
11) VM hot op micro-optimizations
|
||||||
- Reduce frame reads/writes in ADD_INT, MUL_REAL, CMP_*_INT/REAL when operands are temps.
|
- Reduce frame reads/writes in ADD_INT, MUL_REAL, CMP_*_INT/REAL when operands are temps.
|
||||||
- Compare against baseline; revert if regression after 10-run median.
|
- Compare against baseline; revert if regression after 10-run median.
|
||||||
|
|||||||
@ -1,146 +0,0 @@
|
|||||||
# Non-Suspending Call Optimization Plan
|
|
||||||
|
|
||||||
## Current state
|
|
||||||
|
|
||||||
Completed in the current phase:
|
|
||||||
|
|
||||||
- Higher-order lambda inlining is now metadata-driven through `CallSignature`.
|
|
||||||
- Built-in member methods (`let`, `also`, `apply`, `run`, `forEach`, `map`, `mapNotNull`, `associateBy`, `getOrPut`) publish inline metadata at declaration sites.
|
|
||||||
- Lyng extension wrappers now preserve and expose `callSignature`, so extension methods such as `Iterable.filter` use the same inlining path as built-in members.
|
|
||||||
- `BytecodeCompiler` no longer relies on a backend hardcoded name table for these higher-order inlining cases.
|
|
||||||
- JVM tests are green after the metadata move.
|
|
||||||
|
|
||||||
Primary motivation remains unchanged: suspend call overhead is still significant, and lambda inlining only removes part of it.
|
|
||||||
|
|
||||||
## Constraints
|
|
||||||
|
|
||||||
- Keep source positions, stack traces, and throw-site reporting correct.
|
|
||||||
- Do not reintroduce one-off special cases tied to specific stdlib method names.
|
|
||||||
- Prefer declaration metadata and reusable compiler/runtime mechanisms.
|
|
||||||
- Preserve Kotlin Multiplatform compatibility in `commonMain`.
|
|
||||||
- Avoid changing public language semantics just to optimize the runtime path.
|
|
||||||
|
|
||||||
## Why this is the next step
|
|
||||||
|
|
||||||
Lambda inlining helps when the callee body is directly available at the call site.
|
|
||||||
The next large remaining cost is calling compiled functions through suspend entry points even when the generated body never suspends.
|
|
||||||
|
|
||||||
That suggests a second optimization track:
|
|
||||||
|
|
||||||
1. detect bytecode callables that are safe to execute through a non-suspending fast path;
|
|
||||||
2. route direct calls to that path when the caller can prove it is safe;
|
|
||||||
3. keep the suspend path as the fallback for correctness.
|
|
||||||
|
|
||||||
## Proposed phases
|
|
||||||
|
|
||||||
### Phase 1: Define "non-suspending compiled callable"
|
|
||||||
|
|
||||||
Add explicit metadata on compiled functions / lambdas indicating whether their bytecode body may suspend.
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
|
|
||||||
- Computed once during bytecode generation.
|
|
||||||
- Conservative: false negatives are acceptable; false positives are not.
|
|
||||||
- Must account for:
|
|
||||||
- direct suspend-capable call opcodes;
|
|
||||||
- flow / coroutine constructs;
|
|
||||||
- delegated runtime helpers that may suspend;
|
|
||||||
- nested lambda creation if invocation may suspend.
|
|
||||||
|
|
||||||
Likely implementation direction:
|
|
||||||
|
|
||||||
- store `maySuspend` or `fastOnly`-adjacent metadata on `CmdFunction` or the callable wrapper;
|
|
||||||
- derive it from emitted bytecode opcodes and embedded lambda constants.
|
|
||||||
|
|
||||||
### Phase 2: Add a direct non-suspending invoke path
|
|
||||||
|
|
||||||
For bytecode callables proven non-suspending, add an execution entry point that avoids suspend machinery for ordinary direct calls.
|
|
||||||
|
|
||||||
Requirements:
|
|
||||||
|
|
||||||
- Reuse as much of the existing fast frame setup as possible.
|
|
||||||
- Keep exception translation and source mapping identical to the suspend path.
|
|
||||||
- Do not depend on JVM-only tricks.
|
|
||||||
|
|
||||||
Potential direction:
|
|
||||||
|
|
||||||
- extend `BytecodeCallable` with a capability query or richer fast-call API;
|
|
||||||
- let call sites choose among:
|
|
||||||
- inline body
|
|
||||||
- non-suspending compiled call
|
|
||||||
- existing suspend call
|
|
||||||
|
|
||||||
### Phase 3: Teach bytecode call sites to use it
|
|
||||||
|
|
||||||
Apply the new path only where the callee is known precisely.
|
|
||||||
|
|
||||||
Initial targets:
|
|
||||||
|
|
||||||
- direct lambda invocation where exact lambda ref is known but inlining is not possible;
|
|
||||||
- direct local function calls where the binding resolves to a compiled callable;
|
|
||||||
- extension wrapper calls where wrapper binding is known and non-suspending.
|
|
||||||
|
|
||||||
Do not start with dynamic dispatch or reflective calls.
|
|
||||||
|
|
||||||
### Phase 4: Validate behavioral fidelity
|
|
||||||
|
|
||||||
Must explicitly verify:
|
|
||||||
|
|
||||||
- thrown exceptions still report the same Lyng source positions;
|
|
||||||
- stack traces remain useful enough for debugging;
|
|
||||||
- optional calls / null propagation are unchanged;
|
|
||||||
- captures and implicit `this` still bind correctly.
|
|
||||||
|
|
||||||
### Phase 5: Measure before broadening
|
|
||||||
|
|
||||||
Benchmark after each widening step, especially:
|
|
||||||
|
|
||||||
- `OptTest.testAddToArray`
|
|
||||||
- iterable pipeline samples using `filter` / `map`
|
|
||||||
- direct lambda call microbenchmarks
|
|
||||||
- closure-heavy samples with captures
|
|
||||||
|
|
||||||
## Open technical questions
|
|
||||||
|
|
||||||
1. Where should non-suspending capability live?
|
|
||||||
- `CmdFunction`
|
|
||||||
- `BytecodeStatement`
|
|
||||||
- callable wrapper object
|
|
||||||
- `CallSignature`-adjacent metadata
|
|
||||||
|
|
||||||
2. Should the compiler emit a separate opcode for known non-suspending compiled calls, or should runtime dispatch pick the fast path from a normal call opcode?
|
|
||||||
|
|
||||||
3. Can we preserve the current error/stack behavior if we bypass suspend wrappers entirely, or do we need a thin compatibility layer?
|
|
||||||
|
|
||||||
4. Should capture-free and capture-heavy compiled lambdas share the same direct-call mechanism, or should captured callables stay on the safer path initially?
|
|
||||||
|
|
||||||
## Suggested order of execution
|
|
||||||
|
|
||||||
1. Add conservative `maySuspend` analysis for compiled bytecode functions.
|
|
||||||
2. Expose a non-suspending direct-call capability on compiled callables.
|
|
||||||
3. Use it for exact direct lambda calls first.
|
|
||||||
4. Extend to exact local function calls.
|
|
||||||
5. Re-measure.
|
|
||||||
6. Only then consider broader dispatch sites.
|
|
||||||
|
|
||||||
## Validation checklist
|
|
||||||
|
|
||||||
- `./gradlew :lynglib:compileKotlinJvm --console=plain`
|
|
||||||
- `./gradlew :lynglib:jvmTest --tests net.sergeych.lyng.OptTest.testAddToArray --console=plain`
|
|
||||||
- `./gradlew :lynglib:jvmTest --tests StdlibTest.testIterableFilter --tests CompilerVmReviewRegressionTest --console=plain`
|
|
||||||
- `./gradlew :lynglib:jvmTest --console=plain`
|
|
||||||
|
|
||||||
## Notes from the completed phase
|
|
||||||
|
|
||||||
Relevant current commits before this follow-up work:
|
|
||||||
|
|
||||||
- `3be2892` Use fast compiled callbacks in dynamic and flow helpers
|
|
||||||
- `1d5caaa` Broaden lambda method inlining with captures
|
|
||||||
- `0c3242c` Generalize higher-order lambda inlining
|
|
||||||
- `f4ab2eb` Extend lambda inlining to getOrPut and implicit it calls
|
|
||||||
|
|
||||||
Current working tree phase adds:
|
|
||||||
|
|
||||||
- metadata-driven higher-order inlining through member and extension signatures;
|
|
||||||
- extension wrapper signature propagation;
|
|
||||||
- removal of the compiler-side higher-order name table fallback.
|
|
||||||
Loading…
x
Reference in New Issue
Block a user