fixing feature incompatibility

This commit is contained in:
Jakub Doka 2024-10-12 13:07:49 +02:00
parent 07638caff0
commit c4826d3bfd
No known key found for this signature in database
GPG key ID: C6E9A89936B8C143
22 changed files with 556 additions and 367 deletions

View file

@ -1,2 +1,4 @@
[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 -p"

1
.gitignore vendored
View file

@ -3,3 +3,4 @@
/.rgignore
rustc-ice-*
db.sqlite
/depell/src/*.wasm

11
Cargo.lock generated
View file

@ -810,6 +810,9 @@ name = "wasm-hbc"
version = "0.1.0"
dependencies = [
"hblang",
"hbvm",
"log",
"wasm-rt",
]
[[package]]
@ -817,6 +820,14 @@ name = "wasm-hbfmt"
version = "0.1.0"
dependencies = [
"hblang",
"wasm-rt",
]
[[package]]
name = "wasm-rt"
version = "0.1.0"
dependencies = [
"log",
]
[[package]]

View file

@ -1,3 +1,5 @@
cargo-features = ["profile-rustflags"]
[workspace]
resolver = "2"
members = [
@ -9,11 +11,12 @@ members = [
"depell",
"depell/wasm-fmt",
"depell/wasm-hbc",
"depell/wasm-rt",
]
[workspace.dependencies]
hbbytecode = { path = "bytecode", default-features = false }
hbvm = { path = "vm" }
hbvm = { path = "vm", default-features = false }
hbxrt = { path = "xrt" }
hblang = { path = "lang", default-features = false }
hbjit = { path = "jit" }
@ -26,9 +29,13 @@ codegen-units = 1
panic = "abort"
[profile.small]
#rustflags = ["-Zfmt-debug=none", "-Zlocation-detail=none"]
inherits = "release"
opt-level = "z"
strip = true
strip = "debuginfo"
#strip = true
lto = true
codegen-units = 1
panic = "abort"

View file

@ -54,9 +54,6 @@ form {
flex-direction: column;
gap: var(--small-gap);
::placeholder {
color: var(--placeholder);
}
.error {
color: var(--error);

View file

@ -3,19 +3,82 @@
/** @return {never} */
function never() { throw new Error() }
/**@type{WebAssembly.Instance}*/ let instance;
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let instaceFuture;
/**@type{WebAssembly.Instance}*/ let hbcInstance;
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let hbcInstaceFuture;
/** @param {Uint8Array} code @param {number} fuel
* @returns {Promise<string | undefined> | string | undefined} */
function compileCode(code, fuel) {
if (!hbcInstance) {
hbcInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbc.wasm"), {});
return (async () => {
hbcInstance = (await hbcInstaceFuture).instance;
return compileCodeSync(hbcInstance, code, fuel);
})();
} else {
return compileCodeSync(hbcInstance, code, fuel);
}
}
/** @param {WebAssembly.Instance} instance @param {Uint8Array} code @param {number} fuel @returns {string | undefined} */
function compileCodeSync(instance, code, fuel) {
let {
INPUT, INPUT_LEN,
LOG_MESSAGES, LOG_MESSAGES_LEN,
PANIC_MESSAGE, PANIC_MESSAGE_LEN,
memory, compile_and_run
} = instance.exports;
if (!(true
&& INPUT instanceof WebAssembly.Global
&& INPUT_LEN instanceof WebAssembly.Global
&& LOG_MESSAGES instanceof WebAssembly.Global
&& LOG_MESSAGES_LEN instanceof WebAssembly.Global
&& memory instanceof WebAssembly.Memory
&& typeof compile_and_run === "function"
)) console.log(instance.exports), never();
const dw = new DataView(memory.buffer);
dw.setUint32(INPUT_LEN.value, code.length, true);
new Uint8Array(memory.buffer, INPUT.value).set(code);
try {
compile_and_run(fuel);
return bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN);
} catch (e) {
if (PANIC_MESSAGE instanceof WebAssembly.Global
&& PANIC_MESSAGE_LEN instanceof WebAssembly.Global) {
console.error(bufToString(memory, PANIC_MESSAGE, PANIC_MESSAGE_LEN));
}
let log = bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN);
console.error(log, e);
return undefined;
}
}
/** @param {WebAssembly.Memory} mem
* @param {WebAssembly.Global} ptr
* @param {WebAssembly.Global} len
* @return {string} */
function bufToString(mem, ptr, len) {
return new TextDecoder()
.decode(new Uint8Array(mem.buffer, ptr.value,
new DataView(mem.buffer).getUint32(len.value, true)));
}
/**@type{WebAssembly.Instance}*/ let fmtInstance;
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let fmtInstaceFuture;
/** @param {string} code @param {"fmt" | "minify"} action
* @returns {Promise<string | undefined> | string | undefined} */
function modifyCode(code, action) {
if (!instance) {
instaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {});
if (!fmtInstance) {
fmtInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {});
return (async () => {
instance = (await instaceFuture).instance;
return modifyCodeSync(instance, code, action);
fmtInstance = (await fmtInstaceFuture).instance;
return modifyCodeSync(fmtInstance, code, action);
})();
} else {
return modifyCodeSync(instance, code, action);
return modifyCodeSync(fmtInstance, code, action);
}
}
@ -100,11 +163,15 @@ function bindTextareaAutoResize(target) {
for (const textarea of target.querySelectorAll("textarea")) {
if (!(textarea instanceof HTMLTextAreaElement)) never();
textarea.style.height = textarea.scrollHeight + "px";
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() {
textarea.style.height = "auto";
textarea.style.height = textarea.scrollHeight + "px";
textarea.style.height = (textarea.scrollHeight - padding) + "px";
});
textarea.onkeydown = (ev) => {
@ -112,10 +179,7 @@ function bindTextareaAutoResize(target) {
if (ev.key === "Tab") {
ev.preventDefault();
const prevPos = textarea.selectionStart;
textarea.value = textarea.value.slice(0, textarea.selectionStart) +
' ' + textarea.value.slice(textarea.selectionEnd);
textarea.selectionStart = textarea.selectionEnd = prevPos + 4;
document.execCommand('insertText', false, " ");
}
if (ev.key === "Backspace" && textarea.selectionStart != 0 && !selecting) {
@ -125,10 +189,8 @@ function bindTextareaAutoResize(target) {
ev.preventDefault();
let toDelete = (textarea.selectionStart - (i + 1)) % 4;
if (toDelete === 0) toDelete = 4;
const prevPos = textarea.selectionStart;
textarea.value = textarea.value.slice(0, textarea.selectionStart - toDelete) +
textarea.value.slice(textarea.selectionEnd);
textarea.selectionStart = textarea.selectionEnd = prevPos - toDelete;
textarea.selectionStart -= toDelete;
document.execCommand('delete', false);
}
}
}
@ -165,11 +227,27 @@ if (window.location.hostname === 'localhost') {
if (id !== new_id) window.location.reload();
}, 300);
(async function testCodeChange() {
const code = "main:=fn():void{return}";
const fmtd = await modifyCode(code, "fmt") ?? never();
const prev = await modifyCode(fmtd, "minify") ?? never();
if (code != prev) console.error(code, prev);
(async function test() {
{
const code = "main:=fn():void{return}";
const fmtd = await modifyCode(code, "fmt") ?? never();
const prev = await modifyCode(fmtd, "minify") ?? never();
if (code != prev) console.error(code, prev);
}
{
const name = "foo.hb";
const code = "main:=fn():void{return 42}";
const buf = new Uint8Array(2 + name.length + 2 + code.length);
const view = new DataView(buf.buffer);
view.setUint16(0, name.length, true);
buf.set(new TextEncoder().encode(name), 2);
view.setUint16(2 + name.length, code.length, true);
buf.set(new TextEncoder().encode(code), 2 + name.length + 2);
const res = await compileCode(buf, 1) ?? never();
const expected = "";
if (expected != res) console.error(expected, res);
}
})()
}

View file

@ -40,6 +40,8 @@ async fn amain() {
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!(
@ -47,6 +49,13 @@ async fn amain() {
"../../target/wasm32-unknown-unknown/small/wasm_hbfmt.wasm"
),
)
.route(
"/hbc.wasm",
static_asset!(
"application/wasm",
"../../target/wasm32-unknown-unknown/small/wasm_hbc.wasm"
),
)
.route("/index-view", get(Index::get))
.route("/feed", get(Index::page))
.route("/profile", get(Profile::page))
@ -377,7 +386,7 @@ async fn base(body: impl FnOnce(&mut String), session: Option<&Session>) -> Html
"<!DOCTYPE html>"
<html lang="en">
<head>
<style>!{include_str!("index.css")}</style>
<link rel="stylesheet" href="/index.css">
</head>
<body>
<nav>
@ -401,7 +410,7 @@ async fn base(body: impl FnOnce(&mut String), session: Option<&Session>) -> Html
<main>|f|{body(f)}</main>
</body>
<script src="https://unpkg.com/htmx.org@2.0.3" integrity="sha384-0895/pl2MU10Hqc6jd4RvrthNlDiE9U1tWmX7WRESftEDRosgxNsQG/Ze9YMRzHq" crossorigin="anonymous"></script>
<script>!{include_str!("index.js")}</script>
<script src="/index.js"></script>
</html>
})
}

View file

@ -8,3 +8,4 @@ crate-type = ["cdylib"]
[dependencies]
hblang = { workspace = true, features = ["no_log"] }
wasm-rt = { version = "0.1.0", path = "../wasm-rt" }

View file

@ -1,115 +1,16 @@
#![no_std]
#![feature(slice_take)]
#![feature(str_from_raw_parts)]
#![feature(alloc_error_handler)]
use {
core::{
alloc::{GlobalAlloc, Layout},
cell::UnsafeCell,
},
hblang::parser::ParserCtx,
};
use hblang::{fmt, parser};
wasm_rt::decl_runtime!(128 * 1024, 1024 * 4);
const ARENA_SIZE: usize = 128 * 1024;
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;
#[cfg(target_arch = "wasm32")]
#[panic_handler]
pub fn handle_panic(_info: &core::panic::PanicInfo) -> ! {
//unsafe {
// use core::fmt::Write;
// let mut f = Write(&mut PANIC_MESSAGE[..]);
// _ = writeln!(f, "{}", info);
// PANIC_MESSAGE_LEN = 1024 - f.0.len();
//}
core::arch::wasm32::unreachable();
}
//#[no_mangle]
//static mut PANIC_MESSAGE: [u8; 1024] = [0; 1024];
//#[no_mangle]
//static mut PANIC_MESSAGE_LEN: usize = 0;
#[global_allocator]
static ALLOCATOR: ArenaAllocator = ArenaAllocator::new();
#[cfg(target_arch = "wasm32")]
#[alloc_error_handler]
fn alloc_error(_: core::alloc::Layout) -> ! {
core::arch::wasm32::unreachable()
}
#[repr(C, align(32))]
struct ArenaAllocator {
arena: UnsafeCell<[u8; ARENA_SIZE]>,
head: UnsafeCell<*mut u8>,
}
impl ArenaAllocator {
const fn new() -> Self {
ArenaAllocator {
arena: UnsafeCell::new([0; ARENA_SIZE]),
head: UnsafeCell::new(core::ptr::null_mut()),
}
}
unsafe fn reset(&self) {
(*self.head.get()) = self.arena.get().cast::<u8>().add(ARENA_SIZE);
}
}
unsafe impl Sync for ArenaAllocator {}
unsafe impl GlobalAlloc for ArenaAllocator {
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 & !(1 << (align - 1))) as *mut u8;
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 */
}
}
struct Write<'a>(&'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)
}
}
}
#[no_mangle]
static mut OUTPUT: [u8; MAX_OUTPUT_SIZE] = [0; MAX_OUTPUT_SIZE];
#[no_mangle]
static mut OUTPUT_LEN: usize = 0;
#[no_mangle]
static MAX_INPUT: usize = MAX_INPUT_SIZE;
#[no_mangle]
static mut INPUT: [u8; MAX_INPUT_SIZE] = [0; MAX_INPUT_SIZE];
#[no_mangle]
static mut INPUT_LEN: usize = 0;
wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
#[no_mangle]
unsafe extern "C" fn fmt() {
@ -117,19 +18,17 @@ unsafe extern "C" fn fmt() {
let code = core::str::from_raw_parts(core::ptr::addr_of!(INPUT).cast(), INPUT_LEN);
let arena =
hblang::parser::Arena::with_capacity(code.len() * hblang::parser::SOURCE_TO_AST_FACTOR);
let mut ctx = ParserCtx::default();
let exprs =
hblang::parser::Parser::parse(&mut ctx, code, "source.hb", &mut |_, _| Ok(0), &arena);
let arena = parser::Arena::with_capacity(code.len() * parser::SOURCE_TO_AST_FACTOR);
let mut ctx = parser::ParserCtx::default();
let exprs = parser::Parser::parse(&mut ctx, code, "source.hb", &mut |_, _| Ok(0), &arena);
let mut f = Write(&mut OUTPUT[..]);
hblang::fmt::fmt_file(exprs, code, &mut f).unwrap();
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 minify() {
let code = core::str::from_raw_parts_mut(core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
OUTPUT_LEN = hblang::fmt::minify(code);
OUTPUT_LEN = fmt::minify(code);
}

View file

@ -7,4 +7,8 @@ edition = "2021"
crate-type = ["cdylib"]
[dependencies]
hblang.workspace = true
hblang = { workspace = true, features = [] }
hbvm.workspace = true
log = "0.4.22"
wasm-rt = { version = "0.1.0", path = "../wasm-rt", features = ["log"] }

View file

@ -1,103 +1,26 @@
#![feature(alloc_error_handler)]
#![feature(slice_take)]
#![no_std]
use {
alloc::{borrow::ToOwned, string::String, vec::Vec},
core::{
alloc::{GlobalAlloc, Layout},
cell::UnsafeCell,
},
alloc::{string::String, vec::Vec},
hblang::{codegen::Codegen, parser::FileId},
};
extern crate alloc;
const ARENA_SIZE: usize = 4 * 128 * 1024;
const MAX_PANIC_SIZE: usize = 1024 * 4;
wasm_rt::decl_runtime!(128 * 8 * 1024, 1024 * 4);
const MAX_INPUT_SIZE: usize = 32 * 4 * 1024;
#[cfg(target_arch = "wasm32")]
#[panic_handler]
pub fn handle_panic(_info: &core::panic::PanicInfo) -> ! {
unsafe {
use core::fmt::Write;
let mut f = Write(&mut PANIC_MESSAGE[..]);
_ = writeln!(f, "{}", info);
PANIC_MESSAGE_LEN = 1024 - f.0.len();
}
core::arch::wasm32::unreachable();
}
wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
#[no_mangle]
static mut PANIC_MESSAGE: [u8; MAX_PANIC_SIZE] = [0; MAX_PANIC_SIZE];
#[no_mangle]
static mut PANIC_MESSAGE_LEN: usize = 0;
#[global_allocator]
static ALLOCATOR: ArenaAllocator = ArenaAllocator::new();
#[cfg(target_arch = "wasm32")]
#[alloc_error_handler]
fn alloc_error(_: core::alloc::Layout) -> ! {
core::arch::wasm32::unreachable()
}
#[repr(C, align(32))]
struct ArenaAllocator {
arena: UnsafeCell<[u8; ARENA_SIZE]>,
head: UnsafeCell<*mut u8>,
}
impl ArenaAllocator {
const fn new() -> Self {
ArenaAllocator {
arena: UnsafeCell::new([0; ARENA_SIZE]),
head: UnsafeCell::new(core::ptr::null_mut()),
}
}
unsafe fn reset(&self) {
(*self.head.get()) = self.arena.get().cast::<u8>().add(ARENA_SIZE);
}
}
unsafe impl Sync for ArenaAllocator {}
unsafe impl GlobalAlloc for ArenaAllocator {
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 & !(1 << (align - 1))) as *mut u8;
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 */
}
}
#[no_mangle]
static MAX_INPUT: usize = MAX_INPUT_SIZE;
#[no_mangle]
static mut INPUT_LEN: usize = 0;
#[no_mangle]
static mut INPUT: [u8; MAX_INPUT_SIZE] = [0; MAX_INPUT_SIZE];
#[no_mangle]
unsafe fn compile_and_run() {
unsafe fn compile_and_run(mut fuel: usize) {
ALLOCATOR.reset();
log::set_logger(&wasm_rt::Logger).unwrap();
log::set_max_level(log::LevelFilter::Error);
struct File<'a> {
path: &'a str,
code: &'a mut str,
@ -118,7 +41,8 @@ unsafe fn compile_and_run() {
});
input_bytes = rest;
}
files.sort_unstable_by_key(|f| f.path);
hblang::quad_sort(&mut files, |a, b| a.path.cmp(b.path));
files
};
@ -140,12 +64,29 @@ unsafe fn compile_and_run() {
.collect::<Vec<_>>()
};
let code = {
let mut ct = {
let mut c = Codegen::default();
c.files = files;
c.generate();
let mut buf = Vec::with_capacity(1024 * 8);
c.assemble(&mut buf);
buf
c.assemble_comptime()
};
while fuel != 0 {
match ct.vm.run() {
Ok(hbvm::VmRunOk::End) => {
log::error!("exit code: {}", ct.vm.read_reg(1).0);
break;
}
Ok(hbvm::VmRunOk::Ecall) => {
let unknown = ct.vm.read_reg(2).0;
log::error!("unknown ecall: {unknown}")
}
Ok(hbvm::VmRunOk::Timer) => fuel -= 1,
Ok(hbvm::VmRunOk::Breakpoint) => todo!(),
Err(e) => {
log::error!("vm error: {e}");
break;
}
}
}
}

View file

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

144
depell/wasm-rt/src/lib.rs Normal file
View file

@ -0,0 +1,144 @@
#![feature(alloc_error_handler)]
#![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(target_arch = "wasm32")]
#[panic_handler]
pub fn handle_panic(_info: &core::panic::PanicInfo) -> ! {
#[cfg(debug_assertions)]
{
#[no_mangle]
static mut PANIC_MESSAGE: [u8; $max_panic_size] = [0; $max_panic_size];
#[no_mangle]
static mut PANIC_MESSAGE_LEN: usize = 0;
unsafe {
use core::fmt::Write;
let mut f = Write(&mut PANIC_MESSAGE[..]);
_ = writeln!(f, "{}", _info);
PANIC_MESSAGE_LEN = 1024 - 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) -> ! {
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 * 4;
#[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> {
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
ArenaAllocator {
arena: UnsafeCell::new([0; SIZE]),
head: UnsafeCell::new(core::ptr::null_mut()),
}
}
#[allow(clippy::missing_safety_doc)]
pub unsafe fn reset(&self) {
(*self.head.get()) = self.arena.get().cast::<u8>().add(SIZE);
}
}
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 & !(1 << (align - 1))) as *mut u8;
if until > aligned_head {
return core::ptr::null_mut();
}
*self.head.get() = aligned_head;
aligned_head
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
/* lol */
}
}
pub struct Write<'a>(pub &'a mut [u8]);
impl core::fmt::Write for Write<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if let Some(m) = self.0.take_mut(..s.len()) {
m.copy_from_slice(s.as_bytes());
Ok(())
} else {
Err(core::fmt::Error)
}
}
}

View file

@ -11,7 +11,7 @@ path = "src/main.rs"
hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry"] }
hbbytecode = { workspace = true, features = ["disasm"] }
hbvm = { workspace = true, features = ["nightly"] }
log = { version = "0.4.22", features = ["release_max_level_error"] }
log = "0.4.22"
[dependencies.regalloc2]
git = "https://github.com/jakubDoka/regalloc2"

View file

@ -169,16 +169,16 @@ main := fn(): int {
}
finst := Ty2.{ty: .{a: 4, b: 1}, c: 3}
//inst := odher_pass(finst)
//if finst.c == 3 {
//return finst.ty.a - finst.ty.b
return pass(&finst.ty)
//}
//return 0
inst := odher_pass(finst)
if inst.c == 3 {
return pass(&inst.ty)
}
return 0
}
pass := fn(t: ^Ty): int {
return t.a - t.b
.{a, b} := *t
return a - b
}
odher_pass := fn(t: Ty2): Ty2 {

View file

@ -8,7 +8,7 @@ use {
self, find_symbol, idfl, CommentOr, CtorField, Expr, ExprRef, FileId, Pos, StructField,
},
ty, Field, Func, Global, LoggedMem, OffsetIter, ParamAlloc, Reloc, Sig, Struct, SymKey,
TypedReloc, Types,
TypedReloc, Types, HEADER_SIZE,
},
alloc::{boxed::Box, string::String, vec::Vec},
core::fmt::Display,
@ -637,11 +637,19 @@ struct Pool {
const VM_STACK_SIZE: usize = 1024 * 64;
struct Comptime {
vm: hbvm::Vm<LoggedMem, { 1024 * 10 }>,
_stack: Box<[u8; VM_STACK_SIZE]>,
pub struct Comptime {
pub vm: hbvm::Vm<LoggedMem, { 1024 * 10 }>,
stack: Box<[u8; VM_STACK_SIZE]>,
code: Vec<u8>,
}
impl Comptime {
fn reset(&mut self) {
let ptr = unsafe { self.stack.as_mut_ptr().cast::<u8>().add(VM_STACK_SIZE) as u64 };
self.vm.registers.fill(hbvm::value::Value(0));
self.vm.write_reg(STACK_PTR, ptr);
self.vm.pc = hbvm::mem::Address::new(self.code.as_ptr() as u64 + HEADER_SIZE as u64);
}
}
impl Default for Comptime {
fn default() -> Self {
@ -649,7 +657,7 @@ impl Default for Comptime {
let mut vm = hbvm::Vm::default();
let ptr = unsafe { stack.as_mut_ptr().cast::<u8>().add(VM_STACK_SIZE) as u64 };
vm.write_reg(STACK_PTR, ptr);
Self { vm, _stack: unsafe { stack.assume_init() }, code: Default::default() }
Self { vm, stack: unsafe { stack.assume_init() }, code: Default::default() }
}
}
@ -1233,9 +1241,12 @@ impl Codegen {
e => unimplemented!("{e:?}"),
}
}
smh => self.report(
_ => self.report(
target.pos(),
format_args!("the field operation is not supported: {smh:?}"),
format_args!(
"the field operation is not supported: {}",
self.ty_display(tal.ty)
),
),
}
}
@ -1456,8 +1467,7 @@ impl Codegen {
self.report(
pos,
format_args!(
"this integer was inferred to be '{}' \
which does not make sense",
"this integer was inferred to be '{}'",
self.ty_display(ty)
),
);
@ -1715,16 +1725,11 @@ impl Codegen {
unimplemented!("{:#?}", op)
}
E::Comment { .. } => Some(Value::void()),
ref ast => self.report_unhandled_ast(ast, "expression"),
ref ast => self.report_unhandled_ast(ast, "something"),
}?;
if let Some(ty) = ctx.ty {
_ = self.assert_ty(
expr.pos(),
value.ty,
ty,
format_args!("'{}'", self.ast_display(expr)),
);
_ = self.assert_ty(expr.pos(), value.ty, ty, "somehow");
}
Some(match ctx.loc {
@ -2666,7 +2671,7 @@ impl Codegen {
fn run_vm(&mut self) {
loop {
match self.ct.vm.run().unwrap() {
match self.ct.vm.run().unwrap_or_else(|e| panic!("{e:?}")) {
hbvm::VmRunOk::End => break,
hbvm::VmRunOk::Timer => unreachable!(),
hbvm::VmRunOk::Ecall => self.handle_ecall(),
@ -2702,26 +2707,16 @@ impl Codegen {
}
}
fn report_log(&self, pos: Pos, msg: impl core::fmt::Display) {
log::error!("{}", self.cfile().report(pos, msg));
}
#[track_caller]
fn report(&self, pos: Pos, msg: impl core::fmt::Display) -> ! {
self.report_log(pos, msg);
log::error!("{}", self.cfile().report(pos, msg));
unreachable!();
}
#[track_caller]
fn report_unhandled_ast(&self, ast: &Expr, hint: &str) -> ! {
log::debug!("{ast:#?}");
self.report(
ast.pos(),
format_args!(
"compiler does not (yet) know how to handle ({hint}):\n{}",
self.ast_display(ast)
),
)
self.report(ast.pos(), format_args!("compiler does not (yet) know how to handle ({hint})",))
}
fn cfile(&self) -> &parser::Ast {
@ -2753,9 +2748,14 @@ impl Codegen {
}
pub fn assemble(&mut self, buf: &mut Vec<u8>) {
self.tys.ins.funcs.iter_mut().for_each(|f| f.offset = u32::MAX);
self.tys.ins.globals.iter_mut().for_each(|g| g.offset = u32::MAX);
self.tys.assemble(buf)
self.tys.reassemble(buf);
}
pub fn assemble_comptime(mut self) -> Comptime {
self.ct.code.clear();
self.tys.reassemble(&mut self.ct.code);
self.ct.reset();
self.ct
}
}

View file

@ -6,6 +6,26 @@ use {
core::fmt,
};
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!()
}
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..)
@ -16,7 +36,7 @@ pub fn minify(source: &mut str) -> usize {
let mut prev_needs_whitecpace = false;
let mut prev_needs_newline = false;
loop {
let mut token = lexer::Lexer::new(reader).next();
let mut token = lexer::Lexer::new(reader).eat();
match token.kind {
TokenKind::Eof => break,
TokenKind::CtIdent | TokenKind::Directive => token.start -= 1,
@ -321,25 +341,6 @@ impl<'a> Formatter<'a> {
self.fmt_list(f, true, "}", "", stmts, Self::fmt)
}
Expr::Number { value, radix, .. } => {
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!()
}
f.write_str(match radix {
Radix::Decimal => "",
Radix::Hex => "0x",
@ -404,7 +405,7 @@ pub fn preserve_newlines(source: &str) -> usize {
}
pub fn insert_needed_semicolon(source: &str) -> bool {
let kind = lexer::Lexer::new(source).next().kind;
let kind = lexer::Lexer::new(source).eat().kind;
kind.precedence().is_some() || matches!(kind, TokenKind::Ctor | TokenKind::Tupl)
}
@ -450,15 +451,15 @@ pub mod test {
minned.truncate(len);
let ast = parser::Ast::new(ident, minned, &mut ParserCtx::default(), &mut |_, _| Ok(0));
log::error!(
"{} / {} = {} | {} / {} = {}",
ast.mem.size(),
input.len(),
ast.mem.size() as f32 / input.len() as f32,
ast.mem.size(),
ast.file.len(),
ast.mem.size() as f32 / ast.file.len() as f32
);
//log::error!(
// "{} / {} = {} | {} / {} = {}",
// ast.mem.size(),
// input.len(),
// ast.mem.size() as f32 / input.len() as f32,
// ast.mem.size(),
// ast.file.len(),
// ast.mem.size() as f32 / ast.file.len() as f32
//);
let mut output = String::new();
write!(output, "{ast}").unwrap();

View file

@ -72,7 +72,6 @@ macro_rules! gen_token_kind {
} + 1)
}
#[inline(always)]
fn from_ident(ident: &[u8]) -> Self {
match ident {
$($keyword_lit => Self::$keyword,)*
@ -377,6 +376,25 @@ impl<'a> Lexer<'a> {
Self::restore(input, 0)
}
pub fn imports(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() }
}
@ -404,9 +422,9 @@ impl<'a> Lexer<'a> {
}
pub fn last(&mut self) -> Token {
let mut token = self.next();
let mut token = self.eat();
loop {
let next = self.next();
let next = self.eat();
if next.kind == TokenKind::Eof {
break;
}
@ -415,7 +433,7 @@ impl<'a> Lexer<'a> {
token
}
pub fn next(&mut self) -> Token {
pub fn eat(&mut self) -> Token {
use TokenKind as T;
loop {
let mut start = self.pos;

View file

@ -20,7 +20,8 @@
iter_intersperse,
str_from_raw_parts,
ptr_sub_ptr,
slice_from_ptr_range
slice_from_ptr_range,
is_sorted
)]
#![warn(clippy::dbg_macro)]
#![allow(stable_features, internal_features)]
@ -36,7 +37,7 @@ use {
ty::ArrayLen,
},
alloc::{collections::BTreeMap, string::String, vec::Vec},
core::{cell::Cell, fmt::Display, ops::Range},
core::{cell::Cell, ops::Range},
hashbrown::hash_map,
hbbytecode as instrs,
};
@ -68,7 +69,7 @@ pub mod parser;
#[cfg(feature = "opts")]
pub mod son;
mod lexer;
pub mod lexer;
#[cfg(feature = "opts")]
mod vc;
@ -535,37 +536,54 @@ mod ty {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
use Kind as TK;
match TK::from_ty(self.ty) {
TK::Module(idx) => write!(f, "@use({:?})[{}]", self.files[idx as usize].path, idx),
TK::Builtin(ty) => write!(f, "{}", to_str(ty)),
TK::Ptr(ty) => {
write!(f, "^{}", self.rety(self.tys.ins.ptrs[ty as usize].base))
TK::Module(idx) => {
f.write_str("@use(\"")?;
self.files[idx as usize].path.fmt(f)?;
f.write_str(")[")?;
idx.fmt(f)?;
f.write_str("]")
}
TK::Builtin(ty) => f.write_str(to_str(ty)),
TK::Ptr(ty) => self.rety(self.tys.ins.ptrs[ty as usize].base).fmt(f),
TK::Struct(idx) => {
let record = &self.tys.ins.structs[idx as usize];
if ident::is_null(record.name) {
write!(f, "[{idx}]{{")?;
f.write_str("[")?;
idx.fmt(f)?;
f.write_str("]{")?;
for (i, &super::Field { name, ty }) in
self.tys.struct_fields(idx).iter().enumerate()
{
if i != 0 {
write!(f, ", ")?;
f.write_str(", ")?;
}
write!(f, "{}: {}", self.tys.names.ident_str(name), self.rety(ty))?;
f.write_str(self.tys.names.ident_str(name))?;
f.write_str(": ")?;
self.rety(ty).fmt(f)?;
}
write!(f, "}}")
f.write_str("}")
} else {
let file = &self.files[record.file as usize];
write!(f, "{}", file.ident_str(record.name))
f.write_str(file.ident_str(record.name))
}
}
TK::Func(idx) => write!(f, "fn{idx}"),
TK::Global(idx) => write!(f, "global{idx}"),
TK::Func(idx) => {
f.write_str("fn")?;
idx.fmt(f)
}
TK::Global(idx) => {
f.write_str("global")?;
idx.fmt(f)
}
TK::Slice(idx) => {
let array = self.tys.ins.arrays[idx as usize];
match array.len {
ArrayLen::MAX => write!(f, "[{}]", self.rety(array.ty)),
len => write!(f, "[{}; {len}]", self.rety(array.ty)),
f.write_str("[")?;
self.rety(array.ty).fmt(f)?;
if array.len != ArrayLen::MAX {
f.write_str("; ")?;
array.len.fmt(f)?;
}
f.write_str("]")
}
}
}
@ -911,6 +929,12 @@ impl Types {
})
}
fn reassemble(&mut self, buf: &mut Vec<u8>) {
self.ins.funcs.iter_mut().for_each(|f| f.offset = u32::MAX);
self.ins.globals.iter_mut().for_each(|g| g.offset = u32::MAX);
self.assemble(buf)
}
fn assemble(&mut self, to: &mut Vec<u8>) {
to.extend([0u8; HEADER_SIZE]);
@ -1505,7 +1529,7 @@ impl hbvm::mem::Memory for LoggedMem {
struct AsHex<'a>(&'a [u8]);
impl Display for AsHex<'_> {
impl core::fmt::Display for AsHex<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for &b in self.0 {
write!(f, "{b:02x}")?;
@ -1513,3 +1537,14 @@ impl Display for AsHex<'_> {
Ok(())
}
}
pub fn quad_sort<T>(mut slice: &mut [T], mut cmp: impl FnMut(&T, &T) -> core::cmp::Ordering) {
while let Some(it) = slice.take_first_mut() {
for ot in &mut *slice {
if cmp(it, ot) == core::cmp::Ordering::Greater {
core::mem::swap(it, ot);
}
}
}
debug_assert!(slice.is_sorted_by(|a, b| cmp(a, b) != core::cmp::Ordering::Greater));
}

View file

@ -85,7 +85,7 @@ impl<'a, 'b> Parser<'a, 'b> {
let mut lexer = Lexer::new(input);
Self {
loader,
token: lexer.next(),
token: lexer.eat(),
lexer,
path,
ctx,
@ -111,7 +111,7 @@ impl<'a, 'b> Parser<'a, 'b> {
self.lexer.source(),
self.path,
ident::pos(id.ident),
format_args!(
&format_args!(
"undeclared identifier: {}",
self.lexer.slice(ident::range(id.ident))
),
@ -127,7 +127,7 @@ impl<'a, 'b> Parser<'a, 'b> {
}
fn next(&mut self) -> Token {
core::mem::replace(&mut self.token, self.lexer.next())
core::mem::replace(&mut self.token, self.lexer.eat())
}
fn ptr_expr(&mut self) -> &'a Expr<'a> {
@ -328,16 +328,9 @@ impl<'a, 'b> Parser<'a, 'b> {
},
captured: {
self.ns_bound = prev_boundary;
let mut captured = &mut self.ctx.captured[prev_captured..];
while let Some(it) = captured.take_first_mut() {
for ot in &mut *captured {
if it > ot {
core::mem::swap(it, ot);
}
}
}
debug_assert!(captured.is_sorted());
let preserved = self.ctx.captured[prev_captured..].partition_dedup().0.len();
let captured = &mut self.ctx.captured[prev_captured..];
crate::quad_sort(captured, core::cmp::Ord::cmp);
let preserved = captured.partition_dedup().0.len();
self.ctx.captured.truncate(prev_captured + preserved);
self.arena.alloc_slice(&self.ctx.captured[prev_captured..])
},
@ -983,7 +976,7 @@ impl AstInner<[Symbol]> {
let exprs =
unsafe { core::mem::transmute(Parser::parse(ctx, &file, path, loader, &arena)) };
ctx.symbols.sort_unstable_by_key(|s| s.name);
crate::quad_sort(&mut ctx.symbols, |a, b| a.name.cmp(&b.name));
let layout = Self::layout(ctx.symbols.len());
@ -1012,22 +1005,6 @@ impl AstInner<[Symbol]> {
}
}
fn report_to(file: &str, path: &str, pos: Pos, msg: impl fmt::Display, out: &mut impl fmt::Write) {
let (line, mut col) = lexer::line_col(file.as_bytes(), pos);
#[cfg(feature = "std")]
let disp = crate::fs::display_rel_path(path);
#[cfg(not(feature = "std"))]
let disp = path;
_ = writeln!(out, "{}:{}:{}: {}", disp, line, col, msg);
let line = &file[file[..pos as usize].rfind('\n').map_or(0, |i| i + 1)
..file[pos as usize..].find('\n').unwrap_or(file.len()) + pos as usize];
col += line.matches('\t').count() * 3;
_ = writeln!(out, "{}", line.replace("\t", " "));
_ = writeln!(out, "{}^", " ".repeat(col - 1));
}
pub struct Report<'a, D> {
file: &'a str,
path: &'a str,
@ -1048,6 +1025,22 @@ impl<D: core::fmt::Display> core::fmt::Display for Report<'_, D> {
}
}
fn report_to(file: &str, path: &str, pos: Pos, msg: &dyn fmt::Display, out: &mut impl fmt::Write) {
let (line, mut col) = lexer::line_col(file.as_bytes(), pos);
#[cfg(feature = "std")]
let disp = crate::fs::display_rel_path(path);
#[cfg(not(feature = "std"))]
let disp = path;
_ = writeln!(out, "{}:{}:{}: {}", disp, line, col, msg);
let line = &file[file[..pos as usize].rfind('\n').map_or(0, |i| i + 1)
..file[pos as usize..].find('\n').map_or(file.len(), |i| i + pos as usize)];
col += line.matches('\t').count() * 3;
_ = writeln!(out, "{}", line.replace("\t", " "));
_ = writeln!(out, "{}^", " ".repeat(col - 1));
}
#[derive(PartialEq, Eq, Hash)]
pub struct Ast(NonNull<AstInner<[Symbol]>>);

View file

@ -126,11 +126,33 @@ pub enum VmRunError {
/// Invalid operand
InvalidOperand,
/// Unimplemented feature
Unimplemented,
}
impl core::fmt::Display for VmRunError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
VmRunError::InvalidOpcode(op) => {
f.write_str("invalid op code: ").and_then(|_| op.fmt(f))
}
VmRunError::LoadAccessEx(address) => {
f.write_str("falied to load at ").and_then(|_| address.fmt(f))
}
VmRunError::ProgramFetchLoadEx(address) => {
f.write_str("falied to load instruction at ").and_then(|_| address.fmt(f))
}
VmRunError::StoreAccessEx(address) => {
f.write_str("falied to store at ").and_then(|_| address.fmt(f))
}
VmRunError::RegOutOfBounds => f.write_str("reg out of bounds"),
VmRunError::AddrOutOfBounds => f.write_str("addr out-of-bounds"),
VmRunError::Unreachable => f.write_str("unreachable"),
VmRunError::InvalidOperand => f.write_str("invalud operand"),
}
}
}
impl core::error::Error for VmRunError {}
/// Virtual machine halt ok
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum VmRunOk {

View file

@ -13,28 +13,47 @@ fn root() -> &'static Path {
Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap()
}
fn exec(cmd: impl AsRef<str>) -> io::Result<()> {
fn build_cmd(cmd: impl AsRef<str>) -> std::process::Command {
let mut args = cmd.as_ref().split_whitespace();
let mut c = std::process::Command::new(args.next().unwrap());
for arg in args {
c.arg(arg);
}
if !c.status()?.success() {
return Err(io::Error::other(format!("command failed: {}", cmd.as_ref())));
c
}
fn exec(mut cmd: std::process::Command) -> io::Result<()> {
if !cmd.status()?.success() {
return Err(io::Error::other(format!("command failed: {:?}", cmd)));
}
Ok(())
}
fn build_wasm_blob(name: &str, debug: bool) -> io::Result<()> {
let mut c = build_cmd(if debug { "cargo wasm-build-debug" } else { "cargo wasm-build" });
c.arg(format!("wasm-{name}"));
exec(c)?;
let out_path = format!("target/wasm32-unknown-unknown/small/wasm_{name}.wasm");
if !debug {
exec(build_cmd(format!("wasm-opt -Oz {out_path} -o {out_path}")))?;
}
exec(build_cmd(format!("cp {out_path} depell/src/wasm-{name}.wasm")))
}
fn main() -> io::Result<()> {
let args = std::env::args().skip(1).collect::<Vec<_>>();
match args[0].as_str() {
"fmt" => fmt(args[1] == "-r" || args[1] == "--renumber"),
"build-depell" => {
exec(
"cargo build -p wasm-hbfmt --target wasm32-unknown-unknown \
--profile=small -Zbuild-std=core,alloc",
)?;
exec("cargo build -p depell --release")?;
build_wasm_blob("hbfmt", false)?;
build_wasm_blob("hbc", false)?;
exec(build_cmd("cargo build -p depell --release"))?;
Ok(())
}
"build-depell-debug" => {
build_wasm_blob("hbfmt", true)?;
build_wasm_blob("hbc", true)?;
Ok(())
}
_ => Ok(()),