authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2023-04-10 00:00:35-04:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2023-04-20 20:28:47+01:00
log02a8b66b003bcf7fd8a7629f6c155df1d7e0a360
tree3168820ce70256a9f36f585ab49e1a01f37a6e02
parent6f210b74eeff0557fffb034386f4e5bb992daba0
signature Commit is signed but in an unrecognized format.

Liveness: add a liveness verification pass

This code only runs in a debug zig compiler, similar to verifying llvm modules.

4 files changed, 640 insertions(+), 1 deletions(-)

src/Compilation.zig+2-1
...@@ -3092,6 +3092,7 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v...@@ -3092,6 +3092,7 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v
30923092
3093 .file_failure,3093 .file_failure,
3094 .sema_failure,3094 .sema_failure,
3095 .liveness_failure,
3095 .codegen_failure,3096 .codegen_failure,
3096 .dependency_failure,3097 .dependency_failure,
3097 .sema_failure_retryable,3098 .sema_failure_retryable,
...@@ -3142,7 +3143,7 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v...@@ -3142,7 +3143,7 @@ fn processOneJob(comp: *Compilation, job: Job, prog_node: *std.Progress.Node) !v
31423143
3143 // emit-h only requires semantic analysis of the Decl to be complete,3144 // emit-h only requires semantic analysis of the Decl to be complete,
3144 // it does not depend on machine code generation to succeed.3145 // it does not depend on machine code generation to succeed.
3145 .codegen_failure, .codegen_failure_retryable, .complete => {3146 .liveness_failure, .codegen_failure, .codegen_failure_retryable, .complete => {
3146 const named_frame = tracy.namedFrame("emit_h_decl");3147 const named_frame = tracy.namedFrame("emit_h_decl");
3147 defer named_frame.end();3148 defer named_frame.end();
31483149
src/Liveness.zig+2
...@@ -14,6 +14,8 @@ const Allocator = std.mem.Allocator;...@@ -14,6 +14,8 @@ const Allocator = std.mem.Allocator;
14const Air = @import("Air.zig");14const Air = @import("Air.zig");
15const Log2Int = std.math.Log2Int;15const Log2Int = std.math.Log2Int;
1616
17pub const Verify = @import("Liveness/Verify.zig");
18
17/// This array is split into sets of 4 bits per AIR instruction.19/// This array is split into sets of 4 bits per AIR instruction.
18/// The MSB (0bX000) is whether the instruction is unreferenced.20/// The MSB (0bX000) is whether the instruction is unreferenced.
19/// The LSB (0b000X) is the first operand, and so on, up to 3 operands. A set bit means the21/// The LSB (0b000X) is the first operand, and so on, up to 3 operands. A set bit means the
src/Liveness/Verify.zig created+603
...@@ -0,0 +1,603 @@
1//! Verifies that liveness information is valid.
2
3gpa: std.mem.Allocator,
4air: Air,
5liveness: Liveness,
6live: LiveMap = .{},
7blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, LiveMap) = .{},
8
9pub const Error = error{ LivenessInvalid, OutOfMemory };
10
11pub fn deinit(self: *Verify) void {
12 self.live.deinit(self.gpa);
13 var block_it = self.blocks.valueIterator();
14 while (block_it.next()) |block| block.deinit(self.gpa);
15 self.blocks.deinit(self.gpa);
16 self.* = undefined;
17}
18
19pub fn verify(self: *Verify) Error!void {
20 self.live.clearRetainingCapacity();
21 self.blocks.clearRetainingCapacity();
22 try self.verifyBody(self.air.getMainBody());
23 // We don't care about `self.live` now, because the loop body was noreturn - everything being dead was checked on `ret` etc
24 assert(self.blocks.count() == 0);
25}
26
27const LiveMap = std.AutoHashMapUnmanaged(Air.Inst.Index, void);
28
29fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
30 const tag = self.air.instructions.items(.tag);
31 const data = self.air.instructions.items(.data);
32 for (body) |inst| switch (tag[inst]) {
33 // no operands
34 .arg,
35 .alloc,
36 .ret_ptr,
37 .constant,
38 .const_ty,
39 .breakpoint,
40 .dbg_stmt,
41 .dbg_inline_begin,
42 .dbg_inline_end,
43 .dbg_block_begin,
44 .dbg_block_end,
45 .fence,
46 .ret_addr,
47 .frame_addr,
48 .wasm_memory_size,
49 .err_return_trace,
50 .save_err_return_trace_index,
51 .c_va_start,
52 .work_item_id,
53 .work_group_size,
54 .work_group_id,
55 => try self.verifyInst(inst, .{ .none, .none, .none }),
56
57 .trap, .unreach => {
58 try self.verifyInst(inst, .{ .none, .none, .none });
59 // This instruction terminates the function, so everything should be dead
60 if (self.live.count() > 0) return invalid("%{}: instructions still alive", .{inst});
61 },
62
63 // unary
64 .not,
65 .bitcast,
66 .load,
67 .fpext,
68 .fptrunc,
69 .intcast,
70 .trunc,
71 .optional_payload,
72 .optional_payload_ptr,
73 .optional_payload_ptr_set,
74 .errunion_payload_ptr_set,
75 .wrap_optional,
76 .unwrap_errunion_payload,
77 .unwrap_errunion_err,
78 .unwrap_errunion_payload_ptr,
79 .unwrap_errunion_err_ptr,
80 .wrap_errunion_payload,
81 .wrap_errunion_err,
82 .slice_ptr,
83 .slice_len,
84 .ptr_slice_len_ptr,
85 .ptr_slice_ptr_ptr,
86 .struct_field_ptr_index_0,
87 .struct_field_ptr_index_1,
88 .struct_field_ptr_index_2,
89 .struct_field_ptr_index_3,
90 .array_to_slice,
91 .float_to_int,
92 .float_to_int_optimized,
93 .int_to_float,
94 .get_union_tag,
95 .clz,
96 .ctz,
97 .popcount,
98 .byte_swap,
99 .bit_reverse,
100 .splat,
101 .error_set_has_value,
102 .addrspace_cast,
103 .c_va_arg,
104 .c_va_copy,
105 => {
106 const ty_op = data[inst].ty_op;
107 try self.verifyInst(inst, .{ ty_op.operand, .none, .none });
108 },
109 .is_null,
110 .is_non_null,
111 .is_null_ptr,
112 .is_non_null_ptr,
113 .is_err,
114 .is_non_err,
115 .is_err_ptr,
116 .is_non_err_ptr,
117 .ptrtoint,
118 .bool_to_int,
119 .is_named_enum_value,
120 .tag_name,
121 .error_name,
122 .sqrt,
123 .sin,
124 .cos,
125 .tan,
126 .exp,
127 .exp2,
128 .log,
129 .log2,
130 .log10,
131 .fabs,
132 .floor,
133 .ceil,
134 .round,
135 .trunc_float,
136 .neg,
137 .neg_optimized,
138 .cmp_lt_errors_len,
139 .set_err_return_trace,
140 .c_va_end,
141 => {
142 const un_op = data[inst].un_op;
143 try self.verifyInst(inst, .{ un_op, .none, .none });
144 },
145 .ret,
146 .ret_load,
147 => {
148 const un_op = data[inst].un_op;
149 try self.verifyInst(inst, .{ un_op, .none, .none });
150 // This instruction terminates the function, so everything should be dead
151 if (self.live.count() > 0) return invalid("%{}: instructions still alive", .{inst});
152 },
153 .dbg_var_ptr,
154 .dbg_var_val,
155 .wasm_memory_grow,
156 => {
157 const pl_op = data[inst].pl_op;
158 try self.verifyInst(inst, .{ pl_op.operand, .none, .none });
159 },
160 .prefetch => {
161 const prefetch = data[inst].prefetch;
162 try self.verifyInst(inst, .{ prefetch.ptr, .none, .none });
163 },
164 .reduce,
165 .reduce_optimized,
166 => {
167 const reduce = data[inst].reduce;
168 try self.verifyInst(inst, .{ reduce.operand, .none, .none });
169 },
170 .union_init => {
171 const ty_pl = data[inst].ty_pl;
172 const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data;
173 try self.verifyInst(inst, .{ extra.init, .none, .none });
174 },
175 .struct_field_ptr, .struct_field_val => {
176 const ty_pl = data[inst].ty_pl;
177 const extra = self.air.extraData(Air.StructField, ty_pl.payload).data;
178 try self.verifyInst(inst, .{ extra.struct_operand, .none, .none });
179 },
180 .field_parent_ptr => {
181 const ty_pl = data[inst].ty_pl;
182 const extra = self.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
183 try self.verifyInst(inst, .{ extra.field_ptr, .none, .none });
184 },
185 .atomic_load => {
186 const atomic_load = data[inst].atomic_load;
187 try self.verifyInst(inst, .{ atomic_load.ptr, .none, .none });
188 },
189
190 // binary
191 .add,
192 .add_optimized,
193 .addwrap,
194 .addwrap_optimized,
195 .add_sat,
196 .sub,
197 .sub_optimized,
198 .subwrap,
199 .subwrap_optimized,
200 .sub_sat,
201 .mul,
202 .mul_optimized,
203 .mulwrap,
204 .mulwrap_optimized,
205 .mul_sat,
206 .div_float,
207 .div_float_optimized,
208 .div_trunc,
209 .div_trunc_optimized,
210 .div_floor,
211 .div_floor_optimized,
212 .div_exact,
213 .div_exact_optimized,
214 .rem,
215 .rem_optimized,
216 .mod,
217 .mod_optimized,
218 .bit_and,
219 .bit_or,
220 .xor,
221 .cmp_lt,
222 .cmp_lt_optimized,
223 .cmp_lte,
224 .cmp_lte_optimized,
225 .cmp_eq,
226 .cmp_eq_optimized,
227 .cmp_gte,
228 .cmp_gte_optimized,
229 .cmp_gt,
230 .cmp_gt_optimized,
231 .cmp_neq,
232 .cmp_neq_optimized,
233 .bool_and,
234 .bool_or,
235 .store,
236 .array_elem_val,
237 .slice_elem_val,
238 .ptr_elem_val,
239 .shl,
240 .shl_exact,
241 .shl_sat,
242 .shr,
243 .shr_exact,
244 .atomic_store_unordered,
245 .atomic_store_monotonic,
246 .atomic_store_release,
247 .atomic_store_seq_cst,
248 .set_union_tag,
249 .min,
250 .max,
251 => {
252 const bin_op = data[inst].bin_op;
253 try self.verifyInst(inst, .{ bin_op.lhs, bin_op.rhs, .none });
254 },
255 .add_with_overflow,
256 .sub_with_overflow,
257 .mul_with_overflow,
258 .shl_with_overflow,
259 .ptr_add,
260 .ptr_sub,
261 .ptr_elem_ptr,
262 .slice_elem_ptr,
263 .slice,
264 => {
265 const ty_pl = data[inst].ty_pl;
266 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
267 try self.verifyInst(inst, .{ extra.lhs, extra.rhs, .none });
268 },
269 .shuffle => {
270 const ty_pl = data[inst].ty_pl;
271 const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data;
272 try self.verifyInst(inst, .{ extra.a, extra.b, .none });
273 },
274 .cmp_vector,
275 .cmp_vector_optimized,
276 => {
277 const ty_pl = data[inst].ty_pl;
278 const extra = self.air.extraData(Air.VectorCmp, ty_pl.payload).data;
279 try self.verifyInst(inst, .{ extra.lhs, extra.rhs, .none });
280 },
281 .atomic_rmw => {
282 const pl_op = data[inst].pl_op;
283 const extra = self.air.extraData(Air.AtomicRmw, pl_op.payload).data;
284 try self.verifyInst(inst, .{ pl_op.operand, extra.operand, .none });
285 },
286
287 // ternary
288 .select => {
289 const pl_op = data[inst].pl_op;
290 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
291 try self.verifyInst(inst, .{ pl_op.operand, extra.lhs, extra.rhs });
292 },
293 .mul_add => {
294 const pl_op = data[inst].pl_op;
295 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
296 try self.verifyInst(inst, .{ extra.lhs, extra.rhs, pl_op.operand });
297 },
298 .vector_store_elem => {
299 const vector_store_elem = data[inst].vector_store_elem;
300 const extra = self.air.extraData(Air.Bin, vector_store_elem.payload).data;
301 try self.verifyInst(inst, .{ vector_store_elem.vector_ptr, extra.lhs, extra.rhs });
302 },
303 .memset,
304 .memcpy,
305 => {
306 const pl_op = data[inst].pl_op;
307 const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
308 try self.verifyInst(inst, .{ pl_op.operand, extra.lhs, extra.rhs });
309 },
310 .cmpxchg_strong,
311 .cmpxchg_weak,
312 => {
313 const ty_pl = data[inst].ty_pl;
314 const extra = self.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
315 try self.verifyInst(inst, .{ extra.ptr, extra.expected_value, extra.new_value });
316 },
317
318 // big tombs
319 .aggregate_init => {
320 const ty_pl = data[inst].ty_pl;
321 const aggregate_ty = self.air.getRefType(ty_pl.ty);
322 const len = @intCast(usize, aggregate_ty.arrayLen());
323 const elements = @ptrCast([]const Air.Inst.Ref, self.air.extra[ty_pl.payload..][0..len]);
324
325 var bt = self.liveness.iterateBigTomb(inst);
326 for (elements) |element| {
327 try self.verifyOperand(inst, element, bt.feed());
328 }
329 try self.verifyInst(inst, .{ .none, .none, .none });
330 },
331 .call, .call_always_tail, .call_never_tail, .call_never_inline => {
332 const pl_op = data[inst].pl_op;
333 const extra = self.air.extraData(Air.Call, pl_op.payload);
334 const args = @ptrCast(
335 []const Air.Inst.Ref,
336 self.air.extra[extra.end..][0..extra.data.args_len],
337 );
338
339 var bt = self.liveness.iterateBigTomb(inst);
340 try self.verifyOperand(inst, pl_op.operand, bt.feed());
341 for (args) |arg| {
342 try self.verifyOperand(inst, arg, bt.feed());
343 }
344 try self.verifyInst(inst, .{ .none, .none, .none });
345 },
346 .assembly => {
347 const ty_pl = data[inst].ty_pl;
348 const extra = self.air.extraData(Air.Asm, ty_pl.payload);
349 var extra_i = extra.end;
350 const outputs = @ptrCast(
351 []const Air.Inst.Ref,
352 self.air.extra[extra_i..][0..extra.data.outputs_len],
353 );
354 extra_i += outputs.len;
355 const inputs = @ptrCast(
356 []const Air.Inst.Ref,
357 self.air.extra[extra_i..][0..extra.data.inputs_len],
358 );
359 extra_i += inputs.len;
360
361 var bt = self.liveness.iterateBigTomb(inst);
362 for (outputs) |output| {
363 if (output != .none) {
364 try self.verifyOperand(inst, output, bt.feed());
365 }
366 }
367 for (inputs) |input| {
368 try self.verifyOperand(inst, input, bt.feed());
369 }
370 try self.verifyInst(inst, .{ .none, .none, .none });
371 },
372
373 // control flow
374 .@"try" => {
375 const pl_op = data[inst].pl_op;
376 const extra = self.air.extraData(Air.Try, pl_op.payload);
377 const try_body = self.air.extra[extra.end..][0..extra.data.body_len];
378
379 const cond_br_liveness = self.liveness.getCondBr(inst);
380
381 try self.verifyOperand(inst, pl_op.operand, self.liveness.operandDies(inst, 0));
382
383 var live = try self.live.clone(self.gpa);
384 defer live.deinit(self.gpa);
385
386 for (cond_br_liveness.else_deaths) |death| try self.verifyDeath(inst, death);
387 try self.verifyBody(try_body);
388
389 self.live.deinit(self.gpa);
390 self.live = live.move();
391
392 for (cond_br_liveness.then_deaths) |death| try self.verifyDeath(inst, death);
393
394 try self.verifyInst(inst, .{ .none, .none, .none });
395 },
396 .try_ptr => {
397 const ty_pl = data[inst].ty_pl;
398 const extra = self.air.extraData(Air.TryPtr, ty_pl.payload);
399 const try_body = self.air.extra[extra.end..][0..extra.data.body_len];
400
401 const cond_br_liveness = self.liveness.getCondBr(inst);
402
403 try self.verifyOperand(inst, extra.data.ptr, self.liveness.operandDies(inst, 0));
404
405 var live = try self.live.clone(self.gpa);
406 defer live.deinit(self.gpa);
407
408 for (cond_br_liveness.else_deaths) |death| try self.verifyDeath(inst, death);
409 try self.verifyBody(try_body);
410
411 self.live.deinit(self.gpa);
412 self.live = live.move();
413
414 for (cond_br_liveness.then_deaths) |death| try self.verifyDeath(inst, death);
415
416 try self.verifyInst(inst, .{ .none, .none, .none });
417 },
418 .br => {
419 const br = data[inst].br;
420 const gop = try self.blocks.getOrPut(self.gpa, br.block_inst);
421
422 try self.verifyOperand(inst, br.operand, self.liveness.operandDies(inst, 0));
423 if (gop.found_existing) {
424 try self.verifyMatchingLiveness(br.block_inst, gop.value_ptr.*);
425 } else {
426 gop.value_ptr.* = try self.live.clone(self.gpa);
427 }
428 try self.verifyInst(inst, .{ .none, .none, .none });
429 },
430 .block => {
431 const ty_pl = data[inst].ty_pl;
432 const block_ty = self.air.getRefType(ty_pl.ty);
433 const extra = self.air.extraData(Air.Block, ty_pl.payload);
434 const block_body = self.air.extra[extra.end..][0..extra.data.body_len];
435 const block_liveness = self.liveness.getBlock(inst);
436
437 var orig_live = try self.live.clone(self.gpa);
438 defer orig_live.deinit(self.gpa);
439
440 assert(!self.blocks.contains(inst));
441 try self.verifyBody(block_body);
442
443 // Liveness data after the block body is garbage, but we want to
444 // restore it to verify deaths
445 self.live.deinit(self.gpa);
446 self.live = orig_live.move();
447
448 for (block_liveness.deaths) |death| try self.verifyDeath(inst, death);
449
450 if (block_ty.isNoReturn()) {
451 assert(!self.blocks.contains(inst));
452 } else {
453 var live = self.blocks.fetchRemove(inst).?.value;
454 defer live.deinit(self.gpa);
455
456 try self.verifyMatchingLiveness(inst, live);
457 }
458
459 try self.verifyInst(inst, .{ .none, .none, .none });
460 },
461 .loop => {
462 const ty_pl = data[inst].ty_pl;
463 const extra = self.air.extraData(Air.Block, ty_pl.payload);
464 const loop_body = self.air.extra[extra.end..][0..extra.data.body_len];
465
466 var live = try self.live.clone(self.gpa);
467 defer live.deinit(self.gpa);
468
469 try self.verifyBody(loop_body);
470
471 // The same stuff should be alive after the loop as before it
472 try self.verifyMatchingLiveness(inst, live);
473
474 try self.verifyInst(inst, .{ .none, .none, .none });
475 },
476 .cond_br => {
477 const pl_op = data[inst].pl_op;
478 const extra = self.air.extraData(Air.CondBr, pl_op.payload);
479 const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
480 const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
481 const cond_br_liveness = self.liveness.getCondBr(inst);
482
483 try self.verifyOperand(inst, pl_op.operand, self.liveness.operandDies(inst, 0));
484
485 var live = try self.live.clone(self.gpa);
486 defer live.deinit(self.gpa);
487
488 for (cond_br_liveness.then_deaths) |death| try self.verifyDeath(inst, death);
489 try self.verifyBody(then_body);
490
491 self.live.deinit(self.gpa);
492 self.live = live.move();
493
494 for (cond_br_liveness.else_deaths) |death| try self.verifyDeath(inst, death);
495 try self.verifyBody(else_body);
496
497 try self.verifyInst(inst, .{ .none, .none, .none });
498 },
499 .switch_br => {
500 const pl_op = data[inst].pl_op;
501 const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
502 var extra_index = switch_br.end;
503 var case_i: u32 = 0;
504 const switch_br_liveness = try self.liveness.getSwitchBr(
505 self.gpa,
506 inst,
507 switch_br.data.cases_len + 1,
508 );
509 defer self.gpa.free(switch_br_liveness.deaths);
510
511 try self.verifyOperand(inst, pl_op.operand, self.liveness.operandDies(inst, 0));
512
513 var live = self.live.move();
514 defer live.deinit(self.gpa);
515
516 while (case_i < switch_br.data.cases_len) : (case_i += 1) {
517 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
518 const items = @ptrCast(
519 []const Air.Inst.Ref,
520 self.air.extra[case.end..][0..case.data.items_len],
521 );
522 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
523 extra_index = case.end + items.len + case_body.len;
524
525 self.live.deinit(self.gpa);
526 self.live = try live.clone(self.gpa);
527
528 for (switch_br_liveness.deaths[case_i]) |death| try self.verifyDeath(inst, death);
529 try self.verifyBody(case_body);
530 }
531
532 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
533 if (else_body.len > 0) {
534 self.live.deinit(self.gpa);
535 self.live = try live.clone(self.gpa);
536
537 for (switch_br_liveness.deaths[case_i]) |death| try self.verifyDeath(inst, death);
538 try self.verifyBody(else_body);
539 }
540
541 try self.verifyInst(inst, .{ .none, .none, .none });
542 },
543 };
544}
545
546fn verifyDeath(self: *Verify, inst: Air.Inst.Index, operand: Air.Inst.Index) Error!void {
547 try self.verifyOperand(inst, Air.indexToRef(operand), true);
548}
549
550fn verifyOperand(self: *Verify, inst: Air.Inst.Index, op_ref: Air.Inst.Ref, dies: bool) Error!void {
551 const operand = Air.refToIndex(op_ref) orelse return;
552 switch (self.air.instructions.items(.tag)[operand]) {
553 .constant, .const_ty => {},
554 else => {
555 if (dies) {
556 if (!self.live.remove(operand)) return invalid("%{}: dead operand %{} reused and killed again", .{ inst, operand });
557 } else {
558 if (!self.live.contains(operand)) return invalid("%{}: dead operand %{} reused", .{ inst, operand });
559 }
560 },
561 }
562}
563
564fn verifyInst(
565 self: *Verify,
566 inst: Air.Inst.Index,
567 operands: [Liveness.bpi - 1]Air.Inst.Ref,
568) Error!void {
569 for (operands, 0..) |operand, operand_index| {
570 const dies = self.liveness.operandDies(inst, @intCast(Liveness.OperandInt, operand_index));
571 try self.verifyOperand(inst, operand, dies);
572 }
573 const tag = self.air.instructions.items(.tag);
574 switch (tag[inst]) {
575 .constant, .const_ty => unreachable,
576 else => {
577 if (self.liveness.isUnused(inst)) {
578 assert(!self.live.contains(inst));
579 } else {
580 try self.live.putNoClobber(self.gpa, inst, {});
581 }
582 },
583 }
584}
585
586fn verifyMatchingLiveness(self: *Verify, block: Air.Inst.Index, live: LiveMap) Error!void {
587 if (self.live.count() != live.count()) return invalid("%{}: different deaths across branches", .{block});
588 var live_it = self.live.keyIterator();
589 while (live_it.next()) |live_inst| if (!live.contains(live_inst.*)) return invalid("%{}: different deaths across branches", .{block});
590}
591
592fn invalid(comptime fmt: []const u8, args: anytype) error{LivenessInvalid} {
593 log.err(fmt, args);
594 return error.LivenessInvalid;
595}
596
597const std = @import("std");
598const assert = std.debug.assert;
599const log = std.log.scoped(.liveness_verify);
600
601const Air = @import("../Air.zig");
602const Liveness = @import("../Liveness.zig");
603const Verify = @This();
src/Module.zig+33
...@@ -483,6 +483,8 @@ pub const Decl = struct {...@@ -483,6 +483,8 @@ pub const Decl = struct {
483 /// and attempting semantic analysis again may succeed.483 /// and attempting semantic analysis again may succeed.
484 sema_failure_retryable,484 sema_failure_retryable,
485 /// There will be a corresponding ErrorMsg in Module.failed_decls.485 /// There will be a corresponding ErrorMsg in Module.failed_decls.
486 liveness_failure,
487 /// There will be a corresponding ErrorMsg in Module.failed_decls.
486 codegen_failure,488 codegen_failure,
487 /// There will be a corresponding ErrorMsg in Module.failed_decls.489 /// There will be a corresponding ErrorMsg in Module.failed_decls.
488 /// This indicates the failure was something like running out of disk space,490 /// This indicates the failure was something like running out of disk space,
...@@ -4129,6 +4131,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {...@@ -4129,6 +4131,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
4129 .file_failure,4131 .file_failure,
4130 .sema_failure,4132 .sema_failure,
4131 .sema_failure_retryable,4133 .sema_failure_retryable,
4134 .liveness_failure,
4132 .codegen_failure,4135 .codegen_failure,
4133 .dependency_failure,4136 .dependency_failure,
4134 .codegen_failure_retryable,4137 .codegen_failure_retryable,
...@@ -4222,6 +4225,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {...@@ -4222,6 +4225,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void {
4222 .dependency_failure,4225 .dependency_failure,
4223 .sema_failure,4226 .sema_failure,
4224 .sema_failure_retryable,4227 .sema_failure_retryable,
4228 .liveness_failure,
4225 .codegen_failure,4229 .codegen_failure,
4226 .codegen_failure_retryable,4230 .codegen_failure_retryable,
4227 .complete,4231 .complete,
...@@ -4247,6 +4251,7 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func: *Fn) SemaError!void {...@@ -4247,6 +4251,7 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func: *Fn) SemaError!void {
42474251
4248 .file_failure,4252 .file_failure,
4249 .sema_failure,4253 .sema_failure,
4254 .liveness_failure,
4250 .codegen_failure,4255 .codegen_failure,
4251 .dependency_failure,4256 .dependency_failure,
4252 .sema_failure_retryable,4257 .sema_failure_retryable,
...@@ -4306,6 +4311,33 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func: *Fn) SemaError!void {...@@ -4306,6 +4311,33 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func: *Fn) SemaError!void {
4306 std.debug.print("# End Function AIR: {s}\n\n", .{fqn});4311 std.debug.print("# End Function AIR: {s}\n\n", .{fqn});
4307 }4312 }
43084313
4314 if (std.debug.runtime_safety) {
4315 var verify = Liveness.Verify{
4316 .gpa = gpa,
4317 .air = air,
4318 .liveness = liveness,
4319 };
4320 defer verify.deinit();
4321
4322 verify.verify() catch |err| switch (err) {
4323 error.OutOfMemory => return error.OutOfMemory,
4324 else => {
4325 try mod.failed_decls.ensureUnusedCapacity(gpa, 1);
4326 mod.failed_decls.putAssumeCapacityNoClobber(
4327 decl_index,
4328 try Module.ErrorMsg.create(
4329 gpa,
4330 decl.srcLoc(),
4331 "invalid liveness: {s}",
4332 .{@errorName(err)},
4333 ),
4334 );
4335 decl.analysis = .liveness_failure;
4336 return error.AnalysisFail;
4337 },
4338 };
4339 }
4340
4309 if (no_bin_file and !dump_llvm_ir) return;4341 if (no_bin_file and !dump_llvm_ir) return;
43104342
4311 comp.bin_file.updateFunc(mod, func, air, liveness) catch |err| switch (err) {4343 comp.bin_file.updateFunc(mod, func, air, liveness) catch |err| switch (err) {
...@@ -4349,6 +4381,7 @@ pub fn updateEmbedFile(mod: *Module, embed_file: *EmbedFile) SemaError!void {...@@ -4349,6 +4381,7 @@ pub fn updateEmbedFile(mod: *Module, embed_file: *EmbedFile) SemaError!void {
4349 .dependency_failure,4381 .dependency_failure,
4350 .sema_failure,4382 .sema_failure,
4351 .sema_failure_retryable,4383 .sema_failure_retryable,
4384 .liveness_failure,
4352 .codegen_failure,4385 .codegen_failure,
4353 .codegen_failure_retryable,4386 .codegen_failure_retryable,
4354 .complete,4387 .complete,