A game about forced loneliness, made by TACStudios
1#if UNITY_EDITOR || BURST_INTERNAL
2using System;
3using System.Collections.Generic;
4using System.Diagnostics;
5using System.Runtime.InteropServices;
6using System.Text;
7using Debug = UnityEngine.Debug;
8
9namespace Unity.Burst.Editor
10{
11 /// <summary>
12 /// Disassembler for Intel and ARM
13 /// </summary>
14 internal partial class BurstDisassembler
15 {
16 // The following member need to be reset/clear on each Reset()
17 private readonly Dictionary<int, string> _fileName;
18 private readonly Dictionary<int, string[]> _fileList;
19 private readonly List<AsmToken> _tokens;
20 private readonly List<AsmBlock> _blocks;
21 private readonly List<string> _blockToString;
22 private readonly List<int> _columnIndices;
23 private readonly List<AsmLine> _lines;
24 internal UsedRegisters _registersUsedAtLine;
25 private readonly DictionaryGlobalLabel _globalLabels;
26 private readonly List<TempLabelRef> _tempLabelRefs;
27 private readonly Dictionary<int, StringSlice> _mapBlockIndexToGlobalLabel;
28 private DictionaryLocalLabel _currentDictLocalLabel;
29 public bool IsInitialized { get; private set; }
30
31 // ^^^
32 private string _input;
33 private AsmKind _inputAsmKind;
34 internal readonly StringBuilder _output;
35 private bool _colored;
36
37 // This is used to aligned instructions and there operands so they look like this
38 //
39 // mulps x,x,x
40 // shufbps x,x,x
41 //
42 // instead of
43 //
44 // mulps x,x,x
45 // shufbps x,x,x
46 //
47 // Notice if instruction name is longer than this no alignment will be done.
48 private const int InstructionAlignment = 10;
49
50 private static readonly StringSlice CVLocDirective = new StringSlice(".cv_loc");
51
52 // Colors used for the tokens
53 // TODO: Make this configurable via some editor settings?
54 private const string DarkColorLineDirective = "#FFFF00";
55 private const string DarkColorDirective = "#CCCCCC";
56 private const string DarkColorIdentifier = "#d4d4d4";
57 private const string DarkColorQualifier = "#DCDCAA";
58 private const string DarkColorInstruction = "#4EC9B0";
59 internal const string DarkColorInstructionSIMD = "#C586C0";
60 internal const string DarkColorInstructionSIMDPacked = "#A586C0";
61 internal const string DarkColorInstructionSIMDScalar = "#E586C0";
62 private const string DarkColorRegister = "#d7ba7d";
63 private const string DarkColorNumber = "#9cdcfe";
64 private const string DarkColorString = "#ce9178";
65 private const string DarkColorComment = "#6A9955";
66
67 private const string LightColorLineDirective = "#888800";
68 private const string LightColorDirective = "#444444";
69 private const string LightColorIdentifier = "#1c1c1c";
70 private const string LightColorQualifier = "#267f99";
71 private const string LightColorInstruction = "#0451a5";
72 private const string LightColorInstructionSIMD = "#0000ff";
73 private const string LightColorInstructionSIMDPacked = "#8000ff";
74 private const string LightColorInstructionSIMDScalar = "#8050ff";
75 private const string LightColorRegister = "#811f3f";
76 private const string LightColorNumber = "#007ACC";
77 private const string LightColorString = "#a31515";
78 private const string LightColorComment = "#008000";
79
80 private string ColorLineDirective;
81 private string ColorDirective;
82 private string ColorIdentifier;
83 private string ColorQualifier;
84 private string ColorInstruction;
85 private string ColorInstructionSIMD;
86 private string ColorInstructionSIMDPacked;
87 private string ColorInstructionSIMDScalar;
88 private string ColorRegister;
89 private string ColorNumber;
90 private string ColorString;
91 private string ColorComment;
92
93 private char _commentStart;
94
95 public BurstDisassembler()
96 {
97 _fileName = new Dictionary<int, string>();
98 _fileList = new Dictionary<int, string[]>();
99 _tokens = new List<AsmToken>(65536);
100 _blocks = new List<AsmBlock>(128);
101 _blockToString = new List<string>(128);
102 _columnIndices = new List<int>(65536);
103 _lines = new List<AsmLine>(4096);
104 _registersUsedAtLine = new UsedRegisters(4096);
105 _tempLabelRefs = new List<TempLabelRef>(4096);
106 _globalLabels = new DictionaryGlobalLabel(128);
107 _mapBlockIndexToGlobalLabel = new Dictionary<int, StringSlice>(128);
108 _output = new StringBuilder();
109 }
110
111 internal List<int> ColumnIndices => _columnIndices;
112
113 /// <summary>
114 /// Gets all the blocks.
115 /// </summary>
116 public List<AsmBlock> Blocks => _blocks;
117
118 /// <summary>
119 /// Gets whether the disassembly is colored.
120 /// </summary>
121 public bool IsColored => _colored;
122
123 /// <summary>
124 /// Gets all the lines for all the blocks.
125 /// </summary>
126 public List<AsmLine> Lines => _lines;
127
128 /// <summary>
129 /// Gets all the tokens
130 /// </summary>
131 public List<AsmToken> Tokens => _tokens;
132
133 public int LineUsedReg(int lineIdx, string reg) => _registersUsedAtLine.RegisterMatch(lineIdx, reg);
134 public bool LineUsesRegs(int lineIdx, out List<string> usedRegs) => _registersUsedAtLine.LineContainsRegs(lineIdx, out usedRegs);
135 public List<string> CleanRegs(List<string> regs) => _registersUsedAtLine.CleanRegs(regs);
136
137 public int GetRegisterTokenIndex(AsmLine line, string reg, int startIndex = 0)
138 {
139 var idx = -1;
140
141 var i = Math.Max(line.TokenIndex, startIndex);
142 var len = line.TokenIndex + line.Length;
143 for (; i < len; i++)
144 {
145 var token = Tokens[i];
146 if (_registersUsedAtLine.RegisterEquality(reg, GetTokenAsText(token)))
147 {
148 idx = i;
149 break;
150 }
151 }
152
153 return idx;
154 }
155
156 /// <summary>
157 /// Get a token index for a particular block, line number and column number.
158 /// </summary>
159 /// <param name="blockIndex"></param>
160 /// <param name="line"></param>
161 /// <param name="column"></param>
162 /// <param name="lineIndex">Returns the line index to query <see cref="Lines"/></param>
163 /// <returns>The token index to use with <see cref="GetToken"/> or -1 if the line, column was not found.</returns>
164 public int GetTokenIndexFromColumn(int blockIndex, int line, int column, out int lineIndex)
165 {
166 lineIndex = -1;
167 var block = _blocks[blockIndex];
168 var lineStartIndex = block.LineIndex + line;
169 var asmLine = _lines[lineStartIndex];
170 if (asmLine.Kind != AsmLineKind.SourceFileLocation)
171 {
172 var columnIndex = asmLine.ColumnIndex;
173 for (int j = 1; j < asmLine.Length; j++)
174 {
175 // _columnIndices doesn't have an index for the first token (because the column is always 0)
176 var tokenColumn = _columnIndices[columnIndex + j - 1];
177 var token = GetToken(asmLine.TokenIndex + j);
178
179 if (tokenColumn <= column && column < tokenColumn + token.Length)
180 {
181 lineIndex = lineStartIndex;
182 return asmLine.TokenIndex + j;
183 }
184 }
185 }
186 return -1;
187 }
188
189 /// <summary>
190 /// Gets or renders a particular block to text without caching the result.
191 /// </summary>
192 /// <param name="blockIndex">The block to render.</param>
193 /// <param name="colored">Whether output should be colored.</param>
194 /// <returns>A string representation of the block.</returns>
195 public string GetOrRenderBlockToTextUncached(int blockIndex, bool colored)
196 {
197 return RenderBlock(blockIndex, colored);
198 }
199
200 /// <summary>
201 /// Gets or renders a particular block to text (colored if specified at <see cref="Initialize"/> time)
202 /// </summary>
203 /// <param name="blockIndex">The block to render.</param>
204 /// <returns>A string representation of the block.</returns>
205 public string GetOrRenderBlockToText(int blockIndex)
206 {
207 var str = _blockToString[blockIndex];
208 if (str == null)
209 {
210 str = RenderBlock(blockIndex, _colored);
211 _blockToString[blockIndex] = str;
212 }
213 return str;
214 }
215
216 /// <summary>
217 /// Gets a token at the specified token index.
218 /// </summary>
219 /// <param name="tokenIndex">The token index</param>
220 /// <returns>The token available at the specified index</returns>
221 public AsmToken GetToken(int tokenIndex)
222 {
223 return _tokens[tokenIndex];
224 }
225
226 /// <summary>
227 /// Returns the text representation of the token at the specified index
228 /// </summary>
229 /// <param name="tokenIndex"></param>
230 /// <returns></returns>
231 public StringSlice GetTokenAsTextSlice(int tokenIndex)
232 {
233 return _tokens[tokenIndex].Slice(_input);
234 }
235
236 /// <summary>
237 /// Returns the text representation of the specified token.
238 /// </summary>
239 public StringSlice GetTokenAsTextSlice(AsmToken token)
240 {
241 return token.Slice(_input);
242 }
243
244 /// <summary>
245 /// Returns the text representation of the specified token.
246 /// </summary>
247 public string GetTokenAsText(AsmToken token)
248 {
249 return token.ToString(_input);
250 }
251
252 /// <summary>
253 /// Try and get description of <see cref="instruction"/>.
254 /// </summary>
255 /// <param name="instruction">Instruction to query information about.</param>
256 /// <param name="info">If instruction present the queried information, else default string.</param>
257 /// <returns>Whether instruction was present in burst disassembler core.</returns>
258 internal bool GetInstructionInformation(string instruction, out string info)
259 {
260 switch (_inputAsmKind)
261 {
262 case AsmKind.Intel:
263 return X86AsmInstructionInfo.GetX86InstructionInfo(instruction, out info);
264 case AsmKind.ARM:
265 return ARM64InstructionInfo.GetARM64Info(instruction, out info);
266 case AsmKind.LLVMIR:
267 return LLVMIRInstructionInfo.GetLLVMIRInfo(instruction, out info);
268 case AsmKind.Wasm:
269 return WasmInstructionInfo.GetWasmInfo(instruction, out info);
270 default:
271 throw new InvalidOperationException($"No instruction information for {_inputAsmKind}");
272 }
273 }
274
275 /// <summary>
276 /// Initialize the disassembler with the input and parametesr.
277 /// </summary>
278 /// <param name="input"></param>
279 /// <param name="asmKind"></param>
280 /// <param name="useDarkSkin"></param>
281 /// <param name="useSyntaxColoring"></param>
282 /// <param name="smellTest"></param>
283 /// <returns></returns>
284 public bool Initialize(string input, AsmKind asmKind, bool useDarkSkin = true, bool useSyntaxColoring = true, bool smellTest = false)
285 {
286 try
287 {
288 InitializeImpl(input, asmKind, useDarkSkin, useSyntaxColoring, smellTest);
289 IsInitialized = true;
290 }
291 catch (Exception ex)
292 {
293 Reset();
294#if BURST_INTERNAL
295 throw new InvalidOperationException($"Error while trying to disassemble the input: {ex}");
296#else
297 UnityEngine.Debug.Log($"Error while trying to disassemble the input: {ex}");
298#endif
299 }
300
301 return IsInitialized;
302 }
303
304 /// <summary>
305 /// Helper method to output the full (colored) text as we did before.
306 ///
307 /// This method will be deprecated. Just here for testing during the transition.
308 /// </summary>
309 public string RenderFullText()
310 {
311 // If not initialized correctly (disassembly failed), return the input string as-is
312 if (!IsInitialized) return _input ?? string.Empty;
313
314 var builder = new StringBuilder();
315 for (int i = 0; i < _blocks.Count; i++)
316 {
317 var text = GetOrRenderBlockToText(i);
318 builder.Append(text);
319 }
320 return builder.ToString();
321 }
322
323 private void Reset()
324 {
325 _registersUsedAtLine.Clear();
326 _fileList.Clear();
327 _fileName.Clear();
328 _tokens.Clear();
329 _blocks.Clear();
330 _blockTextIdxs.Clear();
331 _blockToString.Clear();
332 _columnIndices.Clear();
333 _lines.Clear();
334 _tempLabelRefs.Clear();
335 _globalLabels.Clear();
336 _mapBlockIndexToGlobalLabel.Clear();
337 _currentDictLocalLabel = null;
338 IsInitialized = false;
339 }
340
341 private AsmTokenKindProvider _tokenProvider = null;
342
343 private void InitializeImpl(string input, AsmKind asmKind, bool useDarkSkin = true, bool useSyntaxColoring = true, bool smellTest=false)
344 {
345 _commentStart = (asmKind == AsmKind.Intel || asmKind == AsmKind.Wasm) ? '#' : ';';
346 UseSkin(useDarkSkin, smellTest);
347 _colored = useSyntaxColoring;
348 _tokenProvider = InitializeInput(input, asmKind);
349 _registersUsedAtLine.AddTokenProvider(_tokenProvider);
350 ParseAndProcessTokens(_tokenProvider);
351 }
352
353 /// <summary>
354 /// Finds the block index encapsulating <see cref="textIdx"/>.
355 /// </summary>
356 /// <param name="textIdx">Text index relative to <see cref="_input"/>.</param>
357 /// <param name="start">Left-most block index to search within.</param>
358 /// <returns>(block index, blocks start index in <see cref="_input"/>)</returns>
359 public (int idx, int l) GetBlockIdxFromTextIdx(int textIdx)
360 {
361 return GetBlockIdxFromTextIdx(textIdx, 0);
362 }
363
364
365 /// <summary>
366 /// Finds the block index encapsulating <see cref="textIdx"/>.
367 /// </summary>
368 /// <param name="textIdx">Text index relative to <see cref="_input"/>.</param>
369 /// <param name="start">Left-most block index to search within.</param>
370 /// <returns>(block index, blocks start index in <see cref="_input"/>)</returns>
371 public (int idx, int l) GetBlockIdxFromTextIdx(int textIdx, int start)
372 {
373 int end = _blockTextIdxs.Count-1;
374 while (start <= end)
375 {
376 int mid = (end + start) / 2;
377 var (startIdx, endIdx) = _blockTextIdxs[mid];
378
379 if (startIdx <= textIdx && textIdx <= endIdx)
380 {
381 return (mid, startIdx);
382 }
383
384 if (endIdx < textIdx)
385 {
386 start = mid + 1;
387 }
388 else
389 {
390 end = mid - 1;
391 }
392 }
393 return (-1, -1);
394 }
395
396 private bool _smellTest;
397 private void UseSkin(bool useDarkSkin, bool smellTest)
398 {
399 _smellTest = smellTest;
400 if (useDarkSkin)
401 {
402 ColorLineDirective = DarkColorLineDirective;
403 ColorDirective = DarkColorDirective;
404 ColorIdentifier = DarkColorIdentifier;
405 ColorQualifier = DarkColorQualifier;
406 ColorInstruction = DarkColorInstruction;
407 ColorInstructionSIMD = DarkColorInstructionSIMD;
408 ColorInstructionSIMDPacked = DarkColorInstructionSIMDPacked;
409 ColorInstructionSIMDScalar = DarkColorInstructionSIMDScalar;
410 ColorRegister = DarkColorRegister;
411 ColorNumber = DarkColorNumber;
412 ColorString = DarkColorString;
413 ColorComment = DarkColorComment;
414 }
415 else
416 {
417 ColorLineDirective = LightColorLineDirective;
418 ColorDirective = LightColorDirective;
419 ColorIdentifier = LightColorIdentifier;
420 ColorQualifier = LightColorQualifier;
421 ColorInstruction = LightColorInstruction;
422 ColorInstructionSIMD = LightColorInstructionSIMD;
423 ColorInstructionSIMDPacked = LightColorInstructionSIMDPacked;
424 ColorInstructionSIMDScalar = LightColorInstructionSIMDScalar;
425 ColorRegister = LightColorRegister;
426 ColorNumber = LightColorNumber;
427 ColorString = LightColorString;
428 ColorComment = LightColorComment;
429 }
430 }
431
432 private int AlignInstruction(StringBuilder output, int instructionLength, AsmKind asmKind)
433 {
434 // Only support Intel for now
435 if (instructionLength >= InstructionAlignment || asmKind != AsmKind.Intel)
436 return 0;
437
438 int align = InstructionAlignment - instructionLength;
439 output.Append(' ', align);
440 return align;
441 }
442
443 private AsmTokenKindProvider InitializeInput(string input, AsmKind asmKind)
444 {
445 AsmTokenKindProvider asmTokenProvider = null;
446
447 _input = input;
448 _inputAsmKind = asmKind;
449
450 switch (asmKind)
451 {
452 case AsmKind.Intel:
453 asmTokenProvider = (AsmTokenKindProvider)X86AsmTokenKindProvider.Instance;
454 break;
455 case AsmKind.ARM:
456 asmTokenProvider = (AsmTokenKindProvider)ARM64AsmTokenKindProvider.Instance;
457 break;
458 case AsmKind.Wasm:
459 asmTokenProvider = (AsmTokenKindProvider)WasmAsmTokenKindProvider.Instance;
460 break;
461 case AsmKind.LLVMIR:
462 asmTokenProvider = (AsmTokenKindProvider)LLVMIRAsmTokenKindProvider.Instance;
463 break;
464 default:
465 throw new InvalidOperationException($"No {nameof(AsmTokenKindProvider)} for {asmKind}");
466 }
467
468 return asmTokenProvider;
469 }
470
471 private int GetLineLen(in AsmLine line)
472 {
473 int len = 0;
474 int offset = line.TokenIndex;
475 int numLineTokens = line.Length;
476 for (int i = 0; i < numLineTokens; i++)
477 {
478 AsmToken token = _tokens[offset + i];
479 len += token.Kind != AsmTokenKind.NewLine
480 ? token.Length
481 : 1; // We don't use windows line endings, but internal token might,
482 }
483
484 return len;
485 }
486
487 private void ParseAndProcessTokens(AsmTokenKindProvider asmTokenProvider)
488 {
489 Reset();
490
491 var tokenizer = new AsmTokenizer(_input, _inputAsmKind, asmTokenProvider, _commentStart);
492
493 // Adjust token size
494 var pseudoTokenSizeMax = _input.Length / 7;
495 if (pseudoTokenSizeMax > _tokens.Capacity)
496 {
497 _tokens.Capacity = pseudoTokenSizeMax;
498 }
499
500 // Start the top-block as a directive block
501 var block = new AsmBlock { Kind = AsmBlockKind.Block };
502 AsmLine line = default;
503 var blockKindDetectFlags = BlockKindDetectFlags.None;
504
505 // Skip first line
506 // Don't tokenize the first line that contains e.g:
507 // While compiling job: System.Single BurstJobTester/MyJob::CheckFmaSlow(System.Single,System.Single,System.Single)
508 while (tokenizer.TryGetNextToken(out var token))
509 {
510 if (token.Kind == AsmTokenKind.NewLine)
511 {
512 break;
513 }
514 }
515
516 // Read all tokens
517 // Create blocks and lines on the fly, record functions
518 int totalIdx = 0;
519 int blockStartIdx = 0;
520 bool newLine = false;
521 var (possiblyRemoveAlignment, addedAlignment) = (false, 0);
522 while (tokenizer.TryGetNextToken(out var token))
523 {
524 var tokenIndex = _tokens.Count;
525 _tokens.Add(token);
526
527 if (newLine)
528 {
529 if (possiblyRemoveAlignment)
530 {
531 // Alignment was added just before a newline
532 totalIdx -= addedAlignment;
533 }
534
535 // Push new line
536 if (line.Kind == AsmLineKind.SourceFile)
537 {
538 // Have to remove the line from totalIdx, for proper block idx saving.
539 totalIdx -= GetLineLen(line);
540 ProcessSourceFile(ref line);
541 // We drop this line, we don't store SourceFile line as-is but just below as SourceFileLocation
542 }
543 else
544 {
545 var lineRef = new AsmLineRef(_blocks.Count, block.Length);
546 if (line.Kind == AsmLineKind.SourceLocation)
547 {
548 // Have to remove the line from totalIdx, for proper block idx saving.
549 totalIdx -= GetLineLen(line);
550 ProcessSourceLocation(ref line, ref totalIdx);
551 // after this, the line is now a SourceFileLocation
552 }
553 else if (line.Kind == AsmLineKind.LabelDeclaration)
554 {
555 // Record labels (global and locals)
556 ProcessLabelDeclaration(lineRef, line);
557 }
558 else if (line.Kind == AsmLineKind.CodeBranch || line.Kind == AsmLineKind.CodeJump)
559 {
560 // Record temp branch/jumps
561 ProcessJumpOrBranch(lineRef, ref line);
562 }
563
564 _lines.Add(line);
565 _registersUsedAtLine.PushLine();
566 block.Length++;
567 }
568
569 bool previousLineWasBranch = line.Kind == AsmLineKind.CodeBranch;
570
571 // Reset the line
572 line = default;
573 line.Kind = AsmLineKind.Empty;
574 line.TokenIndex = tokenIndex;
575 // We create a new block when hitting a label declaration
576 // If the previous line was a conditional branch, it is like having an implicit label
577 if (previousLineWasBranch || token.Kind == AsmTokenKind.Label)
578 {
579 // Refine the kind of block before pushing it
580 if ((blockKindDetectFlags & BlockKindDetectFlags.Code) != 0)
581 {
582 block.Kind = AsmBlockKind.Code;
583 }
584 else if ((blockKindDetectFlags & BlockKindDetectFlags.Data) != 0)
585 {
586 block.Kind = AsmBlockKind.Data;
587 }
588 else if ((blockKindDetectFlags & BlockKindDetectFlags.Directive) != 0)
589 {
590 block.Kind = AsmBlockKind.Directive;
591 }
592
593 // Push the current block
594 _blocks.Add(block);
595 _blockTextIdxs.Add((blockStartIdx, totalIdx-1));
596 _blockToString.Add(null);
597
598 // Create a new block
599 blockStartIdx = totalIdx;
600 block = new AsmBlock
601 {
602 Kind = AsmBlockKind.None,
603 LineIndex = _lines.Count,
604 Length = 0
605 };
606 blockKindDetectFlags = BlockKindDetectFlags.None;
607 }
608 }
609
610 // If the current line is still undefined try to detect what kind of line we have
611 var lineKind = line.Kind;
612 if (lineKind == AsmLineKind.Empty)
613 {
614 switch (token.Kind)
615 {
616 case AsmTokenKind.Directive:
617 lineKind = AsmLineKind.Directive;
618 blockKindDetectFlags |= BlockKindDetectFlags.Directive;
619 break;
620 case AsmTokenKind.SourceFile:
621 lineKind = AsmLineKind.SourceFile;
622 break;
623 case AsmTokenKind.SourceLocation:
624 lineKind = AsmLineKind.SourceLocation;
625 blockKindDetectFlags |= BlockKindDetectFlags.Code;
626 break;
627 case AsmTokenKind.DataDirective:
628 lineKind = AsmLineKind.Data;
629 blockKindDetectFlags |= BlockKindDetectFlags.Data;
630 break;
631 case AsmTokenKind.Instruction:
632 case AsmTokenKind.InstructionSIMD:
633 lineKind = AsmLineKind.Code;
634 blockKindDetectFlags |= BlockKindDetectFlags.Code;
635 break;
636 case AsmTokenKind.BranchInstruction:
637 lineKind = AsmLineKind.CodeBranch;
638 blockKindDetectFlags |= BlockKindDetectFlags.Code;
639 break;
640 case AsmTokenKind.JumpInstruction:
641 lineKind = AsmLineKind.CodeJump;
642 blockKindDetectFlags |= BlockKindDetectFlags.Code;
643 break;
644 case AsmTokenKind.CallInstruction:
645 lineKind = AsmLineKind.CodeCall;
646 blockKindDetectFlags |= BlockKindDetectFlags.Code;
647 break;
648 case AsmTokenKind.ReturnInstruction:
649 lineKind = AsmLineKind.CodeReturn;
650 blockKindDetectFlags |= BlockKindDetectFlags.Code;
651 break;
652 case AsmTokenKind.Label:
653 lineKind = newLine ? AsmLineKind.LabelDeclaration : AsmLineKind.Empty;
654 break;
655 case AsmTokenKind.Comment:
656 lineKind = AsmLineKind.Comment;
657 break;
658 case AsmTokenKind.FunctionBegin:
659 lineKind = AsmLineKind.FunctionBegin;
660 break;
661 case AsmTokenKind.FunctionEnd:
662 lineKind = AsmLineKind.FunctionEnd;
663 break;
664 }
665 line.Kind = lineKind;
666 }
667
668 // Add alignment for it to match the output BurstDisassembler gives to the outside world
669 switch (token.Kind)
670 {
671 case AsmTokenKind.Instruction:
672 case AsmTokenKind.CallInstruction:
673 case AsmTokenKind.BranchInstruction:
674 case AsmTokenKind.JumpInstruction:
675 case AsmTokenKind.ReturnInstruction:
676 case AsmTokenKind.InstructionSIMD:
677 if (!(token.Length >= InstructionAlignment || _inputAsmKind != AsmKind.Intel))
678 {
679 totalIdx += (InstructionAlignment - token.Length);
680 possiblyRemoveAlignment = true;
681 addedAlignment = InstructionAlignment - token.Length;
682 }
683 break;
684 // If new line is hit do not set to false, as to carry the information
685 // into the next iteration.
686 case AsmTokenKind.NewLine:
687 break;
688 default:
689 possiblyRemoveAlignment = false;
690 break;
691 }
692
693 // Add used registers to the index appropriate for specific line.
694 if (token.Kind == AsmTokenKind.Register)
695 {
696 _registersUsedAtLine.Add(_lines.Count, GetTokenAsText(token));
697 }
698
699 line.Length++;
700 newLine = token.Kind == AsmTokenKind.NewLine;
701 totalIdx += newLine ? 1 : token.Length;
702 }
703
704 // Process the remaining line
705 if (line.Length > 0)
706 {
707 _lines.Add(line);
708 block.Length++;
709
710 _registersUsedAtLine.PushLine();
711 }
712
713 if (block.Length > 0)
714 {
715 _blocks.Add(block);
716 _blockTextIdxs.Add((blockStartIdx, totalIdx - 1));
717 _blockToString.Add(null);
718 }
719
720 ProcessLabelsAndCreateEdges();
721 }
722
723 private void ProcessLabelDeclaration(in AsmLineRef lineRef, in AsmLine line)
724 {
725 var iterator = GetIterator(line);
726
727 iterator.TryGetNext(out var token); // label
728 var text = token.Slice(_input);
729 if (IsLabelLocal(text))
730 {
731 // if ´_currentDictLocalLabel==null´ we just hit a local label prior to any global labels.
732 // So we simply create a empty global label, to hold this local:
733 if (_currentDictLocalLabel is null)
734 {
735 _currentDictLocalLabel = _globalLabels.GetOrCreate(new StringSlice(""), lineRef);
736 _mapBlockIndexToGlobalLabel[lineRef.BlockIndex] = text;
737 }
738
739 // Record local labels to the current global label dictionary
740 _currentDictLocalLabel.Add(text, lineRef);
741 }
742 else
743 {
744 // Create a local label dictionary per global label
745 _currentDictLocalLabel = _globalLabels.GetOrCreate(text, lineRef);
746 // Associate the current block index to this global index
747 _mapBlockIndexToGlobalLabel[lineRef.BlockIndex] = text;
748 }
749 }
750
751 private void ProcessJumpOrBranch(in AsmLineRef lineRef, ref AsmLine line)
752 {
753 var iterator = GetIterator(line);
754 iterator.TryGetNext(out _); // branch/jump instruction
755
756 if (iterator.TryGetNext(out var label, out var labelTokenIndex))
757 {
758 if (label.Kind == AsmTokenKind.String || label.Kind == AsmTokenKind.Identifier || label.Kind == AsmTokenKind.Label)
759 {
760 // In case the token is not a label, convert it to a label after this
761 if (label.Kind != AsmTokenKind.Label)
762 {
763 var token = _tokens[labelTokenIndex];
764 token = new AsmToken(AsmTokenKind.Label, token.Position, token.AlignedPosition, token.Length);
765 _tokens[labelTokenIndex] = token;
766 }
767
768 var currentGlobalBlockIndex = _currentDictLocalLabel.GlobalLabelLineRef.BlockIndex;
769 _tempLabelRefs.Add(new TempLabelRef(currentGlobalBlockIndex, lineRef, label.Position, label.Length));
770 }
771 }
772 }
773
774 private void ProcessSourceFile(ref AsmLine line)
775 {
776 var it = GetIterator(line);
777
778 it.TryGetNext(out _); // skip .file or .cv_file
779
780 int index = 0;
781 if (it.TryGetNext(out var token) && token.Kind == AsmTokenKind.Number)
782 {
783 var numberAsStr = GetTokenAsText(token);
784 index = int.Parse(numberAsStr);
785 }
786
787 if (it.TryGetNext(out token) && token.Kind == AsmTokenKind.String)
788 {
789 var filename = GetTokenAsText(token).Trim('"').Replace('\\', '/');
790 string[] fileLines = null;
791
792 //blockIdx += 4 + System.IO.Path.GetFileName(filename).Length;// ("=== " + filename).Length
793 try
794 {
795 if (System.IO.File.Exists(filename))
796 {
797 fileLines = System.IO.File.ReadAllLines(filename);
798 }
799 }
800 catch
801 {
802 fileLines = null;
803 }
804
805
806 _fileName.Add(index, filename);
807 _fileList.Add(index, fileLines);
808 }
809 }
810
811 private void ProcessSourceLocation(ref AsmLine line, ref int blockIdx)
812 {
813 var it = GetIterator(line);
814
815 // .loc {fileno} {lineno} [column] [options] -
816 // .cv_loc funcid fileno lineno [column]
817 int fileno = 0;
818 int colno = 0;
819 int lineno = 0; // NB 0 indicates no information given
820
821 if (it.TryGetNext(out var token))
822 {
823 var tokenSlice = GetTokenAsTextSlice(token);
824 if (tokenSlice == CVLocDirective)
825 {
826 // skip funcId
827 it.TryGetNext(out token);
828 }
829 }
830
831 if (it.TryGetNext(out token) && token.Kind == AsmTokenKind.Number)
832 {
833 var numberAsStr = GetTokenAsText(token);
834 fileno = int.Parse(numberAsStr);
835 }
836
837 if (it.TryGetNext(out token) && token.Kind == AsmTokenKind.Number)
838 {
839 var numberAsStr = GetTokenAsText(token);
840 lineno = int.Parse(numberAsStr);
841 }
842
843 if (it.TryGetNext(out token) && token.Kind == AsmTokenKind.Number)
844 {
845 var numberAsStr = GetTokenAsText(token);
846 colno = int.Parse(numberAsStr);
847 }
848
849 // Transform the SourceLocation into a SourceFileLocation
850 line.Kind = AsmLineKind.SourceFileLocation;
851 line.SourceFileNumber = fileno;
852 line.SourceLineNumber = lineno;
853 line.SourceColumnNumber = colno;
854
855 // Make sure blockTextIdxs are correct
856 if (fileno == 0) return;
857 blockIdx += 2 + System.IO.Path.GetFileName(_fileName[fileno]).Length; // ("; " + filename).length
858
859 if (lineno != 0)
860 {
861 blockIdx += 4 + lineno.ToString().Length + (colno + 1).ToString().Length;// "(x, y)"
862
863 if (_fileList.ContainsKey(fileno) && _fileList[fileno] != null && lineno - 1 < _fileList[fileno].Length)
864 {
865 blockIdx += _fileList[fileno][lineno - 1].Length;
866 }
867 }
868 blockIdx++; // \n
869 }
870
871 private static bool IsLabelLocal(in StringSlice slice)
872 {
873 return slice.StartsWith(".L");
874 }
875
876 private void ProcessLabelsAndCreateEdges()
877 {
878 foreach (var tempLabelRef in _tempLabelRefs)
879 {
880 var globalBlockIndex = tempLabelRef.GlobalBlockIndex;
881
882 // Source Block + Line
883 var srcRef = tempLabelRef.LineRef;
884 var srcBlockIndex = srcRef.BlockIndex;
885 var srcLineIndex = srcRef.LineIndex;
886 var srcBlock = _blocks[srcBlockIndex];
887 // Line where the edge occurs
888 var srcLine = _lines[srcBlock.LineIndex + srcLineIndex];
889
890 var label = new StringSlice(_input, tempLabelRef.StringIndex, tempLabelRef.StringLength);
891 var isLocal = IsLabelLocal(label);
892 AsmLineRef destRef;
893 if (isLocal)
894 {
895 var globalLabel = _mapBlockIndexToGlobalLabel[globalBlockIndex];
896 var localLabel = _globalLabels[globalLabel];
897 destRef = localLabel[label];
898 }
899 else
900 {
901 if (_globalLabels.TryGetValue(label, out var entry))
902 {
903 destRef = entry.GlobalLabelLineRef;
904 }
905 else
906 {
907 continue; // Some global labels (at least on arm) e.g. __divsi3 are runtime library defined and not present at all in the source
908 }
909 }
910
911 // Destination Block + Line
912 var dstBlock = _blocks[destRef.BlockIndex];
913
914 // Create edges
915 srcBlock.AddEdge(new AsmEdge(AsmEdgeKind.OutBound, srcRef, destRef));
916 dstBlock.AddEdge(new AsmEdge(AsmEdgeKind.InBound, destRef, srcRef));
917
918 // For conditional branches, add the false branch as well
919 // TODO: should we comment that in the meantime or?
920 if (srcLine.Kind == AsmLineKind.CodeBranch)
921 {
922 // The implicit destination block for the false branch is the next block of the source
923 // TODO: we pickup the line 0, while we might want to select the first code of line or first Label declaration
924 var blockFalseRef = new AsmLineRef(srcRef.BlockIndex + 1, 0);
925 dstBlock = _blocks[blockFalseRef.BlockIndex];
926
927 srcBlock.AddEdge(new AsmEdge(AsmEdgeKind.OutBound, srcRef, blockFalseRef));
928 dstBlock.AddEdge(new AsmEdge(AsmEdgeKind.InBound, blockFalseRef, srcRef));
929 }
930 }
931
932 // Sort all edges
933 foreach (var block in Blocks)
934 {
935 block.SortEdges();
936 }
937 }
938
939 private List<(int startIdx, int endIdx)> _blockTextIdxs = new List<(int startIdx, int endIdx)>(128);
940
941 public List<(int startIdx, int endIdx)> BlockIdxs => _blockTextIdxs;
942
943
944 private string RenderBlock(int blockIndex, bool colored)
945 {
946 var block = _blocks[blockIndex];
947 _output.Clear();
948 var lineStart = block.LineIndex;
949 var length = block.Length;
950 for (int i = 0; i < length; i++)
951 {
952 var line = _lines[lineStart + i];
953 RenderLine(ref line, colored);
954 // write back the line that has been modified. But only if we run with the same color mode,
955 // that the disassembler was initialized with.
956 if (colored == _colored) _lines[lineStart + i] = line;
957 }
958
959 var str = _output.ToString();
960 _output.Length = 0;
961 return str;
962 }
963
964 internal void RenderLine(ref AsmLine line, bool colored)
965 {
966 // Render this line with a specific renderer
967 if (line.Kind == AsmLineKind.SourceFileLocation)
968 {
969 RenderSourceFileLocation(ref line, colored);
970 return;
971 }
972
973 // Process all tokens
974 var length = line.Length;
975 int column = 0;
976 for (int i = 0; i < length; i++)
977 {
978 var token = _tokens[line.TokenIndex + i];
979 var slice = token.Slice(_input);
980
981 // We don't record the first column because it is always 0
982 if (column > 0)
983 {
984 if (line.ColumnIndex == 0)
985 {
986 line.ColumnIndex = _columnIndices.Count;
987 }
988 _columnIndices.Add(column);
989 }
990
991 if (colored)
992 {
993 switch (token.Kind)
994 {
995 case AsmTokenKind.DataDirective:
996 case AsmTokenKind.Directive:
997 case AsmTokenKind.FunctionBegin:
998 case AsmTokenKind.FunctionEnd:
999 _output.Append("<color=").Append(ColorDirective).Append('>');
1000 _output.Append(_input, slice.Position, slice.Length);
1001 column += slice.Length;
1002 _output.Append("</color>");
1003 break;
1004 case AsmTokenKind.Label:
1005 case AsmTokenKind.Identifier:
1006 _output.Append("<color=").Append(ColorIdentifier).Append('>');
1007 _output.Append(_input, slice.Position, slice.Length);
1008 column += slice.Length;
1009 _output.Append("</color>");
1010 break;
1011 case AsmTokenKind.Qualifier:
1012 _output.Append("<color=").Append(ColorQualifier).Append('>');
1013 _output.Append(_input, slice.Position, slice.Length);
1014 column += slice.Length;
1015 _output.Append("</color>");
1016 break;
1017 case AsmTokenKind.Instruction:
1018 case AsmTokenKind.CallInstruction:
1019 case AsmTokenKind.BranchInstruction:
1020 case AsmTokenKind.JumpInstruction:
1021 case AsmTokenKind.ReturnInstruction:
1022 _output.Append("<color=").Append(ColorInstruction).Append('>');
1023 _output.Append(_input, slice.Position, slice.Length);
1024 column += slice.Length;
1025 _output.Append("</color>");
1026 if (i == length - 2) // last slice always a newline
1027 break;
1028 column += AlignInstruction(_output, slice.Length, _inputAsmKind);
1029 break;
1030 case AsmTokenKind.InstructionSIMD:
1031 // Perform smell test for simd instructions:
1032 var col = ColorInstructionSIMD;
1033 if (_smellTest)
1034 {
1035 switch (_tokenProvider.SimdKind(slice))
1036 {
1037 case SIMDkind.Packed:
1038 col = ColorInstructionSIMDPacked;
1039 break;
1040 case SIMDkind.Scalar:
1041 col = ColorInstructionSIMDScalar;
1042 break;
1043 case SIMDkind.Infrastructure:
1044 break;
1045 }
1046 }
1047
1048 _output.Append("<color=").Append(col).Append('>');
1049 _output.Append(_input, slice.Position, slice.Length);
1050 column += slice.Length;
1051 _output.Append("</color>");
1052 if (i == length - 2) // last slice always newline
1053 break;
1054 column += AlignInstruction(_output, slice.Length, _inputAsmKind);
1055 break;
1056 case AsmTokenKind.Register:
1057 _output.Append("<color=").Append(ColorRegister).Append('>');
1058 _output.Append(_input, slice.Position, slice.Length);
1059 column += slice.Length;
1060 _output.Append("</color>");
1061 break;
1062 case AsmTokenKind.Number:
1063 _output.Append("<color=").Append(ColorNumber).Append('>');
1064 _output.Append(_input, slice.Position, slice.Length);
1065 column += slice.Length;
1066 _output.Append("</color>");
1067 break;
1068 case AsmTokenKind.String:
1069 _output.Append("<color=").Append(ColorString).Append('>');
1070 _output.Append(_input, slice.Position, slice.Length);
1071 column += slice.Length;
1072 _output.Append("</color>");
1073 break;
1074 case AsmTokenKind.Comment:
1075 _output.Append("<color=").Append(ColorComment).Append('>');
1076 _output.Append(_input, slice.Position, slice.Length);
1077 column += slice.Length;
1078 _output.Append("</color>");
1079 break;
1080 case AsmTokenKind.NewLine:
1081 _output.Append('\n');
1082 break;
1083 default:
1084 _output.Append(_input, slice.Position, slice.Length);
1085 column += slice.Length;
1086 break;
1087 }
1088 }
1089 else
1090 {
1091 if (token.Kind == AsmTokenKind.NewLine)
1092 {
1093 _output.Append('\n');
1094 }
1095 else
1096 {
1097 _output.Append(_input, slice.Position, slice.Length);
1098 column += slice.Length;
1099 }
1100
1101 // Also wants to align instructions in uncolored mode the same way as colored.
1102 switch (token.Kind)
1103 {
1104 case AsmTokenKind.Instruction:
1105 case AsmTokenKind.CallInstruction:
1106 case AsmTokenKind.BranchInstruction:
1107 case AsmTokenKind.JumpInstruction:
1108 case AsmTokenKind.ReturnInstruction:
1109 case AsmTokenKind.InstructionSIMD:
1110 // Do not add alignment to instruction with no arguments
1111 // last slice always a newline
1112 if (i == length - 2) break;
1113 column += AlignInstruction(_output, slice.Length, _inputAsmKind);
1114 break;
1115 }
1116 }
1117 }
1118 }
1119
1120 private void RenderSourceFileLocation(ref AsmLine line, bool colored)
1121 {
1122 char[] comment = {_commentStart, ' '};
1123 var fileno = line.SourceFileNumber;
1124 var lineno = line.SourceLineNumber;
1125 var colno = line.SourceColumnNumber;
1126
1127 // If the file number is 0, skip the line
1128 if (fileno == 0)
1129 {
1130 }
1131 // If the line number is 0, then we can update the file tracking, but still not output a line
1132 else if (lineno == 0)
1133 {
1134 if (colored) _output.Append("<color=").Append(ColorLineDirective).Append('>');
1135 _output.Append(comment).Append(System.IO.Path.GetFileName(_fileName[fileno]));
1136 if (colored) _output.Append("</color>");
1137 }
1138 // We have a source line and number -- can we load file and extract this line?
1139 else
1140 {
1141 if (_fileList.ContainsKey(fileno) && _fileList[fileno] != null && lineno - 1 < _fileList[fileno].Length)
1142 {
1143 if (colored) _output.Append("<color=").Append(ColorLineDirective).Append('>');
1144 _output.Append(comment).Append(System.IO.Path.GetFileName(_fileName[fileno])).Append('(').Append(lineno).Append(", ").Append(colno + 1).Append(')').Append(_fileList[fileno][lineno - 1]);
1145 if (colored) _output.Append("</color>");
1146 }
1147 else
1148 {
1149 if (colored) _output.Append("<color=").Append(ColorLineDirective).Append('>');
1150 _output.Append(comment).Append(System.IO.Path.GetFileName(_fileName[fileno])).Append('(').Append(lineno).Append(", ").Append(colno + 1).Append(')');
1151 if (colored) _output.Append("</color>");
1152 }
1153 }
1154 _output.Append('\n');
1155 }
1156 private AsmTokenIterator GetIterator(in AsmLine line)
1157 {
1158 return new AsmTokenIterator(_tokens, line.TokenIndex, line.Length);
1159 }
1160
1161 public enum AsmKind
1162 {
1163 Intel,
1164 ARM,
1165 Wasm,
1166 LLVMIR
1167 }
1168
1169 [Flags]
1170 enum BlockKindDetectFlags
1171 {
1172 None = 0,
1173 Code = 1 << 0,
1174 Data = 1 << 1,
1175 Directive = 1 << 2,
1176 }
1177
1178 public enum AsmBlockKind
1179 {
1180 None,
1181 Block,
1182 Directive,
1183 Code,
1184 Data
1185 }
1186
1187 [DebuggerDisplay("Block {Kind} LineIndex = {LineIndex} Length = {Length}")]
1188 public class AsmBlock
1189 {
1190 public AsmBlockKind Kind;
1191
1192 public int LineIndex;
1193
1194 public int Length;
1195
1196 // Edges attached to this block, might be null if no edges
1197 public List<AsmEdge> Edges;
1198
1199 public void AddEdge(in AsmEdge edge)
1200 {
1201 var edges = Edges;
1202 if (edges == null)
1203 {
1204 edges = new List<AsmEdge>();
1205 Edges = edges;
1206 }
1207 edges.Add(edge);
1208 }
1209
1210 /// <summary>
1211 /// Sort edges by in-bound first, block index, line index
1212 /// </summary>
1213 public void SortEdges()
1214 {
1215 var edges = Edges;
1216 if (edges == null) return;
1217 edges.Sort(EdgeComparer.Instance);
1218 }
1219
1220 private class EdgeComparer : IComparer<AsmEdge>
1221 {
1222 public static readonly EdgeComparer Instance = new EdgeComparer();
1223
1224 public int Compare(AsmEdge x, AsmEdge y)
1225 {
1226 // Order by kind first (InBound first, outbound first)
1227 if (x.Kind != y.Kind)
1228 {
1229 return x.Kind == AsmEdgeKind.InBound ? -1 : 1;
1230 }
1231
1232 // Order by Block Index
1233 if (x.LineRef.BlockIndex != y.LineRef.BlockIndex) return x.LineRef.BlockIndex.CompareTo(y.LineRef.BlockIndex);
1234
1235 // Then order by Line Index
1236 return x.LineRef.LineIndex.CompareTo(y.LineRef.LineIndex);
1237 }
1238 }
1239 }
1240
1241 public enum AsmLineKind
1242 {
1243 Empty = 0,
1244 Comment,
1245 Directive,
1246 SourceFile,
1247 SourceLocation,
1248 SourceFileLocation, // computed line
1249 FunctionBegin,
1250 FunctionEnd,
1251 LabelDeclaration,
1252 Code,
1253 CodeCall,
1254 CodeBranch,
1255 CodeJump,
1256 CodeReturn,
1257 Data,
1258 }
1259
1260 /// <summary>
1261 /// An <see cref="AsmToken"/> iterator skipping spaces.
1262 /// </summary>
1263 struct AsmTokenIterator
1264 {
1265 private readonly List<AsmToken> _tokens;
1266 private readonly int _startIndex;
1267 private readonly int _endIndex;
1268 private int _index;
1269
1270 public AsmTokenIterator(List<AsmToken> tokens, int index, int length)
1271 {
1272 if (tokens == null) throw new ArgumentNullException(nameof(tokens));
1273 _tokens = tokens;
1274 if (index < 0 || index >= tokens.Count) throw new ArgumentOutOfRangeException(nameof(index), $"Invalid index {index}. Must be >= 0 and < {tokens.Count}");
1275 if (length < 0) throw new ArgumentOutOfRangeException(nameof(length), $"Invalid length {length}. Must be >=0");
1276 _startIndex = index;
1277 _endIndex = index + length - 1;
1278 if (_endIndex >= tokens.Count) throw new ArgumentOutOfRangeException(nameof(length), $"Invalid length {length}. The final index {_endIndex} cannot be >= {tokens.Count}");
1279 _index = index;
1280 }
1281
1282 public void Reset()
1283 {
1284 _index = _startIndex;
1285 }
1286
1287 public bool TryGetNext(out AsmToken token)
1288 {
1289 while (_index <= _endIndex)
1290 {
1291 var nextToken = _tokens[_index++];
1292 if (nextToken.Kind == AsmTokenKind.Misc) continue;
1293 token = nextToken;
1294 return true;
1295 }
1296
1297 token = default;
1298 return false;
1299 }
1300
1301 public bool TryGetNext(out AsmToken token, out int tokenIndex)
1302 {
1303 while (_index <= _endIndex)
1304 {
1305 tokenIndex = _index;
1306 var nextToken = _tokens[_index++];
1307 if (nextToken.Kind == AsmTokenKind.Misc) continue;
1308 token = nextToken;
1309 return true;
1310 }
1311
1312 tokenIndex = -1;
1313 token = default;
1314 return false;
1315 }
1316 }
1317
1318 [DebuggerDisplay("{ToDebuggerDisplay(),nq}")]
1319 [StructLayout(LayoutKind.Explicit)]
1320 public struct AsmLine
1321 {
1322 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1323 // CAUTION: It is important to not put *any managed objects*
1324 // into this struct for GC efficiency
1325 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1326
1327 [FieldOffset(0)] public AsmLineKind Kind;
1328
1329 [FieldOffset(4)] public int TokenIndex;
1330
1331 // only valid when Kind == SourceFileLocation
1332 [FieldOffset(4)] public int SourceFileNumber;
1333
1334 [FieldOffset(8)] public int Length;
1335
1336 // only valid when Kind == SourceFileLocation
1337 [FieldOffset(8)] public int SourceLineNumber;
1338
1339 // only valid when Kind == SourceFileLocation
1340 [FieldOffset(12)] public int SourceColumnNumber;
1341
1342 /// <summary>
1343 /// Index into <see cref="_columnIndices"/>, the column indices will then contain <see cref="Length"/> minus 1 of column ints,
1344 /// each column corresponding the horizontal offset to a token.
1345 /// The first column is always 0 for the first token, hence the minus 1.
1346 /// Only get filled when asking for the text for a block.
1347 /// </summary>
1348 [FieldOffset(16)] public int ColumnIndex;
1349
1350 private string ToDebuggerDisplay()
1351 {
1352 if (Kind == AsmLineKind.SourceFileLocation)
1353 {
1354 return $"Line {Kind} File={SourceFileNumber} Line={SourceLineNumber} Column={SourceColumnNumber}";
1355 }
1356 else
1357 {
1358 return $"Line {Kind} TokenIndex={TokenIndex} Length={Length} ColumnIndex={ColumnIndex}";
1359 }
1360 }
1361 }
1362
1363
1364 public enum AsmEdgeKind
1365 {
1366 InBound,
1367 OutBound,
1368 }
1369
1370 /// <summary>
1371 /// An inbound or outbound connection for a block to another block+line
1372 /// </summary>
1373 [DebuggerDisplay("Edge {Kind} Origin: {OriginRef} LineRef: {LineRef}")]
1374 public struct AsmEdge : IEquatable<AsmEdge>
1375 {
1376 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1377 // CAUTION: It is important to not put *any managed objects*
1378 // into this struct for GC efficiency
1379 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1380
1381 public AsmEdge(AsmEdgeKind kind, AsmLineRef originRef, AsmLineRef lineRef)
1382 {
1383 Kind = kind;
1384 OriginRef = originRef;
1385 LineRef = lineRef;
1386 }
1387
1388
1389 public AsmEdgeKind Kind;
1390
1391 public AsmLineRef OriginRef;
1392
1393 public AsmLineRef LineRef;
1394
1395 public override string ToString()
1396 {
1397 return Kind == AsmEdgeKind.InBound ?
1398 $"Edge {Kind} {LineRef} => {OriginRef}"
1399 : $"Edge {Kind} {OriginRef} => {LineRef}";
1400 }
1401
1402 public bool Equals(AsmEdge obj) => Kind == obj.Kind && OriginRef.Equals(obj.OriginRef) && LineRef.Equals(obj.LineRef);
1403
1404 public override bool Equals(object obj) => obj is AsmEdge other && Equals(other);
1405
1406 public override int GetHashCode() => base.GetHashCode();
1407 }
1408
1409 public readonly struct AsmLineRef: IEquatable<AsmLineRef>
1410 {
1411 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1412 // CAUTION: It is important to not put *any managed objects*
1413 // into this struct for GC efficiency
1414 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1415
1416
1417 public AsmLineRef(int blockIndex, int lineIndex)
1418 {
1419 BlockIndex = blockIndex;
1420 LineIndex = lineIndex;
1421 }
1422
1423 public readonly int BlockIndex;
1424
1425 public readonly int LineIndex;
1426
1427 public override string ToString()
1428 {
1429 return $"Block: {BlockIndex}, Line: {LineIndex}";
1430 }
1431
1432 public bool Equals(AsmLineRef obj) => BlockIndex == obj.BlockIndex && LineIndex == obj.LineIndex;
1433
1434 public override bool Equals(object obj) => obj is AsmLineRef other && Equals(other);
1435
1436 public override int GetHashCode() => base.GetHashCode();
1437 }
1438
1439 /// <summary>
1440 /// Structure used to store all label references before they are getting fully resolved
1441 /// </summary>
1442 [DebuggerDisplay("TempLabelRef {LineRef} - String {StringIndex}, {StringLength}")]
1443 private readonly struct TempLabelRef
1444 {
1445 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1446 // CAUTION: It is important to not put *any managed objects*
1447 // into this struct for GC efficiency
1448 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1449
1450 public TempLabelRef(int globalBlockIndex, AsmLineRef lineRef, int stringIndex, int stringLength)
1451 {
1452 GlobalBlockIndex = globalBlockIndex;
1453 LineRef = lineRef;
1454 StringIndex = stringIndex;
1455 StringLength = stringLength;
1456 }
1457
1458 public readonly int GlobalBlockIndex;
1459
1460 public readonly AsmLineRef LineRef;
1461
1462 public readonly int StringIndex;
1463
1464 public readonly int StringLength;
1465 }
1466
1467 private class DictionaryLocalLabel : Dictionary<StringSlice, AsmLineRef>
1468 {
1469 public DictionaryLocalLabel()
1470 {
1471 }
1472
1473 public DictionaryLocalLabel(int capacity) : base(capacity)
1474 {
1475 }
1476
1477 public AsmLineRef GlobalLabelLineRef;
1478 }
1479
1480 private class DictionaryGlobalLabel : Dictionary<StringSlice, DictionaryLocalLabel>
1481 {
1482 public DictionaryGlobalLabel()
1483 {
1484 }
1485
1486 public DictionaryGlobalLabel(int capacity) : base(capacity)
1487 {
1488 }
1489
1490 public DictionaryLocalLabel GetOrCreate(StringSlice label, AsmLineRef globalLineRef)
1491 {
1492 if (!TryGetValue(label, out var dictLabel))
1493 {
1494 dictLabel = new DictionaryLocalLabel();
1495 Add(label, dictLabel);
1496 }
1497 dictLabel.GlobalLabelLineRef = globalLineRef;
1498 return dictLabel;
1499 }
1500 }
1501
1502 internal struct UsedRegisters
1503 {
1504 private AsmTokenKindProvider _tokenProvider;
1505
1506 /// <summary>
1507 /// Dictionary<lineNr, List<reg>>
1508 /// </summary>
1509 internal readonly Dictionary<int, List<string>> _linesRegisters;
1510
1511 private readonly List<string> _tmp;
1512 private int _currentLineIdx;
1513
1514 public UsedRegisters(int count)
1515 {
1516 _linesRegisters = new Dictionary<int, List<string>>(count);
1517 _tmp = new List<string>(2);
1518 _currentLineIdx = -1;
1519 _tokenProvider = null;
1520 }
1521
1522 public void AddTokenProvider(AsmTokenKindProvider provider)
1523 {
1524 _tokenProvider = provider;
1525 }
1526
1527 private int NumberOfOcurences(List<string> regs, string target)
1528 {
1529 var count = 0;
1530 foreach (var elm in regs)
1531 {
1532 if (_tokenProvider.RegisterEqual(elm, target))
1533 {
1534 count++;
1535 }
1536 }
1537 return count;
1538 }
1539
1540 public int RegisterMatch(int lineIdx, string reg)
1541 {
1542 return LineContainsRegs(lineIdx, out var actualRegs)
1543 ? NumberOfOcurences(actualRegs, reg)
1544 : 0;
1545 }
1546
1547 public bool RegisterEquality(string regA, string regB) => _tokenProvider.RegisterEqual(regA, regB);
1548
1549 public List<string> CleanRegs(List<string> regs)
1550 {
1551 var tmpTokenProvider = _tokenProvider;
1552 var retVal = new List<string>(regs.Count);
1553
1554 foreach (var reg in regs)
1555 {
1556 if (!retVal.Exists(elm => tmpTokenProvider.RegisterEqual(reg, elm)))
1557 {
1558 retVal.Add(reg);
1559 }
1560 }
1561 return retVal;
1562 }
1563
1564 public bool LineContainsRegs(int lineIdx, out List<string> value)
1565 {
1566 return _linesRegisters.TryGetValue(lineIdx, out value);
1567 }
1568
1569 public void Add(int lineIdx, string reg)
1570 {
1571 _currentLineIdx = lineIdx;
1572 _tmp.Add(reg);
1573 }
1574
1575 public void PushLine()
1576 {
1577 if (_currentLineIdx == -1)
1578 {
1579 // We haven't actually tried to add anything.
1580 return;
1581 }
1582 _linesRegisters[_currentLineIdx] = new List<string>(_tmp);
1583 _tmp.Clear();
1584 _currentLineIdx = -1;
1585 }
1586
1587 public int Count => _linesRegisters.Count;
1588
1589 public void Clear()
1590 {
1591 _linesRegisters.Clear();
1592 _tmp.Clear();
1593 }
1594 }
1595 }
1596}
1597#endif