Drive higher-order lambda inlining from call metadata

This commit is contained in:
Sergey Chernov 2026-04-21 20:51:16 +03:00
parent f4ab2ebab4
commit fbb5688696
11 changed files with 401 additions and 121 deletions

View File

@ -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
}
}

View File

@ -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
} }
@ -1904,6 +1953,7 @@ class Compiler(
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,
@ -2260,6 +2310,7 @@ class Compiler(
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,
@ -2294,6 +2345,7 @@ class Compiler(
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,
@ -2353,6 +2405,7 @@ class Compiler(
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,
@ -9208,7 +9261,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)
@ -9230,7 +9283,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)
} }

View File

@ -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(

View File

@ -42,6 +42,7 @@ class BytecodeCompiler(
private val enumEntriesByName: Map<String, List<String>> = emptyMap(), private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(), private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(), private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
private val callSignatureByName: Map<String, CallSignature> = emptyMap(),
private val externCallableNames: Set<String> = emptySet(), private val externCallableNames: Set<String> = emptySet(),
private val externBindingNames: Set<String> = emptySet(), private val externBindingNames: Set<String> = emptySet(),
private val preparedModuleBindingNames: Set<String> = emptySet(), private val preparedModuleBindingNames: Set<String> = emptySet(),
@ -4980,22 +4981,22 @@ class BytecodeCompiler(
} }
private fun compileInlineHigherOrderMethodCall(ref: MethodCallRef): CompiledValue? { private fun compileInlineHigherOrderMethodCall(ref: MethodCallRef): CompiledValue? {
val spec = inlineHigherOrderMethodSpec(ref.name) ?: return null val spec = inlineHigherOrderMethodSpec(ref) ?: return null
if (ref.args.size != spec.argCount || ref.args.any { it.isSplat || it.name != null }) return null if (ref.args.size != spec.argCount || ref.args.any { it.isSplat || it.name != null }) return null
if (!ref.explicitTypeArgs.isNullOrEmpty()) return null if (!ref.explicitTypeArgs.isNullOrEmpty()) return null
val lambdaRef = extractExactLambdaRef(ref.args[spec.lambdaArgIndex].value) ?: return null val lambdaRef = extractExactLambdaRef(ref.args[spec.lambdaArgIndex].value) ?: return null
val inlineRef = lambdaRef.inlineBodyRef ?: return null val inlineRef = lambdaRef.inlineBodyRef ?: return null
return when (spec.kind) { return when (spec.kind) {
InlineHigherOrderMethodKind.UNARY_ARGUMENT -> { CallSignature.Kind.UNARY_ARGUMENT -> {
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
val receiverObj = ensureObjSlot(receiver) val receiverObj = ensureObjSlot(receiver)
compileOptionalInlineMethod(ref.isOptional, receiverObj) { compileOptionalInlineMethod(ref.isOptional, receiverObj) {
val bindings = prepareInlineLambdaBindingsFromValues(lambdaRef, listOf(receiver)) ?: return@compileOptionalInlineMethod null val bindings = prepareInlineLambdaBindingsFromValues(lambdaRef, listOf(receiver)) ?: return@compileOptionalInlineMethod null
when (spec.result) { when (spec.result) {
InlineHigherOrderResultMode.BLOCK_RESULT -> CallSignature.ResultMode.BLOCK_RESULT ->
compileInlineLambdaBody(lambdaRef, inlineRef, bindings) compileInlineLambdaBody(lambdaRef, inlineRef, bindings)
InlineHigherOrderResultMode.RETURN_RECEIVER -> { CallSignature.ResultMode.RETURN_RECEIVER -> {
compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return@compileOptionalInlineMethod null compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return@compileOptionalInlineMethod null
CompiledValue(receiverObj.slot, SlotType.OBJ) CompiledValue(receiverObj.slot, SlotType.OBJ)
} }
@ -5003,7 +5004,7 @@ class BytecodeCompiler(
} }
} }
} }
InlineHigherOrderMethodKind.RECEIVER -> { CallSignature.Kind.RECEIVER -> {
val receiverInfo = receiverInlineInfo(lambdaRef) ?: return null val receiverInfo = receiverInlineInfo(lambdaRef) ?: return null
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = true, allowCaptures = true)) return null if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = true, allowCaptures = true)) return null
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
@ -5012,7 +5013,7 @@ class BytecodeCompiler(
compileInlineReceiverLambdaInvocation(receiverObj, lambdaRef, spec.result, receiverInfo) compileInlineReceiverLambdaInvocation(receiverObj, lambdaRef, spec.result, receiverInfo)
} }
} }
InlineHigherOrderMethodKind.ITERABLE -> { CallSignature.Kind.ITERABLE -> {
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
val receiverObj = ensureObjSlot(receiver) val receiverObj = ensureObjSlot(receiver)
@ -5020,7 +5021,7 @@ class BytecodeCompiler(
compileInlineIterableLambdaLoop(receiverObj, ref, lambdaRef, inlineRef, spec.result) compileInlineIterableLambdaLoop(receiverObj, ref, lambdaRef, inlineRef, spec.result)
} }
} }
InlineHigherOrderMethodKind.MAP_GET_OR_PUT -> { CallSignature.Kind.MAP_GET_OR_PUT -> {
val receiverClass = resolveReceiverClass(ref.receiver) ?: return null val receiverClass = resolveReceiverClass(ref.receiver) ?: return null
if (receiverClass != ObjMap.type) return null if (receiverClass != ObjMap.type) return null
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
@ -5131,81 +5132,20 @@ class BytecodeCompiler(
return slot return slot
} }
private enum class InlineHigherOrderMethodKind {
UNARY_ARGUMENT,
RECEIVER,
ITERABLE,
MAP_GET_OR_PUT
}
private enum class InlineHigherOrderResultMode {
BLOCK_RESULT,
RETURN_RECEIVER,
FOR_EACH,
MAP,
FILTER,
MAP_NOT_NULL,
ASSOCIATE_BY
}
private data class InlineHigherOrderMethodSpec(
val kind: InlineHigherOrderMethodKind,
val result: InlineHigherOrderResultMode,
val argCount: Int = 1,
val lambdaArgIndex: Int = 0
)
private data class InlineReceiverInfo( private data class InlineReceiverInfo(
val explicitBindings: List<Pair<String, Int>>, val explicitBindings: List<Pair<String, Int>>,
val thisTypeName: String? val thisTypeName: String?
) )
private fun inlineHigherOrderMethodSpec(name: String): InlineHigherOrderMethodSpec? { private fun inlineHigherOrderMethodSpec(ref: MethodCallRef): CallSignature.HigherOrderInline? {
return when (name) { resolveReceiverClass(ref.receiver)
"let" -> InlineHigherOrderMethodSpec( ?.resolveInstanceMember(ref.name)
InlineHigherOrderMethodKind.UNARY_ARGUMENT, ?.record
InlineHigherOrderResultMode.BLOCK_RESULT ?.callSignature
) ?.inlineHigherOrder
"also" -> InlineHigherOrderMethodSpec( ?.let { return it }
InlineHigherOrderMethodKind.UNARY_ARGUMENT, val receiverClass = resolveReceiverClass(ref.receiver) ?: return null
InlineHigherOrderResultMode.RETURN_RECEIVER return extensionCallableSignature(receiverClass, ref.name)?.inlineHigherOrder
)
"apply" -> InlineHigherOrderMethodSpec(
InlineHigherOrderMethodKind.RECEIVER,
InlineHigherOrderResultMode.RETURN_RECEIVER
)
"run" -> InlineHigherOrderMethodSpec(
InlineHigherOrderMethodKind.RECEIVER,
InlineHigherOrderResultMode.BLOCK_RESULT
)
"forEach" -> InlineHigherOrderMethodSpec(
InlineHigherOrderMethodKind.ITERABLE,
InlineHigherOrderResultMode.FOR_EACH
)
"map" -> InlineHigherOrderMethodSpec(
InlineHigherOrderMethodKind.ITERABLE,
InlineHigherOrderResultMode.MAP
)
"filter" -> InlineHigherOrderMethodSpec(
InlineHigherOrderMethodKind.ITERABLE,
InlineHigherOrderResultMode.FILTER
)
"mapNotNull" -> InlineHigherOrderMethodSpec(
InlineHigherOrderMethodKind.ITERABLE,
InlineHigherOrderResultMode.MAP_NOT_NULL
)
"associateBy" -> InlineHigherOrderMethodSpec(
InlineHigherOrderMethodKind.ITERABLE,
InlineHigherOrderResultMode.ASSOCIATE_BY
)
"getOrPut" -> InlineHigherOrderMethodSpec(
InlineHigherOrderMethodKind.MAP_GET_OR_PUT,
InlineHigherOrderResultMode.BLOCK_RESULT,
argCount = 2,
lambdaArgIndex = 1
)
else -> null
}
} }
private fun compileOptionalInlineMethod( private fun compileOptionalInlineMethod(
@ -5329,7 +5269,7 @@ class BytecodeCompiler(
private fun compileInlineReceiverLambdaInvocation( private fun compileInlineReceiverLambdaInvocation(
receiverObj: CompiledValue, receiverObj: CompiledValue,
lambdaRef: LambdaFnRef, lambdaRef: LambdaFnRef,
behavior: InlineHigherOrderResultMode, behavior: CallSignature.ResultMode,
receiverInfo: InlineReceiverInfo receiverInfo: InlineReceiverInfo
): CompiledValue? { ): CompiledValue? {
val inlineRef = lambdaRef.inlineBodyRef ?: return null val inlineRef = lambdaRef.inlineBodyRef ?: return null
@ -5338,9 +5278,9 @@ class BytecodeCompiler(
inlineThisBindings.addLast(previousBinding) inlineThisBindings.addLast(previousBinding)
return try { return try {
when (behavior) { when (behavior) {
InlineHigherOrderResultMode.BLOCK_RESULT -> CallSignature.ResultMode.BLOCK_RESULT ->
compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings) compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings)
InlineHigherOrderResultMode.RETURN_RECEIVER -> { CallSignature.ResultMode.RETURN_RECEIVER -> {
compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings) ?: return null compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings) ?: return null
CompiledValue(receiverSlot, SlotType.OBJ) CompiledValue(receiverSlot, SlotType.OBJ)
} }
@ -5372,7 +5312,7 @@ class BytecodeCompiler(
ref: MethodCallRef, ref: MethodCallRef,
lambdaRef: LambdaFnRef, lambdaRef: LambdaFnRef,
inlineRef: ObjRef, inlineRef: ObjRef,
behavior: InlineHigherOrderResultMode behavior: CallSignature.ResultMode
): CompiledValue? { ): CompiledValue? {
val iterableMethods = ObjIterable.instanceMethodIdMap(includeAbstract = true) val iterableMethods = ObjIterable.instanceMethodIdMap(includeAbstract = true)
val iteratorMethodId = iterableMethods["iterator"] val iteratorMethodId = iterableMethods["iterator"]
@ -5388,17 +5328,17 @@ class BytecodeCompiler(
builder.emit(Opcode.ITER_PUSH, iterSlot) builder.emit(Opcode.ITER_PUSH, iterSlot)
val result = when (behavior) { val result = when (behavior) {
InlineHigherOrderResultMode.FOR_EACH -> CompiledValue(ensureVoidSlot(), SlotType.OBJ) CallSignature.ResultMode.FOR_EACH -> CompiledValue(ensureVoidSlot(), SlotType.OBJ)
InlineHigherOrderResultMode.MAP, CallSignature.ResultMode.MAP,
InlineHigherOrderResultMode.FILTER, CallSignature.ResultMode.FILTER,
InlineHigherOrderResultMode.MAP_NOT_NULL -> createEmptyMutableList() ?: return null CallSignature.ResultMode.MAP_NOT_NULL -> createEmptyMutableList() ?: return null
InlineHigherOrderResultMode.ASSOCIATE_BY -> createEmptyMutableMap() ?: return null CallSignature.ResultMode.ASSOCIATE_BY -> createEmptyMutableMap() ?: return null
else -> return null else -> return null
} }
if (behavior == InlineHigherOrderResultMode.FILTER) { if (behavior == CallSignature.ResultMode.FILTER) {
listElementClassFromReceiverRef(ref.receiver)?.let { listElementClassBySlot[result.slot] = it } listElementClassFromReceiverRef(ref.receiver)?.let { listElementClassBySlot[result.slot] = it }
} }
if (behavior == InlineHigherOrderResultMode.MAP) { if (behavior == CallSignature.ResultMode.MAP) {
lambdaRef.inferredReturnClass?.let { listElementClassBySlot[result.slot] = it } lambdaRef.inferredReturnClass?.let { listElementClassBySlot[result.slot] = it }
} }
@ -5418,14 +5358,14 @@ class BytecodeCompiler(
val nextObj = ensureObjSlot(CompiledValue(nextSlot, SlotType.UNKNOWN)) val nextObj = ensureObjSlot(CompiledValue(nextSlot, SlotType.UNKNOWN))
val bindings = prepareInlineLambdaBindingsFromValues(lambdaRef, listOf(nextObj)) ?: return null val bindings = prepareInlineLambdaBindingsFromValues(lambdaRef, listOf(nextObj)) ?: return null
when (behavior) { when (behavior) {
InlineHigherOrderResultMode.FOR_EACH -> { CallSignature.ResultMode.FOR_EACH -> {
compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
} }
InlineHigherOrderResultMode.MAP -> { CallSignature.ResultMode.MAP -> {
val mapped = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null val mapped = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
appendToList(result, mapped) ?: return null appendToList(result, mapped) ?: return null
} }
InlineHigherOrderResultMode.FILTER -> { CallSignature.ResultMode.FILTER -> {
val predicate = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null val predicate = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
val predicateBool = compileValueAsBool(predicate) val predicateBool = compileValueAsBool(predicate)
val skipLabel = builder.label() val skipLabel = builder.label()
@ -5436,7 +5376,7 @@ class BytecodeCompiler(
appendToList(result, nextObj) ?: return null appendToList(result, nextObj) ?: return null
builder.mark(skipLabel) builder.mark(skipLabel)
} }
InlineHigherOrderResultMode.MAP_NOT_NULL -> { CallSignature.ResultMode.MAP_NOT_NULL -> {
val mapped = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null val mapped = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
val mappedObj = ensureObjSlot(mapped) val mappedObj = ensureObjSlot(mapped)
val nullSlot = allocSlot() val nullSlot = allocSlot()
@ -5451,7 +5391,7 @@ class BytecodeCompiler(
appendToList(result, mappedObj) ?: return null appendToList(result, mappedObj) ?: return null
builder.mark(skipLabel) builder.mark(skipLabel)
} }
InlineHigherOrderResultMode.ASSOCIATE_BY -> { CallSignature.ResultMode.ASSOCIATE_BY -> {
val key = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null val key = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
appendToMap(result, key, nextObj) appendToMap(result, key, nextObj)
} }
@ -5986,11 +5926,11 @@ class BytecodeCompiler(
return names return names
} }
private fun resolveExtensionSlotByReceiverNames( private fun resolveExtensionWrapperNameByReceiverNames(
receiverClass: ObjClass, receiverClass: ObjClass,
memberName: String, memberName: String,
wrapperName: (String, String) -> String wrapperName: (String, String) -> String
): CompiledValue? { ): String? {
for (receiverName in extensionReceiverTypeNames(receiverClass)) { for (receiverName in extensionReceiverTypeNames(receiverClass)) {
val candidate = wrapperName(receiverName, memberName) val candidate = wrapperName(receiverName, memberName)
if (allowedScopeNames != null && if (allowedScopeNames != null &&
@ -5999,15 +5939,15 @@ class BytecodeCompiler(
) { ) {
continue continue
} }
resolveDirectNameSlot(candidate)?.let { return it } if (resolveDirectNameSlot(candidate) != null) return candidate
} }
return null return null
} }
private fun resolveUniqueExtensionWrapperSlot( private fun resolveUniqueExtensionWrapperName(
memberName: String, memberName: String,
wrapperPrefix: String wrapperPrefix: String
): CompiledValue? { ): String? {
val suffix = "__$memberName" val suffix = "__$memberName"
val candidates = LinkedHashSet<String>() val candidates = LinkedHashSet<String>()
for (name in localSlotIndexByName.keys) { for (name in localSlotIndexByName.keys) {
@ -6020,8 +5960,31 @@ class BytecodeCompiler(
candidates.add(name) candidates.add(name)
} }
} }
if (candidates.size != 1) return null return candidates.singleOrNull()
return resolveDirectNameSlot(candidates.first()) }
private fun resolveExtensionSlotByReceiverNames(
receiverClass: ObjClass,
memberName: String,
wrapperName: (String, String) -> String
): CompiledValue? {
val resolvedName = resolveExtensionWrapperNameByReceiverNames(receiverClass, memberName, wrapperName) ?: return null
return resolveDirectNameSlot(resolvedName)
}
private fun resolveUniqueExtensionWrapperSlot(
memberName: String,
wrapperPrefix: String
): CompiledValue? {
val resolvedName = resolveUniqueExtensionWrapperName(memberName, wrapperPrefix) ?: return null
return resolveDirectNameSlot(resolvedName)
}
private fun extensionCallableSignature(receiverClass: ObjClass, memberName: String): CallSignature? {
val wrapperName = resolveExtensionWrapperNameByReceiverNames(receiverClass, memberName, ::extensionCallableName)
?: resolveUniqueExtensionWrapperName(memberName, "__ext__")
?: return null
return callSignatureByName[wrapperName]
} }
private fun resolveExtensionCallableSlot(receiverClass: ObjClass, memberName: String): CompiledValue? { private fun resolveExtensionCallableSlot(receiverClass: ObjClass, memberName: String): CompiledValue? {

View File

@ -106,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(),
@ -146,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,

View File

@ -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)

View File

@ -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())
} }

View File

@ -849,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
@ -949,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,
@ -975,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]
@ -1016,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
) )
@ -1035,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
@ -1042,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
) )
} }
@ -1074,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
)
} }

View File

@ -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>()

View File

@ -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) {

View 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.