Skip to content

v2: add target-specific cleanc generation controls#27290

Merged
medvednikov merged 10 commits into
vlang:masterfrom
GGRei:v2-c-target-specific-codegen-27270-upstream
May 31, 2026
Merged

v2: add target-specific cleanc generation controls#27290
medvednikov merged 10 commits into
vlang:masterfrom
GGRei:v2-c-target-specific-codegen-27270-upstream

Conversation

@GGRei
Copy link
Copy Markdown
Contributor

@GGRei GGRei commented May 30, 2026

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

  • Host-targeted C generation by default, without requiring -os.
  • Explicit OS override support with -os linux, -os macos, or -os windows.
  • -os cross support for generated C that keeps target-specific $if branches and C directives available through preprocessor checks.
  • -freestanding support for C output that does not assume a normal OS runtime.
  • Pure freestanding output with -freestanding -os none, where no concrete OS target is selected.
  • Freestanding hook controls with -fhooks for platform-provided output, panic, and allocation functions.
  • Clear diagnostics when generated C needs a runtime/platform hook that was not enabled.
  • Builder behavior that avoids compiling output that is only meant to be generated C, such as freestanding, OS-less freestanding, or non-host target output.

How to use it

To generate C for a specific OS target:

-v2 -os linux app.v
-v2 -os macos app.v
-v2 -os windows app.v

To generate cross-target C that keeps platform branches in the generated source:

-v2 -os cross app.v

In this mode, $if linux, $if macos, $if windows, and similar C directives are preserved as C preprocessor guards where possible.

Current limitation: -os cross does not yet merge separate OS-suffixed V source files such as foo_linux.c.v, foo_windows.c.v, and foo_macos.c.v into 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:

-v2 -freestanding app.v

In that form, -freestanding changes 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 -freestanding with -os:

-v2 -freestanding -os linux app.v

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:

-v2 -freestanding -os none app.v

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:

-v2 -freestanding -fhooks output app.v
-v2 -freestanding -fhooks panic app.v
-v2 -freestanding -fhooks alloc app.v

These can be combined when needed:

-v2 -freestanding -fhooks output,panic,alloc -os linux app.v
-v2 -freestanding -fhooks output,panic,alloc -os none app.v

Use -b only 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:

  • target OS detection and override behavior,
  • -os cross,
  • -freestanding,
  • -freestanding -os none,
  • freestanding hook diagnostics,
  • generated C preprocessor directives,
  • builder behavior for compile vs generate-only paths,
  • and usage examples under vlib/v2/tests/target_codegen_example.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment thread vlib/v2/pref/pref.v
Comment on lines +226 to +228
if p.is_cross_target() {
return normalize_target_os_name(os.user_os())
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

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.

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 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread vlib/v2/gen/cleanc/cleanc.v Outdated
Comment on lines +2637 to +2638
for stmt in file.stmts {
if stmt is ast.FnDecl {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread vlib/v2/gen/cleanc/cheaders.v Outdated
Comment on lines +263 to +265
return (g.pref != unsafe { nil } && g.pref.is_freestanding())
|| target_os in ['freestanding', 'bare']
|| g.has_target_define('freestanding')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread vlib/v2/builder/builder.v Outdated
Comment on lines +399 to +403
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]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@GGRei GGRei marked this pull request as draft May 30, 2026 15:52
@GGRei GGRei marked this pull request as ready for review May 30, 2026 16:19
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment thread vlib/v2/gen/cleanc/cleanc.v Outdated
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);')
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread vlib/v2/builder/builder.v Outdated
Comment on lines +699 to +705
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)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread vlib/v2/builder/builder.v Outdated
mut diagnostics := []string{}
mut seen := map[string]bool{}
target_os := b.pref.target_os_or_host()
if 'prealloc' in b.pref.user_defines {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@GGRei GGRei marked this pull request as draft May 30, 2026 16:32
@GGRei GGRei marked this pull request as ready for review May 30, 2026 16:52
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment thread vlib/v2/gen/cleanc/cleanc.v Outdated
Comment on lines +2758 to +2760
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)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread vlib/v2/builder/builder.v Outdated
Comment on lines +833 to +835
ast.PostfixExpr {
freestanding_restricted_call_in_expr(expr.expr, ctx)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@GGRei GGRei marked this pull request as draft May 30, 2026 17:06
@GGRei GGRei marked this pull request as ready for review May 30, 2026 19:02
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment thread vlib/v2/pref/pref.v Outdated
Comment on lines +254 to +255
'linux', 'macos', 'windows', 'cross', 'none', 'freebsd', 'openbsd', 'netbsd', 'dragonfly',
'android', 'ios', 'solaris', 'qnx', 'serenity', 'plan9', 'vinix' {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@GGRei GGRei marked this pull request as draft May 30, 2026 19:14
@GGRei GGRei marked this pull request as ready for review May 30, 2026 19:27
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment thread vlib/v2/pref/comptime.v
Comment on lines 17 to +18
'linux' {
return normalize_current_os_name(pref.target_os_or_host()) == 'linux'
return pref.normalized_target_os() == 'linux'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@GGRei GGRei marked this pull request as draft May 30, 2026 19:38
@GGRei GGRei marked this pull request as ready for review May 30, 2026 20:16
@GGRei
Copy link
Copy Markdown
Contributor Author

GGRei commented May 30, 2026

@codex review

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 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".

Comment thread vlib/v2/pref/pref.v
Comment on lines +227 to +228
if p.is_cross_target() {
return normalize_target_os_name(os.user_os())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

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.

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.

Comment thread vlib/v2/pref/comptime.v
Comment on lines +26 to +29
'cross',
'none',
'freestanding',
'bare',
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

@GGRei GGRei May 30, 2026

Choose a reason for hiding this comment

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

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..

@GGRei GGRei marked this pull request as draft May 30, 2026 20:41
@GGRei GGRei marked this pull request as ready for review May 30, 2026 20:42
@GGRei
Copy link
Copy Markdown
Contributor Author

GGRei commented May 30, 2026

Ready

@medvednikov
Copy link
Copy Markdown
Member

I pushed a big flat ast fix, so will need to fix 2 conflicts here.

@GGRei GGRei marked this pull request as draft May 31, 2026 05:25
@GGRei GGRei marked this pull request as ready for review May 31, 2026 05:25
@GGRei
Copy link
Copy Markdown
Contributor Author

GGRei commented May 31, 2026

Conflict resolved. Codex Review accepted the latest changes.

Nice work on the big V2 update

@medvednikov
Copy link
Copy Markdown
Member

Lots of freestanding logic in builder needs to be moved to builder/freestanding.v

I'm not a fan of ~40 new freestanding_* functions that manually re-walk the AST to flag "restricted" calls. heuristic helpers like freestanding_print_arg_is_obvious_non_string, freestanding_expr_is_provably_numeric_no_alloc, freestanding_array_init_needs_alloc. This is a fragile, hand-rolled static analyzer duplicating traversal the type checker already does, living in the builder.

@GGRei
Copy link
Copy Markdown
Contributor Author

GGRei commented May 31, 2026

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 --skip-builtin / --skip-type-check where the normal runtime/type-checking paths are reduced.

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.

@GGRei
Copy link
Copy Markdown
Contributor Author

GGRei commented May 31, 2026

@medvednikov : I’ll wait for #27303 to be merged before reworking this PR, to avoid resolving the same conflicts twice.

@medvednikov
Copy link
Copy Markdown
Member

@GGRei merged, thanks.

@GGRei GGRei marked this pull request as draft May 31, 2026 19:03
@GGRei GGRei marked this pull request as ready for review May 31, 2026 19:07
@GGRei
Copy link
Copy Markdown
Contributor Author

GGRei commented May 31, 2026

The freestanding build contract logic has been moved out of builder.v into vlib/v2/builder/freestanding.v, leaving builder.v as orchestration only.

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.

@medvednikov medvednikov merged commit 10a27a3 into vlang:master May 31, 2026
71 of 83 checks passed
@medvednikov
Copy link
Copy Markdown
Member

@GGRei always run cmd/v2 $ sh test_all.sh to make sure all V2 tests pass. You probably do already, just in case.

@GGRei
Copy link
Copy Markdown
Contributor Author

GGRei commented May 31, 2026

@medvednikov Noted. I do run it sometimes, but admittedly not every time.

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.

OS-specific .c code of empty .v project

2 participants