1/// Verifies that AIR is valid, in that every instruction has valid operands and types. In compiler
2/// builds with debug extensions, this is run on all AIR, both before `Air.Legalize` is run and (if
3/// it is run) after it.
4///
5/// This verification pass is currently highly incomplete---expand it as needed.
6const Verify = @This();
7
8zcu: *Zcu,
9func_index: InternPool.Index,
10ret_ty: Type,
11air: *const Air,
12cur_inst: Air.Inst.Index,
13
14pub fn run(pt: Zcu.PerThread, func_index: InternPool.Index, air: *const Air) void {
15 if (!@import("build_options").enable_debug_extensions) {
16 // `Air.Verify` is a debugging feature---it should not be used in release builds because it
17 // has little benefit and negatively affects compiler performance.
18 return;
19 }
20
21 const zcu = pt.zcu;
22
23 const func_ty: Type = Value.fromInterned(func_index).typeOf(zcu);
24 const ret_ty = func_ty.fnReturnType(zcu);
25
26 var verify: Verify = .{
27 .zcu = zcu,
28 .func_index = func_index,
29 .ret_ty = ret_ty,
30 .air = air,
31 .cur_inst = undefined, // populated by `body(...)`
32 };
33 verify.body(air.getMainBody()) catch |verify_err| switch (verify_err) {
34 error.VerifyFail => {
35 const ip = &zcu.intern_pool;
36 const func_nav = ip.indexToKey(func_index).func.owner_nav;
37 const func_fqn = ip.getNav(func_nav).fqn.toSlice(ip);
38 log.info("AIR for '{s}':", .{func_fqn});
39 const io = zcu.comp.io;
40 const stderr = io.lockStderr(&.{}, null) catch |err| switch (err) {
41 error.Canceled => return io.recancel(),
42 };
43 defer io.unlockStderr();
44 air.write(&stderr.file_writer.interface, pt, null) catch |err| switch (err) {
45 error.WriteFailed => switch (stderr.file_writer.err.?) {
46 error.Canceled => return io.recancel(),
47 else => {},
48 },
49 };
50 },
51 };
52}
53
54const Error = error{VerifyFail};
55
56fn fail(verify: *Verify, msg: []const u8) Error {
57 const ip = &verify.zcu.intern_pool;
58 const func_nav = ip.indexToKey(verify.func_index).func.owner_nav;
59 const func_fqn = ip.getNav(func_nav).fqn.toSlice(ip);
60 log.err("'{s}', %{d}: {s}", .{ func_fqn, verify.cur_inst, msg });
61 return error.VerifyFail;
62}
63
64fn body(verify: *Verify, body_insts: []const Air.Inst.Index) Error!void {
65 const zcu = verify.zcu;
66 const ip = &zcu.intern_pool;
67 const air = verify.air;
68 const tags = air.instructions.items(.tag);
69 const data = air.instructions.items(.data);
70 for (body_insts, 0..) |inst, body_index| {
71 verify.cur_inst = inst;
72 switch (tags[@backingInt(inst)]) {
73 .block => {
74 const block = air.unwrapBlock(inst);
75 try verify.body(block.body);
76 },
77 .dbg_inline_block => {
78 const block = air.unwrapDbgBlock(inst);
79 try verify.body(block.body);
80 },
81 .@"try", .try_cold => {
82 const @"try" = air.unwrapTry(inst);
83 try verify.body(@"try".else_body);
84 },
85 .try_ptr, .try_ptr_cold => {
86 const try_ptr = air.unwrapTryPtr(inst);
87 try verify.body(try_ptr.else_body);
88 },
89 .loop => {
90 const block = air.unwrapBlock(inst);
91 try verify.body(block.body);
92 },
93 .cond_br => {
94 const cond_br = air.unwrapCondBr(inst);
95 try verify.body(cond_br.then_body);
96 try verify.body(cond_br.else_body);
97 },
98 .switch_br, .loop_switch_br => {
99 const switch_br = air.unwrapSwitch(inst);
100 var it = switch_br.iterateCases();
101 while (it.next()) |case| {
102 try verify.body(case.body);
103 }
104 const else_body = it.elseBody();
105 if (else_body.len > 0) {
106 try verify.body(else_body);
107 }
108 },
109 .ret, .ret_safe => {
110 const operand = data[@backingInt(inst)].un_op;
111 if (air.typeOf(operand, ip).toIntern() != verify.ret_ty.toIntern()) return verify.fail("bad return type");
112 },
113 .ret_load => {
114 const operand = data[@backingInt(inst)].un_op;
115 const ptr_ty = air.typeOf(operand, ip);
116 if (ptr_ty.zigTypeTag(zcu) != .pointer) return verify.fail("operand is not a pointer");
117 if (ptr_ty.ptrSize(zcu) != .one) return verify.fail("pointer size is not '.one'");
118 if (ptr_ty.childType(zcu).toIntern() != verify.ret_ty.toIntern()) return verify.fail("bad return type");
119 },
120
121 .bit_cast, .bit_cast_safe => {
122 const ty_op = data[@backingInt(inst)].ty_op;
123 const operand_ty = air.typeOf(ty_op.operand, ip);
124 const result_ty = ty_op.ty;
125 // Enums are allowed here even if their backing type is implicit.
126 if (!operand_ty.hasBitRepresentation(zcu) and operand_ty.zigTypeTag(zcu) != .@"enum") {
127 return verify.fail("bad operand type");
128 }
129 if (!result_ty.hasBitRepresentation(zcu) and result_ty.zigTypeTag(zcu) != .@"enum") {
130 return verify.fail("bad result type");
131 }
132 if (operand_ty.isPtrAtRuntime(zcu)) return verify.fail("bad operand type (pointer)");
133 if (result_ty.isPtrAtRuntime(zcu)) return verify.fail("bad result type (pointer)");
134 if (operand_ty.bitSize(zcu) != result_ty.bitSize(zcu)) return verify.fail("bit size mismatch");
135 },
136 .ptr_cast => {
137 const ty_op = data[@backingInt(inst)].ty_op;
138 const operand_ty = air.typeOf(ty_op.operand, ip);
139 const result_ty = ty_op.ty;
140 const operand_scalar_ty = operand_ty.scalarType(zcu);
141 const result_scalar_ty = result_ty.scalarType(zcu);
142 if (operand_ty.isSliceAtRuntime(zcu)) {
143 if (!result_ty.isSliceAtRuntime(zcu)) return verify.fail("operand is slice, but result is not");
144 } else {
145 if (!operand_scalar_ty.isPtrAtRuntime(zcu)) return verify.fail("bad operand type");
146 if (!result_scalar_ty.isPtrAtRuntime(zcu)) return verify.fail("operand is pointer, but result is not");
147 if (operand_ty.isVector(zcu) and !result_ty.isVector(zcu)) return verify.fail("operand is vector, but result is not");
148 if (!operand_ty.isVector(zcu) and result_ty.isVector(zcu)) return verify.fail("result is vector, but operand is not");
149 }
150 if (operand_scalar_ty.ptrAddressSpace(zcu) != result_scalar_ty.ptrAddressSpace(zcu)) {
151 return verify.fail("illegal change to address space");
152 }
153 },
154 .ptr_from_int => {
155 const ty_op = data[@backingInt(inst)].ty_op;
156 const operand_ty = air.typeOf(ty_op.operand, ip);
157 const result_ty = ty_op.ty;
158 const operand_scalar_ty = operand_ty.scalarType(zcu);
159 const result_scalar_ty = result_ty.scalarType(zcu);
160 if (operand_scalar_ty.toIntern() != .usize_type) return verify.fail("bad operand type");
161 if (!result_scalar_ty.isPtrAtRuntime(zcu)) return verify.fail("bad result type");
162 if (operand_ty.isVector(zcu) and !result_ty.isVector(zcu)) return verify.fail("operand is vector, but result is not");
163 if (!operand_ty.isVector(zcu) and result_ty.isVector(zcu)) return verify.fail("result is vector, but operand is not");
164 },
165 .int_from_ptr => {
166 const ty_op = data[@backingInt(inst)].ty_op;
167 const operand_ty = air.typeOf(ty_op.operand, ip);
168 const result_ty = ty_op.ty;
169 const operand_scalar_ty = operand_ty.scalarType(zcu);
170 const result_scalar_ty = result_ty.scalarType(zcu);
171 if (!operand_scalar_ty.isPtrAtRuntime(zcu)) return verify.fail("bad operand type");
172 if (result_scalar_ty.toIntern() != .usize_type) return verify.fail("bad result type");
173 if (operand_ty.isVector(zcu) and !result_ty.isVector(zcu)) return verify.fail("operand is vector, but result is not");
174 if (!operand_ty.isVector(zcu) and result_ty.isVector(zcu)) return verify.fail("result is vector, but operand is not");
175 },
176 .error_cast => {
177 const ty_op = data[@backingInt(inst)].ty_op;
178 const operand_ty = air.typeOf(ty_op.operand, ip);
179 const result_ty = ty_op.ty;
180 switch (operand_ty.zigTypeTag(zcu)) {
181 else => return verify.fail("bad operand type"),
182 .error_union => {
183 if (result_ty.zigTypeTag(zcu) != .error_union) {
184 return verify.fail("operand is error union, but result is not");
185 }
186 if (operand_ty.errorUnionPayload(zcu).toIntern() != result_ty.errorUnionPayload(zcu).toIntern()) {
187 return verify.fail("error union payload type differs");
188 }
189 },
190 .error_set => if (result_ty.zigTypeTag(zcu) != .error_set) {
191 return verify.fail("operand is error set, but result is not");
192 },
193 }
194 },
195 .error_from_int => {
196 const ty_op = data[@backingInt(inst)].ty_op;
197 const operand_ty = air.typeOf(ty_op.operand, ip);
198 const result_ty = ty_op.ty;
199 if (!operand_ty.isUnsignedInt(zcu)) return verify.fail("bad operand type");
200 if (operand_ty.bitSize(zcu) != zcu.errorSetBits()) return verify.fail("bad operand bit size");
201 if (result_ty.zigTypeTag(zcu) != .error_set) return verify.fail("bad result type");
202 },
203 .int_from_error => {
204 const ty_op = data[@backingInt(inst)].ty_op;
205 const operand_ty = air.typeOf(ty_op.operand, ip);
206 const result_ty = ty_op.ty;
207 if (operand_ty.zigTypeTag(zcu) != .error_set) return verify.fail("bad operand type");
208 if (!result_ty.isUnsignedInt(zcu)) return verify.fail("bad result type");
209 if (result_ty.bitSize(zcu) != zcu.errorSetBits()) return verify.fail("bad result bit size");
210 },
211 .union_from_enum => {
212 const ty_op = data[@backingInt(inst)].ty_op;
213 const operand_ty = air.typeOf(ty_op.operand, ip);
214 const result_ty = ty_op.ty;
215 if (operand_ty.zigTypeTag(zcu) != .@"enum") return verify.fail("bad operand type");
216 if (result_ty.zigTypeTag(zcu) != .@"union") return verify.fail("bad result type");
217 const union_tag_ty = result_ty.unionTagType(zcu) orelse return verify.fail("union type is not tagged");
218 if (union_tag_ty.toIntern() != operand_ty.toIntern()) return verify.fail("union tag type does not match operand type");
219 },
220
221 .ptr_elem_ptr => {
222 const ty_pl = data[@backingInt(inst)].ty_pl;
223 const bin_op = air.extraData(Air.Bin, ty_pl.payload).data;
224 const ptr_ty = air.typeOf(bin_op.lhs, ip);
225 const result_ty = ty_pl.ty;
226 if (ptr_ty.zigTypeTag(zcu) != .pointer) return verify.fail("bad pointer type");
227 if (result_ty.zigTypeTag(zcu) != .pointer) return verify.fail("bad result type");
228 const ptr_info = ptr_ty.ptrInfo(zcu);
229 const result_ptr_info = result_ty.ptrInfo(zcu);
230 if (ptr_info.packed_offset.host_size != 0) return verify.fail("pointer type is bitpacked pointer");
231 if (result_ptr_info.packed_offset.host_size != 0) return verify.fail("result type is bitpacked pointer");
232 },
233
234 .arg,
235 .add,
236 .add_safe,
237 .add_optimized,
238 .add_wrap,
239 .add_sat,
240 .sub,
241 .sub_safe,
242 .sub_optimized,
243 .sub_wrap,
244 .sub_sat,
245 .mul,
246 .mul_safe,
247 .mul_optimized,
248 .mul_wrap,
249 .mul_sat,
250 .div_float,
251 .div_float_optimized,
252 .div_trunc,
253 .div_trunc_optimized,
254 .div_floor,
255 .div_floor_optimized,
256 .div_ceil,
257 .div_ceil_optimized,
258 .div_exact,
259 .div_exact_optimized,
260 .rem,
261 .rem_optimized,
262 .mod,
263 .mod_optimized,
264 .ptr_add,
265 .ptr_sub,
266 .max,
267 .min,
268 .add_with_overflow,
269 .sub_with_overflow,
270 .mul_with_overflow,
271 .shl_with_overflow,
272 .alloc,
273 .inferred_alloc,
274 .inferred_alloc_comptime,
275 .ret_ptr,
276 .assembly,
277 .bit_and,
278 .bit_or,
279 .shr,
280 .shr_exact,
281 .shl,
282 .shl_exact,
283 .shl_sat,
284 .xor,
285 .not,
286 .repeat,
287 .br,
288 .trap,
289 .breakpoint,
290 .ret_addr,
291 .frame_addr,
292 .call,
293 .call_always_tail,
294 .call_never_tail,
295 .call_never_inline,
296 .clz,
297 .ctz,
298 .popcount,
299 .byte_swap,
300 .bit_reverse,
301 .sqrt,
302 .sin,
303 .cos,
304 .tan,
305 .exp,
306 .exp2,
307 .log,
308 .log2,
309 .log10,
310 .abs,
311 .floor,
312 .ceil,
313 .round,
314 .trunc_float,
315 .neg,
316 .neg_optimized,
317 .cmp_lt,
318 .cmp_lt_optimized,
319 .cmp_lte,
320 .cmp_lte_optimized,
321 .cmp_eq,
322 .cmp_eq_optimized,
323 .cmp_gte,
324 .cmp_gte_optimized,
325 .cmp_gt,
326 .cmp_gt_optimized,
327 .cmp_neq,
328 .cmp_neq_optimized,
329 .cmp_vector,
330 .cmp_vector_optimized,
331 .switch_dispatch,
332 .dbg_stmt,
333 .dbg_empty_stmt,
334 .dbg_var_ptr,
335 .dbg_var_val,
336 .dbg_arg_inline,
337 .is_null,
338 .is_non_null,
339 .is_null_ptr,
340 .is_non_null_ptr,
341 .is_err,
342 .is_non_err,
343 .is_err_ptr,
344 .is_non_err_ptr,
345 .load,
346 .store,
347 .store_safe,
348 .unreach,
349 .fptrunc,
350 .fpext,
351 .int_cast,
352 .int_cast_safe,
353 .trunc,
354 .optional_payload,
355 .optional_payload_ptr,
356 .optional_payload_ptr_set,
357 .wrap_optional,
358 .unwrap_errunion_payload,
359 .unwrap_errunion_err,
360 .unwrap_errunion_payload_ptr,
361 .unwrap_errunion_err_ptr,
362 .errunion_payload_ptr_set,
363 .wrap_errunion_payload,
364 .wrap_errunion_err,
365 .struct_field_ptr,
366 .struct_field_ptr_index_0,
367 .struct_field_ptr_index_1,
368 .struct_field_ptr_index_2,
369 .struct_field_ptr_index_3,
370 .agg_field_val,
371 .set_union_tag,
372 .get_union_tag,
373 .slice,
374 .slice_len,
375 .slice_ptr,
376 .ptr_slice_len_ptr,
377 .ptr_slice_ptr_ptr,
378 .array_elem_val,
379 .slice_elem_val,
380 .slice_elem_ptr,
381 .ptr_elem_val,
382 .array_to_slice,
383 .array_to_vector,
384 .int_from_float,
385 .int_from_float_optimized,
386 .int_from_float_safe,
387 .int_from_float_optimized_safe,
388 .float_from_int,
389 .reduce,
390 .reduce_optimized,
391 .splat,
392 .shuffle_one,
393 .shuffle_two,
394 .select,
395 .memset,
396 .memset_safe,
397 .memcpy,
398 .memmove,
399 .cmpxchg_weak,
400 .cmpxchg_strong,
401 .atomic_load,
402 .atomic_store_unordered,
403 .atomic_store_monotonic,
404 .atomic_store_release,
405 .atomic_store_seq_cst,
406 .atomic_rmw,
407 .is_named_enum_value,
408 .tag_name,
409 .error_name,
410 .error_set_has_value,
411 .aggregate_init,
412 .union_init,
413 .prefetch,
414 .mul_add,
415 .field_parent_ptr,
416 .wasm_memory_size,
417 .wasm_memory_grow,
418 .cmp_lte_errors_len,
419 .err_return_trace,
420 .set_err_return_trace,
421 .addrspace_cast,
422 .save_err_return_trace_index,
423 .runtime_nav_ptr,
424 .c_va_arg,
425 .c_va_copy,
426 .c_va_end,
427 .c_va_start,
428 .spirv_runtime_array_len,
429 .work_item_id,
430 .work_group_size,
431 .work_group_id,
432 .legalize_vec_store_elem,
433 .legalize_vec_elem_val,
434 .legalize_compiler_rt_call,
435 => {},
436 }
437 if (air.typeOfIndex(inst, ip).isNoReturn(zcu)) {
438 if (body_index == body_insts.len - 1) return;
439
440 // HACK: right now, we emit the safety check for noreturn functions returning in a weird
441 // way, where the `call` instruction is `noreturn` but there are still instructions
442 // following it. We need to figure out a better way to represent that! That safety check
443 // probably just needs to live exclusively in backends; putting AIR instructions after a
444 // call implies that we have e.g. a valid stack at that point, which we can't actually
445 // assume when the user has gotten a function's ABI wrong.
446 switch (tags[@backingInt(inst)]) {
447 .call,
448 .call_always_tail,
449 .call_never_tail,
450 .call_never_inline,
451 => continue,
452 else => {},
453 }
454
455 return verify.fail("body contains instructions after noreturn");
456 }
457 }
458 return verify.fail("body does not terminate noreturn");
459}
460
461const std = @import("std");
462const log = std.log.scoped(.air_verify);
463
464const Zcu = @import("../Zcu.zig");
465const InternPool = @import("../InternPool.zig");
466const Air = @import("../Air.zig");
467const Type = @import("../Type.zig");
468const Value = @import("../Value.zig");