diff --git a/src/interop/interop_wrapper.cxx b/src/interop/interop_wrapper.cxx index a4c6b53..e366d24 100644 --- a/src/interop/interop_wrapper.cxx +++ b/src/interop/interop_wrapper.cxx @@ -666,6 +666,16 @@ interop::TCppScope_t interop::GetUnderlyingScope(TCppScope_t scope) { interop::TCppScope_t interop::GetScope(const std::string& name, TCppScope_t parent_scope) { std::lock_guard Lock(InterOpMutex); + // Resolving a templated name below declares a new variable in the + // interpreter (see AppendTypesSlow), so remember the result to not declare + // one, and grow the memory, on every lookup of the same name. + static std::map, TCppScope_t> template_scopes; + const bool is_template = name.find('<') != std::string::npos; + const auto key = std::make_pair(parent_scope.data, name); + if (is_template) + if (auto it = template_scopes.find(key); it != template_scopes.end()) + return it->second; + if (interop::TCppScope_t scope = Cpp::GetScope(name, parent_scope)) return scope; if (!parent_scope || parent_scope == Cpp::GetGlobalScope()) @@ -673,7 +683,7 @@ interop::TCppScope_t interop::GetScope(const std::string& name, return scope; // FIXME: avoid string parsing here - if (name.find('<') != std::string::npos) { + if (is_template) { // Templated type; may need instantiation. Resolve the whole type // expression (e.g. "std::array") and read back its scope. // Splitting off the argument list and resolving it directly cannot @@ -696,8 +706,10 @@ interop::TCppScope_t interop::GetScope(const std::string& name, // it, so the specialization may still be declared-but-undefined. // Force its definition: callers expect a complete scope, e.g. to // walk its base classes. - if (scope) + if (scope) { Cpp::IsComplete(scope); + template_scopes[key] = scope; + } return scope; } } diff --git a/test/test_regression.py b/test/test_regression.py index 40327ac..e7a2ceb 100644 --- a/test/test_regression.py +++ b/test/test_regression.py @@ -1772,3 +1772,23 @@ def test57_function_pointer_types_from_ast(self): # member function pointers are not function pointers; they are not # supported, but must not crash either ns.get_mf() + + def test58_repeated_template_scope_lookup(self): + """Binding to a templated class by name does not grow the memory""" + + from pytest import importorskip + + psutil = importorskip("psutil") + + import cppjit + + v = cppjit.gbl.std.vector[int]() + addr = cppjit.addressof(v) + process = psutil.Process() + cppjit.bind_object(addr, "std::vector") + before = process.memory_info().rss + for _ in range(2000): + assert type(cppjit.bind_object(addr, "std::vector")) is type(v) + # resolving the name used to declare code in the interpreter on every + # call, growing the memory by several kB each time + assert process.memory_info().rss - before < 4 * 1024 * 1024