Compare commits
29 commits
Author | SHA1 | Date | |
---|---|---|---|
IntoTheNight | 73ad40b369 | ||
0fb89ec4b3 | |||
f44220074d | |||
Erin | b218aa4a00 | ||
63b2dc7514 | |||
Erin | 0351a954d0 | ||
Erin | e32f0d1e61 | ||
Erin | 81f79dc7a5 | ||
Erin | 7ca0b1d4eb | ||
Erin | 447f8b2075 | ||
Erin | b271d024cd | ||
Erin | 7d17f48562 | ||
Erin | 387d4c7ce7 | ||
Erin | b7d4243113 | ||
Erin | 3af50b29fb | ||
able | 2d639797d9 | ||
able | a63c252c7a | ||
Erin | da1553d030 | ||
Erin | f0a00ebb8d | ||
Erin | 2bbf6ceee0 | ||
Erin | 2c9e315889 | ||
able | f53a42977d | ||
able | 8bc0d0020c | ||
able | f58f801aa9 | ||
able | a642b68474 | ||
Erin | 79c367dc18 | ||
Erin | 8b9a75adb4 | ||
Erin | 7e233f4ae1 | ||
Erin | 0c69d80fc2 |
|
@ -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
12
.gitignore
vendored
|
@ -1,13 +1 @@
|
||||||
# garbage
|
|
||||||
/target
|
/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
1628
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
50
Cargo.toml
50
Cargo.toml
|
@ -1,50 +1,2 @@
|
||||||
cargo-features = ["profile-rustflags"]
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
members = ["hbasm", "hbbytecode", "hbvm"]
|
||||||
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"
|
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "hbbytecode"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[features]
|
|
||||||
default = ["disasm"]
|
|
||||||
std = []
|
|
||||||
disasm = ["std"]
|
|
||||||
|
|
|
@ -1,204 +0,0 @@
|
||||||
#![feature(iter_next_chunk)]
|
|
||||||
|
|
||||||
use std::{collections::HashSet, fmt::Write};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
println!("cargo:rerun-if-changed=build.rs");
|
|
||||||
println!("cargo:rerun-if-changed=instructions.in");
|
|
||||||
|
|
||||||
let mut generated = String::new();
|
|
||||||
gen_instrs(&mut generated)?;
|
|
||||||
std::fs::write("src/instrs.rs", generated)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn gen_instrs(generated: &mut String) -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
writeln!(generated, "#![expect(dead_code)]")?;
|
|
||||||
writeln!(generated, "use crate::*;")?;
|
|
||||||
|
|
||||||
'_opcode_structs: {
|
|
||||||
let mut seen = HashSet::new();
|
|
||||||
for [.., args, _] in instructions() {
|
|
||||||
if !seen.insert(args) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(generated, "#[derive(Clone, Copy, Debug)]")?;
|
|
||||||
writeln!(generated, "#[repr(packed)]")?;
|
|
||||||
write!(generated, "pub struct Ops{args}(")?;
|
|
||||||
let mut first = true;
|
|
||||||
for ch in args.chars().filter(|&ch| ch != 'N') {
|
|
||||||
if !std::mem::take(&mut first) {
|
|
||||||
write!(generated, ",")?;
|
|
||||||
}
|
|
||||||
write!(generated, "pub Op{ch}")?;
|
|
||||||
}
|
|
||||||
writeln!(generated, ");")?;
|
|
||||||
writeln!(generated, "unsafe impl BytecodeItem for Ops{args} {{}}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
'_max_size: {
|
|
||||||
let max = instructions()
|
|
||||||
.map(
|
|
||||||
|[_, _, ty, _]| {
|
|
||||||
if ty == "N" {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
iter_args(ty).map(arg_to_width).sum::<usize>() + 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.max()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
writeln!(generated, "pub const MAX_SIZE: usize = {max};")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
'_encoders: {
|
|
||||||
for [op, name, ty, doc] in instructions() {
|
|
||||||
writeln!(generated, "/// {}", doc.trim_matches('"'))?;
|
|
||||||
let name = name.to_lowercase();
|
|
||||||
let args = comma_sep(
|
|
||||||
iter_args(ty)
|
|
||||||
.enumerate()
|
|
||||||
.map(|(i, c)| format!("{}{i}: {}", arg_to_name(c), arg_to_type(c))),
|
|
||||||
);
|
|
||||||
writeln!(generated, "pub fn {name}({args}) -> (usize, [u8; MAX_SIZE]) {{")?;
|
|
||||||
let arg_names =
|
|
||||||
comma_sep(iter_args(ty).enumerate().map(|(i, c)| format!("{}{i}", arg_to_name(c))));
|
|
||||||
writeln!(generated, " unsafe {{ crate::encode({ty}({op}, {arg_names})) }}")?;
|
|
||||||
writeln!(generated, "}}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
'_structs: {
|
|
||||||
let mut seen = std::collections::HashSet::new();
|
|
||||||
for [_, _, ty, _] in instructions() {
|
|
||||||
if !seen.insert(ty) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let types = comma_sep(iter_args(ty).map(arg_to_type).map(|s| s.to_string()));
|
|
||||||
writeln!(generated, "#[repr(packed)] pub struct {ty}(u8, {types});")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
'_name_list: {
|
|
||||||
writeln!(generated, "pub const COUNT: u8 = {};", instructions().count())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let instr = "Instr";
|
|
||||||
let oper = "Oper";
|
|
||||||
|
|
||||||
'_instr_enum: {
|
|
||||||
writeln!(generated, "#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)]")?;
|
|
||||||
writeln!(generated, "pub enum {instr} {{")?;
|
|
||||||
for [id, name, ..] in instructions() {
|
|
||||||
writeln!(generated, " {name} = {id},")?;
|
|
||||||
}
|
|
||||||
writeln!(generated, "}}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
'_arg_kind: {
|
|
||||||
writeln!(generated, "#[derive(Debug, Clone, Copy, PartialEq, Eq)]")?;
|
|
||||||
writeln!(generated, "pub enum {oper} {{")?;
|
|
||||||
let mut seen = HashSet::new();
|
|
||||||
for ty in instructions().flat_map(|[.., ty, _]| iter_args(ty)) {
|
|
||||||
if !seen.insert(ty) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
writeln!(generated, " {ty}({}),", arg_to_type(ty))?;
|
|
||||||
}
|
|
||||||
writeln!(generated, "}}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
'_parse_opers: {
|
|
||||||
writeln!(
|
|
||||||
generated,
|
|
||||||
"/// This assumes the instruction byte is still at the beginning of the buffer"
|
|
||||||
)?;
|
|
||||||
writeln!(generated, "#[cfg(feature = \"disasm\")]")?;
|
|
||||||
writeln!(generated, "pub fn parse_args(bytes: &mut &[u8], kind: {instr}, buf: &mut alloc::vec::Vec<{oper}>) -> Option<()> {{")?;
|
|
||||||
writeln!(generated, " match kind {{")?;
|
|
||||||
let mut instrs = instructions().collect::<Vec<_>>();
|
|
||||||
instrs.sort_unstable_by_key(|&[.., ty, _]| ty);
|
|
||||||
for group in instrs.chunk_by(|[.., a, _], [.., b, _]| a == b) {
|
|
||||||
let ty = group[0][2];
|
|
||||||
for &[_, name, ..] in group {
|
|
||||||
writeln!(generated, " | {instr}::{name}")?;
|
|
||||||
}
|
|
||||||
generated.pop();
|
|
||||||
writeln!(generated, " => {{")?;
|
|
||||||
if iter_args(ty).count() != 0 {
|
|
||||||
writeln!(generated, " let data = crate::decode::<{ty}>(bytes)?;")?;
|
|
||||||
writeln!(
|
|
||||||
generated,
|
|
||||||
" buf.extend([{}]);",
|
|
||||||
comma_sep(
|
|
||||||
iter_args(ty).zip(1u32..).map(|(t, i)| format!("{oper}::{t}(data.{i})"))
|
|
||||||
)
|
|
||||||
)?;
|
|
||||||
} else {
|
|
||||||
writeln!(generated, " crate::decode::<{ty}>(bytes)?;")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(generated, " }}")?;
|
|
||||||
}
|
|
||||||
writeln!(generated, " }}")?;
|
|
||||||
writeln!(generated, " Some(())")?;
|
|
||||||
writeln!(generated, "}}")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::fs::write("src/instrs.rs", generated)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn comma_sep(items: impl Iterator<Item = String>) -> String {
|
|
||||||
items.map(|item| item.to_string()).collect::<Vec<_>>().join(", ")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn instructions() -> impl Iterator<Item = [&'static str; 4]> {
|
|
||||||
include_str!("instructions.in")
|
|
||||||
.lines()
|
|
||||||
.filter_map(|line| line.strip_suffix(';'))
|
|
||||||
.map(|line| line.splitn(4, ',').map(str::trim).next_chunk().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arg_to_type(arg: char) -> &'static str {
|
|
||||||
match arg {
|
|
||||||
'R' | 'B' => "u8",
|
|
||||||
'H' => "u16",
|
|
||||||
'W' => "u32",
|
|
||||||
'D' | 'A' => "u64",
|
|
||||||
'P' => "i16",
|
|
||||||
'O' => "i32",
|
|
||||||
_ => panic!("unknown type: {}", arg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arg_to_width(arg: char) -> usize {
|
|
||||||
match arg {
|
|
||||||
'R' | 'B' => 1,
|
|
||||||
'H' => 2,
|
|
||||||
'W' => 4,
|
|
||||||
'D' | 'A' => 8,
|
|
||||||
'P' => 2,
|
|
||||||
'O' => 4,
|
|
||||||
_ => panic!("unknown type: {}", arg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn arg_to_name(arg: char) -> &'static str {
|
|
||||||
match arg {
|
|
||||||
'R' => "reg",
|
|
||||||
'B' | 'H' | 'W' | 'D' => "imm",
|
|
||||||
'P' | 'O' => "offset",
|
|
||||||
'A' => "addr",
|
|
||||||
_ => panic!("unknown type: {}", arg),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn iter_args(ty: &'static str) -> impl Iterator<Item = char> {
|
|
||||||
ty.chars().filter(|c| *c != 'N')
|
|
||||||
}
|
|
|
@ -1,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)
|
|
|
@ -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)" ;
|
|
|
@ -1,284 +0,0 @@
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
#[cfg(feature = "disasm")]
|
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
pub use crate::instrs::*;
|
|
||||||
use core::convert::TryFrom;
|
|
||||||
|
|
||||||
mod instrs;
|
|
||||||
|
|
||||||
type OpR = u8;
|
|
||||||
|
|
||||||
type OpA = u64;
|
|
||||||
type OpO = i32;
|
|
||||||
type OpP = i16;
|
|
||||||
|
|
||||||
type OpB = u8;
|
|
||||||
type OpH = u16;
|
|
||||||
type OpW = u32;
|
|
||||||
type OpD = u64;
|
|
||||||
|
|
||||||
/// # Safety
|
|
||||||
/// Has to be valid to be decoded from bytecode.
|
|
||||||
pub unsafe trait BytecodeItem {}
|
|
||||||
unsafe impl BytecodeItem for u8 {}
|
|
||||||
|
|
||||||
impl TryFrom<u8> for Instr {
|
|
||||||
type Error = u8;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
||||||
#[cold]
|
|
||||||
fn failed(value: u8) -> Result<Instr, u8> {
|
|
||||||
Err(value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if value < COUNT {
|
|
||||||
unsafe { Ok(core::mem::transmute::<u8, Instr>(value)) }
|
|
||||||
} else {
|
|
||||||
failed(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
unsafe fn encode<T>(instr: T) -> (usize, [u8; instrs::MAX_SIZE]) {
|
|
||||||
let mut buf = [0; instrs::MAX_SIZE];
|
|
||||||
core::ptr::write(buf.as_mut_ptr() as *mut T, instr);
|
|
||||||
(core::mem::size_of::<T>(), buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[cfg(feature = "disasm")]
|
|
||||||
fn decode<T>(binary: &mut &[u8]) -> Option<T> {
|
|
||||||
let (front, rest) = core::mem::take(binary).split_at_checked(core::mem::size_of::<T>())?;
|
|
||||||
*binary = rest;
|
|
||||||
unsafe { Some(core::ptr::read(front.as_ptr() as *const T)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rounding mode
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum RoundingMode {
|
|
||||||
NearestEven = 0,
|
|
||||||
Truncate = 1,
|
|
||||||
Up = 2,
|
|
||||||
Down = 3,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<u8> for RoundingMode {
|
|
||||||
type Error = ();
|
|
||||||
|
|
||||||
fn try_from(value: u8) -> Result<Self, Self::Error> {
|
|
||||||
(value <= 3).then(|| unsafe { core::mem::transmute(value) }).ok_or(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "disasm")]
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum DisasmItem {
|
|
||||||
Func,
|
|
||||||
Global,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "disasm")]
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum DisasmError<'a> {
|
|
||||||
InvalidInstruction(u8),
|
|
||||||
InstructionOutOfBounds(&'a str),
|
|
||||||
FmtFailed(core::fmt::Error),
|
|
||||||
HasOutOfBoundsJumps,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "disasm")]
|
|
||||||
impl From<core::fmt::Error> for DisasmError<'_> {
|
|
||||||
fn from(value: core::fmt::Error) -> Self {
|
|
||||||
Self::FmtFailed(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "disasm")]
|
|
||||||
impl core::fmt::Display for DisasmError<'_> {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
match *self {
|
|
||||||
DisasmError::InvalidInstruction(b) => write!(f, "invalid instruction opcode: {b}"),
|
|
||||||
DisasmError::InstructionOutOfBounds(name) => {
|
|
||||||
write!(f, "instruction would go out of bounds of {name} symbol")
|
|
||||||
}
|
|
||||||
DisasmError::FmtFailed(error) => write!(f, "fmt failed: {error}"),
|
|
||||||
DisasmError::HasOutOfBoundsJumps => write!(
|
|
||||||
f,
|
|
||||||
"the code contained jumps that dont got neither to a \
|
|
||||||
valid symbol or local insturction"
|
|
||||||
),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "disasm")]
|
|
||||||
impl core::error::Error for DisasmError<'_> {}
|
|
||||||
|
|
||||||
#[cfg(feature = "disasm")]
|
|
||||||
pub fn disasm<'a>(
|
|
||||||
binary: &mut &[u8],
|
|
||||||
functions: &alloc::collections::BTreeMap<u32, (&'a str, u32, DisasmItem)>,
|
|
||||||
out: &mut alloc::string::String,
|
|
||||||
mut eca_handler: impl FnMut(&mut &[u8]),
|
|
||||||
) -> Result<(), DisasmError<'a>> {
|
|
||||||
use {
|
|
||||||
self::instrs::Instr,
|
|
||||||
alloc::{
|
|
||||||
collections::btree_map::{BTreeMap, Entry},
|
|
||||||
vec::Vec,
|
|
||||||
},
|
|
||||||
core::{convert::TryInto, fmt::Write},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn instr_from_byte(b: u8) -> Result<Instr, DisasmError<'static>> {
|
|
||||||
b.try_into().map_err(DisasmError::InvalidInstruction)
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut labels = BTreeMap::<u32, u32>::default();
|
|
||||||
let mut buf = Vec::<instrs::Oper>::new();
|
|
||||||
let mut has_oob = false;
|
|
||||||
|
|
||||||
'_offset_pass: for (&off, &(name, len, kind)) in functions.iter() {
|
|
||||||
if matches!(kind, DisasmItem::Global) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let prev = *binary;
|
|
||||||
|
|
||||||
*binary = &binary[off as usize..];
|
|
||||||
|
|
||||||
let mut label_count = 0;
|
|
||||||
while let Some(&byte) = binary.first() {
|
|
||||||
let offset: i32 = (prev.len() - binary.len()).try_into().unwrap();
|
|
||||||
if offset as u32 == off + len {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let Ok(inst) = instr_from_byte(byte) else { break };
|
|
||||||
instrs::parse_args(binary, inst, &mut buf)
|
|
||||||
.ok_or(DisasmError::InstructionOutOfBounds(name))?;
|
|
||||||
|
|
||||||
for op in buf.drain(..) {
|
|
||||||
let rel = match op {
|
|
||||||
instrs::Oper::O(rel) => rel,
|
|
||||||
instrs::Oper::P(rel) => rel.into(),
|
|
||||||
_ => continue,
|
|
||||||
};
|
|
||||||
|
|
||||||
let global_offset: u32 = (offset + rel).try_into().unwrap();
|
|
||||||
if functions.get(&global_offset).is_some() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
label_count += match labels.entry(global_offset) {
|
|
||||||
Entry::Occupied(_) => 0,
|
|
||||||
Entry::Vacant(entry) => {
|
|
||||||
entry.insert(label_count);
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches!(inst, Instr::ECA) {
|
|
||||||
eca_handler(binary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*binary = prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ordered = functions.iter().collect::<Vec<_>>();
|
|
||||||
ordered.sort_unstable_by_key(|(_, (name, _, _))| name);
|
|
||||||
|
|
||||||
'_dump: for (&off, &(name, len, kind)) in ordered {
|
|
||||||
if matches!(kind, DisasmItem::Global) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let prev = *binary;
|
|
||||||
|
|
||||||
writeln!(out, "{name}:")?;
|
|
||||||
|
|
||||||
*binary = &binary[off as usize..];
|
|
||||||
while let Some(&byte) = binary.first() {
|
|
||||||
let offset: i32 = (prev.len() - binary.len()).try_into().unwrap();
|
|
||||||
if offset as u32 == off + len {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let Ok(inst) = instr_from_byte(byte) else {
|
|
||||||
writeln!(out, "invalid instr {byte}")?;
|
|
||||||
break;
|
|
||||||
};
|
|
||||||
instrs::parse_args(binary, inst, &mut buf).unwrap();
|
|
||||||
|
|
||||||
if let Some(label) = labels.get(&offset.try_into().unwrap()) {
|
|
||||||
write!(out, "{:>2}: ", label)?;
|
|
||||||
} else {
|
|
||||||
write!(out, " ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
write!(out, "{inst:<8?} ")?;
|
|
||||||
|
|
||||||
'a: for (i, op) in buf.drain(..).enumerate() {
|
|
||||||
if i != 0 {
|
|
||||||
write!(out, ", ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rel = 'b: {
|
|
||||||
match op {
|
|
||||||
instrs::Oper::O(rel) => break 'b rel,
|
|
||||||
instrs::Oper::P(rel) => break 'b rel.into(),
|
|
||||||
instrs::Oper::R(r) => write!(out, "r{r}")?,
|
|
||||||
instrs::Oper::B(b) => write!(out, "{b}b")?,
|
|
||||||
instrs::Oper::H(h) => write!(out, "{h}h")?,
|
|
||||||
instrs::Oper::W(w) => write!(out, "{w}w")?,
|
|
||||||
instrs::Oper::D(d) if (d as i64) < 0 => write!(out, "{}d", d as i64)?,
|
|
||||||
instrs::Oper::D(d) => write!(out, "{d}d")?,
|
|
||||||
instrs::Oper::A(a) => write!(out, "{a}a")?,
|
|
||||||
}
|
|
||||||
|
|
||||||
continue 'a;
|
|
||||||
};
|
|
||||||
|
|
||||||
let global_offset: u32 = (offset + rel).try_into().unwrap();
|
|
||||||
if let Some(&(name, ..)) = functions.get(&global_offset) {
|
|
||||||
if name.contains('\0') {
|
|
||||||
write!(out, ":{name:?}")?;
|
|
||||||
} else {
|
|
||||||
write!(out, ":{name}")?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let local_has_oob = global_offset < off
|
|
||||||
|| global_offset > off + len
|
|
||||||
|| prev
|
|
||||||
.get(global_offset as usize)
|
|
||||||
.map_or(true, |&b| instr_from_byte(b).is_err())
|
|
||||||
|| prev[global_offset as usize] == 0;
|
|
||||||
has_oob |= local_has_oob;
|
|
||||||
let label = labels.get(&global_offset).unwrap();
|
|
||||||
if local_has_oob {
|
|
||||||
write!(out, "!!!!!!!!!{rel}")?;
|
|
||||||
} else {
|
|
||||||
write!(out, ":{label}")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(out)?;
|
|
||||||
|
|
||||||
if matches!(inst, Instr::ECA) {
|
|
||||||
eca_handler(binary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*binary = prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
if has_oob {
|
|
||||||
return Err(DisasmError::HasOutOfBoundsJumps);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "depell"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
argon2 = "0.5.3"
|
|
||||||
axum = "0.7.7"
|
|
||||||
axum-server = { version = "0.7.1", optional = true, features = ["rustls", "tls-rustls"] }
|
|
||||||
const_format = "0.2.33"
|
|
||||||
getrandom = "0.2.15"
|
|
||||||
hblang.workspace = true
|
|
||||||
htmlm = "0.5.0"
|
|
||||||
log = "0.4.22"
|
|
||||||
rand_core = { version = "0.6.4", features = ["getrandom"] }
|
|
||||||
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
|
||||||
time = "0.3.36"
|
|
||||||
tokio = { version = "1.40.0", features = ["rt"] }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
#default = ["tls"]
|
|
||||||
tls = ["dep:axum-server"]
|
|
|
@ -1,14 +0,0 @@
|
||||||
# Depell
|
|
||||||
|
|
||||||
Depell is a website that allows users to import/post/run hblang code and create huge dependency graphs. Its currently hosted at https://depell.mlokis.tech.
|
|
||||||
|
|
||||||
## Local Development
|
|
||||||
|
|
||||||
Prerequirements:
|
|
||||||
- rust nigthly toolchain: install rust from [here](https://www.rust-lang.org/tools/install)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rustup default nightly
|
|
||||||
cargo xtask watch-depell-debug
|
|
||||||
# browser http://localhost:8080
|
|
||||||
```
|
|
|
@ -1,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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
|
|
|
@ -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) {}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
<div id="dep-list">
|
|
||||||
<input placeholder="search impoted deps.." oninput="filterCodeDeps(this, event)">
|
|
||||||
<section id="deps">
|
|
||||||
results show here...
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
|
|
||||||
<h3>About posting code</h3>
|
|
||||||
<p>
|
|
||||||
If you are unfammiliar with <a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a>, refer to the
|
|
||||||
<strong>hblang/README.md</strong> or
|
|
||||||
vizit <a href="/profile/mlokis">mlokis'es posts</a>. Preferably don't edit the code here.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h3>Extra textarea features</h3>
|
|
||||||
<ul>
|
|
||||||
<li>proper tab behaviour</li>
|
|
||||||
<li>snap to previous tab boundary on "empty" lines</li>
|
|
||||||
</ul>
|
|
|
@ -1,55 +0,0 @@
|
||||||
PRAGMA foreign_keys = ON;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS user(
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
password_hash TEXT NOT NULL,
|
|
||||||
PRIMARY KEY (name)
|
|
||||||
) WITHOUT ROWID;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS session(
|
|
||||||
id BLOB NOT NULL,
|
|
||||||
username TEXT NOT NULL,
|
|
||||||
expiration INTEGER NOT NULL,
|
|
||||||
FOREIGN KEY (username) REFERENCES user (name)
|
|
||||||
PRIMARY KEY (username)
|
|
||||||
) WITHOUT ROWID;
|
|
||||||
|
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS
|
|
||||||
session_id ON session (id);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS post(
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
author TEXT,
|
|
||||||
timestamp INTEGER,
|
|
||||||
code TEXT NOT NULL,
|
|
||||||
FOREIGN KEY (author) REFERENCES user(name) ON DELETE SET NULL,
|
|
||||||
PRIMARY KEY (author, name)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS
|
|
||||||
post_timestamp ON post(timestamp DESC);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS import(
|
|
||||||
from_name TEXT NOT NULL,
|
|
||||||
from_author TEXT,
|
|
||||||
to_name TEXT NOT NULL,
|
|
||||||
to_author TEXT,
|
|
||||||
FOREIGN KEY (from_name, from_author) REFERENCES post(name, author),
|
|
||||||
FOREIGN KEY (to_name, to_author) REFERENCES post(name, author)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS
|
|
||||||
dependencies ON import(from_name, from_author);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS
|
|
||||||
dependants ON import(to_name, to_author);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS run(
|
|
||||||
code_name TEXT NOT NULL,
|
|
||||||
code_author TEXT NOT NULL,
|
|
||||||
runner TEXT NOT NULL,
|
|
||||||
FOREIGN KEY (code_name, code_author) REFERENCES post(name, author),
|
|
||||||
FOREIGN KEY (runner) REFERENCES user(name),
|
|
||||||
PRIMARY KEY (code_name, code_author, runner)
|
|
||||||
);
|
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
<h1>Welcome to depell</h1>
|
|
||||||
<p>
|
|
||||||
Depell (dependency hell) is a simple "social" media site best compared to twitter, except that all you can post is
|
|
||||||
<a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a> code with no comments allowed. Instead of likes you
|
|
||||||
run the program, and instead of retweets you import the program as dependency. Run counts even when ran indirectly.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
The backend only serves the code and frontend compiles and runs it locally. All posts are immutable.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Security?</h2>
|
|
||||||
<p>
|
|
||||||
All code runs in WASM (inside a holey-bytes VM until hblang compiles to wasm) and is controlled by JavaScript. WASM
|
|
||||||
cant do any form of IO without going trough JavaScript so as long as JS import does not allow wasm to execute
|
|
||||||
arbitrary JS code, WASM can act as a container inside the JS.
|
|
||||||
</p>
|
|
|
@ -1,11 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "wasm-hbfmt"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
hblang = { workspace = true, features = ["no_log"] }
|
|
||||||
wasm-rt = { version = "0.1.0", path = "../wasm-rt" }
|
|
|
@ -1,42 +0,0 @@
|
||||||
#![no_std]
|
|
||||||
#![feature(str_from_raw_parts)]
|
|
||||||
#![feature(alloc_error_handler)]
|
|
||||||
|
|
||||||
use hblang::{fmt, parser};
|
|
||||||
|
|
||||||
wasm_rt::decl_runtime!(128 * 1024, 1024 * 4);
|
|
||||||
|
|
||||||
const MAX_OUTPUT_SIZE: usize = 1024 * 10;
|
|
||||||
wasm_rt::decl_buffer!(MAX_OUTPUT_SIZE, MAX_OUTPUT, OUTPUT, OUTPUT_LEN);
|
|
||||||
|
|
||||||
const MAX_INPUT_SIZE: usize = 1024 * 4;
|
|
||||||
wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
unsafe extern "C" fn fmt() {
|
|
||||||
ALLOCATOR.reset();
|
|
||||||
|
|
||||||
let code = core::str::from_raw_parts(core::ptr::addr_of!(INPUT).cast(), INPUT_LEN);
|
|
||||||
|
|
||||||
let arena = parser::Arena::with_capacity(code.len() * parser::SOURCE_TO_AST_FACTOR);
|
|
||||||
let mut ctx = parser::Ctx::default();
|
|
||||||
let exprs = parser::Parser::parse(&mut ctx, code, "source.hb", &mut parser::no_loader, &arena);
|
|
||||||
|
|
||||||
let mut f = wasm_rt::Write(&mut OUTPUT[..]);
|
|
||||||
fmt::fmt_file(exprs, code, &mut f).unwrap();
|
|
||||||
OUTPUT_LEN = MAX_OUTPUT_SIZE - f.0.len();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
unsafe extern "C" fn tok() {
|
|
||||||
let code = core::slice::from_raw_parts_mut(
|
|
||||||
core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
|
|
||||||
OUTPUT_LEN = fmt::get_token_kinds(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
unsafe extern "C" fn minify() {
|
|
||||||
let code = core::str::from_raw_parts_mut(
|
|
||||||
core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
|
|
||||||
OUTPUT_LEN = fmt::minify(code);
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "wasm-hbc"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["cdylib"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
hblang = { workspace = true, features = [] }
|
|
||||||
hbvm.workspace = true
|
|
||||||
log = { version = "0.4.22", features = ["release_max_level_error"] }
|
|
||||||
wasm-rt = { version = "0.1.0", path = "../wasm-rt", features = ["log"] }
|
|
||||||
|
|
|
@ -1,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);
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "wasm-rt"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
log = { version = "0.4.22", optional = true }
|
|
|
@ -1,162 +0,0 @@
|
||||||
#![feature(alloc_error_handler)]
|
|
||||||
#![feature(pointer_is_aligned_to)]
|
|
||||||
#![feature(slice_take)]
|
|
||||||
#![no_std]
|
|
||||||
|
|
||||||
use core::{
|
|
||||||
alloc::{GlobalAlloc, Layout},
|
|
||||||
cell::UnsafeCell,
|
|
||||||
};
|
|
||||||
|
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! decl_buffer {
|
|
||||||
($cap:expr, $export_cap:ident, $export_base:ident, $export_len:ident) => {
|
|
||||||
#[no_mangle]
|
|
||||||
static $export_cap: usize = $cap;
|
|
||||||
#[no_mangle]
|
|
||||||
static mut $export_base: [u8; $cap] = [0; $cap];
|
|
||||||
#[no_mangle]
|
|
||||||
static mut $export_len: usize = 0;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! decl_runtime {
|
|
||||||
($memory_size:expr, $max_panic_size:expr) => {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
#[no_mangle]
|
|
||||||
static mut PANIC_MESSAGE: [u8; $max_panic_size] = [0; $max_panic_size];
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
#[no_mangle]
|
|
||||||
static mut PANIC_MESSAGE_LEN: usize = 0;
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
#[panic_handler]
|
|
||||||
pub fn handle_panic(_info: &core::panic::PanicInfo) -> ! {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
unsafe {
|
|
||||||
use core::fmt::Write;
|
|
||||||
let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
|
|
||||||
_ = writeln!(f, "{}", _info);
|
|
||||||
PANIC_MESSAGE_LEN = $max_panic_size - f.0.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
core::arch::wasm32::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[global_allocator]
|
|
||||||
static ALLOCATOR: $crate::ArenaAllocator<{ $memory_size }> = $crate::ArenaAllocator::new();
|
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
|
||||||
#[alloc_error_handler]
|
|
||||||
fn alloc_error(_: core::alloc::Layout) -> ! {
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
{
|
|
||||||
unsafe {
|
|
||||||
use core::fmt::Write;
|
|
||||||
let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
|
|
||||||
_ = writeln!(f, "out of memory");
|
|
||||||
PANIC_MESSAGE_LEN = $max_panic_size - f.0.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
core::arch::wasm32::unreachable()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "log")]
|
|
||||||
pub struct Logger;
|
|
||||||
|
|
||||||
#[cfg(feature = "log")]
|
|
||||||
impl log::Log for Logger {
|
|
||||||
fn enabled(&self, _: &log::Metadata) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &log::Record) {
|
|
||||||
if self.enabled(record.metadata()) {
|
|
||||||
const MAX_LOG_MESSAGE: usize = 1024 * 8;
|
|
||||||
#[no_mangle]
|
|
||||||
static mut LOG_MESSAGES: [u8; MAX_LOG_MESSAGE] = [0; MAX_LOG_MESSAGE];
|
|
||||||
#[no_mangle]
|
|
||||||
static mut LOG_MESSAGES_LEN: usize = 0;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
use core::fmt::Write;
|
|
||||||
let mut f = Write(&mut LOG_MESSAGES[LOG_MESSAGES_LEN..]);
|
|
||||||
_ = writeln!(f, "{}", record.args());
|
|
||||||
LOG_MESSAGES_LEN = MAX_LOG_MESSAGE - f.0.len();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ArenaAllocator<const SIZE: usize> {
|
|
||||||
arena: UnsafeCell<[u8; SIZE]>,
|
|
||||||
head: UnsafeCell<*mut u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const SIZE: usize> ArenaAllocator<SIZE> {
|
|
||||||
#[expect(clippy::new_without_default)]
|
|
||||||
pub const fn new() -> Self {
|
|
||||||
ArenaAllocator {
|
|
||||||
arena: UnsafeCell::new([0; SIZE]),
|
|
||||||
head: UnsafeCell::new(core::ptr::null_mut()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[expect(clippy::missing_safety_doc)]
|
|
||||||
pub unsafe fn reset(&self) {
|
|
||||||
(*self.head.get()) = self.arena.get().cast::<u8>().add(SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn used(&self) -> usize {
|
|
||||||
unsafe { self.arena.get() as usize + SIZE - (*self.head.get()) as usize }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<const SIZE: usize> Sync for ArenaAllocator<SIZE> {}
|
|
||||||
|
|
||||||
unsafe impl<const SIZE: usize> GlobalAlloc for ArenaAllocator<SIZE> {
|
|
||||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
|
||||||
let size = layout.size();
|
|
||||||
let align = layout.align();
|
|
||||||
|
|
||||||
let until = self.arena.get() as *mut u8;
|
|
||||||
|
|
||||||
let new_head = (*self.head.get()).sub(size);
|
|
||||||
let aligned_head = (new_head as usize & !(align - 1)) as *mut u8;
|
|
||||||
debug_assert!(aligned_head.is_aligned_to(align));
|
|
||||||
|
|
||||||
if until > aligned_head {
|
|
||||||
return core::ptr::null_mut();
|
|
||||||
}
|
|
||||||
|
|
||||||
*self.head.get() = aligned_head;
|
|
||||||
aligned_head
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
|
|
||||||
/* lol */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Write<'a>(pub &'a mut [u8]);
|
|
||||||
|
|
||||||
impl core::fmt::Write for Write<'_> {
|
|
||||||
fn write_str(&mut self, s: &str) -> core::fmt::Result {
|
|
||||||
if let Some(m) = self.0.take_mut(..s.len()) {
|
|
||||||
m.copy_from_slice(s.as_bytes());
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(core::fmt::Error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
20
hbasm/Cargo.toml
Normal 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"]
|
18
hbasm/assets/serial_driver.hbasm
Normal file
18
hbasm/assets/serial_driver.hbasm
Normal 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
65
hbasm/src/lib.rs
Normal 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
72
hbasm/src/macros.rs
Normal 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
46
hbasm/src/main.rs
Normal 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
273
hbasm/src/text.rs
Normal 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 })
|
||||||
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "xtask"
|
name = "hbbytecode"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.89"
|
|
||||||
walrus = "0.22.0"
|
|
||||||
|
|
60
hbbytecode/hbbytecode.h
Normal file
60
hbbytecode/hbbytecode.h
Normal 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
107
hbbytecode/src/lib.rs
Normal 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
16
hbvm/Cargo.toml
Normal 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
BIN
hbvm/assets/inf_loop.hb
Normal file
Binary file not shown.
7
hbvm/src/lib.rs
Normal file
7
hbvm/src/lib.rs
Normal 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
59
hbvm/src/main.rs
Normal 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
64
hbvm/src/validate.rs
Normal 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
430
hbvm/src/vm/mem/mod.rs
Normal 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
152
hbvm/src/vm/mem/paging.rs
Normal 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
373
hbvm/src/vm/mod.rs
Normal 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
35
hbvm/src/vm/trap.rs
Normal 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
48
hbvm/src/vm/value.rs
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"]
|
|
1803
lang/README.md
1803
lang/README.md
File diff suppressed because one or more lines are too long
|
@ -1,4 +0,0 @@
|
||||||
--fmt - format all imported source files
|
|
||||||
--fmt-stdout - dont write the formatted file but print it
|
|
||||||
--dump-asm - output assembly instead of raw code, (the assembly is more for debugging the compiler)
|
|
||||||
--threads <1...> - number of extra threads compiler can use [default: 0]
|
|
614
lang/src/fmt.rs
614
lang/src/fmt.rs
|
@ -1,614 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
lexer::{self, Lexer, TokenKind},
|
|
||||||
parser::{self, CommentOr, CtorField, EnumField, Expr, Poser, Radix, StructField},
|
|
||||||
},
|
|
||||||
core::fmt::{self},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn display_radix(radix: Radix, mut value: u64, buf: &mut [u8; 64]) -> &str {
|
|
||||||
fn conv_radix(d: u8) -> u8 {
|
|
||||||
match d {
|
|
||||||
0..=9 => d + b'0',
|
|
||||||
_ => d - 10 + b'A',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i, b) in buf.iter_mut().enumerate().rev() {
|
|
||||||
let d = (value % radix as u64) as u8;
|
|
||||||
value /= radix as u64;
|
|
||||||
*b = conv_radix(d);
|
|
||||||
if value == 0 {
|
|
||||||
return unsafe { core::str::from_utf8_unchecked(&buf[i..]) };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[repr(u8)]
|
|
||||||
enum TokenGroup {
|
|
||||||
Blank,
|
|
||||||
Comment,
|
|
||||||
Keyword,
|
|
||||||
Identifier,
|
|
||||||
Directive,
|
|
||||||
Number,
|
|
||||||
String,
|
|
||||||
Op,
|
|
||||||
Assign,
|
|
||||||
Paren,
|
|
||||||
Bracket,
|
|
||||||
Colon,
|
|
||||||
Comma,
|
|
||||||
Dot,
|
|
||||||
Ctor,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn token_group(kind: TokenKind) -> TokenGroup {
|
|
||||||
use {crate::lexer::TokenKind::*, TokenGroup as TG};
|
|
||||||
match kind {
|
|
||||||
BSlash | Pound | Eof | Ct => TG::Blank,
|
|
||||||
Comment => TG::Comment,
|
|
||||||
Directive => TG::Directive,
|
|
||||||
Colon => TG::Colon,
|
|
||||||
Semi | Comma => TG::Comma,
|
|
||||||
Dot => TG::Dot,
|
|
||||||
Ctor | Tupl | TArrow => TG::Ctor,
|
|
||||||
LParen | RParen => TG::Paren,
|
|
||||||
LBrace | RBrace | LBrack | RBrack => TG::Bracket,
|
|
||||||
Number | Float => TG::Number,
|
|
||||||
Under | CtIdent | Ident => TG::Identifier,
|
|
||||||
Tick | Tilde | Que | Not | Mod | Band | Bor | Xor | Mul | Add | Sub | Div | Shl | Shr
|
|
||||||
| Or | And | Lt | Gt | Eq | Le | Ge | Ne => TG::Op,
|
|
||||||
Decl | Assign | BorAss | XorAss | BandAss | AddAss | SubAss | MulAss | DivAss | ModAss
|
|
||||||
| ShrAss | ShlAss => TG::Assign,
|
|
||||||
DQuote | Quote => TG::String,
|
|
||||||
Return | If | Else | Loop | Break | Continue | Fn | Idk | Die | Struct | Packed | True
|
|
||||||
| False | Null | Match | Enum => TG::Keyword,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_token_kinds(mut source: &mut [u8]) -> usize {
|
|
||||||
let len = source.len();
|
|
||||||
loop {
|
|
||||||
let src = unsafe { core::str::from_utf8_unchecked(source) };
|
|
||||||
let mut token = lexer::Lexer::new(src).eat();
|
|
||||||
match token.kind {
|
|
||||||
TokenKind::Eof => break,
|
|
||||||
// ???
|
|
||||||
TokenKind::CtIdent | TokenKind::Directive => token.start -= 1,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
let start = token.start as usize;
|
|
||||||
let end = token.end as usize;
|
|
||||||
source[..start].fill(0);
|
|
||||||
source[start..end].fill(token_group(token.kind) as u8);
|
|
||||||
source = &mut source[end..];
|
|
||||||
}
|
|
||||||
len
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn minify(source: &mut str) -> usize {
|
|
||||||
fn needs_space(c: u8) -> bool {
|
|
||||||
matches!(c, b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | 127..)
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut writer = source.as_mut_ptr();
|
|
||||||
let mut reader = &source[..];
|
|
||||||
let mut prev_needs_whitecpace = false;
|
|
||||||
let mut prev_needs_newline = false;
|
|
||||||
loop {
|
|
||||||
let mut token = lexer::Lexer::new(reader).eat();
|
|
||||||
match token.kind {
|
|
||||||
TokenKind::Eof => break,
|
|
||||||
TokenKind::CtIdent | TokenKind::Directive => token.start -= 1,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cpy_len = token.range().len();
|
|
||||||
|
|
||||||
let mut prefix = 0;
|
|
||||||
if prev_needs_whitecpace && needs_space(reader.as_bytes()[token.start as usize]) {
|
|
||||||
prefix = b' ';
|
|
||||||
debug_assert!(token.start != 0, "{reader}");
|
|
||||||
}
|
|
||||||
prev_needs_whitecpace = needs_space(reader.as_bytes()[token.end as usize - 1]);
|
|
||||||
|
|
||||||
let inbetween_new_lines =
|
|
||||||
reader[..token.start as usize].bytes().filter(|&b| b == b'\n').count()
|
|
||||||
+ token.kind.precedence().is_some() as usize;
|
|
||||||
let extra_prefix_new_lines = if inbetween_new_lines > 1 {
|
|
||||||
1 + token.kind.precedence().is_none() as usize
|
|
||||||
} else {
|
|
||||||
prev_needs_newline as usize
|
|
||||||
};
|
|
||||||
|
|
||||||
if token.kind == TokenKind::Comment && reader.as_bytes()[token.end as usize - 1] != b'/' {
|
|
||||||
prev_needs_newline = true;
|
|
||||||
prev_needs_whitecpace = false;
|
|
||||||
} else {
|
|
||||||
prev_needs_newline = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sstr = reader[token.start as usize..].as_ptr();
|
|
||||||
reader = &reader[token.end as usize..];
|
|
||||||
unsafe {
|
|
||||||
if extra_prefix_new_lines != 0 {
|
|
||||||
for _ in 0..extra_prefix_new_lines {
|
|
||||||
writer.write(b'\n');
|
|
||||||
writer = writer.add(1);
|
|
||||||
}
|
|
||||||
} else if prefix != 0 {
|
|
||||||
writer.write(prefix);
|
|
||||||
writer = writer.add(1);
|
|
||||||
}
|
|
||||||
writer.copy_from(sstr, cpy_len);
|
|
||||||
writer = writer.add(cpy_len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe { writer.sub_ptr(source.as_mut_ptr()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Formatter<'a> {
|
|
||||||
source: &'a str,
|
|
||||||
depth: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
// we exclusively use `write_str` to reduce bloat
|
|
||||||
impl<'a> Formatter<'a> {
|
|
||||||
pub fn new(source: &'a str) -> Self {
|
|
||||||
Self { source, depth: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_list<T: Poser, F: core::fmt::Write>(
|
|
||||||
&mut self,
|
|
||||||
f: &mut F,
|
|
||||||
trailing: bool,
|
|
||||||
end: &str,
|
|
||||||
sep: &str,
|
|
||||||
list: &[T],
|
|
||||||
fmt: impl Fn(&mut Self, &T, &mut F) -> fmt::Result,
|
|
||||||
) -> fmt::Result {
|
|
||||||
self.fmt_list_low(f, trailing, end, sep, list, |s, v, f| {
|
|
||||||
fmt(s, v, f)?;
|
|
||||||
Ok(true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_list_low<T: Poser, F: core::fmt::Write>(
|
|
||||||
&mut self,
|
|
||||||
f: &mut F,
|
|
||||||
trailing: bool,
|
|
||||||
end: &str,
|
|
||||||
sep: &str,
|
|
||||||
list: &[T],
|
|
||||||
fmt: impl Fn(&mut Self, &T, &mut F) -> Result<bool, fmt::Error>,
|
|
||||||
) -> fmt::Result {
|
|
||||||
if !trailing {
|
|
||||||
let mut first = true;
|
|
||||||
for expr in list {
|
|
||||||
if !core::mem::take(&mut first) {
|
|
||||||
f.write_str(sep)?;
|
|
||||||
f.write_str(" ")?;
|
|
||||||
}
|
|
||||||
first = !fmt(self, expr, f)?;
|
|
||||||
}
|
|
||||||
return f.write_str(end);
|
|
||||||
}
|
|
||||||
|
|
||||||
writeln!(f)?;
|
|
||||||
self.depth += 1;
|
|
||||||
let res = (|| {
|
|
||||||
for (i, stmt) in list.iter().enumerate() {
|
|
||||||
for _ in 0..self.depth {
|
|
||||||
f.write_str("\t")?;
|
|
||||||
}
|
|
||||||
let add_sep = fmt(self, stmt, f)?;
|
|
||||||
if add_sep {
|
|
||||||
f.write_str(sep)?;
|
|
||||||
}
|
|
||||||
if let Some(expr) = list.get(i + 1)
|
|
||||||
&& let Some(rest) = self.source.get(expr.posi() as usize..)
|
|
||||||
{
|
|
||||||
if sep.is_empty() && insert_needed_semicolon(rest) {
|
|
||||||
f.write_str(";")?;
|
|
||||||
}
|
|
||||||
if preserve_newlines(&self.source[..expr.posi() as usize]) > 1 {
|
|
||||||
f.write_str("\n")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if add_sep {
|
|
||||||
f.write_str("\n")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})();
|
|
||||||
self.depth -= 1;
|
|
||||||
|
|
||||||
for _ in 0..self.depth {
|
|
||||||
f.write_str("\t")?;
|
|
||||||
}
|
|
||||||
f.write_str(end)?;
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fmt_paren<F: core::fmt::Write>(
|
|
||||||
&mut self,
|
|
||||||
expr: &Expr,
|
|
||||||
f: &mut F,
|
|
||||||
cond: impl FnOnce(&Expr) -> bool,
|
|
||||||
) -> fmt::Result {
|
|
||||||
if cond(expr) {
|
|
||||||
f.write_str("(")?;
|
|
||||||
self.fmt(expr, f)?;
|
|
||||||
f.write_str(")")
|
|
||||||
} else {
|
|
||||||
self.fmt(expr, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fmt<F: core::fmt::Write>(&mut self, expr: &Expr, f: &mut F) -> fmt::Result {
|
|
||||||
macro_rules! impl_parenter {
|
|
||||||
($($name:ident => $pat:pat,)*) => {
|
|
||||||
$(
|
|
||||||
let $name = |e: &Expr| matches!(e, $pat);
|
|
||||||
)*
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl_parenter! {
|
|
||||||
unary => Expr::BinOp { .. },
|
|
||||||
postfix => Expr::UnOp { .. } | Expr::BinOp { .. },
|
|
||||||
consecutive => Expr::UnOp { .. },
|
|
||||||
}
|
|
||||||
|
|
||||||
match *expr {
|
|
||||||
Expr::Ct { value, .. } => {
|
|
||||||
f.write_str("$: ")?;
|
|
||||||
self.fmt(value, f)
|
|
||||||
}
|
|
||||||
Expr::String { literal, .. } => f.write_str(literal),
|
|
||||||
Expr::Comment { literal, .. } => f.write_str(literal),
|
|
||||||
Expr::Mod { path, .. } => write!(f, "@use(\"{path}\")"),
|
|
||||||
Expr::Embed { path, .. } => write!(f, "@embed(\"{path}\")"),
|
|
||||||
Expr::Field { target, name: field, .. } => {
|
|
||||||
self.fmt_paren(target, f, postfix)?;
|
|
||||||
f.write_str(".")?;
|
|
||||||
f.write_str(field)
|
|
||||||
}
|
|
||||||
Expr::Directive { name, args, .. } => {
|
|
||||||
f.write_str("@")?;
|
|
||||||
f.write_str(name)?;
|
|
||||||
f.write_str("(")?;
|
|
||||||
self.fmt_list(f, false, ")", ",", args, Self::fmt)
|
|
||||||
}
|
|
||||||
Expr::Struct { fields, trailing_comma, packed, .. } => {
|
|
||||||
if packed {
|
|
||||||
f.write_str("packed ")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
f.write_str("struct {")?;
|
|
||||||
self.fmt_list_low(f, trailing_comma, "}", ",", fields, |s, field, f| {
|
|
||||||
match field {
|
|
||||||
CommentOr::Or(StructField { name, ty, .. }) => {
|
|
||||||
f.write_str(name)?;
|
|
||||||
f.write_str(": ")?;
|
|
||||||
s.fmt(ty, f)?
|
|
||||||
}
|
|
||||||
CommentOr::Comment { literal, .. } => {
|
|
||||||
f.write_str(literal)?;
|
|
||||||
f.write_str("\n")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(field.or().is_some())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Expr::Enum { variants, trailing_comma, .. } => {
|
|
||||||
f.write_str("enum {")?;
|
|
||||||
self.fmt_list_low(f, trailing_comma, "}", ",", variants, |_, var, f| {
|
|
||||||
match var {
|
|
||||||
CommentOr::Or(EnumField { name, .. }) => {
|
|
||||||
f.write_str(name)?;
|
|
||||||
}
|
|
||||||
CommentOr::Comment { literal, .. } => {
|
|
||||||
f.write_str(literal)?;
|
|
||||||
f.write_str("\n")?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(var.or().is_some())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Expr::Ctor { ty, fields, trailing_comma, .. } => {
|
|
||||||
if let Some(ty) = ty {
|
|
||||||
self.fmt_paren(ty, f, unary)?;
|
|
||||||
}
|
|
||||||
f.write_str(".{")?;
|
|
||||||
self.fmt_list(
|
|
||||||
f,
|
|
||||||
trailing_comma,
|
|
||||||
"}",
|
|
||||||
",",
|
|
||||||
fields,
|
|
||||||
|s: &mut Self, CtorField { name, value, .. }: &_, f| {
|
|
||||||
f.write_str(name)?;
|
|
||||||
if !matches!(value, &Expr::Ident { id, .. } if *name == &self.source[id.range()]) {
|
|
||||||
f.write_str(": ")?;
|
|
||||||
s.fmt(value, f)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Expr::Tupl {
|
|
||||||
pos,
|
|
||||||
ty: Some(&Expr::Slice { pos: spos, size: Some(&Expr::Number { value, .. }), item }),
|
|
||||||
fields,
|
|
||||||
trailing_comma,
|
|
||||||
} if value as usize == fields.len() => self.fmt(
|
|
||||||
&Expr::Tupl {
|
|
||||||
pos,
|
|
||||||
ty: Some(&Expr::Slice { pos: spos, size: None, item }),
|
|
||||||
fields,
|
|
||||||
trailing_comma,
|
|
||||||
},
|
|
||||||
f,
|
|
||||||
),
|
|
||||||
Expr::Tupl { ty, fields, trailing_comma, .. } => {
|
|
||||||
if let Some(ty) = ty {
|
|
||||||
self.fmt_paren(ty, f, unary)?;
|
|
||||||
}
|
|
||||||
f.write_str(".(")?;
|
|
||||||
self.fmt_list(f, trailing_comma, ")", ",", fields, Self::fmt)
|
|
||||||
}
|
|
||||||
Expr::Slice { item, size, .. } => {
|
|
||||||
f.write_str("[")?;
|
|
||||||
self.fmt(item, f)?;
|
|
||||||
if let Some(size) = size {
|
|
||||||
f.write_str("; ")?;
|
|
||||||
self.fmt(size, f)?;
|
|
||||||
}
|
|
||||||
f.write_str("]")
|
|
||||||
}
|
|
||||||
Expr::Index { base, index } => {
|
|
||||||
self.fmt(base, f)?;
|
|
||||||
f.write_str("[")?;
|
|
||||||
self.fmt(index, f)?;
|
|
||||||
f.write_str("]")
|
|
||||||
}
|
|
||||||
Expr::UnOp { op, val, .. } => {
|
|
||||||
f.write_str(op.name())?;
|
|
||||||
self.fmt_paren(val, f, unary)
|
|
||||||
}
|
|
||||||
Expr::Break { .. } => f.write_str("break"),
|
|
||||||
Expr::Continue { .. } => f.write_str("continue"),
|
|
||||||
Expr::If { cond, then, else_, .. } => {
|
|
||||||
f.write_str("if ")?;
|
|
||||||
self.fmt(cond, f)?;
|
|
||||||
f.write_str(" ")?;
|
|
||||||
self.fmt_paren(then, f, consecutive)?;
|
|
||||||
if let Some(e) = else_ {
|
|
||||||
f.write_str(" else ")?;
|
|
||||||
self.fmt(e, f)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Expr::Match { value, branches, .. } => {
|
|
||||||
f.write_str("match ")?;
|
|
||||||
self.fmt(value, f)?;
|
|
||||||
f.write_str(" {")?;
|
|
||||||
self.fmt_list(f, true, "}", ",", branches, |s, br, f| {
|
|
||||||
s.fmt(&br.pat, f)?;
|
|
||||||
f.write_str(" => ")?;
|
|
||||||
s.fmt(&br.body, f)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Expr::Loop { body, .. } => {
|
|
||||||
f.write_str("loop ")?;
|
|
||||||
self.fmt(body, f)
|
|
||||||
}
|
|
||||||
Expr::Closure { ret, body, args, .. } => {
|
|
||||||
f.write_str("fn(")?;
|
|
||||||
self.fmt_list(f, false, "", ",", args, |s, arg, f| {
|
|
||||||
if arg.is_ct {
|
|
||||||
f.write_str("$")?;
|
|
||||||
}
|
|
||||||
f.write_str(arg.name)?;
|
|
||||||
f.write_str(": ")?;
|
|
||||||
s.fmt(&arg.ty, f)
|
|
||||||
})?;
|
|
||||||
f.write_str("): ")?;
|
|
||||||
self.fmt(ret, f)?;
|
|
||||||
f.write_str(" ")?;
|
|
||||||
self.fmt_paren(body, f, consecutive)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Expr::Call { func, args, trailing_comma } => {
|
|
||||||
self.fmt_paren(func, f, postfix)?;
|
|
||||||
f.write_str("(")?;
|
|
||||||
self.fmt_list(f, trailing_comma, ")", ",", args, Self::fmt)
|
|
||||||
}
|
|
||||||
Expr::Return { val: Some(val), .. } => {
|
|
||||||
f.write_str("return ")?;
|
|
||||||
self.fmt(val, f)
|
|
||||||
}
|
|
||||||
Expr::Return { val: None, .. } => f.write_str("return"),
|
|
||||||
Expr::Wildcard { .. } => f.write_str("_"),
|
|
||||||
Expr::Ident { pos, is_ct, .. } => {
|
|
||||||
if is_ct {
|
|
||||||
f.write_str("$")?;
|
|
||||||
}
|
|
||||||
f.write_str(&self.source[Lexer::restore(self.source, pos).eat().range()])
|
|
||||||
}
|
|
||||||
Expr::Block { stmts, .. } => {
|
|
||||||
f.write_str("{")?;
|
|
||||||
self.fmt_list(f, true, "}", "", stmts, Self::fmt)
|
|
||||||
}
|
|
||||||
Expr::Number { value, radix, .. } => {
|
|
||||||
f.write_str(match radix {
|
|
||||||
Radix::Decimal => "",
|
|
||||||
Radix::Hex => "0x",
|
|
||||||
Radix::Octal => "0o",
|
|
||||||
Radix::Binary => "0b",
|
|
||||||
})?;
|
|
||||||
let mut buf = [0u8; 64];
|
|
||||||
f.write_str(display_radix(radix, value as u64, &mut buf))
|
|
||||||
}
|
|
||||||
Expr::Float { pos, .. } => {
|
|
||||||
f.write_str(&self.source[Lexer::restore(self.source, pos).eat().range()])
|
|
||||||
}
|
|
||||||
Expr::Bool { value, .. } => f.write_str(if value { "true" } else { "false" }),
|
|
||||||
Expr::Idk { .. } => f.write_str("idk"),
|
|
||||||
Expr::Die { .. } => f.write_str("die"),
|
|
||||||
Expr::Null { .. } => f.write_str("null"),
|
|
||||||
Expr::BinOp {
|
|
||||||
left,
|
|
||||||
op: TokenKind::Assign,
|
|
||||||
right: &Expr::BinOp { left: lleft, op, right, .. },
|
|
||||||
..
|
|
||||||
} if left.pos() == lleft.pos() => {
|
|
||||||
self.fmt(left, f)?;
|
|
||||||
f.write_str(" ")?;
|
|
||||||
f.write_str(op.name())?;
|
|
||||||
f.write_str("= ")?;
|
|
||||||
self.fmt(right, f)
|
|
||||||
}
|
|
||||||
Expr::BinOp { right, op, left, .. } => {
|
|
||||||
let prec_miss_left = |e: &Expr| {
|
|
||||||
matches!(
|
|
||||||
e, Expr::BinOp { op: lop, .. } if op.precedence() > lop.precedence()
|
|
||||||
)
|
|
||||||
};
|
|
||||||
let prec_miss_right = |e: &Expr| {
|
|
||||||
matches!(
|
|
||||||
e, Expr::BinOp { op: lop, .. }
|
|
||||||
if (op.precedence() == lop.precedence() && !op.is_comutative())
|
|
||||||
|| op.precedence() > lop.precedence()
|
|
||||||
)
|
|
||||||
};
|
|
||||||
|
|
||||||
self.fmt_paren(left, f, prec_miss_left)?;
|
|
||||||
if let Some(mut prev) = self.source.get(..right.pos() as usize) {
|
|
||||||
prev = prev.trim_end();
|
|
||||||
let estimate_bound =
|
|
||||||
prev.rfind(|c: char| c.is_ascii_whitespace()).map_or(prev.len(), |i| i + 1);
|
|
||||||
let exact_bound = lexer::Lexer::new(&prev[estimate_bound..]).last().start;
|
|
||||||
prev = &prev[..exact_bound as usize + estimate_bound];
|
|
||||||
if preserve_newlines(prev) > 0 {
|
|
||||||
f.write_str("\n")?;
|
|
||||||
for _ in 0..self.depth + 1 {
|
|
||||||
f.write_str("\t")?;
|
|
||||||
}
|
|
||||||
f.write_str(op.name())?;
|
|
||||||
f.write_str(" ")?;
|
|
||||||
} else {
|
|
||||||
f.write_str(" ")?;
|
|
||||||
f.write_str(op.name())?;
|
|
||||||
f.write_str(" ")?;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
f.write_str(" ")?;
|
|
||||||
f.write_str(op.name())?;
|
|
||||||
f.write_str(" ")?;
|
|
||||||
}
|
|
||||||
self.fmt_paren(right, f, prec_miss_right)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn preserve_newlines(source: &str) -> usize {
|
|
||||||
source[source.trim_end().len()..].bytes().filter(|&c| c == b'\n').count()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_needed_semicolon(source: &str) -> bool {
|
|
||||||
let kind = lexer::Lexer::new(source).eat().kind;
|
|
||||||
kind.precedence().is_some() || matches!(kind, TokenKind::Ctor | TokenKind::Tupl)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::fmt::Display for parser::Ast {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
fmt_file(self.exprs(), &self.file, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fmt_file(exprs: &[Expr], file: &str, f: &mut impl fmt::Write) -> fmt::Result {
|
|
||||||
for (i, expr) in exprs.iter().enumerate() {
|
|
||||||
Formatter::new(file).fmt(expr, f)?;
|
|
||||||
if let Some(expr) = exprs.get(i + 1)
|
|
||||||
&& let Some(rest) = file.get(expr.pos() as usize..)
|
|
||||||
{
|
|
||||||
if insert_needed_semicolon(rest) {
|
|
||||||
write!(f, ";")?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if preserve_newlines(&file[..expr.pos() as usize]) > 1 {
|
|
||||||
writeln!(f)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if i + 1 != exprs.len() {
|
|
||||||
writeln!(f)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub mod test {
|
|
||||||
use {
|
|
||||||
crate::parser::{self, Ctx},
|
|
||||||
alloc::borrow::ToOwned,
|
|
||||||
std::{fmt::Write, string::String},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn format(ident: &str, input: &str) {
|
|
||||||
let mut minned = input.to_owned();
|
|
||||||
let len = crate::fmt::minify(&mut minned);
|
|
||||||
minned.truncate(len);
|
|
||||||
|
|
||||||
let mut ctx = Ctx::default();
|
|
||||||
let ast = parser::Ast::new(ident, minned, &mut ctx, &mut parser::no_loader);
|
|
||||||
let mut output = String::new();
|
|
||||||
write!(output, "{ast}").unwrap();
|
|
||||||
|
|
||||||
let input_path = format!("formatter_{ident}.expected");
|
|
||||||
let output_path = format!("formatter_{ident}.actual");
|
|
||||||
std::fs::write(&input_path, input).unwrap();
|
|
||||||
std::fs::write(&output_path, output).unwrap();
|
|
||||||
|
|
||||||
let success = std::process::Command::new("diff")
|
|
||||||
.arg("-u")
|
|
||||||
.arg("--color")
|
|
||||||
.arg(&input_path)
|
|
||||||
.arg(&output_path)
|
|
||||||
.status()
|
|
||||||
.unwrap()
|
|
||||||
.success();
|
|
||||||
std::fs::remove_file(&input_path).unwrap();
|
|
||||||
std::fs::remove_file(&output_path).unwrap();
|
|
||||||
assert!(success, "test failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! test {
|
|
||||||
($($name:ident => $input:expr;)*) => {$(
|
|
||||||
#[test]
|
|
||||||
fn $name() {
|
|
||||||
format(stringify!($name), $input);
|
|
||||||
}
|
|
||||||
)*};
|
|
||||||
}
|
|
||||||
|
|
||||||
test! {
|
|
||||||
comments => "// comment\n// comment\n\n// comment\n\n\
|
|
||||||
/* comment */\n/* comment */\n\n/* comment */";
|
|
||||||
some_ordinary_code => "loft := fn(): int return loft(1, 2, 3)";
|
|
||||||
some_arg_per_line_code => "loft := fn(): int return loft(\
|
|
||||||
\n\t1,\n\t2,\n\t3,\n)";
|
|
||||||
some_ordinary_struct => "loft := fn(): int return loft.{a: 1, b: 2}";
|
|
||||||
some_ordinary_fild_per_lin_struct => "loft := fn(): int return loft.{\
|
|
||||||
\n\ta: 1,\n\tb: 2,\n}";
|
|
||||||
code_block => "loft := fn(): int {\n\tloft()\n\treturn 1\n}";
|
|
||||||
}
|
|
||||||
}
|
|
384
lang/src/fs.rs
384
lang/src/fs.rs
|
@ -1,384 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
parser::{Ast, Ctx, FileKind},
|
|
||||||
son::{self, hbvm::HbvmBackend},
|
|
||||||
ty, FnvBuildHasher,
|
|
||||||
},
|
|
||||||
alloc::{string::String, vec::Vec},
|
|
||||||
core::{fmt::Write, num::NonZeroUsize, ops::Deref},
|
|
||||||
hashbrown::hash_map,
|
|
||||||
std::{
|
|
||||||
collections::VecDeque,
|
|
||||||
eprintln,
|
|
||||||
ffi::OsStr,
|
|
||||||
io::{self, Write as _},
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
string::ToString,
|
|
||||||
sync::Mutex,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
type HashMap<K, V> = hashbrown::HashMap<K, V, FnvBuildHasher>;
|
|
||||||
|
|
||||||
pub struct Logger;
|
|
||||||
|
|
||||||
impl log::Log for Logger {
|
|
||||||
fn enabled(&self, _: &log::Metadata) -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log(&self, record: &log::Record) {
|
|
||||||
if self.enabled(record.metadata()) {
|
|
||||||
eprintln!("{}", record.args())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn flush(&self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
pub struct Options {
|
|
||||||
pub fmt: bool,
|
|
||||||
pub fmt_stdout: bool,
|
|
||||||
pub dump_asm: bool,
|
|
||||||
pub in_house_regalloc: bool,
|
|
||||||
pub extra_threads: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Options {
|
|
||||||
pub fn from_args(args: &[&str], out: &mut Vec<u8>) -> std::io::Result<Self> {
|
|
||||||
if args.contains(&"--help") || args.contains(&"-h") {
|
|
||||||
writeln!(out, "Usage: hbc [OPTIONS...] <FILE>")?;
|
|
||||||
writeln!(out, include_str!("../command-help.txt"))?;
|
|
||||||
return Err(std::io::ErrorKind::Other.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Options {
|
|
||||||
fmt: args.contains(&"--fmt"),
|
|
||||||
fmt_stdout: args.contains(&"--fmt-stdout"),
|
|
||||||
dump_asm: args.contains(&"--dump-asm"),
|
|
||||||
in_house_regalloc: args.contains(&"--in-house-regalloc"),
|
|
||||||
extra_threads: args
|
|
||||||
.iter()
|
|
||||||
.position(|&a| a == "--threads")
|
|
||||||
.map(|i| {
|
|
||||||
args[i + 1].parse::<NonZeroUsize>().map_err(|e| {
|
|
||||||
writeln!(out, "--threads expects non zero integer: {e}")
|
|
||||||
.err()
|
|
||||||
.unwrap_or(std::io::ErrorKind::Other.into())
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.transpose()?
|
|
||||||
.map_or(1, NonZeroUsize::get)
|
|
||||||
- 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_compiler(
|
|
||||||
root_file: &str,
|
|
||||||
options: Options,
|
|
||||||
out: &mut Vec<u8>,
|
|
||||||
warnings: &mut String,
|
|
||||||
) -> std::io::Result<()> {
|
|
||||||
let parsed = parse_from_fs(options.extra_threads, root_file)?;
|
|
||||||
|
|
||||||
if (options.fmt || options.fmt_stdout) && !parsed.errors.is_empty() {
|
|
||||||
*out = parsed.errors.into_bytes();
|
|
||||||
return Err(std::io::Error::other("fmt fialed (errors are in out)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if options.fmt {
|
|
||||||
let mut output = String::new();
|
|
||||||
for ast in parsed.ast {
|
|
||||||
write!(output, "{ast}").unwrap();
|
|
||||||
if ast.file.deref().trim() != output.as_str().trim() {
|
|
||||||
std::fs::write(&*ast.path, &output)?;
|
|
||||||
}
|
|
||||||
output.clear();
|
|
||||||
}
|
|
||||||
} else if options.fmt_stdout {
|
|
||||||
write!(out, "{}", &parsed.ast[0])?;
|
|
||||||
} else {
|
|
||||||
let mut backend = HbvmBackend::default();
|
|
||||||
backend.use_in_house_regalloc = options.in_house_regalloc;
|
|
||||||
|
|
||||||
let mut ctx = crate::son::CodegenCtx::default();
|
|
||||||
*ctx.parser.errors.get_mut() = parsed.errors;
|
|
||||||
let mut codegen = son::Codegen::new(&mut backend, &parsed.ast, &mut ctx);
|
|
||||||
codegen.push_embeds(parsed.embeds);
|
|
||||||
codegen.generate(ty::Module::MAIN);
|
|
||||||
|
|
||||||
*warnings = core::mem::take(&mut *codegen.warnings.borrow_mut());
|
|
||||||
|
|
||||||
if !codegen.errors.borrow().is_empty() {
|
|
||||||
drop(codegen);
|
|
||||||
*out = ctx.parser.errors.into_inner().into_bytes();
|
|
||||||
return Err(std::io::Error::other("compilation faoled (errors are in out)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
codegen.assemble(out);
|
|
||||||
|
|
||||||
if options.dump_asm {
|
|
||||||
let mut disasm = String::new();
|
|
||||||
codegen.disasm(&mut disasm, out).map_err(|e| io::Error::other(e.to_string()))?;
|
|
||||||
*out = disasm.into_bytes();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TaskQueue<T> {
|
|
||||||
inner: Mutex<TaskQueueInner<T>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> TaskQueue<T> {
|
|
||||||
fn new(max_waiters: usize) -> Self {
|
|
||||||
Self { inner: Mutex::new(TaskQueueInner::new(max_waiters)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&self, message: T) {
|
|
||||||
self.extend([message]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn extend(&self, messages: impl IntoIterator<Item = T>) {
|
|
||||||
self.inner.lock().unwrap().push(messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(&self) -> Option<T> {
|
|
||||||
TaskQueueInner::pop(&self.inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum TaskSlot<T> {
|
|
||||||
Waiting,
|
|
||||||
Delivered(T),
|
|
||||||
Closed,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TaskQueueInner<T> {
|
|
||||||
max_waiters: usize,
|
|
||||||
messages: VecDeque<T>,
|
|
||||||
parked: VecDeque<(*mut TaskSlot<T>, std::thread::Thread)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl<T: Send> Send for TaskQueueInner<T> {}
|
|
||||||
unsafe impl<T: Send + Sync> Sync for TaskQueueInner<T> {}
|
|
||||||
|
|
||||||
impl<T> TaskQueueInner<T> {
|
|
||||||
fn new(max_waiters: usize) -> Self {
|
|
||||||
Self { max_waiters, messages: Default::default(), parked: Default::default() }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(&mut self, messages: impl IntoIterator<Item = T>) {
|
|
||||||
for msg in messages {
|
|
||||||
if let Some((dest, thread)) = self.parked.pop_front() {
|
|
||||||
unsafe { *dest = TaskSlot::Delivered(msg) };
|
|
||||||
thread.unpark();
|
|
||||||
} else {
|
|
||||||
self.messages.push_back(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pop(s: &Mutex<Self>) -> Option<T> {
|
|
||||||
let mut res = TaskSlot::Waiting;
|
|
||||||
{
|
|
||||||
let mut s = s.lock().unwrap();
|
|
||||||
if let Some(msg) = s.messages.pop_front() {
|
|
||||||
return Some(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.max_waiters == s.parked.len() + 1 {
|
|
||||||
for (dest, thread) in s.parked.drain(..) {
|
|
||||||
unsafe { *dest = TaskSlot::Closed };
|
|
||||||
thread.unpark();
|
|
||||||
}
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
s.parked.push_back((&mut res, std::thread::current()));
|
|
||||||
}
|
|
||||||
|
|
||||||
loop {
|
|
||||||
std::thread::park();
|
|
||||||
|
|
||||||
let _s = s.lock().unwrap();
|
|
||||||
match core::mem::replace(&mut res, TaskSlot::Waiting) {
|
|
||||||
TaskSlot::Delivered(msg) => return Some(msg),
|
|
||||||
TaskSlot::Closed => return None,
|
|
||||||
TaskSlot::Waiting => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Loaded {
|
|
||||||
ast: Vec<Ast>,
|
|
||||||
embeds: Vec<Vec<u8>>,
|
|
||||||
errors: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Loaded> {
|
|
||||||
fn resolve(path: &str, from: &str, tmp: &mut PathBuf) -> Result<PathBuf, CantLoadFile> {
|
|
||||||
tmp.clear();
|
|
||||||
match Path::new(from).parent() {
|
|
||||||
Some(parent) => tmp.extend([parent, Path::new(path)]),
|
|
||||||
None => tmp.push(path),
|
|
||||||
};
|
|
||||||
|
|
||||||
tmp.canonicalize().map_err(|source| CantLoadFile { path: std::mem::take(tmp), source })
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct CantLoadFile {
|
|
||||||
path: PathBuf,
|
|
||||||
source: io::Error,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::fmt::Display for CantLoadFile {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
||||||
write!(f, "can't load file: {}", display_rel_path(&self.path),)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::error::Error for CantLoadFile {
|
|
||||||
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
|
|
||||||
Some(&self.source)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CantLoadFile> for io::Error {
|
|
||||||
fn from(e: CantLoadFile) -> Self {
|
|
||||||
io::Error::new(io::ErrorKind::InvalidData, e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Task = (usize, PathBuf);
|
|
||||||
|
|
||||||
let seen_modules = Mutex::new(HashMap::<PathBuf, usize>::default());
|
|
||||||
let seen_embeds = Mutex::new(HashMap::<PathBuf, usize>::default());
|
|
||||||
let tasks = TaskQueue::<Task>::new(extra_threads + 1);
|
|
||||||
let ast = Mutex::new(Vec::<io::Result<Ast>>::new());
|
|
||||||
let embeds = Mutex::new(Vec::<Vec<u8>>::new());
|
|
||||||
|
|
||||||
let loader = |path: &str, from: &str, kind: FileKind, tmp: &mut _| {
|
|
||||||
let mut physiscal_path = resolve(path, from, tmp)?;
|
|
||||||
|
|
||||||
match kind {
|
|
||||||
FileKind::Module => {
|
|
||||||
let id = {
|
|
||||||
let mut seen = seen_modules.lock().unwrap();
|
|
||||||
let len = seen.len();
|
|
||||||
match seen.entry(physiscal_path) {
|
|
||||||
hash_map::Entry::Occupied(entry) => {
|
|
||||||
return Ok(*entry.get());
|
|
||||||
}
|
|
||||||
hash_map::Entry::Vacant(entry) => {
|
|
||||||
physiscal_path = entry.insert_entry(len as _).key().clone();
|
|
||||||
len
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !physiscal_path.exists() {
|
|
||||||
return Err(io::Error::new(
|
|
||||||
io::ErrorKind::NotFound,
|
|
||||||
format!("can't find file: {}", display_rel_path(&physiscal_path)),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
tasks.push((id, physiscal_path));
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
FileKind::Embed => {
|
|
||||||
let id = {
|
|
||||||
let mut seen = seen_embeds.lock().unwrap();
|
|
||||||
let len = seen.len();
|
|
||||||
match seen.entry(physiscal_path) {
|
|
||||||
hash_map::Entry::Occupied(entry) => {
|
|
||||||
return Ok(*entry.get());
|
|
||||||
}
|
|
||||||
hash_map::Entry::Vacant(entry) => {
|
|
||||||
physiscal_path = entry.insert_entry(len as _).key().clone();
|
|
||||||
len
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let content = std::fs::read(&physiscal_path).map_err(|e| {
|
|
||||||
io::Error::new(
|
|
||||||
e.kind(),
|
|
||||||
format!(
|
|
||||||
"can't load embed file: {}: {e}",
|
|
||||||
display_rel_path(&physiscal_path)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
let mut embeds = embeds.lock().unwrap();
|
|
||||||
if id as usize >= embeds.len() {
|
|
||||||
embeds.resize(id as usize + 1, Default::default());
|
|
||||||
}
|
|
||||||
embeds[id as usize] = content;
|
|
||||||
Ok(id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let execute_task = |ctx: &mut _, (_, path): Task, tmp: &mut _| {
|
|
||||||
let path = path.to_str().ok_or_else(|| {
|
|
||||||
io::Error::new(
|
|
||||||
io::ErrorKind::InvalidData,
|
|
||||||
format!("path contains invalid characters: {}", display_rel_path(&path)),
|
|
||||||
)
|
|
||||||
})?;
|
|
||||||
Ok(Ast::new(path, std::fs::read_to_string(path)?, ctx, &mut |path, from, kind| {
|
|
||||||
loader(path, from, kind, tmp).map_err(|e| e.to_string())
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
|
|
||||||
let thread = || {
|
|
||||||
let mut ctx = Ctx::default();
|
|
||||||
let mut tmp = PathBuf::new();
|
|
||||||
while let Some(task @ (indx, ..)) = tasks.pop() {
|
|
||||||
let res = execute_task(&mut ctx, task, &mut tmp);
|
|
||||||
let mut ast = ast.lock().unwrap();
|
|
||||||
let len = ast.len().max(indx + 1);
|
|
||||||
ast.resize_with(len, || Err(io::ErrorKind::InvalidData.into()));
|
|
||||||
ast[indx] = res;
|
|
||||||
}
|
|
||||||
ctx.errors.into_inner()
|
|
||||||
};
|
|
||||||
|
|
||||||
let path = Path::new(root).canonicalize().map_err(|e| {
|
|
||||||
io::Error::new(e.kind(), format!("can't canonicalize root file path ({root})"))
|
|
||||||
})?;
|
|
||||||
seen_modules.lock().unwrap().insert(path.clone(), 0);
|
|
||||||
tasks.push((0, path));
|
|
||||||
|
|
||||||
let errors = if extra_threads == 0 {
|
|
||||||
thread()
|
|
||||||
} else {
|
|
||||||
std::thread::scope(|s| {
|
|
||||||
(0..extra_threads + 1)
|
|
||||||
.map(|_| s.spawn(thread))
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.into_iter()
|
|
||||||
.map(|t| t.join().unwrap())
|
|
||||||
.collect::<String>()
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Loaded {
|
|
||||||
ast: ast.into_inner().unwrap().into_iter().collect::<io::Result<Vec<_>>>()?,
|
|
||||||
embeds: embeds.into_inner().unwrap(),
|
|
||||||
errors,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn display_rel_path(path: &(impl AsRef<OsStr> + ?Sized)) -> std::path::Display {
|
|
||||||
static CWD: std::sync::LazyLock<PathBuf> =
|
|
||||||
std::sync::LazyLock::new(|| std::env::current_dir().unwrap_or_default());
|
|
||||||
std::path::Path::new(path).strip_prefix(&*CWD).unwrap_or(std::path::Path::new(path)).display()
|
|
||||||
}
|
|
141
lang/src/fuzz.rs
141
lang/src/fuzz.rs
|
@ -1,141 +0,0 @@
|
||||||
use {
|
|
||||||
crate::{
|
|
||||||
lexer::TokenKind,
|
|
||||||
parser,
|
|
||||||
son::{hbvm::HbvmBackend, Codegen, CodegenCtx},
|
|
||||||
ty::Module,
|
|
||||||
},
|
|
||||||
alloc::string::String,
|
|
||||||
core::{fmt::Write, hash::BuildHasher, ops::Range},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct Rand(pub u64);
|
|
||||||
|
|
||||||
impl Rand {
|
|
||||||
pub fn next(&mut self) -> u64 {
|
|
||||||
self.0 = crate::FnvBuildHasher::default().hash_one(self.0);
|
|
||||||
self.0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn range(&mut self, min: u64, max: u64) -> u64 {
|
|
||||||
self.next() % (max - min) + min
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bool(&mut self) -> bool {
|
|
||||||
self.next() % 2 == 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default)]
|
|
||||||
struct FuncGen {
|
|
||||||
rand: Rand,
|
|
||||||
buf: String,
|
|
||||||
vars: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FuncGen {
|
|
||||||
fn gen(&mut self, seed: u64) -> &str {
|
|
||||||
self.rand = Rand(seed);
|
|
||||||
self.buf.clear();
|
|
||||||
self.buf.push_str("main := fn(): void ");
|
|
||||||
self.block().unwrap();
|
|
||||||
&self.buf
|
|
||||||
}
|
|
||||||
|
|
||||||
fn block(&mut self) -> core::fmt::Result {
|
|
||||||
let prev_vars = self.vars;
|
|
||||||
self.buf.push('{');
|
|
||||||
for _ in 0..self.rand.range(1, 10) {
|
|
||||||
self.stmt()?;
|
|
||||||
}
|
|
||||||
self.buf.push('}');
|
|
||||||
self.vars = prev_vars;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stmt(&mut self) -> core::fmt::Result {
|
|
||||||
match self.rand.range(0, 100) {
|
|
||||||
0..4 => _ = self.block(),
|
|
||||||
4..10 => {
|
|
||||||
write!(self.buf, "var{} := ", self.vars)?;
|
|
||||||
self.expr()?;
|
|
||||||
self.vars += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
10..20 if self.vars != 0 => {
|
|
||||||
write!(self.buf, "var{} = ", self.rand.range(0, self.vars))?;
|
|
||||||
self.expr()?;
|
|
||||||
}
|
|
||||||
20..23 => {
|
|
||||||
self.buf.push_str("if ");
|
|
||||||
self.expr()?;
|
|
||||||
self.block()?;
|
|
||||||
if self.rand.bool() {
|
|
||||||
self.buf.push_str(" else ");
|
|
||||||
self.block()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.buf.push_str("return ");
|
|
||||||
self.expr()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.buf.push(';');
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expr(&mut self) -> core::fmt::Result {
|
|
||||||
match self.rand.range(0, 100) {
|
|
||||||
0..80 => {
|
|
||||||
write!(self.buf, "{}", self.rand.next())
|
|
||||||
}
|
|
||||||
80..90 if self.vars != 0 => {
|
|
||||||
write!(self.buf, "var{}", self.rand.range(0, self.vars))
|
|
||||||
}
|
|
||||||
80..100 => {
|
|
||||||
self.expr()?;
|
|
||||||
let ops = [
|
|
||||||
TokenKind::Add,
|
|
||||||
TokenKind::Sub,
|
|
||||||
TokenKind::Mul,
|
|
||||||
TokenKind::Div,
|
|
||||||
TokenKind::Shl,
|
|
||||||
TokenKind::Eq,
|
|
||||||
TokenKind::Ne,
|
|
||||||
TokenKind::Lt,
|
|
||||||
TokenKind::Gt,
|
|
||||||
TokenKind::Le,
|
|
||||||
TokenKind::Ge,
|
|
||||||
TokenKind::Band,
|
|
||||||
TokenKind::Bor,
|
|
||||||
TokenKind::Xor,
|
|
||||||
TokenKind::Mod,
|
|
||||||
TokenKind::Shr,
|
|
||||||
];
|
|
||||||
let op = ops[self.rand.range(0, ops.len() as u64) as usize];
|
|
||||||
write!(self.buf, " {op} ")?;
|
|
||||||
self.expr()
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fuzz(seed_range: Range<u64>) {
|
|
||||||
let mut gen = FuncGen::default();
|
|
||||||
let mut ctx = CodegenCtx::default();
|
|
||||||
for i in seed_range {
|
|
||||||
ctx.clear();
|
|
||||||
let src = gen.gen(i);
|
|
||||||
let parsed = parser::Ast::new("fuzz", src, &mut ctx.parser, &mut parser::no_loader);
|
|
||||||
|
|
||||||
assert!(ctx.parser.errors.get_mut().is_empty());
|
|
||||||
|
|
||||||
let mut backend = HbvmBackend::default();
|
|
||||||
let mut cdg = Codegen::new(&mut backend, core::slice::from_ref(&parsed), &mut ctx);
|
|
||||||
cdg.generate(Module::MAIN);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
fn main() {
|
|
||||||
hblang::fuzz::fuzz(0..1000000);
|
|
||||||
}
|
|
|
@ -1,567 +0,0 @@
|
||||||
const fn ascii_mask(chars: &[u8]) -> u128 {
|
|
||||||
let mut eq = 0;
|
|
||||||
let mut i = 0;
|
|
||||||
while i < chars.len() {
|
|
||||||
let b = chars[i];
|
|
||||||
eq |= 1 << b;
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
eq
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
|
||||||
pub struct Token {
|
|
||||||
pub kind: TokenKind,
|
|
||||||
pub start: u32,
|
|
||||||
pub end: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Token {
|
|
||||||
pub fn range(&self) -> core::ops::Range<usize> {
|
|
||||||
self.start as usize..self.end as usize
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! gen_token_kind {
|
|
||||||
($(
|
|
||||||
#[$atts:meta])*
|
|
||||||
$vis:vis enum $name:ident {
|
|
||||||
#[patterns] $(
|
|
||||||
$pattern:ident,
|
|
||||||
)*
|
|
||||||
#[keywords] $(
|
|
||||||
$keyword:ident = $keyword_lit:literal,
|
|
||||||
)*
|
|
||||||
#[punkt] $(
|
|
||||||
$punkt:ident = $punkt_lit:literal,
|
|
||||||
)*
|
|
||||||
#[ops] $(
|
|
||||||
#[$prec:ident] $(
|
|
||||||
$op:ident = $op_lit:literal $(=> $assign:ident)?,
|
|
||||||
)*
|
|
||||||
)*
|
|
||||||
}
|
|
||||||
) => {
|
|
||||||
impl core::fmt::Display for $name {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
|
|
||||||
f.write_str(self.name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl $name {
|
|
||||||
pub const OPS: &[Self] = &[$($(Self::$op),*),*];
|
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
|
||||||
let sf = unsafe { &*(self as *const _ as *const u8) } ;
|
|
||||||
match *self {
|
|
||||||
$( Self::$pattern => concat!('<', stringify!($pattern), '>'), )*
|
|
||||||
$( Self::$keyword => stringify!($keyword_lit), )*
|
|
||||||
$( Self::$punkt => stringify!($punkt_lit), )*
|
|
||||||
$($( Self::$op => $op_lit,
|
|
||||||
$(Self::$assign => concat!($op_lit, "="),)?)*)*
|
|
||||||
_ => unsafe { core::str::from_utf8_unchecked(core::slice::from_ref(&sf)) },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn precedence(&self) -> Option<u8> {
|
|
||||||
Some(match self {
|
|
||||||
$($(Self::$op => ${ignore($prec)} ${index(1)},
|
|
||||||
$(Self::$assign => 0,)?)*)*
|
|
||||||
_ => return None,
|
|
||||||
} + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_ident(ident: &[u8]) -> Self {
|
|
||||||
match ident {
|
|
||||||
$($keyword_lit => Self::$keyword,)*
|
|
||||||
_ => Self::Ident,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum TokenKind {
|
|
||||||
Not = b'!',
|
|
||||||
DQuote = b'"',
|
|
||||||
Pound = b'#',
|
|
||||||
CtIdent = b'$',
|
|
||||||
Mod = b'%',
|
|
||||||
Band = b'&',
|
|
||||||
Quote = b'\'',
|
|
||||||
LParen = b'(',
|
|
||||||
RParen = b')',
|
|
||||||
Mul = b'*',
|
|
||||||
Add = b'+',
|
|
||||||
Comma = b',',
|
|
||||||
Sub = b'-',
|
|
||||||
Dot = b'.',
|
|
||||||
Div = b'/',
|
|
||||||
// Unused = 2-6
|
|
||||||
Shl = b'<' - 5,
|
|
||||||
// Unused = 8
|
|
||||||
Shr = b'>' - 5,
|
|
||||||
Colon = b':',
|
|
||||||
Semi = b';',
|
|
||||||
Lt = b'<',
|
|
||||||
Assign = b'=',
|
|
||||||
Gt = b'>',
|
|
||||||
Que = b'?',
|
|
||||||
Directive = b'@',
|
|
||||||
|
|
||||||
Comment,
|
|
||||||
|
|
||||||
Ident,
|
|
||||||
Number,
|
|
||||||
Float,
|
|
||||||
Eof,
|
|
||||||
|
|
||||||
Ct,
|
|
||||||
|
|
||||||
Ctor,
|
|
||||||
Tupl,
|
|
||||||
TArrow,
|
|
||||||
|
|
||||||
Or,
|
|
||||||
And,
|
|
||||||
|
|
||||||
// Unused = R-Z
|
|
||||||
LBrack = b'[',
|
|
||||||
BSlash = b'\\',
|
|
||||||
RBrack = b']',
|
|
||||||
Xor = b'^',
|
|
||||||
Under = b'_',
|
|
||||||
Tick = b'`',
|
|
||||||
|
|
||||||
Return,
|
|
||||||
If,
|
|
||||||
Match,
|
|
||||||
Else,
|
|
||||||
Loop,
|
|
||||||
Break,
|
|
||||||
Continue,
|
|
||||||
Fn,
|
|
||||||
Struct,
|
|
||||||
Packed,
|
|
||||||
Enum,
|
|
||||||
True,
|
|
||||||
False,
|
|
||||||
Null,
|
|
||||||
Idk,
|
|
||||||
Die,
|
|
||||||
|
|
||||||
// Unused = a-z
|
|
||||||
LBrace = b'{',
|
|
||||||
Bor = b'|',
|
|
||||||
RBrace = b'}',
|
|
||||||
Tilde = b'~',
|
|
||||||
|
|
||||||
Decl = b':' + 128,
|
|
||||||
Eq = b'=' + 128,
|
|
||||||
Ne = b'!' + 128,
|
|
||||||
Le = b'<' + 128,
|
|
||||||
Ge = b'>' + 128,
|
|
||||||
|
|
||||||
BorAss = b'|' + 128,
|
|
||||||
AddAss = b'+' + 128,
|
|
||||||
SubAss = b'-' + 128,
|
|
||||||
MulAss = b'*' + 128,
|
|
||||||
DivAss = b'/' + 128,
|
|
||||||
ModAss = b'%' + 128,
|
|
||||||
XorAss = b'^' + 128,
|
|
||||||
BandAss = b'&' + 128,
|
|
||||||
ShrAss = b'>' - 5 + 128,
|
|
||||||
ShlAss = b'<' - 5 + 128,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl core::fmt::Debug for TokenKind {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
core::fmt::Display::fmt(self, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TokenKind {
|
|
||||||
pub fn ass_op(self) -> Option<Self> {
|
|
||||||
let id = (self as u8).saturating_sub(128);
|
|
||||||
if ascii_mask(b"|+-*/%^&79") & (1u128 << id) == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(unsafe { core::mem::transmute::<u8, Self>(id) })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_comutative(self) -> bool {
|
|
||||||
use TokenKind as S;
|
|
||||||
matches!(self, S::Eq | S::Ne | S::Bor | S::Xor | S::Band | S::Add | S::Mul)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_compatison(self) -> bool {
|
|
||||||
matches!(self, Self::Lt | Self::Gt | Self::Ge | Self::Le | Self::Ne | Self::Eq)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_supported_float_op(self) -> bool {
|
|
||||||
matches!(
|
|
||||||
self,
|
|
||||||
Self::Add
|
|
||||||
| Self::Sub
|
|
||||||
| Self::Mul
|
|
||||||
| Self::Div
|
|
||||||
| Self::Eq
|
|
||||||
| Self::Ne
|
|
||||||
| Self::Le
|
|
||||||
| Self::Ge
|
|
||||||
| Self::Lt
|
|
||||||
| Self::Gt
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_binop(self, a: i64, b: i64, float: bool) -> i64 {
|
|
||||||
if float {
|
|
||||||
debug_assert!(self.is_supported_float_op());
|
|
||||||
let [a, b] = [f64::from_bits(a as _), f64::from_bits(b as _)];
|
|
||||||
let res = match self {
|
|
||||||
Self::Add => a + b,
|
|
||||||
Self::Sub => a - b,
|
|
||||||
Self::Mul => a * b,
|
|
||||||
Self::Div => a / b,
|
|
||||||
Self::Eq => return (a == b) as i64,
|
|
||||||
Self::Ne => return (a != b) as i64,
|
|
||||||
Self::Lt => return (a < b) as i64,
|
|
||||||
Self::Gt => return (a > b) as i64,
|
|
||||||
Self::Le => return (a >= b) as i64,
|
|
||||||
Self::Ge => return (a <= b) as i64,
|
|
||||||
_ => todo!("floating point op: {self}"),
|
|
||||||
};
|
|
||||||
|
|
||||||
return res.to_bits() as _;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self {
|
|
||||||
Self::Add => a.wrapping_add(b),
|
|
||||||
Self::Sub => a.wrapping_sub(b),
|
|
||||||
Self::Mul => a.wrapping_mul(b),
|
|
||||||
Self::Div if b == 0 => 0,
|
|
||||||
Self::Div => a.wrapping_div(b),
|
|
||||||
Self::Shl => a.wrapping_shl(b as _),
|
|
||||||
Self::Eq => (a == b) as i64,
|
|
||||||
Self::Ne => (a != b) as i64,
|
|
||||||
Self::Lt => (a < b) as i64,
|
|
||||||
Self::Gt => (a > b) as i64,
|
|
||||||
Self::Le => (a >= b) as i64,
|
|
||||||
Self::Ge => (a <= b) as i64,
|
|
||||||
Self::Band => a & b,
|
|
||||||
Self::Bor => a | b,
|
|
||||||
Self::Xor => a ^ b,
|
|
||||||
Self::Mod if b == 0 => 0,
|
|
||||||
Self::Mod => a.wrapping_rem(b),
|
|
||||||
Self::Shr => a.wrapping_shr(b as _),
|
|
||||||
s => todo!("{s}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_homogenous(&self) -> bool {
|
|
||||||
self.precedence() != Self::Eq.precedence()
|
|
||||||
&& self.precedence() != Self::Gt.precedence()
|
|
||||||
&& self.precedence() != Self::Eof.precedence()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_unop(&self, value: i64, float: bool) -> i64 {
|
|
||||||
match self {
|
|
||||||
Self::Sub if float => (-f64::from_bits(value as _)).to_bits() as _,
|
|
||||||
Self::Sub => value.wrapping_neg(),
|
|
||||||
Self::Not => (value == 0) as _,
|
|
||||||
Self::Float if float => value,
|
|
||||||
Self::Float => (value as f64).to_bits() as _,
|
|
||||||
Self::Number if float => f64::from_bits(value as _) as _,
|
|
||||||
Self::Number => value,
|
|
||||||
s => todo!("{s}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn closing(&self) -> Option<TokenKind> {
|
|
||||||
Some(match self {
|
|
||||||
Self::Ctor => Self::RBrace,
|
|
||||||
Self::Tupl => Self::RParen,
|
|
||||||
Self::LParen => Self::RParen,
|
|
||||||
Self::LBrack => Self::RBrack,
|
|
||||||
Self::LBrace => Self::RBrace,
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gen_token_kind! {
|
|
||||||
pub enum TokenKind {
|
|
||||||
#[patterns]
|
|
||||||
CtIdent,
|
|
||||||
Ident,
|
|
||||||
Number,
|
|
||||||
Float,
|
|
||||||
Eof,
|
|
||||||
Directive,
|
|
||||||
#[keywords]
|
|
||||||
Return = b"return",
|
|
||||||
If = b"if",
|
|
||||||
Match = b"match",
|
|
||||||
Else = b"else",
|
|
||||||
Loop = b"loop",
|
|
||||||
Break = b"break",
|
|
||||||
Continue = b"continue",
|
|
||||||
Fn = b"fn",
|
|
||||||
Struct = b"struct",
|
|
||||||
Packed = b"packed",
|
|
||||||
Enum = b"enum",
|
|
||||||
True = b"true",
|
|
||||||
False = b"false",
|
|
||||||
Null = b"null",
|
|
||||||
Idk = b"idk",
|
|
||||||
Die = b"die",
|
|
||||||
Under = b"_",
|
|
||||||
#[punkt]
|
|
||||||
Ctor = ".{",
|
|
||||||
Tupl = ".(",
|
|
||||||
TArrow = "=>",
|
|
||||||
// #define OP: each `#[prec]` delimeters a level of precedence from lowest to highest
|
|
||||||
#[ops]
|
|
||||||
#[prec]
|
|
||||||
// this also includess all `<op>=` tokens
|
|
||||||
Decl = ":=",
|
|
||||||
Assign = "=",
|
|
||||||
#[prec]
|
|
||||||
Or = "||",
|
|
||||||
#[prec]
|
|
||||||
And = "&&",
|
|
||||||
#[prec]
|
|
||||||
Bor = "|" => BorAss,
|
|
||||||
#[prec]
|
|
||||||
Xor = "^" => XorAss,
|
|
||||||
#[prec]
|
|
||||||
Band = "&" => BandAss,
|
|
||||||
#[prec]
|
|
||||||
Eq = "==",
|
|
||||||
Ne = "!=",
|
|
||||||
#[prec]
|
|
||||||
Le = "<=",
|
|
||||||
Ge = ">=",
|
|
||||||
Lt = "<",
|
|
||||||
Gt = ">",
|
|
||||||
#[prec]
|
|
||||||
Shl = "<<" => ShlAss,
|
|
||||||
Shr = ">>" => ShrAss,
|
|
||||||
#[prec]
|
|
||||||
Add = "+" => AddAss,
|
|
||||||
Sub = "-" => SubAss,
|
|
||||||
#[prec]
|
|
||||||
Mul = "*" => MulAss,
|
|
||||||
Div = "/" => DivAss,
|
|
||||||
Mod = "%" => ModAss,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Lexer<'a> {
|
|
||||||
pos: u32,
|
|
||||||
source: &'a [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Lexer<'a> {
|
|
||||||
pub fn new(input: &'a str) -> Self {
|
|
||||||
Self::restore(input, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uses(input: &'a str) -> impl Iterator<Item = &'a str> {
|
|
||||||
let mut s = Self::new(input);
|
|
||||||
core::iter::from_fn(move || loop {
|
|
||||||
let t = s.eat();
|
|
||||||
if t.kind == TokenKind::Eof {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
if t.kind == TokenKind::Directive
|
|
||||||
&& s.slice(t.range()) == "use"
|
|
||||||
&& s.eat().kind == TokenKind::LParen
|
|
||||||
{
|
|
||||||
let t = s.eat();
|
|
||||||
if t.kind == TokenKind::DQuote {
|
|
||||||
return Some(&s.slice(t.range())[1..t.range().len() - 1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn restore(input: &'a str, pos: u32) -> Self {
|
|
||||||
Self { pos, source: input.as_bytes() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn source(&self) -> &'a str {
|
|
||||||
unsafe { core::str::from_utf8_unchecked(self.source) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slice(&self, tok: core::ops::Range<usize>) -> &'a str {
|
|
||||||
unsafe { core::str::from_utf8_unchecked(&self.source[tok]) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peek(&self) -> Option<u8> {
|
|
||||||
if core::intrinsics::unlikely(self.pos >= self.source.len() as u32) {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(unsafe { *self.source.get_unchecked(self.pos as usize) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance(&mut self) -> Option<u8> {
|
|
||||||
let c = self.peek()?;
|
|
||||||
self.pos += 1;
|
|
||||||
Some(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn last(&mut self) -> Token {
|
|
||||||
let mut token = self.eat();
|
|
||||||
loop {
|
|
||||||
let next = self.eat();
|
|
||||||
if next.kind == TokenKind::Eof {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
token = next;
|
|
||||||
}
|
|
||||||
token
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn eat(&mut self) -> Token {
|
|
||||||
use TokenKind as T;
|
|
||||||
loop {
|
|
||||||
let mut start = self.pos;
|
|
||||||
|
|
||||||
let Some(c) = self.advance() else {
|
|
||||||
return Token { kind: T::Eof, start, end: self.pos };
|
|
||||||
};
|
|
||||||
|
|
||||||
let advance_ident = |s: &mut Self| {
|
|
||||||
while let Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | 127..) = s.peek() {
|
|
||||||
s.advance();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let identity = |s: u8| unsafe { core::mem::transmute::<u8, T>(s) };
|
|
||||||
|
|
||||||
let kind = match c {
|
|
||||||
..=b' ' => continue,
|
|
||||||
b'0' if self.advance_if(b'x') => {
|
|
||||||
while let Some(b'0'..=b'9' | b'A'..=b'F' | b'a'..=b'f') = self.peek() {
|
|
||||||
self.advance();
|
|
||||||
}
|
|
||||||
T::Number
|
|
||||||
}
|
|
||||||
b'0' if self.advance_if(b'b') => {
|
|
||||||
while let Some(b'0' | b'1') = self.peek() {
|
|
||||||
self.advance();
|
|
||||||
}
|
|
||||||
T::Number
|
|
||||||
}
|
|
||||||
b'0' if self.advance_if(b'o') => {
|
|
||||||
while let Some(b'0'..=b'7') = self.peek() {
|
|
||||||
self.advance();
|
|
||||||
}
|
|
||||||
T::Number
|
|
||||||
}
|
|
||||||
b'0'..=b'9' => {
|
|
||||||
while let Some(b'0'..=b'9') = self.peek() {
|
|
||||||
self.advance();
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.advance_if(b'.') {
|
|
||||||
while let Some(b'0'..=b'9') = self.peek() {
|
|
||||||
self.advance();
|
|
||||||
}
|
|
||||||
T::Float
|
|
||||||
} else {
|
|
||||||
T::Number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b'a'..=b'z' | b'A'..=b'Z' | b'_' | 127.. => {
|
|
||||||
advance_ident(self);
|
|
||||||
let ident = &self.source[start as usize..self.pos as usize];
|
|
||||||
T::from_ident(ident)
|
|
||||||
}
|
|
||||||
b'"' | b'\'' => loop {
|
|
||||||
match self.advance() {
|
|
||||||
Some(b'\\') => _ = self.advance(),
|
|
||||||
Some(nc) if nc == c => break identity(c),
|
|
||||||
Some(_) => {}
|
|
||||||
None => break T::Eof,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
b'/' if self.advance_if(b'/') => {
|
|
||||||
while let Some(l) = self.peek()
|
|
||||||
&& l != b'\n'
|
|
||||||
{
|
|
||||||
self.pos += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let end = self.source[..self.pos as usize]
|
|
||||||
.iter()
|
|
||||||
.rposition(|&b| !b.is_ascii_whitespace())
|
|
||||||
.map_or(self.pos, |i| i as u32 + 1);
|
|
||||||
|
|
||||||
return Token { kind: T::Comment, start, end };
|
|
||||||
}
|
|
||||||
b'/' if self.advance_if(b'*') => {
|
|
||||||
let mut depth = 1;
|
|
||||||
while let Some(l) = self.advance() {
|
|
||||||
match l {
|
|
||||||
b'/' if self.advance_if(b'*') => depth += 1,
|
|
||||||
b'*' if self.advance_if(b'/') => match depth {
|
|
||||||
1 => break,
|
|
||||||
_ => depth -= 1,
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
T::Comment
|
|
||||||
}
|
|
||||||
b'.' if self.advance_if(b'{') => T::Ctor,
|
|
||||||
b'.' if self.advance_if(b'(') => T::Tupl,
|
|
||||||
b'=' if self.advance_if(b'>') => T::TArrow,
|
|
||||||
b'&' if self.advance_if(b'&') => T::And,
|
|
||||||
b'|' if self.advance_if(b'|') => T::Or,
|
|
||||||
b'$' if self.advance_if(b':') => T::Ct,
|
|
||||||
b'@' | b'$' => {
|
|
||||||
start += 1;
|
|
||||||
advance_ident(self);
|
|
||||||
identity(c)
|
|
||||||
}
|
|
||||||
b'<' | b'>' if self.advance_if(c) => {
|
|
||||||
identity(c - 5 + 128 * self.advance_if(b'=') as u8)
|
|
||||||
}
|
|
||||||
b':' | b'=' | b'!' | b'<' | b'>' | b'|' | b'+' | b'-' | b'*' | b'/' | b'%'
|
|
||||||
| b'^' | b'&'
|
|
||||||
if self.advance_if(b'=') =>
|
|
||||||
{
|
|
||||||
identity(c + 128)
|
|
||||||
}
|
|
||||||
_ => identity(c),
|
|
||||||
};
|
|
||||||
|
|
||||||
return Token { kind, start, end: self.pos };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance_if(&mut self, arg: u8) -> bool {
|
|
||||||
if self.peek() == Some(arg) {
|
|
||||||
self.advance();
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn line_col(bytes: &[u8], pos: u32) -> (usize, usize) {
|
|
||||||
bytes[..pos as usize]
|
|
||||||
.split(|&b| b == b'\n')
|
|
||||||
.map(<[u8]>::len)
|
|
||||||
.enumerate()
|
|
||||||
.last()
|
|
||||||
.map(|(line, col)| (line + 1, col + 1))
|
|
||||||
.unwrap_or((1, 1))
|
|
||||||
}
|
|
1534
lang/src/lib.rs
1534
lang/src/lib.rs
File diff suppressed because it is too large
Load diff
|
@ -1,31 +0,0 @@
|
||||||
#[cfg(feature = "std")]
|
|
||||||
fn main() {
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
fn run(out: &mut Vec<u8>, warnings: &mut String) -> std::io::Result<()> {
|
|
||||||
let args = std::env::args().collect::<Vec<_>>();
|
|
||||||
let args = args.iter().map(String::as_str).collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let opts = hblang::Options::from_args(&args, out)?;
|
|
||||||
let file = args.iter().filter(|a| !a.starts_with('-')).nth(1).copied().unwrap_or("main.hb");
|
|
||||||
|
|
||||||
hblang::run_compiler(file, opts, out, warnings)
|
|
||||||
}
|
|
||||||
|
|
||||||
log::set_logger(&hblang::fs::Logger).unwrap();
|
|
||||||
log::set_max_level(log::LevelFilter::Error);
|
|
||||||
|
|
||||||
let mut out = Vec::new();
|
|
||||||
let mut warnings = String::new();
|
|
||||||
match run(&mut out, &mut warnings) {
|
|
||||||
Ok(_) => {
|
|
||||||
std::io::stderr().write_all(warnings.as_bytes()).unwrap();
|
|
||||||
std::io::stdout().write_all(&out).unwrap()
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
std::io::stderr().write_all(warnings.as_bytes()).unwrap();
|
|
||||||
std::io::stderr().write_all(&out).unwrap();
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
1625
lang/src/parser.rs
1625
lang/src/parser.rs
File diff suppressed because it is too large
Load diff
5451
lang/src/son.rs
5451
lang/src/son.rs
File diff suppressed because it is too large
Load diff
1147
lang/src/son/hbvm.rs
1147
lang/src/son/hbvm.rs
File diff suppressed because it is too large
Load diff
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,649 +0,0 @@
|
||||||
#![expect(dead_code)]
|
|
||||||
use {
|
|
||||||
alloc::alloc,
|
|
||||||
core::{
|
|
||||||
alloc::Layout,
|
|
||||||
fmt::Debug,
|
|
||||||
hint::unreachable_unchecked,
|
|
||||||
marker::PhantomData,
|
|
||||||
mem::MaybeUninit,
|
|
||||||
ops::{Deref, DerefMut, Not},
|
|
||||||
ptr::Unique,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
fn decide(b: bool, name: &'static str) -> Result<(), &'static str> {
|
|
||||||
b.then_some(()).ok_or(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_snake_case(str: &str) -> Result<(), &'static str> {
|
|
||||||
decide(str.bytes().all(|c| matches!(c, b'a'..=b'z' | b'0'..=b'9' | b'_')), "snake_case")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_pascal_case(str: &str) -> Result<(), &'static str> {
|
|
||||||
decide(
|
|
||||||
str.as_bytes()[0].is_ascii_uppercase() && str.bytes().all(|c| c.is_ascii_alphanumeric()),
|
|
||||||
"PascalCase",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_screaming_case(str: &str) -> Result<(), &'static str> {
|
|
||||||
decide(str.bytes().all(|c| matches!(c, b'A'..=b'Z' | b'0'..=b'9' | b'_')), "SCREAMING_CASE")
|
|
||||||
}
|
|
||||||
|
|
||||||
type Nid = u16;
|
|
||||||
|
|
||||||
pub union BitSet {
|
|
||||||
inline: usize,
|
|
||||||
alloced: Unique<AllocedBitSet>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for BitSet {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
f.debug_list().entries(self.iter()).finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for BitSet {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
if self.is_inline() {
|
|
||||||
Self { inline: unsafe { self.inline } }
|
|
||||||
} else {
|
|
||||||
let (data, _) = self.data_and_len();
|
|
||||||
let (layout, _) = Self::layout(data.len());
|
|
||||||
unsafe {
|
|
||||||
let ptr = alloc::alloc(layout);
|
|
||||||
ptr.copy_from_nonoverlapping(self.alloced.as_ptr() as _, layout.size());
|
|
||||||
Self { alloced: Unique::new_unchecked(ptr as _) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for BitSet {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if !self.is_inline() {
|
|
||||||
unsafe {
|
|
||||||
let cap = self.alloced.as_ref().cap;
|
|
||||||
alloc::dealloc(self.alloced.as_ptr() as _, Self::layout(cap).0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for BitSet {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { inline: Self::FLAG }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BitSet {
|
|
||||||
const FLAG: usize = 1 << (Self::UNIT - 1);
|
|
||||||
const INLINE_ELEMS: usize = Self::UNIT - 1;
|
|
||||||
const UNIT: usize = core::mem::size_of::<usize>() * 8;
|
|
||||||
|
|
||||||
pub fn with_capacity(len: usize) -> Self {
|
|
||||||
let mut s = Self::default();
|
|
||||||
s.reserve(len);
|
|
||||||
s
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_inline(&self) -> bool {
|
|
||||||
unsafe { self.inline & Self::FLAG != 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_and_len(&self) -> (&[usize], usize) {
|
|
||||||
unsafe {
|
|
||||||
if self.is_inline() {
|
|
||||||
(core::slice::from_ref(&self.inline), Self::INLINE_ELEMS)
|
|
||||||
} else {
|
|
||||||
let small_vec = self.alloced.as_ref();
|
|
||||||
(
|
|
||||||
core::slice::from_raw_parts(
|
|
||||||
&small_vec.data as *const _ as *const usize,
|
|
||||||
small_vec.cap,
|
|
||||||
),
|
|
||||||
small_vec.cap * core::mem::size_of::<usize>() * 8,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_mut_and_len(&mut self) -> (&mut [usize], usize) {
|
|
||||||
unsafe {
|
|
||||||
if self.is_inline() {
|
|
||||||
(core::slice::from_mut(&mut self.inline), INLINE_ELEMS)
|
|
||||||
} else {
|
|
||||||
let small_vec = self.alloced.as_mut();
|
|
||||||
(
|
|
||||||
core::slice::from_raw_parts_mut(
|
|
||||||
&mut small_vec.data as *mut _ as *mut usize,
|
|
||||||
small_vec.cap,
|
|
||||||
),
|
|
||||||
small_vec.cap * Self::UNIT,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn indexes(index: usize) -> (usize, usize) {
|
|
||||||
(index / Self::UNIT, index % Self::UNIT)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, index: Nid) -> bool {
|
|
||||||
let index = index as usize;
|
|
||||||
let (data, len) = self.data_and_len();
|
|
||||||
if index >= len {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let (elem, bit) = Self::indexes(index);
|
|
||||||
(unsafe { *data.get_unchecked(elem) }) & (1 << bit) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(&mut self, index: Nid) -> bool {
|
|
||||||
let index = index as usize;
|
|
||||||
let (mut data, len) = self.data_mut_and_len();
|
|
||||||
if core::intrinsics::unlikely(index >= len) {
|
|
||||||
self.grow((index + 1).next_power_of_two().max(4 * Self::UNIT));
|
|
||||||
(data, _) = self.data_mut_and_len();
|
|
||||||
}
|
|
||||||
|
|
||||||
let (elem, bit) = Self::indexes(index);
|
|
||||||
debug_assert!(elem < data.len(), "{} < {}", elem, data.len());
|
|
||||||
let elem = unsafe { data.get_unchecked_mut(elem) };
|
|
||||||
let prev = *elem;
|
|
||||||
*elem |= 1 << bit;
|
|
||||||
*elem != prev
|
|
||||||
}
|
|
||||||
|
|
||||||
fn grow(&mut self, size: usize) {
|
|
||||||
debug_assert!(size.is_power_of_two());
|
|
||||||
let slot_count = size / Self::UNIT;
|
|
||||||
let (layout, off) = Self::layout(slot_count);
|
|
||||||
let (ptr, prev_len) = unsafe {
|
|
||||||
if self.is_inline() {
|
|
||||||
let ptr = alloc::alloc(layout);
|
|
||||||
*ptr.add(off).cast::<usize>() = self.inline & !Self::FLAG;
|
|
||||||
(ptr, 1)
|
|
||||||
} else {
|
|
||||||
let prev_len = self.alloced.as_ref().cap;
|
|
||||||
let (prev_layout, _) = Self::layout(prev_len);
|
|
||||||
(alloc::realloc(self.alloced.as_ptr() as _, prev_layout, layout.size()), prev_len)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
unsafe {
|
|
||||||
MaybeUninit::fill(
|
|
||||||
core::slice::from_raw_parts_mut(
|
|
||||||
ptr.add(off).cast::<MaybeUninit<usize>>().add(prev_len),
|
|
||||||
slot_count - prev_len,
|
|
||||||
),
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
*ptr.cast::<usize>() = slot_count;
|
|
||||||
core::ptr::write(self, Self { alloced: Unique::new_unchecked(ptr as _) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout(slot_count: usize) -> (core::alloc::Layout, usize) {
|
|
||||||
unsafe {
|
|
||||||
core::alloc::Layout::new::<AllocedBitSet>()
|
|
||||||
.extend(Layout::array::<usize>(slot_count).unwrap_unchecked())
|
|
||||||
.unwrap_unchecked()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> BitSetIter {
|
|
||||||
if self.is_inline() {
|
|
||||||
BitSetIter { index: 0, current: unsafe { self.inline & !Self::FLAG }, remining: &[] }
|
|
||||||
} else {
|
|
||||||
let &[current, ref remining @ ..] = self.data_and_len().0 else {
|
|
||||||
unsafe { unreachable_unchecked() }
|
|
||||||
};
|
|
||||||
BitSetIter { index: 0, current, remining }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self, len: usize) {
|
|
||||||
self.reserve(len);
|
|
||||||
if self.is_inline() {
|
|
||||||
unsafe { self.inline &= Self::FLAG };
|
|
||||||
} else {
|
|
||||||
self.data_mut_and_len().0.fill(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn units<'a>(&'a self, slot: &'a mut usize) -> &'a [usize] {
|
|
||||||
if self.is_inline() {
|
|
||||||
*slot = unsafe { self.inline } & !Self::FLAG;
|
|
||||||
core::slice::from_ref(slot)
|
|
||||||
} else {
|
|
||||||
self.data_and_len().0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reserve(&mut self, len: usize) {
|
|
||||||
if len > self.data_and_len().1 {
|
|
||||||
self.grow(len.next_power_of_two().max(4 * Self::UNIT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn units_mut(&mut self) -> Result<&mut [usize], &mut InlineBitSetView> {
|
|
||||||
if self.is_inline() {
|
|
||||||
Err(unsafe {
|
|
||||||
core::mem::transmute::<&mut usize, &mut InlineBitSetView>(&mut self.inline)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Ok(self.data_mut_and_len().0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct InlineBitSetView(usize);
|
|
||||||
|
|
||||||
impl InlineBitSetView {
|
|
||||||
pub(crate) fn add_mask(&mut self, tmp: usize) {
|
|
||||||
debug_assert!(tmp & BitSet::FLAG == 0);
|
|
||||||
self.0 |= tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BitSetIter<'a> {
|
|
||||||
index: usize,
|
|
||||||
current: usize,
|
|
||||||
remining: &'a [usize],
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for BitSetIter<'_> {
|
|
||||||
type Item = usize;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
while self.current == 0 {
|
|
||||||
self.current = *self.remining.take_first()?;
|
|
||||||
self.index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
let sub_idx = self.current.trailing_zeros() as usize;
|
|
||||||
self.current &= self.current - 1;
|
|
||||||
Some(self.index * BitSet::UNIT + sub_idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AllocedBitSet {
|
|
||||||
cap: usize,
|
|
||||||
data: [usize; 0],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[test]
|
|
||||||
fn test_small_bit_set() {
|
|
||||||
use std::vec::Vec;
|
|
||||||
|
|
||||||
let mut sv = BitSet::default();
|
|
||||||
|
|
||||||
sv.set(10);
|
|
||||||
debug_assert!(sv.get(10));
|
|
||||||
sv.set(100);
|
|
||||||
debug_assert!(sv.get(100));
|
|
||||||
sv.set(10000);
|
|
||||||
debug_assert!(sv.get(10000));
|
|
||||||
debug_assert_eq!(sv.iter().collect::<Vec<_>>(), &[10, 100, 10000]);
|
|
||||||
sv.clear(10000);
|
|
||||||
debug_assert_eq!(sv.iter().collect::<Vec<_>>(), &[]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub union Vc {
|
|
||||||
inline: InlineVc,
|
|
||||||
alloced: AllocedVc,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Vc {
|
|
||||||
fn default() -> Self {
|
|
||||||
Vc { inline: InlineVc { elems: MaybeUninit::uninit(), cap: Default::default() } }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for Vc {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
self.as_slice().fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromIterator<Nid> for Vc {
|
|
||||||
fn from_iter<T: IntoIterator<Item = Nid>>(iter: T) -> Self {
|
|
||||||
let mut slf = Self::default();
|
|
||||||
for i in iter {
|
|
||||||
slf.push(i);
|
|
||||||
}
|
|
||||||
slf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const INLINE_ELEMS: usize = VC_SIZE / 2 - 1;
|
|
||||||
const VC_SIZE: usize = 16;
|
|
||||||
|
|
||||||
impl Vc {
|
|
||||||
fn is_inline(&self) -> bool {
|
|
||||||
unsafe { self.inline.cap <= INLINE_ELEMS as Nid }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout(&self) -> Option<core::alloc::Layout> {
|
|
||||||
unsafe {
|
|
||||||
self.is_inline().not().then(|| {
|
|
||||||
core::alloc::Layout::array::<Nid>(self.alloced.cap as _).unwrap_unchecked()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
unsafe {
|
|
||||||
if self.is_inline() {
|
|
||||||
self.inline.cap as _
|
|
||||||
} else {
|
|
||||||
self.alloced.len as _
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn len_mut(&mut self) -> &mut Nid {
|
|
||||||
unsafe {
|
|
||||||
if self.is_inline() {
|
|
||||||
&mut self.inline.cap
|
|
||||||
} else {
|
|
||||||
&mut self.alloced.len
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_ptr(&self) -> *const Nid {
|
|
||||||
unsafe {
|
|
||||||
match self.is_inline() {
|
|
||||||
true => self.inline.elems.as_ptr().cast(),
|
|
||||||
false => self.alloced.base.as_ptr(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_mut_ptr(&mut self) -> *mut Nid {
|
|
||||||
unsafe {
|
|
||||||
match self.is_inline() {
|
|
||||||
true => self.inline.elems.as_mut_ptr().cast(),
|
|
||||||
false => self.alloced.base.as_ptr(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_slice(&self) -> &[Nid] {
|
|
||||||
unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_slice_mut(&mut self) -> &mut [Nid] {
|
|
||||||
unsafe { core::slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, value: Nid) {
|
|
||||||
if let Some(layout) = self.layout()
|
|
||||||
&& unsafe { self.alloced.len == self.alloced.cap }
|
|
||||||
{
|
|
||||||
unsafe {
|
|
||||||
self.alloced.cap *= 2;
|
|
||||||
self.alloced.base = Unique::new_unchecked(
|
|
||||||
alloc::realloc(
|
|
||||||
self.alloced.base.as_ptr().cast(),
|
|
||||||
layout,
|
|
||||||
self.alloced.cap as usize * core::mem::size_of::<Nid>(),
|
|
||||||
)
|
|
||||||
.cast(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else if self.len() == INLINE_ELEMS {
|
|
||||||
unsafe {
|
|
||||||
let mut allcd =
|
|
||||||
Self::alloc((self.inline.cap + 1).next_power_of_two() as _, self.len());
|
|
||||||
core::ptr::copy_nonoverlapping(self.as_ptr(), allcd.as_mut_ptr(), self.len());
|
|
||||||
*self = allcd;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
*self.len_mut() += 1;
|
|
||||||
self.as_mut_ptr().add(self.len() - 1).write(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn alloc(cap: usize, len: usize) -> Self {
|
|
||||||
debug_assert!(cap > INLINE_ELEMS);
|
|
||||||
let layout = unsafe { core::alloc::Layout::array::<Nid>(cap).unwrap_unchecked() };
|
|
||||||
let alloc = unsafe { alloc::alloc(layout) };
|
|
||||||
unsafe {
|
|
||||||
Vc {
|
|
||||||
alloced: AllocedVc {
|
|
||||||
base: Unique::new_unchecked(alloc.cast()),
|
|
||||||
len: len as _,
|
|
||||||
cap: cap as _,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn swap_remove(&mut self, index: usize) {
|
|
||||||
let len = self.len() - 1;
|
|
||||||
self.as_slice_mut().swap(index, len);
|
|
||||||
*self.len_mut() -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(&mut self, index: usize) {
|
|
||||||
self.as_slice_mut().copy_within(index + 1.., index);
|
|
||||||
*self.len_mut() -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for Vc {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
if let Some(layout) = self.layout() {
|
|
||||||
unsafe {
|
|
||||||
alloc::dealloc(self.alloced.base.as_ptr().cast(), layout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for Vc {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
self.as_slice().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoIterator for Vc {
|
|
||||||
type IntoIter = VcIntoIter;
|
|
||||||
type Item = Nid;
|
|
||||||
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
VcIntoIter { start: 0, end: self.len(), vc: self }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct VcIntoIter {
|
|
||||||
start: usize,
|
|
||||||
end: usize,
|
|
||||||
vc: Vc,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for VcIntoIter {
|
|
||||||
type Item = Nid;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.start == self.end {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let ret = unsafe { core::ptr::read(self.vc.as_slice().get_unchecked(self.start)) };
|
|
||||||
self.start += 1;
|
|
||||||
Some(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
||||||
let len = self.end - self.start;
|
|
||||||
(len, Some(len))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DoubleEndedIterator for VcIntoIter {
|
|
||||||
fn next_back(&mut self) -> Option<Self::Item> {
|
|
||||||
if self.start == self.end {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.end -= 1;
|
|
||||||
Some(unsafe { core::ptr::read(self.vc.as_slice().get_unchecked(self.end)) })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ExactSizeIterator for VcIntoIter {}
|
|
||||||
|
|
||||||
impl<const SIZE: usize> From<[Nid; SIZE]> for Vc {
|
|
||||||
fn from(value: [Nid; SIZE]) -> Self {
|
|
||||||
value.as_slice().into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a [Nid]> for Vc {
|
|
||||||
fn from(value: &'a [Nid]) -> Self {
|
|
||||||
if value.len() <= INLINE_ELEMS {
|
|
||||||
let mut dflt = Self::default();
|
|
||||||
unsafe {
|
|
||||||
core::ptr::copy_nonoverlapping(value.as_ptr(), dflt.as_mut_ptr(), value.len())
|
|
||||||
};
|
|
||||||
dflt.inline.cap = value.len() as _;
|
|
||||||
dflt
|
|
||||||
} else {
|
|
||||||
let mut allcd = unsafe { Self::alloc(value.len(), value.len()) };
|
|
||||||
unsafe {
|
|
||||||
core::ptr::copy_nonoverlapping(value.as_ptr(), allcd.as_mut_ptr(), value.len())
|
|
||||||
};
|
|
||||||
allcd
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for Vc {
|
|
||||||
type Target = [Nid];
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
self.as_slice()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for Vc {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
self.as_slice_mut()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
struct InlineVc {
|
|
||||||
cap: Nid,
|
|
||||||
elems: MaybeUninit<[Nid; INLINE_ELEMS]>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
struct AllocedVc {
|
|
||||||
cap: Nid,
|
|
||||||
len: Nid,
|
|
||||||
base: Unique<Nid>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait Ent: Copy {
|
|
||||||
fn new(index: usize) -> Self;
|
|
||||||
fn index(self) -> usize;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EntVec<K: Ent, T> {
|
|
||||||
data: ::alloc::vec::Vec<T>,
|
|
||||||
k: PhantomData<fn(K)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ent, T> Default for EntVec<K, T> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { data: Default::default(), k: PhantomData }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ent, T> EntVec<K, T> {
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.data.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.data.is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.data.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, value: T) -> K {
|
|
||||||
let k = K::new(self.data.len());
|
|
||||||
self.data.push(value);
|
|
||||||
k
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn next(&self, index: K) -> Option<&T> {
|
|
||||||
self.data.get(index.index() + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn shadow(&mut self, len: usize)
|
|
||||||
where
|
|
||||||
T: Default,
|
|
||||||
{
|
|
||||||
if self.data.len() < len {
|
|
||||||
self.data.resize_with(len, Default::default);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter(&self) -> core::slice::Iter<T> {
|
|
||||||
self.data.iter()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ent, T> core::ops::Index<K> for EntVec<K, T> {
|
|
||||||
type Output = T;
|
|
||||||
|
|
||||||
fn index(&self, index: K) -> &Self::Output {
|
|
||||||
&self.data[index.index()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: Ent, T> core::ops::IndexMut<K> for EntVec<K, T> {
|
|
||||||
fn index_mut(&mut self, index: K) -> &mut Self::Output {
|
|
||||||
&mut self.data[index.index()]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! decl_ent {
|
|
||||||
($(
|
|
||||||
$vis:vis struct $name:ident($index:ty);
|
|
||||||
)*) => {$(
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
|
||||||
$vis struct $name($index);
|
|
||||||
|
|
||||||
impl crate::utils::Ent for $name {
|
|
||||||
fn new(index: usize) -> Self {
|
|
||||||
Self(index as $index)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn index(self) -> usize {
|
|
||||||
self.0 as _
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
impl core::fmt::Display for $name {
|
|
||||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
||||||
write!(f, concat!(stringify!($name), "{}"), self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)*};
|
|
||||||
}
|
|
||||||
pub(crate) use decl_ent;
|
|
|
@ -1,48 +0,0 @@
|
||||||
main:
|
|
||||||
ADDI64 r254, r254, -24d
|
|
||||||
ST r31, r254, 0a, 24h
|
|
||||||
LI32 r32, 1148846080w
|
|
||||||
CP r2, r32
|
|
||||||
JAL r31, r0, :sin
|
|
||||||
CP r33, r1
|
|
||||||
FMUL32 r32, r33, r32
|
|
||||||
FTI32 r32, r32, 1b
|
|
||||||
CP r1, r32
|
|
||||||
LD r31, r254, 0a, 24h
|
|
||||||
ADDI64 r254, r254, 24d
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
sin:
|
|
||||||
CP r13, r2
|
|
||||||
LI32 r14, 1124073472w
|
|
||||||
LI32 r15, 1078530011w
|
|
||||||
FMUL32 r14, r13, r14
|
|
||||||
FDIV32 r14, r14, r15
|
|
||||||
FTI32 r14, r14, 1b
|
|
||||||
ANDI r15, r14, 255d
|
|
||||||
ITF64 r16, r14
|
|
||||||
MULI64 r15, r15, 4d
|
|
||||||
LRA r17, r0, :sin_table
|
|
||||||
LI32 r18, 1086918619w
|
|
||||||
FC64T32 r16, r16, 1b
|
|
||||||
ADDI64 r14, r14, 64d
|
|
||||||
ADD64 r15, r17, r15
|
|
||||||
LI32 r19, 1132462080w
|
|
||||||
FMUL32 r16, r16, r18
|
|
||||||
ANDI r14, r14, 255d
|
|
||||||
LI32 r18, 1056964608w
|
|
||||||
LD r15, r15, 0a, 4h
|
|
||||||
FDIV32 r16, r16, r19
|
|
||||||
MULI64 r14, r14, 4d
|
|
||||||
FMUL32 r18, r15, r18
|
|
||||||
FSUB32 r13, r13, r16
|
|
||||||
ADD64 r14, r17, r14
|
|
||||||
FMUL32 r16, r13, r18
|
|
||||||
LD r14, r14, 0a, 4h
|
|
||||||
FSUB32 r14, r14, r16
|
|
||||||
FMUL32 r13, r14, r13
|
|
||||||
FADD32 r13, r15, r13
|
|
||||||
CP r1, r13
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 1315
|
|
||||||
ret: 826
|
|
||||||
status: Ok(())
|
|
|
@ -1,6 +0,0 @@
|
||||||
main:
|
|
||||||
CP r1, r0
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 22
|
|
||||||
ret: 0
|
|
||||||
status: Ok(())
|
|
|
@ -1,6 +0,0 @@
|
||||||
main:
|
|
||||||
CP r1, r0
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 22
|
|
||||||
ret: 0
|
|
||||||
status: Ok(())
|
|
|
@ -1,32 +0,0 @@
|
||||||
main:
|
|
||||||
ADDI64 r254, r254, -56d
|
|
||||||
ST r31, r254, 24a, 32h
|
|
||||||
LI64 r32, 1d
|
|
||||||
ADDI64 r33, r254, 0d
|
|
||||||
ST r32, r254, 0a, 8h
|
|
||||||
LI64 r34, 2d
|
|
||||||
ST r34, r254, 8a, 8h
|
|
||||||
LI64 r34, 4d
|
|
||||||
ST r34, r254, 16a, 8h
|
|
||||||
CP r2, r33
|
|
||||||
JAL r31, r0, :pass
|
|
||||||
CP r33, r1
|
|
||||||
ADD64 r32, r33, r32
|
|
||||||
CP r1, r32
|
|
||||||
LD r31, r254, 24a, 32h
|
|
||||||
ADDI64 r254, r254, 56d
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
pass:
|
|
||||||
CP r13, r2
|
|
||||||
LD r14, r13, 8a, 8h
|
|
||||||
MULI64 r15, r14, 8d
|
|
||||||
LD r16, r13, 0a, 8h
|
|
||||||
ADD64 r13, r15, r13
|
|
||||||
ADD64 r14, r14, r16
|
|
||||||
LD r13, r13, 0a, 8h
|
|
||||||
ADD64 r13, r13, r14
|
|
||||||
CP r1, r13
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 246
|
|
||||||
ret: 8
|
|
||||||
status: Ok(())
|
|
|
@ -1,8 +0,0 @@
|
||||||
main:
|
|
||||||
LRA r13, r0, :sin_table
|
|
||||||
LD r13, r13, 80a, 8h
|
|
||||||
CP r1, r13
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 770
|
|
||||||
ret: 1736
|
|
||||||
status: Ok(())
|
|
|
@ -1,14 +0,0 @@
|
||||||
main:
|
|
||||||
CP r14, r2
|
|
||||||
LI64 r13, 1d
|
|
||||||
JNE r14, r13, :0
|
|
||||||
JMP :1
|
|
||||||
0: JNE r14, r0, :2
|
|
||||||
LI64 r13, 2d
|
|
||||||
JMP :1
|
|
||||||
2: LI64 r13, 3d
|
|
||||||
1: CP r1, r13
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 75
|
|
||||||
ret: 2
|
|
||||||
status: Ok(())
|
|
|
@ -1,32 +0,0 @@
|
||||||
main:
|
|
||||||
ADDI64 r254, r254, -24d
|
|
||||||
ST r31, r254, 0a, 24h
|
|
||||||
LRA r32, r0, :"abඞ\n\r\t56789\0"
|
|
||||||
CP r2, r32
|
|
||||||
JAL r31, r0, :str_len
|
|
||||||
CP r32, r1
|
|
||||||
LRA r33, r0, :"fff\0"
|
|
||||||
CP r2, r33
|
|
||||||
JAL r31, r0, :str_len
|
|
||||||
CP r33, r1
|
|
||||||
ADD64 r32, r33, r32
|
|
||||||
CP r1, r32
|
|
||||||
LD r31, r254, 0a, 24h
|
|
||||||
ADDI64 r254, r254, 24d
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
str_len:
|
|
||||||
CP r15, r2
|
|
||||||
CP r14, r0
|
|
||||||
CP r13, r14
|
|
||||||
2: LD r16, r15, 0a, 1h
|
|
||||||
ANDI r16, r16, 255d
|
|
||||||
JNE r16, r14, :0
|
|
||||||
CP r1, r13
|
|
||||||
JMP :1
|
|
||||||
0: ADDI64 r15, r15, 1d
|
|
||||||
ADDI64 r13, r13, 1d
|
|
||||||
JMP :2
|
|
||||||
1: JALA r0, r31, 0a
|
|
||||||
code size: 216
|
|
||||||
ret: 16
|
|
||||||
status: Ok(())
|
|
|
@ -1,13 +0,0 @@
|
||||||
foo:
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
main:
|
|
||||||
ADDI64 r254, r254, -8d
|
|
||||||
ST r31, r254, 0a, 8h
|
|
||||||
JAL r31, r0, :foo
|
|
||||||
CP r1, r0
|
|
||||||
LD r31, r254, 0a, 8h
|
|
||||||
ADDI64 r254, r254, 8d
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 88
|
|
||||||
ret: 0
|
|
||||||
status: Ok(())
|
|
|
@ -1,8 +0,0 @@
|
||||||
main:
|
|
||||||
LRA r13, r0, :a
|
|
||||||
LD r13, r13, 0a, 8h
|
|
||||||
CP r1, r13
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 50
|
|
||||||
ret: 50
|
|
||||||
status: Ok(())
|
|
|
@ -1,8 +0,0 @@
|
||||||
main:
|
|
||||||
LRA r13, r0, :a
|
|
||||||
LD r13, r13, 0a, 8h
|
|
||||||
CP r1, r13
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 50
|
|
||||||
ret: 50
|
|
||||||
status: Ok(())
|
|
|
@ -1,19 +0,0 @@
|
||||||
cond:
|
|
||||||
CP r1, r0
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
main:
|
|
||||||
ADDI64 r254, r254, -24d
|
|
||||||
ST r31, r254, 0a, 24h
|
|
||||||
JAL r31, r0, :cond
|
|
||||||
CP r33, r1
|
|
||||||
CP r32, r0
|
|
||||||
JNE r33, r32, :0
|
|
||||||
JMP :1
|
|
||||||
0: LI64 r32, 2d
|
|
||||||
1: CP r1, r32
|
|
||||||
LD r31, r254, 0a, 24h
|
|
||||||
ADDI64 r254, r254, 24d
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 117
|
|
||||||
ret: 0
|
|
||||||
status: Ok(())
|
|
|
@ -1,6 +0,0 @@
|
||||||
main:
|
|
||||||
CP r1, r0
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 22
|
|
||||||
ret: 0
|
|
||||||
status: Ok(())
|
|
|
@ -1,7 +0,0 @@
|
||||||
main:
|
|
||||||
LI32 r13, 69w
|
|
||||||
CP r1, r13
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 28
|
|
||||||
ret: 69
|
|
||||||
status: Ok(())
|
|
|
@ -1,6 +0,0 @@
|
||||||
main:
|
|
||||||
CP r1, r0
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 22
|
|
||||||
ret: 0
|
|
||||||
status: Ok(())
|
|
|
@ -1,5 +0,0 @@
|
||||||
main:
|
|
||||||
UN
|
|
||||||
code size: 9
|
|
||||||
ret: 0
|
|
||||||
status: Err(Unreachable)
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -1,7 +0,0 @@
|
||||||
main:
|
|
||||||
LI32 r13, 3212836864w
|
|
||||||
CP r1, r13
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 28
|
|
||||||
ret: 3212836864
|
|
||||||
status: Ok(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -1,6 +0,0 @@
|
||||||
main:
|
|
||||||
CP r1, r0
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 22
|
|
||||||
ret: 0
|
|
||||||
status: Ok(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -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(())
|
|
|
@ -1,6 +0,0 @@
|
||||||
main:
|
|
||||||
CP r1, r0
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 22
|
|
||||||
ret: 0
|
|
||||||
status: Ok(())
|
|
|
@ -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(())
|
|
|
@ -1,7 +0,0 @@
|
||||||
main:
|
|
||||||
LI64 r13, 10d
|
|
||||||
CP r1, r13
|
|
||||||
JALA r0, r31, 0a
|
|
||||||
code size: 32
|
|
||||||
ret: 10
|
|
||||||
status: Ok(())
|
|
|
@ -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
Loading…
Reference in a new issue