Target Profiles

There is one runtime codebase, configured by levers. A target profile is the complete set of lever positions — how a value is represented, how memory is collected, what the scheduler looks like, what the runtime is allowed to do. Every artifact the build produces carries the profile it was made for, and a runtime refuses an artifact built for a different one.

A target name is a label a recipe declares for a set of lever positions, not a thing the system knows. Nothing in the VM, the compiler, the shop, cake, or the runtime branches on “is this a PS1.” There is no supported-target list to be added to, and no per-platform mode. A recipe sets the levers it wants — 32-bit word, fixed-point numbers, single-threaded scheduler, no native loading, a cart carrying no compiler — and that combination is the PS1 build. Any other combination is equally expressible, whether or not anyone has named it.

So this page is normative and The Target Matrix is not: the matrix records recipes that have been built and run, as advice about which positions suit which machine. Recipes is the file these levers are written in, and which of them enter the profile string at all.

The one place a target name appears outside a recipe is a package’s own modules arms, where the package declares how it varies and the recipe’s name selects an arm. That is the package speaking about itself, matched against a string; nothing derives platform, ABI, or toolchain facts from the name, because the recipe already states them. Raw endowments need no arm at all — they are per-platform by being in a per-platform package.

This is also the object the rest of the documentation refers to when it says an image “carries the complete target profile.”

The axes

AxisChoosesValues
target / architecturewhich packages supply platform C, and which toolchain builds itwhatever the recipe names
value wordhow wide a PitValue is64, 32, or 16 bits (nano)
numeric representationhow numbers are encodedsee below
endianbyte order of the mach payloadlittle, big
textthe text body layout and its hashutf32, kim8
recordrecord layoutprofile-selected
GCthe collectorcopy, compact, arena
wota widththe in-runtime encoding’s word widthindependent lever: 16, 32, 64 — chosen for what the target CPU handles fast, which may differ from the value word
provider setwhich endowments the binary suppliesper target
development powersnative build/load; $vm, $hook, $pgo, $inspectlinked in or left out

The first eight are physical: they change how bytes are laid out, so an artifact built under one is meaningless under another. The last two are about authority rather than layout, and both are read off the provider catalog the binary was linked from — so the binary itself answers what it can grant. See Capabilities and Debugging.

The scheduler arrangement — threaded or single-threaded — is a build lever rather than an axis of this profile. It selects which provider files a binary compiles and moves no byte in any artifact, so identical pools load under either. See Recipes.

That division is load-bearing, and only the physical axes belong in the profile string. Installing the call hook adds a branch to the interpreter; it does not move a byte in any artifact. If $hook, $pgo, $inspect or the native-build power entered the stamp, every development runtime would sit in its own artifact universe — the realization cache would fork, and a cart built against a dev binary would refuse to mount on the shipped one it was built for. Which endowments a binary supplies is a link-time fact, invisible to everything it produces.

The practical consequence, since a provider normally enters a build only because something claimed it: a runtime that wants to offer $hook and $pgo with nothing claiming them takes their provider package whole. See Endowments.

Numeric representations

The numeric axis is the one that varies most, because it is where a target’s hardware shows through.

RepValue wordFor
nan6464-bitthe desktop default: tagged integers and short doubles
nan3232-bitthe handheld and console default: NaN banks over binary32, region-relative references
fixed3232-bitfixed-point (Q16.16-class) for a tiny handheld with no FPU and no room for soft-float
val1616-bitthe nano word: small immediate integers and a fixed-point immediate lane, references into a banked pool
fixed64-bita fixed-point pilot under the 64-bit word (Q47.16)
f3264-bita binary32 pilot under the 64-bit word

fixed and f32 share nan64’s profile name and widths and differ only in the numeric lane, which is why the profile string carries rep= separately from value=.

A dec64 rep — decimal, exact for decimal fractions — is the reason the axis exists in this shape. Numbers cross between runtimes as nota, which carries a decimal coefficient and exponent, so a runtime built one way and a runtime built another agree on what a number is without agreeing on how to store it.

The profile string

The axes that affect layout are concatenated into one string that every artifact carries:

host-nan64-v1;value=64;rep=nan64;endian=le;text=utf32;record=…;gc=copy;wota=64

A Mach pool, a cart, and the runtime binary all carry it. Mounting compares them, and a mismatch is refused by naming both — the artifact’s and the runtime’s. That refusal is the point: a cart built for a Playdate is not slightly wrong on a desktop, it is uninterpretable, and failing at mount is better than failing later.

Artifacts never cross profiles

Because the profile decides output bytes, it is part of every derivation key. Two builds differing on any axis are different computations with different keys, so each runtime draws the artifacts built for its own configuration, and the store keeps every profile’s images under their own names.

Portable artifacts sit above this line. Source and mcode are target-neutral by construction — an mcode unit records no target, no layout, and no representation — so one mcode object serves every profile, and only finalization binds it to one. See Build and Artifacts.

Building for another profile

A build does not have to target the machine it runs on. A desktop can produce the complete artifact set for a handheld or a console — including the boot artifacts that machine needs to start at all — by selecting a different profile.

This is more than cross-compiling for another CPU: the value representation, the collector, and the text layout all change, so what is produced is a different runtime and a matching set of artifacts, neither of which the host can run. The host builds them, stamps them, and ships them.

Porting

Bringing up a new machine is writing a recipe and filling a provider package with the C that compiles there. Nothing in the runtime learns its name, no list grows, and no existing page becomes wrong.

The build itself is ordinary tooling — a recipe that picks the lever positions, asks cake for the C, and cements a cart from the actor closure it wants. Nothing about that has to live inside pit, in the same way that targeting a new architecture with a C compiler is a matter of flags and a backend rather than a new mode in the language.

Two things stay separate while porting. What the machine can physically do is the profile — word width, numbers, collector, threads — plus whichever raw endowments the packages the recipe names supply. What a particular build is allowed to do is which actors its boot cart carries and which endowments they hold. A PS1 build wants a 32-bit word because that is the machine, and wants no compiler in its cart because that is a choice; those are different kinds of decision and neither implies the other. See Compilation and Shipping.