Q+ Quick Start (10 minutes)
This guide takes you from a source checkout to a running Q+ program, including a native ELF build, on a Linux x86-64 machine with gcc and bash. It takes about ten minutes, most of which is reading.
The commands below are for Q+ Language 1.1.0-dev (language specification v4.36). Run them from the repository root (the directory that contains tools/ and src/), or set QPLUS_ROOT to the repository root if you run from elsewhere.
1. Get the source
Use the release source archive:
tar xzf qplus-language-1.1.0-dev-release.tar.gz
cd qplus-language-1.1.0-dev
or a Git clone of the repository:
git clone <repository-url> qplus
cd qplus
The directory name does not matter; everything below uses paths relative to the repository root.
2. Build the toolchain
tools/build.sh
The script builds the bytecode VM from C sources, self-hosts the compiler with a two-step bootstrap, and rebuilds the AOT toolchain. It takes about 20–30 seconds on a typical machine and prints the SHA-256 checksums of the artifacts:
| Artifact | Purpose |
|---|---|
build/bqvm | Bytecode virtual machine |
build/qboot.qbc | Self-hosted compiler |
build/cgtest.qbc / build/cgtest_aot | AOT compiler / ELF backend |
build/stage0.qbc | Capacity-only bootstrap compiler |
build/bqvm_small | Small-heap VM used by the heap-exhaustion regression |
On the reference Linux x86-64 toolchain a clean build produces:
build/bqvm c94042851edc62bb04d30bbf2cee32fa4b650a6816806991ed3a910459321fe0
build/qboot.qbc 378f5f858d8553760ae4b55aecac7773cdbec6386a6a986dd34a51d722175b34
build/cgtest.qbc f2f24e897891cb48159d784bce282909820c2f7974eeee574f2e3d431cd6eec1
build/cgtest_aot 7ca041362c45bd8759077595abdc68429e8961ef3cc0ca01ddbf147e0ebdb316
build/qboot.qbc is deterministic and is the reproducibility anchor; if it does not match, see BUILDING.en.md. The checksum of build/bqvm can legitimately differ with a different host C toolchain.
3. Write your first program
Create a file named hello.q in the repository root:
fn main() -> i32 {
print("Hello, Q+!\n")
return 0
}
Execution starts at fn main. By convention the entry point is declared fn main() -> i32; the integer it returns becomes the process exit status. print is a language builtin: it writes its argument without adding a newline, which is why the example includes the "\n" explicitly.
4. Compile it
build/bqvm --timeout-ms 0 build/qboot.qbc hello.q -o hello.qbc
The compiler prints its phases (lexer, parser, code generation) and exits with status 0. A non-zero status means compilation failed; see common errors.
- The first argument is the compiler bytecode (
build/qboot.qbc). - The second argument is your source file.
-o hello.qbcnames the output file. If you omit it, the compiler writesoutput.qbcin the current directory.--timeout-ms 0disables the VM execution limit for this compile. The default limit is 60000 ms; compilation is normally much faster.
5. Run it
build/bqvm hello.qbc
Output:
Hello, Q+!
The compiled program is an ordinary file; you can copy hello.qbc to another machine and run it with the same build/bqvm binary.
Check the exit status of main:
cat > status.q <<'EOF'
fn main() -> i32 {
return 42
}
EOF
build/bqvm --timeout-ms 0 build/qboot.qbc status.q -o status.qbc
build/bqvm status.qbc
echo $? # 42
Two details that surprise new users:
printdoes not append a newline. Add"\n"yourself, or use theprintln_*helpers fromstd.iowhen running on the VM.- On Linux,
bqvmcurrently writes printed lines with CRLF (\r\n) endings. If you compare output in a script, tolerate or strip\r. The specification records this as an observed behavior.
6. Build a native ELF
build/cgtest_aot hello.qbc hello.elf
chmod +x hello.elf # the AOT tool writes the file, not its executable bit
./hello.elf
The ELF backend is independent of the VM and produces a standalone native binary for the same QBC. Its stdout and exit status match the VM's for supported features; runtime errors also print a stderr traceback with real source lines.
The AOT backend is deliberately loud about unsupported features: it rejects programs that reference VM service builtins, extern "C" FFI shims, or std.panic at compile time with exit status 5, and writes no ELF. In particular, importing std.io pulls in VM service builtins, so AOT-friendly programs should use the print builtin as in this example. Async/thread primitives are the exception: the ELF builds, and executing such a primitive traps at runtime (AOT: async primitives unsupported, exit status 1).
# Example loud failure: std.io is a VM-service module.
cat > io_hello.q <<'EOF'
use std.io
fn main() -> i32 {
println_str("Hello from the VM")
return 0
}
EOF
build/bqvm --timeout-ms 0 build/qboot.qbc io_hello.q -o io_hello.qbc
build/cgtest_aot io_hello.qbc io_hello.elf # exit status 5, no io_hello.elf
7. Explore
# Compile and run one of the 193 bundled examples.
build/bqvm --timeout-ms 0 build/qboot.qbc src/examples/fib.q -o /tmp/fib.qbc
build/bqvm /tmp/fib.qbc # prints 6765
# Run the language regression suite (builds first if build/ is missing).
tools/test.sh # ends with QPLUS_LANGUAGE_TESTS_PASS
# Optional: benchmark VM vs AOT ELF vs C reference kernels.
bench/run.sh 3
Concurrency notes for v4.18 (P2-C10 safety fixes):
- The
std.asyncstackful task scheduler is main-thread-only. Worker threads that call async primitives fail loudly (async: tasks are not supported in worker threads); usestd.threadchannels for cross-thread coordination. - Thread state is synchronized. A concurrent second join of the same thread is a loud error (
thread: join already in progress on this tid); a repeated join from the same thread is idempotent; creating more than 16 threads is a loud error (spawn limit exceeded (max 16 threads)). - Blocking channel sends/receives are interruptible by the global
--timeout-msbudget; use the*_timeoutchannel APIs for precise per-call deadlines. Channel slots are not recycled after close, and undrained string messages are retained until process exit. - Read LANGUAGE_GUIDE.en.md for the full language: types and the integer matrix, generics, traits, macros, FFI, async tasks, threads/channels/pools, the AOT support matrix, and known boundaries.
- Browse
src/examples/— sorting, trees, graphs, dynamic programming, string processing, and more. - Read BUILDING.en.md for build internals and anchors.
- Read FAQ.en.md for licensing, async/threads/macros/FFI, editor support, and the
qpmpackage manager.
Common errors
| Message | Exit code | Cause and fix |
|---|---|---|
Error: expected token N got M at line L | 2 | Syntax error. Check braces and semicolons first. if and match are statements, not expressions: let x = if ... and let x = match ... do not parse. Use the cond ? a : b ternary at expression level. |
Error: unknown module in 'use' ... | 2 | Only use std.<module> (or use std::<module>) is supported. Use a module that exists under stdlib/std/, and run from the repository root or set QPLUS_ROOT. |
[TC1] line 5 want=i32 got=string (or [TC4], [TC2]) followed by === Compilation aborted: type errors above === | 4 | Static type error. The message names the expected (want=) and actual (got=) types. Fix the annotation or the expression. |
Error: integer literal out of range at line N type=u8 range=[0..255] [TC15] | 4 | A literal does not fit its annotated small-integer type. Small integers are checked; choose a wider type or a valid value. |
Error: undefined function called at line L | 3 | A function is not visible. Add the missing use std.<module> line, check spelling, and check the argument count. |
Error: failed to load QBC: unsupported extern function: <name> | 1 | The program calls an extern "C" function that is not in the VM shim whitelist (puts, strlen in this release). |
Runtime error: division_by_zero: division by zero | 1 | Runtime abort. Q+ has no exceptions; guard divisors before dividing. |
Runtime error: index_out_of_bounds: ... | 1 | Runtime abort. Check the index against len(array) before indexing. |
Runtime error: overflow: integer overflow | 1 | A value exceeded the range of its small-integer type. Use a wider type or checked conversion. |
Runtime error: assert_failed: assertion failed | 1 | The builtin assert(cond) received a falsy condition. |
Runtime error: panic: <msg> (with a preceding PANIC: <msg> line) | 1 | panic() or Result unwrap on an Err was reached. Handle Result values explicitly if termination is not intended. |
Runtime error: execution timeout exceeded | 1 | The program exceeded its execution limit (60 s by default). Raise or disable it with --timeout-ms 0 before the QBC argument, and check for infinite loops. Since v4.18 this also interrupts a thread blocked in a channel send/receive (reported as Runtime error: timeout: execution timeout exceeded). |
Runtime error: runtime_error: thread: spawn limit exceeded (max 16 threads) | 1 | The program tried to create more than 16 threads. The limit is loud instead of returning a silent -1; reuse threads or restructure the work. |
thread: join already in progress on this tid (with a [thread <tid>] prefix when raised inside a worker) | 1 or worker error | Two threads tried to join the same thread concurrently. Join each thread from one owner; a repeated join from the same thread after a successful join is idempotent and returns the recorded result. |
[thread <tid>] Runtime error: runtime_error: async: tasks are not supported in worker threads | process continues | A worker thread called an async primitive. The stackful task scheduler is main-thread-only; coordinate workers with channels and keep async tasks on the main thread. |
AOT_UNSUPPORTED_BUILTIN: ... / AOT_UNSUPPORTED_OPCODE: ... from build/cgtest_aot | 5 | The ELF AOT backend does not support a feature the program references (VM service builtin such as std.io, FFI shim, panic/sys_abort, or an unsupported constant). Run on the VM or rewrite the AOT target. |
Runtime error: runtime_error: AOT: async primitives unsupported (VM-only) | 1 | The ELF builds, but it executed an async/task/thread/channel primitive; those are VM-only in this release. |
AOT_UNSUPPORTED_I64_CONST: integer constant outside AOT tagged-int range [-2^60, 2^60-1] | 5 | The AOT build saw an i64 constant outside the exact tagged range (v4.29), including a negative literal whose magnitude exceeds 2^60-1. No ELF is written; the VM still supports the full i64 range. |
AOT_UNSUPPORTED_OPCODE: code=11 | 5 | A5 Stage1 supports u64 constants/print/arithmetic/comparisons in AOT; programs using A5 Stage2/3 features (casts/bitwise/std.u64/full containers) stay loud (no ELF). p05_a2_context (code=125) is such a Stage2 case. |
Error: no QBC file specified | 1 | You ran bqvm without a program file, for example after a compile failed. Check that the .qbc file exists and pass it as the first argument. |
gcc: command not found / a missing source: build error | varies | The C toolchain or checkout is incomplete. Install gcc and bash, re-extract the release, and rerun tools/build.sh. |
Permission denied when running a script | 126 | Run bash tools/build.sh or chmod +x tools/build.sh tools/test.sh. |
If a command still fails, collect the exact command, the full output, the output of build/bqvm --version, and your gcc --version, then open an issue using the repository's issue template.