• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

SamboyCoding / Cpp2IL / 30857607998

03 Aug 2026 10:09PM UTC coverage: 32.946% (-1.4%) from 34.341%
30857607998

push

github

SamboyCoding
Decompiler: Handle arrays of structs, struct return buffers, interface method calls

2940 of 10661 branches covered (27.58%)

Branch coverage included in aggregate %.

20 of 467 new or added lines in 12 files covered. (4.28%)

8 existing lines in 6 files now uncovered.

5375 of 14577 relevant lines covered (36.87%)

151659.48 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.0
/Cpp2IL.Core/Analysis/DeadCodeEliminator.cs
1
using System.Collections.Generic;
2
using Cpp2IL.Core.Graphs;
3
using Cpp2IL.Core.ISIL;
4
using Cpp2IL.Core.Model.Contexts;
5

6
namespace Cpp2IL.Core.Analysis;
7

8
/// <summary>
9
/// Removes pure instructions whose result is never used. This eliminates, among other things, the
10
/// dead flag/temporary computations the x86 lifter emits eagerly for every comparison - a single
11
/// <c>cmp</c>/<c>test</c> produces all of CF/OF/SF/ZF/PF plus scratch temporaries, but the branch
12
/// that follows only consumes one of them.
13
///
14
/// Must run while the graph is still in SSA form (every local is assigned exactly once), so that a
15
/// global use count of zero is sufficient to prove a definition dead. Instructions are turned into
16
/// nops rather than spliced out; the structural cleanup happens later, out of SSA, where it is safe
17
/// for phi nodes.
18
/// </summary>
19
public static class DeadCodeEliminator
20
{
21
    public static void Run(MethodAnalysisContext method) => Run(method.ControlFlowGraph!);
×
22

23
    public static void Run(ISILControlFlowGraph cfg)
24
    {
25
        // Removing a dead definition can make its operands dead in turn, so iterate to a fixpoint.
26
        // This is monotonic (each pass only nops instructions) and therefore always terminates.
27
        var changed = true;
4✔
28
        while (changed)
11✔
29
        {
30
            changed = false;
7✔
31

32
            var useCounts = CountUses(cfg);
7✔
33

34
            foreach (var block in cfg.Blocks)
58✔
35
            {
36
                foreach (var instruction in block.Instructions)
98✔
37
                {
38
                    if (!IsRemovable(instruction.OpCode))
27✔
39
                        continue;
40

41
                    // Only definitions of a register local are candidates. Stores have a memory or
42
                    // field destination (Destination is not a local) and are never dead.
43
                    if (instruction.Destination is not LocalVariable destination)
15✔
44
                        continue;
45

46
                    if (useCounts.TryGetValue(destination, out var count) && count > 0)
15✔
47
                        continue;
48

49
                    instruction.OpCode = OpCode.Nop;
3✔
50
                    instruction.SetOperands();
3✔
51
                    changed = true;
3✔
52
                }
53
            }
54
        }
55
    }
4✔
56

57
    private static Dictionary<LocalVariable, int> CountUses(ISILControlFlowGraph cfg)
58
    {
59
        var counts = new Dictionary<LocalVariable, int>();
7✔
60

61
        foreach (var block in cfg.Blocks)
58✔
62
            foreach (var instruction in block.Instructions)
98✔
63
                foreach (var used in UsedLocals(instruction))
88✔
64
                    counts[used] = counts.TryGetValue(used, out var c) ? c + 1 : 1;
17✔
65

66
        return counts;
7✔
67
    }
68

69
    /// <summary>
70
    /// Every local read by the instruction. The single write position - a plain local destination -
71
    /// is excluded. Memory and field operands always contribute their address/object locals as
72
    /// reads, even when they are the destination of a store.
73
    /// </summary>
74
    private static IEnumerable<LocalVariable> UsedLocals(Instruction instruction)
75
    {
76
        var destination = instruction.Destination as LocalVariable;
27✔
77

78
        foreach (var operand in instruction.Operands)
140✔
79
        {
80
            switch (operand)
43!
81
            {
82
                case LocalVariable local when !ReferenceEquals(local, destination):
29✔
83
                    yield return local;
12✔
84
                    break;
12✔
85
                case MemoryOperand memory:
86
                    if (memory.Base is LocalVariable baseLocal)
×
87
                        yield return baseLocal;
×
88
                    if (memory.Index is LocalVariable indexLocal)
×
89
                        yield return indexLocal;
×
90
                    break;
×
91
                // A static field access doesn't read the storage pointer it was resolved from, so that
92
                // pointer (and the class load feeding it) is free to die.
93
                case FieldReference { Field.IsStatic: false, Local: { } fieldLocal }:
94
                    yield return fieldLocal;
×
95
                    break;
×
96
                // Handing out a slot's address is a read of it as far as we can tell, whatever the callee then does with it.
97
                case AddressOf { Target: LocalVariable addressed }:
98
                    yield return addressed;
×
UNCOV
99
                    break;
×
100
                case AddressOf { Target: ArrayAccess addressedElement }:
101
                    foreach (var used in ArrayAccessLocals(addressedElement))
6✔
102
                        yield return used;
2✔
103
                    break;
1✔
104
                case ArrayAccess access:
105
                    foreach (var used in ArrayAccessLocals(access))
6✔
106
                        yield return used;
2✔
107
                    break;
1✔
108
                case ArrayLength { Array: { } lengthArray }:
109
                    yield return lengthArray;
1✔
110
                    break;
111
            }
112
        }
113
    }
27✔
114

115
    private static IEnumerable<LocalVariable> ArrayAccessLocals(ArrayAccess access)
116
    {
117
        yield return access.Array;
2✔
118

119
        if (access.Index is LocalVariable index)
2!
120
            yield return index;
2✔
121
    }
2✔
122

123
    /// <summary>
124
    /// Opcodes with no side effects, so removing a never-read result is safe. Calls, stores,
125
    /// returns and branches are intentionally excluded.
126
    /// </summary>
127
    private static bool IsRemovable(OpCode opCode) =>
128
        opCode switch
27✔
129
        {
27✔
130
            OpCode.Move or OpCode.Phi
27✔
131
                or OpCode.Add or OpCode.Subtract or OpCode.Multiply or OpCode.Divide
27✔
132
                or OpCode.ShiftLeft or OpCode.ShiftRight
27✔
133
                or OpCode.And or OpCode.Or or OpCode.Xor
27✔
134
                or OpCode.Not or OpCode.Negate=> true,
14✔
135
            >= OpCode.CheckEqual and <= OpCode.CheckLessOrEqual => true,
1✔
136
            _ => false
12✔
137
        };
27✔
138
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc