Skip to content

Commit e1a8b94

Browse files
vchuravygbaraldi
andcommitted
Allow for :foreigncall to transition to GC safe automatically
Co-authored-by: Gabriel Baraldi <baraldigabriel@gmail.com>
1 parent 888cf03 commit e1a8b94

15 files changed

+146
-41
lines changed

Diff for: Compiler/src/abstractinterpretation.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -3409,7 +3409,7 @@ function abstract_eval_foreigncall(interp::AbstractInterpreter, e::Expr, sstate:
34093409
abstract_eval_value(interp, x, sstate, sv)
34103410
end
34113411
cconv = e.args[5]
3412-
if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt16}))
3412+
if isa(cconv, QuoteNode) && (v = cconv.value; isa(v, Tuple{Symbol, UInt16, Bool}))
34133413
override = decode_effects_override(v[2])
34143414
effects = override_effects(effects, override)
34153415
end

Diff for: Compiler/src/validation.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}(
2323
:meta => 0:typemax(Int),
2424
:global => 1:1,
2525
:globaldecl => 1:2,
26-
:foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects), args..., roots...
26+
:foreigncall => 5:typemax(Int), # name, RT, AT, nreq, (cconv, effects, gc_safe), args..., roots...
2727
:cfunction => 5:5,
2828
:isdefined => 1:2,
2929
:code_coverage_effect => 0:0,

Diff for: base/c.jl

+44-7
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,32 @@ The above input outputs this:
268268
269269
(:printf, :Cvoid, [:Cstring, :Cuint], ["%d", :value])
270270
"""
271-
function ccall_macro_parse(expr::Expr)
271+
function ccall_macro_parse(exprs)
272+
gc_safe = false
273+
expr = nothing
274+
ccall(:jl_, Cvoid, (Any,), exprs)
275+
if exprs isa Expr
276+
expr = exprs
277+
elseif length(exprs) == 1
278+
expr = exprs[1]
279+
elseif length(exprs) == 2
280+
gc_expr = exprs[1]
281+
expr = exprs[2]
282+
if gc_expr.head == :(=) && gc_expr.args[1] == :gc_safe
283+
if gc_expr.args[2] == true
284+
gc_safe = true
285+
elseif gc_expr.args[2] == false
286+
gc_safe = false
287+
else
288+
throw(ArgumentError("gc_safe must be true or false"))
289+
end
290+
else
291+
throw(ArgumentError("@ccall option must be `gc_safe=true` or `gc_safe=false`"))
292+
end
293+
else
294+
throw(ArgumentError("@ccall needs a function signature with a return type"))
295+
end
296+
272297
# setup and check for errors
273298
if !isexpr(expr, :(::))
274299
throw(ArgumentError("@ccall needs a function signature with a return type"))
@@ -328,12 +353,11 @@ function ccall_macro_parse(expr::Expr)
328353
pusharg!(a)
329354
end
330355
end
331-
332-
return func, rettype, types, args, nreq
356+
return func, rettype, types, args, gc_safe, nreq
333357
end
334358

335359

336-
function ccall_macro_lower(convention, func, rettype, types, args, nreq)
360+
function ccall_macro_lower(convention, func, rettype, types, args, gc_safe, nreq)
337361
statements = []
338362

339363
# if interpolation was used, ensure the value is a function pointer at runtime.
@@ -351,9 +375,15 @@ function ccall_macro_lower(convention, func, rettype, types, args, nreq)
351375
else
352376
func = esc(func)
353377
end
378+
cconv = nothing
379+
if convention isa Tuple
380+
cconv = Expr(:cconv, (convention..., gc_safe), nreq)
381+
else
382+
cconv = Expr(:cconv, (convention, UInt16(0), gc_safe), nreq)
383+
end
354384

355385
return Expr(:block, statements...,
356-
Expr(:call, :ccall, func, Expr(:cconv, convention, nreq), esc(rettype),
386+
Expr(:call, :ccall, func, cconv, esc(rettype),
357387
Expr(:tuple, map(esc, types)...), map(esc, args)...))
358388
end
359389

@@ -404,9 +434,16 @@ Example using an external library:
404434
405435
The string literal could also be used directly before the function
406436
name, if desired `"libglib-2.0".g_uri_escape_string(...`
437+
438+
It's possible to declare the ccall as `gc_safe` by using the `gc_safe = true` option:
439+
@ccall gc_safe=true strlen(s::Cstring)::Csize_t
440+
This allows the garbage collector to run concurrently with the ccall, which can be useful whenever
441+
the ccall may block outside of julia.
442+
WARNING: This option should be used with caution, as it can lead to undefined behavior if the ccall
443+
calls back into the julia runtime. (@cfunction/@ccallables are safe however)
407444
"""
408-
macro ccall(expr)
409-
return ccall_macro_lower(:ccall, ccall_macro_parse(expr)...)
445+
macro ccall(exprs...)
446+
return ccall_macro_lower((:ccall), ccall_macro_parse(exprs)...)
410447
end
411448

412449
macro ccall_effects(effects::UInt16, expr)

Diff for: base/meta.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -427,7 +427,7 @@ function _partially_inline!(@nospecialize(x), slot_replacements::Vector{Any},
427427
elseif i == 4
428428
@assert isa(x.args[4], Int)
429429
elseif i == 5
430-
@assert isa((x.args[5]::QuoteNode).value, Union{Symbol, Tuple{Symbol, UInt8}})
430+
@assert isa((x.args[5]::QuoteNode).value, Union{Symbol, Tuple{Symbol, UInt16, Bool}})
431431
else
432432
x.args[i] = _partially_inline!(x.args[i], slot_replacements,
433433
type_signature, static_param_values,

Diff for: base/strings/string.jl

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ end
107107
# but the macro is not available at this time in bootstrap, so we write it manually.
108108
const _string_n_override = 0x04ee
109109
@eval _string_n(n::Integer) = $(Expr(:foreigncall, QuoteNode(:jl_alloc_string), Ref{String},
110-
:(Core.svec(Csize_t)), 1, QuoteNode((:ccall, _string_n_override)), :(convert(Csize_t, n))))
110+
:(Core.svec(Csize_t)), 1, QuoteNode((:ccall, _string_n_override, false)), :(convert(Csize_t, n))))
111111

112112
"""
113113
String(s::AbstractString)

Diff for: doc/src/devdocs/ast.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -498,9 +498,9 @@ These symbols appear in the `head` field of [`Expr`](@ref)s in lowered form.
498498

499499
The number of required arguments for a varargs function definition.
500500

501-
* `args[5]::QuoteNode{<:Union{Symbol,Tuple{Symbol,UInt16}}`: calling convention
501+
* `args[5]::QuoteNode{<:Union{Symbol,Tuple{Symbol,UInt16}, Tuple{Symbol,UInt16,Bool}}`: calling convention
502502

503-
The calling convention for the call, optionally with effects.
503+
The calling convention for the call, optionally with effects, and `gc_safe` (safe to execute concurrently to GC.).
504504

505505
* `args[6:5+length(args[3])]` : arguments
506506

Diff for: src/ccall.cpp

+12-4
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,7 @@ class function_sig_t {
11341134
AttributeList attributes; // vector of function call site attributes
11351135
Type *lrt; // input parameter of the llvm return type (from julia_struct_to_llvm)
11361136
bool retboxed; // input parameter indicating whether lrt is jl_value_t*
1137+
bool gc_safe; // input parameter indicating whether the call is safe to execute concurrently to GC
11371138
Type *prt; // out parameter of the llvm return type for the function signature
11381139
int sret; // out parameter for indicating whether return value has been moved to the first argument position
11391140
std::string err_msg;
@@ -1146,8 +1147,8 @@ class function_sig_t {
11461147
size_t nreqargs; // number of required arguments in ccall function definition
11471148
jl_codegen_params_t *ctx;
11481149

1149-
function_sig_t(const char *fname, Type *lrt, jl_value_t *rt, bool retboxed, jl_svec_t *at, jl_unionall_t *unionall_env, size_t nreqargs, CallingConv::ID cc, bool llvmcall, jl_codegen_params_t *ctx)
1150-
: lrt(lrt), retboxed(retboxed),
1150+
function_sig_t(const char *fname, Type *lrt, jl_value_t *rt, bool retboxed, bool gc_safe, jl_svec_t *at, jl_unionall_t *unionall_env, size_t nreqargs, CallingConv::ID cc, bool llvmcall, jl_codegen_params_t *ctx)
1151+
: lrt(lrt), retboxed(retboxed), gc_safe(gc_safe),
11511152
prt(NULL), sret(0), cc(cc), llvmcall(llvmcall),
11521153
at(at), rt(rt), unionall_env(unionall_env),
11531154
nccallargs(jl_svec_len(at)), nreqargs(nreqargs),
@@ -1295,6 +1296,9 @@ std::string generate_func_sig(const char *fname)
12951296
RetAttrs = RetAttrs.addAttribute(LLVMCtx, Attribute::NonNull);
12961297
if (rt == jl_bottom_type)
12971298
FnAttrs = FnAttrs.addAttribute(LLVMCtx, Attribute::NoReturn);
1299+
if (gc_safe)
1300+
FnAttrs = FnAttrs.addAttribute(LLVMCtx, "julia.gc_safe");
1301+
12981302
assert(attributes.isEmpty());
12991303
attributes = AttributeList::get(LLVMCtx, FnAttrs, RetAttrs, paramattrs);
13001304
return "";
@@ -1412,7 +1416,7 @@ static const std::string verify_ccall_sig(jl_value_t *&rt, jl_value_t *at,
14121416

14131417
const int fc_args_start = 6;
14141418

1415-
// Expr(:foreigncall, pointer, rettype, (argtypes...), nreq, [cconv | (cconv, effects)], args..., roots...)
1419+
// Expr(:foreigncall, pointer, rettype, (argtypes...), nreq, gc_safe, [cconv | (cconv, effects)], args..., roots...)
14161420
static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs)
14171421
{
14181422
JL_NARGSV(ccall, 5);
@@ -1424,11 +1428,15 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs)
14241428
assert(jl_is_quotenode(args[5]));
14251429
jl_value_t *jlcc = jl_quotenode_value(args[5]);
14261430
jl_sym_t *cc_sym = NULL;
1431+
// TODO: Can we introduce a intrinsic token = @julia.gc_safe_begin()
1432+
// and "grow" gc safe regions so that we minimize the overhead?
1433+
bool gc_safe = false;
14271434
if (jl_is_symbol(jlcc)) {
14281435
cc_sym = (jl_sym_t*)jlcc;
14291436
}
14301437
else if (jl_is_tuple(jlcc)) {
14311438
cc_sym = (jl_sym_t*)jl_get_nth_field_noalloc(jlcc, 0);
1439+
gc_safe = jl_unbox_bool(jl_get_nth_field_checked(jlcc, 2));
14321440
}
14331441
assert(jl_is_symbol(cc_sym));
14341442
native_sym_arg_t symarg = {};
@@ -1547,7 +1555,7 @@ static jl_cgval_t emit_ccall(jl_codectx_t &ctx, jl_value_t **args, size_t nargs)
15471555
}
15481556
if (rt != args[2] && rt != (jl_value_t*)jl_any_type)
15491557
jl_temporary_root(ctx, rt);
1550-
function_sig_t sig("ccall", lrt, rt, retboxed,
1558+
function_sig_t sig("ccall", lrt, rt, retboxed, gc_safe,
15511559
(jl_svec_t*)at, unionall, nreqargs,
15521560
cc, llvmcall, &ctx.emission_context);
15531561
for (size_t i = 0; i < nccallargs; i++) {

Diff for: src/codegen.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -8065,7 +8065,7 @@ static jl_cgval_t emit_cfunction(jl_codectx_t &ctx, jl_value_t *output_type, con
80658065
if (rt != declrt && rt != (jl_value_t*)jl_any_type)
80668066
jl_temporary_root(ctx, rt);
80678067

8068-
function_sig_t sig("cfunction", lrt, rt, retboxed, argt, unionall_env, false, CallingConv::C, false, &ctx.emission_context);
8068+
function_sig_t sig("cfunction", lrt, rt, retboxed, false, argt, unionall_env, false, CallingConv::C, false, &ctx.emission_context);
80698069
assert(sig.fargt.size() + sig.sret == sig.fargt_sig.size());
80708070
if (!sig.err_msg.empty()) {
80718071
emit_error(ctx, sig.err_msg);
@@ -8205,7 +8205,7 @@ const char *jl_generate_ccallable(Module *llvmmod, void *sysimg_handle, jl_value
82058205
}
82068206
jl_value_t *err;
82078207
{ // scope block for sig
8208-
function_sig_t sig("cfunction", lcrt, crt, toboxed,
8208+
function_sig_t sig("cfunction", lcrt, crt, toboxed, false,
82098209
argtypes, NULL, false, CallingConv::C, false, &params);
82108210
if (sig.err_msg.empty()) {
82118211
if (sysimg_handle) {

Diff for: src/llvm-codegen-shared.h

+5-9
Original file line numberDiff line numberDiff line change
@@ -244,21 +244,17 @@ static inline llvm::Value *emit_gc_state_set(llvm::IRBuilder<> &builder, llvm::T
244244
unsigned offset = offsetof(jl_tls_states_t, gc_state);
245245
Value *gc_state = builder.CreateConstInBoundsGEP1_32(T_int8, ptls, offset, "gc_state");
246246
if (old_state == nullptr) {
247-
old_state = builder.CreateLoad(T_int8, gc_state);
247+
old_state = builder.CreateLoad(T_int8, gc_state, "old_state");
248248
cast<LoadInst>(old_state)->setOrdering(AtomicOrdering::Monotonic);
249249
}
250250
builder.CreateAlignedStore(state, gc_state, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release);
251251
if (auto *C = dyn_cast<ConstantInt>(old_state))
252-
if (C->isZero())
253-
return old_state;
254-
if (auto *C = dyn_cast<ConstantInt>(state))
255-
if (!C->isZero())
256-
return old_state;
252+
if (auto *C2 = dyn_cast<ConstantInt>(state))
253+
if (C->getZExtValue() == C2->getZExtValue())
254+
return old_state;
257255
BasicBlock *passBB = BasicBlock::Create(builder.getContext(), "safepoint", builder.GetInsertBlock()->getParent());
258256
BasicBlock *exitBB = BasicBlock::Create(builder.getContext(), "after_safepoint", builder.GetInsertBlock()->getParent());
259-
Constant *zero8 = ConstantInt::get(T_int8, 0);
260-
builder.CreateCondBr(builder.CreateOr(builder.CreateICmpEQ(old_state, zero8), // if (!old_state || !state)
261-
builder.CreateICmpEQ(state, zero8)),
257+
builder.CreateCondBr(builder.CreateICmpEQ(old_state, state, "is_new_state"), // Safepoint whenever we change the GC state
262258
passBB, exitBB);
263259
builder.SetInsertPoint(passBB);
264260
MDNode *tbaa = get_tbaa_const(builder.getContext());

Diff for: src/llvm-late-gc-lowering.cpp

+32-9
Original file line numberDiff line numberDiff line change
@@ -2181,16 +2181,39 @@ bool LateLowerGCFrame::CleanupIR(Function &F, State *S, bool *CFGModified) {
21812181
NewCall->copyMetadata(*CI);
21822182
CI->replaceAllUsesWith(NewCall);
21832183
UpdatePtrNumbering(CI, NewCall, S);
2184-
} else if (CI->arg_size() == CI->getNumOperands()) {
2185-
/* No operand bundle to lower */
2186-
++it;
2187-
continue;
21882184
} else {
2189-
CallInst *NewCall = CallInst::Create(CI, None, CI);
2190-
NewCall->takeName(CI);
2191-
NewCall->copyMetadata(*CI);
2192-
CI->replaceAllUsesWith(NewCall);
2193-
UpdatePtrNumbering(CI, NewCall, S);
2185+
if (CI->getFnAttr("julia.gc_safe").isValid()) {
2186+
// Insert the operations to switch to gc_safe if necessary.
2187+
IRBuilder<> builder(CI);
2188+
Value *pgcstack = getOrAddPGCstack(F);
2189+
assert(pgcstack);
2190+
// We dont use emit_state_set here because safepoints are unconditional for any code that reaches this
2191+
// We are basically guaranteed to go from gc_unsafe to gc_safe and back, and both transitions need a safepoint
2192+
// We also can't add any BBs here, so just avoiding the branches is good
2193+
Value *ptls = get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, pgcstack), tbaa_gcframe);
2194+
unsigned offset = offsetof(jl_tls_states_t, gc_state);
2195+
Value *gc_state = builder.CreateConstInBoundsGEP1_32(Type::getInt8Ty(builder.getContext()), ptls, offset, "gc_state");
2196+
LoadInst *last_gc_state = builder.CreateAlignedLoad(Type::getInt8Ty(builder.getContext()), gc_state, Align(sizeof(void*)));
2197+
last_gc_state->setOrdering(AtomicOrdering::Monotonic);
2198+
builder.CreateAlignedStore(builder.getInt8(JL_GC_STATE_SAFE), gc_state, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release);
2199+
MDNode *tbaa = get_tbaa_const(builder.getContext());
2200+
emit_gc_safepoint(builder, T_size, ptls, tbaa, false);
2201+
builder.SetInsertPoint(CI->getNextNode());
2202+
builder.CreateAlignedStore(last_gc_state, gc_state, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release);
2203+
emit_gc_safepoint(builder, T_size, ptls, tbaa, false);
2204+
}
2205+
if (CI->arg_size() == CI->getNumOperands()) {
2206+
/* No operand bundle to lower */
2207+
++it;
2208+
continue;
2209+
} else {
2210+
// remove operand bundle
2211+
CallInst *NewCall = CallInst::Create(CI, None, CI);
2212+
NewCall->takeName(CI);
2213+
NewCall->copyMetadata(*CI);
2214+
CI->replaceAllUsesWith(NewCall);
2215+
UpdatePtrNumbering(CI, NewCall, S);
2216+
}
21942217
}
21952218
if (!CI->use_empty()) {
21962219
CI->replaceAllUsesWith(UndefValue::get(CI->getType()));

Diff for: src/llvm-pass-helpers.cpp

+21
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,27 @@ llvm::CallInst *JuliaPassContext::getPGCstack(llvm::Function &F) const
8888
return nullptr;
8989
}
9090

91+
llvm::CallInst *JuliaPassContext::getOrAddPGCstack(llvm::Function &F)
92+
{
93+
if (pgcstack_getter || adoptthread_func)
94+
for (auto &I : F.getEntryBlock()) {
95+
if (CallInst *callInst = dyn_cast<CallInst>(&I)) {
96+
Value *callee = callInst->getCalledOperand();
97+
if ((pgcstack_getter && callee == pgcstack_getter) ||
98+
(adoptthread_func && callee == adoptthread_func)) {
99+
return callInst;
100+
}
101+
}
102+
}
103+
IRBuilder<> builder(&F.getEntryBlock().front());
104+
if (pgcstack_getter)
105+
return builder.CreateCall(pgcstack_getter);
106+
auto FT = FunctionType::get(PointerType::get(F.getContext(), 0), false);
107+
auto F2 = Function::Create(FT, Function::ExternalLinkage, "julia.get_pgcstack", F.getParent());
108+
pgcstack_getter = F2;
109+
return builder.CreateCall( F2);
110+
}
111+
91112
llvm::Function *JuliaPassContext::getOrNull(
92113
const jl_intrinsics::IntrinsicDescription &desc) const
93114
{

Diff for: src/llvm-pass-helpers.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ struct JuliaPassContext {
8787
// point of the given function, if there exists such a call.
8888
// Otherwise, `nullptr` is returned.
8989
llvm::CallInst *getPGCstack(llvm::Function &F) const;
90-
90+
// Gets a call to the `julia.get_pgcstack' intrinsic in the entry
91+
// point of the given function, if there exists such a call.
92+
// Otherwise, creates a new call to the intrinsic
93+
llvm::CallInst *getOrAddPGCstack(llvm::Function &F);
9194
// Gets the intrinsic or well-known function that conforms to
9295
// the given description if it exists in the module. If not,
9396
// `nullptr` is returned.

Diff for: src/llvm-ptls.cpp

+6-1
Original file line numberDiff line numberDiff line change
@@ -196,8 +196,13 @@ void LowerPTLS::fix_pgcstack_use(CallInst *pgcstack, Function *pgcstack_getter,
196196
last_gc_state->addIncoming(prior, fastTerm->getParent());
197197
for (auto &BB : *pgcstack->getParent()->getParent()) {
198198
if (isa<ReturnInst>(BB.getTerminator())) {
199+
// Don't use emit_gc_safe_leave here, as that introduces a new BB while iterating BBs
199200
builder.SetInsertPoint(BB.getTerminator());
200-
emit_gc_unsafe_leave(builder, T_size, get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, phi), tbaa), last_gc_state, true);
201+
Value *ptls = get_current_ptls_from_task(builder, get_current_task_from_pgcstack(builder, pgcstack), tbaa_gcframe);
202+
unsigned offset = offsetof(jl_tls_states_t, gc_state);
203+
Value *gc_state = builder.CreateConstInBoundsGEP1_32(Type::getInt8Ty(builder.getContext()), ptls, offset, "gc_state");
204+
builder.CreateAlignedStore(last_gc_state, gc_state, Align(sizeof(void*)))->setOrdering(AtomicOrdering::Release);
205+
emit_gc_safepoint(builder, T_size, ptls, tbaa, true);
201206
}
202207
}
203208
}

Diff for: src/method.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ static jl_value_t *resolve_definition_effects(jl_value_t *expr, jl_module_t *mod
138138
return expr;
139139
}
140140
if (e->head == jl_foreigncall_sym) {
141-
JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, nreq, (cc, effects))
141+
JL_NARGSV(ccall method definition, 5); // (fptr, rt, at, nreq, (cc, effects, gc_safe))
142142
jl_task_t *ct = jl_current_task;
143143
jl_value_t *rt = jl_exprarg(e, 1);
144144
jl_value_t *at = jl_exprarg(e, 2);
@@ -172,11 +172,12 @@ static jl_value_t *resolve_definition_effects(jl_value_t *expr, jl_module_t *mod
172172
jl_value_t *cc = jl_quotenode_value(jl_exprarg(e, 4));
173173
if (!jl_is_symbol(cc)) {
174174
JL_TYPECHK(ccall method definition, tuple, cc);
175-
if (jl_nfields(cc) != 2) {
175+
if (jl_nfields(cc) != 3) {
176176
jl_error("In ccall calling convention, expected two argument tuple or symbol.");
177177
}
178178
JL_TYPECHK(ccall method definition, symbol, jl_get_nth_field(cc, 0));
179179
JL_TYPECHK(ccall method definition, uint16, jl_get_nth_field(cc, 1));
180+
JL_TYPECHK(ccall method definition, bool, jl_get_nth_field(cc, 2));
180181
}
181182
}
182183
if (e->head == jl_call_sym && nargs > 0 &&

Diff for: test/ccall.jl

+11
Original file line numberDiff line numberDiff line change
@@ -1966,3 +1966,14 @@ let llvm = sprint(code_llvm, world_counter, ())
19661966
# the world age should be -1 in generated functions (or other pure contexts)
19671967
@test (generated_world_counter() == reinterpret(UInt, -1))
19681968
end
1969+
1970+
function gc_safe_ccall()
1971+
@ccall gc_safe=true jl_get_cpu_features()::String
1972+
end
1973+
1974+
let llvm = sprint(code_llvm, gc_safe_ccall, ())
1975+
# check that the call works
1976+
@test gc_safe_ccall() isa String
1977+
# check for the gc_safe store
1978+
@test occursin("store atomic i8 2", llvm)
1979+
end

0 commit comments

Comments
 (0)