From 8fe63d504226d5b181e627361e997a160dee4908 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jul 2020 23:04:00 -0700 Subject: stage2: peer type resolution with noreturn --- src-self-hosted/Module.zig | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 3f5a833680..bb8eca5ccf 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -3518,9 +3518,26 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { if (instructions.len == 0) return Type.initTag(.noreturn); + if (instructions.len == 1) return instructions[0].ty; - return self.fail(scope, instructions[0].src, "TODO peer type resolution", .{}); + + var prev_inst = instructions[0]; + for (instructions[1..]) |next_inst| { + if (next_inst.ty.eql(prev_inst.ty)) + continue; + if (next_inst.ty.zigTypeTag() == .NoReturn) + continue; + if (prev_inst.ty.zigTypeTag() == .NoReturn) { + prev_inst = next_inst; + continue; + } + + // TODO error notes pointing out each type + return self.fail(scope, next_inst.src, "incompatible types: '{}' and '{}'", .{ prev_inst.ty, next_inst.ty }); + } + + return prev_inst.ty; } fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { -- cgit v1.2.3 From b75a51f94b3b2ba1dc240a27caced8e848db5e10 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jul 2020 23:04:24 -0700 Subject: stage2: implement function calling convention for calls --- src-self-hosted/codegen.zig | 317 ++++++++++++++++++++++++++------------------ 1 file changed, 185 insertions(+), 132 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index ba91e0726f..487f20d1db 100644 --- a/src-self-hosted/codegen.zig +++ b/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); -- cgit v1.2.3 From c306392b4404a5a05dcb0958b88b50ecc9c7b6f5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jul 2020 23:12:41 -0700 Subject: stage2: codegen: more branching support --- src-self-hosted/codegen.zig | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 487f20d1db..143ad787b7 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -780,14 +780,22 @@ const Function = struct { } fn genBr(self: *Function, inst: *ir.Inst.Br, comptime arch: std.Target.Cpu.Arch) !MCValue { + if (!inst.args.operand.ty.hasCodeGenBits()) + return self.brVoid(inst.base.src, inst.args.block, arch); + + const operand = try self.resolveInst(inst.args.operand); switch (arch) { else => return self.fail(inst.base.src, "TODO implement br for {}", .{self.target.cpu.arch}), } } fn genBrVoid(self: *Function, inst: *ir.Inst.BrVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { + return self.brVoid(inst.base.src, inst.args.block, arch); + } + + fn brVoid(self: *Function, src: usize, block: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { // Emit a jump with a relocation. It will be patched up after the block ends. - try inst.args.block.codegen.relocs.ensureCapacity(self.gpa, inst.args.block.codegen.relocs.items.len + 1); + try block.codegen.relocs.ensureCapacity(self.gpa, block.codegen.relocs.items.len + 1); switch (arch) { .i386, .x86_64 => { @@ -796,9 +804,9 @@ const Function = struct { try self.code.resize(self.code.items.len + 5); self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 // Leave the jump offset undefined - inst.args.block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); + block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); }, - else => return self.fail(inst.base.src, "TODO implement brvoid for {}", .{self.target.cpu.arch}), + else => return self.fail(src, "TODO implement brvoid for {}", .{self.target.cpu.arch}), } return .none; } -- cgit v1.2.3 From 25b1c00c72b51ef9e011867b3fc4f37b3e216223 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 12 Jul 2020 23:50:25 -0700 Subject: stage2: add implicit return void where applicable --- src-self-hosted/Module.zig | 10 ++++++++-- src-self-hosted/ir.zig | 30 ------------------------------ src-self-hosted/type.zig | 4 ++++ src-self-hosted/zir.zig | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 58 insertions(+), 32 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index bb8eca5ccf..03307ab96d 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1210,6 +1210,12 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { try self.astGenBlock(&gen_scope.base, body_block); + const last_inst = gen_scope.instructions.items[gen_scope.instructions.items.len - 1]; + if (!last_inst.tag.isNoReturn()) { + const src = tree.token_locs[body_block.rbrace].start; + _ = try self.addZIRInst(&gen_scope.base, src, zir.Inst.ReturnVoid, .{}, .{}); + } + const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); fn_zir.* = .{ .body = .{ @@ -2686,7 +2692,7 @@ fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerEr // Blocks must terminate with noreturn instruction. assert(child_block.instructions.items.len != 0); - assert(child_block.instructions.items[child_block.instructions.items.len - 1].tag.isNoReturn()); + assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); // Need to set the type and emit the Block instruction. This allows machine code generation // to emit a jump instruction to after the block when it encounters the break. @@ -3271,7 +3277,7 @@ fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) Inner defer false_block.instructions.deinit(self.gpa); try self.analyzeBody(&false_block.base, inst.positionals.false_body); - return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){ + return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.noreturn), Inst.CondBr, Inst.Args(Inst.CondBr){ .condition = cond, .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) }, .false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) }, diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 094b877b53..74da3430a7 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -60,36 +60,6 @@ pub const Inst = struct { retvoid, sub, unreach, - - /// Returns whether the instruction is one of the control flow "noreturn" types. - /// Function calls do not count. When ZIR is generated, the compiler automatically - /// emits an `Unreach` after a function call with the `noreturn` return type. - pub fn isNoReturn(tag: Tag) bool { - return switch (tag) { - .add, - .arg, - .assembly, - .bitcast, - .block, - .breakpoint, - .call, - .cmp, - .constant, - .isnonnull, - .isnull, - .ptrtoint, - .sub, - => false, - - .br, - .brvoid, - .condbr, - .ret, - .retvoid, - .unreach, - => true, - }; - } }; pub fn cast(base: *Inst, comptime T: type) ?*T { diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index c0d88b1414..512dd631a7 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -468,6 +468,10 @@ pub const Type = extern union { }; } + pub fn isNoReturn(self: Type) bool { + return self.zigTypeTag() == .NoReturn; + } + /// Asserts that hasCodeGenBits() is true. pub fn abiAlignment(self: Type, target: Target) u32 { return switch (self.tag()) { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 8faf248636..0dfbdd20a0 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -81,6 +81,52 @@ pub const Inst = struct { condbr, isnull, isnonnull, + + /// Returns whether the instruction is one of the control flow "noreturn" types. + /// Function calls do not count. + pub fn isNoReturn(tag: Tag) bool { + return switch (tag) { + .arg, + .block, + .breakpoint, + .call, + .@"const", + .declref, + .declref_str, + .declval, + .declval_in_module, + .str, + .int, + .inttype, + .ptrtoint, + .fieldptr, + .deref, + .as, + .@"asm", + .@"fn", + .fntype, + .@"export", + .primitive, + .intcast, + .bitcast, + .elemptr, + .add, + .sub, + .cmp, + .isnull, + .isnonnull, + => false, + + .condbr, + .@"unreachable", + .@"return", + .returnvoid, + .@"break", + .breakvoid, + .compileerror, + => true, + }; + } }; pub fn TagToType(tag: Tag) type { -- cgit v1.2.3 From 08154c0deb653e016d6eb285c85094048eeded89 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2020 00:28:11 -0700 Subject: stage2: add retvoid support to CBE --- src-self-hosted/Module.zig | 5 +- src-self-hosted/cgen.zig | 205 ----------------------------------------- src-self-hosted/codegen/c.zig | 206 ++++++++++++++++++++++++++++++++++++++++++ src-self-hosted/link.zig | 4 +- test/stage2/cbe.zig | 1 + 5 files changed, 212 insertions(+), 209 deletions(-) delete mode 100644 src-self-hosted/cgen.zig create mode 100644 src-self-hosted/codegen/c.zig diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 03307ab96d..975158c477 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -1210,8 +1210,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { try self.astGenBlock(&gen_scope.base, body_block); - const last_inst = gen_scope.instructions.items[gen_scope.instructions.items.len - 1]; - if (!last_inst.tag.isNoReturn()) { + if (!fn_type.fnReturnType().isNoReturn() and (gen_scope.instructions.items.len == 0 or + !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) + { const src = tree.token_locs[body_block.rbrace].start; _ = try self.addZIRInst(&gen_scope.base, src, zir.Inst.ReturnVoid, .{}, .{}); } diff --git a/src-self-hosted/cgen.zig b/src-self-hosted/cgen.zig deleted file mode 100644 index 91dc4a81a4..0000000000 --- a/src-self-hosted/cgen.zig +++ /dev/null @@ -1,205 +0,0 @@ -const link = @import("link.zig"); -const Module = @import("Module.zig"); - -const std = @import("std"); - -const Inst = @import("ir.zig").Inst; -const Value = @import("value.zig").Value; -const Type = @import("type.zig").Type; - -const C = link.File.C; -const Decl = Module.Decl; -const mem = std.mem; - -/// Maps a name from Zig source to C. This will always give the same output for -/// any given input. -fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { - return allocator.dupe(u8, name); -} - -fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) !void { - if (T.tag() == .usize) { - file.need_stddef = true; - try writer.writeAll("size_t"); - } else { - switch (T.zigTypeTag()) { - .NoReturn => { - file.need_noreturn = true; - try writer.writeAll("noreturn void"); - }, - .Void => try writer.writeAll("void"), - .Int => { - if (T.tag() == .u8) { - file.need_stdint = true; - try writer.writeAll("uint8_t"); - } else { - return file.fail(src, "TODO implement int types", .{}); - } - }, - else => |e| return file.fail(src, "TODO implement type {}", .{e}), - } - } -} - -fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { - const tv = decl.typed_value.most_recent.typed_value; - try renderType(file, writer, tv.ty.fnReturnType(), decl.src()); - const name = try map(file.allocator, mem.spanZ(decl.name)); - defer file.allocator.free(name); - try writer.print(" {}(", .{name}); - if (tv.ty.fnParamLen() == 0) - try writer.writeAll("void)") - else - return file.fail(decl.src(), "TODO implement parameters", .{}); -} - -pub fn generate(file: *C, decl: *Decl) !void { - switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { - .Fn => try genFn(file, decl), - .Array => try genArray(file, decl), - else => |e| return file.fail(decl.src(), "TODO {}", .{e}), - } -} - -fn genArray(file: *C, decl: *Decl) !void { - const tv = decl.typed_value.most_recent.typed_value; - // TODO: prevent inline asm constants from being emitted - const name = try map(file.allocator, mem.span(decl.name)); - defer file.allocator.free(name); - if (tv.val.cast(Value.Payload.Bytes)) |payload| - if (tv.ty.arraySentinel()) |sentinel| - if (sentinel.toUnsignedInt() == 0) - try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }) - else - return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{}) - else - return file.fail(decl.src(), "TODO byte arrays without sentinels", .{}) - else - return file.fail(decl.src(), "TODO non-byte arrays", .{}); -} - -fn genFn(file: *C, decl: *Decl) !void { - const writer = file.main.writer(); - const tv = decl.typed_value.most_recent.typed_value; - - try renderFunctionSignature(file, writer, decl); - - try writer.writeAll(" {"); - - const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; - const instructions = func.analysis.success.instructions; - if (instructions.len > 0) { - for (instructions) |inst| { - try writer.writeAll("\n\t"); - switch (inst.tag) { - .assembly => try genAsm(file, inst.cast(Inst.Assembly).?, decl), - .call => try genCall(file, inst.cast(Inst.Call).?, decl), - .ret => try genRet(file, inst.cast(Inst.Ret).?, decl, tv.ty.fnReturnType()), - else => |e| return file.fail(decl.src(), "TODO {}", .{e}), - } - } - try writer.writeAll("\n"); - } - - try writer.writeAll("}\n\n"); -} - -fn genRet(file: *C, inst: *Inst.Ret, decl: *Decl, expected_return_type: Type) !void { - const writer = file.main.writer(); - const ret_value = inst.args.operand; - const value = ret_value.value().?; - if (expected_return_type.eql(ret_value.ty)) - return file.fail(decl.src(), "TODO return {}", .{expected_return_type}) - else if (expected_return_type.isInt() and ret_value.ty.tag() == .comptime_int) - if (value.intFitsInType(expected_return_type, file.options.target)) - if (expected_return_type.intInfo(file.options.target).bits <= 64) - try writer.print("return {};", .{value.toUnsignedInt()}) - else - return file.fail(decl.src(), "TODO return ints > 64 bits", .{}) - else - return file.fail(decl.src(), "comptime int {} does not fit in {}", .{ value.toUnsignedInt(), expected_return_type }) - else - return file.fail(decl.src(), "return type mismatch: expected {}, found {}", .{ expected_return_type, ret_value.ty }); -} - -fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { - const writer = file.main.writer(); - const header = file.header.writer(); - if (inst.args.func.cast(Inst.Constant)) |func_inst| { - if (func_inst.val.cast(Value.Payload.Function)) |func_val| { - const target = func_val.func.owner_decl; - const target_ty = target.typed_value.most_recent.typed_value.ty; - const ret_ty = target_ty.fnReturnType().tag(); - if (target_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) { - try writer.print("(void)", .{}); - } - const tname = mem.spanZ(target.name); - if (file.called.get(tname) == null) { - try file.called.put(tname, void{}); - try renderFunctionSignature(file, header, target); - try header.writeAll(";\n"); - } - try writer.print("{}();", .{tname}); - } else { - return file.fail(decl.src(), "TODO non-function call target?", .{}); - } - if (inst.args.args.len != 0) { - return file.fail(decl.src(), "TODO function arguments", .{}); - } - } else { - return file.fail(decl.src(), "TODO non-constant call inst?", .{}); - } -} - -fn genAsm(file: *C, inst: *Inst.Assembly, decl: *Decl) !void { - const as = inst.args; - const writer = file.main.writer(); - for (as.inputs) |i, index| { - if (i[0] == '{' and i[i.len - 1] == '}') { - const reg = i[1 .. i.len - 1]; - const arg = as.args[index]; - if (arg.cast(Inst.Constant)) |c| { - if (c.val.tag() == .int_u64) { - try writer.writeAll("register "); - try renderType(file, writer, arg.ty, decl.src()); - try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() }); - } else { - return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()}); - } - } else { - return file.fail(decl.src(), "TODO non-constant inline asm args", .{}); - } - } else { - return file.fail(decl.src(), "TODO non-explicit inline asm regs", .{}); - } - } - try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); - if (as.output) |o| { - return file.fail(decl.src(), "TODO inline asm output", .{}); - } - if (as.inputs.len > 0) { - if (as.output == null) { - try writer.writeAll(" :"); - } - try writer.writeAll(": "); - for (as.inputs) |i, index| { - if (i[0] == '{' and i[i.len - 1] == '}') { - const reg = i[1 .. i.len - 1]; - const arg = as.args[index]; - if (index > 0) { - try writer.writeAll(", "); - } - if (arg.cast(Inst.Constant)) |c| { - try writer.print("\"\"({}_constant)", .{reg}); - } else { - // This is blocked by the earlier test - unreachable; - } - } else { - // This is blocked by the earlier test - unreachable; - } - } - } - try writer.writeAll(");"); -} diff --git a/src-self-hosted/codegen/c.zig b/src-self-hosted/codegen/c.zig new file mode 100644 index 0000000000..ebc4ff7e1a --- /dev/null +++ b/src-self-hosted/codegen/c.zig @@ -0,0 +1,206 @@ +const std = @import("std"); + +const link = @import("../link.zig"); +const Module = @import("../Module.zig"); + +const Inst = @import("../ir.zig").Inst; +const Value = @import("../value.zig").Value; +const Type = @import("../type.zig").Type; + +const C = link.File.C; +const Decl = Module.Decl; +const mem = std.mem; + +/// Maps a name from Zig source to C. This will always give the same output for +/// any given input. +fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { + return allocator.dupe(u8, name); +} + +fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) !void { + if (T.tag() == .usize) { + file.need_stddef = true; + try writer.writeAll("size_t"); + } else { + switch (T.zigTypeTag()) { + .NoReturn => { + file.need_noreturn = true; + try writer.writeAll("noreturn void"); + }, + .Void => try writer.writeAll("void"), + .Int => { + if (T.tag() == .u8) { + file.need_stdint = true; + try writer.writeAll("uint8_t"); + } else { + return file.fail(src, "TODO implement int types", .{}); + } + }, + else => |e| return file.fail(src, "TODO implement type {}", .{e}), + } + } +} + +fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *Decl) !void { + const tv = decl.typed_value.most_recent.typed_value; + try renderType(file, writer, tv.ty.fnReturnType(), decl.src()); + const name = try map(file.allocator, mem.spanZ(decl.name)); + defer file.allocator.free(name); + try writer.print(" {}(", .{name}); + if (tv.ty.fnParamLen() == 0) + try writer.writeAll("void)") + else + return file.fail(decl.src(), "TODO implement parameters", .{}); +} + +pub fn generate(file: *C, decl: *Decl) !void { + switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { + .Fn => try genFn(file, decl), + .Array => try genArray(file, decl), + else => |e| return file.fail(decl.src(), "TODO {}", .{e}), + } +} + +fn genArray(file: *C, decl: *Decl) !void { + const tv = decl.typed_value.most_recent.typed_value; + // TODO: prevent inline asm constants from being emitted + const name = try map(file.allocator, mem.span(decl.name)); + defer file.allocator.free(name); + if (tv.val.cast(Value.Payload.Bytes)) |payload| + if (tv.ty.arraySentinel()) |sentinel| + if (sentinel.toUnsignedInt() == 0) + try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data }) + else + return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{}) + else + return file.fail(decl.src(), "TODO byte arrays without sentinels", .{}) + else + return file.fail(decl.src(), "TODO non-byte arrays", .{}); +} + +fn genFn(file: *C, decl: *Decl) !void { + const writer = file.main.writer(); + const tv = decl.typed_value.most_recent.typed_value; + + try renderFunctionSignature(file, writer, decl); + + try writer.writeAll(" {"); + + const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func; + const instructions = func.analysis.success.instructions; + if (instructions.len > 0) { + for (instructions) |inst| { + try writer.writeAll("\n\t"); + switch (inst.tag) { + .assembly => try genAsm(file, inst.cast(Inst.Assembly).?, decl), + .call => try genCall(file, inst.cast(Inst.Call).?, decl), + .ret => try genRet(file, inst.cast(Inst.Ret).?, decl, tv.ty.fnReturnType()), + .retvoid => try file.main.writer().print("return;", .{}), + else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}), + } + } + try writer.writeAll("\n"); + } + + try writer.writeAll("}\n\n"); +} + +fn genRet(file: *C, inst: *Inst.Ret, decl: *Decl, expected_return_type: Type) !void { + const writer = file.main.writer(); + const ret_value = inst.args.operand; + const value = ret_value.value().?; + if (expected_return_type.eql(ret_value.ty)) + return file.fail(decl.src(), "TODO return {}", .{expected_return_type}) + else if (expected_return_type.isInt() and ret_value.ty.tag() == .comptime_int) + if (value.intFitsInType(expected_return_type, file.options.target)) + if (expected_return_type.intInfo(file.options.target).bits <= 64) + try writer.print("return {};", .{value.toUnsignedInt()}) + else + return file.fail(decl.src(), "TODO return ints > 64 bits", .{}) + else + return file.fail(decl.src(), "comptime int {} does not fit in {}", .{ value.toUnsignedInt(), expected_return_type }) + else + return file.fail(decl.src(), "return type mismatch: expected {}, found {}", .{ expected_return_type, ret_value.ty }); +} + +fn genCall(file: *C, inst: *Inst.Call, decl: *Decl) !void { + const writer = file.main.writer(); + const header = file.header.writer(); + if (inst.args.func.cast(Inst.Constant)) |func_inst| { + if (func_inst.val.cast(Value.Payload.Function)) |func_val| { + const target = func_val.func.owner_decl; + const target_ty = target.typed_value.most_recent.typed_value.ty; + const ret_ty = target_ty.fnReturnType().tag(); + if (target_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) { + try writer.print("(void)", .{}); + } + const tname = mem.spanZ(target.name); + if (file.called.get(tname) == null) { + try file.called.put(tname, void{}); + try renderFunctionSignature(file, header, target); + try header.writeAll(";\n"); + } + try writer.print("{}();", .{tname}); + } else { + return file.fail(decl.src(), "TODO non-function call target?", .{}); + } + if (inst.args.args.len != 0) { + return file.fail(decl.src(), "TODO function arguments", .{}); + } + } else { + return file.fail(decl.src(), "TODO non-constant call inst?", .{}); + } +} + +fn genAsm(file: *C, inst: *Inst.Assembly, decl: *Decl) !void { + const as = inst.args; + const writer = file.main.writer(); + for (as.inputs) |i, index| { + if (i[0] == '{' and i[i.len - 1] == '}') { + const reg = i[1 .. i.len - 1]; + const arg = as.args[index]; + if (arg.cast(Inst.Constant)) |c| { + if (c.val.tag() == .int_u64) { + try writer.writeAll("register "); + try renderType(file, writer, arg.ty, decl.src()); + try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() }); + } else { + return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()}); + } + } else { + return file.fail(decl.src(), "TODO non-constant inline asm args", .{}); + } + } else { + return file.fail(decl.src(), "TODO non-explicit inline asm regs", .{}); + } + } + try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); + if (as.output) |o| { + return file.fail(decl.src(), "TODO inline asm output", .{}); + } + if (as.inputs.len > 0) { + if (as.output == null) { + try writer.writeAll(" :"); + } + try writer.writeAll(": "); + for (as.inputs) |i, index| { + if (i[0] == '{' and i[i.len - 1] == '}') { + const reg = i[1 .. i.len - 1]; + const arg = as.args[index]; + if (index > 0) { + try writer.writeAll(", "); + } + if (arg.cast(Inst.Constant)) |c| { + try writer.print("\"\"({}_constant)", .{reg}); + } else { + // This is blocked by the earlier test + unreachable; + } + } else { + // This is blocked by the earlier test + unreachable; + } + } + } + try writer.writeAll(");"); +} diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 64328d4df5..776f6de31c 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -7,7 +7,7 @@ const Module = @import("Module.zig"); const fs = std.fs; const elf = std.elf; const codegen = @import("codegen.zig"); -const cgen = @import("cgen.zig"); +const c_codegen = @import("codegen/c.zig"); const default_entry_addr = 0x8000000; @@ -259,7 +259,7 @@ pub const File = struct { } pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void { - cgen.generate(self, decl) catch |err| { + c_codegen.generate(self, decl) catch |err| { if (err == error.CGenFailure) { try module.failed_decls.put(module.gpa, decl, self.error_msg); } diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index e642489b01..e817029b83 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -62,6 +62,7 @@ pub fn addCases(ctx: *TestContext) !void { \\ register size_t rax_constant __asm__("rax") = 231; \\ register size_t rdi_constant __asm__("rdi") = 0; \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); + \\ return; \\} \\ ); -- cgit v1.2.3 From c94652a2fd210fe4f007dbdd47c9e55b38e482fb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 13 Jul 2020 00:31:55 -0700 Subject: stage2: add new test case --- test/stage2/compare_output.zig | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig index 1f2853707a..27c9f48d72 100644 --- a/test/stage2/compare_output.zig +++ b/test/stage2/compare_output.zig @@ -120,7 +120,7 @@ pub fn addCases(ctx: *TestContext) !void { } { - var case = ctx.exe("adding numbers", linux_x64); + var case = ctx.exe("adding numbers at comptime", linux_x64); case.addCompareOutput( \\export fn _start() noreturn { \\ asm volatile ("syscall" @@ -143,4 +143,31 @@ pub fn addCases(ctx: *TestContext) !void { "Hello, World!\n", ); } + + { + var case = ctx.exe("adding numbers at runtime", linux_x64); + case.addCompareOutput( + \\export fn _start() noreturn { + \\ add(3, 4); + \\ + \\ exit(); + \\} + \\ + \\fn add(a: u32, b: u32) void { + \\ if (a + b != 7) unreachable; + \\} + \\ + \\fn exit() noreturn { + \\ asm volatile ("syscall" + \\ : + \\ : [number] "{rax}" (231), + \\ [arg1] "{rdi}" (0) + \\ : "rcx", "r11", "memory" + \\ ); + \\ unreachable; + \\} + , + "", + ); + } } -- cgit v1.2.3