Debugging

C Backend binaries (Default)

To debug issues in the generated binary (flag: -b c), you can pass these flags:

  • -g - produces a less optimized executable with more debug information in it. Generated C uses #line directives so debuggers and panic stacktraces resolve positions to the original .v files. It is usually better to pass -g, unless you are writing low-level code, in which case use the next option -cg.
  • -cg - produces a less optimized executable with more debug information in it. The executable will use C source line numbers in this case. It is frequently used in combination with -keepc, so that you can inspect the generated C program in case of panic, or so that your debugger (gdb, lldb etc.) can show you the generated C source code. The C backend retains its per-build .<executable>.v3cc.* directory beside the executable so the source paths in the debug information remain available. You can remove this directory after debugging.
  • -showcc - prints the C command that is used to build the program.
  • -show-c-output - prints the output, that your C compiler produced while compiling your program.
  • -keepc - do not delete the generated C source code file after a successful compilation. Also keep using the same file path, so it is more stable, and easier to keep opened in an editor/IDE.

On macOS, debug builds keep their .dSYM bundle beside the final executable.

For best debugging experience if you are writing a low-level wrapper for an existing C library, you can pass several of these flags at the same time: v -keepc -cg -showcc yourprogram.v, then just run your debugger (gdb/lldb) or IDE on the produced executable yourprogram.

If you just want to inspect the generated C code, without further compilation, you can also use the -o flag (e.g. -o file.c). This will make V produce the file.c then stop.

If you want to see the generated C source code for just a single C function, for example main, you can use: -printfn main -o file.c.

To debug the V executable itself you need to compile from src with ./v -g -o v cmd/v.

You can debug tests with for example v -g -keepc prog_test.v. The -keepc flag is needed, so that the executable is not deleted, after it was created and ran.

To see a detailed list of all flags that V supports, use v help, v help build and v help build-c.

Commandline Debugging

  1. compile your binary with debugging info v -g hello.v
  2. debug with lldb or GDB e.g. lldb hello

Troubleshooting (debugging) executables created with V in GDB

Visual debugging Setup:

Native Backend binaries

v self builds the compiler with only the C backend by default. Selecting another backend, such as v -b arm64 file.v, automatically builds and runs a full compiler in the tool cache. Later requests reuse it until its compiler or source dependencies change. VTOOLS_NO_CACHE=1 builds a temporary full compiler for each request instead. To include optional backends in v itself, use v -compile-backend arm64 self or v -all-backends self.

-b native has been replaced by -b arm64 and -b x64. Use v -b arm64 file.v on macOS ARM64 or v -b x64 file.v on Linux AMD64. The native backends do not link a garbage collector yet, so they always build as if -gc none was passed. For x64, -prod enables SSA optimization, scalar register allocation, and optimized C helpers. Very large x64 modules may skip the global SSA pass to bound compiler memory. The ARM64 backend retains its existing optimization behavior. ARM64 executable linking is silent by default; use -stats to display compiler stage timings. ARM64 builds reject native source files included with #include, #insert, #preinclude, or #postinclude, because its linker cannot link those sources yet. Use the C backend for them. ARM64 resolves a fixed set of platform C constants. Other C constants produce an error; use the C backend when a header defines a constant the native backend does not support. The supported set includes the memory-order and pthread compatibility constants used by V's synchronization runtime. The x64 backend is experimental. Calls without a native implementation fail compilation or linking rather than producing placeholder implementations. It requires a Linux AMD64 host. Passing or returning C structs and arrays by value is currently unsupported and produces a diagnostic; pass pointers to C aggregates instead. Stored C function values support integer and pointer arguments with 64-bit integer, pointer, or void returns. Taking the address of a variadic C function, one with floating-point arguments or returns, or one returning a narrower integer produces a diagnostic. Direct C calls retain their scalar calling convention. C callbacks passed to or returned from C functions support 64-bit integer and pointer arguments, and 64-bit integer, pointer, or void returns. Unsupported callback conventions, including floating-point, narrow integer, aggregate, and variadic signatures, produce a diagnostic.

Native 128-bit integers currently support integer widening, copying, bitwise &, |, ^, addition, subtraction, comparisons, and shifts with constant counts from 0 through 127. Signed scalar widening sign-extends for both i128 and u128; unsigned scalar widening zero-extends. Signed i128 >> preserves the sign; u128 >> and >>> fill with zeroes. Multiplication, division, remainder, unary negation and complement, variable shifts, compound assignments, increment/decrement, narrowing casts, and conversions to or from floating-point values are unsupported and produce a diagnostic. Range iteration with 128-bit values is also unsupported. Array and string indices and slice bounds must use scalar integers. Integer literals must fit in a single 64-bit limb; construct larger values with shifts and bitwise operations. Use the C backend for the full set of 128-bit operations.

Currently there is no debugging support for binaries created by the native backends.

Javascript Backend

To debug the generated Javascript output you can activate source maps: v -b js -sourcemap hello.v -o hello.js

For all supported options check the latest help: v help build-js