Operand.cs
  1  using ARMeilleure.CodeGen.Linking;
  2  using ARMeilleure.Common;
  3  using System;
  4  using System.Collections.Generic;
  5  using System.Diagnostics;
  6  using System.Runtime.CompilerServices;
  7  
  8  namespace ARMeilleure.IntermediateRepresentation
  9  {
 10      unsafe struct Operand : IEquatable<Operand>
 11      {
 12          internal struct Data
 13          {
 14              public byte Kind;
 15              public byte Type;
 16              public byte SymbolType;
 17              public byte Padding; // Unused space.
 18              public ushort AssignmentsCount;
 19              public ushort AssignmentsCapacity;
 20              public uint UsesCount;
 21              public uint UsesCapacity;
 22              public Operation* Assignments;
 23              public Operation* Uses;
 24              public ulong Value;
 25              public ulong SymbolValue;
 26          }
 27  
 28          private Data* _data;
 29  
 30          public readonly OperandKind Kind
 31          {
 32              get => (OperandKind)_data->Kind;
 33              private set => _data->Kind = (byte)value;
 34          }
 35  
 36          public readonly OperandType Type
 37          {
 38              get => (OperandType)_data->Type;
 39              private set => _data->Type = (byte)value;
 40          }
 41  
 42          public readonly ulong Value
 43          {
 44              get => _data->Value;
 45              private set => _data->Value = value;
 46          }
 47  
 48          public readonly Symbol Symbol
 49          {
 50              get
 51              {
 52                  Debug.Assert(Kind != OperandKind.Memory);
 53  
 54                  return new Symbol((SymbolType)_data->SymbolType, _data->SymbolValue);
 55              }
 56              private set
 57              {
 58                  Debug.Assert(Kind != OperandKind.Memory);
 59  
 60                  if (value.Type == SymbolType.None)
 61                  {
 62                      _data->SymbolType = (byte)SymbolType.None;
 63                  }
 64                  else
 65                  {
 66                      _data->SymbolType = (byte)value.Type;
 67                      _data->SymbolValue = value.Value;
 68                  }
 69              }
 70          }
 71  
 72          public readonly ReadOnlySpan<Operation> Assignments
 73          {
 74              get
 75              {
 76                  Debug.Assert(Kind != OperandKind.Memory);
 77  
 78                  return new ReadOnlySpan<Operation>(_data->Assignments, _data->AssignmentsCount);
 79              }
 80          }
 81  
 82          public readonly ReadOnlySpan<Operation> Uses
 83          {
 84              get
 85              {
 86                  Debug.Assert(Kind != OperandKind.Memory);
 87  
 88                  return new ReadOnlySpan<Operation>(_data->Uses, (int)_data->UsesCount);
 89              }
 90          }
 91  
 92          public readonly int UsesCount => (int)_data->UsesCount;
 93          public readonly int AssignmentsCount => _data->AssignmentsCount;
 94  
 95          public readonly bool Relocatable => Symbol.Type != SymbolType.None;
 96  
 97          [MethodImpl(MethodImplOptions.AggressiveInlining)]
 98          public readonly Register GetRegister()
 99          {
100              Debug.Assert(Kind == OperandKind.Register);
101  
102              return new Register((int)Value & 0xffffff, (RegisterType)(Value >> 24));
103          }
104  
105          [MethodImpl(MethodImplOptions.AggressiveInlining)]
106          public readonly MemoryOperand GetMemory()
107          {
108              Debug.Assert(Kind == OperandKind.Memory);
109  
110              return new MemoryOperand(this);
111          }
112  
113          public readonly int GetLocalNumber()
114          {
115              Debug.Assert(Kind == OperandKind.LocalVariable);
116  
117              return (int)Value;
118          }
119  
120          public readonly byte AsByte()
121          {
122              return (byte)Value;
123          }
124  
125          public readonly short AsInt16()
126          {
127              return (short)Value;
128          }
129  
130          public readonly int AsInt32()
131          {
132              return (int)Value;
133          }
134  
135          public readonly long AsInt64()
136          {
137              return (long)Value;
138          }
139  
140          public readonly float AsFloat()
141          {
142              return BitConverter.Int32BitsToSingle((int)Value);
143          }
144  
145          public readonly double AsDouble()
146          {
147              return BitConverter.Int64BitsToDouble((long)Value);
148          }
149  
150          [MethodImpl(MethodImplOptions.AggressiveInlining)]
151          internal readonly ref ulong GetValueUnsafe()
152          {
153              return ref _data->Value;
154          }
155  
156          internal void NumberLocal(int number)
157          {
158              if (Kind != OperandKind.LocalVariable)
159              {
160                  throw new InvalidOperationException("The operand is not a local variable.");
161              }
162  
163              Value = (ulong)number;
164          }
165  
166          public readonly void AddAssignment(Operation operation)
167          {
168              if (Kind == OperandKind.LocalVariable)
169              {
170                  Add(operation, ref _data->Assignments, ref _data->AssignmentsCount, ref _data->AssignmentsCapacity);
171              }
172              else if (Kind == OperandKind.Memory)
173              {
174                  MemoryOperand memOp = GetMemory();
175                  Operand addr = memOp.BaseAddress;
176                  Operand index = memOp.Index;
177  
178                  if (addr != default)
179                  {
180                      Add(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount, ref addr._data->AssignmentsCapacity);
181                  }
182  
183                  if (index != default)
184                  {
185                      Add(operation, ref index._data->Assignments, ref index._data->AssignmentsCount, ref index._data->AssignmentsCapacity);
186                  }
187              }
188          }
189  
190          public readonly void RemoveAssignment(Operation operation)
191          {
192              if (Kind == OperandKind.LocalVariable)
193              {
194                  Remove(operation, ref _data->Assignments, ref _data->AssignmentsCount);
195              }
196              else if (Kind == OperandKind.Memory)
197              {
198                  MemoryOperand memOp = GetMemory();
199                  Operand addr = memOp.BaseAddress;
200                  Operand index = memOp.Index;
201  
202                  if (addr != default)
203                  {
204                      Remove(operation, ref addr._data->Assignments, ref addr._data->AssignmentsCount);
205                  }
206  
207                  if (index != default)
208                  {
209                      Remove(operation, ref index._data->Assignments, ref index._data->AssignmentsCount);
210                  }
211              }
212          }
213  
214          public readonly void AddUse(Operation operation)
215          {
216              if (Kind == OperandKind.LocalVariable)
217              {
218                  Add(operation, ref _data->Uses, ref _data->UsesCount, ref _data->UsesCapacity);
219              }
220              else if (Kind == OperandKind.Memory)
221              {
222                  MemoryOperand memOp = GetMemory();
223                  Operand addr = memOp.BaseAddress;
224                  Operand index = memOp.Index;
225  
226                  if (addr != default)
227                  {
228                      Add(operation, ref addr._data->Uses, ref addr._data->UsesCount, ref addr._data->UsesCapacity);
229                  }
230  
231                  if (index != default)
232                  {
233                      Add(operation, ref index._data->Uses, ref index._data->UsesCount, ref index._data->UsesCapacity);
234                  }
235              }
236          }
237  
238          public readonly void RemoveUse(Operation operation)
239          {
240              if (Kind == OperandKind.LocalVariable)
241              {
242                  Remove(operation, ref _data->Uses, ref _data->UsesCount);
243              }
244              else if (Kind == OperandKind.Memory)
245              {
246                  MemoryOperand memOp = GetMemory();
247                  Operand addr = memOp.BaseAddress;
248                  Operand index = memOp.Index;
249  
250                  if (addr != default)
251                  {
252                      Remove(operation, ref addr._data->Uses, ref addr._data->UsesCount);
253                  }
254  
255                  if (index != default)
256                  {
257                      Remove(operation, ref index._data->Uses, ref index._data->UsesCount);
258                  }
259              }
260          }
261  
262          public readonly Span<Operation> GetUses(ref Span<Operation> buffer)
263          {
264              ReadOnlySpan<Operation> uses = Uses;
265  
266              if (buffer.Length < uses.Length)
267              {
268                  buffer = Allocators.Default.AllocateSpan<Operation>((uint)uses.Length);
269              }
270  
271              uses.CopyTo(buffer);
272  
273              return buffer[..uses.Length];
274          }
275  
276          private static void New<T>(ref T* data, ref ushort count, ref ushort capacity, ushort initialCapacity) where T : unmanaged
277          {
278              count = 0;
279              capacity = initialCapacity;
280              data = Allocators.References.Allocate<T>(initialCapacity);
281          }
282  
283          private static void New<T>(ref T* data, ref uint count, ref uint capacity, uint initialCapacity) where T : unmanaged
284          {
285              count = 0;
286              capacity = initialCapacity;
287              data = Allocators.References.Allocate<T>(initialCapacity);
288          }
289  
290          private static void Add<T>(T item, ref T* data, ref ushort count, ref ushort capacity) where T : unmanaged
291          {
292              if (count < capacity)
293              {
294                  data[(uint)count++] = item;
295  
296                  return;
297              }
298  
299              // Could not add item in the fast path, fallback onto the slow path.
300              ExpandAdd(item, ref data, ref count, ref capacity);
301  
302              static void ExpandAdd(T item, ref T* data, ref ushort count, ref ushort capacity)
303              {
304                  ushort newCount = checked((ushort)(count + 1));
305                  ushort newCapacity = (ushort)Math.Min(capacity * 2, ushort.MaxValue);
306  
307                  var oldSpan = new Span<T>(data, count);
308  
309                  capacity = newCapacity;
310                  data = Allocators.References.Allocate<T>(capacity);
311  
312                  oldSpan.CopyTo(new Span<T>(data, count));
313  
314                  data[count] = item;
315                  count = newCount;
316              }
317          }
318  
319          private static void Add<T>(T item, ref T* data, ref uint count, ref uint capacity) where T : unmanaged
320          {
321              if (count < capacity)
322              {
323                  data[count++] = item;
324  
325                  return;
326              }
327  
328              // Could not add item in the fast path, fallback onto the slow path.
329              ExpandAdd(item, ref data, ref count, ref capacity);
330  
331              static void ExpandAdd(T item, ref T* data, ref uint count, ref uint capacity)
332              {
333                  uint newCount = checked(count + 1);
334                  uint newCapacity = (uint)Math.Min(capacity * 2, int.MaxValue);
335  
336                  if (newCapacity <= capacity)
337                  {
338                      throw new OverflowException();
339                  }
340  
341                  var oldSpan = new Span<T>(data, (int)count);
342  
343                  capacity = newCapacity;
344                  data = Allocators.References.Allocate<T>(capacity);
345  
346                  oldSpan.CopyTo(new Span<T>(data, (int)count));
347  
348                  data[count] = item;
349                  count = newCount;
350              }
351          }
352  
353          private static void Remove<T>(in T item, ref T* data, ref ushort count) where T : unmanaged
354          {
355              var span = new Span<T>(data, count);
356  
357              for (int i = 0; i < span.Length; i++)
358              {
359                  if (EqualityComparer<T>.Default.Equals(span[i], item))
360                  {
361                      if (i + 1 < count)
362                      {
363                          span[(i + 1)..].CopyTo(span[i..]);
364                      }
365  
366                      count--;
367  
368                      return;
369                  }
370              }
371          }
372  
373          private static void Remove<T>(in T item, ref T* data, ref uint count) where T : unmanaged
374          {
375              var span = new Span<T>(data, (int)count);
376  
377              for (int i = 0; i < span.Length; i++)
378              {
379                  if (EqualityComparer<T>.Default.Equals(span[i], item))
380                  {
381                      if (i + 1 < count)
382                      {
383                          span[(i + 1)..].CopyTo(span[i..]);
384                      }
385  
386                      count--;
387  
388                      return;
389                  }
390              }
391          }
392  
393          public readonly override int GetHashCode()
394          {
395              return ((ulong)_data).GetHashCode();
396          }
397  
398          public readonly bool Equals(Operand operand)
399          {
400              return operand._data == _data;
401          }
402  
403          public readonly override bool Equals(object obj)
404          {
405              return obj is Operand operand && Equals(operand);
406          }
407  
408          public static bool operator ==(Operand a, Operand b)
409          {
410              return a.Equals(b);
411          }
412  
413          public static bool operator !=(Operand a, Operand b)
414          {
415              return !a.Equals(b);
416          }
417  
418          public static class Factory
419          {
420              private const int InternTableSize = 256;
421              private const int InternTableProbeLength = 8;
422  
423              [ThreadStatic]
424              private static Data* _internTable;
425  
426              private static Data* InternTable
427              {
428                  get
429                  {
430                      if (_internTable == null)
431                      {
432                          _internTable = (Data*)NativeAllocator.Instance.Allocate((uint)sizeof(Data) * InternTableSize);
433  
434                          // Make sure the table is zeroed.
435                          new Span<Data>(_internTable, InternTableSize).Clear();
436                      }
437  
438                      return _internTable;
439                  }
440              }
441  
442              private static Operand Make(OperandKind kind, OperandType type, ulong value, Symbol symbol = default)
443              {
444                  Debug.Assert(kind != OperandKind.None);
445  
446                  Data* data = null;
447  
448                  // If constant or register, then try to look up in the intern table before allocating.
449                  if (kind == OperandKind.Constant || kind == OperandKind.Register)
450                  {
451                      uint hash = (uint)HashCode.Combine(kind, type, value);
452  
453                      // Look in the next InternTableProbeLength slots for a match.
454                      for (uint i = 0; i < InternTableProbeLength; i++)
455                      {
456                          Operand interned = new()
457                          {
458                              _data = &InternTable[(hash + i) % InternTableSize],
459                          };
460  
461                          // If slot matches the allocation request then return that slot.
462                          if (interned.Kind == kind && interned.Type == type && interned.Value == value && interned.Symbol == symbol)
463                          {
464                              return interned;
465                          }
466                          // Otherwise if the slot is not occupied, we store in that slot.
467                          else if (interned.Kind == OperandKind.None)
468                          {
469                              data = interned._data;
470  
471                              break;
472                          }
473                      }
474                  }
475  
476                  // If we could not get a slot from the intern table, we allocate somewhere else and store there.
477                  if (data == null)
478                  {
479                      data = Allocators.Operands.Allocate<Data>();
480                  }
481  
482                  *data = default;
483  
484                  Operand result = new()
485                  {
486                      _data = data,
487                      Value = value,
488                      Kind = kind,
489                      Type = type,
490                  };
491  
492                  if (kind != OperandKind.Memory)
493                  {
494                      result.Symbol = symbol;
495                  }
496  
497                  // If local variable, then the use and def list is initialized with default sizes.
498                  if (kind == OperandKind.LocalVariable)
499                  {
500                      New(ref result._data->Assignments, ref result._data->AssignmentsCount, ref result._data->AssignmentsCapacity, 1);
501                      New(ref result._data->Uses, ref result._data->UsesCount, ref result._data->UsesCapacity, 4);
502                  }
503  
504                  return result;
505              }
506  
507              public static Operand Const(OperandType type, long value)
508              {
509                  Debug.Assert(type is OperandType.I32 or OperandType.I64);
510  
511                  return type == OperandType.I32 ? Const((int)value) : Const(value);
512              }
513  
514              public static Operand Const(bool value)
515              {
516                  return Const(value ? 1 : 0);
517              }
518  
519              public static Operand Const(int value)
520              {
521                  return Const((uint)value);
522              }
523  
524              public static Operand Const(uint value)
525              {
526                  return Make(OperandKind.Constant, OperandType.I32, value);
527              }
528  
529              public static Operand Const(long value)
530              {
531                  return Const(value, symbol: default);
532              }
533  
534              public static Operand Const<T>(ref T reference, Symbol symbol = default)
535              {
536                  return Const((long)Unsafe.AsPointer(ref reference), symbol);
537              }
538  
539              public static Operand Const(long value, Symbol symbol)
540              {
541                  return Make(OperandKind.Constant, OperandType.I64, (ulong)value, symbol);
542              }
543  
544              public static Operand Const(ulong value)
545              {
546                  return Make(OperandKind.Constant, OperandType.I64, value);
547              }
548  
549              public static Operand ConstF(float value)
550              {
551                  return Make(OperandKind.Constant, OperandType.FP32, (ulong)BitConverter.SingleToInt32Bits(value));
552              }
553  
554              public static Operand ConstF(double value)
555              {
556                  return Make(OperandKind.Constant, OperandType.FP64, (ulong)BitConverter.DoubleToInt64Bits(value));
557              }
558  
559              public static Operand Label()
560              {
561                  return Make(OperandKind.Label, OperandType.None, 0);
562              }
563  
564              public static Operand Local(OperandType type)
565              {
566                  return Make(OperandKind.LocalVariable, type, 0);
567              }
568  
569              public static Operand Register(int index, RegisterType regType, OperandType type)
570              {
571                  return Make(OperandKind.Register, type, (ulong)((int)regType << 24 | index));
572              }
573  
574              public static Operand Undef()
575              {
576                  return Make(OperandKind.Undefined, OperandType.None, 0);
577              }
578  
579              public static Operand MemoryOp(
580                  OperandType type,
581                  Operand baseAddress,
582                  Operand index = default,
583                  Multiplier scale = Multiplier.x1,
584                  int displacement = 0)
585              {
586                  Operand result = Make(OperandKind.Memory, type, 0);
587  
588                  MemoryOperand memory = result.GetMemory();
589                  memory.BaseAddress = baseAddress;
590                  memory.Index = index;
591                  memory.Scale = scale;
592                  memory.Displacement = displacement;
593  
594                  return result;
595              }
596          }
597      }
598  }