Skip to content

Commit 3dad123

Browse files
committed
Move ABI export functionality to abi_export.jl
1 parent c0e1c3e commit 3dad123

File tree

2 files changed

+224
-222
lines changed

2 files changed

+224
-222
lines changed

contrib/juliac/abi_export.jl

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
const C_friendly_types = Base.IdSet{Any}([ # a few of these are redundant to make it easier to maintain
2+
Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float32, Float64, Bool,
3+
Cvoid, Cint, Cshort, Clong, Cuint, Cushort, Culong, Cssize_t, Csize_t,
4+
Cchar, Cwchar_t, Cstring, Cwstring,
5+
RawFD,
6+
])
7+
8+
function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::DataType))
9+
T in types && return types
10+
while T.name === Ptr.body.name
11+
push!(types, T)
12+
T = T.parameters[1] # unwrap Ptr{...}
13+
T in types && return types
14+
end
15+
if T.name.module === Core && T C_friendly_types
16+
error("invalid type for juliac: ", T) # exclude internals (they may change)
17+
end
18+
push!(types, T)
19+
for list in (T.parameters, fieldtypes(T))
20+
for S in list
21+
recursively_add_types!(types, S)
22+
end
23+
end
24+
return types
25+
end
26+
27+
struct TypeEmitter
28+
io::IO
29+
type_ids::IdDict{Any,Int}
30+
end
31+
32+
function escape_string_json(s::AbstractString)
33+
iob = IOBuffer()
34+
print(iob, '"')
35+
for c in s
36+
if c == '"'
37+
print(iob, "\\\"")
38+
elseif c == '\\'
39+
print(iob, "\\\\")
40+
elseif c == '\b'
41+
print(iob, "\\b")
42+
elseif c == '\f'
43+
print(iob, "\\f")
44+
elseif c == '\n'
45+
print(iob, "\\n")
46+
elseif c == '\r'
47+
print(iob, "\\r")
48+
elseif c == '\t'
49+
print(iob, "\\t")
50+
elseif '\x00' <= c <= '\x1f'
51+
print(iob, "\\u", lpad(string(UInt16(c), base=16), 4, '0'))
52+
else
53+
@assert isvalid(c) "invalid unicode character"
54+
print(iob, c)
55+
end
56+
end
57+
print(iob, '"')
58+
return String(take!(iob))
59+
end
60+
61+
function type_name_json(@nospecialize(dt::DataType))
62+
return escape_string_json(repr(dt))
63+
end
64+
65+
function field_name_json(@nospecialize(dt::DataType), field::Int)
66+
name = String(fieldname(dt, field))
67+
return escape_string_json(name)
68+
end
69+
70+
function emit_pointer_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0)
71+
pointee_type_id = ctx.type_ids[dt.parameters[1]]
72+
let indented_println(args...) = println(ctx.io, " " ^ indent, args...)
73+
indented_println("{")
74+
indented_println(" \"id\": ", ctx.type_ids[dt], ",")
75+
indented_println(" \"kind\": \"pointer\",")
76+
indented_println(" \"name\": ", type_name_json(dt), ",")
77+
indented_println(" \"pointee\": ", pointee_type_id)
78+
print(ctx.io, " " ^ indent, "}")
79+
end
80+
end
81+
82+
function emit_field_info!(ctx::TypeEmitter, @nospecialize(dt::DataType), field::Int; indent::Int = 0)
83+
desc = Base.DataTypeFieldDesc(dt)[field]
84+
type_id = ctx.type_ids[fieldtype(dt, field)]
85+
let indented_println(args...) = println(ctx.io, " " ^ indent, args...)
86+
indented_println("{")
87+
indented_println(" \"name\": ", field_name_json(dt, field), ",")
88+
indented_println(" \"offset\": ", desc.offset, ",")
89+
indented_println(" \"type\": ", type_id)
90+
print(ctx.io, " " ^ indent, "}")
91+
end
92+
end
93+
94+
function emit_struct_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0)
95+
type_id = ctx.type_ids[dt]
96+
let indented_println(args...) = println(ctx.io, " " ^ indent, args...)
97+
indented_println("{")
98+
indented_println(" \"id\": ", type_id, ",")
99+
indented_println(" \"kind\": \"struct\",")
100+
indented_println(" \"name\": ", type_name_json(dt), ",")
101+
indented_println(" \"size\": ", Core.sizeof(dt), ",")
102+
indented_println(" \"alignment\": ", Base.datatype_alignment(dt), ",")
103+
indented_println(" \"fields\": [")
104+
for i = 1:Base.datatype_nfields(dt)
105+
emit_field_info!(ctx, dt, i; indent = indent + 4)
106+
println(ctx.io, i == Base.datatype_nfields(dt) ? "" : ",")
107+
end
108+
indented_println(" ]")
109+
print(ctx.io, " " ^ indent, "}")
110+
end
111+
end
112+
113+
function emit_primitive_type!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0)
114+
type_id = ctx.type_ids[dt]
115+
let indented_println(args...) = println(ctx.io, " " ^ indent, args...)
116+
indented_println("{")
117+
indented_println(" \"id\": ", type_id, ",")
118+
indented_println(" \"kind\": \"primitive\",")
119+
indented_println(" \"name\": ", type_name_json(dt), ",")
120+
indented_println(" \"size\": ", Core.sizeof(dt), ",")
121+
indented_println(" \"alignment\": ", Base.datatype_alignment(dt))
122+
print(ctx.io, " " ^ indent, "}")
123+
end
124+
end
125+
126+
function emit_type_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0)
127+
if dt.name === Ptr.body.name
128+
emit_pointer_info!(ctx, dt; indent)
129+
elseif Base.isprimitivetype(dt)
130+
emit_primitive_type!(ctx, dt; indent)
131+
else
132+
emit_struct_info!(ctx, dt; indent)
133+
end
134+
end
135+
136+
function emit_method_info!(ctx::TypeEmitter, method::Core.Method; indent::Int = 0)
137+
(rt, sig) = method.ccallable
138+
(name, symbol) = let
139+
symbol = length(method.ccallable) > 2 ? Symbol(method.ccallable[3]) : method.name
140+
iob = IOBuffer()
141+
print(IOContext(iob, :print_method_signature_only => true), method)
142+
str = String(take!(iob))
143+
if symbol !== method.name && startswith(str, String(method.name))
144+
# Make a best-effort attempt to use the exported name
145+
#
146+
# Note: the `startswith` check is to make sure we support 'functor's in arg0,
147+
# which Base.@ccallable supports as long as they are singletons.
148+
str = replace(str, String(method.name) => String(symbol); count = 1)
149+
end
150+
(str, String(symbol))
151+
end
152+
153+
argnames = String.(Base.method_argnames(method))
154+
let indented_println(args...) = println(ctx.io, " " ^ indent, args...)
155+
indented_println("{")
156+
indented_println(" \"symbol\": ", escape_string_json(symbol), ",")
157+
indented_println(" \"name\": ", escape_string_json(name), ",")
158+
indented_println(" \"arguments\": [")
159+
for i in 2:length(sig.parameters)
160+
indented_println(" {")
161+
indented_println(" \"name\": ", escape_string_json(argnames[i]), ",")
162+
indented_println(" \"type\": ", ctx.type_ids[sig.parameters[i]])
163+
indented_println(" }", i == length(sig.parameters) ? "" : ",")
164+
end
165+
indented_println(" ],")
166+
indented_println(" \"returns\": {")
167+
indented_println(" \"type\": ", ctx.type_ids[rt])
168+
indented_println(" }")
169+
print(ctx.io, " " ^ indent, "}")
170+
end
171+
end
172+
173+
function emit_abi_info!(ctx::TypeEmitter, exported::Vector{Core.Method}, types::IdSet{DataType})
174+
println(ctx.io, "{")
175+
176+
# assign an ID to each type, so that we can refer to them
177+
for (i, T) in enumerate(types)
178+
ctx.type_ids[T] = i
179+
end
180+
181+
# print exported functions
182+
println(ctx.io, " \"functions\": [")
183+
for (i, method) in enumerate(exported)
184+
emit_method_info!(ctx, method; indent = 4)
185+
println(ctx.io, i == length(exported) ? "" : ",")
186+
end
187+
println(ctx.io, " ],")
188+
189+
# print type / structure information
190+
println(ctx.io, " \"types\": [")
191+
for (i, T) in enumerate(types)
192+
emit_type_info!(ctx, T; indent = 4)
193+
println(ctx.io, i == length(types) ? "" : ",")
194+
end
195+
println(ctx.io, " ]")
196+
197+
println(ctx.io, "}")
198+
end
199+
200+
function write_abi_metadata(io::IO)
201+
types = Base.IdSet{DataType}()
202+
203+
# discover all exported methods + any types they reference
204+
exported = Core.Method[]
205+
Base.visit(Core.GlobalMethods) do method
206+
if isdefined(method, :ccallable)
207+
push!(exported, method)
208+
(rt, sig) = method.ccallable
209+
for T in sig.parameters[2:end]
210+
recursively_add_types!(types, T)
211+
end
212+
recursively_add_types!(types, rt)
213+
end
214+
end
215+
216+
# print the discovered ABI info
217+
ctx = TypeEmitter(io, IdDict{Any,Int}())
218+
emit_abi_info!(ctx, exported, types)
219+
end
220+

0 commit comments

Comments
 (0)