Skip to content

Build a softfp world of the toolchain - #178

Merged
frangarcj merged 4 commits into
masterfrom
next-softfp
Aug 27, 2026
Merged

Build a softfp world of the toolchain#178
frangarcj merged 4 commits into
masterfrom
next-softfp

Conversation

@frangarcj

Copy link
Copy Markdown
Contributor

Softfp is a build parameter and a world of the channel, never a branch. The 2021 branch was one line — --with-float=hardsoftfp — plus hand publishing, and what killed it was the cost of keeping a fork in step. This is that one line, in the same tree, reached through the profile the lock already carries.

What it adds

  • VITASDK_FLOAT_ABI, selecting --with-float= in the common GCC configure args, so all three GCC stages and everything built with them inherit it.
  • The world stamped into the core: version_info.txt, the pacman Architecture, and the installed makepkg.conf's CARCH. validate-core-package.sh refuses a core whose three answers disagree.
  • The profile is what says which world it is. The staged executor took --profile from the lock, required it, and then dropped it — the comment where it landed said so. describe --profile vita-softfp produced a lock saying softfp and a toolchain that was hard-float, with nothing anywhere noticing. Profiles.cmake already names the worlds this tree publishes, so the ABI each bakes in lives next to the name.
  • 23 ABI shims. The system ABI is hard-float, which the audit settled with code rather than belief. softfp-shim/generate.py derives the AAPCS-base→AAPCS-VFP move sequence for each of the 23 crossings from the real signatures in vita-headers, and patch-softfp-stub-archives.sh splices the wrappers into the stub archives vita-headers installed. A .a resolves through its member index, not member order, so there is no link-order race to win and no recipe changes: packages keep linking -lSceGxm_stub as before.

Verified

Built for real in a container with -DVITASDK_FLOAT_ABI=softfp, twice — once before the shims existed and once after. The ABI contract test checks both directions (__ARM_PCS_VFP and Tag_ABI_VFP_args present in hard, absent in softfp), the 73 public headers each compile on their own, and the shim test links the 23 against their real stubs and reads the map file to prove each resolved against the wrapper rather than the renamed trampoline.

Known gap

A caller carrying its own hard→softfp wrapper — the vitasdk-softfp org's vitaGL with SOFTFP_ABI=1 — would translate twice against this. That is a line in the migration guide, not a mechanism here: a new world has no binary legacy to be compatible with, so the fix is to stop wrapping.

🤖 Generated with Claude Code

softfp died as a maintained buildscripts branch in 2022 (a synced fork is
expensive to keep up), but the workload didn't: github.com/vitasdk-softfp
keeps shipping it for Android-to-Vita ports. Coming back as a branch risks
the same drift, so VITASDK_FLOAT_ABI makes it a --with-float switch
instead, inherited by gcc-base/gcc-final/newlib/pthread-embedded without
touching any of their recipes. The world's identity is stamped into
version_info.txt, pacman's Architecture and makepkg's CARCH so a softfp
core can never be mistaken for the default one.

The Sony modules are hard-float (confirmed against vitaGL's existing
naked wrapper for sceGxmSetViewport). A softfp caller and a hard-float
stub disagree on where 23 functions' float/double arguments and returns
live. softfp-shim/ derives the AAPCS-base-to-AAPCS-VFP register move for
each straight from vita-headers' real signatures, and
patch-softfp-stub-archives.sh splices the generated wrapper into the
already-installed stub archives: the real object gets renamed
(objcopy --redefine-sym) and the wrapper takes its place under the
original name. That's a library placed in front of the stubs in
LIB_SPEC in an earlier version of this — it doesn't work, because GCC
expands a caller's own -l flags before LIB_SPEC, and every one of these
five modules is always linked explicitly by whatever package needs it.
An archive splice has no such ordering to lose.

Verified against a real linux/aarch64 container build: the toolchain
contract passes in both directions (hard and softfp), and a link test
reads the linker map to confirm all 23 shimmed symbols resolve against
the wrapper, not the raw stub.
Regeneration is a handful of regex passes over a few vita-headers files,
cheap enough that trusting a checked-in copy to still match vita-headers
was the wrong trade: a stale .S from before a signature change is a wrong
ABI shim that still assembles and links without complaint, and nothing
would have caught it. build-softfp-shim.sh now runs generate.py against
the vita-headers this same build just installed, into a scratch
directory, before assembling anything -- functions.tsv stays checked in
(it records module ownership vita-headers itself does not expose), only
the derived wrappers do not.
Regressed by an earlier git checkout -- in this same session that reverted
this file to HEAD and got only partially undone: vita-makepkg went back to
a plain copy of makepkg.conf.sample, which always says CARCH="vita",
instead of running it through WriteMakepkgConf.cmake. Caught by CI, not
locally: validate-core-package.sh's own world-coherence check failed a
real vita-softfp build with "version_info.txt says vita-softfp,
bin/makepkg.conf CARCH says vita" on every host.
The staged executor takes the profile from the lock, requires it, and then
dropped it -- the comment where it landed said as much: "profile is required
but not yet consumed: no profile -> VITASDK_FLOAT_ABI mapping exists in
CMakeLists.txt yet". So `describe --profile vita-softfp` produced a lock that
said softfp and a toolchain that was hard-float, and nothing anywhere said
the two disagreed.

Profiles.cmake already names the worlds this tree publishes, so it is where
the ABI each of them bakes in belongs, next to the name. CMakeLists takes
VITASDK_PROFILE, refuses one the tree does not publish, and derives the float
ABI and the world's arch from it; build-host.sh passes it in both cmake
invocations, since stage 1 builds the sysroot the later stages import.

A profile wins over VITASDK_FLOAT_ABI, and silently. CMake cannot tell a
cache entry left at its default from one somebody passed, so a check that the
two agree would either miss -DVITASDK_FLOAT_ABI=hard or refuse every
re-configure of an existing build directory. Passing both is passing the same
thing twice.

The test runs stage 1 against a cmake that records its arguments, which is
what the flag being parsed and dropped would have failed.
@frangarcj
frangarcj merged commit 0a518f9 into master Aug 27, 2026
17 of 20 checks passed
@frangarcj
frangarcj deleted the next-softfp branch August 27, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant