Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
254 changes: 254 additions & 0 deletions accepted/2021/User-Experience/Apple-Silicon-UX.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
# Apple Silicon User Experience

Apple has [announced plans to transition its MacOS hardware line]( https://www.apple.com/newsroom/2020/06/apple-announces-mac-transition-to-apple-silicon/) to a new Arm64-based chip that they refer to as “Apple Silicon”.

The transition has a few impacts on the .NET user experience.

## Background

### Apple Multiple Architecture Support

#### Rosetta 2 x64 Emulation

As part of the their transition plan, Apple created the Rosetta 2 `x86_64` emulator to run `x64` code on an Apple Silicon processor. It is intended to support most `x64` code on Apple Silicon without modification.

Usage is generally transparent to the user. When creating a new process, the current process architectural affinity is used to select the best architecture to use for the new process.

The user also has explicit architecture control through the use of the `arch` utility. The user can select a specific architecture when executing a command from the command line i.e. `arch -arch x86_64 <command>`.

While Rosetta 2 is supported for running `x64` code on Apple Silicon, the reverse emulator is not currently shipping.

#### Universal Binaries

For macOS, universal are the default approach. Most binaries are multi-arch. For single arch binaries the kernel can detect and automatically switch the process to run in the correct architecture, either native or Rosetta 2 emulation of x64.

A universal binary can be created simply by combining two or more architecture specific binaries into a universal binary using the `lipo` tool. The process is reversible too. A universal binary can be `thinned` to extract a single arch binaries.

Given both architectures, universal binaries are relatively easy to create. The general syntax would be `lipo <binaryArchA> <binaryArchB> -create -output <binaryUniversal>`

#### Apple Store requires universal binaries

I have heard that publishing native Apple Silicon (or Apps supporting multiple architectures) apps to the Apple store will require them to be published as Universal binaries. I have not been able to find public documentation of this, so the details are not clear.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rolfbjarne @marek-safar Is this requirement documented anywhere?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't heard anything about it, but it wouldn't surprise me if Apple made it required at some point.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK it's the default setup for xcode projects but still a recommendation. I agree with Rolf that this can change in the future.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not yet formally required by Apple, but something we fully expect to be required later this year. As an example, Apple started mandating that all apps have 64-bit slices in their universal binaries about a year before they started telling people that 32-bit was ending. We know they will stop building Intel-based Macs within the next 18 months or so, so we expect them to start requiring Arm slices this summer.


This document is assumes this requirement is reasonably accurate.

### Security Enhancements

#### Write No Execute

Another major change with the Apple Silicon is the transition to a new prohibition of pages simultaneously marker writable and executable. A new API was introduced for JIT usage to allow a single thread to switch a page from executable to writable or vice-versa.

This requirement currently only applies when executing as Apple Silicon native code.

This is not expected to impact our customers as it needs to be handled automatically by our run-times.

#### Tightened Signing Requirements

When running on Apple Silicon unsigned binaries will get killed automatically by the kernel. A simple non-obvious message will be printed on the console (`zsh killed: helloWorld`).

To allow a native shared library to be loaded, it will also need to be signed.

When running the equivalent unsigned binary from a Rosetta 2 emulated process the process is not killed. When running a x64 process on Apple Silicon the process was not killed either.

XCode 12 adhoc signs native binaries. These anonymous signature are sufficient to prevent these from being killed (on the machine that compiled them at least.)

I haven't found the documentation exactly how and what changed. These behavior descriptions have been through experimentation and debugging.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you seen the work we have done on this? https://docs.microsoft.com/en-us/dotnet/core/install/macos-notarization-issues

When running the equivalent unsigned binary from a Rosetta 2 emulated process the process is not killed.

I think that this is temporary to allow easier transition and it will be locked down.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@odhanson Might be able to provide more context.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try gather more information both about the signing and the Apple store requirements and comment back here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a link:
https://developer.apple.com/documentation/macos-release-notes/macos-big-sur-11_0_1-universal-apps-release-notes

Code Signing
New Features
New in macOS 11 on Macs with Apple silicon, and starting in macOS Big Sur 11 beta 6, the operating system enforces that any executable must be signed before it’s allowed to run. There isn’t a specific identity requirement for this signature: a simple ad-hoc signature is sufficient. This new behavior doesn’t change the long-established policy that our users and developers can run arbitrary code on their Macs, and is designed to simplify the execution policies on Macs with Apple silicon and enable the system to better detect code modifications. This new policy doesn’t apply to translated x86 binaries running under Rosetta 2, nor does it apply to macOS 11 running on Intel-based platforms.


### Microsoft One.NET Transition

We are in the middle of a migration from two distinct .NET ecosystems (Mono & .NET Core), to a shared ecosystem, One.NET.

This document was originally written form a pure CoreCLR perspective and was missing a Mono perspective. As Mono feedback is received it will be incorporate to make this a better design.

Any feedback is appreciated.

### New Architecture and .NET Back-porting

Support for a new processor architecture is a significant undertaking. It is too large an undertaking to consider on our stable release branches. New architecture development is occurring in .NET 6.

#### CoreCLR Runtime

We do not plan to back-port Apple Silicon support to the current CoreCLR runtime releases .NET 2.1, .NET 3.1, and .NET 5.0.

We have tested the .NET 5 release with Rosetta 2. Apple has resolved the majority of issues we reported to them. This means that CoreCLR .NET 2.1 LTS, .NET 3.1 LTS and .NET 5 will mostly just work on Apple Silicon.

## .NET 6 Preview1 CoreCLR User Experience Issues

### Mixed Architecture Side By Side Installs Fail

While the customer can install the macOS Apple Silicon .NET preview build by itself, install this beside the macOS x64 builds is problematic.

We have several possible representative scenarios. Two typical ones are described:

- .NET 3.1 x64 with .NET 6 Apple Silicon. As side effect of not back-porting older releases to the new architecture is that customer will need to deal with multi-architecture side by side installs at least until .NET 3.1 goes out of support. However, this mixed forced architecture problem is transient in nature.

- .NET 6 x64 with .NET 6 Apple Silicon. This scenario is not absolutely critical, but may be required by some customers.
- It enables moving legacy Apps with x64 dependent apps to .NET 6.
- It enables testing macOS x64 apps under the Rosetta 2 emulator.
Comment on lines +85 to +87
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running apps via dotnet app if both x64 and arm64 is supported by the dotnet - this is currently problematic because there's nothing in the app.runtimeconfig.json which would hint the dotnet/hostfxr which architecture the app needs.

  • We would either have to add this information somewhere (.runtimeconfig.json is probably the most natural place for this)
  • Rely on app (executable) instead
    • Currently this is disabled by default for MacOS RID targets (if I remember correctly) - this fact is not discussed anywhere in this doc either
    • Requires signing - as mentioned above

The universal muxer should default to one architecture if it doesn't know better (probably arm64)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's nothing in the app.runtimeconfig.json which would hint the dotnet/hostfxr which architecture the app needs.

This is intentional design. We talk about these as cross-platform apps that can run anywhere.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know of the existence of "portable apps" - that's why the muxer will need a "default" architecture (for those apps). The point was about current state of things where we don't generate apphost for apps on MacOS and thus they're executed via dotnet app.dll. Those apps may not be portable, but currently there's no way to tell which RID they were built for (if any) and will break in bad ways if run on the wrong architecture (for example if they have native dependencies).

If we solve the signing problem and turn on apphost by default for MacOS, then this problem becomes much less important and we can probably continue to ignore it (just like we ignore it on Win x86/x64 today).


For .NET 6 Preview1 these Side By Side mixed architecture scenarios simply don't work.

#### Installer Path is Hardcoded and Uses Same path

One key issue is that all runtime installs use the same path. The users is not given the option of choosing an alternative path (except a different hard drive.)

#### Shared dotnet muxer

The `dotnet` muxer is a shared component. Effectively every side-by-side install was designed to share the same muxer. The muxer's job was to run the SDK command or user application. It is intended to be the entry point to all versions of the SDK and the runtime.

This `dotnet` command line architecture makes it difficult to support side-by-side mixed architecture CoreCLR installs. The dotnet tool is responsible for starting the newest version of the `hostfxr` library to resolve the runtime version to use. Until the runtime version is selected it is not clear which architecture the runtime will require.

For instance, the `dotnet --info` command must resolve the currently selected SDK, by finding the `global.json` or the most recent SDK. Then it must find the best runtime as requested by that SDK. Once that is determined the runtime required architecture could be known.

The current installers are installing a single architecture version of `dotnet`. The process will fail when either

- `dotnet` and the newest `hostfxr` are different machine architectures.
- `dotnet`/`hostfxr` and the selected CoreCLR runtime's `hostpolicy` are different architectures.

#### Current Workaround

The best known workaround is for the customer to install the runtimes/SDK for a single architecture only to the default path.

If the customer needs the second architecture. They must rename the patch to the first architecture, before installing the second architecture.

For instance:

`sudo mv $(dirname $(which dotnet)) $(dirname $(which dotnet))-x64`

Which version to be used must be controlled by the path environment variable.

#### Design Constraints

- This mixed forced architecture problem is transient in nature.

- We want to avoid making major architectural changes to support this temporary design problem.

- We want to keep the process parent-child relationships as consistent as possible.

- We want minimal changes to the earlier releases.

#### Design Experiment

https://github.com/dotnet/runtime/pull/48541 represented an early design experiment to allow side-by-side macOS-x64 .NET 5 with macOS-arm64 .NET 6 to coexist.

The experiment:

- Assumed .NET runtime versions less than 6.0 will always require `x86_64` architecture. All other runtime versions will require `arm64` architecture on Apple Silicon.

- Made the .NET 6 `dotnet` & `libhostfxr.dylib` (`hostfxr`) universal binaries. This means both native and emulated processes can perform runtime resolution to identify the correct runtime to use.

- The `hostfxr` was modified to enforce the architecture version policy above. It will return an architecture mismatch error when the runtime (by version policy above) require a different architecture than the currently running process.

- `dotnet` was modified to enable switching the architecture of the current process when it saw an architecture mismatch error returned by `hostfxr`. The current process will switch architecture as needed and run with the same arguments as the new process. The switching will use `posix_spawn` with changes to `posix_spawnattr_t` to set `POSIX_SPAWN_SETEXEC` and preferred architecture via `posix_spawnattr_setbinpref_np` (see Apple Silicon man page) and setting `sysctl` `kern.curproc_arch_affinity` property appropriately.

Overall the experiment demonstrated a highly workable user experience.

- The experiment represents a viable design alternative. It did not support side-by-side install of multiple architectures of the same runtime version.

- The implementation was incomplete. It did not automate the build process or deal with installer issues

### Dotnet Publish Experience Inconsistent

#### Signing

- dotnet publish for Xamarin workloads handles signing automatically.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- dotnet publish for Xamarin workloads handles signing automatically.
- dotnet publish for UI based workloads handles signing automatically.

- dotnet publish for CoreClr console apps do not handle signing automatically. The developer would need to explicitly sign after publishing for Apple Silicon. The signing command is relatively simple `codesign -s <signature> --entitlements <app-entitlements-path> <app-path>`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- dotnet publish for CoreClr console apps do not handle signing automatically. The developer would need to explicitly sign after publishing for Apple Silicon. The signing command is relatively simple `codesign -s <signature> --entitlements <app-entitlements-path> <app-path>`.
- dotnet publish for console apps do not handle signing automatically. The developer would need to explicitly sign after publishing for Apple

- There is no message to the customer that the published app needs to be signed

#### Dotnet Publish as Universal Binaries

- This scenario is also supported for Xamarin iOS workloads. This is a slightly different scenario as Xamarin iOS apps are native binaries (not JIT binaries)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Xamarin.Mac is what runs on Apple Silicon (and it's jitted):

Suggested change
- This scenario is also supported for Xamarin iOS workloads. This is a slightly different scenario as Xamarin iOS apps are native binaries (not JIT binaries)
- This scenario is also supported for Xamarin Mac workloads.

- CoreCLR runtime apps have no mechanism to publish universal binaries.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that the correct message if we considering coreclr JIT to be backend for Xamarin.Mac ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, we would need to make this work as part of adding support for coreclr w/ Xamarin.Mac.


### Dotnet-SOS and the SOS plugin Issues

The `dotnet-sos` tool installs the `SOS` diagnostic plugin into `lldb`. Since `lldb` is a universal binary, it expects its plugins to also be universal binaries. With the single architecture design, when `SOS` is installed, `lldb` will complain about not being able to load the plugin when the architecture mismatches.

#### Universal Binary Experiment

Making the Apple Silcon SOS be a universal binary to allowed SOS to work on `x86_64` and `arm64` processes.

## User Experience Designs

### Mixed Architecture Side By Side Installs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to drive to consensus around this section. At least from here to the primary design alternative described in Open Questions below.

As I wrote the Open Questions, it was clear the second alternative may be easier and perhaps more preferable. This alternative may make it easier to support publishing universal binaries.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are other runtimes/SDKs with global installs planning to address this problem?

It may be useful to look at Java, golang, python, rust, ... to see whether there is a trend.

Copy link
Contributor Author

@sdmaclea sdmaclea Mar 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • I couldn't find an Oracle JDK for Apple Silicon.
  • Zulu OpenJDK was arch specific.
  • golang was arch specific.
  • Python was a universal binary (signed by Apple)
  • Python3 was arch specific from a homebrew cask.
  • Rust looks like it is not ready. They are still protoyping.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our muxer architecture, readytorun story, and legacy mixed architecture make it more complicated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From this list, it sounds like arch specific is the norm and we won't be an outlier if we stick with the simpler arch-specific as well.


This is controversial. This is _uncommitted/unplanned_. Ongoing discussions will likely affect this proposal.

For CoreCLR runtime, the proposal would be to:

- Make `dotnet` & `hostfxr` to be universal binaries
- Support automatic architecture selection
- Modify `hostfxr` to assume .NET 5 and earlier require x64 architecture.
- Modify `dotnet` to restart in the correct architecture on an architecture mismatch.
- Make the CoreCLR runtime effectively a Universal Binaries
- Create a single distribution for both macOS architectures.
- Make the runtime native binaries to be universal binaries
- Modify `hostpolicy` to probe for trusted platform assemblies based on the current RID. This is designed to architecture specific ready-to-run managed assemblies. This RID specific path would be probed before probing the common any platform assembly path.
- Modify the runtime probing to be consistent with the new `hostpolicy` probing.
- Modify the build system to build the Universal Binaries
- Modify installers
- Modify .NET 5 installers to allow installation without breaking universal `dotnet`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- Modify .NET 5 installers to allow installation without breaking universal `dotnet`.
- Modify .NET 3.1 and .NET 5 installers to allow installation without breaking universal `dotnet`.

? .NET 5 goes out of support short after .NET 6 ships. .NET 3.1 is supported for much longer.

- Modify .NET 6 installer to overwrite the x64 `dotnet`

#### Open Questions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My personal preference:

  • Choose a different location for installation of x64 .NET 6+ runtime - something like dotnet_x64. The existence of this is transient and in some not so distant future release will go away
  • The primary install location for .NET 6+ will be arm64 only (for the most part)
    • Keep the muxer/hostfxr as universal binaries in the main install location
    • Add some logic to also look for runtimes/SDKs in the dotnet_x64 location - the universal muxer/hostfxr would use this to get the right runtime if asked to run x64 app
  • The primary location would be the only one registered (in config file and so on) and it would be the only one on "path".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading this document, the above statement by @vitek-karas would be my preference for a solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Choose a different location for installation of x64 .NET 6+ runtime - something like dotnet_x64.

Are you proposing we backport this change to the 3.1/5.0 branches.

I was thinking we could only do this for the .NET 6+ branches.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the advantage of @vitek-karas's approach would be that we just need to teach the installer to alter the install path for x64 on Apple Silicon.

That seems easier that fixing it overwriting the dotnet and hostfxr universal binaries.

That said, I am leanig to @vitek's approach too.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we never install .NET 6+ x64 into global location (we simply don't produce the installers for this) then the dotnet_x64 approach is not much different from keeping everything in dotnet. Obviously this is ignoring the potential problems with overwriting the dotnet (note that hostfxr doesn't have a problem since it's installed into a version specific directory, so the older installers will never overwrite the newer one).

The only true advantage of having dotnet_x64 is that it would allow to install both .NET 6 x64 and .NET 6 arm64 side by side.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The only true advantage of having dotnet_x64 is that it would allow to install both .NET 6 x64 and .NET 6 arm64 side by side.

It would also give us a pattern to follow - which is important here. Having a process that we can fallback on is important for our customers and ourselves when we rediscover this problem in 18 months.


- Would an alternative design be better? For instance:
- Choose a simpler design
- Use same basic universal `dotnet` and `hostfxr` ideas
- Keep separate runtimes. Each can optionally be installed without overwriting the other
- Move macOS .NET 6 CoreCLR runtime into a RID specific directory
- Teach `hostfxr` about the new `RID` in the probing for `hostpolicy`
- If the current architecture is not present treat it as a missing runtime.
- Probably more consistent with the original scope of our .NET 6
Apple Silicon plan.

- What unanticipated consequences will a universal runtime have?

- How will a universal runtime affect macOS publishing?

#### Dotnet Muxer Side-By-Side Design Alternatives

##### Separate install directories

For other OS architectures CoreCLR has dealt with this by using separate install directories. The default architecture would be on the path and the IDEs would know how to select the architecture as needed.

##### Universal binaries

There are a some design choices here:

- How much runtime is universal?
- Dotnet muxer & hosfxr (library responsible for runtime selection)
- All runtime native binaries (starting at .NET 6)
- All runtime native and _some_ managed binaries (starting at .NET 6)
- All runtime native and _all_ managed binaries (starting at .NET 6)
- How does `hostfxr` we identify which runtimes support which architecture?
- Let the kernel fatally fail
- Fail and retry with another arch (slow, not scalable)
- Policy heuristic
- .NET 5 runtime = x64
- .NET 6 runtime must be native
- .NET 6 runtime has RID in some well defined place
- .NET 6 runtime must be universal
- New config variable in runtime's json config file
- Runtime install path. Add RID to runtime install path.
- Universal `hostpolicy` with RID aware probing paths

### Apple Silicon requires Signed Binaries

Given that the SDK needs to handle this automatically for some platforms, it seems best to handle this consistently and automatically on all platforms.

Proposal is currently _Unplanned/Uncommitted._

### Publishing Universal Binary Apps

No planned support for publishing CoreCLR universal binaries in .NET 6.

This represent the current CoreCLR runtime team plan.

Pursuing a universal CoreCLR runtime would affect this design and might allow us to at least specify a manual method to produce a universal app.
Comment on lines +242 to +248
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be better to fold this into https://github.com/dotnet/designs/pull/182/files#diff-f9955dc1a401114b9383fdfecab5a55da35312f33af1af21d4d46a0246fa7e16R158 section. It also needs clarification of what is exactly not supported.


### Dotnet-SOS & SOS

SOS will be installed on Apple Silicon as a Universal Binaries.

This represents the current CoreCLR runtime diagnostics plan.