Skip to content

feat(cl): automatic detection of forwarding macros - #1110

Merged
xushiwei merged 11 commits into
mainfrom
fennoai/issue-1109-1793030693
Oct 27, 2026
Merged

xushiwei merged 11 commits into
mainfrom
fennoai/issue-1109-1793030693

Conversation

@fennoai

@fennoai fennoai Bot commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Requested by @xushiwei

Closes #1109.

Implements automatic detection of forwarding macros, as proposed in #1109. Function-like macros whose body is a single plain forwarding call are recognized on their own, their reference function is inferred, and they are emitted as ordinary Go functions — no C wrapper and no configuration.

What it does

A function-like macro not listed in MacroFunc is turned into a Go function when all of these hold:

  1. Single forwarding call — the body is one call whose callee is a plain identifier (optionally parenthesized). Conditionals, comma expressions, member access, nested calls, and casts around the whole call are not detected.
  2. Generated callee — the callee is a non-variadic function llcppg parsed and generated (inferred from the body; the user never names it).
  3. Params or constants — every argument is exactly one macro parameter, or a constant: a value llcppg can evaluate (literals, constant macros such as ZLIB_VERSION, casts/arithmetic over them), or sizeof(T) over a complete generated type (optionally scalar-cast).
  4. Each parameter once — every macro parameter is passed exactly once (avoids double-evaluation differences).

Macros that fail any rule are silently skipped (logged in verbose mode) and can still be handled explicitly via MacroFunc. A macro listed in MacroFunc is always wrapped and never reinterpreted.

Semantics

  • Parameter types come from the callee's prototype; the macro's own parameter names are kept.
  • Return type is the callee's (void → no result, bare call).
  • Platform-independent constants are evaluated and emitted inline; strings become c.Str("...").
  • sizeof(T) is routed through unsafe.Sizeof(T{}) (converted to the cast type or the callee's parameter type) so the size is computed by the Go compiler per target platform. Incomplete/opaque types (which llcppg would emit as empty Go structs) are rejected, so such macros are skipped.
  • Name collisions with an existing generated symbol are skipped with a warning.
  • The generated function calls the callee's own Go declaration directly, matching whether it was generated as a package function or a method.

Implementation

  • cl/macrofunc_auto.go — detection driver, argument classification, and Go function generation.
  • cl/macrofunc_auto_parse.go — token-level parsing of the macro body (forwarding-call shape, sizeof, constant/type resolution); no preprocessor expansion, so the rules stay syntactic.
  • Detection is scheduled into a dedicated pass that runs after every callee has been compiled, so each callee's generated Go declaration is available to call.
  • MacroFunc doc comments in cl and tool updated.

Tests

New fixture cl/_testc/macrofn_auto covers: method and package-function callees, void return, string/numeric/constant-macro/sizeof arguments, callee-doc fallback, and the skip paths (parameter used twice, non-single-call body, sizeof over an incomplete type). The explicit MacroFunc path (z_gzgetc → C wrapper) is exercised alongside, confirming explicit entries are not reinterpreted.

Full llgo test ./cl/... ./tool/... passes; the generated output type-checks.

Add always-on automatic detection for function-like macros whose body is a
single plain forwarding call to a generated function. Such a macro is turned
into an ordinary Go function that calls the callee directly, filling in the
constant arguments, with no C wrapper and no //go:linkname.

A macro qualifies when: its body is one call to a non-variadic function llcppg
generates (the callee, inferred from the body); every argument is either one of
the macro's own parameters or a compile-time constant (a value llcppg evaluates,
or sizeof(T) over a complete generated type, optionally scalar-cast); and every
macro parameter is passed exactly once. Macros that fail any rule are silently
skipped (logged in verbose mode) and can still be handled explicitly. A macro
listed in MacroFunc is always wrapped and never reinterpreted by detection.

Parameter types come from the callee prototype, the return type is the callee's,
constants are evaluated and emitted inline (c.Str for strings), and sizeof(T) is
routed through unsafe.Sizeof(T{}) so the size is computed per target platform.
Name collisions with existing symbols are skipped with a warning.

See #1109.
@codecov

codecov Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.38579% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.48%. Comparing base (eeb7e96) to head (7e713c2).

Files with missing lines Patch % Lines
cl/macrofunc.go 88.76% 10 Missing ⚠️
cl/eval_expr.go 93.33% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1110      +/-   ##
==========================================
+ Coverage   90.26%   90.48%   +0.21%     
==========================================
  Files          26       26              
  Lines        3278     3437     +159     
==========================================
+ Hits         2959     3110     +151     
- Misses        319      327       +8     
Flag Coverage Δ
llgo-tests 90.48% <92.38%> (+0.21%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is a well-structured and thoroughly documented feature. The deferral design (running the autoMacros pass after compiles so every callee's Go declaration is already recorded) is the right fix for the callee-availability ordering, the token-level parsing is largely defensive, and the test fixture covers the important accept/reject cases (sizeof, opaque/zero-field type, double-use, conditional body). Restricting compiledFns recording to Cursor_FunctionDecl to exclude the synthetic macro-wrapper overload, and the unsafe.Sizeof workaround for the llgo toolchain, are both subtle points that are well commented.

A few findings below are worth addressing before merge — one confirmed resource leak, one confirmed nil-dereference panic path, and a method-callee robustness gap — plus some minor cleanups. All findings are code-quality / robustness / doc-accuracy; I did not find a behavioral bug in the happy-path generation itself.

Note: I could not run go test ./cl/... in this environment because the package links against the llgo toolchain (the same reason the unsafe.Sizeof eager-evaluation workaround exists), so findings are from static analysis of the diff and surrounding code.

Comment thread cl/macrofunc_auto.go Outdated
Comment thread cl/macrofunc_auto_parse.go Outdated
Comment thread cl/macrofunc_auto.go Outdated
Comment thread cl/macrofunc_auto_parse.go Outdated
Comment thread cl/macrofunc_auto.go Outdated
Comment thread cl/macrofunc_auto_parse.go Outdated
@xushiwei
xushiwei merged commit d336e57 into main Oct 27, 2026
4 checks passed
@fennoai
fennoai Bot deleted the fennoai/issue-1109-1793030693 branch October 27, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: MacroFunc Automatic Detection of Forwarding Macros

1 participant