Compile pipeline
weavec-cc compiles a C translation unit as Clang does, in-process, with
these additions in an enforcing mode (-fweavec-checks=trap, the default,
report or verify; RFC 0035 §1 and amendment 1, RFC 0036 §1):
-
Zero-initialised locals.
-ftrivial-auto-var-init=zero, for locals up to 4 KiB unless the command line sets its own limit (-fno-weavec-zero-initturns it off). The runtime’s allocator zero-fills every block. -
Array bounds.
-fsanitize=array-bounds, reported through the runtime’s weak__ubsan_handle_out_of_boundsand__ubsan_handle_out_of_bounds_abort, unless the command line already asks for a sanitizer; report mode adds-fsanitize-recoverfor it. A program that also links UBSan’s runtime gets UBSan’s handlers. -
Locations. Without
-g, the unit is compiled with location tracking only (DebugInfoKind=LocTrackingOnly, with columns): instructions carry source locations for the passes and the reports, and no debug information is emitted. -
Lifetime markers at every
-Olevel (SanitizeAddressUseAfterScope), so a use after scope traps at-O0too. -
No conditional compares on AArch64 (
-aarch64-enable-ccmp=false; a later-mllvmoption of the user’s wins). The backend folded a guard’s partial-granule test back into its fast path. -
Fortification (§1, §6). On an optimising glibc build whose command line neither defines nor undefines
_FORTIFY_SOURCE, the driver adds-D_FORTIFY_SOURCE=3; Darwin’s SDK fortifies at level 2 by default. The guards check the object size a fortified call carries. -
ASan’s contract (§1, §5). The unit is parsed with AddressSanitizer in its language options, so
__has_feature(address_sanitizer)is true,__SANITIZE_ADDRESS__is defined andno_sanitize("address")is accepted; at the end of the unit, before code generation, the option is taken out again, so ASan’s instrumentation never runs, and GuardInsert removes thesanitize_addressfunction attribute. Clang’s_FORTIFY_SOURCE=0predefine for ASan on Darwin is undone unless the command line chose a level. Preprocessor-only jobs get the same macros.-fno-weavec-asan-compatkeeps the language options as the command line set them, and a command line that asks for an address sanitizer itself keeps its own. -
The unsafe-region collector (
UnsafeRegions) runs over the AST before code generation (see Unsafe regions). -
Four LLVM passes (
registerGuardPassesinlib/Frontend/GuardPass.cpp), at every optimisation level:Pass Extension point Files Does GuardInsertPipelineStartEPGuardInsert.cpp,GuardLibrary.cppputs a marker before each access and each memory argument of a library call, redirects the library calls whose wrappers write, records the ledger rows, drops sanitize_addressGuardPrunePeepholeEPGuardInsert.cppafter inlining, removes the markers rule 6.1 proves on a local or global at constant offsets, so that SROA still promotes those locals; manages the scope barriers GuardLoopVectorizerStartEPGuardLoops.cpprule 6.4: versions loops whose whole ranges one check covers GuardExpandOptimizerLastEPGuardExpand.cpp,GuardFrames.cpp,GuardGlobals.cpp,GuardLibrary.cpprules 6.1–6.3; the frame layout, shared space and scopes; global redzones; the unpoisoning before noreturncalls; the late library redirections; each marker into its inline checkGuardPass.cppand the private headerGuardPassImpl.hhold what the passes share: the module’s context, the markers, the runtime’s entry points, the site records, and the inline checks of the shadow. The passes run on 64-bit targets only, and skip declarations, naked functions and the runtime’s own functions. Anavailable_externallybody (a C99 inline definition) is guarded, since the inliner copies it into the unit. Verify mode skips GuardLoop.
Guards are inserted first and expanded last because the optimiser exploits
undefined behaviour: an out-of-bounds read in a loop can make the loop
infinite, and a read after free can become undef, before a pass at the
end of the pipeline sees the access. GuardInsert’s markers are
void __weavec.guard(ptr at, i64 width, i32 flags, ptr nullBase, i32 row)void __weavec.range(ptr at, i64 length, i32 flags, i32 row)i64 __weavec.strlen(ptr string, i64 max, i32 flags, i32 row)void __weavec.disjoint(ptr destination, ptr source, i64 length)They are nounwind and nomerge, read only the runtime’s memory (a string
marker the string too), capture nothing, and are not willreturn: the
optimiser may neither drop nor merge them, nor move an access past one, but
it may hoist and remove them as it does loads. Beside each lifetime marker
GuardInsert puts __weavec.scope(), which writes the runtime’s memory, so
that no marker is hoisted above the lifetime.start of a local declared in
a loop; GuardPrune drops the barriers of locals SROA promoted and adds them
beside the lifetime markers inlining brought (RFC 0035 amendment 2).
What is collected (RFC 0035 §2.1): a load, store, atomicrmw or
cmpxchg is a guard of the bytes of its type; llvm.memcpy,
llvm.memmove and llvm.memset a range for each memory operand; a masked
load or store, a gather or a scatter one access per lane under its mask
bit; a call of a library-table function a range or a string for each
memory argument. An access to a stack or global object at a constant
offset inside it gets no marker and no ledger row, so the object stays
promotable to registers. Inline assembly operands, accesses in a
non-default address space and the passes’ own shadow accesses are not
collected.
Nothing else changes: no AST is rewritten and code generation is not
deferred. A unit compiled with -fweavec-checks=none or -fno-weavec, or
as C++, Objective-C, OpenCL or CUDA, is compiled as Clang compiles it.