| 1 | const std = @import("std"); |
| 2 | const Allocator = std.mem.Allocator; |
| 3 | const assert = std.debug.assert; |
| 4 | |
| 5 | const aro = @import("aro"); |
| 6 | const Assembly = aro.Assembly; |
| 7 | const Compilation = aro.Compilation; |
| 8 | const Source = aro.Source; |
| 9 | const Tree = aro.Tree; |
| 10 | const QualType = aro.QualType; |
| 11 | const Value = aro.Value; |
| 12 | const Error = aro.Compilation.Error; |
| 13 | const Node = Tree.Node; |
| 14 | |
| 15 | const AsmCodeGen = @This(); |
| 16 | tree: *const Tree, |
| 17 | comp: *Compilation, |
| 18 | text: *std.Io.Writer, |
| 19 | data: *std.Io.Writer, |
| 20 | |
| 21 | const StorageUnit = enum(u8) { |
| 22 | byte = 8, |
| 23 | short = 16, |
| 24 | long = 32, |
| 25 | quad = 64, |
| 26 | |
| 27 | fn trunc(self: StorageUnit, val: u64) u64 { |
| 28 | return switch (self) { |
| 29 | .byte => @as(u8, @truncate(val)), |
| 30 | .short => @as(u16, @truncate(val)), |
| 31 | .long => @as(u32, @truncate(val)), |
| 32 | .quad => val, |
| 33 | }; |
| 34 | } |
| 35 | }; |
| 36 | |
| 37 | fn serializeInt(value: u64, storage_unit: StorageUnit, w: *std.Io.Writer) !void { |
| 38 | try w.print(" .{s} 0x{x}\n", .{ @tagName(storage_unit), storage_unit.trunc(value) }); |
| 39 | } |
| 40 | |
| 41 | fn serializeFloat(comptime T: type, value: T, w: *std.Io.Writer) !void { |
| 42 | switch (T) { |
| 43 | f128 => { |
| 44 | const bytes = std.mem.asBytes(&value); |
| 45 | const first = std.mem.bytesToValue(u64, bytes[0..8]); |
| 46 | try serializeInt(first, .quad, w); |
| 47 | const second = std.mem.bytesToValue(u64, bytes[8..16]); |
| 48 | return serializeInt(second, .quad, w); |
| 49 | }, |
| 50 | f80 => { |
| 51 | const bytes = std.mem.asBytes(&value); |
| 52 | const first = std.mem.bytesToValue(u64, bytes[0..8]); |
| 53 | try serializeInt(first, .quad, w); |
| 54 | const second = std.mem.bytesToValue(u16, bytes[8..10]); |
| 55 | try serializeInt(second, .short, w); |
| 56 | return w.writeAll(" .zero 6\n"); |
| 57 | }, |
| 58 | else => { |
| 59 | const size = @bitSizeOf(T); |
| 60 | const storage_unit = std.enums.fromInt(StorageUnit, size) orelse unreachable; |
| 61 | const IntTy = @Int(.unsigned, size); |
| 62 | const int_val: IntTy = @bitCast(value); |
| 63 | return serializeInt(int_val, storage_unit, w); |
| 64 | }, |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | pub fn todo(c: *AsmCodeGen, msg: []const u8, tok: Tree.TokenIndex) Error { |
| 69 | const loc: Source.Location = c.tree.tokens.items(.loc)[tok]; |
| 70 | |
| 71 | var bfa_buf: [u8]1024 = undefined; |
| 72 | var bfa: std.heap.BufferFirstAllocator = .init(&bfa_buf, c.comp.gpa); |
| 73 | const allocator = bfa.allocator(); |
| 74 | var buf: std.ArrayList(u8) = .empty; |
| 75 | defer buf.deinit(allocator); |
| 76 | |
| 77 | try buf.print(allocator, "TODO: {s}", .{msg}); |
| 78 | try c.comp.diagnostics.add(.{ |
| 79 | .text = buf.items, |
| 80 | .kind = .@"error", |
| 81 | .location = loc.expand(c.comp), |
| 82 | }); |
| 83 | return error.FatalError; |
| 84 | } |
| 85 | |
| 86 | fn emitAggregate(c: *AsmCodeGen, qt: QualType, node: Node.Index) !void { |
| 87 | _ = qt; |
| 88 | return c.todo("Codegen aggregates", node.tok(c.tree)); |
| 89 | } |
| 90 | |
| 91 | fn emitSingleValue(c: *AsmCodeGen, qt: QualType, node: Node.Index) !void { |
| 92 | const value = c.tree.value_map.get(node) orelse return; |
| 93 | const bit_size = qt.bitSizeof(c.comp); |
| 94 | const scalar_kind = qt.scalarKind(c.comp); |
| 95 | if (!scalar_kind.isReal()) { |
| 96 | return c.todo("Codegen _Complex values", node.tok(c.tree)); |
| 97 | } else if (scalar_kind.isInt()) { |
| 98 | const storage_unit = std.enums.fromInt(StorageUnit, bit_size) orelse return c.todo("Codegen _BitInt values", node.tok(c.tree)); |
| 99 | try c.data.print(" .{s} ", .{@tagName(storage_unit)}); |
| 100 | _ = try value.print(qt, c.comp, c.data); |
| 101 | try c.data.writeByte('\n'); |
| 102 | } else if (scalar_kind.isFloat()) { |
| 103 | switch (bit_size) { |
| 104 | 16 => return serializeFloat(f16, value.toFloat(f16, c.comp), c.data), |
| 105 | 32 => return serializeFloat(f32, value.toFloat(f32, c.comp), c.data), |
| 106 | 64 => return serializeFloat(f64, value.toFloat(f64, c.comp), c.data), |
| 107 | 80 => return serializeFloat(f80, value.toFloat(f80, c.comp), c.data), |
| 108 | 128 => return serializeFloat(f128, value.toFloat(f128, c.comp), c.data), |
| 109 | else => unreachable, |
| 110 | } |
| 111 | } else if (scalar_kind.isPointer()) { |
| 112 | return c.todo("Codegen pointer", node.tok(c.tree)); |
| 113 | } else if (qt.is(c.comp, .array)) { |
| 114 | // Todo: |
| 115 | // Handle truncated initializers e.g. char x[3] = "hello"; |
| 116 | // Zero out remaining bytes if initializer is shorter than storage capacity |
| 117 | // Handle non-char strings |
| 118 | const bytes = value.toBytes(c.comp); |
| 119 | const directive = if (bytes.len > bit_size / 8) "ascii" else "string"; |
| 120 | try c.data.print(" .{s} ", .{directive}); |
| 121 | try Value.printString(bytes, qt, c.comp, c.data); |
| 122 | |
| 123 | try c.data.writeByte('\n'); |
| 124 | } else unreachable; |
| 125 | } |
| 126 | |
| 127 | fn emitValue(c: *AsmCodeGen, qt: QualType, node: Node.Index) !void { |
| 128 | switch (node.get(c.tree)) { |
| 129 | .array_init_expr, |
| 130 | .struct_init_expr, |
| 131 | .union_init_expr, |
| 132 | => return c.todo("Codegen multiple inits", node.tok(c.tree)), |
| 133 | else => return c.emitSingleValue(qt, node), |
| 134 | } |
| 135 | } |
| 136 | |
| 137 | pub fn genAsm(tree: *const Tree) Error!Assembly { |
| 138 | var data: std.Io.Writer.Allocating = .init(tree.comp.gpa); |
| 139 | defer data.deinit(); |
| 140 | |
| 141 | var text: std.Io.Writer.Allocating = .init(tree.comp.gpa); |
| 142 | defer text.deinit(); |
| 143 | |
| 144 | var codegen: AsmCodeGen = .{ |
| 145 | .tree = tree, |
| 146 | .comp = tree.comp, |
| 147 | .text = &text.writer, |
| 148 | .data = &data.writer, |
| 149 | }; |
| 150 | |
| 151 | codegen.genDecls() catch |err| switch (err) { |
| 152 | error.WriteFailed => return error.OutOfMemory, |
| 153 | error.OutOfMemory, error.FatalError => |e| return e, |
| 154 | }; |
| 155 | |
| 156 | const text_slice = try text.toOwnedSlice(); |
| 157 | errdefer tree.comp.gpa.free(text_slice); |
| 158 | const data_slice = try data.toOwnedSlice(); |
| 159 | return .{ |
| 160 | .text = text_slice, |
| 161 | .data = data_slice, |
| 162 | }; |
| 163 | } |
| 164 | |
| 165 | fn genDecls(c: *AsmCodeGen) !void { |
| 166 | if (c.tree.comp.code_gen_options.debug != .strip) { |
| 167 | const sources = c.tree.comp.sources.values(); |
| 168 | for (sources) |source| { |
| 169 | try c.data.print(" .file {d} \"{s}\"\n", .{ @backingInt(source.id.index) + 1, source.path }); |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | for (c.tree.root_decls.items) |decl| { |
| 174 | switch (decl.get(c.tree)) { |
| 175 | .static_assert, |
| 176 | .typedef, |
| 177 | .struct_decl, |
| 178 | .union_decl, |
| 179 | .enum_decl, |
| 180 | => {}, |
| 181 | |
| 182 | .function => |function| { |
| 183 | if (function.body == null) continue; |
| 184 | try c.genFn(function); |
| 185 | }, |
| 186 | |
| 187 | .variable => |variable| try c.genVar(variable), |
| 188 | |
| 189 | else => unreachable, |
| 190 | } |
| 191 | } |
| 192 | try c.text.writeAll(" .section .note.GNU-stack,\"\",@progbits\n"); |
| 193 | } |
| 194 | |
| 195 | fn genFn(c: *AsmCodeGen, function: Node.Function) !void { |
| 196 | return c.todo("Codegen functions", function.name_tok); |
| 197 | } |
| 198 | |
| 199 | fn genVar(c: *AsmCodeGen, variable: Node.Variable) !void { |
| 200 | const comp = c.comp; |
| 201 | const qt = variable.qt; |
| 202 | |
| 203 | const is_tentative = variable.initializer == null; |
| 204 | const size = qt.sizeofOrNull(comp) orelse blk: { |
| 205 | // tentative array definition assumed to have one element |
| 206 | std.debug.assert(is_tentative and qt.is(c.comp, .array)); |
| 207 | break :blk qt.childType(c.comp).sizeof(comp); |
| 208 | }; |
| 209 | |
| 210 | const name = c.tree.tokSlice(variable.name_tok); |
| 211 | const nat_align = qt.alignof(comp); |
| 212 | const alignment = if (qt.is(c.comp, .array) and size >= 16) @max(16, nat_align) else nat_align; |
| 213 | |
| 214 | if (variable.storage_class == .static) { |
| 215 | try c.data.print(" .local \"{s}\"\n", .{name}); |
| 216 | } else { |
| 217 | try c.data.print(" .globl \"{s}\"\n", .{name}); |
| 218 | } |
| 219 | |
| 220 | if (is_tentative and comp.code_gen_options.common) { |
| 221 | try c.data.print(" .comm \"{s}\", {d}, {d}\n", .{ name, size, alignment }); |
| 222 | return; |
| 223 | } |
| 224 | if (variable.initializer) |init| { |
| 225 | if (variable.thread_local and comp.code_gen_options.data_sections) { |
| 226 | try c.data.print(" .section .tdata.\"{s}\",\"awT\",@progbits\n", .{name}); |
| 227 | } else if (variable.thread_local) { |
| 228 | try c.data.writeAll(" .section .tdata,\"awT\",@progbits\n"); |
| 229 | } else if (comp.code_gen_options.data_sections) { |
| 230 | try c.data.print(" .section .data.\"{s}\",\"aw\",@progbits\n", .{name}); |
| 231 | } else { |
| 232 | try c.data.writeAll(" .data\n"); |
| 233 | } |
| 234 | |
| 235 | try c.data.print(" .type \"{s}\", @object\n", .{name}); |
| 236 | try c.data.print(" .size \"{s}\", {d}\n", .{ name, size }); |
| 237 | try c.data.print(" .align {d}\n", .{alignment}); |
| 238 | try c.data.print("\"{s}\":\n", .{name}); |
| 239 | try c.emitValue(qt, init); |
| 240 | return; |
| 241 | } |
| 242 | if (variable.thread_local and comp.code_gen_options.data_sections) { |
| 243 | try c.data.print(" .section .tbss.\"{s}\",\"awT\",@nobits\n", .{name}); |
| 244 | } else if (variable.thread_local) { |
| 245 | try c.data.writeAll(" .section .tbss,\"awT\",@nobits\n"); |
| 246 | } else if (comp.code_gen_options.data_sections) { |
| 247 | try c.data.print(" .section .bss.\"{s}\",\"aw\",@nobits\n", .{name}); |
| 248 | } else { |
| 249 | try c.data.writeAll(" .bss\n"); |
| 250 | } |
| 251 | try c.data.print(" .align {d}\n", .{alignment}); |
| 252 | try c.data.print("\"{s}\":\n", .{name}); |
| 253 | try c.data.print(" .zero {d}\n", .{size}); |
| 254 | } |