master
  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 Node = Tree.Node;
  9const Source = aro.Source;
 10const Tree = aro.Tree;
 11const QualType = aro.QualType;
 12const Value = aro.Value;
 13
 14const AsmCodeGen = @This();
 15const Error = aro.Compilation.Error;
 16
 17tree: *const Tree,
 18comp: *Compilation,
 19text: *std.Io.Writer,
 20data: *std.Io.Writer,
 21
 22const StorageUnit = enum(u8) {
 23    byte = 8,
 24    short = 16,
 25    long = 32,
 26    quad = 64,
 27
 28    fn trunc(self: StorageUnit, val: u64) u64 {
 29        return switch (self) {
 30            .byte => @as(u8, @truncate(val)),
 31            .short => @as(u16, @truncate(val)),
 32            .long => @as(u32, @truncate(val)),
 33            .quad => val,
 34        };
 35    }
 36};
 37
 38fn serializeInt(value: u64, storage_unit: StorageUnit, w: *std.Io.Writer) !void {
 39    try w.print("  .{s}  0x{x}\n", .{ @tagName(storage_unit), storage_unit.trunc(value) });
 40}
 41
 42fn serializeFloat(comptime T: type, value: T, w: *std.Io.Writer) !void {
 43    switch (T) {
 44        f128 => {
 45            const bytes = std.mem.asBytes(&value);
 46            const first = std.mem.bytesToValue(u64, bytes[0..8]);
 47            try serializeInt(first, .quad, w);
 48            const second = std.mem.bytesToValue(u64, bytes[8..16]);
 49            return serializeInt(second, .quad, w);
 50        },
 51        f80 => {
 52            const bytes = std.mem.asBytes(&value);
 53            const first = std.mem.bytesToValue(u64, bytes[0..8]);
 54            try serializeInt(first, .quad, w);
 55            const second = std.mem.bytesToValue(u16, bytes[8..10]);
 56            try serializeInt(second, .short, w);
 57            return w.writeAll("  .zero 6\n");
 58        },
 59        else => {
 60            const size = @bitSizeOf(T);
 61            const storage_unit = std.meta.intToEnum(StorageUnit, size) catch unreachable;
 62            const IntTy = @Int(.unsigned, size);
 63            const int_val: IntTy = @bitCast(value);
 64            return serializeInt(int_val, storage_unit, w);
 65        },
 66    }
 67}
 68
 69pub fn todo(c: *AsmCodeGen, msg: []const u8, tok: Tree.TokenIndex) Error {
 70    const loc: Source.Location = c.tree.tokens.items(.loc)[tok];
 71
 72    var sf = std.heap.stackFallback(1024, c.comp.gpa);
 73    const allocator = sf.get();
 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.meta.intToEnum(StorageUnit, bit_size) catch 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 => return error.OutOfMemory,
154        error.FatalError => return error.FatalError,
155    };
156
157    const text_slice = try text.toOwnedSlice();
158    errdefer tree.comp.gpa.free(text_slice);
159    const data_slice = try data.toOwnedSlice();
160    return .{
161        .text = text_slice,
162        .data = data_slice,
163    };
164}
165
166fn genDecls(c: *AsmCodeGen) !void {
167    if (c.tree.comp.code_gen_options.debug != .strip) {
168        const sources = c.tree.comp.sources.values();
169        for (sources) |source| {
170            try c.data.print("  .file {d} \"{s}\"\n", .{ @intFromEnum(source.id.index) + 1, source.path });
171        }
172    }
173
174    for (c.tree.root_decls.items) |decl| {
175        switch (decl.get(c.tree)) {
176            .static_assert,
177            .typedef,
178            .struct_decl,
179            .union_decl,
180            .enum_decl,
181            => {},
182
183            .function => |function| {
184                if (function.body == null) continue;
185                try c.genFn(function);
186            },
187
188            .variable => |variable| try c.genVar(variable),
189
190            else => unreachable,
191        }
192    }
193    try c.text.writeAll("  .section  .note.GNU-stack,\"\",@progbits\n");
194}
195
196fn genFn(c: *AsmCodeGen, function: Node.Function) !void {
197    return c.todo("Codegen functions", function.name_tok);
198}
199
200fn genVar(c: *AsmCodeGen, variable: Node.Variable) !void {
201    const comp = c.comp;
202    const qt = variable.qt;
203
204    const is_tentative = variable.initializer == null;
205    const size = qt.sizeofOrNull(comp) orelse blk: {
206        // tentative array definition assumed to have one element
207        std.debug.assert(is_tentative and qt.is(c.comp, .array));
208        break :blk qt.childType(c.comp).sizeof(comp);
209    };
210
211    const name = c.tree.tokSlice(variable.name_tok);
212    const nat_align = qt.alignof(comp);
213    const alignment = if (qt.is(c.comp, .array) and size >= 16) @max(16, nat_align) else nat_align;
214
215    if (variable.storage_class == .static) {
216        try c.data.print("  .local \"{s}\"\n", .{name});
217    } else {
218        try c.data.print("  .globl \"{s}\"\n", .{name});
219    }
220
221    if (is_tentative and comp.code_gen_options.common) {
222        try c.data.print("  .comm \"{s}\", {d}, {d}\n", .{ name, size, alignment });
223        return;
224    }
225    if (variable.initializer) |init| {
226        if (variable.thread_local and comp.code_gen_options.data_sections) {
227            try c.data.print("  .section .tdata.\"{s}\",\"awT\",@progbits\n", .{name});
228        } else if (variable.thread_local) {
229            try c.data.writeAll("  .section .tdata,\"awT\",@progbits\n");
230        } else if (comp.code_gen_options.data_sections) {
231            try c.data.print("  .section .data.\"{s}\",\"aw\",@progbits\n", .{name});
232        } else {
233            try c.data.writeAll("  .data\n");
234        }
235
236        try c.data.print("  .type \"{s}\", @object\n", .{name});
237        try c.data.print("  .size \"{s}\", {d}\n", .{ name, size });
238        try c.data.print("  .align {d}\n", .{alignment});
239        try c.data.print("\"{s}\":\n", .{name});
240        try c.emitValue(qt, init);
241        return;
242    }
243    if (variable.thread_local and comp.code_gen_options.data_sections) {
244        try c.data.print("  .section .tbss.\"{s}\",\"awT\",@nobits\n", .{name});
245    } else if (variable.thread_local) {
246        try c.data.writeAll("  .section .tbss,\"awT\",@nobits\n");
247    } else if (comp.code_gen_options.data_sections) {
248        try c.data.print("  .section .bss.\"{s}\",\"aw\",@nobits\n", .{name});
249    } else {
250        try c.data.writeAll("  .bss\n");
251    }
252    try c.data.print("  .align {d}\n", .{alignment});
253    try c.data.print("\"{s}\":\n", .{name});
254    try c.data.print("  .zero {d}\n", .{size});
255}