Andrew Kelley 240d0b68f6 make aro-based translate-c lazily built from source
Part of #19063.

Primarily, this moves Aro from deps/ to lib/compiler/ so that it can be
lazily compiled from source. src/aro_translate_c.zig is moved to
lib/compiler/aro_translate_c.zig and some of Zig CLI logic moved to a
main() function there.

aro_translate_c.zig becomes the "common" import for clang-based
translate-c.

Not all of the compiler was able to be detangled from Aro, however, so
it still, for now, remains being compiled with the main compiler
sources due to the clang-based translate-c depending on it. Once
aro-based translate-c achieves feature parity with the clang-based
translate-c implementation, the clang-based one can be removed from Zig.

Aro made it unnecessarily difficult to depend on with these .def files
and all these Zig module requirements. I looked at the .def files and
made these observations:

- The canonical source is llvm .def files.
- Therefore there is an update process to sync with llvm that involves
  regenerating the .def files in Aro.
- Therefore you might as well just regenerate the .zig files directly
  and check those into Aro.
- Also with a small amount of tinkering, the file size on disk of these
  generated .zig files can be made many times smaller, without
  compromising type safety in the usage of the data.

This would make things much easier on Zig as downstream project,
particularly we could remove those pesky stubs when bootstrapping.

I have gone ahead with these changes since they unblock me and I will
have a chat with Vexu to see what he thinks.
2024-02-28 13:21:05 -07:00

697 lines
22 KiB
Zig

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.fmt.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 });
}