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.
|
* Single argument declaration descriptor.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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>,
|
||||||
|
|||||||
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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>,
|
||||||
|
|||||||
@ -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 ->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user