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");...@@ -26,6 +26,8 @@ const Module = @import("Module.zig");
26const Cache = @import("Cache.zig");26const Cache = @import("Cache.zig");
27const stage1 = @import("stage1.zig");27const stage1 = @import("stage1.zig");
28const translate_c = @import("translate_c.zig");28const translate_c = @import("translate_c.zig");
29const c_codegen = @import("codegen/c.zig");
30const c_link = @import("link/C.zig");
29const ThreadPool = @import("ThreadPool.zig");31const ThreadPool = @import("ThreadPool.zig");
30const WaitGroup = @import("WaitGroup.zig");32const WaitGroup = @import("WaitGroup.zig");
3133
...@@ -126,12 +128,13 @@ test_filter: ?[]const u8,...@@ -126,12 +128,13 @@ test_filter: ?[]const u8,
126test_name_prefix: ?[]const u8,128test_name_prefix: ?[]const u8,
127test_evented_io: bool,129test_evented_io: bool,
128130
129emit_h: ?EmitLoc,
130emit_asm: ?EmitLoc,131emit_asm: ?EmitLoc,
131emit_llvm_ir: ?EmitLoc,132emit_llvm_ir: ?EmitLoc,
132emit_analysis: ?EmitLoc,133emit_analysis: ?EmitLoc,
133emit_docs: ?EmitLoc,134emit_docs: ?EmitLoc,
134135
136c_header: ?c_link.Header,
137
135pub const InnerError = Module.InnerError;138pub const InnerError = Module.InnerError;
136139
137pub const CRTFile = struct {140pub const CRTFile = struct {
...@@ -895,10 +898,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {...@@ -895,10 +898,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
895 };898 };
896 };899 };
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
902 var system_libs: std.StringArrayHashMapUnmanaged(void) = .{};901 var system_libs: std.StringArrayHashMapUnmanaged(void) = .{};
903 errdefer system_libs.deinit(gpa);902 errdefer system_libs.deinit(gpa);
904 try system_libs.ensureCapacity(gpa, options.system_libs.len);903 try system_libs.ensureCapacity(gpa, options.system_libs.len);
...@@ -974,7 +973,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {...@@ -974,7 +973,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
974 .local_cache_directory = options.local_cache_directory,973 .local_cache_directory = options.local_cache_directory,
975 .global_cache_directory = options.global_cache_directory,974 .global_cache_directory = options.global_cache_directory,
976 .bin_file = bin_file,975 .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,
978 .emit_asm = options.emit_asm,977 .emit_asm = options.emit_asm,
979 .emit_llvm_ir = options.emit_llvm_ir,978 .emit_llvm_ir = options.emit_llvm_ir,
980 .emit_analysis = options.emit_analysis,979 .emit_analysis = options.emit_analysis,
...@@ -1185,6 +1184,10 @@ pub fn destroy(self: *Compilation) void {...@@ -1185,6 +1184,10 @@ pub fn destroy(self: *Compilation) void {
1185 }1184 }
1186 self.failed_c_objects.deinit(gpa);1185 self.failed_c_objects.deinit(gpa);
11871186
1187 if (self.c_header) |*header| {
1188 header.deinit();
1189 }
1190
1188 self.cache_parent.manifest_dir.close();1191 self.cache_parent.manifest_dir.close();
1189 if (self.owned_link_dir) |*dir| dir.close();1192 if (self.owned_link_dir) |*dir| dir.close();
11901193
...@@ -1286,6 +1289,20 @@ pub fn update(self: *Compilation) !void {...@@ -1286,6 +1289,20 @@ pub fn update(self: *Compilation) !void {
1286 module.root_scope.unload(self.gpa);1289 module.root_scope.unload(self.gpa);
1287 }1290 }
1288 }1291 }
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 }
1289}1306}
12901307
1291/// Having the file open for writing is problematic as far as executing the1308/// 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...@@ -1385,6 +1402,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
1385 var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len);1402 var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len);
1386 defer c_comp_progress_node.end();1403 defer c_comp_progress_node.end();
13871404
1405 var arena = std.heap.ArenaAllocator.init(self.gpa);
1406 defer arena.deinit();
1407
1388 var wg = WaitGroup{};1408 var wg = WaitGroup{};
1389 defer wg.wait();1409 defer wg.wait();
13901410
...@@ -1432,22 +1452,44 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor...@@ -1432,22 +1452,44 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
14321452
1433 assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits());1453 assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits());
14341454
1435 self.bin_file.updateDecl(module, decl) catch |err| switch (err) {1455 self.bin_file.updateDecl(module, decl) catch |err| {
1436 error.OutOfMemory => return error.OutOfMemory,1456 switch (err) {
1437 error.AnalysisFail => {1457 error.OutOfMemory => return error.OutOfMemory,
1438 decl.analysis = .dependency_failure;1458 error.AnalysisFail => {
1439 },1459 decl.analysis = .dependency_failure;
1440 else => {1460 },
1441 try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);1461 else => {
1442 module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(1462 try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
1443 module.gpa,1463 module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
1444 decl.src(),1464 module.gpa,
1445 "unable to codegen: {}",1465 decl.src(),
1446 .{@errorName(err)},1466 "unable to codegen: {}",
1447 ));1467 .{@errorName(err)},
1448 decl.analysis = .codegen_failure_retryable;1468 ));
1449 },1469 decl.analysis = .codegen_failure_retryable;
1470 },
1471 }
1472 return;
1450 };1473 };
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 }
1451 },1493 },
1452 },1494 },
1453 .analyze_decl => |decl| {1495 .analyze_decl => |decl| {
...@@ -2913,7 +2955,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node...@@ -2913,7 +2955,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
2913 man.hash.add(comp.bin_file.options.function_sections);2955 man.hash.add(comp.bin_file.options.function_sections);
2914 man.hash.add(comp.bin_file.options.is_test);2956 man.hash.add(comp.bin_file.options.is_test);
2915 man.hash.add(comp.bin_file.options.emit != null);2957 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 }
2917 man.hash.addOptionalEmitLoc(comp.emit_asm);2962 man.hash.addOptionalEmitLoc(comp.emit_asm);
2918 man.hash.addOptionalEmitLoc(comp.emit_llvm_ir);2963 man.hash.addOptionalEmitLoc(comp.emit_llvm_ir);
2919 man.hash.addOptionalEmitLoc(comp.emit_analysis);2964 man.hash.addOptionalEmitLoc(comp.emit_analysis);
...@@ -3012,10 +3057,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node...@@ -3012,10 +3057,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
3012 });3057 });
3013 break :blk try directory.join(arena, &[_][]const u8{bin_basename});3058 break :blk try directory.join(arena, &[_][]const u8{bin_basename});
3014 } else "";3059 } else "";
3015 if (comp.emit_h != null) {3060 if (comp.c_header != null) {
3016 log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{});3061 log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{});
3017 }3062 }
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);
3019 const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);3064 const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);
3020 const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);3065 const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);
3021 const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory);3066 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");...@@ -2,6 +2,7 @@ const std = @import("std");
22
3const link = @import("../link.zig");3const link = @import("../link.zig");
4const Module = @import("../Module.zig");4const Module = @import("../Module.zig");
5const Compilation = @import("../Compilation.zig");
56
6const Inst = @import("../ir.zig").Inst;7const Inst = @import("../ir.zig").Inst;
7const Value = @import("../value.zig").Value;8const Value = @import("../value.zig").Value;
...@@ -19,24 +20,28 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {...@@ -19,24 +20,28 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
19 return allocator.dupe(u8, name);20 return allocator.dupe(u8, name);
20}21}
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 {
23 switch (T.zigTypeTag()) {24 switch (T.zigTypeTag()) {
24 .NoReturn => {25 .NoReturn => {
25 try writer.writeAll("zig_noreturn void");26 try writer.writeAll("zig_noreturn void");
26 },27 },
27 .Void => try writer.writeAll("void"),28 .Void => try writer.writeAll("void"),
29 .Bool => try writer.writeAll("bool"),
28 .Int => {30 .Int => {
29 if (T.tag() == .u8) {31 if (T.tag() == .u8) {
30 ctx.file.need_stdint = true;32 header.need_stdint = true;
31 try writer.writeAll("uint8_t");33 try writer.writeAll("uint8_t");
34 } else if (T.tag() == .u32) {
35 header.need_stdint = true;
36 try writer.writeAll("uint32_t");
32 } else if (T.tag() == .usize) {37 } else if (T.tag() == .usize) {
33 ctx.file.need_stddef = true;38 header.need_stddef = true;
34 try writer.writeAll("size_t");39 try writer.writeAll("size_t");
35 } else {40 } 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});
37 }42 }
38 },43 },
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}),
40 }45 }
41}46}
4247
...@@ -47,13 +52,13 @@ fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Va...@@ -47,13 +52,13 @@ fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Va
47 return writer.print("{}", .{val.toSignedInt()});52 return writer.print("{}", .{val.toSignedInt()});
48 return writer.print("{}", .{val.toUnsignedInt()});53 return writer.print("{}", .{val.toUnsignedInt()});
49 },54 },
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}),
51 }56 }
52}57}
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 {
55 const tv = decl.typed_value.most_recent.typed_value;60 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());
57 // Use the child allocator directly, as we know the name can be freed before62 // Use the child allocator directly, as we know the name can be freed before
58 // the rest of the arena.63 // the rest of the arena.
59 const name = try map(ctx.arena.child_allocator, mem.spanZ(decl.name));64 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...@@ -68,7 +73,7 @@ fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl
68 if (index > 0) {73 if (index > 0) {
69 try writer.writeAll(", ");74 try writer.writeAll(", ");
70 }75 }
71 try renderType(ctx, writer, tv.ty.fnParamType(index));76 try renderType(ctx, header, writer, tv.ty.fnParamType(index));
72 try writer.print(" arg{}", .{index});77 try writer.print(" arg{}", .{index});
73 }78 }
74 }79 }
...@@ -83,6 +88,34 @@ pub fn generate(file: *C, decl: *Decl) !void {...@@ -83,6 +88,34 @@ pub fn generate(file: *C, decl: *Decl) !void {
83 }88 }
84}89}
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
86fn genArray(file: *C, decl: *Decl) !void {119fn genArray(file: *C, decl: *Decl) !void {
87 const tv = decl.typed_value.most_recent.typed_value;120 const tv = decl.typed_value.most_recent.typed_value;
88 // TODO: prevent inline asm constants from being emitted121 // TODO: prevent inline asm constants from being emitted
...@@ -102,12 +135,12 @@ fn genArray(file: *C, decl: *Decl) !void {...@@ -102,12 +135,12 @@ fn genArray(file: *C, decl: *Decl) !void {
102}135}
103136
104const Context = struct {137const Context = struct {
105 file: *C,
106 decl: *Decl,138 decl: *Decl,
107 inst_map: *std.AutoHashMap(*Inst, []u8),139 inst_map: *std.AutoHashMap(*Inst, []u8),
108 arena: *std.heap.ArenaAllocator,140 arena: *std.heap.ArenaAllocator,
109 argdex: usize = 0,141 argdex: usize = 0,
110 unnamed_index: usize = 0,142 unnamed_index: usize = 0,
143 error_msg: *Compilation.ErrorMsg = undefined,
111144
112 fn resolveInst(self: *Context, inst: *Inst) ![]u8 {145 fn resolveInst(self: *Context, inst: *Inst) ![]u8 {
113 if (inst.cast(Inst.Constant)) |const_inst| {146 if (inst.cast(Inst.Constant)) |const_inst| {
...@@ -127,6 +160,11 @@ const Context = struct {...@@ -127,6 +160,11 @@ const Context = struct {
127 return val;160 return val;
128 }161 }
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
130 fn deinit(self: *Context) void {168 fn deinit(self: *Context) void {
131 self.* = undefined;169 self.* = undefined;
132 }170 }
...@@ -141,14 +179,16 @@ fn genFn(file: *C, decl: *Decl) !void {...@@ -141,14 +179,16 @@ fn genFn(file: *C, decl: *Decl) !void {
141 var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator);179 var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator);
142 defer inst_map.deinit();180 defer inst_map.deinit();
143 var ctx = Context{181 var ctx = Context{
144 .file = file,
145 .decl = decl,182 .decl = decl,
146 .arena = &arena,183 .arena = &arena,
147 .inst_map = &inst_map,184 .inst_map = &inst_map,
148 };185 };
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
153 try writer.writeAll(" {");193 try writer.writeAll(" {");
154194
...@@ -158,18 +198,18 @@ fn genFn(file: *C, decl: *Decl) !void {...@@ -158,18 +198,18 @@ fn genFn(file: *C, decl: *Decl) !void {
158 try writer.writeAll("\n");198 try writer.writeAll("\n");
159 for (instructions) |inst| {199 for (instructions) |inst| {
160 if (switch (inst.tag) {200 if (switch (inst.tag) {
161 .assembly => try genAsm(&ctx, inst.castTag(.assembly).?),201 .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?),
162 .call => try genCall(&ctx, inst.castTag(.call).?),202 .call => try genCall(&ctx, file, inst.castTag(.call).?),
163 .add => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "+"),203 .add => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "+"),
164 .sub => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "-"),204 .sub => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "-"),
165 .ret => try genRet(&ctx, inst.castTag(.ret).?),205 .ret => try genRet(&ctx, inst.castTag(.ret).?),
166 .retvoid => try genRetVoid(&ctx),206 .retvoid => try genRetVoid(file),
167 .arg => try genArg(&ctx),207 .arg => try genArg(&ctx),
168 .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),208 .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),
169 .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?),209 .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?),
170 .unreach => try genUnreach(&ctx, inst.castTag(.unreach).?),210 .unreach => try genUnreach(file, inst.castTag(.unreach).?),
171 .intcast => try genIntCast(&ctx, inst.castTag(.intcast).?),211 .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?),
172 else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),212 else => |e| return ctx.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
173 }) |name| {213 }) |name| {
174 try ctx.inst_map.putNoClobber(inst, name);214 try ctx.inst_map.putNoClobber(inst, name);
175 }215 }
...@@ -185,46 +225,46 @@ fn genArg(ctx: *Context) !?[]u8 {...@@ -185,46 +225,46 @@ fn genArg(ctx: *Context) !?[]u8 {
185 return name;225 return name;
186}226}
187227
188fn genRetVoid(ctx: *Context) !?[]u8 {228fn genRetVoid(file: *C) !?[]u8 {
189 try ctx.file.main.writer().print(indentation ++ "return;\n", .{});229 try file.main.writer().print(indentation ++ "return;\n", .{});
190 return null;230 return null;
191}231}
192232
193fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {233fn 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", .{});
195}235}
196236
197fn genIntCast(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {237fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
198 if (inst.base.isUnused())238 if (inst.base.isUnused())
199 return null;239 return null;
200 const op = inst.operand;240 const op = inst.operand;
201 const writer = ctx.file.main.writer();241 const writer = file.main.writer();
202 const name = try ctx.name();242 const name = try ctx.name();
203 const from = try ctx.resolveInst(inst.operand);243 const from = try ctx.resolveInst(inst.operand);
204 try writer.writeAll(indentation ++ "const ");244 try writer.writeAll(indentation ++ "const ");
205 try renderType(ctx, writer, inst.base.ty);245 try renderType(ctx, &file.header, writer, inst.base.ty);
206 try writer.print(" {} = (", .{name});246 try writer.print(" {} = (", .{name});
207 try renderType(ctx, writer, inst.base.ty);247 try renderType(ctx, &file.header, writer, inst.base.ty);
208 try writer.print("){};\n", .{from});248 try writer.print("){};\n", .{from});
209 return name;249 return name;
210}250}
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 {
213 if (inst.base.isUnused())253 if (inst.base.isUnused())
214 return null;254 return null;
215 const lhs = ctx.resolveInst(inst.lhs);255 const lhs = ctx.resolveInst(inst.lhs);
216 const rhs = ctx.resolveInst(inst.rhs);256 const rhs = ctx.resolveInst(inst.rhs);
217 const writer = ctx.file.main.writer();257 const writer = file.main.writer();
218 const name = try ctx.name();258 const name = try ctx.name();
219 try writer.writeAll(indentation ++ "const ");259 try writer.writeAll(indentation ++ "const ");
220 try renderType(ctx, writer, inst.base.ty);260 try renderType(ctx, &file.header, writer, inst.base.ty);
221 try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs });261 try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs });
222 return name;262 return name;
223}263}
224264
225fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {265fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
226 const writer = ctx.file.main.writer();266 const writer = file.main.writer();
227 const header = ctx.file.header.writer();267 const header = file.header.buf.writer();
228 try writer.writeAll(indentation);268 try writer.writeAll(indentation);
229 if (inst.func.castTag(.constant)) |func_inst| {269 if (inst.func.castTag(.constant)) |func_inst| {
230 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {270 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
...@@ -235,9 +275,9 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {...@@ -235,9 +275,9 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
235 try writer.print("(void)", .{});275 try writer.print("(void)", .{});
236 }276 }
237 const tname = mem.spanZ(target.name);277 const tname = mem.spanZ(target.name);
238 if (ctx.file.called.get(tname) == null) {278 if (file.called.get(tname) == null) {
239 try ctx.file.called.put(tname, void{});279 try file.called.put(tname, void{});
240 try renderFunctionSignature(ctx, header, target);280 try renderFunctionSignature(ctx, &file.header, header, target);
241 try header.writeAll(";\n");281 try header.writeAll(";\n");
242 }282 }
243 try writer.print("{}(", .{tname});283 try writer.print("{}(", .{tname});
...@@ -256,10 +296,10 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {...@@ -256,10 +296,10 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
256 }296 }
257 try writer.writeAll(");\n");297 try writer.writeAll(");\n");
258 } else {298 } 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?", .{});
260 }300 }
261 } else {301 } 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?", .{});
263 }303 }
264 return null;304 return null;
265}305}
...@@ -274,20 +314,20 @@ fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {...@@ -274,20 +314,20 @@ fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
274 return null;314 return null;
275}315}
276316
277fn genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {317fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 {
278 try ctx.file.main.writer().writeAll(indentation ++ "zig_unreachable();\n");318 try file.main.writer().writeAll(indentation ++ "zig_unreachable();\n");
279 return null;319 return null;
280}320}
281321
282fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {322fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 {
283 const writer = ctx.file.main.writer();323 const writer = file.main.writer();
284 try writer.writeAll(indentation);324 try writer.writeAll(indentation);
285 for (as.inputs) |i, index| {325 for (as.inputs) |i, index| {
286 if (i[0] == '{' and i[i.len - 1] == '}') {326 if (i[0] == '{' and i[i.len - 1] == '}') {
287 const reg = i[1 .. i.len - 1];327 const reg = i[1 .. i.len - 1];
288 const arg = as.args[index];328 const arg = as.args[index];
289 try writer.writeAll("register ");329 try writer.writeAll("register ");
290 try renderType(ctx, writer, arg.ty);330 try renderType(ctx, &file.header, writer, arg.ty);
291 try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg });331 try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg });
292 // TODO merge constant handling into inst_map as well332 // TODO merge constant handling into inst_map as well
293 if (arg.castTag(.constant)) |c| {333 if (arg.castTag(.constant)) |c| {
...@@ -296,17 +336,17 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {...@@ -296,17 +336,17 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
296 } else {336 } else {
297 const gop = try ctx.inst_map.getOrPut(arg);337 const gop = try ctx.inst_map.getOrPut(arg);
298 if (!gop.found_existing) {338 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", .{});
300 }340 }
301 try writer.print("{};\n ", .{gop.entry.value});341 try writer.print("{};\n ", .{gop.entry.value});
302 }342 }
303 } else {343 } 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", .{});
305 }345 }
306 }346 }
307 try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });347 try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
308 if (as.output) |o| {348 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", .{});
310 }350 }
311 if (as.inputs.len > 0) {351 if (as.inputs.len > 0) {
312 if (as.output == null) {352 if (as.output == null) {
src/link/C.zig+45-18
...@@ -13,15 +13,54 @@ const C = @This();...@@ -13,15 +13,54 @@ const C = @This();
1313
14pub const base_tag: File.Tag = .c;14pub 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
16base: File,57base: File,
1758
18header: std.ArrayList(u8),59header: Header,
19constants: std.ArrayList(u8),60constants: std.ArrayList(u8),
20main: std.ArrayList(u8),61main: std.ArrayList(u8),
2162
22called: std.StringHashMap(void),63called: std.StringHashMap(void),
23need_stddef: bool = false,
24need_stdint: bool = false,
25error_msg: *Compilation.ErrorMsg = undefined,64error_msg: *Compilation.ErrorMsg = undefined,
2665
27pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C {66pub 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...@@ -44,7 +83,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
44 .allocator = allocator,83 .allocator = allocator,
45 },84 },
46 .main = std.ArrayList(u8).init(allocator),85 .main = std.ArrayList(u8).init(allocator),
47 .header = std.ArrayList(u8).init(allocator),86 .header = Header.init(allocator, null),
48 .constants = std.ArrayList(u8).init(allocator),87 .constants = std.ArrayList(u8).init(allocator),
49 .called = std.StringHashMap(void).init(allocator),88 .called = std.StringHashMap(void).init(allocator),
50 };89 };
...@@ -82,22 +121,10 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {...@@ -82,22 +121,10 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
82 defer tracy.end();121 defer tracy.end();
83122
84 const writer = self.base.file.?.writer();123 const writer = self.base.file.?.writer();
85 try writer.writeAll(@embedFile("cbe.h"));124 try self.header.flush(writer);
86 var includes = false;125 if (self.header.buf.items.len > 0) {
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');126 try writer.writeByte('\n');
97 }127 }
98 if (self.header.items.len > 0) {
99 try writer.print("{}\n", .{self.header.items});
100 }
101 if (self.constants.items.len > 0) {128 if (self.constants.items.len > 0) {
102 try writer.print("{}\n", .{self.constants.items});129 try writer.print("{}\n", .{self.constants.items});
103 }130 }
src/link/cbe.h+10
...@@ -1,3 +1,12 @@...@@ -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
1#if __STDC_VERSION__ >= 201112L10#if __STDC_VERSION__ >= 201112L
2#define zig_noreturn _Noreturn11#define zig_noreturn _Noreturn
3#elif __GNUC__12#elif __GNUC__
...@@ -13,3 +22,4 @@...@@ -13,3 +22,4 @@
13#else22#else
14#define zig_unreachable()23#define zig_unreachable()
15#endif24#endif
25
src/main.zig+1-11
...@@ -490,7 +490,7 @@ fn buildOutputType(...@@ -490,7 +490,7 @@ fn buildOutputType(
490 var target_dynamic_linker: ?[]const u8 = null;490 var target_dynamic_linker: ?[]const u8 = null;
491 var target_ofmt: ?[]const u8 = null;491 var target_ofmt: ?[]const u8 = null;
492 var output_mode: std.builtin.OutputMode = undefined;492 var output_mode: std.builtin.OutputMode = undefined;
493 var emit_h: Emit = undefined;493 var emit_h: Emit = .no;
494 var soname: SOName = undefined;494 var soname: SOName = undefined;
495 var ensure_libc_on_non_freestanding = false;495 var ensure_libc_on_non_freestanding = false;
496 var ensure_libcpp_on_non_freestanding = false;496 var ensure_libcpp_on_non_freestanding = false;
...@@ -594,16 +594,6 @@ fn buildOutputType(...@@ -594,16 +594,6 @@ fn buildOutputType(
594 },594 },
595 else => unreachable,595 else => unreachable,
596 }596 }
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
608 soname = .yes_default_value;598 soname = .yes_default_value;
609 const args = all_args[2..];599 const args = all_args[2..];
src/test.zig+45-4
...@@ -96,6 +96,10 @@ pub const TestContext = struct {...@@ -96,6 +96,10 @@ pub const TestContext = struct {
96 /// stdout against the expected results96 /// stdout against the expected results
97 /// This is a slice containing the expected message.97 /// This is a slice containing the expected message.
98 Execution: []const u8,98 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,
99 },103 },
100 };104 };
101105
...@@ -138,6 +142,15 @@ pub const TestContext = struct {...@@ -138,6 +142,15 @@ pub const TestContext = struct {
138 }) catch unreachable;142 }) catch unreachable;
139 }143 }
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
141 /// Adds a subcase in which the module is updated with `src`, compiled,154 /// Adds a subcase in which the module is updated with `src`, compiled,
142 /// run, and the output is tested against `result`.155 /// run, and the output is tested against `result`.
143 pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {156 pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void {
...@@ -269,6 +282,10 @@ pub const TestContext = struct {...@@ -269,6 +282,10 @@ pub const TestContext = struct {
269 ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out);282 ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out);
270 }283 }
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
272 pub fn addCompareOutput(289 pub fn addCompareOutput(
273 ctx: *TestContext,290 ctx: *TestContext,
274 name: []const u8,291 name: []const u8,
...@@ -547,6 +564,13 @@ pub const TestContext = struct {...@@ -547,6 +564,13 @@ pub const TestContext = struct {
547 .directory = emit_directory,564 .directory = emit_directory,
548 .basename = bin_name,565 .basename = bin_name,
549 };566 };
567 const emit_h: ?Compilation.EmitLoc = if (case.cbe)
568 .{
569 .directory = emit_directory,
570 .basename = "test_case.h",
571 }
572 else
573 null;
550 const comp = try Compilation.create(allocator, .{574 const comp = try Compilation.create(allocator, .{
551 .local_cache_directory = zig_cache_directory,575 .local_cache_directory = zig_cache_directory,
552 .global_cache_directory = zig_cache_directory,576 .global_cache_directory = zig_cache_directory,
...@@ -561,6 +585,7 @@ pub const TestContext = struct {...@@ -561,6 +585,7 @@ pub const TestContext = struct {
561 // TODO: support testing optimizations585 // TODO: support testing optimizations
562 .optimize_mode = .Debug,586 .optimize_mode = .Debug,
563 .emit_bin = emit_bin,587 .emit_bin = emit_bin,
588 .emit_h = emit_h,
564 .root_pkg = &root_pkg,589 .root_pkg = &root_pkg,
565 .keep_source_files_loaded = true,590 .keep_source_files_loaded = true,
566 .object_format = ofmt,591 .object_format = ofmt,
...@@ -616,6 +641,22 @@ pub const TestContext = struct {...@@ -616,6 +641,22 @@ pub const TestContext = struct {
616 }641 }
617642
618 switch (update.case) {643 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 },
619 .Transformation => |expected_output| {660 .Transformation => |expected_output| {
620 if (case.cbe) {661 if (case.cbe) {
621 // The C file is always closed after an update, because we don't support662 // The C file is always closed after an update, because we don't support
...@@ -670,8 +711,8 @@ pub const TestContext = struct {...@@ -670,8 +711,8 @@ pub const TestContext = struct {
670 test_node.activate();711 test_node.activate();
671 defer test_node.end();712 defer test_node.end();
672 var handled_errors = try arena.alloc(bool, e.len);713 var handled_errors = try arena.alloc(bool, e.len);
673 for (handled_errors) |*h| {714 for (handled_errors) |*handled| {
674 h.* = false;715 handled.* = false;
675 }716 }
676 var all_errors = try comp.getAllErrorsAlloc();717 var all_errors = try comp.getAllErrorsAlloc();
677 defer all_errors.deinit(allocator);718 defer all_errors.deinit(allocator);
...@@ -709,8 +750,8 @@ pub const TestContext = struct {...@@ -709,8 +750,8 @@ pub const TestContext = struct {
709 }750 }
710 }751 }
711752
712 for (handled_errors) |h, i| {753 for (handled_errors) |handled, i| {
713 if (!h) {754 if (!handled) {
714 const er = e[i];755 const er = e[i];
715 std.debug.print(756 std.debug.print(
716 "{s}\nDid not receive error:\n================\n{}\n================\nTest failed.\n",757 "{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 {...@@ -19,6 +19,12 @@ pub fn addCases(ctx: *TestContext) !void {
19 \\}19 \\}
20 \\20 \\
21 );21 );
22 ctx.h("simple header", linux_x64,
23 \\export fn start() void{}
24 ,
25 \\void start(void);
26 \\
27 );
22 ctx.c("less empty start function", linux_x64,28 ctx.c("less empty start function", linux_x64,
23 \\fn main() noreturn {29 \\fn main() noreturn {
24 \\ unreachable;30 \\ unreachable;
...@@ -243,4 +249,69 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -243,4 +249,69 @@ pub fn addCases(ctx: *TestContext) !void {
243 \\}249 \\}
244 \\250 \\
245 );251 );
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 );
246}317}