| 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. |
| 6 | const Verify = @This(); |
| 7 | |
| 8 | zcu: *Zcu, |
| 9 | func_index: InternPool.Index, |
| 10 | ret_ty: Type, |
| 11 | air: *const Air, |
| 12 | cur_inst: Air.Inst.Index, |
| 13 | |
| 14 | pub 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 | |
| 54 | const Error = error{VerifyFail}; |
| 55 | |
| 56 | fn 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 | |
| 64 | fn 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 | |
| 461 | const std = @import("std"); |
| 462 | const log = std.log.scoped(.air_verify); |
| 463 | |
| 464 | const Zcu = @import("../Zcu.zig"); |
| 465 | const InternPool = @import("../InternPool.zig"); |
| 466 | const Air = @import("../Air.zig"); |
| 467 | const Type = @import("../Type.zig"); |
| 468 | const Value = @import("../Value.zig"); |