Inline simple lambdas in bytecode fast paths
This commit is contained in:
parent
10fa4de4fa
commit
f72cdfdf83
@ -302,8 +302,6 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.firebase.crashlytics.buildtools)
|
|
||||||
implementation(libs.compiler)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,6 +5073,10 @@ 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
|
||||||
|
lambdaRef.inlineBodyRef?.let { inlineRef ->
|
||||||
|
return compileInlineListFillInt(size, lambdaRef, inlineRef)
|
||||||
|
}
|
||||||
|
run {
|
||||||
val callable = ensureObjSlot(compileArgValue(ref.args[1].value) ?: return null)
|
val callable = ensureObjSlot(compileArgValue(ref.args[1].value) ?: return null)
|
||||||
val dst = allocSlot()
|
val dst = allocSlot()
|
||||||
builder.emit(Opcode.LIST_FILL_INT, size.slot, callable.slot, dst)
|
builder.emit(Opcode.LIST_FILL_INT, size.slot, callable.slot, dst)
|
||||||
@ -4908,6 +5085,486 @@ class BytecodeCompiler(
|
|||||||
listElementClassBySlot[dst] = ObjInt.type
|
listElementClassBySlot[dst] = ObjInt.type
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
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.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? {
|
private fun compileThisMethodSlotCall(ref: ThisMethodSlotCallRef): CompiledValue? {
|
||||||
val callPos = callSitePos()
|
val callPos = callSitePos()
|
||||||
@ -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) {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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])
|
||||||
|
|||||||
@ -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 ->
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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),
|
||||||
|
|||||||
@ -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>,
|
||||||
|
|||||||
@ -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!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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 (initialList.isNotEmpty()) {
|
||||||
if (!adoptPrimitiveIntList(initialList)) {
|
if (!adoptPrimitiveIntList(initialList)) {
|
||||||
boxedList = 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,10 +88,25 @@ 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) {
|
||||||
|
// 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
|
return
|
||||||
}
|
}
|
||||||
|
if (boxedList == null) {
|
||||||
|
// Lazy empty state: first int element starts primitive mode
|
||||||
|
primitiveIntList = LongArray(16).also { it[0] = value.value }
|
||||||
|
primitiveIntSize = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
ensureBoxedList().add(value)
|
ensureBoxedList().add(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,10 +114,12 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
val ints = primitiveIntList
|
val ints = primitiveIntList
|
||||||
val otherInts = other.primitiveIntList
|
val otherInts = other.primitiveIntList
|
||||||
if (ints != null && otherInts != null) {
|
if (ints != null && otherInts != null) {
|
||||||
primitiveIntList = LongArray(ints.size + otherInts.size).also {
|
val otherSize = other.primitiveIntSize
|
||||||
ints.copyInto(it, 0, 0, ints.size)
|
val newSize = primitiveIntSize + otherSize
|
||||||
otherInts.copyInto(it, ints.size, 0, otherInts.size)
|
val dest = if (newSize <= ints.size) ints else ints.copyOf(newSize)
|
||||||
}
|
otherInts.copyInto(dest, primitiveIntSize, 0, otherSize)
|
||||||
|
primitiveIntList = dest
|
||||||
|
primitiveIntSize = newSize
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ensureBoxedList().addAll(other.list)
|
ensureBoxedList().addAll(other.list)
|
||||||
@ -108,6 +133,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
ints[i] = value.value
|
ints[i] = value.value
|
||||||
}
|
}
|
||||||
primitiveIntList = ints
|
primitiveIntList = ints
|
||||||
|
primitiveIntSize = ints.size
|
||||||
boxedList = null
|
boxedList = null
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@ -120,12 +146,13 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
boxedList = empty
|
boxedList = empty
|
||||||
return empty
|
return empty
|
||||||
}
|
}
|
||||||
val materialized = ArrayList<Obj>(ints.size)
|
val materialized = ArrayList<Obj>(primitiveIntSize)
|
||||||
for (value in ints) {
|
for (i in 0..<primitiveIntSize) {
|
||||||
materialized.add(ObjInt.of(value))
|
materialized.add(ObjInt.of(ints[i]))
|
||||||
}
|
}
|
||||||
boxedList = materialized
|
boxedList = materialized
|
||||||
primitiveIntList = null
|
primitiveIntList = null
|
||||||
|
primitiveIntSize = 0
|
||||||
return materialized
|
return materialized
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,6 +167,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
|
|
||||||
internal constructor(intValues: LongArray) : this(mutableListOf()) {
|
internal constructor(intValues: LongArray) : this(mutableListOf()) {
|
||||||
primitiveIntList = intValues
|
primitiveIntList = intValues
|
||||||
|
primitiveIntSize = intValues.size
|
||||||
boxedList = null
|
boxedList = null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,9 +281,11 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
val ints = primitiveIntList
|
val ints = primitiveIntList
|
||||||
val otherInts = other.primitiveIntList
|
val otherInts = other.primitiveIntList
|
||||||
if (ints != null && otherInts != null) {
|
if (ints != null && otherInts != null) {
|
||||||
ObjList(LongArray(ints.size + otherInts.size).also {
|
val mySize = primitiveIntSize
|
||||||
ints.copyInto(it, 0, 0, ints.size)
|
val otherSize = other.primitiveIntSize
|
||||||
otherInts.copyInto(it, ints.size, 0, otherInts.size)
|
ObjList(LongArray(mySize + otherSize).also {
|
||||||
|
ints.copyInto(it, 0, 0, mySize)
|
||||||
|
otherInts.copyInto(it, mySize, 0, otherSize)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
ObjList((list + other.list).toMutableList())
|
ObjList((list + other.list).toMutableList())
|
||||||
@ -276,7 +306,9 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
|
|
||||||
|
|
||||||
open override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
|
open override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
|
||||||
if (other is ObjList) {
|
if (other is ObjInt || other is ObjString || other is ObjBool || other is ObjReal || other is ObjNull) {
|
||||||
|
appendFast(other)
|
||||||
|
} else if (other is ObjList) {
|
||||||
appendAllFast(other)
|
appendAllFast(other)
|
||||||
} else if (!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable)) {
|
} else if (!shouldTreatAsSingleElement(scope, other) && other.isInstanceOf(ObjIterable)) {
|
||||||
val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list
|
val otherList = (other.invokeInstanceMethod(scope, "toList") as ObjList).list
|
||||||
@ -327,8 +359,8 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
val ints = primitiveIntList
|
val ints = primitiveIntList
|
||||||
if (ints != null && other is ObjInt) {
|
if (ints != null && other is ObjInt) {
|
||||||
for (value in ints) {
|
for (i in 0..<primitiveIntSize) {
|
||||||
if (value == other.value) return true
|
if (ints[i] == other.value) return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -351,8 +383,8 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||||
val ints = primitiveIntList
|
val ints = primitiveIntList
|
||||||
if (ints != null) {
|
if (ints != null) {
|
||||||
for (value in ints) {
|
for (i in 0..<primitiveIntSize) {
|
||||||
if (!callback(ObjInt.of(value))) break
|
if (!callback(ObjInt.of(ints[i]))) break
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -366,7 +398,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
|
|
||||||
override suspend fun toKotlin(scope: Scope): Any {
|
override suspend fun toKotlin(scope: Scope): Any {
|
||||||
val ints = primitiveIntList
|
val ints = primitiveIntList
|
||||||
if (ints != null) return ints.map { it }
|
if (ints != null) return (0..<primitiveIntSize).map { ints[it] }
|
||||||
return list.map { it.toKotlin(scope) }
|
return list.map { it.toKotlin(scope) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -398,19 +430,24 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return primitiveIntList?.contentHashCode() ?: list.hashCode()
|
val ints = primitiveIntList
|
||||||
|
return if (ints != null) {
|
||||||
|
var result = 1
|
||||||
|
for (i in 0..<primitiveIntSize) result = 31 * result + ints[i].hashCode()
|
||||||
|
result
|
||||||
|
} else list.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
if (this === other) return true
|
if (this === other) return true
|
||||||
if (other == null || this::class != other::class) return false
|
if (other == null || this::class != other::class) return false
|
||||||
|
|
||||||
other as ObjList
|
other as ObjList
|
||||||
|
|
||||||
val ints = primitiveIntList
|
val ints = primitiveIntList
|
||||||
val otherInts = other.primitiveIntList
|
val otherInts = other.primitiveIntList
|
||||||
return if (ints != null && otherInts != null) {
|
return if (ints != null && otherInts != null) {
|
||||||
ints.contentEquals(otherInts)
|
if (primitiveIntSize != other.primitiveIntSize) return false
|
||||||
|
for (i in 0..<primitiveIntSize) if (ints[i] != otherInts[i]) return false
|
||||||
|
true
|
||||||
} else {
|
} else {
|
||||||
list == other.list
|
list == other.list
|
||||||
}
|
}
|
||||||
@ -419,7 +456,9 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
||||||
val ints = primitiveIntList
|
val ints = primitiveIntList
|
||||||
if (ints != null) {
|
if (ints != null) {
|
||||||
encoder.encodeAnyList(scope, ints.mapTo(ArrayList(ints.size)) { ObjInt.of(it) })
|
val boxed = ArrayList<Obj>(primitiveIntSize)
|
||||||
|
for (i in 0..<primitiveIntSize) boxed.add(ObjInt.of(ints[i]))
|
||||||
|
encoder.encodeAnyList(scope, boxed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
encoder.encodeAnyList(scope, list)
|
encoder.encodeAnyList(scope, list)
|
||||||
@ -430,7 +469,7 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
override suspend fun toJson(scope: Scope): JsonElement {
|
override suspend fun toJson(scope: Scope): JsonElement {
|
||||||
val ints = primitiveIntList
|
val ints = primitiveIntList
|
||||||
if (ints != null) {
|
if (ints != null) {
|
||||||
return JsonArray(ints.map { ObjInt.of(it).toJson(scope) })
|
return JsonArray((0..<primitiveIntSize).map { ObjInt.of(ints[it]).toJson(scope) })
|
||||||
}
|
}
|
||||||
return JsonArray(list.map { it.toJson(scope) })
|
return JsonArray(list.map { it.toJson(scope) })
|
||||||
}
|
}
|
||||||
@ -441,9 +480,9 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
var first = true
|
var first = true
|
||||||
val ints = primitiveIntList
|
val ints = primitiveIntList
|
||||||
if (ints != null) {
|
if (ints != null) {
|
||||||
for (v in ints) {
|
for (i in 0..<primitiveIntSize) {
|
||||||
if (first) first = false else append(",")
|
if (first) first = false else append(",")
|
||||||
append(v)
|
append(ints[i])
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (v in list) {
|
for (v in list) {
|
||||||
@ -539,9 +578,15 @@ open class ObjList(initialList: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
moduleName = "lyng.stdlib"
|
moduleName = "lyng.stdlib"
|
||||||
) {
|
) {
|
||||||
val self = thisAs<ObjList>()
|
val self = thisAs<ObjList>()
|
||||||
val list = self.list as ArrayList
|
|
||||||
val count = requireOnlyArg<ObjInt>().value.toInt()
|
val count = requireOnlyArg<ObjInt>().value.toInt()
|
||||||
list.ensureCapacity(count)
|
if (count > 0) {
|
||||||
|
val ints = self.primitiveIntList
|
||||||
|
when {
|
||||||
|
ints != null -> if (ints.size < count) self.primitiveIntList = ints.copyOf(count)
|
||||||
|
self.boxedList == null -> { self.primitiveIntList = LongArray(count); self.primitiveIntSize = 0 }
|
||||||
|
else -> (self.boxedList as? ArrayList)?.ensureCapacity(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
59
lynglib/src/commonTest/kotlin/net/sergeych/lyng/OptTest.kt
Normal file
59
lynglib/src/commonTest/kotlin/net/sergeych/lyng/OptTest.kt
Normal 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user