diff options
| author | Veikka Tuominen <git@vexu.eu> | 2020-12-23 00:01:22 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-12-23 00:01:22 +0200 |
| commit | 03113d92467a99658c2606a21821b28b19dfdded (patch) | |
| tree | 9897504c15a08c1ef9165bee3b3299274eb171c9 /src | |
| parent | ba2f2e139306618b8beaa8e833b1f9845309df73 (diff) | |
| parent | 9849e894d52dd7b9aedc149011cb19d2bccaab70 (diff) | |
| download | zig-03113d92467a99658c2606a21821b28b19dfdded.tar.gz zig-03113d92467a99658c2606a21821b28b19dfdded.zip | |
Merge pull request #7111 from tetsuo-cpp/emit-h
Implement emit-h
Diffstat (limited to 'src')
| -rw-r--r-- | src/Compilation.zig | 93 | ||||
| -rw-r--r-- | src/codegen/c.zig | 134 | ||||
| -rw-r--r-- | src/link/C.zig | 63 | ||||
| -rw-r--r-- | src/link/cbe.h | 10 | ||||
| -rw-r--r-- | src/main.zig | 12 | ||||
| -rw-r--r-- | src/test.zig | 49 |
6 files changed, 257 insertions, 104 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index 23f67f5b37..11521e5a52 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -26,6 +26,8 @@ const Module = @import("Module.zig"); const Cache = @import("Cache.zig"); const stage1 = @import("stage1.zig"); const translate_c = @import("translate_c.zig"); +const c_codegen = @import("codegen/c.zig"); +const c_link = @import("link/C.zig"); const ThreadPool = @import("ThreadPool.zig"); const WaitGroup = @import("WaitGroup.zig"); @@ -126,12 +128,13 @@ test_filter: ?[]const u8, test_name_prefix: ?[]const u8, test_evented_io: bool, -emit_h: ?EmitLoc, emit_asm: ?EmitLoc, emit_llvm_ir: ?EmitLoc, emit_analysis: ?EmitLoc, emit_docs: ?EmitLoc, +c_header: ?c_link.Header, + pub const InnerError = Module.InnerError; pub const CRTFile = struct { @@ -895,10 +898,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { }; }; - if (!use_llvm and options.emit_h != null) { - fatal("TODO implement support for -femit-h in the self-hosted backend", .{}); - } - var system_libs: std.StringArrayHashMapUnmanaged(void) = .{}; errdefer system_libs.deinit(gpa); try system_libs.ensureCapacity(gpa, options.system_libs.len); @@ -974,7 +973,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .local_cache_directory = options.local_cache_directory, .global_cache_directory = options.global_cache_directory, .bin_file = bin_file, - .emit_h = options.emit_h, + .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa, options.emit_h) else null, .emit_asm = options.emit_asm, .emit_llvm_ir = options.emit_llvm_ir, .emit_analysis = options.emit_analysis, @@ -1185,6 +1184,10 @@ pub fn destroy(self: *Compilation) void { } self.failed_c_objects.deinit(gpa); + if (self.c_header) |*header| { + header.deinit(); + } + self.cache_parent.manifest_dir.close(); if (self.owned_link_dir) |*dir| dir.close(); @@ -1286,6 +1289,20 @@ pub fn update(self: *Compilation) !void { module.root_scope.unload(self.gpa); } } + + // If we've chosen to emit a C header, flush the header to the disk. + if (self.c_header) |header| { + const header_path = header.emit_loc.?; + // If a directory has been provided, write the header there. Otherwise, just write it to the + // cache directory. + const header_dir = if (header_path.directory) |dir| + dir.handle + else + self.local_cache_directory.handle; + const header_file = try header_dir.createFile(header_path.basename, .{}); + defer header_file.close(); + try header.flush(header_file.writer()); + } } /// Having the file open for writing is problematic as far as executing the @@ -1385,6 +1402,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len); defer c_comp_progress_node.end(); + var arena = std.heap.ArenaAllocator.init(self.gpa); + defer arena.deinit(); + var wg = WaitGroup{}; defer wg.wait(); @@ -1432,22 +1452,44 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); - self.bin_file.updateDecl(module, decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - decl.analysis = .dependency_failure; - }, - else => { - try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( - module.gpa, - decl.src(), - "unable to codegen: {}", - .{@errorName(err)}, - )); - decl.analysis = .codegen_failure_retryable; - }, + self.bin_file.updateDecl(module, decl) catch |err| { + switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + decl.analysis = .dependency_failure; + }, + else => { + try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); + module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.gpa, + decl.src(), + "unable to codegen: {}", + .{@errorName(err)}, + )); + decl.analysis = .codegen_failure_retryable; + }, + } + return; }; + + if (self.c_header) |*header| { + c_codegen.generateHeader(&arena, module, &header.*, decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + decl.analysis = .dependency_failure; + }, + else => { + try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); + module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.gpa, + decl.src(), + "unable to generate C header: {}", + .{@errorName(err)}, + )); + decl.analysis = .codegen_failure_retryable; + }, + }; + } }, }, .analyze_decl => |decl| { @@ -2913,7 +2955,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node man.hash.add(comp.bin_file.options.function_sections); man.hash.add(comp.bin_file.options.is_test); man.hash.add(comp.bin_file.options.emit != null); - man.hash.addOptionalEmitLoc(comp.emit_h); + man.hash.add(comp.c_header != null); + if (comp.c_header) |header| { + man.hash.addEmitLoc(header.emit_loc.?); + } man.hash.addOptionalEmitLoc(comp.emit_asm); man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); man.hash.addOptionalEmitLoc(comp.emit_analysis); @@ -3012,10 +3057,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node }); break :blk try directory.join(arena, &[_][]const u8{bin_basename}); } else ""; - if (comp.emit_h != null) { + if (comp.c_header != null) { log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{}); } - const emit_h_path = try stage1LocPath(arena, comp.emit_h, directory); + const emit_h_path = try stage1LocPath(arena, if (comp.c_header) |header| header.emit_loc else null, directory); const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory); const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory); const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index e562ce320e..589e2f17e0 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -2,6 +2,7 @@ const std = @import("std"); const link = @import("../link.zig"); const Module = @import("../Module.zig"); +const Compilation = @import("../Compilation.zig"); const Inst = @import("../ir.zig").Inst; const Value = @import("../value.zig").Value; @@ -19,24 +20,28 @@ fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { return allocator.dupe(u8, name); } -fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void { +fn renderType(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, T: Type) !void { switch (T.zigTypeTag()) { .NoReturn => { try writer.writeAll("zig_noreturn void"); }, .Void => try writer.writeAll("void"), + .Bool => try writer.writeAll("bool"), .Int => { if (T.tag() == .u8) { - ctx.file.need_stdint = true; + header.need_stdint = true; try writer.writeAll("uint8_t"); + } else if (T.tag() == .u32) { + header.need_stdint = true; + try writer.writeAll("uint32_t"); } else if (T.tag() == .usize) { - ctx.file.need_stddef = true; + header.need_stddef = true; try writer.writeAll("size_t"); } else { - return ctx.file.fail(ctx.decl.src(), "TODO implement int types", .{}); + return ctx.fail(ctx.decl.src(), "TODO implement int type {}", .{T}); } }, - else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement type {}", .{e}), + else => |e| return ctx.fail(ctx.decl.src(), "TODO implement type {}", .{e}), } } @@ -47,13 +52,13 @@ fn renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Va return writer.print("{}", .{val.toSignedInt()}); return writer.print("{}", .{val.toUnsignedInt()}); }, - else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement value {}", .{e}), + else => |e| return ctx.fail(ctx.decl.src(), "TODO implement value {}", .{e}), } } -fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { +fn renderFunctionSignature(ctx: *Context, header: *C.Header, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; - try renderType(ctx, writer, tv.ty.fnReturnType()); + try renderType(ctx, header, writer, tv.ty.fnReturnType()); // Use the child allocator directly, as we know the name can be freed before // the rest of the arena. const name = try map(ctx.arena.child_allocator, mem.spanZ(decl.name)); @@ -68,7 +73,7 @@ fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl if (index > 0) { try writer.writeAll(", "); } - try renderType(ctx, writer, tv.ty.fnParamType(index)); + try renderType(ctx, header, writer, tv.ty.fnParamType(index)); try writer.print(" arg{}", .{index}); } } @@ -83,6 +88,34 @@ pub fn generate(file: *C, decl: *Decl) !void { } } +pub fn generateHeader( + arena: *std.heap.ArenaAllocator, + module: *Module, + header: *C.Header, + decl: *Decl, +) error{ AnalysisFail, OutOfMemory }!void { + switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { + .Fn => { + var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); + defer inst_map.deinit(); + var ctx = Context{ + .decl = decl, + .arena = arena, + .inst_map = &inst_map, + }; + const writer = header.buf.writer(); + renderFunctionSignature(&ctx, header, writer, decl) catch |err| { + if (err == error.AnalysisFail) { + try module.failed_decls.put(module.gpa, decl, ctx.error_msg); + } + return err; + }; + try writer.writeAll(";\n"); + }, + else => {}, + } +} + fn genArray(file: *C, decl: *Decl) !void { const tv = decl.typed_value.most_recent.typed_value; // TODO: prevent inline asm constants from being emitted @@ -102,12 +135,12 @@ fn genArray(file: *C, decl: *Decl) !void { } const Context = struct { - file: *C, decl: *Decl, inst_map: *std.AutoHashMap(*Inst, []u8), arena: *std.heap.ArenaAllocator, argdex: usize = 0, unnamed_index: usize = 0, + error_msg: *Compilation.ErrorMsg = undefined, fn resolveInst(self: *Context, inst: *Inst) ![]u8 { if (inst.cast(Inst.Constant)) |const_inst| { @@ -127,6 +160,11 @@ const Context = struct { return val; } + fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { + self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args); + return error.AnalysisFail; + } + fn deinit(self: *Context) void { self.* = undefined; } @@ -141,14 +179,16 @@ fn genFn(file: *C, decl: *Decl) !void { var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); defer inst_map.deinit(); var ctx = Context{ - .file = file, .decl = decl, .arena = &arena, .inst_map = &inst_map, }; - defer ctx.deinit(); + defer { + file.error_msg = ctx.error_msg; + ctx.deinit(); + } - try renderFunctionSignature(&ctx, writer, decl); + try renderFunctionSignature(&ctx, &file.header, writer, decl); try writer.writeAll(" {"); @@ -158,18 +198,18 @@ fn genFn(file: *C, decl: *Decl) !void { try writer.writeAll("\n"); for (instructions) |inst| { if (switch (inst.tag) { - .assembly => try genAsm(&ctx, inst.castTag(.assembly).?), - .call => try genCall(&ctx, inst.castTag(.call).?), - .add => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "+"), - .sub => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "-"), + .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), + .call => try genCall(&ctx, file, inst.castTag(.call).?), + .add => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "+"), + .sub => try genBinOp(&ctx, file, inst.cast(Inst.BinOp).?, "-"), .ret => try genRet(&ctx, inst.castTag(.ret).?), - .retvoid => try genRetVoid(&ctx), + .retvoid => try genRetVoid(file), .arg => try genArg(&ctx), .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?), - .unreach => try genUnreach(&ctx, inst.castTag(.unreach).?), - .intcast => try genIntCast(&ctx, inst.castTag(.intcast).?), - else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}), + .unreach => try genUnreach(file, inst.castTag(.unreach).?), + .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), + else => |e| return ctx.fail(decl.src(), "TODO implement C codegen for {}", .{e}), }) |name| { try ctx.inst_map.putNoClobber(inst, name); } @@ -185,46 +225,46 @@ fn genArg(ctx: *Context) !?[]u8 { return name; } -fn genRetVoid(ctx: *Context) !?[]u8 { - try ctx.file.main.writer().print(indentation ++ "return;\n", .{}); +fn genRetVoid(file: *C) !?[]u8 { + try file.main.writer().print(indentation ++ "return;\n", .{}); return null; } fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 { - return ctx.file.fail(ctx.decl.src(), "TODO return", .{}); + return ctx.fail(ctx.decl.src(), "TODO return", .{}); } -fn genIntCast(ctx: *Context, inst: *Inst.UnOp) !?[]u8 { +fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { if (inst.base.isUnused()) return null; const op = inst.operand; - const writer = ctx.file.main.writer(); + const writer = file.main.writer(); const name = try ctx.name(); const from = try ctx.resolveInst(inst.operand); try writer.writeAll(indentation ++ "const "); - try renderType(ctx, writer, inst.base.ty); + try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print(" {} = (", .{name}); - try renderType(ctx, writer, inst.base.ty); + try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print("){};\n", .{from}); return name; } -fn genBinOp(ctx: *Context, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { +fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 { if (inst.base.isUnused()) return null; const lhs = ctx.resolveInst(inst.lhs); const rhs = ctx.resolveInst(inst.rhs); - const writer = ctx.file.main.writer(); + const writer = file.main.writer(); const name = try ctx.name(); try writer.writeAll(indentation ++ "const "); - try renderType(ctx, writer, inst.base.ty); + try renderType(ctx, &file.header, writer, inst.base.ty); try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs }); return name; } -fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { - const writer = ctx.file.main.writer(); - const header = ctx.file.header.writer(); +fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { + const writer = file.main.writer(); + const header = file.header.buf.writer(); try writer.writeAll(indentation); if (inst.func.castTag(.constant)) |func_inst| { if (func_inst.val.cast(Value.Payload.Function)) |func_val| { @@ -235,9 +275,9 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { try writer.print("(void)", .{}); } const tname = mem.spanZ(target.name); - if (ctx.file.called.get(tname) == null) { - try ctx.file.called.put(tname, void{}); - try renderFunctionSignature(ctx, header, target); + if (file.called.get(tname) == null) { + try file.called.put(tname, void{}); + try renderFunctionSignature(ctx, &file.header, header, target); try header.writeAll(";\n"); } try writer.print("{}(", .{tname}); @@ -256,10 +296,10 @@ fn genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 { } try writer.writeAll(");\n"); } else { - return ctx.file.fail(ctx.decl.src(), "TODO non-function call target?", .{}); + return ctx.fail(ctx.decl.src(), "TODO non-function call target?", .{}); } } else { - return ctx.file.fail(ctx.decl.src(), "TODO non-constant call inst?", .{}); + return ctx.fail(ctx.decl.src(), "TODO non-constant call inst?", .{}); } return null; } @@ -274,20 +314,20 @@ fn genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { return null; } -fn genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { - try ctx.file.main.writer().writeAll(indentation ++ "zig_unreachable();\n"); +fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { + try file.main.writer().writeAll(indentation ++ "zig_unreachable();\n"); return null; } -fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 { - const writer = ctx.file.main.writer(); +fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { + const writer = file.main.writer(); try writer.writeAll(indentation); for (as.inputs) |i, index| { if (i[0] == '{' and i[i.len - 1] == '}') { const reg = i[1 .. i.len - 1]; const arg = as.args[index]; try writer.writeAll("register "); - try renderType(ctx, writer, arg.ty); + try renderType(ctx, &file.header, writer, arg.ty); try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg }); // TODO merge constant handling into inst_map as well if (arg.castTag(.constant)) |c| { @@ -296,17 +336,17 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 { } else { const gop = try ctx.inst_map.getOrPut(arg); if (!gop.found_existing) { - return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); + return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); } try writer.print("{};\n ", .{gop.entry.value}); } } else { - return ctx.file.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); + return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); } } try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); if (as.output) |o| { - return ctx.file.fail(ctx.decl.src(), "TODO inline asm output", .{}); + return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{}); } if (as.inputs.len > 0) { if (as.output == null) { diff --git a/src/link/C.zig b/src/link/C.zig index 467e10998e..325f2ee048 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -13,15 +13,54 @@ const C = @This(); pub const base_tag: File.Tag = .c; +pub const Header = struct { + buf: std.ArrayList(u8), + need_stddef: bool = false, + need_stdint: bool = false, + emit_loc: ?Compilation.EmitLoc, + + pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header { + return .{ + .buf = std.ArrayList(u8).init(allocator), + .emit_loc = emit_loc, + }; + } + + pub fn flush(self: *const Header, writer: anytype) !void { + const tracy = trace(@src()); + defer tracy.end(); + + try writer.writeAll(@embedFile("cbe.h")); + var includes = false; + if (self.need_stddef) { + try writer.writeAll("#include <stddef.h>\n"); + includes = true; + } + if (self.need_stdint) { + try writer.writeAll("#include <stdint.h>\n"); + includes = true; + } + if (includes) { + try writer.writeByte('\n'); + } + if (self.buf.items.len > 0) { + try writer.print("{}", .{self.buf.items}); + } + } + + pub fn deinit(self: *Header) void { + self.buf.deinit(); + self.* = undefined; + } +}; + base: File, -header: std.ArrayList(u8), +header: Header, constants: std.ArrayList(u8), main: std.ArrayList(u8), called: std.StringHashMap(void), -need_stddef: bool = false, -need_stdint: bool = false, error_msg: *Compilation.ErrorMsg = undefined, pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { @@ -44,7 +83,7 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio .allocator = allocator, }, .main = std.ArrayList(u8).init(allocator), - .header = std.ArrayList(u8).init(allocator), + .header = Header.init(allocator, null), .constants = std.ArrayList(u8).init(allocator), .called = std.StringHashMap(void).init(allocator), }; @@ -82,22 +121,10 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { defer tracy.end(); const writer = self.base.file.?.writer(); - try writer.writeAll(@embedFile("cbe.h")); - var includes = false; - if (self.need_stddef) { - try writer.writeAll("#include <stddef.h>\n"); - includes = true; - } - if (self.need_stdint) { - try writer.writeAll("#include <stdint.h>\n"); - includes = true; - } - if (includes) { + try self.header.flush(writer); + if (self.header.buf.items.len > 0) { try writer.writeByte('\n'); } - if (self.header.items.len > 0) { - try writer.print("{}\n", .{self.header.items}); - } if (self.constants.items.len > 0) { try writer.print("{}\n", .{self.constants.items}); } diff --git a/src/link/cbe.h b/src/link/cbe.h index 854032227d..c3f889c6f1 100644 --- a/src/link/cbe.h +++ b/src/link/cbe.h @@ -1,3 +1,12 @@ +#if __STDC_VERSION__ >= 199901L +// C99 or newer +#include <stdbool.h> +#else +#define bool unsigned char +#define true 1 +#define false 0 +#endif + #if __STDC_VERSION__ >= 201112L #define zig_noreturn _Noreturn #elif __GNUC__ @@ -13,3 +22,4 @@ #else #define zig_unreachable() #endif + diff --git a/src/main.zig b/src/main.zig index 131e55bb09..8145cac617 100644 --- a/src/main.zig +++ b/src/main.zig @@ -490,7 +490,7 @@ fn buildOutputType( var target_dynamic_linker: ?[]const u8 = null; var target_ofmt: ?[]const u8 = null; var output_mode: std.builtin.OutputMode = undefined; - var emit_h: Emit = undefined; + var emit_h: Emit = .no; var soname: SOName = undefined; var ensure_libc_on_non_freestanding = false; var ensure_libcpp_on_non_freestanding = false; @@ -594,16 +594,6 @@ fn buildOutputType( }, else => unreachable, } - // TODO finish self-hosted and add support for emitting C header files - emit_h = .no; - //switch (arg_mode) { - // .build => switch (output_mode) { - // .Exe => emit_h = .no, - // .Obj, .Lib => emit_h = .yes_default_path, - // }, - // .translate_c, .zig_test, .run => emit_h = .no, - // else => unreachable, - //} soname = .yes_default_value; const args = all_args[2..]; diff --git a/src/test.zig b/src/test.zig index bc141c94f0..6deee347af 100644 --- a/src/test.zig +++ b/src/test.zig @@ -96,6 +96,10 @@ pub const TestContext = struct { /// stdout against the expected results /// This is a slice containing the expected message. Execution: []const u8, + /// A header update compiles the input with the equivalent of + /// `-femit-h` and tests the produced header against the + /// expected result + Header: []const u8, }, }; @@ -138,6 +142,15 @@ pub const TestContext = struct { }) catch unreachable; } + /// Adds a subcase in which the module is updated with `src`, and a C + /// header is generated. + pub fn addHeader(self: *Case, src: [:0]const u8, result: [:0]const u8) void { + self.updates.append(.{ + .src = src, + .case = .{ .Header = result }, + }) catch unreachable; + } + /// Adds a subcase in which the module is updated with `src`, compiled, /// run, and the output is tested against `result`. pub fn addCompareOutput(self: *Case, src: [:0]const u8, result: []const u8) void { @@ -269,6 +282,10 @@ pub const TestContext = struct { ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out); } + pub fn h(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { + ctx.addC(name, target, .Zig).addHeader(src, cheader ++ out); + } + pub fn addCompareOutput( ctx: *TestContext, name: []const u8, @@ -547,6 +564,13 @@ pub const TestContext = struct { .directory = emit_directory, .basename = bin_name, }; + const emit_h: ?Compilation.EmitLoc = if (case.cbe) + .{ + .directory = emit_directory, + .basename = "test_case.h", + } + else + null; const comp = try Compilation.create(allocator, .{ .local_cache_directory = zig_cache_directory, .global_cache_directory = zig_cache_directory, @@ -561,6 +585,7 @@ pub const TestContext = struct { // TODO: support testing optimizations .optimize_mode = .Debug, .emit_bin = emit_bin, + .emit_h = emit_h, .root_pkg = &root_pkg, .keep_source_files_loaded = true, .object_format = ofmt, @@ -616,6 +641,22 @@ pub const TestContext = struct { } switch (update.case) { + .Header => |expected_output| { + var file = try tmp.dir.openFile("test_case.h", .{ .read = true }); + defer file.close(); + var out = file.reader().readAllAlloc(arena, 1024 * 1024) catch @panic("Unable to read headeroutput!"); + + if (expected_output.len != out.len) { + std.debug.print("\nTransformed header length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); + std.process.exit(1); + } + for (expected_output) |e, i| { + if (out[i] != e) { + std.debug.print("\nTransformed header differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out }); + std.process.exit(1); + } + } + }, .Transformation => |expected_output| { if (case.cbe) { // The C file is always closed after an update, because we don't support @@ -670,8 +711,8 @@ pub const TestContext = struct { test_node.activate(); defer test_node.end(); var handled_errors = try arena.alloc(bool, e.len); - for (handled_errors) |*h| { - h.* = false; + for (handled_errors) |*handled| { + handled.* = false; } var all_errors = try comp.getAllErrorsAlloc(); defer all_errors.deinit(allocator); @@ -709,8 +750,8 @@ pub const TestContext = struct { } } - for (handled_errors) |h, i| { - if (!h) { + for (handled_errors) |handled, i| { + if (!handled) { const er = e[i]; std.debug.print( "{s}\nDid not receive error:\n================\n{}\n================\nTest failed.\n", |
