Optimize fast-path bytecode lambda invocation

This commit is contained in:
Sergey Chernov 2026-04-21 11:29:14 +03:00
parent f72cdfdf83
commit 33d170f525
9 changed files with 382 additions and 82 deletions

View File

@ -434,6 +434,57 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
} }
} }
fun supportsFastFrameBinding(arguments: Arguments): Boolean {
if (arguments.named.isNotEmpty() || arguments.tailBlockMode) return false
return params.none { it.isEllipsis || it.defaultValue != null }
}
fun assignToFrameFast(
scope: Scope,
arguments: Arguments = scope.args,
paramSlotPlan: Map<String, Int>,
frame: FrameAccess,
slotOffset: Int = 0
) {
if (!supportsFastFrameBinding(arguments)) {
scope.raiseIllegalState("fast frame binding is not supported for this call shape")
}
fun slotFor(name: String): Int {
val full = paramSlotPlan[name] ?: scope.raiseIllegalState("parameter slot for '$name' is missing")
val slot = full - slotOffset
if (slot < 0) scope.raiseIllegalState("parameter slot for '$name' is out of range")
return slot
}
fun assign(slot: Int, value: Obj) {
when (value) {
is net.sergeych.lyng.obj.ObjInt -> frame.setInt(slot, value.value)
is net.sergeych.lyng.obj.ObjReal -> frame.setReal(slot, value.value)
is net.sergeych.lyng.obj.ObjBool -> frame.setBool(slot, value.value)
else -> frame.setObj(slot, value)
}
}
if (arguments.list.size > params.size) {
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
}
if (arguments.list.size < params.size) {
for (i in arguments.list.size until params.size) {
val a = params[i]
if (!a.type.isNullable) {
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
}
}
}
for (i in params.indices) {
val slot = slotFor(params[i].name)
val value = if (i < arguments.list.size) arguments.list[i] else ObjNull
assign(slot, value.byValueCopy())
}
}
/** /**
* Single argument declaration descriptor. * Single argument declaration descriptor.
* *

View File

@ -3545,6 +3545,11 @@ class Compiler(
} }
val bytecodeFn = (fnStatements as? BytecodeStatement)?.bytecodeFunction() val bytecodeFn = (fnStatements as? BytecodeStatement)?.bytecodeFunction()
val inlineBodyRef = argsDeclaration?.let { null } ?: extractInlineLambdaBodyRef(body) val inlineBodyRef = argsDeclaration?.let { null } ?: extractInlineLambdaBodyRef(body)
val supportsDirectInvokeFastPath = bytecodeFn != null &&
bytecodeFn.scopeSlotCount == 0 &&
expectedReceiverType == null &&
!wrapAsExtensionCallable &&
!containsDelegatedRefs(body)
val ref = LambdaFnRef( val ref = LambdaFnRef(
valueFn = { closureScope -> valueFn = { closureScope ->
val captureRecords = closureScope.captureRecords val captureRecords = closureScope.captureRecords
@ -3628,6 +3633,7 @@ class Compiler(
captureEntries = captureEntries, captureEntries = captureEntries,
inferredReturnClass = returnClass, inferredReturnClass = returnClass,
inlineBodyRef = inlineBodyRef, inlineBodyRef = inlineBodyRef,
supportsDirectInvokeFastPath = supportsDirectInvokeFastPath,
preferredThisType = expectedReceiverType, preferredThisType = expectedReceiverType,
wrapAsExtensionCallable = wrapAsExtensionCallable, wrapAsExtensionCallable = wrapAsExtensionCallable,
returnLabels = returnLabels, returnLabels = returnLabels,

View File

@ -899,6 +899,7 @@ class BytecodeCompiler(
captureNames = captureNames, captureNames = captureNames,
paramSlotPlan = ref.paramSlotPlan, paramSlotPlan = ref.paramSlotPlan,
argsDeclaration = ref.argsDeclaration, argsDeclaration = ref.argsDeclaration,
supportsDirectInvokeFastPath = ref.supportsDirectInvokeFastPath,
preferredThisType = ref.preferredThisType, preferredThisType = ref.preferredThisType,
wrapAsExtensionCallable = ref.wrapAsExtensionCallable, wrapAsExtensionCallable = ref.wrapAsExtensionCallable,
returnLabels = ref.returnLabels, returnLabels = ref.returnLabels,

View File

@ -40,6 +40,7 @@ sealed class BytecodeConst {
val captureNames: List<String>, val captureNames: List<String>,
val paramSlotPlan: Map<String, Int>, val paramSlotPlan: Map<String, Int>,
val argsDeclaration: ArgsDeclaration?, val argsDeclaration: ArgsDeclaration?,
val supportsDirectInvokeFastPath: Boolean,
val preferredThisType: String?, val preferredThisType: String?,
val wrapAsExtensionCallable: Boolean, val wrapAsExtensionCallable: Boolean,
val returnLabels: Set<String>, val returnLabels: Set<String>,

View File

@ -112,6 +112,7 @@ class CmdBuilder {
} }
cmds.add(createCmd(ins.op, operands, scopeSlotCount, localSlotCaptures)) cmds.add(createCmd(ins.op, operands, scopeSlotCount, localSlotCaptures))
} }
val cmdArray = cmds.toTypedArray()
return CmdFunction( return CmdFunction(
name = name, name = name,
localCount = localCount, localCount = localCount,
@ -128,8 +129,9 @@ class CmdBuilder {
localSlotDelegated = localSlotDelegated, localSlotDelegated = localSlotDelegated,
localSlotCaptures = localSlotCaptures, localSlotCaptures = localSlotCaptures,
constants = constPool.toList(), constants = constPool.toList(),
cmds = cmds.toTypedArray(), cmds = cmdArray,
posByIp = posByInstr.toTypedArray() posByIp = posByInstr.toTypedArray(),
fastOnly = computeFastOnlyBytecode(scopeSlotCount, cmdArray)
) )
} }

View File

@ -35,6 +35,7 @@ data class CmdFunction(
val constants: List<BytecodeConst>, val constants: List<BytecodeConst>,
val cmds: Array<Cmd>, val cmds: Array<Cmd>,
val posByIp: Array<net.sergeych.lyng.Pos?>, val posByIp: Array<net.sergeych.lyng.Pos?>,
val fastOnly: Boolean = false,
) { ) {
init { init {
require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" } require(scopeSlotIndices.size == scopeSlotCount) { "scopeSlotIndices size mismatch" }
@ -71,3 +72,118 @@ data class CmdFunction(
} }
} }
internal fun computeFastOnlyBytecode(scopeSlotCount: Int, cmds: Array<Cmd>): Boolean {
if (scopeSlotCount != 0) return false
return cmds.all(::supportsFastOnlyExecution)
}
private fun supportsFastOnlyExecution(cmd: Cmd): Boolean {
return when (cmd) {
is CmdMoveIntLocal,
is CmdMoveRealLocal,
is CmdMoveBoolLocal,
is CmdConstObj,
is CmdConstInt,
is CmdConstIntLocal,
is CmdConstReal,
is CmdConstBool,
is CmdConstNull,
is CmdUnboxIntObjLocal,
is CmdUnboxRealObjLocal,
is CmdIntToRealLocal,
is CmdRealToIntLocal,
is CmdBoolToIntLocal,
is CmdIntToBoolLocal,
is CmdAddIntLocal,
is CmdSubIntLocal,
is CmdMulIntLocal,
is CmdDivIntLocal,
is CmdModIntLocal,
is CmdNegIntLocal,
is CmdIncIntLocal,
is CmdDecIntLocal,
is CmdAddRealLocal,
is CmdSubRealLocal,
is CmdMulRealLocal,
is CmdDivRealLocal,
is CmdNegRealLocal,
is CmdAndIntLocal,
is CmdOrIntLocal,
is CmdXorIntLocal,
is CmdShlIntLocal,
is CmdShrIntLocal,
is CmdUshrIntLocal,
is CmdInvIntLocal,
is CmdCmpEqIntLocal,
is CmdCmpNeqIntLocal,
is CmdCmpLtIntLocal,
is CmdCmpLteIntLocal,
is CmdCmpGtIntLocal,
is CmdCmpGteIntLocal,
is CmdCmpEqRealLocal,
is CmdCmpNeqRealLocal,
is CmdCmpLtRealLocal,
is CmdCmpLteRealLocal,
is CmdCmpGtRealLocal,
is CmdCmpGteRealLocal,
is CmdCmpEqBoolLocal,
is CmdCmpNeqBoolLocal,
is CmdCmpEqIntRealLocal,
is CmdCmpEqRealIntLocal,
is CmdCmpLtIntRealLocal,
is CmdCmpLtRealIntLocal,
is CmdCmpLteIntRealLocal,
is CmdCmpLteRealIntLocal,
is CmdCmpGtIntRealLocal,
is CmdCmpGtRealIntLocal,
is CmdCmpGteIntRealLocal,
is CmdCmpGteRealIntLocal,
is CmdCmpNeqIntRealLocal,
is CmdCmpNeqRealIntLocal,
is CmdCmpEqStrLocal,
is CmdCmpNeqStrLocal,
is CmdCmpLtStrLocal,
is CmdCmpLteStrLocal,
is CmdCmpGtStrLocal,
is CmdCmpGteStrLocal,
is CmdCmpEqIntObjLocal,
is CmdCmpNeqIntObjLocal,
is CmdCmpLtIntObjLocal,
is CmdCmpLteIntObjLocal,
is CmdCmpGtIntObjLocal,
is CmdCmpGteIntObjLocal,
is CmdCmpEqRealObjLocal,
is CmdCmpNeqRealObjLocal,
is CmdCmpLtRealObjLocal,
is CmdCmpLteRealObjLocal,
is CmdCmpGtRealObjLocal,
is CmdCmpGteRealObjLocal,
is CmdAddIntObjLocal,
is CmdSubIntObjLocal,
is CmdMulIntObjLocal,
is CmdDivIntObjLocal,
is CmdModIntObjLocal,
is CmdAddRealObjLocal,
is CmdSubRealObjLocal,
is CmdMulRealObjLocal,
is CmdDivRealObjLocal,
is CmdModRealObjLocal,
is CmdNotBoolLocal,
is CmdAndBoolLocal,
is CmdOrBoolLocal,
is CmdJmp,
is CmdJmpIfTrueLocal,
is CmdJmpIfFalseLocal,
is CmdJmpIfEqIntLocal,
is CmdJmpIfNeqIntLocal,
is CmdJmpIfLtIntLocal,
is CmdJmpIfLteIntLocal,
is CmdJmpIfGtIntLocal,
is CmdJmpIfGteIntLocal,
is CmdRet,
is CmdRetVoid -> true
else -> false
}
}

View File

@ -57,6 +57,39 @@ class CmdVm {
suspend fun execute(fn: CmdFunction, scope0: Scope, args: List<Obj>): Obj { suspend fun execute(fn: CmdFunction, scope0: Scope, args: List<Obj>): Obj {
return execute(fn, scope0, Arguments.from(args)) return execute(fn, scope0, Arguments.from(args))
} }
suspend fun executeFastOnly(
fn: CmdFunction,
scope0: Scope,
args: Arguments,
binder: ((CmdFrame, Arguments) -> Unit)? = null
): Obj {
require(fn.fastOnly) { "fast-only execution requested for non-fast function ${fn.name}" }
result = null
val frame = CmdFrame(this, fn, scope0, args.list)
frame.applyCaptureRecords()
binder?.invoke(frame, args)
val cmds = fn.cmds
while (true) {
try {
while (result == null) {
val cmd = cmds[frame.ip++]
if (!cmd.performFast(frame)) {
error("fast-only command not supported: ${cmd::class.simpleName}")
}
}
break
} catch (e: Throwable) {
val throwable = frame.normalizeThrowable(e)
if (!frame.handleException(throwable)) {
frame.cancelIterators()
throw throwable
}
}
}
frame.cancelIterators()
return result ?: ObjVoid
}
} }
sealed class Cmd { sealed class Cmd {
@ -280,6 +313,11 @@ class CmdMakeRange(
} }
class CmdConstNull(internal val dst: Int) : Cmd() { class CmdConstNull(internal val dst: Int) : Cmd() {
override fun performFast(frame: CmdFrame): Boolean {
frame.setObj(dst, ObjNull)
return true
}
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, ObjNull) frame.setObj(dst, ObjNull)
return return
@ -2301,6 +2339,11 @@ class CmdJmpIfGteIntLocal(internal val a: Int, internal val b: Int, internal val
} }
class CmdRet(internal val slot: Int) : Cmd() { class CmdRet(internal val slot: Int) : Cmd() {
override fun performFast(frame: CmdFrame): Boolean {
frame.vm.result = frame.storedSlotObj(slot)
return true
}
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.vm.result = frame.slotToObj(slot) frame.vm.result = frame.slotToObj(slot)
return return
@ -2322,6 +2365,11 @@ class CmdRetLabel(internal val labelId: Int, internal val slot: Int) : Cmd() {
} }
class CmdRetVoid : Cmd() { class CmdRetVoid : Cmd() {
override fun performFast(frame: CmdFrame): Boolean {
frame.vm.result = ObjVoid
return true
}
override suspend fun perform(frame: CmdFrame) { override suspend fun perform(frame: CmdFrame) {
frame.vm.result = ObjVoid frame.vm.result = ObjVoid
return return
@ -3220,7 +3268,12 @@ class CmdCallDirect(
val result = if (PerfFlags.SCOPE_POOL) { val result = if (PerfFlags.SCOPE_POOL) {
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) } frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
} else { } else {
callee.callOn(frame.ensureScope().createChildScope(frame.ensureScope().pos, args = args)) val scope = frame.ensureScope()
if (callee is BytecodeLambdaCallable && callee.supportsDirectInvokeFastPath()) {
callee.invokeWithArgs(scope, args)
} else {
callee.callOn(scope.createChildScope(scope.pos, args = args))
}
} }
frame.storeObjResult(dst, result) frame.storeObjResult(dst, result)
return return
@ -3258,8 +3311,12 @@ class CmdCallSlot(
scope.raiseIllegalState("bytecode runtime cannot call non-bytecode Statement") scope.raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
} }
} }
if (callee is BytecodeLambdaCallable && callee.supportsDirectInvokeFastPath()) {
callee.invokeWithArgs(scope, args)
} else {
callee.callOn(scope.createChildScope(scope.pos, args = args)) callee.callOn(scope.createChildScope(scope.pos, args = args))
} }
}
frame.storeObjResult(dst, result) frame.storeObjResult(dst, result)
return return
} }
@ -3343,6 +3400,8 @@ class CmdListFillInt(
for (i in 0 until size) { for (i in 0 until size) {
val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) { val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) {
callable.invokeImplicitIntArg(scope, i.toLong()) callable.invokeImplicitIntArg(scope, i.toLong())
} else if (callable is BytecodeLambdaCallable && callable.supportsDirectInvokeFastPath()) {
callable.invokeWithArgs(scope, Arguments(ObjInt.of(i.toLong())))
} else { } else {
callable.callOn(scope.createChildScope(scope.pos, args = Arguments(ObjInt.of(i.toLong())))) callable.callOn(scope.createChildScope(scope.pos, args = Arguments(ObjInt.of(i.toLong()))))
} }
@ -3862,6 +3921,7 @@ class CmdMakeLambda(internal val id: Int, internal val dst: Int) : Cmd() {
captureNames = lambdaConst.captureNames, captureNames = lambdaConst.captureNames,
paramSlotPlan = lambdaConst.paramSlotPlan, paramSlotPlan = lambdaConst.paramSlotPlan,
argsDeclaration = lambdaConst.argsDeclaration, argsDeclaration = lambdaConst.argsDeclaration,
supportsDirectInvokeFastPath = lambdaConst.supportsDirectInvokeFastPath,
preferredThisType = lambdaConst.preferredThisType, preferredThisType = lambdaConst.preferredThisType,
returnLabels = lambdaConst.returnLabels, returnLabels = lambdaConst.returnLabels,
pos = lambdaConst.pos pos = lambdaConst.pos
@ -3883,10 +3943,18 @@ class BytecodeLambdaCallable(
private val captureNames: List<String>, private val captureNames: List<String>,
private val paramSlotPlan: Map<String, Int>, private val paramSlotPlan: Map<String, Int>,
private val argsDeclaration: ArgsDeclaration?, private val argsDeclaration: ArgsDeclaration?,
private val supportsDirectInvokeFastPath: Boolean,
private val preferredThisType: String?, private val preferredThisType: String?,
private val returnLabels: Set<String>, private val returnLabels: Set<String>,
override val pos: Pos, override val pos: Pos,
) : Statement(), BytecodeCallable { ) : Statement(), BytecodeCallable {
private val slotPlanByName: Map<String, Int> by lazy(LazyThreadSafetyMode.NONE) { fn.localSlotPlanByName() }
private val declaredLocalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) {
fn.constants
.mapNotNull { it as? BytecodeConst.LocalDecl }
.mapTo(mutableSetOf()) { it.name }
}
private fun freezeRecord(record: ObjRecord): ObjRecord { private fun freezeRecord(record: ObjRecord): ObjRecord {
if (record.isMutable) return record if (record.isMutable) return record
val raw = record.value as Obj? val raw = record.value as Obj?
@ -3918,6 +3986,7 @@ class BytecodeLambdaCallable(
captureNames = captureNames, captureNames = captureNames,
paramSlotPlan = paramSlotPlan, paramSlotPlan = paramSlotPlan,
argsDeclaration = argsDeclaration, argsDeclaration = argsDeclaration,
supportsDirectInvokeFastPath = supportsDirectInvokeFastPath,
preferredThisType = preferredThisType, preferredThisType = preferredThisType,
returnLabels = returnLabels, returnLabels = returnLabels,
pos = pos pos = pos
@ -3934,6 +4003,7 @@ class BytecodeLambdaCallable(
captureNames = captureNames, captureNames = captureNames,
paramSlotPlan = paramSlotPlan, paramSlotPlan = paramSlotPlan,
argsDeclaration = argsDeclaration, argsDeclaration = argsDeclaration,
supportsDirectInvokeFastPath = supportsDirectInvokeFastPath,
preferredThisType = preferredThisType, preferredThisType = preferredThisType,
returnLabels = returnLabels, returnLabels = returnLabels,
pos = pos pos = pos
@ -3942,32 +4012,27 @@ class BytecodeLambdaCallable(
fun supportsImplicitIntFillFastPath(): Boolean = argsDeclaration == null fun supportsImplicitIntFillFastPath(): Boolean = argsDeclaration == null
suspend fun invokeImplicitIntArg(scope: Scope, arg: Long): Obj { fun supportsDirectInvokeFastPath(): Boolean = supportsDirectInvokeFastPath
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
it.args = Arguments.EMPTY private val supportsFastUndeclaredLocalInit: Boolean by lazy(LazyThreadSafetyMode.NONE) {
} val parameterSlots = paramSlotPlan.values.toHashSet()
if (captureRecords != null) { fn.localSlotNames.indices.all { localIndex ->
context.captureRecords = captureRecords val name = fn.localSlotNames[localIndex] ?: return@all true
context.captureNames = captureNames if (declaredLocalNames.contains(name)) return@all true
} else if (captureNames.isNotEmpty()) { if (fn.localSlotCaptures.getOrNull(localIndex) == true) return@all true
closureScope.raiseIllegalState("bytecode lambda capture records missing") parameterSlots.contains(fn.scopeSlotCount + localIndex)
}
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
paramSlotPlan["it"]?.let { itSlot ->
frame.frame.setInt(itSlot, arg)
}
}
return try {
CmdVm().execute(fn, context, Arguments.EMPTY, binder)
} catch (e: ReturnException) {
if (e.label == null || returnLabels.contains(e.label)) e.result
else throw e
} }
} }
override suspend fun execute(scope: Scope): Obj { private fun supportsFastOnlyVm(arguments: Arguments): Boolean {
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also { if (!supportsDirectInvokeFastPath || !fn.fastOnly) return false
it.args = scope.args if (!supportsFastUndeclaredLocalInit) return false
return argsDeclaration == null || argsDeclaration.supportsFastFrameBinding(arguments)
}
private fun buildContext(callScope: Scope, args: Arguments): Scope {
val context = callScope.applyClosureForBytecode(closureScope, preferredThisType).also {
it.args = args
} }
if (captureRecords != null) { if (captureRecords != null) {
context.captureRecords = captureRecords context.captureRecords = captureRecords
@ -3975,17 +4040,10 @@ class BytecodeLambdaCallable(
} else if (captureNames.isNotEmpty()) { } else if (captureNames.isNotEmpty()) {
closureScope.raiseIllegalState("bytecode lambda capture records missing") closureScope.raiseIllegalState("bytecode lambda capture records missing")
} }
if (argsDeclaration == null) { return context
// Bound in the bytecode entry binder.
} else {
// args bound into frame slots in the bytecode entry binder
} }
return try {
val declaredNames = fn.constants private fun bindArgumentsFast(frame: CmdFrame, context: Scope, arguments: Arguments) {
.mapNotNull { it as? BytecodeConst.LocalDecl }
.mapTo(mutableSetOf()) { it.name }
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
val slotPlan = fn.localSlotPlanByName()
if (argsDeclaration == null) { if (argsDeclaration == null) {
val l = arguments.list val l = arguments.list
val itValue: Obj = when (l.size) { val itValue: Obj = when (l.size) {
@ -3993,7 +4051,7 @@ class BytecodeLambdaCallable(
1 -> l[0] 1 -> l[0]
else -> ObjList(l.toMutableList()) else -> ObjList(l.toMutableList())
} }
val itSlot = slotPlan["it"] val itSlot = slotPlanByName["it"]
if (itSlot != null) { if (itSlot != null) {
when (itValue) { when (itValue) {
is ObjInt -> frame.frame.setInt(itSlot, itValue.value) is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
@ -4003,17 +4061,33 @@ class BytecodeLambdaCallable(
} }
} }
} else { } else {
argsDeclaration.assignToFrame( argsDeclaration.assignToFrameFast(
context, context,
arguments, arguments,
slotPlan, slotPlanByName,
frame.frame frame.frame
) )
} }
}
private suspend fun bindArguments(frame: CmdFrame, context: Scope, arguments: Arguments) {
if (argsDeclaration == null) {
bindArgumentsFast(frame, context, arguments)
} else {
argsDeclaration.assignToFrame(
context,
arguments,
slotPlanByName,
frame.frame
)
}
}
private suspend fun seedUndeclaredLocals(frame: CmdFrame, context: Scope) {
val localNames = frame.fn.localSlotNames val localNames = frame.fn.localSlotNames
for (i in localNames.indices) { for (i in localNames.indices) {
val name = localNames[i] ?: continue val name = localNames[i] ?: continue
if (declaredNames.contains(name)) continue if (declaredLocalNames.contains(name)) continue
val slotType = frame.getLocalSlotTypeCode(i) val slotType = frame.getLocalSlotTypeCode(i)
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) { if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
continue continue
@ -4039,11 +4113,56 @@ class BytecodeLambdaCallable(
frame.frame.setObj(i, value) frame.frame.setObj(i, value)
} }
} }
CmdVm().execute(fn, context, scope.args, binder)
suspend fun invokeImplicitIntArg(scope: Scope, arg: Long): Obj {
val context = buildContext(scope, Arguments.EMPTY)
val fastBinder: (CmdFrame, Arguments) -> Unit = { frame, _ ->
slotPlanByName["it"]?.let { itSlot ->
frame.frame.setInt(itSlot, arg)
}
}
return try {
val vm = CmdVm()
if (supportsFastOnlyVm(Arguments.EMPTY)) {
vm.executeFastOnly(fn, context, Arguments.EMPTY, fastBinder)
} else {
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
slotPlanByName["it"]?.let { itSlot ->
frame.frame.setInt(itSlot, arg)
}
}
vm.execute(fn, context, Arguments.EMPTY, binder)
}
} catch (e: ReturnException) {
if (e.label == null || returnLabels.contains(e.label)) e.result
else throw e
}
}
suspend fun invokeWithArgs(scope: Scope, args: Arguments): Obj {
val context = buildContext(scope, args)
return try {
val vm = CmdVm()
if (supportsFastOnlyVm(args)) {
val binder: (CmdFrame, Arguments) -> Unit = { frame, arguments ->
bindArgumentsFast(frame, context, arguments)
}
vm.executeFastOnly(fn, context, args, binder)
} else {
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
bindArguments(frame, context, arguments)
seedUndeclaredLocals(frame, context)
}
vm.execute(fn, context, args, binder)
}
} catch (e: ReturnException) { } catch (e: ReturnException) {
if (e.label == null || returnLabels.contains(e.label)) e.result else throw e if (e.label == null || returnLabels.contains(e.label)) e.result else throw e
} }
} }
override suspend fun execute(scope: Scope): Obj {
return invokeWithArgs(scope, scope.args)
}
} }
class CmdIterPush(internal val iterSlot: Int) : Cmd() { class CmdIterPush(internal val iterSlot: Int) : Cmd() {

View File

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

View File

@ -17,6 +17,7 @@
package net.sergeych.lyng package net.sergeych.lyng
import kotlinx.coroutines.delay
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.obj.toInt import net.sergeych.lyng.obj.toInt
import kotlin.test.Test import kotlin.test.Test
@ -40,9 +41,11 @@ class OptTest {
repeat(3) { pass -> repeat(3) { pass ->
val size = scope.eval("buildArray(200000)").toInt() val size = scope.eval("buildArray(200000)").toInt()
assertEquals(200000, size, "warmup pass ${pass + 1} failed") assertEquals(200000, size, "warmup pass ${pass + 1} failed")
delay(100)
} }
val passes = 3
val passes = 4
var bestMs = Long.MAX_VALUE var bestMs = Long.MAX_VALUE
var totalMs = 0L var totalMs = 0L
repeat(passes) { pass -> repeat(passes) { pass ->