PlainEngine

internal object PlainEngine

Compiles a snippet by driving the compiler directly, holding the expensive state ourselves.

The only thing that makes this fast is WHERE the session boundary sits: the project environment, the FirJvmSessionFactory.Context and the shared + library sessions (the classpath FIR index, seconds on a modpack) are built once in warmUp and reused, while each eval builds only a source session — provider wiring, not a classpath scan. Every CLI-level entry point (K2JVMCompiler, prepareJvmSessions, KotlinToJVMBytecodeCompiler) rebuilds all of it per call, which is why none of them appear here.

Types

Link copied to clipboard
sealed interface Compiled

A compiled snippet, or why it did not compile. Ok.resultField comes from the lowering's scriptResultFieldDataAttr; Ok.resultType is what resultTypeName renders off the frontend cone. Both are null when the snippet ended on a statement rather than a value.

Link copied to clipboard
private class Emitted(val irModule: IrModuleFragment, val state: GenerationState)
Link copied to clipboard
private class Warm(val configuration: CompilerConfiguration, val projectEnvironment: VfsBasedProjectEnvironment, val sessionContext: FirJvmSessionFactory.Context, val libraryList: DependencyListForCliModule, val collector: ScriptDiagnosticsMessageCollector, val classpath: List<File>)

Everything reused across evals. Built once; the game classpath does not change under us. The KotlinCoreEnvironment is deliberately absent: nothing reads it, and the Disposer it registered with keeps it (and its project, which projectEnvironment wraps) alive for the life of the process.

Properties

Link copied to clipboard

The FIR caches under a CLI session are single-threaded, so one compile at a time.

Link copied to clipboard
private val registrars: List<FirExtensionRegistrar>

Stateless factories, so one instance serves every session — which is also what the CLI hands around.

Link copied to clipboard

Functions

Link copied to clipboard
fun compile(code: String, name: String, killIdField: String, evalId: Int): PlainEngine.Compiled

Compile code to woven class bytes. Off-tick, and warmUp must already have run — warming here would silently drop its SPLIT api pinning, since the first call is the one that sticks. name becomes the generated class's name, so a fixed one would give every snippet the same class.

Link copied to clipboard
private fun emitBytecode(w: PlainEngine.Warm, targetId: TargetId, frontend: AllModulesFrontendOutput, reporter: BaseDiagnosticsCollector, yieldTypes: Map<Pair<Int, Int>, String>): PlainEngine.Emitted

Resolved FIR through fir2ir and the JVM backend. yieldTypes rides along as one more IR extension — the conversion is where an IrCall is still a call, so it is the only window to fill its arguments.

Link copied to clipboard
fun execute(ok: PlainEngine.Compiled.Ok, scope: ScriptScope, parentLoader: ClassLoader): Any?

Run a compiled snippet and hand back its result: the script body IS the class constructor, so constructing it runs it.

Link copied to clipboard
private fun resultConeType(fir: List<FirFile>): ConeKotlinType?

The result property's resolved type. Two things are read off it that the rendered name cannot carry: flexibility, which the renderer flattens to the upper bound, and value-class-ness.

Link copied to clipboard
private fun resultTypeName(cone: ConeKotlinType?, fallback: String): String

The result type as the frontend saw it: IR flattens a platform type to its upper bound at EVERY level, so patching the rendered string reaches the outermost ? and nothing inside it. The cone still knows.

Link copied to clipboard
private fun scriptClassName(module: IrModuleFragment): String

Only reached when the snippet had no result value, so the lowering left no tag to read the name off. A script's own declarations become members of its class, so the file holds exactly the one.

Link copied to clipboard
private fun valueClassName(cone: ConeKotlinType?, session: FirSession, scriptClass: String): String?

JVM binary name of the result's type when it is a value class, else null — the one case where the field alone cannot report the value it holds. A root package means the SNIPPET declared it, and the script lowering nests those inside scriptClass: a ClassId carries the relative path, never the container.

Link copied to clipboard
fun warmUp(cpFiles: List<File>, parentApiVersion: String? = null)