| 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,25 +1012,27 @@ pub const Dir = struct { |
| 1012 | /// On success, caller owns returned buffer. | 1012 | /// On success, caller owns returned buffer. |
| 1013 | /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. | 1013 | /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. |
| 1014 | pub fn readFileAlloc(self: Dir, allocator: *mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 { | 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 | /// On success, caller owns returned buffer. | 1018 | /// On success, caller owns returned buffer. |
| 1019 | /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. | 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 | self: Dir, | 1022 | self: Dir, |
| 1022 | allocator: *mem.Allocator, | 1023 | allocator: *mem.Allocator, |
| 1023 | file_path: []const u8, | 1024 | file_path: []const u8, |
| 1024 | max_bytes: usize, | 1025 | max_bytes: usize, |
| 1025 | comptime A: u29, | 1026 | comptime alignment: u29, |
| 1026 | ) ![]align(A) u8 { | 1027 | comptime optional_sentinel: ?u8, |
| 1028 | ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) { | ||
| 1027 | var file = try self.openFile(file_path, .{}); | 1029 | var file = try self.openFile(file_path, .{}); |
| 1028 | defer file.close(); | 1030 | defer file.close(); |
| 1029 | 1031 | ||
| 1030 | const size = math.cast(usize, try file.getEndPos()) catch math.maxInt(usize); | 1032 | const size = math.cast(usize, try file.getEndPos()) catch math.maxInt(usize); |
| 1031 | if (size > max_bytes) return error.FileTooBig; | 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 | errdefer allocator.free(buf); | 1036 | errdefer allocator.free(buf); |
| 1035 | 1037 | ||
| 1036 | try file.inStream().readNoEof(buf); | 1038 | try file.inStream().readNoEof(buf); |
lib/std/mem.zig+28-3| ... | @@ -105,6 +105,31 @@ pub const Allocator = struct { | ... | @@ -105,6 +105,31 @@ pub const Allocator = struct { |
| 105 | return self.alignedAlloc(T, null, n); | 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 | /// Allocates an array of `n + 1` items of type `T` and sets the first `n` | 133 | /// Allocates an array of `n + 1` items of type `T` and sets the first `n` |
| 109 | /// items to `undefined` and the last item to `sentinel`. Depending on the | 134 | /// items to `undefined` and the last item to `sentinel`. Depending on the |
| 110 | /// Allocator implementation, it may be required to call `free` once the | 135 | /// Allocator implementation, it may be required to call `free` once the |
| ... | @@ -113,10 +138,10 @@ pub const Allocator = struct { | ... | @@ -113,10 +138,10 @@ pub const Allocator = struct { |
| 113 | /// call `free` when done. | 138 | /// call `free` when done. |
| 114 | /// | 139 | /// |
| 115 | /// For allocating a single item, see `create`. | 140 | /// For allocating a single item, see `create`. |
| 141 | /// | ||
| 142 | /// Deprecated; use `allocWithOptions`. | ||
| 116 | pub fn allocSentinel(self: *Allocator, comptime Elem: type, n: usize, comptime sentinel: Elem) Error![:sentinel]Elem { | 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); | 144 | return self.allocWithOptions(Elem, n, null, sentinel); |
| 118 | ptr[n] = sentinel; | ||
| 119 | return ptr[0..n :sentinel]; | ||
| 120 | } | 145 | } |
| 121 | 146 | ||
| 122 | pub fn alignedAlloc( | 147 | pub fn alignedAlloc( |
src-self-hosted/ir.zig+69-647| ... | @@ -4,41 +4,26 @@ const Allocator = std.mem.Allocator; | ... | @@ -4,41 +4,26 @@ const Allocator = std.mem.Allocator; |
| 4 | const Value = @import("value.zig").Value; | 4 | const Value = @import("value.zig").Value; |
| 5 | const Type = @import("type.zig").Type; | 5 | const Type = @import("type.zig").Type; |
| 6 | const assert = std.debug.assert; | 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 | pub const Inst = struct { | 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. | 18 | .ptrtoint => return Type.initTag(.@"usize"), |
| 12 | pub const Tag = enum { | 19 | .@"unreachable" => return Type.initTag(.@"noreturn"), |
| 13 | constant, | 20 | .@"export" => return Type.initTag(.@"void"), |
| 14 | ptrtoint, | 21 | .fntype, .primitive => return Type.initTag(.@"type"), |
| 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 | } | ||
| 35 | 22 | ||
| 36 | pub fn cast(base: *Inst, comptime T: type) ?*T { | 23 | .fieldptr, |
| 37 | const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag; | 24 | .deref, |
| 38 | if (base.tag != expected_tag) | 25 | => return null, |
| 39 | return null; | 26 | } |
| 40 | |||
| 41 | return @fieldParentPtr(T, "base", base); | ||
| 42 | } | 27 | } |
| 43 | 28 | ||
| 44 | /// This struct owns the `Value` memory. When the struct is deallocated, | 29 | /// This struct owns the `Value` memory. When the struct is deallocated, |
| ... | @@ -53,644 +38,70 @@ pub const Inst = struct { | ... | @@ -53,644 +38,70 @@ pub const Inst = struct { |
| 53 | }, | 38 | }, |
| 54 | kw_args: struct {}, | 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 | allocator: *Allocator, | 44 | allocator: *Allocator, |
| 293 | i: usize, | 45 | old_tree: *const Module, |
| 294 | source: []const u8, | ||
| 295 | errors: std.ArrayList(ErrorMsg), | 46 | errors: std.ArrayList(ErrorMsg), |
| 296 | decls: std.ArrayList(*Inst), | 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 { | 49 | const NewInst = struct { |
| 301 | var global_name_map = std.StringHashMap(usize).init(allocator); | 50 | ptr: *Inst, |
| 302 | defer global_name_map.deinit(); | 51 | }; |
| 52 | }; | ||
| 303 | 53 | ||
| 304 | var ctx: ParseContext = .{ | 54 | pub fn analyze(allocator: *Allocator, old_tree: Module) !Module { |
| 55 | var ctx = Analyze{ | ||
| 305 | .allocator = allocator, | 56 | .allocator = allocator, |
| 306 | .i = 0, | 57 | .old_tree = &old_tree, |
| 307 | .source = source, | ||
| 308 | .decls = std.ArrayList(*Inst).init(allocator), | 58 | .decls = std.ArrayList(*Inst).init(allocator), |
| 309 | .errors = std.ArrayList(ErrorMsg).init(allocator), | 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) { | 62 | defer ctx.decls.deinit(); |
| 313 | error.ParseFailure => { | 63 | defer ctx.errors.deinit(); |
| 64 | defer inst_table.deinit(); | ||
| 65 | |||
| 66 | analyzeRoot(&ctx) catch |err| switch (err) { | ||
| 67 | error.AnalyzeFailure => { | ||
| 314 | assert(ctx.errors.items.len != 0); | 68 | assert(ctx.errors.items.len != 0); |
| 315 | }, | 69 | }, |
| 316 | else => |e| return e, | 70 | else => |e| return e, |
| 317 | }; | 71 | }; |
| 318 | return Tree{ | 72 | return Module{ |
| 319 | .decls = ctx.decls.toOwnedSlice(), | 73 | .decls = ctx.decls.toOwnedSlice(), |
| 320 | .errors = ctx.errors.toOwnedSlice(), | 74 | .errors = ctx.errors.toOwnedSlice(), |
| 321 | }; | 75 | }; |
| 322 | } | 76 | } |
| 323 | 77 | ||
| 324 | pub fn parseRoot(ctx: *ParseContext) !void { | 78 | fn analyzeRoot(ctx: *Analyze) !void { |
| 325 | // The IR format is designed so that it can be tokenized and parsed at the same time. | 79 | for (old_tree.decls) |decl| { |
| 326 | while (ctx.i < ctx.source.len) : (ctx.i += 1) switch (ctx.source[ctx.i]) { | 80 | if (decl.cast(Inst.Export)) |export_inst| { |
| 327 | ';' => _ = try skipToAndOver(ctx, '\n'), | 81 | try analyzeExport(ctx, export_inst); |
| 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; | ||
| 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 } { | 86 | fn analyzeExport(ctx: *Analyze, export_inst: *Inst.Export) !void { |
| 379 | const msg = try std.fmt.allocPrint(ctx.allocator, format, args); | 87 | const old_decl = export_inst.positionals.value; |
| 380 | (try ctx.errors.addOne()).* = .{ | 88 | const new_info = ctx.inst_table.get(old_exp_target) orelse blk: { |
| 381 | .byte_offset = ctx.i, | 89 | const new_decl = try analyzeDecl(ctx, old_decl); |
| 382 | .msg = msg, | 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")) { | 95 | //const exp_type = new_info.ptr.ty(); |
| 440 | inst_specific.ty = opt_type orelse { | 96 | //switch (exp_type.zigTypeTag()) { |
| 441 | return parseError(ctx, "instruction '" ++ fn_name ++ "' requires type", .{}); | 97 | // .Fn => { |
| 442 | }; | 98 | // if () |kv| { |
| 443 | } | 99 | // kv.value |
| 444 | 100 | // } | |
| 445 | const Positionals = @TypeOf(inst_specific.positionals); | 101 | // return analyzeExportFn(ctx, exp_target.cast(Inst., |
| 446 | inline for (@typeInfo(Positionals).Struct.fields) |arg_field| { | 102 | // }, |
| 447 | if (ctx.source[ctx.i] == ',') { | 103 | // else => return ctx.fail("unable to export type '{}'", .{exp_type}), |
| 448 | ctx.i += 1; | 104 | //} |
| 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; | ||
| 694 | } | 105 | } |
| 695 | 106 | ||
| 696 | pub fn main() anyerror!void { | 107 | pub fn main() anyerror!void { |
| ... | @@ -703,9 +114,9 @@ pub fn main() anyerror!void { | ... | @@ -703,9 +114,9 @@ pub fn main() anyerror!void { |
| 703 | const src_path = args[1]; | 114 | const src_path = args[1]; |
| 704 | const debug_error_trace = true; | 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 | defer tree.deinit(); | 120 | defer tree.deinit(); |
| 710 | 121 | ||
| 711 | if (tree.errors.len != 0) { | 122 | if (tree.errors.len != 0) { |
| ... | @@ -719,8 +130,19 @@ pub fn main() anyerror!void { | ... | @@ -719,8 +130,19 @@ pub fn main() anyerror!void { |
| 719 | 130 | ||
| 720 | tree.dump(); | 131 | tree.dump(); |
| 721 | 132 | ||
| 722 | //const new_tree = try semanticallyAnalyze(tree); | 133 | //const new_tree = try analyze(allocator, tree); |
| 723 | //defer new_tree.deinit(); | 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 | fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } { | 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,4 +163,4 @@ fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, |
| 741 | } | 163 | } |
| 742 | 164 | ||
| 743 | // Performance optimization ideas: | 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,7 +18,7 @@ pub const Type = extern union { |
| 18 | 18 | ||
| 19 | pub fn zigTypeTag(self: Type) std.builtin.TypeId { | 19 | pub fn zigTypeTag(self: Type) std.builtin.TypeId { |
| 20 | switch (self.tag()) { | 20 | switch (self.tag()) { |
| 21 | .int_u8, .int_usize => return .Int, | 21 | .@"u8", .@"usize" => return .Int, |
| 22 | .array_u8, .array_u8_sentinel_0 => return .Array, | 22 | .array_u8, .array_u8_sentinel_0 => return .Array, |
| 23 | .single_const_pointer => return .Pointer, | 23 | .single_const_pointer => return .Pointer, |
| 24 | } | 24 | } |
| ... | @@ -52,10 +52,35 @@ pub const Type = extern union { | ... | @@ -52,10 +52,35 @@ pub const Type = extern union { |
| 52 | var ty = self; | 52 | var ty = self; |
| 53 | while (true) { | 53 | while (true) { |
| 54 | switch (ty.tag()) { | 54 | switch (ty.tag()) { |
| 55 | .no_return => return out_stream.writeAll("noreturn"), | 55 | @"u8", |
| 56 | .int_comptime => return out_stream.writeAll("comptime_int"), | 56 | @"i8", |
| 57 | .int_u8 => return out_stream.writeAll("u8"), | 57 | @"isize", |
| 58 | .int_usize => return out_stream.writeAll("usize"), | 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 | .array_u8_sentinel_0 => { | 84 | .array_u8_sentinel_0 => { |
| 60 | const payload = @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", ty.ptr_otherwise); | 85 | const payload = @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", ty.ptr_otherwise); |
| 61 | return out_stream.print("[{}:0]u8", .{payload.len}); | 86 | return out_stream.print("[{}:0]u8", .{payload.len}); |
| ... | @@ -85,17 +110,38 @@ pub const Type = extern union { | ... | @@ -85,17 +110,38 @@ pub const Type = extern union { |
| 85 | /// See `zigTypeTag` for the function that corresponds to `std.builtin.TypeId`. | 110 | /// See `zigTypeTag` for the function that corresponds to `std.builtin.TypeId`. |
| 86 | pub const Tag = enum { | 111 | pub const Tag = enum { |
| 87 | // The first section of this enum are tags that require no payload. | 112 | // The first section of this enum are tags that require no payload. |
| 88 | no_return, | 113 | @"u8", |
| 89 | int_comptime, | 114 | @"i8", |
| 90 | int_u8, | 115 | @"isize", |
| 91 | int_usize, // See last_no_payload_tag below. | 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 | // After this, the tag requires a payload. | 138 | // After this, the tag requires a payload. |
| 93 | 139 | ||
| 94 | array_u8_sentinel_0, | 140 | array_u8_sentinel_0, |
| 95 | array, | 141 | array, |
| 96 | single_const_pointer, | 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 | pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; | 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,33 +1,50 @@ |
| 1 | test "hello world IR" { | 1 | test "hello world IR" { |
| 2 | exeCmp( | 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({ | 12 | \\@8 = fn(@3, { |
| 6 | \\ %0 : usize = 1 ;SYS_write | 13 | \\ %0 = as(@2, @5) ; SYS_write |
| 7 | \\ %1 : usize = 1 ;STDOUT_FILENO | 14 | \\ %1 = as(@2, @5) ; STDOUT_FILENO |
| 8 | \\ %2 = ptrtoint(@0) ; msg ptr | 15 | \\ %2 = ptrtoint(@0) ; msg ptr |
| 9 | \\ %3 = fieldptr(@0, "len") ; msg len ptr | 16 | \\ %3 = fieldptr(@0, @7) ; msg len ptr |
| 10 | \\ %4 = deref(%3) ; msg len | 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 | \\ volatile=1, | 28 | \\ volatile=1, |
| 13 | \\ output="={rax}", | 29 | \\ output=%sysoutreg, |
| 14 | \\ inputs=["{rax}", "{rdi}", "{rsi}", "{rdx}"], | 30 | \\ inputs=[%rax, %rdi, %rsi, %rdx], |
| 15 | \\ clobbers=["rcx", "r11", "memory"], | 31 | \\ clobbers=[%rcx, %r11, %memory], |
| 16 | \\ args=[%0, %1, %2, %4]) | 32 | \\ args=[%0, %1, %2, %4]) |
| 17 | \\ | 33 | \\ |
| 18 | \\ %6 : usize = 231 ;SYS_exit_group | 34 | \\ %6 = as(@2, @6) ;SYS_exit_group |
| 19 | \\ %7 : usize = 0 ;exit code | 35 | \\ %7 = as(@2, @4) ;exit code |
| 20 | \\ %8 = asm("syscall", | 36 | \\ %8 = asm(%syscall, @2, |
| 21 | \\ volatile=1, | 37 | \\ volatile=1, |
| 22 | \\ output="={rax}", | 38 | \\ output=%sysoutreg, |
| 23 | \\ inputs=["{rax}", "{rdi}"], | 39 | \\ inputs=[%rax, %rdi], |
| 24 | \\ clobbers=["rcx", "r11", "memory"], | 40 | \\ clobbers=[%rcx, %r11, %memory], |
| 25 | \\ args=[%6, %7]) | 41 | \\ args=[%6, %7]) |
| 26 | \\ | 42 | \\ |
| 27 | \\ %9 = unreachable() | 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 | \\Hello, world! | 49 | \\Hello, world! |
| 33 | \\ | 50 | \\ |