Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
2798aa9
v3: add direct fastc backend
medvednikov Aug 20, 2026
b21338c
v3: add full fastc backend
medvednikov Aug 22, 2026
06ab85d
v3: validate direct fastc output
medvednikov Aug 22, 2026
258ca9d
v3: preserve fastc language semantics
medvednikov Aug 22, 2026
b5dfa43
v3: preserve fastc literal semantics
medvednikov Aug 22, 2026
3d99015
v3: preserve fastc expression types
medvednikov Aug 22, 2026
4c06d76
v3: promote fastc indexing expressions
medvednikov Aug 22, 2026
6c5caae
v3: preserve fastc escape and backend semantics
medvednikov Aug 22, 2026
e4ad36b
v3: reject unsafe fastc shifts and options
medvednikov Aug 22, 2026
88af6f5
v3: promote strict and sizeof fastc builds
medvednikov Aug 22, 2026
5428bf8
v3: preserve fastc pointer-map iteration
medvednikov Aug 22, 2026
290f5b7
v3: preserve fastc scalar edge semantics
medvednikov Aug 22, 2026
416324b
v3: preserve fastc minimum int inference
medvednikov Aug 22, 2026
16870c1
v3: guard fastc loop minimum int inference
medvednikov Aug 22, 2026
9141bde
v3: preserve fastc hexadecimal int inference
medvednikov Aug 22, 2026
cd5de7b
v3: promote fastc parallel assignments
medvednikov Aug 22, 2026
c8c34a4
v3: promote fastc composite minimum ints
medvednikov Aug 22, 2026
f44ca9a
v3: tighten fastc direct inference
medvednikov Aug 22, 2026
6cea2fe
v3: detect wrapped fastc NUL escapes
medvednikov Aug 22, 2026
191ac4d
v3: decode fastc string continuations
medvednikov Aug 22, 2026
e51232b
v3: address fastc review feedback
medvednikov Aug 22, 2026
4df9862
v3: preserve fastc escape and no-main semantics
medvednikov Aug 22, 2026
c19a293
v3: preserve fastc pointer and stdout semantics
medvednikov Aug 22, 2026
ed8ab55
v3: preserve fastc operator precedence
medvednikov Aug 22, 2026
5c39cb9
v3: preserve fastc rune printing
medvednikov Aug 22, 2026
4dcd274
cmd: allow fastc target OS options
medvednikov Aug 22, 2026
f9810de
Merge master into fastc-full-v3-backend
medvednikov Aug 22, 2026
ecdaffc
v3: keep fastc AST-free
medvednikov Aug 22, 2026
9b9c848
v3: reject unresolved fastc names
medvednikov Aug 22, 2026
2460024
v3: harden fastc control flow and literals
medvednikov Aug 22, 2026
d7de568
v3: address fastc review feedback
medvednikov Aug 22, 2026
ed082fe
v3: validate more fastc expression types
medvednikov Aug 22, 2026
57f482c
v3: harden fastc expression validation
medvednikov Aug 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions cmd/v/macos_v3_args.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@ fn macos_v3_non_compilation_command(command string) bool {
// macos_v3_force_requested reports whether `-new-compiler` should hand this
// invocation to the embedded V3 compiler. It gates on `-old-compiler`
// precedence, options/modes V3 cannot honor yet, and whether the command is an
// actual compilation command (never `test`, external tools, or the `cmd/v` /
// `vlib/v3/v3.v` bootstrap). Both the Darwin dispatcher (where it overrides the
// default heuristic) and the non-macOS dispatcher (where it is the sole gate)
// rely on it, so it must stay platform neutral.
// actual compilation command (never `test` or external tools). Compiler bootstrap
// targets normally stay on V1, but explicit `-b fastc` owns those targets too: its
// complete lane uses V3's checked frontend and full fastc generator for the compiler source.
// Both the Darwin dispatcher (where it overrides the default heuristic) and the
// non-macOS dispatcher (where it is the sole gate) rely on it, so it must stay
// platform neutral.
@[markused]
fn macos_v3_force_requested(command string, prefs &pref.Preferences) bool {
if !prefs.new_compiler || prefs.old_compiler {
Expand All @@ -34,23 +36,47 @@ fn macos_v3_force_requested(command string, prefs &pref.Preferences) bool {
if v3_has_v1_only_preferences(prefs) || (prefs.gc_set_by_flag && prefs.gc_mode != .no_gc) {
return false
}
if prefs.autofree && prefs.is_run {
if prefs.autofree && prefs.is_run && !macos_v3_fastc_requested(prefs) {
// V1 still owns the established `v -autofree run ...` orchestration.
// FastC promotes autofree programs to its checked C lane.
return false
}
if prefs.path == '' || command == 'test' || macos_v3_non_compilation_command(command)
|| command in external_tools {
return false
}
normalized_path := prefs.path.replace('\\', '/').trim_right('/')
if normalized_path == 'cmd/v' || normalized_path.starts_with('cmd/v/')
compiler_bootstrap := normalized_path == 'cmd/v' || normalized_path.starts_with('cmd/v/')
|| normalized_path.contains('/cmd/v/') || normalized_path.ends_with('/cmd/v')
|| normalized_path == 'vlib/v3/v3.v' || normalized_path.ends_with('/vlib/v3/v3.v') {
|| normalized_path == 'vlib/v3/v3.v' || normalized_path.ends_with('/vlib/v3/v3.v')
if compiler_bootstrap && !macos_v3_fastc_requested(prefs) {
return false
}
return command in ['run', 'build'] || prefs.is_script || os.is_dir(prefs.path)
|| normalized_path.ends_with('.v') || normalized_path.ends_with('.vsh')
}

fn macos_v3_fastc_requested(prefs &pref.Preferences) bool {
mut selected_backend := ''
for option in prefs.build_options {
parts := option.fields()
if parts.len == 2 && parts[0] in ['-b', '-backend'] {
selected_backend = parts[1]
}
Comment thread
medvednikov marked this conversation as resolved.
Outdated
}
Comment thread
medvednikov marked this conversation as resolved.
return selected_backend == 'fastc'
}

// macos_v3_test_ownership_uses_v1 keeps ownership/autofree test binaries on
// V1. vtest marks its per-file compilations with `-skip-running`; compiling an
// autofree test through the ownership-enabled V3 tool can consume far more
// memory than the test itself. Direct V3 ownership builds remain available;
// fastc uses the same checked ownership lane as the C backend.
fn macos_v3_test_ownership_uses_v1(prefs &pref.Preferences, args []string) bool {
return prefs.skip_running && !macos_v3_fastc_requested(prefs)
&& (prefs.autofree || '-ownership' in args)
}

// These helpers are shared by the native Darwin dispatcher and the default
// implementation selected while generating cross-platform VC sources, so this
// file has to stay platform neutral (no `_darwin.c.v` suffix). Keep them outside
Expand Down
13 changes: 11 additions & 2 deletions cmd/v/macos_v3_darwin.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,11 @@ fn maybe_delegate_to_macos_v3(command string, prefs &pref.Preferences) ?MacosV3C
}
all_args := util.join_env_vflags_and_os_args()
forwarded_args := all_args[1..]
if !is_macos_v3_default_executable(os.executable()) {
if macos_v3_test_ownership_uses_v1(prefs, forwarded_args) {
trace_macos_v3_skip('vtest ownership/autofree compilation')
return take_macos_v3_c_error_report()
}
if !macos_v3_executable_can_dispatch(os.executable(), prefs) {
trace_macos_v3_skip('non-default compiler executable `${os.executable()}`')
return none
}
Expand All @@ -66,6 +70,10 @@ fn is_macos_v3_default_executable(vexe string) bool {
return os.base(vexe) in ['v', 'v.exe', 'vnew', 'vnew.exe']
}

fn macos_v3_executable_can_dispatch(vexe string, prefs &pref.Preferences) bool {
return is_macos_v3_default_executable(vexe) || macos_v3_fastc_requested(prefs)
}

fn is_macos_v3_relevant_command(command string, prefs &pref.Preferences) bool {
if prefs.old_compiler {
return false
Expand All @@ -76,9 +84,10 @@ fn is_macos_v3_relevant_command(command string, prefs &pref.Preferences) bool {
// dispatch, but it must not prevent V3 from being the default compiler.
return false
}
if prefs.autofree && prefs.is_run {
if prefs.autofree && prefs.is_run && !macos_v3_fastc_requested(prefs) {
// V1 still owns the established `v -autofree run ...` orchestration.
// Direct autofree builds are selected earlier by the ownership dispatcher.
// FastC promotes autofree programs to its checked C lane.
return false
}
if command == 'test' {
Expand Down
4 changes: 2 additions & 2 deletions cmd/v/macos_v3_default.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import v.util
// compiler normally. `-new-compiler` opts into the embedded V3 driver and runs
// it in THIS process, the same way V3 runs by default on macOS. It applies the
// same gating as the Darwin dispatcher: `-old-compiler` wins, V1-only options
// are rejected, and only actual compilation commands are taken over (never
// `fmt`, `version`, `test`, external tools, or the compiler bootstrap).
// are rejected, and only actual compilation commands are taken over. Compiler
// bootstrap targets are included only when the V3-only fastc backend is explicit.
fn maybe_delegate_to_macos_v3(command string, prefs &pref.Preferences) ?MacosV3CErrorReport {
if !prefs.new_compiler || prefs.old_compiler {
// V1 is the default here, and `-old-compiler` takes precedence.
Expand Down
45 changes: 45 additions & 0 deletions cmd/v/macos_v3_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -1573,6 +1573,10 @@ fn test_macos_v3_default_executable_excludes_temporary_self_hosted_compilers() {
assert !is_macos_v3_default_executable('/tmp/v2')
assert !is_macos_v3_default_executable('/tmp/vstrict1')
assert !is_macos_v3_default_executable('/tmp/vp')
assert !macos_v3_executable_can_dispatch('/tmp/v2', &pref.Preferences{})
assert macos_v3_executable_can_dispatch('/tmp/v2', &pref.Preferences{
build_options: ['-b fastc']
})
}
}

Expand Down Expand Up @@ -1687,3 +1691,44 @@ fn test_macos_v3_new_compiler_routing_and_precedence() {
path: 'main.v'
})
}

fn test_macos_v3_fastc_routes_compiler_selfhost_targets() {
for target in ['cmd/v', 'cmd/v/v.v', 'vlib/v3/v3.v'] {
assert macos_v3_force_requested('build', &pref.Preferences{
new_compiler: true
path: target
build_options: ['-b fastc']
})
assert !macos_v3_force_requested('build', &pref.Preferences{
new_compiler: true
path: target
build_options: ['-b c']
})
}
assert macos_v3_fastc_requested(&pref.Preferences{
build_options: ['-backend fastc']
})
assert !macos_v3_fastc_requested(&pref.Preferences{
build_options: ['-b fastc', '-b c']
})
assert macos_v3_force_requested('run', &pref.Preferences{
new_compiler: true
autofree: true
is_run: true
path: 'main.v'
build_options: ['-b fastc']
})
}

fn test_macos_v3_vtest_ownership_modes_use_v1_except_fastc() {
mut prefs := &pref.Preferences{
skip_running: true
autofree: true
}
assert macos_v3_test_ownership_uses_v1(prefs, ['-skip-running', '-autofree', 'main.v'])
prefs.autofree = false
assert macos_v3_test_ownership_uses_v1(prefs, ['-skip-running', '-ownership', 'main.v'])
prefs.build_options = ['-b fastc']
assert !macos_v3_test_ownership_uses_v1(prefs, ['-skip-running', '-ownership', '-b',
'fastc', 'main.v'])
}
5 changes: 5 additions & 0 deletions cmd/v/v.v
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,11 @@ fn invoke_help_and_exit(remaining []string) {
fn maybe_delegate_to_ownership(command string, prefs &pref.Preferences, merged_args []string) {
is_ownership := '-ownership' in merged_args
is_autofree := prefs.autofree
$if macos {
if macos_v3_test_ownership_uses_v1(prefs, merged_args) {
return
}
}
if !ownership_delegation_is_requested(is_ownership, is_autofree, prefs.old_compiler,
os.user_os()) {
return
Expand Down
9 changes: 7 additions & 2 deletions vlib/v/pref/pref.v
Original file line number Diff line number Diff line change
Expand Up @@ -1131,7 +1131,12 @@ fn parse_args_impl(known_external_commands []string, args []string, show_output
sbackend := cmdline.option(args[i..], arg, 'c')
res.build_options << '${arg} ${sbackend}'
b := backend_from_string(sbackend) or {
eprintln_exit('Unknown V backend: ${sbackend}\nValid -backend choices are: c, js, js_node, js_browser, js_freestanding, wasm')
eprintln_exit('Unknown V backend: ${sbackend}\nValid -backend choices are: c, fastc, js, js_node, js_browser, js_freestanding, wasm')
Comment thread
medvednikov marked this conversation as resolved.
}
if sbackend == 'fastc' {
// FastC belongs to the embedded V3 driver. Keep V1's backend enum on C
// solely so cmd/v can finish parsing and forward the original arguments.
res.new_compiler = true
Comment thread
medvednikov marked this conversation as resolved.
Outdated
}
if b == .wasm {
res.compile_defines << 'wasm'
Expand Down Expand Up @@ -1439,7 +1444,7 @@ pub fn backend_from_string(s string) !Backend {
// TODO: unify the "different js backend" options into a single `-b js`
// + a separate option, to choose the wanted JS output.
return match s {
'c' { .c }
'c', 'fastc' { .c }
'eval', 'interpret' { eprintln_exit('The eval backend has been removed.') }
'js', 'js_node' { .js_node }
'js_browser' { .js_browser }
Expand Down
10 changes: 10 additions & 0 deletions vlib/v/pref/pref_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -553,6 +553,16 @@ fn test_new_compiler_flag_is_accepted() {
assert '-new-compiler' !in prefs.build_options
}

fn test_fastc_backend_selects_v3_driver() {
target := os.join_path(vroot, 'examples', 'hello_world.v')
prefs, command := pref.parse_args_and_show_errors([], ['-b', 'fastc', target], false)
assert command == target
assert prefs.backend == .c
assert prefs.backend_set_by_flag
assert prefs.new_compiler
assert prefs.build_options.contains('-b fastc')
}

fn test_v3_checker_fixture_flag_is_accepted() {
target := os.join_path(vroot, 'examples', 'hello_world.v')
for flag in ['-checker-fixture', '-macos-v3-compat-c99'] {
Expand Down
45 changes: 40 additions & 5 deletions vlib/v3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
Clean rewrite of the V compiler. Reuses v2's scanner, uses a flat AST parser
with Pratt parsing, a structured type system with sum-type variants, lexical
scoping, a transformer for AST simplification, a shared type-checking phase, a
markused pass for dead-code elimination, recursive import resolution, and three
backends: a direct flat-AST-to-C backend, a native ARM64 backend via SSA IR with
a built-in linker, and a direct flat-AST-to-WebAssembly backend. With `-prod`,
the ARM64 backend runs SSA optimization, MIR lowering, and instruction
selection.
markused pass for dead-code elimination, recursive import resolution, and
backends: a direct flat-AST-to-C backend, a scanner-to-C fast path, a native
ARM64 backend via SSA IR with a built-in linker, and a direct
flat-AST-to-WebAssembly backend. With `-prod`, the ARM64 backend runs SSA
optimization, MIR lowering, and instruction selection.

Imports all `vlib/builtin/` V source files, both pure V (`.v`) and C-interop
(`.c.v`), for struct, enum, type alias, interface, C function declarations, and
Expand Down Expand Up @@ -88,6 +88,38 @@ Stage rows recorded at pipeline boundaries report sampled peak RSS and the proce
breakdowns reconstructed after a stage omit the sampled peak. On macOS each row also prints
physical footprint immediately after RSS.

## Fast C backend

`-b fastc` selects the embedded V3 driver and a speculative single-file backend for the shortest
edit-run cycle. It scans the source once and emits GNU C while consuming tokens. This path does
not create a flat AST itself. Before accepting its speculative output, the driver runs the normal
parser and semantic checker over the V source, then asks bundled TinyCC to validate the complete
translation unit. A successful direct build skips transform, type annotation, and mark-used.

FastC's direct lane currently emits primitive functions and parameters, inferred local
declarations, ordinary expressions, `if`/`else`, and condition, C-style, infinite, and
integer-range `for` loops. GNU
`typeof` carries `:=` declarations into C without V type inference. Unsupported syntax promotes
the source to fastc's complete lane, which uses the parser, checker, transformer, and mark-used
pass, then emits C with its own `v3.gen.fastc.FlatGen` backend. That backend is a full fork of the
V3 C generator rather than an alias or a runtime switch to `v3.gen.c`. A TinyCC error is also
discarded and the original V source is compiled by the checked fastc lane, so the user receives V
parser or type-checker diagnostics rather than a speculative C diagnostic. The complete lane has
the same language and ownership/autofree coverage as the C backend. A successfully compiled `run`
program keeps its exit status and is never retried.

The direct path is limited to host-target, non-production, non-test, non-shared single-file builds.
Compiler/self-host and other non-direct modes enter the complete lane before source scanning.
`-o file.c` emits the standalone fast C translation unit when the direct lane supports the input;
otherwise it emits the complete `v3.gen.fastc` translation unit. Direct C-only output is published
only after both V semantic checking and TinyCC validation succeed.

`v self -b fastc` and direct compiler builds such as `v -b fastc -o v2 cmd/v` are routed to V3.
Self-host builds enter fastc's complete lane directly. The checked frontend feeds the independent
`v3.gen.fastc.FlatGen` implementation, so the resulting compiler supports the full V3 source tree
and retains both fastc lanes for user programs, including when its output has a custom filename in
the V checkout. The fastc integration test exercises five successive self-host generations.

Generated C represents `thread` values with a typed wrapper around `pthread_t`. `spawn` and
detached standard-library workers use the target's default thread stack (8 MiB on 64-bit targets
and 2 MiB on 32-bit targets); `-thread-stack-size <bytes>` overrides it. Thread allocation,
Expand Down Expand Up @@ -169,6 +201,9 @@ the plan and run the complete diagnostic and generation pipeline normally.
## Architecture

```
source -> fastc scanner/emitter -> TinyCC
\-> on unsupported syntax or TinyCC error: normal pipeline below

source + vlib/builtin -> scanner -> flat parser -> flat AST -> imports
-> check -> transform -> annotate types -> markused -> gen C -> cc
\-> SSA build -> ARM64 gen -> link
Expand Down
Loading
Loading