Runtime: Unsafe API for comparing byrefs as pointers

Created on 19 Jan 2018  路  18Comments  路  Source: dotnet/runtime

S.R.CS.Unsafe should provide operations that allow comparing byrefs. These operations in combination with C# support for ref locals reassigments (Roslyn feature in progress - https://github.com/dotnet/csharplang/blob/master/proposals/ref-local-reassignment.md) will allow significantly more efficient unsafe implementations of low-level algorithms.

For example, Span Reverse prototype done by @GrabYourPitchforks showed up to 30% performance improvements (https://github.com/dotnet/corefx/pull/26381#issuecomment-358852586).

Proposed API:

public static class Unsafe
{
    // Returns true when left is pointing to lower memory address than right
    public static bool IsBelow<T>(ref T left, ref T right);

    // Returns true when left is pointing to higher memory address than right
    public static bool IsAbove<T>(ref T left, ref T right);
}
api-approved area-System.Runtime.CompilerServices

Most helpful comment

Video

We think these names align closer to the framework naming:

C# public static class Unsafe { public static bool IsAddressLessThan<T>(ref T left, ref T right); public static bool IsAddressGreaterThan<T>(ref T left, ref T right); }

All 18 comments

cc @GrabYourPitchforks @KrzysztofCwalina @ahsonkhan

Between?

public static bool IsBetween<T>(ref T left, ref T right, ref target);

Why ref bool rather than bool as the return value?

ref bool

Copy&paste mistake. Updated above.

Could C# support the full set of comparison operators for ref types instead?

The way those names read is unfortunate: I mentally read out a line of code like:

`if (Unsafe.IsBelow(ref left, ref right))`

and I hear "if is below left" which is the opposite of what the conditional does...

bool IsBetween

S.R.CS.Unsafe APIs are trying to have 1:1 mapping to IL instructions. IsBetween does not map to a single IL instruction. It is a convenience wrapper over pair of IsAbove+IsBelow calls.

Having said that, we have violated this design principle in a few places when it has a demonstrated benefits. Do you have a good case where IsBetween would help significantly?

Could C# support the full set of comparison operators for ref types instead?

Adding operators for byrefs is hard to fit into C# design. This applies to all pointer-like operations, not just comparison: add/subtract, casting, ... . If it was not the case, S.R.CS.Unsafe would not be really needed.

I hear "if is below left" which is the opposite of what the conditional does

Can you think about a different name that would read better?

Can you think about a different name that would read better?

I'll throw in IsLeftBelowRight() as an opening bid. I'll see if I can brainstorm something more succinct but given Unsafe's low-level nature, and a really good solution probably requires an infix operator, I could live with this.

I'll assume a standard Compare method that returns +1/-1/0 is off the table (can't generate the same short IL...)

Do you have a good case where IsBetween would help significantly?

Similar to the Span.IsSliceOf(Span<T> span) https://github.com/dotnet/corefx/issues/18750 which went on to be Overlaps https://github.com/dotnet/corefx/pull/24980; but not necessarily using Span (though could be used for this single ref in this Span)

I don't have a strong use case

The only argument for _IsBetween_ that I can imagine is that it's a notoriously difficult check for the average developer to get correct. But at that point we're talking about callers that manipulate memory directly, and realistically I don't know who other than the BCL would ever be doing that.

Can you think about a different name that would read better?

Something closer to Compare/CompareTo

IsLessThan(value1, value2)
IsGreatherThan(value1, value2)

Video

We think these names align closer to the framework naming:

C# public static class Unsafe { public static bool IsAddressLessThan<T>(ref T left, ref T right); public static bool IsAddressGreaterThan<T>(ref T left, ref T right); }

Given that the GC can relocate the references during or after the call, how is possible to use this API correctly? If both references need to be pinned, we typically document that in the method name (similarly to GCHandle.AddrOfPinnedObject).

I'd also like to confirm that this API really is unsafe. The answer might be wrong if references aren't pinned, but the method won't corrupt memory.

how is possible to use this API correctly

If the references are not pinned, you have to make sure that both point into same object or array.

I'd also like to confirm that this API really is unsafe.

You are right that this API is not going to corrupt your memory. Unsafe here really means that you have to know what you are doing. here are other similar APIs on this type like Unsafe.SizeOf<T>(). It is similar to unsafe context in C#: there is nothing strictly unsafe about sizeof(Foo), but C# compiler does not allow you to do that outside unsafe context in number of cases.

It would be good to describe in the docs the things people need to know to use the API "safely".

@GrabYourPitchforks , is the API guaranteed to pin the inputs for the same array/object/span case? Otherwise a torn read could still give an undefined answer.

@morganbr There is no risk of torn read. The GC moves all byrefs atomically.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

omariom picture omariom  路  3Comments

GitAntoinee picture GitAntoinee  路  3Comments

iCodeWebApps picture iCodeWebApps  路  3Comments

chunseoklee picture chunseoklee  路  3Comments

jkotas picture jkotas  路  3Comments