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.
|
* 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
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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? {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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>()
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
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