| author | |
| committer | |
| log | 03113d92467a99658c2606a21821b28b19dfdded |
| tree | 9897504c15a08c1ef9165bee3b3299274eb171c9 |
| parent | ba2f2e139306618b8beaa8e833b1f9845309df73 |
| parent | 9849e894d52dd7b9aedc149011cb19d2bccaab70 |
| signature |
Implement emit-h7 files changed, 328 insertions(+), 104 deletions(-)
src/Compilation.zig+69-24| ... | ... | @@ -26,6 +26,8 @@ const Module = @import("Module.zig"); |
| 26 | 26 | const Cache = @import("Cache.zig"); |
| 27 | 27 | const stage1 = @import("stage1.zig"); |
| 28 | 28 | const translate_c = @import("translate_c.zig"); |
| 29 | const c_codegen = @import("codegen/c.zig"); | |
| 30 | const c_link = @import("link/C.zig"); | |
| 29 | 31 | const ThreadPool = @import("ThreadPool.zig"); |
| 30 | 32 | const WaitGroup = @import("WaitGroup.zig"); |
| 31 | 33 | |
| ... | ... | @@ -126,12 +128,13 @@ test_filter: ?[]const u8, |
| 126 | 128 | test_name_prefix: ?[]const u8, |
| 127 | 129 | test_evented_io: bool, |
| 128 | 130 | |
| 129 | emit_h: ?EmitLoc, | |
| 130 | 131 | emit_asm: ?EmitLoc, |
| 131 | 132 | emit_llvm_ir: ?EmitLoc, |
| 132 | 133 | emit_analysis: ?EmitLoc, |
| 133 | 134 | emit_docs: ?EmitLoc, |
| 134 | 135 | |
| 136 | c_header: ?c_link.Header, | |
| 137 | ||
| 135 | 138 | pub const InnerError = Module.InnerError; |
| 136 | 139 | |
| 137 | 140 | pub const CRTFile = struct { |
| ... | ... | @@ -895,10 +898,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { |
| 895 | 898 | }; |
| 896 | 899 | }; |
| 897 | 900 | |
| 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 | 901 | var system_libs: std.StringArrayHashMapUnmanaged(void) = .{}; |
| 903 | 902 | errdefer system_libs.deinit(gpa); |
| 904 | 903 | try system_libs.ensureCapacity(gpa, options.system_libs.len); |
| ... | ... | @@ -974,7 +973,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { |
| 974 | 973 | .local_cache_directory = options.local_cache_directory, |
| 975 | 974 | .global_cache_directory = options.global_cache_directory, |
| 976 | 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 | 977 | .emit_asm = options.emit_asm, |
| 979 | 978 | .emit_llvm_ir = options.emit_llvm_ir, |
| 980 | 979 | .emit_analysis = options.emit_analysis, |
| ... | ... | @@ -1185,6 +1184,10 @@ pub fn destroy(self: *Compilation) void { |
| 1185 | 1184 | } |
| 1186 | 1185 | self.failed_c_objects.deinit(gpa); |
| 1187 | 1186 | |
| 1187 | if (self.c_header) |*header| { | |
| 1188 | header.deinit(); | |
| 1189 | } | |
| 1190 | ||
| 1188 | 1191 | self.cache_parent.manifest_dir.close(); |
| 1189 | 1192 | if (self.owned_link_dir) |*dir| dir.close(); |
| 1190 | 1193 | |
| ... | ... | @@ -1286,6 +1289,20 @@ pub fn update(self: *Compilation) !void { |
| 1286 | 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 | } |
| 1290 | 1307 | |
| 1291 | 1308 | /// 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 | 1402 | var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len); |
| 1386 | 1403 | defer c_comp_progress_node.end(); |
| 1387 | 1404 | |
| 1405 | var arena = std.heap.ArenaAllocator.init(self.gpa); | |
| 1406 | defer arena.deinit(); | |
| 1407 | ||
| 1388 | 1408 | var wg = WaitGroup{}; |
| 1389 | 1409 | defer wg.wait(); |
| 1390 | 1410 | |
| ... | ... | @@ -1432,22 +1452,44 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor |
| 1432 | 1452 | |
| 1433 | 1453 | assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); |
| 1434 | 1454 | |
| 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; | |
| 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 | 1495 | .analyze_decl => |decl| { |
| ... | ... | @@ -2913,7 +2955,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 2913 | 2955 | man.hash.add(comp.bin_file.options.function_sections); |
| 2914 | 2956 | man.hash.add(comp.bin_file.options.is_test); |
| 2915 | 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 | 2962 | man.hash.addOptionalEmitLoc(comp.emit_asm); |
| 2918 | 2963 | man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); |
| 2919 | 2964 | man.hash.addOptionalEmitLoc(comp.emit_analysis); |
| ... | ... | @@ -3012,10 +3057,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 3012 | 3057 | }); |
| 3013 | 3058 | break :blk try directory.join(arena, &[_][]const u8{bin_basename}); |
| 3014 | 3059 | } else ""; |
| 3015 | if (comp.emit_h != null) { | |
| 3060 | if (comp.c_header != null) { | |
| 3016 | 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 | 3064 | const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory); |
| 3020 | 3065 | const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory); |
| 3021 | 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 | 2 | |
| 3 | 3 | const link = @import("../link.zig"); |
| 4 | 4 | const Module = @import("../Module.zig"); |
| 5 | const Compilation = @import("../Compilation.zig"); | |
| 5 | 6 | |
| 6 | 7 | const Inst = @import("../ir.zig").Inst; |
| 7 | 8 | const Value = @import("../value.zig").Value; |
| ... | ... | @@ -19,24 +20,28 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { |
| 19 | 20 | return allocator.dupe(u8, name); |
| 20 | 21 | } |
| 21 | 22 | |
| 22 | fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void { | |
| 23 | fn renderType(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, T: Type) !void { | |
| 23 | 24 | switch (T.zigTypeTag()) { |
| 24 | 25 | .NoReturn => { |
| 25 | 26 | try writer.writeAll("zig_noreturn void"); |
| 26 | 27 | }, |
| 27 | 28 | .Void => try writer.writeAll("void"), |
| 29 | .Bool => try writer.writeAll("bool"), | |
| 28 | 30 | .Int => { |
| 29 | 31 | if (T.tag() == .u8) { |
| 30 | ctx.file.need_stdint = true; | |
| 32 | header.need_stdint = true; | |
| 31 | 33 | try writer.writeAll("uint8_t"); |
| 34 | } else if (T.tag() == .u32) { | |
| 35 | header.need_stdint = true; | |
| 36 | try writer.writeAll("uint32_t"); | |
| 32 | 37 | } else if (T.tag() == .usize) { |
| 33 | ctx.file.need_stddef = true; | |
| 38 | header.need_stddef = true; | |
| 34 | 39 | try writer.writeAll("size_t"); |
| 35 | 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 | } |
| 42 | 47 | |
| ... | ... | @@ -47,13 +52,13 @@ fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Va |
| 47 | 52 | return writer.print("{}", .{val.toSignedInt()}); |
| 48 | 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 | } |
| 53 | 58 | |
| 54 | fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { | |
| 59 | fn renderFunctionSignature(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { | |
| 55 | 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 | 62 | // Use the child allocator directly, as we know the name can be freed before |
| 58 | 63 | // the rest of the arena. |
| 59 | 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 | 73 | if (index > 0) { |
| 69 | 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 | 77 | try writer.print(" arg{}", .{index}); |
| 73 | 78 | } |
| 74 | 79 | } |
| ... | ... | @@ -83,6 +88,34 @@ pub fn generate(file: *C, decl: *Decl) !void { |
| 83 | 88 | } |
| 84 | 89 | } |
| 85 | 90 | |
| 91 | pub 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 | ||
| 86 | 119 | fn genArray(file: *C, decl: *Decl) !void { |
| 87 | 120 | const tv = decl.typed_value.most_recent.typed_value; |
| 88 | 121 | // TODO: prevent inline asm constants from being emitted |
| ... | ... | @@ -102,12 +135,12 @@ fn genArray(file: *C, decl: *Decl) !void { |
| 102 | 135 | } |
| 103 | 136 | |
| 104 | 137 | const Context = struct { |
| 105 | file: *C, | |
| 106 | 138 | decl: *Decl, |
| 107 | 139 | inst_map: *std.AutoHashMap(*Inst, []u8), |
| 108 | 140 | arena: *std.heap.ArenaAllocator, |
| 109 | 141 | argdex: usize = 0, |
| 110 | 142 | unnamed_index: usize = 0, |
| 143 | error_msg: *Compilation.ErrorMsg = undefined, | |
| 111 | 144 | |
| 112 | 145 | fn resolveInst(self: *Context, inst: *Inst) ![]u8 { |
| 113 | 146 | if (inst.cast(Inst.Constant)) |const_inst| { |
| ... | ... | @@ -127,6 +160,11 @@ const Context = struct { |
| 127 | 160 | return val; |
| 128 | 161 | } |
| 129 | 162 | |
| 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 | 168 | fn deinit(self: *Context) void { |
| 131 | 169 | self.* = undefined; |
| 132 | 170 | } |
| ... | ... | @@ -141,14 +179,16 @@ fn genFn(file: *C, decl: *Decl) !void { |
| 141 | 179 | var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); |
| 142 | 180 | defer inst_map.deinit(); |
| 143 | 181 | var ctx = Context{ |
| 144 | .file = file, | |
| 145 | 182 | .decl = decl, |
| 146 | 183 | .arena = &arena, |
| 147 | 184 | .inst_map = &inst_map, |
| 148 | 185 | }; |
| 149 | defer ctx.deinit(); | |
| 186 | defer { | |
| 187 | file.error_msg = ctx.error_msg; | |
| 188 | ctx.deinit(); | |
| 189 | } | |
| 150 | 190 | |
| 151 | try renderFunctionSignature(&ctx, writer, decl); | |
| 191 | try renderFunctionSignature(&ctx, &file.header, writer, decl); | |
| 152 | 192 | |
| 153 | 193 | try writer.writeAll(" {"); |
| 154 | 194 | |
| ... | ... | @@ -158,18 +198,18 @@ fn genFn(file: *C, decl: *Decl) !void { |
| 158 | 198 | try writer.writeAll("\n"); |
| 159 | 199 | for (instructions) |inst| { |
| 160 | 200 | 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).?, "-"), | |
| 165 | 205 | .ret => try genRet(&ctx, inst.castTag(.ret).?), |
| 166 | .retvoid => try genRetVoid(&ctx), | |
| 206 | .retvoid => try genRetVoid(file), | |
| 167 | 207 | .arg => try genArg(&ctx), |
| 168 | 208 | .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), |
| 169 | 209 | .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}), | |
| 173 | 213 | }) |name| { |
| 174 | 214 | try ctx.inst_map.putNoClobber(inst, name); |
| 175 | 215 | } |
| ... | ... | @@ -185,46 +225,46 @@ fn genArg(ctx: *Context) !?[]u8 { |
| 185 | 225 | return name; |
| 186 | 226 | } |
| 187 | 227 | |
| 188 | fn genRetVoid(ctx: *Context) !?[]u8 { | |
| 189 | try ctx.file.main.writer().print(indentation ++ "return;\n", .{}); | |
| 228 | fn genRetVoid(file: *C) !?[]u8 { | |
| 229 | try file.main.writer().print(indentation ++ "return;\n", .{}); | |
| 190 | 230 | return null; |
| 191 | 231 | } |
| 192 | 232 | |
| 193 | 233 | fn 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 | } |
| 196 | 236 | |
| 197 | fn genIntCast(ctx: *Context, inst: *Inst.UnOp) !?[]u8 { | |
| 237 | fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | |
| 198 | 238 | if (inst.base.isUnused()) |
| 199 | 239 | return null; |
| 200 | 240 | const op = inst.operand; |
| 201 | const writer = ctx.file.main.writer(); | |
| 241 | const writer = file.main.writer(); | |
| 202 | 242 | const name = try ctx.name(); |
| 203 | 243 | const from = try ctx.resolveInst(inst.operand); |
| 204 | 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 | 246 | try writer.print(" {} = (", .{name}); |
| 207 | try renderType(ctx, writer, inst.base.ty); | |
| 247 | try renderType(ctx, &file.header, writer, inst.base.ty); | |
| 208 | 248 | try writer.print("){};\n", .{from}); |
| 209 | 249 | return name; |
| 210 | 250 | } |
| 211 | 251 | |
| 212 | fn genBinOp(ctx: *Context, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { | |
| 252 | fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { | |
| 213 | 253 | if (inst.base.isUnused()) |
| 214 | 254 | return null; |
| 215 | 255 | const lhs = ctx.resolveInst(inst.lhs); |
| 216 | 256 | const rhs = ctx.resolveInst(inst.rhs); |
| 217 | const writer = ctx.file.main.writer(); | |
| 257 | const writer = file.main.writer(); | |
| 218 | 258 | const name = try ctx.name(); |
| 219 | 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 | 261 | try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs }); |
| 222 | 262 | return name; |
| 223 | 263 | } |
| 224 | 264 | |
| 225 | fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { | |
| 226 | const writer = ctx.file.main.writer(); | |
| 227 | const header = ctx.file.header.writer(); | |
| 265 | fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { | |
| 266 | const writer = file.main.writer(); | |
| 267 | const header = file.header.buf.writer(); | |
| 228 | 268 | try writer.writeAll(indentation); |
| 229 | 269 | if (inst.func.castTag(.constant)) |func_inst| { |
| 230 | 270 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { |
| ... | ... | @@ -235,9 +275,9 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { |
| 235 | 275 | try writer.print("(void)", .{}); |
| 236 | 276 | } |
| 237 | 277 | 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); | |
| 241 | 281 | try header.writeAll(";\n"); |
| 242 | 282 | } |
| 243 | 283 | try writer.print("{}(", .{tname}); |
| ... | ... | @@ -256,10 +296,10 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { |
| 256 | 296 | } |
| 257 | 297 | try writer.writeAll(");\n"); |
| 258 | 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 | 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 | 304 | return null; |
| 265 | 305 | } |
| ... | ... | @@ -274,20 +314,20 @@ fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { |
| 274 | 314 | return null; |
| 275 | 315 | } |
| 276 | 316 | |
| 277 | fn genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { | |
| 278 | try ctx.file.main.writer().writeAll(indentation ++ "zig_unreachable();\n"); | |
| 317 | fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { | |
| 318 | try file.main.writer().writeAll(indentation ++ "zig_unreachable();\n"); | |
| 279 | 319 | return null; |
| 280 | 320 | } |
| 281 | 321 | |
| 282 | fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 { | |
| 283 | const writer = ctx.file.main.writer(); | |
| 322 | fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { | |
| 323 | const writer = file.main.writer(); | |
| 284 | 324 | try writer.writeAll(indentation); |
| 285 | 325 | for (as.inputs) |i, index| { |
| 286 | 326 | if (i[0] == '{' and i[i.len - 1] == '}') { |
| 287 | 327 | const reg = i[1 .. i.len - 1]; |
| 288 | 328 | const arg = as.args[index]; |
| 289 | 329 | try writer.writeAll("register "); |
| 290 | try renderType(ctx, writer, arg.ty); | |
| 330 | try renderType(ctx, &file.header, writer, arg.ty); | |
| 291 | 331 | try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg }); |
| 292 | 332 | // TODO merge constant handling into inst_map as well |
| 293 | 333 | if (arg.castTag(.constant)) |c| { |
| ... | ... | @@ -296,17 +336,17 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 { |
| 296 | 336 | } else { |
| 297 | 337 | const gop = try ctx.inst_map.getOrPut(arg); |
| 298 | 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 | 341 | try writer.print("{};\n ", .{gop.entry.value}); |
| 302 | 342 | } |
| 303 | 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 | 347 | try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); |
| 308 | 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 | 351 | if (as.inputs.len > 0) { |
| 312 | 352 | if (as.output == null) { |
src/link/C.zig+45-18| ... | ... | @@ -13,15 +13,54 @@ const C = @This(); |
| 13 | 13 | |
| 14 | 14 | pub const base_tag: File.Tag = .c; |
| 15 | 15 | |
| 16 | pub 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 | ||
| 16 | 57 | base: File, |
| 17 | 58 | |
| 18 | header: std.ArrayList(u8), | |
| 59 | header: Header, | |
| 19 | 60 | constants: std.ArrayList(u8), |
| 20 | 61 | main: std.ArrayList(u8), |
| 21 | 62 | |
| 22 | 63 | called: std.StringHashMap(void), |
| 23 | need_stddef: bool = false, | |
| 24 | need_stdint: bool = false, | |
| 25 | 64 | error_msg: *Compilation.ErrorMsg = undefined, |
| 26 | 65 | |
| 27 | 66 | pub 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 | 83 | .allocator = allocator, |
| 45 | 84 | }, |
| 46 | 85 | .main = std.ArrayList(u8).init(allocator), |
| 47 | .header = std.ArrayList(u8).init(allocator), | |
| 86 | .header = Header.init(allocator, null), | |
| 48 | 87 | .constants = std.ArrayList(u8).init(allocator), |
| 49 | 88 | .called = std.StringHashMap(void).init(allocator), |
| 50 | 89 | }; |
| ... | ... | @@ -82,22 +121,10 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 82 | 121 | defer tracy.end(); |
| 83 | 122 | |
| 84 | 123 | 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) { | |
| 96 | 126 | try writer.writeByte('\n'); |
| 97 | 127 | } |
| 98 | if (self.header.items.len > 0) { | |
| 99 | try writer.print("{}\n", .{self.header.items}); | |
| 100 | } | |
| 101 | 128 | if (self.constants.items.len > 0) { |
| 102 | 129 | try writer.print("{}\n", .{self.constants.items}); |
| 103 | 130 | } |
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 | ||
| 1 | 10 | #if __STDC_VERSION__ >= 201112L |
| 2 | 11 | #define zig_noreturn _Noreturn |
| 3 | 12 | #elif __GNUC__ |
| ... | ... | @@ -13,3 +22,4 @@ |
| 13 | 22 | #else |
| 14 | 23 | #define zig_unreachable() |
| 15 | 24 | #endif |
| 25 |
src/main.zig+1-11| ... | ... | @@ -490,7 +490,7 @@ fn buildOutputType( |
| 490 | 490 | var target_dynamic_linker: ?[]const u8 = null; |
| 491 | 491 | var target_ofmt: ?[]const u8 = null; |
| 492 | 492 | var output_mode: std.builtin.OutputMode = undefined; |
| 493 | var emit_h: Emit = undefined; | |
| 493 | var emit_h: Emit = .no; | |
| 494 | 494 | var soname: SOName = undefined; |
| 495 | 495 | var ensure_libc_on_non_freestanding = false; |
| 496 | 496 | var ensure_libcpp_on_non_freestanding = false; |
| ... | ... | @@ -594,16 +594,6 @@ fn buildOutputType( |
| 594 | 594 | }, |
| 595 | 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 | //} | |
| 607 | 597 | |
| 608 | 598 | soname = .yes_default_value; |
| 609 | 599 | const args = all_args[2..]; |
src/test.zig+45-4| ... | ... | @@ -96,6 +96,10 @@ pub const TestContext = struct { |
| 96 | 96 | /// stdout against the expected results |
| 97 | 97 | /// This is a slice containing the expected message. |
| 98 | 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 | }; |
| 101 | 105 | |
| ... | ... | @@ -138,6 +142,15 @@ pub const TestContext = struct { |
| 138 | 142 | }) catch unreachable; |
| 139 | 143 | } |
| 140 | 144 | |
| 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 | 154 | /// Adds a subcase in which the module is updated with `src`, compiled, |
| 142 | 155 | /// run, and the output is tested against `result`. |
| 143 | 156 | pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { |
| ... | ... | @@ -269,6 +282,10 @@ pub const TestContext = struct { |
| 269 | 282 | ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out); |
| 270 | 283 | } |
| 271 | 284 | |
| 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 | 289 | pub fn addCompareOutput( |
| 273 | 290 | ctx: *TestContext, |
| 274 | 291 | name: []const u8, |
| ... | ... | @@ -547,6 +564,13 @@ pub const TestContext = struct { |
| 547 | 564 | .directory = emit_directory, |
| 548 | 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 | 574 | const comp = try Compilation.create(allocator, .{ |
| 551 | 575 | .local_cache_directory = zig_cache_directory, |
| 552 | 576 | .global_cache_directory = zig_cache_directory, |
| ... | ... | @@ -561,6 +585,7 @@ pub const TestContext = struct { |
| 561 | 585 | // TODO: support testing optimizations |
| 562 | 586 | .optimize_mode = .Debug, |
| 563 | 587 | .emit_bin = emit_bin, |
| 588 | .emit_h = emit_h, | |
| 564 | 589 | .root_pkg = &root_pkg, |
| 565 | 590 | .keep_source_files_loaded = true, |
| 566 | 591 | .object_format = ofmt, |
| ... | ... | @@ -616,6 +641,22 @@ pub const TestContext = struct { |
| 616 | 641 | } |
| 617 | 642 | |
| 618 | 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 | 660 | .Transformation => |expected_output| { |
| 620 | 661 | if (case.cbe) { |
| 621 | 662 | // The C file is always closed after an update, because we don't support |
| ... | ... | @@ -670,8 +711,8 @@ pub const TestContext = struct { |
| 670 | 711 | test_node.activate(); |
| 671 | 712 | defer test_node.end(); |
| 672 | 713 | 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; | |
| 675 | 716 | } |
| 676 | 717 | var all_errors = try comp.getAllErrorsAlloc(); |
| 677 | 718 | defer all_errors.deinit(allocator); |
| ... | ... | @@ -709,8 +750,8 @@ pub const TestContext = struct { |
| 709 | 750 | } |
| 710 | 751 | } |
| 711 | 752 | |
| 712 | for (handled_errors) |h, i| { | |
| 713 | if (!h) { | |
| 753 | for (handled_errors) |handled, i| { | |
| 754 | if (!handled) { | |
| 714 | 755 | const er = e[i]; |
| 715 | 756 | std.debug.print( |
| 716 | 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 | 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 | 28 | ctx.c("less empty start function", linux_x64, |
| 23 | 29 | \\fn main() noreturn { |
| 24 | 30 | \\ unreachable; |
| ... | ... | @@ -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 | } |