From 22e7ca5613c32b14043dee4531fe8b180bfc407a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 21 Apr 2020 16:06:15 -0400 Subject: [PATCH] ir: analysis of fn instruction --- lib/std/mem.zig | 2 +- src-self-hosted/ir.zig | 108 +++++++++++++++++++++++++++++++------- src-self-hosted/value.zig | 25 +++++++++ 3 files changed, 115 insertions(+), 20 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 584ba116d..1900a36df 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -118,7 +118,7 @@ pub const Allocator = struct { ptr[n] = sentinel; return ptr[0..n :sentinel]; } else { - return alignedAlloc(Elem, optional_alignment, n); + return self.alignedAlloc(Elem, optional_alignment, n); } } diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 3e08f1f5b..12f902a2f 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -68,12 +68,18 @@ pub const Module = struct { exports: []Export, errors: []ErrorMsg, arena: std.heap.ArenaAllocator, + fns: []Fn, pub const Export = struct { name: []const u8, typed_value: TypedValue, }; + pub const Fn = struct { + analysis_status: enum { in_progress, failure, success }, + body: []*Inst, + }; + pub fn deinit(self: *Module, allocator: *Allocator) void { allocator.free(self.exports); allocator.free(self.errors); @@ -97,12 +103,14 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { .arena = std.heap.ArenaAllocator.init(allocator), .old_module = &old_module, .errors = std.ArrayList(ErrorMsg).init(allocator), - .inst_table = std.AutoHashMap(*text.Inst, Analyze.NewInst).init(allocator), + .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator), .exports = std.ArrayList(Module.Export).init(allocator), + .fns = std.ArrayList(Module.Fn).init(allocator), }; defer ctx.errors.deinit(); - defer ctx.inst_table.deinit(); + defer ctx.decl_table.deinit(); defer ctx.exports.deinit(); + defer ctx.fns.deinit(); ctx.analyzeRoot() catch |err| switch (err) { error.AnalysisFail => { @@ -113,6 +121,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { return Module{ .exports = ctx.exports.toOwnedSlice(), .errors = ctx.errors.toOwnedSlice(), + .fns = ctx.fns.toOwnedSlice(), .arena = ctx.arena, }; } @@ -122,42 +131,57 @@ const Analyze = struct { arena: std.heap.ArenaAllocator, old_module: *const text.Module, errors: std.ArrayList(ErrorMsg), - inst_table: std.AutoHashMap(*text.Inst, NewInst), + decl_table: std.AutoHashMap(*text.Inst, NewDecl), exports: std.ArrayList(Module.Export), + fns: std.ArrayList(Module.Fn), - const NewInst = struct { + const NewDecl = struct { /// null means a semantic analysis error happened ptr: ?*Inst, }; + const NewInst = struct { + ptr: *Inst, + }; + + const Fn = struct { + body: std.ArrayList(*Inst), + inst_table: std.AutoHashMap(*text.Inst, NewInst), + /// Index into Module fns array + fn_index: usize, + }; + const InnerError = error{ OutOfMemory, AnalysisFail }; fn analyzeRoot(self: *Analyze) !void { for (self.old_module.decls) |decl| { if (decl.cast(text.Inst.Export)) |export_inst| { - try analyzeExport(self, export_inst); + try analyzeExport(self, null, export_inst); } } } - fn resolveInst(self: *Analyze, old_inst: *text.Inst) InnerError!*Inst { - if (self.inst_table.get(old_inst)) |kv| { + fn resolveInst(self: *Analyze, opt_func: ?*Fn, old_inst: *text.Inst) InnerError!*Inst { + if (opt_func) |func| { + const kv = func.inst_table.get(old_inst) orelse return error.AnalysisFail; + return kv.value.ptr; + } else if (self.decl_table.get(old_inst)) |kv| { return kv.value.ptr orelse return error.AnalysisFail; } else { - const new_inst = self.analyzeDecl(old_inst) catch |err| switch (err) { + const new_inst = self.analyzeInst(old_inst, null) catch |err| switch (err) { error.AnalysisFail => { - try self.inst_table.putNoClobber(old_inst, .{ .ptr = null }); + try self.decl_table.putNoClobber(old_inst, .{ .ptr = null }); return error.AnalysisFail; }, else => |e| return e, }; - try self.inst_table.putNoClobber(old_inst, .{ .ptr = new_inst }); + try self.decl_table.putNoClobber(old_inst, .{ .ptr = new_inst }); return new_inst; } } - fn resolveInstConst(self: *Analyze, old_inst: *text.Inst) InnerError!TypedValue { - const new_inst = try self.resolveInst(old_inst); + fn resolveInstConst(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) InnerError!TypedValue { + const new_inst = try self.resolveInst(func, old_inst); const val = try self.resolveConstValue(new_inst); return TypedValue{ .ty = new_inst.ty, @@ -169,17 +193,25 @@ const Analyze = struct { return base.value() orelse return self.fail(base.src, "unable to resolve comptime value", .{}); } - fn resolveConstString(self: *Analyze, old_inst: *text.Inst) ![]u8 { - const new_inst = try self.resolveInst(old_inst); + fn resolveConstString(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) ![]u8 { + const new_inst = try self.resolveInst(func, old_inst); const wanted_type = Type.initTag(.const_slice_u8); const coerced_inst = try self.coerce(wanted_type, new_inst); const val = try self.resolveConstValue(coerced_inst); return val.toAllocatedBytes(&self.arena.allocator); } - fn analyzeExport(self: *Analyze, export_inst: *text.Inst.Export) !void { - const symbol_name = try self.resolveConstString(export_inst.positionals.symbol_name); - const typed_value = try self.resolveInstConst(export_inst.positionals.value); + fn resolveType(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) !Type { + const new_inst = try self.resolveInst(func, old_inst); + const wanted_type = Type.initTag(.@"type"); + const coerced_inst = try self.coerce(wanted_type, new_inst); + const val = try self.resolveConstValue(coerced_inst); + return val.toType(); + } + + fn analyzeExport(self: *Analyze, func: ?*Fn, export_inst: *text.Inst.Export) !void { + const symbol_name = try self.resolveConstString(func, export_inst.positionals.symbol_name); + const typed_value = try self.resolveInstConst(func, export_inst.positionals.value); switch (typed_value.ty.zigTypeTag()) { .Fn => {}, @@ -224,7 +256,7 @@ const Analyze = struct { }); } - fn analyzeDecl(self: *Analyze, old_inst: *text.Inst) !*Inst { + fn analyzeInst(self: *Analyze, old_inst: *text.Inst, opt_func: ?*Fn) InnerError!*Inst { switch (old_inst.tag) { .str => { // We can use this reference because Inst.Const's Value is arena-allocated. @@ -239,7 +271,45 @@ const Analyze = struct { .as => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), .@"asm" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), .@"unreachable" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), - .@"fn" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), + .@"fn" => { + const fn_inst = old_inst.cast(text.Inst.Fn).?; + const fn_type = try self.resolveType(opt_func, fn_inst.positionals.fn_type); + + var new_func: Fn = .{ + .body = std.ArrayList(*Inst).init(self.allocator), + .inst_table = std.AutoHashMap(*text.Inst, NewInst).init(self.allocator), + .fn_index = self.fns.items.len, + }; + defer new_func.body.deinit(); + defer new_func.inst_table.deinit(); + // Don't hang on to a reference to this when analyzing body instructions, since the memory + // could become invalid. + (try self.fns.addOne()).* = .{ + .analysis_status = .in_progress, + .body = undefined, + }; + + for (fn_inst.positionals.body.instructions) |src_inst| { + const new_inst = self.analyzeInst(src_inst, &new_func) catch |err| { + self.fns.items[new_func.fn_index].analysis_status = .failure; + return err; + }; + try new_func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst }); + } + + self.fns.items[new_func.fn_index] = .{ + .analysis_status = .success, + .body = new_func.body.toOwnedSlice(), + }; + + const fn_payload = try self.arena.allocator.create(Value.Payload.Function); + fn_payload.* = .{ .index = new_func.fn_index }; + + return self.constInst(old_inst.src, .{ + .ty = fn_type, + .val = Value.initPayload(&fn_payload.base), + }); + }, .@"export" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), .primitive => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), .fntype => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 12fabfd81..14edfeea9 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -100,6 +100,29 @@ pub const Value = extern union { unreachable; } + /// Asserts that the value is representable as a type. + pub fn toType(self: Value) Type { + return switch (self.tag()) { + .ty => self.cast(Payload.Ty).?.ty, + + .void_type => Type.initTag(.@"void"), + .noreturn_type => Type.initTag(.@"noreturn"), + .bool_type => Type.initTag(.@"bool"), + .usize_type => Type.initTag(.@"usize"), + + .void_value, + .noreturn_value, + .bool_true, + .bool_false, + .int_u64, + .int_i64, + .function, + .ref, + .bytes, + => unreachable, + }; + } + /// This type is not copyable since it may contain pointers to its inner data. pub const Payload = struct { tag: Tag, @@ -116,6 +139,8 @@ pub const Value = extern union { pub const Function = struct { base: Payload = Payload{ .tag = .function }, + /// Index into the `fns` array of the `ir.Module` + index: usize, }; pub const ArraySentinel0_u8_Type = struct {