Commit 40f0275e7c

Alex Cameron <ascottcameron@gmail.com>
2020-11-12 14:50:02
Implement emit-h
1 parent 43dbe86
Changed files (3)
src
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) {
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 <stddef.h>\n");
+            includes = true;
+        }
+        if (self.need_stdint) {
+            try writer.writeAll("#include <stdint.h>\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 <stddef.h>\n");
-        includes = true;
-    }
-    if (self.need_stdint) {
-        try writer.writeAll("#include <stdint.h>\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});
     }
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| {