Internals

IronRust compiles RustScript bytecode into a CLR assembly and executes a generated IPdVmProgram. The WinForms path adds metadata-derived CLR bindings, object handles, callable RSS functions, and a main-form application session on the Runner's STA thread.

Components

ComponentResponsibility
PdVm.RunnerCLI entry point for source compilation, VMBC compilation, assembly loading, and execution.
PdVm.Compiler.PdVmDotNetSourceCompilerBuilds typed modules for reachable CLR imports and drives source-to-CLR compilation.
PdVm.Compiler.PdVmNativeCompilerCalls the bundled native RustScript compiler and returns VMBC bytes.
PdVm.Compiler.PdVmVmbcReaderDecodes VMBC into PdVmProgramModel.
PdVm.Compiler.PdVmStackAnalyzerComputes reachable operand-stack depths before IL emission.
PdVm.Compiler.PdVmClrCompilerEmits and saves the generated CLR program assembly.
PdVm.Runtime.PdVmProgramBaseStores resumable program state, real call frames, callable values, and the serialized callback queue.
PdVm.Runtime.IPdVmCallableProgramExposes exported and runtime callable values, managed callback creation, reset, and shutdown.
PdVm.Runtime.PdVmScriptCallbackConverts managed event arguments, posts RSS callable invocations, and optionally schedules them on a synchronization context.
PdVm.Runtime.PdVmAssemblyLoaderLoads the generated assembly and creates its concrete IPdVmProgram.
PdVm.Runtime.PdVmExecutionDrives RunStep, instruction budgets, yield, and asynchronous host completion.
PdVm.Runtime.PdVmDotNetHostDecodes exact CLR binding descriptors, converts values, and invokes CLR members.
PdVm.Runtime.PdVmWinFormsApplicationAttaches a callable program to the current STA context, owns callback resources, registers the main form, and runs its message loop.
PdVm.Runtime.PdVmWinFormsEventLoopBinds WinForms events directly to RSS callable values and tracks close permission.

Source-to-assembly pipeline

PdVmDotNetSourceCompiler.CompileFile performs the following steps:

1. Resolve the entry source and source root, and reject an entry outside that root. 2. Recursively scan reachable RustScript modules for concrete use System::... imports. 3. Resolve imported CLR types from loaded/runtime assemblies and managed DLLs found in the source tree, Runner directory, or current working directory. 4. Reflect supported public constructors, methods, and instance properties from each imported type. 5. Copy the .rss source tree to a temporary overlay and write generated typed modules into reserved System/.../*.rss paths. 6. Call PdVmNativeCompiler.CompileFile on the overlay to obtain VMBC bytes. 7. Decode the bytes with PdVmVmbcReader.ReadBytes and replace synthetic generated imports with encoded CLR binding descriptors. 8. Call PdVmClrCompiler.Compile to emit the final assembly. 9. Copy PdVm.Runtime.dll and required external managed assemblies beside the program DLL. 10. Remove the temporary overlay unless KeepTemporaryFiles is enabled through the managed API.

The generated module has a declaration for a synthetic host import and a public wrapper with the name visible to RustScript. The synthetic name is based on the first 64 bits of a SHA-256 hash of the encoded binding descriptor.

Metadata-derived CLR bindings

A binding descriptor records:

  • CLR assembly full name
  • module version ID (MVID)
  • declaring type full name
  • member kind and member name
  • exact CLR parameter type identities
  • exact CLR return type identity

At runtime, PdVmDotNetHost.Call decodes the descriptor, loads the named assembly, verifies its MVID, resolves the exact constructor, method, or property, converts arguments, and invokes that member. Typed interop is therefore metadata-precise dispatch; generated RustScript does not fall back to best-overload selection.

The compiler also emits a real CLR assembly reference for every imported CLR type. This keeps the generated assembly's metadata dependency explicit even though member dispatch passes through versioned descriptors.

The dynamic name-based host path is separate and disabled by default. --enable-dynamic-dotnet enables that experimental path for import names such as System::Object::Call; the typed WinForms example does not require it.

Supported metadata shapes

The current metadata scanner exposes:

  • public constructors on instantiable types
  • public static and instance methods that are non-generic and are not special-name accessors
  • public non-indexed instance property getters and setters
  • a generated release function for supported reference types

Generic members, out parameters, pointers, by-reference types, and byref-like types are omitted.

Schema conversion is defined by TryGetSchema:

CLR shapeRustScript schema
voidnull
string, char, enumstring
boolbool
byte[]bytes
float, double, decimalfloat
supported one-dimensional array[element]
integral values and reference objectsint

For reference objects, the int is an IronRust handle rather than a CLR address.

Generated CLR program

PdVmClrCompiler first runs PdVmStackAnalyzer.Analyze, then uses PersistedAssemblyBuilder and System.Reflection.Emit to create a public sealed type derived from PdVmProgramBase.

The assembly contains:

  • static PdVmValue[] constants
  • static PdVmHostImport[] imports
  • one shared runtime local-value array
  • a generated RunStep(IPdVmHost, int) method

The VMBC instruction stream is decoded during compilation and is not embedded as an interpreter loop. Reachable VMBC offsets become CLR labels. Backward jumps become CLR branches inside the generated method.

When type-map information is available, integer, float, boolean, and string arithmetic or comparison can lower to CLR opcodes. Supported builtins can become direct calls to focused PdVmBuiltins methods. Dynamic operations use PdVmOps.

The operand stack normally lives in generated CLR locals. IronRust materializes it into PdVmProgramBase state at host-call, halt, yield/wait, resume, and instruction-budget boundaries so execution can return and later continue at the stored instruction pointer.

Loading and execution

For a saved DLL, PdVmAssemblyLoader.LoadProgram registers the output directory for dependency resolution, loads the assembly, finds a non-abstract parameterless type implementing IPdVmProgram, and creates it.

For direct .rss execution, the Runner compiles to a temporary DLL and calls PdVmAssemblyLoader.CreateProgram on an in-memory assembly. Both paths then create the console host and register PdVmDotNetHost.Call as fallback host dispatch.

For non-WinForms profiles, PdVmExecution.RunAsync repeatedly calls RunStep until the program halts. A waiting status is completed through IAsyncPdVmHost.WaitAsync, then passed back with ResumePending. --max-steps supplies the total instruction limit, while checks inside generated CLR code enforce the remaining budget during a single RunStep call.

The WinForms profile requires a callable VMBC v10 program implementing IPdVmCallableProgram. The Runner sets a callback error observer, attaches PdVmWinFormsApplication, executes top-level RSS setup with PdVmExecution.Run, then calls RunMessageLoop when Ui::Show registered a main form.

CLR values and object handles

PdVmDotNetHost converts scalar CLR results directly to PdVmValue. Other CLR objects receive monotonically increasing integer handles kept in two maps:

  • handle to object
  • object identity to existing handle

Returning the same CLR object reuses its handle. Generated instance calls pass the handle first; PdVmDotNetHost resolves it and verifies that the object is assignable to the descriptor's declaring type.

A generated release call removes both mappings. If the CLR object implements IDisposable, release also calls Dispose.

WinForms threading and events

Program.Main carries [STAThread] and initializes WinForms before entering the Runner. PdVmWinFormsApplication.Attach records the callable program and host in an application session bound to that thread. Ui::Show(form) registers one main form; after top-level RSS setup completes, RunMessageLoop invokes Application.Run(form) on the same thread.

Every BindClick, BindShown, BindClosing, pointer, and timer binding accepts an RSS callable value. The application session converts that value into a PdVmScriptCallback, schedules it on a ControlSynchronizationContext backed by the form's BeginInvoke, and tracks both callback and event subscription for disposal.

The CLR event handler calls Post and returns without running RustScript inside the managed event stack. PdVmProgramBase serializes callback work, restores the callable's function or closure environment, executes it through the generated program, and reports asynchronous failures through CallbackErrorObserver.

BindClosing installs a delegate matching the form's FormClosing event. Until an explicit close is allowed, the handler sets the event argument's Cancel property and posts the bound RSS callable. Ui::Close sets AllowClose and invokes the form's Close method from that callback.

This division keeps window creation and application behavior in examples/dotnet-typed-winforms.rss; the C# layer supplies callable adaptation, UI-thread scheduling, dialog invocation, pointer payload conversion, timer subscriptions, and close coordination.