v2: add target-specific cleanc generation controls#27290
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e3f84d1026
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if p.is_cross_target() { | ||
| return normalize_target_os_name(os.user_os()) | ||
| } |
There was a problem hiding this comment.
Preserve cross target filtering instead of host filtering
When -os cross is selected, this returns the host OS, so parse_files() and cache dependency collection only include host-suffixed files. On a Linux host, for example, a module with foo_linux.c.v and foo_windows.c.v will generate “cross” C from the Linux file and never see the Windows implementation/directives, even though the new cross target is meant to keep generated C portable. This makes the output depend on the build host and can produce C that is missing non-host target branches.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is a current limitation and is already scoped out of this PR.
-os cross keeps C target branches portable, but it does not yet merge separate OS-suffixed V source files like foo_linux.c.v, foo_windows.c.v, and foo_macos.c.v into one C output.
Source parsing/type checking still run once with the host-compatible source set, full OS-split V support needs a separate multi-target source pipeline.
| none | ||
| } | ||
| } | ||
| ast.PostfixExpr { |
There was a problem hiding this comment.
Preserve optional custom-flag semantics in cross guards
For $if name ? this stringifies the condition as just name, so in -os cross mode an optional custom flag whose name is also an OS flag is reinterpreted as a target guard. For example, without -d linux, $if linux ? { #include <x.h> } should be inactive, but this becomes #if defined(__linux__) in the generated cross C and includes the block on Linux targets. The ? needs to be preserved or represented so optional user flags do not become platform conditions.
Useful? React with 👍 / 👎.
| for stmt in file.stmts { | ||
| if stmt is ast.FnDecl { |
There was a problem hiding this comment.
Detect live functions inside active comptime blocks
This pre-scan only checks top-level FnDecl statements before deciding whether to emit the __v_live_init declaration and call. A live function declared inside an active top-level $if block is represented under a comptime expression rather than as a direct FnDecl, so pass 5 can still generate the live wrapper but main will not call __v_live_init. Programs that gate @[live] functions with platform or feature $if blocks therefore compile without starting the reloader.
Useful? React with 👍 / 👎.
| return (g.pref != unsafe { nil } && g.pref.is_freestanding()) | ||
| || target_os in ['freestanding', 'bare'] | ||
| || g.has_target_define('freestanding') |
There was a problem hiding this comment.
Do not let
-d freestanding switch codegen modes
Because this treats a user-defined comptime flag named freestanding as the platform contract, a normal hosted build using -d freestanding now enters freestanding C generation even though pref.freestanding is false and the freestanding validation/local-C-only paths are not enabled. That can remove hosted headers and replace allocations with missing-hook static assertions for projects that were only using freestanding as a feature flag; the codegen mode should be driven by -freestanding, not by the user define that is kept for $if compatibility.
Useful? React with 👍 / 👎.
| fn freestanding_print_arg_is_obvious_non_string(expr ast.Expr) bool { | ||
| return match expr { | ||
| ast.BasicLiteral { | ||
| expr.kind in [.number, .char, .key_true, .key_false] | ||
| } |
There was a problem hiding this comment.
Reject all non-string output-hook prints before C-only success
This validation only recognizes literal numbers/bools/chars as non-string print arguments. With -freestanding -fhooks output --skip-builtin --skip-type-check, a program like x := 1; println(x) passes the contract check; freestanding targets then just write a .c file and exit successfully, even though the generated C contains the later missing-format static assertion. The check needs to use type information or conservatively reject unknown print argument types so unsupported output-hook programs fail at the V invocation instead of producing unusable C with exit code 0.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 55cecf1a8d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if g.has_freestanding_hook_capability('output') { | ||
| g.sb.writeln('\t\tisize written = v_platform_write(fd, ptr, remaining_bytes);') | ||
| } else { | ||
| g.sb.writeln('\t\tisize written = write(fd, ptr, remaining_bytes);') |
There was a problem hiding this comment.
Use a Windows-compatible write fallback
When targeting Windows with builtins skipped, a program that calls print/println relies on this weak _write_buf_to_fd fallback, but the new Windows preambles deliberately omit POSIX headers and include only Windows headers. Emitting write(fd, ...) here therefore produces generated C that is not valid for the -os windows --skip-builtin path; this branch needs a Windows implementation such as _write/WriteFile or a target-specific guard.
Useful? React with 👍 / 👎.
| ast.InfixExpr { | ||
| lhs_call := freestanding_restricted_call_in_expr(expr.lhs, ctx) | ||
| if lhs_call != '' { | ||
| lhs_call | ||
| } else { | ||
| freestanding_restricted_call_in_expr(expr.rhs, ctx) | ||
| } |
There was a problem hiding this comment.
Reject string concatenation without alloc hook
With freestanding hooks that omit alloc, a string concatenation such as s := prefix + 'x' reaches this InfixExpr branch and only scans the operands. The transformer later lowers that expression to string__plus(...), which requires string allocation/runtime support, but -freestanding --skip-builtin --skip-type-check -fhooks output,panic will still write C and exit successfully; require the alloc hook or reject string concatenation here so the freestanding contract fails at the V invocation.
Useful? React with 👍 / 👎.
| mut diagnostics := []string{} | ||
| mut seen := map[string]bool{} | ||
| target_os := b.pref.target_os_or_host() | ||
| if 'prealloc' in b.pref.user_defines { |
There was a problem hiding this comment.
Check the prealloc mode flag instead of user defines
When a freestanding build uses a normal custom define named prealloc (for example -freestanding -d prealloc --skip-builtin --skip-type-check), this condition reports the -prealloc error even though pref.prealloc is false and the prealloc allocator mode is not enabled. The validation should key off the actual -prealloc preference rather than the user define list, otherwise unrelated feature flags can make otherwise valid freestanding C-only builds fail before codegen.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 695293e453
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| need_f64_strg_fallback := g.runtime_fallback_needed_in_source('f64__strg', generated_csrc) | ||
| need_f32_str_fallback := g.runtime_fallback_needed_in_source('f32__str', generated_csrc) | ||
| need_f32_strg_fallback := g.runtime_fallback_needed_in_source('f32__strg', generated_csrc) |
There was a problem hiding this comment.
Emit dependent float fallback with f32 strg
When the generated source references f32__strg but not f64__strg directly, this snapshot-based gating emits the new f32__strg fallback while leaving need_f64_strg_fallback false. The emitted fallback body calls f64__strg((f64)x), so builds that rely on these missing-runtime fallbacks can now fail later with an undefined f64__strg; make the f64 fallback depend on the f32 strg fallback as well.
Useful? React with 👍 / 👎.
| ast.PostfixExpr { | ||
| freestanding_restricted_call_in_expr(expr.expr, ctx) | ||
| } |
There was a problem hiding this comment.
Reject bang unwraps without a panic hook
In freestanding C-only builds without panic, a result/option bang unwrap such as x := fallible()! reaches this branch and only scans the wrapped call. The transformer/codegen emits a v_panic(...) path for the unwrap failure, but emit_missing_runtime_fallbacks only provides v_panic when the panic hook is present, so -freestanding --skip-builtin can successfully write C with an unresolved panic path instead of failing the freestanding contract.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 358c9990d3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| 'linux', 'macos', 'windows', 'cross', 'none', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', | ||
| 'android', 'ios', 'solaris', 'qnx', 'serenity', 'plan9', 'vinix' { |
There was a problem hiding this comment.
Accept the Termux target in -os validation
When users pass -os termux, this validation now reports an unknown target even though the source filter explicitly supports _termux.c.v files. That makes the new target-selection path unable to build Termux-specific variants from non-Termux hosts; include termux in the accepted target OS list (and matching diagnostics) so those suffixes can actually be selected.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 31b1042211
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| 'linux' { | ||
| return normalize_current_os_name(pref.target_os_or_host()) == 'linux' | ||
| return pref.normalized_target_os() == 'linux' |
There was a problem hiding this comment.
Preserve optional custom-flag semantics in comptime evaluation
When compiling for a concrete target, a source guard like $if linux ? { ... } still reaches this helper through the parser/transformer PostfixExpr handling, so this branch returns true solely because -os linux was selected even when the user did not pass -d linux. Fresh evidence after the earlier directive-only fix is that the normal comptime evaluator still delegates optional ? checks to comptime_flag_value; optional custom flags need to be evaluated against user_defines only, otherwise target builds can incorrectly include feature-gated code.
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e180723224
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if p.is_cross_target() { | ||
| return normalize_target_os_name(os.user_os()) |
There was a problem hiding this comment.
Avoid host-specific file filtering for cross C
When -os cross is used on a non-Windows host, this returns the host OS for source filtering, so the build parses host-specific files such as vlib/builtin/closure/closure_nix.c.v. Those files emit unguarded POSIX/pthread code, while the new cross preamble only includes pthread headers under #if !defined(_WIN32), so the generated “cross” C can fail when compiled on Windows and also varies by the machine that generated it. The cross target should filter sources in a target-neutral way or avoid including host-only _nix/_linux implementations in portable output.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is the same known limitation already scoped out here: -os cross preserves C-level target branches, but it does not yet provide a multi-target V source pipeline for OS-suffixed implementations.
Full source-level cross merging is a separate design task.
| 'cross', | ||
| 'none', | ||
| 'freestanding', | ||
| 'bare', |
There was a problem hiding this comment.
Preserve
-d bare as a user flag
Adding bare to the reserved target/mode list makes bare ? false unless another code path explicitly maps it to freestanding, but this commit never synthesizes or sets a bare define. Existing users can still pass -d bare, and plain $if bare/#flag bare are treated as freestanding-only elsewhere, so code that uses the optional form ($if bare ?) will now silently take the wrong branch even though the flag was explicitly supplied. Either do not reserve bare here, or make -d bare participate in the explicit freestanding/bare mode consistently.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The behavior was already correct here: explicit -d bare is preserved through explicit_user_defines, so bare ? stays true when the user defines it.
I added a small regression test to make that contract explicit..
|
Ready |
|
I pushed a big flat ast fix, so will need to fix 2 conflicts here. |
|
Conflict resolved. Codex Review accepted the latest changes. Nice work on the big V2 update |
|
Lots of freestanding logic in builder needs to be moved to builder/freestanding.v I'm not a fan of ~40 new |
|
The initial placement in builder.v was chosen because this is a build contract check: freestanding output needs to fail before V reports a successful C generation, especially in modes like The goal was to avoid silently emitting C that still depends on hosted runtime output, panic, allocation, or OS services. That said, you are right that the implementation has grown too large for builder.v and needs to be refactored. |
|
@medvednikov : I’ll wait for #27303 to be merged before reworking this PR, to avoid resolving the same conflicts twice. |
|
@GGRei merged, thanks. |
…fic-codegen-27270-upstream
|
The freestanding build contract logic has been moved out of I also reduced the fragile AST-side heuristics and moved runtime/hook dependency diagnostics closer to the cleanc generation path, where the generated C dependencies are actually visible. Codex Review accepted the latest changes. |
|
@GGRei always run |
|
@medvednikov Noted. I do run it sometimes, but admittedly not every time. |
This PR adds target-specific C generation controls to the V2 C backend.
By default, V2 keeps using the host platform as the implicit target, and cleanc remains the default V2 C backend. The new flags are overrides for cases where the user wants generated C for another target, cross-target C, or a freestanding environment.
Fixes #27270
What this adds
-os.-os linux,-os macos, or-os windows.-os crosssupport for generated C that keeps target-specific$ifbranches and C directives available through preprocessor checks.-freestandingsupport for C output that does not assume a normal OS runtime.-freestanding -os none, where no concrete OS target is selected.-fhooksfor platform-provided output, panic, and allocation functions.How to use it
To generate C for a specific OS target:
To generate cross-target C that keeps platform branches in the generated source:
In this mode,
$if linux,$if macos,$if windows, and similar C directives are preserved as C preprocessor guards where possible.Current limitation:
-os crossdoes not yet merge separate OS-suffixed V source files such asfoo_linux.c.v,foo_windows.c.v, andfoo_macos.c.vinto one generated C file. Source parsing and type checking still happen once, using the host-compatible source set. Full support for OS-split V implementations in cross output will need a separate multi-target source pipeline.To generate freestanding C for the current host target:
In that form,
-freestandingchanges the runtime assumptions, while the OS target remains implicit. For example, on a Linux host this generates freestanding C targeted at Linux.To make the target explicit, combine
-freestandingwith-os:This is useful when the embedding platform is still target-specific. For example, a game engine can provide its own platform hooks while still building a Linux, macOS, or Windows variant.
To generate pure freestanding C without selecting any OS target:
In this mode, generated C does not select Linux, macOS, Windows, or any other concrete OS branch. It is intended for OS-agnostic embedding scenarios where the host application provides the platform/runtime layer.
Freestanding hooks can be enabled when the generated code needs platform-provided services:
These can be combined when needed:
Use
-bonly when selecting a non-default backend explicitly.Why
Some V2 C output should not always carry the same cross-platform runtime assumptions. This is useful for smaller target-specific C output, embedded-style use cases, OS-agnostic freestanding environments, cross-target C generation, and future leaner native output paths.
Tests
Added coverage for:
-os cross,-freestanding,-freestanding -os none,vlib/v2/tests/target_codegen_example.