Files
Koichi SasadaandClaude Fable 5.1 15a43f6688 coroutine/ppc64le: save the non-volatile FP and vector registers (#18760)
The ELFv2 ABI makes f14-f31 and v20-v31 callee-saved (OpenPOWER 64-bit
ELF V2 ABI, 2.2.1 Register Roles), but coroutine_transfer saved only
r14-r31, LR and CR.  A coroutine resuming from a transfer therefore
sees whatever the other side left in those registers, and GCC does keep
GPR data there: it vectorizes "store the same pointer twice" as
mtvsrdd vs63 / stxvx, and spills GPRs to VSX under register pressure.

This never bit while nothing on either side of a transfer held a live
value there.  ce6f200972 reshaped nt_start, and GCC 13 now keeps a
pointer in v31 for the whole shared-nt loop, the transfer target of
every M:N park.  From then on rubyci ppc64le was red on every run:
non-main Ractor threads resumed with a stale VALUE where the VM expected
a block handler (rb_block_given_p() true without a block, "the block
passed to ... may be ignored" warnings, SEGV in invoke_block_from_c_bh
at 0x71/0x75 with the same address on every run).  x86_64 has no
callee-saved FP registers and the aarch64 Context.S already saves
d8-d15, which is why only ppc64le failed.

  https://rubyci.s3.amazonaws.com/ppc64le/ruby-master/recent.html
  https://rubyci.s3.amazonaws.com/ppc64le/ruby-master/log/20260911T003005Z.fail.html.gz

Save f14-f31 with stfd/lfd and v20-v31 with stvx/lvx (VMX only, no VSX
requirement); the frame grows from 160 to 496 bytes and
COROUTINE_REGISTERS from 24 to 66 words.  The LR slot stays at index
18, so coroutine_initialize is otherwise unchanged.  Fibers use the same
transfer and are covered too.  glibc's setjmp/longjmp on ppc64 already
save and restore this set, so the EC tags and
RB_VM_SAVE_MACHINE_CONTEXT were not affected.

Verified on POWER9 (Ubuntu 24.04, gcc 13.3.0, 64K pages, the rubyci
configure) at fef20ce27c: without this patch
TestSetTraceFunc#test_tp_ractor_local_untargeted dies at 0x75 and
TestEnv#test_delete_in_ractor at 0x71 as on rubyci, and
TestTmpdir#test_ractor / TestEnv#test_fetch_in_ractor fail on the same
warnings; with it all of them pass and bootstraptest/test_ractor.rb
passes.

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-12 10:17:30 +12:00

60 lines
1.6 KiB
C

#ifndef COROUTINE_PPC64LE_CONTEXT_H
#define COROUTINE_PPC64LE_CONTEXT_H 1
#pragma once
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#define COROUTINE __attribute__((noreturn)) void
enum {
COROUTINE_REGISTERS =
20 /* 18 general purpose registers (r14-r31), 1 special register (cr) and 1 return address */
+ 18 /* non-volatile FP registers f14-f31 */
+ 24 /* non-volatile vector registers v20-v31, 16 bytes each */
+ 4 /* space for fiber_entry() to store the link register */
};
struct coroutine_context
{
void **stack_pointer;
void *argument;
};
typedef COROUTINE(* coroutine_start)(struct coroutine_context *from, struct coroutine_context *self);
static inline void coroutine_initialize_main(struct coroutine_context * context) {
context->stack_pointer = NULL;
}
static inline void coroutine_initialize(
struct coroutine_context *context,
coroutine_start start,
void *stack,
size_t size
) {
assert(start && stack && size >= 1024);
// Stack grows down. Force 16-byte alignment.
char * top = (char*)stack + size;
context->stack_pointer = (void**)((uintptr_t)top & ~0xF);
context->stack_pointer -= COROUTINE_REGISTERS;
memset(context->stack_pointer, 0, sizeof(void*) * COROUTINE_REGISTERS);
/* Skip a global prologue that sets the TOC register */
context->stack_pointer[18] = ((char*)start) + 8;
}
struct coroutine_context * coroutine_transfer(struct coroutine_context * current, struct coroutine_context * target);
static inline void coroutine_destroy(struct coroutine_context * context)
{
context->stack_pointer = NULL;
}
#endif /* COROUTINE_PPC64LE_CONTEXT_H */