authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-13 00:32:12-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-13 00:32:12-07:00
log75a720565bb72e7fab03346df6a35e9a8f380962
tree954fccd90a73b8ed6e7c9f7a45a2c978c1a67840
parent1cab40d7837c69fe6a77f76be9dc27026acd935e
parentc94652a2fd210fe4f007dbdd47c9e55b38e482fb

Merge branch 'stage2-condbr'


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,6 +1210,13 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
12101210
1211 try self.astGenBlock(&gen_scope.base, body_block);1211 try self.astGenBlock(&gen_scope.base, body_block);
12121212
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 const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR);1220 const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR);
1214 fn_zir.* = .{1221 fn_zir.* = .{
1215 .body = .{1222 .body = .{
...@@ -2686,7 +2693,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr...@@ -2686,7 +2693,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr
26862693
2687 // Blocks must terminate with noreturn instruction.2694 // Blocks must terminate with noreturn instruction.
2688 assert(child_block.instructions.items.len != 0);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());
26902697
2691 // Need to set the type and emit the Block instruction. This allows machine code generation2698 // Need to set the type and emit the Block instruction. This allows machine code generation
2692 // to emit a jump instruction to after the block when it encounters the break.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,7 +3278,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner
3271 defer false_block.instructions.deinit(self.gpa);3278 defer false_block.instructions.deinit(self.gpa);
3272 try self.analyzeBody(&false_block.base, inst.positionals.false_body);3279 try self.analyzeBody(&false_block.base, inst.positionals.false_body);
32733280
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 .condition = cond,3282 .condition = cond,
3276 .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) },3283 .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) },
3277 .false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) },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,9 +3525,26 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type {
3518fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type {3525fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type {
3519 if (instructions.len == 0)3526 if (instructions.len == 0)
3520 return Type.initTag(.noreturn);3527 return Type.initTag(.noreturn);
3528
3521 if (instructions.len == 1)3529 if (instructions.len == 1)
3522 return instructions[0].ty;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}
35253549
3526fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {3550fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
src-self-hosted/cgen.zig deleted-205
...@@ -1,205 +0,0 @@
1const link = @import("link.zig");
2const Module = @import("Module.zig");
3
4const std = @import("std");
5
6const Inst = @import("ir.zig").Inst;
7const Value = @import("value.zig").Value;
8const Type = @import("type.zig").Type;
9
10const C = link.File.C;
11const Decl = Module.Decl;
12const 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.
16fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
17 return allocator.dupe(u8, name);
18}
19
20fn 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
44fn 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
56pub 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
64fn 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
81fn 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
107fn 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
125fn 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
154fn 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,10 +53,8 @@ pub fn generateSymbol(
53 const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen());53 const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen());
54 defer bin_file.allocator.free(param_types);54 defer bin_file.allocator.free(param_types);
55 fn_type.fnParamTypes(param_types);55 fn_type.fnParamTypes(param_types);
56 // A parameter may be broken into multiple machine code parameters, so we don't56 var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len);
57 // know the size up front.57 defer bin_file.allocator.free(mc_args);
58 var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len);
59 defer mc_args.deinit();
6058
61 var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator);59 var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator);
62 defer {60 defer {
...@@ -67,57 +65,6 @@ pub fn generateSymbol(...@@ -67,57 +65,6 @@ pub fn generateSymbol(
67 const branch = try branch_stack.addOne();65 const branch = try branch_stack.addOne();
68 branch.* = .{};66 branch.* = .{};
6967
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 var function = Function{68 var function = Function{
122 .gpa = bin_file.allocator,69 .gpa = bin_file.allocator,
123 .target = &bin_file.options.target,70 .target = &bin_file.options.target,
...@@ -125,11 +72,17 @@ pub fn generateSymbol(...@@ -125,11 +72,17 @@ pub fn generateSymbol(
125 .mod_fn = module_fn,72 .mod_fn = module_fn,
126 .code = code,73 .code = code,
127 .err_msg = null,74 .err_msg = null,
128 .args = mc_args.items,75 .args = mc_args,
129 .branch_stack = &branch_stack,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 };
13185
132 branch.max_end_stack = branch.next_stack_offset;
133 function.gen() catch |err| switch (err) {86 function.gen() catch |err| switch (err) {
134 error.CodegenFail => return Result{ .fail = function.err_msg.? },87 error.CodegenFail => return Result{ .fail = function.err_msg.? },
135 else => |e| return e,88 else => |e| return e,
...@@ -235,6 +188,65 @@ const InnerError = error{...@@ -235,6 +188,65 @@ const InnerError = error{
235 CodegenFail,188 CodegenFail,
236};189};
237190
191const 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
238const Function = struct {250const Function = struct {
239 gpa: *Allocator,251 gpa: *Allocator,
240 bin_file: *link.File.Elf,252 bin_file: *link.File.Elf,
...@@ -243,6 +255,7 @@ const Function = struct {...@@ -243,6 +255,7 @@ const Function = struct {
243 code: *std.ArrayList(u8),255 code: *std.ArrayList(u8),
244 err_msg: ?*ErrorMsg,256 err_msg: ?*ErrorMsg,
245 args: []MCValue,257 args: []MCValue,
258 src: usize,
246259
247 /// Whenever there is a runtime branch, we push a Branch onto this stack,260 /// Whenever there is a runtime branch, we push a Branch onto this stack,
248 /// and pop it off when the runtime branch joins. This provides an "overlay"261 /// and pop it off when the runtime branch joins. This provides an "overlay"
...@@ -284,65 +297,6 @@ const Function = struct {...@@ -284,65 +297,6 @@ const Function = struct {
284 size: u32,297 size: u32,
285 };298 };
286299
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 fn gen(self: *Function) !void {300 fn gen(self: *Function) !void {
347 switch (self.target.cpu.arch) {301 switch (self.target.cpu.arch) {
348 .arm => return self.genArch(.arm),302 .arm => return self.genArch(.arm),
...@@ -400,7 +354,28 @@ const Function = struct {...@@ -400,7 +354,28 @@ const Function = struct {
400 }354 }
401355
402 fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void {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 }
405380
406 fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void {381 fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void {
...@@ -593,13 +568,42 @@ const Function = struct {...@@ -593,13 +568,42 @@ const Function = struct {
593 }568 }
594569
595 fn genCall(self: *Function, inst: *ir.Inst.Call, comptime arch: std.Target.Cpu.Arch) !MCValue {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 switch (arch) {580 switch (arch) {
597 .x86_64, .i386 => {581 .x86_64 => {
598 if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {582 for (mc_args) |mc_arg, arg_i| {
599 if (inst.args.args.len != 0) {583 const arg = inst.args.args[arg_i];
600 return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{});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 }
602605
606 if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {
603 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {607 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
604 const func = func_val.func;608 const func = func_val.func;
605 const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];609 const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
...@@ -607,17 +611,11 @@ const Function = struct {...@@ -607,17 +611,11 @@ const Function = struct {
607 const ptr_bytes: u64 = @divExact(ptr_bits, 8);611 const ptr_bytes: u64 = @divExact(ptr_bits, 8);
608 const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes);612 const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes);
609 // ff 14 25 xx xx xx xx call [addr]613 // ff 14 25 xx xx xx xx call [addr]
610 try self.code.resize(self.code.items.len + 7);614 try self.code.ensureCapacity(self.code.items.len + 7);
611 self.code.items[self.code.items.len - 7 ..][0..3].* = [3]u8{ 0xff, 0x14, 0x25 };615 self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 });
612 mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], got_addr);616 mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(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 }
619 } else {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 } else {620 } else {
623 return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});621 return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
...@@ -625,6 +623,13 @@ const Function = struct {...@@ -625,6 +623,13 @@ const Function = struct {
625 },623 },
626 else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),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 }
629634
630 fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue {635 fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue {
...@@ -632,9 +637,15 @@ const Function = struct {...@@ -632,9 +637,15 @@ const Function = struct {
632 return self.fail(src, "TODO implement return with non-void operand", .{});637 return self.fail(src, "TODO implement return with non-void operand", .{});
633 }638 }
634 switch (arch) {639 switch (arch) {
635 .i386, .x86_64 => {640 .i386 => {
636 try self.code.append(0xc3); // ret641 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 else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),649 else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
639 }650 }
640 return .unreach;651 return .unreach;
...@@ -769,14 +780,22 @@ const Function = struct {...@@ -769,14 +780,22 @@ const Function = struct {
769 }780 }
770781
771 fn genBr(self: *Function, inst: *ir.Inst.Br, comptime arch: std.Target.Cpu.Arch) !MCValue {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 switch (arch) {787 switch (arch) {
773 else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}),788 else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}),
774 }789 }
775 }790 }
776791
777 fn genBrVoid(self: *Function, inst: *ir.Inst.BrVoid, comptime arch: std.Target.Cpu.Arch) !MCValue {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 // Emit a jump with a relocation. It will be patched up after the block ends.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);
780799
781 switch (arch) {800 switch (arch) {
782 .i386, .x86_64 => {801 .i386, .x86_64 => {
...@@ -785,9 +804,9 @@ const Function = struct {...@@ -785,9 +804,9 @@ const Function = struct {
785 try self.code.resize(self.code.items.len + 5);804 try self.code.resize(self.code.items.len + 5);
786 self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32805 self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32
787 // Leave the jump offset undefined806 // 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 return .none;811 return .none;
793 }812 }
...@@ -1122,6 +1141,48 @@ const Function = struct {...@@ -1122,6 +1141,48 @@ const Function = struct {
1122 }1141 }
1123 }1142 }
11241143
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 fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {1186 fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {
1126 @setCold(true);1187 @setCold(true);
1127 assert(self.err_msg == null);1188 assert(self.err_msg == null);
src-self-hosted/codegen/c.zig created+206
...@@ -0,0 +1,206 @@
1const std = @import("std");
2
3const link = @import("../link.zig");
4const Module = @import("../Module.zig");
5
6const Inst = @import("../ir.zig").Inst;
7const Value = @import("../value.zig").Value;
8const Type = @import("../type.zig").Type;
9
10const C = link.File.C;
11const Decl = Module.Decl;
12const 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.
16fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
17 return allocator.dupe(u8, name);
18}
19
20fn 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
44fn 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
56pub 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
64fn 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
81fn 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
108fn 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
126fn 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
155fn 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,36 +60,6 @@ pub const Inst = struct {
60 retvoid,60 retvoid,
61 sub,61 sub,
62 unreach,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 };
9464
95 pub fn cast(base: *Inst, comptime T: type) ?*T {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 +7,7 @@ const Module = @import("Module.zig");
7const fs = std.fs;7const fs = std.fs;
8const elf = std.elf;8const elf = std.elf;
9const codegen = @import("codegen.zig");9const codegen = @import("codegen.zig");
10const cgen = @import("cgen.zig");10const c_codegen = @import("codegen/c.zig");
1111
12const default_entry_addr = 0x8000000;12const default_entry_addr = 0x8000000;
1313
...@@ -259,7 +259,7 @@ pub const File = struct {...@@ -259,7 +259,7 @@ pub const File = struct {
259 }259 }
260260
261 pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void {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 if (err == error.CGenFailure) {263 if (err == error.CGenFailure) {
264 try module.failed_decls.put(module.gpa, decl, self.error_msg);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,6 +468,10 @@ pub const Type = extern union {
468 };468 };
469 }469 }
470470
471 pub fn isNoReturn(self: Type) bool {
472 return self.zigTypeTag() == .NoReturn;
473 }
474
471 /// Asserts that hasCodeGenBits() is true.475 /// Asserts that hasCodeGenBits() is true.
472 pub fn abiAlignment(self: Type, target: Target) u32 {476 pub fn abiAlignment(self: Type, target: Target) u32 {
473 return switch (self.tag()) {477 return switch (self.tag()) {
src-self-hosted/zir.zig+46
...@@ -81,6 +81,52 @@ pub const Inst = struct {...@@ -81,6 +81,52 @@ pub const Inst = struct {
81 condbr,81 condbr,
82 isnull,82 isnull,
83 isnonnull,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 };
85131
86 pub fn TagToType(tag: Tag) type {132 pub fn TagToType(tag: Tag) type {
test/stage2/cbe.zig+1
...@@ -62,6 +62,7 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -62,6 +62,7 @@ pub fn addCases(ctx: *TestContext) !void {
62 \\ register size_t rax_constant __asm__("rax") = 231;62 \\ register size_t rax_constant __asm__("rax") = 231;
63 \\ register size_t rdi_constant __asm__("rdi") = 0;63 \\ register size_t rdi_constant __asm__("rdi") = 0;
64 \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant));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,7 +120,7 @@ pub fn addCases(ctx: *TestContext) !void {
120 }120 }
121121
122 {122 {
123 var case = ctx.exe("adding numbers", linux_x64);123 var case = ctx.exe("adding numbers at comptime", linux_x64);
124 case.addCompareOutput(124 case.addCompareOutput(
125 \\export fn _start() noreturn {125 \\export fn _start() noreturn {
126 \\ asm volatile ("syscall"126 \\ asm volatile ("syscall"
...@@ -143,4 +143,31 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -143,4 +143,31 @@ pub fn addCases(ctx: *TestContext) !void {
143 "Hello, World!\n",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}