fix: Fixes sending packets and sidesteps a major issue with stackalloc and PGO in .NET 8 (#1607)

### Summary
- Works around a sneaky edge case bug in the JIT with stackalloc where sometimes the buffer is not zero'd.
- Fixes SendDisplayBoatHS
- Fixes sending health bars in the `SendEverything()` logic.
- Fixes a bug in sizing for some string helper functions.

### Developer Note
We are enabled `SkipLocalsInit` - do not rely on `stackalloc` to be zero'd. To zero the buffer, use `span.Clear();`

Closes #1606
This commit is contained in:
Kamron Batman 2023-11-21 12:18:20 -08:00 • committed by GitHub
parent 79ba1ea917
commit 03f850fe03
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 67 additions and 76 deletions

View file

@ -21,9 +21,10 @@ namespace Server.Json;
public class Point2DConverter : JsonConverter<Point2D>
{
private Point2D DeserializeArray(ref Utf8JsonReader reader)
private static Point2D DeserializeArray(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[2];
data.Clear();
var count = 0;
while (true)
@ -53,9 +54,10 @@ public class Point2DConverter : JsonConverter<Point2D>
return new Point2D(data[0], data[1]);
}
private Point2D DeserializeObj(ref Utf8JsonReader reader)
private static Point2D DeserializeObj(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[2];
data.Clear();
while (true)
{

View file

@ -21,9 +21,11 @@ namespace Server.Json;
public class Point3DConverter : JsonConverter<Point3D>
{
private Point3D DeserializeArray(ref Utf8JsonReader reader)
private static Point3D DeserializeArray(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[3];
data.Clear();
var count = 0;
while (true)
@ -53,9 +55,10 @@ public class Point3DConverter : JsonConverter<Point3D>
return new Point3D(data[0], data[1], data[2]);
}
private Point3D DeserializeObj(ref Utf8JsonReader reader)
private static Point3D DeserializeObj(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[3];
data.Clear();
while (true)
{

View file

@ -21,9 +21,11 @@ namespace Server.Json;
public class Rectangle3DConverter : JsonConverter<Rectangle3D>
{
private Rectangle3D DeserializeArray(ref Utf8JsonReader reader)
private static Rectangle3D DeserializeArray(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[6];
data.Clear();
var count = 0;
while (true)
@ -53,9 +55,10 @@ public class Rectangle3DConverter : JsonConverter<Rectangle3D>
return new Rectangle3D(data[0], data[1], data[2], data[3], data[4], data[5]);
}
private Rectangle3D DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options)
private static Rectangle3D DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
Span<int> data = stackalloc int[6];
data.Clear();
// 0 - xyzwhd, 1 - x1y1z1x2y2z2, 2 - start/end
var objType = -1;

View file

@ -24,9 +24,11 @@ public class WorldLocationConverter : JsonConverter<WorldLocation>
private static Point3DConverter _point3DConverter;
private static MapConverter _mapConverter;
private WorldLocation DeserializeArray(ref Utf8JsonReader reader)
private static WorldLocation DeserializeArray(ref Utf8JsonReader reader)
{
Span<int> data = stackalloc int[3];
data.Clear();
var count = 0;
var hasMap = false;
Map map = null;
@ -77,9 +79,11 @@ public class WorldLocationConverter : JsonConverter<WorldLocation>
return new WorldLocation(data[0], data[1], data[2], map);
}
private WorldLocation DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options)
private static WorldLocation DeserializeObj(ref Utf8JsonReader reader, JsonSerializerOptions options)
{
Span<int> data = stackalloc int[3];
data.Clear();
var hasLoc = false;
var hasXYZ = false;
var hasMap = false;

View file

@ -2721,9 +2721,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
Span<byte> facialHairPacket = stackalloc byte[facialHairLength].InitializePacket();
const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength;
const int width = OutgoingMobilePackets.MobileMovingPacketLength;
var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width);
Span<byte> mobileMovingCache = stackalloc byte[cacheLength];
mobileMovingCache.Clear();
var ourState = m_NetState;
@ -4369,9 +4369,9 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
if (moveClientQueue.Count > 0)
{
const int cacheLength = OutgoingMobilePackets.MobileMovingPacketCacheByteLength;
const int width = OutgoingMobilePackets.MobileMovingPacketLength;
var mobileMovingCache = stackalloc byte[cacheLength].InitializePackets(width);
Span<byte> mobileMovingCache = stackalloc byte[cacheLength];
mobileMovingCache.Clear();
while (moveClientQueue.Count > 0)
{
@ -6923,8 +6923,14 @@ public partial class Mobile : IHued, IComparable<Mobile>, ISpawnable, IObjectPro
if (ns.StygianAbyss)
{
ns.SendMobileHealthbar(m, Healthbar.Poison);
ns.SendMobileHealthbar(m, Healthbar.Yellow);
if (m.Blessed || m.YellowHealthbar)
{
ns.SendMobileHealthbar(m, Healthbar.Yellow);
}
else if (m.Poisoned)
{
ns.SendMobileHealthbar(m, Healthbar.Poison);
}
}
if (m.IsDeadBondedPet)

View file

@ -0,0 +1,4 @@
using System.Runtime.CompilerServices;
// Skips initializing stackalloc with zeros.
[module: SkipLocalsInit]

View file

@ -35,24 +35,7 @@ public static class PacketUtilities
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<byte> InitializePacket(this Span<byte> buffer)
{
#if NO_LOCAL_INIT
if (buffer != null)
{
buffer[0] = 0;
}
#endif
return buffer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Span<byte> InitializePackets(this Span<byte> buffer, int width)
{
#if NO_LOCAL_INIT
for (var i = 0; i < buffer.Length; i += width)
{
buffer[i] = 0;
}
#endif
buffer[0] = 0;
return buffer;
}
}

View file

@ -15,7 +15,10 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using Server.Items;
using Server.Text;
namespace Server.Network;
@ -25,6 +28,7 @@ public static class OutgoingEntityPackets
public const int RemoveEntityLength = 5;
public const int MaxWorldEntityPacketLength = 26;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CreateOPLInfo(Span<byte> buffer, Item item) =>
CreateOPLInfo(buffer, item.Serial, item.PropertyList.Hash);
@ -41,6 +45,7 @@ public static class OutgoingEntityPackets
writer.Write(hash);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void SendOPLInfo(this NetState ns, IObjectPropertyListEntity obj) =>
ns.SendOPLInfo(obj.Serial, obj.PropertyList.Hash);

View file

@ -604,9 +604,7 @@ public static class OutgoingMobilePackets
}
Span<bool> layers = stackalloc bool[256];
#if NO_LOCAL_INIT
layers.Clear();
#endif
layers.Clear();
var eq = beheld.Items;
var maxLength = 23 + (eq.Count + 2) * 9;

View file

@ -82,27 +82,14 @@ public static class StringHelpers
return "";
}
Span<char> span = a.Length < 1024 ? stackalloc char[a.Length] : null;
char[] chrs;
if (span == null)
{
chrs = STArrayPool<char>.Shared.Rent(a.Length);
span = chrs.AsSpan();
}
else
{
chrs = null;
}
char[] chrs = STArrayPool<char>.Shared.Rent(a.Length);
var span = chrs.AsSpan(0, a.Length);
a.Remove(b, comparison, span, out var size);
var str = span[..size].ToString();
if (chrs != null)
{
STArrayPool<char>.Shared.Return(chrs);
}
STArrayPool<char>.Shared.Return(chrs);
return str;
}
@ -114,17 +101,8 @@ public static class StringHelpers
return value;
}
Span<char> span = value.Length < 1024 ? stackalloc char[value.Length] : null;
char[] chrs;
if (span == null)
{
chrs = STArrayPool<char>.Shared.Rent(value.Length);
span = chrs.AsSpan();
}
else
{
chrs = null;
}
char[] chrs = STArrayPool<char>.Shared.Rent(value.Length);
var span = chrs.AsSpan(0, value.Length);
var sliced = value.AsSpan();
// Copy over the previous span
@ -161,11 +139,7 @@ public static class StringHelpers
var str = span.ToString();
if (chrs != null)
{
STArrayPool<char>.Shared.Return(chrs);
}
STArrayPool<char>.Shared.Return(chrs);
return str;
}

View file

@ -1001,6 +1001,8 @@ public static class Utility
var length = source.Length;
Span<bool> list = stackalloc bool[length];
list.Clear();
var sampleList = new T[count];
var i = 0;
@ -1025,6 +1027,8 @@ public static class Utility
var length = source.Count;
Span<bool> list = stackalloc bool[length];
list.Clear();
var sampleList = new List<T>(count);
var i = 0;
@ -1049,6 +1053,7 @@ public static class Utility
var length = source.Length;
Span<bool> list = stackalloc bool[length];
list.Clear();
var i = 0;
do