authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-04-20 19:19:35-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-04-20 19:21:03-04:00
logcc1c2bd5684a6195e9535fff38ca54ffb70ebe5a
treec9a3979ec26f9072b1ce643cab19d4522d77ee43
parentaf4ccf34c1aac9c9914aa9cd5f3c857b7b90615d

simplify ZIR spec; separate parsing/rendering from analysis


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 }
10171017
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();
10291031
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;
10321034
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);
10351037
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 }
107107
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 the134 /// items to `undefined` and the last item to `sentinel`. Depending on the
110 /// Allocator implementation, it may be required to call `free` once the135 /// 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 }
121146
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;
4const Value = @import("value.zig").Value;4const Value = @import("value.zig").Value;
5const Type = @import("type.zig").Type;5const Type = @import("type.zig").Type;
6const assert = std.debug.assert;6const assert = std.debug.assert;
7const text = @import("ir/text.zig");
78
9/// These are in-memory, analyzed instructions. See `text.Inst` for the representation
10/// of instructions that correspond to the ZIR text format.
8pub const Inst = struct {11pub 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,
1017
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 }
3522
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 }
4328
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
133pub const ErrorMsg = struct {
134 byte_offset: usize,
135 msg: []const u8,
136};
137
138pub 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};
29042
291const ParseContext = struct {43const 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};
29948
300pub 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};
30353
304 var ctx: ParseContext = .{54pub 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}
32377
324pub fn parseRoot(ctx: *ParseContext) !void {78fn 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
345fn 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
352fn 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
358fn 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
366fn 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}
37785
378fn parseError(ctx: *ParseContext, comptime format: []const u8, args: var) error{ ParseFailure, OutOfMemory } {86fn 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 '='.
388fn 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
409fn 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
429fn 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.?;
43894
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
444100 // }
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
487fn 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
535fn 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
573const BodyContext = struct {
574 instructions: std.ArrayList(*Inst),
575 name_map: std.StringHashMap(usize),
576};
577
578fn 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
616fn 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
669fn 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}
695106
696pub fn main() anyerror!void {107pub 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;
705116
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);
707118
708 var tree = try parse(allocator, source);119 var tree = try text.parse(allocator, source);
709 defer tree.deinit();120 defer tree.deinit();
710121
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 {
719130
720 tree.dump();131 tree.dump();
721132
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}
725147
726fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } {148fn 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}
742164
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 skipped166// * 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.
2const std = @import("std");
3const mem = std.mem;
4const Allocator = std.mem.Allocator;
5const Value = @import("../value.zig").Value;
6const assert = std.debug.assert;
7const ir = @import("../ir.zig");
8const 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.
12pub 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
207pub const ErrorMsg = struct {
208 byte_offset: usize,
209 msg: []const u8,
210};
211
212pub 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
356pub 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
380const 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 {
1818
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.
93139
94 array_u8_sentinel_0,140 array_u8_sentinel_0,
95 array,141 array,
96 single_const_pointer,142 single_const_pointer,
97143
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 };
101147
test/stage2/ir.zig+34-17
...@@ -1,33 +1,50 @@...@@ -1,33 +1,50 @@
1test "hello world IR" {1test "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_write13 \\ %0 = as(@2, @5) ; SYS_write
7 \\ %1 : usize = 1 ;STDOUT_FILENO14 \\ %1 = as(@2, @5) ; STDOUT_FILENO
8 \\ %2 = ptrtoint(@0) ; msg ptr15 \\ %2 = ptrtoint(@0) ; msg ptr
9 \\ %3 = fieldptr(@0, "len") ; msg len ptr16 \\ %3 = fieldptr(@0, @7) ; msg len ptr
10 \\ %4 = deref(%3) ; msg len17 \\ %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_group34 \\ %6 = as(@2, @6) ;SYS_exit_group
19 \\ %7 : usize = 0 ;exit code35 \\ %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 \\