Skip to content

Commit e05165a

Browse files
xal-0vtjnash
andcommitted
Lower const x = ... to new builtin Core.setconst! (#58187)
Adds a new builtin, `Core.setconst!`, with the following signature: ``` setconst!(module::Module, name::Symbol, [x]) ``` The lowered `Expr(:const, :name, value)` form has been removed, saving some duplicated effort in the interpreter and code generation. `Expr(:const, ...)` is now always lowered to the builtin. ``` julia> Meta.@lower const example = 123 :($(Expr(:thunk, CodeInfo( 1 ─ %1 = 123 │ builtin Core.setconst!(Main, :example, %1) │ $(Expr(:latestworld)) └── return %1 )))) ``` The single-argument form of const, having no surface syntax, was previously only available through `eval(Expr(:const, :foo))`: ``` julia> eval(Expr(:const, :example)) julia> GlobalRef(Main, :example).binding Binding Main.example 38582:∞ - undefined const binding 38579:38581 - undefined binding - guard entry ``` It survived lowering by being special-cased in `expand-toplevel-expr--`, resulting in some inconsistencies: ``` julia> eval(:(begin $(Expr(:const, :example)) end)) ERROR: syntax: malformed expression Stacktrace: [1] top-level scope @ none:1 [2] eval(m::Module, e::Any) @ Core ./boot.jl:489 [3] top-level scope @ REPL[1]:1 ``` These are fixed now that const is always lowered. --------- Co-authored-by: Jameson Nash <vtjnash@gmail.com>
1 parent a2a60da commit e05165a

File tree

12 files changed

+104
-108
lines changed

12 files changed

+104
-108
lines changed

Compiler/src/ssair/ir.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ function is_relevant_expr(e::Expr)
584584
:foreigncall, :isdefined, :copyast,
585585
:throw_undef_if_not,
586586
:cfunction, :method, :pop_exception,
587-
:leave, :const,
587+
:leave,
588588
:new_opaque_closure)
589589
end
590590

Compiler/src/validation.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ const VALID_EXPR_HEADS = IdDict{Symbol,UnitRange{Int}}(
99
:(&) => 1:1,
1010
:(=) => 2:2,
1111
:method => 1:4,
12-
:const => 1:2,
1312
:new => 1:typemax(Int),
1413
:splatnew => 2:2,
1514
:the_exception => 0:0,
@@ -145,7 +144,7 @@ function validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo, is_top_le
145144
elseif head === :call || head === :invoke || x.head === :invoke_modify ||
146145
head === :gc_preserve_end || head === :meta ||
147146
head === :inbounds || head === :foreigncall || head === :cfunction ||
148-
head === :const || head === :leave || head === :pop_exception ||
147+
head === :leave || head === :pop_exception ||
149148
head === :method || head === :static_parameter ||
150149
head === :new || head === :splatnew || head === :thunk || head === :loopinfo ||
151150
head === :throw_undef_if_not || head === :code_coverage_effect || head === :inline || head === :noinline

base/docs/basedocs.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2769,6 +2769,41 @@ See also [`global`](@ref), [`setglobal!`](@ref), [`get_binding_type`](@ref Core.
27692769
"""
27702770
Core.declare_global
27712771

2772+
"""
2773+
declare_const(module::Module, name::Symbol, [x])
2774+
2775+
Create or replace the constant `name` in `module` with the new value `x`. When
2776+
replacing, `x` does not need to have the same type as the original constant.
2777+
2778+
When `x` is not given, `name` becomes an undefined constant; it cannot be read
2779+
or written to, but can be redefined.
2780+
2781+
Unlike the syntax `const`, calling this function does not insert `Core.@latestworld` to update the world age of the current frame:
2782+
```
2783+
julia> begin
2784+
const x = 1
2785+
println(x)
2786+
const x = 2
2787+
println(x)
2788+
Core.declare_const(Main, :x, 3)
2789+
println(x)
2790+
Core.@latestworld
2791+
println(x)
2792+
end
2793+
1
2794+
2
2795+
2
2796+
3
2797+
```
2798+
2799+
!!! compat "Julia 1.12"
2800+
This function requires Julia 1.12 or later. Redefining constants on earlier
2801+
versions of Julia is unpredictable.
2802+
2803+
See also [`const`](@ref).
2804+
"""
2805+
Core.declare_const
2806+
27722807
"""
27732808
_import(to::Module, from::Module, asname::Symbol, [sym::Symbol, imported::Bool])
27742809

doc/src/base/base.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ Core.modifyglobal!
501501
Core.swapglobal!
502502
Core.setglobalonce!
503503
Core.replaceglobal!
504+
Core.declare_const
504505
```
505506

506507
## Documentation

src/builtin_proto.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ extern "C" {
6262
XX(opaque_closure_call,"opaque_closure_call") \
6363
XX(replacefield,"replacefield!") \
6464
XX(replaceglobal,"replaceglobal!") \
65+
XX(declare_const,"declare_const") \
6566
XX(setfield,"setfield!") \
6667
XX(setfieldonce,"setfieldonce!") \
6768
XX(setglobal,"setglobal!") \

src/builtins.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,18 @@ JL_CALLABLE(jl_f_declare_global)
15351535
return jl_nothing;
15361536
}
15371537

1538+
JL_CALLABLE(jl_f_declare_const)
1539+
{
1540+
JL_NARGS(declare_const, 2, 3);
1541+
JL_TYPECHK(declare_const, module, args[0]);
1542+
if (nargs == 3)
1543+
JL_TYPECHK(declare_const, symbol, args[1]);
1544+
jl_binding_t *b = jl_get_module_binding((jl_module_t *)args[0], (jl_sym_t *)args[1], 1);
1545+
jl_value_t *val = nargs == 3 ? args[2] : NULL;
1546+
jl_declare_constant_val(b, (jl_module_t *)args[0], (jl_sym_t *)args[1], val);
1547+
return nargs > 2 ? args[2] : jl_nothing;
1548+
}
1549+
15381550
// import, using --------------------------------------------------------------
15391551

15401552
// Import binding `from.sym` as `asname` into `to`:

src/codegen.cpp

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -881,15 +881,6 @@ static const auto jlcheckassignonce_func = new JuliaFunction<>{
881881
{T_pjlvalue, T_pjlvalue, T_pjlvalue, PointerType::get(JuliaType::get_jlvalue_ty(C), AddressSpace::CalleeRooted)}, false); },
882882
nullptr,
883883
};
884-
static const auto jldeclareconstval_func = new JuliaFunction<>{
885-
XSTR(jl_declare_constant_val),
886-
[](LLVMContext &C) {
887-
auto T_pjlvalue = JuliaType::get_pjlvalue_ty(C);
888-
auto T_prjlvalue = JuliaType::get_prjlvalue_ty(C);
889-
return FunctionType::get(getVoidTy(C),
890-
{T_pjlvalue, T_pjlvalue, T_pjlvalue, T_prjlvalue}, false); },
891-
nullptr,
892-
};
893884
static const auto jldeclareglobal_func = new JuliaFunction<>{
894885
XSTR(jl_declare_global),
895886
[](LLVMContext &C) {
@@ -6469,26 +6460,6 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaidx_
64696460
jl_method_type);
64706461
return meth;
64716462
}
6472-
else if (head == jl_const_sym) {
6473-
assert(nargs <= 2);
6474-
jl_sym_t *sym = (jl_sym_t*)args[0];
6475-
jl_module_t *mod = ctx.module;
6476-
if (jl_is_globalref(sym)) {
6477-
mod = jl_globalref_mod(sym);
6478-
sym = jl_globalref_name(sym);
6479-
}
6480-
if (jl_is_symbol(sym)) {
6481-
jl_binding_t *bnd = jl_get_module_binding(mod, sym, 1);
6482-
if (nargs == 2) {
6483-
jl_cgval_t rhs = emit_expr(ctx, args[1]);
6484-
ctx.builder.CreateCall(prepare_call(jldeclareconstval_func),
6485-
{ julia_binding_gv(ctx, bnd), literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), boxed(ctx, rhs) });
6486-
} else {
6487-
ctx.builder.CreateCall(prepare_call(jldeclareconstval_func),
6488-
{ julia_binding_gv(ctx, bnd), literal_pointer_val(ctx, (jl_value_t*)mod), literal_pointer_val(ctx, (jl_value_t*)sym), ConstantPointerNull::get(cast<PointerType>(ctx.types().T_prjlvalue)) });
6489-
}
6490-
}
6491-
}
64926463
else if (head == jl_new_sym) {
64936464
bool is_promotable = false;
64946465
if (ssaidx_0based >= 0) {

src/interpreter.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -634,12 +634,6 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip,
634634
jl_value_t *res = jl_toplevel_eval(s->module, stmt);
635635
s->locals[jl_source_nslots(s->src) + s->ip] = res;
636636
}
637-
else if (head == jl_const_sym) {
638-
jl_value_t *val = jl_expr_nargs(stmt) == 1 ? NULL : eval_value(jl_exprarg(stmt, 1), s);
639-
s->locals[jl_source_nslots(s->src) + s->ip] = val; // temporarily root
640-
jl_eval_const_decl(s->module, jl_exprarg(stmt, 0), val);
641-
s->locals[jl_source_nslots(s->src) + s->ip] = jl_nothing;
642-
}
643637
else if (head == jl_latestworld_sym) {
644638
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
645639
}

src/jlfrontend.scm

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,7 @@
139139
(define (toplevel-only-expr? e)
140140
(and (pair? e)
141141
(or (memq (car e) '(toplevel line module export public
142-
error incomplete))
143-
(and (memq (car e) '(const)) (every symbol? (cdr e))))))
142+
error incomplete)))))
144143

145144
(define *in-lowering* #f)
146145

src/julia-syntax.scm

Lines changed: 49 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1460,16 +1460,22 @@
14601460
(if (length= e 3)
14611461
`(const ,(cadr e) ,(expand-forms (caddr e)))
14621462
(let ((arg (cadr e)))
1463-
(case (car arg)
1464-
((global) (let ((asgn (cadr arg)))
1465-
(check-assignment asgn)
1466-
`(block
1467-
,.(map (lambda (v) `(global ,v))
1468-
(lhs-bound-names (cadr asgn)))
1469-
,(expand-assignment asgn #t))))
1470-
((=) (check-assignment arg)
1471-
(expand-assignment arg #t))
1472-
(else (error "expected assignment after \"const\""))))))
1463+
(cond
1464+
((symbol? arg)
1465+
;; Undefined constant: Expr(:const, :a) (not available in surface syntax)
1466+
`(block ,e (latestworld)))
1467+
((eq? (car arg) 'global)
1468+
(let ((asgn (cadr arg)))
1469+
(check-assignment asgn)
1470+
`(block
1471+
,.(map (lambda (v) `(global ,v))
1472+
(lhs-bound-names (cadr asgn)))
1473+
,(expand-assignment asgn #t))))
1474+
((eq? (car arg) '=)
1475+
(check-assignment arg)
1476+
(expand-assignment arg #t))
1477+
(else
1478+
(error "expected assignment after \"const\""))))))
14731479

14741480
(define (expand-atomic-decl e)
14751481
(error "unimplemented or unsupported atomic declaration"))
@@ -3103,7 +3109,8 @@
31033109
(set! vars (cons (cadr e) vars)))
31043110
((= const)
31053111
(let ((v (decl-var (cadr e))))
3106-
(find-assigned-vars- (caddr e))
3112+
(unless (and (eq? (car e) 'const) (null? (cddr e)))
3113+
(find-assigned-vars- (caddr e)))
31073114
(if (or (ssavalue? v) (globalref? v) (underscore-symbol? v))
31083115
'()
31093116
(set! vars (cons v vars)))))
@@ -3525,7 +3532,8 @@
35253532
(vinfo:set-sa! vi #f)
35263533
(vinfo:set-sa! vi #t))
35273534
(vinfo:set-asgn! vi #t))))
3528-
(analyze-vars (caddr e) env captvars sp tab))
3535+
(unless (null? (cddr e))
3536+
(analyze-vars (caddr e) env captvars sp tab)))
35293537
((call)
35303538
(let ((vi (get tab (cadr e) #f)))
35313539
(if vi
@@ -4148,8 +4156,6 @@ f(x) = yt(x)
41484156
`(call (core declare_global) (thismodule) (inert ,(cadr e)) (false)))
41494157
(latestworld)))))
41504158
((const)
4151-
;; Check we've expanded surface `const` (1 argument form)
4152-
(assert (and (length= e 3)))
41534159
(when (globalref? (cadr e))
41544160
(put! globals (cadr e) #f))
41554161
e)
@@ -4719,10 +4725,15 @@ f(x) = yt(x)
47194725
(list cnd))))))
47204726
tests))
47214727
(define (emit-assignment-or-setglobal lhs rhs (op '=))
4722-
;; (const (globalref _ _) _) does not use setglobal!
4723-
(if (and (globalref? lhs) (eq? op '=))
4724-
(emit `(call (top setglobal!) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs))
4725-
(emit `(,op ,lhs ,rhs))))
4728+
;; (= (globalref _ _) _) => setglobal!
4729+
;; (const (globalref _ _) _) => declare_const
4730+
(cond ((and (globalref? lhs) (eq? op '=))
4731+
(emit `(call (core setglobal!) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs)))
4732+
((and (globalref? lhs) (eq? op 'const))
4733+
(emit `(call (core declare_const) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs)))
4734+
(else
4735+
(assert (eq? op '=))
4736+
(emit `(= ,lhs ,rhs)))))
47264737
(define (emit-assignment lhs rhs (op '=))
47274738
(if rhs
47284739
(if (valid-ir-rvalue? lhs rhs)
@@ -4803,21 +4814,26 @@ f(x) = yt(x)
48034814
(when (pair? (cadr lam))
48044815
(error (string "`global const` declaration not allowed inside function" (format-loc current-loc)))))
48054816
(let ((lhs (cadr e)))
4806-
(if (and (symbol? lhs) (underscore-symbol? lhs))
4807-
(compile (caddr e) break-labels value tail)
4808-
(let* ((rhs (compile (caddr e) break-labels #t #f))
4809-
(lhs (if (and arg-map (symbol? lhs))
4810-
(get arg-map lhs lhs)
4811-
lhs)))
4812-
(if (and value rhs)
4813-
(let ((rr (if (or (atom? rhs) (ssavalue? rhs) (eq? (car rhs) 'null))
4814-
rhs (make-ssavalue))))
4815-
(if (not (eq? rr rhs))
4816-
(emit `(= ,rr ,rhs)))
4817-
(emit-assignment-or-setglobal lhs rr (car e))
4818-
(if tail (emit-return tail rr))
4819-
rr)
4820-
(emit-assignment lhs rhs (car e)))))))
4817+
(cond ((and (symbol? lhs) (underscore-symbol? lhs))
4818+
(compile (caddr e) break-labels value tail))
4819+
((and (eq? (car e) 'const) (null? (cddr e)) (globalref? (cadr e)))
4820+
;; No RHS - make undefined constant
4821+
(let ((lhs (cadr e)))
4822+
(emit `(call (core declare_const) ,(cadr lhs) (inert ,(caddr lhs))))))
4823+
(else
4824+
(let* ((rhs (compile (caddr e) break-labels #t #f))
4825+
(lhs (if (and arg-map (symbol? lhs))
4826+
(get arg-map lhs lhs)
4827+
lhs)))
4828+
(if (and value rhs)
4829+
(let ((rr (if (or (atom? rhs) (ssavalue? rhs) (eq? (car rhs) 'null))
4830+
rhs (make-ssavalue))))
4831+
(if (not (eq? rr rhs))
4832+
(emit `(= ,rr ,rhs)))
4833+
(emit-assignment-or-setglobal lhs rr (car e))
4834+
(if tail (emit-return tail rr))
4835+
rr)
4836+
(emit-assignment lhs rhs (car e))))))))
48214837
((block)
48224838
(let* ((last-fname filename)
48234839
(fnm (first-non-meta e))

0 commit comments

Comments
 (0)