aboutsummaryrefslogtreecommitdiff
path: root/backend/unittests/CodeGen/MachineJumpTableInfoTest.cpp
blob: 027380110d1797c4d273e66b130bbc2f1055e14f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// SPDX-License-Identifier: GPL-3.0-or-later

#include "rcc/CodeGen/MachineJumpTableInfo.h"

#include "rcc/CodeGen/MachineBasicBlock.h"
#include "rcc/CodeGen/MachineFunction.h"

#include <cstdint>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>

namespace {

class TestContext final {
public:
  void expect(bool Condition, std::string_view Message) {
    if (Condition)
      return;
    std::cerr << Message << '\n';
    ++FailureCount;
  }

  [[nodiscard]] int result() const noexcept {
    return FailureCount == 0 ? 0 : 1;
  }

private:
  int FailureCount = 0;
};

[[nodiscard]] std::unique_ptr<rcc::MachineFunction>
createFunction(TestContext &Context, std::string Name, std::uint64_t ID) {
  auto Function =
      rcc::MachineFunction::create(std::move(Name), rcc::MachineFunctionStableID::fromValue(ID));
  Context.expect(Function.has_value(), "machine function creation must succeed");
  if (!Function)
    return nullptr;
  return std::move(*Function);
}

[[nodiscard]] rcc::MachineBasicBlock *createBlock(TestContext &Context,
                                                  rcc::MachineFunction &Function) {
  auto Block = Function.createBasicBlock();
  Context.expect(Block.has_value() && Function.appendBlock(**Block).has_value(),
                 "jump table test block creation must succeed");
  return Block ? *Block : nullptr;
}

[[nodiscard]] rcc::MachineJumpTableDesc tableDescription(rcc::MachineBasicBlock &First,
                                                         rcc::MachineBasicBlock &Second) {
  rcc::MachineJumpTableDesc Description;
  Description.Encoding = rcc::JumpTableEntryEncoding::RelativeToTableBase;
  Description.EntrySize = 4;
  Description.Align = *rcc::Alignment::create(4);
  Description.Targets = {&First, &Second, &Second};
  Description.Placement.MaximumDistance = 4096;
  return Description;
}

void testTableLifecycle(TestContext &Context) {
  auto Function = createFunction(Context, "jump_tables", 1);
  if (!Function)
    return;
  rcc::MachineBasicBlock *Entry = createBlock(Context, *Function);
  rcc::MachineBasicBlock *Exit = createBlock(Context, *Function);
  if (Entry == nullptr || Exit == nullptr)
    return;

  auto First = Function->jumpTableInfo().createTable(tableDescription(*Entry, *Exit));
  auto TargetDescription = tableDescription(*Exit, *Entry);
  TargetDescription.Encoding = rcc::JumpTableEntryEncoding::Target;
  TargetDescription.TargetEncoding = 19;
  TargetDescription.Placement.TargetFlags = 7;
  auto Target = Function->jumpTableInfo().createTable(std::move(TargetDescription));
  Context.expect(First && Target && First->value() == 0 && Target->value() == 1 &&
                     Function->jumpTableInfo().liveTableCount() == 2,
                 "valid jump tables must receive monotonic indices");
  if (!First || !Target)
    return;

  const rcc::MachineJumpTableEntry *FirstEntry = Function->jumpTableInfo().table(*First);
  Context.expect(FirstEntry != nullptr && FirstEntry->description()->Targets.size() == 3 &&
                     FirstEntry->description()->Targets[1] == Exit &&
                     FirstEntry->description()->Targets[2] == Exit,
                 "jump table target order and duplicate cases must be preserved");

  Context.expect(Function->jumpTableInfo().eraseTable(*First).has_value() &&
                     Function->jumpTableInfo().table(*First) == nullptr,
                 "erasing a jump table must leave a tombstone");
  auto CompressedDescription = tableDescription(*Entry, *Exit);
  CompressedDescription.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset;
  CompressedDescription.EntrySize = 2;
  auto Compressed = Function->jumpTableInfo().createTable(std::move(CompressedDescription));
  Context.expect(Compressed && Compressed->value() == 2,
                 "jump table tombstone indices must never be reused");

  auto Updated = tableDescription(*Entry, *Exit);
  Updated.Encoding = rcc::JumpTableEntryEncoding::RelativeToEntry;
  Context.expect(Function->jumpTableInfo().updateTable(*Target, std::move(Updated)).has_value() &&
                     Function->jumpTableInfo().verify().has_value() &&
                     Function->jumpTableInfo().freeze().has_value() &&
                     Function->jumpTableInfo().isFrozen(),
                 "valid jump table updates must verify and freeze");
  Context.expect(
      !Function->jumpTableInfo().eraseTable(*Target) &&
          !Function->jumpTableInfo().updateTable(*Target, tableDescription(*Entry, *Exit)) &&
          !Function->jumpTableInfo().createTable(tableDescription(*Entry, *Exit)) &&
          !Function->jumpTableInfo().freeze(),
      "frozen jump table info must reject every mutation");
}

void testInvalidTables(TestContext &Context) {
  auto Function = createFunction(Context, "invalid_tables", 2);
  auto Foreign = createFunction(Context, "foreign_tables", 3);
  if (!Function || !Foreign)
    return;
  rcc::MachineBasicBlock *Block = createBlock(Context, *Function);
  rcc::MachineBasicBlock *ForeignBlock = createBlock(Context, *Foreign);
  if (Block == nullptr || ForeignBlock == nullptr)
    return;

  rcc::MachineJumpTableDesc Empty;
  Context.expect(!Function->jumpTableInfo().createTable(std::move(Empty)),
                 "jump table must reject an empty target list");

  auto ForeignTarget = tableDescription(*Block, *ForeignBlock);
  Context.expect(!Function->jumpTableInfo().createTable(std::move(ForeignTarget)),
                 "jump table must reject a foreign target block");

  auto InvalidSize = tableDescription(*Block, *Block);
  InvalidSize.EntrySize = 3;
  Context.expect(!Function->jumpTableInfo().createTable(std::move(InvalidSize)),
                 "jump table entry size must be a bounded power of two");

  auto MissingTargetEncoding = tableDescription(*Block, *Block);
  MissingTargetEncoding.Encoding = rcc::JumpTableEntryEncoding::Target;
  Context.expect(!Function->jumpTableInfo().createTable(std::move(MissingTargetEncoding)),
                 "target jump table must carry a target encoding ID");

  auto UnexpectedTargetEncoding = tableDescription(*Block, *Block);
  UnexpectedTargetEncoding.TargetEncoding = 1;
  Context.expect(!Function->jumpTableInfo().createTable(std::move(UnexpectedTargetEncoding)),
                 "generic jump table encoding must reject target encoding IDs");

  auto OversizedCompressed = tableDescription(*Block, *Block);
  OversizedCompressed.Encoding = rcc::JumpTableEntryEncoding::CompressedOffset;
  OversizedCompressed.EntrySize = 8;
  Context.expect(!Function->jumpTableInfo().createTable(std::move(OversizedCompressed)),
                 "compressed jump table must reject oversized entries");

  auto ZeroDistance = tableDescription(*Block, *Block);
  ZeroDistance.Placement.MaximumDistance = 0;
  Context.expect(!Function->jumpTableInfo().createTable(std::move(ZeroDistance)),
                 "jump table maximum distance must be nonzero");

  auto LiveTable = Function->jumpTableInfo().createTable(tableDescription(*Block, *Block));
  Context.expect(LiveTable && Function->removeBlock(*Block).has_value() &&
                     !Function->jumpTableInfo().verify(),
                 "jump table verification must detect a detached target");
}

} // namespace

int main() {
  TestContext Context;
  testTableLifecycle(Context);
  testInvalidTables(Context);
  return Context.result();
}