close
Skip to content

fix(compiler): Correct parsetree caching behaviour#2280

Merged
ospencer merged 1 commit into
grain-lang:mainfrom
spotandjake:spotandjake-parsetree-cache
Feb 8, 2026
Merged

fix(compiler): Correct parsetree caching behaviour#2280
ospencer merged 1 commit into
grain-lang:mainfrom
spotandjake:spotandjake-parsetree-cache

Conversation

@spotandjake
Copy link
Copy Markdown
Member

@spotandjake spotandjake commented Apr 19, 2025

This corrects the behaviour of parsetree caching, we noticed the other day that the lsp wasn't updating prompting #2267. This corrects the root source of that bug by validating the source hashes match when we are caching.

I think with this change we would be safe to remove the compiler reset allowing the lsp to run a little faster however I don't want todo that in this pr as I think we still need to validate the behaviour of the other things we don't reset DependencyGraph and Env, Ctype levels, Warnings, FS_Access, testing locally everything was working again though.

Note: After I made this pr I realized we use file_older in the dependencytree to check if something is dirty I'm wondering if instead of my source hashing semantics it would be better to use that api but I would like a bit of feedback before I go and do that.

@spotandjake spotandjake self-assigned this Apr 19, 2025
@spotandjake spotandjake changed the title fix(grainc): Correct cache parsetree cache behaviour fix(grainc): Correct parsetree caching behaviour Apr 19, 2025
Option.fold(~none=None, ~some=Hashtbl.find_opt(cached_parsetrees), name)
) {
| Some((cached_source, cached_program))
when cached_source == Hashtbl.hash(source) =>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Hashing the entire parsetree is super expensive. I would use file_older or some other mechanism to verify that the file hasn't changed and that the cache is valid.

Copy link
Copy Markdown
Member Author

@spotandjake spotandjake Apr 23, 2025

Choose a reason for hiding this comment

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

Hashing the entire parsetree is super expensive. I would use file_older or some other mechanism to verify that the file hasn't changed and that the cache is valid.

I'm only hashing the source which is just hashing a string right? I am not against using file_older though but I don't think that would work here because of compile_string caching.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That's not as bad; I thought it was hashing the AST.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nope I am essentially just using the source hash to validate if the contents of the name has changed and we should be using a new parsetree rather than the cache.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think it would be useful to test this with a large file (maybe like 25k-50k lines) and make sure the hashing isn't really noticeable compared to the parsing. Also, we should make an issue to only keep a number of cached parsetrees, because this is basically a memory leak in its current form.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We do not. They're both O(n), but remember that runtime complexity just describes how performance of the algorithm grows with more input, not actual running. You can have two O(n) algorithms, but one might take 20x longer than the other one. Both being O(n) just means that with more data, that one will still only take 20x longer than the other one.

Strings are easy to hash and have quick, optimized algorithms, whereas hashing a big data structure requires chasing a bunch of pointers, offset calculations, etc. ASTs are also a lot more data than the source strings.

Copy link
Copy Markdown
Member Author

@spotandjake spotandjake Oct 29, 2025

Choose a reason for hiding this comment

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

Coming back to this, do you think it would be acceptable to test with Unix.time() and just ensure the time taken in A is less than B?

My concern with this approach is if the times are too close, a cpu hickup could cause our tests to flake.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Apologies if you thought I meant I wanted perf tests in the test suite. I just want benchmarks that you ran on your machine and you report back on the numbers.

Copy link
Copy Markdown
Member Author

@spotandjake spotandjake Feb 5, 2026

Choose a reason for hiding this comment

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

The results from testing this are below, it seems that it's way faster to have the cache, the top image is with the caching logic implemented and the bottom is without the caching logic enabled.

The reason I mentioned the O(n) above was just a note on scaling that if the cache is that much faster for smaller files or larger files it should scale relatively the same.

Screenshot 2026-02-05 at 2 59 08 PM Screenshot 2026-02-05 at 3 00 12 PM

I agree that we should open an issue about leaking parsetrees I think when we delete them though, is a complicated question. We may also want to consider skipping the hash altogether for short inputs, we could do a very quick check of the byte length of the string, and if it's under a threshold, we just don't cache. I'll open that issue whenever this is ready to be merged.

As just one more note, I think we should avoid using file_older where possible to try and reduce the compilers dependence on a file system.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for checking!

@spotandjake spotandjake force-pushed the spotandjake-parsetree-cache branch from 7cb911d to 6d70250 Compare February 5, 2026 19:43
@ospencer ospencer changed the title fix(grainc): Correct parsetree caching behaviour fix(compiler): Correct parsetree caching behaviour Feb 8, 2026
@ospencer ospencer added this pull request to the merge queue Feb 8, 2026
Merged via the queue into grain-lang:main with commit 5f3f54d Feb 8, 2026
12 checks passed
@github-actions github-actions Bot mentioned this pull request Feb 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants