Drive higher-order lambda inlining from call metadata
This commit is contained in:
parent
f4ab2ebab4
commit
fbb5688696
@ -20,5 +20,30 @@ package net.sergeych.lyng
|
||||
* Compile-time call metadata for known functions. Used to select lambda receiver semantics.
|
||||
*/
|
||||
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 callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
|
||||
private val callableReturnTypeDeclByName: MutableMap<String, TypeDecl> = mutableMapOf()
|
||||
private val callSignatureByName: MutableMap<String, CallSignature> = mutableMapOf()
|
||||
private val lambdaReturnTypeByRef: MutableMap<ObjRef, ObjClass> = mutableMapOf()
|
||||
private val exactLambdaRefByScopeId: MutableMap<Int, MutableMap<Int, LambdaFnRef>> = mutableMapOf()
|
||||
private val lambdaCaptureEntriesByRef: MutableMap<ValueFnRef, List<net.sergeych.lyng.bytecode.LambdaCaptureEntry>> =
|
||||
@ -265,6 +266,11 @@ class Compiler(
|
||||
if (plan.slots.containsKey(name)) continue
|
||||
declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated)
|
||||
scopeSeedNames.add(name)
|
||||
record.callSignature?.let { signature ->
|
||||
if (!callSignatureByName.containsKey(name)) {
|
||||
callSignatureByName[name] = signature
|
||||
}
|
||||
}
|
||||
if (record.typeDecl != null && nameTypeDecl[name] == null) {
|
||||
nameTypeDecl[name] = record.typeDecl
|
||||
if (nameObjClass[name] == null) {
|
||||
@ -291,6 +297,11 @@ class Compiler(
|
||||
)
|
||||
scopeSeedNames.add(getterName)
|
||||
}
|
||||
record.callSignature?.let { signature ->
|
||||
if (!callSignatureByName.containsKey(getterName)) {
|
||||
callSignatureByName[getterName] = signature
|
||||
}
|
||||
}
|
||||
val prop = record.value as? ObjProperty
|
||||
if (prop?.setter != null) {
|
||||
val setterName = extensionPropertySetterName(cls.className, name)
|
||||
@ -316,6 +327,11 @@ class Compiler(
|
||||
)
|
||||
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
|
||||
)
|
||||
scopeSeedNames.add(name)
|
||||
record.callSignature?.let { signature ->
|
||||
if (!callSignatureByName.containsKey(name)) {
|
||||
callSignatureByName[name] = signature
|
||||
}
|
||||
}
|
||||
if (record.typeDecl != null && nameTypeDecl[name] == null) {
|
||||
nameTypeDecl[name] = record.typeDecl
|
||||
if (nameObjClass[name] == null) {
|
||||
@ -762,6 +783,29 @@ class Compiler(
|
||||
?: 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?)
|
||||
|
||||
private fun resolveMemberIds(name: String, pos: Pos, qualifier: String? = null): MemberIds {
|
||||
@ -1429,6 +1473,11 @@ class Compiler(
|
||||
}
|
||||
|
||||
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) {
|
||||
nameTypeDecl[name] = record.typeDecl
|
||||
}
|
||||
@ -1904,6 +1953,7 @@ class Compiler(
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
callSignatureByName = callSignatureByName,
|
||||
externBindingNames = externBindingNames,
|
||||
preparedModuleBindingNames = importBindings.keys,
|
||||
scopeRefPosByName = moduleReferencePosByName,
|
||||
@ -2260,6 +2310,7 @@ class Compiler(
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
callSignatureByName = callSignatureByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
preparedModuleBindingNames = importBindings.keys,
|
||||
@ -2294,6 +2345,7 @@ class Compiler(
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
callSignatureByName = callSignatureByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
preparedModuleBindingNames = importBindings.keys,
|
||||
@ -2353,6 +2405,7 @@ class Compiler(
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
callSignatureByName = callSignatureByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
preparedModuleBindingNames = importBindings.keys,
|
||||
@ -9208,7 +9261,7 @@ class Compiler(
|
||||
val extensionWrapperName = extTypeName?.let { extensionCallableName(it, name) }
|
||||
val classCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody
|
||||
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
|
||||
resolutionSink?.declareSymbol(name, declKind, isMutable = false, pos = nameStartPos, isOverride = isOverride)
|
||||
@ -9230,7 +9283,9 @@ class Compiler(
|
||||
}
|
||||
if (extensionWrapperName != null) {
|
||||
declareLocalName(extensionWrapperName, isMutable = false)
|
||||
externCallSignature?.let { callSignatureByName[extensionWrapperName] = it }
|
||||
}
|
||||
externCallSignature?.let { callSignatureByName[name] = it }
|
||||
if (actualExtern && declKind != SymbolKind.MEMBER) {
|
||||
externCallableNames.add(name)
|
||||
}
|
||||
|
||||
@ -118,7 +118,14 @@ internal suspend fun executeFunctionDecl(
|
||||
scope.addExtension(
|
||||
type,
|
||||
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
|
||||
}
|
||||
)
|
||||
@ -135,7 +142,8 @@ internal suspend fun executeFunctionDecl(
|
||||
null,
|
||||
spec.startPos,
|
||||
isTransient = spec.isTransient,
|
||||
type = ObjRecord.Type.Delegated
|
||||
type = ObjRecord.Type.Delegated,
|
||||
callSignature = spec.externCallSignature
|
||||
).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
@ -204,6 +212,7 @@ internal suspend fun executeFunctionDecl(
|
||||
visibility = spec.visibility,
|
||||
pos = spec.startPos,
|
||||
type = ObjRecord.Type.Fun,
|
||||
callSignature = spec.externCallSignature,
|
||||
)
|
||||
} else {
|
||||
scope.addExtension(
|
||||
@ -215,6 +224,7 @@ internal suspend fun executeFunctionDecl(
|
||||
visibility = spec.visibility,
|
||||
declaringClass = null,
|
||||
type = ObjRecord.Type.Fun,
|
||||
callSignature = spec.externCallSignature,
|
||||
typeDecl = spec.typeDecl
|
||||
)
|
||||
)
|
||||
@ -227,6 +237,7 @@ internal suspend fun executeFunctionDecl(
|
||||
wrapper,
|
||||
spec.visibility,
|
||||
recordType = ObjRecord.Type.Fun,
|
||||
callSignature = spec.externCallSignature,
|
||||
typeDecl = spec.typeDecl
|
||||
)
|
||||
} ?: run {
|
||||
@ -245,7 +256,8 @@ internal suspend fun executeFunctionDecl(
|
||||
isOverride = spec.isOverride,
|
||||
type = ObjRecord.Type.Fun,
|
||||
methodId = spec.memberMethodId,
|
||||
typeDecl = spec.typeDecl
|
||||
typeDecl = spec.typeDecl,
|
||||
callSignature = spec.externCallSignature
|
||||
)
|
||||
val memberValue = cls.members[spec.name]?.value ?: compiledFnBody
|
||||
scope.addItem(
|
||||
|
||||
@ -42,6 +42,7 @@ class BytecodeCompiler(
|
||||
private val enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||
private val callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||
private val callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||
private val callSignatureByName: Map<String, CallSignature> = emptyMap(),
|
||||
private val externCallableNames: Set<String> = emptySet(),
|
||||
private val externBindingNames: Set<String> = emptySet(),
|
||||
private val preparedModuleBindingNames: Set<String> = emptySet(),
|
||||
@ -4980,22 +4981,22 @@ class BytecodeCompiler(
|
||||
}
|
||||
|
||||
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.explicitTypeArgs.isNullOrEmpty()) return null
|
||||
val lambdaRef = extractExactLambdaRef(ref.args[spec.lambdaArgIndex].value) ?: return null
|
||||
val inlineRef = lambdaRef.inlineBodyRef ?: return null
|
||||
return when (spec.kind) {
|
||||
InlineHigherOrderMethodKind.UNARY_ARGUMENT -> {
|
||||
CallSignature.Kind.UNARY_ARGUMENT -> {
|
||||
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
|
||||
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
|
||||
val receiverObj = ensureObjSlot(receiver)
|
||||
compileOptionalInlineMethod(ref.isOptional, receiverObj) {
|
||||
val bindings = prepareInlineLambdaBindingsFromValues(lambdaRef, listOf(receiver)) ?: return@compileOptionalInlineMethod null
|
||||
when (spec.result) {
|
||||
InlineHigherOrderResultMode.BLOCK_RESULT ->
|
||||
CallSignature.ResultMode.BLOCK_RESULT ->
|
||||
compileInlineLambdaBody(lambdaRef, inlineRef, bindings)
|
||||
InlineHigherOrderResultMode.RETURN_RECEIVER -> {
|
||||
CallSignature.ResultMode.RETURN_RECEIVER -> {
|
||||
compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return@compileOptionalInlineMethod null
|
||||
CompiledValue(receiverObj.slot, SlotType.OBJ)
|
||||
}
|
||||
@ -5003,7 +5004,7 @@ class BytecodeCompiler(
|
||||
}
|
||||
}
|
||||
}
|
||||
InlineHigherOrderMethodKind.RECEIVER -> {
|
||||
CallSignature.Kind.RECEIVER -> {
|
||||
val receiverInfo = receiverInlineInfo(lambdaRef) ?: return null
|
||||
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = true, allowCaptures = true)) 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)
|
||||
}
|
||||
}
|
||||
InlineHigherOrderMethodKind.ITERABLE -> {
|
||||
CallSignature.Kind.ITERABLE -> {
|
||||
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
|
||||
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
|
||||
val receiverObj = ensureObjSlot(receiver)
|
||||
@ -5020,7 +5021,7 @@ class BytecodeCompiler(
|
||||
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
|
||||
if (receiverClass != ObjMap.type) return null
|
||||
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
|
||||
@ -5131,81 +5132,20 @@ class BytecodeCompiler(
|
||||
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(
|
||||
val explicitBindings: List<Pair<String, Int>>,
|
||||
val thisTypeName: String?
|
||||
)
|
||||
|
||||
private fun inlineHigherOrderMethodSpec(name: String): InlineHigherOrderMethodSpec? {
|
||||
return when (name) {
|
||||
"let" -> InlineHigherOrderMethodSpec(
|
||||
InlineHigherOrderMethodKind.UNARY_ARGUMENT,
|
||||
InlineHigherOrderResultMode.BLOCK_RESULT
|
||||
)
|
||||
"also" -> InlineHigherOrderMethodSpec(
|
||||
InlineHigherOrderMethodKind.UNARY_ARGUMENT,
|
||||
InlineHigherOrderResultMode.RETURN_RECEIVER
|
||||
)
|
||||
"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 inlineHigherOrderMethodSpec(ref: MethodCallRef): CallSignature.HigherOrderInline? {
|
||||
resolveReceiverClass(ref.receiver)
|
||||
?.resolveInstanceMember(ref.name)
|
||||
?.record
|
||||
?.callSignature
|
||||
?.inlineHigherOrder
|
||||
?.let { return it }
|
||||
val receiverClass = resolveReceiverClass(ref.receiver) ?: return null
|
||||
return extensionCallableSignature(receiverClass, ref.name)?.inlineHigherOrder
|
||||
}
|
||||
|
||||
private fun compileOptionalInlineMethod(
|
||||
@ -5329,7 +5269,7 @@ class BytecodeCompiler(
|
||||
private fun compileInlineReceiverLambdaInvocation(
|
||||
receiverObj: CompiledValue,
|
||||
lambdaRef: LambdaFnRef,
|
||||
behavior: InlineHigherOrderResultMode,
|
||||
behavior: CallSignature.ResultMode,
|
||||
receiverInfo: InlineReceiverInfo
|
||||
): CompiledValue? {
|
||||
val inlineRef = lambdaRef.inlineBodyRef ?: return null
|
||||
@ -5338,9 +5278,9 @@ class BytecodeCompiler(
|
||||
inlineThisBindings.addLast(previousBinding)
|
||||
return try {
|
||||
when (behavior) {
|
||||
InlineHigherOrderResultMode.BLOCK_RESULT ->
|
||||
CallSignature.ResultMode.BLOCK_RESULT ->
|
||||
compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings)
|
||||
InlineHigherOrderResultMode.RETURN_RECEIVER -> {
|
||||
CallSignature.ResultMode.RETURN_RECEIVER -> {
|
||||
compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings) ?: return null
|
||||
CompiledValue(receiverSlot, SlotType.OBJ)
|
||||
}
|
||||
@ -5372,7 +5312,7 @@ class BytecodeCompiler(
|
||||
ref: MethodCallRef,
|
||||
lambdaRef: LambdaFnRef,
|
||||
inlineRef: ObjRef,
|
||||
behavior: InlineHigherOrderResultMode
|
||||
behavior: CallSignature.ResultMode
|
||||
): CompiledValue? {
|
||||
val iterableMethods = ObjIterable.instanceMethodIdMap(includeAbstract = true)
|
||||
val iteratorMethodId = iterableMethods["iterator"]
|
||||
@ -5388,17 +5328,17 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.ITER_PUSH, iterSlot)
|
||||
|
||||
val result = when (behavior) {
|
||||
InlineHigherOrderResultMode.FOR_EACH -> CompiledValue(ensureVoidSlot(), SlotType.OBJ)
|
||||
InlineHigherOrderResultMode.MAP,
|
||||
InlineHigherOrderResultMode.FILTER,
|
||||
InlineHigherOrderResultMode.MAP_NOT_NULL -> createEmptyMutableList() ?: return null
|
||||
InlineHigherOrderResultMode.ASSOCIATE_BY -> createEmptyMutableMap() ?: return null
|
||||
CallSignature.ResultMode.FOR_EACH -> CompiledValue(ensureVoidSlot(), SlotType.OBJ)
|
||||
CallSignature.ResultMode.MAP,
|
||||
CallSignature.ResultMode.FILTER,
|
||||
CallSignature.ResultMode.MAP_NOT_NULL -> createEmptyMutableList() ?: return null
|
||||
CallSignature.ResultMode.ASSOCIATE_BY -> createEmptyMutableMap() ?: return null
|
||||
else -> return null
|
||||
}
|
||||
if (behavior == InlineHigherOrderResultMode.FILTER) {
|
||||
if (behavior == CallSignature.ResultMode.FILTER) {
|
||||
listElementClassFromReceiverRef(ref.receiver)?.let { listElementClassBySlot[result.slot] = it }
|
||||
}
|
||||
if (behavior == InlineHigherOrderResultMode.MAP) {
|
||||
if (behavior == CallSignature.ResultMode.MAP) {
|
||||
lambdaRef.inferredReturnClass?.let { listElementClassBySlot[result.slot] = it }
|
||||
}
|
||||
|
||||
@ -5418,14 +5358,14 @@ class BytecodeCompiler(
|
||||
val nextObj = ensureObjSlot(CompiledValue(nextSlot, SlotType.UNKNOWN))
|
||||
val bindings = prepareInlineLambdaBindingsFromValues(lambdaRef, listOf(nextObj)) ?: return null
|
||||
when (behavior) {
|
||||
InlineHigherOrderResultMode.FOR_EACH -> {
|
||||
CallSignature.ResultMode.FOR_EACH -> {
|
||||
compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
|
||||
}
|
||||
InlineHigherOrderResultMode.MAP -> {
|
||||
CallSignature.ResultMode.MAP -> {
|
||||
val mapped = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
|
||||
appendToList(result, mapped) ?: return null
|
||||
}
|
||||
InlineHigherOrderResultMode.FILTER -> {
|
||||
CallSignature.ResultMode.FILTER -> {
|
||||
val predicate = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
|
||||
val predicateBool = compileValueAsBool(predicate)
|
||||
val skipLabel = builder.label()
|
||||
@ -5436,7 +5376,7 @@ class BytecodeCompiler(
|
||||
appendToList(result, nextObj) ?: return null
|
||||
builder.mark(skipLabel)
|
||||
}
|
||||
InlineHigherOrderResultMode.MAP_NOT_NULL -> {
|
||||
CallSignature.ResultMode.MAP_NOT_NULL -> {
|
||||
val mapped = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
|
||||
val mappedObj = ensureObjSlot(mapped)
|
||||
val nullSlot = allocSlot()
|
||||
@ -5451,7 +5391,7 @@ class BytecodeCompiler(
|
||||
appendToList(result, mappedObj) ?: return null
|
||||
builder.mark(skipLabel)
|
||||
}
|
||||
InlineHigherOrderResultMode.ASSOCIATE_BY -> {
|
||||
CallSignature.ResultMode.ASSOCIATE_BY -> {
|
||||
val key = compileInlineLambdaBody(lambdaRef, inlineRef, bindings) ?: return null
|
||||
appendToMap(result, key, nextObj)
|
||||
}
|
||||
@ -5986,11 +5926,11 @@ class BytecodeCompiler(
|
||||
return names
|
||||
}
|
||||
|
||||
private fun resolveExtensionSlotByReceiverNames(
|
||||
private fun resolveExtensionWrapperNameByReceiverNames(
|
||||
receiverClass: ObjClass,
|
||||
memberName: String,
|
||||
wrapperName: (String, String) -> String
|
||||
): CompiledValue? {
|
||||
): String? {
|
||||
for (receiverName in extensionReceiverTypeNames(receiverClass)) {
|
||||
val candidate = wrapperName(receiverName, memberName)
|
||||
if (allowedScopeNames != null &&
|
||||
@ -5999,15 +5939,15 @@ class BytecodeCompiler(
|
||||
) {
|
||||
continue
|
||||
}
|
||||
resolveDirectNameSlot(candidate)?.let { return it }
|
||||
if (resolveDirectNameSlot(candidate) != null) return candidate
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun resolveUniqueExtensionWrapperSlot(
|
||||
private fun resolveUniqueExtensionWrapperName(
|
||||
memberName: String,
|
||||
wrapperPrefix: String
|
||||
): CompiledValue? {
|
||||
): String? {
|
||||
val suffix = "__$memberName"
|
||||
val candidates = LinkedHashSet<String>()
|
||||
for (name in localSlotIndexByName.keys) {
|
||||
@ -6020,8 +5960,31 @@ class BytecodeCompiler(
|
||||
candidates.add(name)
|
||||
}
|
||||
}
|
||||
if (candidates.size != 1) return null
|
||||
return resolveDirectNameSlot(candidates.first())
|
||||
return candidates.singleOrNull()
|
||||
}
|
||||
|
||||
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? {
|
||||
|
||||
@ -106,6 +106,7 @@ class BytecodeStatement private constructor(
|
||||
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||
callSignatureByName: Map<String, CallSignature> = emptyMap(),
|
||||
externCallableNames: Set<String> = emptySet(),
|
||||
externBindingNames: Set<String> = emptySet(),
|
||||
preparedModuleBindingNames: Set<String> = emptySet(),
|
||||
@ -146,6 +147,7 @@ class BytecodeStatement private constructor(
|
||||
enumEntriesByName = enumEntriesByName,
|
||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||
callableReturnTypeByName = callableReturnTypeByName,
|
||||
callSignatureByName = callSignatureByName,
|
||||
externCallableNames = externCallableNames,
|
||||
externBindingNames = externBindingNames,
|
||||
preparedModuleBindingNames = preparedModuleBindingNames,
|
||||
|
||||
@ -99,10 +99,11 @@ fun ObjClass.addFnDoc(
|
||||
visibility: Visibility = Visibility.Public,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||
code: suspend ScopeFacade.() -> Obj
|
||||
) {
|
||||
// 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
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
classDoc(this@addFnDoc.className, doc = "") {
|
||||
@ -137,9 +138,10 @@ fun ObjClass.addClassFnDoc(
|
||||
isOpen: Boolean = false,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||
code: suspend ScopeFacade.() -> Obj
|
||||
) {
|
||||
addClassFn(name, isOpen, code)
|
||||
addClassFn(name, isOpen, callSignature, code)
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
classDoc(this@addClassFnDoc.className, doc = "") {
|
||||
method(name = name, doc = doc, params = params, returns = returns, isStatic = true, tags = tags)
|
||||
|
||||
@ -828,7 +828,13 @@ open class Obj {
|
||||
name = "let",
|
||||
doc = "Calls the specified function block with `this` value as its argument and returns its result.",
|
||||
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))
|
||||
}
|
||||
@ -836,7 +842,13 @@ open class Obj {
|
||||
name = "apply",
|
||||
doc = "Calls the specified function block with `this` value as its receiver and returns `this` value.",
|
||||
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 scope = requireScope()
|
||||
@ -852,7 +864,13 @@ open class Obj {
|
||||
name = "also",
|
||||
doc = "Calls the specified function block with `this` value as its argument and returns `this` value.",
|
||||
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))
|
||||
thisObj
|
||||
@ -861,7 +879,13 @@ open class Obj {
|
||||
name = "run",
|
||||
doc = "Calls the specified function block with `this` value as its receiver and returns its result.",
|
||||
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())
|
||||
}
|
||||
|
||||
@ -849,6 +849,7 @@ open class ObjClass(
|
||||
fieldId: Int? = null,
|
||||
methodId: Int? = null,
|
||||
typeDecl: net.sergeych.lyng.TypeDecl? = null,
|
||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||
): ObjRecord {
|
||||
// Validation of override rules: only for non-system declarations
|
||||
var existing: ObjRecord? = null
|
||||
@ -949,6 +950,7 @@ open class ObjClass(
|
||||
isOverride = isOverride,
|
||||
isTransient = isTransient,
|
||||
type = type,
|
||||
callSignature = callSignature,
|
||||
typeDecl = typeDecl,
|
||||
memberName = name,
|
||||
fieldId = effectiveFieldId,
|
||||
@ -975,7 +977,8 @@ open class ObjClass(
|
||||
isTransient: Boolean = false,
|
||||
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||
fieldId: Int? = null,
|
||||
methodId: Int? = null
|
||||
methodId: Int? = null,
|
||||
callSignature: net.sergeych.lyng.CallSignature? = null
|
||||
): ObjRecord {
|
||||
initClassScope()
|
||||
val existing = classScope!!.objects[name]
|
||||
@ -1016,6 +1019,7 @@ open class ObjClass(
|
||||
writeVisibility,
|
||||
recordType = type,
|
||||
isTransient = isTransient,
|
||||
callSignature = callSignature,
|
||||
fieldId = effectiveFieldId,
|
||||
methodId = effectiveMethodId
|
||||
)
|
||||
@ -1035,6 +1039,7 @@ open class ObjClass(
|
||||
isOverride: Boolean = false,
|
||||
pos: Pos = Pos.builtIn,
|
||||
methodId: Int? = null,
|
||||
callSignature: net.sergeych.lyng.CallSignature? = null,
|
||||
code: (suspend net.sergeych.lyng.ScopeFacade.() -> Obj)? = null
|
||||
) {
|
||||
val stmt = code?.let { ObjExternCallable.fromBridge { it() } } ?: ObjNull
|
||||
@ -1042,7 +1047,8 @@ open class ObjClass(
|
||||
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||
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 addClassFn(name: String, isOpen: Boolean = false, code: suspend net.sergeych.lyng.ScopeFacade.() -> Obj) {
|
||||
createClassField(name, ObjExternCallable.fromBridge { code() }, isOpen, type = ObjRecord.Type.Fun)
|
||||
fun addClassFn(
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.CallSignature
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
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.",
|
||||
params = listOf(ParamDoc("keySelector")),
|
||||
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 result = ObjMap()
|
||||
@ -204,7 +211,13 @@ val ObjIterable by lazy {
|
||||
doc = "Apply the lambda to each element in iteration order.",
|
||||
params = listOf(ParamDoc("action")),
|
||||
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 it = thisObj.invokeInstanceMethod(scope, "iterator")
|
||||
@ -222,7 +235,13 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("transform")),
|
||||
returns = type("lyng.List"),
|
||||
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 result = mutableListOf<Obj>()
|
||||
@ -238,7 +257,13 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("transform")),
|
||||
returns = type("lyng.List"),
|
||||
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 result = mutableListOf<Obj>()
|
||||
|
||||
@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import net.sergeych.lyng.CallSignature
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.*
|
||||
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.",
|
||||
params = listOf(ParamDoc("key"), ParamDoc("default")),
|
||||
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)
|
||||
thisAs<ObjMap>().map.getOrPut(key) {
|
||||
|
||||
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