autodoc: init work

This commit is contained in:
Loris Cro 2022-01-28 22:50:03 +01:00 committed by Andrew Kelley
parent 0efc6a35be
commit 652e13e7c0
2 changed files with 563 additions and 0 deletions

555
src/Autodoc.zig Normal file
View File

@ -0,0 +1,555 @@
const std = @import("std");
const Autodoc = @This();
const Compilation = @import("Compilation.zig");
const Module = @import("Module.zig");
const Zir = @import("Zir.zig");
module: *Module,
doc_location: ?Compilation.EmitLoc,
pub fn init(m: *Module, dl: ?Compilation.EmitLoc) Autodoc {
return .{
.doc_location = dl,
.module = m,
};
}
pub fn generateZirData(self: Autodoc) !void {
const gpa = self.module.gpa;
std.debug.print("yay, you called me!\n", .{});
if (self.doc_location) |loc| {
if (loc.directory) |dir| {
if (dir.path) |path| {
std.debug.print("path: {s}\n", .{path});
}
}
std.debug.print("basename: {s}\n", .{loc.basename});
}
// const root_file_path = self.module.main_pkg.root_src_path;
const root_file_path = "/home/kristoff/test/test.zig";
const zir = self.module.import_table.get(root_file_path).?.zir;
var types = std.ArrayList(DocData.Type).init(gpa);
var decls = std.ArrayList(DocData.Decl).init(gpa);
var ast_nodes = std.ArrayList(DocData.AstNode).init(gpa);
// var decl_map = std.AutoHashMap(Zir.Inst.Index, usize); // values are positions in the `decls` array
try types.append(.{
.kind = 0,
.name = "type",
});
var root_scope: Scope = .{ .parent = null };
try ast_nodes.append(.{ .name = "(root)" });
const main_type_index = try walkInstruction(zir, gpa, &root_scope, &types, &decls, &ast_nodes, Zir.main_struct_inst);
var data = DocData{
.files = &[1][]const u8{root_file_path},
.types = types.items,
.decls = decls.items,
.astNodes = ast_nodes.items,
};
data.packages[0].main = main_type_index.type;
const out = std.io.getStdOut().writer();
out.print("zigAnalysis=", .{}) catch unreachable;
std.json.stringify(
data,
.{
.whitespace = .{},
.emit_null_optional_fields = false,
},
out,
) catch unreachable;
out.print(";", .{}) catch unreachable;
}
const Scope = struct {
parent: ?*Scope,
map: std.AutoHashMapUnmanaged(u32, usize) = .{}, // index into `decls`
/// Assumes all decls in present scope and upper scopes have already
/// been either fully resolved or at least reserved.
pub fn resolveDeclName(self: Scope, string_table_idx: u32) usize {
var cur: ?*const Scope = &self;
return while (cur) |s| : (cur = s.parent) {
break s.map.get(string_table_idx) orelse continue;
} else unreachable;
}
pub fn insertDeclRef(
self: *Scope,
gpa: std.mem.Allocator,
decl_name_index: u32, // decl name
decls_slot_index: usize,
) !void {
try self.map.put(gpa, decl_name_index, decls_slot_index);
}
};
const DocData = struct {
typeKinds: []const []const u8 = std.meta.fieldNames(std.builtin.TypeId),
rootPkg: u32 = 0,
params: struct {
zigId: []const u8 = "arst",
zigVersion: []const u8 = "arst",
target: []const u8 = "arst",
rootName: []const u8 = "arst",
builds: []const struct { target: []const u8 } = &.{
.{ .target = "arst" },
},
} = .{},
packages: [1]Package = .{.{}},
fns: []struct {} = &.{},
errors: []struct {} = &.{},
calls: []struct {} = &.{},
// non-hardcoded stuff
astNodes: []AstNode,
files: []const []const u8,
types: []Type,
decls: []Decl,
const Package = struct {
name: []const u8 = "root",
file: usize = 0, // index into files
main: usize = 0, // index into decls
table: struct { root: usize } = .{
.root = 0,
},
};
const Decl = struct {
name: []const u8,
kind: []const u8, // TODO: where do we find this info?
src: usize, // index into astNodes
type: usize, // index into types
value: usize,
};
const AstNode = struct {
file: usize = 0, // index into files
line: usize = 0,
col: usize = 0,
name: ?[]const u8 = null,
docs: ?[]const u8 = null,
fields: ?[]usize = null, // index into astNodes
};
const Type = struct {
kind: u32, // index into typeKinds
name: []const u8,
src: ?usize = null, // index into astNodes
privDecls: ?[]usize = null, // index into decls
pubDecls: ?[]usize = null, // index into decls
fields: ?[]WalkResult = null, // (use src->fields to find names)
};
const WalkResult = union(enum) {
failure: bool,
type: usize, // index in `types`
decl_ref: usize, // index in `decls`
pub fn jsonStringify(
self: WalkResult,
_: std.json.StringifyOptions,
w: anytype,
) !void {
switch (self) {
.failure => |v| {
try w.print(
\\{{ "failure":{} }}
, .{v});
},
.type, .decl_ref => |v| {
try w.print(
\\{{ "{s}":{} }}
, .{ @tagName(self), v });
},
// .decl_ref => |v| {
// try w.print(
// \\{{ "{s}":"{s}" }}
// , .{ @tagName(self), v });
// },
}
}
};
};
fn walkInstruction(
zir: Zir,
gpa: std.mem.Allocator,
parent_scope: *Scope,
types: *std.ArrayList(DocData.Type),
decls: *std.ArrayList(DocData.Decl),
ast_nodes: *std.ArrayList(DocData.AstNode),
inst_index: usize,
) error{OutOfMemory}!DocData.WalkResult {
const tags = zir.instructions.items(.tag);
const data = zir.instructions.items(.data);
// We assume that the topmost ast_node entry corresponds to our decl
const self_ast_node_index = ast_nodes.items.len - 1;
switch (tags[inst_index]) {
else => {
std.debug.print(
"TODO: implement `walkInstruction` for {s}\n\n",
.{@tagName(tags[inst_index])},
);
return DocData.WalkResult{ .failure = true };
},
.decl_val => {
const str_tok = data[inst_index].str_tok;
const decls_slot_index = parent_scope.resolveDeclName(str_tok.start);
return DocData.WalkResult{ .decl_ref = decls_slot_index };
},
.int_type => {
const int_type = data[inst_index].int_type;
const sign = if (int_type.signedness == .unsigned) "u" else "i";
const bits = int_type.bit_count;
const name = try std.fmt.allocPrint(gpa, "{s}{}", .{ sign, bits });
try types.append(.{
.kind = @enumToInt(std.builtin.TypeId.Int),
.name = name,
});
return DocData.WalkResult{ .type = types.items.len - 1 };
},
.block_inline => {
const pl_node = data[inst_index].pl_node;
const body_len = zir.extra[pl_node.payload_index];
std.debug.print("body len: {}\n", .{body_len});
const result_index = inst_index + body_len - 1;
return walkInstruction(zir, gpa, parent_scope, types, decls, ast_nodes, result_index);
},
.extended => {
const extended = data[inst_index].extended;
switch (extended.opcode) {
else => {
std.debug.print(
"TODO: implement `walkInstruction` (inside .extended case) for {s}\n\n",
.{@tagName(extended.opcode)},
);
return DocData.WalkResult{ .failure = true };
},
.struct_decl => {
var scope: Scope = .{ .parent = parent_scope };
const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
var extra_index: usize = extended.operand;
const src_node: ?i32 = if (small.has_src_node) blk: {
const src_node = @bitCast(i32, zir.extra[extra_index]);
extra_index += 1;
break :blk src_node;
} else null;
_ = src_node;
const body_len = if (small.has_body_len) blk: {
const body_len = zir.extra[extra_index];
extra_index += 1;
break :blk body_len;
} else 0;
const fields_len = if (small.has_fields_len) blk: {
const fields_len = zir.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
_ = fields_len;
const decls_len = if (small.has_decls_len) blk: {
const decls_len = zir.extra[extra_index];
extra_index += 1;
break :blk decls_len;
} else 0;
var decl_indexes = std.ArrayList(usize).init(gpa);
var priv_decl_indexes = std.ArrayList(usize).init(gpa);
const decls_first_index = decls.items.len;
// Decl name lookahead for reserving slots in `scope` (and `decls`).
// Done to make sure that all decl refs can be resolved correctly,
// even if we haven't fully analyzed the decl yet.
{
var it = zir.declIterator(@intCast(u32, inst_index));
try decls.resize(decls_first_index + it.decls_len);
var decls_slot_index = decls_first_index;
while (it.next()) |d| : (decls_slot_index += 1) {
const decl_name_index = zir.extra[d.sub_index + 5];
try scope.insertDeclRef(gpa, decl_name_index, decls_slot_index);
}
}
extra_index = try walkDecls(
zir,
gpa,
&scope,
decls,
decls_first_index,
decls_len,
&decl_indexes,
&priv_decl_indexes,
types,
ast_nodes,
extra_index,
);
// const body = zir.extra[extra_index..][0..body_len];
extra_index += body_len;
var field_type_indexes = std.ArrayList(DocData.WalkResult).init(gpa);
var field_name_indexes = std.ArrayList(usize).init(gpa);
try collectFieldInfo(
zir,
gpa,
&scope,
types,
decls,
fields_len,
&field_type_indexes,
&field_name_indexes,
ast_nodes,
extra_index,
);
ast_nodes.items[self_ast_node_index].fields = field_name_indexes.items;
try types.append(.{
.kind = @enumToInt(std.builtin.TypeId.Struct),
.name = "todo_name",
.src = self_ast_node_index,
.privDecls = priv_decl_indexes.items,
.pubDecls = decl_indexes.items,
.fields = field_type_indexes.items,
});
return DocData.WalkResult{ .type = types.items.len - 1 };
},
}
},
}
}
fn walkDecls(
zir: Zir,
gpa: std.mem.Allocator,
scope: *Scope,
decls: *std.ArrayList(DocData.Decl),
decls_first_index: usize,
decls_len: u32,
decl_indexes: *std.ArrayList(usize),
priv_decl_indexes: *std.ArrayList(usize),
types: *std.ArrayList(DocData.Type),
ast_nodes: *std.ArrayList(DocData.AstNode),
extra_start: usize,
) error{OutOfMemory}!usize {
const bit_bags_count = std.math.divCeil(usize, decls_len, 8) catch unreachable;
var extra_index = extra_start + bit_bags_count;
var bit_bag_index: usize = extra_start;
var cur_bit_bag: u32 = undefined;
var decl_i: u32 = 0;
while (decl_i < decls_len) : (decl_i += 1) {
const decls_slot_index = decls_first_index + decl_i;
if (decl_i % 8 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
const is_pub = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const is_exported = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
// const has_align = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
// const has_section_or_addrspace = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
// const sub_index = extra_index;
// const hash_u32s = zir.extra[extra_index..][0..4];
extra_index += 4;
// const line = zir.extra[extra_index];
extra_index += 1;
const decl_name_index = zir.extra[extra_index];
extra_index += 1;
const decl_index = zir.extra[extra_index];
extra_index += 1;
const doc_comment_index = zir.extra[extra_index];
extra_index += 1;
// const align_inst: Zir.Inst.Ref = if (!has_align) .none else inst: {
// const inst = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
// extra_index += 1;
// break :inst inst;
// };
// const section_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: {
// const inst = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
// extra_index += 1;
// break :inst inst;
// };
// const addrspace_inst: Zir.Inst.Ref = if (!has_section_or_addrspace) .none else inst: {
// const inst = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
// extra_index += 1;
// break :inst inst;
// };
// const pub_str = if (is_pub) "pub " else "";
// const hash_bytes = @bitCast([16]u8, hash_u32s.*);
const name: []const u8 = blk: {
if (decl_name_index == 0) {
break :blk if (is_exported) "usingnamespace" else "comptime";
} else if (decl_name_index == 1) {
break :blk "test";
} else {
const raw_decl_name = zir.nullTerminatedString(decl_name_index);
if (raw_decl_name.len == 0) {
break :blk zir.nullTerminatedString(decl_name_index + 1);
} else {
break :blk raw_decl_name;
}
}
};
const doc_comment: ?[]const u8 = if (doc_comment_index != 0)
zir.nullTerminatedString(doc_comment_index)
else
null;
// astnode
const ast_node_index = idx: {
const idx = ast_nodes.items.len;
try ast_nodes.append(.{
.file = 0,
.line = 0,
.col = 0,
.docs = doc_comment,
.fields = null, // walkInstruction will fill `fields` if necessary
});
break :idx idx;
};
const walk_result = try walkInstruction(zir, gpa, scope, types, decls, ast_nodes, decl_index);
const type_index = walk_result.type;
if (is_pub) {
try decl_indexes.append(decls_slot_index);
} else {
try priv_decl_indexes.append(decls_slot_index);
}
decls.items[decls_slot_index] = .{
.name = name,
.src = ast_node_index,
.type = 0,
.value = type_index,
.kind = "const", // find where this information can be found
};
}
return extra_index;
}
fn collectFieldInfo(
zir: Zir,
gpa: std.mem.Allocator,
scope: *Scope,
types: *std.ArrayList(DocData.Type),
decls: *std.ArrayList(DocData.Decl),
fields_len: usize,
field_type_indexes: *std.ArrayList(DocData.WalkResult),
field_name_indexes: *std.ArrayList(usize),
ast_nodes: *std.ArrayList(DocData.AstNode),
ei: usize,
) !void {
if (fields_len == 0) return;
var extra_index = ei;
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
var bit_bag_index: usize = extra_index;
extra_index += bit_bags_count;
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
while (field_i < fields_len) : (field_i += 1) {
if (field_i % fields_per_u32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
// const has_align = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
// const has_default = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
// const is_comptime = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
const unused = @truncate(u1, cur_bit_bag) != 0;
cur_bit_bag >>= 1;
_ = unused;
const field_name = zir.nullTerminatedString(zir.extra[extra_index]);
extra_index += 1;
const field_type = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]);
extra_index += 1;
const doc_comment_index = zir.extra[extra_index];
extra_index += 1;
// type
{
switch (field_type) {
.void_type => {
try field_type_indexes.append(.{ .type = types.items.len });
try types.append(.{
.kind = @enumToInt(std.builtin.TypeId.Void),
.name = "void",
});
},
.usize_type => {
try field_type_indexes.append(.{ .type = types.items.len });
try types.append(.{
.kind = @enumToInt(std.builtin.TypeId.Int),
.name = "usize",
});
},
else => {
const enum_value = @enumToInt(field_type);
if (enum_value < Zir.Inst.Ref.typed_value_map.len) {
std.debug.print(
"TODO: handle ref type: {s}",
.{@tagName(field_type)},
);
try field_type_indexes.append(DocData.WalkResult{ .failure = true });
} else {
const zir_index = enum_value - Zir.Inst.Ref.typed_value_map.len;
const walk_result = try walkInstruction(zir, gpa, scope, types, decls, ast_nodes, zir_index);
try field_type_indexes.append(walk_result);
}
},
}
}
// ast node
{
try field_name_indexes.append(ast_nodes.items.len);
const doc_comment: ?[]const u8 = if (doc_comment_index != 0)
zir.nullTerminatedString(doc_comment_index)
else
null;
try ast_nodes.append(.{
.name = field_name,
.docs = doc_comment,
});
}
}
}

View File

@ -34,6 +34,7 @@ const ThreadPool = @import("ThreadPool.zig");
const WaitGroup = @import("WaitGroup.zig");
const libtsan = @import("libtsan.zig");
const Zir = @import("Zir.zig");
const Autodoc = @import("Autodoc.zig");
const Color = @import("main.zig").Color;
/// General-purpose allocator. Used for both temporary and long-term storage.
@ -2866,6 +2867,13 @@ pub fn performAllTheWork(
}
}
if (comp.emit_docs) |doc_location| {
if (comp.bin_file.options.module) |module| {
var autodoc = Autodoc.init(module, doc_location);
try autodoc.generateZirData();
}
}
if (!use_stage1) {
const outdated_and_deleted_decls_frame = tracy.namedFrame("outdated_and_deleted_decls");
defer outdated_and_deleted_decls_frame.end();