// This file is a part of Julia. License is MIT: https://julialang.org/license
//

/*
  modules and top-level bindings
*/
#include "julia.h"
#include "julia_internal.h"
#include "julia_assert.h"

#ifdef __cplusplus
extern "C" {
#endif

// A fresh partition is the last (oldest) in its chain, so its `next` field holds
// a backreference to the owning binding rather than another partition. Callers
// that link it ahead of another partition overwrite this.
static jl_binding_partition_t *new_binding_partition(jl_binding_t *b) JL_CANSAFEPOINT
{
    jl_binding_partition_t *bpart = (jl_binding_partition_t*)jl_gc_alloc(jl_current_task->ptls, sizeof(jl_binding_partition_t), jl_binding_partition_type);
    bpart->restriction = NULL;
    bpart->kind = (size_t)PARTITION_KIND_GUARD;
    jl_atomic_store_relaxed(&bpart->min_world, 0);
    jl_atomic_store_relaxed(&bpart->max_world, (size_t)-1);
    jl_gc_wb_fresh(bpart, b);
    jl_atomic_store_relaxed(&bpart->next, (jl_binding_partition_t*)b);
    return bpart;
}

// Whether `p`, a value loaded from a partition's `next` field (or a binding's
// `partitions` field), refers to an actual partition, as opposed to NULL (an
// empty chain) or the owning-binding backreference stored in the last partition
// of a chain.
STATIC_INLINE int is_some_partition(jl_binding_partition_t *p) JL_NOTSAFEPOINT
{
    return p != NULL && jl_is_binding_partition((jl_value_t*)p);
}

struct implicit_search_gap {
    _Atomic(jl_binding_partition_t *) *insert;
    jl_binding_partition_t *replace;
    jl_value_t *parent;

    size_t min_world;
    size_t max_world;
    size_t inherited_flags;
};

STATIC_INLINE jl_binding_partition_t *jl_get_binding_partition__(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world, struct implicit_search_gap *gap) JL_GLOBALLY_ROOTED
{
    // Iterate through the list of binding partitions, keeping track of where to insert a new one for an implicit
    // resolution if necessary.
    while (is_some_partition(gap->replace)) {
        size_t replace_min_world = jl_atomic_load_relaxed(&gap->replace->min_world);
        if (world >= replace_min_world)
            break;
        gap->insert = &gap->replace->next;
        gap->max_world = replace_min_world - 1;
        gap->parent = (jl_value_t*)gap->replace;
        // The last partition's `next` is the owning-binding backreference, so
        // `gap->replace` becomes that binding (not a partition) at end-of-chain.
        gap->replace = jl_atomic_load_relaxed(gap->insert);
    }
    if (is_some_partition(gap->replace) && world <= jl_atomic_load_relaxed(&gap->replace->max_world)) {
        return gap->replace;
    }
    gap->min_world = is_some_partition(gap->replace) ? jl_atomic_load_relaxed(&gap->replace->max_world) + 1 : 0;
    if (is_some_partition(gap->replace))
        gap->inherited_flags = gap->replace->kind & PARTITION_MASK_FLAG;
    else
        gap->inherited_flags = 0;
    return NULL;
}

STATIC_INLINE jl_binding_partition_t *jl_get_binding_partition_if_present(jl_binding_t *b JL_PROPAGATES_ROOT, size_t world, struct implicit_search_gap *gap)
{
    gap->parent = (jl_value_t*)b;
    gap->insert = &b->partitions;
    gap->replace = jl_atomic_load_relaxed(gap->insert);
    gap->min_world = 0;
    gap->max_world = ~(size_t)0;
    gap->inherited_flags = 0;
    return jl_get_binding_partition__(b, world, gap);
}

struct implicit_search_resolution {
    enum jl_partition_kind ultimate_kind;
    jl_value_t *binding_or_const;
    size_t min_world;
    size_t max_world;
    int saw_cycle;
    int should_be_reexported;  // Set if resolved through a using with JL_MODULE_USING_REEXPORT
    //// Not semantic, but used for reflection.
    // If non-null, the unique module from which this binding was imported
    jl_module_t *debug_only_import_from;
    // If non-null, the unique binding imported. For PARTITION_KIND_IMPLICIT_GLOBAL, always matches binding_or_const.
    // Must have trust_cache = 0.
    jl_binding_t *debug_only_ultimate_binding;
};

static size_t WORLDMAX(size_t a, size_t b) { return a > b ? a : b; }
static size_t WORLDMIN(size_t a, size_t b) { return a > b ? b : a; }

static void update_implicit_resolution(struct implicit_search_resolution *to_update, struct implicit_search_resolution resolution)
{
    to_update->min_world = WORLDMAX(to_update->min_world, resolution.min_world);
    to_update->max_world = WORLDMIN(to_update->max_world, resolution.max_world);
    to_update->saw_cycle |= resolution.saw_cycle;
    to_update->should_be_reexported |= resolution.should_be_reexported;
    if (resolution.ultimate_kind == PARTITION_FAKE_KIND_CYCLE) {
        // Cycles get ignored. This causes the resolution to only be partial, so we can't
        // cache it. This gets tracked in saw_cycle;
        to_update->saw_cycle = 1;
        return;
    }
    if (resolution.ultimate_kind == PARTITION_KIND_GUARD) {
        // Ignore guard imports
        return;
    }
    if (to_update->ultimate_kind == PARTITION_KIND_GUARD) {
        to_update->ultimate_kind = resolution.ultimate_kind;
        to_update->binding_or_const = resolution.binding_or_const;
        to_update->debug_only_import_from = resolution.debug_only_import_from;
        to_update->debug_only_ultimate_binding = resolution.debug_only_ultimate_binding;
        return;
    }
    if (resolution.ultimate_kind == to_update->ultimate_kind &&
        resolution.binding_or_const == to_update->binding_or_const) {
        if (resolution.debug_only_import_from != to_update->debug_only_import_from) {
            to_update->debug_only_import_from = NULL;
        }
        if (resolution.debug_only_ultimate_binding != to_update->debug_only_ultimate_binding) {
            to_update->debug_only_ultimate_binding = NULL;
        }
        return;
    }
    to_update->ultimate_kind = PARTITION_KIND_FAILED;
    to_update->binding_or_const = NULL;
    to_update->debug_only_import_from = NULL;
    to_update->debug_only_ultimate_binding = NULL;
}

static jl_binding_partition_t *jl_implicit_import_resolved(jl_binding_t *b, struct implicit_search_gap gap, struct implicit_search_resolution resolution) JL_CANSAFEPOINT
{
    size_t new_kind = resolution.ultimate_kind | gap.inherited_flags;
    // If the resolution indicates this should be reexported, add the implicit export flag
    if (resolution.should_be_reexported) {
        new_kind |= PARTITION_FLAG_IMPLICITLY_EXPORTED;
    }
    size_t new_max_world = gap.max_world < resolution.max_world ? gap.max_world : resolution.max_world;
    size_t new_min_world = gap.min_world > resolution.min_world ? gap.min_world : resolution.min_world;
    jl_binding_partition_t *next = gap.replace;
    if (jl_is_binding_partition(gap.parent)) {
        // Check if we can merge this into the previous binding partition
        jl_binding_partition_t *prev = (jl_binding_partition_t *)gap.parent;
        assert(new_max_world != ~(size_t)0); // It is inconsistent to have a gap with `gap.parent` set, but max_world == ~(size_t)0
        size_t expected_prev_min_world = new_max_world + 1;
        if (prev->restriction == resolution.binding_or_const && prev->kind == new_kind) {
retry:
            if (!jl_atomic_cmpswap(&prev->min_world, &expected_prev_min_world, new_min_world)) {
                if (expected_prev_min_world <= new_min_world) {
                    return prev;
                }
                else if (expected_prev_min_world <= new_max_world) {
                    // Concurrent modification of the partition. However, our lookup is still valid,
                    // so we should still be able to extend the partition.
                    goto retry;
                }
                // There remains a gap - proceed
            } else {
                if (is_some_partition(next)) {
                    size_t next_min_world = jl_atomic_load_relaxed(&next->min_world);
                    expected_prev_min_world = new_min_world;
                    for (;;) {
                        // We've updated the previous partition - check if we've closed a gap
                        size_t next_max_world = jl_atomic_load_relaxed(&next->max_world);
                        if (next_max_world >= expected_prev_min_world-1 && next->kind == new_kind && next->restriction == resolution.binding_or_const) {
                            if (jl_atomic_cmpswap(&prev->min_world, &expected_prev_min_world, next_min_world)) {
                                jl_binding_partition_t *nextnext = jl_atomic_load_relaxed(&next->next);
                                jl_gc_wb(prev, nextnext);
                                if (!jl_atomic_cmpswap(&prev->next, &next, nextnext)) {
                                    // `next` may have been merged into its subsequent partition - we need to retry
                                    assert(is_some_partition(next));
                                    continue;
                                }
                                // N.B.: This can lose modifications to next->{min_world, next}.
                                // However, those modifications could only have been for another implicit
                                // partition, so we are ok to lose them and recompute them later if necessary.
                            }
                            assert(expected_prev_min_world <= new_min_world);
                        }
                        break;
                    }
                }
                return prev;
            }
        }
    }
    jl_binding_partition_t *new_bpart = new_binding_partition(b);
    jl_atomic_store_relaxed(&new_bpart->max_world, new_max_world);
    new_bpart->kind = new_kind;
    jl_gc_wb_fresh(new_bpart, resolution.binding_or_const);
    new_bpart->restriction = resolution.binding_or_const;

    if (is_some_partition(next)) {
        // See if we can merge the next partition into this one
        size_t next_max_world = jl_atomic_load_relaxed(&next->max_world);
        if (next_max_world == new_min_world - 1 && next->kind == new_kind && next->restriction == resolution.binding_or_const) {
            // See above for potentially losing modifications to next.
            new_min_world = jl_atomic_load_acquire(&next->min_world);
            next = jl_atomic_load_relaxed(&next->next);
        }
    }

    jl_atomic_store_relaxed(&new_bpart->min_world, new_min_world);
    if (is_some_partition(next))
        jl_atomic_store_relaxed(&new_bpart->next, next);
    // else `new_bpart` is the last in the chain, so leave its `next` as the
    // owning-binding backreference set by `new_binding_partition`.
    jl_gc_wb(gap.parent, new_bpart);
    if (!jl_atomic_cmpswap(gap.insert, &gap.replace, new_bpart))
        return NULL;
    return new_bpart;
}

// find a binding from a module's `usings` list
static struct implicit_search_resolution jl_resolve_implicit_import(jl_binding_t *b, modstack_t *st, size_t world, int trust_cache) JL_CANSAFEPOINT
{
    // First check if we've hit a cycle in this resolution
    {
        modstack_t *tmp = st;
        for (; tmp != NULL; tmp = tmp->prev) {
            if (tmp->b == b) {
                return (struct implicit_search_resolution){ PARTITION_FAKE_KIND_CYCLE, NULL, 0, ~(size_t)0, 1, 0, NULL, NULL };
            }
        }
    }

    jl_module_t *m = b->globalref->mod;
    jl_sym_t *var = b->globalref->name;

    modstack_t top = { b, st };
    struct implicit_search_resolution impstate;
    struct implicit_search_resolution depimpstate;
    size_t min_world = 0;
    size_t max_world = ~(size_t)0;
    impstate = depimpstate = (struct implicit_search_resolution){ PARTITION_KIND_GUARD, NULL, min_world, max_world, 0, 0, NULL, NULL };

    JL_LOCK(&m->lock);
    int i = (int)module_usings_length(m) - 1;
    JL_UNLOCK(&m->lock);
    for (; i >= 0 && impstate.ultimate_kind != PARTITION_KIND_FAILED; --i) {
        JL_LOCK(&m->lock);
        struct _jl_module_using data = *module_usings_getidx(m, i);
        JL_UNLOCK(&m->lock);
        if (data.min_world > world) {
            max_world = WORLDMIN(max_world, data.min_world - 1);
            continue;
        }
        if (data.max_world < world) {
            min_world = WORLDMAX(min_world, data.max_world + 1);
            continue;
        }

        min_world = WORLDMAX(min_world, data.min_world);
        max_world = WORLDMIN(max_world, data.max_world);

        jl_module_t *imp = data.mod;
        uint8_t has_reexports = jl_atomic_load_relaxed(&imp->has_reexports);
        JL_GC_PROMISE_ROOTED(imp);
        jl_binding_t *tempb = jl_get_module_binding(imp, var, 0);
        if (!tempb) {
            // If the binding has never been allocated, it could not have been marked exported, so
            // it is irrelevant for our resolution. We can move on.
            // Exception: if this module has reexports, the binding might be reexported from another module,
            // so we need to create the binding to trigger implicit resolution
            if (!has_reexports)
                continue;
            tempb = jl_get_module_binding(imp, var, 1);  // Create the binding
        }

        struct implicit_search_gap gap;
        jl_binding_partition_t *tempbpart;
        tempbpart = jl_get_binding_partition_if_present(tempb, world, &gap);
        size_t tempbpart_flags = tempbpart ? (tempbpart->kind & PARTITION_MASK_FLAG) : gap.inherited_flags;

        while (tempbpart && jl_bkind_is_some_explicit_import(jl_binding_kind(tempbpart))) {
            max_world = WORLDMIN(max_world, jl_atomic_load_relaxed(&tempbpart->max_world));
            min_world = WORLDMAX(min_world, jl_atomic_load_relaxed(&tempbpart->min_world));

            tempb = (jl_binding_t*)tempbpart->restriction;
            tempbpart = jl_get_binding_partition_if_present(tempb, world, &gap);
        }

        int tempbpart_valid = tempbpart && (trust_cache || !jl_bkind_is_some_implicit(jl_binding_kind(tempbpart)));
        size_t tembppart_max_world = tempbpart_valid ? jl_atomic_load_relaxed(&tempbpart->max_world) : gap.max_world;
        size_t tembppart_min_world = tempbpart ? WORLDMAX(jl_atomic_load_relaxed(&tempbpart->min_world), gap.min_world) : gap.min_world;

        max_world = WORLDMIN(max_world, tembppart_max_world);
        min_world = WORLDMAX(min_world, tembppart_min_world);

        uint8_t is_any_exported = jl_bpart_is_exported(tempbpart_flags);
        if (!is_any_exported && (tempbpart_valid || !has_reexports)) {
            // Partition not exported - skip.
            continue;
        }

        struct implicit_search_resolution *comparison = &impstate;
        if (impstate.ultimate_kind != PARTITION_KIND_GUARD) {
            if (tempbpart_flags & PARTITION_FLAG_DEPRECATED) {
                // Deprecated, but we already have a non-deprecated binding for this - skip.
                continue;
            }
        } else if (tempbpart_flags & PARTITION_FLAG_DEPRECATED) {
            if (depimpstate.ultimate_kind == PARTITION_KIND_FAILED) {
                // We've already decided that the deprecated bindings are ambiguous, so skip this, but
                // keep going to look for non-deprecated bindings.
                continue;
            }
            comparison = &depimpstate;
        }

        struct implicit_search_resolution imp_resolution = { PARTITION_KIND_GUARD, NULL, min_world, max_world, 0, 0, NULL, NULL };
        if (!tempbpart_valid) {
            imp_resolution = jl_resolve_implicit_import(tempb, &top, world, trust_cache);
            // imp_resolution is the resolution for import into tempb (which may have been cached for tempb
            // if we're not in a cycle). `imp_resolution.should_be_reexported` indicates whether the binding
            // should be reexported from `imp`, not `m`, so check here.
            if (!(tempbpart_flags & PARTITION_FLAG_EXPORTED) && !imp_resolution.should_be_reexported)
                continue;
            imp_resolution.should_be_reexported = 0;
        } else {
            enum jl_partition_kind kind = jl_binding_kind(tempbpart);
            if (kind == PARTITION_KIND_IMPLICIT_GLOBAL) {
                imp_resolution.binding_or_const = tempbpart->restriction;
                imp_resolution.debug_only_ultimate_binding = (jl_binding_t*)tempbpart->restriction;
                imp_resolution.ultimate_kind = PARTITION_KIND_IMPLICIT_GLOBAL;
            } else if (kind == PARTITION_KIND_GLOBAL || kind == PARTITION_KIND_DECLARED || kind == PARTITION_KIND_BACKDATED_CONST) {
                imp_resolution.binding_or_const = (jl_value_t *)tempb;
                imp_resolution.debug_only_ultimate_binding = tempb;
                imp_resolution.ultimate_kind = PARTITION_KIND_IMPLICIT_GLOBAL;
            } else if (jl_bkind_is_defined_constant(kind)) {
                assert(tempbpart->restriction);
                imp_resolution.binding_or_const = tempbpart->restriction;
                imp_resolution.debug_only_ultimate_binding = tempb;
                imp_resolution.ultimate_kind = PARTITION_KIND_IMPLICIT_CONST;
            } else if (kind == PARTITION_KIND_FAILED) {
                imp_resolution.binding_or_const = NULL;
                imp_resolution.debug_only_ultimate_binding = tempb;
                imp_resolution.ultimate_kind = PARTITION_KIND_FAILED;
            }
        }
        // If this using has the reexport flag, mark that the binding should be reexported
        if (data.flags & JL_MODULE_USING_REEXPORT) {
            imp_resolution.should_be_reexported = 1;
        }
        imp_resolution.debug_only_import_from = imp;
        update_implicit_resolution(comparison, imp_resolution);

        if (!tempbpart && !imp_resolution.saw_cycle) {
            // Independent of whether or not we trust the cache, we have independently computed the implicit resolution
            // for this import, so we can put it in the cache.
            jl_implicit_import_resolved(tempb, gap, imp_resolution);
        }
    }

    if (impstate.ultimate_kind == PARTITION_KIND_GUARD && depimpstate.ultimate_kind != PARTITION_KIND_GUARD) {
        depimpstate.min_world = WORLDMAX(depimpstate.min_world, min_world);
        depimpstate.max_world = WORLDMIN(depimpstate.max_world, max_world);
        return depimpstate;
    }
    impstate.min_world = WORLDMAX(impstate.min_world, min_world);
    impstate.max_world = WORLDMIN(impstate.max_world, max_world);
    return impstate;
}

JL_DLLEXPORT jl_binding_partition_t *jl_maybe_reresolve_implicit(jl_binding_t *b, size_t new_max_world) JL_CANSAFEPOINT
{
    struct implicit_search_gap gap;
    while (1) {
        jl_binding_partition_t *bpart = jl_get_binding_partition_if_present(b, new_max_world+1, &gap);
        assert(bpart == jl_atomic_load_relaxed(&b->partitions));
        assert(bpart);
        struct implicit_search_resolution resolution = jl_resolve_implicit_import(b, NULL, new_max_world+1, 0);
        int resolution_unchanged = bpart->restriction == resolution.binding_or_const && jl_binding_kind(bpart) == resolution.ultimate_kind;
        size_t bpart_min_world = jl_atomic_load_relaxed(&bpart->min_world);
        if (resolution.min_world == bpart_min_world) {
            // The resolution has the same world bounds - it must be unchanged
            assert(resolution_unchanged);
            return bpart;
        } else if (resolution_unchanged) {
            // If the resolution is unchanged, we can still keep the bpart
            assert(resolution.min_world > bpart_min_world);
            return bpart;
        }
        assert(resolution.min_world == new_max_world+1 && "Missed an invalidation or bad resolution bounds");
        size_t expected_max_world = ~(size_t)0;
        if (jl_atomic_cmpswap(&bpart->max_world, &expected_max_world, new_max_world))
        {
            gap.min_world = new_max_world+1;
            gap.inherited_flags = bpart->kind & PARTITION_MASK_FLAG;
            jl_binding_partition_t *new_bpart = jl_implicit_import_resolved(b, gap, resolution);
            if (new_bpart)
                return new_bpart;
        }
    }
}

JL_DLLEXPORT void jl_update_loaded_bpart(jl_binding_t *b, jl_binding_partition_t *bpart)
{
    struct implicit_search_resolution resolution = jl_resolve_implicit_import(b, NULL, jl_atomic_load_acquire(&jl_world_counter), 0);
    jl_atomic_store_relaxed(&bpart->min_world, resolution.min_world);
    jl_atomic_store_relaxed(&bpart->max_world, resolution.max_world);
    jl_gc_write(bpart, bpart->restriction, jl_value_t, resolution.binding_or_const);
    bpart->kind = resolution.ultimate_kind;
}

static void jl_walk_binding_inplace(jl_binding_t **bnd, jl_binding_partition_t **pbpart, size_t world) JL_CANSAFEPOINT
{
    jl_binding_partition_t *bpart = *pbpart;
    while (1) {
        enum jl_partition_kind kind = jl_binding_kind(bpart);
        if (!jl_bkind_is_some_explicit_import(kind) && kind != PARTITION_KIND_IMPLICIT_GLOBAL) {
            break;
        }
        *bnd = (jl_binding_t*)bpart->restriction;
        bpart = jl_get_binding_partition(*bnd, world);
    }
    *pbpart = bpart;
}

static void jl_walk_binding_inplace_depwarn(jl_binding_t **bnd, jl_binding_partition_t **pbpart, size_t world, int *depwarn) JL_CANSAFEPOINT
{
    int passed_explicit = 0;
    jl_binding_partition_t *bpart = *pbpart;
    while (1) {
        enum jl_partition_kind kind = jl_binding_kind(bpart);
        if (!jl_bkind_is_some_explicit_import(kind) && kind != PARTITION_KIND_IMPLICIT_GLOBAL) {
            if (!passed_explicit && depwarn)
                *depwarn |= bpart->kind & PARTITION_FLAG_DEPWARN;
            break;
        }
        if (!passed_explicit && depwarn)
            *depwarn |= bpart->kind & PARTITION_FLAG_DEPWARN;
        if (kind != PARTITION_KIND_IMPLICIT_GLOBAL)
            passed_explicit = 1;
        *bnd = (jl_binding_t*)bpart->restriction;
        bpart = jl_get_binding_partition(*bnd, world);
    }
    *pbpart = bpart;
}

static void jl_walk_binding_inplace_worlds(jl_binding_t **bnd, jl_binding_partition_t **pbpart, size_t *min_world, size_t *max_world, int *depwarn, size_t world) JL_CANSAFEPOINT
{
    int passed_explicit = 0;
    jl_binding_partition_t *bpart = *pbpart;
    while (bpart) {
        size_t bpart_min_world = jl_atomic_load_relaxed(&bpart->min_world);
        if (*min_world < bpart_min_world)
            *min_world = bpart_min_world;
        size_t bpart_max_world = jl_atomic_load_relaxed(&bpart->max_world);
        if (*max_world > bpart_max_world)
            *max_world = bpart_max_world;
        enum jl_partition_kind kind = jl_binding_kind(bpart);
        if (!jl_bkind_is_some_explicit_import(kind) && kind != PARTITION_KIND_IMPLICIT_GLOBAL) {
            if (!passed_explicit && depwarn)
                *depwarn |= bpart->kind & PARTITION_FLAG_DEPWARN;
            break;
        }
        if (!passed_explicit && depwarn)
            *depwarn |= bpart->kind & PARTITION_FLAG_DEPWARN;
        if (kind != PARTITION_KIND_IMPLICIT_GLOBAL)
            passed_explicit = 1;
        *bnd = (jl_binding_t*)bpart->restriction;
        bpart = jl_get_binding_partition(*bnd, world);
    }
    *pbpart = bpart;
}

STATIC_INLINE jl_binding_partition_t *jl_get_binding_partition_(jl_binding_t *b JL_PROPAGATES_ROOT, jl_value_t *parent, _Atomic(jl_binding_partition_t *)*insert, size_t world, size_t max_world, modstack_t *st) JL_CANSAFEPOINT JL_GLOBALLY_ROOTED
{
    assert(jl_is_binding(b));
    struct implicit_search_gap gap;
    gap.parent = parent;
    gap.insert = insert;
    gap.inherited_flags = 0;
    gap.min_world = 0;
    gap.max_world = max_world;
    while (1) {
        gap.replace = jl_atomic_load_relaxed(gap.insert);
        jl_binding_partition_t *bpart = jl_get_binding_partition__(b, world, &gap);
        if (bpart)
            return bpart;
        struct implicit_search_resolution resolution = jl_resolve_implicit_import(b, NULL, world, 1);
        jl_binding_partition_t *new_bpart = jl_implicit_import_resolved(b, gap, resolution);
        if (new_bpart)
            return new_bpart;
    }
}

jl_binding_partition_t *jl_get_binding_partition(jl_binding_t *b, size_t world) {
    if (!b)
        return NULL;
    // Duplicate the code for the entry frame for branch prediction
    return jl_get_binding_partition_(b, (jl_value_t*)b, &b->partitions, world, ~(size_t)0, NULL);
}

jl_binding_partition_t *jl_get_binding_partition_with_hint(jl_binding_t *b, jl_binding_partition_t *prev, size_t world) JL_GLOBALLY_ROOTED {
    // Helper for getting a binding partition for an older world after we've already looked up the partition for a newer world
    assert(b);
    size_t prev_min_world = jl_atomic_load_relaxed(&prev->min_world);
    return jl_get_binding_partition_(b, (jl_value_t*)prev, &prev->next, world, prev_min_world-1, NULL);
}

jl_binding_partition_t *jl_get_binding_partition_all(jl_binding_t *b, size_t min_world, size_t max_world) {
    if (!b)
        return NULL;
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, min_world);
    if (!bpart)
        return NULL;
    if (jl_atomic_load_relaxed(&bpart->max_world) < max_world)
        return NULL;
    return bpart;
}

JL_DLLEXPORT int jl_get_binding_leaf_partitions_restriction_kind(jl_binding_t *b JL_PROPAGATES_ROOT, struct restriction_kind_pair *rkp, size_t min_world, size_t max_world) {
    if (!b)
        return 0;

    int first = 1;
    size_t validated_min_world = max_world == ~(size_t)0 ? ~(size_t)0 : max_world + 1;
    jl_binding_partition_t *bpart = NULL;
    int maybe_depwarn = 0;
    while (validated_min_world > min_world) {
        bpart = bpart ? jl_get_binding_partition_with_hint(b, bpart, validated_min_world - 1) :
                        jl_get_binding_partition(b, validated_min_world - 1);
        size_t bpart_min_world = jl_atomic_load_relaxed(&bpart->min_world);
        while (validated_min_world > min_world && validated_min_world > bpart_min_world) {
            jl_binding_t *curb = b;
            jl_binding_partition_t *curbpart = bpart;
            size_t cur_min_world = bpart_min_world;
            size_t cur_max_world = validated_min_world - 1;
            jl_walk_binding_inplace_worlds(&curb, &curbpart, &cur_min_world, &cur_max_world, &maybe_depwarn, cur_max_world);
            enum jl_partition_kind kind = jl_binding_kind(curbpart);
            if (kind == PARTITION_KIND_IMPLICIT_CONST)
                kind = PARTITION_KIND_CONST;
            if (first == 1) {
                rkp->kind = kind;
                rkp->restriction = curbpart->restriction;
                if (rkp->kind == PARTITION_KIND_GLOBAL || rkp->kind == PARTITION_KIND_DECLARED)
                    rkp->binding_if_global = curb;
                first = 0;
            } else {
                if (kind != rkp->kind || curbpart->restriction != rkp->restriction)
                    return 0;
                if ((rkp->kind == PARTITION_KIND_GLOBAL || rkp->kind == PARTITION_KIND_DECLARED) && rkp->binding_if_global != curb)
                    return 0;
            }
            validated_min_world = cur_min_world;
        }
    }
    rkp->maybe_depwarn = maybe_depwarn;
    return 1;
}

JL_DLLEXPORT jl_value_t *jl_get_binding_leaf_partitions_value_if_const(jl_binding_t *b JL_PROPAGATES_ROOT, int *maybe_depwarn, size_t min_world, size_t max_world) {
    struct restriction_kind_pair rkp = { NULL, NULL, PARTITION_KIND_GUARD, 0 };
    if (!jl_get_binding_leaf_partitions_restriction_kind(b, &rkp, min_world, max_world))
        return NULL;
    if (jl_bkind_is_real_constant(rkp.kind)) {
        *maybe_depwarn = rkp.maybe_depwarn;
        return rkp.restriction;
    }
    return NULL;
}

JL_DLLEXPORT size_t jl_binding_backedges_length(jl_binding_t *b) JL_CANSAFEPOINT
{
    JL_LOCK(&b->globalref->mod->lock);
    size_t len = 0;
    if (b->backedges)
        len = jl_array_len(b->backedges);
    JL_UNLOCK(&b->globalref->mod->lock);
    return len;
}

JL_DLLEXPORT jl_value_t *jl_binding_backedges_getindex(jl_binding_t *b, size_t i) JL_CANSAFEPOINT
{
    JL_LOCK(&b->globalref->mod->lock);
    assert(b->backedges);
    jl_value_t *ret = jl_array_ptr_ref(b->backedges, i-1);
    JL_UNLOCK(&b->globalref->mod->lock);
    return ret;
}

static jl_module_t *jl_new_module__(jl_sym_t *name, jl_module_t *parent) JL_CANSAFEPOINT
{
    jl_task_t *ct = jl_current_task;
    const jl_uuid_t uuid_zero = {0, 0};
    jl_module_t *m = (jl_module_t*)jl_gc_alloc(ct->ptls, sizeof(jl_module_t),
                                               jl_module_type);
    jl_set_typetagof(m, jl_module_tag, 0);
    assert(jl_is_symbol(name));
    m->name = name;
    m->parent = parent ? parent : m;
    m->istopmod = 0;
    m->uuid = uuid_zero;
    static _Atomic(unsigned int) mcounter; // simple counter backup, in case hrtime is not incrementing
    unsigned int count = jl_atomic_fetch_add_relaxed(&mcounter, 1);
    // TODO: this is used for ir decompression and is liable to hash collisions so use more of the bits
    m->build_id.lo = bitmix(jl_hrtime() + count, jl_rand());
    if (!m->build_id.lo)
        m->build_id.lo++; // build id 0 is invalid
    m->build_id.hi = ~(uint64_t)0;
    jl_atomic_store_relaxed(&m->counter, 1);
    m->usings_backedges = jl_nothing;
    m->scanned_methods = jl_nothing;
    m->nospecialize = 0;
    m->optlevel = -1;
    m->compile = -1;
    m->infer = -1;
    m->max_methods = -1;
    jl_atomic_store_relaxed(&m->has_reexports, 0);
    jl_atomic_store_relaxed(&m->export_set_changed_since_require_world, 0);
    m->file = jl_empty_sym;
    m->line = 0;
    m->hash = parent == NULL ? bitmix(name->hash, jl_module_type->hash) :
        bitmix(name->hash, parent->hash);
    JL_MUTEX_INIT(&m->lock, "module->lock");
    jl_atomic_store_relaxed(&m->bindings, jl_emptysvec);
    jl_atomic_store_relaxed(&m->bindingkeyset, (jl_genericmemory_t*)jl_an_empty_memory_any);
    arraylist_new(&m->usings, 0);
    return m;
}

static void jl_add_default_names(jl_module_t *m, uint8_t default_using_core, uint8_t self_name) JL_CANSAFEPOINT
{
    if (jl_core_module) {
        // Bootstrap: Before jl_core_module is defined, we don't have enough infrastructure
        // for bindings, so Core itself gets special handling in jltypes.c
        if (default_using_core) {
            jl_module_initial_using(m, jl_core_module);
        }
        if (self_name) {
            // export own name, so "using Foo" makes "Foo" itself visible
            jl_set_initial_const(m, m->name, (jl_value_t*)m, 1);
        }
    }
}

jl_module_t *jl_new_module_(jl_sym_t *name, jl_module_t *parent, uint8_t default_using_core, uint8_t self_name)
{
    jl_module_t *m = jl_new_module__(name, parent);
    JL_GC_PUSH1(&m);
    JL_LOCK(&world_counter_lock);
    jl_add_default_names(m, default_using_core, self_name);
    JL_UNLOCK(&world_counter_lock);
    JL_GC_POP();
    return m;
}


// Precondition: world_counter_lock is held
JL_DLLEXPORT jl_binding_partition_t *jl_declare_constant_val3(
    jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *val,
    enum jl_partition_kind constant_kind, size_t new_world)
{
    jl_binding_partition_t *new_prev_bpart = NULL;
    JL_GC_PUSH2(&val, &new_prev_bpart);
    if (!b) {
        b = jl_get_module_binding(mod, var, 1);
    }
    jl_binding_partition_t *new_bpart = NULL;
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world);
    while (!new_bpart) {
        enum jl_partition_kind kind = jl_binding_kind(bpart);
        if (jl_bkind_is_some_constant(kind) && !jl_bkind_is_some_implicit(kind)) {
            if (!val) {
                new_bpart = bpart;
                break;
            }
            jl_value_t *old = bpart->restriction;
            JL_GC_PROMISE_ROOTED(old);
            if (val == old || (val && old && jl_egal(val, old))) {
                new_bpart = bpart;
                break;
            }
        } else if (jl_bkind_is_some_explicit_import(kind)) {
            jl_errorf("cannot declare %s.%s constant; it was already declared as an import",
                      jl_symbol_name(mod->name), jl_symbol_name(var));
        } else if (kind == PARTITION_KIND_GLOBAL) {
            jl_errorf("cannot declare %s.%s constant; it was already declared global",
                      jl_symbol_name(mod->name), jl_symbol_name(var));
        }
        if (jl_atomic_load_relaxed(&bpart->min_world) == new_world) {
            bpart->kind = constant_kind | (bpart->kind & PARTITION_MASK_FLAG);
            jl_gc_write(bpart, bpart->restriction, jl_value_t, val);
            new_bpart = bpart;
        } else {
            new_bpart = jl_replace_binding_locked(b, bpart, val, constant_kind, new_world);
        }
        int need_backdate = new_world && val;
        if (need_backdate) {
            // We will backdate as long as this partition was never explicitly
            // declared const, global, or imported.
            jl_binding_partition_t *prev_bpart = bpart;
            for (;;) {
                enum jl_partition_kind prev_kind = jl_binding_kind(prev_bpart);
                if (jl_bkind_is_some_constant(prev_kind) || prev_kind == PARTITION_KIND_GLOBAL ||
                    jl_bkind_is_some_import(prev_kind)) {
                    need_backdate = 0;
                    break;
                }
                size_t prev_bpart_min_world = jl_atomic_load_relaxed(&prev_bpart->min_world);
                if (prev_bpart_min_world == 0)
                    break;
                prev_bpart = jl_get_binding_partition(b, prev_bpart_min_world - 1);
            }
        }
        // If backdate is required, replace each existing partition by a new one.
        // We can't use one binding to cover the entire range, because we need to
        // keep the flags partitioned.
        if (need_backdate) {
            jl_binding_partition_t *prev_bpart = bpart;
            jl_binding_partition_t *backdate_bpart = new_binding_partition(b);
            new_prev_bpart = backdate_bpart;
            while (1) {
                backdate_bpart->kind = (size_t)PARTITION_KIND_BACKDATED_CONST | (prev_bpart->kind & 0xf0);
                jl_gc_wb_fresh(backdate_bpart, val);
                backdate_bpart->restriction = val;
                jl_atomic_store_relaxed(&backdate_bpart->min_world,
                    jl_atomic_load_relaxed(&prev_bpart->min_world));
                jl_atomic_store_relaxed(&backdate_bpart->max_world,
                    jl_atomic_load_relaxed(&prev_bpart->max_world));
                prev_bpart = jl_atomic_load_relaxed(&prev_bpart->next);
                if (!is_some_partition(prev_bpart))
                    break;
                jl_binding_partition_t *next_prev_bpart = new_binding_partition(b);
                jl_gc_wb(backdate_bpart, next_prev_bpart);
                jl_atomic_store_relaxed(&backdate_bpart->next, next_prev_bpart);
                backdate_bpart = next_prev_bpart;
            }
            jl_gc_write_atomic(new_bpart, new_bpart->next, jl_binding_partition_t, new_prev_bpart, release);
        }
    }
    JL_GC_POP();
    return new_bpart;
}

JL_DLLEXPORT jl_module_t *jl_new_module(jl_sym_t *name, jl_module_t *parent)
{
    return jl_new_module_(name, parent, 1, 1);
}

uint32_t jl_module_next_counter(jl_module_t *m)
{
    return jl_atomic_fetch_add_relaxed(&m->counter, 1);
}

JL_DLLEXPORT jl_value_t *jl_f_new_module(jl_sym_t *name, uint8_t std_imports, uint8_t default_names) JL_CANSAFEPOINT
{
    // TODO: should we prohibit this during incremental compilation?
    // TODO: the parent module is a lie
    jl_module_t *m = jl_new_module_(name, jl_main_module, default_names, default_names);
    JL_GC_PUSH1(&m);
    if (std_imports)
        jl_add_standard_imports(m);
    JL_GC_POP();
    // TODO: should we somehow try to gc-root this correctly?
    return (jl_value_t*)m;
}

JL_DLLEXPORT void jl_set_module_nospecialize(jl_module_t *self, int on)
{
    self->nospecialize = (on ? -1 : 0);
}

JL_DLLEXPORT void jl_set_module_optlevel(jl_module_t *self, int lvl)
{
    self->optlevel = lvl;
}

JL_DLLEXPORT int jl_get_module_optlevel(jl_module_t *m)
{
    int lvl = m->optlevel;
    while (lvl == -1 && m->parent != m && m != jl_base_module) {
        m = m->parent;
        lvl = m->optlevel;
    }
    return lvl;
}

JL_DLLEXPORT void jl_set_module_compile(jl_module_t *self, int value)
{
    self->compile = value;
}

JL_DLLEXPORT int jl_get_module_compile(jl_module_t *m)
{
    int value = m->compile;
    while (value == -1 && m->parent != m && m != jl_base_module) {
        m = m->parent;
        value = m->compile;
    }
    return value;
}

JL_DLLEXPORT void jl_set_module_infer(jl_module_t *self, int value)
{
    self->infer = value;
    // no reason to specialize if inference is off
    if (!value)
        jl_set_module_nospecialize(self, 1);
}

JL_DLLEXPORT int jl_get_module_infer(jl_module_t *m)
{
    int value = m->infer;
    while (value == -1 && m->parent != m && m != jl_base_module) {
        m = m->parent;
        value = m->infer;
    }
    return value;
}

JL_DLLEXPORT void jl_set_module_max_methods(jl_module_t *self, int value)
{
    self->max_methods = value;
}

JL_DLLEXPORT int jl_get_module_max_methods(jl_module_t *m)
{
    int value = m->max_methods;
    while (value == -1 && m->parent != m && m != jl_base_module) {
        m = m->parent;
        value = m->max_methods;
    }
    return value;
}

JL_DLLEXPORT void jl_set_istopmod(jl_module_t *self, uint8_t isprimary)
{
    self->istopmod = 1;
    if (isprimary) {
        jl_top_module = self;
    }
}

JL_DLLEXPORT uint8_t jl_istopmod(jl_module_t *mod)
{
    return mod->istopmod;
}

static jl_globalref_t *jl_new_globalref(jl_module_t *mod, jl_sym_t *name, jl_binding_t *b) JL_CANSAFEPOINT
{
    jl_task_t *ct = jl_current_task;
    jl_globalref_t *g = (jl_globalref_t*)jl_gc_alloc(ct->ptls, sizeof(jl_globalref_t), jl_globalref_type);
    jl_set_typetagof(g, jl_globalref_tag, 0);
    jl_gc_wb_fresh(g, mod);
    g->mod = mod;
    jl_gc_wb_fresh(g, name);
    g->name = name;
    jl_gc_wb_fresh(g, b);
    g->binding = b;
    return g;
}

static jl_binding_t *new_binding(jl_module_t *mod, jl_sym_t *name) JL_CANSAFEPOINT
{
    jl_task_t *ct = jl_current_task;
    assert(jl_is_module(mod) && jl_is_symbol(name));
    jl_binding_t *b = (jl_binding_t*)jl_gc_alloc(ct->ptls, sizeof(jl_binding_t), jl_binding_type);
    jl_atomic_store_relaxed(&b->value, NULL);
    jl_atomic_store_relaxed(&b->partitions, NULL);
    b->globalref = NULL;
    b->backedges = NULL;
    jl_atomic_store_relaxed(&b->flags, 0);
    JL_GC_PUSH1(&b);
    jl_gc_write(b, b->globalref, jl_globalref_t, jl_new_globalref(mod, name, b));
    JL_GC_POP();
    return b;
}

extern jl_mutex_t jl_modules_mutex;

static int is_module_open(jl_module_t *m) JL_CANSAFEPOINT
{
    JL_LOCK(&jl_modules_mutex);
    int open = ptrhash_has(&jl_current_modules, (void*)m);
    if (!open && jl_module_init_order != NULL) {
        size_t i, l = jl_array_len(jl_module_init_order);
        for (i = 0; i < l; i++) {
            if (m == (jl_module_t*)jl_array_ptr_ref(jl_module_init_order, i)) {
                open = 1;
                break;
            }
        }
    }
    JL_UNLOCK(&jl_modules_mutex);
    return open;
}

void check_safe_newbinding(jl_module_t *m, jl_sym_t *var)
{
    if (jl_current_task->ptls->in_pure_callback)
        jl_errorf("new strong globals cannot be created in a generated function. Declare them outside using `global x::Any`.");
    if (jl_options.incremental && jl_generating_output() && !is_module_open(m)) {
        jl_errorf("Creating a new global in closed module `%s` (`%s`) breaks incremental compilation "
                    "because the side effects will not be permanent.",
                    jl_symbol_name(m->name), jl_symbol_name(var));
    }
}

static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b) JL_CANSAFEPOINT JL_GLOBALLY_ROOTED;

// Checks that the binding in general is currently writable, but does not perform any checks on the
// value to be written into the binding.
JL_DLLEXPORT void jl_check_binding_currently_writable(jl_binding_t *b, jl_module_t *m, jl_sym_t *s)
{
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    if (jl_options.depwarn && (bpart->kind & PARTITION_FLAG_DEPWARN)) {
        jl_binding_deprecation_warning(b);
    }
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (kind != PARTITION_KIND_GLOBAL && kind != PARTITION_KIND_DECLARED) {
        if (jl_bkind_is_some_guard(kind)) {
            jl_errorf("Global %s.%s does not exist and cannot be assigned.\n"
                        "Note: Julia 1.9 and 1.10 inadvertently omitted this error check (#56933).\n"
                        "Hint: Declare it using `global %s` inside `%s` before attempting assignment.",
                        jl_symbol_name(m->name), jl_symbol_name(s),
                        jl_symbol_name(s), jl_symbol_name(m->name));
        }
        else if (jl_bkind_is_some_constant(kind) && kind != PARTITION_KIND_IMPLICIT_CONST) {
            jl_errorf("invalid assignment to constant %s.%s. This redefinition may be permitted using the `const` keyword.",
                        jl_symbol_name(m->name), jl_symbol_name(s));
        }
        else {
            jl_module_t *from = jl_binding_dbgmodule(b);
            if (from == m || !from)
                jl_errorf("cannot assign a value to imported variable %s.%s",
                          jl_symbol_name(m->name), jl_symbol_name(s));
            else
                jl_errorf("cannot assign a value to imported variable %s.%s from module %s",
                          jl_symbol_name(from->name), jl_symbol_name(s), jl_symbol_name(m->name));
        }
    }
}

JL_DLLEXPORT jl_binding_t *jl_get_binding_wr(jl_module_t *m JL_PROPAGATES_ROOT, jl_sym_t *var)
{
    jl_binding_t *b = jl_get_module_binding(m, var, 1);
    jl_check_binding_currently_writable(b, m, var);
    return b;
}

// return module of binding
JL_DLLEXPORT jl_module_t *jl_get_module_of_binding(jl_module_t *m, jl_sym_t *var) JL_CANSAFEPOINT
{
    size_t world = jl_current_task->world_age;
    jl_binding_t *b = jl_get_module_binding(m, var, 1);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, world);
    jl_walk_binding_inplace(&b, &bpart, world);
    if (jl_binding_kind(bpart) == PARTITION_KIND_IMPLICIT_CONST) {
        struct implicit_search_resolution resolution = jl_resolve_implicit_import(b, NULL, world, 0);
        if (!resolution.debug_only_ultimate_binding)
            jl_error("Constant binding was imported from multiple modules");
        b = resolution.debug_only_ultimate_binding;
    }
    return b ? b->globalref->mod : m;
}

static NOINLINE void print_backdate_admonition(jl_binding_t *b) JL_NOTSAFEPOINT
{
    jl_safe_printf(
        "WARNING: Detected access to binding `%s.%s` in a world prior to its definition world.\n"
        "  Julia 1.12 has introduced more strict world age semantics for global bindings.\n"
        "  !!! This code may malfunction under Revise.\n"
        "  !!! This code will error in future versions of Julia.\n"
        "Hint: Add an appropriate `invokelatest` around the access to this binding.\n"
        "To make this warning an error, and hence obtain a stack trace, use `julia --depwarn=error`.\n",
        jl_symbol_name(b->globalref->mod->name), jl_symbol_name(b->globalref->name));
}

static inline void check_backdated_binding(jl_binding_t *b, enum jl_partition_kind kind) JL_NOTSAFEPOINT
{
    if (__unlikely(kind == PARTITION_KIND_BACKDATED_CONST)) {
        // We don't want functions that inference executes speculatively to print this warning, so turn those into
        // an error for inference purposes.
        if (jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR)
            jl_undefined_var_error(b->globalref->name, (jl_value_t*)b->globalref->mod);
        if (!(jl_atomic_fetch_or_relaxed(&b->flags, BINDING_FLAG_DID_PRINT_BACKDATE_ADMONITION) & BINDING_FLAG_DID_PRINT_BACKDATE_ADMONITION))
            print_backdate_admonition(b);
    }
}

JL_DLLEXPORT jl_value_t *jl_get_binding_value(jl_binding_t *b)
{
    return jl_get_binding_value_in_world(b, jl_current_task->world_age);
}

JL_DLLEXPORT jl_value_t *jl_get_binding_value_in_world(jl_binding_t *b, size_t world)
{
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, world);
    jl_walk_binding_inplace(&b, &bpart, world);
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_guard(kind))
        return NULL;
    if (jl_bkind_is_some_constant(kind)) {
        check_backdated_binding(b, kind);
        return bpart->restriction;
    }
    assert(!jl_bkind_is_some_import(kind));
    return jl_atomic_load_relaxed(&b->value);
}

static jl_value_t *jl_get_binding_value_depwarn(jl_binding_t *b, size_t world) JL_CANSAFEPOINT
{
    assert(b); // alloc=1 parameter ensured that jl_get_module_binding returns a valid binding
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, world);
    if (jl_options.depwarn) {
        int needs_depwarn = 0;
        jl_walk_binding_inplace_depwarn(&b, &bpart, world, &needs_depwarn);
        if (needs_depwarn)
            jl_binding_deprecation_warning(b);
    }
    else {
        jl_walk_binding_inplace(&b, &bpart, world);
    }
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_guard(kind))
        return NULL;
    if (jl_bkind_is_some_constant(kind)) {
        check_backdated_binding(b, kind);
        return bpart->restriction;
    }
    assert(!jl_bkind_is_some_import(kind));
    return jl_atomic_load_relaxed(&b->value);
}

JL_DLLEXPORT jl_value_t *jl_get_binding_value_seqcst(jl_binding_t *b) JL_CANSAFEPOINT
{
    size_t world = jl_current_task->world_age;
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, world);
    jl_walk_binding_inplace(&b, &bpart, world);
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_guard(kind))
        return NULL;
    if (jl_bkind_is_some_constant(kind)) {
        check_backdated_binding(b, kind);
        return bpart->restriction;
    }
    assert(!jl_bkind_is_some_import(kind));
    return jl_atomic_load(&b->value);
}

JL_DLLEXPORT jl_value_t *jl_get_latest_binding_value_if_const(jl_binding_t *b)
{
    // See note below. Note that this is for some deprecated uses, and should not be added to new code.
    size_t world = jl_atomic_load_acquire(&jl_world_counter);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, world);
    jl_walk_binding_inplace(&b, &bpart, world);
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_guard(kind))
        return NULL;
    if (!jl_bkind_is_real_constant(kind))
        return NULL;
    return bpart->restriction;
}

JL_DLLEXPORT jl_value_t *jl_get_latest_binding_value_if_resolved_and_const_debug_only(jl_binding_t *b)
{
    // Unlike jl_get_latest_binding_value_if_const this doesn't try to allocate new binding partitions if they
    // don't already exist, making this JL_NOTSAFEPOINT. However, as a result, this may fail to return
    // a value - even if one does exist. It should only be used for reflection/debugging when the integrity
    // of the runtime is not guaranteed.
    if (!b)
        return NULL;
    jl_binding_partition_t *bpart = jl_atomic_load_relaxed(&b->partitions);
    if (!bpart)
        return NULL;
    size_t max_world = jl_atomic_load_relaxed(&bpart->max_world);
    if (max_world != ~(size_t)0)
        return NULL;
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_guard(kind))
        return NULL;
    if (!jl_bkind_is_real_constant(kind))
        return NULL;
    return bpart->restriction;
}

JL_DLLEXPORT jl_value_t *jl_get_latest_binding_value_if_resolved_debug_only(jl_binding_t *b)
{
    // See note above. Use for debug/reflection purposes only.
    if (!b)
        return NULL;
    jl_binding_partition_t *bpart = jl_atomic_load_relaxed(&b->partitions);
    if (!bpart)
        return NULL;
    size_t max_world = jl_atomic_load_relaxed(&bpart->max_world);
    if (max_world != ~(size_t)0)
        return NULL;
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_guard(kind))
        return NULL;
    if (jl_bkind_is_some_import(kind))
        return NULL;
    if (jl_bkind_is_some_constant(kind)) {
        return bpart->restriction;
    }
    return jl_atomic_load_relaxed(&b->value);
}

JL_DLLEXPORT jl_value_t *jl_bpart_get_restriction_value(jl_binding_partition_t *bpart)
{
    jl_value_t *v = bpart->restriction;
    if (!v)
        jl_throw(jl_undefref_exception);
    return v;
}

// for error message printing: look up the module that exported a binding to m as var
// this might not be the same as the owner of the binding, since the binding itself may itself have been imported from elsewhere
static jl_module_t *jl_binding_dbgmodule(jl_binding_t *b)
{
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_explicit_import(kind) || kind == PARTITION_KIND_IMPLICIT_GLOBAL) {
        return ((jl_binding_t*)bpart->restriction)->globalref->mod;
    }
    if (kind == PARTITION_KIND_IMPLICIT_CONST) {
        struct implicit_search_resolution resolution = jl_resolve_implicit_import(b, NULL, jl_current_task->world_age, 1);
        return resolution.debug_only_import_from;
    }
    return b->globalref->mod;
}

// Look at the given binding and decide whether to add a new method to an existing generic function
// or ask for the creation of a new generic function (NULL return), checking various error conditions
// along the way.
JL_DLLEXPORT jl_value_t *jl_get_existing_strong_gf(jl_binding_t *b, size_t new_world)
{
    assert(new_world > jl_atomic_load_relaxed(&jl_world_counter));
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world);
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_constant(kind) && kind != PARTITION_KIND_IMPLICIT_CONST)
        return bpart->restriction;
    if (jl_bkind_is_some_guard(kind) || kind == PARTITION_KIND_DECLARED) {
        check_safe_newbinding(b->globalref->mod, b->globalref->name);
        return NULL;
    }
    if (!jl_bkind_is_some_import(kind)) {
        jl_errorf("cannot define function %s; it already has a value", jl_symbol_name(b->globalref->name));
    }
    jl_binding_t *ownerb = b;
    jl_walk_binding_inplace(&ownerb, &bpart, new_world);
    jl_value_t *f = NULL;
    if (jl_bkind_is_some_constant(jl_binding_kind(bpart)))
        f = bpart->restriction;
    if (f == NULL) {
        if (jl_bkind_is_some_implicit(kind)) {
            check_safe_newbinding(b->globalref->mod, b->globalref->name);
            return NULL;
        }
        jl_module_t *from = jl_binding_dbgmodule(b);
        assert(from); // Can only be NULL if implicit, which we excluded above
        jl_errorf("invalid method definition in %s: exported function %s.%s does not exist",
                    jl_module_debug_name(b->globalref->mod), jl_module_debug_name(from), jl_symbol_name(b->globalref->name));
    }
    int istype = f && jl_is_type(f);
    if (!istype) {
        if (jl_bkind_is_some_implicit(kind)) {
            check_safe_newbinding(b->globalref->mod, b->globalref->name);
            return NULL;
        }
        else if (kind != PARTITION_KIND_IMPORTED) {
            // TODO: we might want to require explicitly importing types to add constructors
            //       or we might want to drop this error entirely
            jl_module_t *from = jl_binding_dbgmodule(b);
            assert(from); // Can only be NULL if implicit, which we excluded above
            jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended",
                        jl_module_debug_name(b->globalref->mod), jl_module_debug_name(from), jl_symbol_name(b->globalref->name));
        }
    }
    else if (kind != PARTITION_KIND_IMPORTED) {
        int should_error = strcmp(jl_symbol_name(b->globalref->name), "=>") == 0;
        jl_module_t *from = jl_binding_dbgmodule(b);
        if (should_error) {
            jl_errorf("invalid method definition in %s: function %s.%s must be explicitly imported to be extended",
                        jl_module_debug_name(b->globalref->mod), from ? jl_module_debug_name(from) : "<multiple modules>", jl_symbol_name(b->globalref->name));
        }
        else if (!(jl_atomic_fetch_or_relaxed(&b->flags, BINDING_FLAG_DID_PRINT_IMPLICIT_IMPORT_ADMONITION) &
                                              BINDING_FLAG_DID_PRINT_IMPLICIT_IMPORT_ADMONITION)) {
            jl_printf(JL_STDERR, "WARNING: Constructor for type \"%s\" was extended in `%s` without explicit qualification or import.\n"
                                 "  NOTE: Assumed \"%s\" refers to `%s.%s`. This behavior is deprecated and may differ in future versions.\n"
                                 "  NOTE: This behavior may have differed in Julia versions prior to 1.12.\n"
                                 "  Hint: If you intended to create a new generic function of the same name, use `function %s end`.\n"
                                 "  Hint: To silence the warning, qualify `%s` as `%s.%s` in the method signature or explicitly `import %s: %s`.\n",
                jl_symbol_name(b->globalref->name), jl_module_debug_name(b->globalref->mod),
                jl_symbol_name(b->globalref->name), jl_module_debug_name(from), jl_symbol_name(b->globalref->name),
                jl_symbol_name(b->globalref->name), jl_symbol_name(b->globalref->name), jl_module_debug_name(from), jl_symbol_name(b->globalref->name),
                jl_module_debug_name(from), jl_symbol_name(b->globalref->name));
        }
    }
    return f;
}

static void jl_binding_dep_message(jl_binding_t *b) JL_CANSAFEPOINT;

// get type of binding m.var, without resolving the binding
JL_DLLEXPORT jl_value_t *jl_get_binding_type(jl_module_t *m, jl_sym_t *var)
{
    jl_binding_t *b = jl_get_module_binding(m, var, 0);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    if (b == NULL)
        return jl_nothing;
    jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age);
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_guard(kind) || kind == PARTITION_KIND_DECLARED)
        return jl_nothing;
    if (jl_bkind_is_some_constant(kind)) {
        // TODO: We would like to return the type of the constant, but
        // currently code relies on this returning any to bypass conversion
        // before an attempted assignment to a constant.
        // return bpart->restriction;
        return (jl_value_t*)jl_any_type;
    }
    return bpart->restriction;
}

JL_DLLEXPORT jl_binding_t *jl_get_binding(jl_module_t *m, jl_sym_t *var)
{
    return jl_get_module_binding(m, var, 1);
}

JL_DLLEXPORT jl_value_t *jl_module_globalref(jl_module_t *m, jl_sym_t *var)
{
    jl_binding_t *b = jl_get_module_binding(m, var, 1);
    jl_globalref_t *globalref = b->globalref;
    assert(globalref != NULL);
    return (jl_value_t*)globalref;
}

// does module m explicitly import s?
JL_DLLEXPORT int jl_is_imported(jl_module_t *m, jl_sym_t *var)
{
    jl_binding_t *b = jl_get_module_binding(m, var, 0);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    return b && jl_binding_kind(bpart) == PARTITION_KIND_IMPORTED;
}

extern _Atomic(const char *) jl_filename;
extern _Atomic(int) jl_lineno;

static char const dep_message_prefix[] = "_dep_message_";

static void jl_binding_dep_message(jl_binding_t *b)
{
    jl_module_t *m = b->globalref->mod;
    jl_sym_t *name = b->globalref->name;
    size_t prefix_len = strlen(dep_message_prefix);
    size_t name_len = strlen(jl_symbol_name(name));
    char *dep_binding_name = (char*)alloca(prefix_len+name_len+1);
    memcpy(dep_binding_name, dep_message_prefix, prefix_len);
    memcpy(dep_binding_name + prefix_len, jl_symbol_name(name), name_len);
    dep_binding_name[prefix_len+name_len] = '\0';
    jl_binding_t *dep_message_binding = jl_get_binding(m, jl_symbol(dep_binding_name));
    jl_value_t *dep_message = NULL;
    if (dep_message_binding != NULL)
        dep_message = jl_get_binding_value(dep_message_binding);
    JL_GC_PUSH1(&dep_message);
    if (dep_message != NULL) {
        if (jl_is_string(dep_message)) {
            jl_uv_puts(JL_STDERR, jl_string_data(dep_message), jl_string_len(dep_message));
        }
        else {
            jl_static_show(JL_STDERR, dep_message);
        }
    }
    else {
        jl_value_t *v = jl_get_binding_value(b);
        dep_message = v; // use as gc-root
        if (v) {
            if (jl_is_type(v) || jl_is_module(v)) {
                jl_printf(JL_STDERR, ", use ");
                jl_static_show(JL_STDERR, v);
                jl_printf(JL_STDERR, " instead.");
            }
            else {
                jl_typename_t *tn = ((jl_datatype_t*)jl_typeof(v))->name;
                if (tn != NULL) {
                    jl_printf(JL_STDERR, ", use ");
                    if (tn->module != jl_core_module) {
                        jl_static_show(JL_STDERR, (jl_value_t*)tn->module);
                        jl_printf(JL_STDERR, ".");
                    }
                    jl_printf(JL_STDERR, "%s", jl_symbol_name(tn->singletonname));
                    jl_printf(JL_STDERR, " instead.");
                }
            }
        }
    }
    jl_printf(JL_STDERR, "\n");
    JL_GC_POP();
}

JL_DLLEXPORT void check_safe_import_from(jl_module_t *m)
{
    if (jl_options.incremental && jl_generating_output() && m == jl_main_module) {
        jl_errorf("Any `import` or `using` from `Main` is prohibited during incremental compilation.");
    }
}

static int eq_bindings(jl_binding_partition_t *owner, jl_binding_t *alias, size_t world) JL_CANSAFEPOINT
{
    jl_binding_t *ownerb = NULL;
    jl_binding_partition_t *alias_bpart = jl_get_binding_partition(alias, world);
    if (owner == alias_bpart)
        return 1;
    jl_walk_binding_inplace(&ownerb, &owner, world);
    jl_walk_binding_inplace(&alias, &alias_bpart, world);
    if (jl_bkind_is_some_constant(jl_binding_kind(owner)) &&
        jl_bkind_is_some_constant(jl_binding_kind(alias_bpart)) &&
        owner->restriction &&
        alias_bpart->restriction == owner->restriction)
        return 1;
    return owner == alias_bpart;
}

// NOTE: we use explici since explicit is a C++ keyword
JL_DLLEXPORT void jl_module_import(jl_task_t *ct, jl_module_t *to, jl_module_t *from, jl_sym_t *asname, jl_sym_t *s, int explici)
{
    check_safe_import_from(from);
    jl_binding_t *b = jl_get_binding(from, s);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    if (bpart->kind & PARTITION_FLAG_DEPRECATED) {
        if (jl_get_binding_value(b) == jl_nothing) {
            // silently skip importing deprecated values assigned to nothing (to allow later mutation)
            return;
        }
        else if (to != jl_main_module && to != jl_base_module &&
                    jl_options.depwarn != JL_OPTIONS_DEPWARN_OFF) {
            /* with #22763, external packages wanting to replace
                deprecated Base bindings should simply export the new
                binding */
            jl_printf(JL_STDERR,
                        "WARNING: importing deprecated binding %s.%s into %s%s%s.\n",
                        jl_symbol_name(from->name), jl_symbol_name(s),
                        jl_symbol_name(to->name),
                        asname == s ? "" : " as ",
                        asname == s ? "" : jl_symbol_name(asname));
            jl_binding_dep_message(b);
        }
    }

    jl_binding_t *ownerb = b;
    jl_binding_partition_t *ownerbpart = bpart;
    jl_walk_binding_inplace(&ownerb, &ownerbpart, ct->world_age);

    if (jl_bkind_is_some_guard(jl_binding_kind(ownerbpart))) {
        jl_printf(JL_STDERR,
                  "WARNING: Imported binding %s.%s was undeclared at import time during import to %s.\n",
                  jl_symbol_name(from->name), jl_symbol_name(s),
                  jl_symbol_name(to->name));
    }

    jl_binding_t *bto = jl_get_module_binding(to, asname, 1);
    if (bto == b) {
        // importing a binding on top of itself. harmless.
        return;
    }
    JL_LOCK(&world_counter_lock);
    size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1;
    jl_binding_partition_t *btopart = jl_get_binding_partition(bto, new_world);
    enum jl_partition_kind btokind = jl_binding_kind(btopart);
    if (jl_bkind_is_some_implicit(btokind)) {
        jl_binding_partition_t *new_bpart = jl_replace_binding_locked(bto, btopart, (jl_value_t*)b, (explici != 0) ? PARTITION_KIND_IMPORTED : PARTITION_KIND_EXPLICIT, new_world);
        if (jl_atomic_load_relaxed(&new_bpart->max_world) == ~(size_t)0)
            jl_add_binding_backedge(b, (jl_value_t*)bto);
        jl_atomic_store_release(&jl_world_counter, new_world);
    }
    else {
        if (eq_bindings(bpart, bto, new_world)) {
            // already imported - potentially upgrade _EXPLICIT to _IMPORTED
            if (btokind == PARTITION_KIND_EXPLICIT && explici != 0) {
                jl_replace_binding_locked(bto, btopart, (jl_value_t*)b, PARTITION_KIND_IMPORTED, new_world);
                jl_atomic_store_release(&jl_world_counter, new_world);
            }
        }
        else if (jl_bkind_is_some_import(btokind)) {
            // already imported from somewhere else
            jl_printf(JL_STDERR,
                        "WARNING: ignoring conflicting import of %s.%s into %s\n",
                        jl_symbol_name(from->name), jl_symbol_name(s),
                        jl_symbol_name(to->name));
        }
        else {
            // conflict with name owned by destination module
            jl_printf(JL_STDERR,
                        "WARNING: import of %s.%s into %s conflicts with an existing identifier; ignored.\n",
                        jl_symbol_name(from->name), jl_symbol_name(s),
                        jl_symbol_name(to->name));
        }
    }
    JL_UNLOCK(&world_counter_lock);
}

JL_DLLEXPORT void jl_import_module(jl_task_t *ct, jl_module_t *JL_NONNULL m, jl_module_t *import, jl_sym_t *asname)
{
    assert(m);
    jl_sym_t *name = asname ? asname : import->name;
    // TODO: this is a bit race-y with what error message we might print
    jl_binding_t *b = jl_get_module_binding(m, name, 1);
    size_t world = jl_atomic_load_acquire(&jl_world_counter);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, world);
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (!jl_bkind_is_some_implicit(kind) && kind != PARTITION_KIND_DECLARED) {
        // Unlike regular constant declaration, we allow this as long as we eventually end up at a constant.
        jl_walk_binding_inplace(&b, &bpart, world);
        if (jl_bkind_is_some_constant(jl_binding_kind(bpart))) {
            // Already declared (e.g. on another thread) or imported.
            if (bpart->restriction == (jl_value_t*)import)
                return;
        }
        jl_errorf("importing %s into %s conflicts with an existing global",
                    jl_symbol_name(name), jl_symbol_name(m->name));
    }
    jl_declare_constant_val2(b, m, name, (jl_value_t*)import, PARTITION_KIND_CONST_IMPORT);
}

void jl_add_usings_backedge(jl_module_t *from, jl_module_t *to)
{
    JL_LOCK(&from->lock);
    if (from->usings_backedges == jl_nothing) {
        jl_gc_write(from, from->usings_backedges, jl_value_t, (jl_value_t*)jl_alloc_vec_any(0));
    }
    jl_array_ptr_1d_push((jl_array_t*)from->usings_backedges, (jl_value_t*)to);
    JL_UNLOCK(&from->lock);
}

void jl_module_initial_using(jl_module_t *to, jl_module_t *from)
{
    struct _jl_module_using new_item = {
        .mod = from,
        .min_world = 0,
        .max_world = ~(size_t)0,
        .flags = 0
    };
    jl_gc_wb(to, from);
    arraylist_grow(&to->usings, sizeof(struct _jl_module_using)/sizeof(void*));
    memcpy(&to->usings.items[to->usings.len-4], &new_item, sizeof(struct _jl_module_using));
    jl_add_usings_backedge(from, to);
}

JL_DLLEXPORT void jl_module_using(jl_module_t *to, jl_module_t *from, size_t flags)
{
    if (to == from)
        return;
    check_safe_import_from(from);
    JL_LOCK(&world_counter_lock);
    JL_LOCK(&to->lock);

    // Check if this module is already in the usings list
    size_t existing_idx = (size_t)-1;
    for (size_t i = 0; i < module_usings_length(to); i++) {
        if (from == module_usings_getmod(to, i)) {
            existing_idx = i;
            break;
        }
    }

    size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1;

    if (existing_idx == (size_t)-1) {
        // Add new using entry
        struct _jl_module_using new_item = {
            .mod = from,
            .min_world = new_world,
            .max_world = ~(size_t)0,
            .flags = flags
        };
        jl_gc_wb(to, from);
        arraylist_grow(&to->usings, sizeof(struct _jl_module_using)/sizeof(void*));
        memcpy(&to->usings.items[to->usings.len-4], &new_item, sizeof(struct _jl_module_using));
    } else {
        // Update existing entry to add new flags
        struct _jl_module_using *existing = module_usings_getidx(to, existing_idx);
        // Early out if reexport is already set (strongest form), or if all requested flags are already set
        if ((existing->flags & JL_MODULE_USING_REEXPORT) || (existing->flags & flags) == flags) {
            JL_UNLOCK(&to->lock);
            JL_UNLOCK(&world_counter_lock);
            return;
        }
        existing->flags |= flags;
    }

    // Set has_reexports flag if this is a reexport using
    if (flags & JL_MODULE_USING_REEXPORT) {
        jl_atomic_store_relaxed(&to->has_reexports, 1);
    }

    JL_UNLOCK(&to->lock);

    // Go through all exported bindings. If we have a binding for this in the
    // importing module and it is some import or guard, we need to recompute
    // it.
    jl_svec_t *table = jl_atomic_load_relaxed(&from->bindings);
    for (size_t i = 0; i < jl_svec_len(table); i++) {
        jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i);
        if ((void*)b == jl_nothing)
            break;
        jl_binding_partition_t *frombpart = jl_get_binding_partition(b, new_world);
        if (jl_bpart_is_exported(frombpart->kind)) {
            jl_sym_t *var = b->globalref->name;
            jl_binding_t *tob = jl_get_module_binding(to, var, 0);
            if (tob) {
                jl_binding_partition_t *tobpart = jl_atomic_load_relaxed(&tob->partitions);
                if (tobpart) {
                    enum jl_partition_kind kind = jl_binding_kind(tobpart);
                    if (jl_bkind_is_some_implicit(kind)) {
                        jl_replace_binding_locked(tob, tobpart, NULL, PARTITION_FAKE_KIND_IMPLICIT_RECOMPUTE, new_world);
                    }
                }
            }
        }
        table = jl_atomic_load_relaxed(&from->bindings);
    }

    jl_add_usings_backedge(from, to);

    jl_atomic_store_release(&jl_world_counter, new_world);
    JL_UNLOCK(&world_counter_lock);
}

JL_DLLEXPORT jl_value_t *jl_get_module_usings_backedges(jl_module_t *m)
{
    // We assume the caller holds the world_counter_lock, which is the only place we set this
    // TODO: We may want to make this more precise with the module lock
    return m->usings_backedges;
}

JL_DLLEXPORT size_t jl_module_scanned_methods_length(jl_module_t *m) JL_CANSAFEPOINT
{
    JL_LOCK(&m->lock);
    size_t len = 0;
    if (m->scanned_methods != jl_nothing)
        len = jl_array_len(m->scanned_methods);
    JL_UNLOCK(&m->lock);
    return len;
}

JL_DLLEXPORT jl_value_t *jl_module_scanned_methods_getindex(jl_module_t *m, size_t i) JL_CANSAFEPOINT
{
    JL_LOCK(&m->lock);
    assert(m->scanned_methods != jl_nothing);
    jl_value_t *ret = jl_array_ptr_ref(m->scanned_methods, i-1);
    JL_UNLOCK(&m->lock);
    return ret;
}

JL_DLLEXPORT jl_value_t *jl_get_module_binding_or_nothing(jl_module_t *m, jl_sym_t *s)
{
    jl_binding_t *b = jl_get_module_binding(m, s, 0);
    if (!b)
        return jl_nothing;
    return (jl_value_t*)b;
}

int jl_module_public_(jl_module_t *from, jl_sym_t *s, int exported, size_t new_world)
{
    // caller must hold world_counter_lock
    jl_binding_t *b = jl_get_module_binding(from, s, 1);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, new_world);
    int was_exported = (bpart->kind & PARTITION_FLAG_EXPORTED) != 0;
    if (jl_atomic_load_relaxed(&b->flags) & BINDING_FLAG_PUBLICP) {
        // check for conflicting declarations
        if (was_exported && !exported)
            jl_errorf("cannot declare %s.%s public; it is already declared exported",
                      jl_symbol_name(from->name), jl_symbol_name(s));
        if (!was_exported && exported)
            jl_errorf("cannot declare %s.%s exported; it is already declared public",
                      jl_symbol_name(from->name), jl_symbol_name(s));
    }
    jl_atomic_fetch_or_relaxed(&b->flags, BINDING_FLAG_PUBLICP);
    if (was_exported != exported) {
        jl_replace_binding_locked2(b, bpart, bpart->restriction, bpart->kind | PARTITION_FLAG_EXPORTED, new_world);
        return 1;
    }
    return 0;
}

// Declare `symbols` in module `from` public or public+exported
JL_DLLEXPORT void jl_module_public(jl_module_t *from, jl_value_t **symbols, size_t nsymbols, int exported)
{
    volatile int any_new = 0;
    JL_LOCK(&world_counter_lock);
    size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1;
    JL_TRY {
        for (size_t i = 0; i < nsymbols; i++) {
            jl_sym_t *name = (jl_sym_t*)symbols[i];
            JL_TYPECHK(jl_module_public, symbol, (jl_value_t*)name);
            if (jl_module_public_(from, name, exported, new_world))
                any_new = 1;
        }
    }
    JL_CATCH {
        if (any_new)
            jl_atomic_store_release(&jl_world_counter, new_world);
        JL_UNLOCK(&world_counter_lock);
        jl_rethrow();
    }
    if (any_new)
        jl_atomic_store_release(&jl_world_counter, new_world);
    JL_UNLOCK(&world_counter_lock);
}

JL_DLLEXPORT int jl_boundp(jl_module_t *m, jl_sym_t *var, int allow_import) // unlike most queries here, this is currently seq_cst
{
    jl_binding_t *b = jl_get_module_binding(m, var, allow_import);
    if (!b)
        return 0;
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    if (!bpart)
        return 0;
    if (!allow_import) {
        if (!bpart || jl_bkind_is_some_import(jl_binding_kind(bpart)))
            return 0;
    } else {
        jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age);
    }
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    if (jl_bkind_is_some_guard(kind))
        return 0;
    if (jl_bkind_is_defined_constant(kind)) {
        if (__unlikely(kind == PARTITION_KIND_BACKDATED_CONST)) {
            return !(jl_current_task->ptls->in_pure_callback || jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR);
        }
        // N.B.: No backdated admonition for isdefined
        return 1;
    }
    return jl_atomic_load(&b->value) != NULL;
}

#ifndef __clang_gcanalyzer__ // this method is unsound due to mutating behavior of jl_get_binding_partition
JL_DLLEXPORT int jl_module_exports_p(jl_module_t *m, jl_sym_t *var) JL_NO_SAFEPOINT_ANALYSIS
{
    jl_binding_t *b = jl_get_module_binding(m, var, 0);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    return b && jl_bpart_is_exported(bpart->kind);
}
#endif

JL_DLLEXPORT int jl_module_public_p(jl_module_t *m, jl_sym_t *var) JL_CANSAFEPOINT
{
    jl_binding_t *b = jl_get_module_binding(m, var, 0);
    return b && (jl_atomic_load_relaxed(&b->flags) & BINDING_FLAG_PUBLICP);
}

uint_t bindingkey_hash(size_t idx, jl_value_t *data)
{
    jl_binding_t *b = (jl_binding_t*)jl_svecref(data, idx); // This must always happen inside the lock
    jl_sym_t *var = b->globalref->name;
    return var->hash;
}

static int bindingkey_eq(size_t idx, const void *var, jl_value_t *data, uint_t hv)
{
    if (idx >= jl_svec_len(data))
        return 0; // We got a OOB access, probably due to a data race
    jl_binding_t *b = (jl_binding_t*)jl_svecref(data, idx);
    if (b == NULL || (jl_value_t*)b == jl_nothing)
        return 0; // slot not yet published, probably due to a data race
    jl_sym_t *name = b->globalref->name;
    return var == name;
}

JL_DLLEXPORT jl_binding_t *jl_get_module_binding(jl_module_t *m, jl_sym_t *var, int alloc)
{
    uint_t hv = var->hash;
    for (int locked = 0; ; locked++) {
        jl_genericmemory_t *bindingkeyset = jl_atomic_load_acquire(&m->bindingkeyset);
        jl_svec_t *bindings = jl_atomic_load_relaxed(&m->bindings);
        ssize_t idx = jl_smallintset_lookup(bindingkeyset, bindingkey_eq, var, (jl_value_t*)bindings, hv, 0); // acquire
        if (idx != -1) {
            jl_binding_t *b = (jl_binding_t*)jl_svecref(bindings, idx); // relaxed
            JL_GC_PROMISE_ROOTED(b);
            if (locked)
                JL_UNLOCK(&m->lock);
            return b;
        }
        if (!alloc) {
            return NULL;
        }
        else if (!locked) {
            JL_LOCK(&m->lock);
        }
        else {
            size_t i, cl = jl_svec_len(bindings);
            for (i = cl; i > 0; i--) {
                jl_value_t *b = jl_svecref(bindings, i - 1);
                if (b != jl_nothing)
                    break;
            }
            if (i == cl) {
                size_t ncl = cl < 8 ? 8 : (cl*3)>>1; // grow 50%
                jl_svec_t *nc = jl_alloc_svec_uninit(ncl);
                if (i > 0)
                    memcpy((char*)jl_svec_data(nc), jl_svec_data(bindings), sizeof(void*) * i);
                for (size_t j = i; j < ncl; j++)
                    jl_svec_data(nc)[j] = jl_nothing;
                jl_gc_write_atomic(m, m->bindings, jl_svec_t, nc, release);
                bindings = nc;
            }
            jl_binding_t *b = new_binding(m, var);
            assert(jl_svecref(bindings, i) == jl_nothing);
            jl_svecset(bindings, i, b); // relaxed
            jl_smallintset_insert(&m->bindingkeyset, (jl_value_t*)m, bindingkey_hash, i, (jl_value_t*)bindings); // release
            JL_UNLOCK(&m->lock);
            return b;
        }
    }
}


// get the value (or null) in the world
jl_value_t *jl_get_globalref_value(jl_globalref_t *gr, size_t world)
{
    jl_binding_t *b = gr->binding;
    if (!b)
        b = jl_get_module_binding(gr->mod, gr->name, 1);
    return jl_get_binding_value_depwarn(b, world);
}

// get the value (or null) in the world
jl_value_t *jl_get_global_value(jl_module_t *m, jl_sym_t *var, size_t world)
{
    jl_binding_t *b = jl_get_module_binding(m, var, 1);
    return jl_get_binding_value_depwarn(b, world);
}

// get the global (or null) in the latest world
JL_DLLEXPORT jl_value_t *jl_get_global(jl_module_t *m, jl_sym_t *var)
{
    jl_binding_t *b = jl_get_module_binding(m, var, 1);
    return jl_get_binding_value_depwarn(b, jl_atomic_load_acquire(&jl_world_counter));
}

JL_DLLEXPORT void jl_set_global(jl_module_t *m, jl_sym_t *var, jl_value_t *val JL_ROOTED_BY_ARG(0))
{
    jl_binding_t *bp = jl_get_binding_wr(m, var);
    jl_checked_assignment(bp, m, var, val);
}

void jl_set_initial_const(jl_module_t *m, jl_sym_t *var, jl_value_t *val JL_ROOTED_BY_ARG(0), int exported)
{
    // this function is only valid during initialization, so there is no risk of data races
    int kind = PARTITION_KIND_CONST | (exported ? PARTITION_FLAG_EXPORTED : 0);
    // jl_declare_constant_val3(NULL, m, var, (jl_value_t*)jl_any_type, kind, 0);
    jl_binding_t *bp = jl_get_module_binding(m, var, 1);
    jl_binding_partition_t *bpart = jl_get_binding_partition(bp, 0);
    assert(jl_atomic_load_relaxed(&bpart->min_world) == 0);
    jl_atomic_store_relaxed(&bpart->max_world, ~(size_t)0); // jl_check_new_binding_implicit likely incorrectly truncated it
    if (exported)
        jl_atomic_fetch_or_relaxed(&bp->flags, BINDING_FLAG_PUBLICP);
    bpart->kind = kind | (bpart->kind & PARTITION_MASK_FLAG);
    jl_gc_write(bpart, bpart->restriction, jl_value_t, val);
}

JL_DLLEXPORT void jl_set_const(jl_module_t *m, jl_sym_t *var, jl_value_t *val JL_ROOTED_BY_ARG(0))
{
    // this function is dangerous and unsound. do not use.
    jl_binding_t *bp = jl_get_module_binding(m, var, 1);
    jl_binding_partition_t *bpart = jl_get_binding_partition(bp, jl_current_task->world_age);
    jl_atomic_store_relaxed(&bpart->min_world, 0);
    jl_atomic_store_release(&bpart->max_world, ~(size_t)0);
    bpart->kind = PARTITION_KIND_CONST | (bpart->kind & PARTITION_MASK_FLAG);
    jl_gc_write(bpart, bpart->restriction, jl_value_t, val);
}

static void jl_invalidate_binding_refs(jl_globalref_t *ref, jl_binding_partition_t *invalidated_bpart, jl_binding_partition_t *new_bpart, size_t new_world) JL_CANSAFEPOINT
{
    jl_value_t *invalidate_code_for_globalref = NULL;
    if (jl_base_module != NULL)
        invalidate_code_for_globalref = jl_get_global(jl_base_module, jl_symbol("invalidate_code_for_globalref!"));
    if (!invalidate_code_for_globalref)
        jl_error("Binding invalidation is not permitted during bootstrap.");
    jl_value_t **fargs;
    JL_GC_PUSHARGS(fargs, 5);
    fargs[0] = (jl_value_t*)invalidate_code_for_globalref;
    fargs[1] = (jl_value_t*)ref;
    fargs[2] = (jl_value_t*)invalidated_bpart;
    fargs[3] = (jl_value_t*)new_bpart;
    fargs[4] = jl_box_ulong(new_world);
    jl_apply(fargs, 5);
    JL_GC_POP();
}

JL_DLLEXPORT void jl_add_binding_backedge(jl_binding_t *b, jl_value_t *edge)
{
    JL_LOCK(&b->globalref->mod->lock);
    if (!b->backedges) {
        jl_gc_write(b, b->backedges, jl_array_t, jl_alloc_vec_any(0));
    } else if (jl_array_len(b->backedges) > 0 &&
               jl_array_ptr_ref(b->backedges, jl_array_len(b->backedges)-1) == edge) {
        // Optimization: Deduplicate repeated insertion of the same edge (e.g. during
        // definition of a method that contains many references to the same global)
        JL_UNLOCK(&b->globalref->mod->lock);
        return;
    }
    jl_array_ptr_1d_push(b->backedges, edge);
    JL_UNLOCK(&b->globalref->mod->lock);
}

// Called for all GlobalRefs found in lowered code. Adds backedges for cross-module
// GlobalRefs.
JL_DLLEXPORT int jl_maybe_add_binding_backedge(jl_binding_t *b, jl_value_t *edge, jl_method_t *for_method)
{
    if (!edge)
        return 0;
    jl_module_t *defining_module = for_method->module;
    // N.B.: The logic for evaluating whether a backedge is required must
    // match the invalidation logic.
    if (b->globalref->mod == defining_module) {
        // No backedge required - invalidation will forward scan
        jl_atomic_fetch_or(&b->flags, BINDING_FLAG_ANY_IMPLICIT_EDGES);
        if (!(jl_atomic_fetch_or(&for_method->did_scan_source, 0x2) & 0x2))
            jl_add_scanned_method(for_method->module, for_method);
        return 1;
    }
    jl_add_binding_backedge(b, (jl_value_t*)edge);
    return 0;
}

JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked(jl_binding_t *b,
    jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, enum jl_partition_kind kind, size_t new_world)
{
    // Copy flags from old bpart
    return jl_replace_binding_locked2(b, old_bpart, restriction_val, (size_t)kind | (size_t)(old_bpart->kind & PARTITION_MASK_FLAG),
        new_world);
}

extern JL_DLLEXPORT _Atomic(size_t) jl_first_image_replacement_world;
JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding_locked2(jl_binding_t *b,
    jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, size_t kind, size_t new_world)
{
    check_safe_newbinding(b->globalref->mod, b->globalref->name);

    // Check if this is a replacing a binding in the system or a package image.
    // Until the first such replacement, we can fast-path validation.
    // For these purposes, we consider the `Main` module to be a non-sysimg module.
    // This is legal, because we special case the `Main` in check_safe_import_from.
    if (jl_object_in_image((jl_value_t*)b) && b->globalref->mod != jl_main_module && jl_atomic_load_relaxed(&jl_first_image_replacement_world) == ~(size_t)0) {
        // During incremental compilation replacement of image bindings is forbidden;
        // We use this to avoid inserting backedges while loading pkgimages.
        // `check_safe_newbinding` checks an equivalent condition on `b->globalref->mod`,
        // but doesn't quite query `jl_object_in_image`, so assert here to be extra sure.
        assert(!(jl_options.incremental && jl_generating_output()));
        jl_atomic_store_relaxed(&jl_first_image_replacement_world, new_world);
    }

    assert(jl_atomic_load_relaxed(&b->partitions) == old_bpart);
    jl_binding_partition_t *new_bpart = new_binding_partition(b);
    JL_GC_PUSH1(&new_bpart);
    jl_atomic_store_relaxed(&new_bpart->min_world, new_world);
    if ((kind & PARTITION_MASK_KIND) == PARTITION_FAKE_KIND_IMPLICIT_RECOMPUTE) {
        assert(!restriction_val);
        struct implicit_search_resolution resolution = jl_resolve_implicit_import(b, NULL, new_world, 0);
        new_bpart->kind = resolution.ultimate_kind | (kind & PARTITION_MASK_FLAG);
        // If the resolution indicates this should be reexported, add the implicit export flag
        if (resolution.should_be_reexported) {
            new_bpart->kind |= PARTITION_FLAG_IMPLICITLY_EXPORTED;
        }
        jl_gc_wb_fresh(new_bpart, resolution.binding_or_const);
        new_bpart->restriction = resolution.binding_or_const;
        assert(resolution.min_world <= new_world && resolution.max_world == ~(size_t)0);
        if (new_bpart->kind == old_bpart->kind && new_bpart->restriction == old_bpart->restriction) {
            JL_GC_POP();
            return old_bpart;
        }
    }
    else {
        new_bpart->kind = kind;
        jl_gc_wb_fresh(new_bpart, restriction_val);
        new_bpart->restriction = restriction_val;
    }
    jl_atomic_store_release(&old_bpart->max_world, new_world-1);
    jl_gc_wb_fresh(new_bpart, old_bpart);
    jl_atomic_store_relaxed(&new_bpart->next, old_bpart);

    if ((jl_bpart_is_exported(old_bpart->kind) || jl_bpart_is_exported(kind)) && jl_require_world != ~(size_t)0) {
        jl_atomic_store_release(&b->globalref->mod->export_set_changed_since_require_world, 1);
    }

    jl_gc_write_atomic(b, b->partitions, jl_binding_partition_t, new_bpart, release);
    JL_GC_POP();

    if (jl_typeinf_world != 1) {
        jl_task_t *ct = jl_current_task;
        size_t last_world = ct->world_age;
        ct->world_age = jl_typeinf_world;
        jl_invalidate_binding_refs(b->globalref, old_bpart, new_bpart, new_world-1);
        ct->world_age = last_world;
    }

    return new_bpart;
}

JL_DLLEXPORT jl_binding_partition_t *jl_replace_binding(jl_binding_t *b,
        jl_binding_partition_t *old_bpart, jl_value_t *restriction_val, enum jl_partition_kind kind) JL_CANSAFEPOINT {

    JL_LOCK(&world_counter_lock);

    if (jl_atomic_load_relaxed(&b->partitions) != old_bpart) {
        JL_UNLOCK(&world_counter_lock);
        return NULL;
    }

    size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1;
    jl_binding_partition_t *bpart = jl_replace_binding_locked(b, old_bpart, restriction_val, kind, new_world);
    if (bpart && jl_atomic_load_relaxed(&bpart->min_world) == new_world)
        jl_atomic_store_release(&jl_world_counter, new_world);

    JL_UNLOCK(&world_counter_lock);
    return bpart;
}

JL_DLLEXPORT int jl_globalref_is_const(jl_globalref_t *gr)
{
    jl_binding_t *b = gr->binding;
    if (!b)
        b = jl_get_module_binding(gr->mod, gr->name, 1);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age);
    return jl_bkind_is_real_constant(jl_binding_kind(bpart));
}

JL_DLLEXPORT void jl_disable_binding(jl_globalref_t *gr) JL_CANSAFEPOINT
{
    jl_binding_t *b = gr->binding;
    if (!b)
        b = jl_get_module_binding(gr->mod, gr->name, 1);

    for (;;) {
        jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_atomic_load_acquire(&jl_world_counter));

        if (jl_binding_kind(bpart) == PARTITION_KIND_GUARD) {
            // Already guard
            return;
        }

        if (!jl_replace_binding(b, bpart, NULL, PARTITION_KIND_GUARD))
            continue;

        return;
    }
}

JL_DLLEXPORT int jl_is_const(jl_module_t *m, jl_sym_t *var)
{
    jl_binding_t *b = jl_get_binding(m, var);
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    jl_walk_binding_inplace(&b, &bpart, jl_current_task->world_age);
    return b && jl_bkind_is_real_constant(jl_binding_kind(bpart));
}

// set the deprecated flag for a binding:
//   0=not deprecated, 1=renamed, 2=moved to another package
static const size_t DEPWARN_FLAGS = PARTITION_FLAG_DEPRECATED | PARTITION_FLAG_DEPWARN;
JL_DLLEXPORT void jl_deprecate_binding(jl_module_t *m, jl_sym_t *var, int flag) JL_CANSAFEPOINT
{
    jl_binding_t *b = jl_get_binding(m, var);
    size_t new_flags = flag == 1 ? PARTITION_FLAG_DEPRECATED | PARTITION_FLAG_DEPWARN :
                       flag == 2 ? PARTITION_FLAG_DEPRECATED :
                                   0;
    JL_LOCK(&world_counter_lock);
    size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1;
    jl_binding_partition_t *old_bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    if ((old_bpart->kind & DEPWARN_FLAGS) == new_flags) {
        JL_UNLOCK(&world_counter_lock);
        return;
    }
    jl_replace_binding_locked2(b, old_bpart, old_bpart->restriction,
        (old_bpart->kind & ~DEPWARN_FLAGS) | new_flags, new_world);
    jl_atomic_store_release(&jl_world_counter, new_world);
    JL_UNLOCK(&world_counter_lock);
}

// Select the declared visibility of a binding:
//   0=none (neither), 1=public, 2=export
// The exported flag is world-versioned (it lives in the binding partition and
// gates implicit resolution through `using`), so clearing it creates a new
// partition and invalidates dependent code. The public flag is world-agnostic
// (it lives on the binding). Unlike the `export`/`public` keyword path
// (jl_module_public_), this both raises and lowers the state, so it does not
// enforce the public-vs-exported conflict.
JL_DLLEXPORT void jl_module_set_visibility(jl_module_t *m, jl_sym_t *var, int state)
{
    int want_exported = state == 2;
    int want_public = state >= 1;
    jl_binding_t *b = jl_get_module_binding(m, var, 1);
    JL_LOCK(&world_counter_lock);
    size_t new_world = jl_atomic_load_acquire(&jl_world_counter)+1;
    jl_binding_partition_t *old_bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    int was_exported = (old_bpart->kind & PARTITION_FLAG_EXPORTED) != 0;
    if (was_exported != want_exported) {
        size_t new_kind = want_exported ? (old_bpart->kind | PARTITION_FLAG_EXPORTED) :
                                          (old_bpart->kind & ~(size_t)PARTITION_FLAG_EXPORTED);
        jl_replace_binding_locked2(b, old_bpart, old_bpart->restriction, new_kind, new_world);
        jl_atomic_store_release(&jl_world_counter, new_world);
    }
    JL_UNLOCK(&world_counter_lock);
    if (want_public)
        jl_atomic_fetch_or_relaxed(&b->flags, BINDING_FLAG_PUBLICP);
    else
        jl_atomic_fetch_and_relaxed(&b->flags, (uint8_t)~BINDING_FLAG_PUBLICP);
}

static int should_depwarn(jl_binding_t *b, uint8_t flag) JL_CANSAFEPOINT
{
    // We consider bindings deprecated, if:
    //
    // 1. The binding itself is deprecated, or
    // 2. We implicitly import any deprecated binding.
    //
    // However, we do not consider the binding deprecated if the import was an explicit
    // (`using` or `import`). The logic here is that the thing that needs to be adjusted
    // is not the use itself, but rather the `using` or `import` (which already prints
    // an appropriate warning).
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    if (bpart->kind & flag)
        return 1;
    return 0;
}

JL_DLLEXPORT void jl_binding_deprecation_check(jl_binding_t *b) JL_CANSAFEPOINT
{
    if (jl_options.depwarn && should_depwarn(b, PARTITION_FLAG_DEPWARN))
        jl_binding_deprecation_warning(b);
}

JL_DLLEXPORT int jl_is_binding_deprecated(jl_module_t *m, jl_sym_t *var) JL_CANSAFEPOINT
{
    jl_binding_t *b = jl_get_module_binding(m, var, 0);
    if (!b)
        return 0;
    return should_depwarn(b, PARTITION_FLAG_DEPRECATED);
}

void jl_binding_deprecation_warning(jl_binding_t *b)
{
    if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR)
        jl_printf(JL_STDERR, "WARNING: ");
    jl_printf(JL_STDERR, "Use of ");

    jl_printf(JL_STDERR, "%s.%s is deprecated",
                jl_symbol_name(b->globalref->mod->name), jl_symbol_name(b->globalref->name));
    jl_binding_dep_message(b);

    if (jl_options.depwarn != JL_OPTIONS_DEPWARN_ERROR) {
        if (jl_atomic_load_relaxed(&jl_lineno) != 0) {
            jl_printf(JL_STDERR, "  likely near %s:%d\n", jl_atomic_load_relaxed(&jl_filename), jl_atomic_load_relaxed(&jl_lineno));
        }
    }

    if (jl_options.depwarn == JL_OPTIONS_DEPWARN_ERROR) {
        jl_errorf("use of deprecated variable: %s.%s",
                    jl_symbol_name(b->globalref->mod->name),
                    jl_symbol_name(b->globalref->name));
    }
}

// For a generally writable binding (checked using jl_check_binding_currently_writable in this world age), check whether
// we can actually write the value `rhs` to it.
jl_value_t *jl_check_binding_assign_value(jl_binding_t *b JL_PROPAGATES_ROOT, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs, const char *msg)
{
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    assert(kind == PARTITION_KIND_DECLARED || kind == PARTITION_KIND_GLOBAL);
    jl_value_t *old_ty = kind == PARTITION_KIND_DECLARED ? (jl_value_t*)jl_any_type : bpart->restriction;
    JL_GC_PROMISE_ROOTED(old_ty);
    if (old_ty != (jl_value_t*)jl_any_type && jl_typeof(rhs) != old_ty && !jl_isa(rhs, old_ty)) {
        jl_type_error_global(msg, mod, var, old_ty, rhs);
    }
    return old_ty;
}

JL_DLLEXPORT void jl_checked_assignment(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs)
{
    if (jl_check_binding_assign_value(b, mod, var, rhs, "setglobal!") != NULL) {
        jl_gc_write_atomic(b, b->value, jl_value_t, rhs, release);
    }
}

JL_DLLEXPORT jl_value_t *jl_checked_swap(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs)
{
    jl_check_binding_assign_value(b, mod, var, rhs, "swapglobal!");
    jl_gc_wb(b, rhs);
    jl_value_t *old = jl_atomic_exchange(&b->value, rhs);
    if (__unlikely(old == NULL))
        jl_undefined_var_error(var, (jl_value_t*)mod);
    return old;
}

JL_DLLEXPORT jl_value_t *jl_checked_replace(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *expected, jl_value_t *rhs)
{
    jl_value_t *ty = jl_check_binding_assign_value(b, mod, var, rhs, "replaceglobal!");
    return replace_value(ty, &b->value, (jl_value_t*)b, expected, rhs, 1, mod, var);
}

JL_DLLEXPORT jl_value_t *jl_checked_modify(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *op, jl_value_t *rhs)
{
    jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
    enum jl_partition_kind kind = jl_binding_kind(bpart);
    assert(!jl_bkind_is_some_guard(kind) && !jl_bkind_is_some_import(kind));
    if (jl_bkind_is_some_constant(kind))
        jl_errorf("invalid assignment to constant %s.%s",
                  jl_symbol_name(mod->name), jl_symbol_name(var));
    jl_value_t *ty = bpart->restriction;
    JL_GC_PROMISE_ROOTED(ty);
    return modify_value(ty, &b->value, (jl_value_t*)b, op, rhs, 1, b, mod, var);
}

JL_DLLEXPORT jl_value_t *jl_checked_assignonce(jl_binding_t *b, jl_module_t *mod, jl_sym_t *var, jl_value_t *rhs)
{
    jl_check_binding_assign_value(b, mod, var, rhs, "setglobalonce!");
    jl_value_t *old = NULL;
    jl_gc_wb(b, rhs);
    jl_atomic_cmpswap(&b->value, &old, rhs);
    return old;
}

JL_DLLEXPORT jl_value_t *jl_module_usings(jl_module_t *m) JL_CANSAFEPOINT
{
    JL_LOCK(&m->lock);
    int j = module_usings_length(m);
    jl_array_t *a = jl_alloc_array_1d(jl_array_any_type, j);
    JL_GC_PUSH1(&a);
    for (int i = 0; j > 0; i++) {
        j--;
        jl_module_t *imp = module_usings_getmod(m, i);
        jl_array_ptr_set(a, j, (jl_value_t*)imp);
    }
    JL_UNLOCK(&m->lock); // may gc
    JL_GC_POP();
    return (jl_value_t*)a;
}

static void _append_symbol_to_bindings_array(jl_array_t* a, jl_sym_t *name) JL_CANSAFEPOINT {
    jl_array_grow_end(a, 1);
    //XXX: change to jl_arrayset if array storage allocation for Array{Symbols,1} changes:
    jl_array_ptr_set(a, jl_array_dim0(a)-1, (jl_value_t*)name);
}

static void _materialize_reexported_bindings(jl_module_t *m, size_t world, jl_array_t *visited_modules) JL_CANSAFEPOINT
{
    size_t len = jl_array_len(visited_modules);
    for (size_t i = 0; i < len; i++) {
        if (jl_array_ptr_ref(visited_modules, i) == (jl_value_t*)m)
            return;
    }
    jl_array_ptr_1d_push(visited_modules, (jl_value_t*)m);

    JL_LOCK(&m->lock);
    size_t usings_len = module_usings_length(m);
    JL_UNLOCK(&m->lock);

    for (size_t i = 0; i < usings_len; i++) {
        JL_LOCK(&m->lock);
        struct _jl_module_using data = *module_usings_getidx(m, i);
        JL_UNLOCK(&m->lock);

        if (data.min_world > world || data.max_world < world)
            continue;

        if (data.flags & JL_MODULE_USING_REEXPORT) {
            jl_module_t *from = data.mod;
            JL_GC_PROMISE_ROOTED(from);

            _materialize_reexported_bindings(from, world, visited_modules);

            jl_svec_t *table = jl_atomic_load_relaxed(&from->bindings);
            for (size_t j = 0; j < jl_svec_len(table); j++) {
                jl_binding_t *b = (jl_binding_t*)jl_svecref(table, j);
                if ((void*)b == jl_nothing)
                    break;

                jl_binding_partition_t *frombpart = jl_get_binding_partition(b, world);
                if (jl_bpart_is_exported(frombpart->kind)) {
                    jl_sym_t *var = b->globalref->name;
                    jl_binding_t *tob = jl_get_module_binding(m, var, 1);
                    jl_get_binding_partition(tob, world);  // Force implicit resolution
                }
                table = jl_atomic_load_relaxed(&from->bindings);
            }
        }
    }
}

static void append_module_names(jl_array_t* a, jl_module_t *m, int all, int imported, int usings, size_t world) JL_CANSAFEPOINT
{
    // Materialize reexported bindings first
    jl_array_t *visited_modules = jl_alloc_vec_any(0);
    JL_GC_PUSH1(&visited_modules);
    _materialize_reexported_bindings(m, world, visited_modules);
    JL_GC_POP();

    jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings);
    for (size_t i = 0; i < jl_svec_len(table); i++) {
        jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i);
        if ((void*)b == jl_nothing)
            break;
        jl_sym_t *asname = b->globalref->name;
        int hidden = jl_symbol_name(asname)[0]=='#';
        int main_public = (m == jl_main_module && !(asname == jl_eval_sym || asname == jl_include_sym));
        // Use non-materializing read to avoid creating binding partitions as a
        // side effect. Only fall back to materializing for `usings`, since
        // using'd bindings may not have partitions yet. Non-using'd bindings
        // (imported, global, const, etc.) will already have been materialized
        // by their definition.
        struct implicit_search_gap gap;
        jl_binding_partition_t *bpart = jl_get_binding_partition_if_present(b, world, &gap);
        if (!bpart && usings)
            bpart = jl_get_binding_partition(b, world);
        if (!bpart) {
            // No partition — check public flag on the binding and exported
            // flag from adjacent partitions.
            if (!(jl_atomic_load_relaxed(&b->flags) & BINDING_FLAG_PUBLICP) &&
                !jl_bpart_is_exported(gap.inherited_flags))
                continue;
            if (!all && ((gap.inherited_flags & PARTITION_FLAG_DEPRECATED) || hidden))
                continue;
        } else {
            enum jl_partition_kind kind = jl_binding_kind(bpart);
            if (!(jl_atomic_load_relaxed(&b->flags) & BINDING_FLAG_PUBLICP) &&
                !jl_bpart_is_exported(bpart->kind) &&
                !(imported && (kind == PARTITION_KIND_CONST_IMPORT || kind == PARTITION_KIND_IMPORTED)) &&
                !(usings && kind == PARTITION_KIND_EXPLICIT) &&
                !((kind == PARTITION_KIND_GLOBAL || kind == PARTITION_KIND_CONST || kind == PARTITION_KIND_DECLARED) && (all || main_public)))
                continue;
            if (!all && ((bpart->kind & PARTITION_FLAG_DEPRECATED) || hidden))
                continue;
        }
        _append_symbol_to_bindings_array(a, asname);
    }
}

static void append_exported_names(jl_array_t* a, jl_module_t *m, int all, size_t world) JL_CANSAFEPOINT
{
    // First, materialize all reexported bindings
    jl_array_t *visited_modules = jl_alloc_vec_any(0);
    JL_GC_PUSH1(&visited_modules);
    _materialize_reexported_bindings(m, world, visited_modules);
    JL_GC_POP();

    // Now collect all exported bindings
    jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings);
    for (size_t i = 0; i < jl_svec_len(table); i++) {
        jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i);
        if ((void*)b == jl_nothing)
            break;
        struct implicit_search_gap gap;
        jl_binding_partition_t *bpart = jl_get_binding_partition_if_present(b, world, &gap);
        if (!bpart) {
            if (jl_bpart_is_exported(gap.inherited_flags) && (all || !(gap.inherited_flags & PARTITION_FLAG_DEPRECATED)))
                _append_symbol_to_bindings_array(a, b->globalref->name);
            continue;
        }
        if (jl_bpart_is_exported(bpart->kind) && (all || !(bpart->kind & PARTITION_FLAG_DEPRECATED)))
            _append_symbol_to_bindings_array(a, b->globalref->name);
    }
}

JL_DLLEXPORT jl_value_t *jl_module_names(jl_module_t *m, int all, int imported, int usings, size_t world) JL_CANSAFEPOINT
{
    jl_array_t *a = jl_alloc_array_1d(jl_array_symbol_type, 0);
    JL_GC_PUSH1(&a);
    append_module_names(a, m, all, imported, usings, world);
    if (usings) {
        // If `usings` is specified, traverse the list of `using`-ed modules and incorporate
        // the names exported by those modules into the list.
        for (int i = module_usings_length(m)-1; i >= 0; i--)
            append_exported_names(a, module_usings_getmod(m, i), all, world);
    }
    JL_GC_POP();
    return (jl_value_t*)a;
}

JL_DLLEXPORT jl_sym_t *jl_module_name(jl_module_t *m) { return m->name; }
JL_DLLEXPORT jl_module_t *jl_module_parent(jl_module_t *m) { return m->parent; }
jl_module_t *jl_module_root(jl_module_t *m)
{
    while (1) {
        if (m->parent == NULL || m->parent == m)
            return m;
        m = m->parent;
    }
}

JL_DLLEXPORT jl_sym_t *jl_module_getloc(jl_module_t *m, int32_t *line)
{
    if (line) {
        *line = m->line;
    }
    return m->file;
}

JL_DLLEXPORT jl_uuid_t jl_module_build_id(jl_module_t *m) { return m->build_id; }
JL_DLLEXPORT jl_uuid_t jl_module_uuid(jl_module_t* m) { return m->uuid; }

// TODO: make this part of the module constructor and read-only?
JL_DLLEXPORT void jl_set_module_uuid(jl_module_t *m, jl_uuid_t uuid) { m->uuid = uuid; }
JL_DLLEXPORT void jl_set_module_parent(jl_module_t *m, jl_module_t *parent) { jl_gc_write(m, m->parent, jl_module_t, parent); }

int jl_is_submodule(jl_module_t *child, jl_module_t *parent) JL_NOTSAFEPOINT
{
    while (1) {
        if (parent == child)
            return 1;
        if (child == NULL || child == child->parent)
            return 0;
        child = child->parent;
    }
}

// Remove implicitly imported identifiers, effectively resetting all the binding
// resolution decisions for a module. This is dangerous, and should only be
// done for modules that are essentially empty anyway. The only use case for this
// is to leave `Main` as empty as possible in the default system image.
JL_DLLEXPORT void jl_clear_implicit_imports(jl_module_t *m) JL_CANSAFEPOINT
{
    JL_LOCK(&m->lock);
    jl_svec_t *table = jl_atomic_load_relaxed(&m->bindings);
    for (size_t i = 0; i < jl_svec_len(table); i++) {
        jl_binding_t *b = (jl_binding_t*)jl_svecref(table, i);
        if ((void*)b == jl_nothing)
            break;
        jl_binding_partition_t *bpart = jl_get_binding_partition(b, jl_current_task->world_age);
        if (jl_bkind_is_some_implicit(jl_binding_kind(bpart))) {
            jl_gc_write_atomic(b, b->partitions, jl_binding_partition_t, NULL, relaxed);
        }
    }
    JL_UNLOCK(&m->lock);
}

JL_DLLEXPORT void jl_add_to_module_init_list(jl_value_t *mod) JL_CANSAFEPOINT
{
    if (jl_module_init_order == NULL)
        jl_module_init_order = jl_alloc_vec_any(0);
    jl_array_ptr_1d_push(jl_module_init_order, mod);
}

JL_DLLEXPORT jl_svec_t *jl_module_get_bindings(jl_module_t *m)
{
    return jl_atomic_load_relaxed(&m->bindings);
}

JL_DLLEXPORT void jl_init_restored_module(jl_value_t *mod) JL_CANSAFEPOINT
{
    if (!jl_generating_output() || jl_options.incremental) {
        jl_module_run_initializer((jl_module_t*)mod);
    }
    else {
        jl_add_to_module_init_list(mod);
    }
}

#ifdef __cplusplus
}
#endif
