diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BytecodeCallable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BytecodeCallable.kt index bf650c5..73d3979 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BytecodeCallable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BytecodeCallable.kt @@ -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 +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index a69589c..20d0428 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -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() - 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(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 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index cf4f918..047b75a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -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) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index 7e7c6fe..a871bed 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -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, override val pos: Pos, -) : Statement(), BytecodeCallable { +) : Statement(), BytecodeCallable, BytecodeArgCallable { private val slotPlanByName: Map by lazy(LazyThreadSafetyMode.NONE) { fn.localSlotPlanByName() } private val declaredLocalNames: Set 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 { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt index eab741f..19157d3 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt @@ -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()) {