Fast-path compiled lambda calls with args
This commit is contained in:
parent
97990f00ce
commit
3721ee8332
@ -21,3 +21,7 @@ import net.sergeych.lyng.obj.Obj
|
||||
interface BytecodeCallable {
|
||||
fun callOnFast(scope: Scope): Obj? = null
|
||||
}
|
||||
|
||||
interface BytecodeArgCallable {
|
||||
fun callWithArgsFast(scope: Scope, args: Arguments): Obj? = null
|
||||
}
|
||||
|
||||
@ -9510,79 +9510,11 @@ class Compiler(
|
||||
val closureBox = FunctionClosureBox()
|
||||
|
||||
val captureSlots = capturePlan.captures.toList()
|
||||
val fnBody = object : Statement(), BytecodeBodyProvider, BytecodeCallable {
|
||||
val fnBody = object : Statement(), BytecodeBodyProvider {
|
||||
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
|
||||
|
||||
// restore closure where the function was defined, and making a copy of it
|
||||
// for local space. If there is no closure, we are in, say, class context where
|
||||
// the closure is in the class initialization and we needn't more:
|
||||
@ -9591,6 +9523,7 @@ class Compiler(
|
||||
it.args = scope.args
|
||||
}
|
||||
} ?: scope
|
||||
context.pos = start
|
||||
|
||||
// Capacity hint: parameters + declared locals + small overhead
|
||||
val capacityHint = paramNames.size + fnLocalDecls + 4
|
||||
|
||||
@ -3622,7 +3622,7 @@ class BytecodeCompiler(
|
||||
if (!ref.isOptional) {
|
||||
val args = compileCallArgsWithReceiver(receiver, emptyList(), false) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst)
|
||||
emitCallCompiled(callee, args.base, encodedCount, dst)
|
||||
} else {
|
||||
val nullSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||
@ -3636,7 +3636,7 @@ class BytecodeCompiler(
|
||||
)
|
||||
val args = compileCallArgsWithReceiver(receiver, emptyList(), false) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst)
|
||||
emitCallCompiled(callee, args.base, encodedCount, dst)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||
builder.mark(nullLabel)
|
||||
builder.emit(Opcode.CONST_NULL, dst)
|
||||
@ -4895,7 +4895,7 @@ class BytecodeCompiler(
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
setPos(callPos)
|
||||
builder.emit(Opcode.CALL_SLOT, calleeSlot, args.base, encodedCount, dst)
|
||||
emitCallCompiled(CompiledValue(calleeSlot, SlotType.OBJ), args.base, encodedCount, dst)
|
||||
} else {
|
||||
val nullSlot = allocSlot()
|
||||
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||
@ -4911,7 +4911,7 @@ class BytecodeCompiler(
|
||||
val args = compileCallArgs(ref.args, ref.tailBlock, ref.explicitTypeArgs) ?: return null
|
||||
val encodedCount = encodeCallArgCount(args) ?: return null
|
||||
setPos(callPos)
|
||||
builder.emit(Opcode.CALL_SLOT, calleeSlot, args.base, encodedCount, dst)
|
||||
emitCallCompiled(CompiledValue(calleeSlot, SlotType.OBJ), args.base, encodedCount, dst)
|
||||
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
|
||||
builder.mark(nullLabel)
|
||||
builder.emit(Opcode.CONST_NULL, dst)
|
||||
|
||||
@ -3290,7 +3290,10 @@ class CmdCallDirect(
|
||||
frame.ensureScope().raiseIllegalState("bytecode runtime cannot call non-bytecode Statement")
|
||||
}
|
||||
}
|
||||
val result = if (PerfFlags.SCOPE_POOL) {
|
||||
val directFastResult = (callee as? BytecodeArgCallable)?.callWithArgsFast(frame.ensureScope(), args)
|
||||
val result = if (directFastResult != null) {
|
||||
directFastResult
|
||||
} else if (PerfFlags.SCOPE_POOL) {
|
||||
frame.ensureScope().withChildFrame(args) { child ->
|
||||
(callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
||||
}
|
||||
@ -3328,13 +3331,16 @@ class CmdCallSlot(
|
||||
frame.ensureScope().raiseUnset(message)
|
||||
}
|
||||
val args = frame.buildArguments(argBase, argCount)
|
||||
val scope = frame.ensureScope()
|
||||
val directFastResult = (callee as? BytecodeArgCallable)?.callWithArgsFast(scope, args)
|
||||
val canPool = PerfFlags.SCOPE_POOL && callee !is Statement
|
||||
val result = if (canPool) {
|
||||
val result = if (directFastResult != null) {
|
||||
directFastResult
|
||||
} else if (canPool) {
|
||||
frame.ensureScope().withChildFrame(args) { child ->
|
||||
(callee as? BytecodeCallable)?.callOnFast(child) ?: callee.callOn(child)
|
||||
}
|
||||
} else {
|
||||
val scope = frame.ensureScope()
|
||||
if (callee is Statement) {
|
||||
val bytecodeBody = (callee as? BytecodeBodyProvider)?.bytecodeBody()
|
||||
if (callee !is BytecodeStatement && callee !is BytecodeCallable && bytecodeBody == null) {
|
||||
@ -3429,13 +3435,16 @@ class CmdListFillInt(
|
||||
val scope = frame.ensureScope()
|
||||
val result = ObjList(LongArray(size))
|
||||
for (i in 0 until size) {
|
||||
val args = Arguments(ObjInt.of(i.toLong()))
|
||||
val value = if (callable is BytecodeLambdaCallable && callable.supportsImplicitIntFillFastPath()) {
|
||||
callable.invokeImplicitIntArgFast(scope, i.toLong()) ?: callable.invokeImplicitIntArg(scope, i.toLong())
|
||||
} else if (callable is BytecodeLambdaCallable && callable.supportsDirectInvokeFastPath()) {
|
||||
callable.invokeWithArgsFast(scope, Arguments(ObjInt.of(i.toLong())))
|
||||
?: callable.invokeWithArgs(scope, Arguments(ObjInt.of(i.toLong())))
|
||||
} else if (callable is BytecodeArgCallable) {
|
||||
callable.callWithArgsFast(scope, args) ?: run {
|
||||
val child = scope.createChildScope(scope.pos, args = args)
|
||||
(callable as? BytecodeCallable)?.callOnFast(child) ?: callable.callOn(child)
|
||||
}
|
||||
} else {
|
||||
val child = scope.createChildScope(scope.pos, args = Arguments(ObjInt.of(i.toLong())))
|
||||
val child = scope.createChildScope(scope.pos, args = args)
|
||||
(callable as? BytecodeCallable)?.callOnFast(child) ?: callable.callOn(child)
|
||||
}
|
||||
val intValue = (value as? ObjInt)?.value ?: scope.raiseClassCastError("expected Int fill result")
|
||||
@ -3980,7 +3989,7 @@ class BytecodeLambdaCallable(
|
||||
private val preferredThisType: String?,
|
||||
private val returnLabels: Set<String>,
|
||||
override val pos: Pos,
|
||||
) : Statement(), BytecodeCallable {
|
||||
) : Statement(), BytecodeCallable, BytecodeArgCallable {
|
||||
private val slotPlanByName: Map<String, Int> by lazy(LazyThreadSafetyMode.NONE) { fn.localSlotPlanByName() }
|
||||
private val declaredLocalNames: Set<String> by lazy(LazyThreadSafetyMode.NONE) {
|
||||
fn.constants
|
||||
@ -4223,6 +4232,8 @@ class BytecodeLambdaCallable(
|
||||
}
|
||||
}
|
||||
|
||||
override fun callWithArgsFast(scope: Scope, args: Arguments): Obj? = invokeWithArgsFast(scope, args)
|
||||
|
||||
override fun callOnFast(scope: Scope): Obj? = invokeWithArgsFast(scope, scope.args)
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
|
||||
@ -395,8 +395,17 @@ suspend fun Obj.getLyngExceptionMessageWithStackTrace(scope: Scope? = null,showD
|
||||
var at = "unknown"
|
||||
val stack = if (!trace.list.isEmpty()) {
|
||||
val first = trace.list[0]
|
||||
at = (first.readField(s, "at").value as ObjString).value
|
||||
"\n" + trace.list.map { " at " + it.toString(s).value }.joinToString("\n")
|
||||
suspend fun formatTraceEntry(entry: Obj): String {
|
||||
return when (entry) {
|
||||
is ObjString -> entry.value.removePrefix("#")
|
||||
else -> entry.toString(s).value
|
||||
}
|
||||
}
|
||||
at = when (first) {
|
||||
is ObjString -> formatTraceEntry(first)
|
||||
else -> (first.readField(s, "at").value as ObjString).value
|
||||
}
|
||||
"\n" + trace.list.map { " at " + formatTraceEntry(it) }.joinToString("\n")
|
||||
} else {
|
||||
val pos = s.pos
|
||||
if (pos.source.fileName.isNotEmpty() && pos.currentLine.isNotEmpty()) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user