stage2: implement integer return values

This commit is contained in:
Andrew Kelley
2020-07-27 18:59:00 -07:00
parent 3e0a46281c
commit b8e22d2002
3 changed files with 113 additions and 42 deletions

View File

@@ -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 } {

View File

@@ -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 };

View File

@@ -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;
\\}
,
"",
);
}
}