Follow-up to the scaffolding session.
The scaffolding pass ended with everything written but nothing compiled.
This session takes it from there to a linked bare-metal ELF with zero
undefined references. Boot blob assembly and sim are still NOT RUN
because packbeam (the AtomVM tool that bundles .beam -> .avm)
needs rebar3, which is a separate devshell yak.
What landed
flake.nix
Added two parallel pieces:
- Bare-metal RV32 newlib toolchain via
pkgs.pkgsCross.riscv32-embedded.buildPackages.gcc. Prefix isriscv32-none-elf-. Coexists with the kernel build’sriscv64-unknown-linux-gnu-multilib — no PATH collision.CROSS_COMPILEstays pointed at the Linux toolchain so the kernel build doesn’t break. - gperf, called from
libAtomVM/CMakeLists.txtto generatebifs_hash.h/nifs_hash.hat configure time.
test/atomvm_baremetal/
A small wrapper CMakeLists.txt + toolchain file. The wrapper
add_subdirectory()s the upstream vendor/AtomVM/src/libAtomVM
without going through the top-level CMakeLists.txt (which
hard-fails on any system that isn’t Darwin/Linux/FreeBSD/DragonFly).
The wrapper pre-seeds the rp2 platform’s set(HAVE_FOO "" CACHE INTERNAL FORCE) list — those symbols are declared in newlib
headers but not implemented, and without the forces the
CheckCSourceCompiles probes succeed and we’d compile in code that
fails at link.
The toolchain file selects riscv32-none-elf-, sets
CMAKE_SYSTEM_NAME=Generic (so libAtomVM/CMakeLists.txt skips its
per-system platform link library), and crucially sets the arch flags
to -march=rv32imafd_zba_zbb_zicsr_zifencei -mabi=ilp32d.
The “lying about FP” hack
The chip has no FPU. The natural ABI is -mabi=ilp32. But the
nixpkgs riscv32-none-elf toolchain is built --disable-multilib
against rv32gc/ilp32d (hard-FP), so its libc.a and libgcc.a
are tagged “double-float” — and ld refuses to link them against
soft-float objects with:
can't link double-float modules with soft-float modules
I went down two dead ends before settling on the right fix:
- Provide soft-FP stubs ourselves (
__muldf3,__adddf3, etc.) so-lgcccould be dropped. This works for libgcc but newlib’s ownlibc.aalso wants the helpers internally, and dozens of newlib objects are tagged hard-FP regardless of what symbols we provide. Dead end. - Stop linking libc (
-nostdlib+ reimplement printf/strtoul from scratch). Too much surface to reproduce. - Lie about FP to the compiler: pass
-march=rv32imafd_zba_zbb -mabi=ilp32dat every compile site. The libraries are now ABI-compatible with our objects, the link succeeds, and our reachable C never performs floating-point arithmetic so the compiler doesn’t emit FP instructions and the chip (which lacks FP regs) never sees one. This is what committed.
This is a sharp edge. If a future P68 demo actually uses floats
the compiler will emit FP instructions and the chip will trap as
illegal-instruction. The long-term fix is either (a) a multilib
toolchain rebuild targeting rv32ima/ilp32 or (b) a vendored
soft-fp compiler-rt + custom newlib. Both are too expensive for
hello-world. Documented in the toolchain file and the project Makefile.
What now compiles
[37/37] Linking C static library libAtomVM/liblibAtomVM.a
riscv32-none-elf-gcc ... -o p68_atomvm.elf ... \
-Wl,--start-group libAtomVM/liblibAtomVM.a -lc -lm -lgcc -Wl,--end-group
ld.bfd: warning: p68_atomvm.elf has a LOAD segment with RWX permissions
The RWX warning is from our linker script lumping .text, .data,
and .bss into one segment — fine for bare-metal where there’s no
MMU enforcing W^X. Not a blocker.
$ readelf -h p68_atomvm.elf | head -12
Type: EXEC (Executable file)
Machine: RISC-V
Entry point address: 0x0
Flags: 0x5, RVC, double-float ABI
$ nm p68_atomvm.elf | grep -E "_start|main|globalcontext_run"
00000000 T _start
0000047c T main
00001588 T globalcontext_run
$ nm -u p68_atomvm.elf | wc -l
0
Zero undefined references. Entry at 0x0 is _start (sets sp,
mtvec, clears bss, jumps to main). 6 MiB ELF, 728 KiB raw binary.
Honest status
| step | status |
|---|---|
| flake.nix toolchain + gperf | PASS |
vendor-fetch (clones AtomVM at pinned commit) | PASS |
libatomvm-build (cmake + ninja + 37 obj) | PASS |
beam (erlc hello.erl) | PASS — 880 byte .beam |
| Bare-metal ELF link | PASS — zero undefined refs |
objcopy -O binary -> raw .bin | PASS — 728 KiB |
packbeam host tool | NOT RUN — needs rebar3 |
main.avm AVMPack assembly | NOT RUN |
| Boot blob (16 MiB image with ELF + AVMPack) | NOT RUN |
| Verilator sim shows AtomVM banner | NOT RUN |
Surprises
- The
nif_collection_init_all/platform_defaultatoms_init/platform_nifs_get_nif/sys_get_infoquartet is referenced unconditionally from libAtomVM but lives in the platform port (per-port). The first pass missed all four; second pass adds them as no-op stubs (NULL/term 0returns). settimeofdayis referenced fromposix_nifs.c. Newlib’slibc.adoesn’t provide it. Stub returning -1.- libAtomVM’s CMake target name is
libAtomVM, so the static archive it produces isliblibAtomVM.a. CMake prependedlib. Fixed in the Makefile. - The
--gc-sectionsdiscipline matters: without-ffunction-sections -fdata-sectionson every libAtomVM compile, the link would still pull in code that references undefined symbols by side effect. We set those flags in the toolchain file so they apply uniformly.
Next session
In rough order:
- Get
packbeamworking. The AtomVM CMakeLists builds it viarebar3 escriptize, which needs Erlang’s rebar3 package manager. Either addpkgs.rebar3to flake.nix or vendor a pre-builtpackbeamescript. make allto assemble the 16 MiB boot blob.make verilator-run. If we get to “Found startup beam: hello” the FP stubs hold and the platform shim is wired correctly.- If FP traps on real execution, chase down the call site in the disassembly and see whether it’s a cold path (data init) or actually reachable.
What just happened?
Burned a session on the toolchain question and ended with a
bare-metal RV32 ELF that links cleanly against AtomVM. The
“compile bare-metal C against newlib via nixpkgs” recipe is now
documented in flake.nix + test/atomvm_baremetal/. The
remaining work to actually run hello-world on the chip is small
in code-volume but blocked on packbeam, which is a rebar3
problem rather than a code problem.