Fork of daniellemaywood.uk/gleam — Wasm codegen work
5.9 kB
180 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 The Gleam contributors
3
4#[cfg(test)]
5mod tests;
6
7use std::{collections::HashSet, time::SystemTime};
8
9use camino::{Utf8Path, Utf8PathBuf};
10
11use ecow::EcoString;
12use serde::{Deserialize, Serialize};
13
14use super::{
15 Mode, Origin, SourceFingerprint, Target,
16 package_compiler::{CacheMetadata, CachedModule, Input, UncompiledModule},
17 package_loader::{CodegenRequired, GleamFile},
18};
19use crate::{
20 Error, Result,
21 error::{FileIoAction, FileKind},
22 io::{CommandExecutor, FileSystemReader, FileSystemWriter},
23 warning::{TypeWarningEmitter, WarningEmitter},
24};
25
26#[derive(Debug)]
27pub(crate) struct ModuleLoader<'a, IO> {
28 pub io: IO,
29 pub warnings: &'a WarningEmitter,
30 pub mode: Mode,
31 pub target: Target,
32 pub codegen: CodegenRequired,
33 pub package_name: &'a EcoString,
34 pub artefact_directory: &'a Utf8Path,
35 pub origin: Origin,
36 /// The set of modules that have had partial compilation done since the last
37 /// successful compilation.
38 pub incomplete_modules: &'a HashSet<EcoString>,
39}
40
41impl<'a, IO> ModuleLoader<'a, IO>
42where
43 IO: FileSystemReader + FileSystemWriter + CommandExecutor + Clone,
44{
45 /// Load a module from the given path.
46 ///
47 /// If the module has been compiled before and the source file has not been
48 /// changed since then, load the precompiled data instead.
49 ///
50 /// Whether the module has changed or not is determined by comparing the
51 /// modification time of the source file with the value recorded in the
52 /// `.timestamp` file in the artefact directory.
53 pub fn load(&self, file: GleamFile) -> Result<Input> {
54 let name = file.module_name.clone();
55 let source_mtime = self.io.modification_time(&file.path)?;
56
57 let read_source = |name| self.read_source(file.path.clone(), name, source_mtime);
58
59 let meta = match self.read_cache_metadata(&file)? {
60 Some(meta) => meta,
61 None => return read_source(name).map(Input::New),
62 };
63
64 // The cache currently does not contain enough data to perform codegen,
65 // so if codegen is required in this compiler run then we must check
66 // that codegen has already been performed before using a cache.
67 if self.codegen.is_required() && !meta.codegen_performed {
68 tracing::debug!(?name, "codegen_required_cache_insufficient");
69 return read_source(name).map(Input::New);
70 }
71
72 // If the timestamp of the source is newer than the cache entry and
73 // the hash of the source differs from the one in the cache entry,
74 // then we need to recompile.
75 if meta.mtime < source_mtime {
76 let source_module = read_source(name.clone())?;
77 if meta.fingerprint != SourceFingerprint::new(&source_module.code) {
78 tracing::debug!(?name, "cache_stale");
79 return Ok(Input::New(source_module));
80 } else if self.mode == Mode::Lsp && self.incomplete_modules.contains(&name) {
81 // Since the lsp can have valid but incorrect intermediate code states between
82 // successful compilations, we need to invalidate the cache even if the fingerprint matches
83 tracing::debug!(?name, "cache_stale for lsp");
84 return Ok(Input::New(source_module));
85 }
86 }
87
88 Ok(Input::Cached(self.cached(file, meta)))
89 }
90
91 /// Read the cache metadata file from the artefact directory for the given
92 /// source file. If the file does not exist, return `None`.
93 fn read_cache_metadata(&self, source_file: &GleamFile) -> Result<Option<CacheMetadata>> {
94 let meta_path = source_file.cache_files(&self.artefact_directory).meta_path;
95
96 if !self.io.is_file(&meta_path) {
97 return Ok(None);
98 }
99
100 let binary = self.io.read_bytes(&meta_path)?;
101 let cache_metadata = CacheMetadata::from_binary(&binary).map_err(|e| -> Error {
102 Error::FileIo {
103 action: FileIoAction::Parse,
104 kind: FileKind::File,
105 path: meta_path,
106 err: Some(e),
107 }
108 })?;
109 Ok(Some(cache_metadata))
110 }
111
112 fn read_source(
113 &self,
114 path: Utf8PathBuf,
115 name: EcoString,
116 mtime: SystemTime,
117 ) -> Result<UncompiledModule, Error> {
118 read_source(
119 self.io.clone(),
120 self.target,
121 self.origin,
122 path,
123 name,
124 self.package_name.clone(),
125 mtime,
126 self.warnings.clone(),
127 )
128 }
129
130 fn cached(&self, file: GleamFile, meta: CacheMetadata) -> CachedModule {
131 CachedModule {
132 dependencies: meta.dependencies,
133 source_path: file.path,
134 origin: self.origin,
135 name: file.module_name,
136 line_numbers: meta.line_numbers,
137 }
138 }
139}
140
141pub(crate) fn read_source<IO>(
142 io: IO,
143 target: Target,
144 origin: Origin,
145 path: Utf8PathBuf,
146 name: EcoString,
147 package_name: EcoString,
148 mtime: SystemTime,
149 emitter: WarningEmitter,
150) -> Result<UncompiledModule>
151where
152 IO: FileSystemReader + FileSystemWriter + CommandExecutor + Clone,
153{
154 let code: EcoString = io.read(&path)?.into();
155
156 let parsed = crate::parse::parse_module(path.clone(), &code, &emitter).map_err(|error| {
157 Error::Parse {
158 path: path.clone(),
159 src: code.clone(),
160 error: Box::new(error),
161 }
162 })?;
163 let mut ast = parsed.module;
164 let extra = parsed.extra;
165 let dependencies = ast.dependencies(target);
166
167 ast.name = name.clone();
168 let module = UncompiledModule {
169 package: package_name,
170 dependencies,
171 origin,
172 extra,
173 mtime,
174 path,
175 name,
176 code,
177 ast,
178 };
179 Ok(module)
180}