authorgravatar for kappaloris@gmail.comLoris Cro <kappaloris@gmail.com> 2022-01-28 22:50:03+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-07-19 19:10:10-07:00
log652e13e7c06db318d88a93db24a8fb2c2f8d249b
tree4b86e50ac4283675702706c445b349a1ce355454
parent0efc6a35bead74b5faffbc87b446b5087f1bb99b

autodoc: init work


2 files changed, 563 insertions(+), 0 deletions(-)

src/Autodoc.zig created+555
...@@ -0,0 +1,555 @@
1const std = @import("std");
2const Autodoc = @This();
3const Compilation = @import("Compilation.zig");
4const Module = @import("Module.zig");
5const Zir = @import("Zir.zig");
6
7module: *Module,
8doc_location: ?Compilation.EmitLoc,
9
10pub fn init(m: *Module, dl: ?Compilation.EmitLoc) Autodoc {
11 return .{
12 .doc_location = dl,
13 .module = m,
14 };
15}
16
17pub fn generateZirData(self: Autodoc) !void {
18 const gpa = self.module.gpa;
19 std.debug.print("yay, you called me!\n", .{});
20 if (self.doc_location) |loc| {
21 if (loc.directory) |dir| {
22 if (dir.path) |path| {
23 std.debug.print("path: {s}\n", .{path});
24 }
25 }
26 std.debug.print("basename: {s}\n", .{loc.basename});
27 }
28
29 // const root_file_path = self.module.main_pkg.root_src_path;
30 const root_file_path = "/home/kristoff/test/test.zig";
31 const zir = self.module.import_table.get(root_file_path).?.zir;
32
33 var types = std.ArrayList(DocData.Type).init(gpa);
34 var decls = std.ArrayList(DocData.Decl).init(gpa);
35 var ast_nodes = std.ArrayList(DocData.AstNode).init(gpa);
36
37 // var decl_map = std.AutoHashMap(Zir.Inst.Index, usize); // values are positions in the `decls` array
38
39 try types.append(.{
40 .kind = 0,
41 .name = "type",
42 });
43
44 var root_scope: Scope = .{ .parent = null };
45 try ast_nodes.append(.{ .name = "(root)" });
46 const main_type_index = try walkInstruction(zir, gpa, &root_scope, &types, &decls, &ast_nodes, Zir.main_struct_inst);
47
48 var data = DocData{
49 .files = &[1][]const u8{root_file_path},
50 .types = types.items,
51 .decls = decls.items,
52 .astNodes = ast_nodes.items,
53 };
54
55 data.packages[0].main = main_type_index.type;
56
57 const out = std.io.getStdOut().writer();
58 out.print("zigAnalysis=", .{}) catch unreachable;
59 std.json.stringify(
60 data,
61 .{
62 .whitespace = .{},
63 .emit_null_optional_fields = false,
64 },
65 out,
66 ) catch unreachable;
67 out.print(";", .{}) catch unreachable;
68}
69
70const Scope = struct {
71 parent: ?*Scope,
72 map: std.AutoHashMapUnmanaged(u32, usize) = .{}, // index into `decls`
73
74 /// Assumes all decls in present scope and upper scopes have already
75 /// been either fully resolved or at least reserved.
76 pub fn resolveDeclName(self: Scope, string_table_idx: u32) usize {
77 var cur: ?*const Scope = &self;
78 return while (cur) |s| : (cur = s.parent) {
79 break s.map.get(string_table_idx) orelse continue;
80 } else unreachable;
81 }
82
83 pub fn insertDeclRef(
84 self: *Scope,
85 gpa: std.mem.Allocator,
86 decl_name_index: u32, // decl name
87 decls_slot_index: usize,
88 ) !void {
89 try self.map.put(gpa, decl_name_index, decls_slot_index);
90 }
91};
92
93const DocData = struct {
94 typeKinds: []const []const u8 = std.meta.fieldNames(std.builtin.TypeId),
95 rootPkg: u32 = 0,
96 params: struct {
97 zigId: []const u8 = "arst",
98 zigVersion: []const u8 = "arst",
99 target: []const u8 = "arst",
100 rootName: []const u8 = "arst",
101 builds: []const struct { target: []const u8 } = &.{
102 .{ .target = "arst" },
103 },
104 } = .{},
105 packages: [1]Package = .{.{}},
106 fns: []struct {} = &.{},
107 errors: []struct {} = &.{},
108 calls: []struct {} = &.{},
109
110 // non-hardcoded stuff
111 astNodes: []AstNode,
112 files: []const []const u8,
113 types: []Type,
114 decls: []Decl,
115
116 const Package = struct {
117 name: []const u8 = "root",
118 file: usize = 0, // index into files
119 main: usize = 0, // index into decls
120 table: struct { root: usize } = .{
121 .root = 0,
122 },
123 };
124
125 const Decl = struct {
126 name: []const u8,
127 kind: []const u8, // TODO: where do we find this info?
128 src: usize, // index into astNodes
129 type: usize, // index into types
130 value: usize,
131 };
132
133 const AstNode = struct {
134 file: usize = 0, // index into files
135 line: usize = 0,
136 col: usize = 0,
137 name: ?[]const u8 = null,
138 docs: ?[]const u8 = null,
139 fields: ?[]usize = null, // index into astNodes
140 };
141
142 const Type = struct {
143 kind: u32, // index into typeKinds
144 name: []const u8,
145 src: ?usize = null, // index into astNodes
146 privDecls: ?[]usize = null, // index into decls
147 pubDecls: ?[]usize = null, // index into decls
148 fields: ?[]WalkResult = null, // (use src->fields to find names)
149 };
150
151 const WalkResult = union(enum) {
152 failure: bool,
153 type: usize, // index in `types`
154 decl_ref: usize, // index in `decls`
155
156 pub fn jsonStringify(
157 self: WalkResult,
158 _: std.json.StringifyOptions,
159 w: anytype,
160 ) !void {
161 switch (self) {
162 .failure => |v| {
163 try w.print(
164 \\{{ "failure":{} }}
165 , .{v});
166 },
167 .type, .decl_ref => |v| {
168 try w.print(
169 \\{{ "{s}":{} }}
170 , .{ @tagName(self), v });
171 },
172
173 // .decl_ref => |v| {
174 // try w.print(
175 // \\{{ "{s}":"{s}" }}
176 // , .{ @tagName(self), v });
177 // },
178 }
179 }
180 };
181};
182
183fn walkInstruction(
184 zir: Zir,
185 gpa: std.mem.Allocator,
186 parent_scope: *Scope,
187 types: *std.ArrayList(DocData.Type),
188 decls: *std.ArrayList(DocData.Decl),
189 ast_nodes: *std.ArrayList(DocData.AstNode),
190 inst_index: usize,
191) error{OutOfMemory}!DocData.WalkResult {
192 const tags = zir.instructions.items(.tag);
193 const data = zir.instructions.items(.data);
194
195 // We assume that the topmost ast_node entry corresponds to our decl
196 const self_ast_node_index = ast_nodes.items.len - 1;
197
198 switch (tags[inst_index]) {
199 else => {
200 std.debug.print(
201 "TODO: implement `walkInstruction` for {s}\n\n",
202 .{@tagName(tags[inst_index])},
203 );
204 return DocData.WalkResult{ .failure = true };
205 },
206 .decl_val => {
207 const str_tok = data[inst_index].str_tok;
208 const decls_slot_index = parent_scope.resolveDeclName(str_tok.start);
209 return DocData.WalkResult{ .decl_ref = decls_slot_index };
210 },
211 .int_type => {
212 const int_type = data[inst_index].int_type;
213 const sign = if (int_type.signedness == .unsigned) "u" else "i";
214 const bits = int_type.bit_count;
215 const name = try std.fmt.allocPrint(gpa, "{s}{}", .{ sign, bits });
216
217 try types.append(.{
218 .kind = @enumToInt(std.builtin.TypeId.Int),
219 .name = name,
220 });
221 return DocData.WalkResult{ .type = types.items.len - 1 };
222 },
223 .block_inline => {
224 const pl_node = data[inst_index].pl_node;
225 const body_len = zir.extra[pl_node.payload_index];
226
227 std.debug.print("body len: {}\n", .{body_len});
228
229 const result_index = inst_index + body_len - 1;
230 return walkInstruction(zir, gpa, parent_scope, types, decls, ast_nodes, result_index);
231 },
232 .extended => {
233 const extended = data[inst_index].extended;
234 switch (extended.opcode) {
235 else => {
236 std.debug.print(
237 "TODO: implement `walkInstruction` (inside .extended case) for {s}\n\n",
238 .{@tagName(extended.opcode)},
239 );
240 return DocData.WalkResult{ .failure = true };
241 },
242 .struct_decl => {
243 var scope: Scope = .{ .parent = parent_scope };
244
245 const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
246 var extra_index: usize = extended.operand;
247
248 const src_node: ?i32 = if (small.has_src_node) blk: {
249 const src_node = @bitCast(i32, zir.extra[extra_index]);
250 extra_index += 1;
251 break :blk src_node;
252 } else null;
253 _ = src_node;
254
255 const body_len = if (small.has_body_len) blk: {
256 const body_len = zir.extra[extra_index];
257 extra_index += 1;
258 break :blk body_len;
259 } else 0;
260
261 const fields_len = if (small.has_fields_len) blk: {
262 const fields_len = zir.extra[extra_index];
263 extra_index += 1;
264 break :blk fields_len;
265 } else 0;
266 _ = fields_len;
267
268 const decls_len = if (small.has_decls_len) blk: {
269 const decls_len = zir.extra[extra_index];
270 extra_index += 1;
271 break :blk decls_len;
272 } else 0;
273
274 var decl_indexes = std.ArrayList(usize).init(gpa);
275 var priv_decl_indexes = std.ArrayList(usize).init(gpa);
276
277 const decls_first_index = decls.items.len;
278 // Decl name lookahead for reserving slots in `scope` (and `decls`).
279 // Done to make sure that all decl refs can be resolved correctly,
280 // even if we haven't fully analyzed the decl yet.
281 {
282 var it = zir.declIterator(@intCast(u32, inst_index));
283 try decls.resize(decls_first_index + it.decls_len);
284 var decls_slot_index = decls_first_index;
285 while (it.next()) |d| : (decls_slot_index += 1) {
286 const decl_name_index = zir.extra[d.sub_index + 5];
287 try scope.insertDeclRef(gpa, decl_name_index, decls_slot_index);
288 }
289 }
290
291 extra_index = try walkDecls(
292 zir,
293 gpa,
294 &scope,
295 decls,
296 decls_first_index,
297 decls_len,
298 &decl_indexes,
299 &priv_decl_indexes,
300 types,
301 ast_nodes,
302 extra_index,
303 );
304
305 // const body = zir.extra[extra_index..][0..body_len];
306 extra_index += body_len;
307
308 var field_type_indexes = std.ArrayList(DocData.WalkResult).init(gpa);
309 var field_name_indexes = std.ArrayList(usize).init(gpa);
310 try collectFieldInfo(
311 zir,
312 gpa,
313 &scope,
314 types,
315 decls,
316 fields_len,
317 &field_type_indexes,
318 &field_name_indexes,
319 ast_nodes,
320 extra_index,
321 );
322
323 ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
324
325 try types.append(.{
326 .kind = @enumToInt(std.builtin.TypeId.Struct),
327 .name = "todo_name",
328 .src = self_ast_node_index,
329 .privDecls = priv_decl_indexes.items,
330 .pubDecls = decl_indexes.items,
331 .fields = field_type_indexes.items,
332 });
333
334 return DocData.WalkResult{ .type = types.items.len - 1 };
335 },
336 }
337 },
338 }
339}
340
341fn walkDecls(
342 zir: Zir,
343 gpa: std.mem.Allocator,
344 scope: *Scope,
345 decls: *std.ArrayList(DocData.Decl),
346 decls_first_index: usize,
347 decls_len: u32,
348 decl_indexes: *std.ArrayList(usize),
349 priv_decl_indexes: *std.ArrayList(usize),
350 types: *std.ArrayList(DocData.Type),
351 ast_nodes: *std.ArrayList(DocData.AstNode),
352 extra_start: usize,
353) error{OutOfMemory}!usize {
354 const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable;
355 var extra_index = extra_start + bit_bags_count;
356 var bit_bag_index: usize = extra_start;
357 var cur_bit_bag: u32 = undefined;
358 var decl_i: u32 = 0;
359
360 while (decl_i < decls_len) : (decl_i += 1) {
361 const decls_slot_index = decls_first_index + decl_i;
362
363 if (decl_i % 8 == 0) {
364 cur_bit_bag = zir.extra[bit_bag_index];
365 bit_bag_index += 1;
366 }
367 const is_pub = @truncate(u1, cur_bit_bag) != 0;
368 cur_bit_bag >>= 1;
369 const is_exported = @truncate(u1, cur_bit_bag) != 0;
370 cur_bit_bag >>= 1;
371 // const has_align = @truncate(u1, cur_bit_bag) != 0;
372 cur_bit_bag >>= 1;
373 // const has_section_or_addrspace = @truncate(u1, cur_bit_bag) != 0;
374 cur_bit_bag >>= 1;
375
376 // const sub_index = extra_index;
377
378 // const hash_u32s = zir.extra[extra_index..][0..4];
379 extra_index += 4;
380 // const line = zir.extra[extra_index];
381 extra_index += 1;
382 const decl_name_index = zir.extra[extra_index];
383 extra_index += 1;
384 const decl_index = zir.extra[extra_index];
385 extra_index += 1;
386 const doc_comment_index = zir.extra[extra_index];
387 extra_index += 1;
388
389 // const align_inst: Zir.Inst.Ref = if (!has_align) .none else inst: {
390 // const inst = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
391 // extra_index += 1;
392 // break :inst inst;
393 // };
394 // const section_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: {
395 // const inst = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
396 // extra_index += 1;
397 // break :inst inst;
398 // };
399 // const addrspace_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: {
400 // const inst = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
401 // extra_index += 1;
402 // break :inst inst;
403 // };
404
405 // const pub_str = if (is_pub) "pub " else "";
406 // const hash_bytes = @bitCast([16]u8, hash_u32s.*);
407
408 const name: []const u8 = blk: {
409 if (decl_name_index == 0) {
410 break :blk if (is_exported) "usingnamespace" else "comptime";
411 } else if (decl_name_index == 1) {
412 break :blk "test";
413 } else {
414 const raw_decl_name = zir.nullTerminatedString(decl_name_index);
415 if (raw_decl_name.len == 0) {
416 break :blk zir.nullTerminatedString(decl_name_index + 1);
417 } else {
418 break :blk raw_decl_name;
419 }
420 }
421 };
422
423 const doc_comment: ?[]const u8 = if (doc_comment_index != 0)
424 zir.nullTerminatedString(doc_comment_index)
425 else
426 null;
427
428 // astnode
429 const ast_node_index = idx: {
430 const idx = ast_nodes.items.len;
431 try ast_nodes.append(.{
432 .file = 0,
433 .line = 0,
434 .col = 0,
435 .docs = doc_comment,
436 .fields = null, // walkInstruction will fill `fields` if necessary
437 });
438 break :idx idx;
439 };
440
441 const walk_result = try walkInstruction(zir, gpa, scope, types, decls, ast_nodes, decl_index);
442 const type_index = walk_result.type;
443
444 if (is_pub) {
445 try decl_indexes.append(decls_slot_index);
446 } else {
447 try priv_decl_indexes.append(decls_slot_index);
448 }
449
450 decls.items[decls_slot_index] = .{
451 .name = name,
452 .src = ast_node_index,
453 .type = 0,
454 .value = type_index,
455 .kind = "const", // find where this information can be found
456 };
457 }
458
459 return extra_index;
460}
461
462fn collectFieldInfo(
463 zir: Zir,
464 gpa: std.mem.Allocator,
465 scope: *Scope,
466 types: *std.ArrayList(DocData.Type),
467 decls: *std.ArrayList(DocData.Decl),
468 fields_len: usize,
469 field_type_indexes: *std.ArrayList(DocData.WalkResult),
470 field_name_indexes: *std.ArrayList(usize),
471 ast_nodes: *std.ArrayList(DocData.AstNode),
472 ei: usize,
473) !void {
474 if (fields_len == 0) return;
475 var extra_index = ei;
476
477 const bits_per_field = 4;
478 const fields_per_u32 = 32 / bits_per_field;
479 const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
480 var bit_bag_index: usize = extra_index;
481 extra_index += bit_bags_count;
482
483 var cur_bit_bag: u32 = undefined;
484 var field_i: u32 = 0;
485 while (field_i < fields_len) : (field_i += 1) {
486 if (field_i % fields_per_u32 == 0) {
487 cur_bit_bag = zir.extra[bit_bag_index];
488 bit_bag_index += 1;
489 }
490 // const has_align = @truncate(u1, cur_bit_bag) != 0;
491 cur_bit_bag >>= 1;
492 // const has_default = @truncate(u1, cur_bit_bag) != 0;
493 cur_bit_bag >>= 1;
494 // const is_comptime = @truncate(u1, cur_bit_bag) != 0;
495 cur_bit_bag >>= 1;
496 const unused = @truncate(u1, cur_bit_bag) != 0;
497 cur_bit_bag >>= 1;
498 _ = unused;
499
500 const field_name = zir.nullTerminatedString(zir.extra[extra_index]);
501 extra_index += 1;
502 const field_type = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
503 extra_index += 1;
504 const doc_comment_index = zir.extra[extra_index];
505 extra_index += 1;
506
507 // type
508 {
509 switch (field_type) {
510 .void_type => {
511 try field_type_indexes.append(.{ .type = types.items.len });
512 try types.append(.{
513 .kind = @enumToInt(std.builtin.TypeId.Void),
514 .name = "void",
515 });
516 },
517 .usize_type => {
518 try field_type_indexes.append(.{ .type = types.items.len });
519 try types.append(.{
520 .kind = @enumToInt(std.builtin.TypeId.Int),
521 .name = "usize",
522 });
523 },
524
525 else => {
526 const enum_value = @enumToInt(field_type);
527 if (enum_value < Zir.Inst.Ref.typed_value_map.len) {
528 std.debug.print(
529 "TODO: handle ref type: {s}",
530 .{@tagName(field_type)},
531 );
532 try field_type_indexes.append(DocData.WalkResult{ .failure = true });
533 } else {
534 const zir_index = enum_value - Zir.Inst.Ref.typed_value_map.len;
535 const walk_result = try walkInstruction(zir, gpa, scope, types, decls, ast_nodes, zir_index);
536 try field_type_indexes.append(walk_result);
537 }
538 },
539 }
540 }
541
542 // ast node
543 {
544 try field_name_indexes.append(ast_nodes.items.len);
545 const doc_comment: ?[]const u8 = if (doc_comment_index != 0)
546 zir.nullTerminatedString(doc_comment_index)
547 else
548 null;
549 try ast_nodes.append(.{
550 .name = field_name,
551 .docs = doc_comment,
552 });
553 }
554 }
555}
src/Compilation.zig+8
...@@ -34,6 +34,7 @@ const ThreadPool = @import("ThreadPool.zig");...@@ -34,6 +34,7 @@ const ThreadPool = @import("ThreadPool.zig");
34const WaitGroup = @import("WaitGroup.zig");34const WaitGroup = @import("WaitGroup.zig");
35const libtsan = @import("libtsan.zig");35const libtsan = @import("libtsan.zig");
36const Zir = @import("Zir.zig");36const Zir = @import("Zir.zig");
37const Autodoc = @import("Autodoc.zig");
37const Color = @import("main.zig").Color;38const Color = @import("main.zig").Color;
3839
39/// General-purpose allocator. Used for both temporary and long-term storage.40/// General-purpose allocator. Used for both temporary and long-term storage.
...@@ -2866,6 +2867,13 @@ pub fn performAllTheWork(...@@ -2866,6 +2867,13 @@ pub fn performAllTheWork(
2866 }2867 }
2867 }2868 }
28682869
2870 if (comp.emit_docs) |doc_location| {
2871 if (comp.bin_file.options.module) |module| {
2872 var autodoc = Autodoc.init(module, doc_location);
2873 try autodoc.generateZirData();
2874 }
2875 }
2876
2869 if (!use_stage1) {2877 if (!use_stage1) {
2870 const outdated_and_deleted_decls_frame = tracy.namedFrame("outdated_and_deleted_decls");2878 const outdated_and_deleted_decls_frame = tracy.namedFrame("outdated_and_deleted_decls");
2871 defer outdated_and_deleted_decls_frame.end();2879 defer outdated_and_deleted_decls_frame.end();