Commit b8e22d2002

Andrew Kelley <andrew@ziglang.org>
2020-07-28 03:59:00
stage2: implement integer return values
1 parent 3e0a462
Changed files (3)
src-self-hosted
test
src-self-hosted/codegen/x86_64.zig
@@ -88,3 +88,4 @@ pub const Register = enum(u8) {
 /// These registers belong to the called function.
 pub const callee_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8, .r9, .r10, .r11 };
 pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
+pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };
src-self-hosted/codegen.zig
@@ -206,6 +206,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         code: *std.ArrayList(u8),
         err_msg: ?*ErrorMsg,
         args: []MCValue,
+        ret_mcv: MCValue,
         arg_index: usize,
         src: usize,
 
@@ -333,11 +334,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
 
             const fn_type = module_fn.owner_decl.typed_value.most_recent.typed_value.ty;
-            const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen());
-            defer bin_file.allocator.free(param_types);
-            fn_type.fnParamTypes(param_types);
-            var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len);
-            defer bin_file.allocator.free(mc_args);
 
             var branch_stack = std.ArrayList(Branch).init(bin_file.allocator);
             defer {
@@ -355,17 +351,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .mod_fn = module_fn,
                 .code = code,
                 .err_msg = null,
-                .args = mc_args,
+                .args = undefined, // populated after `resolveCallingConventionValues`
+                .ret_mcv = undefined, // populated after `resolveCallingConventionValues`
                 .arg_index = 0,
                 .branch_stack = &branch_stack,
                 .src = src,
             };
 
-            const cc = fn_type.fnCallingConvention();
-            branch.max_end_stack = function.resolveParameters(src, cc, param_types, mc_args) catch |err| switch (err) {
+            var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) {
                 error.CodegenFail => return Result{ .fail = function.err_msg.? },
                 else => |e| return e,
             };
+            defer call_info.deinit(&function);
+
+            function.args = call_info.args;
+            function.ret_mcv = call_info.return_value;
+            branch.max_end_stack = call_info.stack_byte_count;
 
             function.gen() catch |err| switch (err) {
                 error.CodegenFail => return Result{ .fail = function.err_msg.? },
@@ -705,18 +706,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
         }
 
         fn genCall(self: *Self, inst: *ir.Inst.Call) !MCValue {
-            const fn_ty = inst.func.ty;
-            const cc = fn_ty.fnCallingConvention();
-            const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
-            defer self.gpa.free(param_types);
-            fn_ty.fnParamTypes(param_types);
-            var mc_args = try self.gpa.alloc(MCValue, param_types.len);
-            defer self.gpa.free(mc_args);
-            const stack_byte_count = try self.resolveParameters(inst.base.src, cc, param_types, mc_args);
+            var info = try self.resolveCallingConventionValues(inst.base.src, inst.func.ty);
+            defer info.deinit(self);
 
             switch (arch) {
                 .x86_64 => {
-                    for (mc_args) |mc_arg, arg_i| {
+                    for (info.args) |mc_arg, arg_i| {
                         const arg = inst.args[arg_i];
                         const arg_mcv = try self.resolveInst(inst.args[arg_i]);
                         switch (mc_arg) {
@@ -761,18 +756,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
             }
 
-            const return_type = fn_ty.fnReturnType();
-            switch (return_type.zigTypeTag()) {
-                .Void => return MCValue{ .none = {} },
-                .NoReturn => return MCValue{ .unreach = {} },
-                else => return self.fail(inst.base.src, "TODO implement fn call with non-void return value", .{}),
-            }
+            return info.return_value;
         }
 
         fn ret(self: *Self, src: usize, mcv: MCValue) !MCValue {
-            if (mcv != .none) {
-                return self.fail(src, "TODO implement return with non-void operand", .{});
-            }
+            try self.setRegOrStack(src, self.ret_mcv, mcv);
             switch (arch) {
                 .i386 => {
                     try self.code.append(0xc3); // ret
@@ -1024,12 +1012,23 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
+        /// Sets the value without any modifications to register allocation metadata or stack allocation metadata.
+        fn setRegOrStack(self: *Self, src: usize, loc: MCValue, val: MCValue) !void {
+            switch (loc) {
+                .none => return,
+                .register => |reg| return self.genSetReg(src, reg, val),
+                .stack_offset => {
+                    return self.fail(src, "TODO implement setRegOrStack for stack offset", .{});
+                },
+                else => unreachable,
+            }
+        }
+
         fn genSetReg(self: *Self, src: usize, reg: Register, mcv: MCValue) error{ CodegenFail, OutOfMemory }!void {
             switch (arch) {
                 .x86_64 => switch (mcv) {
                     .dead => unreachable,
-                    .none => unreachable,
-                    .unreach => unreachable,
+                    .unreach, .none => return, // Nothing to do.
                     .compare_flags_unsigned => |op| {
                         try self.code.ensureCapacity(self.code.items.len + 3);
                         self.rex(.{ .b = reg.isExtended(), .w = reg.size() == 64 });
@@ -1131,6 +1130,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         mem.writeIntLittle(i32, imm_ptr, offset);
                     },
                     .register => |src_reg| {
+                        // If the registers are the same, nothing to do.
+                        if (src_reg == reg)
+                            return;
+
                         if (reg.size() != 64) {
                             return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
                         }
@@ -1211,7 +1214,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                         return self.fail(src, "TODO implement genSetReg for stack variables", .{});
                     },
                 },
-                else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}),
+                else => return self.fail(src, "TODO implement getSetReg for {}", .{self.target.cpu.arch}),
             }
         }
 
@@ -1320,19 +1323,40 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
-        fn resolveParameters(
-            self: *Self,
-            src: usize,
-            cc: std.builtin.CallingConvention,
-            param_types: []const Type,
-            results: []MCValue,
-        ) !u32 {
+        const CallMCValues = struct {
+            args: []MCValue,
+            return_value: MCValue,
+            stack_byte_count: u32,
+
+            fn deinit(self: *CallMCValues, func: *Self) void {
+                func.gpa.free(self.args);
+                self.* = undefined;
+            }
+        };
+
+        /// Caller must call `CallMCValues.deinit`.
+        fn resolveCallingConventionValues(self: *Self, src: usize, fn_ty: Type) !CallMCValues {
+            const cc = fn_ty.fnCallingConvention();
+            const param_types = try self.gpa.alloc(Type, fn_ty.fnParamLen());
+            defer self.gpa.free(param_types);
+            fn_ty.fnParamTypes(param_types);
+            var result: CallMCValues = .{
+                .args = try self.gpa.alloc(MCValue, param_types.len),
+                .return_value = undefined,
+                .stack_byte_count = undefined,
+            };
+            errdefer self.gpa.free(result.args);
+
+            const ret_ty = fn_ty.fnReturnType();
+
             switch (arch) {
                 .x86_64 => {
                     switch (cc) {
                         .Naked => {
-                            assert(results.len == 0);
-                            return 0;
+                            assert(result.args.len == 0);
+                            result.return_value = .{ .unreach = {} };
+                            result.stack_byte_count = 0;
+                            return result;
                         },
                         .Unspecified, .C => {
                             var next_int_reg: usize = 0;
@@ -1342,23 +1366,39 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                                 switch (ty.zigTypeTag()) {
                                     .Bool, .Int => {
                                         if (next_int_reg >= c_abi_int_param_regs.len) {
-                                            results[i] = .{ .stack_offset = next_stack_offset };
+                                            result.args[i] = .{ .stack_offset = next_stack_offset };
                                             next_stack_offset += @intCast(u32, ty.abiSize(self.target.*));
                                         } else {
-                                            results[i] = .{ .register = c_abi_int_param_regs[next_int_reg] };
+                                            result.args[i] = .{ .register = c_abi_int_param_regs[next_int_reg] };
                                             next_int_reg += 1;
                                         }
                                     },
                                     else => return self.fail(src, "TODO implement function parameters of type {}", .{@tagName(ty.zigTypeTag())}),
                                 }
                             }
-                            return next_stack_offset;
+                            result.stack_byte_count = next_stack_offset;
                         },
                         else => return self.fail(src, "TODO implement function parameters for {}", .{cc}),
                     }
                 },
-                else => return self.fail(src, "TODO implement C ABI support for {}", .{self.target.cpu.arch}),
+                else => return self.fail(src, "TODO implement codegen parameters for {}", .{self.target.cpu.arch}),
+            }
+
+            if (ret_ty.zigTypeTag() == .NoReturn) {
+                result.return_value = .{ .unreach = {} };
+            } else if (!ret_ty.hasCodeGenBits()) {
+                result.return_value = .{ .none = {} };
+            } else switch (arch) {
+                .x86_64 => switch (cc) {
+                    .Naked => unreachable,
+                    .Unspecified, .C => {
+                        result.return_value = .{ .register = c_abi_int_return_regs[0] };
+                    },
+                    else => return self.fail(src, "TODO implement function return values for {}", .{cc}),
+                },
+                else => return self.fail(src, "TODO implement codegen return values for {}", .{self.target.cpu.arch}),
             }
+            return result;
         }
 
         fn fail(self: *Self, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {
test/stage2/compare_output.zig
@@ -333,5 +333,35 @@ pub fn addCases(ctx: *TestContext) !void {
         ,
             "",
         );
+
+        // Now we test integer return values.
+        case.addCompareOutput(
+            \\export fn _start() noreturn {
+            \\    assert(add(3, 4) == 7);
+            \\    assert(add(20, 10) == 30);
+            \\
+            \\    exit();
+            \\}
+            \\
+            \\fn add(a: u32, b: u32) u32 {
+            \\    return a + b;
+            \\}
+            \\
+            \\pub fn assert(ok: bool) void {
+            \\    if (!ok) unreachable; // assertion failure
+            \\}
+            \\
+            \\fn exit() noreturn {
+            \\    asm volatile ("syscall"
+            \\        :
+            \\        : [number] "{rax}" (231),
+            \\          [arg1] "{rdi}" (0)
+            \\        : "rcx", "r11", "memory"
+            \\    );
+            \\    unreachable;
+            \\}
+        ,
+            "",
+        );
     }
 }