Add non-suspending fast calls for bytecode callables
This commit is contained in:
parent
33d170f525
commit
a61b5a31be
@ -16,4 +16,8 @@
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
interface BytecodeCallable
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
|
||||
interface BytecodeCallable {
|
||||
fun callOnFast(scope: Scope): Obj? = null
|
||||
}
|
||||
|
||||
@ -3553,11 +3553,82 @@ class Compiler(
|
||||
val ref = LambdaFnRef(
|
||||
valueFn = { closureScope ->
|
||||
val captureRecords = closureScope.captureRecords
|
||||
val stmt = object : Statement(), BytecodeBodyProvider {
|
||||
val stmt = object : Statement(), BytecodeBodyProvider, BytecodeCallable {
|
||||
override val pos: Pos = fnStatements.pos
|
||||
|
||||
override fun bytecodeBody(): BytecodeStatement? = fnStatements as? BytecodeStatement
|
||||
|
||||
override fun callOnFast(scope: Scope): Obj? {
|
||||
val context = scope.applyClosureForBytecode(closureScope, preferredThisType = expectedReceiverType).also {
|
||||
it.args = scope.args
|
||||
}
|
||||
if (captureSlots.isNotEmpty()) {
|
||||
if (captureRecords != null) {
|
||||
context.captureRecords = captureRecords
|
||||
context.captureNames = captureSlots.map { it.name }
|
||||
} else {
|
||||
val resolvedRecords = ArrayList<ObjRecord>(captureSlots.size)
|
||||
val resolvedNames = ArrayList<String>(captureSlots.size)
|
||||
for (capture in captureSlots) {
|
||||
val rec = resolveStableCaptureRecord(
|
||||
closureScope,
|
||||
capture.name,
|
||||
context.currentClassCtx
|
||||
) ?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found")
|
||||
resolvedRecords.add(freezeImmutableCaptureRecord(rec))
|
||||
resolvedNames.add(capture.name)
|
||||
}
|
||||
context.captureRecords = resolvedRecords
|
||||
context.captureNames = resolvedNames
|
||||
}
|
||||
}
|
||||
val bytecodeBody = fnStatements as? BytecodeStatement ?: return null
|
||||
val bytecodeFn = bytecodeBody.bytecodeFunction()
|
||||
if (!supportsDirectInvokeFastPath || !bytecodeFn.fastOnly) return null
|
||||
val fastPreboundNames = if (argsDeclaration == null) {
|
||||
setOf("it")
|
||||
} else {
|
||||
argsDeclaration.params.mapTo(LinkedHashSet()) { it.name }
|
||||
}
|
||||
val declaredNames = bytecodeFn.constants
|
||||
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
||||
.mapTo(mutableSetOf()) { it.name }
|
||||
if (!canFastSeedUndeclaredLocals(bytecodeFn, declaredNames, fastPreboundNames)) return null
|
||||
if (argsDeclaration != null && !argsDeclaration.supportsFastFrameBinding(scope.args)) return null
|
||||
val slotPlan = bytecodeFn.localSlotPlanByName()
|
||||
val binder: (net.sergeych.lyng.bytecode.CmdFrame, Arguments) -> Unit = { frame, 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 = 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.assignToFrameFast(
|
||||
context,
|
||||
arguments,
|
||||
slotPlan,
|
||||
frame.frame
|
||||
)
|
||||
}
|
||||
}
|
||||
return try {
|
||||
net.sergeych.lyng.bytecode.CmdVm().executeFastOnlyNoSuspend(bytecodeFn, 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 {
|
||||
val context = scope.applyClosureForBytecode(closureScope, preferredThisType = expectedReceiverType).also {
|
||||
it.args = scope.args
|
||||
@ -9439,9 +9510,76 @@ class Compiler(
|
||||
val closureBox = FunctionClosureBox()
|
||||
|
||||
val captureSlots = capturePlan.captures.toList()
|
||||
val fnBody = object : Statement(), BytecodeBodyProvider {
|
||||
val fnBody = object : Statement(), BytecodeBodyProvider, BytecodeCallable {
|
||||
override val pos: Pos = start
|
||||
override fun bytecodeBody(): BytecodeStatement? = fnStatements as? BytecodeStatement
|
||||
|
||||
override fun callOnFast(scope: Scope): Obj? {
|
||||
scope.pos = start
|
||||
val context = closureBox.closure?.let { closure ->
|
||||
scope.applyClosureForBytecode(closure).also {
|
||||
it.args = scope.args
|
||||
}
|
||||
} ?: scope
|
||||
|
||||
val captureBase = closureBox.captureContext ?: closureBox.closure
|
||||
val bytecodeBody = (fnStatements as? BytecodeStatement) ?: return null
|
||||
val bytecodeFn = bytecodeBody.bytecodeFunction()
|
||||
if (!bytecodeFn.fastOnly || !argsDeclaration.supportsFastFrameBinding(scope.args)) return null
|
||||
val declaredNames = bytecodeFn.constants
|
||||
.mapNotNull { it as? BytecodeConst.LocalDecl }
|
||||
.mapTo(mutableSetOf()) { it.name }
|
||||
val preboundNames = LinkedHashSet<String>()
|
||||
argsDeclaration.params.mapTo(preboundNames) { it.name }
|
||||
mergedTypeParamDecls.mapTo(preboundNames) { it.name }
|
||||
if (!canFastSeedUndeclaredLocals(bytecodeFn, declaredNames, preboundNames)) return null
|
||||
val captureNames = captureNamesForBytecodeFunction(
|
||||
bytecodeFn,
|
||||
captureSlots.map { it.name }
|
||||
)
|
||||
val prebuiltCaptures = closureBox.captureRecords
|
||||
if (prebuiltCaptures != null && captureNames.isNotEmpty()) {
|
||||
context.captureRecords = prebuiltCaptures
|
||||
context.captureNames = captureNames
|
||||
} else if (captureBase != null && captureNames.isNotEmpty()) {
|
||||
val resolvedRecords = ArrayList<ObjRecord>(captureNames.size)
|
||||
for (name in captureNames) {
|
||||
val rec = resolveStableCaptureRecord(
|
||||
captureBase,
|
||||
name,
|
||||
context.currentClassCtx
|
||||
) ?: captureBase.raiseSymbolNotFound("symbol $name not found")
|
||||
resolvedRecords.add(freezeImmutableCaptureRecord(rec))
|
||||
}
|
||||
context.captureRecords = resolvedRecords
|
||||
context.captureNames = captureNames
|
||||
}
|
||||
val slotPlan = bytecodeFn.localSlotPlanByName()
|
||||
val binder: (net.sergeych.lyng.bytecode.CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||
argsDeclaration.assignToFrameFast(
|
||||
context,
|
||||
arguments,
|
||||
slotPlan,
|
||||
frame.frame
|
||||
)
|
||||
val typeBindings = bindTypeParamsAtRuntime(context, argsDeclaration, mergedTypeParamDecls)
|
||||
if (typeBindings.isNotEmpty()) {
|
||||
for ((name, bound) in typeBindings) {
|
||||
val slot = slotPlan[name] ?: continue
|
||||
frame.frame.setObj(slot, bound)
|
||||
}
|
||||
}
|
||||
if (extTypeName != null) {
|
||||
context.thisObj = scope.thisObj
|
||||
}
|
||||
}
|
||||
return try {
|
||||
net.sergeych.lyng.bytecode.CmdVm().executeFastOnlyNoSuspend(bytecodeFn, context, scope.args, binder)
|
||||
} catch (e: ReturnException) {
|
||||
if (e.label == null || e.label == name || e.label == outerLabel) e.result else throw e
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
scope.pos = start
|
||||
|
||||
|
||||
@ -66,7 +66,8 @@ internal class ScopeBridge(internal val scope: Scope) : ScopeFacade {
|
||||
override fun raiseIllegalState(message: String): Nothing = scope.raiseIllegalState(message)
|
||||
override fun raiseNotImplemented(what: String): Nothing = scope.raiseNotImplemented(what)
|
||||
override suspend fun call(callee: Obj, args: Arguments, newThisObj: Obj?): Obj {
|
||||
return callee.callOn(scope.createChildScope(scope.pos, args = args, newThisObj = newThisObj))
|
||||
val child = scope.createChildScope(scope.pos, args = args, newThisObj = newThisObj)
|
||||
return (callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
||||
}
|
||||
override suspend fun toStringOf(obj: Obj, forInspect: Boolean): ObjString = obj.toString(scope, forInspect)
|
||||
override suspend fun inspect(obj: Obj): String = obj.inspect(scope)
|
||||
|
||||
@ -58,6 +58,31 @@ class CmdVm {
|
||||
return execute(fn, scope0, Arguments.from(args))
|
||||
}
|
||||
|
||||
fun executeFastOnlyNoSuspend(
|
||||
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
|
||||
try {
|
||||
while (result == null) {
|
||||
val cmd = cmds[frame.ip++]
|
||||
if (!cmd.performFast(frame)) {
|
||||
error("fast-only command not supported: ${cmd::class.simpleName}")
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
throw frame.normalizeThrowableFast(e)
|
||||
}
|
||||
return result ?: ObjVoid
|
||||
}
|
||||
|
||||
suspend fun executeFastOnly(
|
||||
fn: CmdFunction,
|
||||
scope0: Scope,
|
||||
@ -3270,7 +3295,7 @@ class CmdCallDirect(
|
||||
} else {
|
||||
val scope = frame.ensureScope()
|
||||
if (callee is BytecodeLambdaCallable && callee.supportsDirectInvokeFastPath()) {
|
||||
callee.invokeWithArgs(scope, args)
|
||||
callee.invokeWithArgsFast(scope, args) ?: callee.invokeWithArgs(scope, args)
|
||||
} else {
|
||||
callee.callOn(scope.createChildScope(scope.pos, args = args))
|
||||
}
|
||||
@ -3312,7 +3337,7 @@ class CmdCallSlot(
|
||||
}
|
||||
}
|
||||
if (callee is BytecodeLambdaCallable && callee.supportsDirectInvokeFastPath()) {
|
||||
callee.invokeWithArgs(scope, args)
|
||||
callee.invokeWithArgsFast(scope, args) ?: callee.invokeWithArgs(scope, args)
|
||||
} else {
|
||||
callee.callOn(scope.createChildScope(scope.pos, args = args))
|
||||
}
|
||||
@ -3399,9 +3424,10 @@ class CmdListFillInt(
|
||||
val result = ObjList(LongArray(size))
|
||||
for (i in 0 until size) {
|
||||
val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) {
|
||||
callable.invokeImplicitIntArg(scope, i.toLong())
|
||||
callable.invokeImplicitIntArgFast(scope, i.toLong()) ?: callable.invokeImplicitIntArg(scope, i.toLong())
|
||||
} else if (callable is BytecodeLambdaCallable && callable.supportsDirectInvokeFastPath()) {
|
||||
callable.invokeWithArgs(scope, Arguments(ObjInt.of(i.toLong())))
|
||||
callable.invokeWithArgsFast(scope, Arguments(ObjInt.of(i.toLong())))
|
||||
?: callable.invokeWithArgs(scope, Arguments(ObjInt.of(i.toLong())))
|
||||
} else {
|
||||
callable.callOn(scope.createChildScope(scope.pos, args = Arguments(ObjInt.of(i.toLong()))))
|
||||
}
|
||||
@ -4014,16 +4040,18 @@ class BytecodeLambdaCallable(
|
||||
|
||||
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 val fastPreboundLocalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) {
|
||||
if (argsDeclaration == null) {
|
||||
setOf("it")
|
||||
} else {
|
||||
argsDeclaration.params.mapTo(LinkedHashSet()) { it.name }
|
||||
}
|
||||
}
|
||||
|
||||
private val supportsFastUndeclaredLocalInit: Boolean by lazy(LazyThreadSafetyMode.NONE) {
|
||||
canFastSeedUndeclaredLocals(fn, declaredLocalNames, fastPreboundLocalNames)
|
||||
}
|
||||
|
||||
private fun supportsFastOnlyVm(arguments: Arguments): Boolean {
|
||||
if (!supportsDirectInvokeFastPath || !fn.fastOnly) return false
|
||||
if (!supportsFastUndeclaredLocalInit) return false
|
||||
@ -4139,6 +4167,21 @@ class BytecodeLambdaCallable(
|
||||
}
|
||||
}
|
||||
|
||||
fun invokeImplicitIntArgFast(scope: Scope, arg: Long): Obj? {
|
||||
if (!supportsFastOnlyVm(Arguments.EMPTY)) return null
|
||||
val context = buildContext(scope, Arguments.EMPTY)
|
||||
val binder: (CmdFrame, Arguments) -> Unit = { frame, _ ->
|
||||
slotPlanByName["it"]?.let { itSlot ->
|
||||
frame.frame.setInt(itSlot, arg)
|
||||
}
|
||||
}
|
||||
return try {
|
||||
CmdVm().executeFastOnlyNoSuspend(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 {
|
||||
@ -4160,6 +4203,21 @@ class BytecodeLambdaCallable(
|
||||
}
|
||||
}
|
||||
|
||||
fun invokeWithArgsFast(scope: Scope, args: Arguments): Obj? {
|
||||
if (!supportsFastOnlyVm(args)) return null
|
||||
val context = buildContext(scope, args)
|
||||
val binder: (CmdFrame, Arguments) -> Unit = { frame, arguments ->
|
||||
bindArgumentsFast(frame, context, arguments)
|
||||
}
|
||||
return try {
|
||||
CmdVm().executeFastOnlyNoSuspend(fn, context, args, binder)
|
||||
} catch (e: ReturnException) {
|
||||
if (e.label == null || returnLabels.contains(e.label)) e.result else throw e
|
||||
}
|
||||
}
|
||||
|
||||
override fun callOnFast(scope: Scope): Obj? = invokeWithArgsFast(scope, scope.args)
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
return invokeWithArgs(scope, scope.args)
|
||||
}
|
||||
@ -4611,6 +4669,19 @@ class CmdFrame(
|
||||
return ExecutionError(errorObject, pos, message, t)
|
||||
}
|
||||
|
||||
fun normalizeThrowableFast(t: Throwable): Throwable {
|
||||
if (t is ExecutionError || t is ReturnException || t is LoopBreakContinueException) return t
|
||||
val parentScope = ensureScope()
|
||||
val pos = (t as? ScriptError)?.pos ?: currentErrorPos() ?: parentScope.pos
|
||||
val throwScope = parentScope.createChildScope(pos = pos)
|
||||
val message = when (t) {
|
||||
is ScriptError -> t.errorMessage
|
||||
else -> t.message ?: t.toString()
|
||||
}
|
||||
val errorObject = ObjUnknownException(throwScope, message)
|
||||
return ExecutionError(errorObject, pos, message, t)
|
||||
}
|
||||
|
||||
suspend fun handleException(t: Throwable): Boolean {
|
||||
val handler = tryStack.lastOrNull() ?: return false
|
||||
vmIterDebug {
|
||||
|
||||
@ -19,6 +19,22 @@ package net.sergeych.lyng.bytecode
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.obj.ObjRecord
|
||||
|
||||
internal fun canFastSeedUndeclaredLocals(
|
||||
fn: CmdFunction,
|
||||
declaredLocalNames: Set<String>,
|
||||
preboundLocalNames: Set<String>
|
||||
): Boolean {
|
||||
if (fn.localSlotNames.isEmpty()) return true
|
||||
for (i in fn.localSlotNames.indices) {
|
||||
val name = fn.localSlotNames[i] ?: continue
|
||||
if (declaredLocalNames.contains(name)) continue
|
||||
if (fn.localSlotCaptures.getOrNull(i) == true) continue
|
||||
if (preboundLocalNames.contains(name)) continue
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
||||
val localNames = frame.fn.localSlotNames
|
||||
if (localNames.isEmpty()) return
|
||||
|
||||
@ -24,6 +24,7 @@ import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.serializer
|
||||
import net.sergeych.lyng.BytecodeCallable
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.InteropOperator
|
||||
import net.sergeych.lyng.OperatorInteropRegistry
|
||||
@ -728,12 +729,13 @@ open class Obj {
|
||||
return if (usePool) {
|
||||
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||
callOn(child)
|
||||
(this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child)
|
||||
}
|
||||
} else {
|
||||
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
||||
val child = scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
||||
if (declaringClass != null) it.currentClassCtx = declaringClass
|
||||
})
|
||||
}
|
||||
(this as? BytecodeCallable)?.callOnFast(child) ?: callOn(child)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user