Commit b75a51f94b

Andrew Kelley <andrew@ziglang.org>
2020-07-13 08:04:24
stage2: implement function calling convention for calls
1 parent 8fe63d5
Changed files (1)
src-self-hosted
src-self-hosted/codegen.zig
@@ -53,10 +53,8 @@ pub fn generateSymbol(
             const param_types = try bin_file.allocator.alloc(Type, fn_type.fnParamLen());
             defer bin_file.allocator.free(param_types);
             fn_type.fnParamTypes(param_types);
-            // A parameter may be broken into multiple machine code parameters, so we don't
-            // know the size up front.
-            var mc_args = try std.ArrayList(Function.MCValue).initCapacity(bin_file.allocator, param_types.len);
-            defer mc_args.deinit();
+            var mc_args = try bin_file.allocator.alloc(MCValue, param_types.len);
+            defer bin_file.allocator.free(mc_args);
 
             var branch_stack = std.ArrayList(Function.Branch).init(bin_file.allocator);
             defer {
@@ -67,57 +65,6 @@ pub fn generateSymbol(
             const branch = try branch_stack.addOne();
             branch.* = .{};
 
-            switch (fn_type.fnCallingConvention()) {
-                .Naked => assert(mc_args.items.len == 0),
-                .Unspecified, .C => {
-                    // Prepare the function parameters
-                    switch (bin_file.options.target.cpu.arch) {
-                        .x86_64 => {
-                            const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
-                            var next_int_reg: usize = 0;
-
-                            for (param_types) |param_type, src_i| {
-                                switch (param_type.zigTypeTag()) {
-                                    .Bool, .Int => {
-                                        if (next_int_reg >= integer_registers.len) {
-                                            try mc_args.append(.{ .stack_offset = branch.next_stack_offset });
-                                            branch.next_stack_offset += @intCast(u32, param_type.abiSize(bin_file.options.target));
-                                        } else {
-                                            try mc_args.append(.{ .register = @enumToInt(integer_registers[next_int_reg]) });
-                                            next_int_reg += 1;
-                                        }
-                                    },
-                                    else => return Result{
-                                        .fail = try ErrorMsg.create(
-                                            bin_file.allocator,
-                                            src,
-                                            "TODO implement function parameters of type {}",
-                                            .{@tagName(param_type.zigTypeTag())},
-                                        ),
-                                    },
-                                }
-                            }
-                        },
-                        else => return Result{
-                            .fail = try ErrorMsg.create(
-                                bin_file.allocator,
-                                src,
-                                "TODO implement function parameters for {}",
-                                .{bin_file.options.target.cpu.arch},
-                            ),
-                        },
-                    }
-                },
-                else => return Result{
-                    .fail = try ErrorMsg.create(
-                        bin_file.allocator,
-                        src,
-                        "TODO implement {} calling convention",
-                        .{fn_type.fnCallingConvention()},
-                    ),
-                },
-            }
-
             var function = Function{
                 .gpa = bin_file.allocator,
                 .target = &bin_file.options.target,
@@ -125,11 +72,17 @@ pub fn generateSymbol(
                 .mod_fn = module_fn,
                 .code = code,
                 .err_msg = null,
-                .args = mc_args.items,
+                .args = mc_args,
                 .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) {
+                error.CodegenFail => return Result{ .fail = function.err_msg.? },
+                else => |e| return e,
             };
 
-            branch.max_end_stack = branch.next_stack_offset;
             function.gen() catch |err| switch (err) {
                 error.CodegenFail => return Result{ .fail = function.err_msg.? },
                 else => |e| return e,
@@ -235,6 +188,65 @@ const InnerError = error{
     CodegenFail,
 };
 
+const MCValue = union(enum) {
+    /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
+    none,
+    /// Control flow will not allow this value to be observed.
+    unreach,
+    /// No more references to this value remain.
+    dead,
+    /// A pointer-sized integer that fits in a register.
+    immediate: u64,
+    /// The constant was emitted into the code, at this offset.
+    embedded_in_code: usize,
+    /// The value is in a target-specific register. The value can
+    /// be @intToEnum casted to the respective Reg enum.
+    register: usize,
+    /// The value is in memory at a hard-coded address.
+    memory: u64,
+    /// The value is one of the stack variables.
+    stack_offset: u64,
+    /// The value is in the compare flags assuming an unsigned operation,
+    /// with this operator applied on top of it.
+    compare_flags_unsigned: std.math.CompareOperator,
+    /// The value is in the compare flags assuming a signed operation,
+    /// with this operator applied on top of it.
+    compare_flags_signed: std.math.CompareOperator,
+
+    fn isMemory(mcv: MCValue) bool {
+        return switch (mcv) {
+            .embedded_in_code, .memory, .stack_offset => true,
+            else => false,
+        };
+    }
+
+    fn isImmediate(mcv: MCValue) bool {
+        return switch (mcv) {
+            .immediate => true,
+            else => false,
+        };
+    }
+
+    fn isMutable(mcv: MCValue) bool {
+        return switch (mcv) {
+            .none => unreachable,
+            .unreach => unreachable,
+            .dead => unreachable,
+
+            .immediate,
+            .embedded_in_code,
+            .memory,
+            .compare_flags_unsigned,
+            .compare_flags_signed,
+            => false,
+
+            .register,
+            .stack_offset,
+            => true,
+        };
+    }
+};
+
 const Function = struct {
     gpa: *Allocator,
     bin_file: *link.File.Elf,
@@ -243,6 +255,7 @@ const Function = struct {
     code: *std.ArrayList(u8),
     err_msg: ?*ErrorMsg,
     args: []MCValue,
+    src: usize,
 
     /// Whenever there is a runtime branch, we push a Branch onto this stack,
     /// and pop it off when the runtime branch joins. This provides an "overlay"
@@ -284,65 +297,6 @@ const Function = struct {
         size: u32,
     };
 
-    const MCValue = union(enum) {
-        /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc.
-        none,
-        /// Control flow will not allow this value to be observed.
-        unreach,
-        /// No more references to this value remain.
-        dead,
-        /// A pointer-sized integer that fits in a register.
-        immediate: u64,
-        /// The constant was emitted into the code, at this offset.
-        embedded_in_code: usize,
-        /// The value is in a target-specific register. The value can
-        /// be @intToEnum casted to the respective Reg enum.
-        register: usize,
-        /// The value is in memory at a hard-coded address.
-        memory: u64,
-        /// The value is one of the stack variables.
-        stack_offset: u64,
-        /// The value is in the compare flags assuming an unsigned operation,
-        /// with this operator applied on top of it.
-        compare_flags_unsigned: std.math.CompareOperator,
-        /// The value is in the compare flags assuming a signed operation,
-        /// with this operator applied on top of it.
-        compare_flags_signed: std.math.CompareOperator,
-
-        fn isMemory(mcv: MCValue) bool {
-            return switch (mcv) {
-                .embedded_in_code, .memory, .stack_offset => true,
-                else => false,
-            };
-        }
-
-        fn isImmediate(mcv: MCValue) bool {
-            return switch (mcv) {
-                .immediate => true,
-                else => false,
-            };
-        }
-
-        fn isMutable(mcv: MCValue) bool {
-            return switch (mcv) {
-                .none => unreachable,
-                .unreach => unreachable,
-                .dead => unreachable,
-
-                .immediate,
-                .embedded_in_code,
-                .memory,
-                .compare_flags_unsigned,
-                .compare_flags_signed,
-                => false,
-
-                .register,
-                .stack_offset,
-                => true,
-            };
-        }
-    };
-
     fn gen(self: *Function) !void {
         switch (self.target.cpu.arch) {
             .arm => return self.genArch(.arm),
@@ -400,7 +354,28 @@ const Function = struct {
     }
 
     fn genArch(self: *Function, comptime arch: std.Target.Cpu.Arch) !void {
-        return self.genBody(self.mod_fn.analysis.success, arch);
+        try self.code.ensureCapacity(self.code.items.len + 11);
+
+        // push rbp
+        // mov rbp, rsp
+        self.code.appendSliceAssumeCapacity(&[_]u8{ 0x55, 0x48, 0x89, 0xe5 });
+
+        // sub rsp, x
+        const stack_end = self.branch_stack.items[0].max_end_stack;
+        if (stack_end > std.math.maxInt(i32)) {
+            return self.fail(self.src, "too much stack used in call parameters", .{});
+        } else if (stack_end > std.math.maxInt(i8)) {
+            // 48 83 ec xx    sub rsp,0x10
+            self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x81, 0xec });
+            const x = @intCast(u32, stack_end);
+            mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), x);
+        } else if (stack_end != 0) {
+            // 48 81 ec xx xx xx xx   sub rsp,0x80
+            const x = @intCast(u8, stack_end);
+            self.code.appendSliceAssumeCapacity(&[_]u8{ 0x48, 0x83, 0xec, x });
+        }
+
+        try self.genBody(self.mod_fn.analysis.success, arch);
     }
 
     fn genBody(self: *Function, body: ir.Body, comptime arch: std.Target.Cpu.Arch) InnerError!void {
@@ -593,13 +568,42 @@ const Function = struct {
     }
 
     fn genCall(self: *Function, inst: *ir.Inst.Call, comptime arch: std.Target.Cpu.Arch) !MCValue {
+        const fn_ty = inst.args.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);
+
         switch (arch) {
-            .x86_64, .i386 => {
-                if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {
-                    if (inst.args.args.len != 0) {
-                        return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{});
+            .x86_64 => {
+                for (mc_args) |mc_arg, arg_i| {
+                    const arg = inst.args.args[arg_i];
+                    const arg_mcv = try self.resolveInst(inst.args.args[arg_i]);
+                    switch (mc_arg) {
+                        .none => continue,
+                        .register => |reg| {
+                            try self.genSetReg(arg.src, arch, @intToEnum(Reg(arch), @intCast(u8, reg)), arg_mcv);
+                            // TODO interact with the register allocator to mark the instruction as moved.
+                        },
+                        .stack_offset => {
+                            // Here we need to emit instructions like this:
+                            // mov     qword ptr [rsp + stack_offset], x
+                            return self.fail(inst.base.src, "TODO implement calling with parameters in memory", .{});
+                        },
+                        .immediate => unreachable,
+                        .unreach => unreachable,
+                        .dead => unreachable,
+                        .embedded_in_code => unreachable,
+                        .memory => unreachable,
+                        .compare_flags_signed => unreachable,
+                        .compare_flags_unsigned => unreachable,
                     }
+                }
 
+                if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {
                     if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
                         const func = func_val.func;
                         const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
@@ -607,17 +611,11 @@ const Function = struct {
                         const ptr_bytes: u64 = @divExact(ptr_bits, 8);
                         const got_addr = @intCast(u32, got.p_vaddr + func.owner_decl.link.offset_table_index * ptr_bytes);
                         // ff 14 25 xx xx xx xx    call [addr]
-                        try self.code.resize(self.code.items.len + 7);
-                        self.code.items[self.code.items.len - 7 ..][0..3].* = [3]u8{ 0xff, 0x14, 0x25 };
-                        mem.writeIntLittle(u32, self.code.items[self.code.items.len - 4 ..][0..4], got_addr);
-                        const return_type = func.owner_decl.typed_value.most_recent.typed_value.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", .{}),
-                        }
+                        try self.code.ensureCapacity(self.code.items.len + 7);
+                        self.code.appendSliceAssumeCapacity(&[3]u8{ 0xff, 0x14, 0x25 });
+                        mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), got_addr);
                     } else {
-                        return self.fail(inst.base.src, "TODO implement calling weird function values", .{});
+                        return self.fail(inst.base.src, "TODO implement calling bitcasted functions", .{});
                     }
                 } else {
                     return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
@@ -625,6 +623,13 @@ const Function = struct {
             },
             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", .{}),
+        }
     }
 
     fn ret(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, mcv: MCValue) !MCValue {
@@ -632,9 +637,15 @@ const Function = struct {
             return self.fail(src, "TODO implement return with non-void operand", .{});
         }
         switch (arch) {
-            .i386, .x86_64 => {
+            .i386 => {
                 try self.code.append(0xc3); // ret
             },
+            .x86_64 => {
+                try self.code.appendSlice(&[_]u8{
+                    0x5d, // pop rbp
+                    0xc3, // ret
+                });
+            },
             else => return self.fail(src, "TODO implement return for {}", .{self.target.cpu.arch}),
         }
         return .unreach;
@@ -1122,6 +1133,48 @@ const Function = struct {
         }
     }
 
+    fn resolveParameters(
+        self: *Function,
+        src: usize,
+        cc: std.builtin.CallingConvention,
+        param_types: []const Type,
+        results: []MCValue,
+    ) !u32 {
+        switch (self.target.cpu.arch) {
+            .x86_64 => {
+                switch (cc) {
+                    .Naked => {
+                        assert(results.len == 0);
+                        return 0;
+                    },
+                    .Unspecified, .C => {
+                        var next_int_reg: usize = 0;
+                        var next_stack_offset: u32 = 0;
+
+                        const integer_registers = [_]Reg(.x86_64){ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
+                        for (param_types) |ty, i| {
+                            switch (ty.zigTypeTag()) {
+                                .Bool, .Int => {
+                                    if (next_int_reg >= integer_registers.len) {
+                                        results[i] = .{ .stack_offset = next_stack_offset };
+                                        next_stack_offset += @intCast(u32, ty.abiSize(self.target.*));
+                                    } else {
+                                        results[i] = .{ .register = @enumToInt(integer_registers[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;
+                    },
+                    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}),
+        }
+    }
+
     fn fail(self: *Function, src: usize, comptime format: []const u8, args: anytype) error{ CodegenFail, OutOfMemory } {
         @setCold(true);
         assert(self.err_msg == null);