| ... | ... | @@ -68,12 +68,18 @@ pub const Module = struct { |
| 68 | 68 | exports: []Export, |
| 69 | 69 | errors: []ErrorMsg, |
| 70 | 70 | arena: std.heap.ArenaAllocator, |
| 71 | fns: []Fn, |
| 71 | 72 | |
| 72 | 73 | pub const Export = struct { |
| 73 | 74 | name: []const u8, |
| 74 | 75 | typed_value: TypedValue, |
| 75 | 76 | }; |
| 76 | 77 | |
| 78 | pub const Fn = struct { |
| 79 | analysis_status: enum { in_progress, failure, success }, |
| 80 | body: []*Inst, |
| 81 | }; |
| 82 | |
| 77 | 83 | pub fn deinit(self: *Module, allocator: *Allocator) void { |
| 78 | 84 | allocator.free(self.exports); |
| 79 | 85 | allocator.free(self.errors); |
| ... | ... | @@ -97,12 +103,14 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { |
| 97 | 103 | .arena = std.heap.ArenaAllocator.init(allocator), |
| 98 | 104 | .old_module = &old_module, |
| 99 | 105 | .errors = std.ArrayList(ErrorMsg).init(allocator), |
| 100 | | .inst_table = std.AutoHashMap(*text.Inst, Analyze.NewInst).init(allocator), |
| 106 | .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator), |
| 101 | 107 | .exports = std.ArrayList(Module.Export).init(allocator), |
| 108 | .fns = std.ArrayList(Module.Fn).init(allocator), |
| 102 | 109 | }; |
| 103 | 110 | defer ctx.errors.deinit(); |
| 104 | | defer ctx.inst_table.deinit(); |
| 111 | defer ctx.decl_table.deinit(); |
| 105 | 112 | defer ctx.exports.deinit(); |
| 113 | defer ctx.fns.deinit(); |
| 106 | 114 | |
| 107 | 115 | ctx.analyzeRoot() catch |err| switch (err) { |
| 108 | 116 | error.AnalysisFail => { |
| ... | ... | @@ -113,6 +121,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { |
| 113 | 121 | return Module{ |
| 114 | 122 | .exports = ctx.exports.toOwnedSlice(), |
| 115 | 123 | .errors = ctx.errors.toOwnedSlice(), |
| 124 | .fns = ctx.fns.toOwnedSlice(), |
| 116 | 125 | .arena = ctx.arena, |
| 117 | 126 | }; |
| 118 | 127 | } |
| ... | ... | @@ -122,42 +131,57 @@ const Analyze = struct { |
| 122 | 131 | arena: std.heap.ArenaAllocator, |
| 123 | 132 | old_module: *const text.Module, |
| 124 | 133 | errors: std.ArrayList(ErrorMsg), |
| 125 | | inst_table: std.AutoHashMap(*text.Inst, NewInst), |
| 134 | decl_table: std.AutoHashMap(*text.Inst, NewDecl), |
| 126 | 135 | exports: std.ArrayList(Module.Export), |
| 136 | fns: std.ArrayList(Module.Fn), |
| 127 | 137 | |
| 128 | | const NewInst = struct { |
| 138 | const NewDecl = struct { |
| 129 | 139 | /// null means a semantic analysis error happened |
| 130 | 140 | ptr: ?*Inst, |
| 131 | 141 | }; |
| 132 | 142 | |
| 143 | const NewInst = struct { |
| 144 | ptr: *Inst, |
| 145 | }; |
| 146 | |
| 147 | const Fn = struct { |
| 148 | body: std.ArrayList(*Inst), |
| 149 | inst_table: std.AutoHashMap(*text.Inst, NewInst), |
| 150 | /// Index into Module fns array |
| 151 | fn_index: usize, |
| 152 | }; |
| 153 | |
| 133 | 154 | const InnerError = error{ OutOfMemory, AnalysisFail }; |
| 134 | 155 | |
| 135 | 156 | fn analyzeRoot(self: *Analyze) !void { |
| 136 | 157 | for (self.old_module.decls) |decl| { |
| 137 | 158 | if (decl.cast(text.Inst.Export)) |export_inst| { |
| 138 | | try analyzeExport(self, export_inst); |
| 159 | try analyzeExport(self, null, export_inst); |
| 139 | 160 | } |
| 140 | 161 | } |
| 141 | 162 | } |
| 142 | 163 | |
| 143 | | fn resolveInst(self: *Analyze, old_inst: *text.Inst) InnerError!*Inst { |
| 144 | | if (self.inst_table.get(old_inst)) |kv| { |
| 164 | fn resolveInst(self: *Analyze, opt_func: ?*Fn, old_inst: *text.Inst) InnerError!*Inst { |
| 165 | if (opt_func) |func| { |
| 166 | const kv = func.inst_table.get(old_inst) orelse return error.AnalysisFail; |
| 167 | return kv.value.ptr; |
| 168 | } else if (self.decl_table.get(old_inst)) |kv| { |
| 145 | 169 | return kv.value.ptr orelse return error.AnalysisFail; |
| 146 | 170 | } else { |
| 147 | | const new_inst = self.analyzeDecl(old_inst) catch |err| switch (err) { |
| 171 | const new_inst = self.analyzeInst(old_inst, null) catch |err| switch (err) { |
| 148 | 172 | error.AnalysisFail => { |
| 149 | | try self.inst_table.putNoClobber(old_inst, .{ .ptr = null }); |
| 173 | try self.decl_table.putNoClobber(old_inst, .{ .ptr = null }); |
| 150 | 174 | return error.AnalysisFail; |
| 151 | 175 | }, |
| 152 | 176 | else => |e| return e, |
| 153 | 177 | }; |
| 154 | | try self.inst_table.putNoClobber(old_inst, .{ .ptr = new_inst }); |
| 178 | try self.decl_table.putNoClobber(old_inst, .{ .ptr = new_inst }); |
| 155 | 179 | return new_inst; |
| 156 | 180 | } |
| 157 | 181 | } |
| 158 | 182 | |
| 159 | | fn resolveInstConst(self: *Analyze, old_inst: *text.Inst) InnerError!TypedValue { |
| 160 | | const new_inst = try self.resolveInst(old_inst); |
| 183 | fn resolveInstConst(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) InnerError!TypedValue { |
| 184 | const new_inst = try self.resolveInst(func, old_inst); |
| 161 | 185 | const val = try self.resolveConstValue(new_inst); |
| 162 | 186 | return TypedValue{ |
| 163 | 187 | .ty = new_inst.ty, |
| ... | ... | @@ -169,17 +193,25 @@ const Analyze = struct { |
| 169 | 193 | return base.value() orelse return self.fail(base.src, "unable to resolve comptime value", .{}); |
| 170 | 194 | } |
| 171 | 195 | |
| 172 | | fn resolveConstString(self: *Analyze, old_inst: *text.Inst) ![]u8 { |
| 173 | | const new_inst = try self.resolveInst(old_inst); |
| 196 | fn resolveConstString(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) ![]u8 { |
| 197 | const new_inst = try self.resolveInst(func, old_inst); |
| 174 | 198 | const wanted_type = Type.initTag(.const_slice_u8); |
| 175 | 199 | const coerced_inst = try self.coerce(wanted_type, new_inst); |
| 176 | 200 | const val = try self.resolveConstValue(coerced_inst); |
| 177 | 201 | return val.toAllocatedBytes(&self.arena.allocator); |
| 178 | 202 | } |
| 179 | 203 | |
| 180 | | fn analyzeExport(self: *Analyze, export_inst: *text.Inst.Export) !void { |
| 181 | | const symbol_name = try self.resolveConstString(export_inst.positionals.symbol_name); |
| 182 | | const typed_value = try self.resolveInstConst(export_inst.positionals.value); |
| 204 | fn resolveType(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) !Type { |
| 205 | const new_inst = try self.resolveInst(func, old_inst); |
| 206 | const wanted_type = Type.initTag(.@"type"); |
| 207 | const coerced_inst = try self.coerce(wanted_type, new_inst); |
| 208 | const val = try self.resolveConstValue(coerced_inst); |
| 209 | return val.toType(); |
| 210 | } |
| 211 | |
| 212 | fn analyzeExport(self: *Analyze, func: ?*Fn, export_inst: *text.Inst.Export) !void { |
| 213 | const symbol_name = try self.resolveConstString(func, export_inst.positionals.symbol_name); |
| 214 | const typed_value = try self.resolveInstConst(func, export_inst.positionals.value); |
| 183 | 215 | |
| 184 | 216 | switch (typed_value.ty.zigTypeTag()) { |
| 185 | 217 | .Fn => {}, |
| ... | ... | @@ -224,7 +256,7 @@ const Analyze = struct { |
| 224 | 256 | }); |
| 225 | 257 | } |
| 226 | 258 | |
| 227 | | fn analyzeDecl(self: *Analyze, old_inst: *text.Inst) !*Inst { |
| 259 | fn analyzeInst(self: *Analyze, old_inst: *text.Inst, opt_func: ?*Fn) InnerError!*Inst { |
| 228 | 260 | switch (old_inst.tag) { |
| 229 | 261 | .str => { |
| 230 | 262 | // We can use this reference because Inst.Const's Value is arena-allocated. |
| ... | ... | @@ -239,7 +271,45 @@ const Analyze = struct { |
| 239 | 271 | .as => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 240 | 272 | .@"asm" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 241 | 273 | .@"unreachable" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 242 | | .@"fn" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 274 | .@"fn" => { |
| 275 | const fn_inst = old_inst.cast(text.Inst.Fn).?; |
| 276 | const fn_type = try self.resolveType(opt_func, fn_inst.positionals.fn_type); |
| 277 | |
| 278 | var new_func: Fn = .{ |
| 279 | .body = std.ArrayList(*Inst).init(self.allocator), |
| 280 | .inst_table = std.AutoHashMap(*text.Inst, NewInst).init(self.allocator), |
| 281 | .fn_index = self.fns.items.len, |
| 282 | }; |
| 283 | defer new_func.body.deinit(); |
| 284 | defer new_func.inst_table.deinit(); |
| 285 | // Don't hang on to a reference to this when analyzing body instructions, since the memory |
| 286 | // could become invalid. |
| 287 | (try self.fns.addOne()).* = .{ |
| 288 | .analysis_status = .in_progress, |
| 289 | .body = undefined, |
| 290 | }; |
| 291 | |
| 292 | for (fn_inst.positionals.body.instructions) |src_inst| { |
| 293 | const new_inst = self.analyzeInst(src_inst, &new_func) catch |err| { |
| 294 | self.fns.items[new_func.fn_index].analysis_status = .failure; |
| 295 | return err; |
| 296 | }; |
| 297 | try new_func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst }); |
| 298 | } |
| 299 | |
| 300 | self.fns.items[new_func.fn_index] = .{ |
| 301 | .analysis_status = .success, |
| 302 | .body = new_func.body.toOwnedSlice(), |
| 303 | }; |
| 304 | |
| 305 | const fn_payload = try self.arena.allocator.create(Value.Payload.Function); |
| 306 | fn_payload.* = .{ .index = new_func.fn_index }; |
| 307 | |
| 308 | return self.constInst(old_inst.src, .{ |
| 309 | .ty = fn_type, |
| 310 | .val = Value.initPayload(&fn_payload.base), |
| 311 | }); |
| 312 | }, |
| 243 | 313 | .@"export" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 244 | 314 | .primitive => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 245 | 315 | .fntype => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |