use crate::engine::{self, EngineContext, EngineJob, EngineSession};
use crate::{Natural, PARTS};
use std::alloc::{Layout, alloc, dealloc};
use std::cell::RefCell;
const ABI_VERSION: u32 = 2;
const MAX_PACKET: usize = 16 * 1024 * 1024;
type WasmNatural = Natural;
struct Slot<T> {
generation: u16,
value: Option<T>,
}
struct Registry<T> {
slots: Vec<Slot<T>>,
}
impl<T> Registry<T> {
const fn new() -> Self {
Self { slots: Vec::new() }
}
fn insert(&mut self, value: T) -> u32 {
for (i, s) in self.slots.iter_mut().enumerate() {
if s.value.is_none() {
s.value = Some(value);
return ((s.generation as u32) << 16) | (i as u32 + 1);
}
}
let i = self.slots.len();
if i >= u16::MAX as usize {
return 0;
}
self.slots.push(Slot {
generation: 1,
value: Some(value),
});
((1u32) << 16) | (i as u32 + 1)
}
fn get(&self, h: u32) -> Option<&T> {
let i = (h & 0xffff).checked_sub(1)? as usize;
let g = (h >> 16) as u16;
let s = self.slots.get(i)?;
if s.generation == g {
s.value.as_ref()
} else {
None
}
}
fn get_mut(&mut self, h: u32) -> Option<&mut T> {
let i = (h & 0xffff).checked_sub(1)? as usize;
let g = (h >> 16) as u16;
let s = self.slots.get_mut(i)?;
if s.generation == g {
s.value.as_mut()
} else {
None
}
}
fn remove(&mut self, h: u32) {
let i = match (h & 0xffff).checked_sub(1) {
Some(v) => v as usize,
None => return,
};
let g = (h >> 16) as u16;
if let Some(s) = self.slots.get_mut(i)
&& s.generation == g
{
s.value = None;
s.generation = s.generation.wrapping_add(1).max(1)
}
}
}
thread_local! {static BUFFERS:RefCell<Registry<Box<[u8]>>>=const{RefCell::new(Registry::new())};
static COORDS: RefCell<Registry<EngineSession>> = const { RefCell::new(Registry::new()) };
static WORKERS: RefCell<Registry<EngineContext>> = const { RefCell::new(Registry::new()) };
}
fn memory_bytes() -> usize {
core::arch::wasm32::memory_size(0) * 65536
}
fn input(pointer: u32, length: u32) -> Option<Vec<u8>> {
let p = pointer as usize;
let n = length as usize;
if n == 0 || n > MAX_PACKET || p.checked_add(n)? > memory_bytes() {
return None;
}
Some(unsafe { core::slice::from_raw_parts(p as *const u8, n) }.to_vec())
}
fn packet(kind: u16, payload: &[u8]) -> u32 {
if payload.len() > MAX_PACKET {
return 0;
}
let mut v = Vec::with_capacity(12 + payload.len());
v.extend_from_slice(b"QSV1");
v.extend_from_slice(&kind.to_le_bytes());
v.extend_from_slice(&1u16.to_le_bytes());
v.extend_from_slice(&(payload.len() as u32).to_le_bytes());
v.extend_from_slice(payload);
BUFFERS.with(|r| r.borrow_mut().insert(v.into_boxed_slice()))
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_abi_version() -> u32 {
ABI_VERSION
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_alloc(size: u32, align: u32) -> u32 {
let Ok(layout) = Layout::from_size_align(size as usize, align as usize) else {
return 0;
};
if size == 0 {
return align;
}
let p = unsafe { alloc(layout) };
p as u32
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn qs_dealloc(pointer: u32, size: u32, align: u32) {
let Ok(layout) = Layout::from_size_align(size as usize, align as usize) else {
return;
};
if size != 0 && pointer != 0 {
unsafe { dealloc(pointer as *mut u8, layout) }
}
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_buffer_pointer(handle: u32) -> u32 {
BUFFERS.with(|r| r.borrow().get(handle).map_or(0, |b| b.as_ptr() as u32))
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_buffer_length(handle: u32) -> u32 {
BUFFERS.with(|r| {
r.borrow()
.get(handle)
.and_then(|b| u32::try_from(b.len()).ok())
.unwrap_or(0)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_buffer_free(handle: u32) {
BUFFERS.with(|r| r.borrow_mut().remove(handle))
}
fn parse_decimal(pointer: u32, length: u32) -> Option<WasmNatural> {
let bytes = input(pointer, length)?;
let text = core::str::from_utf8(&bytes).ok()?;
WasmNatural::from_decimal(text).ok()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn qs_worker_prepare(n_pointer: u32, n_length: u32) -> u32 {
let Some(n) = parse_decimal(n_pointer, n_length) else {
return 0;
};
let Ok(ctx) = engine::prepare(n, &crate::factor::FactorTuning::default()) else {
return 0;
};
WORKERS.with(|r| r.borrow_mut().insert(ctx))
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_worker_sieve(context: u32, family_first: u32, count: u32) -> u32 {
WORKERS.with(|r| {
let reg = r.borrow();
let Some(ctx) = reg.get(context) else {
return 0;
};
let count = count.min(4096);
let mut payload = Vec::new();
payload.extend_from_slice(&count.to_le_bytes());
for k in 0..count {
let job = EngineJob {
family: (family_first + k) as u64,
};
let bytes = engine::execute(ctx, job).to_bytes();
payload.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
payload.extend_from_slice(&bytes);
}
packet(10, &payload)
})
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_worker_free(context: u32) {
WORKERS.with(|r| r.borrow_mut().remove(context))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn qs_coord_new(n_pointer: u32, n_length: u32) -> u32 {
let Some(n) = parse_decimal(n_pointer, n_length) else {
return 0;
};
let Ok(ctx) = engine::prepare(n, &crate::factor::FactorTuning::default()) else {
return 0;
};
COORDS.with(|r| r.borrow_mut().insert(EngineSession::new(ctx)))
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_coord_target(session: u32) -> u32 {
COORDS.with(|r| r.borrow().get(session).map_or(0, |s| s.target() as u32))
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_coord_relations(session: u32) -> u32 {
COORDS.with(|r| r.borrow().get(session).map_or(0, |s| s.relations() as u32))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn qs_coord_submit(session: u32, pointer: u32, length: u32) -> u32 {
let Some(bytes) = input(pointer, length) else {
return 0;
};
COORDS.with(|r| {
let mut reg = r.borrow_mut();
let Some(s) = reg.get_mut(session) else {
return 0;
};
if bytes.len() >= 4 {
let count = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
let mut o = 4usize;
for _ in 0..count {
if o + 4 > bytes.len() {
break;
}
let len = u32::from_le_bytes(bytes[o..o + 4].try_into().unwrap()) as usize;
o += 4;
if o + len > bytes.len() {
break;
}
s.submit_bytes(&bytes[o..o + len]);
o += len;
}
}
s.relations() as u32
})
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_coord_extract(session: u32) -> u32 {
COORDS.with(|r| {
let reg = r.borrow();
let Some(s) = reg.get(session) else {
return 0;
};
match s.extract_factor() {
Ok(d) => {
let mut payload = Vec::with_capacity(PARTS * 8);
for limb in d.as_parts() {
payload.extend_from_slice(&limb.to_le_bytes());
}
packet(11, &payload)
}
Err(_) => 0,
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn qs_coord_free(session: u32) {
COORDS.with(|r| r.borrow_mut().remove(session))
}