authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-08 00:55:18+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:51:53+02:00
log405f7298acaa4818a26fdc93991c48705c19de15
tree5aada5fd3997215cfbdb51f3dcb37225cf0e7bbf
parentefe7fae6afe1ecdfc3838a97651dc617c4c747c2
signaturelock-open Commit is signed but in an unrecognized format.

spirv: add decl dependencies for functions also

Entry points need to be attributed with a complete list of global variables that they use. To that end, the global dependencies mechanism is extended to also allow functions - when flushing the module, the list of dependencies is examined to generate this list of global variable result-ids.

2 files changed, 205 insertions(+), 150 deletions(-)

src/codegen/spirv.zig+108-89
...@@ -19,6 +19,7 @@ const Word = spec.Word;...@@ -19,6 +19,7 @@ const Word = spec.Word;
19const IdRef = spec.IdRef;19const IdRef = spec.IdRef;
20const IdResult = spec.IdResult;20const IdResult = spec.IdResult;
21const IdResultType = spec.IdResultType;21const IdResultType = spec.IdResultType;
22const StorageClass = spec.StorageClass;
2223
23const SpvModule = @import("spirv/Module.zig");24const SpvModule = @import("spirv/Module.zig");
24const SpvSection = @import("spirv/Section.zig");25const SpvSection = @import("spirv/Section.zig");
...@@ -37,23 +38,8 @@ const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {...@@ -37,23 +38,8 @@ const BlockMap = std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
37 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),38 incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),
38});39});
3940
40/// Linking information about a particular decl.
41/// The active field of this enum depends on the type of the corresponding decl.
42const DeclLink = union {
43 /// Linking information about a function.
44 /// Active when the decl is a function.
45 func: struct {
46 /// Result-id of the OpFunction instruction.
47 result_id: IdResult,
48 },
49 /// Linking information about a global. This index points into the
50 /// SPIR-V module's `globals` array.
51 /// Active when the decl is a variable.
52 global: SpvModule.Global.Index,
53};
54
55/// Maps Zig decl indices to linking SPIR-V linking information.41/// Maps Zig decl indices to linking SPIR-V linking information.
56pub const DeclLinkMap = std.AutoHashMap(Module.Decl.Index, DeclLink);42pub const DeclLinkMap = std.AutoHashMap(Module.Decl.Index, SpvModule.Decl.Index);
5743
58/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.44/// This structure is used to compile a declaration, and contains all relevant meta-information to deal with that.
59pub const DeclGen = struct {45pub const DeclGen = struct {
...@@ -251,8 +237,8 @@ pub const DeclGen = struct {...@@ -251,8 +237,8 @@ pub const DeclGen = struct {
251 .function => val.castTag(.function).?.data.owner_decl,237 .function => val.castTag(.function).?.data.owner_decl,
252 else => unreachable,238 else => unreachable,
253 };239 };
254 const link = try self.resolveDecl(fn_decl_index);240 const spv_decl_index = try self.resolveDecl(fn_decl_index);
255 return link.func.result_id;241 return self.spv.declPtr(spv_decl_index).result_id;
256 }242 }
257243
258 return try self.constant(ty, val);244 return try self.constant(ty, val);
...@@ -263,19 +249,19 @@ pub const DeclGen = struct {...@@ -263,19 +249,19 @@ pub const DeclGen = struct {
263249
264 /// Fetch or allocate a result id for decl index. This function also marks the decl as alive.250 /// Fetch or allocate a result id for decl index. This function also marks the decl as alive.
265 /// Note: Function does not actually generate the decl.251 /// Note: Function does not actually generate the decl.
266 fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !DeclLink {252 fn resolveDecl(self: *DeclGen, decl_index: Module.Decl.Index) !SpvModule.Decl.Index {
267 const decl = self.module.declPtr(decl_index);253 const decl = self.module.declPtr(decl_index);
268 self.module.markDeclAlive(decl);254 self.module.markDeclAlive(decl);
269255
270 const entry = try self.decl_link.getOrPut(decl_index);256 const entry = try self.decl_link.getOrPut(decl_index);
271 const result_id = self.spv.allocId();
272
273 if (!entry.found_existing) {257 if (!entry.found_existing) {
274 if (decl.val.castTag(.function)) |_| {258 // TODO: Extern fn?
275 entry.value_ptr.* = .{ .func = .{ .result_id = result_id } };259 const kind: SpvModule.DeclKind = if (decl.val.tag() == .function)
276 } else {260 .func
277 entry.value_ptr.* = .{ .global = try self.spv.allocGlobal() };261 else
278 }262 .global;
263
264 entry.value_ptr.* = try self.spv.allocDecl(kind);
279 }265 }
280266
281 return entry.value_ptr.*;267 return entry.value_ptr.*;
...@@ -440,6 +426,8 @@ pub const DeclGen = struct {...@@ -440,6 +426,8 @@ pub const DeclGen = struct {
440 /// The partially filled last constant.426 /// The partially filled last constant.
441 /// If full, its flushed.427 /// If full, its flushed.
442 partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{},428 partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{},
429 /// The declaration dependencies of the constant we are lowering.
430 decl_deps: std.ArrayList(SpvModule.Decl.Index),
443431
444 /// Utility function to get the section that instructions should be lowered to.432 /// Utility function to get the section that instructions should be lowered to.
445 fn section(self: *@This()) *SpvSection {433 fn section(self: *@This()) *SpvSection {
...@@ -554,7 +542,7 @@ pub const DeclGen = struct {...@@ -554,7 +542,7 @@ pub const DeclGen = struct {
554 const ty_id = dg.typeId(ty_ref);542 const ty_id = dg.typeId(ty_ref);
555543
556 const decl = dg.module.declPtr(decl_index);544 const decl = dg.module.declPtr(decl_index);
557 const link = try dg.resolveDecl(decl_index);545 const spv_decl_index = try dg.resolveDecl(decl_index);
558546
559 switch (decl.val.tag()) {547 switch (decl.val.tag()) {
560 .function => {548 .function => {
...@@ -569,14 +557,15 @@ pub const DeclGen = struct {...@@ -569,14 +557,15 @@ pub const DeclGen = struct {
569 const result_id = dg.spv.allocId();557 const result_id = dg.spv.allocId();
570 log.debug("addDeclRef {s} = {}", .{ decl.name, result_id.id });558 log.debug("addDeclRef {s} = {}", .{ decl.name, result_id.id });
571559
572 const global = dg.spv.globalPtr(link.global);560 try self.decl_deps.append(spv_decl_index);
573 try dg.spv.addGlobalDependency(link.global);561
562 const decl_id = dg.spv.declPtr(spv_decl_index).result_id;
574 // TODO: Do we need a storage class cast here?563 // TODO: Do we need a storage class cast here?
575 // TODO: We can probably eliminate these casts564 // TODO: We can probably eliminate these casts
576 try dg.spv.globals.section.emitSpecConstantOp(dg.spv.gpa, .OpBitcast, .{565 try dg.spv.globals.section.emitSpecConstantOp(dg.spv.gpa, .OpBitcast, .{
577 .id_result_type = ty_id,566 .id_result_type = ty_id,
578 .id_result = result_id,567 .id_result = result_id,
579 .operand = global.result_id,568 .operand = decl_id,
580 });569 });
581570
582 try self.addPtr(ty_ref, result_id);571 try self.addPtr(ty_ref, result_id);
...@@ -810,10 +799,11 @@ pub const DeclGen = struct {...@@ -810,10 +799,11 @@ pub const DeclGen = struct {
810 /// pointer points to. Note: result is not necessarily an OpVariable instruction!799 /// pointer points to. Note: result is not necessarily an OpVariable instruction!
811 fn lowerIndirectConstant(800 fn lowerIndirectConstant(
812 self: *DeclGen,801 self: *DeclGen,
813 result_id: IdRef,802 spv_decl_index: SpvModule.Decl.Index,
814 ty: Type,803 ty: Type,
815 val: Value,804 val: Value,
816 storage_class: spec.StorageClass,805 storage_class: StorageClass,
806 cast_to_generic: bool,
817 alignment: u32,807 alignment: u32,
818 ) Error!void {808 ) Error!void {
819 // To simplify constant generation, we're going to generate constants as a word-array, and809 // To simplify constant generation, we're going to generate constants as a word-array, and
...@@ -844,23 +834,27 @@ pub const DeclGen = struct {...@@ -844,23 +834,27 @@ pub const DeclGen = struct {
844 const ty_ref = try self.resolveType(ty, .indirect);834 const ty_ref = try self.resolveType(ty, .indirect);
845 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, alignment);835 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, alignment);
846836
847 const target = self.getTarget();837 // const target = self.getTarget();
848838
849 if (val.isUndef()) {839 // TODO: Fix the resulting global linking for these paths.
850 // Special case: the entire value is undefined. In this case, we can just840 // if (val.isUndef()) {
851 // generate an OpVariable with no initializer.841 // // Special case: the entire value is undefined. In this case, we can just
852 return try section.emit(self.spv.gpa, .OpVariable, .{842 // // generate an OpVariable with no initializer.
853 .id_result_type = self.typeId(ptr_ty_ref),843 // return try section.emit(self.spv.gpa, .OpVariable, .{
854 .id_result = result_id,844 // .id_result_type = self.typeId(ptr_ty_ref),
855 .storage_class = storage_class,845 // .id_result = result_id,
856 });846 // .storage_class = storage_class,
857 } else if (ty.abiSize(target) == 0) {847 // });
858 // Special case: if the type has no size, then return an undefined pointer.848 // } else if (ty.abiSize(target) == 0) {
859 return try section.emit(self.spv.gpa, .OpUndef, .{849 // // Special case: if the type has no size, then return an undefined pointer.
860 .id_result_type = self.typeId(ptr_ty_ref),850 // return try section.emit(self.spv.gpa, .OpUndef, .{
861 .id_result = result_id,851 // .id_result_type = self.typeId(ptr_ty_ref),
862 });852 // .id_result = result_id,
863 }853 // });
854 // }
855
856 // TODO: Capture the above stuff in here as well...
857 const begin_inst = self.spv.beginGlobal();
864858
865 const u32_ty_ref = try self.intType(.unsigned, 32);859 const u32_ty_ref = try self.intType(.unsigned, 32);
866 var icl = IndirectConstantLowering{860 var icl = IndirectConstantLowering{
...@@ -869,10 +863,12 @@ pub const DeclGen = struct {...@@ -869,10 +863,12 @@ pub const DeclGen = struct {
869 .u32_ty_id = self.typeId(u32_ty_ref),863 .u32_ty_id = self.typeId(u32_ty_ref),
870 .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa),864 .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa),
871 .initializers = std.ArrayList(IdRef).init(self.gpa),865 .initializers = std.ArrayList(IdRef).init(self.gpa),
866 .decl_deps = std.ArrayList(SpvModule.Decl.Index).init(self.gpa),
872 };867 };
873868
874 defer icl.members.deinit();869 defer icl.members.deinit();
875 defer icl.initializers.deinit();870 defer icl.initializers.deinit();
871 defer icl.decl_deps.deinit();
876872
877 try icl.lower(ty, val);873 try icl.lower(ty, val);
878 try icl.flush();874 try icl.flush();
...@@ -888,6 +884,7 @@ pub const DeclGen = struct {...@@ -888,6 +884,7 @@ pub const DeclGen = struct {
888 });884 });
889885
890 const var_id = self.spv.allocId();886 const var_id = self.spv.allocId();
887 self.spv.globalPtr(spv_decl_index).?.result_id = var_id;
891 try section.emit(self.spv.gpa, .OpVariable, .{888 try section.emit(self.spv.gpa, .OpVariable, .{
892 .id_result_type = self.typeId(ptr_constant_struct_ty_ref),889 .id_result_type = self.typeId(ptr_constant_struct_ty_ref),
893 .id_result = var_id,890 .id_result = var_id,
...@@ -896,12 +893,32 @@ pub const DeclGen = struct {...@@ -896,12 +893,32 @@ pub const DeclGen = struct {
896 });893 });
897 // TODO: Set alignment of OpVariable.894 // TODO: Set alignment of OpVariable.
898 // TODO: We may be able to eliminate these casts.895 // TODO: We may be able to eliminate these casts.
896
899 const const_ptr_id = try self.makePointerConstant(section, ptr_constant_struct_ty_ref, var_id);897 const const_ptr_id = try self.makePointerConstant(section, ptr_constant_struct_ty_ref, var_id);
898 const result_id = self.spv.declPtr(spv_decl_index).result_id;
899
900 const bitcast_result_id = if (cast_to_generic)
901 self.spv.allocId()
902 else
903 result_id;
904
900 try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{905 try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{
901 .id_result_type = self.typeId(ptr_ty_ref),906 .id_result_type = self.typeId(ptr_ty_ref),
902 .id_result = result_id,907 .id_result = bitcast_result_id,
903 .operand = const_ptr_id,908 .operand = const_ptr_id,
904 });909 });
910
911 if (cast_to_generic) {
912 const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic, alignment);
913 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
914 .id_result_type = self.typeId(generic_ptr_ty_ref),
915 .id_result = result_id,
916 .pointer = bitcast_result_id,
917 });
918 }
919
920 try self.spv.declareDeclDeps(spv_decl_index, icl.decl_deps.items);
921 self.spv.endGlobal(spv_decl_index, begin_inst);
905 }922 }
906923
907 /// This function generates a load for a constant in direct (ie, non-memory) representation.924 /// This function generates a load for a constant in direct (ie, non-memory) representation.
...@@ -940,19 +957,28 @@ pub const DeclGen = struct {...@@ -940,19 +957,28 @@ pub const DeclGen = struct {
940 try section.emit(self.spv.gpa, .OpConstantFalse, operands);957 try section.emit(self.spv.gpa, .OpConstantFalse, operands);
941 }958 }
942 },959 },
960 // TODO: We can handle most pointers here (decl refs etc), because now they emit an extra
961 // OpVariable that is not really required.
943 else => {962 else => {
944 // The value cannot be generated directly, so generate it as an indirect constant,963 // The value cannot be generated directly, so generate it as an indirect constant,
945 // and then perform an OpLoad.964 // and then perform an OpLoad.
946 const alignment = ty.abiAlignment(target);965 const alignment = ty.abiAlignment(target);
947 const global_index = try self.spv.allocGlobal();966 const spv_decl_index = try self.spv.allocDecl(.global);
948 log.debug("constant {}", .{global_index});967
949 const ptr_id = self.spv.beginGlobal(global_index);968 try self.lowerIndirectConstant(
950 defer self.spv.endGlobal();969 spv_decl_index,
951 try self.lowerIndirectConstant(ptr_id, ty, val, .UniformConstant, alignment);970 ty,
971 val,
972 .UniformConstant,
973 false,
974 alignment,
975 );
976 try self.func.decl_deps.append(self.spv.gpa, spv_decl_index);
977
952 try self.func.body.emit(self.spv.gpa, .OpLoad, .{978 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
953 .id_result_type = result_ty_id,979 .id_result_type = result_ty_id,
954 .id_result = result_id,980 .id_result = result_id,
955 .pointer = ptr_id,981 .pointer = self.spv.declPtr(spv_decl_index).result_id,
956 });982 });
957 // TODO: Convert bools? This logic should hook into `load`. It should be a dead983 // TODO: Convert bools? This logic should hook into `load`. It should be a dead
958 // path though considering .Bool is handled above.984 // path though considering .Bool is handled above.
...@@ -1289,7 +1315,7 @@ pub const DeclGen = struct {...@@ -1289,7 +1315,7 @@ pub const DeclGen = struct {
1289 }1315 }
1290 }1316 }
12911317
1292 fn spvStorageClass(as: std.builtin.AddressSpace) spec.StorageClass {1318 fn spvStorageClass(as: std.builtin.AddressSpace) StorageClass {
1293 return switch (as) {1319 return switch (as) {
1294 .generic => .Generic, // TODO: Disallow?1320 .generic => .Generic, // TODO: Disallow?
1295 .gs, .fs, .ss => unreachable,1321 .gs, .fs, .ss => unreachable,
...@@ -1370,16 +1396,17 @@ pub const DeclGen = struct {...@@ -1370,16 +1396,17 @@ pub const DeclGen = struct {
13701396
1371 fn genDecl(self: *DeclGen) !void {1397 fn genDecl(self: *DeclGen) !void {
1372 const decl = self.module.declPtr(self.decl_index);1398 const decl = self.module.declPtr(self.decl_index);
1373 const link = try self.resolveDecl(self.decl_index);1399 const spv_decl_index = try self.resolveDecl(self.decl_index);
13741400
1375 if (decl.val.castTag(.function)) |_| {1401 const decl_id = self.spv.declPtr(spv_decl_index).result_id;
1376 log.debug("genDecl function {s} = {}", .{ decl.name, link.func.result_id.id });1402 log.debug("genDecl {s} = {}", .{ decl.name, decl_id });
13771403
1404 if (decl.val.castTag(.function)) |_| {
1378 assert(decl.ty.zigTypeTag() == .Fn);1405 assert(decl.ty.zigTypeTag() == .Fn);
1379 const prototype_id = try self.resolveTypeId(decl.ty);1406 const prototype_id = try self.resolveTypeId(decl.ty);
1380 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{1407 try self.func.prologue.emit(self.spv.gpa, .OpFunction, .{
1381 .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()),1408 .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()),
1382 .id_result = link.func.result_id,1409 .id_result = decl_id,
1383 .function_control = .{}, // TODO: We can set inline here if the type requires it.1410 .function_control = .{}, // TODO: We can set inline here if the type requires it.
1384 .function_type = prototype_id,1411 .function_type = prototype_id,
1385 });1412 });
...@@ -1413,18 +1440,18 @@ pub const DeclGen = struct {...@@ -1413,18 +1440,18 @@ pub const DeclGen = struct {
14131440
1414 // Append the actual code into the functions section.1441 // Append the actual code into the functions section.
1415 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});1442 try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {});
1416 try self.spv.addFunction(self.func);1443 try self.spv.addFunction(spv_decl_index, self.func);
14171444
1418 const fqn = try decl.getFullyQualifiedName(self.module);1445 const fqn = try decl.getFullyQualifiedName(self.module);
1419 defer self.module.gpa.free(fqn);1446 defer self.module.gpa.free(fqn);
14201447
1421 try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{1448 try self.spv.sections.debug_names.emit(self.gpa, .OpName, .{
1422 .target = link.func.result_id,1449 .target = decl_id,
1423 .name = fqn,1450 .name = fqn,
1424 });1451 });
14251452
1426 if (self.module.test_functions.contains(self.decl_index)) {1453 if (self.module.test_functions.contains(self.decl_index)) {
1427 try self.generateTestEntryPoint(fqn, link.func.result_id);1454 try self.generateTestEntryPoint(fqn, decl_id);
1428 }1455 }
1429 } else {1456 } else {
1430 const init_val = if (decl.val.castTag(.variable)) |payload|1457 const init_val = if (decl.val.castTag(.variable)) |payload|
...@@ -1438,41 +1465,33 @@ pub const DeclGen = struct {...@@ -1438,41 +1465,33 @@ pub const DeclGen = struct {
14381465
1439 // TODO: integrate with variable().1466 // TODO: integrate with variable().
14401467
1441 const storage_class = spvStorageClass(decl.@"addrspace");1468 const final_storage_class = spvStorageClass(decl.@"addrspace");
1442 const actual_storage_class = switch (storage_class) {1469 const actual_storage_class = switch (final_storage_class) {
1443 .Generic => .CrossWorkgroup,1470 .Generic => .CrossWorkgroup,
1444 else => storage_class,1471 else => final_storage_class,
1445 };
1446
1447 const global_result_id = self.spv.beginGlobal(link.global);
1448 defer self.spv.endGlobal();
1449 log.debug("genDecl {}", .{link.global});
1450
1451 const var_result_id = switch (storage_class) {
1452 .Generic => self.spv.allocId(),
1453 else => global_result_id,
1454 };1472 };
14551473
1456 try self.lowerIndirectConstant(1474 try self.lowerIndirectConstant(
1457 var_result_id,1475 spv_decl_index,
1458 decl.ty,1476 decl.ty,
1459 init_val,1477 init_val,
1460 actual_storage_class,1478 actual_storage_class,
1479 final_storage_class == .Generic,
1461 decl.@"align",1480 decl.@"align",
1462 );1481 );
14631482
1464 if (storage_class == .Generic) {1483 // if (storage_class == .Generic) {
1465 const section = &self.spv.globals.section;1484 // const section = &self.spv.globals.section;
1466 const ty_ref = try self.resolveType(decl.ty, .indirect);1485 // const ty_ref = try self.resolveType(decl.ty, .indirect);
1467 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align");1486 // const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align");
1468 // TODO: Can we eliminate this cast?1487 // // TODO: Can we eliminate this cast?
1469 // TODO: Const-wash pointer1488 // // TODO: Const-wash pointer?
1470 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{1489 // try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
1471 .id_result_type = self.typeId(ptr_ty_ref),1490 // .id_result_type = self.typeId(ptr_ty_ref),
1472 .id_result = global_result_id,1491 // .id_result = global_result_id,
1473 .pointer = var_result_id,1492 // .pointer = casted_result_id,
1474 });1493 // });
1475 }1494 // }
1476 }1495 }
1477 }1496 }
14781497
src/codegen/spirv/Module.zig+97-61
...@@ -39,41 +39,58 @@ pub const Fn = struct {...@@ -39,41 +39,58 @@ pub const Fn = struct {
39 /// This section should also contain the OpFunctionEnd instruction marking39 /// This section should also contain the OpFunctionEnd instruction marking
40 /// the end of this function definition.40 /// the end of this function definition.
41 body: Section = .{},41 body: Section = .{},
42 /// The decl dependencies that this function depends on.
43 decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{},
4244
43 /// Reset this function without deallocating resources, so that45 /// Reset this function without deallocating resources, so that
44 /// it may be used to emit code for another function.46 /// it may be used to emit code for another function.
45 pub fn reset(self: *Fn) void {47 pub fn reset(self: *Fn) void {
46 self.prologue.reset();48 self.prologue.reset();
47 self.body.reset();49 self.body.reset();
50 self.decl_deps.items.len = 0;
48 }51 }
4952
50 /// Free the resources owned by this function.53 /// Free the resources owned by this function.
51 pub fn deinit(self: *Fn, a: Allocator) void {54 pub fn deinit(self: *Fn, a: Allocator) void {
52 self.prologue.deinit(a);55 self.prologue.deinit(a);
53 self.body.deinit(a);56 self.body.deinit(a);
57 self.decl_deps.deinit(a);
54 self.* = undefined;58 self.* = undefined;
55 }59 }
56};60};
5761
62/// Declarations, both functions and globals, can have dependencies. These are used for 2 things:
63/// - Globals must be declared before they are used, also between globals. The compiler processes
64/// globals unordered, so we must use the dependencies here to figure out how to order the globals
65/// in the final module. The Globals structure is also used for that.
66/// - Entry points must declare the complete list of OpVariable instructions that they access.
67/// For these we use the same dependency structure.
68/// In this mechanism, globals will only depend on other globals, while functions may depend on
69/// globals or other functions.
70pub const Decl = struct {
71 /// Index to refer to a Decl by.
72 pub const Index = enum(u32) { _ };
73
74 /// The result-id to be used for this declaration. This is the final result-id
75 /// of the decl, which may be an OpFunction, OpVariable, or the result of a sequence
76 /// of OpSpecConstantOp operations.
77 result_id: IdRef,
78 /// The offset of the first dependency of this decl in the `decl_deps` array.
79 begin_dep: u32,
80 /// The past-end offset of the dependencies of this decl in the `decl_deps` array.
81 end_dep: u32,
82};
83
58/// Globals must be kept in order: operations involving globals must be ordered84/// Globals must be kept in order: operations involving globals must be ordered
59/// so that the global declaration precedes any usage.85/// so that the global declaration precedes any usage.
60pub const Global = struct {86pub const Global = struct {
61 /// Index type to refer to a global by.87 /// This is the result-id of the OpVariable instruction that declares the global.
62 pub const Index = enum(u32) { _ };
63
64 /// The result-id to be used for this global declaration. Note that this does not
65 /// necessarily refer to an OpVariable instruction - it may also be the final result
66 /// id of a number of OpSpecConstantOp instructions.
67 result_id: IdRef,88 result_id: IdRef,
68 /// The offset into `self.globals.section` of the first instruction of this global89 /// The offset into `self.globals.section` of the first instruction of this global
69 /// declaration.90 /// declaration.
70 begin_inst: u32,91 begin_inst: u32,
71 /// The past-end offset into `self.flobals.section`.92 /// The past-end offset into `self.flobals.section`.
72 end_inst: u32,93 end_inst: u32,
73 /// The first dependency in the `self.globals.dependencies` array list.
74 begin_dep: u32,
75 /// The past-end dependency in `self.globals.dependencies`.
76 end_dep: u32,
77};94};
7895
79/// A general-purpose allocator which may be used to allocate resources for this module96/// A general-purpose allocator which may be used to allocate resources for this module
...@@ -123,18 +140,19 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},...@@ -123,18 +140,19 @@ source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
123/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).140/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).
124type_cache: TypeCache = .{},141type_cache: TypeCache = .{},
125142
143/// Set of Decls, referred to by Decl.Index.
144decls: std.ArrayListUnmanaged(Decl) = .{},
145
146decl_deps: std.ArrayListUnmanaged(Decl.Index) = .{},
147
126/// The fields in this structure help to maintain the required order for global variables.148/// The fields in this structure help to maintain the required order for global variables.
127globals: struct {149globals: struct {
128 /// The graph nodes of global variables present in the module.150 /// Set of globals, referred to by Decl.Index.
129 nodes: std.ArrayListUnmanaged(Global) = .{},151 globals: std.AutoArrayHashMapUnmanaged(Decl.Index, Global) = .{},
130 /// This pseudo-section contains the initialization code for all the globals. Instructions from152 /// This pseudo-section contains the initialization code for all the globals. Instructions from
131 /// here are reordered when flushing the module. Its contents should be part of the153 /// here are reordered when flushing the module. Its contents should be part of the
132 /// `types_globals_constants` SPIR-V section.154 /// `types_globals_constants` SPIR-V section.
133 section: Section = .{},155 section: Section = .{},
134 /// Holds a list of dependent global variables for each global variable.
135 dependencies: std.ArrayListUnmanaged(Global.Index) = .{},
136 /// The global that initialization code/dependencies are currently being generated for, if any.
137 current_global: ?Global.Index = null,
138} = .{},156} = .{},
139157
140pub fn init(gpa: Allocator, arena: Allocator) Module {158pub fn init(gpa: Allocator, arena: Allocator) Module {
...@@ -159,9 +177,11 @@ pub fn deinit(self: *Module) void {...@@ -159,9 +177,11 @@ pub fn deinit(self: *Module) void {
159 self.source_file_names.deinit(self.gpa);177 self.source_file_names.deinit(self.gpa);
160 self.type_cache.deinit(self.gpa);178 self.type_cache.deinit(self.gpa);
161179
162 self.globals.nodes.deinit(self.gpa);180 self.decls.deinit(self.gpa);
181 self.decl_deps.deinit(self.gpa);
182
183 self.globals.globals.deinit(self.gpa);
163 self.globals.section.deinit(self.gpa);184 self.globals.section.deinit(self.gpa);
164 self.globals.dependencies.deinit(self.gpa);
165185
166 self.* = undefined;186 self.* = undefined;
167}187}
...@@ -181,16 +201,17 @@ pub fn idBound(self: Module) Word {...@@ -181,16 +201,17 @@ pub fn idBound(self: Module) Word {
181}201}
182202
183fn orderGlobalsInto(203fn orderGlobalsInto(
184 self: Module,204 self: *Module,
185 global_index: Global.Index,205 index: Decl.Index,
186 section: *Section,206 section: *Section,
187 seen: *std.DynamicBitSetUnmanaged,207 seen: *std.DynamicBitSetUnmanaged,
188) !void {208) !void {
189 const node = self.globals.nodes.items[@enumToInt(global_index)];209 const decl = self.declPtr(index);
190 const deps = self.globals.dependencies.items[node.begin_dep..node.end_dep];210 const deps = self.decl_deps.items[decl.begin_dep..decl.end_dep];
191 const insts = self.globals.section.instructions.items[node.begin_inst..node.end_inst];211 const global = self.globalPtr(index).?;
212 const insts = self.globals.section.instructions.items[global.begin_inst..global.end_inst];
192213
193 seen.set(@enumToInt(global_index));214 seen.set(@enumToInt(index));
194215
195 for (deps) |dep| {216 for (deps) |dep| {
196 if (!seen.isSet(@enumToInt(dep))) {217 if (!seen.isSet(@enumToInt(dep))) {
...@@ -201,17 +222,16 @@ fn orderGlobalsInto(...@@ -201,17 +222,16 @@ fn orderGlobalsInto(
201 try section.instructions.appendSlice(self.gpa, insts);222 try section.instructions.appendSlice(self.gpa, insts);
202}223}
203224
204fn orderGlobals(self: Module) !Section {225fn orderGlobals(self: *Module) !Section {
205 const nodes = self.globals.nodes.items;226 const globals = self.globals.globals.keys();
206227
207 var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, nodes.len);228 var seen = try std.DynamicBitSetUnmanaged.initEmpty(self.gpa, self.decls.items.len);
208 defer seen.deinit(self.gpa);229 defer seen.deinit(self.gpa);
209230
210 var ordered_globals = Section{};231 var ordered_globals = Section{};
211232 for (globals) |decl_index| {
212 for (0..nodes.len) |global_index| {233 if (!seen.isSet(@enumToInt(decl_index))) {
213 if (!seen.isSet(global_index)) {234 try self.orderGlobalsInto(decl_index, &ordered_globals, &seen);
214 try self.orderGlobalsInto(@intToEnum(Global.Index, @intCast(u32, global_index)), &ordered_globals, &seen);
215 }235 }
216 }236 }
217237
...@@ -219,12 +239,14 @@ fn orderGlobals(self: Module) !Section {...@@ -219,12 +239,14 @@ fn orderGlobals(self: Module) !Section {
219}239}
220240
221/// Emit this module as a spir-v binary.241/// Emit this module as a spir-v binary.
222pub fn flush(self: Module, file: std.fs.File) !void {242pub fn flush(self: *Module, file: std.fs.File) !void {
223 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"243 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
224244
225 const header = [_]Word{245 const header = [_]Word{
226 spec.magic_number,246 spec.magic_number,
227 (1 << 16) | (4 << 8), // TODO: From cpu features247 // TODO: From cpu features
248 // Emit SPIR-V 1.4 for now. This is the highest version that Intel's CPU OpenCL supports.
249 (1 << 16) | (4 << 8),
228 0, // TODO: Register Zig compiler magic number.250 0, // TODO: Register Zig compiler magic number.
229 self.idBound(),251 self.idBound(),
230 0, // Schema (currently reserved for future use)252 0, // Schema (currently reserved for future use)
...@@ -265,9 +287,10 @@ pub fn flush(self: Module, file: std.fs.File) !void {...@@ -265,9 +287,10 @@ pub fn flush(self: Module, file: std.fs.File) !void {
265}287}
266288
267/// Merge the sections making up a function declaration into this module.289/// Merge the sections making up a function declaration into this module.
268pub fn addFunction(self: *Module, func: Fn) !void {290pub fn addFunction(self: *Module, decl_index: Decl.Index, func: Fn) !void {
269 try self.sections.functions.append(self.gpa, func.prologue);291 try self.sections.functions.append(self.gpa, func.prologue);
270 try self.sections.functions.append(self.gpa, func.body);292 try self.sections.functions.append(self.gpa, func.body);
293 try self.declareDeclDeps(decl_index, func.decl_deps.items);
271}294}
272295
273/// Fetch the result-id of an OpString instruction that encodes the path of the source296/// Fetch the result-id of an OpString instruction that encodes the path of the source
...@@ -719,43 +742,56 @@ pub fn decorateMember(...@@ -719,43 +742,56 @@ pub fn decorateMember(
719 });742 });
720}743}
721744
722pub fn allocGlobal(self: *Module) !Global.Index {745pub const DeclKind = enum {
723 try self.globals.nodes.append(self.gpa, .{746 func,
747 global,
748};
749
750pub fn allocDecl(self: *Module, kind: DeclKind) !Decl.Index {
751 try self.decls.append(self.gpa, .{
724 .result_id = self.allocId(),752 .result_id = self.allocId(),
725 .begin_inst = undefined,
726 .end_inst = undefined,
727 .begin_dep = undefined,753 .begin_dep = undefined,
728 .end_dep = undefined,754 .end_dep = undefined,
729 });755 });
730 return @intToEnum(Global.Index, @intCast(u32, self.globals.nodes.items.len - 1));756 const index = @intToEnum(Decl.Index, @intCast(u32, self.decls.items.len - 1));
757 switch (kind) {
758 .func => {},
759 // If the decl represents a global, also allocate a global node.
760 .global => try self.globals.globals.putNoClobber(self.gpa, index, .{
761 .result_id = undefined,
762 .begin_inst = undefined,
763 .end_inst = undefined,
764 }),
765 }
766
767 return index;
731}768}
732769
733pub fn globalPtr(self: *Module, index: Global.Index) *Global {770pub fn declPtr(self: *Module, index: Decl.Index) *Decl {
734 return &self.globals.nodes.items[@enumToInt(index)];771 return &self.decls.items[@enumToInt(index)];
735}772}
736773
737/// Begin generating the global for `index`. The previous global is finalized774pub fn globalPtr(self: *Module, index: Decl.Index) ?*Global {
738/// at this point, and the global for `index` is made active. Any new calls to775 return self.globals.globals.getPtr(index);
739/// `addGlobalDependency` will affect this global. After a new call to this function,
740/// the prior active global cannot be modified again.
741pub fn beginGlobal(self: *Module, index: Global.Index) IdRef {
742 const global = self.globalPtr(index);
743 global.begin_inst = @intCast(u32, self.globals.section.instructions.items.len);
744 global.begin_dep = @intCast(u32, self.globals.dependencies.items.len);
745 self.globals.current_global = index;
746 return global.result_id;
747}776}
748777
749/// Finalize the global. After this point, the current global cannot be modified anymore.778/// Declare ALL dependencies for a decl.
750pub fn endGlobal(self: *Module) void {779pub fn declareDeclDeps(self: *Module, decl_index: Decl.Index, deps: []const Decl.Index) !void {
751 const global = self.globalPtr(self.globals.current_global.?);780 const begin_dep = @intCast(u32, self.decl_deps.items.len);
752 global.end_inst = @intCast(u32, self.globals.section.instructions.items.len);781 try self.decl_deps.appendSlice(self.gpa, deps);
753 global.end_dep = @intCast(u32, self.globals.dependencies.items.len);782 const end_dep = @intCast(u32, self.decl_deps.items.len);
754 self.globals.current_global = null;783
784 const decl = self.declPtr(decl_index);
785 decl.begin_dep = begin_dep;
786 decl.end_dep = end_dep;
755}787}
756788
757pub fn addGlobalDependency(self: *Module, dependency: Global.Index) !void {789pub fn beginGlobal(self: *Module) u32 {
758 assert(self.globals.current_global != null);790 return @intCast(u32, self.globals.section.instructions.items.len);
759 assert(self.globals.current_global.? != dependency);791}
760 try self.globals.dependencies.append(self.gpa, dependency);792
793pub fn endGlobal(self: *Module, global_index: Decl.Index, begin_inst: u32) void {
794 const global = self.globalPtr(global_index).?;
795 global.begin_inst = begin_inst;
796 global.end_inst = @intCast(u32, self.globals.section.instructions.items.len);
761}797}