-
Notifications
You must be signed in to change notification settings - Fork 0
Adjust to new JSON ABI format #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
9b6ffe9
3f25fc1
52058dc
b3278d0
4b9388f
eedd3d8
c5d0dd0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,12 @@ authors = ["Tim Holy <[email protected]> and contributors"] | |
|
||
[deps] | ||
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6" | ||
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" | ||
OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" | ||
|
||
[compat] | ||
Graphs = "1" | ||
JSON = "0.21.4" | ||
OrderedCollections = "1.8.1" | ||
julia = "1.12" | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,10 @@ module JuliaLibWrapping | |
using OrderedCollections | ||
using Graphs | ||
|
||
export parselog, wrapper | ||
export CProject | ||
export import_abi_info, wrapper | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an improvement in naming, thanks |
||
export CProject, ABIInfo | ||
|
||
include("parselog.jl") | ||
include("abi_import.jl") | ||
include("c.jl") | ||
|
||
end |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,209 @@ | ||||||
using JSON | ||||||
|
||||||
struct FieldDesc | ||||||
name::String | ||||||
type::Int | ||||||
offset::Int | ||||||
end | ||||||
|
||||||
struct PrimitiveTypeDesc | ||||||
# TODO: support custom primitive types? | ||||||
name::String | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it make sense to include |
||||||
end | ||||||
|
||||||
struct StructDesc | ||||||
name::String | ||||||
size::Int | ||||||
alignment::Int | ||||||
fields::Vector{FieldDesc} | ||||||
end | ||||||
|
||||||
struct PointerDesc | ||||||
name::String | ||||||
pointee_type::Int # type of pointee | ||||||
end | ||||||
|
||||||
struct ArgDesc | ||||||
name::String | ||||||
type::Int | ||||||
isva::Bool # is this a varargs argument? | ||||||
end | ||||||
|
||||||
struct MethodDesc | ||||||
symbol::String # exported C symbol | ||||||
name::String # full method name w/ args | ||||||
return_type::Int | ||||||
args::Vector{ArgDesc} | ||||||
end | ||||||
|
||||||
const TypeDesc = Union{StructDesc, PointerDesc, PrimitiveTypeDesc} | ||||||
|
||||||
function from_json(::Type{PrimitiveTypeDesc}, type::Dict{String,Any}) | ||||||
return PrimitiveTypeDesc(type["name"]) | ||||||
end | ||||||
|
||||||
function from_json(::Type{StructDesc}, type::Dict{String, Any}) | ||||||
return StructDesc( | ||||||
type["name"], | ||||||
type["size"], | ||||||
type["alignment"], | ||||||
FieldDesc[ | ||||||
FieldDesc( | ||||||
field["name"], | ||||||
field["type"], | ||||||
field["offset"] | ||||||
) | ||||||
for field in type["fields"] | ||||||
] | ||||||
) | ||||||
end | ||||||
|
||||||
function from_json(::Type{PointerDesc}, json::Dict{String, Any}) | ||||||
return PointerDesc(json["name"], json["pointee"]) | ||||||
end | ||||||
|
||||||
function from_json(::Type{TypeDesc}, json::Dict{String, Any}) | ||||||
kind = json["kind"]::String | ||||||
if kind === "primitive" | ||||||
return from_json(PrimitiveTypeDesc, json) | ||||||
elseif kind === "struct" | ||||||
return from_json(StructDesc, json) | ||||||
elseif kind === "pointer" | ||||||
return from_json(PointerDesc, json) | ||||||
else # unreachable | ||||||
@assert false "unexpected kind '$(json["kind"])' in type metadata" | ||||||
end | ||||||
end | ||||||
|
||||||
function from_json(::Type{MethodDesc}, method::Dict{String, Any}) | ||||||
return MethodDesc( | ||||||
method["symbol"], | ||||||
method["name"], | ||||||
method["returns"]["type"], | ||||||
ArgDesc[ | ||||||
ArgDesc( | ||||||
arg["name"], | ||||||
arg["type"], | ||||||
#= isva =# false | ||||||
) | ||||||
for arg in method["arguments"] | ||||||
], | ||||||
) | ||||||
end | ||||||
|
||||||
function build_type_graph(typedescs::OrderedDict{Int, TypeDesc}; | ||||||
pointer_filter::Function) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason not to make |
||||||
g = SimpleDiGraph(length(typedescs)) | ||||||
for (id, desc) in pairs(typedescs) | ||||||
if desc isa StructDesc | ||||||
for field in desc.fields | ||||||
add_edge!(g, field.type, id) | ||||||
end | ||||||
elseif desc isa PointerDesc | ||||||
if pointer_filter(id) | ||||||
add_edge!(g, desc.pointee_type, id) | ||||||
else | ||||||
# pointee types don't affect data layout (no edges to add) | ||||||
end | ||||||
elseif desc isa PrimitiveTypeDesc | ||||||
# dependency is tracked from the `struct` side | ||||||
end | ||||||
end | ||||||
return g | ||||||
end | ||||||
|
||||||
""" | ||||||
sort_declarations!(typedescs) -> forward_declarations | ||||||
|
||||||
Sort `typedescs` w.r.t. type-dependencies (e.g. Type A using Type B in a field), | ||||||
so that a type-descriptor always appears after any dependencies. Sort is performed | ||||||
in-place. | ||||||
|
||||||
Returns indices of all types that could not be sorted (due to recursive types, e.g. | ||||||
a linked list in C). In C, these are the definitions that must be forward-declared. | ||||||
""" | ||||||
function sort_declarations!(typedescs::OrderedDict{Int, TypeDesc}) | ||||||
# First we have to identify the parts of the graph where we have type-recursion and | ||||||
# therefore have to use forward-declarations instead of just sorting declarations. | ||||||
recursive_types = BitSet() | ||||||
|
||||||
full_type_graph = build_type_graph(typedescs; pointer_filter = (id)->true) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
for scc in strongly_connected_components(full_type_graph) | ||||||
length(scc) == 1 && continue | ||||||
for type_id in scc | ||||||
push!(recursive_types, type_id) | ||||||
end | ||||||
end | ||||||
|
||||||
# Now that we know the recursive types, we can restrict them from treating their pointers | ||||||
# as a type dependency and re-build a type-graph that is acyclic (these deleted dependencies | ||||||
# will later become a forward-declaration) | ||||||
type_graph = build_type_graph(typedescs; pointer_filter = (id)->!in(id, recursive_types)) | ||||||
|
||||||
# We now have an acyclic type-dependency graph, and we have to emit our declarations in a | ||||||
# topological order. This guarantees that if a type A has a dependency on type B, type B | ||||||
# will appear before type A. | ||||||
order_to_emit = zeros(length(typedescs)) | ||||||
for (pos, desc_id) in enumerate(topological_sort(type_graph)) | ||||||
# poor man's permute!(typedescs, topological_sort(g)) | ||||||
order_to_emit[desc_id] = pos | ||||||
end | ||||||
sort!(typedescs; by=(id)->order_to_emit[id]) | ||||||
|
||||||
# Finally, we just need to compute the set of required forward declarations. | ||||||
forwarddecls = BitSet() | ||||||
for id in recursive_types | ||||||
desc = typedescs[id] | ||||||
desc isa StructDesc || continue | ||||||
for field in desc.fields | ||||||
dep = field.type | ||||||
while typedescs[dep] isa PointerDesc | ||||||
# 'dereference' any pointer type | ||||||
dep = (typedescs[dep]::PointerDesc).pointee_type | ||||||
end | ||||||
order_to_emit[id] ≥ order_to_emit[dep] && continue | ||||||
# this struct refers (through 1 or more pointers) to a type that will | ||||||
# be emitted after it, so that type must be forward-declared. | ||||||
push!(forwarddecls, dep) | ||||||
end | ||||||
end | ||||||
|
||||||
return forwarddecls | ||||||
end | ||||||
|
||||||
struct ABIInfo | ||||||
# A map from `type_id` to `type_descriptor`, sorted by declaration order. | ||||||
typeinfo::OrderedDict{Int, TypeDesc} | ||||||
# Indexes into `types`, indicates which types must be forward-declared for C. | ||||||
forward_declared::BitSet | ||||||
# A vector of exposed functions from the imported ABI | ||||||
entrypoints::Vector{MethodDesc} | ||||||
end | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would this benefit from having a |
||||||
|
||||||
""" | ||||||
entrypoints, typedescs, forward_declarations = parselog(filename::String) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
Extract the signatures of the entrypoints and types from an exported ABI info (JSON) file. | ||||||
""" | ||||||
function import_abi_info(filename::String) | ||||||
abi_info = JSON.Parser.parsefile(filename) | ||||||
|
||||||
# Extract all the type descriptors | ||||||
typedescs = OrderedDict{Int, TypeDesc}() | ||||||
for type in abi_info["types"] | ||||||
id = Int(type["id"]::Int64) | ||||||
typedescs[id] = from_json(TypeDesc, type) | ||||||
end | ||||||
|
||||||
# Then collect the methods | ||||||
entrypoints = MethodDesc[] | ||||||
for method in abi_info["functions"] | ||||||
push!(entrypoints, from_json(MethodDesc, method)) | ||||||
end | ||||||
|
||||||
forward_declared = sort_declarations!(typedescs) | ||||||
|
||||||
return ABIInfo(typedescs, forward_declared, entrypoints) | ||||||
end | ||||||
|
||||||
import_abi_info(filename::AbstractString) = import_abi_info(String(filename)::String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
JSON3/StructTypes might automate some of the json->julia parsing, but I am not sure there would be any real gain and this seems fine to me.