Skip to content

Fixes to reflection cache #381

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open

Conversation

tunabrain
Copy link
Collaborator

This is a (very simple!) fix to a reflection caching bug that causes a range of convoluted bugs and weird behavior.

Anytime we look up a function, whether by name or by reflection, we construct a new cache key representing the function name and save the reflection for future lookups and hot reloads.

This causes a few problems:

  1. The cache key we build can be different from the name used to look up the function, e.g. for specialized generics. This makes SlangPy act differently depending on your history of function calls. For the function int foo<int X>() { return X; }, we can look up the specialized function "foo<5>". SlangPy checks the cache for prior lookups of "foo<5>", but saves it in the cache under the name "foo". This means lookups for specialized generics never hit the cache.
    Even weirder: Calling foo() directly is not allowed, since X can't be inferred. But if you called or looked up "foo<5>" at some earlier point, calling foo() now suddenly succeeds, because the cache returns the function foo<5> instead when you look up "foo".
  2. We sometimes cache reflection objects by name that can't actually be looked up by name: E.g. for an overloaded function, it's specializations can't be retrieved by name and they shouldn't be cached. For void foo(int x) {} void foo(int2 x) {}, reflecting "foo" returns an overloaded reflection object. As a result of how the reflection API handles these, SlangPy instead caches this lookup under None. OTOH, when slangpy specializes the function to get a specific overload (e.g. foo(int)), this one is cached under "foo". From then on, looking up the function "foo" by name returns that specific specialization, not the overloaded function, and trying to call e.g. foo(int2) after that could fail. It's surprising this hasn't caused issues before - our savior here is that the Module keeps a separate _attr_cache and avoids hitting the (incorrect) reflection cache most of the time.
  3. As a result of 2), if you ever reflected an overloaded function, hot reload crashes, as it will try to re-lookup a None function name.

The fix is very simple, and moves the by-name-caching into the functions that look up by name, and caches under the exact name used to query the reflection API.

@tunabrain tunabrain requested a review from a team as a code owner July 28, 2025 22:07
Copy link
Contributor

@ccummingsNV ccummingsNV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one does make me a bit nervous, as I went through a lot of combinations of working out how to cache these things. If you're confident it's the right fix though, go for it.

@tunabrain
Copy link
Collaborator Author

If there are sanity checks you would want to run, or more extensive tests you can think of that would make you feel less nervous, I'm happy to add them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants