Merge pull request #5816 from pixelherodev/cbe

Beginnings of C backend
master
Andrew Kelley 2020-07-08 10:45:17 +00:00 committed by GitHub
commit 6fbb5f0a81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1797 additions and 1290 deletions

View File

@ -26,7 +26,7 @@ root_pkg: *Package,
/// Module owns this resource.
/// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`.
root_scope: *Scope,
bin_file: link.ElfFile,
bin_file: *link.File,
bin_file_dir: std.fs.Dir,
bin_file_path: []const u8,
/// It's rare for a decl to be exported, so we save memory by having a sparse map of
@ -45,7 +45,7 @@ export_owners: std.AutoHashMap(*Decl, []*Export),
decl_table: DeclTable,
optimize_mode: std.builtin.Mode,
link_error_flags: link.ElfFile.ErrorFlags = .{},
link_error_flags: link.File.ErrorFlags = .{},
work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
@ -91,7 +91,7 @@ pub const Export = struct {
/// Byte offset into the file that contains the export directive.
src: usize,
/// Represents the position of the export, if any, in the output file.
link: link.ElfFile.Export,
link: link.File.Elf.Export,
/// The Decl that performs the export. Note that this is *not* the Decl being exported.
owner_decl: *Decl,
/// The Decl being exported. Note this is *not* the Decl performing the export.
@ -169,7 +169,7 @@ pub const Decl = struct {
/// Represents the position of the code in the output file.
/// This is populated regardless of semantic analysis and code generation.
link: link.ElfFile.TextBlock = link.ElfFile.TextBlock.empty,
link: link.File.Elf.TextBlock = link.File.Elf.TextBlock.empty,
contents_hash: std.zig.SrcHash,
@ -732,17 +732,19 @@ pub const InitOptions = struct {
object_format: ?std.builtin.ObjectFormat = null,
optimize_mode: std.builtin.Mode = .Debug,
keep_source_files_loaded: bool = false,
cbe: bool = false,
};
pub fn init(gpa: *Allocator, options: InitOptions) !Module {
const bin_file_dir = options.bin_file_dir orelse std.fs.cwd();
var bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{
const bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{
.target = options.target,
.output_mode = options.output_mode,
.link_mode = options.link_mode orelse .Static,
.object_format = options.object_format orelse options.target.getObjectFormat(),
.cbe = options.cbe,
});
errdefer bin_file.deinit();
errdefer bin_file.destroy();
const root_scope = blk: {
if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zig")) {
@ -791,7 +793,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
}
pub fn deinit(self: *Module) void {
self.bin_file.deinit();
self.bin_file.destroy();
const allocator = self.allocator;
self.deletion_set.deinit(allocator);
self.work_queue.deinit();
@ -840,7 +842,7 @@ fn freeExportList(allocator: *Allocator, export_list: []*Export) void {
}
pub fn target(self: Module) std.Target {
return self.bin_file.options.target;
return self.bin_file.options().target;
}
/// Detect changes to source files, perform semantic analysis, and update the output files.
@ -882,7 +884,7 @@ pub fn update(self: *Module) !void {
try self.deleteDecl(decl);
}
self.link_error_flags = self.bin_file.error_flags;
self.link_error_flags = self.bin_file.errorFlags();
// If there are any errors, we anticipate the source files being loaded
// to report error messages. Otherwise we unload all source files to save memory.
@ -1898,8 +1900,9 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {
self.decl_exports.removeAssertDiscard(exp.exported_decl);
}
}
self.bin_file.deleteExport(exp.link);
if (self.bin_file.cast(link.File.Elf)) |elf| {
elf.deleteExport(exp.link);
}
if (self.failed_exports.remove(exp)) |entry| {
entry.value.destroy(self.allocator);
}
@ -1961,7 +1964,7 @@ fn allocateNewDecl(
.analysis = .unreferenced,
.deletion_flag = false,
.contents_hash = contents_hash,
.link = link.ElfFile.TextBlock.empty,
.link = link.File.Elf.TextBlock.empty,
.generation = 0,
};
return new_decl;
@ -2189,19 +2192,21 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const
}
try self.symbol_exports.putNoClobber(symbol_name, new_export);
self.bin_file.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => {
try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1);
self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create(
self.allocator,
src,
"unable to export: {}",
.{@errorName(err)},
));
new_export.status = .failed_retryable;
},
};
if (self.bin_file.cast(link.File.Elf)) |elf| {
elf.updateDeclExports(self, exported_decl, de_gop.entry.value) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
else => {
try self.failed_exports.ensureCapacity(self.failed_exports.items().len + 1);
self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create(
self.allocator,
src,
"unable to export: {}",
.{@errorName(err)},
));
new_export.status = .failed_retryable;
},
};
}
}
fn addNewInstArgs(

8
src-self-hosted/cbe.h Normal file
View File

@ -0,0 +1,8 @@
#if __STDC_VERSION__ >= 201112L
#define noreturn _Noreturn
#elif !__STRICT_ANSI__
#define noreturn __attribute__ ((noreturn))
#else
#define noreturn
#endif

161
src-self-hosted/cgen.zig Normal file
View File

@ -0,0 +1,161 @@
const link = @import("link.zig");
const Module = @import("Module.zig");
const ir = @import("ir.zig");
const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const std = @import("std");
const C = link.File.C;
const Decl = Module.Decl;
const mem = std.mem;
/// Maps a name from Zig source to C. This will always give the same output for
/// any given input.
fn map(name: []const u8) ![]const u8 {
return name;
}
fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) !void {
if (T.tag() == .usize) {
file.need_stddef = true;
try writer.writeAll("size_t");
} else {
switch (T.zigTypeTag()) {
.NoReturn => {
file.need_noreturn = true;
try writer.writeAll("noreturn void");
},
.Void => try writer.writeAll("void"),
else => |e| return file.fail(src, "TODO implement type {}", .{e}),
}
}
}
fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *Decl) !void {
const tv = decl.typed_value.most_recent.typed_value;
try renderType(file, writer, tv.ty.fnReturnType(), decl.src());
const name = try map(mem.spanZ(decl.name));
try writer.print(" {}(", .{name});
if (tv.ty.fnParamLen() == 0) {
try writer.writeAll("void)");
} else {
return file.fail(decl.src(), "TODO implement parameters", .{});
}
}
pub fn generate(file: *C, decl: *Decl) !void {
const writer = file.main.writer();
const header = file.header.writer();
const tv = decl.typed_value.most_recent.typed_value;
switch (tv.ty.zigTypeTag()) {
.Fn => {
try renderFunctionSignature(file, writer, decl);
try writer.writeAll(" {");
const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func;
const instructions = func.analysis.success.instructions;
if (instructions.len > 0) {
for (instructions) |inst| {
try writer.writeAll("\n\t");
switch (inst.tag) {
.assembly => {
const as = inst.cast(ir.Inst.Assembly).?.args;
for (as.inputs) |i, index| {
if (i[0] == '{' and i[i.len - 1] == '}') {
const reg = i[1 .. i.len - 1];
const arg = as.args[index];
if (arg.cast(ir.Inst.Constant)) |c| {
if (c.val.tag() == .int_u64) {
try writer.writeAll("register ");
try renderType(file, writer, arg.ty, decl.src());
try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() });
} else {
return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()});
}
} else {
return file.fail(decl.src(), "TODO non-constant inline asm args", .{});
}
} else {
return file.fail(decl.src(), "TODO non-explicit inline asm regs", .{});
}
}
try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
if (as.output) |o| {
return file.fail(decl.src(), "TODO inline asm output", .{});
}
if (as.inputs.len > 0) {
if (as.output == null) {
try writer.writeAll(" :");
}
try writer.writeAll(": ");
for (as.inputs) |i, index| {
if (i[0] == '{' and i[i.len - 1] == '}') {
const reg = i[1 .. i.len - 1];
const arg = as.args[index];
if (index > 0) {
try writer.writeAll(", ");
}
if (arg.cast(ir.Inst.Constant)) |c| {
try writer.print("\"\"({}_constant)", .{reg});
} else {
// This is blocked by the earlier test
unreachable;
}
} else {
// This is blocked by the earlier test
unreachable;
}
}
}
try writer.writeAll(");");
},
.call => {
const call = inst.cast(ir.Inst.Call).?.args;
if (call.func.cast(ir.Inst.Constant)) |func_inst| {
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const target = func_val.func.owner_decl;
const tname = mem.spanZ(target.name);
if (file.called.get(tname) == null) {
try file.called.put(tname, void{});
try renderFunctionSignature(file, header, target);
try header.writeAll(";\n");
}
try writer.print("{}();", .{tname});
} else {
return file.fail(decl.src(), "TODO non-function call target?", .{});
}
if (call.args.len != 0) {
return file.fail(decl.src(), "TODO function arguments", .{});
}
} else {
return file.fail(decl.src(), "TODO non-constant call inst?", .{});
}
},
else => |e| {
return file.fail(decl.src(), "TODO {}", .{e});
},
}
}
try writer.writeAll("\n");
}
try writer.writeAll("}\n\n");
},
.Array => {
if (mem.indexOf(u8, mem.span(decl.name), "$") == null) {
// TODO: prevent inline asm constants from being emitted
if (tv.val.cast(Value.Payload.Bytes)) |payload| {
try writer.print("const char *const {} = \"{}\";\n", .{ decl.name, payload.data });
std.debug.warn("\n\nARRAYTRANS\n", .{});
if (tv.ty.arraySentinel()) |sentinel| {}
} else {
return file.fail(decl.src(), "TODO non-byte arrays", .{});
}
}
},
else => |e| {
return file.fail(decl.src(), "TODO {}", .{e});
},
}
}

View File

@ -21,7 +21,7 @@ pub const Result = union(enum) {
};
pub fn generateSymbol(
bin_file: *link.ElfFile,
bin_file: *link.File.Elf,
src: usize,
typed_value: TypedValue,
code: *std.ArrayList(u8),
@ -211,7 +211,7 @@ pub fn generateSymbol(
}
const Function = struct {
bin_file: *link.ElfFile,
bin_file: *link.File.Elf,
target: *const std.Target,
mod_fn: *const Module.Fn,
code: *std.ArrayList(u8),

File diff suppressed because it is too large Load Diff

View File

@ -71,7 +71,7 @@ pub fn main() !void {
const args = try process.argsAlloc(arena);
if (args.len <= 1) {
std.debug.warn("expected command argument\n\n{}", .{usage});
std.debug.print("expected command argument\n\n{}", .{usage});
process.exit(1);
}
@ -91,14 +91,14 @@ pub fn main() !void {
return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
} else if (mem.eql(u8, cmd, "version")) {
// Need to set up the build script to give the version as a comptime value.
std.debug.warn("TODO version command not implemented yet\n", .{});
std.debug.print("TODO version command not implemented yet\n", .{});
return error.Unimplemented;
} else if (mem.eql(u8, cmd, "zen")) {
try io.getStdOut().writeAll(info_zen);
} else if (mem.eql(u8, cmd, "help")) {
try io.getStdOut().writeAll(usage);
} else {
std.debug.warn("unknown command: {}\n\n{}", .{ args[1], usage });
std.debug.print("unknown command: {}\n\n{}", .{ args[1], usage });
process.exit(1);
}
}
@ -191,6 +191,7 @@ fn buildOutputType(
var emit_zir: Emit = .no;
var target_arch_os_abi: []const u8 = "native";
var target_mcpu: ?[]const u8 = null;
var cbe: bool = false;
var target_dynamic_linker: ?[]const u8 = null;
var system_libs = std.ArrayList([]const u8).init(gpa);
@ -206,7 +207,7 @@ fn buildOutputType(
process.exit(0);
} else if (mem.eql(u8, arg, "--color")) {
if (i + 1 >= args.len) {
std.debug.warn("expected [auto|on|off] after --color\n", .{});
std.debug.print("expected [auto|on|off] after --color\n", .{});
process.exit(1);
}
i += 1;
@ -218,12 +219,12 @@ fn buildOutputType(
} else if (mem.eql(u8, next_arg, "off")) {
color = .Off;
} else {
std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
process.exit(1);
}
} else if (mem.eql(u8, arg, "--mode")) {
if (i + 1 >= args.len) {
std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{});
std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{});
process.exit(1);
}
i += 1;
@ -237,52 +238,54 @@ fn buildOutputType(
} else if (mem.eql(u8, next_arg, "ReleaseSmall")) {
build_mode = .ReleaseSmall;
} else {
std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg});
std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg});
process.exit(1);
}
} else if (mem.eql(u8, arg, "--name")) {
if (i + 1 >= args.len) {
std.debug.warn("expected parameter after --name\n", .{});
std.debug.print("expected parameter after --name\n", .{});
process.exit(1);
}
i += 1;
provided_name = args[i];
} else if (mem.eql(u8, arg, "--library")) {
if (i + 1 >= args.len) {
std.debug.warn("expected parameter after --library\n", .{});
std.debug.print("expected parameter after --library\n", .{});
process.exit(1);
}
i += 1;
try system_libs.append(args[i]);
} else if (mem.eql(u8, arg, "--version")) {
if (i + 1 >= args.len) {
std.debug.warn("expected parameter after --version\n", .{});
std.debug.print("expected parameter after --version\n", .{});
process.exit(1);
}
i += 1;
version = std.builtin.Version.parse(args[i]) catch |err| {
std.debug.warn("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) });
std.debug.print("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) });
process.exit(1);
};
} else if (mem.eql(u8, arg, "-target")) {
if (i + 1 >= args.len) {
std.debug.warn("expected parameter after -target\n", .{});
std.debug.print("expected parameter after -target\n", .{});
process.exit(1);
}
i += 1;
target_arch_os_abi = args[i];
} else if (mem.eql(u8, arg, "-mcpu")) {
if (i + 1 >= args.len) {
std.debug.warn("expected parameter after -mcpu\n", .{});
std.debug.print("expected parameter after -mcpu\n", .{});
process.exit(1);
}
i += 1;
target_mcpu = args[i];
} else if (mem.eql(u8, arg, "--c")) {
cbe = true;
} else if (mem.startsWith(u8, arg, "-mcpu=")) {
target_mcpu = arg["-mcpu=".len..];
} else if (mem.eql(u8, arg, "--dynamic-linker")) {
if (i + 1 >= args.len) {
std.debug.warn("expected parameter after --dynamic-linker\n", .{});
std.debug.print("expected parameter after --dynamic-linker\n", .{});
process.exit(1);
}
i += 1;
@ -324,39 +327,39 @@ fn buildOutputType(
} else if (mem.startsWith(u8, arg, "-l")) {
try system_libs.append(arg[2..]);
} else {
std.debug.warn("unrecognized parameter: '{}'", .{arg});
std.debug.print("unrecognized parameter: '{}'", .{arg});
process.exit(1);
}
} else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) {
std.debug.warn("assembly files not supported yet", .{});
std.debug.print("assembly files not supported yet", .{});
process.exit(1);
} else if (mem.endsWith(u8, arg, ".o") or
mem.endsWith(u8, arg, ".obj") or
mem.endsWith(u8, arg, ".a") or
mem.endsWith(u8, arg, ".lib"))
{
std.debug.warn("object files and static libraries not supported yet", .{});
std.debug.print("object files and static libraries not supported yet", .{});
process.exit(1);
} else if (mem.endsWith(u8, arg, ".c") or
mem.endsWith(u8, arg, ".cpp"))
{
std.debug.warn("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{});
std.debug.print("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{});
process.exit(1);
} else if (mem.endsWith(u8, arg, ".so") or
mem.endsWith(u8, arg, ".dylib") or
mem.endsWith(u8, arg, ".dll"))
{
std.debug.warn("linking against dynamic libraries not yet supported", .{});
std.debug.print("linking against dynamic libraries not yet supported", .{});
process.exit(1);
} else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) {
if (root_src_file) |other| {
std.debug.warn("found another zig file '{}' after root source file '{}'", .{ arg, other });
std.debug.print("found another zig file '{}' after root source file '{}'", .{ arg, other });
process.exit(1);
} else {
root_src_file = arg;
}
} else {
std.debug.warn("unrecognized file extension of parameter '{}'", .{arg});
std.debug.print("unrecognized file extension of parameter '{}'", .{arg});
}
}
}
@ -367,13 +370,13 @@ fn buildOutputType(
var it = mem.split(basename, ".");
break :blk it.next() orelse basename;
} else {
std.debug.warn("--name [name] not provided and unable to infer\n", .{});
std.debug.print("--name [name] not provided and unable to infer\n", .{});
process.exit(1);
}
};
if (system_libs.items.len != 0) {
std.debug.warn("linking against system libraries not yet supported", .{});
std.debug.print("linking against system libraries not yet supported", .{});
process.exit(1);
}
@ -385,17 +388,17 @@ fn buildOutputType(
.diagnostics = &diags,
}) catch |err| switch (err) {
error.UnknownCpuModel => {
std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
std.debug.print("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
diags.cpu_name.?,
@tagName(diags.arch.?),
});
for (diags.arch.?.allCpuModels()) |cpu| {
std.debug.warn(" {}\n", .{cpu.name});
std.debug.print(" {}\n", .{cpu.name});
}
process.exit(1);
},
error.UnknownCpuFeature => {
std.debug.warn(
std.debug.print(
\\Unknown CPU feature: '{}'
\\Available CPU features for architecture '{}':
\\
@ -404,7 +407,7 @@ fn buildOutputType(
@tagName(diags.arch.?),
});
for (diags.arch.?.allFeaturesList()) |feature| {
std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });
std.debug.print(" {}: {}\n", .{ feature.name, feature.description });
}
process.exit(1);
},
@ -416,21 +419,22 @@ fn buildOutputType(
if (target_info.cpu_detection_unimplemented) {
// TODO We want to just use detected_info.target but implementing
// CPU model & feature detection is todo so here we rely on LLVM.
std.debug.warn("CPU features detection is not yet available for this system without LLVM extensions\n", .{});
std.debug.print("CPU features detection is not yet available for this system without LLVM extensions\n", .{});
process.exit(1);
}
const src_path = root_src_file orelse {
std.debug.warn("expected at least one file argument", .{});
std.debug.print("expected at least one file argument", .{});
process.exit(1);
};
const bin_path = switch (emit_bin) {
.no => {
std.debug.warn("-fno-emit-bin not supported yet", .{});
std.debug.print("-fno-emit-bin not supported yet", .{});
process.exit(1);
},
.yes_default_path => try std.zig.binNameAlloc(arena, root_name, target_info.target, output_mode, link_mode),
.yes_default_path => try std.fmt.allocPrint(arena, "{}.c", .{root_name}),
.yes => |p| p,
};
@ -460,6 +464,7 @@ fn buildOutputType(
.object_format = object_format,
.optimize_mode = build_mode,
.keep_source_files_loaded = zir_out_path != null,
.cbe = cbe,
});
defer module.deinit();
@ -506,7 +511,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo
if (errors.list.len != 0) {
for (errors.list) |full_err_msg| {
std.debug.warn("{}:{}:{}: error: {}\n", .{
std.debug.print("{}:{}:{}: error: {}\n", .{
full_err_msg.src_path,
full_err_msg.line + 1,
full_err_msg.column + 1,
@ -583,7 +588,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
process.exit(0);
} else if (mem.eql(u8, arg, "--color")) {
if (i + 1 >= args.len) {
std.debug.warn("expected [auto|on|off] after --color\n", .{});
std.debug.print("expected [auto|on|off] after --color\n", .{});
process.exit(1);
}
i += 1;
@ -595,7 +600,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
} else if (mem.eql(u8, next_arg, "off")) {
color = .Off;
} else {
std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
process.exit(1);
}
} else if (mem.eql(u8, arg, "--stdin")) {
@ -603,7 +608,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
} else if (mem.eql(u8, arg, "--check")) {
check_flag = true;
} else {
std.debug.warn("unrecognized parameter: '{}'", .{arg});
std.debug.print("unrecognized parameter: '{}'", .{arg});
process.exit(1);
}
} else {
@ -614,7 +619,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
if (stdin_flag) {
if (input_files.items.len != 0) {
std.debug.warn("cannot use --stdin with positional arguments\n", .{});
std.debug.print("cannot use --stdin with positional arguments\n", .{});
process.exit(1);
}
@ -624,7 +629,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
defer gpa.free(source_code);
const tree = std.zig.parse(gpa, source_code) catch |err| {
std.debug.warn("error parsing stdin: {}\n", .{err});
std.debug.print("error parsing stdin: {}\n", .{err});
process.exit(1);
};
defer tree.deinit();
@ -647,7 +652,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
}
if (input_files.items.len == 0) {
std.debug.warn("expected at least one source file argument\n", .{});
std.debug.print("expected at least one source file argument\n", .{});
process.exit(1);
}
@ -664,7 +669,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
for (input_files.span()) |file_path| {
// Get the real path here to avoid Windows failing on relative file paths with . or .. in them.
const real_path = fs.realpathAlloc(gpa, file_path) catch |err| {
std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });
std.debug.print("unable to open '{}': {}\n", .{ file_path, err });
process.exit(1);
};
defer gpa.free(real_path);
@ -702,7 +707,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_
fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
else => {
std.debug.warn("unable to format '{}': {}\n", .{ file_path, err });
std.debug.print("unable to format '{}': {}\n", .{ file_path, err });
fmt.any_error = true;
return;
},
@ -733,7 +738,7 @@ fn fmtPathDir(
try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
} else {
fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
std.debug.warn("unable to format '{}': {}\n", .{ full_path, err });
std.debug.print("unable to format '{}': {}\n", .{ full_path, err });
fmt.any_error = true;
return;
};
@ -784,7 +789,7 @@ fn fmtPathFile(
if (check_mode) {
const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree);
if (anything_changed) {
std.debug.warn("{}\n", .{file_path});
std.debug.print("{}\n", .{file_path});
fmt.any_error = true;
}
} else {
@ -800,7 +805,7 @@ fn fmtPathFile(
try af.file.writeAll(fmt.out_buffer.items);
try af.finish();
std.debug.warn("{}\n", .{file_path});
std.debug.print("{}\n", .{file_path});
}
}

View File

@ -5,6 +5,8 @@ const Allocator = std.mem.Allocator;
const zir = @import("zir.zig");
const Package = @import("Package.zig");
const cheader = @embedFile("cbe.h");
test "self-hosted" {
var ctx = TestContext.init();
defer ctx.deinit();
@ -68,6 +70,7 @@ pub const TestContext = struct {
output_mode: std.builtin.OutputMode,
updates: std.ArrayList(Update),
extension: TestType,
cbe: bool = false,
/// Adds a subcase in which the module is updated with `src`, and the
/// resulting ZIR is validated against `result`.
@ -187,6 +190,22 @@ pub const TestContext = struct {
return ctx.addObj(name, target, .ZIR);
}
pub fn addC(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, T: TestType) *Case {
ctx.cases.append(Case{
.name = name,
.target = target,
.updates = std.ArrayList(Update).init(ctx.cases.allocator),
.output_mode = .Obj,
.extension = T,
.cbe = true,
}) catch unreachable;
return &ctx.cases.items[ctx.cases.items.len - 1];
}
pub fn c(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out);
}
pub fn addCompareOutput(
ctx: *TestContext,
name: []const u8,
@ -365,13 +384,13 @@ pub const TestContext = struct {
}
fn deinit(self: *TestContext) void {
for (self.cases.items) |c| {
for (c.updates.items) |u| {
for (self.cases.items) |case| {
for (case.updates.items) |u| {
if (u.case == .Error) {
c.updates.allocator.free(u.case.Error);
case.updates.allocator.free(u.case.Error);
}
}
c.updates.deinit();
case.updates.deinit();
}
self.cases.deinit();
self.* = undefined;
@ -415,9 +434,6 @@ pub const TestContext = struct {
var module = try Module.init(allocator, .{
.target = target,
// This is an Executable, as opposed to e.g. a *library*. This does
// not mean no ZIR is generated.
//
// TODO: support tests for object file building, and library builds
// and linking. This will require a rework to support multi-file
// tests.
@ -428,6 +444,7 @@ pub const TestContext = struct {
.bin_file_path = bin_name,
.root_pkg = root_pkg,
.keep_source_files_loaded = true,
.cbe = case.cbe,
});
defer module.deinit();
@ -447,33 +464,66 @@ pub const TestContext = struct {
try module.update();
module_node.end();
if (update.case != .Error) {
var all_errors = try module.getAllErrorsAlloc();
defer all_errors.deinit(allocator);
if (all_errors.list.len != 0) {
std.debug.warn("\nErrors occurred updating the module:\n================\n", .{});
for (all_errors.list) |err| {
std.debug.warn(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg });
}
std.debug.warn("Test failed.\n", .{});
std.process.exit(1);
}
}
switch (update.case) {
.Transformation => |expected_output| {
update_node.estimated_total_items = 5;
var emit_node = update_node.start("emit", null);
emit_node.activate();
var new_zir_module = try zir.emit(allocator, module);
defer new_zir_module.deinit(allocator);
emit_node.end();
if (case.cbe) {
var cfile: *link.File.C = module.bin_file.cast(link.File.C).?;
cfile.file.?.close();
cfile.file = null;
var file = try tmp.dir.openFile(bin_name, .{ .read = true });
defer file.close();
var out = file.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!");
defer allocator.free(out);
var write_node = update_node.start("write", null);
write_node.activate();
var out_zir = std.ArrayList(u8).init(allocator);
defer out_zir.deinit();
try new_zir_module.writeToStream(allocator, out_zir.outStream());
write_node.end();
if (expected_output.len != out.len) {
std.debug.warn("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
std.process.exit(1);
}
for (expected_output) |e, i| {
if (out[i] != e) {
std.debug.warn("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
std.process.exit(1);
}
}
} else {
update_node.estimated_total_items = 5;
var emit_node = update_node.start("emit", null);
emit_node.activate();
var new_zir_module = try zir.emit(allocator, module);
defer new_zir_module.deinit(allocator);
emit_node.end();
var test_node = update_node.start("assert", null);
test_node.activate();
defer test_node.end();
if (expected_output.len != out_zir.items.len) {
std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
std.process.exit(1);
}
for (expected_output) |e, i| {
if (out_zir.items[i] != e) {
if (expected_output.len != out_zir.items.len) {
std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
var write_node = update_node.start("write", null);
write_node.activate();
var out_zir = std.ArrayList(u8).init(allocator);
defer out_zir.deinit();
try new_zir_module.writeToStream(allocator, out_zir.outStream());
write_node.end();
var test_node = update_node.start("assert", null);
test_node.activate();
defer test_node.end();
if (expected_output.len != out_zir.items.len) {
std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
std.process.exit(1);
}
for (expected_output) |e, i| {
if (out_zir.items[i] != e) {
std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
std.process.exit(1);
}
}
@ -511,6 +561,8 @@ pub const TestContext = struct {
}
},
.Execution => |expected_stdout| {
std.debug.assert(!case.cbe);
update_node.estimated_total_items = 4;
var exec_result = x: {
var exec_node = update_node.start("execute", null);

85
test/stage2/cbe.zig Normal file
View File

@ -0,0 +1,85 @@
const std = @import("std");
const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
// These tests should work with all platforms, but we're using linux_x64 for
// now for consistency. Will be expanded eventually.
const linux_x64 = std.zig.CrossTarget{
.cpu_arch = .x86_64,
.os_tag = .linux,
};
pub fn addCases(ctx: *TestContext) !void {
ctx.c("empty start function", linux_x64,
\\export fn _start() noreturn {}
,
\\noreturn void _start(void) {}
\\
);
ctx.c("less empty start function", linux_x64,
\\fn main() noreturn {}
\\
\\export fn _start() noreturn {
\\ main();
\\}
,
\\noreturn void main(void);
\\
\\noreturn void _start(void) {
\\ main();
\\}
\\
\\noreturn void main(void) {}
\\
);
// TODO: implement return values
ctx.c("inline asm", linux_x64,
\\fn exitGood() void {
\\ asm volatile ("syscall"
\\ :
\\ : [number] "{rax}" (231),
\\ [arg1] "{rdi}" (0)
\\ );
\\}
\\
\\export fn _start() noreturn {
\\ exitGood();
\\}
,
\\#include <stddef.h>
\\
\\void exitGood(void);
\\
\\noreturn void _start(void) {
\\ exitGood();
\\}
\\
\\void exitGood(void) {
\\ register size_t rax_constant __asm__("rax") = 231;
\\ register size_t rdi_constant __asm__("rdi") = 0;
\\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant));
\\}
\\
);
//ctx.c("basic return", linux_x64,
// \\fn main() u8 {
// \\ return 103;
// \\}
// \\
// \\export fn _start() noreturn {
// \\ _ = main();
// \\}
//,
// \\#include <stdint.h>
// \\
// \\uint8_t main(void);
// \\
// \\noreturn void _start(void) {
// \\ (void)main();
// \\}
// \\
// \\uint8_t main(void) {
// \\ return 103;
// \\}
// \\
//);
}

View File

@ -4,4 +4,5 @@ pub fn addCases(ctx: *TestContext) !void {
try @import("compile_errors.zig").addCases(ctx);
try @import("compare_output.zig").addCases(ctx);
try @import("zir.zig").addCases(ctx);
try @import("cbe.zig").addCases(ctx);
}