Inline simple lambdas in bytecode fast paths

This commit is contained in:
Sergey Chernov 2026-04-21 00:38:04 +03:00
parent 10fa4de4fa
commit f72cdfdf83
14 changed files with 1229 additions and 55 deletions

View File

@ -302,8 +302,6 @@ android {
} }
} }
dependencies { dependencies {
implementation(libs.firebase.crashlytics.buildtools)
implementation(libs.compiler)
} }
publishing { publishing {

View File

@ -188,6 +188,7 @@ class Compiler(
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 lambdaReturnTypeByRef: MutableMap<ObjRef, ObjClass> = 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>> = private val lambdaCaptureEntriesByRef: MutableMap<ValueFnRef, List<net.sergeych.lyng.bytecode.LambdaCaptureEntry>> =
mutableMapOf() mutableMapOf()
private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf() private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
@ -1894,6 +1895,7 @@ class Compiler(
forcedLocalSlotInfo = forcedLocalInfo, forcedLocalSlotInfo = forcedLocalInfo,
forcedLocalScopeId = forcedLocalScopeId, forcedLocalScopeId = forcedLocalScopeId,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
exactLambdaRefByScopeId = exactLambdaRefByScopeId,
slotTypeDeclByScopeId = slotTypeDeclByScopeId, slotTypeDeclByScopeId = slotTypeDeclByScopeId,
knownNameObjClass = knownClassMapForBytecode(), knownNameObjClass = knownClassMapForBytecode(),
knownClassNames = knownClassNamesForBytecode(), knownClassNames = knownClassNamesForBytecode(),
@ -2249,6 +2251,7 @@ class Compiler(
scopeSlotNameSet = scopeSlotNameSet, scopeSlotNameSet = scopeSlotNameSet,
moduleScopeId = moduleScopeId, moduleScopeId = moduleScopeId,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
exactLambdaRefByScopeId = exactLambdaRefByScopeId,
slotTypeDeclByScopeId = slotTypeDeclByScopeId, slotTypeDeclByScopeId = slotTypeDeclByScopeId,
knownNameObjClass = knownClassMapForBytecode(), knownNameObjClass = knownClassMapForBytecode(),
knownClassNames = knownClassNamesForBytecode(), knownClassNames = knownClassNamesForBytecode(),
@ -2282,6 +2285,7 @@ class Compiler(
scopeSlotNameSet = scopeSlotNameSet, scopeSlotNameSet = scopeSlotNameSet,
moduleScopeId = moduleScopeId, moduleScopeId = moduleScopeId,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
exactLambdaRefByScopeId = exactLambdaRefByScopeId,
slotTypeDeclByScopeId = slotTypeDeclByScopeId, slotTypeDeclByScopeId = slotTypeDeclByScopeId,
knownNameObjClass = knownClassMapForBytecode(), knownNameObjClass = knownClassMapForBytecode(),
knownClassNames = knownClassNamesForBytecode(), knownClassNames = knownClassNamesForBytecode(),
@ -2340,6 +2344,7 @@ class Compiler(
globalSlotInfo = globalSlotInfo, globalSlotInfo = globalSlotInfo,
globalSlotScopeId = globalSlotScopeId, globalSlotScopeId = globalSlotScopeId,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
exactLambdaRefByScopeId = exactLambdaRefByScopeId,
slotTypeDeclByScopeId = slotTypeDeclByScopeId, slotTypeDeclByScopeId = slotTypeDeclByScopeId,
knownNameObjClass = knownNames, knownNameObjClass = knownNames,
knownClassNames = knownClassNamesForBytecode(), knownClassNames = knownClassNamesForBytecode(),
@ -3539,6 +3544,7 @@ class Compiler(
body body
} }
val bytecodeFn = (fnStatements as? BytecodeStatement)?.bytecodeFunction() val bytecodeFn = (fnStatements as? BytecodeStatement)?.bytecodeFunction()
val inlineBodyRef = argsDeclaration?.let { null } ?: extractInlineLambdaBodyRef(body)
val ref = LambdaFnRef( val ref = LambdaFnRef(
valueFn = { closureScope -> valueFn = { closureScope ->
val captureRecords = closureScope.captureRecords val captureRecords = closureScope.captureRecords
@ -3621,6 +3627,7 @@ class Compiler(
argsDeclaration = argsDeclaration, argsDeclaration = argsDeclaration,
captureEntries = captureEntries, captureEntries = captureEntries,
inferredReturnClass = returnClass, inferredReturnClass = returnClass,
inlineBodyRef = inlineBodyRef,
preferredThisType = expectedReceiverType, preferredThisType = expectedReceiverType,
wrapAsExtensionCallable = wrapAsExtensionCallable, wrapAsExtensionCallable = wrapAsExtensionCallable,
returnLabels = returnLabels, returnLabels = returnLabels,
@ -3635,6 +3642,18 @@ class Compiler(
return ref return ref
} }
private fun extractInlineLambdaBodyRef(statement: Statement): ObjRef? {
val target = if (statement is BytecodeStatement) statement.original else statement
return when (target) {
is ExpressionStatement -> target.ref
is BlockStatement -> {
val statements = target.statements()
if (statements.size == 1) extractInlineLambdaBodyRef(statements[0]) else null
}
else -> null
}
}
private suspend fun parseArrayLiteral(): List<ListEntry> { private suspend fun parseArrayLiteral(): List<ListEntry> {
// it should be called after Token.Type.LBRACKET is consumed // it should be called after Token.Type.LBRACKET is consumed
val entries = mutableListOf<ListEntry>() val entries = mutableListOf<ListEntry>()
@ -4968,6 +4987,26 @@ class Compiler(
?: slotTypeDeclByScopeId[slotLoc.scopeId]?.get(slotLoc.slot)?.let { resolveTypeDeclObjClass(it) } ?: slotTypeDeclByScopeId[slotLoc.scopeId]?.get(slotLoc.slot)?.let { resolveTypeDeclObjClass(it) }
} }
private fun lookupExactLambdaRefByName(name: String): LambdaFnRef? {
val slotLoc = lookupSlotLocation(name, includeModule = true) ?: return null
return exactLambdaRefByScopeId[slotLoc.scopeId]?.get(slotLoc.slot)
}
private fun resolveExactLambdaRef(ref: ObjRef?): LambdaFnRef? {
return when (ref) {
is LambdaFnRef -> ref
is LocalVarRef -> lookupExactLambdaRefByName(ref.name)
is FastLocalVarRef -> lookupExactLambdaRefByName(ref.name)
is LocalSlotRef -> {
val ownerScopeId = ref.captureOwnerScopeId ?: ref.scopeId
val ownerSlot = ref.captureOwnerSlot ?: ref.slot
exactLambdaRefByScopeId[ownerScopeId]?.get(ownerSlot)
?: ref.name.takeIf { it.isNotEmpty() }?.let(::lookupExactLambdaRefByName)
}
else -> null
}
}
private fun resolveReceiverTypeDecl(ref: ObjRef): TypeDecl? { private fun resolveReceiverTypeDecl(ref: ObjRef): TypeDecl? {
return when (ref) { return when (ref) {
is LocalSlotRef -> { is LocalSlotRef -> {
@ -10415,6 +10454,15 @@ class Compiler(
encodedPayloadTypeByName[name] = payloadClass encodedPayloadTypeByName[name] = payloadClass
} }
} }
if (slotIndex != null && scopeId != null) {
val exactLambdaRef = if (!isMutable) resolveExactLambdaRef(directRef) else null
val scopeMap = exactLambdaRefByScopeId.getOrPut(scopeId) { mutableMapOf() }
if (exactLambdaRef != null) {
scopeMap[slotIndex] = exactLambdaRef
} else {
scopeMap.remove(slotIndex)
}
}
if (initObjClass != null) { if (initObjClass != null) {
if (slotIndex != null && scopeId != null) { if (slotIndex != null && scopeId != null) {
slotTypeByScopeId.getOrPut(scopeId) { mutableMapOf() }[slotIndex] = initObjClass slotTypeByScopeId.getOrPut(scopeId) { mutableMapOf() }[slotIndex] = initObjClass

View File

@ -33,6 +33,7 @@ class BytecodeCompiler(
private val globalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(), private val globalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
private val globalSlotScopeId: Int? = null, private val globalSlotScopeId: Int? = null,
private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(), private val slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
private val exactLambdaRefByScopeId: Map<Int, Map<Int, LambdaFnRef>> = emptyMap(),
private val slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(), private val slotTypeDeclByScopeId: Map<Int, Map<Int, TypeDecl>> = emptyMap(),
private val knownNameObjClass: Map<String, ObjClass> = emptyMap(), private val knownNameObjClass: Map<String, ObjClass> = emptyMap(),
private val knownClassNames: Set<String> = emptySet(), private val knownClassNames: Set<String> = emptySet(),
@ -89,6 +90,9 @@ class BytecodeCompiler(
private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>() private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>()
private val intLoopVarNames = LinkedHashSet<String>() private val intLoopVarNames = LinkedHashSet<String>()
private val valueFnRefs = LinkedHashSet<ValueFnRef>() private val valueFnRefs = LinkedHashSet<ValueFnRef>()
private val exactLambdaRefBySlot = LinkedHashMap<Int, LambdaFnRef>()
private val activeInlineLambdas = LinkedHashSet<LambdaFnRef>()
private val inlineThisBindings = ArrayDeque<InlineThisBinding>()
private val loopVarKeys = LinkedHashSet<ScopeSlotKey>() private val loopVarKeys = LinkedHashSet<ScopeSlotKey>()
private val loopVarSlots = HashSet<Int>() private val loopVarSlots = HashSet<Int>()
private val loopStack = ArrayDeque<LoopContext>() private val loopStack = ArrayDeque<LoopContext>()
@ -104,6 +108,8 @@ class BytecodeCompiler(
val hasIterator: Boolean, val hasIterator: Boolean,
) )
private data class InlineThisBinding(val slot: Int, val typeName: String?)
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? { fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? {
prepareCompilation(stmt) prepareCompilation(stmt)
setPos(stmt.pos) setPos(stmt.pos)
@ -559,7 +565,10 @@ class BytecodeCompiler(
val mapped = resolveSlot(ref) ?: return null val mapped = resolveSlot(ref) ?: return null
var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN var resolved = slotTypes[mapped] ?: SlotType.UNKNOWN
if (resolved == SlotType.UNKNOWN) { if (resolved == SlotType.UNKNOWN) {
val key = ScopeSlotKey(refScopeId(ref), refSlot(ref)) val key = ScopeSlotKey(
ref.captureOwnerScopeId ?: refScopeId(ref),
ref.captureOwnerSlot ?: refSlot(ref)
)
val inferred = slotTypeFromClass(slotInitClassByKey[key]) val inferred = slotTypeFromClass(slotInitClassByKey[key])
if (inferred != null) { if (inferred != null) {
updateSlotType(mapped, inferred) updateSlotType(mapped, inferred)
@ -696,7 +705,7 @@ class BytecodeCompiler(
compileThisVariantRef(typeName) ?: return null compileThisVariantRef(typeName) ?: return null
} ?: compileThisRef() } ?: compileThisRef()
val ownerClass = ref.preferredThisTypeName()?.let { resolveTypeNameClass(it) } val ownerClass = ref.preferredThisTypeName()?.let { resolveTypeNameClass(it) }
?: implicitThisTypeName?.let { resolveTypeNameClass(it) } ?: currentImplicitThisTypeName()?.let { resolveTypeNameClass(it) }
val fieldId = ref.fieldId ?: -1 val fieldId = ref.fieldId ?: -1
val methodId = ref.methodId ?: -1 val methodId = ref.methodId ?: -1
if (fieldId < 0 && methodId < 0) { if (fieldId < 0 && methodId < 0) {
@ -804,6 +813,9 @@ class BytecodeCompiler(
} }
private fun compileThisRef(): CompiledValue { private fun compileThisRef(): CompiledValue {
inlineThisBindings.lastOrNull()?.let { binding ->
return CompiledValue(binding.slot, SlotType.OBJ)
}
val slot = allocSlot() val slot = allocSlot()
builder.emit(Opcode.LOAD_THIS, slot) builder.emit(Opcode.LOAD_THIS, slot)
updateSlotType(slot, SlotType.OBJ) updateSlotType(slot, SlotType.OBJ)
@ -811,6 +823,9 @@ class BytecodeCompiler(
} }
private fun compileThisVariantRef(typeName: String): CompiledValue? { private fun compileThisVariantRef(typeName: String): CompiledValue? {
inlineThisBindings.lastOrNull { it.typeName == typeName }?.let { binding ->
return CompiledValue(binding.slot, SlotType.OBJ)
}
val typeId = builder.addConst(BytecodeConst.StringVal(typeName)) val typeId = builder.addConst(BytecodeConst.StringVal(typeName))
if (typeId > 0xFFFF) return null if (typeId > 0xFFFF) return null
val slot = allocSlot() val slot = allocSlot()
@ -819,6 +834,10 @@ class BytecodeCompiler(
return CompiledValue(slot, SlotType.OBJ) return CompiledValue(slot, SlotType.OBJ)
} }
private fun currentImplicitThisTypeName(): String? {
return inlineThisBindings.lastOrNull()?.typeName ?: implicitThisTypeName
}
private fun compileConst(obj: Obj): CompiledValue? { private fun compileConst(obj: Obj): CompiledValue? {
val slot = allocSlot() val slot = allocSlot()
when (obj) { when (obj) {
@ -2509,6 +2528,7 @@ class BytecodeCompiler(
} }
updateSlotType(slot, value.type) updateSlotType(slot, value.type)
propagateObjClass(value.type, value.slot, slot) propagateObjClass(value.type, value.slot, slot)
trackExactLambdaAtSlot(slot, null)
updateNameObjClassFromSlot(localTarget.name, slot) updateNameObjClassFromSlot(localTarget.name, slot)
return value return value
} }
@ -2552,6 +2572,7 @@ class BytecodeCompiler(
} }
updateSlotType(slot, value.type) updateSlotType(slot, value.type)
propagateObjClass(value.type, value.slot, slot) propagateObjClass(value.type, value.slot, slot)
trackExactLambdaAtSlot(slot, null)
updateNameObjClassFromSlot(nameTarget, slot) updateNameObjClassFromSlot(nameTarget, slot)
return value return value
} }
@ -3587,7 +3608,7 @@ class BytecodeCompiler(
private fun compileThisFieldSlotRef(ref: ThisFieldSlotRef): CompiledValue? { private fun compileThisFieldSlotRef(ref: ThisFieldSlotRef): CompiledValue? {
val receiver = compileThisRef() val receiver = compileThisRef()
val ownerClass = implicitThisTypeName?.let { resolveTypeNameClass(it) } val ownerClass = currentImplicitThisTypeName()?.let { resolveTypeNameClass(it) }
val fieldId = ref.fieldId() ?: -1 val fieldId = ref.fieldId() ?: -1
val methodId = ref.methodId() ?: -1 val methodId = ref.methodId() ?: -1
if (fieldId < 0 && methodId < 0) { if (fieldId < 0 && methodId < 0) {
@ -4566,6 +4587,10 @@ class BytecodeCompiler(
private fun compileCall(ref: CallRef): CompiledValue? { private fun compileCall(ref: CallRef): CompiledValue? {
val callPos = callSitePos() val callPos = callSitePos()
val lambdaTarget = resolveInlineCallableLambda(ref.target)
if (lambdaTarget != null) {
compileInlineDirectLambdaCall(ref, lambdaTarget)?.let { return it }
}
val fieldTarget = ref.target as? FieldRef val fieldTarget = ref.target as? FieldRef
if (fieldTarget != null && isKnownClassReceiver(fieldTarget.target)) { if (fieldTarget != null && isKnownClassReceiver(fieldTarget.target)) {
val receiverClass = resolveReceiverClass(fieldTarget.target) val receiverClass = resolveReceiverClass(fieldTarget.target)
@ -4715,6 +4740,9 @@ class BytecodeCompiler(
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? { private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
compileListFillIntCall(ref)?.let { return it } compileListFillIntCall(ref)?.let { return it }
compileInlineUnaryLambdaMethodCall(ref)?.let { return it }
compileInlineReceiverLambdaMethodCall(ref)?.let { return it }
compileInlineIterableLambdaMethodCall(ref)?.let { return it }
val callPos = callSitePos() val callPos = callSitePos()
val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
@ -4893,6 +4921,151 @@ class BytecodeCompiler(
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
private fun compileInlineUnaryLambdaMethodCall(ref: MethodCallRef): CompiledValue? {
val behavior = when (ref.name) {
"let" -> InlineUnaryLambdaMethodBehavior.RETURN_BLOCK_RESULT
"also" -> InlineUnaryLambdaMethodBehavior.RETURN_RECEIVER
else -> return null
}
if (ref.args.size != 1 || ref.args.any { it.isSplat || it.name != null }) return null
if (!ref.explicitTypeArgs.isNullOrEmpty()) return null
val lambdaRef = extractExactLambdaRef(ref.args.first().value) ?: return null
if (hasModuleCapture(lambdaRef)) return null
val inlineRef = lambdaRef.inlineBodyRef ?: return null
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = true)) return null
val paramName = lambdaRef.inlineParamNames()?.singleOrNull() ?: return null
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
return if (!ref.isOptional) {
val receiverSlot = materializeInlineBinding(receiver)
when (behavior) {
InlineUnaryLambdaMethodBehavior.RETURN_BLOCK_RESULT ->
compileInlineLambdaBody(lambdaRef, inlineRef, listOf(paramName to receiverSlot))
InlineUnaryLambdaMethodBehavior.RETURN_RECEIVER -> {
compileInlineLambdaBody(lambdaRef, inlineRef, listOf(paramName to receiverSlot)) ?: return null
CompiledValue(receiverSlot, receiver.type)
}
}
} else {
val receiverObj = ensureObjSlot(receiver)
val dst = allocSlot()
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiverObj.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
val receiverSlot = materializeInlineBinding(receiver)
when (behavior) {
InlineUnaryLambdaMethodBehavior.RETURN_BLOCK_RESULT -> {
val inlineResult =
compileInlineLambdaBody(lambdaRef, inlineRef, listOf(paramName to receiverSlot)) ?: return null
val inlineObj = ensureObjSlot(inlineResult)
builder.emit(Opcode.MOVE_OBJ, inlineObj.slot, dst)
}
InlineUnaryLambdaMethodBehavior.RETURN_RECEIVER -> {
compileInlineLambdaBody(lambdaRef, inlineRef, listOf(paramName to receiverSlot)) ?: return null
builder.emit(Opcode.MOVE_OBJ, receiverObj.slot, dst)
}
}
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst)
builder.mark(endLabel)
updateSlotType(dst, SlotType.OBJ)
CompiledValue(dst, SlotType.OBJ)
}
}
private fun compileInlineReceiverLambdaMethodCall(ref: MethodCallRef): CompiledValue? {
val behavior = when (ref.name) {
"apply" -> InlineReceiverLambdaMethodBehavior.RETURN_RECEIVER
"run" -> InlineReceiverLambdaMethodBehavior.RETURN_BLOCK_RESULT
else -> return null
}
if (ref.args.size != 1 || ref.args.any { it.isSplat || it.name != null }) return null
if (!ref.explicitTypeArgs.isNullOrEmpty()) return null
val lambdaRef = extractExactLambdaRef(ref.args.first().value) ?: return null
if (hasModuleCapture(lambdaRef)) return null
val receiverInfo = receiverInlineInfo(lambdaRef) ?: return null
val inlineRef = lambdaRef.inlineBodyRef ?: return null
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = true, allowCaptures = true)) return null
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
val receiverObj = ensureObjSlot(receiver)
return if (!ref.isOptional) {
compileInlineReceiverLambdaInvocation(receiverObj, lambdaRef, behavior, receiverInfo)
} else {
val dst = allocSlot()
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiverObj.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
val nonNullResult =
compileInlineReceiverLambdaInvocation(receiverObj, lambdaRef, behavior, receiverInfo) ?: return null
val nonNullObj = ensureObjSlot(nonNullResult)
builder.emit(Opcode.MOVE_OBJ, nonNullObj.slot, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst)
builder.mark(endLabel)
updateSlotType(dst, SlotType.OBJ)
CompiledValue(dst, SlotType.OBJ)
}
}
private fun compileInlineIterableLambdaMethodCall(ref: MethodCallRef): CompiledValue? {
val behavior = when (ref.name) {
"forEach" -> InlineIterableLambdaMethodBehavior.FOR_EACH
"map" -> InlineIterableLambdaMethodBehavior.MAP
"filter" -> InlineIterableLambdaMethodBehavior.FILTER
else -> return null
}
if (ref.args.size != 1 || ref.args.any { it.isSplat || it.name != null }) return null
if (!ref.explicitTypeArgs.isNullOrEmpty()) return null
val lambdaRef = extractExactLambdaRef(ref.args.first().value) ?: return null
if (hasAnyCapture(lambdaRef)) return null
val inlineRef = lambdaRef.inlineBodyRef ?: return null
if (!isMethodInlineSafe(lambdaRef, inlineRef, allowReceiverRefs = false, allowCaptures = false)) return null
val paramNames = lambdaRef.inlineParamNames() ?: return null
if (paramNames.size != 1) return null
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
val receiverObj = ensureObjSlot(receiver)
return if (!ref.isOptional) {
compileInlineIterableLambdaLoop(receiverObj, ref, lambdaRef, inlineRef, paramNames[0], behavior)
} else {
val dst = allocSlot()
val nullSlot = allocSlot()
builder.emit(Opcode.CONST_NULL, nullSlot)
val cmpSlot = allocSlot()
builder.emit(Opcode.CMP_REF_EQ_OBJ, receiverObj.slot, nullSlot, cmpSlot)
val nullLabel = builder.label()
val endLabel = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel))
)
val nonNullResult =
compileInlineIterableLambdaLoop(receiverObj, ref, lambdaRef, inlineRef, paramNames[0], behavior) ?: return null
val nonNullObj = ensureObjSlot(nonNullResult)
builder.emit(Opcode.MOVE_OBJ, nonNullObj.slot, dst)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nullLabel)
builder.emit(Opcode.CONST_NULL, dst)
builder.mark(endLabel)
updateSlotType(dst, SlotType.OBJ)
CompiledValue(dst, SlotType.OBJ)
}
}
private fun compileListFillIntCall(ref: MethodCallRef): CompiledValue? { private fun compileListFillIntCall(ref: MethodCallRef): CompiledValue? {
if (ref.name != "fill" || !isListTypeRef(ref.receiver)) return null if (ref.name != "fill" || !isListTypeRef(ref.receiver)) return null
if (ref.args.size != 2 || ref.args.any { it.isSplat || it.name != null }) return null if (ref.args.size != 2 || ref.args.any { it.isSplat || it.name != null }) return null
@ -4900,15 +5073,499 @@ class BytecodeCompiler(
if (lambdaRef.inferredReturnClass != ObjInt.type) return null if (lambdaRef.inferredReturnClass != ObjInt.type) return null
val size = compileArgValue(ref.args[0].value) ?: return null val size = compileArgValue(ref.args[0].value) ?: return null
if (size.type != SlotType.INT) return null if (size.type != SlotType.INT) return null
val callable = ensureObjSlot(compileArgValue(ref.args[1].value) ?: return null) lambdaRef.inlineBodyRef?.let { inlineRef ->
return compileInlineListFillInt(size, lambdaRef, inlineRef)
}
run {
val callable = ensureObjSlot(compileArgValue(ref.args[1].value) ?: return null)
val dst = allocSlot()
builder.emit(Opcode.LIST_FILL_INT, size.slot, callable.slot, dst)
updateSlotType(dst, SlotType.OBJ)
slotObjClass[dst] = ObjList.type
listElementClassBySlot[dst] = ObjInt.type
return CompiledValue(dst, SlotType.OBJ)
}
}
private fun compileInlineDirectLambdaCall(ref: CallRef, lambdaRef: LambdaFnRef): CompiledValue? {
if (ref.isOptionalInvoke) return null
if (ref.tailBlock) return null
if (!ref.explicitTypeArgs.isNullOrEmpty()) return null
val inlineRef = lambdaRef.inlineBodyRef ?: return null
val bindings = prepareInlineLambdaBindings(lambdaRef, ref.args) ?: return null
return compileInlineLambdaBody(lambdaRef, inlineRef, bindings)
}
private fun prepareInlineLambdaBindings(
lambdaRef: LambdaFnRef,
args: List<ParsedArgument>
): List<Pair<String, Int>>? {
if (args.any { it.isSplat || it.name != null }) return null
val paramNames = lambdaRef.inlineParamNames() ?: return null
if (args.size != paramNames.size) return null
if (args.isEmpty()) return emptyList()
val bindings = ArrayList<Pair<String, Int>>(args.size)
for ((index, arg) in args.withIndex()) {
val compiled = compileArgValue(arg.value) ?: return null
bindings += paramNames[index] to materializeInlineBinding(compiled)
}
return bindings
}
private fun LambdaFnRef.inlineParamNames(): List<String>? {
val declaration = argsDeclaration
if (declaration == null) {
return listOf("it")
}
if (declaration.params.any { it.isEllipsis || it.defaultValue != null }) return null
return declaration.params.map { it.name }
}
private fun materializeInlineBinding(value: CompiledValue): Int {
val slot = allocSlot()
emitMove(value, slot)
updateSlotType(slot, value.type)
if (value.type == SlotType.OBJ) {
slotObjClass[value.slot]?.let { slotObjClass[slot] = it }
}
return slot
}
private enum class InlineUnaryLambdaMethodBehavior {
RETURN_BLOCK_RESULT,
RETURN_RECEIVER
}
private enum class InlineReceiverLambdaMethodBehavior {
RETURN_BLOCK_RESULT,
RETURN_RECEIVER
}
private enum class InlineIterableLambdaMethodBehavior {
FOR_EACH,
MAP,
FILTER
}
private data class InlineReceiverInfo(
val explicitBindings: List<Pair<String, Int>>,
val thisTypeName: String?
)
private fun hasModuleCapture(lambdaRef: LambdaFnRef): Boolean {
val captures = (lambdaCaptureEntriesByRef[lambdaRef] ?: lambdaRef.captureEntries).orEmpty()
return captures.any { it.ownerKind == CaptureOwnerFrameKind.MODULE }
}
private fun hasAnyCapture(lambdaRef: LambdaFnRef): Boolean {
val captures = (lambdaCaptureEntriesByRef[lambdaRef] ?: lambdaRef.captureEntries).orEmpty()
return captures.isNotEmpty()
}
private fun isMethodInlineSafe(
lambdaRef: LambdaFnRef,
inlineRef: ObjRef,
allowReceiverRefs: Boolean,
allowCaptures: Boolean
): Boolean {
val allowedLocalNames = LinkedHashSet<String>()
lambdaRef.inlineParamNames()?.let { allowedLocalNames.addAll(it) }
if (allowCaptures) {
val captures = (lambdaCaptureEntriesByRef[lambdaRef] ?: lambdaRef.captureEntries).orEmpty()
allowedLocalNames.addAll(captures.map { it.ownerName })
}
fun isAllowedName(name: String): Boolean {
return name == "this" || allowedLocalNames.contains(name)
}
fun scan(ref: ObjRef): Boolean {
return when (ref) {
is ConstRef,
is TypeDeclRef,
is ClassOperatorRef -> true
is LambdaFnRef -> false
is LocalSlotRef -> isAllowedName(ref.name)
is LocalVarRef -> isAllowedName(ref.name)
is FastLocalVarRef -> isAllowedName(ref.name)
is BoundLocalVarRef -> false
is FieldRef -> scan(ref.target)
is MethodCallRef -> scan(ref.receiver) && ref.args.all { arg ->
val expr = arg.value as? ExpressionStatement ?: return@all false
scan(expr.ref)
}
is CallRef -> scan(ref.target) && ref.args.all { arg ->
val expr = arg.value as? ExpressionStatement ?: return@all false
scan(expr.ref)
}
is BinaryOpRef -> scan(ref.left) && scan(ref.right)
is UnaryOpRef -> scan(ref.a)
is LogicalAndRef -> scan(ref.left()) && scan(ref.right())
is LogicalOrRef -> scan(ref.left()) && scan(ref.right())
is ConditionalRef -> scan(ref.condition) && scan(ref.ifTrue) && scan(ref.ifFalse)
is ElvisRef -> scan(ref.left) && scan(ref.right)
is CastRef -> scan(ref.castValueRef()) && scan(ref.castTypeRef())
is RangeRef -> listOfNotNull(ref.left, ref.right, ref.step).all(::scan)
is AssignRef -> scan(ref.target) && scan(ref.value)
is AssignOpRef -> scan(ref.target) && scan(ref.value)
is AssignIfNullRef -> scan(ref.target) && scan(ref.value)
is IncDecRef -> scan(ref.target)
is IndexRef -> scan(ref.targetRef) && scan(ref.indexRef)
is ListLiteralRef -> ref.entries().all { entry ->
when (entry) {
is ListEntry.Element -> scan(entry.ref)
is ListEntry.Spread -> scan(entry.ref)
}
}
is MapLiteralRef -> ref.entries().all { entry ->
when (entry) {
is MapLiteralEntry.Named -> scan(entry.value)
is MapLiteralEntry.Spread -> scan(entry.ref)
}
}
is StatementRef -> {
val expr = ref.statement as? ExpressionStatement ?: return false
scan(expr.ref)
}
is ImplicitThisMemberRef,
is ImplicitThisMethodCallRef,
is ThisFieldSlotRef,
is ThisMethodSlotCallRef,
is QualifiedThisFieldSlotRef,
is QualifiedThisMethodSlotCallRef,
is QualifiedThisRef -> allowReceiverRefs
else -> false
}
}
return scan(inlineRef)
}
private fun receiverInlineInfo(lambdaRef: LambdaFnRef): InlineReceiverInfo? {
val declaration = lambdaRef.argsDeclaration
return if (declaration == null) {
InlineReceiverInfo(listOf("it" to ensureVoidSlot()), lambdaRef.preferredThisType)
} else {
if (declaration.params.any { it.isEllipsis || it.defaultValue != null }) return null
if (declaration.params.isNotEmpty()) return null
InlineReceiverInfo(emptyList(), lambdaRef.preferredThisType)
}
}
private fun compileInlineReceiverLambdaInvocation(
receiverObj: CompiledValue,
lambdaRef: LambdaFnRef,
behavior: InlineReceiverLambdaMethodBehavior,
receiverInfo: InlineReceiverInfo
): CompiledValue? {
val inlineRef = lambdaRef.inlineBodyRef ?: return null
val receiverSlot = materializeInlineBinding(receiverObj)
val previousBinding = InlineThisBinding(receiverSlot, receiverInfo.thisTypeName)
inlineThisBindings.addLast(previousBinding)
return try {
when (behavior) {
InlineReceiverLambdaMethodBehavior.RETURN_BLOCK_RESULT ->
compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings)
InlineReceiverLambdaMethodBehavior.RETURN_RECEIVER -> {
compileInlineLambdaBody(lambdaRef, inlineRef, receiverInfo.explicitBindings) ?: return null
CompiledValue(receiverSlot, SlotType.OBJ)
}
}
} finally {
inlineThisBindings.removeLast()
}
}
private fun createEmptyMutableList(): CompiledValue? {
val calleeId = builder.addConst(BytecodeConst.ObjRef(ObjList.type))
val calleeSlot = allocSlot()
builder.emit(Opcode.CONST_OBJ, calleeId, calleeSlot)
updateSlotType(calleeSlot, SlotType.OBJ)
val dst = allocSlot() val dst = allocSlot()
builder.emit(Opcode.LIST_FILL_INT, size.slot, callable.slot, dst) builder.emit(Opcode.CALL_SLOT, calleeSlot, 0, 0, dst)
updateSlotType(dst, SlotType.OBJ)
slotObjClass[dst] = ObjList.type
return CompiledValue(dst, SlotType.OBJ)
}
private fun compileInlineIterableLambdaLoop(
receiverObj: CompiledValue,
ref: MethodCallRef,
lambdaRef: LambdaFnRef,
inlineRef: ObjRef,
paramName: String,
behavior: InlineIterableLambdaMethodBehavior
): CompiledValue? {
val iterableMethods = ObjIterable.instanceMethodIdMap(includeAbstract = true)
val iteratorMethodId = iterableMethods["iterator"]
?: throw BytecodeCompileException("Missing member id for Iterable.iterator", refPosOrCurrent(ref.receiver))
val iteratorMethods = ObjIterator.instanceMethodIdMap(includeAbstract = true)
val hasNextMethodId = iteratorMethods["hasNext"]
?: throw BytecodeCompileException("Missing member id for Iterator.hasNext", refPosOrCurrent(ref.receiver))
val nextMethodId = iteratorMethods["next"]
?: throw BytecodeCompileException("Missing member id for Iterator.next", refPosOrCurrent(ref.receiver))
val iterSlot = allocSlot()
builder.emit(Opcode.CALL_MEMBER_SLOT, receiverObj.slot, iteratorMethodId, 0, 0, iterSlot)
builder.emit(Opcode.ITER_PUSH, iterSlot)
val result = when (behavior) {
InlineIterableLambdaMethodBehavior.FOR_EACH -> CompiledValue(ensureVoidSlot(), SlotType.OBJ)
InlineIterableLambdaMethodBehavior.MAP,
InlineIterableLambdaMethodBehavior.FILTER -> createEmptyMutableList() ?: return null
}
if (behavior == InlineIterableLambdaMethodBehavior.FILTER) {
listElementClassFromReceiverRef(ref.receiver)?.let { listElementClassBySlot[result.slot] = it }
}
if (behavior == InlineIterableLambdaMethodBehavior.MAP) {
lambdaRef.inferredReturnClass?.let { listElementClassBySlot[result.slot] = it }
}
val loopLabel = builder.label()
val endLabel = builder.label()
builder.mark(loopLabel)
val hasNextSlot = allocSlot()
builder.emit(Opcode.CALL_MEMBER_SLOT, iterSlot, hasNextMethodId, 0, 0, hasNextSlot)
val condSlot = allocSlot()
builder.emit(Opcode.OBJ_TO_BOOL, hasNextSlot, condSlot)
builder.emit(
Opcode.JMP_IF_FALSE,
listOf(CmdBuilder.Operand.IntVal(condSlot), CmdBuilder.Operand.LabelRef(endLabel))
)
val nextSlot = allocSlot()
builder.emit(Opcode.CALL_MEMBER_SLOT, iterSlot, nextMethodId, 0, 0, nextSlot)
val nextObj = ensureObjSlot(CompiledValue(nextSlot, SlotType.UNKNOWN))
when (behavior) {
InlineIterableLambdaMethodBehavior.FOR_EACH -> {
compileInlineLambdaBody(lambdaRef, inlineRef, listOf(paramName to nextObj.slot)) ?: return null
}
InlineIterableLambdaMethodBehavior.MAP -> {
val mapped = compileInlineLambdaBody(lambdaRef, inlineRef, listOf(paramName to nextObj.slot)) ?: return null
appendToList(result, mapped) ?: return null
}
InlineIterableLambdaMethodBehavior.FILTER -> {
val predicate = compileInlineLambdaBody(lambdaRef, inlineRef, listOf(paramName to nextObj.slot)) ?: return null
val predicateBool = compileValueAsBool(predicate)
val skipLabel = builder.label()
builder.emit(
Opcode.JMP_IF_FALSE,
listOf(CmdBuilder.Operand.IntVal(predicateBool.slot), CmdBuilder.Operand.LabelRef(skipLabel))
)
appendToList(result, nextObj) ?: return null
builder.mark(skipLabel)
}
}
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
builder.mark(endLabel)
builder.emit(Opcode.ITER_POP)
return result
}
private fun appendToList(listValue: CompiledValue, itemValue: CompiledValue): CompiledValue? {
val addMethodId = ObjList.type.instanceMethodIdMap(includeAbstract = true)["add"]
?: throw BytecodeCompileException("Missing member id for List.add", Pos.builtIn)
val listObj = ensureObjSlot(listValue)
val itemObj = ensureObjSlot(itemValue)
val argSlot = allocSlot()
builder.emit(Opcode.MOVE_OBJ, itemObj.slot, argSlot)
updateSlotType(argSlot, SlotType.OBJ)
val dst = allocSlot()
builder.emit(Opcode.CALL_MEMBER_SLOT, listObj.slot, addMethodId, argSlot, 1, dst)
noteListElementClassMutation(listObj.slot, itemObj)
updateSlotType(dst, SlotType.OBJ)
return CompiledValue(dst, SlotType.OBJ)
}
private fun compileValueAsBool(value: CompiledValue): CompiledValue {
if (value.type == SlotType.BOOL) return value
val dst = allocSlot()
when (value.type) {
SlotType.INT -> builder.emit(Opcode.INT_TO_BOOL, value.slot, dst)
SlotType.OBJ, SlotType.UNKNOWN, SlotType.REAL -> {
val obj = ensureObjSlot(value)
builder.emit(Opcode.OBJ_TO_BOOL, obj.slot, dst)
}
}
updateSlotType(dst, SlotType.BOOL)
return CompiledValue(dst, SlotType.BOOL)
}
private fun compileInlineLambdaBody(
lambdaRef: LambdaFnRef,
inlineRef: ObjRef,
explicitBindings: List<Pair<String, Int>>
): CompiledValue? {
if (!activeInlineLambdas.add(lambdaRef)) return null
val previousOverrides = ArrayList<Pair<String, Int?>>(lambdaRef.captureEntries.size + explicitBindings.size)
for (capture in lambdaRef.captureEntries) {
val slot = resolveInlineCaptureSlot(capture) ?: continue
previousOverrides += capture.ownerName to loopSlotOverrides.put(capture.ownerName, slot)
}
for ((name, slot) in explicitBindings) {
previousOverrides += name to loopSlotOverrides.put(name, slot)
}
return try {
compileRefWithFallback(inlineRef, null, refPosOrCurrent(inlineRef))
} finally {
for ((name, previous) in previousOverrides.asReversed()) {
if (previous == null) loopSlotOverrides.remove(name) else loopSlotOverrides[name] = previous
}
activeInlineLambdas.remove(lambdaRef)
}
}
private fun resolveInlineCallableLambda(target: ObjRef): LambdaFnRef? {
val lambdaRef = when (target) {
is LambdaFnRef -> target
is LocalSlotRef -> {
val ownerScopeId = target.captureOwnerScopeId ?: target.scopeId
val ownerSlot = target.captureOwnerSlot ?: target.slot
exactLambdaRefByScopeId[ownerScopeId]?.get(ownerSlot)
?: resolveLocalSlotByRefOrName(target)?.let { exactLambdaRefBySlot[it] }
}
is LocalVarRef -> resolveDirectNameSlot(target.name)?.slot?.let { exactLambdaRefBySlot[it] }
is FastLocalVarRef -> resolveDirectNameSlot(target.name)?.slot?.let { exactLambdaRefBySlot[it] }
is BoundLocalVarRef -> exactLambdaRefBySlot[target.slotIndex()]
else -> null
}
return lambdaRef?.takeUnless { activeInlineLambdas.contains(it) }
}
private fun trackExactLambdaAtSlot(slot: Int, lambdaRef: LambdaFnRef?) {
if (lambdaRef == null) {
exactLambdaRefBySlot.remove(slot)
} else {
exactLambdaRefBySlot[slot] = lambdaRef
}
}
private fun preloadExactLambdaRefs() {
if (exactLambdaRefByScopeId.isEmpty()) return
for ((scopeId, slots) in exactLambdaRefByScopeId) {
for ((slotIndex, lambdaRef) in slots) {
val key = ScopeSlotKey(scopeId, slotIndex)
val localIndex = localSlotIndexByKey[key]
if (localIndex != null) {
trackExactLambdaAtSlot(scopeSlotCount + localIndex, lambdaRef)
continue
}
val scopeIndex = scopeSlotMap[key]
if (scopeIndex != null) {
trackExactLambdaAtSlot(scopeIndex, lambdaRef)
}
}
}
}
private fun collectExactLambdaModuleCaptures() {
if (exactLambdaRefByScopeId.isEmpty()) return
val seen = LinkedHashSet<LambdaFnRef>()
for (slots in exactLambdaRefByScopeId.values) {
for (lambdaRef in slots.values) {
if (!seen.add(lambdaRef)) continue
val captures = (lambdaCaptureEntriesByRef[lambdaRef] ?: lambdaRef.captureEntries).orEmpty()
for (entry in captures) {
if (entry.ownerKind != CaptureOwnerFrameKind.MODULE) continue
val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
if (useScopeSlots) {
if (!scopeSlotMap.containsKey(key)) {
scopeSlotMap[key] = scopeSlotMap.size
}
if (!scopeSlotNameMap.containsKey(key)) {
scopeSlotNameMap[key] = entry.ownerName
}
if (!scopeSlotMutableMap.containsKey(key)) {
scopeSlotMutableMap[key] = entry.ownerIsMutable
}
} else if (globalSlotInfo.isNotEmpty() && globalSlotScopeId != null) {
if (!localSlotInfoMap.containsKey(key)) {
localSlotInfoMap[key] = LocalSlotInfo(
entry.ownerName,
entry.ownerIsMutable,
entry.ownerIsDelegated
)
}
captureSlotKeys.add(key)
}
}
}
}
}
private fun extractExactLambdaRef(value: Obj?): LambdaFnRef? {
val expr = value as? ExpressionStatement ?: return null
return when (val ref = expr.ref) {
is LambdaFnRef -> ref
is LocalSlotRef -> resolveLocalSlotByRefOrName(ref)?.let { exactLambdaRefBySlot[it] }
is LocalVarRef -> resolveDirectNameSlot(ref.name)?.slot?.let { exactLambdaRefBySlot[it] }
is FastLocalVarRef -> resolveDirectNameSlot(ref.name)?.slot?.let { exactLambdaRefBySlot[it] }
is BoundLocalVarRef -> exactLambdaRefBySlot[ref.slotIndex()]
else -> null
}
}
private fun compileInlineListFillInt(size: CompiledValue, lambdaRef: LambdaFnRef, inlineRef: ObjRef): CompiledValue {
if (isImplicitItIdentityRef(inlineRef)) {
val dst = allocSlot()
builder.emit(Opcode.LIST_IOTA_INT, size.slot, dst)
updateSlotType(dst, SlotType.OBJ)
slotObjClass[dst] = ObjList.type
listElementClassBySlot[dst] = ObjInt.type
return CompiledValue(dst, SlotType.OBJ)
}
val dst = allocSlot()
builder.emit(Opcode.LIST_NEW_INT, size.slot, dst)
updateSlotType(dst, SlotType.OBJ) updateSlotType(dst, SlotType.OBJ)
slotObjClass[dst] = ObjList.type slotObjClass[dst] = ObjList.type
listElementClassBySlot[dst] = ObjInt.type listElementClassBySlot[dst] = ObjInt.type
val iSlot = allocSlot()
val zeroId = builder.addConst(BytecodeConst.IntVal(0))
builder.emit(Opcode.CONST_INT, zeroId, iSlot)
updateSlotType(iSlot, SlotType.INT)
val loopLabel = builder.label()
val endLabel = builder.label()
builder.mark(loopLabel)
builder.emit(
Opcode.JMP_IF_GTE_INT,
listOf(
CmdBuilder.Operand.IntVal(iSlot),
CmdBuilder.Operand.IntVal(size.slot),
CmdBuilder.Operand.LabelRef(endLabel)
)
)
val paramName = lambdaRef.inlineParamNames()?.singleOrNull()
?: throw BytecodeCompileException("unsupported List.fill lambda parameters", refPosOrCurrent(inlineRef))
val compiledValue = compileInlineLambdaBody(lambdaRef, inlineRef, listOf(paramName to iSlot))
?: throw BytecodeCompileException("failed to inline List.fill lambda", refPosOrCurrent(inlineRef))
val intValue = coerceToLoopInt(compiledValue)
?: throw BytecodeCompileException("inlined List.fill lambda must produce Int", refPosOrCurrent(inlineRef))
builder.emit(Opcode.SET_INDEX_INT, dst, iSlot, intValue.slot)
builder.emit(Opcode.INC_INT, iSlot)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
builder.mark(endLabel)
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
} }
private fun resolveInlineCaptureSlot(entry: LambdaCaptureEntry): Int? {
if (entry.ownerKind != CaptureOwnerFrameKind.LOCAL) return null
val key = ScopeSlotKey(entry.ownerScopeId, entry.ownerSlotId)
localSlotIndexByKey[key]?.let { return scopeSlotCount + it }
return null
}
private fun isImplicitItIdentityRef(ref: ObjRef): Boolean {
return when (ref) {
is LocalVarRef -> ref.name == "it"
is FastLocalVarRef -> ref.name == "it"
is LocalSlotRef -> ref.name == "it"
else -> false
}
}
private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? { private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? {
val callPos = callSitePos() val callPos = callSitePos()
val receiver = compileThisRef() val receiver = compileThisRef()
@ -5991,6 +6648,7 @@ class BytecodeCompiler(
?: updateSlotObjClass(localSlot, stmt.initializer, stmt.initializerObjClass) ?: updateSlotObjClass(localSlot, stmt.initializer, stmt.initializerObjClass)
updateListElementClassFromDecl(localSlot, scopeId, stmt.slotIndex) updateListElementClassFromDecl(localSlot, scopeId, stmt.slotIndex)
updateListElementClassFromInitializer(localSlot, stmt.initializer) updateListElementClassFromInitializer(localSlot, stmt.initializer)
trackExactLambdaAtSlot(localSlot, if (!stmt.isMutable) extractExactLambdaRef(stmt.initializer) else null)
updateNameObjClassFromSlot(stmt.name, localSlot) updateNameObjClassFromSlot(stmt.name, localSlot)
val shadowedScopeSlot = scopeSlotIndexByName.containsKey(stmt.name) val shadowedScopeSlot = scopeSlotIndexByName.containsKey(stmt.name)
val isModuleScope = moduleScopeId != null && scopeId == moduleScopeId val isModuleScope = moduleScopeId != null && scopeId == moduleScopeId
@ -6024,6 +6682,7 @@ class BytecodeCompiler(
?: updateSlotObjClass(scopeSlot, stmt.initializer, stmt.initializerObjClass) ?: updateSlotObjClass(scopeSlot, stmt.initializer, stmt.initializerObjClass)
updateListElementClassFromDecl(scopeSlot, scopeId, stmt.slotIndex) updateListElementClassFromDecl(scopeSlot, scopeId, stmt.slotIndex)
updateListElementClassFromInitializer(scopeSlot, stmt.initializer) updateListElementClassFromInitializer(scopeSlot, stmt.initializer)
trackExactLambdaAtSlot(scopeSlot, if (!stmt.isMutable) extractExactLambdaRef(stmt.initializer) else null)
val declId = builder.addConst( val declId = builder.addConst(
BytecodeConst.LocalDecl( BytecodeConst.LocalDecl(
stmt.name, stmt.name,
@ -7859,7 +8518,9 @@ class BytecodeCompiler(
scopeSlotRefPosByKey[scopeKey] = ref.pos() scopeSlotRefPosByKey[scopeKey] = ref.pos()
} }
} }
return resolved if (resolved != null) return resolved
resolveCapturedOwnerSlot(ref)?.let { return it }
return null
} }
if (ref.isDelegated) { if (ref.isDelegated) {
val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref)) val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
@ -7893,6 +8554,14 @@ class BytecodeCompiler(
return scopeSlotMap[key] return scopeSlotMap[key]
} }
private fun resolveCapturedOwnerSlot(ref: LocalSlotRef): Int? {
val ownerScopeId = ref.captureOwnerScopeId ?: return null
val ownerSlot = ref.captureOwnerSlot ?: return null
val key = ScopeSlotKey(ownerScopeId, ownerSlot)
localSlotIndexByKey[key]?.let { return scopeSlotCount + it }
return scopeSlotMap[key]
}
private fun updateSlotType(slot: Int, type: SlotType) { private fun updateSlotType(slot: Int, type: SlotType) {
if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return
if (type == SlotType.UNKNOWN) { if (type == SlotType.UNKNOWN) {
@ -7980,7 +8649,7 @@ class BytecodeCompiler(
} }
} }
is ThisFieldSlotRef -> { is ThisFieldSlotRef -> {
val ownerClass = implicitThisTypeName?.let { resolveTypeNameClass(it) } ?: return null val ownerClass = currentImplicitThisTypeName()?.let { resolveTypeNameClass(it) } ?: return null
val fieldClass = inferFieldReturnClass(ownerClass, ref.name) ?: return null val fieldClass = inferFieldReturnClass(ownerClass, ref.name) ?: return null
when (fieldClass.className) { when (fieldClass.className) {
"Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type "Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
@ -8070,6 +8739,8 @@ class BytecodeCompiler(
loopVarKeys.clear() loopVarKeys.clear()
loopVarSlots.clear() loopVarSlots.clear()
valueFnRefs.clear() valueFnRefs.clear()
exactLambdaRefBySlot.clear()
activeInlineLambdas.clear()
addrSlotByScopeSlot.clear() addrSlotByScopeSlot.clear()
loopStack.clear() loopStack.clear()
if (slotTypeByScopeId.isNotEmpty()) { if (slotTypeByScopeId.isNotEmpty()) {
@ -8102,6 +8773,7 @@ class BytecodeCompiler(
collectLoopVarNames(stmt) collectLoopVarNames(stmt)
} }
collectScopeSlots(stmt) collectScopeSlots(stmt)
collectExactLambdaModuleCaptures()
if (allowLocalSlots) { if (allowLocalSlots) {
collectLoopSlotPlans(stmt, 0) collectLoopSlotPlans(stmt, 0)
} }
@ -8293,6 +8965,7 @@ class BytecodeCompiler(
} }
} }
} }
preloadExactLambdaRefs()
if (allowLocalSlots && captureSlotKeys.isNotEmpty() && slotInitClassByKey.isNotEmpty()) { if (allowLocalSlots && captureSlotKeys.isNotEmpty() && slotInitClassByKey.isNotEmpty()) {
val scopeSlotsBase = scopeSlotMap.size val scopeSlotsBase = scopeSlotMap.size
for (key in captureSlotKeys) { for (key in captureSlotKeys) {

View File

@ -18,10 +18,7 @@
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
import net.sergeych.lyng.* import net.sergeych.lyng.*
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.*
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ValueFnRef
class BytecodeStatement private constructor( class BytecodeStatement private constructor(
val original: Statement, val original: Statement,
@ -84,6 +81,7 @@ class BytecodeStatement private constructor(
globalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(), globalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
globalSlotScopeId: Int? = null, globalSlotScopeId: Int? = null,
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(), slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
exactLambdaRefByScopeId: Map<Int, Map<Int, LambdaFnRef>> = emptyMap(),
knownNameObjClass: Map<String, ObjClass> = emptyMap(), knownNameObjClass: Map<String, ObjClass> = emptyMap(),
knownClassNames: Set<String> = emptySet(), knownClassNames: Set<String> = emptySet(),
knownObjectNames: Set<String> = emptySet(), knownObjectNames: Set<String> = emptySet(),
@ -122,6 +120,7 @@ class BytecodeStatement private constructor(
globalSlotInfo = globalSlotInfo, globalSlotInfo = globalSlotInfo,
globalSlotScopeId = globalSlotScopeId, globalSlotScopeId = globalSlotScopeId,
slotTypeByScopeId = slotTypeByScopeId, slotTypeByScopeId = slotTypeByScopeId,
exactLambdaRefByScopeId = exactLambdaRefByScopeId,
slotTypeDeclByScopeId = slotTypeDeclByScopeId, slotTypeDeclByScopeId = slotTypeDeclByScopeId,
knownNameObjClass = knownNameObjClass, knownNameObjClass = knownNameObjClass,
knownClassNames = knownClassNames, knownClassNames = knownClassNames,

View File

@ -231,8 +231,12 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX_INT -> Opcode.SET_INDEX_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_NEW_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_FILL_INT -> Opcode.LIST_FILL_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_IOTA_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.MAKE_RANGE -> Opcode.MAKE_RANGE ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL -> Opcode.LIST_LITERAL ->
@ -835,7 +839,9 @@ class CmdBuilder {
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2]) Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
Opcode.GET_INDEX_INT -> CmdGetIndexInt(operands[0], operands[1], operands[2]) Opcode.GET_INDEX_INT -> CmdGetIndexInt(operands[0], operands[1], operands[2])
Opcode.SET_INDEX_INT -> CmdSetIndexInt(operands[0], operands[1], operands[2]) Opcode.SET_INDEX_INT -> CmdSetIndexInt(operands[0], operands[1], operands[2])
Opcode.LIST_NEW_INT -> CmdListNewInt(operands[0], operands[1])
Opcode.LIST_FILL_INT -> CmdListFillInt(operands[0], operands[1], operands[2]) Opcode.LIST_FILL_INT -> CmdListFillInt(operands[0], operands[1], operands[2])
Opcode.LIST_IOTA_INT -> CmdListIotaInt(operands[0], operands[1])
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3]) Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3]) Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])

View File

@ -495,7 +495,9 @@ object CmdDisassembler {
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot) is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
is CmdGetIndexInt -> Opcode.GET_INDEX_INT to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst) is CmdGetIndexInt -> Opcode.GET_INDEX_INT to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
is CmdSetIndexInt -> Opcode.SET_INDEX_INT to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot) is CmdSetIndexInt -> Opcode.SET_INDEX_INT to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
is CmdListNewInt -> Opcode.LIST_NEW_INT to intArrayOf(cmd.sizeSlot, cmd.dst)
is CmdListFillInt -> Opcode.LIST_FILL_INT to intArrayOf(cmd.sizeSlot, cmd.callableSlot, cmd.dst) is CmdListFillInt -> Opcode.LIST_FILL_INT to intArrayOf(cmd.sizeSlot, cmd.callableSlot, cmd.dst)
is CmdListIotaInt -> Opcode.LIST_IOTA_INT to intArrayOf(cmd.sizeSlot, cmd.dst)
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst) is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst) is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst)
is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot) is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
@ -619,8 +621,12 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX_INT -> Opcode.SET_INDEX_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_NEW_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_FILL_INT -> Opcode.LIST_FILL_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_IOTA_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL -> Opcode.LIST_LITERAL ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT) listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_MEMBER_SLOT -> Opcode.GET_MEMBER_SLOT ->

View File

@ -3730,6 +3730,34 @@ class CmdCallMemberSlot(
} }
} }
class CmdListIotaInt(
internal val sizeSlot: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val size = frame.getInt(sizeSlot).toInt()
if (size < 0) frame.ensureScope().raiseIllegalArgument("list size must be non-negative")
val values = LongArray(size)
for (i in 0 until size) {
values[i] = i.toLong()
}
frame.storeObjResult(dst, ObjList(values))
return
}
}
class CmdListNewInt(
internal val sizeSlot: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val size = frame.getInt(sizeSlot).toInt()
if (size < 0) frame.ensureScope().raiseIllegalArgument("list size must be non-negative")
frame.storeObjResult(dst, ObjList(LongArray(size)))
return
}
}
class CmdGetIndex( class CmdGetIndex(
internal val targetSlot: Int, internal val targetSlot: Int,
internal val indexSlot: Int, internal val indexSlot: Int,

View File

@ -175,6 +175,8 @@ enum class Opcode(val code: Int) {
CALL_SLOT(0x93), CALL_SLOT(0x93),
CALL_BRIDGE_SLOT(0x94), CALL_BRIDGE_SLOT(0x94),
LIST_NEW_INT(0xA0),
LIST_IOTA_INT(0xA1),
GET_INDEX(0xA2), GET_INDEX(0xA2),
SET_INDEX(0xA3), SET_INDEX(0xA3),
GET_INDEX_INT(0xA4), GET_INDEX_INT(0xA4),

View File

@ -30,6 +30,7 @@ class LambdaFnRef(
val argsDeclaration: ArgsDeclaration?, val argsDeclaration: ArgsDeclaration?,
val captureEntries: List<LambdaCaptureEntry>, val captureEntries: List<LambdaCaptureEntry>,
val inferredReturnClass: ObjClass?, val inferredReturnClass: ObjClass?,
val inlineBodyRef: ObjRef?,
val preferredThisType: String?, val preferredThisType: String?,
val wrapAsExtensionCallable: Boolean, val wrapAsExtensionCallable: Boolean,
val returnLabels: Set<String>, val returnLabels: Set<String>,

View File

@ -959,7 +959,7 @@ open class ObjClass(
} }
private fun initClassScope(): Scope { private fun initClassScope(): Scope {
if (classScope == null) classScope = Scope() if (classScope == null) classScope = Scope(parent = null)
return classScope!! return classScope!!
} }

View File

@ -30,19 +30,27 @@ import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() { open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
private var boxedList: MutableList<Obj>? = null internal var boxedList: MutableList<Obj>? = null
private var primitiveIntList: LongArray? = null internal var primitiveIntList: LongArray? = null
// Logical size of primitiveIntList; capacity = primitiveIntList!!.size
internal var primitiveIntSize: Int = 0
init { init {
if (!adoptPrimitiveIntList(initialList)) { if (initialList.isNotEmpty()) {
boxedList = initialList if (!adoptPrimitiveIntList(initialList)) {
boxedList = initialList
}
} }
// Empty initialList: both null — lazy mode, avoids boxing on first append
} }
val list: MutableList<Obj> val list: MutableList<Obj>
get() = ensureBoxedList() get() = ensureBoxedList()
internal fun sizeFast(): Int = primitiveIntList?.size ?: boxedList?.size ?: 0 internal fun sizeFast(): Int = when {
primitiveIntList != null -> primitiveIntSize
else -> boxedList?.size ?: 0
}
internal fun getObjAtFast(index: Int): Obj = internal fun getObjAtFast(index: Int): Obj =
primitiveIntList?.let { ObjInt.of(it[index]) } ?: boxedList!![index] primitiveIntList?.let { ObjInt.of(it[index]) } ?: boxedList!![index]
@ -80,9 +88,24 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
internal fun appendFast(value: Obj) { internal fun appendFast(value: Obj) {
val ints = primitiveIntList val ints = primitiveIntList
if (ints != null && value is ObjInt) { if (value is ObjInt) {
primitiveIntList = ints.copyOf(ints.size + 1).also { it[ints.size] = value.value } if (ints != null) {
return // Primitive mode: amortized growth (no copy when capacity allows)
if (primitiveIntSize < ints.size) {
ints[primitiveIntSize++] = value.value
} else {
val grown = ints.copyOf(maxOf(ints.size * 2, 16))
grown[primitiveIntSize++] = value.value
primitiveIntList = grown
}
return
}
if (boxedList == null) {
// Lazy empty state: first int element starts primitive mode
primitiveIntList = LongArray(16).also { it[0] = value.value }
primitiveIntSize = 1
return
}
} }
ensureBoxedList().add(value) ensureBoxedList().add(value)
} }
@ -91,10 +114,12 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
val ints = primitiveIntList val ints = primitiveIntList
val otherInts = other.primitiveIntList val otherInts = other.primitiveIntList
if (ints != null && otherInts != null) { if (ints != null && otherInts != null) {
primitiveIntList = LongArray(ints.size + otherInts.size).also { val otherSize = other.primitiveIntSize
ints.copyInto(it, 0, 0, ints.size) val newSize = primitiveIntSize + otherSize
otherInts.copyInto(it, ints.size, 0, otherInts.size) val dest = if (newSize <= ints.size) ints else ints.copyOf(newSize)
} otherInts.copyInto(dest, primitiveIntSize, 0, otherSize)
primitiveIntList = dest
primitiveIntSize = newSize
return return
} }
ensureBoxedList().addAll(other.list) ensureBoxedList().addAll(other.list)
@ -108,6 +133,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
ints[i] = value.value ints[i] = value.value
} }
primitiveIntList = ints primitiveIntList = ints
primitiveIntSize = ints.size
boxedList = null boxedList = null
return true return true
} }
@ -120,12 +146,13 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
boxedList = empty boxedList = empty
return empty return empty
} }
val materialized = ArrayList<Obj>(ints.size) val materialized = ArrayList<Obj>(primitiveIntSize)
for (value in ints) { for (i in 0..<primitiveIntSize) {
materialized.add(ObjInt.of(value)) materialized.add(ObjInt.of(ints[i]))
} }
boxedList = materialized boxedList = materialized
primitiveIntList = null primitiveIntList = null
primitiveIntSize = 0
return materialized return materialized
} }
@ -140,6 +167,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
internal constructor(intValues: LongArray) : this(mutableListOf()) { internal constructor(intValues: LongArray) : this(mutableListOf()) {
primitiveIntList = intValues primitiveIntList = intValues
primitiveIntSize = intValues.size
boxedList = null boxedList = null
} }
@ -253,9 +281,11 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
val ints = primitiveIntList val ints = primitiveIntList
val otherInts = other.primitiveIntList val otherInts = other.primitiveIntList
if (ints != null && otherInts != null) { if (ints != null && otherInts != null) {
ObjList(LongArray(ints.size + otherInts.size).also { val mySize = primitiveIntSize
ints.copyInto(it, 0, 0, ints.size) val otherSize = other.primitiveIntSize
otherInts.copyInto(it, ints.size, 0, otherInts.size) ObjList(LongArray(mySize + otherSize).also {
ints.copyInto(it, 0, 0, mySize)
otherInts.copyInto(it, mySize, 0, otherSize)
}) })
} else { } else {
ObjList((list + other.list).toMutableList()) ObjList((list + other.list).toMutableList())
@ -276,7 +306,9 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
open override suspend fun plusAssign(scope: Scope, other: Obj): Obj { open override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
if (other is ObjList) { if (other is ObjInt || other is ObjString || other is ObjBool || other is ObjReal || other is ObjNull) {
appendFast(other)
} else if (other is ObjList) {
appendAllFast(other) appendAllFast(other)
} else if (!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable)) { } else if (!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable)) {
val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list
@ -327,8 +359,8 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun contains(scope: Scope, other: Obj): Boolean { override suspend fun contains(scope: Scope, other: Obj): Boolean {
val ints = primitiveIntList val ints = primitiveIntList
if (ints != null && other is ObjInt) { if (ints != null && other is ObjInt) {
for (value in ints) { for (i in 0..<primitiveIntSize) {
if (value == other.value) return true if (ints[i] == other.value) return true
} }
return false return false
} }
@ -351,8 +383,8 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) { override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
val ints = primitiveIntList val ints = primitiveIntList
if (ints != null) { if (ints != null) {
for (value in ints) { for (i in 0..<primitiveIntSize) {
if (!callback(ObjInt.of(value))) break if (!callback(ObjInt.of(ints[i]))) break
} }
return return
} }
@ -366,7 +398,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun toKotlin(scope: Scope): Any { override suspend fun toKotlin(scope: Scope): Any {
val ints = primitiveIntList val ints = primitiveIntList
if (ints != null) return ints.map { it } if (ints != null) return (0..<primitiveIntSize).map { ints[it] }
return list.map { it.toKotlin(scope) } return list.map { it.toKotlin(scope) }
} }
@ -398,19 +430,24 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return primitiveIntList?.contentHashCode() ?: list.hashCode() val ints = primitiveIntList
return if (ints != null) {
var result = 1
for (i in 0..<primitiveIntSize) result = 31 * result + ints[i].hashCode()
result
} else list.hashCode()
} }
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (other == null || this::class != other::class) return false if (other == null || this::class != other::class) return false
other as ObjList other as ObjList
val ints = primitiveIntList val ints = primitiveIntList
val otherInts = other.primitiveIntList val otherInts = other.primitiveIntList
return if (ints != null && otherInts != null) { return if (ints != null && otherInts != null) {
ints.contentEquals(otherInts) if (primitiveIntSize != other.primitiveIntSize) return false
for (i in 0..<primitiveIntSize) if (ints[i] != otherInts[i]) return false
true
} else { } else {
list == other.list list == other.list
} }
@ -419,7 +456,9 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) { override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
val ints = primitiveIntList val ints = primitiveIntList
if (ints != null) { if (ints != null) {
encoder.encodeAnyList(scope, ints.mapTo(ArrayList(ints.size)) { ObjInt.of(it) }) val boxed = ArrayList<Obj>(primitiveIntSize)
for (i in 0..<primitiveIntSize) boxed.add(ObjInt.of(ints[i]))
encoder.encodeAnyList(scope, boxed)
return return
} }
encoder.encodeAnyList(scope, list) encoder.encodeAnyList(scope, list)
@ -430,7 +469,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun toJson(scope: Scope): JsonElement { override suspend fun toJson(scope: Scope): JsonElement {
val ints = primitiveIntList val ints = primitiveIntList
if (ints != null) { if (ints != null) {
return JsonArray(ints.map { ObjInt.of(it).toJson(scope) }) return JsonArray((0..<primitiveIntSize).map { ObjInt.of(ints[it]).toJson(scope) })
} }
return JsonArray(list.map { it.toJson(scope) }) return JsonArray(list.map { it.toJson(scope) })
} }
@ -441,9 +480,9 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
var first = true var first = true
val ints = primitiveIntList val ints = primitiveIntList
if (ints != null) { if (ints != null) {
for (v in ints) { for (i in 0..<primitiveIntSize) {
if (first) first = false else append(",") if (first) first = false else append(",")
append(v) append(ints[i])
} }
} else { } else {
for (v in list) { for (v in list) {
@ -539,9 +578,15 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) {
val self = thisAs<ObjList>() val self = thisAs<ObjList>()
val list = self.list as ArrayList
val count = requireOnlyArg<ObjInt>().value.toInt() val count = requireOnlyArg<ObjInt>().value.toInt()
list.ensureCapacity(count) if (count > 0) {
val ints = self.primitiveIntList
when {
ints != null -> if (ints.size < count) self.primitiveIntList = ints.copyOf(count)
self.boxedList == null -> { self.primitiveIntList = LongArray(count); self.primitiveIntSize = 0 }
else -> (self.boxedList as? ArrayList)?.ensureCapacity(count)
}
}
self self
} }

View File

@ -17,8 +17,10 @@
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.* import net.sergeych.lyng.*
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.toInt import net.sergeych.lyng.obj.toInt
import kotlin.test.Test import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertFailsWith import kotlin.test.assertFailsWith
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -161,7 +163,7 @@ class BytecodeRecentOpsTest {
} }
@Test @Test
fun listFillIntUsesPrimitiveFillBytecode() = runTest { fun listFillConstantExpressionUsesInlineBytecode() = runTest {
val scope = Script.newScope() val scope = Script.newScope()
scope.eval( scope.eval(
""" """
@ -172,26 +174,333 @@ class BytecodeRecentOpsTest {
""".trimIndent() """.trimIndent()
) )
val disasm = scope.disassembleSymbol("calc") val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("LIST_FILL_INT"), disasm) assertTrue(disasm.contains("LIST_NEW_INT"), disasm)
assertFalse(disasm.contains("LIST_FILL_INT"), disasm)
assertEquals(4, scope.eval("calc()").toInt()) assertEquals(4, scope.eval("calc()").toInt())
} }
@Test @Test
fun listFillIntWithIndexLambdaKeepsSemantics() = runTest { fun listFillCapturedExpressionUsesInlineBytecode() = runTest {
val scope = Script.newScope() val scope = Script.newScope()
scope.eval( scope.eval(
""" """
fun calc() { fun calc() {
val xs = List.fill(5) { it * 3 } val k = 3
val xs = List.fill(5) { it * k }
xs[0] + xs[4] xs[0] + xs[4]
} }
""".trimIndent() """.trimIndent()
) )
val disasm = scope.disassembleSymbol("calc") val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("LIST_FILL_INT"), disasm) assertTrue(disasm.contains("LIST_NEW_INT"), disasm)
assertFalse(disasm.contains("LIST_FILL_INT"), disasm)
assertEquals(12, scope.eval("calc()").toInt()) assertEquals(12, scope.eval("calc()").toInt())
} }
@Test
fun listFillIdentityUsesIotaBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val xs = List.fill(5) { it }
xs[0] + xs[4]
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("LIST_IOTA_INT"), disasm)
assertEquals(4, scope.eval("calc()").toInt())
}
@Test
fun directLambdaLiteralCallUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
{ x -> x + 1 }(10)
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("MAKE_LAMBDA_FN"), disasm)
assertFalse(disasm.contains("CALL_SLOT"), disasm)
assertEquals(11, scope.eval("calc()").toInt())
}
@Test
fun directLambdaLiteralCallWithCaptureUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val k = 3
{ x -> x * k }(4)
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("MAKE_LAMBDA_FN"), disasm)
assertFalse(disasm.contains("CALL_SLOT"), disasm)
assertEquals(12, scope.eval("calc()").toInt())
}
@Test
fun localImmutableLambdaCallUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val f = { x -> x + 1 }
f(10)
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_SLOT"), disasm)
assertEquals(11, scope.eval("calc()").toInt())
}
@Test
fun localImmutableCapturedLambdaCallUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val k = 3
val f = { x -> x * k }
f(4)
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_SLOT"), disasm)
assertEquals(12, scope.eval("calc()").toInt())
}
@Test
fun aliasedImmutableLambdaCallUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val f = { x -> x + 1 }
val g = f
g(10)
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_SLOT"), disasm)
assertEquals(11, scope.eval("calc()").toInt())
}
@Test
fun topLevelImmutableLambdaCallUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
val f = { x -> x + 1 }
fun calc() {
f(10)
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_SLOT"), disasm)
assertEquals(11, scope.eval("calc()").toInt())
}
@Test
fun topLevelAliasedImmutableLambdaCallUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
val f = { x -> x + 1 }
val g = f
fun calc() {
g(10)
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_SLOT"), disasm)
assertEquals(11, scope.eval("calc()").toInt())
}
@Test
fun topLevelCapturedLambdaCallUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
val k = 3
val f = { x -> x * k }
fun calc() {
f(4)
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_SLOT"), disasm)
assertEquals(12, scope.eval("calc()").toInt())
}
@Test
fun letLiteralUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
10.let { it + 1 }
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_DYNAMIC_MEMBER"), disasm)
assertEquals(11, scope.eval("calc()").toInt())
}
@Test
fun letAliasedLambdaUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val k = 3
val f = { x -> x * k }
4.let(f)
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_DYNAMIC_MEMBER"), disasm)
assertEquals(12, scope.eval("calc()").toInt())
}
@Test
fun alsoLiteralUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
var acc = 0
val result = 10.also { x ->
acc = x + 1
}
acc + result
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_DYNAMIC_MEMBER"), disasm)
assertEquals(21, scope.eval("calc()").toInt())
}
@Test
fun optionalLetLiteralUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc(flag: Bool) {
val x: Int? = if(flag) 10 else null
x?.let { it + 1 }
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_DYNAMIC_MEMBER"), disasm)
assertEquals(11, scope.eval("calc(true)").toInt())
assertEquals(ObjNull, scope.eval("calc(false)"))
}
@Test
fun applyReceiverLambdaUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
class Box(value: Int)
fun calc() {
val box = Box(10).apply { value += 1 }
box.value
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_DYNAMIC_MEMBER"), disasm)
assertEquals(11, scope.eval("calc()").toInt())
}
@Test
fun runReceiverLambdaUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
class Box(value: Int)
fun calc() {
Box(10).run { value + 1 }
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertFalse(disasm.contains("CALL_DYNAMIC_MEMBER"), disasm)
assertEquals(11, scope.eval("calc()").toInt())
}
@Test
fun forEachUsesInlineLoopBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
[1, 2, 3, 4].forEach { it + 1 }
5
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("ITER_PUSH"), disasm)
assertFalse(disasm.contains("CALL_DYNAMIC_MEMBER"), disasm)
assertEquals(5, scope.eval("calc()").toInt())
}
@Test
fun mapUsesInlineLoopBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val f = { x -> x * 2 }
val xs = [1, 2, 3].map(f)
xs[0] + xs[2]
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("ITER_PUSH"), disasm)
assertFalse(disasm.contains("CALL_DYNAMIC_MEMBER"), disasm)
assertEquals(8, scope.eval("calc()").toInt())
}
@Test
fun filterUsesInlineLoopBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val xs = [1, 2, 3, 4].filter { it % 2 == 0 }
xs[0] + xs[1]
}
""".trimIndent()
)
val disasm = scope.disassembleSymbol("calc")
assertTrue(disasm.contains("ITER_PUSH"), disasm)
assertFalse(disasm.contains("CALL_DYNAMIC_MEMBER"), disasm)
assertEquals(6, scope.eval("calc()").toInt())
}
@Test @Test
fun optionalIndexPreIncSkipsOnNullReceiver() = runTest { fun optionalIndexPreIncSkipsOnNullReceiver() = runTest {
eval( eval(

View File

@ -0,0 +1,59 @@
/*
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.obj.toInt
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.time.TimeSource
class OptTest {
@Test
fun testAddToArray() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun buildArray(n: Int) {
val a: List<Int> = List.fill(n) { it * 10 + 1 }
a.size
}
""".trimIndent()
)
repeat(3) { pass ->
val size = scope.eval("buildArray(200000)").toInt()
assertEquals(200000, size, "warmup pass ${pass + 1} failed")
}
val passes = 3
var bestMs = Long.MAX_VALUE
var totalMs = 0L
repeat(passes) { pass ->
val start = TimeSource.Monotonic.markNow()
val size = scope.eval("buildArray(10000000)").toInt()
val elapsedMs = start.elapsedNow().inWholeMilliseconds
assertEquals(10000000, size, "measured pass ${pass + 1} failed")
bestMs = minOf(bestMs, elapsedMs)
totalMs += elapsedMs
println("add-to-array pass ${pass + 1}/$passes: ${elapsedMs}ms size=$size")
}
println("add-to-array best=${bestMs}ms avg=${totalMs / passes}ms after warmup")
}
}

View File

@ -434,7 +434,7 @@ fun List<T>.sort(): void {
/* /*
Build a new list of `size` elements by calling `block(index)` for each index. Build a new list of `size` elements by calling `block(index)` for each index.
`capacity` less size is ignored (size will be used as capacity). `capacity` less than size is ignored (size will be used as capacity).
*/ */
static fun List<T>.fill(size: Int, capacity = -1, block: (Int)->T): List<T> { static fun List<T>.fill(size: Int, capacity = -1, block: (Int)->T): List<T> {
require(size >= 0, "size must not be negative") require(size >= 0, "size must not be negative")