Skip to content

Commit e9edddb

Browse files
authored
Add the ability to dump type information to a JSON file (#16027)
If the environment variable `CRYSTAL_DUMP_TYPE_INFO` is set, at build time the compiler will emit a bunch of type information to a JSON file at that path. The JSON looks something like: ```json { "types": [ { "name": "Regex", "id": 46, "min_subtype_id": 46, "supertype_id": 188, "has_inner_pointers": true, "size": 8, "align": 8, "instance_size": 56, "instance_align": 8, "instance_vars": [ { "name": "@re", "type_name": "Pointer(LibPCRE2::Code)", "offset": 8, "size": 8 }, { "name": "@jit", "type_name": "Bool", "offset": 16, "size": 1 }, { "name": "@source", "type_name": "String", "offset": 24, "size": 8 }, { "name": "@match_data", "type_name": "Crystal::ThreadLocalValue(Pointer(LibPCRE2::MatchData))", "offset": 32, "size": 16 }, { "name": "@options", "type_name": "Regex::Options", "offset": 48, "size": 8 } ] } ] } ``` At the moment, this is intended to be an internal tool that supplements the similarly named `CRYSTAL_DUMP_TYPE_ID` environment variable. I originally made this to generate human-readable reports from [GC heap dumps](crystal-lang/perf-tools#30), but there are probably other good uses like enhancing the debugger support scripts.
1 parent e4fbb93 commit e9edddb

File tree

2 files changed

+133
-22
lines changed

2 files changed

+133
-22
lines changed

src/compiler/crystal/codegen/codegen.cr

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -556,29 +556,10 @@ module Crystal
556556
mod.verify
557557
end
558558

559-
dump_type_id if ENV["CRYSTAL_DUMP_TYPE_ID"]? == "1"
560-
end
561-
562-
private def dump_type_id
563-
ids = @program.llvm_id.@ids.to_a
564-
ids.sort_by! { |_, (min, max)| {min, -max} }
565-
566-
puts "CRYSTAL_DUMP_TYPE_ID"
567-
parent_ids = [] of {Int32, Int32}
568-
ids.each do |type, (min, max)|
569-
while parent_id = parent_ids.last?
570-
break if min >= parent_id[0] && max <= parent_id[1]
571-
parent_ids.pop
572-
end
573-
indent = " " * (2 * parent_ids.size)
574-
575-
show_generic_args = type.is_a?(GenericInstanceType) ||
576-
type.is_a?(GenericClassInstanceMetaclassType) ||
577-
type.is_a?(GenericModuleInstanceMetaclassType)
578-
puts "#{indent}{#{min} - #{max}}: #{type.to_s(generic_args: show_generic_args)}"
579-
parent_ids << {min, max}
559+
if type_info_path = ENV["CRYSTAL_DUMP_TYPE_INFO"]?.presence
560+
dump_type_info(type_info_path)
580561
end
581-
puts
562+
dump_type_id if ENV["CRYSTAL_DUMP_TYPE_ID"]? == "1"
582563
end
583564

584565
def visit(node : Annotation)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
module Crystal
2+
class CodeGenVisitor
3+
private def dump_type_info(path)
4+
ids = @program.llvm_id.@ids.to_a
5+
ids.sort_by! { |_, (_, id)| id }
6+
7+
File.open(path, "w") do |f|
8+
JSON.build(f) do |j|
9+
j.object do
10+
j.field "types" do
11+
j.array do
12+
ids.each do |type, (min_subtype_id, id)|
13+
dump_single_type(j, type, min_subtype_id, id)
14+
end
15+
end
16+
end
17+
end
18+
end
19+
end
20+
end
21+
22+
private def dump_single_type(j : JSON::Builder, type, min_subtype_id, id)
23+
unless type.is_a?(GenericClassType)
24+
llvm_type = llvm_type(type)
25+
if type.is_a?(InstanceVarContainer)
26+
llvm_struct_type = llvm_struct_type(type)
27+
end
28+
end
29+
30+
j.object do
31+
j.field "name" do
32+
j.string do |io|
33+
type.to_s_with_options(io, codegen: true, generic_args: true)
34+
end
35+
end
36+
j.field "id", id
37+
j.field "min_subtype_id", min_subtype_id
38+
if supertype = type.superclass
39+
j.field "supertype_id", @program.llvm_id.type_id(supertype)
40+
end
41+
if type.metaclass?
42+
j.field "instance_type_id", @program.llvm_id.type_id(type.instance_type)
43+
end
44+
45+
has_inner_pointers =
46+
if type.struct?
47+
type.has_inner_pointers?
48+
else
49+
type.is_a?(InstanceVarContainer) && type.all_instance_vars.each_value.any? &.type.has_inner_pointers?
50+
end
51+
j.field "has_inner_pointers", has_inner_pointers
52+
53+
if llvm_type
54+
j.field "size", @llvm_typer.size_of(llvm_type)
55+
j.field "align", @llvm_typer.align_of(llvm_type)
56+
end
57+
58+
if llvm_struct_type
59+
unless type.struct?
60+
j.field "instance_size", @llvm_typer.size_of(llvm_struct_type)
61+
j.field "instance_align", @llvm_typer.align_of(llvm_struct_type)
62+
end
63+
64+
if type.allows_instance_vars?
65+
j.field "instance_vars" do
66+
j.array do
67+
type.all_instance_vars.each do |name, ivar|
68+
ivar_offset, ivar_size = ivar_offset_and_size(type, llvm_struct_type, name, ivar.type)
69+
j.object do
70+
j.field "name", name
71+
j.field "type_name" do
72+
j.string do |io|
73+
ivar.type.to_s_with_options(io, codegen: true)
74+
end
75+
end
76+
j.field "offset", ivar_offset
77+
j.field "size", ivar_size
78+
end
79+
end
80+
end
81+
end
82+
end
83+
end
84+
end
85+
end
86+
87+
private def ivar_offset_and_size(type, llvm_type, ivar_name, ivar_type) : {UInt64, UInt64}
88+
if type.extern_union? || type.is_a?(StaticArrayInstanceType)
89+
return 0_u64, @llvm_typer.size_of(llvm_type)
90+
end
91+
92+
element_index = type.index_of_instance_var(ivar_name).not_nil!
93+
element_index += 1 unless type.struct?
94+
95+
ivar_llvm_type =
96+
if type.extern?
97+
@llvm_typer.llvm_embedded_c_type(ivar_type, wants_size: true)
98+
else
99+
@llvm_typer.llvm_embedded_type(ivar_type, wants_size: true)
100+
end
101+
102+
{
103+
@llvm_typer.offset_of(llvm_type, element_index),
104+
@llvm_typer.size_of(ivar_llvm_type),
105+
}
106+
end
107+
108+
private def dump_type_id
109+
ids = @program.llvm_id.@ids.to_a
110+
ids.sort_by! { |_, (min, max)| {min, -max} }
111+
112+
puts "CRYSTAL_DUMP_TYPE_ID"
113+
parent_ids = [] of {Int32, Int32}
114+
ids.each do |type, (min, max)|
115+
while parent_id = parent_ids.last?
116+
break if min >= parent_id[0] && max <= parent_id[1]
117+
parent_ids.pop
118+
end
119+
indent = " " * (2 * parent_ids.size)
120+
121+
show_generic_args = type.is_a?(GenericInstanceType) ||
122+
type.is_a?(GenericClassInstanceMetaclassType) ||
123+
type.is_a?(GenericModuleInstanceMetaclassType)
124+
puts "#{indent}{#{min} - #{max}}: #{type.to_s(generic_args: show_generic_args)}"
125+
parent_ids << {min, max}
126+
end
127+
puts
128+
end
129+
end
130+
end

0 commit comments

Comments
 (0)