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
15 changes: 8 additions & 7 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,6 @@ jobs:
node-target: win32-x64
rust-target: x86_64-pc-windows-gnu

# Verify that the compiler still builds with the oldest OCaml version we support.
- os: ubuntu-24.04
ocaml_compiler: ocaml-variants.4.14.2+options,ocaml-option-static
node-target: linux-x64
rust-target: x86_64-unknown-linux-musl

Comment on lines -61 to -66
Copy link
Member Author

Choose a reason for hiding this comment

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

This was discussed in Discord that it's OK for us to move to 5.3.0+.

runs-on: ${{matrix.os}}

env:
Expand Down Expand Up @@ -102,7 +96,7 @@ jobs:
uses: awalsh128/[email protected]
with:
# See https://github.com/ocaml/setup-ocaml/blob/b2105f9/packages/setup-ocaml/src/unix.ts#L9
Copy link
Preview

Copilot AI Sep 5, 2025

Choose a reason for hiding this comment

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

The addition of linux-libc-dev dpkg-dev packages should include a comment explaining why these specific packages are needed for the OCaml 5.3/Eio build requirements.

Copilot uses AI. Check for mistakes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Can you fix this Copilot?

packages: bubblewrap darcs g++-multilib gcc-multilib mercurial musl-tools rsync
packages: bubblewrap darcs g++-multilib gcc-multilib mercurial musl-tools rsync linux-libc-dev dpkg-dev
version: v3

- name: Restore rewatch build cache
Expand Down Expand Up @@ -179,6 +173,13 @@ jobs:
await fs.writeFile('.opam-path', opamPath, 'utf-8');
console.log('stored path to .opam-path');

- name: Configure Linux include paths
if: runner.os == 'Linux'
run: |
arch=$(dpkg-architecture -qDEB_HOST_MULTIARCH)
echo "C_INCLUDE_PATH=/usr/include:/usr/include/$arch" >> "$GITHUB_ENV"
echo "CPATH=/usr/include:/usr/include/$arch" >> "$GITHUB_ENV"

- name: Install OPAM dependencies
if: steps.cache-opam-env.outputs.cache-hit != 'true'
run: opam install . --deps-only --with-test
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Add support for ArrayBuffer and typed arrays to `@unboxed`. https://github.com/rescript-lang/rescript/pull/7788
- Experimental: Add `let?` syntax for unwrapping and propagating errors/none as early returns for option/result types. https://github.com/rescript-lang/rescript/pull/7582
- Add support for shipping features as experimental, including configuring what experimental features are enabled in `rescript.json`. https://github.com/rescript-lang/rescript/pull/7582
- Use multicore OCaml with Eio to speed up the `rename` and `find all references` commands. https://github.com/rescript-lang/rescript/pull/7840

#### :bug: Bug fix

Expand Down
4 changes: 3 additions & 1 deletion analysis.opam
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ license: "LGPL-3.0-or-later"
homepage: "https://github.com/rescript-lang/rescript-compiler"
bug-reports: "https://github.com/rescript-lang/rescript-compiler/issues"
depends: [
"ocaml" {>= "4.14"}
"ocaml" {>= "5.3"}
"cppo" {= "1.8.0"}
"eio"
"eio_main"
"dune" {>= "3.17"}
"odoc" {with-doc}
]
Expand Down
2 changes: 1 addition & 1 deletion analysis/bin/dune
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
(package analysis)
(modes byte exe)
(name main)
(libraries analysis))
(libraries analysis eio eio_main))
18 changes: 10 additions & 8 deletions analysis/bin/main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ let main () =
| [_; "cache-delete"; rootPath] -> (
Cfg.readProjectConfigCache := false;
let uri = Uri.fromPath rootPath in
match Packages.findRoot ~uri (Hashtbl.create 0) with
match Packages.findRoot ~uri with
| Some (`Bs rootPath) -> (
match BuildSystem.getLibBs rootPath with
| None -> print_endline "\"ERR\""
Expand Down Expand Up @@ -194,21 +194,23 @@ let main () =
Sys.argv.(len - 1) <- "";
Reanalyze.cli ()
| [_; "references"; path; line; col] ->
Commands.references ~path
~pos:(int_of_string line, int_of_string col)
~debug
Eio_main.run (fun env ->
Commands.references ~env ~path
Copy link
Collaborator

Choose a reason for hiding this comment

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

why does references need to take an env now and not before?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because it's using Eio, and that requires the Eio env.

~pos:(int_of_string line, int_of_string col)
~debug)
| [_; "rename"; path; line; col; newName] ->
Commands.rename ~path
~pos:(int_of_string line, int_of_string col)
~newName ~debug
Eio_main.run (fun env ->
Commands.rename ~env ~path
~pos:(int_of_string line, int_of_string col)
~newName ~debug)
| [_; "semanticTokens"; currentFile] ->
SemanticTokens.semanticTokens ~currentFile
| [_; "createInterface"; path; cmiFile] ->
Printf.printf "\"%s\""
(Json.escape (CreateInterface.command ~path ~cmiFile))
| [_; "format"; path] ->
Printf.printf "\"%s\"" (Json.escape (Commands.format ~path))
| [_; "test"; path] -> Commands.test ~path
| [_; "test"; path] -> Eio_main.run (fun env -> Commands.test ~env ~path)
Copy link
Member

Choose a reason for hiding this comment

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

This is used because reference tests require the env now right?
The tests themselves are still sequential, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

The specific tests of commands that use Eio will still use it in the tests as well, but yeah, the tests themselves are sequential.

| [_; "cmt"; rescript_json; cmt_path] -> CmtViewer.dump rescript_json cmt_path
| args when List.mem "-h" args || List.mem "--help" args -> prerr_endline help
| _ ->
Expand Down
7 changes: 7 additions & 0 deletions analysis/src/AnalysisCache.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
(* Helpers for domain-local caches *)
Copy link
Member

Choose a reason for hiding this comment

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

Any links to what a domain-local cache even is?

Copy link
Member Author

Choose a reason for hiding this comment

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

Domains in Eio are essentially threads but managed by Eio. So, a domain local cache is a cache that's valid just for a thread (domain).

Previously, everything was sequential and within a single thread. We can now use multiple threads (domains) for parallelizing work, but that means interacting with the cache isn't safe if it's shared between all threads (races etc). So, by doing domain local caches, we dodge that issue.

For the exact tasks in this PR (rename and find references), the cache doesn't matter that much I suspect. But since it was already in place, and it's used in a bunch of other places (where we don't use Eio yet, and maybe won't ever do), it made sense to make them domain specific.

This should be "backwards compatible" too from what I understand, in that the cache will work the same way as it did before for the non-Eio stuff.

Copy link
Member

Choose a reason for hiding this comment

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

Thanks a bunch for explaining this!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Domains in Eio are essentially threads but managed by Eio. So, a domain local cache is a cache that's valid just for a thread (domain).

Previously, everything was sequential and within a single thread. We can now use multiple threads (domains) for parallelizing work, but that means interacting with the cache isn't safe if it's shared between all threads (races etc). So, by doing domain local caches, we dodge that issue.

For the exact tasks in this PR (rename and find references), the cache doesn't matter that much I suspect. But since it was already in place, and it's used in a bunch of other places (where we don't use Eio yet, and maybe won't ever do), it made sense to make them domain specific.

This should be "backwards compatible" too from what I understand, in that the cache will work the same way as it did before for the non-Eio stuff.

Can you try something: create intentionally some shared mutable state and use domain local cache and check that they don't interfere.
Maybe in a file of only a few lines.


let make_hashtbl (size : int) : ('k, 'v) Hashtbl.t Domain.DLS.key =
Domain.DLS.new_key (fun () -> Hashtbl.create size)

let get_hashtbl (key : ('k, 'v) Hashtbl.t Domain.DLS.key) : ('k, 'v) Hashtbl.t =
Domain.DLS.get key
82 changes: 54 additions & 28 deletions analysis/src/Cmt.ml
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
open SharedTypes

(* Caches for the `full` type *)
module FullCache = struct
let key : (string, full) Hashtbl.t Domain.DLS.key =
AnalysisCache.make_hashtbl 64

let get () : (string, full) Hashtbl.t = AnalysisCache.get_hashtbl key
end

let fullForCmt ~moduleName ~package ~uri cmt =
match Shared.tryReadCmt cmt with
| None -> None
Expand All @@ -8,45 +16,63 @@ let fullForCmt ~moduleName ~package ~uri cmt =
let extra = ProcessExtra.getExtra ~file ~infos in
Some {file; extra; package}

let fullFromUri ~uri =
let fullFromUriWithPackage ~package ~uri =
let path = Uri.toPath uri in
match Packages.getPackage ~uri with
| None -> None
| Some package -> (
let moduleName =
BuildSystem.namespacedName package.namespace (FindFiles.getName path)
in
let incremental =
if !Cfg.inIncrementalTypecheckingMode then
let incrementalCmtPath =
package.rootPath ^ "/lib/bs/___incremental" ^ "/" ^ moduleName
^
match Files.classifySourceFile path with
| Resi -> ".cmti"
| _ -> ".cmt"
in
fullForCmt ~moduleName ~package ~uri incrementalCmtPath
else None
let moduleName =
BuildSystem.namespacedName package.namespace (FindFiles.getName path)
in
let cached_full cmt_path =
let cache = FullCache.get () in
match Hashtbl.find_opt cache cmt_path with
| Some v -> Some v
| None -> (
match fullForCmt ~moduleName ~package ~uri cmt_path with
| Some v as res ->
Hashtbl.replace cache cmt_path v;
res
| None -> None)
in
if !Cfg.inIncrementalTypecheckingMode then
let incrementalCmtPath =
package.rootPath ^ "/lib/bs/___incremental" ^ "/" ^ moduleName
^
match Files.classifySourceFile path with
| Resi -> ".cmti"
| _ -> ".cmt"
in
match incremental with
| Some cmtInfo ->
if Debug.verbose () then Printf.printf "[cmt] Found incremental cmt\n";
Some cmtInfo
match cached_full incrementalCmtPath with
| Some x -> Some x
| None -> (
match Hashtbl.find_opt package.pathsForModule moduleName with
| Some paths ->
let cmt = getCmtPath ~uri paths in
fullForCmt ~moduleName ~package ~uri cmt
cached_full cmt
| None ->
prerr_endline ("can't find module " ^ moduleName);
None))
None)
else
match Hashtbl.find_opt package.pathsForModule moduleName with
| Some paths ->
let cmt = getCmtPath ~uri paths in
cached_full cmt
| None ->
prerr_endline ("can't find module " ^ moduleName);
None

let fullFromUri ~uri =
match Packages.getPackage ~uri with
| None -> None
| Some package -> fullFromUriWithPackage ~package ~uri

let fullsFromModule ~package ~moduleName =
if Hashtbl.mem package.pathsForModule moduleName then
let paths = Hashtbl.find package.pathsForModule moduleName in
match Hashtbl.find_opt package.pathsForModule moduleName with
| None -> []
| Some paths ->
let uris = getUris paths in
uris |> List.filter_map (fun uri -> fullFromUri ~uri)
else []
uris
|> List.filter_map (fun uri ->
let cmt = getCmtPath ~uri paths in
fullForCmt ~moduleName ~package ~uri cmt)

let loadFullCmtFromPath ~path =
let uri = Uri.fromPath path in
Expand Down
Loading