Skip to main content

Bridge functions — performance & code size

Nature: architecture and size-model analysis; measurements TBD. Core question: is bridge code one entry per member or stubs reused by signature? How indirection affects ns/call and .so/.dll size.


1. Analysis axes

AxisQuestion
Generation granularityPer method/field bridge vs ReducedType / signature-hash reuse
Call costIndirect jumps, dispatch tables, shared marshal writers
Binary sizeGenerated file totals, symbol counts; linear in members or sublinear in signatures
EditorMono Emit count vs Il2Cpp shared stubs
StrippingDo unused types / signatures enter the Player package?

2. How the four solutions generate bridges

2.1 xLua

ItemNotes
GranularityOne C# Wrap set per type; member-level C# statics (MonoPInvokeCallback)
Lua→C#Wrap inlines or calls LuaDLL; at least one C# entry per exported member
C#→LuaDelegateBridge etc.; templates by signature but still C#-heavy
SizeRoughly linear in exported member count; Generate whitelist controllable
PlayerWrap compiled into Il2Cpp; no C++ stub reuse
Type T with M members → O(M) Wrap functions (C#)

2.2 toLua / tolua#

ItemNotes
GranularityPer exported type *Wrap.cs; member-level tolua bind functions
ModelLike xLua: members × wrappers
SizeLarger export list → more Wrap
PerfVia C# Wrap + lua binding layer; indirection similar to xLua

2.3 SLua

ItemNotes
GranularityAuto-bind generation; mainly member-level wrappers
Size / perfLike toLua; depends on export config

2.4 ZLua

BackendGranularityNotes
Il2Cpp PlayerSignature stub reuseCodegen emits C++ MethodBridge / PropertyBridge / DelegateBridge by ReducedType (arg+return marshal shape); many members share one stub
Mono EditorOne Emit per memberExpression.Compile → lua_pushcfunction into three tables; not in Player package
Il2Cpp: Type T with M members, S distinct ReducedTypes → O(S) stubs, S ≤ M
Mono: M members → M Emit bridges (dev-time specialization; more direct calls)

See impl/codegen/, impl/MONO.md D6.


3. ReducedType & stub reuse (ZLua Il2Cpp)

ReducedType abstracts: ignore member names; keep only marshal shape (e.g. int,int→int, userdata→void).

Member examplesMay share
Add(int,int):intstub A
Sub(int,int):intstub A (same shape)
GetX():intstub B
set_x(int):voidstub C

Benefits:

  • Player binary: size ~ O(signature kinds) not O(member count).
  • icache: hot-path stub bodies stay small; can inline lua API + methodPointer.

Costs:

  • Stub entry needs methodId / dispatch metadata (one table lookup, ~0–50 ns — see PERFORMANCE.md).
  • Codegen + metadata tables are more complex than xLua Generate.

4. Size model (estimate)

Let:

  • W_wrap = average xLua/toLua per-member Wrap size (incl. P/Invoke attrs etc.)
  • W_stub = ZLua per ReducedType stub size
  • M = member count
  • S = distinct ReducedType count (often S << M for large APIs)
SolutionBridge code volume (order)
xLua / toLua / SLua≈ M × W_wrap (by exported members)
ZLua Il2Cpp≈ S × W_stub + metadata tables + generated/ registration
ZLua Mono (Editor only)M × Emit dynamic methods (not counted in Player)

Example (estimate only): a type with 200 methods, 80 signature shapes:

  • xLua: 200 Wrap functions
  • ZLua Player: ~80 C++ stubs + dispatch tables

TBD: for the same assembly, count Generate .cs lines vs ZLua generated/*.cpp symbols.


5. Performance: stub indirection vs specialized direct call

ModelCall pathSizePer call
xLua per-member WrapSpecialized C# + LuaDLL × NLargeMid–high (LuaDLL)
ZLua shared stubC++ stub + methodId dispatchSmallLow–mid (fewer crossings)
ZLua Mono per-member EmitSpecialized C# → direct callLarge in EditorLowest (dev-time)

Conclusion (theory):

  • Il2Cpp: one stub indirection is usually far cheaper than xLua’s many LuaDLL costs (see PERFORMANCE.md).
  • Mono Emit: per-member direct calls help Editor profiling; semantics match Player stubs but implementations differ.

6. Field / property bridges

SolutionRead int field
xLuaWrap → getter or property
ZLua Il2CppPropertyBridge / offset getter; can share “read int field” stub
ZLua MonoPer-field Emit getter/setter

Field fast paths are one of ZLua’s size + perf wins (fewer Wrap, direct memory reads).


7. C#→Lua: Delegate bridge

SolutionModel
xLuaC# DelegateBridge + many LuaDLL calls
ZLuaGetFunction<T> binds function → closed delegate at runtime; Invoke via Delegate bridge (Mono / Il2Cpp paths differ; semantics match)

See spec/01-HOST-API.md and guides/csharp-calling-lua.md.


8. Stripping & linking

SolutionUnused APIs
xLuaTypes not Generated are not in the package
toLua / SLuaUnexported are not in the package
ZLuaLazy Bind; unaccessed types may not register stubs, but Il2Cpp still links assembly metadata; stub tables follow Codegen input scope

Migration: when leaving xLua whitelists for ZLua, don’t assume “uncalled types cost nothing” — understand Codegen inputs (typically test assemblies + game assembly public APIs).


9. vs HybridCLR / other approaches

TrackBridge shape
HybridCLR etc.Change execution model; bridge ideas can reuse signature sharing
xLuaMember-level C# Wrap; mature and controllable
ZLuaIl2Cpp embed + ReducedType; Player-extreme oriented

ZLua is not a HybridCLR replacement; the bridge layer is analogous to “native stub table + methodPointer”.


10. Reproducible size measurement (suggested)

  1. xLua: after Generate, count .cs lines under Wrap/ and IL size (dotnet ilspy / ildasm).
  2. ZLua: count .cpp/.h under libil2cpp/zlua/generated/ and MarshalBindings table entries.
  3. Player: compare libil2cpp.so / GameAssembly.dll size before/after link (control for bridge-only differences).

Fill results below (TBD):

MetricxLuatoLuaSLuaZLua Il2Cpp
Bridge sources total KBTBDTBDTBDTBD
Distinct stub countN/AN/AN/ATBD
Player so deltaTBDTBDTBDTBD

DocContent
PERFORMANCE.mdns/call and indirection
impl/codegen/STUBS-IL2CPP.mdStub type inventory
impl/codegen/EMIT-MONO.mdMono Emit
FEATURES.mdGeneration & whitelist differences

Size and stub counts pending measured supplements.