struct WipFunction [src]
Fields
builder: *Builder
function: Function.Index
prev_debug_location: DebugLocation
debug_location: DebugLocation
cursor: Cursor
blocks: std.ArrayListUnmanaged(Block)
instructions: std.MultiArrayList(Instruction)
names: std.ArrayListUnmanaged(String)
strip: bool
debug_locations: std.AutoArrayHashMapUnmanaged(Instruction.Index, DebugLocation)
debug_values: std.AutoArrayHashMapUnmanaged(Instruction.Index, void)
extra: std.ArrayListUnmanaged(u32)
Members
- @"switch" (Function)
- @"unreachable" (Function)
- alloca (Function)
- arg (Function)
- atomicrmw (Function)
- bin (Function)
- block (Function)
- Block (struct)
- br (Function)
- brCond (Function)
- buildAggregate (Function)
- call (Function)
- callAsm (Function)
- callIntrinsic (Function)
- callIntrinsicAssumeCold (Function)
- callMemCpy (Function)
- callMemSet (Function)
- cast (Function)
- cmpxchg (Function)
- conv (Function)
- Cursor (struct)
- debugValue (Function)
- deinit (Function)
- extractElement (Function)
- extractValue (Function)
- fcmp (Function)
- fence (Function)
- finish (Function)
- gep (Function)
- gepStruct (Function)
- icmp (Function)
- indirectbr (Function)
- init (Function)
- insertElement (Function)
- insertValue (Function)
- Instruction (struct)
- load (Function)
- loadAtomic (Function)
- neg (Function)
- not (Function)
- phi (Function)
- phiFast (Function)
- ret (Function)
- retVoid (Function)
- select (Function)
- shuffleVector (Function)
- splatVector (Function)
- store (Function)
- storeAtomic (Function)
- un (Function)
- vaArg (Function)
- WipPhi (struct)
- WipSwitch (struct)
Source
pub const WipFunction = struct {
builder: *Builder,
function: Function.Index,
prev_debug_location: DebugLocation,
debug_location: DebugLocation,
cursor: Cursor,
blocks: std.ArrayListUnmanaged(Block),
instructions: std.MultiArrayList(Instruction),
names: std.ArrayListUnmanaged(String),
strip: bool,
debug_locations: std.AutoArrayHashMapUnmanaged(Instruction.Index, DebugLocation),
debug_values: std.AutoArrayHashMapUnmanaged(Instruction.Index, void),
extra: std.ArrayListUnmanaged(u32),
pub const Cursor = struct { block: Block.Index, instruction: u32 = 0 };
pub const Block = struct {
name: String,
incoming: u32,
branches: u32 = 0,
instructions: std.ArrayListUnmanaged(Instruction.Index),
const Index = enum(u32) {
entry,
_,
pub fn ptr(self: Index, wip: *WipFunction) *Block {
return &wip.blocks.items[@intFromEnum(self)];
}
pub fn ptrConst(self: Index, wip: *const WipFunction) *const Block {
return &wip.blocks.items[@intFromEnum(self)];
}
pub fn toInst(self: Index, function: *const Function) Instruction.Index {
return function.blocks[@intFromEnum(self)].instruction;
}
};
};
pub const Instruction = Function.Instruction;
pub fn init(builder: *Builder, options: struct {
function: Function.Index,
strip: bool,
}) Allocator.Error!WipFunction {
var self: WipFunction = .{
.builder = builder,
.function = options.function,
.prev_debug_location = .no_location,
.debug_location = .no_location,
.cursor = undefined,
.blocks = .{},
.instructions = .{},
.names = .{},
.strip = options.strip,
.debug_locations = .{},
.debug_values = .{},
.extra = .{},
};
errdefer self.deinit();
const params_len = options.function.typeOf(self.builder).functionParameters(self.builder).len;
try self.ensureUnusedExtraCapacity(params_len, NoExtra, 0);
try self.instructions.ensureUnusedCapacity(self.builder.gpa, params_len);
if (!self.strip) {
try self.names.ensureUnusedCapacity(self.builder.gpa, params_len);
}
for (0..params_len) |param_index| {
self.instructions.appendAssumeCapacity(.{ .tag = .arg, .data = @intCast(param_index) });
if (!self.strip) {
self.names.appendAssumeCapacity(.empty); // TODO: param names
}
}
return self;
}
pub fn arg(self: *const WipFunction, index: u32) Value {
const argument = self.instructions.get(index);
assert(argument.tag == .arg);
assert(argument.data == index);
const argument_index: Instruction.Index = @enumFromInt(index);
return argument_index.toValue();
}
pub fn block(self: *WipFunction, incoming: u32, name: []const u8) Allocator.Error!Block.Index {
try self.blocks.ensureUnusedCapacity(self.builder.gpa, 1);
const index: Block.Index = @enumFromInt(self.blocks.items.len);
const final_name = if (self.strip) .empty else try self.builder.string(name);
self.blocks.appendAssumeCapacity(.{
.name = final_name,
.incoming = incoming,
.instructions = .{},
});
return index;
}
pub fn ret(self: *WipFunction, val: Value) Allocator.Error!Instruction.Index {
assert(val.typeOfWip(self) == self.function.typeOf(self.builder).functionReturn(self.builder));
try self.ensureUnusedExtraCapacity(1, NoExtra, 0);
return try self.addInst(null, .{ .tag = .ret, .data = @intFromEnum(val) });
}
pub fn retVoid(self: *WipFunction) Allocator.Error!Instruction.Index {
try self.ensureUnusedExtraCapacity(1, NoExtra, 0);
return try self.addInst(null, .{ .tag = .@"ret void", .data = undefined });
}
pub fn br(self: *WipFunction, dest: Block.Index) Allocator.Error!Instruction.Index {
try self.ensureUnusedExtraCapacity(1, NoExtra, 0);
const instruction = try self.addInst(null, .{ .tag = .br, .data = @intFromEnum(dest) });
dest.ptr(self).branches += 1;
return instruction;
}
pub fn brCond(
self: *WipFunction,
cond: Value,
then: Block.Index,
@"else": Block.Index,
weights: enum { none, unpredictable, then_likely, else_likely },
) Allocator.Error!Instruction.Index {
assert(cond.typeOfWip(self) == .i1);
try self.ensureUnusedExtraCapacity(1, Instruction.BrCond, 0);
const instruction = try self.addInst(null, .{
.tag = .br_cond,
.data = self.addExtraAssumeCapacity(Instruction.BrCond{
.cond = cond,
.then = then,
.@"else" = @"else",
.weights = switch (weights) {
.none => .none,
.unpredictable => .unpredictable,
.then_likely, .else_likely => w: {
const branch_weights_str = try self.builder.metadataString("branch_weights");
const unlikely_const = try self.builder.metadataConstant(try self.builder.intConst(.i32, 1));
const likely_const = try self.builder.metadataConstant(try self.builder.intConst(.i32, 2000));
const weight_vals: [2]Metadata = switch (weights) {
.none, .unpredictable => unreachable,
.then_likely => .{ likely_const, unlikely_const },
.else_likely => .{ unlikely_const, likely_const },
};
const tuple = try self.builder.strTuple(branch_weights_str, &weight_vals);
break :w @enumFromInt(@intFromEnum(tuple));
},
},
}),
});
then.ptr(self).branches += 1;
@"else".ptr(self).branches += 1;
return instruction;
}
pub const WipSwitch = struct {
index: u32,
instruction: Instruction.Index,
pub fn addCase(
self: *WipSwitch,
val: Constant,
dest: Block.Index,
wip: *WipFunction,
) Allocator.Error!void {
const instruction = wip.instructions.get(@intFromEnum(self.instruction));
var extra = wip.extraDataTrail(Instruction.Switch, instruction.data);
assert(val.typeOf(wip.builder) == extra.data.val.typeOfWip(wip));
extra.trail.nextMut(extra.data.cases_len, Constant, wip)[self.index] = val;
extra.trail.nextMut(extra.data.cases_len, Block.Index, wip)[self.index] = dest;
self.index += 1;
dest.ptr(wip).branches += 1;
}
pub fn finish(self: WipSwitch, wip: *WipFunction) void {
const instruction = wip.instructions.get(@intFromEnum(self.instruction));
const extra = wip.extraData(Instruction.Switch, instruction.data);
assert(self.index == extra.cases_len);
}
};
pub fn @"switch"(
self: *WipFunction,
val: Value,
default: Block.Index,
cases_len: u32,
weights: Instruction.BrCond.Weights,
) Allocator.Error!WipSwitch {
try self.ensureUnusedExtraCapacity(1, Instruction.Switch, cases_len * 2);
const instruction = try self.addInst(null, .{
.tag = .@"switch",
.data = self.addExtraAssumeCapacity(Instruction.Switch{
.val = val,
.default = default,
.cases_len = cases_len,
.weights = weights,
}),
});
_ = self.extra.addManyAsSliceAssumeCapacity(cases_len * 2);
default.ptr(self).branches += 1;
return .{ .index = 0, .instruction = instruction };
}
pub fn indirectbr(
self: *WipFunction,
addr: Value,
targets: []const Block.Index,
) Allocator.Error!Instruction.Index {
try self.ensureUnusedExtraCapacity(1, Instruction.IndirectBr, targets.len);
const instruction = try self.addInst(null, .{
.tag = .indirectbr,
.data = self.addExtraAssumeCapacity(Instruction.IndirectBr{
.addr = addr,
.targets_len = @intCast(targets.len),
}),
});
_ = self.extra.appendSliceAssumeCapacity(@ptrCast(targets));
for (targets) |target| target.ptr(self).branches += 1;
return instruction;
}
pub fn @"unreachable"(self: *WipFunction) Allocator.Error!Instruction.Index {
try self.ensureUnusedExtraCapacity(1, NoExtra, 0);
return try self.addInst(null, .{ .tag = .@"unreachable", .data = undefined });
}
pub fn un(
self: *WipFunction,
tag: Instruction.Tag,
val: Value,
name: []const u8,
) Allocator.Error!Value {
switch (tag) {
.fneg,
.@"fneg fast",
=> assert(val.typeOfWip(self).scalarType(self.builder).isFloatingPoint()),
else => unreachable,
}
try self.ensureUnusedExtraCapacity(1, NoExtra, 0);
const instruction = try self.addInst(name, .{ .tag = tag, .data = @intFromEnum(val) });
return instruction.toValue();
}
pub fn not(self: *WipFunction, val: Value, name: []const u8) Allocator.Error!Value {
const ty = val.typeOfWip(self);
const all_ones = try self.builder.splatValue(
ty,
try self.builder.intConst(ty.scalarType(self.builder), -1),
);
return self.bin(.xor, val, all_ones, name);
}
pub fn neg(self: *WipFunction, val: Value, name: []const u8) Allocator.Error!Value {
return self.bin(.sub, try self.builder.zeroInitValue(val.typeOfWip(self)), val, name);
}
pub fn bin(
self: *WipFunction,
tag: Instruction.Tag,
lhs: Value,
rhs: Value,
name: []const u8,
) Allocator.Error!Value {
switch (tag) {
.add,
.@"add nsw",
.@"add nuw",
.@"and",
.ashr,
.@"ashr exact",
.fadd,
.@"fadd fast",
.fdiv,
.@"fdiv fast",
.fmul,
.@"fmul fast",
.frem,
.@"frem fast",
.fsub,
.@"fsub fast",
.lshr,
.@"lshr exact",
.mul,
.@"mul nsw",
.@"mul nuw",
.@"or",
.sdiv,
.@"sdiv exact",
.shl,
.@"shl nsw",
.@"shl nuw",
.srem,
.sub,
.@"sub nsw",
.@"sub nuw",
.udiv,
.@"udiv exact",
.urem,
.xor,
=> assert(lhs.typeOfWip(self) == rhs.typeOfWip(self)),
else => unreachable,
}
try self.ensureUnusedExtraCapacity(1, Instruction.Binary, 0);
const instruction = try self.addInst(name, .{
.tag = tag,
.data = self.addExtraAssumeCapacity(Instruction.Binary{ .lhs = lhs, .rhs = rhs }),
});
return instruction.toValue();
}
pub fn extractElement(
self: *WipFunction,
val: Value,
index: Value,
name: []const u8,
) Allocator.Error!Value {
assert(val.typeOfWip(self).isVector(self.builder));
assert(index.typeOfWip(self).isInteger(self.builder));
try self.ensureUnusedExtraCapacity(1, Instruction.ExtractElement, 0);
const instruction = try self.addInst(name, .{
.tag = .extractelement,
.data = self.addExtraAssumeCapacity(Instruction.ExtractElement{
.val = val,
.index = index,
}),
});
return instruction.toValue();
}
pub fn insertElement(
self: *WipFunction,
val: Value,
elem: Value,
index: Value,
name: []const u8,
) Allocator.Error!Value {
assert(val.typeOfWip(self).scalarType(self.builder) == elem.typeOfWip(self));
assert(index.typeOfWip(self).isInteger(self.builder));
try self.ensureUnusedExtraCapacity(1, Instruction.InsertElement, 0);
const instruction = try self.addInst(name, .{
.tag = .insertelement,
.data = self.addExtraAssumeCapacity(Instruction.InsertElement{
.val = val,
.elem = elem,
.index = index,
}),
});
return instruction.toValue();
}
pub fn shuffleVector(
self: *WipFunction,
lhs: Value,
rhs: Value,
mask: Value,
name: []const u8,
) Allocator.Error!Value {
assert(lhs.typeOfWip(self).isVector(self.builder));
assert(lhs.typeOfWip(self) == rhs.typeOfWip(self));
assert(mask.typeOfWip(self).scalarType(self.builder).isInteger(self.builder));
_ = try self.ensureUnusedExtraCapacity(1, Instruction.ShuffleVector, 0);
const instruction = try self.addInst(name, .{
.tag = .shufflevector,
.data = self.addExtraAssumeCapacity(Instruction.ShuffleVector{
.lhs = lhs,
.rhs = rhs,
.mask = mask,
}),
});
return instruction.toValue();
}
pub fn splatVector(
self: *WipFunction,
ty: Type,
elem: Value,
name: []const u8,
) Allocator.Error!Value {
const scalar_ty = try ty.changeLength(1, self.builder);
const mask_ty = try ty.changeScalar(.i32, self.builder);
const poison = try self.builder.poisonValue(scalar_ty);
const mask = try self.builder.splatValue(mask_ty, .@"0");
const scalar = try self.insertElement(poison, elem, .@"0", name);
return self.shuffleVector(scalar, poison, mask, name);
}
pub fn extractValue(
self: *WipFunction,
val: Value,
indices: []const u32,
name: []const u8,
) Allocator.Error!Value {
assert(indices.len > 0);
_ = val.typeOfWip(self).childTypeAt(indices, self.builder);
try self.ensureUnusedExtraCapacity(1, Instruction.ExtractValue, indices.len);
const instruction = try self.addInst(name, .{
.tag = .extractvalue,
.data = self.addExtraAssumeCapacity(Instruction.ExtractValue{
.val = val,
.indices_len = @intCast(indices.len),
}),
});
self.extra.appendSliceAssumeCapacity(indices);
return instruction.toValue();
}
pub fn insertValue(
self: *WipFunction,
val: Value,
elem: Value,
indices: []const u32,
name: []const u8,
) Allocator.Error!Value {
assert(indices.len > 0);
assert(val.typeOfWip(self).childTypeAt(indices, self.builder) == elem.typeOfWip(self));
try self.ensureUnusedExtraCapacity(1, Instruction.InsertValue, indices.len);
const instruction = try self.addInst(name, .{
.tag = .insertvalue,
.data = self.addExtraAssumeCapacity(Instruction.InsertValue{
.val = val,
.elem = elem,
.indices_len = @intCast(indices.len),
}),
});
self.extra.appendSliceAssumeCapacity(indices);
return instruction.toValue();
}
pub fn buildAggregate(
self: *WipFunction,
ty: Type,
elems: []const Value,
name: []const u8,
) Allocator.Error!Value {
assert(ty.aggregateLen(self.builder) == elems.len);
var cur = try self.builder.poisonValue(ty);
for (elems, 0..) |elem, index|
cur = try self.insertValue(cur, elem, &[_]u32{@intCast(index)}, name);
return cur;
}
pub fn alloca(
self: *WipFunction,
kind: Instruction.Alloca.Kind,
ty: Type,
len: Value,
alignment: Alignment,
addr_space: AddrSpace,
name: []const u8,
) Allocator.Error!Value {
assert(len == .none or len.typeOfWip(self).isInteger(self.builder));
_ = try self.builder.ptrType(addr_space);
try self.ensureUnusedExtraCapacity(1, Instruction.Alloca, 0);
const instruction = try self.addInst(name, .{
.tag = switch (kind) {
.normal => .alloca,
.inalloca => .@"alloca inalloca",
},
.data = self.addExtraAssumeCapacity(Instruction.Alloca{
.type = ty,
.len = switch (len) {
.none => .@"1",
else => len,
},
.info = .{ .alignment = alignment, .addr_space = addr_space },
}),
});
return instruction.toValue();
}
pub fn load(
self: *WipFunction,
access_kind: MemoryAccessKind,
ty: Type,
ptr: Value,
alignment: Alignment,
name: []const u8,
) Allocator.Error!Value {
return self.loadAtomic(access_kind, ty, ptr, .system, .none, alignment, name);
}
pub fn loadAtomic(
self: *WipFunction,
access_kind: MemoryAccessKind,
ty: Type,
ptr: Value,
sync_scope: SyncScope,
ordering: AtomicOrdering,
alignment: Alignment,
name: []const u8,
) Allocator.Error!Value {
assert(ptr.typeOfWip(self).isPointer(self.builder));
try self.ensureUnusedExtraCapacity(1, Instruction.Load, 0);
const instruction = try self.addInst(name, .{
.tag = switch (ordering) {
.none => .load,
else => .@"load atomic",
},
.data = self.addExtraAssumeCapacity(Instruction.Load{
.info = .{
.access_kind = access_kind,
.sync_scope = switch (ordering) {
.none => .system,
else => sync_scope,
},
.success_ordering = ordering,
.alignment = alignment,
},
.type = ty,
.ptr = ptr,
}),
});
return instruction.toValue();
}
pub fn store(
self: *WipFunction,
kind: MemoryAccessKind,
val: Value,
ptr: Value,
alignment: Alignment,
) Allocator.Error!Instruction.Index {
return self.storeAtomic(kind, val, ptr, .system, .none, alignment);
}
pub fn storeAtomic(
self: *WipFunction,
access_kind: MemoryAccessKind,
val: Value,
ptr: Value,
sync_scope: SyncScope,
ordering: AtomicOrdering,
alignment: Alignment,
) Allocator.Error!Instruction.Index {
assert(ptr.typeOfWip(self).isPointer(self.builder));
try self.ensureUnusedExtraCapacity(1, Instruction.Store, 0);
const instruction = try self.addInst(null, .{
.tag = switch (ordering) {
.none => .store,
else => .@"store atomic",
},
.data = self.addExtraAssumeCapacity(Instruction.Store{
.info = .{
.access_kind = access_kind,
.sync_scope = switch (ordering) {
.none => .system,
else => sync_scope,
},
.success_ordering = ordering,
.alignment = alignment,
},
.val = val,
.ptr = ptr,
}),
});
return instruction;
}
pub fn fence(
self: *WipFunction,
sync_scope: SyncScope,
ordering: AtomicOrdering,
) Allocator.Error!Instruction.Index {
assert(ordering != .none);
try self.ensureUnusedExtraCapacity(1, NoExtra, 0);
const instruction = try self.addInst(null, .{
.tag = .fence,
.data = @bitCast(MemoryAccessInfo{
.sync_scope = sync_scope,
.success_ordering = ordering,
}),
});
return instruction;
}
pub fn cmpxchg(
self: *WipFunction,
kind: Instruction.CmpXchg.Kind,
access_kind: MemoryAccessKind,
ptr: Value,
cmp: Value,
new: Value,
sync_scope: SyncScope,
success_ordering: AtomicOrdering,
failure_ordering: AtomicOrdering,
alignment: Alignment,
name: []const u8,
) Allocator.Error!Value {
assert(ptr.typeOfWip(self).isPointer(self.builder));
const ty = cmp.typeOfWip(self);
assert(ty == new.typeOfWip(self));
assert(success_ordering != .none);
assert(failure_ordering != .none);
_ = try self.builder.structType(.normal, &.{ ty, .i1 });
try self.ensureUnusedExtraCapacity(1, Instruction.CmpXchg, 0);
const instruction = try self.addInst(name, .{
.tag = switch (kind) {
.strong => .cmpxchg,
.weak => .@"cmpxchg weak",
},
.data = self.addExtraAssumeCapacity(Instruction.CmpXchg{
.info = .{
.access_kind = access_kind,
.sync_scope = sync_scope,
.success_ordering = success_ordering,
.failure_ordering = failure_ordering,
.alignment = alignment,
},
.ptr = ptr,
.cmp = cmp,
.new = new,
}),
});
return instruction.toValue();
}
pub fn atomicrmw(
self: *WipFunction,
access_kind: MemoryAccessKind,
operation: Instruction.AtomicRmw.Operation,
ptr: Value,
val: Value,
sync_scope: SyncScope,
ordering: AtomicOrdering,
alignment: Alignment,
name: []const u8,
) Allocator.Error!Value {
assert(ptr.typeOfWip(self).isPointer(self.builder));
assert(ordering != .none);
try self.ensureUnusedExtraCapacity(1, Instruction.AtomicRmw, 0);
const instruction = try self.addInst(name, .{
.tag = .atomicrmw,
.data = self.addExtraAssumeCapacity(Instruction.AtomicRmw{
.info = .{
.access_kind = access_kind,
.atomic_rmw_operation = operation,
.sync_scope = sync_scope,
.success_ordering = ordering,
.alignment = alignment,
},
.ptr = ptr,
.val = val,
}),
});
return instruction.toValue();
}
pub fn gep(
self: *WipFunction,
kind: Instruction.GetElementPtr.Kind,
ty: Type,
base: Value,
indices: []const Value,
name: []const u8,
) Allocator.Error!Value {
const base_ty = base.typeOfWip(self);
const base_is_vector = base_ty.isVector(self.builder);
const VectorInfo = struct {
kind: Type.Vector.Kind,
len: u32,
fn init(vector_ty: Type, builder: *const Builder) @This() {
return .{ .kind = vector_ty.vectorKind(builder), .len = vector_ty.vectorLen(builder) };
}
};
var vector_info: ?VectorInfo =
if (base_is_vector) VectorInfo.init(base_ty, self.builder) else null;
for (indices) |index| {
const index_ty = index.typeOfWip(self);
switch (index_ty.tag(self.builder)) {
.integer => {},
.vector, .scalable_vector => {
const index_info = VectorInfo.init(index_ty, self.builder);
if (vector_info) |info|
assert(std.meta.eql(info, index_info))
else
vector_info = index_info;
},
else => unreachable,
}
}
if (!base_is_vector) if (vector_info) |info| switch (info.kind) {
inline else => |vector_kind| _ = try self.builder.vectorType(
vector_kind,
info.len,
base_ty,
),
};
try self.ensureUnusedExtraCapacity(1, Instruction.GetElementPtr, indices.len);
const instruction = try self.addInst(name, .{
.tag = switch (kind) {
.normal => .getelementptr,
.inbounds => .@"getelementptr inbounds",
},
.data = self.addExtraAssumeCapacity(Instruction.GetElementPtr{
.type = ty,
.base = base,
.indices_len = @intCast(indices.len),
}),
});
self.extra.appendSliceAssumeCapacity(@ptrCast(indices));
return instruction.toValue();
}
pub fn gepStruct(
self: *WipFunction,
ty: Type,
base: Value,
index: usize,
name: []const u8,
) Allocator.Error!Value {
assert(ty.isStruct(self.builder));
return self.gep(.inbounds, ty, base, &.{ .@"0", try self.builder.intValue(.i32, index) }, name);
}
pub fn conv(
self: *WipFunction,
signedness: Instruction.Cast.Signedness,
val: Value,
ty: Type,
name: []const u8,
) Allocator.Error!Value {
const val_ty = val.typeOfWip(self);
if (val_ty == ty) return val;
return self.cast(self.builder.convTag(signedness, val_ty, ty), val, ty, name);
}
pub fn cast(
self: *WipFunction,
tag: Instruction.Tag,
val: Value,
ty: Type,
name: []const u8,
) Allocator.Error!Value {
switch (tag) {
.addrspacecast,
.bitcast,
.fpext,
.fptosi,
.fptoui,
.fptrunc,
.inttoptr,
.ptrtoint,
.sext,
.sitofp,
.trunc,
.uitofp,
.zext,
=> {},
else => unreachable,
}
if (val.typeOfWip(self) == ty) return val;
try self.ensureUnusedExtraCapacity(1, Instruction.Cast, 0);
const instruction = try self.addInst(name, .{
.tag = tag,
.data = self.addExtraAssumeCapacity(Instruction.Cast{
.val = val,
.type = ty,
}),
});
return instruction.toValue();
}
pub fn icmp(
self: *WipFunction,
cond: IntegerCondition,
lhs: Value,
rhs: Value,
name: []const u8,
) Allocator.Error!Value {
return self.cmpTag(switch (cond) {
inline else => |tag| @field(Instruction.Tag, "icmp " ++ @tagName(tag)),
}, lhs, rhs, name);
}
pub fn fcmp(
self: *WipFunction,
fast: FastMathKind,
cond: FloatCondition,
lhs: Value,
rhs: Value,
name: []const u8,
) Allocator.Error!Value {
return self.cmpTag(switch (fast) {
inline else => |fast_tag| switch (cond) {
inline else => |cond_tag| @field(Instruction.Tag, "fcmp " ++ switch (fast_tag) {
.normal => "",
.fast => "fast ",
} ++ @tagName(cond_tag)),
},
}, lhs, rhs, name);
}
pub const WipPhi = struct {
block: Block.Index,
instruction: Instruction.Index,
pub fn toValue(self: WipPhi) Value {
return self.instruction.toValue();
}
pub fn finish(
self: WipPhi,
vals: []const Value,
blocks: []const Block.Index,
wip: *WipFunction,
) void {
const incoming_len = self.block.ptrConst(wip).incoming;
assert(vals.len == incoming_len and blocks.len == incoming_len);
const instruction = wip.instructions.get(@intFromEnum(self.instruction));
var extra = wip.extraDataTrail(Instruction.Phi, instruction.data);
for (vals) |val| assert(val.typeOfWip(wip) == extra.data.type);
@memcpy(extra.trail.nextMut(incoming_len, Value, wip), vals);
@memcpy(extra.trail.nextMut(incoming_len, Block.Index, wip), blocks);
}
};
pub fn phi(self: *WipFunction, ty: Type, name: []const u8) Allocator.Error!WipPhi {
return self.phiTag(.phi, ty, name);
}
pub fn phiFast(self: *WipFunction, ty: Type, name: []const u8) Allocator.Error!WipPhi {
return self.phiTag(.@"phi fast", ty, name);
}
pub fn select(
self: *WipFunction,
fast: FastMathKind,
cond: Value,
lhs: Value,
rhs: Value,
name: []const u8,
) Allocator.Error!Value {
return self.selectTag(switch (fast) {
.normal => .select,
.fast => .@"select fast",
}, cond, lhs, rhs, name);
}
pub fn call(
self: *WipFunction,
kind: Instruction.Call.Kind,
call_conv: CallConv,
function_attributes: FunctionAttributes,
ty: Type,
callee: Value,
args: []const Value,
name: []const u8,
) Allocator.Error!Value {
return self.callInner(kind, call_conv, function_attributes, ty, callee, args, name, false);
}
fn callInner(
self: *WipFunction,
kind: Instruction.Call.Kind,
call_conv: CallConv,
function_attributes: FunctionAttributes,
ty: Type,
callee: Value,
args: []const Value,
name: []const u8,
has_op_bundle_cold: bool,
) Allocator.Error!Value {
const ret_ty = ty.functionReturn(self.builder);
assert(ty.isFunction(self.builder));
assert(callee.typeOfWip(self).isPointer(self.builder));
const params = ty.functionParameters(self.builder);
for (params, args[0..params.len]) |param, arg_val| assert(param == arg_val.typeOfWip(self));
try self.ensureUnusedExtraCapacity(1, Instruction.Call, args.len);
const instruction = try self.addInst(switch (ret_ty) {
.void => null,
else => name,
}, .{
.tag = switch (kind) {
.normal => .call,
.fast => .@"call fast",
.musttail => .@"musttail call",
.musttail_fast => .@"musttail call fast",
.notail => .@"notail call",
.notail_fast => .@"notail call fast",
.tail => .@"tail call",
.tail_fast => .@"tail call fast",
},
.data = self.addExtraAssumeCapacity(Instruction.Call{
.info = .{
.call_conv = call_conv,
.has_op_bundle_cold = has_op_bundle_cold,
},
.attributes = function_attributes,
.ty = ty,
.callee = callee,
.args_len = @intCast(args.len),
}),
});
self.extra.appendSliceAssumeCapacity(@ptrCast(args));
return instruction.toValue();
}
pub fn callAsm(
self: *WipFunction,
function_attributes: FunctionAttributes,
ty: Type,
kind: Constant.Assembly.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 callIntrinsic(
self: *WipFunction,
fast: FastMathKind,
function_attributes: FunctionAttributes,
id: Intrinsic,
overload: []const Type,
args: []const Value,
name: []const u8,
) Allocator.Error!Value {
const intrinsic = try self.builder.getIntrinsic(id, overload);
return self.call(
fast.toCallKind(),
CallConv.default,
function_attributes,
intrinsic.typeOf(self.builder),
intrinsic.toValue(self.builder),
args,
name,
);
}
pub fn callIntrinsicAssumeCold(self: *WipFunction) Allocator.Error!Value {
const intrinsic = try self.builder.getIntrinsic(.assume, &.{});
return self.callInner(
.normal,
CallConv.default,
.none,
intrinsic.typeOf(self.builder),
intrinsic.toValue(self.builder),
&.{try self.builder.intValue(.i1, 1)},
"",
true,
);
}
pub fn callMemCpy(
self: *WipFunction,
dst: Value,
dst_align: Alignment,
src: Value,
src_align: Alignment,
len: Value,
kind: MemoryAccessKind,
@"inline": bool,
) Allocator.Error!Instruction.Index {
var dst_attrs = [_]Attribute.Index{try self.builder.attr(.{ .@"align" = dst_align })};
var src_attrs = [_]Attribute.Index{try self.builder.attr(.{ .@"align" = src_align })};
const value = try self.callIntrinsic(
.normal,
try self.builder.fnAttrs(&.{
.none,
.none,
try self.builder.attrs(&dst_attrs),
try self.builder.attrs(&src_attrs),
}),
if (@"inline") .@"memcpy.inline" else .memcpy,
&.{ dst.typeOfWip(self), src.typeOfWip(self), len.typeOfWip(self) },
&.{ dst, src, len, switch (kind) {
.normal => Value.false,
.@"volatile" => Value.true,
} },
undefined,
);
return value.unwrap().instruction;
}
pub fn callMemSet(
self: *WipFunction,
dst: Value,
dst_align: Alignment,
val: Value,
len: Value,
kind: MemoryAccessKind,
@"inline": bool,
) Allocator.Error!Instruction.Index {
var dst_attrs = [_]Attribute.Index{try self.builder.attr(.{ .@"align" = dst_align })};
const value = try self.callIntrinsic(
.normal,
try self.builder.fnAttrs(&.{ .none, .none, try self.builder.attrs(&dst_attrs) }),
if (@"inline") .@"memset.inline" else .memset,
&.{ dst.typeOfWip(self), len.typeOfWip(self) },
&.{ dst, val, len, switch (kind) {
.normal => Value.false,
.@"volatile" => Value.true,
} },
undefined,
);
return value.unwrap().instruction;
}
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, .{
.tag = .va_arg,
.data = self.addExtraAssumeCapacity(Instruction.VaArg{
.list = list,
.type = ty,
}),
});
return instruction.toValue();
}
pub fn debugValue(self: *WipFunction, value: Value) Allocator.Error!Metadata {
if (self.strip) return .none;
return switch (value.unwrap()) {
.instruction => |instr_index| blk: {
const gop = try self.debug_values.getOrPut(self.builder.gpa, instr_index);
const metadata: Metadata = @enumFromInt(Metadata.first_local_metadata + gop.index);
if (!gop.found_existing) gop.key_ptr.* = instr_index;
break :blk metadata;
},
.constant => |constant| try self.builder.metadataConstant(constant),
.metadata => |metadata| metadata,
};
}
pub fn finish(self: *WipFunction) Allocator.Error!void {
const gpa = self.builder.gpa;
const function = self.function.ptr(self.builder);
const params_len = self.function.typeOf(self.builder).functionParameters(self.builder).len;
const final_instructions_len = self.blocks.items.len + self.instructions.len;
const blocks = try gpa.alloc(Function.Block, self.blocks.items.len);
errdefer gpa.free(blocks);
const instructions: struct {
items: []Instruction.Index,
fn map(instructions: @This(), val: Value) Value {
if (val == .none) return .none;
return switch (val.unwrap()) {
.instruction => |instruction| instructions.items[
@intFromEnum(instruction)
].toValue(),
.constant => |constant| constant.toValue(),
.metadata => |metadata| metadata.toValue(),
};
}
} = .{ .items = try gpa.alloc(Instruction.Index, self.instructions.len) };
defer gpa.free(instructions.items);
const names = try gpa.alloc(String, final_instructions_len);
errdefer gpa.free(names);
const value_indices = try gpa.alloc(u32, final_instructions_len);
errdefer gpa.free(value_indices);
var debug_locations: std.AutoHashMapUnmanaged(Instruction.Index, DebugLocation) = .empty;
errdefer debug_locations.deinit(gpa);
try debug_locations.ensureUnusedCapacity(gpa, @intCast(self.debug_locations.count()));
const debug_values = try gpa.alloc(Instruction.Index, self.debug_values.count());
errdefer gpa.free(debug_values);
var wip_extra: struct {
index: Instruction.ExtraIndex = 0,
items: []u32,
fn addExtra(wip_extra: *@This(), extra: anytype) Instruction.ExtraIndex {
const result = wip_extra.index;
inline for (@typeInfo(@TypeOf(extra)).@"struct".fields) |field| {
const value = @field(extra, field.name);
wip_extra.items[wip_extra.index] = switch (field.type) {
u32 => value,
Alignment,
AtomicOrdering,
Block.Index,
FunctionAttributes,
Type,
Value,
Instruction.BrCond.Weights,
=> @intFromEnum(value),
MemoryAccessInfo,
Instruction.Alloca.Info,
Instruction.Call.Info,
=> @bitCast(value),
else => @compileError("bad field type: " ++ field.name ++ ": " ++ @typeName(field.type)),
};
wip_extra.index += 1;
}
return result;
}
fn appendSlice(wip_extra: *@This(), slice: anytype) void {
if (@typeInfo(@TypeOf(slice)).pointer.child == Value)
@compileError("use appendMappedValues");
const data: []const u32 = @ptrCast(slice);
@memcpy(wip_extra.items[wip_extra.index..][0..data.len], data);
wip_extra.index += @intCast(data.len);
}
fn appendMappedValues(wip_extra: *@This(), vals: []const Value, ctx: anytype) void {
for (wip_extra.items[wip_extra.index..][0..vals.len], vals) |*extra, val|
extra.* = @intFromEnum(ctx.map(val));
wip_extra.index += @intCast(vals.len);
}
fn finish(wip_extra: *const @This()) []const u32 {
assert(wip_extra.index == wip_extra.items.len);
return wip_extra.items;
}
} = .{ .items = try gpa.alloc(u32, self.extra.items.len) };
errdefer gpa.free(wip_extra.items);
gpa.free(function.blocks);
function.blocks = &.{};
gpa.free(function.names[0..function.instructions.len]);
function.debug_locations.deinit(gpa);
function.debug_locations = .{};
gpa.free(function.debug_values);
function.debug_values = &.{};
gpa.free(function.extra);
function.extra = &.{};
function.instructions.shrinkRetainingCapacity(0);
try function.instructions.setCapacity(gpa, final_instructions_len);
errdefer function.instructions.shrinkRetainingCapacity(0);
{
var final_instruction_index: Instruction.Index = @enumFromInt(0);
for (0..params_len) |param_index| {
instructions.items[param_index] = final_instruction_index;
final_instruction_index = @enumFromInt(@intFromEnum(final_instruction_index) + 1);
}
for (blocks, self.blocks.items) |*final_block, current_block| {
assert(current_block.incoming == current_block.branches);
final_block.instruction = final_instruction_index;
final_instruction_index = @enumFromInt(@intFromEnum(final_instruction_index) + 1);
for (current_block.instructions.items) |instruction| {
instructions.items[@intFromEnum(instruction)] = final_instruction_index;
final_instruction_index = @enumFromInt(@intFromEnum(final_instruction_index) + 1);
}
}
}
var wip_name: struct {
next_name: String = @enumFromInt(0),
next_unique_name: std.AutoHashMap(String, String),
builder: *Builder,
fn map(wip_name: *@This(), name: String, sep: []const u8) Allocator.Error!String {
switch (name) {
.none => return .none,
.empty => {
assert(wip_name.next_name != .none);
defer wip_name.next_name = @enumFromInt(@intFromEnum(wip_name.next_name) + 1);
return wip_name.next_name;
},
_ => {
assert(!name.isAnon());
const gop = try wip_name.next_unique_name.getOrPut(name);
if (!gop.found_existing) {
gop.value_ptr.* = @enumFromInt(0);
return name;
}
while (true) {
gop.value_ptr.* = @enumFromInt(@intFromEnum(gop.value_ptr.*) + 1);
const unique_name = try wip_name.builder.fmt("{r}{s}{r}", .{
name.fmt(wip_name.builder),
sep,
gop.value_ptr.fmt(wip_name.builder),
});
const unique_gop = try wip_name.next_unique_name.getOrPut(unique_name);
if (!unique_gop.found_existing) {
unique_gop.value_ptr.* = @enumFromInt(0);
return unique_name;
}
}
},
}
}
} = .{
.next_unique_name = std.AutoHashMap(String, String).init(gpa),
.builder = self.builder,
};
defer wip_name.next_unique_name.deinit();
var value_index: u32 = 0;
for (0..params_len) |param_index| {
const old_argument_index: Instruction.Index = @enumFromInt(param_index);
const new_argument_index: Instruction.Index = @enumFromInt(function.instructions.len);
const argument = self.instructions.get(@intFromEnum(old_argument_index));
assert(argument.tag == .arg);
assert(argument.data == param_index);
value_indices[function.instructions.len] = value_index;
value_index += 1;
function.instructions.appendAssumeCapacity(argument);
names[@intFromEnum(new_argument_index)] = try wip_name.map(
if (self.strip) .empty else self.names.items[@intFromEnum(old_argument_index)],
".",
);
if (self.debug_locations.get(old_argument_index)) |location| {
debug_locations.putAssumeCapacity(new_argument_index, location);
}
if (self.debug_values.getIndex(old_argument_index)) |index| {
debug_values[index] = new_argument_index;
}
}
for (self.blocks.items) |current_block| {
const new_block_index: Instruction.Index = @enumFromInt(function.instructions.len);
value_indices[function.instructions.len] = value_index;
function.instructions.appendAssumeCapacity(.{
.tag = .block,
.data = current_block.incoming,
});
names[@intFromEnum(new_block_index)] = try wip_name.map(current_block.name, "");
for (current_block.instructions.items) |old_instruction_index| {
const new_instruction_index: Instruction.Index = @enumFromInt(function.instructions.len);
var instruction = self.instructions.get(@intFromEnum(old_instruction_index));
switch (instruction.tag) {
.add,
.@"add nsw",
.@"add nuw",
.@"add nuw nsw",
.@"and",
.ashr,
.@"ashr exact",
.fadd,
.@"fadd fast",
.@"fcmp false",
.@"fcmp fast false",
.@"fcmp fast oeq",
.@"fcmp fast oge",
.@"fcmp fast ogt",
.@"fcmp fast ole",
.@"fcmp fast olt",
.@"fcmp fast one",
.@"fcmp fast ord",
.@"fcmp fast true",
.@"fcmp fast ueq",
.@"fcmp fast uge",
.@"fcmp fast ugt",
.@"fcmp fast ule",
.@"fcmp fast ult",
.@"fcmp fast une",
.@"fcmp fast uno",
.@"fcmp oeq",
.@"fcmp oge",
.@"fcmp ogt",
.@"fcmp ole",
.@"fcmp olt",
.@"fcmp one",
.@"fcmp ord",
.@"fcmp true",
.@"fcmp ueq",
.@"fcmp uge",
.@"fcmp ugt",
.@"fcmp ule",
.@"fcmp ult",
.@"fcmp une",
.@"fcmp uno",
.fdiv,
.@"fdiv fast",
.fmul,
.@"fmul fast",
.frem,
.@"frem fast",
.fsub,
.@"fsub fast",
.@"icmp eq",
.@"icmp ne",
.@"icmp sge",
.@"icmp sgt",
.@"icmp sle",
.@"icmp slt",
.@"icmp uge",
.@"icmp ugt",
.@"icmp ule",
.@"icmp ult",
.lshr,
.@"lshr exact",
.mul,
.@"mul nsw",
.@"mul nuw",
.@"mul nuw nsw",
.@"or",
.sdiv,
.@"sdiv exact",
.shl,
.@"shl nsw",
.@"shl nuw",
.@"shl nuw nsw",
.srem,
.sub,
.@"sub nsw",
.@"sub nuw",
.@"sub nuw nsw",
.udiv,
.@"udiv exact",
.urem,
.xor,
=> {
const extra = self.extraData(Instruction.Binary, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.Binary{
.lhs = instructions.map(extra.lhs),
.rhs = instructions.map(extra.rhs),
});
},
.addrspacecast,
.bitcast,
.fpext,
.fptosi,
.fptoui,
.fptrunc,
.inttoptr,
.ptrtoint,
.sext,
.sitofp,
.trunc,
.uitofp,
.zext,
=> {
const extra = self.extraData(Instruction.Cast, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.Cast{
.val = instructions.map(extra.val),
.type = extra.type,
});
},
.alloca,
.@"alloca inalloca",
=> {
const extra = self.extraData(Instruction.Alloca, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.Alloca{
.type = extra.type,
.len = instructions.map(extra.len),
.info = extra.info,
});
},
.arg,
.block,
=> unreachable,
.atomicrmw => {
const extra = self.extraData(Instruction.AtomicRmw, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.AtomicRmw{
.info = extra.info,
.ptr = instructions.map(extra.ptr),
.val = instructions.map(extra.val),
});
},
.br,
.fence,
.@"ret void",
.@"unreachable",
=> {},
.br_cond => {
const extra = self.extraData(Instruction.BrCond, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.BrCond{
.cond = instructions.map(extra.cond),
.then = extra.then,
.@"else" = extra.@"else",
.weights = extra.weights,
});
},
.call,
.@"call fast",
.@"musttail call",
.@"musttail call fast",
.@"notail call",
.@"notail call fast",
.@"tail call",
.@"tail call fast",
=> {
var extra = self.extraDataTrail(Instruction.Call, instruction.data);
const args = extra.trail.next(extra.data.args_len, Value, self);
instruction.data = wip_extra.addExtra(Instruction.Call{
.info = extra.data.info,
.attributes = extra.data.attributes,
.ty = extra.data.ty,
.callee = instructions.map(extra.data.callee),
.args_len = extra.data.args_len,
});
wip_extra.appendMappedValues(args, instructions);
},
.cmpxchg,
.@"cmpxchg weak",
=> {
const extra = self.extraData(Instruction.CmpXchg, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.CmpXchg{
.info = extra.info,
.ptr = instructions.map(extra.ptr),
.cmp = instructions.map(extra.cmp),
.new = instructions.map(extra.new),
});
},
.extractelement => {
const extra = self.extraData(Instruction.ExtractElement, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.ExtractElement{
.val = instructions.map(extra.val),
.index = instructions.map(extra.index),
});
},
.extractvalue => {
var extra = self.extraDataTrail(Instruction.ExtractValue, instruction.data);
const indices = extra.trail.next(extra.data.indices_len, u32, self);
instruction.data = wip_extra.addExtra(Instruction.ExtractValue{
.val = instructions.map(extra.data.val),
.indices_len = extra.data.indices_len,
});
wip_extra.appendSlice(indices);
},
.fneg,
.@"fneg fast",
.ret,
=> instruction.data = @intFromEnum(instructions.map(@enumFromInt(instruction.data))),
.getelementptr,
.@"getelementptr inbounds",
=> {
var extra = self.extraDataTrail(Instruction.GetElementPtr, instruction.data);
const indices = extra.trail.next(extra.data.indices_len, Value, self);
instruction.data = wip_extra.addExtra(Instruction.GetElementPtr{
.type = extra.data.type,
.base = instructions.map(extra.data.base),
.indices_len = extra.data.indices_len,
});
wip_extra.appendMappedValues(indices, instructions);
},
.indirectbr => {
var extra = self.extraDataTrail(Instruction.IndirectBr, instruction.data);
const targets = extra.trail.next(extra.data.targets_len, Block.Index, self);
instruction.data = wip_extra.addExtra(Instruction.IndirectBr{
.addr = instructions.map(extra.data.addr),
.targets_len = extra.data.targets_len,
});
wip_extra.appendSlice(targets);
},
.insertelement => {
const extra = self.extraData(Instruction.InsertElement, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.InsertElement{
.val = instructions.map(extra.val),
.elem = instructions.map(extra.elem),
.index = instructions.map(extra.index),
});
},
.insertvalue => {
var extra = self.extraDataTrail(Instruction.InsertValue, instruction.data);
const indices = extra.trail.next(extra.data.indices_len, u32, self);
instruction.data = wip_extra.addExtra(Instruction.InsertValue{
.val = instructions.map(extra.data.val),
.elem = instructions.map(extra.data.elem),
.indices_len = extra.data.indices_len,
});
wip_extra.appendSlice(indices);
},
.load,
.@"load atomic",
=> {
const extra = self.extraData(Instruction.Load, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.Load{
.type = extra.type,
.ptr = instructions.map(extra.ptr),
.info = extra.info,
});
},
.phi,
.@"phi fast",
=> {
const incoming_len = current_block.incoming;
var extra = self.extraDataTrail(Instruction.Phi, instruction.data);
const incoming_vals = extra.trail.next(incoming_len, Value, self);
const incoming_blocks = extra.trail.next(incoming_len, Block.Index, self);
instruction.data = wip_extra.addExtra(Instruction.Phi{
.type = extra.data.type,
});
wip_extra.appendMappedValues(incoming_vals, instructions);
wip_extra.appendSlice(incoming_blocks);
},
.select,
.@"select fast",
=> {
const extra = self.extraData(Instruction.Select, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.Select{
.cond = instructions.map(extra.cond),
.lhs = instructions.map(extra.lhs),
.rhs = instructions.map(extra.rhs),
});
},
.shufflevector => {
const extra = self.extraData(Instruction.ShuffleVector, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.ShuffleVector{
.lhs = instructions.map(extra.lhs),
.rhs = instructions.map(extra.rhs),
.mask = instructions.map(extra.mask),
});
},
.store,
.@"store atomic",
=> {
const extra = self.extraData(Instruction.Store, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.Store{
.val = instructions.map(extra.val),
.ptr = instructions.map(extra.ptr),
.info = extra.info,
});
},
.@"switch" => {
var extra = self.extraDataTrail(Instruction.Switch, instruction.data);
const case_vals = extra.trail.next(extra.data.cases_len, Constant, self);
const case_blocks = extra.trail.next(extra.data.cases_len, Block.Index, self);
instruction.data = wip_extra.addExtra(Instruction.Switch{
.val = instructions.map(extra.data.val),
.default = extra.data.default,
.cases_len = extra.data.cases_len,
.weights = extra.data.weights,
});
wip_extra.appendSlice(case_vals);
wip_extra.appendSlice(case_blocks);
},
.va_arg => {
const extra = self.extraData(Instruction.VaArg, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.VaArg{
.list = instructions.map(extra.list),
.type = extra.type,
});
},
}
function.instructions.appendAssumeCapacity(instruction);
names[@intFromEnum(new_instruction_index)] = try wip_name.map(if (self.strip)
if (old_instruction_index.hasResultWip(self)) .empty else .none
else
self.names.items[@intFromEnum(old_instruction_index)], ".");
if (self.debug_locations.get(old_instruction_index)) |location| {
debug_locations.putAssumeCapacity(new_instruction_index, location);
}
if (self.debug_values.getIndex(old_instruction_index)) |index| {
debug_values[index] = new_instruction_index;
}
value_indices[@intFromEnum(new_instruction_index)] = value_index;
if (old_instruction_index.hasResultWip(self)) value_index += 1;
}
}
assert(function.instructions.len == final_instructions_len);
function.extra = wip_extra.finish();
function.blocks = blocks;
function.names = names.ptr;
function.value_indices = value_indices.ptr;
function.strip = self.strip;
function.debug_locations = debug_locations;
function.debug_values = debug_values;
}
pub fn deinit(self: *WipFunction) void {
self.extra.deinit(self.builder.gpa);
self.debug_values.deinit(self.builder.gpa);
self.debug_locations.deinit(self.builder.gpa);
self.names.deinit(self.builder.gpa);
self.instructions.deinit(self.builder.gpa);
for (self.blocks.items) |*b| b.instructions.deinit(self.builder.gpa);
self.blocks.deinit(self.builder.gpa);
self.* = undefined;
}
fn cmpTag(
self: *WipFunction,
tag: Instruction.Tag,
lhs: Value,
rhs: Value,
name: []const u8,
) Allocator.Error!Value {
switch (tag) {
.@"fcmp false",
.@"fcmp fast false",
.@"fcmp fast oeq",
.@"fcmp fast oge",
.@"fcmp fast ogt",
.@"fcmp fast ole",
.@"fcmp fast olt",
.@"fcmp fast one",
.@"fcmp fast ord",
.@"fcmp fast true",
.@"fcmp fast ueq",
.@"fcmp fast uge",
.@"fcmp fast ugt",
.@"fcmp fast ule",
.@"fcmp fast ult",
.@"fcmp fast une",
.@"fcmp fast uno",
.@"fcmp oeq",
.@"fcmp oge",
.@"fcmp ogt",
.@"fcmp ole",
.@"fcmp olt",
.@"fcmp one",
.@"fcmp ord",
.@"fcmp true",
.@"fcmp ueq",
.@"fcmp uge",
.@"fcmp ugt",
.@"fcmp ule",
.@"fcmp ult",
.@"fcmp une",
.@"fcmp uno",
.@"icmp eq",
.@"icmp ne",
.@"icmp sge",
.@"icmp sgt",
.@"icmp sle",
.@"icmp slt",
.@"icmp uge",
.@"icmp ugt",
.@"icmp ule",
.@"icmp ult",
=> assert(lhs.typeOfWip(self) == rhs.typeOfWip(self)),
else => unreachable,
}
_ = try lhs.typeOfWip(self).changeScalar(.i1, self.builder);
try self.ensureUnusedExtraCapacity(1, Instruction.Binary, 0);
const instruction = try self.addInst(name, .{
.tag = tag,
.data = self.addExtraAssumeCapacity(Instruction.Binary{
.lhs = lhs,
.rhs = rhs,
}),
});
return instruction.toValue();
}
fn phiTag(
self: *WipFunction,
tag: Instruction.Tag,
ty: Type,
name: []const u8,
) Allocator.Error!WipPhi {
switch (tag) {
.phi, .@"phi fast" => assert(try ty.isSized(self.builder)),
else => unreachable,
}
const incoming = self.cursor.block.ptrConst(self).incoming;
assert(incoming > 0);
try self.ensureUnusedExtraCapacity(1, Instruction.Phi, incoming * 2);
const instruction = try self.addInst(name, .{
.tag = tag,
.data = self.addExtraAssumeCapacity(Instruction.Phi{ .type = ty }),
});
_ = self.extra.addManyAsSliceAssumeCapacity(incoming * 2);
return .{ .block = self.cursor.block, .instruction = instruction };
}
fn selectTag(
self: *WipFunction,
tag: Instruction.Tag,
cond: Value,
lhs: Value,
rhs: Value,
name: []const u8,
) Allocator.Error!Value {
switch (tag) {
.select, .@"select fast" => {
assert(cond.typeOfWip(self).scalarType(self.builder) == .i1);
assert(lhs.typeOfWip(self) == rhs.typeOfWip(self));
},
else => unreachable,
}
try self.ensureUnusedExtraCapacity(1, Instruction.Select, 0);
const instruction = try self.addInst(name, .{
.tag = tag,
.data = self.addExtraAssumeCapacity(Instruction.Select{
.cond = cond,
.lhs = lhs,
.rhs = rhs,
}),
});
return instruction.toValue();
}
fn ensureUnusedExtraCapacity(
self: *WipFunction,
count: usize,
comptime Extra: type,
trail_len: usize,
) Allocator.Error!void {
try self.extra.ensureUnusedCapacity(
self.builder.gpa,
count * (@typeInfo(Extra).@"struct".fields.len + trail_len),
);
}
fn addInst(
self: *WipFunction,
name: ?[]const u8,
instruction: Instruction,
) Allocator.Error!Instruction.Index {
const block_instructions = &self.cursor.block.ptr(self).instructions;
try self.instructions.ensureUnusedCapacity(self.builder.gpa, 1);
if (!self.strip) {
try self.names.ensureUnusedCapacity(self.builder.gpa, 1);
try self.debug_locations.ensureUnusedCapacity(self.builder.gpa, 1);
}
try block_instructions.ensureUnusedCapacity(self.builder.gpa, 1);
const final_name = if (name) |n|
if (self.strip) .empty else try self.builder.string(n)
else
.none;
const index: Instruction.Index = @enumFromInt(self.instructions.len);
self.instructions.appendAssumeCapacity(instruction);
if (!self.strip) {
self.names.appendAssumeCapacity(final_name);
if (block_instructions.items.len == 0 or
!std.meta.eql(self.debug_location, self.prev_debug_location))
{
self.debug_locations.putAssumeCapacity(index, self.debug_location);
self.prev_debug_location = self.debug_location;
}
}
block_instructions.insertAssumeCapacity(self.cursor.instruction, index);
self.cursor.instruction += 1;
return index;
}
fn addExtraAssumeCapacity(self: *WipFunction, extra: anytype) Instruction.ExtraIndex {
const result: Instruction.ExtraIndex = @intCast(self.extra.items.len);
inline for (@typeInfo(@TypeOf(extra)).@"struct".fields) |field| {
const value = @field(extra, field.name);
self.extra.appendAssumeCapacity(switch (field.type) {
u32 => value,
Alignment,
AtomicOrdering,
Block.Index,
FunctionAttributes,
Type,
Value,
Instruction.BrCond.Weights,
=> @intFromEnum(value),
MemoryAccessInfo,
Instruction.Alloca.Info,
Instruction.Call.Info,
=> @bitCast(value),
else => @compileError("bad field type: " ++ field.name ++ ": " ++ @typeName(field.type)),
});
}
return result;
}
const ExtraDataTrail = struct {
index: Instruction.ExtraIndex,
fn nextMut(self: *ExtraDataTrail, len: u32, comptime Item: type, wip: *WipFunction) []Item {
const items: []Item = @ptrCast(wip.extra.items[self.index..][0..len]);
self.index += @intCast(len);
return items;
}
fn next(
self: *ExtraDataTrail,
len: u32,
comptime Item: type,
wip: *const WipFunction,
) []const Item {
const items: []const Item = @ptrCast(wip.extra.items[self.index..][0..len]);
self.index += @intCast(len);
return items;
}
};
fn extraDataTrail(
self: *const WipFunction,
comptime T: type,
index: Instruction.ExtraIndex,
) struct { data: T, trail: ExtraDataTrail } {
var result: T = undefined;
const fields = @typeInfo(T).@"struct".fields;
inline for (fields, self.extra.items[index..][0..fields.len]) |field, value|
@field(result, field.name) = switch (field.type) {
u32 => value,
Alignment,
AtomicOrdering,
Block.Index,
FunctionAttributes,
Type,
Value,
Instruction.BrCond.Weights,
=> @enumFromInt(value),
MemoryAccessInfo,
Instruction.Alloca.Info,
Instruction.Call.Info,
=> @bitCast(value),
else => @compileError("bad field type: " ++ field.name ++ ": " ++ @typeName(field.type)),
};
return .{
.data = result,
.trail = .{ .index = index + @as(Type.Item.ExtraIndex, @intCast(fields.len)) },
};
}
fn extraData(self: *const WipFunction, comptime T: type, index: Instruction.ExtraIndex) T {
return self.extraDataTrail(T, index).data;
}
}