Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,7 @@
- Layouts for targets whose data layout aligns a field differently than Julia does (e.g.
`Int128` on SPIR-V, Metal and BPF): a packed struct with the fields at Julia's offsets
and explicit padding.
- Globals (not constants) are read and written through their binding, as native code
does: an unordered load of the binding's value (with `UndefVarError` from the runtime)
and, for globals of type `Any`, a release store and the binding's write barrier; also
for `getglobal` and `setglobal!` with constant arguments.
88 changes: 83 additions & 5 deletions src/builtins.jl
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,71 @@ emit_new_dynamic!(fc::FnCtx, T::CGVal, args, @nospecialize(typ)) =
emit_new_runtime(fc, T, CGVal[args...], typ)

# a read of a global that is not constant, by the runtime
nonconst_global(fc::FnCtx, ref::GlobalRef) =
emit_dynamic_call(fc, const_value(fc, Core.getglobal),
CGVal[const_value(fc, ref.mod), const_value(fc, ref.name)], Any)
# A global (not a constant) is read and written through its binding's `value`, as native
# code does (`emit_globalop`): with an unordered load, or a release store and the binding's
# write barrier. Bindings of other kinds (imported, not yet resolved) go through the
# runtime, as do assignments of globals with a declared type, which convert the value.
const BINDING_VALUE_OFFSET = fieldoffset(Core.Binding, Base.fieldindex(Core.Binding, :value))

# the binding of the global `ref` in the world of the code and its declared type, if it is
# a global
function direct_binding(fc::FnCtx, ref::GlobalRef)
p = Base.lookup_binding_partition(codegen_world(fc), ref)
Base.binding_kind(p) == Base.PARTITION_KIND_GLOBAL || return nothing
return convert(Core.Binding, ref), p.restriction
end

# the address of the value of the binding `bnd`: an untracked pointer into it, as native
# code addresses it (through the global that holds the binding's address)
function binding_value_address(fc::FnCtx, bnd::Core.Binding)
t = literal_pointer(fc, bnd)
raw = t isa LLVM.AddrSpaceCastInst ? LLVM.operands(t)[1] : LLVM.addrspacecast!(fc.builder, t, T_ptr())
return LLVM.inbounds_gep!(fc.builder, LLVM.Int8Type(), raw, [iptr(fc, BINDING_VALUE_OFFSET)])
end

function nonconst_global(fc::FnCtx, ref::GlobalRef)
d = direct_binding(fc, ref)
if d === nothing
return emit_dynamic_call(fc, const_value(fc, Core.getglobal),
CGVal[const_value(fc, ref.mod), const_value(fc, ref.name)], Any)
end
bnd, T = d
b = fc.builder
v = LLVM.load!(b, T_tracked(), binding_value_address(fc, bnd))
LLVM.ordering!(v, LLVM.API.LLVMAtomicOrderingUnordered)
LLVM.alignment!(v, config(fc).target.pointer_size)
tbaa!(fc, v, :binding)
# undefined: an UndefVarError, from the runtime as native code's
f = current_function(fc)
err = LLVM.BasicBlock(f, "err")
ok = LLVM.BasicBlock(f, "ok")
LLVM.br!(b, nonnull(fc, v), ok, err)
LLVM.position!(b, LLVM.at_end(err))
rooted(x) = LLVM.addrspacecast!(b, literal_pointer(fc, x), LLVM.PointerType(AddrSpace.CalleeRooted))
uve = declare_runtime(fc.cgm.mod, dialect(fc), :jl_undefined_var_error)
LLVM.call!(b, LLVM.function_type(uve), uve, LLVM.Value[rooted(ref.name), rooted(ref.mod)])
LLVM.unreachable!(b)
LLVM.position!(b, LLVM.at_end(ok))
return boxed(T isa Type ? T : Any, v)
end

# `global ref = v` of a global of type `Any` through its binding, as native code does;
# `false` for other bindings
function emit_binding_assignment!(fc::FnCtx, ref::GlobalRef, v::CGVal)
d = direct_binding(fc, ref)
(d === nothing || d[2] !== Any) && return false
bnd, _ = d
b = fc.builder
p = emit_box(fc, v)
addr = binding_value_address(fc, bnd)
st = LLVM.store!(b, p, addr)
LLVM.ordering!(st, LLVM.API.LLVMAtomicOrderingRelease)
LLVM.alignment!(st, config(fc).target.pointer_size)
tbaa!(fc, st, :binding)
write_barrier!(b, fc.cgm.mod, dialect(fc), literal_pointer(fc, bnd),
LLVM.addrspacecast!(b, addr, T_derived()), p)
return true
end

# the field index of the constant field `f` of `T`
function field_index(@nospecialize(T), f::CGVal)
Expand Down Expand Up @@ -482,8 +544,23 @@ function emit_getglobal(fc::FnCtx, args::Vector{CGVal}, @nospecialize(rt))
m, s = constant_value(args[1]), constant_value(args[2])
(m isa Module && s isa Symbol) || return nothing
c = const_global(fc.cgm.world, m, s)
c === nothing && return nothing
return const_value(fc, something(c))
c === nothing || return const_value(fc, something(c))
# a global, through its binding
ref = GlobalRef(m, s)
direct_binding(fc, ref) === nothing && return nothing
return convert_repr(fc, nonconst_global(fc, ref), rt)
end

# `setglobal!(m, s, x, [order])` of a global of type `Any` with the default (release) order,
# through its binding
function emit_setglobal!(fc::FnCtx, args::Vector{CGVal}, @nospecialize(rt))
(length(args) in (3, 4) && isconstant(args[1]) && isconstant(args[2])) || return nothing
length(args) == 4 && !(isconstant(args[4]) && constant_value(args[4]) === :release) &&
return nothing
m, s = constant_value(args[1]), constant_value(args[2])
(m isa Module && s isa Symbol) || return nothing
emit_binding_assignment!(fc, GlobalRef(m, s), args[3]) || return nothing
return convert_repr(fc, args[3], rt)
end

# `isdefined(x, field)` with a constant field of a concrete type
Expand Down Expand Up @@ -519,6 +596,7 @@ const BUILTINS = IdDict{Any,Function}(
Core.throw => emit_throw,
Core.sizeof => emit_sizeof,
Core.getglobal => emit_getglobal,
Core.setglobal! => emit_setglobal!,
)

"""
Expand Down
4 changes: 3 additions & 1 deletion src/exceptions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,16 @@ function emit_exception_head!(fc::FnCtx, idx::Int, ex::Expr, @nospecialize(typ))
end
return ghost(Nothing, nothing)
elseif head === :(=)
# the assignment of a global: `setglobal!` with release order, as native code
# the assignment of a global: through its binding, or `setglobal!` with release
# order, as native code
lhs = ex.args[1]
if lhs isa Core.BindingPartition
lhs = (ccall(:jl_binding_partition_owner, Any, (Any,), lhs)::Core.Binding).globalref
end
lhs isa GlobalRef || unsupported("an assignment to ", typeof(lhs))
rhs = operand(fc, ex.args[2])
rhs.kind === Bottom && return rhs
emit_binding_assignment!(fc, lhs, rhs) && return rhs
emit_dynamic_call_raw(fc, const_value(fc, Core.setglobal!),
CGVal[const_value(fc, lhs.mod), const_value(fc, lhs.name), rhs,
const_value(fc, :release)])
Expand Down
4 changes: 4 additions & 0 deletions src/runtime.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ function declare_runtime(mod::LLVM.Module, dialect::JuliaIR.Dialect, name::Symbo
push!(f.function_attributes, LLVM.EnumAttribute("willreturn"))
elseif name === :jl_current_exception
f = LLVM.Function(mod, sym, LLVM.FunctionType(T_tracked(), [T_ptr()]))
elseif name === :jl_undefined_var_error
# `void (jl_sym_t *var, jl_value_t *scope)`
f = LLVM.Function(mod, sym, LLVM.FunctionType(LLVM.VoidType(), [T_rooted, T_rooted]))
push!(f.function_attributes, LLVM.EnumAttribute("noreturn"))
elseif name === :jl_throw
f = LLVM.Function(mod, sym, LLVM.FunctionType(LLVM.VoidType(), [T_rooted]))
push!(f.function_attributes, LLVM.EnumAttribute("noreturn"))
Expand Down
25 changes: 25 additions & 0 deletions test/exceptions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@ function e_undef(c)
end
e_global_counter = 0
e_assign(x) = (global e_global_counter = x; e_global_counter)
global e_typed_global::Int = 1
e_typed_read() = e_typed_global + 1
e_typed_assign(x) = (global e_typed_global = x; e_typed_global)
e_read_undefined() = e_never_assigned
global e_never_assigned
e_read_imported() = Base.Filesystem.JL_O_RDONLY + 0 # (an imported constant)

@testset "exceptions" begin

Expand Down Expand Up @@ -66,6 +72,25 @@ end
@cgtest e_undef(false) # UndefVarError
@test cgrun(e_assign, 7) == 7
@test e_global_counter == 7
# globals with a declared type, and declared globals without a value
@cgtest e_typed_read()
@test cgrun(e_typed_assign, 5) == 5
@test e_typed_global == 5
@cgtest e_read_undefined()
end

@testset "globals" begin
# read and written through the binding, as native code does
@test @filecheck begin
@check_label "define"
@check_not "@ijl_apply_generic"
@check "store atomic ptr addrspace(10) %{{[0-9]+}}, ptr %{{[0-9]+}} release"
@check "load atomic ptr addrspace(10), ptr %{{[0-9]+}} unordered"
@check "call void @ijl_undefined_var_error"
test_ir(e_assign, (Int,))
end
# a typed global is assigned through the runtime, which converts the value
@test occursin("@ijl_apply_generic", test_ir(e_typed_assign, (Int,)))
end

@testset "code" begin
Expand Down
Loading