zig/src-self-hosted/ir.zig

294 lines
7.3 KiB
Zig

const std = @import("std");
const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const Module = @import("Module.zig");
const assert = std.debug.assert;
const codegen = @import("codegen.zig");
/// These are in-memory, analyzed instructions. See `zir.Inst` for the representation
/// of instructions that correspond to the ZIR text format.
/// This struct owns the `Value` and `Type` memory. When the struct is deallocated,
/// so are the `Value` and `Type`. The value of a constant must be copied into
/// a memory location for the value to survive after a const instruction.
pub const Inst = struct {
tag: Tag,
/// Each bit represents the index of an `Inst` parameter in the `args` field.
/// If a bit is set, it marks the end of the lifetime of the corresponding
/// instruction parameter. For example, 0b000_00101 means that the first and
/// third `Inst` parameters' lifetimes end after this instruction, and will
/// not have any more following references.
/// The most significant bit being set means that the instruction itself is
/// never referenced, in other words its lifetime ends as soon as it finishes.
/// If bit 7 (0b1xxx_xxxx) is set, it means this instruction itself is unreferenced.
/// If bit 6 (0bx1xx_xxxx) is set, it means this is a special case and the
/// lifetimes of operands are encoded elsewhere.
deaths: u8 = undefined,
ty: Type,
/// Byte offset into the source.
src: usize,
pub fn isUnused(self: Inst) bool {
return (self.deaths & 0b1000_0000) != 0;
}
pub fn operandDies(self: Inst, index: u3) bool {
assert(index < 6);
return @truncate(u1, self.deaths << index) != 0;
}
pub fn specialOperandDeaths(self: Inst) bool {
return (self.deaths & 0b1000_0000) != 0;
}
pub const Tag = enum {
add,
arg,
assembly,
bitcast,
block,
br,
breakpoint,
brvoid,
call,
cmp,
condbr,
constant,
isnonnull,
isnull,
ptrtoint,
ret,
retvoid,
sub,
unreach,
/// Returns whether the instruction is one of the control flow "noreturn" types.
/// Function calls do not count. When ZIR is generated, the compiler automatically
/// emits an `Unreach` after a function call with the `noreturn` return type.
pub fn isNoReturn(tag: Tag) bool {
return switch (tag) {
.add,
.arg,
.assembly,
.bitcast,
.block,
.breakpoint,
.call,
.cmp,
.constant,
.isnonnull,
.isnull,
.ptrtoint,
.sub,
=> false,
.br,
.brvoid,
.condbr,
.ret,
.retvoid,
.unreach,
=> true,
};
}
};
pub fn cast(base: *Inst, comptime T: type) ?*T {
if (base.tag != T.base_tag)
return null;
return @fieldParentPtr(T, "base", base);
}
pub fn Args(comptime T: type) type {
return std.meta.fieldInfo(T, "args").field_type;
}
/// Returns `null` if runtime-known.
pub fn value(base: *Inst) ?Value {
if (base.ty.onePossibleValue())
return Value.initTag(.the_one_possible_value);
const inst = base.cast(Constant) orelse return null;
return inst.val;
}
pub const Add = struct {
pub const base_tag = Tag.add;
base: Inst,
args: struct {
lhs: *Inst,
rhs: *Inst,
},
};
pub const Arg = struct {
pub const base_tag = Tag.arg;
base: Inst,
args: struct {
index: usize,
},
};
pub const Assembly = struct {
pub const base_tag = Tag.assembly;
base: Inst,
args: struct {
asm_source: []const u8,
is_volatile: bool,
output: ?[]const u8,
inputs: []const []const u8,
clobbers: []const []const u8,
args: []const *Inst,
},
};
pub const BitCast = struct {
pub const base_tag = Tag.bitcast;
base: Inst,
args: struct {
operand: *Inst,
},
};
pub const Block = struct {
pub const base_tag = Tag.block;
base: Inst,
args: struct {
body: Body,
},
/// This memory is reserved for codegen code to do whatever it needs to here.
codegen: codegen.BlockData = .{},
};
pub const Br = struct {
pub const base_tag = Tag.br;
base: Inst,
args: struct {
block: *Block,
operand: *Inst,
},
};
pub const Breakpoint = struct {
pub const base_tag = Tag.breakpoint;
base: Inst,
args: void,
};
pub const BrVoid = struct {
pub const base_tag = Tag.brvoid;
base: Inst,
args: struct {
block: *Block,
},
};
pub const Call = struct {
pub const base_tag = Tag.call;
base: Inst,
args: struct {
func: *Inst,
args: []const *Inst,
},
};
pub const Cmp = struct {
pub const base_tag = Tag.cmp;
base: Inst,
args: struct {
lhs: *Inst,
op: std.math.CompareOperator,
rhs: *Inst,
},
};
pub const CondBr = struct {
pub const base_tag = Tag.condbr;
base: Inst,
args: struct {
condition: *Inst,
true_body: Body,
false_body: Body,
},
/// Set of instructions whose lifetimes end at the start of one of the branches.
/// The `true` branch is first: `deaths[0..true_death_count]`.
/// The `false` branch is next: `(deaths + true_death_count)[..false_death_count]`.
deaths: [*]*Inst = undefined,
true_death_count: u32 = 0,
false_death_count: u32 = 0,
};
pub const Constant = struct {
pub const base_tag = Tag.constant;
base: Inst,
val: Value,
};
pub const IsNonNull = struct {
pub const base_tag = Tag.isnonnull;
base: Inst,
args: struct {
operand: *Inst,
},
};
pub const IsNull = struct {
pub const base_tag = Tag.isnull;
base: Inst,
args: struct {
operand: *Inst,
},
};
pub const PtrToInt = struct {
pub const base_tag = Tag.ptrtoint;
base: Inst,
args: struct {
ptr: *Inst,
},
};
pub const Ret = struct {
pub const base_tag = Tag.ret;
base: Inst,
args: struct {
operand: *Inst,
},
};
pub const RetVoid = struct {
pub const base_tag = Tag.retvoid;
base: Inst,
args: void,
};
pub const Sub = struct {
pub const base_tag = Tag.sub;
base: Inst,
args: struct {
lhs: *Inst,
rhs: *Inst,
},
};
pub const Unreach = struct {
pub const base_tag = Tag.unreach;
base: Inst,
args: void,
};
};
pub const Body = struct {
instructions: []*Inst,
};