| author | |
| committer | |
| log | 75a720565bb72e7fab03346df6a35e9a8f380962 |
| tree | 954fccd90a73b8ed6e7c9f7a45a2c978c1a67840 |
| parent | 1cab40d7837c69fe6a77f76be9dc27026acd935e |
| parent | c94652a2fd210fe4f007dbdd47c9e55b38e482fb |
10 files changed, 510 insertions(+), 376 deletions(-)
src-self-hosted/Module.zig+27-3| ... | ... | @@ -1210,6 +1210,13 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1210 | 1210 | |
| 1211 | 1211 | try self.astGenBlock(&gen_scope.base, body_block); |
| 1212 | 1212 | |
| 1213 | if (!fn_type.fnReturnType().isNoReturn() and (gen_scope.instructions.items.len == 0 or | |
| 1214 | !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) | |
| 1215 | { | |
| 1216 | const src = tree.token_locs[body_block.rbrace].start; | |
| 1217 | _ = try self.addZIRInst(&gen_scope.base, src, zir.Inst.ReturnVoid, .{}, .{}); | |
| 1218 | } | |
| 1219 | ||
| 1213 | 1220 | const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); |
| 1214 | 1221 | fn_zir.* = .{ |
| 1215 | 1222 | .body = .{ |
| ... | ... | @@ -2686,7 +2693,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr |
| 2686 | 2693 | |
| 2687 | 2694 | // Blocks must terminate with noreturn instruction. |
| 2688 | 2695 | assert(child_block.instructions.items.len != 0); |
| 2689 | assert(child_block.instructions.items[child_block.instructions.items.len - 1].tag.isNoReturn()); | |
| 2696 | assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); | |
| 2690 | 2697 | |
| 2691 | 2698 | // Need to set the type and emit the Block instruction. This allows machine code generation |
| 2692 | 2699 | // to emit a jump instruction to after the block when it encounters the break. |
| ... | ... | @@ -3271,7 +3278,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner |
| 3271 | 3278 | defer false_block.instructions.deinit(self.gpa); |
| 3272 | 3279 | try self.analyzeBody(&false_block.base, inst.positionals.false_body); |
| 3273 | 3280 | |
| 3274 | return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){ | |
| 3281 | return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.noreturn), Inst.CondBr, Inst.Args(Inst.CondBr){ | |
| 3275 | 3282 | .condition = cond, |
| 3276 | 3283 | .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) }, |
| 3277 | 3284 | .false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) }, |
| ... | ... | @@ -3518,9 +3525,26 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { |
| 3518 | 3525 | fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { |
| 3519 | 3526 | if (instructions.len == 0) |
| 3520 | 3527 | return Type.initTag(.noreturn); |
| 3528 | ||
| 3521 | 3529 | if (instructions.len == 1) |
| 3522 | 3530 | return instructions[0].ty; |
| 3523 | return self.fail(scope, instructions[0].src, "TODO peer type resolution", .{}); | |
| 3531 | ||
| 3532 | var prev_inst = instructions[0]; | |
| 3533 | for (instructions[1..]) |next_inst| { | |
| 3534 | if (next_inst.ty.eql(prev_inst.ty)) | |
| 3535 | continue; | |
| 3536 | if (next_inst.ty.zigTypeTag() == .NoReturn) | |
| 3537 | continue; | |
| 3538 | if (prev_inst.ty.zigTypeTag() == .NoReturn) { | |
| 3539 | prev_inst = next_inst; | |
| 3540 | continue; | |
| 3541 | } | |
| 3542 | ||
| 3543 | // TODO error notes pointing out each type | |
| 3544 | return self.fail(scope, next_inst.src, "incompatible types: '{}' and '{}'", .{ prev_inst.ty, next_inst.ty }); | |
| 3545 | } | |
| 3546 | ||
| 3547 | return prev_inst.ty; | |
| 3524 | 3548 | } |
| 3525 | 3549 | |
| 3526 | 3550 | fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { |
src-self-hosted/cgen.zig deleted-205| ... | ... | @@ -1,205 +0,0 @@ |
| 1 | const link = @import("link.zig"); | |
| 2 | const Module = @import("Module.zig"); | |
| 3 | ||
| 4 | const std = @import("std"); | |
| 5 | ||
| 6 | const Inst = @import("ir.zig").Inst; | |
| 7 | const Value = @import("value.zig").Value; | |
| 8 | const Type = @import("type.zig").Type; | |
| 9 | ||
| 10 | const C = link.File.C; | |
| 11 | const Decl = Module.Decl; | |
| 12 | const mem = std.mem; | |
| 13 | ||
| 14 | /// Maps a name from Zig source to C. This will always give the same output for | |
| 15 | /// any given input. | |
| 16 | fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { | |
| 17 | return allocator.dupe(u8, name); | |
| 18 | } | |
| 19 | ||
| 20 | fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) !void { | |
| 21 | if (T.tag() == .usize) { | |
| 22 | file.need_stddef = true; | |
| 23 | try writer.writeAll("size_t"); | |
| 24 | } else { | |
| 25 | switch (T.zigTypeTag()) { | |
| 26 | .NoReturn => { | |
| 27 | file.need_noreturn = true; | |
| 28 | try writer.writeAll("noreturn void"); | |
| 29 | }, | |
| 30 | .Void => try writer.writeAll("void"), | |
| 31 | .Int => { | |
| 32 | if (T.tag() == .u8) { | |
| 33 | file.need_stdint = true; | |
| 34 | try writer.writeAll("uint8_t"); | |
| 35 | } else { | |
| 36 | return file.fail(src, "TODO implement int types", .{}); | |
| 37 | } | |
| 38 | }, | |
| 39 | else => |e| return file.fail(src, "TODO implement type {}", .{e}), | |
| 40 | } | |
| 41 | } | |
| 42 | } | |
| 43 | ||
| 44 | fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { | |
| 45 | const tv = decl.typed_value.most_recent.typed_value; | |
| 46 | try renderType(file, writer, tv.ty.fnReturnType(), decl.src()); | |
| 47 | const name = try map(file.allocator, mem.spanZ(decl.name)); | |
| 48 | defer file.allocator.free(name); | |
| 49 | try writer.print(" {}(", .{name}); | |
| 50 | if (tv.ty.fnParamLen() == 0) | |
| 51 | try writer.writeAll("void)") | |
| 52 | else | |
| 53 | return file.fail(decl.src(), "TODO implement parameters", .{}); | |
| 54 | } | |
| 55 | ||
| 56 | pub fn generate(file: *C, decl: *Decl) !void { | |
| 57 | switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { | |
| 58 | .Fn => try genFn(file, decl), | |
| 59 | .Array => try genArray(file, decl), | |
| 60 | else => |e| return file.fail(decl.src(), "TODO {}", .{e}), | |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | fn genArray(file: *C, decl: *Decl) !void { | |
| 65 | const tv = decl.typed_value.most_recent.typed_value; | |
| 66 | // TODO: prevent inline asm constants from being emitted | |
| 67 | const name = try map(file.allocator, mem.span(decl.name)); | |
| 68 | defer file.allocator.free(name); | |
| 69 | if (tv.val.cast(Value.Payload.Bytes)) |payload| | |
| 70 | if (tv.ty.arraySentinel()) |sentinel| | |
| 71 | if (sentinel.toUnsignedInt() == 0) | |
| 72 | try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }) | |
| 73 | else | |
| 74 | return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{}) | |
| 75 | else | |
| 76 | return file.fail(decl.src(), "TODO byte arrays without sentinels", .{}) | |
| 77 | else | |
| 78 | return file.fail(decl.src(), "TODO non-byte arrays", .{}); | |
| 79 | } | |
| 80 | ||
| 81 | fn genFn(file: *C, decl: *Decl) !void { | |
| 82 | const writer = file.main.writer(); | |
| 83 | const tv = decl.typed_value.most_recent.typed_value; | |
| 84 | ||
| 85 | try renderFunctionSignature(file, writer, decl); | |
| 86 | ||
| 87 | try writer.writeAll(" {"); | |
| 88 | ||
| 89 | const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; | |
| 90 | const instructions = func.analysis.success.instructions; | |
| 91 | if (instructions.len > 0) { | |
| 92 | for (instructions) |inst| { | |
| 93 | try writer.writeAll("\n\t"); | |
| 94 | switch (inst.tag) { | |
| 95 | .assembly => try genAsm(file, inst.cast(Inst.Assembly).?, decl), | |
| 96 | .call => try genCall(file, inst.cast(Inst.Call).?, decl), | |
| 97 | .ret => try genRet(file, inst.cast(Inst.Ret).?, decl, tv.ty.fnReturnType()), | |
| 98 | else => |e| return file.fail(decl.src(), "TODO {}", .{e}), | |
| 99 | } | |
| 100 | } | |
| 101 | try writer.writeAll("\n"); | |
| 102 | } | |
| 103 | ||
| 104 | try writer.writeAll("}\n\n"); | |
| 105 | } | |
| 106 | ||
| 107 | fn genRet(file: *C, inst: *Inst.Ret, decl: *Decl, expected_return_type: Type) !void { | |
| 108 | const writer = file.main.writer(); | |
| 109 | const ret_value = inst.args.operand; | |
| 110 | const value = ret_value.value().?; | |
| 111 | if (expected_return_type.eql(ret_value.ty)) | |
| 112 | return file.fail(decl.src(), "TODO return {}", .{expected_return_type}) | |
| 113 | else if (expected_return_type.isInt() and ret_value.ty.tag() == .comptime_int) | |
| 114 | if (value.intFitsInType(expected_return_type, file.options.target)) | |
| 115 | if (expected_return_type.intInfo(file.options.target).bits <= 64) | |
| 116 | try writer.print("return {};", .{value.toUnsignedInt()}) | |
| 117 | else | |
| 118 | return file.fail(decl.src(), "TODO return ints > 64 bits", .{}) | |
| 119 | else | |
| 120 | return file.fail(decl.src(), "comptime int {} does not fit in {}", .{ value.toUnsignedInt(), expected_return_type }) | |
| 121 | else | |
| 122 | return file.fail(decl.src(), "return type mismatch: expected {}, found {}", .{ expected_return_type, ret_value.ty }); | |
| 123 | } | |
| 124 | ||
| 125 | fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { | |
| 126 | const writer = file.main.writer(); | |
| 127 | const header = file.header.writer(); | |
| 128 | if (inst.args.func.cast(Inst.Constant)) |func_inst| { | |
| 129 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | |
| 130 | const target = func_val.func.owner_decl; | |
| 131 | const target_ty = target.typed_value.most_recent.typed_value.ty; | |
| 132 | const ret_ty = target_ty.fnReturnType().tag(); | |
| 133 | if (target_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) { | |
| 134 | try writer.print("(void)", .{}); | |
| 135 | } | |
| 136 | const tname = mem.spanZ(target.name); | |
| 137 | if (file.called.get(tname) == null) { | |
| 138 | try file.called.put(tname, void{}); | |
| 139 | try renderFunctionSignature(file, header, target); | |
| 140 | try header.writeAll(";\n"); | |
| 141 | } | |
| 142 | try writer.print("{}();", .{tname}); | |
| 143 | } else { | |
| 144 | return file.fail(decl.src(), "TODO non-function call target?", .{}); | |
| 145 | } | |
| 146 | if (inst.args.args.len != 0) { | |
| 147 | return file.fail(decl.src(), "TODO function arguments", .{}); | |
| 148 | } | |
| 149 | } else { | |
| 150 | return file.fail(decl.src(), "TODO non-constant call inst?", .{}); | |
| 151 | } | |
| 152 | } | |
| 153 | ||
| 154 | fn genAsm(file: *C, inst: *Inst.Assembly, decl: *Decl) !void { | |
| 155 | const as = inst.args; | |
| 156 | const writer = file.main.writer(); | |
| 157 | for (as.inputs) |i, index| { | |
| 158 | if (i[0] == '{' and i[i.len - 1] == '}') { | |
| 159 | const reg = i[1 .. i.len - 1]; | |
| 160 | const arg = as.args[index]; | |
| 161 | if (arg.cast(Inst.Constant)) |c| { | |
| 162 | if (c.val.tag() == .int_u64) { | |
| 163 | try writer.writeAll("register "); | |
| 164 | try renderType(file, writer, arg.ty, decl.src()); | |
| 165 | try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() }); | |
| 166 | } else { | |
| 167 | return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()}); | |
| 168 | } | |
| 169 | } else { | |
| 170 | return file.fail(decl.src(), "TODO non-constant inline asm args", .{}); | |
| 171 | } | |
| 172 | } else { | |
| 173 | return file.fail(decl.src(), "TODO non-explicit inline asm regs", .{}); | |
| 174 | } | |
| 175 | } | |
| 176 | try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); | |
| 177 | if (as.output) |o| { | |
| 178 | return file.fail(decl.src(), "TODO inline asm output", .{}); | |
| 179 | } | |
| 180 | if (as.inputs.len > 0) { | |
| 181 | if (as.output == null) { | |
| 182 | try writer.writeAll(" :"); | |
| 183 | } | |
| 184 | try writer.writeAll(": "); | |
| 185 | for (as.inputs) |i, index| { | |
| 186 | if (i[0] == '{' and i[i.len - 1] == '}') { | |
| 187 | const reg = i[1 .. i.len - 1]; | |
| 188 | const arg = as.args[index]; | |
| 189 | if (index > 0) { | |
| 190 | try writer.writeAll(", "); | |
| 191 | } | |
| 192 | if (arg.cast(Inst.Constant)) |c| { | |
| 193 | try writer.print("\"\"({}_constant)", .{reg}); | |
| 194 | } else { | |
| 195 | // This is blocked by the earlier test | |
| 196 | unreachable; | |
| 197 | } | |
| 198 | } else { | |
| 199 | // This is blocked by the earlier test | |
| 200 | unreachable; | |
| 201 | } | |
| 202 | } | |
| 203 | } | |
| 204 | try writer.writeAll(");"); | |
| 205 | } |
src-self-hosted/codegen.zig+196-135| ... | ... | @@ -53,10 +53,8 @@ pub fn generateSymbol( |
| 53 | 53 | const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen()); |
| 54 | 54 | defer bin_file.allocator.free(param_types); |
| 55 | 55 | fn_type.fnParamTypes(param_types); |
| 56 | // A parameter may be broken into multiple machine code parameters, so we don't | |
| 57 | // know the size up front. | |
| 58 | var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len); | |
| 59 | defer mc_args.deinit(); | |
| 56 | var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len); | |
| 57 | defer bin_file.allocator.free(mc_args); | |
| 60 | 58 | |
| 61 | 59 | var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator); |
| 62 | 60 | defer { |
| ... | ... | @@ -67,57 +65,6 @@ pub fn generateSymbol( |
| 67 | 65 | const branch = try branch_stack.addOne(); |
| 68 | 66 | branch.* = .{}; |
| 69 | 67 | |
| 70 | switch (fn_type.fnCallingConvention()) { | |
| 71 | .Naked => assert(mc_args.items.len == 0), | |
| 72 | .Unspecified, .C => { | |
| 73 | // Prepare the function parameters | |
| 74 | switch (bin_file.options.target.cpu.arch) { | |
| 75 | .x86_64 => { | |
| 76 | const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; | |
| 77 | var next_int_reg: usize = 0; | |
| 78 | ||
| 79 | for (param_types) |param_type, src_i| { | |
| 80 | switch (param_type.zigTypeTag()) { | |
| 81 | .Bool, .Int => { | |
| 82 | if (next_int_reg >= integer_registers.len) { | |
| 83 | try mc_args.append(.{ .stack_offset = branch.next_stack_offset }); | |
| 84 | branch.next_stack_offset += @intCast(u32, param_type.abiSize(bin_file.options.target)); | |
| 85 | } else { | |
| 86 | try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg]) }); | |
| 87 | next_int_reg += 1; | |
| 88 | } | |
| 89 | }, | |
| 90 | else => return Result{ | |
| 91 | .fail = try ErrorMsg.create( | |
| 92 | bin_file.allocator, | |
| 93 | src, | |
| 94 | "TODO implement function parameters of type {}", | |
| 95 | .{@tagName(param_type.zigTypeTag())}, | |
| 96 | ), | |
| 97 | }, | |
| 98 | } | |
| 99 | } | |
| 100 | }, | |
| 101 | else => return Result{ | |
| 102 | .fail = try ErrorMsg.create( | |
| 103 | bin_file.allocator, | |
| 104 | src, | |
| 105 | "TODO implement function parameters for {}", | |
| 106 | .{bin_file.options.target.cpu.arch}, | |
| 107 | ), | |
| 108 | }, | |
| 109 | } | |
| 110 | }, | |
| 111 | else => return Result{ | |
| 112 | .fail = try ErrorMsg.create( | |
| 113 | bin_file.allocator, | |
| 114 | src, | |
| 115 | "TODO implement {} calling convention", | |
| 116 | .{fn_type.fnCallingConvention()}, | |
| 117 | ), | |
| 118 | }, | |
| 119 | } | |
| 120 | ||
| 121 | 68 | var function = Function{ |
| 122 | 69 | .gpa = bin_file.allocator, |
| 123 | 70 | .target = &bin_file.options.target, |
| ... | ... | @@ -125,11 +72,17 @@ pub fn generateSymbol( |
| 125 | 72 | .mod_fn = module_fn, |
| 126 | 73 | .code = code, |
| 127 | 74 | .err_msg = null, |
| 128 | .args = mc_args.items, | |
| 75 | .args = mc_args, | |
| 129 | 76 | .branch_stack = &branch_stack, |
| 77 | .src = src, | |
| 78 | }; | |
| 79 | ||
| 80 | const cc = fn_type.fnCallingConvention(); | |
| 81 | branch.max_end_stack = function.resolveParameters(src, cc, param_types, mc_args) catch |err| switch (err) { | |
| 82 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, | |
| 83 | else => |e| return e, | |
| 130 | 84 | }; |
| 131 | 85 | |
| 132 | branch.max_end_stack = branch.next_stack_offset; | |
| 133 | 86 | function.gen() catch |err| switch (err) { |
| 134 | 87 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, |
| 135 | 88 | else => |e| return e, |
| ... | ... | @@ -235,6 +188,65 @@ const InnerError = error{ |
| 235 | 188 | CodegenFail, |
| 236 | 189 | }; |
| 237 | 190 | |
| 191 | const MCValue = union(enum) { | |
| 192 | /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc. | |
| 193 | none, | |
| 194 | /// Control flow will not allow this value to be observed. | |
| 195 | unreach, | |
| 196 | /// No more references to this value remain. | |
| 197 | dead, | |
| 198 | /// A pointer-sized integer that fits in a register. | |
| 199 | immediate: u64, | |
| 200 | /// The constant was emitted into the code, at this offset. | |
| 201 | embedded_in_code: usize, | |
| 202 | /// The value is in a target-specific register. The value can | |
| 203 | /// be @intToEnum casted to the respective Reg enum. | |
| 204 | register: usize, | |
| 205 | /// The value is in memory at a hard-coded address. | |
| 206 | memory: u64, | |
| 207 | /// The value is one of the stack variables. | |
| 208 | stack_offset: u64, | |
| 209 | /// The value is in the compare flags assuming an unsigned operation, | |
| 210 | /// with this operator applied on top of it. | |
| 211 | compare_flags_unsigned: std.math.CompareOperator, | |
| 212 | /// The value is in the compare flags assuming a signed operation, | |
| 213 | /// with this operator applied on top of it. | |
| 214 | compare_flags_signed: std.math.CompareOperator, | |
| 215 | ||
| 216 | fn isMemory(mcv: MCValue) bool { | |
| 217 | return switch (mcv) { | |
| 218 | .embedded_in_code, .memory, .stack_offset => true, | |
| 219 | else => false, | |
| 220 | }; | |
| 221 | } | |
| 222 | ||
| 223 | fn isImmediate(mcv: MCValue) bool { | |
| 224 | return switch (mcv) { | |
| 225 | .immediate => true, | |
| 226 | else => false, | |
| 227 | }; | |
| 228 | } | |
| 229 | ||
| 230 | fn isMutable(mcv: MCValue) bool { | |
| 231 | return switch (mcv) { | |
| 232 | .none => unreachable, | |
| 233 | .unreach => unreachable, | |
| 234 | .dead => unreachable, | |
| 235 | ||
| 236 | .immediate, | |
| 237 | .embedded_in_code, | |
| 238 | .memory, | |
| 239 | .compare_flags_unsigned, | |
| 240 | .compare_flags_signed, | |
| 241 | => false, | |
| 242 | ||
| 243 | .register, | |
| 244 | .stack_offset, | |
| 245 | => true, | |
| 246 | }; | |
| 247 | } | |
| 248 | }; | |
| 249 | ||
| 238 | 250 | const Function = struct { |
| 239 | 251 | gpa: *Allocator, |
| 240 | 252 | bin_file: *link.File.Elf, |
| ... | ... | @@ -243,6 +255,7 @@ const Function = struct { |
| 243 | 255 | code: *std.ArrayList(u8), |
| 244 | 256 | err_msg: ?*ErrorMsg, |
| 245 | 257 | args: []MCValue, |
| 258 | src: usize, | |
| 246 | 259 | |
| 247 | 260 | /// Whenever there is a runtime branch, we push a Branch onto this stack, |
| 248 | 261 | /// and pop it off when the runtime branch joins. This provides an "overlay" |
| ... | ... | @@ -284,65 +297,6 @@ const Function = struct { |
| 284 | 297 | size: u32, |
| 285 | 298 | }; |
| 286 | 299 | |
| 287 | const MCValue = union(enum) { | |
| 288 | /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc. | |
| 289 | none, | |
| 290 | /// Control flow will not allow this value to be observed. | |
| 291 | unreach, | |
| 292 | /// No more references to this value remain. | |
| 293 | dead, | |
| 294 | /// A pointer-sized integer that fits in a register. | |
| 295 | immediate: u64, | |
| 296 | /// The constant was emitted into the code, at this offset. | |
| 297 | embedded_in_code: usize, | |
| 298 | /// The value is in a target-specific register. The value can | |
| 299 | /// be @intToEnum casted to the respective Reg enum. | |
| 300 | register: usize, | |
| 301 | /// The value is in memory at a hard-coded address. | |
| 302 | memory: u64, | |
| 303 | /// The value is one of the stack variables. | |
| 304 | stack_offset: u64, | |
| 305 | /// The value is in the compare flags assuming an unsigned operation, | |
| 306 | /// with this operator applied on top of it. | |
| 307 | compare_flags_unsigned: std.math.CompareOperator, | |
| 308 | /// The value is in the compare flags assuming a signed operation, | |
| 309 | /// with this operator applied on top of it. | |
| 310 | compare_flags_signed: std.math.CompareOperator, | |
| 311 | ||
| 312 | fn isMemory(mcv: MCValue) bool { | |
| 313 | return switch (mcv) { | |
| 314 | .embedded_in_code, .memory, .stack_offset => true, | |
| 315 | else => false, | |
| 316 | }; | |
| 317 | } | |
| 318 | ||
| 319 | fn isImmediate(mcv: MCValue) bool { | |
| 320 | return switch (mcv) { | |
| 321 | .immediate => true, | |
| 322 | else => false, | |
| 323 | }; | |
| 324 | } | |
| 325 | ||
| 326 | fn isMutable(mcv: MCValue) bool { | |
| 327 | return switch (mcv) { | |
| 328 | .none => unreachable, | |
| 329 | .unreach => unreachable, | |
| 330 | .dead => unreachable, | |
| 331 | ||
| 332 | .immediate, | |
| 333 | .embedded_in_code, | |
| 334 | .memory, | |
| 335 | .compare_flags_unsigned, | |
| 336 | .compare_flags_signed, | |
| 337 | => false, | |
| 338 | ||
| 339 | .register, | |
| 340 | .stack_offset, | |
| 341 | => true, | |
| 342 | }; | |
| 343 | } | |
| 344 | }; | |
| 345 | ||
| 346 | 300 | fn gen(self: *Function) !void { |
| 347 | 301 | switch (self.target.cpu.arch) { |
| 348 | 302 | .arm => return self.genArch(.arm), |
| ... | ... | @@ -400,7 +354,28 @@ const Function = struct { |
| 400 | 354 | } |
| 401 | 355 | |
| 402 | 356 | fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void { |
| 403 | return self.genBody(self.mod_fn.analysis.success, arch); | |
| 357 | try self.code.ensureCapacity(self.code.items.len + 11); | |
| 358 | ||
| 359 | // push rbp | |
| 360 | // mov rbp, rsp | |
| 361 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 }); | |
| 362 | ||
| 363 | // sub rsp, x | |
| 364 | const stack_end = self.branch_stack.items[0].max_end_stack; | |
| 365 | if (stack_end > std.math.maxInt(i32)) { | |
| 366 | return self.fail(self.src, "too much stack used in call parameters", .{}); | |
| 367 | } else if (stack_end > std.math.maxInt(i8)) { | |
| 368 | // 48 83 ec xx sub rsp,0x10 | |
| 369 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xec }); | |
| 370 | const x = @intCast(u32, stack_end); | |
| 371 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x); | |
| 372 | } else if (stack_end != 0) { | |
| 373 | // 48 81 ec xx xx xx xx sub rsp,0x80 | |
| 374 | const x = @intCast(u8, stack_end); | |
| 375 | self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xec, x }); | |
| 376 | } | |
| 377 | ||
| 378 | try self.genBody(self.mod_fn.analysis.success, arch); | |
| 404 | 379 | } |
| 405 | 380 | |
| 406 | 381 | fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void { |
| ... | ... | @@ -593,13 +568,42 @@ const Function = struct { |
| 593 | 568 | } |
| 594 | 569 | |
| 595 | 570 | fn genCall(self: *Function, inst: *ir.Inst.Call, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 571 | const fn_ty = inst.args.func.ty; | |
| 572 | const cc = fn_ty.fnCallingConvention(); | |
| 573 | const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen()); | |
| 574 | defer self.gpa.free(param_types); | |
| 575 | fn_ty.fnParamTypes(param_types); | |
| 576 | var mc_args = try self.gpa.alloc(MCValue, param_types.len); | |
| 577 | defer self.gpa.free(mc_args); | |
| 578 | const stack_byte_count = try self.resolveParameters(inst.base.src, cc, param_types, mc_args); | |
| 579 | ||
| 596 | 580 | switch (arch) { |
| 597 | .x86_64, .i386 => { | |
| 598 | if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| { | |
| 599 | if (inst.args.args.len != 0) { | |
| 600 | return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{}); | |
| 581 | .x86_64 => { | |
| 582 | for (mc_args) |mc_arg, arg_i| { | |
| 583 | const arg = inst.args.args[arg_i]; | |
| 584 | const arg_mcv = try self.resolveInst(inst.args.args[arg_i]); | |
| 585 | switch (mc_arg) { | |
| 586 | .none => continue, | |
| 587 | .register => |reg| { | |
| 588 | try self.genSetReg(arg.src, arch, @intToEnum(Reg(arch), @intCast(u8, reg)), arg_mcv); | |
| 589 | // TODO interact with the register allocator to mark the instruction as moved. | |
| 590 | }, | |
| 591 | .stack_offset => { | |
| 592 | // Here we need to emit instructions like this: | |
| 593 | // mov qword ptr [rsp + stack_offset], x | |
| 594 | return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{}); | |
| 595 | }, | |
| 596 | .immediate => unreachable, | |
| 597 | .unreach => unreachable, | |
| 598 | .dead => unreachable, | |
| 599 | .embedded_in_code => unreachable, | |
| 600 | .memory => unreachable, | |
| 601 | .compare_flags_signed => unreachable, | |
| 602 | .compare_flags_unsigned => unreachable, | |
| 601 | 603 | } |
| 604 | } | |
| 602 | 605 | |
| 606 | if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| { | |
| 603 | 607 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { |
| 604 | 608 | const func = func_val.func; |
| 605 | 609 | const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?]; |
| ... | ... | @@ -607,17 +611,11 @@ const Function = struct { |
| 607 | 611 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 608 | 612 | const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes); |
| 609 | 613 | // ff 14 25 xx xx xx xx call [addr] |
| 610 | try self.code.resize(self.code.items.len + 7); | |
| 611 | self.code.items[self.code.items.len - 7 ..][0..3].* = [3]u8{ 0xff, 0x14, 0x25 }; | |
| 612 | mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], got_addr); | |
| 613 | const return_type = func.owner_decl.typed_value.most_recent.typed_value.ty.fnReturnType(); | |
| 614 | switch (return_type.zigTypeTag()) { | |
| 615 | .Void => return MCValue{ .none = {} }, | |
| 616 | .NoReturn => return MCValue{ .unreach = {} }, | |
| 617 | else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}), | |
| 618 | } | |
| 614 | try self.code.ensureCapacity(self.code.items.len + 7); | |
| 615 | self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 }); | |
| 616 | mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr); | |
| 619 | 617 | } else { |
| 620 | return self.fail(inst.base.src, "TODO implement calling weird function values", .{}); | |
| 618 | return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{}); | |
| 621 | 619 | } |
| 622 | 620 | } else { |
| 623 | 621 | return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{}); |
| ... | ... | @@ -625,6 +623,13 @@ const Function = struct { |
| 625 | 623 | }, |
| 626 | 624 | else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}), |
| 627 | 625 | } |
| 626 | ||
| 627 | const return_type = fn_ty.fnReturnType(); | |
| 628 | switch (return_type.zigTypeTag()) { | |
| 629 | .Void => return MCValue{ .none = {} }, | |
| 630 | .NoReturn => return MCValue{ .unreach = {} }, | |
| 631 | else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}), | |
| 632 | } | |
| 628 | 633 | } |
| 629 | 634 | |
| 630 | 635 | fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue { |
| ... | ... | @@ -632,9 +637,15 @@ const Function = struct { |
| 632 | 637 | return self.fail(src, "TODO implement return with non-void operand", .{}); |
| 633 | 638 | } |
| 634 | 639 | switch (arch) { |
| 635 | .i386, .x86_64 => { | |
| 640 | .i386 => { | |
| 636 | 641 | try self.code.append(0xc3); // ret |
| 637 | 642 | }, |
| 643 | .x86_64 => { | |
| 644 | try self.code.appendSlice(&[_]u8{ | |
| 645 | 0x5d, // pop rbp | |
| 646 | 0xc3, // ret | |
| 647 | }); | |
| 648 | }, | |
| 638 | 649 | else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}), |
| 639 | 650 | } |
| 640 | 651 | return .unreach; |
| ... | ... | @@ -769,14 +780,22 @@ const Function = struct { |
| 769 | 780 | } |
| 770 | 781 | |
| 771 | 782 | fn genBr(self: *Function, inst: *ir.Inst.Br, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 783 | if (!inst.args.operand.ty.hasCodeGenBits()) | |
| 784 | return self.brVoid(inst.base.src, inst.args.block, arch); | |
| 785 | ||
| 786 | const operand = try self.resolveInst(inst.args.operand); | |
| 772 | 787 | switch (arch) { |
| 773 | 788 | else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}), |
| 774 | 789 | } |
| 775 | 790 | } |
| 776 | 791 | |
| 777 | 792 | fn genBrVoid(self: *Function, inst: *ir.Inst.BrVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 793 | return self.brVoid(inst.base.src, inst.args.block, arch); | |
| 794 | } | |
| 795 | ||
| 796 | fn brVoid(self: *Function, src: usize, block: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { | |
| 778 | 797 | // Emit a jump with a relocation. It will be patched up after the block ends. |
| 779 | try inst.args.block.codegen.relocs.ensureCapacity(self.gpa, inst.args.block.codegen.relocs.items.len + 1); | |
| 798 | try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1); | |
| 780 | 799 | |
| 781 | 800 | switch (arch) { |
| 782 | 801 | .i386, .x86_64 => { |
| ... | ... | @@ -785,9 +804,9 @@ const Function = struct { |
| 785 | 804 | try self.code.resize(self.code.items.len + 5); |
| 786 | 805 | self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 |
| 787 | 806 | // Leave the jump offset undefined |
| 788 | inst.args.block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); | |
| 807 | block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); | |
| 789 | 808 | }, |
| 790 | else => return self.fail(inst.base.src, "TODO implement brvoid for {}", .{self.target.cpu.arch}), | |
| 809 | else => return self.fail(src, "TODO implement brvoid for {}", .{self.target.cpu.arch}), | |
| 791 | 810 | } |
| 792 | 811 | return .none; |
| 793 | 812 | } |
| ... | ... | @@ -1122,6 +1141,48 @@ const Function = struct { |
| 1122 | 1141 | } |
| 1123 | 1142 | } |
| 1124 | 1143 | |
| 1144 | fn resolveParameters( | |
| 1145 | self: *Function, | |
| 1146 | src: usize, | |
| 1147 | cc: std.builtin.CallingConvention, | |
| 1148 | param_types: []const Type, | |
| 1149 | results: []MCValue, | |
| 1150 | ) !u32 { | |
| 1151 | switch (self.target.cpu.arch) { | |
| 1152 | .x86_64 => { | |
| 1153 | switch (cc) { | |
| 1154 | .Naked => { | |
| 1155 | assert(results.len == 0); | |
| 1156 | return 0; | |
| 1157 | }, | |
| 1158 | .Unspecified, .C => { | |
| 1159 | var next_int_reg: usize = 0; | |
| 1160 | var next_stack_offset: u32 = 0; | |
| 1161 | ||
| 1162 | const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 }; | |
| 1163 | for (param_types) |ty, i| { | |
| 1164 | switch (ty.zigTypeTag()) { | |
| 1165 | .Bool, .Int => { | |
| 1166 | if (next_int_reg >= integer_registers.len) { | |
| 1167 | results[i] = .{ .stack_offset = next_stack_offset }; | |
| 1168 | next_stack_offset += @intCast(u32, ty.abiSize(self.target.*)); | |
| 1169 | } else { | |
| 1170 | results[i] = .{ .register = @enumToInt(integer_registers[next_int_reg]) }; | |
| 1171 | next_int_reg += 1; | |
| 1172 | } | |
| 1173 | }, | |
| 1174 | else => return self.fail(src, "TODO implement function parameters of type {}", .{@tagName(ty.zigTypeTag())}), | |
| 1175 | } | |
| 1176 | } | |
| 1177 | return next_stack_offset; | |
| 1178 | }, | |
| 1179 | else => return self.fail(src, "TODO implement function parameters for {}", .{cc}), | |
| 1180 | } | |
| 1181 | }, | |
| 1182 | else => return self.fail(src, "TODO implement C ABI support for {}", .{self.target.cpu.arch}), | |
| 1183 | } | |
| 1184 | } | |
| 1185 | ||
| 1125 | 1186 | fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } { |
| 1126 | 1187 | @setCold(true); |
| 1127 | 1188 | assert(self.err_msg == null); |
src-self-hosted/codegen/c.zig created+206| ... | ... | @@ -0,0 +1,206 @@ |
| 1 | const std = @import("std"); | |
| 2 | ||
| 3 | const link = @import("../link.zig"); | |
| 4 | const Module = @import("../Module.zig"); | |
| 5 | ||
| 6 | const Inst = @import("../ir.zig").Inst; | |
| 7 | const Value = @import("../value.zig").Value; | |
| 8 | const Type = @import("../type.zig").Type; | |
| 9 | ||
| 10 | const C = link.File.C; | |
| 11 | const Decl = Module.Decl; | |
| 12 | const mem = std.mem; | |
| 13 | ||
| 14 | /// Maps a name from Zig source to C. This will always give the same output for | |
| 15 | /// any given input. | |
| 16 | fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { | |
| 17 | return allocator.dupe(u8, name); | |
| 18 | } | |
| 19 | ||
| 20 | fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) !void { | |
| 21 | if (T.tag() == .usize) { | |
| 22 | file.need_stddef = true; | |
| 23 | try writer.writeAll("size_t"); | |
| 24 | } else { | |
| 25 | switch (T.zigTypeTag()) { | |
| 26 | .NoReturn => { | |
| 27 | file.need_noreturn = true; | |
| 28 | try writer.writeAll("noreturn void"); | |
| 29 | }, | |
| 30 | .Void => try writer.writeAll("void"), | |
| 31 | .Int => { | |
| 32 | if (T.tag() == .u8) { | |
| 33 | file.need_stdint = true; | |
| 34 | try writer.writeAll("uint8_t"); | |
| 35 | } else { | |
| 36 | return file.fail(src, "TODO implement int types", .{}); | |
| 37 | } | |
| 38 | }, | |
| 39 | else => |e| return file.fail(src, "TODO implement type {}", .{e}), | |
| 40 | } | |
| 41 | } | |
| 42 | } | |
| 43 | ||
| 44 | fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { | |
| 45 | const tv = decl.typed_value.most_recent.typed_value; | |
| 46 | try renderType(file, writer, tv.ty.fnReturnType(), decl.src()); | |
| 47 | const name = try map(file.allocator, mem.spanZ(decl.name)); | |
| 48 | defer file.allocator.free(name); | |
| 49 | try writer.print(" {}(", .{name}); | |
| 50 | if (tv.ty.fnParamLen() == 0) | |
| 51 | try writer.writeAll("void)") | |
| 52 | else | |
| 53 | return file.fail(decl.src(), "TODO implement parameters", .{}); | |
| 54 | } | |
| 55 | ||
| 56 | pub fn generate(file: *C, decl: *Decl) !void { | |
| 57 | switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { | |
| 58 | .Fn => try genFn(file, decl), | |
| 59 | .Array => try genArray(file, decl), | |
| 60 | else => |e| return file.fail(decl.src(), "TODO {}", .{e}), | |
| 61 | } | |
| 62 | } | |
| 63 | ||
| 64 | fn genArray(file: *C, decl: *Decl) !void { | |
| 65 | const tv = decl.typed_value.most_recent.typed_value; | |
| 66 | // TODO: prevent inline asm constants from being emitted | |
| 67 | const name = try map(file.allocator, mem.span(decl.name)); | |
| 68 | defer file.allocator.free(name); | |
| 69 | if (tv.val.cast(Value.Payload.Bytes)) |payload| | |
| 70 | if (tv.ty.arraySentinel()) |sentinel| | |
| 71 | if (sentinel.toUnsignedInt() == 0) | |
| 72 | try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }) | |
| 73 | else | |
| 74 | return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{}) | |
| 75 | else | |
| 76 | return file.fail(decl.src(), "TODO byte arrays without sentinels", .{}) | |
| 77 | else | |
| 78 | return file.fail(decl.src(), "TODO non-byte arrays", .{}); | |
| 79 | } | |
| 80 | ||
| 81 | fn genFn(file: *C, decl: *Decl) !void { | |
| 82 | const writer = file.main.writer(); | |
| 83 | const tv = decl.typed_value.most_recent.typed_value; | |
| 84 | ||
| 85 | try renderFunctionSignature(file, writer, decl); | |
| 86 | ||
| 87 | try writer.writeAll(" {"); | |
| 88 | ||
| 89 | const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; | |
| 90 | const instructions = func.analysis.success.instructions; | |
| 91 | if (instructions.len > 0) { | |
| 92 | for (instructions) |inst| { | |
| 93 | try writer.writeAll("\n\t"); | |
| 94 | switch (inst.tag) { | |
| 95 | .assembly => try genAsm(file, inst.cast(Inst.Assembly).?, decl), | |
| 96 | .call => try genCall(file, inst.cast(Inst.Call).?, decl), | |
| 97 | .ret => try genRet(file, inst.cast(Inst.Ret).?, decl, tv.ty.fnReturnType()), | |
| 98 | .retvoid => try file.main.writer().print("return;", .{}), | |
| 99 | else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}), | |
| 100 | } | |
| 101 | } | |
| 102 | try writer.writeAll("\n"); | |
| 103 | } | |
| 104 | ||
| 105 | try writer.writeAll("}\n\n"); | |
| 106 | } | |
| 107 | ||
| 108 | fn genRet(file: *C, inst: *Inst.Ret, decl: *Decl, expected_return_type: Type) !void { | |
| 109 | const writer = file.main.writer(); | |
| 110 | const ret_value = inst.args.operand; | |
| 111 | const value = ret_value.value().?; | |
| 112 | if (expected_return_type.eql(ret_value.ty)) | |
| 113 | return file.fail(decl.src(), "TODO return {}", .{expected_return_type}) | |
| 114 | else if (expected_return_type.isInt() and ret_value.ty.tag() == .comptime_int) | |
| 115 | if (value.intFitsInType(expected_return_type, file.options.target)) | |
| 116 | if (expected_return_type.intInfo(file.options.target).bits <= 64) | |
| 117 | try writer.print("return {};", .{value.toUnsignedInt()}) | |
| 118 | else | |
| 119 | return file.fail(decl.src(), "TODO return ints > 64 bits", .{}) | |
| 120 | else | |
| 121 | return file.fail(decl.src(), "comptime int {} does not fit in {}", .{ value.toUnsignedInt(), expected_return_type }) | |
| 122 | else | |
| 123 | return file.fail(decl.src(), "return type mismatch: expected {}, found {}", .{ expected_return_type, ret_value.ty }); | |
| 124 | } | |
| 125 | ||
| 126 | fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { | |
| 127 | const writer = file.main.writer(); | |
| 128 | const header = file.header.writer(); | |
| 129 | if (inst.args.func.cast(Inst.Constant)) |func_inst| { | |
| 130 | if (func_inst.val.cast(Value.Payload.Function)) |func_val| { | |
| 131 | const target = func_val.func.owner_decl; | |
| 132 | const target_ty = target.typed_value.most_recent.typed_value.ty; | |
| 133 | const ret_ty = target_ty.fnReturnType().tag(); | |
| 134 | if (target_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) { | |
| 135 | try writer.print("(void)", .{}); | |
| 136 | } | |
| 137 | const tname = mem.spanZ(target.name); | |
| 138 | if (file.called.get(tname) == null) { | |
| 139 | try file.called.put(tname, void{}); | |
| 140 | try renderFunctionSignature(file, header, target); | |
| 141 | try header.writeAll(";\n"); | |
| 142 | } | |
| 143 | try writer.print("{}();", .{tname}); | |
| 144 | } else { | |
| 145 | return file.fail(decl.src(), "TODO non-function call target?", .{}); | |
| 146 | } | |
| 147 | if (inst.args.args.len != 0) { | |
| 148 | return file.fail(decl.src(), "TODO function arguments", .{}); | |
| 149 | } | |
| 150 | } else { | |
| 151 | return file.fail(decl.src(), "TODO non-constant call inst?", .{}); | |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | fn genAsm(file: *C, inst: *Inst.Assembly, decl: *Decl) !void { | |
| 156 | const as = inst.args; | |
| 157 | const writer = file.main.writer(); | |
| 158 | for (as.inputs) |i, index| { | |
| 159 | if (i[0] == '{' and i[i.len - 1] == '}') { | |
| 160 | const reg = i[1 .. i.len - 1]; | |
| 161 | const arg = as.args[index]; | |
| 162 | if (arg.cast(Inst.Constant)) |c| { | |
| 163 | if (c.val.tag() == .int_u64) { | |
| 164 | try writer.writeAll("register "); | |
| 165 | try renderType(file, writer, arg.ty, decl.src()); | |
| 166 | try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() }); | |
| 167 | } else { | |
| 168 | return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()}); | |
| 169 | } | |
| 170 | } else { | |
| 171 | return file.fail(decl.src(), "TODO non-constant inline asm args", .{}); | |
| 172 | } | |
| 173 | } else { | |
| 174 | return file.fail(decl.src(), "TODO non-explicit inline asm regs", .{}); | |
| 175 | } | |
| 176 | } | |
| 177 | try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); | |
| 178 | if (as.output) |o| { | |
| 179 | return file.fail(decl.src(), "TODO inline asm output", .{}); | |
| 180 | } | |
| 181 | if (as.inputs.len > 0) { | |
| 182 | if (as.output == null) { | |
| 183 | try writer.writeAll(" :"); | |
| 184 | } | |
| 185 | try writer.writeAll(": "); | |
| 186 | for (as.inputs) |i, index| { | |
| 187 | if (i[0] == '{' and i[i.len - 1] == '}') { | |
| 188 | const reg = i[1 .. i.len - 1]; | |
| 189 | const arg = as.args[index]; | |
| 190 | if (index > 0) { | |
| 191 | try writer.writeAll(", "); | |
| 192 | } | |
| 193 | if (arg.cast(Inst.Constant)) |c| { | |
| 194 | try writer.print("\"\"({}_constant)", .{reg}); | |
| 195 | } else { | |
| 196 | // This is blocked by the earlier test | |
| 197 | unreachable; | |
| 198 | } | |
| 199 | } else { | |
| 200 | // This is blocked by the earlier test | |
| 201 | unreachable; | |
| 202 | } | |
| 203 | } | |
| 204 | } | |
| 205 | try writer.writeAll(");"); | |
| 206 | } |
src-self-hosted/ir.zig-30| ... | ... | @@ -60,36 +60,6 @@ pub const Inst = struct { |
| 60 | 60 | retvoid, |
| 61 | 61 | sub, |
| 62 | 62 | unreach, |
| 63 | ||
| 64 | /// Returns whether the instruction is one of the control flow "noreturn" types. | |
| 65 | /// Function calls do not count. When ZIR is generated, the compiler automatically | |
| 66 | /// emits an `Unreach` after a function call with the `noreturn` return type. | |
| 67 | pub fn isNoReturn(tag: Tag) bool { | |
| 68 | return switch (tag) { | |
| 69 | .add, | |
| 70 | .arg, | |
| 71 | .assembly, | |
| 72 | .bitcast, | |
| 73 | .block, | |
| 74 | .breakpoint, | |
| 75 | .call, | |
| 76 | .cmp, | |
| 77 | .constant, | |
| 78 | .isnonnull, | |
| 79 | .isnull, | |
| 80 | .ptrtoint, | |
| 81 | .sub, | |
| 82 | => false, | |
| 83 | ||
| 84 | .br, | |
| 85 | .brvoid, | |
| 86 | .condbr, | |
| 87 | .ret, | |
| 88 | .retvoid, | |
| 89 | .unreach, | |
| 90 | => true, | |
| 91 | }; | |
| 92 | } | |
| 93 | 63 | }; |
| 94 | 64 | |
| 95 | 65 | pub fn cast(base: *Inst, comptime T: type) ?*T { |
src-self-hosted/link.zig+2-2| ... | ... | @@ -7,7 +7,7 @@ const Module = @import("Module.zig"); |
| 7 | 7 | const fs = std.fs; |
| 8 | 8 | const elf = std.elf; |
| 9 | 9 | const codegen = @import("codegen.zig"); |
| 10 | const cgen = @import("cgen.zig"); | |
| 10 | const c_codegen = @import("codegen/c.zig"); | |
| 11 | 11 | |
| 12 | 12 | const default_entry_addr = 0x8000000; |
| 13 | 13 | |
| ... | ... | @@ -259,7 +259,7 @@ pub const File = struct { |
| 259 | 259 | } |
| 260 | 260 | |
| 261 | 261 | pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { |
| 262 | cgen.generate(self, decl) catch |err| { | |
| 262 | c_codegen.generate(self, decl) catch |err| { | |
| 263 | 263 | if (err == error.CGenFailure) { |
| 264 | 264 | try module.failed_decls.put(module.gpa, decl, self.error_msg); |
| 265 | 265 | } |
src-self-hosted/type.zig+4| ... | ... | @@ -468,6 +468,10 @@ pub const Type = extern union { |
| 468 | 468 | }; |
| 469 | 469 | } |
| 470 | 470 | |
| 471 | pub fn isNoReturn(self: Type) bool { | |
| 472 | return self.zigTypeTag() == .NoReturn; | |
| 473 | } | |
| 474 | ||
| 471 | 475 | /// Asserts that hasCodeGenBits() is true. |
| 472 | 476 | pub fn abiAlignment(self: Type, target: Target) u32 { |
| 473 | 477 | return switch (self.tag()) { |
src-self-hosted/zir.zig+46| ... | ... | @@ -81,6 +81,52 @@ pub const Inst = struct { |
| 81 | 81 | condbr, |
| 82 | 82 | isnull, |
| 83 | 83 | isnonnull, |
| 84 | ||
| 85 | /// Returns whether the instruction is one of the control flow "noreturn" types. | |
| 86 | /// Function calls do not count. | |
| 87 | pub fn isNoReturn(tag: Tag) bool { | |
| 88 | return switch (tag) { | |
| 89 | .arg, | |
| 90 | .block, | |
| 91 | .breakpoint, | |
| 92 | .call, | |
| 93 | .@"const", | |
| 94 | .declref, | |
| 95 | .declref_str, | |
| 96 | .declval, | |
| 97 | .declval_in_module, | |
| 98 | .str, | |
| 99 | .int, | |
| 100 | .inttype, | |
| 101 | .ptrtoint, | |
| 102 | .fieldptr, | |
| 103 | .deref, | |
| 104 | .as, | |
| 105 | .@"asm", | |
| 106 | .@"fn", | |
| 107 | .fntype, | |
| 108 | .@"export", | |
| 109 | .primitive, | |
| 110 | .intcast, | |
| 111 | .bitcast, | |
| 112 | .elemptr, | |
| 113 | .add, | |
| 114 | .sub, | |
| 115 | .cmp, | |
| 116 | .isnull, | |
| 117 | .isnonnull, | |
| 118 | => false, | |
| 119 | ||
| 120 | .condbr, | |
| 121 | .@"unreachable", | |
| 122 | .@"return", | |
| 123 | .returnvoid, | |
| 124 | .@"break", | |
| 125 | .breakvoid, | |
| 126 | .compileerror, | |
| 127 | => true, | |
| 128 | }; | |
| 129 | } | |
| 84 | 130 | }; |
| 85 | 131 | |
| 86 | 132 | pub fn TagToType(tag: Tag) type { |
test/stage2/cbe.zig+1| ... | ... | @@ -62,6 +62,7 @@ pub fn addCases(ctx: *TestContext) !void { |
| 62 | 62 | \\	register size_t rax_constant __asm__("rax") = 231; |
| 63 | 63 | \\	register size_t rdi_constant __asm__("rdi") = 0; |
| 64 | 64 | \\	__asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); |
| 65 | \\	return; | |
| 65 | 66 | \\} |
| 66 | 67 | \\ |
| 67 | 68 | ); |
test/stage2/compare_output.zig+28-1| ... | ... | @@ -120,7 +120,7 @@ pub fn addCases(ctx: *TestContext) !void { |
| 120 | 120 | } |
| 121 | 121 | |
| 122 | 122 | { |
| 123 | var case = ctx.exe("adding numbers", linux_x64); | |
| 123 | var case = ctx.exe("adding numbers at comptime", linux_x64); | |
| 124 | 124 | case.addCompareOutput( |
| 125 | 125 | \\export fn _start() noreturn { |
| 126 | 126 | \\ asm volatile ("syscall" |
| ... | ... | @@ -143,4 +143,31 @@ pub fn addCases(ctx: *TestContext) !void { |
| 143 | 143 | "Hello, World!\n", |
| 144 | 144 | ); |
| 145 | 145 | } |
| 146 | ||
| 147 | { | |
| 148 | var case = ctx.exe("adding numbers at runtime", linux_x64); | |
| 149 | case.addCompareOutput( | |
| 150 | \\export fn _start() noreturn { | |
| 151 | \\ add(3, 4); | |
| 152 | \\ | |
| 153 | \\ exit(); | |
| 154 | \\} | |
| 155 | \\ | |
| 156 | \\fn add(a: u32, b: u32) void { | |
| 157 | \\ if (a + b != 7) unreachable; | |
| 158 | \\} | |
| 159 | \\ | |
| 160 | \\fn exit() noreturn { | |
| 161 | \\ asm volatile ("syscall" | |
| 162 | \\ : | |
| 163 | \\ : [number] "{rax}" (231), | |
| 164 | \\ [arg1] "{rdi}" (0) | |
| 165 | \\ : "rcx", "r11", "memory" | |
| 166 | \\ ); | |
| 167 | \\ unreachable; | |
| 168 | \\} | |
| 169 | , | |
| 170 | "", | |
| 171 | ); | |
| 172 | } | |
| 146 | 173 | } |