[PATCH 0/3] MR10903: ntdll: Add support for ARM64EC cooperative suspend.
When running simulated code, instead of suspending a thread immediately, the kernel sets a registered doorbell, expecting the jit to stop execution as soon as it reaches a consistent state that can be represented as an x86_64 context. Based on patch by @bylaws. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10903
From: Jacek Caban <jacek@codeweavers.com> When running simulated code, instead of suspending a thread immediately, the kernel sets a registered doorbell, expecting the jit to stop execution as soon as it reaches a consistent state that can be represented as an x86_64 context. Based on patch by Billy Laws. --- dlls/ntdll/unix/signal_arm64.c | 38 +++++++++++++++++++++++++++++++++- server/protocol.def | 5 +++-- server/thread.c | 14 +++++++++++-- 3 files changed, 52 insertions(+), 5 deletions(-) diff --git a/dlls/ntdll/unix/signal_arm64.c b/dlls/ntdll/unix/signal_arm64.c index b53af9dd3a0..f6edbba5de2 100644 --- a/dlls/ntdll/unix/signal_arm64.c +++ b/dlls/ntdll/unix/signal_arm64.c @@ -64,6 +64,18 @@ WINE_DEFAULT_DEBUG_CHANNEL(seh); #define NTDLL_DWARF_H_NO_UNWINDER #include "dwarf.h" +struct arm64_thread_data +{ + BOOL suspend_pending; +}; + +C_ASSERT( sizeof(struct arm64_thread_data) <= sizeof(((struct teb_data *)0)->cpu_data) ); + +static inline struct arm64_thread_data *arm64_thread_data( struct thread_data *data ) +{ + return (struct arm64_thread_data *)get_teb_data(data)->cpu_data; +} + /*********************************************************************** * signal context platform-specific definitions */ @@ -320,7 +332,20 @@ NTSTATUS signal_set_full_context( CONTEXT *context ) { struct thread_data *data = get_thread_data(); struct syscall_frame *frame = get_syscall_frame( data ); - NTSTATUS status = NtSetContextThread( GetCurrentThread(), context ); + struct arm64_thread_data *arm64_data = arm64_thread_data( data ); + NTSTATUS status; + + if (arm64_data->suspend_pending) + { + sigset_t old_set; + pthread_sigmask( SIG_BLOCK, &server_block_set, &old_set ); + *data->teb->ChpeV2CpuAreaInfo->SuspendDoorbell = 0; + arm64_data->suspend_pending = FALSE; + wait_suspend( context ); + status = NtSetContextThread( GetCurrentThread(), context ); + pthread_sigmask( SIG_SETMASK, &old_set, NULL ); + } + else status = NtSetContextThread( GetCurrentThread(), context ); if (!status && (context->ContextFlags & CONTEXT_INTEGER) == CONTEXT_INTEGER) frame->restore_flags |= CONTEXT_INTEGER; @@ -1321,12 +1346,23 @@ static void usr1_handler( int signal, siginfo_t *siginfo, void *_sigcontext ) { ucontext_t *sigcontext = _sigcontext; struct thread_data *data = get_thread_data(); + CHPE_V2_CPU_AREA_INFO *chpe; CONTEXT context; if (!data->teb) { server_select( NULL, 0, SELECT_INTERRUPTIBLE, 0, NULL, NULL ); } + else if ((chpe = data->teb->ChpeV2CpuAreaInfo) && chpe->InSimulation && chpe->SuspendDoorbell) + { + NTSTATUS status = server_select( NULL, 0, SELECT_INTERRUPTIBLE | SELECT_COOPERATIVE_SUSPEND, + 0, NULL, NULL ); + if (status == STATUS_THREAD_WAS_SUSPENDED) + { + *chpe->SuspendDoorbell = -1; + arm64_thread_data( data )->suspend_pending = TRUE; + } + } else if (is_inside_syscall( data, SP_sig(sigcontext) )) { context.ContextFlags = CONTEXT_FULL | CONTEXT_EXCEPTION_REQUEST; diff --git a/server/protocol.def b/server/protocol.def index 2d651e076de..3ee3be605d4 100644 --- a/server/protocol.def +++ b/server/protocol.def @@ -1448,8 +1448,9 @@ typedef volatile struct VARARG(call,apc_call); /* APC call arguments */ VARARG(contexts,contexts); /* suspend context(s) */ @END -#define SELECT_ALERTABLE 1 -#define SELECT_INTERRUPTIBLE 2 +#define SELECT_ALERTABLE 1 +#define SELECT_INTERRUPTIBLE 2 +#define SELECT_COOPERATIVE_SUSPEND 4 /* Create an event */ diff --git a/server/thread.c b/server/thread.c index e0d04b066da..19c928f7bee 100644 --- a/server/thread.c +++ b/server/thread.c @@ -132,6 +132,7 @@ struct context struct object obj; /* object header */ struct object *sync; /* sync object for wait/signal */ unsigned int status; /* status of the context */ + int cooperative;/* waiting for the cooperative suspend */ struct context_data regs[2]; /* context data */ }; #define CTX_NATIVE 0 /* context for native machine */ @@ -445,6 +446,7 @@ static inline void init_thread_structure( struct thread *thread ) static inline int is_thread_suspended( struct thread *thread ) { + if (thread->context && thread->context->cooperative) return 0; if (thread->suspend) return 1; return !thread->bypass_proc_suspend && thread->process->suspend; } @@ -485,8 +487,9 @@ static struct context *create_thread_context( struct thread *thread ) { struct context *context; if (!(context = alloc_object( &context_ops ))) return NULL; - context->sync = NULL; - context->status = STATUS_PENDING; + context->sync = NULL; + context->status = STATUS_PENDING; + context->cooperative = 0; memset( &context->regs, 0, sizeof(context->regs) ); context->regs[CTX_NATIVE].machine = native_machine; @@ -1167,6 +1170,12 @@ static int check_wait( struct thread *thread ) if ((wait->flags & SELECT_INTERRUPTIBLE) && !list_empty( &thread->system_apc )) return STATUS_KERNEL_APC; + if ((wait->flags & SELECT_COOPERATIVE_SUSPEND) && thread->context) + { + thread->context->cooperative = 1; + return STATUS_THREAD_WAS_SUSPENDED; + } + /* Suspended threads may not acquire locks, but they can run system APCs */ if (is_thread_suspended( thread )) return -1; @@ -1988,6 +1997,7 @@ DECL_HANDLER(select) copy_context( &ctx->regs[CTX_WOW], wow_context, wow_context->flags & ~ctx->regs[CTX_WOW].flags ); } ctx->status = STATUS_SUCCESS; + ctx->cooperative = 0; current->suspend_cookie = req->cookie; signal_sync( ctx->sync ); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10903
From: Jacek Caban <jacek@codeweavers.com> --- dlls/ntdll/signal_arm64ec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dlls/ntdll/signal_arm64ec.c b/dlls/ntdll/signal_arm64ec.c index 954ace7824e..b6a9aa5f045 100644 --- a/dlls/ntdll/signal_arm64ec.c +++ b/dlls/ntdll/signal_arm64ec.c @@ -1404,7 +1404,8 @@ static void __attribute__((used)) capture_context( CONTEXT *context, UINT cpsr, /* unwind one level to get register values from caller function */ unwind_context = *context; unwind_one_frame( &unwind_context ); - memcpy( &context->Rax, &unwind_context.Rax, offsetof(CONTEXT,FltSave) - offsetof(CONTEXT,Rax) ); + if (!RtlIsEcCode( unwind_context.Rip )) + memcpy( &context->Rax, &unwind_context.Rax, offsetof(CONTEXT,FltSave) - offsetof(CONTEXT,Rax) ); } /*********************************************************************** -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10903
From: Jacek Caban <jacek@codeweavers.com> --- dlls/ntdll/tests/wow64.c | 80 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/dlls/ntdll/tests/wow64.c b/dlls/ntdll/tests/wow64.c index 4ad6965b469..a88c3db031d 100644 --- a/dlls/ntdll/tests/wow64.c +++ b/dlls/ntdll/tests/wow64.c @@ -3213,6 +3213,83 @@ static void test_exception_dispatcher(void) #endif /* _WIN64 */ +#ifdef __arm64ec__ + +static ULONG *doorbell; +static ULONG64 suspend_rip; + +static DWORD WINAPI doorbell_thread( void *arg ) +{ + CHPE_V2_CPU_AREA_INFO *chpe = NtCurrentTeb()->ChpeV2CpuAreaInfo; + ULONG signaled_doorbell = -1; + NTSTATUS status; + HANDLE event; + CONTEXT ctx; + LONG i = 0; + + RtlCaptureContext( &ctx ); + + if (InterlockedIncrement( &i ) == 1) + { + suspend_rip = ctx.Rip; + + chpe->InSimulation = 1; + doorbell = chpe->SuspendDoorbell; + ok( doorbell != NULL, "doorbell is not available\n" ); + while (!(signaled_doorbell = *doorbell)) YieldProcessor(); + chpe->InSimulation = 0; + + /* syscalls, including waits, continue working */ + event = CreateEventW( NULL, FALSE, TRUE, NULL ); + ok( event != NULL, "CreateEvent failed\n" ); + status = NtWaitForSingleObject( event, FALSE, NULL ); + ok( !status, "NtWaitForSingleObject failed\n" ); + status = NtClose( event ); + ok( !status, "NtClose failed\n" ); + + NtContinue( &ctx, FALSE ); + ok( 0, "NtContinue failed\n" ); + } + + ok( !*doorbell, "doorbell = %lx\n", *doorbell ); + ok( signaled_doorbell == -1, "signaled_doorbell = %lx\n", signaled_doorbell ); + doorbell = NULL; + return 0; +} + +static void test_suspend_doorbell(void) +{ + HANDLE thread; + CONTEXT ctx; + int suspend; + + for (suspend = 0; suspend < 2; suspend++) + { + doorbell = NULL; + suspend_rip = 0; + + thread = CreateThread( NULL, 0, doorbell_thread, NULL, 0, NULL ); + ok( thread != NULL, "CreateThread failed\n" ); + + while (!doorbell) YieldProcessor(); + ok( !*doorbell, "doorbell = %lx\n", *doorbell ); + + if (suspend) SuspendThread( thread ); + + memset( &ctx, 0xcc, sizeof(ctx) ); + ctx.ContextFlags = CONTEXT_FULL; + GetThreadContext( thread, &ctx ); + ok( ctx.Rip == suspend_rip, "Rip = %llx, expected %llx\n", ctx.Rip, suspend_rip ); + + if (suspend) ResumeThread( thread ); + + WaitForSingleObject( thread, INFINITE ); + ok( !doorbell, "thread did not reset doorbell\n" ); + } +} + +#endif /* __arm64ec__ */ + static void test_arm64ec(void) { #ifdef __aarch64__ @@ -3254,6 +3331,9 @@ START_TEST(wow64) test_init_block(); test_iosb(); test_syscalls(); +#endif +#ifdef __arm64ec__ + test_suspend_doorbell(); #endif test_memory_notifications(); test_cpu_area(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10903
participants (2)
-
Jacek Caban -
Jacek Caban (@jacek)