Skip to content

Commit

Permalink
backend: basic x86_64 assembly codegen for some global vars
Browse files Browse the repository at this point in the history
  • Loading branch information
ehaas committed Nov 9, 2024
1 parent 337cb2e commit 22f2ad6
Show file tree
Hide file tree
Showing 13 changed files with 515 additions and 60 deletions.
10 changes: 10 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,15 @@ pub fn build(b: *Build) !void {
GenerateDef.create(b, .{ .name = "Diagnostics/messages.def", .kind = .named }),
},
});
const assembly_backend = b.addModule("assembly_backend", .{
.root_source_file = b.path("src/assembly_backend.zig"),
.imports = &.{
.{
.name = "aro",
.module = aro_module,
},
},
});

b.installDirectory(.{
.source_dir = b.path("include"),
Expand All @@ -173,6 +182,7 @@ pub fn build(b: *Build) !void {
.use_lld = use_llvm,
});
exe.root_module.addImport("aro", aro_module);
exe.root_module.addImport("assembly_backend", assembly_backend);

// tracy integration
if (tracy) |tracy_path| {
Expand Down
1 change: 1 addition & 0 deletions src/aro.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub const Interner = backend.Interner;
pub const Ir = backend.Ir;
pub const Object = backend.Object;
pub const CallingConvention = backend.CallingConvention;
pub const Assembly = backend.Assembly;

pub const version_str = backend.version_str;
pub const version = backend.version;
Expand Down
5 changes: 5 additions & 0 deletions src/aro/Diagnostics/messages.def
Original file line number Diff line number Diff line change
Expand Up @@ -1477,6 +1477,11 @@ cli_invalid_emulate
.extra = .str
.kind = .@"error"

cli_invalid_optimization
.msg = "invalid optimization level '{s}'"
.extra = .str
.kind = .@"error"

cli_unknown_arg
.msg = "unknown argument '{s}'"
.extra = .str
Expand Down
197 changes: 139 additions & 58 deletions src/aro/Driver.zig
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ pub const usage =
\\ Allow '$' in identifiers
\\ -fno-dollars-in-identifiers
\\ Disallow '$' in identifiers
\\ -g Generate debug information
\\ -fmacro-backtrace-limit=<limit>
\\ Set limit on how many macro expansion traces are shown in errors (default 6)
\\ -fnative-half-type Use the native half type for __fp16 instead of promoting to float
Expand Down Expand Up @@ -283,6 +284,11 @@ pub fn parseArgs(
macro = args[i];
}
try macro_buf.print("#undef {s}\n", .{macro});
} else if (mem.startsWith(u8, arg, "-O")) {
d.comp.code_gen_options.optimization_level = backend.CodeGenOptions.OptimizationLevel.fromString(arg["-O".len..]) orelse {
try d.comp.addDiagnostic(.{ .tag = .cli_invalid_optimization, .extra = .{ .str = arg } }, &.{});
continue;
};
} else if (mem.eql(u8, arg, "-undef")) {
d.system_defines = .no_system_defines;
} else if (mem.eql(u8, arg, "-c") or mem.eql(u8, arg, "--compile")) {
Expand Down Expand Up @@ -330,6 +336,10 @@ pub fn parseArgs(
d.comp.langopts.dollars_in_identifiers = true;
} else if (mem.eql(u8, arg, "-fno-dollars-in-identifiers")) {
d.comp.langopts.dollars_in_identifiers = false;
} else if (mem.eql(u8, arg, "-g")) {
d.comp.code_gen_options.debug = true;
} else if (mem.eql(u8, arg, "-g0")) {
d.comp.code_gen_options.debug = false;
} else if (mem.eql(u8, arg, "-fdigraphs")) {
d.comp.langopts.digraphs = true;
} else if (mem.eql(u8, arg, "-fgnu-inline-asm")) {
Expand Down Expand Up @@ -665,7 +675,7 @@ pub fn errorDescription(e: anyerror) []const u8 {

/// The entry point of the Aro compiler.
/// **MAY call `exit` if `fast_exit` is set.**
pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_exit: bool) !void {
pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_exit: bool, asm_gen_fn: anytype) !void {
var macro_buf = std.ArrayList(u8).init(d.comp.gpa);
defer macro_buf.deinit();

Expand Down Expand Up @@ -694,7 +704,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_

if (fast_exit and d.inputs.items.len == 1) {
const builtin = try d.comp.generateBuiltinMacrosFromPath(d.system_defines, d.inputs.items[0].path);
d.processSource(tc, d.inputs.items[0], builtin, user_macros, fast_exit) catch |e| switch (e) {
d.processSource(tc, d.inputs.items[0], builtin, user_macros, fast_exit, asm_gen_fn) catch |e| switch (e) {
error.FatalError => {
d.renderErrors();
d.exitWithCleanup(1);
Expand All @@ -706,7 +716,7 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_

for (d.inputs.items) |source| {
const builtin = try d.comp.generateBuiltinMacrosFromPath(d.system_defines, source.path);
d.processSource(tc, source, builtin, user_macros, fast_exit) catch |e| switch (e) {
d.processSource(tc, source, builtin, user_macros, fast_exit, asm_gen_fn) catch |e| switch (e) {
error.FatalError => {
d.renderErrors();
},
Expand All @@ -723,13 +733,73 @@ pub fn main(d: *Driver, tc: *Toolchain, args: []const []const u8, comptime fast_
if (fast_exit) std.process.exit(0);
}

fn getRandomFilename(d: *Driver, buf: *[std.fs.max_name_bytes]u8, extension: []const u8) ![]const u8 {
const random_bytes_count = 12;
const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);

var random_bytes: [random_bytes_count]u8 = undefined;
std.crypto.random.bytes(&random_bytes);
var random_name: [sub_path_len]u8 = undefined;
_ = std.fs.base64_encoder.encode(&random_name, &random_bytes);

const fmt_template = "/tmp/{s}{s}";
const fmt_args = .{
random_name,
extension,
};
return std.fmt.bufPrint(buf, fmt_template, fmt_args) catch return d.fatal("Filename too long for filesystem: " ++ fmt_template, fmt_args);
}

/// If it's used, buf will either hold a filename or `/tmp/<12 random bytes with base-64 encoding>.<extension>`
/// both of which should fit into max_name_bytes for all systems
fn getOutFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u8) ![]const u8 {
if (d.only_compile or d.only_preprocess_and_compile) {
const fmt_template = "{s}{s}";
const fmt_args = .{
std.fs.path.stem(source.path),
if (d.only_preprocess_and_compile) ".s" else d.comp.target.ofmt.fileExt(d.comp.target.cpu.arch),
};
return d.output_name orelse
std.fmt.bufPrint(buf, fmt_template, fmt_args) catch return d.fatal("Filename too long for filesystem: " ++ fmt_template, fmt_args);
}

return d.getRandomFilename(buf, d.comp.target.ofmt.fileExt(d.comp.target.cpu.arch));
}

fn invokeAssembler(d: *Driver, tc: *Toolchain, input_path: []const u8, output_path: []const u8) !void {
var assembler_path_buf: [std.fs.max_path_bytes]u8 = undefined;
const assembler_path = try tc.getAssemblerPath(&assembler_path_buf);
const argv = [_][]const u8{ assembler_path, input_path, "-o", output_path };

var child = std.process.Child.init(&argv, d.comp.gpa);
// TODO handle better
child.stdin_behavior = .Inherit;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;

const term = child.spawnAndWait() catch |er| {
return d.fatal("unable to spawn linker: {s}", .{errorDescription(er)});
};
switch (term) {
.Exited => |code| if (code != 0) {
const e = d.fatal("assembler exited with an error code", .{});
return e;
},
else => {
const e = d.fatal("assembler crashed", .{});
return e;
},
}
}

fn processSource(
d: *Driver,
tc: *Toolchain,
source: Source,
builtin: Source,
user_macros: Source,
comptime fast_exit: bool,
asm_gen_fn: anytype,
) !void {
d.comp.generated_buf.items.len = 0;
var pp = try Preprocessor.initDefault(d.comp);
Expand Down Expand Up @@ -809,70 +879,81 @@ fn processSource(
);
}

var ir = try tree.genIr();
defer ir.deinit(d.comp.gpa);
var name_buf: [std.fs.max_name_bytes]u8 = undefined;
const out_file_name = try d.getOutFileName(source, &name_buf);

if (d.verbose_ir) {
const stdout = std.io.getStdOut();
var buf_writer = std.io.bufferedWriter(stdout.writer());
ir.dump(d.comp.gpa, d.detectConfig(stdout), buf_writer.writer()) catch {};
buf_writer.flush() catch {};
}
if (d.comp.code_gen_options.optimization_level == .@"0") {
const assembly = asm_gen_fn(d.comp.target, tree) catch |er| switch (er) {
error.CodegenFailed => {
d.renderErrors();
d.exitWithCleanup(1);
},
else => |e| return e,
};
defer assembly.deinit(d.comp.gpa);

var render_errors: Ir.Renderer.ErrorList = .{};
defer {
for (render_errors.values()) |msg| d.comp.gpa.free(msg);
render_errors.deinit(d.comp.gpa);
}
if (d.only_preprocess_and_compile) {
const out_file = d.comp.cwd.createFile(out_file_name, .{}) catch |er|
return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) });
defer out_file.close();

var obj = ir.render(d.comp.gpa, d.comp.target, &render_errors) catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
error.LowerFail => {
return d.fatal(
"unable to render Ir to machine code: {s}",
.{render_errors.values()[0]},
);
},
};
defer obj.deinit();
assembly.writeToFile(out_file) catch |er|
return d.fatal("unable to write to output file '{s}': {s}", .{ out_file_name, errorDescription(er) });
if (fast_exit) std.process.exit(0); // Not linking, no need for cleanup.
return;
}

// If it's used, name_buf will either hold a filename or `/tmp/<12 random bytes with base-64 encoding>.<extension>`
// both of which should fit into max_name_bytes for all systems
var name_buf: [std.fs.max_name_bytes]u8 = undefined;
// write to assembly_out_file_name
// then assemble to out_file_name
var assembly_name_buf: [std.fs.max_name_bytes]u8 = undefined;
const assembly_out_file_name = try d.getRandomFilename(&assembly_name_buf, ".s");
const out_file = d.comp.cwd.createFile(assembly_out_file_name, .{}) catch |er|
return d.fatal("unable to create output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) });
defer out_file.close();
assembly.writeToFile(out_file) catch |er|
return d.fatal("unable to write to output file '{s}': {s}", .{ assembly_out_file_name, errorDescription(er) });
try d.invokeAssembler(tc, assembly_out_file_name, out_file_name);
if (d.only_compile) {
if (fast_exit) std.process.exit(0); // Not linking, no need for cleanup.
return;
}
} else {
var ir = try tree.genIr();
defer ir.deinit(d.comp.gpa);

if (d.verbose_ir) {
const stdout = std.io.getStdOut();
var buf_writer = std.io.bufferedWriter(stdout.writer());
ir.dump(d.comp.gpa, d.detectConfig(stdout), buf_writer.writer()) catch {};
buf_writer.flush() catch {};
}

const out_file_name = if (d.only_compile) blk: {
const fmt_template = "{s}{s}";
const fmt_args = .{
std.fs.path.stem(source.path),
d.comp.target.ofmt.fileExt(d.comp.target.cpu.arch),
};
break :blk d.output_name orelse
std.fmt.bufPrint(&name_buf, fmt_template, fmt_args) catch return d.fatal("Filename too long for filesystem: " ++ fmt_template, fmt_args);
} else blk: {
const random_bytes_count = 12;
const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count);

var random_bytes: [random_bytes_count]u8 = undefined;
std.crypto.random.bytes(&random_bytes);
var random_name: [sub_path_len]u8 = undefined;
_ = std.fs.base64_encoder.encode(&random_name, &random_bytes);

const fmt_template = "/tmp/{s}{s}";
const fmt_args = .{
random_name,
d.comp.target.ofmt.fileExt(d.comp.target.cpu.arch),
var render_errors: Ir.Renderer.ErrorList = .{};
defer {
for (render_errors.values()) |msg| d.comp.gpa.free(msg);
render_errors.deinit(d.comp.gpa);
}

var obj = ir.render(d.comp.gpa, d.comp.target, &render_errors) catch |e| switch (e) {
error.OutOfMemory => return error.OutOfMemory,
error.LowerFail => {
return d.fatal(
"unable to render Ir to machine code: {s}",
.{render_errors.values()[0]},
);
},
};
break :blk std.fmt.bufPrint(&name_buf, fmt_template, fmt_args) catch return d.fatal("Filename too long for filesystem: " ++ fmt_template, fmt_args);
};
defer obj.deinit();

const out_file = d.comp.cwd.createFile(out_file_name, .{}) catch |er|
return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) });
defer out_file.close();
const out_file = d.comp.cwd.createFile(out_file_name, .{}) catch |er|
return d.fatal("unable to create output file '{s}': {s}", .{ out_file_name, errorDescription(er) });
defer out_file.close();

obj.finish(out_file) catch |er|
return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(er) });
obj.finish(out_file) catch |er|
return d.fatal("could not output to object file '{s}': {s}", .{ out_file_name, errorDescription(er) });
}

if (d.only_compile) {
if (d.only_compile or d.only_preprocess_and_compile) {
if (fast_exit) std.process.exit(0); // Not linking, no need for cleanup.
return;
}
Expand Down
5 changes: 5 additions & 0 deletions src/aro/Toolchain.zig
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ pub fn deinit(tc: *Toolchain) void {
tc.program_paths.deinit(gpa);
}

/// Write assembler path to `buf` and return a slice of it
pub fn getAssemblerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
return tc.getProgramPath("as", buf);
}

/// Write linker path to `buf` and return a slice of it
pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
// --ld-path= takes precedence over -fuse-ld= and specifies the executable
Expand Down
2 changes: 1 addition & 1 deletion src/aro/Tree.zig
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,7 @@ pub fn nodeTok(tree: *const Tree, node: NodeIndex) ?TokenIndex {

pub fn nodeLoc(tree: *const Tree, node: NodeIndex) ?Source.Location {
const tok_i = tree.nodeTok(node) orelse return null;
return tree.tokens.items(.loc)[@intFromEnum(tok_i)];
return tree.tokens.items(.loc)[tok_i];
}

pub fn dump(tree: *const Tree, config: std.io.tty.Config, writer: anytype) !void {
Expand Down
6 changes: 6 additions & 0 deletions src/aro/Value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,12 @@ pub fn toInt(v: Value, comptime T: type, comp: *const Compilation) ?T {
return big_int.to(T) catch null;
}

pub fn toBytes(v: Value, comp: *const Compilation) []const u8 {
assert(v.opt_ref != .none);
const key = comp.interner.get(v.ref());
return key.bytes;
}

const ComplexOp = enum {
add,
sub,
Expand Down
13 changes: 13 additions & 0 deletions src/assembly_backend.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const std = @import("std");

const aro = @import("aro");
pub const Error = aro.Compilation.Error || error{CodegenFailed};

pub const x86_64 = @import("assembly_backend/x86_64.zig");

pub fn genAsm(target: std.Target, tree: aro.Tree) Error!aro.Assembly {
return switch (target.cpu.arch) {
.x86_64 => x86_64.genAsm(tree),
else => std.debug.panic("genAsm not implemented: {s}", .{@tagName(target.cpu.arch)}),
};
}
Loading

0 comments on commit 22f2ad6

Please sign in to comment.