authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-03-29 01:12:05+02:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:51:53+02:00
log80b84355692606ac840584baa62aaafdd8ecd425
treecdd9ef56bddb4c7d40e841d9b7accfa67e9f4bc9
parent764f19034d9aa74ce2220937d090c60f8f8bf919
signaturelock-open Commit is signed but in an unrecognized format.

spirv: overhaul constant lowering

Lowering constants is currently not really compatible with unions. In this commit, constant lowering is drastically overhauled: instead of playing nice and generating SPIR-V constant representations for everything directly, we're just going to treat globals as an untyped bag of bytes ( or rather, SPIR-V 32-bit words), which we cast to the desired type at usage. This is similar to how Rust generates constants in its LLVm backend.

3 files changed, 550 insertions(+), 314 deletions(-)

src/codegen/spirv.zig+496-312
...@@ -238,7 +238,7 @@ pub const DeclGen = struct {...@@ -238,7 +238,7 @@ pub const DeclGen = struct {
238 return try self.resolveDecl(fn_decl_index);238 return try self.resolveDecl(fn_decl_index);
239 }239 }
240240
241 return try self.constant(ty, val, .direct);241 return try self.constant(ty, val);
242 }242 }
243 const index = Air.refToIndex(inst).?;243 const index = Air.refToIndex(inst).?;
244 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.244 return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
...@@ -404,320 +404,493 @@ pub const DeclGen = struct {...@@ -404,320 +404,493 @@ pub const DeclGen = struct {
404 return result_id;404 return result_id;
405 }405 }
406406
407 fn constant(self: *DeclGen, ty: Type, val: Value, repr: Repr) Error!IdRef {407 const IndirectConstantLowering = struct {
408 const result_id = self.spv.allocId();408 const undef = 0xAA;
409 try self.genConstant(result_id, ty, val, repr);409
410 return result_id;410 dg: *DeclGen,
411 }411 /// Cached reference of the u32 type.
412 u32_ty_ref: SpvType.Ref,
413 /// Cached type id of the u32 type.
414 u32_ty_id: IdRef,
415 /// The members of the resulting structure type
416 members: std.ArrayList(SpvType.Payload.Struct.Member),
417 /// The initializers of each of the members.
418 initializers: std.ArrayList(IdRef),
419 /// The current size of the structure. Includes
420 /// the bytes in partial_word.
421 size: u32 = 0,
422 /// The partially filled last constant.
423 /// If full, its flushed.
424 partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{},
425
426 /// Flush the partial_word to the members. If the partial_word is not
427 /// filled, this adds padding bytes (which are undefined).
428 fn flush(self: *@This()) !void {
429 if (self.partial_word.len == 0) {
430 // No need to add it there.
431 return;
432 }
412433
413 /// Generate a constant representing `val`.434 for (self.partial_word.unusedCapacitySlice()) |*unused| {
414 /// TODO: Deduplication?435 // TODO: Perhaps we should generate OpUndef for these bytes?
415 fn genConstant(self: *DeclGen, result_id: IdRef, ty: Type, val: Value, repr: Repr) Error!void {436 unused.* = undef;
416 const target = self.getTarget();437 }
417 const section = &self.spv.sections.types_globals_constants;
418 const result_ty_ref = try self.resolveType(ty, repr);
419 const result_ty_id = self.typeId(result_ty_ref);
420438
421 log.debug("genConstant: ty = {}, val = {}", .{ ty.fmtDebug(), val.fmtDebug() });439 const word = @bitCast(Word, self.partial_word.buffer);
440 const result_id = self.dg.spv.allocId();
441 try self.dg.spv.emitConstant(self.u32_ty_id, result_id, .{ .uint32 = word });
442 try self.members.append(.{ .ty = self.u32_ty_ref });
443 try self.initializers.append(result_id);
422444
423 if (val.isUndef()) {445 self.partial_word.len = 0;
424 try section.emit(self.spv.gpa, .OpUndef, .{ .id_result_type = result_ty_id, .id_result = result_id });446 self.size = std.mem.alignForwardGeneric(u32, self.size, @sizeOf(Word));
425 }447 }
426448
427 switch (ty.zigTypeTag()) {449 /// Fill the buffer with undefined values until the size is aligned to `align`.
428 .Int => {450 fn fillToAlign(self: *@This(), alignment: u32) !void {
429 const int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt(target)) else val.toUnsignedInt(target);451 const target_size = std.mem.alignForwardGeneric(u32, self.size, alignment);
430 try self.genConstInt(result_ty_ref, result_id, int_bits);452 try self.addUndef(target_size - self.size);
431 },453 }
432 .Bool => switch (repr) {
433 .direct => {
434 const operands = .{ .id_result_type = result_ty_id, .id_result = result_id };
435 if (val.toBool()) {
436 try section.emit(self.spv.gpa, .OpConstantTrue, operands);
437 } else {
438 try section.emit(self.spv.gpa, .OpConstantFalse, operands);
439 }
440 },
441 .indirect => try self.genConstInt(result_ty_ref, result_id, @boolToInt(val.toBool())),
442 },
443 .Float => {
444 // At this point we are guaranteed that the target floating point type is supported, otherwise the function
445 // would have exited at resolveTypeId(ty).
446 const literal: spec.LiteralContextDependentNumber = switch (ty.floatBits(target)) {
447 // Prevent upcasting to f32 by bitcasting and writing as a uint32.
448 16 => .{ .uint32 = @bitCast(u16, val.toFloat(f16)) },
449 32 => .{ .float32 = val.toFloat(f32) },
450 64 => .{ .float64 = val.toFloat(f64) },
451 128 => unreachable, // Filtered out in the call to resolveTypeId.
452 // TODO: Insert case for long double when the layout for that is determined?
453 else => unreachable,
454 };
455454
456 try self.spv.emitConstant(result_ty_id, result_id, literal);455 fn addUndef(self: *@This(), amt: u64) !void {
457 },456 for (0..@intCast(usize, amt)) |_| {
458 .Array => switch (val.tag()) {457 try self.addByte(undef);
459 .aggregate => { // todo: combine with Vector458 }
460 const elem_vals = val.castTag(.aggregate).?.data;459 }
461 const elem_ty = ty.elemType();460
462 const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way.461 /// Add a single byte of data to the constant.
463 const constituents = try self.spv.gpa.alloc(IdRef, len);462 fn addByte(self: *@This(), data: u8) !void {
464 defer self.spv.gpa.free(constituents);463 self.partial_word.append(data) catch {
465 for (elem_vals[0..len], 0..) |elem_val, i| {464 try self.flush();
466 constituents[i] = try self.constant(elem_ty, elem_val, repr);465 self.partial_word.append(data) catch unreachable;
467 }466 };
468 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{467 self.size += 1;
469 .id_result_type = result_ty_id,468 }
470 .id_result = result_id,469
471 .constituents = constituents,470 /// Add many bytes of data to the constnat.
472 });471 fn addBytes(self: *@This(), data: []const u8) !void {
473 },472 // TODO: Improve performance by adding in bulk, or something?
474 .repeated => {473 for (data) |byte| {
475 const elem_val = val.castTag(.repeated).?.data;474 try self.addByte(byte);
476 const elem_ty = ty.elemType();475 }
477 const len = @intCast(u32, ty.arrayLen());476 }
478 const total_len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way.477
479 const constituents = try self.spv.gpa.alloc(IdRef, total_len);478 fn addPtr(self: *@This(), ptr_ty_ref: SpvType.Ref, ptr_id: IdRef) !void {
480 defer self.spv.gpa.free(constituents);479 // TODO: Double check pointer sizes here.
481480 // shared pointers might be u32...
482 const elem_val_id = try self.constant(elem_ty, elem_val, repr);481 const target = self.dg.getTarget();
483 for (constituents[0..len]) |*elem| {482 const width = @divExact(target.cpu.arch.ptrBitWidth(), 8);
484 elem.* = elem_val_id;483 if (self.size % width != 0) {
485 }484 return self.dg.todo("misaligned pointer constants", .{});
486 if (ty.sentinel()) |sentinel| {485 }
487 constituents[len] = try self.constant(elem_ty, sentinel, repr);486 try self.members.append(.{ .ty = ptr_ty_ref });
488 }487 try self.initializers.append(ptr_id);
489 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{488 self.size += width;
490 .id_result_type = result_ty_id,489 }
491 .id_result = result_id,490
492 .constituents = constituents,491 fn addNullPtr(self: *@This(), ptr_ty_ref: SpvType.Ref) !void {
493 });492 const result_id = self.dg.spv.allocId();
493 try self.dg.spv.sections.types_globals_constants.emit(self.dg.spv.gpa, .OpConstantNull, .{
494 .id_result_type = self.dg.typeId(ptr_ty_ref),
495 .id_result = result_id,
496 });
497 try self.addPtr(ptr_ty_ref, result_id);
498 }
499
500 fn addConstInt(self: *@This(), comptime T: type, value: T) !void {
501 if (@bitSizeOf(T) % 8 != 0) {
502 @compileError("todo: non byte aligned int constants");
503 }
504
505 // TODO: Swap endianness if the compiler is big endian.
506 try self.addBytes(std.mem.asBytes(&value));
507 }
508
509 fn addConstBool(self: *@This(), value: bool) !void {
510 try self.addByte(@boolToInt(value)); // TODO: Keep in sync with something?
511 }
512
513 fn addInt(self: *@This(), ty: Type, val: Value) !void {
514 const target = self.dg.getTarget();
515 const int_info = ty.intInfo(target);
516 const int_bits = switch (int_info.signedness) {
517 .signed => @bitCast(u64, val.toSignedInt(target)),
518 .unsigned => val.toUnsignedInt(target),
519 };
520
521 // TODO: Swap endianess if the compiler is big endian.
522 const len = ty.abiSize(target);
523 try self.addBytes(std.mem.asBytes(&int_bits)[0..@intCast(usize, len)]);
524 }
525
526 fn lower(self: *@This(), ty: Type, val: Value) !void {
527 const target = self.dg.getTarget();
528 const dg = self.dg;
529
530 switch (ty.zigTypeTag()) {
531 .Int => try self.addInt(ty, val),
532 .Bool => try self.addConstBool(val.toBool()),
533 .Array => switch (val.tag()) {
534 .aggregate => {
535 const elem_vals = val.castTag(.aggregate).?.data;
536 const elem_ty = ty.elemType();
537 const len = @intCast(u32, ty.arrayLenIncludingSentinel()); // TODO: limit spir-v to 32 bit arrays in a more elegant way.
538 for (elem_vals[0..len]) |elem_val| {
539 try self.lower(elem_ty, elem_val);
540 }
541 },
542 .repeated => {
543 const elem_val = val.castTag(.repeated).?.data;
544 const elem_ty = ty.elemType();
545 const len = @intCast(u32, ty.arrayLen());
546 for (0..len) |_| {
547 try self.lower(elem_ty, elem_val);
548 }
549 if (ty.sentinel()) |sentinel| {
550 try self.lower(elem_ty, sentinel);
551 }
552 },
553 .str_lit => {
554 const str_lit = val.castTag(.str_lit).?.data;
555 const bytes = dg.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len];
556 try self.addBytes(bytes);
557 if (ty.sentinel()) |sentinel| {
558 try self.addByte(@intCast(u8, sentinel.toUnsignedInt(target)));
559 }
560 },
561 else => |tag| return dg.todo("indirect array constant with tag {s}", .{@tagName(tag)}),
494 },562 },
495 .str_lit => {563 .Pointer => switch (val.tag()) {
496 // TODO: This is very efficient code generation, should probably implement constant caching for this.564 .decl_ref_mut => {
497 const str_lit = val.castTag(.str_lit).?.data;565 const ptr_ty_ref = try dg.resolveType(ty, .indirect);
498 const bytes = self.module.string_literal_bytes.items[str_lit.index..][0..str_lit.len];566 const ptr_id = dg.spv.allocId();
499 const elem_ty = ty.elemType();567 const decl_index = val.castTag(.decl_ref_mut).?.data.decl_index;
500 const elem_ty_id = try self.resolveTypeId(elem_ty);568 try dg.genDeclRef(ptr_ty_ref, ptr_id, decl_index);
501 const len = @intCast(u32, ty.arrayLen());569 try self.addPtr(ptr_ty_ref, ptr_id);
502 const total_len = @intCast(u32, ty.arrayLenIncludingSentinel());570 },
503 const constituents = try self.spv.gpa.alloc(IdRef, total_len);571 .decl_ref => {
504 defer self.spv.gpa.free(constituents);572 const ptr_ty_ref = try dg.resolveType(ty, .indirect);
505 for (bytes, 0..) |byte, i| {573 const ptr_id = dg.spv.allocId();
506 constituents[i] = self.spv.allocId();574 const decl_index = val.castTag(.decl_ref).?.data;
507 try self.spv.emitConstant(elem_ty_id, constituents[i], .{ .uint32 = byte });575 try dg.genDeclRef(ptr_ty_ref, ptr_id, decl_index);
508 }576 try self.addPtr(ptr_ty_ref, ptr_id);
509 if (ty.sentinel()) |sentinel| {577 },
510 constituents[len] = self.spv.allocId();578 .slice => {
511 const byte = @intCast(u8, sentinel.toUnsignedInt(target));579 const slice = val.castTag(.slice).?.data;
512 try self.spv.emitConstant(elem_ty_id, constituents[len], .{ .uint32 = byte });580
513 }581 var buf: Type.SlicePtrFieldTypeBuffer = undefined;
514 try section.emit(self.spv.gpa, .OpConstantComposite, .{582 const ptr_ty = ty.slicePtrFieldType(&buf);
515 .id_result_type = result_ty_id,583
516 .id_result = result_id,584 try self.lower(ptr_ty, slice.ptr);
517 .constituents = constituents,585 try self.addInt(Type.usize, slice.len);
518 });586 },
587 else => |tag| return dg.todo("pointer value of type {s}", .{@tagName(tag)}),
519 },588 },
520 else => return self.todo("array constant with tag {s}", .{@tagName(val.tag())}),589 .Struct => {
521 },590 if (ty.isSimpleTupleOrAnonStruct()) {
522 .Vector => switch (val.tag()) {591 unreachable; // TODO
523 .aggregate => {592 } else {
524 const elem_vals = val.castTag(.aggregate).?.data;593 const struct_ty = ty.castTag(.@"struct").?.data;
525 const vector_len = @intCast(usize, ty.vectorLen());594
526 const elem_ty = ty.elemType();595 if (struct_ty.layout == .Packed) {
527596 return dg.todo("packed struct constants", .{});
528 const elem_refs = try self.gpa.alloc(IdRef, vector_len);597 }
529 defer self.gpa.free(elem_refs);598
530 for (elem_refs, 0..) |*elem, i| {599 const struct_begin = self.size;
531 elem.* = try self.constant(elem_ty, elem_vals[i], repr);600 const field_vals = val.castTag(.aggregate).?.data;
601 for (struct_ty.fields.values(), 0..) |field, i| {
602 if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
603 try self.lower(field.ty, field_vals[i]);
604
605 // Add padding if required.
606 // TODO: Add to type generation as well?
607 const unpadded_field_end = self.size - struct_begin;
608 const padded_field_end = ty.structFieldOffset(i + 1, target);
609 const padding = padded_field_end - unpadded_field_end;
610 try self.addUndef(padding);
611 }
532 }612 }
533 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{
534 .id_result_type = result_ty_id,
535 .id_result = result_id,
536 .constituents = elem_refs,
537 });
538 },613 },
539 else => return self.todo("vector constant with tag {s}", .{@tagName(val.tag())}),614 .Optional => {
540 },615 var opt_buf: Type.Payload.ElemType = undefined;
541 .Enum => {616 const payload_ty = ty.optionalChild(&opt_buf);
542 var int_buffer: Value.Payload.U64 = undefined;617 const has_payload = !val.isNull();
543 const int_val = val.enumToInt(ty, &int_buffer).toUnsignedInt(target); // TODO: composite integer constants618 const abi_size = ty.abiSize(target);
544 return self.genConstInt(result_ty_ref, result_id, int_val);619
545 },620 if (!payload_ty.hasRuntimeBits()) {
546 .Struct => {621 try self.addConstBool(has_payload);
547 const constituents = if (ty.isSimpleTupleOrAnonStruct()) blk: {622 return;
548 const tuple = ty.tupleFields();623 } else if (ty.optionalReprIsPayload()) {
549 const constituents = try self.spv.gpa.alloc(IdRef, tuple.types.len);624 // Optional representation is a nullable pointer.
550 errdefer self.spv.gpa.free(constituents);625 if (val.castTag(.opt_payload)) |payload| {
551626 try self.lower(payload_ty, payload.data);
552 var member_i: usize = 0;627 } else if (has_payload) {
553 for (tuple.types, 0..) |field_ty, i| {628 try self.lower(payload_ty, val);
554 const field_val = tuple.values[i];629 } else {
555 if (field_val.tag() != .unreachable_value or !field_ty.hasRuntimeBits()) continue;630 const ptr_ty_ref = try dg.resolveType(ty, .indirect);
556 constituents[member_i] = try self.constant(field_ty, field_val, .indirect);631 try self.addNullPtr(ptr_ty_ref);
557 member_i += 1;632 }
633 return;
558 }634 }
559635
560 break :blk constituents[0..member_i];636 // Optional representation is a structure.
561 } else blk: {637 // { Payload, Bool }
562 const struct_ty = ty.castTag(.@"struct").?.data;
563638
564 if (struct_ty.layout == .Packed) {639 // Subtract 1 for @sizeOf(bool).
565 return self.todo("packed struct constants", .{});640 // TODO: Make this not hardcoded.
566 }641 const payload_size = payload_ty.abiSize(target);
642 const padding = abi_size - payload_size - 1;
567643
568 const field_vals = val.castTag(.aggregate).?.data;644 if (val.castTag(.opt_payload)) |payload| {
569 const constituents = try self.spv.gpa.alloc(IdRef, struct_ty.fields.count());645 try self.lower(payload_ty, payload.data);
570 errdefer self.spv.gpa.free(constituents);646 } else {
571 var member_i: usize = 0;647 try self.addUndef(payload_size);
572 for (struct_ty.fields.values(), 0..) |field, i| {
573 if (field.is_comptime or !field.ty.hasRuntimeBits()) continue;
574 constituents[member_i] = try self.constant(field.ty, field_vals[i], .indirect);
575 member_i += 1;
576 }648 }
649 try self.addConstBool(has_payload);
650 try self.addUndef(padding);
651 },
652 .Enum => {
653 var int_val_buffer: Value.Payload.U64 = undefined;
654 const int_val = val.enumToInt(ty, &int_val_buffer);
577655
578 break :blk constituents[0..member_i];656 var int_ty_buffer: Type.Payload.Bits = undefined;
579 };657 const int_ty = ty.intTagType(&int_ty_buffer);
580 defer self.spv.gpa.free(constituents);
581658
582 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{659 try self.lower(int_ty, int_val);
583 .id_result_type = result_ty_id,
584 .id_result = result_id,
585 .constituents = constituents,
586 });
587 },
588 .Pointer => switch (val.tag()) {
589 .decl_ref_mut => try self.genDeclRef(result_ty_ref, result_id, val.castTag(.decl_ref_mut).?.data.decl_index),
590 .decl_ref => try self.genDeclRef(result_ty_ref, result_id, val.castTag(.decl_ref).?.data),
591 .slice => {
592 const slice = val.castTag(.slice).?.data;
593 var buf: Type.SlicePtrFieldTypeBuffer = undefined;
594
595 const ptr_id = try self.constant(ty.slicePtrFieldType(&buf), slice.ptr, .indirect);
596 const len_id = try self.constant(Type.usize, slice.len, .indirect);
597
598 const constituents = [_]IdRef{ ptr_id, len_id };
599 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{
600 .id_result_type = result_ty_id,
601 .id_result = result_id,
602 .constituents = &constituents,
603 });
604 },660 },
605 else => return self.todo("pointer of value type {s}", .{@tagName(val.tag())}),661 .Union => {
606 },662 const tag_and_val = val.castTag(.@"union").?.data;
607 .Optional => {663 const layout = ty.unionGetLayout(target);
608 var buf: Type.Payload.ElemType = undefined;
609 const payload_ty = ty.optionalChild(&buf);
610664
611 const has_payload = !val.isNull();665 if (layout.payload_size == 0) {
666 return try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag);
667 }
612668
613 // Note: keep in sync with the resolveType implementation for optionals.669 const union_ty = ty.cast(Type.Payload.Union).?.data;
614 if (!payload_ty.hasRuntimeBitsIgnoreComptime()) {670 if (union_ty.layout == .Packed) {
615 // Just a bool. Note: always in indirect representation.671 return dg.todo("packed union constants", .{});
616 try self.genConstInt(result_ty_ref, result_id, @boolToInt(has_payload));
617 } else if (ty.optionalReprIsPayload()) {
618 // A nullable pointer.
619 if (val.castTag(.opt_payload)) |payload| {
620 try self.genConstant(result_id, payload_ty, payload.data, repr);
621 } else if (has_payload) {
622 try self.genConstant(result_id, payload_ty, val, repr);
623 } else {
624 try section.emit(self.spv.gpa, .OpConstantNull, .{
625 .id_result_type = result_ty_id,
626 .id_result = result_id,
627 });
628 }672 }
629 return;
630 }
631673
632 // Struct-and-field pair.674 const active_field = ty.unionTagFieldIndex(tag_and_val.tag, dg.module).?;
633 // Note: If this optional has no payload, we initialize the the data member with OpUndef.675 const active_field_ty = union_ty.fields.values()[active_field].ty;
634 const bool_ty_ref = try self.resolveType(Type.bool, .indirect);
635 const valid_id = try self.constInt(bool_ty_ref, @boolToInt(has_payload));
636 const payload_val = if (val.castTag(.opt_payload)) |pl| pl.data else Value.undef;
637 const payload_id = try self.constant(payload_ty, payload_val, .indirect);
638676
639 const constituents = [_]IdRef{ payload_id, valid_id };677 const has_tag = layout.tag_size != 0;
640 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{678 const tag_first = layout.tag_align >= layout.payload_align;
641 .id_result_type = result_ty_id,
642 .id_result = result_id,
643 .constituents = &constituents,
644 });
645 },
646 .Union => {
647 const tag_and_val = val.castTag(.@"union").?.data;
648 const layout = ty.unionGetLayout(target);
649679
650 if (layout.payload_size == 0) {680 if (has_tag and tag_first) {
651 return try self.genConstant(result_id, ty.unionTagTypeSafety().?, tag_and_val.tag, .indirect);681 try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag);
652 }682 }
653683
654 const union_ty = ty.cast(Type.Payload.Union).?.data;684 const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: {
655 if (union_ty.layout == .Packed) {685 try self.lower(active_field_ty, tag_and_val.val);
656 return self.todo("packed union constants", .{});686 break :blk active_field_ty.abiSize(target);
657 }687 } else 0;
658688
659 const active_field = ty.unionTagFieldIndex(tag_and_val.tag, self.module).?;689 const payload_padding_len = layout.payload_size - active_field_size;
660 const union_ty_ref = try self.resolveUnionType(ty, active_field);690 try self.addUndef(payload_padding_len);
661 const active_field_ty = union_ty.fields.values()[active_field].ty;
662691
663 const tag_first = layout.tag_align >= layout.payload_align;692 if (has_tag and !tag_first) {
664 const u8_ty_ref = try self.intType(.unsigned, 8);693 try self.lower(ty.unionTagTypeSafety().?, tag_and_val.tag);
694 }
665695
666 const tag = if (layout.tag_size != 0)696 try self.addUndef(layout.padding);
667 try self.constant(ty.unionTagTypeSafety().?, tag_and_val.tag, .indirect)697 },
668 else698 else => |tag| return dg.todo("indirect constant of type {s}", .{@tagName(tag)}),
669 null;699 }
700 }
701 };
670702
671 var members = std.BoundedArray(IdRef, 4){};703 /// Returns a pointer to `val`. The value is placed directly
704 /// into the storage class `storage_class`, and this is also where the resulting
705 /// pointer points to. Note: result is not necessarily an OpVariable instruction!
706 fn lowerIndirectConstant(
707 self: *DeclGen,
708 result_id: IdRef,
709 ty: Type,
710 val: Value,
711 storage_class: spec.StorageClass,
712 alignment: u32,
713 ) Error!void {
714 // To simplify constant generation, we're going to generate constants as a word-array, and
715 // pointer cast the result to the right type.
716 // This means that the final constant will be generated as follows:
717 // %T = OpTypeStruct %members...
718 // %P = OpTypePointer %T
719 // %U = OpTypePointer %ty
720 // %1 = OpConstantComposite %T %initializers...
721 // %2 = OpVariable %P %1
722 // %result_id = OpSpecConstantOp OpBitcast %U %2
723 //
724 // The members consist of two options:
725 // - Literal values: ints, strings, etc. These are generated as u32 words.
726 // - Relocations, such as pointers: These are generated by embedding the pointer into the
727 // to-be-generated structure. There are two options here, depending on the alignment of the
728 // pointer value itself (not the alignment of the pointee).
729 // - Natively or over-aligned values. These can just be generated directly.
730 // - Underaligned pointers. These need to be packed into the word array by using a mixture of
731 // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc.
732
733 log.debug("lowerIndirectConstant: ty = {}, val = {}", .{ ty.fmtDebug(), val.fmtDebug() });
734
735 const constant_section = &self.spv.sections.types_globals_constants;
736
737 const ty_ref = try self.resolveType(ty, .indirect);
738 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, alignment);
672739
673 if (tag_first) {740 if (val.isUndef()) {
674 if (tag) |id| members.appendAssumeCapacity(id);741 // Special case: the entire value is undefined. In this case, we can just
675 }742 // generate an OpVariable with no initializer.
743 try constant_section.emit(self.spv.gpa, .OpVariable, .{
744 .id_result_type = self.typeId(ptr_ty_ref),
745 .id_result = result_id,
746 .storage_class = storage_class,
747 });
748 return;
749 }
676750
677 const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime()) blk: {751 const u32_ty_ref = try self.intType(.unsigned, 32);
678 const payload = try self.constant(active_field_ty, tag_and_val.val, .indirect);752 var icl = IndirectConstantLowering{
679 members.appendAssumeCapacity(payload);753 .dg = self,
680 break :blk active_field_ty.abiSize(target);754 .u32_ty_ref = u32_ty_ref,
681 } else 0;755 .u32_ty_id = self.typeId(u32_ty_ref),
756 .members = std.ArrayList(SpvType.Payload.Struct.Member).init(self.gpa),
757 .initializers = std.ArrayList(IdRef).init(self.gpa),
758 };
682759
683 const payload_padding_len = layout.payload_size - active_field_size;760 try icl.lower(ty, val);
684 if (payload_padding_len != 0) {761 try icl.flush();
685 const payload_padding_ty_ref = try self.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref);
686 members.appendAssumeCapacity(try self.genUndef(payload_padding_ty_ref));
687 }
688762
689 if (!tag_first) {763 defer icl.members.deinit();
690 if (tag) |id| members.appendAssumeCapacity(id);764 defer icl.initializers.deinit();
691 }
692765
693 if (layout.padding != 0) {766 const constant_struct_ty_ref = try self.spv.simpleStructType(icl.members.items);
694 const padding_ty_ref = try self.arrayType(layout.padding, u8_ty_ref);767 const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class, alignment);
695 members.appendAssumeCapacity(try self.genUndef(padding_ty_ref));768
696 }769 const constant_struct_id = self.spv.allocId();
770 try constant_section.emit(self.spv.gpa, .OpSpecConstantComposite, .{
771 .id_result_type = self.typeId(constant_struct_ty_ref),
772 .id_result = constant_struct_id,
773 .constituents = icl.initializers.items,
774 });
775
776 const var_id = self.spv.allocId();
777 switch (storage_class) {
778 .Generic => unreachable,
779 .Function => {
780 try self.func.prologue.emit(self.spv.gpa, .OpVariable, .{
781 .id_result_type = self.typeId(ptr_constant_struct_ty_ref),
782 .id_result = var_id,
783 .storage_class = storage_class,
784 .initializer = constant_struct_id,
785 });
786 // TODO: Set alignment of OpVariable.
697787
698 try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{788 try self.func.body.emit(self.spv.gpa, .OpBitcast, .{
699 .id_result_type = self.typeId(union_ty_ref),789 .id_result_type = self.typeId(ptr_ty_ref),
700 .id_result = result_id,790 .id_result = result_id,
701 .constituents = members.slice(),791 .operand = var_id,
702 });792 });
793 },
794 else => {
795 try constant_section.emit(self.spv.gpa, .OpVariable, .{
796 .id_result_type = self.typeId(ptr_constant_struct_ty_ref),
797 .id_result = var_id,
798 .storage_class = storage_class,
799 .initializer = constant_struct_id,
800 });
801 // TODO: Set alignment of OpVariable.
703802
704 // TODO: Cast to general union type? Required for pointers only or something?803 try constant_section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{
804 .id_result_type = self.typeId(ptr_ty_ref),
805 .id_result = result_id,
806 .operand = var_id,
807 });
705 },808 },
809 }
810 }
811
812 /// This function generates a load for a constant in direct (ie, non-memory) representation.
813 /// When the constant is simple, it can be generated directly using OpConstant instructions. When
814 /// the constant is more complicated however, it needs to be lowered to an indirect constant, which
815 /// is then loaded using OpLoad. Such values are loaded into the Function address space by default.
816 /// This function should only be called during function code generation.
817 fn constant(self: *DeclGen, ty: Type, val: Value) !IdRef {
818 const target = self.getTarget();
819 const section = &self.spv.sections.types_globals_constants;
820 const result_ty_ref = try self.resolveType(ty, .direct);
821 const result_ty_id = self.typeId(result_ty_ref);
822 const result_id = self.spv.allocId();
823
824 if (val.isUndef()) {
825 try section.emit(self.spv.gpa, .OpUndef, .{
826 .id_result_type = result_ty_id,
827 .id_result = result_id,
828 });
829 return result_id;
830 }
706831
707 .Fn => switch (repr) {832 switch (ty.zigTypeTag()) {
708 .direct => unreachable,833 .Int => {
709 .indirect => return self.todo("function pointers", .{}),834 const int_bits = if (ty.isSignedInt())
835 @bitCast(u64, val.toSignedInt(target))
836 else
837 val.toUnsignedInt(target);
838 try self.genConstInt(result_ty_ref, result_id, int_bits);
839 },
840 .Bool => {
841 const operands = .{ .id_result_type = result_ty_id, .id_result = result_id };
842 if (val.toBool()) {
843 try section.emit(self.spv.gpa, .OpConstantTrue, operands);
844 } else {
845 try section.emit(self.spv.gpa, .OpConstantFalse, operands);
846 }
847 },
848 else => {
849 // The value cannot be generated directly, so generate it as an indirect function-local
850 // constant, and then perform an OpLoad.
851 const ptr_id = self.spv.allocId();
852 const alignment = ty.abiAlignment(target);
853 try self.lowerIndirectConstant(ptr_id, ty, val, .Function, alignment);
854 try self.func.body.emit(self.spv.gpa, .OpLoad, .{
855 .id_result_type = result_ty_id,
856 .id_result = result_id,
857 .pointer = ptr_id,
858 });
859 // TODO: Convert bools? This logic should hook into `load`.
710 },860 },
711 .Void => unreachable,
712 else => return self.todo("constant generation of type {s}: {}", .{ @tagName(ty.zigTypeTag()), ty.fmtDebug() }),
713 }861 }
862
863 return result_id;
714 }864 }
715865
716 fn genDeclRef(self: *DeclGen, result_ty_ref: SpvType.Ref, result_id: IdRef, decl_index: Decl.Index) Error!void {866 fn genDeclRef(self: *DeclGen, result_ty_ref: SpvType.Ref, result_id: IdRef, decl_index: Decl.Index) Error!void {
867 // TODO: Clean up
717 const decl = self.module.declPtr(decl_index);868 const decl = self.module.declPtr(decl_index);
718 self.module.markDeclAlive(decl);869 self.module.markDeclAlive(decl);
719 const decl_id = try self.constant(decl.ty, decl.val, .indirect);870 // _ = result_ty_ref;
720 try self.variable(.global, result_id, result_ty_ref, decl_id);871 // const decl_id = try self.constant(decl.ty, decl.val, .indirect);
872 // try self.variable(.global, result_id, result_ty_ref, decl_id);
873 const result_storage_class = self.spv.typeRefType(result_ty_ref).payload(.pointer).storage_class;
874 const indirect_result_id = if (result_storage_class != .CrossWorkgroup)
875 self.spv.allocId()
876 else
877 result_id;
878
879 try self.lowerIndirectConstant(
880 indirect_result_id,
881 decl.ty,
882 decl.val,
883 .CrossWorkgroup, // TODO: Make this .Function if required
884 decl.@"align",
885 );
886 const section = &self.spv.sections.types_globals_constants;
887 if (result_storage_class != .CrossWorkgroup) {
888 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
889 .id_result_type = self.typeId(result_ty_ref),
890 .id_result = result_id,
891 .pointer = indirect_result_id,
892 });
893 }
721 }894 }
722895
723 /// Turn a Zig type into a SPIR-V Type, and return its type result-id.896 /// Turn a Zig type into a SPIR-V Type, and return its type result-id.
...@@ -746,32 +919,6 @@ pub const DeclGen = struct {...@@ -746,32 +919,6 @@ pub const DeclGen = struct {
746 return try self.intType(.unsigned, self.getTarget().cpu.arch.ptrBitWidth());919 return try self.intType(.unsigned, self.getTarget().cpu.arch.ptrBitWidth());
747 }920 }
748921
749 /// Construct a simple struct type which consists of some members, and no decorations.
750 /// `members` lifetime only needs to last for this function as it is copied.
751 fn simpleStructType(self: *DeclGen, members: []const SpvType.Payload.Struct.Member) !SpvType.Ref {
752 const payload = try self.spv.arena.create(SpvType.Payload.Struct);
753 payload.* = .{
754 .members = try self.spv.arena.dupe(SpvType.Payload.Struct.Member, members),
755 .decorations = .{},
756 };
757 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
758 }
759
760 fn simpleStructTypeId(self: *DeclGen, members: []const SpvType.Payload.Struct.Member) !IdResultType {
761 const type_ref = try self.simpleStructType(members);
762 return self.typeId(type_ref);
763 }
764
765 /// Construct an array type which has 'len' elements of 'type'
766 fn arrayType(self: *DeclGen, len: u32, ty: SpvType.Ref) !SpvType.Ref {
767 const payload = try self.spv.arena.create(SpvType.Payload.Array);
768 payload.* = .{
769 .element_type = ty,
770 .length = len,
771 };
772 return try self.spv.resolveType(SpvType.initPayload(&payload.base));
773 }
774
775 /// Generate a union type, optionally with a known field. If the tag alignment is greater922 /// Generate a union type, optionally with a known field. If the tag alignment is greater
776 /// than that of the payload, a regular union (non-packed, with both tag and payload), will923 /// than that of the payload, a regular union (non-packed, with both tag and payload), will
777 /// be generated as follows:924 /// be generated as follows:
...@@ -831,7 +978,7 @@ pub const DeclGen = struct {...@@ -831,7 +978,7 @@ pub const DeclGen = struct {
831978
832 const payload_padding_len = layout.payload_size - active_field_size;979 const payload_padding_len = layout.payload_size - active_field_size;
833 if (payload_padding_len != 0) {980 if (payload_padding_len != 0) {
834 const payload_padding_ty_ref = try self.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref);981 const payload_padding_ty_ref = try self.spv.arrayType(@intCast(u32, payload_padding_len), u8_ty_ref);
835 members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref });982 members.appendAssumeCapacity(.{ .name = "padding_payload", .ty = payload_padding_ty_ref });
836 }983 }
837984
...@@ -840,11 +987,11 @@ pub const DeclGen = struct {...@@ -840,11 +987,11 @@ pub const DeclGen = struct {
840 }987 }
841988
842 if (layout.padding != 0) {989 if (layout.padding != 0) {
843 const padding_ty_ref = try self.arrayType(layout.padding, u8_ty_ref);990 const padding_ty_ref = try self.spv.arrayType(layout.padding, u8_ty_ref);
844 members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref });991 members.appendAssumeCapacity(.{ .name = "padding", .ty = padding_ty_ref });
845 }992 }
846993
847 return try self.simpleStructType(members.slice());994 return try self.spv.simpleStructType(members.slice());
848 }995 }
849996
850 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.997 /// Turn a Zig type into a SPIR-V Type, and return a reference to it.
...@@ -893,7 +1040,7 @@ pub const DeclGen = struct {...@@ -893,7 +1040,7 @@ pub const DeclGen = struct {
893 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse {1040 const total_len = std.math.cast(u32, ty.arrayLenIncludingSentinel()) orelse {
894 return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()});1041 return self.fail("array type of {} elements is too large", .{ty.arrayLenIncludingSentinel()});
895 };1042 };
896 return try self.arrayType(total_len, elem_ty_ref);1043 return try self.spv.arrayType(total_len, elem_ty_ref);
897 },1044 },
898 .Fn => {1045 .Fn => {
899 // TODO: Put this somewhere in Sema.zig1046 // TODO: Put this somewhere in Sema.zig
...@@ -918,7 +1065,7 @@ pub const DeclGen = struct {...@@ -918,7 +1065,7 @@ pub const DeclGen = struct {
9181065
919 const ptr_payload = try self.spv.arena.create(SpvType.Payload.Pointer);1066 const ptr_payload = try self.spv.arena.create(SpvType.Payload.Pointer);
920 ptr_payload.* = .{1067 ptr_payload.* = .{
921 .storage_class = spirvStorageClass(ptr_info.@"addrspace"),1068 .storage_class = spvStorageClass(ptr_info.@"addrspace"),
922 .child_type = try self.resolveType(ptr_info.pointee_type, .indirect),1069 .child_type = try self.resolveType(ptr_info.pointee_type, .indirect),
923 // Note: only available in Kernels!1070 // Note: only available in Kernels!
924 .alignment = ty.ptrAlignment(target) * 8,1071 .alignment = ty.ptrAlignment(target) * 8,
...@@ -929,7 +1076,7 @@ pub const DeclGen = struct {...@@ -929,7 +1076,7 @@ pub const DeclGen = struct {
929 return ptr_ty_id;1076 return ptr_ty_id;
930 }1077 }
9311078
932 return try self.simpleStructType(&.{1079 return try self.spv.simpleStructType(&.{
933 .{ .ty = ptr_ty_id, .name = "ptr" },1080 .{ .ty = ptr_ty_id, .name = "ptr" },
934 .{ .ty = try self.sizeType(), .name = "len" },1081 .{ .ty = try self.sizeType(), .name = "len" },
935 });1082 });
...@@ -1018,7 +1165,7 @@ pub const DeclGen = struct {...@@ -1018,7 +1165,7 @@ pub const DeclGen = struct {
1018 const bool_ty_ref = try self.resolveType(Type.bool, .indirect);1165 const bool_ty_ref = try self.resolveType(Type.bool, .indirect);
10191166
1020 // its an actual optional1167 // its an actual optional
1021 return try self.simpleStructType(&.{1168 return try self.spv.simpleStructType(&.{
1022 .{ .ty = payload_ty_ref, .name = "payload" },1169 .{ .ty = payload_ty_ref, .name = "payload" },
1023 .{ .ty = bool_ty_ref, .name = "valid" },1170 .{ .ty = bool_ty_ref, .name = "valid" },
1024 });1171 });
...@@ -1037,7 +1184,7 @@ pub const DeclGen = struct {...@@ -1037,7 +1184,7 @@ pub const DeclGen = struct {
1037 }1184 }
1038 }1185 }
10391186
1040 fn spirvStorageClass(as: std.builtin.AddressSpace) spec.StorageClass {1187 fn spvStorageClass(as: std.builtin.AddressSpace) spec.StorageClass {
1041 return switch (as) {1188 return switch (as) {
1042 .generic => .Generic, // TODO: Disallow?1189 .generic => .Generic, // TODO: Disallow?
1043 .gs, .fs, .ss => unreachable,1190 .gs, .fs, .ss => unreachable,
...@@ -1100,7 +1247,46 @@ pub const DeclGen = struct {...@@ -1100,7 +1247,46 @@ pub const DeclGen = struct {
1100 .name = fqn,1247 .name = fqn,
1101 });1248 });
1102 } else {1249 } else {
1103 try self.genConstant(result_id, decl.ty, decl.val, .direct);1250 const init_val = if (decl.val.castTag(.variable)) |payload|
1251 payload.data.init
1252 else
1253 decl.val;
1254
1255 if (init_val.tag() == .unreachable_value) {
1256 return self.todo("importing extern variables", .{});
1257 }
1258
1259 // TODO: integrate with variable().
1260
1261 const storage_class = spvStorageClass(decl.@"addrspace");
1262 const actual_storage_class = switch (storage_class) {
1263 .Generic => .CrossWorkgroup,
1264 else => storage_class,
1265 };
1266
1267 const var_result_id = switch (storage_class) {
1268 .Generic => self.spv.allocId(),
1269 else => result_id,
1270 };
1271
1272 try self.lowerIndirectConstant(
1273 var_result_id,
1274 decl.ty,
1275 init_val,
1276 actual_storage_class,
1277 decl.@"align",
1278 );
1279
1280 if (storage_class == .Generic) {
1281 const section = &self.spv.sections.types_globals_constants;
1282 const ty_ref = try self.resolveType(decl.ty, .indirect);
1283 const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class, decl.@"align");
1284 try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
1285 .id_result_type = self.typeId(ptr_ty_ref),
1286 .id_result = result_id,
1287 .pointer = var_result_id,
1288 });
1289 }
1104 }1290 }
1105 }1291 }
11061292
...@@ -1358,13 +1544,13 @@ pub const DeclGen = struct {...@@ -1358,13 +1544,13 @@ pub const DeclGen = struct {
1358 // Construct the SPIR-V result type.1544 // Construct the SPIR-V result type.
1359 // It is almost the same as the zig one, except that the fields must be the same type1545 // It is almost the same as the zig one, except that the fields must be the same type
1360 // and they must be unsigned.1546 // and they must be unsigned.
1361 const overflow_result_ty = try self.simpleStructTypeId(&.{1547 const overflow_result_ty_ref = try self.spv.simpleStructType(&.{
1362 .{ .ty = overflow_member_ty, .name = "res" },1548 .{ .ty = overflow_member_ty, .name = "res" },
1363 .{ .ty = overflow_member_ty, .name = "ov" },1549 .{ .ty = overflow_member_ty, .name = "ov" },
1364 });1550 });
1365 const result_id = self.spv.allocId();1551 const result_id = self.spv.allocId();
1366 try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{1552 try self.func.body.emit(self.spv.gpa, .OpIAddCarry, .{
1367 .id_result_type = overflow_result_ty,1553 .id_result_type = self.typeId(overflow_result_ty_ref),
1368 .id_result = result_id,1554 .id_result = result_id,
1369 .operand_1 = lhs,1555 .operand_1 = lhs,
1370 .operand_2 = rhs,1556 .operand_2 = rhs,
...@@ -1786,13 +1972,11 @@ pub const DeclGen = struct {...@@ -1786,13 +1972,11 @@ pub const DeclGen = struct {
1786 .id_result = result_id,1972 .id_result = result_id,
1787 .pointer = alloc_result_id,1973 .pointer = alloc_result_id,
1788 }),1974 }),
1789 else => {1975 else => try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{
1790 try section.emitRaw(self.spv.gpa, .OpSpecConstantOp, 3 + 1);1976 .id_result_type = self.typeId(ptr_ty_ref),
1791 section.writeOperand(IdRef, self.typeId(ptr_ty_ref));1977 .id_result = result_id,
1792 section.writeOperand(IdRef, result_id);1978 .pointer = alloc_result_id,
1793 section.writeOperand(Opcode, .OpPtrCastToGeneric);1979 }),
1794 section.writeOperand(IdRef, alloc_result_id);
1795 },
1796 }1980 }
1797 }1981 }
17981982
src/codegen/spirv/Module.zig+35-2
...@@ -556,6 +556,39 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct...@@ -556,6 +556,39 @@ fn decorateStruct(self: *Module, target: IdRef, info: *const Type.Payload.Struct
556 }556 }
557}557}
558558
559pub fn simpleStructType(self: *Module, members: []const Type.Payload.Struct.Member) !Type.Ref {
560 const payload = try self.arena.create(Type.Payload.Struct);
561 payload.* = .{
562 .members = try self.arena.dupe(Type.Payload.Struct.Member, members),
563 .decorations = .{},
564 };
565 return try self.resolveType(Type.initPayload(&payload.base));
566}
567
568pub fn arrayType(self: *Module, len: u32, ty: Type.Ref) !Type.Ref {
569 const payload = try self.arena.create(Type.Payload.Array);
570 payload.* = .{
571 .element_type = ty,
572 .length = len,
573 };
574 return try self.resolveType(Type.initPayload(&payload.base));
575}
576
577pub fn ptrType(
578 self: *Module,
579 child: Type.Ref,
580 storage_class: spec.StorageClass,
581 alignment: ?u32,
582) !Type.Ref {
583 const ptr_payload = try self.arena.create(Type.Payload.Pointer);
584 ptr_payload.* = .{
585 .storage_class = storage_class,
586 .child_type = child,
587 .alignment = alignment,
588 };
589 return try self.resolveType(Type.initPayload(&ptr_payload.base));
590}
591
559pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref {592pub fn changePtrStorageClass(self: *Module, ptr_ty_ref: Type.Ref, new_storage_class: spec.StorageClass) !Type.Ref {
560 const payload = try self.arena.create(Type.Payload.Pointer);593 const payload = try self.arena.create(Type.Payload.Pointer);
561 payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*;594 payload.* = self.typeRefType(ptr_ty_ref).payload(.pointer).*;
...@@ -579,7 +612,7 @@ pub fn emitConstant(...@@ -579,7 +612,7 @@ pub fn emitConstant(
579/// Decorate a result-id.612/// Decorate a result-id.
580pub fn decorate(613pub fn decorate(
581 self: *Module,614 self: *Module,
582 target: spec.IdRef,615 target: IdRef,
583 decoration: spec.Decoration.Extended,616 decoration: spec.Decoration.Extended,
584) !void {617) !void {
585 try self.sections.annotations.emit(self.gpa, .OpDecorate, .{618 try self.sections.annotations.emit(self.gpa, .OpDecorate, .{
...@@ -591,7 +624,7 @@ pub fn decorate(...@@ -591,7 +624,7 @@ pub fn decorate(
591/// Decorate a result-id which is a member of some struct.624/// Decorate a result-id which is a member of some struct.
592pub fn decorateMember(625pub fn decorateMember(
593 self: *Module,626 self: *Module,
594 structure_type: spec.IdRef,627 structure_type: IdRef,
595 member: u32,628 member: u32,
596 decoration: spec.Decoration.Extended,629 decoration: spec.Decoration.Extended,
597) !void {630) !void {
src/codegen/spirv/Section.zig+19
...@@ -65,6 +65,25 @@ pub fn emit(...@@ -65,6 +65,25 @@ pub fn emit(
65 section.writeOperands(opcode.Operands(), operands);65 section.writeOperands(opcode.Operands(), operands);
66}66}
6767
68pub fn emitSpecConstantOp(
69 section: *Section,
70 allocator: Allocator,
71 comptime opcode: spec.Opcode,
72 operands: opcode.Operands(),
73) !void {
74 const word_count = operandsSize(opcode.Operands(), operands);
75 try section.emitRaw(allocator, .OpSpecConstantOp, 1 + word_count);
76 section.writeOperand(spec.IdRef, operands.id_result_type);
77 section.writeOperand(spec.IdRef, operands.id_result);
78 section.writeOperand(Opcode, opcode);
79
80 const fields = @typeInfo(opcode.Operands()).Struct.fields;
81 // First 2 fields are always id_result_type and id_result.
82 inline for (fields[2..]) |field| {
83 section.writeOperand(field.type, @field(operands, field.name));
84 }
85}
86
68pub fn writeWord(section: *Section, word: Word) void {87pub fn writeWord(section: *Section, word: Word) void {
69 section.instructions.appendAssumeCapacity(word);88 section.instructions.appendAssumeCapacity(word);
70}89}