feat: use compiler tracers for project indexing#764
Conversation
eb8fb1d to
d8aa44f
Compare
The main idea of this PR is that we can use Compiler Tracers for all compiled files like NextLS does. This allows us to index definitions and references *as the project compiles* rather than as a separate step. As a consequence, the indexer no longer goes over .ex files, as those are compilable, and indexes all .beam files instead if a compiler tracer missed them. Compiler tracers provide all the information we need: aliases, alias references, remote/imported functions/macros, local functions/macros, struct expansion, and definitions(via the module's compiled bytecode), which in principle should allow us to skip indexing from source code, or in the worst case scenario reduce the source analysis to range refinement, which is faster than full AST analysis. For non-compiled files like .exs scripts and tests, we still run the source code based indexer so we don't lose support for those. At a high level this works as follows: - File is saved, compilation is scheduled - The compiler emits tracer events, we pick them up and update both the index and the indexer manifest - The indexer runs, skips the .beams from files already analyzed via compiler tracer events, indexes the rest of the stale files if any If the index is missing but the project was already compiled, we will index from disk files instead: from .beam files for definitions since those are generally way more complete, and from the .ex sources for references. --- This introduces two different compiler tracers(just like NextLS). This is because mix compilation happens in two main stages: 1. Deps compilation via `deps.loadpaths` 2. Actual compilation of the user code This happens both in Expert and in regular `mix compile` runs, as `deps.loadpaths` is called internally by `mix compile`. We need to both run a compiler tracer for dependencies, AND to discriminate between dependency code and user code to decide wether or not to index references. Having a DependencyTracer that is configured only during `deps.loadpaths` and a ProjectTracer that is configured afterwards allows us to do both things. There is another quirk with `deps.loadpaths` as of elixir 1.20 where the compiler options will not get propagated to the compiler workers if `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` is `>1`, fixed in elixir's main branch with commit `https://github.com/elixir-lang/elixir/commit/6f18bf70c07b118dc434b3fd478b420071bc487f` and not yet released in a stable version. To workaround this issue, that env var is forced to `1` for the duration of the compilation. --- Later on for 0.4 we will want to investigate using the compiler locks to share the expert build with the user builds and save repeated work. That would be partially incompatible with compiler tracers because a user triggered build will not send the compiler tracer events to expert. In that case we want to also have the ability to index what's missing after compilation, and this PR should set the foundations for that. I'd like to know how this perform in real codebases too, as I see a noticeable slowdown in cold compilation(mostly the `deps.loadpaths` step), but I don't know how impactful this is on large codebases.
d8aa44f to
0c7992c
Compare
|
I think the issues are fixed now, in particular find references which was reported as not working: For all the above, go to definition also works. One reported issue was that references for modules and functions were not working. This new PR does not have commits that specifically fix those, so I'm not really sure what happened in that case and I can't reproduce the problem as you see above. I'd appreciate more eyes testing this! |
| defp timed(fun) when is_function(fun, 0) do | ||
| start = System.monotonic_time(:millisecond) | ||
| result = fun.() | ||
| {System.monotonic_time(:millisecond) - start, result} | ||
| end |
There was a problem hiding this comment.
isn't this just :timer.tc but with millis? Why not just use timed_log ?
|
Running this branch, I get "mix deps.loadpaths" as progress during deps compilation, not the actual files being compiled. |
|
again, there's a dramatic slowdown during compilation of our largest file. It's still taking ages. |








This is a re-publishing of #705, which was reverted due to issues we found after merging. You can read that PR for the original description of how this works.
Adds the following fixes on top of the previous attempt:
GenServer.castinstead ofGenServer.callin theTraceBuffer, this should make compilation faster by not serializing the work done with the tracer events. This was the original intent but I missed it in the original implementation, as the timing didn't noticeable change when testing it against expert itself.