From 751903ba8fba467411942317c8da0e6bc22a0ff6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 29 Apr 2020 19:11:40 -0400 Subject: [PATCH] zir: add breakpoint() instruction and object file ability --- src-self-hosted/codegen.zig | 1 + src-self-hosted/ir.zig | 118 +++++++++++++++++++++++------------- src-self-hosted/ir/text.zig | 42 +++++++------ src-self-hosted/link.zig | 21 +++++-- 4 files changed, 117 insertions(+), 65 deletions(-) diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index 2e8bcd9624..675b8faad2 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -77,6 +77,7 @@ const Function = struct { fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue { switch (inst.tag) { + .breakpoint => return self.genBreakpoint(inst.src), .unreach => return MCValue{ .unreach = {} }, .constant => unreachable, // excluded from function bodies .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?), diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index d34726d23e..e78ccaf4f0 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -21,16 +21,17 @@ pub const Inst = struct { src: usize, pub const Tag = enum { - unreach, - ret, - constant, assembly, - ptrtoint, bitcast, + breakpoint, cmp, condbr, - isnull, + constant, isnonnull, + isnull, + ptrtoint, + ret, + unreach, }; pub fn cast(base: *Inst, comptime T: type) ?*T { @@ -53,25 +54,6 @@ pub const Inst = struct { return inst.val; } - pub const Unreach = struct { - pub const base_tag = Tag.unreach; - base: Inst, - args: void, - }; - - pub const Ret = struct { - pub const base_tag = Tag.ret; - base: Inst, - args: void, - }; - - pub const Constant = struct { - pub const base_tag = Tag.constant; - base: Inst, - - val: Value, - }; - pub const Assembly = struct { pub const base_tag = Tag.assembly; base: Inst, @@ -86,15 +68,6 @@ pub const Inst = struct { }, }; - pub const PtrToInt = struct { - pub const base_tag = Tag.ptrtoint; - - base: Inst, - args: struct { - ptr: *Inst, - }, - }; - pub const BitCast = struct { pub const base_tag = Tag.bitcast; @@ -104,6 +77,12 @@ pub const Inst = struct { }, }; + pub const Breakpoint = struct { + pub const base_tag = Tag.breakpoint; + base: Inst, + args: void, + }; + pub const Cmp = struct { pub const base_tag = Tag.cmp; @@ -126,13 +105,11 @@ pub const Inst = struct { }, }; - pub const IsNull = struct { - pub const base_tag = Tag.isnull; - + pub const Constant = struct { + pub const base_tag = Tag.constant; base: Inst, - args: struct { - operand: *Inst, - }, + + val: Value, }; pub const IsNonNull = struct { @@ -143,6 +120,36 @@ pub const Inst = struct { operand: *Inst, }, }; + + pub const IsNull = struct { + pub const base_tag = Tag.isnull; + + base: Inst, + args: struct { + operand: *Inst, + }, + }; + + pub const PtrToInt = struct { + pub const base_tag = Tag.ptrtoint; + + base: Inst, + args: struct { + ptr: *Inst, + }, + }; + + pub const Ret = struct { + pub const base_tag = Tag.ret; + base: Inst, + args: void, + }; + + pub const Unreach = struct { + pub const base_tag = Tag.unreach; + base: Inst, + args: void, + }; }; pub const TypedValue = struct { @@ -159,6 +166,7 @@ pub const Module = struct { link_mode: std.builtin.LinkMode, output_mode: std.builtin.OutputMode, object_format: std.Target.ObjectFormat, + optimize_mode: std.builtin.Mode, pub const Export = struct { name: []const u8, @@ -198,6 +206,7 @@ pub const AnalyzeOptions = struct { output_mode: std.builtin.OutputMode, link_mode: std.builtin.LinkMode, object_format: ?std.Target.ObjectFormat = null, + optimize_mode: std.builtin.Mode, }; pub fn analyze(allocator: *Allocator, old_module: text.Module, options: AnalyzeOptions) !Module { @@ -210,6 +219,9 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, options: AnalyzeO .exports = std.ArrayList(Module.Export).init(allocator), .fns = std.ArrayList(Module.Fn).init(allocator), .target = options.target, + .optimize_mode = options.optimize_mode, + .link_mode = options.link_mode, + .output_mode = options.output_mode, }; defer ctx.errors.deinit(); defer ctx.decl_table.deinit(); @@ -229,9 +241,10 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module, options: AnalyzeO .fns = ctx.fns.toOwnedSlice(), .arena = ctx.arena, .target = ctx.target, - .link_mode = options.link_mode, - .output_mode = options.output_mode, + .link_mode = ctx.link_mode, + .output_mode = ctx.output_mode, .object_format = options.object_format orelse ctx.target.getObjectFormat(), + .optimize_mode = ctx.optimize_mode, }; } @@ -244,6 +257,9 @@ const Analyze = struct { exports: std.ArrayList(Module.Export), fns: std.ArrayList(Module.Fn), target: Target, + link_mode: std.builtin.LinkMode, + optimize_mode: std.builtin.Mode, + output_mode: std.builtin.OutputMode, const NewDecl = struct { /// null means a semantic analysis error happened @@ -495,6 +511,7 @@ const Analyze = struct { fn analyzeInst(self: *Analyze, block: ?*Block, old_inst: *text.Inst) InnerError!*Inst { switch (old_inst.tag) { + .breakpoint => return self.analyzeInstBreakpoint(block, old_inst.cast(text.Inst.Breakpoint).?), .str => { // We can use this reference because Inst.Const's Value is arena-allocated. // The value would get copied to a MemoryCell before the `text.Inst.Str` lifetime ends. @@ -530,6 +547,11 @@ const Analyze = struct { } } + fn analyzeInstBreakpoint(self: *Analyze, block: ?*Block, inst: *text.Inst.Breakpoint) InnerError!*Inst { + const b = try self.requireRuntimeBlock(block, inst.base.src); + return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, Inst.Args(Inst.Breakpoint){}); + } + fn analyzeInstFn(self: *Analyze, block: ?*Block, fn_inst: *text.Inst.Fn) InnerError!*Inst { const fn_type = try self.resolveType(block, fn_inst.positionals.fn_type); @@ -909,8 +931,21 @@ const Analyze = struct { }); } + fn wantSafety(self: *Analyze, block: ?*Block) bool { + return switch (self.optimize_mode) { + .Debug => true, + .ReleaseSafe => true, + .ReleaseFast => false, + .ReleaseSmall => false, + }; + } + fn analyzeInstUnreachable(self: *Analyze, block: ?*Block, unreach: *text.Inst.Unreachable) InnerError!*Inst { const b = try self.requireRuntimeBlock(block, unreach.base.src); + if (self.wantSafety(block)) { + // TODO Once we have a panic function to call, call it here instead of this. + _ = try self.addNewInstArgs(b, unreach.base.src, Type.initTag(.void), Inst.Breakpoint, {}); + } return self.addNewInstArgs(b, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {}); } @@ -1258,6 +1293,7 @@ pub fn main() anyerror!void { .target = native_info.target, .output_mode = .Obj, .link_mode = .Static, + .optimize_mode = .Debug, }); defer analyzed_module.deinit(allocator); diff --git a/src-self-hosted/ir/text.zig b/src-self-hosted/ir/text.zig index 7d444d9310..536d50864d 100644 --- a/src-self-hosted/ir/text.zig +++ b/src-self-hosted/ir/text.zig @@ -18,6 +18,7 @@ pub const Inst = struct { /// These names are used directly as the instruction names in the text format. pub const Tag = enum { + breakpoint, str, int, ptrtoint, @@ -43,6 +44,7 @@ pub const Inst = struct { pub fn TagToType(tag: Tag) type { return switch (tag) { + .breakpoint => Breakpoint, .str => Str, .int => Int, .ptrtoint => PtrToInt, @@ -74,6 +76,14 @@ pub const Inst = struct { return @fieldParentPtr(T, "base", base); } + pub const Breakpoint = struct { + pub const base_tag = Tag.breakpoint; + base: Inst, + + positionals: struct {}, + kw_args: struct {}, + }; + pub const Str = struct { pub const base_tag = Tag.str; base: Inst, @@ -419,6 +429,7 @@ pub const Module = struct { ) @TypeOf(stream).Error!void { // TODO I tried implementing this with an inline for loop and hit a compiler bug switch (decl.tag) { + .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table), .str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table), .int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table), .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table), @@ -1030,6 +1041,16 @@ const EmitZIR = struct { } } + fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst { + const new_inst = try self.arena.allocator.create(T); + new_inst.* = .{ + .base = .{ .src = src, .tag = T.base_tag }, + .positionals = .{}, + .kw_args = .{}, + }; + return &new_inst.base; + } + fn emitBody( self: *EmitZIR, body: ir.Module.Body, @@ -1038,24 +1059,9 @@ const EmitZIR = struct { ) Allocator.Error!void { for (body.instructions) |inst| { const new_inst = switch (inst.tag) { - .unreach => blk: { - const unreach_inst = try self.arena.allocator.create(Inst.Unreachable); - unreach_inst.* = .{ - .base = .{ .src = inst.src, .tag = Inst.Unreachable.base_tag }, - .positionals = .{}, - .kw_args = .{}, - }; - break :blk &unreach_inst.base; - }, - .ret => blk: { - const ret_inst = try self.arena.allocator.create(Inst.Return); - ret_inst.* = .{ - .base = .{ .src = inst.src, .tag = Inst.Return.base_tag }, - .positionals = .{}, - .kw_args = .{}, - }; - break :blk &ret_inst.base; - }, + .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), + .unreach => try self.emitTrivial(inst.src, Inst.Unreachable), + .ret => try self.emitTrivial(inst.src, Inst.Return), .constant => unreachable, // excluded from function bodies .assembly => blk: { const old_inst = inst.cast(ir.Inst.Assembly).?; diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig index 4fe31de768..504c374ca7 100644 --- a/src-self-hosted/link.zig +++ b/src-self-hosted/link.zig @@ -431,7 +431,7 @@ const Update = struct { }, } } - if (self.entry_addr == null) { + if (self.entry_addr == null and self.module.output_mode == .Exe) { const msg = try std.fmt.allocPrint(self.errors.allocator, "no entry point found", .{}); errdefer self.errors.allocator.free(msg); try self.errors.append(.{ @@ -480,7 +480,15 @@ const Update = struct { assert(index == 16); - mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf.ET.EXEC), endian); + const elf_type = switch (self.module.output_mode) { + .Exe => elf.ET.EXEC, + .Obj => elf.ET.REL, + .Lib => switch (self.module.link_mode) { + .Static => elf.ET.REL, + .Dynamic => elf.ET.DYN, + }, + }; + mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian); index += 2; const machine = self.module.target.cpu.arch.toElfMachine(); @@ -491,10 +499,11 @@ const Update = struct { mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian); index += 4; + const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?; + switch (ptr_width) { .p32 => { - // e_entry - mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.entry_addr.?), endian); + mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian); index += 4; // e_phoff @@ -507,7 +516,7 @@ const Update = struct { }, .p64 => { // e_entry - mem.writeInt(u64, hdr_buf[index..][0..8], self.entry_addr.?, endian); + mem.writeInt(u64, hdr_buf[index..][0..8], e_entry, endian); index += 8; // e_phoff @@ -748,7 +757,7 @@ const Update = struct { pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result { switch (module.output_mode) { .Exe => {}, - .Obj => return error.TODOImplementWritingObjectFiles, + .Obj => {}, .Lib => return error.TODOImplementWritingLibFiles, } switch (module.object_format) {