Q+ FAQ
What is Q+?
Q+ is a statically typed systems and application programming language. It comes with a self-hosted compiler (qboot), a bytecode virtual machine (bqvm), an ELF AOT backend, a standard library of over 940 modules, 193 example programs, a regression suite, and the language specification.
This tree is Q+ Language 1.1.0-dev, the current development release, with language specification v4.36. Q+ 1.0.0 was the first public source release.
What is new in 1.1.0-dev?
The 1.1 line is a major language and runtime step over 1.0.0:
- Formal error model. Classified runtime errors (
Runtime error: <class>: <detail>), cleanpanic()abort,Resultunwrapping that terminates instead of silently continuing, the builtinassert(cond), and VM/AOT-identical error lines and exit codes. - AOT parity and loud failure. The ELF AOT backend supports checked integer bounds, casts, indirect calls, and tracebacks; unsupported features fail loudly (exit status 5) instead of producing wrong binaries.
- Integer matrix.
u8,u16,u32,u64,i8,i16with[TC15]literal range checks and checked overflow at type boundaries. - Language abstractions. Erased generics with call-site checks, generic struct field tracking, static
traitcontracts, and snapshot closures. - Macros and FFI. Declarative
macro/name!expansion, and a controlledextern "C"shim interface. - Concurrency. Cooperative and suspending async tasks (
std.alg,std.async), pthread-backed threads with channels and a thread pool (std.thread). - Concurrency safety (P2-C10, v4.18). Thread/channel/pool state is synchronized, parent-VM snapshots are copied before thread creation, a concurrent double-join is deterministic and loud, the stackful async scheduler is main-thread-only (worker threads are rejected loudly), the thread limit is enforced loudly, blocking channel waits are interruptible by the global timeout, and teardown joins workers before freeing the parent VM (no UAF).
- Full 64-bit u64 (P3-A1). On the VM,
u64supports full-width literals/printing/comparison and unsigned wrap-around arithmetic; the type context currently propagates throughletinitializers (A2-A5 residuals). - AOT integer constants (v4.29). ELF AOT is exact for i64 constants in
[-2^60, 2^60-1]; out-of-range constants are a build-time loud failure (AOT_UNSUPPORTED_I64_CONST, exit 5, no ELF). u64 programs are VM-only and AOT-build as loud failures (AOT_UNSUPPORTED_OPCODE: code=11).
See CHANGELOG.md and the language guide for details.
Why is Q+ open source?
The language layer — compiler, VM, standard library, examples, tests, and specification — is released so that developers can use it, study it, verify its documented behavior, and help improve it. Publishing the complete source of a self-hosting compiler and its runtime makes the language's behavior inspectable instead of opaque, and lets the project grow beyond what one team can do alone.
The specification is deliberately explicit about the current boundaries of the implementation. Publishing those limits alongside the code is part of the same principle: users should be able to see exactly what is guaranteed and what is not.
How does Q+ relate to a separate product line?
They are separate product lines. Q+ is the language, and this repository contains the language only. The a separate product line (另一产品线) kernel and OS services are a separate closed-source product line and are not part of this repository, not covered by its license, and out of scope for its issue tracker and pull requests.
a separate product line is not required to use Q+. The standalone build in this repository produces a bytecode toolchain and an AOT ELF backend that run on Linux x86-64 with no operating-system components from this project.
Is a separate product line open source?
No. a separate product line is a separate closed-source product line. This repository does not contain its kernel, drivers, OS services, or build system.
What license does Q+ use?
Q+ is released under the Apache License 2.0. The license permits use, modification, and distribution, including commercial use, provided the terms are met — notably preserving copyright and license notices and including the NOTICE file when redistributing.
See NOTICE for attribution and THIRD_PARTY_NOTICES.md for build dependencies. The build depends on the host system's C compiler and C library; no third-party source is vendored in the repository.
Can I use Q+ commercially?
Yes. The Apache License 2.0 permits commercial and non-commercial use, modification, and redistribution under its terms. It includes an express patent grant from contributors. It does not require you to open-source your own programs.
Do I need to sign a CLA to contribute?
No. Q+ uses the Developer Certificate of Origin (DCO) instead of a contributor license agreement. Sign off each commit with git commit -s; the Signed-off-by: line certifies that you have the right to submit the change under the project's license. See CONTRIBUTING.en.md for details.
How can I contribute?
Start with CONTRIBUTING.en.md:
- Build the toolchain with
tools/build.shand runtools/test.sh. - Find or open an issue describing the change.
- Make a focused change with tests; compiler fixes need a regression fixture under
src/tests/. - Sign off your commits and open a pull request against the repository.
Useful contribution areas include compiler diagnostics, standard-library modules and tests, example programs, documentation, AOT coverage, and platform portability. Security problems should be reported privately as described in SECURITY.md.
What platforms are supported?
The validated platform for Q+ 1.1.0-dev is Linux x86-64 with gcc. The build scripts target that environment. The threaded concurrency backend uses pthreads, so it also requires a host pthread implementation; this release does not provide Windows or macOS binaries or build instructions. If you are working on a port, open an issue to discuss the approach before sending a large pull request.
How do I build and run a program?
The short version, from the repository root:
tools/build.sh
build/bqvm --timeout-ms 0 build/qboot.qbc hello.q -o hello.qbc
build/bqvm hello.qbc
For a native ELF instead of bytecode:
build/cgtest_aot hello.qbc hello.elf
chmod +x hello.elf
./hello.elf
A complete walkthrough is in QUICKSTART.en.md; build details and expected checksums are in BUILDING.en.md.
Does Q+ support async/await?
Yes, at two levels:
std.algprovides a minimal cooperative task queue (spawn(fn()->i32),await_task(id),yield_now()) with deterministic FIFO run-to-completion scheduling.async fnandawaitare syntax supported here, and the queue also works on the ELF AOT backend.std.asyncprovides stackful, suspending VM tasks with real suspend/resume,task_state, cooperativetask_cancel,task_wait_timeout, andtask_sleep. Cancellation propagates along task-tree and waiter edges.
Limits: one VM, at most 16 tasks, up to 4096 operand-stack values per task, no IO-ready queue, no preemption, and no compiler-generated Future state machine. The stackful task scheduler is main-thread-only: a worker thread that calls any async primitive fails loudly (async: tasks are not supported in worker threads), and cross-thread coordination goes through channels. ELF AOT builds such programs and traps loudly only if a task primitive is actually executed. See the language guide and specification §9.5–§9.5.6.
Does Q+ have threads and channels?
Yes, as a production-oriented runtime feature in std.thread:
thread_spawn,thread_spawn_arg,thread_join,thread_join_timeout, andthread_last_error.- Each thread runs an independent VM instance with fresh stack, heap, and globals. Threads never share mutable memory; data crosses thread boundaries only through explicit channel messages.
- Bounded
channel_*queues carryi32,i64, and strings. Strings are copied into the receiver's heap (1 MiB limit per message); there are no cross-thread pointers. Channels support close, drain,is_closed, and timeout variants. pool_new/pool_submit/pool_join/pool_shutdown/pool_job_resultrun jobs on a fixed worker pool, one child VM per job.- Thread/worker errors and panics are loud on stderr with the thread/pool prefix; the process keeps running and reports errors through join/last_error results.
- Join state is synchronized (v4.18). A concurrent second join of the same thread from another thread fails loudly with
thread: join already in progress on this tid— it does not hang and never double-joins. A repeated join from the same thread after a successful join is idempotent and returns the recorded result; after athread_join_timeoutreturns-2, the thread can be joined later. - Creating more than 16 threads is a loud error (
spawn limit exceeded (max 16 threads)), not a silent-1. - Blocking channel sends/receives wait in 20 ms slices and can be interrupted by the global
--timeout-msbudget or runtime shutdown (loudexecution timeout exceededinstead of a hang). The*_timeoutAPIs provide precise per-call deadlines (-2timeout,-3closed). - Channel slots are not reclaimed after
channel_close, and undrained string messages are retained until process exit; long-running programs must bound channel creation and drain before closing.
Caps: 16 threads (ids 1..15 usable), 4 pools, 16 workers per pool, 64 channels, channel capacity 64, 256 jobs per pool, 1 MiB per string message. The backend is host pthread (Linux); the kernel backend is part of the separate a separate product line product line. AOT: these primitives are VM-only — the ELF builds but executing a primitive traps loudly.
Memory model (v4.18): threads never share mutable Q+ memory. Each child VM is created from a read-only parent snapshot copied in the spawning thread before pthread_create; thread/channel/pool slots, join state, and console output are serialized by process-level mutexes; channel data paths use per-channel mutex/condition variables; teardown shuts down concurrency and joins all workers before the parent VM is freed.
Does Q+ have macros?
Yes. Q+ has declarative, token-level macros:
macro square($x: expr) { $x * $x }
called as square!(5). Expansion happens before parsing, so macros add no runtime objects or opcodes and behave identically on the VM and AOT. Parameter categories are expr, stmt, and ident (up to 8 parameters); arguments are not auto-parenthesized; body identifiers starting with __ get hygienic renaming; recursion is limited to 64 levels; diagnostics point at the invocation line.
Not implemented: macro_rules!-style pattern matching/repetition, procedure macros, macros that declare macros, and cross-module macro imports.
Does Q+ have FFI?
A small, controlled C FFI is available as extern "C" declarations. The VM maintains a trusted shim whitelist — currently puts and strlen — so no arbitrary symbol resolution or dynamic loading occurs. Only the "C" ABI string is accepted; parameters are scalars or borrowed read-only strings valid for the call; structs, arrays, and raw pointers are not supported, and unknown extern names fail loudly at load time.
The ELF AOT backend has no shim table, so FFI programs fail loudly at AOT compile time (exit status 5, no ELF). See docs/FFI.md.
Does Q+ have generics, traits, or closures?
All three exist, with explicit boundaries:
- Generics are erased: code generation produces one copy, while the type checker binds type parameters at call sites, enforces consistency, and substitutes return types (
let s: string = id(7)is[TC1]). Generic structs (Box<i32>vsBox<string>) are type-tracked for fields and assignment, with erased codegen. Generic bounds and monomorphization are not implemented. - Traits are static contracts:
impl Trait for Typemust satisfy the declared method names, arity, parameter types, and return types. There are no trait objects;dyn Traitis a loud[TC11]error. Default methods and associated types are not implemented. - Closures are snapshot captures in nested functions: a nested function may read enclosing locals/parameters, which are passed as implicit by-value arguments at call sites. Writes inside the nested function affect only its copy; outer values are unchanged. A capturing nested function cannot be stored in a
fnslot or otherwise escape; there are no heap environments, delayed calls, or multi-level captures.
Why is i32 not a 32-bit integer?
i32 is the default integer type, but integers are represented as tagged values with a 64-bit carrier. Arithmetic does not wrap or truncate, so 2147483647 + 1 evaluates to 2147483648 and 70000 * 70000 evaluates to 4900000000. i64 and u64 are available where wider integers are needed. The VM supports the full i64 and u64 ranges (v4.29); the ELF AOT backend is exact only in [-2^60, 2^60-1] and fails loudly outside that range (v4.29).
Code that implements 32-bit algorithms (hash functions, bit mixing, checksums) must mask explicitly, for example & 0xFFFFFFFF, or use the explicit wrapping_add/sub/mul_* helpers in std.alg. See the language guide and specification §8.2.
What is the story for u64?
On the VM, u64 supports the full 64-bit range [0, 18446744073709551615] (P3-A1): full-width decimal literals, unsigned print and comparison, and unsigned wrap-around arithmetic in a u64 context (max + 1 -> 0, 0 - 1 -> max). A full-width literal used where a non-u64 type is expected is a loud [TC15] compile error.
The A-series boundaries are explicit: type-context propagation currently covers let initialization expressions (A2: assignment, parameters/returns, containers, comparison contexts are not yet uniform); casts/string/checked/wrapping APIs are A3; container/struct/ABI unification is A4; AOT u64 is A5 Stage1+2+3 (constants/print/arithmetic/comparisons/casts/f64/containers/std.u64) with documented loud residuals (A5 programs are AOT loud failures today). The std.alg wrapping helpers for i64/u64 still wrap modulo 2^56 (A3).
Separately, the ELF AOT backend can represent i64 constants only in [-2^60, 2^60-1] (v4.29). In-range constants are exact; out-of-range constants — including a negative literal whose magnitude exceeds 2^60-1 — fail the AOT build loudly with AOT_UNSUPPORTED_I64_CONST (exit 5, no ELF). The VM still supports the full i64 range, and runtime tagged arithmetic overflow beyond the AOT i64-tagged arithmetic overflow remains an A5 residual.
Does Q+ have exceptions?
No. Q+ has no exceptions and no try/catch. Compile-time failures are reported as diagnostics with distinct exit statuses: 1 (I/O), 2 (parse/macro), 3 (semantic/codegen), 4 (type), and 5 (AOT unsupported); 0 means success. Runtime failures abort with status 1 and a classified Runtime error: <class>: <detail> message: panic, overflow, division_by_zero, modulo_by_zero, index_out_of_bounds, stack_overflow, heap_exhausted/out_of_memory, assert_failed, timeout, and others.
Recoverable errors are modeled as values. std.panic provides panic() and panic_i32(); std.result provides the library-level Result convention (Result.Ok(v), Result.Err(msg), is_ok, result_unwrap, ...), where unwrapping an error terminates instead of silently continuing. The builtin assert(cond) aborts with assert_failed. There is no language-level error catching.
How do I debug a runtime failure?
Runtime failures print Runtime error: <class>: <detail> on stdout, and the VM also prints a traceback on stderr with function names, source lines, and bytecode PCs (up to 8 frames):
Traceback (most recent call last):
at boom (line 1, pc=12)
at mid (line 2, pc=13)
at main (line 3, pc=14)
Set QPLUS_TRACEBACK=0 to disable the traceback. The ELF AOT backend prints a stderr traceback with function names and real source lines for non-panic runtime errors; its stdout error line and exit status are identical to the VM's. Panic paths (sys_abort) are compile-time AOT loud failures and produce no ELF.
Which programs can I compile with the AOT backend?
Supported: control flow, arithmetic, calls, print, assert, checked integer bounds and overflow, casts, indirect calls through function values, the cooperative std.alg task queue, and runtime errors with tracebacks.
Loudly unsupported (compile-time, exit status 5, no ELF): programs that reference VM service builtins (sys_*, which includes importing std.io), FFI shim calls, std.panic/Result unwrap on errors, and unsupported constants such as exponent-notation floats.
Runtime traps (the ELF builds; exit status 1 only if a primitive executes): std.async task primitives and std.thread/channel/pool primitives. Unused wrappers do not block an AOT build.
AOT integer constants are exact only in [-2^60, 2^60-1] (v4.29): in-range constants match the VM byte-for-byte, out-of-range constants fail the AOT build loudly (AOT_UNSUPPORTED_I64_CONST, exit 5, no ELF). Full u64 programs are VM-only and AOT-build as loud failures (AOT_UNSUPPORTED_OPCODE: code=11).
For programs that need the VM service layer, compile with the bytecode VM instead of the AOT backend; the two backends support different slices of the language by design, and this is documented in the specification's AOT support matrix.
Does Q+ support u64 bitwise operations?
Yes, since Q+ 1.5.0-dev (spec v4.36): & | ^ ~ << >> work on raw 64-bit unsigned values. << wraps and >> is logical; shift counts clamp to [0, 63]; results print unsigned. Mixing a u64 with a non-literal signed value remains a loud [TC9] error; u64 float/bool/nil operands are not enabled. AOT and VM are bit-identical for the covered fixtures (the old qbc bootstrap was refreshed).
Does Q+ have editor/LSP support?
The release package's supported editor component is the VS Code extension 0.6.0: syntax highlighting and snippets, a zero-dependency formatter, build / build-AOT / run / test commands, compiler/runtime/AOT diagnostics mapped to the Problems panel, and an offline zero-dependency LSP (qplus-lsp, stdio JSON-RPC) with document/workspace symbols, definition, references, rename, signature help, semantic tokens, hover/completion, code actions, inlay hints, document links, code lenses, workspace folders, and execute-command support. scripts/validate.sh --integration reports 190 checks ALL PASS. The extension has no third-party runtime dependencies and no Marketplace publication is bundled.
An optional/dev LSP 0.7.0 component exists outside the release tar (208 checks; Modules B qualified paths, OS SDK 0.1 os.* module awareness, TC16/TC17 Explain). It is not a supported release component, is not in the language tar, and is not Marketplace published. 0.9.0 is the current optional/dev .vsix (not in the tar, not Marketplace-published); no released features are claimed for it.
What is qpm?
qpm is the Q+ package manager, a separate tooling component (not part of this language tree). The current line is qpm 0.3.0-alpha.1: a single-file Python tool using only the standard library, with static registries (index.json plus package tarballs over a directory, file://, or HTTP), deterministic publish, versions and dependencies, search/info/yank, add, and an offline install cache. It is useful for organizing external Q+ source packages; the language tree itself only ships and imports the standard library, and does not integrate a package manager into use.
Do Q+ packages integrate with use?
Not in this release. use resolves standard-library modules by name (use std.<module>) and rejects unknown modules loudly. qpm manages external source packages as a separate workflow; importing them as std.* modules is not part of this release.
Why are if and match not expressions?
By design in the current language, if/else and match are statement forms. They cannot initialize a variable or appear as a function argument; doing so is a parse error. The ternary operator cond ? a : b is the expression-level conditional. This is documented in specification §7.5 and in the language guide.
What is explicitly not implemented?
The current release is explicit about what is future work, so that unsupported features fail loudly or are documented rather than being silently misbehaving:
- Trait objects /
dyn, trait default methods, associated types. - Generic bounds and monomorphization (generics are erased).
- Uniform
u64semantics beyondletinitializers (assignment, parameters/returns, containers, comparison contexts) and AOT u64 Stage1+2+3 (documented loud residuals): P3-A1 milestones A2-A5.u64literals/print/compare/unsigned wrap work on the VM and on AOT for the Stage1 subset. - Exact AOT integer constants outside
[-2^60, 2^60-1]; AOT runtime tagged tagged-int arithmetic overflow remains an A5 residual. - Heap-environment closures; captures are by-value snapshots at visible call sites only.
- Procedure macros and pattern-matching/repetition macros.
- Arbitrary C FFI (only the trusted shim whitelist), callbacks, and raw pointers.
- IO-ready async queues, preemptive scheduling, compiler-generated Future state machines, and async/thread primitives on the ELF AOT backend (runtime traps, no silent wrong results).
- Async tasks outside the main thread: the stackful scheduler is main-thread-only and worker-thread async calls are rejected loudly.
- Cross-thread shared mutable state, thread cancellation objects, channel slot reclamation after close, and string-message reclamation beyond channel lifetime; blocking channel waits are interruptible only by the global timeout, not by per-call cancellation.
- Windows/macOS toolchain support.
Why do printed lines end with CRLF?
On Linux, bqvm currently writes printed output with \r\n line endings. This is an observed runtime behavior documented in the specification; it may change in a future release. If you compare output in a shell script, strip \r or use od -c to inspect actual bytes.
Also note that the print builtin does not append a newline at all. Add "\n" yourself, or use println_int, println_str, println_float, or println_bool from std.io when running on the VM.
Where is the language specification?
The normative specification is docs/qplus_language_spec.md, currently v4.36. It covers syntax, the type system, compiler behavior, the memory model, the standard-library interface contract, boundaries of the type checker and inference, value and reference semantics, the error model, concurrency, FFI, macros, the AOT support matrix, reproducibility, and versioning.
The language guide is a practical English companion with runnable examples. The specification is written in Chinese.
Why does bqvm --version print 2.0.0 when the language release is 1.1.0-dev?
The release version (Q+ 1.1.0-dev) and the version of its components are separate: bqvm --version reports the versions of the bytecode VM and its minimal runtime components, which follow their own component numbering. Use VERSION, CHANGELOG.md, and the specification to identify the release you are using.
How do I report a bug or ask a question?
Open an issue using the repository's issue templates (.github/ISSUE_TEMPLATE/). Include the exact command, the expected behavior, the actual output, the Q+ release, and your platform and gcc version. A minimal Q+ reproducer is extremely helpful.
Do not open public issues for security problems; follow SECURITY.md.
Where can I learn more?
- README.en.md — project overview and repository layout.
- QUICKSTART.en.md — first program in ten minutes, including AOT.
- LANGUAGE_GUIDE.en.md — language reference with known boundaries.
- BUILDING.en.md — building, hashes, and tests.
- CONTRIBUTING.en.md — how to contribute.
- docs/qplus_language_spec.md — normative specification (v4.36).
- CHANGELOG.md — release history.