|
| 1 | +# Cross-Compilation Over openkal |
| 2 | + |
| 3 | +Conventional cross-compilation is served by a payload. A toolchain is built for |
| 4 | +one target, its driver has exactly one answer, and reaching a second target |
| 5 | +means obtaining a second toolchain. The number of payloads a distribution must |
| 6 | +publish is therefore the number of host-target pairs it supports. |
| 7 | + |
| 8 | +openkal changes what is being crossed. The target side — the platform interface, |
| 9 | +the C library, the compiler runtime and the C++ runtime — becomes a set of |
| 10 | +packages resolved from the dependency graph and compiled from source by whichever |
| 11 | +compiler is running. What remains for the compiler is code generation, and one |
| 12 | +Clang binary emits every object format it was built with. |
| 13 | + |
| 14 | +This document states the model, what a project writes, what the ecosystem |
| 15 | +supplies, and the limits that have been measured. |
| 16 | + |
| 17 | +## The Claim |
| 18 | + |
| 19 | +An ecosystem of N platforms and M architectures requires N implementations of |
| 20 | +one interface rather than N×M toolchains. The count follows from where the |
| 21 | +target side lives: a package built from source is built for whatever target the |
| 22 | +compiler is asked to emit, so a platform implementation is written once and |
| 23 | +reaches every architecture the compiler supports. |
| 24 | + |
| 25 | +The claim is verified by a matrix of three hosts and three targets, each cell |
| 26 | +building one source and running the result. |
| 27 | + |
| 28 | +## What A Project Writes |
| 29 | + |
| 30 | +```toml |
| 31 | +[dependencies] |
| 32 | +openkal-llvm-runtime = "0.1.1" |
| 33 | + |
| 34 | +[toolchain] |
| 35 | +default = "llvm@22.1.8" |
| 36 | +``` |
| 37 | + |
| 38 | +Two lines. The first selects three layers of the target side; the second names |
| 39 | +a compiler and says nothing about where anything else comes from. |
| 40 | + |
| 41 | +Targets are given on the command line: |
| 42 | + |
| 43 | +```bash |
| 44 | +mcpp build --target x86_64-linux |
| 45 | +mcpp build --target aarch64-macos |
| 46 | +mcpp build --target x86_64-windows-gnu |
| 47 | +``` |
| 48 | + |
| 49 | +No `[target.<triple>]` section is required for a hosted target, and no |
| 50 | +preprocessor directive is required in the source. A worked example is |
| 51 | +[examples/06-openkal-cross](../examples/06-openkal-cross). |
| 52 | + |
| 53 | +## What The Ecosystem Supplies |
| 54 | + |
| 55 | +| Package | Layer | Content | |
| 56 | +|---|---|---| |
| 57 | +| `openkal` | — | the specification, and the C++ modules that declare it | |
| 58 | +| `openkal-linux` | `kernel-abi` | the reference implementation, on Linux system calls | |
| 59 | +| `openkal-macos` | `kernel-abi` | on the macOS system-call surface | |
| 60 | +| `openkal-windows` | `kernel-abi` | on Win32 and the object manager, using no C runtime symbol | |
| 61 | +| `openkal-opensbi` | `kernel-abi` | on the RISC-V Supervisor Binary Interface, no operating system | |
| 62 | +| `openkal-uefi` | `kernel-abi` | on UEFI Boot Services, before an operating system exists | |
| 63 | +| `openkal-musl` | `c-abi` | musl redirected onto openkal, ported once | |
| 64 | +| `openkal-llvm-runtime` | `compiler-runtime`, `c++-abi` | compiler-rt builtins, libunwind, libc++abi and libc++, configured for openkal-musl | |
| 65 | + |
| 66 | +A project names the last of these. The others follow from its dependencies. |
| 67 | + |
| 68 | +## Why The Compiler Must Be LLVM |
| 69 | + |
| 70 | +`openkal-llvm-runtime` declares the requirement rather than leaving it to be |
| 71 | +discovered: |
| 72 | + |
| 73 | +```toml |
| 74 | +requires = ["mcpp:compiler=llvm"] |
| 75 | +``` |
| 76 | + |
| 77 | +Its sources are libc++'s, and its `std` module source in particular is compiled |
| 78 | +by Clang. Handing that source to GCC fails inside libc++'s own headers, in a |
| 79 | +message naming a file the reader has never opened: |
| 80 | + |
| 81 | +``` |
| 82 | +fatal error: __config: No such file or directory |
| 83 | +``` |
| 84 | + |
| 85 | +With the requirement declared, the build refuses the combination before it |
| 86 | +compiles anything, and names the command that selects a compiler which satisfies |
| 87 | +it. |
| 88 | + |
| 89 | +## How The Target Is Chosen |
| 90 | + |
| 91 | +The target row of mcpp's own vocabulary may carry a toolchain convention. That |
| 92 | +convention names the payload which supplies **that target's C library**, and it |
| 93 | +applies only when two conditions hold: the manifest states nothing for the |
| 94 | +target, and nothing in the dependency graph supplies the target's system. |
| 95 | + |
| 96 | +The second condition is knowable only after the graph is resolved. A project |
| 97 | +whose C library comes from `openkal-musl` therefore keeps the compiler it asked |
| 98 | +for, while a project with no dependencies still receives the payload the row |
| 99 | +names. Both behaviours were measured; deciding either way in advance was wrong |
| 100 | +for the other. |
| 101 | + |
| 102 | +## The Environment Segment |
| 103 | + |
| 104 | +On Linux the third segment of a target triple names the C library. Under openkal |
| 105 | +the C library comes from the graph, so a triple that names one states a request |
| 106 | +the graph may not honour: |
| 107 | + |
| 108 | +``` |
| 109 | +mcpp build --target x86_64-linux-gnu # asks for glibc |
| 110 | + c-abi musl (openkal-musl@0.3.3, graph) |
| 111 | +``` |
| 112 | + |
| 113 | +The graph decides. Omitting the segment states no request and produces the same |
| 114 | +artifact: |
| 115 | + |
| 116 | +``` |
| 117 | +mcpp build --target x86_64-linux |
| 118 | +``` |
| 119 | + |
| 120 | +The build reports the mismatch when the segment is present and disagrees. It is |
| 121 | +a report rather than a refusal, because the segment is ignored rather than |
| 122 | +violated. Measured on one host, `x86_64-linux` against `x86_64-linux-musl`: the |
| 123 | +two executables differ, and after stripping they are byte-identical. What |
| 124 | +differs is the debug information, which records the output directory, and the |
| 125 | +directory is named after the triple. The code is the same code. |
| 126 | + |
| 127 | +On Windows the same segment names the object ABI instead — `gnu` for PE with the |
| 128 | +GNU ABI, `msvc` for PE with Microsoft's — and both are compatible with more than |
| 129 | +one C library. The mismatch report is therefore scoped to platforms where the |
| 130 | +segment names a C library. |
| 131 | + |
| 132 | +## Bare Metal |
| 133 | + |
| 134 | +A target with no operating system is the same model with the platform layer |
| 135 | +supplied by firmware rather than by a kernel. `riscv64-none-elf` over OpenSBI |
| 136 | +runs the same source as a hosted target, including `import std`, because the |
| 137 | +standard library it uses is the one the graph supplied rather than the |
| 138 | +compiler's own. |
| 139 | + |
| 140 | +Two things must be declared, both properties of the board rather than defaults: |
| 141 | + |
| 142 | +```toml |
| 143 | +[target.riscv64-none-elf] |
| 144 | +sysroot = "" |
| 145 | +runner = ["qemu-system-riscv64", "-machine", "virt", "-nographic", |
| 146 | + "-no-reboot", "-bios", "default", "-kernel"] |
| 147 | +``` |
| 148 | + |
| 149 | +`sysroot = ""` selects the zero-libc tier. Which machine model and which |
| 150 | +firmware mode to use are board facts, and an engine that guesses one is an |
| 151 | +engine a different board has to fight. |
| 152 | + |
| 153 | +### The Source Is The Same, The Program Is Not |
| 154 | + |
| 155 | +"The same source" is a claim about the toolchain and the standard library, and |
| 156 | +it holds: `import std` works, the C++ runtime is the one the graph supplied, and |
| 157 | +no `#if` distinguishes the targets. It is not a claim that any given program |
| 158 | +builds for any given target, and the specification is explicit about why. |
| 159 | + |
| 160 | +A bare-metal backend provides some interfaces and not others. `openkal-opensbi` |
| 161 | +provides `abort`, `stream`, `memory`, `env` and `time`; it provides no |
| 162 | +filesystem and no tasks, because the machine has none. Clause 6.1 makes that |
| 163 | +absence a link-time fact: |
| 164 | + |
| 165 | +> An interface that an implementation does not provide is absent as a link-time |
| 166 | +> definition, and a consumer that uses it fails to link. |
| 167 | +
|
| 168 | +A capability word therefore answers a narrower question than it first appears |
| 169 | +to. It says how an implementation behaves *within an interface it provides* — |
| 170 | +whether names are compared case-sensitively, what the granularity of a clock is. |
| 171 | +Whether the interface exists at all is answered before that, by the dependency |
| 172 | +graph, and failing that by the linker. |
| 173 | + |
| 174 | +The distinction is easy to lose, because the query is an inline function over a |
| 175 | +data object, so a program that merely asks whether a filesystem exists takes the |
| 176 | +address of `kal_fs_props` and fails to link with no filesystem call anywhere in |
| 177 | +it. Defining that word as zero in the backend removes the error and is the one |
| 178 | +remedy the clause forbids: the program then proceeds past the point the linker |
| 179 | +existed to stop it at. It was tried, published as `openkal-opensbi@0.1.3`, and |
| 180 | +retracted. |
| 181 | + |
| 182 | +### Two Routes To A Bare x86_64 Machine |
| 183 | + |
| 184 | +An x86_64 machine with no operating system is reached in two different ways, and |
| 185 | +the difference is what loads the program. |
| 186 | + |
| 187 | +| Route | Target | Platform layer | Entry | |
| 188 | +|---|---|---|---| |
| 189 | +| UEFI application | `x86_64-windows-gnu` | `openkal-uefi` | firmware, with Boot Services available | |
| 190 | +| Kernel, or raw bare metal | `x86_64-none-elf` | none, or `openarch` | the reset vector, with nothing beneath | |
| 191 | + |
| 192 | +A UEFI application is PE/COFF entered through the Microsoft x64 calling |
| 193 | +convention. Both are properties the LLVM toolchain already has, so its target is |
| 194 | +the same triple as a Windows program and firmware function pointers are called |
| 195 | +directly. What distinguishes it from a Windows build is which implementation of |
| 196 | +the platform interface the graph resolved, together with three link flags that |
| 197 | +select `IMAGE_SUBSYSTEM_EFI_APPLICATION`. |
| 198 | + |
| 199 | +A kernel has no firmware services to call. Its target is `x86_64-none-elf`, the |
| 200 | +zero-libc tier: no C library on the compile line, no library directory on the |
| 201 | +link, and `#include <stdio.h>` does not resolve. The program is entered at its |
| 202 | +own `_start` and reaches hardware directly. |
| 203 | + |
| 204 | +`openarch` is the layer such a program builds on. It is not a platform interface |
| 205 | +and does not answer to `mcpp:kernel-abi`; it is the architecture mechanism — |
| 206 | +execution contexts, traps, per-CPU state and address spaces — presented as one |
| 207 | +interface over several instruction sets, with a backend package per instruction |
| 208 | +set. A kernel depends on it and supplies its own platform layer, or none. |
| 209 | + |
| 210 | +### Why x86_64 Bare Metal Required Engine Work |
| 211 | + |
| 212 | +`riscv64-none-elf` and `aarch64-none-elf` are rows in a table and nothing more: |
| 213 | +Clang has a BareMetal toolchain for both, drives their links itself and reaches |
| 214 | +`ld.lld`. It has none for x86_64, so that triple falls through to the generic |
| 215 | +GCC toolchain, whose linker is the host's `g++`: |
| 216 | + |
| 217 | +``` |
| 218 | +g++: error: unrecognized command-line option '-fuse-ld=…/ld.lld' |
| 219 | +``` |
| 220 | + |
| 221 | +Measured for every spelling of a bare x86_64 triple, and not correctable by any |
| 222 | +flag. The row therefore carries a linker emulation and mcpp invokes `ld.lld` |
| 223 | +itself, which is also why the host toolchain must be shown not to participate in |
| 224 | +such a link. |
| 225 | + |
| 226 | +## Measured Limits |
| 227 | + |
| 228 | +Three, recorded because each was found by building rather than by reading. |
| 229 | + |
| 230 | +**A backend must define every capability word.** The specification's queries are |
| 231 | +inline functions over property objects, so a program that merely asks whether a |
| 232 | +filesystem exists takes the address of `kal_fs_props`. A backend that omits the |
| 233 | +words for layers it lacks makes the question fail to link on exactly the class of |
| 234 | +machine the question exists for. |
| 235 | + |
| 236 | +**Two suppliers of one layer is an error rather than a choice.** A C library, a |
| 237 | +platform interface and a C++ runtime are mutually exclusive. Selecting the wrong |
| 238 | +one does not fail the link; it produces a program that runs and intermittently |
| 239 | +does not. |
| 240 | + |
| 241 | +**A payload's C++ runtime cannot sit above a foreign C library.** Its |
| 242 | +`__config_site` records the configuration it was built with. The resolver's |
| 243 | +structure prevents the combination on the default path, and a diagnostic covers |
| 244 | +the paths where a project overrides the contract explicitly. |
| 245 | + |
| 246 | +## Reference |
| 247 | + |
| 248 | +[docs/14 — The Target Side](14-target-side.md) for the five layers, the four |
| 249 | +origins and the rules. [SPEC-002](spec/target-side.md) for the normative |
| 250 | +statement of the capability grammar. |
0 commit comments