use crate::loader::Instruction;
use crate::scheduler::ServiceMode;
use crate::scheduler::dirty::{DirtyPool, DirtySubmitError, DirtyTask};
use std::sync::Arc;
use super::cache::{JitCache, JitCacheKey};
use super::compiler::{JitCompiler, JitError};
use super::profiler::JitProfiler;
pub struct CompilationRequest {
key: JitCacheKey,
epoch: u64,
instructions: Vec<Instruction>,
}
impl CompilationRequest {
#[must_use]
pub fn new(
module: crate::atom::Atom,
function: crate::atom::Atom,
arity: u8,
generation: u64,
epoch: u64,
instructions: Vec<Instruction>,
) -> Self {
Self {
key: JitCacheKey::new(module, function, arity, generation),
epoch,
instructions,
}
}
}
pub struct CompilationJob {
request: CompilationRequest,
compiler: Arc<JitCompiler>,
profiler: Arc<JitProfiler>,
cache: Arc<JitCache>,
}
impl CompilationJob {
#[must_use]
pub fn new(
request: CompilationRequest,
compiler: Arc<JitCompiler>,
profiler: Arc<JitProfiler>,
cache: Arc<JitCache>,
) -> Self {
Self {
request,
compiler,
profiler,
cache,
}
}
fn run(self) {
let request = self.request;
let key = request.key;
let epoch = request.epoch;
match self
.compiler
.compile(&request.instructions, key.module, key.function, key.arity)
{
Ok(native_code) => {
let published = self.profiler.publish_compiled(
key.module,
key.function,
key.arity,
key.generation,
epoch,
|| self.cache.insert(key, native_code),
);
if published {
self.profiler.note_success();
} else {
self.profiler.note_transient_failure();
}
}
Err(
JitError::UnsupportedOpcode { .. }
| JitError::UnsupportedOperand { .. }
| JitError::UnknownLabel { .. },
) => {
let applied = self.profiler.mark_unsupported(
key.module,
key.function,
key.arity,
key.generation,
epoch,
);
if applied {
self.profiler.note_unsupported();
} else {
self.profiler.note_transient_failure();
}
}
Err(JitError::CraneliftError(_) | JitError::EmptyFunction) => {
self.profiler.reset_counter(
key.module,
key.function,
key.arity,
key.generation,
epoch,
);
self.profiler.note_transient_failure();
}
}
}
}
pub trait JitSubmissionFacility: Send + Sync {
fn submit(&self, request: CompilationRequest) -> Result<(), DirtySubmitError>;
}
pub struct JitProfilingServices {
pub profiler: Arc<JitProfiler>,
pub submitter: Arc<dyn JitSubmissionFacility>,
}
pub fn submit_jit_compilation(
dirty_cpu: &DirtyPool,
job: CompilationJob,
) -> Result<(), DirtySubmitError> {
dirty_cpu.submit_task(DirtyTask::new(move || job.run()))
}
pub fn try_submit_jit_compilation(
dirty_cpu: &ServiceMode<DirtyPool>,
job: CompilationJob,
) -> Result<(), DirtySubmitError> {
dirty_cpu.submit_task(DirtyTask::new(move || job.run()))
}
#[cfg(test)]
mod tests {
use super::{
CompilationJob, CompilationRequest, submit_jit_compilation, try_submit_jit_compilation,
};
use crate::atom::Atom;
use crate::jit::cache::JitCache;
use crate::jit::compiler::{JitCompiler, JitSettings};
use crate::jit::profiler::{JitProfiler, RecordResult};
use crate::loader::Instruction;
use crate::scheduler::ServiceMode;
use crate::scheduler::dirty::{DirtyPool, DirtySubmitError};
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
fn wait_until(mut predicate: impl FnMut() -> bool) -> bool {
let deadline = Instant::now() + Duration::from_secs(2);
while Instant::now() < deadline {
if predicate() {
return true;
}
thread::sleep(Duration::from_millis(10));
}
false
}
#[test]
fn empty_return_function_marks_compiled() {
let pool = DirtyPool::with_queue_depth("jit-compile-success", 1, 4);
let compiler = Arc::new(JitCompiler::new(JitSettings).unwrap());
let profiler = Arc::new(JitProfiler::new(1));
let cache = Arc::new(JitCache::new());
assert!(matches!(
profiler.record_call(Atom::MODULE, Atom::OK, 0, 1),
RecordResult::CompileNow { .. }
));
let epoch = profiler
.profile_epoch(Atom::MODULE, Atom::OK, 0)
.expect("profile exists");
let job = CompilationJob::new(
CompilationRequest::new(
Atom::MODULE,
Atom::OK,
0,
1,
epoch,
vec![Instruction::Return],
),
Arc::clone(&compiler),
Arc::clone(&profiler),
Arc::clone(&cache),
);
assert_eq!(submit_jit_compilation(&pool, job), Ok(()));
assert!(wait_until(|| profiler.is_compiled(
Atom::MODULE,
Atom::OK,
0
) && cache
.lookup(Atom::MODULE, Atom::OK, 0, 1)
.is_some()));
drop(pool);
}
#[test]
fn unsupported_function_marks_unsupported() {
let pool = ServiceMode::Owned(DirtyPool::with_queue_depth("jit-compile-unsupported", 1, 4));
let compiler = Arc::new(JitCompiler::new(JitSettings).unwrap());
let profiler = Arc::new(JitProfiler::new(1));
let cache = Arc::new(JitCache::new());
assert!(matches!(
profiler.record_call(Atom::MODULE, Atom::ERROR, 0, 1),
RecordResult::CompileNow { .. }
));
let epoch = profiler
.profile_epoch(Atom::MODULE, Atom::ERROR, 0)
.expect("profile exists");
let job = CompilationJob::new(
CompilationRequest::new(
Atom::MODULE,
Atom::ERROR,
0,
1,
epoch,
vec![Instruction::Generic {
opcode: 255,
name: "unknown",
operands: Vec::new(),
}],
),
Arc::clone(&compiler),
Arc::clone(&profiler),
Arc::clone(&cache),
);
assert_eq!(try_submit_jit_compilation(&pool, job), Ok(()));
assert!(wait_until(|| profiler.is_unsupported(
Atom::MODULE,
Atom::ERROR,
0
)));
assert!(cache.lookup(Atom::MODULE, Atom::ERROR, 0, 1).is_none());
for _ in 0..10 {
assert_eq!(
profiler.record_call(Atom::MODULE, Atom::ERROR, 0, 1),
RecordResult::Continue
);
}
drop(pool);
}
#[test]
fn disabled_pool_refuses_jit_submission() {
let pool: ServiceMode<DirtyPool> = ServiceMode::Disabled;
let compiler = Arc::new(JitCompiler::new(JitSettings).unwrap());
let profiler = Arc::new(JitProfiler::new(1));
let cache = Arc::new(JitCache::new());
let job = CompilationJob::new(
CompilationRequest::new(Atom::MODULE, Atom::OK, 0, 1, 1, vec![Instruction::Return]),
compiler,
Arc::clone(&profiler),
cache,
);
assert_eq!(
try_submit_jit_compilation(&pool, job),
Err(DirtySubmitError::Disabled)
);
assert!(!profiler.is_compiled(Atom::MODULE, Atom::OK, 0));
}
}