Optimize fast-path bytecode lambda invocation
This commit is contained in:
parent
f72cdfdf83
commit
33d170f525
@ -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.
|
||||
*
|
||||
|
||||
@ -3545,6 +3545,11 @@ class Compiler(
|
||||
}
|
||||
val bytecodeFn = (fnStatements as? BytecodeStatement)?.bytecodeFunction()
|
||||
val inlineBodyRef = argsDeclaration?.let { null } ?: extractInlineLambdaBodyRef(body)
|
||||
val supportsDirectInvokeFastPath = bytecodeFn != null &&
|
||||
bytecodeFn.scopeSlotCount == 0 &&
|
||||
expectedReceiverType == null &&
|
||||
!wrapAsExtensionCallable &&
|
||||
!containsDelegatedRefs(body)
|
||||
val ref = LambdaFnRef(
|
||||
valueFn = { closureScope ->
|
||||
val captureRecords = closureScope.captureRecords
|
||||
@ -3628,6 +3633,7 @@ class Compiler(
|
||||
captureEntries = captureEntries,
|
||||
inferredReturnClass = returnClass,
|
||||
inlineBodyRef = inlineBodyRef,
|
||||
supportsDirectInvokeFastPath = supportsDirectInvokeFastPath,
|
||||
preferredThisType = expectedReceiverType,
|
||||
wrapAsExtensionCallable = wrapAsExtensionCallable,
|
||||
returnLabels = returnLabels,
|
||||
|
||||
@ -899,6 +899,7 @@ class BytecodeCompiler(
|
||||
captureNames = captureNames,
|
||||
paramSlotPlan = ref.paramSlotPlan,
|
||||
argsDeclaration = ref.argsDeclaration,
|
||||
supportsDirectInvokeFastPath = ref.supportsDirectInvokeFastPath,
|
||||
preferredThisType = ref.preferredThisType,
|
||||
wrapAsExtensionCallable = ref.wrapAsExtensionCallable,
|
||||
returnLabels = ref.returnLabels,
|
||||
|
||||
@ -40,6 +40,7 @@ sealed class BytecodeConst {
|
||||
val captureNames: List<String>,
|
||||
val paramSlotPlan: Map<String, Int>,
|
||||
val argsDeclaration: ArgsDeclaration?,
|
||||
val supportsDirectInvokeFastPath: Boolean,
|
||||
val preferredThisType: String?,
|
||||
val wrapAsExtensionCallable: Boolean,
|
||||
val returnLabels: Set<String>,
|
||||
|
||||
@ -112,6 +112,7 @@ class CmdBuilder {
|
||||
}
|
||||
cmds.add(createCmd(ins.op, operands, scopeSlotCount, localSlotCaptures))
|
||||
}
|
||||
val cmdArray = cmds.toTypedArray()
|
||||
return CmdFunction(
|
||||
name = name,
|
||||
localCount = localCount,
|
||||
@ -128,8 +129,9 @@ class CmdBuilder {
|
||||
localSlotDelegated = localSlotDelegated,
|
||||
localSlotCaptures = localSlotCaptures,
|
||||
constants = constPool.toList(),
|
||||
cmds = cmds.toTypedArray(),
|
||||
posByIp = posByInstr.toTypedArray()
|
||||
cmds = cmdArray,
|
||||
posByIp = posByInstr.toTypedArray(),
|
||||
fastOnly = computeFastOnlyBytecode(scopeSlotCount, cmdArray)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ data class CmdFunction(
|
||||
val constants: List<BytecodeConst>,
|
||||
val cmds: Array<Cmd>,
|
||||
val posByIp: Array<net.sergeych.lyng.Pos?>,
|
||||
val fastOnly: Boolean = false,
|
||||
) {
|
||||
init {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,6 +57,39 @@ class CmdVm {
|
||||
suspend fun execute(fn: CmdFunction, scope0: Scope, args: List<Obj>): Obj {
|
||||
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 {
|
||||
@ -280,6 +313,11 @@ class CmdMakeRange(
|
||||
}
|
||||
|
||||
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) {
|
||||
frame.setObj(dst, ObjNull)
|
||||
return
|
||||
@ -2301,6 +2339,11 @@ class CmdJmpIfGteIntLocal(internal val a: Int, internal val b: Int, internal val
|
||||
}
|
||||
|
||||
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) {
|
||||
frame.vm.result = frame.slotToObj(slot)
|
||||
return
|
||||
@ -2322,6 +2365,11 @@ class CmdRetLabel(internal val labelId: Int, internal val slot: Int) : Cmd() {
|
||||
}
|
||||
|
||||
class CmdRetVoid : Cmd() {
|
||||
override fun performFast(frame: CmdFrame): Boolean {
|
||||
frame.vm.result = ObjVoid
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
frame.vm.result = ObjVoid
|
||||
return
|
||||
@ -3220,7 +3268,12 @@ class CmdCallDirect(
|
||||
val result = if (PerfFlags.SCOPE_POOL) {
|
||||
frame.ensureScope().withChildFrame(args) { child -> callee.callOn(child) }
|
||||
} 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)
|
||||
return
|
||||
@ -3258,7 +3311,11 @@ class CmdCallSlot(
|
||||
scope.raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
|
||||
}
|
||||
}
|
||||
callee.callOn(scope.createChildScope(scope.pos, args = args))
|
||||
if (callee is BytecodeLambdaCallable && callee.supportsDirectInvokeFastPath()) {
|
||||
callee.invokeWithArgs(scope, args)
|
||||
} else {
|
||||
callee.callOn(scope.createChildScope(scope.pos, args = args))
|
||||
}
|
||||
}
|
||||
frame.storeObjResult(dst, result)
|
||||
return
|
||||
@ -3343,6 +3400,8 @@ class CmdListFillInt(
|
||||
for (i in 0 until size) {
|
||||
val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) {
|
||||
callable.invokeImplicitIntArg(scope, i.toLong())
|
||||
} else if (callable is BytecodeLambdaCallable && callable.supportsDirectInvokeFastPath()) {
|
||||
callable.invokeWithArgs(scope, Arguments(ObjInt.of(i.toLong())))
|
||||
} else {
|
||||
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,
|
||||
paramSlotPlan = lambdaConst.paramSlotPlan,
|
||||
argsDeclaration = lambdaConst.argsDeclaration,
|
||||
supportsDirectInvokeFastPath = lambdaConst.supportsDirectInvokeFastPath,
|
||||
preferredThisType = lambdaConst.preferredThisType,
|
||||
returnLabels = lambdaConst.returnLabels,
|
||||
pos = lambdaConst.pos
|
||||
@ -3883,10 +3943,18 @@ class BytecodeLambdaCallable(
|
||||
private val captureNames: List<String>,
|
||||
private val paramSlotPlan: Map<String, Int>,
|
||||
private val argsDeclaration: ArgsDeclaration?,
|
||||
private val supportsDirectInvokeFastPath: Boolean,
|
||||
private val preferredThisType: String?,
|
||||
private val returnLabels: Set<String>,
|
||||
override val pos: Pos,
|
||||
) : 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 {
|
||||
if (record.isMutable) return record
|
||||
val raw = record.value as Obj?
|
||||
@ -3918,6 +3986,7 @@ class BytecodeLambdaCallable(
|
||||
captureNames = captureNames,
|
||||
paramSlotPlan = paramSlotPlan,
|
||||
argsDeclaration = argsDeclaration,
|
||||
supportsDirectInvokeFastPath = supportsDirectInvokeFastPath,
|
||||
preferredThisType = preferredThisType,
|
||||
returnLabels = returnLabels,
|
||||
pos = pos
|
||||
@ -3934,6 +4003,7 @@ class BytecodeLambdaCallable(
|
||||
captureNames = captureNames,
|
||||
paramSlotPlan = paramSlotPlan,
|
||||
argsDeclaration = argsDeclaration,
|
||||
supportsDirectInvokeFastPath = supportsDirectInvokeFastPath,
|
||||
preferredThisType = preferredThisType,
|
||||
returnLabels = returnLabels,
|
||||
pos = pos
|
||||
@ -3942,9 +4012,27 @@ class BytecodeLambdaCallable(
|
||||
|
||||
fun supportsImplicitIntFillFastPath(): Boolean = argsDeclaration == null
|
||||
|
||||
suspend fun invokeImplicitIntArg(scope: Scope, arg: Long): Obj {
|
||||
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
|
||||
it.args = Arguments.EMPTY
|
||||
fun supportsDirectInvokeFastPath(): Boolean = supportsDirectInvokeFastPath
|
||||
|
||||
private val supportsFastUndeclaredLocalInit: Boolean by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val parameterSlots = paramSlotPlan.values.toHashSet()
|
||||
fn.localSlotNames.indices.all { localIndex ->
|
||||
val name = fn.localSlotNames[localIndex] ?: return@all true
|
||||
if (declaredLocalNames.contains(name)) return@all true
|
||||
if (fn.localSlotCaptures.getOrNull(localIndex) == true) return@all true
|
||||
parameterSlots.contains(fn.scopeSlotCount + localIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun supportsFastOnlyVm(arguments: Arguments): Boolean {
|
||||
if (!supportsDirectInvokeFastPath || !fn.fastOnly) return false
|
||||
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) {
|
||||
context.captureRecords = captureRecords
|
||||
@ -3952,98 +4040,129 @@ class BytecodeLambdaCallable(
|
||||
} else if (captureNames.isNotEmpty()) {
|
||||
closureScope.raiseIllegalState("bytecode lambda capture records missing")
|
||||
}
|
||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
||||
paramSlotPlan["it"]?.let { itSlot ->
|
||||
return context
|
||||
}
|
||||
|
||||
private fun bindArgumentsFast(frame: CmdFrame, context: Scope, arguments: Arguments) {
|
||||
if (argsDeclaration == null) {
|
||||
val l = arguments.list
|
||||
val itValue: Obj = when (l.size) {
|
||||
0 -> ObjVoid
|
||||
1 -> l[0]
|
||||
else -> ObjList(l.toMutableList())
|
||||
}
|
||||
val itSlot = slotPlanByName["it"]
|
||||
if (itSlot != null) {
|
||||
when (itValue) {
|
||||
is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
|
||||
is ObjReal -> frame.frame.setReal(itSlot, itValue.value)
|
||||
is ObjBool -> frame.frame.setBool(itSlot, itValue.value)
|
||||
else -> frame.frame.setObj(itSlot, itValue)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
argsDeclaration.assignToFrameFast(
|
||||
context,
|
||||
arguments,
|
||||
slotPlanByName,
|
||||
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
|
||||
for (i in localNames.indices) {
|
||||
val name = localNames[i] ?: continue
|
||||
if (declaredLocalNames.contains(name)) continue
|
||||
val slotType = frame.getLocalSlotTypeCode(i)
|
||||
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
|
||||
continue
|
||||
}
|
||||
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) {
|
||||
continue
|
||||
}
|
||||
val record = context.getLocalRecordDirect(name)
|
||||
?: context.parent?.get(name)
|
||||
?: context.get(name)
|
||||
?: continue
|
||||
val value =
|
||||
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||
context.resolve(record, name)
|
||||
} else {
|
||||
when (val direct = record.value) {
|
||||
is FrameSlotRef -> direct.read()
|
||||
is RecordSlotRef -> direct.read(context, name)
|
||||
is ScopeSlotRef -> direct.read()
|
||||
else -> direct
|
||||
}
|
||||
}
|
||||
frame.frame.setObj(i, value)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
CmdVm().execute(fn, context, Arguments.EMPTY, binder)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val context = scope.applyClosureForBytecode(closureScope, preferredThisType).also {
|
||||
it.args = scope.args
|
||||
}
|
||||
if (captureRecords != null) {
|
||||
context.captureRecords = captureRecords
|
||||
context.captureNames = captureNames
|
||||
} else if (captureNames.isNotEmpty()) {
|
||||
closureScope.raiseIllegalState("bytecode lambda capture records missing")
|
||||
}
|
||||
if (argsDeclaration == null) {
|
||||
// Bound in the bytecode entry binder.
|
||||
} else {
|
||||
// args bound into frame slots in the bytecode entry binder
|
||||
}
|
||||
suspend fun invokeWithArgs(scope: Scope, args: Arguments): Obj {
|
||||
val context = buildContext(scope, args)
|
||||
return try {
|
||||
val declaredNames = fn.constants
|
||||
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
||||
.mapTo(mutableSetOf()) { it.name }
|
||||
val binder: suspend (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||
val slotPlan = fn.localSlotPlanByName()
|
||||
if (argsDeclaration == null) {
|
||||
val l = arguments.list
|
||||
val itValue: Obj = when (l.size) {
|
||||
0 -> ObjVoid
|
||||
1 -> l[0]
|
||||
else -> ObjList(l.toMutableList())
|
||||
}
|
||||
val itSlot = slotPlan["it"]
|
||||
if (itSlot != null) {
|
||||
when (itValue) {
|
||||
is ObjInt -> frame.frame.setInt(itSlot, itValue.value)
|
||||
is ObjReal -> frame.frame.setReal(itSlot, itValue.value)
|
||||
is ObjBool -> frame.frame.setBool(itSlot, itValue.value)
|
||||
else -> frame.frame.setObj(itSlot, itValue)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
argsDeclaration.assignToFrame(
|
||||
context,
|
||||
arguments,
|
||||
slotPlan,
|
||||
frame.frame
|
||||
)
|
||||
val vm = CmdVm()
|
||||
if (supportsFastOnlyVm(args)) {
|
||||
val binder: (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||
bindArgumentsFast(frame, context, arguments)
|
||||
}
|
||||
val localNames = frame.fn.localSlotNames
|
||||
for (i in localNames.indices) {
|
||||
val name = localNames[i] ?: continue
|
||||
if (declaredNames.contains(name)) continue
|
||||
val slotType = frame.getLocalSlotTypeCode(i)
|
||||
if (slotType != SlotType.UNKNOWN.code && slotType != SlotType.OBJ.code) {
|
||||
continue
|
||||
}
|
||||
if (slotType == SlotType.OBJ.code && frame.frame.getRawObj(i) != null) {
|
||||
continue
|
||||
}
|
||||
val record = context.getLocalRecordDirect(name)
|
||||
?: context.parent?.get(name)
|
||||
?: context.get(name)
|
||||
?: continue
|
||||
val value =
|
||||
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||
context.resolve(record, name)
|
||||
} else {
|
||||
when (val direct = record.value) {
|
||||
is FrameSlotRef -> direct.read()
|
||||
is RecordSlotRef -> direct.read(context, name)
|
||||
is ScopeSlotRef -> direct.read()
|
||||
else -> direct
|
||||
}
|
||||
}
|
||||
frame.frame.setObj(i, value)
|
||||
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)
|
||||
}
|
||||
CmdVm().execute(fn, context, scope.args, binder)
|
||||
} catch (e: ReturnException) {
|
||||
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() {
|
||||
|
||||
@ -31,6 +31,7 @@ class LambdaFnRef(
|
||||
val captureEntries: List<LambdaCaptureEntry>,
|
||||
val inferredReturnClass: ObjClass?,
|
||||
val inlineBodyRef: ObjRef?,
|
||||
val supportsDirectInvokeFastPath: Boolean,
|
||||
val preferredThisType: String?,
|
||||
val wrapAsExtensionCallable: Boolean,
|
||||
val returnLabels: Set<String>,
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.obj.toInt
|
||||
import kotlin.test.Test
|
||||
@ -40,9 +41,11 @@ class OptTest {
|
||||
repeat(3) { pass ->
|
||||
val size = scope.eval("buildArray(200000)").toInt()
|
||||
assertEquals(200000, size, "warmup pass ${pass + 1} failed")
|
||||
delay(100)
|
||||
}
|
||||
|
||||
val passes = 3
|
||||
|
||||
val passes = 4
|
||||
var bestMs = Long.MAX_VALUE
|
||||
var totalMs = 0L
|
||||
repeat(passes) { pass ->
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user