| author | |
| committer | |
| log | cc1c2bd5684a6195e9535fff38ca54ffb70ebe5a |
| tree | c9a3979ec26f9072b1ce643cab19d4522d77ee43 |
| parent | af4ccf34c1aac9c9914aa9cd5f3c857b7b90615d |
6 files changed, 906 insertions(+), 682 deletions(-)
lib/std/fs.zig+7-5| ... | ... | @@ -1012,25 +1012,27 @@ pub const Dir = struct { |
| 1012 | 1012 | /// On success, caller owns returned buffer. |
| 1013 | 1013 | /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. |
| 1014 | 1014 | pub fn readFileAlloc(self: Dir, allocator: *mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 { |
| 1015 | return self.readFileAllocAligned(allocator, file_path, max_bytes, @alignOf(u8)); | |
| 1015 | return self.readFileAllocOptions(allocator, file_path, max_bytes, @alignOf(u8), null); | |
| 1016 | 1016 | } |
| 1017 | 1017 | |
| 1018 | 1018 | /// On success, caller owns returned buffer. |
| 1019 | 1019 | /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. |
| 1020 | pub fn readFileAllocAligned( | |
| 1020 | /// Allows specifying alignment and a sentinel value. | |
| 1021 | pub fn readFileAllocOptions( | |
| 1021 | 1022 | self: Dir, |
| 1022 | 1023 | allocator: *mem.Allocator, |
| 1023 | 1024 | file_path: []const u8, |
| 1024 | 1025 | max_bytes: usize, |
| 1025 | comptime A: u29, | |
| 1026 | ) ![]align(A) u8 { | |
| 1026 | comptime alignment: u29, | |
| 1027 | comptime optional_sentinel: ?u8, | |
| 1028 | ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) { | |
| 1027 | 1029 | var file = try self.openFile(file_path, .{}); |
| 1028 | 1030 | defer file.close(); |
| 1029 | 1031 | |
| 1030 | 1032 | const size = math.cast(usize, try file.getEndPos()) catch math.maxInt(usize); |
| 1031 | 1033 | if (size > max_bytes) return error.FileTooBig; |
| 1032 | 1034 | |
| 1033 | const buf = try allocator.alignedAlloc(u8, A, size); | |
| 1035 | const buf = try allocator.allocWithOptions(u8, size, alignment, optional_sentinel); | |
| 1034 | 1036 | errdefer allocator.free(buf); |
| 1035 | 1037 | |
| 1036 | 1038 | try file.inStream().readNoEof(buf); |
lib/std/mem.zig+28-3| ... | ... | @@ -105,6 +105,31 @@ pub const Allocator = struct { |
| 105 | 105 | return self.alignedAlloc(T, null, n); |
| 106 | 106 | } |
| 107 | 107 | |
| 108 | pub fn allocWithOptions( | |
| 109 | self: *Allocator, | |
| 110 | comptime Elem: type, | |
| 111 | n: usize, | |
| 112 | /// null means naturally aligned | |
| 113 | comptime optional_alignment: ?u29, | |
| 114 | comptime optional_sentinel: ?Elem, | |
| 115 | ) Error!AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) { | |
| 116 | if (optional_sentinel) |sentinel| { | |
| 117 | const ptr = try self.alignedAlloc(Elem, optional_alignment, n + 1); | |
| 118 | ptr[n] = sentinel; | |
| 119 | return ptr[0..n :sentinel]; | |
| 120 | } else { | |
| 121 | return alignedAlloc(Elem, optional_alignment, n); | |
| 122 | } | |
| 123 | } | |
| 124 | ||
| 125 | fn AllocWithOptionsPayload(comptime Elem: type, comptime alignment: ?u29, comptime sentinel: ?Elem) type { | |
| 126 | if (sentinel) |s| { | |
| 127 | return [:s]align(alignment orelse @alignOf(T)) Elem; | |
| 128 | } else { | |
| 129 | return []align(alignment orelse @alignOf(T)) Elem; | |
| 130 | } | |
| 131 | } | |
| 132 | ||
| 108 | 133 | /// Allocates an array of `n + 1` items of type `T` and sets the first `n` |
| 109 | 134 | /// items to `undefined` and the last item to `sentinel`. Depending on the |
| 110 | 135 | /// Allocator implementation, it may be required to call `free` once the |
| ... | ... | @@ -113,10 +138,10 @@ pub const Allocator = struct { |
| 113 | 138 | /// call `free` when done. |
| 114 | 139 | /// |
| 115 | 140 | /// For allocating a single item, see `create`. |
| 141 | /// | |
| 142 | /// Deprecated; use `allocWithOptions`. | |
| 116 | 143 | pub fn allocSentinel(self: *Allocator, comptime Elem: type, n: usize, comptime sentinel: Elem) Error![:sentinel]Elem { |
| 117 | var ptr = try self.alloc(Elem, n + 1); | |
| 118 | ptr[n] = sentinel; | |
| 119 | return ptr[0..n :sentinel]; | |
| 144 | return self.allocWithOptions(Elem, n, null, sentinel); | |
| 120 | 145 | } |
| 121 | 146 | |
| 122 | 147 | pub fn alignedAlloc( |
src-self-hosted/ir.zig+69-647| ... | ... | @@ -4,41 +4,26 @@ const Allocator = std.mem.Allocator; |
| 4 | 4 | const Value = @import("value.zig").Value; |
| 5 | 5 | const Type = @import("type.zig").Type; |
| 6 | 6 | const assert = std.debug.assert; |
| 7 | const text = @import("ir/text.zig"); | |
| 7 | 8 | |
| 9 | /// These are in-memory, analyzed instructions. See `text.Inst` for the representation | |
| 10 | /// of instructions that correspond to the ZIR text format. | |
| 8 | 11 | pub const Inst = struct { |
| 9 | tag: Tag, | |
| 12 | pub fn ty(base: *Inst) ?Type { | |
| 13 | switch (base.tag) { | |
| 14 | .constant => return base.cast(Constant).?.ty, | |
| 15 | .@"asm" => return base.cast(Assembly).?.ty, | |
| 16 | .@"fn" => return base.cast(Fn).?.ty, | |
| 10 | 17 | |
| 11 | /// These names are used for the IR text format. | |
| 12 | pub const Tag = enum { | |
| 13 | constant, | |
| 14 | ptrtoint, | |
| 15 | fieldptr, | |
| 16 | deref, | |
| 17 | @"asm", | |
| 18 | @"unreachable", | |
| 19 | @"fn", | |
| 20 | @"export", | |
| 21 | }; | |
| 22 | ||
| 23 | pub fn TagToType(tag: Tag) type { | |
| 24 | return switch (tag) { | |
| 25 | .constant => Constant, | |
| 26 | .ptrtoint => PtrToInt, | |
| 27 | .fieldptr => FieldPtr, | |
| 28 | .deref => Deref, | |
| 29 | .@"asm" => Assembly, | |
| 30 | .@"unreachable" => Unreachable, | |
| 31 | .@"fn" => Fn, | |
| 32 | .@"export" => Export, | |
| 33 | }; | |
| 34 | } | |
| 18 | .ptrtoint => return Type.initTag(.@"usize"), | |
| 19 | .@"unreachable" => return Type.initTag(.@"noreturn"), | |
| 20 | .@"export" => return Type.initTag(.@"void"), | |
| 21 | .fntype, .primitive => return Type.initTag(.@"type"), | |
| 35 | 22 | |
| 36 | pub fn cast(base: *Inst, comptime T: type) ?*T { | |
| 37 | const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag; | |
| 38 | if (base.tag != expected_tag) | |
| 39 | return null; | |
| 40 | ||
| 41 | return @fieldParentPtr(T, "base", base); | |
| 23 | .fieldptr, | |
| 24 | .deref, | |
| 25 | => return null, | |
| 26 | } | |
| 42 | 27 | } |
| 43 | 28 | |
| 44 | 29 | /// This struct owns the `Value` memory. When the struct is deallocated, |
| ... | ... | @@ -53,644 +38,70 @@ pub const Inst = struct { |
| 53 | 38 | }, |
| 54 | 39 | kw_args: struct {}, |
| 55 | 40 | }; |
| 56 | ||
| 57 | pub const PtrToInt = struct { | |
| 58 | base: Inst = Inst{ .tag = .ptrtoint }, | |
| 59 | ||
| 60 | positionals: struct { | |
| 61 | ptr: *Inst, | |
| 62 | }, | |
| 63 | kw_args: struct {}, | |
| 64 | }; | |
| 65 | ||
| 66 | pub const FieldPtr = struct { | |
| 67 | base: Inst = Inst{ .tag = .fieldptr }, | |
| 68 | ||
| 69 | positionals: struct { | |
| 70 | object_ptr: *Inst, | |
| 71 | field_name: *Inst, | |
| 72 | }, | |
| 73 | kw_args: struct {}, | |
| 74 | }; | |
| 75 | ||
| 76 | pub const Deref = struct { | |
| 77 | base: Inst = Inst{ .tag = .deref }, | |
| 78 | ||
| 79 | positionals: struct { | |
| 80 | ptr: *Inst, | |
| 81 | }, | |
| 82 | kw_args: struct {}, | |
| 83 | }; | |
| 84 | ||
| 85 | pub const Assembly = struct { | |
| 86 | base: Inst = Inst{ .tag = .@"asm" }, | |
| 87 | ||
| 88 | positionals: struct { | |
| 89 | asm_source: *Inst, | |
| 90 | }, | |
| 91 | kw_args: struct { | |
| 92 | @"volatile": bool = false, | |
| 93 | output: ?*Inst = null, | |
| 94 | inputs: []*Inst = &[0]*Inst{}, | |
| 95 | clobbers: []*Inst = &[0]*Inst{}, | |
| 96 | args: []*Inst = &[0]*Inst{}, | |
| 97 | }, | |
| 98 | }; | |
| 99 | ||
| 100 | pub const Unreachable = struct { | |
| 101 | base: Inst = Inst{ .tag = .@"unreachable" }, | |
| 102 | ||
| 103 | positionals: struct {}, | |
| 104 | kw_args: struct {}, | |
| 105 | }; | |
| 106 | ||
| 107 | pub const Fn = struct { | |
| 108 | base: Inst = Inst{ .tag = .@"fn" }, | |
| 109 | ||
| 110 | positionals: struct { | |
| 111 | body: Body, | |
| 112 | }, | |
| 113 | kw_args: struct { | |
| 114 | cc: std.builtin.CallingConvention = .Unspecified, | |
| 115 | }, | |
| 116 | ||
| 117 | pub const Body = struct { | |
| 118 | instructions: []*Inst, | |
| 119 | }; | |
| 120 | }; | |
| 121 | ||
| 122 | pub const Export = struct { | |
| 123 | base: Inst = Inst{ .tag = .@"export" }, | |
| 124 | ||
| 125 | positionals: struct { | |
| 126 | symbol_name: *Inst, | |
| 127 | value: *Inst, | |
| 128 | }, | |
| 129 | kw_args: struct {}, | |
| 130 | }; | |
| 131 | }; | |
| 132 | ||
| 133 | pub const ErrorMsg = struct { | |
| 134 | byte_offset: usize, | |
| 135 | msg: []const u8, | |
| 136 | }; | |
| 137 | ||
| 138 | pub const Tree = struct { | |
| 139 | decls: []*Inst, | |
| 140 | errors: []ErrorMsg, | |
| 141 | ||
| 142 | pub fn deinit(self: *Tree) void { | |
| 143 | // TODO resource deallocation | |
| 144 | self.* = undefined; | |
| 145 | } | |
| 146 | ||
| 147 | /// This is a debugging utility for rendering the tree to stderr. | |
| 148 | pub fn dump(self: Tree) void { | |
| 149 | self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {}; | |
| 150 | } | |
| 151 | ||
| 152 | const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Inst.Fn.Body }); | |
| 153 | ||
| 154 | pub fn writeToStream(self: Tree, allocator: *Allocator, stream: var) !void { | |
| 155 | // First, build a map of *Inst to @ or % indexes | |
| 156 | var inst_table = InstPtrTable.init(allocator); | |
| 157 | defer inst_table.deinit(); | |
| 158 | ||
| 159 | try inst_table.ensureCapacity(self.decls.len); | |
| 160 | ||
| 161 | for (self.decls) |decl, decl_i| { | |
| 162 | try inst_table.putNoClobber(decl, .{ .index = decl_i, .fn_body = null }); | |
| 163 | ||
| 164 | if (decl.cast(Inst.Fn)) |fn_inst| { | |
| 165 | for (fn_inst.positionals.body.instructions) |inst, inst_i| { | |
| 166 | try inst_table.putNoClobber(inst, .{ .index = inst_i, .fn_body = &fn_inst.positionals.body }); | |
| 167 | } | |
| 168 | } | |
| 169 | } | |
| 170 | ||
| 171 | for (self.decls) |decl, i| { | |
| 172 | try stream.print("@{} ", .{i}); | |
| 173 | try self.writeInstToStream(stream, decl, &inst_table); | |
| 174 | try stream.writeByte('\n'); | |
| 175 | } | |
| 176 | } | |
| 177 | ||
| 178 | fn writeInstToStream( | |
| 179 | self: Tree, | |
| 180 | stream: var, | |
| 181 | decl: *Inst, | |
| 182 | inst_table: *const InstPtrTable, | |
| 183 | ) @TypeOf(stream).Error!void { | |
| 184 | // TODO I tried implementing this with an inline for loop and hit a compiler bug | |
| 185 | switch (decl.tag) { | |
| 186 | .constant => return self.writeInstToStreamGeneric(stream, .constant, decl, inst_table), | |
| 187 | .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table), | |
| 188 | .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, decl, inst_table), | |
| 189 | .deref => return self.writeInstToStreamGeneric(stream, .deref, decl, inst_table), | |
| 190 | .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", decl, inst_table), | |
| 191 | .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", decl, inst_table), | |
| 192 | .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table), | |
| 193 | .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table), | |
| 194 | } | |
| 195 | } | |
| 196 | ||
| 197 | fn writeInstToStreamGeneric( | |
| 198 | self: Tree, | |
| 199 | stream: var, | |
| 200 | comptime inst_tag: Inst.Tag, | |
| 201 | base: *Inst, | |
| 202 | inst_table: *const InstPtrTable, | |
| 203 | ) !void { | |
| 204 | const SpecificInst = Inst.TagToType(inst_tag); | |
| 205 | const inst = @fieldParentPtr(SpecificInst, "base", base); | |
| 206 | if (@hasField(SpecificInst, "ty")) { | |
| 207 | try stream.print(": {} ", .{inst.ty}); | |
| 208 | } | |
| 209 | if (inst_tag == .constant) { | |
| 210 | if (inst.positionals.value.cast(Value.Payload.Bytes)) |bytes_value| { | |
| 211 | try stream.writeAll("= "); | |
| 212 | return std.zig.renderStringLiteral(bytes_value.data, stream); | |
| 213 | } else if (inst.positionals.value.cast(Value.Payload.Int_u64)) |v| { | |
| 214 | return stream.print("= {}", .{v.int}); | |
| 215 | } else if (inst.positionals.value.cast(Value.Payload.Int_i64)) |v| { | |
| 216 | return stream.print("= {}", .{v.int}); | |
| 217 | } | |
| 218 | } | |
| 219 | const Positionals = @TypeOf(inst.positionals); | |
| 220 | try stream.writeAll("= " ++ @tagName(inst_tag) ++ "("); | |
| 221 | const pos_fields = @typeInfo(Positionals).Struct.fields; | |
| 222 | inline for (pos_fields) |arg_field, i| { | |
| 223 | if (i != 0) { | |
| 224 | try stream.writeAll(", "); | |
| 225 | } | |
| 226 | try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table); | |
| 227 | } | |
| 228 | ||
| 229 | comptime var need_comma = pos_fields.len != 0; | |
| 230 | const KW_Args = @TypeOf(inst.kw_args); | |
| 231 | inline for (@typeInfo(KW_Args).Struct.fields) |arg_field, i| { | |
| 232 | if (need_comma) { | |
| 233 | try stream.writeAll(",\n "); | |
| 234 | } | |
| 235 | if (@typeInfo(arg_field.field_type) == .Optional) { | |
| 236 | if (@field(inst.kw_args, arg_field.name)) |non_optional| { | |
| 237 | try stream.print("{}=", .{arg_field.name}); | |
| 238 | try self.writeParamToStream(stream, non_optional, inst_table); | |
| 239 | need_comma = true; | |
| 240 | } | |
| 241 | } else { | |
| 242 | try stream.print("{}=", .{arg_field.name}); | |
| 243 | try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table); | |
| 244 | need_comma = true; | |
| 245 | } | |
| 246 | } | |
| 247 | ||
| 248 | try stream.writeByte(')'); | |
| 249 | } | |
| 250 | ||
| 251 | fn writeParamToStream(self: Tree, stream: var, param: var, inst_table: *const InstPtrTable) !void { | |
| 252 | if (@typeInfo(@TypeOf(param)) == .Enum) { | |
| 253 | return stream.writeAll(@tagName(param)); | |
| 254 | } | |
| 255 | switch (@TypeOf(param)) { | |
| 256 | Value => { | |
| 257 | try stream.print("{}", .{param}); | |
| 258 | }, | |
| 259 | *Inst => return self.writeInstParamToStream(stream, param, inst_table), | |
| 260 | []*Inst => { | |
| 261 | try stream.writeByte('['); | |
| 262 | for (param) |inst, i| { | |
| 263 | if (i != 0) { | |
| 264 | try stream.writeAll(", "); | |
| 265 | } | |
| 266 | try self.writeInstParamToStream(stream, inst, inst_table); | |
| 267 | } | |
| 268 | try stream.writeByte(']'); | |
| 269 | }, | |
| 270 | Inst.Fn.Body => { | |
| 271 | try stream.writeAll("{\n"); | |
| 272 | for (param.instructions) |inst, i| { | |
| 273 | try stream.print(" %{} ", .{i}); | |
| 274 | try self.writeInstToStream(stream, inst, inst_table); | |
| 275 | try stream.writeByte('\n'); | |
| 276 | } | |
| 277 | try stream.writeByte('}'); | |
| 278 | }, | |
| 279 | bool => return stream.writeByte("01"[@boolToInt(param)]), | |
| 280 | else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), | |
| 281 | } | |
| 282 | } | |
| 283 | ||
| 284 | fn writeInstParamToStream(self: Tree, stream: var, inst: *Inst, inst_table: *const InstPtrTable) !void { | |
| 285 | const info = inst_table.getValue(inst).?; | |
| 286 | const prefix = if (info.fn_body == null) "@" else "%"; | |
| 287 | try stream.print("{}{}", .{ prefix, info.index }); | |
| 288 | } | |
| 289 | 41 | }; |
| 290 | 42 | |
| 291 | const ParseContext = struct { | |
| 43 | const Analyze = struct { | |
| 292 | 44 | allocator: *Allocator, |
| 293 | i: usize, | |
| 294 | source: []const u8, | |
| 45 | old_tree: *const Module, | |
| 295 | 46 | errors: std.ArrayList(ErrorMsg), |
| 296 | 47 | decls: std.ArrayList(*Inst), |
| 297 | global_name_map: *std.StringHashMap(usize), | |
| 298 | }; | |
| 299 | 48 | |
| 300 | pub fn parse(allocator: *Allocator, source: []const u8) Allocator.Error!Tree { | |
| 301 | var global_name_map = std.StringHashMap(usize).init(allocator); | |
| 302 | defer global_name_map.deinit(); | |
| 49 | const NewInst = struct { | |
| 50 | ptr: *Inst, | |
| 51 | }; | |
| 52 | }; | |
| 303 | 53 | |
| 304 | var ctx: ParseContext = .{ | |
| 54 | pub fn analyze(allocator: *Allocator, old_tree: Module) !Module { | |
| 55 | var ctx = Analyze{ | |
| 305 | 56 | .allocator = allocator, |
| 306 | .i = 0, | |
| 307 | .source = source, | |
| 57 | .old_tree = &old_tree, | |
| 308 | 58 | .decls = std.ArrayList(*Inst).init(allocator), |
| 309 | 59 | .errors = std.ArrayList(ErrorMsg).init(allocator), |
| 310 | .global_name_map = &global_name_map, | |
| 60 | .inst_table = std.HashMap(*Inst, Analyze.InstData).init(allocator), | |
| 311 | 61 | }; |
| 312 | parseRoot(&ctx) catch |err| switch (err) { | |
| 313 | error.ParseFailure => { | |
| 62 | defer ctx.decls.deinit(); | |
| 63 | defer ctx.errors.deinit(); | |
| 64 | defer inst_table.deinit(); | |
| 65 | ||
| 66 | analyzeRoot(&ctx) catch |err| switch (err) { | |
| 67 | error.AnalyzeFailure => { | |
| 314 | 68 | assert(ctx.errors.items.len != 0); |
| 315 | 69 | }, |
| 316 | 70 | else => |e| return e, |
| 317 | 71 | }; |
| 318 | return Tree{ | |
| 72 | return Module{ | |
| 319 | 73 | .decls = ctx.decls.toOwnedSlice(), |
| 320 | 74 | .errors = ctx.errors.toOwnedSlice(), |
| 321 | 75 | }; |
| 322 | 76 | } |
| 323 | 77 | |
| 324 | pub fn parseRoot(ctx: *ParseContext) !void { | |
| 325 | // The IR format is designed so that it can be tokenized and parsed at the same time. | |
| 326 | while (ctx.i < ctx.source.len) : (ctx.i += 1) switch (ctx.source[ctx.i]) { | |
| 327 | ';' => _ = try skipToAndOver(ctx, '\n'), | |
| 328 | '@' => { | |
| 329 | ctx.i += 1; | |
| 330 | const ident = try skipToAndOver(ctx, ' '); | |
| 331 | const opt_type = try parseOptionalType(ctx); | |
| 332 | const inst = try parseInstruction(ctx, opt_type, null); | |
| 333 | const ident_index = ctx.decls.items.len; | |
| 334 | if (try ctx.global_name_map.put(ident, ident_index)) |_| { | |
| 335 | return parseError(ctx, "redefinition of identifier '{}'", .{ident}); | |
| 336 | } | |
| 337 | try ctx.decls.append(inst); | |
| 338 | continue; | |
| 339 | }, | |
| 340 | ' ', '\n' => continue, | |
| 341 | else => |byte| return parseError(ctx, "unexpected byte: '{c}'", .{byte}), | |
| 342 | }; | |
| 343 | } | |
| 344 | ||
| 345 | fn eatByte(ctx: *ParseContext, byte: u8) bool { | |
| 346 | if (ctx.i >= ctx.source.len) return false; | |
| 347 | if (ctx.source[ctx.i] != byte) return false; | |
| 348 | ctx.i += 1; | |
| 349 | return true; | |
| 350 | } | |
| 351 | ||
| 352 | fn skipSpace(ctx: *ParseContext) void { | |
| 353 | while (ctx.i < ctx.source.len and (ctx.source[ctx.i] == ' ' or ctx.source[ctx.i] == '\n')) { | |
| 354 | ctx.i += 1; | |
| 355 | } | |
| 356 | } | |
| 357 | ||
| 358 | fn requireEatBytes(ctx: *ParseContext, bytes: []const u8) !void { | |
| 359 | if (ctx.i + bytes.len > ctx.source.len) | |
| 360 | return parseError(ctx, "unexpected EOF", .{}); | |
| 361 | if (!mem.eql(u8, ctx.source[ctx.i..][0..bytes.len], bytes)) | |
| 362 | return parseError(ctx, "expected '{}'", .{bytes}); | |
| 363 | ctx.i += bytes.len; | |
| 364 | } | |
| 365 | ||
| 366 | fn skipToAndOver(ctx: *ParseContext, byte: u8) ![]const u8 { | |
| 367 | const start_i = ctx.i; | |
| 368 | while (ctx.i < ctx.source.len) : (ctx.i += 1) { | |
| 369 | if (ctx.source[ctx.i] == byte) { | |
| 370 | const result = ctx.source[start_i..ctx.i]; | |
| 371 | ctx.i += 1; | |
| 372 | return result; | |
| 78 | fn analyzeRoot(ctx: *Analyze) !void { | |
| 79 | for (old_tree.decls) |decl| { | |
| 80 | if (decl.cast(Inst.Export)) |export_inst| { | |
| 81 | try analyzeExport(ctx, export_inst); | |
| 373 | 82 | } |
| 374 | 83 | } |
| 375 | return parseError(ctx, "unexpected EOF", .{}); | |
| 376 | 84 | } |
| 377 | 85 | |
| 378 | fn parseError(ctx: *ParseContext, comptime format: []const u8, args: var) error{ ParseFailure, OutOfMemory } { | |
| 379 | const msg = try std.fmt.allocPrint(ctx.allocator, format, args); | |
| 380 | (try ctx.errors.addOne()).* = .{ | |
| 381 | .byte_offset = ctx.i, | |
| 382 | .msg = msg, | |
| 86 | fn analyzeExport(ctx: *Analyze, export_inst: *Inst.Export) !void { | |
| 87 | const old_decl = export_inst.positionals.value; | |
| 88 | const new_info = ctx.inst_table.get(old_exp_target) orelse blk: { | |
| 89 | const new_decl = try analyzeDecl(ctx, old_decl); | |
| 90 | const new_info: Analyze.NewInst = .{ .ptr = new_decl }; | |
| 91 | try ctx.inst_table.put(old_decl, new_info); | |
| 92 | break :blk new_info; | |
| 383 | 93 | }; |
| 384 | return error.ParseFailure; | |
| 385 | } | |
| 386 | ||
| 387 | /// Regardless of whether a `Type` is returned, it skips past the '='. | |
| 388 | fn parseOptionalType(ctx: *ParseContext) !?Type { | |
| 389 | skipSpace(ctx); | |
| 390 | if (eatByte(ctx, ':')) { | |
| 391 | const type_text_untrimmed = try skipToAndOver(ctx, '='); | |
| 392 | skipSpace(ctx); | |
| 393 | const type_text = mem.trim(u8, type_text_untrimmed, " \n"); | |
| 394 | if (mem.eql(u8, type_text, "usize")) { | |
| 395 | return Type.initTag(.int_usize); | |
| 396 | } else if (mem.eql(u8, type_text, "noreturn")) { | |
| 397 | return Type.initTag(.no_return); | |
| 398 | } else { | |
| 399 | return parseError(ctx, "TODO parse type '{}'", .{type_text}); | |
| 400 | } | |
| 401 | } else { | |
| 402 | skipSpace(ctx); | |
| 403 | try requireEatBytes(ctx, "="); | |
| 404 | skipSpace(ctx); | |
| 405 | return null; | |
| 406 | } | |
| 407 | } | |
| 408 | ||
| 409 | fn parseInstruction( | |
| 410 | ctx: *ParseContext, | |
| 411 | opt_type: ?Type, | |
| 412 | body_ctx: ?*BodyContext, | |
| 413 | ) error{ OutOfMemory, ParseFailure }!*Inst { | |
| 414 | switch (ctx.source[ctx.i]) { | |
| 415 | '"' => return parseStringLiteralConst(ctx, opt_type), | |
| 416 | '0'...'9' => return parseIntegerLiteralConst(ctx, opt_type), | |
| 417 | else => {}, | |
| 418 | } | |
| 419 | const fn_name = try skipToAndOver(ctx, '('); | |
| 420 | inline for (@typeInfo(Inst.Tag).Enum.fields) |field| { | |
| 421 | if (mem.eql(u8, field.name, fn_name)) { | |
| 422 | const tag = @field(Inst.Tag, field.name); | |
| 423 | return parseInstructionGeneric(ctx, field.name, Inst.TagToType(tag), opt_type, body_ctx); | |
| 424 | } | |
| 425 | } | |
| 426 | return parseError(ctx, "unknown instruction '{}'", .{fn_name}); | |
| 427 | } | |
| 428 | ||
| 429 | fn parseInstructionGeneric( | |
| 430 | ctx: *ParseContext, | |
| 431 | comptime fn_name: []const u8, | |
| 432 | comptime InstType: type, | |
| 433 | opt_type: ?Type, | |
| 434 | body_ctx: ?*BodyContext, | |
| 435 | ) !*Inst { | |
| 436 | const inst_specific = try ctx.allocator.create(InstType); | |
| 437 | inst_specific.base = std.meta.fieldInfo(InstType, "base").default_value.?; | |
| 438 | 94 | |
| 439 | if (@hasField(InstType, "ty")) { | |
| 440 | inst_specific.ty = opt_type orelse { | |
| 441 | return parseError(ctx, "instruction '" ++ fn_name ++ "' requires type", .{}); | |
| 442 | }; | |
| 443 | } | |
| 444 | ||
| 445 | const Positionals = @TypeOf(inst_specific.positionals); | |
| 446 | inline for (@typeInfo(Positionals).Struct.fields) |arg_field| { | |
| 447 | if (ctx.source[ctx.i] == ',') { | |
| 448 | ctx.i += 1; | |
| 449 | skipSpace(ctx); | |
| 450 | } else if (ctx.source[ctx.i] == ')') { | |
| 451 | return parseError(ctx, "expected positional parameter '{}'", .{arg_field.name}); | |
| 452 | } | |
| 453 | @field(inst_specific.positionals, arg_field.name) = try parseParameterGeneric( | |
| 454 | ctx, | |
| 455 | arg_field.field_type, | |
| 456 | body_ctx, | |
| 457 | ); | |
| 458 | skipSpace(ctx); | |
| 459 | } | |
| 460 | ||
| 461 | const KW_Args = @TypeOf(inst_specific.kw_args); | |
| 462 | inst_specific.kw_args = .{}; // assign defaults | |
| 463 | skipSpace(ctx); | |
| 464 | while (eatByte(ctx, ',')) { | |
| 465 | skipSpace(ctx); | |
| 466 | const name = try skipToAndOver(ctx, '='); | |
| 467 | inline for (@typeInfo(KW_Args).Struct.fields) |arg_field| { | |
| 468 | const field_name = arg_field.name; | |
| 469 | if (mem.eql(u8, name, field_name)) { | |
| 470 | const NonOptional = switch (@typeInfo(arg_field.field_type)) { | |
| 471 | .Optional => |info| info.child, | |
| 472 | else => arg_field.field_type, | |
| 473 | }; | |
| 474 | @field(inst_specific.kw_args, field_name) = try parseParameterGeneric(ctx, NonOptional, body_ctx); | |
| 475 | break; | |
| 476 | } | |
| 477 | } else { | |
| 478 | return parseError(ctx, "unrecognized keyword parameter: '{}'", .{name}); | |
| 479 | } | |
| 480 | skipSpace(ctx); | |
| 481 | } | |
| 482 | try requireEatBytes(ctx, ")"); | |
| 483 | ||
| 484 | return &inst_specific.base; | |
| 485 | } | |
| 486 | ||
| 487 | fn parseParameterGeneric(ctx: *ParseContext, comptime T: type, body_ctx: ?*BodyContext) !T { | |
| 488 | if (@typeInfo(T) == .Enum) { | |
| 489 | const start = ctx.i; | |
| 490 | while (ctx.i < ctx.source.len) : (ctx.i += 1) switch (ctx.source[ctx.i]) { | |
| 491 | ' ', '\n', ',', ')' => { | |
| 492 | const enum_name = ctx.source[start..ctx.i]; | |
| 493 | return std.meta.stringToEnum(T, enum_name) orelse { | |
| 494 | return parseError(ctx, "tag '{}' not a member of enum '{}'", .{ enum_name, @typeName(T) }); | |
| 495 | }; | |
| 496 | }, | |
| 497 | else => continue, | |
| 498 | }; | |
| 499 | return parseError(ctx, "unexpected EOF in enum parameter", .{}); | |
| 500 | } | |
| 501 | switch (T) { | |
| 502 | Inst.Fn.Body => return parseBody(ctx), | |
| 503 | bool => { | |
| 504 | const bool_value = switch (ctx.source[ctx.i]) { | |
| 505 | '0' => false, | |
| 506 | '1' => true, | |
| 507 | else => |byte| return parseError(ctx, "expected '0' or '1' for boolean value, found {c}", .{byte}), | |
| 508 | }; | |
| 509 | ctx.i += 1; | |
| 510 | return bool_value; | |
| 511 | }, | |
| 512 | []*Inst => { | |
| 513 | try requireEatBytes(ctx, "["); | |
| 514 | skipSpace(ctx); | |
| 515 | if (eatByte(ctx, ']')) return &[0]*Inst{}; | |
| 516 | ||
| 517 | var instructions = std.ArrayList(*Inst).init(ctx.allocator); | |
| 518 | defer instructions.deinit(); | |
| 519 | while (true) { | |
| 520 | skipSpace(ctx); | |
| 521 | try instructions.append(try parseParameterInst(ctx, body_ctx)); | |
| 522 | skipSpace(ctx); | |
| 523 | if (!eatByte(ctx, ',')) break; | |
| 524 | } | |
| 525 | try requireEatBytes(ctx, "]"); | |
| 526 | return instructions.toOwnedSlice(); | |
| 527 | }, | |
| 528 | *Inst => return parseParameterInst(ctx, body_ctx), | |
| 529 | Value => return parseError(ctx, "TODO implement parseParameterGeneric for type Value", .{}), | |
| 530 | else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), | |
| 531 | } | |
| 532 | return parseError(ctx, "TODO parse parameter {}", .{@typeName(T)}); | |
| 533 | } | |
| 534 | ||
| 535 | fn parseParameterInst(ctx: *ParseContext, body_ctx: ?*BodyContext) !*Inst { | |
| 536 | const local_ref = switch (ctx.source[ctx.i]) { | |
| 537 | '@' => false, | |
| 538 | '%' => true, | |
| 539 | '"' => { | |
| 540 | const str_lit_inst = try parseStringLiteralConst(ctx, null); | |
| 541 | try ctx.decls.append(str_lit_inst); | |
| 542 | return str_lit_inst; | |
| 543 | }, | |
| 544 | else => |byte| return parseError(ctx, "unexpected byte: '{c}'", .{byte}), | |
| 545 | }; | |
| 546 | const map = if (local_ref) | |
| 547 | if (body_ctx) |bc| | |
| 548 | &bc.name_map | |
| 549 | else | |
| 550 | return parseError(ctx, "referencing a % instruction in global scope", .{}) | |
| 551 | else | |
| 552 | ctx.global_name_map; | |
| 553 | ||
| 554 | ctx.i += 1; | |
| 555 | const name_start = ctx.i; | |
| 556 | while (ctx.i < ctx.source.len) : (ctx.i += 1) switch (ctx.source[ctx.i]) { | |
| 557 | ' ', '\n', ',', ')', ']' => break, | |
| 558 | else => continue, | |
| 559 | }; | |
| 560 | const ident = ctx.source[name_start..ctx.i]; | |
| 561 | const kv = map.get(ident) orelse { | |
| 562 | const bad_name = ctx.source[name_start - 1 .. ctx.i]; | |
| 563 | ctx.i = name_start - 1; | |
| 564 | return parseError(ctx, "unrecognized identifier: {}", .{bad_name}); | |
| 565 | }; | |
| 566 | if (local_ref) { | |
| 567 | return body_ctx.?.instructions.items[kv.value]; | |
| 568 | } else { | |
| 569 | return ctx.decls.items[kv.value]; | |
| 570 | } | |
| 571 | } | |
| 572 | ||
| 573 | const BodyContext = struct { | |
| 574 | instructions: std.ArrayList(*Inst), | |
| 575 | name_map: std.StringHashMap(usize), | |
| 576 | }; | |
| 577 | ||
| 578 | fn parseBody(ctx: *ParseContext) !Inst.Fn.Body { | |
| 579 | var body_context = BodyContext{ | |
| 580 | .instructions = std.ArrayList(*Inst).init(ctx.allocator), | |
| 581 | .name_map = std.StringHashMap(usize).init(ctx.allocator), | |
| 582 | }; | |
| 583 | defer body_context.instructions.deinit(); | |
| 584 | defer body_context.name_map.deinit(); | |
| 585 | ||
| 586 | try requireEatBytes(ctx, "{"); | |
| 587 | skipSpace(ctx); | |
| 588 | ||
| 589 | while (ctx.i < ctx.source.len) : (ctx.i += 1) switch (ctx.source[ctx.i]) { | |
| 590 | ';' => _ = try skipToAndOver(ctx, '\n'), | |
| 591 | '%' => { | |
| 592 | ctx.i += 1; | |
| 593 | const ident = try skipToAndOver(ctx, ' '); | |
| 594 | const opt_type = try parseOptionalType(ctx); | |
| 595 | const inst = try parseInstruction(ctx, opt_type, &body_context); | |
| 596 | const ident_index = body_context.instructions.items.len; | |
| 597 | if (try body_context.name_map.put(ident, ident_index)) |_| { | |
| 598 | return parseError(ctx, "redefinition of identifier '{}'", .{ident}); | |
| 599 | } | |
| 600 | try body_context.instructions.append(inst); | |
| 601 | continue; | |
| 602 | }, | |
| 603 | ' ', '\n' => continue, | |
| 604 | '}' => { | |
| 605 | ctx.i += 1; | |
| 606 | break; | |
| 607 | }, | |
| 608 | else => |byte| return parseError(ctx, "unexpected byte: '{c}'", .{byte}), | |
| 609 | }; | |
| 610 | ||
| 611 | return Inst.Fn.Body{ | |
| 612 | .instructions = body_context.instructions.toOwnedSlice(), | |
| 613 | }; | |
| 614 | } | |
| 615 | ||
| 616 | fn parseStringLiteralConst(ctx: *ParseContext, opt_type: ?Type) !*Inst { | |
| 617 | const start = ctx.i; | |
| 618 | ctx.i += 1; // skip over '"' | |
| 619 | ||
| 620 | while (ctx.i < ctx.source.len) : (ctx.i += 1) switch (ctx.source[ctx.i]) { | |
| 621 | '"' => { | |
| 622 | ctx.i += 1; | |
| 623 | const span = ctx.source[start..ctx.i]; | |
| 624 | var bad_index: usize = undefined; | |
| 625 | const parsed = std.zig.parseStringLiteral(ctx.allocator, span, &bad_index) catch |err| switch (err) { | |
| 626 | error.InvalidCharacter => { | |
| 627 | ctx.i = start + bad_index; | |
| 628 | const bad_byte = ctx.source[ctx.i]; | |
| 629 | return parseError(ctx, "invalid string literal character: '{c}'\n", .{bad_byte}); | |
| 630 | }, | |
| 631 | else => |e| return e, | |
| 632 | }; | |
| 633 | const const_inst = try ctx.allocator.create(Inst.Constant); | |
| 634 | errdefer ctx.allocator.destroy(const_inst); | |
| 635 | ||
| 636 | const bytes_payload = try ctx.allocator.create(Value.Payload.Bytes); | |
| 637 | errdefer ctx.allocator.destroy(bytes_payload); | |
| 638 | bytes_payload.* = .{ .data = parsed }; | |
| 639 | ||
| 640 | const ty = opt_type orelse blk: { | |
| 641 | const array_payload = try ctx.allocator.create(Type.Payload.Array_u8_Sentinel0); | |
| 642 | errdefer ctx.allocator.destroy(array_payload); | |
| 643 | array_payload.* = .{ .len = parsed.len }; | |
| 644 | ||
| 645 | const ty_payload = try ctx.allocator.create(Type.Payload.SingleConstPointer); | |
| 646 | errdefer ctx.allocator.destroy(ty_payload); | |
| 647 | ty_payload.* = .{ .pointee_type = Type.initPayload(&array_payload.base) }; | |
| 648 | ||
| 649 | break :blk Type.initPayload(&ty_payload.base); | |
| 650 | }; | |
| 651 | ||
| 652 | const_inst.* = .{ | |
| 653 | .ty = ty, | |
| 654 | .positionals = .{ .value = Value.initPayload(&bytes_payload.base) }, | |
| 655 | .kw_args = .{}, | |
| 656 | }; | |
| 657 | return &const_inst.base; | |
| 658 | }, | |
| 659 | '\\' => { | |
| 660 | ctx.i += 1; | |
| 661 | if (ctx.i >= ctx.source.len) break; | |
| 662 | continue; | |
| 663 | }, | |
| 664 | else => continue, | |
| 665 | }; | |
| 666 | return parseError(ctx, "unexpected EOF in string literal", .{}); | |
| 667 | } | |
| 668 | ||
| 669 | fn parseIntegerLiteralConst(ctx: *ParseContext, opt_type: ?Type) !*Inst { | |
| 670 | const start = ctx.i; | |
| 671 | while (ctx.i < ctx.source.len) : (ctx.i += 1) switch (ctx.source[ctx.i]) { | |
| 672 | '0'...'9' => continue, | |
| 673 | else => break, | |
| 674 | }; | |
| 675 | const number_text = ctx.source[start..ctx.i]; | |
| 676 | const number = std.fmt.parseInt(u64, number_text, 10) catch |err| switch (err) { | |
| 677 | error.Overflow => return parseError(ctx, "TODO handle big integers", .{}), | |
| 678 | error.InvalidCharacter => return parseError(ctx, "invalid integer literal", .{}), | |
| 679 | }; | |
| 680 | ||
| 681 | const int_payload = try ctx.allocator.create(Value.Payload.Int_u64); | |
| 682 | errdefer ctx.allocator.destroy(int_payload); | |
| 683 | int_payload.* = .{ .int = number }; | |
| 684 | ||
| 685 | const const_inst = try ctx.allocator.create(Inst.Constant); | |
| 686 | errdefer ctx.allocator.destroy(const_inst); | |
| 687 | ||
| 688 | const_inst.* = .{ | |
| 689 | .ty = opt_type orelse Type.initTag(.int_comptime), | |
| 690 | .positionals = .{ .value = Value.initPayload(&int_payload.base) }, | |
| 691 | .kw_args = .{}, | |
| 692 | }; | |
| 693 | return &const_inst.base; | |
| 95 | //const exp_type = new_info.ptr.ty(); | |
| 96 | //switch (exp_type.zigTypeTag()) { | |
| 97 | // .Fn => { | |
| 98 | // if () |kv| { | |
| 99 | // kv.value | |
| 100 | // } | |
| 101 | // return analyzeExportFn(ctx, exp_target.cast(Inst., | |
| 102 | // }, | |
| 103 | // else => return ctx.fail("unable to export type '{}'", .{exp_type}), | |
| 104 | //} | |
| 694 | 105 | } |
| 695 | 106 | |
| 696 | 107 | pub fn main() anyerror!void { |
| ... | ... | @@ -703,9 +114,9 @@ pub fn main() anyerror!void { |
| 703 | 114 | const src_path = args[1]; |
| 704 | 115 | const debug_error_trace = true; |
| 705 | 116 | |
| 706 | const source = try std.fs.cwd().readFileAlloc(allocator, src_path, std.math.maxInt(u32)); | |
| 117 | const source = try std.fs.cwd().readFileAllocOptions(allocator, src_path, std.math.maxInt(u32), 1, 0); | |
| 707 | 118 | |
| 708 | var tree = try parse(allocator, source); | |
| 119 | var tree = try text.parse(allocator, source); | |
| 709 | 120 | defer tree.deinit(); |
| 710 | 121 | |
| 711 | 122 | if (tree.errors.len != 0) { |
| ... | ... | @@ -719,8 +130,19 @@ pub fn main() anyerror!void { |
| 719 | 130 | |
| 720 | 131 | tree.dump(); |
| 721 | 132 | |
| 722 | //const new_tree = try semanticallyAnalyze(tree); | |
| 133 | //const new_tree = try analyze(allocator, tree); | |
| 723 | 134 | //defer new_tree.deinit(); |
| 135 | ||
| 136 | //if (new_tree.errors.len != 0) { | |
| 137 | // for (new_tree.errors) |err_msg| { | |
| 138 | // const loc = findLineColumn(source, err_msg.byte_offset); | |
| 139 | // std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg }); | |
| 140 | // } | |
| 141 | // if (debug_error_trace) return error.ParseFailure; | |
| 142 | // std.process.exit(1); | |
| 143 | //} | |
| 144 | ||
| 145 | //new_tree.dump(); | |
| 724 | 146 | } |
| 725 | 147 | |
| 726 | 148 | fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } { |
| ... | ... | @@ -741,4 +163,4 @@ fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, |
| 741 | 163 | } |
| 742 | 164 | |
| 743 | 165 | // Performance optimization ideas: |
| 744 | // * make the source code sentinel-terminated, so that all the checks against the length can be skipped | |
| 166 | // * when analyzing use a field in the Inst instead of HashMap to track corresponding instructions |
src-self-hosted/ir/text.zig created+712| ... | ... | @@ -0,0 +1,712 @@ |
| 1 | //! This file has to do with parsing and rendering the ZIR text format. | |
| 2 | const std = @import("std"); | |
| 3 | const mem = std.mem; | |
| 4 | const Allocator = std.mem.Allocator; | |
| 5 | const Value = @import("../value.zig").Value; | |
| 6 | const assert = std.debug.assert; | |
| 7 | const ir = @import("../ir.zig"); | |
| 8 | const BigInt = std.math.big.Int; | |
| 9 | ||
| 10 | /// These are instructions that correspond to the ZIR text format. See `ir.Inst` for | |
| 11 | /// in-memory, analyzed instructions with types and values. | |
| 12 | pub const Inst = struct { | |
| 13 | tag: Tag, | |
| 14 | ||
| 15 | /// These names are used directly as the instruction names in the text format. | |
| 16 | pub const Tag = enum { | |
| 17 | str, | |
| 18 | int, | |
| 19 | ptrtoint, | |
| 20 | fieldptr, | |
| 21 | deref, | |
| 22 | as, | |
| 23 | @"asm", | |
| 24 | @"unreachable", | |
| 25 | @"fn", | |
| 26 | @"export", | |
| 27 | primitive, | |
| 28 | fntype, | |
| 29 | }; | |
| 30 | ||
| 31 | pub fn TagToType(tag: Tag) type { | |
| 32 | return switch (tag) { | |
| 33 | .str => Str, | |
| 34 | .int => Int, | |
| 35 | .ptrtoint => PtrToInt, | |
| 36 | .fieldptr => FieldPtr, | |
| 37 | .deref => Deref, | |
| 38 | .as => As, | |
| 39 | .@"asm" => Assembly, | |
| 40 | .@"unreachable" => Unreachable, | |
| 41 | .@"fn" => Fn, | |
| 42 | .@"export" => Export, | |
| 43 | .primitive => Primitive, | |
| 44 | .fntype => FnType, | |
| 45 | }; | |
| 46 | } | |
| 47 | ||
| 48 | pub fn cast(base: *Inst, comptime T: type) ?*T { | |
| 49 | const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag; | |
| 50 | if (base.tag != expected_tag) | |
| 51 | return null; | |
| 52 | ||
| 53 | return @fieldParentPtr(T, "base", base); | |
| 54 | } | |
| 55 | ||
| 56 | pub const Str = struct { | |
| 57 | base: Inst = Inst{ .tag = .str }, | |
| 58 | ||
| 59 | positionals: struct { | |
| 60 | bytes: []u8, | |
| 61 | }, | |
| 62 | kw_args: struct {}, | |
| 63 | }; | |
| 64 | ||
| 65 | pub const Int = struct { | |
| 66 | base: Inst = Inst{ .tag = .int }, | |
| 67 | ||
| 68 | positionals: struct { | |
| 69 | int: BigInt, | |
| 70 | }, | |
| 71 | kw_args: struct {}, | |
| 72 | }; | |
| 73 | ||
| 74 | pub const PtrToInt = struct { | |
| 75 | base: Inst = Inst{ .tag = .ptrtoint }, | |
| 76 | ||
| 77 | positionals: struct { | |
| 78 | ptr: *Inst, | |
| 79 | }, | |
| 80 | kw_args: struct {}, | |
| 81 | }; | |
| 82 | ||
| 83 | pub const FieldPtr = struct { | |
| 84 | base: Inst = Inst{ .tag = .fieldptr }, | |
| 85 | ||
| 86 | positionals: struct { | |
| 87 | object_ptr: *Inst, | |
| 88 | field_name: *Inst, | |
| 89 | }, | |
| 90 | kw_args: struct {}, | |
| 91 | }; | |
| 92 | ||
| 93 | pub const Deref = struct { | |
| 94 | base: Inst = Inst{ .tag = .deref }, | |
| 95 | ||
| 96 | positionals: struct { | |
| 97 | ptr: *Inst, | |
| 98 | }, | |
| 99 | kw_args: struct {}, | |
| 100 | }; | |
| 101 | ||
| 102 | pub const As = struct { | |
| 103 | base: Inst = Inst{ .tag = .as }, | |
| 104 | ||
| 105 | positionals: struct { | |
| 106 | dest_type: *Inst, | |
| 107 | value: *Inst, | |
| 108 | }, | |
| 109 | kw_args: struct {}, | |
| 110 | }; | |
| 111 | ||
| 112 | pub const Assembly = struct { | |
| 113 | base: Inst = Inst{ .tag = .@"asm" }, | |
| 114 | ||
| 115 | positionals: struct { | |
| 116 | asm_source: *Inst, | |
| 117 | return_type: *Inst, | |
| 118 | }, | |
| 119 | kw_args: struct { | |
| 120 | @"volatile": bool = false, | |
| 121 | output: ?*Inst = null, | |
| 122 | inputs: []*Inst = &[0]*Inst{}, | |
| 123 | clobbers: []*Inst = &[0]*Inst{}, | |
| 124 | args: []*Inst = &[0]*Inst{}, | |
| 125 | }, | |
| 126 | }; | |
| 127 | ||
| 128 | pub const Unreachable = struct { | |
| 129 | base: Inst = Inst{ .tag = .@"unreachable" }, | |
| 130 | ||
| 131 | positionals: struct {}, | |
| 132 | kw_args: struct {}, | |
| 133 | }; | |
| 134 | ||
| 135 | pub const Fn = struct { | |
| 136 | base: Inst = Inst{ .tag = .@"fn" }, | |
| 137 | ||
| 138 | positionals: struct { | |
| 139 | fn_type: *Inst, | |
| 140 | body: Body, | |
| 141 | }, | |
| 142 | kw_args: struct {}, | |
| 143 | ||
| 144 | pub const Body = struct { | |
| 145 | instructions: []*Inst, | |
| 146 | }; | |
| 147 | }; | |
| 148 | ||
| 149 | pub const Export = struct { | |
| 150 | base: Inst = Inst{ .tag = .@"export" }, | |
| 151 | ||
| 152 | positionals: struct { | |
| 153 | symbol_name: *Inst, | |
| 154 | value: *Inst, | |
| 155 | }, | |
| 156 | kw_args: struct {}, | |
| 157 | }; | |
| 158 | ||
| 159 | pub const Primitive = struct { | |
| 160 | base: Inst = Inst{ .tag = .primitive }, | |
| 161 | ||
| 162 | positionals: struct { | |
| 163 | tag: BuiltinType, | |
| 164 | }, | |
| 165 | kw_args: struct {}, | |
| 166 | ||
| 167 | pub const BuiltinType = enum { | |
| 168 | @"isize", | |
| 169 | @"usize", | |
| 170 | @"c_short", | |
| 171 | @"c_ushort", | |
| 172 | @"c_int", | |
| 173 | @"c_uint", | |
| 174 | @"c_long", | |
| 175 | @"c_ulong", | |
| 176 | @"c_longlong", | |
| 177 | @"c_ulonglong", | |
| 178 | @"c_longdouble", | |
| 179 | @"c_void", | |
| 180 | @"f16", | |
| 181 | @"f32", | |
| 182 | @"f64", | |
| 183 | @"f128", | |
| 184 | @"bool", | |
| 185 | @"void", | |
| 186 | @"noreturn", | |
| 187 | @"type", | |
| 188 | @"anyerror", | |
| 189 | @"comptime_int", | |
| 190 | @"comptime_float", | |
| 191 | }; | |
| 192 | }; | |
| 193 | ||
| 194 | pub const FnType = struct { | |
| 195 | base: Inst = Inst{ .tag = .fntype }, | |
| 196 | ||
| 197 | positionals: struct { | |
| 198 | param_types: []*Inst, | |
| 199 | return_type: *Inst, | |
| 200 | }, | |
| 201 | kw_args: struct { | |
| 202 | cc: std.builtin.CallingConvention = .Unspecified, | |
| 203 | }, | |
| 204 | }; | |
| 205 | }; | |
| 206 | ||
| 207 | pub const ErrorMsg = struct { | |
| 208 | byte_offset: usize, | |
| 209 | msg: []const u8, | |
| 210 | }; | |
| 211 | ||
| 212 | pub const Module = struct { | |
| 213 | decls: []*Inst, | |
| 214 | errors: []ErrorMsg, | |
| 215 | ||
| 216 | pub fn deinit(self: *Module) void { | |
| 217 | // TODO resource deallocation | |
| 218 | self.* = undefined; | |
| 219 | } | |
| 220 | ||
| 221 | /// This is a debugging utility for rendering the tree to stderr. | |
| 222 | pub fn dump(self: Module) void { | |
| 223 | self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {}; | |
| 224 | } | |
| 225 | ||
| 226 | const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Inst.Fn.Body }); | |
| 227 | ||
| 228 | pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void { | |
| 229 | // First, build a map of *Inst to @ or % indexes | |
| 230 | var inst_table = InstPtrTable.init(allocator); | |
| 231 | defer inst_table.deinit(); | |
| 232 | ||
| 233 | try inst_table.ensureCapacity(self.decls.len); | |
| 234 | ||
| 235 | for (self.decls) |decl, decl_i| { | |
| 236 | try inst_table.putNoClobber(decl, .{ .index = decl_i, .fn_body = null }); | |
| 237 | ||
| 238 | if (decl.cast(Inst.Fn)) |fn_inst| { | |
| 239 | for (fn_inst.positionals.body.instructions) |inst, inst_i| { | |
| 240 | try inst_table.putNoClobber(inst, .{ .index = inst_i, .fn_body = &fn_inst.positionals.body }); | |
| 241 | } | |
| 242 | } | |
| 243 | } | |
| 244 | ||
| 245 | for (self.decls) |decl, i| { | |
| 246 | try stream.print("@{} ", .{i}); | |
| 247 | try self.writeInstToStream(stream, decl, &inst_table); | |
| 248 | try stream.writeByte('\n'); | |
| 249 | } | |
| 250 | } | |
| 251 | ||
| 252 | fn writeInstToStream( | |
| 253 | self: Module, | |
| 254 | stream: var, | |
| 255 | decl: *Inst, | |
| 256 | inst_table: *const InstPtrTable, | |
| 257 | ) @TypeOf(stream).Error!void { | |
| 258 | // TODO I tried implementing this with an inline for loop and hit a compiler bug | |
| 259 | switch (decl.tag) { | |
| 260 | .str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table), | |
| 261 | .int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table), | |
| 262 | .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table), | |
| 263 | .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, decl, inst_table), | |
| 264 | .deref => return self.writeInstToStreamGeneric(stream, .deref, decl, inst_table), | |
| 265 | .as => return self.writeInstToStreamGeneric(stream, .as, decl, inst_table), | |
| 266 | .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", decl, inst_table), | |
| 267 | .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", decl, inst_table), | |
| 268 | .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table), | |
| 269 | .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table), | |
| 270 | .primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table), | |
| 271 | .fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table), | |
| 272 | } | |
| 273 | } | |
| 274 | ||
| 275 | fn writeInstToStreamGeneric( | |
| 276 | self: Module, | |
| 277 | stream: var, | |
| 278 | comptime inst_tag: Inst.Tag, | |
| 279 | base: *Inst, | |
| 280 | inst_table: *const InstPtrTable, | |
| 281 | ) !void { | |
| 282 | const SpecificInst = Inst.TagToType(inst_tag); | |
| 283 | const inst = @fieldParentPtr(SpecificInst, "base", base); | |
| 284 | const Positionals = @TypeOf(inst.positionals); | |
| 285 | try stream.writeAll("= " ++ @tagName(inst_tag) ++ "("); | |
| 286 | const pos_fields = @typeInfo(Positionals).Struct.fields; | |
| 287 | inline for (pos_fields) |arg_field, i| { | |
| 288 | if (i != 0) { | |
| 289 | try stream.writeAll(", "); | |
| 290 | } | |
| 291 | try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table); | |
| 292 | } | |
| 293 | ||
| 294 | comptime var need_comma = pos_fields.len != 0; | |
| 295 | const KW_Args = @TypeOf(inst.kw_args); | |
| 296 | inline for (@typeInfo(KW_Args).Struct.fields) |arg_field, i| { | |
| 297 | if (need_comma) { | |
| 298 | try stream.writeAll(", "); | |
| 299 | } | |
| 300 | if (@typeInfo(arg_field.field_type) == .Optional) { | |
| 301 | if (@field(inst.kw_args, arg_field.name)) |non_optional| { | |
| 302 | try stream.print("{}=", .{arg_field.name}); | |
| 303 | try self.writeParamToStream(stream, non_optional, inst_table); | |
| 304 | need_comma = true; | |
| 305 | } | |
| 306 | } else { | |
| 307 | try stream.print("{}=", .{arg_field.name}); | |
| 308 | try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table); | |
| 309 | need_comma = true; | |
| 310 | } | |
| 311 | } | |
| 312 | ||
| 313 | try stream.writeByte(')'); | |
| 314 | } | |
| 315 | ||
| 316 | fn writeParamToStream(self: Module, stream: var, param: var, inst_table: *const InstPtrTable) !void { | |
| 317 | if (@typeInfo(@TypeOf(param)) == .Enum) { | |
| 318 | return stream.writeAll(@tagName(param)); | |
| 319 | } | |
| 320 | switch (@TypeOf(param)) { | |
| 321 | Value => return stream.print("{}", .{param}), | |
| 322 | *Inst => return self.writeInstParamToStream(stream, param, inst_table), | |
| 323 | []*Inst => { | |
| 324 | try stream.writeByte('['); | |
| 325 | for (param) |inst, i| { | |
| 326 | if (i != 0) { | |
| 327 | try stream.writeAll(", "); | |
| 328 | } | |
| 329 | try self.writeInstParamToStream(stream, inst, inst_table); | |
| 330 | } | |
| 331 | try stream.writeByte(']'); | |
| 332 | }, | |
| 333 | Inst.Fn.Body => { | |
| 334 | try stream.writeAll("{\n"); | |
| 335 | for (param.instructions) |inst, i| { | |
| 336 | try stream.print(" %{} ", .{i}); | |
| 337 | try self.writeInstToStream(stream, inst, inst_table); | |
| 338 | try stream.writeByte('\n'); | |
| 339 | } | |
| 340 | try stream.writeByte('}'); | |
| 341 | }, | |
| 342 | bool => return stream.writeByte("01"[@boolToInt(param)]), | |
| 343 | []u8 => return std.zig.renderStringLiteral(param, stream), | |
| 344 | BigInt => return stream.print("{}", .{param}), | |
| 345 | else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), | |
| 346 | } | |
| 347 | } | |
| 348 | ||
| 349 | fn writeInstParamToStream(self: Module, stream: var, inst: *Inst, inst_table: *const InstPtrTable) !void { | |
| 350 | const info = inst_table.getValue(inst).?; | |
| 351 | const prefix = if (info.fn_body == null) "@" else "%"; | |
| 352 | try stream.print("{}{}", .{ prefix, info.index }); | |
| 353 | } | |
| 354 | }; | |
| 355 | ||
| 356 | pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module { | |
| 357 | var global_name_map = std.StringHashMap(usize).init(allocator); | |
| 358 | defer global_name_map.deinit(); | |
| 359 | ||
| 360 | var parser: Parser = .{ | |
| 361 | .allocator = allocator, | |
| 362 | .i = 0, | |
| 363 | .source = source, | |
| 364 | .decls = std.ArrayList(*Inst).init(allocator), | |
| 365 | .errors = std.ArrayList(ErrorMsg).init(allocator), | |
| 366 | .global_name_map = &global_name_map, | |
| 367 | }; | |
| 368 | parser.parseRoot() catch |err| switch (err) { | |
| 369 | error.ParseFailure => { | |
| 370 | assert(parser.errors.items.len != 0); | |
| 371 | }, | |
| 372 | else => |e| return e, | |
| 373 | }; | |
| 374 | return Module{ | |
| 375 | .decls = parser.decls.toOwnedSlice(), | |
| 376 | .errors = parser.errors.toOwnedSlice(), | |
| 377 | }; | |
| 378 | } | |
| 379 | ||
| 380 | const Parser = struct { | |
| 381 | allocator: *Allocator, | |
| 382 | i: usize, | |
| 383 | source: [:0]const u8, | |
| 384 | errors: std.ArrayList(ErrorMsg), | |
| 385 | decls: std.ArrayList(*Inst), | |
| 386 | global_name_map: *std.StringHashMap(usize), | |
| 387 | ||
| 388 | const Body = struct { | |
| 389 | instructions: std.ArrayList(*Inst), | |
| 390 | name_map: std.StringHashMap(usize), | |
| 391 | }; | |
| 392 | ||
| 393 | fn parseBody(self: *Parser) !Inst.Fn.Body { | |
| 394 | var body_context = Body{ | |
| 395 | .instructions = std.ArrayList(*Inst).init(self.allocator), | |
| 396 | .name_map = std.StringHashMap(usize).init(self.allocator), | |
| 397 | }; | |
| 398 | defer body_context.instructions.deinit(); | |
| 399 | defer body_context.name_map.deinit(); | |
| 400 | ||
| 401 | try requireEatBytes(self, "{"); | |
| 402 | skipSpace(self); | |
| 403 | ||
| 404 | while (true) : (self.i += 1) switch (self.source[self.i]) { | |
| 405 | ';' => _ = try skipToAndOver(self, '\n'), | |
| 406 | '%' => { | |
| 407 | self.i += 1; | |
| 408 | const ident = try skipToAndOver(self, ' '); | |
| 409 | skipSpace(self); | |
| 410 | try requireEatBytes(self, "="); | |
| 411 | skipSpace(self); | |
| 412 | const inst = try parseInstruction(self, &body_context); | |
| 413 | const ident_index = body_context.instructions.items.len; | |
| 414 | if (try body_context.name_map.put(ident, ident_index)) |_| { | |
| 415 | return self.fail("redefinition of identifier '{}'", .{ident}); | |
| 416 | } | |
| 417 | try body_context.instructions.append(inst); | |
| 418 | continue; | |
| 419 | }, | |
| 420 | ' ', '\n' => continue, | |
| 421 | '}' => { | |
| 422 | self.i += 1; | |
| 423 | break; | |
| 424 | }, | |
| 425 | else => |byte| return self.failByte(byte), | |
| 426 | }; | |
| 427 | ||
| 428 | return Inst.Fn.Body{ | |
| 429 | .instructions = body_context.instructions.toOwnedSlice(), | |
| 430 | }; | |
| 431 | } | |
| 432 | ||
| 433 | fn parseStringLiteral(self: *Parser) ![]u8 { | |
| 434 | const start = self.i; | |
| 435 | try self.requireEatBytes("\""); | |
| 436 | ||
| 437 | while (true) : (self.i += 1) switch (self.source[self.i]) { | |
| 438 | '"' => { | |
| 439 | self.i += 1; | |
| 440 | const span = self.source[start..self.i]; | |
| 441 | var bad_index: usize = undefined; | |
| 442 | const parsed = std.zig.parseStringLiteral(self.allocator, span, &bad_index) catch |err| switch (err) { | |
| 443 | error.InvalidCharacter => { | |
| 444 | self.i = start + bad_index; | |
| 445 | const bad_byte = self.source[self.i]; | |
| 446 | return self.fail("invalid string literal character: '{c}'\n", .{bad_byte}); | |
| 447 | }, | |
| 448 | else => |e| return e, | |
| 449 | }; | |
| 450 | return parsed; | |
| 451 | }, | |
| 452 | '\\' => { | |
| 453 | self.i += 1; | |
| 454 | continue; | |
| 455 | }, | |
| 456 | 0 => return self.failByte(0), | |
| 457 | else => continue, | |
| 458 | }; | |
| 459 | } | |
| 460 | ||
| 461 | fn parseIntegerLiteral(self: *Parser) !BigInt { | |
| 462 | const start = self.i; | |
| 463 | if (self.source[self.i] == '-') self.i += 1; | |
| 464 | while (true) : (self.i += 1) switch (self.source[self.i]) { | |
| 465 | '0'...'9' => continue, | |
| 466 | else => break, | |
| 467 | }; | |
| 468 | const number_text = self.source[start..self.i]; | |
| 469 | var result = try BigInt.init(self.allocator); | |
| 470 | result.setString(10, number_text) catch |err| { | |
| 471 | self.i = start; | |
| 472 | switch (err) { | |
| 473 | error.InvalidBase => unreachable, | |
| 474 | error.InvalidCharForDigit => return self.fail("invalid digit in integer literal", .{}), | |
| 475 | error.DigitTooLargeForBase => return self.fail("digit too large in integer literal", .{}), | |
| 476 | else => |e| return e, | |
| 477 | } | |
| 478 | }; | |
| 479 | return result; | |
| 480 | } | |
| 481 | ||
| 482 | fn parseRoot(self: *Parser) !void { | |
| 483 | // The IR format is designed so that it can be tokenized and parsed at the same time. | |
| 484 | while (true) : (self.i += 1) switch (self.source[self.i]) { | |
| 485 | ';' => _ = try skipToAndOver(self, '\n'), | |
| 486 | '@' => { | |
| 487 | self.i += 1; | |
| 488 | const ident = try skipToAndOver(self, ' '); | |
| 489 | skipSpace(self); | |
| 490 | try requireEatBytes(self, "="); | |
| 491 | skipSpace(self); | |
| 492 | const inst = try parseInstruction(self, null); | |
| 493 | const ident_index = self.decls.items.len; | |
| 494 | if (try self.global_name_map.put(ident, ident_index)) |_| { | |
| 495 | return self.fail("redefinition of identifier '{}'", .{ident}); | |
| 496 | } | |
| 497 | try self.decls.append(inst); | |
| 498 | continue; | |
| 499 | }, | |
| 500 | ' ', '\n' => continue, | |
| 501 | 0 => break, | |
| 502 | else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), | |
| 503 | }; | |
| 504 | } | |
| 505 | ||
| 506 | fn eatByte(self: *Parser, byte: u8) bool { | |
| 507 | if (self.source[self.i] != byte) return false; | |
| 508 | self.i += 1; | |
| 509 | return true; | |
| 510 | } | |
| 511 | ||
| 512 | fn skipSpace(self: *Parser) void { | |
| 513 | while (self.source[self.i] == ' ' or self.source[self.i] == '\n') { | |
| 514 | self.i += 1; | |
| 515 | } | |
| 516 | } | |
| 517 | ||
| 518 | fn requireEatBytes(self: *Parser, bytes: []const u8) !void { | |
| 519 | const start = self.i; | |
| 520 | for (bytes) |byte| { | |
| 521 | if (self.source[self.i] != byte) { | |
| 522 | self.i = start; | |
| 523 | return self.fail("expected '{}'", .{bytes}); | |
| 524 | } | |
| 525 | self.i += 1; | |
| 526 | } | |
| 527 | } | |
| 528 | ||
| 529 | fn skipToAndOver(self: *Parser, byte: u8) ![]const u8 { | |
| 530 | const start_i = self.i; | |
| 531 | while (self.source[self.i] != 0) : (self.i += 1) { | |
| 532 | if (self.source[self.i] == byte) { | |
| 533 | const result = self.source[start_i..self.i]; | |
| 534 | self.i += 1; | |
| 535 | return result; | |
| 536 | } | |
| 537 | } | |
| 538 | return self.fail("unexpected EOF", .{}); | |
| 539 | } | |
| 540 | ||
| 541 | /// ParseFailure is an internal error code; handled in `parse`. | |
| 542 | const InnerError = error{ ParseFailure, OutOfMemory }; | |
| 543 | ||
| 544 | fn failByte(self: *Parser, byte: u8) InnerError { | |
| 545 | if (byte == 0) { | |
| 546 | return self.fail("unexpected EOF", .{}); | |
| 547 | } else { | |
| 548 | return self.fail("unexpected byte: '{c}'", .{byte}); | |
| 549 | } | |
| 550 | } | |
| 551 | ||
| 552 | fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError { | |
| 553 | @setCold(true); | |
| 554 | const msg = try std.fmt.allocPrint(self.allocator, format, args); | |
| 555 | (try self.errors.addOne()).* = .{ | |
| 556 | .byte_offset = self.i, | |
| 557 | .msg = msg, | |
| 558 | }; | |
| 559 | return error.ParseFailure; | |
| 560 | } | |
| 561 | ||
| 562 | fn parseInstruction(self: *Parser, body_ctx: ?*Body) InnerError!*Inst { | |
| 563 | const fn_name = try skipToAndOver(self, '('); | |
| 564 | inline for (@typeInfo(Inst.Tag).Enum.fields) |field| { | |
| 565 | if (mem.eql(u8, field.name, fn_name)) { | |
| 566 | const tag = @field(Inst.Tag, field.name); | |
| 567 | return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx); | |
| 568 | } | |
| 569 | } | |
| 570 | return self.fail("unknown instruction '{}'", .{fn_name}); | |
| 571 | } | |
| 572 | ||
| 573 | fn parseInstructionGeneric( | |
| 574 | self: *Parser, | |
| 575 | comptime fn_name: []const u8, | |
| 576 | comptime InstType: type, | |
| 577 | body_ctx: ?*Body, | |
| 578 | ) !*Inst { | |
| 579 | const inst_specific = try self.allocator.create(InstType); | |
| 580 | inst_specific.base = std.meta.fieldInfo(InstType, "base").default_value.?; | |
| 581 | ||
| 582 | if (@hasField(InstType, "ty")) { | |
| 583 | inst_specific.ty = opt_type orelse { | |
| 584 | return self.fail("instruction '" ++ fn_name ++ "' requires type", .{}); | |
| 585 | }; | |
| 586 | } | |
| 587 | ||
| 588 | const Positionals = @TypeOf(inst_specific.positionals); | |
| 589 | inline for (@typeInfo(Positionals).Struct.fields) |arg_field| { | |
| 590 | if (self.source[self.i] == ',') { | |
| 591 | self.i += 1; | |
| 592 | skipSpace(self); | |
| 593 | } else if (self.source[self.i] == ')') { | |
| 594 | return self.fail("expected positional parameter '{}'", .{arg_field.name}); | |
| 595 | } | |
| 596 | @field(inst_specific.positionals, arg_field.name) = try parseParameterGeneric( | |
| 597 | self, | |
| 598 | arg_field.field_type, | |
| 599 | body_ctx, | |
| 600 | ); | |
| 601 | skipSpace(self); | |
| 602 | } | |
| 603 | ||
| 604 | const KW_Args = @TypeOf(inst_specific.kw_args); | |
| 605 | inst_specific.kw_args = .{}; // assign defaults | |
| 606 | skipSpace(self); | |
| 607 | while (eatByte(self, ',')) { | |
| 608 | skipSpace(self); | |
| 609 | const name = try skipToAndOver(self, '='); | |
| 610 | inline for (@typeInfo(KW_Args).Struct.fields) |arg_field| { | |
| 611 | const field_name = arg_field.name; | |
| 612 | if (mem.eql(u8, name, field_name)) { | |
| 613 | const NonOptional = switch (@typeInfo(arg_field.field_type)) { | |
| 614 | .Optional => |info| info.child, | |
| 615 | else => arg_field.field_type, | |
| 616 | }; | |
| 617 | @field(inst_specific.kw_args, field_name) = try parseParameterGeneric(self, NonOptional, body_ctx); | |
| 618 | break; | |
| 619 | } | |
| 620 | } else { | |
| 621 | return self.fail("unrecognized keyword parameter: '{}'", .{name}); | |
| 622 | } | |
| 623 | skipSpace(self); | |
| 624 | } | |
| 625 | try requireEatBytes(self, ")"); | |
| 626 | ||
| 627 | return &inst_specific.base; | |
| 628 | } | |
| 629 | ||
| 630 | fn parseParameterGeneric(self: *Parser, comptime T: type, body_ctx: ?*Body) !T { | |
| 631 | if (@typeInfo(T) == .Enum) { | |
| 632 | const start = self.i; | |
| 633 | while (true) : (self.i += 1) switch (self.source[self.i]) { | |
| 634 | ' ', '\n', ',', ')' => { | |
| 635 | const enum_name = self.source[start..self.i]; | |
| 636 | return std.meta.stringToEnum(T, enum_name) orelse { | |
| 637 | return self.fail("tag '{}' not a member of enum '{}'", .{ enum_name, @typeName(T) }); | |
| 638 | }; | |
| 639 | }, | |
| 640 | 0 => return self.failByte(0), | |
| 641 | else => continue, | |
| 642 | }; | |
| 643 | } | |
| 644 | switch (T) { | |
| 645 | Inst.Fn.Body => return parseBody(self), | |
| 646 | bool => { | |
| 647 | const bool_value = switch (self.source[self.i]) { | |
| 648 | '0' => false, | |
| 649 | '1' => true, | |
| 650 | else => |byte| return self.fail("expected '0' or '1' for boolean value, found {c}", .{byte}), | |
| 651 | }; | |
| 652 | self.i += 1; | |
| 653 | return bool_value; | |
| 654 | }, | |
| 655 | []*Inst => { | |
| 656 | try requireEatBytes(self, "["); | |
| 657 | skipSpace(self); | |
| 658 | if (eatByte(self, ']')) return &[0]*Inst{}; | |
| 659 | ||
| 660 | var instructions = std.ArrayList(*Inst).init(self.allocator); | |
| 661 | defer instructions.deinit(); | |
| 662 | while (true) { | |
| 663 | skipSpace(self); | |
| 664 | try instructions.append(try parseParameterInst(self, body_ctx)); | |
| 665 | skipSpace(self); | |
| 666 | if (!eatByte(self, ',')) break; | |
| 667 | } | |
| 668 | try requireEatBytes(self, "]"); | |
| 669 | return instructions.toOwnedSlice(); | |
| 670 | }, | |
| 671 | *Inst => return parseParameterInst(self, body_ctx), | |
| 672 | Value => return self.fail("TODO implement parseParameterGeneric for type Value", .{}), | |
| 673 | []u8 => return self.parseStringLiteral(), | |
| 674 | BigInt => return self.parseIntegerLiteral(), | |
| 675 | else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)), | |
| 676 | } | |
| 677 | return self.fail("TODO parse parameter {}", .{@typeName(T)}); | |
| 678 | } | |
| 679 | ||
| 680 | fn parseParameterInst(self: *Parser, body_ctx: ?*Body) !*Inst { | |
| 681 | const local_ref = switch (self.source[self.i]) { | |
| 682 | '@' => false, | |
| 683 | '%' => true, | |
| 684 | else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}), | |
| 685 | }; | |
| 686 | const map = if (local_ref) | |
| 687 | if (body_ctx) |bc| | |
| 688 | &bc.name_map | |
| 689 | else | |
| 690 | return self.fail("referencing a % instruction in global scope", .{}) | |
| 691 | else | |
| 692 | self.global_name_map; | |
| 693 | ||
| 694 | self.i += 1; | |
| 695 | const name_start = self.i; | |
| 696 | while (true) : (self.i += 1) switch (self.source[self.i]) { | |
| 697 | 0, ' ', '\n', ',', ')', ']' => break, | |
| 698 | else => continue, | |
| 699 | }; | |
| 700 | const ident = self.source[name_start..self.i]; | |
| 701 | const kv = map.get(ident) orelse { | |
| 702 | const bad_name = self.source[name_start - 1 .. self.i]; | |
| 703 | self.i = name_start - 1; | |
| 704 | return self.fail("unrecognized identifier: {}", .{bad_name}); | |
| 705 | }; | |
| 706 | if (local_ref) { | |
| 707 | return body_ctx.?.instructions.items[kv.value]; | |
| 708 | } else { | |
| 709 | return self.decls.items[kv.value]; | |
| 710 | } | |
| 711 | } | |
| 712 | }; |
src-self-hosted/type.zig+56-10| ... | ... | @@ -18,7 +18,7 @@ pub const Type = extern union { |
| 18 | 18 | |
| 19 | 19 | pub fn zigTypeTag(self: Type) std.builtin.TypeId { |
| 20 | 20 | switch (self.tag()) { |
| 21 | .int_u8, .int_usize => return .Int, | |
| 21 | .@"u8", .@"usize" => return .Int, | |
| 22 | 22 | .array_u8, .array_u8_sentinel_0 => return .Array, |
| 23 | 23 | .single_const_pointer => return .Pointer, |
| 24 | 24 | } |
| ... | ... | @@ -52,10 +52,35 @@ pub const Type = extern union { |
| 52 | 52 | var ty = self; |
| 53 | 53 | while (true) { |
| 54 | 54 | switch (ty.tag()) { |
| 55 | .no_return => return out_stream.writeAll("noreturn"), | |
| 56 | .int_comptime => return out_stream.writeAll("comptime_int"), | |
| 57 | .int_u8 => return out_stream.writeAll("u8"), | |
| 58 | .int_usize => return out_stream.writeAll("usize"), | |
| 55 | @"u8", | |
| 56 | @"i8", | |
| 57 | @"isize", | |
| 58 | @"usize", | |
| 59 | @"noreturn", | |
| 60 | @"void", | |
| 61 | @"c_short", | |
| 62 | @"c_ushort", | |
| 63 | @"c_int", | |
| 64 | @"c_uint", | |
| 65 | @"c_long", | |
| 66 | @"c_ulong", | |
| 67 | @"c_longlong", | |
| 68 | @"c_ulonglong", | |
| 69 | @"c_longdouble", | |
| 70 | @"c_void", | |
| 71 | @"f16", | |
| 72 | @"f32", | |
| 73 | @"f64", | |
| 74 | @"f128", | |
| 75 | @"bool", | |
| 76 | @"void", | |
| 77 | @"type", | |
| 78 | @"anyerror", | |
| 79 | @"comptime_int", | |
| 80 | @"comptime_float", | |
| 81 | @"noreturn", | |
| 82 | => |t| return out_stream.writeAll(@tagName(t)), | |
| 83 | ||
| 59 | 84 | .array_u8_sentinel_0 => { |
| 60 | 85 | const payload = @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", ty.ptr_otherwise); |
| 61 | 86 | return out_stream.print("[{}:0]u8", .{payload.len}); |
| ... | ... | @@ -85,17 +110,38 @@ pub const Type = extern union { |
| 85 | 110 | /// See `zigTypeTag` for the function that corresponds to `std.builtin.TypeId`. |
| 86 | 111 | pub const Tag = enum { |
| 87 | 112 | // The first section of this enum are tags that require no payload. |
| 88 | no_return, | |
| 89 | int_comptime, | |
| 90 | int_u8, | |
| 91 | int_usize, // See last_no_payload_tag below. | |
| 113 | @"u8", | |
| 114 | @"i8", | |
| 115 | @"isize", | |
| 116 | @"usize", | |
| 117 | @"c_short", | |
| 118 | @"c_ushort", | |
| 119 | @"c_int", | |
| 120 | @"c_uint", | |
| 121 | @"c_long", | |
| 122 | @"c_ulong", | |
| 123 | @"c_longlong", | |
| 124 | @"c_ulonglong", | |
| 125 | @"c_longdouble", | |
| 126 | @"c_void", | |
| 127 | @"f16", | |
| 128 | @"f32", | |
| 129 | @"f64", | |
| 130 | @"f128", | |
| 131 | @"bool", | |
| 132 | @"void", | |
| 133 | @"type", | |
| 134 | @"anyerror", | |
| 135 | @"comptime_int", | |
| 136 | @"comptime_float", | |
| 137 | @"noreturn", // See last_no_payload_tag below. | |
| 92 | 138 | // After this, the tag requires a payload. |
| 93 | 139 | |
| 94 | 140 | array_u8_sentinel_0, |
| 95 | 141 | array, |
| 96 | 142 | single_const_pointer, |
| 97 | 143 | |
| 98 | pub const last_no_payload_tag = Tag.int_usize; | |
| 144 | pub const last_no_payload_tag = Tag.@"noreturn"; | |
| 99 | 145 | pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; |
| 100 | 146 | }; |
| 101 | 147 |
test/stage2/ir.zig+34-17| ... | ... | @@ -1,33 +1,50 @@ |
| 1 | 1 | test "hello world IR" { |
| 2 | 2 | exeCmp( |
| 3 | \\@0 = "Hello, world!\n" | |
| 3 | \\@0 = str("Hello, world!\n") | |
| 4 | \\@1 = primitive(void) | |
| 5 | \\@2 = primitive(usize) | |
| 6 | \\@3 = fntype([], @1, cc=Naked) | |
| 7 | \\@4 = int(0) | |
| 8 | \\@5 = int(1) | |
| 9 | \\@6 = int(231) | |
| 10 | \\@7 = str("len") | |
| 4 | 11 | \\ |
| 5 | \\@1 = fn({ | |
| 6 | \\ %0 : usize = 1 ;SYS_write | |
| 7 | \\ %1 : usize = 1 ;STDOUT_FILENO | |
| 12 | \\@8 = fn(@3, { | |
| 13 | \\ %0 = as(@2, @5) ; SYS_write | |
| 14 | \\ %1 = as(@2, @5) ; STDOUT_FILENO | |
| 8 | 15 | \\ %2 = ptrtoint(@0) ; msg ptr |
| 9 | \\ %3 = fieldptr(@0, "len") ; msg len ptr | |
| 16 | \\ %3 = fieldptr(@0, @7) ; msg len ptr | |
| 10 | 17 | \\ %4 = deref(%3) ; msg len |
| 11 | \\ %5 = asm("syscall", | |
| 18 | \\ %sysoutreg = str("={rax}") | |
| 19 | \\ %rax = str("{rax}") | |
| 20 | \\ %rdi = str("{rdi}") | |
| 21 | \\ %rsi = str("{rsi}") | |
| 22 | \\ %rdx = str("{rdx}") | |
| 23 | \\ %rcx = str("rcx") | |
| 24 | \\ %r11 = str("r11") | |
| 25 | \\ %memory = str("memory") | |
| 26 | \\ %syscall = str("syscall") | |
| 27 | \\ %5 = asm(%syscall, @2, | |
| 12 | 28 | \\ volatile=1, |
| 13 | \\ output="={rax}", | |
| 14 | \\ inputs=["{rax}", "{rdi}", "{rsi}", "{rdx}"], | |
| 15 | \\ clobbers=["rcx", "r11", "memory"], | |
| 29 | \\ output=%sysoutreg, | |
| 30 | \\ inputs=[%rax, %rdi, %rsi, %rdx], | |
| 31 | \\ clobbers=[%rcx, %r11, %memory], | |
| 16 | 32 | \\ args=[%0, %1, %2, %4]) |
| 17 | 33 | \\ |
| 18 | \\ %6 : usize = 231 ;SYS_exit_group | |
| 19 | \\ %7 : usize = 0 ;exit code | |
| 20 | \\ %8 = asm("syscall", | |
| 34 | \\ %6 = as(@2, @6) ;SYS_exit_group | |
| 35 | \\ %7 = as(@2, @4) ;exit code | |
| 36 | \\ %8 = asm(%syscall, @2, | |
| 21 | 37 | \\ volatile=1, |
| 22 | \\ output="={rax}", | |
| 23 | \\ inputs=["{rax}", "{rdi}"], | |
| 24 | \\ clobbers=["rcx", "r11", "memory"], | |
| 38 | \\ output=%sysoutreg, | |
| 39 | \\ inputs=[%rax, %rdi], | |
| 40 | \\ clobbers=[%rcx, %r11, %memory], | |
| 25 | 41 | \\ args=[%6, %7]) |
| 26 | 42 | \\ |
| 27 | 43 | \\ %9 = unreachable() |
| 28 | \\}, cc=naked) | |
| 44 | \\}) | |
| 29 | 45 | \\ |
| 30 | \\@2 = export("_start", @1) | |
| 46 | \\@9 = str("_start") | |
| 47 | \\@10 = export(@9, @8) | |
| 31 | 48 | , |
| 32 | 49 | \\Hello, world! |
| 33 | 50 | \\ |