Skip to content

Commit

Permalink
refactor(codegen): introduces Inkwell lifetimes
Browse files Browse the repository at this point in the history
  • Loading branch information
baszalmstra committed Aug 23, 2020
1 parent 7bc27ba commit 97f4ca8
Show file tree
Hide file tree
Showing 171 changed files with 2,611 additions and 2,242 deletions.
12 changes: 6 additions & 6 deletions .github/actions/install-llvm/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1114,18 +1114,18 @@ async function execute(cmd) {
(async () => {
try {
if(isLinux) {
await exec.exec("sudo apt install llvm-7 llvm-7-* liblld-7*");
await exec.exec("sudo apt install llvm-8 llvm-8-* liblld-8*");
} else if(isMacOS) {
await exec.exec("brew install llvm@7")
let llvmPath = await execute("brew --prefix llvm@7");
await exec.exec("brew install llvm@8")
let llvmPath = await execute("brew --prefix llvm@8");
core.addPath(`${llvmPath}/bin`)
} else if(isWindows) {
let llvmCachedPath = tc.find("llvm", "7.1.0", "windows-x64");
let llvmCachedPath = tc.find("llvm", "8.0.1", "windows-x64");
if(!llvmCachedPath) {
let _7zPath = path.join(__dirname, '..', 'externals', '7zr.exe');
llvmCachedPath = await tc.downloadTool("https://github.com/mun-lang/llvm-package-windows/releases/download/v7.1.0/llvm-7.1.0-windows-x64-msvc15.7z")
llvmCachedPath = await tc.downloadTool("https://github.com/mun-lang/llvm-package-windows/releases/download/v8.0.1/llvm-8.0.1-windows-x64-msvc15.7z")
.then(downloadPath => tc.extract7z(downloadPath, null, _7zPath))
.then(extractPath => tc.cacheDir(extractPath, "llvm", "7.1.0", "windows-x64"));
.then(extractPath => tc.cacheDir(extractPath, "llvm", "8.0.1", "windows-x64"));
}
core.addPath(`${llvmCachedPath}/bin`)
core.exportVariable('LIBCLANG_PATH', `${llvmCachedPath}/bin`)
Expand Down
12 changes: 6 additions & 6 deletions .github/actions/install-llvm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,18 @@ export async function execute(cmd) {
(async () => {
try {
if(isLinux) {
await exec.exec("sudo apt install llvm-7 llvm-7-* liblld-7*");
await exec.exec("sudo apt install llvm-8 llvm-8-* liblld-8*");
} else if(isMacOS) {
await exec.exec("brew install llvm@7")
let llvmPath = await execute("brew --prefix llvm@7");
await exec.exec("brew install llvm@8")
let llvmPath = await execute("brew --prefix llvm@8");
core.addPath(`${llvmPath}/bin`)
} else if(isWindows) {
let llvmCachedPath = tc.find("llvm", "7.1.0", "windows-x64");
let llvmCachedPath = tc.find("llvm", "8.0.1", "windows-x64");
if(!llvmCachedPath) {
let _7zPath = path.join(__dirname, '..', 'externals', '7zr.exe');
llvmCachedPath = await tc.downloadTool("https://github.com/mun-lang/llvm-package-windows/releases/download/v7.1.0/llvm-7.1.0-windows-x64-msvc15.7z")
llvmCachedPath = await tc.downloadTool("https://github.com/mun-lang/llvm-package-windows/releases/download/v8.0.1/llvm-8.0.1-windows-x64-msvc15.7z")
.then(downloadPath => tc.extract7z(downloadPath, null, _7zPath))
.then(extractPath => tc.cacheDir(extractPath, "llvm", "7.1.0", "windows-x64"));
.then(extractPath => tc.cacheDir(extractPath, "llvm", "8.0.1", "windows-x64"));
}
core.addPath(`${llvmCachedPath}/bin`)
core.exportVariable('LIBCLANG_PATH', `${llvmCachedPath}/bin`)
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,15 +153,15 @@ rustup](https://www.rust-lang.org/tools/install).

#### LLVM

Mun targets LLVM 7.1.0. Installing LLVM is platform dependant and as such can be
Mun targets LLVM 8.0.1. Installing LLVM is platform dependant and as such can be
a pain. The following steps are how we install LLVM on [our CI
runners](.github/actions/install-llvm/index.js):

* ***nix**: Package managers of recent *nix distros can install binary versions
of LLVM, e.g.:
```bash
# Ubuntu 18.04
sudo apt install llvm-7 llvm-7-* liblld-7*
sudo apt install llvm-8 llvm-8-* liblld-8*
```
* **Arch Linux** The binary version of LLVM can currently only be installed
using an AUR helper, such as `yay`:
Expand All @@ -181,21 +181,21 @@ runners](.github/actions/install-llvm/index.js):
symlink; e.g. `ln -s /usr/lib/libtinfo.so.6 /usr/lib/libtinfo.so.5`),
otherwise download the library.
* **macOS**: [Brew](https://brew.sh/) contains a binary distribution of LLVM
7.1.0. However, as it's not the latest version, it won't be added to the path.
8.0.1. However, as it's not the latest version, it won't be added to the path.
We are using [llvm-sys](https://crates.io/crates/llvm-sys) to manage version,
but another option is to export the `LLVM_SYS_70_PREFIX` variable, which will
but another option is to export the `LLVM_SYS_80_PREFIX` variable, which will
not clutter your `PATH`. To install:
```bash
brew install llvm@7
brew install llvm@8
# Export LLVM_SYS_PREFIX to not clubber PATH
export LLVM_SYS_PREFIX=$(brew --prefix llvm@7)
export LLVM_SYS_PREFIX=$(brew --prefix llvm@8)
```
* **windows**: Binary distrubutions are available for Windows on the LLVM
website, but they do not contain a number of libraries that are required by
Mun. To avoid having to go to the trouble of compiling LLVM yourself, we
created a [repository](https://github.com/mun-lang/llvm-package-windows) that
automatically compiles the required binaries. It also contains a
[release](https://github.com/mun-lang/llvm-package-windows/releases/download/v7.1.0/llvm-7.1.0-windows-x64-msvc15.7z)
[release](https://github.com/mun-lang/llvm-package-windows/releases/v8.0.1)
that you can download and extract to your machine. Once downloaded and
extracted, add the `<extract_dir>/bin` folder to the `PATH` environment
variable.
Expand Down
10 changes: 3 additions & 7 deletions crates/mun_codegen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ abi = { version = "=0.2.0", path = "../mun_abi", package = "mun_abi" }
hir = { version = "=0.2.0", path = "../mun_hir", package = "mun_hir" }
mun_codegen_macros = { path = "../mun_codegen_macros", package = "mun_codegen_macros" }
mun_target = { version = "=0.2.0", path = "../mun_target" }
mun_lld = { version = "=70.2.0", path = "../mun_lld" }
mun_lld = { version = "=80.0.0", path = "../mun_lld" }
anyhow = "1.0.31"
thiserror = "1.0.19"
salsa = "0.15.0"
Expand All @@ -26,12 +26,8 @@ array-init="0.1.0"
tempfile = "3"
paste = "0.1.6"
parking_lot = "0.10"
by_address = "1.0"

[dependencies.inkwell]
git = "https://github.com/mun-lang/inkwell"
rev = "4448bcd"
features = ["llvm7-0"]
inkwell = { version = "=0.1.0-llvm8sample", features = ["llvm8-0"]}
by_address = "1.0.4"

[dev-dependencies]
abi = { path = "../mun_abi", package = "mun_abi" }
Expand Down
20 changes: 16 additions & 4 deletions crates/mun_codegen/src/assembly.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use crate::{IrDatabase, ModuleBuilder};
use crate::code_gen::{CodeGenContext, ModuleBuilder};
use crate::db::CodeGenDatabase;
use inkwell::context::Context;
use std::path::Path;
use std::sync::Arc;
use tempfile::NamedTempFile;

/// An `Assembly` is a reference to a Mun library stored on disk.
#[derive(Debug)]
pub struct Assembly {
file: NamedTempFile,
Expand Down Expand Up @@ -30,14 +33,23 @@ impl Assembly {
}
}

/// Create a new temporary file that contains the linked object
pub fn assembly_query(db: &dyn IrDatabase, file_id: hir::FileId) -> Arc<Assembly> {
/// Builds an assembly for the specified file
pub(crate) fn build_assembly(db: &dyn CodeGenDatabase, file_id: hir::FileId) -> Arc<Assembly> {
// Construct a temporary file for the assembly
let file = NamedTempFile::new().expect("could not create temp file for shared object");

let module_builder = ModuleBuilder::new(db, file_id).expect("could not create ModuleBuilder");
// Setup the code generation context
let inkwell_context = Context::create();
let code_gen_context = CodeGenContext::new(&inkwell_context, db);

// Construct the module
let module_builder =
ModuleBuilder::new(&code_gen_context, file_id).expect("could not create ModuleBuilder");
let obj_file = module_builder
.build()
.expect("unable to create object file");

// Translate the object file into a shared object
obj_file
.into_shared_object(file.path())
.expect("could not link object file");
Expand Down
199 changes: 7 additions & 192 deletions crates/mun_codegen/src/code_gen.rs
Original file line number Diff line number Diff line change
@@ -1,189 +1,18 @@
use crate::code_gen::linker::LinkerError;
use crate::db::StructMapping;
use crate::value::{IrTypeContext, IrValueContext};
use crate::IrDatabase;
use hir::FileId;
use inkwell::targets::TargetData;
use inkwell::{
module::Module,
passes::{PassManager, PassManagerBuilder},
targets::{CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine},
OptimizationLevel,
};
use mun_target::spec;
use parking_lot::RwLock;
use std::collections::HashMap;
use std::io::{self, Write};
use std::{path::Path, sync::Arc};
use tempfile::NamedTempFile;
use thiserror::Error;

mod linker;
mod context;
mod error;
mod module_builder;
mod object_file;
pub mod symbols;

#[derive(Debug, Error)]
enum CodeGenerationError {
#[error("{0}")]
LinkerError(#[source] LinkerError),
#[error("error linking modules: {0}")]
ModuleLinkerError(String),
#[error("unknown target triple: {0}")]
UnknownTargetTriple(String),
#[error("error creating target machine")]
CouldNotCreateTargetMachine,
#[error("error creating object file")]
CouldNotCreateObjectFile(io::Error),
#[error("error generating machine code")]
CodeGenerationError(String),
}

impl From<LinkerError> for CodeGenerationError {
fn from(e: LinkerError) -> Self {
CodeGenerationError::LinkerError(e)
}
}

pub struct ObjectFile {
target: spec::Target,
obj_file: NamedTempFile,
}

impl ObjectFile {
/// Constructs a new object file from the specified `module` for `target`
pub fn new(
target: &spec::Target,
target_machine: &TargetMachine,
module: Arc<inkwell::module::Module>,
) -> Result<Self, anyhow::Error> {
let obj = target_machine
.write_to_memory_buffer(&module, FileType::Object)
.map_err(|e| CodeGenerationError::CodeGenerationError(e.to_string()))?;

let mut obj_file = tempfile::NamedTempFile::new()
.map_err(CodeGenerationError::CouldNotCreateObjectFile)?;
obj_file
.write(obj.as_slice())
.map_err(CodeGenerationError::CouldNotCreateObjectFile)?;

Ok(Self {
target: target.clone(),
obj_file,
})
}

/// Links the object file into a shared object.
pub fn into_shared_object(self, output_path: &Path) -> Result<(), anyhow::Error> {
// Construct a linker for the target
let mut linker = linker::create_with_target(&self.target);
linker.add_object(self.obj_file.path())?;

// Link the object
linker.build_shared_object(&output_path)?;
linker.finalize()?;

Ok(())
}
}

/// A struct that can be used to build an LLVM `Module`.
pub struct ModuleBuilder<'a> {
db: &'a dyn IrDatabase,
file_id: FileId,
_target: inkwell::targets::Target,
target_machine: inkwell::targets::TargetMachine,
assembly_module: Arc<inkwell::module::Module>,
}

impl<'a> ModuleBuilder<'a> {
/// Constructs module for the given `hir::FileId` at the specified output file location.
pub fn new(db: &'a dyn IrDatabase, file_id: FileId) -> Result<Self, anyhow::Error> {
let target = db.target();

// Construct a module for the assembly
let assembly_module = Arc::new(
db.context()
.create_module(db.file_relative_path(file_id).as_str()),
);

// Initialize the x86 target
Target::initialize_x86(&InitializationConfig::default());

// Retrieve the LLVM target using the specified target.
let llvm_target = Target::from_triple(&target.llvm_target)
.map_err(|e| CodeGenerationError::UnknownTargetTriple(e.to_string()))?;
assembly_module.set_target(&llvm_target);

// Construct target machine for machine code generation
let target_machine = llvm_target
.create_target_machine(
&target.llvm_target,
&target.options.cpu,
&target.options.features,
db.optimization_lvl(),
RelocMode::PIC,
CodeModel::Default,
)
.ok_or(CodeGenerationError::CouldNotCreateTargetMachine)?;

Ok(Self {
db,
file_id,
_target: llvm_target,
target_machine,
assembly_module,
})
}

/// Constructs an object file.
pub fn build(self) -> Result<ObjectFile, anyhow::Error> {
let group_ir = self.db.group_ir(self.file_id);
let file = self.db.file_ir(self.file_id);

// Clone the LLVM modules so that we can modify it without modifying the cached value.
self.assembly_module
.link_in_module(group_ir.llvm_module.clone())
.map_err(|e| CodeGenerationError::ModuleLinkerError(e.to_string()))?;

self.assembly_module
.link_in_module(file.llvm_module.clone())
.map_err(|e| CodeGenerationError::ModuleLinkerError(e.to_string()))?;

let target_data = self.db.target_data();
let struct_types = self.db.type_to_struct_mapping();
let type_context = IrTypeContext {
context: &self.assembly_module.get_context(),
target_data: target_data.as_ref(),
struct_types: struct_types.as_ref(),
};

let value_context = IrValueContext {
type_context: &type_context,
context: type_context.context,
module: &self.assembly_module,
};

// Generate the `get_info` method.
symbols::gen_reflection_ir(
self.db,
&value_context,
&file.api,
&group_ir.dispatch_table,
&group_ir.type_table,
);

// Optimize the assembly module
optimize_module(&self.assembly_module, self.db.optimization_lvl());

// Debug print the IR
//println!("{}", assembly_module.print_to_string().to_string());

ObjectFile::new(
&self.db.target(),
&self.target_machine,
self.assembly_module,
)
}
}
pub use context::CodeGenContext;
pub use error::CodeGenerationError;
pub use module_builder::ModuleBuilder;

/// Optimizes the specified LLVM `Module` using the default passes for the given
/// `OptimizationLevel`.
Expand All @@ -195,17 +24,3 @@ fn optimize_module(module: &Module, optimization_lvl: OptimizationLevel) {
pass_builder.populate_module_pass_manager(&module_pass_manager);
module_pass_manager.run_on(module);
}

/// Create an inkwell TargetData from the target in the database
pub(crate) fn target_data_query(db: &dyn IrDatabase) -> Arc<TargetData> {
Arc::new(TargetData::create(&db.target().data_layout))
}

/// Returns a mapping from struct type to a struct type in the context. This is a query because the
/// value of struct type depends on the target we compile for.
pub(crate) fn type_to_struct_mapping_query(
db: &dyn IrDatabase,
) -> by_address::ByAddress<Arc<StructMapping>> {
let _ = db.target_data();
by_address::ByAddress(Arc::new(RwLock::new(HashMap::default())))
}
Loading

0 comments on commit 97f4ca8

Please sign in to comment.