Commit 22e7ca5613

Andrew Kelley <andrew@ziglang.org>
2020-04-21 22:06:15
ir: analysis of fn instruction
1 parent 9a2ea5c
Changed files (3)
lib
src-self-hosted
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);
         }
     }
 
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)}),
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 {