| 1 | // Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | module pref |
| 5 | |
| 6 | import os |
| 7 | import os.cmdline |
| 8 | |
| 9 | pub enum Backend { |
| 10 | v // V source output (default) |
| 11 | eval // AST interpreter |
| 12 | cleanc // Clean C backend (AST -> C) |
| 13 | c // SSA -> C backend |
| 14 | x64 // Native x64/AMD64 backend |
| 15 | arm64 // Native ARM64 backend |
| 16 | } |
| 17 | |
| 18 | pub enum Arch { |
| 19 | auto // Auto-detect based on OS |
| 20 | x64 |
| 21 | arm64 |
| 22 | } |
| 23 | |
| 24 | // GarbageCollectionMode controls which garbage collector is used. |
| 25 | // Translated from Go's runtime GC, the `vgc` mode provides a concurrent |
| 26 | // tri-color mark-and-sweep collector written in pure V. |
| 27 | pub enum GarbageCollectionMode { |
| 28 | no_gc // no garbage collection |
| 29 | vgc // V GC: concurrent tri-color mark-and-sweep (translated from Go's runtime GC) |
| 30 | boehm // Boehm-Demers-Weiser conservative GC (legacy) |
| 31 | } |
| 32 | |
| 33 | pub struct Preferences { |
| 34 | pub mut: |
| 35 | debug bool |
| 36 | verbose bool |
| 37 | ownership bool // -ownership: enable ownership checking for strings |
| 38 | skip_genv bool |
| 39 | skip_builtin bool |
| 40 | skip_imports bool |
| 41 | skip_type_check bool // Skip type checking phase (for backends that don't need it yet) |
| 42 | no_parallel bool // when true, run type check sequentially (default: parallel) |
| 43 | no_parallel_transform bool // when true, run transform sequentially (default: parallel) |
| 44 | no_cache bool // Disable build cache |
| 45 | no_markused bool // Disable markused stage and dead-function pruning |
| 46 | show_cc bool // Print C compiler command(s) |
| 47 | stats bool // Print extended statistics |
| 48 | print_parsed_files bool // Print all parsed files grouped by full/.vh parse mode |
| 49 | keep_c bool // Keep generated C file after compilation |
| 50 | use_context_allocator bool // Use context allocator for heap allocations (enables profiling) |
| 51 | is_shared_lib bool // Compile to shared library (.dylib/.so) for live reload |
| 52 | no_optimize bool = true // skip SSA optimization (mem2reg, phi elimination); cleared by -prod |
| 53 | is_prod bool // -prod: enable SSA optimization + -O3 -flto for C compiler |
| 54 | prealloc bool // -prealloc: use arena allocation (bump-pointer, not thread-safe) |
| 55 | gc_mode GarbageCollectionMode // Garbage collection mode (-gc flag) |
| 56 | backend Backend |
| 57 | arch Arch = .auto |
| 58 | target_os string = os.user_os() |
| 59 | output_cross_c bool // -os cross: keep generated C portable |
| 60 | freestanding bool // -freestanding: target a platform contract without an OS runtime |
| 61 | freestanding_hooks []string // -fhooks: explicit freestanding platform capabilities |
| 62 | macos_tiny bool = true // -no-mos-tiny: disable the automatic macOS x64 tiny object path |
| 63 | output_file string |
| 64 | printfn_list []string // List of function names whose generated C source should be printed |
| 65 | user_defines []string // All active comptime flags, including compiler-synthesized flags. |
| 66 | explicit_user_defines []string // User-defined comptime flags from explicit -d <name> only. |
| 67 | hot_fn string // Extract raw machine code for this function only (hot reload) |
| 68 | single_backend bool // Only include the selected backend (strip other backends from binary) |
| 69 | eval_runtime_args []string // Program argv exposed to the eval backend |
| 70 | ccompiler string // C compiler override (-cc flag) |
| 71 | pub: |
| 72 | vroot string = detect_vroot() |
| 73 | vmodules_path string = os.vmodules_dir() |
| 74 | } |
| 75 | |
| 76 | fn dir_exists(path string) bool { |
| 77 | if path.len == 0 { |
| 78 | return false |
| 79 | } |
| 80 | if os.is_dir(path) { |
| 81 | return true |
| 82 | } |
| 83 | _ := os.ls(path) or { return false } |
| 84 | return true |
| 85 | } |
| 86 | |
| 87 | fn detect_vroot_from(start string) string { |
| 88 | if start.len == 0 { |
| 89 | return '' |
| 90 | } |
| 91 | mut dir := start |
| 92 | // Avoid os.abs_path during bootstrap; normalize relative paths manually. |
| 93 | if !os.is_abs_path(dir) { |
| 94 | cwd := os.getwd() |
| 95 | if cwd.len > 0 { |
| 96 | dir = os.join_path(cwd, dir) |
| 97 | } |
| 98 | } |
| 99 | if dir_exists(os.join_path(dir, 'vlib', 'builtin')) { |
| 100 | return dir |
| 101 | } |
| 102 | dir = os.dir(dir) |
| 103 | if dir_exists(os.join_path(dir, 'vlib', 'builtin')) { |
| 104 | return dir |
| 105 | } |
| 106 | dir = os.dir(dir) |
| 107 | if dir_exists(os.join_path(dir, 'vlib', 'builtin')) { |
| 108 | return dir |
| 109 | } |
| 110 | dir = os.dir(dir) |
| 111 | if dir_exists(os.join_path(dir, 'vlib', 'builtin')) { |
| 112 | return dir |
| 113 | } |
| 114 | dir = os.dir(dir) |
| 115 | if dir_exists(os.join_path(dir, 'vlib', 'builtin')) { |
| 116 | return dir |
| 117 | } |
| 118 | dir = os.dir(dir) |
| 119 | if dir_exists(os.join_path(dir, 'vlib', 'builtin')) { |
| 120 | return dir |
| 121 | } |
| 122 | dir = os.dir(dir) |
| 123 | if dir_exists(os.join_path(dir, 'vlib', 'builtin')) { |
| 124 | return dir |
| 125 | } |
| 126 | dir = os.dir(dir) |
| 127 | if dir_exists(os.join_path(dir, 'vlib', 'builtin')) { |
| 128 | return dir |
| 129 | } |
| 130 | dir = os.dir(dir) |
| 131 | if dir_exists(os.join_path(dir, 'vlib', 'builtin')) { |
| 132 | return dir |
| 133 | } |
| 134 | return '' |
| 135 | } |
| 136 | |
| 137 | fn detect_vroot() string { |
| 138 | // First prefer the compile-time module root when it points to a valid |
| 139 | // V source tree. This keeps self-host binaries stable during bootstrapping. |
| 140 | compile_time_root := @VMODROOT |
| 141 | if compile_time_root.len > 0 { |
| 142 | // Trust @VMODROOT without runtime validation. During bootstrapping |
| 143 | // (v2 → v3 via ARM64), os.join_path/os.is_dir may not work correctly |
| 144 | // in the native binary, but the compile-time root is always correct. |
| 145 | return compile_time_root |
| 146 | } |
| 147 | // Prefer deriving from executable path: <vroot>/cmd/v2/v3. |
| 148 | if os.args.len > 0 && os.args[0].len > 0 { |
| 149 | vroot := detect_vroot_from(os.args[0]) |
| 150 | if vroot.len > 0 { |
| 151 | return vroot |
| 152 | } |
| 153 | } |
| 154 | // Fallback to current directory ancestry. |
| 155 | cwd := os.getwd() |
| 156 | vroot := detect_vroot_from(cwd) |
| 157 | if vroot.len > 0 { |
| 158 | return vroot |
| 159 | } |
| 160 | // Final fallback: keep old behavior for unusual bootstrap contexts. |
| 161 | if os.args.len > 0 && os.args[0].len > 0 { |
| 162 | if os.is_abs_path(os.args[0]) { |
| 163 | return os.dir(os.args[0]) |
| 164 | } |
| 165 | return cwd |
| 166 | } |
| 167 | return cwd |
| 168 | } |
| 169 | |
| 170 | pub fn new_preferences() Preferences { |
| 171 | return Preferences{ |
| 172 | backend: .cleanc |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | pub fn (p &Preferences) target_os_or_host() string { |
| 177 | if p == unsafe { nil } || p.target_os == '' { |
| 178 | return os.user_os() |
| 179 | } |
| 180 | return p.target_os |
| 181 | } |
| 182 | |
| 183 | pub fn (p &Preferences) normalized_target_os() string { |
| 184 | return normalize_target_os_name(p.target_os_or_host()) |
| 185 | } |
| 186 | |
| 187 | pub fn (p &Preferences) is_cross_target() bool { |
| 188 | return p != unsafe { nil } && (p.output_cross_c || p.normalized_target_os() == 'cross') |
| 189 | } |
| 190 | |
| 191 | pub fn (p &Preferences) is_freestanding() bool { |
| 192 | return p != unsafe { nil } && p.freestanding |
| 193 | } |
| 194 | |
| 195 | pub fn (p &Preferences) can_compile_cleanc_locally() bool { |
| 196 | if p == unsafe { nil } { |
| 197 | return true |
| 198 | } |
| 199 | if p.is_freestanding() { |
| 200 | return false |
| 201 | } |
| 202 | if p.is_cross_target() { |
| 203 | return true |
| 204 | } |
| 205 | return p.normalized_target_os() == normalize_target_os_name(os.user_os()) |
| 206 | } |
| 207 | |
| 208 | pub fn (p &Preferences) can_run_target_binary_locally() bool { |
| 209 | if p == unsafe { nil } { |
| 210 | return true |
| 211 | } |
| 212 | if p.is_freestanding() { |
| 213 | return false |
| 214 | } |
| 215 | if p.backend == .cleanc && p.is_cross_target() { |
| 216 | return true |
| 217 | } |
| 218 | if p.normalized_target_os() != normalize_target_os_name(os.user_os()) { |
| 219 | return false |
| 220 | } |
| 221 | return true |
| 222 | } |
| 223 | |
| 224 | pub fn (p &Preferences) source_filter_target_os() string { |
| 225 | if p == unsafe { nil } { |
| 226 | return normalize_target_os_name(os.user_os()) |
| 227 | } |
| 228 | if p.is_cross_target() { |
| 229 | return normalize_target_os_name(os.user_os()) |
| 230 | } |
| 231 | return p.target_os_or_host() |
| 232 | } |
| 233 | |
| 234 | pub fn (p &Preferences) freestanding_hook_list() []string { |
| 235 | if p == unsafe { nil } { |
| 236 | return []string{} |
| 237 | } |
| 238 | return p.freestanding_hooks.clone() |
| 239 | } |
| 240 | |
| 241 | pub fn (p &Preferences) has_freestanding_hooks() bool { |
| 242 | return p != unsafe { nil } && p.freestanding_hooks.len > 0 |
| 243 | } |
| 244 | |
| 245 | pub fn (p &Preferences) has_freestanding_hook(name string) bool { |
| 246 | if p == unsafe { nil } { |
| 247 | return false |
| 248 | } |
| 249 | normalized := normalize_freestanding_hook_name(name) or { return false } |
| 250 | return normalized in p.freestanding_hooks |
| 251 | } |
| 252 | |
| 253 | fn normalize_cli_target_os(target_os string) string { |
| 254 | normalized := normalize_target_os_name(target_os) |
| 255 | match normalized { |
| 256 | 'linux', 'macos', 'windows', 'cross', 'none', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', |
| 257 | 'android', 'termux', 'ios', 'solaris', 'qnx', 'serenity', 'plan9', 'vinix' { |
| 258 | return normalized |
| 259 | } |
| 260 | else { |
| 261 | eprintln('error: unknown target OS `${target_os}`. Valid targets include: linux, macos, windows, cross, none, freebsd, openbsd, netbsd, dragonfly, android, termux, ios, solaris, qnx, serenity, plan9, vinix') |
| 262 | exit(1) |
| 263 | } |
| 264 | } |
| 265 | } |
| 266 | |
| 267 | fn validate_target_contract(target_os string, freestanding bool) { |
| 268 | if target_os == 'none' && !freestanding { |
| 269 | eprintln('error: -os none requires -freestanding') |
| 270 | exit(1) |
| 271 | } |
| 272 | if target_os == 'cross' && freestanding { |
| 273 | eprintln('error: -freestanding -os cross is not supported; use -os none for pure freestanding targets') |
| 274 | exit(1) |
| 275 | } |
| 276 | } |
| 277 | |
| 278 | fn validate_macos_tiny_flag_contract(options []string, target_os string) ! { |
| 279 | if '-no-mos-tiny' in options && target_os != 'macos' { |
| 280 | return error('-no-mos-tiny requires a macOS target; use -os macos or run on a macOS host') |
| 281 | } |
| 282 | } |
| 283 | |
| 284 | fn normalize_freestanding_hook_name(name string) ?string { |
| 285 | normalized := name.trim_space().to_lower() |
| 286 | return match normalized { |
| 287 | 'output', 'panic', 'alloc' { |
| 288 | normalized |
| 289 | } |
| 290 | else { |
| 291 | none |
| 292 | } |
| 293 | } |
| 294 | } |
| 295 | |
| 296 | fn append_freestanding_hook(mut hooks []string, hook string) { |
| 297 | normalized := normalize_freestanding_hook_name(hook) or { |
| 298 | eprintln('error: unknown freestanding hook `${hook}`. Valid hooks: output, panic, alloc, minimal') |
| 299 | exit(1) |
| 300 | } |
| 301 | if normalized !in hooks { |
| 302 | hooks << normalized |
| 303 | } |
| 304 | } |
| 305 | |
| 306 | fn parse_freestanding_hooks(value string) []string { |
| 307 | mut hooks := []string{} |
| 308 | for raw_part in value.split(',') { |
| 309 | part := raw_part.trim_space() |
| 310 | if part == '' { |
| 311 | continue |
| 312 | } |
| 313 | if part == 'minimal' { |
| 314 | for hook in ['output', 'panic', 'alloc'] { |
| 315 | append_freestanding_hook(mut hooks, hook) |
| 316 | } |
| 317 | continue |
| 318 | } |
| 319 | append_freestanding_hook(mut hooks, part) |
| 320 | } |
| 321 | return hooks |
| 322 | } |
| 323 | |
| 324 | fn add_freestanding_hook_defines(mut defines []string, hooks []string) { |
| 325 | if hooks.len == 0 { |
| 326 | return |
| 327 | } |
| 328 | if 'freestanding_hooks' !in defines { |
| 329 | defines << 'freestanding_hooks' |
| 330 | } |
| 331 | for hook in hooks { |
| 332 | for define in ['freestanding_${hook}', 'freestanding_hooks_${hook}'] { |
| 333 | if define !in defines { |
| 334 | defines << define |
| 335 | } |
| 336 | } |
| 337 | } |
| 338 | } |
| 339 | |
| 340 | pub fn source_files_from_args(args []string) []string { |
| 341 | options_with_values := ['-backend', '-b', '-o', '-output', '-arch', '-printfn', '-gc', '-d', |
| 342 | '-hot-fn', '-cc', '-os', '-fhooks'] |
| 343 | mut files := []string{} |
| 344 | mut skip_next := false |
| 345 | for arg in args { |
| 346 | if arg == '--' { |
| 347 | break |
| 348 | } |
| 349 | if skip_next { |
| 350 | skip_next = false |
| 351 | continue |
| 352 | } |
| 353 | if arg.starts_with('-') { |
| 354 | if arg in options_with_values { |
| 355 | skip_next = true |
| 356 | } |
| 357 | continue |
| 358 | } |
| 359 | files << arg |
| 360 | } |
| 361 | return files |
| 362 | } |
| 363 | |
| 364 | // new_preferences_from_args parses full args list including option values |
| 365 | pub fn new_preferences_from_args(args []string) Preferences { |
| 366 | // Default backend is cleanc |
| 367 | default_backend := 'cleanc' |
| 368 | mut backend_str_long := cmdline.option(args, '-backend', '') |
| 369 | if backend_str_long.len == 0 { |
| 370 | backend_str_long = '' |
| 371 | } |
| 372 | mut backend_str_short := cmdline.option(args, '-b', '') |
| 373 | if backend_str_short.len == 0 { |
| 374 | backend_str_short = '' |
| 375 | } |
| 376 | backend_str := if backend_str_long.len > 0 { |
| 377 | backend_str_long |
| 378 | } else if backend_str_short.len > 0 { |
| 379 | backend_str_short |
| 380 | } else { |
| 381 | default_backend |
| 382 | } |
| 383 | mut backend := Backend.cleanc |
| 384 | match backend_str { |
| 385 | 'eval' { |
| 386 | backend = .eval |
| 387 | } |
| 388 | 'cleanc' { |
| 389 | backend = .cleanc |
| 390 | } |
| 391 | 'c' { |
| 392 | backend = .c |
| 393 | } |
| 394 | 'v' { |
| 395 | backend = .v |
| 396 | } |
| 397 | 'arm64' { |
| 398 | backend = .arm64 |
| 399 | } |
| 400 | 'x64' { |
| 401 | backend = .x64 |
| 402 | } |
| 403 | else { |
| 404 | eprintln('error: unknown backend `${backend_str}`. Valid backends: eval, cleanc, c, v, arm64, x64') |
| 405 | exit(1) |
| 406 | } |
| 407 | } |
| 408 | |
| 409 | mut arch_str := cmdline.option(args, '-arch', 'auto') |
| 410 | if arch_str.len == 0 { |
| 411 | arch_str = 'auto' |
| 412 | } |
| 413 | mut arch := Arch.auto |
| 414 | match arch_str { |
| 415 | 'auto' { |
| 416 | arch = .auto |
| 417 | } |
| 418 | 'x64' { |
| 419 | arch = .x64 |
| 420 | } |
| 421 | 'arm64' { |
| 422 | arch = .arm64 |
| 423 | } |
| 424 | else { |
| 425 | eprintln('error: unknown architecture `${arch_str}`. Valid architectures: auto, x64, arm64') |
| 426 | exit(1) |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | output_file := cmdline.option(args, '-o', cmdline.option(args, '-output', '')) |
| 431 | ccompiler := cmdline.option(args, '-cc', '') |
| 432 | target_os_arg := cmdline.option(args, '-os', '') |
| 433 | freestanding_hooks_arg := cmdline.option(args, '-fhooks', '') |
| 434 | // Without -os, V2 targets the host OS. -os is an explicit target override; |
| 435 | // -os cross and -freestanding express separate portable/platform contracts. |
| 436 | normalized_target_os := if target_os_arg.len > 0 { |
| 437 | normalize_cli_target_os(target_os_arg) |
| 438 | } else { |
| 439 | normalize_target_os_name(os.user_os()) |
| 440 | } |
| 441 | output_cross_c := normalized_target_os == 'cross' |
| 442 | |
| 443 | // Parse -printfn option (comma-separated list of function names to print) |
| 444 | mut printfn_str := cmdline.option(args, '-printfn', '') |
| 445 | if printfn_str.len == 0 { |
| 446 | printfn_str = '' |
| 447 | } |
| 448 | printfn_list := if printfn_str.len > 0 { printfn_str.split(',') } else { []string{} } |
| 449 | |
| 450 | // Parse -d <name> comptime defines (can be specified multiple times) |
| 451 | user_defines := cmdline.options(args, '-d') |
| 452 | |
| 453 | // Parse -hot-fn <name> for hot code reloading |
| 454 | mut hot_fn_str := cmdline.option(args, '-hot-fn', '') |
| 455 | if hot_fn_str.len == 0 { |
| 456 | hot_fn_str = '' |
| 457 | } |
| 458 | |
| 459 | // Parse -gc <mode> for garbage collection |
| 460 | gc_mode_str := cmdline.option(args, '-gc', '') |
| 461 | mut gc_mode := GarbageCollectionMode.no_gc |
| 462 | mut gc_defines := []string{} |
| 463 | match gc_mode_str { |
| 464 | '', 'none' { |
| 465 | gc_mode = .no_gc |
| 466 | } |
| 467 | 'vgc' { |
| 468 | gc_mode = .vgc |
| 469 | gc_defines << 'vgc' |
| 470 | } |
| 471 | 'boehm' { |
| 472 | gc_mode = .boehm |
| 473 | gc_defines << 'gcboehm' |
| 474 | gc_defines << 'gcboehm_full' |
| 475 | gc_defines << 'gcboehm_opt' |
| 476 | } |
| 477 | else { |
| 478 | eprintln('error: unknown garbage collection mode `-gc ${gc_mode_str}`') |
| 479 | eprintln(' `-gc vgc` .............. V GC: concurrent tri-color mark-and-sweep (translated from Go)') |
| 480 | eprintln(' `-gc boehm` ............ Boehm-Demers-Weiser conservative GC') |
| 481 | eprintln(' `-gc none` ............. no garbage collection (default)') |
| 482 | exit(1) |
| 483 | } |
| 484 | } |
| 485 | |
| 486 | mut all_defines := user_defines.clone() |
| 487 | all_defines << gc_defines |
| 488 | if output_cross_c && 'cross' !in all_defines { |
| 489 | all_defines << 'cross' |
| 490 | } |
| 491 | if '-prealloc' in args { |
| 492 | all_defines << 'prealloc' |
| 493 | } |
| 494 | freestanding := '-freestanding' in args || '--freestanding' in args |
| 495 | validate_target_contract(normalized_target_os, freestanding) |
| 496 | freestanding_hooks := parse_freestanding_hooks(freestanding_hooks_arg) |
| 497 | if freestanding_hooks.len > 0 && !freestanding { |
| 498 | eprintln('error: -fhooks requires -freestanding') |
| 499 | exit(1) |
| 500 | } |
| 501 | if freestanding && 'freestanding' !in all_defines { |
| 502 | all_defines << 'freestanding' |
| 503 | } |
| 504 | add_freestanding_hook_defines(mut all_defines, freestanding_hooks) |
| 505 | |
| 506 | options := cmdline.only_options(args) |
| 507 | validate_macos_tiny_flag_contract(options, normalized_target_os) or { |
| 508 | eprintln('error: ${err.msg()}') |
| 509 | exit(1) |
| 510 | } |
| 511 | macos_tiny := '-no-mos-tiny' !in options |
| 512 | |
| 513 | // Parse -ownership flag (must be after options is declared) |
| 514 | mut ownership := false |
| 515 | $if ownership ? { |
| 516 | ownership = '-ownership' in options |
| 517 | if ownership { |
| 518 | all_defines << 'ownership' |
| 519 | } |
| 520 | } |
| 521 | |
| 522 | // Validate flags: error on unknown options |
| 523 | known_flags_with_values := ['-backend', '-b', '-o', '-output', '-arch', '-printfn', '-gc', |
| 524 | '-d', '-hot-fn', '-cc', '-os', '-fhooks'] |
| 525 | mut known_boolean_flags := ['--debug', '--verbose', '-v', '--skip-genv', '--skip-builtin', |
| 526 | '--skip-imports', '--skip-type-check', '-no-parallel', '--no-parallel', '-nocache', |
| 527 | '--nocache', '-nomarkused', '--nomarkused', '-showcc', '--showcc', '-stats', '--stats', |
| 528 | '-print-parsed-files', '--print-parsed-files', '-keepc', '--profile-alloc', '-profile-alloc', |
| 529 | '-enable-globals', '--enable-globals', '-shared', '--shared', '-O0', '--single-backend', |
| 530 | '-single-backend', '-prod', '-prealloc', '-freestanding', '--freestanding', '-no-mos-tiny'] |
| 531 | $if ownership ? { |
| 532 | known_boolean_flags << '-ownership' |
| 533 | } |
| 534 | for opt in options { |
| 535 | if opt !in known_flags_with_values && opt !in known_boolean_flags { |
| 536 | eprintln('error: unknown flag `${opt}`') |
| 537 | eprintln('') |
| 538 | eprintln('Usage: v2 [options] <file.v>') |
| 539 | eprintln('') |
| 540 | eprintln('Options:') |
| 541 | eprintln(' -o <file> Output file name') |
| 542 | eprintln(' -b <name> Backend: eval, cleanc, c, v, arm64, x64 (default: cleanc; omit for cleanc)') |
| 543 | eprintln(' -backend <name> is accepted as a compatibility alias') |
| 544 | eprintln(' -arch <name> Architecture: auto, x64, arm64 (default: auto)') |
| 545 | eprintln(' -os <target> Override target OS (default: host OS): linux, macos, windows, termux, cross, none') |
| 546 | eprintln(' -printfn <names> Print generated C for functions (comma-separated)') |
| 547 | eprintln(' -stats, --stats Print compilation statistics') |
| 548 | eprintln(' -nocache, --nocache Disable build cache') |
| 549 | eprintln(' -d <name> Define a comptime flag') |
| 550 | eprintln(' -enable-globals Accepted for v1 compatibility') |
| 551 | eprintln(' -prod Production build: enable SSA optimization + -O3 -flto') |
| 552 | eprintln(' -prealloc Use arena allocation (faster, not thread-safe)') |
| 553 | eprintln(' -freestanding Generate for a freestanding platform contract') |
| 554 | eprintln(' -fhooks <values> Advanced freestanding hooks for --skip-builtin --skip-type-check stubs') |
| 555 | eprintln(' Values: output, panic, alloc, minimal') |
| 556 | eprintln(' -no-mos-tiny Disable macOS x64 tiny object output') |
| 557 | eprintln(' -O0 Skip SSA optimization (default; use -prod to enable)') |
| 558 | $if ownership ? { |
| 559 | eprintln(' -ownership Enable ownership checking for strings') |
| 560 | } |
| 561 | eprintln(' --debug Enable debug mode') |
| 562 | eprintln(' -v, --verbose Enable verbose output') |
| 563 | eprintln(' -cc <compiler> C compiler to use (default: tcc, fallback: cc)') |
| 564 | eprintln(' -showcc, --showcc Print C compiler command') |
| 565 | eprintln(' -keepc Keep generated C file') |
| 566 | eprintln(' -no-parallel, --no-parallel') |
| 567 | eprintln(' Disable parallel type check and transform') |
| 568 | exit(1) |
| 569 | } |
| 570 | } |
| 571 | |
| 572 | implicit_skip_builtin := freestanding && freestanding_hooks.len == 0 |
| 573 | return Preferences{ |
| 574 | debug: '--debug' in options |
| 575 | verbose: '--verbose' in options || '-v' in options |
| 576 | skip_genv: '--skip-genv' in options |
| 577 | skip_builtin: '--skip-builtin' in options || implicit_skip_builtin |
| 578 | skip_imports: '--skip-imports' in options |
| 579 | skip_type_check: '--skip-type-check' in options |
| 580 | no_parallel: '-no-parallel' in options || '--no-parallel' in options |
| 581 | no_parallel_transform: '-no-parallel' in options || '--no-parallel' in options |
| 582 | no_cache: '-nocache' in options || '--nocache' in options |
| 583 | no_markused: '-nomarkused' in options || '--nomarkused' in options |
| 584 | show_cc: '-showcc' in options || '--showcc' in options |
| 585 | stats: '-stats' in options || '--stats' in options |
| 586 | print_parsed_files: '-print-parsed-files' in options || '--print-parsed-files' in options |
| 587 | keep_c: '-keepc' in options |
| 588 | use_context_allocator: '--profile-alloc' in options || '-profile-alloc' in options |
| 589 | is_shared_lib: '-shared' in options || '--shared' in options |
| 590 | no_optimize: '-O0' in options || '-prod' !in options |
| 591 | is_prod: '-prod' in options |
| 592 | prealloc: '-prealloc' in options |
| 593 | single_backend: '--single-backend' in options || '-single-backend' in options |
| 594 | ownership: ownership |
| 595 | gc_mode: gc_mode |
| 596 | backend: backend |
| 597 | arch: arch |
| 598 | target_os: normalized_target_os |
| 599 | output_cross_c: output_cross_c |
| 600 | freestanding: freestanding |
| 601 | freestanding_hooks: freestanding_hooks |
| 602 | macos_tiny: macos_tiny |
| 603 | output_file: output_file |
| 604 | printfn_list: printfn_list |
| 605 | user_defines: all_defines |
| 606 | explicit_user_defines: user_defines.clone() |
| 607 | hot_fn: hot_fn_str |
| 608 | ccompiler: ccompiler |
| 609 | vroot: detect_vroot() |
| 610 | vmodules_path: os.vmodules_dir() |
| 611 | } |
| 612 | } |
| 613 | |
| 614 | fn target_os_from_option_tokens(options []string) string { |
| 615 | for opt in options { |
| 616 | if opt.starts_with('--os-') { |
| 617 | return normalize_cli_target_os(opt.all_after('--os-')) |
| 618 | } |
| 619 | if opt.starts_with('os-') { |
| 620 | return normalize_cli_target_os(opt.all_after('os-')) |
| 621 | } |
| 622 | } |
| 623 | return normalize_target_os_name(os.user_os()) |
| 624 | } |
| 625 | |
| 626 | fn freestanding_hooks_from_option_tokens(options []string) []string { |
| 627 | mut hooks := []string{} |
| 628 | for opt in options { |
| 629 | if opt.starts_with('--fhooks-') { |
| 630 | for hook in parse_freestanding_hooks(opt.all_after('--fhooks-')) { |
| 631 | append_freestanding_hook(mut hooks, hook) |
| 632 | } |
| 633 | } else if opt.starts_with('fhooks-') { |
| 634 | for hook in parse_freestanding_hooks(opt.all_after('fhooks-')) { |
| 635 | append_freestanding_hook(mut hooks, hook) |
| 636 | } |
| 637 | } |
| 638 | } |
| 639 | return hooks |
| 640 | } |
| 641 | |
| 642 | pub fn new_preferences_using_options(options []string) Preferences { |
| 643 | // Default backend based on OS: macOS defaults to arm64, others to x64 |
| 644 | mut backend := if os.user_os() == 'macos' { Backend.arm64 } else { Backend.x64 } |
| 645 | if '--eval' in options || 'eval' in options { |
| 646 | backend = .eval |
| 647 | } else if '--cleanc' in options || 'cleanc' in options { |
| 648 | backend = .cleanc |
| 649 | } else if '--c' in options || 'c' in options { |
| 650 | backend = .c |
| 651 | } else if '--v' in options || 'v' in options { |
| 652 | backend = .v |
| 653 | } else if '--arm64' in options || 'arm64' in options { |
| 654 | backend = .arm64 |
| 655 | } else if '--x64' in options || 'x64' in options { |
| 656 | backend = .x64 |
| 657 | } |
| 658 | |
| 659 | mut arch := Arch.auto |
| 660 | if '--arch-x64' in options { |
| 661 | arch = .x64 |
| 662 | } else if '--arch-arm64' in options { |
| 663 | arch = .arm64 |
| 664 | } |
| 665 | target_os := target_os_from_option_tokens(options) |
| 666 | output_cross_c := target_os == 'cross' |
| 667 | freestanding := '--freestanding' in options || '-freestanding' in options |
| 668 | || 'freestanding' in options |
| 669 | validate_target_contract(target_os, freestanding) |
| 670 | freestanding_hooks := freestanding_hooks_from_option_tokens(options) |
| 671 | if freestanding_hooks.len > 0 && !freestanding { |
| 672 | eprintln('error: freestanding hooks require freestanding target mode') |
| 673 | exit(1) |
| 674 | } |
| 675 | mut user_defines := []string{} |
| 676 | if output_cross_c { |
| 677 | user_defines << 'cross' |
| 678 | } |
| 679 | if freestanding { |
| 680 | user_defines << 'freestanding' |
| 681 | } |
| 682 | add_freestanding_hook_defines(mut user_defines, freestanding_hooks) |
| 683 | |
| 684 | implicit_skip_builtin := freestanding && freestanding_hooks.len == 0 |
| 685 | return Preferences{ |
| 686 | // config flags |
| 687 | debug: '--debug' in options |
| 688 | verbose: '--verbose' in options || '-v' in options |
| 689 | skip_genv: '--skip-genv' in options |
| 690 | skip_builtin: '--skip-builtin' in options || implicit_skip_builtin |
| 691 | skip_imports: '--skip-imports' in options |
| 692 | skip_type_check: '--skip-type-check' in options |
| 693 | no_parallel: '-no-parallel' in options || '--no-parallel' in options |
| 694 | no_parallel_transform: '-no-parallel' in options || '--no-parallel' in options |
| 695 | no_cache: '-nocache' in options || '--nocache' in options |
| 696 | no_markused: '-nomarkused' in options || '--nomarkused' in options |
| 697 | show_cc: '-showcc' in options || '--showcc' in options |
| 698 | stats: '-stats' in options || '--stats' in options |
| 699 | print_parsed_files: '-print-parsed-files' in options || '--print-parsed-files' in options |
| 700 | use_context_allocator: '--profile-alloc' in options || '-profile-alloc' in options |
| 701 | is_shared_lib: '-shared' in options || '--shared' in options |
| 702 | no_optimize: '-O0' in options || '-prod' !in options |
| 703 | is_prod: '-prod' in options |
| 704 | prealloc: '-prealloc' in options |
| 705 | single_backend: '--single-backend' in options || '-single-backend' in options |
| 706 | backend: backend |
| 707 | arch: arch |
| 708 | target_os: target_os |
| 709 | output_cross_c: output_cross_c |
| 710 | freestanding: freestanding |
| 711 | freestanding_hooks: freestanding_hooks |
| 712 | user_defines: user_defines |
| 713 | explicit_user_defines: []string{} |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | // get_effective_arch returns the architecture to use based on preferences and OS |
| 718 | pub fn (p &Preferences) get_effective_arch() Arch { |
| 719 | if p.arch != .auto { |
| 720 | return p.arch |
| 721 | } |
| 722 | // Auto-detect: macOS defaults to arm64, others to x64 |
| 723 | return if normalize_current_os_name(p.target_os_or_host()) == 'macos' { |
| 724 | Arch.arm64 |
| 725 | } else { |
| 726 | Arch.x64 |
| 727 | } |
| 728 | } |
| 729 | |