Skip to content
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

feat: incremental compilation on hot reloading #49

Merged
merged 5 commits into from
Nov 22, 2019
Merged
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
14 changes: 8 additions & 6 deletions crates/mun/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::time::Duration;

use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
use mun_abi::Reflection;
use mun_compiler::PathOrInline;
use mun_compiler::{host_triple, Config, PathOrInline, Target};
use mun_runtime::{invoke_fn, Runtime, RuntimeBuilder};

fn main() -> Result<(), failure::Error> {
Expand Down Expand Up @@ -77,9 +77,9 @@ fn main() -> Result<(), failure::Error> {
fn build(matches: &ArgMatches) -> Result<(), failure::Error> {
let options = compiler_options(matches)?;
if matches.is_present("watch") {
mun_compiler_daemon::main(&options)
mun_compiler_daemon::main(options)
} else {
mun_compiler::main(&options).map(|_| {})
mun_compiler::main(options).map(|_| {})
}
}

Expand Down Expand Up @@ -136,9 +136,11 @@ fn compiler_options(matches: &ArgMatches) -> Result<mun_compiler::CompilerOption

Ok(mun_compiler::CompilerOptions {
input: PathOrInline::Path(matches.value_of("INPUT").unwrap().into()), // Safe because its a required arg
target: matches.value_of("target").map(|t| t.to_string()),
optimization_lvl,
out_dir: None,
config: Config {
target: Target::search(matches.value_of("target").unwrap_or_else(|| host_triple()))?,
optimization_lvl,
out_dir: None,
},
})
}

Expand Down
19 changes: 13 additions & 6 deletions crates/mun_codegen/src/mock.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::{IrDatabase, OptimizationLevel};
use mun_hir::SourceDatabase;
use mun_hir::{FileId, PackageInput, RelativePathBuf};
use mun_hir::{FileId, RelativePathBuf};
use mun_hir::{SourceDatabase, SourceRoot, SourceRootId};
use std::sync::Arc;

/// A mock implementation of the IR database. It can be used to set up a simple test case.
Expand All @@ -25,12 +25,19 @@ impl MockDatabase {
/// Creates a database from the given text.
pub fn with_single_file(text: &str) -> (MockDatabase, FileId) {
let mut db: MockDatabase = Default::default();

let mut source_root = SourceRoot::default();
let source_root_id = SourceRootId(0);

let text = Arc::new(text.to_owned());
let rel_path = RelativePathBuf::from("main.mun");
let file_id = FileId(0);
db.set_file_relative_path(file_id, RelativePathBuf::from("main.mun"));
db.set_file_relative_path(file_id, rel_path.clone());
db.set_file_text(file_id, Arc::new(text.to_string()));
let mut package_input = PackageInput::default();
package_input.add_module(file_id);
db.set_package_input(Arc::new(package_input));
db.set_file_source_root(file_id, source_root_id);
source_root.insert_file(rel_path, file_id);

db.set_source_root(source_root_id, Arc::new(source_root));
db.set_optimization_lvl(OptimizationLevel::Default);

let context = crate::Context::create();
Expand Down
7 changes: 1 addition & 6 deletions crates/mun_codegen/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,12 +310,7 @@ fn test_snapshot(text: &str) {
diag.message()
));
});
if let Some(module) = Module::package_modules(&db)
.iter()
.find(|m| m.file_id() == file_id)
{
module.diagnostics(&db, &mut sink)
}
Module::from(file_id).diagnostics(&db, &mut sink);
drop(sink);
let messages = messages.into_inner();

Expand Down
26 changes: 26 additions & 0 deletions crates/mun_compiler/src/db.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use mun_hir::salsa;

#[salsa::database(
mun_hir::SourceDatabaseStorage,
mun_hir::DefDatabaseStorage,
mun_hir::HirDatabaseStorage,
mun_codegen::IrDatabaseStorage
)]
#[derive(Debug)]
pub(crate) struct CompilerDatabase {
runtime: salsa::Runtime<CompilerDatabase>,
}

impl CompilerDatabase {
pub fn new() -> Self {
CompilerDatabase {
runtime: salsa::Runtime::default(),
}
}
}

impl salsa::Database for CompilerDatabase {
fn salsa_runtime(&self) -> &salsa::Runtime<CompilerDatabase> {
&self.runtime
}
}
89 changes: 89 additions & 0 deletions crates/mun_compiler/src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use mun_hir::diagnostics::{Diagnostic as HirDiagnostic, DiagnosticSink};
use mun_hir::{FileId, HirDatabase, HirDisplay, Module};
use mun_syntax::{ast, AstNode, SyntaxKind};
use std::cell::RefCell;

mod emit;

pub use emit::Emit;
use mun_errors::{Diagnostic, Level};

/// Constructs diagnostic messages for the given file.
pub fn diagnostics(db: &impl HirDatabase, file_id: FileId) -> Vec<Diagnostic> {
let parse = db.parse(file_id);
let mut result = Vec::new();

result.extend(parse.errors().iter().map(|err| Diagnostic {
level: Level::Error,
loc: err.location(),
message: format!("Syntax Error: {}", err),
}));

let result = RefCell::new(result);
let mut sink = DiagnosticSink::new(|d| {
result.borrow_mut().push(Diagnostic {
level: Level::Error,
loc: d.highlight_range().into(),
message: d.message(),
});
})
.on::<mun_hir::diagnostics::UnresolvedValue, _>(|d| {
let text = d.expr.to_node(&parse.tree().syntax()).text().to_string();
result.borrow_mut().push(Diagnostic {
level: Level::Error,
loc: d.highlight_range().into(),
message: format!("could not find value `{}` in this scope", text),
});
})
.on::<mun_hir::diagnostics::UnresolvedType, _>(|d| {
let text = d
.type_ref
.to_node(&parse.tree().syntax())
.syntax()
.text()
.to_string();
result.borrow_mut().push(Diagnostic {
level: Level::Error,
loc: d.highlight_range().into(),
message: format!("could not find type `{}` in this scope", text),
});
})
.on::<mun_hir::diagnostics::ExpectedFunction, _>(|d| {
result.borrow_mut().push(Diagnostic {
level: Level::Error,
loc: d.highlight_range().into(),
message: format!("expected function, found `{}`", d.found.display(db)),
});
})
.on::<mun_hir::diagnostics::MismatchedType, _>(|d| {
result.borrow_mut().push(Diagnostic {
level: Level::Error,
loc: d.highlight_range().into(),
message: format!(
"expected `{}`, found `{}`",
d.expected.display(db),
d.found.display(db)
),
});
})
.on::<mun_hir::diagnostics::DuplicateDefinition, _>(|d| {
result.borrow_mut().push(Diagnostic {
level: Level::Error,
loc: match d.definition.kind() {
SyntaxKind::FUNCTION_DEF => {
ast::FunctionDef::cast(d.definition.to_node(&parse.tree().syntax()))
.map(|f| f.signature_range())
.unwrap_or_else(|| d.highlight_range())
.into()
}
_ => d.highlight_range().into(),
},
message: d.message(),
});
});

Module::from(file_id).diagnostics(db, &mut sink);

drop(sink);
result.into_inner()
}
157 changes: 157 additions & 0 deletions crates/mun_compiler/src/driver.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
//! `Driver` is a stateful compiler frontend that enables incremental compilation by retaining state
//! from previous compilation.

use crate::{
db::CompilerDatabase,
diagnostics::{diagnostics, Emit},
PathOrInline,
};
use mun_codegen::IrDatabase;
use mun_hir::{FileId, RelativePathBuf, SourceDatabase, SourceRoot, SourceRootId};
use mun_target::spec::Target;
use std::{
path::{Path, PathBuf},
sync::Arc,
};

mod config;

pub use self::config::Config;
use mun_errors::{Diagnostic, Level};
use termcolor::WriteColor;

pub const WORKSPACE: SourceRootId = SourceRootId(0);

#[derive(Debug)]
pub struct Driver {
db: CompilerDatabase,
out_dir: Option<PathBuf>,
}

impl Driver {
/// Constructs a driver with a specific configuration.
pub fn with_config(config: Config) -> Self {
let mut driver = Driver {
db: CompilerDatabase::new(),
out_dir: None,
};

// Move relevant configuration into the database
driver.db.set_target(config.target);
driver
.db
.set_context(Arc::new(mun_codegen::Context::create()));
driver.db.set_optimization_lvl(config.optimization_lvl);

driver.out_dir = config.out_dir;

driver
}

/// Constructs a driver with a configuration and a single file.
pub fn with_file(
config: Config,
path: PathOrInline,
) -> Result<(Driver, FileId), failure::Error> {
let mut driver = Driver::with_config(config);

// Construct a SourceRoot
let mut source_root = SourceRoot::default();

// Get the path and contents of the path
let (rel_path, text) = match path {
PathOrInline::Path(p) => {
let filename = p.file_name().ok_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Input path is missing a filename.",
)
})?;
(
RelativePathBuf::from_path(filename).unwrap(),
std::fs::read_to_string(p)?,
)
}
PathOrInline::Inline { rel_path, contents } => (rel_path, contents),
};

// Store the file information in the database together with the source root
let file_id = FileId(0);
driver.db.set_file_relative_path(file_id, rel_path.clone());
driver.db.set_file_text(file_id, Arc::new(text));
driver.db.set_file_source_root(file_id, WORKSPACE);
source_root.insert_file(rel_path, file_id);
driver.db.set_source_root(WORKSPACE, Arc::new(source_root));

Ok((driver, file_id))
}
}

impl Driver {
/// Sets the contents of a specific file.
pub fn set_file_text<T: AsRef<str>>(&mut self, file_id: FileId, text: T) {
self.db
.set_file_text(file_id, Arc::new(text.as_ref().to_owned()));
}
}

impl Driver {
/// Returns a vector containing all the diagnostic messages for the project.
pub fn diagnostics(&self) -> Vec<Diagnostic> {
self.db
.source_root(WORKSPACE)
.files()
.map(|f| diagnostics(&self.db, f))
.flatten()
.collect()
}

/// Emits all diagnostic messages currently in the database; returns true if errors were
/// emitted.
pub fn emit_diagnostics(&self, writer: &mut impl WriteColor) -> Result<bool, failure::Error> {
let mut has_errors = false;
for file_id in self.db.source_root(WORKSPACE).files() {
let diags = diagnostics(&self.db, file_id);
for diagnostic in diags.iter() {
diagnostic.emit(writer, &self.db, file_id)?;
if diagnostic.level == Level::Error {
has_errors = true;
}
}
}
Ok(has_errors)
}
}

impl Driver {
/// Computes the output path for the assembly of the specified file.
fn assembly_output_path(&self, file_id: FileId) -> PathBuf {
let target: Target = self.db.target();
let relative_path: RelativePathBuf = self.db.file_relative_path(file_id);
let original_filename = Path::new(relative_path.file_name().unwrap());

// Get the dll suffix without the starting dot
let dll_extension = if target.options.dll_suffix.starts_with('.') {
&target.options.dll_suffix[1..]
} else {
&target.options.dll_suffix
};

// Add the dll suffix to the original filename
let output_file_name = original_filename.with_extension(dll_extension);

// If there is an out dir specified, prepend the output directory
if let Some(ref out_dir) = self.out_dir {
out_dir.join(output_file_name)
} else {
output_file_name
}
}

/// Generate an assembly for the given file
pub fn write_assembly(&self, file_id: FileId) -> Result<Option<PathBuf>, failure::Error> {
let output_path = self.assembly_output_path(file_id);
mun_codegen::write_module_shared_object(&self.db, file_id, &output_path)?;
Ok(Some(output_path))
}
}
31 changes: 31 additions & 0 deletions crates/mun_compiler/src/driver/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::host_triple;
use mun_codegen::OptimizationLevel;
use mun_target::spec::Target;
use std::path::PathBuf;

/// Describes all the permanent settings that are used during compilations.
#[derive(Debug, Clone)]
pub struct Config {
/// The target triple to compile the code for.
pub target: Target,

/// The optimization level to use for the IR generation.
pub optimization_lvl: OptimizationLevel,

/// The optional output directory to store all outputs. If no directory is specified all output
/// is stored in a temporary directory.
pub out_dir: Option<PathBuf>,
}

impl Default for Config {
fn default() -> Self {
let target = Target::search(&host_triple());
Config {
// This unwrap is safe because we only compile for targets that have an implemented host
// triple.
target: target.unwrap(),
optimization_lvl: OptimizationLevel::Default,
out_dir: None,
}
}
}
Loading