| ... | @@ -1,495 +1,526 @@ | ... | @@ -1,495 +1,526 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| | 2 | const mem = std.mem; |
| | 3 | const log = std.log.scoped(.c); |
| | 4 | const Writer = std.ArrayList(u8).Writer; |
| 2 | | 5 | |
| 3 | const link = @import("../link.zig"); | 6 | const link = @import("../link.zig"); |
| 4 | const Module = @import("../Module.zig"); | 7 | const Module = @import("../Module.zig"); |
| 5 | const Compilation = @import("../Compilation.zig"); | 8 | const Compilation = @import("../Compilation.zig"); |
| 6 | | | |
| 7 | const Inst = @import("../ir.zig").Inst; | 9 | const Inst = @import("../ir.zig").Inst; |
| 8 | const Value = @import("../value.zig").Value; | 10 | const Value = @import("../value.zig").Value; |
| 9 | const Type = @import("../type.zig").Type; | 11 | const Type = @import("../type.zig").Type; |
| 10 | | | |
| 11 | const C = link.File.C; | 12 | const C = link.File.C; |
| 12 | const Decl = Module.Decl; | 13 | const Decl = Module.Decl; |
| 13 | const mem = std.mem; | 14 | const trace = @import("../tracy.zig").trace; |
| 14 | const log = std.log.scoped(.c); | | |
| 15 | | 15 | |
| 16 | const Writer = std.ArrayList(u8).Writer; | 16 | const Mutability = enum { Const, Mut }; |
| 17 | | 17 | |
| 18 | /// Maps a name from Zig source to C. Currently, this will always give the same | 18 | pub const CValue = union(enum) { |
| 19 | /// output for any given input, sometimes resulting in broken identifiers. | 19 | none: void, |
| 20 | fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { | 20 | /// Index into local_names |
| 21 | return allocator.dupe(u8, name); | 21 | local: usize, |
| 22 | } | 22 | /// Index into local_names, but take the address. |
| | 23 | local_ref: usize, |
| | 24 | /// A constant instruction, to be rendered inline. |
| | 25 | constant: *Inst, |
| | 26 | /// Index into the parameters |
| | 27 | arg: usize, |
| | 28 | /// By-value |
| | 29 | decl: *Decl, |
| 23 | | 30 | |
| 24 | const Mutability = enum { Const, Mut }; | 31 | pub fn printed(value: CValue, object: *Object) Printed { |
| | 32 | return .{ |
| | 33 | .value = value, |
| | 34 | .object = object, |
| | 35 | }; |
| | 36 | } |
| | 37 | |
| | 38 | pub const Printed = struct { |
| | 39 | value: CValue, |
| | 40 | object: *Object, |
| | 41 | |
| | 42 | /// TODO this got unwieldly, I want to remove the ability to print this way |
| | 43 | pub fn format( |
| | 44 | self: Printed, |
| | 45 | comptime fmt: []const u8, |
| | 46 | options: std.fmt.FormatOptions, |
| | 47 | writer: anytype, |
| | 48 | ) error{OutOfMemory}!void { |
| | 49 | if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'"); |
| | 50 | switch (self.value) { |
| | 51 | .none => unreachable, |
| | 52 | .local => |i| return std.fmt.format(writer, "t{d}", .{i}), |
| | 53 | .local_ref => |i| return std.fmt.format(writer, "&t{d}", .{i}), |
| | 54 | .constant => |inst| { |
| | 55 | const o = self.object; |
| | 56 | o.dg.renderValue(writer, inst.ty, inst.value().?) catch |err| switch (err) { |
| | 57 | error.OutOfMemory => return error.OutOfMemory, |
| | 58 | error.AnalysisFail => return, |
| | 59 | }; |
| | 60 | }, |
| | 61 | .arg => |i| return std.fmt.format(writer, "a{d}", .{i}), |
| | 62 | .decl => |decl| return writer.writeAll(mem.span(decl.name)), |
| | 63 | } |
| | 64 | } |
| | 65 | }; |
| | 66 | }; |
| 25 | | 67 | |
| 26 | fn renderTypeAndName( | 68 | pub const CValueMap = std.AutoHashMap(*Inst, CValue); |
| 27 | ctx: *Context, | 69 | |
| 28 | writer: Writer, | 70 | /// This data is available when outputting .c code for a Module. |
| 29 | ty: Type, | 71 | /// It is not available when generating .h file. |
| 30 | name: []const u8, | 72 | pub const Object = struct { |
| 31 | mutability: Mutability, | 73 | dg: DeclGen, |
| 32 | ) error{ OutOfMemory, AnalysisFail }!void { | 74 | gpa: *mem.Allocator, |
| 33 | var suffix = std.ArrayList(u8).init(&ctx.arena.allocator); | 75 | code: std.ArrayList(u8), |
| 34 | | 76 | value_map: CValueMap, |
| 35 | var render_ty = ty; | 77 | next_arg_index: usize = 0, |
| 36 | while (render_ty.zigTypeTag() == .Array) { | 78 | next_local_index: usize = 0, |
| 37 | const sentinel_bit = @boolToInt(render_ty.sentinel() != null); | 79 | |
| 38 | const c_len = render_ty.arrayLen() + sentinel_bit; | 80 | fn resolveInst(o: *Object, inst: *Inst) !CValue { |
| 39 | try suffix.writer().print("[{d}]", .{c_len}); | 81 | if (inst.value()) |_| { |
| 40 | render_ty = render_ty.elemType(); | 82 | return CValue{ .constant = inst }; |
| | 83 | } |
| | 84 | return o.value_map.get(inst).?; // Instruction does not dominate all uses! |
| 41 | } | 85 | } |
| 42 | | 86 | |
| 43 | try renderType(ctx, writer, render_ty); | 87 | fn allocLocalValue(o: *Object) CValue { |
| | 88 | const result = o.next_local_index; |
| | 89 | o.next_local_index += 1; |
| | 90 | return .{ .local = result }; |
| | 91 | } |
| 44 | | 92 | |
| 45 | const const_prefix = switch (mutability) { | 93 | fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue { |
| 46 | .Const => "const ", | 94 | const local_value = o.allocLocalValue(); |
| 47 | .Mut => "", | 95 | try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability); |
| 48 | }; | 96 | return local_value; |
| 49 | try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items }); | 97 | } |
| 50 | } | | |
| 51 | | 98 | |
| 52 | fn renderType( | 99 | fn indent(o: *Object) !void { |
| 53 | ctx: *Context, | 100 | const indent_size = 4; |
| 54 | writer: Writer, | 101 | const indent_level = 1; |
| 55 | t: Type, | 102 | const indent_amt = indent_size * indent_level; |
| 56 | ) error{ OutOfMemory, AnalysisFail }!void { | 103 | try o.code.writer().writeByteNTimes(' ', indent_amt); |
| 57 | switch (t.zigTypeTag()) { | 104 | } |
| 58 | .NoReturn => { | 105 | |
| 59 | try writer.writeAll("zig_noreturn void"); | 106 | fn renderTypeAndName( |
| 60 | }, | 107 | o: *Object, |
| 61 | .Void => try writer.writeAll("void"), | 108 | writer: Writer, |
| 62 | .Bool => try writer.writeAll("bool"), | 109 | ty: Type, |
| 63 | .Int => { | 110 | name: CValue, |
| 64 | switch (t.tag()) { | 111 | mutability: Mutability, |
| 65 | .u8 => try writer.writeAll("uint8_t"), | 112 | ) error{ OutOfMemory, AnalysisFail }!void { |
| 66 | .i8 => try writer.writeAll("int8_t"), | 113 | var suffix = std.ArrayList(u8).init(o.gpa); |
| 67 | .u16 => try writer.writeAll("uint16_t"), | 114 | defer suffix.deinit(); |
| 68 | .i16 => try writer.writeAll("int16_t"), | 115 | |
| 69 | .u32 => try writer.writeAll("uint32_t"), | 116 | var render_ty = ty; |
| 70 | .i32 => try writer.writeAll("int32_t"), | 117 | while (render_ty.zigTypeTag() == .Array) { |
| 71 | .u64 => try writer.writeAll("uint64_t"), | 118 | const sentinel_bit = @boolToInt(render_ty.sentinel() != null); |
| 72 | .i64 => try writer.writeAll("int64_t"), | 119 | const c_len = render_ty.arrayLen() + sentinel_bit; |
| 73 | .usize => try writer.writeAll("uintptr_t"), | 120 | try suffix.writer().print("[{d}]", .{c_len}); |
| 74 | .isize => try writer.writeAll("intptr_t"), | 121 | render_ty = render_ty.elemType(); |
| 75 | .c_short => try writer.writeAll("short"), | 122 | } |
| 76 | .c_ushort => try writer.writeAll("unsigned short"), | 123 | |
| 77 | .c_int => try writer.writeAll("int"), | 124 | try o.dg.renderType(writer, render_ty); |
| 78 | .c_uint => try writer.writeAll("unsigned int"), | 125 | |
| 79 | .c_long => try writer.writeAll("long"), | 126 | const const_prefix = switch (mutability) { |
| 80 | .c_ulong => try writer.writeAll("unsigned long"), | 127 | .Const => "const ", |
| 81 | .c_longlong => try writer.writeAll("long long"), | 128 | .Mut => "", |
| 82 | .c_ulonglong => try writer.writeAll("unsigned long long"), | 129 | }; |
| 83 | .int_signed, .int_unsigned => { | 130 | try writer.print(" {s}{}{s}", .{ const_prefix, name.printed(o), suffix.items }); |
| 84 | const info = t.intInfo(ctx.target); | 131 | } |
| 85 | const sign_prefix = switch (info.signedness) { | 132 | }; |
| 86 | .signed => "i", | 133 | |
| 87 | .unsigned => "", | 134 | /// This data is available both when outputting .c code and when outputting an .h file. |
| 88 | }; | 135 | const DeclGen = struct { |
| 89 | inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { | 136 | module: *Module, |
| 90 | if (info.bits <= nbits) { | 137 | decl: *Decl, |
| 91 | try writer.print("{s}int{d}_t", .{ sign_prefix, nbits }); | 138 | fwd_decl: std.ArrayList(u8), |
| 92 | break; | 139 | error_msg: ?*Compilation.ErrorMsg, |
| 93 | } | 140 | |
| | 141 | fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { |
| | 142 | dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args); |
| | 143 | return error.AnalysisFail; |
| | 144 | } |
| | 145 | |
| | 146 | fn renderValue( |
| | 147 | dg: *DeclGen, |
| | 148 | writer: Writer, |
| | 149 | t: Type, |
| | 150 | val: Value, |
| | 151 | ) error{ OutOfMemory, AnalysisFail }!void { |
| | 152 | switch (t.zigTypeTag()) { |
| | 153 | .Int => { |
| | 154 | if (t.isSignedInt()) |
| | 155 | return writer.print("{d}", .{val.toSignedInt()}); |
| | 156 | return writer.print("{d}", .{val.toUnsignedInt()}); |
| | 157 | }, |
| | 158 | .Pointer => switch (val.tag()) { |
| | 159 | .undef, .zero => try writer.writeAll("0"), |
| | 160 | .one => try writer.writeAll("1"), |
| | 161 | .decl_ref => { |
| | 162 | const decl = val.castTag(.decl_ref).?.data; |
| | 163 | |
| | 164 | // Determine if we must pointer cast. |
| | 165 | const decl_tv = decl.typed_value.most_recent.typed_value; |
| | 166 | if (t.eql(decl_tv.ty)) { |
| | 167 | try writer.print("&{s}", .{decl.name}); |
| 94 | } else { | 168 | } else { |
| 95 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); | 169 | try writer.writeAll("("); |
| | 170 | try dg.renderType(writer, t); |
| | 171 | try writer.print(")&{s}", .{decl.name}); |
| 96 | } | 172 | } |
| 97 | }, | 173 | }, |
| | 174 | .function => { |
| | 175 | const func = val.castTag(.function).?.data; |
| | 176 | try writer.print("{s}", .{func.owner_decl.name}); |
| | 177 | }, |
| | 178 | .extern_fn => { |
| | 179 | const decl = val.castTag(.extern_fn).?.data; |
| | 180 | try writer.print("{s}", .{decl.name}); |
| | 181 | }, |
| | 182 | else => |e| return dg.fail( |
| | 183 | dg.decl.src(), |
| | 184 | "TODO: C backend: implement Pointer value {s}", |
| | 185 | .{@tagName(e)}, |
| | 186 | ), |
| | 187 | }, |
| | 188 | .Array => { |
| | 189 | // First try specific tag representations for more efficiency. |
| | 190 | switch (val.tag()) { |
| | 191 | .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), |
| | 192 | .bytes => { |
| | 193 | const bytes = val.castTag(.bytes).?.data; |
| | 194 | // TODO: make our own C string escape instead of using {Z} |
| | 195 | try writer.print("\"{Z}\"", .{bytes}); |
| | 196 | }, |
| | 197 | else => { |
| | 198 | // Fall back to generic implementation. |
| | 199 | var arena = std.heap.ArenaAllocator.init(dg.module.gpa); |
| | 200 | defer arena.deinit(); |
| | 201 | |
| | 202 | try writer.writeAll("{"); |
| | 203 | var index: usize = 0; |
| | 204 | const len = t.arrayLen(); |
| | 205 | const elem_ty = t.elemType(); |
| | 206 | while (index < len) : (index += 1) { |
| | 207 | if (index != 0) try writer.writeAll(","); |
| | 208 | const elem_val = try val.elemValue(&arena.allocator, index); |
| | 209 | try dg.renderValue(writer, elem_ty, elem_val); |
| | 210 | } |
| | 211 | if (t.sentinel()) |sentinel_val| { |
| | 212 | if (index != 0) try writer.writeAll(","); |
| | 213 | try dg.renderValue(writer, elem_ty, sentinel_val); |
| | 214 | } |
| | 215 | try writer.writeAll("}"); |
| | 216 | }, |
| | 217 | } |
| | 218 | }, |
| | 219 | else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ |
| | 220 | @tagName(e), |
| | 221 | }), |
| | 222 | } |
| | 223 | } |
| | 224 | |
| | 225 | fn renderFunctionSignature(dg: *DeclGen, w: Writer) !void { |
| | 226 | const tv = dg.decl.typed_value.most_recent.typed_value; |
| | 227 | // Determine whether the function is globally visible. |
| | 228 | const is_global = blk: { |
| | 229 | switch (tv.val.tag()) { |
| | 230 | .extern_fn => break :blk true, |
| | 231 | .function => { |
| | 232 | const func = tv.val.castTag(.function).?.data; |
| | 233 | break :blk dg.module.decl_exports.contains(func.owner_decl); |
| | 234 | }, |
| 98 | else => unreachable, | 235 | else => unreachable, |
| 99 | } | 236 | } |
| 100 | }, | 237 | }; |
| 101 | .Pointer => { | 238 | if (!is_global) { |
| 102 | if (t.isSlice()) { | 239 | try w.writeAll("static "); |
| 103 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{}); | 240 | } |
| 104 | } else { | 241 | try dg.renderType(w, tv.ty.fnReturnType()); |
| 105 | try renderType(ctx, writer, t.elemType()); | 242 | const decl_name = mem.span(dg.decl.name); |
| 106 | try writer.writeAll(" *"); | 243 | try w.print(" {s}(", .{decl_name}); |
| 107 | if (t.isConstPtr()) { | 244 | var param_len = tv.ty.fnParamLen(); |
| 108 | try writer.writeAll("const "); | 245 | if (param_len == 0) |
| 109 | } | 246 | try w.writeAll("void") |
| 110 | if (t.isVolatilePtr()) { | 247 | else { |
| 111 | try writer.writeAll("volatile "); | 248 | var index: usize = 0; |
| | 249 | while (index < param_len) : (index += 1) { |
| | 250 | if (index > 0) { |
| | 251 | try w.writeAll(", "); |
| 112 | } | 252 | } |
| | 253 | try dg.renderType(w, tv.ty.fnParamType(index)); |
| | 254 | try w.print(" a{d}", .{index}); |
| 113 | } | 255 | } |
| 114 | }, | 256 | } |
| 115 | .Array => { | 257 | try w.writeByte(')'); |
| 116 | try renderType(ctx, writer, t.elemType()); | | |
| 117 | try writer.writeAll(" *"); | | |
| 118 | }, | | |
| 119 | else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement type {s}", .{ | | |
| 120 | @tagName(e), | | |
| 121 | }), | | |
| 122 | } | 258 | } |
| 123 | } | | |
| 124 | | 259 | |
| 125 | fn renderValue( | 260 | fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void { |
| 126 | ctx: *Context, | 261 | switch (t.zigTypeTag()) { |
| 127 | writer: Writer, | 262 | .NoReturn => { |
| 128 | t: Type, | 263 | try w.writeAll("zig_noreturn void"); |
| 129 | val: Value, | | |
| 130 | ) error{ OutOfMemory, AnalysisFail }!void { | | |
| 131 | switch (t.zigTypeTag()) { | | |
| 132 | .Int => { | | |
| 133 | if (t.isSignedInt()) | | |
| 134 | return writer.print("{d}", .{val.toSignedInt()}); | | |
| 135 | return writer.print("{d}", .{val.toUnsignedInt()}); | | |
| 136 | }, | | |
| 137 | .Pointer => switch (val.tag()) { | | |
| 138 | .undef, .zero => try writer.writeAll("0"), | | |
| 139 | .one => try writer.writeAll("1"), | | |
| 140 | .decl_ref => { | | |
| 141 | const decl = val.castTag(.decl_ref).?.data; | | |
| 142 | | | |
| 143 | // Determine if we must pointer cast. | | |
| 144 | const decl_tv = decl.typed_value.most_recent.typed_value; | | |
| 145 | if (t.eql(decl_tv.ty)) { | | |
| 146 | try writer.print("&{s}", .{decl.name}); | | |
| 147 | } else { | | |
| 148 | try writer.writeAll("("); | | |
| 149 | try renderType(ctx, writer, t); | | |
| 150 | try writer.print(")&{s}", .{decl.name}); | | |
| 151 | } | | |
| 152 | }, | 264 | }, |
| 153 | .function => { | 265 | .Void => try w.writeAll("void"), |
| 154 | const func = val.castTag(.function).?.data; | 266 | .Bool => try w.writeAll("bool"), |
| 155 | try writer.print("{s}", .{func.owner_decl.name}); | 267 | .Int => { |
| 156 | }, | 268 | switch (t.tag()) { |
| 157 | .extern_fn => { | 269 | .u8 => try w.writeAll("uint8_t"), |
| 158 | const decl = val.castTag(.extern_fn).?.data; | 270 | .i8 => try w.writeAll("int8_t"), |
| 159 | try writer.print("{s}", .{decl.name}); | 271 | .u16 => try w.writeAll("uint16_t"), |
| | 272 | .i16 => try w.writeAll("int16_t"), |
| | 273 | .u32 => try w.writeAll("uint32_t"), |
| | 274 | .i32 => try w.writeAll("int32_t"), |
| | 275 | .u64 => try w.writeAll("uint64_t"), |
| | 276 | .i64 => try w.writeAll("int64_t"), |
| | 277 | .usize => try w.writeAll("uintptr_t"), |
| | 278 | .isize => try w.writeAll("intptr_t"), |
| | 279 | .c_short => try w.writeAll("short"), |
| | 280 | .c_ushort => try w.writeAll("unsigned short"), |
| | 281 | .c_int => try w.writeAll("int"), |
| | 282 | .c_uint => try w.writeAll("unsigned int"), |
| | 283 | .c_long => try w.writeAll("long"), |
| | 284 | .c_ulong => try w.writeAll("unsigned long"), |
| | 285 | .c_longlong => try w.writeAll("long long"), |
| | 286 | .c_ulonglong => try w.writeAll("unsigned long long"), |
| | 287 | .int_signed, .int_unsigned => { |
| | 288 | const info = t.intInfo(dg.module.getTarget()); |
| | 289 | const sign_prefix = switch (info.signedness) { |
| | 290 | .signed => "i", |
| | 291 | .unsigned => "", |
| | 292 | }; |
| | 293 | inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { |
| | 294 | if (info.bits <= nbits) { |
| | 295 | try w.print("{s}int{d}_t", .{ sign_prefix, nbits }); |
| | 296 | break; |
| | 297 | } |
| | 298 | } else { |
| | 299 | return dg.fail(dg.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); |
| | 300 | } |
| | 301 | }, |
| | 302 | else => unreachable, |
| | 303 | } |
| 160 | }, | 304 | }, |
| 161 | else => |e| return ctx.fail( | 305 | .Pointer => { |
| 162 | ctx.decl.src(), | 306 | if (t.isSlice()) { |
| 163 | "TODO: C backend: implement Pointer value {s}", | 307 | return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{}); |
| 164 | .{@tagName(e)}, | 308 | } else { |
| 165 | ), | 309 | try dg.renderType(w, t.elemType()); |
| 166 | }, | 310 | try w.writeAll(" *"); |
| 167 | .Array => { | 311 | if (t.isConstPtr()) { |
| 168 | // First try specific tag representations for more efficiency. | 312 | try w.writeAll("const "); |
| 169 | switch (val.tag()) { | | |
| 170 | .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), | | |
| 171 | .bytes => { | | |
| 172 | const bytes = val.castTag(.bytes).?.data; | | |
| 173 | // TODO: make our own C string escape instead of using {Z} | | |
| 174 | try writer.print("\"{Z}\"", .{bytes}); | | |
| 175 | }, | | |
| 176 | else => { | | |
| 177 | // Fall back to generic implementation. | | |
| 178 | try writer.writeAll("{"); | | |
| 179 | var index: usize = 0; | | |
| 180 | const len = t.arrayLen(); | | |
| 181 | const elem_ty = t.elemType(); | | |
| 182 | while (index < len) : (index += 1) { | | |
| 183 | if (index != 0) try writer.writeAll(","); | | |
| 184 | const elem_val = try val.elemValue(&ctx.arena.allocator, index); | | |
| 185 | try renderValue(ctx, writer, elem_ty, elem_val); | | |
| 186 | } | 313 | } |
| 187 | if (t.sentinel()) |sentinel_val| { | 314 | if (t.isVolatilePtr()) { |
| 188 | if (index != 0) try writer.writeAll(","); | 315 | try w.writeAll("volatile "); |
| 189 | try renderValue(ctx, writer, elem_ty, sentinel_val); | | |
| 190 | } | 316 | } |
| 191 | try writer.writeAll("}"); | 317 | } |
| 192 | }, | | |
| 193 | } | | |
| 194 | }, | | |
| 195 | else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{ | | |
| 196 | @tagName(e), | | |
| 197 | }), | | |
| 198 | } | | |
| 199 | } | | |
| 200 | | | |
| 201 | fn renderFunctionSignature( | | |
| 202 | ctx: *Context, | | |
| 203 | writer: Writer, | | |
| 204 | decl: *Decl, | | |
| 205 | ) !void { | | |
| 206 | const tv = decl.typed_value.most_recent.typed_value; | | |
| 207 | // Determine whether the function is globally visible. | | |
| 208 | const is_global = blk: { | | |
| 209 | switch (tv.val.tag()) { | | |
| 210 | .extern_fn => break :blk true, | | |
| 211 | .function => { | | |
| 212 | const func = tv.val.castTag(.function).?.data; | | |
| 213 | break :blk ctx.module.decl_exports.contains(func.owner_decl); | | |
| 214 | }, | 318 | }, |
| 215 | else => unreachable, | 319 | .Array => { |
| 216 | } | 320 | try dg.renderType(w, t.elemType()); |
| 217 | }; | 321 | try w.writeAll(" *"); |
| 218 | if (!is_global) { | 322 | }, |
| 219 | try writer.writeAll("static "); | 323 | else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ |
| 220 | } | 324 | @tagName(e), |
| 221 | try renderType(ctx, writer, tv.ty.fnReturnType()); | 325 | }), |
| 222 | // Use the child allocator directly, as we know the name can be freed before | | |
| 223 | // the rest of the arena. | | |
| 224 | const decl_name = mem.span(decl.name); | | |
| 225 | const name = try map(ctx.arena.child_allocator, decl_name); | | |
| 226 | defer ctx.arena.child_allocator.free(name); | | |
| 227 | try writer.print(" {s}(", .{name}); | | |
| 228 | var param_len = tv.ty.fnParamLen(); | | |
| 229 | if (param_len == 0) | | |
| 230 | try writer.writeAll("void") | | |
| 231 | else { | | |
| 232 | var index: usize = 0; | | |
| 233 | while (index < param_len) : (index += 1) { | | |
| 234 | if (index > 0) { | | |
| 235 | try writer.writeAll(", "); | | |
| 236 | } | | |
| 237 | try renderType(ctx, writer, tv.ty.fnParamType(index)); | | |
| 238 | try writer.print(" arg{d}", .{index}); | | |
| 239 | } | 326 | } |
| 240 | } | 327 | } |
| 241 | try writer.writeByte(')'); | 328 | }; |
| 242 | } | | |
| 243 | | 329 | |
| 244 | fn indent(file: *C) !void { | 330 | pub fn genDecl(o: *Object) !void { |
| 245 | const indent_size = 4; | 331 | const tracy = trace(@src()); |
| 246 | const indent_level = 1; | 332 | defer tracy.end(); |
| 247 | const indent_amt = indent_size * indent_level; | | |
| 248 | try file.main.writer().writeByteNTimes(' ', indent_amt); | | |
| 249 | } | | |
| 250 | | 333 | |
| 251 | pub fn generate(file: *C, module: *Module, decl: *Decl) !void { | 334 | const tv = o.dg.decl.typed_value.most_recent.typed_value; |
| 252 | const tv = decl.typed_value.most_recent.typed_value; | | |
| 253 | | | |
| 254 | var arena = std.heap.ArenaAllocator.init(file.base.allocator); | | |
| 255 | defer arena.deinit(); | | |
| 256 | var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); | | |
| 257 | defer inst_map.deinit(); | | |
| 258 | var ctx = Context{ | | |
| 259 | .decl = decl, | | |
| 260 | .arena = &arena, | | |
| 261 | .inst_map = &inst_map, | | |
| 262 | .target = file.base.options.target, | | |
| 263 | .header = &file.header, | | |
| 264 | .module = module, | | |
| 265 | }; | | |
| 266 | defer { | | |
| 267 | file.error_msg = ctx.error_msg; | | |
| 268 | ctx.deinit(); | | |
| 269 | } | | |
| 270 | | 335 | |
| 271 | if (tv.val.castTag(.function)) |func_payload| { | 336 | if (tv.val.castTag(.function)) |func_payload| { |
| 272 | const writer = file.main.writer(); | 337 | const fwd_decl_writer = o.dg.fwd_decl.writer(); |
| 273 | try renderFunctionSignature(&ctx, writer, decl); | 338 | try o.dg.renderFunctionSignature(fwd_decl_writer); |
| 274 | | 339 | try fwd_decl_writer.writeAll(";\n"); |
| 275 | try writer.writeAll(" {"); | | |
| 276 | | 340 | |
| 277 | const func: *Module.Fn = func_payload.data; | 341 | const func: *Module.Fn = func_payload.data; |
| 278 | const instructions = func.body.instructions; | 342 | const instructions = func.body.instructions; |
| 279 | if (instructions.len > 0) { | 343 | const writer = o.code.writer(); |
| 280 | try writer.writeAll("\n"); | 344 | try o.dg.renderFunctionSignature(writer); |
| 281 | for (instructions) |inst| { | 345 | if (instructions.len == 0) { |
| 282 | if (switch (inst.tag) { | 346 | try writer.writeAll(" {}\n\n"); |
| 283 | .add => try genBinOp(&ctx, file, inst.castTag(.add).?, "+"), | 347 | return; |
| 284 | .alloc => try genAlloc(&ctx, file, inst.castTag(.alloc).?), | 348 | } |
| 285 | .arg => try genArg(&ctx), | 349 | |
| 286 | .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), | 350 | try writer.writeAll(" {"); |
| 287 | .block => try genBlock(&ctx, file, inst.castTag(.block).?), | 351 | |
| 288 | .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?), | 352 | try writer.writeAll("\n"); |
| 289 | .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), | 353 | for (instructions) |inst| { |
| 290 | .call => try genCall(&ctx, file, inst.castTag(.call).?), | 354 | const result_value = switch (inst.tag) { |
| 291 | .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="), | 355 | .add => try genBinOp(o, inst.castTag(.add).?, "+"), |
| 292 | .cmp_gt => try genBinOp(&ctx, file, inst.castTag(.cmp_gt).?, ">"), | 356 | .alloc => try genAlloc(o, inst.castTag(.alloc).?), |
| 293 | .cmp_gte => try genBinOp(&ctx, file, inst.castTag(.cmp_gte).?, ">="), | 357 | .arg => genArg(o), |
| 294 | .cmp_lt => try genBinOp(&ctx, file, inst.castTag(.cmp_lt).?, "<"), | 358 | .assembly => try genAsm(o, inst.castTag(.assembly).?), |
| 295 | .cmp_lte => try genBinOp(&ctx, file, inst.castTag(.cmp_lte).?, "<="), | 359 | .block => try genBlock(o, inst.castTag(.block).?), |
| 296 | .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="), | 360 | .bitcast => try genBitcast(o, inst.castTag(.bitcast).?), |
| 297 | .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), | 361 | .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?), |
| 298 | .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), | 362 | .call => try genCall(o, inst.castTag(.call).?), |
| 299 | .load => try genLoad(&ctx, file, inst.castTag(.load).?), | 363 | .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, "=="), |
| 300 | .ret => try genRet(&ctx, file, inst.castTag(.ret).?), | 364 | .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, ">"), |
| 301 | .retvoid => try genRetVoid(file), | 365 | .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, ">="), |
| 302 | .store => try genStore(&ctx, file, inst.castTag(.store).?), | 366 | .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, "<"), |
| 303 | .sub => try genBinOp(&ctx, file, inst.castTag(.sub).?, "-"), | 367 | .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, "<="), |
| 304 | .unreach => try genUnreach(file, inst.castTag(.unreach).?), | 368 | .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, "!="), |
| 305 | else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}), | 369 | .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?), |
| 306 | }) |name| { | 370 | .intcast => try genIntCast(o, inst.castTag(.intcast).?), |
| 307 | try ctx.inst_map.putNoClobber(inst, name); | 371 | .load => try genLoad(o, inst.castTag(.load).?), |
| 308 | } | 372 | .ret => try genRet(o, inst.castTag(.ret).?), |
| | 373 | .retvoid => try genRetVoid(o), |
| | 374 | .store => try genStore(o, inst.castTag(.store).?), |
| | 375 | .sub => try genBinOp(o, inst.castTag(.sub).?, "-"), |
| | 376 | .unreach => try genUnreach(o, inst.castTag(.unreach).?), |
| | 377 | else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), |
| | 378 | }; |
| | 379 | switch (result_value) { |
| | 380 | .none => {}, |
| | 381 | else => try o.value_map.putNoClobber(inst, result_value), |
| 309 | } | 382 | } |
| 310 | } | 383 | } |
| 311 | | 384 | |
| 312 | try writer.writeAll("}\n\n"); | 385 | try writer.writeAll("}\n\n"); |
| 313 | } else if (tv.val.tag() == .extern_fn) { | 386 | } else if (tv.val.tag() == .extern_fn) { |
| 314 | return; // handled when referenced | 387 | const writer = o.code.writer(); |
| | 388 | try o.dg.renderFunctionSignature(writer); |
| | 389 | try writer.writeAll(";\n"); |
| 315 | } else { | 390 | } else { |
| 316 | const writer = file.constants.writer(); | 391 | const writer = o.code.writer(); |
| 317 | try writer.writeAll("static "); | 392 | try writer.writeAll("static "); |
| 318 | | 393 | |
| 319 | // TODO ask the Decl if it is const | 394 | // TODO ask the Decl if it is const |
| 320 | // https://github.com/ziglang/zig/issues/7582 | 395 | // https://github.com/ziglang/zig/issues/7582 |
| 321 | | 396 | |
| 322 | try renderTypeAndName(&ctx, writer, tv.ty, mem.span(decl.name), .Mut); | 397 | const decl_c_value: CValue = .{ .decl = o.dg.decl }; |
| | 398 | try o.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut); |
| 323 | | 399 | |
| 324 | try writer.writeAll(" = "); | 400 | try writer.writeAll(" = "); |
| 325 | try renderValue(&ctx, writer, tv.ty, tv.val); | 401 | try o.dg.renderValue(writer, tv.ty, tv.val); |
| 326 | try writer.writeAll(";\n"); | 402 | try writer.writeAll(";\n"); |
| 327 | } | 403 | } |
| 328 | } | 404 | } |
| 329 | | 405 | |
| 330 | pub fn generateHeader( | 406 | pub fn genHeader(comp: *Compilation, dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { |
| 331 | comp: *Compilation, | 407 | const tracy = trace(@src()); |
| 332 | module: *Module, | 408 | defer tracy.end(); |
| 333 | header: *C.Header, | 409 | |
| 334 | decl: *Decl, | | |
| 335 | ) error{ AnalysisFail, OutOfMemory }!void { | | |
| 336 | switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { | 410 | switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { |
| 337 | .Fn => { | 411 | .Fn => { |
| 338 | var inst_map = std.AutoHashMap(*Inst, []u8).init(comp.gpa); | 412 | dg.renderFunctionSignature() catch |err| switch (err) { |
| 339 | defer inst_map.deinit(); | 413 | error.AnalysisFail => { |
| 340 | | 414 | try dg.module.failed_decls.put(dg.module.gpa, decl, dg.error_msg.?); |
| 341 | var arena = std.heap.ArenaAllocator.init(comp.gpa); | 415 | dg.error_msg = null; |
| 342 | defer arena.deinit(); | 416 | return error.AnalysisFail; |
| 343 | | 417 | }, |
| 344 | var ctx = Context{ | 418 | else => |e| return e, |
| 345 | .decl = decl, | | |
| 346 | .arena = &arena, | | |
| 347 | .inst_map = &inst_map, | | |
| 348 | .target = comp.getTarget(), | | |
| 349 | .header = header, | | |
| 350 | .module = module, | | |
| 351 | }; | | |
| 352 | const writer = header.buf.writer(); | | |
| 353 | renderFunctionSignature(&ctx, writer, decl) catch |err| { | | |
| 354 | if (err == error.AnalysisFail) { | | |
| 355 | try module.failed_decls.put(module.gpa, decl, ctx.error_msg); | | |
| 356 | } | | |
| 357 | return err; | | |
| 358 | }; | 419 | }; |
| 359 | try writer.writeAll(";\n"); | 420 | try dg.fwd_decl.appendSlice(";\n"); |
| 360 | }, | 421 | }, |
| 361 | else => {}, | 422 | else => {}, |
| 362 | } | 423 | } |
| 363 | } | 424 | } |
| 364 | | 425 | |
| 365 | const Context = struct { | 426 | fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue { |
| 366 | decl: *Decl, | 427 | const writer = o.code.writer(); |
| 367 | inst_map: *std.AutoHashMap(*Inst, []u8), | | |
| 368 | arena: *std.heap.ArenaAllocator, | | |
| 369 | argdex: usize = 0, | | |
| 370 | unnamed_index: usize = 0, | | |
| 371 | error_msg: *Compilation.ErrorMsg = undefined, | | |
| 372 | target: std.Target, | | |
| 373 | header: *C.Header, | | |
| 374 | module: *Module, | | |
| 375 | | | |
| 376 | fn resolveInst(self: *Context, inst: *Inst) ![]u8 { | | |
| 377 | if (inst.value()) |val| { | | |
| 378 | var out = std.ArrayList(u8).init(&self.arena.allocator); | | |
| 379 | try renderValue(self, out.writer(), inst.ty, val); | | |
| 380 | return out.toOwnedSlice(); | | |
| 381 | } | | |
| 382 | return self.inst_map.get(inst).?; // Instruction does not dominate all uses! | | |
| 383 | } | | |
| 384 | | | |
| 385 | fn name(self: *Context) ![]u8 { | | |
| 386 | const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{d}", .{self.unnamed_index}); | | |
| 387 | self.unnamed_index += 1; | | |
| 388 | return val; | | |
| 389 | } | | |
| 390 | | | |
| 391 | fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { | | |
| 392 | self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args); | | |
| 393 | return error.AnalysisFail; | | |
| 394 | } | | |
| 395 | | | |
| 396 | fn deinit(self: *Context) void { | | |
| 397 | self.* = undefined; | | |
| 398 | } | | |
| 399 | }; | | |
| 400 | | | |
| 401 | fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 { | | |
| 402 | const writer = file.main.writer(); | | |
| 403 | | 428 | |
| 404 | // First line: the variable used as data storage. | 429 | // First line: the variable used as data storage. |
| 405 | try indent(file); | 430 | try o.indent(); |
| 406 | const local_name = try ctx.name(); | | |
| 407 | const elem_type = alloc.base.ty.elemType(); | 431 | const elem_type = alloc.base.ty.elemType(); |
| 408 | const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut; | 432 | const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut; |
| 409 | try renderTypeAndName(ctx, writer, elem_type, local_name, mutability); | 433 | const local = try o.allocLocal(elem_type, mutability); |
| 410 | try writer.writeAll(";\n"); | 434 | try writer.writeAll(";\n"); |
| 411 | | 435 | |
| 412 | // Second line: a pointer to it so that we can refer to it as the allocation. | 436 | return CValue{ .local_ref = local.local }; |
| 413 | // One line for the variable, one line for the pointer to the variable, which we return. | | |
| 414 | try indent(file); | | |
| 415 | const ptr_local_name = try ctx.name(); | | |
| 416 | try renderTypeAndName(ctx, writer, alloc.base.ty, ptr_local_name, .Const); | | |
| 417 | try writer.print(" = &{s};\n", .{local_name}); | | |
| 418 | | | |
| 419 | return ptr_local_name; | | |
| 420 | } | 437 | } |
| 421 | | 438 | |
| 422 | fn genArg(ctx: *Context) !?[]u8 { | 439 | fn genArg(o: *Object) CValue { |
| 423 | const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{d}", .{ctx.argdex}); | 440 | const i = o.next_arg_index; |
| 424 | ctx.argdex += 1; | 441 | o.next_arg_index += 1; |
| 425 | return name; | 442 | return .{ .arg = i }; |
| 426 | } | 443 | } |
| 427 | | 444 | |
| 428 | fn genRetVoid(file: *C) !?[]u8 { | 445 | fn genRetVoid(o: *Object) !CValue { |
| 429 | try indent(file); | 446 | try o.indent(); |
| 430 | try file.main.writer().print("return;\n", .{}); | 447 | try o.code.writer().print("return;\n", .{}); |
| 431 | return null; | 448 | return CValue.none; |
| 432 | } | 449 | } |
| 433 | | 450 | |
| 434 | fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | 451 | fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { |
| 435 | const operand = try ctx.resolveInst(inst.operand); | 452 | const operand = try o.resolveInst(inst.operand); |
| 436 | const writer = file.main.writer(); | 453 | const writer = o.code.writer(); |
| 437 | try indent(file); | 454 | try o.indent(); |
| 438 | const local_name = try ctx.name(); | 455 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 439 | try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); | 456 | switch (operand) { |
| 440 | try writer.print(" = *{s};\n", .{operand}); | 457 | .local_ref => |i| { |
| 441 | return local_name; | 458 | const wrapped: CValue = .{ .local = i }; |
| | 459 | try writer.print(" = {};\n", .{wrapped.printed(o)}); |
| | 460 | }, |
| | 461 | else => { |
| | 462 | try writer.print(" = *{};\n", .{operand.printed(o)}); |
| | 463 | }, |
| | 464 | } |
| | 465 | return local; |
| 442 | } | 466 | } |
| 443 | | 467 | |
| 444 | fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | 468 | fn genRet(o: *Object, inst: *Inst.UnOp) !CValue { |
| 445 | try indent(file); | 469 | const operand = try o.resolveInst(inst.operand); |
| 446 | const writer = file.main.writer(); | 470 | try o.indent(); |
| 447 | try writer.print("return {s};\n", .{try ctx.resolveInst(inst.operand)}); | 471 | try o.code.writer().print("return {};\n", .{operand.printed(o)}); |
| 448 | return null; | 472 | return CValue.none; |
| 449 | } | 473 | } |
| 450 | | 474 | |
| 451 | fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | 475 | fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue { |
| 452 | if (inst.base.isUnused()) | 476 | if (inst.base.isUnused()) |
| 453 | return null; | 477 | return CValue.none; |
| 454 | try indent(file); | | |
| 455 | const writer = file.main.writer(); | | |
| 456 | const name = try ctx.name(); | | |
| 457 | const from = try ctx.resolveInst(inst.operand); | | |
| 458 | | 478 | |
| 459 | try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); | 479 | const from = try o.resolveInst(inst.operand); |
| | 480 | |
| | 481 | try o.indent(); |
| | 482 | const writer = o.code.writer(); |
| | 483 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 460 | try writer.writeAll(" = ("); | 484 | try writer.writeAll(" = ("); |
| 461 | try renderType(ctx, writer, inst.base.ty); | 485 | try o.dg.renderType(writer, inst.base.ty); |
| 462 | try writer.print("){s};\n", .{from}); | 486 | try writer.print("){};\n", .{from.printed(o)}); |
| 463 | return name; | 487 | return local; |
| 464 | } | 488 | } |
| 465 | | 489 | |
| 466 | fn genStore(ctx: *Context, file: *C, inst: *Inst.BinOp) !?[]u8 { | 490 | fn genStore(o: *Object, inst: *Inst.BinOp) !CValue { |
| 467 | // *a = b; | 491 | // *a = b; |
| 468 | try indent(file); | 492 | const dest_ptr = try o.resolveInst(inst.lhs); |
| 469 | const writer = file.main.writer(); | 493 | const src_val = try o.resolveInst(inst.rhs); |
| 470 | const dest_ptr_name = try ctx.resolveInst(inst.lhs); | 494 | |
| 471 | const src_val_name = try ctx.resolveInst(inst.rhs); | 495 | try o.indent(); |
| 472 | try writer.print("*{s} = {s};\n", .{ dest_ptr_name, src_val_name }); | 496 | const writer = o.code.writer(); |
| 473 | return null; | 497 | switch (dest_ptr) { |
| | 498 | .local_ref => |i| { |
| | 499 | const dest: CValue = .{ .local = i }; |
| | 500 | try writer.print("{} = {};\n", .{ dest.printed(o), src_val.printed(o) }); |
| | 501 | }, |
| | 502 | else => { |
| | 503 | try writer.print("*{} = {};\n", .{ dest_ptr.printed(o), src_val.printed(o) }); |
| | 504 | }, |
| | 505 | } |
| | 506 | return CValue.none; |
| 474 | } | 507 | } |
| 475 | | 508 | |
| 476 | fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !?[]u8 { | 509 | fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { |
| 477 | if (inst.base.isUnused()) | 510 | if (inst.base.isUnused()) |
| 478 | return null; | 511 | return CValue.none; |
| 479 | try indent(file); | 512 | |
| 480 | const lhs = try ctx.resolveInst(inst.lhs); | 513 | const lhs = try o.resolveInst(inst.lhs); |
| 481 | const rhs = try ctx.resolveInst(inst.rhs); | 514 | const rhs = try o.resolveInst(inst.rhs); |
| 482 | const writer = file.main.writer(); | 515 | |
| 483 | const name = try ctx.name(); | 516 | try o.indent(); |
| 484 | try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); | 517 | const writer = o.code.writer(); |
| 485 | try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs }); | 518 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 486 | return name; | 519 | try writer.print(" = {} {s} {};\n", .{ lhs.printed(o), operator, rhs.printed(o) }); |
| | 520 | return local; |
| 487 | } | 521 | } |
| 488 | | 522 | |
| 489 | fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { | 523 | fn genCall(o: *Object, inst: *Inst.Call) !CValue { |
| 490 | try indent(file); | | |
| 491 | const writer = file.main.writer(); | | |
| 492 | const header = file.header.buf.writer(); | | |
| 493 | if (inst.func.castTag(.constant)) |func_inst| { | 524 | if (inst.func.castTag(.constant)) |func_inst| { |
| 494 | const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn| | 525 | const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn| |
| 495 | extern_fn.data | 526 | extern_fn.data |
| ... | @@ -501,23 +532,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { | ... | @@ -501,23 +532,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { |
| 501 | const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty; | 532 | const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty; |
| 502 | const ret_ty = fn_ty.fnReturnType(); | 533 | const ret_ty = fn_ty.fnReturnType(); |
| 503 | const unused_result = inst.base.isUnused(); | 534 | const unused_result = inst.base.isUnused(); |
| 504 | var result_name: ?[]u8 = null; | 535 | var result_local: CValue = .none; |
| | 536 | |
| | 537 | try o.indent(); |
| | 538 | const writer = o.code.writer(); |
| 505 | if (unused_result) { | 539 | if (unused_result) { |
| 506 | if (ret_ty.hasCodeGenBits()) { | 540 | if (ret_ty.hasCodeGenBits()) { |
| 507 | try writer.print("(void)", .{}); | 541 | try writer.print("(void)", .{}); |
| 508 | } | 542 | } |
| 509 | } else { | 543 | } else { |
| 510 | const local_name = try ctx.name(); | 544 | result_local = try o.allocLocal(ret_ty, .Const); |
| 511 | try renderTypeAndName(ctx, writer, ret_ty, local_name, .Const); | | |
| 512 | try writer.writeAll(" = "); | 545 | try writer.writeAll(" = "); |
| 513 | result_name = local_name; | | |
| 514 | } | 546 | } |
| 515 | const fn_name = mem.spanZ(fn_decl.name); | 547 | const fn_name = mem.spanZ(fn_decl.name); |
| 516 | if (file.called.get(fn_name) == null) { | | |
| 517 | try file.called.put(fn_name, {}); | | |
| 518 | try renderFunctionSignature(ctx, header, fn_decl); | | |
| 519 | try header.writeAll(";\n"); | | |
| 520 | } | | |
| 521 | try writer.print("{s}(", .{fn_name}); | 548 | try writer.print("{s}(", .{fn_name}); |
| 522 | if (inst.args.len != 0) { | 549 | if (inst.args.len != 0) { |
| 523 | for (inst.args) |arg, i| { | 550 | for (inst.args) |arg, i| { |
| ... | @@ -525,87 +552,88 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { | ... | @@ -525,87 +552,88 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { |
| 525 | try writer.writeAll(", "); | 552 | try writer.writeAll(", "); |
| 526 | } | 553 | } |
| 527 | if (arg.value()) |val| { | 554 | if (arg.value()) |val| { |
| 528 | try renderValue(ctx, writer, arg.ty, val); | 555 | try o.dg.renderValue(writer, arg.ty, val); |
| 529 | } else { | 556 | } else { |
| 530 | const val = try ctx.resolveInst(arg); | 557 | const val = try o.resolveInst(arg); |
| 531 | try writer.print("{s}", .{val}); | 558 | try writer.print("{}", .{val.printed(o)}); |
| 532 | } | 559 | } |
| 533 | } | 560 | } |
| 534 | } | 561 | } |
| 535 | try writer.writeAll(");\n"); | 562 | try writer.writeAll(");\n"); |
| 536 | return result_name; | 563 | return result_local; |
| 537 | } else { | 564 | } else { |
| 538 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement function pointers", .{}); | 565 | return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{}); |
| 539 | } | 566 | } |
| 540 | } | 567 | } |
| 541 | | 568 | |
| 542 | fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { | 569 | fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue { |
| 543 | // TODO emit #line directive here with line number and filename | 570 | // TODO emit #line directive here with line number and filename |
| 544 | return null; | 571 | return CValue.none; |
| 545 | } | 572 | } |
| 546 | | 573 | |
| 547 | fn genBlock(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 { | 574 | fn genBlock(o: *Object, inst: *Inst.Block) !CValue { |
| 548 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{}); | 575 | return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{}); |
| 549 | } | 576 | } |
| 550 | | 577 | |
| 551 | fn genBitcast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | 578 | fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { |
| 552 | const writer = file.main.writer(); | 579 | const operand = try o.resolveInst(inst.operand); |
| 553 | try indent(file); | 580 | |
| 554 | const local_name = try ctx.name(); | 581 | const writer = o.code.writer(); |
| 555 | const operand = try ctx.resolveInst(inst.operand); | 582 | try o.indent(); |
| 556 | try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); | | |
| 557 | if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { | 583 | if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { |
| | 584 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 558 | try writer.writeAll(" = ("); | 585 | try writer.writeAll(" = ("); |
| 559 | try renderType(ctx, writer, inst.base.ty); | 586 | try o.dg.renderType(writer, inst.base.ty); |
| 560 | try writer.print("){s};\n", .{operand}); | 587 | try writer.print("){};\n", .{operand.printed(o)}); |
| 561 | } else { | 588 | return local; |
| 562 | try writer.writeAll(";\n"); | | |
| 563 | try indent(file); | | |
| 564 | try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name }); | | |
| 565 | } | 589 | } |
| 566 | return local_name; | 590 | |
| | 591 | const local = try o.allocLocal(inst.base.ty, .Mut); |
| | 592 | try writer.writeAll(";\n"); |
| | 593 | try o.indent(); |
| | 594 | try writer.print("memcpy(&{}, &{}, sizeof {});\n", .{ |
| | 595 | local.printed(o), operand.printed(o), local.printed(o), |
| | 596 | }); |
| | 597 | return local; |
| 567 | } | 598 | } |
| 568 | | 599 | |
| 569 | fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 { | 600 | fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue { |
| 570 | try indent(file); | 601 | try o.indent(); |
| 571 | try file.main.writer().writeAll("zig_breakpoint();\n"); | 602 | try o.code.writer().writeAll("zig_breakpoint();\n"); |
| 572 | return null; | 603 | return CValue.none; |
| 573 | } | 604 | } |
| 574 | | 605 | |
| 575 | fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { | 606 | fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { |
| 576 | try indent(file); | 607 | try o.indent(); |
| 577 | try file.main.writer().writeAll("zig_unreachable();\n"); | 608 | try o.code.writer().writeAll("zig_unreachable();\n"); |
| 578 | return null; | 609 | return CValue.none; |
| 579 | } | 610 | } |
| 580 | | 611 | |
| 581 | fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { | 612 | fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { |
| 582 | try indent(file); | 613 | if (as.base.isUnused() and !as.is_volatile) |
| 583 | const writer = file.main.writer(); | 614 | return CValue.none; |
| | 615 | |
| | 616 | const writer = o.code.writer(); |
| 584 | for (as.inputs) |i, index| { | 617 | for (as.inputs) |i, index| { |
| 585 | if (i[0] == '{' and i[i.len - 1] == '}') { | 618 | if (i[0] == '{' and i[i.len - 1] == '}') { |
| 586 | const reg = i[1 .. i.len - 1]; | 619 | const reg = i[1 .. i.len - 1]; |
| 587 | const arg = as.args[index]; | 620 | const arg = as.args[index]; |
| | 621 | const arg_c_value = try o.resolveInst(arg); |
| | 622 | try o.indent(); |
| 588 | try writer.writeAll("register "); | 623 | try writer.writeAll("register "); |
| 589 | try renderType(ctx, writer, arg.ty); | 624 | try o.dg.renderType(writer, arg.ty); |
| 590 | try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); | 625 | try writer.print(" {s}_constant __asm__(\"{s}\") = {};\n", .{ |
| 591 | // TODO merge constant handling into inst_map as well | 626 | reg, reg, arg_c_value.printed(o), |
| 592 | if (arg.castTag(.constant)) |c| { | 627 | }); |
| 593 | try renderValue(ctx, writer, arg.ty, c.val); | | |
| 594 | try writer.writeAll(";\n "); | | |
| 595 | } else { | | |
| 596 | const gop = try ctx.inst_map.getOrPut(arg); | | |
| 597 | if (!gop.found_existing) { | | |
| 598 | return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); | | |
| 599 | } | | |
| 600 | try writer.print("{s};\n ", .{gop.entry.value}); | | |
| 601 | } | | |
| 602 | } else { | 628 | } else { |
| 603 | return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); | 629 | return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{}); |
| 604 | } | 630 | } |
| 605 | } | 631 | } |
| 606 | try writer.print("__asm {s} (\"{s}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); | 632 | try o.indent(); |
| 607 | if (as.output) |o| { | 633 | const volatile_string: []const u8 = if (as.is_volatile) "volatile " else ""; |
| 608 | return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{}); | 634 | try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source }); |
| | 635 | if (as.output) |_| { |
| | 636 | return o.dg.fail(o.dg.decl.src(), "TODO inline asm output", .{}); |
| 609 | } | 637 | } |
| 610 | if (as.inputs.len > 0) { | 638 | if (as.inputs.len > 0) { |
| 611 | if (as.output == null) { | 639 | if (as.output == null) { |
| ... | @@ -627,5 +655,9 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { | ... | @@ -627,5 +655,9 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { |
| 627 | } | 655 | } |
| 628 | } | 656 | } |
| 629 | try writer.writeAll(");\n"); | 657 | try writer.writeAll(");\n"); |
| 630 | return null; | 658 | |
| | 659 | if (as.base.isUnused()) |
| | 660 | return CValue.none; |
| | 661 | |
| | 662 | return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{}); |
| 631 | } | 663 | } |