authorgravatar for git@vexu.euVeikka Tuominen <git@vexu.eu> 2020-12-23 00:01:22+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-12-23 00:01:22+02:00
log03113d92467a99658c2606a21821b28b19dfdded
tree9897504c15a08c1ef9165bee3b3299274eb171c9
parentba2f2e139306618b8beaa8e833b1f9845309df73
parent9849e894d52dd7b9aedc149011cb19d2bccaab70
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #7111 from tetsuo-cpp/emit-h

Implement emit-h

7 files changed, 328 insertions(+), 104 deletions(-)

src/Compilation.zig+69-24
......@@ -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
......@@ -126,12 +128,13 @@ test_filter: ?[]const u8,
126128test_name_prefix: ?[]const u8,
127129test_evented_io: bool,
128130
129emit_h: ?EmitLoc,
130131emit_asm: ?EmitLoc,
131132emit_llvm_ir: ?EmitLoc,
132133emit_analysis: ?EmitLoc,
133134emit_docs: ?EmitLoc,
134135
136c_header: ?c_link.Header,
137
135138pub const InnerError = Module.InnerError;
136139
137140pub const CRTFile = struct {
......@@ -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);
......@@ -974,7 +973,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
974973 .local_cache_directory = options.local_cache_directory,
975974 .global_cache_directory = options.global_cache_directory,
976975 .bin_file = bin_file,
977 .emit_h = options.emit_h,
976 .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa, options.emit_h) else null,
978977 .emit_asm = options.emit_asm,
979978 .emit_llvm_ir = options.emit_llvm_ir,
980979 .emit_analysis = options.emit_analysis,
......@@ -1185,6 +1184,10 @@ pub fn destroy(self: *Compilation) void {
11851184 }
11861185 self.failed_c_objects.deinit(gpa);
11871186
1187 if (self.c_header) |*header| {
1188 header.deinit();
1189 }
1190
11881191 self.cache_parent.manifest_dir.close();
11891192 if (self.owned_link_dir) |*dir| dir.close();
11901193
......@@ -1286,6 +1289,20 @@ pub fn update(self: *Compilation) !void {
12861289 module.root_scope.unload(self.gpa);
12871290 }
12881291 }
1292
1293 // If we've chosen to emit a C header, flush the header to the disk.
1294 if (self.c_header) |header| {
1295 const header_path = header.emit_loc.?;
1296 // If a directory has been provided, write the header there. Otherwise, just write it to the
1297 // cache directory.
1298 const header_dir = if (header_path.directory) |dir|
1299 dir.handle
1300 else
1301 self.local_cache_directory.handle;
1302 const header_file = try header_dir.createFile(header_path.basename, .{});
1303 defer header_file.close();
1304 try header.flush(header_file.writer());
1305 }
12891306}
12901307
12911308/// Having the file open for writing is problematic as far as executing the
......@@ -1385,6 +1402,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
13851402 var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len);
13861403 defer c_comp_progress_node.end();
13871404
1405 var arena = std.heap.ArenaAllocator.init(self.gpa);
1406 defer arena.deinit();
1407
13881408 var wg = WaitGroup{};
13891409 defer wg.wait();
13901410
......@@ -1432,22 +1452,44 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
14321452
14331453 assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits());
14341454
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 },
1455 self.bin_file.updateDecl(module, decl) catch |err| {
1456 switch (err) {
1457 error.OutOfMemory => return error.OutOfMemory,
1458 error.AnalysisFail => {
1459 decl.analysis = .dependency_failure;
1460 },
1461 else => {
1462 try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
1463 module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
1464 module.gpa,
1465 decl.src(),
1466 "unable to codegen: {}",
1467 .{@errorName(err)},
1468 ));
1469 decl.analysis = .codegen_failure_retryable;
1470 },
1471 }
1472 return;
14501473 };
1474
1475 if (self.c_header) |*header| {
1476 c_codegen.generateHeader(&arena, module, &header.*, decl) catch |err| switch (err) {
1477 error.OutOfMemory => return error.OutOfMemory,
1478 error.AnalysisFail => {
1479 decl.analysis = .dependency_failure;
1480 },
1481 else => {
1482 try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
1483 module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
1484 module.gpa,
1485 decl.src(),
1486 "unable to generate C header: {}",
1487 .{@errorName(err)},
1488 ));
1489 decl.analysis = .codegen_failure_retryable;
1490 },
1491 };
1492 }
14511493 },
14521494 },
14531495 .analyze_decl => |decl| {
......@@ -2913,7 +2955,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
29132955 man.hash.add(comp.bin_file.options.function_sections);
29142956 man.hash.add(comp.bin_file.options.is_test);
29152957 man.hash.add(comp.bin_file.options.emit != null);
2916 man.hash.addOptionalEmitLoc(comp.emit_h);
2958 man.hash.add(comp.c_header != null);
2959 if (comp.c_header) |header| {
2960 man.hash.addEmitLoc(header.emit_loc.?);
2961 }
29172962 man.hash.addOptionalEmitLoc(comp.emit_asm);
29182963 man.hash.addOptionalEmitLoc(comp.emit_llvm_ir);
29192964 man.hash.addOptionalEmitLoc(comp.emit_analysis);
......@@ -3012,10 +3057,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
30123057 });
30133058 break :blk try directory.join(arena, &[_][]const u8{bin_basename});
30143059 } else "";
3015 if (comp.emit_h != null) {
3060 if (comp.c_header != null) {
30163061 log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{});
30173062 }
3018 const emit_h_path = try stage1LocPath(arena, comp.emit_h, directory);
3063 const emit_h_path = try stage1LocPath(arena, if (comp.c_header) |header| header.emit_loc else null, directory);
30193064 const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);
30203065 const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);
30213066 const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory);
src/codegen/c.zig+87-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,24 +20,28 @@ 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");
2627 },
2728 .Void => try writer.writeAll("void"),
29 .Bool => try writer.writeAll("bool"),
2830 .Int => {
2931 if (T.tag() == .u8) {
30 ctx.file.need_stdint = true;
32 header.need_stdint = true;
3133 try writer.writeAll("uint8_t");
34 } else if (T.tag() == .u32) {
35 header.need_stdint = true;
36 try writer.writeAll("uint32_t");
3237 } else if (T.tag() == .usize) {
33 ctx.file.need_stddef = true;
38 header.need_stddef = true;
3439 try writer.writeAll("size_t");
3540 } else {
36 return ctx.file.fail(ctx.decl.src(), "TODO implement int types", .{});
41 return ctx.fail(ctx.decl.src(), "TODO implement int type {}", .{T});
3742 }
3843 },
39 else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement type {}", .{e}),
44 else => |e| return ctx.fail(ctx.decl.src(), "TODO implement type {}", .{e}),
4045 }
4146}
4247
......@@ -47,13 +52,13 @@ fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Va
4752 return writer.print("{}", .{val.toSignedInt()});
4853 return writer.print("{}", .{val.toUnsignedInt()});
4954 },
50 else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement value {}", .{e}),
55 else => |e| return ctx.fail(ctx.decl.src(), "TODO implement value {}", .{e}),
5156 }
5257}
5358
54fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl: *Decl) !void {
59fn renderFunctionSignature(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, decl: *Decl) !void {
5560 const tv = decl.typed_value.most_recent.typed_value;
56 try renderType(ctx, writer, tv.ty.fnReturnType());
61 try renderType(ctx, header, writer, tv.ty.fnReturnType());
5762 // Use the child allocator directly, as we know the name can be freed before
5863 // the rest of the arena.
5964 const name = try map(ctx.arena.child_allocator, mem.spanZ(decl.name));
......@@ -68,7 +73,7 @@ fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl
6873 if (index > 0) {
6974 try writer.writeAll(", ");
7075 }
71 try renderType(ctx, writer, tv.ty.fnParamType(index));
76 try renderType(ctx, header, writer, tv.ty.fnParamType(index));
7277 try writer.print(" arg{}", .{index});
7378 }
7479 }
......@@ -83,6 +88,34 @@ pub fn generate(file: *C, decl: *Decl) !void {
8388 }
8489}
8590
91pub fn generateHeader(
92 arena: *std.heap.ArenaAllocator,
93 module: *Module,
94 header: *C.Header,
95 decl: *Decl,
96) error{ AnalysisFail, OutOfMemory }!void {
97 switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
98 .Fn => {
99 var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator);
100 defer inst_map.deinit();
101 var ctx = Context{
102 .decl = decl,
103 .arena = arena,
104 .inst_map = &inst_map,
105 };
106 const writer = header.buf.writer();
107 renderFunctionSignature(&ctx, header, writer, decl) catch |err| {
108 if (err == error.AnalysisFail) {
109 try module.failed_decls.put(module.gpa, decl, ctx.error_msg);
110 }
111 return err;
112 };
113 try writer.writeAll(";\n");
114 },
115 else => {},
116 }
117}
118
86119fn genArray(file: *C, decl: *Decl) !void {
87120 const tv = decl.typed_value.most_recent.typed_value;
88121 // TODO: prevent inline asm constants from being emitted
......@@ -102,12 +135,12 @@ fn genArray(file: *C, decl: *Decl) !void {
102135}
103136
104137const Context = struct {
105 file: *C,
106138 decl: *Decl,
107139 inst_map: *std.AutoHashMap(*Inst, []u8),
108140 arena: *std.heap.ArenaAllocator,
109141 argdex: usize = 0,
110142 unnamed_index: usize = 0,
143 error_msg: *Compilation.ErrorMsg = undefined,
111144
112145 fn resolveInst(self: *Context, inst: *Inst) ![]u8 {
113146 if (inst.cast(Inst.Constant)) |const_inst| {
......@@ -127,6 +160,11 @@ const Context = struct {
127160 return val;
128161 }
129162
163 fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
164 self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args);
165 return error.AnalysisFail;
166 }
167
130168 fn deinit(self: *Context) void {
131169 self.* = undefined;
132170 }
......@@ -141,14 +179,16 @@ fn genFn(file: *C, decl: *Decl) !void {
141179 var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator);
142180 defer inst_map.deinit();
143181 var ctx = Context{
144 .file = file,
145182 .decl = decl,
146183 .arena = &arena,
147184 .inst_map = &inst_map,
148185 };
149 defer ctx.deinit();
186 defer {
187 file.error_msg = ctx.error_msg;
188 ctx.deinit();
189 }
150190
151 try renderFunctionSignature(&ctx, writer, decl);
191 try renderFunctionSignature(&ctx, &file.header, writer, decl);
152192
153193 try writer.writeAll(" {");
154194
......@@ -158,18 +198,18 @@ fn genFn(file: *C, decl: *Decl) !void {
158198 try writer.writeAll("\n");
159199 for (instructions) |inst| {
160200 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).?, "-"),
201 .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?),
202 .call => try genCall(&ctx, file, inst.castTag(.call).?),
203 .add => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "+"),
204 .sub => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "-"),
165205 .ret => try genRet(&ctx, inst.castTag(.ret).?),
166 .retvoid => try genRetVoid(&ctx),
206 .retvoid => try genRetVoid(file),
167207 .arg => try genArg(&ctx),
168208 .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),
169209 .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}),
210 .unreach => try genUnreach(file, inst.castTag(.unreach).?),
211 .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?),
212 else => |e| return ctx.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
173213 }) |name| {
174214 try ctx.inst_map.putNoClobber(inst, name);
175215 }
......@@ -185,46 +225,46 @@ fn genArg(ctx: *Context) !?[]u8 {
185225 return name;
186226}
187227
188fn genRetVoid(ctx: *Context) !?[]u8 {
189 try ctx.file.main.writer().print(indentation ++ "return;\n", .{});
228fn genRetVoid(file: *C) !?[]u8 {
229 try file.main.writer().print(indentation ++ "return;\n", .{});
190230 return null;
191231}
192232
193233fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
194 return ctx.file.fail(ctx.decl.src(), "TODO return", .{});
234 return ctx.fail(ctx.decl.src(), "TODO return", .{});
195235}
196236
197fn genIntCast(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
237fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
198238 if (inst.base.isUnused())
199239 return null;
200240 const op = inst.operand;
201 const writer = ctx.file.main.writer();
241 const writer = file.main.writer();
202242 const name = try ctx.name();
203243 const from = try ctx.resolveInst(inst.operand);
204244 try writer.writeAll(indentation ++ "const ");
205 try renderType(ctx, writer, inst.base.ty);
245 try renderType(ctx, &file.header, writer, inst.base.ty);
206246 try writer.print(" {} = (", .{name});
207 try renderType(ctx, writer, inst.base.ty);
247 try renderType(ctx, &file.header, writer, inst.base.ty);
208248 try writer.print("){};\n", .{from});
209249 return name;
210250}
211251
212fn genBinOp(ctx: *Context, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 {
252fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 {
213253 if (inst.base.isUnused())
214254 return null;
215255 const lhs = ctx.resolveInst(inst.lhs);
216256 const rhs = ctx.resolveInst(inst.rhs);
217 const writer = ctx.file.main.writer();
257 const writer = file.main.writer();
218258 const name = try ctx.name();
219259 try writer.writeAll(indentation ++ "const ");
220 try renderType(ctx, writer, inst.base.ty);
260 try renderType(ctx, &file.header, writer, inst.base.ty);
221261 try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs });
222262 return name;
223263}
224264
225fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
226 const writer = ctx.file.main.writer();
227 const header = ctx.file.header.writer();
265fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
266 const writer = file.main.writer();
267 const header = file.header.buf.writer();
228268 try writer.writeAll(indentation);
229269 if (inst.func.castTag(.constant)) |func_inst| {
230270 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
......@@ -235,9 +275,9 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
235275 try writer.print("(void)", .{});
236276 }
237277 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);
278 if (file.called.get(tname) == null) {
279 try file.called.put(tname, void{});
280 try renderFunctionSignature(ctx, &file.header, header, target);
241281 try header.writeAll(";\n");
242282 }
243283 try writer.print("{}(", .{tname});
......@@ -256,10 +296,10 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
256296 }
257297 try writer.writeAll(");\n");
258298 } else {
259 return ctx.file.fail(ctx.decl.src(), "TODO non-function call target?", .{});
299 return ctx.fail(ctx.decl.src(), "TODO non-function call target?", .{});
260300 }
261301 } else {
262 return ctx.file.fail(ctx.decl.src(), "TODO non-constant call inst?", .{});
302 return ctx.fail(ctx.decl.src(), "TODO non-constant call inst?", .{});
263303 }
264304 return null;
265305}
......@@ -274,20 +314,20 @@ fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
274314 return null;
275315}
276316
277fn genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
278 try ctx.file.main.writer().writeAll(indentation ++ "zig_unreachable();\n");
317fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 {
318 try file.main.writer().writeAll(indentation ++ "zig_unreachable();\n");
279319 return null;
280320}
281321
282fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
283 const writer = ctx.file.main.writer();
322fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 {
323 const writer = file.main.writer();
284324 try writer.writeAll(indentation);
285325 for (as.inputs) |i, index| {
286326 if (i[0] == '{' and i[i.len - 1] == '}') {
287327 const reg = i[1 .. i.len - 1];
288328 const arg = as.args[index];
289329 try writer.writeAll("register ");
290 try renderType(ctx, writer, arg.ty);
330 try renderType(ctx, &file.header, writer, arg.ty);
291331 try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg });
292332 // TODO merge constant handling into inst_map as well
293333 if (arg.castTag(.constant)) |c| {
......@@ -296,17 +336,17 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
296336 } else {
297337 const gop = try ctx.inst_map.getOrPut(arg);
298338 if (!gop.found_existing) {
299 return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
339 return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
300340 }
301341 try writer.print("{};\n ", .{gop.entry.value});
302342 }
303343 } else {
304 return ctx.file.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{});
344 return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{});
305345 }
306346 }
307347 try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
308348 if (as.output) |o| {
309 return ctx.file.fail(ctx.decl.src(), "TODO inline asm output", .{});
349 return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{});
310350 }
311351 if (as.inputs.len > 0) {
312352 if (as.output == null) {
src/link/C.zig+45-18
......@@ -13,15 +13,54 @@ 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 emit_loc: ?Compilation.EmitLoc,
21
22 pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header {
23 return .{
24 .buf = std.ArrayList(u8).init(allocator),
25 .emit_loc = emit_loc,
26 };
27 }
28
29 pub fn flush(self: *const Header, writer: anytype) !void {
30 const tracy = trace(@src());
31 defer tracy.end();
32
33 try writer.writeAll(@embedFile("cbe.h"));
34 var includes = false;
35 if (self.need_stddef) {
36 try writer.writeAll("#include <stddef.h>\n");
37 includes = true;
38 }
39 if (self.need_stdint) {
40 try writer.writeAll("#include <stdint.h>\n");
41 includes = true;
42 }
43 if (includes) {
44 try writer.writeByte('\n');
45 }
46 if (self.buf.items.len > 0) {
47 try writer.print("{}", .{self.buf.items});
48 }
49 }
50
51 pub fn deinit(self: *Header) void {
52 self.buf.deinit();
53 self.* = undefined;
54 }
55};
56
1657base: File,
1758
18header: std.ArrayList(u8),
59header: Header,
1960constants: std.ArrayList(u8),
2061main: std.ArrayList(u8),
2162
2263called: std.StringHashMap(void),
23need_stddef: bool = false,
24need_stdint: bool = false,
2564error_msg: *Compilation.ErrorMsg = undefined,
2665
2766pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C {
......@@ -44,7 +83,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
4483 .allocator = allocator,
4584 },
4685 .main = std.ArrayList(u8).init(allocator),
47 .header = std.ArrayList(u8).init(allocator),
86 .header = Header.init(allocator, null),
4887 .constants = std.ArrayList(u8).init(allocator),
4988 .called = std.StringHashMap(void).init(allocator),
5089 };
......@@ -82,22 +121,10 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
82121 defer tracy.end();
83122
84123 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) {
124 try self.header.flush(writer);
125 if (self.header.buf.items.len > 0) {
96126 try writer.writeByte('\n');
97127 }
98 if (self.header.items.len > 0) {
99 try writer.print("{}\n", .{self.header.items});
100 }
101128 if (self.constants.items.len > 0) {
102129 try writer.print("{}\n", .{self.constants.items});
103130 }
src/link/cbe.h+10
......@@ -1,3 +1,12 @@
1#if __STDC_VERSION__ >= 199901L
2// C99 or newer
3#include <stdbool.h>
4#else
5#define bool unsigned char
6#define true 1
7#define false 0
8#endif
9
110#if __STDC_VERSION__ >= 201112L
211#define zig_noreturn _Noreturn
312#elif __GNUC__
......@@ -13,3 +22,4 @@
1322#else
1423#define zig_unreachable()
1524#endif
25
src/main.zig+1-11
......@@ -490,7 +490,7 @@ fn buildOutputType(
490490 var target_dynamic_linker: ?[]const u8 = null;
491491 var target_ofmt: ?[]const u8 = null;
492492 var output_mode: std.builtin.OutputMode = undefined;
493 var emit_h: Emit = undefined;
493 var emit_h: Emit = .no;
494494 var soname: SOName = undefined;
495495 var ensure_libc_on_non_freestanding = false;
496496 var ensure_libcpp_on_non_freestanding = false;
......@@ -594,16 +594,6 @@ fn buildOutputType(
594594 },
595595 else => unreachable,
596596 }
597 // TODO finish self-hosted and add support for emitting C header files
598 emit_h = .no;
599 //switch (arg_mode) {
600 // .build => switch (output_mode) {
601 // .Exe => emit_h = .no,
602 // .Obj, .Lib => emit_h = .yes_default_path,
603 // },
604 // .translate_c, .zig_test, .run => emit_h = .no,
605 // else => unreachable,
606 //}
607597
608598 soname = .yes_default_value;
609599 const args = all_args[2..];
src/test.zig+45-4
......@@ -96,6 +96,10 @@ pub const TestContext = struct {
9696 /// stdout against the expected results
9797 /// This is a slice containing the expected message.
9898 Execution: []const u8,
99 /// A header update compiles the input with the equivalent of
100 /// `-femit-h` and tests the produced header against the
101 /// expected result
102 Header: []const u8,
99103 },
100104 };
101105
......@@ -138,6 +142,15 @@ pub const TestContext = struct {
138142 }) catch unreachable;
139143 }
140144
145 /// Adds a subcase in which the module is updated with `src`, and a C
146 /// header is generated.
147 pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void {
148 self.updates.append(.{
149 .src = src,
150 .case = .{ .Header = result },
151 }) catch unreachable;
152 }
153
141154 /// Adds a subcase in which the module is updated with `src`, compiled,
142155 /// run, and the output is tested against `result`.
143156 pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {
......@@ -269,6 +282,10 @@ pub const TestContext = struct {
269282 ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out);
270283 }
271284
285 pub fn h(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
286 ctx.addC(name, target, .Zig).addHeader(src, cheader ++ out);
287 }
288
272289 pub fn addCompareOutput(
273290 ctx: *TestContext,
274291 name: []const u8,
......@@ -547,6 +564,13 @@ pub const TestContext = struct {
547564 .directory = emit_directory,
548565 .basename = bin_name,
549566 };
567 const emit_h: ?Compilation.EmitLoc = if (case.cbe)
568 .{
569 .directory = emit_directory,
570 .basename = "test_case.h",
571 }
572 else
573 null;
550574 const comp = try Compilation.create(allocator, .{
551575 .local_cache_directory = zig_cache_directory,
552576 .global_cache_directory = zig_cache_directory,
......@@ -561,6 +585,7 @@ pub const TestContext = struct {
561585 // TODO: support testing optimizations
562586 .optimize_mode = .Debug,
563587 .emit_bin = emit_bin,
588 .emit_h = emit_h,
564589 .root_pkg = &root_pkg,
565590 .keep_source_files_loaded = true,
566591 .object_format = ofmt,
......@@ -616,6 +641,22 @@ pub const TestContext = struct {
616641 }
617642
618643 switch (update.case) {
644 .Header => |expected_output| {
645 var file = try tmp.dir.openFile("test_case.h", .{ .read = true });
646 defer file.close();
647 var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read headeroutput!");
648
649 if (expected_output.len != out.len) {
650 std.debug.print("\nTransformed header length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
651 std.process.exit(1);
652 }
653 for (expected_output) |e, i| {
654 if (out[i] != e) {
655 std.debug.print("\nTransformed header differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
656 std.process.exit(1);
657 }
658 }
659 },
619660 .Transformation => |expected_output| {
620661 if (case.cbe) {
621662 // The C file is always closed after an update, because we don't support
......@@ -670,8 +711,8 @@ pub const TestContext = struct {
670711 test_node.activate();
671712 defer test_node.end();
672713 var handled_errors = try arena.alloc(bool, e.len);
673 for (handled_errors) |*h| {
674 h.* = false;
714 for (handled_errors) |*handled| {
715 handled.* = false;
675716 }
676717 var all_errors = try comp.getAllErrorsAlloc();
677718 defer all_errors.deinit(allocator);
......@@ -709,8 +750,8 @@ pub const TestContext = struct {
709750 }
710751 }
711752
712 for (handled_errors) |h, i| {
713 if (!h) {
753 for (handled_errors) |handled, i| {
754 if (!handled) {
714755 const er = e[i];
715756 std.debug.print(
716757 "{s}\nDid not receive error:\n================\n{}\n================\nTest failed.\n",
test/stage2/cbe.zig+71
......@@ -19,6 +19,12 @@ pub fn addCases(ctx: *TestContext) !void {
1919 \\}
2020 \\
2121 );
22 ctx.h("simple header", linux_x64,
23 \\export fn start() void{}
24 ,
25 \\void start(void);
26 \\
27 );
2228 ctx.c("less empty start function", linux_x64,
2329 \\fn main() noreturn {
2430 \\ unreachable;
......@@ -243,4 +249,69 @@ pub fn addCases(ctx: *TestContext) !void {
243249 \\}
244250 \\
245251 );
252 ctx.h("header with single param function", linux_x64,
253 \\export fn start(a: u8) void{}
254 ,
255 \\#include <stdint.h>
256 \\
257 \\void start(uint8_t arg0);
258 \\
259 );
260 ctx.h("header with multiple param function", linux_x64,
261 \\export fn start(a: u8, b: u8, c: u8) void{}
262 ,
263 \\#include <stdint.h>
264 \\
265 \\void start(uint8_t arg0, uint8_t arg1, uint8_t arg2);
266 \\
267 );
268 ctx.h("header with u32 param function", linux_x64,
269 \\export fn start(a: u32) void{}
270 ,
271 \\#include <stdint.h>
272 \\
273 \\void start(uint32_t arg0);
274 \\
275 );
276 ctx.h("header with usize param function", linux_x64,
277 \\export fn start(a: usize) void{}
278 ,
279 \\#include <stddef.h>
280 \\
281 \\void start(size_t arg0);
282 \\
283 );
284 ctx.h("header with bool param function", linux_x64,
285 \\export fn start(a: bool) void{}
286 ,
287 \\void start(bool arg0);
288 \\
289 );
290 ctx.h("header with noreturn function", linux_x64,
291 \\export fn start() noreturn {
292 \\ unreachable;
293 \\}
294 ,
295 \\zig_noreturn void start(void);
296 \\
297 );
298 ctx.h("header with multiple functions", linux_x64,
299 \\export fn a() void{}
300 \\export fn b() void{}
301 \\export fn c() void{}
302 ,
303 \\void a(void);
304 \\void b(void);
305 \\void c(void);
306 \\
307 );
308 ctx.h("header with multiple includes", linux_x64,
309 \\export fn start(a: u32, b: usize) void{}
310 ,
311 \\#include <stddef.h>
312 \\#include <stdint.h>
313 \\
314 \\void start(uint32_t arg0, size_t arg1);
315 \\
316 );
246317}