From 40f0275e7cb7f3f4b2c382e5b457e714080c4cf2 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Fri, 13 Nov 2020 00:50:02 +1100 Subject: [PATCH 01/12] Implement emit-h --- src/Compilation.zig | 77 +++++++++++++++++++------- src/codegen/c.zig | 130 ++++++++++++++++++++++++++++---------------- src/link/C.zig | 62 ++++++++++++++------- 3 files changed, 183 insertions(+), 86 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 23f67f5b37..5b06bd6370 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -26,6 +26,8 @@ const Module = @import("Module.zig"); const Cache = @import("Cache.zig"); const stage1 = @import("stage1.zig"); const translate_c = @import("translate_c.zig"); +const c_codegen = @import("codegen/c.zig"); +const c_link = @import("link/C.zig"); const ThreadPool = @import("ThreadPool.zig"); const WaitGroup = @import("WaitGroup.zig"); @@ -131,6 +133,7 @@ emit_asm: ?EmitLoc, emit_llvm_ir: ?EmitLoc, emit_analysis: ?EmitLoc, emit_docs: ?EmitLoc, +c_header: ?c_link.Header, pub const InnerError = Module.InnerError; @@ -895,10 +898,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { }; }; - if (!use_llvm and options.emit_h != null) { - fatal("TODO implement support for -femit-h in the self-hosted backend", .{}); - } - var system_libs: std.StringArrayHashMapUnmanaged(void) = .{}; errdefer system_libs.deinit(gpa); try system_libs.ensureCapacity(gpa, options.system_libs.len); @@ -975,6 +974,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .global_cache_directory = options.global_cache_directory, .bin_file = bin_file, .emit_h = options.emit_h, + .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa) else null, .emit_asm = options.emit_asm, .emit_llvm_ir = options.emit_llvm_ir, .emit_analysis = options.emit_analysis, @@ -1286,6 +1286,20 @@ pub fn update(self: *Compilation) !void { module.root_scope.unload(self.gpa); } } + + // If we've chosen to emit a C header, flush the header to the disk. + if (self.c_header) |header| { + const header_path = self.emit_h.?; + // If a directory has been provided, write the header there. Otherwise, just write it to the + // cache directory. + const header_dir = if (header_path.directory) |dir| + dir.handle + else + self.local_cache_directory.handle; + const header_file = try header_dir.createFile(header_path.basename, .{}); + defer header_file.close(); + try header.flush(header_file.writer()); + } } /// Having the file open for writing is problematic as far as executing the @@ -1385,6 +1399,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len); defer c_comp_progress_node.end(); + var arena = std.heap.ArenaAllocator.init(self.gpa); + defer arena.deinit(); + var wg = WaitGroup{}; defer wg.wait(); @@ -1432,22 +1449,44 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); - self.bin_file.updateDecl(module, decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - decl.analysis = .dependency_failure; - }, - else => { - try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - module.gpa, - decl.src(), - "unable to codegen: {}", - .{@errorName(err)}, - )); - decl.analysis = .codegen_failure_retryable; - }, + self.bin_file.updateDecl(module, decl) catch |err| { + switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + decl.analysis = .dependency_failure; + }, + else => { + try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); + module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.gpa, + decl.src(), + "unable to codegen: {}", + .{@errorName(err)}, + )); + decl.analysis = .codegen_failure_retryable; + }, + } + return; }; + + if (self.c_header) |*header| { + c_codegen.generateHeader(&arena, module, &header.*, decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + decl.analysis = .dependency_failure; + }, + else => { + try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); + module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.gpa, + decl.src(), + "unable to generate C header: {}", + .{@errorName(err)}, + )); + decl.analysis = .codegen_failure_retryable; + }, + }; + } }, }, .analyze_decl => |decl| { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index e562ce320e..425663ad9c 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -2,6 +2,7 @@ const std = @import("std"); const link = @import("../link.zig"); const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); const Inst = @import("../ir.zig").Inst; const Value = @import("../value.zig").Value; @@ -19,7 +20,7 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { return allocator.dupe(u8, name); } -fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void { +fn renderType(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, T: Type) !void { switch (T.zigTypeTag()) { .NoReturn => { try writer.writeAll("zig_noreturn void"); @@ -27,16 +28,16 @@ fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void { .Void => try writer.writeAll("void"), .Int => { if (T.tag() == .u8) { - ctx.file.need_stdint = true; + header.need_stdint = true; try writer.writeAll("uint8_t"); } else if (T.tag() == .usize) { - ctx.file.need_stddef = true; + header.need_stddef = true; try writer.writeAll("size_t"); } else { - return ctx.file.fail(ctx.decl.src(), "TODO implement int types", .{}); + return ctx.fail(ctx.decl.src(), "TODO implement int types", .{}); } }, - else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement type {}", .{e}), + else => |e| return ctx.fail(ctx.decl.src(), "TODO implement type {}", .{e}), } } @@ -47,13 +48,13 @@ fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Va return writer.print("{}", .{val.toSignedInt()}); return writer.print("{}", .{val.toUnsignedInt()}); }, - else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement value {}", .{e}), + else => |e| return ctx.fail(ctx.decl.src(), "TODO implement value {}", .{e}), } } -fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { +fn renderFunctionSignature(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; - try renderType(ctx, writer, tv.ty.fnReturnType()); + try renderType(ctx, header, writer, tv.ty.fnReturnType()); // Use the child allocator directly, as we know the name can be freed before // the rest of the arena. const name = try map(ctx.arena.child_allocator, mem.spanZ(decl.name)); @@ -68,7 +69,7 @@ fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl if (index > 0) { try writer.writeAll(", "); } - try renderType(ctx, writer, tv.ty.fnParamType(index)); + try renderType(ctx, header, writer, tv.ty.fnParamType(index)); try writer.print(" arg{}", .{index}); } } @@ -83,6 +84,34 @@ pub fn generate(file: *C, decl: *Decl) !void { } } +pub fn generateHeader( + arena: *std.heap.ArenaAllocator, + module: *Module, + header: *C.Header, + decl: *Decl, +) error{ AnalysisFail, OutOfMemory }!void { + switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { + .Fn => { + var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); + defer inst_map.deinit(); + var ctx = Context{ + .decl = decl, + .arena = arena, + .inst_map = &inst_map, + }; + const writer = header.buf.writer(); + renderFunctionSignature(&ctx, header, writer, decl) catch |err| { + if (err == error.AnalysisFail) { + try module.failed_decls.put(module.gpa, decl, ctx.error_msg); + } + return err; + }; + try writer.writeAll(";\n"); + }, + else => {}, + } +} + fn genArray(file: *C, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; // TODO: prevent inline asm constants from being emitted @@ -102,12 +131,12 @@ fn genArray(file: *C, decl: *Decl) !void { } const Context = struct { - file: *C, decl: *Decl, inst_map: *std.AutoHashMap(*Inst, []u8), arena: *std.heap.ArenaAllocator, argdex: usize = 0, unnamed_index: usize = 0, + error_msg: *Compilation.ErrorMsg = undefined, fn resolveInst(self: *Context, inst: *Inst) ![]u8 { if (inst.cast(Inst.Constant)) |const_inst| { @@ -127,6 +156,11 @@ const Context = struct { return val; } + fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { + self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args); + return error.AnalysisFail; + } + fn deinit(self: *Context) void { self.* = undefined; } @@ -141,14 +175,16 @@ fn genFn(file: *C, decl: *Decl) !void { var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); defer inst_map.deinit(); var ctx = Context{ - .file = file, .decl = decl, .arena = &arena, .inst_map = &inst_map, }; - defer ctx.deinit(); + defer { + file.error_msg = ctx.error_msg; + ctx.deinit(); + } - try renderFunctionSignature(&ctx, writer, decl); + try renderFunctionSignature(&ctx, &file.header, writer, decl); try writer.writeAll(" {"); @@ -158,18 +194,18 @@ fn genFn(file: *C, decl: *Decl) !void { try writer.writeAll("\n"); for (instructions) |inst| { if (switch (inst.tag) { - .assembly => try genAsm(&ctx, inst.castTag(.assembly).?), - .call => try genCall(&ctx, inst.castTag(.call).?), - .add => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "+"), - .sub => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "-"), + .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), + .call => try genCall(&ctx, file, inst.castTag(.call).?), + .add => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "+"), + .sub => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "-"), .ret => try genRet(&ctx, inst.castTag(.ret).?), - .retvoid => try genRetVoid(&ctx), + .retvoid => try genRetVoid(file), .arg => try genArg(&ctx), .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?), - .unreach => try genUnreach(&ctx, inst.castTag(.unreach).?), - .intcast => try genIntCast(&ctx, inst.castTag(.intcast).?), - else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}), + .unreach => try genUnreach(file, inst.castTag(.unreach).?), + .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), + else => |e| return ctx.fail(decl.src(), "TODO implement C codegen for {}", .{e}), }) |name| { try ctx.inst_map.putNoClobber(inst, name); } @@ -185,46 +221,46 @@ fn genArg(ctx: *Context) !?[]u8 { return name; } -fn genRetVoid(ctx: *Context) !?[]u8 { - try ctx.file.main.writer().print(indentation ++ "return;\n", .{}); +fn genRetVoid(file: *C) !?[]u8 { + try file.main.writer().print(indentation ++ "return;\n", .{}); return null; } fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 { - return ctx.file.fail(ctx.decl.src(), "TODO return", .{}); + return ctx.fail(ctx.decl.src(), "TODO return", .{}); } -fn genIntCast(ctx: *Context, inst: *Inst.UnOp) !?[]u8 { +fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { if (inst.base.isUnused()) return null; const op = inst.operand; - const writer = ctx.file.main.writer(); + const writer = file.main.writer(); const name = try ctx.name(); const from = try ctx.resolveInst(inst.operand); try writer.writeAll(indentation ++ "const "); - try renderType(ctx, writer, inst.base.ty); + try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print(" {} = (", .{name}); - try renderType(ctx, writer, inst.base.ty); + try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print("){};\n", .{from}); return name; } -fn genBinOp(ctx: *Context, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { +fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { if (inst.base.isUnused()) return null; const lhs = ctx.resolveInst(inst.lhs); const rhs = ctx.resolveInst(inst.rhs); - const writer = ctx.file.main.writer(); + const writer = file.main.writer(); const name = try ctx.name(); try writer.writeAll(indentation ++ "const "); - try renderType(ctx, writer, inst.base.ty); + try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs }); return name; } -fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { - const writer = ctx.file.main.writer(); - const header = ctx.file.header.writer(); +fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { + const writer = file.main.writer(); + const header = file.header.buf.writer(); try writer.writeAll(indentation); if (inst.func.castTag(.constant)) |func_inst| { if (func_inst.val.cast(Value.Payload.Function)) |func_val| { @@ -235,9 +271,9 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { try writer.print("(void)", .{}); } const tname = mem.spanZ(target.name); - if (ctx.file.called.get(tname) == null) { - try ctx.file.called.put(tname, void{}); - try renderFunctionSignature(ctx, header, target); + if (file.called.get(tname) == null) { + try file.called.put(tname, void{}); + try renderFunctionSignature(ctx, &file.header, header, target); try header.writeAll(";\n"); } try writer.print("{}(", .{tname}); @@ -256,10 +292,10 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { } try writer.writeAll(");\n"); } else { - return ctx.file.fail(ctx.decl.src(), "TODO non-function call target?", .{}); + return ctx.fail(ctx.decl.src(), "TODO non-function call target?", .{}); } } else { - return ctx.file.fail(ctx.decl.src(), "TODO non-constant call inst?", .{}); + return ctx.fail(ctx.decl.src(), "TODO non-constant call inst?", .{}); } return null; } @@ -274,20 +310,20 @@ fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { return null; } -fn genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { - try ctx.file.main.writer().writeAll(indentation ++ "zig_unreachable();\n"); +fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { + try file.main.writer().writeAll(indentation ++ "zig_unreachable();\n"); return null; } -fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 { - const writer = ctx.file.main.writer(); +fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { + const writer = file.main.writer(); try writer.writeAll(indentation); 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]; try writer.writeAll("register "); - try renderType(ctx, writer, arg.ty); + try renderType(ctx, &file.header, writer, arg.ty); try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg }); // TODO merge constant handling into inst_map as well if (arg.castTag(.constant)) |c| { @@ -296,17 +332,17 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 { } else { const gop = try ctx.inst_map.getOrPut(arg); if (!gop.found_existing) { - return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); + return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); } try writer.print("{};\n ", .{gop.entry.value}); } } else { - return ctx.file.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); + return ctx.fail(ctx.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 ctx.file.fail(ctx.decl.src(), "TODO inline asm output", .{}); + return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{}); } if (as.inputs.len > 0) { if (as.output == null) { diff --git a/src/link/C.zig b/src/link/C.zig index 467e10998e..0906482dd1 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -13,15 +13,52 @@ const C = @This(); pub const base_tag: File.Tag = .c; +pub const Header = struct { + buf: std.ArrayList(u8), + need_stddef: bool = false, + need_stdint: bool = false, + + pub fn init(allocator: *Allocator) Header { + return .{ + .buf = std.ArrayList(u8).init(allocator), + }; + } + + pub fn flush(self: *const Header, writer: anytype) !void { + const tracy = trace(@src()); + defer tracy.end(); + + try writer.writeAll(@embedFile("cbe.h")); + var includes = false; + if (self.need_stddef) { + try writer.writeAll("#include \n"); + includes = true; + } + if (self.need_stdint) { + try writer.writeAll("#include \n"); + includes = true; + } + if (includes) { + try writer.writeByte('\n'); + } + if (self.buf.items.len > 0) { + try writer.print("{}\n", .{self.buf.items}); + } + } + + pub fn deinit(self: *Header) void { + self.buf.deinit(); + self.* = undefined; + } +}; + base: File, -header: std.ArrayList(u8), +header: Header, constants: std.ArrayList(u8), main: std.ArrayList(u8), called: std.StringHashMap(void), -need_stddef: bool = false, -need_stdint: bool = false, error_msg: *Compilation.ErrorMsg = undefined, pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { @@ -44,7 +81,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio .allocator = allocator, }, .main = std.ArrayList(u8).init(allocator), - .header = std.ArrayList(u8).init(allocator), + .header = Header.init(allocator), .constants = std.ArrayList(u8).init(allocator), .called = std.StringHashMap(void).init(allocator), }; @@ -82,22 +119,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { defer tracy.end(); const writer = self.base.file.?.writer(); - try writer.writeAll(@embedFile("cbe.h")); - var includes = false; - if (self.need_stddef) { - try writer.writeAll("#include \n"); - includes = true; - } - if (self.need_stdint) { - try writer.writeAll("#include \n"); - includes = true; - } - if (includes) { - try writer.writeByte('\n'); - } - if (self.header.items.len > 0) { - try writer.print("{}\n", .{self.header.items}); - } + try self.header.flush(writer); if (self.constants.items.len > 0) { try writer.print("{}\n", .{self.constants.items}); } From aba273d7313ece312cbf747ab5808063a6d8a1b4 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Thu, 19 Nov 2020 19:20:10 +1100 Subject: [PATCH 02/12] Enable emit-h by default for obj and lib compilation. --- src/main.zig | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/main.zig b/src/main.zig index c25bc20d85..c307dc7819 100644 --- a/src/main.zig +++ b/src/main.zig @@ -591,16 +591,14 @@ fn buildOutputType( }, else => unreachable, } - // TODO finish self-hosted and add support for emitting C header files - emit_h = .no; - //switch (arg_mode) { - // .build => switch (output_mode) { - // .Exe => emit_h = .no, - // .Obj, .Lib => emit_h = .yes_default_path, - // }, - // .translate_c, .zig_test, .run => emit_h = .no, - // else => unreachable, - //} + switch (arg_mode) { + .build => switch (output_mode) { + .Exe => emit_h = .no, + .Obj, .Lib => emit_h = .yes_default_path, + }, + .translate_c, .zig_test, .run => emit_h = .no, + else => unreachable, + } soname = .yes_default_value; const args = all_args[2..]; From c87da2f45afedb33b5d1531ee0e4f67675aa9f78 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Thu, 19 Nov 2020 19:57:22 +1100 Subject: [PATCH 03/12] Remove redundant emit_h member in Compilation struct. --- src/Compilation.zig | 13 ++++++------- src/link/C.zig | 6 ++++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 5b06bd6370..2754141370 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -128,11 +128,11 @@ test_filter: ?[]const u8, test_name_prefix: ?[]const u8, test_evented_io: bool, -emit_h: ?EmitLoc, emit_asm: ?EmitLoc, emit_llvm_ir: ?EmitLoc, emit_analysis: ?EmitLoc, emit_docs: ?EmitLoc, + c_header: ?c_link.Header, pub const InnerError = Module.InnerError; @@ -973,8 +973,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .local_cache_directory = options.local_cache_directory, .global_cache_directory = options.global_cache_directory, .bin_file = bin_file, - .emit_h = options.emit_h, - .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa) else null, + .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa, options.emit_h) else null, .emit_asm = options.emit_asm, .emit_llvm_ir = options.emit_llvm_ir, .emit_analysis = options.emit_analysis, @@ -1289,7 +1288,7 @@ pub fn update(self: *Compilation) !void { // If we've chosen to emit a C header, flush the header to the disk. if (self.c_header) |header| { - const header_path = self.emit_h.?; + const header_path = header.emit_loc.?; // If a directory has been provided, write the header there. Otherwise, just write it to the // cache directory. const header_dir = if (header_path.directory) |dir| @@ -2952,7 +2951,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node man.hash.add(comp.bin_file.options.function_sections); man.hash.add(comp.bin_file.options.is_test); man.hash.add(comp.bin_file.options.emit != null); - man.hash.addOptionalEmitLoc(comp.emit_h); + man.hash.add(comp.c_header != null); man.hash.addOptionalEmitLoc(comp.emit_asm); man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); man.hash.addOptionalEmitLoc(comp.emit_analysis); @@ -3051,10 +3050,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node }); break :blk try directory.join(arena, &[_][]const u8{bin_basename}); } else ""; - if (comp.emit_h != null) { + if (comp.c_header != null) { log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{}); } - const emit_h_path = try stage1LocPath(arena, comp.emit_h, directory); + const emit_h_path = try stage1LocPath(arena, if (comp.c_header) |header| header.emit_loc else null, directory); const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory); const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory); const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory); diff --git a/src/link/C.zig b/src/link/C.zig index 0906482dd1..839c8cabfe 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -17,10 +17,12 @@ pub const Header = struct { buf: std.ArrayList(u8), need_stddef: bool = false, need_stdint: bool = false, + emit_loc: ?Compilation.EmitLoc, - pub fn init(allocator: *Allocator) Header { + pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header { return .{ .buf = std.ArrayList(u8).init(allocator), + .emit_loc = emit_loc, }; } @@ -81,7 +83,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio .allocator = allocator, }, .main = std.ArrayList(u8).init(allocator), - .header = Header.init(allocator), + .header = Header.init(allocator, null), .constants = std.ArrayList(u8).init(allocator), .called = std.StringHashMap(void).init(allocator), }; From 15a148db01896d8ef7290593755d185292584510 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 18 Nov 2020 20:50:12 -0500 Subject: [PATCH 04/12] Fix memory leak --- src/Compilation.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Compilation.zig b/src/Compilation.zig index 2754141370..8f1c9ac29d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1184,6 +1184,10 @@ pub fn destroy(self: *Compilation) void { } self.failed_c_objects.deinit(gpa); + if (self.c_header) |*header| { + header.deinit(); + } + self.cache_parent.manifest_dir.close(); if (self.owned_link_dir) |*dir| dir.close(); From 37438dd78975cf4ce46afaf4ba8c31d268d480bc Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 18 Nov 2020 20:50:51 -0500 Subject: [PATCH 05/12] CBE: add support for bool,u32 --- src/codegen/c.zig | 6 +++++- src/link/cbe.h | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 425663ad9c..589e2f17e0 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -26,15 +26,19 @@ fn renderType(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer try writer.writeAll("zig_noreturn void"); }, .Void => try writer.writeAll("void"), + .Bool => try writer.writeAll("bool"), .Int => { if (T.tag() == .u8) { header.need_stdint = true; try writer.writeAll("uint8_t"); + } else if (T.tag() == .u32) { + header.need_stdint = true; + try writer.writeAll("uint32_t"); } else if (T.tag() == .usize) { header.need_stddef = true; try writer.writeAll("size_t"); } else { - return ctx.fail(ctx.decl.src(), "TODO implement int types", .{}); + return ctx.fail(ctx.decl.src(), "TODO implement int type {}", .{T}); } }, else => |e| return ctx.fail(ctx.decl.src(), "TODO implement type {}", .{e}), diff --git a/src/link/cbe.h b/src/link/cbe.h index 854032227d..f35c6b8fff 100644 --- a/src/link/cbe.h +++ b/src/link/cbe.h @@ -1,3 +1,12 @@ +#if __STDC_VERSION__ >= 199901L +// C99 or newer +#include +#else +#define bool unsigned char +#define true 1 +#define false 0 +#endif + #if __STDC_VERSION__ >= 201112L #define zig_noreturn _Noreturn #elif __GNUC__ From ac33b10b5e75293357ee50b0b5e7a7b3591a0c31 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 18 Nov 2020 20:51:26 -0500 Subject: [PATCH 06/12] CBE: Improve formatting --- src/link/C.zig | 5 ++++- src/link/cbe.h | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/link/C.zig b/src/link/C.zig index 839c8cabfe..325f2ee048 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -44,7 +44,7 @@ pub const Header = struct { try writer.writeByte('\n'); } if (self.buf.items.len > 0) { - try writer.print("{}\n", .{self.buf.items}); + try writer.print("{}", .{self.buf.items}); } } @@ -122,6 +122,9 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { const writer = self.base.file.?.writer(); try self.header.flush(writer); + if (self.header.buf.items.len > 0) { + try writer.writeByte('\n'); + } if (self.constants.items.len > 0) { try writer.print("{}\n", .{self.constants.items}); } diff --git a/src/link/cbe.h b/src/link/cbe.h index f35c6b8fff..c3f889c6f1 100644 --- a/src/link/cbe.h +++ b/src/link/cbe.h @@ -22,3 +22,4 @@ #else #define zig_unreachable() #endif + From 8538053940bafde64941f8ebf6d559e5ade490f1 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 18 Nov 2020 20:51:42 -0500 Subject: [PATCH 07/12] Add header test harness --- src/test.zig | 46 +++++++++++++++++++++++++++++++++++++++++---- test/stage2/cbe.zig | 6 ++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/test.zig b/src/test.zig index bc141c94f0..d1eee284c5 100644 --- a/src/test.zig +++ b/src/test.zig @@ -96,6 +96,10 @@ pub const TestContext = struct { /// stdout against the expected results /// This is a slice containing the expected message. Execution: []const u8, + /// A header update compiles the input with the equivalent of + /// `-Demit_h=true` and tests the produced header against the + /// expected result + Header: []const u8, }, }; @@ -138,6 +142,15 @@ pub const TestContext = struct { }) catch unreachable; } + /// Adds a subcase in which the module is updated with `src`, and a C + /// header is generated. + pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void { + self.updates.append(.{ + .src = src, + .case = .{ .Header = result }, + }) catch unreachable; + } + /// Adds a subcase in which the module is updated with `src`, compiled, /// run, and the output is tested against `result`. pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { @@ -269,6 +282,10 @@ pub const TestContext = struct { ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out); } + pub fn h(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { + ctx.addC(name, target, .Zig).addHeader(src, cheader ++ out); + } + pub fn addCompareOutput( ctx: *TestContext, name: []const u8, @@ -547,6 +564,10 @@ pub const TestContext = struct { .directory = emit_directory, .basename = bin_name, }; + const emit_h: Compilation.EmitLoc = .{ + .directory = emit_directory, + .basename = "test_case.h", + }; const comp = try Compilation.create(allocator, .{ .local_cache_directory = zig_cache_directory, .global_cache_directory = zig_cache_directory, @@ -561,6 +582,7 @@ pub const TestContext = struct { // TODO: support testing optimizations .optimize_mode = .Debug, .emit_bin = emit_bin, + .emit_h = emit_h, .root_pkg = &root_pkg, .keep_source_files_loaded = true, .object_format = ofmt, @@ -616,6 +638,22 @@ pub const TestContext = struct { } switch (update.case) { + .Header => |expected_output| { + var file = try tmp.dir.openFile("test_case.h", .{ .read = true }); + defer file.close(); + var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read headeroutput!"); + + if (expected_output.len != out.len) { + std.debug.print("\nTransformed header 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.print("\nTransformed header differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); + std.process.exit(1); + } + } + }, .Transformation => |expected_output| { if (case.cbe) { // The C file is always closed after an update, because we don't support @@ -670,8 +708,8 @@ pub const TestContext = struct { test_node.activate(); defer test_node.end(); var handled_errors = try arena.alloc(bool, e.len); - for (handled_errors) |*h| { - h.* = false; + for (handled_errors) |*handled| { + handled.* = false; } var all_errors = try comp.getAllErrorsAlloc(); defer all_errors.deinit(allocator); @@ -709,8 +747,8 @@ pub const TestContext = struct { } } - for (handled_errors) |h, i| { - if (!h) { + for (handled_errors) |handled, i| { + if (!handled) { const er = e[i]; std.debug.print( "{s}\nDid not receive error:\n================\n{}\n================\nTest failed.\n", diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index fbf0f9e708..a3ad90b501 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -19,6 +19,12 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ ); + ctx.h("Simple header", linux_x64, + \\export fn start() void{} + , + \\void start(void); + \\ + ); ctx.c("less empty start function", linux_x64, \\fn main() noreturn { \\ unreachable; From 8d6066e09cd9dbdb41f1be5b067bcff5423e2cbc Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Thu, 19 Nov 2020 16:46:07 -0500 Subject: [PATCH 08/12] Fix a dumb --- src/test.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test.zig b/src/test.zig index d1eee284c5..aafd469585 100644 --- a/src/test.zig +++ b/src/test.zig @@ -97,7 +97,7 @@ pub const TestContext = struct { /// This is a slice containing the expected message. Execution: []const u8, /// A header update compiles the input with the equivalent of - /// `-Demit_h=true` and tests the produced header against the + /// `-femit-h` and tests the produced header against the /// expected result Header: []const u8, }, From 58bd6c5f8e4b26300dcdd9542881fe148fb51d0c Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Sun, 22 Nov 2020 18:51:15 +1100 Subject: [PATCH 09/12] Add tests for emit-h functionality. --- test/stage2/cbe.zig | 67 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index a3ad90b501..96db7b835e 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -19,7 +19,7 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ ); - ctx.h("Simple header", linux_x64, + ctx.h("simple header", linux_x64, \\export fn start() void{} , \\void start(void); @@ -249,4 +249,69 @@ pub fn addCases(ctx: *TestContext) !void { \\} \\ ); + ctx.h("header with single param function", linux_x64, + \\export fn start(a: u8) void{} + , + \\#include + \\ + \\void start(uint8_t arg0); + \\ + ); + ctx.h("header with multiple param function", linux_x64, + \\export fn start(a: u8, b: u8, c: u8) void{} + , + \\#include + \\ + \\void start(uint8_t arg0, uint8_t arg1, uint8_t arg2); + \\ + ); + ctx.h("header with u32 param function", linux_x64, + \\export fn start(a: u32) void{} + , + \\#include + \\ + \\void start(uint32_t arg0); + \\ + ); + ctx.h("header with usize param function", linux_x64, + \\export fn start(a: usize) void{} + , + \\#include + \\ + \\void start(size_t arg0); + \\ + ); + ctx.h("header with bool param function", linux_x64, + \\export fn start(a: bool) void{} + , + \\void start(bool arg0); + \\ + ); + ctx.h("header with noreturn function", linux_x64, + \\export fn start() noreturn { + \\ unreachable; + \\} + , + \\zig_noreturn void start(void); + \\ + ); + ctx.h("header with multiple functions", linux_x64, + \\export fn a() void{} + \\export fn b() void{} + \\export fn c() void{} + , + \\void a(void); + \\void b(void); + \\void c(void); + \\ + ); + ctx.h("header with multiple includes", linux_x64, + \\export fn start(a: u32, b: usize) void{} + , + \\#include + \\#include + \\ + \\void start(uint32_t arg0, size_t arg1); + \\ + ); } From e834d0369a2339059284bcd9e695ae3ef25509fb Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Sun, 22 Nov 2020 18:59:48 +1100 Subject: [PATCH 10/12] Make sure emit-h is off by default. --- src/main.zig | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/main.zig b/src/main.zig index c307dc7819..6f6d5c9f6f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -490,7 +490,7 @@ fn buildOutputType( var target_dynamic_linker: ?[]const u8 = null; var target_ofmt: ?[]const u8 = null; var output_mode: std.builtin.OutputMode = undefined; - var emit_h: Emit = undefined; + var emit_h: Emit = .no; var soname: SOName = undefined; var ensure_libc_on_non_freestanding = false; var ensure_libcpp_on_non_freestanding = false; @@ -591,14 +591,6 @@ fn buildOutputType( }, else => unreachable, } - switch (arg_mode) { - .build => switch (output_mode) { - .Exe => emit_h = .no, - .Obj, .Lib => emit_h = .yes_default_path, - }, - .translate_c, .zig_test, .run => emit_h = .no, - else => unreachable, - } soname = .yes_default_value; const args = all_args[2..]; From 0771aac48b2e9acde348db9985c270abad329e39 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Mon, 23 Nov 2020 23:57:18 +1100 Subject: [PATCH 11/12] Don't use emit-h in test cases for other backends. --- src/test.zig | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/test.zig b/src/test.zig index aafd469585..6deee347af 100644 --- a/src/test.zig +++ b/src/test.zig @@ -564,10 +564,13 @@ pub const TestContext = struct { .directory = emit_directory, .basename = bin_name, }; - const emit_h: Compilation.EmitLoc = .{ - .directory = emit_directory, - .basename = "test_case.h", - }; + const emit_h: ?Compilation.EmitLoc = if (case.cbe) + .{ + .directory = emit_directory, + .basename = "test_case.h", + } + else + null; const comp = try Compilation.create(allocator, .{ .local_cache_directory = zig_cache_directory, .global_cache_directory = zig_cache_directory, From 9849e894d52dd7b9aedc149011cb19d2bccaab70 Mon Sep 17 00:00:00 2001 From: Alex Cameron Date: Sat, 19 Dec 2020 05:31:00 +0000 Subject: [PATCH 12/12] Add emit_h path to compilation hash. --- src/Compilation.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Compilation.zig b/src/Compilation.zig index 8f1c9ac29d..11521e5a52 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2956,6 +2956,9 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node man.hash.add(comp.bin_file.options.is_test); man.hash.add(comp.bin_file.options.emit != null); man.hash.add(comp.c_header != null); + if (comp.c_header) |header| { + man.hash.addEmitLoc(header.emit_loc.?); + } man.hash.addOptionalEmitLoc(comp.emit_asm); man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); man.hash.addOptionalEmitLoc(comp.emit_analysis);