Compare commits

..

No commits in common. "trunk" and "master" have entirely different histories.

16 changed files with 176 additions and 176 deletions

11
Cargo.lock generated
View file

@ -220,7 +220,7 @@ name = "hbxrt"
version = "0.1.0"
dependencies = [
"hbvm",
"memmap2",
"nix",
]
[[package]]
@ -256,15 +256,6 @@ version = "2.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
[[package]]
name = "memmap2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "deaba38d7abf1d4cca21cc89e932e542ba2b9258664d2a9ef0e61512039c9375"
dependencies = [
"libc",
]
[[package]]
name = "miniz_oxide"
version = "0.7.1"

29
c-abi.md Normal file
View file

@ -0,0 +1,29 @@
# C ABI (proposal)
## C datatypes
| C Type | Description | Size (B) |
|:------------|:-------------------------|-------------:|
| char | Character / byte | 8 |
| short | Short integer | 16 |
| int | Integer | 32 |
| long | Long integer | 64 |
| long long | Long long integer | 64 |
| T* | Pointer | 64 |
| float | Single-precision float | 32 |
| double | Double-precision float | 64 |
| long double | Extended-precision float | **Bikeshed** |
## Registers
| Register | ABI Name | Description | Saver |
|:---------|:---------|:---------------|:-------|
| `r0` | — | Zero register | N/A |
| `r1` | `ra` | Return address | Caller |
| `r2` | `sp` | Stack pointer | Callee |
| `r3` | `tp` | Thread pointer | N/A |
**TODO:** Parameters
**TODO:** Saved
**TODO:** Temp

View file

@ -47,7 +47,7 @@
0x2E, ADDI16, RRH, "Addition with immediate (16b)" ;
0x2F, ADDI32, RRW, "Addition with immediate (32b)" ;
0x30, ADDI64, RRD, "Addition with immediate (64b)" ;
0x31, MULI8, RRB, "Multiplication with immediate (8b)" ;
0x31, MULI8, RRW, "Multiplication with immediate (8b)" ;
0x32, MULI16, RRH, "Multiplication with immediate (16b)" ;
0x33, MULI32, RRW, "Multiplication with immediate (32b)" ;
0x34, MULI64, RRD, "Multiplication with immediate (64b)" ;
@ -114,7 +114,7 @@
0x71, FTI64, RRB, "Float 64 to int" ;
0x72, FC32T64, RR, "Float 64 to Float 32" ;
0x73, FC64T32, RRB, "Float 32 to Float 64" ;
0x74, LRA16, RRO, "Load relative immediate (16 bit)" ;
0x74, LRA16, RRP, "Load relative immediate (16 bit)" ;
0x75, LDR16, RRPH, "Load from relative address (16 bit)" ;
0x76, STR16, RRPH, "Store to relative address (16 bit)" ;
0x77, JMP16, P, "Relative jump (16 bit)" ;

View file

@ -43,19 +43,17 @@ impl BlockCopier {
/// - Same as for [`Memory::load`] and [`Memory::store`]
pub unsafe fn poll(&mut self, memory: &mut impl Memory) -> Poll<Result<(), BlkCopyError>> {
// Safety: Assuming uninit of array of MaybeUninit is sound
let mut buf = AlignedBuf(unsafe { MaybeUninit::uninit().assume_init() });
let mut buf = AlignedBuf(MaybeUninit::uninit().assume_init());
// We have at least one buffer size to copy
if self.n_buffers != 0 {
if let Err(e) = unsafe {
act(
if let Err(e) = act(
memory,
self.src,
self.dst,
buf.0.as_mut_ptr().cast(),
BUF_SIZE,
)
} {
) {
return Poll::Ready(Err(e));
}
@ -75,15 +73,13 @@ impl BlockCopier {
}
if self.rem != 0 {
if let Err(e) = unsafe {
act(
if let Err(e) = act(
memory,
self.src,
self.dst,
buf.0.as_mut_ptr().cast(),
self.rem,
)
} {
) {
return Poll::Ready(Err(e));
}
}
@ -101,7 +97,6 @@ unsafe fn act(
buf: *mut u8,
count: usize,
) -> Result<(), BlkCopyError> {
unsafe {
// Load to buffer
memory
.load(src, buf, count)
@ -117,7 +112,6 @@ unsafe fn act(
access_reason: MemoryAccessReason::Store,
addr,
})?;
}
Ok(())
}

View file

@ -46,7 +46,7 @@ unsafe fn set_rounding_mode(mode: RoundingMode) {
}
let fpcr: u64;
unsafe { asm!("mrs {}, fpcr", out(reg) fpcr) };
asm!("mrs {}, fpcr", out(reg) fpcr);
let fpcr = fpcr & !(0b11 << 22)
| (match mode {
@ -56,7 +56,7 @@ unsafe fn set_rounding_mode(mode: RoundingMode) {
RoundingMode::Down => 0b10,
}) << 22;
unsafe { asm!("msr fpcr, {}", in(reg) fpcr) };
asm!("msr fpcr, {}", in(reg) fpcr);
}
#[inline(always)]

View file

@ -56,14 +56,12 @@ fnsdef! {
/// [`default_rounding_mode`], you have to rely on inline assembly
#[inline(always)]
unsafe fn set_rounding_mode(mode: RoundingMode) {
unsafe {
arin::_MM_SET_ROUNDING_MODE(match mode {
RoundingMode::NearestEven => return,
RoundingMode::Truncate => arin::_MM_ROUND_TOWARD_ZERO,
RoundingMode::Up => arin::_MM_ROUND_UP,
RoundingMode::Down => arin::_MM_ROUND_DOWN,
})
}
}
#[inline(always)]

View file

@ -12,7 +12,6 @@
#![no_std]
#![cfg_attr(feature = "nightly", feature(fn_align))]
#![deny(unsafe_op_in_unsafe_fn)]
#[cfg(feature = "alloc")]
extern crate alloc;

View file

@ -48,14 +48,14 @@ impl ICache {
let pbase = self
.data
.or_else(|| unsafe { self.fetch_page(self.base + self.size, root_pt) })?;
.or_else(|| self.fetch_page(self.base + self.size, root_pt))?;
// Get address base
let base = addr.map(|x| x & self.mask);
// Base not matching, fetch anew
if base != self.base {
unsafe { self.fetch_page(base, root_pt) }?;
self.fetch_page(base, root_pt)?;
};
let offset = addr.get() & !self.mask;
@ -68,27 +68,25 @@ impl ICache {
let first_copy = requ_size.saturating_sub(rem);
// Copy non-overflowing part
unsafe { copy_nonoverlapping(pbase.as_ptr(), ret.as_mut_ptr().cast::<u8>(), first_copy) };
copy_nonoverlapping(pbase.as_ptr(), ret.as_mut_ptr().cast::<u8>(), first_copy);
// Copy overflow
if rem != 0 {
let pbase = unsafe { self.fetch_page(self.base + self.size, root_pt) }?;
let pbase = self.fetch_page(self.base + self.size, root_pt)?;
// Unlikely, unsupported scenario
if rem > self.size as _ {
return None;
}
unsafe {
copy_nonoverlapping(
pbase.as_ptr(),
ret.as_mut_ptr().cast::<u8>().add(first_copy),
rem,
)
};
);
}
Some(unsafe { ret.assume_init() })
Some(ret.assume_init())
}
/// Fetch a page

View file

@ -36,11 +36,9 @@ impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> {
// Walk pagetable levels
for lvl in (lookup_depth + 1..5).rev() {
let entry = unsafe {
(*current_pt)
let entry = (*current_pt)
.table
.get_unchecked_mut(addr_extract_index(target, lvl))
};
.get_unchecked_mut(addr_extract_index(target, lvl));
let ptr = entry.ptr();
match entry.permission() {
@ -48,13 +46,13 @@ impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> {
// No worries! Let's create one (allocates).
Permission::Empty => {
// Increase children count
unsafe { *current_pt }.childen += 1;
(*current_pt).childen += 1;
let table = Box::into_raw(Box::new(PtPointedData {
pt: PageTable::default(),
}));
unsafe { core::ptr::write(entry, PtEntry::new(table, Permission::Node)) };
core::ptr::write(entry, PtEntry::new(table, Permission::Node));
current_pt = table as _;
}
// Continue walking
@ -65,11 +63,9 @@ impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> {
}
}
let node = unsafe {
(*current_pt)
let node = (*current_pt)
.table
.get_unchecked_mut(addr_extract_index(target, lookup_depth))
};
.get_unchecked_mut(addr_extract_index(target, lookup_depth));
// Check if node is not mapped
if node.permission() != Permission::Empty {
@ -77,10 +73,8 @@ impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> {
}
// Write entry
unsafe {
(*current_pt).childen += 1;
core::ptr::write(node, PtEntry::new(host.cast(), perm));
}
Ok(())
}

View file

@ -51,7 +51,7 @@ impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory
target,
count,
perm_check::readable,
|src, dst, count| unsafe { core::ptr::copy_nonoverlapping(src, dst, count) },
|src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count),
)
.map_err(LoadError)
}
@ -72,7 +72,7 @@ impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory
source.cast_mut(),
count,
perm_check::writable,
|dst, src, count| unsafe { core::ptr::copy_nonoverlapping(src, dst, count) },
|dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count),
)
.map_err(StoreError)
}
@ -80,14 +80,16 @@ impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory
#[inline(always)]
unsafe fn prog_read<T>(&mut self, addr: Address) -> T {
if OUT_PROG_EXEC && addr.truncate_usize() > self.program.len() {
return unsafe { self.icache.fetch::<T>(addr, self.root_pt) }
return self
.icache
.fetch::<T>(addr, self.root_pt)
.unwrap_or_else(|| unsafe { core::mem::zeroed() });
}
let addr = addr.truncate_usize();
self.program
.get(addr..addr + size_of::<T>())
.map(|x| unsafe { x.as_ptr().cast::<T>().read() })
.map(|x| x.as_ptr().cast::<T>().read())
.unwrap_or_else(|| unsafe { core::mem::zeroed() })
}
}

View file

@ -17,8 +17,7 @@ use {
macro_rules! handler {
($self:expr, |$ty:ident ($($ident:pat),* $(,)?)| $expr:expr) => {{
#[allow(unused_unsafe)]
let $ty($($ident),*) = unsafe { $self.decode::<$ty>() };
let $ty($($ident),*) = $self.decode::<$ty>();
#[allow(clippy::no_effect)] let e = $expr;
$self.bump_pc::<$ty>();
e
@ -41,14 +40,14 @@ where
// Contribution guide:
// - Zero register shall never be overwitten. It's value has to always be 0.
// - Prefer `Self::read_reg` and `Self::write_reg` functions
// - Try to use `handler!` macro for decoding and then bumping program counter
// - Extract parameters using `param!` macro
// - Prioritise speed over code size
// - Memory is cheap, CPUs not that much
// - Do not heap allocate at any cost
// - Yes, user-provided trap handler may allocate,
// but that is not our »fault«.
// - Unsafe is kinda must, but be sure you have validated everything
// - Your contributions have to pass sanitizers, fuzzer and Miri
// - Your contributions have to pass sanitizers and Miri
// - Strictly follow the spec
// - The spec does not specify how you perform actions, in what order,
// just that the observable effects have to be performed in order and
@ -375,16 +374,13 @@ where
/// Bump instruction pointer
#[inline(always)]
fn bump_pc<T: Copy>(&mut self) {
self.pc = self
.pc
.wrapping_add(core::mem::size_of::<T>())
.wrapping_add(1);
self.pc = self.pc.wrapping_add(core::mem::size_of::<T>());
}
/// Decode instruction operands
#[inline(always)]
unsafe fn decode<T: Copy>(&mut self) -> T {
unsafe { self.memory.prog_read::<T>(self.pc + 1_u64) }
self.memory.prog_read::<T>(self.pc + 1_u64)
}
/// Load
@ -401,7 +397,6 @@ where
_ => 0,
};
unsafe {
self.memory.load(
self.ldst_addr_uber(dst, base, offset, count, n)?,
self.registers
@ -409,8 +404,7 @@ where
.add(usize::from(dst) + usize::from(n))
.cast(),
usize::from(count).saturating_sub(n.into()),
)
}?;
)?;
Ok(())
}
@ -424,13 +418,11 @@ where
offset: u64,
count: u16,
) -> Result<(), VmRunError> {
unsafe {
self.memory.store(
self.ldst_addr_uber(dst, base, offset, count, 0)?,
self.registers.as_ptr().add(usize::from(dst)).cast(),
count.into(),
)
}?;
)?;
Ok(())
}
@ -443,7 +435,7 @@ where
/// Perform binary operating over two registers
#[inline(always)]
unsafe fn binary_op<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) {
let OpsRRR(tg, a0, a1) = unsafe { self.decode() };
let OpsRRR(tg, a0, a1) = self.decode();
self.write_reg(
tg,
op(self.read_reg(a0).cast::<T>(), self.read_reg(a1).cast::<T>()),
@ -458,7 +450,7 @@ where
#[repr(packed)]
struct OpsRRImm<I>(OpsRR, I);
let OpsRRImm::<T>(OpsRR(tg, reg), imm) = unsafe { self.decode() };
let OpsRRImm::<T>(OpsRR(tg, reg), imm) = self.decode();
self.write_reg(tg, op(self.read_reg(reg).cast::<T>(), imm));
self.bump_pc::<OpsRRImm<T>>();
}
@ -466,7 +458,7 @@ where
/// Perform binary operation over register and shift immediate
#[inline(always)]
unsafe fn binary_op_shift<T: ValueVariant>(&mut self, op: impl Fn(T, u32) -> T) {
let OpsRRR(tg, a0, a1) = unsafe { self.decode() };
let OpsRRR(tg, a0, a1) = self.decode();
self.write_reg(
tg,
op(
@ -480,7 +472,7 @@ where
/// Perform binary operation over register and shift immediate
#[inline(always)]
unsafe fn binary_op_ims<T: ValueVariant>(&mut self, op: impl Fn(T, u32) -> T) {
let OpsRRB(tg, reg, imm) = unsafe { self.decode() };
let OpsRRB(tg, reg, imm) = self.decode();
self.write_reg(tg, op(self.read_reg(reg).cast::<T>(), imm.into()));
self.bump_pc::<OpsRRW>();
}
@ -539,7 +531,7 @@ where
/// Jump at `PC + #3` if ordering on `#0 <=> #1` is equal to expected
#[inline(always)]
unsafe fn cond_jmp<T: ValueVariant + Ord>(&mut self, expected: Ordering) {
let OpsRRP(a0, a1, ja) = unsafe { self.decode() };
let OpsRRP(a0, a1, ja) = self.decode();
if self
.read_reg(a0)
.cast::<T>()

View file

@ -6,4 +6,4 @@ default-run = "hbxrt"
[dependencies]
hbvm.path = "../hbvm"
memmap2 = "0.9"
nix = { version = "0.27", features = ["mman", "signal"] }

View file

@ -1,50 +1,65 @@
//! Holey Bytes Experimental Runtime
#![deny(unsafe_op_in_unsafe_fn)]
mod mem;
use {
hbvm::{mem::Address, Vm, VmRunOk},
memmap2::Mmap,
std::{env::args, fs::File, mem::MaybeUninit, process::exit},
nix::sys::mman::{mmap, MapFlags, ProtFlags},
std::{env::args, fs::File, num::NonZeroUsize, process::exit},
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
eprintln!("== HB×RT (Holey Bytes Experimental Runtime) v0.1 ==");
eprintln!("== HB×RT (Holey Bytes Linux Runtime) v0.1 ==");
eprintln!("[W] Currently supporting only flat images");
let mut args = args().skip(1);
let Some(image_path) = args.next() else {
let Some(image_path) = args().nth(1) else {
eprintln!("[E] Missing image path");
exit(1);
};
let dsls = args.next().as_deref() == Some("-L");
if cfg!(not(target_os = "linux")) && dsls {
eprintln!("[E] Unsupported platform for Direct Linux syscall mode");
exit(1);
}
if dsls {
eprintln!("[I] Direct Linux syscall mode activated")
}
// Allocate stack
let mut stack = Box::new(MaybeUninit::<[u8; 1024 * 1024 * 2]>::uninit());
eprintln!("[I] Stack allocated at {:p}", stack.as_ptr());
// Load program
eprintln!("[I] Loading image from \"{image_path}\"");
let file_handle = File::open(image_path)?;
let mmap = unsafe { Mmap::map(&file_handle) }?;
let file = File::open(image_path)?;
let ptr = unsafe {
mmap(
None,
NonZeroUsize::new(file.metadata()?.len() as usize).ok_or("File is empty")?,
ProtFlags::PROT_READ,
MapFlags::MAP_PRIVATE,
Some(&file),
0,
)?
};
eprintln!("[I] Image loaded at {:p}", mmap.as_ptr());
let mut vm = unsafe { Vm::<_, 0>::new(mem::HostMemory, Address::new(mmap.as_ptr() as u64)) };
vm.write_reg(254, stack.as_mut_ptr() as u64);
eprintln!("[I] Image loaded at {ptr:p}");
// Execute program
let mut vm = unsafe { Vm::<_, 0>::new(mem::HostMemory, Address::new(ptr as u64)) };
// Memory access fault handling
unsafe {
use nix::sys::signal;
extern "C" fn action(
_: std::ffi::c_int,
info: *mut nix::libc::siginfo_t,
_: *mut std::ffi::c_void,
) {
unsafe {
eprintln!("[E] Memory access fault at {:p}", (*info).si_addr());
exit(2);
}
}
signal::sigaction(
signal::Signal::SIGSEGV,
&nix::sys::signal::SigAction::new(
signal::SigHandler::SigAction(action),
signal::SaFlags::SA_NODEFER,
nix::sys::signalfd::SigSet::empty(),
),
)?;
}
let stat = loop {
match vm.run() {
Ok(VmRunOk::Breakpoint) => eprintln!(
@ -52,7 +67,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
vm.pc, vm.registers
),
Ok(VmRunOk::Timer) => (),
Ok(VmRunOk::Ecall) if dsls => unsafe {
Ok(VmRunOk::Ecall) => unsafe {
std::arch::asm!(
"syscall",
inlateout("rax") vm.registers[1].0,
@ -64,10 +79,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
in("r9") vm.registers[7].0,
)
},
Ok(VmRunOk::Ecall) => {
eprintln!("[E] General environment calls not supported");
exit(1);
}
Ok(VmRunOk::End) => break Ok(()),
Err(e) => break Err(e),
}

View file

@ -26,6 +26,6 @@ impl Memory for HostMemory {
#[inline]
unsafe fn prog_read<T: Copy>(&mut self, addr: Address) -> T {
unsafe { core::ptr::read(addr.get() as *const T) }
core::ptr::read(addr.get() as *const T)
}
}

22
spec.md
View file

@ -480,26 +480,18 @@ Program counter stays on the currently executed instruction
| long long | Long long integer | 8 |
| float | Single-precision float | 4 |
| double | Double-precision float | 8 |
| long double | Extended-precision float | 8 |
- Bikeshedding note: `long double` is now 8 bytes as
the base ISA does not support `f128`. an extension
for that should be made.
| long double | Extended-precision float | TBD |
## Call convention
- Registers r1 r31 are caller saved
- Registers r32 r255 are callee saved
- Registers r1 r30 are caller saved
- Registers r31 r255 are callee saved
| Register | Description | Saver |
|:-----------|:--------------------|:-------|
|:---------|:--------------------|:-------|
| r0 | Hard-wired zero | N/A |
| r1 - r2 | Return values | Caller |
| r2 - r11 | Function parameters | Caller |
| r12 - r30 | General purpose | Caller |
| r31 | Return address | Caller |
| r32 - r253 | General purpose | Callee |
| r254 | Stack pointer | Callee |
| r255 | Thread pointer | N/A |
| r30 | Return address | Caller |
- If return value is too big to fit r1, r2 is also used.
- Values larger than two double-words are passed by reference
If return value is too big to fit one register, r2 is also used.
TODO: Stack pointer, Thread pointer, ...