Skip to main content

Mono Expression Emit

Target directory: Runtime/Mono/Emit/ (from Phase 3) Il2Cpp counterpart: STUBS-IL2CPP.md (build-time stub tables) Constraints: ../MONO.md D3, D6, D7

1. Design principles

At type bind time (after MetaBinding.EnsureBinding, when a Lua closure is first needed), Mono Editor generates a unique Lua C# callback for each public field, property accessor, method, and constructor:

System.Linq.Expressions → Compile() → LuaCSFunction / delegate
→ lua_pushcfunction → write into three tables (method / fieldGetter / fieldSetter)

Unlike Il2Cpp ReducedType signature reuse, Mono does not maintain a global stub hash table; each (Type, MemberInfo, isStatic, isByVal) combo gets one specialized bridge.

2. Failure policy (D3)

CaseBehavior
Signature in Expression-expressible setNormal Compile
Unsupported signature (e.g. open generic method, certain byref combos)Bind-time InvalidOperationException, message includes type and member full names
ForbiddenSilent fallback to Method.Invoke + object[]

Known supported sets are covered by Tests/Lua/**; new signature kinds must extend Emit first, then open binding.

3. Implementation order (Phase 3)

../MONO.md suggests simple → complex by dependency:

StepMemberWrite tableNotes
1Field static/instancegetter → fieldGetterTable; writable → fieldSetterTableExpression or emit IL for FieldInfo.Get/SetValue
2Property no-argSame as Field; call property getter/setter MethodInfoIndexer property → methodTable (dispatch)
3Method single overloadmethodTable direct closureCapture MethodInfo + typed pop/push
4Method multi-overloadmethodTable dispatch closureCalls managed MethodOverloadResolver (align ../marshal/OVERLOAD-RESOLVER.md)
5ConstructorSMT.__callReuse ctor overload grouping
6AliasesExtra keys in methodTable[LuaAlias], zlua.register_method

Run corresponding Lua cases after each step before continuing.

4. Closure shape (D7)

4.1 Forbidden upvalue pattern

Il2Cpp direct method closures use:

  • ClosureKind + TypeBinding* + MethodMarshalCtx*

Mono Emit must not copy MethodMarshalCtx / methodId into Lua upvalues. Specialized bridges should:

  • Directly Compile to (IntPtr L) => int delegates;
  • Hold statically or capture in closures already resolved MethodInfo/FieldInfo and prebuilt pop/push delegates;
  • Overload dispatch captures a read-only candidate array, not runtime string lookup.

4.2 Writing the three tables

After Emit:

// Pseudocode
LuaDll.lua_rawgeti(L, RegistryIndex, binding.StaticTables.MethodTableRef);
LuaDll.lua_pushcfunction(L, compiledDelegate);
LuaDll.lua_setfield(L, -2, methodName);
LuaDll.lua_pop(L, 1);

Aligned with ../metatable/INDEXER-MONO.md: method tables store the closure itself, not a wrapper.

5. this and ByVal / ByObj

Instance method Emit must branch:

Target kindthis source
class ByObjObjectRegistry.PopThis(L, 1)
struct ByValInstanceTarget equivalent: payload pointer / copy
struct ByObjboxed ByObj userdata

Align with Il2Cpp InstanceTarget::GetResolveMethodThisFunc; helpers live in Mt/InstanceTarget.cs (placeholder exists).

6. Marshal and Emit cooperation

Pop/push logic reuses typed APIs in Marshaling/*Marshaling*.cs; do not re-encode spec rules inside Emit:

  • At bind time, build a delegate chain per parameter/return (same idea as Il2Cpp MarshalMetaInfo writers);
  • Hot path must not allocate object[] per call (unless boxing is required by the spec itself).

[LuaMarshalAs]: Emit reads attributes to choose pop/push strategy (align ../../spec/marshal/02-MARSHAL-AS.md.

7. Boundary with GetFunction / Delegate

PathImplementation
Lua→C# membersEmit/ (this doc)
C#→Lua GetFunctionDelegateImpl/ + LuaCallInvoker (not Emit)
Lua function → C# delegateDelegateImpl/ Expression (Phase 4)

Do not store C#→Lua delegate bridge logic under the Emit directory.

8. Suggested layout

Emit/
├── FieldEmitter.cs
├── PropertyEmitter.cs
├── MethodEmitter.cs
├── ConstructorEmitter.cs
├── MemberTableEmitter.cs // Fill three tables + BindCall
├── BridgeMarshaling.cs
├── EmitMethods.cs // MethodInfo cache for Expression
├── ClosurePin.cs
└── EmitException.cs

Namespace: ZLua.Emit. Finer overload (better-member) and OverloadDispatchEmitter may split later.

9. Acceptance

  • Unsupported signatures at bind → EmitException; no silent Invoke
  • Three-table members match Il2Cpp member set (same type public API)
  • Hot path has no Method.Invoke, no object[] arg arrays (Field uses FieldInfo.Get/SetValue)
  • Overload selects same overload as Il2Cpp (currently arity MVP)
  • MetaBinding / closures have no methodId upvalue
  • Tests/Lua/manifest.lua Phase 3 cases all green