use crate::compiler::CraneliftCompiler;
use cranelift_codegen::{
CodegenResult,
isa::{TargetIsa, lookup},
settings::{self, Configurable},
};
use std::{
collections::HashMap,
fs::File,
io::{self, Write},
sync::Arc,
};
use std::{num::NonZero, path::PathBuf};
use target_lexicon::{OperatingSystem, Vendor};
use wasmer_compiler::{
Compiler, CompilerConfig, Debugger, Engine, EngineBuilder, ModuleMiddleware,
misc::{CompiledKind, function_kind_to_filename, save_assembly_to_file},
};
use wasmer_types::{
Features,
target::{Architecture, CpuFeature, Target},
};
#[derive(Debug, Clone)]
pub struct CraneliftCallbacks {
debug_dir: PathBuf,
}
impl CraneliftCallbacks {
pub fn new(debug_dir: PathBuf) -> Result<Self, io::Error> {
std::fs::create_dir_all(&debug_dir)?;
Ok(Self { debug_dir })
}
pub fn debug_dir(&self) -> &PathBuf {
&self.debug_dir
}
fn base_path(&self, module_hash: &Option<String>) -> PathBuf {
let mut path = self.debug_dir.clone();
if let Some(hash) = module_hash {
path.push(hash);
}
std::fs::create_dir_all(&path)
.unwrap_or_else(|_| panic!("cannot create debug directory: {}", path.display()));
path
}
pub fn preopt_ir(&self, kind: &CompiledKind, module_hash: &Option<String>, mem_buffer: &[u8]) {
let mut path = self.base_path(module_hash);
path.push(function_kind_to_filename(kind, ".preopt.clif"));
let mut file =
File::create(path).expect("Error while creating debug file from Cranelift IR");
file.write_all(mem_buffer).unwrap();
}
pub fn obj_memory_buffer(
&self,
kind: &CompiledKind,
module_hash: &Option<String>,
mem_buffer: &[u8],
) {
let mut path = self.base_path(module_hash);
path.push(function_kind_to_filename(kind, ".o"));
let mut file =
File::create(path).expect("Error while creating debug file from Cranelift object");
file.write_all(mem_buffer).unwrap();
}
pub fn asm_memory_buffer(
&self,
kind: &CompiledKind,
module_hash: &Option<String>,
arch: Architecture,
mem_buffer: &[u8],
) -> Result<(), wasmer_types::CompileError> {
let mut path = self.base_path(module_hash);
path.push(function_kind_to_filename(kind, ".s"));
save_assembly_to_file(arch, path, mem_buffer, HashMap::<usize, String>::new())
}
}
#[non_exhaustive]
#[derive(Clone, Debug)]
pub enum CraneliftOptLevel {
None,
Speed,
SpeedAndSize,
}
#[derive(Debug, Clone)]
pub struct Cranelift {
pub(crate) enable_nan_canonicalization: bool,
pub(crate) allow_experimental_unaligned_memory_accesses: bool,
enable_verifier: bool,
pub(crate) enable_perfmap: bool,
pub(crate) debugger: Option<Debugger>,
pub(crate) enable_pic: bool,
pub(crate) experimental_artifact: bool,
pub(crate) opt_level: CraneliftOptLevel,
pub num_threads: NonZero<usize>,
pub(crate) middlewares: Vec<Arc<dyn ModuleMiddleware>>,
pub(crate) callbacks: Option<CraneliftCallbacks>,
}
impl Cranelift {
pub fn new() -> Self {
Self {
enable_nan_canonicalization: false,
allow_experimental_unaligned_memory_accesses: false,
enable_verifier: false,
opt_level: CraneliftOptLevel::Speed,
enable_pic: false,
experimental_artifact: false,
num_threads: std::thread::available_parallelism().unwrap_or(NonZero::new(1).unwrap()),
middlewares: vec![],
enable_perfmap: false,
debugger: None,
callbacks: None,
}
}
pub fn experimental_artifact(&mut self, enable: bool) -> &mut Self {
self.experimental_artifact = enable;
self
}
pub fn canonicalize_nans(&mut self, enable: bool) -> &mut Self {
self.enable_nan_canonicalization = enable;
self
}
pub fn allow_experimental_unaligned_memory_accesses(&mut self, enable: bool) -> &mut Self {
self.allow_experimental_unaligned_memory_accesses = enable;
self
}
pub fn num_threads(&mut self, num_threads: NonZero<usize>) -> &mut Self {
self.num_threads = num_threads;
self
}
pub fn opt_level(&mut self, opt_level: CraneliftOptLevel) -> &mut Self {
self.opt_level = opt_level;
self
}
pub fn isa(&self, target: &Target) -> CodegenResult<Arc<dyn TargetIsa>> {
let mut builder =
lookup(target.triple().clone()).expect("construct Cranelift ISA for triple");
let cpu_features = target.cpu_features();
if target.triple().architecture == Architecture::X86_64
&& !cpu_features.contains(CpuFeature::SSE2)
{
panic!("x86 support requires SSE2");
}
if cpu_features.contains(CpuFeature::SSE3) {
builder.enable("has_sse3").expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::SSSE3) {
builder.enable("has_ssse3").expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::SSE41) {
builder.enable("has_sse41").expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::SSE42) {
builder.enable("has_sse42").expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::POPCNT) {
builder.enable("has_popcnt").expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::AVX) {
builder.enable("has_avx").expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::BMI1) {
builder.enable("has_bmi1").expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::BMI2) {
builder.enable("has_bmi2").expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::AVX2) {
builder.enable("has_avx2").expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::AVX512DQ) {
builder
.enable("has_avx512dq")
.expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::AVX512VL) {
builder
.enable("has_avx512vl")
.expect("should be valid flag");
}
if cpu_features.contains(CpuFeature::LZCNT) {
builder.enable("has_lzcnt").expect("should be valid flag");
}
builder.finish(self.flags(target))
}
pub fn flags(&self, target: &Target) -> settings::Flags {
let mut flags = settings::builder();
flags
.enable("enable_probestack")
.expect("should be valid flag");
flags
.set("probestack_strategy", "inline")
.expect("should be valid flag");
if self.enable_pic {
flags.enable("is_pic").expect("should be a valid flag");
}
flags
.enable("use_colocated_libcalls")
.expect("should be a valid flag");
if matches!(target.triple().operating_system, OperatingSystem::Windows) {
flags
.enable("enable_multi_ret_implicit_sret")
.expect("should be a valid flag");
}
flags
.set("enable_verifier", &self.enable_verifier.to_string())
.expect("should be valid flag");
flags
.set(
"opt_level",
match self.opt_level {
CraneliftOptLevel::None => "none",
CraneliftOptLevel::Speed => "speed",
CraneliftOptLevel::SpeedAndSize => "speed_and_size",
},
)
.expect("should be valid flag");
flags
.set(
"enable_nan_canonicalization",
&self.enable_nan_canonicalization.to_string(),
)
.expect("should be valid flag");
if matches!(target.triple().vendor, Vendor::Apple) {
flags
.enable("enable_compact_unwind_abi")
.expect("should be valid flag");
}
settings::Flags::new(flags)
}
pub fn callbacks(&mut self, callbacks: Option<CraneliftCallbacks>) -> &mut Self {
self.callbacks = callbacks;
self
}
}
impl CompilerConfig for Cranelift {
fn experimental_artifact(&mut self, enable: bool) {
self.experimental_artifact = enable;
}
fn enable_pic(&mut self) {
self.enable_pic = true;
}
fn enable_verifier(&mut self) {
self.enable_verifier = true;
}
fn enable_perfmap(&mut self) {
self.enable_perfmap = true;
}
fn enable_debugger(&mut self, debugger: Debugger) {
self.debugger = Some(debugger);
}
fn enable_experimental_unaligned_memory_accesses(&mut self) {
self.allow_experimental_unaligned_memory_accesses = true;
}
fn canonicalize_nans(&mut self, enable: bool) {
self.enable_nan_canonicalization = enable;
}
fn compiler(self: Box<Self>) -> Box<dyn Compiler> {
Box::new(CraneliftCompiler::new(*self))
}
fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>) {
self.middlewares.push(middleware);
}
fn supported_features_for_target(&self, target: &Target) -> wasmer_types::Features {
let mut feats = Features::default();
if matches!(
target.triple().operating_system,
OperatingSystem::Linux | OperatingSystem::Darwin(_)
) {
feats.exceptions(true);
}
feats.exceptions(true);
feats.relaxed_simd(true);
feats.wide_arithmetic(true);
feats
}
}
impl Default for Cranelift {
fn default() -> Self {
Self::new()
}
}
impl From<Cranelift> for Engine {
fn from(config: Cranelift) -> Self {
EngineBuilder::new(config).engine()
}
}