Skip to content

Commit 05df495

Browse files
committed
Improve DangerousGetValueOrNullReference codegen
1 parent 7e56365 commit 05df495

File tree

1 file changed

+17
-3
lines changed

1 file changed

+17
-3
lines changed

CommunityToolkit.HighPerformance/Extensions/NullableExtensions.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,30 @@ public static ref T DangerousGetValueOrDefaultReference<T>(this ref T? value)
5353
public static unsafe ref T DangerousGetValueOrNullReference<T>(ref this T? value)
5454
where T : struct
5555
{
56+
ref T resultRef = ref Unsafe.NullRef<T>();
57+
58+
// This pattern ensures that the resulting code ends up having a single return, and a single
59+
// forward branch (the one where the value is null) that is predicted non taken. That is,
60+
// the initial null ref is very cheap as it's just clearing a register, and the rest of the
61+
// code is a single assignment (lea on x86-64) that should always be taken. This results in:
62+
// =============================
63+
// L0000: xor eax, eax
64+
// L0002: cmp byte ptr[rcx], 0
65+
// L0005: je short L000b
66+
// L0007: lea rax, [rcx + 4]
67+
// L000b: ret
68+
// =============================
69+
// This is better than what the code would've been with two separate returns in the method.
5670
if (value.HasValue)
5771
{
5872
#if NET7_0_OR_GREATER
59-
return ref Unsafe.AsRef(in Nullable.GetValueRefOrDefaultRef(in value));
73+
resultRef = ref Unsafe.AsRef(in Nullable.GetValueRefOrDefaultRef(in value));
6074
#else
61-
return ref Unsafe.As<T?, RawNullableData<T>>(ref value).Value;
75+
resultRef = ref Unsafe.As<T?, RawNullableData<T>>(ref value).Value;
6276
#endif
6377
}
6478

65-
return ref Unsafe.NullRef<T>();
79+
return ref resultRef;
6680
}
6781

6882
#if !NET7_0_OR_GREATER

0 commit comments

Comments
 (0)