Compare commits

..

29 commits

Author SHA1 Message Date
IntoTheNight 73ad40b369 Merge pull request 'master' (#1) from AbleOS/holey-bytes:master into master
Reviewed-on: https://git.ablecorp.us/IntoTheNight/holey-bytes/pulls/1
2023-07-11 09:28:48 +00:00
MunirG05 0fb89ec4b3 the design is very human 2023-07-11 14:54:49 +05:30
MunirG05 f44220074d add fancy errors 2023-07-11 14:38:20 +05:30
Erin b218aa4a00 doc 2023-07-11 10:33:55 +02:00
MunirG05 63b2dc7514 tried to shove the timer back in 2023-07-11 14:03:25 +05:30
Erin 0351a954d0 Moved 2023-07-11 10:32:26 +02:00
Erin e32f0d1e61 wrap around timer 2023-07-11 10:31:03 +02:00
Erin 81f79dc7a5 Implement timer 2023-07-11 10:29:23 +02:00
Erin 7ca0b1d4eb Improved assembler library 2023-07-11 02:08:55 +02:00
Erin 447f8b2075 Moved 2023-07-10 23:18:23 +02:00
Erin b271d024cd Rename 2023-07-07 15:23:53 +02:00
Erin 7d17f48562 Updated flots 2023-07-07 15:22:03 +02:00
Erin 387d4c7ce7 assert char bit 2023-07-07 14:36:40 +02:00
Erin b7d4243113 Updated C header 2023-07-07 14:33:08 +02:00
Erin 3af50b29fb Updated spec 2023-07-07 14:33:07 +02:00
able 2d639797d9 HBASM: derp forgot that deps also need to be nostd 2023-06-26 05:23:52 -05:00
able a63c252c7a HBASM: no_std compatible now 2023-06-26 05:18:14 -05:00
Erin da1553d030 Improved unhandled trap handling 2023-06-25 00:28:20 +02:00
Erin f0a00ebb8d Stole docs 2023-06-25 00:21:40 +02:00
Erin 2bbf6ceee0 docs 2023-06-25 00:18:31 +02:00
Erin 2c9e315889 Implemented traps 2023-06-25 00:16:14 +02:00
able f53a42977d Initial work on a simple serial driver for ableos 2023-06-21 08:22:56 -05:00
able 8bc0d0020c Update to stable 2023-06-21 08:22:21 -05:00
able f58f801aa9 clear out assets 2023-06-21 07:54:10 -05:00
able a642b68474 NIX: fix nix-shell 2023-06-21 07:53:01 -05:00
Erin 79c367dc18 HoleyBytes, almost adhering the spec
- Changed instruction encoding to be faster to match on
- Implemented all instructions defined in spec
- Bytecode validation
- Assembler
- Implemented 5 level paging (based on SV57)
- Implemented some degree of interrupts (though not fully adhering the spec yet)
2023-06-21 02:07:48 +02:00
Erin 8b9a75adb4 a 2023-05-28 23:38:26 +02:00
Erin 7e233f4ae1 fixup32 2023-05-28 23:37:43 +02:00
Erin 0c69d80fc2 Changed register handling 2023-05-28 16:49:01 +02:00
179 changed files with 2248 additions and 24772 deletions

View file

@ -1,4 +0,0 @@
[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
View file

@ -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

1628
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,50 +1,2 @@
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"]

View file

@ -1,10 +0,0 @@
[package]
name = "hbbytecode"
version = "0.1.0"
edition = "2018"
[features]
default = ["disasm"]
std = []
disasm = ["std"]

View file

@ -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')
}

View file

@ -1,75 +0,0 @@
/* HoleyBytes Bytecode representation in C
* Requires C23 compiler or better
*
* Uses MSVC pack pragma extension,
* proved to work with Clang and GNU® GCC.
*/
#pragma once
#include <assert.h>
#include <limits.h>
#include <stdint.h>
static_assert(CHAR_BIT == 8, "Cursed architectures are not supported");
enum hbbc_Opcode: uint8_t {
hbbc_Op_UN , hbbc_Op_TX , hbbc_Op_NOP , hbbc_Op_ADD , hbbc_Op_SUB , hbbc_Op_MUL ,
hbbc_Op_AND , hbbc_Op_OR , hbbc_Op_XOR , hbbc_Op_SL , hbbc_Op_SR , hbbc_Op_SRS ,
hbbc_Op_CMP , hbbc_Op_CMPU , hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI ,
hbbc_Op_MULI , hbbc_Op_ANDI , hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI ,
hbbc_Op_SRSI , hbbc_Op_CMPI , hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI ,
hhbc_Op_LRA , hbbc_Op_LD , hbbc_Op_ST , hbbc_Op_LDR , hhbc_Op_STR , hbbc_Op_BMC ,
hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JMPR , hbbc_Op_JAL , hbbc_Op_JALR , hbbc_Op_JEQ ,
hbbc_Op_JNE , hbbc_Op_JLT , hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL ,
hbbc_Op_ADDF , hbbc_Op_SUBF , hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF ,
hbbc_Op_ITF , hbbc_Op_FTI , hbbc_Op_ADDFI , hbbc_Op_MULFI ,
} typedef hbbc_Opcode;
static_assert(sizeof(hbbc_Opcode) == 1);
#pragma pack(push, 1)
struct hbbc_ParamBBBB
{ uint8_t _0; uint8_t _1; uint8_t _2; uint8_t _3; }
typedef hbbc_ParamBBBB;
static_assert(sizeof(hbbc_ParamBBBB) == 32 / 8);
struct hbbc_ParamBBB
{ uint8_t _0; uint8_t _1; uint8_t _2; }
typedef hbbc_ParamBBB;
static_assert(sizeof(hbbc_ParamBBB) == 24 / 8);
struct hbbc_ParamBBDH
{ uint8_t _0; uint8_t _1; uint64_t _2; uint16_t _3; }
typedef hbbc_ParamBBDH;
static_assert(sizeof(hbbc_ParamBBDH) == 96 / 8);
struct hbbc_ParamBBWH
{ uint8_t _0; uint8_t _1; uint32_t _2; uint16_t _3; }
typedef hbbc_ParamBBWH;
static_assert(sizeof(hbbc_ParamBBWH) == 64 / 8);
struct hbbc_ParamBBD
{ uint8_t _0; uint8_t _1; uint64_t _2; }
typedef hbbc_ParamBBD;
static_assert(sizeof(hbbc_ParamBBD) == 80 / 8);
struct hbbc_ParamBBW
{ uint8_t _0; uint8_t _1; uint32_t _2; }
typedef hbbc_ParamBBW;
static_assert(sizeof(hbbc_ParamBBW) == 48 / 8);
struct hbbc_ParamBB
{ uint8_t _0; uint8_t _1; }
typedef hbbc_ParamBB;
static_assert(sizeof(hbbc_ParamBB) == 16 / 8);
struct hbbc_ParamBD
{ uint8_t _0; uint64_t _1; }
typedef hbbc_ParamBD;
static_assert(sizeof(hbbc_ParamBD) == 72 / 8);
typedef uint64_t hbbc_ParamD;
static_assert(sizeof(hbbc_ParamD) == 64 / 8);
#pragma pack(pop)

View file

@ -1,120 +0,0 @@
0x00, UN, N, "Cause an unreachable code trap" ;
0x01, TX, N, "Termiante execution" ;
0x02, NOP, N, "Do nothing" ;
0x03, ADD8, RRR, "Addition (8b)" ;
0x04, ADD16, RRR, "Addition (16b)" ;
0x05, ADD32, RRR, "Addition (32b)" ;
0x06, ADD64, RRR, "Addition (64b)" ;
0x07, SUB8, RRR, "Subtraction (8b)" ;
0x08, SUB16, RRR, "Subtraction (16b)" ;
0x09, SUB32, RRR, "Subtraction (32b)" ;
0x0A, SUB64, RRR, "Subtraction (64b)" ;
0x0B, MUL8, RRR, "Multiplication (8b)" ;
0x0C, MUL16, RRR, "Multiplication (16b)" ;
0x0D, MUL32, RRR, "Multiplication (32b)" ;
0x0E, MUL64, RRR, "Multiplication (64b)" ;
0x0F, AND, RRR, "Bitand" ;
0x10, OR, RRR, "Bitor" ;
0x11, XOR, RRR, "Bitxor" ;
0x12, SLU8, RRR, "Unsigned left bitshift (8b)" ;
0x13, SLU16, RRR, "Unsigned left bitshift (16b)" ;
0x14, SLU32, RRR, "Unsigned left bitshift (32b)" ;
0x15, SLU64, RRR, "Unsigned left bitshift (64b)" ;
0x16, SRU8, RRR, "Unsigned right bitshift (8b)" ;
0x17, SRU16, RRR, "Unsigned right bitshift (16b)" ;
0x18, SRU32, RRR, "Unsigned right bitshift (32b)" ;
0x19, SRU64, RRR, "Unsigned right bitshift (64b)" ;
0x1A, SRS8, RRR, "Signed right bitshift (8b)" ;
0x1B, SRS16, RRR, "Signed right bitshift (16b)" ;
0x1C, SRS32, RRR, "Signed right bitshift (32b)" ;
0x1D, SRS64, RRR, "Signed right bitshift (64b)" ;
0x1E, CMPU, RRR, "Unsigned comparsion" ;
0x1F, CMPS, RRR, "Signed comparsion" ;
0x20, DIRU8, RRRR, "Merged divide-remainder (unsigned 8b)" ;
0x21, DIRU16, RRRR, "Merged divide-remainder (unsigned 16b)" ;
0x22, DIRU32, RRRR, "Merged divide-remainder (unsigned 32b)" ;
0x23, DIRU64, RRRR, "Merged divide-remainder (unsigned 64b)" ;
0x24, DIRS8, RRRR, "Merged divide-remainder (signed 8b)" ;
0x25, DIRS16, RRRR, "Merged divide-remainder (signed 16b)" ;
0x26, DIRS32, RRRR, "Merged divide-remainder (signed 32b)" ;
0x27, DIRS64, RRRR, "Merged divide-remainder (signed 64b)" ;
0x28, NEG, RR, "Bit negation" ;
0x29, NOT, RR, "Logical negation" ;
0x2A, SXT8, RR, "Sign extend 8b to 64b" ;
0x2B, SXT16, RR, "Sign extend 16b to 64b" ;
0x2C, SXT32, RR, "Sign extend 32b to 64b" ;
0x2D, ADDI8, RRB, "Addition with immediate (8b)" ;
0x2E, ADDI16, RRH, "Addition with immediate (16b)" ;
0x2F, ADDI32, RRW, "Addition with immediate (32b)" ;
0x30, ADDI64, RRD, "Addition with immediate (64b)" ;
0x31, MULI8, RRB, "Multiplication with immediate (8b)" ;
0x32, MULI16, RRH, "Multiplication with immediate (16b)" ;
0x33, MULI32, RRW, "Multiplication with immediate (32b)" ;
0x34, MULI64, RRD, "Multiplication with immediate (64b)" ;
0x35, ANDI, RRD, "Bitand with immediate" ;
0x36, ORI, RRD, "Bitor with immediate" ;
0x37, XORI, RRD, "Bitxor with immediate" ;
0x38, SLUI8, RRB, "Unsigned left bitshift with immedidate (8b)" ;
0x39, SLUI16, RRB, "Unsigned left bitshift with immedidate (16b)";
0x3A, SLUI32, RRB, "Unsigned left bitshift with immedidate (32b)";
0x3B, SLUI64, RRB, "Unsigned left bitshift with immedidate (64b)";
0x3C, SRUI8, RRB, "Unsigned right bitshift with immediate (8b)" ;
0x3D, SRUI16, RRB, "Unsigned right bitshift with immediate (16b)";
0x3E, SRUI32, RRB, "Unsigned right bitshift with immediate (32b)";
0x3F, SRUI64, RRB, "Unsigned right bitshift with immediate (64b)";
0x40, SRSI8, RRB, "Signed right bitshift with immediate" ;
0x41, SRSI16, RRB, "Signed right bitshift with immediate" ;
0x42, SRSI32, RRB, "Signed right bitshift with immediate" ;
0x43, SRSI64, RRB, "Signed right bitshift with immediate" ;
0x44, CMPUI, RRD, "Unsigned compare with immediate" ;
0x45, CMPSI, RRD, "Signed compare with immediate" ;
0x46, CP, RR, "Copy register" ;
0x47, SWA, RR, "Swap registers" ;
0x48, LI8, RB, "Load immediate (8b)" ;
0x49, LI16, RH, "Load immediate (16b)" ;
0x4A, LI32, RW, "Load immediate (32b)" ;
0x4B, LI64, RD, "Load immediate (64b)" ;
0x4C, LRA, RRO, "Load relative address" ;
0x4D, LD, RRAH, "Load from absolute address" ;
0x4E, ST, RRAH, "Store to absolute address" ;
0x4F, LDR, RROH, "Load from relative address" ;
0x50, STR, RROH, "Store to relative address" ;
0x51, BMC, RRH, "Copy block of memory" ;
0x52, BRC, RRB, "Copy register block" ;
0x53, JMP, O, "Relative jump" ;
0x54, JAL, RRO, "Linking relative jump" ;
0x55, JALA, RRA, "Linking absolute jump" ;
0x56, JEQ, RRP, "Branch on equal" ;
0x57, JNE, RRP, "Branch on nonequal" ;
0x58, JLTU, RRP, "Branch on lesser-than (unsigned)" ;
0x59, JGTU, RRP, "Branch on greater-than (unsigned)" ;
0x5A, JLTS, RRP, "Branch on lesser-than (signed)" ;
0x5B, JGTS, RRP, "Branch on greater-than (signed)" ;
0x5C, ECA, N, "Environment call trap" ;
0x5D, EBP, N, "Environment breakpoint" ;
0x5E, FADD32, RRR, "Floating point addition (32b)" ;
0x5F, FADD64, RRR, "Floating point addition (64b)" ;
0x60, FSUB32, RRR, "Floating point subtraction (32b)" ;
0x61, FSUB64, RRR, "Floating point subtraction (64b)" ;
0x62, FMUL32, RRR, "Floating point multiply (32b)" ;
0x63, FMUL64, RRR, "Floating point multiply (64b)" ;
0x64, FDIV32, RRR, "Floating point division (32b)" ;
0x65, FDIV64, RRR, "Floating point division (64b)" ;
0x66, FMA32, RRRR, "Float fused multiply-add (32b)" ;
0x67, FMA64, RRRR, "Float fused multiply-add (64b)" ;
0x68, FINV32, RR, "Float reciprocal (32b)" ;
0x69, FINV64, RR, "Float reciprocal (64b)" ;
0x6A, FCMPLT32, RRR, "Flaot compare less than (32b)" ;
0x6B, FCMPLT64, RRR, "Flaot compare less than (64b)" ;
0x6C, FCMPGT32, RRR, "Flaot compare greater than (32b)" ;
0x6D, FCMPGT64, RRR, "Flaot compare greater than (64b)" ;
0x6E, ITF32, RR, "Int to 32 bit float" ;
0x6F, ITF64, RR, "Int to 64 bit float" ;
0x70, FTI32, RRB, "Float 32 to int" ;
0x71, FTI64, RRB, "Float 64 to int" ;
0x72, FC32T64, RR, "Float 64 to Float 32" ;
0x73, FC64T32, RRB, "Float 32 to Float 64" ;
0x74, LRA16, RRP, "Load relative immediate (16 bit)" ;
0x75, LDR16, RRPH, "Load from relative address (16 bit)" ;
0x76, STR16, RRPH, "Store to relative address (16 bit)" ;
0x77, JMP16, P, "Relative jump (16 bit)" ;

View file

@ -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(())
}

View file

@ -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"]

View file

@ -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
```

View file

@ -1,179 +0,0 @@
* {
font-family: var(--font);
}
body {
--primary: white;
--secondary: #EFEFEF;
--timestamp: #777777;
--error: #ff3333;
--placeholder: #333333;
}
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 {
div.info {
display: flex;
gap: var(--small-gap);
span[apply=timestamp] {
color: var(--timestamp);
}
}
div.stats {
display: flex;
gap: var(--small-gap);
}
}
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;
}
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,
&.Dor,
&.Ctor,
&.Colon {
color: #5c6a72;
}
}

View file

@ -1,507 +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) {
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);
}
/**@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 {string} code
* @param {string[]} roots
* @param {Post[]} buf
* @param {Set<string>} prevRoots
* @returns {void} */
function loadCachedPackages(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);
buf.push({ path: imp, code: localStorage.getItem("package-" + imp) ?? never() });
for (const match of buf[buf.length - 1].code.matchAll(importRe)) {
roots.push(match[1]);
}
}
}
/**@type{Set<string>}*/ const prevRoots = new Set();
/** @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();
const keyBuf = [];
/**@type{Post[]}*/
const packages = [{ path: "local.hb", code: "" }];
const debounce = 100;
/**@type{AbortController|undefined}*/
let cancelation = undefined;
let timeout = 0;
prevRoots.clear();
const onInput = () => {
importDiff.clear();
for (const match of edit.value.matchAll(importRe)) {
if (localStorage["package-" + match[1]]) continue;
importDiff.add(match[1]);
}
if (importDiff.size !== 0) {
if (cancelation) cancelation.abort();
cancelation = new AbortController();
keyBuf.length = 0;
keyBuf.push(...importDiff.keys());
errors.textContent = "fetching: " + keyBuf.join(", ");
fetch(`/code`, {
method: "POST",
signal: cancelation.signal,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(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 = keyBuf.filter(i => json[i] === undefined);
if (missing.length !== 0) {
errors.textContent = "deps not found: " + missing.join(", ");
} else {
cancelation = undefined;
edit.dispatchEvent(new InputEvent("input"));
}
}
} catch (er) {
errors.textContent = "completely failed to fetch ("
+ e.status + "): " + keyBuf.join(", ");
console.error(e, er);
}
});
}
if (cancelation && importDiff.size !== 0) {
return;
}
loadCachedPackages(edit.value, keyBuf, packages, prevRoots);
errors.textContent = compileCode(hbc, packages, 1);
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, 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 */
async function execApply(target) {
for (const elem of target.querySelectorAll('[apply]')) {
if (!(elem instanceof HTMLElement)) continue;
const funcname = elem.getAttribute('apply') ?? never();
applyFns[funcname](elem);
}
}
/** @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 updaetTab(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\n";
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")
updaetTab(ev['detail'].pathInfo.finalRequestPath);
console.log(ev);
});
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 });
});
updaetTab();
wireUp(document.body);

View file

@ -1,884 +0,0 @@
#![feature(iter_collect_into)]
use {
argon2::{password_hash::SaltString, PasswordVerifier},
axum::{
body::Bytes,
extract::{DefaultBodyLimit, Path},
http::{header::COOKIE, request::Parts},
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("/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")}
}
}
}
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)?,
..Default::default()
})
}
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">
<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>
</div>
<div class="stats">
for (name, count) in "inps runs deps".split(' ')
.zip([imports, runs, dependencies])
.filter(|(_, &c)| c != 0)
{
name ": "<span>count</span>
}
</div>
<pre apply="fmt">code</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">
<link rel="stylesheet" href="/index.css">
</head>
<body>
<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 FROM post WHERE author = ?
ORDER BY timestamp DESC",
get_pots_before: "SELECT author, name, timestamp, code FROM post 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(?, ?, ?, ?)",
}
}
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() {
let db = rusqlite::Connection::open("db.sqlite").unwrap();
db.execute_batch(include_str!("schema.sql")).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) {}
}

View file

@ -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>

View file

@ -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)
);

View file

@ -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>

View file

@ -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" }

View file

@ -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);
}

View file

@ -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"] }

View file

@ -1,119 +0,0 @@
#![feature(alloc_error_handler)]
#![feature(slice_take)]
#![no_std]
use {
alloc::{string::String, vec::Vec},
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 unknown = ct.vm.read_reg(2).0;
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);
}

View file

@ -1,7 +0,0 @@
[package]
name = "wasm-rt"
version = "0.1.0"
edition = "2021"
[dependencies]
log = { version = "0.4.22", optional = true }

View file

@ -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)
}
}
}

View file

@ -1,100 +0,0 @@
jmp entry
puts:
-- Write string to console
-- r2: [IN] *const u8 String pointer
-- r3: [IN] usize String length
li8 r1, 0x1 -- Write syscall
brc r2, r3, 2 -- Copy parameters
li8 r2, 0x1 -- STDOUT
eca
jal r0, r31, 0
gets:
-- Read string until end of buffer or LF
-- r2: [IN] *mut u8 Buffer
-- r3: [IN] usize Buffer length
-- Register allocations:
-- r33: *mut u8 Buffer end
-- r34: u8 Immediate char
-- r35: u8 Const [0x0A = LF]
li8 r35, 0x0A
add64 r33, r2, r3
-- Setup syscall
li8 r2, 0x1 -- Stdin
cp r3, r2
li8 r4, 0x1 -- Read one char
jeq r3, r33, end
loop:
li8 r1, 0x1 -- Read syscall
eca
addi64 r3, r3, 1
ld r34, r3, 0, 1
jeq r34, r35, end
jne r3, r33, loop
end:
-- Set copied amount
sub64 r1, r33, r3
addi64 r1, -1
jal r0, r31, 0
alloc-pages:
-- Allocate pages
-- r1: [OUT] *mut u8 Pointer to page
-- r2: [IN] u16 Page count
muli16 r3, r2, 4096 -- page count
li8 r1, 0x9 -- mmap syscall
li8 r2, 0x0 -- no address set, kernel chosen
li8 r4, 0x2 -- PROT_WRITE
li8 r5, 0x20 -- MAP_ANONYMOUS
li64 r6, -1 -- Doesn't map file
li8 r7, 0x0 -- Doesn't map file
eca
jal r0, r31, 0
entry:
-- Program entrypoint
-- Register allocations:
-- r32: *mut u8 Buffer
-- r36: usize Read buffer length
-- Allocate one page (4096 KiB)
li8 r2, 1
jal r31, 0, alloc-pages
cp r32, r1
-- Print message
lra16 r2, r0, #enter-your-name
li8 r3, 17
jal r31, r0, puts
-- Read name
cp r2, r32
li16 r3, 4096
jal r31, r0, gets
cp r36, r1
-- Print your name is
lra16 r2, r0, #your-name-is
li8 r3, 15
jal r31, r0, puts
-- And now print the name
cp r2, r32
cp r3, r36
jal r31, r0, puts
tx
#enter-your-name: "Enter your name: "
#your-name-is : "\nYour name is: "

Binary file not shown.

Binary file not shown.

20
hbasm/Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
[package]
name = "hbasm"
version = "0.1.0"
edition = "2021"
[dependencies]
hbbytecode = { path = "../hbbytecode" }
paste = "1.0"
hashbrown = "0.14.0"
ariadne = "0.3.0"
[dependencies.lasso]
version = "0.7"
default-features = false
features = ["no-std"]
[dependencies.logos]
version = "0.13"
default-features = false
features = ["export_derive"]

View file

@ -0,0 +1,18 @@
jmp r0, start
start:
jmp r0, init_serial_port
-- Uses r20 to set the port
init_serial_port:
add r20, r30, r10
li r20, 00
-- outb(PORT + 1, 0x00); // Disable all interrupts
-- outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
-- outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud
-- outb(PORT + 1, 0x00); // (hi byte)
-- outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
-- outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
-- outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
-- outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip
-- outb(PORT + 0, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte)

65
hbasm/src/lib.rs Normal file
View file

@ -0,0 +1,65 @@
#![no_std]
#![feature(error_in_core)]
extern crate alloc;
pub mod text;
mod macros;
use {alloc::vec::Vec, hashbrown::HashSet};
#[derive(Default)]
pub struct Assembler {
pub buf: Vec<u8>,
sub: HashSet<usize>,
}
impl Assembler {
macros::impl_asm!(
bbbb(p0: u8, p1: u8, p2: u8, p3: u8)
=> [DIR, DIRF, FMAF],
bbb(p0: u8, p1: u8, p2: u8)
=> [ADD, SUB, MUL, AND, OR, XOR, SL, SR, SRS, CMP, CMPU, BRC, ADDF, SUBF, MULF],
bbdh(p0: u8, p1: u8, p2: impl Imm, p3: u16)
=> [LD, ST],
bbd(p0: u8, p1: u8, p2: impl Imm)
=> [ADDI, MULI, ANDI, ORI, XORI, SLI, SRI, SRSI, CMPI, CMPUI,
BMC, JEQ, JNE, JLT, JGT, JLTU, JGTU, ADDFI, MULFI],
bb(p0: u8, p1: u8)
=> [NEG, NOT, CP, SWA, NEGF, ITF, FTI],
bd(p0: u8, p1: impl Imm)
=> [LI, JMP],
n()
=> [NOP, ECALL],
);
}
pub trait Imm {
fn insert(&self, asm: &mut Assembler);
}
macro_rules! impl_imm_le_bytes {
($($ty:ty),* $(,)?) => {
$(
impl Imm for $ty {
#[inline(always)]
fn insert(&self, asm: &mut Assembler) {
asm.buf.extend(self.to_le_bytes());
}
}
)*
};
}
impl_imm_le_bytes!(u64, i64, f64);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Symbol(pub u64);
impl Imm for Symbol {
#[inline(always)]
fn insert(&self, asm: &mut Assembler) {
asm.sub.insert(asm.buf.len());
asm.buf.extend(self.0.to_le_bytes());
}
}

72
hbasm/src/macros.rs Normal file
View file

@ -0,0 +1,72 @@
macro_rules! impl_asm_opcodes {
(
$generic:ident
($($param_i:ident: $param_ty:ty),*)
=> []
) => {};
(
$generic:ident
($($param_i:ident: $param_ty:ty),*)
=> [$opcode:ident, $($rest:tt)*]
) => {
paste::paste! {
#[allow(dead_code)]
#[inline(always)]
pub fn [<i_ $opcode:lower>](&mut self, $($param_i: $param_ty),*) {
self.$generic(hbbytecode::opcode::$opcode, $($param_i),*)
}
}
macros::impl_asm_opcodes!(
$generic($($param_i: $param_ty),*)
=> [$($rest)*]
);
};
}
macro_rules! gen_impl_asm_insert {
($($ty:ident),* $(,)?) => {
macro_rules! impl_asm_insert {
$(($self:expr, $id:ident, $ty) => {
$self.buf.extend($id.to_le_bytes())
};)*
($self:expr, $id:ident, $_:ty) => {
Imm::insert(&$id, $self)
};
}
};
}
gen_impl_asm_insert!(u8, u16, u64);
macro_rules! impl_asm {
(
$(
$ityn:ident
($($param_i:ident: $param_ty:ty),* $(,)?)
=> [$($opcode:ident),* $(,)?],
)*
) => {
paste::paste! {
$(
#[allow(dead_code)]
fn [<i_param_ $ityn>](&mut self, opcode: u8, $($param_i: $param_ty),*) {
self.buf.push(opcode);
$(macros::impl_asm_insert!(self, $param_i, $param_ty);)*
}
macros::impl_asm_opcodes!(
[<i_param_ $ityn>]($($param_i: $param_ty),*)
=> [$($opcode,)*]
);
)*
}
};
}
pub(super) use {impl_asm, impl_asm_opcodes};
#[allow(clippy::single_component_path_imports)]
pub(super) use impl_asm_insert;

46
hbasm/src/main.rs Normal file
View file

@ -0,0 +1,46 @@
use {
ariadne::{ColorGenerator, Label, Report, ReportKind, Source},
std::{
error::Error,
io::{stdin, Read},
},
};
fn main() -> Result<(), Box<dyn Error>> {
let mut code = String::new();
stdin().read_to_string(&mut code)?;
let mut buf = vec![];
if let Err(e) = hbasm::text::assembly(&code, &mut buf) {
let mut colors = ColorGenerator::new();
let e_code = match e.kind {
hbasm::text::ErrorKind::UnexpectedToken => 1,
hbasm::text::ErrorKind::InvalidToken => 2,
hbasm::text::ErrorKind::UnexpectedEnd => 3,
hbasm::text::ErrorKind::InvalidSymbol => 4,
};
let message = match e.kind {
hbasm::text::ErrorKind::UnexpectedToken => "This token is not expected!",
hbasm::text::ErrorKind::InvalidToken => "The token is not valid!",
hbasm::text::ErrorKind::UnexpectedEnd => "The assembler reached the end of input unexpectedly!",
hbasm::text::ErrorKind::InvalidSymbol => "This referenced symbol doesn't have a corresponding label!",
};
let a = colors.next();
Report::build(ReportKind::Error, "engine_internal", e.span.clone().start)
.with_code(e_code)
.with_message(format!("{:?}", e.kind))
.with_label(
Label::new(("engine_internal", e.span.clone()))
.with_message(message)
.with_color(a),
)
.finish()
.eprint(("engine_internal", Source::from(&code)))
.unwrap();
}
Ok(())
}

273
hbasm/src/text.rs Normal file
View file

@ -0,0 +1,273 @@
extern crate alloc;
use alloc::vec::Vec;
use {
core::fmt::{Display, Formatter},
hashbrown::HashMap,
lasso::{Rodeo, Spur},
logos::{Lexer, Logos, Span},
};
macro_rules! tokendef {
($($opcode:literal),* $(,)?) => {
paste::paste! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Logos)]
#[logos(extras = Rodeo)]
#[logos(skip r"[ \t\f]+")]
#[logos(skip r"-- .*")]
pub enum Token {
$(#[token($opcode, |_| hbbytecode::opcode::[<$opcode:upper>])])*
OpCode(u8),
#[regex("[0-9]+", |l| l.slice().parse().ok())]
#[regex(
"-[0-9]+",
|lexer| {
Some(u64::from_ne_bytes(lexer.slice().parse::<i64>().ok()?.to_ne_bytes()))
},
)] Integer(u64),
#[regex(
"r[0-9]+",
|lexer| match lexer.slice()[1..].parse() {
Ok(n) => Some(n),
_ => None
},
)] Register(u8),
#[regex(
r"\p{XID_Start}\p{XID_Continue}*:",
|lexer| lexer.extras.get_or_intern(&lexer.slice()[..lexer.slice().len() - 1]),
)] Label(Spur),
#[regex(
r"\p{XID_Start}\p{XID_Continue}*",
|lexer| lexer.extras.get_or_intern(lexer.slice()),
)] Symbol(Spur),
#[token("\n")]
#[token(";")] ISep,
#[token(",")] PSep,
}
}
};
}
#[rustfmt::skip]
tokendef![
"nop", "add", "sub", "mul", "and", "or", "xor", "sl", "sr", "srs", "cmp", "cmpu",
"dir", "neg", "not", "addi", "muli", "andi", "ori", "xori", "sli", "sri", "srsi",
"cmpi", "cmpui", "cp", "swa", "li", "ld", "st", "bmc", "brc", "jmp", "jeq", "jne",
"jlt", "jgt", "jltu", "jgtu", "ecall", "addf", "subf", "mulf", "dirf", "fmaf", "negf",
"itf", "fti", "addfi", "mulfi",
];
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ErrorKind {
UnexpectedToken,
InvalidToken,
UnexpectedEnd,
InvalidSymbol,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub kind: ErrorKind,
pub span: Span,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "Error {:?} at {:?}", self.kind, self.span)
}
}
impl core::error::Error for Error {}
macro_rules! expect_matches {
($self:expr, $($pat:pat),* $(,)?) => {$(
let $pat = $self.next()?
else { return Err(ErrorKind::UnexpectedToken) };
)*}
}
pub fn assembly(code: &str, buf: &mut Vec<u8>) -> Result<(), Error> {
struct Assembler<'a> {
lexer: Lexer<'a, Token>,
buf: &'a mut Vec<u8>,
label_map: HashMap<Spur, u64>,
to_sub_label: HashMap<usize, Spur>,
}
impl<'a> Assembler<'a> {
fn next(&mut self) -> Result<Token, ErrorKind> {
match self.lexer.next() {
Some(Ok(t)) => Ok(t),
Some(Err(())) => Err(ErrorKind::InvalidToken),
None => Err(ErrorKind::UnexpectedEnd),
}
}
fn assemble(&mut self) -> Result<(), ErrorKind> {
use hbbytecode::opcode::*;
loop {
match self.lexer.next() {
Some(Ok(Token::OpCode(op))) => {
self.buf.push(op);
match op {
NOP | ECALL => Ok(()),
DIR | DIRF => {
expect_matches!(
self,
Token::Register(r0),
Token::PSep,
Token::Register(r1),
Token::PSep,
Token::Register(r2),
Token::PSep,
Token::Register(r3),
);
self.buf.extend([r0, r1, r2, r3]);
Ok(())
}
ADD..=CMPU | ADDF..=MULF => {
expect_matches!(
self,
Token::Register(r0),
Token::PSep,
Token::Register(r1),
Token::PSep,
Token::Register(r2),
);
self.buf.extend([r0, r1, r2]);
Ok(())
}
BRC => {
expect_matches!(
self,
Token::Register(r0),
Token::PSep,
Token::Register(r1),
Token::PSep,
Token::Integer(count),
);
self.buf.extend([
r0,
r1,
u8::try_from(count).map_err(|_| ErrorKind::UnexpectedToken)?,
]);
Ok(())
}
NEG..=NOT | CP..=SWA | NEGF..=FTI => {
expect_matches!(
self,
Token::Register(r0),
Token::PSep,
Token::Register(r1),
);
self.buf.extend([r0, r1]);
Ok(())
}
LI | JMP => {
expect_matches!(self, Token::Register(r0), Token::PSep);
self.buf.push(r0);
self.insert_imm()?;
Ok(())
}
ADDI..=CMPUI | BMC | JEQ..=JGTU | ADDFI..=MULFI => {
expect_matches!(
self,
Token::Register(r0),
Token::PSep,
Token::Register(r1),
Token::PSep,
);
self.buf.extend([r0, r1]);
self.insert_imm()?;
Ok(())
}
LD..=ST => {
expect_matches!(
self,
Token::Register(r0),
Token::PSep,
Token::Register(r1),
Token::PSep,
Token::Integer(offset),
Token::PSep,
Token::Integer(len),
);
self.buf.extend([r0, r1]);
self.buf.extend(offset.to_le_bytes());
self.buf.extend(
u16::try_from(len)
.map_err(|_| ErrorKind::InvalidToken)?
.to_le_bytes(),
);
Ok(())
}
_ => unreachable!(),
}?;
match self.next() {
Ok(Token::ISep) => (),
Ok(_) => return Err(ErrorKind::UnexpectedToken),
Err(ErrorKind::UnexpectedEnd) => return Ok(()),
Err(e) => return Err(e),
}
}
Some(Ok(Token::Label(lbl))) => {
self.label_map.insert(lbl, self.buf.len() as u64);
}
Some(Ok(Token::ISep)) => (),
Some(Ok(_)) => return Err(ErrorKind::UnexpectedToken),
Some(Err(())) => return Err(ErrorKind::InvalidToken),
None => return Ok(()),
}
}
}
fn link_local_syms(&mut self) -> Result<(), ErrorKind> {
for (ix, sym) in &self.to_sub_label {
self.label_map
.get(sym)
.ok_or(ErrorKind::InvalidSymbol)?
.to_le_bytes()
.iter()
.enumerate()
.for_each(|(i, b)| {
self.buf[ix + i] = *b;
});
}
Ok(())
}
fn insert_imm(&mut self) -> Result<(), ErrorKind> {
let imm = match self.next()? {
Token::Integer(i) => i.to_le_bytes(),
Token::Symbol(s) => {
self.to_sub_label.insert(self.buf.len(), s);
[0; 8]
}
_ => return Err(ErrorKind::UnexpectedToken),
};
self.buf.extend(imm);
Ok(())
}
}
let mut asm = Assembler {
lexer: Token::lexer(code),
label_map: Default::default(),
to_sub_label: Default::default(),
buf,
};
asm.assemble().map_err(|kind| Error {
kind,
span: asm.lexer.span(),
})?;
asm.link_local_syms()
.map_err(|kind| Error { kind, span: 0..0 })
}

View file

@ -1,9 +1,6 @@
[package]
name = "xtask"
name = "hbbytecode"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.89"
walrus = "0.22.0"

60
hbbytecode/hbbytecode.h Normal file
View file

@ -0,0 +1,60 @@
/* HoleyBytes Bytecode representation in C
* Requires C23 compiler or better
*/
#pragma once
#include <assert.h>
#include <limits.h>
#include <stdint.h>
static_assert(CHAR_BIT == 8, "Cursed architectures are not supported");
enum hbbc_Opcode: uint8_t {
hbbc_Op_NOP , hbbc_Op_ADD , hbbc_Op_SUB , hbbc_Op_MUL , hbbc_Op_AND , hbbc_Op_OR ,
hbbc_Op_XOR , hbbc_Op_SL , hbbc_Op_SR , hbbc_Op_SRS , hbbc_Op_CMP , hbbc_Op_CMPU ,
hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI , hbbc_Op_MULI , hbbc_Op_ANDI ,
hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI , hbbc_Op_SRSI , hbbc_Op_CMPI ,
hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI , hbbc_Op_LD , hbbc_Op_ST ,
hbbc_Op_BMC , hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JEQ , hbbc_Op_JNE , hbbc_Op_JLT ,
hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL , hbbc_Op_ADDF , hbbc_Op_SUBF ,
hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF , hbbc_Op_ITF , hbbc_Op_FTI ,
hbbc_Op_ADDFI , hbbc_Op_MULFI ,
} typedef hbbc_Opcode;
static_assert(sizeof(hbbc_Opcode) == 1);
#pragma pack(push, 1)
struct hbbc_ParamBBBB
{ uint8_t _0; uint8_t _1; uint8_t _2; uint8_t _3; }
typedef hbbc_ParamBBBB;
static_assert(sizeof(hbbc_ParamBBBB) == 32 / 8);
struct hbbc_ParamBBB
{ uint8_t _0; uint8_t _1; uint8_t _2; }
typedef hbbc_ParamBBB;
static_assert(sizeof(hbbc_ParamBBB) == 24 / 8);
struct hbbc_ParamBBDH
{ uint8_t _0; uint8_t _1; uint64_t _2; uint16_t _3; }
typedef hbbc_ParamBBDH;
static_assert(sizeof(hbbc_ParamBBDH) == 96 / 8);
struct hbbc_ParamBBD
{ uint8_t _0; uint8_t _1; uint64_t _2; }
typedef hbbc_ParamBBD;
static_assert(sizeof(hbbc_ParamBBD) == 80 / 8);
struct hbbc_ParamBB
{ uint8_t _0; uint8_t _1; }
typedef hbbc_ParamBB;
static_assert(sizeof(hbbc_ParamBB) == 16 / 8);
struct hbbc_ParamBD
{ uint8_t _0; uint64_t _1; }
typedef hbbc_ParamBD;
static_assert(sizeof(hbbc_ParamBD) == 72 / 8);
typedef uint64_t hbbc_ParamD;
static_assert(sizeof(hbbc_ParamD) == 64 / 8);
#pragma pack(pop)

107
hbbytecode/src/lib.rs Normal file
View file

@ -0,0 +1,107 @@
#![no_std]
macro_rules! constmod {
($vis:vis $mname:ident($repr:ty) {
$(#![doc = $mdoc:literal])?
$($cname:ident = $val:expr $(,$doc:literal)?;)*
}) => {
$(#[doc = $mdoc])?
$vis mod $mname {
$(
$(#[doc = $doc])?
pub const $cname: $repr = $val;
)*
}
};
}
constmod!(pub opcode(u8) {
//! Opcode constant module
NOP = 0, "N; Do nothing";
ADD = 1, "BBB; #0 ← #1 + #2";
SUB = 2, "BBB; #0 ← #1 - #2";
MUL = 3, "BBB; #0 ← #1 × #2";
AND = 4, "BBB; #0 ← #1 & #2";
OR = 5, "BBB; #0 ← #1 | #2";
XOR = 6, "BBB; #0 ← #1 ^ #2";
SL = 7, "BBB; #0 ← #1 « #2";
SR = 8, "BBB; #0 ← #1 » #2";
SRS = 9, "BBB; #0 ← #1 » #2 (signed)";
CMP = 10, "BBB; #0 ← #1 <=> #2";
CMPU = 11, "BBB; #0 ← #1 <=> #2 (unsigned)";
DIR = 12, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3";
NEG = 13, "BB; #0 ← -#1";
NOT = 14, "BB; #0 ← !#1";
ADDI = 15, "BBD; #0 ← #1 + imm #2";
MULI = 16, "BBD; #0 ← #1 × imm #2";
ANDI = 17, "BBD; #0 ← #1 & imm #2";
ORI = 18, "BBD; #0 ← #1 | imm #2";
XORI = 19, "BBD; #0 ← #1 ^ imm #2";
SLI = 20, "BBD; #0 ← #1 « imm #2";
SRI = 21, "BBD; #0 ← #1 » imm #2";
SRSI = 22, "BBD; #0 ← #1 » imm #2 (signed)";
CMPI = 23, "BBD; #0 ← #1 <=> imm #2";
CMPUI = 24, "BBD; #0 ← #1 <=> imm #2 (unsigned)";
CP = 25, "BB; Copy #0 ← #1";
SWA = 26, "BB; Swap #0 and #1";
LI = 27, "BD; #0 ← imm #1";
LD = 28, "BBDB; #0 ← [#1 + imm #3], imm #4 bytes, overflowing";
ST = 29, "BBDB; [#1 + imm #3] ← #0, imm #4 bytes, overflowing";
BMC = 30, "BBD; [#0] ← [#1], imm #2 bytes";
BRC = 31, "BBB; #0 ← #1, imm #2 registers";
JMP = 32, "BD; Unconditional jump [#0 + imm #1]";
JEQ = 33, "BBD; if #0 = #1 → jump imm #2";
JNE = 34, "BBD; if #0 ≠ #1 → jump imm #2";
JLT = 35, "BBD; if #0 < #1 → jump imm #2";
JGT = 36, "BBD; if #0 > #1 → jump imm #2";
JLTU = 37, "BBD; if #0 < #1 → jump imm #2 (unsigned)";
JGTU = 38, "BBD; if #0 > #1 → jump imm #2 (unsigned)";
ECALL = 39, "N; Issue system call";
ADDF = 40, "BBB; #0 ← #1 +. #2";
SUBF = 41, "BBB; #0 ← #1 -. #2";
MULF = 42, "BBB; #0 ← #1 +. #2";
DIRF = 43, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3";
FMAF = 44, "BBBB; #0 ← (#1 * #2) + #3";
NEGF = 45, "BB; #0 ← -#1";
ITF = 46, "BB; #0 ← #1 as float";
FTI = 47, "BB; #0 ← #1 as int";
ADDFI = 48, "BBD; #0 ← #1 +. imm #2";
MULFI = 49, "BBD; #0 ← #1 *. imm #2";
});
#[repr(packed)]
pub struct ParamBBBB(pub u8, pub u8, pub u8, pub u8);
#[repr(packed)]
pub struct ParamBBB(pub u8, pub u8, pub u8);
#[repr(packed)]
pub struct ParamBBDH(pub u8, pub u8, pub u64, pub u16);
#[repr(packed)]
pub struct ParamBBD(pub u8, pub u8, pub u64);
#[repr(packed)]
pub struct ParamBB(pub u8, pub u8);
#[repr(packed)]
pub struct ParamBD(pub u8, pub u64);
/// # Safety
/// Has to be valid to be decoded from bytecode.
pub unsafe trait OpParam {}
unsafe impl OpParam for ParamBBBB {}
unsafe impl OpParam for ParamBBB {}
unsafe impl OpParam for ParamBBDH {}
unsafe impl OpParam for ParamBBD {}
unsafe impl OpParam for ParamBB {}
unsafe impl OpParam for ParamBD {}
unsafe impl OpParam for u64 {}
unsafe impl OpParam for () {}

16
hbvm/Cargo.toml Normal file
View file

@ -0,0 +1,16 @@
[package]
name = "hbvm"
version = "0.1.0"
edition = "2021"
[profile.release]
lto = true
[dependencies]
delegate = "0.9"
derive_more = "0.99"
hashbrown = "0.13"
hbbytecode.path = "../hbbytecode"
log = "0.4"
paste = "1.0"
static_assertions = "1.0"

BIN
hbvm/assets/inf_loop.hb Normal file

Binary file not shown.

7
hbvm/src/lib.rs Normal file
View file

@ -0,0 +1,7 @@
#![doc = include_str!("../README.md")]
#![no_std]
extern crate alloc;
pub mod validate;
pub mod vm;

59
hbvm/src/main.rs Normal file
View file

@ -0,0 +1,59 @@
use hbvm::vm::{
mem::{Memory, MemoryAccessReason, PageSize},
trap::HandleTrap,
value::Value,
};
use {
hbvm::{validate::validate, vm::Vm},
std::io::{stdin, Read},
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut prog = vec![];
stdin().read_to_end(&mut prog)?;
if let Err(e) = validate(&prog) {
eprintln!("Program validation error: {e:?}");
return Ok(());
} else {
unsafe {
let mut vm = Vm::<_, 0>::new_unchecked(&prog, TestTrapHandler);
vm.memory.insert_test_page();
println!("Program interrupt: {:?}", vm.run());
println!("{:?}", vm.registers);
}
}
Ok(())
}
pub fn time() -> u32 {
9
}
struct TestTrapHandler;
impl HandleTrap for TestTrapHandler {
fn page_fault(
&mut self,
_: MemoryAccessReason,
_: &mut Memory,
_: u64,
_: PageSize,
_: *mut u8,
) -> bool {
false
}
fn invalid_op(&mut self, _: &mut [Value; 256], _: &mut usize, _: &mut Memory, _: u8) -> bool
where
Self: Sized,
{
false
}
fn ecall(&mut self, _: &mut [Value; 256], _: &mut usize, _: &mut Memory)
where
Self: Sized,
{
}
}

64
hbvm/src/validate.rs Normal file
View file

@ -0,0 +1,64 @@
//! Validate if program is sound to execute
/// Program validation error kind
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ErrorKind {
/// Unknown opcode
InvalidInstruction,
/// VM doesn't implement this valid opcode
Unimplemented,
/// Attempted to copy over register boundary
RegisterArrayOverflow,
}
/// Error
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Error {
/// Kind
pub kind: ErrorKind,
/// Location in bytecode
pub index: usize,
}
/// Perform bytecode validation. If it passes, the program should be
/// sound to execute.
pub fn validate(mut program: &[u8]) -> Result<(), Error> {
use hbbytecode::opcode::*;
let start = program;
loop {
// Match on instruction types and perform necessary checks
program = match program {
[] => return Ok(()),
[LD..=ST, reg, _, _, _, _, _, _, _, _, _, count, ..]
if usize::from(*reg) * 8 + usize::from(*count) > 2048 =>
{
return Err(Error {
kind: ErrorKind::RegisterArrayOverflow,
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
})
}
[BRC, src, dst, count, ..]
if src.checked_add(*count).is_none() || dst.checked_add(*count).is_none() =>
{
return Err(Error {
kind: ErrorKind::RegisterArrayOverflow,
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
})
}
[NOP | ECALL, rest @ ..]
| [DIR | DIRF, _, _, _, _, rest @ ..]
| [ADD..=CMPU | BRC | ADDF..=MULF, _, _, _, rest @ ..]
| [NEG..=NOT | CP..=SWA | NEGF..=FTI, _, _, rest @ ..]
| [LI | JMP, _, _, _, _, _, _, _, _, _, rest @ ..]
| [ADDI..=CMPUI | BMC | JEQ..=JGTU | ADDFI..=MULFI, _, _, _, _, _, _, _, _, _, _, rest @ ..]
| [LD..=ST, _, _, _, _, _, _, _, _, _, _, _, _, rest @ ..] => rest,
_ => {
return Err(Error {
kind: ErrorKind::InvalidInstruction,
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
})
}
}
}
}

430
hbvm/src/vm/mem/mod.rs Normal file
View file

@ -0,0 +1,430 @@
//! Program memory implementation
pub mod paging;
use {
self::paging::{PageTable, Permission, PtEntry},
super::{trap::HandleTrap, VmRunError},
alloc::boxed::Box,
core::mem::MaybeUninit,
derive_more::Display,
};
/// HoleyBytes virtual memory
#[derive(Clone, Debug)]
pub struct Memory {
/// Root page table
root_pt: *mut PageTable,
}
impl Default for Memory {
fn default() -> Self {
Self {
root_pt: Box::into_raw(Box::default()),
}
}
}
impl Drop for Memory {
fn drop(&mut self) {
let _ = unsafe { Box::from_raw(self.root_pt) };
}
}
impl Memory {
// HACK: Just for allocation testing, will be removed when proper memory interfaces
// implemented.
pub fn insert_test_page(&mut self) {
unsafe {
let mut entry = PtEntry::new(
{
let layout = alloc::alloc::Layout::from_size_align_unchecked(4096, 4096);
let ptr = alloc::alloc::alloc_zeroed(layout);
if ptr.is_null() {
alloc::alloc::handle_alloc_error(layout);
}
core::ptr::write_bytes(ptr, 69, 10);
ptr.cast()
},
Permission::Write,
);
for _ in 0..4 {
let mut pt = Box::<PageTable>::default();
pt[0] = entry;
entry = PtEntry::new(Box::into_raw(pt) as _, Permission::Node);
}
(*self.root_pt)[0] = entry;
}
}
/// Load value from an address
///
/// # Safety
/// Applies same conditions as for [`core::ptr::copy_nonoverlapping`]
pub unsafe fn load(
&mut self,
addr: u64,
target: *mut u8,
count: usize,
traph: &mut impl HandleTrap,
) -> Result<(), LoadError> {
self.memory_access(
MemoryAccessReason::Load,
addr,
target,
count,
|perm| {
matches!(
perm,
Permission::Readonly | Permission::Write | Permission::Exec
)
},
|src, dst, count| core::ptr::copy_nonoverlapping(src, dst, count),
traph,
)
.map_err(LoadError)
}
/// Store value to an address
///
/// # Safety
/// Applies same conditions as for [`core::ptr::copy_nonoverlapping`]
pub unsafe fn store(
&mut self,
addr: u64,
source: *const u8,
count: usize,
traph: &mut impl HandleTrap,
) -> Result<(), StoreError> {
self.memory_access(
MemoryAccessReason::Store,
addr,
source.cast_mut(),
count,
|perm| perm == Permission::Write,
|dst, src, count| core::ptr::copy_nonoverlapping(src, dst, count),
traph,
)
.map_err(StoreError)
}
/// Copy a block of memory
///
/// # Safety
/// - Same as for [`Self::load`] and [`Self::store`]
/// - Your faith in the gods of UB
/// - Addr-san claims it's fine but who knows is she isn't lying :ferrisSus:
pub unsafe fn block_copy(
&mut self,
src: u64,
dst: u64,
count: usize,
traph: &mut impl HandleTrap,
) -> Result<(), BlkCopyError> {
// Yea, i know it is possible to do this more efficiently, but I am too lazy.
const STACK_BUFFER_SIZE: usize = 512;
// Decide if to use stack-allocated buffer or to heap allocate
// Deallocation is again decided on size at the end of the function
let mut buf = MaybeUninit::<[u8; STACK_BUFFER_SIZE]>::uninit();
let buf = if count <= STACK_BUFFER_SIZE {
buf.as_mut_ptr().cast()
} else {
unsafe {
let layout = core::alloc::Layout::from_size_align_unchecked(count, 1);
let ptr = alloc::alloc::alloc(layout);
if ptr.is_null() {
alloc::alloc::handle_alloc_error(layout);
}
ptr
}
};
// Perform memory block transfer
let status = (|| {
// Load to buffer
self.memory_access(
MemoryAccessReason::Load,
src,
buf,
count,
|perm| {
matches!(
perm,
Permission::Readonly | Permission::Write | Permission::Exec
)
},
|src, dst, count| core::ptr::copy(src, dst, count),
traph,
)
.map_err(|addr| BlkCopyError {
access_reason: MemoryAccessReason::Load,
addr,
})?;
// Store from buffer
self.memory_access(
MemoryAccessReason::Store,
dst,
buf,
count,
|perm| perm == Permission::Write,
|dst, src, count| core::ptr::copy(src, dst, count),
traph,
)
.map_err(|addr| BlkCopyError {
access_reason: MemoryAccessReason::Store,
addr,
})?;
Ok::<_, BlkCopyError>(())
})();
// Deallocate if used heap-allocated array
if count > STACK_BUFFER_SIZE {
alloc::alloc::dealloc(
buf,
core::alloc::Layout::from_size_align_unchecked(count, 1),
);
}
status
}
/// 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,
src: u64,
mut dst: *mut u8,
len: usize,
permission_check: fn(Permission) -> bool,
action: fn(*mut u8, *mut u8, usize),
traph: &mut impl HandleTrap,
) -> Result<(), u64> {
let mut pspl = AddrSplitter::new(src, len, self.root_pt);
loop {
match pspl.next() {
// Page found
Some(Ok(AddrSplitOk {
vaddr,
ptr,
size,
perm,
})) => {
if !permission_check(perm) {
return Err(vaddr);
}
// Perform memory action and bump dst pointer
action(ptr, dst, size);
dst = unsafe { dst.add(size) };
}
Some(Err(AddrSplitError { addr, size })) => {
// Execute page fault handler
if traph.page_fault(reason, self, addr, size, dst) {
// Shift the splitter address
pspl.bump(size);
// Bump dst pointer
dst = unsafe { dst.add(size as _) };
} else {
return Err(addr); // Unhandleable
}
}
None => return Ok(()),
}
}
}
}
/// Result from address split
struct AddrSplitOk {
/// Virtual address
vaddr: u64,
/// Pointer to the start for perform operation
ptr: *mut u8,
/// Size to the end of page / end of desired size
size: usize,
/// Page permission
perm: Permission,
}
struct AddrSplitError {
/// Address of failure
addr: u64,
/// Requested page size
size: PageSize,
}
/// Address splitter into pages
struct AddrSplitter {
/// Current address
addr: u64,
/// Size left
size: usize,
/// Page table
pagetable: *const PageTable,
}
impl AddrSplitter {
/// Create a new page splitter
pub const fn new(addr: u64, size: usize, pagetable: *const PageTable) -> Self {
Self {
addr,
size,
pagetable,
}
}
/// Bump address by size X
fn bump(&mut self, page_size: PageSize) {
self.addr += page_size as u64;
self.size = self.size.saturating_sub(page_size as _);
}
}
impl Iterator for AddrSplitter {
type Item = Result<AddrSplitOk, AddrSplitError>;
fn next(&mut self) -> Option<Self::Item> {
// The end, everything is fine
if self.size == 0 {
return None;
}
let (base, perm, size, offset) = 'a: {
let mut current_pt = self.pagetable;
// Walk the page table
for lvl in (0..5).rev() {
// Get an entry
unsafe {
let entry = (*current_pt).get_unchecked(
usize::try_from((self.addr >> (lvl * 9 + 12)) & ((1 << 9) - 1))
.expect("?conradluget a better CPU"),
);
let ptr = entry.ptr();
match entry.permission() {
// No page → page fault
Permission::Empty => {
return Some(Err(AddrSplitError {
addr: self.addr,
size: PageSize::from_lvl(lvl)?,
}))
}
// Node → proceed waking
Permission::Node => current_pt = ptr as _,
// Leaft → return relevant data
perm => {
break 'a (
// Pointer in host memory
ptr as *mut u8,
perm,
PageSize::from_lvl(lvl)?,
// In-page offset
self.addr as usize & ((1 << (lvl * 9 + 12)) - 1),
);
}
}
}
}
return None; // Reached the end (should not happen)
};
// Get available byte count in the selected page with offset
let avail = (size as usize - offset).clamp(0, self.size);
self.bump(size);
Some(Ok(AddrSplitOk {
vaddr: self.addr,
ptr: unsafe { base.add(offset) }, // Return pointer to the start of region
size: avail,
perm,
}))
}
}
/// Page size
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PageSize {
/// 4 KiB page (on level 0)
Size4K = 4096,
/// 2 MiB page (on level 1)
Size2M = 1024 * 1024 * 2,
/// 1 GiB page (on level 2)
Size1G = 1024 * 1024 * 1024,
}
impl PageSize {
/// Convert page table level to size of page
fn from_lvl(lvl: u8) -> Option<Self> {
match lvl {
0 => Some(PageSize::Size4K),
1 => Some(PageSize::Size2M),
2 => Some(PageSize::Size1G),
_ => None,
}
}
}
/// Unhandled load access trap
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)]
pub struct LoadError(u64);
/// Unhandled store access trap
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)]
pub struct StoreError(u64);
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)]
pub enum MemoryAccessReason {
Load,
Store,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BlkCopyError {
access_reason: MemoryAccessReason,
addr: u64,
}
impl From<BlkCopyError> for VmRunError {
fn from(value: BlkCopyError) -> Self {
match value.access_reason {
MemoryAccessReason::Load => Self::LoadAccessEx(value.addr),
MemoryAccessReason::Store => Self::StoreAccessEx(value.addr),
}
}
}
impl From<LoadError> for VmRunError {
fn from(value: LoadError) -> Self {
Self::LoadAccessEx(value.0)
}
}
impl From<StoreError> for VmRunError {
fn from(value: StoreError) -> Self {
Self::StoreAccessEx(value.0)
}
}

152
hbvm/src/vm/mem/paging.rs Normal file
View file

@ -0,0 +1,152 @@
//! Page table and associated structures implementation
use {
core::{
fmt::Debug,
mem::MaybeUninit,
ops::{Index, IndexMut},
slice::SliceIndex,
},
delegate::delegate,
};
/// Page entry permission
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[repr(u8)]
pub enum Permission {
/// No page present
#[default]
Empty,
/// Points to another pagetable
Node,
/// Page is read only
Readonly,
/// Page is readable and writable
Write,
/// Page is readable and executable
Exec,
}
/// Page table entry
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub struct PtEntry(u64);
impl PtEntry {
/// Create new
///
/// # Safety
/// - `ptr` has to point to valid data and shall not be deallocated
/// troughout the entry lifetime
#[inline]
pub unsafe fn new(ptr: *mut PtPointedData, permission: Permission) -> Self {
Self(ptr as u64 | permission as u64)
}
/// Get permission
#[inline]
pub fn permission(&self) -> Permission {
unsafe { core::mem::transmute(self.0 as u8 & 0b111) }
}
/// Get pointer to the data (leaf) or next page table (node)
#[inline]
pub fn ptr(&self) -> *mut PtPointedData {
(self.0 & !((1 << 12) - 1)) as _
}
}
impl Debug for PtEntry {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("PtEntry")
.field("ptr", &self.ptr())
.field("permission", &self.permission())
.finish()
}
}
/// Page table
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(align(4096))]
pub struct PageTable([PtEntry; 512]);
impl PageTable {
delegate!(to self.0 {
/// Returns a reference to an element or subslice depending on the type of
/// index.
///
/// - If given a position, returns a reference to the element at that
/// position or `None` if out of bounds.
/// - If given a range, returns the subslice corresponding to that range,
/// or `None` if out of bounds.
///
pub fn get<I>(&self, ix: I) -> Option<&I::Output>
where I: SliceIndex<[PtEntry]>;
/// Returns a mutable reference to an element or subslice depending on the
/// type of index (see [`get`]) or `None` if the index is out of bounds.
pub fn get_mut<I>(&mut self, ix: I) -> Option<&mut I::Output>
where I: SliceIndex<[PtEntry]>;
/// Returns a reference to an element or subslice, without doing bounds
/// checking.
///
/// For a safe alternative see [`get`].
///
/// # Safety
///
/// Calling this method with an out-of-bounds index is *[undefined behavior]*
/// even if the resulting reference is not used.
pub unsafe fn get_unchecked<I>(&self, index: I) -> &I::Output
where I: SliceIndex<[PtEntry]>;
/// Returns a mutable reference to an element or subslice, without doing
/// bounds checking.
///
/// For a safe alternative see [`get_mut`].
///
/// # Safety
///
/// Calling this method with an out-of-bounds index is *[undefined behavior]*
/// even if the resulting reference is not used.
pub unsafe fn get_unchecked_mut<I>(&mut self, index: I) -> &mut I::Output
where I: SliceIndex<[PtEntry]>;
});
}
impl<Idx> Index<Idx> for PageTable
where
Idx: SliceIndex<[PtEntry]>,
{
type Output = Idx::Output;
#[inline(always)]
fn index(&self, index: Idx) -> &Self::Output {
&self.0[index]
}
}
impl<Idx> IndexMut<Idx> for PageTable
where
Idx: SliceIndex<[PtEntry]>,
{
#[inline(always)]
fn index_mut(&mut self, index: Idx) -> &mut Self::Output {
&mut self.0[index]
}
}
impl Default for PageTable {
fn default() -> Self {
// SAFETY: It's fine, zeroed page table entry is valid (= empty)
Self(unsafe { MaybeUninit::zeroed().assume_init() })
}
}
/// Data page table entry can possibly point to
#[derive(Clone, Copy)]
#[repr(C, align(4096))]
pub union PtPointedData {
/// Node - next page table
pub pt: PageTable,
/// Leaf
pub page: u8,
}

373
hbvm/src/vm/mod.rs Normal file
View file

@ -0,0 +1,373 @@
//! HoleyBytes Virtual Machine
//!
//! All unsafe code here should be sound, if input bytecode passes validation.
// # General safety notice:
// - Validation has to assure there is 256 registers (r0 - r255)
// - Instructions have to be valid as specified (values and sizes)
// - Mapped pages should be at least 4 KiB
// - Yes, I am aware of the UB when jumping in-mid of instruction where
// the read byte corresponds to an instruction whose lenght exceets the
// program size. If you are (rightfully) worried about the UB, for now just
// append your program with 11 zeroes.
use self::trap::HandleTrap;
pub mod mem;
pub mod trap;
pub mod value;
use {
crate::validate,
core::ops,
hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBD},
mem::Memory,
static_assertions::assert_impl_one,
value::Value,
};
/// Extract a parameter from program
macro_rules! param {
($self:expr, $ty:ty) => {{
assert_impl_one!($ty: OpParam);
let data = $self
.program
.as_ptr()
.add($self.pc + 1)
.cast::<$ty>()
.read();
$self.pc += 1 + core::mem::size_of::<$ty>();
data
}};
}
/// Perform binary operation `#0 ← #1 OP #2`
macro_rules! binary_op {
($self:expr, $ty:ident, $handler:expr) => {{
let ParamBBB(tg, a0, a1) = param!($self, ParamBBB);
$self.write_reg(
tg,
$handler(
Value::$ty(&$self.read_reg(a0)),
Value::$ty(&$self.read_reg(a1)),
),
);
}};
}
/// Perform binary operation with immediate `#0 ← #1 OP imm #2`
macro_rules! binary_op_imm {
($self:expr, $ty:ident, $handler:expr) => {{
let ParamBBD(tg, a0, imm) = param!($self, ParamBBD);
$self.write_reg(
tg,
$handler(Value::$ty(&$self.read_reg(a0)), Value::$ty(&imm.into())),
);
}};
}
/// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected
macro_rules! cond_jump {
($self:expr, $ty:ident, $expected:ident) => {{
let ParamBBD(a0, a1, jt) = param!($self, ParamBBD);
if core::cmp::Ord::cmp(&$self.read_reg(a0).as_u64(), &$self.read_reg(a1).as_u64())
== core::cmp::Ordering::$expected
{
$self.pc = jt as usize;
}
}};
}
/// HoleyBytes Virtual Machine
pub struct Vm<'a, T, const TIMER_QUOTIENT: usize> {
/// Holds 256 registers
///
/// Writing to register 0 is considered undefined behaviour
/// in terms of HoleyBytes program execution
pub registers: [Value; 256],
/// Memory implementation
pub memory: Memory,
/// Trap handler
pub traph: T,
// Program counter
pc: usize,
/// Program
program: &'a [u8],
/// Program timer
timer: usize,
}
impl<'a, T: HandleTrap, const TIMER_QUOTIENT: usize> Vm<'a, T, TIMER_QUOTIENT> {
/// Create a new VM with program and trap handler
///
/// # Safety
/// Program code has to be validated
pub unsafe fn new_unchecked(program: &'a [u8], traph: T) -> Self {
Self {
registers: [Value::from(0_u64); 256],
memory: Default::default(),
traph,
pc: 0,
program,
timer: 0,
}
}
/// Create a new VM with program and trap handler only if it passes validation
pub fn new_validated(program: &'a [u8], traph: T) -> Result<Self, validate::Error> {
validate::validate(program)?;
Ok(unsafe { Self::new_unchecked(program, traph) })
}
/// Execute program
///
/// Program can return [`VmRunError`] if a trap handling failed
pub fn run(&mut self) -> Result<VmRunOk, VmRunError> {
use hbbytecode::opcode::*;
loop {
// Fetch instruction
let Some(&opcode) = self.program.get(self.pc)
else { return Ok(VmRunOk::End) };
// Big match
unsafe {
match opcode {
NOP => param!(self, ()),
ADD => binary_op!(self, as_u64, u64::wrapping_add),
SUB => binary_op!(self, as_u64, u64::wrapping_sub),
MUL => binary_op!(self, as_u64, u64::wrapping_mul),
AND => binary_op!(self, as_u64, ops::BitAnd::bitand),
OR => binary_op!(self, as_u64, ops::BitOr::bitor),
XOR => binary_op!(self, as_u64, ops::BitXor::bitxor),
SL => binary_op!(self, as_u64, ops::Shl::shl),
SR => binary_op!(self, as_u64, ops::Shr::shr),
SRS => binary_op!(self, as_i64, ops::Shr::shr),
CMP => {
let ParamBBB(tg, a0, a1) = param!(self, ParamBBB);
self.write_reg(
tg,
self.read_reg(a0).as_i64().cmp(&self.read_reg(a1).as_i64()) as i64,
);
}
CMPU => {
let ParamBBB(tg, a0, a1) = param!(self, ParamBBB);
self.write_reg(
tg,
self.read_reg(a0).as_u64().cmp(&self.read_reg(a1).as_u64()) as i64,
);
}
NOT => {
let param = param!(self, ParamBB);
self.write_reg(param.0, !self.read_reg(param.1).as_u64());
}
NEG => {
let param = param!(self, ParamBB);
self.write_reg(
param.0,
match self.read_reg(param.1).as_u64() {
0 => 1_u64,
_ => 0,
},
);
}
DIR => {
let ParamBBBB(dt, rt, a0, a1) = param!(self, ParamBBBB);
let a0 = self.read_reg(a0).as_u64();
let a1 = self.read_reg(a1).as_u64();
self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX));
self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX));
}
ADDI => binary_op_imm!(self, as_u64, ops::Add::add),
MULI => binary_op_imm!(self, as_u64, ops::Mul::mul),
ANDI => binary_op_imm!(self, as_u64, ops::BitAnd::bitand),
ORI => binary_op_imm!(self, as_u64, ops::BitOr::bitor),
XORI => binary_op_imm!(self, as_u64, ops::BitXor::bitxor),
SLI => binary_op_imm!(self, as_u64, ops::Shl::shl),
SRI => binary_op_imm!(self, as_u64, ops::Shr::shr),
SRSI => binary_op_imm!(self, as_i64, ops::Shr::shr),
CMPI => {
let ParamBBD(tg, a0, imm) = param!(self, ParamBBD);
self.write_reg(
tg,
self.read_reg(a0).as_i64().cmp(&Value::from(imm).as_i64()) as i64,
);
}
CMPUI => {
let ParamBBD(tg, a0, imm) = param!(self, ParamBBD);
self.write_reg(tg, self.read_reg(a0).as_u64().cmp(&imm) as i64);
}
CP => {
let param = param!(self, ParamBB);
self.write_reg(param.0, self.read_reg(param.1));
}
SWA => {
let ParamBB(src, dst) = param!(self, ParamBB);
if src + dst != 0 {
core::ptr::swap(
self.registers.get_unchecked_mut(usize::from(src)),
self.registers.get_unchecked_mut(usize::from(dst)),
);
}
}
LI => {
let param = param!(self, ParamBD);
self.write_reg(param.0, param.1);
}
LD => {
let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH);
let n: usize = match dst {
0 => 1,
_ => 0,
};
self.memory.load(
self.read_reg(base).as_u64() + off + n as u64,
self.registers.as_mut_ptr().add(usize::from(dst) + n).cast(),
usize::from(count).saturating_sub(n),
&mut self.traph,
)?;
}
ST => {
let ParamBBDH(dst, base, off, count) = param!(self, ParamBBDH);
self.memory.store(
self.read_reg(base).as_u64() + off,
self.registers.as_ptr().add(usize::from(dst)).cast(),
count.into(),
&mut self.traph,
)?;
}
BMC => {
let ParamBBD(src, dst, count) = param!(self, ParamBBD);
self.memory.block_copy(
self.read_reg(src).as_u64(),
self.read_reg(dst).as_u64(),
count as _,
&mut self.traph,
)?;
}
BRC => {
let ParamBBB(src, dst, count) = param!(self, ParamBBB);
core::ptr::copy(
self.registers.get_unchecked(usize::from(src)),
self.registers.get_unchecked_mut(usize::from(dst)),
usize::from(count * 8),
);
}
JMP => {
let ParamBD(reg, offset) = param!(self, ParamBD);
self.pc = (self.read_reg(reg).as_u64() + offset) as usize;
}
JEQ => cond_jump!(self, int, Equal),
JNE => {
let ParamBBD(a0, a1, jt) = param!(self, ParamBBD);
if self.read_reg(a0).as_u64() != self.read_reg(a1).as_u64() {
self.pc = jt as usize;
}
}
JLT => cond_jump!(self, int, Less),
JGT => cond_jump!(self, int, Greater),
JLTU => cond_jump!(self, sint, Less),
JGTU => cond_jump!(self, sint, Greater),
ECALL => {
param!(self, ());
self.traph
.ecall(&mut self.registers, &mut self.pc, &mut self.memory);
}
ADDF => binary_op!(self, as_f64, ops::Add::add),
SUBF => binary_op!(self, as_f64, ops::Sub::sub),
MULF => binary_op!(self, as_f64, ops::Mul::mul),
DIRF => {
let ParamBBBB(dt, rt, a0, a1) = param!(self, ParamBBBB);
let a0 = self.read_reg(a0).as_f64();
let a1 = self.read_reg(a1).as_f64();
self.write_reg(dt, a0 / a1);
self.write_reg(rt, a0 % a1);
}
FMAF => {
let ParamBBBB(dt, a0, a1, a2) = param!(self, ParamBBBB);
self.write_reg(
dt,
self.read_reg(a0).as_f64() * self.read_reg(a1).as_f64()
+ self.read_reg(a2).as_f64(),
);
}
NEGF => {
let ParamBB(dt, a0) = param!(self, ParamBB);
self.write_reg(dt, -self.read_reg(a0).as_f64());
}
ITF => {
let ParamBB(dt, a0) = param!(self, ParamBB);
self.write_reg(dt, self.read_reg(a0).as_i64() as f64);
}
FTI => {
let ParamBB(dt, a0) = param!(self, ParamBB);
self.write_reg(dt, self.read_reg(a0).as_f64() as i64);
}
ADDFI => binary_op_imm!(self, as_f64, ops::Add::add),
MULFI => binary_op_imm!(self, as_f64, ops::Mul::mul),
op => {
if !self.traph.invalid_op(
&mut self.registers,
&mut self.pc,
&mut self.memory,
op,
) {
return Err(VmRunError::InvalidOpcodeEx(op));
}
}
}
}
if TIMER_QUOTIENT != 0 {
self.timer = self.timer.wrapping_add(1);
if self.timer % TIMER_QUOTIENT == 0 {
return Ok(VmRunOk::Timer);
}
}
}
}
/// Read register
#[inline]
unsafe fn read_reg(&self, n: u8) -> Value {
*self.registers.get_unchecked(n as usize)
}
/// Write a register.
/// Writing to register 0 is no-op.
#[inline]
unsafe fn write_reg(&mut self, n: u8, value: impl Into<Value>) {
if n != 0 {
*self.registers.get_unchecked_mut(n as usize) = value.into();
}
}
}
/// Virtual machine halt error
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum VmRunError {
/// Unhandled invalid opcode exceptions
InvalidOpcodeEx(u8),
/// Unhandled load access exception
LoadAccessEx(u64),
/// Unhandled store access exception
StoreAccessEx(u64),
}
/// Virtual machine halt ok
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum VmRunOk {
/// Program has eached its end
End,
/// Program was interrupted by a timer
Timer,
}

35
hbvm/src/vm/trap.rs Normal file
View file

@ -0,0 +1,35 @@
//! Program trap handling interfaces
use super::{
mem::{Memory, MemoryAccessReason, PageSize},
value::Value,
};
/// Handle VM traps
pub trait HandleTrap {
/// Handle page fault
fn page_fault(
&mut self,
reason: MemoryAccessReason,
memory: &mut Memory,
vaddr: u64,
size: PageSize,
dataptr: *mut u8,
) -> bool;
/// Handle invalid opcode exception
fn invalid_op(
&mut self,
regs: &mut [Value; 256],
pc: &mut usize,
memory: &mut Memory,
op: u8,
) -> bool
where
Self: Sized;
/// Handle environment calls
fn ecall(&mut self, regs: &mut [Value; 256], pc: &mut usize, memory: &mut Memory)
where
Self: Sized;
}

48
hbvm/src/vm/value.rs Normal file
View file

@ -0,0 +1,48 @@
//! HoleyBytes register value definition
use core::fmt::Debug;
/// Define [`Value`] union
///
/// # Safety
/// Union variants have to be sound to byte-reinterpretate
/// between each other. Otherwise the behaviour is undefined.
macro_rules! value_def {
($($ty:ident),* $(,)?) => {
/// HBVM register value
#[derive(Copy, Clone)]
#[repr(packed)]
pub union Value {
$(pub $ty: $ty),*
}
paste::paste! {
impl Value {$(
#[doc = "Byte-reinterpret [`Value`] as [`" $ty "`]"]
#[inline]
pub fn [<as_ $ty>](&self) -> $ty {
unsafe { self.$ty }
}
)*}
}
$(
impl From<$ty> for Value {
#[inline]
fn from(value: $ty) -> Self {
Self { $ty: value }
}
}
)*
};
}
value_def!(u64, i64, f64);
static_assertions::const_assert_eq!(core::mem::size_of::<Value>(), 8);
impl Debug for Value {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// Print formatted as hexadecimal, unsigned integer
write!(f, "{:x}", self.as_u64())
}
}

View file

@ -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"]

File diff suppressed because one or more lines are too long

View file

@ -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]

View file

@ -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}";
}
}

View file

@ -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()
}

View file

@ -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);
}
}

View file

@ -1,3 +0,0 @@
fn main() {
hblang::fuzz::fuzz(0..1000000);
}

View file

@ -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))
}

File diff suppressed because it is too large Load diff

View file

@ -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);
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,772 +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);
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));
}
res.node_to_reg[MEM as usize] = res.bundles.len() as u8 + 1;
let reg_offset = if tail { reg::RET + 12 } else { reg::RET_ADDR + 1 };
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;
}
}
});
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;
let bundle_count = self.ralloc.bundles.len() + (reg_offset as usize);
(
if tail {
assert!(bundle_count < reg::STACK_PTR as usize, "TODO: spill memory");
self.ralloc.bundles.len()
} else {
bundle_count.saturating_sub((reg::RET_ADDR - 1) as _)
},
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);
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);
while !self[block].kind.starts_basic_block() {
block = self.idom(block);
}
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);
}
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, "{:?}", range);
let new = range.end.min(
s.instr_of(uinst)
.filter(|_| {
n == cursor
&& self.nodes.loop_depth(dom) == self.nodes.loop_depth(cursor)
})
.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));
while let Some(nid) = self.res.dfs_buf.pop() {
debug_assert!(self.nodes.dominates(until, nid), "{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
}
}

View file

@ -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;

View file

@ -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(())

View file

@ -1,6 +0,0 @@
main:
CP r1, r0
JALA r0, r31, 0a
code size: 22
ret: 0
status: Ok(())

View file

@ -1,6 +0,0 @@
main:
CP r1, r0
JALA r0, r31, 0a
code size: 22
ret: 0
status: Ok(())

View file

@ -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(())

View file

@ -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(())

View file

@ -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(())

View file

@ -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(())

View file

@ -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(())

View file

@ -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(())

View file

@ -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(())

View file

@ -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(())

View file

@ -1,6 +0,0 @@
main:
CP r1, r0
JALA r0, r31, 0a
code size: 22
ret: 0
status: Ok(())

View file

@ -1,7 +0,0 @@
main:
LI32 r13, 69w
CP r1, r13
JALA r0, r31, 0a
code size: 28
ret: 69
status: Ok(())

View file

@ -1,6 +0,0 @@
main:
CP r1, r0
JALA r0, r31, 0a
code size: 22
ret: 0
status: Ok(())

View file

@ -1,5 +0,0 @@
main:
UN
code size: 9
ret: 0
status: Err(Unreachable)

View file

@ -1,84 +0,0 @@
main:
ADDI64 r254, r254, -144d
ST r31, r254, 80a, 64h
LRA r32, r0, :glob_stru
JAL r31, r0, :new_stru
ST r1, r32, 0a, 16h
CP r33, r0
LD r34, r32, 0a, 8h
JEQ r34, r33, :0
LI64 r32, 300d
CP r1, r32
JMP :1
0: LI64 r35, 1d
ST r35, r32, 0a, 8h
ST r35, r32, 8a, 8h
ST r33, r32, 0a, 8h
LD r34, r32, 0a, 8h
JEQ r34, r33, :2
LI64 r32, 200d
CP r1, r32
JMP :1
2: LI64 r36, 3d
ST r35, r32, 0a, 8h
ST r35, r32, 8a, 8h
ADDI64 r37, r254, 16d
ST r35, r254, 16a, 8h
ST r35, r254, 24a, 8h
ST r35, r254, 32a, 8h
ST r35, r254, 40a, 8h
ST r35, r254, 48a, 8h
ST r35, r254, 56a, 8h
CP r32, r33
8: JNE r32, r36, :3
LD r32, r254, 48a, 8h
JEQ r32, r33, :4
LI64 r32, 100d
CP r1, r32
JMP :1
4: ST r33, r254, 0a, 8h
ST r33, r254, 8a, 8h
ST r33, r254, 64a, 8h
ST r33, r254, 72a, 8h
ST r35, r254, 16a, 8h
ST r35, r254, 24a, 8h
ST r35, r254, 32a, 8h
ST r35, r254, 40a, 8h
ST r35, r254, 48a, 8h
ST r35, r254, 56a, 8h
CP r32, r33
7: LD r38, r254, 48a, 8h
JNE r32, r36, :5
JEQ r38, r33, :6
LI64 r32, 10d
CP r1, r32
JMP :1
6: CP r1, r33
JMP :1
5: ADD64 r34, r32, r35
MULI64 r32, r32, 16d
ADD64 r32, r37, r32
ST r33, r32, 0a, 8h
ST r33, r32, 8a, 8h
CP r32, r34
JMP :7
3: MULI64 r34, r32, 16d
ADD64 r34, r37, r34
JAL r31, r0, :new_stru
ST r1, r34, 0a, 16h
ADD64 r32, r32, r35
JMP :8
1: LD r31, r254, 80a, 64h
ADDI64 r254, r254, 144d
JALA r0, r31, 0a
new_stru:
ADDI64 r254, r254, -16d
ADDI64 r13, r254, 0d
ST r0, r254, 0a, 8h
ST r0, r254, 8a, 8h
LD r1, r13, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 765
ret: 0
status: Ok(())

View file

@ -1,29 +0,0 @@
main:
ADDI64 r254, r254, -12d
LI8 r13, 255b
ST r13, r254, 0a, 1h
ST r0, r254, 1a, 1h
ST r0, r254, 2a, 1h
ST r13, r254, 3a, 1h
ST r0, r254, 4a, 4h
LD r13, r254, 4a, 4h
LI32 r14, 2w
ST r14, r254, 8a, 4h
LD r14, r254, 8a, 4h
LI64 r15, 2d
ANDI r14, r14, 4294967295d
JEQ r14, r15, :0
CP r1, r0
JMP :1
0: ANDI r13, r13, 4294967295d
JEQ r13, r0, :2
LI64 r13, 64d
CP r1, r13
JMP :1
2: LI64 r13, 512d
CP r1, r13
1: ADDI64 r254, r254, 12d
JALA r0, r31, 0a
code size: 235
ret: 512
status: Ok(())

View file

@ -1,22 +0,0 @@
main:
ADDI64 r254, r254, -16d
LI64 r13, 10d
ADDI64 r14, r254, 0d
ST r13, r254, 0a, 8h
LI64 r13, 20d
ST r13, r254, 8a, 8h
LI64 r13, 6d
LI64 r15, 5d
LI64 r16, 1d
CP r2, r16
CP r5, r15
CP r6, r13
LD r3, r14, 0a, 16h
ECA
CP r1, r0
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
ev: Ecall
code size: 154
ret: 0
status: Ok(())

View file

@ -1,20 +0,0 @@
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
JAL r31, r0, :some_enum
CP r32, r1
ANDI r32, r32, 255d
JNE r32, r0, :0
CP r1, r0
JMP :1
0: LI64 r32, 100d
CP r1, r32
1: LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
some_enum:
CP r1, r0
JALA r0, r31, 0a
code size: 128
ret: 0
status: Ok(())

View file

@ -1,114 +0,0 @@
continue_and_state_change:
CP r13, r2
CP r15, r0
LI64 r16, 3d
LI64 r14, 4d
LI64 r17, 2d
LI64 r18, 10d
6: JLTU r13, r18, :0
JMP :1
0: JNE r13, r17, :2
CP r13, r14
JMP :3
2: JNE r13, r16, :4
CP r13, r15
1: CP r1, r13
JMP :5
4: ADDI64 r13, r13, 1d
3: JMP :6
5: JALA r0, r31, 0a
infinite_loop:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
LI64 r34, 1d
CP r33, r0
CP r32, r33
1: JNE r32, r34, :0
JMP :0
0: CP r2, r33
JAL r31, r0, :continue_and_state_change
CP r32, r1
JMP :1
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r2, r0
JAL r31, r0, :multiple_breaks
CP r32, r1
LI64 r33, 3d
JEQ r32, r33, :0
LI64 r32, 1d
CP r1, r32
JMP :1
0: LI64 r32, 4d
CP r2, r32
JAL r31, r0, :multiple_breaks
CP r34, r1
LI64 r35, 10d
JEQ r34, r35, :2
LI64 r32, 2d
CP r1, r32
JMP :1
2: CP r2, r0
JAL r31, r0, :state_change_in_break
CP r34, r1
JEQ r34, r0, :3
CP r1, r33
JMP :1
3: CP r2, r32
JAL r31, r0, :state_change_in_break
CP r34, r1
JEQ r34, r35, :4
CP r1, r32
JMP :1
4: CP r2, r35
JAL r31, r0, :continue_and_state_change
CP r32, r1
JEQ r32, r35, :5
LI64 r32, 5d
CP r1, r32
JMP :1
5: CP r2, r33
JAL r31, r0, :continue_and_state_change
CP r32, r1
JEQ r32, r0, :6
LI64 r32, 6d
CP r1, r32
JMP :1
6: JAL r31, r0, :infinite_loop
CP r1, r0
1: LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
multiple_breaks:
CP r13, r2
LI64 r14, 3d
LI64 r15, 10d
4: JLTU r13, r15, :0
JMP :1
0: ADDI64 r13, r13, 1d
JNE r13, r14, :2
1: CP r1, r13
JMP :3
2: JMP :4
3: JALA r0, r31, 0a
state_change_in_break:
CP r13, r2
LI64 r14, 3d
LI64 r15, 10d
4: JLTU r13, r15, :0
JMP :1
0: JNE r13, r14, :2
CP r13, r0
1: CP r1, r13
JMP :3
2: ADDI64 r13, r13, 1d
JMP :4
3: JALA r0, r31, 0a
timed out
code size: 667
ret: 10
status: Ok(())

View file

@ -1,55 +0,0 @@
check_platform:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
JAL r31, r0, :x86_fb_ptr
CP r32, r1
CP r1, r32
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -56d
ST r31, r254, 0a, 56h
JAL r31, r0, :check_platform
CP r33, r0
LI64 r36, 30d
LI64 r37, 100d
CP r35, r33
CP r34, r33
CP r32, r33
5: JLTU r32, r36, :0
ADDI64 r34, r34, 1d
CP r2, r33
CP r3, r34
CP r4, r36
JAL r31, r0, :set_pixel
CP r32, r1
JEQ r32, r35, :1
CP r1, r33
JMP :2
1: JNE r34, r37, :3
CP r1, r35
JMP :2
3: CP r32, r33
JMP :4
0: ADDI64 r35, r35, 1d
ADDI64 r32, r32, 1d
4: JMP :5
2: LD r31, r254, 0a, 56h
ADDI64 r254, r254, 56d
JALA r0, r31, 0a
set_pixel:
CP r13, r2
CP r14, r3
CP r15, r4
MUL64 r14, r14, r15
ADD64 r13, r14, r13
CP r1, r13
JALA r0, r31, 0a
x86_fb_ptr:
LI64 r13, 100d
CP r1, r13
JALA r0, r31, 0a
code size: 329
ret: 3000
status: Ok(())

View file

@ -1,7 +0,0 @@
main:
LI32 r13, 3212836864w
CP r1, r13
JALA r0, r31, 0a
code size: 28
ret: 3212836864
status: Ok(())

View file

@ -1,29 +0,0 @@
add_one:
CP r13, r2
ADDI64 r13, r13, 1d
CP r1, r13
JALA r0, r31, 0a
add_two:
CP r13, r2
ADDI64 r13, r13, 2d
CP r1, r13
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LI64 r32, 10d
CP r2, r32
JAL r31, r0, :add_one
CP r32, r1
LI64 r33, 20d
CP r2, r33
JAL r31, r0, :add_two
CP r33, r1
ADD64 r32, r33, r32
CP r1, r32
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 176
ret: 33
status: Ok(())

View file

@ -1,35 +0,0 @@
add:
CP r13, r2
CP r14, r3
ADD64 r13, r13, r14
CP r1, r13
JALA r0, r31, 0a
add:
CP r13, r2
CP r14, r3
ADD32 r13, r13, r14
CP r1, r13
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
LI32 r32, 2w
CP r2, r32
CP r3, r32
JAL r31, r0, :add
CP r32, r1
LI64 r33, 3d
LI64 r34, 1d
CP r2, r34
CP r3, r33
JAL r31, r0, :add
CP r33, r1
ANDI r32, r32, 4294967295d
SUB64 r32, r32, r33
CP r1, r32
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 191
ret: 0
status: Ok(())

View file

@ -1,32 +0,0 @@
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :process
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
opaque:
JALA r0, r31, 0a
process:
ADDI64 r254, r254, -48d
ST r31, r254, 16a, 32h
LI64 r32, 1000d
ADDI64 r33, r254, 0d
ST r0, r254, 0a, 1h
4: JGTU r32, r0, :0
JMP :1
0: CP r2, r33
JAL r31, r0, :opaque
LD r34, r254, 0a, 1h
ANDI r34, r34, 255d
JEQ r34, r0, :2
JMP :3
2: ADDI64 r32, r32, -1d
1: JMP :4
3: LD r31, r254, 16a, 32h
ADDI64 r254, r254, 48d
JALA r0, r31, 0a
timed out
code size: 248
ret: 0
status: Ok(())

View file

@ -1,130 +0,0 @@
deinit:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r32, r2
LD r33, r32, 16a, 8h
LI64 r34, 8d
MUL64 r33, r33, r34
LD r35, r32, 0a, 8h
CP r2, r35
CP r3, r33
CP r4, r34
JAL r31, r0, :free
CP r1, r32
JAL r31, r0, :new
LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
free:
CP r13, r2
CP r14, r3
CP r15, r4
LRA r16, r0, :free_sys_call
LD r16, r16, 0a, 8h
CP r2, r16
CP r3, r13
CP r4, r14
CP r5, r15
ECA
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -56d
ST r31, r254, 24a, 32h
ADDI64 r32, r254, 0d
CP r1, r32
JAL r31, r0, :new
LI64 r33, 69d
CP r2, r32
CP r3, r33
JAL r31, r0, :push
CP r33, r1
LD r34, r254, 0a, 8h
LD r33, r34, 0a, 8h
CP r2, r32
JAL r31, r0, :deinit
CP r1, r33
LD r31, r254, 24a, 32h
ADDI64 r254, r254, 56d
JALA r0, r31, 0a
malloc:
CP r13, r2
CP r14, r3
LRA r15, r0, :malloc_sys_call
LD r15, r15, 0a, 8h
CP r2, r15
CP r3, r13
CP r4, r14
ECA
CP r13, r1
CP r1, r13
JALA r0, r31, 0a
new:
ADDI64 r254, r254, -24d
CP r14, r1
ADDI64 r13, r254, 0d
ST r0, r254, 0a, 8h
ST r0, r254, 8a, 8h
ST r0, r254, 16a, 8h
BMC r13, r14, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
push:
ADDI64 r254, r254, -104d
ST r31, r254, 0a, 104h
CP r38, r2
CP r39, r3
LI64 r37, 1d
LD r33, r38, 8a, 8h
LD r32, r38, 16a, 8h
JNE r32, r33, :0
JNE r32, r0, :1
CP r32, r37
JMP :2
1: MULI64 r32, r32, 2d
2: LI64 r40, 8d
MUL64 r34, r32, r40
CP r2, r34
CP r3, r40
JAL r31, r0, :malloc
CP r35, r1
ST r32, r38, 16a, 8h
JNE r35, r0, :3
CP r1, r0
JMP :4
3: MULI64 r33, r33, 8d
LD r32, r38, 0a, 8h
ADD64 r41, r32, r33
CP r34, r35
7: LD r42, r38, 0a, 8h
LD r43, r38, 8a, 8h
JNE r41, r32, :5
JEQ r43, r0, :6
MUL64 r32, r43, r40
CP r2, r42
CP r3, r32
CP r4, r40
JAL r31, r0, :free
JMP :6
6: ST r35, r38, 0a, 8h
JMP :0
5: ADDI64 r36, r34, 8d
ADDI64 r33, r32, 8d
LD r32, r32, 0a, 8h
ST r32, r34, 0a, 8h
CP r34, r36
CP r32, r33
JMP :7
0: LD r32, r38, 8a, 8h
MULI64 r33, r32, 8d
LD r34, r38, 0a, 8h
ADD64 r33, r34, r33
ST r39, r33, 0a, 8h
ADD64 r32, r32, r37
ST r32, r38, 8a, 8h
CP r1, r33
4: LD r31, r254, 0a, 104h
ADDI64 r254, r254, 104d
JALA r0, r31, 0a
code size: 923
ret: 69
status: Ok(())

View file

@ -1,19 +0,0 @@
clobber:
LRA r13, r0, :var
ST r0, r13, 0a, 8h
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LRA r32, r0, :var
LI64 r33, 2d
ST r33, r32, 0a, 8h
JAL r31, r0, :clobber
LD r32, r32, 0a, 8h
CP r1, r32
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 159
ret: 0
status: Ok(())

View file

@ -1,23 +0,0 @@
inb:
CP r1, r0
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
LRA r32, r0, :ports
LD r33, r32, 0a, 1h
ANDI r33, r33, 255d
JNE r33, r0, :0
JMP :1
0: JAL r31, r0, :inb
CP r33, r1
CMPU r34, r33, r0
CMPUI r34, r34, 0d
NOT r34, r34
ST r34, r32, 0a, 1h
1: LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 164
ret: 0
status: Ok(())

View file

@ -1,10 +0,0 @@
main:
LRA r13, r0, :complex_global_var
LD r14, r13, 0a, 8h
ADDI64 r14, r14, 5d
ST r14, r13, 0a, 8h
CP r1, r14
JALA r0, r31, 0a
code size: 74
ret: 55
status: Ok(())

View file

@ -1,6 +0,0 @@
main:
CP r1, r0
JALA r0, r31, 0a
code size: 22
ret: 0
status: Ok(())

View file

@ -1,21 +0,0 @@
main:
ADDI64 r254, r254, -128d
LI8 r15, 69b
LI64 r16, 128d
CP r13, r0
ADDI64 r17, r254, 0d
2: LD r18, r254, 42a, 1h
JLTU r13, r16, :0
ANDI r13, r18, 255d
CP r1, r13
JMP :1
0: ADDI64 r14, r13, 1d
ADD64 r13, r17, r13
ST r15, r13, 0a, 1h
CP r13, r14
JMP :2
1: ADDI64 r254, r254, 128d
JALA r0, r31, 0a
code size: 141
ret: 69
status: Ok(())

View file

@ -1,36 +0,0 @@
fib:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
CP r32, r2
LI64 r33, 1d
LI64 r34, 2d
JGTU r32, r34, :0
CP r1, r33
JMP :1
0: SUB64 r33, r32, r33
CP r2, r33
JAL r31, r0, :fib
CP r33, r1
SUB64 r32, r32, r34
CP r2, r32
JAL r31, r0, :fib
CP r32, r1
ADD64 r32, r32, r33
CP r1, r32
1: LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LI64 r32, 10d
CP r2, r32
JAL r31, r0, :fib
CP r32, r1
CP r1, r32
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 229
ret: 55
status: Ok(())

View file

@ -1,9 +0,0 @@
main:
CP r13, r0
0: ADDI64 r13, r13, 1d
JMP :0
JALA r0, r31, 0a
timed out
code size: 38
ret: 0
status: Ok(())

View file

@ -1,21 +0,0 @@
main:
LI64 r13, 8d
CP r2, r13
ECA
LI64 r14, 6d
LRA r13, r0, :gb
LD r13, r13, 0a, 8h
CMPU r13, r13, r0
CMPUI r13, r13, 0d
OR r13, r13, r0
ANDI r13, r13, 255d
JNE r13, r0, :0
CP r13, r14
JMP :1
0: LI64 r13, 1d
1: SUB64 r13, r13, r14
CP r1, r13
JALA r0, r31, 0a
code size: 131
ret: 0
status: Ok(())

View file

@ -1,6 +0,0 @@
main:
CP r1, r0
JALA r0, r31, 0a
code size: 22
ret: 0
status: Ok(())

View file

@ -1,29 +0,0 @@
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
JAL r31, r0, :scalar_values
CP r32, r1
JEQ r32, r0, :0
LI64 r32, 1d
CP r1, r32
JMP :1
0: JAL r31, r0, :structs
CP r32, r1
JEQ r32, r0, :2
JAL r31, r0, :structs
CP r32, r1
CP r1, r32
JMP :1
2: CP r1, r0
1: LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
scalar_values:
CP r1, r0
JALA r0, r31, 0a
structs:
CP r1, r0
JALA r0, r31, 0a
code size: 164
ret: 0
status: Ok(())

View file

@ -1,7 +0,0 @@
main:
LI64 r13, 10d
CP r1, r13
JALA r0, r31, 0a
code size: 32
ret: 10
status: Ok(())

View file

@ -1,104 +0,0 @@
main:
ADDI64 r254, r254, -122d
ST r31, r254, 58a, 64h
ADDI64 r32, r254, 33d
ADDI64 r33, r254, 34d
ADDI64 r34, r254, 1d
ADDI64 r35, r254, 17d
ST r32, r254, 34a, 8h
LI64 r36, 100d
ADDI64 r37, r254, 0d
LI8 r38, 1b
ST r0, r254, 1a, 8h
ST r0, r254, 17a, 8h
ST r36, r254, 42a, 8h
ST r38, r254, 0a, 1h
ST r0, r254, 9a, 8h
ST r0, r254, 25a, 8h
ST r36, r254, 50a, 8h
ST r0, r254, 33a, 1h
CP r2, r33
LD r3, r35, 0a, 16h
LD r5, r34, 0a, 16h
LD r7, r37, 0a, 1h
JAL r31, r0, :put_filled_rect
LD r31, r254, 58a, 64h
ADDI64 r254, r254, 122d
JALA r0, r31, 0a
put_filled_rect:
ADDI64 r254, r254, -108d
CP r14, r2
ST r3, r254, 92a, 16h
ADDI64 r3, r254, 92d
CP r15, r3
ST r5, r254, 76a, 16h
ADDI64 r5, r254, 76d
CP r13, r5
ST r7, r254, 75a, 1h
ADDI64 r7, r254, 75d
CP r16, r7
LI64 r17, 25d
LI64 r18, 2d
LI64 r19, 8d
ADDI64 r20, r254, 25d
ADDI64 r21, r254, 50d
LI8 r22, 5b
ST r22, r254, 25a, 1h
LD r23, r13, 0a, 8h
ST r23, r254, 26a, 4h
LI64 r24, 1d
ST r24, r254, 30a, 4h
ST r16, r254, 34a, 8h
ST r22, r254, 50a, 1h
ST r23, r254, 51a, 4h
ST r24, r254, 55a, 4h
ST r16, r254, 59a, 8h
LD r25, r15, 8a, 8h
LD r13, r13, 8a, 8h
ADD64 r26, r13, r25
SUB64 r26, r26, r24
LD r27, r14, 8a, 8h
MUL64 r26, r27, r26
LD r14, r14, 0a, 8h
ADD64 r26, r14, r26
LD r28, r15, 0a, 8h
ADD64 r15, r28, r26
MUL64 r25, r27, r25
ADD64 r14, r14, r25
ADD64 r14, r28, r14
3: JGTU r13, r24, :0
JNE r13, r24, :1
ADDI64 r13, r254, 0d
ST r22, r254, 0a, 1h
ST r23, r254, 1a, 4h
ST r24, r254, 5a, 4h
ST r16, r254, 9a, 8h
ST r14, r254, 17a, 8h
CP r2, r19
CP r3, r18
CP r4, r13
CP r5, r17
ECA
JMP :1
1: JMP :2
0: ST r14, r254, 67a, 8h
CP r2, r19
CP r3, r18
CP r4, r21
CP r5, r17
ECA
ST r15, r254, 42a, 8h
CP r2, r19
CP r3, r18
CP r4, r20
CP r5, r17
ECA
SUB64 r15, r15, r27
ADD64 r14, r27, r14
SUB64 r13, r13, r18
JMP :3
2: ADDI64 r254, r254, 108d
JALA r0, r31, 0a
code size: 875
ret: 0
status: Ok(())

Some files were not shown because too many files have changed in this diff Show more