mirror of
https://github.com/ruby/ruby.git
synced 2026-10-28 16:36:20 +08:00
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.ce6f200972reshaped 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) atfef20ce27c: 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>
60 lines
1.6 KiB
C
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 */
|