From ebb3ded935195aaba8276929ddc984ff264591f9 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 27 Jul 2025 02:28:26 -0500 Subject: [PATCH 01/29] juliac: log entrypoints and nonstandard typedefs To facilitate creating bindings for other programming languages, this adds logging to the `juliac` buildscript, optionally outputting a logfile that describes the entrypoints (signatures and return types) and nonstandard typedefs that appear in the entrypoint specifications. --- contrib/juliac/juliac-buildscript.jl | 63 ++++++++++++++++++++++++++++ test/trimming/Makefile | 13 +++--- test/trimming/simplelib.jl | 20 +++++++++ test/trimming/trimming.jl | 17 ++++++++ 4 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 test/trimming/simplelib.jl diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index db1cb75339e4d..190647c8b0c4b 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -27,6 +27,37 @@ if Base.JLOptions().trim != 0 include(joinpath(@__DIR__, "juliac-trim-base.jl")) end +const C_friendly_types = Union{ # a few of these are redundant to make it easier to maintain + Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float32, Float64, Bool, + Cvoid, Cint, Cshort, Clong, Cuint, Cushort, Culong, Cssize_t, Csize_t, + Cchar, Cwchar_t, Cstring, Cwstring, + RawFD, +} + +function is_c_friendly(@nospecialize(T::DataType)) + T <: Ptr && return is_c_friendly(T.parameters[1]) + return T <: C_friendly_types +end + +function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::DataType)) + if !is_c_friendly(T) + T.name.module === Core && error("invalid type for juliac: ", T) # exclude internals (they may change) + push!(types, T) + end + for list in (T.parameters, fieldtypes(T)) + for S in list + recursively_add_types!(types, S) + end + end +end + +function mangle_name(@nospecialize(T::DataType)) + is_c_friendly(T) && return string(T) + pname = isempty(T.parameters) ? String(nameof(T)) : + join(pushfirst!(map(mangle_name, T.parameters), String(nameof(T)), "_")) + return "_" * pname * "_" +end + # Load user code import Base.Experimental.entrypoint @@ -73,6 +104,38 @@ let mod = Base.include(Main, ARGS[1]) if ARGS[3] == "true" ccall(:jl_add_ccallable_entrypoints, Cvoid, ()) end + + # Export info about entrypoints and structs needed to create header files + if length(ARGS) >= 4 + logfile = ARGS[4] + open(logfile, "w") do io + types = Base.IdSet{DataType}() + Base.visit(Core.GlobalMethods) do method + if isdefined(method, :ccallable) + rt, sig = method.ccallable + name = length(method.ccallable) > 2 ? Symbol(method.ccallable[3]) : method.name + Base.show_tuple_as_call(io, name, sig) + println(io, "::", rt) + for T in sig.parameters[2:end] + recursively_add_types!(types, T) + end + end + end + println(io) + for T in types + println(io, mangle_name(T)) + dtfd = Base.DataTypeFieldDesc(T) + local fd + for i = 1:Base.datatype_nfields(T) + fd = dtfd[i] + fn = fieldname(T, i) + ft = fieldtype(T, i) + println(io, " ", fn, "::", mangle_name(ft), "[", fd.offset, "]") + end + println(io, fd.offset + fd.size, " bytes") + end + end + end end if Base.JLOptions().trim != 0 diff --git a/test/trimming/Makefile b/test/trimming/Makefile index c3145765655e7..eb829d403036e 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -33,14 +33,17 @@ JULIAC_BUILDSCRIPT := $(shell $(JULIA) -e 'print(joinpath(Sys.BINDIR, Base.DATAR #============================================================================= -release: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) +release: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/simplelib-o.a $(BIN)/hello-o.a: $(SRCDIR)/hello.jl $(JULIAC_BUILDSCRIPT) - $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true + $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true $(BIN)/bindinginfo_hello.log $(BIN)/basic_jll-o.a: $(SRCDIR)/basic_jll.jl $(JULIAC_BUILDSCRIPT) $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) -e "using Pkg; Pkg.instantiate()" - $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true + $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true $(BIN)/bindinginfo_basic_jll.log + +$(BIN)/simplelib-o.a: $(SRCDIR)/simplelib.jl $(JULIAC_BUILDSCRIPT) + $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-lib true $(BIN)/bindinginfo_simplelib.log $(BIN)/hello$(EXE): $(BIN)/hello-o.a $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) @@ -48,11 +51,11 @@ $(BIN)/hello$(EXE): $(BIN)/hello-o.a $(BIN)/basic_jll$(EXE): $(BIN)/basic_jll-o.a $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) -check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) +check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/simplelib-o.a $(JULIA) --depwarn=error $(SRCDIR)/trimming.jl $< clean: - -rm -f $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/basic_jll-o.a + -rm -f $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/basic_jll-o.a $(BIN)/simplelib-o.a .PHONY: release clean check diff --git a/test/trimming/simplelib.jl b/test/trimming/simplelib.jl new file mode 100644 index 0000000000000..02288d92574db --- /dev/null +++ b/test/trimming/simplelib.jl @@ -0,0 +1,20 @@ +module SimpleLib +# Test the logging of entrypoints and types in a C-callable Julia library. + +struct CVector{T} + length::Cint + data::Ptr{T} +end + +struct CVectorPair{T} + from::CVector{T} + to::CVector{T} +end + +Base.@ccallable function copyto_and_sum(fromto::CVectorPair{Float32})::Float32 + from, to = unsafe_wrap(Array, fromto.from.data, fromto.from.length), unsafe_wrap(Array, fromto.to.data, fromto.to.length) + copyto!(to, from) + return sum(to) +end + +end diff --git a/test/trimming/trimming.jl b/test/trimming/trimming.jl index 5d55ed62b03a8..1fa2cc30519d4 100644 --- a/test/trimming/trimming.jl +++ b/test/trimming/trimming.jl @@ -15,4 +15,21 @@ let exe_suffix = splitext(Base.julia_exename())[2] @test lines[2] == lines[3] @test Base.VersionNumber(lines[2]) ≥ v"1.5.7" @test filesize(basic_jll_exe) < filesize(unsafe_string(Base.JLOptions().image_file))/10 + + str = read(joinpath(bindir, "bindinginfo_simplelib.log"), String) + @test occursin("copyto_and_sum(::CVectorPair{Float32})::Float32", str) + @test occursin( + """ + _CVector_Float32_ + length::Int32[0] + data::Ptr{Float32}[8] + 16 bytes""", str + ) + @test occursin( + """ + _CVectorPair_Float32_ + from::_CVector_Float32_[0] + to::_CVector_Float32_[16] + 32 bytes""", str + ) end From 10997532bc127323cfbd8419a1e6ae1ace73a338 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 27 Jul 2025 04:36:42 -0500 Subject: [PATCH 02/29] minor Makefile improvements --- test/trimming/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/trimming/Makefile b/test/trimming/Makefile index eb829d403036e..523af280ce82c 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -36,11 +36,11 @@ JULIAC_BUILDSCRIPT := $(shell $(JULIA) -e 'print(joinpath(Sys.BINDIR, Base.DATAR release: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/simplelib-o.a $(BIN)/hello-o.a: $(SRCDIR)/hello.jl $(JULIAC_BUILDSCRIPT) - $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true $(BIN)/bindinginfo_hello.log + $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true $(BIN)/basic_jll-o.a: $(SRCDIR)/basic_jll.jl $(JULIAC_BUILDSCRIPT) $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) -e "using Pkg; Pkg.instantiate()" - $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true $(BIN)/bindinginfo_basic_jll.log + $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true $(BIN)/simplelib-o.a: $(SRCDIR)/simplelib.jl $(JULIAC_BUILDSCRIPT) $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-lib true $(BIN)/bindinginfo_simplelib.log @@ -55,7 +55,7 @@ check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/simplelib-o.a $(JULIA) --depwarn=error $(SRCDIR)/trimming.jl $< clean: - -rm -f $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/basic_jll-o.a $(BIN)/simplelib-o.a + -rm -f $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/basic_jll-o.a $(BIN)/simplelib-o.a $(BIN)/bindinginfo_simplelib.log .PHONY: release clean check From e90786b6756f9dae57aa70d648290e008d4c6dd0 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 28 Jul 2025 04:59:30 -0500 Subject: [PATCH 03/29] Nicer printing - remove mangling in favor of native Julia printing - include argnames in methods - support and test name argument to ccallable --- contrib/juliac/juliac-buildscript.jl | 22 ++++++++++------------ test/trimming/simplelib.jl | 10 +++++++++- test/trimming/trimming.jl | 18 +++++++++++++----- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 190647c8b0c4b..802dba1ef882f 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -51,13 +51,6 @@ function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::Da end end -function mangle_name(@nospecialize(T::DataType)) - is_c_friendly(T) && return string(T) - pname = isempty(T.parameters) ? String(nameof(T)) : - join(pushfirst!(map(mangle_name, T.parameters), String(nameof(T)), "_")) - return "_" * pname * "_" -end - # Load user code import Base.Experimental.entrypoint @@ -108,29 +101,34 @@ let mod = Base.include(Main, ARGS[1]) # Export info about entrypoints and structs needed to create header files if length(ARGS) >= 4 logfile = ARGS[4] + iotmp = IOBuffer() open(logfile, "w") do io types = Base.IdSet{DataType}() Base.visit(Core.GlobalMethods) do method if isdefined(method, :ccallable) rt, sig = method.ccallable name = length(method.ccallable) > 2 ? Symbol(method.ccallable[3]) : method.name - Base.show_tuple_as_call(io, name, sig) - println(io, "::", rt) + print(IOContext(iotmp, :print_method_signature_only => true), method) + methodstr = String(take!(iotmp)) + if name !== method.name + methodstr = replace(methodstr, String(method.name) => String(name)) + end + println(io, methodstr, "::", rt) for T in sig.parameters[2:end] recursively_add_types!(types, T) end end end - println(io) + println(io) # blank line separates methods from types for T in types - println(io, mangle_name(T)) + println(io, T) dtfd = Base.DataTypeFieldDesc(T) local fd for i = 1:Base.datatype_nfields(T) fd = dtfd[i] fn = fieldname(T, i) ft = fieldtype(T, i) - println(io, " ", fn, "::", mangle_name(ft), "[", fd.offset, "]") + println(io, " ", fn, "::", ft, "[", fd.offset, "]") end println(io, fd.offset + fd.size, " bytes") end diff --git a/test/trimming/simplelib.jl b/test/trimming/simplelib.jl index 02288d92574db..ce7426315a570 100644 --- a/test/trimming/simplelib.jl +++ b/test/trimming/simplelib.jl @@ -11,10 +11,18 @@ struct CVectorPair{T} to::CVector{T} end -Base.@ccallable function copyto_and_sum(fromto::CVectorPair{Float32})::Float32 +Base.@ccallable "copyto_and_sum" function badname(fromto::CVectorPair{Float32})::Float32 from, to = unsafe_wrap(Array, fromto.from.data, fromto.from.length), unsafe_wrap(Array, fromto.to.data, fromto.to.length) copyto!(to, from) return sum(to) end +# FIXME? varargs +# Base.@ccallable function printints(x::Cint...)::Nothing +# for i in 1:length(x) +# print(x[i], " ") +# end +# println() +# end + end diff --git a/test/trimming/trimming.jl b/test/trimming/trimming.jl index 1fa2cc30519d4..c605f4038d8ae 100644 --- a/test/trimming/trimming.jl +++ b/test/trimming/trimming.jl @@ -17,19 +17,27 @@ let exe_suffix = splitext(Base.julia_exename())[2] @test filesize(basic_jll_exe) < filesize(unsafe_string(Base.JLOptions().image_file))/10 str = read(joinpath(bindir, "bindinginfo_simplelib.log"), String) - @test occursin("copyto_and_sum(::CVectorPair{Float32})::Float32", str) + @test occursin("copyto_and_sum(fromto::CVectorPair{Float32})::Float32", str) @test occursin( """ - _CVector_Float32_ + CVector{Float32} length::Int32[0] data::Ptr{Float32}[8] 16 bytes""", str ) @test occursin( """ - _CVectorPair_Float32_ - from::_CVector_Float32_[0] - to::_CVector_Float32_[16] + CVectorPair{Float32} + from::CVector{Float32}[0] + to::CVector{Float32}[16] 32 bytes""", str ) + # ensure that there is a blank line between methods and types + lines = split(str, '\n'; keepempty=true) + nblanks = 0 + for line in lines + nblanks += isempty(line) + occursin("length", line) && break + end + @test nblanks == 1 end From ce0f007827f50b4e1f6fc550876826a965c1ed7d Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Mon, 28 Jul 2025 19:32:23 -0500 Subject: [PATCH 04/29] Use IdSet instead of Union --- contrib/juliac/juliac-buildscript.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 802dba1ef882f..316ec52e73209 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -27,16 +27,16 @@ if Base.JLOptions().trim != 0 include(joinpath(@__DIR__, "juliac-trim-base.jl")) end -const C_friendly_types = Union{ # a few of these are redundant to make it easier to maintain +const C_friendly_types = Base.IdSet{Any}([ # a few of these are redundant to make it easier to maintain Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float32, Float64, Bool, Cvoid, Cint, Cshort, Clong, Cuint, Cushort, Culong, Cssize_t, Csize_t, Cchar, Cwchar_t, Cstring, Cwstring, RawFD, -} +]) function is_c_friendly(@nospecialize(T::DataType)) T <: Ptr && return is_c_friendly(T.parameters[1]) - return T <: C_friendly_types + return T in C_friendly_types end function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::DataType)) From 334f61332f204c2849e58aa58d3f3a5a5f58063f Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 29 Jul 2025 06:05:27 -0500 Subject: [PATCH 05/29] Try using the library --- contrib/juliac/juliac-buildscript.jl | 13 ++++----- test/trimming/Makefile | 16 +++++++---- test/trimming/capplication.c | 20 ++++++++++++++ test/trimming/libsimple.h | 28 ++++++++++++++++++++ test/trimming/{simplelib.jl => libsimple.jl} | 19 +++++++++++++ 5 files changed, 85 insertions(+), 11 deletions(-) create mode 100644 test/trimming/capplication.c create mode 100644 test/trimming/libsimple.h rename test/trimming/{simplelib.jl => libsimple.jl} (62%) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 316ec52e73209..34dae9f767dbe 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -34,13 +34,13 @@ const C_friendly_types = Base.IdSet{Any}([ # a few of these are redundant to RawFD, ]) -function is_c_friendly(@nospecialize(T::DataType)) - T <: Ptr && return is_c_friendly(T.parameters[1]) - return T in C_friendly_types -end - function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::DataType)) - if !is_c_friendly(T) + if T ∉ C_friendly_types + if T <: Ptr + return recursively_add_types!(types, T.parameters[1]) + elseif T <: Array + return recursively_add_types!(types, T.parameters[1]) + end T.name.module === Core && error("invalid type for juliac: ", T) # exclude internals (they may change) push!(types, T) end @@ -49,6 +49,7 @@ function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::Da recursively_add_types!(types, S) end end + return types end # Load user code diff --git a/test/trimming/Makefile b/test/trimming/Makefile index 523af280ce82c..3833de8746df7 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -33,7 +33,7 @@ JULIAC_BUILDSCRIPT := $(shell $(JULIA) -e 'print(joinpath(Sys.BINDIR, Base.DATAR #============================================================================= -release: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/simplelib-o.a +release: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/capplication$(EXE) $(BIN)/hello-o.a: $(SRCDIR)/hello.jl $(JULIAC_BUILDSCRIPT) $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true @@ -42,8 +42,8 @@ $(BIN)/basic_jll-o.a: $(SRCDIR)/basic_jll.jl $(JULIAC_BUILDSCRIPT) $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) -e "using Pkg; Pkg.instantiate()" $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true -$(BIN)/simplelib-o.a: $(SRCDIR)/simplelib.jl $(JULIAC_BUILDSCRIPT) - $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-lib true $(BIN)/bindinginfo_simplelib.log +$(BIN)/libsimple-o.a: $(SRCDIR)/libsimple.jl $(JULIAC_BUILDSCRIPT) + $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-lib true $(BIN)/bindinginfo_libsimple.log $(BIN)/hello$(EXE): $(BIN)/hello-o.a $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) @@ -51,11 +51,17 @@ $(BIN)/hello$(EXE): $(BIN)/hello-o.a $(BIN)/basic_jll$(EXE): $(BIN)/basic_jll-o.a $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) -check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/simplelib-o.a +$(BIN)/libsimple.$(SHLIB_EXT): $(BIN)/libsimple-o.a + $(CC) -shared -o $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/libsimple-o.a $(LDFLAGS_ADD) $(LDFLAGS) + +$(BIN)/capplication$(EXE): $(BIN)/capplication.c $(BIN)/libsimple.h $(BIN)/libsimple.$(SHLIB_EXT) + $(CC) -I$(BIN) -I$(SRCDIR) -I$(JULIA_LIBDIR) -o $@ $< $(LDFLAGS_ADD) $(LDFLAGS) -Wl,-rpath=. -L. -lsimple $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) + +check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/capplication$(EXE) $(JULIA) --depwarn=error $(SRCDIR)/trimming.jl $< clean: - -rm -f $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/basic_jll-o.a $(BIN)/simplelib-o.a $(BIN)/bindinginfo_simplelib.log + -rm -f $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/basic_jll-o.a $(BIN)/libsimple-o.a $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/capplication$(EXE) $(BIN)/bindinginfo_libsimple.log .PHONY: release clean check diff --git a/test/trimming/capplication.c b/test/trimming/capplication.c new file mode 100644 index 0000000000000..ad61643eb515d --- /dev/null +++ b/test/trimming/capplication.c @@ -0,0 +1,20 @@ +#include +#include "libsimple.h" + +int main() { + // Example usage of the functions defined in libsimple.h + CVectorPair_Float32 vecPair; + vecPair.from.length = 3; + vecPair.from.data = (float[]){1.0f, 2.0f, 3.0f}; + vecPair.to.length = 3; + vecPair.to.data = (float[]){4.0f, 5.0f, 6.0f}; + + float sum = copyto_and_sum(&vecPair); + printf("Sum of copied values: %f\n", sum); + + MyTwoVec list[] = {{1, 2}, {1, 2}, {3, 4}}; + int32_t count = countsame(list, 3); + printf("Count of same vectors: %d\n", count); + + return 0; +} diff --git a/test/trimming/libsimple.h b/test/trimming/libsimple.h new file mode 100644 index 0000000000000..0919b575d28a1 --- /dev/null +++ b/test/trimming/libsimple.h @@ -0,0 +1,28 @@ +#ifndef SIMPLELIB_H +#define SIMPLELIB_H + +#include +#include + +struct _MyTwoVec_ { + int32_t x; + int32_t y; +}; +typedef struct _MyTwoVec_ MyTwoVec; + +struct _CVector_Float32_ { + int32_t length; + float *data; +}; +typedef struct _CVector_Float32_ CVector_Float32; + +struct _CVectorPair_Float32_ { + CVector_Float32 from; + CVector_Float32 to; +}; +typedef struct _CVectorPair_Float32_ CVectorPair_Float32; + +float copyto_and_sum(CVectorPair_Float32 *fromto); +int32_t countsame(MyTwoVec *list, int32_t length); + +#endif // SIMPLELIB_H diff --git a/test/trimming/simplelib.jl b/test/trimming/libsimple.jl similarity index 62% rename from test/trimming/simplelib.jl rename to test/trimming/libsimple.jl index ce7426315a570..daec7fd7d762c 100644 --- a/test/trimming/simplelib.jl +++ b/test/trimming/libsimple.jl @@ -11,12 +11,31 @@ struct CVectorPair{T} to::CVector{T} end +struct MyTwoVec + x::Int32 + y::Int32 +end + Base.@ccallable "copyto_and_sum" function badname(fromto::CVectorPair{Float32})::Float32 from, to = unsafe_wrap(Array, fromto.from.data, fromto.from.length), unsafe_wrap(Array, fromto.to.data, fromto.to.length) copyto!(to, from) return sum(to) end +# Base.@ccallable function countsame(list::Ptr{MyTwoVec}, n::Int32)::Int32 +Base.@ccallable function countsame(list::Vector{MyTwoVec})::Int32 + count = 0 + # list = unsafe_wrap(Array, list, n) + for v in list + if v.x == v.y + count += 1 + end + end + return count +end + +export countsame, copyto_and_sum + # FIXME? varargs # Base.@ccallable function printints(x::Cint...)::Nothing # for i in 1:length(x) From 612350a54eaa67e37887971e312a5a3533863067 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 30 Jul 2025 06:34:34 -0500 Subject: [PATCH 06/29] Remove Array from C-friendly types --- contrib/juliac/juliac-buildscript.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 34dae9f767dbe..a7be3abcfa11e 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -38,8 +38,6 @@ function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::Da if T ∉ C_friendly_types if T <: Ptr return recursively_add_types!(types, T.parameters[1]) - elseif T <: Array - return recursively_add_types!(types, T.parameters[1]) end T.name.module === Core && error("invalid type for juliac: ", T) # exclude internals (they may change) push!(types, T) From c2dffafb6fba32a7d97fb2ea051aa2aca35dba9d Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 30 Jul 2025 06:35:34 -0500 Subject: [PATCH 07/29] Get the shared library working --- test/trimming/Makefile | 11 ++++------- test/trimming/capplication.c | 4 ++-- test/trimming/libsimple.h | 2 +- test/trimming/libsimple.jl | 9 +++------ test/trimming/trimming.jl | 10 +++++++++- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/test/trimming/Makefile b/test/trimming/Makefile index 3833de8746df7..9c3e01fcf404a 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -33,7 +33,7 @@ JULIAC_BUILDSCRIPT := $(shell $(JULIA) -e 'print(joinpath(Sys.BINDIR, Base.DATAR #============================================================================= -release: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/capplication$(EXE) +release: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/capplication$(EXE) $(BIN)/hello-o.a: $(SRCDIR)/hello.jl $(JULIAC_BUILDSCRIPT) $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true @@ -51,13 +51,10 @@ $(BIN)/hello$(EXE): $(BIN)/hello-o.a $(BIN)/basic_jll$(EXE): $(BIN)/basic_jll-o.a $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) -$(BIN)/libsimple.$(SHLIB_EXT): $(BIN)/libsimple-o.a - $(CC) -shared -o $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/libsimple-o.a $(LDFLAGS_ADD) $(LDFLAGS) +$(BIN)/capplication$(EXE): $(BIN)/capplication.c $(BIN)/libsimple.h $(BIN)/libsimple-o.a + $(CC) -I$(BIN) -I$(SRCDIR) -I$(JULIA_LIBDIR) -o $@ $< -Wl,--whole-archive libsimple-o.a -Wl,--no-whole-archive $(LDFLAGS_ADD) $(LDFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) -$(BIN)/capplication$(EXE): $(BIN)/capplication.c $(BIN)/libsimple.h $(BIN)/libsimple.$(SHLIB_EXT) - $(CC) -I$(BIN) -I$(SRCDIR) -I$(JULIA_LIBDIR) -o $@ $< $(LDFLAGS_ADD) $(LDFLAGS) -Wl,-rpath=. -L. -lsimple $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) - -check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/capplication$(EXE) +check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/capplication$(EXE) $(JULIA) --depwarn=error $(SRCDIR)/trimming.jl $< clean: diff --git a/test/trimming/capplication.c b/test/trimming/capplication.c index ad61643eb515d..390faa282fdab 100644 --- a/test/trimming/capplication.c +++ b/test/trimming/capplication.c @@ -9,10 +9,10 @@ int main() { vecPair.to.length = 3; vecPair.to.data = (float[]){4.0f, 5.0f, 6.0f}; - float sum = copyto_and_sum(&vecPair); + float sum = copyto_and_sum(vecPair); printf("Sum of copied values: %f\n", sum); - MyTwoVec list[] = {{1, 2}, {1, 2}, {3, 4}}; + MyTwoVec list[] = {{1, 2}, {5, 5}, {3, 4}}; int32_t count = countsame(list, 3); printf("Count of same vectors: %d\n", count); diff --git a/test/trimming/libsimple.h b/test/trimming/libsimple.h index 0919b575d28a1..e15378c37b146 100644 --- a/test/trimming/libsimple.h +++ b/test/trimming/libsimple.h @@ -22,7 +22,7 @@ struct _CVectorPair_Float32_ { }; typedef struct _CVectorPair_Float32_ CVectorPair_Float32; -float copyto_and_sum(CVectorPair_Float32 *fromto); +float copyto_and_sum(CVectorPair_Float32 fromto); int32_t countsame(MyTwoVec *list, int32_t length); #endif // SIMPLELIB_H diff --git a/test/trimming/libsimple.jl b/test/trimming/libsimple.jl index daec7fd7d762c..bf6ec8813da14 100644 --- a/test/trimming/libsimple.jl +++ b/test/trimming/libsimple.jl @@ -22,14 +22,11 @@ Base.@ccallable "copyto_and_sum" function badname(fromto::CVectorPair{Float32}): return sum(to) end -# Base.@ccallable function countsame(list::Ptr{MyTwoVec}, n::Int32)::Int32 -Base.@ccallable function countsame(list::Vector{MyTwoVec})::Int32 +Base.@ccallable function countsame(list::Ptr{MyTwoVec}, n::Int32)::Int32 + list = unsafe_wrap(Array, list, n) count = 0 - # list = unsafe_wrap(Array, list, n) for v in list - if v.x == v.y - count += 1 - end + count += v.x == v.y end return count end diff --git a/test/trimming/trimming.jl b/test/trimming/trimming.jl index c605f4038d8ae..9744be3ed155d 100644 --- a/test/trimming/trimming.jl +++ b/test/trimming/trimming.jl @@ -16,7 +16,15 @@ let exe_suffix = splitext(Base.julia_exename())[2] @test Base.VersionNumber(lines[2]) ≥ v"1.5.7" @test filesize(basic_jll_exe) < filesize(unsafe_string(Base.JLOptions().image_file))/10 - str = read(joinpath(bindir, "bindinginfo_simplelib.log"), String) + # Test that the shared library can be used in a C application + capplication_exe = joinpath(bindir, "capplication" * exe_suffix) + lines = split(readchomp(`$capplication_exe`), "\n") + @test length(lines) == 2 + @test lines[1] == "Sum of copied values: 6.000000" + @test lines[2] == "Count of same vectors: 1" + + # Test that the logging of entrypoints and types works correctly + str = read(joinpath(bindir, "bindinginfo_libsimple.log"), String) @test occursin("copyto_and_sum(fromto::CVectorPair{Float32})::Float32", str) @test occursin( """ From ff8931c290879e2ddf1099039b458605736a8332 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 30 Jul 2025 06:42:58 -0500 Subject: [PATCH 08/29] Clean up after trimming tests --- .gitignore | 1 + test/Makefile | 1 + 2 files changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c4df2542005d4..90530bc921aa1 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ /test/results_*.json /test/results_*.dat /test/deps +/test/trimming/Manifest.toml *.expmap *.exe diff --git a/test/Makefile b/test/Makefile index 8b9fc139488f4..61946e650dee5 100644 --- a/test/Makefile +++ b/test/Makefile @@ -75,6 +75,7 @@ gcext: trimming: @$(MAKE) -C $(SRCDIR)/$@ check $(TRIMMING_ARGS) + @$(MAKE) -C $(SRCDIR)/$@ clean $(TRIMMING_ARGS) clangsa: @$(MAKE) -C $(SRCDIR)/$@ From 52878f37a707b7ef23600cb1563ca66057a6f361 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 30 Jul 2025 15:39:56 -0500 Subject: [PATCH 09/29] Update to JuliaLibWrapping .h file --- test/trimming/capplication.c | 2 +- test/trimming/libsimple.h | 40 +++++++++++++++--------------------- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/test/trimming/capplication.c b/test/trimming/capplication.c index 390faa282fdab..9455ad6b1810a 100644 --- a/test/trimming/capplication.c +++ b/test/trimming/capplication.c @@ -3,7 +3,7 @@ int main() { // Example usage of the functions defined in libsimple.h - CVectorPair_Float32 vecPair; + CVectorPair_float_ vecPair; vecPair.from.length = 3; vecPair.from.data = (float[]){1.0f, 2.0f, 3.0f}; vecPair.to.length = 3; diff --git a/test/trimming/libsimple.h b/test/trimming/libsimple.h index e15378c37b146..2382e045f5151 100644 --- a/test/trimming/libsimple.h +++ b/test/trimming/libsimple.h @@ -1,28 +1,22 @@ -#ifndef SIMPLELIB_H -#define SIMPLELIB_H - -#include +#ifndef JULIALIB_LIBSIMPLE_H +#define JULIALIB_LIBSIMPLE_H #include +#include +#include -struct _MyTwoVec_ { +typedef struct { + int32_t length; + float * data; +} CVector_float_; +typedef struct { + CVector_float_ from; + CVector_float_ to; +} CVectorPair_float_; +typedef struct { int32_t x; int32_t y; -}; -typedef struct _MyTwoVec_ MyTwoVec; - -struct _CVector_Float32_ { - int32_t length; - float *data; -}; -typedef struct _CVector_Float32_ CVector_Float32; - -struct _CVectorPair_Float32_ { - CVector_Float32 from; - CVector_Float32 to; -}; -typedef struct _CVectorPair_Float32_ CVectorPair_Float32; - -float copyto_and_sum(CVectorPair_Float32 fromto); -int32_t countsame(MyTwoVec *list, int32_t length); +} MyTwoVec; -#endif // SIMPLELIB_H +int32_t countsame(MyTwoVec * list, int32_t n); +float copyto_and_sum(CVectorPair_float_ fromto); +#endif // JULIALIB_LIBSIMPLE_H From 106868a191d3a38a697d55602f77a6e8fa3115f6 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Wed, 30 Jul 2025 16:20:13 -0500 Subject: [PATCH 10/29] Split `write_logfile` into separate function --- contrib/juliac/juliac-buildscript.jl | 64 +++++++++++++++------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index a7be3abcfa11e..5c8fea4a33ec4 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -50,6 +50,39 @@ function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::Da return types end +function write_logfile(io::IO) + iotmp = IOBuffer() + types = Base.IdSet{DataType}() + Base.visit(Core.GlobalMethods) do method + if isdefined(method, :ccallable) + rt, sig = method.ccallable + name = length(method.ccallable) > 2 ? Symbol(method.ccallable[3]) : method.name + print(IOContext(iotmp, :print_method_signature_only => true), method) + methodstr = String(take!(iotmp)) + if name !== method.name + methodstr = replace(methodstr, String(method.name) => String(name)) + end + println(io, methodstr, "::", rt) + for T in sig.parameters[2:end] + recursively_add_types!(types, T) + end + end + end + println(io) # blank line separates methods from types + for T in types + println(io, T) + dtfd = Base.DataTypeFieldDesc(T) + local fd + for i = 1:Base.datatype_nfields(T) + fd = dtfd[i] + fn = fieldname(T, i) + ft = fieldtype(T, i) + println(io, " ", fn, "::", ft, "[", fd.offset, "]") + end + println(io, fd.offset + fd.size, " bytes") + end +end + # Load user code import Base.Experimental.entrypoint @@ -100,37 +133,8 @@ let mod = Base.include(Main, ARGS[1]) # Export info about entrypoints and structs needed to create header files if length(ARGS) >= 4 logfile = ARGS[4] - iotmp = IOBuffer() open(logfile, "w") do io - types = Base.IdSet{DataType}() - Base.visit(Core.GlobalMethods) do method - if isdefined(method, :ccallable) - rt, sig = method.ccallable - name = length(method.ccallable) > 2 ? Symbol(method.ccallable[3]) : method.name - print(IOContext(iotmp, :print_method_signature_only => true), method) - methodstr = String(take!(iotmp)) - if name !== method.name - methodstr = replace(methodstr, String(method.name) => String(name)) - end - println(io, methodstr, "::", rt) - for T in sig.parameters[2:end] - recursively_add_types!(types, T) - end - end - end - println(io) # blank line separates methods from types - for T in types - println(io, T) - dtfd = Base.DataTypeFieldDesc(T) - local fd - for i = 1:Base.datatype_nfields(T) - fd = dtfd[i] - fn = fieldname(T, i) - ft = fieldtype(T, i) - println(io, " ", fn, "::", ft, "[", fd.offset, "]") - end - println(io, fd.offset + fd.size, " bytes") - end + write_logfile(io) end end end From cb068c234cc5cf78883e1da56c8e5766710d62aa Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 31 Jul 2025 08:32:42 -0500 Subject: [PATCH 11/29] Update contrib/juliac/juliac-buildscript.jl Co-authored-by: Cody Tapscott <84105208+topolarity@users.noreply.github.com> --- contrib/juliac/juliac-buildscript.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 5c8fea4a33ec4..54f21cd86e2bc 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -35,13 +35,13 @@ const C_friendly_types = Base.IdSet{Any}([ # a few of these are redundant to ]) function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::DataType)) - if T ∉ C_friendly_types - if T <: Ptr - return recursively_add_types!(types, T.parameters[1]) - end - T.name.module === Core && error("invalid type for juliac: ", T) # exclude internals (they may change) - push!(types, T) + while T <: Ptr + T = T.parameters[1] # unwrap Ptr{...} + end + if T.name.module === Core && T ∉ C_friendly_types + error("invalid type for juliac: ", T) # exclude internals (they may change) end + push!(types, T) for list in (T.parameters, fieldtypes(T)) for S in list recursively_add_types!(types, S) From c0e1c3e236798b35e8a1b055944ed51d1673c906 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 1 Aug 2025 09:35:51 -0400 Subject: [PATCH 12/29] Export ABI metadata in JSON format With luck, this makes this information easier to consume downstream (and to expand on in the future). Also fixes a couple of obscure bugs that would cause mis-behavior in the presence of recursive types, or which could unintentionally mangle types in the event of sub-string collisions. --- contrib/juliac/juliac-buildscript.jl | 210 ++++++++++++++++++++++++--- test/trimming/libsimple.jl | 14 ++ 2 files changed, 201 insertions(+), 23 deletions(-) diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 54f21cd86e2bc..3dd2658217a29 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -35,8 +35,11 @@ const C_friendly_types = Base.IdSet{Any}([ # a few of these are redundant to ]) function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::DataType)) - while T <: Ptr + T in types && return types + while T.name === Ptr.body.name + push!(types, T) T = T.parameters[1] # unwrap Ptr{...} + T in types && return types end if T.name.module === Core && T ∉ C_friendly_types error("invalid type for juliac: ", T) # exclude internals (they may change) @@ -50,37 +53,198 @@ function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::Da return types end +struct TypeEmitter + io::IO + type_ids::IdDict{Any,Int} +end + +function escape_string_json(s::AbstractString) + iob = IOBuffer() + print(iob, '"') + for c in s + if c == '"' + print(iob, "\\\"") + elseif c == '\\' + print(iob, "\\\\") + elseif c == '\b' + print(iob, "\\b") + elseif c == '\f' + print(iob, "\\f") + elseif c == '\n' + print(iob, "\\n") + elseif c == '\r' + print(iob, "\\r") + elseif c == '\t' + print(iob, "\\t") + elseif '\x00' <= c <= '\x1f' + print(iob, "\\u", lpad(string(UInt16(c), base=16), 4, '0')) + else + @assert isvalid(c) "invalid unicode character" + print(iob, c) + end + end + print(iob, '"') + return String(take!(iob)) +end + +function type_name_json(@nospecialize(dt::DataType)) + return escape_string_json(repr(dt)) +end + +function field_name_json(@nospecialize(dt::DataType), field::Int) + name = String(fieldname(dt, field)) + return escape_string_json(name) +end + +function emit_pointer_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) + pointee_type_id = ctx.type_ids[dt.parameters[1]] + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"id\": ", ctx.type_ids[dt], ",") + indented_println(" \"kind\": \"pointer\",") + indented_println(" \"name\": ", type_name_json(dt), ",") + indented_println(" \"pointee\": ", pointee_type_id) + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_field_info!(ctx::TypeEmitter, @nospecialize(dt::DataType), field::Int; indent::Int = 0) + desc = Base.DataTypeFieldDesc(dt)[field] + type_id = ctx.type_ids[fieldtype(dt, field)] + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"name\": ", field_name_json(dt, field), ",") + indented_println(" \"offset\": ", desc.offset, ",") + indented_println(" \"type\": ", type_id) + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_struct_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) + type_id = ctx.type_ids[dt] + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"id\": ", type_id, ",") + indented_println(" \"kind\": \"struct\",") + indented_println(" \"name\": ", type_name_json(dt), ",") + indented_println(" \"size\": ", Core.sizeof(dt), ",") + indented_println(" \"alignment\": ", Base.datatype_alignment(dt), ",") + indented_println(" \"fields\": [") + for i = 1:Base.datatype_nfields(dt) + emit_field_info!(ctx, dt, i; indent = indent + 4) + println(ctx.io, i == Base.datatype_nfields(dt) ? "" : ",") + end + indented_println(" ]") + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_primitive_type!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) + type_id = ctx.type_ids[dt] + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"id\": ", type_id, ",") + indented_println(" \"kind\": \"primitive\",") + indented_println(" \"name\": ", type_name_json(dt), ",") + indented_println(" \"size\": ", Core.sizeof(dt), ",") + indented_println(" \"alignment\": ", Base.datatype_alignment(dt)) + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_type_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) + if dt.name === Ptr.body.name + emit_pointer_info!(ctx, dt; indent) + elseif Base.isprimitivetype(dt) + emit_primitive_type!(ctx, dt; indent) + else + emit_struct_info!(ctx, dt; indent) + end +end + +function emit_method_info!(ctx::TypeEmitter, method::Core.Method; indent::Int = 0) + (rt, sig) = method.ccallable + (name, symbol) = let + symbol = length(method.ccallable) > 2 ? Symbol(method.ccallable[3]) : method.name + iob = IOBuffer() + print(IOContext(iob, :print_method_signature_only => true), method) + str = String(take!(iob)) + if symbol !== method.name && startswith(str, String(method.name)) + # Make a best-effort attempt to use the exported name + # + # Note: the `startswith` check is to make sure we support 'functor's in arg0, + # which Base.@ccallable supports as long as they are singletons. + str = replace(str, String(method.name) => String(symbol); count = 1) + end + (str, String(symbol)) + end + + argnames = String.(Base.method_argnames(method)) + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"symbol\": ", escape_string_json(symbol), ",") + indented_println(" \"name\": ", escape_string_json(name), ",") + indented_println(" \"arguments\": [") + for i in 2:length(sig.parameters) + indented_println(" {") + indented_println(" \"name\": ", escape_string_json(argnames[i]), ",") + indented_println(" \"type\": ", ctx.type_ids[sig.parameters[i]]) + indented_println(" }", i == length(sig.parameters) ? "" : ",") + end + indented_println(" ],") + indented_println(" \"returns\": {") + indented_println(" \"type\": ", ctx.type_ids[rt]) + indented_println(" }") + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_abi_info!(ctx::TypeEmitter, exported::Vector{Core.Method}, types::IdSet{DataType}) + println(ctx.io, "{") + + # assign an ID to each type, so that we can refer to them + for (i, T) in enumerate(types) + ctx.type_ids[T] = i + end + + # print exported functions + println(ctx.io, " \"functions\": [") + for (i, method) in enumerate(exported) + emit_method_info!(ctx, method; indent = 4) + println(ctx.io, i == length(exported) ? "" : ",") + end + println(ctx.io, " ],") + + # print type / structure information + println(ctx.io, " \"types\": [") + for (i, T) in enumerate(types) + emit_type_info!(ctx, T; indent = 4) + println(ctx.io, i == length(types) ? "" : ",") + end + println(ctx.io, " ]") + + println(ctx.io, "}") +end + function write_logfile(io::IO) - iotmp = IOBuffer() types = Base.IdSet{DataType}() + + # discover all exported methods + any types they reference + exported = Core.Method[] Base.visit(Core.GlobalMethods) do method if isdefined(method, :ccallable) - rt, sig = method.ccallable - name = length(method.ccallable) > 2 ? Symbol(method.ccallable[3]) : method.name - print(IOContext(iotmp, :print_method_signature_only => true), method) - methodstr = String(take!(iotmp)) - if name !== method.name - methodstr = replace(methodstr, String(method.name) => String(name)) - end - println(io, methodstr, "::", rt) + push!(exported, method) + (rt, sig) = method.ccallable for T in sig.parameters[2:end] recursively_add_types!(types, T) end + recursively_add_types!(types, rt) end end - println(io) # blank line separates methods from types - for T in types - println(io, T) - dtfd = Base.DataTypeFieldDesc(T) - local fd - for i = 1:Base.datatype_nfields(T) - fd = dtfd[i] - fn = fieldname(T, i) - ft = fieldtype(T, i) - println(io, " ", fn, "::", ft, "[", fd.offset, "]") - end - println(io, fd.offset + fd.size, " bytes") - end + + # print the discovered ABI info + ctx = TypeEmitter(io, IdDict{Any,Int}()) + emit_abi_info!(ctx, exported, types) end # Load user code diff --git a/test/trimming/libsimple.jl b/test/trimming/libsimple.jl index bf6ec8813da14..64126ff8c384e 100644 --- a/test/trimming/libsimple.jl +++ b/test/trimming/libsimple.jl @@ -16,6 +16,20 @@ struct MyTwoVec y::Int32 end +struct CTree{T} + # test that recursive datatypes work as expected + children::CVector{CTree{T}} +end + +Base.@ccallable "tree_size" function size(tree::CTree{Float64})::Int64 + children = unsafe_wrap(Array, tree.children.data, tree.children.length) + # Return the size of this sub-tree + return sum(Int64[ + size(child) + for child in children + ]; init=1) +end + Base.@ccallable "copyto_and_sum" function badname(fromto::CVectorPair{Float32})::Float32 from, to = unsafe_wrap(Array, fromto.from.data, fromto.from.length), unsafe_wrap(Array, fromto.to.data, fromto.to.length) copyto!(to, from) From a1b8349d9c545e179212fab46cb0da041cb68e97 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 1 Aug 2025 09:53:51 -0400 Subject: [PATCH 13/29] Move ABI export functionality to `abi_export.jl` --- Makefile | 2 +- contrib/juliac/abi_export.jl | 219 ++++++++++++++++++++++++++ contrib/juliac/juliac-buildscript.jl | 226 +-------------------------- 3 files changed, 224 insertions(+), 223 deletions(-) create mode 100644 contrib/juliac/abi_export.jl diff --git a/Makefile b/Makefile index 40dd1565f8744..ef3fc907176f4 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ julia-deps: | $(DIRS) $(build_datarootdir)/julia/base $(build_datarootdir)/julia julia-stdlib: | $(DIRS) julia-deps @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/stdlib -julia-base: julia-deps $(build_sysconfdir)/julia/startup.jl $(build_man1dir)/julia.1 $(build_datarootdir)/julia/julia-config.jl $(build_datarootdir)/julia/juliac/juliac.jl $(build_datarootdir)/julia/juliac/juliac-buildscript.jl $(build_datarootdir)/julia/juliac/juliac-trim-base.jl $(build_datarootdir)/julia/juliac/juliac-trim-stdlib.jl $(build_datarootdir)/julia/juliac/Artifacts.toml +julia-base: julia-deps $(build_sysconfdir)/julia/startup.jl $(build_man1dir)/julia.1 $(build_datarootdir)/julia/julia-config.jl $(build_datarootdir)/julia/juliac/juliac.jl $(build_datarootdir)/julia/juliac/abi_export.jl $(build_datarootdir)/julia/juliac/juliac-buildscript.jl $(build_datarootdir)/julia/juliac/juliac-trim-base.jl $(build_datarootdir)/julia/juliac/juliac-trim-stdlib.jl $(build_datarootdir)/julia/juliac/Artifacts.toml @$(MAKE) $(QUIET_MAKE) -C $(BUILDROOT)/base julia-libccalltest: julia-deps diff --git a/contrib/juliac/abi_export.jl b/contrib/juliac/abi_export.jl new file mode 100644 index 0000000000000..3fd2b54ee773d --- /dev/null +++ b/contrib/juliac/abi_export.jl @@ -0,0 +1,219 @@ +const C_friendly_types = Base.IdSet{Any}([ # a few of these are redundant to make it easier to maintain + Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float32, Float64, Bool, + Cvoid, Cint, Cshort, Clong, Cuint, Cushort, Culong, Cssize_t, Csize_t, + Cchar, Cwchar_t, Cstring, Cwstring, + RawFD, +]) + +function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::DataType)) + T in types && return types + while T.name === Ptr.body.name + push!(types, T) + T = T.parameters[1] # unwrap Ptr{...} + T in types && return types + end + if T.name.module === Core && T ∉ C_friendly_types + error("invalid type for juliac: ", T) # exclude internals (they may change) + end + push!(types, T) + for list in (T.parameters, fieldtypes(T)) + for S in list + recursively_add_types!(types, S) + end + end + return types +end + +struct TypeEmitter + io::IO + type_ids::IdDict{Any,Int} +end + +function escape_string_json(s::AbstractString) + iob = IOBuffer() + print(iob, '"') + for c in s + if c == '"' + print(iob, "\\\"") + elseif c == '\\' + print(iob, "\\\\") + elseif c == '\b' + print(iob, "\\b") + elseif c == '\f' + print(iob, "\\f") + elseif c == '\n' + print(iob, "\\n") + elseif c == '\r' + print(iob, "\\r") + elseif c == '\t' + print(iob, "\\t") + elseif '\x00' <= c <= '\x1f' + print(iob, "\\u", lpad(string(UInt16(c), base=16), 4, '0')) + else + @assert isvalid(c) "invalid unicode character" + print(iob, c) + end + end + print(iob, '"') + return String(take!(iob)) +end + +function type_name_json(@nospecialize(dt::DataType)) + return escape_string_json(repr(dt)) +end + +function field_name_json(@nospecialize(dt::DataType), field::Int) + name = String(fieldname(dt, field)) + return escape_string_json(name) +end + +function emit_pointer_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) + pointee_type_id = ctx.type_ids[dt.parameters[1]] + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"id\": ", ctx.type_ids[dt], ",") + indented_println(" \"kind\": \"pointer\",") + indented_println(" \"name\": ", type_name_json(dt), ",") + indented_println(" \"pointee\": ", pointee_type_id) + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_field_info!(ctx::TypeEmitter, @nospecialize(dt::DataType), field::Int; indent::Int = 0) + desc = Base.DataTypeFieldDesc(dt)[field] + type_id = ctx.type_ids[fieldtype(dt, field)] + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"name\": ", field_name_json(dt, field), ",") + indented_println(" \"offset\": ", desc.offset, ",") + indented_println(" \"type\": ", type_id) + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_struct_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) + type_id = ctx.type_ids[dt] + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"id\": ", type_id, ",") + indented_println(" \"kind\": \"struct\",") + indented_println(" \"name\": ", type_name_json(dt), ",") + indented_println(" \"size\": ", Core.sizeof(dt), ",") + indented_println(" \"alignment\": ", Base.datatype_alignment(dt), ",") + indented_println(" \"fields\": [") + for i = 1:Base.datatype_nfields(dt) + emit_field_info!(ctx, dt, i; indent = indent + 4) + println(ctx.io, i == Base.datatype_nfields(dt) ? "" : ",") + end + indented_println(" ]") + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_primitive_type!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) + type_id = ctx.type_ids[dt] + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"id\": ", type_id, ",") + indented_println(" \"kind\": \"primitive\",") + indented_println(" \"name\": ", type_name_json(dt), ",") + indented_println(" \"size\": ", Core.sizeof(dt), ",") + indented_println(" \"alignment\": ", Base.datatype_alignment(dt)) + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_type_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) + if dt.name === Ptr.body.name + emit_pointer_info!(ctx, dt; indent) + elseif Base.isprimitivetype(dt) + emit_primitive_type!(ctx, dt; indent) + else + emit_struct_info!(ctx, dt; indent) + end +end + +function emit_method_info!(ctx::TypeEmitter, method::Core.Method; indent::Int = 0) + (rt, sig) = method.ccallable + (name, symbol) = let + symbol = length(method.ccallable) > 2 ? Symbol(method.ccallable[3]) : method.name + iob = IOBuffer() + print(IOContext(iob, :print_method_signature_only => true), method) + str = String(take!(iob)) + if symbol !== method.name && startswith(str, String(method.name)) + # Make a best-effort attempt to use the exported name + # + # Note: the `startswith` check is to make sure we support 'functor's in arg0, + # which Base.@ccallable supports as long as they are singletons. + str = replace(str, String(method.name) => String(symbol); count = 1) + end + (str, String(symbol)) + end + + argnames = String.(Base.method_argnames(method)) + let indented_println(args...) = println(ctx.io, " " ^ indent, args...) + indented_println("{") + indented_println(" \"symbol\": ", escape_string_json(symbol), ",") + indented_println(" \"name\": ", escape_string_json(name), ",") + indented_println(" \"arguments\": [") + for i in 2:length(sig.parameters) + indented_println(" {") + indented_println(" \"name\": ", escape_string_json(argnames[i]), ",") + indented_println(" \"type\": ", ctx.type_ids[sig.parameters[i]]) + indented_println(" }", i == length(sig.parameters) ? "" : ",") + end + indented_println(" ],") + indented_println(" \"returns\": {") + indented_println(" \"type\": ", ctx.type_ids[rt]) + indented_println(" }") + print(ctx.io, " " ^ indent, "}") + end +end + +function emit_abi_info!(ctx::TypeEmitter, exported::Vector{Core.Method}, types::IdSet{DataType}) + println(ctx.io, "{") + + # assign an ID to each type, so that we can refer to them + for (i, T) in enumerate(types) + ctx.type_ids[T] = i + end + + # print exported functions + println(ctx.io, " \"functions\": [") + for (i, method) in enumerate(exported) + emit_method_info!(ctx, method; indent = 4) + println(ctx.io, i == length(exported) ? "" : ",") + end + println(ctx.io, " ],") + + # print type / structure information + println(ctx.io, " \"types\": [") + for (i, T) in enumerate(types) + emit_type_info!(ctx, T; indent = 4) + println(ctx.io, i == length(types) ? "" : ",") + end + println(ctx.io, " ]") + + println(ctx.io, "}") +end + +function write_abi_metadata(io::IO) + types = Base.IdSet{DataType}() + + # discover all exported methods + any types they reference + exported = Core.Method[] + Base.visit(Core.GlobalMethods) do method + if isdefined(method, :ccallable) + push!(exported, method) + (rt, sig) = method.ccallable + for T in sig.parameters[2:end] + recursively_add_types!(types, T) + end + recursively_add_types!(types, rt) + end + end + + # print the discovered ABI info + ctx = TypeEmitter(io, IdDict{Any,Int}()) + emit_abi_info!(ctx, exported, types) +end diff --git a/contrib/juliac/juliac-buildscript.jl b/contrib/juliac/juliac-buildscript.jl index 3dd2658217a29..40ab0286f5b14 100644 --- a/contrib/juliac/juliac-buildscript.jl +++ b/contrib/juliac/juliac-buildscript.jl @@ -27,225 +27,7 @@ if Base.JLOptions().trim != 0 include(joinpath(@__DIR__, "juliac-trim-base.jl")) end -const C_friendly_types = Base.IdSet{Any}([ # a few of these are redundant to make it easier to maintain - Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float32, Float64, Bool, - Cvoid, Cint, Cshort, Clong, Cuint, Cushort, Culong, Cssize_t, Csize_t, - Cchar, Cwchar_t, Cstring, Cwstring, - RawFD, -]) - -function recursively_add_types!(types::Base.IdSet{DataType}, @nospecialize(T::DataType)) - T in types && return types - while T.name === Ptr.body.name - push!(types, T) - T = T.parameters[1] # unwrap Ptr{...} - T in types && return types - end - if T.name.module === Core && T ∉ C_friendly_types - error("invalid type for juliac: ", T) # exclude internals (they may change) - end - push!(types, T) - for list in (T.parameters, fieldtypes(T)) - for S in list - recursively_add_types!(types, S) - end - end - return types -end - -struct TypeEmitter - io::IO - type_ids::IdDict{Any,Int} -end - -function escape_string_json(s::AbstractString) - iob = IOBuffer() - print(iob, '"') - for c in s - if c == '"' - print(iob, "\\\"") - elseif c == '\\' - print(iob, "\\\\") - elseif c == '\b' - print(iob, "\\b") - elseif c == '\f' - print(iob, "\\f") - elseif c == '\n' - print(iob, "\\n") - elseif c == '\r' - print(iob, "\\r") - elseif c == '\t' - print(iob, "\\t") - elseif '\x00' <= c <= '\x1f' - print(iob, "\\u", lpad(string(UInt16(c), base=16), 4, '0')) - else - @assert isvalid(c) "invalid unicode character" - print(iob, c) - end - end - print(iob, '"') - return String(take!(iob)) -end - -function type_name_json(@nospecialize(dt::DataType)) - return escape_string_json(repr(dt)) -end - -function field_name_json(@nospecialize(dt::DataType), field::Int) - name = String(fieldname(dt, field)) - return escape_string_json(name) -end - -function emit_pointer_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) - pointee_type_id = ctx.type_ids[dt.parameters[1]] - let indented_println(args...) = println(ctx.io, " " ^ indent, args...) - indented_println("{") - indented_println(" \"id\": ", ctx.type_ids[dt], ",") - indented_println(" \"kind\": \"pointer\",") - indented_println(" \"name\": ", type_name_json(dt), ",") - indented_println(" \"pointee\": ", pointee_type_id) - print(ctx.io, " " ^ indent, "}") - end -end - -function emit_field_info!(ctx::TypeEmitter, @nospecialize(dt::DataType), field::Int; indent::Int = 0) - desc = Base.DataTypeFieldDesc(dt)[field] - type_id = ctx.type_ids[fieldtype(dt, field)] - let indented_println(args...) = println(ctx.io, " " ^ indent, args...) - indented_println("{") - indented_println(" \"name\": ", field_name_json(dt, field), ",") - indented_println(" \"offset\": ", desc.offset, ",") - indented_println(" \"type\": ", type_id) - print(ctx.io, " " ^ indent, "}") - end -end - -function emit_struct_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) - type_id = ctx.type_ids[dt] - let indented_println(args...) = println(ctx.io, " " ^ indent, args...) - indented_println("{") - indented_println(" \"id\": ", type_id, ",") - indented_println(" \"kind\": \"struct\",") - indented_println(" \"name\": ", type_name_json(dt), ",") - indented_println(" \"size\": ", Core.sizeof(dt), ",") - indented_println(" \"alignment\": ", Base.datatype_alignment(dt), ",") - indented_println(" \"fields\": [") - for i = 1:Base.datatype_nfields(dt) - emit_field_info!(ctx, dt, i; indent = indent + 4) - println(ctx.io, i == Base.datatype_nfields(dt) ? "" : ",") - end - indented_println(" ]") - print(ctx.io, " " ^ indent, "}") - end -end - -function emit_primitive_type!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) - type_id = ctx.type_ids[dt] - let indented_println(args...) = println(ctx.io, " " ^ indent, args...) - indented_println("{") - indented_println(" \"id\": ", type_id, ",") - indented_println(" \"kind\": \"primitive\",") - indented_println(" \"name\": ", type_name_json(dt), ",") - indented_println(" \"size\": ", Core.sizeof(dt), ",") - indented_println(" \"alignment\": ", Base.datatype_alignment(dt)) - print(ctx.io, " " ^ indent, "}") - end -end - -function emit_type_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) - if dt.name === Ptr.body.name - emit_pointer_info!(ctx, dt; indent) - elseif Base.isprimitivetype(dt) - emit_primitive_type!(ctx, dt; indent) - else - emit_struct_info!(ctx, dt; indent) - end -end - -function emit_method_info!(ctx::TypeEmitter, method::Core.Method; indent::Int = 0) - (rt, sig) = method.ccallable - (name, symbol) = let - symbol = length(method.ccallable) > 2 ? Symbol(method.ccallable[3]) : method.name - iob = IOBuffer() - print(IOContext(iob, :print_method_signature_only => true), method) - str = String(take!(iob)) - if symbol !== method.name && startswith(str, String(method.name)) - # Make a best-effort attempt to use the exported name - # - # Note: the `startswith` check is to make sure we support 'functor's in arg0, - # which Base.@ccallable supports as long as they are singletons. - str = replace(str, String(method.name) => String(symbol); count = 1) - end - (str, String(symbol)) - end - - argnames = String.(Base.method_argnames(method)) - let indented_println(args...) = println(ctx.io, " " ^ indent, args...) - indented_println("{") - indented_println(" \"symbol\": ", escape_string_json(symbol), ",") - indented_println(" \"name\": ", escape_string_json(name), ",") - indented_println(" \"arguments\": [") - for i in 2:length(sig.parameters) - indented_println(" {") - indented_println(" \"name\": ", escape_string_json(argnames[i]), ",") - indented_println(" \"type\": ", ctx.type_ids[sig.parameters[i]]) - indented_println(" }", i == length(sig.parameters) ? "" : ",") - end - indented_println(" ],") - indented_println(" \"returns\": {") - indented_println(" \"type\": ", ctx.type_ids[rt]) - indented_println(" }") - print(ctx.io, " " ^ indent, "}") - end -end - -function emit_abi_info!(ctx::TypeEmitter, exported::Vector{Core.Method}, types::IdSet{DataType}) - println(ctx.io, "{") - - # assign an ID to each type, so that we can refer to them - for (i, T) in enumerate(types) - ctx.type_ids[T] = i - end - - # print exported functions - println(ctx.io, " \"functions\": [") - for (i, method) in enumerate(exported) - emit_method_info!(ctx, method; indent = 4) - println(ctx.io, i == length(exported) ? "" : ",") - end - println(ctx.io, " ],") - - # print type / structure information - println(ctx.io, " \"types\": [") - for (i, T) in enumerate(types) - emit_type_info!(ctx, T; indent = 4) - println(ctx.io, i == length(types) ? "" : ",") - end - println(ctx.io, " ]") - - println(ctx.io, "}") -end - -function write_logfile(io::IO) - types = Base.IdSet{DataType}() - - # discover all exported methods + any types they reference - exported = Core.Method[] - Base.visit(Core.GlobalMethods) do method - if isdefined(method, :ccallable) - push!(exported, method) - (rt, sig) = method.ccallable - for T in sig.parameters[2:end] - recursively_add_types!(types, T) - end - recursively_add_types!(types, rt) - end - end - - # print the discovered ABI info - ctx = TypeEmitter(io, IdDict{Any,Int}()) - emit_abi_info!(ctx, exported, types) -end +include(joinpath(@__DIR__, "abi_export.jl")) # Load user code @@ -296,9 +78,9 @@ let mod = Base.include(Main, ARGS[1]) # Export info about entrypoints and structs needed to create header files if length(ARGS) >= 4 - logfile = ARGS[4] - open(logfile, "w") do io - write_logfile(io) + abi_export = ARGS[4] + open(abi_export, "w") do io + write_abi_metadata(io) end end end From 0a744d6e812749f62e33ccd5697ad069fd26c36e Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 1 Aug 2025 10:11:10 -0400 Subject: [PATCH 14/29] Print field / argument info more compactly --- contrib/juliac/abi_export.jl | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/contrib/juliac/abi_export.jl b/contrib/juliac/abi_export.jl index 3fd2b54ee773d..fc29c4aafc4ec 100644 --- a/contrib/juliac/abi_export.jl +++ b/contrib/juliac/abi_export.jl @@ -82,13 +82,12 @@ end function emit_field_info!(ctx::TypeEmitter, @nospecialize(dt::DataType), field::Int; indent::Int = 0) desc = Base.DataTypeFieldDesc(dt)[field] type_id = ctx.type_ids[fieldtype(dt, field)] - let indented_println(args...) = println(ctx.io, " " ^ indent, args...) - indented_println("{") - indented_println(" \"name\": ", field_name_json(dt, field), ",") - indented_println(" \"offset\": ", desc.offset, ",") - indented_println(" \"type\": ", type_id) - print(ctx.io, " " ^ indent, "}") - end + print(ctx.io, " " ^ indent) + print(ctx.io, "{") + print(ctx.io, " \"name\": ", field_name_json(dt, field), ",") + print(ctx.io, " \"type\": ", type_id, ",") + print(ctx.io, " \"offset\": ", desc.offset) + print(ctx.io, " }") end function emit_struct_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent::Int = 0) @@ -157,15 +156,14 @@ function emit_method_info!(ctx::TypeEmitter, method::Core.Method; indent::Int = indented_println(" \"name\": ", escape_string_json(name), ",") indented_println(" \"arguments\": [") for i in 2:length(sig.parameters) - indented_println(" {") - indented_println(" \"name\": ", escape_string_json(argnames[i]), ",") - indented_println(" \"type\": ", ctx.type_ids[sig.parameters[i]]) - indented_println(" }", i == length(sig.parameters) ? "" : ",") + print(ctx.io, " " ^ (indent + 4)) + print(ctx.io, "{") + print(ctx.io, " \"name\": ", escape_string_json(argnames[i]), ",") + print(ctx.io, " \"type\": ", ctx.type_ids[sig.parameters[i]]) + println(ctx.io, i == length(sig.parameters) ? " }" : " },") end indented_println(" ],") - indented_println(" \"returns\": {") - indented_println(" \"type\": ", ctx.type_ids[rt]) - indented_println(" }") + indented_println(" \"returns\": { \"type\": ", ctx.type_ids[rt], " }") print(ctx.io, " " ^ indent, "}") end end From e1d30522c49da161fcfc2be9e545be9c428ac5e9 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 1 Aug 2025 10:22:04 -0400 Subject: [PATCH 15/29] Add `--export-abi` arg to `juliac.jl` --- contrib/juliac/juliac.jl | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/contrib/juliac/juliac.jl b/contrib/juliac/juliac.jl index ed80d88444639..2e36aad0703e0 100644 --- a/contrib/juliac/juliac.jl +++ b/contrib/juliac/juliac.jl @@ -13,6 +13,7 @@ julia_cmd = `$(Base.julia_cmd()) --startup-file=no --history-file=no` cpu_target = get(ENV, "JULIA_CPU_TARGET", nothing) julia_cmd_target = `$(Base.julia_cmd(;cpu_target)) --startup-file=no --history-file=no` output_type = nothing # exe, sharedlib, sysimage +abi_export_file = nothing outname = nothing file = nothing add_ccallables = false @@ -25,6 +26,7 @@ if help !== nothing """ Usage: julia juliac.jl [--output-exe | --output-lib | --output-sysimage] [options] --experimental --trim= Only output code statically determined to be reachable + --export-abi Emit type / function information for the ABI (in JSON format) --compile-ccallable Include all methods marked `@ccallable` in output --relative-rpath Configure the library / executable to lookup all required libraries in an adjacent "julia/" folder --verbose Request verbose output @@ -96,6 +98,10 @@ let i = 1 i == length(ARGS) && error("Output specifier requires an argument") global outname = ARGS[i+1] i += 1 + elseif arg == "--export-abi" + i == length(ARGS) && error("Output specifier requires an argument") + global abi_export_file = ARGS[i+1] + i += 1 elseif arg == "--compile-ccallable" global add_ccallables = true elseif arg == "--verbose" @@ -168,7 +174,11 @@ function compile_products(enable_trim::Bool) end # Compile the Julia code - cmd = addenv(`$julia_cmd_target --project=$(Base.active_project()) --output-o $img_path --output-incremental=no $strip_args $julia_args $(joinpath(@__DIR__,"juliac-buildscript.jl")) $absfile $output_type $add_ccallables`, "OPENBLAS_NUM_THREADS" => 1, "JULIA_NUM_THREADS" => 1) + args = String[absfile, output_type, string(add_ccallables)] + if abi_export_file !== nothing + push!(args, abi_export_file) + end + cmd = addenv(`$julia_cmd_target --project=$(Base.active_project()) --output-o $img_path --output-incremental=no $strip_args $julia_args $(joinpath(@__DIR__,"juliac-buildscript.jl")) $(args)`, "OPENBLAS_NUM_THREADS" => 1, "JULIA_NUM_THREADS" => 1) verbose && println("Running: $cmd") if !success(pipeline(cmd; stdout, stderr)) println(stderr, "\nFailed to compile $file") From d51511a3be878c1a46975cc07074e3cb55363d6d Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Fri, 1 Aug 2025 17:54:30 -0400 Subject: [PATCH 16/29] Add extra metadata for `primitive` types --- contrib/juliac/abi_export.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/juliac/abi_export.jl b/contrib/juliac/abi_export.jl index fc29c4aafc4ec..55a6b38ae7830 100644 --- a/contrib/juliac/abi_export.jl +++ b/contrib/juliac/abi_export.jl @@ -116,7 +116,9 @@ function emit_primitive_type!(ctx::TypeEmitter, @nospecialize(dt::DataType); ind indented_println(" \"id\": ", type_id, ",") indented_println(" \"kind\": \"primitive\",") indented_println(" \"name\": ", type_name_json(dt), ",") - indented_println(" \"size\": ", Core.sizeof(dt), ",") + indented_println(" \"signed\": ", (dt <: Signed), ",") + indented_println(" \"bits\": ", 8 * Base.packedsize(dt), ",") # size for reinterpret / in-register + indented_println(" \"size\": ", Base.aligned_sizeof(dt), ",") # size with padding / in-memory indented_println(" \"alignment\": ", Base.datatype_alignment(dt)) print(ctx.io, " " ^ indent, "}") end From 76057d48bfe6c78f586ac2e467fa03a4cfde6aad Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Mon, 4 Aug 2025 13:56:34 -0400 Subject: [PATCH 17/29] Minor fix-up for makefile paths --- test/trimming/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/trimming/Makefile b/test/trimming/Makefile index 9c3e01fcf404a..2601ee6b3422b 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -51,7 +51,7 @@ $(BIN)/hello$(EXE): $(BIN)/hello-o.a $(BIN)/basic_jll$(EXE): $(BIN)/basic_jll-o.a $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) -$(BIN)/capplication$(EXE): $(BIN)/capplication.c $(BIN)/libsimple.h $(BIN)/libsimple-o.a +$(BIN)/capplication$(EXE): $(SRCDIR)/capplication.c $(SRCDIR)/libsimple.h $(BIN)/libsimple-o.a $(CC) -I$(BIN) -I$(SRCDIR) -I$(JULIA_LIBDIR) -o $@ $< -Wl,--whole-archive libsimple-o.a -Wl,--no-whole-archive $(LDFLAGS_ADD) $(LDFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/capplication$(EXE) From 0c38e91cbcf8b11f34a1edc72a96ff2dcd67cfb9 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Mon, 4 Aug 2025 13:59:21 -0400 Subject: [PATCH 18/29] Update header file to latest from `JuliaLibWrapping.jl` --- test/trimming/capplication.c | 2 +- test/trimming/libsimple.h | 29 +++++++++++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/test/trimming/capplication.c b/test/trimming/capplication.c index 9455ad6b1810a..390faa282fdab 100644 --- a/test/trimming/capplication.c +++ b/test/trimming/capplication.c @@ -3,7 +3,7 @@ int main() { // Example usage of the functions defined in libsimple.h - CVectorPair_float_ vecPair; + CVectorPair_Float32 vecPair; vecPair.from.length = 3; vecPair.from.data = (float[]){1.0f, 2.0f, 3.0f}; vecPair.to.length = 3; diff --git a/test/trimming/libsimple.h b/test/trimming/libsimple.h index 2382e045f5151..ddd745f8f8ecc 100644 --- a/test/trimming/libsimple.h +++ b/test/trimming/libsimple.h @@ -4,19 +4,28 @@ #include #include -typedef struct { +struct CTree_Float64; +typedef struct CVector_CTree_Float64 { int32_t length; - float * data; -} CVector_float_; -typedef struct { - CVector_float_ from; - CVector_float_ to; -} CVectorPair_float_; -typedef struct { + struct CTree_Float64* data; +} CVector_CTree_Float64; +typedef struct CTree_Float64 { + CVector_CTree_Float64 children; +} CTree_Float64; +typedef struct MyTwoVec { int32_t x; int32_t y; } MyTwoVec; +typedef struct CVector_Float32 { + int32_t length; + float* data; +} CVector_Float32; +typedef struct CVectorPair_Float32 { + CVector_Float32 from; + CVector_Float32 to; +} CVectorPair_Float32; -int32_t countsame(MyTwoVec * list, int32_t n); -float copyto_and_sum(CVectorPair_float_ fromto); +float copyto_and_sum(CVectorPair_Float32 fromto); +int64_t tree_size(CTree_Float64 tree); +int32_t countsame(MyTwoVec* list, int32_t n); #endif // JULIALIB_LIBSIMPLE_H From 6fd68eccdf1fa0d9e4070f4b96ed4f5f2aaafff2 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Mon, 4 Aug 2025 14:31:45 -0400 Subject: [PATCH 19/29] Adjust to `Core.MethodTable` -> `Core.methodtable` --- contrib/juliac/abi_export.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/juliac/abi_export.jl b/contrib/juliac/abi_export.jl index 55a6b38ae7830..c321cf152b454 100644 --- a/contrib/juliac/abi_export.jl +++ b/contrib/juliac/abi_export.jl @@ -202,7 +202,7 @@ function write_abi_metadata(io::IO) # discover all exported methods + any types they reference exported = Core.Method[] - Base.visit(Core.GlobalMethods) do method + Base.visit(Core.methodtable) do method if isdefined(method, :ccallable) push!(exported, method) (rt, sig) = method.ccallable From 77b1916fadbe8882bc0e3e4baf32fff887ce7a7f Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Mon, 4 Aug 2025 15:14:59 -0400 Subject: [PATCH 20/29] Minor fix-up for makefile paths --- test/trimming/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/trimming/Makefile b/test/trimming/Makefile index 2601ee6b3422b..e225ca8eb8f0d 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -52,7 +52,7 @@ $(BIN)/basic_jll$(EXE): $(BIN)/basic_jll-o.a $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) $(BIN)/capplication$(EXE): $(SRCDIR)/capplication.c $(SRCDIR)/libsimple.h $(BIN)/libsimple-o.a - $(CC) -I$(BIN) -I$(SRCDIR) -I$(JULIA_LIBDIR) -o $@ $< -Wl,--whole-archive libsimple-o.a -Wl,--no-whole-archive $(LDFLAGS_ADD) $(LDFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) + $(CC) -I$(BIN) -I$(SRCDIR) -I$(JULIA_LIBDIR) -o $@ $< -Wl,--whole-archive $(BIN)/libsimple-o.a -Wl,--no-whole-archive $(LDFLAGS_ADD) $(LDFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/capplication$(EXE) $(JULIA) --depwarn=error $(SRCDIR)/trimming.jl $< From c8a0c0b9afe506bfd7b26974c5d8af6e4b093602 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Mon, 4 Aug 2025 17:48:30 -0400 Subject: [PATCH 21/29] Update `test/trimming/trimming.jl` for JSON ABI format --- test/trimming/Makefile | 4 +-- test/trimming/trimming.jl | 56 ++++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/test/trimming/Makefile b/test/trimming/Makefile index e225ca8eb8f0d..3846f92371475 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -43,7 +43,7 @@ $(BIN)/basic_jll-o.a: $(SRCDIR)/basic_jll.jl $(JULIAC_BUILDSCRIPT) $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --project=$(SRCDIR) --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-exe true $(BIN)/libsimple-o.a: $(SRCDIR)/libsimple.jl $(JULIAC_BUILDSCRIPT) - $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-lib true $(BIN)/bindinginfo_libsimple.log + $(JULIA) -t 1 -J $(JULIA_LIBDIR)/julia/sys.$(SHLIB_EXT) --startup-file=no --history-file=no --output-o $@ --output-incremental=no --strip-ir --strip-metadata --experimental --trim $(JULIAC_BUILDSCRIPT) $< --output-lib true $(BIN)/bindinginfo_libsimple.json $(BIN)/hello$(EXE): $(BIN)/hello-o.a $(CC) -o $@ $(WHOLE_ARCHIVE) $< $(NO_WHOLE_ARCHIVE) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) @@ -58,7 +58,7 @@ check: $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/capplication$(EXE) $(JULIA) --depwarn=error $(SRCDIR)/trimming.jl $< clean: - -rm -f $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/basic_jll-o.a $(BIN)/libsimple-o.a $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/capplication$(EXE) $(BIN)/bindinginfo_libsimple.log + -rm -f $(BIN)/hello$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/basic_jll-o.a $(BIN)/libsimple-o.a $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/capplication$(EXE) $(BIN)/bindinginfo_libsimple.json .PHONY: release clean check diff --git a/test/trimming/trimming.jl b/test/trimming/trimming.jl index 9744be3ed155d..7e6dc53c5a851 100644 --- a/test/trimming/trimming.jl +++ b/test/trimming/trimming.jl @@ -1,4 +1,9 @@ +import Pkg + +Pkg.add(["JSON"]) + using Test +using JSON @test length(ARGS) == 1 bindir = dirname(ARGS[1]) @@ -24,28 +29,31 @@ let exe_suffix = splitext(Base.julia_exename())[2] @test lines[2] == "Count of same vectors: 1" # Test that the logging of entrypoints and types works correctly - str = read(joinpath(bindir, "bindinginfo_libsimple.log"), String) - @test occursin("copyto_and_sum(fromto::CVectorPair{Float32})::Float32", str) - @test occursin( - """ - CVector{Float32} - length::Int32[0] - data::Ptr{Float32}[8] - 16 bytes""", str - ) - @test occursin( - """ - CVectorPair{Float32} - from::CVector{Float32}[0] - to::CVector{Float32}[16] - 32 bytes""", str - ) - # ensure that there is a blank line between methods and types - lines = split(str, '\n'; keepempty=true) - nblanks = 0 - for line in lines - nblanks += isempty(line) - occursin("length", line) && break - end - @test nblanks == 1 + str = read(joinpath(bindir, "bindinginfo_libsimple.json"), String) + + # The log should parse as valid JSON + abi = JSON.Parser.parse(str) + + # `copyto_and_sum` should have been exported + @test any(Bool[func["symbol"] == "copyto_and_sum" for func in abi["functions"]]) + + # `CVector{Float32}` should have been exported with the correct info + @test any(Bool[type["name"] == "CVector{Float32}" for type in abi["types"]]) + CVector_Float32 = abi["types"][findfirst(type["name"] == "CVector{Float32}" for type in abi["types"])] + @test length(CVector_Float32["fields"]) == 2 + @test CVector_Float32["fields"][1]["offset"] == 0 + @test CVector_Float32["fields"][2]["offset"] == 8 + @test abi["types"][CVector_Float32["fields"][1]["type"]]["name"] == "Int32" + @test abi["types"][CVector_Float32["fields"][2]["type"]]["name"] == "Ptr{Float32}" + @test CVector_Float32["size"] == 16 + + # `CVectorPair{Float32}` should have been exported with the correct info + @test any(Bool[type["name"] == "CVectorPair{Float32}" for type in abi["types"]]) + CVectorPair_Float32 = abi["types"][findfirst(type["name"] == "CVectorPair{Float32}" for type in abi["types"])] + @test length(CVectorPair_Float32["fields"]) == 2 + @test CVectorPair_Float32["fields"][1]["offset"] == 0 + @test CVectorPair_Float32["fields"][2]["offset"] == 16 + @test abi["types"][CVectorPair_Float32["fields"][1]["type"]]["name"] == "CVector{Float32}" + @test abi["types"][CVectorPair_Float32["fields"][2]["type"]]["name"] == "CVector{Float32}" + @test CVectorPair_Float32["size"] == 32 end From af8e0426b00626190af4efab652f0a825302abd3 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Tue, 19 Aug 2025 16:17:07 -0400 Subject: [PATCH 22/29] Rename `type` to `type_id` --- contrib/juliac/abi_export.jl | 8 ++++---- test/trimming/trimming.jl | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contrib/juliac/abi_export.jl b/contrib/juliac/abi_export.jl index c321cf152b454..4eeccf823fd96 100644 --- a/contrib/juliac/abi_export.jl +++ b/contrib/juliac/abi_export.jl @@ -74,7 +74,7 @@ function emit_pointer_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); inden indented_println(" \"id\": ", ctx.type_ids[dt], ",") indented_println(" \"kind\": \"pointer\",") indented_println(" \"name\": ", type_name_json(dt), ",") - indented_println(" \"pointee\": ", pointee_type_id) + indented_println(" \"pointee_type_id\": ", pointee_type_id) print(ctx.io, " " ^ indent, "}") end end @@ -85,7 +85,7 @@ function emit_field_info!(ctx::TypeEmitter, @nospecialize(dt::DataType), field:: print(ctx.io, " " ^ indent) print(ctx.io, "{") print(ctx.io, " \"name\": ", field_name_json(dt, field), ",") - print(ctx.io, " \"type\": ", type_id, ",") + print(ctx.io, " \"type_id\": ", type_id, ",") print(ctx.io, " \"offset\": ", desc.offset) print(ctx.io, " }") end @@ -161,11 +161,11 @@ function emit_method_info!(ctx::TypeEmitter, method::Core.Method; indent::Int = print(ctx.io, " " ^ (indent + 4)) print(ctx.io, "{") print(ctx.io, " \"name\": ", escape_string_json(argnames[i]), ",") - print(ctx.io, " \"type\": ", ctx.type_ids[sig.parameters[i]]) + print(ctx.io, " \"type_id\": ", ctx.type_ids[sig.parameters[i]]) println(ctx.io, i == length(sig.parameters) ? " }" : " },") end indented_println(" ],") - indented_println(" \"returns\": { \"type\": ", ctx.type_ids[rt], " }") + indented_println(" \"returns\": { \"type_id\": ", ctx.type_ids[rt], " }") print(ctx.io, " " ^ indent, "}") end end diff --git a/test/trimming/trimming.jl b/test/trimming/trimming.jl index 7e6dc53c5a851..3868ec4c41f36 100644 --- a/test/trimming/trimming.jl +++ b/test/trimming/trimming.jl @@ -43,8 +43,8 @@ let exe_suffix = splitext(Base.julia_exename())[2] @test length(CVector_Float32["fields"]) == 2 @test CVector_Float32["fields"][1]["offset"] == 0 @test CVector_Float32["fields"][2]["offset"] == 8 - @test abi["types"][CVector_Float32["fields"][1]["type"]]["name"] == "Int32" - @test abi["types"][CVector_Float32["fields"][2]["type"]]["name"] == "Ptr{Float32}" + @test abi["types"][CVector_Float32["fields"][1]["type_id"]]["name"] == "Int32" + @test abi["types"][CVector_Float32["fields"][2]["type_id"]]["name"] == "Ptr{Float32}" @test CVector_Float32["size"] == 16 # `CVectorPair{Float32}` should have been exported with the correct info @@ -53,7 +53,7 @@ let exe_suffix = splitext(Base.julia_exename())[2] @test length(CVectorPair_Float32["fields"]) == 2 @test CVectorPair_Float32["fields"][1]["offset"] == 0 @test CVectorPair_Float32["fields"][2]["offset"] == 16 - @test abi["types"][CVectorPair_Float32["fields"][1]["type"]]["name"] == "CVector{Float32}" - @test abi["types"][CVectorPair_Float32["fields"][2]["type"]]["name"] == "CVector{Float32}" + @test abi["types"][CVectorPair_Float32["fields"][1]["type_id"]]["name"] == "CVector{Float32}" + @test abi["types"][CVectorPair_Float32["fields"][2]["type_id"]]["name"] == "CVector{Float32}" @test CVectorPair_Float32["size"] == 32 end From 6161dab6c5f21f4b3044d90a71c9cf8ed230659a Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Tue, 19 Aug 2025 16:19:55 -0400 Subject: [PATCH 23/29] Add recursive type tests --- test/trimming/trimming.jl | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/trimming/trimming.jl b/test/trimming/trimming.jl index 3868ec4c41f36..dacf9c2498146 100644 --- a/test/trimming/trimming.jl +++ b/test/trimming/trimming.jl @@ -56,4 +56,26 @@ let exe_suffix = splitext(Base.julia_exename())[2] @test abi["types"][CVectorPair_Float32["fields"][1]["type_id"]]["name"] == "CVector{Float32}" @test abi["types"][CVectorPair_Float32["fields"][2]["type_id"]]["name"] == "CVector{Float32}" @test CVectorPair_Float32["size"] == 32 + + # `CTree{Float64}` should have been exported with the correct info + @test any(Bool[type["name"] == "CTree{Float64}" for type in abi["types"]]) + CTree_Float64_id = findfirst(type["name"] == "CTree{Float64}" for type in abi["types"]) + CTree_Float64 = abi["types"][CTree_Float64_id] + @test length(CTree_Float64["fields"]) == 1 + @test CTree_Float64["fields"][1]["offset"] == 0 + CVector_CTree_Float64 = abi["types"][CTree_Float64["fields"][1]["type_id"]] + @test CVector_CTree_Float64["name"] == "CVector{CTree{Float64}}" + @test CTree_Float64["size"] == sizeof(UInt) * 2 + + # `CVector{CTree{Float64}}` should have been exported with the correct info + @test length(CVector_CTree_Float64["fields"]) == 2 + @test CVector_CTree_Float64["fields"][1]["offset"] == 0 + @test CVector_CTree_Float64["fields"][2]["offset"] == sizeof(UInt) + @test abi["types"][CVector_CTree_Float64["fields"][1]["type_id"]]["name"] == "Int32" + @test abi["types"][CVector_CTree_Float64["fields"][2]["type_id"]]["name"] == "Ptr{CTree{Float64}}" + @test CVector_CTree_Float64["size"] == sizeof(UInt) * 2 + + # `Ptr{CTree{Float64}}` should refer (recursively) back to the original type id + Ptr_CTree_Float64 = abi["types"][CVector_CTree_Float64["fields"][2]["type_id"]] + @test Ptr_CTree_Float64["pointee_type_id"] == CTree_Float64_id end From cb503ee5aab737eac5380f351d85c662645779b1 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 31 Aug 2025 03:41:00 -0500 Subject: [PATCH 24/29] Update contrib/juliac/abi_export.jl Co-authored-by: Jameson Nash --- contrib/juliac/abi_export.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/juliac/abi_export.jl b/contrib/juliac/abi_export.jl index 4eeccf823fd96..211b83f4a36b3 100644 --- a/contrib/juliac/abi_export.jl +++ b/contrib/juliac/abi_export.jl @@ -95,7 +95,7 @@ function emit_struct_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent let indented_println(args...) = println(ctx.io, " " ^ indent, args...) indented_println("{") indented_println(" \"id\": ", type_id, ",") - indented_println(" \"kind\": \"struct\",") + indented_println(ismutabletype(dt) ?(" \"kind\": \"mutable struct\",") : " \"kind\": \"struct\",") indented_println(" \"name\": ", type_name_json(dt), ",") indented_println(" \"size\": ", Core.sizeof(dt), ",") indented_println(" \"alignment\": ", Base.datatype_alignment(dt), ",") From 93a0557f18aa8328b36fd083434d91621851d633 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 31 Aug 2025 03:51:15 -0500 Subject: [PATCH 25/29] Add isptr and isfieldatomic --- contrib/juliac/abi_export.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/juliac/abi_export.jl b/contrib/juliac/abi_export.jl index 211b83f4a36b3..317af3cf34b99 100644 --- a/contrib/juliac/abi_export.jl +++ b/contrib/juliac/abi_export.jl @@ -86,7 +86,9 @@ function emit_field_info!(ctx::TypeEmitter, @nospecialize(dt::DataType), field:: print(ctx.io, "{") print(ctx.io, " \"name\": ", field_name_json(dt, field), ",") print(ctx.io, " \"type_id\": ", type_id, ",") - print(ctx.io, " \"offset\": ", desc.offset) + print(ctx.io, " \"offset\": ", desc.offset, ",") + print(ctx.io, " \"isptr\": ", desc.isptr, ",") + print(ctx.io, " \"isfieldatomic\": ", Base.isfieldatomic(dt, field)) print(ctx.io, " }") end From be561b78e24795b74b73b17ab377de863112b071 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 31 Aug 2025 11:31:15 -0500 Subject: [PATCH 26/29] Fix spacing --- contrib/juliac/abi_export.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/juliac/abi_export.jl b/contrib/juliac/abi_export.jl index 317af3cf34b99..a58f1f024a1d5 100644 --- a/contrib/juliac/abi_export.jl +++ b/contrib/juliac/abi_export.jl @@ -97,7 +97,7 @@ function emit_struct_info!(ctx::TypeEmitter, @nospecialize(dt::DataType); indent let indented_println(args...) = println(ctx.io, " " ^ indent, args...) indented_println("{") indented_println(" \"id\": ", type_id, ",") - indented_println(ismutabletype(dt) ?(" \"kind\": \"mutable struct\",") : " \"kind\": \"struct\",") + indented_println(ismutabletype(dt) ? " \"kind\": \"mutable struct\"," : " \"kind\": \"struct\",") indented_println(" \"name\": ", type_name_json(dt), ",") indented_println(" \"size\": ", Core.sizeof(dt), ",") indented_println(" \"alignment\": ", Base.datatype_alignment(dt), ",") From 7adc37b29a9f2229fbc93aca2c7c9c33486a8d52 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 31 Aug 2025 15:34:18 -0500 Subject: [PATCH 27/29] Debug tests --- test/trimming/Makefile | 2 +- test/trimming/trimming.jl | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/trimming/Makefile b/test/trimming/Makefile index 5c3705ea5042e..353a91185423f 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -61,7 +61,7 @@ $(BIN)/capplication$(EXE): $(SRCDIR)/capplication.c $(SRCDIR)/libsimple.h $(BIN) $(CC) -I$(BIN) -I$(SRCDIR) -I$(JULIA_LIBDIR) -o $@ $< -Wl,--whole-archive $(BIN)/libsimple-o.a -Wl,--no-whole-archive $(LDFLAGS_ADD) $(LDFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) check: $(BIN)/hello$(EXE) $(BIN)/trimmability$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/capplication$(EXE) - $(JULIA) --depwarn=error $(SRCDIR)/trimming.jl $< + $(JULIA) --startup-file=no --history-file=no --depwarn=error --project=$(SRCDIR) $(SRCDIR)/trimming.jl $< clean: -rm -f $(BIN)/hello$(EXE) $(BIN)/trimmability$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/trimmability-o.a $(BIN)/basic_jll-o.a $(BIN)/libsimple-o.a $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/capplication$(EXE) $(BIN)/bindinginfo_libsimple.json diff --git a/test/trimming/trimming.jl b/test/trimming/trimming.jl index 15c8439513869..d255764acbf34 100644 --- a/test/trimming/trimming.jl +++ b/test/trimming/trimming.jl @@ -41,6 +41,7 @@ let exe_suffix = splitext(Base.julia_exename())[2] @test any(Bool[func["symbol"] == "copyto_and_sum" for func in abi["functions"]]) # `CVector{Float32}` should have been exported with the correct info + @show abi["types"] @test any(Bool[type["name"] == "CVector{Float32}" for type in abi["types"]]) CVector_Float32 = abi["types"][findfirst(type["name"] == "CVector{Float32}" for type in abi["types"])] @test length(CVector_Float32["fields"]) == 2 From 53b84e6f42d4f9b4c465a63a742f402660742c98 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 31 Aug 2025 16:04:10 -0500 Subject: [PATCH 28/29] Remove project --- test/trimming/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/trimming/Makefile b/test/trimming/Makefile index 353a91185423f..af5b3f530c5d8 100644 --- a/test/trimming/Makefile +++ b/test/trimming/Makefile @@ -61,7 +61,7 @@ $(BIN)/capplication$(EXE): $(SRCDIR)/capplication.c $(SRCDIR)/libsimple.h $(BIN) $(CC) -I$(BIN) -I$(SRCDIR) -I$(JULIA_LIBDIR) -o $@ $< -Wl,--whole-archive $(BIN)/libsimple-o.a -Wl,--no-whole-archive $(LDFLAGS_ADD) $(LDFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) check: $(BIN)/hello$(EXE) $(BIN)/trimmability$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/capplication$(EXE) - $(JULIA) --startup-file=no --history-file=no --depwarn=error --project=$(SRCDIR) $(SRCDIR)/trimming.jl $< + $(JULIA) --startup-file=no --history-file=no --depwarn=error $(SRCDIR)/trimming.jl $< clean: -rm -f $(BIN)/hello$(EXE) $(BIN)/trimmability$(EXE) $(BIN)/basic_jll$(EXE) $(BIN)/hello-o.a $(BIN)/trimmability-o.a $(BIN)/basic_jll-o.a $(BIN)/libsimple-o.a $(BIN)/libsimple.$(SHLIB_EXT) $(BIN)/capplication$(EXE) $(BIN)/bindinginfo_libsimple.json From 4dfc45ba188feea44ec7a65e25fa6383b7a004e0 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 31 Aug 2025 19:13:37 -0500 Subject: [PATCH 29/29] Remove module qualification --- contrib/juliac/abi_export.jl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contrib/juliac/abi_export.jl b/contrib/juliac/abi_export.jl index a58f1f024a1d5..1d5003d99ac92 100644 --- a/contrib/juliac/abi_export.jl +++ b/contrib/juliac/abi_export.jl @@ -58,8 +58,10 @@ function escape_string_json(s::AbstractString) return String(take!(iob)) end +dequalify(str::AbstractString) = last(split(str, '.')) + function type_name_json(@nospecialize(dt::DataType)) - return escape_string_json(repr(dt)) + return escape_string_json(dequalify(repr(dt))) end function field_name_json(@nospecialize(dt::DataType), field::Int)