Skip to content

Commit e06a591

Browse files
vchuravytimholyfingolfin
committed
Allow re-initialization and caching of foreign types (#47407)
Co-authored-by: Tim Holy <[email protected]> Co-authored-by: Max Horn <[email protected]>
1 parent cbfdb3f commit e06a591

File tree

17 files changed

+254
-6
lines changed

17 files changed

+254
-6
lines changed

src/datatype.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,22 @@ JL_DLLEXPORT jl_datatype_t * jl_new_foreign_type(jl_sym_t *name,
826826
return bt;
827827
}
828828

829+
JL_DLLEXPORT int jl_reinit_foreign_type(jl_datatype_t *dt,
830+
jl_markfunc_t markfunc,
831+
jl_sweepfunc_t sweepfunc)
832+
{
833+
if (!jl_is_foreign_type(dt))
834+
return 0;
835+
const jl_datatype_layout_t *layout = dt->layout;
836+
jl_fielddescdyn_t * desc =
837+
(jl_fielddescdyn_t *) ((char *)layout + sizeof(*layout));
838+
assert(!desc->markfunc);
839+
assert(!desc->sweepfunc);
840+
desc->markfunc = markfunc;
841+
desc->sweepfunc = sweepfunc;
842+
return 1;
843+
}
844+
829845
JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt)
830846
{
831847
return jl_is_datatype(dt) && dt->layout && dt->layout->fielddesc_type == 3;

src/jl_exported_funcs.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@
343343
XX(jl_new_code_info_uninit) \
344344
XX(jl_new_datatype) \
345345
XX(jl_new_foreign_type) \
346+
XX(jl_reinit_foreign_type) \
346347
XX(jl_new_method_instance_uninit) \
347348
XX(jl_new_method_table) \
348349
XX(jl_new_method_uninit) \

src/julia_gcext.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ JL_DLLEXPORT jl_datatype_t *jl_new_foreign_type(
4949
int haspointers,
5050
int large);
5151

52+
53+
#define HAVE_JL_REINIT_FOREIGN_TYPE 1
54+
JL_DLLEXPORT int jl_reinit_foreign_type(
55+
jl_datatype_t *dt,
56+
jl_markfunc_t markfunc,
57+
jl_sweepfunc_t sweepfunc);
58+
5259
JL_DLLEXPORT int jl_is_foreign_type(jl_datatype_t *dt);
5360

5461
JL_DLLEXPORT size_t jl_gc_max_internal_obj_size(void);

src/staticdata.c

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ External links:
7777

7878
#include "julia.h"
7979
#include "julia_internal.h"
80+
#include "julia_gcext.h"
8081
#include "builtin_proto.h"
8182
#include "processor.h"
8283
#include "serialize.h"
@@ -1249,6 +1250,9 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED
12491250
ios_write(s->s, (char*)v, sizeof(void*) + jl_string_len(v));
12501251
write_uint8(s->s, '\0'); // null-terminated strings for easier C-compatibility
12511252
}
1253+
else if (jl_is_foreign_type(t) == 1) {
1254+
jl_error("Cannot serialize instances of foreign datatypes");
1255+
}
12521256
else if (jl_datatype_nfields(t) == 0) {
12531257
// The object has no fields, so we just snapshot its byte representation
12541258
assert(!t->layout->npointers);
@@ -1438,10 +1442,14 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED
14381442
if (dt->layout != NULL) {
14391443
size_t nf = dt->layout->nfields;
14401444
size_t np = dt->layout->npointers;
1441-
size_t fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type);
1445+
size_t fieldsize = 0;
1446+
uint8_t is_foreign_type = dt->layout->fielddesc_type == 3;
1447+
if (!is_foreign_type) {
1448+
fieldsize = jl_fielddesc_size(dt->layout->fielddesc_type);
1449+
}
14421450
char *flddesc = (char*)dt->layout;
14431451
size_t fldsize = sizeof(jl_datatype_layout_t) + nf * fieldsize;
1444-
if (dt->layout->first_ptr != -1)
1452+
if (!is_foreign_type && dt->layout->first_ptr != -1)
14451453
fldsize += np << dt->layout->fielddesc_type;
14461454
uintptr_t layout = LLT_ALIGN(ios_pos(s->const_data), sizeof(void*));
14471455
write_padding(s->const_data, layout - ios_pos(s->const_data)); // realign stream
@@ -1450,6 +1458,13 @@ static void jl_write_values(jl_serializer_state *s) JL_GC_DISABLED
14501458
arraylist_push(&s->relocs_list, (void*)(reloc_offset + offsetof(jl_datatype_t, layout))); // relocation location
14511459
arraylist_push(&s->relocs_list, (void*)(((uintptr_t)ConstDataRef << RELOC_TAG_OFFSET) + layout)); // relocation target
14521460
ios_write(s->const_data, flddesc, fldsize);
1461+
if (is_foreign_type) {
1462+
// make sure we have space for the extra hidden pointers
1463+
// zero them since they will need to be re-initialized externally
1464+
assert(fldsize == sizeof(jl_datatype_layout_t));
1465+
jl_fielddescdyn_t dyn = {0, 0};
1466+
ios_write(s->const_data, (char*)&dyn, sizeof(jl_fielddescdyn_t));
1467+
}
14531468
}
14541469
}
14551470
else if (jl_is_typename(v)) {

test/gcext/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
/gcext
22
/gcext-debug
3+
/Foreign/deps
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# This file is machine-generated - editing it directly is not advised
2+
3+
julia_version = "1.8.3"
4+
manifest_format = "2.0"
5+
project_hash = "e7199d961a5f4ebad68a3deaf5beaa7406a0afcb"
6+
7+
[[deps.Foreign]]
8+
deps = ["Libdl"]
9+
path = "../Foreign"
10+
uuid = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4"
11+
version = "0.1.0"
12+
13+
[[deps.Libdl]]
14+
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name = "DependsOnForeign"
2+
uuid = "4b0716e0-dfb5-4e00-8b44-e2685a41517f"
3+
version = "0.1.0"
4+
5+
[deps]
6+
Foreign = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module DependsOnForeign
2+
3+
using Foreign
4+
5+
f(obj::FObj) = Base.pointer_from_objref(obj)
6+
precompile(f, (FObj,))
7+
8+
const FObjRef = Ref{FObj}()
9+
10+
function __init__()
11+
FObjRef[] = FObj()
12+
end
13+
14+
end # module DependsOnForeign

test/gcext/Foreign/Manifest.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# This file is machine-generated - editing it directly is not advised
2+
3+
julia_version = "1.9.0-DEV"
4+
manifest_format = "2.0"
5+
project_hash = "7b70172a2edbdc772ed789e79d4411d7528eae86"
6+
7+
[[deps.Libdl]]
8+
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

test/gcext/Foreign/Project.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name = "Foreign"
2+
uuid = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4"
3+
version = "0.1.0"
4+
5+
[deps]
6+
Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb"

test/gcext/Foreign/deps/foreignlib.c

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
#include "julia.h"
4+
#include "julia_gcext.h"
5+
6+
// TODO make these atomics
7+
int nmarks = 0;
8+
int nsweeps = 0;
9+
10+
uintptr_t mark(jl_ptls_t ptls, jl_value_t *p)
11+
{
12+
nmarks += 1;
13+
return 0;
14+
}
15+
16+
void sweep(jl_value_t *p)
17+
{
18+
nsweeps++;
19+
}
20+
21+
JL_DLLEXPORT jl_datatype_t *declare_foreign(jl_sym_t* name, jl_module_t *module, jl_datatype_t *parent)
22+
{
23+
return jl_new_foreign_type(name, module, parent, mark, sweep, 1, 0);
24+
}
25+
26+
// #define GC_MAX_SZCLASS (2032 - sizeof(void *))
27+
28+
JL_DLLEXPORT int reinit_foreign(jl_datatype_t *dt)
29+
{
30+
int ret = jl_reinit_foreign_type(dt, mark, sweep);
31+
nmarks = nsweeps = 0;
32+
if (ret == 0)
33+
return 0;
34+
if (dt->layout->npointers != 1)
35+
return -1;
36+
if (dt->layout->size != 0)
37+
return -2;
38+
return ret;
39+
}
40+
41+
JL_DLLEXPORT jl_value_t *allocate_foreign(jl_ptls_t ptls, size_t sz, jl_datatype_t *dt)
42+
{
43+
jl_value_t* obj = jl_gc_alloc_typed(ptls, sz, dt);
44+
jl_gc_schedule_foreign_sweepfunc(ptls, obj);
45+
return obj;
46+
}
47+
48+
JL_DLLEXPORT int nmark_counter()
49+
{
50+
return nmarks;
51+
}
52+
53+
JL_DLLEXPORT int nsweep_counter()
54+
{
55+
return nsweeps;
56+
}

test/gcext/Foreign/src/Foreign.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# This file is a part of Julia. License is MIT: https://julialang.org/license
2+
3+
module Foreign
4+
5+
using Libdl
6+
7+
const foreignlib = joinpath(ENV["BINDIR"], "foreignlib.$(dlext)")
8+
9+
const FObj = ccall((:declare_foreign, foreignlib), Any, (Any, Any, Any), :FObj, @__MODULE__, Any)
10+
FObj() = ccall((:allocate_foreign, foreignlib), Any, (Ptr{Cvoid}, Csize_t, Any,), Core.getptls(), sizeof(Ptr{Cvoid}), FObj)::FObj
11+
12+
export FObj
13+
14+
get_nmark() = ccall((:nmark_counter, foreignlib), Cint, ())
15+
get_nsweep() = ccall((:nsweep_counter, foreignlib), Cint, ())
16+
17+
function __init__()
18+
@assert ccall((:reinit_foreign, foreignlib), Cint, (Any,), FObj) == 1
19+
end
20+
21+
allocs(N) = [Foreign.FObj() for _ in 1:N]
22+
23+
function test(N)
24+
x = allocs(N)
25+
Core.donotdelete(x)
26+
x = nothing
27+
end
28+
29+
end # module Foreign
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# This file is machine-generated - editing it directly is not advised
2+
3+
julia_version = "1.8.3"
4+
manifest_format = "2.0"
5+
project_hash = "e7199d961a5f4ebad68a3deaf5beaa7406a0afcb"
6+
7+
[[deps.Foreign]]
8+
deps = ["Libdl"]
9+
path = "../Foreign"
10+
uuid = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4"
11+
version = "0.1.0"
12+
13+
[[deps.Libdl]]
14+
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
name = "ForeignObjSerialization"
2+
uuid = "2c015d96-a6ca-42f0-bc68-f9090de6bc2c"
3+
version = "0.1.0"
4+
5+
[deps]
6+
Foreign = "de1f6f7a-d7b3-400f-91c2-33f248ee89c4"
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module ForeignObjSerialization
2+
3+
using Foreign
4+
const FObjRef = Ref{FObj}(FObj())
5+
6+
end # module ForeignObjSerialization

test/gcext/Makefile

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,54 @@ SRCDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST))))
1919
# get the executable suffix, if any
2020
EXE := $(suffix $(abspath $(JULIA)))
2121

22+
OS := $(shell uname)
23+
ifeq ($(OS), Darwin)
24+
DYLIB := .dylib
25+
else
26+
DYLIB := .so
27+
endif
28+
2229
# get compiler and linker flags. (see: `contrib/julia-config.jl`)
2330
JULIA_CONFIG := $(JULIA) -e 'include(joinpath(Sys.BINDIR, Base.DATAROOTDIR, "julia", "julia-config.jl"))' --
2431
CPPFLAGS_ADD :=
2532
CFLAGS_ADD = $(shell $(JULIA_CONFIG) --cflags)
2633
LDFLAGS_ADD = -lm $(shell $(JULIA_CONFIG) --ldflags --ldlibs)
34+
DYLIBFLAGS := --shared -fPIC
2735

2836
DEBUGFLAGS += -g
2937

3038
#=============================================================================
3139

32-
release: $(BIN)/gcext$(EXE)
33-
debug: $(BIN)/gcext-debug$(EXE)
40+
release: $(BIN)/gcext$(EXE) $(BIN)/Foreign/deps/foreignlib$(DYLIB)
41+
debug: $(BIN)/gcext-debug$(EXE) $(BIN)/Foreign/deps/foreignlib-debug$(DYLIB)
3442

3543
$(BIN)/gcext$(EXE): $(SRCDIR)/gcext.c
3644
$(CC) $^ -o $@ $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS)
3745

3846
$(BIN)/gcext-debug$(EXE): $(SRCDIR)/gcext.c
3947
$(CC) $^ -o $@ $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) $(DEBUGFLAGS)
4048

49+
$(BIN)/foreignlib$(DYLIB): $(SRCDIR)/Foreign/deps/foreignlib.c
50+
$(CC) $^ -o $@ $(DYLIBFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS)
51+
52+
$(BIN)/foreignlib-debug$(DYLIB): $(SRCDIR)/Foreign/deps/foreignlib.c
53+
$(CC) $^ -o $@ $(DYLIBFLAGS) $(CPPFLAGS_ADD) $(CPPFLAGS) $(CFLAGS_ADD) $(CFLAGS) $(LDFLAGS_ADD) $(LDFLAGS) $(DEBUGFLAGS)
54+
4155
ifneq ($(abspath $(BIN)),$(abspath $(SRCDIR)))
4256
# for demonstration purposes, our demo code is also installed
4357
# in $BIN, although this would likely not be typical
4458
$(BIN)/LocalTest.jl: $(SRCDIR)/LocalTest.jl
4559
cp $< $@
4660
endif
4761

48-
check: $(BIN)/gcext$(EXE) $(BIN)/LocalTest.jl
49-
$(JULIA) --depwarn=error $(SRCDIR)/gcext-test.jl $<
62+
check: $(BIN)/gcext$(EXE) $(BIN)/LocalTest.jl $(BIN)/foreignlib$(DYLIB)
63+
BINDIR=$(BIN) $(JULIA) --depwarn=error $(SRCDIR)/gcext-test.jl $<
5064
@echo SUCCESS
5165

5266
clean:
5367
-rm -f $(BIN)/gcext-debug$(EXE) $(BIN)/gcext$(EXE)
68+
-rm -f $(BIN)/foreignlib$(DYLIB)
69+
-rm -f $(BIN)/foreignlib-debug$(DYLIB)
5470

5571
.PHONY: release debug clean check
5672

test/gcext/gcext-test.jl

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# tests the output of the embedding example is correct
44
using Test
5+
using Pkg
56

67
if Sys.iswindows()
78
# libjulia needs to be in the same directory as the embedding executable or in path
@@ -43,3 +44,35 @@ end
4344
@test checknum(lines[5], r"([0-9]+) corrupted auxiliary roots",
4445
n -> n == 0)
4546
end
47+
48+
@testset "Package with foreign type" begin
49+
load_path = copy(LOAD_PATH)
50+
push!(LOAD_PATH, joinpath(@__DIR__, "Foreign"))
51+
push!(LOAD_PATH, joinpath(@__DIR__, "DependsOnForeign"))
52+
try
53+
# Force recaching
54+
Base.compilecache(Base.identify_package("Foreign"))
55+
Base.compilecache(Base.identify_package("DependsOnForeign"))
56+
57+
push!(LOAD_PATH, joinpath(@__DIR__, "ForeignObjSerialization"))
58+
@test_throws ErrorException Base.compilecache(Base.identify_package("ForeignObjSerialization"), Base.DevNull())
59+
pop!(LOAD_PATH)
60+
61+
(@eval (using Foreign))
62+
@test Base.invokelatest(Foreign.get_nmark) == 0
63+
@test Base.invokelatest(Foreign.get_nsweep) == 0
64+
65+
obj = Base.invokelatest(Foreign.FObj)
66+
GC.@preserve obj begin
67+
GC.gc(true)
68+
end
69+
@test Base.invokelatest(Foreign.get_nmark) > 0
70+
@time Base.invokelatest(Foreign.test, 10)
71+
GC.gc(true)
72+
@test Base.invokelatest(Foreign.get_nsweep) > 0
73+
(@eval (using DependsOnForeign))
74+
Base.invokelatest(DependsOnForeign.f, obj)
75+
finally
76+
copy!(LOAD_PATH, load_path)
77+
end
78+
end

0 commit comments

Comments
 (0)