authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-09-16 13:14:38+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-09-23 12:36:44-07:00
logb30cd679878ab0fab7f1e1589c348a2477d542aa
tree15125b5a26e6b0d4d6393570373bbd0bd220c3c0
parent240f9d740d4d04619de6c6c7dbad46c78e38c831

spirv: put global var initializers in functions


2 files changed, 239 insertions(+), 113 deletions(-)

src/codegen/spirv.zig+234-113
......@@ -425,6 +425,7 @@ pub const DeclGen = struct {
425425 // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
426426 // For now, just initialize the struct by setting the fields manually...
427427 // TODO: Make this OpCompositeConstruct when we can
428 // TODO: Make this Function storage type
428429 const ptr_composite_id = try self.alloc(result_ty_ref, null);
429430 // Note: using 32-bit ints here because usize crashes the translator as well
430431 const index_ty_ref = try self.intType(.unsigned, 32);
......@@ -450,6 +451,40 @@ pub const DeclGen = struct {
450451 return result_id;
451452 }
452453
454 /// Construct a struct at runtime.
455 /// result_ty_ref must be an array type.
456 fn constructArray(self: *DeclGen, result_ty_ref: CacheRef, constituents: []const IdRef) !IdRef {
457 // The Khronos LLVM-SPIRV translator crashes because it cannot construct structs which'
458 // operands are not constant.
459 // See https://github.com/KhronosGroup/SPIRV-LLVM-Translator/issues/1349
460 // For now, just initialize the struct by setting the fields manually...
461 // TODO: Make this OpCompositeConstruct when we can
462 // TODO: Make this Function storage type
463 const ptr_composite_id = try self.alloc(result_ty_ref, null);
464 // Note: using 32-bit ints here because usize crashes the translator as well
465 const index_ty_ref = try self.intType(.unsigned, 32);
466
467 const spv_composite_ty = self.spv.cache.lookup(result_ty_ref).array_type;
468 const elem_ty_ref = spv_composite_ty.element_type;
469 const ptr_elem_ty_ref = try self.spv.ptrType(elem_ty_ref, .Generic);
470
471 for (constituents, 0..) |constitent_id, index| {
472 const index_id = try self.spv.constInt(index_ty_ref, index);
473 const ptr_id = try self.accessChain(ptr_elem_ty_ref, ptr_composite_id, &.{index_id});
474 try self.func.body.emit(self.spv.gpa, .OpStore, .{
475 .pointer = ptr_id,
476 .object = constitent_id,
477 });
478 }
479 const result_id = self.spv.allocId();
480 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
481 .id_result_type = self.typeId(result_ty_ref),
482 .id_result = result_id,
483 .pointer = ptr_composite_id,
484 });
485 return result_id;
486 }
487
453488 fn constructDeclRef(self: *DeclGen, ty: Type, decl_index: Decl.Index) !IdRef {
454489 const mod = self.module;
455490 const ty_ref = try self.resolveType(ty, .direct);
......@@ -459,10 +494,9 @@ pub const DeclGen = struct {
459494 switch (mod.intern_pool.indexToKey(decl.val.ip_index)) {
460495 .func => {
461496 // TODO: Properly lower function pointers. For now we are going to hack around it and
462 // just generate an empty pointer. Function pointers are represented by usize for now,
463 // though.
497 // just generate an empty pointer. Function pointers are represented by a pointer to usize.
464498 // TODO: Add dependency
465 return try self.spv.constInt(ty_ref, 0);
499 return try self.spv.constNull(ty_ref);
466500 },
467501 .extern_func => unreachable, // TODO
468502 else => {
......@@ -1074,7 +1108,7 @@ pub const DeclGen = struct {
10741108 else => {},
10751109 }
10761110
1077 log.debug("constant: ty = {}, val = {}", .{ ty.fmt(self.module), val.fmtValue(ty, self.module) });
1111 log.debug("constant: ty = {}, val = {}", .{ ty.fmt(mod), val.fmtValue(ty, mod) });
10781112 if (val.isUndefDeep(mod)) {
10791113 return self.spv.constUndef(result_ty_ref);
10801114 }
......@@ -1270,7 +1304,7 @@ pub const DeclGen = struct {
12701304 if (array_type.sentinel != .none) {
12711305 constituents[constituents.len - 1] = try self.constant(elem_ty, array_type.sentinel.toValue(), .indirect);
12721306 }
1273 return try self.constructStruct(result_ty_ref, constituents);
1307 return try self.constructArray(result_ty_ref, constituents);
12741308 },
12751309 .struct_type => {
12761310 const struct_ty = mod.typeToStruct(ty).?;
......@@ -1281,18 +1315,14 @@ pub const DeclGen = struct {
12811315 var constituents = std.ArrayList(IdRef).init(self.gpa);
12821316 defer constituents.deinit();
12831317
1284 for (struct_ty.fields.values(), 0..) |field, i| {
1285 if (field.is_comptime or !field.ty.hasRuntimeBits(mod)) continue;
1318 var field_it = struct_ty.runtimeFieldIterator(mod);
1319 while (field_it.next()) |field_and_index| {
1320 const field = field_and_index.field;
1321 const index = field_and_index.index;
1322 // TODO: Padding?
1323 const field_val = try val.fieldValue(mod, index);
1324 const field_id = try self.constant(field.ty, field_val, .indirect);
12861325
1287 const field_val = switch (aggregate.storage) {
1288 .bytes => |bytes| try ip.get(mod.gpa, .{ .int = .{
1289 .ty = field.ty.toIntern(),
1290 .storage = .{ .u64 = bytes[i] },
1291 } }),
1292 .elems => |elems| elems[i],
1293 .repeated_elem => |elem| elem,
1294 };
1295 const field_id = try self.constant(field.ty, field_val.toValue(), .indirect);
12961326 try constituents.append(field_id);
12971327 }
12981328
......@@ -1306,33 +1336,35 @@ pub const DeclGen = struct {
13061336 // type that has the right field active, then pointer-cast and store
13071337 // the active field, and finally load and return the entire union.
13081338
1309 const layout = ty.unionGetLayout(mod);
13101339 const union_ty = mod.typeToUnion(ty).?;
13111340
13121341 if (union_ty.getLayout(ip) == .Packed) {
13131342 return self.todo("packed union types", .{});
1314 } else if (layout.payload_size == 0) {
1343 }
1344
1345 const active_field = ty.unionTagFieldIndex(un.tag.toValue(), mod).?;
1346 const layout = self.unionLayout(ty, active_field);
1347
1348 if (layout.payload_size == 0) {
13151349 // No payload, so represent this as just the tag type.
13161350 return try self.constant(ty.unionTagTypeSafety(mod).?, un.tag.toValue(), .indirect);
13171351 }
13181352
1319 const has_tag = layout.tag_size != 0;
1320 const tag_first = layout.tag_align >= layout.payload_align;
1321
1322 const un_ptr_ty_ref = try self.spv.ptrType(result_ty_ref, .Function);
1353 const un_active_ty_ref = try self.resolveUnionType(ty, active_field);
1354 const un_active_ptr_ty_ref = try self.spv.ptrType(un_active_ty_ref, .Function);
1355 const un_general_ptr_ty_ref = try self.spv.ptrType(result_ty_ref, .Function);
13231356
13241357 const var_id = self.spv.allocId();
13251358 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{
1326 .id_result_type = self.typeId(un_ptr_ty_ref),
1359 .id_result_type = self.typeId(un_active_ptr_ty_ref),
13271360 .id_result = var_id,
13281361 .storage_class = .Function,
13291362 });
13301363
13311364 const index_ty_ref = try self.intType(.unsigned, 32);
13321365
1333 if (has_tag) {
1334 const tag_index: u32 = if (tag_first) 0 else 1;
1335 const index_id = try self.spv.constInt(index_ty_ref, tag_index);
1366 if (layout.tag_size != 0) {
1367 const index_id = try self.spv.constInt(index_ty_ref, @as(u32, @intCast(layout.tag_index)));
13361368 const tag_ty = ty.unionTagTypeSafety(mod).?;
13371369 const tag_ty_ref = try self.resolveType(tag_ty, .indirect);
13381370 const tag_ptr_ty_ref = try self.spv.ptrType(tag_ty_ref, .Function);
......@@ -1344,56 +1376,39 @@ pub const DeclGen = struct {
13441376 });
13451377 }
13461378
1347 const pl_index: u32 = if (tag_first) 1 else 0;
1348 const index_id = try self.spv.constInt(index_ty_ref, pl_index);
1349 const active_field = ty.unionTagFieldIndex(un.tag.toValue(), mod).?;
1350 const active_field_ty = union_ty.field_types.get(ip)[active_field].toType();
1351 const active_field_ty_ref = try self.resolveType(active_field_ty, .indirect);
1352 const active_field_ptr_ty_ref = try self.spv.ptrType(active_field_ty_ref, .Function);
1353 const ptr_id = try self.accessChain(active_field_ptr_ty_ref, var_id, &.{index_id});
1354 const value_id = try self.constant(active_field_ty, un.val.toValue(), .indirect);
1355 try self.func.body.emit(self.spv.gpa, .OpStore, .{
1356 .pointer = ptr_id,
1357 .object = value_id,
1358 });
1379 if (layout.active_field_size != 0) {
1380 const index_id = try self.spv.constInt(index_ty_ref, @as(u32, @intCast(layout.active_field_index)));
1381 const active_field_ty_ref = try self.resolveType(layout.active_field_ty, .indirect);
1382 const active_field_ptr_ty_ref = try self.spv.ptrType(active_field_ty_ref, .Function);
1383 const ptr_id = try self.accessChain(active_field_ptr_ty_ref, var_id, &.{index_id});
1384 const value_id = try self.constant(layout.active_field_ty, un.val.toValue(), .indirect);
1385 try self.func.body.emit(self.spv.gpa, .OpStore, .{
1386 .pointer = ptr_id,
1387 .object = value_id,
1388 });
1389 }
13591390
13601391 // Just leave the padding fields uninitialized...
1392 // TODO: Or should we initialize them with undef explicitly?
13611393
1362 const result_id = self.spv.allocId();
1363 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
1364 .id_result_type = self.typeId(result_ty_ref),
1365 .id_result = result_id,
1366 .pointer = var_id,
1394 // Now cast the pointer and load it as the 'generic' union type.
1395
1396 const casted_var_id = self.spv.allocId();
1397 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
1398 .id_result_type = self.typeId(un_general_ptr_ty_ref),
1399 .id_result = casted_var_id,
1400 .operand = var_id,
13671401 });
1368 return result_id;
1369 },
1370 else => {
1371 // The value cannot be generated directly, so generate it as an indirect constant,
1372 // and then perform an OpLoad.
1373 const result_id = self.spv.allocId();
1374 const alignment = ty.abiAlignment(mod);
1375 const spv_decl_index = try self.spv.allocDecl(.global);
1376
1377 try self.lowerIndirectConstant(
1378 spv_decl_index,
1379 ty,
1380 val,
1381 .UniformConstant,
1382 false,
1383 @intCast(alignment.toByteUnits(0)),
1384 );
1385 log.debug("indirect constant: index = {}", .{@intFromEnum(spv_decl_index)});
1386 try self.func.decl_deps.put(self.spv.gpa, spv_decl_index, {});
13871402
1403 const result_id = self.spv.allocId();
13881404 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
13891405 .id_result_type = self.typeId(result_ty_ref),
13901406 .id_result = result_id,
1391 .pointer = self.spv.declPtr(spv_decl_index).result_id,
1407 .pointer = casted_var_id,
13921408 });
1393 // TODO: Convert bools? This logic should hook into `load`. It should be a dead
1394 // path though considering .Bool is handled above.
13951409 return result_id;
13961410 },
1411 .memoized_call => unreachable,
13971412 }
13981413 }
13991414
......@@ -1458,72 +1473,61 @@ pub const DeclGen = struct {
14581473 fn resolveUnionType(self: *DeclGen, ty: Type, maybe_active_field: ?usize) !CacheRef {
14591474 const mod = self.module;
14601475 const ip = &mod.intern_pool;
1461 const layout = ty.unionGetLayout(mod);
14621476 const union_obj = mod.typeToUnion(ty).?;
14631477
14641478 if (union_obj.getLayout(ip) == .Packed) {
14651479 return self.todo("packed union types", .{});
14661480 }
14671481
1482 const layout = self.unionLayout(ty, maybe_active_field);
1483
14681484 if (layout.payload_size == 0) {
14691485 // No payload, so represent this as just the tag type.
14701486 return try self.resolveType(union_obj.enum_tag_ty.toType(), .indirect);
14711487 }
14721488
1473 const entry = try self.type_map.getOrPut(self.gpa, ty.toIntern());
1474 if (entry.found_existing) return entry.value_ptr.ty_ref;
1489 // TODO: We need to add the active field to the key.
1490 // const entry = try self.type_map.getOrPut(self.gpa, ty.toIntern());
1491 // if (entry.found_existing) return entry.value_ptr.ty_ref;
14751492
1476 var member_types = std.BoundedArray(CacheRef, 4){};
1477 var member_names = std.BoundedArray(CacheString, 4){};
1493 var member_types: [4]CacheRef = undefined;
1494 var member_names: [4]CacheString = undefined;
14781495
1479 const has_tag = layout.tag_size != 0;
1480 const tag_first = layout.tag_align.compare(.gte, layout.payload_align);
14811496 const u8_ty_ref = try self.intType(.unsigned, 8); // TODO: What if Int8Type is not enabled?
14821497
1483 if (has_tag and tag_first) {
1498 if (layout.tag_size != 0) {
14841499 const tag_ty_ref = try self.resolveType(union_obj.enum_tag_ty.toType(), .indirect);
1485 member_types.appendAssumeCapacity(tag_ty_ref);
1486 member_names.appendAssumeCapacity(try self.spv.resolveString("(tag)"));
1500 member_types[layout.tag_index] = tag_ty_ref;
1501 member_names[layout.tag_index] = try self.spv.resolveString("(tag)");
14871502 }
14881503
1489 const active_field = maybe_active_field orelse layout.most_aligned_field;
1490 const active_field_ty = union_obj.field_types.get(ip)[active_field].toType();
1491
1492 const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime(mod)) blk: {
1493 const active_payload_ty_ref = try self.resolveType(active_field_ty, .indirect);
1494 member_types.appendAssumeCapacity(active_payload_ty_ref);
1495 member_names.appendAssumeCapacity(try self.spv.resolveString("(payload)"));
1496 break :blk active_field_ty.abiSize(mod);
1497 } else 0;
1498
1499 const payload_padding_len = layout.payload_size - active_field_size;
1500 if (payload_padding_len != 0) {
1501 const payload_padding_ty_ref = try self.spv.arrayType(@as(u32, @intCast(payload_padding_len)), u8_ty_ref);
1502 member_types.appendAssumeCapacity(payload_padding_ty_ref);
1503 member_names.appendAssumeCapacity(try self.spv.resolveString("(payload padding)"));
1504 if (layout.active_field_size != 0) {
1505 const active_payload_ty_ref = try self.resolveType(layout.active_field_ty, .indirect);
1506 member_types[layout.active_field_index] = active_payload_ty_ref;
1507 member_names[layout.active_field_index] = try self.spv.resolveString("(payload)");
15041508 }
15051509
1506 if (has_tag and !tag_first) {
1507 const tag_ty_ref = try self.resolveType(union_obj.enum_tag_ty.toType(), .indirect);
1508 member_types.appendAssumeCapacity(tag_ty_ref);
1509 member_names.appendAssumeCapacity(try self.spv.resolveString("(tag)"));
1510 if (layout.payload_padding_size != 0) {
1511 const payload_padding_ty_ref = try self.spv.arrayType(@intCast(layout.payload_padding_size), u8_ty_ref);
1512 member_types[layout.payload_padding_index] = payload_padding_ty_ref;
1513 member_names[layout.payload_padding_index] = try self.spv.resolveString("(payload padding)");
15101514 }
15111515
1512 if (layout.padding != 0) {
1513 const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref);
1514 member_types.appendAssumeCapacity(padding_ty_ref);
1515 member_names.appendAssumeCapacity(try self.spv.resolveString("(padding)"));
1516 if (layout.padding_size != 0) {
1517 const padding_ty_ref = try self.spv.arrayType(@intCast(layout.padding_size), u8_ty_ref);
1518 member_types[layout.padding_index] = padding_ty_ref;
1519 member_names[layout.padding_index] = try self.spv.resolveString("(padding)");
15161520 }
15171521
15181522 const ty_ref = try self.spv.resolve(.{ .struct_type = .{
15191523 .name = try self.resolveTypeName(ty),
1520 .member_types = member_types.slice(),
1521 .member_names = member_names.slice(),
1524 .member_types = member_types[0..layout.total_fields],
1525 .member_names = member_names[0..layout.total_fields],
15221526 } });
15231527
1524 entry.value_ptr.* = .{
1525 .ty_ref = ty_ref,
1526 };
1528 // entry.value_ptr.* = .{
1529 // .ty_ref = ty_ref,
1530 // };
15271531
15281532 return ty_ref;
15291533 }
......@@ -1532,7 +1536,7 @@ pub const DeclGen = struct {
15321536 fn resolveType(self: *DeclGen, ty: Type, repr: Repr) Error!CacheRef {
15331537 const mod = self.module;
15341538 const ip = &mod.intern_pool;
1535 log.debug("resolveType: ty = {}", .{ty.fmt(self.module)});
1539 log.debug("resolveType: ty = {}", .{ty.fmt(mod)});
15361540 const target = self.getTarget();
15371541 switch (ty.zigTypeTag(mod)) {
15381542 .Void, .NoReturn => return try self.spv.resolve(.void_type),
......@@ -1854,6 +1858,85 @@ pub const DeclGen = struct {
18541858 };
18551859 }
18561860
1861 const UnionLayout = struct {
1862 active_field: usize,
1863 active_field_ty: Type,
1864 payload_size: usize,
1865
1866 tag_size: usize,
1867 tag_index: usize,
1868 active_field_size: usize,
1869 active_field_index: usize,
1870 payload_padding_size: usize,
1871 payload_padding_index: usize,
1872 padding_size: usize,
1873 padding_index: usize,
1874 total_fields: usize,
1875 };
1876
1877 fn unionLayout(self: *DeclGen, ty: Type, maybe_active_field: ?usize) UnionLayout {
1878 const mod = self.module;
1879 const ip = &mod.intern_pool;
1880 const layout = ty.unionGetLayout(self.module);
1881 const union_obj = mod.typeToUnion(ty).?;
1882
1883 const active_field = maybe_active_field orelse layout.most_aligned_field;
1884 const active_field_ty = union_obj.field_types.get(ip)[active_field].toType();
1885
1886 var union_layout = UnionLayout{
1887 .active_field = active_field,
1888 .active_field_ty = active_field_ty,
1889 .payload_size = layout.payload_size,
1890 .tag_size = layout.tag_size,
1891 .tag_index = undefined,
1892 .active_field_size = undefined,
1893 .active_field_index = undefined,
1894 .payload_padding_size = undefined,
1895 .payload_padding_index = undefined,
1896 .padding_size = layout.padding,
1897 .padding_index = undefined,
1898 .total_fields = undefined,
1899 };
1900
1901 union_layout.active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime(mod))
1902 active_field_ty.abiSize(mod)
1903 else
1904 0;
1905 union_layout.payload_padding_size = layout.payload_size - union_layout.active_field_size;
1906
1907 const tag_first = layout.tag_align.compare(.gte, layout.payload_align);
1908 var field_index: usize = 0;
1909
1910 if (union_layout.tag_size != 0 and tag_first) {
1911 union_layout.tag_index = field_index;
1912 field_index += 1;
1913 }
1914
1915 if (union_layout.active_field_size != 0) {
1916 union_layout.active_field_index = field_index;
1917 field_index += 1;
1918 }
1919
1920 if (union_layout.payload_padding_size != 0) {
1921 union_layout.payload_padding_index = field_index;
1922 field_index += 1;
1923 }
1924
1925 if (union_layout.tag_size != 0 and !tag_first) {
1926 union_layout.tag_index = field_index;
1927 field_index += 1;
1928 }
1929
1930 if (union_layout.padding_size != 0) {
1931 union_layout.padding_index = field_index;
1932 field_index += 1;
1933 }
1934
1935 union_layout.total_fields = field_index;
1936
1937 return union_layout;
1938 }
1939
18571940 /// The SPIR-V backend is not yet advanced enough to support the std testing infrastructure.
18581941 /// In order to be able to run tests, we "temporarily" lower test kernels into separate entry-
18591942 /// points. The test executor will then be able to invoke these to run the tests.
......@@ -1995,22 +2078,60 @@ pub const DeclGen = struct {
19952078 return self.todo("importing extern variables", .{});
19962079 }
19972080
1998 // TODO: integrate with variable().
2081 // Currently, initializers for CrossWorkgroup variables is not implemented
2082 // in Mesa. Therefore we generate an initialization kernel instead.
2083
2084 const void_ty_ref = try self.resolveType(Type.void, .direct);
2085
2086 const initializer_proto_ty_ref = try self.spv.resolve(.{ .function_type = .{
2087 .return_type = void_ty_ref,
2088 .parameters = &.{},
2089 } });
19992090
2091 // Generate the actual variable for the global...
20002092 const final_storage_class = spvStorageClass(decl.@"addrspace");
20012093 const actual_storage_class = switch (final_storage_class) {
20022094 .Generic => .CrossWorkgroup,
20032095 else => final_storage_class,
20042096 };
20052097
2006 try self.lowerIndirectConstant(
2007 spv_decl_index,
2008 decl.ty,
2009 init_val,
2010 actual_storage_class,
2011 final_storage_class == .Generic,
2012 @intCast(decl.alignment.toByteUnits(0)),
2013 );
2098 const ty_ref = try self.resolveType(decl.ty, .indirect);
2099 const ptr_ty_ref = try self.spv.ptrType(ty_ref, actual_storage_class);
2100
2101 const begin = self.spv.beginGlobal();
2102 try self.spv.globals.section.emit(self.spv.gpa, .OpVariable, .{
2103 .id_result_type = self.typeId(ptr_ty_ref),
2104 .id_result = decl_id,
2105 .storage_class = actual_storage_class,
2106 });
2107 // TODO: We should be able to get rid of this by now...
2108 self.spv.endGlobal(spv_decl_index, begin);
2109
2110 // Now emit the instructions that initialize the variable.
2111 const initializer_id = self.spv.allocId();
2112 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
2113 .id_result_type = self.typeId(void_ty_ref),
2114 .id_result = initializer_id,
2115 .function_control = .{},
2116 .function_type = self.typeId(initializer_proto_ty_ref),
2117 });
2118 const root_block_id = self.spv.allocId();
2119 try self.func.prologue.emit(self.spv.gpa, .OpLabel, .{
2120 .id_result = root_block_id,
2121 });
2122 self.current_block_label_id = root_block_id;
2123
2124 const val_id = try self.constant(decl.ty, init_val, .indirect);
2125 try self.func.body.emit(self.spv.gpa, .OpStore, .{
2126 .pointer = decl_id,
2127 .object = val_id,
2128 });
2129
2130 try self.func.body.emit(self.spv.gpa, .OpReturn, {});
2131 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
2132 try self.spv.addFunction(spv_decl_index, self.func);
2133
2134 try self.spv.initializers.append(self.spv.gpa, initializer_id);
20142135 }
20152136 }
20162137
src/codegen/spirv/Module.zig+5
......@@ -174,6 +174,9 @@ globals: struct {
174174 section: Section = .{},
175175} = .{},
176176
177/// The function IDs of global variable initializers
178initializers: std.ArrayListUnmanaged(IdRef) = .{},
179
177180pub fn init(gpa: Allocator, arena: Allocator) Module {
178181 return .{
179182 .gpa = gpa,
......@@ -202,6 +205,8 @@ pub fn deinit(self: *Module) void {
202205 self.globals.globals.deinit(self.gpa);
203206 self.globals.section.deinit(self.gpa);
204207
208 self.initializers.deinit(self.gpa);
209
205210 self.* = undefined;
206211}
207212