diff options
| author | Robin Voetter <robin@voetter.nl> | 2023-09-16 13:16:02 +0200 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2023-09-23 12:36:44 -0700 |
| commit | 66036e600058081ce2f9248fd221900dce793cdd (patch) | |
| tree | 33ffcc5554934f9e9eafe873d5f39f50df017dbe /src/codegen | |
| parent | b30cd679878ab0fab7f1e1589c348a2477d542aa (diff) | |
| download | zig-66036e600058081ce2f9248fd221900dce793cdd.tar.gz zig-66036e600058081ce2f9248fd221900dce793cdd.zip | |
spirv: remove indirect constant lowering
It is stupid and I hate it.
Diffstat (limited to 'src/codegen')
| -rw-r--r-- | src/codegen/spirv.zig | 575 |
1 files changed, 3 insertions, 572 deletions
diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig index 4ab89a1c97..c906e5ad96 100644 --- a/src/codegen/spirv.zig +++ b/src/codegen/spirv.zig @@ -520,576 +520,6 @@ pub const DeclGen = struct { } } - const IndirectConstantLowering = struct { - const undef = 0xAA; - - dg: *DeclGen, - /// Cached reference of the u32 type. - u32_ty_ref: CacheRef, - /// The members of the resulting structure type - members: std.ArrayList(CacheRef), - /// The initializers of each of the members. - initializers: std.ArrayList(IdRef), - /// The current size of the structure. Includes - /// the bytes in partial_word. - size: u32 = 0, - /// The partially filled last constant. - /// If full, its flushed. - partial_word: std.BoundedArray(u8, @sizeOf(Word)) = .{}, - /// The declaration dependencies of the constant we are lowering. - decl_deps: std.AutoArrayHashMap(SpvModule.Decl.Index, void), - - /// Utility function to get the section that instructions should be lowered to. - fn section(self: *@This()) *SpvSection { - return &self.dg.spv.globals.section; - } - - /// Flush the partial_word to the members. If the partial_word is not - /// filled, this adds padding bytes (which are undefined). - fn flush(self: *@This()) !void { - if (self.partial_word.len == 0) { - // No need to add it there. - return; - } - - for (self.partial_word.unusedCapacitySlice()) |*unused| { - // TODO: Perhaps we should generate OpUndef for these bytes? - unused.* = undef; - } - - const word = @as(Word, @bitCast(self.partial_word.buffer)); - const result_id = try self.dg.spv.constInt(self.u32_ty_ref, word); - try self.members.append(self.u32_ty_ref); - try self.initializers.append(result_id); - - self.partial_word.len = 0; - self.size = std.mem.alignForward(u32, self.size, @sizeOf(Word)); - } - - /// Fill the buffer with undefined values until the size is aligned to `align`. - fn fillToAlign(self: *@This(), alignment: u32) !void { - const target_size = std.mem.alignForward(u32, self.size, alignment); - try self.addUndef(target_size - self.size); - } - - fn addUndef(self: *@This(), amt: u64) !void { - for (0..@as(usize, @intCast(amt))) |_| { - try self.addByte(undef); - } - } - - /// Add a single byte of data to the constant. - fn addByte(self: *@This(), data: u8) !void { - self.partial_word.append(data) catch { - try self.flush(); - self.partial_word.append(data) catch unreachable; - }; - self.size += 1; - } - - /// Add many bytes of data to the constnat. - fn addBytes(self: *@This(), data: []const u8) !void { - // TODO: Improve performance by adding in bulk, or something? - for (data) |byte| { - try self.addByte(byte); - } - } - - fn addPtr(self: *@This(), ptr_ty_ref: CacheRef, ptr_id: IdRef) !void { - // TODO: Double check pointer sizes here. - // shared pointers might be u32... - const target = self.dg.getTarget(); - const width = @divExact(target.ptrBitWidth(), 8); - if (self.size % width != 0) { - return self.dg.todo("misaligned pointer constants", .{}); - } - try self.members.append(ptr_ty_ref); - try self.initializers.append(ptr_id); - self.size += width; - } - - fn addNullPtr(self: *@This(), ptr_ty_ref: CacheRef) !void { - const result_id = try self.dg.spv.constNull(ptr_ty_ref); - try self.addPtr(ptr_ty_ref, result_id); - } - - fn addConstInt(self: *@This(), comptime T: type, value: T) !void { - if (@bitSizeOf(T) % 8 != 0) { - @compileError("todo: non byte aligned int constants"); - } - - // TODO: Swap endianness if the compiler is big endian. - try self.addBytes(std.mem.asBytes(&value)); - } - - fn addConstBool(self: *@This(), value: bool) !void { - try self.addByte(@intFromBool(value)); // TODO: Keep in sync with something? - } - - fn addInt(self: *@This(), ty: Type, val: Value) !void { - const mod = self.dg.module; - const len = ty.abiSize(mod); - if (val.isUndef(mod)) { - try self.addUndef(len); - return; - } - - const int_info = ty.intInfo(mod); - const int_bits = switch (int_info.signedness) { - .signed => @as(u64, @bitCast(val.toSignedInt(mod))), - .unsigned => val.toUnsignedInt(mod), - }; - - // TODO: Swap endianess if the compiler is big endian. - try self.addBytes(std.mem.asBytes(&int_bits)[0..@as(usize, @intCast(len))]); - } - - fn addFloat(self: *@This(), ty: Type, val: Value) !void { - const mod = self.dg.module; - const target = self.dg.getTarget(); - const len = ty.abiSize(mod); - - // TODO: Swap endianess if the compiler is big endian. - switch (ty.floatBits(target)) { - 16 => { - const float_bits = val.toFloat(f16, mod); - try self.addBytes(std.mem.asBytes(&float_bits)[0..@as(usize, @intCast(len))]); - }, - 32 => { - const float_bits = val.toFloat(f32, mod); - try self.addBytes(std.mem.asBytes(&float_bits)[0..@as(usize, @intCast(len))]); - }, - 64 => { - const float_bits = val.toFloat(f64, mod); - try self.addBytes(std.mem.asBytes(&float_bits)[0..@as(usize, @intCast(len))]); - }, - else => unreachable, - } - } - - fn addDeclRef(self: *@This(), ty: Type, decl_index: Decl.Index) !void { - const dg = self.dg; - const mod = dg.module; - - const ty_ref = try self.dg.resolveType(ty, .indirect); - const ty_id = dg.typeId(ty_ref); - - const decl = dg.module.declPtr(decl_index); - const spv_decl_index = try dg.resolveDecl(decl_index); - - switch (mod.intern_pool.indexToKey(decl.val.ip_index)) { - .func => { - // TODO: Properly lower function pointers. For now we are going to hack around it and - // just generate an empty pointer. Function pointers are represented by usize for now, - // though. - try self.addInt(Type.usize, Value.zero_usize); - // TODO: Add dependency - return; - }, - .extern_func => unreachable, // TODO - else => { - const result_id = dg.spv.allocId(); - - try self.decl_deps.put(spv_decl_index, {}); - - const decl_id = dg.spv.declPtr(spv_decl_index).result_id; - // TODO: Do we need a storage class cast here? - // TODO: We can probably eliminate these casts - try dg.spv.globals.section.emitSpecConstantOp(dg.spv.gpa, .OpBitcast, .{ - .id_result_type = ty_id, - .id_result = result_id, - .operand = decl_id, - }); - - try self.addPtr(ty_ref, result_id); - }, - } - } - - fn lower(self: *@This(), ty: Type, arg_val: Value) !void { - const dg = self.dg; - const mod = dg.module; - const ip = &mod.intern_pool; - - var val = arg_val; - switch (ip.indexToKey(val.toIntern())) { - .runtime_value => |rt| val = rt.val.toValue(), - else => {}, - } - - if (val.isUndefDeep(mod)) { - const size = ty.abiSize(mod); - return try self.addUndef(size); - } - - switch (ip.indexToKey(val.toIntern())) { - .int_type, - .ptr_type, - .array_type, - .vector_type, - .opt_type, - .anyframe_type, - .error_union_type, - .simple_type, - .struct_type, - .anon_struct_type, - .union_type, - .opaque_type, - .enum_type, - .func_type, - .error_set_type, - .inferred_error_set_type, - => unreachable, // types, not values - - .undef, .runtime_value => unreachable, // handled above - .simple_value => |simple_value| switch (simple_value) { - .undefined, - .void, - .null, - .empty_struct, - .@"unreachable", - .generic_poison, - => unreachable, // non-runtime values - .false, .true => try self.addConstBool(val.toBool()), - }, - .variable, - .extern_func, - .func, - .enum_literal, - .empty_enum_value, - => unreachable, // non-runtime values - .int => try self.addInt(ty, val), - .err => |err| { - const int = try mod.getErrorValue(err.name); - try self.addConstInt(u16, @as(u16, @intCast(int))); - }, - .error_union => |error_union| { - const err_ty = switch (error_union.val) { - .err_name => ty.errorUnionSet(mod), - .payload => Type.err_int, - }; - const err_val = switch (error_union.val) { - .err_name => |err_name| (try mod.intern(.{ .err = .{ - .ty = ty.errorUnionSet(mod).toIntern(), - .name = err_name, - } })).toValue(), - .payload => try mod.intValue(Type.err_int, 0), - }; - const payload_ty = ty.errorUnionPayload(mod); - const eu_layout = dg.errorUnionLayout(payload_ty); - if (!eu_layout.payload_has_bits) { - // We use the error type directly as the type. - try self.lower(err_ty, err_val); - return; - } - - const payload_size = payload_ty.abiSize(mod); - const error_size = err_ty.abiSize(mod); - const ty_size = ty.abiSize(mod); - const padding = ty_size - payload_size - error_size; - - const payload_val = switch (error_union.val) { - .err_name => try mod.intern(.{ .undef = payload_ty.toIntern() }), - .payload => |payload| payload, - }.toValue(); - - if (eu_layout.error_first) { - try self.lower(err_ty, err_val); - try self.lower(payload_ty, payload_val); - } else { - try self.lower(payload_ty, payload_val); - try self.lower(err_ty, err_val); - } - - try self.addUndef(padding); - }, - .enum_tag => { - const int_val = try val.intFromEnum(ty, mod); - - const int_ty = ty.intTagType(mod); - - try self.lower(int_ty, int_val); - }, - .float => try self.addFloat(ty, val), - .ptr => |ptr| { - const ptr_ty = switch (ptr.len) { - .none => ty, - else => ty.slicePtrFieldType(mod), - }; - switch (ptr.addr) { - .decl => |decl| try self.addDeclRef(ptr_ty, decl), - .mut_decl => |mut_decl| try self.addDeclRef(ptr_ty, mut_decl.decl), - .int => |int| try self.addInt(Type.usize, int.toValue()), - else => |tag| return dg.todo("pointer value of type {s}", .{@tagName(tag)}), - } - if (ptr.len != .none) { - try self.addInt(Type.usize, ptr.len.toValue()); - } - }, - .opt => { - const payload_ty = ty.optionalChild(mod); - const payload_val = val.optionalValue(mod); - const abi_size = ty.abiSize(mod); - - if (!payload_ty.hasRuntimeBits(mod)) { - try self.addConstBool(payload_val != null); - return; - } else if (ty.optionalReprIsPayload(mod)) { - // Optional representation is a nullable pointer or slice. - if (payload_val) |pl_val| { - try self.lower(payload_ty, pl_val); - } else { - const ptr_ty_ref = try dg.resolveType(ty, .indirect); - try self.addNullPtr(ptr_ty_ref); - } - return; - } - - // Optional representation is a structure. - // { Payload, Bool } - - // Subtract 1 for @sizeOf(bool). - // TODO: Make this not hardcoded. - const payload_size = payload_ty.abiSize(mod); - const padding = abi_size - payload_size - 1; - - if (payload_val) |pl_val| { - try self.lower(payload_ty, pl_val); - } else { - try self.addUndef(payload_size); - } - try self.addConstBool(payload_val != null); - try self.addUndef(padding); - }, - .aggregate => |aggregate| switch (ip.indexToKey(ty.ip_index)) { - .array_type => |array_type| { - const elem_ty = array_type.child.toType(); - switch (aggregate.storage) { - .bytes => |bytes| try self.addBytes(bytes), - .elems, .repeated_elem => { - for (0..@as(usize, @intCast(array_type.len))) |i| { - try self.lower(elem_ty, switch (aggregate.storage) { - .bytes => unreachable, - .elems => |elem_vals| elem_vals[@as(usize, @intCast(i))].toValue(), - .repeated_elem => |elem_val| elem_val.toValue(), - }); - } - }, - } - if (array_type.sentinel != .none) { - try self.lower(elem_ty, array_type.sentinel.toValue()); - } - }, - .vector_type => return dg.todo("indirect constant of type {}", .{ty.fmt(mod)}), - .struct_type => { - const struct_type = mod.typeToStruct(ty).?; - if (struct_type.layout == .Packed) { - return dg.todo("packed struct constants", .{}); - } - - // TODO iterate with runtime order instead so that struct field - // reordering can be enabled for this backend. - const struct_begin = self.size; - for (struct_type.field_types.get(ip), 0..) |field_ty, i_usize| { - const i: u32 = @intCast(i_usize); - if (struct_type.fieldIsComptime(ip, i)) continue; - if (!field_ty.toType().hasRuntimeBits(mod)) continue; - - const field_val = switch (aggregate.storage) { - .bytes => |bytes| try ip.get(mod.gpa, .{ .int = .{ - .ty = field_ty, - .storage = .{ .u64 = bytes[i] }, - } }), - .elems => |elems| elems[i], - .repeated_elem => |elem| elem, - }; - try self.lower(field_ty.toType(), field_val.toValue()); - - // Add padding if required. - // TODO: Add to type generation as well? - const unpadded_field_end = self.size - struct_begin; - const padded_field_end = ty.structFieldOffset(i + 1, mod); - const padding = padded_field_end - unpadded_field_end; - try self.addUndef(padding); - } - }, - .anon_struct_type => unreachable, // TODO - else => unreachable, - }, - .un => |un| { - const layout = ty.unionGetLayout(mod); - - if (layout.payload_size == 0) { - return try self.lower(ty.unionTagTypeSafety(mod).?, un.tag.toValue()); - } - - const union_obj = mod.typeToUnion(ty).?; - if (union_obj.getLayout(ip) == .Packed) { - return dg.todo("packed union constants", .{}); - } - - const active_field = ty.unionTagFieldIndex(un.tag.toValue(), dg.module).?; - const active_field_ty = union_obj.field_types.get(ip)[active_field].toType(); - - const has_tag = layout.tag_size != 0; - const tag_first = layout.tag_align.compare(.gte, layout.payload_align); - - if (has_tag and tag_first) { - try self.lower(ty.unionTagTypeSafety(mod).?, un.tag.toValue()); - } - - const active_field_size = if (active_field_ty.hasRuntimeBitsIgnoreComptime(mod)) blk: { - try self.lower(active_field_ty, un.val.toValue()); - break :blk active_field_ty.abiSize(mod); - } else 0; - - const payload_padding_len = layout.payload_size - active_field_size; - try self.addUndef(payload_padding_len); - - if (has_tag and !tag_first) { - try self.lower(ty.unionTagTypeSafety(mod).?, un.tag.toValue()); - } - - try self.addUndef(layout.padding); - }, - .memoized_call => unreachable, - } - } - }; - - /// Returns a pointer to `val`. The value is placed directly - /// into the storage class `storage_class`, and this is also where the resulting - /// pointer points to. Note: result is not necessarily an OpVariable instruction! - fn lowerIndirectConstant( - self: *DeclGen, - spv_decl_index: SpvModule.Decl.Index, - ty: Type, - val: Value, - storage_class: StorageClass, - cast_to_generic: bool, - alignment: u32, - ) Error!void { - // To simplify constant generation, we're going to generate constants as a word-array, and - // pointer cast the result to the right type. - // This means that the final constant will be generated as follows: - // %T = OpTypeStruct %members... - // %P = OpTypePointer %T - // %U = OpTypePointer %ty - // %1 = OpConstantComposite %T %initializers... - // %2 = OpVariable %P %1 - // %result_id = OpSpecConstantOp OpBitcast %U %2 - // - // The members consist of two options: - // - Literal values: ints, strings, etc. These are generated as u32 words. - // - Relocations, such as pointers: These are generated by embedding the pointer into the - // to-be-generated structure. There are two options here, depending on the alignment of the - // pointer value itself (not the alignment of the pointee). - // - Natively or over-aligned values. These can just be generated directly. - // - Underaligned pointers. These need to be packed into the word array by using a mixture of - // OpSpecConstantOp instructions such as OpConvertPtrToU, OpBitcast, OpShift, etc. - - // TODO: Implement alignment here. - // This is hoing to require some hacks because there is no real way to - // set an OpVariable's alignment. - _ = alignment; - - assert(storage_class != .Generic and storage_class != .Function); - - const var_id = self.spv.allocId(); - log.debug("lowerIndirectConstant: id = {}, index = {}, ty = {}, val = {}", .{ var_id.id, @intFromEnum(spv_decl_index), ty.fmt(self.module), val.fmtDebug() }); - - const section = &self.spv.globals.section; - - const ty_ref = try self.resolveType(ty, .indirect); - const ptr_ty_ref = try self.spv.ptrType(ty_ref, storage_class); - - // const target = self.getTarget(); - - // TODO: Fix the resulting global linking for these paths. - // if (val.isUndef(mod)) { - // // Special case: the entire value is undefined. In this case, we can just - // // generate an OpVariable with no initializer. - // return try section.emit(self.spv.gpa, .OpVariable, .{ - // .id_result_type = self.typeId(ptr_ty_ref), - // .id_result = result_id, - // .storage_class = storage_class, - // }); - // } else if (ty.abiSize(mod) == 0) { - // // Special case: if the type has no size, then return an undefined pointer. - // return try section.emit(self.spv.gpa, .OpUndef, .{ - // .id_result_type = self.typeId(ptr_ty_ref), - // .id_result = result_id, - // }); - // } - - // TODO: Capture the above stuff in here as well... - const begin_inst = self.spv.beginGlobal(); - - const u32_ty_ref = try self.intType(.unsigned, 32); - var icl = IndirectConstantLowering{ - .dg = self, - .u32_ty_ref = u32_ty_ref, - .members = std.ArrayList(CacheRef).init(self.gpa), - .initializers = std.ArrayList(IdRef).init(self.gpa), - .decl_deps = std.AutoArrayHashMap(SpvModule.Decl.Index, void).init(self.gpa), - }; - - defer icl.members.deinit(); - defer icl.initializers.deinit(); - defer icl.decl_deps.deinit(); - - try icl.lower(ty, val); - try icl.flush(); - - const constant_struct_ty_ref = try self.spv.resolve(.{ .struct_type = .{ - .member_types = icl.members.items, - } }); - const ptr_constant_struct_ty_ref = try self.spv.ptrType(constant_struct_ty_ref, storage_class); - - const constant_struct_id = self.spv.allocId(); - try section.emit(self.spv.gpa, .OpSpecConstantComposite, .{ - .id_result_type = self.typeId(constant_struct_ty_ref), - .id_result = constant_struct_id, - .constituents = icl.initializers.items, - }); - - self.spv.globalPtr(spv_decl_index).?.result_id = var_id; - try section.emit(self.spv.gpa, .OpVariable, .{ - .id_result_type = self.typeId(ptr_constant_struct_ty_ref), - .id_result = var_id, - .storage_class = storage_class, - .initializer = constant_struct_id, - }); - // TODO: Set alignment of OpVariable. - // TODO: We may be able to eliminate these casts. - - const const_ptr_id = try self.makePointerConstant(section, ptr_constant_struct_ty_ref, var_id); - const result_id = self.spv.declPtr(spv_decl_index).result_id; - - const bitcast_result_id = if (cast_to_generic) - self.spv.allocId() - else - result_id; - - try section.emitSpecConstantOp(self.spv.gpa, .OpBitcast, .{ - .id_result_type = self.typeId(ptr_ty_ref), - .id_result = bitcast_result_id, - .operand = const_ptr_id, - }); - - if (cast_to_generic) { - const generic_ptr_ty_ref = try self.spv.ptrType(ty_ref, .Generic); - try section.emitSpecConstantOp(self.spv.gpa, .OpPtrCastToGeneric, .{ - .id_result_type = self.typeId(generic_ptr_ty_ref), - .id_result = result_id, - .pointer = bitcast_result_id, - }); - } - - try self.spv.declareDeclDeps(spv_decl_index, icl.decl_deps.keys()); - self.spv.endGlobal(spv_decl_index, begin_inst); - } - /// This function generates a load for a constant in direct (ie, non-memory) representation. /// When the constant is simple, it can be generated directly using OpConstant instructions. /// When the constant is more complicated however, it needs to be constructed using multiple values. This @@ -2104,8 +1534,6 @@ pub const DeclGen = struct { .id_result = decl_id, .storage_class = actual_storage_class, }); - // TODO: We should be able to get rid of this by now... - self.spv.endGlobal(spv_decl_index, begin); // Now emit the instructions that initialize the variable. const initializer_id = self.spv.allocId(); @@ -2127,6 +1555,9 @@ pub const DeclGen = struct { .object = val_id, }); + // TODO: We should be able to get rid of this by now... + self.spv.endGlobal(spv_decl_index, begin); + try self.func.body.emit(self.spv.gpa, .OpReturn, {}); try self.func.body.emit(self.spv.gpa, .OpFunctionEnd, {}); try self.spv.addFunction(spv_decl_index, self.func); |
