diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 7e71cc55db..e400d7c073 100644 --- a/src-self-hosted/codegen.zig +++ b/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 } { diff --git a/src-self-hosted/codegen/x86_64.zig b/src-self-hosted/codegen/x86_64.zig index 2c0937d28d..f6bad45809 100644 --- a/src-self-hosted/codegen/x86_64.zig +++ b/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 }; diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 89e52f4402..2e7c6317b6 100644 --- a/test/stage2/compare_output.zig +++ b/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; + \\} + , + "", + ); } }