Problem
When local functions in top-level statements reference outer byte[] variables, the C# compiler generates closure structs (display classes). The transpiler encounters stfld/ldfld on these compiler-generated types and throws TranspileException.
Closures are not supported. The compiler generated a closure struct
'<>c__DisplayClass0_0' capturing variable(s): collision_top_left, ...
This blocks the crypto sample (PR #211) which has 7 local functions capturing ~120 byte[] arrays.
Expected behavior
The transpiler should handle closure structs by recognizing that captured byte[] arrays are ROM data tables (same as top-level locals) and captured scalar variables map to zero-page addresses. The ldfld/stfld on the display class should resolve to the same addresses as the original variables.
Workaround
Inline all local functions that capture variables, eliminating closures entirely. This works but loses code structure and causes duplication for functions called from multiple sites (e.g., isBlocked has 9 call sites).
Implementation
- In
DetectStructLayouts(), recognize compiler-generated display classes (<>c__DisplayClass*) as closure structs rather than throwing
- Map each captured field back to its original variable's transpiler allocation (ROM table address for
byte[], zero-page address for scalars)
- Emit
ldfld/stfld on closure fields as regular LDA/STA to the mapped addresses
- Handle the implicit
this parameter (ldarg.0 + ldfld pattern) that the compiler uses to access captured variables inside local functions
Context
Problem
When local functions in top-level statements reference outer
byte[]variables, the C# compiler generates closure structs (display classes). The transpiler encountersstfld/ldfldon these compiler-generated types and throwsTranspileException.This blocks the crypto sample (PR #211) which has 7 local functions capturing ~120
byte[]arrays.Expected behavior
The transpiler should handle closure structs by recognizing that captured
byte[]arrays are ROM data tables (same as top-level locals) and captured scalar variables map to zero-page addresses. Theldfld/stfldon the display class should resolve to the same addresses as the original variables.Workaround
Inline all local functions that capture variables, eliminating closures entirely. This works but loses code structure and causes duplication for functions called from multiple sites (e.g.,
isBlockedhas 9 call sites).Implementation
DetectStructLayouts(), recognize compiler-generated display classes (<>c__DisplayClass*) as closure structs rather than throwingbyte[], zero-page address for scalars)ldfld/stfldon closure fields as regularLDA/STAto the mapped addressesthisparameter (ldarg.0 + ldfld pattern) that the compiler uses to access captured variables inside local functionsContext