Veikka Tuominen 90ab8ea9e6
Sync Aro sources (#19199)
ref: 02353ad9f17f659e173f68975a442fcec3dd2c94
2024-03-06 14:17:41 -05:00

697 lines
22 KiB
Zig
Vendored

const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const Interner = @import("Interner.zig");
const Object = @import("Object.zig");
const Ir = @This();
interner: *Interner,
decls: std.StringArrayHashMapUnmanaged(Decl),
pub const Decl = struct {
instructions: std.MultiArrayList(Inst),
body: std.ArrayListUnmanaged(Ref),
arena: std.heap.ArenaAllocator.State,
pub fn deinit(decl: *Decl, gpa: Allocator) void {
decl.instructions.deinit(gpa);
decl.body.deinit(gpa);
decl.arena.promote(gpa).deinit();
}
};
pub const Builder = struct {
gpa: Allocator,
arena: std.heap.ArenaAllocator,
interner: *Interner,
decls: std.StringArrayHashMapUnmanaged(Decl) = .{},
instructions: std.MultiArrayList(Ir.Inst) = .{},
body: std.ArrayListUnmanaged(Ref) = .{},
alloc_count: u32 = 0,
arg_count: u32 = 0,
current_label: Ref = undefined,
pub fn deinit(b: *Builder) void {
for (b.decls.values()) |*decl| {
decl.deinit(b.gpa);
}
b.arena.deinit();
b.instructions.deinit(b.gpa);
b.body.deinit(b.gpa);
b.* = undefined;
}
pub fn finish(b: *Builder) Ir {
return .{
.interner = b.interner,
.decls = b.decls.move(),
};
}
pub fn startFn(b: *Builder) Allocator.Error!void {
const entry = try b.makeLabel("entry");
try b.body.append(b.gpa, entry);
b.current_label = entry;
}
pub fn finishFn(b: *Builder, name: []const u8) !void {
var duped_instructions = try b.instructions.clone(b.gpa);
errdefer duped_instructions.deinit(b.gpa);
var duped_body = try b.body.clone(b.gpa);
errdefer duped_body.deinit(b.gpa);
try b.decls.put(b.gpa, name, .{
.instructions = duped_instructions,
.body = duped_body,
.arena = b.arena.state,
});
b.instructions.shrinkRetainingCapacity(0);
b.body.shrinkRetainingCapacity(0);
b.arena = std.heap.ArenaAllocator.init(b.gpa);
b.alloc_count = 0;
b.arg_count = 0;
}
pub fn startBlock(b: *Builder, label: Ref) !void {
try b.body.append(b.gpa, label);
b.current_label = label;
}
pub fn addArg(b: *Builder, ty: Interner.Ref) Allocator.Error!Ref {
const ref: Ref = @enumFromInt(b.instructions.len);
try b.instructions.append(b.gpa, .{ .tag = .arg, .data = .{ .none = {} }, .ty = ty });
try b.body.insert(b.gpa, b.arg_count, ref);
b.arg_count += 1;
return ref;
}
pub fn addAlloc(b: *Builder, size: u32, @"align": u32) Allocator.Error!Ref {
const ref: Ref = @enumFromInt(b.instructions.len);
try b.instructions.append(b.gpa, .{
.tag = .alloc,
.data = .{ .alloc = .{ .size = size, .@"align" = @"align" } },
.ty = .ptr,
});
try b.body.insert(b.gpa, b.alloc_count + b.arg_count + 1, ref);
b.alloc_count += 1;
return ref;
}
pub fn addInst(b: *Builder, tag: Ir.Inst.Tag, data: Ir.Inst.Data, ty: Interner.Ref) Allocator.Error!Ref {
const ref: Ref = @enumFromInt(b.instructions.len);
try b.instructions.append(b.gpa, .{ .tag = tag, .data = data, .ty = ty });
try b.body.append(b.gpa, ref);
return ref;
}
pub fn makeLabel(b: *Builder, name: [*:0]const u8) Allocator.Error!Ref {
const ref: Ref = @enumFromInt(b.instructions.len);
try b.instructions.append(b.gpa, .{ .tag = .label, .data = .{ .label = name }, .ty = .void });
return ref;
}
pub fn addJump(b: *Builder, label: Ref) Allocator.Error!void {
_ = try b.addInst(.jmp, .{ .un = label }, .noreturn);
}
pub fn addBranch(b: *Builder, cond: Ref, true_label: Ref, false_label: Ref) Allocator.Error!void {
const branch = try b.arena.allocator().create(Ir.Inst.Branch);
branch.* = .{
.cond = cond,
.then = true_label,
.@"else" = false_label,
};
_ = try b.addInst(.branch, .{ .branch = branch }, .noreturn);
}
pub fn addSwitch(b: *Builder, target: Ref, values: []Interner.Ref, labels: []Ref, default: Ref) Allocator.Error!void {
assert(values.len == labels.len);
const a = b.arena.allocator();
const @"switch" = try a.create(Ir.Inst.Switch);
@"switch".* = .{
.target = target,
.cases_len = @intCast(values.len),
.case_vals = (try a.dupe(Interner.Ref, values)).ptr,
.case_labels = (try a.dupe(Ref, labels)).ptr,
.default = default,
};
_ = try b.addInst(.@"switch", .{ .@"switch" = @"switch" }, .noreturn);
}
pub fn addStore(b: *Builder, ptr: Ref, val: Ref) Allocator.Error!void {
_ = try b.addInst(.store, .{ .bin = .{ .lhs = ptr, .rhs = val } }, .void);
}
pub fn addConstant(b: *Builder, val: Interner.Ref, ty: Interner.Ref) Allocator.Error!Ref {
const ref: Ref = @enumFromInt(b.instructions.len);
try b.instructions.append(b.gpa, .{
.tag = .constant,
.data = .{ .constant = val },
.ty = ty,
});
return ref;
}
pub fn addPhi(b: *Builder, inputs: []const Inst.Phi.Input, ty: Interner.Ref) Allocator.Error!Ref {
const a = b.arena.allocator();
const input_refs = try a.alloc(Ref, inputs.len * 2 + 1);
input_refs[0] = @enumFromInt(inputs.len);
@memcpy(input_refs[1..], std.mem.bytesAsSlice(Ref, std.mem.sliceAsBytes(inputs)));
return b.addInst(.phi, .{ .phi = .{ .ptr = input_refs.ptr } }, ty);
}
pub fn addSelect(b: *Builder, cond: Ref, then: Ref, @"else": Ref, ty: Interner.Ref) Allocator.Error!Ref {
const branch = try b.arena.allocator().create(Ir.Inst.Branch);
branch.* = .{
.cond = cond,
.then = then,
.@"else" = @"else",
};
return b.addInst(.select, .{ .branch = branch }, ty);
}
};
pub const Renderer = struct {
gpa: Allocator,
obj: *Object,
ir: *const Ir,
errors: ErrorList = .{},
pub const ErrorList = std.StringArrayHashMapUnmanaged([]const u8);
pub const Error = Allocator.Error || error{LowerFail};
pub fn deinit(r: *Renderer) void {
for (r.errors.values()) |msg| r.gpa.free(msg);
r.errors.deinit(r.gpa);
}
pub fn render(r: *Renderer) !void {
switch (r.obj.target.cpu.arch) {
.x86, .x86_64 => return @import("Ir/x86/Renderer.zig").render(r),
else => unreachable,
}
}
pub fn fail(
r: *Renderer,
name: []const u8,
comptime format: []const u8,
args: anytype,
) Error {
try r.errors.ensureUnusedCapacity(r.gpa, 1);
r.errors.putAssumeCapacity(name, try std.fmt.allocPrint(r.gpa, format, args));
return error.LowerFail;
}
};
pub fn render(
ir: *const Ir,
gpa: Allocator,
target: std.Target,
errors: ?*Renderer.ErrorList,
) !*Object {
const obj = try Object.create(gpa, target);
errdefer obj.deinit();
var renderer: Renderer = .{
.gpa = gpa,
.obj = obj,
.ir = ir,
};
defer {
if (errors) |some| {
some.* = renderer.errors.move();
}
renderer.deinit();
}
try renderer.render();
return obj;
}
pub const Ref = enum(u32) { none = std.math.maxInt(u32), _ };
pub const Inst = struct {
tag: Tag,
data: Data,
ty: Interner.Ref,
pub const Tag = enum {
// data.constant
// not included in blocks
constant,
// data.arg
// not included in blocks
arg,
symbol,
// data.label
label,
// data.block
label_addr,
jmp,
// data.switch
@"switch",
// data.branch
branch,
select,
// data.un
jmp_val,
// data.call
call,
// data.alloc
alloc,
// data.phi
phi,
// data.bin
store,
bit_or,
bit_xor,
bit_and,
bit_shl,
bit_shr,
cmp_eq,
cmp_ne,
cmp_lt,
cmp_lte,
cmp_gt,
cmp_gte,
add,
sub,
mul,
div,
mod,
// data.un
ret,
load,
bit_not,
negate,
trunc,
zext,
sext,
};
pub const Data = union {
constant: Interner.Ref,
none: void,
bin: struct {
lhs: Ref,
rhs: Ref,
},
un: Ref,
arg: u32,
alloc: struct {
size: u32,
@"align": u32,
},
@"switch": *Switch,
call: *Call,
label: [*:0]const u8,
branch: *Branch,
phi: Phi,
};
pub const Branch = struct {
cond: Ref,
then: Ref,
@"else": Ref,
};
pub const Switch = struct {
target: Ref,
cases_len: u32,
default: Ref,
case_vals: [*]Interner.Ref,
case_labels: [*]Ref,
};
pub const Call = struct {
func: Ref,
args_len: u32,
args_ptr: [*]Ref,
pub fn args(c: Call) []Ref {
return c.args_ptr[0..c.args_len];
}
};
pub const Phi = struct {
ptr: [*]Ir.Ref,
pub const Input = struct {
label: Ir.Ref,
value: Ir.Ref,
};
pub fn inputs(p: Phi) []Input {
const len = @intFromEnum(p.ptr[0]) * 2;
const slice = (p.ptr + 1)[0..len];
return std.mem.bytesAsSlice(Input, std.mem.sliceAsBytes(slice));
}
};
};
pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void {
for (ir.decls.values()) |*decl| {
decl.deinit(gpa);
}
ir.decls.deinit(gpa);
ir.* = undefined;
}
const TYPE = std.io.tty.Color.bright_magenta;
const INST = std.io.tty.Color.bright_cyan;
const REF = std.io.tty.Color.bright_blue;
const LITERAL = std.io.tty.Color.bright_green;
const ATTRIBUTE = std.io.tty.Color.bright_yellow;
const RefMap = std.AutoArrayHashMap(Ref, void);
pub fn dump(ir: *const Ir, gpa: Allocator, config: std.io.tty.Config, w: anytype) !void {
for (ir.decls.keys(), ir.decls.values()) |name, *decl| {
try ir.dumpDecl(decl, gpa, name, config, w);
}
}
fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8, config: std.io.tty.Config, w: anytype) !void {
const tags = decl.instructions.items(.tag);
const data = decl.instructions.items(.data);
var ref_map = RefMap.init(gpa);
defer ref_map.deinit();
var label_map = RefMap.init(gpa);
defer label_map.deinit();
const ret_inst = decl.body.items[decl.body.items.len - 1];
const ret_operand = data[@intFromEnum(ret_inst)].un;
const ret_ty = decl.instructions.items(.ty)[@intFromEnum(ret_operand)];
try ir.writeType(ret_ty, config, w);
try config.setColor(w, REF);
try w.print(" @{s}", .{name});
try config.setColor(w, .reset);
try w.writeAll("(");
var arg_count: u32 = 0;
while (true) : (arg_count += 1) {
const ref = decl.body.items[arg_count];
if (tags[@intFromEnum(ref)] != .arg) break;
if (arg_count != 0) try w.writeAll(", ");
try ref_map.put(ref, {});
try ir.writeRef(decl, &ref_map, ref, config, w);
try config.setColor(w, .reset);
}
try w.writeAll(") {\n");
for (decl.body.items[arg_count..]) |ref| {
switch (tags[@intFromEnum(ref)]) {
.label => try label_map.put(ref, {}),
else => {},
}
}
for (decl.body.items[arg_count..]) |ref| {
const i = @intFromEnum(ref);
const tag = tags[i];
switch (tag) {
.arg, .constant, .symbol => unreachable,
.label => {
const label_index = label_map.getIndex(ref).?;
try config.setColor(w, REF);
try w.print("{s}.{d}:\n", .{ data[i].label, label_index });
},
// .label_val => {
// const un = data[i].un;
// try w.print(" %{d} = label.{d}\n", .{ i, @intFromEnum(un) });
// },
.jmp => {
const un = data[i].un;
try config.setColor(w, INST);
try w.writeAll(" jmp ");
try writeLabel(decl, &label_map, un, config, w);
try w.writeByte('\n');
},
.branch => {
const br = data[i].branch;
try config.setColor(w, INST);
try w.writeAll(" branch ");
try ir.writeRef(decl, &ref_map, br.cond, config, w);
try config.setColor(w, .reset);
try w.writeAll(", ");
try writeLabel(decl, &label_map, br.then, config, w);
try config.setColor(w, .reset);
try w.writeAll(", ");
try writeLabel(decl, &label_map, br.@"else", config, w);
try w.writeByte('\n');
},
.select => {
const br = data[i].branch;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try w.writeAll("select ");
try ir.writeRef(decl, &ref_map, br.cond, config, w);
try config.setColor(w, .reset);
try w.writeAll(", ");
try ir.writeRef(decl, &ref_map, br.then, config, w);
try config.setColor(w, .reset);
try w.writeAll(", ");
try ir.writeRef(decl, &ref_map, br.@"else", config, w);
try w.writeByte('\n');
},
// .jmp_val => {
// const bin = data[i].bin;
// try w.print(" %{s} %{d} label.{d}\n", .{ @tagName(tag), @intFromEnum(bin.lhs), @intFromEnum(bin.rhs) });
// },
.@"switch" => {
const @"switch" = data[i].@"switch";
try config.setColor(w, INST);
try w.writeAll(" switch ");
try ir.writeRef(decl, &ref_map, @"switch".target, config, w);
try config.setColor(w, .reset);
try w.writeAll(" {");
for (@"switch".case_vals[0..@"switch".cases_len], @"switch".case_labels) |val_ref, label_ref| {
try w.writeAll("\n ");
try ir.writeValue(val_ref, config, w);
try config.setColor(w, .reset);
try w.writeAll(" => ");
try writeLabel(decl, &label_map, label_ref, config, w);
try config.setColor(w, .reset);
}
try config.setColor(w, LITERAL);
try w.writeAll("\n default ");
try config.setColor(w, .reset);
try w.writeAll("=> ");
try writeLabel(decl, &label_map, @"switch".default, config, w);
try config.setColor(w, .reset);
try w.writeAll("\n }\n");
},
.call => {
const call = data[i].call;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try w.writeAll("call ");
try ir.writeRef(decl, &ref_map, call.func, config, w);
try config.setColor(w, .reset);
try w.writeAll("(");
for (call.args(), 0..) |arg, arg_i| {
if (arg_i != 0) try w.writeAll(", ");
try ir.writeRef(decl, &ref_map, arg, config, w);
try config.setColor(w, .reset);
}
try w.writeAll(")\n");
},
.alloc => {
const alloc = data[i].alloc;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try w.writeAll("alloc ");
try config.setColor(w, ATTRIBUTE);
try w.writeAll("size ");
try config.setColor(w, LITERAL);
try w.print("{d}", .{alloc.size});
try config.setColor(w, ATTRIBUTE);
try w.writeAll(" align ");
try config.setColor(w, LITERAL);
try w.print("{d}", .{alloc.@"align"});
try w.writeByte('\n');
},
.phi => {
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try w.writeAll("phi");
try config.setColor(w, .reset);
try w.writeAll(" {");
for (data[i].phi.inputs()) |input| {
try w.writeAll("\n ");
try writeLabel(decl, &label_map, input.label, config, w);
try config.setColor(w, .reset);
try w.writeAll(" => ");
try ir.writeRef(decl, &ref_map, input.value, config, w);
try config.setColor(w, .reset);
}
try config.setColor(w, .reset);
try w.writeAll("\n }\n");
},
.store => {
const bin = data[i].bin;
try config.setColor(w, INST);
try w.writeAll(" store ");
try ir.writeRef(decl, &ref_map, bin.lhs, config, w);
try config.setColor(w, .reset);
try w.writeAll(", ");
try ir.writeRef(decl, &ref_map, bin.rhs, config, w);
try w.writeByte('\n');
},
.ret => {
try config.setColor(w, INST);
try w.writeAll(" ret ");
if (data[i].un != .none) try ir.writeRef(decl, &ref_map, data[i].un, config, w);
try w.writeByte('\n');
},
.load => {
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try w.writeAll("load ");
try ir.writeRef(decl, &ref_map, data[i].un, config, w);
try w.writeByte('\n');
},
.bit_or,
.bit_xor,
.bit_and,
.bit_shl,
.bit_shr,
.cmp_eq,
.cmp_ne,
.cmp_lt,
.cmp_lte,
.cmp_gt,
.cmp_gte,
.add,
.sub,
.mul,
.div,
.mod,
=> {
const bin = data[i].bin;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try w.print("{s} ", .{@tagName(tag)});
try ir.writeRef(decl, &ref_map, bin.lhs, config, w);
try config.setColor(w, .reset);
try w.writeAll(", ");
try ir.writeRef(decl, &ref_map, bin.rhs, config, w);
try w.writeByte('\n');
},
.bit_not,
.negate,
.trunc,
.zext,
.sext,
=> {
const un = data[i].un;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try w.print("{s} ", .{@tagName(tag)});
try ir.writeRef(decl, &ref_map, un, config, w);
try w.writeByte('\n');
},
.label_addr, .jmp_val => {},
}
}
try config.setColor(w, .reset);
try w.writeAll("}\n\n");
}
fn writeType(ir: Ir, ty_ref: Interner.Ref, config: std.io.tty.Config, w: anytype) !void {
const ty = ir.interner.get(ty_ref);
try config.setColor(w, TYPE);
switch (ty) {
.ptr_ty, .noreturn_ty, .void_ty, .func_ty => try w.writeAll(@tagName(ty)),
.int_ty => |bits| try w.print("i{d}", .{bits}),
.float_ty => |bits| try w.print("f{d}", .{bits}),
.array_ty => |info| {
try w.print("[{d} * ", .{info.len});
try ir.writeType(info.child, .no_color, w);
try w.writeByte(']');
},
.vector_ty => |info| {
try w.print("<{d} * ", .{info.len});
try ir.writeType(info.child, .no_color, w);
try w.writeByte('>');
},
.record_ty => |elems| {
// TODO collect into buffer and only print once
try w.writeAll("{ ");
for (elems, 0..) |elem, i| {
if (i != 0) try w.writeAll(", ");
try ir.writeType(elem, config, w);
}
try w.writeAll(" }");
},
else => unreachable, // not a type
}
}
fn writeValue(ir: Ir, val: Interner.Ref, config: std.io.tty.Config, w: anytype) !void {
try config.setColor(w, LITERAL);
const key = ir.interner.get(val);
switch (key) {
.null => return w.writeAll("nullptr_t"),
.int => |repr| switch (repr) {
inline else => |x| return w.print("{d}", .{x}),
},
.float => |repr| switch (repr) {
inline else => |x| return w.print("{d}", .{@as(f64, @floatCast(x))}),
},
.bytes => |b| return std.zig.stringEscape(b, "", .{}, w),
else => unreachable, // not a value
}
}
fn writeRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: anytype) !void {
assert(ref != .none);
const index = @intFromEnum(ref);
const ty_ref = decl.instructions.items(.ty)[index];
if (decl.instructions.items(.tag)[index] == .constant) {
try ir.writeType(ty_ref, config, w);
const v_ref = decl.instructions.items(.data)[index].constant;
try w.writeByte(' ');
try ir.writeValue(v_ref, config, w);
return;
} else if (decl.instructions.items(.tag)[index] == .symbol) {
const name = decl.instructions.items(.data)[index].label;
try ir.writeType(ty_ref, config, w);
try config.setColor(w, REF);
try w.print(" @{s}", .{name});
return;
}
try ir.writeType(ty_ref, config, w);
try config.setColor(w, REF);
const ref_index = ref_map.getIndex(ref).?;
try w.print(" %{d}", .{ref_index});
}
fn writeNewRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: anytype) !void {
try ref_map.put(ref, {});
try w.writeAll(" ");
try ir.writeRef(decl, ref_map, ref, config, w);
try config.setColor(w, .reset);
try w.writeAll(" = ");
try config.setColor(w, INST);
}
fn writeLabel(decl: *const Decl, label_map: *RefMap, ref: Ref, config: std.io.tty.Config, w: anytype) !void {
assert(ref != .none);
const index = @intFromEnum(ref);
const label = decl.instructions.items(.data)[index].label;
try config.setColor(w, REF);
const label_index = label_map.getIndex(ref).?;
try w.print("{s}.{d}", .{ label, label_index });
}