Skip to content

Commit 8739df2

Browse files
authored
fix emit_f_is to be safepoint-free, like jl_egal (#41255)
1 parent 9296ec5 commit 8739df2

File tree

5 files changed

+90
-48
lines changed

5 files changed

+90
-48
lines changed

src/builtins.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ JL_DLLEXPORT int (jl_egal)(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value
198198
return jl_egal(a, b);
199199
}
200200

201+
JL_DLLEXPORT int jl_egal__unboxed(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT
202+
{
203+
// warning: a,b may NOT have been gc-rooted by the caller
204+
return jl_egal__unboxed_(a, b, dt);
205+
}
206+
201207
int jl_egal__special(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT
202208
{
203209
if (dt == jl_simplevector_type)

src/cgutils.cpp

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,15 @@ static bool can_optimize_isa_union(jl_uniontype_t *type)
11201120
return (_can_optimize_isa(type->a, counter) && _can_optimize_isa(type->b, counter));
11211121
}
11221122

1123+
// a simple case of emit_isa that is obvious not to include a safe-point
1124+
static Value *emit_exactly_isa(jl_codectx_t &ctx, const jl_cgval_t &arg, jl_value_t *dt)
1125+
{
1126+
assert(jl_is_concrete_type(dt));
1127+
return ctx.builder.CreateICmpEQ(
1128+
emit_typeof_boxed(ctx, arg),
1129+
track_pjlvalue(ctx, literal_pointer_val(ctx, dt)));
1130+
}
1131+
11231132
static std::pair<Value*, bool> emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x,
11241133
jl_value_t *type, const std::string *msg);
11251134

@@ -1217,12 +1226,7 @@ static std::pair<Value*, bool> emit_isa(jl_codectx_t &ctx, const jl_cgval_t &x,
12171226
return std::make_pair(ConstantInt::get(T_int1, 0), false);
12181227
}
12191228
}
1220-
if (auto val = ((jl_datatype_t*)intersected_type)->instance) {
1221-
auto ptr = track_pjlvalue(ctx, literal_pointer_val(ctx, val));
1222-
return {ctx.builder.CreateICmpEQ(boxed(ctx, x), ptr), false};
1223-
}
1224-
return std::make_pair(ctx.builder.CreateICmpEQ(emit_typeof_boxed(ctx, x),
1225-
track_pjlvalue(ctx, literal_pointer_val(ctx, intersected_type))), false);
1229+
return std::make_pair(emit_exactly_isa(ctx, x, intersected_type), false);
12261230
}
12271231
jl_datatype_t *dt = (jl_datatype_t*)jl_unwrap_unionall(intersected_type);
12281232
if (jl_is_datatype(dt) && !dt->name->abstract && jl_subtype(dt->name->wrapper, type)) {

src/codegen.cpp

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -628,11 +628,11 @@ static const auto jl_excstack_state_func = new JuliaFunction{
628628
[](LLVMContext &C) { return FunctionType::get(T_size, false); },
629629
nullptr,
630630
};
631-
static const auto jlegal_func = new JuliaFunction{
632-
"jl_egal",
631+
static const auto jlegalx_func = new JuliaFunction{
632+
"jl_egal__unboxed",
633633
[](LLVMContext &C) {
634-
Type *T = PointerType::get(T_jlvalue, AddressSpace::CalleeRooted);
635-
return FunctionType::get(T_int32, {T, T}, false); },
634+
Type *T = PointerType::get(T_jlvalue, AddressSpace::Derived);
635+
return FunctionType::get(T_int32, {T, T, T_prjlvalue}, false); },
636636
[](LLVMContext &C) { return AttributeList::get(C,
637637
Attributes(C, {Attribute::ReadOnly, Attribute::NoUnwind, Attribute::ArgMemOnly}),
638638
AttributeSet(),
@@ -2411,10 +2411,10 @@ static Value *emit_box_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const
24112411
Value *nullcheck1, Value *nullcheck2)
24122412
{
24132413
if (jl_pointer_egal(arg1.typ) || jl_pointer_egal(arg2.typ)) {
2414+
assert((arg1.isboxed || arg1.constant) && (arg2.isboxed || arg2.constant) &&
2415+
"Expected unboxed cases to be handled earlier");
24142416
Value *varg1 = arg1.constant ? literal_pointer_val(ctx, arg1.constant) : arg1.V;
24152417
Value *varg2 = arg2.constant ? literal_pointer_val(ctx, arg2.constant) : arg2.V;
2416-
assert(varg1 && varg2 && (arg1.isboxed || arg1.TIndex) && (arg2.isboxed || arg2.TIndex) &&
2417-
"Only boxed types are valid for pointer comparison.");
24182418
varg1 = maybe_decay_tracked(ctx, varg1);
24192419
varg2 = maybe_decay_tracked(ctx, varg2);
24202420
if (cast<PointerType>(varg1->getType())->getAddressSpace() != cast<PointerType>(varg2->getType())->getAddressSpace()) {
@@ -2426,10 +2426,19 @@ static Value *emit_box_compare(jl_codectx_t &ctx, const jl_cgval_t &arg1, const
24262426
}
24272427

24282428
return emit_nullcheck_guard2(ctx, nullcheck1, nullcheck2, [&] {
2429-
Value *varg1 = mark_callee_rooted(ctx, boxed(ctx, arg1));
2430-
Value *varg2 = mark_callee_rooted(ctx, boxed(ctx, arg2));
2431-
return ctx.builder.CreateTrunc(ctx.builder.CreateCall(prepare_call(jlegal_func),
2432-
{varg1, varg2}), T_int1);
2429+
Value *varg1 = arg1.constant ? literal_pointer_val(ctx, arg1.constant) : maybe_bitcast(ctx, value_to_pointer(ctx, arg1).V, T_pjlvalue);
2430+
Value *varg2 = arg2.constant ? literal_pointer_val(ctx, arg2.constant) : maybe_bitcast(ctx, value_to_pointer(ctx, arg2).V, T_pjlvalue);
2431+
varg1 = decay_derived(ctx, varg1);
2432+
varg2 = decay_derived(ctx, varg2);
2433+
Value *neq = ctx.builder.CreateICmpNE(varg1, varg2);
2434+
return emit_guarded_test(ctx, neq, true, [&] {
2435+
Value *dtarg = emit_typeof_boxed(ctx, arg1);
2436+
Value *dt_eq = ctx.builder.CreateICmpEQ(dtarg, emit_typeof_boxed(ctx, arg2));
2437+
return emit_guarded_test(ctx, dt_eq, false, [&] {
2438+
return ctx.builder.CreateTrunc(ctx.builder.CreateCall(prepare_call(jlegalx_func),
2439+
{varg1, varg2, dtarg}), T_int1);
2440+
});
2441+
});
24332442
});
24342443
}
24352444

@@ -2583,6 +2592,7 @@ static Value *emit_bits_compare(jl_codectx_t &ctx, jl_cgval_t arg1, jl_cgval_t a
25832592
// representing the undef-ness of `arg1` and `arg2`.
25842593
// This can only happen when comparing two fields of the same time and the result should be
25852594
// true if both are NULL
2595+
// Like the runtime counterpart, this is codegen guaranteed to be non-allocating and to exclude safepoints
25862596
static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgval_t &arg2,
25872597
Value *nullcheck1, Value *nullcheck2)
25882598
{
@@ -2603,46 +2613,45 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva
26032613
// since it is normalized to `::Type{Union{}}` instead...
26042614
if (arg1.TIndex)
26052615
return emit_nullcheck_guard(ctx, nullcheck1, [&] {
2606-
return emit_isa(ctx, arg1, rt2, NULL).first; // rt2 is a singleton type
2616+
return emit_exactly_isa(ctx, arg1, rt2); // rt2 is a singleton type
26072617
});
26082618
if (arg2.TIndex)
26092619
return emit_nullcheck_guard(ctx, nullcheck2, [&] {
2610-
return emit_isa(ctx, arg2, rt1, NULL).first; // rt1 is a singleton type
2620+
return emit_exactly_isa(ctx, arg2, rt1); // rt1 is a singleton type
26112621
});
2622+
if (!(arg1.isboxed || arg1.constant) || !(arg2.isboxed || arg2.constant))
2623+
// not TIndex && not boxed implies it is an unboxed value of a different type from this singleton
2624+
// (which was probably caught above, but just to be safe, we repeat it here explicitly)
2625+
return ConstantInt::get(T_int1, 0);
2626+
Value *varg1 = arg1.constant ? literal_pointer_val(ctx, arg1.constant) : maybe_bitcast(ctx, arg1.Vboxed, T_pjlvalue);
2627+
Value *varg2 = arg2.constant ? literal_pointer_val(ctx, arg2.constant) : maybe_bitcast(ctx, arg2.Vboxed, T_pjlvalue);
26122628
// rooting these values isn't needed since we won't load this pointer
26132629
// and we know at least one of them is a unique Singleton
26142630
// which is already enough to ensure pointer uniqueness for this test
26152631
// even if the other pointer managed to get garbage collected
2616-
return ctx.builder.CreateICmpEQ(
2617-
mark_callee_rooted(ctx, boxed(ctx, arg1)),
2618-
mark_callee_rooted(ctx, boxed(ctx, arg2)));
2632+
// TODO: use emit_pointer_from_objref instead, per comment above
2633+
return ctx.builder.CreateICmpEQ(decay_derived(ctx, varg1), decay_derived(ctx, varg2));
26192634
}
26202635

26212636
if (jl_type_intersection(rt1, rt2) == (jl_value_t*)jl_bottom_type) // types are disjoint (exhaustive test)
26222637
return ConstantInt::get(T_int1, 0);
26232638

2624-
// If both sides are boxed or can be trivially boxed,
2625-
// we'll prefer to do a pointer check.
2626-
// At this point, we know that at least one of the arguments isn't a constant
2627-
// so a runtime content check will involve at least one load from the
2628-
// pointer (and likely a type check)
2629-
// so a pointer comparison should be no worse than that even in imaging mode
2630-
// when the constant pointer has to be loaded.
2631-
if ((arg1.V || arg1.constant) && (arg2.V || arg2.constant) &&
2632-
(jl_pointer_egal(rt1) || jl_pointer_egal(rt2)) &&
2633-
// jl_pointer_egal returns true for Bool, which is not helpful here
2634-
(rt1 != (jl_value_t*)jl_bool_type || rt2 != (jl_value_t*)jl_bool_type))
2635-
return ctx.builder.CreateICmpEQ(boxed(ctx, arg1), boxed(ctx, arg2));
2636-
26372639
bool justbits1 = jl_is_concrete_immutable(rt1);
26382640
bool justbits2 = jl_is_concrete_immutable(rt2);
26392641
if (justbits1 || justbits2) { // whether this type is unique'd by value
26402642
return emit_nullcheck_guard2(ctx, nullcheck1, nullcheck2, [&] () -> Value* {
26412643
jl_value_t *typ = justbits1 ? rt1 : rt2;
2644+
if (typ == (jl_value_t*)jl_bool_type) { // aka jl_pointer_egal
2645+
// some optimizations for bool, since pointer comparison may be better
2646+
if ((arg1.isboxed || arg1.constant) && (arg2.isboxed || arg2.constant)) { // aka have-fast-pointer
2647+
Value *varg1 = arg1.constant ? literal_pointer_val(ctx, arg1.constant) : maybe_bitcast(ctx, arg1.Vboxed, T_pjlvalue);
2648+
Value *varg2 = arg2.constant ? literal_pointer_val(ctx, arg2.constant) : maybe_bitcast(ctx, arg2.Vboxed, T_pjlvalue);
2649+
return ctx.builder.CreateICmpEQ(decay_derived(ctx, varg1), decay_derived(ctx, varg2));
2650+
}
2651+
}
26422652
if (rt1 == rt2)
26432653
return emit_bits_compare(ctx, arg1, arg2);
2644-
Value *same_type = (typ == rt2) ? emit_isa(ctx, arg1, typ, NULL).first :
2645-
emit_isa(ctx, arg2, typ, NULL).first;
2654+
Value *same_type = emit_exactly_isa(ctx, (typ == rt2 ? arg1 : arg2), typ);
26462655
BasicBlock *currBB = ctx.builder.GetInsertBlock();
26472656
BasicBlock *isaBB = BasicBlock::Create(jl_LLVMContext, "is", ctx.f);
26482657
BasicBlock *postBB = BasicBlock::Create(jl_LLVMContext, "post_is", ctx.f);
@@ -2660,6 +2669,25 @@ static Value *emit_f_is(jl_codectx_t &ctx, const jl_cgval_t &arg1, const jl_cgva
26602669
});
26612670
}
26622671

2672+
// If either sides is boxed or can be trivially boxed,
2673+
// we'll prefer to do a pointer check.
2674+
// At this point, we know that at least one of the arguments isn't a constant
2675+
// so a runtime content check will involve at least one load from the
2676+
// pointer (and likely a type check)
2677+
// so a pointer comparison should be no worse than that even in imaging mode
2678+
// when the constant pointer has to be loaded.
2679+
// Note that we ignore nullcheck, since in the case where it may be set, we
2680+
// also knew the types of both fields must be the same so there cannot be
2681+
// any unboxed values on either side.
2682+
if (jl_pointer_egal(rt1) || jl_pointer_egal(rt2)) {
2683+
// n.b. Vboxed == isboxed || Tindex
2684+
if (!(arg1.Vboxed || arg1.constant) || !(arg2.Vboxed || arg2.constant))
2685+
return ConstantInt::get(T_int1, 0);
2686+
Value *varg1 = arg1.constant ? literal_pointer_val(ctx, arg1.constant) : maybe_bitcast(ctx, arg1.Vboxed, T_pjlvalue);
2687+
Value *varg2 = arg2.constant ? literal_pointer_val(ctx, arg2.constant) : maybe_bitcast(ctx, arg2.Vboxed, T_pjlvalue);
2688+
return ctx.builder.CreateICmpEQ(decay_derived(ctx, varg1), decay_derived(ctx, varg2));
2689+
}
2690+
26632691
// TODO: handle the case where arg1.typ != arg2.typ, or when one of these isn't union,
26642692
// or when the union can be pointer
26652693
if (arg1.TIndex && arg2.TIndex && jl_egal(arg1.typ, arg2.typ) &&
@@ -3498,8 +3526,7 @@ static jl_cgval_t emit_call_specfun_other(jl_codectx_t &ctx, jl_method_instance_
34983526
argvals[idx] = boxed(ctx, arg);
34993527
}
35003528
else if (et->isAggregateType()) {
3501-
if (!arg.ispointer())
3502-
arg = value_to_pointer(ctx, arg);
3529+
arg = value_to_pointer(ctx, arg);
35033530
// can lazy load on demand, no copy needed
35043531
assert(at == PointerType::get(et, AddressSpace::Derived));
35053532
argvals[idx] = decay_derived(ctx, maybe_bitcast(ctx,
@@ -5437,8 +5464,7 @@ static Function* gen_cfun_wrapper(
54375464
}
54385465
else if (T->isAggregateType()) {
54395466
// aggregate types are passed by pointer
5440-
if (!inputarg.ispointer())
5441-
inputarg = value_to_pointer(ctx, inputarg);
5467+
inputarg = value_to_pointer(ctx, inputarg);
54425468
arg = maybe_bitcast(ctx, decay_derived(ctx, data_pointer(ctx, inputarg)),
54435469
T->getPointerTo());
54445470
}
@@ -7977,7 +8003,7 @@ static void init_jit_functions(void)
79778003
add_named_global(jlleave_func, &jl_pop_handler);
79788004
add_named_global(jl_restore_excstack_func, &jl_restore_excstack);
79798005
add_named_global(jl_excstack_state_func, &jl_excstack_state);
7980-
add_named_global(jlegal_func, &jl_egal);
8006+
add_named_global(jlegalx_func, &jl_egal__unboxed);
79818007
add_named_global(jlisa_func, &jl_isa);
79828008
add_named_global(jlsubtype_func, &jl_subtype);
79838009
add_named_global(jltypeassert_func, &jl_typeassert);

src/julia.h

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1320,22 +1320,28 @@ STATIC_INLINE int jl_is_array_zeroinit(jl_array_t *a) JL_NOTSAFEPOINT
13201320
JL_DLLEXPORT int jl_egal(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT;
13211321
JL_DLLEXPORT int jl_egal__bits(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT;
13221322
JL_DLLEXPORT int jl_egal__special(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT;
1323+
JL_DLLEXPORT int jl_egal__unboxed(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT;
13231324
JL_DLLEXPORT uintptr_t jl_object_id(jl_value_t *v) JL_NOTSAFEPOINT;
13241325

1325-
STATIC_INLINE int jl_egal_(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT
1326+
STATIC_INLINE int jl_egal__unboxed_(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED, jl_datatype_t *dt) JL_NOTSAFEPOINT
13261327
{
1327-
if (a == b)
1328-
return 1;
1329-
jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(a);
1330-
if (dt != (jl_datatype_t*)jl_typeof(b))
1331-
return 0;
13321328
if (dt->name->mutabl) {
13331329
if (dt == jl_simplevector_type || dt == jl_string_type || dt == jl_datatype_type)
13341330
return jl_egal__special(a, b, dt);
13351331
return 0;
13361332
}
13371333
return jl_egal__bits(a, b, dt);
13381334
}
1335+
1336+
STATIC_INLINE int jl_egal_(const jl_value_t *a JL_MAYBE_UNROOTED, const jl_value_t *b JL_MAYBE_UNROOTED) JL_NOTSAFEPOINT
1337+
{
1338+
if (a == b)
1339+
return 1;
1340+
jl_datatype_t *dt = (jl_datatype_t*)jl_typeof(a);
1341+
if (dt != (jl_datatype_t*)jl_typeof(b))
1342+
return 0;
1343+
return jl_egal__unboxed_(a, b, dt);
1344+
}
13391345
#define jl_egal(a, b) jl_egal_((a), (b))
13401346

13411347
// type predicates and basic operations

src/llvm-late-gc-lowering.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1537,7 +1537,7 @@ State LateLowerGCFrame::LocalScan(Function &F) {
15371537
// Known functions emitted in codegen that are not safepoints
15381538
if (callee == pointer_from_objref_func || callee == gc_preserve_begin_func ||
15391539
callee == gc_preserve_end_func || callee == typeof_func ||
1540-
callee == pgcstack_getter ||
1540+
callee == pgcstack_getter || callee->getName() == "jl_egal__unboxed" ||
15411541
callee->getName() == "jl_lock_value" || callee->getName() == "jl_unlock_value" ||
15421542
callee == write_barrier_func || callee->getName() == "memcmp") {
15431543
continue;

0 commit comments

Comments
 (0)