Skip to content

Commit

Permalink
Merge pull request #183 from nike4613/reorganize
Browse files Browse the repository at this point in the history
Miscellaneous fixes to work properly on old Unity Mono
  • Loading branch information
nike4613 committed Jun 8, 2024
2 parents 53fec67 + 5ff9a73 commit 7e55f80
Show file tree
Hide file tree
Showing 27 changed files with 569 additions and 160 deletions.
14 changes: 1 addition & 13 deletions docs/README.ILHelpers.md
Original file line number Diff line number Diff line change
@@ -1,13 +1 @@
# `MonoMod.ILHelpers`

`MonoMod.ILHelpers` is a collection of helpers manually implemented in IL.

Notably, this contains a backport of `System.Runtime.CompilerServices.Unsafe`, as it exists in .NET 6, to all older
runtimes. This means that any environment which *also* provides that class which is older than .NET 6 will require
an `extern alias` to be able to use properly.

## Notable APIs

- `System.Runtime.CompilerServices.Unsafe`
- `MonoMod.ILHelpers`
-
# DO NOT REFERENCE THIS PACKAGE DIRECTLY! Reference MonoMod.Backports instead.
33 changes: 31 additions & 2 deletions docs/RuntimeIssueNotes.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,41 @@
# Notes on issues in various runtime versions

Martin, this is wrong.

## `sizeof` IL opcode does not work with generic parameters on old Mono

The title says it all. `sizeof` works fine with all other type-specs, but with generic parameters specifically,
it always returns the system pointer size.

The relevant code is in `metadata/metadata.c`, in `mono_type_size` (which `sizeof` correctly embeds as a constant):

```c
int
mono_type_size (MonoType *t, int *align)
{
// ...

switch (t->type){
// ...
case MONO_TYPE_VAR:
case MONO_TYPE_MVAR:
/* FIXME: Martin, this is wrong. */
*align = __alignof__(gpointer);
return sizeof (gpointer);
// ...
}

// ...
}
```
## `fixed` on strings in old Mono
Some old versions of Mono have broken `conv.u` instruction handling.
The following code will crash those old versions with an assert in the JIT's local propagation routine:
```cs
```csharp
fixed (char* pStr = "some string")
{
// ...
Expand All @@ -15,7 +44,7 @@ fixed (char* pStr = "some string")

This is because the sequence that Roslyn emits for `fixed` over a string is this:

```
```il
.locals (
string pinned stringLocalMarkedPinned,
char* ptrLocal
Expand Down
4 changes: 2 additions & 2 deletions global.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"sdk": {
"allowPrerelease": true,
"rollForward": "latestMinor",
"version": "8.0.203"
"rollForward": "latestPatch",
"version": "8.0.301"
},
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "3.7.56",
Expand Down
4 changes: 2 additions & 2 deletions src/Common/UnsafeAlias.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

global using ilhelpers::MonoMod;

#if !NET6_0_OR_GREATER
// Any time we want to use Unsafe, we want ours, not the BCL's
#if !NET6_0_OR_GREATER && (NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NET)
// Any time we want to use Unsafe, we want ours, not the BCL's. Note that we need these funky defs because the location of Unsafe moves between versions.
// I would actually rather move the BCL assembly defining it into an alias, but that doesn't seem to be particularly viable
global using Unsafe = ilhelpers::System.Runtime.CompilerServices.Unsafe;
#else
Expand Down
2 changes: 1 addition & 1 deletion src/MonoMod.Backports/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Project>

<Import Project="$([MSBuild]::GetPathOfFileAbove('Directory.Build.targets', '$(MSBuildThisFileDirectory)../'))" />

<PropertyGroup>
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
</PropertyGroup>
Expand Down
13 changes: 11 additions & 2 deletions src/MonoMod.Backports/MonoMod.Backports.csproj
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(TargetFrameworks);netstandard2.1;netcoreapp2.1;netcoreapp3.0;netcoreapp3.1</TargetFrameworks>
<!-- target frameworks are defined in Common.props -->
<TargetFrameworks>$(BackportsTargetFrameworks)</TargetFrameworks>

<!-- We're not going to be having any of the logic that would use the shared sources, so we'll just not include them -->
<MMSharedSourceFiles>false</MMSharedSourceFiles>
<MMIncludeUnsafeAlias>true</MMIncludeUnsafeAlias>
<MMIncludeUnsafeAlias>false</MMIncludeUnsafeAlias>
<MMReferenceILHelpers>false</MMReferenceILHelpers>
<MMReferenceUtils>false</MMReferenceUtils>
<MMReferenceBackports>false</MMReferenceBackports>
<RootNamespace><![CDATA[ ]]></RootNamespace>
Expand All @@ -27,6 +29,13 @@
or ('$(MMTFKind)' == '.NETCoreApp' and $([MSBuild]::VersionGreaterThanOrEquals('$(MMTFVersion)','2.1')))">true</BclHasOobImpls>
</PropertyGroup>

<ItemGroup>
<!-- Manually reference ILHelpers -->
<ProjectReference Include="$(MMSourcePath)MonoMod.ILHelpers\MonoMod.ILHelpers.ilproj">
<Aliases>ilhelpers</Aliases>
</ProjectReference>
</ItemGroup>

<!-- We only actually want to take a dependency on the packages when they're necessary -->
<ItemGroup Condition="!('$(MMTFKind)' == '.NETFramework' and $([MSBuild]::VersionLessThan('$(MMTFVersion)','4.5')))">
<!-- ArrayPool<T> -->
Expand Down
192 changes: 192 additions & 0 deletions src/MonoMod.Backports/MonoMod.Backports/SRCS.Unsafe.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP || NET
#define UNSAFE_IN_ILHELPERS
#endif

extern alias ilhelpers;

// Sometimes these global usings are unused. That's fine.
#pragma warning disable IDE0005

// Global usings
global using ilhelpers::MonoMod;

#if UNSAFE_IN_ILHELPERS && !NET6_0_OR_GREATER
global using Unsafe = ilhelpers::System.Runtime.CompilerServices.Unsafe;
#else
global using Unsafe = System.Runtime.CompilerServices.Unsafe;
#endif

#pragma warning restore IDE0005

#if UNSAFE_IN_ILHELPERS
// SRCS.Unsafe is defined in ILHelpers, so we want to define UnsafeRaw + a type-forwarder

#if NET6_0_OR_GREATER
using ILImpl = System.Runtime.CompilerServices.Unsafe;
#else
using ILImpl = ilhelpers::System.Runtime.CompilerServices.Unsafe;
#endif

using System;
using System.Runtime.CompilerServices;

[assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(ILImpl))]

namespace MonoMod.Backports.ILHelpers;

[CLSCompliant(false)]
public static unsafe class UnsafeRaw
#else
// SRCS.Unsafe is defined here, so we want to define Unsafe

using MonoMod.Backports;

using ILImpl = ilhelpers::MonoMod.Backports.ILHelpers.UnsafeRaw;

namespace System.Runtime.CompilerServices;

[CLSCompliant(false)]
public static unsafe class Unsafe
#endif
{
#region Direct forwarders
#nullable disable
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static T Read<T>(void* source) => ILImpl.Read<T>(source);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static T ReadUnaligned<T>(void* source) => ILImpl.ReadUnaligned<T>(source);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static T ReadUnaligned<T>(ref byte source) => ILImpl.ReadUnaligned<T>(ref source);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void Write<T>(void* destination, T value) => ILImpl.Write(destination, value);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void WriteUnaligned<T>(void* destination, T value) => ILImpl.WriteUnaligned(destination, value);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void WriteUnaligned<T>(ref byte destination, T value) => ILImpl.WriteUnaligned(ref destination, value);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void Copy<T>(void* destination, ref T source) => ILImpl.Copy(destination, ref source);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void Copy<T>(ref T destination, void* source) => ILImpl.Copy(ref destination, source);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void* AsPointer<T>(ref T value) => ILImpl.AsPointer(ref value);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void SkipInit<T>(out T value) => ILImpl.SkipInit(out value);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void CopyBlock(void* destination, void* source, uint byteCount) => ILImpl.CopyBlock(destination, source, byteCount);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void CopyBlock(ref byte destination, ref byte source, uint byteCount) => ILImpl.CopyBlock(ref destination, ref source, byteCount);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void CopyBlockUnaligned(void* destination, void* source, uint byteCount) => ILImpl.CopyBlockUnaligned(destination, source, byteCount);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void CopyBlockUnaligned(ref byte destination, ref byte source, uint byteCount) => ILImpl.CopyBlockUnaligned(ref destination, ref source, byteCount);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void InitBlock(void* startAddress, byte value, uint byteCount) => ILImpl.InitBlock(startAddress, value, byteCount);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void InitBlock(ref byte startAddress, byte value, uint byteCount) => ILImpl.InitBlock(ref startAddress, value, byteCount);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void InitBlockUnaligned(void* startAddress, byte value, uint byteCount) => ILImpl.InitBlockUnaligned(startAddress, value, byteCount);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void InitBlockUnaligned(ref byte startAddress, byte value, uint byteCount) => ILImpl.InitBlockUnaligned(ref startAddress, value, byteCount);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static T As<T>(object o) where T : class => ILImpl.As<T>(o);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T AsRef<T>(void* source) => ref ILImpl.AsRef<T>(source);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T AsRef<T>(in T source) => ref ILImpl.AsRef(in source);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref TTo As<TFrom, TTo>(ref TFrom source) => ref ILImpl.As<TFrom, TTo>(ref source);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Unbox<T>(object box) where T : struct => ref ILImpl.Unbox<T>(box);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T AddByteOffset<T>(ref T source, nint byteOffset) => ref ILImpl.AddByteOffset(ref source, byteOffset);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T AddByteOffset<T>(ref T source, nuint byteOffset) => ref ILImpl.AddByteOffset(ref source, byteOffset);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T SubtractByteOffset<T>(ref T source, nint byteOffset) => ref ILImpl.SubtractByteOffset(ref source, byteOffset);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T SubtractByteOffset<T>(ref T source, nuint byteOffset) => ref ILImpl.SubtractByteOffset(ref source, byteOffset);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static nint ByteOffset<T>(ref T origin, ref T target) => ILImpl.ByteOffset(ref origin, ref target);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static bool AreSame<T>(ref T left, ref T right) => ILImpl.AreSame(ref left, ref right);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static bool IsAddressGreaterThan<T>(ref T left, ref T right) => ILImpl.IsAddressGreaterThan(ref left, ref right);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static bool IsAddressLessThan<T>(ref T left, ref T right) => ILImpl.IsAddressLessThan(ref left, ref right);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static bool IsNullRef<T>(ref T source) => ILImpl.IsNullRef(ref source);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T NullRef<T>() => ref ILImpl.NullRef<T>();
#nullable enable
#endregion

#if !UNSAFE_IN_ILHELPERS
// See docs/RuntimeIssueNotes.md. Until 2015, Mono returned incorrect values for the sizeof opcode when applied to a type parameter.
// To deal with this, we need to compute type size in another way, and return it as appropriate, specializing all of the below accordingly.
private static class PerTypeValues<T>
{
public static readonly nint TypeSize = ComputeTypeSize();

private static nint ComputeTypeSize()
{
var array = new T[2];
return ILImpl.ByteOffset(ref array[0], ref array[1]);
}
}

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static int SizeOf<T>() => (int)PerTypeValues<T>.TypeSize;

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Add<T>(ref T source, int elementOffset) => ref ILImpl.AddByteOffset(ref source, (nint)elementOffset * PerTypeValues<T>.TypeSize);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void* Add<T>(void* source, int elementOffset) => (byte*)source + (elementOffset * PerTypeValues<T>.TypeSize);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Add<T>(ref T source, nint elementOffset) => ref ILImpl.AddByteOffset(ref source, elementOffset * PerTypeValues<T>.TypeSize);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Add<T>(ref T source, nuint elementOffset) => ref ILImpl.AddByteOffset(ref source, elementOffset * (nuint)PerTypeValues<T>.TypeSize);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Subtract<T>(ref T source, int elementOffset) => ref ILImpl.SubtractByteOffset(ref source, (nint)elementOffset * PerTypeValues<T>.TypeSize);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void* Subtract<T>(void* source, int elementOffset) => (byte*)source - (elementOffset * PerTypeValues<T>.TypeSize);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Subtract<T>(ref T source, nint elementOffset) => ref ILImpl.SubtractByteOffset(ref source, elementOffset * PerTypeValues<T>.TypeSize);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Subtract<T>(ref T source, nuint elementOffset) => ref ILImpl.SubtractByteOffset(ref source, elementOffset * (nuint)PerTypeValues<T>.TypeSize);

#else

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static int SizeOf<T>() => ILImpl.SizeOf<T>();

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Add<T>(ref T source, int elementOffset) => ref ILImpl.Add(ref source, elementOffset);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void* Add<T>(void* source, int elementOffset) => ILImpl.Add<T>(source, elementOffset);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Add<T>(ref T source, nint elementOffset) => ref ILImpl.Add(ref source, elementOffset);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Add<T>(ref T source, nuint elementOffset) => ref ILImpl.Add(ref source, elementOffset);

[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Subtract<T>(ref T source, int elementOffset) => ref ILImpl.Subtract(ref source, elementOffset);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static void* Subtract<T>(void* source, int elementOffset) => ILImpl.Subtract<T>(source, elementOffset);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Subtract<T>(ref T source, nint elementOffset) => ref ILImpl.Subtract(ref source, elementOffset);
[MethodImpl(MethodImplOptionsEx.AggressiveInlining), NonVersionable]
public static ref T Subtract<T>(ref T source, nuint elementOffset) => ref ILImpl.Subtract(ref source, elementOffset);

#endif
}
7 changes: 7 additions & 0 deletions src/MonoMod.Backports/System/NonVersionableAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace System
{
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
internal sealed class NonVersionableAttribute : Attribute
{
}
}
3 changes: 2 additions & 1 deletion src/MonoMod.Core/IDetourFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,10 @@ public static class DetourFactory
public static unsafe IDetourFactory Current
{
[MethodImpl(MethodImplOptionsEx.AggressiveInlining)]
get => Helpers.GetOrInit(ref lazyCurrent, &CreateDefaultFactory);
get => Helpers.GetOrInit(ref lazyCurrent, createDefaultFactoryFunc);
}

private static readonly Func<PlatformTripleDetourFactory> createDefaultFactoryFunc = CreateDefaultFactory;
private static PlatformTripleDetourFactory CreateDefaultFactory()
=> new(PlatformTriple.Current);

Expand Down
3 changes: 2 additions & 1 deletion src/MonoMod.Core/Platforms/Architectures/x86Arch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ internal sealed class x86Arch : IArchitecture
public ArchitectureFeature Features => ArchitectureFeature.CreateAltEntryPoint;

private BytePatternCollection? lazyKnownMethodThunks;
public unsafe BytePatternCollection KnownMethodThunks => Helpers.GetOrInit(ref lazyKnownMethodThunks, &CreateKnownMethodThunks);
public unsafe BytePatternCollection KnownMethodThunks => Helpers.GetOrInit(ref lazyKnownMethodThunks, createKnownMethodThunksFunc);

public IAltEntryFactory AltEntryFactory { get; }

private static readonly Func<BytePatternCollection> createKnownMethodThunksFunc = CreateKnownMethodThunks;
private static BytePatternCollection CreateKnownMethodThunks()
{
const ushort An = BytePattern.SAnyValue;
Expand Down
3 changes: 2 additions & 1 deletion src/MonoMod.Core/Platforms/Architectures/x86_64Arch.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ internal sealed class x86_64Arch : IArchitecture
public ArchitectureFeature Features => ArchitectureFeature.Immediate64 | ArchitectureFeature.CreateAltEntryPoint;

private BytePatternCollection? lazyKnownMethodThunks;
public unsafe BytePatternCollection KnownMethodThunks => Helpers.GetOrInit(ref lazyKnownMethodThunks, &CreateKnownMethodThunks);
public unsafe BytePatternCollection KnownMethodThunks => Helpers.GetOrInit(ref lazyKnownMethodThunks, createKnownMethodThunksFunc);

public IAltEntryFactory AltEntryFactory { get; }

private static readonly Func<BytePatternCollection> createKnownMethodThunksFunc = CreateKnownMethodThunks;
private static BytePatternCollection CreateKnownMethodThunks()
{
const ushort An = BytePattern.SAnyValue;
Expand Down
3 changes: 2 additions & 1 deletion src/MonoMod.Core/Platforms/PlatformTriple.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,9 @@ public static ISystem CreateCurrentSystem()
/// <remarks>
/// This <see cref="PlatformTriple"/> is automatically constructed on first access, according to the values returned by <see cref="PlatformDetection"/>.
/// </remarks>
public static unsafe PlatformTriple Current => Helpers.GetOrInitWithLock(ref lazyCurrent, lazyCurrentLock, &CreateCurrent);
public static unsafe PlatformTriple Current => Helpers.GetOrInitWithLock(ref lazyCurrent, lazyCurrentLock, createCurrentFunc);

private static readonly Func<PlatformTriple> createCurrentFunc = CreateCurrent;
private static PlatformTriple CreateCurrent()
{
var sys = CreateCurrentSystem();
Expand Down
3 changes: 2 additions & 1 deletion src/MonoMod.Core/Platforms/Runtimes/Core21Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,12 @@ internal class Core21Runtime : CoreBaseRuntime
}
*/

private static readonly Func<Core21Runtime, JitHookHelpersHolder> createJitHookHelpersFunc = CreateJitHookHelpers;
private static JitHookHelpersHolder CreateJitHookHelpers(Core21Runtime self) => new(self);

private readonly object sync = new();
private JitHookHelpersHolder? lazyJitHookHelpers;
protected unsafe JitHookHelpersHolder JitHookHelpers => Helpers.GetOrInitWithLock(ref lazyJitHookHelpers, sync, &CreateJitHookHelpers, this);
protected unsafe JitHookHelpersHolder JitHookHelpers => Helpers.GetOrInitWithLock(ref lazyJitHookHelpers, sync, createJitHookHelpersFunc, this);

// src/inc/corinfo.h line 216
// 0ba106c8-81a0-407f-99a1-928448c1eb62
Expand Down
Loading

0 comments on commit 7e55f80

Please sign in to comment.