Andrew Kelley 717b0e8275 stage2: introduce the ability for Scope.Block to be comptime
This gives zir_sema analysis the ability to check if the current scope
is expected to be comptime.
2020-08-31 23:34:58 -07:00

466 lines
12 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");
const ast = std.zig.ast;
/// 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, 0b101 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 15 (0b1xxx_xxxx_xxxx_xxxx) is set, it means this instruction itself is unreferenced.
/// If bit 14 (0bx1xx_xxxx_xxxx_xxxx) is set, it means this is a special case and the
/// lifetimes of operands are encoded elsewhere.
deaths: DeathsInt = undefined,
ty: Type,
/// Byte offset into the source.
src: usize,
pub const DeathsInt = u16;
pub const DeathsBitIndex = std.math.Log2Int(DeathsInt);
pub const unreferenced_bit_index = @typeInfo(DeathsInt).Int.bits - 1;
pub const deaths_bits = unreferenced_bit_index - 1;
pub fn isUnused(self: Inst) bool {
return (self.deaths & (1 << unreferenced_bit_index)) != 0;
}
pub fn operandDies(self: Inst, index: DeathsBitIndex) bool {
assert(index < deaths_bits);
return @truncate(u1, self.deaths >> index) != 0;
}
pub fn clearOperandDeath(self: *Inst, index: DeathsBitIndex) void {
assert(index < deaths_bits);
self.deaths &= ~(@as(DeathsInt, 1) << index);
}
pub fn specialOperandDeaths(self: Inst) bool {
return (self.deaths & (1 << deaths_bits)) != 0;
}
pub const Tag = enum {
add,
alloc,
arg,
assembly,
bitcast,
block,
br,
breakpoint,
brvoid,
call,
cmp_lt,
cmp_lte,
cmp_eq,
cmp_gte,
cmp_gt,
cmp_neq,
condbr,
constant,
dbg_stmt,
isnonnull,
isnull,
iserr,
/// Read a value from a pointer.
load,
loop,
ptrtoint,
ref,
ret,
retvoid,
varptr,
/// Write a value to a pointer. LHS is pointer, RHS is value.
store,
sub,
unreach,
not,
floatcast,
intcast,
unwrap_optional,
wrap_optional,
pub fn Type(tag: Tag) type {
return switch (tag) {
.alloc,
.retvoid,
.unreach,
.breakpoint,
.dbg_stmt,
=> NoOp,
.ref,
.ret,
.bitcast,
.not,
.isnonnull,
.isnull,
.iserr,
.ptrtoint,
.floatcast,
.intcast,
.load,
.unwrap_optional,
.wrap_optional,
=> UnOp,
.add,
.sub,
.cmp_lt,
.cmp_lte,
.cmp_eq,
.cmp_gte,
.cmp_gt,
.cmp_neq,
.store,
=> BinOp,
.arg => Arg,
.assembly => Assembly,
.block => Block,
.br => Br,
.brvoid => BrVoid,
.call => Call,
.condbr => CondBr,
.constant => Constant,
.loop => Loop,
.varptr => VarPtr,
};
}
pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
return switch (op) {
.lt => .cmp_lt,
.lte => .cmp_lte,
.eq => .cmp_eq,
.gte => .cmp_gte,
.gt => .cmp_gt,
.neq => .cmp_neq,
};
}
};
/// Prefer `castTag` to this.
pub fn cast(base: *Inst, comptime T: type) ?*T {
if (@hasField(T, "base_tag")) {
return base.castTag(T.base_tag);
}
inline for (@typeInfo(Tag).Enum.fields) |field| {
const tag = @intToEnum(Tag, field.value);
if (base.tag == tag) {
if (T == tag.Type()) {
return @fieldParentPtr(T, "base", base);
}
return null;
}
}
unreachable;
}
pub fn castTag(base: *Inst, comptime tag: Tag) ?*tag.Type() {
if (base.tag == tag) {
return @fieldParentPtr(tag.Type(), "base", base);
}
return null;
}
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()) |opv| return opv;
const inst = base.cast(Constant) orelse return null;
return inst.val;
}
pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator {
return switch (base.tag) {
.cmp_lt => .lt,
.cmp_lte => .lte,
.cmp_eq => .eq,
.cmp_gte => .gte,
.cmp_gt => .gt,
.cmp_neq => .neq,
else => null,
};
}
pub fn operandCount(base: *Inst) usize {
inline for (@typeInfo(Tag).Enum.fields) |field| {
const tag = @intToEnum(Tag, field.value);
if (tag == base.tag) {
return @fieldParentPtr(tag.Type(), "base", base).operandCount();
}
}
unreachable;
}
pub fn getOperand(base: *Inst, index: usize) ?*Inst {
inline for (@typeInfo(Tag).Enum.fields) |field| {
const tag = @intToEnum(Tag, field.value);
if (tag == base.tag) {
return @fieldParentPtr(tag.Type(), "base", base).getOperand(index);
}
}
unreachable;
}
pub fn breakBlock(base: *Inst) ?*Block {
return switch (base.tag) {
.br => base.castTag(.br).?.block,
.brvoid => base.castTag(.brvoid).?.block,
else => null,
};
}
pub const NoOp = struct {
base: Inst,
pub fn operandCount(self: *const NoOp) usize {
return 0;
}
pub fn getOperand(self: *const NoOp, index: usize) ?*Inst {
return null;
}
};
pub const UnOp = struct {
base: Inst,
operand: *Inst,
pub fn operandCount(self: *const UnOp) usize {
return 1;
}
pub fn getOperand(self: *const UnOp, index: usize) ?*Inst {
if (index == 0)
return self.operand;
return null;
}
};
pub const BinOp = struct {
base: Inst,
lhs: *Inst,
rhs: *Inst,
pub fn operandCount(self: *const BinOp) usize {
return 2;
}
pub fn getOperand(self: *const BinOp, index: usize) ?*Inst {
var i = index;
if (i < 1)
return self.lhs;
i -= 1;
if (i < 1)
return self.rhs;
i -= 1;
return null;
}
};
pub const Arg = struct {
pub const base_tag = Tag.arg;
base: Inst,
name: [*:0]const u8,
pub fn operandCount(self: *const Arg) usize {
return 0;
}
pub fn getOperand(self: *const Arg, index: usize) ?*Inst {
return null;
}
};
pub const Assembly = struct {
pub const base_tag = Tag.assembly;
base: Inst,
asm_source: []const u8,
is_volatile: bool,
output: ?[]const u8,
inputs: []const []const u8,
clobbers: []const []const u8,
args: []const *Inst,
pub fn operandCount(self: *const Assembly) usize {
return self.args.len;
}
pub fn getOperand(self: *const Assembly, index: usize) ?*Inst {
if (index < self.args.len)
return self.args[index];
return null;
}
};
pub const Block = struct {
pub const base_tag = Tag.block;
base: Inst,
body: Body,
/// This memory is reserved for codegen code to do whatever it needs to here.
codegen: codegen.BlockData = .{},
pub fn operandCount(self: *const Block) usize {
return 0;
}
pub fn getOperand(self: *const Block, index: usize) ?*Inst {
return null;
}
};
pub const Br = struct {
pub const base_tag = Tag.br;
base: Inst,
block: *Block,
operand: *Inst,
pub fn operandCount(self: *const Br) usize {
return 0;
}
pub fn getOperand(self: *const Br, index: usize) ?*Inst {
if (index == 0)
return self.operand;
return null;
}
};
pub const BrVoid = struct {
pub const base_tag = Tag.brvoid;
base: Inst,
block: *Block,
pub fn operandCount(self: *const BrVoid) usize {
return 0;
}
pub fn getOperand(self: *const BrVoid, index: usize) ?*Inst {
return null;
}
};
pub const Call = struct {
pub const base_tag = Tag.call;
base: Inst,
func: *Inst,
args: []const *Inst,
pub fn operandCount(self: *const Call) usize {
return self.args.len + 1;
}
pub fn getOperand(self: *const Call, index: usize) ?*Inst {
var i = index;
if (i < 1)
return self.func;
i -= 1;
if (i < self.args.len)
return self.args[i];
i -= self.args.len;
return null;
}
};
pub const CondBr = struct {
pub const base_tag = Tag.condbr;
base: Inst,
condition: *Inst,
then_body: Body,
else_body: Body,
/// Set of instructions whose lifetimes end at the start of one of the branches.
/// The `then` branch is first: `deaths[0..then_death_count]`.
/// The `else` branch is next: `(deaths + then_death_count)[0..else_death_count]`.
deaths: [*]*Inst = undefined,
then_death_count: u32 = 0,
else_death_count: u32 = 0,
pub fn operandCount(self: *const CondBr) usize {
return 1;
}
pub fn getOperand(self: *const CondBr, index: usize) ?*Inst {
var i = index;
if (i < 1)
return self.condition;
i -= 1;
return null;
}
pub fn thenDeaths(self: *const CondBr) []*Inst {
return self.deaths[0..self.then_death_count];
}
pub fn elseDeaths(self: *const CondBr) []*Inst {
return (self.deaths + self.then_death_count)[0..self.else_death_count];
}
};
pub const Constant = struct {
pub const base_tag = Tag.constant;
base: Inst,
val: Value,
pub fn operandCount(self: *const Constant) usize {
return 0;
}
pub fn getOperand(self: *const Constant, index: usize) ?*Inst {
return null;
}
};
pub const Loop = struct {
pub const base_tag = Tag.loop;
base: Inst,
body: Body,
pub fn operandCount(self: *const Loop) usize {
return 0;
}
pub fn getOperand(self: *const Loop, index: usize) ?*Inst {
return null;
}
};
pub const VarPtr = struct {
pub const base_tag = Tag.varptr;
base: Inst,
variable: *Module.Var,
pub fn operandCount(self: *const VarPtr) usize {
return 0;
}
pub fn getOperand(self: *const VarPtr, index: usize) ?*Inst {
return null;
}
};
};
pub const Body = struct {
instructions: []*Inst,
};