1const std = @import("std");
2const Allocator = std.mem.Allocator;
3const assert = std.debug.assert;
4
5const aro = @import("aro");
6const Assembly = aro.Assembly;
7const Compilation = aro.Compilation;
8const Source = aro.Source;
9const Tree = aro.Tree;
10const QualType = aro.QualType;
11const Value = aro.Value;
12const Error = aro.Compilation.Error;
13const Node = Tree.Node;
14
15const AsmCodeGen = @This();
16tree: *const Tree,
17comp: *Compilation,
18text: *std.Io.Writer,
19data: *std.Io.Writer,
20
21const 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
37fn 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
41fn 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
68pub 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
86fn emitAggregate(c: *AsmCodeGen, qt: QualType, node: Node.Index) !void {
87 _ = qt;
88 return c.todo("Codegen aggregates", node.tok(c.tree));
89}
90
91fn 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
127fn 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
137pub 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
165fn 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
195fn genFn(c: *AsmCodeGen, function: Node.Function) !void {
196 return c.todo("Codegen functions", function.name_tok);
197}
198
199fn 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}