journal 2026-05-05

P70 libc revival - newlib, FPU, and AtomVM ping-pong

p70libcnewlibfpufloating-pointatomvmrtl-pass

Claude’s last session stopped at exactly the useful point: phase 2 had been committed, and phase 3 had just rewritten the libc smoke Makefile before the model hit a rate limit.

The committed phase 2 result is solid:

The captured phase-2 UART log shows all three forced asm round-trips passing.

Today I picked up phase 3. The reset path now sets gp, clears .bss, calls __libc_init_array, runs main, then calls __libc_fini_array if main returns. The linker script now keeps the init/fini arrays and provides __global_pointer$. The P68 libc shim no longer overrides malloc, free, calloc, or realloc; newlib’s allocator uses _sbrk.

Then we stopped pretending the dormant FP arithmetic in newlib was someone else’s problem. P70 now implements the D subset that the linked image reaches: add/sub/mul/div, multiply-add variants, comparisons, class, and int/double conversions.

The smoke program links against real -lc -lm -lgcc and exercises stdio, heap allocation, and %f formatting:

P70 phase 3+4 libc/FPU revival
  printf-int: 1 22 333
  printf-hex: 0xdeadbeef
  printf-str: hello from real libc
  printf-double: 3.125
  snprintf into malloc'd buf at 0xe548
  malloc(16)=0xe548 malloc(128)=0xe560 malloc(1024)=0xe5e8
  realloc round-trip: abcdefgXYZ

P70 phase 3+4 PASS - newlib stdio + malloc + FPU work

Harness:

[harness] run ended after 60579 post-load cycles
[harness] halted=1, halt_code=0x00000001

Josh called the right ending: P70 should not stop at “printf works”; it should show AtomVM getting past the P68-era issues as the last step. The first try only ran the old hello beam and still hit the expected missing-init.beam wall. That was not enough, so the final shape bundles lib.avm and runs the process ping-pong example.

The P70 AtomVM build keeps the pinned P68 AtomVM checkout, builds a P70 libAtomVM with floating point enabled, uses a host CMake build to package AtomVM’s atomvmlib.avm, uses the host packbeam from that checkout, and assembles the same 16 MiB boot image layout: stage-0 at zero, lib.avm at 8 MiB, and main.avm at 12 MiB.

Captured AtomVM run:

AtomVM on P70 (real newlib + FPU subset)
Starting AtomVM revision 0.8.0-dev+git.5e47d64
Found startup beam: pingpong.beam
pingpong starting
pingpong done: 32 round trips
Return value: ok
AtomVM exited result=0

The important result is that AtomVM now loads the bundled stdlib pack, gets past init.beam, runs pingpong.beam, spawns a process, sends 32 ping/pong message round trips, prints through io:format/2, and halts PASS:

[harness] === run ended after 9193286 post-load cycles ===
[harness] halted=1, halt_code=0x00000001

Honest status

stepstatus
F/D bit-motion asm smokePASS
libc smoke buildPASS
libc smoke Verilator runPASS
%f formatting through _dtoa_rPASS
AtomVM stdlib pack bundled as lib.avmPASS
AtomVM process ping-pong on P70 real libcPASS
F/D architectural complianceNOT RUN
LibreLane hardeningNOT RUN

This is now rtl-pass, not hardened. It is also still not full F/D compliance: subnormals, exception flags, sqrt, min/max, sign injection, and broad single-precision coverage are not proven. The next sensible AtomVM work is broader library/application coverage on top of this runtime, not the basic process demo anymore.