llvm: convert inline assembly

Also, implement TODOs from a previous commit.
This commit is contained in:
Jacob Young 2023-07-23 05:01:12 -04:00
parent c610cde1eb
commit 533111e849
2 changed files with 297 additions and 68 deletions

View File

@ -6780,7 +6780,7 @@ pub const FuncGen = struct {
const max_param_count = inputs.len + outputs.len;
const llvm_param_types = try arena.alloc(Builder.Type, max_param_count);
const llvm_param_values = try arena.alloc(*llvm.Value, max_param_count);
const llvm_param_values = try arena.alloc(Builder.Value, max_param_count);
// This stores whether we need to add an elementtype attribute and
// if so, the element type itself.
const llvm_param_attrs = try arena.alloc(Builder.Type, max_param_count);
@ -6820,7 +6820,7 @@ pub const FuncGen = struct {
// Pass the result by reference as an indirect output (e.g. "=*m")
llvm_constraints.appendAssumeCapacity('*');
llvm_param_values[llvm_param_i] = output_inst.toLlvm(&self.wip);
llvm_param_values[llvm_param_i] = output_inst;
llvm_param_types[llvm_param_i] = output_inst.typeOfWip(&self.wip);
llvm_param_attrs[llvm_param_i] = elem_llvm_ty;
llvm_param_i += 1;
@ -6870,25 +6870,25 @@ pub const FuncGen = struct {
if (isByRef(arg_ty, mod)) {
llvm_elem_ty = try o.lowerPtrElemTy(arg_ty);
if (constraintAllowsMemory(constraint)) {
llvm_param_values[llvm_param_i] = arg_llvm_value.toLlvm(&self.wip);
llvm_param_values[llvm_param_i] = arg_llvm_value;
llvm_param_types[llvm_param_i] = arg_llvm_value.typeOfWip(&self.wip);
} else {
const alignment = Builder.Alignment.fromByteUnits(arg_ty.abiAlignment(mod));
const arg_llvm_ty = try o.lowerType(arg_ty);
const load_inst =
try self.wip.load(.normal, arg_llvm_ty, arg_llvm_value, alignment, "");
llvm_param_values[llvm_param_i] = load_inst.toLlvm(&self.wip);
llvm_param_values[llvm_param_i] = load_inst;
llvm_param_types[llvm_param_i] = arg_llvm_ty;
}
} else {
if (constraintAllowsRegister(constraint)) {
llvm_param_values[llvm_param_i] = arg_llvm_value.toLlvm(&self.wip);
llvm_param_values[llvm_param_i] = arg_llvm_value;
llvm_param_types[llvm_param_i] = arg_llvm_value.typeOfWip(&self.wip);
} else {
const alignment = Builder.Alignment.fromByteUnits(arg_ty.abiAlignment(mod));
const arg_ptr = try self.buildAlloca(arg_llvm_value.typeOfWip(&self.wip), alignment);
_ = try self.wip.store(.normal, arg_llvm_value, arg_ptr, alignment);
llvm_param_values[llvm_param_i] = arg_ptr.toLlvm(&self.wip);
llvm_param_values[llvm_param_i] = arg_ptr;
llvm_param_types[llvm_param_i] = arg_ptr.typeOfWip(&self.wip);
}
}
@ -7037,40 +7037,24 @@ pub const FuncGen = struct {
var attributes: Builder.FunctionAttributes.Wip = .{};
defer attributes.deinit(&o.builder);
for (llvm_param_attrs[0..param_count], 0..) |llvm_elem_ty, i| if (llvm_elem_ty != .none)
try attributes.addParamAttr(i, .{ .elementtype = llvm_elem_ty }, &o.builder);
const ret_llvm_ty = switch (return_count) {
0 => .void,
1 => llvm_ret_types[0],
else => try o.builder.structType(.normal, llvm_ret_types),
};
const llvm_fn_ty = try o.builder.fnType(ret_llvm_ty, llvm_param_types[0..param_count], .normal);
const asm_fn = llvm.getInlineAsm(
llvm_fn_ty.toLlvm(&o.builder),
rendered_template.items.ptr,
rendered_template.items.len,
llvm_constraints.items.ptr,
llvm_constraints.items.len,
llvm.Bool.fromBool(is_volatile),
.False,
.ATT,
.False,
);
const call = (try self.wip.unimplemented(ret_llvm_ty, "")).finish(self.builder.buildCallOld(
llvm_fn_ty.toLlvm(&o.builder),
asm_fn,
llvm_param_values.ptr,
@intCast(param_count),
.C,
.Auto,
const call = try self.wip.callAsm(
try attributes.finish(&o.builder),
llvm_fn_ty,
.{ .sideeffect = is_volatile },
try o.builder.string(rendered_template.items),
try o.builder.string(llvm_constraints.items),
llvm_param_values[0..param_count],
"",
), &self.wip);
for (llvm_param_attrs[0..param_count], 0..) |llvm_elem_ty, i| {
if (llvm_elem_ty != .none) {
try attributes.addParamAttr(i, .{ .elementtype = llvm_elem_ty }, &o.builder);
llvm.setCallElemTypeAttr(call.toLlvm(&self.wip), i, llvm_elem_ty.toLlvm(&o.builder));
}
}
);
var ret_val = call;
llvm_ret_i = 0;

View File

@ -1072,7 +1072,7 @@ pub const Attribute = union(Kind) {
_: std.fmt.FormatOptions,
writer: anytype,
) @TypeOf(writer).Error!void {
if (comptime std.mem.indexOfNone(u8, fmt_str, "\"")) |_|
if (comptime std.mem.indexOfNone(u8, fmt_str, "\"#")) |_|
@compileError("invalid format string: '" ++ fmt_str ++ "'");
const attribute = data.attribute_index.toAttribute(data.builder);
switch (attribute) {
@ -1154,17 +1154,72 @@ pub const Attribute = union(Kind) {
.sret,
.elementtype,
=> |ty| try writer.print(" {s}({%})", .{ @tagName(attribute), ty.fmt(data.builder) }),
.@"align" => @panic("todo"),
.@"align" => |alignment| try writer.print("{}", .{alignment}),
.dereferenceable,
.dereferenceable_or_null,
=> @panic("todo"),
.nofpclass => @panic("todo"),
.alignstack => @panic("todo"),
.allockind => @panic("todo"),
.allocsize => @panic("todo"),
.memory => @panic("todo"),
.uwtable => @panic("todo"),
.vscale_range => @panic("todo"),
=> |size| try writer.print(" {s}({d})", .{ @tagName(attribute), size }),
.nofpclass => |fpclass| {
const Int = @typeInfo(FpClass).Struct.backing_integer.?;
try writer.print("{s}(", .{@tagName(attribute)});
var any = false;
var remaining: Int = @bitCast(fpclass);
inline for (@typeInfo(FpClass).Struct.decls) |decl| {
if (!decl.is_pub) continue;
const pattern: Int = @bitCast(@field(FpClass, decl.name));
if (remaining & pattern == pattern) {
if (!any) {
try writer.writeByte(' ');
any = true;
}
try writer.writeAll(decl.name);
remaining &= ~pattern;
}
}
try writer.writeByte(')');
},
.alignstack => |alignment| try writer.print(
if (comptime std.mem.indexOfScalar(u8, fmt_str, '#') != null)
"{s}={d}"
else
"{s}({d})",
.{ @tagName(attribute), alignment.toByteUnits() orelse return },
),
.allockind => |allockind| {
try writer.print("{s}(\"", .{@tagName(attribute)});
var any = false;
inline for (@typeInfo(AllocKind).Struct.fields) |field| {
if (comptime std.mem.eql(u8, field.name, "_")) continue;
if (@field(allockind, field.name)) {
if (!any) {
try writer.writeByte(',');
any = true;
}
try writer.writeAll(field.name);
}
}
try writer.writeAll("\")");
},
.allocsize => |allocsize| {
try writer.print("{s}({d}", .{ @tagName(attribute), allocsize.elem_size });
if (allocsize.num_elems != AllocSize.none)
try writer.print(",{d}", .{allocsize.num_elems});
try writer.writeByte(')');
},
.memory => |memory| try writer.print("{s}({s}, argmem: {s}, inaccessiblemem: {s})", .{
@tagName(attribute),
@tagName(memory.other),
@tagName(memory.argmem),
@tagName(memory.inaccessiblemem),
}),
.uwtable => |uwtable| if (uwtable != .none) {
try writer.writeAll(@tagName(attribute));
if (uwtable != UwTable.default) try writer.print("({s})", .{@tagName(uwtable)});
},
.vscale_range => |vscale_range| try writer.print("{s}({d},{d})", .{
@tagName(attribute),
vscale_range.min.toByteUnits().?,
vscale_range.max.toByteUnits() orelse 0,
}),
.string => |string_attr| if (comptime std.mem.indexOfScalar(u8, fmt_str, '"') != null) {
try writer.print(" {\"}", .{string_attr.kind.fmt(data.builder)});
if (string_attr.value != .empty)
@ -1314,11 +1369,6 @@ pub const Attribute = union(Kind) {
positive_infinity: bool = false,
_: u22 = 0,
pub const nan = FpClass{ .signaling_nan = true, .quiet_nan = true };
pub const inf = FpClass{ .negative_infinity = true, .positive_infinity = true };
pub const norm = FpClass{ .positive_normal = true, .negative_normal = true };
pub const sub = FpClass{ .positive_subnormal = true, .negative_subnormal = true };
pub const zero = FpClass{ .positive_zero = true, .negative_zero = true };
pub const all = FpClass{
.signaling_nan = true,
.quiet_nan = true,
@ -1331,16 +1381,26 @@ pub const Attribute = union(Kind) {
.positive_normal = true,
.positive_infinity = true,
};
pub const nan = FpClass{ .signaling_nan = true, .quiet_nan = true };
pub const snan = FpClass{ .signaling_nan = true };
pub const qnan = FpClass{ .quiet_nan = true };
pub const inf = FpClass{ .negative_infinity = true, .positive_infinity = true };
pub const ninf = FpClass{ .negative_infinity = true };
pub const nnorm = FpClass{ .negative_normal = true };
pub const nsub = FpClass{ .negative_subnormal = true };
pub const pinf = FpClass{ .positive_infinity = true };
pub const zero = FpClass{ .positive_zero = true, .negative_zero = true };
pub const nzero = FpClass{ .negative_zero = true };
pub const pzero = FpClass{ .positive_zero = true };
pub const sub = FpClass{ .positive_subnormal = true, .negative_subnormal = true };
pub const nsub = FpClass{ .negative_subnormal = true };
pub const psub = FpClass{ .positive_subnormal = true };
pub const norm = FpClass{ .positive_normal = true, .negative_normal = true };
pub const nnorm = FpClass{ .negative_normal = true };
pub const pnorm = FpClass{ .positive_normal = true };
pub const pinf = FpClass{ .positive_infinity = true };
};
pub const AllocKind = packed struct(u32) {
@ -1924,7 +1984,7 @@ pub const CallConv = enum(u10) {
writer: anytype,
) @TypeOf(writer).Error!void {
switch (self) {
.ccc => {},
default => {},
.fastcc,
.coldcc,
.ghccc,
@ -2445,6 +2505,7 @@ pub const Function = struct {
.br_cond,
.ret,
.@"ret void",
.@"switch",
.@"unreachable",
=> true,
else => false,
@ -2462,6 +2523,7 @@ pub const Function = struct {
.@"store atomic",
.@"store atomic volatile",
.@"store volatile",
.@"switch",
.@"unreachable",
=> false,
.call,
@ -2472,6 +2534,7 @@ pub const Function = struct {
.@"notail call fast",
.@"tail call",
.@"tail call fast",
.unimplemented,
=> self.typeOfWip(wip) != .void,
else => true,
};
@ -4165,7 +4228,7 @@ pub const WipFunction = struct {
callee: Value,
args: []const Value,
name: []const u8,
) if (build_options.have_llvm) Allocator.Error!Value else Value {
) Allocator.Error!Value {
const ret_ty = ty.functionReturn(self.builder);
assert(ty.isFunction(self.builder));
assert(callee.typeOfWip(self).isPointer(self.builder));
@ -4238,6 +4301,20 @@ pub const WipFunction = struct {
return instruction.toValue();
}
pub fn callAsm(
self: *WipFunction,
function_attributes: FunctionAttributes,
ty: Type,
kind: Constant.Asm.Info,
assembly: String,
constraints: String,
args: []const Value,
name: []const u8,
) Allocator.Error!Value {
const callee = try self.builder.asmValue(ty, kind, assembly, constraints);
return self.call(.normal, CallConv.default, function_attributes, ty, callee, args, name);
}
pub fn vaArg(self: *WipFunction, list: Value, ty: Type, name: []const u8) Allocator.Error!Value {
try self.ensureUnusedExtraCapacity(1, Instruction.VaArg, 0);
const instruction = try self.addInst(name, .{
@ -5216,7 +5293,7 @@ pub const Constant = enum(u32) {
const first_global: Constant = @enumFromInt(1 << 30);
pub const Tag = enum(u6) {
pub const Tag = enum(u7) {
positive_integer,
negative_integer,
half,
@ -5276,6 +5353,22 @@ pub const Constant = enum(u32) {
@"and",
@"or",
xor,
@"asm",
@"asm sideeffect",
@"asm alignstack",
@"asm sideeffect alignstack",
@"asm inteldialect",
@"asm sideeffect inteldialect",
@"asm alignstack inteldialect",
@"asm sideeffect alignstack inteldialect",
@"asm unwind",
@"asm sideeffect unwind",
@"asm alignstack unwind",
@"asm sideeffect alignstack unwind",
@"asm inteldialect unwind",
@"asm sideeffect inteldialect unwind",
@"asm alignstack inteldialect unwind",
@"asm sideeffect alignstack inteldialect unwind",
};
pub const Item = struct {
@ -5371,6 +5464,19 @@ pub const Constant = enum(u32) {
rhs: Constant,
};
pub const Asm = extern struct {
type: Type,
assembly: String,
constraints: String,
pub const Info = packed struct {
sideeffect: bool = false,
alignstack: bool = false,
inteldialect: bool = false,
unwind: bool = false,
};
};
pub fn unwrap(self: Constant) union(enum) {
constant: u30,
global: Global.Index,
@ -5489,6 +5595,23 @@ pub const Constant = enum(u32) {
.@"or",
.xor,
=> builder.constantExtraData(Binary, item.data).lhs.typeOf(builder),
.@"asm",
.@"asm sideeffect",
.@"asm alignstack",
.@"asm sideeffect alignstack",
.@"asm inteldialect",
.@"asm sideeffect inteldialect",
.@"asm alignstack inteldialect",
.@"asm sideeffect alignstack inteldialect",
.@"asm unwind",
.@"asm sideeffect unwind",
.@"asm alignstack unwind",
.@"asm sideeffect alignstack unwind",
.@"asm inteldialect unwind",
.@"asm sideeffect inteldialect unwind",
.@"asm alignstack inteldialect unwind",
.@"asm sideeffect alignstack inteldialect unwind",
=> .ptr,
};
},
.global => |global| return builder.ptrTypeAssumeCapacity(
@ -5836,6 +5959,30 @@ pub const Constant = enum(u32) {
extra.rhs.fmt(data.builder),
});
},
.@"asm",
.@"asm sideeffect",
.@"asm alignstack",
.@"asm sideeffect alignstack",
.@"asm inteldialect",
.@"asm sideeffect inteldialect",
.@"asm alignstack inteldialect",
.@"asm sideeffect alignstack inteldialect",
.@"asm unwind",
.@"asm sideeffect unwind",
.@"asm alignstack unwind",
.@"asm sideeffect alignstack unwind",
.@"asm inteldialect unwind",
.@"asm sideeffect inteldialect unwind",
.@"asm alignstack inteldialect unwind",
.@"asm sideeffect alignstack inteldialect unwind",
=> |tag| {
const extra = data.builder.constantExtraData(Asm, item.data);
try writer.print("{s} {\"}, {\"}", .{
@tagName(tag),
extra.assembly.fmt(data.builder),
extra.constraints.fmt(data.builder),
});
},
}
},
.global => |global| try writer.print("{}", .{global.fmt(data.builder)}),
@ -6972,6 +7119,27 @@ pub fn binValue(self: *Builder, tag: Constant.Tag, lhs: Constant, rhs: Constant)
return (try self.binConst(tag, lhs, rhs)).toValue();
}
pub fn asmConst(
self: *Builder,
ty: Type,
info: Constant.Asm.Info,
assembly: String,
constraints: String,
) Allocator.Error!Constant {
try self.ensureUnusedConstantCapacity(1, Constant.Asm, 0);
return self.asmConstAssumeCapacity(ty, info, assembly, constraints);
}
pub fn asmValue(
self: *Builder,
ty: Type,
info: Constant.Asm.Info,
assembly: String,
constraints: String,
) Allocator.Error!Value {
return (try self.asmConst(ty, info, assembly, constraints)).toValue();
}
pub fn dump(self: *Builder) void {
if (self.useLibLlvm())
self.llvm.module.?.dump()
@ -7303,7 +7471,15 @@ pub fn printUnbuffered(
arg.fmt(function_index, self),
});
}
try writer.print("){}\n", .{extra.data.attributes.func(self).fmt(self)});
try writer.writeByte(')');
const call_function_attributes = extra.data.attributes.func(self);
if (call_function_attributes != .none) try writer.print(" #{d}", .{
(try attribute_groups.getOrPutValue(
self.gpa,
call_function_attributes,
{},
)).index,
});
},
.extractelement => |tag| {
const extra =
@ -7401,13 +7577,19 @@ pub fn printUnbuffered(
=> |tag| {
const extra = function.extraData(Function.Instruction.Binary, instruction.data);
const ty = instruction_index.typeOf(function_index, self);
try writer.print(" %{} = call {%} @{s}{m}({%}, {%})\n", .{
try writer.print(" %{} = call {%} @{s}{m}({%}, {%}{s})\n", .{
instruction_index.name(&function).fmt(self),
ty.fmt(self),
@tagName(tag),
ty.fmt(self),
extra.lhs.fmt(function_index, self),
extra.rhs.fmt(function_index, self),
switch (tag) {
.@"llvm.smul.fix.sat.",
.@"llvm.umul.fix.sat.",
=> ", i32 0",
else => "",
},
});
},
.load,
@ -7494,7 +7676,7 @@ pub fn printUnbuffered(
const vals = extra.trail.next(extra.data.cases_len, Constant, &function);
const blocks =
extra.trail.next(extra.data.cases_len, Function.Block.Index, &function);
try writer.print(" {s} {%}, {%} [", .{
try writer.print(" {s} {%}, {%} [\n", .{
@tagName(tag),
extra.data.val.fmt(function_index, self),
extra.data.default.toInst(&function).fmt(function_index, self),
@ -7507,14 +7689,22 @@ pub fn printUnbuffered(
},
.unimplemented => |tag| {
const ty: Type = @enumFromInt(instruction.data);
try writer.writeAll(" ");
switch (ty) {
if (true) {
try writer.writeAll(" ");
switch (ty) {
.none, .void => {},
else => try writer.print("%{} = ", .{
instruction_index.name(&function).fmt(self),
}),
}
try writer.print("{s} {%}\n", .{ @tagName(tag), ty.fmt(self) });
} else switch (ty) {
.none, .void => {},
else => try writer.print("%{} = ", .{
else => try writer.print(" %{} = load {%}, ptr undef\n", .{
instruction_index.name(&function).fmt(self),
ty.fmt(self),
}),
}
try writer.print("{s} {%}\n", .{ @tagName(tag), ty.fmt(self) });
},
.va_arg => |tag| {
const extra = function.extraData(Function.Instruction.VaArg, instruction.data);
@ -7534,7 +7724,7 @@ pub fn printUnbuffered(
for (0.., attribute_groups.keys()) |attribute_group_index, attribute_group|
try writer.print(
\\attribute #{d} = {{{"} }}
\\attributes #{d} = {{{#"} }}
\\
, .{ attribute_group_index, attribute_group.fmt(self) });
}
@ -9080,30 +9270,30 @@ fn binConstAssumeCapacity(
=> {},
else => unreachable,
}
const Key = struct { tag: Constant.Tag, bin: Constant.Binary };
const Key = struct { tag: Constant.Tag, extra: Constant.Binary };
const Adapter = struct {
builder: *const Builder,
pub fn hash(_: @This(), key: Key) u32 {
return @truncate(std.hash.Wyhash.hash(
std.hash.uint32(@intFromEnum(key.tag)),
std.mem.asBytes(&key.bin),
std.mem.asBytes(&key.extra),
));
}
pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool {
if (lhs_key.tag != ctx.builder.constant_items.items(.tag)[rhs_index]) return false;
const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index];
const rhs_extra = ctx.builder.constantExtraData(Constant.Binary, rhs_data);
return std.meta.eql(lhs_key.bin, rhs_extra);
return std.meta.eql(lhs_key.extra, rhs_extra);
}
};
const data = Key{ .tag = tag, .bin = .{ .lhs = lhs, .rhs = rhs } };
const data = Key{ .tag = tag, .extra = .{ .lhs = lhs, .rhs = rhs } };
const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self });
if (!gop.found_existing) {
gop.key_ptr.* = {};
gop.value_ptr.* = {};
self.constant_items.appendAssumeCapacity(.{
.tag = tag,
.data = self.addConstantExtraAssumeCapacity(data.bin),
.data = self.addConstantExtraAssumeCapacity(data.extra),
});
if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity(switch (tag) {
.add => &llvm.Value.constAdd,
@ -9121,6 +9311,61 @@ fn binConstAssumeCapacity(
return @enumFromInt(gop.index);
}
fn asmConstAssumeCapacity(
self: *Builder,
ty: Type,
info: Constant.Asm.Info,
assembly: String,
constraints: String,
) Constant {
const Key = struct { tag: Constant.Tag, extra: Constant.Asm };
const Adapter = struct {
builder: *const Builder,
pub fn hash(_: @This(), key: Key) u32 {
return @truncate(std.hash.Wyhash.hash(
std.hash.uint32(@intFromEnum(key.tag)),
std.mem.asBytes(&key.extra),
));
}
pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool {
if (lhs_key.tag != ctx.builder.constant_items.items(.tag)[rhs_index]) return false;
const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index];
const rhs_extra = ctx.builder.constantExtraData(Constant.Asm, rhs_data);
return std.meta.eql(lhs_key.extra, rhs_extra);
}
};
const data = Key{
.tag = @enumFromInt(@intFromEnum(Constant.Tag.@"asm") + @as(u4, @bitCast(info))),
.extra = .{ .type = ty, .assembly = assembly, .constraints = constraints },
};
const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self });
if (!gop.found_existing) {
gop.key_ptr.* = {};
gop.value_ptr.* = {};
self.constant_items.appendAssumeCapacity(.{
.tag = data.tag,
.data = self.addConstantExtraAssumeCapacity(data.extra),
});
if (self.useLibLlvm()) {
const assembly_slice = assembly.slice(self).?;
const constraints_slice = constraints.slice(self).?;
self.llvm.constants.appendAssumeCapacity(llvm.getInlineAsm(
ty.toLlvm(self),
assembly_slice.ptr,
assembly_slice.len,
constraints_slice.ptr,
constraints_slice.len,
llvm.Bool.fromBool(info.sideeffect),
llvm.Bool.fromBool(info.alignstack),
if (info.inteldialect) .Intel else .ATT,
llvm.Bool.fromBool(info.unwind),
));
}
}
return @enumFromInt(gop.index);
}
fn ensureUnusedConstantCapacity(
self: *Builder,
count: usize,
@ -9211,7 +9456,7 @@ fn addConstantExtraAssumeCapacity(self: *Builder, extra: anytype) Constant.Item.
const value = @field(extra, field.name);
self.constant_extra.appendAssumeCapacity(switch (field.type) {
u32 => value,
Type, Constant, Function.Index, Function.Block.Index => @intFromEnum(value),
String, Type, Constant, Function.Index, Function.Block.Index => @intFromEnum(value),
Constant.GetElementPtr.Info => @bitCast(value),
else => @compileError("bad field type: " ++ @typeName(field.type)),
});
@ -9250,7 +9495,7 @@ fn constantExtraDataTrail(
inline for (fields, self.constant_extra.items[index..][0..fields.len]) |field, value|
@field(result, field.name) = switch (field.type) {
u32 => value,
Type, Constant, Function.Index, Function.Block.Index => @enumFromInt(value),
String, Type, Constant, Function.Index, Function.Block.Index => @enumFromInt(value),
Constant.GetElementPtr.Info => @bitCast(value),
else => @compileError("bad field type: " ++ @typeName(field.type)),
};