Skip to content

Commit f4a9d25

Browse files
authored
internals: add _defaultctor function for defining ctors (JuliaLang#57317)
Deferring this part of defining the ctor code allows it to do some value-based optimizations, without the awkwardness previously of needing to do conditional lowering of all of the possible versions that might be useful just from syntax transforms. This feels very like a generated function: how it expands just the function body, except it is flipped so that then we wrap the result in a single Method instead of being created from a Method! Avoids lowering inaccuracies where a variable appears but is not used. These warnings are now prevented: ```julia julia> struct Foo{T} x::(T; Any) end WARNING: method definition for Foo at REPL[1]:2 declares type variable T but does not use it. julia> struct Foo{T} x::Union{Any, T} end WARNING: method definition for Foo at REPL[1]:2 declares type variable T but does not use it. ``` Avoids hitting JuliaLang#31542. This lowering mistake is now avoided: ```julia julia> struct Foo{T} x::(Val{T} where T) end ERROR: syntax: invalid variable expression in "where" around REPL[1]:2 ``` As a minor optimization, this avoids generating a `convert` call when the user declares explicit `::Any` declarations, the same as if the user didn't annotate the field type: ```julia julia> struct Foo x::Any y::Int z end julia> code_lowered(Foo)[2] CodeInfo( @ REPL[1]:2 within `unknown scope` 1 ─ %1 = builtin Core.fieldtype(#ctor-self#, 2) │ %2 = y │ @_4 = %2 │ %4 = builtin @_4 isa %1 └── goto mmtk#3 if not %4 2 ─ goto mmtk#4 3 ─ @_4 = Base.convert(%1, @_4) 4 ┄ %8 = @_4 │ %9 = %new(#ctor-self#, x, %8, z) └── return %9 ) ``` The outer/inner names might be a bit historical at this point (predating where clauses allowing specifying them flexibly inside or outside of the struct def): they are really exact-type-type&convert-args-from-any / exact-arg-types&apply-type-from-args if named for precisely what they do.
2 parents 504cbc3 + d0e8025 commit f4a9d25

11 files changed

+231
-97
lines changed

src/ast.c

+28
Original file line numberDiff line numberDiff line change
@@ -1369,6 +1369,34 @@ JL_DLLEXPORT jl_value_t *jl_expand_stmt(jl_value_t *expr, jl_module_t *inmodule)
13691369
return jl_expand_stmt_with_loc(expr, inmodule, "none", 0);
13701370
}
13711371

1372+
jl_code_info_t *jl_outer_ctor_body(jl_value_t *thistype, size_t nfields, size_t nsparams, jl_module_t *inmodule, const char *file, int line)
1373+
{
1374+
JL_TIMING(LOWERING, LOWERING);
1375+
jl_timing_show_location(file, line, inmodule, JL_TIMING_DEFAULT_BLOCK);
1376+
jl_expr_t *expr = jl_exprn(jl_empty_sym, 3);
1377+
JL_GC_PUSH1(&expr);
1378+
jl_exprargset(expr, 0, thistype);
1379+
jl_exprargset(expr, 1, jl_box_long(nfields));
1380+
jl_exprargset(expr, 2, jl_box_long(nsparams));
1381+
jl_code_info_t *ci = (jl_code_info_t*)jl_call_scm_on_ast_and_loc("jl-default-outer-ctor-body", (jl_value_t*)expr, inmodule, file, line);
1382+
JL_GC_POP();
1383+
assert(jl_is_code_info(ci));
1384+
return ci;
1385+
}
1386+
1387+
jl_code_info_t *jl_inner_ctor_body(jl_array_t *fieldkinds, jl_module_t *inmodule, const char *file, int line)
1388+
{
1389+
JL_TIMING(LOWERING, LOWERING);
1390+
jl_timing_show_location(file, line, inmodule, JL_TIMING_DEFAULT_BLOCK);
1391+
jl_expr_t *expr = jl_exprn(jl_empty_sym, 0);
1392+
JL_GC_PUSH1(&expr);
1393+
expr->args = fieldkinds;
1394+
jl_code_info_t *ci = (jl_code_info_t*)jl_call_scm_on_ast_and_loc("jl-default-inner-ctor-body", (jl_value_t*)expr, inmodule, file, line);
1395+
JL_GC_POP();
1396+
assert(jl_is_code_info(ci));
1397+
return ci;
1398+
}
1399+
13721400

13731401
//------------------------------------------------------------------------------
13741402
// Parsing API and utils for calling parser from runtime

src/builtin_proto.h

+1
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ JL_CALLABLE(jl_f__structtype);
8080
JL_CALLABLE(jl_f__abstracttype);
8181
JL_CALLABLE(jl_f__primitivetype);
8282
JL_CALLABLE(jl_f__setsuper);
83+
JL_CALLABLE(jl_f__defaultctors);
8384
JL_CALLABLE(jl_f__equiv_typedef);
8485
JL_CALLABLE(jl_f_get_binding_type);
8586
JL_CALLABLE(jl_f__compute_sparams);

src/builtins.c

+8
Original file line numberDiff line numberDiff line change
@@ -2345,6 +2345,13 @@ JL_CALLABLE(jl_f__equiv_typedef)
23452345
return equiv_type(args[0], args[1]) ? jl_true : jl_false;
23462346
}
23472347

2348+
JL_CALLABLE(jl_f__defaultctors)
2349+
{
2350+
JL_NARGS(_defaultctors, 2, 2);
2351+
jl_ctor_def(args[0], args[1]);
2352+
return jl_nothing;
2353+
}
2354+
23482355
// IntrinsicFunctions ---------------------------------------------------------
23492356

23502357
static void (*runtime_fp[num_intrinsics])(void);
@@ -2541,6 +2548,7 @@ void jl_init_primitives(void) JL_GC_DISABLED
25412548
add_builtin_func("_abstracttype", jl_f__abstracttype);
25422549
add_builtin_func("_primitivetype", jl_f__primitivetype);
25432550
add_builtin_func("_setsuper!", jl_f__setsuper);
2551+
add_builtin_func("_defaultctors", jl_f__defaultctors);
25442552
jl_builtin__typebody = add_builtin_func("_typebody!", jl_f__typebody);
25452553
add_builtin_func("_equiv_typedef", jl_f__equiv_typedef);
25462554
jl_builtin_donotdelete = add_builtin_func("donotdelete", jl_f_donotdelete);

src/jlfrontend.scm

+6
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@
198198
(error-wrap (lambda ()
199199
(julia-expand-macroscope expr))))
200200

201+
(define (jl-default-inner-ctor-body field-kinds file line)
202+
(expand-to-thunk- (default-inner-ctor-body (cdr field-kinds) file line) file line))
203+
204+
(define (jl-default-outer-ctor-body args file line)
205+
(expand-to-thunk- (default-outer-ctor-body (cadr args) (caddr args) (cadddr args) file line) file line))
206+
201207
; run whole frontend on a string. useful for testing.
202208
(define (fe str)
203209
(expand-toplevel-expr (julia-parse str) 'none 0))

src/jltypes.c

+3
Original file line numberDiff line numberDiff line change
@@ -3876,7 +3876,10 @@ void jl_init_types(void) JL_GC_DISABLED
38763876
jl_string_type->ismutationfree = jl_string_type->isidentityfree = 1;
38773877
jl_symbol_type->ismutationfree = jl_symbol_type->isidentityfree = 1;
38783878
jl_simplevector_type->ismutationfree = jl_simplevector_type->isidentityfree = 1;
3879+
jl_typename_type->ismutationfree = 1;
38793880
jl_datatype_type->ismutationfree = 1;
3881+
jl_uniontype_type->ismutationfree = 1;
3882+
jl_unionall_type->ismutationfree = 1;
38803883
assert(((jl_datatype_t*)jl_array_any_type)->ismutationfree == 0);
38813884
assert(((jl_datatype_t*)jl_array_uint8_type)->ismutationfree == 0);
38823885

src/julia-syntax.scm

+49-94
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@
183183
(meta ret-type ,R)
184184
,@(list-tail body (+ 1 (length meta))))))))))
185185

186+
186187
;; convert x<:T<:y etc. exprs into (name lower-bound upper-bound)
187188
;; a bound is #f if not specified
188189
(define (analyze-typevar e)
@@ -753,64 +754,34 @@
753754
(params bounds) (sparam-name-bounds params)
754755
(struct-def-expr- name params bounds super (flatten-blocks fields) mut)))
755756

756-
;; replace field names with gensyms if they conflict with field-types
757-
(define (safe-field-names field-names field-types)
758-
(if (any (lambda (v) (contains (lambda (e) (eq? e v)) field-types))
759-
field-names)
760-
(map (lambda (x) (gensy)) field-names)
761-
;; use a different name for a field called `_`
762-
(map (lambda (x) (if (eq? x '_) (gensy) x)) field-names)))
763-
764-
(define (with-wheres call wheres)
765-
(if (pair? wheres)
766-
`(where ,call ,@wheres)
767-
call))
768-
769-
(define (default-inner-ctors name field-names field-types params bounds locs)
770-
(let* ((field-names (safe-field-names field-names field-types))
771-
(all-ctor (if (null? params)
772-
;; definition with exact types for all arguments
773-
`(function (call ,name
774-
,@(map make-decl field-names field-types))
775-
(block
776-
,@locs
777-
(new (globalref (thismodule) ,name) ,@field-names)))
778-
#f))
779-
(any-ctor (if (or (not all-ctor) (any (lambda (t) (not (equal? t '(core Any))))
780-
field-types))
781-
;; definition with Any for all arguments
782-
;; only if any field type is not Any, checked at runtime
783-
`(function (call (|::| |#ctor-self#|
784-
,(with-wheres
785-
`(curly (core Type) ,(if (pair? params)
786-
`(curly ,name ,@params)
787-
name))
788-
(map (lambda (b) (cons 'var-bounds b)) bounds)))
789-
,@field-names)
790-
(block
791-
,@locs
792-
(call new ,@field-names))) ; this will add convert calls later
793-
#f)))
794-
(if all-ctor
795-
(if any-ctor
796-
(list all-ctor
797-
`(if ,(foldl (lambda (t u)
798-
`(&& ,u (call (core ===) (core Any) ,t)))
799-
`(call (core ===) (core Any) ,(car field-types))
800-
(cdr field-types))
801-
'(block)
802-
,any-ctor))
803-
(list all-ctor))
804-
(list any-ctor))))
805-
806-
(define (default-outer-ctor name field-names field-types params bounds locs)
807-
(let ((field-names (safe-field-names field-names field-types)))
808-
`(function ,(with-wheres
809-
`(call ,name ,@(map make-decl field-names field-types))
810-
(map (lambda (b) (cons 'var-bounds b)) bounds))
811-
(block
812-
,@locs
813-
(new (curly ,name ,@params) ,@field-names)))))
757+
;; definition with Any for all arguments (except type, which is exact)
758+
;; field-kinds:
759+
;; -1 no convert (e.g. because it is Any)
760+
;; 0 normal convert to fieldtype
761+
;; 1+ static_parameter N
762+
(define (default-inner-ctor-body field-kinds file line)
763+
(let* ((name '|#ctor-self#|)
764+
(field-names (map (lambda (idx) (symbol (string "_" (+ idx 1)))) (iota (length field-kinds))))
765+
(field-convert (lambda (fld fty val)
766+
(cond ((eq? fty -1) val)
767+
((> fty 0) (convert-for-type-decl val `(static_parameter ,fty) #f #f))
768+
(else (convert-for-type-decl val `(call (core fieldtype) ,name ,(+ fld 1)) #f #f)))))
769+
(field-vals (map field-convert (iota (length field-names)) field-kinds field-names))
770+
(body `(block
771+
(line ,line ,file)
772+
(return (new ,name ,@field-vals)))))
773+
`(lambda ,(cons name field-names) () (scope-block ,body))))
774+
775+
;; definition with exact types for all arguments (except type, which is not parameterized)
776+
(define (default-outer-ctor-body thistype field-count sparam-count file line)
777+
(let* ((name '|#ctor-self#|)
778+
(field-names (map (lambda (idx) (symbol (string "_" (+ idx 1)))) (iota field-count)))
779+
(sparams (map (lambda (idx) `(static_parameter ,(+ idx 1))) (iota sparam-count)))
780+
(type (if (null? sparams) name `(curly ,thistype ,@sparams)))
781+
(body `(block
782+
(line ,line ,file)
783+
(return (new ,type ,@field-names)))))
784+
`(lambda ,(cons name field-names) () (scope-block ,body))))
814785

815786
(define (num-non-varargs args)
816787
(count (lambda (a) (not (vararg? a))) args))
@@ -993,14 +964,11 @@
993964
fields)))
994965
(attrs (reverse attrs))
995966
(defs (filter (lambda (x) (not (or (effect-free? x) (eq? (car x) 'string)))) defs))
996-
(locs (if (and (pair? fields0) (linenum? (car fields0)))
997-
(list (car fields0))
998-
'()))
967+
(loc (if (and (pair? fields0) (linenum? (car fields0)))
968+
(car fields0)
969+
'(line 0 ||)))
999970
(field-names (map decl-var fields))
1000971
(field-types (map decl-type fields))
1001-
(defs2 (if (null? defs)
1002-
(default-inner-ctors name field-names field-types params bounds locs)
1003-
defs))
1004972
(min-initialized (min (ctors-min-initialized defs) (length fields)))
1005973
(hasprev (make-ssavalue))
1006974
(prev (make-ssavalue))
@@ -1042,34 +1010,21 @@
10421010
(const (globalref (thismodule) ,name) ,newdef)
10431011
(latestworld)
10441012
(null)))
1045-
;; "inner" constructors
1046-
(scope-block
1047-
(block
1048-
(hardscope)
1049-
(global ,name)
1050-
,@(map (lambda (c)
1051-
(rewrite-ctor c name params field-names field-types))
1052-
defs2)))
1053-
;; "outer" constructors
1054-
,@(if (and (null? defs)
1055-
(not (null? params))
1056-
;; To generate an outer constructor, each parameter must occur in a field
1057-
;; type, or in the bounds of a subsequent parameter.
1058-
;; Otherwise the constructor would not work, since the parameter values
1059-
;; would never be specified.
1060-
(let loop ((root-types field-types)
1061-
(sp (reverse bounds)))
1062-
(or (null? sp)
1063-
(let ((p (car sp)))
1064-
(and (expr-contains-eq (car p) (cons 'list root-types))
1065-
(loop (append (cdr p) root-types)
1066-
(cdr sp)))))))
1067-
`((scope-block
1068-
(block
1069-
(global ,name)
1070-
,(default-outer-ctor name field-names field-types
1071-
params bounds locs))))
1072-
'())
1013+
;; Always define ctors even if we didn't change the definition.
1014+
;; If newdef===prev, then this is a bit suspect, since we don't know what might be
1015+
;; changing about the old ctor definitions (we don't even track whether we're
1016+
;; replacing defaultctors with identical ones). But it seems better to have the ctors
1017+
;; added alongside (replacing) the old ones, than to not have them and need them.
1018+
;; Commonly Revise.jl should be used to figure out actually which methods should
1019+
;; actually be deleted or added anew.
1020+
,(if (null? defs)
1021+
`(call (core _defaultctors) ,newdef (inert ,loc))
1022+
`(scope-block
1023+
(block
1024+
(hardscope)
1025+
(global ,name)
1026+
,@(map (lambda (c) (rewrite-ctor c name params field-names field-types)) defs))))
1027+
(latestworld)
10731028
(null)))))
10741029

10751030
(define (abstract-type-def-expr name params super)
@@ -4646,7 +4601,7 @@ f(x) = yt(x)
46464601
;; from the current function.
46474602
(define (compile e break-labels value tail)
46484603
(if (or (not (pair? e)) (memq (car e) '(null true false ssavalue quote inert top core copyast the_exception $
4649-
globalref thismodule cdecl stdcall fastcall thiscall llvmcall)))
4604+
globalref thismodule cdecl stdcall fastcall thiscall llvmcall static_parameter)))
46504605
(let ((e1 (if (and arg-map (symbol? e))
46514606
(get arg-map e e)
46524607
e)))
@@ -4657,7 +4612,7 @@ f(x) = yt(x)
46574612
(cond (tail (emit-return tail e1))
46584613
(value e1)
46594614
((symbol? e1) (emit e1) #f) ;; keep symbols for undefined-var checking
4660-
((and (pair? e1) (eq? (car e1) 'globalref)) (emit e1) #f) ;; keep globals for undefined-var checking
4615+
((and (pair? e1) (memq (car e1) '(globalref static_parameter))) (emit e1) #f) ;; keep for undefined-var checking
46614616
(else #f)))
46624617
(case (car e)
46634618
((call new splatnew foreigncall cfunction new_opaque_closure)

src/julia.h

+1
Original file line numberDiff line numberDiff line change
@@ -1612,6 +1612,7 @@ static inline int jl_field_isconst(jl_datatype_t *st, int i) JL_NOTSAFEPOINT
16121612
#define jl_is_quotenode(v) jl_typetagis(v,jl_quotenode_type)
16131613
#define jl_is_newvarnode(v) jl_typetagis(v,jl_newvarnode_type)
16141614
#define jl_is_linenode(v) jl_typetagis(v,jl_linenumbernode_type)
1615+
#define jl_is_linenumbernode(v) jl_typetagis(v,jl_linenumbernode_type)
16151616
#define jl_is_method_instance(v) jl_typetagis(v,jl_method_instance_type)
16161617
#define jl_is_code_instance(v) jl_typetagis(v,jl_code_instance_type)
16171618
#define jl_is_code_info(v) jl_typetagis(v,jl_code_info_type)

src/julia_internal.h

+3
Original file line numberDiff line numberDiff line change
@@ -1214,6 +1214,9 @@ JL_DLLEXPORT int jl_has_meta(jl_array_t *body, jl_sym_t *sym) JL_NOTSAFEPOINT;
12141214

12151215
JL_DLLEXPORT jl_value_t *jl_parse(const char *text, size_t text_len, jl_value_t *filename,
12161216
size_t lineno, size_t offset, jl_value_t *options);
1217+
jl_code_info_t *jl_inner_ctor_body(jl_array_t *fieldkinds, jl_module_t *inmodule, const char *file, int line);
1218+
jl_code_info_t *jl_outer_ctor_body(jl_value_t *thistype, size_t nfields, size_t nsparams, jl_module_t *inmodule, const char *file, int line);
1219+
void jl_ctor_def(jl_value_t *ty, jl_value_t *functionloc);
12171220

12181221
//--------------------------------------------------
12191222
// Backtraces

0 commit comments

Comments
 (0)