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) {
@ -531,7 +570,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
addFnDoc( addFnDoc(
name= "ensureCapacity", name= "ensureCapacity",
doc = """ doc = """
ensure the list capacity allows storing specified amount if items without reallocation. ensure the list capacity allows storing specified amount if items without reallocation.
If current capacity is greater or equal to `count`, does nothing. Note that possible reallocation If current capacity is greater or equal to `count`, does nothing. Note that possible reallocation
could be a costly operation, could be a costly operation,
""".trimIndent(), """.trimIndent(),
@ -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")