From ffa64d691b0170e9f8f21fd9391fb819246f8504 Mon Sep 17 00:00:00 2001 From: sergeych Date: Tue, 21 Apr 2026 13:30:09 +0300 Subject: [PATCH] Use direct calls for constant constructors --- .../lyng/bytecode/BytecodeCompiler.kt | 22 +++----- .../kotlin/BytecodeRecentOpsTest.kt | 51 +++++++++++++++++++ 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index 7b4b57c..1ea769a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -874,6 +874,11 @@ class BytecodeCompiler( } } + private fun emitCallDirect(callee: Obj, argBase: Int, encodedCount: Int, dst: Int) { + val calleeId = builder.addConst(BytecodeConst.ObjRef(callee)) + builder.emit(Opcode.CALL_DIRECT, calleeId, argBase, encodedCount, dst) + } + private fun compileValueFnRef(ref: ValueFnRef): CompiledValue? { if (ref is LambdaFnRef && ref.bytecodeFn != null) { val captures = (lambdaCaptureEntriesByRef[ref] ?: ref.captureEntries).orEmpty() @@ -998,11 +1003,8 @@ class BytecodeCompiler( } private fun compileMapLiteral(ref: MapLiteralRef): CompiledValue? { - val mapClassId = builder.addConst(BytecodeConst.ObjRef(ObjMap.type)) - val mapClassSlot = allocSlot() - builder.emit(Opcode.CONST_OBJ, mapClassId, mapClassSlot) val dst = allocSlot() - builder.emit(Opcode.CALL_SLOT, mapClassSlot, 0, 0, dst) + emitCallDirect(ObjMap.type, 0, 0, dst) updateSlotType(dst, SlotType.OBJ) slotObjClass[dst] = ObjMap.type for (entry in ref.entries()) { @@ -1285,11 +1287,8 @@ class BytecodeCompiler( emitMove(leftObj, argLeft) val argRight = allocSlot() emitMove(rightObj, argRight) - val mapEntryClassId = builder.addConst(BytecodeConst.ObjRef(ObjMapEntry.type)) - val mapEntryClassSlot = allocSlot() - builder.emit(Opcode.CONST_OBJ, mapEntryClassId, mapEntryClassSlot) val dst = allocSlot() - builder.emit(Opcode.CALL_SLOT, mapEntryClassSlot, argBase, 2, dst) + emitCallDirect(ObjMapEntry.type, argBase, 2, dst) updateSlotType(dst, SlotType.OBJ) slotObjClass[dst] = ObjMapEntry.type return CompiledValue(dst, SlotType.OBJ) @@ -5090,7 +5089,6 @@ class BytecodeCompiler( } 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 @@ -5279,12 +5277,8 @@ class BytecodeCompiler( } 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) + emitCallDirect(ObjList.type, 0, 0, dst) updateSlotType(dst, SlotType.OBJ) slotObjClass[dst] = ObjList.type return CompiledValue(dst, SlotType.OBJ) diff --git a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt index bde156f..f6a8f0b 100644 --- a/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt +++ b/lynglib/src/commonTest/kotlin/BytecodeRecentOpsTest.kt @@ -361,6 +361,23 @@ class BytecodeRecentOpsTest { assertEquals(11, scope.eval("calc()").toInt()) } + @Test + fun optionalExactLambdaCallUsesInlineBytecode() = runTest { + val scope = Script.newScope() + scope.eval( + """ + type IntFn = (Int)->Int + fun calc() { + val f: IntFn? = { 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 letLiteralUsesInlineBytecode() = runTest { val scope = Script.newScope() @@ -516,6 +533,40 @@ class BytecodeRecentOpsTest { assertEquals(6, scope.eval("calc()").toInt()) } + @Test + fun mapLiteralUsesDirectConstructorCall() = runTest { + val scope = Script.newScope() + scope.eval( + """ + fun calc() { + val m = { a: 1, b: 2 } + m.size + } + """.trimIndent() + ) + val disasm = scope.disassembleSymbol("calc") + assertTrue(disasm.contains("CALL_DIRECT"), disasm) + assertFalse(disasm.contains("CALL_SLOT"), disasm) + assertEquals(2, scope.eval("calc()").toInt()) + } + + @Test + fun mapEntryLiteralUsesDirectConstructorCall() = runTest { + val scope = Script.newScope() + scope.eval( + """ + fun calc() { + val e = "a" => 2 + e.value + } + """.trimIndent() + ) + val disasm = scope.disassembleSymbol("calc") + assertTrue(disasm.contains("CALL_DIRECT"), disasm) + assertFalse(disasm.contains("CALL_SLOT"), disasm) + assertEquals(2, scope.eval("calc()").toInt()) + } + @Test fun optionalIndexPreIncSkipsOnNullReceiver() = runTest { eval(