Compare commits
20 Commits
f72cdfdf83
...
0973a6afeb
| Author | SHA1 | Date | |
|---|---|---|---|
| 0973a6afeb | |||
| 953f237ca3 | |||
| fbb5688696 | |||
| f4ab2ebab4 | |||
| 0c3242cbd8 | |||
| 1d5caaa836 | |||
| 3be2892025 | |||
| c80900c503 | |||
| ee634c8dff | |||
| 6c91b77a85 | |||
| 3721ee8332 | |||
| 97990f00ce | |||
| 80693e7690 | |||
| 029fe874fa | |||
| ffa64d691b | |||
| 30c9a5a565 | |||
| db3a780645 | |||
| fc7d26ee4b | |||
| a61b5a31be | |||
| 33d170f525 |
15
examples/fillspeed.lyng
Normal file
15
examples/fillspeed.lyng
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
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,6 +434,57 @@ 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,4 +16,12 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
interface BytecodeCallable
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
interface BytecodeCallable {
|
||||||
|
fun callOnFast(scope: Scope): Obj? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BytecodeArgCallable {
|
||||||
|
fun callWithArgsFast(scope: Scope, args: Arguments): Obj? = null
|
||||||
|
}
|
||||||
|
|||||||
@ -28,6 +28,7 @@ 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,5 +20,30 @@ 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,6 +187,7 @@ 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>> =
|
||||||
@ -265,6 +266,11 @@ 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) {
|
||||||
@ -291,6 +297,11 @@ 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)
|
||||||
@ -316,6 +327,11 @@ class Compiler(
|
|||||||
)
|
)
|
||||||
scopeSeedNames.add(callableName)
|
scopeSeedNames.add(callableName)
|
||||||
}
|
}
|
||||||
|
record.callSignature?.let { signature ->
|
||||||
|
if (!callSignatureByName.containsKey(callableName)) {
|
||||||
|
callSignatureByName[callableName] = signature
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -347,6 +363,11 @@ 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) {
|
||||||
@ -762,6 +783,29 @@ 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 {
|
||||||
@ -1429,6 +1473,11 @@ 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
|
||||||
}
|
}
|
||||||
@ -1899,11 +1948,12 @@ class Compiler(
|
|||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
knownClassNames = knownClassNamesForBytecode(),
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = knownObjectNamesForBytecode(),
|
||||||
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,
|
||||||
@ -2227,6 +2277,23 @@ 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
|
||||||
@ -2255,11 +2322,12 @@ class Compiler(
|
|||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
knownClassNames = knownClassNamesForBytecode(),
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = knownObjectNamesForBytecode(),
|
||||||
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,
|
||||||
@ -2289,11 +2357,12 @@ class Compiler(
|
|||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownClassMapForBytecode(),
|
knownNameObjClass = knownClassMapForBytecode(),
|
||||||
knownClassNames = knownClassNamesForBytecode(),
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = knownObjectNamesForBytecode(),
|
||||||
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,
|
||||||
@ -2348,11 +2417,12 @@ class Compiler(
|
|||||||
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
|
||||||
knownNameObjClass = knownNames,
|
knownNameObjClass = knownNames,
|
||||||
knownClassNames = knownClassNamesForBytecode(),
|
knownClassNames = knownClassNamesForBytecode(),
|
||||||
knownObjectNames = objectDeclNames,
|
knownObjectNames = knownObjectNamesForBytecode(),
|
||||||
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,
|
||||||
@ -3545,14 +3615,90 @@ 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 {
|
val stmt = object : Statement(), BytecodeBodyProvider, BytecodeCallable {
|
||||||
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
|
||||||
@ -3628,6 +3774,7 @@ 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,
|
||||||
@ -9131,7 +9278,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 = if (actualExtern) importManager.rootScope.getLocalRecordDirect(name)?.callSignature else null
|
val externCallSignature = declaredCallSignature(name, extTypeName, actualExtern)
|
||||||
|
|
||||||
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)
|
||||||
@ -9153,7 +9300,9 @@ 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)
|
||||||
}
|
}
|
||||||
@ -9436,9 +9585,8 @@ 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 {
|
|
||||||
scope.pos = start
|
|
||||||
|
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
// 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:
|
||||||
@ -9447,6 +9595,7 @@ 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,7 +118,14 @@ internal suspend fun executeFunctionDecl(
|
|||||||
scope.addExtension(
|
scope.addExtension(
|
||||||
type,
|
type,
|
||||||
spec.name,
|
spec.name,
|
||||||
ObjRecord(ObjUnset, isMutable = false, visibility = spec.visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply {
|
ObjRecord(
|
||||||
|
ObjUnset,
|
||||||
|
isMutable = false,
|
||||||
|
visibility = spec.visibility,
|
||||||
|
declaringClass = null,
|
||||||
|
type = ObjRecord.Type.Delegated,
|
||||||
|
callSignature = spec.externCallSignature
|
||||||
|
).apply {
|
||||||
delegate = finalDelegate
|
delegate = finalDelegate
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -135,7 +142,8 @@ 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
|
||||||
}
|
}
|
||||||
@ -204,6 +212,7 @@ 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(
|
||||||
@ -215,6 +224,7 @@ 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
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -227,6 +237,7 @@ 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 {
|
||||||
@ -245,7 +256,8 @@ 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,7 +66,11 @@ 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 {
|
||||||
return callee.callOn(scope.createChildScope(scope.pos, args = args, newThisObj = newThisObj))
|
if (newThisObj == null) {
|
||||||
|
(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,11 +108,7 @@ class Script(
|
|||||||
seedImportBindings(scope, seedScope)
|
seedImportBindings(scope, seedScope)
|
||||||
}
|
}
|
||||||
if (moduleSlotPlan.isNotEmpty()) {
|
if (moduleSlotPlan.isNotEmpty()) {
|
||||||
scope.applySlotPlan(moduleSlotPlan)
|
installModuleSlotPlan(scope)
|
||||||
for (name in moduleSlotPlan.keys) {
|
|
||||||
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
|
|
||||||
scope.updateSlotFor(name, record)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,13 +116,27 @@ 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()) {
|
||||||
scope.applySlotPlan(moduleSlotPlan)
|
installModuleSlotPlan(scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
for (name in moduleSlotPlan.keys) {
|
||||||
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
|
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
|
||||||
scope.updateSlotFor(name, record)
|
scope.updateSlotFor(name, record)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun seedModuleLocals(
|
private suspend fun seedModuleLocals(
|
||||||
frame: net.sergeych.lyng.bytecode.CmdFrame,
|
frame: net.sergeych.lyng.bytecode.CmdFrame,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -40,6 +40,7 @@ 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,19 +23,36 @@ 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) {
|
) : Statement(original.isStaticConst, original.isConst, original.returnType), BytecodeCallable {
|
||||||
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 declaredNames = function.constants
|
val fastResult = callOnFast(scope)
|
||||||
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
if (fastResult != null) return fastResult
|
||||||
.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 (declaredNames.contains(name)) continue
|
if (declaredLocalNames.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
|
||||||
@ -89,6 +106,7 @@ class BytecodeStatement private constructor(
|
|||||||
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||||
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||||
|
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(),
|
||||||
@ -129,6 +147,7 @@ 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,6 +112,7 @@ 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,
|
||||||
@ -128,8 +129,9 @@ class CmdBuilder {
|
|||||||
localSlotDelegated = localSlotDelegated,
|
localSlotDelegated = localSlotDelegated,
|
||||||
localSlotCaptures = localSlotCaptures,
|
localSlotCaptures = localSlotCaptures,
|
||||||
constants = constPool.toList(),
|
constants = constPool.toList(),
|
||||||
cmds = cmds.toTypedArray(),
|
cmds = cmdArray,
|
||||||
posByIp = posByInstr.toTypedArray()
|
posByIp = posByInstr.toTypedArray(),
|
||||||
|
fastOnly = computeFastOnlyBytecode(scopeSlotCount, cmdArray)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,6 +239,10 @@ 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 ->
|
||||||
@ -842,6 +848,8 @@ 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,6 +498,8 @@ 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)
|
||||||
@ -627,6 +629,10 @@ 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,6 +35,7 @@ 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" }
|
||||||
@ -71,3 +72,118 @@ 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,6 +57,64 @@ 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 {
|
||||||
@ -280,6 +338,11 @@ 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
|
||||||
@ -2301,6 +2364,11 @@ 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
|
||||||
@ -2322,6 +2390,11 @@ 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
|
||||||
@ -3217,10 +3290,21 @@ class CmdCallDirect(
|
|||||||
frame.ensureScope().raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
|
frame.ensureScope().raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val result = if (PerfFlags.SCOPE_POOL) {
|
val directFastResult = (callee as? BytecodeArgCallable)?.callWithArgsFast(frame.ensureScope(), args)
|
||||||
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
|
val result = if (directFastResult != null) {
|
||||||
|
directFastResult
|
||||||
|
} else if (PerfFlags.SCOPE_POOL) {
|
||||||
|
frame.ensureScope().withChildFrame(args) { child ->
|
||||||
|
(callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
callee.callOn(frame.ensureScope().createChildScope(frame.ensureScope().pos, args = args))
|
val scope = frame.ensureScope()
|
||||||
|
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
|
||||||
@ -3247,18 +3331,28 @@ class CmdCallSlot(
|
|||||||
frame.ensureScope().raiseUnset(message)
|
frame.ensureScope().raiseUnset(message)
|
||||||
}
|
}
|
||||||
val args = frame.buildArguments(argBase, argCount)
|
val args = frame.buildArguments(argBase, argCount)
|
||||||
val canPool = PerfFlags.SCOPE_POOL && callee !is Statement
|
|
||||||
val result = if (canPool) {
|
|
||||||
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
|
|
||||||
} else {
|
|
||||||
val scope = frame.ensureScope()
|
val scope = frame.ensureScope()
|
||||||
|
val directFastResult = (callee as? BytecodeArgCallable)?.callWithArgsFast(scope, args)
|
||||||
|
val canPool = PerfFlags.SCOPE_POOL && callee !is Statement
|
||||||
|
val result = if (directFastResult != null) {
|
||||||
|
directFastResult
|
||||||
|
} else if (canPool) {
|
||||||
|
frame.ensureScope().withChildFrame(args) { child ->
|
||||||
|
(callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callee.callOn(scope.createChildScope(scope.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
|
||||||
@ -3341,10 +3435,53 @@ 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.invokeImplicitIntArg(scope, i.toLong())
|
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 {
|
} else {
|
||||||
callable.callOn(scope.createChildScope(scope.pos, args = Arguments(ObjInt.of(i.toLong()))))
|
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")
|
||||||
|
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)
|
||||||
@ -3758,6 +3895,22 @@ 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,
|
||||||
@ -3862,6 +4015,7 @@ 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
|
||||||
@ -3883,10 +4037,18 @@ 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 {
|
) : Statement(), BytecodeCallable, BytecodeArgCallable {
|
||||||
|
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?
|
||||||
@ -3918,6 +4080,7 @@ 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
|
||||||
@ -3934,6 +4097,7 @@ 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
|
||||||
@ -3942,32 +4106,29 @@ class BytecodeLambdaCallable(
|
|||||||
|
|
||||||
fun supportsImplicitIntFillFastPath(): Boolean = argsDeclaration == null
|
fun supportsImplicitIntFillFastPath(): Boolean = argsDeclaration == null
|
||||||
|
|
||||||
suspend fun invokeImplicitIntArg(scope: Scope, arg: Long): Obj {
|
fun supportsDirectInvokeFastPath(): Boolean = supportsDirectInvokeFastPath
|
||||||
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
|
|
||||||
it.args = Arguments.EMPTY
|
private val fastPreboundLocalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
}
|
if (argsDeclaration == null) {
|
||||||
if (captureRecords != null) {
|
setOf("it")
|
||||||
context.captureRecords = captureRecords
|
} else {
|
||||||
context.captureNames = captureNames
|
argsDeclaration.params.mapTo(LinkedHashSet()) { it.name }
|
||||||
} else if (captureNames.isNotEmpty()) {
|
|
||||||
closureScope.raiseIllegalState("bytecode lambda capture records missing")
|
|
||||||
}
|
|
||||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
|
||||||
paramSlotPlan["it"]?.let { itSlot ->
|
|
||||||
frame.frame.setInt(itSlot, arg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return try {
|
|
||||||
CmdVm().execute(fn, context, Arguments.EMPTY, binder)
|
|
||||||
} catch (e: ReturnException) {
|
|
||||||
if (e.label == null || returnLabels.contains(e.label)) e.result
|
|
||||||
else throw e
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
private val supportsFastUndeclaredLocalInit: Boolean by lazy(LazyThreadSafetyMode.NONE) {
|
||||||
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
|
canFastSeedUndeclaredLocals(fn, declaredLocalNames, fastPreboundLocalNames)
|
||||||
it.args = scope.args
|
}
|
||||||
|
|
||||||
|
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
|
||||||
@ -3975,17 +4136,10 @@ class BytecodeLambdaCallable(
|
|||||||
} else if (captureNames.isNotEmpty()) {
|
} else if (captureNames.isNotEmpty()) {
|
||||||
closureScope.raiseIllegalState("bytecode lambda capture records missing")
|
closureScope.raiseIllegalState("bytecode lambda capture records missing")
|
||||||
}
|
}
|
||||||
if (argsDeclaration == null) {
|
return context
|
||||||
// Bound in the bytecode entry binder.
|
|
||||||
} else {
|
|
||||||
// args bound into frame slots in the bytecode entry binder
|
|
||||||
}
|
}
|
||||||
return try {
|
|
||||||
val declaredNames = fn.constants
|
private fun bindArgumentsFast(frame: CmdFrame, context: Scope, arguments: Arguments) {
|
||||||
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
|
||||||
.mapTo(mutableSetOf()) { it.name }
|
|
||||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
|
||||||
val slotPlan = fn.localSlotPlanByName()
|
|
||||||
if (argsDeclaration == null) {
|
if (argsDeclaration == null) {
|
||||||
val l = arguments.list
|
val l = arguments.list
|
||||||
val itValue: Obj = when (l.size) {
|
val itValue: Obj = when (l.size) {
|
||||||
@ -3993,7 +4147,7 @@ class BytecodeLambdaCallable(
|
|||||||
1 -> l[0]
|
1 -> l[0]
|
||||||
else -> ObjList(l.toMutableList())
|
else -> ObjList(l.toMutableList())
|
||||||
}
|
}
|
||||||
val itSlot = slotPlan["it"]
|
val itSlot = slotPlanByName["it"]
|
||||||
if (itSlot != null) {
|
if (itSlot != null) {
|
||||||
when (itValue) {
|
when (itValue) {
|
||||||
is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
|
is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
|
||||||
@ -4003,17 +4157,33 @@ class BytecodeLambdaCallable(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
argsDeclaration.assignToFrame(
|
argsDeclaration.assignToFrameFast(
|
||||||
context,
|
context,
|
||||||
arguments,
|
arguments,
|
||||||
slotPlan,
|
slotPlanByName,
|
||||||
frame.frame
|
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
|
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 (declaredNames.contains(name)) continue
|
if (declaredLocalNames.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
|
||||||
@ -4039,11 +4209,88 @@ class BytecodeLambdaCallable(
|
|||||||
frame.frame.setObj(i, value)
|
frame.frame.setObj(i, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CmdVm().execute(fn, context, scope.args, binder)
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return try {
|
||||||
|
val vm = CmdVm()
|
||||||
|
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) {
|
||||||
|
if (e.label == null || returnLabels.contains(e.label)) e.result
|
||||||
|
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) {
|
} catch (e: ReturnException) {
|
||||||
if (e.label == null || returnLabels.contains(e.label)) e.result else throw e
|
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 {
|
||||||
|
return invokeWithArgs(scope, scope.args)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CmdIterPush(internal val iterSlot: Int) : Cmd() {
|
class CmdIterPush(internal val iterSlot: Int) : Cmd() {
|
||||||
@ -4492,6 +4739,19 @@ 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 {
|
||||||
@ -5446,6 +5706,14 @@ 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,6 +190,8 @@ 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,7 +17,58 @@
|
|||||||
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,10 +99,11 @@ 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, code = code)
|
addFn(name, isOpen, visibility, callSignature = callSignature, 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 = "") {
|
||||||
@ -137,9 +138,10 @@ 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, code)
|
addClassFn(name, isOpen, callSignature, 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,6 +31,7 @@ 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,6 +24,7 @@ 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
|
||||||
@ -723,40 +724,39 @@ open class Obj {
|
|||||||
scope.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj {
|
private suspend fun invokeWithBoundScope(
|
||||||
val usePool = PerfFlags.SCOPE_POOL && this !is Statement
|
scope: Scope,
|
||||||
|
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
|
||||||
callOn(child)
|
(this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
val child = scope.createChildScope(atPos, 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 =
|
||||||
callOn(
|
invokeWithBoundScope(scope, Arguments(args.toList()), thisObj)
|
||||||
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 =
|
||||||
callOn(
|
invokeWithBoundScope(scope, Arguments.EMPTY, thisObj)
|
||||||
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 =
|
||||||
callOn(scope.createChildScope(atPos, args = args, newThisObj = thisObj))
|
invokeWithBoundScope(scope, args, thisObj, atPos = atPos)
|
||||||
|
|
||||||
|
|
||||||
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
|
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
|
||||||
@ -828,7 +828,13 @@ 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))
|
||||||
}
|
}
|
||||||
@ -836,7 +842,13 @@ 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()
|
||||||
@ -852,7 +864,13 @@ 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
|
||||||
@ -861,7 +879,13 @@ 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,7 +809,8 @@ 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.callOn(instance.instanceScope)
|
(initStmt as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(instance.instanceScope)
|
||||||
|
?: initStmt.callOn(instance.instanceScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@ -821,13 +822,14 @@ 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.callOn(execScope)
|
(ctor as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(execScope) ?: ctor.callOn(execScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj {
|
suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj {
|
||||||
return callOn(scope.createChildScope(Arguments(*plainArgs)))
|
val child = scope.createChildScope(Arguments(*plainArgs))
|
||||||
|
return (this as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: callOn(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -847,6 +849,7 @@ 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
|
||||||
@ -947,6 +950,7 @@ 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,
|
||||||
@ -973,7 +977,8 @@ 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]
|
||||||
@ -1014,6 +1019,7 @@ open class ObjClass(
|
|||||||
writeVisibility,
|
writeVisibility,
|
||||||
recordType = type,
|
recordType = type,
|
||||||
isTransient = isTransient,
|
isTransient = isTransient,
|
||||||
|
callSignature = callSignature,
|
||||||
fieldId = effectiveFieldId,
|
fieldId = effectiveFieldId,
|
||||||
methodId = effectiveMethodId
|
methodId = effectiveMethodId
|
||||||
)
|
)
|
||||||
@ -1033,6 +1039,7 @@ 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
|
||||||
@ -1040,7 +1047,8 @@ 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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1072,8 +1080,19 @@ open class ObjClass(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
||||||
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend net.sergeych.lyng.ScopeFacade.() -> Obj) {
|
fun addClassFn(
|
||||||
createClassField(name, ObjExternCallable.fromBridge { code() }, isOpen, type = ObjRecord.Type.Fun)
|
name: String,
|
||||||
|
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.callOn(child)
|
(block as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(child) ?: block.callOn(child)
|
||||||
}
|
}
|
||||||
registerBuiltinConversions(decimalClass)
|
registerBuiltinConversions(decimalClass)
|
||||||
registerInterop(decimalClass)
|
registerInterop(decimalClass)
|
||||||
|
|||||||
@ -64,13 +64,19 @@ 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?.callOn(execBase.createChildScope(Arguments(ObjString(name))))?.let {
|
return readCallback?.let { callback ->
|
||||||
|
callCallback(callback, execBase.createChildScope(Arguments(ObjString(name))))
|
||||||
|
}?.let {
|
||||||
if (writeCallback != null)
|
if (writeCallback != null)
|
||||||
it.asMutable
|
it.asMutable
|
||||||
else
|
else
|
||||||
@ -90,26 +96,34 @@ 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?.callOn(execBase.createChildScope(Arguments(ObjString(name))))
|
val over = readCallback?.let { callback ->
|
||||||
|
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?.callOn(execBase.createChildScope(Arguments(ObjString(name), newValue)))
|
writeCallback?.let { callback ->
|
||||||
|
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?.callOn(execBase.createChildScope(Arguments(index)))
|
return readCallback?.let { callback ->
|
||||||
|
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?.callOn(execBase.createChildScope(Arguments(index, newValue)))
|
writeCallback?.let { callback ->
|
||||||
|
callCallback(callback, execBase.createChildScope(Arguments(index, newValue)))
|
||||||
|
}
|
||||||
?: super.putAt(scope, index, newValue)
|
?: super.putAt(scope, index, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,7 +138,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.callOn(buildScope)
|
(builder as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(buildScope) ?: builder.callOn(buildScope)
|
||||||
return delegate
|
return delegate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -395,8 +395,17 @@ 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]
|
||||||
at = (first.readField(s, "at").value as ObjString).value
|
suspend fun formatTraceEntry(entry: Obj): String {
|
||||||
"\n" + trace.list.map { " at " + it.toString(s).value }.joinToString("\n")
|
return when (entry) {
|
||||||
|
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.callOn(builderScope)
|
(producer as? net.sergeych.lyng.BytecodeCallable)?.callOnFast(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,6 +18,7 @@
|
|||||||
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
|
||||||
@ -189,7 +190,13 @@ 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()
|
||||||
@ -204,7 +211,13 @@ 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")
|
||||||
@ -222,7 +235,13 @@ 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>()
|
||||||
@ -238,7 +257,13 @@ 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) : this(mutableListOf()) {
|
internal constructor(intValues: LongArray, size: Int = intValues.size) : this(mutableListOf()) {
|
||||||
primitiveIntList = intValues
|
primitiveIntList = intValues
|
||||||
primitiveIntSize = intValues.size
|
primitiveIntSize = 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>().list
|
val l = thisAs<ObjList>()
|
||||||
for (a in args) l.add(a)
|
for (a in args) l.appendFast(a)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
addFnDoc(
|
addFnDoc(
|
||||||
|
|||||||
@ -19,6 +19,7 @@ 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
|
||||||
@ -262,7 +263,15 @@ 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,6 +18,7 @@
|
|||||||
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
|
||||||
@ -41,14 +42,16 @@ 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") else g.callOn(execScope)
|
if (body != null) executeBytecodeWithSeed(execScope, body, "property getter")
|
||||||
|
else (g as? BytecodeCallable)?.callOnFast(execScope) ?: g.callOn(execScope)
|
||||||
}
|
}
|
||||||
is Statement -> g.callOn(execScope)
|
is Statement -> (g as? BytecodeCallable)?.callOnFast(execScope) ?: g.callOn(execScope)
|
||||||
else -> g.callOn(execScope)
|
else -> (g as? BytecodeCallable)?.callOnFast(execScope) ?: g.callOn(execScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,14 +62,16 @@ 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") else s.callOn(execScope)
|
if (body != null) executeBytecodeWithSeed(execScope, body, "property setter")
|
||||||
|
else (s as? BytecodeCallable)?.callOnFast(execScope) ?: s.callOn(execScope)
|
||||||
}
|
}
|
||||||
is Statement -> s.callOn(execScope)
|
is Statement -> (s as? BytecodeCallable)?.callOnFast(execScope) ?: s.callOn(execScope)
|
||||||
else -> s.callOn(execScope)
|
else -> (s as? BytecodeCallable)?.callOnFast(execScope) ?: s.callOn(execScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,17 +38,26 @@ 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,
|
||||||
var cachedScope: ModuleScope? = null
|
val cacheCell: CacheCell = CacheCell()
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun getScope(pos: Pos): ModuleScope {
|
suspend fun getScope(pos: Pos): ModuleScope {
|
||||||
cachedScope?.let { return it }
|
cacheCell.scope?.let { return it }
|
||||||
return ModuleScope(inner, pos, packageName).apply {
|
val module = ModuleScope(inner, pos, packageName)
|
||||||
cachedScope = this
|
cacheCell.scope = module
|
||||||
builder(this)
|
return try {
|
||||||
|
builder(module)
|
||||||
|
module
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
if (cacheCell.scope === module) {
|
||||||
|
cacheCell.scope = null
|
||||||
|
}
|
||||||
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,14 +161,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.cachedScope)
|
imports[name] = Entry(entry.packageName, entry.builder, entry.cacheCell)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun invalidatePackageCache(name: String) {
|
fun invalidatePackageCache(name: String) {
|
||||||
op.withLock {
|
op.withLock {
|
||||||
imports[name]?.cachedScope = null
|
imports[name]?.cacheCell?.scope = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -58,7 +58,10 @@ abstract class Statement(
|
|||||||
val type = ObjClass("Callable")
|
val type = ObjClass("Callable")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.createChildScope(args = Arguments(*args)))
|
suspend fun call(scope: Scope, vararg args: Obj): Obj {
|
||||||
|
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,6 +213,24 @@ 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()
|
||||||
@ -229,6 +247,21 @@ 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()
|
||||||
@ -346,6 +379,106 @@ 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()
|
||||||
@ -501,6 +634,166 @@ 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,11 +16,17 @@
|
|||||||
|
|
||||||
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
|
||||||
@ -68,6 +74,235 @@ 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,10 +17,12 @@
|
|||||||
|
|
||||||
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 {
|
||||||
@ -40,9 +42,11 @@ 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 ->
|
||||||
@ -56,4 +60,25 @@ 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,6 +13,11 @@ 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,18 +22,25 @@ 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) Mixed compare coverage
|
7) Primitive list fill with capacity (done)
|
||||||
|
- 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.
|
||||||
8) Range-loop invariant hoist
|
10) 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.
|
||||||
9) Boxing elision pass
|
11) 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.
|
||||||
10) Closed-type fast paths expansion
|
12) 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.
|
||||||
11) VM hot op micro-optimizations
|
13) 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.
|
||||||
|
|||||||
146
notes/non_suspending_call_optimization_plan.md
Normal file
146
notes/non_suspending_call_optimization_plan.md
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
# 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