Andrew Kelley d726c2a2d3 self-hosted: beginnings of stack allocation
Comment out non-x86_64 architectures for now in codegen.zig, because
they all have compile errors for their codepaths anyway, and it was
bloating the compilation speed and memory usage when stage1 tried to
build self-hosted. Here's the panic message:

"Backend architectures that don't have good support yet are commented
out, to improve compilation performance. If you are interested in one
of these other backends feel free to uncomment them. Eventually these
will be completed, but stage1 is slow and a memory hog."

This is a workaround to lower the time it takes to build self-hosted
with stage1 as well as use less memory. It should fix the CI.

Additionally:
 * Add `single_mut_pointer` support to `Type`
 * Trivial implementation of stack allocation in codegen.zig. It does
   not deal with freeing yet, and it's missing the stack pointer
   adjustment prologue.
 * Add the `alloc` IR instruction and semantic analysis for `alloc` ZIR
   instruction.
2020-07-28 01:43:04 -07:00

386 lines
10 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 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,
isnonnull,
isnull,
ptrtoint,
ret,
retvoid,
sub,
unreach,
not,
floatcast,
intcast,
pub fn Type(tag: Tag) type {
return switch (tag) {
.alloc,
.retvoid,
.unreach,
.arg,
.breakpoint,
=> NoOp,
.ret,
.bitcast,
.not,
.isnonnull,
.isnull,
.ptrtoint,
.floatcast,
.intcast,
=> UnOp,
.add,
.sub,
.cmp_lt,
.cmp_lte,
.cmp_eq,
.cmp_gte,
.cmp_gt,
.cmp_neq,
=> BinOp,
.assembly => Assembly,
.block => Block,
.br => Br,
.brvoid => BrVoid,
.call => Call,
.condbr => CondBr,
.constant => Constant,
};
}
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())
return Value.initTag(.the_one_possible_value);
const inst = base.cast(Constant) orelse return null;
return inst.val;
}
pub fn cmpOperator(base: *Inst) ?std.math.CompareOperator {
return switch (self.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 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 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 `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 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 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 Body = struct {
instructions: []*Inst,
};