diff --git a/.github/workflows/cmake-single-platform.yml b/.github/workflows/cmake-single-platform.yml index 50cdb50..30db944 100644 --- a/.github/workflows/cmake-single-platform.yml +++ b/.github/workflows/cmake-single-platform.yml @@ -199,7 +199,12 @@ jobs: - name: Test (MSVC) if: matrix.toolchain == 'msvc' shell: pwsh - run: ctest --test-dir build --build-config Release --output-on-failure + # llvm_generated_compile runs cl directly, so it needs the dev shell env. + run: | + $vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath + Import-Module "$vs\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" + Enter-VsDevShell -VsInstallPath $vs -SkipAutomaticLocation -DevCmdArguments '-arch=x64 -host_arch=x64' + ctest --test-dir build --build-config Release --output-on-failure # Serial: these tests share artifacts in the build directory and report # an inconsistent subset of failures when run in parallel. diff --git a/src/backend/llvm/fifo_service.cpp b/src/backend/llvm/fifo_service.cpp index 4d157b0..548ce54 100644 --- a/src/backend/llvm/fifo_service.cpp +++ b/src/backend/llvm/fifo_service.cpp @@ -129,18 +129,17 @@ Value *FunctionEmitter::externalFifoWrite(Value *address, Value *value, builder_.CreateBr(partialInvoke); builder_.SetInsertPoint(partialInvoke); - Value *partialContext = builder_.CreateLoad( - pointer, builder_.CreateStructGEP(servicesType, services, 2)); - StructType *resultType = StructType::get(context_, {i64, i32, i32}); - CallInst *partialResult = builder_.CreateCall( - FunctionType::get(resultType, {pointer, i32, i32, i64, i32, i64}, false), - partialCallback, - {partialContext, builder_.getInt32(current_pc_), address, - builder_.CreateZExtOrTrunc(value, i64), builder_.getInt32(width), elapsed}); - partialResult->addFnAttr(Attribute::NoUnwind); - Value *partialStatus = builder_.CreateExtractValue(partialResult, 1); - Value *partialRemaining = builder_.CreateExtractValue(partialResult, 2); - builder_.CreateStore(partialRemaining, remainingOut); + // The service returns a 16-byte struct, which the C ABI returns in registers + // on SysV but through a hidden pointer on Win64. Call it through C. + FunctionCallee partialService = module_.getOrInsertFunction( + "moderngekko_write_fifo_partial", + FunctionType::get(i32, {pointer, i32, i32, i64, i32, i64, pointer}, false)); + CallInst *partialStatus = builder_.CreateCall( + partialService, + {ctx_, builder_.getInt32(current_pc_), address, + builder_.CreateZExtOrTrunc(value, i64), builder_.getInt32(width), elapsed, + remainingOut}); + partialStatus->addFnAttr(Attribute::NoUnwind); builder_.CreateCondBr( builder_.CreateICmpEQ(partialStatus, builder_.getInt32(ServiceFallback)), fullInvoke, partialClassify, MDBuilder(context_).createBranchWeights(1, 2000)); diff --git a/src/backend/native_state.c b/src/backend/native_state.c index 9546d85..8cae148 100644 --- a/src/backend/native_state.c +++ b/src/backend/native_state.c @@ -245,6 +245,16 @@ void emit_native_state_commit(FILE* out) { " *remaining = result.remaining_cycles;\n" " return result.status;\n" "}\n" + "DOLRECOMP_COLD uint32_t moderngekko_write_fifo_partial(\n" + " const MGNativeRuntime* runtime, uint32_t pc, uint32_t address,\n" + " uint64_t value, uint32_t size, uint64_t elapsed,\n" + " uint32_t* remaining) {\n" + " const MGNativeServices* services = runtime->services;\n" + " MGNativeMemoryResult result = services->write_fifo_partial(\n" + " services->context, pc, address, value, size, elapsed);\n" + " *remaining = result.remaining_cycles;\n" + " return result.status;\n" + "}\n" "#undef DOLRECOMP_COLD\n", out); } diff --git a/tests/modern_memory_service_test.c b/tests/modern_memory_service_test.c index fa83895..e93bfa7 100644 --- a/tests/modern_memory_service_test.c +++ b/tests/modern_memory_service_test.c @@ -160,39 +160,38 @@ static uint32_t should_intercept(void* opaque, uint32_t guest_pc) return guest_pc == context->intercept_pc; } -MGNativeExit raw_func_80003120(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80003120"); -MGNativeExit raw_func_80003140(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80003140"); -MGNativeExit raw_func_80003500(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80003500"); -MGNativeExit raw_func_80003D40(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80003D40"); -MGNativeExit raw_func_80003DA0(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80003DA0"); -MGNativeExit raw_func_80004A00(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80004A00"); -MGNativeExit raw_func_80004E00(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80004E00"); -MGNativeExit raw_func_80005000(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80005000"); -MGNativeExit raw_func_80004D00(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80004D00"); -MGNativeExit raw_func_80004D20(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80004D20"); -MGNativeExit raw_func_80004D40(const MGNativeRuntime*, const MGNativeState*, - uint64_t*, uint64_t*, uint64_t*, - uint32_t, uint32_t, uint32_t) __asm__("func_80004D40"); +// Bind raw_func_X to the generated func_X symbol. GNU compilers take an asm +// label, which needs the platform symbol prefix; MSVC has no asm labels, so it +// gets a linker alias instead. +#define MG_STRINGIFY2(x) #x +#define MG_STRINGIFY(x) MG_STRINGIFY2(x) +#if defined(_MSC_VER) && !defined(__clang__) +#define MG_RAW_LABEL(addr) +#define MG_RAW_ALIAS(addr) \ + __pragma(comment(linker, "/alternatename:raw_func_" #addr "=func_" #addr)) +#else +#define MG_RAW_LABEL(addr) \ + __asm__(MG_STRINGIFY(__USER_LABEL_PREFIX__) "func_" #addr) +#define MG_RAW_ALIAS(addr) +#endif +#define MG_DECLARE_RAW(addr) \ + MGNativeExit raw_func_##addr(const MGNativeRuntime*, const MGNativeState*, \ + uint64_t*, uint64_t*, uint64_t*, \ + uint32_t, uint32_t, uint32_t) \ + MG_RAW_LABEL(addr); \ + MG_RAW_ALIAS(addr) + +MG_DECLARE_RAW(80003120) +MG_DECLARE_RAW(80003140) +MG_DECLARE_RAW(80003500) +MG_DECLARE_RAW(80003D40) +MG_DECLARE_RAW(80003DA0) +MG_DECLARE_RAW(80004A00) +MG_DECLARE_RAW(80004E00) +MG_DECLARE_RAW(80005000) +MG_DECLARE_RAW(80004D00) +MG_DECLARE_RAW(80004D20) +MG_DECLARE_RAW(80004D40) static uint64_t staged_values[256]; static uint64_t staged_dirty[4]; @@ -591,6 +590,17 @@ uint32_t moderngekko_write_memory(const MGNativeRuntime* runtime, uint32_t pc, return result.status; } +uint32_t moderngekko_write_fifo_partial(const MGNativeRuntime* runtime, + uint32_t pc, uint32_t address, + uint64_t value, uint32_t size, + uint64_t elapsed, + uint32_t* remaining) { + MGNativeMemoryResult result = runtime->services->write_fifo_partial( + runtime->services->context, pc, address, value, size, elapsed); + *remaining = result.remaining_cycles; + return result.status; +} + static MGNativeState make_state(TestContext* context) { MGNativeState state; memset(&state, 0, sizeof(state));