diff --git a/Launcher/Build-Installer.ps1 b/Launcher/Build-Installer.ps1 index c208ce7..bc954a3 100644 --- a/Launcher/Build-Installer.ps1 +++ b/Launcher/Build-Installer.ps1 @@ -1,7 +1,6 @@ [CmdletBinding(PositionalBinding = $false)] param( [string]$OutputDirectory = 'Launcher/dist', - [string]$DolphinToolPath, [string]$PortableToolsDirectory = 'Launcher/artifacts/portable-tools', [string]$DependencySourceDirectory = 'Launcher/artifacts/dependencies', [string]$VcRuntimeDirectory, @@ -15,10 +14,6 @@ Set-StrictMode -Version 3.0 # helpers shared with LocalBuild.ps1 and Prepare-NativePrebuilt.ps1. . (Join-Path $PSScriptRoot 'NativeBuildFlags.ps1') -if ([string]::IsNullOrWhiteSpace($DolphinToolPath)) { - throw 'Build-Installer.ps1 requires -DolphinToolPath pointing to DolphinTool.exe.' -} - $repoRoot = [IO.Path]::GetFullPath((Join-Path $PSScriptRoot '..')) $outputRoot = [IO.Path]::GetFullPath((Join-Path $repoRoot $OutputDirectory)) $portableTools = [IO.Path]::GetFullPath((Join-Path $repoRoot $PortableToolsDirectory)) @@ -26,7 +21,7 @@ $dependencySources = [IO.Path]::GetFullPath((Join-Path $repoRoot $DependencySour $workRoot = Join-Path $PSScriptRoot 'artifacts\installer-build' $publish = Join-Path $workRoot 'publish' $payloadRoot = Join-Path $workRoot 'payload' -$setupProject = Join-Path $PSScriptRoot 'WiiCompiled.Setup\WiiCompiled.Setup.csproj' +$setupProject = Join-Path $PSScriptRoot 'WiiCompiled.Setup.Windows\WiiCompiled.Setup.Windows.csproj' $translatorProject = Join-Path $repoRoot 'translator\src\Translator.Cli\Translator.Cli.csproj' $projectFile = Join-Path $repoRoot 'projects\mkwii\recomp.yml' @@ -91,7 +86,6 @@ function Compress-Zip([string]$Source, [string]$Destination, [string[]]$Entries) Assert-File $setupProject '.NET setup project' Assert-File $translatorProject 'Translator CLI project' -Assert-File $DolphinToolPath 'DolphinTool' if (-not (Test-Path -LiteralPath (Join-Path $portableTools 'llvm-mingw\bin\x86_64-w64-mingw32-clang++.exe'))) { & (Join-Path $PSScriptRoot 'Prepare-PortableTools.ps1') -Destination $portableTools @@ -169,6 +163,15 @@ $translator = Join-Path $publish 'translator\Translator.Cli.exe' Assert-File $setupHost 'Published setup host' Assert-File $translator 'Self-contained translator' +# Resolved via the shared WiiCompiled.Setup.Common.Cli helper (also used by build-appimage.sh on +# Linux) rather than a separate download/version-pin copy here: it downloads and caches the same way +# NodToolProvider.cs always does (Launcher/artifacts/nodtool.exe), replacing the old manual +# -DolphinToolPath handoff with an automated, pinned acquisition step. +$nodToolCliProject = Join-Path $PSScriptRoot 'WiiCompiled.Setup.Common.Cli' +$nodTool = (& dotnet run --project $nodToolCliProject -c Release -- --workspace $repoRoot | Select-Object -Last 1) +if ($LASTEXITCODE -ne 0) { throw "nodtool resolution failed with exit code $LASTEXITCODE." } +Assert-File $nodTool 'Resolved nodtool' + Write-Host '[2/6] Staging the explicit, game-code-free payload allowlist...' # The staged layout mirrors the installed layout exactly (Toolkit, BuildWorkspace): payload # identities hash relative paths, so the names here are part of the fingerprint contract. @@ -191,7 +194,7 @@ Get-ChildItem -LiteralPath (Join-Path $toolkit 'llvm-mingw\bin') -File | Remove-Item -Force [IO.Directory]::CreateDirectory((Join-Path $toolkit 'Translator')) | Out-Null Copy-Item -LiteralPath $translator -Destination (Join-Path $toolkit 'Translator\Translator.Cli.exe') -Copy-Item -LiteralPath $DolphinToolPath -Destination (Join-Path $toolkit 'DolphinTool.exe') +Copy-Item -LiteralPath $nodTool -Destination (Join-Path $toolkit 'nodtool.exe') [IO.Directory]::CreateDirectory((Join-Path $toolkit 'Redist')) | Out-Null Copy-Item -Path (Join-Path $vcRuntime '*.dll') -Destination (Join-Path $toolkit 'Redist') Copy-Item -Path (Join-Path $vcRuntime '*.dll') -Destination (Join-Path $toolkit 'CMake\bin') @@ -225,18 +228,38 @@ Copy-Item (Join-Path $dependencySources 'cppwinrt\LICENSE.txt') (Join-Path $payl # The precompiled aurora/third-party archives are built from the very sources # already shipped under build-workspace\Dependencies and aurora-main, so they add # no third-party component and therefore no new license obligation. -$dolphinLicense = Join-Path (Split-Path -Parent $DolphinToolPath) 'COPYING' -if (Test-Path $dolphinLicense) { Copy-Item $dolphinLicense (Join-Path $payloadRoot 'licenses\Dolphin-COPYING.txt') } @" -DolphinTool source offer +nodtool (disc image extraction) -Project and complete corresponding source: https://github.com/dolphin-emu/dolphin -Dolphin is licensed under GPLv2+ with additional per-file SPDX licenses. -"@ | Set-Content (Join-Path $payloadRoot 'licenses\Dolphin-SOURCE.txt') -Encoding UTF8 +Project: https://github.com/encounter/nod +Dual-licensed under MIT OR Apache-2.0. + +MIT License + +Copyright 2021 Luke Street. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +"@ | Set-Content (Join-Path $payloadRoot 'licenses\nodtool-LICENSE-MIT.txt') -Encoding UTF8 @" Microsoft Visual C++ Runtime -Redistributable x64 runtime DLLs are included app-locally for DolphinTool and third-party renderer DLLs. +Redistributable x64 runtime DLLs are included app-locally for nodtool and third-party renderer DLLs. Microsoft license terms: https://visualstudio.microsoft.com/license-terms/ "@ | Set-Content (Join-Path $payloadRoot 'licenses\Microsoft-VC-Runtime.txt') -Encoding UTF8 # Compute the payload's content identities once, here, with the same code every installed host diff --git a/Launcher/Prepare-Release.ps1 b/Launcher/Prepare-Release.ps1 index 8e51414..53a8df0 100644 --- a/Launcher/Prepare-Release.ps1 +++ b/Launcher/Prepare-Release.ps1 @@ -1,6 +1,5 @@ [CmdletBinding(PositionalBinding = $false)] param( - [string]$DolphinToolPath, [string]$PortableToolsDirectory = 'Launcher/artifacts/portable-tools', [string]$DependencySourceDirectory = 'Launcher/artifacts/dependencies', [string]$VcRuntimeDirectory, @@ -10,12 +9,7 @@ param( $ErrorActionPreference = 'Stop' Set-StrictMode -Version 3.0 -if ([string]::IsNullOrWhiteSpace($DolphinToolPath)) { - throw 'Prepare-Release.ps1 requires -DolphinToolPath pointing to DolphinTool.exe.' -} - $arguments = @{ - DolphinToolPath = $DolphinToolPath PortableToolsDirectory = $PortableToolsDirectory DependencySourceDirectory = $DependencySourceDirectory ToolkitReleaseTag = $ToolkitReleaseTag diff --git a/Launcher/Test-NativeDependencies.ps1 b/Launcher/Test-NativeDependencies.ps1 index a8bbe8f..34377fc 100644 --- a/Launcher/Test-NativeDependencies.ps1 +++ b/Launcher/Test-NativeDependencies.ps1 @@ -18,7 +18,7 @@ $llvmReadobj = [System.IO.Path]::GetFullPath($LlvmReadobjPath) $systemDlls = [Collections.Generic.HashSet[string]]::new([StringComparer]::OrdinalIgnoreCase) @( - 'advapi32.dll', 'authz.dll', 'bcrypt.dll', 'combase.dll', 'comdlg32.dll', 'crypt32.dll', + 'advapi32.dll', 'authz.dll', 'bcrypt.dll', 'bcryptprimitives.dll', 'combase.dll', 'comdlg32.dll', 'crypt32.dll', 'd3d11.dll', 'd3d12.dll', 'dbghelp.dll', 'dcomp.dll', 'dwrite.dll', 'dwmapi.dll', 'dxgi.dll', 'gdi32.dll', 'imm32.dll', 'iphlpapi.dll', 'kernel32.dll', 'mf.dll', 'mfplat.dll', 'mfreadwrite.dll', 'mfuuid.dll', diff --git a/Launcher/Test-PinnedFacts.ps1 b/Launcher/Test-PinnedFacts.ps1 index 4d97c9c..bd65c84 100644 --- a/Launcher/Test-PinnedFacts.ps1 +++ b/Launcher/Test-PinnedFacts.ps1 @@ -15,7 +15,8 @@ if ([string]::IsNullOrWhiteSpace($RepositoryRoot)) { } $repoRoot = [IO.Path]::GetFullPath($RepositoryRoot) $launcher = Join-Path $repoRoot 'Launcher' -$setup = Join-Path $launcher 'WiiCompiled.Setup' +$setup = Join-Path $launcher 'WiiCompiled.Setup.Windows' +$common = Join-Path $launcher 'WiiCompiled.Setup.Common' $failures = [Collections.Generic.List[string]]::new() function Add-Failure([string]$Message) { $failures.Add($Message) } @@ -48,9 +49,11 @@ function Compare-Set([string[]]$Expected, [string[]]$Actual, [string]$ExpectedNa $pins = Get-MkwProjectPins (Join-Path $repoRoot 'projects\mkwii\recomp.yml') # --- The Retro-WFC endpoint: recomp.yml owns it; the installer host pins the same string so a -# --- redirected or rewritten endpoint cannot be fetched from. -$inputValidation = Read-SourceFile (Join-Path $setup 'InputValidation.cs') 'InputValidation.cs' -$hostUri = Get-CapturedValue $inputValidation 'CurrentRetroWfcPayloadUri\s*=\s*"([^"]+)"' ` +# --- redirected or rewritten endpoint cannot be fetched from. The literal lives in +# --- WiiCompiled.Setup.Common (shared with WiiCompiled.Setup.Linux) - InputValidation.cs only +# --- re-exports it as `= RetroWfcPayload.CurrentRetroWfcPayloadUri;`, no literal to capture there. +$retroWfcPayload = Read-SourceFile (Join-Path $common 'RetroWfcPayload.cs') 'RetroWfcPayload.cs' +$hostUri = Get-CapturedValue $retroWfcPayload 'CurrentRetroWfcPayloadUri\s*=\s*"([^"]+)"' ` 'The host Retro-WFC endpoint constant' if ($hostUri -cne $pins.RetroWfcPayloadUri) { Add-Failure "InputValidation.CurrentRetroWfcPayloadUri is '$hostUri' but recomp.yml pins '$($pins.RetroWfcPayloadUri)'." diff --git a/Launcher/WiiCompiled.Setup.Common.Cli/Program.cs b/Launcher/WiiCompiled.Setup.Common.Cli/Program.cs new file mode 100644 index 0000000..8fa8bd9 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Common.Cli/Program.cs @@ -0,0 +1,28 @@ +using WiiCompiled.Setup.Common; + +// A packaging-time-only helper - never shipped, never run by an end user. Both +// Launcher/build-appimage.sh and Launcher/Build-Installer.ps1 invoke this to obtain the nodtool +// binary they bundle, so there is exactly one place (NodToolProvider) that knows the pinned +// version/URL/platform-asset mapping, instead of a separate copy per packaging script. +// +// Usage: WiiCompiled.Setup.Common.Cli --workspace +// Prints the resolved nodtool path to stdout. + +string? workspace = null; +for (var i = 0; i < args.Length; i++) +{ + if (args[i] == "--workspace" && i + 1 < args.Length) + { + workspace = args[++i]; + } +} + +if (workspace is null) +{ + Console.Error.WriteLine("Usage: WiiCompiled.Setup.Common.Cli --workspace "); + return 1; +} + +var path = await NodToolProvider.ResolveAsync(workspace, CancellationToken.None); +Console.WriteLine(path); +return 0; diff --git a/Launcher/WiiCompiled.Setup.Common.Cli/WiiCompiled.Setup.Common.Cli.csproj b/Launcher/WiiCompiled.Setup.Common.Cli/WiiCompiled.Setup.Common.Cli.csproj new file mode 100644 index 0000000..b22d680 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Common.Cli/WiiCompiled.Setup.Common.Cli.csproj @@ -0,0 +1,19 @@ + + + Exe + net8.0 + enable + enable + WiiCompiled.Setup.Common.Cli + WiiCompiled.Setup.Common.Cli + 0.2.22 + patchzy + WiiCompiled + Packaging-time helper: resolves (downloading if needed) the nodtool binary bundled by build-appimage.sh and Build-Installer.ps1 + embedded + en + + + + + diff --git a/Launcher/WiiCompiled.Setup/FileSystemUtilities.cs b/Launcher/WiiCompiled.Setup.Common/FileSystemUtilities.cs similarity index 98% rename from Launcher/WiiCompiled.Setup/FileSystemUtilities.cs rename to Launcher/WiiCompiled.Setup.Common/FileSystemUtilities.cs index 1b662f9..cf3d530 100644 --- a/Launcher/WiiCompiled.Setup/FileSystemUtilities.cs +++ b/Launcher/WiiCompiled.Setup.Common/FileSystemUtilities.cs @@ -1,14 +1,14 @@ -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Common; /// /// One entry of an exact regular directory tree. Directory topology is part of the content /// contract everywhere this walker is used: an empty directory can be a runtime-visible asset just /// as a regular file can be, so it must not disappear from a content identity or a staged copy. /// -internal sealed record RegularTreeEntry(string RelativePath, string FullPath, bool IsDirectory, +public sealed record RegularTreeEntry(string RelativePath, string FullPath, bool IsDirectory, bool IsEmptyDirectory, long Length); -internal static class FileSystemUtilities +public static class FileSystemUtilities { public static void CopyDirectory(string source, string destination, CancellationToken cancellationToken = default) diff --git a/Launcher/WiiCompiled.Setup/JsonState.cs b/Launcher/WiiCompiled.Setup.Common/JsonState.cs similarity index 58% rename from Launcher/WiiCompiled.Setup/JsonState.cs rename to Launcher/WiiCompiled.Setup.Common/JsonState.cs index 5b51fc6..ce719b6 100644 --- a/Launcher/WiiCompiled.Setup/JsonState.cs +++ b/Launcher/WiiCompiled.Setup.Common/JsonState.cs @@ -1,9 +1,9 @@ using System.Text.Json; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Common; -/// Reads and atomically writes the small JSON state documents kept inside an installation. -internal static class JsonState +/// Reads and atomically writes the small JSON state documents each installer keeps. +public static class JsonState { private static readonly JsonSerializerOptions ReadOptions = new() { PropertyNameCaseInsensitive = true }; private static readonly JsonSerializerOptions WriteOptions = new() { WriteIndented = true }; @@ -17,11 +17,17 @@ internal static class JsonState catch { // A truncated or hand-edited state document must degrade into "unknown", which every - // caller already treats as "assume stale and rebuild", not into a failed launch. + // caller already treats as "assume stale and rebuild", not into a crash. return null; } } - public static void Write(string path, T value) => - FileSystemUtilities.WriteAtomic(path, JsonSerializer.Serialize(value, WriteOptions)); + public static void Write(string path, T value) + { + var directory = Path.GetDirectoryName(path); + if (!string.IsNullOrEmpty(directory)) Directory.CreateDirectory(directory); + var tempPath = path + ".tmp-" + Guid.NewGuid().ToString("N"); + File.WriteAllText(tempPath, JsonSerializer.Serialize(value, WriteOptions)); + File.Move(tempPath, path, overwrite: true); + } } diff --git a/Launcher/WiiCompiled.Setup.Common/NodToolInfoParser.cs b/Launcher/WiiCompiled.Setup.Common/NodToolInfoParser.cs new file mode 100644 index 0000000..fb77aab --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Common/NodToolInfoParser.cs @@ -0,0 +1,38 @@ +using System.Text.RegularExpressions; + +namespace WiiCompiled.Setup.Common; + +/// Disc metadata parsed from `nodtool info`'s stdout. +public sealed record NodToolDiscInfo(string GameId, string Title, int Revision); + +/// +/// Parses the plain-text stdout of `nodtool info <iso>`. nodtool has no JSON output mode, but +/// prints one unconditional disc-level Title/Game ID/Disc-Revision block (via its own +/// `print_header`) before any per-partition breakdown - Wii discs also have differently-scoped +/// "Title"/"Game ID" lines per update/channel partition further down, so the first match of each +/// pattern is always the disc-level one both installers want. +/// +public static partial class NodToolInfoParser +{ + public static NodToolDiscInfo Parse(string infoStdout) + { + var gameIdMatch = GameIdLine().Match(infoStdout); + if (!gameIdMatch.Success) + throw new InvalidOperationException("nodtool did not return disc metadata."); + var titleMatch = TitleLine().Match(infoStdout); + var revisionMatch = RevisionLine().Match(infoStdout); + return new NodToolDiscInfo( + GameId: gameIdMatch.Groups[1].Value, + Title: titleMatch.Success ? titleMatch.Groups[1].Value : "", + Revision: revisionMatch.Success ? int.Parse(revisionMatch.Groups[1].Value) : 0); + } + + [GeneratedRegex(@"^Game ID: (\S+)", RegexOptions.Multiline)] + private static partial Regex GameIdLine(); + + [GeneratedRegex(@"^Title: (.+)$", RegexOptions.Multiline)] + private static partial Regex TitleLine(); + + [GeneratedRegex(@"^Disc \d+, Revision (\d+)", RegexOptions.Multiline)] + private static partial Regex RevisionLine(); +} diff --git a/Launcher/WiiCompiled.Setup.Common/NodToolProvider.cs b/Launcher/WiiCompiled.Setup.Common/NodToolProvider.cs new file mode 100644 index 0000000..f587695 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Common/NodToolProvider.cs @@ -0,0 +1,67 @@ +using System.Runtime.InteropServices; + +namespace WiiCompiled.Setup.Common; + +/// +/// Resolves the `nodtool` binary both installers use for Wii disc validation/extraction (see +/// NodToolInfoParser.cs), replacing the earlier dependency on `dolphin-tool`/`DolphinTool.exe`. A +/// caller can supply one directly; otherwise this downloads the matching prebuilt release binary +/// from encounter/nod and caches it at Launcher/artifacts/nodtool[.exe]. +/// +/// Shared by: WiiCompiled.Setup.Linux/DiscTool.cs (falls back to this at end-user install time on +/// a plain git checkout), and WiiCompiled.Setup.Common.Cli (invoked once at packaging time by both +/// build-appimage.sh and Build-Installer.ps1 to acquire the copy each bundles). +/// +public static class NodToolProvider +{ + public const string Version = "v2.0.0-alpha.10"; + + public static async Task ResolveAsync(string workspace, CancellationToken cancellationToken) + { + var cacheName = OperatingSystem.IsWindows() ? "nodtool.exe" : "nodtool"; + var cachePath = Path.Combine(workspace, "Launcher", "artifacts", cacheName); + if (File.Exists(cachePath)) return cachePath; + + var url = $"https://github.com/encounter/nod/releases/download/{Version}/{AssetName()}"; + + Directory.CreateDirectory(Path.GetDirectoryName(cachePath)!); + var tempPath = cachePath + ".tmp"; + using (var http = new HttpClient()) + using (var response = await http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken)) + { + response.EnsureSuccessStatusCode(); + await using var fileStream = File.Create(tempPath); + await response.Content.CopyToAsync(fileStream, cancellationToken); + } + File.Move(tempPath, cachePath, overwrite: true); + if (!OperatingSystem.IsWindows()) + { + File.SetUnixFileMode(cachePath, + UnixFileMode.UserRead | UnixFileMode.UserWrite | UnixFileMode.UserExecute | + UnixFileMode.GroupRead | UnixFileMode.GroupExecute | + UnixFileMode.OtherRead | UnixFileMode.OtherExecute); + } + return cachePath; + } + + private static string AssetName() + { + if (OperatingSystem.IsWindows()) + { + return RuntimeInformation.OSArchitecture switch + { + Architecture.X64 => "nodtool-windows-x86_64.exe", + Architecture.Arm64 => "nodtool-windows-arm64.exe", + Architecture.X86 => "nodtool-windows-x86.exe", + var other => throw new PlatformNotSupportedException($"No prebuilt nodtool release for Windows {other}"), + }; + } + return RuntimeInformation.OSArchitecture switch + { + Architecture.X64 => "nodtool-linux-x86_64", + Architecture.Arm64 => "nodtool-linux-aarch64", + Architecture.X86 => "nodtool-linux-i686", + var other => throw new PlatformNotSupportedException($"No prebuilt nodtool release for Linux {other}"), + }; + } +} diff --git a/Launcher/WiiCompiled.Setup/PortableRoot.cs b/Launcher/WiiCompiled.Setup.Common/PortableRoot.cs similarity index 59% rename from Launcher/WiiCompiled.Setup/PortableRoot.cs rename to Launcher/WiiCompiled.Setup.Common/PortableRoot.cs index eab3dcd..8344b5f 100644 --- a/Launcher/WiiCompiled.Setup/PortableRoot.cs +++ b/Launcher/WiiCompiled.Setup.Common/PortableRoot.cs @@ -1,4 +1,4 @@ -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Common; /// /// A portable installation is a self-contained directory tree the user can move or carry on removable media: @@ -9,8 +9,11 @@ namespace WiiCompiled.Setup; /// /// The runtime finds the same root independently (runtime/include/runtime_config.h, /// PortableRootDirectory); this class must keep the same marker name, layout, and depth bound. +/// Shared by both installers - Linux's CLI has no --portable flag, so it only ever calls +/// // (always missing, +/// since it never creates a marker file), not . /// -internal static class PortableRoot +public static class PortableRoot { public const string MarkerFileName = "portable.txt"; public const string UserDataDirectoryName = "UserData"; @@ -72,7 +75,7 @@ internal static class PortableRoot if (!File.Exists(marker)) { File.WriteAllText(marker, - $"{ProductInfo.Name} portable installation." + Environment.NewLine + + "WiiCompiled portable installation." + Environment.NewLine + "This marker makes the runtime keep Config.toml, NAND, Cache, and Logs in UserData\\ " + "beside it instead of in %LOCALAPPDATA%." + Environment.NewLine + "Delete it to make this installation use per-user application data again." + @@ -90,54 +93,3 @@ internal static class PortableRoot private static string Normalize(string path) => FileSystemUtilities.NormalizePath(path); } - -/// -/// A portable root can be moved or renamed between operations. Every installed-host operation that -/// reads install-state.json passes through here first so exactly one place decides what a -/// moved installation means, and so a non-portable installation is never touched. -/// -internal static class PortableInstallHealing -{ - /// - /// Reconciles a moved portable installation with its recorded location: the state file adopts the - /// directory it was actually found in, and the native build tree is discarded because its - /// CMake cache holds absolute paths from the old location. Returns whether anything was healed. - /// - public static bool HealMovedInstall(Installation installation, IInstallReporter? reporter = null) - { - // Guard: an ordinary installation that disagrees with its state file is a real problem for - // the operation to report, not something to silently rewrite. - if (PortableRoot.TryFind(installation.Root) is null) return false; - - var state = installation.ReadInstallState(); - if (state is not { SchemaVersion: 1 } || string.IsNullOrWhiteSpace(state.InstallDir)) return false; - - string recorded; - try - { - recorded = FileSystemUtilities.NormalizePath(state.InstallDir); - } - catch (Exception ex) when (ex is ArgumentException or NotSupportedException or PathTooLongException) - { - recorded = state.InstallDir; - } - if (recorded.Equals(installation.Root, StringComparison.OrdinalIgnoreCase)) return false; - - var previous = state.InstallDir; - state.InstallDir = installation.Root; - JsonState.Write(installation.InstallStatePath, state); - - // The configured native build directory bakes absolute source, toolchain, and output paths - // into CMakeCache.txt. After a move it is unusable and would fail the next configure rather - // than being reused, so it is removed and reconfigured from scratch on the next build. - var nativeBuild = Path.Combine(installation.WorkspaceDirectory, "native-build"); - var hadNativeBuild = Directory.Exists(nativeBuild); - if (hadNativeBuild) FileSystemUtilities.DeleteDirectoryIfExists(nativeBuild); - - reporter?.Diagnostic( - $"This portable installation moved from {previous} to {installation.Root}. " + - "The recorded location was updated" + - (hadNativeBuild ? " and the location-bound native build cache was discarded." : ".")); - return true; - } -} diff --git a/Launcher/WiiCompiled.Setup/RetroRewindSource.cs b/Launcher/WiiCompiled.Setup.Common/RetroRewindSource.cs similarity index 91% rename from Launcher/WiiCompiled.Setup/RetroRewindSource.cs rename to Launcher/WiiCompiled.Setup.Common/RetroRewindSource.cs index 75ea9b8..c74205e 100644 --- a/Launcher/WiiCompiled.Setup/RetroRewindSource.cs +++ b/Launcher/WiiCompiled.Setup.Common/RetroRewindSource.cs @@ -1,16 +1,17 @@ -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Common; /// /// Resolves the one canonical Retro Rewind install. Wheel Wizard owns and passes it as -/// --retro-dir; the backend only resolves, reads, and records it, never packages or copies it. +/// --retro-dir; each installer only resolves, reads, and records it, never packages or +/// copies it. /// -internal static class RetroRewindSource +public static class RetroRewindSource { /// /// Resolves the RetroRewind6 folder from a selection that may be the folder itself or a /// parent containing exactly one RetroRewind6/Binaries/Code.pul. /// - internal static string ResolveRetroRewind6(string selected) + public static string ResolveRetroRewind6(string selected) { if (string.IsNullOrWhiteSpace(selected)) throw new InvalidDataException("Choose the canonical Retro Rewind folder."); diff --git a/Launcher/WiiCompiled.Setup/InputValidation.cs b/Launcher/WiiCompiled.Setup.Common/RetroWfcPayload.cs similarity index 65% rename from Launcher/WiiCompiled.Setup/InputValidation.cs rename to Launcher/WiiCompiled.Setup.Common/RetroWfcPayload.cs index 854bd37..f100171 100644 --- a/Launcher/WiiCompiled.Setup/InputValidation.cs +++ b/Launcher/WiiCompiled.Setup.Common/RetroWfcPayload.cs @@ -1,20 +1,28 @@ -using System.Diagnostics; using System.Buffers.Binary; using System.Net; using System.Security.Cryptography; -using System.Text.Json; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Common; -internal static class InputValidation +/// +/// One validated, content-identified download in operation-owned scratch space. Callers use this +/// exact directory for both the update decision and any resulting build. +/// +public sealed record RetroWfcPayloadSnapshot(string Directory, string Sha256, long ByteLength); + +/// +/// Downloads and verifies the Retro-WFC payload (a small, RSA-signed blob served from a single +/// fixed endpoint). Shared by both installers - moved here from WiiCompiled.Setup.Windows's +/// InputValidation.cs, which keeps every one of these method names as thin forwarding wrappers so +/// its many existing call sites (ProductRepairService.cs, LocalBuildService.cs, Installation.cs, +/// SelfTests.cs) needed no changes. +/// +public static class RetroWfcPayload { private const long MaximumRetroWfcPayloadBytes = 16L * 1024 * 1024; // The payload is tens of kilobytes from a single fixed endpoint 30s is good. private static readonly TimeSpan RetroWfcDownloadTimeout = TimeSpan.FromSeconds(30); private static readonly TimeSpan RetroWfcRetryDelay = TimeSpan.FromSeconds(1); - private static readonly HashSet SupportedDiscImageExtensions = new( - [".iso", ".gcm", ".gcz", ".ciso", ".wbfs", ".wia", ".rvz"], - StringComparer.OrdinalIgnoreCase); public const string CurrentRetroWfcPayloadUri = "http://nas.play.rwfc.net/payload?g=RMCPD00"; private static readonly string RetroWfcOfflinePayloadFile = @@ -37,43 +45,6 @@ internal static class InputValidation private const int RetroWfcPayloadSignatureOffset = 0x10; private const int RetroWfcPayloadMinimumBytes = 0x130; - public static void ValidateExtension(string gamePath) - { - if (!File.Exists(gamePath)) - throw new FileNotFoundException("The selected game image does not exist.", gamePath); - var extension = Path.GetExtension(gamePath); - if (!SupportedDiscImageExtensions.Contains(extension)) - throw new InvalidDataException( - "Select a complete Wii disc image in ISO, GCM, GCZ, CISO, WBFS, WIA, or RVZ format."); - } - - public static async Task ReadDiscHeaderAsync(string dolphinTool, string gamePath, - CancellationToken cancellationToken = default) - { - ValidateExtension(gamePath); - var result = await ProcessRunner.RunAsync(dolphinTool, - ["header", "-i", Path.GetFullPath(gamePath), "-j"], null, cancellationToken); - if (result.ExitCode != 0) - throw new InvalidDataException("DolphinTool could not read this disc image. " + result.CombinedOutput.Trim()); - - var json = result.StandardOutput.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries) - .FirstOrDefault(line => line.TrimStart().StartsWith('{')); - if (json is null) - throw new InvalidDataException("DolphinTool did not return disc metadata."); - return JsonSerializer.Deserialize(json) - ?? throw new InvalidDataException("DolphinTool returned invalid disc metadata."); - } - - public static void EnsureCompatibleDisc(DiscHeader header, PayloadManifest manifest) - { - if (!header.GameId.Equals(manifest.ExpectedGameId, StringComparison.OrdinalIgnoreCase)) - { - throw new InvalidDataException( - $"This build supports Mario Kart Wii PAL ({manifest.ExpectedGameId}). " + - $"The selected image is {header.GameId} ({header.InternalName}, {header.Region})."); - } - } - public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory, RSAParameters? signingKey = null) { @@ -186,7 +157,7 @@ internal static class InputValidation } } - internal static bool IsTransientRetroWfcDownloadFailure(Exception exception, + public static bool IsTransientRetroWfcDownloadFailure(Exception exception, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return false; @@ -232,73 +203,9 @@ internal static class InputValidation "The Retro-WFC payload is not signed by the pinned Retro-WFC signing key."); } - public static string Sha256File(string path) + private static string Sha256File(string path) { using var stream = File.OpenRead(path); return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant(); } } - -internal sealed record ProcessResult(int ExitCode, string StandardOutput, string StandardError) -{ - public string CombinedOutput => StandardOutput + Environment.NewLine + StandardError; -} - -internal static class ProcessRunner -{ - /// Runs a redirected child process to completion. sets up a - /// working directory or scrubbed environment; is off for callers that only - /// forward output live, so a build's output isn't buffered in memory for nobody to read. - public static async Task RunAsync(string executable, IReadOnlyList arguments, - Action? output, CancellationToken cancellationToken, - Action? configure = null, bool capture = true, - Action? onTerminationFailure = null) - { - var info = new ProcessStartInfo - { - FileName = executable, - UseShellExecute = false, - CreateNoWindow = true, - RedirectStandardOutput = true, - RedirectStandardError = true - }; - foreach (var argument in arguments) info.ArgumentList.Add(argument); - configure?.Invoke(info); - - using var process = new Process { StartInfo = info, EnableRaisingEvents = true }; - var stdout = new List(); - var stderr = new List(); - process.OutputDataReceived += (_, e) => { if (e.Data is not null) { if (capture) stdout.Add(e.Data); output?.Invoke(e.Data); } }; - process.ErrorDataReceived += (_, e) => { if (e.Data is not null) { if (capture) stderr.Add(e.Data); output?.Invoke(e.Data); } }; - if (!process.Start()) throw new InvalidOperationException($"Could not start {executable}."); - process.BeginOutputReadLine(); - process.BeginErrorReadLine(); - await WaitForExitAsync(process, cancellationToken, onTerminationFailure); - return new ProcessResult(process.ExitCode, string.Join(Environment.NewLine, stdout), - string.Join(Environment.NewLine, stderr)); - } - - public static async Task WaitForExitAsync(Process process, CancellationToken cancellationToken, - Action? onTerminationFailure = null) - { - try - { - await process.WaitForExitAsync(cancellationToken); - } - catch (OperationCanceledException) - { - try - { - if (!process.HasExited) process.Kill(entireProcessTree: true); - } - catch (Exception ex) - { - onTerminationFailure?.Invoke(ex); - } - await process.WaitForExitAsync(CancellationToken.None); - process.WaitForExit(); - throw; - } - process.WaitForExit(); - } -} diff --git a/Launcher/WiiCompiled.Setup/RuntimeConfiguration.cs b/Launcher/WiiCompiled.Setup.Common/RuntimeConfiguration.cs similarity index 89% rename from Launcher/WiiCompiled.Setup/RuntimeConfiguration.cs rename to Launcher/WiiCompiled.Setup.Common/RuntimeConfiguration.cs index cc7a546..9a4f88a 100644 --- a/Launcher/WiiCompiled.Setup/RuntimeConfiguration.cs +++ b/Launcher/WiiCompiled.Setup.Common/RuntimeConfiguration.cs @@ -1,16 +1,19 @@ -using System.Globalization; using System.Text; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Common; -internal sealed record RuntimeConfigSnapshot(bool Existed, byte[] Contents); +public sealed record RuntimeConfigSnapshot(bool Existed, byte[] Contents); /// /// Reads and writes the runtime's Config.toml. Every entry point takes the file it operates on /// since its location depends on the installation (portable UserData vs. per-user app data); -/// callers obtain it once via . +/// callers obtain it once via . Shared by both installers - Linux +/// never creates a marker file, so +/// /'s portable-root lookups always miss +/// there and this degrades to the same plain per-user-app-data, always-absolute-path behavior a +/// non-portable Windows install already gets. /// -internal static class RuntimeConfiguration +public static class RuntimeConfiguration { public const string ConfigFileName = "Config.toml"; @@ -51,7 +54,7 @@ internal static class RuntimeConfiguration File.Delete(configPath); } - internal static void SetDvdRoot(string configPath, string dvdRoot) => + public static void SetDvdRoot(string configPath, string dvdRoot) => SetPath(configPath, "dvd_root", dvdRoot); /// @@ -59,21 +62,21 @@ internal static class RuntimeConfiguration /// asset overlay by scanning this directory live at launch, so an asset-only Retro Rewind update /// needs no backend work: the next launch simply reads the new files. /// - internal static void SetRetroRewindRoot(string configPath, string retroRewindRoot) => + public static void SetRetroRewindRoot(string configPath, string retroRewindRoot) => SetPath(configPath, "retro_rewind_root", retroRewindRoot); /// The canonical Retro Rewind root, or null when no installation has recorded one. public static string? GetRetroRewindRoot(string configPath) => GetResolvedPath(configPath, "retro_rewind_root"); - internal static void RemoveRetroRewindRootIfOwned(string configPath, string retroRewindRoot) => + public static void RemoveRetroRewindRootIfOwned(string configPath, string retroRewindRoot) => RemovePathIfOwned(configPath, "retro_rewind_root", retroRewindRoot); - internal static void RemoveDvdRootIfOwned(string configPath, string dvdRoot) => + public static void RemoveDvdRootIfOwned(string configPath, string dvdRoot) => RemovePathIfOwned(configPath, "dvd_root", dvdRoot); /// The raw stored text of a [paths] key, exactly as the file holds it. - internal static string? GetPath(string configPath, string key) => + public static string? GetPath(string configPath, string key) => TryUnquoteToml(GetRawValue(configPath, "paths", key) ?? "", out var value) ? value : null; /// @@ -81,17 +84,17 @@ internal static class RuntimeConfiguration /// [paths] value against the directory holding Config.toml (never the working /// directory), so the host must resolve it the same way before comparing or reading it. /// - internal static string? GetResolvedPath(string configPath, string key) + public static string? GetResolvedPath(string configPath, string key) { var stored = GetPath(configPath, key); return string.IsNullOrWhiteSpace(stored) ? null : ResolveAgainstConfig(configPath, stored); } - internal static string ConfigDirectory(string configPath) => + public static string ConfigDirectory(string configPath) => Path.GetDirectoryName(Path.GetFullPath(configPath)) ?? throw new InvalidOperationException($"{configPath} has no containing directory."); - internal static string ResolveAgainstConfig(string configPath, string value) => + public static string ResolveAgainstConfig(string configPath, string value) => Path.GetFullPath(value, ConfigDirectory(configPath)); private static void SetPath(string configPath, string key, string value) @@ -150,7 +153,7 @@ internal static class RuntimeConfiguration } /// The raw TOML literal stored for a key, or null when the section or key is absent. - internal static string? GetRawValue(string configPath, string section, string key) + public static string? GetRawValue(string configPath, string section, string key) { if (!File.Exists(configPath)) return null; var header = $"[{section}]"; @@ -265,7 +268,7 @@ internal static class RuntimeConfiguration private static string QuoteToml(string value) => "\"" + value.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\""; - internal static bool TryUnquoteToml(string value, out string result) + public static bool TryUnquoteToml(string value, out string result) { result = ""; if (value.Length < 2) return false; @@ -292,5 +295,4 @@ internal static class RuntimeConfiguration result = builder.ToString(); return true; } - } diff --git a/Launcher/WiiCompiled.Setup.Common/WiiCompiled.Setup.Common.csproj b/Launcher/WiiCompiled.Setup.Common/WiiCompiled.Setup.Common.csproj new file mode 100644 index 0000000..91fbef7 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Common/WiiCompiled.Setup.Common.csproj @@ -0,0 +1,15 @@ + + + net8.0 + enable + enable + WiiCompiled.Setup.Common + WiiCompiled.Setup.Common + 0.2.22 + patchzy + WiiCompiled + Shared nodtool/Retro-WFC-payload logic used by both the Windows and Linux installers + embedded + en + + diff --git a/Launcher/WiiCompiled.Setup.Linux/BuildRunner.cs b/Launcher/WiiCompiled.Setup.Linux/BuildRunner.cs new file mode 100644 index 0000000..677b757 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Linux/BuildRunner.cs @@ -0,0 +1,81 @@ +using System.Diagnostics; + +namespace WiiCompiled.Setup.Linux; + +/// +/// Invokes Launcher/local-build.sh and turns its stdout into progress reports. Replaces +/// LocalBuildService.cs's hardcoded Windows PowerShell 5.1 invocation - there is no PowerShell +/// dependency here at all, just bash. +/// +internal static class BuildRunner +{ + public static async Task RunAsync( + string workspace, string profile, string outputDir, string? baseOutputDir, + string? retroDir, string? retroWfcOfflineDir, bool skipRetroWfcPayload, + bool forceCleanBuild, string? translatorBin, IInstallReporter reporter, CancellationToken cancellationToken) + { + var script = Path.Combine(workspace, "Launcher", "local-build.sh"); + if (!File.Exists(script)) throw new FileNotFoundException("local-build.sh is missing", script); + + var startInfo = new ProcessStartInfo("bash") + { + WorkingDirectory = workspace, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + }; + startInfo.ArgumentList.Add(script); + startInfo.ArgumentList.Add("--profile"); startInfo.ArgumentList.Add(profile); + startInfo.ArgumentList.Add("--output-dir"); startInfo.ArgumentList.Add(outputDir); + if (!string.IsNullOrEmpty(baseOutputDir)) + { + startInfo.ArgumentList.Add("--base-output-dir"); startInfo.ArgumentList.Add(baseOutputDir); + } + if (!string.IsNullOrEmpty(retroDir)) + { + // Still forwarded to local-build.sh under its own internal name - + // --retro-rewind-package-dir - matching LocalBuild.ps1's own -RetroRewindPackageDirectory. + startInfo.ArgumentList.Add("--retro-rewind-package-dir"); startInfo.ArgumentList.Add(retroDir); + } + if (!string.IsNullOrEmpty(retroWfcOfflineDir)) + { + startInfo.ArgumentList.Add("--retro-wfc-offline-dir"); startInfo.ArgumentList.Add(retroWfcOfflineDir); + } + if (skipRetroWfcPayload) startInfo.ArgumentList.Add("--skip-retro-wfc-payload"); + if (forceCleanBuild) startInfo.ArgumentList.Add("--force-clean-build"); + if (!string.IsNullOrEmpty(translatorBin)) + { + startInfo.ArgumentList.Add("--translator-bin"); startInfo.ArgumentList.Add(translatorBin); + } + + using var process = new Process { StartInfo = startInfo }; + var window = new BuildProgressWindow(reporter, InstallStages.Build, start: 6, end: 96); + + process.OutputDataReceived += (_, e) => { if (e.Data is not null) window.Observe(e.Data); }; + process.ErrorDataReceived += (_, e) => { if (e.Data is not null) reporter.Diagnostic(e.Data); }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + try + { + await process.WaitForExitAsync(cancellationToken); + } + catch (OperationCanceledException) + { + KillProcessTree(process); + throw; + } + + if (process.ExitCode != 0) + { + throw new InvalidOperationException($"local-build.sh failed (exit {process.ExitCode}). See diagnostics above."); + } + } + + private static void KillProcessTree(Process process) + { + try { process.Kill(entireProcessTree: true); } catch { /* best-effort */ } + } +} diff --git a/Launcher/WiiCompiled.Setup.Linux/DesktopEntry.cs b/Launcher/WiiCompiled.Setup.Linux/DesktopEntry.cs new file mode 100644 index 0000000..6ae6426 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Linux/DesktopEntry.cs @@ -0,0 +1,40 @@ +namespace WiiCompiled.Setup.Linux; + +/// +/// freedesktop.org .desktop application-menu entries. Replaces ShellIntegration.cs's registry +/// uninstall entry (no Linux analogue for an unpackaged tool - Windows already skips that step for +/// portable installs, this just applies that same behavior universally) and .lnk shortcuts. +/// +internal static class DesktopEntry +{ + private static string ApplicationsDirectory => + Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "applications"); + + private static string PathFor(string profile) => + Path.Combine(ApplicationsDirectory, $"wiicompiled-{profile}.desktop"); + + public static void Create(string profile, string displayName, string exePath) + { + // exePath is the installed native runtime binary itself (e.g. + // .../Install/Base/WiiCompiled) - each profile already gets its own .desktop file here, + // so there is no need to route through the setup tool's own launch-base/launch-retro + // subcommand dispatch first. Unquoted: the Desktop Entry spec's Exec grammar doesn't take + // a bare '"'-wrapped path, and none is needed here anyway - the only part of this path + // that varies is the username, which Unix forbids containing whitespace. + Directory.CreateDirectory(ApplicationsDirectory); + var contents = + "[Desktop Entry]\n" + + "Type=Application\n" + + $"Name={displayName}\n" + + $"Exec={exePath}\n" + + "Categories=Game;\n" + + "Terminal=false\n"; + File.WriteAllText(PathFor(profile), contents); + } + + public static void Remove(string profile) + { + var path = PathFor(profile); + if (File.Exists(path)) File.Delete(path); + } +} diff --git a/Launcher/WiiCompiled.Setup.Linux/DiscTool.cs b/Launcher/WiiCompiled.Setup.Linux/DiscTool.cs new file mode 100644 index 0000000..fad7213 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Linux/DiscTool.cs @@ -0,0 +1,116 @@ +using System.Security.Cryptography; +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Linux; + +/// +/// Validates and extracts the user's own Mario Kart Wii disc via `nodtool` (see +/// WiiCompiled.Setup.Common/NodToolProvider.cs) - a prebuilt, MIT/Apache-2.0-licensed CLI from +/// encounter/nod, replacing the earlier dependency on a system-installed `dolphin-tool` +/// (GPL-2.0-or-later, and not reliably packaged standalone by every distro). +/// +internal static class DiscTool +{ + public static async Task ValidateAndExtractAsync( + string isoPath, ProjectManifest manifest, string assetsDirectory, string workspace, + string? nodToolBin, IInstallReporter reporter, CancellationToken cancellationToken) + { + var nodTool = nodToolBin ?? await NodToolProvider.ResolveAsync(workspace, cancellationToken); + + // `nodtool info` only decodes the disc/partition headers (milliseconds); `nodtool extract` + // copies the whole data partition to disk (tens of seconds for a custom-track-heavy MKWii + // ISO). Checking the game ID first, before extracting, means a wrong disc fails fast - + // matching the original dolphin-tool `header` step this replaces. + reporter.Progress(InstallStages.ExtractDisc, "Reading the disc header", 2); + var info = NodToolInfoParser.Parse(await RunInfoAsync(nodTool, isoPath, cancellationToken)); + if (!string.Equals(info.GameId, manifest.GameId, StringComparison.Ordinal)) + { + throw new InvalidOperationException( + $"This disc is '{info.GameId}', not the expected '{manifest.GameId}' (Mario Kart Wii, region {manifest.Region}). " + + "Only your own legally-owned copy of that exact game/region can be used."); + } + + // Extracted straight into Assets/DATA (kept, not a scratch dir) - the runtime reads course/ + // texture/audio data from this directory live via [paths] dvd_root, not just at translation + // time, so it has to survive past this install (see Program.cs, which points dvd_root here). + reporter.Progress(InstallStages.ExtractDisc, "Extracting the disc image", 4); + var dataDir = Path.Combine(assetsDirectory, "DATA"); + if (Directory.Exists(dataDir)) Directory.Delete(dataDir, recursive: true); + await RunExtractAsync(nodTool, isoPath, dataDir, cancellationToken); + + var dolPath = Path.Combine(dataDir, "sys", "main.dol"); + var relPath = Path.Combine(dataDir, "files", "rel", "StaticR.rel"); + if (!File.Exists(dolPath)) throw new FileNotFoundException("nodtool did not produce main.dol", dolPath); + if (!File.Exists(relPath)) throw new FileNotFoundException("nodtool did not produce StaticR.rel", relPath); + + var dolSha = Sha256Of(dolPath); + var relSha = Sha256Of(relPath); + if (!string.Equals(dolSha, manifest.DolSha256, StringComparison.Ordinal)) + { + throw new InvalidOperationException( + $"main.dol sha256 mismatch: expected {manifest.DolSha256}, got {dolSha}. " + + "This disc revision does not match what this project's manifest is pinned to."); + } + if (!string.Equals(relSha, manifest.RelSha256, StringComparison.Ordinal)) + { + throw new InvalidOperationException( + $"StaticR.rel sha256 mismatch: expected {manifest.RelSha256}, got {relSha}. " + + "This disc revision does not match what this project's manifest is pinned to."); + } + + Directory.CreateDirectory(assetsDirectory); + File.Copy(dolPath, Path.Combine(assetsDirectory, "main.dol"), overwrite: true); + File.Copy(relPath, Path.Combine(assetsDirectory, "StaticR.rel"), overwrite: true); + reporter.Progress(InstallStages.ExtractDisc, "Disc validated and extracted", 6); + } + + private static async Task RunInfoAsync(string nodTool, string isoPath, CancellationToken cancellationToken) + { + var startInfo = new System.Diagnostics.ProcessStartInfo(nodTool) + { + ArgumentList = { "info", isoPath }, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + }; + using var process = System.Diagnostics.Process.Start(startInfo) + ?? throw new InvalidOperationException($"Failed to start {nodTool}."); + var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken); + var stderr = await process.StandardError.ReadToEndAsync(cancellationToken); + await process.WaitForExitAsync(cancellationToken); + if (process.ExitCode != 0) + { + throw new InvalidOperationException( + $"nodtool could not read this disc image (exit {process.ExitCode}): {stderr}{stdout}".Trim()); + } + return stdout; + } + + private static string Sha256Of(string path) + { + using var stream = File.OpenRead(path); + return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant(); + } + + private static async Task RunExtractAsync(string nodTool, string isoPath, string outDir, CancellationToken cancellationToken) + { + var startInfo = new System.Diagnostics.ProcessStartInfo(nodTool) + { + ArgumentList = { "extract", isoPath, outDir, "-q" }, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + }; + + using var process = System.Diagnostics.Process.Start(startInfo) + ?? throw new InvalidOperationException($"Failed to start {nodTool}."); + var stdout = await process.StandardOutput.ReadToEndAsync(cancellationToken); + var stderr = await process.StandardError.ReadToEndAsync(cancellationToken); + await process.WaitForExitAsync(cancellationToken); + if (process.ExitCode != 0) + { + throw new InvalidOperationException( + $"nodtool extract {isoPath} failed (exit {process.ExitCode}): {stderr}{stdout}"); + } + } +} diff --git a/Launcher/WiiCompiled.Setup.Linux/InstallProgress.cs b/Launcher/WiiCompiled.Setup.Linux/InstallProgress.cs new file mode 100644 index 0000000..e816e62 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Linux/InstallProgress.cs @@ -0,0 +1,209 @@ +using System.Text.Json; + +namespace WiiCompiled.Setup.Linux; + +// Ported near-verbatim from Launcher/WiiCompiled.Setup/InstallProgress.cs: this whole file is +// platform-neutral (System.Text.Json + Console only), so the NDJSON --progress-json wire protocol +// stays byte-for-byte the same shape a future GUI already speaks on Windows. + +/// +/// Stable stage identifiers reported by --progress-json. Kept intentionally small for this +/// lean Linux installer (no toolkit-extraction/publish-transaction stages, since there is no +/// bundled toolkit or staged workspace copy here - see the plan's "operate on a git checkout" +/// scoping decision). +/// +internal static class InstallStages +{ + public const string Validate = "validate"; + public const string ExtractDisc = "extract-disc"; + public const string Build = "build"; + public const string Shortcuts = "shortcuts"; +} + +/// +/// Where an installation reports what it is doing. Progress is coarse and monotonic; raw translator +/// and compiler output is a diagnostic, never progress, because it is unbounded and machine-hostile. +/// +internal interface IInstallReporter +{ + void Progress(string stage, string message, int percent); + void Diagnostic(string line); +} + +/// +/// The --progress-json protocol: one JSON object per line on stdout, nothing else on stdout, +/// diagnostics on stderr. The terminal result line is written exactly once. +/// +internal sealed class NdjsonInstallReporter : IInstallReporter +{ + private static readonly JsonSerializerOptions Options = new() { WriteIndented = false }; + private readonly object _gate = new(); + private int _lastPercent; + private bool _finished; + + public void Progress(string stage, string message, int percent) + { + lock (_gate) + { + if (_finished) return; + // Percentages are clamped monotonic: a caller's progress bar must never walk backwards + // because a later stage happened to estimate a lower number. + _lastPercent = Math.Clamp(Math.Max(percent, _lastPercent), 0, 99); + WriteLine(new { type = "progress", stage, message, percent = _lastPercent }); + } + } + + public void Diagnostic(string line) => Console.Error.WriteLine(line); + + public void Success(string installDirectory) + { + lock (_gate) + { + if (_finished) return; + _finished = true; + WriteLine(new { type = "result", success = true, version = ProductInfo.Version, installDir = installDirectory }); + } + } + + public void Failure(string error) + { + lock (_gate) + { + if (_finished) return; + _finished = true; + WriteLine(new { type = "result", success = false, error }); + } + } + + /// + /// The terminal result line is the caller's only completion signal, so no exit path may skip it. + /// Callers invoke this from a finally block; it is a no-op once a result was already written. + /// + public void EnsureFinished(string errorIfUnfinished) => Failure(errorIfUnfinished); + + private static void WriteLine(object value) + { + Console.Out.WriteLine(JsonSerializer.Serialize(value, Options)); + Console.Out.Flush(); + } +} + +/// Plain-text console reporting for a run without --progress-json. +internal sealed class ConsoleInstallReporter : IInstallReporter +{ + public void Progress(string stage, string message, int percent) => + Console.Out.WriteLine($"[{percent,3}%] {message}"); + + public void Diagnostic(string line) => Console.Out.WriteLine(line); +} + +/// +/// Build step identifiers from local-build.sh's MKWCBUILD:STEP:<id> lines - the id is +/// the contract, matched against Launcher/local-build.sh's log_step() call sites. +/// +internal static class BuildStepIds +{ + public const string BuildTranslator = "build-translator"; + public const string ReuseBaseTranslation = "reuse-base-translation"; + public const string RetranslateBase = "retranslate-base"; + public const string TranslateBase = "translate-base"; + public const string EmitBaseManifest = "emit-base-manifest"; + public const string TranslateMod = "translate-mod"; + public const string GenerateDataInit = "generate-data-init"; + public const string EmitBuildShards = "emit-build-shards"; + public const string ConfigureNative = "configure-native"; + public const string Compile = "compile"; +} + +/// +/// Maps one local-build.sh run onto a slice of the overall percentage. local-build.sh announces +/// every step it starts with an MKWCBUILD: prefix, so the slice can advance on real events +/// instead of on a timer. +/// +internal sealed class BuildProgressWindow +{ + private const string Marker = "MKWCBUILD:"; + private const string StepMarker = "STEP:"; + + /// The fraction the compile step reaches; beyond it, compiler output is a heartbeat. + private const double CompileFraction = 0.58; + + private static readonly (string Id, double Fraction, string Message)[] Steps = + [ + (BuildStepIds.BuildTranslator, 0.04, "Building the translator"), + (BuildStepIds.ReuseBaseTranslation, 0.30, "Reusing the completed base translation"), + (BuildStepIds.RetranslateBase, 0.08, "The base translation is stale; retranslating it"), + (BuildStepIds.TranslateBase, 0.10, "Translating Mario Kart Wii"), + (BuildStepIds.EmitBaseManifest, 0.34, "Creating the translation manifest"), + (BuildStepIds.TranslateMod, 0.38, "Translating the Retro Rewind Code.pul"), + (BuildStepIds.GenerateDataInit, 0.44, "Generating game data initialization"), + (BuildStepIds.EmitBuildShards, 0.48, "Preparing the native build"), + (BuildStepIds.ConfigureNative, 0.52, "Configuring the compiler"), + (BuildStepIds.Compile, CompileFraction, "Compiling the game. This is the longest step"), + ]; + + private readonly IInstallReporter _reporter; + private readonly string _stage; + private readonly int _start; + private readonly int _end; + private double _fraction; + private string _message = "Preparing the local build"; + private int _reportedPercent = -1; + + public BuildProgressWindow(IInstallReporter reporter, string stage, int start, int end) + { + _reporter = reporter; + _stage = stage; + _start = start; + _end = end; + } + + public void Observe(string line) + { + var index = line.IndexOf(Marker, StringComparison.Ordinal); + if (index >= 0) + { + var text = line[(index + Marker.Length)..].Trim(); + if (text.StartsWith(StepMarker, StringComparison.Ordinal)) + { + var identifier = text[StepMarker.Length..]; + var end = identifier.IndexOf(' '); + if (end >= 0) identifier = identifier[..end]; + foreach (var (id, fraction, message) in Steps) + { + if (!id.Equals(identifier, StringComparison.Ordinal)) continue; + if (fraction > _fraction) + { + _fraction = fraction; + _message = message; + Emit(); + } + return; + } + } + } + + // Anything else - a plain MKWCBUILD note, or raw tool output - stays a diagnostic and only + // feeds the heartbeat below. + _reporter.Diagnostic(line); + // Compilation announces itself once and then emits thousands of compiler lines. Treat that + // output as a heartbeat so the slice keeps creeping forward, but only publish a progress + // line when the rounded percentage actually changes. + if (_fraction >= CompileFraction) + { + _fraction = Math.Min(0.97, _fraction + 0.0015); + Emit(); + } + } + + private void Emit() + { + var percent = Interpolate(_fraction); + if (percent == _reportedPercent) return; + _reportedPercent = percent; + _reporter.Progress(_stage, _message, percent); + } + + private int Interpolate(double fraction) => + (int)Math.Round(_start + (_end - _start) * Math.Clamp(fraction, 0, 1)); +} diff --git a/Launcher/WiiCompiled.Setup.Linux/Models.cs b/Launcher/WiiCompiled.Setup.Linux/Models.cs new file mode 100644 index 0000000..b9c9671 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Linux/Models.cs @@ -0,0 +1,31 @@ +namespace WiiCompiled.Setup.Linux; + +internal static class ProductInfo +{ + public const string Name = "WiiCompiled"; + public const string Version = "0.2.22"; +} + +/// One installed product's record inside install-state.json. +internal sealed class ProductInstallRecord +{ + public string Profile { get; set; } = ""; + public string InstallDirectory { get; set; } = ""; + public string ExecutableName { get; set; } = ""; + public string DolSha256 { get; set; } = ""; + public string RelSha256 { get; set; } = ""; + public string BuiltUtc { get; set; } = ""; +} + +/// +/// The whole flat state document this tool keeps at ~/.local/share/WiiCompiled/install-state.json. +/// Deliberately not a fingerprint tree: local-build.sh already does its own incremental-rebuild +/// caching, so this only needs to remember where things were installed and what they were built +/// against, not decide when to rebuild. +/// +internal sealed class InstallState +{ + public int SchemaVersion { get; set; } = 1; + public string Workspace { get; set; } = ""; + public List Products { get; set; } = new(); +} diff --git a/Launcher/WiiCompiled.Setup.Linux/Program.cs b/Launcher/WiiCompiled.Setup.Linux/Program.cs new file mode 100644 index 0000000..dd68523 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Linux/Program.cs @@ -0,0 +1,328 @@ +using System.Security.Cryptography; +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Linux; + +internal static class Program +{ + private static async Task Main(string[] args) + { + // Checked anywhere in argv, not just args[0]: AppRun (Launcher/build-appimage.sh) prepends + // --workspace ahead of whatever the caller passed, so these can't assume position 0. + if (args.Length == 0 || args.Contains("-h") || args.Contains("--help")) { PrintUsage(); return 0; } + if (args.Contains("--version")) { Console.WriteLine(ProductInfo.Version); return 0; } + + using var cts = new CancellationTokenSource(); + // Replaces CancellationSignal.cs's named-EventWaitHandle IPC (Windows-only): SIGINT/SIGTERM + // are the portable, standard way for a parent (Wheel Wizard or a shell) to cancel this + // process and the build it spawned. + using var sigint = System.Runtime.InteropServices.PosixSignalRegistration.Create( + System.Runtime.InteropServices.PosixSignal.SIGINT, context => { context.Cancel = true; cts.Cancel(); }); + using var sigterm = System.Runtime.InteropServices.PosixSignalRegistration.Create( + System.Runtime.InteropServices.PosixSignal.SIGTERM, context => { context.Cancel = true; cts.Cancel(); }); + return await RunAsync(args, cts); + } + + private static async Task RunAsync(string[] args, CancellationTokenSource cts) + { + // AppRun (Launcher/build-appimage.sh) invokes this as `wiicompiled-setup --workspace + // [options]` - a global flag ahead of the subcommand - so the command + // word is whichever token isn't part of a --flag/value pair, not strictly args[0]. + var (command, flags) = ParseArgs(args); + if (command is null) { PrintUsage(); return 1; } + var progressJson = flags.ContainsKey("progress-json"); + IInstallReporter reporter = progressJson ? new NdjsonInstallReporter() : new ConsoleInstallReporter(); + + try + { + switch (command) + { + case "install": + await InstallAsync(flags, reporter, cts.Token); + break; + case "uninstall": + Uninstall(); + break; + case "launch-base": + return Launch("base", flags); + case "launch-retro": + return Launch("retro-rewind", flags); + case "check-products": + CheckProducts(); + break; + default: + Console.Error.WriteLine($"Unknown command: {command}"); + PrintUsage(); + return 1; + } + (reporter as NdjsonInstallReporter)?.Success(flags.GetValueOrDefault("install-dir") ?? ""); + return 0; + } + catch (OperationCanceledException) + { + Console.Error.WriteLine("Cancelled."); + (reporter as NdjsonInstallReporter)?.Failure("cancelled"); + return 130; + } + catch (Exception ex) + { + Console.Error.WriteLine($"error: {ex.Message}"); + (reporter as NdjsonInstallReporter)?.Failure(ex.Message); + return 1; + } + } + + private static async Task InstallAsync(Dictionary flags, IInstallReporter reporter, CancellationToken token) + { + var retroDir = flags.GetValueOrDefault("retro-dir"); + var installsRetro = !string.IsNullOrEmpty(retroDir); + var downloadPayload = flags.ContainsKey("download-retro-wfc-payload"); + var skipPayload = flags.ContainsKey("skip-retro-wfc-payload"); + if (installsRetro) + { + if (downloadPayload == skipPayload) + throw new ArgumentException( + "Choose exactly one Retro-WFC mode: --download-retro-wfc-payload or --skip-retro-wfc-payload."); + } + else if (downloadPayload || skipPayload) + { + throw new ArgumentException("A Retro-WFC payload option is valid only with --retro-dir."); + } + + // Canonicalizes to the exact RetroRewind6 folder (accepting a parent folder or a symlink), + // the same validation Windows applies via this same shared method - local-build.sh's own + // check further down is a simpler backstop, not the primary validation anymore. + if (installsRetro) retroDir = RetroRewindSource.ResolveRetroRewind6(retroDir!); + + var workspace = flags.GetValueOrDefault("workspace") ?? WorkspaceLocator.FindFrom(AppContext.BaseDirectory); + var manifest = ProjectManifest.Load(Path.Combine(workspace, "projects", "mkwii", "recomp.yml")); + var assetsDir = Path.Combine(workspace, "Assets"); + + reporter.Progress(InstallStages.Validate, "Checking prerequisites", 1); + if (flags.TryGetValue("game", out var isoPath) && !string.IsNullOrEmpty(isoPath)) + { + await DiscTool.ValidateAndExtractAsync(isoPath, manifest, assetsDir, workspace, + flags.GetValueOrDefault("disc-tool-bin"), reporter, token); + } + else + { + var dol = Path.Combine(assetsDir, "main.dol"); + var rel = Path.Combine(assetsDir, "StaticR.rel"); + if (!File.Exists(dol) || !File.Exists(rel)) + { + throw new InvalidOperationException( + "No --game ISO was given and Assets/main.dol + Assets/StaticR.rel are not already present. " + + "Either pass --game , or extract them yourself first (see translator/README.md)."); + } + } + + var state = JsonState.TryRead(StatePath) ?? new InstallState { Workspace = workspace }; + state.Workspace = workspace; + + var profile = installsRetro ? "both" : "base"; + var profiles = installsRetro ? new[] { "base", "retro-rewind" } : new[] { "base" }; + var baseInstallDir = installsRetro ? DefaultInstallDir("base") : null; + var installDir = flags.GetValueOrDefault("install-dir") ?? DefaultInstallDir(installsRetro ? "retro-rewind" : "base"); + + string? retroWfcOfflineDir = null; + if (downloadPayload) + { + // Reused if a previous install already downloaded and it's still valid - matches + // Windows's own reuse-if-valid behavior instead of re-downloading on every install. + var cacheDir = Path.Combine(workspace, "generated", "retro-wfc-payload"); + reporter.Progress(InstallStages.Validate, "Preparing the Retro-WFC payload", 1); + try + { + RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(cacheDir); + } + catch (InvalidDataException) + { + await RetroWfcPayload.DownloadRetroWfcPayloadAsync( + RetroWfcPayload.CurrentRetroWfcPayloadUri, cacheDir, token); + } + retroWfcOfflineDir = cacheDir; + } + + await BuildRunner.RunAsync( + workspace, profile, installDir, baseInstallDir, + retroDir, + retroWfcOfflineDir, + skipPayload, + flags.ContainsKey("force-clean-build"), + flags.GetValueOrDefault("translator-bin"), + reporter, token); + + reporter.Progress(InstallStages.Shortcuts, "Creating shortcuts", 98); + var dolSha = Sha256Of(Path.Combine(assetsDir, "main.dol")); + var relSha = Sha256Of(Path.Combine(assetsDir, "StaticR.rel")); + + foreach (var p in profiles) + { + var dir = p == "base" ? (baseInstallDir ?? installDir) : installDir; + var exeName = p == "base" ? "WiiCompiled" : "RetroRewind"; + var displayName = p == "base" ? "WiiCompiled (base game)" : "WiiCompiled (Retro Rewind)"; + state.Products.RemoveAll(r => r.Profile == p); + state.Products.Add(new ProductInstallRecord + { + Profile = p, + InstallDirectory = dir, + ExecutableName = exeName, + DolSha256 = dolSha, + RelSha256 = relSha, + BuiltUtc = DateTime.UtcNow.ToString("O"), + }); + DesktopEntry.Create(p, displayName, Path.Combine(dir, exeName)); + } + JsonState.Write(StatePath, state); + + // The runtime reads course/texture/audio data live from dvd_root at every launch, not just + // at translation time - without this the game fatally errors the instant it needs any file + // that isn't main.dol/StaticR.rel. Linux has no --portable flag, so this is always the + // per-user Config.toml (RuntimeConfiguration.ResolveConfigPath's Windows-only portable-root + // lookup has nothing to find here either way). + var configPath = RuntimeConfiguration.ApplicationDataConfigPath; + var dataDir = Path.Combine(assetsDir, "DATA"); + if (Directory.Exists(dataDir)) + { + RuntimeConfiguration.SetDvdRoot(configPath, dataDir); + } + if (installsRetro) + { + RuntimeConfiguration.SetRetroRewindRoot(configPath, retroDir!); + } + + reporter.Progress(InstallStages.Shortcuts, "Install complete", 99); + } + + private static void Uninstall() + { + // Matches Windows: UninstallService.cs removes the whole install directory unconditionally - + // there is no partial-product uninstall on either platform. + var state = JsonState.TryRead(StatePath) ?? new InstallState(); + foreach (var record in state.Products.ToList()) + { + if (Directory.Exists(record.InstallDirectory)) + { + Directory.Delete(record.InstallDirectory, recursive: true); + } + DesktopEntry.Remove(record.Profile); + state.Products.Remove(record); + Console.WriteLine($"Removed {record.Profile} from {record.InstallDirectory}"); + } + JsonState.Write(StatePath, state); + } + + private static int Launch(string profile, Dictionary flags) + { + var state = JsonState.TryRead(StatePath); + var record = state?.Products.FirstOrDefault(r => r.Profile == profile); + if (record is null) + { + var installHint = profile == "retro-rewind" + ? "install --retro-dir {--download-retro-wfc-payload | --skip-retro-wfc-payload}" + : $"install --profile {profile}"; + Console.Error.WriteLine($"{profile} is not installed. Run '{installHint}' first."); + return 1; + } + var exePath = Path.Combine(record.InstallDirectory, record.ExecutableName); + if (!File.Exists(exePath)) + { + Console.Error.WriteLine($"Installed executable is missing: {exePath}. Run 'install --profile {profile}' again."); + return 1; + } + var startInfo = new System.Diagnostics.ProcessStartInfo(exePath) + { + WorkingDirectory = record.InstallDirectory, + UseShellExecute = false, + }; + using var process = System.Diagnostics.Process.Start(startInfo); + process?.WaitForExit(); + return process?.ExitCode ?? 1; + } + + private static void CheckProducts() + { + var state = JsonState.TryRead(StatePath); + if (state is null || state.Products.Count == 0) + { + Console.WriteLine("Nothing installed."); + return; + } + var assetsDir = Path.Combine(state.Workspace, "Assets"); + var currentDol = Sha256IfExists(Path.Combine(assetsDir, "main.dol")); + var currentRel = Sha256IfExists(Path.Combine(assetsDir, "StaticR.rel")); + foreach (var record in state.Products) + { + var exePath = Path.Combine(record.InstallDirectory, record.ExecutableName); + var present = File.Exists(exePath); + var stale = present && (currentDol != record.DolSha256 || currentRel != record.RelSha256); + var status = !present ? "MISSING" : stale ? "STALE (game assets changed since last build)" : "current"; + Console.WriteLine($"{record.Profile,-14} {status,-45} {record.InstallDirectory}"); + } + } + + private static string Sha256Of(string path) + { + using var stream = File.OpenRead(path); + return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant(); + } + + private static string? Sha256IfExists(string path) => File.Exists(path) ? Sha256Of(path) : null; + + private static string StatePath => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WiiCompiled", "install-state.json"); + + private static string DefaultInstallDir(string profile) => Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WiiCompiled", "Install", + profile == "base" ? "Base" : "RetroRewind"); + + /// + /// A single pass that finds both the command word and every --flag[=value] pair, regardless + /// of order - a --flag may appear before or after the command (see the AppRun caller note in + /// RunAsync). The first token that is neither a --flag nor a value already consumed by the + /// preceding --flag is taken as the command. + /// + private static (string? Command, Dictionary Flags) ParseArgs(string[] args) + { + string? command = null; + var flags = new Dictionary(); + for (var i = 0; i < args.Length; i++) + { + var arg = args[i]; + if (arg.StartsWith("--", StringComparison.Ordinal)) + { + var name = arg[2..]; + if (i + 1 < args.Length && !args[i + 1].StartsWith("--", StringComparison.Ordinal)) + { + flags[name] = args[++i]; + } + else + { + flags[name] = null; // boolean flag + } + } + else if (command is null) + { + command = arg; + } + } + return (command, flags); + } + + private static void PrintUsage() + { + Console.WriteLine(""" + Usage: wiicompiled-setup [options] + + install [--game ISO_PATH] [--install-dir DIR] [--retro-dir DIR + {--download-retro-wfc-payload | --skip-retro-wfc-payload}] + [--force-clean-build] [--translator-bin PATH] [--disc-tool-bin PATH] + [--progress-json] [--workspace DIR] + uninstall + launch-base + launch-retro + check-products + --version + """); + } +} diff --git a/Launcher/WiiCompiled.Setup.Linux/ProjectManifest.cs b/Launcher/WiiCompiled.Setup.Linux/ProjectManifest.cs new file mode 100644 index 0000000..43b9a21 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Linux/ProjectManifest.cs @@ -0,0 +1,70 @@ +using System.Text.RegularExpressions; + +namespace WiiCompiled.Setup.Linux; + +/// +/// The handful of facts this tool needs out of projects/mkwii/recomp.yml. Parsed literally line by +/// line - the same approach Launcher/NativeBuildFlags.ps1's Get-MkwProjectPins and +/// Launcher/local-build.sh already use - rather than pulling in a YAML library, since the manifest +/// is machine-written with a fixed shape. +/// +internal sealed class ProjectManifest +{ + public required string GameId { get; init; } + public required string Region { get; init; } + public required string DolSha256 { get; init; } + public required string RelSha256 { get; init; } + + public static ProjectManifest Load(string path) + { + if (!File.Exists(path)) throw new FileNotFoundException("Translation project file is missing", path); + + string? gameId = null, region = null, dolSha = null, relSha = null; + string section = ""; + string inputKey = ""; + + foreach (var raw in File.ReadLines(path)) + { + var line = Regex.Replace(raw, "#.*$", ""); + if (string.IsNullOrWhiteSpace(line)) continue; + + var sectionMatch = Regex.Match(line, "^([A-Za-z0-9_]+):"); + if (sectionMatch.Success) + { + section = sectionMatch.Groups[1].Value; + inputKey = ""; + continue; + } + + if (section == "inputs") + { + var keyMatch = Regex.Match(line, @"^\s{2}([A-Za-z0-9_]+):\s*$"); + if (keyMatch.Success) { inputKey = keyMatch.Groups[1].Value; continue; } + + var shaMatch = Regex.Match(line, @"^\s*sha256:\s*([0-9a-fA-F]{64})\s*$"); + if (shaMatch.Success) + { + var value = shaMatch.Groups[1].Value.ToLowerInvariant(); + if (inputKey == "dol") dolSha = value; + else if (inputKey == "rel") relSha = value; + } + } + else if (section == "project") + { + var idMatch = Regex.Match(line, @"^\s*game_id:\s*(\S+)\s*$"); + if (idMatch.Success) gameId = idMatch.Groups[1].Value; + + var regionMatch = Regex.Match(line, @"^\s*region:\s*(\S+)\s*$"); + if (regionMatch.Success) region = regionMatch.Groups[1].Value; + } + } + + if (gameId is null || region is null || dolSha is null || relSha is null) + { + throw new InvalidDataException( + $"{path} does not pin game_id/region/dol.sha256/rel.sha256; the project file is not the shape this tool expects."); + } + + return new ProjectManifest { GameId = gameId, Region = region, DolSha256 = dolSha, RelSha256 = relSha }; + } +} diff --git a/Launcher/WiiCompiled.Setup.Linux/WiiCompiled.Setup.Linux.csproj b/Launcher/WiiCompiled.Setup.Linux/WiiCompiled.Setup.Linux.csproj new file mode 100644 index 0000000..7424382 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Linux/WiiCompiled.Setup.Linux.csproj @@ -0,0 +1,20 @@ + + + Exe + net8.0 + enable + enable + WiiCompiled.Setup.Linux + WiiCompiled.Setup.Linux + 0.2.22 + patchzy + WiiCompiled + Command-line installer and launcher for WiiCompiled on Linux + embedded + en + true + + + + + diff --git a/Launcher/WiiCompiled.Setup.Linux/WorkspaceLocator.cs b/Launcher/WiiCompiled.Setup.Linux/WorkspaceLocator.cs new file mode 100644 index 0000000..99d9a93 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Linux/WorkspaceLocator.cs @@ -0,0 +1,27 @@ +namespace WiiCompiled.Setup.Linux; + +/// +/// Finds the repo checkout this tool is running from by walking up from its own directory looking +/// for Launcher/local-build.sh - this tool operates directly on a git checkout (no bundled/staged +/// workspace copy), so there is no installed "Toolkit" layout to anchor on the way the Windows +/// installer's Installation.cs does. +/// +internal static class WorkspaceLocator +{ + private const int MaxSearchDepth = 6; + + public static string FindFrom(string startDirectory) + { + var current = new DirectoryInfo(startDirectory); + for (var level = 0; level <= MaxSearchDepth && current is not null; level++, current = current.Parent) + { + if (File.Exists(Path.Combine(current.FullName, "Launcher", "local-build.sh"))) + { + return current.FullName; + } + } + throw new InvalidOperationException( + "Could not find the WiiCompiled repository (looked for Launcher/local-build.sh walking up " + + $"from {startDirectory}). Pass --workspace explicitly."); + } +} diff --git a/Launcher/WiiCompiled.Setup/CancellationSignal.cs b/Launcher/WiiCompiled.Setup.Windows/CancellationSignal.cs similarity index 98% rename from Launcher/WiiCompiled.Setup/CancellationSignal.cs rename to Launcher/WiiCompiled.Setup.Windows/CancellationSignal.cs index dd4848b..c915aea 100644 --- a/Launcher/WiiCompiled.Setup/CancellationSignal.cs +++ b/Launcher/WiiCompiled.Setup.Windows/CancellationSignal.cs @@ -1,4 +1,4 @@ -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; /// /// Bridges a frontend-owned, named Windows event into the cancellation token used by setup. diff --git a/Launcher/WiiCompiled.Setup/CommandLine.cs b/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/CommandLine.cs rename to Launcher/WiiCompiled.Setup.Windows/CommandLine.cs index ef573d6..14cd664 100644 --- a/Launcher/WiiCompiled.Setup/CommandLine.cs +++ b/Launcher/WiiCompiled.Setup.Windows/CommandLine.cs @@ -1,4 +1,4 @@ -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal enum AppMode { diff --git a/Launcher/WiiCompiled.Setup/CompileInputsFingerprint.cs b/Launcher/WiiCompiled.Setup.Windows/CompileInputsFingerprint.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/CompileInputsFingerprint.cs rename to Launcher/WiiCompiled.Setup.Windows/CompileInputsFingerprint.cs index 8b8dc0a..db69b6d 100644 --- a/Launcher/WiiCompiled.Setup/CompileInputsFingerprint.cs +++ b/Launcher/WiiCompiled.Setup.Windows/CompileInputsFingerprint.cs @@ -1,8 +1,9 @@ using System.Buffers.Binary; using System.Security.Cryptography; using System.Text; +using WiiCompiled.Setup.Common; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal sealed record RetroRewindCompileInputs( string RetroRewindRoot, diff --git a/Launcher/WiiCompiled.Setup/ConsoleCommands.cs b/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/ConsoleCommands.cs rename to Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs index 499b591..fbe9a16 100644 --- a/Launcher/WiiCompiled.Setup/ConsoleCommands.cs +++ b/Launcher/WiiCompiled.Setup.Windows/ConsoleCommands.cs @@ -1,6 +1,7 @@ using System.Text.Json; +using WiiCompiled.Setup.Common; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal static class ConsoleCommands { @@ -63,8 +64,8 @@ internal static class ConsoleCommands { using var payload = PayloadArchive.OpenCurrent(); var manifest = payload.ReadManifest(); - var tool = Path.Combine(temp, "DolphinTool.exe"); - payload.ExtractEntry(InstalledLayout.ToolkitEntryPrefix + "DolphinTool.exe", tool); + var tool = Path.Combine(temp, "nodtool.exe"); + payload.ExtractEntry(InstalledLayout.ToolkitEntryPrefix + "nodtool.exe", tool); payload.ExtractDirectory(InstalledLayout.ToolkitEntryPrefix + "Redist", temp); reporter?.Progress(InstallStages.Validate, "Checking the Wii disc image...", 10); var header = InputValidation.ReadDiscHeaderAsync(tool, command.GamePath!).GetAwaiter().GetResult(); diff --git a/Launcher/WiiCompiled.Setup/GameLaunchService.cs b/Launcher/WiiCompiled.Setup.Windows/GameLaunchService.cs similarity index 98% rename from Launcher/WiiCompiled.Setup/GameLaunchService.cs rename to Launcher/WiiCompiled.Setup.Windows/GameLaunchService.cs index 58d10d0..6bdf7d2 100644 --- a/Launcher/WiiCompiled.Setup/GameLaunchService.cs +++ b/Launcher/WiiCompiled.Setup.Windows/GameLaunchService.cs @@ -1,6 +1,6 @@ using System.Diagnostics; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal static class GameLaunchService { diff --git a/Launcher/WiiCompiled.Setup.Windows/InputValidation.cs b/Launcher/WiiCompiled.Setup.Windows/InputValidation.cs new file mode 100644 index 0000000..5ca7558 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Windows/InputValidation.cs @@ -0,0 +1,165 @@ +using System.Diagnostics; +using System.Buffers.Binary; +using System.Net; +using System.Security.Cryptography; +using System.Text.Json; +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Windows; + +internal static class InputValidation +{ + private static readonly HashSet SupportedDiscImageExtensions = new( + [".iso", ".gcm", ".gcz", ".ciso", ".wbfs", ".wia", ".rvz"], + StringComparer.OrdinalIgnoreCase); + + public static void ValidateExtension(string gamePath) + { + if (!File.Exists(gamePath)) + throw new FileNotFoundException("The selected game image does not exist.", gamePath); + var extension = Path.GetExtension(gamePath); + if (!SupportedDiscImageExtensions.Contains(extension)) + throw new InvalidDataException( + "Select a complete Wii disc image in ISO, GCM, GCZ, CISO, WBFS, WIA, or RVZ format."); + } + + public static async Task ReadDiscHeaderAsync(string nodTool, string gamePath, + CancellationToken cancellationToken = default) + { + ValidateExtension(gamePath); + var result = await ProcessRunner.RunAsync(nodTool, + ["info", Path.GetFullPath(gamePath)], null, cancellationToken); + if (result.ExitCode != 0) + throw new InvalidDataException("nodtool could not read this disc image. " + result.CombinedOutput.Trim()); + + var info = NodToolInfoParser.Parse(result.StandardOutput); + return new DiscHeader + { + GameId = info.GameId, + InternalName = info.Title, + Region = RegionFromGameId(info.GameId), + Revision = info.Revision, + }; + } + + private static string RegionFromGameId(string gameId) => gameId.Length >= 4 + ? gameId[3] switch + { + 'P' => "PAL", + 'E' => "NTSC-U", + 'J' => "NTSC-J", + 'K' => "Korea", + 'W' => "Taiwan", + _ => gameId[3].ToString(), + } + : "Unknown"; + + public static void EnsureCompatibleDisc(DiscHeader header, PayloadManifest manifest) + { + if (!header.GameId.Equals(manifest.ExpectedGameId, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidDataException( + $"This build supports Mario Kart Wii PAL ({manifest.ExpectedGameId}). " + + $"The selected image is {header.GameId} ({header.InternalName}, {header.Region})."); + } + } + + // Thin forwarding wrappers: the actual download/RSA-verification logic lives in + // WiiCompiled.Setup.Common.RetroWfcPayload (shared with WiiCompiled.Setup.Linux) so there's one + // copy of it, not two. Kept under these names so every existing call site here + // (ProductRepairService.cs, LocalBuildService.cs, Installation.cs, SelfTests.cs) is unchanged. + public const string CurrentRetroWfcPayloadUri = RetroWfcPayload.CurrentRetroWfcPayloadUri; + + public static string ValidateStagedRetroWfcPayloadDirectory(string stagedDirectory, + RSAParameters? signingKey = null) => + RetroWfcPayload.ValidateStagedRetroWfcPayloadDirectory(stagedDirectory, signingKey); + + public static string ResolveRetroWfcPayloadFile(string stagedDirectory, + RSAParameters? signingKey = null) => + RetroWfcPayload.ResolveRetroWfcPayloadFile(stagedDirectory, signingKey); + + public static string ComputeRetroWfcPayloadSha256(string stagedDirectory, + RSAParameters? signingKey = null) => + RetroWfcPayload.ComputeRetroWfcPayloadSha256(stagedDirectory, signingKey); + + public static void ValidateRetroWfcPayloadUri(string uriText) => + RetroWfcPayload.ValidateRetroWfcPayloadUri(uriText); + + public static Task DownloadRetroWfcPayloadAsync(string uriText, + string destinationDirectory, CancellationToken cancellationToken) => + RetroWfcPayload.DownloadRetroWfcPayloadAsync(uriText, destinationDirectory, cancellationToken); + + internal static bool IsTransientRetroWfcDownloadFailure(Exception exception, + CancellationToken cancellationToken) => + RetroWfcPayload.IsTransientRetroWfcDownloadFailure(exception, cancellationToken); + + public static string Sha256File(string path) + { + using var stream = File.OpenRead(path); + return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant(); + } +} + +internal sealed record ProcessResult(int ExitCode, string StandardOutput, string StandardError) +{ + public string CombinedOutput => StandardOutput + Environment.NewLine + StandardError; +} + +internal static class ProcessRunner +{ + /// Runs a redirected child process to completion. sets up a + /// working directory or scrubbed environment; is off for callers that only + /// forward output live, so a build's output isn't buffered in memory for nobody to read. + public static async Task RunAsync(string executable, IReadOnlyList arguments, + Action? output, CancellationToken cancellationToken, + Action? configure = null, bool capture = true, + Action? onTerminationFailure = null) + { + var info = new ProcessStartInfo + { + FileName = executable, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + foreach (var argument in arguments) info.ArgumentList.Add(argument); + configure?.Invoke(info); + + using var process = new Process { StartInfo = info, EnableRaisingEvents = true }; + var stdout = new List(); + var stderr = new List(); + process.OutputDataReceived += (_, e) => { if (e.Data is not null) { if (capture) stdout.Add(e.Data); output?.Invoke(e.Data); } }; + process.ErrorDataReceived += (_, e) => { if (e.Data is not null) { if (capture) stderr.Add(e.Data); output?.Invoke(e.Data); } }; + if (!process.Start()) throw new InvalidOperationException($"Could not start {executable}."); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + await WaitForExitAsync(process, cancellationToken, onTerminationFailure); + return new ProcessResult(process.ExitCode, string.Join(Environment.NewLine, stdout), + string.Join(Environment.NewLine, stderr)); + } + + public static async Task WaitForExitAsync(Process process, CancellationToken cancellationToken, + Action? onTerminationFailure = null) + { + try + { + await process.WaitForExitAsync(cancellationToken); + } + catch (OperationCanceledException) + { + try + { + if (!process.HasExited) process.Kill(entireProcessTree: true); + } + catch (Exception ex) + { + onTerminationFailure?.Invoke(ex); + } + await process.WaitForExitAsync(CancellationToken.None); + process.WaitForExit(); + throw; + } + process.WaitForExit(); + } +} diff --git a/Launcher/WiiCompiled.Setup/InstallOperationLock.cs b/Launcher/WiiCompiled.Setup.Windows/InstallOperationLock.cs similarity index 97% rename from Launcher/WiiCompiled.Setup/InstallOperationLock.cs rename to Launcher/WiiCompiled.Setup.Windows/InstallOperationLock.cs index 44f1c78..035a442 100644 --- a/Launcher/WiiCompiled.Setup/InstallOperationLock.cs +++ b/Launcher/WiiCompiled.Setup.Windows/InstallOperationLock.cs @@ -1,7 +1,8 @@ using System.Security.Cryptography; using System.Text; +using WiiCompiled.Setup.Common; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; /// /// A fail-fast, cross-process lock covering install, repair and launch operations for one install diff --git a/Launcher/WiiCompiled.Setup/InstallProgress.cs b/Launcher/WiiCompiled.Setup.Windows/InstallProgress.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/InstallProgress.cs rename to Launcher/WiiCompiled.Setup.Windows/InstallProgress.cs index ac1e1fb..2bacbce 100644 --- a/Launcher/WiiCompiled.Setup/InstallProgress.cs +++ b/Launcher/WiiCompiled.Setup.Windows/InstallProgress.cs @@ -1,6 +1,6 @@ using System.Text.Json; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; /// /// Stable stage identifiers reported by --progress-json. These are part of the public diff --git a/Launcher/WiiCompiled.Setup/InstallScratchSpace.cs b/Launcher/WiiCompiled.Setup.Windows/InstallScratchSpace.cs similarity index 98% rename from Launcher/WiiCompiled.Setup/InstallScratchSpace.cs rename to Launcher/WiiCompiled.Setup.Windows/InstallScratchSpace.cs index 49aa053..4169fbd 100644 --- a/Launcher/WiiCompiled.Setup/InstallScratchSpace.cs +++ b/Launcher/WiiCompiled.Setup.Windows/InstallScratchSpace.cs @@ -1,4 +1,6 @@ -namespace WiiCompiled.Setup; +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Windows; /// /// Owns one temporary directory for an install operation. The name carries the installation's scope diff --git a/Launcher/WiiCompiled.Setup/InstallTransaction.cs b/Launcher/WiiCompiled.Setup.Windows/InstallTransaction.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/InstallTransaction.cs rename to Launcher/WiiCompiled.Setup.Windows/InstallTransaction.cs index 4ee7d4b..8efb95e 100644 --- a/Launcher/WiiCompiled.Setup/InstallTransaction.cs +++ b/Launcher/WiiCompiled.Setup.Windows/InstallTransaction.cs @@ -1,4 +1,6 @@ -namespace WiiCompiled.Setup; +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Windows; internal enum InstallTransactionEntryKind { diff --git a/Launcher/WiiCompiled.Setup/Installation.cs b/Launcher/WiiCompiled.Setup.Windows/Installation.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/Installation.cs rename to Launcher/WiiCompiled.Setup.Windows/Installation.cs index 0b621ff..de600c9 100644 --- a/Launcher/WiiCompiled.Setup/Installation.cs +++ b/Launcher/WiiCompiled.Setup.Windows/Installation.cs @@ -1,4 +1,6 @@ -namespace WiiCompiled.Setup; +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Windows; /// Provenance written by the bundled build script next to every product it produces. internal sealed class LocalBuildProvenance diff --git a/Launcher/WiiCompiled.Setup/InstalledLayout.cs b/Launcher/WiiCompiled.Setup.Windows/InstalledLayout.cs similarity index 98% rename from Launcher/WiiCompiled.Setup/InstalledLayout.cs rename to Launcher/WiiCompiled.Setup.Windows/InstalledLayout.cs index 73d8efb..1372e03 100644 --- a/Launcher/WiiCompiled.Setup/InstalledLayout.cs +++ b/Launcher/WiiCompiled.Setup.Windows/InstalledLayout.cs @@ -1,4 +1,4 @@ -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; /// /// Names of the installed/staged layout. Not cosmetic: payload and toolkit identities hash relative paths diff --git a/Launcher/WiiCompiled.Setup/InstallerEngine.cs b/Launcher/WiiCompiled.Setup.Windows/InstallerEngine.cs similarity index 95% rename from Launcher/WiiCompiled.Setup/InstallerEngine.cs rename to Launcher/WiiCompiled.Setup.Windows/InstallerEngine.cs index 3381eea..fce86f6 100644 --- a/Launcher/WiiCompiled.Setup/InstallerEngine.cs +++ b/Launcher/WiiCompiled.Setup.Windows/InstallerEngine.cs @@ -1,4 +1,6 @@ -namespace WiiCompiled.Setup; +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Windows; internal sealed class InstallerEngine { @@ -57,9 +59,9 @@ internal sealed class InstallerEngine var runtimeAssetsCurrent = sameToolkit && RuntimeAssetsAreCurrent(existing, candidateRuntimeAssetsFingerprint, cancellationToken); - var installedDolphinTool = Path.Combine(existing.ToolkitDirectory, "DolphinTool.exe"); + var installedNodTool = Path.Combine(existing.ToolkitDirectory, "nodtool.exe"); var extractToolkit = MustRefreshToolkit(sameToolkit, samePackageContent, - File.Exists(installedDolphinTool)); + File.Exists(installedNodTool)); var extractWorkspace = !sameToolkit || !runtimeAssetsCurrent; _reporter.Progress(InstallStages.ExtractToolkit, @@ -77,9 +79,9 @@ internal sealed class InstallerEngine payload.ExtractEntry(InstalledLayout.PayloadManifestFileName, Path.Combine(staging, InstalledLayout.PayloadManifestFileName)); - var dolphinTool = extractToolkit ? Path.Combine(toolkit, "DolphinTool.exe") : installedDolphinTool; + var nodTool = extractToolkit ? Path.Combine(toolkit, "nodtool.exe") : installedNodTool; _reporter.Progress(InstallStages.Validate, "Checking the Wii disc image...", 2); - var header = await InputValidation.ReadDiscHeaderAsync(dolphinTool, options.GamePath, + var header = await InputValidation.ReadDiscHeaderAsync(nodTool, options.GamePath, cancellationToken); InputValidation.EnsureCompatibleDisc(header, manifest); var canonicalRetroRoot = options.RetroDirectoryPath is null @@ -153,7 +155,7 @@ internal sealed class InstallerEngine if (reusableGameAssets is null) { - await ExtractGameAssetsAsync(dolphinTool, options.GamePath, + await ExtractGameAssetsAsync(nodTool, options.GamePath, Path.Combine(staging, "GameAssets"), manifest, cancellationToken); } @@ -164,8 +166,8 @@ internal sealed class InstallerEngine internal static bool MustRefreshToolkit(bool sameToolkit, bool samePackageContent, - bool dolphinToolPresent) => - !sameToolkit || !samePackageContent || !dolphinToolPresent; + bool nodToolPresent) => + !sameToolkit || !samePackageContent || !nodToolPresent; private static void AddComponent(List entries, string staging, string installDirectory, string name) => @@ -469,18 +471,23 @@ internal sealed class InstallerEngine } } - private async Task ExtractGameAssetsAsync(string dolphinTool, string gamePath, string destination, + private async Task ExtractGameAssetsAsync(string nodTool, string gamePath, string destination, PayloadManifest manifest, CancellationToken cancellationToken) { _reporter.Progress(InstallStages.ExtractDisc, "Extracting the game disc. This is the longest preparation step...", 6); - var extraction = await ProcessRunner.RunAsync(dolphinTool, - ["extract", "-i", Path.GetFullPath(gamePath), "-o", destination, "-g", "-q"], + // Extracted straight into a "DATA" subfolder so the on-disk layout matches what + // Installation.GameDataDirectory and every other reader of it already expect - nodtool + // itself has no such wrapper (it extracts sys/+files/ directly to whatever is + // given), so this is purely destination-side, not a nodtool convention. + var dataRoot = Path.Combine(destination, "DATA"); + var extraction = await ProcessRunner.RunAsync(nodTool, + ["extract", Path.GetFullPath(gamePath), dataRoot, "-q"], line => { if (!string.IsNullOrWhiteSpace(line)) _reporter.Diagnostic(line); }, cancellationToken); if (extraction.ExitCode != 0) throw new InvalidDataException("Game extraction failed. " + extraction.CombinedOutput.Trim()); - ValidateExtractedGame(Path.Combine(destination, "DATA"), manifest); + ValidateExtractedGame(dataRoot, manifest); } private static void ValidateExtractedGame(string dataRoot, PayloadManifest manifest) diff --git a/Launcher/WiiCompiled.Setup/LocalBuildService.cs b/Launcher/WiiCompiled.Setup.Windows/LocalBuildService.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/LocalBuildService.cs rename to Launcher/WiiCompiled.Setup.Windows/LocalBuildService.cs index 439c594..1347c1e 100644 --- a/Launcher/WiiCompiled.Setup/LocalBuildService.cs +++ b/Launcher/WiiCompiled.Setup.Windows/LocalBuildService.cs @@ -1,6 +1,7 @@ using System.Diagnostics; +using WiiCompiled.Setup.Common; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; /// /// runs one retro-aware translation and compiles the two products from a single diff --git a/Launcher/WiiCompiled.Setup/Models.cs b/Launcher/WiiCompiled.Setup.Windows/Models.cs similarity index 95% rename from Launcher/WiiCompiled.Setup/Models.cs rename to Launcher/WiiCompiled.Setup.Windows/Models.cs index 83eb2cc..e62b261 100644 --- a/Launcher/WiiCompiled.Setup/Models.cs +++ b/Launcher/WiiCompiled.Setup.Windows/Models.cs @@ -1,6 +1,7 @@ using System.Text.Json.Serialization; +using WiiCompiled.Setup.Common; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal enum RetroWfcPayloadMode { @@ -54,12 +55,6 @@ internal sealed class PayloadManifest public string NativeToolchainFingerprint { get; set; } = ""; } -/// -/// One validated, content-identified download in operation-owned scratch space. Callers use this -/// exact directory for both the update decision and any resulting build. -/// -internal sealed record RetroWfcPayloadSnapshot(string Directory, string Sha256, long ByteLength); - internal sealed class DiscHeader { [JsonPropertyName("game_id")] diff --git a/Launcher/WiiCompiled.Setup/PayloadArchive.cs b/Launcher/WiiCompiled.Setup.Windows/PayloadArchive.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/PayloadArchive.cs rename to Launcher/WiiCompiled.Setup.Windows/PayloadArchive.cs index 3fb9055..d85f1fa 100644 --- a/Launcher/WiiCompiled.Setup/PayloadArchive.cs +++ b/Launcher/WiiCompiled.Setup.Windows/PayloadArchive.cs @@ -2,7 +2,7 @@ using System.IO.Compression; using System.Text; using System.Text.Json; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal sealed class PayloadArchive : IDisposable { diff --git a/Launcher/WiiCompiled.Setup.Windows/PortableInstallHealing.cs b/Launcher/WiiCompiled.Setup.Windows/PortableInstallHealing.cs new file mode 100644 index 0000000..786f369 --- /dev/null +++ b/Launcher/WiiCompiled.Setup.Windows/PortableInstallHealing.cs @@ -0,0 +1,54 @@ +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Windows; + +/// +/// A portable root can be moved or renamed between operations. Every installed-host operation that +/// reads install-state.json passes through here first so exactly one place decides what a +/// moved installation means, and so a non-portable installation is never touched. +/// +internal static class PortableInstallHealing +{ + /// + /// Reconciles a moved portable installation with its recorded location: the state file adopts the + /// directory it was actually found in, and the native build tree is discarded because its + /// CMake cache holds absolute paths from the old location. Returns whether anything was healed. + /// + public static bool HealMovedInstall(Installation installation, IInstallReporter? reporter = null) + { + // Guard: an ordinary installation that disagrees with its state file is a real problem for + // the operation to report, not something to silently rewrite. + if (PortableRoot.TryFind(installation.Root) is null) return false; + + var state = installation.ReadInstallState(); + if (state is not { SchemaVersion: 1 } || string.IsNullOrWhiteSpace(state.InstallDir)) return false; + + string recorded; + try + { + recorded = FileSystemUtilities.NormalizePath(state.InstallDir); + } + catch (Exception ex) when (ex is ArgumentException or NotSupportedException or PathTooLongException) + { + recorded = state.InstallDir; + } + if (recorded.Equals(installation.Root, StringComparison.OrdinalIgnoreCase)) return false; + + var previous = state.InstallDir; + state.InstallDir = installation.Root; + JsonState.Write(installation.InstallStatePath, state); + + // The configured native build directory bakes absolute source, toolchain, and output paths + // into CMakeCache.txt. After a move it is unusable and would fail the next configure rather + // than being reused, so it is removed and reconfigured from scratch on the next build. + var nativeBuild = Path.Combine(installation.WorkspaceDirectory, "native-build"); + var hadNativeBuild = Directory.Exists(nativeBuild); + if (hadNativeBuild) FileSystemUtilities.DeleteDirectoryIfExists(nativeBuild); + + reporter?.Diagnostic( + $"This portable installation moved from {previous} to {installation.Root}. " + + "The recorded location was updated" + + (hadNativeBuild ? " and the location-bound native build cache was discarded." : ".")); + return true; + } +} diff --git a/Launcher/WiiCompiled.Setup/ProductRepairService.cs b/Launcher/WiiCompiled.Setup.Windows/ProductRepairService.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/ProductRepairService.cs rename to Launcher/WiiCompiled.Setup.Windows/ProductRepairService.cs index f1a75ca..6ff4d94 100644 --- a/Launcher/WiiCompiled.Setup/ProductRepairService.cs +++ b/Launcher/WiiCompiled.Setup.Windows/ProductRepairService.cs @@ -1,4 +1,6 @@ -namespace WiiCompiled.Setup; +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Windows; /// /// Reconciles installed products against the canonical Retro Rewind install Wheel Wizard owns: the diff --git a/Launcher/WiiCompiled.Setup/Program.cs b/Launcher/WiiCompiled.Setup.Windows/Program.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/Program.cs rename to Launcher/WiiCompiled.Setup.Windows/Program.cs index 7858340..ac062a4 100644 --- a/Launcher/WiiCompiled.Setup/Program.cs +++ b/Launcher/WiiCompiled.Setup.Windows/Program.cs @@ -1,4 +1,4 @@ -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; using System.Runtime.InteropServices; diff --git a/Launcher/WiiCompiled.Setup/RunningProductGuard.cs b/Launcher/WiiCompiled.Setup.Windows/RunningProductGuard.cs similarity index 96% rename from Launcher/WiiCompiled.Setup/RunningProductGuard.cs rename to Launcher/WiiCompiled.Setup.Windows/RunningProductGuard.cs index 00b902d..cdfd77b 100644 --- a/Launcher/WiiCompiled.Setup/RunningProductGuard.cs +++ b/Launcher/WiiCompiled.Setup.Windows/RunningProductGuard.cs @@ -1,6 +1,7 @@ using System.Diagnostics; +using WiiCompiled.Setup.Common; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; /// /// Refuses to replace installed products while one of them is running: publishing renames the diff --git a/Launcher/WiiCompiled.Setup/RuntimeAssetPublication.cs b/Launcher/WiiCompiled.Setup.Windows/RuntimeAssetPublication.cs similarity index 97% rename from Launcher/WiiCompiled.Setup/RuntimeAssetPublication.cs rename to Launcher/WiiCompiled.Setup.Windows/RuntimeAssetPublication.cs index beb4186..9a9ad03 100644 --- a/Launcher/WiiCompiled.Setup/RuntimeAssetPublication.cs +++ b/Launcher/WiiCompiled.Setup.Windows/RuntimeAssetPublication.cs @@ -1,4 +1,6 @@ -namespace WiiCompiled.Setup; +using WiiCompiled.Setup.Common; + +namespace WiiCompiled.Setup.Windows; /// /// The one path by which a product receives its copied runtime assets, shared by install and repair. diff --git a/Launcher/WiiCompiled.Setup/SelfTests.cs b/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/SelfTests.cs rename to Launcher/WiiCompiled.Setup.Windows/SelfTests.cs index 3a807f3..21956a7 100644 --- a/Launcher/WiiCompiled.Setup/SelfTests.cs +++ b/Launcher/WiiCompiled.Setup.Windows/SelfTests.cs @@ -1,7 +1,8 @@ using System.Buffers.Binary; using System.Security.Cryptography; +using WiiCompiled.Setup.Common; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal static class SelfTests { @@ -143,17 +144,17 @@ internal static class SelfTests private static void TestToolkitRefreshDecision() { if (!InstallerEngine.MustRefreshToolkit(sameToolkit: false, samePackageContent: true, - dolphinToolPresent: true)) + nodToolPresent: true)) throw new Exception("A republished workspace kept the installed toolkit; the shipped " + "translator and project file could come from different releases."); if (!InstallerEngine.MustRefreshToolkit(sameToolkit: true, samePackageContent: false, - dolphinToolPresent: true)) + nodToolPresent: true)) throw new Exception("Changed toolkit package content was not extracted."); if (!InstallerEngine.MustRefreshToolkit(sameToolkit: true, samePackageContent: true, - dolphinToolPresent: false)) - throw new Exception("A missing DolphinTool.exe did not force toolkit extraction."); + nodToolPresent: false)) + throw new Exception("A missing nodtool.exe did not force toolkit extraction."); if (InstallerEngine.MustRefreshToolkit(sameToolkit: true, samePackageContent: true, - dolphinToolPresent: true)) + nodToolPresent: true)) throw new Exception("An unchanged toolkit was needlessly re-extracted."); } @@ -1010,7 +1011,7 @@ internal static class SelfTests throw new Exception("The toolkit fingerprint is not stable."); // A file that has nothing to do with generated code must not invalidate every install. - File.WriteAllText(Path.Combine(root, "Toolkit", "DolphinTool.exe"), "irrelevant"); + File.WriteAllText(Path.Combine(root, "Toolkit", "nodtool.exe"), "irrelevant"); if (ToolkitFingerprint.Compute(root) != first) throw new Exception("An unrelated toolkit file changed the fingerprint."); @@ -1177,7 +1178,7 @@ internal static class SelfTests "x86_64-w64-mingw32-clang++.exe", "x86_64-w64-mingw32-windres.exe" }) File.WriteAllText(Path.Combine(root, "Toolkit", "llvm-mingw", "bin", executable), executable); - File.WriteAllText(Path.Combine(root, "Toolkit", "DolphinTool.exe"), "tool"); + File.WriteAllText(Path.Combine(root, "Toolkit", "nodtool.exe"), "tool"); File.WriteAllText(Path.Combine(root, "BuildWorkspace", "LocalBuild.ps1"), "# build"); File.WriteAllText(Path.Combine(root, "BuildWorkspace", "NativeBuildFlags.ps1"), "# flags"); File.WriteAllText(Path.Combine(root, "BuildWorkspace", "projects", "mkwii", "recomp.yml"), "profiles: {}"); diff --git a/Launcher/WiiCompiled.Setup/ShellIntegration.cs b/Launcher/WiiCompiled.Setup.Windows/ShellIntegration.cs similarity index 99% rename from Launcher/WiiCompiled.Setup/ShellIntegration.cs rename to Launcher/WiiCompiled.Setup.Windows/ShellIntegration.cs index 1746848..a17e1f4 100644 --- a/Launcher/WiiCompiled.Setup/ShellIntegration.cs +++ b/Launcher/WiiCompiled.Setup.Windows/ShellIntegration.cs @@ -1,6 +1,6 @@ using Microsoft.Win32; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal static class ShellIntegration { diff --git a/Launcher/WiiCompiled.Setup/ToolkitFingerprint.cs b/Launcher/WiiCompiled.Setup.Windows/ToolkitFingerprint.cs similarity index 98% rename from Launcher/WiiCompiled.Setup/ToolkitFingerprint.cs rename to Launcher/WiiCompiled.Setup.Windows/ToolkitFingerprint.cs index ded4feb..ed1c952 100644 --- a/Launcher/WiiCompiled.Setup/ToolkitFingerprint.cs +++ b/Launcher/WiiCompiled.Setup.Windows/ToolkitFingerprint.cs @@ -1,7 +1,8 @@ using System.Security.Cryptography; using System.Text; +using WiiCompiled.Setup.Common; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; /// /// Content identity of everything that decides what the locally produced executables contain. @@ -78,12 +79,12 @@ internal static class ToolkitFingerprint var workspace = InstalledLayout.Workspace(root); var entries = new SortedDictionary(StringComparer.Ordinal); - // DolphinTool validates/extracts the user disc but does not influence generated products. + // nodtool validates/extracts the user disc but does not influence generated products. // Everything else in Toolkit can affect translation, compilation, linking, or copied // runtime support and therefore belongs to the compile identity. AddDirectory(entries, root, toolkit, null, cancellationToken, - file => !Path.GetFileName(file).Equals("DolphinTool.exe", StringComparison.OrdinalIgnoreCase)); + file => !Path.GetFileName(file).Equals("nodtool.exe", StringComparison.OrdinalIgnoreCase)); AddFile(entries, root, Path.Combine(workspace, "LocalBuild.ps1"), cancellationToken); AddFile(entries, root, Path.Combine(workspace, "NativeBuildFlags.ps1"), cancellationToken); AddDirectory(entries, root, Path.Combine(workspace, "projects"), null, cancellationToken); diff --git a/Launcher/WiiCompiled.Setup/UninstallService.cs b/Launcher/WiiCompiled.Setup.Windows/UninstallService.cs similarity index 98% rename from Launcher/WiiCompiled.Setup/UninstallService.cs rename to Launcher/WiiCompiled.Setup.Windows/UninstallService.cs index 25ac2a4..490f174 100644 --- a/Launcher/WiiCompiled.Setup/UninstallService.cs +++ b/Launcher/WiiCompiled.Setup.Windows/UninstallService.cs @@ -1,7 +1,8 @@ using System.Diagnostics; using System.Runtime.InteropServices; +using WiiCompiled.Setup.Common; -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal static class UninstallService { diff --git a/Launcher/WiiCompiled.Setup/WiiCompiled.Setup.csproj b/Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj similarity index 77% rename from Launcher/WiiCompiled.Setup/WiiCompiled.Setup.csproj rename to Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj index 729883b..5dbb356 100644 --- a/Launcher/WiiCompiled.Setup/WiiCompiled.Setup.csproj +++ b/Launcher/WiiCompiled.Setup.Windows/WiiCompiled.Setup.Windows.csproj @@ -5,7 +5,7 @@ enable enable WiiCompiled.Setup - WiiCompiled.Setup + WiiCompiled.Setup.Windows app.manifest 0.2.24 patchzy @@ -14,4 +14,7 @@ embedded en + + + diff --git a/Launcher/WiiCompiled.Setup/WorkspaceTimestamps.cs b/Launcher/WiiCompiled.Setup.Windows/WorkspaceTimestamps.cs similarity index 98% rename from Launcher/WiiCompiled.Setup/WorkspaceTimestamps.cs rename to Launcher/WiiCompiled.Setup.Windows/WorkspaceTimestamps.cs index ae99f12..bbcabdd 100644 --- a/Launcher/WiiCompiled.Setup/WorkspaceTimestamps.cs +++ b/Launcher/WiiCompiled.Setup.Windows/WorkspaceTimestamps.cs @@ -1,4 +1,4 @@ -namespace WiiCompiled.Setup; +namespace WiiCompiled.Setup.Windows; internal static class WorkspaceTimestamps diff --git a/Launcher/WiiCompiled.Setup/app.manifest b/Launcher/WiiCompiled.Setup.Windows/app.manifest similarity index 100% rename from Launcher/WiiCompiled.Setup/app.manifest rename to Launcher/WiiCompiled.Setup.Windows/app.manifest diff --git a/Launcher/build-appimage.sh b/Launcher/build-appimage.sh new file mode 100644 index 0000000..fe56749 --- /dev/null +++ b/Launcher/build-appimage.sh @@ -0,0 +1,173 @@ +#!/usr/bin/env bash +# Packages Launcher/WiiCompiled.Setup.Linux as a self-contained AppImage: a single file Wheel +# Wizard (or anyone else) can fetch and execute with no git clone, no `dotnet` install, and no +# `dolphin-tool` package required at all. The installer and translator are published as +# self-contained binaries, and `nodtool` (a prebuilt MIT/Apache-2.0 CLI from encounter/nod, see +# NodToolProvider.cs) is downloaded and bundled too - AppRun passes --translator-bin and +# --disc-tool-bin so local-build.sh/DiscTool.cs skip their from-source/download fallbacks entirely. +# It still shells out to system clang/cmake/ninja - no C/C++ toolchain is bundled, matching +# Launcher/local-build.sh's own remaining prerequisites. +# +# An AppImage mounts read-only, but local-build.sh writes generated/, native-build/, Assets/, etc. +# into the workspace it's given. So AppRun (written below) copies the bundled workspace snapshot +# out to a writable cache directory on first run, and only ever re-syncs the bundled directories +# (runtime/, aurora-main/, projects/, local-build.sh) on a later run whose bundled version changed +# - generated/native-build/Assets/PulsarPacks live only in that writable cache and are never +# touched by the sync, so local-build.sh's own incremental caching survives across runs and across +# AppImage updates. translator/ isn't part of this snapshot at all: it's published as its own +# self-contained binary (usr/bin/translator-cli) below and never needs a writable copy. +set -euo pipefail + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +workspace=$(cd "$script_dir/.." && pwd) + +output_dir="$workspace/Launcher/dist" +appimagetool_override="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --output-dir) output_dir=$2; shift 2 ;; + --appimagetool) appimagetool_override=$2; shift 2 ;; + -h|--help) + echo "Usage: build-appimage.sh [--output-dir DIR] [--appimagetool PATH]" + exit 0 + ;; + *) echo "build-appimage.sh: unknown argument: $1" >&2; exit 1 ;; + esac +done + +appdir="$workspace/Launcher/artifacts/appimage-build/AppDir" +rm -rf "$appdir" +mkdir -p "$appdir/usr/bin" "$appdir/workspace/Launcher" + +echo "Publishing the installer (self-contained linux-x64)..." +publish_tmp="$workspace/Launcher/artifacts/appimage-build/publish" +rm -rf "$publish_tmp" +dotnet publish "$workspace/Launcher/WiiCompiled.Setup.Linux" -c Release -r linux-x64 \ + --self-contained -p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true \ + -o "$publish_tmp" +cp "$publish_tmp/WiiCompiled.Setup.Linux" "$appdir/usr/bin/wiicompiled-setup" +chmod +x "$appdir/usr/bin/wiicompiled-setup" + +# Published as a self-contained binary too, so an AppImage user never needs a `dotnet` SDK on +# PATH at all - local-build.sh is told about it via --translator-bin and skips its own +# dotnet-build-from-source step entirely (see local-build.sh's translator resolution branch). +echo "Publishing the translator (self-contained linux-x64)..." +translator_publish_tmp="$workspace/Launcher/artifacts/appimage-build/publish-translator" +rm -rf "$translator_publish_tmp" +dotnet publish "$workspace/translator/src/Translator.Cli" -c Release -r linux-x64 \ + --self-contained -p:PublishSingleFile=true -p:EnableCompressionInSingleFile=true \ + -o "$translator_publish_tmp" +cp "$translator_publish_tmp/Translator.Cli" "$appdir/usr/bin/translator-cli" +chmod +x "$appdir/usr/bin/translator-cli" + +# Resolved via the shared WiiCompiled.Setup.Common.Cli helper (also used by Build-Installer.ps1 on +# Windows) rather than a second curl/version-pin copy here: it downloads and caches the same way +# NodToolProvider.cs always does (Launcher/artifacts/nodtool), so there is exactly one place that +# knows the nodtool version/URL/platform-asset mapping. +echo "Resolving nodtool..." +nodtool_path=$(dotnet run --project "$workspace/Launcher/WiiCompiled.Setup.Common.Cli" -c Release -- \ + --workspace "$workspace" | tail -n1) +cp "$nodtool_path" "$appdir/usr/bin/nodtool" +chmod +x "$appdir/usr/bin/nodtool" + +echo "Staging the bundled workspace snapshot..." +for dir in runtime aurora-main projects; do + cp -r "$workspace/$dir" "$appdir/workspace/$dir" +done +# Mirrors Build-Installer.ps1's own staging exclusions exactly: aurora-main/extern/CMakeLists.txt +# is the real FetchContent driver and must ship, but any already-fetched dependency *subdirectory* +# a developer's local checkout accumulated under extern/ is stale/large build output, not a +# release input - only directories inside extern/ are stripped, never the file itself. runtime/build +# is a plain developer build directory. +find "$appdir/workspace/aurora-main/extern" -mindepth 1 -maxdepth 1 -type d -exec rm -rf {} + +rm -rf "$appdir/workspace/runtime/build" +cp "$workspace/Launcher/local-build.sh" "$appdir/workspace/Launcher/local-build.sh" + +if git -C "$workspace" rev-parse HEAD >/dev/null 2>&1; then + git -C "$workspace" rev-parse HEAD > "$appdir/workspace/.bundle-version" +else + date -u +%s > "$appdir/workspace/.bundle-version" +fi + +echo "Writing AppRun..." +cat > "$appdir/AppRun" <<'APPRUN' +#!/bin/bash +set -euo pipefail +HERE="$(dirname "$(readlink -f "$0")")" +CACHE="${XDG_DATA_HOME:-$HOME/.local/share}/WiiCompiled/workspace" +if [ ! -f "$CACHE/.bundle-version" ] || \ + [ "$(cat "$HERE/workspace/.bundle-version")" != "$(cat "$CACHE/.bundle-version")" ]; then + mkdir -p "$CACHE/Launcher" + for dir in runtime aurora-main projects; do + rm -rf "$CACHE/$dir" + cp -r "$HERE/workspace/$dir" "$CACHE/$dir" + done + cp "$HERE/workspace/Launcher/local-build.sh" "$CACHE/Launcher/local-build.sh" + cp "$HERE/workspace/.bundle-version" "$CACHE/.bundle-version" +fi +exec "$HERE/usr/bin/wiicompiled-setup" --workspace "$CACHE" \ + --translator-bin "$HERE/usr/bin/translator-cli" \ + --disc-tool-bin "$HERE/usr/bin/nodtool" "$@" +APPRUN +chmod +x "$appdir/AppRun" + +echo "Writing desktop entry and icon..." +cat > "$appdir/wiicompiled-setup.desktop" <<'DESKTOP' +[Desktop Entry] +Type=Application +Name=WiiCompiled Setup +Comment=Translate, compile, and launch Mario Kart Wii natively on Linux +Exec=AppRun +Icon=wiicompiled-setup +Categories=Game; +Terminal=true +DESKTOP + +# No WiiCompiled logo/icon asset exists anywhere in this repo yet. appimagetool refuses to package +# without one, so this is a minimal solid-color placeholder - a one-line swap for real branding +# later (just replace this generated file with a real wiicompiled-setup.png before packaging). +python3 - "$appdir/wiicompiled-setup.png" <<'PY' +import struct +import sys +import zlib + +path = sys.argv[1] + + +def chunk(tag: bytes, data: bytes) -> bytes: + return struct.pack(">I", len(data)) + tag + data + struct.pack(">I", zlib.crc32(tag + data)) + + +width = height = 256 +row = b"\x00" + bytes([0x3A, 0x5F, 0x8F, 0xFF]) * width # filter byte + opaque blue-grey pixels +raw = row * height +ihdr = struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0) +idat = zlib.compress(raw, 9) + +with open(path, "wb") as handle: + handle.write(b"\x89PNG\r\n\x1a\n") + handle.write(chunk(b"IHDR", ihdr)) + handle.write(chunk(b"IDAT", idat)) + handle.write(chunk(b"IEND", b"")) +PY + +echo "Resolving appimagetool..." +appimagetool="$appimagetool_override" +if [[ -z "$appimagetool" ]]; then + appimagetool="$workspace/Launcher/artifacts/appimagetool" + if [[ ! -x "$appimagetool" ]]; then + echo "Downloading appimagetool..." + mkdir -p "$(dirname "$appimagetool")" + curl -fsSL "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage" \ + -o "$appimagetool" + chmod +x "$appimagetool" + fi +fi + +mkdir -p "$output_dir" +echo "Packaging..." +# appimagetool detects the target architecture from the first ELF executable it finds in the +# AppDir; AppRun here is a shell script, not ELF, so ARCH must be set explicitly. +ARCH=x86_64 "$appimagetool" "$appdir" "$output_dir/WiiCompiled-Setup-x86_64.AppImage" +echo "Built: $output_dir/WiiCompiled-Setup-x86_64.AppImage" diff --git a/Launcher/local-build.sh b/Launcher/local-build.sh new file mode 100755 index 0000000..ac869c9 --- /dev/null +++ b/Launcher/local-build.sh @@ -0,0 +1,425 @@ +#!/usr/bin/env bash +# Linux build automation: translate -> emit build shards -> configure -> compile -> publish. +# +# This is the native-Linux counterpart to Launcher/LocalBuild.ps1. It is a from-scratch parallel +# implementation, not a port of NativeBuildFlags.ps1: that file's canonical flags and +# prebuilt-package fingerprinting exist only for the Windows/mingw toolchain (a precompiled +# aurora/third-party package, offline pinned dependencies) that this script does not build. +# Linux always builds aurora from source, letting its own CMake auto-detect Vulkan + vendor +# SDL3/Dawn via FetchContent - the same configuration already verified working by hand. +set -euo pipefail + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +log_step() { + # $1 = machine-readable step id, $2 = human sentence. Mirrors LocalBuild.ps1's + # Write-MkwBuildStep: the id is a stable marker a future installer could parse from the log, + # the sentence is for the human reading the terminal. + printf 'MKWCBUILD:STEP:%s %s\n' "$1" "$2" +} + +fail() { + echo "local-build.sh: error: $*" >&2 + exit 1 +} + +assert_file() { + [[ -f "$1" ]] || fail "$2 is missing: $1" +} + +assert_dir() { + [[ -d "$1" ]] || fail "$2 is missing: $1" +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || fail "required tool '$1' was not found on PATH (override with --$2)" +} + +sha256_of() { + sha256sum "$1" | awk '{print $1}' +} + +# --------------------------------------------------------------------------- +# Argument parsing +# --------------------------------------------------------------------------- + +script_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +workspace=$(cd "$script_dir/.." && pwd) +profile=base +output_dir="" +base_output_dir="" +retro_rewind_package_dir="" +retro_wfc_offline_dir="" +skip_retro_wfc_payload=0 +force_clean_build=0 +parallel_override=0 +cc_override="" +cxx_override="" +cmake_override="" +ninja_override="" +dotnet_override="" +translator_dll_override="" +translator_bin_override="" + +usage() { + cat <<'EOF' +Usage: local-build.sh --output-dir DIR [options] + + --workspace DIR Repository root (default: this script's parent directory) + --profile {base|retro-rewind|both} Build profile (default: base) + --output-dir DIR Where the built product is published (required) + --base-output-dir DIR Second output directory; required with --profile both + --retro-rewind-package-dir DIR Retro Rewind source tree (default: PulsarPacks/completed/RetroRewind/RetroRewind6) + --retro-wfc-offline-dir DIR Offline Retro-WFC payload directory + --skip-retro-wfc-payload Build Retro Rewind without a Retro-WFC payload + --force-clean-build Discard every translation/build cache first + --parallel N Pin translator threads, translated-shard job pool, and Ninja parallelism to N + --cc PATH / --cxx PATH C/C++ compiler (default: cc/c++ on PATH) + --cmake PATH / --ninja PATH Build tools (default: on PATH) + --dotnet PATH dotnet executable (default: on PATH) + --translator-dll PATH Pre-built Translator.Cli.dll (skips building the translator; still needs --dotnet to run it) + --translator-bin PATH Self-contained Translator.Cli executable (skips building AND needs no dotnet at all) +EOF +} + +while [[ $# -gt 0 ]]; do + case "$1" in + --workspace) workspace=$(cd "$2" && pwd); shift 2 ;; + --profile) profile=$2; shift 2 ;; + --output-dir) output_dir=$2; shift 2 ;; + --base-output-dir) base_output_dir=$2; shift 2 ;; + --retro-rewind-package-dir) retro_rewind_package_dir=$2; shift 2 ;; + --retro-wfc-offline-dir) retro_wfc_offline_dir=$2; shift 2 ;; + --skip-retro-wfc-payload) skip_retro_wfc_payload=1; shift ;; + --force-clean-build) force_clean_build=1; shift ;; + --parallel) parallel_override=$2; shift 2 ;; + --cc) cc_override=$2; shift 2 ;; + --cxx) cxx_override=$2; shift 2 ;; + --cmake) cmake_override=$2; shift 2 ;; + --ninja) ninja_override=$2; shift 2 ;; + --dotnet) dotnet_override=$2; shift 2 ;; + --translator-dll) translator_dll_override=$2; shift 2 ;; + --translator-bin) translator_bin_override=$2; shift 2 ;; + -h|--help) usage; exit 0 ;; + *) fail "unknown argument: $1" ;; + esac +done + +[[ -n "$output_dir" ]] || { usage; fail "--output-dir is required"; } +case "$profile" in + base|retro-rewind|both) ;; + *) fail "--profile must be base, retro-rewind, or both" ;; +esac + +builds_retro=0 +[[ "$profile" == "retro-rewind" || "$profile" == "both" ]] && builds_retro=1 +has_offline_retro_wfc=0 +[[ -n "$retro_wfc_offline_dir" ]] && has_offline_retro_wfc=1 + +if [[ "$builds_retro" -eq 0 ]]; then + if [[ "$has_offline_retro_wfc" -eq 1 || "$skip_retro_wfc_payload" -eq 1 ]]; then + fail "Retro-WFC payload options are valid only for a Retro Rewind build." + fi + if [[ -n "$retro_rewind_package_dir" ]]; then + fail "--retro-rewind-package-dir is valid only for a Retro Rewind build." + fi +else + if [[ "$has_offline_retro_wfc" -eq "$skip_retro_wfc_payload" ]]; then + fail "Choose exactly one Retro-WFC mode: --retro-wfc-offline-dir or --skip-retro-wfc-payload." + fi +fi +if [[ "$profile" == "both" && -z "$base_output_dir" ]]; then + fail "--base-output-dir is required with --profile both; --output-dir receives the Retro Rewind product." +fi +if [[ "$profile" != "both" && -n "$base_output_dir" ]]; then + fail "--base-output-dir is valid only with --profile both." +fi + +# --------------------------------------------------------------------------- +# Tool resolution and prerequisite checks +# --------------------------------------------------------------------------- + +dotnet_bin=${dotnet_override:-dotnet} +cmake_bin=${cmake_override:-cmake} +ninja_bin=${ninja_override:-ninja} +cc_bin=${cc_override:-clang} +cxx_bin=${cxx_override:-clang++} + +# A self-contained --translator-bin needs no dotnet at all (it bundles its own runtime); dotnet is +# only required when the translator has to be built from source or run as a plain .dll. +if [[ -z "$translator_bin_override" ]]; then + require_command "$dotnet_bin" dotnet +fi +require_command "$cmake_bin" cmake +require_command "$ninja_bin" ninja +require_command "$cc_bin" cc +require_command "$cxx_bin" cxx + +project=$workspace/projects/mkwii/recomp.yml +assets=$workspace/Assets +generated=$workspace/generated +functions=$generated/functions +base_metadata=$generated/base_translation_output.json +base_manifest_dir=$workspace/build/base +base_manifest=$base_manifest_dir/mkwii_base_manifest.json +shards=$generated/build_shards +build=$workspace/native-build +translation_provenance=$generated/translation-provenance.json +toolchain_provenance=$build/toolchain-provenance.json +retro_root=${retro_rewind_package_dir:-$workspace/PulsarPacks/completed/RetroRewind/RetroRewind6} + +assert_file "$project" "Translation project" +assert_file "$assets/main.dol" "Extracted main.dol (see translator/README.md - owning the game is required)" +assert_file "$assets/StaticR.rel" "Extracted StaticR.rel (see translator/README.md - owning the game is required)" + +# Literal line matching against the manifest's fixed shape, not a YAML dependency - the same +# approach NativeBuildFlags.ps1's Get-MkwProjectPins uses on Windows, kept here only for the one +# field this script actually needs from the manifest. +entry_point=$(awk ' + /^translation:/ { in_translation = 1 } + in_translation && /^[[:space:]]*-[[:space:]]*0[xX][0-9a-fA-F]+[[:space:]]*$/ { + gsub(/^[[:space:]]*-[[:space:]]*/, ""); gsub(/[[:space:]]*$/, ""); print; exit + } +' "$project") +[[ -n "$entry_point" ]] || fail "Could not find a translation entry point in $project" + +translator_bin=$translator_bin_override +translator_dll=$translator_dll_override +if [[ -n "$translator_bin" ]]; then + assert_file "$translator_bin" "Translator.Cli executable" + translator() { "$translator_bin" "$@"; } +else + if [[ -z "$translator_dll" ]]; then + translator_dll=$workspace/translator/src/Translator.Cli/bin/Release/net8.0/Translator.Cli.dll + log_step build-translator "Building the translator" + "$dotnet_bin" build "$workspace/translator/src/Translator.Cli/Translator.Cli.csproj" -c Release + fi + assert_file "$translator_dll" "Translator.Cli.dll" + translator() { "$dotnet_bin" "$translator_dll" "$@"; } +fi + +# --------------------------------------------------------------------------- +# Parallelism: three independent knobs, same reasoning as LocalBuild.ps1 - +# translator_threads (translation's own worker threads), translated_jobs (the real RAM guard, +# capping concurrent compiles of memory-hungry translated TUs via Ninja's MKW_TRANSLATED_COMPILE_JOBS +# pool), global_jobs (Ninja's overall parallelism). --parallel pins all three. +# --------------------------------------------------------------------------- + +cpu_count=$(nproc) +mem_gib=$(( $(awk '/^MemTotal:/{print $2}' /proc/meminfo) / 1024 / 1024 )) +(( mem_gib < 1 )) && mem_gib=1 + +if (( parallel_override > 0 )); then + translator_threads=$parallel_override + translated_jobs=$parallel_override + global_jobs=$parallel_override +else + translator_threads=$(( cpu_count < 16 ? cpu_count : 16 )) + (( translator_threads < 1 )) && translator_threads=1 + mem_based_cap=$(( mem_gib / 2 )) + (( mem_based_cap < 1 )) && mem_based_cap=1 + translated_jobs=$(( cpu_count < mem_based_cap ? cpu_count : mem_based_cap )) + (( translated_jobs < 1 )) && translated_jobs=1 + global_jobs=$(( translated_jobs > cpu_count ? translated_jobs : cpu_count )) +fi + +# --------------------------------------------------------------------------- +# Translation cache: this script is the only owner of the reuse decision (unlike LocalBuild.ps1, +# which is handed caller-computed fingerprints by the Windows installer - there is no Linux +# installer yet to supply anything). Hash the game inputs the translation actually depends on; +# a match plus every expected output file present means the previous translation is still good. +# --------------------------------------------------------------------------- + +if (( force_clean_build )); then + log_step force-clean "A clean build was requested; discarding every translation and build cache" + rm -rf "$generated" "$base_manifest_dir" "$build" +fi + +translation_fingerprint=$(cat "$assets/main.dol" "$assets/StaticR.rel" "$project" | sha256sum | awk '{print $1}') +reuse_base=0 +if [[ -f "$translation_provenance" ]]; then + recorded=$(grep -o '"TranslationFingerprint" *: *"[^"]*"' "$translation_provenance" 2>/dev/null | sed 's/.*"\([0-9a-f]*\)"$/\1/' || true) + if [[ "$recorded" == "$translation_fingerprint" && -f "$base_metadata" && -f "$base_manifest" ]]; then + reuse_base=1 + fi +fi + +if (( builds_retro )); then + # The translator discovers the mod through the project file's workspace-relative profile + # paths, and both the base and mod leg block leaf inlining at every address the profile + # patches - so the selected Code.pul must sit at the profile's mod_root before either leg runs. + source_pul=$retro_root/Binaries/Code.pul + assert_file "$source_pul" "Retro Rewind Code.pul" + staged_binaries=$workspace/PulsarPacks/completed/RetroRewind/RetroRewind6/Binaries + mkdir -p "$staged_binaries" + staged_pul=$staged_binaries/Code.pul + if [[ "$(cd "$(dirname "$source_pul")" && pwd)/$(basename "$source_pul")" != "$(cd "$(dirname "$staged_pul")" && pwd)/$(basename "$staged_pul")" ]]; then + cp -f "$source_pul" "$staged_pul" + fi +fi + +if (( reuse_base )) && (( builds_retro )); then + # A base tree that never saw this Code.pul would silently bake vanilla code into the modded + # product - check-base-mod-awareness fails closed (anything but exit 0 forces a retranslation). + retro_code_pul=$retro_root/Binaries/Code.pul + assert_file "$retro_code_pul" "Retro Rewind Code.pul" + pul_sha=$(sha256_of "$retro_code_pul") + if ! grep -q "\"codePulSha256\":\"$pul_sha\"" "$base_metadata"; then + if ! translator check-base-mod-awareness --project "$project" --profile retro-rewind \ + --translation-output-metadata "$base_metadata" --code-pul "$retro_code_pul"; then + log_step retranslate-base "The base translation is stale; retranslating the base game for the new Code.pul" + reuse_base=0 + fi + fi +fi + +if (( reuse_base )); then + log_step reuse-base-translation "Reusing the completed base translation" +else + rm -f "$translation_provenance" + mkdir -p "$generated" "$base_manifest_dir" + + log_step translate-base "Translating the user-owned base game" + translator translate-recursive "$entry_point" --project "$project" \ + --outdir "$functions" --output-metadata "$base_metadata" \ + --production-source-bundle "$generated/base_translation_sources.bin" \ + --no-function-files --prune-stale --threads "$translator_threads" + + log_step emit-base-manifest "Creating the local base translation manifest" + translator emit-base-manifest --project "$project" --out "$base_manifest_dir" \ + --functions-dir "$functions" --translation-output-metadata "$base_metadata" --region P + + printf '{"SchemaVersion":1,"TranslationFingerprint":"%s"}' "$translation_fingerprint" \ + > "$translation_provenance" +fi + +if (( builds_retro )); then + code_pul=$retro_root/Binaries/Code.pul + assert_file "$code_pul" "Retro Rewind Code.pul" + retro_out=$workspace/build/mods/retro_rewind_full_cpp + translate_mod_args=(translate-mod --project "$project" --profile retro-rewind + --base-manifest "$base_manifest" --base-translation-output-metadata "$base_metadata" + --code-pul "$code_pul" --mod-root "$retro_root" --mod-name "Retro Rewind" + --region P --out "$retro_out" --prefer-cached-inputs --emit-cpp + --threads "$translator_threads") + if (( skip_retro_wfc_payload )); then + translate_mod_args+=(--skip-retro-wfc) + else + offline_payload=$retro_wfc_offline_dir/binary/payload.RMCPD00.bin + assert_file "$offline_payload" "Offline Retro-WFC shared payload" + translate_mod_args+=(--retro-wfc-payload "$offline_payload") + fi + log_step translate-mod "Translating the selected Retro Rewind Code.pul" + translator "${translate_mod_args[@]}" +fi + +log_step generate-data-init "Generating local game data initialization" +translator generate-data-init --project "$project" + +shard_args=(emit-build-shards --project "$project" --base-metadata "$base_metadata" + --base-functions-dir "$functions" --native-source-dir "$workspace/runtime/src" --out "$shards") +if (( builds_retro )); then + retro_out=$workspace/build/mods/retro_rewind_full_cpp + shard_args+=(--resolved-profile "$retro_out/resolved_dispatch_profile.json" + --retro-cpp-dir "$retro_out/cpp") +fi +log_step emit-build-shards "Preparing local native build shards" +translator "${shard_args[@]}" + +# --------------------------------------------------------------------------- +# Native configure + build. Deliberately not passing -DAURORA_DAWN_PROVIDER=package or +# -DFETCHCONTENT_FULLY_DISCONNECTED=ON: those exist for the Windows prebuilt-package/offline- +# dependencies workflow this script does not build. aurora's own CMake auto-detects Linux and +# picks Vulkan + vendors SDL3/Dawn via FetchContent, exactly as already verified working by hand. +# --------------------------------------------------------------------------- + +keep_native_build=0 +if [[ -f "$build/CMakeCache.txt" ]]; then + expected_home=$workspace/runtime + cache_home=$(grep '^CMAKE_HOME_DIRECTORY:INTERNAL=' "$build/CMakeCache.txt" | cut -d= -f2- || true) + if [[ -n "$cache_home" && "$(cd "$cache_home" 2>/dev/null && pwd)" == "$expected_home" ]]; then + keep_native_build=1 + fi +fi +if [[ -d "$build" && "$keep_native_build" -eq 0 ]]; then + echo "MKWCBUILD: The native build cache does not belong to this workspace path; rebuilding from scratch" + rm -rf "$build" +elif [[ "$keep_native_build" -eq 1 ]]; then + echo "MKWCBUILD: Reusing the incremental native build directory" +fi + +log_step configure-native "Configuring the native toolchain" +"$cmake_bin" -S "$workspace/runtime" -B "$build" -G Ninja \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_COMPILER="$cc_bin" -DCMAKE_CXX_COMPILER="$cxx_bin" \ + -DCMAKE_MAKE_PROGRAM="$ninja_bin" \ + -DMKW_TRANSLATED_COMPILE_JOBS="$translated_jobs" + +case "$profile" in + base) targets=(WiiCompiled) ;; + retro-rewind) targets=(RetroRewind) ;; + both) targets=(WiiCompiled RetroRewind) ;; +esac +build_args=(--build "$build") +for target in "${targets[@]}"; do build_args+=(--target "$target"); done +build_args+=(--parallel "$global_jobs") +log_step compile "Compiling ${targets[*]} locally" +"$cmake_bin" "${build_args[@]}" + +# --------------------------------------------------------------------------- +# Publish: the Linux build statically links SDL3/Dawn/etc (verified this session), so unlike +# LocalBuild.ps1's DLL-copying dance there is nothing to copy beside the binary except the +# runtime's own first-run assets. +# --------------------------------------------------------------------------- + +dol_sha=$(sha256_of "$assets/main.dol") +rel_sha=$(sha256_of "$assets/StaticR.rel") +compiler_version=$("$cxx_bin" --version | head -1) + +publish_built_product() { + local target=$1 destination=$2 provenance_profile=$3 + mkdir -p "$destination" + local exe=$build/$target + assert_file "$exe" "Locally compiled game executable" + cp -f "$exe" "$destination/$target" + for name in dsp_coef.bin initial_pipeline_cache.db; do + [[ -f "$build/$name" ]] && cp -f "$build/$name" "$destination/" + done + [[ -d "$build/wii_bootstrap" ]] && cp -rf "$build/wii_bootstrap" "$destination/" + + local is_retro=0 code_pul_sha=null + if [[ "$provenance_profile" == "retro-rewind" ]]; then + is_retro=1 + code_pul_sha=\"$(sha256_of "$retro_root/Binaries/Code.pul")\" + fi + local built_utc + built_utc=$(date -u +%Y-%m-%dT%H:%M:%SZ) + cat > "$destination/local-build.json" <. Full license text: Copyright (c) Antoine Aubry and contributors. Referenced by `translator/src/Translator.Core`. Source: +### libco - ISC (valgrind.h: BSD-style) + +Copyright byuu and the higan team. +Non-Windows builds use libco's symmetric stackful coroutines in place of Win32 Fibers for guest +OSThread scheduling (`runtime/src/fiber_manager.cpp`). Vendored in full (all non-Windows +CPU-architecture backends - amd64, x86, arm, aarch64, ppc, ppc64v2, plus the portable sjlj +fallback - though this project's x86_64-only target only ever compiles amd64.c) in +`runtime/third_party/libco` from commit `e18e09d634d612a01781168ad4d76be10a7e3bad`. +Source: . Full license text: +`runtime/third_party/libco/LICENSE`. + --- ## Fetched at build time and redistributed in release builds @@ -129,6 +140,7 @@ included in the installer's `licenses/` folder. | SQLite | 3.51.3 amalgamation | Public domain | | | Tracy Profiler | pinned commit | BSD-3-Clause | | | C++/WinRT | - | MIT (Microsoft) | | +| nodtool (disc image extraction) | v2.0.0-alpha.10 | MIT OR Apache-2.0 | | ### Dual-licensed components - elections made by this project @@ -153,16 +165,15 @@ unmodified, with their license texts, in the installer's `licenses/` folder. | llvm-mingw (Clang, LLD, libc++, libunwind, MinGW-w64 runtime) | Apache-2.0 with LLVM Exception; MinGW-w64 runtime under its own permissive terms; bundled GNU utilities under GPL-2.0-or-later or GPL-3.0-or-later | | | CMake | BSD-3-Clause | | | Ninja | Apache-2.0 | | -| DolphinTool (disc image extraction) | GPL-2.0-or-later | | +| nodtool (disc image extraction) | MIT OR Apache-2.0 | | | Microsoft Visual C++ Runtime (`vcruntime140.dll`, `vcruntime140_1.dll`, `msvcp140.dll`) | Microsoft redistributable terms | Microsoft Visual Studio | | `dxil.dll` | Microsoft redistributable (proprietary signing library) | Microsoft | > [!IMPORTANT] -> Several toolkit components are GPL-licensed (DolphinTool, and the GNU utilities inside -> llvm-mingw). Their complete corresponding source is available from the upstream projects linked -> above at their pinned versions, and this project will supply it on request for the exact versions -> shipped in any given release. Pins live in `Launcher/Prepare-PortableTools.ps1` and -> `Launcher/NativeBuildFlags.ps1`. +> The GNU utilities bundled inside llvm-mingw are GPL-licensed. Their complete corresponding source +> is available from the upstream project linked above at its pinned version, and this project will +> supply it on request for the exact version shipped in any given release. Pins live in +> `Launcher/Prepare-PortableTools.ps1` and `Launcher/NativeBuildFlags.ps1`. --- diff --git a/aurora-main/lib/input.cpp b/aurora-main/lib/input.cpp index 2b3651e..e22e764 100644 --- a/aurora-main/lib/input.cpp +++ b/aurora-main/lib/input.cpp @@ -329,12 +329,52 @@ void apply_port_preferences() noexcept { } } +#if defined(_WIN32) // Ports are explicit assignments. SDL may choose a player index at connection // time, but accepting it would make a newly connected controller silently take -// over a game port before the user assigns it in the controller menu. +// over a game port before the user assigns it in the controller menu - which +// matters here because a manually-assigned WUP-028 adapter port (see +// wup028_adapter.cpp, Windows-only) could otherwise collide with one SDL +// auto-claimed. Elsewhere, with no WUP-028 port to collide with, the original +// auto-claim behavior below is restored instead. void ensure_player_index(GameController& controller) noexcept { assign_player_index(controller, -1); } +#else +// SDL only hands out a player index when the device already had a gamepad mapping +// at connect time, so anything mapped later (the setup wizard) stays at -1. +void ensure_player_index(GameController& controller) noexcept { + const int32_t player = SDL_GetGamepadPlayerIndex(controller.m_controller); + if (player >= 0) { + controller.m_playerIndex = player; + return; + } + if (controller.m_playerIndex >= 0) { + return; + } + ensure_port_preferences_loaded(); + const auto claim = [&](bool skipConfiguredPorts) { + for (int32_t port = 0; port < PAD_MAX_CONTROLLERS; ++port) { + if (skipConfiguredPorts && g_portPreferences[port].state != PortPreferenceState::Unset) { + continue; + } + const bool taken = std::any_of(g_GameControllers.begin(), g_GameControllers.end(), [&](const auto& entry) { + return entry.second.m_controller != controller.m_controller && effective_player_index(entry.second) == port; + }); + if (!taken) { + assign_player_index(controller, port); + return true; + } + } + return false; + }; + // Explicitly configured ports are only used as a last resort so a hot-plugged + // controller cannot steal the port its preferred device will claim. + if (!claim(true)) { + claim(false); + } +} +#endif } // namespace GameController* get_controller_for_player(uint32_t player) noexcept { diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index c2b3a1b..9a3aad1 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -1,10 +1,13 @@ cmake_minimum_required(VERSION 3.16) project(mkw_recompiled) -if(NOT WIN32 OR NOT MINGW OR NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR +if((NOT (WIN32 AND MINGW)) AND (NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")) + message(FATAL_ERROR "WiiCompiled requires Windows (LLVM-MinGW) or native Linux") +endif() +if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR NOT CMAKE_SIZEOF_VOID_P EQUAL 8 OR NOT CMAKE_SYSTEM_PROCESSOR MATCHES "^(AMD64|amd64|x86_64|X86_64)$") - message(FATAL_ERROR "WiiCompiled requires 64-bit LLVM-MinGW Clang on Windows") + message(FATAL_ERROR "WiiCompiled requires 64-bit Clang targeting x86_64") endif() if(NOT CMAKE_BUILD_TYPE STREQUAL "Release") message(FATAL_ERROR "WiiCompiled only supports Release builds") @@ -46,6 +49,23 @@ target_include_directories(mkw_pugixml PUBLIC third_party/pugixml) target_compile_features(mkw_pugixml PUBLIC cxx_std_17) set_target_properties(mkw_pugixml PROPERTIES UNITY_BUILD OFF) +# Non-Windows guest-fiber scheduling (runtime/src/fiber_manager.cpp) needs a symmetric +# stackful-coroutine primitive to stand in for Win32 Fibers. libco's co_switch() transfers +# directly to any other created coroutine, matching SwitchToFiber's semantics exactly (unlike +# asymmetric resume/yield coroutine libraries, which would need every call site restructured). +# Vendored from upstream (higan-emu/libco @ e18e09d, 2019-10-16, ISC license; valgrind.h is +# separately BSD-style licensed, see third_party/libco/LICENSE) - all of libco's non-Windows +# CPU-architecture backends are kept, even though libco.c's own preprocessor dispatch +# (__amd64__/__i386__/__arm__/__aarch64__/etc.) only ever selects amd64.c for this project's +# x86_64-only target (see the platform/arch check above). Windows keeps using native Fibers +# untouched, so this target is never built there. +if(NOT WIN32) + add_library(mkw_libco STATIC third_party/libco/libco.c) + add_library(mkw::libco ALIAS mkw_libco) + target_include_directories(mkw_libco PUBLIC third_party/libco) + set_target_properties(mkw_libco PROPERTIES UNITY_BUILD OFF) +endif() + # Runtime configuration is real TOML, parsed by toml11 rather than a project- # specific line parser. Keep it header-only and vendored so disconnected release # builds have exactly the same parser as developer builds. @@ -110,12 +130,22 @@ else() message(FATAL_ERROR "Requested aurora-main but ${MKW_AURORA_DIR} is missing") endif() set(DAWN_ENABLE_D3D11 OFF CACHE BOOL "" FORCE) - set(DAWN_ENABLE_D3D12 ON CACHE BOOL "" FORCE) + if(WIN32) + set(DAWN_ENABLE_D3D12 ON CACHE BOOL "" FORCE) + set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "" FORCE) + set(DAWN_USE_WINDOWS_UI OFF CACHE BOOL "" FORCE) + else() + # Non-Windows (Linux): mirrors aurora-main's own + # _aurora_dawn_set_platform_backends() choice for this platform - Vulkan only, no + # D3D/HLSL. Kept in sync here because this project's own CMake FORCEs these cache + # variables before aurora-main's add_subdirectory() runs, which pre-empts aurora's + # auto-detection (CACHE ... INTERNAL "" without FORCE never overrides an existing value). + set(DAWN_ENABLE_D3D12 OFF CACHE BOOL "" FORCE) + set(TINT_BUILD_HLSL_WRITER OFF CACHE BOOL "" FORCE) + endif() set(DAWN_ENABLE_VULKAN ON CACHE BOOL "" FORCE) - set(TINT_BUILD_HLSL_WRITER ON CACHE BOOL "" FORCE) set(DAWN_BUILD_SAMPLES OFF CACHE BOOL "" FORCE) set(DAWN_BUILD_TESTS OFF CACHE BOOL "" FORCE) - set(DAWN_USE_WINDOWS_UI OFF CACHE BOOL "" FORCE) # Provide a tiny stub for DXProgrammableCapture when the SDK/PIX headers are # missing (common on MinGW). Dawn only includes the header; no symbols are @@ -207,6 +237,15 @@ set(MKW_CPU_BASELINE_SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/host_cpu_baseline.cpp list(REMOVE_ITEM SOURCES ${MKW_BASE_PRODUCT_SOURCE} ${MKW_RETRO_REWIND_PRODUCT_SOURCE} ${MKW_CPU_BASELINE_SOURCE}) +# WUP-028 (official GameCube adapter) support talks to the adapter over WinUSB, which only +# exists on Windows - SDL3 already exposes the same hardware as a normal joystick on Linux/macOS, +# so this file has nothing to do there. Every call site into it is separately gated behind +# #if defined(_WIN32) (see wup028_adapter.cpp's own header comment), so it's safe to simply not +# compile it at all on other platforms rather than build a stub implementation. +if(NOT WIN32) + list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/wup028_adapter.cpp") +endif() + # The translator emits the complete, content-addressed source graph. Consuming # this one manifest keeps configure independent of the 28k generated function # files and of optional Retro Rewind artifacts such as code.map. diff --git a/runtime/cmake/PublicProducts.cmake b/runtime/cmake/PublicProducts.cmake index 111d354..d1fff5e 100644 --- a/runtime/cmake/PublicProducts.cmake +++ b/runtime/cmake/PublicProducts.cmake @@ -77,7 +77,11 @@ target_compile_definitions(mkw_runtime_common PRIVATE target_link_libraries(mkw_runtime_common PRIVATE aurora::gx aurora::pad aurora::si aurora::vi aurora::mtx) target_link_libraries(mkw_runtime_common PRIVATE mkw::pugixml mkw::toml11 mkw::cryptopp) -target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp) +if(WIN32) + target_link_libraries(mkw_runtime_common PRIVATE shell32 windowsapp) +else() + target_link_libraries(mkw_runtime_common PRIVATE mkw::libco) +endif() if(MKW_CPPWINRT_INCLUDE_DIR) if(NOT EXISTS "${MKW_CPPWINRT_INCLUDE_DIR}/winrt/base.h") message(FATAL_ERROR @@ -204,26 +208,38 @@ function(mkw_configure_product target) $ $) endif() - target_link_libraries(${target} PRIVATE - dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp setupapi winusb) + if(WIN32) + target_link_libraries(${target} PRIVATE + dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp setupapi winusb) - set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE) - foreach(runtime_dll libc++.dll libunwind.dll) - execute_process( - COMMAND "${CMAKE_CXX_COMPILER}" "--print-file-name=${runtime_dll}" - OUTPUT_VARIABLE runtime_dll_path - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(NOT EXISTS "${runtime_dll_path}") - get_filename_component(mkw_compiler_bin "${CMAKE_CXX_COMPILER}" DIRECTORY) - set(runtime_dll_path "${mkw_compiler_bin}/${runtime_dll}") - endif() - if(NOT EXISTS "${runtime_dll_path}") - message(FATAL_ERROR "llvm-mingw runtime DLL not found: ${runtime_dll}") - endif() - add_custom_command(TARGET ${target} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different - "${runtime_dll_path}" $) - endforeach() + set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE) + else() + # mkw_runtime_common is an OBJECT library: WiiCompiled/RetroRewind only pull in its .o + # files via $, which does not propagate mkw_runtime_common's own + # target_link_libraries (object libraries don't carry usage requirements to a consumer + # that isn't itself linked against as a target). fiber_manager.cpp's co_* calls live in + # those objects, so the actual executable link needs mkw::libco directly, same as it + # needs it independently of that first `if(WIN32)` branch above. + target_link_libraries(${target} PRIVATE mkw::libco) + endif() + if(WIN32) + foreach(runtime_dll libc++.dll libunwind.dll) + execute_process( + COMMAND "${CMAKE_CXX_COMPILER}" "--print-file-name=${runtime_dll}" + OUTPUT_VARIABLE runtime_dll_path + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(NOT EXISTS "${runtime_dll_path}") + get_filename_component(mkw_compiler_bin "${CMAKE_CXX_COMPILER}" DIRECTORY) + set(runtime_dll_path "${mkw_compiler_bin}/${runtime_dll}") + endif() + if(NOT EXISTS "${runtime_dll_path}") + message(FATAL_ERROR "llvm-mingw runtime DLL not found: ${runtime_dll}") + endif() + add_custom_command(TARGET ${target} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "${runtime_dll_path}" $) + endforeach() + endif() set(MKW_WII_BOOTSTRAP_SOURCE_DIR "${MKW_RUNTIME_SOURCE_DIR}/assets/wii") if(NOT EXISTS "${MKW_WII_BOOTSTRAP_SOURCE_DIR}/shared2/wc24") diff --git a/runtime/include/fiber_manager.h b/runtime/include/fiber_manager.h index dbe06d3..bdefcf3 100644 --- a/runtime/include/fiber_manager.h +++ b/runtime/include/fiber_manager.h @@ -102,12 +102,24 @@ private: static void CALLBACK FiberProc(void* param); #else static void FiberProc(void* param); + // libco's co_create() entry points take no argument (unlike CreateFiber's FiberProc(void*)), + // so this trampoline reads the guest thread address staged by CreateGuestFiber() and forwards + // into the (platform-neutral-bodied) FiberProc above. See fiber_manager.cpp. + static void FiberProcTrampoline(); #endif - + // Switch from whichever fiber is currently active straight to the scheduler fiber, without + // the SwitchToThread bookkeeping (CPU context save/restore, s_currentGuestThread). Used for + // in-fiber yields that aren't a real guest thread switch: waiting out the EGG::Thread::start + // deferral loop, and returning control on natural thread exit. + static void SwitchToScheduler(); + // Internal state static std::mutex s_mutex; static std::unordered_map s_fibers; static std::vector s_fibersPendingDelete; + // The scheduler's own "fiber": a Windows HFIBER, or (non-Windows) libco's cothread_t for + // whichever native call stack first called GuestFiberManager::Initialize() - both are + // plain void* handles, so one field serves both platforms. static void* s_schedulerFiber; static uint32_t s_currentGuestThread; static bool s_initialized; diff --git a/runtime/include/guest_flat_memory.h b/runtime/include/guest_flat_memory.h index caa71c2..e33573a 100644 --- a/runtime/include/guest_flat_memory.h +++ b/runtime/include/guest_flat_memory.h @@ -73,8 +73,12 @@ FaultCounters Counters(); void LogFaultSummary() noexcept; // Returns true when the access violation was a guest-space fault this module -// resolved; the caller must then resume execution. `exceptionPointers` is a -// Windows EXCEPTION_POINTERS*. -bool HandleAccessViolation(void* exceptionPointers) noexcept; +// resolved; the caller must then resume execution. `faultAddress` is the raw +// host pointer the access violation trapped on (Windows: ExceptionInformation[1]; +// POSIX: siginfo_t::si_addr) and `isWrite` is whether it was a write access +// (Windows: ExceptionInformation[0] != 0; POSIX: derived from the ucontext). +// The platform-specific handler that calls this is expected to have already +// done that extraction - this function only ever works with the parsed pair. +bool HandleAccessViolation(void* faultAddress, bool isWrite) noexcept; } // namespace GuestFlat diff --git a/runtime/include/isa/ppc_isa_config.h b/runtime/include/isa/ppc_isa_config.h index 663b576..f4c41ea 100644 --- a/runtime/include/isa/ppc_isa_config.h +++ b/runtime/include/isa/ppc_isa_config.h @@ -11,11 +11,22 @@ inline constexpr bool MkwStateFreeAbiEnabled(uint32_t) noexcept return true; } +#if defined(_WIN32) #define MKW_PPC_FORCE_INLINE __forceinline #define MKW_PPC_NO_INLINE __declspec(noinline) +#define MKW_PPC_INTERNAL_CALL __regcall +#else +// __forceinline/__declspec are MS-extension keywords Clang only recognizes when targeting +// Windows (MSVC or mingw); native Linux Clang needs the GNU-attribute spellings instead. +// __regcall has no portable non-Windows equivalent worth chasing here - the extra register +// args it saves matter for the hot PPC interpreter loop on Windows, but plain calls are fine +// elsewhere. +#define MKW_PPC_FORCE_INLINE __attribute__((always_inline)) inline +#define MKW_PPC_NO_INLINE __attribute__((noinline)) +#define MKW_PPC_INTERNAL_CALL +#endif #define MKW_PPC_ALWAYS_INLINE_BODY __attribute__((always_inline)) #define MKW_PPC_COLD __attribute__((cold)) -#define MKW_PPC_INTERNAL_CALL __regcall using MkwStateFreeResult2 = uint64_t __attribute__((ext_vector_type(2))); diff --git a/runtime/include/memory_access.h b/runtime/include/memory_access.h index c1cca16..8b19e02 100644 --- a/runtime/include/memory_access.h +++ b/runtime/include/memory_access.h @@ -18,8 +18,15 @@ extern "C" { } namespace MemoryInline { +#if defined(_WIN32) #define MKW_MEMORY_FORCE_INLINE __forceinline #define MKW_MEMORY_NO_INLINE __declspec(noinline) +#else +// See runtime/include/isa/ppc_isa_config.h for why non-Windows Clang needs the GNU-attribute +// spellings instead of the MS-extension keywords. +#define MKW_MEMORY_FORCE_INLINE __attribute__((always_inline)) inline +#define MKW_MEMORY_NO_INLINE __attribute__((noinline)) +#endif #define MKW_MEMORY_COLD __attribute__((cold)) inline constexpr uint32_t kPageShift = 20; inline constexpr uint32_t kPageSize = 1u << kPageShift; diff --git a/runtime/include/runtime_config.h b/runtime/include/runtime_config.h index b86b723..0a0debf 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -23,6 +23,9 @@ #endif #include #include +#else +#include +#include #endif struct RuntimeUserConfig { @@ -60,9 +63,11 @@ struct RuntimeUserConfig { // comma-separated SDL-style physical button names ("south", or // "dpad_up,left_shoulder") as values; pressing either bound button counts. std::array, 12> controllerButtons; +#ifdef _WIN32 // One-based physical WUP-028 adapter port assigned to each game port. // Zero or a missing value means the adapter does not own that game port. std::array gameCubeAdapterPorts{}; +#endif }; namespace RuntimeConfigFile { @@ -168,7 +173,22 @@ inline std::optional ExecutableDirectory() { buffer.resize(buffer.size() * 2); } #else - return std::nullopt; + // /proc/self/exe is a Linux-specific magic symlink to the running executable; readlink() + // does not NUL-terminate and silently truncates if the buffer is too small, so this grows + // the buffer until the result no longer fills it completely, the same doubling strategy as + // the Windows branch above uses for GetModuleFileNameW. + std::string buffer(256, '\0'); + for (;;) { + const ssize_t length = readlink("/proc/self/exe", buffer.data(), buffer.size()); + if (length < 0) { + return std::nullopt; + } + if (static_cast(length) < buffer.size()) { + buffer.resize(static_cast(length)); + return std::filesystem::path(buffer).parent_path(); + } + buffer.resize(buffer.size() * 2); + } #endif } @@ -209,6 +229,15 @@ inline std::filesystem::path ApplicationDataDirectory() { CoTaskMemFree(rawPath); return directory; } +#else + // XDG Base Directory spec equivalent of FOLDERID_LocalAppData: $XDG_DATA_HOME if set and + // non-empty, otherwise its default of $HOME/.local/share. + if (const char* xdgDataHome = std::getenv("XDG_DATA_HOME"); xdgDataHome && *xdgDataHome) { + return std::filesystem::path(xdgDataHome) / kApplicationDirectoryName; + } + if (const char* home = std::getenv("HOME"); home && *home) { + return std::filesystem::path(home) / ".local" / "share" / kApplicationDirectoryName; + } #endif return std::filesystem::current_path() / kApplicationDirectoryName; } @@ -338,12 +367,14 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) { config.controllerButtons[index] = FindConfigValue(document, "controller", buttonKeys[index]); } +#ifdef _WIN32 for (size_t index = 0; index < config.gameCubeAdapterPorts.size(); ++index) { const std::string key = "adapter_port_" + std::to_string(index + 1); if (auto value = FindConfigUint(document, "controller", key); value && *value <= 4) { config.gameCubeAdapterPorts[index] = *value; } } +#endif config.widescreen = FindConfigValue(document, "video", "widescreen"); config.windowPosX = FindConfigInt(document, "video", "window_x"); @@ -607,6 +638,7 @@ inline bool SetControllerButton(size_t index, std::string value) { return WriteSetting("controller", kControllerButtonKeys[index], FormatString(value)); } +#ifdef _WIN32 inline int GameCubeAdapterPort(size_t gamePort) { if (gamePort >= Get().gameCubeAdapterPorts.size()) return -1; const uint32_t physicalPort = Get().gameCubeAdapterPorts[gamePort]; @@ -619,6 +651,7 @@ inline bool SetGameCubeAdapterPort(size_t gamePort, int physicalPort) { Mutable().gameCubeAdapterPorts[gamePort] = storedPort; return WriteSetting("controller", "adapter_port_" + std::to_string(gamePort + 1), std::to_string(storedPort)); } +#endif inline bool SetAudioVolume(float value) { value = std::clamp(value, 0.0f, 1.0f); diff --git a/runtime/include/system_bridge.h b/runtime/include/system_bridge.h index ec76421..1f57f0d 100644 --- a/runtime/include/system_bridge.h +++ b/runtime/include/system_bridge.h @@ -11,10 +11,23 @@ #include "memory.h" +// Windows' SehLogger longjmps out of a vectored exception handler, where plain setjmp/longjmp is +// the norm. A POSIX signal handler jumping back to here must use the sig-prefixed pair instead: +// only sigsetjmp/siglongjmp save and restore the process signal mask, which is what keeps SIGSEGV +// from staying blocked (and a second fault during the same ctor loop from escalating instead of +// trapping) after the first recovered fault. +#if defined(_WIN32) +using MkwJmpBuf = jmp_buf; +#define MKW_SETJMP(buf) setjmp(buf) +#else +using MkwJmpBuf = sigjmp_buf; +#define MKW_SETJMP(buf) sigsetjmp(buf, 1) +#endif + // Global flag to suppress SEH reporting (caught by system_bridge) extern bool g_suppressSehReporting; // Jump buffer for SEH recovery -extern thread_local jmp_buf* g_sehJumpTarget; +extern thread_local MkwJmpBuf* g_sehJumpTarget; // SEH details for the most recent trapped exception (used during ctor execution). extern thread_local uint32_t g_sehLastExceptionCode; extern thread_local uintptr_t g_sehLastExceptionAddress; diff --git a/runtime/src/fiber_manager.cpp b/runtime/src/fiber_manager.cpp index a19731c..8e15c1a 100644 --- a/runtime/src/fiber_manager.cpp +++ b/runtime/src/fiber_manager.cpp @@ -13,8 +13,24 @@ #include #include +#if !defined(_WIN32) +#include "libco.h" +#endif + namespace Fiber { +#if !defined(_WIN32) +namespace { +// libco's co_create() entry points take no argument, unlike CreateFiber(size, FiberProc, param). +// CreateGuestFiber() stages the guest thread address here immediately before the first co_switch +// into a freshly created cothread; FiberProcTrampoline reads it exactly once, at the top of the +// fiber's very first activation. Safe because guest fibers are strictly cooperative on a single +// OS thread: nothing else can run (and so nothing else can overwrite this) between the staging +// write and the trampoline's read of it. +thread_local uint32_t s_pendingFiberArg = 0; +} // namespace +#endif + std::mutex GuestFiberManager::s_mutex; std::unordered_map GuestFiberManager::s_fibers; std::vector GuestFiberManager::s_fibersPendingDelete; @@ -24,18 +40,25 @@ bool GuestFiberManager::s_initialized = false; thread_local CpuContext* GuestFiberManager::s_cpuContext = nullptr; void GuestFiberManager::PurgePendingFibers() { -#if defined(_WIN32) std::vector toDelete; { std::lock_guard lock(s_mutex); toDelete.swap(s_fibersPendingDelete); } +#if defined(_WIN32) const void* current = GetCurrentFiber(); for (void* f : toDelete) { if (f && f != current) { DeleteFiber(f); } } +#else + const void* current = co_active(); + for (void* f : toDelete) { + if (f && f != current) { + co_delete(static_cast(f)); + } + } #endif } @@ -204,10 +227,12 @@ void GuestFiberManager::Initialize() { } #else - RT_LOG(RT_TAG_OS) << "WARNING: Fiber support not available on this platform!" << std::endl; - s_schedulerFiber = nullptr; + // co_active() returns a handle for whichever native stack is currently running, creating one + // on first call if needed - the libco analogue of ConvertThreadToFiber(nullptr): it converts + // this call's own stack into a switchable target without altering control flow. + s_schedulerFiber = co_active(); #endif - + s_currentGuestThread = 0; s_initialized = true; } @@ -223,14 +248,25 @@ void GuestFiberManager::Shutdown() { } } s_fibers.clear(); - + // Convert scheduler fiber back to thread if (s_schedulerFiber) { ConvertFiberToThread(); s_schedulerFiber = nullptr; } +#else + for (auto& [addr, fiber] : s_fibers) { + if (fiber.fiber && !fiber.isSchedulerFiber) { + co_delete(static_cast(fiber.fiber)); + fiber.fiber = nullptr; + } + } + s_fibers.clear(); + // Unlike ConvertFiberToThread, libco has no "undo" for co_active(): the scheduler's own + // stack was never separately allocated, so there is nothing to release here. + s_schedulerFiber = nullptr; #endif - + s_initialized = false; } @@ -250,12 +286,14 @@ bool GuestFiberManager::CreateGuestFiber(uint32_t guestThreadAddr, uint32_t entr // Check if fiber already exists for this thread - if so, reset it auto existingIt = s_fibers.find(guestThreadAddr); if (existingIt != s_fibers.end()) { -#if defined(_WIN32) // Delete the old fiber if it exists and is not the scheduler fiber if (existingIt->second.fiber && !existingIt->second.isSchedulerFiber) { +#if defined(_WIN32) DeleteFiber(existingIt->second.fiber); - } +#else + co_delete(static_cast(existingIt->second.fiber)); #endif + } s_fibers.erase(existingIt); } @@ -288,9 +326,18 @@ bool GuestFiberManager::CreateGuestFiber(uint32_t guestThreadAddr, uint32_t entr return false; } #else - gf.fiber = nullptr; + // libco's co_create() entry point takes no argument; SwitchToThread() stages guestThreadAddr + // into s_pendingFiberArg immediately before the co_switch that first activates this handle. + constexpr unsigned int kHostStackSize = 64 * 1024; + gf.fiber = co_create(kHostStackSize, &FiberProcTrampoline); + + if (!gf.fiber) { + RT_LOG(RT_TAG_OS) << "co_create failed for thread 0x" + << std::hex << guestThreadAddr << std::dec << std::endl; + return false; + } #endif - + s_fibers[guestThreadAddr] = gf; @@ -342,17 +389,27 @@ void GuestFiberManager::ExitGuestThread(uint32_t guestThreadAddr, ThreadState fi s_currentGuestThread = 0; } -#if defined(_WIN32) if (it->second.fiber && !it->second.isSchedulerFiber) { +#if defined(_WIN32) const void* current = GetCurrentFiber(); if (it->second.fiber == current) { s_fibersPendingDelete.push_back(it->second.fiber); } else { DeleteFiber(it->second.fiber); } +#else + const void* current = co_active(); + if (it->second.fiber == current) { + // Deleting the coroutine we're currently executing on would free the very stack + // this call is running on; defer it (PurgePendingFibers) until some other fiber is + // active, exactly like the Windows branch above. + s_fibersPendingDelete.push_back(it->second.fiber); + } else { + co_delete(static_cast(it->second.fiber)); + } +#endif it->second.fiber = nullptr; } -#endif } void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu) { @@ -415,10 +472,13 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu // Store CPU context pointer for the target fiber to use s_cpuContext = cpu; -#if defined(_WIN32) // Check if we're already on the target fiber (e.g., switching to main thread // when we're already on the scheduler fiber) +#if defined(_WIN32) void* currentFiber = GetCurrentFiber(); +#else + void* currentFiber = co_active(); +#endif if (currentFiber == fiberHandle) { // Already executing on the target host fiber. This is common for the // default guest thread, which also owns the scheduler fiber. Keep the @@ -426,7 +486,7 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu // from before the guest thread slept. return; } - + if (cpu && haveTargetContext) { *cpu = targetContext; // FPSCR travels with the guest-thread context, and its NI bit is @@ -436,8 +496,16 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu } // Switch to the target fiber (the target fiber will load its own context) +#if defined(_WIN32) SwitchToFiber(fiberHandle); - +#else + // Staged for FiberProcTrampoline's first (and only) read; a no-op for a fiber that has + // already started, since resuming it re-enters mid-function rather than through the + // trampoline's entry point. + s_pendingFiberArg = guestThreadAddr; + co_switch(static_cast(fiberHandle)); +#endif + // When we return here, the fiber that issued SwitchToThread has resumed. // That does not automatically mean the previous guest thread became runnable // again; a different thread may simply have yielded back to the scheduler. @@ -476,7 +544,6 @@ void GuestFiberManager::SwitchToThread(uint32_t guestThreadAddr, CpuContext* cpu s_currentGuestThread = 0; } } -#endif } uint32_t GuestFiberManager::GetCurrentGuestThread() { @@ -551,6 +618,14 @@ void GuestFiberManager::ProcessTimerEvents(CpuContext* cpu) { } } +void GuestFiberManager::SwitchToScheduler() { +#if defined(_WIN32) + SwitchToFiber(s_schedulerFiber); +#else + co_switch(static_cast(s_schedulerFiber)); +#endif +} + #if defined(_WIN32) void CALLBACK GuestFiberManager::FiberProc(void* param) #else @@ -558,9 +633,7 @@ void GuestFiberManager::FiberProc(void* param) #endif { uint32_t guestThreadAddr = static_cast(reinterpret_cast(param)); - - -#if defined(_WIN32) + // Get our fiber info GuestFiber* fiber = nullptr; uint32_t entryPoint = 0; @@ -571,7 +644,7 @@ void GuestFiberManager::FiberProc(void* param) auto it = s_fibers.find(guestThreadAddr); if (it == s_fibers.end()) { RT_LOG(RT_TAG_OS) << "FiberProc: fiber not found!" << std::endl; - SwitchToFiber(s_schedulerFiber); + SwitchToScheduler(); return; } fiber = &it->second; @@ -634,7 +707,7 @@ void GuestFiberManager::FiberProc(void* param) << ", fn=0x" << startFn << ") after retries; continuing anyway." << std::dec << std::endl; break; } - SwitchToFiber(s_schedulerFiber); + SwitchToScheduler(); } // The deferral loop above yields to the scheduler and therefore can resume @@ -691,11 +764,18 @@ void GuestFiberManager::FiberProc(void* param) } // Return to scheduler - SwitchToFiber(s_schedulerFiber); -#else - (void)guestThreadAddr; - RT_LOG(RT_TAG_OS) << "Fibers not supported on this platform!" << std::endl; -#endif + SwitchToScheduler(); } +#if !defined(_WIN32) +void GuestFiberManager::FiberProcTrampoline() { + const uint32_t guestThreadAddr = s_pendingFiberArg; + FiberProc(reinterpret_cast(static_cast(guestThreadAddr))); + // FiberProc always calls SwitchToScheduler() on every exit path and never falls off its own + // end; this is only a safety net in case that ever changes; falling off co_create's entry + // function is otherwise undefined behavior (libco's own crash() fallback aborts instead). + SwitchToScheduler(); +} +#endif + } // namespace Fiber diff --git a/runtime/src/guest_flat_memory.cpp b/runtime/src/guest_flat_memory.cpp index 2075f42..ae9f3f7 100644 --- a/runtime/src/guest_flat_memory.cpp +++ b/runtime/src/guest_flat_memory.cpp @@ -19,6 +19,7 @@ #include "runtime_log.h" #include "system_bridge.h" +#if defined(_WIN32) #ifndef NOMINMAX #define NOMINMAX #endif @@ -26,28 +27,57 @@ #define WIN32_LEAN_AND_MEAN #endif #include +#else +#include +#include +#include +#include +#include +#endif namespace GuestFlat { namespace { +#if defined(_WIN32) // Placeholder / view constants. Declared here so the build does not depend on // the exact Windows SDK version that first shipped them. constexpr DWORD kMemReplacePlaceholder = 0x00004000; constexpr DWORD kMemReservePlaceholder = 0x00040000; constexpr DWORD kMemPreservePlaceholder = 0x00000002; +#endif constexpr size_t kAllocationGranularity = 0x10000; // 64 KiB constexpr size_t kHostPageSize = 0x1000; +// Named, platform-neutral protection modes so every fault-interception call site below (the +// MMIO window, the executable-write guard, deferred-EFB-read protection, the on-demand +// unmapped-block commit) can stay identical text on both platforms; only ProtectRange() and +// CommitPlaceholder() below branch on VirtualProtect vs. mprotect. +#if defined(_WIN32) +using ProtectionFlags = DWORD; +constexpr ProtectionFlags kProtNone = PAGE_NOACCESS; +constexpr ProtectionFlags kProtRead = PAGE_READONLY; +constexpr ProtectionFlags kProtReadWrite = PAGE_READWRITE; +#else +using ProtectionFlags = int; +constexpr ProtectionFlags kProtNone = PROT_NONE; +constexpr ProtectionFlags kProtRead = PROT_READ; +constexpr ProtectionFlags kProtReadWrite = PROT_READ | PROT_WRITE; +#endif + +#if defined(_WIN32) using VirtualAlloc2Fn = PVOID(WINAPI*)(HANDLE, PVOID, SIZE_T, ULONG, ULONG, void*, ULONG); using MapViewOfFile3Fn = PVOID(WINAPI*)(HANDLE, HANDLE, PVOID, ULONG64, SIZE_T, ULONG, ULONG, void*, ULONG); VirtualAlloc2Fn g_virtualAlloc2 = nullptr; MapViewOfFile3Fn g_mapViewOfFile3 = nullptr; +#endif uint8_t* g_base = nullptr; bool g_initialized = false; std::vector g_activeRegions; +#if defined(_WIN32) PVOID g_vectoredHandle = nullptr; +#endif std::mutex& StateMutex() { static std::mutex mutex; @@ -70,7 +100,11 @@ struct SectionKeyHash { }; struct Section { +#if defined(_WIN32) HANDLE handle = nullptr; +#else + int fd = -1; +#endif uint64_t size = 0; uint8_t* hostView = nullptr; }; @@ -114,6 +148,18 @@ std::vector& DeferredRanges() { return ranges; } +#if !defined(_WIN32) +// Windows disambiguates a racing "unmapped touch" fault via VirtualQuery (did some other thread +// already commit this 64 KiB block, and is it actually accessible enough to satisfy this access). +// mprotect has no query counterpart, so this tracks the same fact ourselves: one bit per 64 KiB +// block, set the first time this module ever commits it, checked-and-set under StateMutex() so +// two threads racing on the same never-yet-committed block still report/commit exactly once. +std::vector& UnmappedCommittedBlocks() { + static std::vector blocks(1u << 16, 0); // 2^32 / 64 KiB + return blocks; +} +#endif + std::atomic g_countMmio{0}; std::atomic g_countEfb{0}; std::atomic g_countXGuard{0}; @@ -133,10 +179,26 @@ uint64_t RoundUp(uint64_t value, uint64_t alignment) { std::string LastErrorText(const char* what) { std::ostringstream oss; +#if defined(_WIN32) oss << what << " failed (GetLastError=" << GetLastError() << ")"; +#else + oss << what << " failed (" << std::strerror(errno) << ")"; +#endif return oss.str(); } +// Protects [address, address+size) with `protection`, bridging VirtualProtect (Windows) and +// mprotect (POSIX) so every fault-interception call site below can stay platform-neutral. +bool ProtectRange(uint8_t* address, uint64_t size, ProtectionFlags protection) { +#if defined(_WIN32) + DWORD previous = 0; + return VirtualProtect(address, static_cast(size), protection, &previous) != FALSE; +#else + return mprotect(address, static_cast(size), protection) == 0; +#endif +} + +#if defined(_WIN32) void ResolvePlacementApi() { if (g_virtualAlloc2 != nullptr && g_mapViewOfFile3 != nullptr) return; HMODULE kernelBase = GetModuleHandleW(L"kernelbase.dll"); @@ -153,9 +215,11 @@ void ResolvePlacementApi() { "are unavailable on this system)."); } } +#endif void EnsureReservation() { if (g_base != nullptr) return; +#if defined(_WIN32) ResolvePlacementApi(); void* requested = reinterpret_cast(kFixedFlatGuestBase); @@ -178,25 +242,59 @@ void EnsureReservation() { "usual cause."; throw std::runtime_error(oss.str()); } - if (reserved != requested) { throw std::runtime_error( "The flat guest reservation did not land on the fixed base the translated code was " "compiled against."); } +#else + void* requested = reinterpret_cast(kFixedFlatGuestBase); + + // No MAP_FIXED here (and deliberately no MAP_FIXED_NOREPLACE, which needs Linux 4.17+ - + // this must work on kernels as old as 4.9): `requested` is only a hint. The kernel's + // get_unmapped_area honors a page-aligned hint when the whole range is free, so this lands + // on the fixed base in the normal case; if anything already occupies part of the range, the + // kernel silently picks a different address instead of clobbering it, which the check below + // catches - same "something got there first" contract as the Windows path, without needing + // a specific kernel version. + void* reserved = mmap(requested, kGuestSpaceSize + kAllocationGranularity, kProtNone, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0); + if (reserved == MAP_FAILED) { + std::ostringstream oss; + oss << "Unable to reserve the 4 GiB flat guest address space at 0x" << std::hex + << reinterpret_cast(requested) << std::dec + << " (" << std::strerror(errno) + << "). The translated code addresses guest memory through this fixed base, so it " + "cannot fall back to another one."; + throw std::runtime_error(oss.str()); + } + if (reserved != requested) { + munmap(reserved, kGuestSpaceSize + kAllocationGranularity); + std::ostringstream oss; + oss << "Unable to reserve the 4 GiB flat guest address space at 0x" << std::hex + << reinterpret_cast(requested) << std::dec + << ". Something else in this process already occupies part of the 16 TiB region - " + "an injected library, an overlay or a debugging tool is the usual cause."; + throw std::runtime_error(oss.str()); + } +#endif + g_base = static_cast(reserved); } +#if defined(_WIN32) // Carves `size` bytes out of the enclosing placeholder so a view or a private // commit can replace it. Splitting an exact-size placeholder is a no-op that // reports ERROR_INVALID_PARAMETER; the caller validates the replacement. void SplitPlaceholder(uint8_t* address, uint64_t size) { VirtualFree(address, static_cast(size), MEM_RELEASE | kMemPreservePlaceholder); } +#endif void MapGuestView(const Section& section, uint64_t sectionOffset, uint32_t guestBase, uint64_t mappedSize) { uint8_t* target = g_base + guestBase; +#if defined(_WIN32) SplitPlaceholder(target, mappedSize); void* view = g_mapViewOfFile3(section.handle, GetCurrentProcess(), target, sectionOffset, static_cast(mappedSize), kMemReplacePlaceholder, @@ -208,16 +306,37 @@ void MapGuestView(const Section& section, uint64_t sectionOffset, uint32_t guest << ")"; throw std::runtime_error(oss.str()); } +#else + // MAP_FIXED is safe (and needs no particular kernel version) here specifically because we're + // deliberately overwriting a sub-range of the PROT_NONE reservation this module already owns + // exclusively (see EnsureReservation) - unlike the initial reservation itself, there's no + // "something else might already be there" concern to guard against. + void* view = mmap(target, static_cast(mappedSize), kProtReadWrite, + MAP_SHARED | MAP_FIXED, section.fd, static_cast(sectionOffset)); + if (view == MAP_FAILED) { + std::ostringstream oss; + oss << "Unable to map guest region 0x" << std::hex << guestBase << " (+0x" << mappedSize + << ") into the flat reservation" << std::dec << " (" << std::strerror(errno) << ")"; + throw std::runtime_error(oss.str()); + } +#endif } // Replaces a placeholder with private committed memory. Used for the MMIO // window (read-only zeros) and for on-demand commits of stray guest pages. -bool CommitPlaceholder(uint8_t* address, uint64_t size, DWORD protection) { +bool CommitPlaceholder(uint8_t* address, uint64_t size, ProtectionFlags protection) { +#if defined(_WIN32) SplitPlaceholder(address, size); void* result = g_virtualAlloc2(GetCurrentProcess(), address, static_cast(size), MEM_RESERVE | MEM_COMMIT | kMemReplacePlaceholder, protection, nullptr, 0); return result != nullptr; +#else + // No separate reserve-vs-commit step is needed: the anonymous PROT_NONE reservation this + // range came from is already demand-zero backed, so mprotect() alone both "commits" and + // protects it. + return ProtectRange(address, size, protection); +#endif } // One definition of the two windows lives in memory_access.h; these are the @@ -237,8 +356,7 @@ void ApplyExecutableProtectionLocked() { for (uint64_t page = first; page < last; page += kHostPageSize) { const uint32_t pageIndex = static_cast(page >> 12); if (protectedPages[pageIndex] != 0) continue; - DWORD previous = 0; - if (VirtualProtect(g_base + page, kHostPageSize, PAGE_READONLY, &previous) != FALSE) { + if (ProtectRange(g_base + page, kHostPageSize, kProtRead)) { protectedPages[pageIndex] = 1; } } @@ -284,8 +402,16 @@ void ZeroMappedStorage() { } } +#if defined(_WIN32) LONG CALLBACK FlatGuestVectoredHandler(EXCEPTION_POINTERS* info) { - if (HandleAccessViolation(info)) { + const auto* record = info->ExceptionRecord; + if (record == nullptr || record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION || + record->NumberParameters < 2) { + return EXCEPTION_CONTINUE_SEARCH; + } + void* faultAddress = reinterpret_cast(record->ExceptionInformation[1]); + const bool isWrite = record->ExceptionInformation[0] != 0; + if (HandleAccessViolation(faultAddress, isWrite)) { return EXCEPTION_CONTINUE_EXECUTION; } return EXCEPTION_CONTINUE_SEARCH; @@ -298,6 +424,7 @@ void InstallVectoredHandler() { throw std::runtime_error(LastErrorText("AddVectoredExceptionHandler")); } } +#endif void ReportFatalGuestFault(const char* category, uint32_t guestAddress, bool isWrite, const char* detail) { @@ -377,9 +504,7 @@ void Initialize(const std::vector& regions) { for (const auto& range : DeferredRanges()) { const uint64_t first = static_cast(range.start) & ~(kHostPageSize - 1u); const uint64_t last = RoundUp(range.end, kHostPageSize); - DWORD previous = 0; - VirtualProtect(g_base + first, static_cast(last - first), PAGE_READWRITE, - &previous); + ProtectRange(g_base + first, last - first, kProtReadWrite); } DeferredRanges().clear(); ZeroMappedStorage(); @@ -408,6 +533,7 @@ void Initialize(const std::vector& regions) { const uint64_t rounded = RoundUp(size, kAllocationGranularity); Section section; section.size = rounded; +#if defined(_WIN32) section.handle = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, static_cast(rounded >> 32), static_cast(rounded & 0xFFFFFFFFu), nullptr); @@ -419,6 +545,25 @@ void Initialize(const std::vector& regions) { if (section.hostView == nullptr) { throw std::runtime_error(LastErrorText("MapViewOfFile for the host guest-RAM alias")); } +#else + // The section is an anonymous shared-memory object: the SAME physical pages get mapped + // twice below (once here as the always-accessible host view, once per-region as the + // guest view whose protection the fault handler controls), the same "one backing store, + // two VA aliases" trick CreateFileMapping/MapViewOfFile(3) gives Windows. + section.fd = memfd_create("wiicompiled-guest-ram", MFD_CLOEXEC); + if (section.fd < 0) { + throw std::runtime_error(LastErrorText("memfd_create for guest RAM")); + } + if (ftruncate(section.fd, static_cast(rounded)) != 0) { + throw std::runtime_error(LastErrorText("ftruncate for guest RAM")); + } + section.hostView = static_cast( + mmap(nullptr, static_cast(rounded), kProtReadWrite, MAP_SHARED, section.fd, 0)); + if (section.hostView == MAP_FAILED) { + section.hostView = nullptr; + throw std::runtime_error(LastErrorText("mmap for the host guest-RAM alias")); + } +#endif Sections()[key] = section; } @@ -435,12 +580,14 @@ void Initialize(const std::vector& regions) { // MMIO stays inaccessible in both directions so the vectored handler can report missing HLE; the old // PAGE_READONLY read window that returned zero turned missing devices into silent hangs instead. - if (!CommitPlaceholder(g_base + 0xCC000000u, 0x02000000u, PAGE_NOACCESS)) { + if (!CommitPlaceholder(g_base + 0xCC000000u, 0x02000000u, kProtNone)) { throw std::runtime_error(LastErrorText("committing the no-access MMIO window")); } ApplyExecutableProtectionLocked(); +#if defined(_WIN32) InstallVectoredHandler(); +#endif // Freshly created section objects are demand-zero, so no explicit clear is // needed on the first mapping (that would fault in all 152 MiB at startup). @@ -470,9 +617,7 @@ void ProtectDeferredRange(uint32_t address, size_t length) { std::lock_guard lock(StateMutex()); const uint64_t first = static_cast(address) & ~(kHostPageSize - 1u); const uint64_t last = RoundUp(end, kHostPageSize); - DWORD previous = 0; - if (VirtualProtect(g_base + first, static_cast(last - first), PAGE_NOACCESS, - &previous) == FALSE) { + if (!ProtectRange(g_base + first, last - first, kProtNone)) { // An unmapped destination cannot be trapped; the checked path still // clears the readable bias, so nothing silently reads stale bytes. return; @@ -492,8 +637,7 @@ void UnprotectDeferredRange(uint32_t address, size_t length) { ranges.erase(it); const uint64_t first = static_cast(address) & ~(kHostPageSize - 1u); const uint64_t last = RoundUp(end, kHostPageSize); - DWORD previous = 0; - VirtualProtect(g_base + first, static_cast(last - first), PAGE_READWRITE, &previous); + ProtectRange(g_base + first, last - first, kProtReadWrite); } void RegisterExecutableRange(uint32_t start, uint32_t end) { @@ -541,21 +685,14 @@ void LogFaultSummary() noexcept { std::cerr.flush(); } -bool HandleAccessViolation(void* exceptionPointers) noexcept { - if (!g_initialized || exceptionPointers == nullptr) return false; - auto* info = static_cast(exceptionPointers); - const auto* record = info->ExceptionRecord; - if (record == nullptr || record->ExceptionCode != EXCEPTION_ACCESS_VIOLATION || - record->NumberParameters < 2) { - return false; - } +bool HandleAccessViolation(void* faultAddress, bool isWrite) noexcept { + if (!g_initialized || faultAddress == nullptr) return false; - const uintptr_t fault = static_cast(record->ExceptionInformation[1]); + const uintptr_t fault = reinterpret_cast(faultAddress); const uintptr_t base = reinterpret_cast(g_base); if (fault < base || fault - base >= kGuestSpaceSize) return false; const uint32_t guestAddress = static_cast(fault - base); - const bool isWrite = record->ExceptionInformation[0] != 0; // 1) Deferred (EFB) read: materialize the pending copy and drop the trap for the whole 4 KiB page span, // not just the registered range, since protection is page-granular. Leaving a range registered but @@ -581,9 +718,7 @@ bool HandleAccessViolation(void* exceptionPointers) noexcept { ranges.erase(it); spanFirst = static_cast(rangeStart) & ~(kHostPageSize - 1u); spanLast = RoundUp(rangeEnd, kHostPageSize); - DWORD previous = 0; - VirtualProtect(g_base + spanFirst, static_cast(spanLast - spanFirst), - PAGE_READWRITE, &previous); + ProtectRange(g_base + spanFirst, spanLast - spanFirst, kProtReadWrite); } } if (covered) { @@ -618,9 +753,8 @@ bool HandleAccessViolation(void* exceptionPointers) noexcept { // Those arrive in bulk, so the page is opened permanently // rather than trapping every relocation. std::lock_guard lock(StateMutex()); - DWORD previous = 0; - if (VirtualProtect(g_base + (static_cast(pageIndex) << 12), kHostPageSize, - PAGE_READWRITE, &previous) != FALSE) { + if (ProtectRange(g_base + (static_cast(pageIndex) << 12), kHostPageSize, + kProtReadWrite)) { ExecutableProtectedPages()[pageIndex] = 0; } } @@ -665,6 +799,7 @@ bool HandleAccessViolation(void* exceptionPointers) noexcept { bool committed = false; { std::lock_guard lock(StateMutex()); +#if defined(_WIN32) MEMORY_BASIC_INFORMATION mbi{}; if (VirtualQuery(g_base + blockBase, &mbi, sizeof(mbi)) == 0) return false; if (mbi.State == MEM_COMMIT) { @@ -678,9 +813,24 @@ bool HandleAccessViolation(void* exceptionPointers) noexcept { PAGE_EXECUTE)) != 0; return isWrite ? writable : readable; } - if (!CommitPlaceholder(g_base + blockBase, kAllocationGranularity, PAGE_READWRITE)) { +#else + // mprotect has no VirtualQuery counterpart to ask "is this block already committed and + // how", so this module tracks the same fact itself (UnmappedCommittedBlocks, checked and + // set under this same lock): once a block has been committed READ|WRITE by an earlier + // call here (this thread's or a racing one's), every subsequent fault on it is a no-op + // resume - there is no POSIX equivalent of "committed but insufficiently permissioned" + // for a block only this function ever touches. + const uint32_t blockIndex = static_cast(blockBase / kAllocationGranularity); + if (UnmappedCommittedBlocks()[blockIndex] != 0) { + return true; + } +#endif + if (!CommitPlaceholder(g_base + blockBase, kAllocationGranularity, kProtReadWrite)) { return false; } +#if !defined(_WIN32) + UnmappedCommittedBlocks()[blockIndex] = 1; +#endif committed = true; } if (committed) { diff --git a/runtime/src/hle/input/pad.cpp b/runtime/src/hle/input/pad.cpp index 68645d7..2a71351 100644 --- a/runtime/src/hle/input/pad.cpp +++ b/runtime/src/hle/input/pad.cpp @@ -1,7 +1,9 @@ #include "hle_stubs.h" #include "memory.h" #include "hle/controller_status_contract.h" +#ifdef _WIN32 #include "wup028_adapter.h" +#endif #include #include @@ -34,7 +36,9 @@ void WritePadStatus(uint32_t base, const PADStatus& status) { extern "C" uint32_t PAD__Init_HLE() { +#if defined(_WIN32) Wup028Adapter::Initialize(); +#endif return PADInit() ? 1u : 0u; } PPC_NATIVE_OVERRIDE(801AF2F0, PAD__Init_HLE, uint32_t, (), ()); @@ -46,8 +50,9 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr) } PADStatus statuses[PAD_CHANMAX]{}; - std::array adapterStatuses{}; uint32_t rumbleMask = PADRead(statuses); +#if defined(_WIN32) + std::array adapterStatuses{}; if (Wup028Adapter::Read(adapterStatuses) && !PADIsInputBlocked()) { for (uint32_t port = 0; port < PAD_CHANMAX; ++port) { if (adapterStatuses[port].err == PAD_ERR_NONE) { @@ -56,6 +61,7 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr) } } } +#endif try { for (uint32_t i = 0; i < PAD_CHANMAX; ++i) { @@ -84,8 +90,12 @@ PPC_NATIVE_OVERRIDE(801AF1E4, PAD__Recalibrate_HLE, uint32_t, (uint32_t mask), ( extern "C" void PAD__ControlMotor_HLE(int32_t chan, uint32_t command) { +#if defined(_WIN32) if (!Wup028Adapter::SetRumble(static_cast(chan), command == PAD_MOTOR_RUMBLE)) { PADControlMotor(chan, command); } +#else + PADControlMotor(chan, command); +#endif } PPC_NATIVE_OVERRIDE_VOID(801AF908, PAD__ControlMotor_HLE, (int32_t chan, uint32_t command), (chan, command)); diff --git a/runtime/src/hle/storage/dvd.cpp b/runtime/src/hle/storage/dvd.cpp index 03c815c..1acf239 100644 --- a/runtime/src/hle/storage/dvd.cpp +++ b/runtime/src/hle/storage/dvd.cpp @@ -635,7 +635,8 @@ static void BuildAndPublishRuntimeFst() { for (const DVDFileEntry& entry : g_fileEntries) { if (entry.discOffsetWords != 0) { nextFreeBytes = std::max( - nextFreeBytes, static_cast(entry.discOffsetWords) * 4ull + entry.size); + nextFreeBytes, static_cast(entry.discOffsetWords) * UINT64_C(4) + + static_cast(entry.size)); } } for (DVDFileEntry& entry : g_fileEntries) { diff --git a/runtime/src/host_cpu_baseline.cpp b/runtime/src/host_cpu_baseline.cpp index 7271f7e..9d8157e 100644 --- a/runtime/src/host_cpu_baseline.cpp +++ b/runtime/src/host_cpu_baseline.cpp @@ -10,7 +10,11 @@ #include #include +#if defined(_WIN32) #include +#else +#include +#endif namespace { @@ -137,7 +141,12 @@ bool CollectMissingBaselineFeatures(TextBuffer& missing) { // up yet, and fprintf(stderr, ...) faults there. Verified on this toolchain: // WriteFile on the raw standard-error handle and MessageBoxA both work, printf // does not. Anything added to this reporting path has to respect that. +// +// The POSIX path runs from an __attribute__((constructor)) instead, ahead of libc's own startup +// guarantees; ::write() on the raw fd is the same kind of allocation-free, libc-init-independent +// primitive as WriteFile is on Windows, so the same restriction is honored here. void WriteStdErrEarly(const char* text) { +#if defined(_WIN32) const HANDLE handle = ::GetStdHandle(STD_ERROR_HANDLE); if (handle == nullptr || handle == INVALID_HANDLE_VALUE) { return; @@ -148,6 +157,13 @@ void WriteStdErrEarly(const char* text) { } DWORD written = 0; ::WriteFile(handle, text, static_cast(length), &written, nullptr); +#else + size_t length = 0; + while (text[length] != '\0') { + ++length; + } + (void)::write(STDERR_FILENO, text, length); +#endif } [[noreturn]] void ReportUnsupportedCpu(const char* missing) { @@ -168,12 +184,18 @@ void WriteStdErrEarly(const char* text) { WriteStdErrEarly(message.data); WriteStdErrEarly("\n"); +#if defined(_WIN32) ::MessageBoxA(nullptr, message.data, "WiiCompiled - Unsupported Processor", MB_OK | MB_ICONERROR | MB_SETFOREGROUND | MB_TASKMODAL); // Leave through the OS rather than exit(): the C++ dynamic initializers // have not run yet, so there is no constructed program state to unwind and // the teardown path itself lives in AVX2 translation units. ::ExitProcess(1u); +#else + // Same reasoning as the Windows path above: no C++ dynamic initializer has run yet, so + // _exit() (skips atexit/global destructors, unlike exit()) is the correct way out. + ::_exit(1); +#endif } } // namespace diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 0378645..84d7a44 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -37,6 +37,8 @@ #include #include #else +#include +#include #include #endif @@ -988,7 +990,12 @@ LONG CALLBACK SehLogger(EXCEPTION_POINTERS* info) { // flat module registers its own handler first, but registration order is // not guaranteed once another VEH is installed later, so consult it here // too - resolving a fault twice is a no-op. - if (GuestFlat::HandleAccessViolation(info)) { + if (info->ExceptionRecord != nullptr && + info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && + info->ExceptionRecord->NumberParameters >= 2 && + GuestFlat::HandleAccessViolation( + reinterpret_cast(info->ExceptionRecord->ExceptionInformation[1]), + info->ExceptionRecord->ExceptionInformation[0] != 0)) { return EXCEPTION_CONTINUE_EXECUTION; } if (g_suppressSehReporting && g_sehJumpTarget) { @@ -1059,6 +1066,102 @@ void InstallSehLogger() { g_vectoredSehHandle = AddVectoredExceptionHandler(1, SehLogger); } } +#else +// POSIX counterpart to SehLogger above. Unlike Windows' AddVectoredExceptionHandler, which lets +// GuestFlat and this module each install their own handler and defensively re-check each other, +// sigaction only allows one handler per signal - the second registration replaces the first +// instead of chaining. So this is the single SIGSEGV/SIGBUS handler for the whole process, and it +// owns checking GuestFlat's fault-interception logic first, exactly mirroring the order SehLogger +// already uses on Windows. +void ReportUnhandledSignalFault(int sig, void* faultAddress) { + RT_LOG(RT_TAG_RUNTIME) << "Signal " << sig << " (fault address 0x" << std::hex + << reinterpret_cast(faultAddress) << std::dec << ")"; + if (!g_lastEntryLabel.empty()) { + std::cerr << " while executing " << g_lastEntryLabel; + } + std::cerr << std::endl; + if (const CpuContext* cpu = TryGetCpuContext()) { + RT_LOG(RT_TAG_RUNTIME) << "===== DUMPING CPU STATE =====" << std::endl; + SystemBridge::DumpCpuState(cpu); + } + std::cerr.flush(); +} + +void PosixMemoryFaultHandler(int sig, siginfo_t* info, void* ucontextVoid) { + void* faultAddress = info != nullptr ? info->si_addr : nullptr; + bool isWrite = false; +#if defined(__x86_64__) + // Standard glibc technique for a POSIX fastmem-style handler: bit 1 (0x2) of the hardware + // error code x86 pushes on a page fault records whether it was a write. + if (ucontextVoid != nullptr) { + auto* uc = static_cast(ucontextVoid); + isWrite = (uc->uc_mcontext.gregs[REG_ERR] & 0x2) != 0; + } +#endif + + // Guest-space faults are the flat memory interception mechanism (MMIO, deferred EFB reads, + // the executable-write guard, unmapped pages). Resolving one here means resuming the + // faulting instruction, which just returning from the handler does. + if (faultAddress != nullptr && GuestFlat::HandleAccessViolation(faultAddress, isWrite)) { + return; + } + + if (g_suppressSehReporting && g_sehJumpTarget) { + g_sehLastExceptionCode = static_cast(sig); + g_sehLastExceptionAddress = reinterpret_cast(faultAddress); + g_sehLastAccessType = isWrite ? 1u : 0u; + g_sehLastAccessedAddress = reinterpret_cast(faultAddress); + siglongjmp(*g_sehJumpTarget, 1); + } + if (g_suppressSehReporting) { + // Reporting suppressed but nobody armed a recovery jump: restore the default disposition + // and re-raise so the process still terminates, instead of returning into the same fault. + signal(sig, SIG_DFL); + raise(sig); + return; + } + + // Guard against re-entrancy: if we crash while reporting, don't recurse. + static std::atomic_flag s_inCrashHandler = ATOMIC_FLAG_INIT; + if (s_inCrashHandler.test_and_set()) { + std::_Exit(EXIT_FAILURE); + } + + ReportUnhandledSignalFault(sig, faultAddress); + std::ostringstream popupDetails; + popupDetails << "A native signal (" << sig << ") occurred"; + if (!g_lastEntryLabel.empty()) { + popupDetails << " while executing " << g_lastEntryLabel; + } + if (faultAddress != nullptr) { + popupDetails << ".\n\nThe game attempted a " << (isWrite ? "write" : "read") + << " at host address 0x" << std::hex + << reinterpret_cast(faultAddress) << std::dec; + } + popupDetails << ".\n\nThe process transcript and crash log contain the full CPU and stack " + "diagnostics."; + ShowRuntimeFatalPopup("a native crash occurred", popupDetails.str()); + DumpHostStackTrace(); + WriteFatalLogImpl(sig == SIGBUS ? "sigbus" : "sigsegv"); + + std::cerr.flush(); + std::cout.flush(); + std::fflush(stdout); + std::fflush(stderr); + std::_Exit(EXIT_FAILURE); +} + +void InstallPosixMemoryFaultHandler() { + struct sigaction action {}; + action.sa_sigaction = PosixMemoryFaultHandler; + action.sa_flags = SA_SIGINFO; + sigemptyset(&action.sa_mask); + sigaction(SIGSEGV, &action, nullptr); + // A touch beyond a memfd-backed mapping's ftruncate()'d size raises SIGBUS rather than + // SIGSEGV on Linux; region sizing should make this unreachable, but routing it to the same + // handler costs nothing and avoids a silent gap if it ever isn't. + sigaction(SIGBUS, &action, nullptr); +} #endif void AbortSignalHandler(int signum) { @@ -1174,6 +1277,8 @@ int RuntimeMain(int argc, char** argv) { ConfigureWindowsFatalDialogBehavior(); InstallSehLogger(); WindowsTimerResolutionGuard timerResolutionGuard; +#else + InstallPosixMemoryFaultHandler(); #endif InitializeProcessTranscript(argc, argv); std::signal(SIGABRT, AbortSignalHandler); @@ -1272,7 +1377,9 @@ int RuntimeMain(int argc, char** argv) { } aurora_set_frame_worker_wait_callback(ServiceGuestTimingDuringAuroraFrameWait); GxGuestWrite::InstallAuroraHooks(); +#if defined(_WIN32) Wup028Adapter::Initialize(); +#endif UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width, auroraInfo.windowSize.native_fb_height); settings_overlay::InitializeRuntimeSettings(); @@ -1314,7 +1421,9 @@ int RuntimeMain(int argc, char** argv) { // Shutdown fiber system Fiber::GuestFiberManager::Shutdown(); WindowPlacementPersistence::Flush(true); +#if defined(_WIN32) Wup028Adapter::Shutdown(); +#endif aurora_shutdown(); SetRuntimeExitCodeImpl(0); ShutdownProcessTranscript(); @@ -1332,7 +1441,9 @@ int RuntimeMain(int argc, char** argv) { SetRuntimeExitCodeImpl(1); Fiber::GuestFiberManager::Shutdown(); WindowPlacementPersistence::Flush(true); +#if defined(_WIN32) Wup028Adapter::Shutdown(); +#endif aurora_shutdown(); ShutdownProcessTranscript(); return 1; @@ -1344,7 +1455,9 @@ int RuntimeMain(int argc, char** argv) { SetRuntimeExitCodeImpl(1); Fiber::GuestFiberManager::Shutdown(); WindowPlacementPersistence::Flush(true); +#if defined(_WIN32) Wup028Adapter::Shutdown(); +#endif aurora_shutdown(); ShutdownProcessTranscript(); return 1; diff --git a/runtime/src/memory.cpp b/runtime/src/memory.cpp index 63f5825..88a484e 100644 --- a/runtime/src/memory.cpp +++ b/runtime/src/memory.cpp @@ -20,11 +20,13 @@ #include #include +#if defined(_WIN32) #ifndef NOMINMAX #define NOMINMAX #endif #include #include +#endif MemoryInline::PageEntry MemoryInline::g_pageTable[MemoryInline::kPageCount]{}; uintptr_t MemoryInline::g_fullPageBias[MemoryInline::kPageCount]{}; diff --git a/runtime/src/settings_overlay.cpp b/runtime/src/settings_overlay.cpp index fcf3389..cb2497c 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -1,5 +1,7 @@ #include "settings_overlay.h" +#ifdef _WIN32 #include "wup028_adapter.h" +#endif #include "audio_backend.h" #include "controller_mapping_wizard.h" #include "game_graphics_options.h" @@ -313,6 +315,12 @@ void ApplyConfiguredMappings() { } void DrawGameCubeAdapterInfo() { + // The official GameCube adapter is Windows-only (see wup028_adapter.cpp); on Linux/macOS, + // Wup028Adapter is a permanently-disconnected stub, and SDL3 already exposes the same + // hardware as a normal joystick, so this menu would only ever show "Searching" and four + // perpetually-empty adapter ports - confusing clutter for a feature that can't do anything + // on this platform. Skip it entirely rather than render a menu that never has content. +#if defined(_WIN32) ImGui::Separator(); if (!ImGui::BeginMenu("GameCube adapter info")) return; @@ -338,6 +346,7 @@ void DrawGameCubeAdapterInfo() { } } ImGui::EndMenu(); +#endif } void DrawControllerSettings() { @@ -351,13 +360,20 @@ void DrawControllerSettings() { ImGui::Separator(); const uint32_t selectedGamePort = static_cast(g_controllerPort); +#if defined(_WIN32) const int adapterAssignment = Wup028Adapter::GetPortAssignment(selectedGamePort); if (adapterAssignment >= 0) { ImGui::Text("Assigned: GameCube adapter port %d", adapterAssignment + 1); - } else { + } else +#endif + { const char* currentName = PADGetName(selectedGamePort); ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None"); } +#if defined(_WIN32) + // Windows-only, same reasoning as DrawGameCubeAdapterInfo() above: on other platforms + // adapterAssignment is always -1 and every port would always read "(empty)", so this submenu + // would never have anything real to offer. if (ImGui::BeginMenu("Assign GameCube adapter port")) { if (ImGui::MenuItem("None", nullptr, adapterAssignment < 0)) { Wup028Adapter::SetPortAssignment(selectedGamePort, -1); @@ -381,10 +397,13 @@ void DrawControllerSettings() { } ImGui::EndMenu(); } +#endif if (ImGui::MenuItem("Unassign controller")) { PADClearPort(selectedGamePort); +#if defined(_WIN32) Wup028Adapter::SetPortAssignment(selectedGamePort, -1); RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1); +#endif g_configuredControllerIndices.fill(std::numeric_limits::min()); } ImGui::Separator(); @@ -392,7 +411,9 @@ void DrawControllerSettings() { const uint32_t controllerCount = PADCount(); if (controllerCount == 0) { ImGui::TextDisabled("No controller connected"); +#if defined(_WIN32) DrawGameCubeAdapterInfo(); +#endif return; } @@ -401,8 +422,10 @@ void DrawControllerSettings() { const char* name = PADGetNameForControllerIndex(index); ImGui::PushID(static_cast(index)); if (ImGui::MenuItem(name != nullptr ? name : "Unknown controller")) { +#if defined(_WIN32) Wup028Adapter::SetPortAssignment(selectedGamePort, -1); RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1); +#endif PADSetPortForIndex(index, selectedGamePort); g_configuredControllerIndices.fill(std::numeric_limits::min()); ApplyConfiguredMappings(); @@ -416,7 +439,9 @@ void DrawControllerSettings() { PADButtonMapping* mappings = PADGetButtonMappings(static_cast(g_controllerPort), &mappingCount); if (mappings == nullptr || mappingCount != PAD_BUTTON_COUNT) { ImGui::TextDisabled("Assign a controller to edit its buttons"); +#if defined(_WIN32) DrawGameCubeAdapterInfo(); +#endif return; } @@ -556,7 +581,9 @@ void DrawControllerSettings() { ImGui::TextUnformatted(kControllerButtons[i].label); ImGui::PopID(); } +#if defined(_WIN32) DrawGameCubeAdapterInfo(); +#endif } void DrawAudioSettings() { diff --git a/runtime/src/system_bridge.cpp b/runtime/src/system_bridge.cpp index 3d0cb45..8b07d12 100644 --- a/runtime/src/system_bridge.cpp +++ b/runtime/src/system_bridge.cpp @@ -9,10 +9,12 @@ #include #include +#if defined(_WIN32) #ifndef NOMINMAX #define NOMINMAX #endif #include +#endif #include "abi_bridge.h" @@ -26,7 +28,7 @@ // Global flag to suppress SEH reporting during static constructor execution bool g_suppressSehReporting = false; -thread_local jmp_buf* g_sehJumpTarget = nullptr; +thread_local MkwJmpBuf* g_sehJumpTarget = nullptr; thread_local uint32_t g_sehLastExceptionCode = 0; thread_local uintptr_t g_sehLastExceptionAddress = 0; thread_local uintptr_t g_sehLastAccessedAddress = 0; @@ -347,9 +349,9 @@ void SystemBridge::Initialize() { if (funcAddr == 0 || funcAddr == 0xFFFFFFFF) continue; if (TranslatedFunctionRegistry::FindByAddressPtr(funcAddr)) { - jmp_buf jumpBuf; + MkwJmpBuf jumpBuf; g_sehJumpTarget = &jumpBuf; - if (setjmp(jumpBuf) == 0) { + if (MKW_SETJMP(jumpBuf) == 0) { cpu.gpr[1] = 0x81700000u; InvokeIndirectCpu(funcAddr, &cpu); dolCount++; @@ -387,9 +389,9 @@ void SystemBridge::Initialize() { if (funcAddr == 0 || funcAddr == 0xFFFFFFFF) continue; if (TranslatedFunctionRegistry::FindByAddressPtr(funcAddr)) { - jmp_buf jumpBuf; + MkwJmpBuf jumpBuf; g_sehJumpTarget = &jumpBuf; - if (setjmp(jumpBuf) == 0) { + if (MKW_SETJMP(jumpBuf) == 0) { cpu.gpr[1] = 0x81700000u; InvokeIndirectCpu(funcAddr, &cpu); count++; diff --git a/runtime/third_party/libco/LICENSE b/runtime/third_party/libco/LICENSE new file mode 100644 index 0000000..2e2e5db --- /dev/null +++ b/runtime/third_party/libco/LICENSE @@ -0,0 +1,9 @@ +ISC License (ISC) + +Copyright byuu and the higan team + +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +The above applies to all files in this project except valgrind.h which is licensed under a BSD-style license. See the license text and copyright notice contained within that file. diff --git a/runtime/third_party/libco/aarch64.c b/runtime/third_party/libco/aarch64.c new file mode 100644 index 0000000..5c46a48 --- /dev/null +++ b/runtime/third_party/libco/aarch64.c @@ -0,0 +1,112 @@ +#define LIBCO_C +#include "libco.h" +#include "settings.h" + +#include +#ifdef LIBCO_MPROTECT + #include + #include +#endif + +#include "valgrind.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local unsigned long co_active_buffer[64]; +static thread_local cothread_t co_active_handle = 0; +static void (*co_swap)(cothread_t, cothread_t) = 0; + +#ifdef LIBCO_MPROTECT + alignas(4096) +#else + section(text) +#endif +static const uint32_t co_swap_function[1024] = { + 0x910003f0, /* mov x16,sp */ + 0xa9007830, /* stp x16,x30,[x1] */ + 0xa9407810, /* ldp x16,x30,[x0] */ + 0x9100021f, /* mov sp,x16 */ + 0xa9015033, /* stp x19,x20,[x1, 16] */ + 0xa9415013, /* ldp x19,x20,[x0, 16] */ + 0xa9025835, /* stp x21,x22,[x1, 32] */ + 0xa9425815, /* ldp x21,x22,[x0, 32] */ + 0xa9036037, /* stp x23,x24,[x1, 48] */ + 0xa9436017, /* ldp x23,x24,[x0, 48] */ + 0xa9046839, /* stp x25,x26,[x1, 64] */ + 0xa9446819, /* ldp x25,x26,[x0, 64] */ + 0xa905703b, /* stp x27,x28,[x1, 80] */ + 0xa945701b, /* ldp x27,x28,[x0, 80] */ + 0xf900303d, /* str x29, [x1, 96] */ + 0xf940301d, /* ldr x29, [x0, 96] */ + 0x6d072428, /* stp d8, d9, [x1,112] */ + 0x6d472408, /* ldp d8, d9, [x0,112] */ + 0x6d082c2a, /* stp d10,d11,[x1,128] */ + 0x6d482c0a, /* ldp d10,d11,[x0,128] */ + 0x6d09342c, /* stp d12,d13,[x1,144] */ + 0x6d49340c, /* ldp d12,d13,[x0,144] */ + 0x6d0a3c2e, /* stp d14,d15,[x1,160] */ + 0x6d4a3c0e, /* ldp d14,d15,[x0,160] */ + 0xd61f03c0, /* br x30 */ +}; + +static void co_init(void) { + #ifdef LIBCO_MPROTECT + unsigned long addr = (unsigned long)co_swap_function; + unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE)); + unsigned long size = (addr - base) + sizeof co_swap_function; + mprotect((void*)base, size, PROT_READ | PROT_EXEC); + #endif +} + +cothread_t co_active(void) { + if(!co_active_handle) co_active_handle = &co_active_buffer; + return co_active_handle; +} + +cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) { + unsigned long* handle; + if(!co_swap) { + co_init(); + co_swap = (void (*)(cothread_t, cothread_t))co_swap_function; + } + if(!co_active_handle) co_active_handle = &co_active_buffer; + + VALGRIND_STACK_REGISTER(memory, memory + size); + + if((handle = (unsigned long*)memory)) { + unsigned long stack_top = (unsigned long)handle + size; + unsigned long *p; + stack_top &= ~((unsigned long) 15); + p = (unsigned long*)(stack_top); + handle[0] = (unsigned long)p; /* x16 (stack pointer) */ + handle[1] = (unsigned long)entrypoint; /* x30 (link register) */ + handle[12] = (unsigned long)p; /* x29 (frame pointer) */ + } + + return handle; +} + +cothread_t co_create(unsigned int size, void (*entrypoint)(void)) { + void* memory = LIBCO_MALLOC(size); + if(!memory) return (cothread_t)0; + return co_derive(memory, size, entrypoint); +} + +void co_delete(cothread_t handle) { + LIBCO_FREE(handle); +} + +void co_switch(cothread_t handle) { + cothread_t co_previous_handle = co_active_handle; + co_swap(co_active_handle = handle, co_previous_handle); +} + +int co_serializable(void) { + return 1; +} + +#ifdef __cplusplus +} +#endif diff --git a/runtime/third_party/libco/amd64.c b/runtime/third_party/libco/amd64.c new file mode 100644 index 0000000..9fc8720 --- /dev/null +++ b/runtime/third_party/libco/amd64.c @@ -0,0 +1,175 @@ +#define LIBCO_C +#include "libco.h" +#include "settings.h" + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local long long co_active_buffer[64]; +static thread_local cothread_t co_active_handle = 0; +static void (*co_swap)(cothread_t, cothread_t) = 0; + +#ifdef LIBCO_MPROTECT + alignas(4096) +#else + section(text) +#endif +#ifdef _WIN32 + /* ABI: Win64 */ + static const unsigned char co_swap_function[4096] = { + 0x48, 0x89, 0x22, /* mov [rdx],rsp */ + 0x48, 0x8b, 0x21, /* mov rsp,[rcx] */ + 0x58, /* pop rax */ + 0x48, 0x89, 0x6a, 0x08, /* mov [rdx+ 8],rbp */ + 0x48, 0x89, 0x72, 0x10, /* mov [rdx+16],rsi */ + 0x48, 0x89, 0x7a, 0x18, /* mov [rdx+24],rdi */ + 0x48, 0x89, 0x5a, 0x20, /* mov [rdx+32],rbx */ + 0x4c, 0x89, 0x62, 0x28, /* mov [rdx+40],r12 */ + 0x4c, 0x89, 0x6a, 0x30, /* mov [rdx+48],r13 */ + 0x4c, 0x89, 0x72, 0x38, /* mov [rdx+56],r14 */ + 0x4c, 0x89, 0x7a, 0x40, /* mov [rdx+64],r15 */ + #if !defined(LIBCO_NO_SSE) + 0x0f, 0x29, 0x72, 0x50, /* movaps [rdx+ 80],xmm6 */ + 0x0f, 0x29, 0x7a, 0x60, /* movaps [rdx+ 96],xmm7 */ + 0x44, 0x0f, 0x29, 0x42, 0x70, /* movaps [rdx+112],xmm8 */ + 0x48, 0x83, 0xc2, 0x70, /* add rdx,112 */ + 0x44, 0x0f, 0x29, 0x4a, 0x10, /* movaps [rdx+ 16],xmm9 */ + 0x44, 0x0f, 0x29, 0x52, 0x20, /* movaps [rdx+ 32],xmm10 */ + 0x44, 0x0f, 0x29, 0x5a, 0x30, /* movaps [rdx+ 48],xmm11 */ + 0x44, 0x0f, 0x29, 0x62, 0x40, /* movaps [rdx+ 64],xmm12 */ + 0x44, 0x0f, 0x29, 0x6a, 0x50, /* movaps [rdx+ 80],xmm13 */ + 0x44, 0x0f, 0x29, 0x72, 0x60, /* movaps [rdx+ 96],xmm14 */ + 0x44, 0x0f, 0x29, 0x7a, 0x70, /* movaps [rdx+112],xmm15 */ + #endif + 0x48, 0x8b, 0x69, 0x08, /* mov rbp,[rcx+ 8] */ + 0x48, 0x8b, 0x71, 0x10, /* mov rsi,[rcx+16] */ + 0x48, 0x8b, 0x79, 0x18, /* mov rdi,[rcx+24] */ + 0x48, 0x8b, 0x59, 0x20, /* mov rbx,[rcx+32] */ + 0x4c, 0x8b, 0x61, 0x28, /* mov r12,[rcx+40] */ + 0x4c, 0x8b, 0x69, 0x30, /* mov r13,[rcx+48] */ + 0x4c, 0x8b, 0x71, 0x38, /* mov r14,[rcx+56] */ + 0x4c, 0x8b, 0x79, 0x40, /* mov r15,[rcx+64] */ + #if !defined(LIBCO_NO_SSE) + 0x0f, 0x28, 0x71, 0x50, /* movaps xmm6, [rcx+ 80] */ + 0x0f, 0x28, 0x79, 0x60, /* movaps xmm7, [rcx+ 96] */ + 0x44, 0x0f, 0x28, 0x41, 0x70, /* movaps xmm8, [rcx+112] */ + 0x48, 0x83, 0xc1, 0x70, /* add rcx,112 */ + 0x44, 0x0f, 0x28, 0x49, 0x10, /* movaps xmm9, [rcx+ 16] */ + 0x44, 0x0f, 0x28, 0x51, 0x20, /* movaps xmm10,[rcx+ 32] */ + 0x44, 0x0f, 0x28, 0x59, 0x30, /* movaps xmm11,[rcx+ 48] */ + 0x44, 0x0f, 0x28, 0x61, 0x40, /* movaps xmm12,[rcx+ 64] */ + 0x44, 0x0f, 0x28, 0x69, 0x50, /* movaps xmm13,[rcx+ 80] */ + 0x44, 0x0f, 0x28, 0x71, 0x60, /* movaps xmm14,[rcx+ 96] */ + 0x44, 0x0f, 0x28, 0x79, 0x70, /* movaps xmm15,[rcx+112] */ + #endif + 0xff, 0xe0, /* jmp rax */ + }; + + /* Valgrind is available on MINGW but not on MSVC. */ + #if defined(__GNUC__) + #include "valgrind.h" + #endif + + #include + + static void co_init(void) { + #ifdef LIBCO_MPROTECT + DWORD old_privileges; + VirtualProtect((void*)co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READ, &old_privileges); + #endif + } +#else + /* ABI: SystemV */ + static const unsigned char co_swap_function[4096] = { + 0x48, 0x89, 0x26, /* mov [rsi],rsp */ + 0x48, 0x8b, 0x27, /* mov rsp,[rdi] */ + 0x58, /* pop rax */ + 0x48, 0x89, 0x6e, 0x08, /* mov [rsi+ 8],rbp */ + 0x48, 0x89, 0x5e, 0x10, /* mov [rsi+16],rbx */ + 0x4c, 0x89, 0x66, 0x18, /* mov [rsi+24],r12 */ + 0x4c, 0x89, 0x6e, 0x20, /* mov [rsi+32],r13 */ + 0x4c, 0x89, 0x76, 0x28, /* mov [rsi+40],r14 */ + 0x4c, 0x89, 0x7e, 0x30, /* mov [rsi+48],r15 */ + 0x48, 0x8b, 0x6f, 0x08, /* mov rbp,[rdi+ 8] */ + 0x48, 0x8b, 0x5f, 0x10, /* mov rbx,[rdi+16] */ + 0x4c, 0x8b, 0x67, 0x18, /* mov r12,[rdi+24] */ + 0x4c, 0x8b, 0x6f, 0x20, /* mov r13,[rdi+32] */ + 0x4c, 0x8b, 0x77, 0x28, /* mov r14,[rdi+40] */ + 0x4c, 0x8b, 0x7f, 0x30, /* mov r15,[rdi+48] */ + 0xff, 0xe0, /* jmp rax */ + }; + + #include "valgrind.h" + #ifdef LIBCO_MPROTECT + #include + #include + #endif + + static void co_init(void) { + #ifdef LIBCO_MPROTECT + unsigned long long addr = (unsigned long long)co_swap_function; + unsigned long long base = addr - (addr % sysconf(_SC_PAGESIZE)); + unsigned long long size = (addr - base) + sizeof co_swap_function; + mprotect((void*)base, size, PROT_READ | PROT_EXEC); + #endif + } +#endif + +static void crash(void) { + LIBCO_ASSERT(0); /* called only if cothread_t entrypoint returns */ +} + +cothread_t co_active(void) { + if(!co_active_handle) co_active_handle = &co_active_buffer; + return co_active_handle; +} + +cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) { + cothread_t handle; + if(!co_swap) { + co_init(); + co_swap = (void (*)(cothread_t, cothread_t))co_swap_function; + } + if(!co_active_handle) co_active_handle = &co_active_buffer; + + #if defined(__VALGRIND_MAJOR__) + VALGRIND_STACK_REGISTER(memory, memory + size); + #endif + + if((handle = (cothread_t)memory)) { + unsigned long long stack_top = (unsigned long long)handle + size; + long long *p; + stack_top -= 32; + stack_top &= ~((unsigned long long) 15); + p = (long long*)(stack_top); /* seek to top of stack */ + *--p = (long long)crash; /* crash if entrypoint returns */ + *--p = (long long)entrypoint; /* start of function */ + *(long long*)handle = (long long)p; /* stack pointer */ + } + + return handle; +} + +cothread_t co_create(unsigned int size, void (*entrypoint)(void)) { + void* memory = LIBCO_MALLOC(size); + if(!memory) return (cothread_t)0; + return co_derive(memory, size, entrypoint); +} + +void co_delete(cothread_t handle) { + LIBCO_FREE(handle); +} + +void co_switch(cothread_t handle) { + register cothread_t co_previous_handle = co_active_handle; + co_swap(co_active_handle = handle, co_previous_handle); +} + +int co_serializable(void) { + return 1; +} + +#ifdef __cplusplus +} +#endif diff --git a/runtime/third_party/libco/arm.c b/runtime/third_party/libco/arm.c new file mode 100644 index 0000000..80105e8 --- /dev/null +++ b/runtime/third_party/libco/arm.c @@ -0,0 +1,87 @@ +#define LIBCO_C +#include "libco.h" +#include "settings.h" +#include "valgrind.h" + +#ifdef LIBCO_MPROTECT + #include + #include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local unsigned long co_active_buffer[64]; +static thread_local cothread_t co_active_handle = 0; +static void (*co_swap)(cothread_t, cothread_t) = 0; + +#ifdef LIBCO_MPROTECT + alignas(4096) +#else + section(text) +#endif +static const unsigned long co_swap_function[1024] = { + 0xe8a16ff0, /* stmia r1!, {r4-r11,sp,lr} */ + 0xe8b0aff0, /* ldmia r0!, {r4-r11,sp,pc} */ + 0xe12fff1e, /* bx lr */ +}; + +static void co_init(void) { + #ifdef LIBCO_MPROTECT + unsigned long addr = (unsigned long)co_swap_function; + unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE)); + unsigned long size = (addr - base) + sizeof co_swap_function; + mprotect((void*)base, size, PROT_READ | PROT_EXEC); + #endif +} + +cothread_t co_active(void) { + if(!co_active_handle) co_active_handle = &co_active_buffer; + return co_active_handle; +} + +cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) { + unsigned long* handle; + if(!co_swap) { + co_init(); + co_swap = (void (*)(cothread_t, cothread_t))co_swap_function; + } + if(!co_active_handle) co_active_handle = &co_active_buffer; + + VALGRIND_STACK_REGISTER(memory, memory + size); + + if((handle = (unsigned long*)memory)) { + unsigned long stack_top = (unsigned long)handle + size; + unsigned long *p; + stack_top &= ~((unsigned long) 15); + p = (unsigned long*)(stack_top); + handle[8] = (unsigned long)p; + handle[9] = (unsigned long)entrypoint; + } + + return handle; +} + +cothread_t co_create(unsigned int size, void (*entrypoint)(void)) { + void* memory = LIBCO_MALLOC(size); + if(!memory) return (cothread_t)0; + return co_derive(memory, size, entrypoint); +} + +void co_delete(cothread_t handle) { + LIBCO_FREE(handle); +} + +void co_switch(cothread_t handle) { + cothread_t co_previous_handle = co_active_handle; + co_swap(co_active_handle = handle, co_previous_handle); +} + +int co_serializable(void) { + return 1; +} + +#ifdef __cplusplus +} +#endif diff --git a/runtime/third_party/libco/fiber.c b/runtime/third_party/libco/fiber.c new file mode 100644 index 0000000..7156a56 --- /dev/null +++ b/runtime/third_party/libco/fiber.c @@ -0,0 +1,55 @@ +#define LIBCO_C +#include "libco.h" +#include "settings.h" + +#define WINVER 0x0400 +#define _WIN32_WINNT 0x0400 +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local cothread_t co_active_ = 0; + +static void __stdcall co_thunk(void* coentry) { + ((void (*)(void))coentry)(); +} + +cothread_t co_active(void) { + if(!co_active_) { + ConvertThreadToFiber(0); + co_active_ = GetCurrentFiber(); + } + return co_active_; +} + +cothread_t co_derive(void* memory, unsigned int heapsize, void (*coentry)(void)) { + /* Windows fibers do not allow users to supply their own memory */ + return (cothread_t)0; +} + +cothread_t co_create(unsigned int heapsize, void (*coentry)(void)) { + if(!co_active_) { + ConvertThreadToFiber(0); + co_active_ = GetCurrentFiber(); + } + return (cothread_t)CreateFiber(heapsize, co_thunk, (void*)coentry); +} + +void co_delete(cothread_t cothread) { + DeleteFiber(cothread); +} + +void co_switch(cothread_t cothread) { + co_active_ = cothread; + SwitchToFiber(cothread); +} + +int co_serializable(void) { + return 0; +} + +#ifdef __cplusplus +} +#endif diff --git a/runtime/third_party/libco/libco.c b/runtime/third_party/libco/libco.c new file mode 100644 index 0000000..21fe4ca --- /dev/null +++ b/runtime/third_party/libco/libco.c @@ -0,0 +1,37 @@ +#if defined(__clang__) + #pragma clang diagnostic ignored "-Wparentheses" + + /* placing code in section(text) does not mark it executable with Clang. */ + #undef LIBCO_MPROTECT + #define LIBCO_MPROTECT +#endif + +#if defined(__clang__) || defined(__GNUC__) + #if defined(__i386__) + #include "x86.c" + #elif defined(__amd64__) + #include "amd64.c" + #elif defined(__arm__) + #include "arm.c" + #elif defined(__aarch64__) + #include "aarch64.c" + #elif defined(__powerpc64__) && defined(_CALL_ELF) && _CALL_ELF == 2 + #include "ppc64v2.c" + #elif defined(_ARCH_PPC) && !defined(__LITTLE_ENDIAN__) + #include "ppc.c" + #elif defined(_WIN32) + #include "fiber.c" + #else + #include "sjlj.c" + #endif +#elif defined(_MSC_VER) + #if defined(_M_IX86) + #include "x86.c" + #elif defined(_M_AMD64) + #include "amd64.c" + #else + #include "fiber.c" + #endif +#else + #error "libco: unsupported processor, compiler or operating system" +#endif diff --git a/runtime/third_party/libco/libco.h b/runtime/third_party/libco/libco.h new file mode 100644 index 0000000..633cd54 --- /dev/null +++ b/runtime/third_party/libco/libco.h @@ -0,0 +1,28 @@ +/* + libco v20 (2019-10-16) + author: byuu + license: ISC +*/ + +#ifndef LIBCO_H +#define LIBCO_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void* cothread_t; + +cothread_t co_active(void); +cothread_t co_derive(void*, unsigned int, void (*)(void)); +cothread_t co_create(unsigned int, void (*)(void)); +void co_delete(cothread_t); +void co_switch(cothread_t); +int co_serializable(void); + +#ifdef __cplusplus +} +#endif + +/* ifndef LIBCO_H */ +#endif diff --git a/runtime/third_party/libco/ppc.c b/runtime/third_party/libco/ppc.c new file mode 100644 index 0000000..cfb4ab3 --- /dev/null +++ b/runtime/third_party/libco/ppc.c @@ -0,0 +1,435 @@ +/* ppc64le (ELFv2) is not currently supported */ + +#define LIBCO_C +#include "libco.h" +#include "settings.h" +#include "valgrind.h" + +#include +#include + +#ifdef LIBCO_MPROTECT + #include + #include +#endif + +/* state format (offsets in 32-bit words) + + +0 pointer to swap code + rest of function descriptor for entry function + +8 PC ++10 SP + special registers + GPRs + FPRs + VRs + stack +*/ + +enum { state_size = 1024 }; +enum { above_stack = 2048 }; +enum { stack_align = 256 }; + +static thread_local cothread_t co_active_handle = 0; + +/* determine environment */ + +#define LIBCO_PPC64 (_ARCH_PPC64 || __PPC64__ || __ppc64__ || __powerpc64__) + +/* whether function calls are indirect through a descriptor, or are directly to function */ +#ifndef LIBCO_PPCDESC + #if !_CALL_SYSV && (_CALL_AIX || _CALL_AIXDESC || (LIBCO_PPC64 && (!defined(_CALL_ELF) || _CALL_ELF == 1))) + #define LIBCO_PPCDESC 1 + #endif +#endif + +#ifdef LIBCO_MPROTECT + alignas(4096) +#else + section(text) +#endif +static const uint32_t libco_ppc_code[1024] = { + #if LIBCO_PPC64 + 0x7d000026, /* mfcr r8 */ + 0xf8240028, /* std r1,40(r4) */ + 0x7d2802a6, /* mflr r9 */ + 0xf9c40048, /* std r14,72(r4) */ + 0xf9e40050, /* std r15,80(r4) */ + 0xfa040058, /* std r16,88(r4) */ + 0xfa240060, /* std r17,96(r4) */ + 0xfa440068, /* std r18,104(r4) */ + 0xfa640070, /* std r19,112(r4) */ + 0xfa840078, /* std r20,120(r4) */ + 0xfaa40080, /* std r21,128(r4) */ + 0xfac40088, /* std r22,136(r4) */ + 0xfae40090, /* std r23,144(r4) */ + 0xfb040098, /* std r24,152(r4) */ + 0xfb2400a0, /* std r25,160(r4) */ + 0xfb4400a8, /* std r26,168(r4) */ + 0xfb6400b0, /* std r27,176(r4) */ + 0xfb8400b8, /* std r28,184(r4) */ + 0xfba400c0, /* std r29,192(r4) */ + 0xfbc400c8, /* std r30,200(r4) */ + 0xfbe400d0, /* std r31,208(r4) */ + 0xf9240020, /* std r9,32(r4) */ + 0xe8e30020, /* ld r7,32(r3) */ + 0xe8230028, /* ld r1,40(r3) */ + 0x48000009, /* bl 1 */ + 0x7fe00008, /* trap */ + 0x91040030, /*1:stw r8,48(r4) */ + 0x80c30030, /* lwz r6,48(r3) */ + 0x7ce903a6, /* mtctr r7 */ + 0xe9c30048, /* ld r14,72(r3) */ + 0xe9e30050, /* ld r15,80(r3) */ + 0xea030058, /* ld r16,88(r3) */ + 0xea230060, /* ld r17,96(r3) */ + 0xea430068, /* ld r18,104(r3) */ + 0xea630070, /* ld r19,112(r3) */ + 0xea830078, /* ld r20,120(r3) */ + 0xeaa30080, /* ld r21,128(r3) */ + 0xeac30088, /* ld r22,136(r3) */ + 0xeae30090, /* ld r23,144(r3) */ + 0xeb030098, /* ld r24,152(r3) */ + 0xeb2300a0, /* ld r25,160(r3) */ + 0xeb4300a8, /* ld r26,168(r3) */ + 0xeb6300b0, /* ld r27,176(r3) */ + 0xeb8300b8, /* ld r28,184(r3) */ + 0xeba300c0, /* ld r29,192(r3) */ + 0xebc300c8, /* ld r30,200(r3) */ + 0xebe300d0, /* ld r31,208(r3) */ + 0x7ccff120, /* mtcr r6 */ + #else + 0x7d000026, /* mfcr r8 */ + 0x90240028, /* stw r1,40(r4) */ + 0x7d2802a6, /* mflr r9 */ + 0x91a4003c, /* stw r13,60(r4) */ + 0x91c40040, /* stw r14,64(r4) */ + 0x91e40044, /* stw r15,68(r4) */ + 0x92040048, /* stw r16,72(r4) */ + 0x9224004c, /* stw r17,76(r4) */ + 0x92440050, /* stw r18,80(r4) */ + 0x92640054, /* stw r19,84(r4) */ + 0x92840058, /* stw r20,88(r4) */ + 0x92a4005c, /* stw r21,92(r4) */ + 0x92c40060, /* stw r22,96(r4) */ + 0x92e40064, /* stw r23,100(r4) */ + 0x93040068, /* stw r24,104(r4) */ + 0x9324006c, /* stw r25,108(r4) */ + 0x93440070, /* stw r26,112(r4) */ + 0x93640074, /* stw r27,116(r4) */ + 0x93840078, /* stw r28,120(r4) */ + 0x93a4007c, /* stw r29,124(r4) */ + 0x93c40080, /* stw r30,128(r4) */ + 0x93e40084, /* stw r31,132(r4) */ + 0x91240020, /* stw r9,32(r4) */ + 0x80e30020, /* lwz r7,32(r3) */ + 0x80230028, /* lwz r1,40(r3) */ + 0x48000009, /* bl 1 */ + 0x7fe00008, /* trap */ + 0x91040030, /*1:stw r8,48(r4) */ + 0x80c30030, /* lwz r6,48(r3) */ + 0x7ce903a6, /* mtctr r7 */ + 0x81a3003c, /* lwz r13,60(r3) */ + 0x81c30040, /* lwz r14,64(r3) */ + 0x81e30044, /* lwz r15,68(r3) */ + 0x82030048, /* lwz r16,72(r3) */ + 0x8223004c, /* lwz r17,76(r3) */ + 0x82430050, /* lwz r18,80(r3) */ + 0x82630054, /* lwz r19,84(r3) */ + 0x82830058, /* lwz r20,88(r3) */ + 0x82a3005c, /* lwz r21,92(r3) */ + 0x82c30060, /* lwz r22,96(r3) */ + 0x82e30064, /* lwz r23,100(r3) */ + 0x83030068, /* lwz r24,104(r3) */ + 0x8323006c, /* lwz r25,108(r3) */ + 0x83430070, /* lwz r26,112(r3) */ + 0x83630074, /* lwz r27,116(r3) */ + 0x83830078, /* lwz r28,120(r3) */ + 0x83a3007c, /* lwz r29,124(r3) */ + 0x83c30080, /* lwz r30,128(r3) */ + 0x83e30084, /* lwz r31,132(r3) */ + 0x7ccff120, /* mtcr r6 */ + #endif + + #ifndef LIBCO_PPC_NOFP + 0xd9c400e0, /* stfd f14,224(r4) */ + 0xd9e400e8, /* stfd f15,232(r4) */ + 0xda0400f0, /* stfd f16,240(r4) */ + 0xda2400f8, /* stfd f17,248(r4) */ + 0xda440100, /* stfd f18,256(r4) */ + 0xda640108, /* stfd f19,264(r4) */ + 0xda840110, /* stfd f20,272(r4) */ + 0xdaa40118, /* stfd f21,280(r4) */ + 0xdac40120, /* stfd f22,288(r4) */ + 0xdae40128, /* stfd f23,296(r4) */ + 0xdb040130, /* stfd f24,304(r4) */ + 0xdb240138, /* stfd f25,312(r4) */ + 0xdb440140, /* stfd f26,320(r4) */ + 0xdb640148, /* stfd f27,328(r4) */ + 0xdb840150, /* stfd f28,336(r4) */ + 0xdba40158, /* stfd f29,344(r4) */ + 0xdbc40160, /* stfd f30,352(r4) */ + 0xdbe40168, /* stfd f31,360(r4) */ + 0xc9c300e0, /* lfd f14,224(r3) */ + 0xc9e300e8, /* lfd f15,232(r3) */ + 0xca0300f0, /* lfd f16,240(r3) */ + 0xca2300f8, /* lfd f17,248(r3) */ + 0xca430100, /* lfd f18,256(r3) */ + 0xca630108, /* lfd f19,264(r3) */ + 0xca830110, /* lfd f20,272(r3) */ + 0xcaa30118, /* lfd f21,280(r3) */ + 0xcac30120, /* lfd f22,288(r3) */ + 0xcae30128, /* lfd f23,296(r3) */ + 0xcb030130, /* lfd f24,304(r3) */ + 0xcb230138, /* lfd f25,312(r3) */ + 0xcb430140, /* lfd f26,320(r3) */ + 0xcb630148, /* lfd f27,328(r3) */ + 0xcb830150, /* lfd f28,336(r3) */ + 0xcba30158, /* lfd f29,344(r3) */ + 0xcbc30160, /* lfd f30,352(r3) */ + 0xcbe30168, /* lfd f31,360(r3) */ + #endif + + #ifdef __ALTIVEC__ + 0x7ca042a6, /* mfvrsave r5 */ + 0x39040180, /* addi r8,r4,384 */ + 0x39240190, /* addi r9,r4,400 */ + 0x70a00fff, /* andi. r0,r5,4095 */ + 0x90a40034, /* stw r5,52(r4) */ + 0x4182005c, /* beq- 2 */ + 0x7e8041ce, /* stvx v20,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ea049ce, /* stvx v21,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7ec041ce, /* stvx v22,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ee049ce, /* stvx v23,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f0041ce, /* stvx v24,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f2049ce, /* stvx v25,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f4041ce, /* stvx v26,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f6049ce, /* stvx v27,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f8041ce, /* stvx v28,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7fa049ce, /* stvx v29,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7fc041ce, /* stvx v30,r0,r8 */ + 0x7fe049ce, /* stvx v31,r0,r9 */ + 0x80a30034, /*2:lwz r5,52(r3) */ + 0x39030180, /* addi r8,r3,384 */ + 0x39230190, /* addi r9,r3,400 */ + 0x70a00fff, /* andi. r0,r5,4095 */ + 0x7ca043a6, /* mtvrsave r5 */ + 0x4d820420, /* beqctr */ + 0x7e8040ce, /* lvx v20,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ea048ce, /* lvx v21,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7ec040ce, /* lvx v22,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7ee048ce, /* lvx v23,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f0040ce, /* lvx v24,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f2048ce, /* lvx v25,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f4040ce, /* lvx v26,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7f6048ce, /* lvx v27,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7f8040ce, /* lvx v28,r0,r8 */ + 0x39080020, /* addi r8,r8,32 */ + 0x7fa048ce, /* lvx v29,r0,r9 */ + 0x39290020, /* addi r9,r9,32 */ + 0x7fc040ce, /* lvx v30,r0,r8 */ + 0x7fe048ce, /* lvx v31,r0,r9 */ + #endif + + 0x4e800420, /* bctr */ +}; + +#if LIBCO_PPCDESC + /* function call goes through indirect descriptor */ + #define CO_SWAP_ASM(x, y) ((void (*)(cothread_t, cothread_t))(uintptr_t)x)(x, y) +#else + /* function call goes directly to code */ + #define CO_SWAP_ASM(x, y) ((void (*)(cothread_t, cothread_t))(uintptr_t)libco_ppc_code)(x, y) +#endif + +static uint32_t* co_derive_(void* memory, unsigned size, uintptr_t entry) { + uint32_t* t = (uint32_t*)memory; + + (void)entry; + + #if LIBCO_PPCDESC + if(t) { + memcpy(t, (void*)entry, sizeof(void*) * 3); /* copy entry's descriptor */ + *(const void**)t = libco_ppc_code; /* set function pointer to swap routine */ + } + #endif + + return t; +} + +cothread_t co_derive(void* memory, unsigned int size, void (*entry_)(void)) { + uintptr_t entry = (uintptr_t)entry_; + uint32_t* t = 0; + + /* be sure main thread was successfully allocated */ + if(co_active()) { + t = co_derive_(memory, size, entry); + } + + if(t) { + uintptr_t sp; + int shift; + + VALGRIND_STACK_REGISTER(t, (char*)t + size); + + /* save current registers into new thread, so that any special ones will have proper values when thread is begun */ + CO_SWAP_ASM(t, t); + + #if LIBCO_PPCDESC + entry = (uintptr_t)*(void**)entry; /* get real address */ + #endif + + /* put stack near end of block, and align */ + sp = (uintptr_t)t + size - above_stack; + sp -= sp % stack_align; + + /* on PPC32, we save and restore GPRs as 32 bits. for PPC64, we + save and restore them as 64 bits, regardless of the size the ABI + uses. so, we manually write pointers at the proper size. we always + save and restore at the same address, and since PPC is big-endian, + we must put the low byte first on PPC32. */ + + /* if uintptr_t is 32 bits, >>32 is undefined behavior, + so we do two shifts and don't have to care how many bits uintptr_t is. */ + #if LIBCO_PPC64 + shift = 16; + #else + shift = 0; + #endif + + /* set up so entry will be called on next swap */ + t[ 8] = (uint32_t)(entry >> shift >> shift); + t[ 9] = (uint32_t)entry; + + t[10] = (uint32_t)(sp >> shift >> shift); + t[11] = (uint32_t)sp; + } + + return t; +} + +static uint32_t* co_create_(unsigned size, uintptr_t entry) { + uint32_t* t = (uint32_t*)LIBCO_MALLOC(size); + + (void)entry; + + #if LIBCO_PPCDESC + if(t) { + memcpy(t, (void*)entry, sizeof(void*) * 3); /* copy entry's descriptor */ + *(const void**)t = libco_ppc_code; /* set function pointer to swap routine */ + } + #endif + + return t; +} + +cothread_t co_create(unsigned int size, void (*entry_)(void)) { + uintptr_t entry = (uintptr_t)entry_; + uint32_t* t = 0; + + /* be sure main thread was successfully allocated */ + if(co_active()) { + size += state_size + above_stack + stack_align; + t = co_create_(size, entry); + } + + if(t) { + uintptr_t sp; + int shift; + + VALGRIND_STACK_REGISTER(t, (char*)t + size); + + /* save current registers into new thread, so that any special ones will have proper values when thread is begun */ + CO_SWAP_ASM(t, t); + + #if LIBCO_PPCDESC + entry = (uintptr_t)*(void**)entry; /* get real address */ + #endif + + /* put stack near end of block, and align */ + sp = (uintptr_t)t + size - above_stack; + sp -= sp % stack_align; + + /* on PPC32, we save and restore GPRs as 32 bits. for PPC64, we + save and restore them as 64 bits, regardless of the size the ABI + uses. so, we manually write pointers at the proper size. we always + save and restore at the same address, and since PPC is big-endian, + we must put the low byte first on PPC32. */ + + /* if uintptr_t is 32 bits, >>32 is undefined behavior, + so we do two shifts and don't have to care how many bits uintptr_t is. */ + #if LIBCO_PPC64 + shift = 16; + #else + shift = 0; + #endif + + /* set up so entry will be called on next swap */ + t[ 8] = (uint32_t)(entry >> shift >> shift); + t[ 9] = (uint32_t)entry; + + t[10] = (uint32_t)(sp >> shift >> shift); + t[11] = (uint32_t)sp; + } + + return t; +} + +void co_delete(cothread_t t) { + LIBCO_FREE(t); +} + +static void co_init_(void) { + #if LIBCO_MPROTECT + long page_size = sysconf(_SC_PAGESIZE); + if(page_size > 0) { + uintptr_t align = page_size; + uintptr_t begin = (uintptr_t)libco_ppc_code; + uintptr_t end = begin + sizeof libco_ppc_code; + + /* align beginning and end */ + end += align - 1; + end -= end % align; + begin -= begin % align; + + mprotect((void*)begin, end - begin, PROT_READ | PROT_EXEC); + } + #endif + + co_active_handle = co_create_(state_size, (uintptr_t)&co_switch); +} + +cothread_t co_active(void) { + if(!co_active_handle) co_init_(); + + return co_active_handle; +} + +void co_switch(cothread_t t) { + cothread_t old = co_active_handle; + co_active_handle = t; + + CO_SWAP_ASM(t, old); +} + +int co_serializable(void) { + return 0; +} diff --git a/runtime/third_party/libco/ppc64v2.c b/runtime/third_party/libco/ppc64v2.c new file mode 100644 index 0000000..10987ba --- /dev/null +++ b/runtime/third_party/libco/ppc64v2.c @@ -0,0 +1,281 @@ +/* author: Shawn Anastasio */ + +#define LIBCO_C +#include "libco.h" +#include "settings.h" +#include "valgrind.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct ppc64_context { + /* GPRs */ + uint64_t gprs[32]; + uint64_t lr; + uint64_t ccr; + + /* FPRs */ + uint64_t fprs[32]; + + #ifdef __ALTIVEC__ + /* Altivec (VMX) */ + uint64_t vmx[12 * 2]; + uint32_t vrsave; + #endif +}; + +static thread_local struct ppc64_context* co_active_handle = 0; + +#define MAX(x, y) ((x) > (y) ? (x) : (y)) +#define ALIGN(p, x) ((void*)((uintptr_t)(p) & ~((x) - 1))) + +#define MIN_STACK 0x10000lu +#define MIN_STACK_FRAME 0x20lu +#define STACK_ALIGN 0x10lu + +void swap_context(struct ppc64_context* read, struct ppc64_context* write); +__asm__( + ".text\n" + ".align 4\n" + ".type swap_context @function\n" + "swap_context:\n" + ".cfi_startproc\n" + + /* save GPRs */ + "std 1, 8(4)\n" + "std 2, 16(4)\n" + "std 12, 96(4)\n" + "std 13, 104(4)\n" + "std 14, 112(4)\n" + "std 15, 120(4)\n" + "std 16, 128(4)\n" + "std 17, 136(4)\n" + "std 18, 144(4)\n" + "std 19, 152(4)\n" + "std 20, 160(4)\n" + "std 21, 168(4)\n" + "std 22, 176(4)\n" + "std 23, 184(4)\n" + "std 24, 192(4)\n" + "std 25, 200(4)\n" + "std 26, 208(4)\n" + "std 27, 216(4)\n" + "std 28, 224(4)\n" + "std 29, 232(4)\n" + "std 30, 240(4)\n" + "std 31, 248(4)\n" + + /* save LR */ + "mflr 5\n" + "std 5, 256(4)\n" + + /* save CCR */ + "mfcr 5\n" + "std 5, 264(4)\n" + + /* save FPRs */ + "stfd 14, 384(4)\n" + "stfd 15, 392(4)\n" + "stfd 16, 400(4)\n" + "stfd 17, 408(4)\n" + "stfd 18, 416(4)\n" + "stfd 19, 424(4)\n" + "stfd 20, 432(4)\n" + "stfd 21, 440(4)\n" + "stfd 22, 448(4)\n" + "stfd 23, 456(4)\n" + "stfd 24, 464(4)\n" + "stfd 25, 472(4)\n" + "stfd 26, 480(4)\n" + "stfd 27, 488(4)\n" + "stfd 28, 496(4)\n" + "stfd 29, 504(4)\n" + "stfd 30, 512(4)\n" + "stfd 31, 520(4)\n" + + #ifdef __ALTIVEC__ + /* save VMX */ + "li 5, 528\n" + "stvxl 20, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 21, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 22, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 23, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 24, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 25, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 26, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 27, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 28, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 29, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 30, 4, 5\n" + "addi 5, 5, 16\n" + "stvxl 31, 4, 5\n" + "addi 5, 5, 16\n" + + /* save VRSAVE */ + "mfvrsave 5\n" + "stw 5, 736(4)\n" + #endif + + /* restore GPRs */ + "ld 1, 8(3)\n" + "ld 2, 16(3)\n" + "ld 12, 96(3)\n" + "ld 13, 104(3)\n" + "ld 14, 112(3)\n" + "ld 15, 120(3)\n" + "ld 16, 128(3)\n" + "ld 17, 136(3)\n" + "ld 18, 144(3)\n" + "ld 19, 152(3)\n" + "ld 20, 160(3)\n" + "ld 21, 168(3)\n" + "ld 22, 176(3)\n" + "ld 23, 184(3)\n" + "ld 24, 192(3)\n" + "ld 25, 200(3)\n" + "ld 26, 208(3)\n" + "ld 27, 216(3)\n" + "ld 28, 224(3)\n" + "ld 29, 232(3)\n" + "ld 30, 240(3)\n" + "ld 31, 248(3)\n" + + /* restore LR */ + "ld 5, 256(3)\n" + "mtlr 5\n" + + /* restore CCR */ + "ld 5, 264(3)\n" + "mtcr 5\n" + + /* restore FPRs */ + "lfd 14, 384(3)\n" + "lfd 15, 392(3)\n" + "lfd 16, 400(3)\n" + "lfd 17, 408(3)\n" + "lfd 18, 416(3)\n" + "lfd 19, 424(3)\n" + "lfd 20, 432(3)\n" + "lfd 21, 440(3)\n" + "lfd 22, 448(3)\n" + "lfd 23, 456(3)\n" + "lfd 24, 464(3)\n" + "lfd 25, 472(3)\n" + "lfd 26, 480(3)\n" + "lfd 27, 488(3)\n" + "lfd 28, 496(3)\n" + "lfd 29, 504(3)\n" + "lfd 30, 512(3)\n" + "lfd 31, 520(3)\n" + + #ifdef __ALTIVEC__ + /* restore VMX */ + "li 5, 528\n" + "lvxl 20, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 21, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 22, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 23, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 24, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 25, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 26, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 27, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 28, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 29, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 30, 3, 5\n" + "addi 5, 5, 16\n" + "lvxl 31, 3, 5\n" + "addi 5, 5, 16\n" + + /* restore VRSAVE */ + "lwz 5, 720(3)\n" + "mtvrsave 5\n" + #endif + + /* branch to LR */ + "blr\n" + + ".cfi_endproc\n" + ".size swap_context, .-swap_context\n" +); + +cothread_t co_active(void) { + if(!co_active_handle) { + co_active_handle = (struct ppc64_context*)LIBCO_MALLOC(MIN_STACK + sizeof(struct ppc64_context)); + } + return (cothread_t)co_active_handle; +} + +cothread_t co_derive(void* memory, unsigned int size, void (*coentry)(void)) { + uint8_t* sp; + struct ppc64_context* context = (struct ppc64_context*)memory; + + VALGRIND_STACK_REGISTER(memory, memory + size); + + /* save current context into new context to initialize it */ + swap_context(context, context); + + /* align stack */ + sp = (uint8_t*)memory + size - STACK_ALIGN; + sp = (uint8_t*)ALIGN(sp, STACK_ALIGN); + + /* write 0 for initial backchain */ + *(uint64_t*)sp = 0; + + /* create new frame with backchain */ + sp -= MIN_STACK_FRAME; + *(uint64_t*)sp = (uint64_t)(sp + MIN_STACK_FRAME); + + /* update context with new stack (r1) and entrypoint (r12, lr) */ + context->gprs[ 1] = (uint64_t)sp; + context->gprs[12] = (uint64_t)coentry; + context->lr = (uint64_t)coentry; + + return (cothread_t)memory; +} + +cothread_t co_create(unsigned int size, void (*coentry)(void)) { + void* memory = LIBCO_MALLOC(size); + if(!memory) return (cothread_t)0; + return co_derive(memory, size, coentry); +} + +void co_delete(cothread_t handle) { + LIBCO_FREE(handle); +} + +void co_switch(cothread_t to) { + struct ppc64_context* from = co_active_handle; + co_active_handle = (struct ppc64_context*)to; + swap_context((struct ppc64_context*)to, from); +} + +int co_serializable(void) { + return 1; +} + +#ifdef __cplusplus +} +#endif diff --git a/runtime/third_party/libco/settings.h b/runtime/third_party/libco/settings.h new file mode 100644 index 0000000..418c00a --- /dev/null +++ b/runtime/third_party/libco/settings.h @@ -0,0 +1,131 @@ +#if defined(LIBCO_C) + +/*[amd64, arm, ppc, x86]: + by default, co_swap_function is marked as a text (code) section + if not supported, uncomment the below line to use mprotect instead */ +/* #define LIBCO_MPROTECT */ + +/*[amd64]: + Win64 only: provides a substantial speed-up, but will thrash XMM regs + do not use this unless you are certain your application won't use SSE */ +/* #define LIBCO_NO_SSE */ + +#if !defined(thread_local) /* User can override thread_local for obscure compilers */ + #if !defined(LIBCO_MP) /* Running in single-threaded environment */ + #define thread_local + #else /* Running in multi-threaded environment */ + #if defined(__STDC__) /* Compiling as C Language */ + #if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */ + #define thread_local __declspec(thread) + #elif __STDC_VERSION__ < 201112L /* If we are on C90/99 */ + #if defined(__clang__) || defined(__GNUC__) /* Clang and GCC */ + #define thread_local __thread + #else /* Otherwise, we ignore the directive (unless user provides their own) */ + #define thread_local + #endif + #else /* C11 and newer define thread_local in threads.h */ + #include + #endif + #elif defined(__cplusplus) /* Compiling as C++ Language */ + #if __cplusplus < 201103L /* thread_local is a C++11 feature */ + #if defined(_MSC_VER) + #define thread_local __declspec(thread) + #elif defined(__clang__) || defined(__GNUC__) + #define thread_local __thread + #else /* Otherwise, we ignore the directive (unless user provides their own) */ + #define thread_local + #endif + #else /* In C++ >= 11, thread_local in a builtin keyword */ + /* Don't do anything */ + #endif + #endif + #endif +#endif + +/* In alignas(a), 'a' should be a power of two that is at least the type's + alignment and at most the implementation's alignment limit. This limit is + 2**13 on MSVC. To be portable to MSVC through at least version 10.0, + 'a' should be an integer constant, as MSVC does not support expressions + such as 1 << 3. + + The following C11 requirements are NOT supported on MSVC: + + - If 'a' is zero, alignas has no effect. + - alignas can be used multiple times; the strictest one wins. + - alignas (TYPE) is equivalent to alignas (alignof (TYPE)). +*/ +#if !defined(alignas) + #if defined(__STDC__) /* C Language */ + #if defined(_MSC_VER) /* Don't rely on MSVC's C11 support */ + #define alignas(bytes) __declspec(align(bytes)) + #elif __STDC_VERSION__ >= 201112L /* C11 and above */ + #include + #elif defined(__clang__) || defined(__GNUC__) /* C90/99 on Clang/GCC */ + #define alignas(bytes) __attribute__ ((aligned (bytes))) + #else /* Otherwise, we ignore the directive (user should provide their own) */ + #define alignas(bytes) + #endif + #elif defined(__cplusplus) /* C++ Language */ + #if __cplusplus < 201103L + #if defined(_MSC_VER) + #define alignas(bytes) __declspec(align(bytes)) + #elif defined(__clang__) || defined(__GNUC__) /* C++98/03 on Clang/GCC */ + #define alignas(bytes) __attribute__ ((aligned (bytes))) + #else /* Otherwise, we ignore the directive (unless user provides their own) */ + #define alignas(bytes) + #endif + #else /* C++ >= 11 has alignas keyword */ + /* Do nothing */ + #endif + #endif /* = !defined(__STDC_VERSION__) && !defined(__cplusplus) */ +#endif + +#if !defined(LIBCO_ASSERT) + #include + #define LIBCO_ASSERT assert +#endif + +#if defined (__OpenBSD__) + #if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE) + #include + #include + + static void* malloc_obsd(size_t size) { + long pagesize = sysconf(_SC_PAGESIZE); + char* memory = (char*)mmap(NULL, size + pagesize, PROT_READ|PROT_WRITE, MAP_STACK|MAP_PRIVATE|MAP_ANON, -1, 0); + if (memory == MAP_FAILED) return NULL; + *(size_t*)memory = size + pagesize; + memory += pagesize; + return (void*)memory; + } + + static void free_obsd(void *ptr) { + char* memory = (char*)ptr - sysconf(_SC_PAGESIZE); + munmap(memory, *(size_t*)memory); + } + + #define LIBCO_MALLOC malloc_obsd + #define LIBCO_FREE free_obsd + #endif +#endif + +#if !defined(LIBCO_MALLOC) || !defined(LIBCO_FREE) + #include + #define LIBCO_MALLOC malloc + #define LIBCO_FREE free +#endif + +#if defined(_MSC_VER) + /* workaround for msvc preprocessor stringification behavior */ + #define LIBCO_STRINGIFY(x) #x + #define LIBCO_TOSTRING(x) LIBCO_STRINGIFY(x) + #define section(name) __pragma(code_seg(LIBCO_TOSTRING("." #name))) __declspec(allocate(LIBCO_TOSTRING("." #name))) +#elif defined(__APPLE__) + #define section(name) __attribute__((section("__TEXT,__" #name))) +#else + #define section(name) __attribute__((section("." #name "#"))) +#endif + + +/* if defined(LIBCO_C) */ +#endif diff --git a/runtime/third_party/libco/sjlj.c b/runtime/third_party/libco/sjlj.c new file mode 100644 index 0000000..5da9b36 --- /dev/null +++ b/runtime/third_party/libco/sjlj.c @@ -0,0 +1,155 @@ +/* + note this was designed for UNIX systems. Based on ideas expressed in a paper by Ralf Engelschall. + for SJLJ on other systems, one would want to rewrite springboard() and co_create() and hack the jmb_buf stack pointer. +*/ + +/* for sigsetjmp(), sigjmp_buf, and stack_t */ +#define _POSIX_C_SOURCE 200809L +/* for SA_ONSTACK */ +#define _XOPEN_SOURCE 600 + +#define LIBCO_C +#include "libco.h" +#include "settings.h" +#include "valgrind.h" + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + sigjmp_buf context; + void (*coentry)(void); + void* stack; +} cothread_struct; + +static thread_local cothread_struct co_primary; +static thread_local cothread_struct* creating; +static thread_local cothread_struct* co_running = 0; + +static void springboard(int ignored) { + if(sigsetjmp(creating->context, 0)) { + co_running->coentry(); + } +} + +cothread_t co_active(void) { + if(!co_running) co_running = &co_primary; + return (cothread_t)co_running; +} + +cothread_t co_derive(void* memory, unsigned int size, void (*coentry)(void)) { + cothread_struct* thread; + if(!co_running) co_running = &co_primary; + + thread = (cothread_struct*)memory; + memory = (unsigned char*)memory + sizeof(cothread_struct); + size -= sizeof(cothread_struct); + if(thread) { + struct sigaction handler; + struct sigaction old_handler; + + stack_t stack; + stack_t old_stack; + + thread->coentry = thread->stack = 0; + + stack.ss_flags = 0; + stack.ss_size = size; + thread->stack = stack.ss_sp = memory; + if(stack.ss_sp && !sigaltstack(&stack, &old_stack)) { + handler.sa_handler = springboard; + handler.sa_flags = SA_ONSTACK; + sigemptyset(&handler.sa_mask); + creating = thread; + + if(!sigaction(SIGUSR1, &handler, &old_handler)) { + if(!raise(SIGUSR1)) { + thread->coentry = coentry; + } + sigaltstack(&old_stack, 0); + sigaction(SIGUSR1, &old_handler, 0); + } + } + + if(thread->coentry != coentry) { + co_delete(thread); + thread = 0; + } else { + VALGRIND_STACK_REGISTER(stack.ss_sp, stack.ss_sp + size); + } + } + + return (cothread_t)thread; +} + +cothread_t co_create(unsigned int size, void (*coentry)(void)) { + cothread_struct* thread; + if(!co_running) co_running = &co_primary; + + thread = (cothread_struct*)malloc(sizeof(cothread_struct)); + if(thread) { + struct sigaction handler; + struct sigaction old_handler; + + stack_t stack; + stack_t old_stack; + + thread->coentry = thread->stack = 0; + + stack.ss_flags = 0; + stack.ss_size = size; + thread->stack = stack.ss_sp = malloc(size); + if(stack.ss_sp && !sigaltstack(&stack, &old_stack)) { + handler.sa_handler = springboard; + handler.sa_flags = SA_ONSTACK; + sigemptyset(&handler.sa_mask); + creating = thread; + + if(!sigaction(SIGUSR1, &handler, &old_handler)) { + if(!raise(SIGUSR1)) { + thread->coentry = coentry; + } + sigaltstack(&old_stack, 0); + sigaction(SIGUSR1, &old_handler, 0); + } + } + + if(thread->coentry != coentry) { + co_delete(thread); + thread = 0; + } else { + VALGRIND_STACK_REGISTER(stack.ss_sp, stack.ss_sp + size); + } + } + + return (cothread_t)thread; +} + +void co_delete(cothread_t cothread) { + if(cothread) { + if(((cothread_struct*)cothread)->stack) { + free(((cothread_struct*)cothread)->stack); + } + free(cothread); + } +} + +void co_switch(cothread_t cothread) { + if(!sigsetjmp(co_running->context, 0)) { + co_running = (cothread_struct*)cothread; + siglongjmp(co_running->context, 1); + } +} + +int co_serializable(void) { + return 0; +} + +#ifdef __cplusplus +} +#endif diff --git a/runtime/third_party/libco/ucontext.c b/runtime/third_party/libco/ucontext.c new file mode 100644 index 0000000..7259db1 --- /dev/null +++ b/runtime/third_party/libco/ucontext.c @@ -0,0 +1,90 @@ +/* + WARNING: the overhead of POSIX ucontext is very high, + assembly versions of libco or libco_sjlj should be much faster + + this library only exists for two reasons: + 1: as an initial test for the viability of a ucontext implementation + 2: to demonstrate the power and speed of libco over existing implementations, + such as pth (which defaults to wrapping ucontext on unix targets) + + use this library only as a *last resort* +*/ + +#define _POSIX_C_SOURCE 200112L +#define LIBCO_C +#include "libco.h" +#include "settings.h" +#include "valgrind.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static thread_local ucontext_t co_primary; +static thread_local ucontext_t* co_running = 0; + +cothread_t co_active(void) { + if(!co_running) co_running = &co_primary; + return (cothread_t)co_running; +} + +cothread_t co_derive(void* memory, unsigned int heapsize, void (*coentry)(void)) { + ucontext_t* thread; + if(!co_running) co_running = &co_primary; + thread = (ucontext_t*)memory; + memory = (unsigned char*)memory + sizeof(ucontext_t); + heapsize -= sizeof(ucontext_t); + if(thread) { + if((!getcontext(thread) && !(thread->uc_stack.ss_sp = 0)) && (thread->uc_stack.ss_sp = memory)) { + thread->uc_link = co_running; + thread->uc_stack.ss_size = heapsize; + makecontext(thread, coentry, 0); + VALGRIND_STACK_REGISTER(thread->uc_stack.ss_sp, thread->uc_stack.ss_sp + heapsize); + } else { + thread = 0; + } + } + return (cothread_t)thread; +} + +cothread_t co_create(unsigned int heapsize, void (*coentry)(void)) { + ucontext_t* thread; + if(!co_running) co_running = &co_primary; + thread = (ucontext_t*)malloc(sizeof(ucontext_t)); + if(thread) { + if((!getcontext(thread) && !(thread->uc_stack.ss_sp = 0)) && (thread->uc_stack.ss_sp = malloc(heapsize))) { + thread->uc_link = co_running; + thread->uc_stack.ss_size = heapsize; + makecontext(thread, coentry, 0); + VALGRIND_STACK_REGISTER(thread->uc_stack.ss_sp, thread->uc_stack.ss_sp + heapsize); + } else { + co_delete((cothread_t)thread); + thread = 0; + } + } + return (cothread_t)thread; +} + +void co_delete(cothread_t cothread) { + if(cothread) { + if(((ucontext_t*)cothread)->uc_stack.ss_sp) { free(((ucontext_t*)cothread)->uc_stack.ss_sp); } + free(cothread); + } +} + +void co_switch(cothread_t cothread) { + ucontext_t* old_thread = co_running; + co_running = (ucontext_t*)cothread; + swapcontext(old_thread, co_running); +} + +int co_serializable(void) { + return 0; +} + +#ifdef __cplusplus +} +#endif diff --git a/runtime/third_party/libco/valgrind.h b/runtime/third_party/libco/valgrind.h new file mode 100644 index 0000000..4acbb81 --- /dev/null +++ b/runtime/third_party/libco/valgrind.h @@ -0,0 +1,7165 @@ +/* -*- c -*- + ---------------------------------------------------------------- + + Notice that the following BSD-style license applies to this one + file (valgrind.h) only. The rest of Valgrind is licensed under the + terms of the GNU General Public License, version 2, unless + otherwise indicated. See the COPYING file in the source + distribution for details. + + ---------------------------------------------------------------- + + This file is part of Valgrind, a dynamic binary instrumentation + framework. + + Copyright (C) 2000-2017 Julian Seward. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product + documentation would be appreciated but is not required. + + 3. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 4. The name of the author may not be used to endorse or promote + products derived from this software without specific prior written + permission. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + ---------------------------------------------------------------- + + Notice that the above BSD-style license applies to this one file + (valgrind.h) only. The entire rest of Valgrind is licensed under + the terms of the GNU General Public License, version 2. See the + COPYING file in the source distribution for details. + + ---------------------------------------------------------------- +*/ + + +/* This file is for inclusion into client (your!) code. + + You can use these macros to manipulate and query Valgrind's + execution inside your own programs. + + The resulting executables will still run without Valgrind, just a + little bit more slowly than they otherwise would, but otherwise + unchanged. When not running on valgrind, each client request + consumes very few (eg. 7) instructions, so the resulting performance + loss is negligible unless you plan to execute client requests + millions of times per second. Nevertheless, if that is still a + problem, you can compile with the NVALGRIND symbol defined (gcc + -DNVALGRIND) so that client requests are not even compiled in. */ + +#ifndef __VALGRIND_H +#define __VALGRIND_H + + +/* ------------------------------------------------------------------ */ +/* VERSION NUMBER OF VALGRIND */ +/* ------------------------------------------------------------------ */ + +/* Specify Valgrind's version number, so that user code can + conditionally compile based on our version number. Note that these + were introduced at version 3.6 and so do not exist in version 3.5 + or earlier. The recommended way to use them to check for "version + X.Y or later" is (eg) + +#if defined(__VALGRIND_MAJOR__) && defined(__VALGRIND_MINOR__) \ + && (__VALGRIND_MAJOR__ > 3 \ + || (__VALGRIND_MAJOR__ == 3 && __VALGRIND_MINOR__ >= 6)) +*/ +#define __VALGRIND_MAJOR__ 3 +#define __VALGRIND_MINOR__ 21 + + +#include + +/* Nb: this file might be included in a file compiled with -ansi. So + we can't use C++ style "//" comments nor the "asm" keyword (instead + use "__asm__"). */ + +/* Derive some tags indicating what the target platform is. Note + that in this file we're using the compiler's CPP symbols for + identifying architectures, which are different to the ones we use + within the rest of Valgrind. Note, __powerpc__ is active for both + 32 and 64-bit PPC, whereas __powerpc64__ is only active for the + latter (on Linux, that is). + + Misc note: how to find out what's predefined in gcc by default: + gcc -Wp,-dM somefile.c +*/ +#undef PLAT_x86_darwin +#undef PLAT_amd64_darwin +#undef PLAT_x86_freebsd +#undef PLAT_amd64_freebsd +#undef PLAT_x86_win32 +#undef PLAT_amd64_win64 +#undef PLAT_x86_linux +#undef PLAT_amd64_linux +#undef PLAT_ppc32_linux +#undef PLAT_ppc64be_linux +#undef PLAT_ppc64le_linux +#undef PLAT_arm_linux +#undef PLAT_arm64_linux +#undef PLAT_s390x_linux +#undef PLAT_mips32_linux +#undef PLAT_mips64_linux +#undef PLAT_nanomips_linux +#undef PLAT_x86_solaris +#undef PLAT_amd64_solaris + + +#if defined(__APPLE__) && defined(__i386__) +# define PLAT_x86_darwin 1 +#elif defined(__APPLE__) && defined(__x86_64__) +# define PLAT_amd64_darwin 1 +#elif defined(__FreeBSD__) && defined(__i386__) +# define PLAT_x86_freebsd 1 +#elif defined(__FreeBSD__) && defined(__amd64__) +# define PLAT_amd64_freebsd 1 +#elif (defined(__MINGW32__) && defined(__i386__)) \ + || defined(__CYGWIN32__) \ + || (defined(_WIN32) && defined(_M_IX86)) +# define PLAT_x86_win32 1 +#elif (defined(__MINGW32__) && defined(__x86_64__)) \ + || (defined(_WIN32) && defined(_M_X64)) +/* __MINGW32__ and _WIN32 are defined in 64 bit mode as well. */ +# define PLAT_amd64_win64 1 +#elif defined(__linux__) && defined(__i386__) +# define PLAT_x86_linux 1 +#elif defined(__linux__) && defined(__x86_64__) && !defined(__ILP32__) +# define PLAT_amd64_linux 1 +#elif defined(__linux__) && defined(__powerpc__) && !defined(__powerpc64__) +# define PLAT_ppc32_linux 1 +#elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) && _CALL_ELF != 2 +/* Big Endian uses ELF version 1 */ +# define PLAT_ppc64be_linux 1 +#elif defined(__linux__) && defined(__powerpc__) && defined(__powerpc64__) && _CALL_ELF == 2 +/* Little Endian uses ELF version 2 */ +# define PLAT_ppc64le_linux 1 +#elif defined(__linux__) && defined(__arm__) && !defined(__aarch64__) +# define PLAT_arm_linux 1 +#elif defined(__linux__) && defined(__aarch64__) && !defined(__arm__) +# define PLAT_arm64_linux 1 +#elif defined(__linux__) && defined(__s390__) && defined(__s390x__) +# define PLAT_s390x_linux 1 +#elif defined(__linux__) && defined(__mips__) && (__mips==64) +# define PLAT_mips64_linux 1 +#elif defined(__linux__) && defined(__mips__) && (__mips==32) +# define PLAT_mips32_linux 1 +#elif defined(__linux__) && defined(__nanomips__) +# define PLAT_nanomips_linux 1 +#elif defined(__sun) && defined(__i386__) +# define PLAT_x86_solaris 1 +#elif defined(__sun) && defined(__x86_64__) +# define PLAT_amd64_solaris 1 +#else +/* If we're not compiling for our target platform, don't generate + any inline asms. */ +# if !defined(NVALGRIND) +# define NVALGRIND 1 +# endif +#endif + + +/* ------------------------------------------------------------------ */ +/* ARCHITECTURE SPECIFICS for SPECIAL INSTRUCTIONS. There is nothing */ +/* in here of use to end-users -- skip to the next section. */ +/* ------------------------------------------------------------------ */ + +/* + * VALGRIND_DO_CLIENT_REQUEST(): a statement that invokes a Valgrind client + * request. Accepts both pointers and integers as arguments. + * + * VALGRIND_DO_CLIENT_REQUEST_STMT(): a statement that invokes a Valgrind + * client request that does not return a value. + + * VALGRIND_DO_CLIENT_REQUEST_EXPR(): a C expression that invokes a Valgrind + * client request and whose value equals the client request result. Accepts + * both pointers and integers as arguments. Note that such calls are not + * necessarily pure functions -- they may have side effects. + */ + +#define VALGRIND_DO_CLIENT_REQUEST(_zzq_rlval, _zzq_default, \ + _zzq_request, _zzq_arg1, _zzq_arg2, \ + _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + do { (_zzq_rlval) = VALGRIND_DO_CLIENT_REQUEST_EXPR((_zzq_default), \ + (_zzq_request), (_zzq_arg1), (_zzq_arg2), \ + (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0) + +#define VALGRIND_DO_CLIENT_REQUEST_STMT(_zzq_request, _zzq_arg1, \ + _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + do { (void) VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + (_zzq_request), (_zzq_arg1), (_zzq_arg2), \ + (_zzq_arg3), (_zzq_arg4), (_zzq_arg5)); } while (0) + +#if defined(NVALGRIND) + +/* Define NVALGRIND to completely remove the Valgrind magic sequence + from the compiled code (analogous to NDEBUG's effects on + assert()) */ +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + (_zzq_default) + +#else /* ! NVALGRIND */ + +/* The following defines the magic code sequences which the JITter + spots and handles magically. Don't look too closely at them as + they will rot your brain. + + The assembly code sequences for all architectures is in this one + file. This is because this file must be stand-alone, and we don't + want to have multiple files. + + For VALGRIND_DO_CLIENT_REQUEST, we must ensure that the default + value gets put in the return slot, so that everything works when + this is executed not under Valgrind. Args are passed in a memory + block, and so there's no intrinsic limit to the number that could + be passed, but it's currently five. + + The macro args are: + _zzq_rlval result lvalue + _zzq_default default value (result returned when running on real CPU) + _zzq_request request code + _zzq_arg1..5 request params + + The other two macros are used to support function wrapping, and are + a lot simpler. VALGRIND_GET_NR_CONTEXT returns the value of the + guest's NRADDR pseudo-register and whatever other information is + needed to safely run the call original from the wrapper: on + ppc64-linux, the R2 value at the divert point is also needed. This + information is abstracted into a user-visible type, OrigFn. + + VALGRIND_CALL_NOREDIR_* behaves the same as the following on the + guest, but guarantees that the branch instruction will not be + redirected: x86: call *%eax, amd64: call *%rax, ppc32/ppc64: + branch-and-link-to-r11. VALGRIND_CALL_NOREDIR is just text, not a + complete inline asm, since it needs to be combined with more magic + inline asm stuff to be useful. +*/ + +/* ----------------- x86-{linux,darwin,solaris} ---------------- */ + +#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \ + || (defined(PLAT_x86_win32) && defined(__GNUC__)) \ + || defined(PLAT_x86_solaris) || defined(PLAT_x86_freebsd) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "roll $3, %%edi ; roll $13, %%edi\n\t" \ + "roll $29, %%edi ; roll $19, %%edi\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({volatile unsigned int _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EDX = client_request ( %EAX ) */ \ + "xchgl %%ebx,%%ebx" \ + : "=d" (_zzq_result) \ + : "a" (&_zzq_args[0]), "0" (_zzq_default) \ + : "cc", "memory" \ + ); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EAX = guest_NRADDR */ \ + "xchgl %%ecx,%%ecx" \ + : "=a" (__addr) \ + : \ + : "cc", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_EAX \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir *%EAX */ \ + "xchgl %%edx,%%edx\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "xchgl %%edi,%%edi\n\t" \ + : : : "cc", "memory" \ + ); \ + } while (0) + +#endif /* PLAT_x86_linux || PLAT_x86_darwin || (PLAT_x86_win32 && __GNUC__) + || PLAT_x86_solaris */ + +/* ------------------------- x86-Win32 ------------------------- */ + +#if defined(PLAT_x86_win32) && !defined(__GNUC__) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#if defined(_MSC_VER) + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + __asm rol edi, 3 __asm rol edi, 13 \ + __asm rol edi, 29 __asm rol edi, 19 + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + valgrind_do_client_request_expr((uintptr_t)(_zzq_default), \ + (uintptr_t)(_zzq_request), (uintptr_t)(_zzq_arg1), \ + (uintptr_t)(_zzq_arg2), (uintptr_t)(_zzq_arg3), \ + (uintptr_t)(_zzq_arg4), (uintptr_t)(_zzq_arg5)) + +static __inline uintptr_t +valgrind_do_client_request_expr(uintptr_t _zzq_default, uintptr_t _zzq_request, + uintptr_t _zzq_arg1, uintptr_t _zzq_arg2, + uintptr_t _zzq_arg3, uintptr_t _zzq_arg4, + uintptr_t _zzq_arg5) +{ + volatile uintptr_t _zzq_args[6]; + volatile unsigned int _zzq_result; + _zzq_args[0] = (uintptr_t)(_zzq_request); + _zzq_args[1] = (uintptr_t)(_zzq_arg1); + _zzq_args[2] = (uintptr_t)(_zzq_arg2); + _zzq_args[3] = (uintptr_t)(_zzq_arg3); + _zzq_args[4] = (uintptr_t)(_zzq_arg4); + _zzq_args[5] = (uintptr_t)(_zzq_arg5); + __asm { __asm lea eax, _zzq_args __asm mov edx, _zzq_default + __SPECIAL_INSTRUCTION_PREAMBLE + /* %EDX = client_request ( %EAX ) */ + __asm xchg ebx,ebx + __asm mov _zzq_result, edx + } + return _zzq_result; +} + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned int __addr; \ + __asm { __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %EAX = guest_NRADDR */ \ + __asm xchg ecx,ecx \ + __asm mov __addr, eax \ + } \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_EAX ERROR + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm { __SPECIAL_INSTRUCTION_PREAMBLE \ + __asm xchg edi,edi \ + } \ + } while (0) + +#else +#error Unsupported compiler. +#endif + +#endif /* PLAT_x86_win32 */ + +/* ----------------- amd64-{linux,darwin,solaris} --------------- */ + +#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \ + || defined(PLAT_amd64_solaris) \ + || defined(PLAT_amd64_freebsd) \ + || (defined(PLAT_amd64_win64) && defined(__GNUC__)) + +typedef + struct { + unsigned long int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rolq $3, %%rdi ; rolq $13, %%rdi\n\t" \ + "rolq $61, %%rdi ; rolq $51, %%rdi\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({ volatile unsigned long int _zzq_args[6]; \ + volatile unsigned long int _zzq_result; \ + _zzq_args[0] = (unsigned long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %RDX = client_request ( %RAX ) */ \ + "xchgq %%rbx,%%rbx" \ + : "=d" (_zzq_result) \ + : "a" (&_zzq_args[0]), "0" (_zzq_default) \ + : "cc", "memory" \ + ); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %RAX = guest_NRADDR */ \ + "xchgq %%rcx,%%rcx" \ + : "=a" (__addr) \ + : \ + : "cc", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_RAX \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir *%RAX */ \ + "xchgq %%rdx,%%rdx\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "xchgq %%rdi,%%rdi\n\t" \ + : : : "cc", "memory" \ + ); \ + } while (0) + +#endif /* PLAT_amd64_linux || PLAT_amd64_darwin || PLAT_amd64_solaris */ + +/* ------------------------- amd64-Win64 ------------------------- */ + +#if defined(PLAT_amd64_win64) && !defined(__GNUC__) + +#error Unsupported compiler. + +#endif /* PLAT_amd64_win64 */ + +/* ------------------------ ppc32-linux ------------------------ */ + +#if defined(PLAT_ppc32_linux) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rlwinm 0,0,3,0,31 ; rlwinm 0,0,13,0,31\n\t" \ + "rlwinm 0,0,29,0,31 ; rlwinm 0,0,19,0,31\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + __extension__ \ + ({ unsigned int _zzq_args[6]; \ + unsigned int _zzq_result; \ + unsigned int* _zzq_ptr; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + _zzq_ptr = _zzq_args; \ + __asm__ volatile("mr 3,%1\n\t" /*default*/ \ + "mr 4,%2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = client_request ( %R4 ) */ \ + "or 1,1,1\n\t" \ + "mr %0,3" /*result*/ \ + : "=b" (_zzq_result) \ + : "b" (_zzq_default), "b" (_zzq_ptr) \ + : "cc", "memory", "r3", "r4"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR */ \ + "or 2,2,2\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R11 */ \ + "or 3,3,3\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "or 5,5,5\n\t" \ + ); \ + } while (0) + +#endif /* PLAT_ppc32_linux */ + +/* ------------------------ ppc64-linux ------------------------ */ + +#if defined(PLAT_ppc64be_linux) + +typedef + struct { + unsigned long int nraddr; /* where's the code? */ + unsigned long int r2; /* what tocptr do we need? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \ + "rotldi 0,0,61 ; rotldi 0,0,51\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + __extension__ \ + ({ unsigned long int _zzq_args[6]; \ + unsigned long int _zzq_result; \ + unsigned long int* _zzq_ptr; \ + _zzq_args[0] = (unsigned long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ + _zzq_ptr = _zzq_args; \ + __asm__ volatile("mr 3,%1\n\t" /*default*/ \ + "mr 4,%2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = client_request ( %R4 ) */ \ + "or 1,1,1\n\t" \ + "mr %0,3" /*result*/ \ + : "=b" (_zzq_result) \ + : "b" (_zzq_default), "b" (_zzq_ptr) \ + : "cc", "memory", "r3", "r4"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR */ \ + "or 2,2,2\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR_GPR2 */ \ + "or 4,4,4\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->r2 = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R11 */ \ + "or 3,3,3\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "or 5,5,5\n\t" \ + ); \ + } while (0) + +#endif /* PLAT_ppc64be_linux */ + +#if defined(PLAT_ppc64le_linux) + +typedef + struct { + unsigned long int nraddr; /* where's the code? */ + unsigned long int r2; /* what tocptr do we need? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "rotldi 0,0,3 ; rotldi 0,0,13\n\t" \ + "rotldi 0,0,61 ; rotldi 0,0,51\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + __extension__ \ + ({ unsigned long int _zzq_args[6]; \ + unsigned long int _zzq_result; \ + unsigned long int* _zzq_ptr; \ + _zzq_args[0] = (unsigned long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ + _zzq_ptr = _zzq_args; \ + __asm__ volatile("mr 3,%1\n\t" /*default*/ \ + "mr 4,%2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = client_request ( %R4 ) */ \ + "or 1,1,1\n\t" \ + "mr %0,3" /*result*/ \ + : "=b" (_zzq_result) \ + : "b" (_zzq_default), "b" (_zzq_ptr) \ + : "cc", "memory", "r3", "r4"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR */ \ + "or 2,2,2\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %R3 = guest_NRADDR_GPR2 */ \ + "or 4,4,4\n\t" \ + "mr %0,3" \ + : "=b" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->r2 = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R12 */ \ + "or 3,3,3\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "or 5,5,5\n\t" \ + ); \ + } while (0) + +#endif /* PLAT_ppc64le_linux */ + +/* ------------------------- arm-linux ------------------------- */ + +#if defined(PLAT_arm_linux) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "mov r12, r12, ror #3 ; mov r12, r12, ror #13 \n\t" \ + "mov r12, r12, ror #29 ; mov r12, r12, ror #19 \n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + __extension__ \ + ({volatile unsigned int _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + __asm__ volatile("mov r3, %1\n\t" /*default*/ \ + "mov r4, %2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* R3 = client_request ( R4 ) */ \ + "orr r10, r10, r10\n\t" \ + "mov %0, r3" /*result*/ \ + : "=r" (_zzq_result) \ + : "r" (_zzq_default), "r" (&_zzq_args[0]) \ + : "cc","memory", "r3", "r4"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* R3 = guest_NRADDR */ \ + "orr r11, r11, r11\n\t" \ + "mov %0, r3" \ + : "=r" (__addr) \ + : \ + : "cc", "memory", "r3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir *%R4 */ \ + "orr r12, r12, r12\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "orr r9, r9, r9\n\t" \ + : : : "cc", "memory" \ + ); \ + } while (0) + +#endif /* PLAT_arm_linux */ + +/* ------------------------ arm64-linux ------------------------- */ + +#if defined(PLAT_arm64_linux) + +typedef + struct { + unsigned long int nraddr; /* where's the code? */ + } + OrigFn; + +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "ror x12, x12, #3 ; ror x12, x12, #13 \n\t" \ + "ror x12, x12, #51 ; ror x12, x12, #61 \n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + \ + __extension__ \ + ({volatile unsigned long int _zzq_args[6]; \ + volatile unsigned long int _zzq_result; \ + _zzq_args[0] = (unsigned long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ + __asm__ volatile("mov x3, %1\n\t" /*default*/ \ + "mov x4, %2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* X3 = client_request ( X4 ) */ \ + "orr x10, x10, x10\n\t" \ + "mov %0, x3" /*result*/ \ + : "=r" (_zzq_result) \ + : "r" ((unsigned long int)(_zzq_default)), \ + "r" (&_zzq_args[0]) \ + : "cc","memory", "x3", "x4"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + unsigned long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* X3 = guest_NRADDR */ \ + "orr x11, x11, x11\n\t" \ + "mov %0, x3" \ + : "=r" (__addr) \ + : \ + : "cc", "memory", "x3" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* branch-and-link-to-noredir X8 */ \ + "orr x12, x12, x12\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "orr x9, x9, x9\n\t" \ + : : : "cc", "memory" \ + ); \ + } while (0) + +#endif /* PLAT_arm64_linux */ + +/* ------------------------ s390x-linux ------------------------ */ + +#if defined(PLAT_s390x_linux) + +typedef + struct { + unsigned long int nraddr; /* where's the code? */ + } + OrigFn; + +/* __SPECIAL_INSTRUCTION_PREAMBLE will be used to identify Valgrind specific + * code. This detection is implemented in platform specific toIR.c + * (e.g. VEX/priv/guest_s390_decoder.c). + */ +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "lr 15,15\n\t" \ + "lr 1,1\n\t" \ + "lr 2,2\n\t" \ + "lr 3,3\n\t" + +#define __CLIENT_REQUEST_CODE "lr 2,2\n\t" +#define __GET_NR_CONTEXT_CODE "lr 3,3\n\t" +#define __CALL_NO_REDIR_CODE "lr 4,4\n\t" +#define __VEX_INJECT_IR_CODE "lr 5,5\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({volatile unsigned long int _zzq_args[6]; \ + volatile unsigned long int _zzq_result; \ + _zzq_args[0] = (unsigned long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ + __asm__ volatile(/* r2 = args */ \ + "lgr 2,%1\n\t" \ + /* r3 = default */ \ + "lgr 3,%2\n\t" \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + __CLIENT_REQUEST_CODE \ + /* results = r3 */ \ + "lgr %0, 3\n\t" \ + : "=d" (_zzq_result) \ + : "a" (&_zzq_args[0]), \ + "0" ((unsigned long int)_zzq_default) \ + : "cc", "2", "3", "memory" \ + ); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + __GET_NR_CONTEXT_CODE \ + "lgr %0, 3\n\t" \ + : "=a" (__addr) \ + : \ + : "cc", "3", "memory" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_R1 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + __CALL_NO_REDIR_CODE + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + __VEX_INJECT_IR_CODE); \ + } while (0) + +#endif /* PLAT_s390x_linux */ + +/* ------------------------- mips32-linux ---------------- */ + +#if defined(PLAT_mips32_linux) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; + +/* .word 0x342 + * .word 0x742 + * .word 0xC2 + * .word 0x4C2*/ +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "srl $0, $0, 13\n\t" \ + "srl $0, $0, 29\n\t" \ + "srl $0, $0, 3\n\t" \ + "srl $0, $0, 19\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({ volatile unsigned int _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + __asm__ volatile("move $11, %1\n\t" /*default*/ \ + "move $12, %2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* T3 = client_request ( T4 ) */ \ + "or $13, $13, $13\n\t" \ + "move %0, $11\n\t" /*result*/ \ + : "=r" (_zzq_result) \ + : "r" (_zzq_default), "r" (&_zzq_args[0]) \ + : "$11", "$12", "memory"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* %t9 = guest_NRADDR */ \ + "or $14, $14, $14\n\t" \ + "move %0, $11" /*result*/ \ + : "=r" (__addr) \ + : \ + : "$11" \ + ); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_T9 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir *%t9 */ \ + "or $15, $15, $15\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "or $11, $11, $11\n\t" \ + ); \ + } while (0) + + +#endif /* PLAT_mips32_linux */ + +/* ------------------------- mips64-linux ---------------- */ + +#if defined(PLAT_mips64_linux) + +typedef + struct { + unsigned long nraddr; /* where's the code? */ + } + OrigFn; + +/* dsll $0,$0, 3 + * dsll $0,$0, 13 + * dsll $0,$0, 29 + * dsll $0,$0, 19*/ +#define __SPECIAL_INSTRUCTION_PREAMBLE \ + "dsll $0,$0, 3 ; dsll $0,$0,13\n\t" \ + "dsll $0,$0,29 ; dsll $0,$0,19\n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({ volatile unsigned long int _zzq_args[6]; \ + volatile unsigned long int _zzq_result; \ + _zzq_args[0] = (unsigned long int)(_zzq_request); \ + _zzq_args[1] = (unsigned long int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned long int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned long int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned long int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned long int)(_zzq_arg5); \ + __asm__ volatile("move $11, %1\n\t" /*default*/ \ + "move $12, %2\n\t" /*ptr*/ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* $11 = client_request ( $12 ) */ \ + "or $13, $13, $13\n\t" \ + "move %0, $11\n\t" /*result*/ \ + : "=r" (_zzq_result) \ + : "r" (_zzq_default), "r" (&_zzq_args[0]) \ + : "$11", "$12", "memory"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* $11 = guest_NRADDR */ \ + "or $14, $14, $14\n\t" \ + "move %0, $11" /*result*/ \ + : "=r" (__addr) \ + : \ + : "$11"); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_T9 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir $25 */ \ + "or $15, $15, $15\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "or $11, $11, $11\n\t" \ + ); \ + } while (0) + +#endif /* PLAT_mips64_linux */ + +#if defined(PLAT_nanomips_linux) + +typedef + struct { + unsigned int nraddr; /* where's the code? */ + } + OrigFn; +/* + 8000 c04d srl zero, zero, 13 + 8000 c05d srl zero, zero, 29 + 8000 c043 srl zero, zero, 3 + 8000 c053 srl zero, zero, 19 +*/ + +#define __SPECIAL_INSTRUCTION_PREAMBLE "srl[32] $zero, $zero, 13 \n\t" \ + "srl[32] $zero, $zero, 29 \n\t" \ + "srl[32] $zero, $zero, 3 \n\t" \ + "srl[32] $zero, $zero, 19 \n\t" + +#define VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + _zzq_default, _zzq_request, \ + _zzq_arg1, _zzq_arg2, _zzq_arg3, _zzq_arg4, _zzq_arg5) \ + __extension__ \ + ({ volatile unsigned int _zzq_args[6]; \ + volatile unsigned int _zzq_result; \ + _zzq_args[0] = (unsigned int)(_zzq_request); \ + _zzq_args[1] = (unsigned int)(_zzq_arg1); \ + _zzq_args[2] = (unsigned int)(_zzq_arg2); \ + _zzq_args[3] = (unsigned int)(_zzq_arg3); \ + _zzq_args[4] = (unsigned int)(_zzq_arg4); \ + _zzq_args[5] = (unsigned int)(_zzq_arg5); \ + __asm__ volatile("move $a7, %1\n\t" /* default */ \ + "move $t0, %2\n\t" /* ptr */ \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* $a7 = client_request( $t0 ) */ \ + "or[32] $t0, $t0, $t0\n\t" \ + "move %0, $a7\n\t" /* result */ \ + : "=r" (_zzq_result) \ + : "r" (_zzq_default), "r" (&_zzq_args[0]) \ + : "$a7", "$t0", "memory"); \ + _zzq_result; \ + }) + +#define VALGRIND_GET_NR_CONTEXT(_zzq_rlval) \ + { volatile OrigFn* _zzq_orig = &(_zzq_rlval); \ + volatile unsigned long int __addr; \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + /* $a7 = guest_NRADDR */ \ + "or[32] $t1, $t1, $t1\n\t" \ + "move %0, $a7" /*result*/ \ + : "=r" (__addr) \ + : \ + : "$a7"); \ + _zzq_orig->nraddr = __addr; \ + } + +#define VALGRIND_CALL_NOREDIR_T9 \ + __SPECIAL_INSTRUCTION_PREAMBLE \ + /* call-noredir $25 */ \ + "or[32] $t2, $t2, $t2\n\t" + +#define VALGRIND_VEX_INJECT_IR() \ + do { \ + __asm__ volatile(__SPECIAL_INSTRUCTION_PREAMBLE \ + "or[32] $t3, $t3, $t3\n\t" \ + ); \ + } while (0) + +#endif +/* Insert assembly code for other platforms here... */ + +#endif /* NVALGRIND */ + + +/* ------------------------------------------------------------------ */ +/* PLATFORM SPECIFICS for FUNCTION WRAPPING. This is all very */ +/* ugly. It's the least-worst tradeoff I can think of. */ +/* ------------------------------------------------------------------ */ + +/* This section defines magic (a.k.a appalling-hack) macros for doing + guaranteed-no-redirection macros, so as to get from function + wrappers to the functions they are wrapping. The whole point is to + construct standard call sequences, but to do the call itself with a + special no-redirect call pseudo-instruction that the JIT + understands and handles specially. This section is long and + repetitious, and I can't see a way to make it shorter. + + The naming scheme is as follows: + + CALL_FN_{W,v}_{v,W,WW,WWW,WWWW,5W,6W,7W,etc} + + 'W' stands for "word" and 'v' for "void". Hence there are + different macros for calling arity 0, 1, 2, 3, 4, etc, functions, + and for each, the possibility of returning a word-typed result, or + no result. +*/ + +/* Use these to write the name of your wrapper. NOTE: duplicates + VG_WRAP_FUNCTION_Z{U,Z} in pub_tool_redir.h. NOTE also: inserts + the default behaviour equivalance class tag "0000" into the name. + See pub_tool_redir.h for details -- normally you don't need to + think about this, though. */ + +/* Use an extra level of macroisation so as to ensure the soname/fnname + args are fully macro-expanded before pasting them together. */ +#define VG_CONCAT4(_aa,_bb,_cc,_dd) _aa##_bb##_cc##_dd + +#define I_WRAP_SONAME_FNNAME_ZU(soname,fnname) \ + VG_CONCAT4(_vgw00000ZU_,soname,_,fnname) + +#define I_WRAP_SONAME_FNNAME_ZZ(soname,fnname) \ + VG_CONCAT4(_vgw00000ZZ_,soname,_,fnname) + +/* Use this macro from within a wrapper function to collect the + context (address and possibly other info) of the original function. + Once you have that you can then use it in one of the CALL_FN_ + macros. The type of the argument _lval is OrigFn. */ +#define VALGRIND_GET_ORIG_FN(_lval) VALGRIND_GET_NR_CONTEXT(_lval) + +/* Also provide end-user facilities for function replacement, rather + than wrapping. A replacement function differs from a wrapper in + that it has no way to get hold of the original function being + called, and hence no way to call onwards to it. In a replacement + function, VALGRIND_GET_ORIG_FN always returns zero. */ + +#define I_REPLACE_SONAME_FNNAME_ZU(soname,fnname) \ + VG_CONCAT4(_vgr00000ZU_,soname,_,fnname) + +#define I_REPLACE_SONAME_FNNAME_ZZ(soname,fnname) \ + VG_CONCAT4(_vgr00000ZZ_,soname,_,fnname) + +/* Derivatives of the main macros below, for calling functions + returning void. */ + +#define CALL_FN_v_v(fnptr) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_v(_junk,fnptr); } while (0) + +#define CALL_FN_v_W(fnptr, arg1) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_W(_junk,fnptr,arg1); } while (0) + +#define CALL_FN_v_WW(fnptr, arg1,arg2) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_WW(_junk,fnptr,arg1,arg2); } while (0) + +#define CALL_FN_v_WWW(fnptr, arg1,arg2,arg3) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_WWW(_junk,fnptr,arg1,arg2,arg3); } while (0) + +#define CALL_FN_v_WWWW(fnptr, arg1,arg2,arg3,arg4) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_WWWW(_junk,fnptr,arg1,arg2,arg3,arg4); } while (0) + +#define CALL_FN_v_5W(fnptr, arg1,arg2,arg3,arg4,arg5) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_5W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5); } while (0) + +#define CALL_FN_v_6W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_6W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6); } while (0) + +#define CALL_FN_v_7W(fnptr, arg1,arg2,arg3,arg4,arg5,arg6,arg7) \ + do { volatile unsigned long _junk; \ + CALL_FN_W_7W(_junk,fnptr,arg1,arg2,arg3,arg4,arg5,arg6,arg7); } while (0) + +/* ----------------- x86-{linux,darwin,solaris} ---------------- */ + +#if defined(PLAT_x86_linux) || defined(PLAT_x86_darwin) \ + || defined(PLAT_x86_solaris) || defined(PLAT_x86_freebsd) + +/* These regs are trashed by the hidden call. No need to mention eax + as gcc can already see that, plus causes gcc to bomb. */ +#define __CALLER_SAVED_REGS /*"eax"*/ "ecx", "edx" + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +#define VALGRIND_ALIGN_STACK \ + "movl %%esp,%%edi\n\t" \ + "andl $0xfffffff0,%%esp\n\t" +#define VALGRIND_RESTORE_STACK \ + "movl %%edi,%%esp\n\t" + +/* These CALL_FN_ macros assume that on x86-linux, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $12, %%esp\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $8, %%esp\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $4, %%esp\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $12, %%esp\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $8, %%esp\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $4, %%esp\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $12, %%esp\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $8, %%esp\n\t" \ + "pushl 40(%%eax)\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "subl $4, %%esp\n\t" \ + "pushl 44(%%eax)\n\t" \ + "pushl 40(%%eax)\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "pushl 48(%%eax)\n\t" \ + "pushl 44(%%eax)\n\t" \ + "pushl 40(%%eax)\n\t" \ + "pushl 36(%%eax)\n\t" \ + "pushl 32(%%eax)\n\t" \ + "pushl 28(%%eax)\n\t" \ + "pushl 24(%%eax)\n\t" \ + "pushl 20(%%eax)\n\t" \ + "pushl 16(%%eax)\n\t" \ + "pushl 12(%%eax)\n\t" \ + "pushl 8(%%eax)\n\t" \ + "pushl 4(%%eax)\n\t" \ + "movl (%%eax), %%eax\n\t" /* target->%eax */ \ + VALGRIND_CALL_NOREDIR_EAX \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "edi" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_x86_linux || PLAT_x86_darwin || PLAT_x86_solaris */ + +/* ---------------- amd64-{linux,darwin,solaris} --------------- */ + +#if defined(PLAT_amd64_linux) || defined(PLAT_amd64_darwin) \ + || defined(PLAT_amd64_solaris) || defined(PLAT_amd64_freebsd) + +/* ARGREGS: rdi rsi rdx rcx r8 r9 (the rest on stack in R-to-L order) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS /*"rax",*/ "rcx", "rdx", "rsi", \ + "rdi", "r8", "r9", "r10", "r11" + +/* This is all pretty complex. It's so as to make stack unwinding + work reliably. See bug 243270. The basic problem is the sub and + add of 128 of %rsp in all of the following macros. If gcc believes + the CFA is in %rsp, then unwinding may fail, because what's at the + CFA is not what gcc "expected" when it constructs the CFIs for the + places where the macros are instantiated. + + But we can't just add a CFI annotation to increase the CFA offset + by 128, to match the sub of 128 from %rsp, because we don't know + whether gcc has chosen %rsp as the CFA at that point, or whether it + has chosen some other register (eg, %rbp). In the latter case, + adding a CFI annotation to change the CFA offset is simply wrong. + + So the solution is to get hold of the CFA using + __builtin_dwarf_cfa(), put it in a known register, and add a + CFI annotation to say what the register is. We choose %rbp for + this (perhaps perversely), because: + + (1) %rbp is already subject to unwinding. If a new register was + chosen then the unwinder would have to unwind it in all stack + traces, which is expensive, and + + (2) %rbp is already subject to precise exception updates in the + JIT. If a new register was chosen, we'd have to have precise + exceptions for it too, which reduces performance of the + generated code. + + However .. one extra complication. We can't just whack the result + of __builtin_dwarf_cfa() into %rbp and then add %rbp to the + list of trashed registers at the end of the inline assembly + fragments; gcc won't allow %rbp to appear in that list. Hence + instead we need to stash %rbp in %r15 for the duration of the asm, + and say that %r15 is trashed instead. gcc seems happy to go with + that. + + Oh .. and this all needs to be conditionalised so that it is + unchanged from before this commit, when compiled with older gccs + that don't support __builtin_dwarf_cfa. Furthermore, since + this header file is freestanding, it has to be independent of + config.h, and so the following conditionalisation cannot depend on + configure time checks. + + Although it's not clear from + 'defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM)', + this expression excludes Darwin. + .cfi directives in Darwin assembly appear to be completely + different and I haven't investigated how they work. + + For even more entertainment value, note we have to use the + completely undocumented __builtin_dwarf_cfa(), which appears to + really compute the CFA, whereas __builtin_frame_address(0) claims + to but actually doesn't. See + https://bugs.kde.org/show_bug.cgi?id=243270#c47 +*/ +#if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM) +# define __FRAME_POINTER \ + ,"r"(__builtin_dwarf_cfa()) +# define VALGRIND_CFI_PROLOGUE \ + "movq %%rbp, %%r15\n\t" \ + "movq %2, %%rbp\n\t" \ + ".cfi_remember_state\n\t" \ + ".cfi_def_cfa rbp, 0\n\t" +# define VALGRIND_CFI_EPILOGUE \ + "movq %%r15, %%rbp\n\t" \ + ".cfi_restore_state\n\t" +#else +# define __FRAME_POINTER +# define VALGRIND_CFI_PROLOGUE +# define VALGRIND_CFI_EPILOGUE +#endif + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +#define VALGRIND_ALIGN_STACK \ + "movq %%rsp,%%r14\n\t" \ + "andq $0xfffffffffffffff0,%%rsp\n\t" +#define VALGRIND_RESTORE_STACK \ + "movq %%r14,%%rsp\n\t" + +/* These CALL_FN_ macros assume that on amd64-linux, sizeof(unsigned + long) == 8. */ + +/* NB 9 Sept 07. There is a nasty kludge here in all these CALL_FN_ + macros. In order not to trash the stack redzone, we need to drop + %rsp by 128 before the hidden call, and restore afterwards. The + nastyness is that it is only by luck that the stack still appears + to be unwindable during the hidden call - since then the behaviour + of any routine using this macro does not match what the CFI data + says. Sigh. + + Why is this important? Imagine that a wrapper has a stack + allocated local, and passes to the hidden call, a pointer to it. + Because gcc does not know about the hidden call, it may allocate + that local in the redzone. Unfortunately the hidden call may then + trash it before it comes to use it. So we must step clear of the + redzone, for the duration of the hidden call, to make it safe. + + Probably the same problem afflicts the other redzone-style ABIs too + (ppc64-linux); but for those, the stack is + self describing (none of this CFI nonsense) so at least messing + with the stack pointer doesn't give a danger of non-unwindable + stack. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $136,%%rsp\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $136,%%rsp\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "pushq 80(%%rax)\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $136,%%rsp\n\t" \ + "pushq 88(%%rax)\n\t" \ + "pushq 80(%%rax)\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + VALGRIND_ALIGN_STACK \ + "subq $128,%%rsp\n\t" \ + "pushq 96(%%rax)\n\t" \ + "pushq 88(%%rax)\n\t" \ + "pushq 80(%%rax)\n\t" \ + "pushq 72(%%rax)\n\t" \ + "pushq 64(%%rax)\n\t" \ + "pushq 56(%%rax)\n\t" \ + "movq 48(%%rax), %%r9\n\t" \ + "movq 40(%%rax), %%r8\n\t" \ + "movq 32(%%rax), %%rcx\n\t" \ + "movq 24(%%rax), %%rdx\n\t" \ + "movq 16(%%rax), %%rsi\n\t" \ + "movq 8(%%rax), %%rdi\n\t" \ + "movq (%%rax), %%rax\n\t" /* target->%rax */ \ + VALGRIND_CALL_NOREDIR_RAX \ + VALGRIND_RESTORE_STACK \ + VALGRIND_CFI_EPILOGUE \ + : /*out*/ "=a" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r14", "r15" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_amd64_linux || PLAT_amd64_darwin || PLAT_amd64_solaris */ + +/* ------------------------ ppc32-linux ------------------------ */ + +#if defined(PLAT_ppc32_linux) + +/* This is useful for finding out about the on-stack stuff: + + extern int f9 ( int,int,int,int,int,int,int,int,int ); + extern int f10 ( int,int,int,int,int,int,int,int,int,int ); + extern int f11 ( int,int,int,int,int,int,int,int,int,int,int ); + extern int f12 ( int,int,int,int,int,int,int,int,int,int,int,int ); + + int g9 ( void ) { + return f9(11,22,33,44,55,66,77,88,99); + } + int g10 ( void ) { + return f10(11,22,33,44,55,66,77,88,99,110); + } + int g11 ( void ) { + return f11(11,22,33,44,55,66,77,88,99,110,121); + } + int g12 ( void ) { + return f12(11,22,33,44,55,66,77,88,99,110,121,132); + } +*/ + +/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "lr", "ctr", "xer", \ + "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ + "r0", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ + "r11", "r12", "r13" + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +#define VALGRIND_ALIGN_STACK \ + "mr 28,1\n\t" \ + "rlwinm 1,1,0,0,27\n\t" +#define VALGRIND_RESTORE_STACK \ + "mr 1,28\n\t" + +/* These CALL_FN_ macros assume that on ppc32-linux, + sizeof(unsigned long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "addi 1,1,-16\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "addi 1,1,-16\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,12(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "addi 1,1,-32\n\t" \ + /* arg11 */ \ + "lwz 3,44(11)\n\t" \ + "stw 3,16(1)\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,12(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + _argvec[12] = (unsigned long)arg12; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "addi 1,1,-32\n\t" \ + /* arg12 */ \ + "lwz 3,48(11)\n\t" \ + "stw 3,20(1)\n\t" \ + /* arg11 */ \ + "lwz 3,44(11)\n\t" \ + "stw 3,16(1)\n\t" \ + /* arg10 */ \ + "lwz 3,40(11)\n\t" \ + "stw 3,12(1)\n\t" \ + /* arg9 */ \ + "lwz 3,36(11)\n\t" \ + "stw 3,8(1)\n\t" \ + /* args1-8 */ \ + "lwz 3,4(11)\n\t" /* arg1->r3 */ \ + "lwz 4,8(11)\n\t" \ + "lwz 5,12(11)\n\t" \ + "lwz 6,16(11)\n\t" /* arg4->r6 */ \ + "lwz 7,20(11)\n\t" \ + "lwz 8,24(11)\n\t" \ + "lwz 9,28(11)\n\t" \ + "lwz 10,32(11)\n\t" /* arg8->r10 */ \ + "lwz 11,0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + VALGRIND_RESTORE_STACK \ + "mr %0,3" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_ppc32_linux */ + +/* ------------------------ ppc64-linux ------------------------ */ + +#if defined(PLAT_ppc64be_linux) + +/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "lr", "ctr", "xer", \ + "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ + "r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ + "r11", "r12", "r13" + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +#define VALGRIND_ALIGN_STACK \ + "mr 28,1\n\t" \ + "rldicr 1,1,0,59\n\t" +#define VALGRIND_RESTORE_STACK \ + "mr 1,28\n\t" + +/* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned + long) == 8. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+0]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+1]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+2]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+3]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+4]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+5]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+6]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+7]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+8]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+9]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-128\n\t" /* expand stack frame */ \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+10]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-128\n\t" /* expand stack frame */ \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+11]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-144\n\t" /* expand stack frame */ \ + /* arg11 */ \ + "ld 3,88(11)\n\t" \ + "std 3,128(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+12]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + _argvec[2+12] = (unsigned long)arg12; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 11,%1\n\t" \ + "std 2,-16(11)\n\t" /* save tocptr */ \ + "ld 2,-8(11)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-144\n\t" /* expand stack frame */ \ + /* arg12 */ \ + "ld 3,96(11)\n\t" \ + "std 3,136(1)\n\t" \ + /* arg11 */ \ + "ld 3,88(11)\n\t" \ + "std 3,128(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(11)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(11)\n\t" \ + "std 3,112(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(11)\n\t" /* arg1->r3 */ \ + "ld 4, 16(11)\n\t" /* arg2->r4 */ \ + "ld 5, 24(11)\n\t" /* arg3->r5 */ \ + "ld 6, 32(11)\n\t" /* arg4->r6 */ \ + "ld 7, 40(11)\n\t" /* arg5->r7 */ \ + "ld 8, 48(11)\n\t" /* arg6->r8 */ \ + "ld 9, 56(11)\n\t" /* arg7->r9 */ \ + "ld 10, 64(11)\n\t" /* arg8->r10 */ \ + "ld 11, 0(11)\n\t" /* target->r11 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R11 \ + "mr 11,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(11)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_ppc64be_linux */ + +/* ------------------------- ppc64le-linux ----------------------- */ +#if defined(PLAT_ppc64le_linux) + +/* ARGREGS: r3 r4 r5 r6 r7 r8 r9 r10 (the rest on stack somewhere) */ + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "lr", "ctr", "xer", \ + "cr0", "cr1", "cr2", "cr3", "cr4", "cr5", "cr6", "cr7", \ + "r0", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", \ + "r11", "r12", "r13" + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +#define VALGRIND_ALIGN_STACK \ + "mr 28,1\n\t" \ + "rldicr 1,1,0,59\n\t" +#define VALGRIND_RESTORE_STACK \ + "mr 1,28\n\t" + +/* These CALL_FN_ macros assume that on ppc64-linux, sizeof(unsigned + long) == 8. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+0]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+1]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+2]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+3]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+4]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 6, 32(12)\n\t" /* arg4->r6 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+5]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 6, 32(12)\n\t" /* arg4->r6 */ \ + "ld 7, 40(12)\n\t" /* arg5->r7 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+6]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 6, 32(12)\n\t" /* arg4->r6 */ \ + "ld 7, 40(12)\n\t" /* arg5->r7 */ \ + "ld 8, 48(12)\n\t" /* arg6->r8 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+7]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 6, 32(12)\n\t" /* arg4->r6 */ \ + "ld 7, 40(12)\n\t" /* arg5->r7 */ \ + "ld 8, 48(12)\n\t" /* arg6->r8 */ \ + "ld 9, 56(12)\n\t" /* arg7->r9 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+8]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 6, 32(12)\n\t" /* arg4->r6 */ \ + "ld 7, 40(12)\n\t" /* arg5->r7 */ \ + "ld 8, 48(12)\n\t" /* arg6->r8 */ \ + "ld 9, 56(12)\n\t" /* arg7->r9 */ \ + "ld 10, 64(12)\n\t" /* arg8->r10 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+9]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-128\n\t" /* expand stack frame */ \ + /* arg9 */ \ + "ld 3,72(12)\n\t" \ + "std 3,96(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 6, 32(12)\n\t" /* arg4->r6 */ \ + "ld 7, 40(12)\n\t" /* arg5->r7 */ \ + "ld 8, 48(12)\n\t" /* arg6->r8 */ \ + "ld 9, 56(12)\n\t" /* arg7->r9 */ \ + "ld 10, 64(12)\n\t" /* arg8->r10 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+10]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-128\n\t" /* expand stack frame */ \ + /* arg10 */ \ + "ld 3,80(12)\n\t" \ + "std 3,104(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(12)\n\t" \ + "std 3,96(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 6, 32(12)\n\t" /* arg4->r6 */ \ + "ld 7, 40(12)\n\t" /* arg5->r7 */ \ + "ld 8, 48(12)\n\t" /* arg6->r8 */ \ + "ld 9, 56(12)\n\t" /* arg7->r9 */ \ + "ld 10, 64(12)\n\t" /* arg8->r10 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+11]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-144\n\t" /* expand stack frame */ \ + /* arg11 */ \ + "ld 3,88(12)\n\t" \ + "std 3,112(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(12)\n\t" \ + "std 3,104(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(12)\n\t" \ + "std 3,96(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 6, 32(12)\n\t" /* arg4->r6 */ \ + "ld 7, 40(12)\n\t" /* arg5->r7 */ \ + "ld 8, 48(12)\n\t" /* arg6->r8 */ \ + "ld 9, 56(12)\n\t" /* arg7->r9 */ \ + "ld 10, 64(12)\n\t" /* arg8->r10 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3+12]; \ + volatile unsigned long _res; \ + /* _argvec[0] holds current r2 across the call */ \ + _argvec[1] = (unsigned long)_orig.r2; \ + _argvec[2] = (unsigned long)_orig.nraddr; \ + _argvec[2+1] = (unsigned long)arg1; \ + _argvec[2+2] = (unsigned long)arg2; \ + _argvec[2+3] = (unsigned long)arg3; \ + _argvec[2+4] = (unsigned long)arg4; \ + _argvec[2+5] = (unsigned long)arg5; \ + _argvec[2+6] = (unsigned long)arg6; \ + _argvec[2+7] = (unsigned long)arg7; \ + _argvec[2+8] = (unsigned long)arg8; \ + _argvec[2+9] = (unsigned long)arg9; \ + _argvec[2+10] = (unsigned long)arg10; \ + _argvec[2+11] = (unsigned long)arg11; \ + _argvec[2+12] = (unsigned long)arg12; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "mr 12,%1\n\t" \ + "std 2,-16(12)\n\t" /* save tocptr */ \ + "ld 2,-8(12)\n\t" /* use nraddr's tocptr */ \ + "addi 1,1,-144\n\t" /* expand stack frame */ \ + /* arg12 */ \ + "ld 3,96(12)\n\t" \ + "std 3,120(1)\n\t" \ + /* arg11 */ \ + "ld 3,88(12)\n\t" \ + "std 3,112(1)\n\t" \ + /* arg10 */ \ + "ld 3,80(12)\n\t" \ + "std 3,104(1)\n\t" \ + /* arg9 */ \ + "ld 3,72(12)\n\t" \ + "std 3,96(1)\n\t" \ + /* args1-8 */ \ + "ld 3, 8(12)\n\t" /* arg1->r3 */ \ + "ld 4, 16(12)\n\t" /* arg2->r4 */ \ + "ld 5, 24(12)\n\t" /* arg3->r5 */ \ + "ld 6, 32(12)\n\t" /* arg4->r6 */ \ + "ld 7, 40(12)\n\t" /* arg5->r7 */ \ + "ld 8, 48(12)\n\t" /* arg6->r8 */ \ + "ld 9, 56(12)\n\t" /* arg7->r9 */ \ + "ld 10, 64(12)\n\t" /* arg8->r10 */ \ + "ld 12, 0(12)\n\t" /* target->r12 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R12 \ + "mr 12,%1\n\t" \ + "mr %0,3\n\t" \ + "ld 2,-16(12)\n\t" /* restore tocptr */ \ + VALGRIND_RESTORE_STACK \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[2]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r28" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_ppc64le_linux */ + +/* ------------------------- arm-linux ------------------------- */ + +#if defined(PLAT_arm_linux) + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS "r0", "r1", "r2", "r3","r4", "r12", "r14" + +/* Macros to save and align the stack before making a function + call and restore it afterwards as gcc may not keep the stack + pointer aligned if it doesn't realise calls are being made + to other functions. */ + +/* This is a bit tricky. We store the original stack pointer in r10 + as it is callee-saves. gcc doesn't allow the use of r11 for some + reason. Also, we can't directly "bic" the stack pointer in thumb + mode since r13 isn't an allowed register number in that context. + So use r4 as a temporary, since that is about to get trashed + anyway, just after each use of this macro. Side effect is we need + to be very careful about any future changes, since + VALGRIND_ALIGN_STACK simply assumes r4 is usable. */ +#define VALGRIND_ALIGN_STACK \ + "mov r10, sp\n\t" \ + "mov r4, sp\n\t" \ + "bic r4, r4, #7\n\t" \ + "mov sp, r4\n\t" +#define VALGRIND_RESTORE_STACK \ + "mov sp, r10\n\t" + +/* These CALL_FN_ macros assume that on arm-linux, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #4 \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "push {r0} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "push {r0, r1} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #4 \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "push {r0, r1, r2} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "push {r0, r1, r2, r3} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #4 \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #40] \n\t" \ + "push {r0} \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #4 \n\t" \ + "ldr r0, [%1, #40] \n\t" \ + "ldr r1, [%1, #44] \n\t" \ + "push {r0, r1} \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr r0, [%1, #40] \n\t" \ + "ldr r1, [%1, #44] \n\t" \ + "ldr r2, [%1, #48] \n\t" \ + "push {r0, r1, r2} \n\t" \ + "ldr r0, [%1, #20] \n\t" \ + "ldr r1, [%1, #24] \n\t" \ + "ldr r2, [%1, #28] \n\t" \ + "ldr r3, [%1, #32] \n\t" \ + "ldr r4, [%1, #36] \n\t" \ + "push {r0, r1, r2, r3, r4} \n\t" \ + "ldr r0, [%1, #4] \n\t" \ + "ldr r1, [%1, #8] \n\t" \ + "ldr r2, [%1, #12] \n\t" \ + "ldr r3, [%1, #16] \n\t" \ + "ldr r4, [%1] \n\t" /* target->r4 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_R4 \ + VALGRIND_RESTORE_STACK \ + "mov %0, r0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "r10" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_arm_linux */ + +/* ------------------------ arm64-linux ------------------------ */ + +#if defined(PLAT_arm64_linux) + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS \ + "x0", "x1", "x2", "x3","x4", "x5", "x6", "x7", "x8", "x9", \ + "x10", "x11", "x12", "x13", "x14", "x15", "x16", "x17", \ + "x18", "x19", "x20", "x30", \ + "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", \ + "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", \ + "v18", "v19", "v20", "v21", "v22", "v23", "v24", "v25", \ + "v26", "v27", "v28", "v29", "v30", "v31" + +/* x21 is callee-saved, so we can use it to save and restore SP around + the hidden call. */ +#define VALGRIND_ALIGN_STACK \ + "mov x21, sp\n\t" \ + "bic sp, x21, #15\n\t" +#define VALGRIND_RESTORE_STACK \ + "mov sp, x21\n\t" + +/* These CALL_FN_ macros assume that on arm64-linux, + sizeof(unsigned long) == 8. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x3, [%1, #32] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x3, [%1, #32] \n\t" \ + "ldr x4, [%1, #40] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x3, [%1, #32] \n\t" \ + "ldr x4, [%1, #40] \n\t" \ + "ldr x5, [%1, #48] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x3, [%1, #32] \n\t" \ + "ldr x4, [%1, #40] \n\t" \ + "ldr x5, [%1, #48] \n\t" \ + "ldr x6, [%1, #56] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x3, [%1, #32] \n\t" \ + "ldr x4, [%1, #40] \n\t" \ + "ldr x5, [%1, #48] \n\t" \ + "ldr x6, [%1, #56] \n\t" \ + "ldr x7, [%1, #64] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #0x20 \n\t" \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x3, [%1, #32] \n\t" \ + "ldr x4, [%1, #40] \n\t" \ + "ldr x5, [%1, #48] \n\t" \ + "ldr x6, [%1, #56] \n\t" \ + "ldr x7, [%1, #64] \n\t" \ + "ldr x8, [%1, #72] \n\t" \ + "str x8, [sp, #0] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #0x20 \n\t" \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x3, [%1, #32] \n\t" \ + "ldr x4, [%1, #40] \n\t" \ + "ldr x5, [%1, #48] \n\t" \ + "ldr x6, [%1, #56] \n\t" \ + "ldr x7, [%1, #64] \n\t" \ + "ldr x8, [%1, #72] \n\t" \ + "str x8, [sp, #0] \n\t" \ + "ldr x8, [%1, #80] \n\t" \ + "str x8, [sp, #8] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #0x30 \n\t" \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x3, [%1, #32] \n\t" \ + "ldr x4, [%1, #40] \n\t" \ + "ldr x5, [%1, #48] \n\t" \ + "ldr x6, [%1, #56] \n\t" \ + "ldr x7, [%1, #64] \n\t" \ + "ldr x8, [%1, #72] \n\t" \ + "str x8, [sp, #0] \n\t" \ + "ldr x8, [%1, #80] \n\t" \ + "str x8, [sp, #8] \n\t" \ + "ldr x8, [%1, #88] \n\t" \ + "str x8, [sp, #16] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10,arg11, \ + arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + VALGRIND_ALIGN_STACK \ + "sub sp, sp, #0x30 \n\t" \ + "ldr x0, [%1, #8] \n\t" \ + "ldr x1, [%1, #16] \n\t" \ + "ldr x2, [%1, #24] \n\t" \ + "ldr x3, [%1, #32] \n\t" \ + "ldr x4, [%1, #40] \n\t" \ + "ldr x5, [%1, #48] \n\t" \ + "ldr x6, [%1, #56] \n\t" \ + "ldr x7, [%1, #64] \n\t" \ + "ldr x8, [%1, #72] \n\t" \ + "str x8, [sp, #0] \n\t" \ + "ldr x8, [%1, #80] \n\t" \ + "str x8, [sp, #8] \n\t" \ + "ldr x8, [%1, #88] \n\t" \ + "str x8, [sp, #16] \n\t" \ + "ldr x8, [%1, #96] \n\t" \ + "str x8, [sp, #24] \n\t" \ + "ldr x8, [%1] \n\t" /* target->x8 */ \ + VALGRIND_BRANCH_AND_LINK_TO_NOREDIR_X8 \ + VALGRIND_RESTORE_STACK \ + "mov %0, x0" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS, "x21" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_arm64_linux */ + +/* ------------------------- s390x-linux ------------------------- */ + +#if defined(PLAT_s390x_linux) + +/* Similar workaround as amd64 (see above), but we use r11 as frame + pointer and save the old r11 in r7. r11 might be used for + argvec, therefore we copy argvec in r1 since r1 is clobbered + after the call anyway. */ +#if defined(__GNUC__) && defined(__GCC_HAVE_DWARF2_CFI_ASM) +# define __FRAME_POINTER \ + ,"d"(__builtin_dwarf_cfa()) +# define VALGRIND_CFI_PROLOGUE \ + ".cfi_remember_state\n\t" \ + "lgr 1,%1\n\t" /* copy the argvec pointer in r1 */ \ + "lgr 7,11\n\t" \ + "lgr 11,%2\n\t" \ + ".cfi_def_cfa r11, 0\n\t" +# define VALGRIND_CFI_EPILOGUE \ + "lgr 11, 7\n\t" \ + ".cfi_restore_state\n\t" +#else +# define __FRAME_POINTER +# define VALGRIND_CFI_PROLOGUE \ + "lgr 1,%1\n\t" +# define VALGRIND_CFI_EPILOGUE +#endif + +/* Nb: On s390 the stack pointer is properly aligned *at all times* + according to the s390 GCC maintainer. (The ABI specification is not + precise in this regard.) Therefore, VALGRIND_ALIGN_STACK and + VALGRIND_RESTORE_STACK are not defined here. */ + +/* These regs are trashed by the hidden call. Note that we overwrite + r14 in s390_irgen_noredir (VEX/priv/guest_s390_irgen.c) to give the + function a proper return address. All others are ABI defined call + clobbers. */ +#if defined(__VX__) || defined(__S390_VX__) +#define __CALLER_SAVED_REGS "0", "1", "2", "3", "4", "5", "14", \ + "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", \ + "v8", "v9", "v10", "v11", "v12", "v13", "v14", "v15", \ + "v16", "v17", "v18", "v19", "v20", "v21", "v22", "v23", \ + "v24", "v25", "v26", "v27", "v28", "v29", "v30", "v31" +#else +#define __CALLER_SAVED_REGS "0", "1", "2", "3", "4", "5", "14", \ + "f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7" +#endif + +/* Nb: Although r11 is modified in the asm snippets below (inside + VALGRIND_CFI_PROLOGUE) it is not listed in the clobber section, for + two reasons: + (1) r11 is restored in VALGRIND_CFI_EPILOGUE, so effectively it is not + modified + (2) GCC will complain that r11 cannot appear inside a clobber section, + when compiled with -O -fno-omit-frame-pointer + */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 1, 0(1)\n\t" /* target->r1 */ \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "d" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +/* The call abi has the arguments in r2-r6 and stack */ +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1, arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1, arg2, arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1, arg2, arg3, arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1, arg2, arg3, arg4, arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-160\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,160\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-168\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,168\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-176\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,176\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-184\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,184\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8, arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-192\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "mvc 184(8,15), 72(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,192\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8, arg9, arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-200\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "mvc 184(8,15), 72(1)\n\t" \ + "mvc 192(8,15), 80(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,200\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8, arg9, arg10, arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-208\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "mvc 184(8,15), 72(1)\n\t" \ + "mvc 192(8,15), 80(1)\n\t" \ + "mvc 200(8,15), 88(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,208\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1, arg2, arg3, arg4, arg5, \ + arg6, arg7 ,arg8, arg9, arg10, arg11, arg12)\ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)arg1; \ + _argvec[2] = (unsigned long)arg2; \ + _argvec[3] = (unsigned long)arg3; \ + _argvec[4] = (unsigned long)arg4; \ + _argvec[5] = (unsigned long)arg5; \ + _argvec[6] = (unsigned long)arg6; \ + _argvec[7] = (unsigned long)arg7; \ + _argvec[8] = (unsigned long)arg8; \ + _argvec[9] = (unsigned long)arg9; \ + _argvec[10] = (unsigned long)arg10; \ + _argvec[11] = (unsigned long)arg11; \ + _argvec[12] = (unsigned long)arg12; \ + __asm__ volatile( \ + VALGRIND_CFI_PROLOGUE \ + "aghi 15,-216\n\t" \ + "lg 2, 8(1)\n\t" \ + "lg 3,16(1)\n\t" \ + "lg 4,24(1)\n\t" \ + "lg 5,32(1)\n\t" \ + "lg 6,40(1)\n\t" \ + "mvc 160(8,15), 48(1)\n\t" \ + "mvc 168(8,15), 56(1)\n\t" \ + "mvc 176(8,15), 64(1)\n\t" \ + "mvc 184(8,15), 72(1)\n\t" \ + "mvc 192(8,15), 80(1)\n\t" \ + "mvc 200(8,15), 88(1)\n\t" \ + "mvc 208(8,15), 96(1)\n\t" \ + "lg 1, 0(1)\n\t" \ + VALGRIND_CALL_NOREDIR_R1 \ + "aghi 15,216\n\t" \ + VALGRIND_CFI_EPILOGUE \ + "lgr %0, 2\n\t" \ + : /*out*/ "=d" (_res) \ + : /*in*/ "a" (&_argvec[0]) __FRAME_POINTER \ + : /*trash*/ "cc", "memory", __CALLER_SAVED_REGS,"6","7" \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + + +#endif /* PLAT_s390x_linux */ + +/* ------------------------- mips32-linux ----------------------- */ + +#if defined(PLAT_mips32_linux) + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS "$2", "$3", "$4", "$5", "$6", \ +"$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \ +"$25", "$31" + +/* These CALL_FN_ macros assume that on mips-linux, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16\n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $4, 4(%1) \n\t" /* arg1*/ \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "subu $29, $29, 16 \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $7, 16(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 16 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "lw $4, 20(%1) \n\t" \ + "subu $29, $29, 24\n\t" \ + "sw $4, 16($29) \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $7, 16(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 24 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "lw $4, 20(%1) \n\t" \ + "subu $29, $29, 32\n\t" \ + "sw $4, 16($29) \n\t" \ + "lw $4, 24(%1) \n\t" \ + "nop\n\t" \ + "sw $4, 20($29) \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $7, 16(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 32 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "lw $4, 20(%1) \n\t" \ + "subu $29, $29, 32\n\t" \ + "sw $4, 16($29) \n\t" \ + "lw $4, 24(%1) \n\t" \ + "sw $4, 20($29) \n\t" \ + "lw $4, 28(%1) \n\t" \ + "sw $4, 24($29) \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $7, 16(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 32 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "lw $4, 20(%1) \n\t" \ + "subu $29, $29, 40\n\t" \ + "sw $4, 16($29) \n\t" \ + "lw $4, 24(%1) \n\t" \ + "sw $4, 20($29) \n\t" \ + "lw $4, 28(%1) \n\t" \ + "sw $4, 24($29) \n\t" \ + "lw $4, 32(%1) \n\t" \ + "sw $4, 28($29) \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $7, 16(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 40 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "lw $4, 20(%1) \n\t" \ + "subu $29, $29, 40\n\t" \ + "sw $4, 16($29) \n\t" \ + "lw $4, 24(%1) \n\t" \ + "sw $4, 20($29) \n\t" \ + "lw $4, 28(%1) \n\t" \ + "sw $4, 24($29) \n\t" \ + "lw $4, 32(%1) \n\t" \ + "sw $4, 28($29) \n\t" \ + "lw $4, 36(%1) \n\t" \ + "sw $4, 32($29) \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $7, 16(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 40 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "lw $4, 20(%1) \n\t" \ + "subu $29, $29, 48\n\t" \ + "sw $4, 16($29) \n\t" \ + "lw $4, 24(%1) \n\t" \ + "sw $4, 20($29) \n\t" \ + "lw $4, 28(%1) \n\t" \ + "sw $4, 24($29) \n\t" \ + "lw $4, 32(%1) \n\t" \ + "sw $4, 28($29) \n\t" \ + "lw $4, 36(%1) \n\t" \ + "sw $4, 32($29) \n\t" \ + "lw $4, 40(%1) \n\t" \ + "sw $4, 36($29) \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $7, 16(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 48 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "lw $4, 20(%1) \n\t" \ + "subu $29, $29, 48\n\t" \ + "sw $4, 16($29) \n\t" \ + "lw $4, 24(%1) \n\t" \ + "sw $4, 20($29) \n\t" \ + "lw $4, 28(%1) \n\t" \ + "sw $4, 24($29) \n\t" \ + "lw $4, 32(%1) \n\t" \ + "sw $4, 28($29) \n\t" \ + "lw $4, 36(%1) \n\t" \ + "sw $4, 32($29) \n\t" \ + "lw $4, 40(%1) \n\t" \ + "sw $4, 36($29) \n\t" \ + "lw $4, 44(%1) \n\t" \ + "sw $4, 40($29) \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $7, 16(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 48 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + "subu $29, $29, 8 \n\t" \ + "sw $28, 0($29) \n\t" \ + "sw $31, 4($29) \n\t" \ + "lw $4, 20(%1) \n\t" \ + "subu $29, $29, 56\n\t" \ + "sw $4, 16($29) \n\t" \ + "lw $4, 24(%1) \n\t" \ + "sw $4, 20($29) \n\t" \ + "lw $4, 28(%1) \n\t" \ + "sw $4, 24($29) \n\t" \ + "lw $4, 32(%1) \n\t" \ + "sw $4, 28($29) \n\t" \ + "lw $4, 36(%1) \n\t" \ + "sw $4, 32($29) \n\t" \ + "lw $4, 40(%1) \n\t" \ + "sw $4, 36($29) \n\t" \ + "lw $4, 44(%1) \n\t" \ + "sw $4, 40($29) \n\t" \ + "lw $4, 48(%1) \n\t" \ + "sw $4, 44($29) \n\t" \ + "lw $4, 4(%1) \n\t" \ + "lw $5, 8(%1) \n\t" \ + "lw $6, 12(%1) \n\t" \ + "lw $7, 16(%1) \n\t" \ + "lw $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "addu $29, $29, 56 \n\t" \ + "lw $28, 0($29) \n\t" \ + "lw $31, 4($29) \n\t" \ + "addu $29, $29, 8 \n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_mips32_linux */ + +/* ------------------------- nanomips-linux -------------------- */ + +#if defined(PLAT_nanomips_linux) + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS "$t4", "$t5", "$a0", "$a1", "$a2", \ +"$a3", "$a4", "$a5", "$a6", "$a7", "$t0", "$t1", "$t2", "$t3", \ +"$t8","$t9", "$at" + +/* These CALL_FN_ macros assume that on mips-linux, sizeof(unsigned + long) == 4. */ + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[1]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + __asm__ volatile( \ + "lw $t9, 0(%1)\n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[2]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + __asm__ volatile( \ + "lw $t9, 0(%1)\n\t" \ + "lw $a0, 4(%1)\n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[3]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + __asm__ volatile( \ + "lw $t9, 0(%1)\n\t" \ + "lw $a0, 4(%1)\n\t" \ + "lw $a1, 8(%1)\n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[4]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + __asm__ volatile( \ + "lw $t9, 0(%1)\n\t" \ + "lw $a0, 4(%1)\n\t" \ + "lw $a1, 8(%1)\n\t" \ + "lw $a2,12(%1)\n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[5]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + __asm__ volatile( \ + "lw $t9, 0(%1)\n\t" \ + "lw $a0, 4(%1)\n\t" \ + "lw $a1, 8(%1)\n\t" \ + "lw $a2,12(%1)\n\t" \ + "lw $a3,16(%1)\n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[6]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + __asm__ volatile( \ + "lw $t9, 0(%1)\n\t" \ + "lw $a0, 4(%1)\n\t" \ + "lw $a1, 8(%1)\n\t" \ + "lw $a2,12(%1)\n\t" \ + "lw $a3,16(%1)\n\t" \ + "lw $a4,20(%1)\n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[7]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + __asm__ volatile( \ + "lw $t9, 0(%1)\n\t" \ + "lw $a0, 4(%1)\n\t" \ + "lw $a1, 8(%1)\n\t" \ + "lw $a2,12(%1)\n\t" \ + "lw $a3,16(%1)\n\t" \ + "lw $a4,20(%1)\n\t" \ + "lw $a5,24(%1)\n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[8]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + __asm__ volatile( \ + "lw $t9, 0(%1)\n\t" \ + "lw $a0, 4(%1)\n\t" \ + "lw $a1, 8(%1)\n\t" \ + "lw $a2,12(%1)\n\t" \ + "lw $a3,16(%1)\n\t" \ + "lw $a4,20(%1)\n\t" \ + "lw $a5,24(%1)\n\t" \ + "lw $a6,28(%1)\n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[9]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + __asm__ volatile( \ + "lw $t9, 0(%1)\n\t" \ + "lw $a0, 4(%1)\n\t" \ + "lw $a1, 8(%1)\n\t" \ + "lw $a2,12(%1)\n\t" \ + "lw $a3,16(%1)\n\t" \ + "lw $a4,20(%1)\n\t" \ + "lw $a5,24(%1)\n\t" \ + "lw $a6,28(%1)\n\t" \ + "lw $a7,32(%1)\n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[10]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + __asm__ volatile( \ + "addiu $sp, $sp, -16 \n\t" \ + "lw $t9,36(%1) \n\t" \ + "sw $t9, 0($sp) \n\t" \ + "lw $t9, 0(%1) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2,12(%1) \n\t" \ + "lw $a3,16(%1) \n\t" \ + "lw $a4,20(%1) \n\t" \ + "lw $a5,24(%1) \n\t" \ + "lw $a6,28(%1) \n\t" \ + "lw $a7,32(%1) \n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0 \n\t" \ + "addiu $sp, $sp, 16 \n\t" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[11]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + __asm__ volatile( \ + "addiu $sp, $sp, -16 \n\t" \ + "lw $t9,36(%1) \n\t" \ + "sw $t9, 0($sp) \n\t" \ + "lw $t9,40(%1) \n\t" \ + "sw $t9, 4($sp) \n\t" \ + "lw $t9, 0(%1) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2,12(%1) \n\t" \ + "lw $a3,16(%1) \n\t" \ + "lw $a4,20(%1) \n\t" \ + "lw $a5,24(%1) \n\t" \ + "lw $a6,28(%1) \n\t" \ + "lw $a7,32(%1) \n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0 \n\t" \ + "addiu $sp, $sp, 16 \n\t" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[12]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + __asm__ volatile( \ + "addiu $sp, $sp, -16 \n\t" \ + "lw $t9,36(%1) \n\t" \ + "sw $t9, 0($sp) \n\t" \ + "lw $t9,40(%1) \n\t" \ + "sw $t9, 4($sp) \n\t" \ + "lw $t9,44(%1) \n\t" \ + "sw $t9, 8($sp) \n\t" \ + "lw $t9, 0(%1) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2,12(%1) \n\t" \ + "lw $a3,16(%1) \n\t" \ + "lw $a4,20(%1) \n\t" \ + "lw $a5,24(%1) \n\t" \ + "lw $a6,28(%1) \n\t" \ + "lw $a7,32(%1) \n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0 \n\t" \ + "addiu $sp, $sp, 16 \n\t" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long _argvec[13]; \ + volatile unsigned long _res; \ + _argvec[0] = (unsigned long)_orig.nraddr; \ + _argvec[1] = (unsigned long)(arg1); \ + _argvec[2] = (unsigned long)(arg2); \ + _argvec[3] = (unsigned long)(arg3); \ + _argvec[4] = (unsigned long)(arg4); \ + _argvec[5] = (unsigned long)(arg5); \ + _argvec[6] = (unsigned long)(arg6); \ + _argvec[7] = (unsigned long)(arg7); \ + _argvec[8] = (unsigned long)(arg8); \ + _argvec[9] = (unsigned long)(arg9); \ + _argvec[10] = (unsigned long)(arg10); \ + _argvec[11] = (unsigned long)(arg11); \ + _argvec[12] = (unsigned long)(arg12); \ + __asm__ volatile( \ + "addiu $sp, $sp, -16 \n\t" \ + "lw $t9,36(%1) \n\t" \ + "sw $t9, 0($sp) \n\t" \ + "lw $t9,40(%1) \n\t" \ + "sw $t9, 4($sp) \n\t" \ + "lw $t9,44(%1) \n\t" \ + "sw $t9, 8($sp) \n\t" \ + "lw $t9,48(%1) \n\t" \ + "sw $t9,12($sp) \n\t" \ + "lw $t9, 0(%1) \n\t" \ + "lw $a0, 4(%1) \n\t" \ + "lw $a1, 8(%1) \n\t" \ + "lw $a2,12(%1) \n\t" \ + "lw $a3,16(%1) \n\t" \ + "lw $a4,20(%1) \n\t" \ + "lw $a5,24(%1) \n\t" \ + "lw $a6,28(%1) \n\t" \ + "lw $a7,32(%1) \n\t" \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $a0 \n\t" \ + "addiu $sp, $sp, 16 \n\t" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) _res; \ + } while (0) + +#endif /* PLAT_nanomips_linux */ + +/* ------------------------- mips64-linux ------------------------- */ + +#if defined(PLAT_mips64_linux) + +/* These regs are trashed by the hidden call. */ +#define __CALLER_SAVED_REGS "$2", "$3", "$4", "$5", "$6", \ +"$7", "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15", "$24", \ +"$25", "$31" + +/* These CALL_FN_ macros assume that on mips64-linux, + sizeof(long long) == 8. */ + +#define MIPS64_LONG2REG_CAST(x) ((long long)(long)x) + +#define CALL_FN_W_v(lval, orig) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[1]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + __asm__ volatile( \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "0" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_W(lval, orig, arg1) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[2]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + __asm__ volatile( \ + "ld $4, 8(%1)\n\t" /* arg1*/ \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_WW(lval, orig, arg1,arg2) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[3]; \ + volatile unsigned long long _res; \ + _argvec[0] = _orig.nraddr; \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + __asm__ volatile( \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + + +#define CALL_FN_W_WWW(lval, orig, arg1,arg2,arg3) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[4]; \ + volatile unsigned long long _res; \ + _argvec[0] = _orig.nraddr; \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + __asm__ volatile( \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_WWWW(lval, orig, arg1,arg2,arg3,arg4) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[5]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ + __asm__ volatile( \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $7, 32(%1)\n\t" \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_5W(lval, orig, arg1,arg2,arg3,arg4,arg5) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[6]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ + _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ + __asm__ volatile( \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $7, 32(%1)\n\t" \ + "ld $8, 40(%1)\n\t" \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_6W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[7]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ + _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ + _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ + __asm__ volatile( \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $7, 32(%1)\n\t" \ + "ld $8, 40(%1)\n\t" \ + "ld $9, 48(%1)\n\t" \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_7W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[8]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ + _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ + _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ + _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ + __asm__ volatile( \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $7, 32(%1)\n\t" \ + "ld $8, 40(%1)\n\t" \ + "ld $9, 48(%1)\n\t" \ + "ld $10, 56(%1)\n\t" \ + "ld $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_8W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[9]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ + _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ + _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ + _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ + _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ + __asm__ volatile( \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $7, 32(%1)\n\t" \ + "ld $8, 40(%1)\n\t" \ + "ld $9, 48(%1)\n\t" \ + "ld $10, 56(%1)\n\t" \ + "ld $11, 64(%1)\n\t" \ + "ld $25, 0(%1) \n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_9W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[10]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ + _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ + _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ + _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ + _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ + _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ + __asm__ volatile( \ + "dsubu $29, $29, 8\n\t" \ + "ld $4, 72(%1)\n\t" \ + "sd $4, 0($29)\n\t" \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $7, 32(%1)\n\t" \ + "ld $8, 40(%1)\n\t" \ + "ld $9, 48(%1)\n\t" \ + "ld $10, 56(%1)\n\t" \ + "ld $11, 64(%1)\n\t" \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "daddu $29, $29, 8\n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_10W(lval, orig, arg1,arg2,arg3,arg4,arg5,arg6, \ + arg7,arg8,arg9,arg10) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[11]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ + _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ + _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ + _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ + _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ + _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ + _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \ + __asm__ volatile( \ + "dsubu $29, $29, 16\n\t" \ + "ld $4, 72(%1)\n\t" \ + "sd $4, 0($29)\n\t" \ + "ld $4, 80(%1)\n\t" \ + "sd $4, 8($29)\n\t" \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $7, 32(%1)\n\t" \ + "ld $8, 40(%1)\n\t" \ + "ld $9, 48(%1)\n\t" \ + "ld $10, 56(%1)\n\t" \ + "ld $11, 64(%1)\n\t" \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "daddu $29, $29, 16\n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_11W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[12]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ + _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ + _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ + _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ + _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ + _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ + _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \ + _argvec[11] = MIPS64_LONG2REG_CAST(arg11); \ + __asm__ volatile( \ + "dsubu $29, $29, 24\n\t" \ + "ld $4, 72(%1)\n\t" \ + "sd $4, 0($29)\n\t" \ + "ld $4, 80(%1)\n\t" \ + "sd $4, 8($29)\n\t" \ + "ld $4, 88(%1)\n\t" \ + "sd $4, 16($29)\n\t" \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $7, 32(%1)\n\t" \ + "ld $8, 40(%1)\n\t" \ + "ld $9, 48(%1)\n\t" \ + "ld $10, 56(%1)\n\t" \ + "ld $11, 64(%1)\n\t" \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "daddu $29, $29, 24\n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#define CALL_FN_W_12W(lval, orig, arg1,arg2,arg3,arg4,arg5, \ + arg6,arg7,arg8,arg9,arg10, \ + arg11,arg12) \ + do { \ + volatile OrigFn _orig = (orig); \ + volatile unsigned long long _argvec[13]; \ + volatile unsigned long long _res; \ + _argvec[0] = MIPS64_LONG2REG_CAST(_orig.nraddr); \ + _argvec[1] = MIPS64_LONG2REG_CAST(arg1); \ + _argvec[2] = MIPS64_LONG2REG_CAST(arg2); \ + _argvec[3] = MIPS64_LONG2REG_CAST(arg3); \ + _argvec[4] = MIPS64_LONG2REG_CAST(arg4); \ + _argvec[5] = MIPS64_LONG2REG_CAST(arg5); \ + _argvec[6] = MIPS64_LONG2REG_CAST(arg6); \ + _argvec[7] = MIPS64_LONG2REG_CAST(arg7); \ + _argvec[8] = MIPS64_LONG2REG_CAST(arg8); \ + _argvec[9] = MIPS64_LONG2REG_CAST(arg9); \ + _argvec[10] = MIPS64_LONG2REG_CAST(arg10); \ + _argvec[11] = MIPS64_LONG2REG_CAST(arg11); \ + _argvec[12] = MIPS64_LONG2REG_CAST(arg12); \ + __asm__ volatile( \ + "dsubu $29, $29, 32\n\t" \ + "ld $4, 72(%1)\n\t" \ + "sd $4, 0($29)\n\t" \ + "ld $4, 80(%1)\n\t" \ + "sd $4, 8($29)\n\t" \ + "ld $4, 88(%1)\n\t" \ + "sd $4, 16($29)\n\t" \ + "ld $4, 96(%1)\n\t" \ + "sd $4, 24($29)\n\t" \ + "ld $4, 8(%1)\n\t" \ + "ld $5, 16(%1)\n\t" \ + "ld $6, 24(%1)\n\t" \ + "ld $7, 32(%1)\n\t" \ + "ld $8, 40(%1)\n\t" \ + "ld $9, 48(%1)\n\t" \ + "ld $10, 56(%1)\n\t" \ + "ld $11, 64(%1)\n\t" \ + "ld $25, 0(%1)\n\t" /* target->t9 */ \ + VALGRIND_CALL_NOREDIR_T9 \ + "daddu $29, $29, 32\n\t" \ + "move %0, $2\n" \ + : /*out*/ "=r" (_res) \ + : /*in*/ "r" (&_argvec[0]) \ + : /*trash*/ "memory", __CALLER_SAVED_REGS \ + ); \ + lval = (__typeof__(lval)) (long)_res; \ + } while (0) + +#endif /* PLAT_mips64_linux */ + +/* ------------------------------------------------------------------ */ +/* ARCHITECTURE INDEPENDENT MACROS for CLIENT REQUESTS. */ +/* */ +/* ------------------------------------------------------------------ */ + +/* Some request codes. There are many more of these, but most are not + exposed to end-user view. These are the public ones, all of the + form 0x1000 + small_number. + + Core ones are in the range 0x00000000--0x0000ffff. The non-public + ones start at 0x2000. +*/ + +/* These macros are used by tools -- they must be public, but don't + embed them into other programs. */ +#define VG_USERREQ_TOOL_BASE(a,b) \ + ((unsigned int)(((a)&0xff) << 24 | ((b)&0xff) << 16)) +#define VG_IS_TOOL_USERREQ(a, b, v) \ + (VG_USERREQ_TOOL_BASE(a,b) == ((v) & 0xffff0000)) + +/* !! ABIWARNING !! ABIWARNING !! ABIWARNING !! ABIWARNING !! + This enum comprises an ABI exported by Valgrind to programs + which use client requests. DO NOT CHANGE THE NUMERIC VALUES OF THESE + ENTRIES, NOR DELETE ANY -- add new ones at the end of the most + relevant group. */ +typedef + enum { VG_USERREQ__RUNNING_ON_VALGRIND = 0x1001, + VG_USERREQ__DISCARD_TRANSLATIONS = 0x1002, + + /* These allow any function to be called from the simulated + CPU but run on the real CPU. Nb: the first arg passed to + the function is always the ThreadId of the running + thread! So CLIENT_CALL0 actually requires a 1 arg + function, etc. */ + VG_USERREQ__CLIENT_CALL0 = 0x1101, + VG_USERREQ__CLIENT_CALL1 = 0x1102, + VG_USERREQ__CLIENT_CALL2 = 0x1103, + VG_USERREQ__CLIENT_CALL3 = 0x1104, + + /* Can be useful in regression testing suites -- eg. can + send Valgrind's output to /dev/null and still count + errors. */ + VG_USERREQ__COUNT_ERRORS = 0x1201, + + /* Allows the client program and/or gdbserver to execute a monitor + command. */ + VG_USERREQ__GDB_MONITOR_COMMAND = 0x1202, + + /* Allows the client program to change a dynamic command line + option. */ + VG_USERREQ__CLO_CHANGE = 0x1203, + + /* These are useful and can be interpreted by any tool that + tracks malloc() et al, by using vg_replace_malloc.c. */ + VG_USERREQ__MALLOCLIKE_BLOCK = 0x1301, + VG_USERREQ__RESIZEINPLACE_BLOCK = 0x130b, + VG_USERREQ__FREELIKE_BLOCK = 0x1302, + /* Memory pool support. */ + VG_USERREQ__CREATE_MEMPOOL = 0x1303, + VG_USERREQ__DESTROY_MEMPOOL = 0x1304, + VG_USERREQ__MEMPOOL_ALLOC = 0x1305, + VG_USERREQ__MEMPOOL_FREE = 0x1306, + VG_USERREQ__MEMPOOL_TRIM = 0x1307, + VG_USERREQ__MOVE_MEMPOOL = 0x1308, + VG_USERREQ__MEMPOOL_CHANGE = 0x1309, + VG_USERREQ__MEMPOOL_EXISTS = 0x130a, + + /* Allow printfs to valgrind log. */ + /* The first two pass the va_list argument by value, which + assumes it is the same size as or smaller than a UWord, + which generally isn't the case. Hence are deprecated. + The second two pass the vargs by reference and so are + immune to this problem. */ + /* both :: char* fmt, va_list vargs (DEPRECATED) */ + VG_USERREQ__PRINTF = 0x1401, + VG_USERREQ__PRINTF_BACKTRACE = 0x1402, + /* both :: char* fmt, va_list* vargs */ + VG_USERREQ__PRINTF_VALIST_BY_REF = 0x1403, + VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF = 0x1404, + + /* Stack support. */ + VG_USERREQ__STACK_REGISTER = 0x1501, + VG_USERREQ__STACK_DEREGISTER = 0x1502, + VG_USERREQ__STACK_CHANGE = 0x1503, + + /* Wine support */ + VG_USERREQ__LOAD_PDB_DEBUGINFO = 0x1601, + + /* Querying of debug info. */ + VG_USERREQ__MAP_IP_TO_SRCLOC = 0x1701, + + /* Disable/enable error reporting level. Takes a single + Word arg which is the delta to this thread's error + disablement indicator. Hence 1 disables or further + disables errors, and -1 moves back towards enablement. + Other values are not allowed. */ + VG_USERREQ__CHANGE_ERR_DISABLEMENT = 0x1801, + + /* Some requests used for Valgrind internal, such as + self-test or self-hosting. */ + /* Initialise IR injection */ + VG_USERREQ__VEX_INIT_FOR_IRI = 0x1901, + /* Used by Inner Valgrind to inform Outer Valgrind where to + find the list of inner guest threads */ + VG_USERREQ__INNER_THREADS = 0x1902 + } Vg_ClientRequest; + +#if !defined(__GNUC__) +# define __extension__ /* */ +#endif + + +/* Returns the number of Valgrinds this code is running under. That + is, 0 if running natively, 1 if running under Valgrind, 2 if + running under Valgrind which is running under another Valgrind, + etc. */ +#define RUNNING_ON_VALGRIND \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* if not */, \ + VG_USERREQ__RUNNING_ON_VALGRIND, \ + 0, 0, 0, 0, 0) \ + + +/* Discard translation of code in the range [_qzz_addr .. _qzz_addr + + _qzz_len - 1]. Useful if you are debugging a JITter or some such, + since it provides a way to make sure valgrind will retranslate the + invalidated area. Returns no value. */ +#define VALGRIND_DISCARD_TRANSLATIONS(_qzz_addr,_qzz_len) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DISCARD_TRANSLATIONS, \ + _qzz_addr, _qzz_len, 0, 0, 0) + +#define VALGRIND_INNER_THREADS(_qzz_addr) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__INNER_THREADS, \ + _qzz_addr, 0, 0, 0, 0) + + +/* These requests are for getting Valgrind itself to print something. + Possibly with a backtrace. This is a really ugly hack. The return value + is the number of characters printed, excluding the "**** " part at the + start and the backtrace (if present). */ + +#if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER) +/* Modern GCC will optimize the static routine out if unused, + and unused attribute will shut down warnings about it. */ +static int VALGRIND_PRINTF(const char *format, ...) + __attribute__((format(__printf__, 1, 2), __unused__)); +#endif +static int +#if defined(_MSC_VER) +__inline +#endif +VALGRIND_PRINTF(const char *format, ...) +{ +#if defined(NVALGRIND) + (void)format; + return 0; +#else /* NVALGRIND */ +#if defined(_MSC_VER) || defined(__MINGW64__) + uintptr_t _qzz_res; +#else + unsigned long _qzz_res; +#endif + va_list vargs; + va_start(vargs, format); +#if defined(_MSC_VER) || defined(__MINGW64__) + _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, + VG_USERREQ__PRINTF_VALIST_BY_REF, + (uintptr_t)format, + (uintptr_t)&vargs, + 0, 0, 0); +#else + _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, + VG_USERREQ__PRINTF_VALIST_BY_REF, + (unsigned long)format, + (unsigned long)&vargs, + 0, 0, 0); +#endif + va_end(vargs); + return (int)_qzz_res; +#endif /* NVALGRIND */ +} + +#if defined(__GNUC__) || defined(__INTEL_COMPILER) && !defined(_MSC_VER) +static int VALGRIND_PRINTF_BACKTRACE(const char *format, ...) + __attribute__((format(__printf__, 1, 2), __unused__)); +#endif +static int +#if defined(_MSC_VER) +__inline +#endif +VALGRIND_PRINTF_BACKTRACE(const char *format, ...) +{ +#if defined(NVALGRIND) + (void)format; + return 0; +#else /* NVALGRIND */ +#if defined(_MSC_VER) || defined(__MINGW64__) + uintptr_t _qzz_res; +#else + unsigned long _qzz_res; +#endif + va_list vargs; + va_start(vargs, format); +#if defined(_MSC_VER) || defined(__MINGW64__) + _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, + VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, + (uintptr_t)format, + (uintptr_t)&vargs, + 0, 0, 0); +#else + _qzz_res = VALGRIND_DO_CLIENT_REQUEST_EXPR(0, + VG_USERREQ__PRINTF_BACKTRACE_VALIST_BY_REF, + (unsigned long)format, + (unsigned long)&vargs, + 0, 0, 0); +#endif + va_end(vargs); + return (int)_qzz_res; +#endif /* NVALGRIND */ +} + + +/* These requests allow control to move from the simulated CPU to the + real CPU, calling an arbitrary function. + + Note that the current ThreadId is inserted as the first argument. + So this call: + + VALGRIND_NON_SIMD_CALL2(f, arg1, arg2) + + requires f to have this signature: + + Word f(Word tid, Word arg1, Word arg2) + + where "Word" is a word-sized type. + + Note that these client requests are not entirely reliable. For example, + if you call a function with them that subsequently calls printf(), + there's a high chance Valgrind will crash. Generally, your prospects of + these working are made higher if the called function does not refer to + any global variables, and does not refer to any libc or other functions + (printf et al). Any kind of entanglement with libc or dynamic linking is + likely to have a bad outcome, for tricky reasons which we've grappled + with a lot in the past. +*/ +#define VALGRIND_NON_SIMD_CALL0(_qyy_fn) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__CLIENT_CALL0, \ + _qyy_fn, \ + 0, 0, 0, 0) + +#define VALGRIND_NON_SIMD_CALL1(_qyy_fn, _qyy_arg1) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__CLIENT_CALL1, \ + _qyy_fn, \ + _qyy_arg1, 0, 0, 0) + +#define VALGRIND_NON_SIMD_CALL2(_qyy_fn, _qyy_arg1, _qyy_arg2) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__CLIENT_CALL2, \ + _qyy_fn, \ + _qyy_arg1, _qyy_arg2, 0, 0) + +#define VALGRIND_NON_SIMD_CALL3(_qyy_fn, _qyy_arg1, _qyy_arg2, _qyy_arg3) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0 /* default return */, \ + VG_USERREQ__CLIENT_CALL3, \ + _qyy_fn, \ + _qyy_arg1, _qyy_arg2, \ + _qyy_arg3, 0) + + +/* Counts the number of errors that have been recorded by a tool. Nb: + the tool must record the errors with VG_(maybe_record_error)() or + VG_(unique_error)() for them to be counted. */ +#define VALGRIND_COUNT_ERRORS \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR( \ + 0 /* default return */, \ + VG_USERREQ__COUNT_ERRORS, \ + 0, 0, 0, 0, 0) + +/* Several Valgrind tools (Memcheck, Massif, Helgrind, DRD) rely on knowing + when heap blocks are allocated in order to give accurate results. This + happens automatically for the standard allocator functions such as + malloc(), calloc(), realloc(), memalign(), new, new[], free(), delete, + delete[], etc. + + But if your program uses a custom allocator, this doesn't automatically + happen, and Valgrind will not do as well. For example, if you allocate + superblocks with mmap() and then allocates chunks of the superblocks, all + Valgrind's observations will be at the mmap() level and it won't know that + the chunks should be considered separate entities. In Memcheck's case, + that means you probably won't get heap block overrun detection (because + there won't be redzones marked as unaddressable) and you definitely won't + get any leak detection. + + The following client requests allow a custom allocator to be annotated so + that it can be handled accurately by Valgrind. + + VALGRIND_MALLOCLIKE_BLOCK marks a region of memory as having been allocated + by a malloc()-like function. For Memcheck (an illustrative case), this + does two things: + + - It records that the block has been allocated. This means any addresses + within the block mentioned in error messages will be + identified as belonging to the block. It also means that if the block + isn't freed it will be detected by the leak checker. + + - It marks the block as being addressable and undefined (if 'is_zeroed' is + not set), or addressable and defined (if 'is_zeroed' is set). This + controls how accesses to the block by the program are handled. + + 'addr' is the start of the usable block (ie. after any + redzone), 'sizeB' is its size. 'rzB' is the redzone size if the allocator + can apply redzones -- these are blocks of padding at the start and end of + each block. Adding redzones is recommended as it makes it much more likely + Valgrind will spot block overruns. `is_zeroed' indicates if the memory is + zeroed (or filled with another predictable value), as is the case for + calloc(). + + VALGRIND_MALLOCLIKE_BLOCK should be put immediately after the point where a + heap block -- that will be used by the client program -- is allocated. + It's best to put it at the outermost level of the allocator if possible; + for example, if you have a function my_alloc() which calls + internal_alloc(), and the client request is put inside internal_alloc(), + stack traces relating to the heap block will contain entries for both + my_alloc() and internal_alloc(), which is probably not what you want. + + For Memcheck users: if you use VALGRIND_MALLOCLIKE_BLOCK to carve out + custom blocks from within a heap block, B, that has been allocated with + malloc/calloc/new/etc, then block B will be *ignored* during leak-checking + -- the custom blocks will take precedence. + + VALGRIND_FREELIKE_BLOCK is the partner to VALGRIND_MALLOCLIKE_BLOCK. For + Memcheck, it does two things: + + - It records that the block has been deallocated. This assumes that the + block was annotated as having been allocated via + VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued. + + - It marks the block as being unaddressable. + + VALGRIND_FREELIKE_BLOCK should be put immediately after the point where a + heap block is deallocated. + + VALGRIND_RESIZEINPLACE_BLOCK informs a tool about reallocation. For + Memcheck, it does four things: + + - It records that the size of a block has been changed. This assumes that + the block was annotated as having been allocated via + VALGRIND_MALLOCLIKE_BLOCK. Otherwise, an error will be issued. + + - If the block shrunk, it marks the freed memory as being unaddressable. + + - If the block grew, it marks the new area as undefined and defines a red + zone past the end of the new block. + + - The V-bits of the overlap between the old and the new block are preserved. + + VALGRIND_RESIZEINPLACE_BLOCK should be put after allocation of the new block + and before deallocation of the old block. + + In many cases, these three client requests will not be enough to get your + allocator working well with Memcheck. More specifically, if your allocator + writes to freed blocks in any way then a VALGRIND_MAKE_MEM_UNDEFINED call + will be necessary to mark the memory as addressable just before the zeroing + occurs, otherwise you'll get a lot of invalid write errors. For example, + you'll need to do this if your allocator recycles freed blocks, but it + zeroes them before handing them back out (via VALGRIND_MALLOCLIKE_BLOCK). + Alternatively, if your allocator reuses freed blocks for allocator-internal + data structures, VALGRIND_MAKE_MEM_UNDEFINED calls will also be necessary. + + Really, what's happening is a blurring of the lines between the client + program and the allocator... after VALGRIND_FREELIKE_BLOCK is called, the + memory should be considered unaddressable to the client program, but the + allocator knows more than the rest of the client program and so may be able + to safely access it. Extra client requests are necessary for Valgrind to + understand the distinction between the allocator and the rest of the + program. + + Ignored if addr == 0. +*/ +#define VALGRIND_MALLOCLIKE_BLOCK(addr, sizeB, rzB, is_zeroed) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MALLOCLIKE_BLOCK, \ + addr, sizeB, rzB, is_zeroed, 0) + +/* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details. + Ignored if addr == 0. +*/ +#define VALGRIND_RESIZEINPLACE_BLOCK(addr, oldSizeB, newSizeB, rzB) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__RESIZEINPLACE_BLOCK, \ + addr, oldSizeB, newSizeB, rzB, 0) + +/* See the comment for VALGRIND_MALLOCLIKE_BLOCK for details. + Ignored if addr == 0. +*/ +#define VALGRIND_FREELIKE_BLOCK(addr, rzB) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__FREELIKE_BLOCK, \ + addr, rzB, 0, 0, 0) + +/* Create a memory pool. */ +#define VALGRIND_CREATE_MEMPOOL(pool, rzB, is_zeroed) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL, \ + pool, rzB, is_zeroed, 0, 0) + +/* Create a memory pool with some flags specifying extended behaviour. + When flags is zero, the behaviour is identical to VALGRIND_CREATE_MEMPOOL. + + The flag VALGRIND_MEMPOOL_METAPOOL specifies that the pieces of memory + associated with the pool using VALGRIND_MEMPOOL_ALLOC will be used + by the application as superblocks to dole out MALLOC_LIKE blocks using + VALGRIND_MALLOCLIKE_BLOCK. In other words, a meta pool is a "2 levels" + pool : first level is the blocks described by VALGRIND_MEMPOOL_ALLOC. + The second level blocks are described using VALGRIND_MALLOCLIKE_BLOCK. + Note that the association between the pool and the second level blocks + is implicit : second level blocks will be located inside first level + blocks. It is necessary to use the VALGRIND_MEMPOOL_METAPOOL flag + for such 2 levels pools, as otherwise valgrind will detect overlapping + memory blocks, and will abort execution (e.g. during leak search). + + Such a meta pool can also be marked as an 'auto free' pool using the flag + VALGRIND_MEMPOOL_AUTO_FREE, which must be OR-ed together with the + VALGRIND_MEMPOOL_METAPOOL. For an 'auto free' pool, VALGRIND_MEMPOOL_FREE + will automatically free the second level blocks that are contained + inside the first level block freed with VALGRIND_MEMPOOL_FREE. + In other words, calling VALGRIND_MEMPOOL_FREE will cause implicit calls + to VALGRIND_FREELIKE_BLOCK for all the second level blocks included + in the first level block. + Note: it is an error to use the VALGRIND_MEMPOOL_AUTO_FREE flag + without the VALGRIND_MEMPOOL_METAPOOL flag. +*/ +#define VALGRIND_MEMPOOL_AUTO_FREE 1 +#define VALGRIND_MEMPOOL_METAPOOL 2 +#define VALGRIND_CREATE_MEMPOOL_EXT(pool, rzB, is_zeroed, flags) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CREATE_MEMPOOL, \ + pool, rzB, is_zeroed, flags, 0) + +/* Destroy a memory pool. */ +#define VALGRIND_DESTROY_MEMPOOL(pool) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__DESTROY_MEMPOOL, \ + pool, 0, 0, 0, 0) + +/* Associate a piece of memory with a memory pool. */ +#define VALGRIND_MEMPOOL_ALLOC(pool, addr, size) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_ALLOC, \ + pool, addr, size, 0, 0) + +/* Disassociate a piece of memory from a memory pool. */ +#define VALGRIND_MEMPOOL_FREE(pool, addr) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_FREE, \ + pool, addr, 0, 0, 0) + +/* Disassociate any pieces outside a particular range. */ +#define VALGRIND_MEMPOOL_TRIM(pool, addr, size) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_TRIM, \ + pool, addr, size, 0, 0) + +/* Resize and/or move a piece associated with a memory pool. */ +#define VALGRIND_MOVE_MEMPOOL(poolA, poolB) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MOVE_MEMPOOL, \ + poolA, poolB, 0, 0, 0) + +/* Resize and/or move a piece associated with a memory pool. */ +#define VALGRIND_MEMPOOL_CHANGE(pool, addrA, addrB, size) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__MEMPOOL_CHANGE, \ + pool, addrA, addrB, size, 0) + +/* Return 1 if a mempool exists, else 0. */ +#define VALGRIND_MEMPOOL_EXISTS(pool) \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__MEMPOOL_EXISTS, \ + pool, 0, 0, 0, 0) + +/* Mark a piece of memory as being a stack. Returns a stack id. + start is the lowest addressable stack byte, end is the highest + addressable stack byte. */ +#define VALGRIND_STACK_REGISTER(start, end) \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__STACK_REGISTER, \ + start, end, 0, 0, 0) + +/* Unmark the piece of memory associated with a stack id as being a + stack. */ +#define VALGRIND_STACK_DEREGISTER(id) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_DEREGISTER, \ + id, 0, 0, 0, 0) + +/* Change the start and end address of the stack id. + start is the new lowest addressable stack byte, end is the new highest + addressable stack byte. */ +#define VALGRIND_STACK_CHANGE(id, start, end) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__STACK_CHANGE, \ + id, start, end, 0, 0) + +/* Load PDB debug info for Wine PE image_map. */ +#define VALGRIND_LOAD_PDB_DEBUGINFO(fd, ptr, total_size, delta) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__LOAD_PDB_DEBUGINFO, \ + fd, ptr, total_size, delta, 0) + +/* Map a code address to a source file name and line number. buf64 + must point to a 64-byte buffer in the caller's address space. The + result will be dumped in there and is guaranteed to be zero + terminated. If no info is found, the first byte is set to zero. */ +#define VALGRIND_MAP_IP_TO_SRCLOC(addr, buf64) \ + (unsigned)VALGRIND_DO_CLIENT_REQUEST_EXPR(0, \ + VG_USERREQ__MAP_IP_TO_SRCLOC, \ + addr, buf64, 0, 0, 0) + +/* Disable error reporting for this thread. Behaves in a stack like + way, so you can safely call this multiple times provided that + VALGRIND_ENABLE_ERROR_REPORTING is called the same number of times + to re-enable reporting. The first call of this macro disables + reporting. Subsequent calls have no effect except to increase the + number of VALGRIND_ENABLE_ERROR_REPORTING calls needed to re-enable + reporting. Child threads do not inherit this setting from their + parents -- they are always created with reporting enabled. */ +#define VALGRIND_DISABLE_ERROR_REPORTING \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \ + 1, 0, 0, 0, 0) + +/* Re-enable error reporting, as per comments on + VALGRIND_DISABLE_ERROR_REPORTING. */ +#define VALGRIND_ENABLE_ERROR_REPORTING \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CHANGE_ERR_DISABLEMENT, \ + -1, 0, 0, 0, 0) + +/* Execute a monitor command from the client program. + If a connection is opened with GDB, the output will be sent + according to the output mode set for vgdb. + If no connection is opened, output will go to the log output. + Returns 1 if command not recognised, 0 otherwise. */ +#define VALGRIND_MONITOR_COMMAND(command) \ + VALGRIND_DO_CLIENT_REQUEST_EXPR(0, VG_USERREQ__GDB_MONITOR_COMMAND, \ + command, 0, 0, 0, 0) + + +/* Change the value of a dynamic command line option. + Note that unknown or not dynamically changeable options + will cause a warning message to be output. */ +#define VALGRIND_CLO_CHANGE(option) \ + VALGRIND_DO_CLIENT_REQUEST_STMT(VG_USERREQ__CLO_CHANGE, \ + option, 0, 0, 0, 0) + + +#undef PLAT_x86_darwin +#undef PLAT_amd64_darwin +#undef PLAT_x86_win32 +#undef PLAT_amd64_win64 +#undef PLAT_x86_linux +#undef PLAT_amd64_linux +#undef PLAT_ppc32_linux +#undef PLAT_ppc64be_linux +#undef PLAT_ppc64le_linux +#undef PLAT_arm_linux +#undef PLAT_s390x_linux +#undef PLAT_mips32_linux +#undef PLAT_mips64_linux +#undef PLAT_nanomips_linux +#undef PLAT_x86_solaris +#undef PLAT_amd64_solaris + +#endif /* __VALGRIND_H */ diff --git a/runtime/third_party/libco/x86.c b/runtime/third_party/libco/x86.c new file mode 100644 index 0000000..d4b0356 --- /dev/null +++ b/runtime/third_party/libco/x86.c @@ -0,0 +1,131 @@ +#define LIBCO_C +#include "libco.h" +#include "settings.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__clang__) || defined(__GNUC__) + #define fastcall __attribute__((fastcall)) +#elif defined(_MSC_VER) + #define fastcall __fastcall +#else + #error "libco: please define fastcall macro" +#endif + +static thread_local long co_active_buffer[64]; +static thread_local cothread_t co_active_handle = 0; +static void (fastcall *co_swap)(cothread_t, cothread_t) = 0; + +#ifdef LIBCO_MPROTECT + alignas(4096) +#else + section(text) +#endif +/* ABI: fastcall */ +static const unsigned char co_swap_function[4096] = { + 0x89, 0x22, /* mov [edx],esp */ + 0x8b, 0x21, /* mov esp,[ecx] */ + 0x58, /* pop eax */ + 0x89, 0x6a, 0x04, /* mov [edx+ 4],ebp */ + 0x89, 0x72, 0x08, /* mov [edx+ 8],esi */ + 0x89, 0x7a, 0x0c, /* mov [edx+12],edi */ + 0x89, 0x5a, 0x10, /* mov [edx+16],ebx */ + 0x8b, 0x69, 0x04, /* mov ebp,[ecx+ 4] */ + 0x8b, 0x71, 0x08, /* mov esi,[ecx+ 8] */ + 0x8b, 0x79, 0x0c, /* mov edi,[ecx+12] */ + 0x8b, 0x59, 0x10, /* mov ebx,[ecx+16] */ + 0xff, 0xe0, /* jmp eax */ +}; + +#ifdef _WIN32 + /* The macro logic below matches what valgrind.h is able to handle. Although + * there's no Valgrind on Windows, it's possible to run a Windows exe on Linux + * with Wine and Valgrind. See https://wiki.winehq.org/Wine_and_Valgrind. */ + #if defined(__GNUC__) || defined(_MSC_VER) + #include "valgrind.h" + #endif + + #include + + static void co_init(void) { + #ifdef LIBCO_MPROTECT + DWORD old_privileges; + VirtualProtect((void*)co_swap_function, sizeof co_swap_function, PAGE_EXECUTE_READ, &old_privileges); + #endif + } +#else + #include "valgrind.h" + #ifdef LIBCO_MPROTECT + #include + #include + #endif + + static void co_init(void) { + #ifdef LIBCO_MPROTECT + unsigned long addr = (unsigned long)co_swap_function; + unsigned long base = addr - (addr % sysconf(_SC_PAGESIZE)); + unsigned long size = (addr - base) + sizeof co_swap_function; + mprotect((void*)base, size, PROT_READ | PROT_EXEC); + #endif + } +#endif + +static void crash(void) { + LIBCO_ASSERT(0); /* called only if cothread_t entrypoint returns */ +} + +cothread_t co_active(void) { + if(!co_active_handle) co_active_handle = &co_active_buffer; + return co_active_handle; +} + +cothread_t co_derive(void* memory, unsigned int size, void (*entrypoint)(void)) { + cothread_t handle; + if(!co_swap) { + co_init(); + co_swap = (void (fastcall*)(cothread_t, cothread_t))co_swap_function; + } + if(!co_active_handle) co_active_handle = &co_active_buffer; + + #if defined(__VALGRIND_MAJOR__) + VALGRIND_STACK_REGISTER(memory, (char*)memory + size); + #endif + + if((handle = (cothread_t)memory)) { + unsigned long stack_top = (unsigned long)handle + size; + long *p; + stack_top -= 32; + stack_top &= ~((unsigned long) 15); + p = (long*)(stack_top); /* seek to top of stack */ + *--p = (long)crash; /* crash if entrypoint returns */ + *--p = (long)entrypoint; /* start of function */ + *(long*)handle = (long)p; /* stack pointer */ + } + + return handle; +} + +cothread_t co_create(unsigned int size, void (*entrypoint)(void)) { + void* memory = LIBCO_MALLOC(size); + if(!memory) return (cothread_t)0; + return co_derive(memory, size, entrypoint); +} + +void co_delete(cothread_t handle) { + LIBCO_FREE(handle); +} + +void co_switch(cothread_t handle) { + register cothread_t co_previous_handle = co_active_handle; + co_swap(co_active_handle = handle, co_previous_handle); +} + +int co_serializable(void) { + return 1; +} + +#ifdef __cplusplus +} +#endif diff --git a/translator/src/Translator.Core/IO/AssemblyBlobWriter.cs b/translator/src/Translator.Core/IO/AssemblyBlobWriter.cs index 2b515dc..3a295f0 100644 --- a/translator/src/Translator.Core/IO/AssemblyBlobWriter.cs +++ b/translator/src/Translator.Core/IO/AssemblyBlobWriter.cs @@ -22,7 +22,14 @@ internal static class AssemblyBlobWriter var expectedFiles = new HashSet(StringComparer.OrdinalIgnoreCase); var assembly = new StringBuilder(); foreach (var header in headerLines) assembly.AppendLine(header); - assembly.AppendLine(".section .rdata,\"dr\""); + // PE/COFF (Windows) and ELF (Linux) spell a read-only data section differently in GNU-as + // syntax - COFF section flags ("dr" = data, read-only) versus an ELF section needing an + // allocatable-only flag plus an explicit @progbits type. The build always targets + // whichever platform the translator itself runs on (there is no cross-compilation + // support), so that's what this picks the section syntax from. + assembly.AppendLine(OperatingSystem.IsWindows() + ? ".section .rdata,\"dr\"" + : ".section .rodata,\"a\",@progbits"); assembly.AppendLine(); foreach (var blob in blobs) @@ -44,6 +51,16 @@ internal static class AssemblyBlobWriter { if (!expectedFiles.Contains(Path.GetFullPath(stalePath))) File.Delete(stalePath); } + if (!OperatingSystem.IsWindows()) + { + // Absence of a .note.GNU-stack section makes the linker assume the oldest, most + // conservative default for this object (an executable stack) and warn about it; this + // file has no code needing one, so mark it explicitly like every other GNU-as ELF + // object linked into the binary already does (the norm on modern toolchains, just not + // producible without an explicit section since this file is hand-assembled, not + // compiler-emitted). + assembly.AppendLine(".section .note.GNU-stack,\"\",@progbits"); + } FileOutput.WriteTextIfChanged(assemblyPath, assembly.ToString()); }