1
0
Fork 0
forked from AbleOS/holey-bytes
This commit is contained in:
peony 2024-12-30 17:40:07 +01:00
commit 74258f62f9
148 changed files with 14603 additions and 8099 deletions
.gitignoreCargo.lockCargo.toml
bytecode
c
cranelift-backend
depell
examples/raylib
lang
Cargo.tomlREADME.mdbuild.rscommand-help.txt
src
tests
son_tests_advanced_floating_point_arithmetic.txtson_tests_aliasing_overoptimization.txtson_tests_arithmetic.txtson_tests_arrays.txtson_tests_big_array_crash.txtson_tests_branch_assignments.txtson_tests_c_strings.txtson_tests_comments.txtson_tests_comparing_floating_points.txtson_tests_comptime_function_from_another_file.txtson_tests_comptime_min_reg_leak.txtson_tests_conditional_stores.txtson_tests_const_folding_with_arg.txtson_tests_constants.txtson_tests_dead_code_in_loop.txtson_tests_defer.txtson_tests_die.txtson_tests_different_function_destinations.txtson_tests_different_types.txtson_tests_directives.txtson_tests_elide_stack_offsets_for_parameters_correctly.txtson_tests_enums.txtson_tests_exhaustive_loop_testing.txtson_tests_fb_driver.txtson_tests_floating_point_arithmetic.txtson_tests_functions.txtson_tests_generic_function_in_struct.txtson_tests_generic_functions.txtson_tests_generic_type_mishap.txtson_tests_generic_types.txtson_tests_global_aliasing_overptimization.txtson_tests_global_variable_wiredness.txtson_tests_global_variables.txtson_tests_hex_octal_binary_literals.txtson_tests_idk.txtson_tests_if_statements.txtson_tests_infinite_loop_after_peephole.txtson_tests_inline.txtson_tests_inline_return_stack.txtson_tests_inline_test.txtson_tests_inlined_generic_functions.txtson_tests_inlining_issues.txtson_tests_inlining_loops.txtson_tests_intcast_store.txtson_tests_integer_inference_issues.txtson_tests_len_never_goes_down.txtson_tests_loop_stores.txtson_tests_loops.txtson_tests_main_fn.txtson_tests_memory_swap.txtson_tests_method_receiver_by_value.txtson_tests_more_if_opts.txtson_tests_needless_unwrap.txtson_tests_null_check_in_the_loop.txt

6
.gitignore vendored
View file

@ -2,6 +2,10 @@
/target
rustc-ice-*
a.out
out.o
/examples/raylib/main
# sqlite
db.sqlite
db.sqlite-journal
@ -9,5 +13,7 @@ db.sqlite-journal
# assets
/depell/src/*.gz
/depell/src/*.wasm
/depell/src/static-pages/*.html
#**/*-sv.rs
/bytecode/src/instrs.rs
/lang/src/testcases.rs

358
Cargo.lock generated
View file

@ -40,15 +40,70 @@ dependencies = [
[[package]]
name = "allocator-api2"
version = "0.2.20"
version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45862d1c77f2228b9e10bc609d5bc203d86ebc9b87ad8d5d5167a6c9abf739d9"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys 0.59.0",
]
[[package]]
name = "anyhow"
version = "1.0.93"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "arbitrary"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223"
[[package]]
name = "arc-swap"
@ -317,6 +372,46 @@ dependencies = [
"libloading",
]
[[package]]
name = "clap"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "cmake"
version = "0.1.51"
@ -326,6 +421,12 @@ dependencies = [
"cc",
]
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "const_format"
version = "0.2.33"
@ -355,6 +456,143 @@ dependencies = [
"libc",
]
[[package]]
name = "cranelift-backend"
version = "0.1.0"
dependencies = [
"cranelift-codegen",
"cranelift-frontend",
"cranelift-module",
"cranelift-object",
"hblang",
"target-lexicon",
]
[[package]]
name = "cranelift-bforest"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac89549be94911dd0e839b4a7db99e9ed29c17517e1c026f61066884c168aa3c"
dependencies = [
"cranelift-entity",
]
[[package]]
name = "cranelift-bitset"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9bd49369f76c77e34e641af85d0956869237832c118964d08bf5f51f210875a"
[[package]]
name = "cranelift-codegen"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd96ce9cf8efebd7f5ab8ced5a0ce44250280bbae9f593d74a6d7effc3582a35"
dependencies = [
"bumpalo",
"cranelift-bforest",
"cranelift-bitset",
"cranelift-codegen-meta",
"cranelift-codegen-shared",
"cranelift-control",
"cranelift-entity",
"cranelift-isle",
"gimli 0.31.1",
"hashbrown 0.14.5",
"log",
"regalloc2",
"rustc-hash 2.1.0",
"serde",
"smallvec",
"target-lexicon",
]
[[package]]
name = "cranelift-codegen-meta"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a68e358827afe4bfb6239fcbf6fbd5ac56206ece8a99c8f5f9bbd518773281a"
dependencies = [
"cranelift-codegen-shared",
]
[[package]]
name = "cranelift-codegen-shared"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e184c9767afbe73d50c55ec29abcf4c32f9baf0d9d22b86d58c4d55e06dee181"
[[package]]
name = "cranelift-control"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc7664f2a66f053e33f149e952bb5971d138e3af637f5097727ed6dc0ed95dd"
dependencies = [
"arbitrary",
]
[[package]]
name = "cranelift-entity"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "118597e3a9cf86c3556fa579a7a23b955fa18231651a52a77a2475d305a9cf84"
dependencies = [
"cranelift-bitset",
]
[[package]]
name = "cranelift-frontend"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7638ea1efb069a0aa18d8ee67401b6b0d19f6bfe5de5e9ede348bfc80bb0d8c7"
dependencies = [
"cranelift-codegen",
"log",
"smallvec",
"target-lexicon",
]
[[package]]
name = "cranelift-isle"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15c53e1152a0b01c4ed2b1e0535602b8e86458777dd9d18b28732b16325c7dc0"
[[package]]
name = "cranelift-module"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11841b3f54ac480db1e8e8d5678ba901a13b387012d315e3f8fba3e7b7a80447"
dependencies = [
"anyhow",
"cranelift-codegen",
"cranelift-control",
]
[[package]]
name = "cranelift-object"
version = "0.115.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e235ddfd19f100855ad03358c7ae0a13070c38a000701054cab46458cca6e81"
dependencies = [
"anyhow",
"cranelift-codegen",
"cranelift-control",
"cranelift-module",
"log",
"object",
"target-lexicon",
]
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -429,7 +667,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -456,6 +694,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -547,6 +791,11 @@ name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
dependencies = [
"fallible-iterator 0.3.0",
"indexmap 2.6.0",
"stable_deref_trait",
]
[[package]]
name = "glob"
@ -595,7 +844,7 @@ version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
dependencies = [
"allocator-api2",
"foldhash",
]
[[package]]
@ -611,6 +860,17 @@ dependencies = [
name = "hbbytecode"
version = "0.1.0"
[[package]]
name = "hbc"
version = "0.1.0"
dependencies = [
"clap",
"cranelift-backend",
"hblang",
"log",
"target-lexicon",
]
[[package]]
name = "hblang"
version = "0.1.0"
@ -619,7 +879,6 @@ dependencies = [
"hbbytecode",
"hbvm",
"log",
"regalloc2",
]
[[package]]
@ -655,7 +914,7 @@ version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
dependencies = [
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -782,6 +1041,12 @@ dependencies = [
"serde",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]]
name = "itertools"
version = "0.12.1"
@ -863,6 +1128,15 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "markdown"
version = "1.0.0-alpha.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6491e6c702bf7e3b24e769d800746d5f2c06a6c6a2db7992612e0f429029e81"
dependencies = [
"unicode-id",
]
[[package]]
name = "matchit"
version = "0.7.3"
@ -914,7 +1188,7 @@ dependencies = [
"hermit-abi",
"libc",
"wasi",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -945,6 +1219,9 @@ version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"crc32fast",
"hashbrown 0.15.0",
"indexmap 2.6.0",
"memchr",
]
@ -1060,14 +1337,15 @@ dependencies = [
[[package]]
name = "regalloc2"
version = "0.10.2"
source = "git+https://github.com/jakubDoka/regalloc2?branch=reuse-allocations#21c43e3ee182824e92e2b25f1d3c03ed47f9c02b"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145c1c267e14f20fb0f88aa76a1c5ffec42d592c1d28b3cd9148ae35916158d3"
dependencies = [
"allocator-api2",
"bumpalo",
"hashbrown 0.14.5",
"hashbrown 0.15.0",
"log",
"rustc-hash 2.0.0",
"rustc-hash 2.1.0",
"smallvec",
]
@ -1112,7 +1390,7 @@ dependencies = [
"libc",
"spin",
"untrusted",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -1143,9 +1421,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.0.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152"
checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
[[package]]
name = "rustix"
@ -1157,7 +1435,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -1221,18 +1499,18 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b"
[[package]]
name = "serde"
version = "1.0.215"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.215"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
@ -1301,7 +1579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -1316,6 +1594,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
@ -1345,6 +1629,12 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
[[package]]
name = "target-lexicon"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "time"
version = "0.3.36"
@ -1377,7 +1667,7 @@ dependencies = [
"pin-project-lite",
"socket2",
"tokio-macros",
"windows-sys",
"windows-sys 0.52.0",
]
[[package]]
@ -1484,6 +1774,12 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "unicode-id"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10103c57044730945224467c09f71a4db0071c123a0648cc3e818913bde6b561"
[[package]]
name = "unicode-ident"
version = "1.0.13"
@ -1502,6 +1798,12 @@ version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
@ -1617,6 +1919,15 @@ dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
@ -1686,6 +1997,7 @@ name = "xtask"
version = "0.1.0"
dependencies = [
"anyhow",
"markdown",
"walrus",
]

View file

@ -12,14 +12,14 @@ members = [
"depell/wasm-fmt",
"depell/wasm-hbc",
"depell/wasm-rt",
"cranelift-backend",
"c",
]
[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

View file

@ -5,6 +5,6 @@ edition = "2018"
[features]
default = ["disasm"]
std = []
disasm = ["std"]
disasm = ["alloc"]
alloc = []

View file

@ -98,6 +98,27 @@ fn gen_instrs(generated: &mut String) -> Result<(), Box<dyn std::error::Error>>
writeln!(generated, " {name} = {id},")?;
}
writeln!(generated, "}}")?;
writeln!(generated, "impl {instr} {{")?;
writeln!(generated, " pub fn size(self) -> usize {{")?;
writeln!(generated, " match self {{")?;
let mut instrs = instructions().collect::<Vec<_>>();
instrs.sort_unstable_by_key(|&[.., ty, _]| iter_args(ty).map(arg_to_width).sum::<usize>());
for group in instrs.chunk_by(|[.., a, _], [.., b, _]| {
iter_args(a).map(arg_to_width).sum::<usize>()
== iter_args(b).map(arg_to_width).sum::<usize>()
}) {
let ty = group[0][2];
for &[_, name, ..] in group {
writeln!(generated, " | {instr}::{name}")?;
}
generated.pop();
let size = iter_args(ty).map(arg_to_width).sum::<usize>() + 1;
writeln!(generated, " => {size},")?;
}
writeln!(generated, " }}")?;
writeln!(generated, " }}")?;
writeln!(generated, "}}")?;
}
'_arg_kind: {

View file

@ -254,8 +254,7 @@ pub fn disasm<'a>(
|| 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;
.is_none_or(|&b| instr_from_byte(b).is_err());
has_oob |= local_has_oob;
let label = labels.get(&global_offset).unwrap();
if local_has_oob {

11
c/Cargo.toml Normal file
View file

@ -0,0 +1,11 @@
[package]
name = "hbc"
version = "0.1.0"
edition = "2024"
[dependencies]
clap = { version = "4.5.23", features = ["derive", "env"] }
cranelift-backend = { version = "0.1.0", path = "../cranelift-backend" }
hblang = { workspace = true, features = ["std"] }
log = "0.4.22"
target-lexicon = { version = "0.12", features = ["std"] }

95
c/src/main.rs Normal file
View file

@ -0,0 +1,95 @@
use {
clap::Parser,
std::{io, str::FromStr},
};
#[derive(Parser)]
struct Args {
/// format depends on the backend used
/// - cranelift-backend expects `<key>=<value>,...` pass `help=me` to see options
#[clap(long, env, default_value = "")]
backend_flags: String,
#[clap(long, short, env, default_value_t = target_lexicon::HOST)]
target: target_lexicon::Triple,
#[clap(long, env, value_parser = ["ableos"])]
path_resolver: Option<String>,
/// format the source code reachable form the root file
#[clap(long, env, default_value_t = false, conflicts_with_all = &["fmt_stdout", "dump_asm"])]
fmt: bool,
/// format the root file only and output the formatted file into stdout
#[clap(long, env, default_value_t = false, conflicts_with_all = &["fmt", "dump_asm"])]
fmt_stdout: bool,
#[clap(long, env, default_value_t = false, conflicts_with_all = &["fmt", "fmt_stdout"])]
dump_asm: bool,
/// extra threads to be used during compilation (currently only parser is parallelized)
#[clap(long, env, default_value_t = 0)]
extra_threads: usize,
/// path to the root file
file: String,
}
fn main() {
use std::io::Write;
fn run(out: &mut Vec<u8>, warnings: &mut String) -> std::io::Result<()> {
let Args {
backend_flags,
target,
path_resolver,
fmt,
fmt_stdout,
dump_asm,
extra_threads,
file,
} = Args::parse();
let resolvers = &[("ableos", hblang::ABLEOS_PATH_RESOLVER)];
let mut native = None;
let backend = if target
== target_lexicon::Triple::from_str(hblang::backend::hbvm::TARGET_TRIPLE).unwrap()
{
None
} else {
Some(
native.insert(
cranelift_backend::Backend::new(target, &backend_flags)
.map_err(io::Error::other)?,
) as &mut dyn hblang::backend::Backend,
)
};
let opts = hblang::Options {
fmt,
fmt_stdout,
dump_asm,
extra_threads,
resolver: resolvers
.iter()
.copied()
.find(|&(name, _)| Some(name) == path_resolver.as_deref())
.map(|(_, v)| v),
backend,
};
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(e) => {
std::io::stderr().write_all(warnings.as_bytes()).unwrap();
std::io::stderr().write_all(&out).unwrap();
std::eprint!("{e}");
std::process::exit(1);
}
}
}

View file

@ -0,0 +1,12 @@
[package]
name = "cranelift-backend"
version = "0.1.0"
edition = "2024"
[dependencies]
cranelift-codegen = "0.115.0"
cranelift-frontend = "0.115.0"
cranelift-module = "0.115.0"
cranelift-object = "0.115.0"
hblang.workspace = true
target-lexicon = "0.12"

View file

@ -0,0 +1,960 @@
#![feature(if_let_guard)]
#![feature(slice_take)]
use {
core::panic,
cranelift_codegen::{
self as cc, CodegenError, Final, FinalizedMachReloc, MachBufferFinalized,
ir::{self as cir, InstBuilder, MemFlags, TrapCode, UserExternalName, condcodes},
isa::{LookupError, TargetIsa},
settings::{Configurable, SetError},
},
cranelift_frontend::{self as cf, FunctionBuilder},
cranelift_module::{self as cm, Module, ModuleError},
hblang::{
lexer::TokenKind,
nodes::{self as hbnodes},
ty as hbty,
utils::{self as hbutils, Ent, EntVec},
},
std::{
fmt::{Display, Write},
ops::Range,
},
};
mod x86_64;
pub struct Backend {
ctx: cc::Context,
dt_ctx: cm::DataDescription,
fb_ctx: cf::FunctionBuilderContext,
module: Option<cranelift_object::ObjectModule>,
ctrl_plane: cc::control::ControlPlane,
funcs: Functions,
globals: EntVec<hbty::Global, Global>,
asm: Assembler,
}
impl Backend {
pub fn new(triple: target_lexicon::Triple, flags: &str) -> Result<Self, BackendCreationError> {
Ok(Self {
ctx: cc::Context::new(),
dt_ctx: cm::DataDescription::new(),
fb_ctx: cf::FunctionBuilderContext::default(),
ctrl_plane: cc::control::ControlPlane::default(),
module: cranelift_object::ObjectModule::new(cranelift_object::ObjectBuilder::new(
cc::isa::lookup(triple)?.finish(cc::settings::Flags::new({
let mut bl = cc::settings::builder();
for (k, v) in flags.split(',').filter_map(|s| s.split_once('=')) {
bl.set(k, v).map_err(|err| BackendCreationError::InvalidFlag {
key: k.to_owned(),
value: v.to_owned(),
err,
})?;
}
bl
}))?,
"main",
cm::default_libcall_names(),
)?)
.into(),
funcs: Default::default(),
globals: Default::default(),
asm: Default::default(),
})
}
}
impl hblang::backend::Backend for Backend {
fn assemble_reachable(
&mut self,
from: hbty::Func,
types: &hbty::Types,
files: &hbutils::EntSlice<hbty::Module, hblang::parser::Ast>,
to: &mut Vec<u8>,
) -> hblang::backend::AssemblySpec {
debug_assert!(self.asm.frontier.is_empty());
debug_assert!(self.asm.funcs.is_empty());
debug_assert!(self.asm.globals.is_empty());
let mut module = self.module.take().expect("backend can assemble only once");
fn clif_name_to_ty(name: UserExternalName) -> hbty::Id {
match name.namespace {
0 => hbty::Kind::Func(hbty::Func::new(name.index as _)),
1 => hbty::Kind::Global(hbty::Global::new(name.index as _)),
_ => unreachable!(),
}
.compress()
}
self.globals.shadow(types.ins.globals.len());
self.asm.frontier.push(from.into());
while let Some(itm) = self.asm.frontier.pop() {
match itm.expand() {
hbty::Kind::Func(func) => {
let fd = &types.ins.funcs[func];
if fd.is_import {
self.funcs.headers.shadow(func.index() + 1);
}
let fuc = &mut self.funcs.headers[func];
let file = &files[fd.file];
if fuc.module_id.is_some() {
continue;
}
self.asm.frontier.extend(
fuc.external_names.clone().map(|r| {
clif_name_to_ty(self.funcs.external_names[r as usize].clone())
}),
);
self.asm.name.clear();
if func == from {
self.asm.name.push_str("main");
} else if fd.is_import {
self.asm.name.push_str(file.ident_str(fd.name));
} else {
self.asm.name.push_str(hblang::strip_cwd(&file.path));
self.asm.name.push('.');
self.asm.name.push_str(file.ident_str(fd.name));
}
let linkage = if func == from {
cm::Linkage::Export
} else if fd.is_import {
cm::Linkage::Import
} else {
cm::Linkage::Local
};
build_signature(
module.isa().default_call_conv(),
fd.sig,
types,
&mut self.ctx.func.signature,
&mut vec![],
);
fuc.module_id = Some(
module
.declare_function(&self.asm.name, linkage, &self.ctx.func.signature)
.unwrap(),
);
if !fd.is_import {
self.asm.funcs.push(func);
}
}
hbty::Kind::Global(glob) => {
if self.globals[glob].module_id.is_some() {
continue;
}
self.asm.globals.push(glob);
self.asm.name.clear();
let mutable = if types.ins.globals[glob].file == Default::default() {
writeln!(self.asm.name, "anon{}", glob.index()).unwrap();
false
} else {
let file = &files[types.ins.globals[glob].file];
self.asm.name.push_str(hblang::strip_cwd(&file.path));
self.asm.name.push('.');
self.asm.name.push_str(file.ident_str(types.ins.globals[glob].name));
true
};
self.globals[glob].module_id = Some(
module
.declare_data(&self.asm.name, cm::Linkage::Local, mutable, false)
.unwrap(),
);
}
_ => unreachable!(),
}
}
for &func in &self.asm.funcs {
let fuc = &self.funcs.headers[func];
assert!(!types.ins.funcs[func].is_import);
debug_assert!(!fuc.code.is_empty());
let names = &mut self.funcs.external_names
[fuc.external_names.start as usize..fuc.external_names.end as usize];
self.ctx.func.clear();
names.iter().for_each(|nm| {
let mut nm = nm.clone();
if nm.namespace == 0 {
nm.index = self.funcs.headers[hbty::Func::new(nm.index as _)]
.module_id
.unwrap()
.as_u32();
} else {
nm.index =
self.globals[hbty::Global::new(nm.index as _)].module_id.unwrap().as_u32();
}
self.ctx.func.params.ensure_user_func_name(nm.clone());
});
module
.define_function_bytes(
fuc.module_id.unwrap(),
&self.ctx.func,
fuc.alignment as _,
&self.funcs.code[fuc.code.start as usize..fuc.code.end as usize],
&self.funcs.relocs[fuc.relocs.start as usize..fuc.relocs.end as usize],
)
.unwrap();
}
for global in self.asm.globals.drain(..) {
let glob = &self.globals[global];
self.dt_ctx.clear();
self.dt_ctx.define(types.ins.globals[global].data.clone().into());
module.define_data(glob.module_id.unwrap(), &self.dt_ctx).unwrap();
}
module.finish().object.write_stream(to).unwrap();
hblang::backend::AssemblySpec { code_length: 0, data_length: 0, entry: 0 }
}
fn disasm<'a>(
&'a self,
_sluce: &[u8],
_eca_handler: &mut dyn FnMut(&mut &[u8]),
_types: &'a hbty::Types,
_files: &'a hbutils::EntSlice<hbty::Module, hblang::parser::Ast>,
_output: &mut String,
) -> Result<(), std::boxed::Box<dyn core::error::Error + Send + Sync + 'a>> {
unimplemented!()
}
fn emit_body(
&mut self,
id: hbty::Func,
nodes: &hbnodes::Nodes,
tys: &hbty::Types,
files: &hbutils::EntSlice<hbty::Module, hblang::parser::Ast>,
) {
self.ctx.clear();
let isa = self.module.as_ref().unwrap().isa();
let mut lens = vec![];
let stack_ret = build_signature(
isa.default_call_conv(),
tys.ins.funcs[id].sig,
tys,
&mut self.ctx.func.signature,
&mut lens,
);
FuncBuilder {
bl: FunctionBuilder::new(&mut self.ctx.func, &mut self.fb_ctx),
isa,
nodes,
tys,
files,
values: &mut vec![None; nodes.len()],
arg_lens: &lens,
stack_ret,
}
.build(tys.ins.funcs[id].sig);
self.ctx.func.name =
cir::UserFuncName::User(cir::UserExternalName { namespace: 0, index: id.index() as _ });
//std::eprintln!("{}", self.ctx.func.display());
self.ctx.compile(isa, &mut self.ctrl_plane).unwrap();
let code = self.ctx.compiled_code().unwrap();
self.funcs.push(id, &self.ctx.func, &code.buffer);
}
}
fn build_signature(
call_conv: cc::isa::CallConv,
sig: hbty::Sig,
types: &hbty::Types,
signature: &mut cir::Signature,
arg_meta: &mut Vec<AbiMeta>,
) -> bool {
signature.clear(call_conv);
match call_conv {
cc::isa::CallConv::SystemV => {
x86_64::build_systemv_signature(sig, types, signature, arg_meta)
}
_ => todo!(),
}
}
#[derive(Clone, Copy)]
struct AbiMeta {
trough_mem: bool,
arg_count: usize,
}
struct FuncBuilder<'a, 'b> {
bl: cf::FunctionBuilder<'b>,
isa: &'a dyn TargetIsa,
nodes: &'a hbnodes::Nodes,
tys: &'a hbty::Types,
files: &'a hbutils::EntSlice<hbty::Module, hblang::parser::Ast>,
values: &'b mut [Option<Result<cir::Value, cir::Block>>],
arg_lens: &'a [AbiMeta],
stack_ret: bool,
}
impl FuncBuilder<'_, '_> {
pub fn build(mut self, sig: hbty::Sig) {
let entry = self.bl.create_block();
self.bl.append_block_params_for_function_params(entry);
self.bl.switch_to_block(entry);
let mut arg_vals = &self.bl.block_params(entry).to_vec()[..];
if self.stack_ret {
let ret_ptr = *arg_vals.take_first().unwrap();
self.values[hbnodes::MEM as usize] = Some(Ok(ret_ptr));
}
let Self { nodes, tys, .. } = self;
let mut parama_len = self.arg_lens[1..].iter();
let mut typs = sig.args.args();
let mut args = nodes[hbnodes::VOID].outputs[hbnodes::ARG_START..].iter();
while let Some(aty) = typs.next(tys) {
let hbty::Arg::Value(ty) = aty else { continue };
let abi_meta = parama_len.next().unwrap();
let &arg = args.next().unwrap();
if !abi_meta.trough_mem && ty.is_aggregate(tys) {
let slot = self.bl.create_sized_stack_slot(cir::StackSlotData {
kind: cir::StackSlotKind::ExplicitSlot,
size: self.tys.size_of(ty),
align_shift: self.tys.align_of(ty).ilog2() as _,
});
let loc = arg_vals.take(..abi_meta.arg_count).unwrap();
assert!(loc.len() <= 2, "NEED handling");
let align =
loc.iter().map(|&p| self.bl.func.dfg.value_type(p).bytes()).max().unwrap();
let mut offset = 0i32;
for &v in loc {
self.bl.ins().stack_store(v, slot, offset);
offset += align as i32;
}
self.values[arg as usize] =
Some(Ok(self.bl.ins().stack_addr(cir::types::I64, slot, 0)))
} else {
let loc = arg_vals.take(..abi_meta.arg_count).unwrap();
debug_assert_eq!(loc.len(), 1);
self.values[arg as usize] = Some(Ok(loc[0]));
}
}
self.values[hbnodes::ENTRY as usize] = Some(Err(entry));
self.emit_node(hbnodes::VOID, hbnodes::VOID);
self.bl.finalize();
}
fn value_of(&self, nid: hbnodes::Nid) -> cir::Value {
self.values[nid as usize].unwrap_or_else(|| panic!("{:?}", self.nodes[nid])).unwrap()
}
fn block_of(&self, nid: hbnodes::Nid) -> cir::Block {
self.values[nid as usize].unwrap().unwrap_err()
}
fn close_block(&mut self, nid: hbnodes::Nid) {
if matches!(self.nodes[nid].kind, hbnodes::Kind::Loop) {
return;
}
self.bl.seal_block(self.block_of(nid));
}
fn emit_node(&mut self, nid: hbnodes::Nid, block: hbnodes::Nid) {
use hbnodes::*;
let mut args = vec![];
if matches!(self.nodes[nid].kind, Kind::Region | Kind::Loop) {
let side = 1 + self.values[nid as usize].is_some() as usize;
for &o in self.nodes[nid].outputs.iter() {
if self.nodes[o].is_data_phi() {
args.push(self.value_of(self.nodes[o].inputs[side]));
}
}
match (self.nodes[nid].kind, self.values[nid as usize]) {
(Kind::Loop, Some(blck)) => {
self.bl.ins().jump(blck.unwrap_err(), &args);
self.bl.seal_block(blck.unwrap_err());
self.close_block(block);
return;
}
(Kind::Region, None) => {
let next = self.bl.create_block();
for &o in self.nodes[nid].outputs.iter() {
if self.nodes[o].is_data_phi() {
self.values[o as usize] = Some(Ok(self
.bl
.append_block_param(next, self.nodes[o].ty.to_clif(self.tys))));
}
}
self.bl.ins().jump(next, &args);
self.close_block(block);
self.values[nid as usize] = Some(Err(next));
return;
}
_ => {}
}
}
let node = &self.nodes[nid];
self.values[nid as usize] = Some(match node.kind {
Kind::Start => {
debug_assert_eq!(self.nodes[node.outputs[0]].kind, Kind::Entry);
self.emit_node(node.outputs[0], block);
return;
}
Kind::If => {
let &[_, cnd] = node.inputs.as_slice() else { unreachable!() };
let &[then, else_] = node.outputs.as_slice() else { unreachable!() };
let then_bl = self.bl.create_block();
let else_bl = self.bl.create_block();
let c = self.value_of(cnd);
self.bl.ins().brif(c, then_bl, &[], else_bl, &[]);
self.values[then as usize] = Some(Err(then_bl));
self.values[else_ as usize] = Some(Err(else_bl));
self.close_block(block);
self.bl.switch_to_block(then_bl);
self.emit_node(then, then);
self.bl.switch_to_block(else_bl);
self.emit_node(else_, else_);
Err(self.block_of(block))
}
Kind::Loop => {
let next = self.bl.create_block();
for &o in self.nodes[nid].outputs.iter() {
if self.nodes[o].is_data_phi() {
self.values[o as usize] = Some(Ok(self
.bl
.append_block_param(next, self.nodes[o].ty.to_clif(self.tys))));
}
}
self.values[nid as usize] = Some(Err(next));
self.bl.ins().jump(self.values[nid as usize].unwrap().unwrap_err(), &args);
self.close_block(block);
self.bl.switch_to_block(self.values[nid as usize].unwrap().unwrap_err());
for &o in node.outputs.iter().rev() {
self.emit_node(o, nid);
}
Err(self.block_of(block))
}
Kind::Region => {
self.bl.ins().jump(self.values[nid as usize].unwrap().unwrap_err(), &args);
self.close_block(block);
self.bl.switch_to_block(self.values[nid as usize].unwrap().unwrap_err());
for &o in node.outputs.iter().rev() {
self.emit_node(o, nid);
}
return;
}
Kind::Die => {
self.bl.ins().trap(TrapCode::unwrap_user(1));
self.close_block(block);
self.emit_node(node.outputs[0], block);
Err(self.block_of(block))
}
Kind::Return { .. } => {
let mut ir_args = vec![];
if node.inputs[1] == hbnodes::VOID {
} else {
let abi_meta = self.arg_lens[0];
let arg = node.inputs[1];
if !abi_meta.trough_mem && self.nodes[node.inputs[1]].ty.is_aggregate(self.tys)
{
let loc = self.bl.func.signature.returns.clone();
assert!(loc.len() <= 2, "NEED handling");
let align = loc.iter().map(|&p| p.value_type.bytes()).max().unwrap();
let mut offset = 0i32;
let src = self.value_of(self.nodes[arg].inputs[1]);
debug_assert!(self.nodes[arg].kind == Kind::Load);
for &v in &loc {
ir_args.push(self.bl.ins().load(
v.value_type,
MemFlags::new(),
src,
offset,
));
offset += align as i32;
}
} else if self.stack_ret {
let src = self.value_of(self.nodes[arg].inputs[1]);
let dest = self.value_of(MEM);
self.bl.emit_small_memory_copy(
self.isa.frontend_config(),
dest,
src,
self.tys.size_of(self.nodes[arg].ty) as _,
self.tys.align_of(self.nodes[arg].ty) as _,
self.tys.align_of(self.nodes[arg].ty) as _,
false,
MemFlags::new(),
);
} else {
ir_args.push(self.value_of(arg));
}
}
self.bl.ins().return_(&ir_args);
self.close_block(block);
self.emit_node(node.outputs[0], block);
Err(self.block_of(block))
}
Kind::Entry => {
for &o in node.outputs.iter().rev() {
self.emit_node(o, nid);
}
return;
}
Kind::Then | Kind::Else => {
for &o in node.outputs.iter().rev() {
self.emit_node(o, block);
}
Err(self.block_of(block))
}
Kind::Call { func, unreachable, args } => {
assert_ne!(func, hbty::Func::ECA, "@eca is not supported");
if unreachable {
todo!()
} else {
let mut arg_lens = vec![];
let mut signature = cir::Signature::new(self.isa.default_call_conv());
let stack_ret = build_signature(
self.isa.default_call_conv(),
self.tys.ins.funcs[func].sig,
self.tys,
&mut signature,
&mut arg_lens,
);
let func_ref =
'b: {
let user_name_ref = self.bl.func.declare_imported_user_function(
cir::UserExternalName { namespace: 0, index: func.index() as _ },
);
if let Some(id) = self.bl.func.dfg.ext_funcs.keys().find(|&k| {
self.bl.func.dfg.ext_funcs[k].name
== cir::ExternalName::user(user_name_ref)
}) {
break 'b id;
}
let signature = self.bl.func.import_signature(signature.clone());
self.bl.func.import_function(cir::ExtFuncData {
name: cir::ExternalName::user(user_name_ref),
signature,
// somehow, this works
colocated: true, // !self.tys.ins.funcs[func].is_import,
})
};
let mut ir_args = vec![];
if stack_ret {
ir_args.push(self.value_of(*node.inputs.last().unwrap()));
}
let mut params = signature.params.as_slice();
let mut parama_len = arg_lens[1..].iter();
let mut typs = args.args();
let mut args = node.inputs[1..].iter();
while let Some(aty) = typs.next(self.tys) {
let hbty::Arg::Value(ty) = aty else { continue };
let abi_meta = parama_len.next().unwrap();
if abi_meta.arg_count == 0 {
continue;
}
let &arg = args.next().unwrap();
if !abi_meta.trough_mem && ty.is_aggregate(self.tys) {
let loc = params.take(..abi_meta.arg_count).unwrap();
assert!(loc.len() <= 2, "NEED handling");
let align = loc.iter().map(|&p| p.value_type.bytes()).max().unwrap();
let mut offset = 0i32;
let src = self.value_of(self.nodes[arg].inputs[1]);
debug_assert!(self.nodes[arg].kind == Kind::Load);
for &v in loc {
ir_args.push(self.bl.ins().load(
v.value_type,
MemFlags::new(),
src,
offset,
));
offset += align as i32;
}
} else {
let loc = params.take(..abi_meta.arg_count).unwrap();
debug_assert_eq!(loc.len(), 1);
ir_args.push(self.value_of(arg));
}
}
let inst = self.bl.ins().call(func_ref, &ir_args);
match *self.bl.inst_results(inst) {
[] => {}
[scala] => self.values[nid as usize] = Some(Ok(scala)),
[a, b] => {
assert!(!stack_ret);
let slot = self.value_of(*node.inputs.last().unwrap());
let loc = [a, b];
assert!(loc.len() <= 2, "NEED handling");
let align = loc
.iter()
.map(|&p| self.bl.func.dfg.value_type(p).bytes())
.max()
.unwrap();
let mut offset = 0i32;
for v in loc {
self.bl.ins().store(MemFlags::new(), v, slot, offset);
offset += align as i32;
}
}
_ => unimplemented!(),
}
for &o in node.outputs.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, block);
}
}
return;
}
}
Kind::CInt { value } if self.nodes[nid].ty.is_float() => {
Ok(match self.tys.size_of(self.nodes[nid].ty) {
4 => self.bl.ins().f32const(f64::from_bits(value as _) as f32),
8 => self.bl.ins().f64const(f64::from_bits(value as _)),
_ => unimplemented!(),
})
}
Kind::CInt { value } => Ok(self.bl.ins().iconst(
cir::Type::int(self.tys.size_of(node.ty) as u16 * 8).unwrap_or_else(|| {
panic!("{}", hbty::Display::new(self.tys, self.files, node.ty),)
}),
value,
)),
Kind::BinOp { op } => {
let &[_, lhs, rhs] = node.inputs.as_slice() else { unreachable!() };
let [lh, rh] = [self.value_of(lhs), self.value_of(rhs)];
let is_int_op = node.ty.is_integer()
|| node.ty.is_pointer()
|| (node.ty == hbty::Id::BOOL
&& (self.nodes[lhs].ty.is_integer()
|| node.ty.is_pointer()
|| self.nodes[lhs].ty == hbty::Id::BOOL));
let is_float_op = node.ty.is_float()
|| (node.ty == hbty::Id::BOOL && self.nodes[lhs].ty.is_float());
Ok(if is_int_op {
let signed = node.ty.is_signed();
match op {
TokenKind::Add => self.bl.ins().iadd(lh, rh),
TokenKind::Sub => self.bl.ins().isub(lh, rh),
TokenKind::Mul => self.bl.ins().imul(lh, rh),
TokenKind::Shl => self.bl.ins().ishl(lh, rh),
TokenKind::Xor => self.bl.ins().bxor(lh, rh),
TokenKind::Band => self.bl.ins().band(lh, rh),
TokenKind::Bor => self.bl.ins().bor(lh, rh),
TokenKind::Div if signed => self.bl.ins().sdiv(lh, rh),
TokenKind::Mod if signed => self.bl.ins().srem(lh, rh),
TokenKind::Shr if signed => self.bl.ins().sshr(lh, rh),
TokenKind::Div => self.bl.ins().udiv(lh, rh),
TokenKind::Mod => self.bl.ins().urem(lh, rh),
TokenKind::Shr => self.bl.ins().ushr(lh, rh),
TokenKind::Lt
| TokenKind::Gt
| TokenKind::Le
| TokenKind::Ge
| TokenKind::Eq
| TokenKind::Ne => self.bl.ins().icmp(op.to_int_cc(signed), lh, rh),
op => todo!("{op}"),
}
} else if is_float_op {
match op {
TokenKind::Add => self.bl.ins().fadd(lh, rh),
TokenKind::Sub => self.bl.ins().fsub(lh, rh),
TokenKind::Mul => self.bl.ins().fmul(lh, rh),
TokenKind::Div => self.bl.ins().fdiv(lh, rh),
TokenKind::Lt
| TokenKind::Gt
| TokenKind::Le
| TokenKind::Ge
| TokenKind::Eq
| TokenKind::Ne => self.bl.ins().fcmp(op.to_float_cc(), lh, rh),
op => todo!("{op}"),
}
} else {
todo!("{}", hbty::Display::new(self.tys, self.files, node.ty))
})
}
Kind::RetVal => Ok(self.value_of(node.inputs[0])),
Kind::UnOp { op } => {
let oper = self.value_of(node.inputs[1]);
let dst = node.ty;
let src = self
.tys
.inner_of(self.nodes[node.inputs[1]].ty)
.unwrap_or(self.nodes[node.inputs[1]].ty);
let dty = dst.to_clif(self.tys);
Ok(match op {
TokenKind::Sub => self.bl.ins().ineg(oper),
TokenKind::Not => self.bl.ins().bnot(oper),
TokenKind::Float if dst.is_float() && src.is_unsigned() => {
self.bl.ins().fcvt_from_uint(dty, oper)
}
TokenKind::Float if dst.is_float() && src.is_signed() => {
self.bl.ins().fcvt_from_sint(dty, oper)
}
TokenKind::Number if src.is_float() && dst.is_unsigned() => {
self.bl.ins().fcvt_to_uint(dty, oper)
}
TokenKind::Number
if src.is_signed() && (dst.is_integer() || dst.is_pointer()) =>
{
self.bl.ins().sextend(dty, oper)
}
TokenKind::Number
if (src.is_unsigned() || src == hbty::Id::BOOL)
&& (dst.is_integer() || dst.is_pointer()) =>
{
self.bl.ins().uextend(dty, oper)
}
TokenKind::Float if dst == hbty::Id::F64 && src.is_float() => {
self.bl.ins().fpromote(dty, oper)
}
TokenKind::Float if dst == hbty::Id::F32 && src.is_float() => {
self.bl.ins().fdemote(dty, oper)
}
_ => todo!(),
})
}
Kind::Stck => {
let slot = self.bl.create_sized_stack_slot(cir::StackSlotData {
kind: cir::StackSlotKind::ExplicitSlot,
size: self.tys.size_of(node.ty),
align_shift: self.tys.align_of(node.ty).ilog2() as _,
});
Ok(self.bl.ins().stack_addr(cir::types::I64, slot, 0))
}
Kind::Global { global } => {
let glob_ref = {
// already deduplicated by the SoN
let colocated = true;
let user_name_ref =
self.bl.func.declare_imported_user_function(cir::UserExternalName {
namespace: 1,
index: global.index() as u32,
});
self.bl.func.create_global_value(cir::GlobalValueData::Symbol {
name: cir::ExternalName::user(user_name_ref),
offset: cir::immediates::Imm64::new(0),
colocated,
tls: false,
})
};
Ok(self.bl.ins().global_value(cir::types::I64, glob_ref))
}
Kind::Load if node.ty.is_aggregate(self.tys) => return,
Kind::Load => {
let ptr = self.value_of(node.inputs[1]);
Ok(self.bl.ins().load(node.ty.to_clif(self.tys), MemFlags::new(), ptr, 0))
}
Kind::Stre if node.ty.is_aggregate(self.tys) => {
let src = self.value_of(self.nodes[node.inputs[1]].inputs[1]);
let dest = self.value_of(node.inputs[2]);
self.bl.emit_small_memory_copy(
self.isa.frontend_config(),
dest,
src,
self.tys.size_of(node.ty) as _,
self.tys.align_of(node.ty) as _,
self.tys.align_of(node.ty) as _,
false,
MemFlags::new(),
);
return;
}
Kind::Stre => {
let value = self.value_of(node.inputs[1]);
let ptr = self.value_of(node.inputs[2]);
self.bl.ins().store(MemFlags::new(), value, ptr, 0);
return;
}
Kind::End | Kind::Phi | Kind::Arg | Kind::Mem | Kind::Loops | Kind::Join => return,
Kind::Assert { .. } => unreachable!(),
});
}
}
trait ToCondcodes {
fn to_int_cc(self, signed: bool) -> condcodes::IntCC;
fn to_float_cc(self) -> condcodes::FloatCC;
}
impl ToCondcodes for TokenKind {
fn to_int_cc(self, signed: bool) -> condcodes::IntCC {
use condcodes::IntCC as ICC;
match self {
Self::Lt if signed => ICC::SignedLessThan,
Self::Gt if signed => ICC::SignedGreaterThan,
Self::Le if signed => ICC::SignedLessThanOrEqual,
Self::Ge if signed => ICC::SignedGreaterThanOrEqual,
Self::Lt => ICC::UnsignedLessThan,
Self::Gt => ICC::UnsignedGreaterThan,
Self::Le => ICC::UnsignedLessThanOrEqual,
Self::Ge => ICC::UnsignedGreaterThanOrEqual,
Self::Eq => ICC::Equal,
Self::Ne => ICC::NotEqual,
_ => unreachable!(),
}
}
fn to_float_cc(self) -> condcodes::FloatCC {
use condcodes::FloatCC as FCC;
match self {
Self::Lt => FCC::LessThan,
Self::Gt => FCC::GreaterThan,
Self::Le => FCC::LessThanOrEqual,
Self::Ge => FCC::GreaterThanOrEqual,
Self::Eq => FCC::Equal,
Self::Ne => FCC::NotEqual,
_ => unreachable!(),
}
}
}
trait ToClifTy {
fn to_clif(self, cx: &hbty::Types) -> cir::Type;
}
impl ToClifTy for hbty::Id {
fn to_clif(self, cx: &hbty::Types) -> cir::Type {
debug_assert!(!self.is_aggregate(cx));
if self.is_integer() | self.is_pointer() | self.is_optional() || self == hbty::Id::BOOL {
cir::Type::int(cx.size_of(self) as u16 * 8).unwrap()
} else if self == hbty::Id::F32 {
cir::types::F32
} else if self == hbty::Id::F64 {
cir::types::F64
} else {
unimplemented!("{:?}", self)
}
}
}
#[derive(Default)]
struct Global {
module_id: Option<cm::DataId>,
}
#[derive(Default)]
struct FuncHeaders {
module_id: Option<cm::FuncId>,
alignment: u32,
code: Range<u32>,
relocs: Range<u32>,
external_names: Range<u32>,
}
#[derive(Default)]
struct Functions {
headers: EntVec<hbty::Func, FuncHeaders>,
code: Vec<u8>,
relocs: Vec<FinalizedMachReloc>,
external_names: Vec<UserExternalName>,
}
impl Functions {
fn push(&mut self, id: hbty::Func, func: &cir::Function, code: &MachBufferFinalized<Final>) {
self.headers.shadow(id.index() + 1);
self.headers[id] = FuncHeaders {
module_id: None,
alignment: code.alignment,
code: self.code.len() as u32..self.code.len() as u32 + code.data().len() as u32,
relocs: self.relocs.len() as u32..self.relocs.len() as u32 + code.relocs().len() as u32,
external_names: self.external_names.len() as u32
..self.external_names.len() as u32 + func.params.user_named_funcs().len() as u32,
};
self.code.extend(code.data());
self.relocs.extend(code.relocs().iter().cloned());
self.external_names.extend(func.params.user_named_funcs().values().cloned());
}
}
#[derive(Default)]
struct Assembler {
name: String,
frontier: Vec<hbty::Id>,
globals: Vec<hbty::Global>,
funcs: Vec<hbty::Func>,
}
#[derive(Debug)]
pub enum BackendCreationError {
UnsupportedTriplet(LookupError),
InvalidFlags(CodegenError),
UnsupportedModuleConfig(ModuleError),
InvalidFlag { key: String, value: String, err: SetError },
}
impl Display for BackendCreationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
BackendCreationError::UnsupportedTriplet(err) => {
write!(f, "Unsupported triplet: {}", err)
}
BackendCreationError::InvalidFlags(err) => {
write!(f, "Invalid flags: {}", err)
}
BackendCreationError::UnsupportedModuleConfig(err) => {
write!(f, "Unsupported module configuration: {}", err)
}
BackendCreationError::InvalidFlag { key, value, err } => {
write!(
f,
"Problem setting a '{key}' to '{value}': {err}\navailable flags: {}",
cc::settings::Flags::new(cc::settings::builder())
)
}
}
}
}
impl core::error::Error for BackendCreationError {}
impl From<LookupError> for BackendCreationError {
fn from(value: LookupError) -> Self {
Self::UnsupportedTriplet(value)
}
}
impl From<CodegenError> for BackendCreationError {
fn from(value: CodegenError) -> Self {
Self::InvalidFlags(value)
}
}
impl From<ModuleError> for BackendCreationError {
fn from(value: ModuleError) -> Self {
Self::UnsupportedModuleConfig(value)
}
}

View file

@ -0,0 +1,310 @@
// The classification code for the x86_64 ABI is taken from the clay language
// https://github.com/jckarter/clay/blob/db0bd2702ab0b6e48965cd85f8859bbd5f60e48e/compiler/externals.cpp
use {crate::AbiMeta, hblang::ty};
pub fn build_systemv_signature(
sig: hblang::ty::Sig,
types: &hblang::ty::Types,
signature: &mut cranelift_codegen::ir::Signature,
arg_lens: &mut Vec<AbiMeta>,
) -> bool {
let mut alloca = Alloca::new();
alloca.next(false, sig.ret, types, &mut signature.returns);
let stack_ret = signature.returns.len() == 1
&& signature.returns[0].purpose == cranelift_codegen::ir::ArgumentPurpose::StructReturn;
if stack_ret {
signature.params.append(&mut signature.returns);
arg_lens.push(AbiMeta { arg_count: signature.params.len(), trough_mem: true });
} else {
arg_lens.push(AbiMeta { arg_count: signature.returns.len(), trough_mem: false });
}
let mut args = sig.args.args();
while let Some(arg) = args.next_value(types) {
let prev = signature.params.len();
let trough_mem = alloca.next(true, arg, types, &mut signature.params);
arg_lens.push(AbiMeta { arg_count: signature.params.len() - prev, trough_mem });
}
stack_ret
}
/// Classification of "eightbyte" components.
// N.B., the order of the variants is from general to specific,
// such that `unify(a, b)` is the "smaller" of `a` and `b`.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)]
enum Class {
Int,
Sse,
SseUp,
}
#[derive(Clone, Copy, Debug)]
struct Memory;
// Currently supported vector size (AVX-512).
const LARGEST_VECTOR_SIZE: usize = 512;
const MAX_EIGHTBYTES: usize = LARGEST_VECTOR_SIZE / 64;
fn classify_arg(
cx: &hblang::ty::Types,
arg: hblang::ty::Id,
) -> Result<[Option<Class>; MAX_EIGHTBYTES], Memory> {
fn classify(
cx: &hblang::ty::Types,
layout: hblang::ty::Id,
cls: &mut [Option<Class>],
off: hblang::ty::Offset,
) -> Result<(), Memory> {
let size = cx.size_of(layout);
if off & (cx.align_of(layout) - 1) != 0 {
if size != 0 {
return Err(Memory);
}
return Ok(());
}
let mut c = match layout.expand() {
_ if size == 0 => return Ok(()),
_ if layout.is_integer() || layout.is_pointer() || layout == ty::Id::BOOL => Class::Int,
_ if layout.is_float() => Class::Sse,
hblang::ty::Kind::Struct(s) => {
for (f, foff) in hblang::ty::OffsetIter::new(s, cx).into_iter(cx) {
classify(cx, f.ty, cls, off + foff)?;
}
return Ok(());
}
hblang::ty::Kind::Tuple(tuple) => {
for (&ty, foff) in hblang::ty::OffsetIter::new(tuple, cx).into_iter(cx) {
classify(cx, ty, cls, off + foff)?;
}
return Ok(());
}
hblang::ty::Kind::Enum(_) => Class::Int,
hblang::ty::Kind::Union(union) => {
for f in cx.union_fields(union) {
classify(cx, f.ty, cls, off)?;
}
return Ok(());
}
hblang::ty::Kind::Slice(slice) if let Some(len) = cx.ins.slices[slice].len() => {
for i in 0..len as u32 {
classify(
cx,
cx.ins.slices[slice].elem,
cls,
off + i * cx.size_of(cx.ins.slices[slice].elem),
)?;
}
return Ok(());
}
hblang::ty::Kind::Slice(_) => {
classify(cx, hblang::ty::Id::UINT, cls, off)?;
classify(cx, hblang::ty::Id::UINT, cls, off + 8)?;
return Ok(());
}
hblang::ty::Kind::Opt(opt) => {
let base = cx.ins.opts[opt].base;
if cx.nieche_of(base).is_some() {
classify(cx, base, cls, off)?;
} else {
classify(cx, hblang::ty::Id::BOOL, cls, off)?;
classify(cx, base, cls, off + cx.align_of(base))?;
}
return Ok(());
}
ty => unimplemented!("{ty:?}"),
};
// Fill in `cls` for scalars (Int/Sse) and vectors (Sse).
let first = (off / 8) as usize;
let last = ((off + size - 1) / 8) as usize;
for cls in &mut cls[first..=last] {
*cls = Some(cls.map_or(c, |old| old.min(c)));
// Everything after the first Sse "eightbyte"
// component is the upper half of a register.
if c == Class::Sse {
c = Class::SseUp;
}
}
Ok(())
}
let size = cx.size_of(arg);
let n = ((size + 7) / 8) as usize;
if n > MAX_EIGHTBYTES {
return Err(Memory);
}
let mut cls = [None; MAX_EIGHTBYTES];
classify(cx, arg, &mut cls, 0)?;
if n > 2 {
if cls[0] != Some(Class::Sse) {
return Err(Memory);
}
if cls[1..n].iter().any(|&c| c != Some(Class::SseUp)) {
return Err(Memory);
}
} else {
let mut i = 0;
while i < n {
if cls[i] == Some(Class::SseUp) {
cls[i] = Some(Class::Sse);
} else if cls[i] == Some(Class::Sse) {
i += 1;
while i != n && cls[i] == Some(Class::SseUp) {
i += 1;
}
} else {
i += 1;
}
}
}
Ok(cls)
}
fn reg_component(
cls: &[Option<Class>],
i: &mut usize,
size: hblang::ty::Size,
) -> Option<cranelift_codegen::ir::Type> {
if *i >= cls.len() {
return None;
}
match cls[*i] {
None => None,
Some(Class::Int) => {
*i += 1;
Some(if size < 8 {
cranelift_codegen::ir::Type::int(size as u16 * 8).unwrap()
} else {
cranelift_codegen::ir::types::I64
})
}
Some(Class::Sse) => {
let vec_len =
1 + cls[*i + 1..].iter().take_while(|&&c| c == Some(Class::SseUp)).count();
*i += vec_len;
Some(if vec_len == 1 {
match size {
4 => cranelift_codegen::ir::types::F32,
_ => cranelift_codegen::ir::types::F64,
}
} else {
cranelift_codegen::ir::types::I64.by(vec_len as _).unwrap()
})
}
Some(c) => unreachable!("reg_component: unhandled class {:?}", c),
}
}
fn cast_target(
cls: &[Option<Class>],
size: hblang::ty::Size,
dest: &mut Vec<cranelift_codegen::ir::AbiParam>,
) {
let mut i = 0;
let lo = reg_component(cls, &mut i, size).unwrap();
let offset = 8 * (i as u32);
dest.push(cranelift_codegen::ir::AbiParam::new(lo));
if size > offset {
if let Some(hi) = reg_component(cls, &mut i, size - offset) {
dest.push(cranelift_codegen::ir::AbiParam::new(hi));
}
}
assert_eq!(reg_component(cls, &mut i, 0), None);
}
const MAX_INT_REGS: usize = 6; // RDI, RSI, RDX, RCX, R8, R9
const MAX_SSE_REGS: usize = 8; // XMM0-7
pub struct Alloca {
int_regs: usize,
sse_regs: usize,
}
impl Alloca {
pub fn new() -> Self {
Self { int_regs: MAX_INT_REGS, sse_regs: MAX_SSE_REGS }
}
pub fn next(
&mut self,
is_arg: bool,
arg: hblang::ty::Id,
cx: &hblang::ty::Types,
dest: &mut Vec<cranelift_codegen::ir::AbiParam>,
) -> bool {
if cx.size_of(arg) == 0 {
return false;
}
let mut cls_or_mem = classify_arg(cx, arg);
if is_arg {
if let Ok(cls) = cls_or_mem {
let mut needed_int = 0;
let mut needed_sse = 0;
for c in cls {
match c {
Some(Class::Int) => needed_int += 1,
Some(Class::Sse) => needed_sse += 1,
_ => {}
}
}
match (self.int_regs.checked_sub(needed_int), self.sse_regs.checked_sub(needed_sse))
{
(Some(left_int), Some(left_sse)) => {
self.int_regs = left_int;
self.sse_regs = left_sse;
}
_ => {
// Not enough registers for this argument, so it will be
// passed on the stack, but we only mark aggregates
// explicitly as indirect `byval` arguments, as LLVM will
// automatically put immediates on the stack itself.
if arg.is_aggregate(cx) {
cls_or_mem = Err(Memory);
}
}
}
}
}
match cls_or_mem {
Err(Memory) => {
if is_arg {
dest.push(cranelift_codegen::ir::AbiParam::new(
cranelift_codegen::ir::types::I64,
));
} else {
dest.push(cranelift_codegen::ir::AbiParam::special(
cranelift_codegen::ir::types::I64,
cranelift_codegen::ir::ArgumentPurpose::StructReturn,
));
}
true
}
Ok(ref cls) => {
// split into sized chunks passed individually
if arg.is_aggregate(cx) {
cast_target(cls, cx.size_of(arg), dest);
} else {
dest.push(cranelift_codegen::ir::AbiParam::new(
reg_component(cls, &mut 0, cx.size_of(arg)).unwrap(),
));
}
false
}
}
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M480-320 280-520l56-58 104 104v-326h80v326l104-104 56 58-200 200ZM240-160q-33 0-56.5-23.5T160-240v-120h80v120h480v-120h80v120q0 33-23.5 56.5T720-160H240Z"/></svg>

After

(image error) Size: 279 B

1
depell/src/icons/run.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M320-200v-560l440 280-440 280Zm80-280Zm0 134 210-134-210-134v268Z"/></svg>

After

(image error) Size: 190 B

View file

@ -1,16 +1,15 @@
* {
font-family: var(--font);
line-height: 1.3;
}
body {
--primary: white;
--secondary: #EFEFEF;
--timestamp: #777777;
--primary: light-dark(white, #181A1B);
--secondary: light-dark(#EFEFEF, #212425);
--timestamp: light-dark(#555555, #AAAAAA);
--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;
@ -36,6 +35,11 @@ body {
}
div.preview {
margin: var(--small-gap) 0px;
display: flex;
flex-direction: column;
gap: var(--small-gap);
div.info {
display: flex;
gap: var(--small-gap);
@ -45,10 +49,32 @@ div.preview {
}
}
div.stats {
div.stat {
display: flex;
gap: var(--small-gap);
svg {
height: 18px;
}
}
div.code {
position: relative;
nav {
position: absolute;
right: 0;
padding: var(--small-gap);
button {
display: flex;
padding: 0;
}
}
}
}
svg {
fill: black;
}
form {
@ -83,6 +109,8 @@ pre {
font-family: var(--monospace);
tab-size: 4;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
input {
@ -108,6 +136,11 @@ button:hover:not(:active) {
background: var(--primary);
}
code {
font-family: var(--monospace);
line-height: 1;
}
div#code-editor {
display: flex;
position: relative;
@ -141,3 +174,40 @@ div#dep-list {
}
}
}
.syn {
font-family: var(--monospace);
&.Comment {
color: #939f91;
}
&.Keyword {
color: #f85552;
}
&.Identifier,
&.Directive {
color: #3a94c5;
}
/* &.Number {} */
&.String {
color: #8da101;
}
&.Op,
&.Assign {
color: #f57d26;
}
&.Paren,
&.Bracket,
&.Comma,
&.Dot,
&.Ctor,
&.Colon {
color: light-dark(#5c6a72, #999999);
}
}

View file

@ -14,7 +14,7 @@ const stack_pointer_offset = 1 << 20;
/** @param {WebAssembly.Instance} instance @param {Post[]} packages @param {number} fuel
* @returns {string} */
function compileCode(instance, packages, fuel) {
function compileCode(instance, packages, fuel = 100) {
let {
INPUT, INPUT_LEN,
LOG_MESSAGES, LOG_MESSAGES_LEN,
@ -34,7 +34,7 @@ function compileCode(instance, packages, fuel) {
new DataView(memory.buffer).setUint32(INPUT_LEN.value, codeLength, true);
runWasmFunction(instance, compile_and_run, fuel);
return bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN);
return bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN).trim();
}
/**@type{WebAssembly.Instance}*/ let fmtInstance;
@ -44,23 +44,25 @@ async function getFmtInstance() {
return fmtInstance ??= (await fmtInstaceFuture).instance;
}
/** @param {WebAssembly.Instance} instance @param {string} code @param {"fmt" | "minify"} action
* @returns {string | undefined} */
/** @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, minify
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
&& typeof fmt === "function"
&& typeof minify === "function"
&& funs.hasOwnProperty(action)
&& typeof fun === "function"
)) never();
if (action !== "fmt") {
@ -72,8 +74,14 @@ function modifyCode(instance, code, action) {
dw.setUint32(INPUT_LEN.value, code.length, true);
new Uint8Array(memory.buffer, INPUT.value).set(new TextEncoder().encode(code));
return runWasmFunction(instance, action === "fmt" ? fmt : minify) ?
bufToString(memory, OUTPUT, OUTPUT_LEN) : undefined;
if (!runWasmFunction(instance, fun)) {
return undefined;
}
if (action === "tok") {
return bufSlice(memory, OUTPUT, OUTPUT_LEN);
} else {
return bufToString(memory, OUTPUT, OUTPUT_LEN);
}
}
@ -119,6 +127,15 @@ function packPosts(posts, view) {
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
@ -141,12 +158,13 @@ function wireUp(target) {
const importRe = /@use\s*\(\s*"(([^"]|\\")+)"\s*\)/g;
/** @param {string} code
/** @param {WebAssembly.Instance} fmt
* @param {string} code
* @param {string[]} roots
* @param {Post[]} buf
* @param {Set<string>} prevRoots
* @returns {void} */
function loadCachedPackages(code, roots, buf, prevRoots) {
function loadCachedPackages(fmt, code, roots, buf, prevRoots) {
buf[0].code = code;
roots.length = 0;
@ -162,7 +180,10 @@ function loadCachedPackages(code, roots, buf, prevRoots) {
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() });
const fmtd = modifyCode(fmt, localStorage.getItem("package-" + imp) ?? never(), "fmt");
if (typeof fmtd != "string") never();
buf.push({ path: imp, code: fmtd });
for (const match of buf[buf.length - 1].code.matchAll(importRe)) {
roots.push(match[1]);
}
@ -170,6 +191,61 @@ function loadCachedPackages(code, roots, buf, prevRoots) {
}
/**@type{Set<string>}*/ const prevRoots = new Set();
/**@typedef {Object} PackageCtx
* @property {AbortController} [cancelation]
* @property {string[]} keyBuf
* @property {Set<string>} prevParams
* @property {HTMLTextAreaElement} [edit] */
/** @param {string} source @param {Set<string>} importDiff @param {HTMLPreElement} errors @param {PackageCtx} ctx */
async function fetchPackages(source, importDiff, errors, ctx) {
importDiff.clear();
for (const match of source.matchAll(importRe)) {
if (localStorage["package-" + match[1]]) continue;
importDiff.add(match[1]);
}
if (importDiff.size !== 0 && (ctx.prevParams.size != importDiff.size
|| [...ctx.prevParams.keys()].every(e => importDiff.has(e)))) {
if (ctx.cancelation) ctx.cancelation.abort();
ctx.prevParams.clear();
ctx.prevParams = new Set([...importDiff]);
ctx.cancelation = new AbortController();
ctx.keyBuf.length = 0;
ctx.keyBuf.push(...importDiff.keys());
errors.textContent = "fetching: " + ctx.keyBuf.join(", ");
await fetch(`/code`, {
method: "POST",
signal: ctx.cancelation.signal,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(ctx.keyBuf),
}).then(async e => {
try {
const json = await e.json();
if (e.status == 200) {
for (const [key, value] of Object.entries(json)) {
localStorage["package-" + key] = value;
}
const missing = ctx.keyBuf.filter(i => json[i] === undefined);
if (missing.length !== 0) {
errors.textContent = "deps not found: " + missing.join(", ");
} else {
ctx.cancelation = undefined;
ctx.edit?.dispatchEvent(new InputEvent("input"));
}
}
} catch (er) {
errors.textContent = "completely failed to fetch ("
+ e.status + "): " + ctx.keyBuf.join(", ");
console.error(e, er);
}
});
}
}
/** @param {HTMLElement} target */
async function bindCodeEdit(target) {
@ -188,72 +264,29 @@ async function bindCodeEdit(target) {
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;
const ctx = { keyBuf: [], prevParams: new Set(), edit };
prevRoots.clear();
const onInput = () => {
importDiff.clear();
for (const match of edit.value.matchAll(importRe)) {
if (localStorage["package-" + match[1]]) continue;
importDiff.add(match[1]);
}
fetchPackages(edit.value, importDiff, errors, ctx);
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) {
if (ctx.cancelation && importDiff.size !== 0) {
return;
}
loadCachedPackages(edit.value, keyBuf, packages, prevRoots);
loadCachedPackages(fmt, edit.value, ctx.keyBuf, packages, prevRoots);
errors.textContent = compileCode(hbc, packages, 1);
errors.textContent = compileCode(hbc, packages);
const minified_size = modifyCode(fmt, edit.value, "minify")?.length;
if (minified_size) {
codeSize.textContent = (MAX_CODE_SIZE - minified_size) + "";
const perc = Math.min(100, Math.floor(100 * (minified_size / MAX_CODE_SIZE)));
codeSize.style.color = `color-mix(in srgb, white, var(--error) ${perc}%)`;
codeSize.style.color = `color-mix(in srgb, light-dark(black, white), var(--error) ${perc}%)`;
}
timeout = 0;
};
@ -265,19 +298,86 @@ async function bindCodeEdit(target) {
edit.dispatchEvent(new InputEvent("input"));
}
/** @type {{ [key: string]: (content: string) => Promise<string> | string }} */
/**
* @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: (content) => new Date(parseInt(content) * 1000).toLocaleString(),
fmt: (content) => getFmtInstance().then(i => modifyCode(i, content, "fmt") ?? "invalid code"),
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") return;
const codeBytes = new TextEncoder().encode(fmt);
const tok = modifyCode(instance, fmt, 'tok');
if (!(tok instanceof Uint8Array)) return;
target.innerHTML = '';
let start = 0;
let kind = tok[0];
for (let ii = 1; ii <= tok.length; ii += 1) {
// split over same tokens and buffer end
if (tok[ii] === kind && ii < tok.length) {
continue;
}
const text = decoder.decode(codeBytes.subarray(start, ii));
const textNode = document.createTextNode(text);;
if (kind === 0) {
target.appendChild(textNode);
} else {
const el = document.createElement('span');
el.classList.add('syn');
el.classList.add(TOK_CLASSES[kind]);
el.appendChild(textNode);
target.appendChild(el);
}
if (ii == tok.length) {
break;
}
start = ii;
kind = tok[ii];
}
}
/** @param {HTMLElement} target */
function execApply(target) {
const proises = [];
for (const elem of target.querySelectorAll('[apply]')) {
if (!(elem instanceof HTMLElement)) continue;
const funcname = elem.getAttribute('apply') ?? never();
let res = applyFns[funcname](elem.textContent ?? "");
if (res instanceof Promise) res.then(c => elem.textContent = c);
else elem.textContent = res;
const vl = applyFns[funcname](elem);
if (vl instanceof Promise) proises.push(vl);
}
if (target === document.body) {
Promise.all(proises).then(() => document.body.hidden = false);
}
}
@ -332,10 +432,13 @@ function cacheInputs(target) {
}
/** @param {string} [path] */
function updaetTab(path) {
function updateTab(path) {
console.log(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);
elem.disabled =
elem.getAttribute("hx-push-url") === path
|| elem.getAttribute("hx-push-url") === window.location.pathname;
}
}
@ -351,6 +454,7 @@ if (window.location.hostname === 'localhost') {
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);
}
@ -360,7 +464,7 @@ if (window.location.hostname === 'localhost') {
code: "main:=fn():int{return 42}",
}];
const res = compileCode(await getHbcInstance(), posts, 1) ?? never();
const expected = "exit code: 42\n";
const expected = "exit code: 42";
if (expected != res) console.error(expected, res);
}
})()
@ -370,8 +474,7 @@ 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);
updateTab(ev['detail'].pathInfo.finalRequestPath);
});
getFmtInstance().then(inst => {
@ -422,6 +525,30 @@ getFmtInstance().then(inst => {
Object.assign(window, { filterCodeDeps });
});
updaetTab();
/** @param {HTMLElement} target */
function runPost(target) {
while (!target.matches("div[class=preview]")) target = target.parentElement ?? never();
const code = target.querySelector("pre[apply=fmt]");
if (!(code instanceof HTMLPreElement)) never();
const output = target.querySelector("pre[id=compiler-output]");
if (!(output instanceof HTMLPreElement)) never();
Promise.all([getHbcInstance(), getFmtInstance()]).then(async ([hbc, fmt]) => {
const ctx = { keyBuf: [], prevParams: new Set() };
await fetchPackages(code.innerText ?? never(), new Set(), output, ctx);
const posts = [{ path: "this", code: "" }];
loadCachedPackages(fmt, code.innerText ?? never(), ctx.keyBuf, posts, new Set());
output.textContent = compileCode(hbc, posts);
output.hidden = false;
});
let author = encodeURIComponent(target.dataset.author ?? never());
let name = encodeURIComponent(target.dataset.name ?? never());
fetch(`/post/run?author=${author}&name=${name}`, { method: "POST" })
}
Object.assign(window, { runPost });
updateTab();
wireUp(document.body);

View file

@ -1,10 +1,10 @@
#![feature(iter_collect_into)]
#![feature(iter_collect_into, macro_metavar_expr)]
use {
argon2::{password_hash::SaltString, PasswordVerifier},
axum::{
body::Bytes,
extract::Path,
http::{header::COOKIE, request::Parts},
extract::{DefaultBodyLimit, Path},
http::{header::COOKIE, request::Parts, StatusCode},
response::{AppendHeaders, Html},
},
const_format::formatcp,
@ -52,22 +52,27 @@ async fn amain() {
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("/", get(Index::page))
.route("/index-view", get(Index::get_with_blog))
.route("/blogs/index-view", get(Index::get))
.route("/blogs/developing-hblang", get(DevelopingHblang::page))
.route("/blogs/developing-hblang-view", get(DevelopingHblang::get))
.route("/feed", get(Feed::page))
.route("/feed-view", get(Feed::get))
.route("/feed-more", post(Feed::more))
.route("/profile", get(Profile::page))
.route("/profile-view", get(Profile::get))
.route("/profile/:name", get(Profile::get_other_page))
.route("/profile/password", post(PasswordChange::post))
.route("/profile-view/:name", get(Profile::get_other))
.route("/post", get(Post::page))
.route("/post-view", get(Post::get))
.route("/post", post(Post::post))
.route("/post/run", post(Post::run))
.route("/code", post(fetch_code))
.route("/login", get(Login::page))
.route("/login-view", get(Login::get))
@ -85,7 +90,8 @@ async fn amain() {
.as_millis();
move || async move { id.to_string() }
}),
);
)
.layer(DefaultBodyLimit::max(16 * 1024));
#[cfg(feature = "tls")]
{
@ -196,12 +202,45 @@ impl Page for Feed {
}
}
#[derive(Default)]
struct Index;
macro_rules! decl_static_pages {
($(
#[derive(PublicPage)]
#[page(static = $file:literal)]
struct $name:ident;
)*) => {
const ALL_STATIC_PAGES: [&str; ${count($file)}] = [$($file),*];
impl PublicPage for Index {
fn render_to_buf(self, buf: &mut String) {
buf.push_str(include_str!("welcome-page.html"));
$(
#[derive(Default)]
struct $name;
impl PublicPage for $name {
fn render_to_buf(self, buf: &mut String) {
buf.push_str(include_str!(concat!("static-pages/", $file, ".html")));
}
async fn page(session: Option<Session>) -> Html<String> {
base(|s| blog_base(s, |s| Self::default().render_to_buf(s)), session.as_ref())
}
}
)*
};
}
decl_static_pages! {
#[derive(PublicPage)]
#[page(static = "welcome")]
struct Index;
#[derive(PublicPage)]
#[page(static = "developing-hblang")]
struct DevelopingHblang;
}
impl Index {
async fn get_with_blog() -> Html<String> {
let mut buf = String::new();
blog_base(&mut buf, |s| Index.render_to_buf(s));
Html(buf)
}
}
@ -239,11 +278,27 @@ impl Page for Post {
<input type="submit" value="submit">
<pre id="compiler-output"></pre>
</form>
!{include_str!("post-page.html")}
<div id="dep-list">
<input placeholder="search impoted deps.." oninput="filterCodeDeps(this, event)">
<section id="deps">
"results show here..."
</section>
</div>
<div>
!{include_str!("static-pages/post.html")}
</div>
}
}
}
#[derive(Deserialize)]
struct Run {
author: String,
name: String,
}
impl Post {
pub fn from_row(r: &rusqlite::Row) -> rusqlite::Result<Self> {
Ok(Post {
@ -251,10 +306,25 @@ impl Post {
name: r.get(1)?,
timestamp: r.get(2)?,
code: r.get(3)?,
imports: r.get(4)?,
runs: r.get(5)?,
..Default::default()
})
}
async fn run(
session: Session,
axum::extract::Query(run): axum::extract::Query<Run>,
) -> StatusCode {
match db::with(|qes| qes.creata_run.insert((run.name, run.author, session.name))) {
Ok(_) => StatusCode::OK,
Err(e) => {
log::error!("creating run record failed: {e}");
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
async fn post(
session: Session,
axum::Form(mut data): axum::Form<Self>,
@ -310,7 +380,7 @@ impl Post {
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">
write_html! { f <div class="preview" "data-author"=author "data-name"=name>
<div class="info">
<span>
<a "hx-get"={format_args!("/profile-view/{author}")} href="" "hx-target"="main"
@ -320,16 +390,21 @@ impl fmt::Display for Post {
name
</span>
<span apply="timestamp">timestamp</span>
</div>
<div class="stats">
for (name, count) in "inps runs deps".split(' ')
for (name, count) in [include_str!("icons/download.svg"), include_str!("icons/run.svg"), "deps"]
.iter()
.zip([imports, runs, dependencies])
.filter(|(_, &c)| c != 0)
{
name ": "<span>count</span>
<div class="stat">!name count</div>
}
</div>
<pre apply="fmt">code</pre>
<div class="code">
<nav>
<button onmousedown="runPost(this)">!{include_str!("icons/run.svg")}</button>
</nav>
<pre apply="fmt">code</pre>
</div>
<pre hidden id="compiler-output"></pre>
if *timestamp == 0 {
<button "hx-get"="/post" "hx-swap"="outerHTML"
"hx-target"="[preview]">"edit"</button>
@ -339,6 +414,70 @@ impl fmt::Display for Post {
}
}
#[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>,
@ -357,20 +496,24 @@ impl Profile {
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((self.other.as_ref().unwrap_or(&session.name),), Post::from_row)
.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)
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")}
}
})
}
@ -399,7 +542,7 @@ struct Login {
impl PublicPage for Login {
fn render_to_buf(self, buf: &mut String) {
let Login { name, password, error } = self;
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> }
@ -439,13 +582,12 @@ impl Login {
data.error = Some("invalid credentials");
}
Err(e) => {
log::error!("foo {e}");
log::error!("login queri failed: {e}");
data.error = Some("internal server error");
}
});
if data.error.is_some() {
log::error!("what {:?}", data);
Err(data.render())
} else {
Ok(AppendHeaders([
@ -534,6 +676,29 @@ impl Signup {
}
}
fn blog_base(s: &mut String, body: impl FnOnce(&mut String)) {
let nav_button = |f: &mut String, name: &str| {
write_html! {(f)
<button "hx-push-url"={format_args!("/blogs/{name}")}
"hx-get"={format_args!("/blogs/{name}-view")}
"hx-target"="main#blog"
"hx-swap"="innerHTML">name</button>
}
};
write_html! {(*s)
<nav><section>
<button "hx-push-url"="/" "hx-get"="/blogs/index-view"
"hx-target"="main#blog" "hx-swap"="innerHTML">"welcome"</button>
for name in &ALL_STATIC_PAGES[1..] {
|f|{nav_button(f, name)}
}
</section></nav>
<section id="post-form"></section>
<main id="blog">|f|{body(f)}</main>
}
}
fn base(body: impl FnOnce(&mut String), session: Option<&Session>) -> Html<String> {
let username = session.map(|s| &s.name);
@ -551,15 +716,17 @@ fn base(body: impl FnOnce(&mut String), session: Option<&Session>) -> Html<Strin
<html lang="en">
<head>
<meta name="charset" content="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="code dependency hell socila media hblang">
<link rel="stylesheet" href="/index.css">
<title>"depell"</title>
</head>
<body>
<body hidden>
<nav>
<button "hx-push-url"="/" "hx-get"="/index-view" "hx-target"="main" "hx-swap"="innerHTML">"depell"</button>
<section>
if let Some(username) = username {
<button "hx-push-url"="/profile" "hx-get"="/profile-view" "hx-target"="main"
<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>
@ -682,13 +849,58 @@ mod db {
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 < ?",
get_user_posts: "SELECT author, name, timestamp, code, (
WITH RECURSIVE roots(name, author, code) AS (
SELECT name, author, code FROM post WHERE name = outher.name AND author = outher.author
UNION
SELECT post.name, post.author, post.code FROM
post JOIN import ON post.name = import.from_name
AND post.author = import.from_author
JOIN roots ON import.to_name = roots.name
AND import.to_author = roots.author
) SELECT (count(*) - 1) FROM roots
) AS imports, (
WITH RECURSIVE roots(name, author, code) AS (
SELECT name, author, code FROM post WHERE name = outher.name AND author = outher.author
UNION
SELECT post.name, post.author, post.code FROM post
JOIN import ON post.name = import.from_name
AND post.author = import.from_author
JOIN roots ON import.to_name = roots.name
AND import.to_author = roots.author
) SELECT count(*) FROM roots
JOIN run ON roots.name = run.code_name
AND roots.author = run.code_author
) AS runs FROM post as outher WHERE author = ? ORDER BY timestamp DESC",
// TODO: we might want to cache the recursive queries
get_pots_before: "SELECT author, name, timestamp, code, (
WITH RECURSIVE roots(name, author, code) AS (
SELECT name, author, code FROM post WHERE name = outher.name AND author = outher.author
UNION
SELECT post.name, post.author, post.code FROM
post JOIN import ON post.name = import.from_name
AND post.author = import.from_author
JOIN roots ON import.to_name = roots.name
AND import.to_author = roots.author
) SELECT (count(*) - 1) FROM roots
) AS imports, (
WITH RECURSIVE roots(name, author, code) AS (
SELECT name, author, code FROM post WHERE name = outher.name AND author = outher.author
UNION
SELECT post.name, post.author, post.code FROM post
JOIN import ON post.name = import.from_name
AND post.author = import.from_author
JOIN roots ON import.to_name = roots.name
AND import.to_author = roots.author
) SELECT count(*) FROM roots
JOIN run ON roots.name = run.code_name
AND roots.author = run.code_author
) as runs FROM post AS outher WHERE timestamp < ?",
create_post: "INSERT INTO post (name, author, timestamp, code) VALUES(?, ?, ?, ?)",
fetch_deps: "
WITH RECURSIVE roots(name, author, code) AS (
@ -703,6 +915,7 @@ mod db {
",
create_import: "INSERT INTO import(to_author, to_name, from_author, from_name)
VALUES(?, ?, ?, ?)",
creata_run: "INSERT OR IGNORE INTO run(code_name, code_author, runner) VALUES(?, ?, ?)",
}
}
@ -729,8 +942,22 @@ mod db {
}
pub fn init() {
const SCHEMA_VERSION: usize = 0;
const MIGRATIONS: &[&str] = &[include_str!("migrations/1.sql")];
let db = rusqlite::Connection::open("db.sqlite").unwrap();
db.execute_batch(include_str!("schema.sql")).unwrap();
let schema_version =
db.pragma_query_value(None, "user_version", |v| v.get::<_, usize>(0)).unwrap();
if schema_version != SCHEMA_VERSION {
for &mig in &MIGRATIONS[schema_version..] {
db.execute_batch(mig).expect(mig);
}
db.pragma_update(None, "user_version", SCHEMA_VERSION).unwrap();
}
Queries::new(&db);
}
}

View file

@ -1,21 +0,0 @@
<div id="dep-list">
<input placeholder="search impoted deps.." oninput="filterCodeDeps(this, event)">
<section id="deps">
results show here...
</section>
</div>
<div>
<h3>About posting code</h3>
<p>
If you are unfammiliar with <a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a>, refer to the
<strong>hblang/README.md</strong> or
vizit <a href="/profile/mlokis">mlokis'es posts</a>. Preferably don't edit the code here.
</p>
<h3>Extra textarea features</h3>
<ul>
<li>proper tab behaviour</li>
<li>snap to previous tab boundary on "empty" lines</li>
</ul>

View file

@ -0,0 +1,61 @@
# The journey to an optimizing compiler
It's been years since I was continuously trying to make a compiler to implement language of my dreams. Problem was tho that I wanted something similar to Rust, which if you did not know, `rustc` far exceeded the one million lines of code mark some time ago, so implementing such language would take me years if not decades, but I still tired it.
Besides being extremely ambitions, the problem with my earliest attempts at making a compiler, is that literally nobody, not even me, was using the language, and so retroactively I am confident, what I implemented was a complex test-case implementation, and not a compiler. I often fall into a trap of implementing edge cases instead of an algorithm that would handle not only the very few thing the tests do but also all the other stuff that users of the language would try.
Another part of why I was failing for all that time, is that I did the hardest thing first without understanding the core concepts involved in translating written language to IR, god forbid assembly. I wasted a lot of time like this, but at least I learned Rust well. At some point I found a job where I started developing a decentralized network and that fully drawn me away from language development.
## Completely new approach
At some point the company I was working for started having financial issues and they were unable to pay me. During that period, I discovered that my love for networking was majorly fueled by the monetary gains associated with it. I burned out, and started to look for things to do with the free time.
One could say timing was perfect because [`ableos`](https://git.ablecorp.us/AbleOS/ableos) was desperately in need of a sane programming language that compiles to the home made VM ISA used for all software ran in `ableos`, but there was nobody crazy enough to do this. I got terribly nerd sniped, tho I don't regret it. Process of making a language for `ableos` was completely different. Firstly, it needed to be done asap, the lack of a good language blocked everyone form writing drivers for `ableos`, secondly, the moment the language is at least a little bit usable, people other then me will start using it, and lastly, the ISA the language compiles to very simple to emit, understand, and run.
### Urgency is a bliss
I actually managed to make the language somewhat work in one week, mainly because my mind set changed. I no longer spent a lot of time designing syntax for elegance, I designed it so that it incredibly easy to parse, meaning I can spent minimal effort implementing the parser, and fully focus on the hard problem of translating AST to instructions. Surprisingly, making everything an expression and not enforcing any arbitrary rules, makes the code you can write incredibly flexible and (most) people love it. One of the decisions I made to save time (or maybe it was an accident) was to make `,;` not enforced, meaning, you are allowed to write delimiters in lists but, as long as it does not change the intent of the code, you can leave them out. In practice, you actually don't need semicolons, unless the next line starts with something sticky like `*x`, int that case you put a semicolon on the previous line to tell the parser where the current expression ends.
### Only the problem I care about
Its good to note that writing a parser is no longer interesting for me. I wrote many parsers before and writing one no longer feel rewarding, but more like a chore. The real problem I was excited about was translating AST to instructions, I always ended up overcomplicating this step wit edge cases for every possible scenario that can happen in code, for which there are infinite. But why did I succeed this time? Well all the friction related to getting something that I can execute was so low, I could iterate quickly and realize what I am doing wrong before I burn out. In a week I managed to understand what I was failing to do for years, partly because of all the previous suffering, but mainly because it was so easy to pivot and try new things. And so I managed to make my first single pass compiler, and people immediately started using it.
### Don't implement features nobody asked for
Immediately after someone else then me wrote something in `hb` stuff started breaking, over the course of a month I kept fixing bugs and adding new features just fine, and more people started to use the language. All was good and well until I looked into the code. It was incredibly cursed, full of tricks to work around the compiler not doing any optimizations. At that moment I realized the whole compiler after parser needs to be rewritten, I had to implement optimizations, otherwise people wont be able to write readable code that runs fast. All of the features I have added up until now, were a technical dept now. Unless they are all working with optimizations, can't compile the existing code. Yes, if feature exists, be sure as hell it will be used.
It took around 4 months to reimplement everything make make the optimal code look like what you are used to in other languages. I am really thankful for [sea of nodes](https://github.com/SeaOfNodes), and all the amazing work Cliff Click and others do to make demystify optimizers, It would have taken much longer to for me to figure all the principles out without the exhaustive [tutorial](https://github.com/SeaOfNodes/Simple?tab=readme-ov-file).
## How my understanding of optimizations changed
### Optimizations allow us to scale software
I need to admit, before writing a single pass compiler and later upgrading it to optimizing one, I thought optimizations only affect the quality of final assembly emitted by the compiler. It never occur to me that what the optimizations actually do, is reduce the impact of how you decide to write the code. In a single pass compiler (with zero optimizations), the machine code reflects:
- order of operations as written in code
- whether the value was stored in intermediate locations
- exact structure of the control flow and at which point the operations are placed
- how many times is something recomputed
- operations that only help to convey intent for the reader of the source code
- and more I can't think of...
If you took some code you wrote and then modified it to obfuscate these aspects (in reference to the original code), you would to a subset of what optimizing compiler does. Of course, a good compiler would try hard to improve the metrics its optimizing for, it would:
- reorder operations to allow the CPU to parallelize them
- remove needless stores, or store values directly to places you cant express in code
- pull operations out of the loops and into the branches (if it can)
- find all common sub-expressions and compute them only once
- fold constants as much as possible and use obscure tricks to replace slow instructions if any of the operands are constant
- and more...
In the end, compiler optimizations try to reduce correlation between how the code happens to be written and how well it performs, which is extremely important when you want humans to read the code.
### Optimizing compilers know more then you
Optimizing code is a search problem, an optimizer searches the code for patterns that can be rewritten so something more practical for the computer, while preserving the observable behavior of the program. This means it needs enough context about the code to not make a mistake. In fact, the optimizer has so much context, it is able to determine your code is useless. But wait, didn't you write the code because you needed it to do something? Maybe your intention was to break out of the loop after you are done, but the optimizer looked at the code and said, "great, we are so lucky that this integer is always small enough to miss this check by one, DELETE", and then he goes "jackpot, since this loop is now infinite, we don't need this code after it, DELETE". Notice that the optimizer is eager to delete dead code, it did not ask you "Brah, why did you place all your code after an infinite loop?". This is just an example, there are many more cases where modern optimizers just delete all your code because they proven it does something invalid without running it.
Its stupid but its the world we live in, optimizers are usually a black box you import and feed it the code in a format they understand, they then proceed to optimize it, and if they find a glaring bug they wont tell you, god forbid, they will just molest the code in unspecified ways and spit out whats left. Before writing an optimizer, I did no know this can happen and I did not know this is a problem I pay for with my time, spent figuring out why noting is happening when I run the program.
But wait its worse! Since optimizers wont ever share the fact you are stupid, we end up with other people painstakingly writing complex linters, that will do a shitty job detecting things that matter, and instead whine about style and other bullcrap (and they suck even at that). If the people who write linters and people who write optimizers swapped the roles, I would be ranting about optimizers instead.
And so, this is the area where I want to innovate, lets report the dead code to the frontend, and let the compiler frontend filter out the noise and show relevant information in the diagnostics. Refuse to compile the program if you `i /= 0`. Refuse to compile if you `arr[arr.len]`. This is the level of stupid optimizer sees, once it normalizes your code, but proceeds to protect your feelings. My goal so for hblang to relay this to you as much as possible. If we can query for optimizations, we can query for bugs too.

View file

@ -0,0 +1,8 @@
### About posting code
If you are unfammiliar with [hblang](https://git.ablecorp.us/AbleOS/holey-bytes), refer to the **hblang/README.md** or vizit [mlokis'es posts](/profile/mlokis). Preferably don't edit the code here.
### Extra textarea features
- proper tab behaviour
- snap to previous tab boundary on "empty" lines

View file

@ -0,0 +1,11 @@
## Welcome to depell
Depell (dependency hell) is a simple "social" media site, except that all you can post is [hblang](https://git.ablecorp.us/AbleOS/holey-bytes) code. Instead of likes you run the program, and instead of mentions you import the program as dependency. Run counts even when ran indirectly.
The backend only serves the code and frontend compiles and runs it locally. All posts are immutable.
## Security?
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.

View file

@ -1,17 +0,0 @@
<h1>Welcome to depell</h1>
<p>
Depell (dependency hell) is a simple "social" media site best compared to twitter, except that all you can post is
<a href="https://git.ablecorp.us/AbleOS/holey-bytes">hblang</a> code with no comments allowed. Instead of likes you
run the program, and instead of retweets you import the program as dependency. Run counts even when ran indirectly.
</p>
<p>
The backend only serves the code and frontend compiles and runs it locally. All posts are immutable.
</p>
<h2>Security?</h2>
<p>
All code runs in WASM (inside a holey-bytes VM until hblang compiles to wasm) and is controlled by JavaScript. WASM
cant do any form of IO without going trough JavaScript so as long as JS import does not allow wasm to execute
arbitrary JS code, WASM can act as a container inside the JS.
</p>

View file

@ -27,8 +27,16 @@ unsafe extern "C" fn fmt() {
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);
let code = core::str::from_raw_parts_mut(
core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
OUTPUT_LEN = fmt::minify(code);
}

View file

@ -4,9 +4,12 @@
use {
alloc::{string::String, vec::Vec},
core::ffi::CStr,
hblang::{
parser::FileId,
son::{hbvm::HbvmBackend, Codegen, CodegenCtx},
backend::hbvm::HbvmBackend,
son::{Codegen, CodegenCtx},
ty::Module,
utils::Ent,
},
};
@ -60,7 +63,7 @@ unsafe fn compile_and_run(mut fuel: usize) {
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() as FileId),
hblang::parser::FileKind::Module => Ok(paths.binary_search(&path).unwrap()),
hblang::parser::FileKind::Embed => Err("embeds are not supported".into()),
};
files
@ -79,7 +82,7 @@ unsafe fn compile_and_run(mut fuel: usize) {
let mut ct = {
let mut backend = HbvmBackend::default();
Codegen::new(&mut backend, &files, &mut ctx).generate(root as FileId);
Codegen::new(&mut backend, &files, &mut ctx).generate(Module::new(root));
if !ctx.parser.errors.borrow().is_empty() {
log::error!("{}", ctx.parser.errors.borrow());
@ -97,8 +100,15 @@ unsafe fn compile_and_run(mut fuel: usize) {
break;
}
Ok(hbvm::VmRunOk::Ecall) => {
let unknown = ct.vm.read_reg(2).0;
log::error!("unknown ecall: {unknown}")
let kind = ct.vm.read_reg(2).0;
match kind {
0 => {
let str = ct.vm.read_reg(3).0;
let str = unsafe { CStr::from_ptr(str as _) };
log::error!("{}", str.to_str().unwrap());
}
unknown => log::error!("unknown ecall: {unknown}"),
}
}
Ok(hbvm::VmRunOk::Timer) => {
fuel -= 1;

47
examples/raylib/main.hb Normal file
View file

@ -0,0 +1,47 @@
InitWindow := fn(w: uint, h: uint, name: ^u8): uint @import()
WindowShouldClose := fn(): bool @import()
BeginDrawing := fn(): void @import()
EndDrawing := fn(): void @import()
DrawRectangleV := fn(pos: Vec2, size: Vec2, color: Color): void @import()
DrawRectangle := fn(a: uint, b: uint, c: uint, d: uint, color: Color): void @import()
ClearBackground := fn(color: Color): void @import()
SetTargetFPS := fn(target: uint): void @import()
GetFrameTime := fn(): f32 @import()
Vec2 := struct {x: f32, y: f32}
Color := struct {r: u8, g: u8, b: u8, a: u8}
$W := 800
$H := 600
main := fn(): uint {
_ = InitWindow(W, H, "whawee\0".ptr)
SetTargetFPS(60)
pos := Vec2.(100, 100)
vel := Vec2.(300, 300)
size := Vec2.(100, 100)
color := Color.(17, 255, 17, 255)
loop if WindowShouldClose() break else {
BeginDrawing()
ClearBackground(.(0, 0, 0, 255))
DrawRectangleV(pos, size, color)
pos += vel * .(GetFrameTime(), GetFrameTime())
if pos.x < 0 | pos.x + size.x > W {
vel.x *= -1
color += .(32, 11, 20, 0)
}
if pos.y < 0 | pos.y + size.y > H {
vel.y *= -1
color += .(32, 11, 20, 0)
}
EndDrawing()
}
return 0
}

4
examples/raylib/run.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
DIR=$(dirname $0)
cd $DIR
cargo run -p hbc main.hb > out.o && gcc -o main out.o -lraylib -lm -ldl -lpthread -lrt -lGL -lX11 && ./main

View file

@ -3,26 +3,17 @@ name = "hblang"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "hbc"
path = "src/main.rs"
[[bin]]
name = "fuzz"
path = "src/fuzz_main.rs"
[dependencies]
hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry", "allocator-api2"] }
hbbytecode = { workspace = true, features = ["disasm"] }
hbvm = { workspace = true, features = ["nightly"] }
hbvm = { workspace = true, features = ["nightly", "alloc"] }
hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry"] }
log = "0.4.22"
[dependencies.regalloc2]
git = "https://github.com/jakubDoka/regalloc2"
branch = "reuse-allocations"
default-features = false
[features]
default = ["std", "regalloc2/trace-log"]
default = ["std"]
std = []
no_log = ["log/max_level_off"]

File diff suppressed because one or more lines are too long

35
lang/build.rs Normal file
View file

@ -0,0 +1,35 @@
use std::{fmt::Write, iter};
fn main() {
const TEST_FILE: &str = "src/testcases.rs";
const INPUT: &str = include_str!("./README.md");
let mut out = String::new();
for (name, code) in block_iter(INPUT) {
let name = name.replace(' ', "_");
_ = writeln!(
out,
"#[test] fn {name}() {{ run_codegen_test(\"{name}\", r##\"{code}\"##) }}"
);
}
std::fs::write(TEST_FILE, out).unwrap();
}
fn block_iter(mut input: &str) -> impl Iterator<Item = (&str, &str)> {
const CASE_PREFIX: &str = "#### ";
const CASE_SUFFIX: &str = "\n```hb";
iter::from_fn(move || loop {
let pos = input.find(CASE_PREFIX)?;
input = unsafe { input.get_unchecked(pos + CASE_PREFIX.len()..) };
let Some((test_name, rest)) = input.split_once(CASE_SUFFIX) else { continue };
if !test_name.chars().all(|c| c.is_alphanumeric() || c == '_') {
continue;
}
input = rest;
let (body, rest) = input.split_once("```").unwrap_or((input, ""));
input = rest;
break Some((test_name, body));
})
}

View file

@ -1,4 +0,0 @@
--fmt - format all imported source files
--fmt-stdout - dont write the formatted file but print it
--dump-asm - output assembly instead of raw code, (the assembly is more for debugging the compiler)
--threads <1...> - number of extra threads compiler can use [default: 0]

View file

@ -1,19 +1,66 @@
use {
super::{AssemblySpec, Backend, Nid, Node, Nodes},
super::{AssemblySpec, Backend},
crate::{
lexer::TokenKind,
parser, reg,
son::{debug_assert_matches, write_reloc, Kind, MEM},
ty::{self, Loc},
Offset, Reloc, Size, TypedReloc, Types,
nodes::{Kind, Nid, Nodes, MEM},
parser,
ty::{self, Loc, Module, Offset, Size, Types},
utils::{EntSlice, EntVec},
},
alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec},
core::mem,
core::{assert_matches::debug_assert_matches, error, mem, ops::Range},
hbbytecode::{self as instrs, *},
reg::Reg,
};
mod my_regalloc;
mod their_regalloc;
mod regalloc;
mod reg {
pub const STACK_PTR: Reg = 254;
pub const ZERO: Reg = 0;
pub const RET: Reg = 1;
pub const RET_ADDR: Reg = 31;
pub type Reg = u8;
}
fn write_reloc(doce: &mut [u8], offset: usize, value: i64, size: u16) {
let value = value.to_ne_bytes();
doce[offset..offset + size as usize].copy_from_slice(&value[..size as usize]);
}
#[derive(Clone, Copy)]
struct TypedReloc {
target: ty::Id,
reloc: Reloc,
}
// TODO: make into bit struct (width: u2, sub_offset: u3, offset: u27)
#[derive(Clone, Copy, Debug)]
struct Reloc {
offset: Offset,
sub_offset: u8,
width: u8,
}
impl Reloc {
fn new(offset: usize, sub_offset: u8, width: u8) -> Self {
Self { offset: offset as u32, sub_offset, width }
}
fn apply_jump(mut self, code: &mut [u8], to: u32, from: u32) -> i64 {
self.offset += from;
let offset = to as i64 - self.offset as i64;
self.write_offset(code, offset);
offset
}
fn write_offset(&self, code: &mut [u8], offset: i64) {
let bytes = offset.to_ne_bytes();
let slice = &mut code[self.offset as usize + self.sub_offset as usize..];
slice[..self.width as usize].copy_from_slice(&bytes[..self.width as usize]);
}
}
struct FuncDt {
offset: Offset,
@ -47,11 +94,10 @@ struct Assembler {
#[derive(Default)]
pub struct HbvmBackend {
funcs: Vec<FuncDt>,
globals: Vec<GlobalDt>,
funcs: EntVec<ty::Func, FuncDt>,
globals: EntVec<ty::Global, GlobalDt>,
asm: Assembler,
ralloc: their_regalloc::Regalloc,
ralloc_my: my_regalloc::Res,
ralloc: regalloc::Res,
ret_relocs: Vec<Reloc>,
relocs: Vec<TypedReloc>,
@ -60,6 +106,8 @@ pub struct HbvmBackend {
offsets: Vec<Offset>,
}
pub const TARGET_TRIPLE: &str = "unknown-virt-unknown";
impl HbvmBackend {
fn emit(&mut self, instr: (usize, [u8; instrs::MAX_SIZE])) {
emit(&mut self.code, instr);
@ -67,12 +115,18 @@ impl HbvmBackend {
}
impl Backend for HbvmBackend {
fn assemble_bin(&mut self, entry: ty::Func, types: &Types, to: &mut Vec<u8>) {
fn assemble_bin(
&mut self,
entry: ty::Func,
types: &Types,
files: &EntSlice<Module, parser::Ast>,
to: &mut Vec<u8>,
) {
to.extend([0u8; HEADER_SIZE]);
binary_prelude(to);
let AssemblySpec { code_length, data_length, entry } =
self.assemble_reachable(entry, types, to);
self.assemble_reachable(entry, types, files, to);
let exe = AbleOsExecutableHeader {
magic_number: [0x15, 0x91, 0xD2],
@ -92,19 +146,20 @@ impl Backend for HbvmBackend {
&mut self,
from: ty::Func,
types: &Types,
_files: &EntSlice<Module, parser::Ast>,
to: &mut Vec<u8>,
) -> AssemblySpec {
debug_assert!(self.asm.frontier.is_empty());
debug_assert!(self.asm.funcs.is_empty());
debug_assert!(self.asm.globals.is_empty());
self.globals.resize_with(types.ins.globals.len(), Default::default);
self.globals.shadow(types.ins.globals.len());
self.asm.frontier.push(ty::Kind::Func(from).compress());
self.asm.frontier.push(from.into());
while let Some(itm) = self.asm.frontier.pop() {
match itm.expand() {
ty::Kind::Func(func) => {
let fuc = &mut self.funcs[func as usize];
let fuc = &mut self.funcs[func];
debug_assert!(!fuc.code.is_empty());
if fuc.offset != u32::MAX {
continue;
@ -114,7 +169,7 @@ impl Backend for HbvmBackend {
self.asm.frontier.extend(fuc.relocs.iter().map(|r| r.target));
}
ty::Kind::Global(glob) => {
let glb = &mut self.globals[glob as usize];
let glb = &mut self.globals[glob];
if glb.offset != u32::MAX {
continue;
}
@ -128,7 +183,7 @@ impl Backend for HbvmBackend {
let init_len = to.len();
for &func in &self.asm.funcs {
let fuc = &mut self.funcs[func as usize];
let fuc = &mut self.funcs[func];
fuc.offset = to.len() as _;
debug_assert!(!fuc.code.is_empty());
to.extend(&fuc.code);
@ -137,18 +192,18 @@ impl Backend for HbvmBackend {
let code_length = to.len() - init_len;
for global in self.asm.globals.drain(..) {
self.globals[global as usize].offset = to.len() as _;
to.extend(&types.ins.globals[global as usize].data);
self.globals[global].offset = to.len() as _;
to.extend(&types.ins.globals[global].data);
}
let data_length = to.len() - code_length - init_len;
for func in self.asm.funcs.drain(..) {
let fuc = &self.funcs[func as usize];
let fuc = &self.funcs[func];
for rel in &fuc.relocs {
let offset = match rel.target.expand() {
ty::Kind::Func(fun) => self.funcs[fun as usize].offset,
ty::Kind::Global(glo) => self.globals[glo as usize].offset,
ty::Kind::Func(fun) => self.funcs[fun].offset,
ty::Kind::Global(glo) => self.globals[glo].offset,
_ => unreachable!(),
};
rel.reloc.apply_jump(to, offset, fuc.offset);
@ -158,7 +213,7 @@ impl Backend for HbvmBackend {
AssemblySpec {
code_length: code_length as _,
data_length: data_length as _,
entry: self.funcs[from as usize].offset,
entry: self.funcs[from].offset,
}
}
@ -167,19 +222,19 @@ impl Backend for HbvmBackend {
mut sluce: &[u8],
eca_handler: &mut dyn FnMut(&mut &[u8]),
types: &'a Types,
files: &'a [parser::Ast],
files: &'a EntSlice<Module, parser::Ast>,
output: &mut String,
) -> Result<(), hbbytecode::DisasmError<'a>> {
) -> Result<(), alloc::boxed::Box<dyn error::Error + Send + Sync + 'a>> {
use hbbytecode::DisasmItem;
let functions = types
.ins
.funcs
.iter()
.zip(&self.funcs)
.zip(self.funcs.iter())
.filter(|(_, f)| f.offset != u32::MAX)
.map(|(f, fd)| {
let name = if f.file != u32::MAX {
let file = &files[f.file as usize];
let name = if f.file != Module::default() {
let file = &files[f.file];
file.ident_str(f.name)
} else {
"target_fn"
@ -191,51 +246,62 @@ impl Backend for HbvmBackend {
.ins
.globals
.iter()
.zip(&self.globals)
.zip(self.globals.iter())
.filter(|(_, g)| g.offset != u32::MAX)
.map(|(g, gd)| {
let name = if g.file == u32::MAX {
let name = if g.file == Module::default() {
core::str::from_utf8(&g.data).unwrap_or("invalid utf-8")
} else {
let file = &files[g.file as usize];
let file = &files[g.file];
file.ident_str(g.name)
};
(gd.offset, (name, g.data.len() as Size, DisasmItem::Global))
}),
)
.collect::<BTreeMap<_, _>>();
hbbytecode::disasm(&mut sluce, &functions, output, eca_handler)
hbbytecode::disasm(&mut sluce, &functions, output, eca_handler).map_err(Into::into)
}
fn emit_ct_body(
&mut self,
id: ty::Func,
nodes: &mut Nodes,
nodes: &Nodes,
tys: &Types,
files: &[parser::Ast],
files: &EntSlice<Module, parser::Ast>,
) {
self.emit_body(id, nodes, tys, files);
let fd = &mut self.funcs[id as usize];
let fd = &mut self.funcs[id];
fd.code.truncate(fd.code.len() - instrs::jala(0, 0, 0).0);
emit(&mut fd.code, instrs::tx());
}
fn emit_body(&mut self, id: ty::Func, nodes: &mut Nodes, tys: &Types, files: &[parser::Ast]) {
let sig = tys.ins.funcs[id as usize].sig.unwrap();
fn emit_body(
&mut self,
id: ty::Func,
nodes: &Nodes,
tys: &Types,
files: &EntSlice<Module, parser::Ast>,
) {
let sig = tys.ins.funcs[id].sig;
debug_assert!(self.code.is_empty());
self.offsets.clear();
self.offsets.resize(nodes.values.len(), Offset::MAX);
self.offsets.resize(nodes.len(), Offset::MAX);
let mut stack_size = 0;
'_compute_stack: {
let mems = mem::take(&mut nodes[MEM].outputs);
let mems = &nodes[MEM].outputs;
for &stck in mems.iter() {
if !matches!(nodes[stck].kind, Kind::Stck | Kind::Arg) {
debug_assert_matches!(
nodes[stck].kind,
Kind::Phi | Kind::Return | Kind::Load | Kind::Call { .. } | Kind::Stre
Kind::Phi
| Kind::Return { .. }
| Kind::Load
| Kind::Call { .. }
| Kind::Stre
| Kind::Join
);
continue;
}
@ -248,27 +314,23 @@ impl Backend for HbvmBackend {
}
self.offsets[stck as usize] = stack_size - self.offsets[stck as usize];
}
nodes[MEM].outputs = mems;
}
let (saved, tail) = self.emit_body_code(nodes, sig, tys, files);
//let (saved, tail) = self.emit_body_code_my(nodes, sig, tys, files);
if let Some(last_ret) = self.ret_relocs.last()
&& last_ret.offset as usize == self.code.len() - 5
&& self
.jump_relocs
.last()
.map_or(true, |&(r, _)| self.offsets[r as usize] as usize != self.code.len())
.is_none_or(|&(r, _)| self.offsets[r as usize] as usize != self.code.len())
{
self.code.truncate(self.code.len() - 5);
self.ret_relocs.pop();
}
// FIXME: maybe do this incrementally
for (nd, rel) in self.jump_relocs.drain(..) {
let offset = self.offsets[nd as usize];
//debug_assert!(offset < self.code.len() as u32 - 1);
rel.apply_jump(&mut self.code, offset, 0);
}
@ -319,11 +381,9 @@ impl Backend for HbvmBackend {
self.emit(instrs::jala(reg::ZERO, reg::RET_ADDR, 0));
}
if self.funcs.get(id as usize).is_none() {
self.funcs.resize_with(id as usize + 1, Default::default);
}
self.funcs[id as usize].code = mem::take(&mut self.code);
self.funcs[id as usize].relocs = mem::take(&mut self.relocs);
self.funcs.shadow(tys.ins.funcs.len());
self.funcs[id].code = mem::take(&mut self.code);
self.funcs[id].relocs = mem::take(&mut self.relocs);
debug_assert_eq!(self.ret_relocs.len(), 0);
debug_assert_eq!(self.relocs.len(), 0);
@ -333,27 +393,52 @@ impl Backend for HbvmBackend {
}
impl Nodes {
fn cond_op(&self, cnd: Nid) -> CondRet {
let Kind::BinOp { op } = self[cnd].kind else { return None };
if self.is_unlocked(cnd) {
return None;
}
op.cond_op(self[self[cnd].inputs[1]].ty)
}
fn strip_offset(&self, region: Nid) -> (Nid, Offset) {
if matches!(self[region].kind, Kind::BinOp { op: TokenKind::Add | TokenKind::Sub })
&& self.is_locked(region)
&& let Kind::CInt { value } = self[self[region].inputs[2]].kind
{
(self[region].inputs[1], value as _)
} else {
(region, 0)
}
}
fn is_never_used(&self, nid: Nid, tys: &Types) -> bool {
let node = &self[nid];
match node.kind {
Kind::CInt { .. } => node.outputs.iter().all(|&o| {
Kind::CInt { value: 0 } => false,
Kind::CInt { value: 1.. } => node.outputs.iter().all(|&o| {
matches!(self[o].kind, Kind::BinOp { op }
if op.imm_binop(self[o].ty).is_some()
&& self.is_const(self[o].inputs[2])
&& op.cond_op(self[o].ty).is_none())
}),
Kind::BinOp { op: TokenKind::Mul } if node.ty.is_float() => {
node.outputs.iter().all(|&n| {
self[n].kind == Kind::BinOp { op: TokenKind::Add } && self[n].inputs[1] == nid
})
}
Kind::BinOp { op: TokenKind::Add | TokenKind::Sub } => {
self[node.inputs[1]].lock_rc != 0
(self.is_locked(node.inputs[1]) && !self[node.inputs[1]].ty.is_float())
|| (self.is_const(node.inputs[2])
&& node.outputs.iter().all(|&n| self[n].uses_direct_offset_of(nid, tys)))
&& node.outputs.iter().all(|&n| self.uses_direct_offset_of(n, nid, tys)))
}
Kind::BinOp { op } => {
op.cond_op(node.ty).is_some()
op.cond_op(self[node.inputs[1]].ty).is_some()
&& node.outputs.iter().all(|&n| self[n].kind == Kind::If)
}
Kind::Stck if tys.size_of(node.ty) == 0 => true,
Kind::Stck | Kind::Arg => node.outputs.iter().all(|&n| {
self[n].uses_direct_offset_of(nid, tys)
self.uses_direct_offset_of(n, nid, tys)
|| (matches!(self[n].kind, Kind::BinOp { op: TokenKind::Add })
&& self.is_never_used(n, tys))
}),
@ -361,22 +446,59 @@ impl Nodes {
_ => false,
}
}
}
impl Node {
fn uses_direct_offset_of(&self, nid: Nid, tys: &Types) -> bool {
((self.kind == Kind::Stre && self.inputs[2] == nid)
|| (self.kind == Kind::Load && self.inputs[1] == nid))
&& self.ty.loc(tys) == Loc::Reg
fn uses_direct_offset_of(&self, user: Nid, target: Nid, tys: &Types) -> bool {
let node = &self[user];
((node.kind == Kind::Stre && node.inputs[2] == target)
|| (node.kind == Kind::Load && node.inputs[1] == target))
&& (node.ty.loc(tys) == Loc::Reg
// this means the struct is actually loaded into a register so no BMC needed
|| (node.kind == Kind::Load
&& !matches!(tys.parama(node.ty).0, Some(PLoc::Ref(..)))
&& node.outputs.iter().all(|&o| matches!(self[o].kind, Kind::Call { .. } | Kind::Return { .. }))))
}
}
impl HbvmBackend {
fn extend(
&mut self,
base: ty::Id,
dest: ty::Id,
reg: Reg,
tys: &Types,
files: &EntSlice<Module, parser::Ast>,
) {
if reg == 0 {
return;
}
let (bsize, dsize) = (tys.size_of(base), tys.size_of(dest));
debug_assert!(bsize <= 8, "{}", ty::Display::new(tys, files, base));
debug_assert!(dsize <= 8, "{}", ty::Display::new(tys, files, dest));
if bsize == dsize {
return Default::default();
}
self.emit(match (base.is_signed(), dest.is_signed()) {
(true, true) => {
let op = [instrs::sxt8, instrs::sxt16, instrs::sxt32][bsize.ilog2() as usize];
op(reg, reg)
}
_ => {
let mask = (1u64 << (bsize * 8)) - 1;
instrs::andi(reg, reg, mask)
}
});
}
}
type CondRet = Option<(fn(u8, u8, i16) -> EncodedInstr, bool)>;
impl TokenKind {
fn cmp_against(self) -> Option<u64> {
Some(match self {
TokenKind::Le | TokenKind::Gt => 1,
TokenKind::Ne | TokenKind::Eq => 0,
TokenKind::Ge | TokenKind::Lt => (-1i64) as _,
Self::Le | Self::Gt => 1,
Self::Ne | Self::Eq => 0,
Self::Ge | Self::Lt => (-1i64) as _,
_ => return None,
})
}
@ -388,22 +510,21 @@ impl TokenKind {
let size = ty.simple_size().unwrap();
let ops = match self {
TokenKind::Gt => [instrs::fcmpgt32, instrs::fcmpgt64],
TokenKind::Lt => [instrs::fcmplt32, instrs::fcmplt64],
Self::Gt => [instrs::fcmpgt32, instrs::fcmpgt64],
Self::Lt => [instrs::fcmplt32, instrs::fcmplt64],
_ => return None,
};
Some(ops[size.ilog2() as usize - 2])
}
#[expect(clippy::type_complexity)]
fn cond_op(self, ty: ty::Id) -> Option<(fn(u8, u8, i16) -> EncodedInstr, bool)> {
if ty.is_float() {
return None;
}
fn cond_op(self, ty: ty::Id) -> CondRet {
let signed = ty.is_signed();
Some((
match self {
Self::Eq => instrs::jne,
Self::Ne => instrs::jeq,
_ if ty.is_float() => return None,
Self::Le if signed => instrs::jgts,
Self::Le => instrs::jgtu,
Self::Lt if signed => instrs::jlts,
@ -412,16 +533,14 @@ impl TokenKind {
Self::Ge => instrs::jltu,
Self::Gt if signed => instrs::jgts,
Self::Gt => instrs::jgtu,
Self::Eq => instrs::jne,
Self::Ne => instrs::jeq,
_ => return None,
},
matches!(self, Self::Lt | TokenKind::Gt),
matches!(self, Self::Lt | Self::Gt),
))
}
fn binop(self, ty: ty::Id) -> Option<fn(u8, u8, u8) -> EncodedInstr> {
let size = ty.simple_size().unwrap();
let size = ty.simple_size().unwrap_or_else(|| panic!("{:?}", ty.expand()));
if ty.is_integer() || ty == ty::Id::BOOL || ty.is_pointer() {
macro_rules! div { ($($op:ident),*) => {[$(|a, b, c| $op(a, 0, b, c)),*]}; }
macro_rules! rem { ($($op:ident),*) => {[$(|a, b, c| $op(0, a, b, c)),*]}; }
@ -486,7 +605,7 @@ impl TokenKind {
Self::Band => return Some(andi),
Self::Bor => return Some(ori),
Self::Xor => return Some(xori),
Self::Shr if signed => basic_op!(srui8, srui16, srui32, srui64),
Self::Shr if signed => basic_op!(srsi8, srsi16, srsi32, srsi64),
Self::Shr => basic_op!(srui8, srui16, srui32, srui64),
Self::Shl => basic_op!(slui8, slui16, slui32, slui64),
_ => return None,
@ -496,25 +615,84 @@ impl TokenKind {
Some(ops[size.ilog2() as usize])
}
fn unop(&self, dst: ty::Id, src: ty::Id) -> Option<fn(u8, u8) -> EncodedInstr> {
let src_idx = src.simple_size().unwrap().ilog2() as usize - 2;
fn unop(&self, dst: ty::Id, src: ty::Id, tys: &Types) -> Option<fn(u8, u8) -> EncodedInstr> {
let src_idx = tys.size_of(src).ilog2() as usize;
Some(match self {
Self::Sub => instrs::neg,
Self::Sub => [
|a, b| sub8(a, reg::ZERO, b),
|a, b| sub16(a, reg::ZERO, b),
|a, b| sub32(a, reg::ZERO, b),
|a, b| sub64(a, reg::ZERO, b),
][src_idx],
Self::Not => instrs::not,
Self::Float if dst.is_float() && src.is_integer() => {
debug_assert_eq!(dst.simple_size(), src.simple_size());
[instrs::itf32, instrs::itf64][src_idx]
debug_assert_matches!(
(dst.simple_size(), src.simple_size()),
(Some(4 | 8), Some(8))
);
[instrs::itf32, instrs::itf64][dst.simple_size().unwrap().ilog2() as usize - 2]
}
Self::Number if src.is_float() && dst.is_integer() => {
[|a, b| instrs::fti32(a, b, 1), |a, b| instrs::fti64(a, b, 1)][src_idx]
[|a, b| instrs::fti32(a, b, 1), |a, b| instrs::fti64(a, b, 1)][src_idx - 2]
}
Self::Number if src.is_signed() && (dst.is_integer() || dst.is_pointer()) => {
[instrs::sxt8, instrs::sxt16, instrs::sxt32][src_idx]
}
Self::Number
if (src.is_unsigned() || src == ty::Id::BOOL)
&& (dst.is_integer() || dst.is_pointer()) =>
{
[
|a, b| instrs::andi(a, b, 0xff),
|a, b| instrs::andi(a, b, 0xffff),
|a, b| instrs::andi(a, b, 0xffffffff),
][src_idx]
}
Self::Float if dst.is_float() && src.is_float() => {
[instrs::fc32t64, |a, b| instrs::fc64t32(a, b, 1)][src_idx]
[instrs::fc32t64, |a, b| instrs::fc64t32(a, b, 1)][src_idx - 2]
}
_ => return None,
})
}
}
#[derive(Clone, Copy, Debug)]
enum PLoc {
Reg(Reg, u16),
WideReg(Reg, u16),
Ref(Reg, u32),
}
impl PLoc {
fn reg(self) -> u8 {
match self {
PLoc::Reg(r, _) | PLoc::WideReg(r, _) | PLoc::Ref(r, _) => r,
}
}
}
struct ParamAlloc(Range<Reg>);
impl ParamAlloc {
pub fn next(&mut self, ty: ty::Id, tys: &Types) -> Option<PLoc> {
Some(match tys.size_of(ty) {
0 => return None,
size @ 1..=8 => PLoc::Reg(self.0.next().unwrap(), size as _),
size @ 9..=16 => PLoc::WideReg(self.0.next_chunk::<2>().unwrap()[0], size as _),
size @ 17.. => PLoc::Ref(self.0.next().unwrap(), size),
})
}
}
impl Types {
fn parama(&self, ret: ty::Id) -> (Option<PLoc>, ParamAlloc) {
let mut iter = ParamAlloc(1..12);
let ret = iter.next(ret, self);
iter.0.start += ret.is_none() as u8;
(ret, iter)
}
}
type EncodedInstr = (usize, [u8; instrs::MAX_SIZE]);
fn emit(out: &mut Vec<u8>, (len, instr): EncodedInstr) {
out.extend_from_slice(&instr[..len]);
@ -528,42 +706,7 @@ fn binary_prelude(to: &mut Vec<u8>) {
#[derive(Default)]
pub struct LoggedMem {
pub mem: hbvm::mem::HostMemory,
op_buf: Vec<hbbytecode::Oper>,
disp_buf: String,
prev_instr: Option<hbbytecode::Instr>,
}
impl LoggedMem {
unsafe fn display_instr<T>(&mut self, instr: hbbytecode::Instr, addr: hbvm::mem::Address) {
let novm: *const hbvm::Vm<Self, 0> = core::ptr::null();
let offset = core::ptr::addr_of!((*novm).memory) as usize;
let regs = unsafe {
&*core::ptr::addr_of!(
(*(((self as *mut _ as *mut u8).sub(offset)) as *const hbvm::Vm<Self, 0>))
.registers
)
};
let mut bytes = core::slice::from_raw_parts(
(addr.get() - 1) as *const u8,
core::mem::size_of::<T>() + 1,
);
use core::fmt::Write;
hbbytecode::parse_args(&mut bytes, instr, &mut self.op_buf).unwrap();
debug_assert!(bytes.is_empty());
self.disp_buf.clear();
write!(self.disp_buf, "{:<10}", format!("{instr:?}")).unwrap();
for (i, op) in self.op_buf.drain(..).enumerate() {
if i != 0 {
write!(self.disp_buf, ", ").unwrap();
}
write!(self.disp_buf, "{op:?}").unwrap();
if let hbbytecode::Oper::R(r) = op {
write!(self.disp_buf, "({})", regs[r as usize].0).unwrap()
}
}
log::trace!("read-typed: {:x}: {}", addr.get(), self.disp_buf);
}
logger: hbvm::mem::InstrLogger,
}
impl hbvm::mem::Memory for LoggedMem {
@ -596,20 +739,14 @@ impl hbvm::mem::Memory for LoggedMem {
}
unsafe fn prog_read<T: Copy + 'static>(&mut self, addr: hbvm::mem::Address) -> T {
if log::log_enabled!(log::Level::Trace) {
if core::any::TypeId::of::<u8>() == core::any::TypeId::of::<T>() {
if let Some(instr) = self.prev_instr {
self.display_instr::<()>(instr, addr);
}
self.prev_instr = hbbytecode::Instr::try_from(*(addr.get() as *const u8)).ok();
} else {
let instr = self.prev_instr.take().unwrap();
self.display_instr::<T>(instr, addr);
}
}
self.mem.prog_read(addr)
}
fn log_instr(&mut self, at: hbvm::mem::Address, regs: &[hbvm::value::Value]) {
log::trace!("read-typed: {:x}: {}", at.get(), unsafe {
self.logger.display_instr(at, regs)
});
}
}
struct AsHex<'a>(&'a [u8]);
@ -715,7 +852,7 @@ pub struct AbleOsExecutableHeader {
#[cfg(test)]
pub fn test_run_vm(out: &[u8], output: &mut String) {
use core::fmt::Write;
use core::{ffi::CStr, fmt::Write};
let mut stack = [0_u64; 1024 * 20];
@ -732,6 +869,12 @@ pub fn test_run_vm(out: &[u8], output: &mut String) {
match vm.run() {
Ok(hbvm::VmRunOk::End) => break Ok(()),
Ok(hbvm::VmRunOk::Ecall) => match vm.read_reg(2).0 {
37 => writeln!(
output,
"{}",
unsafe { CStr::from_ptr(vm.read_reg(3).0 as _) }.to_str().unwrap()
)
.unwrap(),
1 => writeln!(output, "ev: Ecall").unwrap(), // compatibility with a test
69 => {
let [size, align] = [vm.read_reg(3).0 as usize, vm.read_reg(4).0 as usize];

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,15 @@
use {
crate::{
lexer::{self, Lexer, TokenKind},
parser::{self, CommentOr, CtorField, Expr, Poser, Radix, StructField},
parser::{
self, CommentOr, CtorField, EnumField, Expr, FieldList, ListKind, Poser, Radix,
StructField, UnionField,
},
},
core::{
fmt::{self},
mem,
},
core::fmt::{self},
};
pub fn display_radix(radix: Radix, mut value: u64, buf: &mut [u8; 64]) -> &str {
@ -26,6 +32,71 @@ pub fn display_radix(radix: Radix, mut value: u64, buf: &mut [u8; 64]) -> &str {
unreachable!()
}
#[repr(u8)]
enum TokenGroup {
Blank,
Comment,
Keyword,
Identifier,
Directive,
Number,
String,
Op,
Assign,
Paren,
Bracket,
Colon,
Comma,
Dot,
Ctor,
}
impl TokenKind {
fn to_higlight_group(self) -> TokenGroup {
use {TokenGroup as TG, TokenKind::*};
match self {
BSlash | Pound | Eof | Ct => TG::Blank,
Comment => TG::Comment,
Directive => TG::Directive,
Colon => TG::Colon,
Semi | Comma => TG::Comma,
Dot => TG::Dot,
Ctor | Arr | Tupl | TArrow | Range => 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,
Slf | Defer | Return | If | Else | Loop | Break | Continue | Fn | Idk | Die
| Struct | Packed | True | False | Null | Match | Enum | Union | CtLoop => 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.kind.to_higlight_group() 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..)
@ -39,7 +110,7 @@ pub fn minify(source: &mut str) -> usize {
let mut token = lexer::Lexer::new(reader).eat();
match token.kind {
TokenKind::Eof => break,
TokenKind::CtIdent | TokenKind::Directive => token.start -= 1,
TokenKind::CtIdent | TokenKind::CtLoop | TokenKind::Directive => token.start -= 1,
_ => {}
}
@ -135,24 +206,30 @@ impl<'a> Formatter<'a> {
return f.write_str(end);
}
writeln!(f)?;
self.depth += 1;
if !end.is_empty() {
writeln!(f)?;
}
self.depth += !end.is_empty() as usize;
let mut already_indented = end.is_empty();
let res = (|| {
for (i, stmt) in list.iter().enumerate() {
for _ in 0..self.depth {
f.write_str("\t")?;
if !mem::take(&mut already_indented) {
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..)
&& let Some(prev) = self.source.get(..expr.posi() as usize)
{
if insert_needed_semicolon(rest) {
if sep.is_empty() && prev.trim_end().ends_with(';') {
f.write_str(";")?;
}
if preserve_newlines(&self.source[..expr.posi() as usize]) > 1 {
if count_trailing_newlines(prev) > 1 {
f.write_str("\n")?;
}
}
@ -162,12 +239,14 @@ impl<'a> Formatter<'a> {
}
Ok(())
})();
self.depth -= 1;
self.depth -= !end.is_empty() as usize;
for _ in 0..self.depth {
f.write_str("\t")?;
if !end.is_empty() {
for _ in 0..self.depth {
f.write_str("\t")?;
}
f.write_str(end)?;
}
f.write_str(end)?;
res
}
@ -186,6 +265,32 @@ impl<'a> Formatter<'a> {
}
}
fn fmt_fields<F: core::fmt::Write, T: Poser + Copy>(
&mut self,
f: &mut F,
keyword: &str,
trailing_comma: bool,
fields: FieldList<T>,
fmt: impl Fn(&mut Self, &T, &mut F) -> Result<(), fmt::Error>,
) -> fmt::Result {
f.write_str(keyword)?;
f.write_str(" {")?;
self.fmt_list_low(f, trailing_comma, "}", ",", fields, |s, field, f| {
match field {
CommentOr::Or(Ok(field)) => fmt(s, field, f)?,
CommentOr::Or(Err(scope)) => {
s.fmt_list(f, true, "", "", scope, Self::fmt)?;
return Ok(false);
}
CommentOr::Comment { literal, .. } => {
f.write_str(literal)?;
f.write_str("\n")?;
}
}
Ok(field.or().is_some())
})
}
pub fn fmt<F: core::fmt::Write>(&mut self, expr: &Expr, f: &mut F) -> fmt::Result {
macro_rules! impl_parenter {
($($name:ident => $pat:pat,)*) => {
@ -202,11 +307,13 @@ impl<'a> Formatter<'a> {
}
match *expr {
Expr::Ct { value, .. } => {
f.write_str("$: ")?;
Expr::Defer { value, .. } => {
f.write_str("defer ")?;
self.fmt(value, f)
}
Expr::Slf { .. } => f.write_str("Self"),
Expr::String { literal, .. } => f.write_str(literal),
Expr::Char { 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}\")"),
@ -215,6 +322,16 @@ impl<'a> Formatter<'a> {
f.write_str(".")?;
f.write_str(field)
}
Expr::Range { start, end, .. } => {
if let Some(start) = start {
self.fmt(start, f)?;
}
f.write_str("..")?;
if let Some(end) = end {
self.fmt(end, f)?;
}
Ok(())
}
Expr::Directive { name, args, .. } => {
f.write_str("@")?;
f.write_str(name)?;
@ -226,25 +343,44 @@ impl<'a> Formatter<'a> {
f.write_str("packed ")?;
}
write!(f, "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)?
self.fmt_fields(
f,
"struct",
trailing_comma,
fields,
|s, StructField { name, ty, default_value, .. }, f| {
f.write_str(name)?;
f.write_str(": ")?;
s.fmt(ty, f)?;
if let Some(deva) = default_value {
f.write_str(" = ")?;
s.fmt(deva, f)?;
}
CommentOr::Comment { literal, .. } => {
f.write_str(literal)?;
f.write_str("\n")?;
}
}
Ok(field.or().is_some())
})
Ok(())
},
)
}
Expr::Union { fields, trailing_comma, .. } => self.fmt_fields(
f,
"union",
trailing_comma,
fields,
|s, UnionField { name, ty, .. }, f| {
f.write_str(name)?;
f.write_str(": ")?;
s.fmt(ty, f)
},
),
Expr::Enum { variants, trailing_comma, .. } => self.fmt_fields(
f,
"enum",
trailing_comma,
variants,
|_, EnumField { name, .. }, f| f.write_str(name),
),
Expr::Ctor { ty, fields, trailing_comma, .. } => {
if let Some(ty) = ty {
self.fmt_paren(ty, f, unary)?;
self.fmt_paren(ty, f, postfix)?;
}
f.write_str(".{")?;
self.fmt_list(
@ -263,38 +399,43 @@ impl<'a> Formatter<'a> {
},
)
}
Expr::Tupl {
Expr::List {
pos,
kind: term,
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 {
&Expr::List {
pos,
kind: term,
ty: Some(&Expr::Slice { pos: spos, size: None, item }),
fields,
trailing_comma,
},
f,
),
Expr::Tupl { ty, fields, trailing_comma, .. } => {
Expr::List { ty, kind: term, fields, trailing_comma, .. } => {
if let Some(ty) = ty {
self.fmt_paren(ty, f, unary)?;
self.fmt_paren(ty, f, postfix)?;
}
f.write_str(".(")?;
self.fmt_list(f, trailing_comma, ")", ",", fields, Self::fmt)
let (start, end) = match term {
ListKind::Tuple => (".(", ")"),
ListKind::Array => (".[", "]"),
};
f.write_str(start)?;
self.fmt_list(f, trailing_comma, end, ",", 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("]")
f.write_str("]")?;
self.fmt_paren(item, f, unary)
}
Expr::Index { base, index } => {
self.fmt(base, f)?;
self.fmt_paren(base, f, postfix)?;
f.write_str("[")?;
self.fmt(index, f)?;
f.write_str("]")
@ -316,8 +457,18 @@ impl<'a> Formatter<'a> {
}
Ok(())
}
Expr::Loop { body, .. } => {
f.write_str("loop ")?;
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, unrolled, .. } => {
f.write_str(if unrolled { "$loop " } else { "loop " })?;
self.fmt(body, f)
}
Expr::Closure { ret, body, args, .. } => {
@ -407,7 +558,7 @@ impl<'a> Formatter<'a> {
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 {
if count_trailing_newlines(prev) > 0 {
f.write_str("\n")?;
for _ in 0..self.depth + 1 {
f.write_str("\t")?;
@ -415,7 +566,9 @@ impl<'a> Formatter<'a> {
f.write_str(op.name())?;
f.write_str(" ")?;
} else {
f.write_str(" ")?;
if op != TokenKind::Colon {
f.write_str(" ")?;
}
f.write_str(op.name())?;
f.write_str(" ")?;
}
@ -430,15 +583,10 @@ impl<'a> Formatter<'a> {
}
}
pub fn preserve_newlines(source: &str) -> usize {
pub fn count_trailing_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)
@ -449,14 +597,14 @@ pub fn fmt_file(exprs: &[Expr], file: &str, f: &mut impl fmt::Write) -> fmt::Res
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..)
&& let Some(prefix) = file.get(..expr.pos() as usize)
{
if insert_needed_semicolon(rest) {
write!(f, ";")?;
if prefix.trim_end().ends_with(';') {
f.write_str(";")?;
}
if preserve_newlines(&file[..expr.pos() as usize]) > 1 {
writeln!(f)?;
if count_trailing_newlines(prefix) > 1 {
f.write_str("\n")?;
}
}
@ -482,15 +630,7 @@ pub mod test {
let mut ctx = Ctx::default();
let ast = parser::Ast::new(ident, minned, &mut ctx, &mut parser::no_loader);
//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::info!("{}", ctx.errors.borrow());
let mut output = String::new();
write!(output, "{ast}").unwrap();

View file

@ -1,15 +1,17 @@
use {
crate::{
parser::{self, Ast, Ctx, FileKind},
son::{self, hbvm::HbvmBackend},
backend::{hbvm::HbvmBackend, Backend},
parser::{Ast, Ctx, FileKind},
son::{self},
ty, FnvBuildHasher,
},
alloc::{string::String, vec::Vec},
core::{fmt::Write, num::NonZeroUsize, ops::Deref},
core::{fmt::Write, ops::Deref},
hashbrown::hash_map,
std::{
borrow::ToOwned,
collections::VecDeque,
eprintln,
ffi::OsStr,
io::{self, Write as _},
path::{Path, PathBuf},
string::ToString,
@ -17,6 +19,8 @@ use {
},
};
type HashMap<K, V> = hashbrown::HashMap<K, V, FnvBuildHasher>;
pub struct Logger;
impl log::Log for Logger {
@ -33,90 +37,96 @@ impl log::Log for Logger {
fn flush(&self) {}
}
pub const ABLEOS_PATH_RESOLVER: PathResolver =
&|mut path: &str, mut from: &str, tmp: &mut PathBuf| {
tmp.clear();
path = match path {
"stn" => {
from = "";
"./sysdata/libraries/stn/src/lib.hb"
}
_ => path,
};
match path.split_once(':') {
Some(("lib", p)) => tmp.extend(["./sysdata/libraries", p, "src/lib.hb"]),
Some(("stn", p)) => {
tmp.extend(["./sysdata/libraries/stn/src", &(p.to_owned() + ".hb")])
}
Some(("sysdata", p)) => tmp.extend(["./sysdata", p]),
None => match Path::new(from).parent() {
Some(parent) => tmp.extend([parent, Path::new(path)]),
None => tmp.push(path),
},
_ => panic!("path: '{path}' is invalid: unexpected ':'"),
};
tmp.canonicalize().map_err(|source| CantLoadFile { path: std::mem::take(tmp), source })
};
#[derive(Default)]
pub struct Options {
pub struct Options<'a> {
pub fmt: bool,
pub fmt_stdout: bool,
pub dump_asm: bool,
pub extra_threads: usize,
pub resolver: Option<PathResolver<'a>>,
pub backend: Option<&'a mut dyn Backend>,
}
impl Options {
pub fn from_args(args: &[&str]) -> std::io::Result<Self> {
if args.contains(&"--help") || args.contains(&"-h") {
log::error!("Usage: hbc [OPTIONS...] <FILE>");
log::error!(include_str!("../command-help.txt"));
return Err(std::io::ErrorKind::Other.into());
}
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,
options.resolver.unwrap_or(&default_resolve),
)?;
Ok(Options {
fmt: args.contains(&"--fmt"),
fmt_stdout: args.contains(&"--fmt-stdout"),
dump_asm: args.contains(&"--dump-asm"),
extra_threads: args
.iter()
.position(|&a| a == "--threads")
.map(|i| {
args[i + 1].parse::<NonZeroUsize>().map_err(|e| {
std::io::Error::other(format!("--threads expects non zero integer: {e}"))
})
})
.transpose()?
.map_or(1, NonZeroUsize::get)
- 1,
})
}
}
pub fn run_compiler(root_file: &str, options: Options, out: &mut Vec<u8>) -> std::io::Result<()> {
let parsed = parse_from_fs(options.extra_threads, root_file)?;
fn format_ast(ast: parser::Ast) -> std::io::Result<()> {
let mut output = String::new();
write!(output, "{ast}").unwrap();
if ast.file.deref().trim() != output.as_str().trim() {
std::fs::write(&*ast.path, output)?;
}
Ok(())
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 {
if !parsed.errors.is_empty() {
*out = parsed.errors.into_bytes();
return Err(std::io::Error::other("parsing fialed"));
}
for parsed in parsed.ast {
format_ast(parsed)?;
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 {
if !parsed.errors.is_empty() {
*out = parsed.errors.into_bytes();
return Err(std::io::Error::other("parsing fialed"));
}
let ast = parsed.ast.into_iter().next().unwrap();
write!(out, "{ast}").unwrap();
write!(out, "{}", &parsed.ast[0])?;
} else {
let mut backend = HbvmBackend::default();
let backend = options.backend.unwrap_or(&mut backend);
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);
let mut codegen = son::Codegen::new(backend, &parsed.ast, &mut ctx);
codegen.push_embeds(parsed.embeds);
codegen.generate(0);
codegen.generate(ty::Module::MAIN);
*warnings = core::mem::take(&mut *codegen.warnings.borrow_mut());
if !codegen.errors.borrow().is_empty() {
log::error!("{}", codegen.errors.borrow());
return Err(std::io::Error::other("compilation faoled"));
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()))?;
let err = codegen.disasm(&mut disasm, out).map_err(|e| io::Error::other(e.to_string()));
*out = disasm.into_bytes();
err?
}
}
@ -214,45 +224,53 @@ pub struct Loaded {
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),
};
fn default_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 })
tmp.canonicalize().map_err(|source| CantLoadFile { path: std::mem::take(tmp), source })
}
/// fn(path, from, tmp)
pub type PathResolver<'a> =
&'a (dyn Fn(&str, &str, &mut PathBuf) -> Result<PathBuf, CantLoadFile> + Send + Sync);
#[derive(Debug)]
pub struct CantLoadFile {
pub path: PathBuf,
pub 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: {}", crate::display_rel_path(&self.path),)
}
}
#[derive(Debug)]
struct CantLoadFile {
path: PathBuf,
source: io::Error,
impl core::error::Error for CantLoadFile {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.source)
}
}
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 From<CantLoadFile> for io::Error {
fn from(e: CantLoadFile) -> Self {
io::Error::new(io::ErrorKind::InvalidData, e)
}
}
impl core::error::Error for CantLoadFile {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.source)
}
}
pub fn parse_from_fs(
extra_threads: usize,
root: &str,
resolve: PathResolver,
) -> io::Result<Loaded> {
type Task = (usize, PathBuf);
impl From<CantLoadFile> for io::Error {
fn from(e: CantLoadFile) -> Self {
io::Error::new(io::ErrorKind::InvalidData, e)
}
}
type Task = (u32, PathBuf);
let seen_modules = Mutex::new(crate::HashMap::<PathBuf, u32>::default());
let seen_embeds = Mutex::new(crate::HashMap::<PathBuf, u32>::default());
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());
@ -271,7 +289,7 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Loaded> {
}
hash_map::Entry::Vacant(entry) => {
physiscal_path = entry.insert_entry(len as _).key().clone();
len as u32
len
}
}
};
@ -279,7 +297,7 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Loaded> {
if !physiscal_path.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("can't find file: {}", display_rel_path(&physiscal_path)),
format!("can't find file: {}", crate::display_rel_path(&physiscal_path)),
));
}
@ -296,7 +314,7 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Loaded> {
}
hash_map::Entry::Vacant(entry) => {
physiscal_path = entry.insert_entry(len as _).key().clone();
len as u32
len
}
}
};
@ -306,15 +324,15 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Loaded> {
e.kind(),
format!(
"can't load embed file: {}: {e}",
display_rel_path(&physiscal_path)
crate::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());
if id >= embeds.len() {
embeds.resize(id + 1, Default::default());
}
embeds[id as usize] = content;
embeds[id] = content;
Ok(id)
}
}
@ -324,7 +342,7 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Loaded> {
let path = path.to_str().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("path contains invalid characters: {}", display_rel_path(&path)),
format!("path contains invalid characters: {}", crate::display_rel_path(&path)),
)
})?;
Ok(Ast::new(path, std::fs::read_to_string(path)?, ctx, &mut |path, from, kind| {
@ -338,9 +356,9 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Loaded> {
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 as usize + 1);
let len = ast.len().max(indx + 1);
ast.resize_with(len, || Err(io::ErrorKind::InvalidData.into()));
ast[indx as usize] = res;
ast[indx] = res;
}
ctx.errors.into_inner()
};
@ -370,9 +388,3 @@ pub fn parse_from_fs(extra_threads: usize, root: &str) -> io::Result<Loaded> {
errors,
})
}
pub fn display_rel_path(path: &(impl AsRef<OsStr> + ?Sized)) -> std::path::Display {
static CWD: std::sync::LazyLock<PathBuf> =
std::sync::LazyLock::new(|| std::env::current_dir().unwrap_or_default());
std::path::Path::new(path).strip_prefix(&*CWD).unwrap_or(std::path::Path::new(path)).display()
}

View file

@ -1,8 +1,10 @@
use {
crate::{
backend::hbvm::HbvmBackend,
lexer::TokenKind,
parser,
son::{hbvm::HbvmBackend, Codegen, CodegenCtx},
son::{Codegen, CodegenCtx},
ty::Module,
},
alloc::string::String,
core::{fmt::Write, hash::BuildHasher, ops::Range},
@ -135,6 +137,6 @@ pub fn fuzz(seed_range: Range<u64>) {
let mut backend = HbvmBackend::default();
let mut cdg = Codegen::new(&mut backend, core::slice::from_ref(&parsed), &mut ctx);
cdg.generate(0);
cdg.generate(Module::MAIN);
}
}

View file

@ -32,6 +32,9 @@ macro_rules! gen_token_kind {
#[keywords] $(
$keyword:ident = $keyword_lit:literal,
)*
#[const_keywords] $(
$const_keyword:ident = $const_keyword_lit:literal,
)*
#[punkt] $(
$punkt:ident = $punkt_lit:literal,
)*
@ -56,6 +59,7 @@ macro_rules! gen_token_kind {
match *self {
$( Self::$pattern => concat!('<', stringify!($pattern), '>'), )*
$( Self::$keyword => stringify!($keyword_lit), )*
$( Self::$const_keyword => concat!('$', $const_keyword_lit), )*
$( Self::$punkt => stringify!($punkt_lit), )*
$($( Self::$op => $op_lit,
$(Self::$assign => concat!($op_lit, "="),)?)*)*
@ -72,12 +76,23 @@ macro_rules! gen_token_kind {
} + 1)
}
#[allow(non_upper_case_globals)]
fn from_ident(ident: &[u8]) -> Self {
$(const $keyword: &[u8] = $keyword_lit.as_bytes();)*
match ident {
$($keyword_lit => Self::$keyword,)*
$($keyword => Self::$keyword,)*
_ => Self::Ident,
}
}
#[allow(non_upper_case_globals)]
fn from_ct_ident(ident: &[u8]) -> Self {
$(const $const_keyword: &[u8] = $const_keyword_lit.as_bytes();)*
match ident {
$($const_keyword => Self::$const_keyword,)*
_ => Self::CtIdent,
}
}
}
};
}
@ -121,23 +136,11 @@ pub enum TokenKind {
Ct,
Return,
If,
Else,
Loop,
Break,
Continue,
Fn,
Struct,
Packed,
True,
False,
Null,
Idk,
Die,
Ctor,
Tupl,
Arr,
TArrow,
Range,
Or,
And,
@ -147,8 +150,31 @@ pub enum TokenKind {
BSlash = b'\\',
RBrack = b']',
Xor = b'^',
Tick = b'`',
Under = b'_',
Tick = b'`',
Slf,
Return,
If,
Match,
Else,
Loop,
Break,
Continue,
Fn,
Struct,
Packed,
Enum,
Union,
True,
False,
Null,
Idk,
Die,
Defer,
CtLoop,
// Unused = a-z
LBrace = b'{',
Bor = b'|',
@ -193,6 +219,10 @@ impl TokenKind {
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,
@ -263,12 +293,11 @@ impl TokenKind {
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 => {
debug_assert!(float);
f64::from_bits(value as _).to_bits() as _
}
Self::Number if float => f64::from_bits(value as _) as _,
Self::Number => value,
s => todo!("{s}"),
}
}
@ -295,24 +324,34 @@ gen_token_kind! {
Eof,
Directive,
#[keywords]
Return = b"return",
If = b"if",
Else = b"else",
Loop = b"loop",
Break = b"break",
Continue = b"continue",
Fn = b"fn",
Struct = b"struct",
Packed = b"packed",
True = b"true",
False = b"false",
Null = b"null",
Idk = b"idk",
Die = b"die",
Under = b"_",
Slf = "Self",
Return = "return",
If = "if",
Match = "match",
Else = "else",
Loop = "loop",
Break = "break",
Continue = "continue",
Fn = "fn",
Struct = "struct",
Packed = "packed",
Enum = "enum",
Union = "union",
True = "true",
False = "false",
Null = "null",
Idk = "idk",
Die = "die",
Defer = "defer",
Under = "_",
#[const_keywords]
CtLoop = "loop",
#[punkt]
Ctor = ".{",
Tupl = ".(",
Arr = ".[",
TArrow = "=>",
Range = "..",
// #define OP: each `#[prec]` delimeters a level of precedence from lowest to highest
#[ops]
#[prec]
@ -391,6 +430,23 @@ impl<'a> Lexer<'a> {
unsafe { core::str::from_utf8_unchecked(&self.source[tok]) }
}
pub fn taste(&self) -> Token {
Lexer { pos: self.pos, source: self.source }.eat()
}
fn peek_n<const N: usize>(&self) -> Option<&[u8; N]> {
if core::intrinsics::unlikely(self.pos as usize + N > self.source.len()) {
None
} else {
Some(unsafe {
self.source
.get_unchecked(self.pos as usize..self.pos as usize + N)
.first_chunk()
.unwrap_unchecked()
})
}
}
fn peek(&self) -> Option<u8> {
if core::intrinsics::unlikely(self.pos >= self.source.len() as u32) {
None
@ -483,8 +539,12 @@ impl<'a> Lexer<'a> {
self.advance();
}
if self.advance_if(b'.') {
while let Some(b'0'..=b'9' | b'_') = self.peek() {
if self
.peek_n()
.map_or_else(|| self.peek() == Some(b'.'), |&[a, b]| a == b'.' && b != b'.')
{
self.pos += 1;
while let Some(b'0'..=b'9') = self.peek() {
self.advance();
}
T::Float
@ -535,14 +595,23 @@ impl<'a> Lexer<'a> {
}
b'.' if self.advance_if(b'{') => T::Ctor,
b'.' if self.advance_if(b'(') => T::Tupl,
b'.' if self.advance_if(b'[') => T::Arr,
b'.' if self.advance_if(b'.') => T::Range,
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'$' => {
b'@' => {
start += 1;
advance_ident(self);
identity(c)
}
b'$' => {
start += 1;
advance_ident(self);
let ident = &self.source[start as usize..self.pos as usize];
T::from_ct_ident(ident)
}
b'<' | b'>' if self.advance_if(c) => {
identity(c - 5 + 128 * self.advance_if(b'=') as u8)
}

File diff suppressed because it is too large Load diff

View file

@ -1,17 +0,0 @@
#[cfg(feature = "std")]
fn main() -> std::io::Result<()> {
use std::io::Write;
log::set_logger(&hblang::Logger).unwrap();
log::set_max_level(log::LevelFilter::Info);
let args = std::env::args().collect::<Vec<_>>();
let args = args.iter().map(String::as_str).collect::<Vec<_>>();
let opts = hblang::Options::from_args(&args)?;
let file = args.iter().filter(|a| !a.starts_with('-')).nth(1).copied().unwrap_or("main.hb");
let mut out = Vec::new();
hblang::run_compiler(file, opts, &mut out)?;
std::io::stdout().write_all(&out)
}

2253
lang/src/nodes.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,8 @@ use {
crate::{
fmt::Formatter,
lexer::{self, Lexer, Token, TokenKind},
ty::{Global, Module},
utils::Ent as _,
Ident,
},
alloc::{boxed::Box, string::String, vec::Vec},
@ -19,10 +21,9 @@ use {
pub type Pos = u32;
pub type IdentFlags = u32;
pub type FileId = u32;
pub type IdentIndex = u16;
pub type LoaderError = String;
pub type Loader<'a> = &'a mut (dyn FnMut(&str, &str, FileKind) -> Result<FileId, LoaderError> + 'a);
pub type Loader<'a> = &'a mut (dyn FnMut(&str, &str, FileKind) -> Result<usize, LoaderError> + 'a);
#[derive(PartialEq, Eq, Debug)]
pub enum FileKind {
@ -30,7 +31,7 @@ pub enum FileKind {
Embed,
}
trait Trans {
pub trait Trans {
fn trans(self) -> Self;
}
@ -63,7 +64,7 @@ pub mod idfl {
}
}
pub fn no_loader(_: &str, _: &str, _: FileKind) -> Result<FileId, LoaderError> {
pub fn no_loader(_: &str, _: &str, _: FileKind) -> Result<usize, LoaderError> {
Ok(0)
}
@ -78,6 +79,8 @@ struct ScopeIdent {
ident: Ident,
declared: bool,
ordered: bool,
used: bool,
is_ct: bool,
flags: IdentFlags,
}
@ -194,8 +197,8 @@ impl<'a, 'b> Parser<'a, 'b> {
fn declare_rec(&mut self, expr: &Expr, top_level: bool) {
match *expr {
Expr::Ident { pos, id, is_first, .. } => {
self.declare(pos, id, !top_level, is_first || top_level)
Expr::Ident { pos, id, is_first, is_ct, .. } => {
self.declare(pos, id, !top_level, is_first || top_level, is_ct)
}
Expr::Ctor { fields, .. } => {
for CtorField { value, .. } in fields {
@ -206,7 +209,7 @@ impl<'a, 'b> Parser<'a, 'b> {
}
}
fn declare(&mut self, pos: Pos, id: Ident, ordered: bool, valid_order: bool) {
fn declare(&mut self, pos: Pos, id: Ident, ordered: bool, valid_order: bool, is_ct: bool) {
if !valid_order {
self.report(
pos,
@ -228,7 +231,7 @@ impl<'a, 'b> Parser<'a, 'b> {
);
return;
}
self.ctx.idents[index].is_ct = is_ct;
self.ctx.idents[index].ordered = ordered;
}
@ -247,11 +250,18 @@ impl<'a, 'b> Parser<'a, 'b> {
.enumerate()
.rfind(|(_, elem)| self.lexer.slice(elem.ident.range()) == name)
{
Some((i, elem)) => (i, elem, false),
Some((i, elem)) => {
elem.used = true;
(i, elem, false)
}
None => {
let ident = match Ident::new(token.start, name.len() as _) {
None => {
self.report(token.start, "identifier can at most have 64 characters");
self.report(
token.start,
"identifier can at most have 63 characters, \
the code is too clean to efficiently represent in memory",
);
Ident::new(token.start, 63).unwrap()
}
Some(id) => id,
@ -260,7 +270,9 @@ impl<'a, 'b> Parser<'a, 'b> {
self.ctx.idents.push(ScopeIdent {
ident,
declared: false,
used: false,
ordered: false,
is_ct: false,
flags: 0,
});
(self.ctx.idents.len() - 1, self.ctx.idents.last_mut().unwrap(), true)
@ -270,7 +282,7 @@ impl<'a, 'b> Parser<'a, 'b> {
id.flags |= idfl::COMPTIME * is_ct as u32;
if id.declared && id.ordered && self.ns_bound > i {
id.flags |= idfl::COMPTIME;
self.ctx.captured.push(id.ident);
self.ctx.captured.push(CapturedIdent { id: id.ident, is_ct: id.is_ct });
}
(id.ident, bl)
@ -281,13 +293,27 @@ impl<'a, 'b> Parser<'a, 'b> {
}
fn unit_expr(&mut self) -> Option<Expr<'a>> {
self.unit_expr_low(true)
}
fn unit_expr_low(&mut self, eat_tail: bool) -> Option<Expr<'a>> {
use {Expr as E, TokenKind as T};
if matches!(
self.token.kind,
T::RParen | T::RBrace | T::RBrack | T::Comma | T::Semi | T::Else
) {
self.report(self.token.start, "expected expression")?;
}
let frame = self.ctx.idents.len();
let token @ Token { start: pos, .. } = self.next();
let prev_boundary = self.ns_bound;
let prev_captured = self.ctx.captured.len();
let mut must_trail = false;
let mut expr = match token.kind {
T::Ct => E::Ct { pos, value: self.ptr_expr()? },
T::Defer => E::Defer { pos, value: self.ptr_expr()? },
T::Slf => E::Slf { pos },
T::Directive if self.lexer.slice(token.range()) == "use" => {
self.expect_advance(TokenKind::LParen)?;
let str = self.expect_advance(TokenKind::DQuote)?;
@ -299,7 +325,7 @@ impl<'a, 'b> Parser<'a, 'b> {
pos,
path,
id: match (self.loader)(path, self.path, FileKind::Module) {
Ok(id) => id,
Ok(id) => Module::new(id),
Err(e) => {
self.report(str.start, format_args!("error loading dependency: {e:#}"))?
}
@ -317,7 +343,7 @@ impl<'a, 'b> Parser<'a, 'b> {
pos,
path,
id: match (self.loader)(path, self.path, FileKind::Embed) {
Ok(id) => id,
Ok(id) => Global::new(id),
Err(e) => self.report(
str.start,
format_args!("error loading embedded file: {e:#}"),
@ -339,6 +365,7 @@ impl<'a, 'b> Parser<'a, 'b> {
T::Idk => E::Idk { pos },
T::Die => E::Die { pos },
T::DQuote => E::String { pos, literal: self.tok_str(token) },
T::Quote => E::Char { pos, literal: self.tok_str(token) },
T::Packed => {
self.packed = true;
let expr = self.unit_expr()?;
@ -352,45 +379,62 @@ impl<'a, 'b> Parser<'a, 'b> {
expr
}
T::Struct => E::Struct {
pos,
packed: core::mem::take(&mut self.packed),
fields: {
self.ns_bound = self.ctx.idents.len();
self.expect_advance(T::LBrace)?;
self.collect_list(T::Comma, T::RBrace, |s| {
let tok = s.token;
Some(if s.advance_if(T::Comment) {
CommentOr::Comment { literal: s.tok_str(tok), pos: tok.start }
} else {
let name = s.expect_advance(T::Ident)?;
s.expect_advance(T::Colon)?;
CommentOr::Or(StructField {
pos: name.start,
name: s.tok_str(name),
ty: s.expr()?,
})
})
})
},
captured: {
self.ns_bound = prev_boundary;
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..])
},
pos: {
if self.ns_bound == 0 {
// we might save some memory
self.ctx.captured.clear();
fields: self.collect_fields(&mut must_trail, |s| {
if s.lexer.taste().kind != T::Colon {
return Some(None);
}
pos
},
trailing_comma: core::mem::take(&mut self.trailing_sep),
let name = s.expect_advance(T::Ident)?;
s.expect_advance(T::Colon)?;
let (ty, default_value) = match s.expr()? {
Expr::BinOp { left, op: T::Assign, right, .. } => (*left, Some(*right)),
ty => (ty, None),
};
Some(Some(StructField {
pos: name.start,
name: s.tok_str(name),
ty,
default_value,
}))
})?,
captured: self.collect_captures(prev_boundary, prev_captured),
trailing_comma: core::mem::take(&mut self.trailing_sep) || must_trail,
},
T::Union => E::Union {
pos,
fields: self.collect_fields(&mut must_trail, |s| {
if s.lexer.taste().kind != T::Colon {
return Some(None);
}
let name = s.expect_advance(T::Ident)?;
s.expect_advance(T::Colon)?;
Some(Some(UnionField { pos: name.start, name: s.tok_str(name), ty: s.expr()? }))
})?,
captured: self.collect_captures(prev_boundary, prev_captured),
trailing_comma: core::mem::take(&mut self.trailing_sep) || must_trail,
},
T::Enum => E::Enum {
pos,
variants: self.collect_fields(&mut must_trail, |s| {
if !matches!(s.lexer.taste().kind, T::Comma | T::RBrace) {
return Some(None);
}
let name = s.expect_advance(T::Ident)?;
Some(Some(EnumField { pos: name.start, name: s.tok_str(name) }))
})?,
captured: self.collect_captures(prev_boundary, prev_captured),
trailing_comma: core::mem::take(&mut self.trailing_sep) || must_trail,
},
T::Ident | T::CtIdent => {
let (id, is_first) = self.resolve_ident(token);
E::Ident { pos, is_ct: token.kind == T::CtIdent, id, is_first }
E::Ident {
pos: pos - (token.kind == T::CtIdent) as Pos,
is_ct: token.kind == T::CtIdent,
id,
is_first,
}
}
T::Under => E::Wildcard { pos },
T::If => E::If {
@ -399,7 +443,22 @@ impl<'a, 'b> Parser<'a, 'b> {
then: self.ptr_expr()?,
else_: self.advance_if(T::Else).then(|| self.ptr_expr()).trans()?,
},
T::Loop => E::Loop { pos, body: self.ptr_expr()? },
T::Match => E::Match {
pos,
value: self.ptr_expr()?,
branches: {
self.expect_advance(T::LBrace)?;
self.collect_list(T::Comma, T::RBrace, |s| {
Some(MatchBranch {
pat: s.expr()?,
pos: s.expect_advance(T::TArrow)?.start,
body: s.expr()?,
})
})
},
},
T::Loop => E::Loop { pos, unrolled: false, body: self.ptr_expr()? },
T::CtLoop => E::Loop { pos, unrolled: true, body: self.ptr_expr()? },
T::Break => E::Break { pos },
T::Continue => E::Continue { pos },
T::Return => E::Return {
@ -418,7 +477,7 @@ impl<'a, 'b> Parser<'a, 'b> {
self.collect_list(T::Comma, T::RParen, |s| {
let name = s.advance_ident()?;
let (id, _) = s.resolve_ident(name);
s.declare(name.start, id, true, true);
s.declare(name.start, id, true, true, name.kind == T::CtIdent);
s.expect_advance(T::Colon)?;
Some(Arg {
pos: name.start,
@ -436,23 +495,33 @@ impl<'a, 'b> Parser<'a, 'b> {
body: self.ptr_expr()?,
},
T::Ctor => self.ctor(pos, None),
T::Tupl => self.tupl(pos, None),
T::Tupl => self.tupl(pos, None, ListKind::Tuple),
T::Arr => self.tupl(pos, None, ListKind::Array),
T::LBrack => E::Slice {
item: self.ptr_unit_expr()?,
size: self.advance_if(T::Semi).then(|| self.ptr_expr()).trans()?,
pos: {
self.expect_advance(T::RBrack)?;
pos
size: {
if self.advance_if(T::RBrack) {
None
} else {
let adv = self.ptr_expr()?;
self.expect_advance(T::RBrack)?;
Some(adv)
}
},
item: self.arena.alloc(self.unit_expr_low(false)?),
pos,
},
T::Band | T::Mul | T::Xor | T::Sub | T::Que => E::UnOp {
T::Band | T::Mul | T::Xor | T::Sub | T::Que | T::Not | T::Dot => E::UnOp {
pos,
op: token.kind,
val: {
let prev_ident_stack = self.ctx.idents.len();
let expr = self.ptr_unit_expr()?;
if token.kind == T::Band {
self.flag_idents(*expr, idfl::REFERENCED);
}
if token.kind == T::Dot {
self.ctx.idents.truncate(prev_ident_stack);
}
expr
},
},
@ -491,52 +560,100 @@ impl<'a, 'b> Parser<'a, 'b> {
tok => self.report(token.start, format_args!("unexpected token: {tok}"))?,
};
loop {
let token = self.token;
if matches!(token.kind, T::LParen | T::Ctor | T::Dot | T::Tupl | T::LBrack) {
self.next();
}
if eat_tail {
loop {
let token = self.token;
if matches!(
token.kind,
T::LParen | T::Ctor | T::Dot | T::Tupl | T::Arr | T::LBrack | T::Colon
) {
self.next();
}
expr = match token.kind {
T::LParen => Expr::Call {
func: self.arena.alloc(expr),
args: self.collect_list(T::Comma, T::RParen, Self::expr),
trailing_comma: core::mem::take(&mut self.trailing_sep),
},
T::Ctor => self.ctor(token.start, Some(expr)),
T::Tupl => self.tupl(token.start, Some(expr)),
T::LBrack => E::Index {
base: self.arena.alloc(expr),
index: {
let index = self.expr()?;
self.expect_advance(T::RBrack)?;
self.arena.alloc(index)
expr = match token.kind {
T::LParen => Expr::Call {
func: self.arena.alloc(expr),
args: self.collect_list(T::Comma, T::RParen, Self::expr),
trailing_comma: core::mem::take(&mut self.trailing_sep),
},
},
T::Dot => E::Field {
target: self.arena.alloc(expr),
pos: token.start,
name: {
let token = self.expect_advance(T::Ident)?;
self.tok_str(token)
T::Ctor => self.ctor(token.start, Some(expr)),
T::Tupl => self.tupl(token.start, Some(expr), ListKind::Tuple),
T::Arr => self.tupl(token.start, Some(expr), ListKind::Array),
T::LBrack => E::Index {
base: self.arena.alloc(expr),
index: self.arena.alloc({
if self.advance_if(T::Range) {
let pos = self.token.start;
if self.advance_if(T::RBrack) {
Expr::Range { pos, start: None, end: None }
} else {
let res = Expr::Range {
pos,
start: None,
end: Some(self.ptr_expr()?),
};
self.expect_advance(T::RBrack)?;
res
}
} else {
let start = self.expr()?;
let pos = self.token.start;
if self.advance_if(T::Range) {
let start = self.arena.alloc(start);
if self.advance_if(T::RBrack) {
Expr::Range { pos, start: Some(start), end: None }
} else {
let res = Expr::Range {
pos,
start: Some(start),
end: Some(self.ptr_expr()?),
};
self.expect_advance(T::RBrack)?;
res
}
} else {
self.expect_advance(T::RBrack)?;
start
}
}
}),
},
},
_ => break,
T::Colon => E::BinOp {
left: {
self.declare_rec(&expr, false);
self.arena.alloc(expr)
},
pos,
op: T::Colon,
right: self.ptr_expr()?,
},
T::Dot => E::Field {
target: self.arena.alloc(expr),
pos: token.start,
name: {
let token = self.expect_advance(T::Ident)?;
self.tok_str(token)
},
},
_ => break,
}
}
}
if matches!(token.kind, T::Loop | T::LBrace | T::Fn) {
if matches!(token.kind, T::Loop | T::LBrace | T::Fn | T::Struct) {
self.pop_scope(frame);
}
Some(expr)
}
fn tupl(&mut self, pos: Pos, ty: Option<Expr<'a>>) -> Expr<'a> {
Expr::Tupl {
fn tupl(&mut self, pos: Pos, ty: Option<Expr<'a>>, kind: ListKind) -> Expr<'a> {
Expr::List {
pos,
kind,
ty: ty.map(|ty| self.arena.alloc(ty)),
fields: self.collect_list(TokenKind::Comma, TokenKind::RParen, Self::expr),
fields: self.collect_list(TokenKind::Comma, kind.term(), Self::expr),
trailing_comma: core::mem::take(&mut self.trailing_sep),
}
}
@ -563,6 +680,49 @@ impl<'a, 'b> Parser<'a, 'b> {
}
}
fn collect_fields<T: Copy>(
&mut self,
must_trail: &mut bool,
mut parse_field: impl FnMut(&mut Self) -> Option<Option<T>>,
) -> Option<FieldList<'a, T>> {
use TokenKind as T;
self.ns_bound = self.ctx.idents.len();
self.expect_advance(T::LBrace)?;
Some(self.collect_list(T::Comma, T::RBrace, |s| {
let tok = s.token;
Some(if s.advance_if(T::Comment) {
CommentOr::Comment { literal: s.tok_str(tok), pos: tok.start }
} else if let Some(field) = parse_field(s)? {
CommentOr::Or(Ok(field))
} else {
*must_trail = true;
CommentOr::Or(Err(
s.collect_list_low(T::Semi, T::RBrace, true, |s| s.expr_low(true))
))
})
}))
}
fn collect_captures(
&mut self,
prev_captured: usize,
prev_boundary: usize,
) -> &'a [CapturedIdent] {
self.ns_bound = prev_boundary;
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);
let slc = self.arena.alloc_slice(&self.ctx.captured[prev_captured..]);
if self.ns_bound == 0 {
// we might save some memory
self.ctx.captured.clear();
}
slc
}
fn advance_ident(&mut self) -> Option<Token> {
let next = self.next();
if matches!(next.kind, TokenKind::Ident | TokenKind::CtIdent) {
@ -578,6 +738,8 @@ impl<'a, 'b> Parser<'a, 'b> {
if !&self.ctx.idents[i].declared {
self.ctx.idents.swap(i, undeclared_count);
undeclared_count += 1;
} else if !self.ctx.idents[i].used {
self.warn(self.ctx.idents[i].ident.pos(), "unused identifier");
}
}
@ -596,11 +758,23 @@ impl<'a, 'b> Parser<'a, 'b> {
&mut self,
delim: TokenKind,
end: TokenKind,
f: impl FnMut(&mut Self) -> Option<T>,
) -> &'a [T] {
self.collect_list_low(delim, end, false, f)
}
fn collect_list_low<T: Copy>(
&mut self,
delim: TokenKind,
end: TokenKind,
keep_end: bool,
mut f: impl FnMut(&mut Self) -> Option<T>,
) -> &'a [T] {
let mut trailing_sep = false;
let mut view = self.ctx.stack.view();
'o: while !self.advance_if(end) {
'o: while (keep_end && self.token.kind != end)
|| (!keep_end && !self.advance_if(end)) && self.token.kind != TokenKind::Eof
{
let val = match f(self) {
Some(val) => val,
None => {
@ -657,9 +831,25 @@ impl<'a, 'b> Parser<'a, 'b> {
}
}
#[track_caller]
fn warn(&mut self, pos: Pos, msg: impl fmt::Display) {
if log::log_enabled!(log::Level::Error) {
use core::fmt::Write;
writeln!(
self.ctx.warnings.get_mut(),
"(W) {}",
Report::new(self.lexer.source(), self.path, pos, msg)
)
.unwrap();
}
}
#[track_caller]
fn report(&mut self, pos: Pos, msg: impl fmt::Display) -> Option<!> {
if log::log_enabled!(log::Level::Error) {
if self.ctx.errors.get_mut().len() > 1024 * 10 {
panic!("{}", self.ctx.errors.get_mut());
}
use core::fmt::Write;
writeln!(
self.ctx.errors.get_mut(),
@ -673,15 +863,19 @@ impl<'a, 'b> Parser<'a, 'b> {
fn flag_idents(&mut self, e: Expr<'a>, flags: IdentFlags) {
match e {
Expr::Ident { id, .. } => find_ident(&mut self.ctx.idents, id).flags |= flags,
Expr::Ident { id, .. } => {
if let Some(f) = find_ident(&mut self.ctx.idents, id) {
f.flags |= flags;
}
}
Expr::Field { target, .. } => self.flag_idents(*target, flags),
_ => {}
}
}
}
fn find_ident(idents: &mut [ScopeIdent], id: Ident) -> &mut ScopeIdent {
idents.binary_search_by_key(&id, |si| si.ident).map(|i| &mut idents[i]).unwrap()
fn find_ident(idents: &mut [ScopeIdent], id: Ident) -> Option<&mut ScopeIdent> {
idents.binary_search_by_key(&id, |si| si.ident).map(|i| &mut idents[i]).ok()
}
pub fn find_symbol(symbols: &[Symbol], id: Ident) -> &Symbol {
@ -755,21 +949,32 @@ pub enum Radix {
Decimal = 10,
}
pub type FieldList<'a, T> = &'a [CommentOr<'a, Result<T, &'a [Expr<'a>]>>];
generate_expr! {
/// `LIST(start, sep, end, elem) => start { elem sep } [elem] end`
/// `OP := grep for `#define OP:`
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Expr<'a> {
/// `'ct' Expr`
Ct {
/// `'defer' Expr`
Defer {
pos: Pos,
value: &'a Self,
},
/// `'Self'`
Slf {
pos: Pos,
},
/// `'"([^"]|\\")"'`
String {
pos: Pos,
literal: &'a str,
},
/// `'\'([^']|\\\')\''`
Char {
pos: Pos,
literal: &'a str,
},
/// `'//[^\n]' | '/*' { '([^/*]|*/)*' | Comment } '*/'
Comment {
pos: Pos,
@ -844,9 +1049,15 @@ generate_expr! {
then: &'a Self,
else_: Option<&'a Self>,
},
Match {
pos: Pos,
value: &'a Self,
branches: &'a [MatchBranch<'a>],
},
/// `'loop' Expr`
Loop {
pos: Pos,
unrolled: bool,
body: &'a Self,
},
/// `('&' | '*' | '^') Expr`
@ -858,11 +1069,25 @@ generate_expr! {
/// `'struct' LIST('{', ',', '}', Ident ':' Expr)`
Struct {
pos: Pos,
fields: &'a [CommentOr<'a, StructField<'a>>],
captured: &'a [Ident],
fields: FieldList<'a, StructField<'a>>,
captured: &'a [CapturedIdent],
trailing_comma: bool,
packed: bool,
},
/// `'union' LIST('{', ',', '}', Ident ':' Expr)`
Union {
pos: Pos,
fields: FieldList<'a, UnionField<'a>>,
captured: &'a [CapturedIdent],
trailing_comma: bool,
},
/// `'enum' LIST('{', ',', '}', Ident)`
Enum {
pos: Pos,
variants: FieldList<'a, EnumField<'a>>,
captured: &'a [CapturedIdent],
trailing_comma: bool,
},
/// `[Expr] LIST('.{', ',', '}', Ident [':' Expr])`
Ctor {
pos: Pos,
@ -871,8 +1096,9 @@ generate_expr! {
trailing_comma: bool,
},
/// `[Expr] LIST('.(', ',', ')', Ident [':' Expr])`
Tupl {
List {
pos: Pos,
kind: ListKind,
ty: Option<&'a Self>,
fields: &'a [Self],
trailing_comma: bool,
@ -888,6 +1114,12 @@ generate_expr! {
base: &'a Self,
index: &'a Self,
},
/// `[ Expr ] .. [ Expr ]`
Range {
pos: u32,
start: Option<&'a Self>,
end: Option<&'a Self>,
},
/// `Expr '.' Ident`
Field {
target: &'a Self,
@ -921,22 +1153,44 @@ generate_expr! {
/// `'@use' '(' String ')'`
Mod {
pos: Pos,
id: FileId,
id: Module,
path: &'a str,
},
/// `'@use' '(' String ')'`
Embed {
pos: Pos,
id: FileId,
id: Global,
path: &'a str,
},
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
pub struct CapturedIdent {
pub id: Ident,
pub is_ct: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ListKind {
Tuple,
Array,
}
impl ListKind {
fn term(self) -> TokenKind {
match self {
ListKind::Tuple => TokenKind::RParen,
ListKind::Array => TokenKind::RBrack,
}
}
}
impl Expr<'_> {
pub fn declares(&self, iden: Result<Ident, &str>, source: &str) -> Option<Ident> {
pub fn declares(&self, iden: DeclId, source: &str) -> Option<Ident> {
match *self {
Self::Ident { id, .. } if iden == Ok(id) || iden == Err(&source[id.range()]) => {
Self::Ident { id, .. }
if iden == DeclId::Ident(id) || iden == DeclId::Name(&source[id.range()]) =>
{
Some(id)
}
Self::Ctor { fields, .. } => fields.iter().find_map(|f| f.value.declares(iden, source)),
@ -952,14 +1206,14 @@ impl Expr<'_> {
}
}
pub fn find_pattern_path<T, F: FnOnce(&Expr) -> T>(
pub fn find_pattern_path<T, F: FnOnce(&Expr, bool) -> T>(
&self,
ident: Ident,
target: &Expr,
mut with_final: F,
) -> Result<T, F> {
match *self {
Self::Ident { id, .. } if id == ident => Ok(with_final(target)),
Self::Ident { id, is_ct, .. } if id == ident => Ok(with_final(target, is_ct)),
Self::Ctor { fields, .. } => {
for &CtorField { name, value, pos } in fields {
match value.find_pattern_path(
@ -978,11 +1232,50 @@ impl Expr<'_> {
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct MatchBranch<'a> {
pub pat: Expr<'a>,
pub pos: Pos,
pub body: Expr<'a>,
}
impl Poser for MatchBranch<'_> {
fn posi(&self) -> Pos {
self.pat.pos()
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct EnumField<'a> {
pub pos: Pos,
pub name: &'a str,
}
impl Poser for EnumField<'_> {
fn posi(&self) -> Pos {
self.pos
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct UnionField<'a> {
pub pos: Pos,
pub name: &'a str,
pub ty: Expr<'a>,
}
impl Poser for UnionField<'_> {
fn posi(&self) -> Pos {
self.pos
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct StructField<'a> {
pub pos: Pos,
pub name: &'a str,
pub ty: Expr<'a>,
pub default_value: Option<Expr<'a>>,
}
impl Poser for StructField<'_> {
@ -1008,6 +1301,18 @@ pub trait Poser {
fn posi(&self) -> Pos;
}
impl<O: Poser, E: Poser> Poser for Result<O, E> {
fn posi(&self) -> Pos {
self.as_ref().map_or_else(Poser::posi, Poser::posi)
}
}
impl<T: Poser> Poser for &[T] {
fn posi(&self) -> Pos {
self[0].posi()
}
}
impl Poser for Pos {
fn posi(&self) -> Pos {
*self
@ -1035,9 +1340,9 @@ pub enum CommentOr<'a, T> {
Comment { literal: &'a str, pos: Pos },
}
impl<T: Copy> CommentOr<'_, T> {
pub fn or(&self) -> Option<T> {
match *self {
impl<T> CommentOr<'_, T> {
pub fn or(&self) -> Option<&T> {
match self {
CommentOr::Or(v) => Some(v),
CommentOr::Comment { .. } => None,
}
@ -1064,10 +1369,11 @@ impl core::fmt::Display for Display<'_> {
#[derive(Default)]
pub struct Ctx {
pub errors: RefCell<String>,
pub warnings: RefCell<String>,
symbols: Vec<Symbol>,
stack: StackAlloc,
idents: Vec<ScopeIdent>,
captured: Vec<Ident>,
captured: Vec<CapturedIdent>,
}
impl Ctx {
@ -1159,10 +1465,7 @@ 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;
let disp = crate::display_rel_path(path);
_ = writeln!(out, "{}:{}:{}: {}", disp, line, col, msg);
let line = &file[file[..pos as usize].rfind('\n').map_or(0, |i| i + 1)
@ -1206,13 +1509,8 @@ impl Ast {
unsafe { self.0.as_ref() }
}
pub fn find_decl(&self, id: Result<Ident, &str>) -> Option<(&Expr, Ident)> {
self.exprs().iter().find_map(|expr| match expr {
Expr::BinOp { left, op: TokenKind::Decl, .. } => {
left.declares(id, &self.file).map(|id| (expr, id))
}
_ => None,
})
pub fn find_decl(&self, id: DeclId) -> Option<(&Expr, Ident)> {
find_decl(self.exprs(), &self.file, id)
}
pub fn ident_str(&self, ident: Ident) -> &str {
@ -1220,13 +1518,44 @@ impl Ast {
}
}
pub fn find_decl<'a>(
exprs: &'a [Expr<'a>],
file: &str,
id: DeclId,
) -> Option<(&'a Expr<'a>, Ident)> {
exprs.iter().find_map(|expr| match expr {
Expr::BinOp { left, op: TokenKind::Decl | TokenKind::Colon, .. } => {
left.declares(id, file).map(|id| (expr, id))
}
_ => None,
})
}
#[derive(PartialEq, Eq, Clone, Copy)]
pub enum DeclId<'a> {
Ident(Ident),
Name(&'a str),
}
impl From<Ident> for DeclId<'_> {
fn from(value: Ident) -> Self {
Self::Ident(value)
}
}
impl<'a> From<&'a str> for DeclId<'a> {
fn from(value: &'a str) -> Self {
Self::Name(value)
}
}
impl Default for Ast {
fn default() -> Self {
Self(AstInner::new("".into(), "".into(), &mut Ctx::default(), &mut no_loader))
}
}
#[derive(Clone, Copy)]
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[repr(packed)]
pub struct ExprRef(NonNull<Expr<'static>>);
@ -1492,7 +1821,7 @@ impl ArenaChunk {
fn contains(&self, arg: *mut u8) -> bool {
(self.base <= arg && unsafe { self.base.add(self.size) } > arg)
|| self.next().map_or(false, |s| s.contains(arg))
|| self.next().is_some_and(|s| s.contains(arg))
}
pub fn size(&self) -> usize {

File diff suppressed because it is too large Load diff

View file

@ -1,903 +0,0 @@
use {
super::{HbvmBackend, Nid, Nodes},
crate::{
lexer::TokenKind,
parser,
reg::{self, Reg},
son::{debug_assert_matches, Kind, ARG_START, MEM, VOID},
ty::{self, Arg, Loc},
utils::{BitSet, Vc},
Offset, PLoc, Reloc, Sig, TypedReloc, Types,
},
alloc::{borrow::ToOwned, vec::Vec},
core::{mem, ops::Range},
hbbytecode::{self as instrs},
};
impl HbvmBackend {
pub fn emit_body_code_my(
&mut self,
nodes: &mut Nodes,
sig: Sig,
tys: &Types,
files: &[parser::Ast],
) -> (usize, bool) {
let mut fuc = Function::new(nodes, tys, sig);
log::info!("{fuc:?}");
let mut res = mem::take(&mut self.ralloc_my);
Env::new(&fuc, &fuc.func, &mut res).run();
'_open_function: {
self.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, 0));
self.emit(instrs::st(reg::RET_ADDR + fuc.tail as u8, reg::STACK_PTR, 0, 0));
}
let reg_offset = if fuc.tail { reg::RET + 12 } else { reg::RET_ADDR + 1 };
res.node_to_reg.iter_mut().filter(|r| **r != 0).for_each(|r| {
*r += reg_offset - 1;
if fuc.tail && *r >= reg::RET_ADDR {
*r += 1;
}
});
let atr = |allc: Nid| res.node_to_reg[allc as usize];
//for (id, node) in fuc.nodes.iter() {
// if node.kind == Kind::Phi {
// debug_assert_eq!(atr(node.inputs[1]), atr(node.inputs[2]));
// debug_assert_eq!(atr(id), atr(node.inputs[2]));
// }
//}
let (retl, mut parama) = tys.parama(sig.ret);
let mut typs = sig.args.args();
let mut args = fuc.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(instrs::cp(atr(arg), r));
continue;
}
};
self.emit(instrs::st(rg, reg::STACK_PTR, self.offsets[arg as usize] as _, size));
if fuc.nodes[arg].lock_rc == 0 {
self.emit(instrs::addi64(rg, reg::STACK_PTR, self.offsets[arg as usize] as _));
}
self.emit(instrs::cp(atr(arg), rg));
}
for (i, block) in fuc.func.blocks.iter().enumerate() {
self.offsets[block.entry as usize] = self.code.len() as _;
for &nid in &fuc.func.instrs[block.range.clone()] {
if nid == VOID {
continue;
}
let node = &fuc.nodes[nid];
let extend = |base: ty::Id, dest: ty::Id, from: Nid, to: Nid| {
let (bsize, dsize) = (tys.size_of(base), tys.size_of(dest));
debug_assert!(bsize <= 8, "{}", ty::Display::new(tys, files, base));
debug_assert!(dsize <= 8, "{}", ty::Display::new(tys, files, dest));
if bsize == dsize {
return Default::default();
}
match (base.is_signed(), dest.is_signed()) {
(true, true) => {
let op = [instrs::sxt8, instrs::sxt16, instrs::sxt32]
[bsize.ilog2() as usize];
op(atr(to), atr(from))
}
_ => {
let mask = (1u64 << (bsize * 8)) - 1;
instrs::andi(atr(to), atr(from), mask)
}
}
};
match node.kind {
Kind::If => {
let &[_, cnd] = node.inputs.as_slice() else { unreachable!() };
if let Kind::BinOp { op } = fuc.nodes[cnd].kind
&& let Some((op, swapped)) =
op.cond_op(fuc.nodes[fuc.nodes[cnd].inputs[1]].ty)
{
let &[_, lhs, rhs] = fuc.nodes[cnd].inputs.as_slice() else {
unreachable!()
};
self.emit(extend(fuc.nodes[lhs].ty, fuc.nodes[lhs].ty.extend(), 0, 0));
self.emit(extend(fuc.nodes[rhs].ty, fuc.nodes[rhs].ty.extend(), 1, 1));
let rel = Reloc::new(self.code.len(), 3, 2);
self.jump_relocs.push((node.outputs[!swapped as usize], rel));
self.emit(op(atr(lhs), atr(rhs), 0));
} else {
self.emit(extend(fuc.nodes[cnd].ty, fuc.nodes[cnd].ty.extend(), 0, 0));
let rel = Reloc::new(self.code.len(), 3, 2);
self.jump_relocs.push((node.outputs[0], rel));
self.emit(instrs::jne(atr(cnd), reg::ZERO, 0));
}
}
Kind::Loop | Kind::Region => {
if (mem::replace(&mut fuc.backrefs[nid as usize], u16::MAX) != u16::MAX)
^ (node.kind == Kind::Loop)
{
let index = (node.kind == Kind::Loop) as usize + 1;
for &out in node.outputs.iter() {
if fuc.nodes[out].is_data_phi()
&& atr(out) != atr(fuc.nodes[out].inputs[index])
{
self.emit(instrs::cp(
atr(out),
atr(fuc.nodes[out].inputs[index]),
));
}
}
let rel = Reloc::new(self.code.len(), 1, 4);
self.jump_relocs.push((nid, rel));
self.emit(instrs::jmp(0));
} else {
let index = (node.kind != Kind::Loop) as usize + 1;
for &out in node.outputs.iter() {
if fuc.nodes[out].is_data_phi()
&& atr(out) != atr(fuc.nodes[out].inputs[index])
{
self.emit(instrs::cp(
atr(out),
atr(fuc.nodes[out].inputs[index]),
));
}
}
}
}
Kind::Return => {
let &[_, mut ret, ..] = node.inputs.as_slice() else { unreachable!() };
match retl {
None => {}
Some(PLoc::Reg(r, _)) if sig.ret.loc(tys) == Loc::Reg => {
self.emit(instrs::cp(r, atr(ret)));
}
Some(PLoc::Reg(r, size)) | Some(PLoc::WideReg(r, size)) => {
ret = match fuc.nodes[ret].kind {
Kind::Load { .. } => fuc.nodes[ret].inputs[1],
_ => ret,
};
self.emit(instrs::ld(r, atr(ret), 0, size))
}
Some(PLoc::Ref(_, size)) => {
ret = match fuc.nodes[ret].kind {
Kind::Load { .. } => fuc.nodes[ret].inputs[1],
_ => ret,
};
let [src, dst] = [atr(ret), atr(MEM)];
if let Ok(size) = u16::try_from(size) {
self.emit(instrs::bmc(src, dst, size));
} else {
for _ in 0..size / u16::MAX as u32 {
self.emit(instrs::bmc(src, dst, u16::MAX));
self.emit(instrs::addi64(src, src, u16::MAX as _));
self.emit(instrs::addi64(dst, dst, u16::MAX as _));
}
self.emit(instrs::bmc(src, dst, size as u16));
self.emit(instrs::addi64(src, src, size.wrapping_neg() as _));
self.emit(instrs::addi64(dst, dst, size.wrapping_neg() as _));
}
}
}
if i != fuc.func.blocks.len() - 1 {
let rel = Reloc::new(self.code.len(), 1, 4);
self.ret_relocs.push(rel);
self.emit(instrs::jmp(0));
}
}
Kind::Die => self.emit(instrs::un()),
Kind::CInt { value } if node.ty.is_float() => {
self.emit(match node.ty {
ty::Id::F32 => instrs::li32(
atr(nid),
(f64::from_bits(value as _) as f32).to_bits(),
),
ty::Id::F64 => instrs::li64(atr(nid), value as _),
_ => unreachable!(),
});
}
Kind::CInt { value } => self.emit(match tys.size_of(node.ty) {
1 => instrs::li8(atr(nid), value as _),
2 => instrs::li16(atr(nid), value as _),
4 => instrs::li32(atr(nid), value as _),
_ => instrs::li64(atr(nid), value as _),
}),
Kind::UnOp { op } => {
let op = op
.unop(node.ty, fuc.nodes[node.inputs[1]].ty)
.expect("TODO: unary operator not supported");
self.emit(op(atr(nid), atr(node.inputs[1])));
}
Kind::BinOp { .. } if node.lock_rc != 0 => {}
Kind::BinOp { op } => {
let &[.., lhs, rhs] = node.inputs.as_slice() else { unreachable!() };
if let Kind::CInt { value } = fuc.nodes[rhs].kind
&& fuc.nodes[rhs].lock_rc != 0
&& let Some(op) = op.imm_binop(node.ty)
{
self.emit(op(atr(nid), atr(lhs), value as _));
} else if let Some(op) =
op.binop(node.ty).or(op.float_cmp(fuc.nodes[lhs].ty))
{
self.emit(op(atr(nid), atr(lhs), atr(rhs)));
} else if let Some(against) = op.cmp_against() {
let op_ty = fuc.nodes[lhs].ty;
self.emit(extend(fuc.nodes[lhs].ty, fuc.nodes[lhs].ty.extend(), 0, 0));
self.emit(extend(fuc.nodes[rhs].ty, fuc.nodes[rhs].ty.extend(), 1, 1));
if op_ty.is_float() && matches!(op, TokenKind::Le | TokenKind::Ge) {
let opop = match op {
TokenKind::Le => TokenKind::Gt,
TokenKind::Ge => TokenKind::Lt,
_ => unreachable!(),
};
let op_fn = opop.float_cmp(op_ty).unwrap();
self.emit(op_fn(atr(nid), atr(lhs), atr(rhs)));
self.emit(instrs::not(atr(nid), atr(nid)));
} else if op_ty.is_integer() {
let op_fn =
if op_ty.is_signed() { instrs::cmps } else { instrs::cmpu };
self.emit(op_fn(atr(nid), atr(lhs), atr(rhs)));
self.emit(instrs::cmpui(atr(nid), atr(nid), against));
if matches!(op, TokenKind::Eq | TokenKind::Lt | TokenKind::Gt) {
self.emit(instrs::not(atr(nid), atr(nid)));
}
} else {
todo!("unhandled operator: {op}");
}
} else {
todo!("unhandled operator: {op}");
}
}
Kind::Call { args, func } => {
let (ret, mut parama) = tys.parama(node.ty);
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 mut arg = *allocs.next().unwrap();
let (rg, size) = match loc {
PLoc::Reg(rg, size) if ty.loc(tys) == Loc::Stack => (rg, size),
PLoc::WideReg(rg, size) => (rg, size),
PLoc::Ref(r, ..) => {
arg = match fuc.nodes[arg].kind {
Kind::Load { .. } => fuc.nodes[arg].inputs[1],
_ => arg,
};
self.emit(instrs::cp(r, atr(arg)));
continue;
}
PLoc::Reg(r, ..) => {
self.emit(instrs::cp(r, atr(arg)));
continue;
}
};
arg = match fuc.nodes[arg].kind {
Kind::Load { .. } => fuc.nodes[arg].inputs[1],
_ => arg,
};
self.emit(instrs::ld(rg, atr(arg), 0, size));
}
debug_assert!(
!matches!(ret, Some(PLoc::Ref(..))) || allocs.next().is_some()
);
if func == ty::ECA {
self.emit(instrs::eca());
} else {
self.relocs.push(TypedReloc {
target: ty::Kind::Func(func).compress(),
reloc: Reloc::new(self.code.len(), 3, 4),
});
self.emit(instrs::jal(reg::RET_ADDR, reg::ZERO, 0));
}
match ret {
Some(PLoc::WideReg(r, size)) => {
debug_assert_eq!(
fuc.nodes[*node.inputs.last().unwrap()].kind,
Kind::Stck
);
let stck = self.offsets[*node.inputs.last().unwrap() as usize];
self.emit(instrs::st(r, reg::STACK_PTR, stck as _, size));
}
Some(PLoc::Reg(r, size)) if node.ty.loc(tys) == Loc::Stack => {
debug_assert_eq!(
fuc.nodes[*node.inputs.last().unwrap()].kind,
Kind::Stck
);
let stck = self.offsets[*node.inputs.last().unwrap() as usize];
self.emit(instrs::st(r, reg::STACK_PTR, stck as _, size));
}
Some(PLoc::Reg(r, ..)) => self.emit(instrs::cp(atr(nid), r)),
None | Some(PLoc::Ref(..)) => {}
}
}
Kind::Global { global } => {
let reloc = Reloc::new(self.code.len(), 3, 4);
self.relocs.push(TypedReloc {
target: ty::Kind::Global(global).compress(),
reloc,
});
self.emit(instrs::lra(atr(nid), 0, 0));
}
Kind::Stck => {
let base = reg::STACK_PTR;
let offset = self.offsets[nid as usize];
self.emit(instrs::addi64(atr(nid), base, offset as _));
}
Kind::Load => {
let mut region = node.inputs[1];
let mut offset = 0;
if fuc.nodes[region].kind == (Kind::BinOp { op: TokenKind::Add })
&& let Kind::CInt { value } =
fuc.nodes[fuc.nodes[region].inputs[2]].kind
{
region = fuc.nodes[region].inputs[1];
offset = value as Offset;
}
let size = tys.size_of(node.ty);
if node.ty.loc(tys) != Loc::Stack {
let (base, offset) = match fuc.nodes[region].kind {
Kind::Stck => {
(reg::STACK_PTR, self.offsets[region as usize] + offset)
}
_ => (atr(region), offset),
};
self.emit(instrs::ld(atr(nid), base, offset as _, size as _));
}
}
Kind::Stre if node.inputs[1] == VOID => {}
Kind::Stre => {
let mut region = node.inputs[2];
let mut offset = 0;
let size = u16::try_from(tys.size_of(node.ty)).expect("TODO");
if fuc.nodes[region].kind == (Kind::BinOp { op: TokenKind::Add })
&& let Kind::CInt { value } =
fuc.nodes[fuc.nodes[region].inputs[2]].kind
&& node.ty.loc(tys) == Loc::Reg
{
region = fuc.nodes[region].inputs[1];
offset = value as Offset;
}
let nd = &fuc.nodes[region];
let value = node.inputs[1];
let (base, offset, src) = match nd.kind {
Kind::Stck if node.ty.loc(tys) == Loc::Reg => {
(reg::STACK_PTR, self.offsets[region as usize] + offset, value)
}
_ => (atr(region), offset, match fuc.nodes[value].kind {
Kind::Load { .. } => fuc.nodes[value].inputs[1],
_ => value,
}),
};
match node.ty.loc(tys) {
Loc::Reg => self.emit(instrs::st(atr(src), base, offset as _, size)),
Loc::Stack => {
debug_assert_eq!(offset, 0);
self.emit(instrs::bmc(atr(src), base, size))
}
}
}
Kind::Mem => self.emit(instrs::cp(atr(MEM), reg::RET)),
Kind::Arg => {}
e @ (Kind::Start
| Kind::Entry
| Kind::End
| Kind::Loops
| Kind::Then
| Kind::Else
| Kind::Phi
| Kind::Assert { .. }) => unreachable!("{e:?}"),
}
}
}
self.ralloc_my = res;
let bundle_count = self.ralloc_my.bundles.len() + (reg_offset as usize);
(
if fuc.tail {
bundle_count.saturating_sub(reg::RET_ADDR as _)
} else {
assert!(bundle_count < reg::STACK_PTR as usize, "TODO: spill memory");
self.ralloc_my.bundles.len()
},
fuc.tail,
)
}
}
pub struct Function<'a> {
sig: Sig,
tail: bool,
backrefs: Vec<u16>,
nodes: &'a mut Nodes,
tys: &'a Types,
visited: BitSet,
func: Func,
}
impl Function<'_> {
fn vreg_count(&self) -> usize {
self.nodes.values.len()
}
fn uses_of(&self, nid: Nid, buf: &mut Vec<Nid>) {
if self.nodes[nid].kind.is_cfg() && !matches!(self.nodes[nid].kind, Kind::Call { .. }) {
return;
}
self.nodes[nid]
.outputs
.iter()
.filter(|&&n| self.nodes.is_data_dep(nid, n))
.collect_into(buf);
}
fn phi_inputs_of(&self, nid: Nid, buf: &mut Vec<Nid>) {
match self.nodes[nid].kind {
Kind::Region => {
for &inp in self.nodes[nid].outputs.as_slice() {
if self.nodes[inp].is_data_phi() {
buf.extend(&self.nodes[inp].inputs[1..]);
buf.push(inp);
}
}
}
Kind::Loop => {
for &inp in self.nodes[nid].outputs.as_slice() {
if self.nodes[inp].is_data_phi() {
buf.push(self.nodes[inp].inputs[1]);
buf.push(inp);
buf.push(self.nodes[inp].inputs[2]);
}
}
}
_ => {}
}
}
fn instr_of(&self, nid: Nid) -> Option<Nid> {
if self.nodes[nid].kind == Kind::Phi || self.nodes[nid].lock_rc != 0 {
return None;
}
debug_assert_ne!(self.backrefs[nid as usize], Nid::MAX, "{:?}", self.nodes[nid]);
Some(self.backrefs[nid as usize])
}
fn block_of(&self, nid: Nid) -> Nid {
debug_assert!(self.nodes[nid].kind.starts_basic_block());
self.backrefs[nid as usize]
}
fn idom_of(&self, mut nid: Nid) -> Nid {
while !self.nodes[nid].kind.starts_basic_block() {
nid = self.nodes.idom(nid);
}
nid
}
fn use_block(&self, inst: Nid, uinst: Nid) -> Nid {
let mut block = self.nodes.use_block(inst, uinst);
while !self.nodes[block].kind.starts_basic_block() {
block = self.nodes.idom(block);
}
block
}
}
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.clone()] {
writeln!(f, "{:?}", self.nodes[instr].kind)?;
}
}
Ok(())
}
}
impl<'a> Function<'a> {
fn new(nodes: &'a mut Nodes, tys: &'a Types, sig: Sig) -> Self {
let mut s = Self {
backrefs: vec![u16::MAX; nodes.values.len()],
tail: true,
nodes,
tys,
sig,
visited: Default::default(),
func: Default::default(),
};
s.visited.clear(s.nodes.values.len());
s.emit_node(VOID);
s
}
fn add_block(&mut self, entry: Nid) {
self.func
.blocks
.push(Block { range: self.func.instrs.len()..self.func.instrs.len(), entry });
self.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.range.end = self.func.instrs.len();
}
fn add_instr(&mut self, nid: Nid) {
debug_assert_ne!(self.nodes[nid].kind, Kind::Loop);
self.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.visited.set(nid)) {
(Kind::Loop, false) | (Kind::Region, true) => {
self.close_block(nid);
return;
}
_ => {}
}
} else if !self.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 &[_, cond] = node.inputs.as_slice() else { unreachable!() };
let &[mut then, mut else_] = node.outputs.as_slice() else { unreachable!() };
if let Kind::BinOp { op } = self.nodes[cond].kind
&& let Some((_, swapped)) = op.cond_op(node.ty)
&& swapped
{
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.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);
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),
}
}
if let Some(PLoc::Ref(..)) = ret {
self.add_instr(MEM);
}
self.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.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::ECA;
self.add_instr(nid);
self.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 { .. }
| 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::Assert { .. } => unreachable!(),
}
}
fn reschedule_block(&mut self, from: Nid, outputs: &mut Vc) {
let from = Some(&from);
let mut buf = Vec::with_capacity(outputs.len());
let mut seen = BitSet::default();
seen.clear(self.nodes.values.len());
for &o in outputs.iter() {
if !self.nodes.is_cfg(o) {
continue;
}
seen.set(o);
let mut cursor = buf.len();
buf.push(o);
while let Some(&n) = buf.get(cursor) {
for &i in &self.nodes[n].inputs[1..] {
if from == self.nodes[i].inputs.first()
&& self.nodes[i]
.outputs
.iter()
.all(|&o| self.nodes[o].inputs.first() != from || seen.get(o))
&& seen.set(i)
{
buf.push(i);
}
}
cursor += 1;
}
}
for &o in outputs.iter() {
if !seen.set(o) {
continue;
}
let mut cursor = buf.len();
buf.push(o);
while let Some(&n) = buf.get(cursor) {
for &i in &self.nodes[n].inputs[1..] {
if from == self.nodes[i].inputs.first()
&& self.nodes[i]
.outputs
.iter()
.all(|&o| self.nodes[o].inputs.first() != from || seen.get(o))
&& seen.set(i)
{
buf.push(i);
}
}
cursor += 1;
}
}
debug_assert!(
outputs.len() == buf.len() || outputs.len() == buf.len() + 1,
"{:?} {:?}",
outputs,
buf
);
if buf.len() + 1 == outputs.len() {
outputs.remove(outputs.len() - 1);
}
outputs.copy_from_slice(&buf);
}
}
pub struct Env<'a> {
ctx: &'a Function<'a>,
func: &'a Func,
res: &'a mut Res,
}
impl<'a> Env<'a> {
pub fn new(ctx: &'a Function<'a>, func: &'a Func, res: &'a mut Res) -> Self {
Self { ctx, func, res }
}
pub fn run(&mut self) {
self.res.bundles.clear();
self.res.node_to_reg.clear();
self.res.node_to_reg.resize(self.ctx.vreg_count(), 0);
debug_assert!(self.res.dfs_buf.is_empty());
debug_assert!(self.res.use_buf.is_empty());
debug_assert!(self.res.phi_input_buf.is_empty());
let mut bundle = Bundle::new(self.func.instrs.len());
let mut visited = BitSet::with_capacity(self.ctx.nodes.values.len());
let mut use_buf = mem::take(&mut self.res.use_buf);
let mut phi_input_buf = mem::take(&mut self.res.phi_input_buf);
for block in &self.func.blocks {
self.ctx.phi_inputs_of(block.entry, &mut phi_input_buf);
for param in phi_input_buf.drain(..) {
if !visited.set(param) {
continue;
}
self.append_bundle(param, &mut bundle, &mut use_buf);
}
}
self.res.phi_input_buf = phi_input_buf;
for &inst in &self.func.instrs {
if visited.get(inst) || inst == 0 {
continue;
}
self.append_bundle(inst, &mut bundle, &mut use_buf);
}
self.res.use_buf = use_buf;
}
fn append_bundle(&mut self, inst: Nid, bundle: &mut Bundle, use_buf: &mut Vec<Nid>) {
let mut dom = self.ctx.idom_of(inst);
if self.ctx.nodes[dom].kind == Kind::Loop && self.ctx.nodes[inst].kind == Kind::Phi {
dom = self.ctx.nodes.idom(dom);
dom = self.ctx.idom_of(dom);
}
self.ctx.uses_of(inst, use_buf);
for uinst in use_buf.drain(..) {
let cursor = self.ctx.use_block(inst, uinst);
self.reverse_cfg_dfs(cursor, dom, |_, n, b| {
let mut range = b.range.clone();
range.start =
range.start.max(self.ctx.instr_of(inst).map_or(0, |n| n + 1) as usize);
range.end = range.end.min(
self.ctx
.instr_of(uinst)
.filter(|_| self.ctx.nodes.loop_depth(dom) == self.ctx.nodes.loop_depth(n))
.map_or(Nid::MAX, |n| n + 1) as usize,
);
bundle.add(range);
});
}
match self.res.bundles.iter_mut().enumerate().find(|(_, b)| !b.overlaps(bundle)) {
Some((i, other)) => {
other.merge(bundle);
bundle.clear();
self.res.node_to_reg[inst as usize] = i as Reg + 1;
}
None => {
self.res.bundles.push(mem::replace(bundle, Bundle::new(self.func.instrs.len())));
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);
self.res.dfs_seem.clear(self.ctx.nodes.values.len());
while let Some(nid) = self.res.dfs_buf.pop() {
each(self, nid, &self.func.blocks[self.ctx.block_of(nid) as usize]);
if nid == until {
continue;
}
match self.ctx.nodes[nid].kind {
Kind::Then | Kind::Else | Kind::Region | Kind::Loop => {
for &n in self.ctx.nodes[nid].inputs.iter() {
let d = self.ctx.idom_of(n);
if self.res.dfs_seem.set(d) {
self.res.dfs_buf.push(d);
}
}
}
Kind::Start => {}
_ => unreachable!(),
}
}
}
}
#[derive(Default)]
pub struct Res {
pub bundles: Vec<Bundle>,
pub node_to_reg: Vec<Reg>,
use_buf: Vec<Nid>,
phi_input_buf: Vec<Nid>,
dfs_buf: Vec<Nid>,
dfs_seem: BitSet,
}
pub 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);
}
}
#[derive(Default)]
pub struct Func {
pub blocks: Vec<Block>,
pub instrs: Vec<Nid>,
}
pub struct Block {
pub range: Range<usize>,
pub entry: Nid,
}

File diff suppressed because it is too large Load diff

1389
lang/src/ty.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,20 +1,40 @@
#![expect(dead_code)]
use {
alloc::alloc,
core::{
alloc::Layout,
fmt::Debug,
hint::unreachable_unchecked,
marker::PhantomData,
mem::MaybeUninit,
ops::{Deref, DerefMut, Not},
ops::{Deref, DerefMut, Not, Range},
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;
type BitSetUnit = usize;
pub union BitSet {
inline: usize,
inline: BitSetUnit,
alloced: Unique<AllocedBitSet>,
}
@ -58,9 +78,9 @@ impl Default for BitSet {
}
impl BitSet {
const FLAG: usize = 1 << (Self::UNIT - 1);
const FLAG: BitSetUnit = 1 << (Self::UNIT - 1);
const INLINE_ELEMS: usize = Self::UNIT - 1;
const UNIT: usize = core::mem::size_of::<usize>() * 8;
pub const UNIT: usize = core::mem::size_of::<BitSetUnit>() * 8;
pub fn with_capacity(len: usize) -> Self {
let mut s = Self::default();
@ -72,7 +92,7 @@ impl BitSet {
unsafe { self.inline & Self::FLAG != 0 }
}
fn data_and_len(&self) -> (&[usize], usize) {
fn data_and_len(&self) -> (&[BitSetUnit], usize) {
unsafe {
if self.is_inline() {
(core::slice::from_ref(&self.inline), Self::INLINE_ELEMS)
@ -80,16 +100,16 @@ impl BitSet {
let small_vec = self.alloced.as_ref();
(
core::slice::from_raw_parts(
&small_vec.data as *const _ as *const usize,
&small_vec.data as *const _ as *const BitSetUnit,
small_vec.cap,
),
small_vec.cap * core::mem::size_of::<usize>() * 8,
small_vec.cap * Self::UNIT,
)
}
}
}
fn data_mut_and_len(&mut self) -> (&mut [usize], usize) {
fn data_mut_and_len(&mut self) -> (&mut [BitSetUnit], usize) {
unsafe {
if self.is_inline() {
(core::slice::from_mut(&mut self.inline), INLINE_ELEMS)
@ -97,7 +117,7 @@ impl BitSet {
let small_vec = self.alloced.as_mut();
(
core::slice::from_raw_parts_mut(
&mut small_vec.data as *mut _ as *mut usize,
&mut small_vec.data as *mut _ as *mut BitSetUnit,
small_vec.cap,
),
small_vec.cap * Self::UNIT,
@ -124,11 +144,12 @@ impl BitSet {
let index = index as usize;
let (mut data, len) = self.data_mut_and_len();
if core::intrinsics::unlikely(index >= len) {
self.grow(index.next_power_of_two().max(4 * Self::UNIT));
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;
@ -142,7 +163,7 @@ impl BitSet {
let (ptr, prev_len) = unsafe {
if self.is_inline() {
let ptr = alloc::alloc(layout);
*ptr.add(off).cast::<usize>() = self.inline & !Self::FLAG;
*ptr.add(off).cast::<BitSetUnit>() = self.inline & !Self::FLAG;
(ptr, 1)
} else {
let prev_len = self.alloced.as_ref().cap;
@ -153,7 +174,7 @@ impl BitSet {
unsafe {
MaybeUninit::fill(
core::slice::from_raw_parts_mut(
ptr.add(off).cast::<MaybeUninit<usize>>().add(prev_len),
ptr.add(off).cast::<MaybeUninit<BitSetUnit>>().add(prev_len),
slot_count - prev_len,
),
0,
@ -166,7 +187,7 @@ impl BitSet {
fn layout(slot_count: usize) -> (core::alloc::Layout, usize) {
unsafe {
core::alloc::Layout::new::<AllocedBitSet>()
.extend(Layout::array::<usize>(slot_count).unwrap_unchecked())
.extend(Layout::array::<BitSetUnit>(slot_count).unwrap_unchecked())
.unwrap_unchecked()
}
}
@ -184,6 +205,10 @@ impl BitSet {
pub fn clear(&mut self, len: usize) {
self.reserve(len);
self.clear_as_is();
}
pub fn clear_as_is(&mut self) {
if self.is_inline() {
unsafe { self.inline &= Self::FLAG };
} else {
@ -191,7 +216,11 @@ impl BitSet {
}
}
pub fn units<'a>(&'a self, slot: &'a mut usize) -> &'a [usize] {
pub fn approx_unit_cap(&self) -> usize {
self.data_and_len().0.len()
}
pub fn units<'a>(&'a self, slot: &'a mut BitSetUnit) -> &'a [BitSetUnit] {
if self.is_inline() {
*slot = unsafe { self.inline } & !Self::FLAG;
core::slice::from_ref(slot)
@ -200,36 +229,47 @@ impl BitSet {
}
}
pub fn units_mut(&mut self) -> Option<&mut [BitSetUnit]> {
self.is_inline().not().then(|| self.data_mut_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 fn set_range(&mut self, proj_range: Range<usize>) {
if proj_range.is_empty() {
return;
}
}
}
pub struct InlineBitSetView(usize);
self.reserve(proj_range.end);
let (units, _) = self.data_mut_and_len();
impl InlineBitSetView {
pub(crate) fn add_mask(&mut self, tmp: usize) {
debug_assert!(tmp & BitSet::FLAG == 0);
self.0 |= tmp;
if proj_range.start / Self::UNIT == (proj_range.end - 1) / Self::UNIT {
debug_assert!(proj_range.len() <= Self::UNIT);
let mask = ((1 << proj_range.len()) - 1) << (proj_range.start % Self::UNIT);
units[proj_range.start / Self::UNIT] |= mask;
} else {
let fill_range = proj_range.start.div_ceil(Self::UNIT)..proj_range.end / Self::UNIT;
units[fill_range].fill(BitSetUnit::MAX);
let prefix_len = Self::UNIT - proj_range.start % Self::UNIT;
let prefix_mask = ((1 << prefix_len) - 1) << (proj_range.start % Self::UNIT);
units[proj_range.start / Self::UNIT] |= prefix_mask;
let postfix_len = proj_range.end % Self::UNIT;
let postfix_mask = (1 << postfix_len) - 1;
units[proj_range.end / Self::UNIT] |= postfix_mask;
}
}
}
pub struct BitSetIter<'a> {
index: usize,
current: usize,
remining: &'a [usize],
current: BitSetUnit,
remining: &'a [BitSetUnit],
}
impl Iterator for BitSetIter<'_> {
@ -249,7 +289,7 @@ impl Iterator for BitSetIter<'_> {
struct AllocedBitSet {
cap: usize,
data: [usize; 0],
data: [BitSetUnit; 0],
}
#[cfg(test)]
@ -323,6 +363,10 @@ impl Vc {
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
fn len_mut(&mut self) -> &mut Nid {
unsafe {
if self.is_inline() {
@ -360,25 +404,26 @@ impl Vc {
}
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(),
);
if let Some(layout) = self.layout() {
if 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());
debug_assert!(!allcd.is_inline());
*self = allcd;
}
}
@ -532,3 +577,117 @@ struct AllocedVc {
len: Nid,
base: Unique<Nid>,
}
pub trait Ent: Copy {
fn new(index: usize) -> Self;
fn index(self) -> usize;
}
#[repr(transparent)]
pub struct EntSlice<K: Ent, T> {
k: PhantomData<fn(K)>,
data: [T],
}
impl<'a, K: Ent, T> From<&'a [T]> for &'a EntSlice<K, T> {
fn from(value: &'a [T]) -> Self {
unsafe { core::mem::transmute(value) }
}
}
impl<K: Ent, T> core::ops::Index<K> for EntSlice<K, T> {
type Output = T;
fn index(&self, index: K) -> &Self::Output {
&self.data[index.index()]
}
}
pub struct EntVec<K: Ent, T> {
data: ::alloc::vec::Vec<T>,
k: PhantomData<fn(K)>,
}
impl<K: Ent, T> Default for EntVec<K, T> {
fn default() -> Self {
Self { data: Default::default(), k: PhantomData }
}
}
impl<K: Ent, T> EntVec<K, T> {
pub fn clear(&mut self) {
self.data.clear();
}
pub fn is_empty(&self) -> bool {
self.data.is_empty()
}
pub fn len(&self) -> usize {
self.data.len()
}
pub fn push(&mut self, value: T) -> K {
let k = K::new(self.data.len());
self.data.push(value);
k
}
pub fn next(&self, index: K) -> Option<&T> {
self.data.get(index.index() + 1)
}
pub fn shadow(&mut self, len: usize)
where
T: Default,
{
if self.data.len() < len {
self.data.resize_with(len, Default::default);
}
}
pub fn iter(&self) -> core::slice::Iter<T> {
self.data.iter()
}
}
impl<K: Ent, T> core::ops::Index<K> for EntVec<K, T> {
type Output = T;
fn index(&self, index: K) -> &Self::Output {
&self.data[index.index()]
}
}
impl<K: Ent, T> core::ops::IndexMut<K> for EntVec<K, T> {
fn index_mut(&mut self, index: K) -> &mut Self::Output {
&mut self.data[index.index()]
}
}
macro_rules! decl_ent {
($(
$vis:vis struct $name:ident($index:ty);
)*) => {$(
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
$vis struct $name($index);
impl crate::utils::Ent for $name {
fn new(index: usize) -> Self {
Self(index as $index)
}
fn index(self) -> usize {
self.0 as _
}
}
impl core::fmt::Display for $name {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, concat!(stringify!($name), "{}"), self.0)
}
}
)*};
}
pub(crate) use decl_ent;

View file

@ -4,41 +4,44 @@ main:
LI32 r32, 1148846080w
CP r2, r32
JAL r31, r0, :sin
FMUL32 r33, r1, r32
FTI32 r1, r33, 1b
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:
LI32 r4, 1124073472w
LI32 r5, 1078530011w
FMUL32 r7, r2, r4
FDIV32 r9, r7, r5
FTI32 r11, r9, 1b
ANDI r10, r11, 255d
ITF64 r5, r11
MULI64 r4, r10, 4d
LRA r3, r0, :SIN_TABLE
LI32 r7, 1086918619w
FC64T32 r9, r5, 1b
ADDI64 r5, r11, 64d
ADD64 r8, r3, r4
LI32 r1, 1132462080w
FMUL32 r6, r9, r7
ANDI r7, r5, 255d
LI32 r5, 1056964608w
LD r4, r8, 0a, 4h
FDIV32 r8, r6, r1
MULI64 r6, r7, 4d
FMUL32 r10, r4, r5
FSUB32 r11, r2, r8
ADD64 r9, r3, r6
FMUL32 r2, r11, r10
LD r12, r9, 0a, 4h
FSUB32 r5, r12, r2
FMUL32 r7, r5, r11
FADD32 r1, r4, r7
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
MULI64 r15, r15, 4d
LRA r16, r0, :sin_table
LI32 r17, 1086918619w
ITF32 r18, r14
ADDI64 r14, r14, 64d
ADD64 r15, r16, r15
LI32 r19, 1132462080w
FMUL32 r17, r18, r17
ANDI r14, r14, 255d
LI32 r18, 1056964608w
LD r15, r15, 0a, 4h
FDIV32 r17, r17, r19
MULI64 r14, r14, 4d
FMUL32 r18, r15, r18
FSUB32 r13, r13, r17
ADD64 r14, r16, 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: 1303
code size: 1311
ret: 826
status: Ok(())

View file

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

View file

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

View file

@ -2,26 +2,31 @@ main:
ADDI64 r254, r254, -56d
ST r31, r254, 24a, 32h
LI64 r32, 1d
ADDI64 r2, r254, 0d
ADDI64 r33, r254, 0d
ST r32, r254, 0a, 8h
LI64 r33, 2d
ST r33, r254, 8a, 8h
LI64 r34, 2d
ST r34, r254, 8a, 8h
LI64 r34, 4d
ST r34, r254, 16a, 8h
CP r2, r33
JAL r31, r0, :pass
ADD64 r1, r1, r32
CP r33, r1
ADD64 r32, r33, r32
CP r1, r32
LD r31, r254, 24a, 32h
ADDI64 r254, r254, 56d
JALA r0, r31, 0a
pass:
LD r4, r2, 8a, 8h
MULI64 r7, r4, 8d
LD r5, r2, 0a, 8h
ADD64 r10, r7, r2
ADD64 r9, r4, r5
LD r1, r10, 0a, 8h
ADD64 r1, r1, r9
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: 231
code size: 246
ret: 8
status: Ok(())

View file

@ -1,7 +1,8 @@
main:
LRA r1, r0, :SIN_TABLE
LD r1, r1, 80a, 8h
LRA r13, r0, :sin_table
LD r13, r13, 80a, 8h
CP r1, r13
JALA r0, r31, 0a
code size: 767
code size: 770
ret: 1736
status: Ok(())

View file

@ -1,13 +1,14 @@
main:
LI64 r1, 1d
JNE r2, r1, :0
CP r14, r2
LI64 r13, 1d
JNE r14, r13, :0
JMP :1
0: LI64 r7, 0d
JNE r2, r7, :2
LI64 r1, 2d
0: JNE r14, r0, :2
LI64 r13, 2d
JMP :1
2: LI64 r1, 3d
1: JALA r0, r31, 0a
code size: 79
2: LI64 r13, 3d
1: CP r1, r13
JALA r0, r31, 0a
code size: 75
ret: 2
status: Ok(())

View file

@ -1,25 +1,30 @@
main:
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LRA r2, r0, :"abඞ\n\r\t56789\0"
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 r2, r0, :"fff\0"
LRA r33, r0, :"fff\0"
CP r2, r33
JAL r31, r0, :str_len
ADD64 r1, r1, r32
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
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:
LI8 r6, 0b
LI64 r1, 0d
2: LD r8, r2, 0a, 1h
ANDI r8, r8, 255d
ANDI r6, r6, 255d
JNE r8, r6, :0
CP r13, r2
CP r15, r0
CP r14, r15
2: LD r16, r13, 0a, 1h
ANDI r16, r16, 255d
JNE r16, r15, :0
CP r1, r14
JMP :1
0: ADDI64 r2, r2, 1d
ADDI64 r1, r1, 1d
0: ADDI64 r13, r13, 1d
ADDI64 r14, r14, 1d
JMP :2
1: JALA r0, r31, 0a
code size: 216

View file

@ -4,10 +4,10 @@ main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :foo
LI64 r1, 0d
CP r1, r0
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 95
code size: 88
ret: 0
status: Ok(())

View file

@ -0,0 +1,65 @@
box:
CP r13, r2
CP r1, r13
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
LI32 r32, 1065353216w
CP r2, r32
JAL r31, r0, :box
CP r33, r1
CP r2, r0
JAL r31, r0, :box
CP r34, r1
FCMPLT32 r33, r33, r34
ANDI r33, r33, 255d
JNE r33, r0, :0
CP r2, r32
JAL r31, r0, :box
CP r33, r1
CP r2, r0
JAL r31, r0, :box
CP r34, r1
FCMPGT32 r33, r33, r34
NOT r33, r33
ANDI r33, r33, 255d
JNE r33, r0, :1
CP r2, r0
JAL r31, r0, :box
CP r33, r1
CP r2, r32
JAL r31, r0, :box
CP r34, r1
FCMPGT32 r33, r33, r34
ANDI r33, r33, 255d
JNE r33, r0, :2
CP r2, r0
JAL r31, r0, :box
CP r33, r1
CP r2, r32
JAL r31, r0, :box
CP r32, r1
FCMPLT32 r32, r33, r32
NOT r32, r32
ANDI r32, r32, 255d
JNE r32, r0, :3
CP r1, r0
JMP :4
3: LI64 r32, 4d
CP r1, r32
JMP :4
2: LI64 r32, 3d
CP r1, r32
JMP :4
1: LI64 r32, 2d
CP r1, r32
JMP :4
0: LI64 r32, 1d
CP r1, r32
4: LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 355
ret: 0
status: Ok(())

View file

@ -1,7 +1,8 @@
main:
LRA r1, r0, :a
LD r1, r1, 0a, 8h
LRA r13, r0, :a
LD r13, r13, 0a, 8h
CP r1, r13
JALA r0, r31, 0a
code size: 47
code size: 50
ret: 50
status: Ok(())

View file

@ -1,7 +1,8 @@
main:
LRA r1, r0, :a
LD r1, r1, 0a, 8h
LRA r13, r0, :a
LD r13, r13, 0a, 8h
CP r1, r13
JALA r0, r31, 0a
code size: 47
code size: 50
ret: 50
status: Ok(())

View file

@ -1,20 +1,19 @@
cond:
LI64 r1, 0d
CP r1, r0
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
JAL r31, r0, :cond
LI64 r32, 0d
CP r33, r32
JNE r1, r33, :0
CP r32, r33
CP r1, r32
CP r32, r0
CP r33, r1
JNE r33, r32, :0
JMP :1
0: LI64 r1, 2d
1: LD r31, r254, 0a, 24h
0: LI64 r32, 2d
1: CP r1, r32
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 134
code size: 117
ret: 0
status: Ok(())

View file

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

View file

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

View file

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

View file

@ -0,0 +1,19 @@
main:
LI64 r15, 3d
LI64 r16, 10d
CP r14, r0
CP r13, r14
3: JNE r13, r16, :0
LI64 r14, -10d
ADD64 r14, r13, r14
CP r1, r14
JMP :1
0: DIRU64 r0, r17, r13, r15
JNE r17, r14, :2
JMP :2
2: ADDI64 r13, r13, 1d
JMP :3
1: JALA r0, r31, 0a
code size: 103
ret: 0
status: Ok(())

View file

@ -1,5 +1,11 @@
main:
fun:
UN
code size: 9
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
JAL r31, r0, :fun
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
code size: 64
ret: 0
status: Err(Unreachable)

View file

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

View file

@ -1,30 +1,29 @@
main:
ADDI64 r254, r254, -12d
LI8 r1, 255b
ST r1, r254, 0a, 1h
LI8 r4, 0b
ST r4, r254, 1a, 1h
ST r4, r254, 2a, 1h
ST r1, r254, 3a, 1h
LI32 r9, 0w
ST r9, r254, 4a, 4h
LI32 r12, 2w
ST r12, r254, 8a, 4h
LD r3, r254, 8a, 4h
ANDI r3, r3, 4294967295d
ANDI r12, r12, 4294967295d
JEQ r3, r12, :0
LI64 r1, 0d
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: LD r10, r254, 4a, 4h
ANDI r10, r10, 4294967295d
ANDI r9, r9, 4294967295d
JEQ r10, r9, :2
LI64 r1, 64d
0: ANDI r13, r13, 4294967295d
JEQ r13, r0, :2
LI64 r13, 64d
CP r1, r13
JMP :1
2: LI64 r1, 512d
2: LI64 r13, 512d
CP r1, r13
1: ADDI64 r254, r254, 12d
JALA r0, r31, 0a
code size: 257
code size: 235
ret: 512
status: Ok(())

View file

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

View file

@ -0,0 +1,28 @@
main:
ADDI64 r254, r254, -40d
ST r31, r254, 24a, 16h
LI64 r32, 1d
ST r32, r254, 0a, 8h
ST r0, r254, 8a, 8h
ST r0, r254, 16a, 8h
LD r2, r254, 8a, 16h
JAL r31, r0, :pass
CP r32, r1
CP r1, r32
LD r31, r254, 24a, 16h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
pass:
ADDI64 r254, r254, -16d
ST r2, r254, 0a, 16h
ADDI64 r2, r254, 0d
CP r13, r2
LD r14, r13, 0a, 8h
LD r13, r13, 8a, 8h
ADD64 r13, r13, r14
CP r1, r13
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 235
ret: 0
status: Ok(())

View file

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

View file

@ -1,105 +1,114 @@
continue_and_state_change:
LI64 r7, 3d
LI64 r8, 4d
LI64 r9, 2d
LI64 r10, 10d
6: JLTU r2, r10, :0
CP r1, r2
CP r13, r2
LI64 r16, 3d
LI64 r17, 2d
LI64 r18, 10d
CP r15, r0
LI64 r14, 4d
6: JLTU r13, r18, :0
JMP :1
0: JNE r2, r9, :2
CP r2, r8
0: JNE r13, r17, :2
CP r13, r14
JMP :3
2: JNE r2, r7, :4
LI64 r1, 0d
1: JMP :5
4: ADDI64 r2, r2, 1d
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, -24d
ST r31, r254, 0a, 24h
LI64 r32, 1d
LI64 r33, 0d
CP r1, r33
1: JNE r1, r32, :0
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, 24h
ADDI64 r254, r254, 24d
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -64d
ST r31, r254, 0a, 64h
LI64 r32, 0d
CP r2, r32
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r2, r0
JAL r31, r0, :multiple_breaks
LI64 r32, 3d
CP r33, r1
LI64 r1, 3d
JEQ r33, r1, :0
LI64 r1, 1d
JEQ r33, r32, :0
LI64 r32, 1d
CP r1, r32
JMP :1
0: CP r34, r1
LI64 r35, 4d
CP r2, r35
0: LI64 r33, 4d
CP r2, r33
JAL r31, r0, :multiple_breaks
CP r36, r35
LI64 r37, 10d
JEQ r1, r37, :2
LI64 r1, 2d
LI64 r34, 10d
CP r35, r1
JEQ r35, r34, :2
LI64 r32, 2d
CP r1, r32
JMP :1
2: CP r2, r32
2: CP r2, r0
JAL r31, r0, :state_change_in_break
JEQ r1, r32, :3
CP r1, r34
CP r35, r1
JEQ r35, r0, :3
CP r1, r32
JMP :1
3: CP r2, r36
3: CP r2, r33
JAL r31, r0, :state_change_in_break
JEQ r1, r37, :4
CP r1, r36
CP r35, r1
JEQ r35, r34, :4
CP r1, r33
JMP :1
4: CP r2, r37
4: CP r2, r34
JAL r31, r0, :continue_and_state_change
JEQ r1, r37, :5
LI64 r1, 5d
CP r33, r1
JEQ r33, r34, :5
LI64 r32, 5d
CP r1, r32
JMP :1
5: CP r2, r34
5: CP r2, r32
JAL r31, r0, :continue_and_state_change
JEQ r1, r32, :6
LI64 r1, 6d
CP r32, r1
JEQ r32, r0, :6
LI64 r32, 6d
CP r1, r32
JMP :1
6: CP r38, r32
JAL r31, r0, :infinite_loop
CP r1, r38
1: LD r31, r254, 0a, 64h
ADDI64 r254, r254, 64d
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:
LI64 r6, 3d
LI64 r5, 10d
4: JLTU r2, r5, :0
CP r1, r2
CP r13, r2
LI64 r14, 3d
LI64 r15, 10d
4: JLTU r13, r15, :0
JMP :1
0: ADDI64 r1, r2, 1d
JNE r1, r6, :2
1: JMP :3
2: CP r2, r1
JMP :4
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:
LI64 r5, 3d
LI64 r6, 10d
4: JLTU r2, r6, :0
CP r1, r2
CP r13, r2
LI64 r14, 3d
LI64 r15, 10d
4: JLTU r13, r15, :0
JMP :1
0: JNE r2, r5, :2
LI64 r1, 0d
1: JMP :3
2: ADDI64 r2, r2, 1d
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: 668
code size: 667
ret: 10
status: Ok(())

View file

@ -1,53 +1,55 @@
check_platform:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
JAL r31, r0, :x86_fb_ptr
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
CP r32, r1
CP r1, r32
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -64d
ST r31, r254, 0a, 64h
ADDI64 r254, r254, -56d
ST r31, r254, 0a, 56h
JAL r31, r0, :check_platform
LI64 r32, 0d
LI64 r33, 30d
LI64 r34, 100d
CP r35, r32
CP r36, r32
CP r37, r32
5: JLTU r35, r33, :0
ADDI64 r36, r36, 1d
CP r2, r32
CP r3, r36
CP r4, r33
CP r35, r0
LI64 r36, 30d
LI64 r37, 100d
CP r34, r35
CP r32, r35
CP r33, r35
5: JLTU r34, r36, :0
ADDI64 r32, r32, 1d
CP r2, r35
CP r3, r32
CP r4, r36
JAL r31, r0, :set_pixel
JEQ r1, r37, :1
CP r1, r32
CP r34, r1
JEQ r34, r33, :1
CP r1, r35
JMP :2
1: CP r38, r32
JNE r36, r34, :3
CP r1, r37
1: JNE r32, r37, :3
CP r1, r33
JMP :2
3: CP r1, r37
CP r35, r38
3: CP r34, r35
JMP :4
0: CP r1, r37
CP r38, r32
ADDI64 r1, r1, 1d
ADDI64 r35, r35, 1d
4: CP r32, r38
CP r37, r1
JMP :5
2: LD r31, r254, 0a, 64h
ADDI64 r254, r254, 64d
0: ADDI64 r33, r33, 1d
ADDI64 r34, r34, 1d
4: JMP :5
2: LD r31, r254, 0a, 56h
ADDI64 r254, r254, 56d
JALA r0, r31, 0a
set_pixel:
MUL64 r7, r3, r4
ADD64 r1, r7, r2
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 r1, 100d
LI64 r13, 100d
CP r1, r13
JALA r0, r31, 0a
code size: 330
code size: 329
ret: 3000
status: Ok(())

View file

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

View file

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

View file

@ -0,0 +1,21 @@
b:
CP r13, r3
CP r1, r13
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -32d
ST r31, r254, 8a, 24h
ADDI64 r32, r254, 0d
LI64 r33, 100d
ST r33, r254, 0a, 8h
CP r2, r32
CP r3, r33
JAL r31, r0, :b
CP r32, r1
CP r1, r32
LD r31, r254, 8a, 24h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 137
ret: 100
status: Ok(())

View file

@ -1,24 +1,38 @@
add:
ADD64 r1, r2, r3
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
add:
ADD32 r1, r2, r3
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LI32 r3, 2w
CP r2, r3
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
JAL r31, r0, :add
LI32 r32, 2w
CP r2, r32
CP r3, r32
JAL r31, r0, :add
CP r32, r1
LI64 r3, 3d
LI64 r2, 1d
LI64 r33, 3d
LI64 r34, 1d
CP r2, r34
CP r3, r33
JAL r31, r0, :add
ANDI r33, r32, 4294967295d
SUB64 r1, r33, r1
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
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: 158
code size: 209
ret: 0
status: Ok(())

View file

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

View file

@ -1,127 +1,219 @@
deinit:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r32, r2
LD r33, r2, 16a, 8h
LI64 r4, 8d
MUL64 r3, r33, r4
CP r34, r32
LD r2, r34, 0a, 8h
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, 32h
ADDI64 r254, r254, 32d
LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
free:
CP r10, r2
LRA r7, r0, :FREE_SYS_CALL
LD r2, r7, 0a, 8h
CP r5, r4
CP r4, r3
CP r3, r10
ECA
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -56d
ST r31, r254, 24a, 32h
ADDI64 r32, r254, 0d
deinit:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r32, r2
LI64 r33, 1d
LD r34, r32, 16a, 8h
LD r35, r32, 0a, 8h
CP r2, r35
CP r3, r34
CP r4, r33
JAL r31, r0, :free
CP r1, r32
JAL r31, r0, :new
LI64 r3, 69d
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
CP r13, r1
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -88d
ST r31, r254, 48a, 40h
ADDI64 r32, r254, 24d
CP r1, r32
JAL r31, r0, :new
LI64 r33, 35d
CP r2, r32
CP r3, r33
JAL r31, r0, :push
LD r33, r254, 0a, 8h
LD r34, r33, 0a, 8h
ADDI64 r33, r254, 0d
CP r1, r33
JAL r31, r0, :new
LI8 r34, 34b
CP r2, r33
CP r3, r34
JAL r31, r0, :push
LD r34, r254, 0a, 8h
LD r34, r34, 0a, 1h
LD r35, r254, 24a, 8h
LD r35, r35, 0a, 8h
CP r2, r33
JAL r31, r0, :deinit
CP r2, r32
JAL r31, r0, :deinit
CP r1, r34
LD r31, r254, 24a, 32h
ADDI64 r254, r254, 56d
ANDI r32, r34, 255d
ADD64 r32, r35, r32
CP r1, r32
LD r31, r254, 48a, 40h
ADDI64 r254, r254, 88d
JALA r0, r31, 0a
malloc:
CP r9, r2
LRA r5, r0, :MALLOC_SYS_CALL
LD r2, r5, 0a, 8h
CP r4, r3
CP r3, r9
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
LI64 r4, 0d
ADDI64 r5, r254, 0d
ST r4, r254, 0a, 8h
ST r4, r254, 8a, 8h
ST r4, r254, 16a, 8h
BMC r5, r1, 24h
CP r15, r1
LI64 r14, 8d
ADDI64 r13, r254, 0d
ST r14, r254, 0a, 8h
ST r0, r254, 8a, 8h
ST r0, r254, 16a, 8h
BMC r13, r15, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
new:
ADDI64 r254, r254, -24d
CP r15, r1
LI64 r14, 1d
ADDI64 r13, r254, 0d
ST r14, r254, 0a, 8h
ST r0, r254, 8a, 8h
ST r0, r254, 16a, 8h
BMC r13, r15, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
push:
ADDI64 r254, r254, -192d
ST r31, r254, 0a, 192h
CP r32, r3
LI64 r33, 1d
LD r34, r2, 8a, 8h
LD r35, r2, 16a, 8h
ADDI64 r254, r254, -80d
ST r31, r254, 0a, 80h
CP r36, r2
JNE r35, r34, :0
LI64 r37, 0d
JNE r35, r37, :1
CP r38, r33
CP r37, r3
LI64 r35, 1d
LD r33, r36, 8a, 8h
LD r32, r36, 16a, 8h
JNE r32, r33, :0
JNE r32, r0, :1
CP r32, r35
JMP :2
1: MULI64 r38, r35, 2d
2: LI64 r39, 8d
MUL64 r2, r38, r39
CP r3, r39
1: MULI64 r32, r32, 2d
2: CP r2, r32
CP r3, r35
JAL r31, r0, :malloc
CP r40, r1
LI64 r1, 0d
CP r41, r40
JNE r41, r1, :3
ST r32, r36, 16a, 8h
CP r34, r1
JNE r34, r0, :3
CP r1, r0
JMP :4
3: CP r40, r41
CP r42, r36
ST r38, r42, 16a, 8h
LD r36, r42, 8a, 8h
MULI64 r43, r36, 8d
LD r44, r42, 0a, 8h
ADD64 r45, r44, r43
CP r46, r40
9: LD r2, r42, 0a, 8h
LD r47, r42, 8a, 8h
JNE r45, r44, :5
JEQ r47, r37, :6
CP r4, r39
MUL64 r3, r47, r4
3: LD r32, r36, 0a, 8h
ADD64 r38, r33, r32
CP r33, r34
7: LD r39, r36, 0a, 8h
LD r40, r36, 8a, 8h
JNE r38, r32, :5
JEQ r40, r0, :6
CP r2, r39
CP r3, r40
CP r4, r35
JAL r31, r0, :free
CP r1, r40
JMP :6
6: ST r34, r36, 0a, 8h
JMP :0
5: LD r39, r32, 0a, 1h
ST r39, r33, 0a, 1h
ADDI64 r33, r33, 1d
ADDI64 r32, r32, 1d
JMP :7
6: CP r1, r40
7: ST r1, r42, 0a, 8h
JMP :8
5: CP r1, r40
CP r4, r39
ADDI64 r41, r46, 8d
ADDI64 r48, r44, 8d
LD r49, r44, 0a, 8h
ST r49, r46, 0a, 8h
CP r44, r48
CP r46, r41
JMP :9
0: CP r42, r36
8: LD r50, r42, 8a, 8h
MULI64 r51, r50, 8d
LD r52, r42, 0a, 8h
ADD64 r1, r52, r51
CP r3, r32
ST r3, r1, 0a, 8h
LD r53, r42, 8a, 8h
ADD64 r54, r53, r33
ST r54, r42, 8a, 8h
4: LD r31, r254, 0a, 192h
ADDI64 r254, r254, 192d
0: LD r32, r36, 8a, 8h
LD r33, r36, 0a, 8h
ADD64 r33, r32, r33
ST r37, r33, 0a, 1h
ADD64 r32, r32, r35
ST r32, r36, 8a, 8h
CP r1, r33
4: LD r31, r254, 0a, 80h
ADDI64 r254, r254, 80d
JALA r0, r31, 0a
code size: 955
push:
ADDI64 r254, r254, -88d
ST r31, r254, 0a, 88h
CP r36, r2
CP r37, r3
LI64 r35, 1d
LD r33, r36, 8a, 8h
LD r32, r36, 16a, 8h
JNE r32, r33, :0
JNE r32, r0, :1
CP r32, r35
JMP :2
1: MULI64 r32, r32, 2d
2: LI64 r38, 8d
MUL64 r34, r32, r38
CP r2, r34
CP r3, r38
JAL r31, r0, :malloc
ST r32, r36, 16a, 8h
CP r34, r1
JNE r34, r0, :3
CP r1, r0
JMP :4
3: MULI64 r33, r33, 8d
LD r32, r36, 0a, 8h
ADD64 r39, r32, r33
CP r33, r34
7: LD r40, r36, 0a, 8h
LD r41, r36, 8a, 8h
JNE r39, r32, :5
JEQ r41, r0, :6
MUL64 r32, r41, r38
CP r2, r40
CP r3, r32
CP r4, r38
JAL r31, r0, :free
JMP :6
6: ST r34, r36, 0a, 8h
JMP :0
5: LD r40, r32, 0a, 8h
ST r40, r33, 0a, 8h
ADDI64 r33, r33, 8d
ADDI64 r32, r32, 8d
JMP :7
0: LD r32, r36, 8a, 8h
MULI64 r33, r32, 8d
LD r34, r36, 0a, 8h
ADD64 r33, r34, r33
ST r37, r33, 0a, 8h
ADD64 r32, r32, r35
ST r32, r36, 8a, 8h
CP r1, r33
4: LD r31, r254, 0a, 88h
ADDI64 r254, r254, 88d
JALA r0, r31, 0a
code size: 1623
ret: 69
status: Ok(())

View file

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

View file

@ -0,0 +1,23 @@
inb:
CP r1, r0
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
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 r33, r33, r0
CMPUI r33, r33, 0d
NOT r33, r33
ST r33, r32, 0a, 1h
1: LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
code size: 164
ret: 0
status: Ok(())

View file

@ -1,9 +1,10 @@
main:
LRA r2, r0, :complex_global_var
LD r3, r2, 0a, 8h
ADDI64 r1, r3, 5d
ST r1, r2, 0a, 8h
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: 71
code size: 74
ret: 55
status: Ok(())

View file

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

View file

@ -1,20 +1,20 @@
main:
ADDI64 r254, r254, -128d
LI8 r5, 69b
LI64 r6, 128d
LI64 r7, 0d
ADDI64 r4, r254, 0d
2: LD r12, r254, 42a, 1h
JLTU r7, r6, :0
ANDI r1, r12, 255d
ADDI64 r14, r254, 0d
LI8 r15, 69b
LI64 r16, 128d
CP r13, r0
2: LD r17, r254, 42a, 1h
JLTU r13, r16, :0
ANDI r13, r17, 255d
CP r1, r13
JMP :1
0: ADDI64 r3, r7, 1d
ADD64 r7, r4, r7
ST r5, r7, 0a, 1h
CP r7, r3
0: ADD64 r17, r14, r13
ST r15, r17, 0a, 1h
ADDI64 r13, r13, 1d
JMP :2
1: ADDI64 r254, r254, 128d
JALA r0, r31, 0a
code size: 145
code size: 138
ret: 69
status: Ok(())

View file

@ -1,30 +1,36 @@
fib:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
LI64 r1, 1d
LI64 r32, 2d
JGTU r2, r32, :0
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: CP r33, r2
SUB64 r2, r33, r1
CP r34, r33
0: SUB64 r33, r32, r33
CP r2, r33
JAL r31, r0, :fib
CP r2, r34
CP r35, r1
SUB64 r2, r2, r32
CP r33, r1
SUB64 r32, r32, r34
CP r2, r32
JAL r31, r0, :fib
ADD64 r1, r1, r35
1: LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
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, -8d
ST r31, r254, 0a, 8h
LI64 r2, 10d
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LI64 r32, 10d
CP r2, r32
JAL r31, r0, :fib
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
CP r32, r1
CP r1, r32
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 211
code size: 229
ret: 55
status: Ok(())

View file

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

View file

@ -1,17 +1,20 @@
main:
LI64 r7, 6d
LRA r3, r0, :gb
LI64 r6, 0d
LD r8, r3, 0a, 8h
CMPU r9, r8, r6
CMPUI r9, r9, 0d
ORI r11, r9, 0d
ANDI r11, r11, 255d
JNE r11, r0, :0
CP r4, r7
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 r4, 1d
1: SUB64 r1, r4, r7
0: LI64 r13, 1d
1: SUB64 r13, r13, r14
CP r1, r13
JALA r0, r31, 0a
code size: 131
ret: 0

View file

@ -0,0 +1,19 @@
main:
ADDI64 r254, r254, -72d
ADDI64 r13, r254, 24d
ST r0, r254, 24a, 8h
LI64 r14, 1d
ST r14, r254, 32a, 8h
LI64 r14, 2d
ST r14, r254, 40a, 8h
ADDI64 r14, r254, 0d
BMC r13, r14, 24h
ADDI64 r13, r254, 48d
BMC r14, r13, 24h
LD r13, r254, 48a, 8h
CP r1, r13
ADDI64 r254, r254, 72d
JALA r0, r31, 0a
code size: 159
ret: 0
status: Ok(())

View file

@ -1,39 +1,29 @@
main:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
JAL r31, r0, :scalar_values
LI64 r32, 0d
CP r33, r32
JEQ r1, r33, :0
LI64 r1, 1d
CP r32, r1
JEQ r32, r0, :0
LI64 r32, 1d
CP r1, r32
JMP :1
0: JAL r31, r0, :structs
CP r34, r33
JEQ r1, r34, :2
CP r32, r1
JEQ r32, r0, :2
JAL r31, r0, :structs
CP r32, r1
CP r1, r32
JMP :1
2: CP r1, r34
CP r33, r34
1: LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
2: CP r1, r0
1: LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
scalar_values:
LI64 r1, 0d
CP r1, r0
JALA r0, r31, 0a
structs:
ADDI64 r254, r254, -32d
LI64 r1, 5d
ST r1, r254, 16a, 8h
ST r1, r254, 24a, 8h
LD r5, r254, 16a, 8h
ADDI64 r7, r5, 15d
ST r7, r254, 0a, 8h
LI64 r10, 20d
ST r10, r254, 8a, 8h
LD r1, r254, 0a, 8h
SUB64 r1, r1, r10
ADDI64 r254, r254, 32d
CP r1, r0
JALA r0, r31, 0a
code size: 307
code size: 164
ret: 0
status: Ok(())

View file

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

View file

@ -1,109 +1,101 @@
main:
ADDI64 r254, r254, -106d
ST r31, r254, 58a, 48h
ADDI64 r254, r254, -98d
ST r31, r254, 58a, 40h
ADDI64 r32, r254, 33d
ADDI64 r2, r254, 34d
ADDI64 r6, r254, 1d
LI64 r33, 0d
ADDI64 r4, r254, 17d
ADDI64 r33, r254, 34d
ST r32, r254, 34a, 8h
LI64 r34, 100d
ADDI64 r7, r254, 0d
LI8 r35, 1b
ST r33, r254, 1a, 8h
ST r33, r254, 17a, 8h
ST r0, r254, 1a, 8h
ST r0, r254, 17a, 8h
ST r34, r254, 42a, 8h
LI8 r36, 0b
ST r35, r254, 0a, 1h
ST r33, r254, 9a, 8h
ST r33, r254, 25a, 8h
ST r0, r254, 9a, 8h
ST r0, r254, 25a, 8h
ST r34, r254, 50a, 8h
ST r36, r254, 33a, 1h
CP r3, r4
CP r5, r6
LD r3, r3, 0a, 16h
LD r5, r5, 0a, 16h
LD r7, r7, 0a, 1h
ST r0, r254, 33a, 1h
CP r2, r33
LD r3, r254, 17a, 16h
LD r5, r254, 1a, 16h
LD r7, r254, 0a, 1h
JAL r31, r0, :put_filled_rect
LD r31, r254, 58a, 48h
ADDI64 r254, r254, 106d
LD r31, r254, 58a, 40h
ADDI64 r254, r254, 98d
JALA r0, r31, 0a
put_filled_rect:
ADDI64 r254, r254, -212d
ST r32, r254, 108a, 104h
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
LI64 r8, 25d
LI64 r32, 2d
LI64 r6, 8d
ADDI64 r33, r254, 25d
ADDI64 r34, r254, 50d
LI8 r35, 5b
ST r35, r254, 25a, 1h
LD r36, r5, 0a, 8h
ST r36, r254, 26a, 4h
LI64 r37, 1d
ST r37, r254, 30a, 4h
ST r7, r254, 34a, 8h
ST r35, r254, 50a, 1h
ST r36, r254, 51a, 4h
ST r37, r254, 55a, 4h
ST r7, r254, 59a, 8h
CP r38, r7
LD r7, r3, 8a, 8h
LD r39, r5, 8a, 8h
ADD64 r11, r39, r7
SUB64 r4, r11, r37
LD r40, r2, 8a, 8h
MUL64 r5, r40, r4
LD r9, r2, 0a, 8h
ADD64 r10, r9, r5
LD r2, r3, 0a, 8h
ADD64 r41, r2, r10
MUL64 r3, r40, r7
ADD64 r4, r9, r3
ADD64 r42, r2, r4
3: JGTU r39, r37, :0
JNE r39, r37, :1
ADDI64 r4, r254, 0d
ST r35, r254, 0a, 1h
ST r36, r254, 1a, 4h
ST r37, r254, 5a, 4h
ST r38, r254, 9a, 8h
ST r42, r254, 17a, 8h
CP r2, r6
CP r3, r32
CP r5, r8
CP r16, r7
ADDI64 r17, r254, 25d
LI8 r18, 5b
ST r18, r254, 25a, 1h
LD r19, r13, 0a, 8h
ST r19, r254, 26a, 4h
LI64 r20, 1d
ST r20, r254, 30a, 4h
ST r16, r254, 34a, 8h
LI64 r21, 25d
ADDI64 r22, r254, 50d
ST r18, r254, 50a, 1h
ST r19, r254, 51a, 4h
ST r20, r254, 55a, 4h
ST r16, r254, 59a, 8h
LI64 r23, 2d
LI64 r24, 8d
LD r25, r15, 8a, 8h
LD r13, r13, 8a, 8h
ADD64 r26, r13, r25
SUB64 r26, r26, r20
LD r27, r14, 8a, 8h
MUL64 r26, r27, r26
LD r14, r14, 0a, 8h
ADD64 r26, r14, r26
LD r28, r15, 0a, 8h
MUL64 r15, r27, r25
ADD64 r14, r14, r15
ADD64 r15, r28, r26
ADD64 r14, r28, r14
3: JGTU r13, r20, :0
JNE r13, r20, :1
ADDI64 r13, r254, 0d
ST r18, r254, 0a, 1h
ST r19, r254, 1a, 4h
ST r20, r254, 5a, 4h
ST r16, r254, 9a, 8h
ST r14, r254, 17a, 8h
CP r2, r24
CP r3, r23
CP r4, r13
CP r5, r21
ECA
JMP :1
1: JMP :2
0: CP r3, r32
CP r43, r6
CP r44, r8
ST r42, r254, 67a, 8h
CP r2, r43
CP r4, r34
CP r5, r44
0: ST r14, r254, 67a, 8h
CP r2, r24
CP r3, r23
CP r4, r22
CP r5, r21
ECA
ST r41, r254, 42a, 8h
CP r2, r43
CP r3, r32
CP r4, r33
CP r5, r44
ST r15, r254, 42a, 8h
CP r2, r24
CP r3, r23
CP r4, r17
CP r5, r21
ECA
ADD64 r42, r40, r42
SUB64 r41, r41, r40
SUB64 r39, r39, r32
CP r6, r43
CP r8, r44
SUB64 r13, r13, r23
SUB64 r15, r15, r27
ADD64 r14, r27, r14
JMP :3
2: LD r32, r254, 108a, 104h
ADDI64 r254, r254, 212d
2: ADDI64 r254, r254, 108d
JALA r0, r31, 0a
code size: 917
code size: 842
ret: 0
status: Ok(())

View file

@ -1,8 +0,0 @@
main:
LRA r2, r0, :x
LI64 r1, 0d
ST r1, r2, 0a, 8h
JALA r0, r31, 0a
code size: 57
ret: 0
status: Ok(())

View file

@ -1,20 +1,25 @@
main:
ADDI64 r254, r254, -32d
ST r31, r254, 16a, 16h
ADDI64 r3, r254, 0d
ADDI64 r2, r254, 8d
LI64 r32, 0d
ST r32, r254, 0a, 8h
ST r32, r254, 8a, 8h
LI64 r4, 1024d
ADDI64 r254, r254, -48d
ST r31, r254, 16a, 32h
ADDI64 r32, r254, 0d
ADDI64 r33, r254, 8d
ST r0, r254, 0a, 8h
ST r0, r254, 8a, 8h
LI64 r34, 1024d
CP r2, r33
CP r3, r32
CP r4, r34
JAL r31, r0, :set
ANDI r1, r1, 4294967295d
LD r31, r254, 16a, 16h
ADDI64 r254, r254, 32d
CP r32, r1
ANDI r32, r32, 4294967295d
CP r1, r32
LD r31, r254, 16a, 32h
ADDI64 r254, r254, 48d
JALA r0, r31, 0a
set:
CP r1, r4
CP r13, r4
CP r1, r13
JALA r0, r31, 0a
code size: 167
code size: 175
ret: 1024
status: Ok(())

View file

@ -1,29 +1,28 @@
integer_range:
ADDI64 r254, r254, -16d
ST r32, r254, 0a, 16h
CP r32, r2
CP r33, r3
LI64 r3, 4d
LI64 r2, 3d
CP r13, r2
CP r14, r3
LI64 r15, 4d
LI64 r16, 3d
CP r2, r16
CP r3, r15
ECA
CP r2, r32
CP r3, r33
SUB64 r11, r3, r2
ADDI64 r3, r11, 1d
DIRU64 r0, r3, r1, r3
ADD64 r1, r3, r2
LD r32, r254, 0a, 16h
ADDI64 r254, r254, 16d
SUB64 r14, r14, r13
ADDI64 r14, r14, 1d
CP r15, r1
DIRU64 r0, r14, r15, r14
ADD64 r13, r14, r13
CP r1, r13
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r3, 1000d
LI64 r2, 0d
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LI64 r32, 1000d
CP r2, r0
CP r3, r32
JAL r31, r0, :integer_range
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 210
code size: 164
ret: 42
status: Ok(())

View file

@ -0,0 +1,49 @@
chars:
ADDI64 r254, r254, -32d
ST r3, r254, 16a, 16h
ADDI64 r3, r254, 16d
CP r13, r3
ADDI64 r14, r254, 0d
BMC r13, r14, 16h
LD r1, r14, 0a, 16h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -56d
ST r31, r254, 32a, 24h
LRA r32, r0, :Hello, World!
ST r32, r254, 16a, 8h
LI64 r32, 13d
ST r32, r254, 24a, 8h
ADDI64 r32, r254, 0d
LD r3, r254, 16a, 16h
JAL r31, r0, :chars
ST r1, r32, 0a, 16h
2: CP r2, r32
JAL r31, r0, :next
CP r33, r1
ANDI r33, r33, 65535d
JNE r33, r0, :0
JMP :1
0: JMP :2
1: LD r31, r254, 32a, 24h
ADDI64 r254, r254, 56d
JALA r0, r31, 0a
next:
CP r13, r2
LD r14, r13, 8a, 8h
JNE r14, r0, :0
CP r1, r0
JMP :1
0: LD r15, r13, 0a, 8h
ADDI64 r15, r15, 1d
ST r15, r13, 0a, 8h
ADDI64 r14, r14, -1d
LD r15, r15, 0a, 1h
ST r14, r13, 8a, 8h
ORI r13, r15, 32768d
CP r1, r13
1: JALA r0, r31, 0a
code size: 423
ret: 0
status: Ok(())

View file

@ -1,16 +1,16 @@
main:
ADDI64 r254, r254, -8d
LI64 r3, 0d
LI64 r2, 10d
ST r2, r254, 0a, 8h
2: LD r1, r254, 0a, 8h
JNE r1, r3, :0
LI64 r13, 10d
ST r13, r254, 0a, 8h
2: LD r13, r254, 0a, 8h
JNE r13, r0, :0
CP r1, r13
JMP :1
0: ADDI64 r11, r1, -1d
ST r11, r254, 0a, 8h
0: ADDI64 r13, r13, -1d
ST r13, r254, 0a, 8h
JMP :2
1: ADDI64 r254, r254, 8d
JALA r0, r31, 0a
code size: 126
code size: 119
ret: 0
status: Ok(())

View file

@ -1,25 +1,28 @@
fib:
LI64 r4, 1d
LI64 r5, 0d
CP r1, r5
CP r10, r4
2: JNE r2, r5, :0
CP r13, r2
LI64 r17, 1d
CP r15, r0
CP r14, r15
CP r16, r17
2: JNE r13, r15, :0
CP r1, r14
JMP :1
0: ADD64 r1, r10, r1
SUB64 r2, r2, r4
CP r3, r1
CP r1, r10
CP r10, r3
0: SUB64 r13, r13, r17
ADD64 r14, r16, r14
SWA r14, r16
JMP :2
1: JALA r0, r31, 0a
main:
ADDI64 r254, r254, -8d
ST r31, r254, 0a, 8h
LI64 r2, 10d
ADDI64 r254, r254, -16d
ST r31, r254, 0a, 16h
LI64 r32, 10d
CP r2, r32
JAL r31, r0, :fib
LD r31, r254, 0a, 8h
ADDI64 r254, r254, 8d
CP r32, r1
CP r1, r32
LD r31, r254, 0a, 16h
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
code size: 153
code size: 155
ret: 55
status: Ok(())

View file

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

View file

@ -0,0 +1,36 @@
decide:
ADDI64 r254, r254, -24d
CP r14, r2
CP r15, r1
ADDI64 r13, r254, 0d
ST r14, r254, 0a, 8h
ST r0, r254, 8a, 8h
ST r0, r254, 16a, 8h
BMC r13, r15, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
main:
ADDI64 r254, r254, -104d
ST r31, r254, 72a, 32h
ADDI64 r32, r254, 48d
CP r1, r32
CP r2, r0
JAL r31, r0, :decide
ADDI64 r33, r254, 24d
BMC r32, r33, 24h
LI64 r34, 1d
CP r1, r33
CP r2, r34
JAL r31, r0, :decide
ADDI64 r34, r254, 0d
BMC r32, r34, 24h
LD r32, r254, 24a, 8h
LD r33, r254, 0a, 8h
ADD64 r32, r33, r32
CP r1, r32
LD r31, r254, 72a, 32h
ADDI64 r254, r254, 104d
JALA r0, r31, 0a
code size: 273
ret: 1
status: Ok(())

View file

@ -0,0 +1,59 @@
main:
ADDI64 r254, r254, -72d
ST r31, r254, 32a, 40h
LRA r32, r0, :"Goodbye, World!\0"
LRA r33, r0, :"Hello, World!\0"
ST r32, r254, 16a, 8h
ST r33, r254, 24a, 8h
LD r2, r254, 24a, 8h
LD r3, r254, 16a, 8h
JAL r31, r0, :print
ADDI64 r34, r254, 8d
ADDI64 r35, r254, 0d
ST r32, r254, 8a, 8h
ST r33, r254, 0a, 8h
CP r2, r35
CP r3, r34
JAL r31, r0, :print2
LD r31, r254, 32a, 40h
ADDI64 r254, r254, 72d
JALA r0, r31, 0a
print:
ADDI64 r254, r254, -16d
ST r2, r254, 8a, 8h
ADDI64 r2, r254, 8d
CP r13, r2
ST r3, r254, 0a, 8h
ADDI64 r3, r254, 0d
CP r14, r3
LD r13, r13, 0a, 8h
LI64 r15, 37d
CP r2, r15
CP r3, r13
ECA
LD r13, r14, 0a, 8h
CP r2, r15
CP r3, r13
ECA
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
print2:
CP r13, r2
CP r14, r3
LD r13, r13, 0a, 8h
LI64 r15, 37d
CP r2, r15
CP r3, r13
ECA
LD r13, r14, 0a, 8h
CP r2, r15
CP r3, r13
ECA
JALA r0, r31, 0a
Hello, World!
Goodbye, World!
Hello, World!
Goodbye, World!
code size: 435
ret: 0
status: Ok(())

View file

@ -1,27 +1,23 @@
main:
ADDI64 r254, r254, -32d
ST r31, r254, 0a, 32h
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
JAL r31, r0, :opaque
CP r32, r1
CP r33, r1
JAL r31, r0, :opaque
LI64 r33, 0d
CP r1, r32
JNE r1, r33, :0
CP r32, r1
LI64 r1, 0d
CP r34, r32
JNE r33, r0, :0
CP r32, r0
JMP :1
0: CP r34, r1
LD r1, r34, 0a, 8h
1: JEQ r34, r33, :2
LD r1, r34, 0a, 8h
0: LD r32, r33, 0a, 8h
1: JEQ r33, r0, :2
LD r32, r33, 0a, 8h
JMP :2
2: LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
2: CP r1, r32
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
opaque:
LI64 r1, 0d
CP r1, r0
JALA r0, r31, 0a
code size: 183
code size: 150
ret: 0
status: Ok(())

View file

@ -1,6 +1,6 @@
test.hb:4:17: unwrap is not needed since the value is (provably) never null, remove it, or replace with '@as(<expr_ty>, <opt_expr>)'
ptr := @unwrap(always_nn)
^
test.hb:6:16: unwrap is incorrect since the value is (provably) always null, make sure your logic is correct
ptr = @unwrap(always_n)
^
test.hb:4:18: unwrap is not needed since the value is (provably) never null, remove it, or replace with '@as(<expr_ty>, <opt_expr>)'
ptr1 := @unwrap(always_nn)
^
test.hb:6:18: unwrap is incorrect since the value is (provably) always null, make sure your logic is correct
ptr2 := @unwrap(always_n)
^

View file

@ -0,0 +1,31 @@
main:
ADDI64 r254, r254, -30d
ST r31, r254, 6a, 24h
ADDI64 r32, r254, 0d
2: JAL r31, r0, :return_fn
ST r1, r32, 0a, 6h
LD r33, r254, 0a, 1h
ANDI r33, r33, 255d
JEQ r33, r0, :0
LI64 r32, 1d
CP r1, r32
JMP :1
0: JMP :2
1: LD r31, r254, 6a, 24h
ADDI64 r254, r254, 30d
JALA r0, r31, 0a
return_fn:
ADDI64 r254, r254, -6d
LI8 r13, 1b
ST r13, r254, 0a, 1h
ST r0, r254, 1a, 1h
ST r0, r254, 2a, 1h
ST r0, r254, 3a, 1h
ST r0, r254, 4a, 1h
ST r0, r254, 5a, 1h
LD r1, r254, 0a, 6h
ADDI64 r254, r254, 6d
JALA r0, r31, 0a
code size: 277
ret: 1
status: Ok(())

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