fix: symbol name for native boxed declarations in the interpreter (#12095)

This PR fixes the procedure for finding the mangled symbol name of boxed
variants of native functions. Previously, the wrong symbol name has been
used for names ending in `_`: For example `test_` mangles to `l_test__`
but `test_._boxed` mangles to `l_test___00__boxed`, not
`l_test_____boxed` which the compiler would previously wrongly use.
This probably didn't affect anybody though since the failure condition
is pretty rare: the name of a native function that the interpreter tries
to execute would've had to end in `_`.
This commit is contained in:
Rob23oba 2026-01-21 21:38:29 +01:00 committed by GitHub
parent 16873fb123
commit 6bec8adf16
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 30 additions and 11 deletions

View file

@ -56,9 +56,9 @@ public def Environment.getModulePackageByIdx? (env : Environment) (idx : ModuleI
Returns the standard base of the native symbol for the compiled constant {lean}`declName`.
For many constants, this is the full symbol. However, initializers have an additional prefix
(i.e., {lit}`_init_`) and boxed functions have an additional suffix (i.e., {lit}`___boxed`).
Furthermore, some constants do not use this stem at all (e.g., {lit}`main` and definitions
with {lit}`@[export]`).
(i.e., {lit}`_init_`) and boxed functions have an additional suffix
(see {name}`mkMangledBoxedName`). Furthermore, some constants do not use this stem at all
(e.g., {lit}`main` and definitions with {lit}`@[export]`).
-/
@[export lean_get_symbol_stem]
public def getSymbolStem (env : Environment) (declName : Name) : String :=

View file

@ -7,7 +7,7 @@ module
prelude
public import Lean.Setup
import Init.Data.String.Termination
import Init.Data.String.TakeDrop
namespace String
@ -133,6 +133,18 @@ def Name.mangleAux : Name → String
public def Name.mangle (n : Name) (pre : String := "l_") : String :=
pre ++ Name.mangleAux n
/--
Given `s = nm.mangle pre` for some `nm : Name` and `pre : String` with `nm != Name.anonymous`,
returns `(mkBoxedName nm).mangle pre`. This is used in the interpreter to find names of boxed
IR declarations.
-/
@[export lean_mk_mangled_boxed_name]
public def mkMangledBoxedName (s : String) : String :=
if s.endsWith "__" then
s ++ "_00__boxed"
else
s ++ "___boxed"
/--
The mangled name of the name used to create the module initialization function.

View file

@ -200,7 +200,6 @@ option_ref<decl> find_ir_decl_boxed(elab_environment const & env, name const & n
extern "C" double lean_float_of_nat(lean_obj_arg a);
extern "C" float lean_float32_of_nat(lean_obj_arg a);
static string_ref * g_boxed_mangled_suffix = nullptr;
static name * g_interpreter_prefer_native = nullptr;
DEBUG_CODE(static name * g_interpreter_step = nullptr;)
DEBUG_CODE(static name * g_interpreter_call = nullptr;)
@ -216,6 +215,12 @@ string_ref get_symbol_stem(elab_environment const & env, name const & fn) {
return string_ref(lean_get_symbol_stem(env.to_obj_arg(), fn.to_obj_arg()));
}
extern "C" obj_res lean_mk_mangled_boxed_name(obj_arg str);
string_ref mk_mangled_boxed_name(string_ref const & str) {
return string_ref(lean_mk_mangled_boxed_name(str.to_obj_arg()));
}
extern "C" object * lean_ir_format_fn_body_head(object * b);
std::string format_fn_body_head(fn_body const & b) {
object_ref s(lean_ir_format_fn_body_head(b.to_obj_arg()));
@ -843,7 +848,7 @@ private:
symbol_cache_entry e_new { get_decl(fn), {nullptr, false} };
if (m_prefer_native || decl_tag(e_new.m_decl) == decl_kind::Extern || has_init_attribute(m_env, fn)) {
string_ref mangled = get_symbol_stem(m_env, fn);
string_ref boxed_mangled(string_append(mangled.to_obj_arg(), g_boxed_mangled_suffix->raw()));
string_ref boxed_mangled = mk_mangled_boxed_name(mangled);
// check for boxed version first
if (void *p_boxed = lookup_symbol_in_cur_exe(boxed_mangled.data())) {
e_new.m_native.m_addr = p_boxed;
@ -965,7 +970,7 @@ private:
} else {
if (decl_tag(e.m_decl) == decl_kind::Extern) {
string_ref mangled = get_symbol_stem(m_env, fn);
string_ref boxed_mangled(string_append(mangled.to_obj_arg(), g_boxed_mangled_suffix->raw()));
string_ref boxed_mangled = mk_mangled_boxed_name(mangled);
throw exception(sstream() << "Could not find native implementation of external declaration '" << fn
<< "' (symbols '" << boxed_mangled.data() << "' or '" << mangled.data() << "').\n"
<< "For declarations from `Init`, `Std`, or `Lean`, you need to set `supportInterpreter := true` "
@ -1212,8 +1217,6 @@ extern "C" LEAN_EXPORT object * lean_run_init(object * env, object * opts, objec
}
void initialize_ir_interpreter() {
ir::g_boxed_mangled_suffix = new string_ref("___boxed");
mark_persistent(ir::g_boxed_mangled_suffix->raw());
ir::g_interpreter_prefer_native = new name({"interpreter", "prefer_native"});
ir::g_init_globals = new name_hash_map<object *>();
register_bool_option(*ir::g_interpreter_prefer_native, LEAN_DEFAULT_INTERPRETER_PREFER_NATIVE, "(interpreter) whether to use precompiled code where available");
@ -1236,6 +1239,5 @@ void finalize_ir_interpreter() {
});
delete ir::g_init_globals;
delete ir::g_interpreter_prefer_native;
delete ir::g_boxed_mangled_suffix;
}
}

View file

@ -1,15 +1,20 @@
module
import Lean.Compiler.IR.CompilerM
import Lean.Compiler.NameMangling
/-!
# Test behavior of name mangling
-/
def checkMangle (n : Lean.Name) (s : String) : IO Unit := do
open Lean IR ExplicitBoxing
def checkMangle (n : Name) (s : String) : IO Unit := do
if n.mangle "" ≠ s then
throw <| .userError s!"failed: {n} mangles to {n.mangle ""} but expected {s}"
if .demangle s ≠ n then
throw <| .userError s!"failed: {s} demangles to {Lean.Name.demangle s} but expected {n}"
if n ≠ .anonymous ∧ mkMangledBoxedName s ≠ (mkBoxedName n).mangle "" then
throw <| .userError s!"failed: {mkBoxedName n} mangles to {(mkBoxedName n).mangle ""} but \
mkMangledBoxedName produced {mkMangledBoxedName s}"
/-!
Mangling simple identifiers with optional number components and preceding underscores.