[Kernel] Implement pthread semaphore exports (#424)

This commit is contained in:
wearr
2026-07-18 20:18:16 -05:00
committed by GitHub
parent 0b83b34cda
commit a60bfc9c83
3 changed files with 198 additions and 0 deletions
@@ -1438,6 +1438,9 @@ public sealed partial class DirectExecutionBackend
var expectedMutexTrylockBusy = var expectedMutexTrylockBusy =
string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) && string.Equals(nid, "K-jXhbt2gn4", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY; result == OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY;
var expectedSemaphoreTrywaitAgain =
string.Equals(nid, "H2a+IN9TP0E", StringComparison.Ordinal) &&
result == OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN;
var expectedNetAcceptWouldBlock = var expectedNetAcceptWouldBlock =
string.Equals(nid, "PIWqhn9oSxc", StringComparison.Ordinal) && string.Equals(nid, "PIWqhn9oSxc", StringComparison.Ordinal) &&
resultValue == unchecked((int)0x80410123); resultValue == unchecked((int)0x80410123);
@@ -1451,6 +1454,7 @@ public sealed partial class DirectExecutionBackend
!expectedTimedWaitTimeout && !expectedTimedWaitTimeout &&
!expectedEqueueTimeout && !expectedEqueueTimeout &&
!expectedMutexTrylockBusy && !expectedMutexTrylockBusy &&
!expectedSemaphoreTrywaitAgain &&
!expectedNetAcceptWouldBlock && !expectedNetAcceptWouldBlock &&
!expectedUserServiceNoEvent && !expectedUserServiceNoEvent &&
!expectedPrivacyInvalidParameter) !expectedPrivacyInvalidParameter)
@@ -428,6 +428,22 @@ public static class KernelSemaphoreCompatExports
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK); return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
} }
[SysAbiExport(
Nid = "GEnUkDZoUwY",
ExportName = "scePthreadSemInit",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemInit(CpuContext ctx)
{
// scePthreadSemInit(sem, flag, value, name) seems to only support private semaphores
if (ctx[CpuRegister.Rsi] != 0)
{
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
}
return PosixSemInit(ctx);
}
[SysAbiExport( [SysAbiExport(
Nid = "YCV5dGGBcCo", Nid = "YCV5dGGBcCo",
ExportName = "sem_wait", ExportName = "sem_wait",
@@ -446,6 +462,13 @@ public static class KernelSemaphoreCompatExports
return KernelWaitSema(ctx); return KernelWaitSema(ctx);
} }
[SysAbiExport(
Nid = "C36iRE0F5sE",
ExportName = "scePthreadSemWait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemWait(CpuContext ctx) => PosixSemWait(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "WBWzsRifCEA", Nid = "WBWzsRifCEA",
ExportName = "sem_trywait", ExportName = "sem_trywait",
@@ -463,6 +486,19 @@ public static class KernelSemaphoreCompatExports
return KernelPollSema(ctx, handle, 1); return KernelPollSema(ctx, handle, 1);
} }
[SysAbiExport(
Nid = "H2a+IN9TP0E",
ExportName = "scePthreadSemTrywait",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemTryWait(CpuContext ctx)
{
var result = PosixSemTryWait(ctx);
return result == (int)OrbisGen2Result.ORBIS_GEN2_ERROR_BUSY
? SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN)
: result;
}
[SysAbiExport( [SysAbiExport(
Nid = "w5IHyvahg-o", Nid = "w5IHyvahg-o",
ExportName = "sem_timedwait", ExportName = "sem_timedwait",
@@ -499,6 +535,13 @@ public static class KernelSemaphoreCompatExports
return KernelSignalSema(ctx, handle, 1); return KernelSignalSema(ctx, handle, 1);
} }
[SysAbiExport(
Nid = "aishVAiFaYM",
ExportName = "scePthreadSemPost",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemPost(CpuContext ctx) => PosixSemPost(ctx);
[SysAbiExport( [SysAbiExport(
Nid = "Bq+LRV-N6Hk", Nid = "Bq+LRV-N6Hk",
ExportName = "sem_getvalue", ExportName = "sem_getvalue",
@@ -549,6 +592,13 @@ public static class KernelSemaphoreCompatExports
return result; return result;
} }
[SysAbiExport(
Nid = "Vwc+L05e6oE",
ExportName = "scePthreadSemDestroy",
Target = Generation.Gen4 | Generation.Gen5,
LibraryName = "libKernel")]
public static int PthreadSemDestroy(CpuContext ctx) => PosixSemDestroy(ctx);
private static bool TryGetPosixSemaphoreHandle(CpuContext ctx, ulong semaphoreAddress, out uint handle) private static bool TryGetPosixSemaphoreHandle(CpuContext ctx, ulong semaphoreAddress, out uint handle)
{ {
handle = 0; handle = 0;
@@ -0,0 +1,144 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using SharpEmu.HLE;
using SharpEmu.Libs.Kernel;
using Xunit;
namespace SharpEmu.Libs.Tests.Pthread;
public sealed class PthreadSemaphoreSemanticsTests
{
private const ulong MemoryBase = 0x1_0000_0000;
private const ulong SemaphoreAddress = MemoryBase + 0x100;
private const ulong ValueAddress = MemoryBase + 0x200;
[Theory]
[InlineData("C36iRE0F5sE", "scePthreadSemWait")]
[InlineData("aishVAiFaYM", "scePthreadSemPost")]
[InlineData("H2a+IN9TP0E", "scePthreadSemTrywait")]
[InlineData("GEnUkDZoUwY", "scePthreadSemInit")]
[InlineData("Vwc+L05e6oE", "scePthreadSemDestroy")]
public void RegistryResolvesPthreadSemaphoreExports(string nid, string exportName)
{
var manager = new ModuleManager();
manager.RegisterExports(SharpEmu.Generated.SysAbiExportRegistry.CreateExports(
Generation.Gen4 | Generation.Gen5));
Assert.True(manager.TryGetExport(nid, out var export));
Assert.Equal(exportName, export.Name);
Assert.Equal("libKernel", export.LibraryName);
}
[Fact]
public void PthreadSemPostAndWaitShareSemaphoreState()
{
var context = CreateContext();
InitializeSemaphore(context, initialCount: 0);
context[CpuRegister.Rdi] = SemaphoreAddress;
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemPost(context));
Assert.Equal(0UL, context[CpuRegister.Rax]);
Assert.Equal(1, ReadSemaphoreValue(context));
context[CpuRegister.Rdi] = SemaphoreAddress;
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemWait(context));
Assert.Equal(0UL, context[CpuRegister.Rax]);
Assert.Equal(0, ReadSemaphoreValue(context));
DestroySemaphore(context);
}
[Fact]
public void PthreadSemTryWaitConsumesAvailableTokenAndReturnsTryAgainWhenEmpty()
{
var context = CreateContext();
InitializeSemaphore(context, initialCount: 1);
context[CpuRegister.Rdi] = SemaphoreAddress;
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemTryWait(context));
Assert.Equal(0, ReadSemaphoreValue(context));
context[CpuRegister.Rdi] = SemaphoreAddress;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN,
KernelSemaphoreCompatExports.PthreadSemTryWait(context));
Assert.Equal(unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_TRY_AGAIN),
context[CpuRegister.Rax]);
DestroySemaphore(context);
}
[Fact]
public void PthreadSemInitRejectsNonPrivateFlag()
{
var context = CreateContext();
context[CpuRegister.Rdi] = SemaphoreAddress;
context[CpuRegister.Rsi] = 1;
context[CpuRegister.Rdx] = 0;
context[CpuRegister.Rcx] = 0;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
KernelSemaphoreCompatExports.PthreadSemInit(context));
Assert.Equal(unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT),
context[CpuRegister.Rax]);
}
[Fact]
public void PthreadSemDestroyClearsSemaphoreAndRejectsSecondDestroy()
{
var context = CreateContext();
InitializeSemaphore(context, initialCount: 0);
context[CpuRegister.Rdi] = SemaphoreAddress;
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemDestroy(context));
Assert.True(context.TryReadUInt32(SemaphoreAddress, out var handle));
Assert.Equal(0U, handle);
context[CpuRegister.Rdi] = SemaphoreAddress;
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT,
KernelSemaphoreCompatExports.PthreadSemDestroy(context));
}
[Theory]
[InlineData(true)]
[InlineData(false)]
public void PthreadSemOperationRejectsInvalidSemaphore(bool post)
{
var context = CreateContext();
context[CpuRegister.Rdi] = SemaphoreAddress;
var result = post
? KernelSemaphoreCompatExports.PthreadSemPost(context)
: KernelSemaphoreCompatExports.PthreadSemWait(context);
Assert.Equal((int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT, result);
Assert.Equal(unchecked((ulong)(int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT),
context[CpuRegister.Rax]);
}
private static CpuContext CreateContext() => new(new FakeCpuMemory(MemoryBase, 0x1000), Generation.Gen5);
private static void InitializeSemaphore(CpuContext context, uint initialCount)
{
context[CpuRegister.Rdi] = SemaphoreAddress;
context[CpuRegister.Rsi] = 0;
context[CpuRegister.Rdx] = initialCount;
context[CpuRegister.Rcx] = 0;
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemInit(context));
}
private static int ReadSemaphoreValue(CpuContext context)
{
context[CpuRegister.Rdi] = SemaphoreAddress;
context[CpuRegister.Rsi] = ValueAddress;
Assert.Equal(0, KernelSemaphoreCompatExports.PosixSemGetValue(context));
Assert.True(context.TryReadUInt32(ValueAddress, out var value));
return unchecked((int)value);
}
private static void DestroySemaphore(CpuContext context)
{
context[CpuRegister.Rdi] = SemaphoreAddress;
Assert.Equal(0, KernelSemaphoreCompatExports.PthreadSemDestroy(context));
}
}