| author | |
| committer | |
| log | 98ee39d1b0ed516428c611d8dc1e52d21c786f97 |
| tree | a5667f3012952c1301b5d8631d86b753d0f71704 |
| parent | 1b6ebce0da45169979b0f51a07274ff6fb5590bc |
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; |
| 21 | 21 | ||
| 22 | const SpvModule = @import("spirv/Module.zig"); | 22 | const SpvModule = @import("spirv/Module.zig"); |
| 23 | const SpvSection = @import("spirv/Section.zig"); | 23 | const SpvSection = @import("spirv/Section.zig"); |
| 24 | const SpvType = @import("spirv/type.zig").Type; | ||
| 24 | 25 | ||
| 25 | const TypeCache = std.HashMapUnmanaged(Type, IdResultType, Type.HashContext64, std.hash_map.default_max_load_percentage); | ||
| 26 | const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef); | 26 | const InstMap = std.AutoHashMapUnmanaged(Air.Inst.Index, IdRef); |
| 27 | 27 | ||
| 28 | const IncomingBlock = struct { | 28 | const 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, |
| 63 | 63 | ||
| 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 = .{}, |
| 70 | 66 | ||
| ... | @@ -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 to | 214 | /// 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 | } |
| 226 | 220 | ||
| ... | @@ -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 | } |
| 325 | 319 | ||
| ... | @@ -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); |
| 333 | 327 | ||
| 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 | }; |
| 346 | 340 | ||
| 347 | // We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any | 341 | // 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(); |
| 355 | 349 | ||
| 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 | }; |
| 361 | 355 | ||
| ... | @@ -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 function | 371 | // 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). |
| 379 | 373 | ||
| 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 | } |
| 399 | 393 | ||
| 400 | return result_id.toRef(); | 394 | return result_id.toRef(); |
| 401 | } | 395 | } |
| 402 | 396 | ||
| 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 | } | ||
| 408 | 401 | ||
| 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)), |
| 412 | 407 | .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 | }; |
| 422 | 419 | ||
| 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 | } |
| 448 | 442 | ||
| 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()); | ||
| 467 | 455 | ||
| 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 | } | ||
| 469 | 460 | ||
| 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); | ||
| 473 | 462 | ||
| 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 operations | 472 | // 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. |
| 489 | 478 | ||
| 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. |
| 500 | 490 | ||
| 501 | .BoundFn => unreachable, // this type will be deleted from the language. | 491 | .BoundFn => unreachable, // this type will be deleted from the language. |
| 502 | 492 | ||
| 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 | } |
| 509 | 496 | ||
| 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 types | 504 | // 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()); |
| 521 | 508 | ||
| 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 { |
| 534 | 521 | ||
| 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 { |
| 547 | 534 | ||
| 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 | } |
| 579 | 567 | ||
| ... | @@ -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: on | 611 | // zig fmt: on |
| 624 | 612 | ||
| 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); |
| 655 | 643 | ||
| 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); |
| 658 | 646 | ||
| 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 { |
| 665 | 653 | ||
| 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))); |
| 708 | 696 | ||
| ... | @@ -712,10 +700,10 @@ pub const DeclGen = struct { | ... | @@ -712,10 +700,10 @@ pub const DeclGen = struct { |
| 712 | 700 | ||
| 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(); |
| 814 | 802 | ||
| 815 | // TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types | 803 | // 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 throws | 804 | // 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; |
| 820 | 808 | ||
| 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 | } |
| 840 | 828 | ||
| 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 | } |
| 843 | 831 | ||
| 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); |
| 884 | 872 | ||
| 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(); |
| 887 | 875 | ||
| 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(); |
| 907 | 895 | ||
| 908 | // Jump to the loop entry point | 896 | // 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() }); |
| 910 | 898 | ||
| 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); |
| 914 | 902 | ||
| 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 | } |
| 917 | 905 | ||
| 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(); |
| 9 | 9 | ||
| 10 | const std = @import("std"); | 10 | const std = @import("std"); |
| 11 | const Allocator = std.mem.Allocator; | 11 | const Allocator = std.mem.Allocator; |
| 12 | const assert = std.debug.assert; | ||
| 12 | 13 | ||
| 13 | const ZigDecl = @import("../../Module.zig").Decl; | 14 | const ZigDecl = @import("../../Module.zig").Decl; |
| 14 | 15 | ||
| 15 | const spec = @import("spec.zig"); | 16 | const spec = @import("spec.zig"); |
| 16 | const Word = spec.Word; | 17 | const Word = spec.Word; |
| 17 | const IdRef = spec.IdRef; | 18 | const IdRef = spec.IdRef; |
| 19 | const IdResult = spec.IdResult; | ||
| 20 | const IdResultType = spec.IdResultType; | ||
| 18 | 21 | ||
| 19 | const Section = @import("Section.zig"); | 22 | const Section = @import("Section.zig"); |
| 23 | const Type = @import("type.zig").Type; | ||
| 24 | |||
| 25 | const TypeCache = std.ArrayHashMapUnmanaged(Type, IdResultType, Type.ShallowHashContext32, true); | ||
| 20 | 26 | ||
| 21 | /// A general-purpose allocator which may be used to allocate resources for this module | 27 | /// A general-purpose allocator which may be used to allocate resources for this module |
| 22 | gpa: Allocator, | 28 | gpa: 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. |
| 58 | source_file_names: std.StringHashMapUnmanaged(IdRef) = .{}, | 64 | source_file_names: std.StringHashMapUnmanaged(IdRef) = .{}, |
| 59 | 65 | ||
| 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). | ||
| 70 | type_cache: TypeCache = .{}, | ||
| 71 | |||
| 60 | pub fn init(gpa: Allocator, arena: Allocator) Module { | 72 | pub 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); |
| 76 | 88 | ||
| 77 | self.source_file_names.deinit(self.gpa); | 89 | self.source_file_names.deinit(self.gpa); |
| 90 | self.type_cache.deinit(self.gpa); | ||
| 78 | 91 | ||
| 79 | self.* = undefined; | 92 | self.* = undefined; |
| 80 | } | 93 | } |
| 81 | 94 | ||
| 82 | pub fn allocId(self: *Module) spec.IdResult { | 95 | pub 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 | } |
| 86 | 99 | ||
| 87 | pub fn idBound(self: Module) Word { | 100 | pub fn idBound(self: Module) Word { |
| 88 | return self.next_result_id; | 101 | return self.next_result_id; |
| 89 | } | 102 | } |
| 90 | 103 | ||
| 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. | ||
| 94 | pub 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. |
| 117 | pub fn flush(self: Module, file: std.fs.File) !void { | 105 | pub 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. | ||
| 146 | pub 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. | ||
| 173 | pub 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 | |||
| 181 | pub 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. | ||
| 186 | pub 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. | ||
| 191 | pub 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. | ||
| 201 | pub 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 | |||
| 328 | fn 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 | } |
| 33 | 33 | ||
| 34 | /// Append the instructions from another section into this section. | 34 | /// Append the instructions from another section into this section. |
| 35 | pub fn append( | 35 | pub 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 | } |
| 42 | 38 | ||
| ... | @@ -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 | } |
| 66 | 62 | ||
| 63 | /// Decorate a result-id. | ||
| 64 | pub 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. | ||
| 77 | pub 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 | |||
| 67 | pub fn writeWord(section: *Section, word: Word) void { | 91 | pub 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) |
| 93 | 117 | ||
| 94 | pub fn writeOperand(section: *Section, comptime Operand: type, operand: Operand) void { | 118 | pub 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), | ||
| 100 | 121 | ||
| 101 | spec.LiteralInteger => section.writeWord(operand), | 122 | spec.LiteralInteger => section.writeWord(operand), |
| 102 | 123 | ||
| ... | @@ -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); |
| 321 | 342 | ||
| 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 | }); |
| 326 | 347 | ||
| 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 | }); |
| 344 | 365 | ||
| ... | @@ -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); |
| 362 | 383 | ||
| 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); |
| 386 | 407 | ||
| 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 | }); |
| 393 | 414 |
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 | |||
| 4 | const std = @import("std"); | ||
| 5 | const assert = std.debug.assert; | ||
| 6 | |||
| 7 | const spec = @import("spec.zig"); | ||
| 8 | |||
| 9 | pub 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 | }; | ||