From 9e22b9fcaa8e83302ca559fec77305fa6f83ae0e Mon Sep 17 00:00:00 2001 From: Sven Boemer Date: Fri, 4 Sep 2020 17:08:27 -0700 Subject: [PATCH] Bundle assemblies at 4K for linux arm64 (#41809) * Bundle assemblies at 4K for linux arm64 * Add arch to bundler trace output * PR feedback * Remove extra overload * Update comment * Fix RID computation --- .../Microsoft.NET.HostModel/Bundle/Bundler.cs | 10 ++++---- .../Microsoft.NET.HostModel/Bundle/TargetInfo.cs | 27 ++++++++++++++++++---- .../Helpers/BundleHelper.cs | 25 +++++++++++++++++++- .../BundlerConsistencyTests.cs | 22 +++++++++++++----- 4 files changed, 67 insertions(+), 17 deletions(-) diff --git a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs index 3310cc1..8fc97d6 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/Bundle/Bundler.cs @@ -29,13 +29,11 @@ namespace Microsoft.NET.HostModel.Bundle readonly TargetInfo Target; readonly BundleOptions Options; - // Assemblies are 16 bytes aligned, so that their sections can be memory-mapped cache aligned. - public const int AssemblyAlignment = 16; - public Bundler(string hostName, string outputDir, BundleOptions options = BundleOptions.None, OSPlatform? targetOS = null, + Architecture? targetArch = null, Version targetFrameworkVersion = null, bool diagnosticOutput = false, string appAssemblyName = null) @@ -44,7 +42,7 @@ namespace Microsoft.NET.HostModel.Bundle HostName = hostName; OutputDir = Path.GetFullPath(string.IsNullOrEmpty(outputDir) ? Environment.CurrentDirectory : outputDir); - Target = new TargetInfo(targetOS, targetFrameworkVersion); + Target = new TargetInfo(targetOS, targetArch, targetFrameworkVersion); appAssemblyName ??= Target.GetAssemblyName(hostName); DepsJson = appAssemblyName + ".deps.json"; @@ -64,11 +62,11 @@ namespace Microsoft.NET.HostModel.Bundle { if (type == FileType.Assembly) { - long misalignment = (bundle.Position % AssemblyAlignment); + long misalignment = (bundle.Position % Target.AssemblyAlignment); if (misalignment != 0) { - long padding = AssemblyAlignment - misalignment; + long padding = Target.AssemblyAlignment - misalignment; bundle.Position += padding; } } diff --git a/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs b/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs index 5db826b..867e779 100644 --- a/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs +++ b/src/installer/managed/Microsoft.NET.HostModel/Bundle/TargetInfo.cs @@ -14,20 +14,25 @@ namespace Microsoft.NET.HostModel.Bundle /// /// Currently the TargetInfo only tracks: /// - the target operating system - /// - The target framework - /// If necessary, the target architecture may be tracked in future. + /// - the target architecture + /// - the target framework + /// - the default options for this target + /// - the assembly alignment for this target /// public class TargetInfo { public readonly OSPlatform OS; + public readonly Architecture Arch; public readonly Version FrameworkVersion; public readonly uint BundleVersion; public readonly BundleOptions DefaultOptions; + public readonly int AssemblyAlignment; - public TargetInfo(OSPlatform? os, Version targetFrameworkVersion) + public TargetInfo(OSPlatform? os, Architecture? arch, Version targetFrameworkVersion) { OS = os ?? HostOS; + Arch = arch ?? RuntimeInformation.OSArchitecture; FrameworkVersion = targetFrameworkVersion ?? net50; Debug.Assert(IsLinux || IsOSX || IsWindows); @@ -46,6 +51,19 @@ namespace Microsoft.NET.HostModel.Bundle { throw new ArgumentException($"Invalid input: Unsupported Target Framework Version {targetFrameworkVersion}"); } + + if (IsLinux && Arch == Architecture.Arm64) + { + // We align assemblies in the bundle at 4K so that we can use mmap on Linux without changing the page alignment of ARM64 R2R code. + // This is only necessary for R2R assemblies, but we do it for all assemblies for simplicity. + // See https://github.com/dotnet/runtime/issues/41832. + AssemblyAlignment = 4096; + } + else + { + // Otherwise, assemblies are 16 bytes aligned, so that their sections can be memory-mapped cache aligned. + AssemblyAlignment = 16; + } } public bool IsNativeBinary(string filePath) @@ -63,7 +81,8 @@ namespace Microsoft.NET.HostModel.Bundle public override string ToString() { string os = IsWindows ? "win" : IsLinux ? "linux" : "osx"; - return $"OS: {os} FrameworkVersion: {FrameworkVersion}"; + string arch = Arch.ToString().ToLowerInvariant(); + return $"OS: {os} Arch: {arch} FrameworkVersion: {FrameworkVersion}"; } static OSPlatform HostOS => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? OSPlatform.Linux : diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs index 5f57eda..53c8d24 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/Helpers/BundleHelper.cs @@ -6,6 +6,7 @@ using Microsoft.NET.HostModel.Bundle; using System; using System.Collections.Generic; using System.IO; +using System.Runtime.InteropServices; using System.Xml.Linq; namespace BundleTests.Helpers @@ -96,11 +97,31 @@ namespace BundleTests.Helpers return Path.Combine(GetExtractionRootPath(fixture), GetAppBaseName(fixture), bundler.BundleManifest.BundleID); } + public static DirectoryInfo GetExtractionDir(TestProjectFixture fixture, Bundler bundler) { return new DirectoryInfo(GetExtractionPath(fixture, bundler)); } + public static OSPlatform GetTargetOS(string runtimeIdentifier) + { + return runtimeIdentifier.Split('-')[0] switch { + "win" => OSPlatform.Windows, + "osx" => OSPlatform.OSX, + "linux" => OSPlatform.Linux, + _ => throw new ArgumentException(nameof(runtimeIdentifier)) + }; + } + + public static Architecture GetTargetArch(string runtimeIdentifier) + { + return runtimeIdentifier.EndsWith("-x64") || runtimeIdentifier.Contains("-x64-") ? Architecture.X64 : + runtimeIdentifier.EndsWith("-x86") || runtimeIdentifier.Contains("-x86-") ? Architecture.X86 : + runtimeIdentifier.EndsWith("-arm64") || runtimeIdentifier.Contains("-arm64-") ? Architecture.Arm64 : + runtimeIdentifier.EndsWith("-arm") || runtimeIdentifier.Contains("-arm-") ? Architecture.Arm : + throw new ArgumentException(nameof (runtimeIdentifier)); + } + /// Generate a bundle containind the (embeddable) files in sourceDir public static string GenerateBundle(Bundler bundler, string sourceDir, string outputDir, bool copyExludedFiles=true) { @@ -151,8 +172,10 @@ namespace BundleTests.Helpers var hostName = GetHostName(fixture); string publishPath = GetPublishPath(fixture); var bundleDir = GetBundleDir(fixture); + var targetOS = GetTargetOS(fixture.CurrentRid); + var targetArch = GetTargetArch(fixture.CurrentRid); - var bundler = new Bundler(hostName, bundleDir.FullName, options, targetFrameworkVersion: targetFrameworkVersion); + var bundler = new Bundler(hostName, bundleDir.FullName, options, targetOS, targetArch, targetFrameworkVersion); singleFile = GenerateBundle(bundler, publishPath, bundleDir.FullName, copyExcludedFiles); return bundler; diff --git a/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs b/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs index 3cf9d35..19db431 100644 --- a/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs +++ b/src/installer/tests/Microsoft.NET.HostModel.Tests/Microsoft.NET.HostModel.Bundle.Tests/BundlerConsistencyTests.cs @@ -32,7 +32,9 @@ namespace Microsoft.NET.HostModel.Tests var hostName = BundleHelper.GetHostName(fixture); var bundleDir = BundleHelper.GetBundleDir(fixture); - Bundler bundler = new Bundler(hostName, bundleDir.FullName); + var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid); + var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid); + Bundler bundler = new Bundler(hostName, bundleDir.FullName, targetOS: targetOS, targetArch: targetArch); FileSpec[][] invalidSpecs = { @@ -55,13 +57,15 @@ namespace Microsoft.NET.HostModel.Tests var hostName = BundleHelper.GetHostName(fixture); var appName = Path.GetFileNameWithoutExtension(hostName); var bundleDir = BundleHelper.GetBundleDir(fixture); + var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid); + var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid); // Generate a file specification without the apphost var fileSpecs = new List(); string[] files = { $"{appName}.dll", $"{appName}.deps.json", $"{appName}.runtimeconfig.json" }; Array.ForEach(files, x => fileSpecs.Add(new FileSpec(x, x))); - Bundler bundler = new Bundler(hostName, bundleDir.FullName); + Bundler bundler = new Bundler(hostName, bundleDir.FullName, targetOS: targetOS, targetArch: targetArch); Assert.Throws(() => bundler.GenerateBundle(fileSpecs)); } @@ -73,6 +77,8 @@ namespace Microsoft.NET.HostModel.Tests var hostName = BundleHelper.GetHostName(fixture); var bundleDir = BundleHelper.GetBundleDir(fixture); + var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid); + var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid); // Generate a file specification with duplicate entries var fileSpecs = new List(); @@ -80,7 +86,7 @@ namespace Microsoft.NET.HostModel.Tests fileSpecs.Add(new FileSpec(BundleHelper.GetAppPath(fixture), "app.repeat")); fileSpecs.Add(new FileSpec(BundleHelper.GetAppPath(fixture), "app.repeat")); - Bundler bundler = new Bundler(hostName, bundleDir.FullName); + Bundler bundler = new Bundler(hostName, bundleDir.FullName, targetOS: targetOS, targetArch: targetArch); Assert.Throws(() => bundler.GenerateBundle(fileSpecs)); } @@ -90,6 +96,8 @@ namespace Microsoft.NET.HostModel.Tests var fixture = sharedTestState.TestFixture.Copy(); var publishPath = BundleHelper.GetPublishPath(fixture); var bundleDir = BundleHelper.GetBundleDir(fixture); + var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid); + var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid); // Rename the host from "StandaloneApp" to "Stand.Alone.App" to check that baseName computation // (and consequently deps.json and runtimeconfig.json name computations) in the bundler @@ -110,7 +118,7 @@ namespace Microsoft.NET.HostModel.Tests var depsJson = newBaseName + ".deps.json"; var runtimeconfigJson = newBaseName + ".runtimeconfig.json"; - var bundler = new Bundler(hostName, bundleDir.FullName); + var bundler = new Bundler(hostName, bundleDir.FullName, targetOS: targetOS, targetArch: targetArch); BundleHelper.GenerateBundle(bundler, publishPath, bundleDir.FullName); string[] jsonFiles = { depsJson, runtimeconfigJson }; @@ -196,9 +204,11 @@ namespace Microsoft.NET.HostModel.Tests { var fixture = sharedTestState.TestFixture.Copy(); var bundler = BundleHelper.Bundle(fixture); - + var targetOS = BundleHelper.GetTargetOS(fixture.CurrentRid); + var targetArch = BundleHelper.GetTargetArch(fixture.CurrentRid); + var alignment = (targetOS == OSPlatform.Linux && targetArch == Architecture.Arm64) ? 4096 : 16; bundler.BundleManifest.Files.ForEach(file => - Assert.True((file.Type != FileType.Assembly) || (file.Offset % Bundler.AssemblyAlignment == 0))); + Assert.True((file.Type != FileType.Assembly) || (file.Offset % alignment == 0))); } [Fact] -- 2.7.4