| author | |
| committer | |
| log | 3f92eaceb61796254d0465ba5689378f15155791 |
| tree | 6ac76e438e7ef8823aa40b06cd7f793ddecee4ff |
| parent | dae8b4c11f6a59dc6ccd3e4fe327c43eb73c44cb |
| signature |
Implements type lowering for arrays and structs, and implements instruction
lowering for bitcast and call. Bitcast currently naively maps to the OpBitcast
instruction - this is only valid for some primitive types, and should be
improved to work with composites.4 files changed, 146 insertions(+), 28 deletions(-)
src/codegen/spirv.zig+104-1| ... | @@ -492,6 +492,21 @@ pub const DeclGen = struct { | ... | @@ -492,6 +492,21 @@ pub const DeclGen = struct { |
| 492 | 492 | ||
| 493 | return try self.spv.resolveType(SpvType.float(bits)); | 493 | return try self.spv.resolveType(SpvType.float(bits)); |
| 494 | }, | 494 | }, |
| 495 | .Array => { | ||
| 496 | const elem_ty = ty.childType(); | ||
| 497 | const total_len_u64 = ty.arrayLen() + @boolToInt(ty.sentinel() != null); | ||
| 498 | const total_len = std.math.cast(u32, total_len_u64) orelse { | ||
| 499 | return self.fail("array type of {} elements is too large", .{total_len_u64}); | ||
| 500 | }; | ||
| 501 | |||
| 502 | const payload = try self.spv.arena.create(SpvType.Payload.Array); | ||
| 503 | payload.* = .{ | ||
| 504 | .element_type = try self.resolveType(elem_ty), | ||
| 505 | .length = total_len, | ||
| 506 | .array_stride = @intCast(u32, ty.abiSize(target)), | ||
| 507 | }; | ||
| 508 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); | ||
| 509 | }, | ||
| 495 | .Fn => { | 510 | .Fn => { |
| 496 | // TODO: Put this somewhere in Sema.zig | 511 | // TODO: Put this somewhere in Sema.zig |
| 497 | if (ty.fnIsVarArgs()) | 512 | if (ty.fnIsVarArgs()) |
| ... | @@ -537,7 +552,37 @@ pub const DeclGen = struct { | ... | @@ -537,7 +552,37 @@ pub const DeclGen = struct { |
| 537 | }; | 552 | }; |
| 538 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); | 553 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); |
| 539 | }, | 554 | }, |
| 555 | .Struct => { | ||
| 556 | if (ty.isSimpleTupleOrAnonStruct()) { | ||
| 557 | return self.todo("implement tuple struct type", .{}); | ||
| 558 | } | ||
| 559 | |||
| 560 | const struct_ty = ty.castTag(.@"struct").?.data; | ||
| 561 | |||
| 562 | if (struct_ty.layout == .Packed) { | ||
| 563 | return try self.resolveType(struct_ty.backing_int_ty); | ||
| 564 | } | ||
| 565 | |||
| 566 | const members = try self.spv.arena.alloc(SpvType.Payload.Struct.Member, struct_ty.fields.count()); | ||
| 567 | var member_index: usize = 0; | ||
| 568 | for (struct_ty.fields.values()) |field| { | ||
| 569 | if (field.is_comptime or !field.ty.hasRuntimeBits()) continue; | ||
| 570 | |||
| 571 | members[member_index] = .{ | ||
| 572 | .ty = try self.resolveType(field.ty), | ||
| 573 | .offset = field.offset, | ||
| 574 | .decorations = .{}, | ||
| 575 | }; | ||
| 576 | } | ||
| 540 | 577 | ||
| 578 | const payload = try self.spv.arena.create(SpvType.Payload.Struct); | ||
| 579 | payload.* = .{ | ||
| 580 | .members = members[0..member_index], | ||
| 581 | .decorations = .{}, | ||
| 582 | .member_decoration_extra = &.{}, | ||
| 583 | }; | ||
| 584 | return try self.spv.resolveType(SpvType.initPayload(&payload.base)); | ||
| 585 | }, | ||
| 541 | .Null, | 586 | .Null, |
| 542 | .Undefined, | 587 | .Undefined, |
| 543 | .EnumLiteral, | 588 | .EnumLiteral, |
| ... | @@ -632,7 +677,7 @@ pub const DeclGen = struct { | ... | @@ -632,7 +677,7 @@ pub const DeclGen = struct { |
| 632 | .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), | 677 | .bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd), |
| 633 | .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), | 678 | .bool_or => try self.airBinOpSimple(inst, .OpLogicalOr), |
| 634 | 679 | ||
| 635 | .not => try self.airNot(inst), | 680 | .not => try self.airNot(inst), |
| 636 | 681 | ||
| 637 | .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), | 682 | .cmp_eq => try self.airCmp(inst, .OpFOrdEqual, .OpLogicalEqual, .OpIEqual), |
| 638 | .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), | 683 | .cmp_neq => try self.airCmp(inst, .OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual), |
| ... | @@ -646,6 +691,7 @@ pub const DeclGen = struct { | ... | @@ -646,6 +691,7 @@ pub const DeclGen = struct { |
| 646 | .block => (try self.airBlock(inst)) orelse return, | 691 | .block => (try self.airBlock(inst)) orelse return, |
| 647 | .load => try self.airLoad(inst), | 692 | .load => try self.airLoad(inst), |
| 648 | 693 | ||
| 694 | .bitcast => try self.airBitcast(inst), | ||
| 649 | .br => return self.airBr(inst), | 695 | .br => return self.airBr(inst), |
| 650 | .breakpoint => return, | 696 | .breakpoint => return, |
| 651 | .cond_br => return self.airCondBr(inst), | 697 | .cond_br => return self.airCondBr(inst), |
| ... | @@ -657,6 +703,11 @@ pub const DeclGen = struct { | ... | @@ -657,6 +703,11 @@ pub const DeclGen = struct { |
| 657 | .unreach => return self.airUnreach(), | 703 | .unreach => return self.airUnreach(), |
| 658 | .assembly => (try self.airAssembly(inst)) orelse return, | 704 | .assembly => (try self.airAssembly(inst)) orelse return, |
| 659 | 705 | ||
| 706 | .call => (try self.airCall(inst, .auto)) orelse return, | ||
| 707 | .call_always_tail => (try self.airCall(inst, .always_tail)) orelse return, | ||
| 708 | .call_never_tail => (try self.airCall(inst, .never_tail)) orelse return, | ||
| 709 | .call_never_inline => (try self.airCall(inst, .never_inline)) orelse return, | ||
| 710 | |||
| 660 | .dbg_var_ptr => return, | 711 | .dbg_var_ptr => return, |
| 661 | .dbg_var_val => return, | 712 | .dbg_var_val => return, |
| 662 | .dbg_block_begin => return, | 713 | .dbg_block_begin => return, |
| ... | @@ -911,6 +962,19 @@ pub const DeclGen = struct { | ... | @@ -911,6 +962,19 @@ pub const DeclGen = struct { |
| 911 | return result_id.toRef(); | 962 | return result_id.toRef(); |
| 912 | } | 963 | } |
| 913 | 964 | ||
| 965 | fn airBitcast(self: *DeclGen, inst: Air.Inst.Index) !IdRef { | ||
| 966 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; | ||
| 967 | const operand_id = try self.resolve(ty_op.operand); | ||
| 968 | const result_id = self.spv.allocId(); | ||
| 969 | const result_type_id = try self.resolveTypeId(Type.initTag(.bool)); | ||
| 970 | try self.func.body.emit(self.spv.gpa, .OpBitcast, .{ | ||
| 971 | .id_result_type = result_type_id, | ||
| 972 | .id_result = result_id, | ||
| 973 | .operand = operand_id, | ||
| 974 | }); | ||
| 975 | return result_id.toRef(); | ||
| 976 | } | ||
| 977 | |||
| 914 | fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void { | 978 | fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void { |
| 915 | const br = self.air.instructions.items(.data)[inst].br; | 979 | const br = self.air.instructions.items(.data)[inst].br; |
| 916 | const block = self.blocks.get(br.block_inst).?; | 980 | const block = self.blocks.get(br.block_inst).?; |
| ... | @@ -1158,4 +1222,43 @@ pub const DeclGen = struct { | ... | @@ -1158,4 +1222,43 @@ pub const DeclGen = struct { |
| 1158 | 1222 | ||
| 1159 | return null; | 1223 | return null; |
| 1160 | } | 1224 | } |
| 1225 | |||
| 1226 | fn airCall(self: *DeclGen, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.Modifier) !?IdRef { | ||
| 1227 | _ = modifier; | ||
| 1228 | |||
| 1229 | const pl_op = self.air.instructions.items(.data)[inst].pl_op; | ||
| 1230 | const extra = self.air.extraData(Air.Call, pl_op.payload); | ||
| 1231 | const args = @ptrCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]); | ||
| 1232 | const callee_ty = self.air.typeOf(pl_op.operand); | ||
| 1233 | const zig_fn_ty = switch (callee_ty.zigTypeTag()) { | ||
| 1234 | .Fn => callee_ty, | ||
| 1235 | .Pointer => return self.fail("cannot call function pointers", .{}), | ||
| 1236 | else => unreachable, | ||
| 1237 | }; | ||
| 1238 | const fn_info = zig_fn_ty.fnInfo(); | ||
| 1239 | const return_type = fn_info.return_type; | ||
| 1240 | |||
| 1241 | const result_type_id = try self.resolveTypeId(return_type); | ||
| 1242 | const result_id = self.spv.allocId(); | ||
| 1243 | const callee_id = try self.resolve(pl_op.operand); | ||
| 1244 | |||
| 1245 | try self.func.body.emitRaw(self.spv.gpa, .OpFunctionCall, 3 + args.len); | ||
| 1246 | self.func.body.writeOperand(spec.IdResultType, result_type_id); | ||
| 1247 | self.func.body.writeOperand(spec.IdResult, result_id); | ||
| 1248 | self.func.body.writeOperand(spec.IdRef, callee_id); | ||
| 1249 | |||
| 1250 | for (args) |arg| { | ||
| 1251 | const arg_id = try self.resolve(arg); | ||
| 1252 | const arg_ty = self.air.typeOf(arg); | ||
| 1253 | if (!arg_ty.hasRuntimeBitsIgnoreComptime()) continue; | ||
| 1254 | |||
| 1255 | self.func.body.writeOperand(spec.IdRef, arg_id); | ||
| 1256 | } | ||
| 1257 | |||
| 1258 | if (return_type.isNoReturn()) { | ||
| 1259 | try self.func.body.emit(self.spv.gpa, .OpUnreachable, {}); | ||
| 1260 | } | ||
| 1261 | |||
| 1262 | return result_id.toRef(); | ||
| 1263 | } | ||
| 1161 | }; | 1264 | }; |
src/codegen/spirv/Module.zig+14-3| ... | @@ -222,7 +222,7 @@ pub fn resolveType(self: *Module, ty: Type) !Type.Ref { | ... | @@ -222,7 +222,7 @@ pub fn resolveType(self: *Module, ty: Type) !Type.Ref { |
| 222 | return @intToEnum(Type.Ref, result.index); | 222 | return @intToEnum(Type.Ref, result.index); |
| 223 | } | 223 | } |
| 224 | 224 | ||
| 225 | pub fn resolveTypeId(self: *Module, ty: Type) !IdRef { | 225 | pub fn resolveTypeId(self: *Module, ty: Type) !IdResultType { |
| 226 | const type_ref = try self.resolveType(ty); | 226 | const type_ref = try self.resolveType(ty); |
| 227 | return self.typeResultId(type_ref); | 227 | return self.typeResultId(type_ref); |
| 228 | } | 228 | } |
| ... | @@ -243,7 +243,7 @@ pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { | ... | @@ -243,7 +243,7 @@ pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef { |
| 243 | /// Note: This function does not attempt to perform any validation on the type. | 243 | /// Note: This function does not attempt to perform any validation on the type. |
| 244 | /// The type is emitted in a shallow fashion; any child types should already | 244 | /// The type is emitted in a shallow fashion; any child types should already |
| 245 | /// be emitted at this point. | 245 | /// be emitted at this point. |
| 246 | pub fn emitType(self: *Module, ty: Type) !IdResultType { | 246 | pub fn emitType(self: *Module, ty: Type) error{OutOfMemory}!IdResultType { |
| 247 | const result_id = self.allocId(); | 247 | const result_id = self.allocId(); |
| 248 | const ref_id = result_id.toRef(); | 248 | const ref_id = result_id.toRef(); |
| 249 | const types = &self.sections.types_globals_constants; | 249 | const types = &self.sections.types_globals_constants; |
| ... | @@ -347,10 +347,21 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { | ... | @@ -347,10 +347,21 @@ pub fn emitType(self: *Module, ty: Type) !IdResultType { |
| 347 | .array => { | 347 | .array => { |
| 348 | const info = ty.payload(.array); | 348 | const info = ty.payload(.array); |
| 349 | assert(info.length != 0); | 349 | assert(info.length != 0); |
| 350 | |||
| 351 | const size_type = Type.initTag(.u32); | ||
| 352 | const size_type_id = try self.resolveTypeId(size_type); | ||
| 353 | |||
| 354 | const length_id = self.allocId(); | ||
| 355 | try types.emit(self.gpa, .OpConstant, .{ | ||
| 356 | .id_result_type = size_type_id, | ||
| 357 | .id_result = length_id, | ||
| 358 | .value = .{ .uint32 = info.length }, | ||
| 359 | }); | ||
| 360 | |||
| 350 | try types.emit(self.gpa, .OpTypeArray, .{ | 361 | try types.emit(self.gpa, .OpTypeArray, .{ |
| 351 | .id_result = result_id, | 362 | .id_result = result_id, |
| 352 | .element_type = self.typeResultId(ty.childType()).toRef(), | 363 | .element_type = self.typeResultId(ty.childType()).toRef(), |
| 353 | .length = .{ .id = 0 }, // TODO: info.length must be emitted as constant! | 364 | .length = length_id.toRef(), |
| 354 | }); | 365 | }); |
| 355 | if (info.array_stride != 0) { | 366 | if (info.array_stride != 0) { |
| 356 | try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); | 367 | try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } }); |
src/codegen/spirv/type.zig+26-24| ... | @@ -421,7 +421,7 @@ pub const Type = extern union { | ... | @@ -421,7 +421,7 @@ pub const Type = extern union { |
| 421 | length: u32, | 421 | length: u32, |
| 422 | /// Type has the 'ArrayStride' decoration. | 422 | /// Type has the 'ArrayStride' decoration. |
| 423 | /// If zero, no stride is present. | 423 | /// If zero, no stride is present. |
| 424 | array_stride: u32, | 424 | array_stride: u32 = 0, |
| 425 | }; | 425 | }; |
| 426 | 426 | ||
| 427 | pub const RuntimeArray = struct { | 427 | pub const RuntimeArray = struct { |
| ... | @@ -434,6 +434,7 @@ pub const Type = extern union { | ... | @@ -434,6 +434,7 @@ pub const Type = extern union { |
| 434 | 434 | ||
| 435 | pub const Struct = struct { | 435 | pub const Struct = struct { |
| 436 | base: Payload = .{ .tag = .@"struct" }, | 436 | base: Payload = .{ .tag = .@"struct" }, |
| 437 | // TODO: name | ||
| 437 | members: []Member, | 438 | members: []Member, |
| 438 | decorations: StructDecorations, | 439 | decorations: StructDecorations, |
| 439 | 440 | ||
| ... | @@ -444,20 +445,21 @@ pub const Type = extern union { | ... | @@ -444,20 +445,21 @@ pub const Type = extern union { |
| 444 | pub const Member = struct { | 445 | pub const Member = struct { |
| 445 | ty: Ref, | 446 | ty: Ref, |
| 446 | offset: u32, | 447 | offset: u32, |
| 448 | // TODO: name | ||
| 447 | decorations: MemberDecorations, | 449 | decorations: MemberDecorations, |
| 448 | }; | 450 | }; |
| 449 | 451 | ||
| 450 | pub const StructDecorations = packed struct { | 452 | pub const StructDecorations = packed struct { |
| 451 | /// Type has the 'Block' decoration. | 453 | /// Type has the 'Block' decoration. |
| 452 | block: bool, | 454 | block: bool = false, |
| 453 | /// Type has the 'BufferBlock' decoration. | 455 | /// Type has the 'BufferBlock' decoration. |
| 454 | buffer_block: bool, | 456 | buffer_block: bool = false, |
| 455 | /// Type has the 'GLSLShared' decoration. | 457 | /// Type has the 'GLSLShared' decoration. |
| 456 | glsl_shared: bool, | 458 | glsl_shared: bool = false, |
| 457 | /// Type has the 'GLSLPacked' decoration. | 459 | /// Type has the 'GLSLPacked' decoration. |
| 458 | glsl_packed: bool, | 460 | glsl_packed: bool = false, |
| 459 | /// Type has the 'CPacked' decoration. | 461 | /// Type has the 'CPacked' decoration. |
| 460 | c_packed: bool, | 462 | c_packed: bool = false, |
| 461 | }; | 463 | }; |
| 462 | 464 | ||
| 463 | pub const MemberDecorations = packed struct { | 465 | pub const MemberDecorations = packed struct { |
| ... | @@ -473,31 +475,31 @@ pub const Type = extern union { | ... | @@ -473,31 +475,31 @@ pub const Type = extern union { |
| 473 | col_major, | 475 | col_major, |
| 474 | /// Member is not a matrix or array of matrices. | 476 | /// Member is not a matrix or array of matrices. |
| 475 | none, | 477 | none, |
| 476 | }, | 478 | } = .none, |
| 477 | 479 | ||
| 478 | // Regular decorations, these do not imply extra fields. | 480 | // Regular decorations, these do not imply extra fields. |
| 479 | 481 | ||
| 480 | /// Member has the 'NoPerspective' decoration. | 482 | /// Member has the 'NoPerspective' decoration. |
| 481 | no_perspective: bool, | 483 | no_perspective: bool = false, |
| 482 | /// Member has the 'Flat' decoration. | 484 | /// Member has the 'Flat' decoration. |
| 483 | flat: bool, | 485 | flat: bool = false, |
| 484 | /// Member has the 'Patch' decoration. | 486 | /// Member has the 'Patch' decoration. |
| 485 | patch: bool, | 487 | patch: bool = false, |
| 486 | /// Member has the 'Centroid' decoration. | 488 | /// Member has the 'Centroid' decoration. |
| 487 | centroid: bool, | 489 | centroid: bool = false, |
| 488 | /// Member has the 'Sample' decoration. | 490 | /// Member has the 'Sample' decoration. |
| 489 | sample: bool, | 491 | sample: bool = false, |
| 490 | /// Member has the 'Invariant' decoration. | 492 | /// Member has the 'Invariant' decoration. |
| 491 | /// Note: requires parent struct to have 'Block'. | 493 | /// Note: requires parent struct to have 'Block'. |
| 492 | invariant: bool, | 494 | invariant: bool = false, |
| 493 | /// Member has the 'Volatile' decoration. | 495 | /// Member has the 'Volatile' decoration. |
| 494 | @"volatile": bool, | 496 | @"volatile": bool = false, |
| 495 | /// Member has the 'Coherent' decoration. | 497 | /// Member has the 'Coherent' decoration. |
| 496 | coherent: bool, | 498 | coherent: bool = false, |
| 497 | /// Member has the 'NonWritable' decoration. | 499 | /// Member has the 'NonWritable' decoration. |
| 498 | non_writable: bool, | 500 | non_writable: bool = false, |
| 499 | /// Member has the 'NonReadable' decoration. | 501 | /// Member has the 'NonReadable' decoration. |
| 500 | non_readable: bool, | 502 | non_readable: bool = false, |
| 501 | 503 | ||
| 502 | // The following decorations all imply extra field(s). | 504 | // The following decorations all imply extra field(s). |
| 503 | 505 | ||
| ... | @@ -506,27 +508,27 @@ pub const Type = extern union { | ... | @@ -506,27 +508,27 @@ pub const Type = extern union { |
| 506 | /// Note: If any member of a struct has the BuiltIn decoration, all members must have one. | 508 | /// Note: If any member of a struct has the BuiltIn decoration, all members must have one. |
| 507 | /// Note: Each builtin may only be reachable once for a particular entry point. | 509 | /// Note: Each builtin may only be reachable once for a particular entry point. |
| 508 | /// Note: The member type may be constrained by a particular built-in, defined in the client API specification. | 510 | /// Note: The member type may be constrained by a particular built-in, defined in the client API specification. |
| 509 | builtin: bool, | 511 | builtin: bool = false, |
| 510 | /// Member has the 'Stream' decoration. | 512 | /// Member has the 'Stream' decoration. |
| 511 | /// This member has an extra field of type `u32`. | 513 | /// This member has an extra field of type `u32`. |
| 512 | stream: bool, | 514 | stream: bool = false, |
| 513 | /// Member has the 'Location' decoration. | 515 | /// Member has the 'Location' decoration. |
| 514 | /// This member has an extra field of type `u32`. | 516 | /// This member has an extra field of type `u32`. |
| 515 | location: bool, | 517 | location: bool = false, |
| 516 | /// Member has the 'Component' decoration. | 518 | /// Member has the 'Component' decoration. |
| 517 | /// This member has an extra field of type `u32`. | 519 | /// This member has an extra field of type `u32`. |
| 518 | component: bool, | 520 | component: bool = false, |
| 519 | /// Member has the 'XfbBuffer' decoration. | 521 | /// Member has the 'XfbBuffer' decoration. |
| 520 | /// This member has an extra field of type `u32`. | 522 | /// This member has an extra field of type `u32`. |
| 521 | xfb_buffer: bool, | 523 | xfb_buffer: bool = false, |
| 522 | /// Member has the 'XfbStride' decoration. | 524 | /// Member has the 'XfbStride' decoration. |
| 523 | /// This member has an extra field of type `u32`. | 525 | /// This member has an extra field of type `u32`. |
| 524 | xfb_stride: bool, | 526 | xfb_stride: bool = false, |
| 525 | /// Member has the 'UserSemantic' decoration. | 527 | /// Member has the 'UserSemantic' decoration. |
| 526 | /// This member has an extra field of type `[]u8`, which is encoded | 528 | /// This member has an extra field of type `[]u8`, which is encoded |
| 527 | /// by an `u32` containing the number of chars exactly, and then the string padded to | 529 | /// by an `u32` containing the number of chars exactly, and then the string padded to |
| 528 | /// a multiple of 4 bytes with zeroes. | 530 | /// a multiple of 4 bytes with zeroes. |
| 529 | user_semantic: bool, | 531 | user_semantic: bool = false, |
| 530 | }; | 532 | }; |
| 531 | }; | 533 | }; |
| 532 | 534 |
src/link/SpirV.zig+2| ... | @@ -226,6 +226,8 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No | ... | @@ -226,6 +226,8 @@ pub fn flushModule(self: *SpirV, comp: *Compilation, prog_node: *std.Progress.No |
| 226 | const air = entry.value_ptr.air; | 226 | const air = entry.value_ptr.air; |
| 227 | const liveness = entry.value_ptr.liveness; | 227 | const liveness = entry.value_ptr.liveness; |
| 228 | 228 | ||
| 229 | log.debug("generating code for {s}", .{decl.name}); | ||
| 230 | |||
| 229 | // Note, if `decl` is not a function, air/liveness may be undefined. | 231 | // Note, if `decl` is not a function, air/liveness may be undefined. |
| 230 | if (try decl_gen.gen(decl_index, air, liveness)) |msg| { | 232 | if (try decl_gen.gen(decl_index, air, liveness)) |msg| { |
| 231 | try module.failed_decls.put(module.gpa, decl_index, msg); | 233 | try module.failed_decls.put(module.gpa, decl_index, msg); |