| ... | @@ -68,12 +68,18 @@ pub const Module = struct { | ... | @@ -68,12 +68,18 @@ pub const Module = struct { |
| 68 | exports: []Export, | 68 | exports: []Export, |
| 69 | errors: []ErrorMsg, | 69 | errors: []ErrorMsg, |
| 70 | arena: std.heap.ArenaAllocator, | 70 | arena: std.heap.ArenaAllocator, |
| | 71 | fns: []Fn, |
| 71 | | 72 | |
| 72 | pub const Export = struct { | 73 | pub const Export = struct { |
| 73 | name: []const u8, | 74 | name: []const u8, |
| 74 | typed_value: TypedValue, | 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 | pub fn deinit(self: *Module, allocator: *Allocator) void { | 83 | pub fn deinit(self: *Module, allocator: *Allocator) void { |
| 78 | allocator.free(self.exports); | 84 | allocator.free(self.exports); |
| 79 | allocator.free(self.errors); | 85 | allocator.free(self.errors); |
| ... | @@ -97,12 +103,14 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { | ... | @@ -97,12 +103,14 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { |
| 97 | .arena = std.heap.ArenaAllocator.init(allocator), | 103 | .arena = std.heap.ArenaAllocator.init(allocator), |
| 98 | .old_module = &old_module, | 104 | .old_module = &old_module, |
| 99 | .errors = std.ArrayList(ErrorMsg).init(allocator), | 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 | .exports = std.ArrayList(Module.Export).init(allocator), | 107 | .exports = std.ArrayList(Module.Export).init(allocator), |
| | 108 | .fns = std.ArrayList(Module.Fn).init(allocator), |
| 102 | }; | 109 | }; |
| 103 | defer ctx.errors.deinit(); | 110 | defer ctx.errors.deinit(); |
| 104 | defer ctx.inst_table.deinit(); | 111 | defer ctx.decl_table.deinit(); |
| 105 | defer ctx.exports.deinit(); | 112 | defer ctx.exports.deinit(); |
| | 113 | defer ctx.fns.deinit(); |
| 106 | | 114 | |
| 107 | ctx.analyzeRoot() catch |err| switch (err) { | 115 | ctx.analyzeRoot() catch |err| switch (err) { |
| 108 | error.AnalysisFail => { | 116 | error.AnalysisFail => { |
| ... | @@ -113,6 +121,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { | ... | @@ -113,6 +121,7 @@ pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module { |
| 113 | return Module{ | 121 | return Module{ |
| 114 | .exports = ctx.exports.toOwnedSlice(), | 122 | .exports = ctx.exports.toOwnedSlice(), |
| 115 | .errors = ctx.errors.toOwnedSlice(), | 123 | .errors = ctx.errors.toOwnedSlice(), |
| | 124 | .fns = ctx.fns.toOwnedSlice(), |
| 116 | .arena = ctx.arena, | 125 | .arena = ctx.arena, |
| 117 | }; | 126 | }; |
| 118 | } | 127 | } |
| ... | @@ -122,42 +131,57 @@ const Analyze = struct { | ... | @@ -122,42 +131,57 @@ const Analyze = struct { |
| 122 | arena: std.heap.ArenaAllocator, | 131 | arena: std.heap.ArenaAllocator, |
| 123 | old_module: *const text.Module, | 132 | old_module: *const text.Module, |
| 124 | errors: std.ArrayList(ErrorMsg), | 133 | errors: std.ArrayList(ErrorMsg), |
| 125 | inst_table: std.AutoHashMap(*text.Inst, NewInst), | 134 | decl_table: std.AutoHashMap(*text.Inst, NewDecl), |
| 126 | exports: std.ArrayList(Module.Export), | 135 | exports: std.ArrayList(Module.Export), |
| | 136 | fns: std.ArrayList(Module.Fn), |
| 127 | | 137 | |
| 128 | const NewInst = struct { | 138 | const NewDecl = struct { |
| 129 | /// null means a semantic analysis error happened | 139 | /// null means a semantic analysis error happened |
| 130 | ptr: ?*Inst, | 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 | const InnerError = error{ OutOfMemory, AnalysisFail }; | 154 | const InnerError = error{ OutOfMemory, AnalysisFail }; |
| 134 | | 155 | |
| 135 | fn analyzeRoot(self: *Analyze) !void { | 156 | fn analyzeRoot(self: *Analyze) !void { |
| 136 | for (self.old_module.decls) |decl| { | 157 | for (self.old_module.decls) |decl| { |
| 137 | if (decl.cast(text.Inst.Export)) |export_inst| { | 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 { | 164 | fn resolveInst(self: *Analyze, opt_func: ?*Fn, old_inst: *text.Inst) InnerError!*Inst { |
| 144 | if (self.inst_table.get(old_inst)) |kv| { | 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 | return kv.value.ptr orelse return error.AnalysisFail; | 169 | return kv.value.ptr orelse return error.AnalysisFail; |
| 146 | } else { | 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 | error.AnalysisFail => { | 172 | error.AnalysisFail => { |
| 149 | try self.inst_table.putNoClobber(old_inst, .{ .ptr = null }); | 173 | try self.decl_table.putNoClobber(old_inst, .{ .ptr = null }); |
| 150 | return error.AnalysisFail; | 174 | return error.AnalysisFail; |
| 151 | }, | 175 | }, |
| 152 | else => |e| return e, | 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 | return new_inst; | 179 | return new_inst; |
| 156 | } | 180 | } |
| 157 | } | 181 | } |
| 158 | | 182 | |
| 159 | fn resolveInstConst(self: *Analyze, old_inst: *text.Inst) InnerError!TypedValue { | 183 | fn resolveInstConst(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) InnerError!TypedValue { |
| 160 | const new_inst = try self.resolveInst(old_inst); | 184 | const new_inst = try self.resolveInst(func, old_inst); |
| 161 | const val = try self.resolveConstValue(new_inst); | 185 | const val = try self.resolveConstValue(new_inst); |
| 162 | return TypedValue{ | 186 | return TypedValue{ |
| 163 | .ty = new_inst.ty, | 187 | .ty = new_inst.ty, |
| ... | @@ -169,17 +193,25 @@ const Analyze = struct { | ... | @@ -169,17 +193,25 @@ const Analyze = struct { |
| 169 | return base.value() orelse return self.fail(base.src, "unable to resolve comptime value", .{}); | 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 { | 196 | fn resolveConstString(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) ![]u8 { |
| 173 | const new_inst = try self.resolveInst(old_inst); | 197 | const new_inst = try self.resolveInst(func, old_inst); |
| 174 | const wanted_type = Type.initTag(.const_slice_u8); | 198 | const wanted_type = Type.initTag(.const_slice_u8); |
| 175 | const coerced_inst = try self.coerce(wanted_type, new_inst); | 199 | const coerced_inst = try self.coerce(wanted_type, new_inst); |
| 176 | const val = try self.resolveConstValue(coerced_inst); | 200 | const val = try self.resolveConstValue(coerced_inst); |
| 177 | return val.toAllocatedBytes(&self.arena.allocator); | 201 | return val.toAllocatedBytes(&self.arena.allocator); |
| 178 | } | 202 | } |
| 179 | | 203 | |
| 180 | fn analyzeExport(self: *Analyze, export_inst: *text.Inst.Export) !void { | 204 | fn resolveType(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) !Type { |
| 181 | const symbol_name = try self.resolveConstString(export_inst.positionals.symbol_name); | 205 | const new_inst = try self.resolveInst(func, old_inst); |
| 182 | const typed_value = try self.resolveInstConst(export_inst.positionals.value); | 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 | switch (typed_value.ty.zigTypeTag()) { | 216 | switch (typed_value.ty.zigTypeTag()) { |
| 185 | .Fn => {}, | 217 | .Fn => {}, |
| ... | @@ -224,7 +256,7 @@ const Analyze = struct { | ... | @@ -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 | switch (old_inst.tag) { | 260 | switch (old_inst.tag) { |
| 229 | .str => { | 261 | .str => { |
| 230 | // We can use this reference because Inst.Const's Value is arena-allocated. | 262 | // We can use this reference because Inst.Const's Value is arena-allocated. |
| ... | @@ -239,7 +271,45 @@ const Analyze = struct { | ... | @@ -239,7 +271,45 @@ const Analyze = struct { |
| 239 | .as => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), | 271 | .as => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 240 | .@"asm" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), | 272 | .@"asm" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 241 | .@"unreachable" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), | 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 | .@"export" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), | 313 | .@"export" => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 244 | .primitive => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), | 314 | .primitive => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |
| 245 | .fntype => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), | 315 | .fntype => return self.fail(old_inst.src, "TODO implement analyzing {}", .{@tagName(old_inst.tag)}), |