Skip to content

Commit 040f6cd

Browse files
xal-0vtjnash
andauthored
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 <[email protected]>
1 parent 3ba31ed commit 040f6cd

File tree

12 files changed

+105
-99
lines changed

12 files changed

+105
-99
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, :globaldecl,
587+
:leave, :globaldecl,
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,
@@ -149,7 +148,7 @@ function validate_code!(errors::Vector{InvalidCodeError}, c::CodeInfo, is_top_le
149148
elseif head === :call || head === :invoke || x.head === :invoke_modify ||
150149
head === :gc_preserve_end || head === :meta ||
151150
head === :inbounds || head === :foreigncall || head === :cfunction ||
152-
head === :const || head === :leave || head === :pop_exception ||
151+
head === :leave || head === :pop_exception ||
153152
head === :method || head === :global || head === :static_parameter ||
154153
head === :new || head === :splatnew || head === :thunk || head === :loopinfo ||
155154
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
@@ -2758,6 +2758,41 @@ See also [`setpropertyonce!`](@ref Base.setpropertyonce!) and [`setglobal!`](@re
27582758
"""
27592759
setglobalonce!
27602760

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

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
@@ -61,6 +61,7 @@ extern "C" {
6161
XX(opaque_closure_call,"opaque_closure_call") \
6262
XX(replacefield,"replacefield!") \
6363
XX(replaceglobal,"replaceglobal!") \
64+
XX(declare_const,"declare_const") \
6465
XX(setfield,"setfield!") \
6566
XX(setfieldonce,"setfieldonce!") \
6667
XX(setglobal,"setglobal!") \

src/builtins.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,6 +1518,18 @@ JL_CALLABLE(jl_f_setglobalonce)
15181518
return old == NULL ? jl_true : jl_false;
15191519
}
15201520

1521+
JL_CALLABLE(jl_f_declare_const)
1522+
{
1523+
JL_NARGS(declare_const, 2, 3);
1524+
JL_TYPECHK(declare_const, module, args[0]);
1525+
if (nargs == 3)
1526+
JL_TYPECHK(declare_const, symbol, args[1]);
1527+
jl_binding_t *b = jl_get_module_binding((jl_module_t *)args[0], (jl_sym_t *)args[1], 1);
1528+
jl_value_t *val = nargs == 3 ? args[2] : NULL;
1529+
jl_declare_constant_val(b, (jl_module_t *)args[0], (jl_sym_t *)args[1], val);
1530+
return nargs > 2 ? args[2] : jl_nothing;
1531+
}
1532+
15211533
// import, using --------------------------------------------------------------
15221534

15231535
// 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_globaldecl_sym) {
64936464
assert(nargs <= 2 && nargs >= 1);
64946465
jl_sym_t *sym = (jl_sym_t*)args[0];

src/interpreter.c

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -643,12 +643,6 @@ static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s, size_t ip,
643643
jl_declare_global(s->module, jl_exprarg(stmt, 0), val, 1);
644644
s->locals[jl_source_nslots(s->src) + s->ip] = jl_nothing;
645645
}
646-
else if (head == jl_const_sym) {
647-
jl_value_t *val = jl_expr_nargs(stmt) == 1 ? NULL : eval_value(jl_exprarg(stmt, 1), s);
648-
s->locals[jl_source_nslots(s->src) + s->ip] = val; // temporarily root
649-
jl_eval_const_decl(s->module, jl_exprarg(stmt, 0), val);
650-
s->locals[jl_source_nslots(s->src) + s->ip] = jl_nothing;
651-
}
652646
else if (head == jl_latestworld_sym) {
653647
ct->world_age = jl_atomic_load_acquire(&jl_world_counter);
654648
}

src/jlfrontend.scm

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@
140140
(and (pair? e)
141141
(or (memq (car e) '(toplevel line module export public
142142
error incomplete))
143-
(and (memq (car e) '(global const)) (every symbol? (cdr e))))))
143+
(and (memq (car e) '(global)) (every symbol? (cdr e))))))
144144

145145
(define *in-lowering* #f)
146146

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"))
@@ -3100,7 +3106,8 @@
31003106
(set! vars (cons (cadr e) vars)))
31013107
((= const)
31023108
(let ((v (decl-var (cadr e))))
3103-
(find-assigned-vars- (caddr e))
3109+
(unless (and (eq? (car e) 'const) (null? (cddr e)))
3110+
(find-assigned-vars- (caddr e)))
31043111
(if (or (ssavalue? v) (globalref? v) (underscore-symbol? v))
31053112
'()
31063113
(set! vars (cons v vars)))))
@@ -3522,7 +3529,8 @@
35223529
(vinfo:set-sa! vi #f)
35233530
(vinfo:set-sa! vi #t))
35243531
(vinfo:set-asgn! vi #t))))
3525-
(analyze-vars (caddr e) env captvars sp tab))
3532+
(unless (null? (cddr e))
3533+
(analyze-vars (caddr e) env captvars sp tab)))
35263534
((call)
35273535
(let ((vi (get tab (cadr e) #f)))
35283536
(if vi
@@ -4126,8 +4134,6 @@ f(x) = yt(x)
41264134
'(null)
41274135
`(newvar ,(cadr e))))))
41284136
((const)
4129-
;; Check we've expanded surface `const` (1 argument form)
4130-
(assert (and (length= e 3)))
41314137
(when (globalref? (cadr e))
41324138
(put! globals (cadr e) #f))
41334139
e)
@@ -4696,10 +4702,15 @@ f(x) = yt(x)
46964702
(list cnd))))))
46974703
tests))
46984704
(define (emit-assignment-or-setglobal lhs rhs (op '=))
4699-
;; (const (globalref _ _) _) does not use setglobal!
4700-
(if (and (globalref? lhs) (eq? op '=))
4701-
(emit `(call (top setglobal!) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs))
4702-
(emit `(,op ,lhs ,rhs))))
4705+
;; (= (globalref _ _) _) => setglobal!
4706+
;; (const (globalref _ _) _) => declare_const
4707+
(cond ((and (globalref? lhs) (eq? op '=))
4708+
(emit `(call (core setglobal!) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs)))
4709+
((and (globalref? lhs) (eq? op 'const))
4710+
(emit `(call (core declare_const) ,(cadr lhs) (inert ,(caddr lhs)) ,rhs)))
4711+
(else
4712+
(assert (eq? op '=))
4713+
(emit `(= ,lhs ,rhs)))))
47034714
(define (emit-assignment lhs rhs (op '=))
47044715
(if rhs
47054716
(if (valid-ir-rvalue? lhs rhs)
@@ -4780,21 +4791,26 @@ f(x) = yt(x)
47804791
(when (pair? (cadr lam))
47814792
(error (string "`global const` declaration not allowed inside function" (format-loc current-loc)))))
47824793
(let ((lhs (cadr e)))
4783-
(if (and (symbol? lhs) (underscore-symbol? lhs))
4784-
(compile (caddr e) break-labels value tail)
4785-
(let* ((rhs (compile (caddr e) break-labels #t #f))
4786-
(lhs (if (and arg-map (symbol? lhs))
4787-
(get arg-map lhs lhs)
4788-
lhs)))
4789-
(if (and value rhs)
4790-
(let ((rr (if (or (atom? rhs) (ssavalue? rhs) (eq? (car rhs) 'null))
4791-
rhs (make-ssavalue))))
4792-
(if (not (eq? rr rhs))
4793-
(emit `(= ,rr ,rhs)))
4794-
(emit-assignment-or-setglobal lhs rr (car e))
4795-
(if tail (emit-return tail rr))
4796-
rr)
4797-
(emit-assignment lhs rhs (car e)))))))
4794+
(cond ((and (symbol? lhs) (underscore-symbol? lhs))
4795+
(compile (caddr e) break-labels value tail))
4796+
((and (eq? (car e) 'const) (null? (cddr e)) (globalref? (cadr e)))
4797+
;; No RHS - make undefined constant
4798+
(let ((lhs (cadr e)))
4799+
(emit `(call (core declare_const) ,(cadr lhs) (inert ,(caddr lhs))))))
4800+
(else
4801+
(let* ((rhs (compile (caddr e) break-labels #t #f))
4802+
(lhs (if (and arg-map (symbol? lhs))
4803+
(get arg-map lhs lhs)
4804+
lhs)))
4805+
(if (and value rhs)
4806+
(let ((rr (if (or (atom? rhs) (ssavalue? rhs) (eq? (car rhs) 'null))
4807+
rhs (make-ssavalue))))
4808+
(if (not (eq? rr rhs))
4809+
(emit `(= ,rr ,rhs)))
4810+
(emit-assignment-or-setglobal lhs rr (car e))
4811+
(if tail (emit-return tail rr))
4812+
rr)
4813+
(emit-assignment lhs rhs (car e))))))))
47984814
((block)
47994815
(let* ((last-fname filename)
48004816
(fnm (first-non-meta e))

0 commit comments

Comments
 (0)