Common Lisp Argument Passing in CLRHack
The CLRHack engine translates the dynamic, flexible argument-passing semantics of Common Lisp into the static, strongly-typed environment of the .NET Common Language Runtime (CLR). It achieves this through a combination of CIL method overloading, sentinel-based defaulting, and runtime list construction.
1. The Overloading Architecture
Since CIL methods have a fixed arity (number of arguments), but Common Lisp functions support variable arguments, the compiler generates multiple entry points for every defined function.
- Public Overloads: For every function, the compiler generates a set of
public staticmethods (typically from 0 to 8 arguments). These serve as the "API" for both direct calls and closure invocation. - The Body Method: The actual logic of the Lisp function is compiled into a single
private staticmethod named[FunctionName]_Body. All valid public overloads normalize their arguments and delegate to this method. - Arity Enforcement: Overloads representing invalid argument counts (e.g., calling a 2-arg function
with 5 args) are generated to throw a
Lisp.WrongNumberOfArgumentsException.
2. Parameter Type Implementation
Required Parameters
Required parameters are the simplest. They map directly to the leading object arguments in both the public
overloads and the internal body method.
Optional Parameters (&optional)
Handling &optional involves a "Sentinel Pattern":
- Sentinels: If a caller uses an overload that provides fewer than the maximum number of optional
arguments, the compiler passes a special global constant:
Lisp.Undefined::Value. - Late Defaulting: Inside the
_Bodymethod, the engine generates CIL code to check if the argument isEQto the Undefined sentinel. If the check passes, the code evaluates the Lisp default expression and stores the result back into the parameter usingstarg. - Supplied-p: If the Lisp code defines a "supplied-p" variable, an extra boolean parameter is added
to the
_Bodymethod, which is set totrueorfalsebased on the presence of the argument in the specific overload.
Rest Parameters (&rest)
Rest parameters are handled by runtime list construction:
- In the public overloads, any arguments provided beyond the required/optional count are bundled into a linked list
using
Lisp.List/ListCell. - The
_Bodymethod receives this list as a singleobjectargument.
Keyword Parameters (&key)
Keyword parameters are implemented as a transformation over the &rest mechanism:
- Trailing arguments are gathered into a "rest" list.
- The
_Bodymethod defines local variables for every keyword parameter, initialized to their Lisp default values. - The Keyword Loop: At the start of the function, the engine emits a loop that scans the rest list
in pairs. It uses
Object.Equalsto compare each key against the pre-interned keyword symbols (e.g.,:TEST). When a match is found, the corresponding local variable is updated with the value following the key.
3. The Calling Mechanism
Direct Calls
When the compiler identifies a call to a known defun in the same assembly, it emits a direct
call to the public overload that exactly matches the number of provided arguments. This provides near-native
performance for fixed-arity calls.
Indirect Calls (Closures & Funcall)
All Lisp functions are instances of the Lisp.Closure class. This class provides virtual
Invoke methods. When a closure is created, it captures its environment and provides overrides for these
Invoke methods that jump into the appropriate Program static overloads.
4. Multiple Return Values
Because .NET methods return only one value, CLRHack uses a Thread-Static Side-Channel in the
Lisp.Values class:
- The first value is returned normally as the method's return value.
- Additional values are stored in
[ThreadStatic]fields (Value1,Value2, ... up toValue63). - A
ReturnCountfield is updated to tell the caller how many values are waiting in the buffer.
Example CIL Generation
; Lisp: (defun add-optional (x &optional (y 5)) (+ x y)) ; Overload for 1 arg .method public static object 'ADD-OPTIONAL'(object x) { ldarg 0 ldsfld object [LispBase]Lisp.Undefined::Value tail. call object Program::'ADD-OPTIONAL_Body'(object, object) ret } ; The Body Method .method private static object 'ADD-OPTIONAL_Body'(object x, object y) { ; Defaulting logic for Y ldarg 1 ldsfld object [LispBase]Lisp.Undefined::Value bne.un SKIP_DEFAULT ldc.i4 5 box int32 starg 1 SKIP_DEFAULT: ; ... rest of function ... }
No comments:
Post a Comment