From 9360e5887ce0bf0ce204eb49f0d0b253348ef557 Mon Sep 17 00:00:00 2001 From: Noam Preil Date: Wed, 30 Dec 2020 05:22:50 -0500 Subject: [PATCH 1/7] integrate CBE with Compilation.update pipeline (closes #7589) * CBE buffers are only valid during a flush() * the file is reopened and truncated during each flush() * CBE now explicitly ignores updateDecl and deleteDecl * CBE updateDecl is gone * test case is enabled --- src/link.zig | 4 +-- src/link/C.zig | 66 ++++++++++++++++++++++++--------------------- test/stage2/cbe.zig | 30 ++++++++++----------- 3 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/link.zig b/src/link.zig index 3dbfb3b922..488f8bf69b 100644 --- a/src/link.zig +++ b/src/link.zig @@ -291,7 +291,7 @@ pub const File = struct { .coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl), .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl), - .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), + .c => {}, .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl), } } @@ -412,7 +412,7 @@ pub const File = struct { .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl), .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), - .c => unreachable, + .c => {}, .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl), } } diff --git a/src/link/C.zig b/src/link/C.zig index c3ab506b2c..5f38c9324f 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -8,10 +8,9 @@ const fs = std.fs; const codegen = @import("../codegen/c.zig"); const link = @import("../link.zig"); const trace = @import("../tracy.zig").trace; -const File = link.File; const C = @This(); -pub const base_tag: File.Tag = .c; +pub const base_tag: link.File.Tag = .c; pub const Header = struct { buf: std.ArrayList(u8), @@ -40,13 +39,16 @@ pub const Header = struct { } }; -base: File, +base: link.File, +path: []const u8, + +// These are only valid during a flush()! header: Header, constants: std.ArrayList(u8), main: std.ArrayList(u8), - called: std.StringHashMap(void), + error_msg: *Compilation.ErrorMsg = undefined, pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { @@ -55,9 +57,6 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_llvm) return error.LLVMHasNoCBackend; if (options.use_lld) return error.LLDHasNoCBackend; - const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) }); - errdefer file.close(); - var c_file = try allocator.create(C); errdefer allocator.destroy(c_file); @@ -65,13 +64,14 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio .base = .{ .tag = .c, .options = options, - .file = file, + .file = null, .allocator = allocator, }, - .main = std.ArrayList(u8).init(allocator), - .header = Header.init(allocator, null), - .constants = std.ArrayList(u8).init(allocator), - .called = std.StringHashMap(void).init(allocator), + .main = undefined, + .header = undefined, + .constants = undefined, + .called = undefined, + .path = sub_path, }; return c_file; @@ -82,21 +82,7 @@ pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) er return error.AnalysisFail; } -pub fn deinit(self: *C) void { - self.main.deinit(); - self.header.deinit(); - self.constants.deinit(); - self.called.deinit(); -} - -pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { - codegen.generate(self, module, decl) catch |err| { - if (err == error.AnalysisFail) { - try module.failed_decls.put(module.gpa, decl, self.error_msg); - } - return err; - }; -} +pub fn deinit(self: *C) void {} pub fn flush(self: *C, comp: *Compilation) !void { return self.flushModule(comp); @@ -106,7 +92,29 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - const writer = self.base.file.?.writer(); + self.main = std.ArrayList(u8).init(self.base.allocator); + self.header = Header.init(self.base.allocator, null); + self.constants = std.ArrayList(u8).init(self.base.allocator); + self.called = std.StringHashMap(void).init(self.base.allocator); + defer self.main.deinit(); + defer self.header.deinit(); + defer self.constants.deinit(); + defer self.called.deinit(); + + const module = self.base.options.module.?; + for (self.base.options.module.?.decl_table.entries.items) |kv| { + codegen.generate(self, module, kv.value) catch |err| { + if (err == error.AnalysisFail) { + try module.failed_decls.put(module.gpa, kv.value, self.error_msg); + } + return err; + }; + } + + const file = try self.base.options.emit.?.directory.handle.createFile(self.path, .{ .truncate = true, .read = true, .mode = link.determineMode(self.base.options) }); + defer file.close(); + + const writer = file.writer(); try self.header.flush(writer); if (self.header.buf.items.len > 0) { try writer.writeByte('\n'); @@ -121,6 +129,4 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { } } try writer.writeAll(self.main.items); - self.base.file.?.close(); - self.base.file = null; } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index b227d6a783..a740a8851a 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -24,13 +24,13 @@ pub fn addCases(ctx: *TestContext) !void { // Now change the message only // TODO fix C backend not supporting updates // https://github.com/ziglang/zig/issues/7589 - //case.addCompareOutput( - // \\extern fn puts(s: [*:0]const u8) c_int; - // \\export fn main() c_int { - // \\ _ = puts("yo"); - // \\ return 0; - // \\} - //, "yo" ++ std.cstr.line_sep); + case.addCompareOutput( + \\extern fn puts(s: [*:0]const u8) c_int; + \\export fn main() c_int { + \\ _ = puts("yo"); + \\ return 0; + \\} + , "yo" ++ std.cstr.line_sep); } { @@ -111,15 +111,15 @@ pub fn addCases(ctx: *TestContext) !void { , \\static zig_noreturn void main(void); \\ - \\zig_noreturn void _start(void) { - \\ main(); - \\} - \\ \\static zig_noreturn void main(void) { \\ zig_breakpoint(); \\ zig_unreachable(); \\} \\ + \\zig_noreturn void _start(void) { + \\ main(); + \\} + \\ ); // TODO: implement return values // TODO: figure out a way to prevent asm constants from being generated @@ -143,10 +143,6 @@ pub fn addCases(ctx: *TestContext) !void { \\static uint8_t exitGood__anon_1[6] = "{rdi}"; \\static uint8_t exitGood__anon_2[8] = "syscall"; \\ - \\zig_noreturn void _start(void) { - \\ exitGood(); - \\} - \\ \\static zig_noreturn void exitGood(void) { \\ register uintptr_t rax_constant __asm__("rax") = 231; \\ register uintptr_t rdi_constant __asm__("rdi") = 0; @@ -155,6 +151,10 @@ pub fn addCases(ctx: *TestContext) !void { \\ zig_unreachable(); \\} \\ + \\zig_noreturn void _start(void) { + \\ exitGood(); + \\} + \\ ); ctx.c("exit with parameter", linux_x64, \\export fn _start() noreturn { From 7b8cede61fc20c137aca4e02425536bfc9a5a400 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 11:08:34 -0700 Subject: [PATCH 2/7] stage2: rework the C backend * std.ArrayList gains `moveToUnmanaged` and dead code `ArrayListUnmanaged.appendWrite` is deleted. * emit_h state is attached to Module rather than Compilation. * remove the implementation of emit-h because it did not properly integrate with incremental compilation. I will re-implement it in a follow-up commit. * Compilation: use the .codegen_failure tag rather than .dependency_failure tag for when `bin_file.updateDecl` fails. C backend: * Use a CValue tagged union instead of strings for C values. * Cleanly separate state into Object and DeclGen: - Object is present only when generating a .c file - DeclGen is present for both generating a .c and .h * Move some functions into their respective Object/DeclGen namespace. * Forward decls are managed by the incremental compilation frontend; C backend no longer renders function signatures based on callsites. For simplicity, all functions always get forward decls. * Constants are managed by the incremental compilation frontend. C backend no longer has a "constants" section. * Participate in incremental compilation. Each Decl gets an ArrayList for its generated C code and it is updated when the Decl is updated. During flush(), all these are joined together in the output file. * The new CValue tagged union is used to clean up using of assigning to locals without an additional pointer local. * Fix bug with bitcast of non-pointers making the memcpy destination immutable. --- lib/std/array_list.zig | 22 +- src/Compilation.zig | 58 +-- src/Module.zig | 6 +- src/codegen/c.zig | 952 +++++++++++++++++++----------------- src/link.zig | 19 +- src/link/C.zig | 197 +++++--- src/link/{cbe.h => C/zig.h} | 1 + src/test.zig | 17 +- test/stage2/cbe.zig | 2 - 9 files changed, 661 insertions(+), 613 deletions(-) rename src/link/{cbe.h => C/zig.h} (99%) diff --git a/lib/std/array_list.zig b/lib/std/array_list.zig index 3114d1b744..51f5b8dc09 100644 --- a/lib/std/array_list.zig +++ b/lib/std/array_list.zig @@ -100,10 +100,20 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields /// of this ArrayList. This ArrayList retains ownership of underlying memory. + /// Deprecated: use `moveToUnmanaged` which has different semantics. pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) { return .{ .items = self.items, .capacity = self.capacity }; } + /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields + /// of this ArrayList. Empties this ArrayList. + pub fn moveToUnmanaged(self: *Self) ArrayListAlignedUnmanaged(T, alignment) { + const allocator = self.allocator; + const result = .{ .items = self.items, .capacity = self.capacity }; + self.* = init(allocator); + return result; + } + /// The caller owns the returned memory. Empties this ArrayList. pub fn toOwnedSlice(self: *Self) Slice { const allocator = self.allocator; @@ -551,14 +561,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ mem.copy(T, self.items[oldlen..], items); } - /// Same as `append` except it returns the number of bytes written, which is always the same - /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API. - /// This function may be called only when `T` is `u8`. - fn appendWrite(self: *Self, allocator: *Allocator, m: []const u8) !usize { - try self.appendSlice(allocator, m); - return m.len; - } - /// Append a value to the list `n` times. /// Allocates more memory as necessary. pub fn appendNTimes(self: *Self, allocator: *Allocator, value: T, n: usize) !void { @@ -1129,13 +1131,13 @@ test "std.ArrayList/ArrayListUnmanaged: ArrayList(T) of struct T" { } } -test "std.ArrayList(u8) implements outStream" { +test "std.ArrayList(u8) implements writer" { var buffer = ArrayList(u8).init(std.testing.allocator); defer buffer.deinit(); const x: i32 = 42; const y: i32 = 1234; - try buffer.outStream().print("x: {}\ny: {}\n", .{ x, y }); + try buffer.writer().print("x: {}\ny: {}\n", .{ x, y }); testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items); } diff --git a/src/Compilation.zig b/src/Compilation.zig index 9912520437..c654833270 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -138,8 +138,6 @@ emit_llvm_ir: ?EmitLoc, emit_analysis: ?EmitLoc, emit_docs: ?EmitLoc, -c_header: ?c_link.Header, - work_queue_wait_group: WaitGroup, pub const InnerError = Module.InnerError; @@ -866,9 +864,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .root_pkg = root_pkg, .root_scope = root_scope, .zig_cache_artifact_directory = zig_cache_artifact_directory, + .emit_h = options.emit_h, }; break :blk module; - } else null; + } else blk: { + if (options.emit_h != null) return error.NoZigModuleForCHeader; + break :blk null; + }; errdefer if (module) |zm| zm.deinit(); const error_return_tracing = !strip and switch (options.optimize_mode) { @@ -996,7 +998,6 @@ 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, - .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, @@ -1218,10 +1219,6 @@ 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(); @@ -1325,20 +1322,6 @@ 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 @@ -1497,7 +1480,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => { - decl.analysis = .dependency_failure; + decl.analysis = .codegen_failure; }, else => { try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); @@ -1512,25 +1495,6 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor } return; }; - - if (self.c_header) |*header| { - c_codegen.generateHeader(self, 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: {s}", - .{@errorName(err)}, - )); - decl.analysis = .codegen_failure_retryable; - }, - }; - } }, }, .analyze_decl => |decl| { @@ -2998,9 +2962,9 @@ 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.add(comp.c_header != null); - if (comp.c_header) |header| { - man.hash.addEmitLoc(header.emit_loc.?); + man.hash.add(mod.emit_h != null); + if (mod.emit_h) |emit_h| { + man.hash.addEmitLoc(emit_h); } man.hash.addOptionalEmitLoc(comp.emit_asm); man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); @@ -3105,10 +3069,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node }); break :blk try directory.join(arena, &[_][]const u8{bin_basename}); } else ""; - if (comp.c_header != null) { + if (comp.emit_h != 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, if (comp.c_header) |header| header.emit_loc else null, directory); + const emit_h_path = try stage1LocPath(arena, mod.emit_h, 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/Module.zig b/src/Module.zig index 6a4575394a..f1cec82680 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -94,6 +94,8 @@ stage1_flags: packed struct { reserved: u2 = 0, } = .{}, +emit_h: ?Compilation.EmitLoc, + pub const Export = struct { options: std.builtin.ExportOptions, /// Byte offset into the file that contains the export directive. @@ -1943,14 +1945,14 @@ fn allocateNewDecl( .coff => .{ .coff = link.File.Coff.TextBlock.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, .macho => .{ .macho = link.File.MachO.TextBlock.empty }, - .c => .{ .c = {} }, + .c => .{ .c = link.File.C.DeclBlock.empty }, .wasm => .{ .wasm = {} }, }, .fn_link = switch (self.comp.bin_file.tag) { .coff => .{ .coff = {} }, .elf => .{ .elf = link.File.Elf.SrcFn.empty }, .macho => .{ .macho = link.File.MachO.SrcFn.empty }, - .c => .{ .c = {} }, + .c => .{ .c = link.File.C.FnBlock.empty }, .wasm => .{ .wasm = null }, }, .generation = 0, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1a89e22d48..d87801ae2e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1,495 +1,526 @@ const std = @import("std"); +const mem = std.mem; +const log = std.log.scoped(.c); +const Writer = std.ArrayList(u8).Writer; 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; const Type = @import("../type.zig").Type; - const C = link.File.C; const Decl = Module.Decl; -const mem = std.mem; -const log = std.log.scoped(.c); - -const Writer = std.ArrayList(u8).Writer; - -/// Maps a name from Zig source to C. Currently, this will always give the same -/// output for any given input, sometimes resulting in broken identifiers. -fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { - return allocator.dupe(u8, name); -} +const trace = @import("../tracy.zig").trace; const Mutability = enum { Const, Mut }; -fn renderTypeAndName( - ctx: *Context, - writer: Writer, - ty: Type, - name: []const u8, - mutability: Mutability, -) error{ OutOfMemory, AnalysisFail }!void { - var suffix = std.ArrayList(u8).init(&ctx.arena.allocator); +pub const CValue = union(enum) { + none: void, + /// Index into local_names + local: usize, + /// Index into local_names, but take the address. + local_ref: usize, + /// A constant instruction, to be rendered inline. + constant: *Inst, + /// Index into the parameters + arg: usize, + /// By-value + decl: *Decl, - var render_ty = ty; - while (render_ty.zigTypeTag() == .Array) { - const sentinel_bit = @boolToInt(render_ty.sentinel() != null); - const c_len = render_ty.arrayLen() + sentinel_bit; - try suffix.writer().print("[{d}]", .{c_len}); - render_ty = render_ty.elemType(); + pub fn printed(value: CValue, object: *Object) Printed { + return .{ + .value = value, + .object = object, + }; } - try renderType(ctx, writer, render_ty); + pub const Printed = struct { + value: CValue, + object: *Object, - const const_prefix = switch (mutability) { - .Const => "const ", - .Mut => "", - }; - try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items }); -} - -fn renderType( - ctx: *Context, - writer: Writer, - t: Type, -) error{ OutOfMemory, AnalysisFail }!void { - switch (t.zigTypeTag()) { - .NoReturn => { - try writer.writeAll("zig_noreturn void"); - }, - .Void => try writer.writeAll("void"), - .Bool => try writer.writeAll("bool"), - .Int => { - switch (t.tag()) { - .u8 => try writer.writeAll("uint8_t"), - .i8 => try writer.writeAll("int8_t"), - .u16 => try writer.writeAll("uint16_t"), - .i16 => try writer.writeAll("int16_t"), - .u32 => try writer.writeAll("uint32_t"), - .i32 => try writer.writeAll("int32_t"), - .u64 => try writer.writeAll("uint64_t"), - .i64 => try writer.writeAll("int64_t"), - .usize => try writer.writeAll("uintptr_t"), - .isize => try writer.writeAll("intptr_t"), - .c_short => try writer.writeAll("short"), - .c_ushort => try writer.writeAll("unsigned short"), - .c_int => try writer.writeAll("int"), - .c_uint => try writer.writeAll("unsigned int"), - .c_long => try writer.writeAll("long"), - .c_ulong => try writer.writeAll("unsigned long"), - .c_longlong => try writer.writeAll("long long"), - .c_ulonglong => try writer.writeAll("unsigned long long"), - .int_signed, .int_unsigned => { - const info = t.intInfo(ctx.target); - const sign_prefix = switch (info.signedness) { - .signed => "i", - .unsigned => "", + /// TODO this got unwieldly, I want to remove the ability to print this way + pub fn format( + self: Printed, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + writer: anytype, + ) error{OutOfMemory}!void { + if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'"); + switch (self.value) { + .none => unreachable, + .local => |i| return std.fmt.format(writer, "t{d}", .{i}), + .local_ref => |i| return std.fmt.format(writer, "&t{d}", .{i}), + .constant => |inst| { + const o = self.object; + o.dg.renderValue(writer, inst.ty, inst.value().?) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => return, }; - inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { - if (info.bits <= nbits) { - try writer.print("{s}int{d}_t", .{ sign_prefix, nbits }); - break; - } + }, + .arg => |i| return std.fmt.format(writer, "a{d}", .{i}), + .decl => |decl| return writer.writeAll(mem.span(decl.name)), + } + } + }; +}; + +pub const CValueMap = std.AutoHashMap(*Inst, CValue); + +/// This data is available when outputting .c code for a Module. +/// It is not available when generating .h file. +pub const Object = struct { + dg: DeclGen, + gpa: *mem.Allocator, + code: std.ArrayList(u8), + value_map: CValueMap, + next_arg_index: usize = 0, + next_local_index: usize = 0, + + fn resolveInst(o: *Object, inst: *Inst) !CValue { + if (inst.value()) |_| { + return CValue{ .constant = inst }; + } + return o.value_map.get(inst).?; // Instruction does not dominate all uses! + } + + fn allocLocalValue(o: *Object) CValue { + const result = o.next_local_index; + o.next_local_index += 1; + return .{ .local = result }; + } + + fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue { + const local_value = o.allocLocalValue(); + try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability); + return local_value; + } + + fn indent(o: *Object) !void { + const indent_size = 4; + const indent_level = 1; + const indent_amt = indent_size * indent_level; + try o.code.writer().writeByteNTimes(' ', indent_amt); + } + + fn renderTypeAndName( + o: *Object, + writer: Writer, + ty: Type, + name: CValue, + mutability: Mutability, + ) error{ OutOfMemory, AnalysisFail }!void { + var suffix = std.ArrayList(u8).init(o.gpa); + defer suffix.deinit(); + + var render_ty = ty; + while (render_ty.zigTypeTag() == .Array) { + const sentinel_bit = @boolToInt(render_ty.sentinel() != null); + const c_len = render_ty.arrayLen() + sentinel_bit; + try suffix.writer().print("[{d}]", .{c_len}); + render_ty = render_ty.elemType(); + } + + try o.dg.renderType(writer, render_ty); + + const const_prefix = switch (mutability) { + .Const => "const ", + .Mut => "", + }; + try writer.print(" {s}{}{s}", .{ const_prefix, name.printed(o), suffix.items }); + } +}; + +/// This data is available both when outputting .c code and when outputting an .h file. +const DeclGen = struct { + module: *Module, + decl: *Decl, + fwd_decl: std.ArrayList(u8), + error_msg: ?*Compilation.ErrorMsg, + + fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { + dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args); + return error.AnalysisFail; + } + + fn renderValue( + dg: *DeclGen, + writer: Writer, + t: Type, + val: Value, + ) error{ OutOfMemory, AnalysisFail }!void { + switch (t.zigTypeTag()) { + .Int => { + if (t.isSignedInt()) + return writer.print("{d}", .{val.toSignedInt()}); + return writer.print("{d}", .{val.toUnsignedInt()}); + }, + .Pointer => switch (val.tag()) { + .undef, .zero => try writer.writeAll("0"), + .one => try writer.writeAll("1"), + .decl_ref => { + const decl = val.castTag(.decl_ref).?.data; + + // Determine if we must pointer cast. + const decl_tv = decl.typed_value.most_recent.typed_value; + if (t.eql(decl_tv.ty)) { + try writer.print("&{s}", .{decl.name}); } else { - return ctx.fail(ctx.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); + try writer.writeAll("("); + try dg.renderType(writer, t); + try writer.print(")&{s}", .{decl.name}); } }, + .function => { + const func = val.castTag(.function).?.data; + try writer.print("{s}", .{func.owner_decl.name}); + }, + .extern_fn => { + const decl = val.castTag(.extern_fn).?.data; + try writer.print("{s}", .{decl.name}); + }, + else => |e| return dg.fail( + dg.decl.src(), + "TODO: C backend: implement Pointer value {s}", + .{@tagName(e)}, + ), + }, + .Array => { + // First try specific tag representations for more efficiency. + switch (val.tag()) { + .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), + .bytes => { + const bytes = val.castTag(.bytes).?.data; + // TODO: make our own C string escape instead of using {Z} + try writer.print("\"{Z}\"", .{bytes}); + }, + else => { + // Fall back to generic implementation. + var arena = std.heap.ArenaAllocator.init(dg.module.gpa); + defer arena.deinit(); + + try writer.writeAll("{"); + var index: usize = 0; + const len = t.arrayLen(); + const elem_ty = t.elemType(); + while (index < len) : (index += 1) { + if (index != 0) try writer.writeAll(","); + const elem_val = try val.elemValue(&arena.allocator, index); + try dg.renderValue(writer, elem_ty, elem_val); + } + if (t.sentinel()) |sentinel_val| { + if (index != 0) try writer.writeAll(","); + try dg.renderValue(writer, elem_ty, sentinel_val); + } + try writer.writeAll("}"); + }, + } + }, + else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ + @tagName(e), + }), + } + } + + fn renderFunctionSignature(dg: *DeclGen, w: Writer) !void { + const tv = dg.decl.typed_value.most_recent.typed_value; + // Determine whether the function is globally visible. + const is_global = blk: { + switch (tv.val.tag()) { + .extern_fn => break :blk true, + .function => { + const func = tv.val.castTag(.function).?.data; + break :blk dg.module.decl_exports.contains(func.owner_decl); + }, else => unreachable, } - }, - .Pointer => { - if (t.isSlice()) { - return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{}); - } else { - try renderType(ctx, writer, t.elemType()); - try writer.writeAll(" *"); - if (t.isConstPtr()) { - try writer.writeAll("const "); - } - if (t.isVolatilePtr()) { - try writer.writeAll("volatile "); + }; + if (!is_global) { + try w.writeAll("static "); + } + try dg.renderType(w, tv.ty.fnReturnType()); + const decl_name = mem.span(dg.decl.name); + try w.print(" {s}(", .{decl_name}); + var param_len = tv.ty.fnParamLen(); + if (param_len == 0) + try w.writeAll("void") + else { + var index: usize = 0; + while (index < param_len) : (index += 1) { + if (index > 0) { + try w.writeAll(", "); } + try dg.renderType(w, tv.ty.fnParamType(index)); + try w.print(" a{d}", .{index}); } - }, - .Array => { - try renderType(ctx, writer, t.elemType()); - try writer.writeAll(" *"); - }, - else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement type {s}", .{ - @tagName(e), - }), + } + try w.writeByte(')'); } -} -fn renderValue( - ctx: *Context, - writer: Writer, - t: Type, - val: Value, -) error{ OutOfMemory, AnalysisFail }!void { - switch (t.zigTypeTag()) { - .Int => { - if (t.isSignedInt()) - return writer.print("{d}", .{val.toSignedInt()}); - return writer.print("{d}", .{val.toUnsignedInt()}); - }, - .Pointer => switch (val.tag()) { - .undef, .zero => try writer.writeAll("0"), - .one => try writer.writeAll("1"), - .decl_ref => { - const decl = val.castTag(.decl_ref).?.data; - - // Determine if we must pointer cast. - const decl_tv = decl.typed_value.most_recent.typed_value; - if (t.eql(decl_tv.ty)) { - try writer.print("&{s}", .{decl.name}); + fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void { + switch (t.zigTypeTag()) { + .NoReturn => { + try w.writeAll("zig_noreturn void"); + }, + .Void => try w.writeAll("void"), + .Bool => try w.writeAll("bool"), + .Int => { + switch (t.tag()) { + .u8 => try w.writeAll("uint8_t"), + .i8 => try w.writeAll("int8_t"), + .u16 => try w.writeAll("uint16_t"), + .i16 => try w.writeAll("int16_t"), + .u32 => try w.writeAll("uint32_t"), + .i32 => try w.writeAll("int32_t"), + .u64 => try w.writeAll("uint64_t"), + .i64 => try w.writeAll("int64_t"), + .usize => try w.writeAll("uintptr_t"), + .isize => try w.writeAll("intptr_t"), + .c_short => try w.writeAll("short"), + .c_ushort => try w.writeAll("unsigned short"), + .c_int => try w.writeAll("int"), + .c_uint => try w.writeAll("unsigned int"), + .c_long => try w.writeAll("long"), + .c_ulong => try w.writeAll("unsigned long"), + .c_longlong => try w.writeAll("long long"), + .c_ulonglong => try w.writeAll("unsigned long long"), + .int_signed, .int_unsigned => { + const info = t.intInfo(dg.module.getTarget()); + const sign_prefix = switch (info.signedness) { + .signed => "i", + .unsigned => "", + }; + inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { + if (info.bits <= nbits) { + try w.print("{s}int{d}_t", .{ sign_prefix, nbits }); + break; + } + } else { + return dg.fail(dg.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); + } + }, + else => unreachable, + } + }, + .Pointer => { + if (t.isSlice()) { + return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{}); } else { - try writer.writeAll("("); - try renderType(ctx, writer, t); - try writer.print(")&{s}", .{decl.name}); + try dg.renderType(w, t.elemType()); + try w.writeAll(" *"); + if (t.isConstPtr()) { + try w.writeAll("const "); + } + if (t.isVolatilePtr()) { + try w.writeAll("volatile "); + } } }, - .function => { - const func = val.castTag(.function).?.data; - try writer.print("{s}", .{func.owner_decl.name}); + .Array => { + try dg.renderType(w, t.elemType()); + try w.writeAll(" *"); }, - .extern_fn => { - const decl = val.castTag(.extern_fn).?.data; - try writer.print("{s}", .{decl.name}); - }, - else => |e| return ctx.fail( - ctx.decl.src(), - "TODO: C backend: implement Pointer value {s}", - .{@tagName(e)}, - ), - }, - .Array => { - // First try specific tag representations for more efficiency. - switch (val.tag()) { - .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), - .bytes => { - const bytes = val.castTag(.bytes).?.data; - // TODO: make our own C string escape instead of using {Z} - try writer.print("\"{Z}\"", .{bytes}); - }, - else => { - // Fall back to generic implementation. - try writer.writeAll("{"); - var index: usize = 0; - const len = t.arrayLen(); - const elem_ty = t.elemType(); - while (index < len) : (index += 1) { - if (index != 0) try writer.writeAll(","); - const elem_val = try val.elemValue(&ctx.arena.allocator, index); - try renderValue(ctx, writer, elem_ty, elem_val); - } - if (t.sentinel()) |sentinel_val| { - if (index != 0) try writer.writeAll(","); - try renderValue(ctx, writer, elem_ty, sentinel_val); - } - try writer.writeAll("}"); - }, - } - }, - else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{ - @tagName(e), - }), - } -} - -fn renderFunctionSignature( - ctx: *Context, - writer: Writer, - decl: *Decl, -) !void { - const tv = decl.typed_value.most_recent.typed_value; - // Determine whether the function is globally visible. - const is_global = blk: { - switch (tv.val.tag()) { - .extern_fn => break :blk true, - .function => { - const func = tv.val.castTag(.function).?.data; - break :blk ctx.module.decl_exports.contains(func.owner_decl); - }, - else => unreachable, - } - }; - if (!is_global) { - try writer.writeAll("static "); - } - try renderType(ctx, writer, tv.ty.fnReturnType()); - // Use the child allocator directly, as we know the name can be freed before - // the rest of the arena. - const decl_name = mem.span(decl.name); - const name = try map(ctx.arena.child_allocator, decl_name); - defer ctx.arena.child_allocator.free(name); - try writer.print(" {s}(", .{name}); - var param_len = tv.ty.fnParamLen(); - if (param_len == 0) - try writer.writeAll("void") - else { - var index: usize = 0; - while (index < param_len) : (index += 1) { - if (index > 0) { - try writer.writeAll(", "); - } - try renderType(ctx, writer, tv.ty.fnParamType(index)); - try writer.print(" arg{d}", .{index}); + else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ + @tagName(e), + }), } } - try writer.writeByte(')'); -} +}; -fn indent(file: *C) !void { - const indent_size = 4; - const indent_level = 1; - const indent_amt = indent_size * indent_level; - try file.main.writer().writeByteNTimes(' ', indent_amt); -} +pub fn genDecl(o: *Object) !void { + const tracy = trace(@src()); + defer tracy.end(); -pub fn generate(file: *C, module: *Module, decl: *Decl) !void { - const tv = decl.typed_value.most_recent.typed_value; - - var arena = std.heap.ArenaAllocator.init(file.base.allocator); - defer arena.deinit(); - 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, - .target = file.base.options.target, - .header = &file.header, - .module = module, - }; - defer { - file.error_msg = ctx.error_msg; - ctx.deinit(); - } + const tv = o.dg.decl.typed_value.most_recent.typed_value; if (tv.val.castTag(.function)) |func_payload| { - const writer = file.main.writer(); - try renderFunctionSignature(&ctx, writer, decl); - - try writer.writeAll(" {"); + const fwd_decl_writer = o.dg.fwd_decl.writer(); + try o.dg.renderFunctionSignature(fwd_decl_writer); + try fwd_decl_writer.writeAll(";\n"); const func: *Module.Fn = func_payload.data; const instructions = func.body.instructions; - if (instructions.len > 0) { - try writer.writeAll("\n"); - for (instructions) |inst| { - if (switch (inst.tag) { - .add => try genBinOp(&ctx, file, inst.castTag(.add).?, "+"), - .alloc => try genAlloc(&ctx, file, inst.castTag(.alloc).?), - .arg => try genArg(&ctx), - .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), - .block => try genBlock(&ctx, file, inst.castTag(.block).?), - .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?), - .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), - .call => try genCall(&ctx, file, inst.castTag(.call).?), - .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="), - .cmp_gt => try genBinOp(&ctx, file, inst.castTag(.cmp_gt).?, ">"), - .cmp_gte => try genBinOp(&ctx, file, inst.castTag(.cmp_gte).?, ">="), - .cmp_lt => try genBinOp(&ctx, file, inst.castTag(.cmp_lt).?, "<"), - .cmp_lte => try genBinOp(&ctx, file, inst.castTag(.cmp_lte).?, "<="), - .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="), - .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), - .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), - .load => try genLoad(&ctx, file, inst.castTag(.load).?), - .ret => try genRet(&ctx, file, inst.castTag(.ret).?), - .retvoid => try genRetVoid(file), - .store => try genStore(&ctx, file, inst.castTag(.store).?), - .sub => try genBinOp(&ctx, file, inst.castTag(.sub).?, "-"), - .unreach => try genUnreach(file, inst.castTag(.unreach).?), - else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}), - }) |name| { - try ctx.inst_map.putNoClobber(inst, name); - } + const writer = o.code.writer(); + try o.dg.renderFunctionSignature(writer); + if (instructions.len == 0) { + try writer.writeAll(" {}\n\n"); + return; + } + + try writer.writeAll(" {"); + + try writer.writeAll("\n"); + for (instructions) |inst| { + const result_value = switch (inst.tag) { + .add => try genBinOp(o, inst.castTag(.add).?, "+"), + .alloc => try genAlloc(o, inst.castTag(.alloc).?), + .arg => genArg(o), + .assembly => try genAsm(o, inst.castTag(.assembly).?), + .block => try genBlock(o, inst.castTag(.block).?), + .bitcast => try genBitcast(o, inst.castTag(.bitcast).?), + .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?), + .call => try genCall(o, inst.castTag(.call).?), + .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, "=="), + .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, ">"), + .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, ">="), + .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, "<"), + .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, "<="), + .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, "!="), + .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?), + .intcast => try genIntCast(o, inst.castTag(.intcast).?), + .load => try genLoad(o, inst.castTag(.load).?), + .ret => try genRet(o, inst.castTag(.ret).?), + .retvoid => try genRetVoid(o), + .store => try genStore(o, inst.castTag(.store).?), + .sub => try genBinOp(o, inst.castTag(.sub).?, "-"), + .unreach => try genUnreach(o, inst.castTag(.unreach).?), + else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), + }; + switch (result_value) { + .none => {}, + else => try o.value_map.putNoClobber(inst, result_value), } } try writer.writeAll("}\n\n"); } else if (tv.val.tag() == .extern_fn) { - return; // handled when referenced + const writer = o.code.writer(); + try o.dg.renderFunctionSignature(writer); + try writer.writeAll(";\n"); } else { - const writer = file.constants.writer(); + const writer = o.code.writer(); try writer.writeAll("static "); // TODO ask the Decl if it is const // https://github.com/ziglang/zig/issues/7582 - try renderTypeAndName(&ctx, writer, tv.ty, mem.span(decl.name), .Mut); + const decl_c_value: CValue = .{ .decl = o.dg.decl }; + try o.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut); try writer.writeAll(" = "); - try renderValue(&ctx, writer, tv.ty, tv.val); + try o.dg.renderValue(writer, tv.ty, tv.val); try writer.writeAll(";\n"); } } -pub fn generateHeader( - comp: *Compilation, - module: *Module, - header: *C.Header, - decl: *Decl, -) error{ AnalysisFail, OutOfMemory }!void { +pub fn genHeader(comp: *Compilation, dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { + const tracy = trace(@src()); + defer tracy.end(); + switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { .Fn => { - var inst_map = std.AutoHashMap(*Inst, []u8).init(comp.gpa); - defer inst_map.deinit(); - - var arena = std.heap.ArenaAllocator.init(comp.gpa); - defer arena.deinit(); - - var ctx = Context{ - .decl = decl, - .arena = &arena, - .inst_map = &inst_map, - .target = comp.getTarget(), - .header = header, - .module = module, + dg.renderFunctionSignature() catch |err| switch (err) { + error.AnalysisFail => { + try dg.module.failed_decls.put(dg.module.gpa, decl, dg.error_msg.?); + dg.error_msg = null; + return error.AnalysisFail; + }, + else => |e| return e, }; - const writer = header.buf.writer(); - renderFunctionSignature(&ctx, 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"); + try dg.fwd_decl.appendSlice(";\n"); }, else => {}, } } -const Context = struct { - decl: *Decl, - inst_map: *std.AutoHashMap(*Inst, []u8), - arena: *std.heap.ArenaAllocator, - argdex: usize = 0, - unnamed_index: usize = 0, - error_msg: *Compilation.ErrorMsg = undefined, - target: std.Target, - header: *C.Header, - module: *Module, - - fn resolveInst(self: *Context, inst: *Inst) ![]u8 { - if (inst.value()) |val| { - var out = std.ArrayList(u8).init(&self.arena.allocator); - try renderValue(self, out.writer(), inst.ty, val); - return out.toOwnedSlice(); - } - return self.inst_map.get(inst).?; // Instruction does not dominate all uses! - } - - fn name(self: *Context) ![]u8 { - const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{d}", .{self.unnamed_index}); - self.unnamed_index += 1; - 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; - } -}; - -fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 { - const writer = file.main.writer(); +fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue { + const writer = o.code.writer(); // First line: the variable used as data storage. - try indent(file); - const local_name = try ctx.name(); + try o.indent(); const elem_type = alloc.base.ty.elemType(); const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut; - try renderTypeAndName(ctx, writer, elem_type, local_name, mutability); + const local = try o.allocLocal(elem_type, mutability); try writer.writeAll(";\n"); - // Second line: a pointer to it so that we can refer to it as the allocation. - // One line for the variable, one line for the pointer to the variable, which we return. - try indent(file); - const ptr_local_name = try ctx.name(); - try renderTypeAndName(ctx, writer, alloc.base.ty, ptr_local_name, .Const); - try writer.print(" = &{s};\n", .{local_name}); - - return ptr_local_name; + return CValue{ .local_ref = local.local }; } -fn genArg(ctx: *Context) !?[]u8 { - const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{d}", .{ctx.argdex}); - ctx.argdex += 1; - return name; +fn genArg(o: *Object) CValue { + const i = o.next_arg_index; + o.next_arg_index += 1; + return .{ .arg = i }; } -fn genRetVoid(file: *C) !?[]u8 { - try indent(file); - try file.main.writer().print("return;\n", .{}); - return null; +fn genRetVoid(o: *Object) !CValue { + try o.indent(); + try o.code.writer().print("return;\n", .{}); + return CValue.none; } -fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { - const operand = try ctx.resolveInst(inst.operand); - const writer = file.main.writer(); - try indent(file); - const local_name = try ctx.name(); - try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); - try writer.print(" = *{s};\n", .{operand}); - return local_name; +fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { + const operand = try o.resolveInst(inst.operand); + const writer = o.code.writer(); + try o.indent(); + const local = try o.allocLocal(inst.base.ty, .Const); + switch (operand) { + .local_ref => |i| { + const wrapped: CValue = .{ .local = i }; + try writer.print(" = {};\n", .{wrapped.printed(o)}); + }, + else => { + try writer.print(" = *{};\n", .{operand.printed(o)}); + }, + } + return local; } -fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { - try indent(file); - const writer = file.main.writer(); - try writer.print("return {s};\n", .{try ctx.resolveInst(inst.operand)}); - return null; +fn genRet(o: *Object, inst: *Inst.UnOp) !CValue { + const operand = try o.resolveInst(inst.operand); + try o.indent(); + try o.code.writer().print("return {};\n", .{operand.printed(o)}); + return CValue.none; } -fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { +fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue { if (inst.base.isUnused()) - return null; - try indent(file); - const writer = file.main.writer(); - const name = try ctx.name(); - const from = try ctx.resolveInst(inst.operand); + return CValue.none; - try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); + const from = try o.resolveInst(inst.operand); + + try o.indent(); + const writer = o.code.writer(); + const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); - try renderType(ctx, writer, inst.base.ty); - try writer.print("){s};\n", .{from}); - return name; + try o.dg.renderType(writer, inst.base.ty); + try writer.print("){};\n", .{from.printed(o)}); + return local; } -fn genStore(ctx: *Context, file: *C, inst: *Inst.BinOp) !?[]u8 { +fn genStore(o: *Object, inst: *Inst.BinOp) !CValue { // *a = b; - try indent(file); - const writer = file.main.writer(); - const dest_ptr_name = try ctx.resolveInst(inst.lhs); - const src_val_name = try ctx.resolveInst(inst.rhs); - try writer.print("*{s} = {s};\n", .{ dest_ptr_name, src_val_name }); - return null; + const dest_ptr = try o.resolveInst(inst.lhs); + const src_val = try o.resolveInst(inst.rhs); + + try o.indent(); + const writer = o.code.writer(); + switch (dest_ptr) { + .local_ref => |i| { + const dest: CValue = .{ .local = i }; + try writer.print("{} = {};\n", .{ dest.printed(o), src_val.printed(o) }); + }, + else => { + try writer.print("*{} = {};\n", .{ dest_ptr.printed(o), src_val.printed(o) }); + }, + } + return CValue.none; } -fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !?[]u8 { +fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { if (inst.base.isUnused()) - return null; - try indent(file); - const lhs = try ctx.resolveInst(inst.lhs); - const rhs = try ctx.resolveInst(inst.rhs); - const writer = file.main.writer(); - const name = try ctx.name(); - try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); - try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs }); - return name; + return CValue.none; + + const lhs = try o.resolveInst(inst.lhs); + const rhs = try o.resolveInst(inst.rhs); + + try o.indent(); + const writer = o.code.writer(); + const local = try o.allocLocal(inst.base.ty, .Const); + try writer.print(" = {} {s} {};\n", .{ lhs.printed(o), operator, rhs.printed(o) }); + return local; } -fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { - try indent(file); - const writer = file.main.writer(); - const header = file.header.buf.writer(); +fn genCall(o: *Object, inst: *Inst.Call) !CValue { if (inst.func.castTag(.constant)) |func_inst| { const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn| extern_fn.data @@ -501,23 +532,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty; const ret_ty = fn_ty.fnReturnType(); const unused_result = inst.base.isUnused(); - var result_name: ?[]u8 = null; + var result_local: CValue = .none; + + try o.indent(); + const writer = o.code.writer(); if (unused_result) { if (ret_ty.hasCodeGenBits()) { try writer.print("(void)", .{}); } } else { - const local_name = try ctx.name(); - try renderTypeAndName(ctx, writer, ret_ty, local_name, .Const); + result_local = try o.allocLocal(ret_ty, .Const); try writer.writeAll(" = "); - result_name = local_name; } const fn_name = mem.spanZ(fn_decl.name); - if (file.called.get(fn_name) == null) { - try file.called.put(fn_name, {}); - try renderFunctionSignature(ctx, header, fn_decl); - try header.writeAll(";\n"); - } try writer.print("{s}(", .{fn_name}); if (inst.args.len != 0) { for (inst.args) |arg, i| { @@ -525,87 +552,88 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { try writer.writeAll(", "); } if (arg.value()) |val| { - try renderValue(ctx, writer, arg.ty, val); + try o.dg.renderValue(writer, arg.ty, val); } else { - const val = try ctx.resolveInst(arg); - try writer.print("{s}", .{val}); + const val = try o.resolveInst(arg); + try writer.print("{}", .{val.printed(o)}); } } } try writer.writeAll(");\n"); - return result_name; + return result_local; } else { - return ctx.fail(ctx.decl.src(), "TODO: C backend: implement function pointers", .{}); + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{}); } } -fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { +fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue { // TODO emit #line directive here with line number and filename - return null; + return CValue.none; } -fn genBlock(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 { - return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{}); +fn genBlock(o: *Object, inst: *Inst.Block) !CValue { + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{}); } -fn genBitcast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { - const writer = file.main.writer(); - try indent(file); - const local_name = try ctx.name(); - const operand = try ctx.resolveInst(inst.operand); - try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); +fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { + const operand = try o.resolveInst(inst.operand); + + const writer = o.code.writer(); + try o.indent(); if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { + const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); - try renderType(ctx, writer, inst.base.ty); - try writer.print("){s};\n", .{operand}); - } else { - try writer.writeAll(";\n"); - try indent(file); - try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name }); + try o.dg.renderType(writer, inst.base.ty); + try writer.print("){};\n", .{operand.printed(o)}); + return local; } - return local_name; + + const local = try o.allocLocal(inst.base.ty, .Mut); + try writer.writeAll(";\n"); + try o.indent(); + try writer.print("memcpy(&{}, &{}, sizeof {});\n", .{ + local.printed(o), operand.printed(o), local.printed(o), + }); + return local; } -fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 { - try indent(file); - try file.main.writer().writeAll("zig_breakpoint();\n"); - return null; +fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue { + try o.indent(); + try o.code.writer().writeAll("zig_breakpoint();\n"); + return CValue.none; } -fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { - try indent(file); - try file.main.writer().writeAll("zig_unreachable();\n"); - return null; +fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { + try o.indent(); + try o.code.writer().writeAll("zig_unreachable();\n"); + return CValue.none; } -fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { - try indent(file); - const writer = file.main.writer(); +fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { + if (as.base.isUnused() and !as.is_volatile) + return CValue.none; + + const writer = o.code.writer(); 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]; + const arg_c_value = try o.resolveInst(arg); + try o.indent(); try writer.writeAll("register "); - try renderType(ctx, writer, arg.ty); - try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); - // TODO merge constant handling into inst_map as well - if (arg.castTag(.constant)) |c| { - try renderValue(ctx, writer, arg.ty, c.val); - try writer.writeAll(";\n "); - } else { - const gop = try ctx.inst_map.getOrPut(arg); - if (!gop.found_existing) { - return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); - } - try writer.print("{s};\n ", .{gop.entry.value}); - } + try o.dg.renderType(writer, arg.ty); + try writer.print(" {s}_constant __asm__(\"{s}\") = {};\n", .{ + reg, reg, arg_c_value.printed(o), + }); } else { - return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); + return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{}); } } - try writer.print("__asm {s} (\"{s}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); - if (as.output) |o| { - return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{}); + try o.indent(); + const volatile_string: []const u8 = if (as.is_volatile) "volatile " else ""; + try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source }); + if (as.output) |_| { + return o.dg.fail(o.dg.decl.src(), "TODO inline asm output", .{}); } if (as.inputs.len > 0) { if (as.output == null) { @@ -627,5 +655,9 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { } } try writer.writeAll(");\n"); - return null; + + if (as.base.isUnused()) + return CValue.none; + + return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{}); } diff --git a/src/link.zig b/src/link.zig index 488f8bf69b..18b093a07a 100644 --- a/src/link.zig +++ b/src/link.zig @@ -130,7 +130,7 @@ pub const File = struct { elf: Elf.TextBlock, coff: Coff.TextBlock, macho: MachO.TextBlock, - c: void, + c: C.DeclBlock, wasm: void, }; @@ -138,7 +138,7 @@ pub const File = struct { elf: Elf.SrcFn, coff: Coff.SrcFn, macho: MachO.SrcFn, - c: void, + c: C.FnBlock, wasm: ?Wasm.FnData, }; @@ -291,7 +291,7 @@ pub const File = struct { .coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl), .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl), - .c => {}, + .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl), .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl), } } @@ -301,7 +301,8 @@ pub const File = struct { .coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl), .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl), - .c, .wasm => {}, + .c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl), + .wasm => {}, } } @@ -312,7 +313,8 @@ pub const File = struct { .coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl), .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl), - .c, .wasm => {}, + .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl), + .wasm => {}, } } @@ -407,12 +409,13 @@ pub const File = struct { } } + /// Called when a Decl is deleted from the Module. pub fn freeDecl(base: *File, decl: *Module.Decl) void { switch (base.tag) { .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl), .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), - .c => {}, + .c => @fieldParentPtr(C, "base", base).freeDecl(decl), .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl), } } @@ -432,14 +435,14 @@ pub const File = struct { pub fn updateDeclExports( base: *File, module: *Module, - decl: *const Module.Decl, + decl: *Module.Decl, exports: []const *Module.Export, ) !void { switch (base.tag) { .coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports), .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports), - .c => return {}, + .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports), .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports), } } diff --git a/src/link/C.zig b/src/link/C.zig index 5f38c9324f..10b98b854c 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -11,45 +11,28 @@ const trace = @import("../tracy.zig").trace; const C = @This(); pub const base_tag: link.File.Tag = .c; - -pub const Header = struct { - buf: std.ArrayList(u8), - 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")); - if (self.buf.items.len > 0) { - try writer.print("{s}", .{self.buf.items}); - } - } - - pub fn deinit(self: *Header) void { - self.buf.deinit(); - self.* = undefined; - } -}; +pub const zig_h = @embedFile("C/zig.h"); base: link.File, -path: []const u8, +/// Per-declaration data. For functions this is the body, and +/// the forward declaration is stored in the FnBlock. +pub const DeclBlock = struct { + code: std.ArrayListUnmanaged(u8), -// These are only valid during a flush()! -header: Header, -constants: std.ArrayList(u8), -main: std.ArrayList(u8), -called: std.StringHashMap(void), + pub const empty: DeclBlock = .{ + .code = .{}, + }; +}; -error_msg: *Compilation.ErrorMsg = undefined, +/// Per-function data. +pub const FnBlock = struct { + fwd_decl: std.ArrayListUnmanaged(u8), + + pub const empty: FnBlock = .{ + .fwd_decl = .{}, + }; +}; pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { assert(options.object_format == .c); @@ -57,6 +40,14 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_llvm) return error.LLVMHasNoCBackend; if (options.use_lld) return error.LLDHasNoCBackend; + const file = try options.emit.?.directory.handle.createFile(sub_path, .{ + .truncate = true, + .mode = link.determineMode(options), + }); + errdefer file.close(); + + try file.writeAll(zig_h); + var c_file = try allocator.create(C); errdefer allocator.destroy(c_file); @@ -64,25 +55,75 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio .base = .{ .tag = .c, .options = options, - .file = null, + .file = file, .allocator = allocator, }, - .main = undefined, - .header = undefined, - .constants = undefined, - .called = undefined, - .path = sub_path, }; return c_file; } -pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { - self.error_msg = try Compilation.ErrorMsg.create(self.base.allocator, src, format, args); - return error.AnalysisFail; +pub fn deinit(self: *C) void { + const module = self.base.options.module orelse return; + for (module.decl_table.items()) |entry| { + self.freeDecl(entry.value); + } } -pub fn deinit(self: *C) void {} +pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {} + +pub fn freeDecl(self: *C, decl: *Module.Decl) void { + decl.link.c.code.deinit(self.base.allocator); + decl.fn_link.c.fwd_decl.deinit(self.base.allocator); +} + +pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const fwd_decl = &decl.fn_link.c.fwd_decl; + const code = &decl.link.c.code; + fwd_decl.shrinkRetainingCapacity(0); + code.shrinkRetainingCapacity(0); + + var object: codegen.Object = .{ + .dg = .{ + .module = module, + .error_msg = null, + .decl = decl, + .fwd_decl = fwd_decl.toManaged(module.gpa), + }, + .gpa = module.gpa, + .code = code.toManaged(module.gpa), + .value_map = codegen.CValueMap.init(module.gpa), + }; + defer object.value_map.deinit(); + defer object.code.deinit(); + defer object.dg.fwd_decl.deinit(); + + codegen.genDecl(&object) catch |err| switch (err) { + error.AnalysisFail => {}, + else => |e| return e, + }; + // The code may populate this error without returning error.AnalysisFail. + if (object.dg.error_msg) |msg| { + try module.failed_decls.put(module.gpa, decl, msg); + return; + } + + fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); + code.* = object.code.moveToUnmanaged(); + + // Free excess allocated memory for this Decl. + fwd_decl.shrink(module.gpa, fwd_decl.items.len); + code.shrink(module.gpa, code.items.len); +} + +pub fn updateDeclLineNumber(self: *C, module: *Module, decl: *Module.Decl) !void { + // The C backend does not have the ability to fix line numbers without re-generating + // the entire Decl. + return self.updateDecl(module, decl); +} pub fn flush(self: *C, comp: *Compilation) !void { return self.flushModule(comp); @@ -92,41 +133,45 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - self.main = std.ArrayList(u8).init(self.base.allocator); - self.header = Header.init(self.base.allocator, null); - self.constants = std.ArrayList(u8).init(self.base.allocator); - self.called = std.StringHashMap(void).init(self.base.allocator); - defer self.main.deinit(); - defer self.header.deinit(); - defer self.constants.deinit(); - defer self.called.deinit(); + const file = self.base.file.?; - const module = self.base.options.module.?; - for (self.base.options.module.?.decl_table.entries.items) |kv| { - codegen.generate(self, module, kv.value) catch |err| { - if (err == error.AnalysisFail) { - try module.failed_decls.put(module.gpa, kv.value, self.error_msg); - } - return err; - }; - } + // The header is written upon opening; here we truncate and seek to after the header. + // TODO: use writev + try file.seekTo(zig_h.len); + try file.setEndPos(zig_h.len); - const file = try self.base.options.emit.?.directory.handle.createFile(self.path, .{ .truncate = true, .read = true, .mode = link.determineMode(self.base.options) }); - defer file.close(); + var buffered_writer = std.io.bufferedWriter(file.writer()); + const writer = buffered_writer.writer(); - const writer = file.writer(); - try self.header.flush(writer); - if (self.header.buf.items.len > 0) { - try writer.writeByte('\n'); - } - if (self.constants.items.len > 0) { - try writer.print("{s}\n", .{self.constants.items}); - } - if (self.main.items.len > 1) { - const last_two = self.main.items[self.main.items.len - 2 ..]; - if (std.mem.eql(u8, last_two, "\n\n")) { - self.main.items.len -= 1; + const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; + + // Forward decls and non-functions first. + // TODO: use writev + for (module.decl_table.items()) |kv| { + const decl = kv.value; + const decl_tv = decl.typed_value.most_recent.typed_value; + if (decl_tv.val.castTag(.function)) |_| { + try writer.writeAll(decl.fn_link.c.fwd_decl.items); + } else { + try writer.writeAll(decl.link.c.code.items); } } - try writer.writeAll(self.main.items); + + // Now the function bodies. + for (module.decl_table.items()) |kv| { + const decl = kv.value; + const decl_tv = decl.typed_value.most_recent.typed_value; + if (decl_tv.val.castTag(.function)) |_| { + try writer.writeAll(decl.link.c.code.items); + } + } + + try buffered_writer.flush(); } + +pub fn updateDeclExports( + self: *C, + module: *Module, + decl: *Module.Decl, + exports: []const *Module.Export, +) !void {} diff --git a/src/link/cbe.h b/src/link/C/zig.h similarity index 99% rename from src/link/cbe.h rename to src/link/C/zig.h index 8452af8fbc..49f97210eb 100644 --- a/src/link/cbe.h +++ b/src/link/C/zig.h @@ -42,3 +42,4 @@ #define int128_t __int128 #define uint128_t unsigned __int128 #include + diff --git a/src/test.zig b/src/test.zig index 67a30f1f32..f630898189 100644 --- a/src/test.zig +++ b/src/test.zig @@ -13,7 +13,7 @@ const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_d const ThreadPool = @import("ThreadPool.zig"); const CrossTarget = std.zig.CrossTarget; -const c_header = @embedFile("link/cbe.h"); +const zig_h = link.File.C.zig_h; test "self-hosted" { var ctx = TestContext.init(); @@ -324,11 +324,11 @@ pub const TestContext = struct { } pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target, .Zig).addCompareObjectFile(src, c_header ++ out); + ctx.addC(name, target, .Zig).addCompareObjectFile(src, zig_h ++ out); } pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { - ctx.addC(name, target, .Zig).addHeader(src, c_header ++ out); + ctx.addC(name, target, .Zig).addHeader(src, zig_h ++ out); } pub fn addCompareOutput( @@ -700,11 +700,12 @@ pub const TestContext = struct { }, } } - if (comp.bin_file.cast(link.File.C)) |c_file| { - std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ - c_file.main.items, - }); - } + // TODO print generated C code + //if (comp.bin_file.cast(link.File.C)) |c_file| { + // std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ + // c_file.main.items, + // }); + //} std.debug.print("Test failed.\n", .{}); std.process.exit(1); } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index a740a8851a..9947c90f13 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -22,8 +22,6 @@ pub fn addCases(ctx: *TestContext) !void { , "hello world!" ++ std.cstr.line_sep); // Now change the message only - // TODO fix C backend not supporting updates - // https://github.com/ziglang/zig/issues/7589 case.addCompareOutput( \\extern fn puts(s: [*:0]const u8) c_int; \\export fn main() c_int { From e1811f72eb1bae11934dfd423baa5b26efd99afd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 13:04:51 -0700 Subject: [PATCH 3/7] stage2: link.C: use pwritev --- lib/std/fs/file.zig | 8 +++++++ src/link/C.zig | 58 ++++++++++++++++++++++++++++++--------------- 2 files changed, 47 insertions(+), 19 deletions(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index d8fa529c17..2d5b311ece 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -459,6 +459,7 @@ pub const File = struct { return index; } + /// See https://github.com/ziglang/zig/issues/7699 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { if (is_windows) { // TODO improve this to use ReadFileScatter @@ -479,6 +480,7 @@ pub const File = struct { /// is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. + /// See https://github.com/ziglang/zig/issues/7699 pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize { if (iovecs.len == 0) return; @@ -500,6 +502,7 @@ pub const File = struct { } } + /// See https://github.com/ziglang/zig/issues/7699 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize { if (is_windows) { // TODO improve this to use ReadFileScatter @@ -520,6 +523,7 @@ pub const File = struct { /// is not an error condition. /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial reads from the underlying OS layer. + /// See https://github.com/ziglang/zig/issues/7699 pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void { if (iovecs.len == 0) return; @@ -582,6 +586,7 @@ pub const File = struct { } } + /// See https://github.com/ziglang/zig/issues/7699 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize { if (is_windows) { // TODO improve this to use WriteFileScatter @@ -599,6 +604,7 @@ pub const File = struct { /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. + /// See https://github.com/ziglang/zig/issues/7699 pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { if (iovecs.len == 0) return; @@ -615,6 +621,7 @@ pub const File = struct { } } + /// See https://github.com/ziglang/zig/issues/7699 pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize { if (is_windows) { // TODO improve this to use WriteFileScatter @@ -632,6 +639,7 @@ pub const File = struct { /// The `iovecs` parameter is mutable because this function needs to mutate the fields in /// order to handle partial writes from the underlying OS layer. + /// See https://github.com/ziglang/zig/issues/7699 pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void { if (iovecs.len == 0) return; diff --git a/src/link/C.zig b/src/link/C.zig index 10b98b854c..68eb56c5b9 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -41,13 +41,12 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio if (options.use_lld) return error.LLDHasNoCBackend; const file = try options.emit.?.directory.handle.createFile(sub_path, .{ - .truncate = true, + // Truncation is done on `flush`. + .truncate = false, .mode = link.determineMode(options), }); errdefer file.close(); - try file.writeAll(zig_h); - var c_file = try allocator.create(C); errdefer allocator.destroy(c_file); @@ -133,40 +132,61 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - const file = self.base.file.?; + const module = self.base.options.module orelse + return error.LinkingWithoutZigSourceUnimplemented; - // The header is written upon opening; here we truncate and seek to after the header. - // TODO: use writev - try file.seekTo(zig_h.len); - try file.setEndPos(zig_h.len); + // We collect a list of buffers to write, and write them all at once with pwritev 😎 + var all_buffers = std.ArrayList(std.os.iovec_const).init(comp.gpa); + defer all_buffers.deinit(); - var buffered_writer = std.io.bufferedWriter(file.writer()); - const writer = buffered_writer.writer(); + // This is at least enough until we get to the function bodies without error handling. + try all_buffers.ensureCapacity(module.decl_table.count() + 1); - const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; + var file_size: u64 = zig_h.len; + all_buffers.appendAssumeCapacity(.{ + .iov_base = zig_h, + .iov_len = zig_h.len, + }); + + var fn_count: usize = 0; // Forward decls and non-functions first. - // TODO: use writev for (module.decl_table.items()) |kv| { const decl = kv.value; const decl_tv = decl.typed_value.most_recent.typed_value; - if (decl_tv.val.castTag(.function)) |_| { - try writer.writeAll(decl.fn_link.c.fwd_decl.items); - } else { - try writer.writeAll(decl.link.c.code.items); - } + const buf = buf: { + if (decl_tv.val.castTag(.function)) |_| { + fn_count += 1; + break :buf decl.fn_link.c.fwd_decl.items; + } else { + break :buf decl.link.c.code.items; + } + }; + all_buffers.appendAssumeCapacity(.{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }); + file_size += buf.len; } // Now the function bodies. + try all_buffers.ensureCapacity(all_buffers.items.len + fn_count); for (module.decl_table.items()) |kv| { const decl = kv.value; const decl_tv = decl.typed_value.most_recent.typed_value; if (decl_tv.val.castTag(.function)) |_| { - try writer.writeAll(decl.link.c.code.items); + const buf = decl.link.c.code.items; + all_buffers.appendAssumeCapacity(.{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }); + file_size += buf.len; } } - try buffered_writer.flush(); + const file = self.base.file.?; + try file.setEndPos(file_size); + try file.pwritevAll(all_buffers.items, 0); } pub fn updateDeclExports( From 58cfaa5982fc6c216627fb6f2cfd0d0e4e89d92b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 13:06:32 -0700 Subject: [PATCH 4/7] stage2: C backend: adjust spaces around functions --- src/codegen/c.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index d87801ae2e..ab66869d31 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -341,9 +341,10 @@ pub fn genDecl(o: *Object) !void { const func: *Module.Fn = func_payload.data; const instructions = func.body.instructions; const writer = o.code.writer(); + try writer.writeAll("\n"); try o.dg.renderFunctionSignature(writer); if (instructions.len == 0) { - try writer.writeAll(" {}\n\n"); + try writer.writeAll(" {}\n"); return; } @@ -382,7 +383,7 @@ pub fn genDecl(o: *Object) !void { } } - try writer.writeAll("}\n\n"); + try writer.writeAll("}\n"); } else if (tv.val.tag() == .extern_fn) { const writer = o.code.writer(); try o.dg.renderFunctionSignature(writer); From cd95444e4729761033f35d689a3b6ad6f4630552 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 13:59:33 -0700 Subject: [PATCH 5/7] stage2: C backend: remove format() hackery All C backend tests passing now, except for emit-h tests. Next task in the branch is to restore emit-h. --- src/codegen/c.zig | 131 +++++++++--------- src/link/C.zig | 10 +- src/link/C/zig.h | 31 +++-- test/stage2/cbe.zig | 313 ++++++++++++++------------------------------ 4 files changed, 189 insertions(+), 296 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index ab66869d31..5e274e0351 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -27,42 +27,6 @@ pub const CValue = union(enum) { arg: usize, /// By-value decl: *Decl, - - pub fn printed(value: CValue, object: *Object) Printed { - return .{ - .value = value, - .object = object, - }; - } - - pub const Printed = struct { - value: CValue, - object: *Object, - - /// TODO this got unwieldly, I want to remove the ability to print this way - pub fn format( - self: Printed, - comptime fmt: []const u8, - options: std.fmt.FormatOptions, - writer: anytype, - ) error{OutOfMemory}!void { - if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'"); - switch (self.value) { - .none => unreachable, - .local => |i| return std.fmt.format(writer, "t{d}", .{i}), - .local_ref => |i| return std.fmt.format(writer, "&t{d}", .{i}), - .constant => |inst| { - const o = self.object; - o.dg.renderValue(writer, inst.ty, inst.value().?) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => return, - }; - }, - .arg => |i| return std.fmt.format(writer, "a{d}", .{i}), - .decl => |decl| return writer.writeAll(mem.span(decl.name)), - } - } - }; }; pub const CValueMap = std.AutoHashMap(*Inst, CValue); @@ -103,6 +67,17 @@ pub const Object = struct { try o.code.writer().writeByteNTimes(' ', indent_amt); } + fn writeCValue(o: *Object, writer: Writer, c_value: CValue) !void { + switch (c_value) { + .none => unreachable, + .local => |i| return writer.print("t{d}", .{i}), + .local_ref => |i| return writer.print("&t{d}", .{i}), + .constant => |inst| return o.dg.renderValue(writer, inst.ty, inst.value().?), + .arg => |i| return writer.print("a{d}", .{i}), + .decl => |decl| return writer.writeAll(mem.span(decl.name)), + } + } + fn renderTypeAndName( o: *Object, writer: Writer, @@ -127,7 +102,9 @@ pub const Object = struct { .Const => "const ", .Mut => "", }; - try writer.print(" {s}{}{s}", .{ const_prefix, name.printed(o), suffix.items }); + try writer.print(" {s}", .{const_prefix}); + try o.writeCValue(writer, name); + try writer.writeAll(suffix.items); } }; @@ -353,7 +330,7 @@ pub fn genDecl(o: *Object) !void { try writer.writeAll("\n"); for (instructions) |inst| { const result_value = switch (inst.tag) { - .add => try genBinOp(o, inst.castTag(.add).?, "+"), + .add => try genBinOp(o, inst.castTag(.add).?, " + "), .alloc => try genAlloc(o, inst.castTag(.alloc).?), .arg => genArg(o), .assembly => try genAsm(o, inst.castTag(.assembly).?), @@ -361,19 +338,19 @@ pub fn genDecl(o: *Object) !void { .bitcast => try genBitcast(o, inst.castTag(.bitcast).?), .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?), .call => try genCall(o, inst.castTag(.call).?), - .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, "=="), - .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, ">"), - .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, ">="), - .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, "<"), - .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, "<="), - .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, "!="), + .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "), + .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "), + .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "), + .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "), + .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "), + .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "), .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?), .intcast => try genIntCast(o, inst.castTag(.intcast).?), .load => try genLoad(o, inst.castTag(.load).?), .ret => try genRet(o, inst.castTag(.ret).?), .retvoid => try genRetVoid(o), .store => try genStore(o, inst.castTag(.store).?), - .sub => try genBinOp(o, inst.castTag(.sub).?, "-"), + .sub => try genBinOp(o, inst.castTag(.sub).?, " - "), .unreach => try genUnreach(o, inst.castTag(.unreach).?), else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), }; @@ -457,10 +434,14 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { switch (operand) { .local_ref => |i| { const wrapped: CValue = .{ .local = i }; - try writer.print(" = {};\n", .{wrapped.printed(o)}); + try writer.writeAll(" = "); + try o.writeCValue(writer, wrapped); + try writer.writeAll(";\n"); }, else => { - try writer.print(" = *{};\n", .{operand.printed(o)}); + try writer.writeAll(" = *"); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); }, } return local; @@ -469,7 +450,10 @@ fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { fn genRet(o: *Object, inst: *Inst.UnOp) !CValue { const operand = try o.resolveInst(inst.operand); try o.indent(); - try o.code.writer().print("return {};\n", .{operand.printed(o)}); + const writer = o.code.writer(); + try writer.writeAll("return "); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); return CValue.none; } @@ -484,7 +468,9 @@ fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue { const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); try o.dg.renderType(writer, inst.base.ty); - try writer.print("){};\n", .{from.printed(o)}); + try writer.writeAll(")"); + try o.writeCValue(writer, from); + try writer.writeAll(";\n"); return local; } @@ -498,10 +484,17 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue { switch (dest_ptr) { .local_ref => |i| { const dest: CValue = .{ .local = i }; - try writer.print("{} = {};\n", .{ dest.printed(o), src_val.printed(o) }); + try o.writeCValue(writer, dest); + try writer.writeAll(" = "); + try o.writeCValue(writer, src_val); + try writer.writeAll(";\n"); }, else => { - try writer.print("*{} = {};\n", .{ dest_ptr.printed(o), src_val.printed(o) }); + try writer.writeAll("*"); + try o.writeCValue(writer, dest_ptr); + try writer.writeAll(" = "); + try o.writeCValue(writer, src_val); + try writer.writeAll(";\n"); }, } return CValue.none; @@ -517,7 +510,13 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { try o.indent(); const writer = o.code.writer(); const local = try o.allocLocal(inst.base.ty, .Const); - try writer.print(" = {} {s} {};\n", .{ lhs.printed(o), operator, rhs.printed(o) }); + + try writer.writeAll(" = "); + try o.writeCValue(writer, lhs); + try writer.writeAll(operator); + try o.writeCValue(writer, rhs); + try writer.writeAll(";\n"); + return local; } @@ -556,7 +555,7 @@ fn genCall(o: *Object, inst: *Inst.Call) !CValue { try o.dg.renderValue(writer, arg.ty, val); } else { const val = try o.resolveInst(arg); - try writer.print("{}", .{val.printed(o)}); + try o.writeCValue(writer, val); } } } @@ -585,16 +584,25 @@ fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { const local = try o.allocLocal(inst.base.ty, .Const); try writer.writeAll(" = ("); try o.dg.renderType(writer, inst.base.ty); - try writer.print("){};\n", .{operand.printed(o)}); + + try writer.writeAll(")"); + try o.writeCValue(writer, operand); + try writer.writeAll(";\n"); return local; } const local = try o.allocLocal(inst.base.ty, .Mut); try writer.writeAll(";\n"); try o.indent(); - try writer.print("memcpy(&{}, &{}, sizeof {});\n", .{ - local.printed(o), operand.printed(o), local.printed(o), - }); + + try writer.writeAll("memcpy(&"); + try o.writeCValue(writer, local); + try writer.writeAll(", &"); + try o.writeCValue(writer, operand); + try writer.writeAll(", sizeof "); + try o.writeCValue(writer, local); + try writer.writeAll(");\n"); + return local; } @@ -623,9 +631,10 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { try o.indent(); try writer.writeAll("register "); try o.dg.renderType(writer, arg.ty); - try writer.print(" {s}_constant __asm__(\"{s}\") = {};\n", .{ - reg, reg, arg_c_value.printed(o), - }); + + try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); + try o.writeCValue(writer, arg_c_value); + try writer.writeAll(";\n"); } else { return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{}); } @@ -648,7 +657,7 @@ fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { if (index > 0) { try writer.writeAll(", "); } - try writer.print("\"\"({s}_constant)", .{reg}); + try writer.print("\"r\"({s}_constant)", .{reg}); } else { // This is blocked by the earlier test unreachable; diff --git a/src/link/C.zig b/src/link/C.zig index 68eb56c5b9..0bca77f25f 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -101,14 +101,12 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { defer object.dg.fwd_decl.deinit(); codegen.genDecl(&object) catch |err| switch (err) { - error.AnalysisFail => {}, + error.AnalysisFail => { + try module.failed_decls.put(module.gpa, decl, object.dg.error_msg.?); + return; + }, else => |e| return e, }; - // The code may populate this error without returning error.AnalysisFail. - if (object.dg.error_msg) |msg| { - try module.failed_decls.put(module.gpa, decl, msg); - return; - } fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); code.* = object.code.moveToUnmanaged(); diff --git a/src/link/C/zig.h b/src/link/C/zig.h index 49f97210eb..fed799d348 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -22,24 +22,31 @@ #define zig_unreachable() #endif -#if defined(_MSC_VER) -#define zig_breakpoint __debugbreak() -#else -#if defined(__MINGW32__) || defined(__MINGW64__) -#define zig_breakpoint __debugbreak() -#elif defined(__clang__) -#define zig_breakpoint __builtin_debugtrap() +#if __STDC_VERSION__ >= 199901L +#define zig_restrict restrict #elif defined(__GNUC__) -#define zig_breakpoint __builtin_trap() -#elif defined(__i386__) || defined(__x86_64__) -#define zig_breakpoint __asm__ volatile("int $0x03"); +#define zig_restrict __restrict #else -#define zig_breakpoint raise(SIGTRAP) +#define zig_restrict #endif + +#if defined(_MSC_VER) +#define zig_breakpoint() __debugbreak() +#elif defined(__MINGW32__) || defined(__MINGW64__) +#define zig_breakpoint() __debugbreak() +#elif defined(__clang__) +#define zig_breakpoint() __builtin_debugtrap() +#elif defined(__GNUC__) +#define zig_breakpoint() __builtin_trap() +#elif defined(__i386__) || defined(__x86_64__) +#define zig_breakpoint() __asm__ volatile("int $0x03"); +#else +#define zig_breakpoint() raise(SIGTRAP) #endif #include +#include #define int128_t __int128 #define uint128_t unsigned __int128 -#include +void *memcpy (void *zig_restrict, const void *zig_restrict, size_t); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index 9947c90f13..e66dabc147 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -31,6 +31,100 @@ pub fn addCases(ctx: *TestContext) !void { , "yo" ++ std.cstr.line_sep); } + { + var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64); + + // Exit with 0 + case.addCompareOutput( + \\fn exitGood() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ ); + \\ unreachable; + \\} + \\ + \\export fn main() c_int { + \\ exitGood(); + \\} + , ""); + + // Pass a usize parameter to exit + case.addCompareOutput( + \\export fn main() c_int { + \\ exit(0); + \\} + \\ + \\fn exit(code: usize) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); + + // Change the parameter to u8 + case.addCompareOutput( + \\export fn main() c_int { + \\ exit(0); + \\} + \\ + \\fn exit(code: u8) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + , ""); + + // Do some arithmetic at the exit callsite + case.addCompareOutput( + \\export fn main() c_int { + \\ exitMath(1); + \\} + \\ + \\fn exitMath(a: u8) noreturn { + \\ exit(0 + a - a); + \\} + \\ + \\fn exit(code: u8) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + \\ + , ""); + + // Invert the arithmetic + case.addCompareOutput( + \\export fn main() c_int { + \\ exitMath(1); + \\} + \\ + \\fn exitMath(a: u8) noreturn { + \\ exit(a + 0 - a); + \\} + \\ + \\fn exit(code: u8) noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (code) + \\ ); + \\ unreachable; + \\} + \\ + , ""); + } + { var case = ctx.exeFromCompiledC("alloc and retptr", .{}); @@ -86,6 +180,8 @@ pub fn addCases(ctx: *TestContext) !void { \\ unreachable; \\} , + \\zig_noreturn void _start(void); + \\ \\zig_noreturn void _start(void) { \\ zig_breakpoint(); \\ zig_unreachable(); @@ -98,223 +194,6 @@ pub fn addCases(ctx: *TestContext) !void { \\void start(void); \\ ); - ctx.c("less empty start function", linux_x64, - \\fn main() noreturn { - \\ unreachable; - \\} - \\ - \\export fn _start() noreturn { - \\ main(); - \\} - , - \\static zig_noreturn void main(void); - \\ - \\static zig_noreturn void main(void) { - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - \\zig_noreturn void _start(void) { - \\ main(); - \\} - \\ - ); - // TODO: implement return values - // TODO: figure out a way to prevent asm constants from being generated - ctx.c("inline asm", linux_x64, - \\fn exitGood() noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (0) - \\ ); - \\ unreachable; - \\} - \\ - \\export fn _start() noreturn { - \\ exitGood(); - \\} - , - \\static zig_noreturn void exitGood(void); - \\ - \\static uint8_t exitGood__anon_0[6] = "{rax}"; - \\static uint8_t exitGood__anon_1[6] = "{rdi}"; - \\static uint8_t exitGood__anon_2[8] = "syscall"; - \\ - \\static zig_noreturn void exitGood(void) { - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = 0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - \\zig_noreturn void _start(void) { - \\ exitGood(); - \\} - \\ - ); - ctx.c("exit with parameter", linux_x64, - \\export fn _start() noreturn { - \\ exit(0); - \\} - \\ - \\fn exit(code: usize) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , - \\static zig_noreturn void exit(uintptr_t arg0); - \\ - \\static uint8_t exit__anon_0[6] = "{rax}"; - \\static uint8_t exit__anon_1[6] = "{rdi}"; - \\static uint8_t exit__anon_2[8] = "syscall"; - \\ - \\zig_noreturn void _start(void) { - \\ exit(0); - \\} - \\ - \\static zig_noreturn void exit(uintptr_t arg0) { - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = arg0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - ); - ctx.c("exit with u8 parameter", linux_x64, - \\export fn _start() noreturn { - \\ exit(0); - \\} - \\ - \\fn exit(code: u8) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , - \\static zig_noreturn void exit(uint8_t arg0); - \\ - \\static uint8_t exit__anon_0[6] = "{rax}"; - \\static uint8_t exit__anon_1[6] = "{rdi}"; - \\static uint8_t exit__anon_2[8] = "syscall"; - \\ - \\zig_noreturn void _start(void) { - \\ exit(0); - \\} - \\ - \\static zig_noreturn void exit(uint8_t arg0) { - \\ uintptr_t const __temp_0 = (uintptr_t)arg0; - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - ); - ctx.c("exit with u8 arithmetic", linux_x64, - \\export fn _start() noreturn { - \\ exitMath(1); - \\} - \\ - \\fn exitMath(a: u8) noreturn { - \\ exit(0 + a - a); - \\} - \\ - \\fn exit(code: u8) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , - \\static zig_noreturn void exitMath(uint8_t arg0); - \\static zig_noreturn void exit(uint8_t arg0); - \\ - \\static uint8_t exit__anon_0[6] = "{rax}"; - \\static uint8_t exit__anon_1[6] = "{rdi}"; - \\static uint8_t exit__anon_2[8] = "syscall"; - \\ - \\zig_noreturn void _start(void) { - \\ exitMath(1); - \\} - \\ - \\static zig_noreturn void exitMath(uint8_t arg0) { - \\ uint8_t const __temp_0 = 0 + arg0; - \\ uint8_t const __temp_1 = __temp_0 - arg0; - \\ exit(__temp_1); - \\} - \\ - \\static zig_noreturn void exit(uint8_t arg0) { - \\ uintptr_t const __temp_0 = (uintptr_t)arg0; - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - ); - ctx.c("exit with u8 arithmetic inverted", linux_x64, - \\export fn _start() noreturn { - \\ exitMath(1); - \\} - \\ - \\fn exitMath(a: u8) noreturn { - \\ exit(a + 0 - a); - \\} - \\ - \\fn exit(code: u8) noreturn { - \\ asm volatile ("syscall" - \\ : - \\ : [number] "{rax}" (231), - \\ [arg1] "{rdi}" (code) - \\ ); - \\ unreachable; - \\} - \\ - , - \\static zig_noreturn void exitMath(uint8_t arg0); - \\static zig_noreturn void exit(uint8_t arg0); - \\ - \\static uint8_t exit__anon_0[6] = "{rax}"; - \\static uint8_t exit__anon_1[6] = "{rdi}"; - \\static uint8_t exit__anon_2[8] = "syscall"; - \\ - \\zig_noreturn void _start(void) { - \\ exitMath(1); - \\} - \\ - \\static zig_noreturn void exitMath(uint8_t arg0) { - \\ uint8_t const __temp_0 = arg0 + 0; - \\ uint8_t const __temp_1 = __temp_0 - arg0; - \\ exit(__temp_1); - \\} - \\ - \\static zig_noreturn void exit(uint8_t arg0) { - \\ uintptr_t const __temp_0 = (uintptr_t)arg0; - \\ register uintptr_t rax_constant __asm__("rax") = 231; - \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; - \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); - \\ zig_breakpoint(); - \\ zig_unreachable(); - \\} - \\ - ); ctx.h("header with single param function", linux_x64, \\export fn start(a: u8) void{} , From 1a2dd85570a6439f82f7a0dd2cbf452198168c5a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 17:33:31 -0700 Subject: [PATCH 6/7] stage2: C backend: re-implement emit-h and also mark functions as `extern "C"` as appropriate to support c++ compilers. --- src/Compilation.zig | 96 +++++++++++++++++++++++++++++++++++---------- src/Module.zig | 73 ++++++++++++++++++++++++++++++---- src/codegen/c.zig | 60 +++++++++++++++------------- src/link/C.zig | 46 +++++++++++++++++++++- src/link/C/zig.h | 14 +++++-- test/stage2/cbe.zig | 24 ++++++------ 6 files changed, 239 insertions(+), 74 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index c654833270..552c550149 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -27,7 +27,6 @@ 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"); const libtsan = @import("libtsan.zig"); @@ -162,6 +161,8 @@ pub const CSourceFile = struct { const Job = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Module.Decl, + /// Render the .h file snippet for the Decl. + emit_h_decl: *Module.Decl, /// The Decl needs to be analyzed and possibly export itself. /// It may have already be analyzed, or it may have been determined /// to be outdated; in this case perform semantic analysis again. @@ -1312,9 +1313,14 @@ pub fn update(self: *Compilation) !void { // This is needed before reading the error flags. try self.bin_file.flush(self); - self.link_error_flags = self.bin_file.errorFlags(); + if (!use_stage1) { + if (self.bin_file.options.module) |module| { + try link.File.C.flushEmitH(module); + } + } + // If there are any errors, we anticipate the source files being loaded // to report error messages. Otherwise we unload all source files to save memory. if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { @@ -1340,7 +1346,8 @@ pub fn totalErrorCount(self: *Compilation) usize { var total: usize = self.failed_c_objects.items().len; if (self.bin_file.options.module) |module| { - total += module.failed_decls.items().len + + total += module.failed_decls.count() + + module.emit_h_failed_decls.count() + module.failed_exports.items().len + module.failed_files.items().len + @boolToInt(module.failed_root_src_file != null); @@ -1379,6 +1386,12 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { const source = try decl.scope.getSource(module); try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); } + for (module.emit_h_failed_decls.items()) |entry| { + const decl = entry.key; + const err_msg = entry.value; + const source = try decl.scope.getSource(module); + try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + } for (module.failed_exports.items()) |entry| { const decl = entry.key.owner_decl; const err_msg = entry.value; @@ -1476,27 +1489,68 @@ 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 = .codegen_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: {s}", - .{@errorName(err)}, - )); - decl.analysis = .codegen_failure_retryable; - }, - } - return; + self.bin_file.updateDecl(module, decl) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.AnalysisFail => { + decl.analysis = .codegen_failure; + continue; + }, + 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: {s}", + .{@errorName(err)}, + )); + decl.analysis = .codegen_failure_retryable; + continue; + }, }; }, }, + .emit_h_decl => |decl| switch (decl.analysis) { + .unreferenced => unreachable, + .in_progress => unreachable, + .outdated => unreachable, + + .sema_failure, + .dependency_failure, + .sema_failure_retryable, + => continue, + + // emit-h only requires semantic analysis of the Decl to be complete, + // it does not depend on machine code generation to succeed. + .codegen_failure, .codegen_failure_retryable, .complete => { + if (build_options.omit_stage2) + @panic("sadly stage2 is omitted from this build to save memory on the CI server"); + const module = self.bin_file.options.module.?; + const emit_loc = module.emit_h.?; + const tv = decl.typed_value.most_recent.typed_value; + const emit_h = decl.getEmitH(module); + const fwd_decl = &emit_h.fwd_decl; + fwd_decl.shrinkRetainingCapacity(0); + + var dg: c_codegen.DeclGen = .{ + .module = module, + .error_msg = null, + .decl = decl, + .fwd_decl = fwd_decl.toManaged(module.gpa), + }; + defer dg.fwd_decl.deinit(); + + c_codegen.genHeader(&dg) catch |err| switch (err) { + error.AnalysisFail => { + try module.emit_h_failed_decls.put(module.gpa, decl, dg.error_msg.?); + continue; + }, + else => |e| return e, + }; + + fwd_decl.* = dg.fwd_decl.moveToUnmanaged(); + fwd_decl.shrink(module.gpa, fwd_decl.items.len); + }, + }, .analyze_decl => |decl| { if (build_options.omit_stage2) @panic("sadly stage2 is omitted from this build to save memory on the CI server"); diff --git a/src/Module.zig b/src/Module.zig index f1cec82680..6395a7f3a5 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -57,6 +57,10 @@ decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_has /// Note that a Decl can succeed but the Fn it represents can fail. In this case, /// a Decl can have a failed_decls entry but have analysis status of success. failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, +/// When emit_h is non-null, each Decl gets one more compile error slot for +/// emit-h failing for that Decl. This table is also how we tell if a Decl has +/// failed emit-h or succeeded. +emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, /// Using a map here for consistency with the other fields here. /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, @@ -116,6 +120,13 @@ pub const Export = struct { }, }; +/// When Module emit_h field is non-null, each Decl is allocated via this struct, so that +/// there can be EmitH state attached to each Decl. +pub const DeclPlusEmitH = struct { + decl: Decl, + emit_h: EmitH, +}; + pub const Decl = struct { /// This name is relative to the containing namespace of the decl. It uses a null-termination /// to save bytes, since there can be a lot of decls in a compilation. The null byte is not allowed @@ -204,14 +215,21 @@ pub const Decl = struct { /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself` pub const DepsTable = std.ArrayHashMapUnmanaged(*Decl, void, std.array_hash_map.getAutoHashFn(*Decl), std.array_hash_map.getAutoEqlFn(*Decl), false); - pub fn destroy(self: *Decl, gpa: *Allocator) void { + pub fn destroy(self: *Decl, module: *Module) void { + const gpa = module.gpa; gpa.free(mem.spanZ(self.name)); if (self.typedValueManaged()) |tvm| { tvm.deinit(gpa); } self.dependants.deinit(gpa); self.dependencies.deinit(gpa); - gpa.destroy(self); + if (module.emit_h != null) { + const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", self); + decl_plus_emit_h.emit_h.fwd_decl.deinit(gpa); + gpa.destroy(decl_plus_emit_h); + } else { + gpa.destroy(self); + } } pub fn src(self: Decl) usize { @@ -277,6 +295,12 @@ pub const Decl = struct { return self.scope.cast(Scope.Container).?.file_scope; } + pub fn getEmitH(decl: *Decl, module: *Module) *EmitH { + assert(module.emit_h != null); + const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", decl); + return &decl_plus_emit_h.emit_h; + } + fn removeDependant(self: *Decl, other: *Decl) void { self.dependants.removeAssertDiscard(other); } @@ -286,6 +310,11 @@ pub const Decl = struct { } }; +/// This state is attached to every Decl when Module emit_h is non-null. +pub const EmitH = struct { + fwd_decl: std.ArrayListUnmanaged(u8) = .{}, +}; + /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. /// Extern functions do not have this data structure; they are represented by /// the `Decl` only, with a `Value` tag of `extern_fn`. @@ -883,7 +912,7 @@ pub fn deinit(self: *Module) void { self.deletion_set.deinit(gpa); for (self.decl_table.items()) |entry| { - entry.value.destroy(gpa); + entry.value.destroy(self); } self.decl_table.deinit(gpa); @@ -892,6 +921,11 @@ pub fn deinit(self: *Module) void { } self.failed_decls.deinit(gpa); + for (self.emit_h_failed_decls.items()) |entry| { + entry.value.destroy(gpa); + } + self.emit_h_failed_decls.deinit(gpa); + for (self.failed_files.items()) |entry| { entry.value.destroy(gpa); } @@ -1150,6 +1184,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { try self.comp.bin_file.allocateDeclIndexes(decl); try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); + if (type_changed and self.emit_h != null) { + try self.comp.work_queue.writeItem(.{ .emit_h_decl = decl }); + } + return type_changed; }; @@ -1269,6 +1307,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { // increasing how many computations can be done in parallel. try self.comp.bin_file.allocateDeclIndexes(decl); try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); + if (type_changed and self.emit_h != null) { + try self.comp.work_queue.writeItem(.{ .emit_h_decl = decl }); + } } else if (!prev_is_inline and prev_type_has_bits) { self.comp.bin_file.freeDecl(decl); } @@ -1837,9 +1878,13 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { if (self.failed_decls.remove(decl)) |entry| { entry.value.destroy(self.gpa); } + if (self.emit_h_failed_decls.remove(decl)) |entry| { + entry.value.destroy(self.gpa); + } self.deleteDeclExports(decl); self.comp.bin_file.freeDecl(decl); - decl.destroy(self.gpa); + + decl.destroy(self); } /// Delete all the Export objects that are caused by this Decl. Re-analysis of @@ -1923,16 +1968,28 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { if (self.failed_decls.remove(decl)) |entry| { entry.value.destroy(self.gpa); } + if (self.emit_h_failed_decls.remove(decl)) |entry| { + entry.value.destroy(self.gpa); + } decl.analysis = .outdated; } fn allocateNewDecl( - self: *Module, + mod: *Module, scope: *Scope, src_index: usize, contents_hash: std.zig.SrcHash, ) !*Decl { - const new_decl = try self.gpa.create(Decl); + // If we have emit-h then we must allocate a bigger structure to store the emit-h state. + const new_decl: *Decl = if (mod.emit_h != null) blk: { + const parent_struct = try mod.gpa.create(DeclPlusEmitH); + parent_struct.* = .{ + .emit_h = .{}, + .decl = undefined, + }; + break :blk &parent_struct.decl; + } else try mod.gpa.create(Decl); + new_decl.* = .{ .name = "", .scope = scope.namespace(), @@ -1941,14 +1998,14 @@ fn allocateNewDecl( .analysis = .unreferenced, .deletion_flag = false, .contents_hash = contents_hash, - .link = switch (self.comp.bin_file.tag) { + .link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = link.File.Coff.TextBlock.empty }, .elf => .{ .elf = link.File.Elf.TextBlock.empty }, .macho => .{ .macho = link.File.MachO.TextBlock.empty }, .c => .{ .c = link.File.C.DeclBlock.empty }, .wasm => .{ .wasm = {} }, }, - .fn_link = switch (self.comp.bin_file.tag) { + .fn_link = switch (mod.comp.bin_file.tag) { .coff => .{ .coff = {} }, .elf => .{ .elf = link.File.Elf.SrcFn.empty }, .macho => .{ .macho = link.File.MachO.SrcFn.empty }, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 5e274e0351..3de79e2a3d 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -9,6 +9,7 @@ const Compilation = @import("../Compilation.zig"); const Inst = @import("../ir.zig").Inst; const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; +const TypedValue = @import("../TypedValue.zig"); const C = link.File.C; const Decl = Module.Decl; const trace = @import("../tracy.zig").trace; @@ -109,7 +110,7 @@ pub const Object = struct { }; /// This data is available both when outputting .c code and when outputting an .h file. -const DeclGen = struct { +pub const DeclGen = struct { module: *Module, decl: *Decl, fwd_decl: std.ArrayList(u8), @@ -199,22 +200,11 @@ const DeclGen = struct { } } - fn renderFunctionSignature(dg: *DeclGen, w: Writer) !void { - const tv = dg.decl.typed_value.most_recent.typed_value; - // Determine whether the function is globally visible. - const is_global = blk: { - switch (tv.val.tag()) { - .extern_fn => break :blk true, - .function => { - const func = tv.val.castTag(.function).?.data; - break :blk dg.module.decl_exports.contains(func.owner_decl); - }, - else => unreachable, - } - }; + fn renderFunctionSignature(dg: *DeclGen, w: Writer, is_global: bool) !void { if (!is_global) { try w.writeAll("static "); } + const tv = dg.decl.typed_value.most_recent.typed_value; try dg.renderType(w, tv.ty.fnReturnType()); const decl_name = mem.span(dg.decl.name); try w.print(" {s}(", .{decl_name}); @@ -302,6 +292,17 @@ const DeclGen = struct { }), } } + + fn functionIsGlobal(dg: *DeclGen, tv: TypedValue) bool { + switch (tv.val.tag()) { + .extern_fn => return true, + .function => { + const func = tv.val.castTag(.function).?.data; + return dg.module.decl_exports.contains(func.owner_decl); + }, + else => unreachable, + } + } }; pub fn genDecl(o: *Object) !void { @@ -311,15 +312,19 @@ pub fn genDecl(o: *Object) !void { const tv = o.dg.decl.typed_value.most_recent.typed_value; if (tv.val.castTag(.function)) |func_payload| { + const is_global = o.dg.functionIsGlobal(tv); const fwd_decl_writer = o.dg.fwd_decl.writer(); - try o.dg.renderFunctionSignature(fwd_decl_writer); + if (is_global) { + try fwd_decl_writer.writeAll("ZIG_EXTERN_C "); + } + try o.dg.renderFunctionSignature(fwd_decl_writer, is_global); try fwd_decl_writer.writeAll(";\n"); const func: *Module.Fn = func_payload.data; const instructions = func.body.instructions; const writer = o.code.writer(); try writer.writeAll("\n"); - try o.dg.renderFunctionSignature(writer); + try o.dg.renderFunctionSignature(writer, is_global); if (instructions.len == 0) { try writer.writeAll(" {}\n"); return; @@ -363,7 +368,8 @@ pub fn genDecl(o: *Object) !void { try writer.writeAll("}\n"); } else if (tv.val.tag() == .extern_fn) { const writer = o.code.writer(); - try o.dg.renderFunctionSignature(writer); + try writer.writeAll("ZIG_EXTERN_C "); + try o.dg.renderFunctionSignature(writer, true); try writer.writeAll(";\n"); } else { const writer = o.code.writer(); @@ -381,20 +387,20 @@ pub fn genDecl(o: *Object) !void { } } -pub fn genHeader(comp: *Compilation, dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { +pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { const tracy = trace(@src()); defer tracy.end(); - switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { + const tv = dg.decl.typed_value.most_recent.typed_value; + const writer = dg.fwd_decl.writer(); + + switch (tv.ty.zigTypeTag()) { .Fn => { - dg.renderFunctionSignature() catch |err| switch (err) { - error.AnalysisFail => { - try dg.module.failed_decls.put(dg.module.gpa, decl, dg.error_msg.?); - dg.error_msg = null; - return error.AnalysisFail; - }, - else => |e| return e, - }; + const is_global = dg.functionIsGlobal(tv); + if (is_global) { + try writer.writeAll("ZIG_EXTERN_C "); + } + try dg.renderFunctionSignature(writer, is_global); try dg.fwd_decl.appendSlice(";\n"); }, else => {}, diff --git a/src/link/C.zig b/src/link/C.zig index 0bca77f25f..32d859f43e 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -130,8 +130,10 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); - const module = self.base.options.module orelse - return error.LinkingWithoutZigSourceUnimplemented; + const module = self.base.options.module.?; + + // This code path happens exclusively with -ofmt=c. The flush logic for + // emit-h is in `flushEmitH` below. // We collect a list of buffers to write, and write them all at once with pwritev 😎 var all_buffers = std.ArrayList(std.os.iovec_const).init(comp.gpa); @@ -187,6 +189,46 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { try file.pwritevAll(all_buffers.items, 0); } +pub fn flushEmitH(module: *Module) !void { + const tracy = trace(@src()); + defer tracy.end(); + + const emit_h_loc = module.emit_h orelse return; + + // We collect a list of buffers to write, and write them all at once with pwritev 😎 + var all_buffers = std.ArrayList(std.os.iovec_const).init(module.gpa); + defer all_buffers.deinit(); + + try all_buffers.ensureCapacity(module.decl_table.count() + 1); + + var file_size: u64 = zig_h.len; + all_buffers.appendAssumeCapacity(.{ + .iov_base = zig_h, + .iov_len = zig_h.len, + }); + + for (module.decl_table.items()) |kv| { + const emit_h = kv.value.getEmitH(module); + const buf = emit_h.fwd_decl.items; + all_buffers.appendAssumeCapacity(.{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }); + file_size += buf.len; + } + + const directory = emit_h_loc.directory orelse module.comp.local_cache_directory; + const file = try directory.handle.createFile(emit_h_loc.basename, .{ + // We set the end position explicitly below; by not truncating the file, we possibly + // make it easier on the file system by doing 1 reallocation instead of two. + .truncate = false, + }); + defer file.close(); + + try file.setEndPos(file_size); + try file.pwritevAll(all_buffers.items, 0); +} + pub fn updateDeclExports( self: *C, module: *Module, diff --git a/src/link/C/zig.h b/src/link/C/zig.h index fed799d348..640dfb2345 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -23,11 +23,17 @@ #endif #if __STDC_VERSION__ >= 199901L -#define zig_restrict restrict +#define ZIG_RESTRICT restrict #elif defined(__GNUC__) -#define zig_restrict __restrict +#define ZIG_RESTRICT __restrict #else -#define zig_restrict +#define ZIG_RESTRICT +#endif + +#ifdef __cplusplus +#define ZIG_EXTERN_C extern "C" +#else +#define ZIG_EXTERN_C #endif #if defined(_MSC_VER) @@ -48,5 +54,5 @@ #include #define int128_t __int128 #define uint128_t unsigned __int128 -void *memcpy (void *zig_restrict, const void *zig_restrict, size_t); +ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t); diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index e66dabc147..9163ac1662 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -180,7 +180,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ unreachable; \\} , - \\zig_noreturn void _start(void); + \\ZIG_EXTERN_C zig_noreturn void _start(void); \\ \\zig_noreturn void _start(void) { \\ zig_breakpoint(); @@ -191,37 +191,37 @@ pub fn addCases(ctx: *TestContext) !void { ctx.h("simple header", linux_x64, \\export fn start() void{} , - \\void start(void); + \\ZIG_EXTERN_C void start(void); \\ ); ctx.h("header with single param function", linux_x64, \\export fn start(a: u8) void{} , - \\void start(uint8_t arg0); + \\ZIG_EXTERN_C void start(uint8_t a0); \\ ); ctx.h("header with multiple param function", linux_x64, \\export fn start(a: u8, b: u8, c: u8) void{} , - \\void start(uint8_t arg0, uint8_t arg1, uint8_t arg2); + \\ZIG_EXTERN_C void start(uint8_t a0, uint8_t a1, uint8_t a2); \\ ); ctx.h("header with u32 param function", linux_x64, \\export fn start(a: u32) void{} , - \\void start(uint32_t arg0); + \\ZIG_EXTERN_C void start(uint32_t a0); \\ ); ctx.h("header with usize param function", linux_x64, \\export fn start(a: usize) void{} , - \\void start(uintptr_t arg0); + \\ZIG_EXTERN_C void start(uintptr_t a0); \\ ); ctx.h("header with bool param function", linux_x64, \\export fn start(a: bool) void{} , - \\void start(bool arg0); + \\ZIG_EXTERN_C void start(bool a0); \\ ); ctx.h("header with noreturn function", linux_x64, @@ -229,7 +229,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ unreachable; \\} , - \\zig_noreturn void start(void); + \\ZIG_EXTERN_C zig_noreturn void start(void); \\ ); ctx.h("header with multiple functions", linux_x64, @@ -237,15 +237,15 @@ pub fn addCases(ctx: *TestContext) !void { \\export fn b() void{} \\export fn c() void{} , - \\void a(void); - \\void b(void); - \\void c(void); + \\ZIG_EXTERN_C void a(void); + \\ZIG_EXTERN_C void b(void); + \\ZIG_EXTERN_C void c(void); \\ ); ctx.h("header with multiple includes", linux_x64, \\export fn start(a: u32, b: usize) void{} , - \\void start(uint32_t arg0, uintptr_t arg1); + \\ZIG_EXTERN_C void start(uint32_t a0, uintptr_t a1); \\ ); } From 3e39d0c44fbf0cfb56ef0c00fc8ec7c0d5e1c1ec Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 5 Jan 2021 17:41:22 -0700 Subject: [PATCH 7/7] minor fixups from moving identifiers and files around --- CMakeLists.txt | 2 +- src/Compilation.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ed25b93d2..ebb4f74ffa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -559,7 +559,7 @@ set(ZIG_STAGE2_SOURCES "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" - "${CMAKE_SOURCE_DIR}/src/link/cbe.h" + "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" "${CMAKE_SOURCE_DIR}/src/liveness.zig" "${CMAKE_SOURCE_DIR}/src/llvm_backend.zig" diff --git a/src/Compilation.zig b/src/Compilation.zig index 552c550149..bf8fd73e93 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -3123,7 +3123,7 @@ 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 (mod.emit_h != 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, mod.emit_h, directory);