authorgravatar for ascottcameron@gmail.comAlex Cameron <ascottcameron@gmail.com> 2020-11-13 00:50:02+11:00
committergravatar for ascottcameron@gmail.comAlex Cameron <ascottcameron@gmail.com> 2020-12-23 01:14:35+11:00
log40f0275e7cb7f3f4b2c382e5b457e714080c4cf2
tree87d919099376fe351c830fdb305d796e52d49b0b
parent43dbe86226fe89c6364fa0261297f1a9d8eb2a58

Implement emit-h


3 files changed, 183 insertions(+), 86 deletions(-)

src/Compilation.zig+58-19
......@@ -26,6 +26,8 @@ const Module = @import("Module.zig");
2626const Cache = @import("Cache.zig");
2727const stage1 = @import("stage1.zig");
2828const translate_c = @import("translate_c.zig");
29const c_codegen = @import("codegen/c.zig");
30const c_link = @import("link/C.zig");
2931const ThreadPool = @import("ThreadPool.zig");
3032const WaitGroup = @import("WaitGroup.zig");
3133
......@@ -131,6 +133,7 @@ emit_asm: ?EmitLoc,
131133emit_llvm_ir: ?EmitLoc,
132134emit_analysis: ?EmitLoc,
133135emit_docs: ?EmitLoc,
136c_header: ?c_link.Header,
134137
135138pub const InnerError = Module.InnerError;
136139
......@@ -895,10 +898,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
895898 };
896899 };
897900
898 if (!use_llvm and options.emit_h != null) {
899 fatal("TODO implement support for -femit-h in the self-hosted backend", .{});
900 }
901
902901 var system_libs: std.StringArrayHashMapUnmanaged(void) = .{};
903902 errdefer system_libs.deinit(gpa);
904903 try system_libs.ensureCapacity(gpa, options.system_libs.len);
......@@ -975,6 +974,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
975974 .global_cache_directory = options.global_cache_directory,
976975 .bin_file = bin_file,
977976 .emit_h = options.emit_h,
977 .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa) else null,
978978 .emit_asm = options.emit_asm,
979979 .emit_llvm_ir = options.emit_llvm_ir,
980980 .emit_analysis = options.emit_analysis,
......@@ -1286,6 +1286,20 @@ pub fn update(self: *Compilation) !void {
12861286 module.root_scope.unload(self.gpa);
12871287 }
12881288 }
1289
1290 // If we've chosen to emit a C header, flush the header to the disk.
1291 if (self.c_header) |header| {
1292 const header_path = self.emit_h.?;
1293 // If a directory has been provided, write the header there. Otherwise, just write it to the
1294 // cache directory.
1295 const header_dir = if (header_path.directory) |dir|
1296 dir.handle
1297 else
1298 self.local_cache_directory.handle;
1299 const header_file = try header_dir.createFile(header_path.basename, .{});
1300 defer header_file.close();
1301 try header.flush(header_file.writer());
1302 }
12891303}
12901304
12911305/// 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
13851399 var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len);
13861400 defer c_comp_progress_node.end();
13871401
1402 var arena = std.heap.ArenaAllocator.init(self.gpa);
1403 defer arena.deinit();
1404
13881405 var wg = WaitGroup{};
13891406 defer wg.wait();
13901407
......@@ -1432,22 +1449,44 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
14321449
14331450 assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits());
14341451
1435 self.bin_file.updateDecl(module, decl) catch |err| switch (err) {
1436 error.OutOfMemory => return error.OutOfMemory,
1437 error.AnalysisFail => {
1438 decl.analysis = .dependency_failure;
1439 },
1440 else => {
1441 try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
1442 module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
1443 module.gpa,
1444 decl.src(),
1445 "unable to codegen: {}",
1446 .{@errorName(err)},
1447 ));
1448 decl.analysis = .codegen_failure_retryable;
1449 },
1452 self.bin_file.updateDecl(module, decl) catch |err| {
1453 switch (err) {
1454 error.OutOfMemory => return error.OutOfMemory,
1455 error.AnalysisFail => {
1456 decl.analysis = .dependency_failure;
1457 },
1458 else => {
1459 try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
1460 module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
1461 module.gpa,
1462 decl.src(),
1463 "unable to codegen: {}",
1464 .{@errorName(err)},
1465 ));
1466 decl.analysis = .codegen_failure_retryable;
1467 },
1468 }
1469 return;
14501470 };
1471
1472 if (self.c_header) |*header| {
1473 c_codegen.generateHeader(&arena, module, &header.*, decl) catch |err| switch (err) {
1474 error.OutOfMemory => return error.OutOfMemory,
1475 error.AnalysisFail => {
1476 decl.analysis = .dependency_failure;
1477 },
1478 else => {
1479 try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
1480 module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
1481 module.gpa,
1482 decl.src(),
1483 "unable to generate C header: {}",
1484 .{@errorName(err)},
1485 ));
1486 decl.analysis = .codegen_failure_retryable;
1487 },
1488 };
1489 }
14511490 },
14521491 },
14531492 .analyze_decl => |decl| {
src/codegen/c.zig+83-47
......@@ -2,6 +2,7 @@ const std = @import("std");
22
33const link = @import("../link.zig");
44const Module = @import("../Module.zig");
5const Compilation = @import("../Compilation.zig");
56
67const Inst = @import("../ir.zig").Inst;
78const Value = @import("../value.zig").Value;
......@@ -19,7 +20,7 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
1920 return allocator.dupe(u8, name);
2021}
2122
22fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void {
23fn renderType(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, T: Type) !void {
2324 switch (T.zigTypeTag()) {
2425 .NoReturn => {
2526 try writer.writeAll("zig_noreturn void");
......@@ -27,16 +28,16 @@ fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void {
2728 .Void => try writer.writeAll("void"),
2829 .Int => {
2930 if (T.tag() == .u8) {
30 ctx.file.need_stdint = true;
31 header.need_stdint = true;
3132 try writer.writeAll("uint8_t");
3233 } else if (T.tag() == .usize) {
33 ctx.file.need_stddef = true;
34 header.need_stddef = true;
3435 try writer.writeAll("size_t");
3536 } else {
36 return ctx.file.fail(ctx.decl.src(), "TODO implement int types", .{});
37 return ctx.fail(ctx.decl.src(), "TODO implement int types", .{});
3738 }
3839 },
39 else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement type {}", .{e}),
40 else => |e| return ctx.fail(ctx.decl.src(), "TODO implement type {}", .{e}),
4041 }
4142}
4243
......@@ -47,13 +48,13 @@ fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Va
4748 return writer.print("{}", .{val.toSignedInt()});
4849 return writer.print("{}", .{val.toUnsignedInt()});
4950 },
50 else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement value {}", .{e}),
51 else => |e| return ctx.fail(ctx.decl.src(), "TODO implement value {}", .{e}),
5152 }
5253}
5354
54fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl: *Decl) !void {
55fn renderFunctionSignature(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, decl: *Decl) !void {
5556 const tv = decl.typed_value.most_recent.typed_value;
56 try renderType(ctx, writer, tv.ty.fnReturnType());
57 try renderType(ctx, header, writer, tv.ty.fnReturnType());
5758 // Use the child allocator directly, as we know the name can be freed before
5859 // the rest of the arena.
5960 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
6869 if (index > 0) {
6970 try writer.writeAll(", ");
7071 }
71 try renderType(ctx, writer, tv.ty.fnParamType(index));
72 try renderType(ctx, header, writer, tv.ty.fnParamType(index));
7273 try writer.print(" arg{}", .{index});
7374 }
7475 }
......@@ -83,6 +84,34 @@ pub fn generate(file: *C, decl: *Decl) !void {
8384 }
8485}
8586
87pub fn generateHeader(
88 arena: *std.heap.ArenaAllocator,
89 module: *Module,
90 header: *C.Header,
91 decl: *Decl,
92) error{ AnalysisFail, OutOfMemory }!void {
93 switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
94 .Fn => {
95 var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator);
96 defer inst_map.deinit();
97 var ctx = Context{
98 .decl = decl,
99 .arena = arena,
100 .inst_map = &inst_map,
101 };
102 const writer = header.buf.writer();
103 renderFunctionSignature(&ctx, header, writer, decl) catch |err| {
104 if (err == error.AnalysisFail) {
105 try module.failed_decls.put(module.gpa, decl, ctx.error_msg);
106 }
107 return err;
108 };
109 try writer.writeAll(";\n");
110 },
111 else => {},
112 }
113}
114
86115fn genArray(file: *C, decl: *Decl) !void {
87116 const tv = decl.typed_value.most_recent.typed_value;
88117 // TODO: prevent inline asm constants from being emitted
......@@ -102,12 +131,12 @@ fn genArray(file: *C, decl: *Decl) !void {
102131}
103132
104133const Context = struct {
105 file: *C,
106134 decl: *Decl,
107135 inst_map: *std.AutoHashMap(*Inst, []u8),
108136 arena: *std.heap.ArenaAllocator,
109137 argdex: usize = 0,
110138 unnamed_index: usize = 0,
139 error_msg: *Compilation.ErrorMsg = undefined,
111140
112141 fn resolveInst(self: *Context, inst: *Inst) ![]u8 {
113142 if (inst.cast(Inst.Constant)) |const_inst| {
......@@ -127,6 +156,11 @@ const Context = struct {
127156 return val;
128157 }
129158
159 fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
160 self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args);
161 return error.AnalysisFail;
162 }
163
130164 fn deinit(self: *Context) void {
131165 self.* = undefined;
132166 }
......@@ -141,14 +175,16 @@ fn genFn(file: *C, decl: *Decl) !void {
141175 var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator);
142176 defer inst_map.deinit();
143177 var ctx = Context{
144 .file = file,
145178 .decl = decl,
146179 .arena = &arena,
147180 .inst_map = &inst_map,
148181 };
149 defer ctx.deinit();
182 defer {
183 file.error_msg = ctx.error_msg;
184 ctx.deinit();
185 }
150186
151 try renderFunctionSignature(&ctx, writer, decl);
187 try renderFunctionSignature(&ctx, &file.header, writer, decl);
152188
153189 try writer.writeAll(" {");
154190
......@@ -158,18 +194,18 @@ fn genFn(file: *C, decl: *Decl) !void {
158194 try writer.writeAll("\n");
159195 for (instructions) |inst| {
160196 if (switch (inst.tag) {
161 .assembly => try genAsm(&ctx, inst.castTag(.assembly).?),
162 .call => try genCall(&ctx, inst.castTag(.call).?),
163 .add => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "+"),
164 .sub => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "-"),
197 .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?),
198 .call => try genCall(&ctx, file, inst.castTag(.call).?),
199 .add => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "+"),
200 .sub => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "-"),
165201 .ret => try genRet(&ctx, inst.castTag(.ret).?),
166 .retvoid => try genRetVoid(&ctx),
202 .retvoid => try genRetVoid(file),
167203 .arg => try genArg(&ctx),
168204 .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),
169205 .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?),
170 .unreach => try genUnreach(&ctx, inst.castTag(.unreach).?),
171 .intcast => try genIntCast(&ctx, inst.castTag(.intcast).?),
172 else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
206 .unreach => try genUnreach(file, inst.castTag(.unreach).?),
207 .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?),
208 else => |e| return ctx.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
173209 }) |name| {
174210 try ctx.inst_map.putNoClobber(inst, name);
175211 }
......@@ -185,46 +221,46 @@ fn genArg(ctx: *Context) !?[]u8 {
185221 return name;
186222}
187223
188fn genRetVoid(ctx: *Context) !?[]u8 {
189 try ctx.file.main.writer().print(indentation ++ "return;\n", .{});
224fn genRetVoid(file: *C) !?[]u8 {
225 try file.main.writer().print(indentation ++ "return;\n", .{});
190226 return null;
191227}
192228
193229fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
194 return ctx.file.fail(ctx.decl.src(), "TODO return", .{});
230 return ctx.fail(ctx.decl.src(), "TODO return", .{});
195231}
196232
197fn genIntCast(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
233fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
198234 if (inst.base.isUnused())
199235 return null;
200236 const op = inst.operand;
201 const writer = ctx.file.main.writer();
237 const writer = file.main.writer();
202238 const name = try ctx.name();
203239 const from = try ctx.resolveInst(inst.operand);
204240 try writer.writeAll(indentation ++ "const ");
205 try renderType(ctx, writer, inst.base.ty);
241 try renderType(ctx, &file.header, writer, inst.base.ty);
206242 try writer.print(" {} = (", .{name});
207 try renderType(ctx, writer, inst.base.ty);
243 try renderType(ctx, &file.header, writer, inst.base.ty);
208244 try writer.print("){};\n", .{from});
209245 return name;
210246}
211247
212fn genBinOp(ctx: *Context, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 {
248fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 {
213249 if (inst.base.isUnused())
214250 return null;
215251 const lhs = ctx.resolveInst(inst.lhs);
216252 const rhs = ctx.resolveInst(inst.rhs);
217 const writer = ctx.file.main.writer();
253 const writer = file.main.writer();
218254 const name = try ctx.name();
219255 try writer.writeAll(indentation ++ "const ");
220 try renderType(ctx, writer, inst.base.ty);
256 try renderType(ctx, &file.header, writer, inst.base.ty);
221257 try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs });
222258 return name;
223259}
224260
225fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
226 const writer = ctx.file.main.writer();
227 const header = ctx.file.header.writer();
261fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
262 const writer = file.main.writer();
263 const header = file.header.buf.writer();
228264 try writer.writeAll(indentation);
229265 if (inst.func.castTag(.constant)) |func_inst| {
230266 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
......@@ -235,9 +271,9 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
235271 try writer.print("(void)", .{});
236272 }
237273 const tname = mem.spanZ(target.name);
238 if (ctx.file.called.get(tname) == null) {
239 try ctx.file.called.put(tname, void{});
240 try renderFunctionSignature(ctx, header, target);
274 if (file.called.get(tname) == null) {
275 try file.called.put(tname, void{});
276 try renderFunctionSignature(ctx, &file.header, header, target);
241277 try header.writeAll(";\n");
242278 }
243279 try writer.print("{}(", .{tname});
......@@ -256,10 +292,10 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
256292 }
257293 try writer.writeAll(");\n");
258294 } else {
259 return ctx.file.fail(ctx.decl.src(), "TODO non-function call target?", .{});
295 return ctx.fail(ctx.decl.src(), "TODO non-function call target?", .{});
260296 }
261297 } else {
262 return ctx.file.fail(ctx.decl.src(), "TODO non-constant call inst?", .{});
298 return ctx.fail(ctx.decl.src(), "TODO non-constant call inst?", .{});
263299 }
264300 return null;
265301}
......@@ -274,20 +310,20 @@ fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
274310 return null;
275311}
276312
277fn genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
278 try ctx.file.main.writer().writeAll(indentation ++ "zig_unreachable();\n");
313fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 {
314 try file.main.writer().writeAll(indentation ++ "zig_unreachable();\n");
279315 return null;
280316}
281317
282fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
283 const writer = ctx.file.main.writer();
318fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 {
319 const writer = file.main.writer();
284320 try writer.writeAll(indentation);
285321 for (as.inputs) |i, index| {
286322 if (i[0] == '{' and i[i.len - 1] == '}') {
287323 const reg = i[1 .. i.len - 1];
288324 const arg = as.args[index];
289325 try writer.writeAll("register ");
290 try renderType(ctx, writer, arg.ty);
326 try renderType(ctx, &file.header, writer, arg.ty);
291327 try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg });
292328 // TODO merge constant handling into inst_map as well
293329 if (arg.castTag(.constant)) |c| {
......@@ -296,17 +332,17 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
296332 } else {
297333 const gop = try ctx.inst_map.getOrPut(arg);
298334 if (!gop.found_existing) {
299 return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
335 return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
300336 }
301337 try writer.print("{};\n ", .{gop.entry.value});
302338 }
303339 } else {
304 return ctx.file.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{});
340 return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{});
305341 }
306342 }
307343 try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
308344 if (as.output) |o| {
309 return ctx.file.fail(ctx.decl.src(), "TODO inline asm output", .{});
345 return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{});
310346 }
311347 if (as.inputs.len > 0) {
312348 if (as.output == null) {
src/link/C.zig+42-20
......@@ -13,15 +13,52 @@ const C = @This();
1313
1414pub const base_tag: File.Tag = .c;
1515
16pub const Header = struct {
17 buf: std.ArrayList(u8),
18 need_stddef: bool = false,
19 need_stdint: bool = false,
20
21 pub fn init(allocator: *Allocator) Header {
22 return .{
23 .buf = std.ArrayList(u8).init(allocator),
24 };
25 }
26
27 pub fn flush(self: *const Header, writer: anytype) !void {
28 const tracy = trace(@src());
29 defer tracy.end();
30
31 try writer.writeAll(@embedFile("cbe.h"));
32 var includes = false;
33 if (self.need_stddef) {
34 try writer.writeAll("#include <stddef.h>\n");
35 includes = true;
36 }
37 if (self.need_stdint) {
38 try writer.writeAll("#include <stdint.h>\n");
39 includes = true;
40 }
41 if (includes) {
42 try writer.writeByte('\n');
43 }
44 if (self.buf.items.len > 0) {
45 try writer.print("{}\n", .{self.buf.items});
46 }
47 }
48
49 pub fn deinit(self: *Header) void {
50 self.buf.deinit();
51 self.* = undefined;
52 }
53};
54
1655base: File,
1756
18header: std.ArrayList(u8),
57header: Header,
1958constants: std.ArrayList(u8),
2059main: std.ArrayList(u8),
2160
2261called: std.StringHashMap(void),
23need_stddef: bool = false,
24need_stdint: bool = false,
2562error_msg: *Compilation.ErrorMsg = undefined,
2663
2764pub 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
4481 .allocator = allocator,
4582 },
4683 .main = std.ArrayList(u8).init(allocator),
47 .header = std.ArrayList(u8).init(allocator),
84 .header = Header.init(allocator),
4885 .constants = std.ArrayList(u8).init(allocator),
4986 .called = std.StringHashMap(void).init(allocator),
5087 };
......@@ -82,22 +119,7 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
82119 defer tracy.end();
83120
84121 const writer = self.base.file.?.writer();
85 try writer.writeAll(@embedFile("cbe.h"));
86 var includes = false;
87 if (self.need_stddef) {
88 try writer.writeAll("#include <stddef.h>\n");
89 includes = true;
90 }
91 if (self.need_stdint) {
92 try writer.writeAll("#include <stdint.h>\n");
93 includes = true;
94 }
95 if (includes) {
96 try writer.writeByte('\n');
97 }
98 if (self.header.items.len > 0) {
99 try writer.print("{}\n", .{self.header.items});
100 }
122 try self.header.flush(writer);
101123 if (self.constants.items.len > 0) {
102124 try writer.print("{}\n", .{self.constants.items});
103125 }