diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-01-05 11:08:34 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-01-05 17:41:14 -0700 |
| commit | 7b8cede61fc20c137aca4e02425536bfc9a5a400 (patch) | |
| tree | 9533e4e3afb63e23ab5a42ecddb18b9998ec1237 /src | |
| parent | 9360e5887ce0bf0ce204eb49f0d0b253348ef557 (diff) | |
| download | zig-7b8cede61fc20c137aca4e02425536bfc9a5a400.tar.gz zig-7b8cede61fc20c137aca4e02425536bfc9a5a400.zip | |
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.
Diffstat (limited to 'src')
| -rw-r--r-- | src/Compilation.zig | 58 | ||||
| -rw-r--r-- | src/Module.zig | 6 | ||||
| -rw-r--r-- | src/codegen/c.zig | 950 | ||||
| -rw-r--r-- | src/link.zig | 19 | ||||
| -rw-r--r-- | src/link/C.zig | 195 | ||||
| -rw-r--r-- | src/link/C/zig.h (renamed from src/link/cbe.h) | 1 | ||||
| -rw-r--r-- | src/test.zig | 17 |
7 files changed, 647 insertions, 599 deletions
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 trace = @import("../tracy.zig").trace; -const Writer = std.ArrayList(u8).Writer; +const Mutability = enum { Const, Mut }; -/// 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); -} +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, -const Mutability = enum { Const, Mut }; + 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)), + } + } + }; +}; -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); - - 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 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! } - try renderType(ctx, writer, render_ty); + fn allocLocalValue(o: *Object) CValue { + const result = o.next_local_index; + o.next_local_index += 1; + return .{ .local = result }; + } - const const_prefix = switch (mutability) { - .Const => "const ", - .Mut => "", - }; - try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items }); -} + 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 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 => "", - }; - inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { - if (info.bits <= nbits) { - try writer.print("{s}int{d}_t", .{ sign_prefix, nbits }); - break; - } + 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}); - } else { - try writer.writeAll("("); - try renderType(ctx, writer, t); - 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"); }, - .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}); + .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, + } }, - 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); + .Pointer => { + if (t.isSlice()) { + return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{}); + } else { + try dg.renderType(w, t.elemType()); + try w.writeAll(" *"); + if (t.isConstPtr()) { + try w.writeAll("const "); } - if (t.sentinel()) |sentinel_val| { - if (index != 0) try writer.writeAll(","); - try renderValue(ctx, writer, elem_ty, sentinel_val); + if (t.isVolatilePtr()) { + try w.writeAll("volatile "); } - 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}); + .Array => { + try dg.renderType(w, t.elemType()); + try w.writeAll(" *"); + }, + 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, - }; - 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; + 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, }; - 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 zig_h = @embedFile("C/zig.h"); -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(); +base: link.File, - try writer.writeAll(@embedFile("cbe.h")); - if (self.buf.items.len > 0) { - try writer.print("{s}", .{self.buf.items}); - } - } +/// 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), - pub fn deinit(self: *Header) void { - self.buf.deinit(); - self.* = undefined; - } + pub const empty: DeclBlock = .{ + .code = .{}, + }; }; -base: link.File, - -path: []const u8, +/// Per-function data. +pub const FnBlock = struct { + fwd_decl: std.ArrayListUnmanaged(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 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 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 deinit(self: *C) void {} +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 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 = self.base.file.?; - 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(); + // 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 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}); + var buffered_writer = std.io.bufferedWriter(file.writer()); + const writer = buffered_writer.writer(); + + 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); + } } - 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; + + // 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 writer.writeAll(self.main.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 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 <string.h> + 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); } |
