|
| 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