Use direct calls for constant constructors

This commit is contained in:
Sergey Chernov 2026-04-21 13:30:09 +03:00
parent 30c9a5a565
commit ffa64d691b
2 changed files with 59 additions and 14 deletions

View File

@ -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? { private fun compileValueFnRef(ref: ValueFnRef): CompiledValue? {
if (ref is LambdaFnRef && ref.bytecodeFn != null) { if (ref is LambdaFnRef && ref.bytecodeFn != null) {
val captures = (lambdaCaptureEntriesByRef[ref] ?: ref.captureEntries).orEmpty() val captures = (lambdaCaptureEntriesByRef[ref] ?: ref.captureEntries).orEmpty()
@ -998,11 +1003,8 @@ class BytecodeCompiler(
} }
private fun compileMapLiteral(ref: MapLiteralRef): CompiledValue? { 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() val dst = allocSlot()
builder.emit(Opcode.CALL_SLOT, mapClassSlot, 0, 0, dst) emitCallDirect(ObjMap.type, 0, 0, dst)
updateSlotType(dst, SlotType.OBJ) updateSlotType(dst, SlotType.OBJ)
slotObjClass[dst] = ObjMap.type slotObjClass[dst] = ObjMap.type
for (entry in ref.entries()) { for (entry in ref.entries()) {
@ -1285,11 +1287,8 @@ class BytecodeCompiler(
emitMove(leftObj, argLeft) emitMove(leftObj, argLeft)
val argRight = allocSlot() val argRight = allocSlot()
emitMove(rightObj, argRight) emitMove(rightObj, argRight)
val mapEntryClassId = builder.addConst(BytecodeConst.ObjRef(ObjMapEntry.type))
val mapEntryClassSlot = allocSlot()
builder.emit(Opcode.CONST_OBJ, mapEntryClassId, mapEntryClassSlot)
val dst = allocSlot() val dst = allocSlot()
builder.emit(Opcode.CALL_SLOT, mapEntryClassSlot, argBase, 2, dst) emitCallDirect(ObjMapEntry.type, argBase, 2, dst)
updateSlotType(dst, SlotType.OBJ) updateSlotType(dst, SlotType.OBJ)
slotObjClass[dst] = ObjMapEntry.type slotObjClass[dst] = ObjMapEntry.type
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)
@ -5090,7 +5089,6 @@ class BytecodeCompiler(
} }
private fun compileInlineDirectLambdaCall(ref: CallRef, lambdaRef: LambdaFnRef): CompiledValue? { private fun compileInlineDirectLambdaCall(ref: CallRef, lambdaRef: LambdaFnRef): CompiledValue? {
if (ref.isOptionalInvoke) return null
if (ref.tailBlock) return null if (ref.tailBlock) return null
if (!ref.explicitTypeArgs.isNullOrEmpty()) return null if (!ref.explicitTypeArgs.isNullOrEmpty()) return null
val inlineRef = lambdaRef.inlineBodyRef ?: return null val inlineRef = lambdaRef.inlineBodyRef ?: return null
@ -5279,12 +5277,8 @@ class BytecodeCompiler(
} }
private fun createEmptyMutableList(): CompiledValue? { private fun createEmptyMutableList(): CompiledValue? {
val calleeId = builder.addConst(BytecodeConst.ObjRef(ObjList.type))
val calleeSlot = allocSlot()
builder.emit(Opcode.CONST_OBJ, calleeId, calleeSlot)
updateSlotType(calleeSlot, SlotType.OBJ)
val dst = allocSlot() val dst = allocSlot()
builder.emit(Opcode.CALL_SLOT, calleeSlot, 0, 0, dst) emitCallDirect(ObjList.type, 0, 0, dst)
updateSlotType(dst, SlotType.OBJ) updateSlotType(dst, SlotType.OBJ)
slotObjClass[dst] = ObjList.type slotObjClass[dst] = ObjList.type
return CompiledValue(dst, SlotType.OBJ) return CompiledValue(dst, SlotType.OBJ)

View File

@ -361,6 +361,23 @@ class BytecodeRecentOpsTest {
assertEquals(11, scope.eval("calc()").toInt()) 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 @Test
fun letLiteralUsesInlineBytecode() = runTest { fun letLiteralUsesInlineBytecode() = runTest {
val scope = Script.newScope() val scope = Script.newScope()
@ -516,6 +533,40 @@ class BytecodeRecentOpsTest {
assertEquals(6, scope.eval("calc()").toInt()) 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 @Test
fun optionalIndexPreIncSkipsOnNullReceiver() = runTest { fun optionalIndexPreIncSkipsOnNullReceiver() = runTest {
eval( eval(