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 {
implementation(libs.firebase.crashlytics.buildtools)
implementation(libs.compiler)
}
publishing {

View File

@ -188,6 +188,7 @@ class Compiler(
private val callableReturnTypeByName: MutableMap<String, ObjClass> = mutableMapOf()
private val callableReturnTypeDeclByName: MutableMap<String, TypeDecl> = 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>> =
mutableMapOf()
private val classFieldTypesByName: MutableMap<String, MutableMap<String, ObjClass>> = mutableMapOf()
@ -1894,6 +1895,7 @@ class Compiler(
forcedLocalSlotInfo = forcedLocalInfo,
forcedLocalScopeId = forcedLocalScopeId,
slotTypeByScopeId = slotTypeByScopeId,
exactLambdaRefByScopeId = exactLambdaRefByScopeId,
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
knownNameObjClass = knownClassMapForBytecode(),
knownClassNames = knownClassNamesForBytecode(),
@ -2249,6 +2251,7 @@ class Compiler(
scopeSlotNameSet = scopeSlotNameSet,
moduleScopeId = moduleScopeId,
slotTypeByScopeId = slotTypeByScopeId,
exactLambdaRefByScopeId = exactLambdaRefByScopeId,
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
knownNameObjClass = knownClassMapForBytecode(),
knownClassNames = knownClassNamesForBytecode(),
@ -2282,6 +2285,7 @@ class Compiler(
scopeSlotNameSet = scopeSlotNameSet,
moduleScopeId = moduleScopeId,
slotTypeByScopeId = slotTypeByScopeId,
exactLambdaRefByScopeId = exactLambdaRefByScopeId,
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
knownNameObjClass = knownClassMapForBytecode(),
knownClassNames = knownClassNamesForBytecode(),
@ -2340,6 +2344,7 @@ class Compiler(
globalSlotInfo = globalSlotInfo,
globalSlotScopeId = globalSlotScopeId,
slotTypeByScopeId = slotTypeByScopeId,
exactLambdaRefByScopeId = exactLambdaRefByScopeId,
slotTypeDeclByScopeId = slotTypeDeclByScopeId,
knownNameObjClass = knownNames,
knownClassNames = knownClassNamesForBytecode(),
@ -3539,6 +3544,7 @@ class Compiler(
body
}
val bytecodeFn = (fnStatements as? BytecodeStatement)?.bytecodeFunction()
val inlineBodyRef = argsDeclaration?.let { null } ?: extractInlineLambdaBodyRef(body)
val ref = LambdaFnRef(
valueFn = { closureScope ->
val captureRecords = closureScope.captureRecords
@ -3621,6 +3627,7 @@ class Compiler(
argsDeclaration = argsDeclaration,
captureEntries = captureEntries,
inferredReturnClass = returnClass,
inlineBodyRef = inlineBodyRef,
preferredThisType = expectedReceiverType,
wrapAsExtensionCallable = wrapAsExtensionCallable,
returnLabels = returnLabels,
@ -3635,6 +3642,18 @@ class Compiler(
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> {
// it should be called after Token.Type.LBRACKET is consumed
val entries = mutableListOf<ListEntry>()
@ -4968,6 +4987,26 @@ class Compiler(
?: 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? {
return when (ref) {
is LocalSlotRef -> {
@ -10415,6 +10454,15 @@ class Compiler(
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 (slotIndex != null && scopeId != null) {
slotTypeByScopeId.getOrPut(scopeId) { mutableMapOf() }[slotIndex] = initObjClass

View File

@ -33,6 +33,7 @@ class BytecodeCompiler(
private val globalSlotInfo: Map<String, ForcedLocalSlotInfo> = emptyMap(),
private val globalSlotScopeId: Int? = null,
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 knownNameObjClass: Map<String, ObjClass> = emptyMap(),
private val knownClassNames: Set<String> = emptySet(),
@ -89,6 +90,9 @@ class BytecodeCompiler(
private val slotInitClassByKey = mutableMapOf<ScopeSlotKey, ObjClass>()
private val intLoopVarNames = LinkedHashSet<String>()
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 loopVarSlots = HashSet<Int>()
private val loopStack = ArrayDeque<LoopContext>()
@ -104,6 +108,8 @@ class BytecodeCompiler(
val hasIterator: Boolean,
)
private data class InlineThisBinding(val slot: Int, val typeName: String?)
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? {
prepareCompilation(stmt)
setPos(stmt.pos)
@ -559,7 +565,10 @@ class BytecodeCompiler(
val mapped = resolveSlot(ref) ?: return null
var resolved = slotTypes[mapped] ?: 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])
if (inferred != null) {
updateSlotType(mapped, inferred)
@ -696,7 +705,7 @@ class BytecodeCompiler(
compileThisVariantRef(typeName) ?: return null
} ?: compileThisRef()
val ownerClass = ref.preferredThisTypeName()?.let { resolveTypeNameClass(it) }
?: implicitThisTypeName?.let { resolveTypeNameClass(it) }
?: currentImplicitThisTypeName()?.let { resolveTypeNameClass(it) }
val fieldId = ref.fieldId ?: -1
val methodId = ref.methodId ?: -1
if (fieldId < 0 && methodId < 0) {
@ -804,6 +813,9 @@ class BytecodeCompiler(
}
private fun compileThisRef(): CompiledValue {
inlineThisBindings.lastOrNull()?.let { binding ->
return CompiledValue(binding.slot, SlotType.OBJ)
}
val slot = allocSlot()
builder.emit(Opcode.LOAD_THIS, slot)
updateSlotType(slot, SlotType.OBJ)
@ -811,6 +823,9 @@ class BytecodeCompiler(
}
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))
if (typeId > 0xFFFF) return null
val slot = allocSlot()
@ -819,6 +834,10 @@ class BytecodeCompiler(
return CompiledValue(slot, SlotType.OBJ)
}
private fun currentImplicitThisTypeName(): String? {
return inlineThisBindings.lastOrNull()?.typeName ?: implicitThisTypeName
}
private fun compileConst(obj: Obj): CompiledValue? {
val slot = allocSlot()
when (obj) {
@ -2509,6 +2528,7 @@ class BytecodeCompiler(
}
updateSlotType(slot, value.type)
propagateObjClass(value.type, value.slot, slot)
trackExactLambdaAtSlot(slot, null)
updateNameObjClassFromSlot(localTarget.name, slot)
return value
}
@ -2552,6 +2572,7 @@ class BytecodeCompiler(
}
updateSlotType(slot, value.type)
propagateObjClass(value.type, value.slot, slot)
trackExactLambdaAtSlot(slot, null)
updateNameObjClassFromSlot(nameTarget, slot)
return value
}
@ -3587,7 +3608,7 @@ class BytecodeCompiler(
private fun compileThisFieldSlotRef(ref: ThisFieldSlotRef): CompiledValue? {
val receiver = compileThisRef()
val ownerClass = implicitThisTypeName?.let { resolveTypeNameClass(it) }
val ownerClass = currentImplicitThisTypeName()?.let { resolveTypeNameClass(it) }
val fieldId = ref.fieldId() ?: -1
val methodId = ref.methodId() ?: -1
if (fieldId < 0 && methodId < 0) {
@ -4566,6 +4587,10 @@ class BytecodeCompiler(
private fun compileCall(ref: CallRef): CompiledValue? {
val callPos = callSitePos()
val lambdaTarget = resolveInlineCallableLambda(ref.target)
if (lambdaTarget != null) {
compileInlineDirectLambdaCall(ref, lambdaTarget)?.let { return it }
}
val fieldTarget = ref.target as? FieldRef
if (fieldTarget != null && isKnownClassReceiver(fieldTarget.target)) {
val receiverClass = resolveReceiverClass(fieldTarget.target)
@ -4715,6 +4740,9 @@ class BytecodeCompiler(
private fun compileMethodCall(ref: MethodCallRef): CompiledValue? {
compileListFillIntCall(ref)?.let { return it }
compileInlineUnaryLambdaMethodCall(ref)?.let { return it }
compileInlineReceiverLambdaMethodCall(ref)?.let { return it }
compileInlineIterableLambdaMethodCall(ref)?.let { return it }
val callPos = callSitePos()
val receiverClass = resolveReceiverClass(ref.receiver) ?: ObjDynamic.type
val receiver = compileRefWithFallback(ref.receiver, null, refPosOrCurrent(ref.receiver)) ?: return null
@ -4893,6 +4921,151 @@ class BytecodeCompiler(
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? {
if (ref.name != "fill" || !isListTypeRef(ref.receiver)) 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
val size = compileArgValue(ref.args[0].value) ?: 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()
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)
slotObjClass[dst] = ObjList.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)
}
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? {
val callPos = callSitePos()
val receiver = compileThisRef()
@ -5991,6 +6648,7 @@ class BytecodeCompiler(
?: updateSlotObjClass(localSlot, stmt.initializer, stmt.initializerObjClass)
updateListElementClassFromDecl(localSlot, scopeId, stmt.slotIndex)
updateListElementClassFromInitializer(localSlot, stmt.initializer)
trackExactLambdaAtSlot(localSlot, if (!stmt.isMutable) extractExactLambdaRef(stmt.initializer) else null)
updateNameObjClassFromSlot(stmt.name, localSlot)
val shadowedScopeSlot = scopeSlotIndexByName.containsKey(stmt.name)
val isModuleScope = moduleScopeId != null && scopeId == moduleScopeId
@ -6024,6 +6682,7 @@ class BytecodeCompiler(
?: updateSlotObjClass(scopeSlot, stmt.initializer, stmt.initializerObjClass)
updateListElementClassFromDecl(scopeSlot, scopeId, stmt.slotIndex)
updateListElementClassFromInitializer(scopeSlot, stmt.initializer)
trackExactLambdaAtSlot(scopeSlot, if (!stmt.isMutable) extractExactLambdaRef(stmt.initializer) else null)
val declId = builder.addConst(
BytecodeConst.LocalDecl(
stmt.name,
@ -7859,7 +8518,9 @@ class BytecodeCompiler(
scopeSlotRefPosByKey[scopeKey] = ref.pos()
}
}
return resolved
if (resolved != null) return resolved
resolveCapturedOwnerSlot(ref)?.let { return it }
return null
}
if (ref.isDelegated) {
val localKey = ScopeSlotKey(refScopeId(ref), refSlot(ref))
@ -7893,6 +8554,14 @@ class BytecodeCompiler(
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) {
if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return
if (type == SlotType.UNKNOWN) {
@ -7980,7 +8649,7 @@ class BytecodeCompiler(
}
}
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
when (fieldClass.className) {
"Buffer", "MutableBuffer", "BitBuffer" -> ObjInt.type
@ -8070,6 +8739,8 @@ class BytecodeCompiler(
loopVarKeys.clear()
loopVarSlots.clear()
valueFnRefs.clear()
exactLambdaRefBySlot.clear()
activeInlineLambdas.clear()
addrSlotByScopeSlot.clear()
loopStack.clear()
if (slotTypeByScopeId.isNotEmpty()) {
@ -8102,6 +8773,7 @@ class BytecodeCompiler(
collectLoopVarNames(stmt)
}
collectScopeSlots(stmt)
collectExactLambdaModuleCaptures()
if (allowLocalSlots) {
collectLoopSlotPlans(stmt, 0)
}
@ -8293,6 +8965,7 @@ class BytecodeCompiler(
}
}
}
preloadExactLambdaRefs()
if (allowLocalSlots && captureSlotKeys.isNotEmpty() && slotInitClassByKey.isNotEmpty()) {
val scopeSlotsBase = scopeSlotMap.size
for (key in captureSlotKeys) {

View File

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

View File

@ -231,8 +231,12 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_NEW_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_FILL_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_IOTA_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.MAKE_RANGE ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL ->
@ -835,7 +839,9 @@ class CmdBuilder {
Opcode.SET_INDEX -> CmdSetIndex(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.LIST_NEW_INT -> CmdListNewInt(operands[0], operands[1])
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.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])

View File

@ -495,7 +495,9 @@ object CmdDisassembler {
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 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 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 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)
@ -619,8 +621,12 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_NEW_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_FILL_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_IOTA_INT ->
listOf(OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.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(
internal val targetSlot: Int,
internal val indexSlot: Int,

View File

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

View File

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

View File

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

View File

@ -30,19 +30,27 @@ import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
private var boxedList: MutableList<Obj>? = null
private var primitiveIntList: LongArray? = null
internal var boxedList: MutableList<Obj>? = null
internal var primitiveIntList: LongArray? = null
// Logical size of primitiveIntList; capacity = primitiveIntList!!.size
internal var primitiveIntSize: Int = 0
init {
if (!adoptPrimitiveIntList(initialList)) {
boxedList = initialList
if (initialList.isNotEmpty()) {
if (!adoptPrimitiveIntList(initialList)) {
boxedList = initialList
}
}
// Empty initialList: both null — lazy mode, avoids boxing on first append
}
val list: MutableList<Obj>
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 =
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) {
val ints = primitiveIntList
if (ints != null && value is ObjInt) {
primitiveIntList = ints.copyOf(ints.size + 1).also { it[ints.size] = value.value }
return
if (value is ObjInt) {
if (ints != null) {
// 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)
}
@ -91,10 +114,12 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
val ints = primitiveIntList
val otherInts = other.primitiveIntList
if (ints != null && otherInts != null) {
primitiveIntList = LongArray(ints.size + otherInts.size).also {
ints.copyInto(it, 0, 0, ints.size)
otherInts.copyInto(it, ints.size, 0, otherInts.size)
}
val otherSize = other.primitiveIntSize
val newSize = primitiveIntSize + otherSize
val dest = if (newSize <= ints.size) ints else ints.copyOf(newSize)
otherInts.copyInto(dest, primitiveIntSize, 0, otherSize)
primitiveIntList = dest
primitiveIntSize = newSize
return
}
ensureBoxedList().addAll(other.list)
@ -108,6 +133,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
ints[i] = value.value
}
primitiveIntList = ints
primitiveIntSize = ints.size
boxedList = null
return true
}
@ -120,12 +146,13 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
boxedList = empty
return empty
}
val materialized = ArrayList<Obj>(ints.size)
for (value in ints) {
materialized.add(ObjInt.of(value))
val materialized = ArrayList<Obj>(primitiveIntSize)
for (i in 0..<primitiveIntSize) {
materialized.add(ObjInt.of(ints[i]))
}
boxedList = materialized
primitiveIntList = null
primitiveIntSize = 0
return materialized
}
@ -140,6 +167,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
internal constructor(intValues: LongArray) : this(mutableListOf()) {
primitiveIntList = intValues
primitiveIntSize = intValues.size
boxedList = null
}
@ -253,9 +281,11 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
val ints = primitiveIntList
val otherInts = other.primitiveIntList
if (ints != null && otherInts != null) {
ObjList(LongArray(ints.size + otherInts.size).also {
ints.copyInto(it, 0, 0, ints.size)
otherInts.copyInto(it, ints.size, 0, otherInts.size)
val mySize = primitiveIntSize
val otherSize = other.primitiveIntSize
ObjList(LongArray(mySize + otherSize).also {
ints.copyInto(it, 0, 0, mySize)
otherInts.copyInto(it, mySize, 0, otherSize)
})
} else {
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 {
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)
} else if (!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable)) {
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 {
val ints = primitiveIntList
if (ints != null && other is ObjInt) {
for (value in ints) {
if (value == other.value) return true
for (i in 0..<primitiveIntSize) {
if (ints[i] == other.value) return true
}
return false
}
@ -351,8 +383,8 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
val ints = primitiveIntList
if (ints != null) {
for (value in ints) {
if (!callback(ObjInt.of(value))) break
for (i in 0..<primitiveIntSize) {
if (!callback(ObjInt.of(ints[i]))) break
}
return
}
@ -366,7 +398,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun toKotlin(scope: Scope): Any {
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) }
}
@ -398,19 +430,24 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
}
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 {
if (this === other) return true
if (other == null || this::class != other::class) return false
other as ObjList
val ints = primitiveIntList
val otherInts = other.primitiveIntList
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 {
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?) {
val ints = primitiveIntList
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
}
encoder.encodeAnyList(scope, list)
@ -430,7 +469,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun toJson(scope: Scope): JsonElement {
val ints = primitiveIntList
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) })
}
@ -441,9 +480,9 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
var first = true
val ints = primitiveIntList
if (ints != null) {
for (v in ints) {
for (i in 0..<primitiveIntSize) {
if (first) first = false else append(",")
append(v)
append(ints[i])
}
} else {
for (v in list) {
@ -531,7 +570,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
addFnDoc(
name= "ensureCapacity",
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
could be a costly operation,
""".trimIndent(),
@ -539,9 +578,15 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
moduleName = "lyng.stdlib"
) {
val self = thisAs<ObjList>()
val list = self.list as ArrayList
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
}

View File

@ -17,8 +17,10 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.*
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.toInt
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
@ -161,7 +163,7 @@ class BytecodeRecentOpsTest {
}
@Test
fun listFillIntUsesPrimitiveFillBytecode() = runTest {
fun listFillConstantExpressionUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
@ -172,26 +174,333 @@ class BytecodeRecentOpsTest {
""".trimIndent()
)
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())
}
@Test
fun listFillIntWithIndexLambdaKeepsSemantics() = runTest {
fun listFillCapturedExpressionUsesInlineBytecode() = runTest {
val scope = Script.newScope()
scope.eval(
"""
fun calc() {
val xs = List.fill(5) { it * 3 }
val k = 3
val xs = List.fill(5) { it * k }
xs[0] + xs[4]
}
""".trimIndent()
)
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())
}
@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
fun optionalIndexPreIncSkipsOnNullReceiver() = runTest {
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.
`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> {
require(size >= 0, "size must not be negative")