Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 35 additions & 1 deletion src/interop/interop_wrapper.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,36 @@ bool interop::AppendTypesSlow(const std::string& name,

std::lock_guard<std::recursive_mutex> Lock(InterOpMutex);

// Every lookup below declares code through the interpreter, which parses,
// commits a transaction and leaves a new declaration in the AST, so each one
// costs more than the last. The same names come back constantly (overload
// ranking resolves every argument type of every candidate on each call that
// is not memoized), so remember what a name resolved to. A name that fails
// may still resolve once more declarations are available, so only successes
// are cached.
using SlowTypesKey = std::pair<std::string, const void*>;
static std::map<SlowTypesKey, std::vector<Cpp::TemplateArgInfo>> s_cache;
const SlowTypesKey cache_key{name, parent.data};
// the integral values are strings whose ownership passes to the caller
// (which frees them), so the cache keeps and hands out copies of its own
auto copy_of = [](const Cpp::TemplateArgInfo& info) {
return Cpp::TemplateArgInfo(info.m_Type, info.m_IntegralValue
? strdup(info.m_IntegralValue)
: nullptr);
};
if (auto cached = s_cache.find(cache_key); cached != s_cache.end()) {
for (const Cpp::TemplateArgInfo& info : cached->second)
types.push_back(copy_of(info));
return false;
}
const size_t n_types_before = types.size();
auto remember = [&] {
std::vector<Cpp::TemplateArgInfo> resolved;
for (size_t i = n_types_before; i < types.size(); ++i)
resolved.push_back(copy_of(types[i]));
s_cache.emplace(cache_key, std::move(resolved));
};

// We might have an entire expression such as int, double.
static unsigned long long struct_count = 0;
std::string code =
Expand Down Expand Up @@ -554,7 +584,10 @@ bool interop::AppendTypesSlow(const std::string& name,
TCppScope_t instance_class = Cpp::GetScopeFromType(varN);
size_t oldSize = types.size();
Cpp::GetClassTemplateInstantiationArgs(instance_class, types);
return oldSize == types.size();
if (oldSize == types.size())
return true;
remember();
return false;
}
}

Expand Down Expand Up @@ -593,6 +626,7 @@ bool interop::AppendTypesSlow(const std::string& name,
strdup(std::to_string(Cpp::GetEnumConstantValue(scope)).c_str());
types.emplace_back(type.data, integral_value);
}
remember();
return false;
}

Expand Down
20 changes: 20 additions & 0 deletions test/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -1727,3 +1727,23 @@ def test56_enum_arg_typedefed_underlying_type(self):

assert ns.pick(ns.S.A) == 1
assert ns.pick(ns.S.B) == 5

def test57_repeated_enum_constant_template_argument(self):
"""A template argument spelled as an enum constant resolves repeatedly"""

import cppjit

cppjit.cppdef("""\
namespace RepeatedEnumArg {
enum E { A = 1, B = 2 };
template <E e> struct T1 { static constexpr int v = (int)e; };
template <E e> struct T2 { static constexpr int v = (int)e; };
}""")

ns = cppjit.gbl.RepeatedEnumArg

# the resolved argument is memoized by name; its integral value is a
# string the first instantiation frees, so the second needs its own
assert ns.T1["RepeatedEnumArg::E::A"].v == 1
assert ns.T2["RepeatedEnumArg::E::A"].v == 1
assert ns.T2["RepeatedEnumArg::E::B"].v == 2
Loading