Skip to content

Claude/upgrade ape c library mm z gd - #717

Merged
staalmannen merged 3 commits into
mainfrom
claude/upgrade-ape-c-library-mmZGd
Nov 5, 2026
Merged

staalmannen merged 3 commits into
mainfrom
claude/upgrade-ape-c-library-mmZGd

Conversation

@staalmannen

Copy link
Copy Markdown
Owner

No description provided.

Section 0c calls _SEGATTACH in a loop rather than going through mmap
and gets 5 -- the same five 0b got at 2MB and at 16MB, now at 64KB.
A 256-fold range of sizes for a total of 320KB, which leaves no
reading in which memory or address space is the limit, and it says
the ceiling did not move when SEG_THRESHOLD did, which is the point
of asking the kernel rather than the library.

0b is 64/64/64 on the same run: mmap spends no slot at any size a
program is likely to ask for.  Section 0's addresses are the
allocator's now (0x436000, 0x837000, ... at offset 0x37000, 4MB
apart) and still misaligned, so zend still trims, and section 1's
head trim is 0x1c8000, so the real path exercises both trims.

Notes and the invariant updated to cite 0c, since 0b can no longer
produce the number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs
php's Zend/zend_fibers.c is unconditional -- Fiber is a language class
-- and wants either boost.context's GAS assembly or <ucontext.h>.
APExp had neither.  This is the second, chosen because it is libap's
own code and reusable; the other ten architectures can follow the same
shape, which is why the machine part is ucontext_arch.h and the
register work is in arch/amd64.

It is setjmp.s with the return-value convention changed, and
deliberately so: the two problems are the same one.  Both of that
file's findings carry over.  Eight registers and not two, because 6c
has REGEXT = D_R15 and R13/R12 can follow R15/R14 as caches for global
variables, so a switch restoring only SP and PC leaves the resumed
code reading another context's globals.  And the restore must not
write to the stack: ADDQ $8,SP then JMP rather than storing the PC at
0(SP) and RETing, since a context resumed at the top of a fresh fiber
stack is exactly the case that faults.

AX is zeroed before the jump, for the reason longjmp does it: without
that line a getcontext resumed by setcontext returns whatever was in
AX and its caller believes the number.

The first design kept the ucontext pointer in a callee-saved register
across the context's own function.  There are none: a host 6c -S on a
function that computes three values, calls through a function pointer
and then uses them spills all three to the stack and touches BX and
R12-R15 not at all.  The pointer goes on the new stack instead.

That leaves one number spanning two languages -- makecontext writes
the pointer at base[UC_ARGMAX+1], _ctxfinish reads it at
sp[UC_ARGMAX] -- and off by one the pointer read back is an argument,
so uc_link is garbage and the usual consequence is exit(0),
indistinguishable from a clean finish at a prompt.  So _ctxreturn
passes its stack pointer to C and does nothing else; no offset into
ucontext_t appears in assembly at all.  UC_ARGMAX is a cap rather
than a convention because only argument zero is in a register here,
so the bookkeeping cell has to follow a fixed number of slots.

mc_regs[3..7] are left as they are, which is why POSIX makes
getcontext a precondition of makecontext: those registers may hold
6c's caches for global variables, so the right value for a new
context is the caller's.

stack_t is defined in <ucontext.h> and not in <signal.h>.  POSIX puts
it beside sigaltstack and APExp has no sigaltstack, so a probe
compiling `stack_t ss;' against <signal.h> would answer yes and then
call the function.  The external/ grep found 39 files, every C use a
consumer behind HAVE_SIGALTSTACK and the two config.h hits gnulib's
replacement slot.

Two instruments.  ucontext-test.c is the switch and has a real
reference implementation, which is rare here: 0 failures against
glibc before the VM sees it.  makectx-xcheck.{c,sh} is the frame
arithmetic on the host, and it compiles the real ucontext.c rather
than a copy -- no extraction, no markers to rot -- because everything
structural in that file is C.  It has no external oracle, so its
control is a deliberate break: the ucp cell off by one gives 3
failures, SP not one slot below gives 1.

And its first run reported its own stub.  Section 3 failed on "did not
exit" because the real _setmcontext never returns while the stub
recorded and returned, falling through to _ctxfinish's exit(0).  The
stub longjmps now.  The assertion was right and the fake was wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs
The measurement is load-bearing beyond this port: a host 6c -S shows
kencc amd64 spilling everything across a call, so nothing may be kept
in a register through a call into arbitrary code.  It goes in the
invariant list because it is surprising and because main9.s says
something that reads like the opposite.

php blocker 1 moves from open to "written and host-checked, unmeasured
on the VM" -- the port itself has not been pointed at it yet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WGAwvvTwDg2yknFkmZ3qzs
@staalmannen
staalmannen merged commit 3aab446 into main Nov 5, 2026
1 check passed
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.

2 participants