C++
The arm
Section titled “The arm”language: "cpp" under the responses-as-code capability makes a model’s reply a
C++ translation unit. The reply is written to disk verbatim as main.cpp and
compiled by clang++ from wasi-sdk into a wasm32-wasip1 core module, then
encoded in process with a pinned preview1 adapter into the WebAssembly component
that turn is evaluated by. The bytes ride on the prepared program, so this arm
declares no guest component of its own.
Nothing is prepended, appended or re-indented, so no line moves and a diagnostic
at line 7 is line 7 of what the model wrote. A translation unit is the only C++
context admitting the template, namespace and #include declarations an
author writes at a file’s top level. C++ has nowhere else to put a statement, so
the reply must define main, which is the entry point gg’s shell calls.
A reply defining none is refused at prepare time as PrepareError::Unsupported,
with a sentence naming what to write. gg has to ask that question because
wasi-libc references main weakly, so a program with no entry point links
cleanly and traps having run nothing. The reply is read lexically for the token
main followed by an open parenthesis in its code bytes, so the reading can
only be wrong in the accepting direction.
member_separator is ::, a code skill’s C++ file is a .hpp, and checker()
is "clang++", so compile time is recorded on every turn.
Every name a program uses is reached through a line the program wrote, gg’s
surface, the C++ standard library and a code module alike. gg is undeclared
until the reply writes #include <gg/files.hpp> or the umbrella
#include <gg.hpp>, and lib until it writes import lib.<key>;. The SDK’s
headers are on the compile’s include path, a module’s precompiled interface is
named to the compile, and the bodies of both are linked into every artifact,
which is packaging rather than scope.
Toolchain and build outputs
Section titled “Toolchain and build outputs”The compiler is a wasi-sdk tree, located at TCAB_GG_WASI_SDK_HOME, then
/opt/gg/toolchains/wasi-sdk, then ~/.local/share/tcab/gg-wasi-sdk, and used
by its real path, every symlink on the way resolved: clang derives its sysroot
from where its binary actually is, so that is the path the compiler records and
the one the arm rewrites to /wasi-sdk in everything it records, above all the
libc++ header a hardening failure names. A compile is bounded at 120
seconds. crates/gg-sandbox-artifacts/cpp builds three
artifacts from packages/gg-sandbox-cpp into that crate’s OUT_DIR, and the
arm embeds them through GG_ARTIFACTS_CPP. None of them are committed. This arm’s
warm-up is unpacking the guest archive.
| Artifact | What it carries |
|---|---|
cpp.guest.tar.gz | the generated WIT header, the SDK headers under the include/ root a program’s own #include <gg/…> resolves against, the library set prelude.hpp declares, gg’s shell as source and as an object, and the SDK, bindings and component-type objects the link needs |
cpp.adapter.wasm | this arm’s pinned wasi_snapshot_preview1 reactor adapter |
cpp.toolchain.json | the pinned release, target, standard and adapter version, and the header list read out of the prelude |
Three compile flags decide what a model can write and read.
-fwasm-exceptions with -mllvm -wasm-use-legacy-eh=false selects the
standardised try_table encoding the pinned wasmtime accepts, so throw, try
and catch work. -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE
turns on libc++‘s bounds checks, which wasi-sdk ships disabled. -g1 supplies
the debug information the engine symbolicates traps with.
The host accepts that encoding through Config::wasm_exceptions, which wasmtime
gates behind its gc build feature. The workspace therefore builds against a
wasmtime with GC support, and the repository’s other wasm hosts turn
gc_support off on their own engines rather than inheriting a wider validation
surface. gg’s engine is the one that opts in.
The declared library set
Section titled “The declared library set”Sources/prelude.hpp declares the library set this arm makes available, which is
the C++ standard library, header by header under // == Heading == groups.
build.sh’s manifest, cpp.toolchain.json’s header list and the catalogue’s
libraries section are all read from that one file. A program writes its own
#include for every facility it names.
SDK and catalogue
Section titled “SDK and catalogue”The SDK is hand-written in packages/gg-sandbox-cpp/Sources/sdk/ and reads as
the standard library it arrives beside: snake_case functions and types,
enum class for a fixed choice, aggregates for records, std::variant for a
value that is one of two things, and a thrown gg::core::api_error deriving
from std::runtime_error for a call that failed. One optional argument is a
default argument; two or more are designated initialisers.
Every capability module is a namespace inside namespace gg, declared in one
header of its own. A program writes #include <gg/files.hpp> and then
gg::files::read_file(…), and #include <gg.hpp> is the umbrella declaring all
thirteen. Each module’s catalogue entry states its own include line, which the
system prompt’s module list and every documentation view of a symbol in it quote.
packages/gg-sandbox-cpp/signatures.sh reflects the signature catalogue out of
clang’s own comment AST, dumped as JSON by the same clang++ that compiles
every program and filtered with -ast-dump-filter=gg. crates/gg/build.rs runs
it while building the crate. The reflection enforces four rules:
///comments are model-facing and//comments are not. A public member documented with//, or with nothing, is left out of what a model is shown.-Wdocumentation -Wdocumentation-pedantic -Werror=documentationis passed, so a\paramnaming an argument the function does not take fails the reflection.- The gg operation a function binds is written on its declaration as a
<ggop>files.read_file</ggop>line, a namespace’s gg module as<ggmodule>files</ggmodule>, and a member function reaching an operation its module already offers as<ggop-alias>. A documented function that names no operation fails the reflection; an id gg’s operations table has no row for is caught the other way, by the registry gate over the catalogue this arm embedded. - The brief is the comment’s first line and the detail is the rest. A declaration whose first line runs on is refused by name.
A \throws names an error type a function’s own documentation declares, and the
catalogue carries those types as that function’s throws list.
Code modules
Section titled “Code modules”A code skill’s or memory’s namespace is lib::<key>. A program reaches it by
writing import lib.<key>;, and then writes one of its names lib::<key>::<name>.
That line is the model’s to write, on the same terms as the #include that
reaches gg’s own surface.
Each module in scope is written into the compile workspace as a named C++
module — export module lib.<key>; over an export namespace lib::<key> opened
around the author’s declarations where they stand — precompiled into a module
interface of its own, and named to the program’s compile as
-fmodule-file=lib.<key>=… and as a link input. Both arguments say where the
module is and declare no name, so a program that writes no import for one earns
use of undeclared identifier 'lib'. Each module is compiled on its own.
module and import are the two words a module-name component may not be and
are both reachable keys, so each is escaped in the module name and left alone in
the namespace. A key of module is reached by import lib.Module; and written
lib::module::<name>.
The module declaration is what keeps gg’s surface out of the program. gg writes
#include <gg.hpp> into the module’s global module fragment, whose names are
attached to the global module and reach nobody who imports the module, so a
program with a code module in scope reaches gg only through a line it wrote.
An author’s #include lines are hoisted into that same fragment beside gg’s own,
above export module lib.<key>;. The fragment is where they belong for the
reason gg’s line is there: its names are attached to the global module and reach
no importer, so a module’s own headers stay the module’s own. #include is
textual, so a line left inside export namespace lib::<key> would nest the
header in that namespace.
Each hoisted line carries a #line stating where its author wrote it, and the
#line 1 in front of the body is untouched, so a module’s body keeps the
numbering its author wrote.
Each export of a module carries the type names its declaration writes in return
position and in parameter position, read off that declaration, which is what the
type views beside a function’s documentation view are opened from. A parameter’s
own name, its default argument, and the const, reference and pointer decoration
around a type are left out, and a template argument is a type name of its own, so
std::vector<row> writes std::vector and row.
That precompile happens once, at the read that binds the module, so a module that does not build is reported to its author rather than to every program the agent writes afterwards. Its own file is the only authored source there, so a diagnostic located elsewhere is a toolchain failure. The interface and the file it was built from are kept in the loaded-module band of the agent’s compile workspace, and every later program is handed the interface rather than the source.
Failures
Section titled “Failures”Everything clang++ rejects is one band, PrepareError::Compile, because clang
has no parse-only phase a program passes before meaning is considered. A
rejection is the model’s when a file somebody authored is named anywhere in the
whole rendering, notes included, because a template error is reported inside the
library with the model’s own line arriving as a note:.
wasm-ld: error: undefined symbol: is also the model’s, and everything else is
a toolchain failure. Warnings and the driver’s own linker command failed
summary are dropped from what the model reads. See
compilation for how the two kinds are reported.
What the model reads is capped group by group. The first four errors are kept
whole, at most three note: lines are kept under one error, and what was
dropped is counted rather than hidden. A diagnostic naming an authored file is
never dropped at any depth, and clang’s own N errors generated. summary is
kept. The band is decided on the whole rendering before the cap runs, so capping
can never turn a compile error into a toolchain failure.
At run time there are four shapes. An uncaught throw is caught by gg’s shell
and reported as a recoverable program error carrying the exception’s demangled
class and its what(), with no location. A libc++ hardening check traps with
libc++‘s own sentence at the model’s own line. A non-zero status returned from
main is read by the shell and reported with the number the program chose, which
is the one failure channel C++ gives an entry point. Any other undefined
behaviour is a trap with no words, located at the model’s own line and no more.
Two failures this arm cannot report faithfully are recorded by
gate G8. std::exit(3) reaches the model as
an exit with a non-zero status, because the pinned preview1 adapter lowers every
non-zero status to the same failure before gg is told, which
the sandbox page states in
full. A null dereference is not a
fault at all: address zero is ordinary linear memory in wasm, so reading and
writing through a null pointer succeeds and the turn is recorded as a clean one.
Prompt segment
Section titled “Prompt segment”system-code.hbs reaches this arm through a segment gated on
cpp, and code-nothing-shown.hbs through a clause naming printing. The
segment states that the reply is compiled verbatim as a whole translation unit
and defines int main.
Each module is a namespace inside namespace gg, and each entry of the module
list beside the segment carries that module’s own #include line.
The arm names clang++ as its checker, so the
shared body states that a program is compiled before it runs, that one the
compiler refuses is not executed, and that a call the run withheld compiles and
fails when it runs.
Source gg synthesizes for this arm is a whole program by the same rules. The
file-view program, the documentation-view program and the bootstrap program each
carry the include lines the calls they make need and a whole int main. A file
view is written gg::views::open_file("src/main.cpp");, with a window as a
designated initialiser.
Byte-level scan
Section titled “Byte-level scan”crates/gg/src/sandbox/language/cpp.mask.rs is the arm’s one lexer over raw
source: // and non-nesting /* */ comments, "…" and '…' with escapes, raw
strings whose author-chosen delimiter is read rather than looked for, and the
digit-separator rule — a ' straight after an alphanumeric is 1'000'000, not
a character literal. The reader asking whether a reply defines main takes the
scan’s best reading whatever happened, because its errors are safe in the
accepting direction.