authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-22 01:49:47+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-01-28 14:45:23+01:00
log98ee39d1b0ed516428c611d8dc1e52d21c786f97
treea5667f3012952c1301b5d8631d86b753d0f71704
parent1b6ebce0da45169979b0f51a07274ff6fb5590bc

spirv: spir-v dedicated type system


4 files changed, 855 insertions(+), 138 deletions(-)

src/codegen/spirv.zig+83-95
...@@ -21,8 +21,8 @@ const IdResultType = spec.IdResultType;...@@ -21,8 +21,8 @@ const IdResultType = spec.IdResultType;
2121
22const SpvModule = @import("spirv/Module.zig");22const SpvModule = @import("spirv/Module.zig");
23const SpvSection = @import("spirv/Section.zig");23const SpvSection = @import("spirv/Section.zig");
24const SpvType = @import("spirv/type.zig").Type;
2425
25const TypeCache = std.HashMapUnmanaged(Type, IdResultType, Type.HashContext64, std.hash_map.default_max_load_percentage);
26const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);26const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef);
2727
28const IncomingBlock = struct {28const IncomingBlock = struct {
...@@ -61,10 +61,6 @@ pub const DeclGen = struct {...@@ -61,10 +61,6 @@ pub const DeclGen = struct {
61 /// A counter to keep track of how many `arg` instructions we've seen yet.61 /// A counter to keep track of how many `arg` instructions we've seen yet.
62 next_arg_index: u32,62 next_arg_index: u32,
6363
64 /// A cache for zig types to prevent having to re-process a particular type. This structure is kept around
65 /// after a call to `gen` so that they don't have to be re-resolved for different decls.
66 type_cache: TypeCache = .{},
67
68 /// A map keeping track of which instruction generated which result-id.64 /// A map keeping track of which instruction generated which result-id.
69 inst_results: InstMap = .{},65 inst_results: InstMap = .{},
7066
...@@ -159,7 +155,6 @@ pub const DeclGen = struct {...@@ -159,7 +155,6 @@ pub const DeclGen = struct {
159 self.liveness = liveness;155 self.liveness = liveness;
160 self.args.items.len = 0;156 self.args.items.len = 0;
161 self.next_arg_index = 0;157 self.next_arg_index = 0;
162 // Note: don't clear type_cache.
163 self.inst_results.clearRetainingCapacity();158 self.inst_results.clearRetainingCapacity();
164 self.blocks.clearRetainingCapacity();159 self.blocks.clearRetainingCapacity();
165 self.current_block_label_id = undefined;160 self.current_block_label_id = undefined;
...@@ -177,7 +172,6 @@ pub const DeclGen = struct {...@@ -177,7 +172,6 @@ pub const DeclGen = struct {
177 /// Free resources owned by the DeclGen.172 /// Free resources owned by the DeclGen.
178 pub fn deinit(self: *DeclGen) void {173 pub fn deinit(self: *DeclGen) void {
179 self.args.deinit(self.spv.gpa);174 self.args.deinit(self.spv.gpa);
180 self.type_cache.deinit(self.spv.gpa);
181 self.inst_results.deinit(self.spv.gpa);175 self.inst_results.deinit(self.spv.gpa);
182 self.blocks.deinit(self.spv.gpa);176 self.blocks.deinit(self.spv.gpa);
183 self.code.deinit(self.spv.gpa);177 self.code.deinit(self.spv.gpa);
...@@ -220,7 +214,7 @@ pub const DeclGen = struct {...@@ -220,7 +214,7 @@ pub const DeclGen = struct {
220 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to214 /// Note that there is no such thing as nested blocks like in ZIR or AIR, so we don't need to
221 /// keep track of the previous block.215 /// keep track of the previous block.
222 fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void {216 fn beginSpvBlock(self: *DeclGen, label_id: IdResult) !void {
223 try self.code.emit(self.spv.gpa, .OpLabel, .{.id_result = label_id});217 try self.code.emit(self.spv.gpa, .OpLabel, .{ .id_result = label_id });
224 self.current_block_label_id = label_id.toRef();218 self.current_block_label_id = label_id.toRef();
225 }219 }
226220
...@@ -317,9 +311,9 @@ pub const DeclGen = struct {...@@ -317,9 +311,9 @@ pub const DeclGen = struct {
317 };311 };
318 },312 },
319 // As of yet, there is no vector support in the self-hosted compiler.313 // As of yet, there is no vector support in the self-hosted compiler.
320 .Vector => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),314 .Vector => self.todo("implement arithmeticTypeInfo for Vector", .{}),
321 // TODO: For which types is this the case?315 // TODO: For which types is this the case?
322 else => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for {}", .{ty}),316 else => self.todo("implement arithmeticTypeInfo for {}", .{ty}),
323 };317 };
324 }318 }
325319
...@@ -329,7 +323,7 @@ pub const DeclGen = struct {...@@ -329,7 +323,7 @@ pub const DeclGen = struct {
329 const target = self.getTarget();323 const target = self.getTarget();
330 const section = &self.spv.sections.types_globals_constants;324 const section = &self.spv.sections.types_globals_constants;
331 const result_id = self.spv.allocId();325 const result_id = self.spv.allocId();
332 const result_type_id = try self.genType(ty);326 const result_type_id = try self.resolveTypeId(ty);
333327
334 if (val.isUndef()) {328 if (val.isUndef()) {
335 try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id });329 try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_type_id, .id_result = result_id });
...@@ -341,7 +335,7 @@ pub const DeclGen = struct {...@@ -341,7 +335,7 @@ pub const DeclGen = struct {
341 const int_info = ty.intInfo(target);335 const int_info = ty.intInfo(target);
342 const backing_bits = self.backingIntBits(int_info.bits) orelse {336 const backing_bits = self.backingIntBits(int_info.bits) orelse {
343 // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.337 // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
344 return self.fail("TODO: SPIR-V backend: implement composite int constants for {}", .{ty});338 return self.todo("implement composite int constants for {}", .{ty});
345 };339 };
346340
347 // We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any341 // We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any
...@@ -354,8 +348,8 @@ pub const DeclGen = struct {...@@ -354,8 +348,8 @@ pub const DeclGen = struct {
354 var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt()) else val.toUnsignedInt();348 var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt()) else val.toUnsignedInt();
355349
356 const value: spec.LiteralContextDependentNumber = switch (backing_bits) {350 const value: spec.LiteralContextDependentNumber = switch (backing_bits) {
357 1...32 => .{.uint32 = @truncate(u32, int_bits)},351 1...32 => .{ .uint32 = @truncate(u32, int_bits) },
358 33...64 => .{.uint64 = int_bits},352 33...64 => .{ .uint64 = int_bits },
359 else => unreachable,353 else => unreachable,
360 };354 };
361355
...@@ -375,14 +369,14 @@ pub const DeclGen = struct {...@@ -375,14 +369,14 @@ pub const DeclGen = struct {
375 },369 },
376 .Float => {370 .Float => {
377 // At this point we are guaranteed that the target floating point type is supported, otherwise the function371 // At this point we are guaranteed that the target floating point type is supported, otherwise the function
378 // would have exited at genType(ty).372 // would have exited at resolveTypeId(ty).
379373
380 const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {374 const value: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {
381 // Prevent upcasting to f32 by bitcasting and writing as a uint32.375 // Prevent upcasting to f32 by bitcasting and writing as a uint32.
382 16 => .{.uint32 = @bitCast(u16, val.toFloat(f16))},376 16 => .{ .uint32 = @bitCast(u16, val.toFloat(f16)) },
383 32 => .{.float32 = val.toFloat(f32)},377 32 => .{ .float32 = val.toFloat(f32) },
384 64 => .{.float64 = val.toFloat(f64)},378 64 => .{ .float64 = val.toFloat(f64) },
385 128 => unreachable, // Filtered out in the call to genType.379 128 => unreachable, // Filtered out in the call to resolveTypeId.
386 // TODO: Insert case for long double when the layout for that is determined?380 // TODO: Insert case for long double when the layout for that is determined?
387 else => unreachable,381 else => unreachable,
388 };382 };
...@@ -394,43 +388,43 @@ pub const DeclGen = struct {...@@ -394,43 +388,43 @@ pub const DeclGen = struct {
394 });388 });
395 },389 },
396 .Void => unreachable,390 .Void => unreachable,
397 else => return self.fail("TODO: SPIR-V backend: constant generation of type {}", .{ty}),391 else => return self.todo("constant generation of type {}", .{ty}),
398 }392 }
399393
400 return result_id.toRef();394 return result_id.toRef();
401 }395 }
402396
403 fn genType(self: *DeclGen, ty: Type) Error!IdResultType {397 /// Turn a Zig type into a SPIR-V Type, and return its type result-id.
404 // We can't use getOrPut here so we can recursively generate types.398 fn resolveTypeId(self: *DeclGen, ty: Type) !IdResultType {
405 if (self.type_cache.get(ty)) |already_generated| {399 return self.spv.typeResultId(try self.resolveType(ty));
406 return already_generated;400 }
407 }
408401
402 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
403 fn resolveType(self: *DeclGen, ty: Type) Error!SpvType.Ref {
409 const target = self.getTarget();404 const target = self.getTarget();
410 const section = &self.spv.sections.types_globals_constants;405 return switch (ty.zigTypeTag()) {
411 const result_id = self.spv.allocId();406 .Void => try self.spv.resolveType(SpvType.initTag(.void)),
412407 .Bool => blk: {
413 switch (ty.zigTypeTag()) {408 // TODO: SPIR-V booleans are opaque. For local variables this is fine, but for structs
414 .Void => try section.emit(self.spv.gpa, .OpTypeVoid, .{.id_result = result_id}),409 // members we want to use integer types instead.
415 .Bool => try section.emit(self.spv.gpa, .OpTypeBool, .{.id_result = result_id}),410 break :blk try self.spv.resolveType(SpvType.initTag(.bool));
416 .Int => {411 },
412 .Int => blk: {
417 const int_info = ty.intInfo(target);413 const int_info = ty.intInfo(target);
418 const backing_bits = self.backingIntBits(int_info.bits) orelse {414 const backing_bits = self.backingIntBits(int_info.bits) orelse {
419 // Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.415 // TODO: Integers too big for any native type are represented as "composite integers":
420 return self.fail("TODO: SPIR-V backend: implement composite int {}", .{ty});416 // An array of largestSupportedIntBits.
417 return self.todo("Implement composite int type {}", .{ty});
421 };418 };
422419
423 // TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.420 const payload = try self.spv.arena.create(SpvType.Payload.Int);
424 try section.emit(self.spv.gpa, .OpTypeInt, .{421 payload.* = .{
425 .id_result = result_id,
426 .width = backing_bits,422 .width = backing_bits,
427 .signedness = switch (int_info.signedness) {423 .signedness = int_info.signedness,
428 .unsigned => @as(spec.LiteralInteger, 0),424 };
429 .signed => 1,425 break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
430 },
431 });
432 },426 },
433 .Float => {427 .Float => blk: {
434 // We can (and want) not really emulate floating points with other floating point types like with the integer types,428 // We can (and want) not really emulate floating points with other floating point types like with the integer types,
435 // so if the float is not supported, just return an error.429 // so if the float is not supported, just return an error.
436 const bits = ty.floatBits(target);430 const bits = ty.floatBits(target);
...@@ -446,39 +440,34 @@ pub const DeclGen = struct {...@@ -446,39 +440,34 @@ pub const DeclGen = struct {
446 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});440 return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
447 }441 }
448442
449 try section.emit(self.spv.gpa, .OpTypeFloat, .{.id_result = result_id, .width = bits});443 const payload = try self.spv.arena.create(SpvType.Payload.Float);
444 payload.* = .{
445 .width = bits,
446 };
447 break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
450 },448 },
451 .Fn => {449 .Fn => blk: {
452 // We only support zig-calling-convention functions, no varargs.450 // We only support zig-calling-convention functions, no varargs.
453 if (ty.fnCallingConvention() != .Unspecified)451 if (ty.fnCallingConvention() != .Unspecified)
454 return self.fail("Unsupported calling convention for SPIR-V", .{});452 return self.fail("Unsupported calling convention for SPIR-V", .{});
455 if (ty.fnIsVarArgs())453 if (ty.fnIsVarArgs())
456 return self.fail("VarArgs unsupported for SPIR-V", .{});454 return self.fail("VarArgs functions are unsupported for SPIR-V", .{});
457
458 // In order to avoid a temporary here, first generate all the required types and then simply look them up
459 // when generating the function type.
460 const params = ty.fnParamLen();
461 var i: usize = 0;
462 while (i < params) : (i += 1) {
463 _ = try self.genType(ty.fnParamType(i));
464 }
465
466 const return_type_id = try self.genType(ty.fnReturnType());
467455
468 try section.emitRaw(self.spv.gpa, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen()));456 const param_types = try self.spv.arena.alloc(SpvType.Ref, ty.fnParamLen());
457 for (param_types) |*param, i| {
458 param.* = try self.resolveType(ty.fnParamType(i));
459 }
469460
470 // result id + result type id + parameter type ids.461 const return_type = try self.resolveType(ty.fnReturnType());
471 section.writeOperand(IdResult, result_id);
472 section.writeOperand(IdResultType, return_type_id);
473462
474 i = 0;463 const payload = try self.spv.arena.create(SpvType.Payload.Function);
475 while (i < params) : (i += 1) {464 payload.* = .{ .return_type = return_type, .parameters = param_types };
476 const param_type_id = self.type_cache.get(ty.fnParamType(i)).?;465 break :blk try self.spv.resolveType(SpvType.initPayload(&payload.base));
477 section.writeOperand(IdRef, param_type_id.toRef());466 },
478 }467 .Pointer => {
468 // This type can now be properly implemented, but we still need to implement the storage classes as proper address spaces.
469 return self.todo("Implement type Pointer properly", .{});
479 },470 },
480 // When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType.
481 .Pointer => return self.fail("Cannot create pointer with unknown storage class", .{}),
482 .Vector => {471 .Vector => {
483 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations472 // Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
484 // which work on them), so simply use those.473 // which work on them), so simply use those.
...@@ -488,23 +477,21 @@ pub const DeclGen = struct {...@@ -488,23 +477,21 @@ pub const DeclGen = struct {
488 // is adequate at all for this.477 // is adequate at all for this.
489478
490 // TODO: Vectors are not yet supported by the self-hosted compiler itself it seems.479 // TODO: Vectors are not yet supported by the self-hosted compiler itself it seems.
491 return self.fail("TODO: SPIR-V backend: implement type Vector", .{});480 return self.todo("Implement type Vector", .{});
492 },481 },
482
493 .Null,483 .Null,
494 .Undefined,484 .Undefined,
495 .EnumLiteral,485 .EnumLiteral,
496 .ComptimeFloat,486 .ComptimeFloat,
497 .ComptimeInt,487 .ComptimeInt,
498 .Type,488 .Type,
499 => unreachable, // Must be const or comptime.489 => unreachable, // Must be comptime.
500490
501 .BoundFn => unreachable, // this type will be deleted from the language.491 .BoundFn => unreachable, // this type will be deleted from the language.
502492
503 else => |tag| return self.fail("TODO: SPIR-V backend: implement type {}s", .{tag}),493 else => |tag| return self.todo("Implement zig type '{}'", .{tag}),
504 }494 };
505
506 try self.type_cache.putNoClobber(self.spv.gpa, ty, result_id.toResultType());
507 return result_id.toResultType();
508 }495 }
509496
510 /// SPIR-V requires pointers to have a storage class (address space), and so we have a special function for that.497 /// SPIR-V requires pointers to have a storage class (address space), and so we have a special function for that.
...@@ -517,7 +504,7 @@ pub const DeclGen = struct {...@@ -517,7 +504,7 @@ pub const DeclGen = struct {
517 // TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types504 // TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types
518 // if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled.505 // if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled.
519 // These also relates to the pointer's address space.506 // These also relates to the pointer's address space.
520 const child_id = try self.genType(ty.elemType());507 const child_id = try self.resolveTypeId(ty.elemType());
521508
522 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{509 try self.spv.sections.types_globals_constants.emit(self.spv.gpa, .OpTypePointer, .{
523 .id_result = result_id,510 .id_result = result_id,
...@@ -534,9 +521,9 @@ pub const DeclGen = struct {...@@ -534,9 +521,9 @@ pub const DeclGen = struct {
534521
535 if (decl.val.castTag(.function)) |_| {522 if (decl.val.castTag(.function)) |_| {
536 assert(decl.ty.zigTypeTag() == .Fn);523 assert(decl.ty.zigTypeTag() == .Fn);
537 const prototype_id = try self.genType(decl.ty);524 const prototype_id = try self.resolveTypeId(decl.ty);
538 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunction, .{525 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunction, .{
539 .id_result_type = self.type_cache.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.526 .id_result_type = try self.resolveTypeId(decl.ty.fnReturnType()),
540 .id_result = result_id,527 .id_result = result_id,
541 .function_control = .{}, // TODO: We can set inline here if the type requires it.528 .function_control = .{}, // TODO: We can set inline here if the type requires it.
542 .function_type = prototype_id.toRef(),529 .function_type = prototype_id.toRef(),
...@@ -547,7 +534,7 @@ pub const DeclGen = struct {...@@ -547,7 +534,7 @@ pub const DeclGen = struct {
547534
548 try self.args.ensureUnusedCapacity(self.spv.gpa, params);535 try self.args.ensureUnusedCapacity(self.spv.gpa, params);
549 while (i < params) : (i += 1) {536 while (i < params) : (i += 1) {
550 const param_type_id = self.type_cache.get(decl.ty.fnParamType(i)).?;537 const param_type_id = try self.resolveTypeId(decl.ty.fnParamType(i));
551 const arg_result_id = self.spv.allocId();538 const arg_result_id = self.spv.allocId();
552 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionParameter, .{539 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionParameter, .{
553 .id_result_type = param_type_id,540 .id_result_type = param_type_id,
...@@ -573,7 +560,8 @@ pub const DeclGen = struct {...@@ -573,7 +560,8 @@ pub const DeclGen = struct {
573 try self.spv.sections.functions.append(self.spv.gpa, self.code);560 try self.spv.sections.functions.append(self.spv.gpa, self.code);
574 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionEnd, {});561 try self.spv.sections.functions.emit(self.spv.gpa, .OpFunctionEnd, {});
575 } else {562 } else {
576 return self.fail("TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()});563 // TODO
564 // return self.todo("generate decl type {}", .{decl.ty.zigTypeTag()});
577 }565 }
578 }566 }
579567
...@@ -622,7 +610,7 @@ pub const DeclGen = struct {...@@ -622,7 +610,7 @@ pub const DeclGen = struct {
622 .unreach => return self.airUnreach(),610 .unreach => return self.airUnreach(),
623 // zig fmt: on611 // zig fmt: on
624612
625 else => |tag| return self.fail("TODO: SPIR-V backend: implement AIR tag {s}", .{613 else => |tag| return self.todo("implement AIR tag {s}", .{
626 @tagName(tag),614 @tagName(tag),
627 }),615 }),
628 };616 };
...@@ -635,7 +623,7 @@ pub const DeclGen = struct {...@@ -635,7 +623,7 @@ pub const DeclGen = struct {
635 const lhs_id = try self.resolve(bin_op.lhs);623 const lhs_id = try self.resolve(bin_op.lhs);
636 const rhs_id = try self.resolve(bin_op.rhs);624 const rhs_id = try self.resolve(bin_op.rhs);
637 const result_id = self.spv.allocId();625 const result_id = self.spv.allocId();
638 const result_type_id = try self.genType(self.air.typeOfIndex(inst));626 const result_type_id = try self.resolveTypeId(self.air.typeOfIndex(inst));
639 try self.code.emit(self.spv.gpa, opcode, .{627 try self.code.emit(self.spv.gpa, opcode, .{
640 .id_result_type = result_type_id,628 .id_result_type = result_type_id,
641 .id_result = result_id,629 .id_result = result_id,
...@@ -654,7 +642,7 @@ pub const DeclGen = struct {...@@ -654,7 +642,7 @@ pub const DeclGen = struct {
654 const rhs_id = try self.resolve(bin_op.rhs);642 const rhs_id = try self.resolve(bin_op.rhs);
655643
656 const result_id = self.spv.allocId();644 const result_id = self.spv.allocId();
657 const result_type_id = try self.genType(ty);645 const result_type_id = try self.resolveTypeId(ty);
658646
659 assert(self.air.typeOf(bin_op.lhs).eql(ty));647 assert(self.air.typeOf(bin_op.lhs).eql(ty));
660 assert(self.air.typeOf(bin_op.rhs).eql(ty));648 assert(self.air.typeOf(bin_op.rhs).eql(ty));
...@@ -665,10 +653,10 @@ pub const DeclGen = struct {...@@ -665,10 +653,10 @@ pub const DeclGen = struct {
665653
666 const opcode_index: usize = switch (info.class) {654 const opcode_index: usize = switch (info.class) {
667 .composite_integer => {655 .composite_integer => {
668 return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{});656 return self.todo("binary operations for composite integers", .{});
669 },657 },
670 .strange_integer => {658 .strange_integer => {
671 return self.fail("TODO: SPIR-V backend: binary operations for strange integers", .{});659 return self.todo("binary operations for strange integers", .{});
672 },660 },
673 .integer => switch (info.signedness) {661 .integer => switch (info.signedness) {
674 .signed => @as(usize, 1),662 .signed => @as(usize, 1),
...@@ -702,7 +690,7 @@ pub const DeclGen = struct {...@@ -702,7 +690,7 @@ pub const DeclGen = struct {
702 const lhs_id = try self.resolve(bin_op.lhs);690 const lhs_id = try self.resolve(bin_op.lhs);
703 const rhs_id = try self.resolve(bin_op.rhs);691 const rhs_id = try self.resolve(bin_op.rhs);
704 const result_id = self.spv.allocId();692 const result_id = self.spv.allocId();
705 const result_type_id = try self.genType(Type.initTag(.bool));693 const result_type_id = try self.resolveTypeId(Type.initTag(.bool));
706 const op_ty = self.air.typeOf(bin_op.lhs);694 const op_ty = self.air.typeOf(bin_op.lhs);
707 assert(op_ty.eql(self.air.typeOf(bin_op.rhs)));695 assert(op_ty.eql(self.air.typeOf(bin_op.rhs)));
708696
...@@ -712,10 +700,10 @@ pub const DeclGen = struct {...@@ -712,10 +700,10 @@ pub const DeclGen = struct {
712700
713 const opcode_index: usize = switch (info.class) {701 const opcode_index: usize = switch (info.class) {
714 .composite_integer => {702 .composite_integer => {
715 return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{});703 return self.todo("binary operations for composite integers", .{});
716 },704 },
717 .strange_integer => {705 .strange_integer => {
718 return self.fail("TODO: SPIR-V backend: comparison for strange integers", .{});706 return self.todo("comparison for strange integers", .{});
719 },707 },
720 .float => 0,708 .float => 0,
721 .bool => 1,709 .bool => 1,
...@@ -746,7 +734,7 @@ pub const DeclGen = struct {...@@ -746,7 +734,7 @@ pub const DeclGen = struct {
746 const ty_op = self.air.instructions.items(.data)[inst].ty_op;734 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
747 const operand_id = try self.resolve(ty_op.operand);735 const operand_id = try self.resolve(ty_op.operand);
748 const result_id = self.spv.allocId();736 const result_id = self.spv.allocId();
749 const result_type_id = try self.genType(Type.initTag(.bool));737 const result_type_id = try self.resolveTypeId(Type.initTag(.bool));
750 try self.code.emit(self.spv.gpa, .OpLogicalNot, .{738 try self.code.emit(self.spv.gpa, .OpLogicalNot, .{
751 .id_result_type = result_type_id,739 .id_result_type = result_type_id,
752 .id_result = result_id,740 .id_result = result_id,
...@@ -813,9 +801,9 @@ pub const DeclGen = struct {...@@ -813,9 +801,9 @@ pub const DeclGen = struct {
813 const result_id = self.spv.allocId();801 const result_id = self.spv.allocId();
814802
815 // TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types803 // TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types
816 // are not allowed to be created from a phi node, and throw an error for those. For now, genType already throws804 // are not allowed to be created from a phi node, and throw an error for those. For now, resolveTypeId already throws
817 // an error for pointers.805 // an error for pointers.
818 const result_type_id = try self.genType(ty);806 const result_type_id = try self.resolveTypeId(ty);
819 _ = result_type_id;807 _ = result_type_id;
820808
821 try self.code.emitRaw(self.spv.gpa, .OpPhi, 2 + @intCast(u16, incoming_blocks.items.len * 2)); // result type + result + variable/parent...809 try self.code.emitRaw(self.spv.gpa, .OpPhi, 2 + @intCast(u16, incoming_blocks.items.len * 2)); // result type + result + variable/parent...
...@@ -838,7 +826,7 @@ pub const DeclGen = struct {...@@ -838,7 +826,7 @@ pub const DeclGen = struct {
838 try block.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id });826 try block.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id });
839 }827 }
840828
841 try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = block.label_id});829 try self.code.emit(self.spv.gpa, .OpBranch, .{ .target_label = block.label_id });
842 }830 }
843831
844 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {832 fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
...@@ -882,7 +870,7 @@ pub const DeclGen = struct {...@@ -882,7 +870,7 @@ pub const DeclGen = struct {
882 const operand_id = try self.resolve(ty_op.operand);870 const operand_id = try self.resolve(ty_op.operand);
883 const ty = self.air.typeOfIndex(inst);871 const ty = self.air.typeOfIndex(inst);
884872
885 const result_type_id = try self.genType(ty);873 const result_type_id = try self.resolveTypeId(ty);
886 const result_id = self.spv.allocId();874 const result_id = self.spv.allocId();
887875
888 const access = spec.MemoryAccess.Extended{876 const access = spec.MemoryAccess.Extended{
...@@ -906,13 +894,13 @@ pub const DeclGen = struct {...@@ -906,13 +894,13 @@ pub const DeclGen = struct {
906 const loop_label_id = self.spv.allocId();894 const loop_label_id = self.spv.allocId();
907895
908 // Jump to the loop entry point896 // Jump to the loop entry point
909 try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = loop_label_id.toRef()});897 try self.code.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() });
910898
911 // TODO: Look into OpLoopMerge.899 // TODO: Look into OpLoopMerge.
912 try self.beginSpvBlock(loop_label_id);900 try self.beginSpvBlock(loop_label_id);
913 try self.genBody(body);901 try self.genBody(body);
914902
915 try self.code.emit(self.spv.gpa, .OpBranch, .{.target_label = loop_label_id.toRef()});903 try self.code.emit(self.spv.gpa, .OpBranch, .{ .target_label = loop_label_id.toRef() });
916 }904 }
917905
918 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {906 fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
...@@ -920,7 +908,7 @@ pub const DeclGen = struct {...@@ -920,7 +908,7 @@ pub const DeclGen = struct {
920 const operand_ty = self.air.typeOf(operand);908 const operand_ty = self.air.typeOf(operand);
921 if (operand_ty.hasRuntimeBits()) {909 if (operand_ty.hasRuntimeBits()) {
922 const operand_id = try self.resolve(operand);910 const operand_id = try self.resolve(operand);
923 try self.code.emit(self.spv.gpa, .OpReturnValue, .{.value = operand_id});911 try self.code.emit(self.spv.gpa, .OpReturnValue, .{ .value = operand_id });
924 } else {912 } else {
925 try self.code.emit(self.spv.gpa, .OpReturn, {});913 try self.code.emit(self.spv.gpa, .OpReturn, {});
926 }914 }
src/codegen/spirv/Module.zig+301-26
...@@ -9,14 +9,20 @@ const Module = @This();...@@ -9,14 +9,20 @@ const Module = @This();
99
10const std = @import("std");10const std = @import("std");
11const Allocator = std.mem.Allocator;11const Allocator = std.mem.Allocator;
12const assert = std.debug.assert;
1213
13const ZigDecl = @import("../../Module.zig").Decl;14const ZigDecl = @import("../../Module.zig").Decl;
1415
15const spec = @import("spec.zig");16const spec = @import("spec.zig");
16const Word = spec.Word;17const Word = spec.Word;
17const IdRef = spec.IdRef;18const IdRef = spec.IdRef;
19const IdResult = spec.IdResult;
20const IdResultType = spec.IdResultType;
1821
19const Section = @import("Section.zig");22const Section = @import("Section.zig");
23const Type = @import("type.zig").Type;
24
25const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true);
2026
21/// A general-purpose allocator which may be used to allocate resources for this module27/// A general-purpose allocator which may be used to allocate resources for this module
22gpa: Allocator,28gpa: Allocator,
...@@ -57,6 +63,12 @@ next_result_id: Word,...@@ -57,6 +63,12 @@ next_result_id: Word,
57/// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.63/// just the ones for OpLine. Note that OpLine needs the result of OpString, and not that of OpSource.
58source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},64source_file_names: std.StringHashMapUnmanaged(IdRef) = .{},
5965
66/// SPIR-V type cache. Note that according to SPIR-V spec section 2.8, Types and Variables, non-pointer
67/// non-aggrerate types (which includes matrices and vectors) must have a _unique_ representation in
68/// the final binary.
69/// Note: Uses ArrayHashMap which is insertion ordered, so that we may refer to other types by index (Type.Ref).
70type_cache: TypeCache = .{},
71
60pub fn init(gpa: Allocator, arena: Allocator) Module {72pub fn init(gpa: Allocator, arena: Allocator) Module {
61 return .{73 return .{
62 .gpa = gpa,74 .gpa = gpa,
...@@ -75,44 +87,20 @@ pub fn deinit(self: *Module) void {...@@ -75,44 +87,20 @@ pub fn deinit(self: *Module) void {
75 self.sections.functions.deinit(self.gpa);87 self.sections.functions.deinit(self.gpa);
7688
77 self.source_file_names.deinit(self.gpa);89 self.source_file_names.deinit(self.gpa);
90 self.type_cache.deinit(self.gpa);
7891
79 self.* = undefined;92 self.* = undefined;
80}93}
8194
82pub fn allocId(self: *Module) spec.IdResult {95pub fn allocId(self: *Module) spec.IdResult {
83 defer self.next_result_id += 1;96 defer self.next_result_id += 1;
84 return .{.id = self.next_result_id};97 return .{ .id = self.next_result_id };
85}98}
8699
87pub fn idBound(self: Module) Word {100pub fn idBound(self: Module) Word {
88 return self.next_result_id;101 return self.next_result_id;
89}102}
90103
91/// Fetch the result-id of an OpString instruction that encodes the path of the source
92/// file of the decl. This function may also emit an OpSource with source-level information regarding
93/// the decl.
94pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
95 const path = decl.getFileScope().sub_file_path;
96 const result = try self.source_file_names.getOrPut(self.gpa, path);
97 if (!result.found_existing) {
98 const file_result_id = self.allocId();
99 result.value_ptr.* = file_result_id.toRef();
100 try self.sections.debug_strings.emit(self.gpa, .OpString, .{
101 .id_result = file_result_id,
102 .string = path,
103 });
104
105 try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
106 .source_language = .Unknown, // TODO: Register Zig source language.
107 .version = 0, // TODO: Zig version as u32?
108 .file = file_result_id.toRef(),
109 .source = null, // TODO: Store actual source also?
110 });
111 }
112
113 return result.value_ptr.*;
114}
115
116/// Emit this module as a spir-v binary.104/// Emit this module as a spir-v binary.
117pub fn flush(self: Module, file: std.fs.File) !void {105pub fn flush(self: Module, file: std.fs.File) !void {
118 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"106 // See SPIR-V Spec section 2.3, "Physical Layout of a SPIR-V Module and Instruction"
...@@ -151,3 +139,290 @@ pub fn flush(self: Module, file: std.fs.File) !void {...@@ -151,3 +139,290 @@ pub fn flush(self: Module, file: std.fs.File) !void {
151 try file.setEndPos(file_size);139 try file.setEndPos(file_size);
152 try file.pwritevAll(&iovc_buffers, 0);140 try file.pwritevAll(&iovc_buffers, 0);
153}141}
142
143/// Fetch the result-id of an OpString instruction that encodes the path of the source
144/// file of the decl. This function may also emit an OpSource with source-level information regarding
145/// the decl.
146pub fn resolveSourceFileName(self: *Module, decl: *ZigDecl) !IdRef {
147 const path = decl.getFileScope().sub_file_path;
148 const result = try self.source_file_names.getOrPut(self.gpa, path);
149 if (!result.found_existing) {
150 const file_result_id = self.allocId();
151 result.value_ptr.* = file_result_id.toRef();
152 try self.sections.debug_strings.emit(self.gpa, .OpString, .{
153 .id_result = file_result_id,
154 .string = path,
155 });
156
157 try self.sections.debug_strings.emit(self.gpa, .OpSource, .{
158 .source_language = .Unknown, // TODO: Register Zig source language.
159 .version = 0, // TODO: Zig version as u32?
160 .file = file_result_id.toRef(),
161 .source = null, // TODO: Store actual source also?
162 });
163 }
164
165 return result.value_ptr.*;
166}
167
168/// Fetch a result-id for a spir-v type. This function deduplicates the type as appropriate,
169/// and returns a cached version if that exists.
170/// Note: This function does not attempt to perform any validation on the type.
171/// The type is emitted in a shallow fashion; any child types should already
172/// be emitted at this point.
173pub fn resolveType(self: *Module, ty: Type) !Type.Ref {
174 const result = try self.type_cache.getOrPut(self.gpa, ty);
175 if (!result.found_existing) {
176 result.value_ptr.* = try self.emitType(ty);
177 }
178 return result.index;
179}
180
181pub fn resolveTypeId(self: *Module, ty: Type) !IdRef {
182 return self.typeResultId(try self.resolveType(ty));
183}
184
185/// Get the result-id of a particular type, by reference. Asserts type_ref is valid.
186pub fn typeResultId(self: Module, type_ref: Type.Ref) IdResultType {
187 return self.type_cache.values()[type_ref];
188}
189
190/// Get the result-id of a particular type as IdRef, by Type.Ref. Asserts type_ref is valid.
191pub fn typeRefId(self: Module, type_ref: Type.Ref) IdRef {
192 return self.type_cache.values()[type_ref].toRef();
193}
194
195/// Unconditionally emit a spir-v type into the appropriate section.
196/// Note: If this function is called with a type that is already generated, it may yield an invalid module
197/// as non-pointer non-aggregrate types must me unique!
198/// Note: This function does not attempt to perform any validation on the type.
199/// The type is emitted in a shallow fashion; any child types should already
200/// be emitted at this point.
201pub fn emitType(self: *Module, ty: Type) !IdResultType {
202 const result_id = self.allocId();
203 const ref_id = result_id.toRef();
204 const types = &self.sections.types_globals_constants;
205 const annotations = &self.sections.annotations;
206 const result_id_operand = .{ .id_result = result_id };
207
208 switch (ty.tag()) {
209 .void => try types.emit(self.gpa, .OpTypeVoid, result_id_operand),
210 .bool => try types.emit(self.gpa, .OpTypeBool, result_id_operand),
211 .int => try types.emit(self.gpa, .OpTypeInt, .{
212 .id_result = result_id,
213 .width = ty.payload(.int).width,
214 .signedness = switch (ty.payload(.int).signedness) {
215 .unsigned => @as(spec.LiteralInteger, 0),
216 .signed => 1,
217 },
218 }),
219 .float => try types.emit(self.gpa, .OpTypeFloat, .{
220 .id_result = result_id,
221 .width = ty.payload(.float).width,
222 }),
223 .vector => try types.emit(self.gpa, .OpTypeVector, .{
224 .id_result = result_id,
225 .component_type = self.typeResultId(ty.childType()).toRef(),
226 .component_count = ty.payload(.vector).component_count,
227 }),
228 .matrix => try types.emit(self.gpa, .OpTypeMatrix, .{
229 .id_result = result_id,
230 .column_type = self.typeResultId(ty.childType()).toRef(),
231 .column_count = ty.payload(.matrix).column_count,
232 }),
233 .image => {
234 const info = ty.payload(.image);
235 try types.emit(self.gpa, .OpTypeImage, .{
236 .id_result = result_id,
237 .sampled_type = self.typeResultId(ty.childType()).toRef(),
238 .dim = info.dim,
239 .depth = @enumToInt(info.depth),
240 .arrayed = @boolToInt(info.arrayed),
241 .ms = @boolToInt(info.multisampled),
242 .sampled = @enumToInt(info.sampled),
243 .image_format = info.format,
244 .access_qualifier = info.access_qualifier,
245 });
246 },
247 .sampler => try types.emit(self.gpa, .OpTypeSampler, result_id_operand),
248 .sampled_image => try types.emit(self.gpa, .OpTypeSampledImage, .{
249 .id_result = result_id,
250 .image_type = self.typeResultId(ty.childType()).toRef(),
251 }),
252 .array => {
253 const info = ty.payload(.array);
254 assert(info.length != 0);
255 try types.emit(self.gpa, .OpTypeArray, .{
256 .id_result = result_id,
257 .element_type = self.typeResultId(ty.childType()).toRef(),
258 .length = .{ .id = 0 }, // TODO: info.length must be emitted as constant!
259 });
260 if (info.array_stride != 0) {
261 try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
262 }
263 },
264 .runtime_array => {
265 const info = ty.payload(.runtime_array);
266 try types.emit(self.gpa, .OpTypeRuntimeArray, .{
267 .id_result = result_id,
268 .element_type = self.typeResultId(ty.childType()).toRef(),
269 });
270 if (info.array_stride != 0) {
271 try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
272 }
273 },
274 .@"struct" => {
275 const info = ty.payload(.@"struct");
276 try types.emitRaw(self.gpa, .OpTypeStruct, 1 + info.members.len);
277 types.writeOperand(IdResult, result_id);
278 for (info.members) |member| {
279 types.writeOperand(IdRef, self.typeResultId(member.ty).toRef());
280 }
281 try self.decorateStruct(ref_id, info);
282 },
283 .@"opaque" => try types.emit(self.gpa, .OpTypeOpaque, .{
284 .id_result = result_id,
285 .literal_string = ty.payload(.@"opaque").name,
286 }),
287 .pointer => {
288 const info = ty.payload(.pointer);
289 try types.emit(self.gpa, .OpTypePointer, .{
290 .id_result = result_id,
291 .storage_class = info.storage_class,
292 .type = self.typeResultId(ty.childType()).toRef(),
293 });
294 if (info.array_stride != 0) {
295 try annotations.decorate(self.gpa, ref_id, .{ .ArrayStride = .{ .array_stride = info.array_stride } });
296 }
297 if (info.alignment) |alignment| {
298 try annotations.decorate(self.gpa, ref_id, .{ .Alignment = .{ .alignment = alignment } });
299 }
300 if (info.max_byte_offset) |max_byte_offset| {
301 try annotations.decorate(self.gpa, ref_id, .{ .MaxByteOffset = .{ .max_byte_offset = max_byte_offset } });
302 }
303 },
304 .function => {
305 const info = ty.payload(.function);
306 try types.emitRaw(self.gpa, .OpTypeFunction, 2 + info.parameters.len);
307 types.writeOperand(IdResult, result_id);
308 types.writeOperand(IdRef, self.typeResultId(info.return_type).toRef());
309 for (info.parameters) |parameter_type| {
310 types.writeOperand(IdRef, self.typeResultId(parameter_type).toRef());
311 }
312 },
313 .event => try types.emit(self.gpa, .OpTypeEvent, result_id_operand),
314 .device_event => try types.emit(self.gpa, .OpTypeDeviceEvent, result_id_operand),
315 .reserve_id => try types.emit(self.gpa, .OpTypeReserveId, result_id_operand),
316 .queue => try types.emit(self.gpa, .OpTypeQueue, result_id_operand),
317 .pipe => try types.emit(self.gpa, .OpTypePipe, .{
318 .id_result = result_id,
319 .qualifier = ty.payload(.pipe).qualifier,
320 }),
321 .pipe_storage => try types.emit(self.gpa, .OpTypePipeStorage, result_id_operand),
322 .named_barrier => try types.emit(self.gpa, .OpTypeNamedBarrier, result_id_operand),
323 }
324
325 return result_id.toResultType();
326}
327
328fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct) !void {
329 const annotations = &self.sections.annotations;
330
331 // Decorations for the struct type itself.
332 if (info.decorations.block)
333 try annotations.decorate(self.gpa, target, .Block);
334 if (info.decorations.buffer_block)
335 try annotations.decorate(self.gpa, target, .BufferBlock);
336 if (info.decorations.glsl_shared)
337 try annotations.decorate(self.gpa, target, .GLSLShared);
338 if (info.decorations.glsl_packed)
339 try annotations.decorate(self.gpa, target, .GLSLPacked);
340 if (info.decorations.c_packed)
341 try annotations.decorate(self.gpa, target, .CPacked);
342
343 // Decorations for the struct members.
344 const extra = info.member_decoration_extra;
345 var extra_i: u32 = 0;
346 for (info.members) |member, i| {
347 const d = member.decorations;
348 const index = @intCast(Word, i);
349 switch (d.matrix_layout) {
350 .row_major => try annotations.decorateMember(self.gpa, target, index, .RowMajor),
351 .col_major => try annotations.decorateMember(self.gpa, target, index, .ColMajor),
352 .none => {},
353 }
354 if (d.matrix_layout != .none) {
355 try annotations.decorateMember(self.gpa, target, index, .{
356 .MatrixStride = .{ .matrix_stride = extra[extra_i] },
357 });
358 extra_i += 1;
359 }
360
361 if (d.no_perspective)
362 try annotations.decorateMember(self.gpa, target, index, .NoPerspective);
363 if (d.flat)
364 try annotations.decorateMember(self.gpa, target, index, .Flat);
365 if (d.patch)
366 try annotations.decorateMember(self.gpa, target, index, .Patch);
367 if (d.centroid)
368 try annotations.decorateMember(self.gpa, target, index, .Centroid);
369 if (d.sample)
370 try annotations.decorateMember(self.gpa, target, index, .Sample);
371 if (d.invariant)
372 try annotations.decorateMember(self.gpa, target, index, .Invariant);
373 if (d.@"volatile")
374 try annotations.decorateMember(self.gpa, target, index, .Volatile);
375 if (d.coherent)
376 try annotations.decorateMember(self.gpa, target, index, .Coherent);
377 if (d.non_writable)
378 try annotations.decorateMember(self.gpa, target, index, .NonWritable);
379 if (d.non_readable)
380 try annotations.decorateMember(self.gpa, target, index, .NonReadable);
381
382 if (d.builtin) {
383 try annotations.decorateMember(self.gpa, target, index, .{
384 .BuiltIn = .{ .built_in = @intToEnum(spec.BuiltIn, extra[extra_i]) },
385 });
386 extra_i += 1;
387 }
388 if (d.stream) {
389 try annotations.decorateMember(self.gpa, target, index, .{
390 .Stream = .{ .stream_number = extra[extra_i] },
391 });
392 extra_i += 1;
393 }
394 if (d.location) {
395 try annotations.decorateMember(self.gpa, target, index, .{
396 .Location = .{ .location = extra[extra_i] },
397 });
398 extra_i += 1;
399 }
400 if (d.component) {
401 try annotations.decorateMember(self.gpa, target, index, .{
402 .Component = .{ .component = extra[extra_i] },
403 });
404 extra_i += 1;
405 }
406 if (d.xfb_buffer) {
407 try annotations.decorateMember(self.gpa, target, index, .{
408 .XfbBuffer = .{ .xfb_buffer_number = extra[extra_i] },
409 });
410 extra_i += 1;
411 }
412 if (d.xfb_stride) {
413 try annotations.decorateMember(self.gpa, target, index, .{
414 .XfbStride = .{ .xfb_stride = extra[extra_i] },
415 });
416 extra_i += 1;
417 }
418 if (d.user_semantic) {
419 const len = extra[extra_i];
420 extra_i += 1;
421 const semantic = @ptrCast([*]const u8, &extra[extra_i])[0..len];
422 try annotations.decorateMember(self.gpa, target, index, .{
423 .UserSemantic = .{ .semantic = semantic },
424 });
425 extra_i += std.math.divCeil(u32, extra_i, @sizeOf(u32)) catch unreachable;
426 }
427 }
428}
src/codegen/spirv/Section.zig+38-17
...@@ -32,11 +32,7 @@ pub fn toWords(section: Section) []Word {...@@ -32,11 +32,7 @@ pub fn toWords(section: Section) []Word {
32}32}
3333
34/// Append the instructions from another section into this section.34/// Append the instructions from another section into this section.
35pub fn append(35pub fn append(section: *Section, allocator: Allocator, other_section: Section) !void {
36 section: *Section,
37 allocator: Allocator,
38 other_section: Section
39) !void {
40 try section.instructions.appendSlice(allocator, other_section.instructions.items);36 try section.instructions.appendSlice(allocator, other_section.instructions.items);
41}37}
4238
...@@ -64,6 +60,34 @@ pub fn emit(...@@ -64,6 +60,34 @@ pub fn emit(
64 section.writeOperands(opcode.Operands(), operands);60 section.writeOperands(opcode.Operands(), operands);
65}61}
6662
63/// Decorate a result-id.
64pub fn decorate(
65 section: *Section,
66 allocator: Allocator,
67 target: spec.IdRef,
68 decoration: spec.Decoration.Extended,
69) !void {
70 try section.emit(allocator, .OpDecorate, .{
71 .target = target,
72 .decoration = decoration,
73 });
74}
75
76/// Decorate a result-id which is a member of some struct.
77pub fn decorateMember(
78 section: *Section,
79 allocator: Allocator,
80 structure_type: spec.IdRef,
81 member: u32,
82 decoration: spec.Decoration.Extended,
83) !void {
84 try section.emit(allocator, .OpMemberDecorate, .{
85 .structure_type = structure_type,
86 .member = member,
87 .decoration = decoration,
88 });
89}
90
67pub fn writeWord(section: *Section, word: Word) void {91pub fn writeWord(section: *Section, word: Word) void {
68 section.instructions.appendAssumeCapacity(word);92 section.instructions.appendAssumeCapacity(word);
69}93}
...@@ -93,10 +117,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)...@@ -93,10 +117,7 @@ fn writeOperands(section: *Section, comptime Operands: type, operands: Operands)
93117
94pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {118pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void {
95 switch (Operand) {119 switch (Operand) {
96 spec.IdResultType,120 spec.IdResultType, spec.IdResult, spec.IdRef => section.writeWord(operand.id),
97 spec.IdResult,
98 spec.IdRef
99 => section.writeWord(operand.id),
100121
101 spec.LiteralInteger => section.writeWord(operand),122 spec.LiteralInteger => section.writeWord(operand),
102123
...@@ -320,8 +341,8 @@ test "SPIR-V Section emit() - simple" {...@@ -320,8 +341,8 @@ test "SPIR-V Section emit() - simple" {
320 defer section.deinit(std.testing.allocator);341 defer section.deinit(std.testing.allocator);
321342
322 try section.emit(std.testing.allocator, .OpUndef, .{343 try section.emit(std.testing.allocator, .OpUndef, .{
323 .id_result_type = .{.id = 0},344 .id_result_type = .{ .id = 0 },
324 .id_result = .{.id = 1},345 .id_result = .{ .id = 1 },
325 });346 });
326347
327 try testing.expectEqualSlices(Word, &.{348 try testing.expectEqualSlices(Word, &.{
...@@ -338,7 +359,7 @@ test "SPIR-V Section emit() - string" {...@@ -338,7 +359,7 @@ test "SPIR-V Section emit() - string" {
338 try section.emit(std.testing.allocator, .OpSource, .{359 try section.emit(std.testing.allocator, .OpSource, .{
339 .source_language = .Unknown,360 .source_language = .Unknown,
340 .version = 123,361 .version = 123,
341 .file = .{.id = 456},362 .file = .{ .id = 456 },
342 .source = "pub fn main() void {}",363 .source = "pub fn main() void {}",
343 });364 });
344365
...@@ -361,8 +382,8 @@ test "SPIR-V Section emit()- extended mask" {...@@ -361,8 +382,8 @@ test "SPIR-V Section emit()- extended mask" {
361 defer section.deinit(std.testing.allocator);382 defer section.deinit(std.testing.allocator);
362383
363 try section.emit(std.testing.allocator, .OpLoopMerge, .{384 try section.emit(std.testing.allocator, .OpLoopMerge, .{
364 .merge_block = .{.id = 10},385 .merge_block = .{ .id = 10 },
365 .continue_target = .{.id = 20},386 .continue_target = .{ .id = 20 },
366 .loop_control = .{387 .loop_control = .{
367 .Unroll = true,388 .Unroll = true,
368 .DependencyLength = .{389 .DependencyLength = .{
...@@ -375,7 +396,7 @@ test "SPIR-V Section emit()- extended mask" {...@@ -375,7 +396,7 @@ test "SPIR-V Section emit()- extended mask" {
375 (@as(Word, 5) << 16) | @enumToInt(Opcode.OpLoopMerge),396 (@as(Word, 5) << 16) | @enumToInt(Opcode.OpLoopMerge),
376 10,397 10,
377 20,398 20,
378 @bitCast(Word, spec.LoopControl{.Unroll = true, .DependencyLength = true}),399 @bitCast(Word, spec.LoopControl{ .Unroll = true, .DependencyLength = true }),
379 2,400 2,
380 }, section.instructions.items);401 }, section.instructions.items);
381}402}
...@@ -385,9 +406,9 @@ test "SPIR-V Section emit() - extended union" {...@@ -385,9 +406,9 @@ test "SPIR-V Section emit() - extended union" {
385 defer section.deinit(std.testing.allocator);406 defer section.deinit(std.testing.allocator);
386407
387 try section.emit(std.testing.allocator, .OpExecutionMode, .{408 try section.emit(std.testing.allocator, .OpExecutionMode, .{
388 .entry_point = .{.id = 888},409 .entry_point = .{ .id = 888 },
389 .mode = .{410 .mode = .{
390 .LocalSize = .{.x_size = 4, .y_size = 8, .z_size = 16},411 .LocalSize = .{ .x_size = 4, .y_size = 8, .z_size = 16 },
391 },412 },
392 });413 });
393414
src/codegen/spirv/type.zig created+433
...@@ -0,0 +1,433 @@
1//! This module models a SPIR-V Type. These are distinct from Zig types, with some types
2//! which are not representable by Zig directly.
3
4const std = @import("std");
5const assert = std.debug.assert;
6
7const spec = @import("spec.zig");
8
9pub const Type = extern union {
10 tag_if_small_enough: Tag,
11 ptr_otherwise: *Payload,
12
13 /// A reference to another SPIR-V type.
14 pub const Ref = usize;
15
16 pub fn initTag(comptime small_tag: Tag) Type {
17 comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
18 return .{ .tag_if_small_enough = small_tag };
19 }
20
21 pub fn initPayload(pl: *Payload) Type {
22 assert(@enumToInt(pl.tag) >= Tag.no_payload_count);
23 return .{ .ptr_otherwise = pl };
24 }
25
26 pub fn tag(self: Type) Tag {
27 if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count) {
28 return self.tag_if_small_enough;
29 } else {
30 return self.ptr_otherwise.tag;
31 }
32 }
33
34 pub fn castTag(self: Type, comptime t: Tag) ?*t.Type() {
35 if (@enumToInt(self.tag_if_small_enough) < Tag.no_payload_count)
36 return null;
37
38 if (self.ptr_otherwise.tag == t)
39 return self.payload(t);
40
41 return null;
42 }
43
44 /// Access the payload of a type directly.
45 pub fn payload(self: Type, comptime t: Tag) *t.Type() {
46 assert(self.tag() == t);
47 return @fieldParentPtr(t.Type(), "base", self.ptr_otherwise);
48 }
49
50 /// Perform a shallow equality test, comparing two types while assuming that any child types
51 /// are equal only if their references are equal.
52 pub fn eqlShallow(a: Type, b: Type) bool {
53 if (a.tag_if_small_enough == b.tag_if_small_enough)
54 return true;
55
56 const tag_a = a.tag();
57 const tag_b = b.tag();
58 if (tag_a != tag_b)
59 return false;
60
61 inline for (@typeInfo(Tag).Enum.fields) |field| {
62 const t = @field(Tag, field.name);
63 if (t == tag_a) {
64 return eqlPayloads(t, a, b);
65 }
66 }
67
68 unreachable;
69 }
70
71 /// Compare the payload of two compatible tags, given that we already know the tag of both types.
72 fn eqlPayloads(comptime t: Tag, a: Type, b: Type) bool {
73 switch (t) {
74 .void,
75 .bool,
76 .sampler,
77 .event,
78 .device_event,
79 .reserve_id,
80 .queue,
81 .pipe_storage,
82 .named_barrier,
83 => return true,
84 .int,
85 .float,
86 .vector,
87 .matrix,
88 .sampled_image,
89 .array,
90 .runtime_array,
91 .@"opaque",
92 .pointer,
93 .pipe,
94 .image,
95 => return std.meta.eql(a.payload(t).*, b.payload(t).*),
96 .@"struct" => {
97 const struct_a = a.payload(.@"struct");
98 const struct_b = b.payload(.@"struct");
99 if (struct_a.members.len != struct_b.members.len)
100 return false;
101 for (struct_a.members) |mem_a, i| {
102 if (!std.meta.eql(mem_a, struct_b.members[i]))
103 return false;
104 }
105 return true;
106 },
107 .@"function" => {
108 const fn_a = a.payload(.function);
109 const fn_b = b.payload(.function);
110 if (fn_a.return_type != fn_b.return_type)
111 return false;
112 return std.mem.eql(Ref, fn_a.parameters, fn_b.parameters);
113 },
114 }
115 }
116
117 /// Perform a shallow hash, which hashes the reference value of child types instead of recursing.
118 pub fn hashShallow(self: Type) u64 {
119 var hasher = std.hash.Wyhash.init(0);
120 const t = self.tag();
121 std.hash.autoHash(&hasher, t);
122
123 inline for (@typeInfo(Tag).Enum.fields) |field| {
124 if (@field(Tag, field.name) == t) {
125 switch (@field(Tag, field.name)) {
126 .void,
127 .bool,
128 .sampler,
129 .event,
130 .device_event,
131 .reserve_id,
132 .queue,
133 .pipe_storage,
134 .named_barrier,
135 => {},
136 else => self.hashPayload(@field(Tag, field.name), &hasher),
137 }
138 }
139 }
140
141 return hasher.final();
142 }
143
144 /// Perform a shallow hash, given that we know the tag of the field ahead of time.
145 fn hashPayload(self: Type, comptime t: Tag, hasher: *std.hash.Wyhash) void {
146 const fields = @typeInfo(t.Type()).Struct.fields;
147 const pl = self.payload(t);
148 comptime assert(std.mem.eql(u8, fields[0].name, "base"));
149 inline for (fields[1..]) |field| { // Skip the 'base' field.
150 std.hash.autoHashStrat(hasher, @field(pl, field.name), .DeepRecursive);
151 }
152 }
153
154 /// Hash context that hashes and compares types in a shallow fashion, useful for type caches.
155 pub const ShallowHashContext32 = struct {
156 pub fn hash(self: @This(), t: Type) u32 {
157 _ = self;
158 return @truncate(u32, t.hashShallow());
159 }
160 pub fn eql(self: @This(), a: Type, b: Type) bool {
161 _ = self;
162 return a.eqlShallow(b);
163 }
164 };
165
166 /// Return the reference to any child type. Asserts the type is one of:
167 /// - Vectors
168 /// - Matrices
169 /// - Images
170 /// - SampledImages,
171 /// - Arrays
172 /// - RuntimeArrays
173 /// - Pointers
174 pub fn childType(self: Type) Ref {
175 return switch (self.tag()) {
176 .vector => self.payload(.vector).component_type,
177 .matrix => self.payload(.matrix).column_type,
178 .image => self.payload(.image).sampled_type,
179 .sampled_image => self.payload(.sampled_image).image_type,
180 .array => self.payload(.array).element_type,
181 .runtime_array => self.payload(.runtime_array).element_type,
182 .pointer => self.payload(.pointer).child_type,
183 else => unreachable,
184 };
185 }
186
187 pub const Tag = enum(usize) {
188 void,
189 bool,
190 sampler,
191 event,
192 device_event,
193 reserve_id,
194 queue,
195 pipe_storage,
196 named_barrier,
197
198 // After this, the tag requires a payload.
199 int,
200 float,
201 vector,
202 matrix,
203 image,
204 sampled_image,
205 array,
206 runtime_array,
207 @"struct",
208 @"opaque",
209 pointer,
210 function,
211 pipe,
212
213 pub const last_no_payload_tag = Tag.named_barrier;
214 pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
215
216 pub fn Type(comptime t: Tag) type {
217 return switch (t) {
218 .void, .bool, .sampler, .event, .device_event, .reserve_id, .queue, .pipe_storage, .named_barrier => @compileError("Type Tag " ++ @tagName(t) ++ " has no payload"),
219 .int => Payload.Int,
220 .float => Payload.Float,
221 .vector => Payload.Vector,
222 .matrix => Payload.Matrix,
223 .image => Payload.Image,
224 .sampled_image => Payload.SampledImage,
225 .array => Payload.Array,
226 .runtime_array => Payload.RuntimeArray,
227 .@"struct" => Payload.Struct,
228 .@"opaque" => Payload.Opaque,
229 .pointer => Payload.Pointer,
230 .function => Payload.Function,
231 .pipe => Payload.Pipe,
232 };
233 }
234 };
235
236 pub const Payload = struct {
237 tag: Tag,
238
239 pub const Int = struct {
240 base: Payload = .{ .tag = .int },
241 width: u32,
242 signedness: std.builtin.Signedness,
243 };
244
245 pub const Float = struct {
246 base: Payload = .{ .tag = .float },
247 width: u32,
248 };
249
250 pub const Vector = struct {
251 base: Payload = .{ .tag = .vector },
252 component_type: Ref,
253 component_count: u32,
254 };
255
256 pub const Matrix = struct {
257 base: Payload = .{ .tag = .matrix },
258 column_type: Ref,
259 column_count: u32,
260 };
261
262 pub const Image = struct {
263 base: Payload = .{ .tag = .image },
264 sampled_type: Ref,
265 dim: spec.Dim,
266 depth: enum(u2) {
267 no = 0,
268 yes = 1,
269 maybe = 2,
270 },
271 arrayed: bool,
272 multisampled: bool,
273 sampled: enum(u2) {
274 known_at_runtime = 0,
275 with_sampler = 1,
276 without_sampler = 2,
277 },
278 format: spec.ImageFormat,
279 access_qualifier: ?spec.AccessQualifier,
280 };
281
282 pub const SampledImage = struct {
283 base: Payload = .{ .tag = .sampled_image },
284 image_type: Ref,
285 };
286
287 pub const Array = struct {
288 base: Payload = .{ .tag = .array },
289 element_type: Ref,
290 /// Note: Must be emitted as constant, not as literal!
291 length: u32,
292 /// Type has the 'ArrayStride' decoration.
293 /// If zero, no stride is present.
294 array_stride: u32,
295 };
296
297 pub const RuntimeArray = struct {
298 base: Payload = .{ .tag = .runtime_array },
299 element_type: Ref,
300 /// Type has the 'ArrayStride' decoration.
301 /// If zero, no stride is present.
302 array_stride: u32,
303 };
304
305 pub const Struct = struct {
306 base: Payload = .{ .tag = .@"struct" },
307 members: []Member,
308 decorations: StructDecorations,
309
310 /// Extra information for decorations, packed for efficiency. Fields are stored sequentially by
311 /// order of the `members` slice and `MemberDecorations` struct.
312 member_decoration_extra: []u32,
313
314 pub const Member = struct {
315 ty: Ref,
316 offset: u32,
317 decorations: MemberDecorations,
318 };
319
320 pub const StructDecorations = packed struct {
321 /// Type has the 'Block' decoration.
322 block: bool,
323 /// Type has the 'BufferBlock' decoration.
324 buffer_block: bool,
325 /// Type has the 'GLSLShared' decoration.
326 glsl_shared: bool,
327 /// Type has the 'GLSLPacked' decoration.
328 glsl_packed: bool,
329 /// Type has the 'CPacked' decoration.
330 c_packed: bool,
331 };
332
333 pub const MemberDecorations = packed struct {
334 /// Matrix layout for (arrays of) matrices. If this field is not .none,
335 /// then there is also an extra field containing the matrix stride corresponding
336 /// to the 'MatrixStride' decoration.
337 matrix_layout: enum(u2) {
338 /// Member has the 'RowMajor' decoration. The member type
339 /// must be a matrix or an array of matrices.
340 row_major,
341 /// Member has the 'ColMajor' decoration. The member type
342 /// must be a matrix or an array of matrices.
343 col_major,
344 /// Member is not a matrix or array of matrices.
345 none,
346 },
347
348 // Regular decorations, these do not imply extra fields.
349
350 /// Member has the 'NoPerspective' decoration.
351 no_perspective: bool,
352 /// Member has the 'Flat' decoration.
353 flat: bool,
354 /// Member has the 'Patch' decoration.
355 patch: bool,
356 /// Member has the 'Centroid' decoration.
357 centroid: bool,
358 /// Member has the 'Sample' decoration.
359 sample: bool,
360 /// Member has the 'Invariant' decoration.
361 /// Note: requires parent struct to have 'Block'.
362 invariant: bool,
363 /// Member has the 'Volatile' decoration.
364 @"volatile": bool,
365 /// Member has the 'Coherent' decoration.
366 coherent: bool,
367 /// Member has the 'NonWritable' decoration.
368 non_writable: bool,
369 /// Member has the 'NonReadable' decoration.
370 non_readable: bool,
371
372 // The following decorations all imply extra field(s).
373
374 /// Member has the 'BuiltIn' decoration.
375 /// This decoration has an extra field of type `spec.BuiltIn`.
376 /// Note: If any member of a struct has the BuiltIn decoration, all members must have one.
377 /// Note: Each builtin may only be reachable once for a particular entry point.
378 /// Note: The member type may be constrained by a particular built-in, defined in the client API specification.
379 builtin: bool,
380 /// Member has the 'Stream' decoration.
381 /// This member has an extra field of type `u32`.
382 stream: bool,
383 /// Member has the 'Location' decoration.
384 /// This member has an extra field of type `u32`.
385 location: bool,
386 /// Member has the 'Component' decoration.
387 /// This member has an extra field of type `u32`.
388 component: bool,
389 /// Member has the 'XfbBuffer' decoration.
390 /// This member has an extra field of type `u32`.
391 xfb_buffer: bool,
392 /// Member has the 'XfbStride' decoration.
393 /// This member has an extra field of type `u32`.
394 xfb_stride: bool,
395 /// Member has the 'UserSemantic' decoration.
396 /// This member has an extra field of type `[]u8`, which is encoded
397 /// by an `u32` containing the number of chars exactly, and then the string padded to
398 /// a multiple of 4 bytes with zeroes.
399 user_semantic: bool,
400 };
401 };
402
403 pub const Opaque = struct {
404 base: Payload = .{ .tag = .@"opaque" },
405 name: []u8,
406 };
407
408 pub const Pointer = struct {
409 base: Payload = .{ .tag = .pointer },
410 storage_class: spec.StorageClass,
411 child_type: Ref,
412 /// Type has the 'ArrayStride' decoration.
413 /// This is valid for pointers to elements of an array.
414 /// If zero, no stride is present.
415 array_stride: u32,
416 /// Type has the 'Alignment' decoration.
417 alignment: ?u32,
418 /// Type has the 'MaxByteOffset' decoration.
419 max_byte_offset: ?u32,
420 };
421
422 pub const Function = struct {
423 base: Payload = .{ .tag = .function },
424 return_type: Ref,
425 parameters: []Ref,
426 };
427
428 pub const Pipe = struct {
429 base: Payload = .{ .tag = .pipe },
430 qualifier: spec.AccessQualifier,
431 };
432 };
433};