Skip to content
Open
Show file tree
Hide file tree
Changes from 31 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
48 changes: 41 additions & 7 deletions cmd/v/macos_v3_args.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ fn is_macos_v3_compiler_bootstrap(normalized_path 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` still routes to V3 so its
// AST-free parser can report unsupported source instead of silently selecting V1.
// 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 @@ -55,23 +57,55 @@ 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.
// Explicit FastC stays on V3 and reports this mode as unsupported.
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')
|| is_macos_v3_compiler_bootstrap(normalized_path) {
|| is_macos_v3_compiler_bootstrap(normalized_path)
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 {
return prefs.is_fastc
}

// macos_v3_fastc_incompatibility reports why an explicit FastC selection cannot
// be honored, so dispatchers fail instead of silently continuing through V1.
fn macos_v3_fastc_incompatibility(prefs &pref.Preferences) ?string {
if !prefs.is_fastc {
return none
}
if prefs.gc_set_by_flag && prefs.gc_mode != .no_gc {
return '`-b fastc` only supports `-gc none`; remove the explicit collector or select `-b c`.'
}
if v3_has_v1_only_preferences(prefs) {
return '`-b fastc` cannot be combined with an option that is only supported by the V1 compiler.'
}
Comment thread
medvednikov marked this conversation as resolved.
return none
}

// 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. Explicit FastC is never diverted to an AST-based
// ownership compiler; its parser reports the unsupported mode itself.
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
17 changes: 15 additions & 2 deletions cmd/v/macos_v3_darwin.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,17 @@ fn maybe_delegate_to_macos_v3(command string, prefs &pref.Preferences) ?MacosV3C
}
return take_macos_v3_c_error_report()
}
if message := macos_v3_fastc_incompatibility(prefs) {
eprintln(message)
exit(1)
}
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 +74,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 +88,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.
// Explicit FastC stays on V3 and reports this mode as unsupported.
return false
}
if command == 'test' {
Expand Down
8 changes: 6 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 All @@ -19,6 +19,10 @@ fn maybe_delegate_to_macos_v3(command string, prefs &pref.Preferences) ?MacosV3C
eprintln('`-new-compiler` requires a build that embeds the V3 compiler, which this one does not.')
exit(1)
}
if message := macos_v3_fastc_incompatibility(prefs) {
eprintln(message)
exit(1)
}
raw_args := util.join_env_vflags_and_os_args()[1..]
if macos_v3_has_v1_only_leading_option(raw_args, command) {
eprintln('`-new-compiler` cannot be combined with a V1-only option; drop `-new-compiler` or the option.')
Expand Down
93 changes: 93 additions & 0 deletions cmd/v/macos_v3_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -1615,6 +1615,11 @@ 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']
is_fastc: true
})
}
}

Expand Down Expand Up @@ -1729,3 +1734,91 @@ 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']
is_fastc: true
})
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']
is_fastc: true
})
assert !macos_v3_fastc_requested(&pref.Preferences{
build_options: ['-b fastc', '-b c']
})
repeated_backends, _ := pref.parse_args_and_show_errors([], ['-b', 'fastc', '-b', 'c', '-b',
'fastc', 'cmd/v'], false)
assert macos_v3_fastc_requested(repeated_backends)
assert macos_v3_force_requested('run', &pref.Preferences{
new_compiler: true
autofree: true
is_run: true
path: 'main.v'
build_options: ['-b fastc']
is_fastc: true
})
}

fn test_macos_v3_fastc_rejects_incompatible_preferences() {
fastc_boehm, _ := pref.parse_args_and_show_errors([],
['-b', 'fastc', '-gc', 'boehm', 'main.v'], false)
message := macos_v3_fastc_incompatibility(fastc_boehm) or {
assert false, 'expected explicit FastC with Boehm GC to be rejected'
return
}
assert message.contains('`-b fastc` only supports `-gc none`')

overridden, _ := pref.parse_args_and_show_errors([], ['-b', 'fastc', '-gc', 'boehm', '-b',
'c', 'main.v'], false)
assert macos_v3_fastc_incompatibility(overridden) == none
}

fn test_macos_v3_fastc_allows_explicit_target_os() {
fastc_linux, _ := pref.parse_args_and_show_errors([],
['-b', 'fastc', '-os', 'linux', 'main.v'], false)
assert fastc_linux.backend == .c
assert fastc_linux.is_fastc
assert fastc_linux.os == .linux
assert !v3_has_v1_only_preferences(fastc_linux)
assert macos_v3_fastc_incompatibility(fastc_linux) == none
assert macos_v3_force_requested('build', fastc_linux)

fastc_cross_c, _ := pref.parse_args_and_show_errors([], ['-b', 'fastc', '-os', 'windows', '-o',
'main.c', 'main.v'], false)
assert fastc_cross_c.backend == .c
assert fastc_cross_c.is_fastc
assert fastc_cross_c.os == .windows
assert fastc_cross_c.out_name.ends_with('main.c')
assert !v3_has_v1_only_preferences(fastc_cross_c)
assert macos_v3_fastc_incompatibility(fastc_cross_c) == none
assert macos_v3_force_requested('build', fastc_cross_c)

standard_linux, _ := pref.parse_args_and_show_errors([], ['-b', 'c', '-os', 'linux', 'main.v'],
false)
assert !standard_linux.is_fastc
assert v3_has_v1_only_preferences(standard_linux)
}

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']
prefs.is_fastc = true
assert !macos_v3_test_ownership_uses_v1(prefs, ['-skip-running', '-ownership', '-b', 'fastc',
'main.v'])
}
12 changes: 11 additions & 1 deletion cmd/v/v.v
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,16 @@ 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 prefs.is_fastc {
// FastC owns its whole invocation and must never launch the AST-based
// ownership compiler. Its direct parser reports unsupported modes.
return
}
$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 Expand Up @@ -288,7 +298,7 @@ fn v3_has_v1_only_preferences(prefs &pref.Preferences) bool {
|| prefs.c_error_bug_report_url.len > 0 || prefs.wasm_validate
|| prefs.wasm_stack_top != 1024 + (16 * 1024) || prefs.line_info.len > 0
|| prefs.use_coroutines || prefs.checker_match_exhaustive_cutoff_limit != 12
|| (prefs.backend == .c && prefs.os !in [._auto, .macos])
|| (prefs.backend == .c && !prefs.is_fastc && prefs.os !in [._auto, .macos])
|| prefs.build_options.any(it.starts_with('-debug-tcc')) || prefs.is_musl
|| prefs.build_options.any(it in ['-musl', '-glibc']) || !prefs.relaxed_gcc14 {
return true
Expand Down
1 change: 1 addition & 0 deletions vlib/v/help/build/build.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ NB: the build flags are shared with the run command too:
Specifies the backend that will be used for building the executable.
Current list of supported backends:
* `c` (default) - V outputs C source code, which is then passed to a C compiler.
* `fastc` - V parses supported source directly to C without an AST.
* `go` - V outputs Go source code, which is then passed to a Go compiler.
* `js` - V outputs JS source code which can be passed to NodeJS to be ran.
* `js_browser` - V outputs JS source code ready for the browser.
Expand Down
6 changes: 6 additions & 0 deletions vlib/v/help/help_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ fn test_run_topic_mentions_conditional_cleanup() {
assert res.output.contains('If the executable already existed before the command')
}

fn test_build_topic_lists_fastc_backend() {
res := os.execute(vexe + ' help build')
assert res.exit_code == 0, res.output
assert res.output.contains('* `fastc`'), res.output
}

fn test_all_topics() {
help_dir := os.join_path(@VEXEROOT, 'vlib', 'v', 'help')
topic_paths := os.walk_ext(help_dir, '.txt')
Expand Down
13 changes: 11 additions & 2 deletions vlib/v/pref/pref.v
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ pub mut:
os OS // the OS to compile for
backend Backend
backend_set_by_flag bool // true when the compiler receives `-b`/`-backend`
is_fastc bool // true when the final `-b`/`-backend` option selects fastc
build_mode BuildMode
arch Arch
output_mode OutputMode = .stdout
Expand Down Expand Up @@ -457,6 +458,7 @@ fn parse_args_impl(known_external_commands []string, args []string, show_output
mut no_skip_unused := false
mut command, mut command_idx := '', 0
mut build_vsh_source := false
mut new_compiler_set_by_flag := false
for i := 0; i < args.len; i++ {
arg := args[i]
if pass_external_command_args && command_idx < i && command in known_external_commands {
Expand Down Expand Up @@ -563,6 +565,7 @@ fn parse_args_impl(known_external_commands []string, args []string, show_output
}
'-new-compiler' {
res.new_compiler = true
new_compiler_set_by_flag = true
}
'-checker-fixture', '-macos-v3-compat-c99' {
// Passed through to the embedded V3 diagnostic fixture runner.
Expand Down Expand Up @@ -1129,9 +1132,10 @@ fn parse_args_impl(known_external_commands []string, args []string, show_output
}
'-b', '-backend' {
sbackend := cmdline.option(args[i..], arg, 'c')
res.is_fastc = sbackend == 'fastc'
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 b == .wasm {
res.compile_defines << 'wasm'
Expand Down Expand Up @@ -1386,6 +1390,11 @@ fn parse_args_impl(known_external_commands []string, args []string, show_output
}
res.build_options = m.keys()
// eprintln('>> res.build_options: ${res.build_options}')
// FastC belongs to the embedded V3 driver, but both `fastc` and `c` use
// Backend.c while cmd/v parses the command line. Only the final backend
// option should select V3 implicitly; an explicit -new-compiler remains an
// independent request.
res.new_compiler = new_compiler_set_by_flag || res.is_fastc
Comment thread
medvednikov marked this conversation as resolved.
res.fill_with_defaults()
if res.generate_c_project != '' {
// The generated C project should not depend on cached V module objects.
Expand Down Expand Up @@ -1439,7 +1448,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
Loading
Loading