Compare commits
1 commit
trunk
...
soft-float
Author | SHA1 | Date | |
---|---|---|---|
Erin | 6348516f8c |
|
@ -1,4 +1,2 @@
|
|||
[alias]
|
||||
xtask = "r -p xtask --"
|
||||
wasm-build = "b --target wasm32-unknown-unknown --profile=small -Zbuild-std=core,alloc -Zbuild-std-features=optimize_for_size,panic_immediate_abort -p"
|
||||
wasm-build-debug = "b --target wasm32-unknown-unknown --profile=small-dev -Zbuild-std=core,alloc -Zbuild-std-features=optimize_for_size -p"
|
||||
|
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -1,13 +1 @@
|
|||
# garbage
|
||||
/target
|
||||
rustc-ice-*
|
||||
|
||||
# sqlite
|
||||
db.sqlite
|
||||
db.sqlite-journal
|
||||
|
||||
# assets
|
||||
/depell/src/*.gz
|
||||
/depell/src/*.wasm
|
||||
#**/*-sv.rs
|
||||
/bytecode/src/instrs.rs
|
||||
|
|
1567
Cargo.lock
generated
1567
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
49
Cargo.toml
49
Cargo.toml
|
@ -1,50 +1,3 @@
|
|||
cargo-features = ["profile-rustflags"]
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"bytecode",
|
||||
"vm",
|
||||
"xrt",
|
||||
"xtask",
|
||||
"lang",
|
||||
"depell",
|
||||
"depell/wasm-fmt",
|
||||
"depell/wasm-hbc",
|
||||
"depell/wasm-rt",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
hbbytecode = { path = "bytecode", default-features = false }
|
||||
hbvm = { path = "vm", default-features = false }
|
||||
hbxrt = { path = "xrt" }
|
||||
hblang = { path = "lang", default-features = false }
|
||||
hbjit = { path = "jit" }
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
#debug = true
|
||||
strip = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[profile.small]
|
||||
rustflags = ["-Zfmt-debug=none", "-Zlocation-detail=none"]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
strip = "debuginfo"
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
panic = "abort"
|
||||
|
||||
[profile.small-dev]
|
||||
inherits = "dev"
|
||||
opt-level = "z"
|
||||
strip = "debuginfo"
|
||||
panic = "abort"
|
||||
|
||||
[profile.fuzz]
|
||||
inherits = "dev"
|
||||
debug = true
|
||||
opt-level = 3
|
||||
panic = "abort"
|
||||
members = ["hbasm", "hbbytecode", "hbvm", "hbxrt", "xtask"]
|
||||
|
|
|
@ -1,204 +0,0 @@
|
|||
#![feature(iter_next_chunk)]
|
||||
|
||||
use std::{collections::HashSet, fmt::Write};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-changed=instructions.in");
|
||||
|
||||
let mut generated = String::new();
|
||||
gen_instrs(&mut generated)?;
|
||||
std::fs::write("src/instrs.rs", generated)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn gen_instrs(generated: &mut String) -> Result<(), Box<dyn std::error::Error>> {
|
||||
writeln!(generated, "#![expect(dead_code)]")?;
|
||||
writeln!(generated, "use crate::*;")?;
|
||||
|
||||
'_opcode_structs: {
|
||||
let mut seen = HashSet::new();
|
||||
for [.., args, _] in instructions() {
|
||||
if !seen.insert(args) {
|
||||
continue;
|
||||
}
|
||||
|
||||
writeln!(generated, "#[derive(Clone, Copy, Debug)]")?;
|
||||
writeln!(generated, "#[repr(packed)]")?;
|
||||
write!(generated, "pub struct Ops{args}(")?;
|
||||
let mut first = true;
|
||||
for ch in args.chars().filter(|&ch| ch != 'N') {
|
||||
if !std::mem::take(&mut first) {
|
||||
write!(generated, ",")?;
|
||||
}
|
||||
write!(generated, "pub Op{ch}")?;
|
||||
}
|
||||
writeln!(generated, ");")?;
|
||||
writeln!(generated, "unsafe impl BytecodeItem for Ops{args} {{}}")?;
|
||||
}
|
||||
}
|
||||
|
||||
'_max_size: {
|
||||
let max = instructions()
|
||||
.map(
|
||||
|[_, _, ty, _]| {
|
||||
if ty == "N" {
|
||||
1
|
||||
} else {
|
||||
iter_args(ty).map(arg_to_width).sum::<usize>() + 1
|
||||
}
|
||||
},
|
||||
)
|
||||
.max()
|
||||
.unwrap();
|
||||
|
||||
writeln!(generated, "pub const MAX_SIZE: usize = {max};")?;
|
||||
}
|
||||
|
||||
'_encoders: {
|
||||
for [op, name, ty, doc] in instructions() {
|
||||
writeln!(generated, "/// {}", doc.trim_matches('"'))?;
|
||||
let name = name.to_lowercase();
|
||||
let args = comma_sep(
|
||||
iter_args(ty)
|
||||
.enumerate()
|
||||
.map(|(i, c)| format!("{}{i}: {}", arg_to_name(c), arg_to_type(c))),
|
||||
);
|
||||
writeln!(generated, "pub fn {name}({args}) -> (usize, [u8; MAX_SIZE]) {{")?;
|
||||
let arg_names =
|
||||
comma_sep(iter_args(ty).enumerate().map(|(i, c)| format!("{}{i}", arg_to_name(c))));
|
||||
writeln!(generated, " unsafe {{ crate::encode({ty}({op}, {arg_names})) }}")?;
|
||||
writeln!(generated, "}}")?;
|
||||
}
|
||||
}
|
||||
|
||||
'_structs: {
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for [_, _, ty, _] in instructions() {
|
||||
if !seen.insert(ty) {
|
||||
continue;
|
||||
}
|
||||
let types = comma_sep(iter_args(ty).map(arg_to_type).map(|s| s.to_string()));
|
||||
writeln!(generated, "#[repr(packed)] pub struct {ty}(u8, {types});")?;
|
||||
}
|
||||
}
|
||||
|
||||
'_name_list: {
|
||||
writeln!(generated, "pub const COUNT: u8 = {};", instructions().count())?;
|
||||
}
|
||||
|
||||
let instr = "Instr";
|
||||
let oper = "Oper";
|
||||
|
||||
'_instr_enum: {
|
||||
writeln!(generated, "#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)]")?;
|
||||
writeln!(generated, "pub enum {instr} {{")?;
|
||||
for [id, name, ..] in instructions() {
|
||||
writeln!(generated, " {name} = {id},")?;
|
||||
}
|
||||
writeln!(generated, "}}")?;
|
||||
}
|
||||
|
||||
'_arg_kind: {
|
||||
writeln!(generated, "#[derive(Debug, Clone, Copy, PartialEq, Eq)]")?;
|
||||
writeln!(generated, "pub enum {oper} {{")?;
|
||||
let mut seen = HashSet::new();
|
||||
for ty in instructions().flat_map(|[.., ty, _]| iter_args(ty)) {
|
||||
if !seen.insert(ty) {
|
||||
continue;
|
||||
}
|
||||
writeln!(generated, " {ty}({}),", arg_to_type(ty))?;
|
||||
}
|
||||
writeln!(generated, "}}")?;
|
||||
}
|
||||
|
||||
'_parse_opers: {
|
||||
writeln!(
|
||||
generated,
|
||||
"/// This assumes the instruction byte is still at the beginning of the buffer"
|
||||
)?;
|
||||
writeln!(generated, "#[cfg(feature = \"disasm\")]")?;
|
||||
writeln!(generated, "pub fn parse_args(bytes: &mut &[u8], kind: {instr}, buf: &mut alloc::vec::Vec<{oper}>) -> Option<()> {{")?;
|
||||
writeln!(generated, " match kind {{")?;
|
||||
let mut instrs = instructions().collect::<Vec<_>>();
|
||||
instrs.sort_unstable_by_key(|&[.., ty, _]| ty);
|
||||
for group in instrs.chunk_by(|[.., a, _], [.., b, _]| a == b) {
|
||||
let ty = group[0][2];
|
||||
for &[_, name, ..] in group {
|
||||
writeln!(generated, " | {instr}::{name}")?;
|
||||
}
|
||||
generated.pop();
|
||||
writeln!(generated, " => {{")?;
|
||||
if iter_args(ty).count() != 0 {
|
||||
writeln!(generated, " let data = crate::decode::<{ty}>(bytes)?;")?;
|
||||
writeln!(
|
||||
generated,
|
||||
" buf.extend([{}]);",
|
||||
comma_sep(
|
||||
iter_args(ty).zip(1u32..).map(|(t, i)| format!("{oper}::{t}(data.{i})"))
|
||||
)
|
||||
)?;
|
||||
} else {
|
||||
writeln!(generated, " crate::decode::<{ty}>(bytes)?;")?;
|
||||
}
|
||||
|
||||
writeln!(generated, " }}")?;
|
||||
}
|
||||
writeln!(generated, " }}")?;
|
||||
writeln!(generated, " Some(())")?;
|
||||
writeln!(generated, "}}")?;
|
||||
}
|
||||
|
||||
std::fs::write("src/instrs.rs", generated)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn comma_sep(items: impl Iterator<Item = String>) -> String {
|
||||
items.map(|item| item.to_string()).collect::<Vec<_>>().join(", ")
|
||||
}
|
||||
|
||||
fn instructions() -> impl Iterator<Item = [&'static str; 4]> {
|
||||
include_str!("instructions.in")
|
||||
.lines()
|
||||
.filter_map(|line| line.strip_suffix(';'))
|
||||
.map(|line| line.splitn(4, ',').map(str::trim).next_chunk().unwrap())
|
||||
}
|
||||
|
||||
fn arg_to_type(arg: char) -> &'static str {
|
||||
match arg {
|
||||
'R' | 'B' => "u8",
|
||||
'H' => "u16",
|
||||
'W' => "u32",
|
||||
'D' | 'A' => "u64",
|
||||
'P' => "i16",
|
||||
'O' => "i32",
|
||||
_ => panic!("unknown type: {}", arg),
|
||||
}
|
||||
}
|
||||
|
||||
fn arg_to_width(arg: char) -> usize {
|
||||
match arg {
|
||||
'R' | 'B' => 1,
|
||||
'H' => 2,
|
||||
'W' => 4,
|
||||
'D' | 'A' => 8,
|
||||
'P' => 2,
|
||||
'O' => 4,
|
||||
_ => panic!("unknown type: {}", arg),
|
||||
}
|
||||
}
|
||||
|
||||
fn arg_to_name(arg: char) -> &'static str {
|
||||
match arg {
|
||||
'R' => "reg",
|
||||
'B' | 'H' | 'W' | 'D' => "imm",
|
||||
'P' | 'O' => "offset",
|
||||
'A' => "addr",
|
||||
_ => panic!("unknown type: {}", arg),
|
||||
}
|
||||
}
|
||||
|
||||
fn iter_args(ty: &'static str) -> impl Iterator<Item = char> {
|
||||
ty.chars().filter(|c| *c != 'N')
|
||||
}
|
|
@ -1,284 +0,0 @@
|
|||
#![no_std]
|
||||
|
||||
#[cfg(feature = "disasm")]
|
||||
extern crate alloc;
|
||||
|
||||
pub use crate::instrs::*;
|
||||
use core::convert::TryFrom;
|
||||
|
||||
mod instrs;
|
||||
|
||||
type OpR = u8;
|
||||
|
||||
type OpA = u64;
|
||||
type OpO = i32;
|
||||
type OpP = i16;
|
||||
|
||||
type OpB = u8;
|
||||
type OpH = u16;
|
||||
type OpW = u32;
|
||||
type OpD = u64;
|
||||
|
||||
/// # Safety
|
||||
/// Has to be valid to be decoded from bytecode.
|
||||
pub unsafe trait BytecodeItem {}
|
||||
unsafe impl BytecodeItem for u8 {}
|
||||
|
||||
impl TryFrom<u8> for Instr {
|
||||
type Error = u8;
|
||||
|
||||
#[inline]
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
#[cold]
|
||||
fn failed(value: u8) -> Result<Instr, u8> {
|
||||
Err(value)
|
||||
}
|
||||
|
||||
if value < COUNT {
|
||||
unsafe { Ok(core::mem::transmute::<u8, Instr>(value)) }
|
||||
} else {
|
||||
failed(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn encode<T>(instr: T) -> (usize, [u8; instrs::MAX_SIZE]) {
|
||||
let mut buf = [0; instrs::MAX_SIZE];
|
||||
core::ptr::write(buf.as_mut_ptr() as *mut T, instr);
|
||||
(core::mem::size_of::<T>(), buf)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
#[cfg(feature = "disasm")]
|
||||
fn decode<T>(binary: &mut &[u8]) -> Option<T> {
|
||||
let (front, rest) = core::mem::take(binary).split_at_checked(core::mem::size_of::<T>())?;
|
||||
*binary = rest;
|
||||
unsafe { Some(core::ptr::read(front.as_ptr() as *const T)) }
|
||||
}
|
||||
|
||||
/// Rounding mode
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum RoundingMode {
|
||||
NearestEven = 0,
|
||||
Truncate = 1,
|
||||
Up = 2,
|
||||
Down = 3,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for RoundingMode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
(value <= 3).then(|| unsafe { core::mem::transmute(value) }).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "disasm")]
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum DisasmItem {
|
||||
Func,
|
||||
Global,
|
||||
}
|
||||
|
||||
#[cfg(feature = "disasm")]
|
||||
#[derive(Debug)]
|
||||
pub enum DisasmError<'a> {
|
||||
InvalidInstruction(u8),
|
||||
InstructionOutOfBounds(&'a str),
|
||||
FmtFailed(core::fmt::Error),
|
||||
HasOutOfBoundsJumps,
|
||||
}
|
||||
|
||||
#[cfg(feature = "disasm")]
|
||||
impl From<core::fmt::Error> for DisasmError<'_> {
|
||||
fn from(value: core::fmt::Error) -> Self {
|
||||
Self::FmtFailed(value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "disasm")]
|
||||
impl core::fmt::Display for DisasmError<'_> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match *self {
|
||||
DisasmError::InvalidInstruction(b) => write!(f, "invalid instruction opcode: {b}"),
|
||||
DisasmError::InstructionOutOfBounds(name) => {
|
||||
write!(f, "instruction would go out of bounds of {name} symbol")
|
||||
}
|
||||
DisasmError::FmtFailed(error) => write!(f, "fmt failed: {error}"),
|
||||
DisasmError::HasOutOfBoundsJumps => write!(
|
||||
f,
|
||||
"the code contained jumps that dont got neither to a \
|
||||
valid symbol or local insturction"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "disasm")]
|
||||
impl core::error::Error for DisasmError<'_> {}
|
||||
|
||||
#[cfg(feature = "disasm")]
|
||||
pub fn disasm<'a>(
|
||||
binary: &mut &[u8],
|
||||
functions: &alloc::collections::BTreeMap<u32, (&'a str, u32, DisasmItem)>,
|
||||
out: &mut alloc::string::String,
|
||||
mut eca_handler: impl FnMut(&mut &[u8]),
|
||||
) -> Result<(), DisasmError<'a>> {
|
||||
use {
|
||||
self::instrs::Instr,
|
||||
alloc::{
|
||||
collections::btree_map::{BTreeMap, Entry},
|
||||
vec::Vec,
|
||||
},
|
||||
core::{convert::TryInto, fmt::Write},
|
||||
};
|
||||
|
||||
fn instr_from_byte(b: u8) -> Result<Instr, DisasmError<'static>> {
|
||||
b.try_into().map_err(DisasmError::InvalidInstruction)
|
||||
}
|
||||
|
||||
let mut labels = BTreeMap::<u32, u32>::default();
|
||||
let mut buf = Vec::<instrs::Oper>::new();
|
||||
let mut has_oob = false;
|
||||
|
||||
'_offset_pass: for (&off, &(name, len, kind)) in functions.iter() {
|
||||
if matches!(kind, DisasmItem::Global) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let prev = *binary;
|
||||
|
||||
*binary = &binary[off as usize..];
|
||||
|
||||
let mut label_count = 0;
|
||||
while let Some(&byte) = binary.first() {
|
||||
let offset: i32 = (prev.len() - binary.len()).try_into().unwrap();
|
||||
if offset as u32 == off + len {
|
||||
break;
|
||||
}
|
||||
let Ok(inst) = instr_from_byte(byte) else { break };
|
||||
instrs::parse_args(binary, inst, &mut buf)
|
||||
.ok_or(DisasmError::InstructionOutOfBounds(name))?;
|
||||
|
||||
for op in buf.drain(..) {
|
||||
let rel = match op {
|
||||
instrs::Oper::O(rel) => rel,
|
||||
instrs::Oper::P(rel) => rel.into(),
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let global_offset: u32 = (offset + rel).try_into().unwrap();
|
||||
if functions.get(&global_offset).is_some() {
|
||||
continue;
|
||||
}
|
||||
label_count += match labels.entry(global_offset) {
|
||||
Entry::Occupied(_) => 0,
|
||||
Entry::Vacant(entry) => {
|
||||
entry.insert(label_count);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(inst, Instr::ECA) {
|
||||
eca_handler(binary);
|
||||
}
|
||||
}
|
||||
|
||||
*binary = prev;
|
||||
}
|
||||
|
||||
let mut ordered = functions.iter().collect::<Vec<_>>();
|
||||
ordered.sort_unstable_by_key(|(_, (name, _, _))| name);
|
||||
|
||||
'_dump: for (&off, &(name, len, kind)) in ordered {
|
||||
if matches!(kind, DisasmItem::Global) {
|
||||
continue;
|
||||
}
|
||||
let prev = *binary;
|
||||
|
||||
writeln!(out, "{name}:")?;
|
||||
|
||||
*binary = &binary[off as usize..];
|
||||
while let Some(&byte) = binary.first() {
|
||||
let offset: i32 = (prev.len() - binary.len()).try_into().unwrap();
|
||||
if offset as u32 == off + len {
|
||||
break;
|
||||
}
|
||||
let Ok(inst) = instr_from_byte(byte) else {
|
||||
writeln!(out, "invalid instr {byte}")?;
|
||||
break;
|
||||
};
|
||||
instrs::parse_args(binary, inst, &mut buf).unwrap();
|
||||
|
||||
if let Some(label) = labels.get(&offset.try_into().unwrap()) {
|
||||
write!(out, "{:>2}: ", label)?;
|
||||
} else {
|
||||
write!(out, " ")?;
|
||||
}
|
||||
|
||||
write!(out, "{inst:<8?} ")?;
|
||||
|
||||
'a: for (i, op) in buf.drain(..).enumerate() {
|
||||
if i != 0 {
|
||||
write!(out, ", ")?;
|
||||
}
|
||||
|
||||
let rel = 'b: {
|
||||
match op {
|
||||
instrs::Oper::O(rel) => break 'b rel,
|
||||
instrs::Oper::P(rel) => break 'b rel.into(),
|
||||
instrs::Oper::R(r) => write!(out, "r{r}")?,
|
||||
instrs::Oper::B(b) => write!(out, "{b}b")?,
|
||||
instrs::Oper::H(h) => write!(out, "{h}h")?,
|
||||
instrs::Oper::W(w) => write!(out, "{w}w")?,
|
||||
instrs::Oper::D(d) if (d as i64) < 0 => write!(out, "{}d", d as i64)?,
|
||||
instrs::Oper::D(d) => write!(out, "{d}d")?,
|
||||
instrs::Oper::A(a) => write!(out, "{a}a")?,
|
||||
}
|
||||
|
||||
continue 'a;
|
||||
};
|
||||
|
||||
let global_offset: u32 = (offset + rel).try_into().unwrap();
|
||||
if let Some(&(name, ..)) = functions.get(&global_offset) {
|
||||
if name.contains('\0') {
|
||||
write!(out, ":{name:?}")?;
|
||||
} else {
|
||||
write!(out, ":{name}")?;
|
||||
}
|
||||
} else {
|
||||
let local_has_oob = global_offset < off
|
||||
|| global_offset > off + len
|
||||
|| prev
|
||||
.get(global_offset as usize)
|
||||
.map_or(true, |&b| instr_from_byte(b).is_err())
|
||||
|| prev[global_offset as usize] == 0;
|
||||
has_oob |= local_has_oob;
|
||||
let label = labels.get(&global_offset).unwrap();
|
||||
if local_has_oob {
|
||||
write!(out, "!!!!!!!!!{rel}")?;
|
||||
} else {
|
||||
write!(out, ":{label}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
writeln!(out)?;
|
||||
|
||||
if matches!(inst, Instr::ECA) {
|
||||
eca_handler(binary);
|
||||
}
|
||||
}
|
||||
|
||||
*binary = prev;
|
||||
}
|
||||
|
||||
if has_oob {
|
||||
return Err(DisasmError::HasOutOfBoundsJumps);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
[package]
|
||||
name = "depell"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
argon2 = "0.5.3"
|
||||
axum = "0.7.7"
|
||||
axum-server = { version = "0.7.1", optional = true, features = ["rustls", "tls-rustls"] }
|
||||
const_format = "0.2.33"
|
||||
getrandom = "0.2.15"
|
||||
hblang.workspace = true
|
||||
htmlm = "0.5.0"
|
||||
log = "0.4.22"
|
||||
rand_core = { version = "0.6.4", features = ["getrandom"] }
|
||||
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
time = "0.3.36"
|
||||
tokio = { version = "1.40.0", features = ["rt"] }
|
||||
|
||||
[features]
|
||||
#default = ["tls"]
|
||||
tls = ["dep:axum-server"]
|
|
@ -1,14 +0,0 @@
|
|||
# Depell
|
||||
|
||||
Depell is a website that allows users to import/post/run hblang code and create huge dependency graphs. Its currently hosted at https://depell.mlokis.tech.
|
||||
|
||||
## Local Development
|
||||
|
||||
Prerequirements:
|
||||
- rust nigthly toolchain: install rust from [here](https://www.rust-lang.org/tools/install)
|
||||
|
||||
```bash
|
||||
rustup default nightly
|
||||
cargo xtask watch-depell-debug
|
||||
# browser http://localhost:8080
|
||||
```
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg>
|
Before Width: | Height: | Size: 279 B |
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M320-200v-560l440 280-440 280Zm80-280Zm0 134 210-134-210-134v268Z"/></svg>
|
Before Width: | Height: | Size: 190 B |
|
@ -1,207 +0,0 @@
|
|||
* {
|
||||
font-family: var(--font);
|
||||
}
|
||||
|
||||
body {
|
||||
--primary: light-dark(white, #181A1B);
|
||||
--secondary: light-dark(#EFEFEF, #212425);
|
||||
--timestamp: light-dark(#555555, #AAAAAA);
|
||||
--error: #ff3333;
|
||||
}
|
||||
|
||||
body {
|
||||
--small-gap: 5px;
|
||||
--font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
--monospace: 'Courier New', Courier, monospace;
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
section:last-child {
|
||||
display: flex;
|
||||
gap: var(--small-gap);
|
||||
}
|
||||
}
|
||||
|
||||
main {
|
||||
margin-top: var(--small-gap);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--small-gap);
|
||||
}
|
||||
}
|
||||
|
||||
div.preview {
|
||||
margin: var(--small-gap) 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--small-gap);
|
||||
|
||||
div.info {
|
||||
display: flex;
|
||||
gap: var(--small-gap);
|
||||
|
||||
span[apply=timestamp] {
|
||||
color: var(--timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
div.stat {
|
||||
display: flex;
|
||||
|
||||
svg {
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
div.code {
|
||||
position: relative;
|
||||
|
||||
nav {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
padding: var(--small-gap);
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: black;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--small-gap);
|
||||
|
||||
|
||||
.error {
|
||||
color: var(--error);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
textarea {
|
||||
outline: none;
|
||||
border: none;
|
||||
background: var(--secondary);
|
||||
padding: var(--small-gap);
|
||||
padding-top: calc(var(--small-gap) * 1.5);
|
||||
font-family: var(--monospace);
|
||||
resize: none;
|
||||
tab-size: 4;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--secondary);
|
||||
padding: var(--small-gap);
|
||||
padding-top: calc(var(--small-gap) * 1.5);
|
||||
margin: 0px;
|
||||
font-family: var(--monospace);
|
||||
tab-size: 4;
|
||||
overflow-x: auto;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: inherit;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: var(--secondary);
|
||||
padding: var(--small-gap);
|
||||
}
|
||||
|
||||
input:is(:hover, :focus) {
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: inherit;
|
||||
background: var(--secondary);
|
||||
}
|
||||
|
||||
button:hover:not(:active) {
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
div#code-editor {
|
||||
display: flex;
|
||||
position: relative;
|
||||
|
||||
textarea {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
span#code-size {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
div#dep-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--small-gap);
|
||||
|
||||
section {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
gap: var(--small-gap);
|
||||
|
||||
div {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.syn {
|
||||
font-family: var(--monospace);
|
||||
|
||||
&.Comment {
|
||||
color: #939f91;
|
||||
}
|
||||
|
||||
&.Keyword {
|
||||
color: #f85552;
|
||||
}
|
||||
|
||||
&.Identifier,
|
||||
&.Directive {
|
||||
color: #3a94c5;
|
||||
}
|
||||
|
||||
/* &.Number {} */
|
||||
|
||||
&.String {
|
||||
color: #8da101;
|
||||
}
|
||||
|
||||
&.Op,
|
||||
&.Assign {
|
||||
color: #f57d26;
|
||||
}
|
||||
|
||||
&.Paren,
|
||||
&.Bracket,
|
||||
&.Comma,
|
||||
&.Dot,
|
||||
&.Ctor,
|
||||
&.Colon {
|
||||
color: light-dark(#5c6a72, #999999);
|
||||
}
|
||||
}
|
|
@ -1,551 +0,0 @@
|
|||
/// @ts-check
|
||||
|
||||
/** @return {never} */
|
||||
function never() { throw new Error() }
|
||||
|
||||
/**@type{WebAssembly.Instance}*/ let hbcInstance;
|
||||
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let hbcInstaceFuture;
|
||||
async function getHbcInstance() {
|
||||
hbcInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbc.wasm"), {});
|
||||
return hbcInstance ??= (await hbcInstaceFuture).instance;
|
||||
}
|
||||
|
||||
const stack_pointer_offset = 1 << 20;
|
||||
|
||||
/** @param {WebAssembly.Instance} instance @param {Post[]} packages @param {number} fuel
|
||||
* @returns {string} */
|
||||
function compileCode(instance, packages, fuel = 100) {
|
||||
let {
|
||||
INPUT, INPUT_LEN,
|
||||
LOG_MESSAGES, LOG_MESSAGES_LEN,
|
||||
memory, compile_and_run,
|
||||
} = instance.exports;
|
||||
|
||||
if (!(true
|
||||
&& memory instanceof WebAssembly.Memory
|
||||
&& INPUT instanceof WebAssembly.Global
|
||||
&& INPUT_LEN instanceof WebAssembly.Global
|
||||
&& LOG_MESSAGES instanceof WebAssembly.Global
|
||||
&& LOG_MESSAGES_LEN instanceof WebAssembly.Global
|
||||
&& typeof compile_and_run === "function"
|
||||
)) never();
|
||||
|
||||
const codeLength = packPosts(packages, new DataView(memory.buffer, INPUT.value));
|
||||
new DataView(memory.buffer).setUint32(INPUT_LEN.value, codeLength, true);
|
||||
|
||||
runWasmFunction(instance, compile_and_run, fuel);
|
||||
return bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN).trim();
|
||||
}
|
||||
|
||||
/**@type{WebAssembly.Instance}*/ let fmtInstance;
|
||||
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let fmtInstaceFuture;
|
||||
async function getFmtInstance() {
|
||||
fmtInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {});
|
||||
return fmtInstance ??= (await fmtInstaceFuture).instance;
|
||||
}
|
||||
|
||||
/** @param {WebAssembly.Instance} instance @param {string} code @param {"tok" | "fmt" | "minify"} action
|
||||
* @returns {string | Uint8Array | undefined} */
|
||||
function modifyCode(instance, code, action) {
|
||||
let {
|
||||
INPUT, INPUT_LEN,
|
||||
OUTPUT, OUTPUT_LEN,
|
||||
memory, fmt, tok, minify
|
||||
} = instance.exports;
|
||||
|
||||
let funs = { fmt, tok, minify };
|
||||
let fun = funs[action];
|
||||
if (!(true
|
||||
&& memory instanceof WebAssembly.Memory
|
||||
&& INPUT instanceof WebAssembly.Global
|
||||
&& INPUT_LEN instanceof WebAssembly.Global
|
||||
&& OUTPUT instanceof WebAssembly.Global
|
||||
&& OUTPUT_LEN instanceof WebAssembly.Global
|
||||
&& funs.hasOwnProperty(action)
|
||||
&& typeof fun === "function"
|
||||
)) never();
|
||||
|
||||
if (action !== "fmt") {
|
||||
INPUT = OUTPUT;
|
||||
INPUT_LEN = OUTPUT_LEN;
|
||||
}
|
||||
|
||||
let dw = new DataView(memory.buffer);
|
||||
dw.setUint32(INPUT_LEN.value, code.length, true);
|
||||
new Uint8Array(memory.buffer, INPUT.value).set(new TextEncoder().encode(code));
|
||||
|
||||
if (!runWasmFunction(instance, fun)) {
|
||||
return undefined;
|
||||
}
|
||||
if (action === "tok") {
|
||||
return bufSlice(memory, OUTPUT, OUTPUT_LEN);
|
||||
} else {
|
||||
return bufToString(memory, OUTPUT, OUTPUT_LEN);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** @param {WebAssembly.Instance} instance @param {CallableFunction} func @param {any[]} args
|
||||
* @returns {boolean} */
|
||||
function runWasmFunction(instance, func, ...args) {
|
||||
const { PANIC_MESSAGE, PANIC_MESSAGE_LEN, memory, stack_pointer } = instance.exports;
|
||||
if (!(true
|
||||
&& memory instanceof WebAssembly.Memory
|
||||
&& stack_pointer instanceof WebAssembly.Global
|
||||
)) never();
|
||||
const ptr = stack_pointer.value;
|
||||
try {
|
||||
func(...args);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (error instanceof WebAssembly.RuntimeError
|
||||
&& error.message == "unreachable"
|
||||
&& PANIC_MESSAGE instanceof WebAssembly.Global
|
||||
&& PANIC_MESSAGE_LEN instanceof WebAssembly.Global) {
|
||||
console.error(bufToString(memory, PANIC_MESSAGE, PANIC_MESSAGE_LEN), error);
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
stack_pointer.value = ptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** @typedef {Object} Post
|
||||
* @property {string} path
|
||||
* @property {string} code */
|
||||
|
||||
/** @param {Post[]} posts @param {DataView} view @returns {number} */
|
||||
function packPosts(posts, view) {
|
||||
const enc = new TextEncoder(), buf = new Uint8Array(view.buffer, view.byteOffset);
|
||||
let len = 0; for (const post of posts) {
|
||||
view.setUint16(len, post.path.length, true); len += 2;
|
||||
buf.set(enc.encode(post.path), len); len += post.path.length;
|
||||
view.setUint16(len, post.code.length, true); len += 2;
|
||||
buf.set(enc.encode(post.code), len); len += post.code.length;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
/** @param {WebAssembly.Memory} mem
|
||||
* @param {WebAssembly.Global} ptr
|
||||
* @param {WebAssembly.Global} len
|
||||
* @return {Uint8Array} */
|
||||
function bufSlice(mem, ptr, len) {
|
||||
return new Uint8Array(mem.buffer, ptr.value,
|
||||
new DataView(mem.buffer).getUint32(len.value, true));
|
||||
}
|
||||
|
||||
/** @param {WebAssembly.Memory} mem
|
||||
* @param {WebAssembly.Global} ptr
|
||||
* @param {WebAssembly.Global} len
|
||||
* @return {string} */
|
||||
function bufToString(mem, ptr, len) {
|
||||
const res = new TextDecoder()
|
||||
.decode(new Uint8Array(mem.buffer, ptr.value,
|
||||
new DataView(mem.buffer).getUint32(len.value, true)));
|
||||
new DataView(mem.buffer).setUint32(len.value, 0, true);
|
||||
return res;
|
||||
}
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
function wireUp(target) {
|
||||
execApply(target);
|
||||
cacheInputs(target);
|
||||
bindCodeEdit(target);
|
||||
bindTextareaAutoResize(target);
|
||||
}
|
||||
|
||||
const importRe = /@use\s*\(\s*"(([^"]|\\")+)"\s*\)/g;
|
||||
|
||||
/** @param {WebAssembly.Instance} fmt
|
||||
* @param {string} code
|
||||
* @param {string[]} roots
|
||||
* @param {Post[]} buf
|
||||
* @param {Set<string>} prevRoots
|
||||
* @returns {void} */
|
||||
function loadCachedPackages(fmt, code, roots, buf, prevRoots) {
|
||||
buf[0].code = code;
|
||||
|
||||
roots.length = 0;
|
||||
let changed = false;
|
||||
for (const match of code.matchAll(importRe)) {
|
||||
changed ||= !prevRoots.has(match[1]);
|
||||
roots.push(match[1]);
|
||||
}
|
||||
|
||||
if (!changed) return;
|
||||
buf.length = 1;
|
||||
prevRoots.clear();
|
||||
|
||||
for (let imp = roots.pop(); imp !== undefined; imp = roots.pop()) {
|
||||
if (prevRoots.has(imp)) continue; prevRoots.add(imp);
|
||||
|
||||
const fmtd = modifyCode(fmt, localStorage.getItem("package-" + imp) ?? never(), "fmt");
|
||||
if (typeof fmtd != "string") never();
|
||||
buf.push({ path: imp, code: fmtd });
|
||||
for (const match of buf[buf.length - 1].code.matchAll(importRe)) {
|
||||
roots.push(match[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**@type{Set<string>}*/ const prevRoots = new Set();
|
||||
/**@typedef {Object} PackageCtx
|
||||
* @property {AbortController} [cancelation]
|
||||
* @property {string[]} keyBuf
|
||||
* @property {Set<string>} prevParams
|
||||
* @property {HTMLTextAreaElement} [edit] */
|
||||
|
||||
/** @param {string} source @param {Set<string>} importDiff @param {HTMLPreElement} errors @param {PackageCtx} ctx */
|
||||
async function fetchPackages(source, importDiff, errors, ctx) {
|
||||
importDiff.clear();
|
||||
for (const match of source.matchAll(importRe)) {
|
||||
if (localStorage["package-" + match[1]]) continue;
|
||||
importDiff.add(match[1]);
|
||||
}
|
||||
|
||||
if (importDiff.size !== 0 && (ctx.prevParams.size != importDiff.size
|
||||
|| [...ctx.prevParams.keys()].every(e => importDiff.has(e)))) {
|
||||
if (ctx.cancelation) ctx.cancelation.abort();
|
||||
ctx.prevParams.clear();
|
||||
ctx.prevParams = new Set([...importDiff]);
|
||||
ctx.cancelation = new AbortController();
|
||||
|
||||
ctx.keyBuf.length = 0;
|
||||
ctx.keyBuf.push(...importDiff.keys());
|
||||
|
||||
errors.textContent = "fetching: " + ctx.keyBuf.join(", ");
|
||||
|
||||
await fetch(`/code`, {
|
||||
method: "POST",
|
||||
signal: ctx.cancelation.signal,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(ctx.keyBuf),
|
||||
}).then(async e => {
|
||||
try {
|
||||
const json = await e.json();
|
||||
if (e.status == 200) {
|
||||
for (const [key, value] of Object.entries(json)) {
|
||||
localStorage["package-" + key] = value;
|
||||
}
|
||||
const missing = ctx.keyBuf.filter(i => json[i] === undefined);
|
||||
if (missing.length !== 0) {
|
||||
errors.textContent = "deps not found: " + missing.join(", ");
|
||||
} else {
|
||||
ctx.cancelation = undefined;
|
||||
ctx.edit?.dispatchEvent(new InputEvent("input"));
|
||||
}
|
||||
}
|
||||
} catch (er) {
|
||||
errors.textContent = "completely failed to fetch ("
|
||||
+ e.status + "): " + ctx.keyBuf.join(", ");
|
||||
console.error(e, er);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
async function bindCodeEdit(target) {
|
||||
const edit = target.querySelector("#code-edit");
|
||||
if (!(edit instanceof HTMLTextAreaElement)) return;
|
||||
|
||||
const codeSize = target.querySelector("#code-size");
|
||||
const errors = target.querySelector("#compiler-output");
|
||||
if (!(true
|
||||
&& codeSize instanceof HTMLSpanElement
|
||||
&& errors instanceof HTMLPreElement
|
||||
)) never();
|
||||
|
||||
const MAX_CODE_SIZE = parseInt(codeSize.innerHTML);
|
||||
if (Number.isNaN(MAX_CODE_SIZE)) never();
|
||||
|
||||
const hbc = await getHbcInstance(), fmt = await getFmtInstance();
|
||||
let importDiff = new Set();
|
||||
/**@type{Post[]}*/
|
||||
const packages = [{ path: "local.hb", code: "" }];
|
||||
const debounce = 100;
|
||||
let timeout = 0;
|
||||
const ctx = { keyBuf: [], prevParams: new Set(), edit };
|
||||
|
||||
prevRoots.clear();
|
||||
|
||||
const onInput = () => {
|
||||
fetchPackages(edit.value, importDiff, errors, ctx);
|
||||
|
||||
if (ctx.cancelation && importDiff.size !== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadCachedPackages(fmt, edit.value, ctx.keyBuf, packages, prevRoots);
|
||||
|
||||
errors.textContent = compileCode(hbc, packages);
|
||||
const minified_size = modifyCode(fmt, edit.value, "minify")?.length;
|
||||
if (minified_size) {
|
||||
codeSize.textContent = (MAX_CODE_SIZE - minified_size) + "";
|
||||
const perc = Math.min(100, Math.floor(100 * (minified_size / MAX_CODE_SIZE)));
|
||||
codeSize.style.color = `color-mix(in srgb, light-dark(black, white), var(--error) ${perc}%)`;
|
||||
}
|
||||
timeout = 0;
|
||||
};
|
||||
|
||||
edit.addEventListener("input", () => {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(onInput, debounce)
|
||||
});
|
||||
edit.dispatchEvent(new InputEvent("input"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Array<string>}
|
||||
* to be synched with `enum TokenGroup` in bytecode/src/fmt.rs */
|
||||
const TOK_CLASSES = [
|
||||
'Blank',
|
||||
'Comment',
|
||||
'Keyword',
|
||||
'Identifier',
|
||||
'Directive',
|
||||
'Number',
|
||||
'String',
|
||||
'Op',
|
||||
'Assign',
|
||||
'Paren',
|
||||
'Bracket',
|
||||
'Colon',
|
||||
'Comma',
|
||||
'Dot',
|
||||
'Ctor',
|
||||
];
|
||||
|
||||
/** @type {{ [key: string]: (el: HTMLElement) => void | Promise<void> }} */
|
||||
const applyFns = {
|
||||
timestamp: (el) => {
|
||||
const timestamp = el.innerText;
|
||||
const date = new Date(parseInt(timestamp) * 1000);
|
||||
el.innerText = date.toLocaleString();
|
||||
},
|
||||
fmt,
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} target */
|
||||
async function fmt(target) {
|
||||
const code = target.innerText;
|
||||
const instance = await getFmtInstance();
|
||||
const decoder = new TextDecoder('utf-8');
|
||||
const fmt = modifyCode(instance, code, 'fmt');
|
||||
if (typeof fmt !== "string") never()
|
||||
const codeBytes = new TextEncoder().encode(fmt);
|
||||
const tok = modifyCode(instance, fmt, 'tok');
|
||||
if (!(tok instanceof Uint8Array)) never();
|
||||
target.innerHTML = '';
|
||||
let start = 0;
|
||||
let kind = tok[0];
|
||||
for (let ii = 1; ii <= tok.length; ii += 1) {
|
||||
// split over same tokens and buffer end
|
||||
if (tok[ii] === kind && ii < tok.length) {
|
||||
continue;
|
||||
}
|
||||
const text = decoder.decode(codeBytes.subarray(start, ii));
|
||||
const textNode = document.createTextNode(text);;
|
||||
if (kind === 0) {
|
||||
target.appendChild(textNode);
|
||||
} else {
|
||||
const el = document.createElement('span');
|
||||
el.classList.add('syn');
|
||||
el.classList.add(TOK_CLASSES[kind]);
|
||||
el.appendChild(textNode);
|
||||
target.appendChild(el);
|
||||
}
|
||||
if (ii == tok.length) {
|
||||
break;
|
||||
}
|
||||
start = ii;
|
||||
kind = tok[ii];
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
function execApply(target) {
|
||||
const proises = [];
|
||||
for (const elem of target.querySelectorAll('[apply]')) {
|
||||
if (!(elem instanceof HTMLElement)) continue;
|
||||
const funcname = elem.getAttribute('apply') ?? never();
|
||||
const vl = applyFns[funcname](elem);
|
||||
if (vl instanceof Promise) proises.push(vl);
|
||||
}
|
||||
if (target === document.body) {
|
||||
Promise.all(proises).then(() => document.body.hidden = false);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
function bindTextareaAutoResize(target) {
|
||||
for (const textarea of target.querySelectorAll("textarea")) {
|
||||
if (!(textarea instanceof HTMLTextAreaElement)) never();
|
||||
|
||||
const taCssMap = window.getComputedStyle(textarea);
|
||||
const padding = parseInt(taCssMap.getPropertyValue('padding-top') ?? "0")
|
||||
+ parseInt(taCssMap.getPropertyValue('padding-top') ?? "0");
|
||||
textarea.style.height = "auto";
|
||||
textarea.style.height = (textarea.scrollHeight - padding) + "px";
|
||||
textarea.style.overflowY = "hidden";
|
||||
textarea.addEventListener("input", function() {
|
||||
let top = window.scrollY;
|
||||
textarea.style.height = "auto";
|
||||
textarea.style.height = (textarea.scrollHeight - padding) + "px";
|
||||
window.scrollTo({ top });
|
||||
});
|
||||
|
||||
textarea.onkeydown = (ev) => {
|
||||
if (ev.key === "Tab") {
|
||||
ev.preventDefault();
|
||||
document.execCommand('insertText', false, "\t");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
function cacheInputs(target) {
|
||||
/**@type {HTMLFormElement}*/ let form;
|
||||
for (form of target.querySelectorAll('form')) {
|
||||
const path = form.getAttribute('hx-post') || form.getAttribute('hx-delete');
|
||||
if (!path) {
|
||||
console.warn('form does not have a hx-post or hx-delete attribute', form);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const input of form.elements) {
|
||||
if (input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement) {
|
||||
if ('password submit button'.includes(input.type)) continue;
|
||||
const key = path + input.name;
|
||||
input.value = localStorage.getItem(key) ?? '';
|
||||
input.addEventListener("input", () => localStorage.setItem(key, input.value));
|
||||
} else {
|
||||
console.warn("unhandled form element: ", input);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {string} [path] */
|
||||
function updateTab(path) {
|
||||
for (const elem of document.querySelectorAll("button[hx-push-url]")) {
|
||||
if (elem instanceof HTMLButtonElement)
|
||||
elem.disabled = elem.getAttribute("hx-push-url") === (path ?? window.location.pathname);
|
||||
}
|
||||
}
|
||||
|
||||
if (window.location.hostname === 'localhost') {
|
||||
let id; setInterval(async () => {
|
||||
let new_id = await fetch('/hot-reload').then(reps => reps.text());
|
||||
id ??= new_id;
|
||||
if (id !== new_id) window.location.reload();
|
||||
}, 300);
|
||||
|
||||
(async function test() {
|
||||
{
|
||||
const code = "main:=fn():void{return}";
|
||||
const inst = await getFmtInstance()
|
||||
const fmtd = modifyCode(inst, code, "fmt") ?? never();
|
||||
if (typeof fmtd !== "string") never();
|
||||
const prev = modifyCode(inst, fmtd, "minify") ?? never();
|
||||
if (code != prev) console.error(code, prev);
|
||||
}
|
||||
{
|
||||
const posts = [{
|
||||
path: "foo.hb",
|
||||
code: "main:=fn():int{return 42}",
|
||||
}];
|
||||
const res = compileCode(await getHbcInstance(), posts, 1) ?? never();
|
||||
const expected = "exit code: 42";
|
||||
if (expected != res) console.error(expected, res);
|
||||
}
|
||||
})()
|
||||
}
|
||||
|
||||
document.body.addEventListener('htmx:afterSwap', (ev) => {
|
||||
if (!(ev.target instanceof HTMLElement)) never();
|
||||
wireUp(ev.target);
|
||||
if (ev.target.tagName == "MAIN" || ev.target.tagName == "BODY")
|
||||
updateTab(ev['detail'].pathInfo.finalRequestPath);
|
||||
});
|
||||
|
||||
getFmtInstance().then(inst => {
|
||||
document.body.addEventListener('htmx:configRequest', (ev) => {
|
||||
const details = ev['detail'];
|
||||
if (details.path === "/post" && details.verb === "post") {
|
||||
details.parameters['code'] = modifyCode(inst, details.parameters['code'], "minify");
|
||||
}
|
||||
});
|
||||
|
||||
/** @param {string} query @param {string} target @returns {number} */
|
||||
function fuzzyCost(query, target) {
|
||||
let qi = 0, bi = 0, cost = 0, matched = false;
|
||||
while (qi < query.length) {
|
||||
if (query.charAt(qi) === target.charAt(bi++)) {
|
||||
matched = true;
|
||||
qi++;
|
||||
} else {
|
||||
cost++;
|
||||
}
|
||||
if (bi === target.length) (bi = 0, qi++);
|
||||
}
|
||||
return cost + (matched ? 0 : 100 * target.length);
|
||||
}
|
||||
|
||||
let deps = undefined;
|
||||
/** @param {HTMLInputElement} input @returns {void} */
|
||||
function filterCodeDeps(input) {
|
||||
deps ??= document.getElementById("deps");
|
||||
if (!(deps instanceof HTMLElement)) never();
|
||||
if (input.value === "") {
|
||||
deps.textContent = "results show here...";
|
||||
return;
|
||||
}
|
||||
deps.innerHTML = "";
|
||||
for (const root of [...prevRoots.keys()]
|
||||
.sort((a, b) => fuzzyCost(input.value, a) - fuzzyCost(input.value, b))) {
|
||||
const pane = document.createElement("div");
|
||||
const code = modifyCode(inst, localStorage["package-" + root], "fmt");
|
||||
pane.innerHTML = `<div>${root}</div><pre>${code}</pre>`;
|
||||
deps.appendChild(pane);
|
||||
}
|
||||
if (deps.innerHTML === "") {
|
||||
deps.textContent = "no results";
|
||||
}
|
||||
}
|
||||
|
||||
Object.assign(window, { filterCodeDeps });
|
||||
});
|
||||
|
||||
/** @param {HTMLElement} target */
|
||||
function runPost(target) {
|
||||
while (!target.matches("div[class=preview]")) target = target.parentElement ?? never();
|
||||
const code = target.querySelector("pre[apply=fmt]");
|
||||
if (!(code instanceof HTMLPreElement)) never();
|
||||
const output = target.querySelector("pre[id=compiler-output]");
|
||||
if (!(output instanceof HTMLPreElement)) never();
|
||||
|
||||
Promise.all([getHbcInstance(), getFmtInstance()]).then(async ([hbc, fmt]) => {
|
||||
const ctx = { keyBuf: [], prevParams: new Set() };
|
||||
await fetchPackages(code.innerText ?? never(), new Set(), output, ctx);
|
||||
const posts = [{ path: "this", code: "" }];
|
||||
loadCachedPackages(fmt, code.innerText ?? never(), ctx.keyBuf, posts, new Set());
|
||||
output.textContent = compileCode(hbc, posts);
|
||||
output.hidden = false;
|
||||
});
|
||||
|
||||
let author = encodeURIComponent(target.dataset.author ?? never());
|
||||
let name = encodeURIComponent(target.dataset.name ?? never());
|
||||
fetch(`/post/run?author=${author}&name=${name}`, { method: "POST" })
|
||||
}
|
||||
|
||||
Object.assign(window, { runPost });
|
||||
|
||||
updateTab();
|
||||
wireUp(document.body);
|
||||
|
|
@ -1,972 +0,0 @@
|
|||
#![feature(iter_collect_into)]
|
||||
use {
|
||||
argon2::{password_hash::SaltString, PasswordVerifier},
|
||||
axum::{
|
||||
body::Bytes,
|
||||
extract::{DefaultBodyLimit, Path},
|
||||
http::{header::COOKIE, request::Parts, StatusCode},
|
||||
response::{AppendHeaders, Html},
|
||||
},
|
||||
const_format::formatcp,
|
||||
core::fmt,
|
||||
htmlm::{html, write_html},
|
||||
rand_core::OsRng,
|
||||
serde::{Deserialize, Serialize},
|
||||
std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt::{Display, Write},
|
||||
net::Ipv4Addr,
|
||||
},
|
||||
};
|
||||
|
||||
const MAX_NAME_LENGTH: usize = 32;
|
||||
const MAX_POSTNAME_LENGTH: usize = 64;
|
||||
const MAX_CODE_LENGTH: usize = 1024 * 4;
|
||||
const SESSION_DURATION_SECS: u64 = 60 * 60;
|
||||
const MAX_FEED_SIZE: usize = 8 * 1024;
|
||||
|
||||
type Redirect<const COUNT: usize = 1> = AppendHeaders<[(&'static str, &'static str); COUNT]>;
|
||||
|
||||
macro_rules! static_asset {
|
||||
($mime:literal, $body:literal) => {
|
||||
get(|| async {
|
||||
axum::http::Response::builder()
|
||||
.header("content-type", $mime)
|
||||
.header("content-encoding", "gzip")
|
||||
.body(axum::body::Body::from(Bytes::from_static(include_bytes!(concat!(
|
||||
$body, ".gz"
|
||||
)))))
|
||||
.unwrap()
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
async fn amain() {
|
||||
use axum::routing::{delete, get, post};
|
||||
|
||||
let debug = cfg!(debug_assertions);
|
||||
|
||||
log::set_logger(&Logger).unwrap();
|
||||
log::set_max_level(if debug { log::LevelFilter::Warn } else { log::LevelFilter::Error });
|
||||
|
||||
db::init();
|
||||
|
||||
let router = axum::Router::new()
|
||||
.route("/", get(Index::page))
|
||||
.route("/index.css", static_asset!("text/css", "index.css"))
|
||||
.route("/index.js", static_asset!("text/javascript", "index.js"))
|
||||
.route("/hbfmt.wasm", static_asset!("application/wasm", "hbfmt.wasm"))
|
||||
.route("/hbc.wasm", static_asset!("application/wasm", "hbc.wasm"))
|
||||
.route("/index-view", get(Index::get))
|
||||
.route("/feed", get(Feed::page))
|
||||
.route("/feed-view", get(Feed::get))
|
||||
.route("/feed-more", post(Feed::more))
|
||||
.route("/profile", get(Profile::page))
|
||||
.route("/profile-view", get(Profile::get))
|
||||
.route("/profile/:name", get(Profile::get_other_page))
|
||||
.route("/profile/password", post(PasswordChange::post))
|
||||
.route("/profile-view/:name", get(Profile::get_other))
|
||||
.route("/post", get(Post::page))
|
||||
.route("/post-view", get(Post::get))
|
||||
.route("/post", post(Post::post))
|
||||
.route("/post/run", post(Post::run))
|
||||
.route("/code", post(fetch_code))
|
||||
.route("/login", get(Login::page))
|
||||
.route("/login-view", get(Login::get))
|
||||
.route("/login", post(Login::post))
|
||||
.route("/login", delete(Login::delete))
|
||||
.route("/signup", get(Signup::page))
|
||||
.route("/signup-view", get(Signup::get))
|
||||
.route("/signup", post(Signup::post))
|
||||
.route(
|
||||
"/hot-reload",
|
||||
get({
|
||||
let id = std::time::SystemTime::now()
|
||||
.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_millis();
|
||||
move || async move { id.to_string() }
|
||||
}),
|
||||
)
|
||||
.layer(DefaultBodyLimit::max(16 * 1024));
|
||||
|
||||
#[cfg(feature = "tls")]
|
||||
{
|
||||
let addr =
|
||||
(Ipv4Addr::UNSPECIFIED, std::env::var("DEPELL_PORT").unwrap().parse::<u16>().unwrap());
|
||||
let config = axum_server::tls_rustls::RustlsConfig::from_pem_file(
|
||||
std::env::var("DEPELL_CERT_PATH").unwrap(),
|
||||
std::env::var("DEPELL_KEY_PATH").unwrap(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
axum_server::bind_rustls(addr.into(), config)
|
||||
.serve(router.into_make_service())
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
#[cfg(not(feature = "tls"))]
|
||||
{
|
||||
let addr = (Ipv4Addr::UNSPECIFIED, 8080);
|
||||
let socket = tokio::net::TcpListener::bind(addr).await.unwrap();
|
||||
axum::serve(socket, router).await.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_code(
|
||||
axum::Json(paths): axum::Json<Vec<String>>,
|
||||
) -> axum::Json<HashMap<String, String>> {
|
||||
let mut deps = HashMap::<String, String>::new();
|
||||
db::with(|db| {
|
||||
for path in &paths {
|
||||
let Some((author, name)) = path.split_once('/') else { continue };
|
||||
db.fetch_deps
|
||||
.query_map((name, author), |r| {
|
||||
Ok((
|
||||
r.get::<_, String>(1)? + "/" + r.get_ref(0)?.as_str()?,
|
||||
r.get::<_, String>(2)?,
|
||||
))
|
||||
})
|
||||
.log("fetch deps query")
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|r| r.log("deps row"))
|
||||
.collect_into(&mut deps);
|
||||
}
|
||||
});
|
||||
axum::Json(deps)
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(untagged)]
|
||||
enum Feed {
|
||||
Before { before_timestamp: u64 },
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Before {
|
||||
before_timestamp: u64,
|
||||
}
|
||||
|
||||
impl Feed {
|
||||
async fn more(session: Session, axum::Form(data): axum::Form<Before>) -> Html<String> {
|
||||
Self::Before { before_timestamp: data.before_timestamp }.render(&session)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Feed {
|
||||
fn default() -> Self {
|
||||
Self::Before { before_timestamp: now() + 3600 }
|
||||
}
|
||||
}
|
||||
|
||||
impl Page for Feed {
|
||||
fn render_to_buf(self, _: &Session, buf: &mut String) {
|
||||
db::with(|db| {
|
||||
let cursor = match self {
|
||||
Feed::Before { before_timestamp } => db
|
||||
.get_pots_before
|
||||
.query_map((before_timestamp,), Post::from_row)
|
||||
.log("fetch before posts query")
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|r| r.log("fetch before posts row")),
|
||||
};
|
||||
|
||||
let base_len = buf.len();
|
||||
let mut last_timestamp = None;
|
||||
for post in cursor {
|
||||
write!(buf, "{}", post).unwrap();
|
||||
if buf.len() - base_len > MAX_FEED_SIZE {
|
||||
last_timestamp = Some(post.timestamp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
write_html!((*buf)
|
||||
if let Some(last_timestamp) = last_timestamp {
|
||||
<div "hx-post"="/feed-more"
|
||||
"hx-trigger"="intersect once"
|
||||
"hx-swap"="outerHTML"
|
||||
"hx-vals"={format_args!("{{\"before_timestamp\":{last_timestamp}}}")}
|
||||
>"there might be more"</div>
|
||||
} else {
|
||||
"no more stuff"
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Index;
|
||||
|
||||
impl PublicPage for Index {
|
||||
fn render_to_buf(self, buf: &mut String) {
|
||||
buf.push_str(include_str!("welcome-page.html"));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
struct Post {
|
||||
author: String,
|
||||
name: String,
|
||||
#[serde(skip)]
|
||||
timestamp: u64,
|
||||
#[serde(skip)]
|
||||
imports: usize,
|
||||
#[serde(skip)]
|
||||
runs: usize,
|
||||
#[serde(skip)]
|
||||
dependencies: usize,
|
||||
code: String,
|
||||
#[serde(skip)]
|
||||
error: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl Page for Post {
|
||||
fn render_to_buf(self, session: &Session, buf: &mut String) {
|
||||
let Self { name, code, error, .. } = self;
|
||||
write_html! { (buf)
|
||||
<form id="postForm" "hx-post"="/post" "hx-swap"="outerHTML">
|
||||
if let Some(e) = error { <div class="error">e</div> }
|
||||
<input name="author" type="text" value={session.name} hidden>
|
||||
<input name="name" type="text" placeholder="name" value=name
|
||||
required maxlength=MAX_POSTNAME_LENGTH>
|
||||
<div id="code-editor">
|
||||
<textarea id="code-edit" name="code" placeholder="code" rows=1
|
||||
required>code</textarea>
|
||||
<span id="code-size">MAX_CODE_LENGTH</span>
|
||||
</div>
|
||||
<input type="submit" value="submit">
|
||||
<pre id="compiler-output"></pre>
|
||||
</form>
|
||||
!{include_str!("post-page.html")}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Run {
|
||||
author: String,
|
||||
name: String,
|
||||
}
|
||||
|
||||
impl Post {
|
||||
pub fn from_row(r: &rusqlite::Row) -> rusqlite::Result<Self> {
|
||||
Ok(Post {
|
||||
author: r.get(0)?,
|
||||
name: r.get(1)?,
|
||||
timestamp: r.get(2)?,
|
||||
code: r.get(3)?,
|
||||
imports: r.get(4)?,
|
||||
runs: r.get(5)?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
async fn run(
|
||||
session: Session,
|
||||
axum::extract::Query(run): axum::extract::Query<Run>,
|
||||
) -> StatusCode {
|
||||
match db::with(|qes| qes.creata_run.insert((run.name, run.author, session.name))) {
|
||||
Ok(_) => StatusCode::OK,
|
||||
Err(e) => {
|
||||
log::error!("creating run record failed: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn post(
|
||||
session: Session,
|
||||
axum::Form(mut data): axum::Form<Self>,
|
||||
) -> Result<Redirect, Html<String>> {
|
||||
if data.name.len() > MAX_POSTNAME_LENGTH {
|
||||
data.error = Some(formatcp!("name too long, max length is {MAX_POSTNAME_LENGTH}"));
|
||||
return Err(data.render(&session));
|
||||
}
|
||||
|
||||
if data.code.len() > MAX_CODE_LENGTH {
|
||||
data.error = Some(formatcp!("code too long, max length is {MAX_CODE_LENGTH}"));
|
||||
return Err(data.render(&session));
|
||||
}
|
||||
|
||||
db::with(|db| {
|
||||
if let Err(e) = db.create_post.insert((&data.name, &session.name, now(), &data.code)) {
|
||||
if let rusqlite::Error::SqliteFailure(e, _) = e {
|
||||
if e.code == rusqlite::ErrorCode::ConstraintViolation {
|
||||
data.error = Some("this name is already used");
|
||||
}
|
||||
}
|
||||
data.error = data.error.or_else(|| {
|
||||
log::error!("create post error: {e}");
|
||||
Some("internal server error")
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
for (author, name) in hblang::lexer::Lexer::uses(&data.code)
|
||||
.filter_map(|v| v.split_once('/'))
|
||||
.collect::<HashSet<_>>()
|
||||
{
|
||||
if db
|
||||
.create_import
|
||||
.insert((author, name, &session.name, &data.name))
|
||||
.log("create import query")
|
||||
.is_none()
|
||||
{
|
||||
data.error = Some("internal server error");
|
||||
return;
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
if data.error.is_some() {
|
||||
Err(data.render(&session))
|
||||
} else {
|
||||
Ok(redirect("/profile"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Post {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { author, name, timestamp, imports, runs, dependencies, code, .. } = self;
|
||||
write_html! { f <div class="preview" "data-author"=author "data-name"=name>
|
||||
<div class="info">
|
||||
<span>
|
||||
<a "hx-get"={format_args!("/profile-view/{author}")} href="" "hx-target"="main"
|
||||
"hx-push-url"={format_args!("/profile/{author}")}
|
||||
"hx-swam"="innerHTML">author</a>
|
||||
"/"
|
||||
name
|
||||
</span>
|
||||
<span apply="timestamp">timestamp</span>
|
||||
for (name, count) in [include_str!("icons/download.svg"), include_str!("icons/run.svg"), "deps"]
|
||||
.iter()
|
||||
.zip([imports, runs, dependencies])
|
||||
.filter(|(_, &c)| c != 0)
|
||||
{
|
||||
<div class="stat">!name count</div>
|
||||
}
|
||||
</div>
|
||||
<div class="code">
|
||||
<nav>
|
||||
<button onmousedown="runPost(this)">!{include_str!("icons/run.svg")}</button>
|
||||
</nav>
|
||||
<pre apply="fmt">code</pre>
|
||||
</div>
|
||||
<pre hidden id="compiler-output"></pre>
|
||||
if *timestamp == 0 {
|
||||
<button "hx-get"="/post" "hx-swap"="outerHTML"
|
||||
"hx-target"="[preview]">"edit"</button>
|
||||
}
|
||||
</div> }
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
struct PasswordChange {
|
||||
old_password: String,
|
||||
new_password: String,
|
||||
#[serde(skip)]
|
||||
error: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl PasswordChange {
|
||||
async fn post(
|
||||
session: Session,
|
||||
axum::Form(mut change): axum::Form<PasswordChange>,
|
||||
) -> Html<String> {
|
||||
db::with(|que| {
|
||||
match que.authenticate.query_row((&session.name,), |r| r.get::<_, String>(1)) {
|
||||
Ok(hash) if verify_password(&hash, &change.old_password).is_err() => {
|
||||
change.error = Some("invalid credentials");
|
||||
}
|
||||
Ok(_) => {
|
||||
let new_hashed = hash_password(&change.new_password);
|
||||
match que
|
||||
.change_passowrd
|
||||
.execute((new_hashed, &session.name))
|
||||
.log("execute update")
|
||||
{
|
||||
None => change.error = Some("intenal server error"),
|
||||
Some(0) => change.error = Some("password is incorrect"),
|
||||
Some(_) => {}
|
||||
}
|
||||
}
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => {
|
||||
change.error = Some("invalid credentials");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("login queri failed: {e}");
|
||||
change.error = Some("internal server error");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if change.error.is_some() {
|
||||
change.render(&session)
|
||||
} else {
|
||||
PasswordChange::default().render(&session)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Page for PasswordChange {
|
||||
fn render_to_buf(self, _: &Session, buf: &mut String) {
|
||||
let Self { old_password, new_password, error } = self;
|
||||
write_html! { (buf)
|
||||
<form "hx-post"="/profile/password" "hx-swap"="outerHTML">
|
||||
if let Some(e) = error { <div class="error">e</div> }
|
||||
<input name="old_password" type="password" autocomplete="old-password"
|
||||
placeholder="old password" value=old_password>
|
||||
<input name="new_password" type="password" autocomplete="new-password" placeholder="new password"
|
||||
value=new_password>
|
||||
<input type="submit" value="submit">
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Profile {
|
||||
other: Option<String>,
|
||||
}
|
||||
|
||||
impl Profile {
|
||||
async fn get_other(session: Session, Path(name): Path<String>) -> Html<String> {
|
||||
Profile { other: Some(name) }.render(&session)
|
||||
}
|
||||
|
||||
async fn get_other_page(session: Session, Path(name): Path<String>) -> Html<String> {
|
||||
base(|b| Profile { other: Some(name) }.render_to_buf(&session, b), Some(&session))
|
||||
}
|
||||
}
|
||||
|
||||
impl Page for Profile {
|
||||
fn render_to_buf(self, session: &Session, buf: &mut String) {
|
||||
db::with(|db| {
|
||||
let name = self.other.as_ref().unwrap_or(&session.name);
|
||||
let iter = db
|
||||
.get_user_posts
|
||||
.query_map((name,), Post::from_row)
|
||||
.log("get user posts query")
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|p| p.log("user post row"));
|
||||
write_html! { (*buf)
|
||||
if name == &session.name {
|
||||
|b|{PasswordChange::default().render_to_buf(session, b)}
|
||||
}
|
||||
|
||||
for post in iter {
|
||||
!{post}
|
||||
} else {
|
||||
"no posts"
|
||||
}
|
||||
!{include_str!("profile-page.html")}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_password(password: &str) -> String {
|
||||
use argon2::PasswordHasher;
|
||||
argon2::Argon2::default()
|
||||
.hash_password(password.as_bytes(), &SaltString::generate(&mut OsRng))
|
||||
.unwrap()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
fn verify_password(hash: &str, password: &str) -> Result<(), argon2::password_hash::Error> {
|
||||
argon2::Argon2::default()
|
||||
.verify_password(password.as_bytes(), &argon2::PasswordHash::new(hash)?)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default, Debug)]
|
||||
struct Login {
|
||||
name: String,
|
||||
password: String,
|
||||
#[serde(skip)]
|
||||
error: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl PublicPage for Login {
|
||||
fn render_to_buf(self, buf: &mut String) {
|
||||
let Self { name, password, error } = self;
|
||||
write_html! { (buf)
|
||||
<form "hx-post"="/login" "hx-swap"="outerHTML">
|
||||
if let Some(e) = error { <div class="error">e</div> }
|
||||
<input name="name" type="text" autocomplete="name" placeholder="name" value=name
|
||||
required maxlength=MAX_NAME_LENGTH>
|
||||
<input name="password" type="password" autocomplete="current-password" placeholder="password"
|
||||
value=password>
|
||||
<input type="submit" value="submit">
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Login {
|
||||
async fn post(
|
||||
axum::Form(mut data): axum::Form<Self>,
|
||||
) -> Result<AppendHeaders<[(&'static str, String); 2]>, Html<String>> {
|
||||
// TODO: hash password
|
||||
let mut id = [0u8; 32];
|
||||
db::with(|db| match db.authenticate.query_row((&data.name,), |r| r.get::<_, String>(1)) {
|
||||
Ok(hash) => {
|
||||
if verify_password(&hash, &data.password).is_err() {
|
||||
data.error = Some("invalid credentials");
|
||||
} else {
|
||||
getrandom::getrandom(&mut id).unwrap();
|
||||
if db
|
||||
.login
|
||||
.insert((id, &data.name, now() + SESSION_DURATION_SECS))
|
||||
.log("create session query")
|
||||
.is_none()
|
||||
{
|
||||
data.error = Some("internal server error");
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => {
|
||||
data.error = Some("invalid credentials");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("login queri failed: {e}");
|
||||
data.error = Some("internal server error");
|
||||
}
|
||||
});
|
||||
|
||||
if data.error.is_some() {
|
||||
Err(data.render())
|
||||
} else {
|
||||
Ok(AppendHeaders([
|
||||
("hx-location", "/feed".into()),
|
||||
(
|
||||
"set-cookie",
|
||||
format!(
|
||||
"id={}; SameSite=Strict; Secure; Max-Age={SESSION_DURATION_SECS}",
|
||||
to_hex(&id)
|
||||
),
|
||||
),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
||||
async fn delete(session: Session) -> Redirect {
|
||||
_ = db::with(|q| q.logout.execute((session.id,)).log("delete session query"));
|
||||
redirect("/login")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Default)]
|
||||
struct Signup {
|
||||
name: String,
|
||||
new_password: String,
|
||||
confirm_password: String,
|
||||
#[serde(default)]
|
||||
confirm_no_password: bool,
|
||||
#[serde(skip)]
|
||||
error: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl PublicPage for Signup {
|
||||
fn render_to_buf(self, buf: &mut String) {
|
||||
let Signup { name, new_password, confirm_password, confirm_no_password, error } = self;
|
||||
let vals = if confirm_no_password { "{\"confirm_no_password\":true}" } else { "{}" };
|
||||
write_html! { (buf)
|
||||
<form "hx-post"="/signup" "hx-swap"="outerHTML" "hx-vals"=vals>
|
||||
if let Some(e) = error { <div class="error">e</div> }
|
||||
<input name="name" type="text" autocomplete="name" placeholder="name" value=name
|
||||
maxlength=MAX_NAME_LENGTH required>
|
||||
<input name="new_password" type="password" autocomplete="new-password" placeholder="new password"
|
||||
value=new_password>
|
||||
<input name="confirm_password" type="password" autocomplete="confirm-password"
|
||||
placeholder="confirm password" value=confirm_password>
|
||||
<input type="submit" value="submit">
|
||||
</form>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Signup {
|
||||
async fn post(axum::Form(mut data): axum::Form<Self>) -> Result<Redirect, Html<String>> {
|
||||
if data.name.len() > MAX_NAME_LENGTH {
|
||||
data.error = Some(formatcp!("name too long, max length is {MAX_NAME_LENGTH}"));
|
||||
return Err(data.render());
|
||||
}
|
||||
|
||||
if !data.confirm_no_password && data.new_password.is_empty() {
|
||||
data.confirm_no_password = true;
|
||||
data.error = Some("Are you sure you don't want to use a password? (then submit again)");
|
||||
return Err(data.render());
|
||||
}
|
||||
|
||||
db::with(|db| {
|
||||
// TODO: hash passwords
|
||||
match db.register.insert((&data.name, hash_password(&data.new_password))) {
|
||||
Ok(_) => {}
|
||||
Err(rusqlite::Error::SqliteFailure(e, _))
|
||||
if e.code == rusqlite::ErrorCode::ConstraintViolation =>
|
||||
{
|
||||
data.error = Some("username already taken");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("create user query: {e}");
|
||||
data.error = Some("internal server error");
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if data.error.is_some() {
|
||||
Err(data.render())
|
||||
} else {
|
||||
Ok(redirect("/login"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn base(body: impl FnOnce(&mut String), session: Option<&Session>) -> Html<String> {
|
||||
let username = session.map(|s| &s.name);
|
||||
|
||||
let nav_button = |f: &mut String, name: &str| {
|
||||
write_html! {(f)
|
||||
<button "hx-push-url"={format_args!("/{name}")}
|
||||
"hx-get"={format_args!("/{name}-view")}
|
||||
"hx-target"="main"
|
||||
"hx-swap"="innerHTML">name</button>
|
||||
}
|
||||
};
|
||||
|
||||
Html(html! {
|
||||
"<!DOCTYPE html>"
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta name="charset" content="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="code dependency hell socila media hblang">
|
||||
<link rel="stylesheet" href="/index.css">
|
||||
<title>"depell"</title>
|
||||
</head>
|
||||
<body hidden>
|
||||
<nav>
|
||||
<button "hx-push-url"="/" "hx-get"="/index-view" "hx-target"="main" "hx-swap"="innerHTML">"depell"</button>
|
||||
<section>
|
||||
if let Some(username) = username {
|
||||
<button "hx-push-url"={format_args!("/profile/{username}")} "hx-get"="/profile-view" "hx-target"="main"
|
||||
"hx-swap"="innerHTML">username</button>
|
||||
|f|{nav_button(f, "feed"); nav_button(f, "post")}
|
||||
<button "hx-delete"="/login">"logout"</button>
|
||||
} else {
|
||||
|f|{nav_button(f, "login"); nav_button(f, "signup")}
|
||||
}
|
||||
</section>
|
||||
</nav>
|
||||
<section id="post-form"></section>
|
||||
<main>|f|{body(f)}</main>
|
||||
</body>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.3/dist/htmx.min.js" integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq" crossorigin="anonymous"></script>
|
||||
<script type="module" src="/index.js"></script>
|
||||
</html>
|
||||
})
|
||||
}
|
||||
|
||||
struct Session {
|
||||
name: String,
|
||||
id: [u8; 32],
|
||||
}
|
||||
|
||||
#[axum::async_trait]
|
||||
impl<S> axum::extract::FromRequestParts<S> for Session {
|
||||
/// If the extractor fails it'll use this "rejection" type. A rejection is
|
||||
/// a kind of error that can be converted into a response.
|
||||
type Rejection = Redirect;
|
||||
|
||||
/// Perform the extraction.
|
||||
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||
let err = redirect("/login");
|
||||
|
||||
let value = parts
|
||||
.headers
|
||||
.get_all(COOKIE)
|
||||
.into_iter()
|
||||
.find_map(|c| c.to_str().ok()?.trim().strip_prefix("id="))
|
||||
.map(|c| c.split_once(';').unwrap_or((c, "")).0)
|
||||
.ok_or(err)?;
|
||||
let mut id = [0u8; 32];
|
||||
parse_hex(value, &mut id).ok_or(err)?;
|
||||
|
||||
let (name, expiration) = db::with(|db| {
|
||||
db.get_session
|
||||
.query_row((id,), |r| Ok((r.get::<_, String>(0)?, r.get::<_, u64>(1)?)))
|
||||
.log("fetching session")
|
||||
.ok_or(err)
|
||||
})?;
|
||||
|
||||
if expiration < now() {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(Self { name, id })
|
||||
}
|
||||
}
|
||||
|
||||
fn now() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
fn parse_hex(hex: &str, dst: &mut [u8]) -> Option<()> {
|
||||
fn hex_to_nibble(b: u8) -> Option<u8> {
|
||||
Some(match b {
|
||||
b'a'..=b'f' => b - b'a' + 10,
|
||||
b'A'..=b'F' => b - b'A' + 10,
|
||||
b'0'..=b'9' => b - b'0',
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
if hex.len() != dst.len() * 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
for (d, p) in dst.iter_mut().zip(hex.as_bytes().chunks_exact(2)) {
|
||||
*d = (hex_to_nibble(p[0])? << 4) | hex_to_nibble(p[1])?;
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn to_hex(src: &[u8]) -> String {
|
||||
use std::fmt::Write;
|
||||
let mut buf = String::new();
|
||||
for &b in src {
|
||||
write!(buf, "{b:02x}").unwrap()
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(amain());
|
||||
}
|
||||
|
||||
mod db {
|
||||
use std::cell::RefCell;
|
||||
|
||||
macro_rules! gen_queries {
|
||||
($vis:vis struct $name:ident {
|
||||
$($qname:ident: $code:expr,)*
|
||||
}) => {
|
||||
$vis struct $name<'a> {
|
||||
$($vis $qname: rusqlite::Statement<'a>,)*
|
||||
}
|
||||
|
||||
impl<'a> $name<'a> {
|
||||
fn new(db: &'a rusqlite::Connection) -> Self {
|
||||
Self {
|
||||
$($qname: db.prepare($code).unwrap(),)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
gen_queries! {
|
||||
pub struct Queries {
|
||||
register: "INSERT INTO user (name, password_hash) VALUES(?, ?)",
|
||||
change_passowrd: "UPDATE user SET password_hash = ? WHERE name = ?",
|
||||
authenticate: "SELECT name, password_hash FROM user WHERE name = ?",
|
||||
login: "INSERT OR REPLACE INTO session (id, username, expiration) VALUES(?, ?, ?)",
|
||||
logout: "DELETE FROM session WHERE id = ?",
|
||||
get_session: "SELECT username, expiration FROM session WHERE id = ?",
|
||||
get_user_posts: "SELECT author, name, timestamp, code, (
|
||||
WITH RECURSIVE roots(name, author, code) AS (
|
||||
SELECT name, author, code FROM post WHERE name = outher.name AND author = outher.author
|
||||
UNION
|
||||
SELECT post.name, post.author, post.code FROM
|
||||
post JOIN import ON post.name = import.from_name
|
||||
AND post.author = import.from_author
|
||||
JOIN roots ON import.to_name = roots.name
|
||||
AND import.to_author = roots.author
|
||||
) SELECT (count(*) - 1) FROM roots
|
||||
) AS imports, (
|
||||
WITH RECURSIVE roots(name, author, code) AS (
|
||||
SELECT name, author, code FROM post WHERE name = outher.name AND author = outher.author
|
||||
UNION
|
||||
SELECT post.name, post.author, post.code FROM post
|
||||
JOIN import ON post.name = import.from_name
|
||||
AND post.author = import.from_author
|
||||
JOIN roots ON import.to_name = roots.name
|
||||
AND import.to_author = roots.author
|
||||
) SELECT count(*) FROM roots
|
||||
JOIN run ON roots.name = run.code_name
|
||||
AND roots.author = run.code_author
|
||||
) AS runs FROM post as outher WHERE author = ? ORDER BY timestamp DESC",
|
||||
// TODO: we might want to cache the recursive queries
|
||||
get_pots_before: "SELECT author, name, timestamp, code, (
|
||||
WITH RECURSIVE roots(name, author, code) AS (
|
||||
SELECT name, author, code FROM post WHERE name = outher.name AND author = outher.author
|
||||
UNION
|
||||
SELECT post.name, post.author, post.code FROM
|
||||
post JOIN import ON post.name = import.from_name
|
||||
AND post.author = import.from_author
|
||||
JOIN roots ON import.to_name = roots.name
|
||||
AND import.to_author = roots.author
|
||||
) SELECT (count(*) - 1) FROM roots
|
||||
) AS imports, (
|
||||
WITH RECURSIVE roots(name, author, code) AS (
|
||||
SELECT name, author, code FROM post WHERE name = outher.name AND author = outher.author
|
||||
UNION
|
||||
SELECT post.name, post.author, post.code FROM post
|
||||
JOIN import ON post.name = import.from_name
|
||||
AND post.author = import.from_author
|
||||
JOIN roots ON import.to_name = roots.name
|
||||
AND import.to_author = roots.author
|
||||
) SELECT count(*) FROM roots
|
||||
JOIN run ON roots.name = run.code_name
|
||||
AND roots.author = run.code_author
|
||||
) as runs FROM post AS outher WHERE timestamp < ?",
|
||||
create_post: "INSERT INTO post (name, author, timestamp, code) VALUES(?, ?, ?, ?)",
|
||||
fetch_deps: "
|
||||
WITH RECURSIVE roots(name, author, code) AS (
|
||||
SELECT name, author, code FROM post WHERE name = ? AND author = ?
|
||||
UNION
|
||||
SELECT post.name, post.author, post.code FROM
|
||||
post JOIN import ON post.name = import.to_name
|
||||
AND post.author = import.to_author
|
||||
JOIN roots ON import.from_name = roots.name
|
||||
AND import.from_author = roots.author
|
||||
) SELECT * FROM roots;
|
||||
",
|
||||
create_import: "INSERT INTO import(to_author, to_name, from_author, from_name)
|
||||
VALUES(?, ?, ?, ?)",
|
||||
creata_run: "INSERT OR IGNORE INTO run(code_name, code_author, runner) VALUES(?, ?, ?)",
|
||||
}
|
||||
}
|
||||
|
||||
struct Db {
|
||||
queries: Queries<'static>,
|
||||
_db: Box<rusqlite::Connection>,
|
||||
}
|
||||
|
||||
impl Db {
|
||||
fn new() -> Self {
|
||||
let db = Box::new(rusqlite::Connection::open("db.sqlite").unwrap());
|
||||
Self {
|
||||
queries: Queries::new(unsafe {
|
||||
std::mem::transmute::<&rusqlite::Connection, &rusqlite::Connection>(&db)
|
||||
}),
|
||||
_db: db,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with<T>(with: impl FnOnce(&mut Queries) -> T) -> T {
|
||||
thread_local! { static DB_CONN: RefCell<Db> = RefCell::new(Db::new()); }
|
||||
DB_CONN.with_borrow_mut(|q| with(&mut q.queries))
|
||||
}
|
||||
|
||||
pub fn init() {
|
||||
const SCHEMA_VERSION: usize = 0;
|
||||
const MIGRATIONS: &[&str] = &[include_str!("migrations/1.sql")];
|
||||
|
||||
let db = rusqlite::Connection::open("db.sqlite").unwrap();
|
||||
db.execute_batch(include_str!("schema.sql")).unwrap();
|
||||
|
||||
let schema_version =
|
||||
db.pragma_query_value(None, "user_version", |v| v.get::<_, usize>(0)).unwrap();
|
||||
|
||||
if schema_version != SCHEMA_VERSION {
|
||||
for &mig in &MIGRATIONS[schema_version..] {
|
||||
db.execute_batch(mig).expect(mig);
|
||||
}
|
||||
db.pragma_update(None, "user_version", SCHEMA_VERSION).unwrap();
|
||||
}
|
||||
|
||||
Queries::new(&db);
|
||||
}
|
||||
}
|
||||
|
||||
fn redirect(to: &'static str) -> Redirect {
|
||||
AppendHeaders([("hx-location", to)])
|
||||
}
|
||||
|
||||
trait PublicPage: Default {
|
||||
fn render_to_buf(self, buf: &mut String);
|
||||
|
||||
fn render(self) -> Html<String> {
|
||||
let mut str = String::new();
|
||||
self.render_to_buf(&mut str);
|
||||
Html(str)
|
||||
}
|
||||
|
||||
async fn get() -> Html<String> {
|
||||
Self::default().render()
|
||||
}
|
||||
|
||||
async fn page(session: Option<Session>) -> Html<String> {
|
||||
base(|s| Self::default().render_to_buf(s), session.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
trait Page: Default {
|
||||
fn render_to_buf(self, session: &Session, buf: &mut String);
|
||||
|
||||
fn render(self, session: &Session) -> Html<String> {
|
||||
let mut str = String::new();
|
||||
self.render_to_buf(session, &mut str);
|
||||
Html(str)
|
||||
}
|
||||
|
||||
async fn get(session: Session) -> Html<String> {
|
||||
Self::default().render(&session)
|
||||
}
|
||||
|
||||
async fn page(session: Option<Session>) -> Result<Html<String>, axum::response::Redirect> {
|
||||
match session {
|
||||
Some(session) => {
|
||||
Ok(base(|f| Self::default().render_to_buf(&session, f), Some(&session)))
|
||||
}
|
||||
None => Err(axum::response::Redirect::permanent("/login")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait ResultExt<O, E> {
|
||||
fn log(self, prefix: impl Display) -> Option<O>;
|
||||
}
|
||||
|
||||
impl<O, E: Display> ResultExt<O, E> for Result<O, E> {
|
||||
fn log(self, prefix: impl Display) -> Option<O> {
|
||||
match self {
|
||||
Ok(v) => Some(v),
|
||||
Err(e) => {
|
||||
log::error!("{prefix}: {e}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Logger;
|
||||
|
||||
impl log::Log for Logger {
|
||||
fn enabled(&self, _: &log::Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
eprintln!("{} - {}", record.module_path().unwrap_or("=="), record.args());
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
|
@ -1 +0,0 @@
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
<div id="dep-list">
|
||||
<input placeholder="search impoted deps.." oninput="filterCodeDeps(this, event)">
|
||||
<section id="deps">
|
||||
results show here...
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
<h3>About posting code</h3>
|
||||
<p>
|
||||
If you are unfammiliar with <a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a>, refer to the
|
||||
<strong>hblang/README.md</strong> or
|
||||
vizit <a href="/profile/mlokis">mlokis'es posts</a>. Preferably don't edit the code here.
|
||||
</p>
|
||||
|
||||
<h3>Extra textarea features</h3>
|
||||
<ul>
|
||||
<li>proper tab behaviour</li>
|
||||
<li>snap to previous tab boundary on "empty" lines</li>
|
||||
</ul>
|
|
@ -1,55 +0,0 @@
|
|||
PRAGMA foreign_keys = ON;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user(
|
||||
name TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
PRIMARY KEY (name)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS session(
|
||||
id BLOB NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
expiration INTEGER NOT NULL,
|
||||
FOREIGN KEY (username) REFERENCES user (name)
|
||||
PRIMARY KEY (username)
|
||||
) WITHOUT ROWID;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS
|
||||
session_id ON session (id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS post(
|
||||
name TEXT NOT NULL,
|
||||
author TEXT,
|
||||
timestamp INTEGER,
|
||||
code TEXT NOT NULL,
|
||||
FOREIGN KEY (author) REFERENCES user(name) ON DELETE SET NULL,
|
||||
PRIMARY KEY (author, name)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS
|
||||
post_timestamp ON post(timestamp DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS import(
|
||||
from_name TEXT NOT NULL,
|
||||
from_author TEXT,
|
||||
to_name TEXT NOT NULL,
|
||||
to_author TEXT,
|
||||
FOREIGN KEY (from_name, from_author) REFERENCES post(name, author),
|
||||
FOREIGN KEY (to_name, to_author) REFERENCES post(name, author)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS
|
||||
dependencies ON import(from_name, from_author);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS
|
||||
dependants ON import(to_name, to_author);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS run(
|
||||
code_name TEXT NOT NULL,
|
||||
code_author TEXT NOT NULL,
|
||||
runner TEXT NOT NULL,
|
||||
FOREIGN KEY (code_name, code_author) REFERENCES post(name, author),
|
||||
FOREIGN KEY (runner) REFERENCES user(name),
|
||||
PRIMARY KEY (code_name, code_author, runner)
|
||||
);
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<h1>Welcome to depell</h1>
|
||||
<p>
|
||||
Depell (dependency hell) is a simple "social" media site best compared to twitter, except that all you can post is
|
||||
<a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a> code with no comments allowed. Instead of likes you
|
||||
run the program, and instead of retweets you import the program as dependency. Run counts even when ran indirectly.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The backend only serves the code and frontend compiles and runs it locally. All posts are immutable.
|
||||
</p>
|
||||
|
||||
<h2>Security?</h2>
|
||||
<p>
|
||||
All code runs in WASM (inside a holey-bytes VM until hblang compiles to wasm) and is controlled by JavaScript. WASM
|
||||
cant do any form of IO without going trough JavaScript so as long as JS import does not allow wasm to execute
|
||||
arbitrary JS code, WASM can act as a container inside the JS.
|
||||
</p>
|
|
@ -1,11 +0,0 @@
|
|||
[package]
|
||||
name = "wasm-hbfmt"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
hblang = { workspace = true, features = ["no_log"] }
|
||||
wasm-rt = { version = "0.1.0", path = "../wasm-rt" }
|
|
@ -1,42 +0,0 @@
|
|||
#![no_std]
|
||||
#![feature(str_from_raw_parts)]
|
||||
#![feature(alloc_error_handler)]
|
||||
|
||||
use hblang::{fmt, parser};
|
||||
|
||||
wasm_rt::decl_runtime!(128 * 1024, 1024 * 4);
|
||||
|
||||
const MAX_OUTPUT_SIZE: usize = 1024 * 10;
|
||||
wasm_rt::decl_buffer!(MAX_OUTPUT_SIZE, MAX_OUTPUT, OUTPUT, OUTPUT_LEN);
|
||||
|
||||
const MAX_INPUT_SIZE: usize = 1024 * 4;
|
||||
wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn fmt() {
|
||||
ALLOCATOR.reset();
|
||||
|
||||
let code = core::str::from_raw_parts(core::ptr::addr_of!(INPUT).cast(), INPUT_LEN);
|
||||
|
||||
let arena = parser::Arena::with_capacity(code.len() * parser::SOURCE_TO_AST_FACTOR);
|
||||
let mut ctx = parser::Ctx::default();
|
||||
let exprs = parser::Parser::parse(&mut ctx, code, "source.hb", &mut parser::no_loader, &arena);
|
||||
|
||||
let mut f = wasm_rt::Write(&mut OUTPUT[..]);
|
||||
fmt::fmt_file(exprs, code, &mut f).unwrap();
|
||||
OUTPUT_LEN = MAX_OUTPUT_SIZE - f.0.len();
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn tok() {
|
||||
let code = core::slice::from_raw_parts_mut(
|
||||
core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
|
||||
OUTPUT_LEN = fmt::get_token_kinds(code);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
unsafe extern "C" fn minify() {
|
||||
let code = core::str::from_raw_parts_mut(
|
||||
core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
|
||||
OUTPUT_LEN = fmt::minify(code);
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
[package]
|
||||
name = "wasm-hbc"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
hblang = { workspace = true, features = [] }
|
||||
hbvm.workspace = true
|
||||
log = { version = "0.4.22", features = ["release_max_level_error"] }
|
||||
wasm-rt = { version = "0.1.0", path = "../wasm-rt", features = ["log"] }
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
#![feature(alloc_error_handler)]
|
||||
#![feature(slice_take)]
|
||||
#![no_std]
|
||||
|
||||
use {
|
||||
alloc::{string::String, vec::Vec},
|
||||
core::ffi::CStr,
|
||||
hblang::{
|
||||
son::{hbvm::HbvmBackend, Codegen, CodegenCtx},
|
||||
ty::Module,
|
||||
Ent,
|
||||
},
|
||||
};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
const ARENA_CAP: usize = 128 * 16 * 1024;
|
||||
wasm_rt::decl_runtime!(ARENA_CAP, 1024 * 4);
|
||||
|
||||
const MAX_INPUT_SIZE: usize = 32 * 4 * 1024;
|
||||
wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
|
||||
|
||||
#[no_mangle]
|
||||
unsafe fn compile_and_run(mut fuel: usize) {
|
||||
ALLOCATOR.reset();
|
||||
|
||||
_ = log::set_logger(&wasm_rt::Logger);
|
||||
log::set_max_level(log::LevelFilter::Error);
|
||||
|
||||
struct File<'a> {
|
||||
path: &'a str,
|
||||
code: &'a mut str,
|
||||
}
|
||||
|
||||
let mut root = 0;
|
||||
|
||||
let files = {
|
||||
let mut input_bytes =
|
||||
core::slice::from_raw_parts_mut(core::ptr::addr_of_mut!(INPUT).cast::<u8>(), INPUT_LEN);
|
||||
|
||||
let mut files = Vec::with_capacity(32);
|
||||
while let Some((&mut path_len, rest)) = input_bytes.split_first_chunk_mut() {
|
||||
let (path, rest) = rest.split_at_mut(u16::from_le_bytes(path_len) as usize);
|
||||
let (&mut code_len, rest) = rest.split_first_chunk_mut().unwrap();
|
||||
let (code, rest) = rest.split_at_mut(u16::from_le_bytes(code_len) as usize);
|
||||
files.push(File {
|
||||
path: core::str::from_utf8_unchecked(path),
|
||||
code: core::str::from_utf8_unchecked_mut(code),
|
||||
});
|
||||
input_bytes = rest;
|
||||
}
|
||||
|
||||
let root_path = files[root].path;
|
||||
hblang::quad_sort(&mut files, |a, b| a.path.cmp(b.path));
|
||||
root = files.binary_search_by_key(&root_path, |p| p.path).unwrap();
|
||||
|
||||
files
|
||||
};
|
||||
|
||||
let mut ctx = CodegenCtx::default();
|
||||
|
||||
let files = {
|
||||
let paths = files.iter().map(|f| f.path).collect::<Vec<_>>();
|
||||
let mut loader = |path: &str, _: &str, kind| match kind {
|
||||
hblang::parser::FileKind::Module => Ok(paths.binary_search(&path).unwrap()),
|
||||
hblang::parser::FileKind::Embed => Err("embeds are not supported".into()),
|
||||
};
|
||||
files
|
||||
.into_iter()
|
||||
.map(|f| {
|
||||
hblang::parser::Ast::new(
|
||||
f.path,
|
||||
// since 'free' does nothing this is fine
|
||||
String::from_raw_parts(f.code.as_mut_ptr(), f.code.len(), f.code.len()),
|
||||
&mut ctx.parser,
|
||||
&mut loader,
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let mut ct = {
|
||||
let mut backend = HbvmBackend::default();
|
||||
Codegen::new(&mut backend, &files, &mut ctx).generate(Module::new(root));
|
||||
|
||||
if !ctx.parser.errors.borrow().is_empty() {
|
||||
log::error!("{}", ctx.parser.errors.borrow());
|
||||
return;
|
||||
}
|
||||
|
||||
let mut c = Codegen::new(&mut backend, &files, &mut ctx);
|
||||
c.assemble_comptime()
|
||||
};
|
||||
|
||||
while fuel != 0 {
|
||||
match ct.vm.run() {
|
||||
Ok(hbvm::VmRunOk::End) => {
|
||||
log::error!("exit code: {}", ct.vm.read_reg(1).0 as i64);
|
||||
break;
|
||||
}
|
||||
Ok(hbvm::VmRunOk::Ecall) => {
|
||||
let kind = ct.vm.read_reg(2).0;
|
||||
match kind {
|
||||
0 => {
|
||||
let str = ct.vm.read_reg(3).0;
|
||||
let str = unsafe { CStr::from_ptr(str as _) };
|
||||
log::error!("{}", str.to_str().unwrap());
|
||||
}
|
||||
unknown => log::error!("unknown ecall: {unknown}"),
|
||||
}
|
||||
}
|
||||
Ok(hbvm::VmRunOk::Timer) => {
|
||||
fuel -= 1;
|
||||
if fuel == 0 {
|
||||
log::error!("program timed out");
|
||||
}
|
||||
}
|
||||
Ok(hbvm::VmRunOk::Breakpoint) => todo!(),
|
||||
Err(e) => {
|
||||
log::error!("vm error: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//log::error!("memory consumption: {}b / {}b", ALLOCATOR.used(), ARENA_CAP);
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
[package]
|
||||
name = "wasm-rt"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
log = { version = "0.4.22", optional = true }
|
|
@ -1,162 +0,0 @@
|
|||
#![feature(alloc_error_handler)]
|
||||
#![feature(pointer_is_aligned_to)]
|
||||
#![feature(slice_take)]
|
||||
#![no_std]
|
||||
|
||||
use core::{
|
||||
alloc::{GlobalAlloc, Layout},
|
||||
cell::UnsafeCell,
|
||||
};
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! decl_buffer {
|
||||
($cap:expr, $export_cap:ident, $export_base:ident, $export_len:ident) => {
|
||||
#[no_mangle]
|
||||
static $export_cap: usize = $cap;
|
||||
#[no_mangle]
|
||||
static mut $export_base: [u8; $cap] = [0; $cap];
|
||||
#[no_mangle]
|
||||
static mut $export_len: usize = 0;
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! decl_runtime {
|
||||
($memory_size:expr, $max_panic_size:expr) => {
|
||||
#[cfg(debug_assertions)]
|
||||
#[no_mangle]
|
||||
static mut PANIC_MESSAGE: [u8; $max_panic_size] = [0; $max_panic_size];
|
||||
#[cfg(debug_assertions)]
|
||||
#[no_mangle]
|
||||
static mut PANIC_MESSAGE_LEN: usize = 0;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[panic_handler]
|
||||
pub fn handle_panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
unsafe {
|
||||
use core::fmt::Write;
|
||||
let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
|
||||
_ = writeln!(f, "{}", _info);
|
||||
PANIC_MESSAGE_LEN = $max_panic_size - f.0.len();
|
||||
}
|
||||
}
|
||||
|
||||
core::arch::wasm32::unreachable();
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static ALLOCATOR: $crate::ArenaAllocator<{ $memory_size }> = $crate::ArenaAllocator::new();
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[alloc_error_handler]
|
||||
fn alloc_error(_: core::alloc::Layout) -> ! {
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
unsafe {
|
||||
use core::fmt::Write;
|
||||
let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
|
||||
_ = writeln!(f, "out of memory");
|
||||
PANIC_MESSAGE_LEN = $max_panic_size - f.0.len();
|
||||
}
|
||||
}
|
||||
|
||||
core::arch::wasm32::unreachable()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
pub struct Logger;
|
||||
|
||||
#[cfg(feature = "log")]
|
||||
impl log::Log for Logger {
|
||||
fn enabled(&self, _: &log::Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
const MAX_LOG_MESSAGE: usize = 1024 * 8;
|
||||
#[no_mangle]
|
||||
static mut LOG_MESSAGES: [u8; MAX_LOG_MESSAGE] = [0; MAX_LOG_MESSAGE];
|
||||
#[no_mangle]
|
||||
static mut LOG_MESSAGES_LEN: usize = 0;
|
||||
|
||||
unsafe {
|
||||
use core::fmt::Write;
|
||||
let mut f = Write(&mut LOG_MESSAGES[LOG_MESSAGES_LEN..]);
|
||||
_ = writeln!(f, "{}", record.args());
|
||||
LOG_MESSAGES_LEN = MAX_LOG_MESSAGE - f.0.len();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
pub struct ArenaAllocator<const SIZE: usize> {
|
||||
arena: UnsafeCell<[u8; SIZE]>,
|
||||
head: UnsafeCell<*mut u8>,
|
||||
}
|
||||
|
||||
impl<const SIZE: usize> ArenaAllocator<SIZE> {
|
||||
#[expect(clippy::new_without_default)]
|
||||
pub const fn new() -> Self {
|
||||
ArenaAllocator {
|
||||
arena: UnsafeCell::new([0; SIZE]),
|
||||
head: UnsafeCell::new(core::ptr::null_mut()),
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(clippy::missing_safety_doc)]
|
||||
pub unsafe fn reset(&self) {
|
||||
(*self.head.get()) = self.arena.get().cast::<u8>().add(SIZE);
|
||||
}
|
||||
|
||||
pub fn used(&self) -> usize {
|
||||
unsafe { self.arena.get() as usize + SIZE - (*self.head.get()) as usize }
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<const SIZE: usize> Sync for ArenaAllocator<SIZE> {}
|
||||
|
||||
unsafe impl<const SIZE: usize> GlobalAlloc for ArenaAllocator<SIZE> {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
let size = layout.size();
|
||||
let align = layout.align();
|
||||
|
||||
let until = self.arena.get() as *mut u8;
|
||||
|
||||
let new_head = (*self.head.get()).sub(size);
|
||||
let aligned_head = (new_head as usize & !(align - 1)) as *mut u8;
|
||||
debug_assert!(aligned_head.is_aligned_to(align));
|
||||
|
||||
if until > aligned_head {
|
||||
return core::ptr::null_mut();
|
||||
}
|
||||
|
||||
*self.head.get() = aligned_head;
|
||||
aligned_head
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
|
||||
/* lol */
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Write<'a>(pub &'a mut [u8]);
|
||||
|
||||
impl core::fmt::Write for Write<'_> {
|
||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
||||
if let Some(m) = self.0.take_mut(..s.len()) {
|
||||
m.copy_from_slice(s.as_bytes());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(core::fmt::Error)
|
||||
}
|
||||
}
|
||||
}
|
9
hbasm/Cargo.toml
Normal file
9
hbasm/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "hbasm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
paste = "1.0"
|
||||
rhai = "1.16"
|
||||
with_builtin_macros = "0.0.3"
|
13
hbasm/examples/ableos/main.rhai
Normal file
13
hbasm/examples/ableos/main.rhai
Normal file
|
@ -0,0 +1,13 @@
|
|||
import "hbasm/examples/ableos/std" as std;
|
||||
|
||||
fn main(){
|
||||
std::Error(":+)");
|
||||
std::Warn("Your mom fell in a well!");
|
||||
std::Info("Hello, world!");
|
||||
std::Debug("ABC");
|
||||
std::Trace("Trace Deez");
|
||||
|
||||
tx();
|
||||
}
|
||||
|
||||
main();
|
24
hbasm/examples/ableos/std.rhai
Normal file
24
hbasm/examples/ableos/std.rhai
Normal file
|
@ -0,0 +1,24 @@
|
|||
fn ipc_send(buffer_id, mem_addr, length){
|
||||
// set the ecall
|
||||
li8(r1, 3);
|
||||
// Set the buffer ID to be the BufferID
|
||||
li64(r2, buffer_id);
|
||||
lra(r3, r0, mem_addr);
|
||||
// set the length
|
||||
li64(r4, length);
|
||||
// ecall
|
||||
eca();
|
||||
}
|
||||
|
||||
private fn log(log_level, string){
|
||||
let str = data::str(string);
|
||||
ipc_send(1, str, str.len);
|
||||
}
|
||||
|
||||
fn Error(string) {log(0, string);}
|
||||
fn Warn(string) {log(1, string);}
|
||||
fn Info(string) {log(2, string);}
|
||||
// Due to rhai limitations this cannot be debug
|
||||
// because of this all of the log levels are upper case
|
||||
fn Debug(string) {log(3, string);}
|
||||
fn Trace(string) {log(4, string);}
|
9
hbasm/examples/hello-linux.rhai
Normal file
9
hbasm/examples/hello-linux.rhai
Normal file
|
@ -0,0 +1,9 @@
|
|||
let hello = data::str("Hello, world!");
|
||||
|
||||
li8 (r1, 1); // Write syscall
|
||||
li8 (r2, 1); // Stdout FD
|
||||
lra16 (r3, r0, hello); // String buffer
|
||||
li8 (r4, hello.len); // String length
|
||||
eca (); // System call
|
||||
|
||||
tx (); // End program
|
33
hbasm/examples/macros.rhai
Normal file
33
hbasm/examples/macros.rhai
Normal file
|
@ -0,0 +1,33 @@
|
|||
li8(r1, 69);
|
||||
li8(r2, 0);
|
||||
|
||||
if_eq(r1, r2,
|
||||
|| puts("Equals!"),
|
||||
|| puts("Not equals!"),
|
||||
);
|
||||
|
||||
|
||||
tx(); // END OF MAIN
|
||||
|
||||
/// Inline function – write text to stdout
|
||||
fn puts(string) {
|
||||
let d = data::str(string);
|
||||
li8 (r1, 1); // Write syscall
|
||||
li8 (r2, 1); // Stdout handle
|
||||
lra16 (r3, r0, d);
|
||||
li64 (r4, d.len);
|
||||
eca ();
|
||||
}
|
||||
|
||||
fn if_eq(a, b, thenblk, elseblk) {
|
||||
let elselbl = declabel();
|
||||
let endlbl = declabel();
|
||||
|
||||
jne(a, b, elselbl);
|
||||
thenblk.call();
|
||||
jmp16(endlbl);
|
||||
|
||||
elselbl.here();
|
||||
elseblk.call();
|
||||
endlbl.here();
|
||||
}
|
101
hbasm/src/data.rs
Normal file
101
hbasm/src/data.rs
Normal file
|
@ -0,0 +1,101 @@
|
|||
//! Data section inserts
|
||||
|
||||
use {
|
||||
crate::{object::SymbolRef, SharedObject},
|
||||
rhai::{CustomType, Engine, FuncRegistration, ImmutableString, Module},
|
||||
};
|
||||
|
||||
/// Generate insertions for data types
|
||||
///
|
||||
/// `gen_data_instructions!($module, $obj, [$type, …]);`
|
||||
/// - `$module`: Rhai module
|
||||
/// - `$obj`: Code object
|
||||
/// - `$type`: Type of single array item
|
||||
macro_rules! gen_data_insertions {
|
||||
($module:expr, $obj:expr, [$($ty:ident),* $(,)?] $(,)?) => {{
|
||||
let (module, obj) = ($module, $obj);
|
||||
$({
|
||||
// Clone object to each function
|
||||
let obj = ::std::rc::Rc::clone(obj);
|
||||
|
||||
FuncRegistration::new(stringify!($ty))
|
||||
.with_namespace(rhai::FnNamespace::Global)
|
||||
.set_into_module::<_, 1, false, _, true, _>(module, move |arr: ::rhai::Array| {
|
||||
let obj = &mut *obj.borrow_mut();
|
||||
let symbol = obj.symbol($crate::object::Section::Data);
|
||||
|
||||
// Reserve space for object so we don't resize it
|
||||
// all the time
|
||||
obj.sections
|
||||
.data
|
||||
.reserve(arr.len() * ::std::mem::size_of::<$ty>());
|
||||
|
||||
// For every item…
|
||||
for item in arr {
|
||||
// … try do conversions from i32 to desired type
|
||||
// and insert it.
|
||||
obj.sections.data.extend(
|
||||
match item.as_int() {
|
||||
Ok(num) => $ty::try_from(num).map_err(|_| "i64".to_owned()),
|
||||
Err(ty) => Err(ty.to_owned()),
|
||||
}
|
||||
.map_err(|err| {
|
||||
|
||||
::rhai::EvalAltResult::ErrorMismatchDataType(
|
||||
stringify!($ty).to_owned(),
|
||||
err,
|
||||
::rhai::Position::NONE,
|
||||
)
|
||||
})?
|
||||
.to_le_bytes(),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(DataRef {
|
||||
symbol,
|
||||
len: obj.sections.data.len() - symbol.0,
|
||||
})
|
||||
});
|
||||
})*
|
||||
}};
|
||||
}
|
||||
|
||||
/// Reference to entry in data section
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DataRef {
|
||||
pub symbol: SymbolRef,
|
||||
pub len: usize,
|
||||
}
|
||||
|
||||
impl CustomType for DataRef {
|
||||
fn build(mut builder: rhai::TypeBuilder<Self>) {
|
||||
builder
|
||||
.with_name("DataRef")
|
||||
.with_get("symbol", |this: &mut Self| this.symbol)
|
||||
.with_get("len", |this: &mut Self| this.len as u64 as i64);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn module(engine: &mut Engine, obj: SharedObject) -> Module {
|
||||
let mut module = Module::new();
|
||||
|
||||
gen_data_insertions!(&mut module, &obj, [i8, i16, i32, i64]);
|
||||
|
||||
// Specialisation for strings, they should be
|
||||
// inserted as plain UTF-8 arrays
|
||||
FuncRegistration::new("str")
|
||||
.with_namespace(rhai::FnNamespace::Global)
|
||||
.set_into_module::<_, 1, false, _, true, _>(&mut module, move |s: ImmutableString| {
|
||||
let obj = &mut *obj.borrow_mut();
|
||||
let symbol = obj.symbol(crate::object::Section::Data);
|
||||
|
||||
obj.sections.data.extend(s.as_bytes());
|
||||
Ok(DataRef {
|
||||
symbol,
|
||||
len: s.len(),
|
||||
})
|
||||
});
|
||||
|
||||
engine.build_type::<DataRef>();
|
||||
module
|
||||
}
|
321
hbasm/src/ins.rs
Normal file
321
hbasm/src/ins.rs
Normal file
|
@ -0,0 +1,321 @@
|
|||
//! Functions for inserting instructions
|
||||
//!
|
||||
//! Most of the code you see is just metaprogramming stuff.
|
||||
//! This ensures that adding new instructions won't need any
|
||||
//! specific changes and consistent behaviour.
|
||||
//!
|
||||
//! > I tried to comment stuff here, but I meanwhile forgor how it works.
|
||||
//!
|
||||
//! — Erin
|
||||
|
||||
use {
|
||||
crate::object::Object,
|
||||
rhai::{FuncRegistration, Module},
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
};
|
||||
|
||||
/// Operand types and their insertions
|
||||
pub mod optypes {
|
||||
use {
|
||||
crate::{
|
||||
label::UnboundLabel,
|
||||
object::{Object, RelocKey, RelocType, SymbolRef},
|
||||
},
|
||||
rhai::{Dynamic, EvalAltResult, ImmutableString, Position},
|
||||
};
|
||||
|
||||
// These types represent operand types to be inserted
|
||||
pub type R = u8;
|
||||
pub type B = i8;
|
||||
pub type H = i16;
|
||||
pub type W = i32;
|
||||
pub type D = i64;
|
||||
|
||||
pub type A = Dynamic;
|
||||
pub type O = Dynamic;
|
||||
pub type P = Dynamic;
|
||||
|
||||
/// Insert relocation into code
|
||||
///
|
||||
/// - If integer, just write it to the code
|
||||
/// - Otherwise insert entry into relocation table
|
||||
/// and fill zeroes
|
||||
pub fn insert_reloc(
|
||||
obj: &mut Object,
|
||||
ty: RelocType,
|
||||
val: &Dynamic,
|
||||
) -> Result<(), EvalAltResult> {
|
||||
match () {
|
||||
// Direct references – insert directly to table
|
||||
_ if val.is::<SymbolRef>() => {
|
||||
obj.relocation(RelocKey::Symbol(val.clone_cast::<SymbolRef>().0), ty)
|
||||
}
|
||||
_ if val.is::<UnboundLabel>() => {
|
||||
obj.relocation(RelocKey::Symbol(val.clone_cast::<UnboundLabel>().0), ty)
|
||||
}
|
||||
_ if val.is::<DataRef>() => {
|
||||
obj.relocation(RelocKey::Symbol(val.clone_cast::<DataRef>().symbol.0), ty)
|
||||
}
|
||||
|
||||
// String (indirect) reference
|
||||
_ if val.is_string() => {
|
||||
obj.relocation(RelocKey::Label(val.clone_cast::<ImmutableString>()), ty)
|
||||
}
|
||||
|
||||
// Manual offset
|
||||
_ if val.is_int() => {
|
||||
let int = val.clone_cast::<i64>();
|
||||
match ty {
|
||||
RelocType::Rel32 => obj.sections.text.extend((int as i32).to_le_bytes()),
|
||||
RelocType::Rel16 => obj.sections.text.extend((int as i16).to_le_bytes()),
|
||||
RelocType::Abs64 => obj.sections.text.extend(int.to_le_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
_ => {
|
||||
return Err(EvalAltResult::ErrorMismatchDataType(
|
||||
"SymbolRef, UnboundLabel, String or Int".to_owned(),
|
||||
val.type_name().to_owned(),
|
||||
Position::NONE,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Generate macro for inserting item into the output object
|
||||
///
|
||||
/// Pre-defines inserts for absolute address and relative offsets.
|
||||
/// These are inserted with function [`insert_reloc`]
|
||||
/// # le_bytes
|
||||
/// `gen_insert!(le_bytes: [B, …]);`
|
||||
///
|
||||
/// Takes sequence of operand types which should be inserted
|
||||
/// by invoking `to_le_bytes` method on it.
|
||||
macro_rules! gen_insert {
|
||||
(le_bytes: [$($lety:ident),* $(,)?]) => {
|
||||
/// `insert!($thing, $obj, $type)` where
|
||||
/// - `$thing`: Value you want to insert
|
||||
/// - `$obj`: Code object
|
||||
/// - `$type`: Type of inserted value
|
||||
///
|
||||
/// Eg. `insert!(69_u8, obj, B);`
|
||||
macro_rules! insert {
|
||||
$(($thing:expr, $obj: expr, $lety) => {
|
||||
$obj.sections.text.extend($thing.to_le_bytes());
|
||||
};)*
|
||||
|
||||
($thing:expr, $obj:expr, A) => {
|
||||
$crate::ins::optypes::insert_reloc(
|
||||
$obj,
|
||||
$crate::object::RelocType::Abs64,
|
||||
$thing
|
||||
)?
|
||||
};
|
||||
($thing:expr, $obj:expr, O) => {
|
||||
$crate::ins::optypes::insert_reloc(
|
||||
$obj,
|
||||
$crate::object::RelocType::Rel32,
|
||||
$thing
|
||||
)?
|
||||
};
|
||||
($thing:expr, $obj:expr, P) => {
|
||||
$crate::ins::optypes::insert_reloc(
|
||||
$obj,
|
||||
$crate::object::RelocType::Rel16,
|
||||
$thing
|
||||
)?
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
gen_insert!(le_bytes: [R, B, H, W, D]);
|
||||
|
||||
#[allow(clippy::single_component_path_imports)]
|
||||
pub(super) use insert;
|
||||
|
||||
use crate::data::DataRef;
|
||||
}
|
||||
|
||||
/// Rhai Types (types for function parameters as Rhai uses only 64bit signed integers)
|
||||
pub mod rity {
|
||||
pub use super::optypes::{A, O, P, R};
|
||||
pub type B = i64;
|
||||
pub type H = i64;
|
||||
pub type W = i64;
|
||||
pub type D = i64;
|
||||
}
|
||||
|
||||
/// Generic instruction (instruction of certain operands type) inserts
|
||||
pub mod generic {
|
||||
use {crate::object::Object, rhai::EvalAltResult};
|
||||
|
||||
pub(super) fn convert_op<A, B>(from: A) -> Result<B, EvalAltResult>
|
||||
where
|
||||
B: TryFrom<A>,
|
||||
<B as TryFrom<A>>::Error: std::error::Error + Sync + Send + 'static,
|
||||
{
|
||||
B::try_from(from).map_err(|e| {
|
||||
EvalAltResult::ErrorSystem("Data conversion error".to_owned(), Box::new(e))
|
||||
})
|
||||
}
|
||||
|
||||
/// Generate opcode-generic instruction insert macro
|
||||
macro_rules! gen_ins {
|
||||
($($($name:ident : $ty:ty),*;)*) => {
|
||||
paste::paste! {
|
||||
$(
|
||||
/// Instruction-generic opcode insertion function
|
||||
/// - `obj`: Code object
|
||||
/// - `opcode`: opcode, not checked if valid for instruction type
|
||||
/// - … for operands
|
||||
#[inline]
|
||||
pub fn [<$($ty:lower)*>](
|
||||
obj: &mut Object,
|
||||
opcode: u8,
|
||||
$($name: $crate::ins::optypes::$ty),*,
|
||||
) -> Result<(), EvalAltResult> {
|
||||
// Push opcode
|
||||
obj.sections.text.push(opcode);
|
||||
|
||||
// Insert based on type
|
||||
$($crate::ins::optypes::insert!(&$name, obj, $ty);)*
|
||||
Ok(())
|
||||
}
|
||||
)*
|
||||
|
||||
/// Generate Rhai opcode-specific instruction insertion functions
|
||||
///
|
||||
/// `gen_ins_fn!($obj, $opcode, $optype);` where:
|
||||
/// - `$obj`: Code object
|
||||
/// - `$opcode`: Opcode value
|
||||
macro_rules! gen_ins_fn {
|
||||
$(
|
||||
($obj:expr, $opcode:expr, [<$($ty)*>]) => {
|
||||
// Opcode-specific insertion function
|
||||
// - Parameters = operands
|
||||
move |$($name: $crate::ins::rity::$ty),*| {
|
||||
// Invoke generic function
|
||||
$crate::ins::generic::[<$($ty:lower)*>](
|
||||
&mut *$obj.borrow_mut(),
|
||||
$opcode,
|
||||
$(
|
||||
// Convert to desired type (from Rhai-provided values)
|
||||
$crate::ins::generic::convert_op::<
|
||||
_,
|
||||
$crate::ins::optypes::$ty
|
||||
>($name)?
|
||||
),*
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
// Internal-use: count args
|
||||
(@arg_count [<$($ty)*>]) => {
|
||||
{ ["", $(stringify!($ty)),*].len() - 1 }
|
||||
};
|
||||
)*
|
||||
|
||||
// Specialisation for no-operand instructions
|
||||
($obj:expr, $opcode:expr, N) => {
|
||||
move || {
|
||||
$crate::ins::generic::n(&mut *$obj.borrow_mut(), $opcode);
|
||||
Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
// Internal-use specialisation: no-operand instructions
|
||||
(@arg_count N) => {
|
||||
{ 0 }
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Specialisation for no-operand instructions – simply just push opcode
|
||||
#[inline]
|
||||
pub fn n(obj: &mut Object, opcode: u8) {
|
||||
obj.sections.text.push(opcode);
|
||||
}
|
||||
|
||||
// Generate opcode-generic instruction inserters
|
||||
// (operand identifiers are arbitrary)
|
||||
//
|
||||
// New instruction types have to be added manually here
|
||||
gen_ins! {
|
||||
o0: R, o1: R;
|
||||
o0: R, o1: R, o2: R;
|
||||
o0: R, o1: R, o2: R, o3: R;
|
||||
o0: R, o1: R, o2: B;
|
||||
o0: R, o1: R, o2: H;
|
||||
o0: R, o1: R, o2: W;
|
||||
o0: R, o1: R, o2: D;
|
||||
o0: R, o1: B;
|
||||
o0: R, o1: H;
|
||||
o0: R, o1: W;
|
||||
o0: R, o1: D;
|
||||
o0: R, o1: R, o2: A;
|
||||
o0: R, o1: R, o2: A, o3: H;
|
||||
o0: R, o1: R, o2: O, o3: H;
|
||||
o0: R, o1: R, o2: P, o3: H;
|
||||
o0: R, o1: R, o2: O;
|
||||
o0: R, o1: R, o2: P;
|
||||
o0: O;
|
||||
o0: P;
|
||||
}
|
||||
|
||||
#[allow(clippy::single_component_path_imports)]
|
||||
pub(super) use gen_ins_fn;
|
||||
}
|
||||
|
||||
/// Generate instructions from instruction table
|
||||
///
|
||||
/// ```ignore
|
||||
/// instructions!(($module, $obj) {
|
||||
/// // Data from instruction table
|
||||
/// $opcode, $mnemonic, $opty, $doc;
|
||||
/// …
|
||||
/// });
|
||||
/// ```
|
||||
/// - `$module`: Rhai module
|
||||
/// - `$obj`: Code object
|
||||
macro_rules! instructions {
|
||||
(
|
||||
($module:expr, $obj:expr $(,)?)
|
||||
{ $($opcode:expr, $mnemonic:ident, $ops:tt, $doc:literal;)* }
|
||||
) => {{
|
||||
paste::paste! {
|
||||
let (module, obj) = ($module, $obj);
|
||||
$({
|
||||
// Object is shared across all functions
|
||||
let obj = Rc::clone(&obj);
|
||||
|
||||
// Register newly generated function for each instruction
|
||||
FuncRegistration::new(stringify!([<$mnemonic:lower>]))
|
||||
.with_namespace(rhai::FnNamespace::Global)
|
||||
.set_into_module::<_, { generic::gen_ins_fn!(@arg_count $ops) }, false, _, true, _>(
|
||||
module,
|
||||
generic::gen_ins_fn!(
|
||||
obj,
|
||||
$opcode,
|
||||
$ops
|
||||
)
|
||||
);
|
||||
})*
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Setup instruction insertors
|
||||
pub fn setup(module: &mut Module, obj: Rc<RefCell<Object>>) {
|
||||
// Import instructions table and use it for generation
|
||||
with_builtin_macros::with_builtin! {
|
||||
let $spec = include_from_root!("../hbbytecode/instructions.in") in {
|
||||
instructions!((module, obj) { $spec });
|
||||
}
|
||||
}
|
||||
}
|
112
hbasm/src/label.rs
Normal file
112
hbasm/src/label.rs
Normal file
|
@ -0,0 +1,112 @@
|
|||
//! Stuff related to labels
|
||||
|
||||
use {
|
||||
crate::SharedObject,
|
||||
rhai::{Engine, FuncRegistration, ImmutableString, Module},
|
||||
};
|
||||
|
||||
/// Macro for creating functions for Rhai which
|
||||
/// is bit more friendly
|
||||
///
|
||||
/// ```ignore
|
||||
/// shdm_fns!{
|
||||
/// module: $module;
|
||||
/// shared: $shared => $shname;
|
||||
///
|
||||
/// $vis fn $name($param_name: $param_ty, …) -> $ret { … }
|
||||
/// …
|
||||
/// }
|
||||
/// ```
|
||||
/// - `$module`: Rhai module
|
||||
/// - `$shared`: Data to be shared across the functions
|
||||
/// - `$shname`: The binding name inside functions
|
||||
/// - `$vis`: Function visibility for Rhai
|
||||
/// - Lowercased [`rhai::FnNamespace`] variants
|
||||
/// - `$name`: Function name
|
||||
/// - `$param_name`: Parameter name
|
||||
/// - `$param_ty`: Rust parameter type
|
||||
/// - `$ret`: Optional return type (otherwise infer)
|
||||
macro_rules! shdm_fns {
|
||||
(
|
||||
module: $module:expr;
|
||||
shared: $shared:expr => $shname:ident;
|
||||
|
||||
$(
|
||||
$vis:ident fn $name:ident($($param_name:ident: $param_ty:ty),*) $(-> $ret:ty)? $blk:block
|
||||
)*
|
||||
) => {{
|
||||
let module = $module;
|
||||
let shared = $shared;
|
||||
paste::paste! {
|
||||
$({
|
||||
|
||||
let $shname = SharedObject::clone(&shared);
|
||||
|
||||
FuncRegistration::new(stringify!($name))
|
||||
.with_namespace(rhai::FnNamespace::[<$vis:camel>])
|
||||
.set_into_module::<_, { ["", $(stringify!($param_name)),*].len() - 1 }, false, _, true, _>(
|
||||
module,
|
||||
move |$($param_name: $param_ty),*| $(-> $ret)? {
|
||||
let mut $shname = $shname.borrow_mut();
|
||||
$blk
|
||||
}
|
||||
);
|
||||
})*
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
/// Label without any place bound
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UnboundLabel(pub usize);
|
||||
|
||||
pub fn setup(engine: &mut Engine, module: &mut Module, object: SharedObject) {
|
||||
shdm_fns! {
|
||||
module: module;
|
||||
shared: object => obj;
|
||||
|
||||
// Insert unnamed label
|
||||
global fn label() {
|
||||
let symbol = obj.symbol(crate::object::Section::Text);
|
||||
Ok(symbol)
|
||||
}
|
||||
|
||||
// Insert string-labeled label
|
||||
global fn label(label: ImmutableString) {
|
||||
let symbol = obj.symbol(crate::object::Section::Text);
|
||||
obj.labels.insert(label, symbol.0);
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
|
||||
// Declare unbound label (to be bound later)
|
||||
global fn declabel() {
|
||||
let index = obj.symbols.len();
|
||||
obj.symbols.push(None);
|
||||
|
||||
Ok(UnboundLabel(index))
|
||||
}
|
||||
|
||||
// Declare unbound label (to be bound later)
|
||||
// with string label
|
||||
global fn declabel(label: ImmutableString) {
|
||||
let index = obj.symbols.len();
|
||||
obj.symbols.push(None);
|
||||
obj.labels.insert(label, index);
|
||||
|
||||
Ok(UnboundLabel(index))
|
||||
}
|
||||
|
||||
// Set location for unbound label
|
||||
global fn here(label: UnboundLabel) {
|
||||
obj.symbols[label.0] = Some(crate::object::SymbolEntry {
|
||||
location: crate::object::Section::Text,
|
||||
offset: obj.sections.text.len(),
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
engine.register_type_with_name::<UnboundLabel>("UnboundLabel");
|
||||
}
|
45
hbasm/src/lib.rs
Normal file
45
hbasm/src/lib.rs
Normal file
|
@ -0,0 +1,45 @@
|
|||
pub mod data;
|
||||
pub mod ins;
|
||||
pub mod label;
|
||||
pub mod linker;
|
||||
pub mod object;
|
||||
|
||||
use {
|
||||
object::Object,
|
||||
rhai::{Engine, Module},
|
||||
std::{cell::RefCell, rc::Rc},
|
||||
};
|
||||
|
||||
type SharedObject = Rc<RefCell<Object>>;
|
||||
|
||||
pub fn assembler(
|
||||
linkout: &mut impl std::io::Write,
|
||||
loader: impl FnOnce(&mut Engine) -> Result<(), Box<rhai::EvalAltResult>>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut engine = Engine::new();
|
||||
let mut module = Module::new();
|
||||
let obj = Rc::new(RefCell::new(Object::default()));
|
||||
ins::setup(&mut module, Rc::clone(&obj));
|
||||
label::setup(&mut engine, &mut module, Rc::clone(&obj));
|
||||
|
||||
// Registers
|
||||
for n in 0_u8..=255 {
|
||||
module.set_var(format!("r{n}"), n);
|
||||
}
|
||||
|
||||
module.set_native_fn("reg", |n: i64| {
|
||||
Ok(u8::try_from(n).map_err(|_| {
|
||||
rhai::EvalAltResult::ErrorRuntime("Invalid register value".into(), rhai::Position::NONE)
|
||||
})?)
|
||||
});
|
||||
|
||||
module.set_native_fn("as_i64", |n: u8| Ok(n as i64));
|
||||
|
||||
let datamod = Rc::new(data::module(&mut engine, SharedObject::clone(&obj)));
|
||||
engine.register_global_module(Rc::new(module));
|
||||
engine.register_static_module("data", datamod);
|
||||
engine.register_type_with_name::<object::SymbolRef>("SymbolRef");
|
||||
loader(&mut engine)?;
|
||||
linker::link(obj, linkout)?;
|
||||
Ok(())
|
||||
}
|
47
hbasm/src/linker.rs
Normal file
47
hbasm/src/linker.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
//! Simple flat-bytecode linker
|
||||
|
||||
use {
|
||||
crate::{
|
||||
object::{RelocKey, RelocType, Section},
|
||||
SharedObject,
|
||||
},
|
||||
std::io::Write,
|
||||
};
|
||||
|
||||
pub fn link(object: SharedObject, out: &mut impl Write) -> std::io::Result<()> {
|
||||
let obj = &mut *object.borrow_mut();
|
||||
|
||||
// Walk relocation table entries
|
||||
for (&loc, entry) in &obj.relocs {
|
||||
let value = match &entry.key {
|
||||
// Symbol – direct reference
|
||||
RelocKey::Symbol(sym) => obj.symbols[*sym],
|
||||
|
||||
// Label – indirect label reference
|
||||
RelocKey::Label(label) => obj.symbols[obj.labels[label]],
|
||||
}
|
||||
.ok_or_else(|| std::io::Error::other("Invalid symbol"))?;
|
||||
|
||||
let offset = match value.location {
|
||||
// Text section is on the beginning
|
||||
Section::Text => value.offset,
|
||||
|
||||
// Data section follows text section immediately
|
||||
Section::Data => value.offset + obj.sections.text.len(),
|
||||
};
|
||||
|
||||
// Insert address or calulate relative offset
|
||||
match entry.ty {
|
||||
RelocType::Rel32 => obj.sections.text[loc..loc + 4]
|
||||
.copy_from_slice(&((offset as isize - loc as isize) as i32).to_le_bytes()),
|
||||
RelocType::Rel16 => obj.sections.text[loc..loc + 2]
|
||||
.copy_from_slice(&((offset as isize - loc as isize) as i16).to_le_bytes()),
|
||||
RelocType::Abs64 => obj.sections.text[loc..loc + 8]
|
||||
.copy_from_slice(&(offset as isize - loc as isize).to_le_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
// Write to output
|
||||
out.write_all(&obj.sections.text)?;
|
||||
out.write_all(&obj.sections.data)
|
||||
}
|
8
hbasm/src/main.rs
Normal file
8
hbasm/src/main.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
use std::{io::stdout, path::PathBuf};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let path = PathBuf::from(std::env::args().nth(1).ok_or("Missing path")?);
|
||||
hbasm::assembler(&mut stdout(), |engine| engine.run_file(path))?;
|
||||
|
||||
Ok(())
|
||||
}
|
94
hbasm/src/object.rs
Normal file
94
hbasm/src/object.rs
Normal file
|
@ -0,0 +1,94 @@
|
|||
//! Code object
|
||||
|
||||
use {rhai::ImmutableString, std::collections::HashMap};
|
||||
|
||||
/// Section tabel
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Section {
|
||||
Text,
|
||||
Data,
|
||||
}
|
||||
|
||||
/// Symbol entry (in what section, where)
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SymbolEntry {
|
||||
pub location: Section,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
/// Relocation table key
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum RelocKey {
|
||||
/// Direct reference
|
||||
Symbol(usize),
|
||||
/// Indirect reference
|
||||
Label(ImmutableString),
|
||||
}
|
||||
|
||||
/// Relocation type
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum RelocType {
|
||||
Rel32,
|
||||
Rel16,
|
||||
Abs64,
|
||||
}
|
||||
|
||||
/// Relocation table entry
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RelocEntry {
|
||||
pub key: RelocKey,
|
||||
pub ty: RelocType,
|
||||
}
|
||||
|
||||
/// Object code
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Sections {
|
||||
pub text: Vec<u8>,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
/// Object
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Object {
|
||||
/// Vectors with sections
|
||||
pub sections: Sections,
|
||||
/// Symbol table
|
||||
pub symbols: Vec<Option<SymbolEntry>>,
|
||||
/// Labels to symbols table
|
||||
pub labels: HashMap<ImmutableString, usize>,
|
||||
/// Relocation table
|
||||
pub relocs: HashMap<usize, RelocEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(transparent)]
|
||||
pub struct SymbolRef(pub usize);
|
||||
|
||||
impl Object {
|
||||
/// Insert symbol at current location in specified section
|
||||
pub fn symbol(&mut self, section: Section) -> SymbolRef {
|
||||
let section_buf = match section {
|
||||
Section::Text => &mut self.sections.text,
|
||||
Section::Data => &mut self.sections.data,
|
||||
};
|
||||
|
||||
self.symbols.push(Some(SymbolEntry {
|
||||
location: section,
|
||||
offset: section_buf.len(),
|
||||
}));
|
||||
|
||||
SymbolRef(self.symbols.len() - 1)
|
||||
}
|
||||
|
||||
/// Insert to relocation table and write zeroes to code
|
||||
pub fn relocation(&mut self, key: RelocKey, ty: RelocType) {
|
||||
self.relocs
|
||||
.insert(self.sections.text.len(), RelocEntry { key, ty });
|
||||
|
||||
self.sections.text.extend(match ty {
|
||||
RelocType::Rel32 => &[0_u8; 4] as &[u8],
|
||||
RelocType::Rel16 => &[0; 2],
|
||||
RelocType::Abs64 => &[0; 8],
|
||||
});
|
||||
}
|
||||
}
|
|
@ -3,8 +3,6 @@ name = "hbbytecode"
|
|||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = ["disasm"]
|
||||
std = []
|
||||
disasm = ["std"]
|
||||
|
||||
[dependencies]
|
||||
paste = "1.0.14"
|
||||
with_builtin_macros = "0.0.3"
|
185
hbbytecode/src/lib.rs
Normal file
185
hbbytecode/src/lib.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
#![no_std]
|
||||
|
||||
use core::convert::TryFrom;
|
||||
|
||||
type OpR = u8;
|
||||
|
||||
type OpA = u64;
|
||||
type OpO = i32;
|
||||
type OpP = i16;
|
||||
|
||||
type OpB = u8;
|
||||
type OpH = u16;
|
||||
type OpW = u32;
|
||||
type OpD = u64;
|
||||
|
||||
/// # Safety
|
||||
/// Has to be valid to be decoded from bytecode.
|
||||
pub unsafe trait BytecodeItem {}
|
||||
macro_rules! define_items {
|
||||
($($name:ident ($($nm:ident: $item:ident),* $(,)?)),* $(,)?) => {
|
||||
$(
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(packed)]
|
||||
pub struct $name($(pub $item),*);
|
||||
unsafe impl BytecodeItem for $name {}
|
||||
|
||||
impl Encodable for $name {
|
||||
fn encode(self, _buffer: &mut impl Buffer) {
|
||||
let Self($($nm),*) = self;
|
||||
$(
|
||||
for byte in $nm.to_le_bytes() {
|
||||
unsafe { _buffer.write(byte) };
|
||||
}
|
||||
)*
|
||||
}
|
||||
|
||||
fn encode_len(self) -> usize {
|
||||
core::mem::size_of::<Self>()
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
define_items! {
|
||||
OpsRR (a: OpR, b: OpR ),
|
||||
OpsRRR (a: OpR, b: OpR, c: OpR ),
|
||||
OpsRRRR (a: OpR, b: OpR, c: OpR, d: OpR),
|
||||
OpsRRB (a: OpR, b: OpR, c: OpB ),
|
||||
OpsRRH (a: OpR, b: OpR, c: OpH ),
|
||||
OpsRRW (a: OpR, b: OpR, c: OpW ),
|
||||
OpsRRD (a: OpR, b: OpR, c: OpD ),
|
||||
OpsRB (a: OpR, b: OpB ),
|
||||
OpsRH (a: OpR, b: OpH ),
|
||||
OpsRW (a: OpR, b: OpW ),
|
||||
OpsRD (a: OpR, b: OpD ),
|
||||
OpsRRA (a: OpR, b: OpR, c: OpA ),
|
||||
OpsRRAH (a: OpR, b: OpR, c: OpA, d: OpH),
|
||||
OpsRROH (a: OpR, b: OpR, c: OpO, d: OpH),
|
||||
OpsRRPH (a: OpR, b: OpR, c: OpP, d: OpH),
|
||||
OpsRRO (a: OpR, b: OpR, c: OpO ),
|
||||
OpsRRP (a: OpR, b: OpR, c: OpP ),
|
||||
OpsO (a: OpO, ),
|
||||
OpsP (a: OpP, ),
|
||||
OpsN ( ),
|
||||
}
|
||||
|
||||
unsafe impl BytecodeItem for u8 {}
|
||||
|
||||
::with_builtin_macros::with_builtin! {
|
||||
let $spec = include_from_root!("instructions.in") in {
|
||||
/// Invoke macro with bytecode definition
|
||||
///
|
||||
/// # Format
|
||||
/// ```text
|
||||
/// Opcode, Mnemonic, Type, Docstring;
|
||||
/// ```
|
||||
///
|
||||
/// # Type
|
||||
/// ```text
|
||||
/// Types consist of letters meaning a single field
|
||||
/// | Type | Size (B) | Meaning |
|
||||
/// |:-----|:---------|:------------------------|
|
||||
/// | N | 0 | Empty |
|
||||
/// | R | 1 | Register |
|
||||
/// | A | 8 | Absolute address |
|
||||
/// | O | 4 | Relative address offset |
|
||||
/// | P | 2 | Relative address offset |
|
||||
/// | B | 1 | Immediate |
|
||||
/// | H | 2 | Immediate |
|
||||
/// | W | 4 | Immediate |
|
||||
/// | D | 8 | Immediate |
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! invoke_with_def {
|
||||
($($macro:tt)*) => {
|
||||
$($macro)*! { $spec }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Buffer {
|
||||
fn reserve(&mut self, bytes: usize);
|
||||
/// # Safety
|
||||
/// Reserve needs to be called before this function, and only reserved amount can be written.
|
||||
unsafe fn write(&mut self, byte: u8);
|
||||
}
|
||||
|
||||
pub trait Encodable {
|
||||
fn encode(self, buffer: &mut impl Buffer);
|
||||
fn encode_len(self) -> usize;
|
||||
}
|
||||
|
||||
macro_rules! gen_opcodes {
|
||||
($($opcode:expr, $mnemonic:ident, $ty:ident, $doc:literal;)*) => {
|
||||
pub mod opcode {
|
||||
$(
|
||||
#[doc = $doc]
|
||||
pub const $mnemonic: u8 = $opcode;
|
||||
)*
|
||||
|
||||
paste::paste! {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum Op { $(
|
||||
[< $mnemonic:lower:camel >](super::[<Ops $ty>]),
|
||||
)* }
|
||||
|
||||
impl Op {
|
||||
pub fn size(&self) -> usize {
|
||||
(match self {
|
||||
$(Self::[<$mnemonic:lower:camel>] { .. } => core::mem::size_of::<super::[<Ops $ty>]>(),)*
|
||||
}) + 1
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::Encodable for Op {
|
||||
fn encode(self, buffer: &mut impl crate::Buffer) {
|
||||
match self {
|
||||
$(
|
||||
Self::[< $mnemonic:lower:camel >](op) => {
|
||||
unsafe { buffer.write($opcode) };
|
||||
op.encode(buffer);
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_len(self) -> usize {
|
||||
match self {
|
||||
$(
|
||||
Self::[< $mnemonic:lower:camel >](op) => {
|
||||
1 + crate::Encodable::encode_len(op)
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Rounding mode
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
#[repr(u8)]
|
||||
pub enum RoundingMode {
|
||||
NearestEven = 0,
|
||||
Truncate = 1,
|
||||
Up = 2,
|
||||
Down = 3,
|
||||
}
|
||||
|
||||
impl TryFrom<u8> for RoundingMode {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
||||
(value <= 3)
|
||||
.then(|| unsafe { core::mem::transmute(value) })
|
||||
.ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
invoke_with_def!(gen_opcodes);
|
13
hbvm/Cargo.toml
Normal file
13
hbvm/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "hbvm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["alloc", "soft-float"]
|
||||
alloc = []
|
||||
nightly = []
|
||||
soft-float = []
|
||||
|
||||
[dependencies]
|
||||
hbbytecode = { path = "../hbbytecode" }
|
|
@ -13,6 +13,9 @@ libfuzzer-sys = "0.4"
|
|||
[dependencies.hbvm]
|
||||
path = ".."
|
||||
|
||||
[dependencies.hbbytecode]
|
||||
path = "../../hbbytecode"
|
||||
|
||||
# Prevent this from interfering with workspaces
|
||||
[workspace]
|
||||
members = ["."]
|
|
@ -16,20 +16,25 @@ struct AlignedBuf([MaybeUninit<u8>; BUF_SIZE]);
|
|||
/// State for block memory copy
|
||||
pub struct BlockCopier {
|
||||
/// Source address
|
||||
src: Address,
|
||||
src: Address,
|
||||
/// Destination address
|
||||
dst: Address,
|
||||
dst: Address,
|
||||
/// How many buffer sizes to copy?
|
||||
n_buffers: usize,
|
||||
/// …and what remainds after?
|
||||
rem: usize,
|
||||
rem: usize,
|
||||
}
|
||||
|
||||
impl BlockCopier {
|
||||
/// Construct a new one
|
||||
#[inline]
|
||||
pub fn new(src: Address, dst: Address, count: usize) -> Self {
|
||||
Self { src, dst, n_buffers: count / BUF_SIZE, rem: count % BUF_SIZE }
|
||||
Self {
|
||||
src,
|
||||
dst,
|
||||
n_buffers: count / BUF_SIZE,
|
||||
rem: count % BUF_SIZE,
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy one block
|
||||
|
@ -42,9 +47,15 @@ impl BlockCopier {
|
|||
|
||||
// We have at least one buffer size to copy
|
||||
if self.n_buffers != 0 {
|
||||
if let Err(e) =
|
||||
unsafe { act(memory, self.src, self.dst, buf.0.as_mut_ptr().cast(), BUF_SIZE) }
|
||||
{
|
||||
if let Err(e) = unsafe {
|
||||
act(
|
||||
memory,
|
||||
self.src,
|
||||
self.dst,
|
||||
buf.0.as_mut_ptr().cast(),
|
||||
BUF_SIZE,
|
||||
)
|
||||
} {
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
|
||||
|
@ -64,9 +75,15 @@ impl BlockCopier {
|
|||
}
|
||||
|
||||
if self.rem != 0 {
|
||||
if let Err(e) =
|
||||
unsafe { act(memory, self.src, self.dst, buf.0.as_mut_ptr().cast(), self.rem) }
|
||||
{
|
||||
if let Err(e) = unsafe {
|
||||
act(
|
||||
memory,
|
||||
self.src,
|
||||
self.dst,
|
||||
buf.0.as_mut_ptr().cast(),
|
||||
self.rem,
|
||||
)
|
||||
} {
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
}
|
||||
|
@ -86,16 +103,20 @@ unsafe fn act(
|
|||
) -> Result<(), BlkCopyError> {
|
||||
unsafe {
|
||||
// Load to buffer
|
||||
memory.load(src, buf, count).map_err(|super::mem::LoadError(addr)| BlkCopyError {
|
||||
access_reason: MemoryAccessReason::Load,
|
||||
addr,
|
||||
})?;
|
||||
memory
|
||||
.load(src, buf, count)
|
||||
.map_err(|super::mem::LoadError(addr)| BlkCopyError {
|
||||
access_reason: MemoryAccessReason::Load,
|
||||
addr,
|
||||
})?;
|
||||
|
||||
// Store from buffer
|
||||
memory.store(dst, buf, count).map_err(|super::mem::StoreError(addr)| BlkCopyError {
|
||||
access_reason: MemoryAccessReason::Store,
|
||||
addr,
|
||||
})?;
|
||||
memory
|
||||
.store(dst, buf, count)
|
||||
.map_err(|super::mem::StoreError(addr)| BlkCopyError {
|
||||
access_reason: MemoryAccessReason::Store,
|
||||
addr,
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(())
|
|
@ -9,8 +9,11 @@ macro_rules! fnsdef {
|
|||
$vis fn $name(val: $from, mode: RoundingMode) -> $to {
|
||||
let result: $to;
|
||||
unsafe {
|
||||
let fpcr: u64;
|
||||
if mode == RoundingMode::NearestEven {
|
||||
return;
|
||||
}
|
||||
|
||||
let fpcr: u64;
|
||||
unsafe { asm!("mrs {}, fpcr", out(reg) fpcr) };
|
||||
|
||||
let fpcr_new = fpcr & !(0b11 << 22)
|
|
@ -3,23 +3,23 @@ macro_rules! arch_specific {
|
|||
$({$($cfg:tt)*} : $mod:ident;)*
|
||||
} => {
|
||||
$(
|
||||
#[cfg($($cfg)*)]
|
||||
#[cfg(all(not(feature = "soft-float"), $($cfg)*))]
|
||||
mod $mod;
|
||||
|
||||
#[cfg($($cfg)*)]
|
||||
#[cfg(all(not(feature = "soft-float"), $($cfg)*))]
|
||||
pub use $mod::*;
|
||||
|
||||
#[cfg($($cfg)*)]
|
||||
#[cfg(all(not(feature = "soft-float"), $($cfg)*))]
|
||||
pub const FL_ARCH_SPECIFIC_SUPPORTED: bool = true;
|
||||
)*
|
||||
|
||||
#[cfg(not(any($($($cfg)*),*)))]
|
||||
#[cfg(any(feature = "soft-float", not(any($($($cfg)*),*))))]
|
||||
mod unsupported;
|
||||
|
||||
#[cfg(not(any($($($cfg)*),*)))]
|
||||
#[cfg(any(feature = "soft-float", not(any($($($cfg)*),*))))]
|
||||
pub use unsupported::*;
|
||||
|
||||
#[cfg(not(any($($($cfg)*),*)))]
|
||||
#[cfg(any(feature = "soft-float", not(any($($($cfg)*),*))))]
|
||||
pub const FL_ARCH_SPECIFIC_SUPPORTED: bool = false;
|
||||
};
|
||||
}
|
|
@ -26,6 +26,7 @@ mod utils;
|
|||
mod vmrun;
|
||||
|
||||
pub use float::FL_ARCH_SPECIFIC_SUPPORTED;
|
||||
|
||||
use {
|
||||
bmc::BlockCopier,
|
||||
mem::{Address, Memory},
|
||||
|
@ -36,7 +37,7 @@ use {
|
|||
pub struct Vm<Mem, const TIMER_QUOTIENT: usize> {
|
||||
/// Holds 256 registers
|
||||
///
|
||||
/// Writing to register 0 is considered idk behaviour
|
||||
/// Writing to register 0 is considered undefined behaviour
|
||||
/// in terms of HoleyBytes program execution
|
||||
pub registers: [Value; 256],
|
||||
|
||||
|
@ -53,18 +54,6 @@ pub struct Vm<Mem, const TIMER_QUOTIENT: usize> {
|
|||
copier: Option<BlockCopier>,
|
||||
}
|
||||
|
||||
impl<Mem: Default, const TIMER_QUOTIENT: usize> Default for Vm<Mem, TIMER_QUOTIENT> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
registers: [Value::from(0_u64); 256],
|
||||
memory: Mem::default(),
|
||||
pc: Address::default(),
|
||||
timer: 0,
|
||||
copier: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Mem, const TIMER_QUOTIENT: usize> Vm<Mem, TIMER_QUOTIENT>
|
||||
where
|
||||
Mem: Memory,
|
||||
|
@ -74,7 +63,13 @@ where
|
|||
/// # Safety
|
||||
/// Program code has to be validated
|
||||
pub unsafe fn new(memory: Mem, entry: Address) -> Self {
|
||||
Self { registers: [Value::from(0_u64); 256], memory, pc: entry, timer: 0, copier: None }
|
||||
Self {
|
||||
registers: [Value::from(0_u64); 256],
|
||||
memory,
|
||||
pc: entry,
|
||||
timer: 0,
|
||||
copier: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read register
|
||||
|
@ -126,33 +121,11 @@ pub enum VmRunError {
|
|||
|
||||
/// Invalid operand
|
||||
InvalidOperand,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for VmRunError {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
VmRunError::InvalidOpcode(op) => {
|
||||
f.write_str("invalid op code: ").and_then(|_| op.fmt(f))
|
||||
}
|
||||
VmRunError::LoadAccessEx(address) => {
|
||||
f.write_str("falied to load at ").and_then(|_| address.fmt(f))
|
||||
}
|
||||
VmRunError::ProgramFetchLoadEx(address) => {
|
||||
f.write_str("falied to load instruction at ").and_then(|_| address.fmt(f))
|
||||
}
|
||||
VmRunError::StoreAccessEx(address) => {
|
||||
f.write_str("falied to store at ").and_then(|_| address.fmt(f))
|
||||
}
|
||||
VmRunError::RegOutOfBounds => f.write_str("reg out of bounds"),
|
||||
VmRunError::AddrOutOfBounds => f.write_str("addr out-of-bounds"),
|
||||
VmRunError::Unreachable => f.write_str("unreachable"),
|
||||
VmRunError::InvalidOperand => f.write_str("invalud operand"),
|
||||
}
|
||||
}
|
||||
/// Unimplemented feature
|
||||
Unimplemented,
|
||||
}
|
||||
|
||||
impl core::error::Error for VmRunError {}
|
||||
|
||||
/// Virtual machine halt ok
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum VmRunOk {
|
|
@ -6,7 +6,7 @@ use {
|
|||
};
|
||||
|
||||
/// Memory address
|
||||
#[derive(Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Address(u64);
|
||||
impl Address {
|
||||
/// A null address
|
||||
|
@ -47,12 +47,6 @@ impl Address {
|
|||
self.0
|
||||
}
|
||||
|
||||
/// Get ptr to the next instruction
|
||||
#[inline(always)]
|
||||
pub fn next<A>(self) -> u64 {
|
||||
self.0.wrapping_add(core::mem::size_of::<A>() as u64 + 1)
|
||||
}
|
||||
|
||||
/// Construct new address
|
||||
#[inline(always)]
|
||||
pub fn new(val: u64) -> Self {
|
|
@ -4,9 +4,10 @@ pub mod softpaging;
|
|||
|
||||
pub(crate) mod addr;
|
||||
|
||||
use crate::utils::impl_display;
|
||||
pub use addr::Address;
|
||||
|
||||
use crate::utils::impl_display;
|
||||
|
||||
/// Load-store memory access
|
||||
pub trait Memory {
|
||||
/// Load data from memory on address
|
||||
|
@ -35,7 +36,7 @@ pub trait Memory {
|
|||
///
|
||||
/// # Safety
|
||||
/// - Data read have to be valid
|
||||
unsafe fn prog_read<T: Copy + 'static>(&mut self, addr: Address) -> T;
|
||||
unsafe fn prog_read<T: Copy>(&mut self, addr: Address) -> T;
|
||||
}
|
||||
|
||||
/// Unhandled load access trap
|
||||
|
@ -77,37 +78,3 @@ impl From<StoreError> for crate::VmRunError {
|
|||
Self::StoreAccessEx(value.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct HostMemory;
|
||||
impl Memory for HostMemory {
|
||||
#[inline]
|
||||
unsafe fn load(
|
||||
&mut self,
|
||||
addr: Address,
|
||||
target: *mut u8,
|
||||
count: usize,
|
||||
) -> Result<(), LoadError> {
|
||||
unsafe { core::ptr::copy(addr.get() as *const u8, target, count) }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn store(
|
||||
&mut self,
|
||||
addr: Address,
|
||||
source: *const u8,
|
||||
count: usize,
|
||||
) -> Result<(), StoreError> {
|
||||
debug_assert!(addr.get() != 0);
|
||||
debug_assert!(!source.is_null());
|
||||
unsafe { core::ptr::copy(source, addr.get() as *mut u8, count) }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn prog_read<T: Copy>(&mut self, addr: Address) -> T {
|
||||
debug_assert!(addr.get() != 0);
|
||||
unsafe { core::ptr::read(addr.get() as *const T) }
|
||||
}
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
//! Program instruction cache
|
||||
|
||||
use crate::mem::Address;
|
||||
|
||||
use {
|
||||
super::{lookup::AddrPageLookuper, paging::PageTable, PageSize},
|
||||
crate::mem::Address,
|
||||
core::{
|
||||
mem::{size_of, MaybeUninit},
|
||||
ptr::{copy_nonoverlapping, NonNull},
|
||||
|
@ -45,8 +46,9 @@ impl ICache {
|
|||
) -> Option<T> {
|
||||
let mut ret = MaybeUninit::<T>::uninit();
|
||||
|
||||
let pbase =
|
||||
self.data.or_else(|| unsafe { self.fetch_page(self.base + self.size, root_pt) })?;
|
||||
let pbase = self
|
||||
.data
|
||||
.or_else(|| unsafe { self.fetch_page(self.base + self.size, root_pt) })?;
|
||||
|
||||
// Get address base
|
||||
let base = addr.map(|x| x & self.mask);
|
||||
|
@ -60,7 +62,9 @@ impl ICache {
|
|||
let requ_size = size_of::<T>();
|
||||
|
||||
// Page overflow
|
||||
let rem = (offset as usize).saturating_add(requ_size).saturating_sub(self.size as _);
|
||||
let rem = (offset as usize)
|
||||
.saturating_add(requ_size)
|
||||
.saturating_sub(self.size as _);
|
||||
let first_copy = requ_size.saturating_sub(rem);
|
||||
|
||||
// Copy non-overflowing part
|
|
@ -1,12 +1,11 @@
|
|||
//! Address lookup
|
||||
|
||||
use {
|
||||
super::{
|
||||
addr_extract_index,
|
||||
paging::{PageTable, Permission},
|
||||
PageSize,
|
||||
},
|
||||
crate::mem::addr::Address,
|
||||
use crate::mem::addr::Address;
|
||||
|
||||
use super::{
|
||||
addr_extract_index,
|
||||
paging::{PageTable, Permission},
|
||||
PageSize,
|
||||
};
|
||||
|
||||
/// Good result from address split
|
||||
|
@ -49,7 +48,11 @@ impl AddrPageLookuper {
|
|||
/// Create a new page lookuper
|
||||
#[inline]
|
||||
pub const fn new(addr: Address, size: usize, pagetable: *const PageTable) -> Self {
|
||||
Self { addr, size, pagetable }
|
||||
Self {
|
||||
addr,
|
||||
size,
|
||||
pagetable,
|
||||
}
|
||||
}
|
||||
|
||||
/// Bump address by size X
|
||||
|
@ -75,8 +78,9 @@ impl Iterator for AddrPageLookuper {
|
|||
for lvl in (0..5).rev() {
|
||||
// Get an entry
|
||||
unsafe {
|
||||
let entry =
|
||||
(*current_pt).table.get_unchecked(addr_extract_index(self.addr, lvl));
|
||||
let entry = (*current_pt)
|
||||
.table
|
||||
.get_unchecked(addr_extract_index(self.addr, lvl));
|
||||
|
||||
let ptr = entry.ptr();
|
||||
match entry.permission() {
|
|
@ -1,16 +1,17 @@
|
|||
//! Automatic memory mapping
|
||||
|
||||
use crate::{mem::addr::Address, utils::impl_display};
|
||||
|
||||
use {
|
||||
super::{
|
||||
addr_extract_index,
|
||||
paging::{PageTable, Permission, PtEntry, PtPointedData},
|
||||
PageSize, SoftPagedMem,
|
||||
},
|
||||
crate::{mem::addr::Address, utils::impl_display},
|
||||
alloc::boxed::Box,
|
||||
};
|
||||
|
||||
impl<A, const OUT_PROG_EXEC: bool> SoftPagedMem<'_, A, OUT_PROG_EXEC> {
|
||||
impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> {
|
||||
/// Maps host's memory into VM's memory
|
||||
///
|
||||
/// # Safety
|
||||
|
@ -35,8 +36,11 @@ impl<A, const OUT_PROG_EXEC: bool> SoftPagedMem<'_, A, OUT_PROG_EXEC> {
|
|||
|
||||
// Walk pagetable levels
|
||||
for lvl in (lookup_depth + 1..5).rev() {
|
||||
let entry =
|
||||
unsafe { (*current_pt).table.get_unchecked_mut(addr_extract_index(target, lvl)) };
|
||||
let entry = unsafe {
|
||||
(*current_pt)
|
||||
.table
|
||||
.get_unchecked_mut(addr_extract_index(target, lvl))
|
||||
};
|
||||
|
||||
let ptr = entry.ptr();
|
||||
match entry.permission() {
|
||||
|
@ -46,7 +50,9 @@ impl<A, const OUT_PROG_EXEC: bool> SoftPagedMem<'_, A, OUT_PROG_EXEC> {
|
|||
// Increase children count
|
||||
unsafe { *current_pt }.childen += 1;
|
||||
|
||||
let table = Box::into_raw(Box::new(PtPointedData { pt: PageTable::default() }));
|
||||
let table = Box::into_raw(Box::new(PtPointedData {
|
||||
pt: PageTable::default(),
|
||||
}));
|
||||
|
||||
unsafe { core::ptr::write(entry, PtEntry::new(table, Permission::Node)) };
|
||||
current_pt = table as _;
|
||||
|
@ -60,7 +66,9 @@ impl<A, const OUT_PROG_EXEC: bool> SoftPagedMem<'_, A, OUT_PROG_EXEC> {
|
|||
}
|
||||
|
||||
let node = unsafe {
|
||||
(*current_pt).table.get_unchecked_mut(addr_extract_index(target, lookup_depth))
|
||||
(*current_pt)
|
||||
.table
|
||||
.get_unchecked_mut(addr_extract_index(target, lookup_depth))
|
||||
};
|
||||
|
||||
// Check if node is not mapped
|
||||
|
@ -87,8 +95,11 @@ impl<A, const OUT_PROG_EXEC: bool> SoftPagedMem<'_, A, OUT_PROG_EXEC> {
|
|||
|
||||
// Walk page table in reverse
|
||||
for lvl in (0..5).rev() {
|
||||
let entry =
|
||||
unsafe { (*current_pt).table.get_unchecked_mut(addr_extract_index(addr, lvl)) };
|
||||
let entry = unsafe {
|
||||
(*current_pt)
|
||||
.table
|
||||
.get_unchecked_mut(addr_extract_index(addr, lvl))
|
||||
};
|
||||
|
||||
let ptr = entry.ptr();
|
||||
match entry.permission() {
|
|
@ -23,17 +23,17 @@ use {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct SoftPagedMem<'p, PfH, const OUT_PROG_EXEC: bool = true> {
|
||||
/// Root page table
|
||||
pub root_pt: *mut PageTable,
|
||||
pub root_pt: *mut PageTable,
|
||||
/// Page fault handler
|
||||
pub pf_handler: PfH,
|
||||
/// Program memory segment
|
||||
pub program: &'p [u8],
|
||||
pub program: &'p [u8],
|
||||
/// Program instruction cache
|
||||
pub icache: ICache,
|
||||
pub icache: ICache,
|
||||
}
|
||||
|
||||
impl<PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory
|
||||
for SoftPagedMem<'_, PfH, OUT_PROG_EXEC>
|
||||
impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory
|
||||
for SoftPagedMem<'p, PfH, OUT_PROG_EXEC>
|
||||
{
|
||||
/// Load value from an address
|
||||
///
|
||||
|
@ -92,13 +92,14 @@ impl<PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory
|
|||
}
|
||||
}
|
||||
|
||||
impl<PfH: HandlePageFault, const OUT_PROG_EXEC: bool> SoftPagedMem<'_, PfH, OUT_PROG_EXEC> {
|
||||
impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, PfH, OUT_PROG_EXEC> {
|
||||
// Everyone behold, the holy function, the god of HBVM memory accesses!
|
||||
|
||||
/// Split address to pages, check their permissions and feed pointers with offset
|
||||
/// to a specified function.
|
||||
///
|
||||
/// If page is not found, execute page fault trap handler.
|
||||
#[allow(clippy::too_many_arguments)] // Silence peasant
|
||||
fn memory_access(
|
||||
&mut self,
|
||||
reason: MemoryAccessReason,
|
||||
|
@ -126,7 +127,10 @@ impl<PfH: HandlePageFault, const OUT_PROG_EXEC: bool> SoftPagedMem<'_, PfH, OUT_
|
|||
);
|
||||
|
||||
// Return shifted from what we've already copied
|
||||
(src.saturating_add(to_copy as u64), len.saturating_sub(to_copy))
|
||||
(
|
||||
src.saturating_add(to_copy as u64),
|
||||
len.saturating_sub(to_copy),
|
||||
)
|
||||
} else {
|
||||
(src, len) // Nothing weird!
|
||||
};
|
||||
|
@ -141,7 +145,12 @@ impl<PfH: HandlePageFault, const OUT_PROG_EXEC: bool> SoftPagedMem<'_, PfH, OUT_
|
|||
loop {
|
||||
match pspl.next() {
|
||||
// Page is found
|
||||
Some(Ok(AddrPageLookupOk { vaddr, ptr, size, perm })) => {
|
||||
Some(Ok(AddrPageLookupOk {
|
||||
vaddr,
|
||||
ptr,
|
||||
size,
|
||||
perm,
|
||||
})) => {
|
||||
if !permission_check(perm) {
|
||||
return Err(vaddr);
|
||||
}
|
||||
|
@ -234,7 +243,10 @@ pub mod perm_check {
|
|||
/// Page is readable
|
||||
#[inline(always)]
|
||||
pub const fn readable(perm: Permission) -> bool {
|
||||
matches!(perm, Permission::Readonly | Permission::Write | Permission::Exec)
|
||||
matches!(
|
||||
perm,
|
||||
Permission::Readonly | Permission::Write | Permission::Exec
|
||||
)
|
||||
}
|
||||
|
||||
/// Page is writable
|
|
@ -62,13 +62,16 @@ pub struct PageTable {
|
|||
/// How much entries are in use
|
||||
pub childen: u8,
|
||||
/// Entries
|
||||
pub table: [PtEntry; 256],
|
||||
pub table: [PtEntry; 256],
|
||||
}
|
||||
|
||||
impl Default for PageTable {
|
||||
fn default() -> Self {
|
||||
// SAFETY: It's fine, zeroed page table entry is valid (= empty)
|
||||
Self { childen: 0, table: unsafe { MaybeUninit::zeroed().assume_init() } }
|
||||
Self {
|
||||
childen: 0,
|
||||
table: unsafe { MaybeUninit::zeroed().assume_init() },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,7 +80,7 @@ impl Default for PageTable {
|
|||
#[repr(C, align(4096))]
|
||||
pub union PtPointedData {
|
||||
/// Node - next page table
|
||||
pub pt: PageTable,
|
||||
pub pt: PageTable,
|
||||
/// Leaf
|
||||
pub page: u8,
|
||||
}
|
|
@ -6,7 +6,7 @@ use crate::utils::static_assert;
|
|||
///
|
||||
/// # Safety
|
||||
/// Its variants have to be sound to byte-reinterpretate
|
||||
/// between each other. Otherwise the behaviour is idk.
|
||||
/// between each other. Otherwise the behaviour is undefined.
|
||||
macro_rules! value_def {
|
||||
($($ty:ident),* $(,)?) => {
|
||||
/// HBVM register value
|
|
@ -17,8 +17,9 @@ use {
|
|||
|
||||
macro_rules! handler {
|
||||
($self:expr, |$ty:ident ($($ident:pat),* $(,)?)| $expr:expr) => {{
|
||||
let $ty($($ident),*) = $self.decode::<$ty>();
|
||||
let e = $expr;
|
||||
#[allow(unused_unsafe)]
|
||||
let $ty($($ident),*) = unsafe { $self.decode::<$ty>() };
|
||||
#[allow(clippy::no_effect)] let e = $expr;
|
||||
$self.bump_pc::<$ty>();
|
||||
e
|
||||
}};
|
||||
|
@ -33,7 +34,7 @@ where
|
|||
/// Program can return [`VmRunError`] if a trap handling failed
|
||||
#[cfg_attr(feature = "nightly", repr(align(4096)))]
|
||||
pub fn run(&mut self) -> Result<VmRunOk, VmRunError> {
|
||||
use hbbytecode::Instr as I;
|
||||
use hbbytecode::opcode::*;
|
||||
loop {
|
||||
// Big match
|
||||
//
|
||||
|
@ -55,112 +56,105 @@ where
|
|||
// - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU
|
||||
// sorry 8 bit fans, HBVM won't run on your Speccy :(
|
||||
unsafe {
|
||||
match self
|
||||
.memory
|
||||
.prog_read::<u8>(self.pc as _)
|
||||
.try_into()
|
||||
.map_err(VmRunError::InvalidOpcode)?
|
||||
{
|
||||
I::UN => {
|
||||
match self.memory.prog_read::<u8>(self.pc as _) {
|
||||
UN => {
|
||||
self.bump_pc::<OpsN>();
|
||||
return Err(VmRunError::Unreachable);
|
||||
}
|
||||
I::TX => {
|
||||
TX => {
|
||||
self.bump_pc::<OpsN>();
|
||||
return Ok(VmRunOk::End);
|
||||
}
|
||||
I::NOP => handler!(self, |OpsN()| ()),
|
||||
I::ADD8 => self.binary_op(u8::wrapping_add),
|
||||
I::ADD16 => self.binary_op(u16::wrapping_add),
|
||||
I::ADD32 => self.binary_op(u32::wrapping_add),
|
||||
I::ADD64 => self.binary_op(u64::wrapping_add),
|
||||
I::SUB8 => self.binary_op(u8::wrapping_sub),
|
||||
I::SUB16 => self.binary_op(u16::wrapping_sub),
|
||||
I::SUB32 => self.binary_op(u32::wrapping_sub),
|
||||
I::SUB64 => self.binary_op(u64::wrapping_sub),
|
||||
I::MUL8 => self.binary_op(u8::wrapping_mul),
|
||||
I::MUL16 => self.binary_op(u16::wrapping_mul),
|
||||
I::MUL32 => self.binary_op(u32::wrapping_mul),
|
||||
I::MUL64 => self.binary_op(u64::wrapping_mul),
|
||||
I::AND => self.binary_op::<u64>(ops::BitAnd::bitand),
|
||||
I::OR => self.binary_op::<u64>(ops::BitOr::bitor),
|
||||
I::XOR => self.binary_op::<u64>(ops::BitXor::bitxor),
|
||||
I::SLU8 => self.binary_op_shift::<u8>(u8::wrapping_shl),
|
||||
I::SLU16 => self.binary_op_shift::<u16>(u16::wrapping_shl),
|
||||
I::SLU32 => self.binary_op_shift::<u32>(u32::wrapping_shl),
|
||||
I::SLU64 => self.binary_op_shift::<u64>(u64::wrapping_shl),
|
||||
I::SRU8 => self.binary_op_shift::<u8>(u8::wrapping_shr),
|
||||
I::SRU16 => self.binary_op_shift::<u16>(u16::wrapping_shr),
|
||||
I::SRU32 => self.binary_op_shift::<u32>(u32::wrapping_shr),
|
||||
I::SRU64 => self.binary_op_shift::<u64>(u64::wrapping_shr),
|
||||
I::SRS8 => self.binary_op_shift::<i8>(i8::wrapping_shr),
|
||||
I::SRS16 => self.binary_op_shift::<i16>(i16::wrapping_shr),
|
||||
I::SRS32 => self.binary_op_shift::<i32>(i32::wrapping_shr),
|
||||
I::SRS64 => self.binary_op_shift::<i64>(i64::wrapping_shr),
|
||||
I::CMPU => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp(
|
||||
NOP => handler!(self, |OpsN()| ()),
|
||||
ADD8 => self.binary_op(u8::wrapping_add),
|
||||
ADD16 => self.binary_op(u16::wrapping_add),
|
||||
ADD32 => self.binary_op(u32::wrapping_add),
|
||||
ADD64 => self.binary_op(u64::wrapping_add),
|
||||
SUB8 => self.binary_op(u8::wrapping_sub),
|
||||
SUB16 => self.binary_op(u16::wrapping_sub),
|
||||
SUB32 => self.binary_op(u32::wrapping_sub),
|
||||
SUB64 => self.binary_op(u64::wrapping_sub),
|
||||
MUL8 => self.binary_op(u8::wrapping_mul),
|
||||
MUL16 => self.binary_op(u16::wrapping_mul),
|
||||
MUL32 => self.binary_op(u32::wrapping_mul),
|
||||
MUL64 => self.binary_op(u64::wrapping_mul),
|
||||
AND => self.binary_op::<u64>(ops::BitAnd::bitand),
|
||||
OR => self.binary_op::<u64>(ops::BitOr::bitor),
|
||||
XOR => self.binary_op::<u64>(ops::BitXor::bitxor),
|
||||
SLU8 => self.binary_op_shift::<u8>(u8::wrapping_shl),
|
||||
SLU16 => self.binary_op_shift::<u16>(u16::wrapping_shl),
|
||||
SLU32 => self.binary_op_shift::<u32>(u32::wrapping_shl),
|
||||
SLU64 => self.binary_op_shift::<u64>(u64::wrapping_shl),
|
||||
SRU8 => self.binary_op_shift::<u8>(u8::wrapping_shr),
|
||||
SRU16 => self.binary_op_shift::<u16>(u16::wrapping_shr),
|
||||
SRU32 => self.binary_op_shift::<u32>(u32::wrapping_shr),
|
||||
SRU64 => self.binary_op_shift::<u64>(u64::wrapping_shr),
|
||||
SRS8 => self.binary_op_shift::<i8>(i8::wrapping_shr),
|
||||
SRS16 => self.binary_op_shift::<i16>(i16::wrapping_shr),
|
||||
SRS32 => self.binary_op_shift::<i32>(i32::wrapping_shr),
|
||||
SRS64 => self.binary_op_shift::<i64>(i64::wrapping_shr),
|
||||
CMPU => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp(
|
||||
tg,
|
||||
a0,
|
||||
self.read_reg(a1).cast::<u64>()
|
||||
)),
|
||||
I::CMPS => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp(
|
||||
CMPS => handler!(self, |OpsRRR(tg, a0, a1)| self.cmp(
|
||||
tg,
|
||||
a0,
|
||||
self.read_reg(a1).cast::<i64>()
|
||||
)),
|
||||
I::DIRU8 => self.dir::<u8>(),
|
||||
I::DIRU16 => self.dir::<u16>(),
|
||||
I::DIRU32 => self.dir::<u32>(),
|
||||
I::DIRU64 => self.dir::<u64>(),
|
||||
I::DIRS8 => self.dir::<i8>(),
|
||||
I::DIRS16 => self.dir::<i16>(),
|
||||
I::DIRS32 => self.dir::<i32>(),
|
||||
I::DIRS64 => self.dir::<i64>(),
|
||||
I::NEG => handler!(self, |OpsRR(tg, a0)| {
|
||||
DIRU8 => self.dir::<u8>(),
|
||||
DIRU16 => self.dir::<u16>(),
|
||||
DIRU32 => self.dir::<u32>(),
|
||||
DIRU64 => self.dir::<u64>(),
|
||||
DIRS8 => self.dir::<i8>(),
|
||||
DIRS16 => self.dir::<i16>(),
|
||||
DIRS32 => self.dir::<i32>(),
|
||||
DIRS64 => self.dir::<i64>(),
|
||||
NEG => handler!(self, |OpsRR(tg, a0)| {
|
||||
// Bit negation
|
||||
self.write_reg(tg, self.read_reg(a0).cast::<u64>().wrapping_neg())
|
||||
self.write_reg(tg, !self.read_reg(a0).cast::<u64>())
|
||||
}),
|
||||
I::NOT => handler!(self, |OpsRR(tg, a0)| {
|
||||
NOT => handler!(self, |OpsRR(tg, a0)| {
|
||||
// Logical negation
|
||||
self.write_reg(tg, u64::from(self.read_reg(a0).cast::<u64>() == 0));
|
||||
}),
|
||||
I::SXT8 => handler!(self, |OpsRR(tg, a0)| {
|
||||
SXT8 => handler!(self, |OpsRR(tg, a0)| {
|
||||
self.write_reg(tg, self.read_reg(a0).cast::<i8>() as i64)
|
||||
}),
|
||||
I::SXT16 => handler!(self, |OpsRR(tg, a0)| {
|
||||
SXT16 => handler!(self, |OpsRR(tg, a0)| {
|
||||
self.write_reg(tg, self.read_reg(a0).cast::<i16>() as i64)
|
||||
}),
|
||||
I::SXT32 => handler!(self, |OpsRR(tg, a0)| {
|
||||
SXT32 => handler!(self, |OpsRR(tg, a0)| {
|
||||
self.write_reg(tg, self.read_reg(a0).cast::<i32>() as i64)
|
||||
}),
|
||||
I::ADDI8 => self.binary_op_imm(u8::wrapping_add),
|
||||
I::ADDI16 => self.binary_op_imm(u16::wrapping_add),
|
||||
I::ADDI32 => self.binary_op_imm(u32::wrapping_add),
|
||||
I::ADDI64 => self.binary_op_imm(u64::wrapping_add),
|
||||
I::MULI8 => self.binary_op_imm(u8::wrapping_mul),
|
||||
I::MULI16 => self.binary_op_imm(u16::wrapping_mul),
|
||||
I::MULI32 => self.binary_op_imm(u32::wrapping_mul),
|
||||
I::MULI64 => self.binary_op_imm(u64::wrapping_mul),
|
||||
I::ANDI => self.binary_op_imm::<u64>(ops::BitAnd::bitand),
|
||||
I::ORI => self.binary_op_imm::<u64>(ops::BitOr::bitor),
|
||||
I::XORI => self.binary_op_imm::<u64>(ops::BitXor::bitxor),
|
||||
I::SLUI8 => self.binary_op_ims::<u8>(u8::wrapping_shl),
|
||||
I::SLUI16 => self.binary_op_ims::<u16>(u16::wrapping_shl),
|
||||
I::SLUI32 => self.binary_op_ims::<u32>(u32::wrapping_shl),
|
||||
I::SLUI64 => self.binary_op_ims::<u64>(u64::wrapping_shl),
|
||||
I::SRUI8 => self.binary_op_ims::<u8>(u8::wrapping_shr),
|
||||
I::SRUI16 => self.binary_op_ims::<u16>(u16::wrapping_shr),
|
||||
I::SRUI32 => self.binary_op_ims::<u32>(u32::wrapping_shr),
|
||||
I::SRUI64 => self.binary_op_ims::<u64>(u64::wrapping_shr),
|
||||
I::SRSI8 => self.binary_op_ims::<i8>(i8::wrapping_shr),
|
||||
I::SRSI16 => self.binary_op_ims::<i16>(i16::wrapping_shr),
|
||||
I::SRSI32 => self.binary_op_ims::<i32>(i32::wrapping_shr),
|
||||
I::SRSI64 => self.binary_op_ims::<i64>(i64::wrapping_shr),
|
||||
I::CMPUI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm) }),
|
||||
I::CMPSI => {
|
||||
handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm as i64) })
|
||||
}
|
||||
I::CP => handler!(self, |OpsRR(tg, a0)| self.write_reg(tg, self.read_reg(a0))),
|
||||
I::SWA => handler!(self, |OpsRR(r0, r1)| {
|
||||
ADDI8 => self.binary_op_imm(u8::wrapping_add),
|
||||
ADDI16 => self.binary_op_imm(u16::wrapping_add),
|
||||
ADDI32 => self.binary_op_imm(u32::wrapping_add),
|
||||
ADDI64 => self.binary_op_imm(u64::wrapping_add),
|
||||
MULI8 => self.binary_op_imm(u8::wrapping_mul),
|
||||
MULI16 => self.binary_op_imm(u16::wrapping_mul),
|
||||
MULI32 => self.binary_op_imm(u32::wrapping_mul),
|
||||
MULI64 => self.binary_op_imm(u64::wrapping_mul),
|
||||
ANDI => self.binary_op_imm::<u64>(ops::BitAnd::bitand),
|
||||
ORI => self.binary_op_imm::<u64>(ops::BitOr::bitor),
|
||||
XORI => self.binary_op_imm::<u64>(ops::BitXor::bitxor),
|
||||
SLUI8 => self.binary_op_ims::<u8>(u8::wrapping_shl),
|
||||
SLUI16 => self.binary_op_ims::<u16>(u16::wrapping_shl),
|
||||
SLUI32 => self.binary_op_ims::<u32>(u32::wrapping_shl),
|
||||
SLUI64 => self.binary_op_ims::<u64>(u64::wrapping_shl),
|
||||
SRUI8 => self.binary_op_ims::<u8>(u8::wrapping_shr),
|
||||
SRUI16 => self.binary_op_ims::<u16>(u16::wrapping_shr),
|
||||
SRUI32 => self.binary_op_ims::<u32>(u32::wrapping_shr),
|
||||
SRUI64 => self.binary_op_ims::<u64>(u64::wrapping_shr),
|
||||
SRSI8 => self.binary_op_ims::<i8>(i8::wrapping_shr),
|
||||
SRSI16 => self.binary_op_ims::<i16>(i16::wrapping_shr),
|
||||
SRSI32 => self.binary_op_ims::<i32>(i32::wrapping_shr),
|
||||
SRSI64 => self.binary_op_ims::<i64>(i64::wrapping_shr),
|
||||
CMPUI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm) }),
|
||||
CMPSI => handler!(self, |OpsRRD(tg, a0, imm)| { self.cmp(tg, a0, imm as i64) }),
|
||||
CP => handler!(self, |OpsRR(tg, a0)| self.write_reg(tg, self.read_reg(a0))),
|
||||
SWA => handler!(self, |OpsRR(r0, r1)| {
|
||||
// Swap registers
|
||||
match (r0, r1) {
|
||||
(0, 0) => (),
|
||||
|
@ -173,33 +167,35 @@ where
|
|||
}
|
||||
}
|
||||
}),
|
||||
I::LI8 => handler!(self, |OpsRB(tg, imm)| self.write_reg(tg, imm)),
|
||||
I::LI16 => handler!(self, |OpsRH(tg, imm)| self.write_reg(tg, imm)),
|
||||
I::LI32 => handler!(self, |OpsRW(tg, imm)| self.write_reg(tg, imm)),
|
||||
I::LI64 => handler!(self, |OpsRD(tg, imm)| self.write_reg(tg, imm)),
|
||||
I::LRA => handler!(self, |OpsRRO(tg, reg, off)| self.write_reg(
|
||||
LI8 => handler!(self, |OpsRB(tg, imm)| self.write_reg(tg, imm)),
|
||||
LI16 => handler!(self, |OpsRH(tg, imm)| self.write_reg(tg, imm)),
|
||||
LI32 => handler!(self, |OpsRW(tg, imm)| self.write_reg(tg, imm)),
|
||||
LI64 => handler!(self, |OpsRD(tg, imm)| self.write_reg(tg, imm)),
|
||||
LRA => handler!(self, |OpsRRO(tg, reg, off)| self.write_reg(
|
||||
tg,
|
||||
self.pcrel(off).wrapping_add(self.read_reg(reg).cast::<i64>()).get(),
|
||||
self.pcrel(off, 3)
|
||||
.wrapping_add(self.read_reg(reg).cast::<i64>())
|
||||
.get(),
|
||||
)),
|
||||
// Load. If loading more than register size, continue on adjecent registers
|
||||
I::LD => handler!(self, |OpsRRAH(dst, base, off, count)| self
|
||||
LD => handler!(self, |OpsRRAH(dst, base, off, count)| self
|
||||
.load(dst, base, off, count)?),
|
||||
// Store. Same rules apply as to LD
|
||||
I::ST => handler!(self, |OpsRRAH(dst, base, off, count)| self
|
||||
ST => handler!(self, |OpsRRAH(dst, base, off, count)| self
|
||||
.store(dst, base, off, count)?),
|
||||
I::LDR => handler!(self, |OpsRROH(dst, base, off, count)| self.load(
|
||||
LDR => handler!(self, |OpsRROH(dst, base, off, count)| self.load(
|
||||
dst,
|
||||
base,
|
||||
self.pcrel(off).get(),
|
||||
self.pcrel(off, 3).get(),
|
||||
count
|
||||
)?),
|
||||
I::STR => handler!(self, |OpsRROH(dst, base, off, count)| self.store(
|
||||
STR => handler!(self, |OpsRROH(dst, base, off, count)| self.store(
|
||||
dst,
|
||||
base,
|
||||
self.pcrel(off).get(),
|
||||
self.pcrel(off, 3).get(),
|
||||
count
|
||||
)?),
|
||||
I::BMC => {
|
||||
BMC => {
|
||||
// Block memory copy
|
||||
match if let Some(copier) = &mut self.copier {
|
||||
// There is some copier, poll.
|
||||
|
@ -233,7 +229,7 @@ where
|
|||
core::task::Poll::Pending => (),
|
||||
}
|
||||
}
|
||||
I::BRC => handler!(self, |OpsRRB(src, dst, count)| {
|
||||
BRC => handler!(self, |OpsRRB(src, dst, count)| {
|
||||
// Block register copy
|
||||
if src.checked_add(count).is_none() || dst.checked_add(count).is_none() {
|
||||
return Err(VmRunError::RegOutOfBounds);
|
||||
|
@ -245,42 +241,44 @@ where
|
|||
usize::from(count),
|
||||
);
|
||||
}),
|
||||
I::JMP => {
|
||||
JMP => {
|
||||
let OpsO(off) = self.decode();
|
||||
self.pc = self.pc.wrapping_add(off);
|
||||
self.pc = self.pc.wrapping_add(off).wrapping_add(1);
|
||||
}
|
||||
I::JAL => {
|
||||
JAL => {
|
||||
// Jump and link. Save PC after this instruction to
|
||||
// specified register and jump to reg + relative offset.
|
||||
let OpsRRO(save, reg, offset) = self.decode();
|
||||
|
||||
self.write_reg(save, self.pc.next::<OpsRRO>());
|
||||
self.pc = self.pcrel(offset).wrapping_add(self.read_reg(reg).cast::<i64>());
|
||||
self.write_reg(save, self.pc.get());
|
||||
self.pc = self
|
||||
.pcrel(offset, 3)
|
||||
.wrapping_add(self.read_reg(reg).cast::<i64>());
|
||||
}
|
||||
I::JALA => {
|
||||
JALA => {
|
||||
// Jump and link. Save PC after this instruction to
|
||||
// specified register and jump to reg
|
||||
let OpsRRA(save, reg, offset) = self.decode();
|
||||
|
||||
self.write_reg(save, self.pc.next::<OpsRRA>());
|
||||
self.write_reg(save, self.pc.get());
|
||||
self.pc =
|
||||
Address::new(self.read_reg(reg).cast::<u64>().wrapping_add(offset));
|
||||
}
|
||||
// Conditional jumps, jump only to immediates
|
||||
I::JEQ => self.cond_jmp::<u64>(Ordering::Equal),
|
||||
I::JNE => {
|
||||
JEQ => self.cond_jmp::<u64>(Ordering::Equal),
|
||||
JNE => {
|
||||
let OpsRRP(a0, a1, ja) = self.decode();
|
||||
if self.read_reg(a0).cast::<u64>() != self.read_reg(a1).cast::<u64>() {
|
||||
self.pc = self.pcrel(ja);
|
||||
self.pc = self.pcrel(ja, 3);
|
||||
} else {
|
||||
self.bump_pc::<OpsRRP>();
|
||||
}
|
||||
}
|
||||
I::JLTS => self.cond_jmp::<i64>(Ordering::Less),
|
||||
I::JGTS => self.cond_jmp::<i64>(Ordering::Greater),
|
||||
I::JLTU => self.cond_jmp::<u64>(Ordering::Less),
|
||||
I::JGTU => self.cond_jmp::<u64>(Ordering::Greater),
|
||||
I::ECA => {
|
||||
JLTS => self.cond_jmp::<u64>(Ordering::Less),
|
||||
JGTS => self.cond_jmp::<u64>(Ordering::Greater),
|
||||
JLTU => self.cond_jmp::<i64>(Ordering::Less),
|
||||
JGTU => self.cond_jmp::<i64>(Ordering::Greater),
|
||||
ECA => {
|
||||
// So we don't get timer interrupt after ECALL
|
||||
if TIMER_QUOTIENT != 0 {
|
||||
self.timer = self.timer.wrapping_add(1);
|
||||
|
@ -289,33 +287,33 @@ where
|
|||
self.bump_pc::<OpsN>();
|
||||
return Ok(VmRunOk::Ecall);
|
||||
}
|
||||
I::EBP => {
|
||||
EBP => {
|
||||
self.bump_pc::<OpsN>();
|
||||
return Ok(VmRunOk::Breakpoint);
|
||||
}
|
||||
I::FADD32 => self.binary_op::<f32>(ops::Add::add),
|
||||
I::FADD64 => self.binary_op::<f64>(ops::Add::add),
|
||||
I::FSUB32 => self.binary_op::<f32>(ops::Sub::sub),
|
||||
I::FSUB64 => self.binary_op::<f64>(ops::Sub::sub),
|
||||
I::FMUL32 => self.binary_op::<f32>(ops::Mul::mul),
|
||||
I::FMUL64 => self.binary_op::<f64>(ops::Mul::mul),
|
||||
I::FDIV32 => self.binary_op::<f32>(ops::Div::div),
|
||||
I::FDIV64 => self.binary_op::<f64>(ops::Div::div),
|
||||
I::FMA32 => self.fma::<f32>(),
|
||||
I::FMA64 => self.fma::<f64>(),
|
||||
I::FINV32 => handler!(self, |OpsRR(tg, reg)| self
|
||||
FADD32 => self.binary_op::<f32>(ops::Add::add),
|
||||
FADD64 => self.binary_op::<f64>(ops::Add::add),
|
||||
FSUB32 => self.binary_op::<f32>(ops::Sub::sub),
|
||||
FSUB64 => self.binary_op::<f64>(ops::Sub::sub),
|
||||
FMUL32 => self.binary_op::<f32>(ops::Mul::mul),
|
||||
FMUL64 => self.binary_op::<f64>(ops::Mul::mul),
|
||||
FDIV32 => self.binary_op::<f32>(ops::Div::div),
|
||||
FDIV64 => self.binary_op::<f64>(ops::Div::div),
|
||||
FMA32 => self.fma::<f32>(),
|
||||
FMA64 => self.fma::<f64>(),
|
||||
FINV32 => handler!(self, |OpsRR(tg, reg)| self
|
||||
.write_reg(tg, 1. / self.read_reg(reg).cast::<f32>())),
|
||||
I::FINV64 => handler!(self, |OpsRR(tg, reg)| self
|
||||
FINV64 => handler!(self, |OpsRR(tg, reg)| self
|
||||
.write_reg(tg, 1. / self.read_reg(reg).cast::<f64>())),
|
||||
I::FCMPLT32 => self.fcmp::<f32>(false, Ordering::Less),
|
||||
I::FCMPLT64 => self.fcmp::<f64>(false, Ordering::Less),
|
||||
I::FCMPGT32 => self.fcmp::<f32>(true, Ordering::Greater),
|
||||
I::FCMPGT64 => self.fcmp::<f64>(true, Ordering::Greater),
|
||||
I::ITF32 => handler!(self, |OpsRR(tg, reg)| self
|
||||
FCMPLT32 => self.fcmp::<f32>(Ordering::Less),
|
||||
FCMPLT64 => self.fcmp::<f64>(Ordering::Less),
|
||||
FCMPGT32 => self.fcmp::<f32>(Ordering::Greater),
|
||||
FCMPGT64 => self.fcmp::<f64>(Ordering::Greater),
|
||||
ITF32 => handler!(self, |OpsRR(tg, reg)| self
|
||||
.write_reg(tg, self.read_reg(reg).cast::<i64>() as f32)),
|
||||
I::ITF64 => handler!(self, |OpsRR(tg, reg)| self
|
||||
ITF64 => handler!(self, |OpsRR(tg, reg)| self
|
||||
.write_reg(tg, self.read_reg(reg).cast::<i64>() as f64)),
|
||||
I::FTI32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg(
|
||||
FTI32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg(
|
||||
tg,
|
||||
crate::float::f32toint(
|
||||
self.read_reg(reg).cast::<f32>(),
|
||||
|
@ -323,7 +321,7 @@ where
|
|||
.map_err(|()| VmRunError::InvalidOperand)?,
|
||||
),
|
||||
)),
|
||||
I::FTI64 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg(
|
||||
FTI64 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg(
|
||||
tg,
|
||||
crate::float::f64toint(
|
||||
self.read_reg(reg).cast::<f64>(),
|
||||
|
@ -331,9 +329,9 @@ where
|
|||
.map_err(|()| VmRunError::InvalidOperand)?,
|
||||
),
|
||||
)),
|
||||
I::FC32T64 => handler!(self, |OpsRR(tg, reg)| self
|
||||
FC32T64 => handler!(self, |OpsRR(tg, reg)| self
|
||||
.write_reg(tg, self.read_reg(reg).cast::<f32>() as f64)),
|
||||
I::FC64T32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg(
|
||||
FC64T32 => handler!(self, |OpsRRB(tg, reg, mode)| self.write_reg(
|
||||
tg,
|
||||
crate::float::conv64to32(
|
||||
self.read_reg(reg).cast(),
|
||||
|
@ -341,26 +339,27 @@ where
|
|||
.map_err(|()| VmRunError::InvalidOperand)?,
|
||||
),
|
||||
)),
|
||||
I::LRA16 => handler!(self, |OpsRRP(tg, reg, imm)| self.write_reg(
|
||||
LRA16 => handler!(self, |OpsRRP(tg, reg, imm)| self.write_reg(
|
||||
tg,
|
||||
(self.pc + self.read_reg(reg).cast::<u64>() + imm + 3_u16).get(),
|
||||
)),
|
||||
I::LDR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.load(
|
||||
LDR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.load(
|
||||
dst,
|
||||
base,
|
||||
self.pcrel(off).get(),
|
||||
self.pcrel(off, 3).get(),
|
||||
count
|
||||
)?),
|
||||
I::STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.store(
|
||||
STR16 => handler!(self, |OpsRRPH(dst, base, off, count)| self.store(
|
||||
dst,
|
||||
base,
|
||||
self.pcrel(off).get(),
|
||||
self.pcrel(off, 3).get(),
|
||||
count
|
||||
)?),
|
||||
I::JMP16 => {
|
||||
JMP16 => {
|
||||
let OpsP(off) = self.decode();
|
||||
self.pc = self.pcrel(off);
|
||||
self.pc = self.pcrel(off, 1);
|
||||
}
|
||||
op => return Err(VmRunError::InvalidOpcode(op)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -376,12 +375,15 @@ 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>())
|
||||
.wrapping_add(1);
|
||||
}
|
||||
|
||||
/// Decode instruction operands
|
||||
#[inline(always)]
|
||||
unsafe fn decode<T: Copy + 'static>(&mut self) -> T {
|
||||
unsafe fn decode<T: Copy>(&mut self) -> T {
|
||||
unsafe { self.memory.prog_read::<T>(self.pc + 1_u64) }
|
||||
}
|
||||
|
||||
|
@ -402,7 +404,10 @@ where
|
|||
unsafe {
|
||||
self.memory.load(
|
||||
self.ldst_addr_uber(dst, base, offset, count, n)?,
|
||||
self.registers.as_mut_ptr().add(usize::from(dst) + usize::from(n)).cast(),
|
||||
self.registers
|
||||
.as_mut_ptr()
|
||||
.add(usize::from(dst) + usize::from(n))
|
||||
.cast(),
|
||||
usize::from(count).saturating_sub(n.into()),
|
||||
)
|
||||
}?;
|
||||
|
@ -439,13 +444,16 @@ where
|
|||
#[inline(always)]
|
||||
unsafe fn binary_op<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) {
|
||||
let OpsRRR(tg, a0, a1) = unsafe { self.decode() };
|
||||
self.write_reg(tg, op(self.read_reg(a0).cast::<T>(), self.read_reg(a1).cast::<T>()));
|
||||
self.write_reg(
|
||||
tg,
|
||||
op(self.read_reg(a0).cast::<T>(), self.read_reg(a1).cast::<T>()),
|
||||
);
|
||||
self.bump_pc::<OpsRRR>();
|
||||
}
|
||||
|
||||
/// Perform binary operation over register and immediate
|
||||
#[inline(always)]
|
||||
unsafe fn binary_op_imm<T: ValueVariant + 'static>(&mut self, op: impl Fn(T, T) -> T) {
|
||||
unsafe fn binary_op_imm<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) {
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(packed)]
|
||||
struct OpsRRImm<I>(OpsRR, I);
|
||||
|
@ -459,7 +467,13 @@ where
|
|||
#[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() };
|
||||
self.write_reg(tg, op(self.read_reg(a0).cast::<T>(), self.read_reg(a1).cast::<u32>()));
|
||||
self.write_reg(
|
||||
tg,
|
||||
op(
|
||||
self.read_reg(a0).cast::<T>(),
|
||||
self.read_reg(a1).cast::<u32>(),
|
||||
),
|
||||
);
|
||||
self.bump_pc::<OpsRRR>();
|
||||
}
|
||||
|
||||
|
@ -474,24 +488,22 @@ where
|
|||
/// Fused division-remainder
|
||||
#[inline(always)]
|
||||
unsafe fn dir<T: ValueVariant + CheckedDivRem>(&mut self) {
|
||||
unsafe {
|
||||
handler!(self, |OpsRRRR(td, tr, a0, a1)| {
|
||||
let a0 = self.read_reg(a0).cast::<T>();
|
||||
let a1 = self.read_reg(a1).cast::<T>();
|
||||
handler!(self, |OpsRRRR(td, tr, a0, a1)| {
|
||||
let a0 = self.read_reg(a0).cast::<T>();
|
||||
let a1 = self.read_reg(a1).cast::<T>();
|
||||
|
||||
if let Some(div) = a0.checked_div(a1) {
|
||||
self.write_reg(td, div);
|
||||
} else {
|
||||
self.write_reg(td, -1_i64);
|
||||
}
|
||||
if let Some(div) = a0.checked_div(a1) {
|
||||
self.write_reg(td, div);
|
||||
} else {
|
||||
self.write_reg(td, -1_i64);
|
||||
}
|
||||
|
||||
if let Some(rem) = a0.checked_rem(a1) {
|
||||
self.write_reg(tr, rem);
|
||||
} else {
|
||||
self.write_reg(tr, a0);
|
||||
}
|
||||
});
|
||||
}
|
||||
if let Some(rem) = a0.checked_rem(a1) {
|
||||
self.write_reg(tr, rem);
|
||||
} else {
|
||||
self.write_reg(tr, a0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// Fused multiply-add
|
||||
|
@ -500,43 +512,41 @@ where
|
|||
where
|
||||
T: ValueVariant + core::ops::Mul<Output = T> + core::ops::Add<Output = T>,
|
||||
{
|
||||
unsafe {
|
||||
handler!(self, |OpsRRRR(tg, a0, a1, a2)| {
|
||||
let a0 = self.read_reg(a0).cast::<T>();
|
||||
let a1 = self.read_reg(a1).cast::<T>();
|
||||
let a2 = self.read_reg(a2).cast::<T>();
|
||||
self.write_reg(tg, a0 * a1 + a2)
|
||||
});
|
||||
}
|
||||
handler!(self, |OpsRRRR(tg, a0, a1, a2)| {
|
||||
let a0 = self.read_reg(a0).cast::<T>();
|
||||
let a1 = self.read_reg(a1).cast::<T>();
|
||||
let a2 = self.read_reg(a2).cast::<T>();
|
||||
self.write_reg(tg, a0 * a1 + a2)
|
||||
});
|
||||
}
|
||||
|
||||
/// Float comparsion
|
||||
#[inline(always)]
|
||||
unsafe fn fcmp<T: PartialOrd + ValueVariant>(&mut self, swapped: bool, expected: Ordering) {
|
||||
unsafe {
|
||||
handler!(self, |OpsRRR(tg, a0, a1)| {
|
||||
let mut a0 = self.read_reg(a0).cast::<T>();
|
||||
let mut a1 = self.read_reg(a1).cast::<T>();
|
||||
if swapped {
|
||||
core::mem::swap(&mut a0, &mut a1);
|
||||
}
|
||||
self.write_reg(tg, (a0.partial_cmp(&a1) == Some(expected)) as u8)
|
||||
});
|
||||
}
|
||||
unsafe fn fcmp<T: PartialOrd + ValueVariant>(&mut self, nan: Ordering) {
|
||||
handler!(self, |OpsRRR(tg, a0, a1)| {
|
||||
let a0 = self.read_reg(a0).cast::<T>();
|
||||
let a1 = self.read_reg(a1).cast::<T>();
|
||||
self.write_reg(tg, (a0.partial_cmp(&a1).unwrap_or(nan) as i8 + 1) as u8)
|
||||
});
|
||||
}
|
||||
|
||||
/// Calculate pc-relative address
|
||||
#[inline(always)]
|
||||
fn pcrel(&self, offset: impl AddressOp) -> Address {
|
||||
self.pc.wrapping_add(offset)
|
||||
fn pcrel(&self, offset: impl AddressOp, pos: u8) -> Address {
|
||||
self.pc.wrapping_add(pos).wrapping_add(offset)
|
||||
}
|
||||
|
||||
/// 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() };
|
||||
if self.read_reg(a0).cast::<T>().cmp(&self.read_reg(a1).cast::<T>()) == expected {
|
||||
self.pc = self.pcrel(ja);
|
||||
if self
|
||||
.read_reg(a0)
|
||||
.cast::<T>()
|
||||
.cmp(&self.read_reg(a1).cast::<T>())
|
||||
== expected
|
||||
{
|
||||
self.pc = self.pcrel(ja, 3);
|
||||
} else {
|
||||
self.bump_pc::<OpsRRP>();
|
||||
}
|
|
@ -5,5 +5,5 @@ edition = "2021"
|
|||
default-run = "hbxrt"
|
||||
|
||||
[dependencies]
|
||||
hbvm.workspace = true
|
||||
hbvm.path = "../hbvm"
|
||||
memmap2 = "0.9"
|
|
@ -52,17 +52,16 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
eprintln!("[I] Image loaded at {:p}", mmap.as_ptr());
|
||||
|
||||
let mut vm = unsafe {
|
||||
Vm::<_, 0>::new(hbvm::mem::HostMemory, Address::new(mmap.as_ptr().add(stack.len()) as u64))
|
||||
};
|
||||
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);
|
||||
|
||||
// Execute program
|
||||
let stat = loop {
|
||||
match vm.run() {
|
||||
Ok(VmRunOk::Breakpoint) => {
|
||||
eprintln!("[I] Hit breakpoint\nIP: {}\n== Registers ==\n{:?}", vm.pc, vm.registers)
|
||||
}
|
||||
Ok(VmRunOk::Breakpoint) => eprintln!(
|
||||
"[I] Hit breakpoint\nIP: {}\n== Registers ==\n{:?}",
|
||||
vm.pc, vm.registers
|
||||
),
|
||||
Ok(VmRunOk::Timer) => (),
|
||||
Ok(VmRunOk::Ecall) if dsls => unsafe {
|
||||
std::arch::asm!(
|
47
hbxrt/src/mem.rs
Normal file
47
hbxrt/src/mem.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use std::alloc::Layout;
|
||||
|
||||
use hbvm::mem::{Address, LoadError, Memory, StoreError};
|
||||
|
||||
pub struct HostMemory;
|
||||
impl Memory for HostMemory {
|
||||
#[inline]
|
||||
unsafe fn load(
|
||||
&mut self,
|
||||
addr: Address,
|
||||
target: *mut u8,
|
||||
count: usize,
|
||||
) -> Result<(), LoadError> {
|
||||
unsafe { core::ptr::copy(addr.get() as *const u8, target, count) }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn store(
|
||||
&mut self,
|
||||
addr: Address,
|
||||
source: *const u8,
|
||||
count: usize,
|
||||
) -> Result<(), StoreError> {
|
||||
unsafe { core::ptr::copy(source, addr.get() as *mut u8, count) }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
unsafe fn prog_read<T: Copy>(&mut self, addr: Address) -> T {
|
||||
unsafe { core::ptr::read(addr.get() as *const T) }
|
||||
}
|
||||
}
|
||||
|
||||
const STACK_SIZE: usize = 2; // MiB
|
||||
type Stack = [u8; 1024 * 1024 * STACK_SIZE];
|
||||
|
||||
/// Allocate stack of size [`STACK_SIZE`] MiB
|
||||
pub unsafe fn alloc_stack() -> Box<Stack> {
|
||||
let layout = Layout::new::<Stack>();
|
||||
let ptr = unsafe { std::alloc::alloc(layout) };
|
||||
if ptr.is_null() {
|
||||
std::alloc::handle_alloc_error(layout);
|
||||
}
|
||||
|
||||
unsafe { Box::from_raw(ptr.cast()) }
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
[package]
|
||||
name = "hblang"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "hbc"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz"
|
||||
path = "src/fuzz_main.rs"
|
||||
|
||||
[dependencies]
|
||||
hbbytecode = { workspace = true, features = ["disasm"] }
|
||||
hbvm = { workspace = true, features = ["nightly"] }
|
||||
hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry"] }
|
||||
log = "0.4.22"
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
no_log = ["log/max_level_off"]
|
1810
lang/README.md
1810
lang/README.md
File diff suppressed because one or more lines are too long
|
@ -1,4 +0,0 @@
|
|||
--fmt - format all imported source files
|
||||
--fmt-stdout - dont write the formatted file but print it
|
||||
--dump-asm - output assembly instead of raw code, (the assembly is more for debugging the compiler)
|
||||
--threads <1...> - number of extra threads compiler can use [default: 0]
|
614
lang/src/fmt.rs
614
lang/src/fmt.rs
|
@ -1,614 +0,0 @@
|
|||
use {
|
||||
crate::{
|
||||
lexer::{self, Lexer, TokenKind},
|
||||
parser::{self, CommentOr, CtorField, EnumField, Expr, Poser, Radix, StructField},
|
||||
},
|
||||
core::fmt::{self},
|
||||
};
|
||||
|
||||
pub fn display_radix(radix: Radix, mut value: u64, buf: &mut [u8; 64]) -> &str {
|
||||
fn conv_radix(d: u8) -> u8 {
|
||||
match d {
|
||||
0..=9 => d + b'0',
|
||||
_ => d - 10 + b'A',
|
||||
}
|
||||
}
|
||||
|
||||
for (i, b) in buf.iter_mut().enumerate().rev() {
|
||||
let d = (value % radix as u64) as u8;
|
||||
value /= radix as u64;
|
||||
*b = conv_radix(d);
|
||||
if value == 0 {
|
||||
return unsafe { core::str::from_utf8_unchecked(&buf[i..]) };
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
enum TokenGroup {
|
||||
Blank,
|
||||
Comment,
|
||||
Keyword,
|
||||
Identifier,
|
||||
Directive,
|
||||
Number,
|
||||
String,
|
||||
Op,
|
||||
Assign,
|
||||
Paren,
|
||||
Bracket,
|
||||
Colon,
|
||||
Comma,
|
||||
Dot,
|
||||
Ctor,
|
||||
}
|
||||
|
||||
fn token_group(kind: TokenKind) -> TokenGroup {
|
||||
use {crate::lexer::TokenKind::*, TokenGroup as TG};
|
||||
match kind {
|
||||
BSlash | Pound | Eof | Ct => TG::Blank,
|
||||
Comment => TG::Comment,
|
||||
Directive => TG::Directive,
|
||||
Colon => TG::Colon,
|
||||
Semi | Comma => TG::Comma,
|
||||
Dot => TG::Dot,
|
||||
Ctor | Tupl | TArrow => TG::Ctor,
|
||||
LParen | RParen => TG::Paren,
|
||||
LBrace | RBrace | LBrack | RBrack => TG::Bracket,
|
||||
Number | Float => TG::Number,
|
||||
Under | CtIdent | Ident => TG::Identifier,
|
||||
Tick | Tilde | Que | Not | Mod | Band | Bor | Xor | Mul | Add | Sub | Div | Shl | Shr
|
||||
| Or | And | Lt | Gt | Eq | Le | Ge | Ne => TG::Op,
|
||||
Decl | Assign | BorAss | XorAss | BandAss | AddAss | SubAss | MulAss | DivAss | ModAss
|
||||
| ShrAss | ShlAss => TG::Assign,
|
||||
DQuote | Quote => TG::String,
|
||||
Return | If | Else | Loop | Break | Continue | Fn | Idk | Die | Struct | Packed | True
|
||||
| False | Null | Match | Enum => TG::Keyword,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_token_kinds(mut source: &mut [u8]) -> usize {
|
||||
let len = source.len();
|
||||
loop {
|
||||
let src = unsafe { core::str::from_utf8_unchecked(source) };
|
||||
let mut token = lexer::Lexer::new(src).eat();
|
||||
match token.kind {
|
||||
TokenKind::Eof => break,
|
||||
// ???
|
||||
TokenKind::CtIdent | TokenKind::Directive => token.start -= 1,
|
||||
_ => {}
|
||||
}
|
||||
let start = token.start as usize;
|
||||
let end = token.end as usize;
|
||||
source[..start].fill(0);
|
||||
source[start..end].fill(token_group(token.kind) as u8);
|
||||
source = &mut source[end..];
|
||||
}
|
||||
len
|
||||
}
|
||||
|
||||
pub fn minify(source: &mut str) -> usize {
|
||||
fn needs_space(c: u8) -> bool {
|
||||
matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | 127..)
|
||||
}
|
||||
|
||||
let mut writer = source.as_mut_ptr();
|
||||
let mut reader = &source[..];
|
||||
let mut prev_needs_whitecpace = false;
|
||||
let mut prev_needs_newline = false;
|
||||
loop {
|
||||
let mut token = lexer::Lexer::new(reader).eat();
|
||||
match token.kind {
|
||||
TokenKind::Eof => break,
|
||||
TokenKind::CtIdent | TokenKind::Directive => token.start -= 1,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let cpy_len = token.range().len();
|
||||
|
||||
let mut prefix = 0;
|
||||
if prev_needs_whitecpace && needs_space(reader.as_bytes()[token.start as usize]) {
|
||||
prefix = b' ';
|
||||
debug_assert!(token.start != 0, "{reader}");
|
||||
}
|
||||
prev_needs_whitecpace = needs_space(reader.as_bytes()[token.end as usize - 1]);
|
||||
|
||||
let inbetween_new_lines =
|
||||
reader[..token.start as usize].bytes().filter(|&b| b == b'\n').count()
|
||||
+ token.kind.precedence().is_some() as usize;
|
||||
let extra_prefix_new_lines = if inbetween_new_lines > 1 {
|
||||
1 + token.kind.precedence().is_none() as usize
|
||||
} else {
|
||||
prev_needs_newline as usize
|
||||
};
|
||||
|
||||
if token.kind == TokenKind::Comment && reader.as_bytes()[token.end as usize - 1] != b'/' {
|
||||
prev_needs_newline = true;
|
||||
prev_needs_whitecpace = false;
|
||||
} else {
|
||||
prev_needs_newline = false;
|
||||
}
|
||||
|
||||
let sstr = reader[token.start as usize..].as_ptr();
|
||||
reader = &reader[token.end as usize..];
|
||||
unsafe {
|
||||
if extra_prefix_new_lines != 0 {
|
||||
for _ in 0..extra_prefix_new_lines {
|
||||
writer.write(b'\n');
|
||||
writer = writer.add(1);
|
||||
}
|
||||
} else if prefix != 0 {
|
||||
writer.write(prefix);
|
||||
writer = writer.add(1);
|
||||
}
|
||||
writer.copy_from(sstr, cpy_len);
|
||||
writer = writer.add(cpy_len);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { writer.sub_ptr(source.as_mut_ptr()) }
|
||||
}
|
||||
|
||||
pub struct Formatter<'a> {
|
||||
source: &'a str,
|
||||
depth: usize,
|
||||
}
|
||||
|
||||
// we exclusively use `write_str` to reduce bloat
|
||||
impl<'a> Formatter<'a> {
|
||||
pub fn new(source: &'a str) -> Self {
|
||||
Self { source, depth: 0 }
|
||||
}
|
||||
|
||||
fn fmt_list<T: Poser, F: core::fmt::Write>(
|
||||
&mut self,
|
||||
f: &mut F,
|
||||
trailing: bool,
|
||||
end: &str,
|
||||
sep: &str,
|
||||
list: &[T],
|
||||
fmt: impl Fn(&mut Self, &T, &mut F) -> fmt::Result,
|
||||
) -> fmt::Result {
|
||||
self.fmt_list_low(f, trailing, end, sep, list, |s, v, f| {
|
||||
fmt(s, v, f)?;
|
||||
Ok(true)
|
||||
})
|
||||
}
|
||||
|
||||
fn fmt_list_low<T: Poser, F: core::fmt::Write>(
|
||||
&mut self,
|
||||
f: &mut F,
|
||||
trailing: bool,
|
||||
end: &str,
|
||||
sep: &str,
|
||||
list: &[T],
|
||||
fmt: impl Fn(&mut Self, &T, &mut F) -> Result<bool, fmt::Error>,
|
||||
) -> fmt::Result {
|
||||
if !trailing {
|
||||
let mut first = true;
|
||||
for expr in list {
|
||||
if !core::mem::take(&mut first) {
|
||||
f.write_str(sep)?;
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
first = !fmt(self, expr, f)?;
|
||||
}
|
||||
return f.write_str(end);
|
||||
}
|
||||
|
||||
writeln!(f)?;
|
||||
self.depth += 1;
|
||||
let res = (|| {
|
||||
for (i, stmt) in list.iter().enumerate() {
|
||||
for _ in 0..self.depth {
|
||||
f.write_str("\t")?;
|
||||
}
|
||||
let add_sep = fmt(self, stmt, f)?;
|
||||
if add_sep {
|
||||
f.write_str(sep)?;
|
||||
}
|
||||
if let Some(expr) = list.get(i + 1)
|
||||
&& let Some(rest) = self.source.get(expr.posi() as usize..)
|
||||
{
|
||||
if sep.is_empty() && insert_needed_semicolon(rest) {
|
||||
f.write_str(";")?;
|
||||
}
|
||||
if preserve_newlines(&self.source[..expr.posi() as usize]) > 1 {
|
||||
f.write_str("\n")?;
|
||||
}
|
||||
}
|
||||
if add_sep {
|
||||
f.write_str("\n")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
self.depth -= 1;
|
||||
|
||||
for _ in 0..self.depth {
|
||||
f.write_str("\t")?;
|
||||
}
|
||||
f.write_str(end)?;
|
||||
res
|
||||
}
|
||||
|
||||
fn fmt_paren<F: core::fmt::Write>(
|
||||
&mut self,
|
||||
expr: &Expr,
|
||||
f: &mut F,
|
||||
cond: impl FnOnce(&Expr) -> bool,
|
||||
) -> fmt::Result {
|
||||
if cond(expr) {
|
||||
f.write_str("(")?;
|
||||
self.fmt(expr, f)?;
|
||||
f.write_str(")")
|
||||
} else {
|
||||
self.fmt(expr, f)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt<F: core::fmt::Write>(&mut self, expr: &Expr, f: &mut F) -> fmt::Result {
|
||||
macro_rules! impl_parenter {
|
||||
($($name:ident => $pat:pat,)*) => {
|
||||
$(
|
||||
let $name = |e: &Expr| matches!(e, $pat);
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl_parenter! {
|
||||
unary => Expr::BinOp { .. },
|
||||
postfix => Expr::UnOp { .. } | Expr::BinOp { .. },
|
||||
consecutive => Expr::UnOp { .. },
|
||||
}
|
||||
|
||||
match *expr {
|
||||
Expr::Ct { value, .. } => {
|
||||
f.write_str("$: ")?;
|
||||
self.fmt(value, f)
|
||||
}
|
||||
Expr::String { literal, .. } => f.write_str(literal),
|
||||
Expr::Comment { literal, .. } => f.write_str(literal),
|
||||
Expr::Mod { path, .. } => write!(f, "@use(\"{path}\")"),
|
||||
Expr::Embed { path, .. } => write!(f, "@embed(\"{path}\")"),
|
||||
Expr::Field { target, name: field, .. } => {
|
||||
self.fmt_paren(target, f, postfix)?;
|
||||
f.write_str(".")?;
|
||||
f.write_str(field)
|
||||
}
|
||||
Expr::Directive { name, args, .. } => {
|
||||
f.write_str("@")?;
|
||||
f.write_str(name)?;
|
||||
f.write_str("(")?;
|
||||
self.fmt_list(f, false, ")", ",", args, Self::fmt)
|
||||
}
|
||||
Expr::Struct { fields, trailing_comma, packed, .. } => {
|
||||
if packed {
|
||||
f.write_str("packed ")?;
|
||||
}
|
||||
|
||||
f.write_str("struct {")?;
|
||||
self.fmt_list_low(f, trailing_comma, "}", ",", fields, |s, field, f| {
|
||||
match field {
|
||||
CommentOr::Or(StructField { name, ty, .. }) => {
|
||||
f.write_str(name)?;
|
||||
f.write_str(": ")?;
|
||||
s.fmt(ty, f)?
|
||||
}
|
||||
CommentOr::Comment { literal, .. } => {
|
||||
f.write_str(literal)?;
|
||||
f.write_str("\n")?;
|
||||
}
|
||||
}
|
||||
Ok(field.or().is_some())
|
||||
})
|
||||
}
|
||||
Expr::Enum { variants, trailing_comma, .. } => {
|
||||
f.write_str("enum {")?;
|
||||
self.fmt_list_low(f, trailing_comma, "}", ",", variants, |_, var, f| {
|
||||
match var {
|
||||
CommentOr::Or(EnumField { name, .. }) => {
|
||||
f.write_str(name)?;
|
||||
}
|
||||
CommentOr::Comment { literal, .. } => {
|
||||
f.write_str(literal)?;
|
||||
f.write_str("\n")?;
|
||||
}
|
||||
}
|
||||
Ok(var.or().is_some())
|
||||
})
|
||||
}
|
||||
Expr::Ctor { ty, fields, trailing_comma, .. } => {
|
||||
if let Some(ty) = ty {
|
||||
self.fmt_paren(ty, f, unary)?;
|
||||
}
|
||||
f.write_str(".{")?;
|
||||
self.fmt_list(
|
||||
f,
|
||||
trailing_comma,
|
||||
"}",
|
||||
",",
|
||||
fields,
|
||||
|s: &mut Self, CtorField { name, value, .. }: &_, f| {
|
||||
f.write_str(name)?;
|
||||
if !matches!(value, &Expr::Ident { id, .. } if *name == &self.source[id.range()]) {
|
||||
f.write_str(": ")?;
|
||||
s.fmt(value, f)?;
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)
|
||||
}
|
||||
Expr::Tupl {
|
||||
pos,
|
||||
ty: Some(&Expr::Slice { pos: spos, size: Some(&Expr::Number { value, .. }), item }),
|
||||
fields,
|
||||
trailing_comma,
|
||||
} if value as usize == fields.len() => self.fmt(
|
||||
&Expr::Tupl {
|
||||
pos,
|
||||
ty: Some(&Expr::Slice { pos: spos, size: None, item }),
|
||||
fields,
|
||||
trailing_comma,
|
||||
},
|
||||
f,
|
||||
),
|
||||
Expr::Tupl { ty, fields, trailing_comma, .. } => {
|
||||
if let Some(ty) = ty {
|
||||
self.fmt_paren(ty, f, unary)?;
|
||||
}
|
||||
f.write_str(".(")?;
|
||||
self.fmt_list(f, trailing_comma, ")", ",", fields, Self::fmt)
|
||||
}
|
||||
Expr::Slice { item, size, .. } => {
|
||||
f.write_str("[")?;
|
||||
self.fmt(item, f)?;
|
||||
if let Some(size) = size {
|
||||
f.write_str("; ")?;
|
||||
self.fmt(size, f)?;
|
||||
}
|
||||
f.write_str("]")
|
||||
}
|
||||
Expr::Index { base, index } => {
|
||||
self.fmt(base, f)?;
|
||||
f.write_str("[")?;
|
||||
self.fmt(index, f)?;
|
||||
f.write_str("]")
|
||||
}
|
||||
Expr::UnOp { op, val, .. } => {
|
||||
f.write_str(op.name())?;
|
||||
self.fmt_paren(val, f, unary)
|
||||
}
|
||||
Expr::Break { .. } => f.write_str("break"),
|
||||
Expr::Continue { .. } => f.write_str("continue"),
|
||||
Expr::If { cond, then, else_, .. } => {
|
||||
f.write_str("if ")?;
|
||||
self.fmt(cond, f)?;
|
||||
f.write_str(" ")?;
|
||||
self.fmt_paren(then, f, consecutive)?;
|
||||
if let Some(e) = else_ {
|
||||
f.write_str(" else ")?;
|
||||
self.fmt(e, f)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Expr::Match { value, branches, .. } => {
|
||||
f.write_str("match ")?;
|
||||
self.fmt(value, f)?;
|
||||
f.write_str(" {")?;
|
||||
self.fmt_list(f, true, "}", ",", branches, |s, br, f| {
|
||||
s.fmt(&br.pat, f)?;
|
||||
f.write_str(" => ")?;
|
||||
s.fmt(&br.body, f)
|
||||
})
|
||||
}
|
||||
Expr::Loop { body, .. } => {
|
||||
f.write_str("loop ")?;
|
||||
self.fmt(body, f)
|
||||
}
|
||||
Expr::Closure { ret, body, args, .. } => {
|
||||
f.write_str("fn(")?;
|
||||
self.fmt_list(f, false, "", ",", args, |s, arg, f| {
|
||||
if arg.is_ct {
|
||||
f.write_str("$")?;
|
||||
}
|
||||
f.write_str(arg.name)?;
|
||||
f.write_str(": ")?;
|
||||
s.fmt(&arg.ty, f)
|
||||
})?;
|
||||
f.write_str("): ")?;
|
||||
self.fmt(ret, f)?;
|
||||
f.write_str(" ")?;
|
||||
self.fmt_paren(body, f, consecutive)?;
|
||||
Ok(())
|
||||
}
|
||||
Expr::Call { func, args, trailing_comma } => {
|
||||
self.fmt_paren(func, f, postfix)?;
|
||||
f.write_str("(")?;
|
||||
self.fmt_list(f, trailing_comma, ")", ",", args, Self::fmt)
|
||||
}
|
||||
Expr::Return { val: Some(val), .. } => {
|
||||
f.write_str("return ")?;
|
||||
self.fmt(val, f)
|
||||
}
|
||||
Expr::Return { val: None, .. } => f.write_str("return"),
|
||||
Expr::Wildcard { .. } => f.write_str("_"),
|
||||
Expr::Ident { pos, is_ct, .. } => {
|
||||
if is_ct {
|
||||
f.write_str("$")?;
|
||||
}
|
||||
f.write_str(&self.source[Lexer::restore(self.source, pos).eat().range()])
|
||||
}
|
||||
Expr::Block { stmts, .. } => {
|
||||
f.write_str("{")?;
|
||||
self.fmt_list(f, true, "}", "", stmts, Self::fmt)
|
||||
}
|
||||
Expr::Number { value, radix, .. } => {
|
||||
f.write_str(match radix {
|
||||
Radix::Decimal => "",
|
||||
Radix::Hex => "0x",
|
||||
Radix::Octal => "0o",
|
||||
Radix::Binary => "0b",
|
||||
})?;
|
||||
let mut buf = [0u8; 64];
|
||||
f.write_str(display_radix(radix, value as u64, &mut buf))
|
||||
}
|
||||
Expr::Float { pos, .. } => {
|
||||
f.write_str(&self.source[Lexer::restore(self.source, pos).eat().range()])
|
||||
}
|
||||
Expr::Bool { value, .. } => f.write_str(if value { "true" } else { "false" }),
|
||||
Expr::Idk { .. } => f.write_str("idk"),
|
||||
Expr::Die { .. } => f.write_str("die"),
|
||||
Expr::Null { .. } => f.write_str("null"),
|
||||
Expr::BinOp {
|
||||
left,
|
||||
op: TokenKind::Assign,
|
||||
right: &Expr::BinOp { left: lleft, op, right, .. },
|
||||
..
|
||||
} if left.pos() == lleft.pos() => {
|
||||
self.fmt(left, f)?;
|
||||
f.write_str(" ")?;
|
||||
f.write_str(op.name())?;
|
||||
f.write_str("= ")?;
|
||||
self.fmt(right, f)
|
||||
}
|
||||
Expr::BinOp { right, op, left, .. } => {
|
||||
let prec_miss_left = |e: &Expr| {
|
||||
matches!(
|
||||
e, Expr::BinOp { op: lop, .. } if op.precedence() > lop.precedence()
|
||||
)
|
||||
};
|
||||
let prec_miss_right = |e: &Expr| {
|
||||
matches!(
|
||||
e, Expr::BinOp { op: lop, .. }
|
||||
if (op.precedence() == lop.precedence() && !op.is_comutative())
|
||||
|| op.precedence() > lop.precedence()
|
||||
)
|
||||
};
|
||||
|
||||
self.fmt_paren(left, f, prec_miss_left)?;
|
||||
if let Some(mut prev) = self.source.get(..right.pos() as usize) {
|
||||
prev = prev.trim_end();
|
||||
let estimate_bound =
|
||||
prev.rfind(|c: char| c.is_ascii_whitespace()).map_or(prev.len(), |i| i + 1);
|
||||
let exact_bound = lexer::Lexer::new(&prev[estimate_bound..]).last().start;
|
||||
prev = &prev[..exact_bound as usize + estimate_bound];
|
||||
if preserve_newlines(prev) > 0 {
|
||||
f.write_str("\n")?;
|
||||
for _ in 0..self.depth + 1 {
|
||||
f.write_str("\t")?;
|
||||
}
|
||||
f.write_str(op.name())?;
|
||||
f.write_str(" ")?;
|
||||
} else {
|
||||
f.write_str(" ")?;
|
||||
f.write_str(op.name())?;
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
} else {
|
||||
f.write_str(" ")?;
|
||||
f.write_str(op.name())?;
|
||||
f.write_str(" ")?;
|
||||
}
|
||||
self.fmt_paren(right, f, prec_miss_right)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn preserve_newlines(source: &str) -> usize {
|
||||
source[source.trim_end().len()..].bytes().filter(|&c| c == b'\n').count()
|
||||
}
|
||||
|
||||
pub fn insert_needed_semicolon(source: &str) -> bool {
|
||||
let kind = lexer::Lexer::new(source).eat().kind;
|
||||
kind.precedence().is_some() || matches!(kind, TokenKind::Ctor | TokenKind::Tupl)
|
||||
}
|
||||
|
||||
impl core::fmt::Display for parser::Ast {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt_file(self.exprs(), &self.file, f)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt_file(exprs: &[Expr], file: &str, f: &mut impl fmt::Write) -> fmt::Result {
|
||||
for (i, expr) in exprs.iter().enumerate() {
|
||||
Formatter::new(file).fmt(expr, f)?;
|
||||
if let Some(expr) = exprs.get(i + 1)
|
||||
&& let Some(rest) = file.get(expr.pos() as usize..)
|
||||
{
|
||||
if insert_needed_semicolon(rest) {
|
||||
write!(f, ";")?;
|
||||
}
|
||||
|
||||
if preserve_newlines(&file[..expr.pos() as usize]) > 1 {
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
if i + 1 != exprs.len() {
|
||||
writeln!(f)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod test {
|
||||
use {
|
||||
crate::parser::{self, Ctx},
|
||||
alloc::borrow::ToOwned,
|
||||
std::{fmt::Write, string::String},
|
||||
};
|
||||
|
||||
pub fn format(ident: &str, input: &str) {
|
||||
let mut minned = input.to_owned();
|
||||
let len = crate::fmt::minify(&mut minned);
|
||||
minned.truncate(len);
|
||||
|
||||
let mut ctx = Ctx::default();
|
||||
let ast = parser::Ast::new(ident, minned, &mut ctx, &mut parser::no_loader);
|
||||
let mut output = String::new();
|
||||
write!(output, "{ast}").unwrap();
|
||||
|
||||
let input_path = format!("formatter_{ident}.expected");
|
||||
let output_path = format!("formatter_{ident}.actual");
|
||||
std::fs::write(&input_path, input).unwrap();
|
||||
std::fs::write(&output_path, output).unwrap();
|
||||
|
||||
let success = std::process::Command::new("diff")
|
||||
.arg("-u")
|
||||
.arg("--color")
|
||||
.arg(&input_path)
|
||||
.arg(&output_path)
|
||||
.status()
|
||||
.unwrap()
|
||||
.success();
|
||||
std::fs::remove_file(&input_path).unwrap();
|
||||
std::fs::remove_file(&output_path).unwrap();
|
||||
assert!(success, "test failed");
|
||||
}
|
||||
|
||||
macro_rules! test {
|
||||
($($name:ident => $input:expr;)*) => {$(
|
||||
#[test]
|
||||
fn $name() {
|
||||
format(stringify!($name), $input);
|
||||
}
|
||||
)*};
|
||||
}
|
||||
|
||||
test! {
|
||||
comments => "// comment\n// comment\n\n// comment\n\n\
|
||||
/* comment */\n/* comment */\n\n/* comment */";
|
||||
some_ordinary_code => "loft := fn(): int return loft(1, 2, 3)";
|
||||
some_arg_per_line_code => "loft := fn(): int return loft(\
|
||||
\n\t1,\n\t2,\n\t3,\n)";
|
||||
some_ordinary_struct => "loft := fn(): int return loft.{a: 1, b: 2}";
|
||||
some_ordinary_fild_per_lin_struct => "loft := fn(): int return loft.{\
|
||||
\n\ta: 1,\n\tb: 2,\n}";
|
||||
code_block => "loft := fn(): int {\n\tloft()\n\treturn 1\n}";
|
||||
}
|
||||
}
|
384
lang/src/fs.rs
384
lang/src/fs.rs
|
@ -1,384 +0,0 @@
|
|||
use {
|
||||
crate::{
|
||||
parser::{Ast, Ctx, FileKind},
|
||||
son::{self, hbvm::HbvmBackend},
|
||||
ty, FnvBuildHasher,
|
||||
},
|
||||
alloc::{string::String, vec::Vec},
|
||||
core::{fmt::Write, num::NonZeroUsize, ops::Deref},
|
||||
hashbrown::hash_map,
|
||||
std::{
|
||||
collections::VecDeque,
|
||||
eprintln,
|
||||
ffi::OsStr,
|
||||
io::{self, Write as _},
|
||||
path::{Path, PathBuf},
|
||||
string::ToString,
|
||||
sync::Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
type HashMap<K, V> = hashbrown::HashMap<K, V, FnvBuildHasher>;
|
||||
|
||||
pub struct Logger;
|
||||
|
||||
impl log::Log for Logger {
|
||||
fn enabled(&self, _: &log::Metadata) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn log(&self, record: &log::Record) {
|
||||
if self.enabled(record.metadata()) {
|
||||
eprintln!("{}", record.args())
|
||||
}
|
||||
}
|
||||
|
||||
fn flush(&self) {}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Options {
|
||||
pub fmt: bool,
|
||||
pub fmt_stdout: bool,
|
||||
pub dump_asm: bool,
|
||||
pub in_house_regalloc: bool,
|
||||
pub extra_threads: usize,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn from_args(args: &[&str], out: &mut Vec<u8>) -> std::io::Result<Self> {
|
||||
if args.contains(&"--help") || args.contains(&"-h") {
|
||||
writeln!(out, "Usage: hbc [OPTIONS...] <FILE>")?;
|
||||
writeln!(out, include_str!("../command-help.txt"))?;
|
||||
return Err(std::io::ErrorKind::Other.into());
|
||||
}
|
||||
|
||||
Ok(Options {
|
||||
fmt: args.contains(&"--fmt"),
|
||||
fmt_stdout: args.contains(&"--fmt-stdout"),
|
||||
dump_asm: args.contains(&"--dump-asm"),
|
||||
in_house_regalloc: args.contains(&"--in-house-regalloc"),
|
||||
extra_threads: args
|
||||
.iter()
|
||||
.position(|&a| a == "--threads")
|
||||
.map(|i| {
|
||||
args[i + 1].parse::<NonZeroUsize>().map_err(|e| {
|
||||
writeln!(out, "--threads expects non zero integer: {e}")
|
||||
.err()
|
||||
.unwrap_or(std::io::ErrorKind::Other.into())
|
||||
})
|
||||
})
|
||||
.transpose()?
|
||||
.map_or(1, NonZeroUsize::get)
|
||||
- 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_compiler(
|
||||
root_file: &str,
|
||||
options: Options,
|
||||
out: &mut Vec<u8>,
|
||||
warnings: &mut String,
|
||||
) -> std::io::Result<()> {
|
||||
let parsed = parse_from_fs(options.extra_threads, root_file)?;
|
||||
|
||||
if (options.fmt || options.fmt_stdout) && !parsed.errors.is_empty() {
|
||||
*out = parsed.errors.into_bytes();
|
||||
return Err(std::io::Error::other("fmt fialed (errors are in out)"));
|
||||
}
|
||||
|
||||
if options.fmt {
|
||||
let mut output = String::new();
|
||||
for ast in parsed.ast {
|
||||
write!(output, "{ast}").unwrap();
|
||||
if ast.file.deref().trim() != output.as_str().trim() {
|
||||
std::fs::write(&*ast.path, &output)?;
|
||||
}
|
||||
output.clear();
|
||||
}
|
||||
} else if options.fmt_stdout {
|
||||
write!(out, "{}", &parsed.ast[0])?;
|
||||
} else {
|
||||
let mut backend = HbvmBackend::default();
|
||||
backend.use_in_house_regalloc = options.in_house_regalloc;
|
||||
|
||||
let mut ctx = crate::son::CodegenCtx::default();
|
||||
*ctx.parser.errors.get_mut() = parsed.errors;
|
||||
let mut codegen = son::Codegen::new(&mut backend, &parsed.ast, &mut ctx);
|
||||
codegen.push_embeds(parsed.embeds);
|
||||
codegen.generate(ty::Module::MAIN);
|
||||
|
||||
*warnings = core::mem::take(&mut *codegen.warnings.borrow_mut());
|
||||
|
||||
if !codegen.errors.borrow().is_empty() {
|
||||
drop(codegen);
|
||||
*out = ctx.parser.errors.into_inner().into_bytes();
|
||||
return Err(std::io::Error::other("compilation faoled (errors are in out)"));
|
||||
}
|
||||
|
||||
codegen.assemble(out);
|
||||
|
||||
if options.dump_asm {
|
||||
let mut disasm = String::new();
|
||||
codegen.disasm(&mut disasm, out).map_err(|e| io::Error::other(e.to_string()))?;
|
||||
*out = disasm.into_bytes();
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct TaskQueue<T> {
|
||||
inner: Mutex<TaskQueueInner<T>>,
|
||||
}
|
||||
|
||||
impl<T> TaskQueue<T> {
|
||||
fn new(max_waiters: usize) -> Self {
|
||||
Self { inner: Mutex::new(TaskQueueInner::new(max_waiters)) }
|
||||
}
|
||||
|
||||
pub fn push(&self, message: T) {
|
||||
self.extend([message]);
|
||||
}
|
||||
|
||||
pub fn extend(&self, messages: impl IntoIterator<Item = T>) {
|
||||
self.inner.lock().unwrap().push(messages);
|
||||
}
|
||||
|
||||
pub fn pop(&self) -> Option<T> {
|
||||
TaskQueueInner::pop(&self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
enum TaskSlot<T> {
|
||||
Waiting,
|
||||
Delivered(T),
|
||||
Closed,
|
||||
}
|
||||
|
||||
struct TaskQueueInner<T> {
|
||||
max_waiters: usize,
|
||||
messages: VecDeque<T>,
|
||||
parked: VecDeque<(*mut TaskSlot<T>, std::thread::Thread)>,
|
||||
}
|
||||
|
||||
unsafe impl<T: Send> Send for TaskQueueInner<T> {}
|
||||
unsafe impl<T: Send + Sync> Sync for TaskQueueInner<T> {}
|
||||
|
||||
impl<T> TaskQueueInner<T> {
|
||||
fn new(max_waiters: usize) -> Self {
|
||||
Self { max_waiters, messages: Default::default(), parked: Default::default() }
|
||||
}
|
||||
|
||||
fn push(&mut self, messages: impl IntoIterator<Item = T>) {
|
||||
for msg in messages {
|
||||
if let Some((dest, thread)) = self.parked.pop_front() {
|
||||
unsafe { *dest = TaskSlot::Delivered(msg) };
|
||||
thread.unpark();
|
||||
} else {
|
||||
self.messages.push_back(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn pop(s: &Mutex<Self>) -> Option<T> {
|
||||
let mut res = TaskSlot::Waiting;
|
||||
{
|
||||
let mut s = s.lock().unwrap();
|
||||
if let Some(msg) = s.messages.pop_front() {
|
||||
return Some(msg);
|
||||
}
|
||||
|
||||
if s.max_waiters == s.parked.len() + 1 {
|
||||
for (dest, thread) in s.parked.drain(..) {
|
||||
unsafe { *dest = TaskSlot::Closed };
|
||||
thread.unpark();
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
s.parked.push_back((&mut res, std::thread::current()));
|
||||
}
|
||||
|
||||
loop {
|
||||
std::thread::park();
|
||||
|
||||
let _s = s.lock().unwrap();
|
||||
match core::mem::replace(&mut res, TaskSlot::Waiting) {
|
||||
TaskSlot::Delivered(msg) => return Some(msg),
|
||||
TaskSlot::Closed => return None,
|
||||
TaskSlot::Waiting => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Loaded {
|
||||
ast: Vec<Ast>,
|
||||
embeds: Vec<Vec<u8>>,
|
||||
errors: String,
|
||||
}
|
||||
|
||||
pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Loaded> {
|
||||
fn resolve(path: &str, from: &str, tmp: &mut PathBuf) -> Result<PathBuf, CantLoadFile> {
|
||||
tmp.clear();
|
||||
match Path::new(from).parent() {
|
||||
Some(parent) => tmp.extend([parent, Path::new(path)]),
|
||||
None => tmp.push(path),
|
||||
};
|
||||
|
||||
tmp.canonicalize().map_err(|source| CantLoadFile { path: std::mem::take(tmp), source })
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CantLoadFile {
|
||||
path: PathBuf,
|
||||
source: io::Error,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for CantLoadFile {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
write!(f, "can't load file: {}", display_rel_path(&self.path),)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::error::Error for CantLoadFile {
|
||||
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
|
||||
Some(&self.source)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CantLoadFile> for io::Error {
|
||||
fn from(e: CantLoadFile) -> Self {
|
||||
io::Error::new(io::ErrorKind::InvalidData, e)
|
||||
}
|
||||
}
|
||||
|
||||
type Task = (usize, PathBuf);
|
||||
|
||||
let seen_modules = Mutex::new(HashMap::<PathBuf, usize>::default());
|
||||
let seen_embeds = Mutex::new(HashMap::<PathBuf, usize>::default());
|
||||
let tasks = TaskQueue::<Task>::new(extra_threads + 1);
|
||||
let ast = Mutex::new(Vec::<io::Result<Ast>>::new());
|
||||
let embeds = Mutex::new(Vec::<Vec<u8>>::new());
|
||||
|
||||
let loader = |path: &str, from: &str, kind: FileKind, tmp: &mut _| {
|
||||
let mut physiscal_path = resolve(path, from, tmp)?;
|
||||
|
||||
match kind {
|
||||
FileKind::Module => {
|
||||
let id = {
|
||||
let mut seen = seen_modules.lock().unwrap();
|
||||
let len = seen.len();
|
||||
match seen.entry(physiscal_path) {
|
||||
hash_map::Entry::Occupied(entry) => {
|
||||
return Ok(*entry.get());
|
||||
}
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
physiscal_path = entry.insert_entry(len as _).key().clone();
|
||||
len
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if !physiscal_path.exists() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
format!("can't find file: {}", display_rel_path(&physiscal_path)),
|
||||
));
|
||||
}
|
||||
|
||||
tasks.push((id, physiscal_path));
|
||||
Ok(id)
|
||||
}
|
||||
FileKind::Embed => {
|
||||
let id = {
|
||||
let mut seen = seen_embeds.lock().unwrap();
|
||||
let len = seen.len();
|
||||
match seen.entry(physiscal_path) {
|
||||
hash_map::Entry::Occupied(entry) => {
|
||||
return Ok(*entry.get());
|
||||
}
|
||||
hash_map::Entry::Vacant(entry) => {
|
||||
physiscal_path = entry.insert_entry(len as _).key().clone();
|
||||
len
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let content = std::fs::read(&physiscal_path).map_err(|e| {
|
||||
io::Error::new(
|
||||
e.kind(),
|
||||
format!(
|
||||
"can't load embed file: {}: {e}",
|
||||
display_rel_path(&physiscal_path)
|
||||
),
|
||||
)
|
||||
})?;
|
||||
let mut embeds = embeds.lock().unwrap();
|
||||
if id as usize >= embeds.len() {
|
||||
embeds.resize(id as usize + 1, Default::default());
|
||||
}
|
||||
embeds[id as usize] = content;
|
||||
Ok(id)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let execute_task = |ctx: &mut _, (_, path): Task, tmp: &mut _| {
|
||||
let path = path.to_str().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("path contains invalid characters: {}", display_rel_path(&path)),
|
||||
)
|
||||
})?;
|
||||
Ok(Ast::new(path, std::fs::read_to_string(path)?, ctx, &mut |path, from, kind| {
|
||||
loader(path, from, kind, tmp).map_err(|e| e.to_string())
|
||||
}))
|
||||
};
|
||||
|
||||
let thread = || {
|
||||
let mut ctx = Ctx::default();
|
||||
let mut tmp = PathBuf::new();
|
||||
while let Some(task @ (indx, ..)) = tasks.pop() {
|
||||
let res = execute_task(&mut ctx, task, &mut tmp);
|
||||
let mut ast = ast.lock().unwrap();
|
||||
let len = ast.len().max(indx + 1);
|
||||
ast.resize_with(len, || Err(io::ErrorKind::InvalidData.into()));
|
||||
ast[indx] = res;
|
||||
}
|
||||
ctx.errors.into_inner()
|
||||
};
|
||||
|
||||
let path = Path::new(root).canonicalize().map_err(|e| {
|
||||
io::Error::new(e.kind(), format!("can't canonicalize root file path ({root})"))
|
||||
})?;
|
||||
seen_modules.lock().unwrap().insert(path.clone(), 0);
|
||||
tasks.push((0, path));
|
||||
|
||||
let errors = if extra_threads == 0 {
|
||||
thread()
|
||||
} else {
|
||||
std::thread::scope(|s| {
|
||||
(0..extra_threads + 1)
|
||||
.map(|_| s.spawn(thread))
|
||||
.collect::<Vec<_>>()
|
||||
.into_iter()
|
||||
.map(|t| t.join().unwrap())
|
||||
.collect::<String>()
|
||||
})
|
||||
};
|
||||
|
||||
Ok(Loaded {
|
||||
ast: ast.into_inner().unwrap().into_iter().collect::<io::Result<Vec<_>>>()?,
|
||||
embeds: embeds.into_inner().unwrap(),
|
||||
errors,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn display_rel_path(path: &(impl AsRef<OsStr> + ?Sized)) -> std::path::Display {
|
||||
static CWD: std::sync::LazyLock<PathBuf> =
|
||||
std::sync::LazyLock::new(|| std::env::current_dir().unwrap_or_default());
|
||||
std::path::Path::new(path).strip_prefix(&*CWD).unwrap_or(std::path::Path::new(path)).display()
|
||||
}
|
141
lang/src/fuzz.rs
141
lang/src/fuzz.rs
|
@ -1,141 +0,0 @@
|
|||
use {
|
||||
crate::{
|
||||
lexer::TokenKind,
|
||||
parser,
|
||||
son::{hbvm::HbvmBackend, Codegen, CodegenCtx},
|
||||
ty::Module,
|
||||
},
|
||||
alloc::string::String,
|
||||
core::{fmt::Write, hash::BuildHasher, ops::Range},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Rand(pub u64);
|
||||
|
||||
impl Rand {
|
||||
pub fn next(&mut self) -> u64 {
|
||||
self.0 = crate::FnvBuildHasher::default().hash_one(self.0);
|
||||
self.0
|
||||
}
|
||||
|
||||
pub fn range(&mut self, min: u64, max: u64) -> u64 {
|
||||
self.next() % (max - min) + min
|
||||
}
|
||||
|
||||
fn bool(&mut self) -> bool {
|
||||
self.next() % 2 == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct FuncGen {
|
||||
rand: Rand,
|
||||
buf: String,
|
||||
vars: u64,
|
||||
}
|
||||
|
||||
impl FuncGen {
|
||||
fn gen(&mut self, seed: u64) -> &str {
|
||||
self.rand = Rand(seed);
|
||||
self.buf.clear();
|
||||
self.buf.push_str("main := fn(): void ");
|
||||
self.block().unwrap();
|
||||
&self.buf
|
||||
}
|
||||
|
||||
fn block(&mut self) -> core::fmt::Result {
|
||||
let prev_vars = self.vars;
|
||||
self.buf.push('{');
|
||||
for _ in 0..self.rand.range(1, 10) {
|
||||
self.stmt()?;
|
||||
}
|
||||
self.buf.push('}');
|
||||
self.vars = prev_vars;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn stmt(&mut self) -> core::fmt::Result {
|
||||
match self.rand.range(0, 100) {
|
||||
0..4 => _ = self.block(),
|
||||
4..10 => {
|
||||
write!(self.buf, "var{} := ", self.vars)?;
|
||||
self.expr()?;
|
||||
self.vars += 1;
|
||||
}
|
||||
|
||||
10..20 if self.vars != 0 => {
|
||||
write!(self.buf, "var{} = ", self.rand.range(0, self.vars))?;
|
||||
self.expr()?;
|
||||
}
|
||||
20..23 => {
|
||||
self.buf.push_str("if ");
|
||||
self.expr()?;
|
||||
self.block()?;
|
||||
if self.rand.bool() {
|
||||
self.buf.push_str(" else ");
|
||||
self.block()?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.buf.push_str("return ");
|
||||
self.expr()?;
|
||||
}
|
||||
}
|
||||
|
||||
self.buf.push(';');
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expr(&mut self) -> core::fmt::Result {
|
||||
match self.rand.range(0, 100) {
|
||||
0..80 => {
|
||||
write!(self.buf, "{}", self.rand.next())
|
||||
}
|
||||
80..90 if self.vars != 0 => {
|
||||
write!(self.buf, "var{}", self.rand.range(0, self.vars))
|
||||
}
|
||||
80..100 => {
|
||||
self.expr()?;
|
||||
let ops = [
|
||||
TokenKind::Add,
|
||||
TokenKind::Sub,
|
||||
TokenKind::Mul,
|
||||
TokenKind::Div,
|
||||
TokenKind::Shl,
|
||||
TokenKind::Eq,
|
||||
TokenKind::Ne,
|
||||
TokenKind::Lt,
|
||||
TokenKind::Gt,
|
||||
TokenKind::Le,
|
||||
TokenKind::Ge,
|
||||
TokenKind::Band,
|
||||
TokenKind::Bor,
|
||||
TokenKind::Xor,
|
||||
TokenKind::Mod,
|
||||
TokenKind::Shr,
|
||||
];
|
||||
let op = ops[self.rand.range(0, ops.len() as u64) as usize];
|
||||
write!(self.buf, " {op} ")?;
|
||||
self.expr()
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fuzz(seed_range: Range<u64>) {
|
||||
let mut gen = FuncGen::default();
|
||||
let mut ctx = CodegenCtx::default();
|
||||
for i in seed_range {
|
||||
ctx.clear();
|
||||
let src = gen.gen(i);
|
||||
let parsed = parser::Ast::new("fuzz", src, &mut ctx.parser, &mut parser::no_loader);
|
||||
|
||||
assert!(ctx.parser.errors.get_mut().is_empty());
|
||||
|
||||
let mut backend = HbvmBackend::default();
|
||||
let mut cdg = Codegen::new(&mut backend, core::slice::from_ref(&parsed), &mut ctx);
|
||||
cdg.generate(Module::MAIN);
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
fn main() {
|
||||
hblang::fuzz::fuzz(0..1000000);
|
||||
}
|
|
@ -1,567 +0,0 @@
|
|||
const fn ascii_mask(chars: &[u8]) -> u128 {
|
||||
let mut eq = 0;
|
||||
let mut i = 0;
|
||||
while i < chars.len() {
|
||||
let b = chars[i];
|
||||
eq |= 1 << b;
|
||||
i += 1;
|
||||
}
|
||||
eq
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub struct Token {
|
||||
pub kind: TokenKind,
|
||||
pub start: u32,
|
||||
pub end: u32,
|
||||
}
|
||||
|
||||
impl Token {
|
||||
pub fn range(&self) -> core::ops::Range<usize> {
|
||||
self.start as usize..self.end as usize
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! gen_token_kind {
|
||||
($(
|
||||
#[$atts:meta])*
|
||||
$vis:vis enum $name:ident {
|
||||
#[patterns] $(
|
||||
$pattern:ident,
|
||||
)*
|
||||
#[keywords] $(
|
||||
$keyword:ident = $keyword_lit:literal,
|
||||
)*
|
||||
#[punkt] $(
|
||||
$punkt:ident = $punkt_lit:literal,
|
||||
)*
|
||||
#[ops] $(
|
||||
#[$prec:ident] $(
|
||||
$op:ident = $op_lit:literal $(=> $assign:ident)?,
|
||||
)*
|
||||
)*
|
||||
}
|
||||
) => {
|
||||
impl core::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
f.write_str(self.name())
|
||||
}
|
||||
}
|
||||
|
||||
impl $name {
|
||||
pub const OPS: &[Self] = &[$($(Self::$op),*),*];
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
let sf = unsafe { &*(self as *const _ as *const u8) } ;
|
||||
match *self {
|
||||
$( Self::$pattern => concat!('<', stringify!($pattern), '>'), )*
|
||||
$( Self::$keyword => stringify!($keyword_lit), )*
|
||||
$( Self::$punkt => stringify!($punkt_lit), )*
|
||||
$($( Self::$op => $op_lit,
|
||||
$(Self::$assign => concat!($op_lit, "="),)?)*)*
|
||||
_ => unsafe { core::str::from_utf8_unchecked(core::slice::from_ref(&sf)) },
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn precedence(&self) -> Option<u8> {
|
||||
Some(match self {
|
||||
$($(Self::$op => ${ignore($prec)} ${index(1)},
|
||||
$(Self::$assign => 0,)?)*)*
|
||||
_ => return None,
|
||||
} + 1)
|
||||
}
|
||||
|
||||
fn from_ident(ident: &[u8]) -> Self {
|
||||
match ident {
|
||||
$($keyword_lit => Self::$keyword,)*
|
||||
_ => Self::Ident,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
|
||||
#[repr(u8)]
|
||||
pub enum TokenKind {
|
||||
Not = b'!',
|
||||
DQuote = b'"',
|
||||
Pound = b'#',
|
||||
CtIdent = b'$',
|
||||
Mod = b'%',
|
||||
Band = b'&',
|
||||
Quote = b'\'',
|
||||
LParen = b'(',
|
||||
RParen = b')',
|
||||
Mul = b'*',
|
||||
Add = b'+',
|
||||
Comma = b',',
|
||||
Sub = b'-',
|
||||
Dot = b'.',
|
||||
Div = b'/',
|
||||
// Unused = 2-6
|
||||
Shl = b'<' - 5,
|
||||
// Unused = 8
|
||||
Shr = b'>' - 5,
|
||||
Colon = b':',
|
||||
Semi = b';',
|
||||
Lt = b'<',
|
||||
Assign = b'=',
|
||||
Gt = b'>',
|
||||
Que = b'?',
|
||||
Directive = b'@',
|
||||
|
||||
Comment,
|
||||
|
||||
Ident,
|
||||
Number,
|
||||
Float,
|
||||
Eof,
|
||||
|
||||
Ct,
|
||||
|
||||
Ctor,
|
||||
Tupl,
|
||||
TArrow,
|
||||
|
||||
Or,
|
||||
And,
|
||||
|
||||
// Unused = R-Z
|
||||
LBrack = b'[',
|
||||
BSlash = b'\\',
|
||||
RBrack = b']',
|
||||
Xor = b'^',
|
||||
Under = b'_',
|
||||
Tick = b'`',
|
||||
|
||||
Return,
|
||||
If,
|
||||
Match,
|
||||
Else,
|
||||
Loop,
|
||||
Break,
|
||||
Continue,
|
||||
Fn,
|
||||
Struct,
|
||||
Packed,
|
||||
Enum,
|
||||
True,
|
||||
False,
|
||||
Null,
|
||||
Idk,
|
||||
Die,
|
||||
|
||||
// Unused = a-z
|
||||
LBrace = b'{',
|
||||
Bor = b'|',
|
||||
RBrace = b'}',
|
||||
Tilde = b'~',
|
||||
|
||||
Decl = b':' + 128,
|
||||
Eq = b'=' + 128,
|
||||
Ne = b'!' + 128,
|
||||
Le = b'<' + 128,
|
||||
Ge = b'>' + 128,
|
||||
|
||||
BorAss = b'|' + 128,
|
||||
AddAss = b'+' + 128,
|
||||
SubAss = b'-' + 128,
|
||||
MulAss = b'*' + 128,
|
||||
DivAss = b'/' + 128,
|
||||
ModAss = b'%' + 128,
|
||||
XorAss = b'^' + 128,
|
||||
BandAss = b'&' + 128,
|
||||
ShrAss = b'>' - 5 + 128,
|
||||
ShlAss = b'<' - 5 + 128,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for TokenKind {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
core::fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl TokenKind {
|
||||
pub fn ass_op(self) -> Option<Self> {
|
||||
let id = (self as u8).saturating_sub(128);
|
||||
if ascii_mask(b"|+-*/%^&79") & (1u128 << id) == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(unsafe { core::mem::transmute::<u8, Self>(id) })
|
||||
}
|
||||
|
||||
pub fn is_comutative(self) -> bool {
|
||||
use TokenKind as S;
|
||||
matches!(self, S::Eq | S::Ne | S::Bor | S::Xor | S::Band | S::Add | S::Mul)
|
||||
}
|
||||
|
||||
pub fn is_compatison(self) -> bool {
|
||||
matches!(self, Self::Lt | Self::Gt | Self::Ge | Self::Le | Self::Ne | Self::Eq)
|
||||
}
|
||||
|
||||
pub fn is_supported_float_op(self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Add
|
||||
| Self::Sub
|
||||
| Self::Mul
|
||||
| Self::Div
|
||||
| Self::Eq
|
||||
| Self::Ne
|
||||
| Self::Le
|
||||
| Self::Ge
|
||||
| Self::Lt
|
||||
| Self::Gt
|
||||
)
|
||||
}
|
||||
|
||||
pub fn apply_binop(self, a: i64, b: i64, float: bool) -> i64 {
|
||||
if float {
|
||||
debug_assert!(self.is_supported_float_op());
|
||||
let [a, b] = [f64::from_bits(a as _), f64::from_bits(b as _)];
|
||||
let res = match self {
|
||||
Self::Add => a + b,
|
||||
Self::Sub => a - b,
|
||||
Self::Mul => a * b,
|
||||
Self::Div => a / b,
|
||||
Self::Eq => return (a == b) as i64,
|
||||
Self::Ne => return (a != b) as i64,
|
||||
Self::Lt => return (a < b) as i64,
|
||||
Self::Gt => return (a > b) as i64,
|
||||
Self::Le => return (a >= b) as i64,
|
||||
Self::Ge => return (a <= b) as i64,
|
||||
_ => todo!("floating point op: {self}"),
|
||||
};
|
||||
|
||||
return res.to_bits() as _;
|
||||
}
|
||||
|
||||
match self {
|
||||
Self::Add => a.wrapping_add(b),
|
||||
Self::Sub => a.wrapping_sub(b),
|
||||
Self::Mul => a.wrapping_mul(b),
|
||||
Self::Div if b == 0 => 0,
|
||||
Self::Div => a.wrapping_div(b),
|
||||
Self::Shl => a.wrapping_shl(b as _),
|
||||
Self::Eq => (a == b) as i64,
|
||||
Self::Ne => (a != b) as i64,
|
||||
Self::Lt => (a < b) as i64,
|
||||
Self::Gt => (a > b) as i64,
|
||||
Self::Le => (a >= b) as i64,
|
||||
Self::Ge => (a <= b) as i64,
|
||||
Self::Band => a & b,
|
||||
Self::Bor => a | b,
|
||||
Self::Xor => a ^ b,
|
||||
Self::Mod if b == 0 => 0,
|
||||
Self::Mod => a.wrapping_rem(b),
|
||||
Self::Shr => a.wrapping_shr(b as _),
|
||||
s => todo!("{s}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_homogenous(&self) -> bool {
|
||||
self.precedence() != Self::Eq.precedence()
|
||||
&& self.precedence() != Self::Gt.precedence()
|
||||
&& self.precedence() != Self::Eof.precedence()
|
||||
}
|
||||
|
||||
pub fn apply_unop(&self, value: i64, float: bool) -> i64 {
|
||||
match self {
|
||||
Self::Sub if float => (-f64::from_bits(value as _)).to_bits() as _,
|
||||
Self::Sub => value.wrapping_neg(),
|
||||
Self::Not => (value == 0) as _,
|
||||
Self::Float if float => value,
|
||||
Self::Float => (value as f64).to_bits() as _,
|
||||
Self::Number if float => f64::from_bits(value as _) as _,
|
||||
Self::Number => value,
|
||||
s => todo!("{s}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn closing(&self) -> Option<TokenKind> {
|
||||
Some(match self {
|
||||
Self::Ctor => Self::RBrace,
|
||||
Self::Tupl => Self::RParen,
|
||||
Self::LParen => Self::RParen,
|
||||
Self::LBrack => Self::RBrack,
|
||||
Self::LBrace => Self::RBrace,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
gen_token_kind! {
|
||||
pub enum TokenKind {
|
||||
#[patterns]
|
||||
CtIdent,
|
||||
Ident,
|
||||
Number,
|
||||
Float,
|
||||
Eof,
|
||||
Directive,
|
||||
#[keywords]
|
||||
Return = b"return",
|
||||
If = b"if",
|
||||
Match = b"match",
|
||||
Else = b"else",
|
||||
Loop = b"loop",
|
||||
Break = b"break",
|
||||
Continue = b"continue",
|
||||
Fn = b"fn",
|
||||
Struct = b"struct",
|
||||
Packed = b"packed",
|
||||
Enum = b"enum",
|
||||
True = b"true",
|
||||
False = b"false",
|
||||
Null = b"null",
|
||||
Idk = b"idk",
|
||||
Die = b"die",
|
||||
Under = b"_",
|
||||
#[punkt]
|
||||
Ctor = ".{",
|
||||
Tupl = ".(",
|
||||
TArrow = "=>",
|
||||
// #define OP: each `#[prec]` delimeters a level of precedence from lowest to highest
|
||||
#[ops]
|
||||
#[prec]
|
||||
// this also includess all `<op>=` tokens
|
||||
Decl = ":=",
|
||||
Assign = "=",
|
||||
#[prec]
|
||||
Or = "||",
|
||||
#[prec]
|
||||
And = "&&",
|
||||
#[prec]
|
||||
Bor = "|" => BorAss,
|
||||
#[prec]
|
||||
Xor = "^" => XorAss,
|
||||
#[prec]
|
||||
Band = "&" => BandAss,
|
||||
#[prec]
|
||||
Eq = "==",
|
||||
Ne = "!=",
|
||||
#[prec]
|
||||
Le = "<=",
|
||||
Ge = ">=",
|
||||
Lt = "<",
|
||||
Gt = ">",
|
||||
#[prec]
|
||||
Shl = "<<" => ShlAss,
|
||||
Shr = ">>" => ShrAss,
|
||||
#[prec]
|
||||
Add = "+" => AddAss,
|
||||
Sub = "-" => SubAss,
|
||||
#[prec]
|
||||
Mul = "*" => MulAss,
|
||||
Div = "/" => DivAss,
|
||||
Mod = "%" => ModAss,
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Lexer<'a> {
|
||||
pos: u32,
|
||||
source: &'a [u8],
|
||||
}
|
||||
|
||||
impl<'a> Lexer<'a> {
|
||||
pub fn new(input: &'a str) -> Self {
|
||||
Self::restore(input, 0)
|
||||
}
|
||||
|
||||
pub fn uses(input: &'a str) -> impl Iterator<Item = &'a str> {
|
||||
let mut s = Self::new(input);
|
||||
core::iter::from_fn(move || loop {
|
||||
let t = s.eat();
|
||||
if t.kind == TokenKind::Eof {
|
||||
return None;
|
||||
}
|
||||
if t.kind == TokenKind::Directive
|
||||
&& s.slice(t.range()) == "use"
|
||||
&& s.eat().kind == TokenKind::LParen
|
||||
{
|
||||
let t = s.eat();
|
||||
if t.kind == TokenKind::DQuote {
|
||||
return Some(&s.slice(t.range())[1..t.range().len() - 1]);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn restore(input: &'a str, pos: u32) -> Self {
|
||||
Self { pos, source: input.as_bytes() }
|
||||
}
|
||||
|
||||
pub fn source(&self) -> &'a str {
|
||||
unsafe { core::str::from_utf8_unchecked(self.source) }
|
||||
}
|
||||
|
||||
pub fn slice(&self, tok: core::ops::Range<usize>) -> &'a str {
|
||||
unsafe { core::str::from_utf8_unchecked(&self.source[tok]) }
|
||||
}
|
||||
|
||||
fn peek(&self) -> Option<u8> {
|
||||
if core::intrinsics::unlikely(self.pos >= self.source.len() as u32) {
|
||||
None
|
||||
} else {
|
||||
Some(unsafe { *self.source.get_unchecked(self.pos as usize) })
|
||||
}
|
||||
}
|
||||
|
||||
fn advance(&mut self) -> Option<u8> {
|
||||
let c = self.peek()?;
|
||||
self.pos += 1;
|
||||
Some(c)
|
||||
}
|
||||
|
||||
pub fn last(&mut self) -> Token {
|
||||
let mut token = self.eat();
|
||||
loop {
|
||||
let next = self.eat();
|
||||
if next.kind == TokenKind::Eof {
|
||||
break;
|
||||
}
|
||||
token = next;
|
||||
}
|
||||
token
|
||||
}
|
||||
|
||||
pub fn eat(&mut self) -> Token {
|
||||
use TokenKind as T;
|
||||
loop {
|
||||
let mut start = self.pos;
|
||||
|
||||
let Some(c) = self.advance() else {
|
||||
return Token { kind: T::Eof, start, end: self.pos };
|
||||
};
|
||||
|
||||
let advance_ident = |s: &mut Self| {
|
||||
while let Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | 127..) = s.peek() {
|
||||
s.advance();
|
||||
}
|
||||
};
|
||||
|
||||
let identity = |s: u8| unsafe { core::mem::transmute::<u8, T>(s) };
|
||||
|
||||
let kind = match c {
|
||||
..=b' ' => continue,
|
||||
b'0' if self.advance_if(b'x') => {
|
||||
while let Some(b'0'..=b'9' | b'A'..=b'F' | b'a'..=b'f') = self.peek() {
|
||||
self.advance();
|
||||
}
|
||||
T::Number
|
||||
}
|
||||
b'0' if self.advance_if(b'b') => {
|
||||
while let Some(b'0' | b'1') = self.peek() {
|
||||
self.advance();
|
||||
}
|
||||
T::Number
|
||||
}
|
||||
b'0' if self.advance_if(b'o') => {
|
||||
while let Some(b'0'..=b'7') = self.peek() {
|
||||
self.advance();
|
||||
}
|
||||
T::Number
|
||||
}
|
||||
b'0'..=b'9' => {
|
||||
while let Some(b'0'..=b'9') = self.peek() {
|
||||
self.advance();
|
||||
}
|
||||
|
||||
if self.advance_if(b'.') {
|
||||
while let Some(b'0'..=b'9') = self.peek() {
|
||||
self.advance();
|
||||
}
|
||||
T::Float
|
||||
} else {
|
||||
T::Number
|
||||
}
|
||||
}
|
||||
b'a'..=b'z' | b'A'..=b'Z' | b'_' | 127.. => {
|
||||
advance_ident(self);
|
||||
let ident = &self.source[start as usize..self.pos as usize];
|
||||
T::from_ident(ident)
|
||||
}
|
||||
b'"' | b'\'' => loop {
|
||||
match self.advance() {
|
||||
Some(b'\\') => _ = self.advance(),
|
||||
Some(nc) if nc == c => break identity(c),
|
||||
Some(_) => {}
|
||||
None => break T::Eof,
|
||||
}
|
||||
},
|
||||
b'/' if self.advance_if(b'/') => {
|
||||
while let Some(l) = self.peek()
|
||||
&& l != b'\n'
|
||||
{
|
||||
self.pos += 1;
|
||||
}
|
||||
|
||||
let end = self.source[..self.pos as usize]
|
||||
.iter()
|
||||
.rposition(|&b| !b.is_ascii_whitespace())
|
||||
.map_or(self.pos, |i| i as u32 + 1);
|
||||
|
||||
return Token { kind: T::Comment, start, end };
|
||||
}
|
||||
b'/' if self.advance_if(b'*') => {
|
||||
let mut depth = 1;
|
||||
while let Some(l) = self.advance() {
|
||||
match l {
|
||||
b'/' if self.advance_if(b'*') => depth += 1,
|
||||
b'*' if self.advance_if(b'/') => match depth {
|
||||
1 => break,
|
||||
_ => depth -= 1,
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
T::Comment
|
||||
}
|
||||
b'.' if self.advance_if(b'{') => T::Ctor,
|
||||
b'.' if self.advance_if(b'(') => T::Tupl,
|
||||
b'=' if self.advance_if(b'>') => T::TArrow,
|
||||
b'&' if self.advance_if(b'&') => T::And,
|
||||
b'|' if self.advance_if(b'|') => T::Or,
|
||||
b'$' if self.advance_if(b':') => T::Ct,
|
||||
b'@' | b'$' => {
|
||||
start += 1;
|
||||
advance_ident(self);
|
||||
identity(c)
|
||||
}
|
||||
b'<' | b'>' if self.advance_if(c) => {
|
||||
identity(c - 5 + 128 * self.advance_if(b'=') as u8)
|
||||
}
|
||||
b':' | b'=' | b'!' | b'<' | b'>' | b'|' | b'+' | b'-' | b'*' | b'/' | b'%'
|
||||
| b'^' | b'&'
|
||||
if self.advance_if(b'=') =>
|
||||
{
|
||||
identity(c + 128)
|
||||
}
|
||||
_ => identity(c),
|
||||
};
|
||||
|
||||
return Token { kind, start, end: self.pos };
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_if(&mut self, arg: u8) -> bool {
|
||||
if self.peek() == Some(arg) {
|
||||
self.advance();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn line_col(bytes: &[u8], pos: u32) -> (usize, usize) {
|
||||
bytes[..pos as usize]
|
||||
.split(|&b| b == b'\n')
|
||||
.map(<[u8]>::len)
|
||||
.enumerate()
|
||||
.last()
|
||||
.map(|(line, col)| (line + 1, col + 1))
|
||||
.unwrap_or((1, 1))
|
||||
}
|
1551
lang/src/lib.rs
1551
lang/src/lib.rs
File diff suppressed because it is too large
Load diff
|
@ -1,31 +0,0 @@
|
|||
#[cfg(feature = "std")]
|
||||
fn main() {
|
||||
use std::io::Write;
|
||||
|
||||
fn run(out: &mut Vec<u8>, warnings: &mut String) -> std::io::Result<()> {
|
||||
let args = std::env::args().collect::<Vec<_>>();
|
||||
let args = args.iter().map(String::as_str).collect::<Vec<_>>();
|
||||
|
||||
let opts = hblang::Options::from_args(&args, out)?;
|
||||
let file = args.iter().filter(|a| !a.starts_with('-')).nth(1).copied().unwrap_or("main.hb");
|
||||
|
||||
hblang::run_compiler(file, opts, out, warnings)
|
||||
}
|
||||
|
||||
log::set_logger(&hblang::fs::Logger).unwrap();
|
||||
log::set_max_level(log::LevelFilter::Error);
|
||||
|
||||
let mut out = Vec::new();
|
||||
let mut warnings = String::new();
|
||||
match run(&mut out, &mut warnings) {
|
||||
Ok(_) => {
|
||||
std::io::stderr().write_all(warnings.as_bytes()).unwrap();
|
||||
std::io::stdout().write_all(&out).unwrap()
|
||||
}
|
||||
Err(_) => {
|
||||
std::io::stderr().write_all(warnings.as_bytes()).unwrap();
|
||||
std::io::stderr().write_all(&out).unwrap();
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
1625
lang/src/parser.rs
1625
lang/src/parser.rs
File diff suppressed because it is too large
Load diff
5576
lang/src/son.rs
5576
lang/src/son.rs
File diff suppressed because it is too large
Load diff
1172
lang/src/son/hbvm.rs
1172
lang/src/son/hbvm.rs
File diff suppressed because it is too large
Load diff
|
@ -1,805 +0,0 @@
|
|||
use {
|
||||
super::{HbvmBackend, Nid, Nodes},
|
||||
crate::{
|
||||
parser,
|
||||
reg::{self, Reg},
|
||||
son::{debug_assert_matches, Kind, ARG_START, MEM, VOID},
|
||||
ty::{self, Arg, Loc},
|
||||
utils::BitSet,
|
||||
PLoc, Sig, Types,
|
||||
},
|
||||
alloc::{borrow::ToOwned, vec::Vec},
|
||||
core::{mem, ops::Range},
|
||||
hbbytecode::{self as instrs},
|
||||
};
|
||||
|
||||
impl HbvmBackend {
|
||||
pub(super) fn emit_body_code(
|
||||
&mut self,
|
||||
nodes: &Nodes,
|
||||
sig: Sig,
|
||||
tys: &Types,
|
||||
files: &[parser::Ast],
|
||||
) -> (usize, bool) {
|
||||
let tail = Function::build(nodes, tys, &mut self.ralloc, sig);
|
||||
nodes.basic_blocks();
|
||||
|
||||
let strip_load = |value| match nodes[value].kind {
|
||||
Kind::Load { .. } if nodes[value].ty.loc(tys) == Loc::Stack => nodes[value].inputs[1],
|
||||
_ => value,
|
||||
};
|
||||
|
||||
let mut res = mem::take(&mut self.ralloc);
|
||||
|
||||
Regalloc::run(nodes, &mut res);
|
||||
|
||||
'_open_function: {
|
||||
self.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, 0));
|
||||
self.emit(instrs::st(reg::RET_ADDR + tail as u8, reg::STACK_PTR, 0, 0));
|
||||
}
|
||||
|
||||
if let Some(PLoc::Ref(..)) = tys.parama(sig.ret).0 {
|
||||
res.node_to_reg[MEM as usize] = res.bundles.len() as u8 + 1;
|
||||
res.bundles.push(Bundle::new(0));
|
||||
}
|
||||
|
||||
let reg_offset = if tail { reg::RET + 12 } else { reg::RET_ADDR + 1 };
|
||||
let bundle_count = res.bundles.len() + (reg_offset as usize);
|
||||
|
||||
res.node_to_reg.iter_mut().filter(|r| **r != 0).for_each(|r| {
|
||||
if *r == u8::MAX {
|
||||
*r = 0
|
||||
} else {
|
||||
*r += reg_offset - 1;
|
||||
if tail && *r >= reg::RET_ADDR {
|
||||
*r += 1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
debug_assert!(!res
|
||||
.node_to_reg
|
||||
.iter()
|
||||
.any(|&a| a == reg::RET_ADDR || (reg::RET..reg_offset - 1).contains(&a)));
|
||||
|
||||
let atr = |allc: Nid| {
|
||||
let allc = strip_load(allc);
|
||||
debug_assert_eq!(
|
||||
nodes[allc].lock_rc.get(),
|
||||
0,
|
||||
"{:?} {}",
|
||||
nodes[allc],
|
||||
ty::Display::new(tys, files, nodes[allc].ty)
|
||||
);
|
||||
res.node_to_reg[allc as usize]
|
||||
};
|
||||
|
||||
let (retl, mut parama) = tys.parama(sig.ret);
|
||||
let mut typs = sig.args.args();
|
||||
let mut args = nodes[VOID].outputs[ARG_START..].iter();
|
||||
while let Some(aty) = typs.next(tys) {
|
||||
let Arg::Value(ty) = aty else { continue };
|
||||
let Some(loc) = parama.next(ty, tys) else { continue };
|
||||
let &arg = args.next().unwrap();
|
||||
let (rg, size) = match loc {
|
||||
PLoc::WideReg(rg, size) => (rg, size),
|
||||
PLoc::Reg(rg, size) if ty.loc(tys) == Loc::Stack => (rg, size),
|
||||
PLoc::Reg(r, ..) | PLoc::Ref(r, ..) => {
|
||||
self.emit_cp(atr(arg), r);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
self.emit(instrs::st(rg, reg::STACK_PTR, self.offsets[arg as usize] as _, size));
|
||||
if nodes.is_unlocked(arg) {
|
||||
self.emit(instrs::addi64(rg, reg::STACK_PTR, self.offsets[arg as usize] as _));
|
||||
}
|
||||
self.emit_cp(atr(arg), rg);
|
||||
}
|
||||
|
||||
let mut alloc_buf = vec![];
|
||||
for (i, block) in res.blocks.iter().enumerate() {
|
||||
self.offsets[block.entry as usize] = self.code.len() as _;
|
||||
for &nid in &res.instrs[block.range()] {
|
||||
if nid == VOID {
|
||||
continue;
|
||||
}
|
||||
|
||||
let node = &nodes[nid];
|
||||
alloc_buf.clear();
|
||||
|
||||
let atr = |allc: Nid| {
|
||||
let allc = strip_load(allc);
|
||||
debug_assert_eq!(
|
||||
nodes[allc].lock_rc.get(),
|
||||
0,
|
||||
"{:?} {}",
|
||||
nodes[allc],
|
||||
ty::Display::new(tys, files, nodes[allc].ty)
|
||||
);
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(
|
||||
res.marked.contains(&(allc, nid))
|
||||
|| nid == allc
|
||||
|| nodes.is_hard_zero(allc)
|
||||
|| allc == MEM
|
||||
|| matches!(node.kind, Kind::Loop | Kind::Region),
|
||||
"{nid} {:?}\n{allc} {:?}",
|
||||
nodes[nid],
|
||||
nodes[allc]
|
||||
);
|
||||
res.node_to_reg[allc as usize]
|
||||
};
|
||||
|
||||
let mut is_next_block = false;
|
||||
match node.kind {
|
||||
Kind::If => {
|
||||
let &[_, cnd] = node.inputs.as_slice() else { unreachable!() };
|
||||
if nodes.cond_op(cnd).is_some() {
|
||||
let &[_, lh, rh] = nodes[cnd].inputs.as_slice() else { unreachable!() };
|
||||
alloc_buf.extend([atr(lh), atr(rh)]);
|
||||
} else {
|
||||
alloc_buf.push(atr(cnd));
|
||||
}
|
||||
}
|
||||
Kind::Loop | Kind::Region => {
|
||||
let index = node
|
||||
.inputs
|
||||
.iter()
|
||||
.position(|&n| block.entry == nodes.idom_of(n))
|
||||
.unwrap()
|
||||
+ 1;
|
||||
|
||||
let mut moves = vec![];
|
||||
for &out in node.outputs.iter() {
|
||||
if nodes[out].is_data_phi() {
|
||||
let src = nodes[out].inputs[index];
|
||||
if atr(out) != atr(src) {
|
||||
moves.push([atr(out), atr(src), 0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug_assert_eq!(moves.len(), {
|
||||
moves.sort_unstable();
|
||||
moves.dedup();
|
||||
moves.len()
|
||||
});
|
||||
|
||||
moves.sort_unstable_by(|[aa, ab, _], [ba, bb, _]| {
|
||||
if aa == bb && ab == ba {
|
||||
core::cmp::Ordering::Equal
|
||||
} else if aa == bb {
|
||||
core::cmp::Ordering::Greater
|
||||
} else {
|
||||
core::cmp::Ordering::Less
|
||||
}
|
||||
});
|
||||
|
||||
moves.dedup_by(|[aa, ab, _], [ba, bb, kind]| {
|
||||
if aa == bb && ab == ba {
|
||||
*kind = 1;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
for [dst, src, kind] in moves {
|
||||
if kind == 0 {
|
||||
self.emit(instrs::cp(dst, src));
|
||||
} else {
|
||||
self.emit(instrs::swa(dst, src));
|
||||
}
|
||||
}
|
||||
is_next_block = res.backrefs[nid as usize] as usize == i + 1;
|
||||
}
|
||||
Kind::Return { .. } => {
|
||||
let &[_, ret, ..] = node.inputs.as_slice() else { unreachable!() };
|
||||
match retl {
|
||||
Some(PLoc::Reg(r, _)) if sig.ret.loc(tys) == Loc::Reg => {
|
||||
alloc_buf.push(atr(ret));
|
||||
self.emit(instrs::cp(r, atr(ret)));
|
||||
}
|
||||
Some(PLoc::Ref(..)) => alloc_buf.extend([atr(ret), atr(MEM)]),
|
||||
Some(_) => alloc_buf.push(atr(ret)),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
Kind::Die => {}
|
||||
Kind::CInt { .. } => alloc_buf.push(atr(nid)),
|
||||
Kind::UnOp { .. } => alloc_buf.extend([atr(nid), atr(node.inputs[1])]),
|
||||
Kind::BinOp { op } => {
|
||||
let &[.., lhs, rhs] = node.inputs.as_slice() else { unreachable!() };
|
||||
|
||||
if let Kind::CInt { .. } = nodes[rhs].kind
|
||||
&& nodes.is_locked(rhs)
|
||||
&& op.imm_binop(node.ty).is_some()
|
||||
{
|
||||
alloc_buf.extend([atr(nid), atr(lhs)]);
|
||||
} else {
|
||||
alloc_buf.extend([atr(nid), atr(lhs), atr(rhs)]);
|
||||
}
|
||||
}
|
||||
Kind::Call { args, .. } => {
|
||||
let (ret, mut parama) = tys.parama(node.ty);
|
||||
if ret.is_some() {
|
||||
alloc_buf.push(atr(nid));
|
||||
}
|
||||
let mut args = args.args();
|
||||
let mut allocs = node.inputs[1..].iter();
|
||||
while let Some(arg) = args.next(tys) {
|
||||
let Arg::Value(ty) = arg else { continue };
|
||||
let Some(loc) = parama.next(ty, tys) else { continue };
|
||||
|
||||
let arg = *allocs.next().unwrap();
|
||||
alloc_buf.push(atr(arg));
|
||||
match loc {
|
||||
PLoc::Reg(..) if ty.loc(tys) == Loc::Stack => {}
|
||||
PLoc::WideReg(..) => alloc_buf.push(0),
|
||||
PLoc::Reg(r, ..) | PLoc::Ref(r, ..) => {
|
||||
self.emit(instrs::cp(r, atr(arg)))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if node.ty.loc(tys) == Loc::Stack {
|
||||
alloc_buf.push(atr(*node.inputs.last().unwrap()));
|
||||
}
|
||||
|
||||
if let Some(PLoc::Ref(r, ..)) = ret {
|
||||
self.emit(instrs::cp(r, *alloc_buf.last().unwrap()))
|
||||
}
|
||||
}
|
||||
Kind::Stck | Kind::Global { .. } => alloc_buf.push(atr(nid)),
|
||||
Kind::Load => {
|
||||
let (region, _) = nodes.strip_offset(node.inputs[1], node.ty, tys);
|
||||
if node.ty.loc(tys) != Loc::Stack {
|
||||
alloc_buf.push(atr(nid));
|
||||
match nodes[region].kind {
|
||||
Kind::Stck => {}
|
||||
_ => alloc_buf.push(atr(region)),
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::Stre if node.inputs[1] == VOID => {}
|
||||
Kind::Stre => {
|
||||
let (region, _) = nodes.strip_offset(node.inputs[2], node.ty, tys);
|
||||
match nodes[region].kind {
|
||||
Kind::Stck if node.ty.loc(tys) == Loc::Reg => {
|
||||
alloc_buf.push(atr(node.inputs[1]))
|
||||
}
|
||||
_ => alloc_buf.extend([atr(region), atr(node.inputs[1])]),
|
||||
}
|
||||
}
|
||||
Kind::Mem => {
|
||||
self.emit(instrs::cp(atr(MEM), reg::RET));
|
||||
continue;
|
||||
}
|
||||
Kind::Arg => {
|
||||
continue;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
self.emit_instr(super::InstrCtx {
|
||||
nid,
|
||||
sig,
|
||||
is_next_block,
|
||||
is_last_block: i == res.blocks.len() - 1,
|
||||
retl,
|
||||
allocs: &alloc_buf,
|
||||
nodes,
|
||||
tys,
|
||||
files,
|
||||
});
|
||||
|
||||
if let Kind::Call { .. } = node.kind {
|
||||
let (ret, ..) = tys.parama(node.ty);
|
||||
|
||||
match ret {
|
||||
Some(PLoc::WideReg(..)) => {}
|
||||
Some(PLoc::Reg(..)) if node.ty.loc(tys) == Loc::Stack => {}
|
||||
Some(PLoc::Reg(r, ..)) => self.emit_cp(atr(nid), r),
|
||||
None | Some(PLoc::Ref(..)) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.ralloc = res;
|
||||
|
||||
debug_assert!(bundle_count < reg::STACK_PTR as usize, "TODO: spill memory");
|
||||
debug_assert_eq!(
|
||||
self.ralloc
|
||||
.node_to_reg
|
||||
.iter()
|
||||
.filter(|&&r| r
|
||||
> (bundle_count as u8
|
||||
+ (tail && bundle_count > (reg::RET_ADDR) as usize) as u8))
|
||||
.copied()
|
||||
.collect::<Vec<_>>(),
|
||||
vec![],
|
||||
"{bundle_count}"
|
||||
);
|
||||
(
|
||||
if tail {
|
||||
bundle_count.saturating_sub(reg::RET_ADDR as _)
|
||||
} else {
|
||||
self.ralloc.bundles.len()
|
||||
},
|
||||
tail,
|
||||
)
|
||||
}
|
||||
|
||||
fn emit_cp(&mut self, dst: Reg, src: Reg) {
|
||||
if dst != 0 {
|
||||
self.emit(instrs::cp(dst, src));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Function<'a> {
|
||||
sig: Sig,
|
||||
tail: bool,
|
||||
nodes: &'a Nodes,
|
||||
tys: &'a Types,
|
||||
func: &'a mut Res,
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for Function<'_> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
for block in &self.func.blocks {
|
||||
writeln!(f, "{:?}", self.nodes[block.entry].kind)?;
|
||||
for &instr in &self.func.instrs[block.range()] {
|
||||
writeln!(f, "{:?}", self.nodes[instr].kind)?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Function<'a> {
|
||||
fn build(nodes: &'a Nodes, tys: &'a Types, func: &'a mut Res, sig: Sig) -> bool {
|
||||
func.blocks.clear();
|
||||
func.instrs.clear();
|
||||
func.backrefs.resize(nodes.values.len(), u16::MAX);
|
||||
func.visited.clear(nodes.values.len());
|
||||
let mut s = Self { tail: true, nodes, tys, sig, func };
|
||||
s.emit_node(VOID);
|
||||
debug_assert!(s.func.blocks.array_chunks().all(|[a, b]| a.end == b.start));
|
||||
log::info!("{s:?}");
|
||||
s.tail
|
||||
}
|
||||
|
||||
fn add_block(&mut self, entry: Nid) {
|
||||
self.func.blocks.push(Block {
|
||||
start: self.func.instrs.len() as _,
|
||||
end: self.func.instrs.len() as _,
|
||||
entry,
|
||||
});
|
||||
self.func.backrefs[entry as usize] = self.func.blocks.len() as u16 - 1;
|
||||
}
|
||||
|
||||
fn close_block(&mut self, exit: Nid) {
|
||||
if !matches!(self.nodes[exit].kind, Kind::Loop | Kind::Region) {
|
||||
self.add_instr(exit);
|
||||
} else {
|
||||
self.func.instrs.push(exit);
|
||||
}
|
||||
let prev = self.func.blocks.last_mut().unwrap();
|
||||
prev.end = self.func.instrs.len() as _;
|
||||
}
|
||||
|
||||
fn add_instr(&mut self, nid: Nid) {
|
||||
debug_assert_ne!(self.nodes[nid].kind, Kind::Loop);
|
||||
self.func.backrefs[nid as usize] = self.func.instrs.len() as u16;
|
||||
self.func.instrs.push(nid);
|
||||
}
|
||||
|
||||
fn emit_node(&mut self, nid: Nid) {
|
||||
if matches!(self.nodes[nid].kind, Kind::Region | Kind::Loop) {
|
||||
match (self.nodes[nid].kind, self.func.visited.set(nid)) {
|
||||
(Kind::Loop, false) | (Kind::Region, true) => {
|
||||
self.close_block(nid);
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else if !self.func.visited.set(nid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.nodes.is_never_used(nid, self.tys) {
|
||||
self.nodes.lock(nid);
|
||||
return;
|
||||
}
|
||||
|
||||
let mut node = self.nodes[nid].clone();
|
||||
match node.kind {
|
||||
Kind::Start => {
|
||||
debug_assert_matches!(self.nodes[node.outputs[0]].kind, Kind::Entry);
|
||||
self.add_block(VOID);
|
||||
self.emit_node(node.outputs[0])
|
||||
}
|
||||
Kind::If => {
|
||||
let &[_, cnd] = node.inputs.as_slice() else { unreachable!() };
|
||||
let &[mut then, mut else_] = node.outputs.as_slice() else { unreachable!() };
|
||||
|
||||
if let Some((_, swapped)) = self.nodes.cond_op(cnd) {
|
||||
if swapped {
|
||||
mem::swap(&mut then, &mut else_);
|
||||
}
|
||||
} else {
|
||||
mem::swap(&mut then, &mut else_);
|
||||
}
|
||||
|
||||
self.close_block(nid);
|
||||
self.emit_node(then);
|
||||
self.emit_node(else_);
|
||||
}
|
||||
Kind::Region | Kind::Loop => {
|
||||
self.close_block(nid);
|
||||
self.add_block(nid);
|
||||
self.nodes.reschedule_block(nid, &mut node.outputs);
|
||||
for o in node.outputs.into_iter().rev() {
|
||||
self.emit_node(o);
|
||||
}
|
||||
}
|
||||
Kind::Return { .. } | Kind::Die => {
|
||||
self.close_block(nid);
|
||||
self.emit_node(node.outputs[0]);
|
||||
}
|
||||
Kind::Entry => {
|
||||
let (ret, mut parama) = self.tys.parama(self.sig.ret);
|
||||
|
||||
if let Some(PLoc::Ref(..)) = ret {
|
||||
self.add_instr(MEM);
|
||||
}
|
||||
|
||||
let mut typs = self.sig.args.args();
|
||||
#[expect(clippy::unnecessary_to_owned)]
|
||||
let mut args = self.nodes[VOID].outputs[ARG_START..].to_owned().into_iter();
|
||||
while let Some(ty) = typs.next_value(self.tys) {
|
||||
let arg = args.next().unwrap();
|
||||
debug_assert_eq!(self.nodes[arg].kind, Kind::Arg);
|
||||
match parama.next(ty, self.tys) {
|
||||
None => {}
|
||||
Some(_) => self.add_instr(arg),
|
||||
}
|
||||
}
|
||||
|
||||
self.nodes.reschedule_block(nid, &mut node.outputs);
|
||||
for o in node.outputs.into_iter().rev() {
|
||||
self.emit_node(o);
|
||||
}
|
||||
}
|
||||
Kind::Then | Kind::Else => {
|
||||
self.add_block(nid);
|
||||
self.nodes.reschedule_block(nid, &mut node.outputs);
|
||||
for o in node.outputs.into_iter().rev() {
|
||||
self.emit_node(o);
|
||||
}
|
||||
}
|
||||
Kind::Call { func, .. } => {
|
||||
self.tail &= func == ty::Func::ECA;
|
||||
|
||||
self.add_instr(nid);
|
||||
|
||||
self.nodes.reschedule_block(nid, &mut node.outputs);
|
||||
for o in node.outputs.into_iter().rev() {
|
||||
if self.nodes[o].inputs[0] == nid
|
||||
|| (matches!(self.nodes[o].kind, Kind::Loop | Kind::Region)
|
||||
&& self.nodes[o].inputs[1] == nid)
|
||||
{
|
||||
self.emit_node(o);
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::CInt { value: 0 } if self.nodes.is_hard_zero(nid) => {}
|
||||
Kind::CInt { .. }
|
||||
| Kind::BinOp { .. }
|
||||
| Kind::UnOp { .. }
|
||||
| Kind::Global { .. }
|
||||
| Kind::Load { .. }
|
||||
| Kind::Stre
|
||||
| Kind::Stck => self.add_instr(nid),
|
||||
Kind::End | Kind::Phi | Kind::Arg | Kind::Mem | Kind::Loops | Kind::Join => {}
|
||||
Kind::Assert { .. } => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Nodes {
|
||||
fn vreg_count(&self) -> usize {
|
||||
self.values.len()
|
||||
}
|
||||
|
||||
fn use_block_of(&self, inst: Nid, uinst: Nid) -> Nid {
|
||||
let mut block = self.use_block(inst, uinst, None);
|
||||
while !self[block].kind.starts_basic_block() {
|
||||
block = self.idom(block, None);
|
||||
}
|
||||
block
|
||||
}
|
||||
|
||||
fn phi_inputs_of(&self, nid: Nid) -> impl Iterator<Item = [Nid; 3]> + use<'_> {
|
||||
match self[nid].kind {
|
||||
Kind::Region | Kind::Loop => Some({
|
||||
self[nid]
|
||||
.outputs
|
||||
.as_slice()
|
||||
.iter()
|
||||
.filter(|&&n| self[n].is_data_phi())
|
||||
.map(|&n| [n, self[n].inputs[1], self[n].inputs[2]])
|
||||
})
|
||||
.into_iter()
|
||||
.flatten(),
|
||||
_ => None.into_iter().flatten(),
|
||||
}
|
||||
}
|
||||
|
||||
fn idom_of(&self, mut nid: Nid) -> Nid {
|
||||
while !self[nid].kind.starts_basic_block() {
|
||||
nid = self.idom(nid, None);
|
||||
}
|
||||
nid
|
||||
}
|
||||
|
||||
fn uses_of(&self, nid: Nid) -> impl Iterator<Item = (Nid, Nid)> + use<'_> {
|
||||
if self[nid].kind.is_cfg() && !matches!(self[nid].kind, Kind::Call { .. }) {
|
||||
return None.into_iter().flatten();
|
||||
}
|
||||
|
||||
Some(
|
||||
self[nid]
|
||||
.outputs
|
||||
.iter()
|
||||
.filter(move |&&n| self.is_data_dep(nid, n))
|
||||
.map(move |n| self.this_or_delegates(nid, n))
|
||||
.flat_map(|(p, ls)| ls.iter().map(move |l| (p, l)))
|
||||
.filter(|&(o, &n)| self.is_data_dep(o, n))
|
||||
.map(|(p, &n)| (self.use_block_of(p, n), n))
|
||||
.inspect(|&(_, n)| debug_assert_eq!(self[n].lock_rc.get(), 0)),
|
||||
)
|
||||
.into_iter()
|
||||
.flatten()
|
||||
}
|
||||
}
|
||||
|
||||
struct Regalloc<'a> {
|
||||
nodes: &'a Nodes,
|
||||
res: &'a mut Res,
|
||||
}
|
||||
|
||||
impl<'a> Regalloc<'a> {
|
||||
fn instr_of(&self, nid: Nid) -> Option<Nid> {
|
||||
if self.nodes[nid].kind == Kind::Phi || self.nodes.is_locked(nid) {
|
||||
return None;
|
||||
}
|
||||
debug_assert_ne!(self.res.backrefs[nid as usize], Nid::MAX, "{:?}", self.nodes[nid]);
|
||||
Some(self.res.backrefs[nid as usize])
|
||||
}
|
||||
|
||||
fn block_of(&self, nid: Nid) -> Nid {
|
||||
debug_assert!(self.nodes[nid].kind.starts_basic_block());
|
||||
self.res.backrefs[nid as usize]
|
||||
}
|
||||
|
||||
fn run(ctx: &'a Nodes, res: &'a mut Res) {
|
||||
Self { nodes: ctx, res }.run_low();
|
||||
}
|
||||
|
||||
fn run_low(&mut self) {
|
||||
self.res.bundles.clear();
|
||||
self.res.node_to_reg.clear();
|
||||
#[cfg(debug_assertions)]
|
||||
self.res.marked.clear();
|
||||
self.res.node_to_reg.resize(self.nodes.vreg_count(), 0);
|
||||
|
||||
debug_assert!(self.res.dfs_buf.is_empty());
|
||||
|
||||
let mut bundle = Bundle::new(self.res.instrs.len());
|
||||
self.res.visited.clear(self.nodes.values.len());
|
||||
|
||||
for i in (0..self.res.blocks.len()).rev() {
|
||||
for [a, rest @ ..] in self.nodes.phi_inputs_of(self.res.blocks[i].entry) {
|
||||
if self.res.visited.set(a) {
|
||||
self.append_bundle(a, &mut bundle, None);
|
||||
}
|
||||
|
||||
for r in rest {
|
||||
if !self.res.visited.set(r) {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.append_bundle(
|
||||
r,
|
||||
&mut bundle,
|
||||
Some(self.res.node_to_reg[a as usize] as usize - 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let instrs = mem::take(&mut self.res.instrs);
|
||||
for &inst in &instrs {
|
||||
if self.nodes[inst].has_no_value() || self.res.visited.get(inst) || inst == 0 {
|
||||
continue;
|
||||
}
|
||||
self.append_bundle(inst, &mut bundle, None);
|
||||
}
|
||||
self.res.instrs = instrs;
|
||||
}
|
||||
|
||||
fn collect_bundle(&mut self, inst: Nid, into: &mut Bundle) {
|
||||
let dom = self.nodes.idom_of(inst);
|
||||
self.res.dfs_seem.clear(self.nodes.values.len());
|
||||
for (cursor, uinst) in self.nodes.uses_of(inst) {
|
||||
if !self.res.dfs_seem.set(uinst) {
|
||||
continue;
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
debug_assert!(self.res.marked.insert((inst, uinst)));
|
||||
|
||||
self.reverse_cfg_dfs(cursor, dom, |s, n, b| {
|
||||
let mut range = b.range();
|
||||
debug_assert!(range.start < range.end);
|
||||
range.start = range.start.max(s.instr_of(inst).map_or(0, |n| n + 1) as usize);
|
||||
debug_assert!(
|
||||
range.start < range.end,
|
||||
"{:?} {:?} {n} {inst}",
|
||||
range,
|
||||
self.nodes[inst]
|
||||
);
|
||||
let new = range.end.min(
|
||||
s.instr_of(uinst)
|
||||
.filter(|_| {
|
||||
n == cursor
|
||||
&& self.nodes.loop_depth(dom, None)
|
||||
== self.nodes.loop_depth(cursor, None)
|
||||
})
|
||||
.map_or(Nid::MAX, |n| n + 1) as usize,
|
||||
);
|
||||
|
||||
range.end = new;
|
||||
debug_assert!(range.start < range.end, "{:?} {inst} {uinst}", range);
|
||||
|
||||
into.add(range);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn append_bundle(&mut self, inst: Nid, tmp: &mut Bundle, prefered: Option<usize>) {
|
||||
self.collect_bundle(inst, tmp);
|
||||
|
||||
if tmp.is_empty() {
|
||||
self.res.node_to_reg[inst as usize] = u8::MAX;
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(prefered) = prefered
|
||||
&& !self.res.bundles[prefered].overlaps(tmp)
|
||||
{
|
||||
self.res.bundles[prefered].merge(tmp);
|
||||
tmp.clear();
|
||||
self.res.node_to_reg[inst as usize] = prefered as Reg + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
match self.res.bundles.iter_mut().enumerate().find(|(_, b)| !b.overlaps(tmp)) {
|
||||
Some((i, other)) => {
|
||||
other.merge(tmp);
|
||||
tmp.clear();
|
||||
self.res.node_to_reg[inst as usize] = i as Reg + 1;
|
||||
}
|
||||
None => {
|
||||
self.res.bundles.push(tmp.take());
|
||||
self.res.node_to_reg[inst as usize] = self.res.bundles.len() as Reg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reverse_cfg_dfs(
|
||||
&mut self,
|
||||
from: Nid,
|
||||
until: Nid,
|
||||
mut each: impl FnMut(&mut Self, Nid, Block),
|
||||
) {
|
||||
debug_assert!(self.res.dfs_buf.is_empty());
|
||||
self.res.dfs_buf.push(from);
|
||||
|
||||
debug_assert!(self.nodes.dominates(until, from, None));
|
||||
|
||||
while let Some(nid) = self.res.dfs_buf.pop() {
|
||||
debug_assert!(
|
||||
self.nodes.dominates(until, nid, None),
|
||||
"{until} {:?}",
|
||||
self.nodes[until]
|
||||
);
|
||||
each(self, nid, self.res.blocks[self.block_of(nid) as usize]);
|
||||
if nid == until {
|
||||
continue;
|
||||
}
|
||||
match self.nodes[nid].kind {
|
||||
Kind::Then | Kind::Else | Kind::Region | Kind::Loop => {
|
||||
for &n in self.nodes[nid].inputs.iter() {
|
||||
if self.nodes[n].kind == Kind::Loops {
|
||||
continue;
|
||||
}
|
||||
let d = self.nodes.idom_of(n);
|
||||
if self.res.dfs_seem.set(d) {
|
||||
self.res.dfs_buf.push(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
Kind::Start => {}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub(super) struct Res {
|
||||
blocks: Vec<Block>,
|
||||
instrs: Vec<Nid>,
|
||||
backrefs: Vec<u16>,
|
||||
|
||||
bundles: Vec<Bundle>,
|
||||
node_to_reg: Vec<Reg>,
|
||||
|
||||
visited: BitSet,
|
||||
dfs_buf: Vec<Nid>,
|
||||
dfs_seem: BitSet,
|
||||
#[cfg(debug_assertions)]
|
||||
marked: hashbrown::HashSet<(Nid, Nid), crate::FnvBuildHasher>,
|
||||
}
|
||||
|
||||
struct Bundle {
|
||||
taken: Vec<bool>,
|
||||
}
|
||||
|
||||
impl Bundle {
|
||||
fn new(size: usize) -> Self {
|
||||
Self { taken: vec![false; size] }
|
||||
}
|
||||
|
||||
fn add(&mut self, range: Range<usize>) {
|
||||
self.taken[range].fill(true);
|
||||
}
|
||||
|
||||
fn overlaps(&self, other: &Self) -> bool {
|
||||
self.taken.iter().zip(other.taken.iter()).any(|(a, b)| a & b)
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: &Self) {
|
||||
debug_assert!(!self.overlaps(other));
|
||||
self.taken.iter_mut().zip(other.taken.iter()).for_each(|(a, b)| *a |= *b);
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.taken.fill(false);
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> bool {
|
||||
!self.taken.contains(&true)
|
||||
}
|
||||
|
||||
fn take(&mut self) -> Self {
|
||||
mem::replace(self, Self::new(self.taken.len()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct Block {
|
||||
start: u16,
|
||||
end: u16,
|
||||
entry: Nid,
|
||||
}
|
||||
|
||||
impl Block {
|
||||
pub fn range(&self) -> Range<usize> {
|
||||
self.start as usize..self.end as usize
|
||||
}
|
||||
}
|
|
@ -1,649 +0,0 @@
|
|||
#![expect(dead_code)]
|
||||
use {
|
||||
alloc::alloc,
|
||||
core::{
|
||||
alloc::Layout,
|
||||
fmt::Debug,
|
||||
hint::unreachable_unchecked,
|
||||
marker::PhantomData,
|
||||
mem::MaybeUninit,
|
||||
ops::{Deref, DerefMut, Not},
|
||||
ptr::Unique,
|
||||
},
|
||||
};
|
||||
|
||||
fn decide(b: bool, name: &'static str) -> Result<(), &'static str> {
|
||||
b.then_some(()).ok_or(name)
|
||||
}
|
||||
|
||||
pub fn is_snake_case(str: &str) -> Result<(), &'static str> {
|
||||
decide(str.bytes().all(|c| matches!(c, b'a'..=b'z' | b'0'..=b'9' | b'_')), "snake_case")
|
||||
}
|
||||
|
||||
pub fn is_pascal_case(str: &str) -> Result<(), &'static str> {
|
||||
decide(
|
||||
str.as_bytes()[0].is_ascii_uppercase() && str.bytes().all(|c| c.is_ascii_alphanumeric()),
|
||||
"PascalCase",
|
||||
)
|
||||
}
|
||||
|
||||
pub fn is_screaming_case(str: &str) -> Result<(), &'static str> {
|
||||
decide(str.bytes().all(|c| matches!(c, b'A'..=b'Z' | b'0'..=b'9' | b'_')), "SCREAMING_CASE")
|
||||
}
|
||||
|
||||
type Nid = u16;
|
||||
|
||||
pub union BitSet {
|
||||
inline: usize,
|
||||
alloced: Unique<AllocedBitSet>,
|
||||
}
|
||||
|
||||
impl Debug for BitSet {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_list().entries(self.iter()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for BitSet {
|
||||
fn clone(&self) -> Self {
|
||||
if self.is_inline() {
|
||||
Self { inline: unsafe { self.inline } }
|
||||
} else {
|
||||
let (data, _) = self.data_and_len();
|
||||
let (layout, _) = Self::layout(data.len());
|
||||
unsafe {
|
||||
let ptr = alloc::alloc(layout);
|
||||
ptr.copy_from_nonoverlapping(self.alloced.as_ptr() as _, layout.size());
|
||||
Self { alloced: Unique::new_unchecked(ptr as _) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BitSet {
|
||||
fn drop(&mut self) {
|
||||
if !self.is_inline() {
|
||||
unsafe {
|
||||
let cap = self.alloced.as_ref().cap;
|
||||
alloc::dealloc(self.alloced.as_ptr() as _, Self::layout(cap).0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BitSet {
|
||||
fn default() -> Self {
|
||||
Self { inline: Self::FLAG }
|
||||
}
|
||||
}
|
||||
|
||||
impl BitSet {
|
||||
const FLAG: usize = 1 << (Self::UNIT - 1);
|
||||
const INLINE_ELEMS: usize = Self::UNIT - 1;
|
||||
const UNIT: usize = core::mem::size_of::<usize>() * 8;
|
||||
|
||||
pub fn with_capacity(len: usize) -> Self {
|
||||
let mut s = Self::default();
|
||||
s.reserve(len);
|
||||
s
|
||||
}
|
||||
|
||||
fn is_inline(&self) -> bool {
|
||||
unsafe { self.inline & Self::FLAG != 0 }
|
||||
}
|
||||
|
||||
fn data_and_len(&self) -> (&[usize], usize) {
|
||||
unsafe {
|
||||
if self.is_inline() {
|
||||
(core::slice::from_ref(&self.inline), Self::INLINE_ELEMS)
|
||||
} else {
|
||||
let small_vec = self.alloced.as_ref();
|
||||
(
|
||||
core::slice::from_raw_parts(
|
||||
&small_vec.data as *const _ as *const usize,
|
||||
small_vec.cap,
|
||||
),
|
||||
small_vec.cap * core::mem::size_of::<usize>() * 8,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn data_mut_and_len(&mut self) -> (&mut [usize], usize) {
|
||||
unsafe {
|
||||
if self.is_inline() {
|
||||
(core::slice::from_mut(&mut self.inline), INLINE_ELEMS)
|
||||
} else {
|
||||
let small_vec = self.alloced.as_mut();
|
||||
(
|
||||
core::slice::from_raw_parts_mut(
|
||||
&mut small_vec.data as *mut _ as *mut usize,
|
||||
small_vec.cap,
|
||||
),
|
||||
small_vec.cap * Self::UNIT,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn indexes(index: usize) -> (usize, usize) {
|
||||
(index / Self::UNIT, index % Self::UNIT)
|
||||
}
|
||||
|
||||
pub fn get(&self, index: Nid) -> bool {
|
||||
let index = index as usize;
|
||||
let (data, len) = self.data_and_len();
|
||||
if index >= len {
|
||||
return false;
|
||||
}
|
||||
let (elem, bit) = Self::indexes(index);
|
||||
(unsafe { *data.get_unchecked(elem) }) & (1 << bit) != 0
|
||||
}
|
||||
|
||||
pub fn set(&mut self, index: Nid) -> bool {
|
||||
let index = index as usize;
|
||||
let (mut data, len) = self.data_mut_and_len();
|
||||
if core::intrinsics::unlikely(index >= len) {
|
||||
self.grow((index + 1).next_power_of_two().max(4 * Self::UNIT));
|
||||
(data, _) = self.data_mut_and_len();
|
||||
}
|
||||
|
||||
let (elem, bit) = Self::indexes(index);
|
||||
debug_assert!(elem < data.len(), "{} < {}", elem, data.len());
|
||||
let elem = unsafe { data.get_unchecked_mut(elem) };
|
||||
let prev = *elem;
|
||||
*elem |= 1 << bit;
|
||||
*elem != prev
|
||||
}
|
||||
|
||||
fn grow(&mut self, size: usize) {
|
||||
debug_assert!(size.is_power_of_two());
|
||||
let slot_count = size / Self::UNIT;
|
||||
let (layout, off) = Self::layout(slot_count);
|
||||
let (ptr, prev_len) = unsafe {
|
||||
if self.is_inline() {
|
||||
let ptr = alloc::alloc(layout);
|
||||
*ptr.add(off).cast::<usize>() = self.inline & !Self::FLAG;
|
||||
(ptr, 1)
|
||||
} else {
|
||||
let prev_len = self.alloced.as_ref().cap;
|
||||
let (prev_layout, _) = Self::layout(prev_len);
|
||||
(alloc::realloc(self.alloced.as_ptr() as _, prev_layout, layout.size()), prev_len)
|
||||
}
|
||||
};
|
||||
unsafe {
|
||||
MaybeUninit::fill(
|
||||
core::slice::from_raw_parts_mut(
|
||||
ptr.add(off).cast::<MaybeUninit<usize>>().add(prev_len),
|
||||
slot_count - prev_len,
|
||||
),
|
||||
0,
|
||||
);
|
||||
*ptr.cast::<usize>() = slot_count;
|
||||
core::ptr::write(self, Self { alloced: Unique::new_unchecked(ptr as _) });
|
||||
}
|
||||
}
|
||||
|
||||
fn layout(slot_count: usize) -> (core::alloc::Layout, usize) {
|
||||
unsafe {
|
||||
core::alloc::Layout::new::<AllocedBitSet>()
|
||||
.extend(Layout::array::<usize>(slot_count).unwrap_unchecked())
|
||||
.unwrap_unchecked()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> BitSetIter {
|
||||
if self.is_inline() {
|
||||
BitSetIter { index: 0, current: unsafe { self.inline & !Self::FLAG }, remining: &[] }
|
||||
} else {
|
||||
let &[current, ref remining @ ..] = self.data_and_len().0 else {
|
||||
unsafe { unreachable_unchecked() }
|
||||
};
|
||||
BitSetIter { index: 0, current, remining }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, len: usize) {
|
||||
self.reserve(len);
|
||||
if self.is_inline() {
|
||||
unsafe { self.inline &= Self::FLAG };
|
||||
} else {
|
||||
self.data_mut_and_len().0.fill(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn units<'a>(&'a self, slot: &'a mut usize) -> &'a [usize] {
|
||||
if self.is_inline() {
|
||||
*slot = unsafe { self.inline } & !Self::FLAG;
|
||||
core::slice::from_ref(slot)
|
||||
} else {
|
||||
self.data_and_len().0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reserve(&mut self, len: usize) {
|
||||
if len > self.data_and_len().1 {
|
||||
self.grow(len.next_power_of_two().max(4 * Self::UNIT));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn units_mut(&mut self) -> Result<&mut [usize], &mut InlineBitSetView> {
|
||||
if self.is_inline() {
|
||||
Err(unsafe {
|
||||
core::mem::transmute::<&mut usize, &mut InlineBitSetView>(&mut self.inline)
|
||||
})
|
||||
} else {
|
||||
Ok(self.data_mut_and_len().0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct InlineBitSetView(usize);
|
||||
|
||||
impl InlineBitSetView {
|
||||
pub(crate) fn add_mask(&mut self, tmp: usize) {
|
||||
debug_assert!(tmp & BitSet::FLAG == 0);
|
||||
self.0 |= tmp;
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BitSetIter<'a> {
|
||||
index: usize,
|
||||
current: usize,
|
||||
remining: &'a [usize],
|
||||
}
|
||||
|
||||
impl Iterator for BitSetIter<'_> {
|
||||
type Item = usize;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
while self.current == 0 {
|
||||
self.current = *self.remining.take_first()?;
|
||||
self.index += 1;
|
||||
}
|
||||
|
||||
let sub_idx = self.current.trailing_zeros() as usize;
|
||||
self.current &= self.current - 1;
|
||||
Some(self.index * BitSet::UNIT + sub_idx)
|
||||
}
|
||||
}
|
||||
|
||||
struct AllocedBitSet {
|
||||
cap: usize,
|
||||
data: [usize; 0],
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[test]
|
||||
fn test_small_bit_set() {
|
||||
use std::vec::Vec;
|
||||
|
||||
let mut sv = BitSet::default();
|
||||
|
||||
sv.set(10);
|
||||
debug_assert!(sv.get(10));
|
||||
sv.set(100);
|
||||
debug_assert!(sv.get(100));
|
||||
sv.set(10000);
|
||||
debug_assert!(sv.get(10000));
|
||||
debug_assert_eq!(sv.iter().collect::<Vec<_>>(), &[10, 100, 10000]);
|
||||
sv.clear(10000);
|
||||
debug_assert_eq!(sv.iter().collect::<Vec<_>>(), &[]);
|
||||
}
|
||||
|
||||
pub union Vc {
|
||||
inline: InlineVc,
|
||||
alloced: AllocedVc,
|
||||
}
|
||||
|
||||
impl Default for Vc {
|
||||
fn default() -> Self {
|
||||
Vc { inline: InlineVc { elems: MaybeUninit::uninit(), cap: Default::default() } }
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Vc {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.as_slice().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Nid> for Vc {
|
||||
fn from_iter<T: IntoIterator<Item = Nid>>(iter: T) -> Self {
|
||||
let mut slf = Self::default();
|
||||
for i in iter {
|
||||
slf.push(i);
|
||||
}
|
||||
slf
|
||||
}
|
||||
}
|
||||
|
||||
const INLINE_ELEMS: usize = VC_SIZE / 2 - 1;
|
||||
const VC_SIZE: usize = 16;
|
||||
|
||||
impl Vc {
|
||||
fn is_inline(&self) -> bool {
|
||||
unsafe { self.inline.cap <= INLINE_ELEMS as Nid }
|
||||
}
|
||||
|
||||
fn layout(&self) -> Option<core::alloc::Layout> {
|
||||
unsafe {
|
||||
self.is_inline().not().then(|| {
|
||||
core::alloc::Layout::array::<Nid>(self.alloced.cap as _).unwrap_unchecked()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe {
|
||||
if self.is_inline() {
|
||||
self.inline.cap as _
|
||||
} else {
|
||||
self.alloced.len as _
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn len_mut(&mut self) -> &mut Nid {
|
||||
unsafe {
|
||||
if self.is_inline() {
|
||||
&mut self.inline.cap
|
||||
} else {
|
||||
&mut self.alloced.len
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_ptr(&self) -> *const Nid {
|
||||
unsafe {
|
||||
match self.is_inline() {
|
||||
true => self.inline.elems.as_ptr().cast(),
|
||||
false => self.alloced.base.as_ptr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_mut_ptr(&mut self) -> *mut Nid {
|
||||
unsafe {
|
||||
match self.is_inline() {
|
||||
true => self.inline.elems.as_mut_ptr().cast(),
|
||||
false => self.alloced.base.as_ptr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[Nid] {
|
||||
unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) }
|
||||
}
|
||||
|
||||
fn as_slice_mut(&mut self) -> &mut [Nid] {
|
||||
unsafe { core::slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) }
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: Nid) {
|
||||
if let Some(layout) = self.layout()
|
||||
&& unsafe { self.alloced.len == self.alloced.cap }
|
||||
{
|
||||
unsafe {
|
||||
self.alloced.cap *= 2;
|
||||
self.alloced.base = Unique::new_unchecked(
|
||||
alloc::realloc(
|
||||
self.alloced.base.as_ptr().cast(),
|
||||
layout,
|
||||
self.alloced.cap as usize * core::mem::size_of::<Nid>(),
|
||||
)
|
||||
.cast(),
|
||||
);
|
||||
}
|
||||
} else if self.len() == INLINE_ELEMS {
|
||||
unsafe {
|
||||
let mut allcd =
|
||||
Self::alloc((self.inline.cap + 1).next_power_of_two() as _, self.len());
|
||||
core::ptr::copy_nonoverlapping(self.as_ptr(), allcd.as_mut_ptr(), self.len());
|
||||
*self = allcd;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
*self.len_mut() += 1;
|
||||
self.as_mut_ptr().add(self.len() - 1).write(value);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn alloc(cap: usize, len: usize) -> Self {
|
||||
debug_assert!(cap > INLINE_ELEMS);
|
||||
let layout = unsafe { core::alloc::Layout::array::<Nid>(cap).unwrap_unchecked() };
|
||||
let alloc = unsafe { alloc::alloc(layout) };
|
||||
unsafe {
|
||||
Vc {
|
||||
alloced: AllocedVc {
|
||||
base: Unique::new_unchecked(alloc.cast()),
|
||||
len: len as _,
|
||||
cap: cap as _,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swap_remove(&mut self, index: usize) {
|
||||
let len = self.len() - 1;
|
||||
self.as_slice_mut().swap(index, len);
|
||||
*self.len_mut() -= 1;
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, index: usize) {
|
||||
self.as_slice_mut().copy_within(index + 1.., index);
|
||||
*self.len_mut() -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Vc {
|
||||
fn drop(&mut self) {
|
||||
if let Some(layout) = self.layout() {
|
||||
unsafe {
|
||||
alloc::dealloc(self.alloced.base.as_ptr().cast(), layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Vc {
|
||||
fn clone(&self) -> Self {
|
||||
self.as_slice().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Vc {
|
||||
type IntoIter = VcIntoIter;
|
||||
type Item = Nid;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
VcIntoIter { start: 0, end: self.len(), vc: self }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VcIntoIter {
|
||||
start: usize,
|
||||
end: usize,
|
||||
vc: Vc,
|
||||
}
|
||||
|
||||
impl Iterator for VcIntoIter {
|
||||
type Item = Nid;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if self.start == self.end {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ret = unsafe { core::ptr::read(self.vc.as_slice().get_unchecked(self.start)) };
|
||||
self.start += 1;
|
||||
Some(ret)
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
let len = self.end - self.start;
|
||||
(len, Some(len))
|
||||
}
|
||||
}
|
||||
|
||||
impl DoubleEndedIterator for VcIntoIter {
|
||||
fn next_back(&mut self) -> Option<Self::Item> {
|
||||
if self.start == self.end {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.end -= 1;
|
||||
Some(unsafe { core::ptr::read(self.vc.as_slice().get_unchecked(self.end)) })
|
||||
}
|
||||
}
|
||||
|
||||
impl ExactSizeIterator for VcIntoIter {}
|
||||
|
||||
impl<const SIZE: usize> From<[Nid; SIZE]> for Vc {
|
||||
fn from(value: [Nid; SIZE]) -> Self {
|
||||
value.as_slice().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a [Nid]> for Vc {
|
||||
fn from(value: &'a [Nid]) -> Self {
|
||||
if value.len() <= INLINE_ELEMS {
|
||||
let mut dflt = Self::default();
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(value.as_ptr(), dflt.as_mut_ptr(), value.len())
|
||||
};
|
||||
dflt.inline.cap = value.len() as _;
|
||||
dflt
|
||||
} else {
|
||||
let mut allcd = unsafe { Self::alloc(value.len(), value.len()) };
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(value.as_ptr(), allcd.as_mut_ptr(), value.len())
|
||||
};
|
||||
allcd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Vc {
|
||||
type Target = [Nid];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Vc {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.as_slice_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct InlineVc {
|
||||
cap: Nid,
|
||||
elems: MaybeUninit<[Nid; INLINE_ELEMS]>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[repr(C)]
|
||||
struct AllocedVc {
|
||||
cap: Nid,
|
||||
len: Nid,
|
||||
base: Unique<Nid>,
|
||||
}
|
||||
|
||||
pub trait Ent: Copy {
|
||||
fn new(index: usize) -> Self;
|
||||
fn index(self) -> usize;
|
||||
}
|
||||
|
||||
pub struct EntVec<K: Ent, T> {
|
||||
data: ::alloc::vec::Vec<T>,
|
||||
k: PhantomData<fn(K)>,
|
||||
}
|
||||
|
||||
impl<K: Ent, T> Default for EntVec<K, T> {
|
||||
fn default() -> Self {
|
||||
Self { data: Default::default(), k: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Ent, T> EntVec<K, T> {
|
||||
pub fn clear(&mut self) {
|
||||
self.data.clear();
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.data.is_empty()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
pub fn push(&mut self, value: T) -> K {
|
||||
let k = K::new(self.data.len());
|
||||
self.data.push(value);
|
||||
k
|
||||
}
|
||||
|
||||
pub fn next(&self, index: K) -> Option<&T> {
|
||||
self.data.get(index.index() + 1)
|
||||
}
|
||||
|
||||
pub fn shadow(&mut self, len: usize)
|
||||
where
|
||||
T: Default,
|
||||
{
|
||||
if self.data.len() < len {
|
||||
self.data.resize_with(len, Default::default);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> core::slice::Iter<T> {
|
||||
self.data.iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Ent, T> core::ops::Index<K> for EntVec<K, T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, index: K) -> &Self::Output {
|
||||
&self.data[index.index()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Ent, T> core::ops::IndexMut<K> for EntVec<K, T> {
|
||||
fn index_mut(&mut self, index: K) -> &mut Self::Output {
|
||||
&mut self.data[index.index()]
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! decl_ent {
|
||||
($(
|
||||
$vis:vis struct $name:ident($index:ty);
|
||||
)*) => {$(
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
$vis struct $name($index);
|
||||
|
||||
impl crate::utils::Ent for $name {
|
||||
fn new(index: usize) -> Self {
|
||||
Self(index as $index)
|
||||
}
|
||||
|
||||
fn index(self) -> usize {
|
||||
self.0 as _
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl core::fmt::Display for $name {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, concat!(stringify!($name), "{}"), self.0)
|
||||
}
|
||||
}
|
||||
)*};
|
||||
}
|
||||
pub(crate) use decl_ent;
|
|
@ -1,48 +0,0 @@
|
|||
main:
|
||||
ADDI64 r254, r254, -24d
|
||||
ST r31, r254, 0a, 24h
|
||||
LI32 r32, 1148846080w
|
||||
CP r2, r32
|
||||
JAL r31, r0, :sin
|
||||
CP r33, r1
|
||||
FMUL32 r32, r33, r32
|
||||
FTI32 r32, r32, 1b
|
||||
CP r1, r32
|
||||
LD r31, r254, 0a, 24h
|
||||
ADDI64 r254, r254, 24d
|
||||
JALA r0, r31, 0a
|
||||
sin:
|
||||
CP r13, r2
|
||||
LI32 r14, 1124073472w
|
||||
LI32 r15, 1078530011w
|
||||
FMUL32 r14, r13, r14
|
||||
FDIV32 r14, r14, r15
|
||||
FTI32 r14, r14, 1b
|
||||
ANDI r15, r14, 255d
|
||||
ITF64 r16, r14
|
||||
MULI64 r15, r15, 4d
|
||||
LRA r17, r0, :sin_table
|
||||
LI32 r18, 1086918619w
|
||||
FC64T32 r16, r16, 1b
|
||||
ADDI64 r14, r14, 64d
|
||||
ADD64 r15, r17, r15
|
||||
LI32 r19, 1132462080w
|
||||
FMUL32 r16, r16, r18
|
||||
ANDI r14, r14, 255d
|
||||
LI32 r18, 1056964608w
|
||||
LD r15, r15, 0a, 4h
|
||||
FDIV32 r16, r16, r19
|
||||
MULI64 r14, r14, 4d
|
||||
FMUL32 r18, r15, r18
|
||||
FSUB32 r13, r13, r16
|
||||
ADD64 r14, r17, r14
|
||||
FMUL32 r16, r13, r18
|
||||
LD r14, r14, 0a, 4h
|
||||
FSUB32 r14, r14, r16
|
||||
FMUL32 r13, r14, r13
|
||||
FADD32 r13, r15, r13
|
||||
CP r1, r13
|
||||
JALA r0, r31, 0a
|
||||
code size: 1315
|
||||
ret: 826
|
||||
status: Ok(())
|
|
@ -1,6 +0,0 @@
|
|||
main:
|
||||
CP r1, r0
|
||||
JALA r0, r31, 0a
|
||||
code size: 22
|
||||
ret: 0
|
||||
status: Ok(())
|
|
@ -1,6 +0,0 @@
|
|||
main:
|
||||
CP r1, r0
|
||||
JALA r0, r31, 0a
|
||||
code size: 22
|
||||
ret: 0
|
||||
status: Ok(())
|
|
@ -1,32 +0,0 @@
|
|||
main:
|
||||
ADDI64 r254, r254, -56d
|
||||
ST r31, r254, 24a, 32h
|
||||
LI64 r32, 1d
|
||||
ADDI64 r33, r254, 0d
|
||||
ST r32, r254, 0a, 8h
|
||||
LI64 r34, 2d
|
||||
ST r34, r254, 8a, 8h
|
||||
LI64 r34, 4d
|
||||
ST r34, r254, 16a, 8h
|
||||
CP r2, r33
|
||||
JAL r31, r0, :pass
|
||||
CP r33, r1
|
||||
ADD64 r32, r33, r32
|
||||
CP r1, r32
|
||||
LD r31, r254, 24a, 32h
|
||||
ADDI64 r254, r254, 56d
|
||||
JALA r0, r31, 0a
|
||||
pass:
|
||||
CP r13, r2
|
||||
LD r14, r13, 8a, 8h
|
||||
MULI64 r15, r14, 8d
|
||||
LD r16, r13, 0a, 8h
|
||||
ADD64 r13, r15, r13
|
||||
ADD64 r14, r14, r16
|
||||
LD r13, r13, 0a, 8h
|
||||
ADD64 r13, r13, r14
|
||||
CP r1, r13
|
||||
JALA r0, r31, 0a
|
||||
code size: 246
|
||||
ret: 8
|
||||
status: Ok(())
|
|
@ -1,8 +0,0 @@
|
|||
main:
|
||||
LRA r13, r0, :sin_table
|
||||
LD r13, r13, 80a, 8h
|
||||
CP r1, r13
|
||||
JALA r0, r31, 0a
|
||||
code size: 770
|
||||
ret: 1736
|
||||
status: Ok(())
|
|
@ -1,14 +0,0 @@
|
|||
main:
|
||||
CP r14, r2
|
||||
LI64 r13, 1d
|
||||
JNE r14, r13, :0
|
||||
JMP :1
|
||||
0: JNE r14, r0, :2
|
||||
LI64 r13, 2d
|
||||
JMP :1
|
||||
2: LI64 r13, 3d
|
||||
1: CP r1, r13
|
||||
JALA r0, r31, 0a
|
||||
code size: 75
|
||||
ret: 2
|
||||
status: Ok(())
|
|
@ -1,32 +0,0 @@
|
|||
main:
|
||||
ADDI64 r254, r254, -24d
|
||||
ST r31, r254, 0a, 24h
|
||||
LRA r32, r0, :"abඞ\n\r\t56789\0"
|
||||
CP r2, r32
|
||||
JAL r31, r0, :str_len
|
||||
CP r32, r1
|
||||
LRA r33, r0, :"fff\0"
|
||||
CP r2, r33
|
||||
JAL r31, r0, :str_len
|
||||
CP r33, r1
|
||||
ADD64 r32, r33, r32
|
||||
CP r1, r32
|
||||
LD r31, r254, 0a, 24h
|
||||
ADDI64 r254, r254, 24d
|
||||
JALA r0, r31, 0a
|
||||
str_len:
|
||||
CP r15, r2
|
||||
CP r14, r0
|
||||
CP r13, r14
|
||||
2: LD r16, r15, 0a, 1h
|
||||
ANDI r16, r16, 255d
|
||||
JNE r16, r14, :0
|
||||
CP r1, r13
|
||||
JMP :1
|
||||
0: ADDI64 r15, r15, 1d
|
||||
ADDI64 r13, r13, 1d
|
||||
JMP :2
|
||||
1: JALA r0, r31, 0a
|
||||
code size: 216
|
||||
ret: 16
|
||||
status: Ok(())
|
|
@ -1,13 +0,0 @@
|
|||
foo:
|
||||
JALA r0, r31, 0a
|
||||
main:
|
||||
ADDI64 r254, r254, -8d
|
||||
ST r31, r254, 0a, 8h
|
||||
JAL r31, r0, :foo
|
||||
CP r1, r0
|
||||
LD r31, r254, 0a, 8h
|
||||
ADDI64 r254, r254, 8d
|
||||
JALA r0, r31, 0a
|
||||
code size: 88
|
||||
ret: 0
|
||||
status: Ok(())
|
|
@ -1,8 +0,0 @@
|
|||
main:
|
||||
LRA r13, r0, :a
|
||||
LD r13, r13, 0a, 8h
|
||||
CP r1, r13
|
||||
JALA r0, r31, 0a
|
||||
code size: 50
|
||||
ret: 50
|
||||
status: Ok(())
|
|
@ -1,8 +0,0 @@
|
|||
main:
|
||||
LRA r13, r0, :a
|
||||
LD r13, r13, 0a, 8h
|
||||
CP r1, r13
|
||||
JALA r0, r31, 0a
|
||||
code size: 50
|
||||
ret: 50
|
||||
status: Ok(())
|
|
@ -1,19 +0,0 @@
|
|||
cond:
|
||||
CP r1, r0
|
||||
JALA r0, r31, 0a
|
||||
main:
|
||||
ADDI64 r254, r254, -24d
|
||||
ST r31, r254, 0a, 24h
|
||||
JAL r31, r0, :cond
|
||||
CP r33, r1
|
||||
CP r32, r0
|
||||
JNE r33, r32, :0
|
||||
JMP :1
|
||||
0: LI64 r32, 2d
|
||||
1: CP r1, r32
|
||||
LD r31, r254, 0a, 24h
|
||||
ADDI64 r254, r254, 24d
|
||||
JALA r0, r31, 0a
|
||||
code size: 117
|
||||
ret: 0
|
||||
status: Ok(())
|
|
@ -1,6 +0,0 @@
|
|||
main:
|
||||
CP r1, r0
|
||||
JALA r0, r31, 0a
|
||||
code size: 22
|
||||
ret: 0
|
||||
status: Ok(())
|
|
@ -1,7 +0,0 @@
|
|||
main:
|
||||
LI32 r13, 69w
|
||||
CP r1, r13
|
||||
JALA r0, r31, 0a
|
||||
code size: 28
|
||||
ret: 69
|
||||
status: Ok(())
|
|
@ -1,6 +0,0 @@
|
|||
main:
|
||||
CP r1, r0
|
||||
JALA r0, r31, 0a
|
||||
code size: 22
|
||||
ret: 0
|
||||
status: Ok(())
|
|
@ -1,5 +0,0 @@
|
|||
main:
|
||||
UN
|
||||
code size: 9
|
||||
ret: 0
|
||||
status: Err(Unreachable)
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue