use crate::{Engine, Job, Loaded, Result, WriteJob, lookup_cached, recharge, tiers};
use asc_cache::Tier;
use asc_dex::Dex;
use asc_findrefs::FindRefs;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, Instant};
const YIELD_POLL: Duration = Duration::from_millis(1);
const MAX_PENDING_WRITES: usize = 2;
const TABLES: [fn(&FindRefs<'_, '_, '_>) -> asc_findrefs::Result<()>; 5] = [
|f| f.insn_locator().map(drop),
|f| f.string_locator().map(drop),
|f| f.type_locator().map(drop),
|f| f.method_locator().map(drop),
|f| f.field_locator().map(drop),
];
#[derive(Default)]
pub(crate) struct Prebuild {
cancel: AtomicBool,
running: Mutex<bool>,
done: Condvar,
queries: AtomicUsize,
}
pub(crate) struct QueryGuard<'p>(&'p Prebuild);
impl Drop for QueryGuard<'_> {
fn drop(&mut self) {
self.0.queries.fetch_sub(1, Ordering::Relaxed);
}
}
impl Prebuild {
pub(crate) fn query(&self) -> QueryGuard<'_> {
self.queries.fetch_add(1, Ordering::Relaxed);
QueryGuard(self)
}
pub(crate) fn cancel(&self) {
self.cancel.store(true, Ordering::Relaxed);
}
fn cancelled(&self) -> bool {
self.cancel.load(Ordering::Relaxed)
}
fn give_way(&self) -> bool {
while self.queries.load(Ordering::Relaxed) > 0 && !self.cancelled() {
std::thread::sleep(YIELD_POLL);
}
!self.cancelled()
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PrebuildReport {
pub dex: usize,
pub from_disk: usize,
pub skipped: usize,
pub cancelled: bool,
pub elapsed_us: u128,
}
impl Engine {
pub fn prebuild(&self) -> Result<PrebuildReport> {
let t0 = Instant::now();
let mut report = PrebuildReport::default();
let Some(cache) = self.cache.as_deref().filter(|c| c.memory.is_some()) else { return Ok(report) };
{
let mut running = self.prebuild.running.lock().unwrap();
if *running || self.prebuild.cancelled() {
report.cancelled = true;
return Ok(report);
}
*running = true;
}
let _done = Finished(&self.prebuild);
let apk = self.open()?;
let mut entries = apk.dex_entries()?;
entries.reverse();
let key = self.apk_key(&apk);
let l2 = cache.disk.as_deref().filter(|d| d.uses(Tier::Derived)).zip(key.as_ref());
let next = AtomicUsize::new(0);
let tally = Mutex::new(&mut report);
std::thread::scope(|s| {
for _ in 0..self.threads().min(entries.len()) {
s.spawn(|| {
lower_priority();
while self.prebuild.give_way() {
let Some(entry) = entries.get(next.fetch_add(1, Ordering::Relaxed)) else { break };
let cached = match lookup_cached(cache, key.as_ref(), entry, &self.stats) {
Some((e, _)) => Some(e),
None if entry.method == asc_apk::METHOD_STORED => {
match crate::load_entry(&apk, key.as_ref(), entry, Some(cache), &self.stats, Some(&self.prebuild.cancel)) {
Ok((Loaded::Cached(e), _)) => Some(e),
_ => None,
}
}
None => None,
};
let Some(e) = cached else {
tally.lock().unwrap().skipped += 1;
continue;
};
for (i, l) in e.logical().iter().enumerate() {
let Ok(dex) = Dex::with_name(&l.data, &l.name) else { continue };
let mapped = l2.is_some_and(|(disk, k)| tiers::load_derived(disk, k, l, &dex));
let (mask, t) = (l.state.table_mask(), Instant::now());
let fr = FindRefs::with_state(&dex, &l.state);
for build in TABLES {
if !self.prebuild.give_way() || build(&fr).is_err() {
break;
}
}
if let Some((disk, k)) = l2
&& let Some(job) = tiers::derived_job(disk, &e, i, mask, t.elapsed())
{
cache.put_async(WriteJob { key: k.clone(), apk: Arc::from(apk.path()), kind: Job::Derived(job) });
while cache.derived_pending.load(Ordering::Relaxed) > MAX_PENDING_WRITES && !self.prebuild.cancelled() {
std::thread::sleep(YIELD_POLL);
}
}
let mut tally = tally.lock().unwrap();
tally.dex += 1;
tally.from_disk += mapped as usize;
}
recharge(Some(cache), key.as_ref(), &e);
}
});
}
});
report.cancelled = self.prebuild.cancelled();
report.elapsed_us = t0.elapsed().as_micros();
self.log(format!(
"[APK] prebuild dex={} from_disk={} skipped={} cancelled={} total={} us",
report.dex, report.from_disk, report.skipped, report.cancelled, report.elapsed_us
));
Ok(report)
}
pub fn cancel_prebuild(&self) {
self.prebuild.cancel();
let mut running = self.prebuild.running.lock().unwrap();
while *running {
running = self.prebuild.done.wait(running).unwrap();
}
}
}
struct Finished<'p>(&'p Prebuild);
impl Drop for Finished<'_> {
fn drop(&mut self) {
*self.0.running.lock().unwrap_or_else(std::sync::PoisonError::into_inner) = false;
self.0.done.notify_all();
}
}
pub(crate) fn lower_priority() {
#[cfg(target_vendor = "apple")]
{
const QOS_CLASS_UTILITY: std::os::raw::c_uint = 0x11;
unsafe extern "C" {
fn pthread_set_qos_class_self_np(
qos_class: std::os::raw::c_uint,
relative_priority: std::os::raw::c_int,
) -> std::os::raw::c_int;
}
unsafe {
pthread_set_qos_class_self_np(QOS_CLASS_UTILITY, 0);
}
}
#[cfg(all(target_os = "linux", target_env = "gnu"))]
{
unsafe extern "C" {
fn gettid() -> std::os::raw::c_int;
fn setpriority(which: std::os::raw::c_int, who: std::os::raw::c_uint, prio: std::os::raw::c_int) -> std::os::raw::c_int;
}
const PRIO_PROCESS: std::os::raw::c_int = 0;
unsafe {
setpriority(PRIO_PROCESS, gettid() as std::os::raw::c_uint, 19);
}
}
}