Cthulhu  0.2.10
Cthulhu compiler collection
opt.c
Go to the documentation of this file.
1 // SPDX-License-Identifier: LGPL-3.0-only
2 
3 #include "common/common.h"
4 
6 
7 #include "memory/memory.h"
8 #include "std/set.h"
9 #include "std/map.h"
10 #include "std/vector.h"
11 
12 #include "std/typed/vector.h"
13 
14 #include "scan/node.h"
15 #include "base/panic.h"
16 
17 typedef struct ssa_vm_t
18 {
19  // externally provided values
23 
24  // TODO: dont use this node, preserve locations from frontend
26 
27  // all globals
29 } ssa_vm_t;
30 
31 typedef struct ssa_scope_t
32 {
34 
36 
40 } ssa_scope_t;
41 
42 static void add_global(ssa_vm_t *vm, ssa_symbol_t *global)
43 {
44  CTASSERTF(!set_contains(vm->globals, global), "global %s already exists", global->name);
45  set_add(vm->globals, global);
46 }
47 
48 static void add_globals(ssa_vm_t *vm, const ssa_module_t *module)
49 {
50  size_t len = vector_len(module->globals);
51  for (size_t i = 0; i < len; i++)
52  {
53  ssa_symbol_t *global = vector_get(module->globals, i);
54  add_global(vm, global);
55  }
56 }
57 
58 static void ssa_opt_global(ssa_vm_t *vm, ssa_symbol_t *global);
59 
60 static const ssa_step_t *get_step_indexed(const ssa_block_t *block, size_t index)
61 {
62  CTASSERT(block != NULL);
63  return typevec_offset(block->steps, index);
64 }
65 
66 static const ssa_value_t *ssa_opt_operand(ssa_scope_t *vm, ssa_operand_t operand)
67 {
68  switch (operand.kind)
69  {
70  case eOperandEmpty: return NULL;
71  case eOperandImm: return operand.value;
72  case eOperandReg: return map_get(vm->step_values, get_step_indexed(operand.vreg_context, operand.vreg_index));
73  case eOperandGlobal: {
74  const ssa_symbol_t *global = operand.global;
75  ssa_opt_global(vm->vm, (void*)global); // TODO: find a nice way to follow const
76  return global->value;
77  }
78 
79  default: CT_NEVER("unhandled operand kind %d (inside %s)", operand.kind, vm->symbol->name);
80  }
81 }
82 
83 static const ssa_value_t *ssa_opt_load(ssa_scope_t *vm, ssa_load_t step)
84 {
85  ssa_operand_t src = step.src;
86  switch (src.kind)
87  {
88  case eOperandGlobal: {
89  const ssa_symbol_t *global = src.global;
90  ssa_opt_global(vm->vm, (void*)global); // TODO: find a nice way to follow const
91  return global->value;
92  }
93  case eOperandLocal:
94  CT_NEVER("unhandled local %zu load (inside %s)", src.local, vm->symbol->name);
95 
96  default: CT_NEVER("unhandled load kind %d (inside %s)", src.kind, vm->symbol->name);
97  }
98 }
99 
100 static const ssa_value_t *ssa_opt_return(ssa_scope_t *vm, ssa_return_t step)
101 {
102  const ssa_value_t *value = ssa_opt_operand(vm, step.value);
103  CTASSERTF(value != NULL, "return value is NULL (inside %s)", vm->symbol->name);
104  vm->return_value = value;
105  return value;
106 }
107 
108 CT_PUREFN
109 static bool value_is(const ssa_value_t *value, ssa_kind_t type)
110 {
111  CTASSERT(value != NULL);
112  CTASSERT(value->type != NULL);
113 
114  return value->type->kind == type;
115 }
116 
117 static bool check_init(ssa_scope_t *vm, const ssa_value_t *value)
118 {
119  CTASSERT(value != NULL);
120 
121  if (!value->init)
122  {
123  msg_notify(vm->vm->reports, &kEvent_UninitializedValueUsed, vm->vm->node, "use of uninitialized value inside `%s`", vm->symbol->name);
124  return false;
125  }
126 
127  return true;
128 }
129 
130 static const ssa_value_t *ssa_opt_unary(ssa_scope_t *vm, ssa_unary_t step)
131 {
132  unary_t unary = step.unary;
133  const ssa_value_t *operand = ssa_opt_operand(vm, step.operand);
134 
135  if (!check_init(vm, operand)) { return operand; }
136 
137  mpz_t result;
138  mpz_init(result);
139 
140  const ssa_literal_value_t *literal = &operand->literal;
141 
142  CTASSERTF(operand->value == eValueLiteral, "operand of unary %s is not a literal (inside %s)", unary_name(unary), vm->symbol->name);
143 
144  switch (unary)
145  {
146  case eUnaryNeg:
147  CTASSERTF(value_is(operand, eTypeDigit), "operand of unary %s is not a digit (inside %s)", unary_name(unary), vm->symbol->name);
148  mpz_neg(result, literal->digit);
149  break;
150 
151  case eUnaryAbs:
152  CTASSERTF(value_is(operand, eTypeDigit), "operand of unary %s is not a digit (inside %s)", unary_name(unary), vm->symbol->name);
153  mpz_abs(result, literal->digit);
154  break;
155 
156  case eUnaryFlip:
157  CTASSERTF(value_is(operand, eTypeDigit), "operand of unary %s is not a digit (inside %s)", unary_name(unary), vm->symbol->name);
158  mpz_com(result, literal->digit);
159  break;
160 
161  case eUnaryNot:
162  CTASSERTF(value_is(operand, eTypeBool), "operand of unary %s is not a bool (inside %s)", unary_name(unary), vm->symbol->name);
163  return ssa_value_bool(operand->type, !literal->boolean);
164 
165  default: CT_NEVER("unhandled unary %s (inside %s)", unary_name(unary), vm->symbol->name);
166  }
167 
168  return ssa_value_digit(operand->type, result);
169 }
170 
171 static const ssa_value_t *ssa_opt_binary(ssa_scope_t *vm, ssa_binary_t step)
172 {
173  binary_t binary = step.binary;
174  const ssa_value_t *lhs = ssa_opt_operand(vm, step.lhs);
175  const ssa_value_t *rhs = ssa_opt_operand(vm, step.rhs);
176 
177  CTASSERTF(value_is(lhs, eTypeDigit), "lhs of binary %s is not a digit (inside %s)", binary_name(binary), vm->symbol->name);
178  CTASSERTF(value_is(rhs, eTypeDigit), "rhs of binary %s is not a digit (inside %s)", binary_name(binary), vm->symbol->name);
179 
180  if (!check_init(vm, lhs)) { return lhs; }
181  if (!check_init(vm, rhs)) { return rhs; }
182 
183  mpz_t result;
184  mpz_init(result);
185 
186  CTASSERT(lhs->value == eValueLiteral);
187  CTASSERT(rhs->value == eValueLiteral);
188 
189  const ssa_literal_value_t *lhs_literal = &lhs->literal;
190  const ssa_literal_value_t *rhs_literal = &rhs->literal;
191 
192  switch (binary)
193  {
194  case eBinaryAdd:
195  mpz_add(result, lhs_literal->digit, rhs_literal->digit);
196  break;
197  case eBinarySub:
198  mpz_sub(result, lhs_literal->digit, rhs_literal->digit);
199  break;
200  case eBinaryMul:
201  mpz_mul(result, lhs_literal->digit, rhs_literal->digit);
202  break;
203  case eBinaryDiv:
204  if (mpz_cmp_ui(rhs_literal->digit, 0) == 0)
205  {
206  msg_notify(vm->vm->reports, &kEvent_UninitializedValueUsed, vm->vm->node, "division by zero inside `%s`", vm->symbol->name);
207  return lhs;
208  }
209  mpz_divexact(result, lhs_literal->digit, rhs_literal->digit);
210  break;
211  case eBinaryRem:
212  if (mpz_cmp_ui(rhs_literal->digit, 0) == 0)
213  {
214  msg_notify(vm->vm->reports, &kEvent_ModuloByZero, vm->vm->node, "modulo by zero inside `%s`", vm->symbol->name);
215  return lhs;
216  }
217  mpz_mod(result, lhs_literal->digit, rhs_literal->digit);
218  break;
219 
220  /* TODO: do these produce correct values? */
221  case eBinaryShl:
222  mpz_mul_2exp(result, lhs_literal->digit, mpz_get_ui(rhs_literal->digit));
223  break;
224  case eBinaryShr:
225  mpz_fdiv_q_2exp(result, lhs_literal->digit, mpz_get_ui(rhs_literal->digit));
226  break;
227  case eBinaryXor:
228  mpz_xor(result, lhs_literal->digit, rhs_literal->digit);
229  break;
230 
231  default: CT_NEVER("unhandled binary %s (inside %s)", binary_name(binary), vm->symbol->name);
232  }
233 
234  // TODO: make sure this is actually the correct type
235  return ssa_value_digit(lhs->type, result);
236 }
237 
238 static const ssa_value_t *cast_to_opaque(const ssa_type_t *type, const ssa_value_t *value)
239 {
240  const ssa_type_t *src = value->type;
241  switch (src->kind)
242  {
243  case eTypeOpaque:
244  return value;
245 
246  case eTypeDigit: {
247  CTASSERT(value->value == eValueLiteral);
248  return ssa_value_literal(type, value->literal);
249  }
250 
251  default: CT_NEVER("unhandled type %s", ssa_type_name(src->kind));
252  }
253 }
254 
255 static const ssa_value_t *cast_to_pointer(const ssa_type_t *type, const ssa_value_t *value)
256 {
257  const ssa_type_t *src = value->type;
258 
259  CTASSERT(src->kind == eTypeOpaque || src->kind == eTypePointer);
260 
261  if (value->value == eValueLiteral)
262  {
263  const ssa_literal_value_t literal = value->literal;
264  return ssa_value_literal(type, literal);
265  }
266 
267  if (value->value == eValueRelative)
268  {
269  return ssa_value_relative(type, value->relative);
270  }
271 
272  CT_NEVER("unhandled value kind %d", value->value);
273 }
274 
275 static const ssa_value_t *cast_to_digit(const ssa_value_t *value)
276 {
277  const ssa_type_t *src = value->type;
278  switch (src->kind)
279  {
280  case eTypeOpaque:
281  case eTypeDigit:
282  return value;
283 
284  default: CT_NEVER("unhandled type %s", ssa_type_name(src->kind));
285  }
286 }
287 
288 static const ssa_value_t *ssa_opt_cast(ssa_scope_t *vm, ssa_cast_t cast)
289 {
290  const ssa_value_t *value = ssa_opt_operand(vm, cast.operand);
291  const ssa_type_t *type = cast.type;
292  switch (type->kind)
293  {
294  case eTypeOpaque:
295  return cast_to_opaque(type, value);
296 
297  case eTypePointer:
298  return cast_to_pointer(type, value);
299 
300  case eTypeDigit:
301  return cast_to_digit(value);
302 
303  default:
304  CT_NEVER("unhandled type %s", ssa_type_name(type->kind));
305  }
306 }
307 
308 static const ssa_value_t *ssa_opt_step(ssa_scope_t *vm, const ssa_step_t *step)
309 {
310  switch (step->opcode)
311  {
312  case eOpLoad: return ssa_opt_load(vm, step->load);
313  case eOpReturn: return ssa_opt_return(vm, step->ret);
314 
315  case eOpUnary: return ssa_opt_unary(vm, step->unary);
316  case eOpBinary: return ssa_opt_binary(vm, step->binary);
317  case eOpCast: return ssa_opt_cast(vm, step->cast);
318 
319  default: CT_NEVER("unhandled opcode %d (inside %s)", step->opcode, vm->symbol->name);
320  }
321 }
322 
323 static void ssa_opt_block(ssa_scope_t *vm, const ssa_block_t *block)
324 {
325  size_t len = typevec_len(block->steps);
326  for (size_t i = 0; i < len; i++)
327  {
328  const ssa_step_t *step = typevec_offset(block->steps, i);
329  const ssa_value_t *value = ssa_opt_step(vm, step);
330  map_set(vm->step_values, step, (void*)value);
331 
332  if (vm->return_value != NULL) { return; }
333  }
334 }
335 
336 static void ssa_opt_global(ssa_vm_t *vm, ssa_symbol_t *global)
337 {
338  CTASSERTF(set_contains(vm->globals, global), "global %s does not exist", global->name);
339 
340  if (global->value != NULL)
341  {
342  return;
343  }
344 
345  ssa_scope_t scope = {
346  .vm = vm,
347  .symbol = global,
348  .return_value = NULL,
349  .step_values = map_optimal(64, kTypeInfoPtr, vm->arena)
350  };
351 
352  ssa_opt_block(&scope, global->entry);
353  CTASSERTF(scope.return_value != NULL, "global %s failed to evaluate", global->name);
354 
355  global->value = scope.return_value;
356 }
357 
358 STA_DECL
359 void ssa_opt(logger_t *reports, ssa_result_t result, arena_t *arena)
360 {
361  CTASSERT(reports != NULL);
362  CTASSERT(arena != NULL);
363 
364  ssa_vm_t vm = {
365  .reports = reports,
366  .arena = arena,
367  .deps = result.deps,
368  .node = node_builtin("ssa", arena),
369 
370  .globals = set_new(64, kTypeInfoPtr, arena),
371  };
372 
373  size_t len = vector_len(result.modules);
374  for (size_t i = 0; i < len; i++)
375  {
376  const ssa_module_t *mod = vector_get(result.modules, i);
377  add_globals(&vm, mod);
378  }
379 
380  set_iter_t iter = set_iter(vm.globals);
381  while (set_has_next(&iter))
382  {
383  ssa_symbol_t *global = (ssa_symbol_t*)set_next(&iter);
384  ssa_opt_global(&vm, global);
385  }
386 }
ssa_value_t * ssa_value_bool(const ssa_type_t *type, bool value)
Definition: value.c:39
ssa_value_t * ssa_value_relative(const ssa_type_t *type, ssa_relative_value_t value)
Definition: value.c:113
ssa_value_t * ssa_value_literal(const ssa_type_t *type, ssa_literal_value_t value)
Definition: value.c:106
ssa_value_t * ssa_value_digit(const ssa_type_t *type, const mpz_t value)
Definition: value.c:46
#define CT_PUREFN
mark a function as pure, always returns the same value for the same arguments
Definition: analyze.h:228
#define STA_DECL
sal2 annotation on function implementations to copy annotations from the declaration
CT_NODISCARD CT_STD_API map_t * map_optimal(size_t size, hash_info_t info, arena_t *arena)
create a new map with an optimal size
Definition: optimal.c:85
CT_STD_API void map_set(map_t *map, const void *key, void *value)
set a key-value pair in a map
Definition: map.c:294
CT_NODISCARD CT_PUREFN CT_STD_API void * map_get(const map_t *map, const void *key)
get a value from a map
Definition: map.c:324
CT_NODISCARD CT_STD_API set_t * set_new(size_t size, hash_info_t info, arena_t *arena)
create a new set
Definition: set.c:51
CT_STD_API const void * set_add(set_t *set, const void *key)
add a key to a set
Definition: set.c:85
CT_NODISCARD CT_PUREFN CT_STD_API set_iter_t set_iter(set_t *set)
acquire a set iterator for a set
Definition: set.c:268
CT_NODISCARD CT_STD_API const void * set_next(set_iter_t *iter)
get the next item from a set iterator
Definition: set.c:288
CT_NODISCARD CT_PUREFN CT_STD_API bool set_contains(const set_t *set, const void *key)
check if a set contains a key
Definition: set.c:118
CT_NODISCARD CT_PUREFN CT_STD_API bool set_has_next(set_iter_t *iter)
check if a set iterator has more items
Definition: set.c:301
CT_NODISCARD CT_SCAN_API node_t * node_builtin(const char *name, arena_t *arena)
get the builtin node node used for drivers that declare builtin symbols
Definition: node.c:11
CT_NOTIFY_API event_builder_t msg_notify(INOUT_NOTNULL logger_t *logs, const diagnostic_t *diagnostic, const node_t *node, STA_FORMAT_STRING const char *fmt,...)
notify the logger of a new message
#define CT_NEVER(...)
assert that a code path is never reached
Definition: panic.h:136
#define CTASSERT(expr)
assert a condition, prints the condition as a message
Definition: panic.h:130
#define CTASSERTF(expr,...)
assert a condition with a message and optional format arguments
Definition: panic.h:116
STA_DECL void ssa_opt(logger_t *reports, ssa_result_t result, arena_t *arena)
Optimize a given module.
Definition: opt.c:359
CT_CONSTFN CT_NODISCARD CT_SSA_API const char * ssa_type_name(STA_IN_RANGE(0, eTypeCount - 1) ssa_kind_t kind)
ssa_kind_t
Definition: ssa.h:40
CT_TREE_API const char * binary_name(binary_t op)
get the pretty name of a binary operator
Definition: ops.c:42
binary_t
all binary operators
Definition: ops.h:32
unary_t
all unary operators
Definition: ops.h:48
CT_TREE_API const char * unary_name(unary_t op)
get the pretty name of a unary operator
Definition: ops.c:18
CT_STD_API const hash_info_t kTypeInfoPtr
type information for a generic pointer this operates on the pointer itself and not the data it points...
Definition: typeinfo.c:41
CT_NODISCARD CT_PUREFN CT_STD_API size_t typevec_len(const typevec_t *vec)
get the length of a vector
Definition: vector.c:120
CT_NODISCARD CT_PUREFN CT_STD_API void * typevec_offset(const typevec_t *vec, size_t index)
get a pointer to the value at the given index
Definition: vector.c:191
CT_NODISCARD CT_PUREFN CT_STD_API void * vector_get(const vector_t *vector, size_t index)
get a value from a vector
Definition: vector.c:134
CT_NODISCARD CT_PUREFN CT_STD_API size_t vector_len(const vector_t *vector)
get the length of a vector
Definition: vector.c:152
an allocator object
Definition: arena.h:86
a logging sink
Definition: notify.c:14
arena_t * arena
Definition: notify.c:15
an unordered hash map
Definition: map.h:38
a position in a source file
Definition: node.h:23
a set iterator handle
Definition: set.h:78
an unordered hash set
Definition: set.c:19
binary_t binary
Definition: ssa.h:242
ssa_operand_t rhs
Definition: ssa.h:241
ssa_operand_t lhs
Definition: ssa.h:240
typevec_t * steps
Definition: ssa.h:330
const ssa_type_t * type
Definition: ssa.h:253
ssa_operand_t operand
Definition: ssa.h:252
ssa_operand_t src
Definition: ssa.h:227
vector_t * globals
vector<ssa_symbol_t> all globals declared/imported/exported by this module
Definition: ssa.h:358
const ssa_value_t * value
Definition: ssa.h:217
size_t local
Definition: ssa.h:205
const ssa_block_t * vreg_context
Definition: ssa.h:200
ssa_opkind_t kind
Definition: ssa.h:192
const ssa_symbol_t * global
Definition: ssa.h:211
size_t vreg_index
Definition: ssa.h:201
vector_t * modules
Definition: ssa.h:363
map_t * deps
Definition: ssa.h:364
ssa_operand_t value
Definition: ssa.h:272
map_t * step_values
Definition: opt.c:39
const ssa_value_t * return_value
Definition: opt.c:38
const ssa_symbol_t * symbol
Definition: opt.c:37
ssa_vm_t * vm
Definition: opt.c:33
typevec_t * locals
Definition: opt.c:35
ssa_unary_t unary
Definition: ssa.h:307
ssa_binary_t binary
Definition: ssa.h:308
ssa_load_t load
Definition: ssa.h:304
ssa_opcode_t opcode
Definition: ssa.h:299
ssa_return_t ret
Definition: ssa.h:317
ssa_cast_t cast
Definition: ssa.h:311
ssa_block_t * entry
entry block
Definition: ssa.h:349
const ssa_value_t * value
the value of this symbol, must always be set for globals
Definition: ssa.h:344
const char * name
internal name
Definition: ssa.h:340
ssa_kind_t kind
Definition: ssa.h:146
unary_t unary
Definition: ssa.h:236
ssa_operand_t operand
Definition: ssa.h:235
ssa_literal_value_t literal
Definition: ssa.h:184
bool init
whether this value has been initialized
Definition: ssa.h:180
ssa_value_state_t value
Definition: ssa.h:179
const ssa_type_t * type
Definition: ssa.h:178
ssa_relative_value_t relative
Definition: ssa.h:187
Definition: opt.c:18
arena_t * arena
Definition: opt.c:21
node_t * node
Definition: opt.c:25
set_t * globals
Definition: opt.c:28
map_t * deps
Definition: opt.c:22
logger_t * reports
Definition: opt.c:20
A vector with a fixed type size.
Definition: vector.h:24