Skip to content

Commit

Permalink
Canonicalize names of nested functions by keeping a more fine grained…
Browse files Browse the repository at this point in the history
… counter -- per (module, method name) pair (JuliaLang#53719)

As mentioned in JuliaLang#53716, we've
been noticing that `precompile` statements lists from one version of our
codebase often don't apply cleanly in a slightly different version.

That's because a lot of nested and anonymous function names have a
global numeric suffix which is incremented every time a new name is
generated, and these numeric suffixes are not very stable across
codebase changes.

To solve this, this PR makes the numeric suffixes a bit more fine
grained: every pair of (module, top-level/outermost function name) will
have its own counter, which should make nested function names a bit more
stable across different versions.

This PR applies @JeffBezanson's idea of making the symbol name changes
directly in `current-julia-module-counter`.

Here is an example:

```Julia
julia> function foo(x)
           function bar(y)
               return x + y
           end
       end
foo (generic function with 1 method)

julia> f = foo(42)
(::var"#bar#foo##0"{Int64}) (generic function with 1 method)
```
  • Loading branch information
d-netto committed Sep 20, 2024
1 parent c690603 commit 3eb8a3f
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 63 deletions.
4 changes: 2 additions & 2 deletions doc/src/manual/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,12 +292,12 @@ syntaxes:

```jldoctest
julia> x -> x^2 + 2x - 1
#1 (generic function with 1 method)
#2 (generic function with 1 method)
julia> function (x)
x^2 + 2x - 1
end
#3 (generic function with 1 method)
#5 (generic function with 1 method)
```

This creates a function taking one argument `x` and returning the value of the polynomial `x^2 +
Expand Down
44 changes: 40 additions & 4 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#ifdef _OS_WINDOWS_
#include <malloc.h>
#endif
Expand Down Expand Up @@ -160,11 +161,46 @@ static value_t fl_defined_julia_global(fl_context_t *fl_ctx, value_t *args, uint
return (b != NULL && jl_atomic_load_relaxed(&b->owner) == b) ? fl_ctx->T : fl_ctx->F;
}

static value_t fl_current_module_counter(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) JL_NOTSAFEPOINT
// Used to generate a unique suffix for a given symbol (e.g. variable or type name)
// first argument contains a stack of method definitions seen so far by `closure-convert` in flisp.
// if the top of the stack is non-NIL, we use it to augment the suffix so that it becomes
// of the form $top_level_method_name##$counter, where `counter` is the smallest integer
// such that the resulting name is not already defined in the current module's bindings.
// If the top of the stack is NIL, we simply return the current module's counter.
// This ensures that precompile statements are a bit more stable across different versions
// of a codebase. see #53719
static value_t fl_module_unique_name(fl_context_t *fl_ctx, value_t *args, uint32_t nargs)
{
argcount(fl_ctx, "julia-module-unique-name", nargs, 1);
jl_ast_context_t *ctx = jl_ast_ctx(fl_ctx);
assert(ctx->module);
return fixnum(jl_module_next_counter(ctx->module));
jl_module_t *m = ctx->module;
assert(m != NULL);
// Get the outermost function name from the `parsed_method_stack` top
char *funcname = NULL;
value_t parsed_method_stack = args[0];
if (parsed_method_stack != fl_ctx->NIL) {
value_t bottom_stack_symbol = fl_applyn(fl_ctx, 1, symbol_value(symbol(fl_ctx, "last")), parsed_method_stack);
funcname = tosymbol(fl_ctx, bottom_stack_symbol, "julia-module-unique-name")->name;
}
size_t sz = funcname != NULL ? strlen(funcname) + 32 : 32; // 32 is enough for the suffix
char *buf = (char*)alloca(sz);
if (funcname != NULL && strchr(funcname, '#') == NULL) {
for (int i = 0; ; i++) {
snprintf(buf, sz, "%s##%d", funcname, i);
jl_sym_t *sym = jl_symbol(buf);
JL_LOCK(&m->lock);
if (jl_get_module_binding(m, sym, 0) == NULL) { // make sure this name is not already taken
jl_get_module_binding(m, sym, 1); // create the binding
JL_UNLOCK(&m->lock);
return symbol(fl_ctx, buf);
}
JL_UNLOCK(&m->lock);
}
}
else {
snprintf(buf, sz, "%d", jl_module_next_counter(m));
}
return symbol(fl_ctx, buf);
}

static value_t fl_julia_current_file(fl_context_t *fl_ctx, value_t *args, uint32_t nargs) JL_NOTSAFEPOINT
Expand Down Expand Up @@ -206,7 +242,7 @@ static jl_value_t *scm_to_julia_(fl_context_t *fl_ctx, value_t e, jl_module_t *m

static const builtinspec_t julia_flisp_ast_ext[] = {
{ "defined-julia-global", fl_defined_julia_global }, // TODO: can we kill this safepoint
{ "current-julia-module-counter", fl_current_module_counter },
{ "current-julia-module-counter", fl_module_unique_name },
{ "julia-scalar?", fl_julia_scalar },
{ "julia-current-file", fl_julia_current_file },
{ "julia-current-line", fl_julia_current_line },
Expand Down
22 changes: 6 additions & 16 deletions src/datatype.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,23 +19,21 @@ extern "C" {

// allocating TypeNames -----------------------------------------------------------

static int is10digit(char c) JL_NOTSAFEPOINT
{
return (c >= '0' && c <= '9');
}

static jl_sym_t *jl_demangle_typename(jl_sym_t *s) JL_NOTSAFEPOINT
{
char *n = jl_symbol_name(s);
if (n[0] != '#')
return s;
char *end = strrchr(n, '#');
char *end = strchr(&n[1], '#');
// handle `#f...##...#...`
if (end != NULL && end[1] == '#')
end = strchr(&end[2], '#');
int32_t len;
if (end == n || end == n+1)
if (end == NULL || end == n+1)
len = strlen(n) - 1;
else
len = (end-n) - 1; // extract `f` from `#f#...`
if (is10digit(n[1]))
if (isdigit(n[1]) || is_canonicalized_anonfn_typename(n))
return _jl_symbol(n, len+1);
return _jl_symbol(&n[1], len);
}
Expand Down Expand Up @@ -687,14 +685,6 @@ void jl_compute_field_offsets(jl_datatype_t *st)
return;
}

static int is_anonfn_typename(char *name)
{
if (name[0] != '#' || name[1] == '#')
return 0;
char *other = strrchr(name, '#');
return other > &name[1] && is10digit(other[1]);
}

JL_DLLEXPORT jl_datatype_t *jl_new_datatype(
jl_sym_t *name,
jl_module_t *module,
Expand Down
2 changes: 1 addition & 1 deletion src/flisp/flisp.h
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ value_t fl_cons(fl_context_t *fl_ctx, value_t a, value_t b) JL_NOTSAFEPOINT;
value_t fl_list2(fl_context_t *fl_ctx, value_t a, value_t b) JL_NOTSAFEPOINT;
value_t fl_listn(fl_context_t *fl_ctx, size_t n, ...) JL_NOTSAFEPOINT;
value_t symbol(fl_context_t *fl_ctx, const char *str) JL_NOTSAFEPOINT;
char *symbol_name(fl_context_t *fl_ctx, value_t v);
char *symbol_name(fl_context_t *fl_ctx, value_t v) JL_NOTSAFEPOINT;
int fl_is_keyword_name(const char *str, size_t len);
value_t alloc_vector(fl_context_t *fl_ctx, size_t n, int init);
size_t llength(value_t v);
Expand Down
8 changes: 0 additions & 8 deletions src/gf.c
Original file line number Diff line number Diff line change
Expand Up @@ -1535,14 +1535,6 @@ void print_func_loc(JL_STREAM *s, jl_method_t *m)
}
}

static int is_anonfn_typename(char *name)
{
if (name[0] != '#' || name[1] == '#')
return 0;
char *other = strrchr(name, '#');
return other > &name[1] && other[1] > '0' && other[1] <= '9';
}

static void method_overwrite(jl_typemap_entry_t *newentry, jl_method_t *oldvalue)
{
// method overwritten
Expand Down
Loading

0 comments on commit 3eb8a3f

Please sign in to comment.