MagicFun1241
## Summary
Replace C hex array literals with GNU assembler `.incbin` directives for `$embed_file`, matching Go's `//go:embed` SRODATA approach. Raw bytes are written directly into `.rodata` via assembly, eliminating the intermediate C source text expansion.
## Problem
V's current embed implementation generates C hex array literals for embedded file data. A 10MB file produces ~53MB of C source, causing:
- **5MB practical limit** — files above this trigger a warning and make compilation prohibitively slow
- **6× source expansion** — 8MB file → 48MB of C source that the C compiler must parse
- **Huge compiler RSS** — 10MB embed → 1.1GB peak compiler memory
- **Slow compilation** — compile time scales linearly with file size (10MB embed → 7.6s)
## Approach
For GCC, Clang, MinGW, and TCC (when a system assembler is available), each embedded file is written as a `.S` assembly file using `.incbin` pointing at the original binary. The `.S` file uses `#if defined(__APPLE__)` / `#elif defined(_WIN32)` / `#else` to emit the correct section directives for each platform (Mach-O `__TEXT,__const`, COFF `.rdata`, ELF `.rodata`) and handle macOS's extra `_` C symbol prefix. ELF-only directives (`.type`, `.size`) are excluded on Windows.
The C hex array path remains as fallback for MSVC, Windows cross-compilation, `-o .c` output, `.o` output mode, wasm targets, and `build_module` (-usecache) mode — zero regressions.
Compressed embeds (`.zlib`) keep their temp `.bin` files alive for `.incbin` to reference at assembly time, with cleanup scheduled via `cleanup_files`.
Symbol names use fnv1a hash-based naming (`_v_embed_blob_`) to avoid collisions during parallel test execution. Deduplication uses `EmbeddedFile.hash()` instead of struct equality to handle compressed files with random temp paths.
`gen_c()` return type stays `strings.Builder` — embed data flows through `Builder` struct fields (`embedded_asm`, `embedded_o_files`, `embedded_temp_files`), compiled to `.o` in `gen_c()` (parallel_cc) or `build_c()` (serial), then added to linker args via the same path as thirdparty `.o` files.
## Design decisions
### Why .incbin instead of alternatives considered
| Option | Pros | Cons |
|--------|------|------|
| **`.incbin` (chosen)** | Zero C source expansion; compiler-agnostic; Go uses this pattern | Requires assembler step |
| `fread` at runtime | Simple | No embedded data — breaks the contract of `$embed_file` |
| Linker `objcopy --input binary` | No assembler needed | Extra tool dependency; harder cross-compilation |
| Keep C hex arrays | Works everywhere | 6× expansion; 5MB limit; OOM on large files |
### Platform fallbacks
| Platform / Mode | Path | Reason |
|----------------|------|--------|
| GCC, Clang, MinGW | `.incbin` | Native assembler handles `.S` files |
| TCC (with clang/gcc/cc available) | `.incbin` | Falls back to system assembler for `.S` files |
| TCC (no system assembler) | C hex arrays | Raw `as` excluded: no `-c` flag, no `#if` preprocessing |
| MSVC | C hex arrays | MSVC assembler doesn't support `.incbin` |
| Windows cross-compile | C hex arrays | Cross-linker can't consume host `.o` files |
| wasm32 / wasm32_emscripten | C hex arrays | WASM object format incompatible with ELF/Mach-O `.S` |
| `build_module` (-usecache) | C hex arrays | Separate `.o` can't be merged into cached module `.o` |
| `-o .c` / `-o .o` / stdout | C hex arrays | No binary output to link embed `.o` into |
### Windows / COFF handling
The `.S` assembly uses `#elif defined(_WIN32)` to emit `.section .rdata` (COFF read-only data) instead of `.section .rodata` (ELF), and skips `.type`/`.size` directives that COFF assemblers reject.
### TCC handling
Two-level fallback:
1. **Codegen time** (`should_use_incbin_embed`): check if clang/gcc/cc is available. If yes → `.incbin`. If no → C hex arrays.
2. **Build time** (`compile_embedded_asm_files`): if TCC reaches this point, find the system assembler. If not found → `verror()` with clear message.
### Linux cross-architecture
When cross-compiling Linux x86_64 → Linux arm64 (same OS, different arch), the `.S` file must be assembled with `-target` matching the cross-compilation triple. The condition now checks `host_os != .linux || host_arch != b.pref.arch`.
### Deduplication
`EmbeddedFile` deduplication uses `hash()` (based on apath, compression_type, is_compressed, len) instead of struct equality, because `compressed_temp_path` contains a random ULID that would prevent deduplication of the same compressed file embedded twice.
### Hash-based symbol naming
Parallel test execution (`v test`) compiles multiple binaries simultaneously. Sequential names like `_v_embed_blob_0` collide; hash-based names (`_v_embed_blob_`) ensure uniqueness per file path.
### Parallel cc ordering
`parallel_cc()` runs inside `gen_c()`, before `build_c()`. Embed `.o` files are compiled in-place in `gen_c()` with a guard (`embedded_o_files.len == 0`) to prevent double-compilation in `build_c()`.
## Performance comparison
macOS, Apple clang 17, averaged over 3 runs:
| Size | Method | Compile (s) | Compiler RSS (MB) | C source (MB) | Binary (MB) |
|------|--------|-------------|-------------------|---------------|-------------|
| 1KB | hex | 1.10 | 105 | 0.40 | 0.20 |
| 1KB | **incbin** | **0.51** | **98** | **~0** | 0.20 |
| 100KB | hex | 1.18 | 112 | 0.91 | 0.31 |
| 100KB | **incbin** | **0.48** | **97** | **~0** | 0.31 |
| 1MB | hex | 1.78 | 206 | 5.70 | 1.21 |
| 1MB | **incbin** | **0.48** | **98** | **~0** | 1.21 |
| 5MB | hex | 4.39 | 623 | 26.95 | 5.24 |
| 5MB | **incbin** | **0.49** | **102** | **~0** | 5.24 |
| 10MB | hex | 7.61 | 1,143 | 53.52 | 10.28 |
| 10MB | **incbin** | **0.50** | **107** | **~0** | 10.28 |
| 25MB | hex | *impossible* | *OOM* | ~134 | — |
| 25MB | **incbin** | **0.51** | **122** | **~0** | 25.40 |