Compare commits

..

819 commits

Author SHA1 Message Date
Jakub Doka 08fc9d6ab6
gc
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-25 21:55:26 +01:00
Jakub Doka abcb3434f8
removing unwraps that can cause panics when typechecking
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-25 21:54:32 +01:00
Jakub Doka 4ebf1c7996
fixing outdated error message
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-25 21:34:58 +01:00
Jakub Doka 3f11e19a91
properly handling generic method calls
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-25 21:29:30 +01:00
Jakub Doka 51bfbdf7ae
tests are now autogenerated from the readme, this makes test names DRY
I want to reduce friction of adding new tests as much as possible

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-25 21:17:03 +01:00
Jakub Doka 5c8f7c9c79
i am not useless after all, the invalid store elimination removed
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-25 20:27:16 +01:00
Jakub Doka 3491814b4f
i am tired
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-22 21:50:03 +01:00
Jakub Doka ee434e6135
more forgotten stuff
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-22 21:43:53 +01:00
Jakub Doka 9afe191bca
fixed a missing feature
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-22 21:41:28 +01:00
Jakub Doka 8ededb8612
adding standard instruction logging utility
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-22 21:33:15 +01:00
Jakub Doka 9c4b84ce33
fixing the precedence regarding slice ranges
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-22 20:43:49 +01:00
Jakub Doka 5909837015
making sure sliced pointer is loaded
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-22 18:53:52 +01:00
Jakub Doka 9f67b22aa2
maybe now the bug is fixed
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-22 17:01:36 +01:00
Jakub Doka 939d0807fb
fixing slice slicing
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-22 16:18:44 +01:00
Jakub Doka 888b38ad4c
frgot to transition @nameof to slices
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-21 23:55:02 +01:00
Jakub Doka 5275a7e0fd
adding slices
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-21 23:44:33 +01:00
Jakub Doka 418fd0039e
making the pointered arrays work properly
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-21 17:36:42 +01:00
Jakub Doka d220823d78
fixing the bug in previous commit
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-21 17:14:33 +01:00
Jakub Doka af19f4e30d
making the identifiers accessible if they are captured
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-21 14:21:58 +01:00
Jakub Doka 1621d93e86
adding more stuff to the blog
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-20 22:54:46 +01:00
Jakub Doka 4b3b6af70e
adding habdler for tuples with known type
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-20 11:59:21 +01:00
Jakub Doka f59c0c1092
some syntax changes
mainly added the explicit type for declarations

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-20 11:32:18 +01:00
Jakub Doka 8ad58ee6b6
changing the array sintax to be distinc from tuple
the arrays can be declared in a more natural way
type of the element can aso be inferred form the first element

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-20 10:17:34 +01:00
Jakub Doka 6e8eb059f6
adding tuples
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-19 23:08:36 +01:00
Jakub Doka 969ea57e3f
optimizing the bitset used in register allocation
also fixing an enum bug

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-19 19:43:30 +01:00
Jakub Doka cfd3eac0a8
making the instruction scheduling smarter
the instructions that are only depended by phis are pushed to the end of
the block, which usually saves copy instructions

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-19 11:00:19 +01:00
Jakub Doka a8aba7e7c2
making the Call node less special
return value is now a separate node pinned to the call

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-19 10:48:05 +01:00
Jakub Doka f05c61a99e
adding @ChildOf directive
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-18 00:06:57 +01:00
Jakub Doka e769fa8dba
removing error for needless @as temporarly
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 22:29:16 +01:00
Jakub Doka b3f858f64b
adding @error directive
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 21:35:47 +01:00
Jakub Doka 1584ec7563
adding @Any directive
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 21:11:02 +01:00
Jakub Doka 6085177982
fixed the unreachable functions deleting branches
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 20:53:13 +01:00
Jakub Doka 47014c6164
lifting the restriction for inlining to allow normal functions as well
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 19:32:30 +01:00
Jakub Doka 3702a99d03
fixing another incorrect file reporting
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 19:30:42 +01:00
Jakub Doka 248bdf003a
making the else branch have less priority
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 19:10:56 +01:00
Jakub Doka d3f3fe98e3
propagating unreachable for functions returning never type
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 19:08:53 +01:00
Jakub Doka 14cf5efaa5
handling comptime known match
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 19:01:01 +01:00
Jakub Doka 95496116b0
making @len work on strings
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 18:30:19 +01:00
Jakub Doka 86f7d70747
adding default values to struct fields and @kindof directive
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 17:51:14 +01:00
Jakub Doka 0516ce68f4
adding @nameof
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 16:46:43 +01:00
Jakub Doka 945e5c70f6
extractng Nodes from son.rs
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 00:18:44 +01:00
Jakub Doka ec9bb886f8
removing more repetative patterns
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-17 00:02:00 +01:00
Jakub Doka 127fdb3cc5
reducing repeating patters
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 23:47:31 +01:00
Jakub Doka e65c72e19f
properly type checking, null checks are fixed
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 23:32:08 +01:00
Jakub Doka 1ca9529302
fixing missing antidependencies
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 17:37:14 +01:00
Jakub Doka c0d957e70c
removing needless errors
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 15:05:19 +01:00
Jakub Doka b9b8233a53
typo
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 15:03:07 +01:00
Jakub Doka d2fa41039b
strengthening the error recovery
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 15:00:48 +01:00
Jakub Doka 9fe8d6bbff
support integer to float coersion in more places
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 14:38:41 +01:00
Jakub Doka 04680c8b7c
fixing an incredible edge case
this basically only happens if the Vc oscilates between 7 and 8 elemenst

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 14:27:23 +01:00
Jakub Doka a1e692eac7
maybe fixed the float op fold
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 14:06:12 +01:00
Jakub Doka 8bf2d1a266
flag the function as inline after checking style
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 14:04:30 +01:00
Jakub Doka 1571938e9f
bools can now upcast to any integer
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 13:54:52 +01:00
Jakub Doka f7d5bccdd9
fixing @itf type inference
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 13:49:20 +01:00
Jakub Doka 07d4fe416a
forgot to add defer handling to unrolled loops
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 13:27:15 +01:00
Jakub Doka b2be007ef0
adding unrolled loops, struct indexing and @len directive
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 13:20:47 +01:00
Jakub Doka bfac81c807
fixing a bug with ITF selecting based of input instead of output type
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 10:35:11 +01:00
Jakub Doka ef36e21475
making the compiler emit FMA instructions
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-16 10:16:53 +01:00
Jakub Doka 8138d6664f
properly releasing the strongrefs now
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-15 23:15:07 +01:00
Jakub Doka 9f43e3bb92
refactoring some stuff and loosening a requrement on assert
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-15 20:49:37 +01:00
Jakub Doka 6fba7da782
more general tree walking algorightm was needed (probably)
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-15 20:35:44 +01:00
Jakub Doka 7837eeb90d
implementing the loop iteration optimization
the multiplication and addition to a pointer is replaced with simply
incremrnting the pointer it self

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-15 19:37:37 +01:00
Jakub Doka 5a7a01ca02
adding the stack offset elision for return values as well
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-15 17:57:22 +01:00
Jakub Doka f9c47f86ad
fixing a glaring bug, where the elidded offset is also offset
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-15 17:42:20 +01:00
Jakub Doka 48a0c8d0b9
POC for removeing needless stack offset computes when only value is used
TBD: there are far more cases where this will apply

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-15 17:17:41 +01:00
Jakub Doka 00f6729d31
supporting ascii literals
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-14 21:02:29 +01:00
Jakub Doka dc96c8b10a
the items accesed outside the nested scope no longer get duplicated
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-14 18:50:13 +01:00
Jakub Doka 91e35b72ee
Fixing the invalid code bricking the UI
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-14 15:33:11 +01:00
Jakub Doka 5aeeedbdce
fixing non pointer struct method receiver not counting as use
forgot to strip pointer

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-14 15:19:02 +01:00
Jakub Doka fae75072f4
removing hardcoded html files and replacing them with markdown
the markdown gets transpiled on build and built files are then included
in the server executable

Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-14 13:17:58 +01:00
Jakub Doka 71ba2c2486
Dividing function into template and instance, rmoving cumbersome options
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-02 15:51:12 +01:00
Jakub Doka c5d5301b7b
Removing some clones and fixing parent scoping in case of globals
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-02 12:59:07 +01:00
Jakub Doka c553c3d9e9
Removing repetative code, adding ent slice to properly index modules
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-01 19:04:27 +01:00
Jakub Doka 9ce446b507
Adding the simplest version of unions
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-01 15:11:38 +01:00
Jakub Doka 3b4b30b2bd
Restructuring the compiler
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-12-01 14:01:44 +01:00
Jakub Doka cf672beb79
making ableos path resolver public
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-11-30 22:45:59 +01:00
Jakub Doka 3f6ebdd009
fixing phi moves (longer move cycles)
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-11-30 22:33:13 +01:00
Jakub Doka 19aca050ed
add new ableos path resolver, separate platform independent code
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-11-30 18:57:29 +01:00
Jakub Doka d368ac023b
making error fields public
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-11-30 16:49:42 +01:00
Jakub Doka 8ea6c5cfcc
fixing miscompilation of generic struct functions
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-11-30 16:30:58 +01:00
Jakub Doka e7cd2c0129
making the loader function customizable
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-11-30 15:44:51 +01:00
Jakub Doka e44d003e7f
completing the generic types example
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-11-30 13:38:44 +01:00
Jakub Doka a2ca8d98df
fixing a bug with floationg point comparison
Signed-off-by: Jakub Doka <jakub.doka2@gmail.com>
2024-11-30 12:40:39 +01:00
Jakub Doka a3355a59c0
update on incomplete example 2024-11-24 18:55:30 +01:00
Jakub Doka b63b832383
wut 2024-11-24 18:51:20 +01:00
Jakub Doka 116f045a5f
adding defer 2024-11-24 18:50:55 +01:00
Jakub Doka 784d552c1d
fixing scoping bug 2024-11-24 16:43:45 +01:00
Jakub Doka 24a3aed360
implementing struct method for non generic contexts 2024-11-24 16:17:57 +01:00
Jakub Doka 58ee5c0a56
in progress of adding methods 2024-11-24 14:47:38 +01:00
Jakub Doka 9dfb2eb606
fg 2024-11-24 11:26:38 +01:00
Jakub Doka 5df4fb8882
changing the gcm to not mutate nodes in recursive functions 2024-11-23 19:47:17 +01:00
Jakub Doka 86ca959ea3
removing needless data dependencies 2024-11-23 17:23:33 +01:00
Jakub Doka f353bd5882
passing down the inference of 'void' to statements 2024-11-23 15:33:28 +01:00
Jakub Doka cad0a828d0
updating tests 2024-11-23 15:28:27 +01:00
Jakub Doka fb119bc6eb
allowing compatison of types 2024-11-23 15:28:02 +01:00
Jakub Doka aa83ed2ec9
fixing the annoyance 2024-11-23 14:19:47 +01:00
Jakub Doka fb11c94af4
fixing some bugs and extending runtime of programs 2024-11-23 12:35:16 +01:00
Jakub Doka b030b1eeb7
fixing user posts not being displayed 2024-11-23 11:14:03 +01:00
Jakub Doka 4856533b22
making sure dependencies are formatted 2024-11-23 10:31:38 +01:00
Jakub Doka d8d039b67a
adding capability to run posts and displaing cumulative runs 2024-11-23 10:18:05 +01:00
Jakub Doka b760d9ef75
improving theming 2024-11-23 08:38:41 +01:00
Jakub Doka e587de1778
fixing some lighthouse issues and bad coloring of icons 2024-11-23 00:59:58 +01:00
Jakub Doka b12579ff65
adding import count to posts 2024-11-23 00:06:25 +01:00
Jakub Doka 0aa355695a
fixing the buggg, finally 2024-11-22 21:01:45 +01:00
Jakub Doka 4a857d2317
garbage 2024-11-22 19:50:59 +01:00
Jakub Doka 2253ac6198
fixing some bugs 2024-11-22 19:50:36 +01:00
Jakub Doka 05a7bd0583
chaning css for no reason 2024-11-18 13:10:44 +01:00
able ab55ec0240 Merge pull request 'point the profile button at a users fully qualified profile page' (#24) from able-patch-1 into trunk
Reviewed-on: https://git.ablecorp.us/AbleOS/holey-bytes/pulls/24
2024-11-18 06:09:19 -06:00
able 8353ab58a5 point the profile button at a users fully qualified profile page
Signed-off-by: able <abl3theabove@gmail.com>
2024-11-18 06:08:46 -06:00
Jakub Doka f527d61c1e
limitting request body size 2024-11-18 11:28:47 +01:00
Jakub Doka f3879cb013
fixing js errors, adding password change form 2024-11-18 10:31:30 +01:00
Jakub Doka e89511b14c
fixing position reporting for optimized returns 2024-11-17 22:26:31 +01:00
Jakub Doka 1c135a3050
adding interesting asert 2024-11-17 21:43:02 +01:00
Jakub Doka f83194359c
fixed yet another off by 1 2024-11-17 21:35:41 +01:00
Jakub Doka becd5c4b7f
well? 2024-11-17 21:30:20 +01:00
Jakub Doka 37dd13cab2
brah 2024-11-17 21:27:30 +01:00
Jakub Doka bc2dd82eb7
welp 2024-11-17 21:25:22 +01:00
Jakub Doka aa2de502cc
saving 2024-11-17 21:09:36 +01:00
Jakub Doka 542c69fd60
changing case checking to a warning 2024-11-17 20:57:10 +01:00
Jakub Doka 95e9270fef
adding case checking 2024-11-17 20:04:53 +01:00
Jakub Doka fe5a8631f6
fixed a bug of not marking idents as used 2024-11-17 18:44:24 +01:00
Jakub Doka 8892dd729a
fixing the false return location 2024-11-17 18:15:58 +01:00
Jakub Doka a7718e1220
cleaning up some code 2024-11-17 17:14:44 +01:00
Jakub Doka e079bbd312
forgot to support explicit enum type 2024-11-17 16:30:59 +01:00
Jakub Doka 12b9d43754
adding minimal enums 2024-11-17 16:25:39 +01:00
Jakub Doka 397b2a4b1b
fixed a stack prelude postlude being needlesly generated + struct can now be compared 2024-11-17 10:06:10 +01:00
Jakub Doka 12bb7029b4
fixed float comparison in the vm 2024-11-16 21:38:10 +01:00
Jakub Doka a64383e72b
saving 2024-11-16 20:52:38 +01:00
Igor null 2034152c83 added syntax highlighting to depell 2024-11-16 13:48:31 -06:00
Jakub Doka 13714eb513
removing stable feature supression 2024-11-16 17:41:40 +01:00
Jakub Doka 4088bd18b1
fixing compilation error for ableos 2024-11-16 17:29:30 +01:00
Jakub Doka e94b812b3b
removing the regalloc dependency 2024-11-16 14:22:34 +01:00
Jakub Doka e5d6b35f66
removing needless copies to zero register for unused values 2024-11-16 13:42:17 +01:00
Jakub Doka e6df9b6b01
cleanup 2024-11-16 11:46:59 +01:00
Jakub Doka baa70d3f12
removing needless copy into ret register 2024-11-16 10:16:35 +01:00
Jakub Doka ec4499e519
removing duplicate un instruction 2024-11-16 10:05:56 +01:00
Jakub Doka 085c593add
removing needless truncation of zeroth register 2024-11-16 10:03:08 +01:00
Jakub Doka 867a750d8f
hopefully this is less of a mess now 2024-11-15 23:18:40 +01:00
Jakub Doka b1b6d9eba1
fix the inference on itf 2024-11-15 22:53:22 +01:00
Jakub Doka 12be64965f
maybe fixed mandelbrot 2024-11-15 22:35:03 +01:00
Jakub Doka 7058efe75c
reverting phi transformation 2024-11-15 19:32:04 +01:00
Jakub Doka afc1c5aac5
orginizing null checks better to get more peephole hits 2024-11-15 14:36:33 +01:00
Jakub Doka 83146cfd61
adding a simple peephole for phis 2024-11-15 13:06:03 +01:00
Jakub Doka bb625a9e19
some cleanump and ironing out bugs in new regalloc 2024-11-15 12:04:05 +01:00
Jakub Doka 81cf39b602
adding more info for assert 2024-11-14 21:57:51 +01:00
Jakub Doka e4da9cc927
adding regalloc, fixing needless semicolon insertion 2024-11-14 21:50:10 +01:00
Jakub Doka 454b0ffd1c
adding regalloc option 2024-11-14 21:34:31 +01:00
Jakub Doka 981c17ff19
fixing function destinations 2024-11-14 20:25:52 +01:00
Jakub Doka d01e31b203
fixing stack return values 2024-11-13 16:18:21 +01:00
Jakub Doka 9cb273a04b
edge case of returning stack from inlined function 2024-11-13 15:56:37 +01:00
Jakub Doka 2e2b7612d9
some cleanup 2024-11-13 15:45:45 +01:00
Jakub Doka f493c2776f
forgot to fix return 2024-11-13 15:27:35 +01:00
Jakub Doka f77bc52465
fixing unchanged parsed file 2024-11-13 15:25:27 +01:00
Jakub Doka f524013c34
making use of zero register 2024-11-13 10:28:16 +01:00
Jakub Doka 3c86eafe72
fixing another problem with rescheduling 2024-11-13 08:49:25 +01:00
Jakub Doka 0d87bf8f09
removing browser from nontest envs 2024-11-12 22:30:10 +01:00
Jakub Doka e5a4561f07
very funny fix 2024-11-12 21:54:23 +01:00
Jakub Doka b71031c146
prolly fix 2024-11-12 21:12:57 +01:00
Jakub Doka dd51961fbb
adding assert for better error 2024-11-12 21:10:42 +01:00
Jakub Doka 63f2a0dac0
well... 2024-11-12 20:59:12 +01:00
Jakub Doka 4ec88e3397
adding pointer edgecase 2024-11-12 20:42:04 +01:00
Jakub Doka f1e715e9bd
refactoring truncation 2024-11-12 19:02:29 +01:00
Jakub Doka 80fd0e89b4
fixing the inline flag delegation with generic functions 2024-11-12 17:32:20 +01:00
Jakub Doka 9949086011
allowing eca in inline functions 2024-11-12 17:11:39 +01:00
Jakub Doka c701eb7b6d
adding extra test 2024-11-12 12:54:36 +01:00
Jakub Doka f1deab11c9
making better peepholes and fixing overoptimization on memory swaps 2024-11-12 12:20:08 +01:00
Jakub Doka f079daa42d
removing redundant loop phys 2024-11-11 23:33:36 +01:00
Jakub Doka 7cac9382ad
we assumed unary operands are at leas 4bytes bit 2024-11-11 23:17:13 +01:00
Jakub Doka ce2f7d2059
fixing negation truncation 2024-11-11 23:02:02 +01:00
Jakub Doka f5f9060803
adding missing instruction selection 2024-11-11 22:36:20 +01:00
Jakub Doka ad7fb5d0fc
adding errors for useless type hints 2024-11-11 22:34:42 +01:00
Jakub Doka d99672b751
fixing too strict assert 2024-11-11 22:14:54 +01:00
Jakub Doka 7def052749
preventing dangling nodes due to cycles in loop phys 2024-11-11 21:55:18 +01:00
Jakub Doka b2eefa5b83
removing assert that can cause crashes 2024-11-11 09:07:36 +01:00
Jakub Doka 3c35557872
fixing type variables in loops 2024-11-11 09:06:34 +01:00
Jakub Doka b6274f3455
fixing yet another edge case 2024-11-10 20:30:35 +01:00
Jakub Doka c61efc3933
adding inline functions 2024-11-10 19:35:48 +01:00
Jakub Doka 654005eea2
updating tests 2024-11-10 18:59:29 +01:00
Jakub Doka 335e6ec20a
fixing nasty aclass clobber priority bug 2024-11-10 18:56:33 +01:00
Jakub Doka 1e02efc1eb
improving load analisys 2024-11-10 17:32:24 +01:00
Jakub Doka 8b98c2ed1b
fixing different file imports 2024-11-10 12:26:30 +01:00
Jakub Doka c353d28be0
fixing another problem with const 2024-11-10 12:03:15 +01:00
Jakub Doka 7865d692a1
fixing the rescheduling edgecase 2024-11-10 11:04:04 +01:00
Jakub Doka 29a23cec0c
removing dbg 2024-11-10 10:30:45 +01:00
Jakub Doka 5dce4df2a1
fixing more stuff 2024-11-10 10:28:02 +01:00
Jakub Doka 42a713aeae
fixing wrong instruction selection 2024-11-10 09:17:43 +01:00
Jakub Doka 823c78bf74
preventing deduplication to cause bugs 2024-11-09 15:14:03 +01:00
Jakub Doka c657084451
integer constants can be casted to floats if type is known to be a float 2024-11-09 14:02:13 +01:00
Jakub Doka 63a1c7feb4
fixing float conversion constant folding 2024-11-09 13:54:08 +01:00
Jakub Doka bedffa9b32
fixing constant fmt newline preservation 2024-11-09 10:58:57 +01:00
Jakub Doka b8032aa840
wrong index for extend 2024-11-09 10:28:53 +01:00
Jakub Doka 65e9f272a8
forgotten dbgs 2024-11-08 23:03:16 +01:00
Jakub Doka d2052cd2a3
adding back the exit code 2024-11-08 21:53:24 +01:00
Jakub Doka 29367d8f8b
fixing compiler pulling function destinations out of the loops 2024-11-08 20:40:18 +01:00
Jakub Doka a299bad75b
adding some simple provenance checks on return values 2024-11-08 11:51:10 +01:00
Jakub Doka 7d48d3beb1
adding constants 2024-11-08 10:57:58 +01:00
Jakub Doka 68c0248189
making type manipulation nicer 2024-11-08 10:25:34 +01:00
Jakub Doka 0ef74d89cb
putting logger back 2024-11-08 08:40:14 +01:00
Jakub Doka 1b2b9f899d
ups 2024-11-08 08:36:13 +01:00
Jakub Doka 455f70db6e
adding better error reporting when compiler crashes errors are now sent trough out buffer 2024-11-08 08:36:00 +01:00
Jakub Doka 0374848b28
fixing formatter not reporting errors 2024-11-07 17:02:22 +01:00
Jakub Doka 513d2c7127
removing log line 2024-11-07 16:42:57 +01:00
Jakub Doka 9d2f419140
fixing messed up flag calculation 2024-11-07 16:39:15 +01:00
Jakub Doka f535ea7b0a
ups, left log lines 2024-11-07 16:05:16 +01:00
Jakub Doka be6d0d3f18
removing wrong graph query in a peephole 2024-11-07 10:47:31 +01:00
Jakub Doka 2718ef8523
ups 2024-11-07 10:43:45 +01:00
Jakub Doka 3ee78f3a31
fixing bugs from the new tests 2024-11-07 10:43:29 +01:00
Jakub Doka 2bac7c1fb3
saving 2024-11-07 08:53:11 +01:00
mlokis 79a3f1ab2b Merge pull request 'various tests' (#23) from koniifer/holey-bytes:trunk into trunk
Reviewed-on: https://git.ablecorp.us/AbleOS/holey-bytes/pulls/23
Reviewed-by: mlokis <mlokis@email.com>
2024-11-07 01:49:56 -06:00
koniifer b15e66b2af test broken sin function 2024-11-06 17:35:01 +00:00
koniifer d2ba7cc101 returning optional issues test 2024-11-06 15:17:03 +00:00
koniifer d3ee72306e optional from eca test 2024-11-05 18:07:04 +00:00
Jakub Doka 87cb77a553
making a Backend trait to separate the different backends we will have in the fucture 2024-11-05 14:52:30 +01:00
Jakub Doka 276d1bb0cf
fixing tab indentation in error messages and depell not displaying errors 2024-11-05 09:41:57 +01:00
Jakub Doka 5cce904135
fixing struct null check on function arguments 2024-11-04 19:57:15 +01:00
Jakub Doka 3338d50672
nasty bug with rescheduled load 2024-11-04 19:18:37 +01:00
Jakub Doka 2e36f32ae0
fixing very sneaky bug 2024-11-04 12:38:47 +01:00
Jakub Doka e8f1d2af8c
allowing 0 idk 2024-11-03 22:54:05 +01:00
Jakub Doka 999b25df8b
adding '_ = <expr>' syntax 2024-11-03 22:27:37 +01:00
Jakub Doka 61250c906a
comparison of non null types to null are now errors 2024-11-03 21:31:46 +01:00
Jakub Doka 44fc9c3e2e
deferring all null checks after the peepholes 2024-11-03 21:13:24 +01:00
Jakub Doka 798000c756
little correction 2024-11-03 10:23:17 +01:00
Jakub Doka 9de631234d
adding unreachable 2024-11-03 10:15:03 +01:00
Jakub Doka 843fbddf3b
loops in inlined functions now work better 2024-11-03 08:59:42 +01:00
Jakub Doka 38a00cbaa0
some start for homemade regalloc 2024-10-31 14:56:55 +01:00
Jakub Doka 4664240e08
eliminating even more todos 2024-10-31 11:10:05 +01:00
Jakub Doka 728d563cea
eliminating more todos 2024-10-31 11:03:58 +01:00
Jakub Doka 56984f08ff
eliminating more todos 2024-10-31 10:56:59 +01:00
Jakub Doka 3f9f99ff65
adding optional values 2024-10-31 10:36:18 +01:00
Jakub Doka 9ed3c7ab9e
saving 2024-10-30 20:20:03 +01:00
Jakub Doka acacd10ee9
microoptimizing bitset 2024-10-30 18:42:25 +01:00
Jakub Doka f6f661cee3
finally struct operators fold into constant 2024-10-30 14:10:46 +01:00
Jakub Doka 4bfb5f192e
fixing the matrix 2024-10-30 13:45:19 +01:00
Jakub Doka ea628c1278
saving 2024-10-29 20:38:33 +01:00
Jakub Doka 7448339605
removing return value temporary optimization sadly 2024-10-29 17:03:00 +01:00
Jakub Doka da7cd5926c
unifiing annoying clobber logic 2024-10-29 15:15:30 +01:00
Jakub Doka 9cf7933251
clobber global loads across functions 2024-10-29 15:04:07 +01:00
Jakub Doka 24b9f9e78b
adding floating point conversions 2024-10-29 14:24:31 +01:00
Jakub Doka 80558ea7e6
adding floating point arithmetic 2024-10-29 13:36:12 +01:00
Jakub Doka 348d9014e3
adding a lot better load elimination 2024-10-29 10:31:52 +01:00
Jakub Doka 30bd6103a6
cleaning up some code 2024-10-29 10:01:37 +01:00
Jakub Doka 97eb985a02
removing specific opts from a fucntion and adding them to the general peepholes 2024-10-29 09:04:49 +01:00
Jakub Doka 7ef1adf7e2
saving 2024-10-28 23:38:57 +01:00
Jakub Doka be828b8c54
properly handling cases when stack is referenced by dofferent part of the memory 2024-10-28 18:53:36 +01:00
Jakub Doka b4b3bae104
fixing storing struct pointer bug 2024-10-28 18:39:42 +01:00
Jakub Doka 33d78fbc52
missing scoping 2024-10-28 17:22:18 +01:00
Jakub Doka be2d38a6d2
making the aliasing analisys bit smarter 2024-10-28 17:19:41 +01:00
Jakub Doka bbd7e12af4
saving this to make sure 2024-10-28 16:18:53 +01:00
Jakub Doka 37db783699
making stack peeps compatible with parallel alias classes 2024-10-28 12:39:26 +01:00
Jakub Doka 948710dc27
fixing an infeence bug 2024-10-28 12:36:46 +01:00
Jakub Doka f0a588fcff
updating test 2024-10-28 11:04:47 +01:00
Jakub Doka 9c32f260a1
cleanup 2024-10-27 21:34:03 +01:00
Jakub Doka 047e1ed15c
adding null 2024-10-27 19:55:11 +01:00
Jakub Doka 2c2f0c048b
cleaning up tests 2024-10-27 19:13:25 +01:00
Jakub Doka 3c12c0e288
removing codegen test outputs 2024-10-27 19:08:22 +01:00
Jakub Doka ca8497550a
updating commandline help 2024-10-27 19:02:13 +01:00
Jakub Doka 849e842336
dont write to the file if the contents did not differ from formatted 2024-10-27 18:56:29 +01:00
Jakub Doka 5c82623db9
removing stuff 2024-10-27 18:37:18 +01:00
Jakub Doka e8a8fa3eb1
simplifiing upcasts, hwich conincidentally allowed more optimizations 2024-10-27 18:21:33 +01:00
Jakub Doka 5926f69e6c
fixing missing upcast 2024-10-27 18:04:50 +01:00
Jakub Doka 83d3fb4919
adding array reformatting rule 2024-10-27 16:07:46 +01:00
Jakub Doka b429534d23
moving hbvm related code into one file 2024-10-27 14:29:14 +01:00
Jakub Doka b187af64a8
removing old compiler 2024-10-27 13:57:00 +01:00
Jakub Doka ce7bb001da
handling infinite loops properly 2024-10-27 11:32:34 +01:00
Jakub Doka 9c90adbfe8
removing idk for scalar values (antipattern) 2024-10-27 00:02:59 +02:00
Jakub Doka db62434736
fixing infinite loop when fetching cycles 2024-10-26 23:24:45 +02:00
Jakub Doka 3d721812f0
adding better dead code elimination 2024-10-26 20:29:31 +02:00
Jakub Doka 5b23a0661b
fixing peep_iter related bugs 2024-10-26 15:18:00 +02:00
Jakub Doka 7c919cd453
fixing nonexistent identifier file mismatch 2024-10-26 14:06:08 +02:00
Jakub Doka bb61526d3e
eliminating more useless stack moves related to return values 2024-10-26 13:43:36 +02:00
Jakub Doka 45e1c6743a
eliminating more useless operations 2024-10-26 12:48:57 +02:00
Jakub Doka 39588579a8
using more typesafe locking 2024-10-26 12:09:53 +02:00
Jakub Doka 9095af6d84
appliing late peepholes 2024-10-26 10:45:50 +02:00
Jakub Doka b62413046d
cleanup 2024-10-26 10:25:42 +02:00
Jakub Doka af4d965b8c
fixed fmt error reporting 2024-10-26 09:53:14 +02:00
Jakub Doka 855da58e06
fixed a binor uga buga 2024-10-26 01:07:35 +02:00
Jakub Doka 2fc24f0f58
ups 2024-10-26 00:37:39 +02:00
Jakub Doka 8016b1fad5
adding rescheduling 2024-10-26 00:34:22 +02:00
Jakub Doka 46f9903562
adding error log when compiler crashes 2024-10-25 23:05:43 +02:00
Jakub Doka 517850f283
fixing undescriptive error or not enough arguments 2024-10-25 22:59:01 +02:00
Jakub Doka faa8dd2e6f
adding pointer checks on ecas 2024-10-25 16:33:56 +02:00
Jakub Doka d23d010917
fixing eror message 2024-10-25 16:08:20 +02:00
Jakub Doka b1da36ecde
fixing upcasting signed to unsigned 2024-10-25 15:45:00 +02:00
Jakub Doka e62aab9b4b
adding better binaro operator errors positions 2024-10-25 15:40:23 +02:00
Jakub Doka 423361a80e
forgottend typecheck on a struct 2024-10-25 15:31:49 +02:00
Jakub Doka 62a7c61cdc
properly selecting li instructions for integer sizes 2024-10-25 15:29:17 +02:00
Jakub Doka 2bab16d3ce
making never type cause less errors 2024-10-25 15:14:32 +02:00
Jakub Doka c88daa4800
adding better negative number inference 2024-10-25 15:07:39 +02:00
Jakub Doka 6988d8893f
changing uint to be the default 2024-10-25 14:51:33 +02:00
Jakub Doka 64e228450f
little cleanup and fixing error recovery 2024-10-25 11:29:54 +02:00
Jakub Doka 897e121eeb
fixing stack alloc overoptimization 2024-10-24 19:57:36 +02:00
Jakub Doka 648bd24d0d
forgot to mul by 8 2024-10-24 16:26:28 +02:00
Jakub Doka aefa7e6405
forgot 2024-10-24 15:49:41 +02:00
Jakub Doka 026f6141e6
forgot 2024-10-24 15:45:16 +02:00
Jakub Doka cb88edea1f
fixing overoptimization of load -> store 2024-10-24 15:39:38 +02:00
Jakub Doka 127e8dcb38
fixing @as misbehaving 2024-10-24 14:10:07 +02:00
Jakub Doka 9c43dafcf5
fixing @as misbehaving 2024-10-24 14:08:17 +02:00
Jakub Doka e65dbcfcbe
fixing bitcasts 2024-10-24 13:58:58 +02:00
Jakub Doka e0d4955bd5
fixing small struct return 2024-10-24 13:25:30 +02:00
Jakub Doka 78ebc3292c
removing useless clobbers 2024-10-24 12:28:18 +02:00
Jakub Doka 0c2db878f0
adding the stack optimizations 2024-10-24 10:21:10 +02:00
Jakub Doka cb9d7f7d1e
okay now it works 2024-10-24 09:43:07 +02:00
Jakub Doka 41b70bec43
should work better 2024-10-23 12:26:07 +02:00
Jakub Doka f013e90936
better somehow 2024-10-22 22:57:40 +02:00
Jakub Doka 6977cb218c
seems to be compiling 2024-10-22 16:54:32 +02:00
Jakub Doka 3f30735eaa
seems to be compiling 2024-10-22 16:53:48 +02:00
Jakub Doka 58f4837ae0
eliminating important todo 2024-10-22 16:03:23 +02:00
Jakub Doka b95bddac7b
ups 2024-10-22 12:57:49 +02:00
Jakub Doka 7d53706e71
adding --optimize flag to the compiler 2024-10-22 12:50:54 +02:00
Jakub Doka 4d699fcbf1
strinc operatos seem to work now 2024-10-22 12:40:41 +02:00
Jakub Doka 5aa6150c70
now the generic types work too 2024-10-22 10:17:16 +02:00
Jakub Doka b0a85f44c9
fixing some bugs and making the generic types work, well not quite 2024-10-22 10:08:50 +02:00
Jakub Doka 2aa5ba9abc
generic functions work now 2024-10-22 07:20:08 +02:00
Jakub Doka 35d34dca54
sweeping trought more tests 2024-10-21 19:57:55 +02:00
Jakub Doka bc817c4ea2
implementing directives 2024-10-21 18:57:23 +02:00
Jakub Doka 0298b32e38
sniping a peephole 2024-10-21 17:29:11 +02:00
Jakub Doka 73c9ccef6a
simplifing code patterns and sixing argument passing 2024-10-21 17:04:29 +02:00
Jakub Doka ad4aed9c98
fixing loop bugs and some optimization edgecases 2024-10-21 15:12:37 +02:00
Jakub Doka 8528bef8cf
adding more tests, fixing pointer math, and integer upcasting 2024-10-20 21:50:08 +02:00
Jakub Doka 11c8755b18
implementing wide returns and adding integer upcast ops 2024-10-20 21:00:56 +02:00
Jakub Doka d5c90b95a7
committy committy 2024-10-20 18:50:10 +02:00
Jakub Doka 1da900461c
fixing struct return and copy miscompilation 2024-10-20 18:49:41 +02:00
Jakub Doka 3aff6fc006
reorganizing the type parser trait 2024-10-20 16:43:25 +02:00
Jakub Doka ccfde6c237
adding more tests and organizing things 2024-10-20 15:33:32 +02:00
Jakub Doka 44c4b71bb3
unifiing the type resolution into a trait 2024-10-20 15:16:55 +02:00
Jakub Doka c3a6e62bf2
implementing strings 2024-10-20 12:22:28 +02:00
Jakub Doka 00949c4ea8
implementing global variables 2024-10-20 10:37:48 +02:00
Jakub Doka 15e4762d4a
cleanup: 2 2024-10-19 19:53:43 +02:00
Jakub Doka 959bfd7f76
cleanup: 1 2024-10-19 19:37:02 +02:00
Jakub Doka 6ad0b41759
fixing code scheduling bugs 2024-10-19 10:17:36 +02:00
Jakub Doka 89cc611f7a
good direction 2024-10-18 16:57:00 +02:00
Jakub Doka cf74fdd99c
adding loops and seeing they totally not work 2024-10-18 16:52:54 +02:00
Jakub Doka 58578dd4b2
adding loops and seeing they totally not work 2024-10-18 16:51:54 +02:00
Jakub Doka 4a7b4e4ead
handling conditional stores 2024-10-18 13:11:11 +02:00
Jakub Doka c900f4ef5c
removing offset from stores and loads 2024-10-18 09:52:50 +02:00
Jakub Doka 3a494147ec
reorganizing tests 2024-10-18 08:43:50 +02:00
Jakub Doka 4336fec653
structs work with optimizations 2024-10-17 22:29:09 +02:00
Jakub Doka 11f6537a09
foo 2024-10-17 19:32:10 +02:00
Jakub Doka da58a5926d
removing some old garbage 2024-10-17 16:08:29 +02:00
Jakub Doka f5ef62c6bb
fixing nasty bug 2024-10-17 15:48:22 +02:00
Jakub Doka f386c332e5
adding link to the hosted page 2024-10-15 12:48:35 +02:00
Jakub Doka 23b90b3dd7
adding tab highlight and some more details in readme 2024-10-15 12:46:36 +02:00
Jakub Doka ea736d8824
lota of progress 2024-10-14 22:04:18 +02:00
Jakub Doka dc2e0cc5b3
implementing dependency fetching 2024-10-14 13:25:38 +02:00
Jakub Doka c9b85f9004
fixing sizeof not storing values 2024-10-13 22:24:57 +02:00
Jakub Doka af147b3cb6
adding test for embed 2024-10-13 20:01:18 +02:00
Jakub Doka 0f8a720fe8
wrong handling of embeds 2024-10-13 16:38:51 +02:00
Jakub Doka 2ab6f6c914
resolving shadoving in inlined functions correctly 2024-10-13 16:16:27 +02:00
Jakub Doka 54d93608aa
correctly implementing big block copies 2024-10-13 15:49:14 +02:00
Jakub Doka 19a6cdd764
implementing embeds 2024-10-13 15:33:57 +02:00
Jakub Doka 2660d976fe
fixing arithmetic bug 2024-10-13 15:22:16 +02:00
Jakub Doka 659ccbd637
adding stricter typechecking 2024-10-13 14:11:17 +02:00
Jakub Doka 3a2367f24f
fixing some bugs 2024-10-13 13:30:00 +02:00
Jakub Doka 0f4ff918d2
fixing bugs and improving memory consumption 2024-10-13 12:25:12 +02:00
Jakub Doka 6d7e726066
fixing generic function inlining 2024-10-12 22:29:52 +02:00
Jakub Doka 9e65f3949d
fixing arg datarace 2024-10-12 22:21:20 +02:00
Jakub Doka bf00dc85b2
progres on inline generic funcs and bettere error message 2024-10-12 22:03:53 +02:00
Jakub Doka 69b58c2b36
progress 2024-10-12 21:25:37 +02:00
Jakub Doka 5364b66629
adding gzip to static files 2024-10-12 15:04:58 +02:00
Jakub Doka c4826d3bfd
fixing feature incompatibility 2024-10-12 13:07:49 +02:00
Jakub Doka 07638caff0
starting the compiler wasm 2024-10-10 19:01:12 +02:00
Jakub Doka 5ef1ec4811
removing obvious temporary allocation 2024-10-10 16:08:03 +02:00
Jakub Doka f0ae65606d
renaming directories, reducing temporary allocations during parsing 2024-10-10 15:48:08 +02:00
Jakub Doka a538c0ddb0
optimizing ast allocation 2024-10-10 13:04:17 +02:00
Jakub Doka c31d1dcb9c
more size opts 2024-10-10 09:51:03 +02:00
Jakub Doka 54a7f85978
progress 2024-10-10 08:35:17 +02:00
Jakub Doka e200c2fc98
lock 2024-10-09 00:17:48 +02:00
Jakub Doka 1626734c1a
some progress 2024-10-09 00:17:13 +02:00
Jakub Doka 13f63c7700
cleanup 2024-10-06 10:51:33 +02:00
Jakub Doka c7dbe1c43d
adding some starter code to the depell 2024-10-05 23:07:33 +02:00
Jakub Doka 4c15f61cb7
something idk 2024-10-04 21:44:29 +02:00
Jakub Doka f1ea01ef0c
unifiing context key maps 2024-10-01 22:53:03 +02:00
Jakub Doka 2361e166cd
save 2024-10-01 21:39:23 +02:00
Jakub Doka 4d913462cb
save 2024-10-01 21:36:23 +02:00
Jakub Doka bdc2c43773
save 2024-10-01 21:33:30 +02:00
Jakub Doka b2254e9820
making compiler a bit smarter about evaluating types (more shortcuts) 2024-10-01 17:43:15 +02:00
Jakub Doka d293e02f62
relaxing by optimizing the compiler 2024-10-01 15:28:18 +02:00
Jakub Doka 1ee8d464c6
fixing infinite recusrion bug 2024-10-01 14:00:41 +02:00
Jakub Doka 2a4d27d8e6
tweak 2024-09-30 22:47:00 +02:00
Jakub Doka 1f5846afaa
fixing struct type display 2024-09-30 22:17:54 +02:00
Jakub Doka 006bc80f12
fixing duplicate structs 2024-09-30 22:15:40 +02:00
Jakub Doka 802e8b5d55
fixing a leaked reg bug when constant happens 2024-09-30 21:55:34 +02:00
Jakub Doka 6b7572f089
logging bugfix 2024-09-30 19:41:52 +02:00
Jakub Doka 1d04287532
separating the cli parsing 2024-09-30 19:35:25 +02:00
Jakub Doka 8b6d9b5de3
transitioning to log crate 2024-09-30 19:27:00 +02:00
Jakub Doka 136bba1631
some cleanup 2024-09-30 19:14:00 +02:00
Jakub Doka c1b00b6d6b
making nostd compat work 2024-09-30 19:09:17 +02:00
Jakub Doka a51b23187d
making a little utility for computing struct layouts 2024-09-28 21:56:39 +02:00
Jakub Doka c3f9e535d3
cleaning up tests 2024-09-28 16:34:08 +02:00
Jakub Doka 6d805dc2ec
fixing the cli 2024-09-28 16:28:05 +02:00
Jakub Doka 4291ebc25e
forgot 2024-09-28 15:14:17 +02:00
Jakub Doka 02c74a181d
making some pointer peepholes 2024-09-28 15:13:32 +02:00
Jakub Doka c0d4464097
implementing pointers example 2024-09-27 16:53:28 +02:00
mlokr 602249a48a
adding packed structs 2024-09-22 18:17:30 +02:00
mlokr 338e3f1519
fixing compiler errors 2024-09-21 14:46:12 +02:00
mlokr 0e9f4402cb
fixing hbbytecode having the std mentioned 2024-09-21 08:19:27 +02:00
mlokr 6057e88034
fixing a bug and preparing form memory manipulation with optimizations 2024-09-20 19:01:44 +02:00
mlokr 2a3d077476
fixing all supidus bugs 2024-09-20 16:37:51 +02:00
mlokr 8e62bd747b
fixing some other stuff that nerfs the code a bit (a lot) 2024-09-20 12:03:24 +02:00
mlokr b8ff503c14
fixing report bug 2024-09-20 11:01:10 +02:00
mlokr 9e69e53e24
merge 2024-09-20 08:20:48 +02:00
mlokr 4d163a2313
bruhma 2024-09-20 08:09:29 +02:00
mlokr e4e7f8d5b5
implementing ableos executable format 2024-09-19 13:40:03 +02:00
mlokr 4849807353
removing git support and relative path prefix which did nothing anyway 2024-09-18 10:34:07 +02:00
mlokr 6e30968c54
improving one particular error message 2024-09-18 10:14:17 +02:00
mlokr 6fc0eb3498
triing to turn absolute to relative paths in error messages 2024-09-18 10:07:40 +02:00
mlokr 98dfd6b09c
improving parser error messages 2024-09-18 09:47:52 +02:00
mlokr ece9bb8bf2
eca now infers the return type 2024-09-17 18:11:07 +02:00
mlokr 09fcbbc03b
forcing structs to always be on stack 2024-09-17 18:07:15 +02:00
mlokr a7fda408ef
forgot update 2024-09-17 17:59:32 +02:00
mlokr 5d77ae93b4
forcing structs to always be on stack 2024-09-17 17:59:03 +02:00
mlokr 4a9b9de87f
nah, lets use dummer code 2024-09-17 15:50:45 +02:00
mlokr bba3570788
adding wide return move for wider range of cases 2024-09-17 15:47:23 +02:00
mlokr 6852452f1a
fixing wide return value 2024-09-17 15:25:31 +02:00
mlokr 254d5ed962
fixing array bug, well actually more serious bug that somehow did not happen until now 2024-09-16 21:46:02 +02:00
mlokr faf068885a
replacing magic zeroes with proper constants 2024-09-16 15:49:27 +02:00
mlokr a2e864360e
removing comand line parsing library that is used for tool that anybody can read to see how to use it 2024-09-16 15:27:38 +02:00
mlokr 79e4cead2d
making many tests work 2024-09-15 20:14:56 +02:00
mlokr 6968e7d769
adding framerk to add comments to different places 2024-09-14 12:27:53 +02:00
mlokr c133c2dbe7
adding negation 2024-09-14 11:26:54 +02:00
mlokr 2bc7a5c13f
bratenburg 2024-09-13 20:31:05 +02:00
mlokr 16e2c32521
brah 2024-09-13 19:30:47 +02:00
mlokr da85d91a09
moving op instruction selection to token methods 2024-09-13 18:41:01 +02:00
mlokr e2a8373c42
updating tests 2024-09-13 18:23:00 +02:00
mlokr fbdabd8314
binary no longer contains comptime code and inoptimized impl is grately simplified 2024-09-13 18:22:27 +02:00
mlokr 39c4526797
saving 2024-09-13 15:12:20 +02:00
mlokr 2e3fbfa966
extracting testing logic 2024-09-13 14:30:23 +02:00
mlokr eebabc5070
accidente 2024-09-13 14:16:34 +02:00
mlokr b177cbe7c7
instruction scheduling somewhat works now 2024-09-13 14:15:45 +02:00
mlokr 641d344d2d
removing false positives 2024-09-12 18:42:21 +02:00
mlokr dc418bd5e0
blah buh leee 2024-09-10 20:54:11 +02:00
mlokr 8bbc40b9b1
fixing the compilation eror and maybe breaking the code 2024-09-10 20:50:36 +02:00
mlokr 8083bcb0e8
fixing the overoptimization 2024-09-10 16:32:15 +02:00
mlokr 8928888481
fixing the useles register alloc when loading 2024-09-10 12:16:42 +02:00
mlokr d64fa7e1f9
more oportunities to reduce register copies 2024-09-10 12:13:01 +02:00
mlokr b51f964cae
optimizing accumulation 2024-09-10 01:15:18 +02:00
mlokr 67b8ffe2f2
prolly fixed 2024-09-09 23:31:22 +02:00
mlokr 32bed04914
fixing vm bug 2024-09-09 22:52:34 +02:00
mlokr 6cb9489e9a
saving 2024-09-09 22:17:54 +02:00
mlokr 73727c2383
fixing more bugs and also adding uninig memory and also optimizing cong jumps 2024-09-09 19:36:53 +02:00
mlokr e8a5027cab
fixing the obscure string allocation bug 2024-09-08 17:25:33 +02:00
mlokr 50f3350418
saving 2024-09-08 17:11:33 +02:00
mlokr bb41da484f
adding global mutatuon to the test 2024-09-08 12:00:07 +02:00
mlokr ee30069195
switching to more optimal lookup and adding dynamic input array 2024-09-08 04:20:10 +02:00
mlokr 58c1c29293
not traversing controlfow can save us time 2024-09-08 03:15:05 +02:00
mlokr 49387dbe16
if a phy does not depend of different phy in the same loop we can modify it in place saving a register copy 2024-09-08 03:12:57 +02:00
mlokr 803095c0c5
implementing multiple breaks 2024-09-06 22:00:23 +02:00
mlokr 514c2fe630
ups 2024-09-06 18:50:53 +02:00
mlokr b4f64656fe
better error recovery 2024-09-06 18:50:28 +02:00
mlokr 73e13bd93c
more tests work now 2024-09-06 16:16:42 +02:00
mlokr b404e5b86d
more tests work now 2024-09-06 16:11:57 +02:00
mlokr 4bcab25231
upgraded error messages and inference 2024-09-06 02:42:07 +02:00
mlokr 414a07b99a
great 2024-09-06 02:04:19 +02:00
mlokr fdf4cccde0
making a mess 2024-09-06 01:17:54 +02:00
mlokr 1a3b0c2eec
implementing optimizations up to is statements 2024-09-05 11:16:11 +02:00
mlokr 955e4a5c7a
making functions example pass 2024-09-04 23:46:32 +02:00
mlokr d9aab2191b
maybe fixing a bug 2 2024-09-04 18:53:07 +02:00
mlokr 9dd09b2122
maybe fixing a bug 2024-09-04 18:51:56 +02:00
mlokr 937c107dec
updating tests and fixing bug 2024-09-04 18:48:25 +02:00
mlokr ed1b9459fc
some more 2024-09-04 18:43:08 +02:00
mlokr f063d0a4fd
disasm now displays literal string value 2024-09-04 18:38:32 +02:00
mlokr a21dee61e7
adding disasm option 2024-09-04 17:56:59 +02:00
mlokr 3807276a55
fixing integer parsing bug 2024-09-04 17:13:43 +02:00
mlokr 894f73ca35
adding more type checking 2024-09-04 16:54:34 +02:00
mlokr 00ad474881
making tests more robust for no reason 2024-09-04 02:35:09 +02:00
mlokr 9e0e0242aa
preparing for dead code elemination 2024-09-03 22:41:44 +02:00
mlokr a31e02449c
adding disasembler 2024-09-03 22:34:17 +02:00
mlokr b956cc78bb
adding bfn to ast nodes 2024-09-03 17:51:28 +02:00
mlokr 7279ed88e9
adding sime ignores 2024-09-03 00:27:50 +02:00
mlokr 9500db8764
some organization 2024-09-03 00:07:20 +02:00
mlokr 9404eb32a2
what 2024-09-02 13:28:07 +02:00
mlokr f172c33247
fixing nasety bug 2024-09-02 04:45:42 +02:00
mlokr 75dca64648
popping the inlined function arguments 2024-09-02 03:56:22 +02:00
mlokr 97c62e424a
fixing inconststent context for function 2024-09-02 03:37:49 +02:00
mlokr a2c08b6ef6
fixing inlining related bugs 2024-09-02 03:21:39 +02:00
mlokr a78d2bc3e9
small fix 2024-09-02 02:45:03 +02:00
mlokr ad3fc1190c
uptimizing a bit more, inline calls 2024-09-02 02:38:11 +02:00
mlokr 641be15703
most likely fixed 2024-09-01 23:51:59 +02:00
mlokr cbe6f98dff
maybe fixed 2024-09-01 23:14:48 +02:00
mlokr 9bdacfffb2
ups 2024-09-01 22:08:17 +02:00
mlokr f13f500d6e
fixing minmaxing bug 2024-09-01 22:07:45 +02:00
mlokis 4e9d6094bd Merge pull request 'Support Hex, Binary, and Octal number literals' (#20) from koniifer/holey-bytes:trunk into trunk
Reviewed-on: https://git.ablecorp.us/AbleOS/holey-bytes/pulls/20
2024-09-01 19:19:10 +00:00
mlokr 28e33d11c9
removing garbage 2024-09-01 21:15:29 +02:00
koniifer 581c4d531c fix obvious bugs, add unit test, hopefully preserve radix when formatting 2024-09-01 20:07:19 +01:00
mlokr 9012f976c5
bruh 2024-09-01 21:05:17 +02:00
koniifer 27462d9a33 forgot octals exist 2024-09-01 18:45:38 +01:00
koniifer 781c40ede0 support hex, octal, binary literals 2024-09-01 18:42:04 +01:00
mlokr 9af7bf559f
fixed confused shift tokens (I still dont know which side is left) 2024-07-21 10:29:58 +02:00
mlokr 5a6474f066
fixed nasty wrong scope bug 2024-07-20 18:52:24 +02:00
mlokr 33a4bf7d01
barbar 2024-07-19 21:19:03 +02:00
mlokr cac99cd34d
foobar 2024-07-19 21:04:22 +02:00
mlokr 5555b9865a
v 2024-07-19 15:51:02 +02:00
mlokr f964520641
u 2024-07-19 14:39:30 +02:00
mlokr a88d3a5c9d
t 2024-07-19 14:24:58 +02:00
mlokr 416f646957
s 2024-07-19 14:17:45 +02:00
mlokr 12b39c5b3f
q 2024-07-19 14:05:13 +02:00
mlokr 4dcaae8362
p 2024-07-19 13:51:38 +02:00
mlokr ab903fa4ea
o 2024-07-19 13:44:35 +02:00
mlokr c48a2d2799
l 2024-07-19 13:39:42 +02:00
mlokr fb01407465
k 2024-07-19 13:38:30 +02:00
mlokr 71359d82aa
j 2024-07-19 13:21:14 +02:00
mlokr 29d5774c47
i 2024-07-19 13:09:45 +02:00
mlokr 434acfbc7b
i 2024-07-19 13:09:07 +02:00
mlokr 6a03f125a5
h 2024-07-19 13:03:11 +02:00
mlokr 03aedb5d3f
g 2024-07-19 13:02:00 +02:00
mlokr a1179f3320
f 2024-07-19 12:56:40 +02:00
mlokr ba73a89171
e 2024-07-19 12:52:11 +02:00
mlokr 523ca6d103
d 2024-07-19 12:51:26 +02:00
mlokr 654b7eb7af
d 2024-07-19 12:42:06 +02:00
mlokr 4c3b63df25
c 2024-07-19 12:39:19 +02:00
mlokr 9a8a56fe97
b 2024-07-19 12:34:22 +02:00
mlokr aeb3a37f7d
fixing typechecking issues, pointers now properly typecheck and generic types are properly cached 2024-07-19 12:00:55 +02:00
mlokr 3c01a40ef2
fixing wide register returns 2024-07-18 17:55:55 +02:00
mlokr 4f9d4f2e71
arrays work i guess 2024-07-08 18:08:58 +02:00
mlokr 25bbe247e9
added example for struct patters 2024-07-08 11:00:35 +02:00
mlokr ab41d49a3d
some stuff 2024-07-08 10:14:38 +02:00
mlokr 11cb875882
some stuff 2024-07-08 10:13:50 +02:00
mlokr 8984dce0e7
more dependencies removed 2024-07-08 07:44:06 +02:00
mlokr fd64968f3a
more garbage 2024-07-08 07:27:40 +02:00
mlokr e00f2f08c8
removing garbage 2024-07-08 07:22:53 +02:00
mlokr 880cd66c66
on the journey twards struct destruct 2024-07-07 19:16:15 +02:00
mlokr fa41c56cb3
interstellar 2024-07-07 18:21:07 +02:00
mlokr efa7271a59
brahmaputra 4 2024-07-07 14:52:31 +02:00
mlokr bd7384123c
brahmaputra 2 2024-07-07 14:29:37 +02:00
mlokr e9589ebcae
brahmaputra 2024-07-07 14:26:43 +02:00
mlokr 22f925b3f5
bruh 2024-07-07 13:44:20 +02:00
mlokr 3807fe22da
YEEEEEEEEEEEEEEEEEEEEEEEEEEEES 2024-07-07 13:42:48 +02:00
mlokr 12c7467be2
removing string optimizations 2024-07-07 12:15:48 +02:00
mlokr cdc8cb35f7
GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH, I did not delete unwanted relocations 2024-07-06 23:02:49 +02:00
mlokr 36bd1a796b
GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH, I did not delete unwanted relocations 2024-07-06 23:02:04 +02:00
mlokr 59705c062d
adding better api 2024-07-06 15:18:57 +02:00
mlokr 9fe734c68c
adding better api 2024-07-06 15:05:56 +02:00
mlokr dc0562553d
adding better api 2024-07-06 14:58:50 +02:00
mlokr 91907a90ff
-__- 2024-07-02 14:49:05 +02:00
mlokr e147358fce
adding spart fmt to struct ctors 2024-06-25 21:51:41 +02:00
mlokr f9e46b4641
added formatting 2024-06-25 21:41:12 +02:00
mlokr 93deeee6b9
adding comments 2024-06-25 19:55:25 +02:00
mlokr 876690319f
slight improvement 2024-06-25 19:46:48 +02:00
mlokr c835317287
slight improvement 2024-06-25 19:22:49 +02:00
mlokr 8442b55aa6
improved exer 2024-06-25 19:13:42 +02:00
mlokr e07265c88b
improved exer 2024-06-25 19:12:35 +02:00
mlokr 6a69042cb7
improved exer 2024-06-25 18:39:59 +02:00
mlokr c85437e4e8
fixed a significant bugus 2024-06-24 17:45:58 +02:00
mlokr 76b3f9ff4b
pekomaaaaa 2024-06-24 17:26:00 +02:00
mlokr 66c3f7b0d4
cleanup 2024-06-23 13:55:48 +02:00
mlokr b04d9e517e
tests pass again 2024-06-23 09:26:03 +02:00
mlokr b46c64db4f
tests pass again 2024-06-23 09:09:33 +02:00
mlokr 6de8496aa5
whew 2024-06-21 23:07:32 +02:00
mlokr 499fe34f1d
psl 2024-06-20 11:23:37 +02:00
mlokr 36d978d798
cleaning up the docs 2024-06-20 11:18:36 +02:00
mlokr bd2a49d29a
brah 2024-06-15 10:49:02 +02:00
mlokr 1c8645bf11
fixing pre 2024-06-15 10:48:42 +02:00
mlokr 1624559e7b
little guide to add examples 2024-06-15 10:46:53 +02:00
mlokr 1ca5d89644
pup 2024-06-15 10:38:16 +02:00
mlokr 61ecbbd304
putting tests as examples in readme 2024-06-15 10:37:50 +02:00
mlokr 002a7df509
adding more elaborate directive example 2024-06-15 09:37:19 +02:00
mlokr 20903ef294
smh my head 2024-06-12 16:29:41 +02:00
mlokr aafcb2fbbd
size improvement 2024-06-06 15:39:10 +02:00
mlokr 98862edd58
other stuff 2024-06-01 20:30:15 +02:00
mlokr b9de362ba2
making modules work 2024-06-01 20:30:15 +02:00
mlokr e494785f93 some stuff 2024-05-20 14:11:58 +02:00
mlokr aef9951bc5 implementing comptime constants 2024-05-19 18:20:42 +02:00
mlokr b922dbd232 making progress on parallelization 2024-05-17 19:53:59 +02:00
mlokr 71c4d3632a welp that was an accident 2024-05-16 22:56:53 +02:00
mlokr 8cb9f2eaac fixing stack to what compiler assumes 2024-05-16 16:54:12 +02:00
mlokr aae217dd00 making better use of parameter and return registers (use register 2 for arguments when possible) 2024-05-16 16:50:29 +02:00
mlokr 4502a64514 some cleanup and bug fix 2024-05-16 13:32:04 +02:00
mlokr ca1d471646 adding imm operations is come cases 2024-05-16 13:29:16 +02:00
mlokr 2dff9f7244 slightly optimizing assignment 2024-05-16 12:56:33 +02:00
mlokr 3127d04e41 doing same for arguments 2024-05-16 12:42:11 +02:00
mlokr 589a30c8a3 making the variables smarter (only putting then on stack when they get referenced) 2024-05-16 12:23:37 +02:00
mlokr 8b81cfef37 cargo update 2024-05-15 14:39:36 +02:00
mlokr 6b74640c3f accident 2024-05-15 14:37:03 +02:00
mlokr 87ba7aa203 removing deendence on macros with a simple build script 2024-05-15 14:36:38 +02:00
mlokr 78f9eb6acc implementing codegen for all the operators 2024-05-15 11:10:20 +02:00
mlokr 3c09a5f23e adding '<op>=' syntax 2024-05-15 10:37:39 +02:00
mlokr 70955c1792 adding directives 2024-05-14 23:07:32 +02:00
mlokr d8a922df26 painfully, but suddle bugs with pointers are now fixed 2024-05-14 15:03:36 +02:00
mlokr 9aa5da82c9 saving before refactoring experiment 2024-05-14 14:01:40 +02:00
mlokr fb481a0600 making stack reclamation 2024-05-14 12:17:39 +02:00
mlokr d90f386bd2 adding an edge case 2024-05-13 15:27:09 +02:00
mlokr c14e6c352d fixing upcating bugs related to pointers 2024-05-13 15:24:48 +02:00
mlokr 9ccf91d072 adding pointer arithmetic test 2024-05-13 14:23:19 +02:00
mlokr 7cca9a3683 adding different-sized integers 2024-05-13 13:36:29 +02:00
mlokr b28baa86f7 adding destination semantics for expressions 2024-05-13 11:05:35 +02:00
mlokr 2226a47aaa fixing struct regurns 2024-05-13 09:55:09 +02:00
mlokr 0aec47e985 fixing struct regurns 2024-05-13 09:38:33 +02:00
mlokr 5c38115119 adding simple cli 2024-05-13 00:02:32 +02:00
mlokr c3cbd054f7 improving code 2024-05-12 23:45:28 +02:00
mlokr 06e30529bf added better error reports 2024-05-12 23:19:45 +02:00
mlokr 4ec635dc56 proper type display and pointer types 2024-05-12 22:40:28 +02:00
mlokr a08856a464 fising spec 2024-05-12 20:14:46 +02:00
mlokr d5a5c932e7 finishing structures 2024-05-12 20:10:50 +02:00
mlokr bc59886428 adding per function dead code elimination 2024-05-12 14:56:59 +02:00
mlokr f87959aacb adding structures 2024-05-12 13:13:36 +02:00
mlokr 80b05779ea adding struct syntax 2024-05-12 12:21:40 +02:00
mlokr 4bb5ec1953 adding struct syntax 2024-05-12 12:16:40 +02:00
mlokr 2aa315a863 accident 2024-05-12 11:53:21 +02:00
mlokr 86013a50a4 identifiers are now properly checked 2024-05-12 11:52:58 +02:00
mlokr 465b185452 foo bar 2024-05-11 22:23:58 +02:00
mlokr b794fa7c3c foo bar 2024-05-11 22:22:12 +02:00
able ebefc85566 :3 Breaky 2024-05-11 15:21:07 -05:00
mlokr a3c4b878b2 adding loops 2024-05-11 18:16:27 +02:00
mlokr 7f32e7775c making if statements without else branch work 2024-05-11 17:05:22 +02:00
mlokr 1d74f27b0e making the functions kind of walk 2024-05-11 16:04:13 +02:00
mlokr 7435218999 fixing relative jumps to not offset from immidiate adress but from instruction adress 2024-05-11 12:51:32 +02:00
mlokr cf99091a45 now compiling some trivial arithmetic 2024-05-10 22:54:12 +02:00
mlokr 81952cfc40 compiling return stmt 2024-05-10 21:38:15 +02:00
mlokr 68d53544fd compiling return stmt 2024-05-10 21:33:42 +02:00
mlokr aa77a2f822 fixing JALA and JAL saving self reference instead of reference to the next instruction 2024-05-10 15:29:11 +02:00
mlokr b80528bfd7 accident 2024-05-09 23:43:18 +02:00
mlokr 1c08148dc9 starting from zero again 2024-05-09 23:41:59 +02:00
mlokr 774735b515 ups 2024-05-09 18:24:30 +02:00
mlokr 870c1f4718 blah 2024-05-09 18:22:31 +02:00
mlokr 326adf47ce wha 2024-05-09 18:18:19 +02:00
able 37ff58c5e5 Removing old deprecated things. 2024-04-29 05:02:33 -05:00
Erin cbf4c6572a fixed hbasm 2024-04-16 15:34:12 +02:00
Erin 0070016f74 Documented hbasm 2024-02-22 23:57:29 +01:00
Erin 6c6e29479f xyzzy 2024-02-16 12:17:15 +01:00
Erin eb46b24a10 xyzzy 2024-02-16 11:49:32 +01:00
Erin 942839a5f8 xyzzy 2024-02-16 11:45:39 +01:00
mlokr 9ddc336ecd fixing deprecated stuff, i hate this 2024-02-14 12:30:38 +01:00
Erin 34d1bf415e Don't fail to compile on unsupported architectures 2024-02-14 01:25:44 +01:00
mlokr c978b408e2 running cargo updtae to fix the broken build on nightly 2024-02-13 15:54:46 +01:00
Erin fe9a0667b8 Maybe fixed questionable code 2024-02-04 22:36:52 +01:00
mlokr 975ce8a9fe more 2024-02-04 18:32:55 +01:00
mlokr 8a3dd3001d smh 2024-02-04 10:34:25 +01:00
Erin de723980da The more macros, the merrier 2024-02-04 03:08:20 +01:00
Erin 4aa39f3fbc a 2024-02-04 02:46:50 +01:00
Erin 5f8864e251 Fixed stack allocation for debug 2024-02-04 02:21:25 +01:00
mlokr bcbe47bcd6 some more progress on codegen 2024-02-03 21:43:54 +01:00
Erin 30ee6c84fc Fixed x86 asm 2024-02-03 20:08:09 +01:00
mlokr 6e464be33d adding more parsing with a sanity test 2024-02-01 16:11:10 +01:00
mlokr 09aacff161 establishing some syntax 2024-01-31 20:11:57 +01:00
mlokr 433f2db4d1 fixing some warnings 2024-01-31 17:54:38 +01:00
mlokr e335e55aa0 removing useles import, super important change 2024-01-31 16:47:17 +01:00
Erin 8e0aeabc07 Baka baka Erin, when you decode RRB, you have to bump it by RRB! 2024-01-15 17:59:46 +01:00
Erin a84e93d562 idk 2024-01-15 17:51:08 +01:00
Erin 8374dfe20a Changed macro param 2024-01-07 00:34:28 +01:00
Erin e9e1242743 Fixed another typo 2023-12-19 12:36:54 +01:00
Erin f604a2463d Fixed typo 2023-11-29 21:07:16 +01:00
Erin 59e38db874 Whoops. 2023-11-26 04:13:52 +01:00
Erin 8c257e9216 More general runtime 2023-11-26 04:13:30 +01:00
Erin 84cc1db691 Added fuzzer back 2023-11-24 12:30:20 +01:00
Erin 633e3adc61 Add basic Win32 support 2023-11-24 01:09:15 +01:00
Erin 7f981fe9a0 It was not a typo, baka Erin. 2023-11-23 22:10:44 +00:00
Erin 43c36774a5 Fixed typo in spec (thanks Ity ^^) 2023-11-23 22:10:23 +00:00
Erin 5dd0e22c0d Moved module 2023-11-23 17:35:11 +01:00
Erin b161d46a5b Changed docs a bit 2023-11-23 16:25:06 +01:00
Erin 42488e1e4a Added stack hopefully maybe who knows 2023-11-15 19:17:25 +01:00
Erin b84ff70014 funny thing 2023-11-15 19:03:56 +01:00
Erin b8432d544c Baka baka Erin, do not forget to bump the opcode too! 2023-11-15 18:23:43 +01:00
Erin 207d8d7fa6 Bye file merged already into spec.md 2023-11-15 18:11:29 +01:00
Erin 569f154bcc Changed psABI spec 2023-11-15 12:45:40 +01:00
Erin 68094ce0ae Merge pull request 'The best kind of pull request' (#17) from bee/holey-bytes:master into trunk
Reviewed-on: https://git.ablecorp.us/AbleOS/holey-bytes/pulls/17
2023-11-15 11:26:39 +00:00
Bee 84dcbfc6bb Fixed typo 2023-11-14 01:46:40 -05:00
Bee d26c285ca7 Merge pull request 'Gooby' (#2) from AbleOS/holey-bytes:trunk into master
Reviewed-on: https://git.ablecorp.us/bee/holey-bytes/pulls/2
2023-11-14 06:42:33 +00:00
Erin d255967125 Ehm? 2023-11-13 00:21:12 +01:00
Erin c5c8d23470 Fixed immediate ops 2023-11-13 00:12:31 +01:00
Erin aca8045a98 Fixed assembler register symbols 2023-11-10 09:51:01 +01:00
Erin 398687d8bf Fixed some panics and some UB 2023-11-08 12:38:14 +01:00
Erin a7c4379976 »fixed« fuzzer 2023-11-03 09:49:42 +01:00
Erin 949dd3ba61 Fixed rounding mode 2023-11-03 09:43:08 +01:00
Erin 3771180909 more fmt 2023-11-03 09:19:41 +01:00
Erin 6b3a132451 fmt 2023-11-03 09:01:26 +01:00
Erin b45d235312 Exit 2023-11-02 19:53:03 +01:00
Erin 9ee3e9cb5f Public accessors 2023-11-02 16:53:44 +01:00
Erin 57f30109c8 Fixed smaller units 2023-10-29 20:38:57 +01:00
Erin d6243fa99f Squashed assembler 2023-10-28 03:29:02 +02:00
Erin 3a6d0fdd2d fixed match 2023-10-22 23:59:27 +02:00
Erin 9b823ef660 Untypo'd 2023-10-22 18:44:08 +02:00
Erin 84aeac0b2a Hints of ABI 2023-10-22 18:43:44 +02:00
Erin fc4118938e Spec update 2023-10-22 18:36:32 +02:00
Erin cb557d1361 Updated spec! 2023-10-22 18:18:50 +02:00
Erin 2715bc9107 Some fmt 2023-10-22 16:17:51 +02:00
Erin 8182abca98 Added assembler
- its a bit cursed, prolly broken, but at least something
2023-10-22 15:08:45 +02:00
Erin 83563fff84 Maybe fixed relative addressing bugs 2023-10-22 14:46:45 +02:00
Erin b4923cfb95 Removed UB 2023-10-22 04:21:45 +02:00
Erin eab47db4d6 chore: cargo update 2023-10-20 12:35:45 +02:00
Erin 4b45407a70 Added test example 2023-10-20 02:05:00 +02:00
Erin a944a145ed relative JAL 2023-10-20 00:42:45 +02:00
Erin 0e701e31b5 Updated runtime stuff 2023-10-20 00:12:32 +02:00
Erin 0cb20d5727 New float instructions, Linux runtime added, some other stuff I forgor 2023-10-18 12:14:24 +02:00
Erin 889aefe87a Changed relative addressing 2023-10-01 16:02:06 +02:00
Erin 59be906835 Sus2 2023-10-01 01:52:26 +02:00
Erin 441356b6f2 Sus stuff 2023-10-01 01:51:51 +02:00
Erin 2f8612c6d2 Added relaxed relative 16 bit instructions 2023-09-29 09:10:36 +02:00
Erin 3e4095da6f Lottsa things changed 2023-09-26 23:36:27 +02:00
Erin b1bdbea991 JMP 2023-09-16 01:02:14 +02:00
Erin 8c8c708279 JMP impl 2023-09-16 00:57:37 +02:00
Erin 35f90e94a8 Changed CMP handling and added simple JMP 2023-09-15 08:43:12 +02:00
able e7aa306e5d examples 2023-09-12 01:38:32 -05:00
Erin 42be580425 ABI proposal part 1 2023-09-08 10:46:41 +02:00
Erin 9b8a4a718e a 2023-08-20 00:24:27 +02:00
Erin 0d2949024c updated macro 2023-08-19 23:57:48 +02:00
Erin 26105bab70 bye stuff 2023-08-19 23:46:47 +02:00
Erin 006dcca309 cleaned up deps 2023-08-19 23:24:31 +02:00
Erin 3034469e89 Address type, changed behaviour on address overflow 2023-08-18 02:31:49 +02:00
Erin 30070818ae Move 2023-08-18 01:41:05 +02:00
Erin d282b3d111 Softpage improvements 2023-08-18 01:28:02 +02:00
Erin 600528434b nope. 2023-08-17 01:37:53 +02:00
Erin 0deeaf3a7e SPID 2023-08-15 17:21:55 +02:00
Erin 3decd01619 Modified memory interface
I have no idea what I am doing rn
2023-08-15 17:05:10 +02:00
Erin a071a4a7ae Notice 2023-08-15 16:33:56 +02:00
Erin af1a7d3bfa Move stuff, deprecate softpage 2023-08-15 16:32:59 +02:00
Erin 3fdf936f77 Some merges 2023-08-11 02:19:26 +02:00
Erin 96b749060d h 2023-08-10 12:39:18 +02:00
Erin 770c2ebcf0 move 2023-08-10 12:39:03 +02:00
Erin 6609bd10c5 executable 2023-08-09 20:19:12 +02:00
Erin 97eaae1c76 bai 2023-08-09 03:12:09 +02:00
Erin 1460a7a230 Edit 0x0 2023-08-09 03:01:42 +02:00
Erin 529fbdaed4 Comments 2023-08-09 02:59:11 +02:00
Erin 3ac80a2e3d Forbid store 2023-08-09 02:57:25 +02:00
Erin 06d66289bc Now finally, leaving Hardvard! 2023-08-09 02:53:55 +02:00
Erin 430ccd170d Von-Neumann? 2023-08-09 02:33:03 +02:00
Erin e2d3f46d3f Added TX instruction (definitely not named after Texas) 2023-08-09 01:24:45 +02:00
Erin eadf9e0a1f Termination instruction 2023-08-09 01:24:13 +02:00
Erin d74b32a38d Changed memory interfacing 2023-08-08 03:14:19 +02:00
Erin 4530ff049e fmt 2023-08-08 03:10:23 +02:00
Erin 2d2978eec7 Added inner memory access 2023-08-08 03:10:11 +02:00
Erin bf50bcb203 Changed stuff aroud 2023-08-08 03:03:15 +02:00
Erin 82f23ec2e2 Abstraction of memory 2023-08-08 02:48:47 +02:00
Erin 5264576274 Reimplemented BMC 2023-08-08 02:06:15 +02:00
Erin cdee99598e const perm check 2023-08-08 01:44:33 +02:00
Erin f130a27685 Shrunk 2023-08-07 01:50:21 +02:00
Erin aa186b35c2 Spec update 2023-08-07 01:43:29 +02:00
Erin 629fc969c2 Spec update 2023-08-07 01:41:26 +02:00
Erin 8287b1bdc1 Changed magic 2023-08-01 22:20:11 +02:00
Erin 73b998911c a 2023-08-01 22:17:20 +02:00
Erin 1a5f101719 Added magic 2023-08-01 22:13:22 +02:00
Erin a667c36d6c Link fix 2023-07-26 21:23:03 +02:00
Erin 582c716445 Nightly opts 2023-07-26 20:54:24 +02:00
Erin 37a1923f1e Added some comments 2023-07-26 20:49:23 +02:00
Erin 10f9907c09 Fixed mapping problems 2023-07-26 13:04:58 +02:00
Erin 2480a65947 Whoops, this is 5-level paging, not 6-level paging 2023-07-26 12:41:18 +02:00
Erin 1ed153a9a2 Fixed memory (un)mapping 2023-07-26 12:22:28 +02:00
Erin 19df4538d7 Fixed page size, fuzzer now does memory. 2023-07-26 03:27:31 +02:00
Erin e07bfb2301 Decreased timeout 2023-07-26 02:35:27 +02:00
Erin cfe3203ef1 Increased timeout 2023-07-26 02:31:06 +02:00
Erin c4e062e742 Increased timeout 2023-07-26 02:30:22 +02:00
Erin 83436507df Fixed few overflows 2023-07-26 02:28:14 +02:00
Erin bdda987da9 BMC is now interruptable 2023-07-26 02:04:26 +02:00
Erin 6588837769 restruct + no-alloc support 2023-07-26 01:11:21 +02:00
Erin f2ec9a3034 Added fuzzy tests 2023-07-26 01:01:53 +02:00
Erin 95c979cb83 a 2023-07-26 00:17:10 +02:00
Erin 66f634a70f Added warning 2023-07-26 00:16:50 +02:00
Erin 077da50787 Reworked macros 2023-07-26 00:12:50 +02:00
Erin 5055626968 Added runtime bound checking 2023-07-26 00:01:25 +02:00
Erin 0f5d78bb27 whoops, fixed builds. 2023-07-25 23:48:59 +02:00
Erin 668b324cc8 Valider 2023-07-25 23:47:51 +02:00
Erin 759514686a Valider is now generated from macro (not done yet) 2023-07-25 23:43:06 +02:00
Erin 92793dc93b Quick valider fix 2023-07-25 23:03:06 +02:00
Erin ac149a5319 Commented valider 2023-07-25 22:44:08 +02:00
able f4c55ae3cc changes I GUESS 2023-07-25 12:20:35 -05:00
Erin a82686ec07 Fixed endian stuffs 2023-07-25 19:10:00 +02:00
Erin b3a6c42af3 Added notice 2023-07-25 14:41:54 +02:00
Erin d20447dd15 Kekw 2023-07-24 20:41:10 +02:00
Erin 193be0bd5a Removed some macros 2023-07-24 18:48:42 +02:00
Erin fce3fa5210 fixed imm shl/r 2023-07-24 16:48:13 +02:00
Erin 4ca4e81ac3 Fixed panic on shift outta bounds
- Pointed out by 5225225
2023-07-24 16:37:37 +02:00
Erin dcfd51999d Fixed missing / 2023-07-22 02:42:43 +02:00
Erin 1532c501a6 added contribution guide to instructions 2023-07-22 02:42:21 +02:00
Erin 8eebbacb91 Name correction 2023-07-22 02:34:41 +02:00
Erin c621a5c71d Edits. 2023-07-22 02:29:05 +02:00
Erin 8d5d22eae1 Moved lore 2023-07-22 02:28:05 +02:00
Erin 3892a719eb A 2023-07-22 02:27:03 +02:00
Erin 47323e140c added notice. 2023-07-22 02:26:29 +02:00
Erin 3833beb17d More comments 2023-07-22 02:26:03 +02:00
Erin afbf6dd2e4 Removed pagetable hack 2023-07-22 01:06:41 +02:00
Erin ec7053a289 Zero alloc BMC! 2023-07-22 01:03:09 +02:00
Erin 1a53c80a62 Fixed bug + spec update 2023-07-22 00:46:30 +02:00
Erin 6fe1fd91bf Mapping + bye bye memory leaks 2023-07-20 20:47:50 +02:00
able ac7fdc7688 code and stufd 2023-07-15 06:27:11 -05:00
able 9cf8789e9a Merge branch 'master' of ssh://git.ablecorp.us:20/AbleOS/holey-bytes 2023-07-13 04:23:06 -05:00
able 3534b26946 Add some example code for hbasm 2023-07-13 04:23:00 -05:00
Erin a21f68ffa6 Merge pull request 'Added UN instruction and fixed UB' (#7) from fix-ub into master
Reviewed-on: https://git.ablecorp.us/AbleOS/holey-bytes/pulls/7
2023-07-13 09:13:34 +00:00
Erin 7833334713 Update spec 2023-07-13 11:11:35 +02:00
Erin 141c5f524f Added UN instruction and fixed UB 2023-07-13 11:10:07 +02:00
Erin 446225bcf6 Merge pull request 'Fixed the number of registers BRC copies' (#6) from bee/holey-bytes:master into master
Reviewed-on: https://git.ablecorp.us/AbleOS/holey-bytes/pulls/6
2023-07-13 09:09:44 +00:00
bee d6ea5adf49 Merge pull request 'merge' (#1) from AbleOS/holey-bytes:master into master
Reviewed-on: https://git.ablecorp.us/bee/holey-bytes/pulls/1
2023-07-12 17:13:38 +00:00
Egggggg 898738fb40 fixed the number of registers BRC copies 2023-07-12 13:12:00 -04:00
Erin beb6e23d71 Map APIs 2023-07-12 14:56:11 +02:00
Egggggg 5afd081c2a hehe oops 2023-07-12 06:50:07 -04:00
Erin 81cf5c4336 JMP → JAL + spec fix 2023-07-12 12:45:50 +02:00
Egggggg 63cf7ac0b0 fixed argument order of BMC and BRC 2023-07-12 06:25:38 -04:00
Erin 3cb3ee1fee fixxed lint 2023-07-12 02:24:05 +02:00
Erin 4dfbe93919 special-cased BRC 2023-07-12 02:23:47 +02:00
Erin 6791b6d48e Rewritten assembler 2023-07-12 02:16:23 +02:00
Erin 0ed89234a7 Revised trap API 2023-07-11 17:04:48 +02:00
able 6afec2a031 Merge pull request 'master' (#3) from IntoTheNight/holey-bytes:master into master
Reviewed-on: https://git.ablecorp.us/AbleOS/holey-bytes/pulls/3
2023-07-11 09:36:39 +00:00
IntoTheNight b83d1838aa Merge pull request 'master' (#1) from AbleOS/holey-bytes:master into master
Reviewed-on: https://git.ablecorp.us/IntoTheNight/holey-bytes/pulls/1
2023-07-11 09:28:48 +00:00
MunirG05 e25a89d56d the design is very human 2023-07-11 14:54:49 +05:30
MunirG05 b72f0afe84 add fancy errors 2023-07-11 14:38:20 +05:30
Erin 9196519fae doc 2023-07-11 10:33:55 +02:00
MunirG05 6759fbd2ab tried to shove the timer back in 2023-07-11 14:03:25 +05:30
Erin 4f53fb1c87 Moved 2023-07-11 10:32:26 +02:00
Erin ad96e83f09 wrap around timer 2023-07-11 10:31:03 +02:00
Erin 5ee8a91479 Implement timer 2023-07-11 10:29:23 +02:00
Erin bde00c13f2 Improved assembler library 2023-07-11 02:08:55 +02:00
Erin b4dac1245b Moved 2023-07-10 23:18:23 +02:00
Erin e700010e7f Rename 2023-07-07 15:23:53 +02:00
Erin 2d34ed61d0 Updated flots 2023-07-07 15:22:03 +02:00
Erin 3919aa8100 assert char bit 2023-07-07 14:36:40 +02:00
Erin a548a7b08e Updated C header 2023-07-07 14:33:08 +02:00
Erin 132fc1a6ed Updated spec 2023-07-07 14:33:07 +02:00
able c31c9e9a54 HBASM: derp forgot that deps also need to be nostd 2023-06-26 05:23:52 -05:00
able c26b559898 HBASM: no_std compatible now 2023-06-26 05:18:14 -05:00
Erin 907dd66d5e Improved unhandled trap handling 2023-06-25 00:28:20 +02:00
Erin 2416526014 Stole docs 2023-06-25 00:21:40 +02:00
Erin bb50c09538 docs 2023-06-25 00:18:31 +02:00
Erin a7cf5e4847 Implemented traps 2023-06-25 00:16:14 +02:00
able 87ec6ded54 Initial work on a simple serial driver for ableos 2023-06-21 08:22:56 -05:00
able 4a840a6ef0 Update to stable 2023-06-21 08:22:21 -05:00
able 5ec6da9fb4 clear out assets 2023-06-21 07:54:10 -05:00
able fdca041e6b NIX: fix nix-shell 2023-06-21 07:53:01 -05:00
Erin 06b1184772 HoleyBytes, almost adhering the spec
- Changed instruction encoding to be faster to match on
- Implemented all instructions defined in spec
- Bytecode validation
- Assembler
- Implemented 5 level paging (based on SV57)
- Implemented some degree of interrupts (though not fully adhering the spec yet)
2023-06-21 02:07:48 +02:00
Erin fb78e0a44a a 2023-05-28 23:38:26 +02:00
Erin 7eaa01f53c fixup32 2023-05-28 23:37:43 +02:00
Erin 119ce4405f Changed register handling 2023-05-28 16:49:01 +02:00
203 changed files with 27395 additions and 2473 deletions

4
.cargo/config.toml Normal file
View file

@ -0,0 +1,4 @@
[alias]
xtask = "r -p xtask --"
wasm-build = "b --target wasm32-unknown-unknown --profile=small -Zbuild-std=core,alloc -Zbuild-std-features=optimize_for_size,panic_immediate_abort -p"
wasm-build-debug = "b --target wasm32-unknown-unknown --profile=small-dev -Zbuild-std=core,alloc -Zbuild-std-features=optimize_for_size -p"

14
.gitignore vendored
View file

@ -1 +1,15 @@
# garbage
/target /target
rustc-ice-*
# sqlite
db.sqlite
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

1718
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,48 @@
cargo-features = ["profile-rustflags"]
[workspace] [workspace]
resolver = "2" resolver = "2"
members = ["hbasm", "hbbytecode", "hbvm"] members = [
"bytecode",
"vm",
"xrt",
"xtask",
"lang",
"depell",
"depell/wasm-fmt",
"depell/wasm-hbc",
"depell/wasm-rt",
]
[workspace.dependencies]
hbbytecode = { path = "bytecode", default-features = false }
hbvm = { path = "vm", default-features = false }
hblang = { path = "lang", default-features = false }
[profile.release]
lto = true
#debug = true
strip = true
codegen-units = 1
panic = "abort"
[profile.small]
rustflags = ["-Zfmt-debug=none", "-Zlocation-detail=none"]
inherits = "release"
opt-level = "z"
strip = "debuginfo"
lto = true
codegen-units = 1
panic = "abort"
[profile.small-dev]
inherits = "dev"
opt-level = "z"
strip = "debuginfo"
panic = "abort"
[profile.fuzz]
inherits = "dev"
debug = true
opt-level = 3
panic = "abort"

10
bytecode/Cargo.toml Normal file
View file

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

225
bytecode/build.rs Normal file
View file

@ -0,0 +1,225 @@
#![feature(iter_next_chunk)]
use std::{collections::HashSet, fmt::Write};
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=instructions.in");
let mut generated = String::new();
gen_instrs(&mut generated)?;
std::fs::write("src/instrs.rs", generated)?;
Ok(())
}
fn gen_instrs(generated: &mut String) -> Result<(), Box<dyn std::error::Error>> {
writeln!(generated, "#![expect(dead_code)]")?;
writeln!(generated, "use crate::*;")?;
'_opcode_structs: {
let mut seen = HashSet::new();
for [.., args, _] in instructions() {
if !seen.insert(args) {
continue;
}
writeln!(generated, "#[derive(Clone, Copy, Debug)]")?;
writeln!(generated, "#[repr(packed)]")?;
write!(generated, "pub struct Ops{args}(")?;
let mut first = true;
for ch in args.chars().filter(|&ch| ch != 'N') {
if !std::mem::take(&mut first) {
write!(generated, ",")?;
}
write!(generated, "pub Op{ch}")?;
}
writeln!(generated, ");")?;
writeln!(generated, "unsafe impl BytecodeItem for Ops{args} {{}}")?;
}
}
'_max_size: {
let max = instructions()
.map(
|[_, _, ty, _]| {
if ty == "N" {
1
} else {
iter_args(ty).map(arg_to_width).sum::<usize>() + 1
}
},
)
.max()
.unwrap();
writeln!(generated, "pub const MAX_SIZE: usize = {max};")?;
}
'_encoders: {
for [op, name, ty, doc] in instructions() {
writeln!(generated, "/// {}", doc.trim_matches('"'))?;
let name = name.to_lowercase();
let args = comma_sep(
iter_args(ty)
.enumerate()
.map(|(i, c)| format!("{}{i}: {}", arg_to_name(c), arg_to_type(c))),
);
writeln!(generated, "pub fn {name}({args}) -> (usize, [u8; MAX_SIZE]) {{")?;
let arg_names =
comma_sep(iter_args(ty).enumerate().map(|(i, c)| format!("{}{i}", arg_to_name(c))));
writeln!(generated, " unsafe {{ crate::encode({ty}({op}, {arg_names})) }}")?;
writeln!(generated, "}}")?;
}
}
'_structs: {
let mut seen = std::collections::HashSet::new();
for [_, _, ty, _] in instructions() {
if !seen.insert(ty) {
continue;
}
let types = comma_sep(iter_args(ty).map(arg_to_type).map(|s| s.to_string()));
writeln!(generated, "#[repr(packed)] pub struct {ty}(u8, {types});")?;
}
}
'_name_list: {
writeln!(generated, "pub const COUNT: u8 = {};", instructions().count())?;
}
let instr = "Instr";
let oper = "Oper";
'_instr_enum: {
writeln!(generated, "#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u8)]")?;
writeln!(generated, "pub enum {instr} {{")?;
for [id, name, ..] in instructions() {
writeln!(generated, " {name} = {id},")?;
}
writeln!(generated, "}}")?;
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: {
writeln!(generated, "#[derive(Debug, Clone, Copy, PartialEq, Eq)]")?;
writeln!(generated, "pub enum {oper} {{")?;
let mut seen = HashSet::new();
for ty in instructions().flat_map(|[.., ty, _]| iter_args(ty)) {
if !seen.insert(ty) {
continue;
}
writeln!(generated, " {ty}({}),", arg_to_type(ty))?;
}
writeln!(generated, "}}")?;
}
'_parse_opers: {
writeln!(
generated,
"/// This assumes the instruction byte is still at the beginning of the buffer"
)?;
writeln!(generated, "#[cfg(feature = \"disasm\")]")?;
writeln!(generated, "pub fn parse_args(bytes: &mut &[u8], kind: {instr}, buf: &mut alloc::vec::Vec<{oper}>) -> Option<()> {{")?;
writeln!(generated, " match kind {{")?;
let mut instrs = instructions().collect::<Vec<_>>();
instrs.sort_unstable_by_key(|&[.., ty, _]| ty);
for group in instrs.chunk_by(|[.., a, _], [.., b, _]| a == b) {
let ty = group[0][2];
for &[_, name, ..] in group {
writeln!(generated, " | {instr}::{name}")?;
}
generated.pop();
writeln!(generated, " => {{")?;
if iter_args(ty).count() != 0 {
writeln!(generated, " let data = crate::decode::<{ty}>(bytes)?;")?;
writeln!(
generated,
" buf.extend([{}]);",
comma_sep(
iter_args(ty).zip(1u32..).map(|(t, i)| format!("{oper}::{t}(data.{i})"))
)
)?;
} else {
writeln!(generated, " crate::decode::<{ty}>(bytes)?;")?;
}
writeln!(generated, " }}")?;
}
writeln!(generated, " }}")?;
writeln!(generated, " Some(())")?;
writeln!(generated, "}}")?;
}
std::fs::write("src/instrs.rs", generated)?;
Ok(())
}
fn comma_sep(items: impl Iterator<Item = String>) -> String {
items.map(|item| item.to_string()).collect::<Vec<_>>().join(", ")
}
fn instructions() -> impl Iterator<Item = [&'static str; 4]> {
include_str!("instructions.in")
.lines()
.filter_map(|line| line.strip_suffix(';'))
.map(|line| line.splitn(4, ',').map(str::trim).next_chunk().unwrap())
}
fn arg_to_type(arg: char) -> &'static str {
match arg {
'R' | 'B' => "u8",
'H' => "u16",
'W' => "u32",
'D' | 'A' => "u64",
'P' => "i16",
'O' => "i32",
_ => panic!("unknown type: {}", arg),
}
}
fn arg_to_width(arg: char) -> usize {
match arg {
'R' | 'B' => 1,
'H' => 2,
'W' => 4,
'D' | 'A' => 8,
'P' => 2,
'O' => 4,
_ => panic!("unknown type: {}", arg),
}
}
fn arg_to_name(arg: char) -> &'static str {
match arg {
'R' => "reg",
'B' | 'H' | 'W' | 'D' => "imm",
'P' | 'O' => "offset",
'A' => "addr",
_ => panic!("unknown type: {}", arg),
}
}
fn iter_args(ty: &'static str) -> impl Iterator<Item = char> {
ty.chars().filter(|c| *c != 'N')
}

View file

@ -18,7 +18,8 @@ enum hbbc_Opcode: uint8_t {
hbbc_Op_CMP , hbbc_Op_CMPU , hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI , hbbc_Op_CMP , hbbc_Op_CMPU , hbbc_Op_DIR , hbbc_Op_NEG , hbbc_Op_NOT , hbbc_Op_ADDI ,
hbbc_Op_MULI , hbbc_Op_ANDI , hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI , hbbc_Op_MULI , hbbc_Op_ANDI , hbbc_Op_ORI , hbbc_Op_XORI , hbbc_Op_SLI , hbbc_Op_SRI ,
hbbc_Op_SRSI , hbbc_Op_CMPI , hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI , hbbc_Op_SRSI , hbbc_Op_CMPI , hbbc_Op_CMPUI , hbbc_Op_CP , hbbc_Op_SWA , hbbc_Op_LI ,
hbbc_Op_LD , hbbc_Op_ST , hbbc_Op_BMC , hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JEQ , hhbc_Op_LRA , hbbc_Op_LD , hbbc_Op_ST , hbbc_Op_LDR , hhbc_Op_STR , hbbc_Op_BMC ,
hbbc_Op_BRC , hbbc_Op_JMP , hbbc_Op_JMPR , hbbc_Op_JAL , hbbc_Op_JALR , hbbc_Op_JEQ ,
hbbc_Op_JNE , hbbc_Op_JLT , hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL , hbbc_Op_JNE , hbbc_Op_JLT , hbbc_Op_JGT , hbbc_Op_JLTU , hbbc_Op_JGTU , hbbc_Op_ECALL ,
hbbc_Op_ADDF , hbbc_Op_SUBF , hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF , hbbc_Op_ADDF , hbbc_Op_SUBF , hbbc_Op_MULF , hbbc_Op_DIRF , hbbc_Op_FMAF , hbbc_Op_NEGF ,
hbbc_Op_ITF , hbbc_Op_FTI , hbbc_Op_ADDFI , hbbc_Op_MULFI , hbbc_Op_ITF , hbbc_Op_FTI , hbbc_Op_ADDFI , hbbc_Op_MULFI ,
@ -42,6 +43,12 @@ struct hbbc_ParamBBDH
typedef hbbc_ParamBBDH; typedef hbbc_ParamBBDH;
static_assert(sizeof(hbbc_ParamBBDH) == 96 / 8); static_assert(sizeof(hbbc_ParamBBDH) == 96 / 8);
struct hbbc_ParamBBWH
{ uint8_t _0; uint8_t _1; uint32_t _2; uint16_t _3; }
typedef hbbc_ParamBBWH;
static_assert(sizeof(hbbc_ParamBBWH) == 64 / 8);
struct hbbc_ParamBBD struct hbbc_ParamBBD
{ uint8_t _0; uint8_t _1; uint64_t _2; } { uint8_t _0; uint8_t _1; uint64_t _2; }
typedef hbbc_ParamBBD; typedef hbbc_ParamBBD;

120
bytecode/instructions.in Normal file
View file

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

283
bytecode/src/lib.rs Normal file
View file

@ -0,0 +1,283 @@
#![no_std]
#[cfg(feature = "disasm")]
extern crate alloc;
pub use crate::instrs::*;
use core::convert::TryFrom;
mod instrs;
type OpR = u8;
type OpA = u64;
type OpO = i32;
type OpP = i16;
type OpB = u8;
type OpH = u16;
type OpW = u32;
type OpD = u64;
/// # Safety
/// Has to be valid to be decoded from bytecode.
pub unsafe trait BytecodeItem {}
unsafe impl BytecodeItem for u8 {}
impl TryFrom<u8> for Instr {
type Error = u8;
#[inline]
fn try_from(value: u8) -> Result<Self, Self::Error> {
#[cold]
fn failed(value: u8) -> Result<Instr, u8> {
Err(value)
}
if value < COUNT {
unsafe { Ok(core::mem::transmute::<u8, Instr>(value)) }
} else {
failed(value)
}
}
}
#[inline]
unsafe fn encode<T>(instr: T) -> (usize, [u8; instrs::MAX_SIZE]) {
let mut buf = [0; instrs::MAX_SIZE];
core::ptr::write(buf.as_mut_ptr() as *mut T, instr);
(core::mem::size_of::<T>(), buf)
}
#[inline]
#[cfg(feature = "disasm")]
fn decode<T>(binary: &mut &[u8]) -> Option<T> {
let (front, rest) = core::mem::take(binary).split_at_checked(core::mem::size_of::<T>())?;
*binary = rest;
unsafe { Some(core::ptr::read(front.as_ptr() as *const T)) }
}
/// Rounding mode
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum RoundingMode {
NearestEven = 0,
Truncate = 1,
Up = 2,
Down = 3,
}
impl TryFrom<u8> for RoundingMode {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
(value <= 3).then(|| unsafe { core::mem::transmute(value) }).ok_or(())
}
}
#[cfg(feature = "disasm")]
#[derive(Clone, Copy)]
pub enum DisasmItem {
Func,
Global,
}
#[cfg(feature = "disasm")]
#[derive(Debug)]
pub enum DisasmError<'a> {
InvalidInstruction(u8),
InstructionOutOfBounds(&'a str),
FmtFailed(core::fmt::Error),
HasOutOfBoundsJumps,
}
#[cfg(feature = "disasm")]
impl From<core::fmt::Error> for DisasmError<'_> {
fn from(value: core::fmt::Error) -> Self {
Self::FmtFailed(value)
}
}
#[cfg(feature = "disasm")]
impl core::fmt::Display for DisasmError<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
DisasmError::InvalidInstruction(b) => write!(f, "invalid instruction opcode: {b}"),
DisasmError::InstructionOutOfBounds(name) => {
write!(f, "instruction would go out of bounds of {name} symbol")
}
DisasmError::FmtFailed(error) => write!(f, "fmt failed: {error}"),
DisasmError::HasOutOfBoundsJumps => write!(
f,
"the code contained jumps that dont got neither to a \
valid symbol or local insturction"
),
}
}
}
#[cfg(feature = "disasm")]
impl core::error::Error for DisasmError<'_> {}
#[cfg(feature = "disasm")]
pub fn disasm<'a>(
binary: &mut &[u8],
functions: &alloc::collections::BTreeMap<u32, (&'a str, u32, DisasmItem)>,
out: &mut alloc::string::String,
mut eca_handler: impl FnMut(&mut &[u8]),
) -> Result<(), DisasmError<'a>> {
use {
self::instrs::Instr,
alloc::{
collections::btree_map::{BTreeMap, Entry},
vec::Vec,
},
core::{convert::TryInto, fmt::Write},
};
fn instr_from_byte(b: u8) -> Result<Instr, DisasmError<'static>> {
b.try_into().map_err(DisasmError::InvalidInstruction)
}
let mut labels = BTreeMap::<u32, u32>::default();
let mut buf = Vec::<instrs::Oper>::new();
let mut has_oob = false;
'_offset_pass: for (&off, &(name, len, kind)) in functions.iter() {
if matches!(kind, DisasmItem::Global) {
continue;
}
let prev = *binary;
*binary = &binary[off as usize..];
let mut label_count = 0;
while let Some(&byte) = binary.first() {
let offset: i32 = (prev.len() - binary.len()).try_into().unwrap();
if offset as u32 == off + len {
break;
}
let Ok(inst) = instr_from_byte(byte) else { break };
instrs::parse_args(binary, inst, &mut buf)
.ok_or(DisasmError::InstructionOutOfBounds(name))?;
for op in buf.drain(..) {
let rel = match op {
instrs::Oper::O(rel) => rel,
instrs::Oper::P(rel) => rel.into(),
_ => continue,
};
let global_offset: u32 = (offset + rel).try_into().unwrap();
if functions.get(&global_offset).is_some() {
continue;
}
label_count += match labels.entry(global_offset) {
Entry::Occupied(_) => 0,
Entry::Vacant(entry) => {
entry.insert(label_count);
1
}
}
}
if matches!(inst, Instr::ECA) {
eca_handler(binary);
}
}
*binary = prev;
}
let mut ordered = functions.iter().collect::<Vec<_>>();
ordered.sort_unstable_by_key(|(_, (name, _, _))| name);
'_dump: for (&off, &(name, len, kind)) in ordered {
if matches!(kind, DisasmItem::Global) {
continue;
}
let prev = *binary;
writeln!(out, "{name}:")?;
*binary = &binary[off as usize..];
while let Some(&byte) = binary.first() {
let offset: i32 = (prev.len() - binary.len()).try_into().unwrap();
if offset as u32 == off + len {
break;
}
let Ok(inst) = instr_from_byte(byte) else {
writeln!(out, "invalid instr {byte}")?;
break;
};
instrs::parse_args(binary, inst, &mut buf).unwrap();
if let Some(label) = labels.get(&offset.try_into().unwrap()) {
write!(out, "{:>2}: ", label)?;
} else {
write!(out, " ")?;
}
write!(out, "{inst:<8?} ")?;
'a: for (i, op) in buf.drain(..).enumerate() {
if i != 0 {
write!(out, ", ")?;
}
let rel = 'b: {
match op {
instrs::Oper::O(rel) => break 'b rel,
instrs::Oper::P(rel) => break 'b rel.into(),
instrs::Oper::R(r) => write!(out, "r{r}")?,
instrs::Oper::B(b) => write!(out, "{b}b")?,
instrs::Oper::H(h) => write!(out, "{h}h")?,
instrs::Oper::W(w) => write!(out, "{w}w")?,
instrs::Oper::D(d) if (d as i64) < 0 => write!(out, "{}d", d as i64)?,
instrs::Oper::D(d) => write!(out, "{d}d")?,
instrs::Oper::A(a) => write!(out, "{a}a")?,
}
continue 'a;
};
let global_offset: u32 = (offset + rel).try_into().unwrap();
if let Some(&(name, ..)) = functions.get(&global_offset) {
if name.contains('\0') {
write!(out, ":{name:?}")?;
} else {
write!(out, ":{name}")?;
}
} else {
let local_has_oob = global_offset < off
|| global_offset > off + len
|| prev
.get(global_offset as usize)
.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 {
write!(out, "!!!!!!!!!{rel}")?;
} else {
write!(out, ":{label}")?;
}
}
}
writeln!(out)?;
if matches!(inst, Instr::ECA) {
eca_handler(binary);
}
}
*binary = prev;
}
if has_oob {
return Err(DisasmError::HasOutOfBoundsJumps);
}
Ok(())
}

23
depell/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "depell"
version = "0.1.0"
edition = "2021"
[dependencies]
argon2 = "0.5.3"
axum = "0.7.7"
axum-server = { version = "0.7.1", optional = true, features = ["rustls", "tls-rustls"] }
const_format = "0.2.33"
getrandom = "0.2.15"
hblang.workspace = true
htmlm = "0.5.0"
log = "0.4.22"
rand_core = { version = "0.6.4", features = ["getrandom"] }
rusqlite = { version = "0.32.1", features = ["bundled"] }
serde = { version = "1.0.210", features = ["derive"] }
time = "0.3.36"
tokio = { version = "1.40.0", features = ["rt"] }
[features]
#default = ["tls"]
tls = ["dep:axum-server"]

14
depell/README.md Normal file
View file

@ -0,0 +1,14 @@
# Depell
Depell is a website that allows users to import/post/run hblang code and create huge dependency graphs. Its currently hosted at https://depell.mlokis.tech.
## Local Development
Prerequirements:
- rust nigthly toolchain: install rust from [here](https://www.rust-lang.org/tools/install)
```bash
rustup default nightly
cargo xtask watch-depell-debug
# browser http://localhost:8080
```

View file

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

Width:  |  Height:  |  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

Width:  |  Height:  |  Size: 190 B

213
depell/src/index.css Normal file
View file

@ -0,0 +1,213 @@
* {
font-family: var(--font);
line-height: 1.3;
}
body {
--primary: light-dark(white, #181A1B);
--secondary: light-dark(#EFEFEF, #212425);
--timestamp: light-dark(#555555, #AAAAAA);
--error: #ff3333;
}
body {
--small-gap: 5px;
--font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--monospace: 'Courier New', Courier, monospace;
nav {
display: flex;
justify-content: space-between;
align-items: center;
section:last-child {
display: flex;
gap: var(--small-gap);
}
}
main {
margin-top: var(--small-gap);
display: flex;
flex-direction: column;
gap: var(--small-gap);
}
}
div.preview {
margin: var(--small-gap) 0px;
display: flex;
flex-direction: column;
gap: var(--small-gap);
div.info {
display: flex;
gap: var(--small-gap);
span[apply=timestamp] {
color: var(--timestamp);
}
}
div.stat {
display: flex;
svg {
height: 18px;
}
}
div.code {
position: relative;
nav {
position: absolute;
right: 0;
padding: var(--small-gap);
button {
display: flex;
padding: 0;
}
}
}
}
svg {
fill: black;
}
form {
display: flex;
flex-direction: column;
gap: var(--small-gap);
.error {
color: var(--error);
text-align: center;
}
}
textarea {
outline: none;
border: none;
background: var(--secondary);
padding: var(--small-gap);
padding-top: calc(var(--small-gap) * 1.5);
font-family: var(--monospace);
resize: none;
tab-size: 4;
}
pre {
background: var(--secondary);
padding: var(--small-gap);
padding-top: calc(var(--small-gap) * 1.5);
margin: 0px;
font-family: var(--monospace);
tab-size: 4;
overflow-x: auto;
white-space: pre-wrap;
word-wrap: break-word;
}
input {
font-size: inherit;
outline: none;
border: none;
background: var(--secondary);
padding: var(--small-gap);
}
input:is(:hover, :focus) {
background: var(--primary);
}
button {
border: none;
outline: none;
font-size: inherit;
background: var(--secondary);
}
button:hover:not(:active) {
background: var(--primary);
}
code {
font-family: var(--monospace);
line-height: 1;
}
div#code-editor {
display: flex;
position: relative;
textarea {
flex: 1;
}
span#code-size {
position: absolute;
right: 2px;
font-size: 12px;
}
}
div#dep-list {
display: flex;
flex-direction: column;
align-items: center;
gap: var(--small-gap);
section {
width: 100%;
display: flex;
flex-direction: column;
text-align: center;
gap: var(--small-gap);
div {
text-align: left;
}
}
}
.syn {
font-family: var(--monospace);
&.Comment {
color: #939f91;
}
&.Keyword {
color: #f85552;
}
&.Identifier,
&.Directive {
color: #3a94c5;
}
/* &.Number {} */
&.String {
color: #8da101;
}
&.Op,
&.Assign {
color: #f57d26;
}
&.Paren,
&.Bracket,
&.Comma,
&.Dot,
&.Ctor,
&.Colon {
color: light-dark(#5c6a72, #999999);
}
}

554
depell/src/index.js Normal file
View file

@ -0,0 +1,554 @@
/// @ts-check
/** @return {never} */
function never() { throw new Error() }
/**@type{WebAssembly.Instance}*/ let hbcInstance;
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let hbcInstaceFuture;
async function getHbcInstance() {
hbcInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbc.wasm"), {});
return hbcInstance ??= (await hbcInstaceFuture).instance;
}
const stack_pointer_offset = 1 << 20;
/** @param {WebAssembly.Instance} instance @param {Post[]} packages @param {number} fuel
* @returns {string} */
function compileCode(instance, packages, fuel = 100) {
let {
INPUT, INPUT_LEN,
LOG_MESSAGES, LOG_MESSAGES_LEN,
memory, compile_and_run,
} = instance.exports;
if (!(true
&& memory instanceof WebAssembly.Memory
&& INPUT instanceof WebAssembly.Global
&& INPUT_LEN instanceof WebAssembly.Global
&& LOG_MESSAGES instanceof WebAssembly.Global
&& LOG_MESSAGES_LEN instanceof WebAssembly.Global
&& typeof compile_and_run === "function"
)) never();
const codeLength = packPosts(packages, new DataView(memory.buffer, INPUT.value));
new DataView(memory.buffer).setUint32(INPUT_LEN.value, codeLength, true);
runWasmFunction(instance, compile_and_run, fuel);
return bufToString(memory, LOG_MESSAGES, LOG_MESSAGES_LEN).trim();
}
/**@type{WebAssembly.Instance}*/ let fmtInstance;
/**@type{Promise<WebAssembly.WebAssemblyInstantiatedSource>}*/ let fmtInstaceFuture;
async function getFmtInstance() {
fmtInstaceFuture ??= WebAssembly.instantiateStreaming(fetch("/hbfmt.wasm"), {});
return fmtInstance ??= (await fmtInstaceFuture).instance;
}
/** @param {WebAssembly.Instance} instance @param {string} code @param {"tok" | "fmt" | "minify"} action
* @returns {string | Uint8Array | undefined} */
function modifyCode(instance, code, action) {
let {
INPUT, INPUT_LEN,
OUTPUT, OUTPUT_LEN,
memory, fmt, tok, minify
} = instance.exports;
let funs = { fmt, tok, minify };
let fun = funs[action];
if (!(true
&& memory instanceof WebAssembly.Memory
&& INPUT instanceof WebAssembly.Global
&& INPUT_LEN instanceof WebAssembly.Global
&& OUTPUT instanceof WebAssembly.Global
&& OUTPUT_LEN instanceof WebAssembly.Global
&& funs.hasOwnProperty(action)
&& typeof fun === "function"
)) never();
if (action !== "fmt") {
INPUT = OUTPUT;
INPUT_LEN = OUTPUT_LEN;
}
let dw = new DataView(memory.buffer);
dw.setUint32(INPUT_LEN.value, code.length, true);
new Uint8Array(memory.buffer, INPUT.value).set(new TextEncoder().encode(code));
if (!runWasmFunction(instance, fun)) {
return undefined;
}
if (action === "tok") {
return bufSlice(memory, OUTPUT, OUTPUT_LEN);
} else {
return bufToString(memory, OUTPUT, OUTPUT_LEN);
}
}
/** @param {WebAssembly.Instance} instance @param {CallableFunction} func @param {any[]} args
* @returns {boolean} */
function runWasmFunction(instance, func, ...args) {
const { PANIC_MESSAGE, PANIC_MESSAGE_LEN, memory, stack_pointer } = instance.exports;
if (!(true
&& memory instanceof WebAssembly.Memory
&& stack_pointer instanceof WebAssembly.Global
)) never();
const ptr = stack_pointer.value;
try {
func(...args);
return true;
} catch (error) {
if (error instanceof WebAssembly.RuntimeError
&& error.message == "unreachable"
&& PANIC_MESSAGE instanceof WebAssembly.Global
&& PANIC_MESSAGE_LEN instanceof WebAssembly.Global) {
console.error(bufToString(memory, PANIC_MESSAGE, PANIC_MESSAGE_LEN), error);
} else {
console.error(error);
}
stack_pointer.value = ptr;
return false;
}
}
/** @typedef {Object} Post
* @property {string} path
* @property {string} code */
/** @param {Post[]} posts @param {DataView} view @returns {number} */
function packPosts(posts, view) {
const enc = new TextEncoder(), buf = new Uint8Array(view.buffer, view.byteOffset);
let len = 0; for (const post of posts) {
view.setUint16(len, post.path.length, true); len += 2;
buf.set(enc.encode(post.path), len); len += post.path.length;
view.setUint16(len, post.code.length, true); len += 2;
buf.set(enc.encode(post.code), len); len += post.code.length;
}
return len;
}
/** @param {WebAssembly.Memory} mem
* @param {WebAssembly.Global} ptr
* @param {WebAssembly.Global} len
* @return {Uint8Array} */
function bufSlice(mem, ptr, len) {
return new Uint8Array(mem.buffer, ptr.value,
new DataView(mem.buffer).getUint32(len.value, true));
}
/** @param {WebAssembly.Memory} mem
* @param {WebAssembly.Global} ptr
* @param {WebAssembly.Global} len
* @return {string} */
function bufToString(mem, ptr, len) {
const res = new TextDecoder()
.decode(new Uint8Array(mem.buffer, ptr.value,
new DataView(mem.buffer).getUint32(len.value, true)));
new DataView(mem.buffer).setUint32(len.value, 0, true);
return res;
}
/** @param {HTMLElement} target */
function wireUp(target) {
execApply(target);
cacheInputs(target);
bindCodeEdit(target);
bindTextareaAutoResize(target);
}
const importRe = /@use\s*\(\s*"(([^"]|\\")+)"\s*\)/g;
/** @param {WebAssembly.Instance} fmt
* @param {string} code
* @param {string[]} roots
* @param {Post[]} buf
* @param {Set<string>} prevRoots
* @returns {void} */
function loadCachedPackages(fmt, code, roots, buf, prevRoots) {
buf[0].code = code;
roots.length = 0;
let changed = false;
for (const match of code.matchAll(importRe)) {
changed ||= !prevRoots.has(match[1]);
roots.push(match[1]);
}
if (!changed) return;
buf.length = 1;
prevRoots.clear();
for (let imp = roots.pop(); imp !== undefined; imp = roots.pop()) {
if (prevRoots.has(imp)) continue; prevRoots.add(imp);
const fmtd = modifyCode(fmt, localStorage.getItem("package-" + imp) ?? never(), "fmt");
if (typeof fmtd != "string") never();
buf.push({ path: imp, code: fmtd });
for (const match of buf[buf.length - 1].code.matchAll(importRe)) {
roots.push(match[1]);
}
}
}
/**@type{Set<string>}*/ const prevRoots = new Set();
/**@typedef {Object} PackageCtx
* @property {AbortController} [cancelation]
* @property {string[]} keyBuf
* @property {Set<string>} prevParams
* @property {HTMLTextAreaElement} [edit] */
/** @param {string} source @param {Set<string>} importDiff @param {HTMLPreElement} errors @param {PackageCtx} ctx */
async function fetchPackages(source, importDiff, errors, ctx) {
importDiff.clear();
for (const match of source.matchAll(importRe)) {
if (localStorage["package-" + match[1]]) continue;
importDiff.add(match[1]);
}
if (importDiff.size !== 0 && (ctx.prevParams.size != importDiff.size
|| [...ctx.prevParams.keys()].every(e => importDiff.has(e)))) {
if (ctx.cancelation) ctx.cancelation.abort();
ctx.prevParams.clear();
ctx.prevParams = new Set([...importDiff]);
ctx.cancelation = new AbortController();
ctx.keyBuf.length = 0;
ctx.keyBuf.push(...importDiff.keys());
errors.textContent = "fetching: " + ctx.keyBuf.join(", ");
await fetch(`/code`, {
method: "POST",
signal: ctx.cancelation.signal,
headers: { "Content-Type": "application/json" },
body: JSON.stringify(ctx.keyBuf),
}).then(async e => {
try {
const json = await e.json();
if (e.status == 200) {
for (const [key, value] of Object.entries(json)) {
localStorage["package-" + key] = value;
}
const missing = ctx.keyBuf.filter(i => json[i] === undefined);
if (missing.length !== 0) {
errors.textContent = "deps not found: " + missing.join(", ");
} else {
ctx.cancelation = undefined;
ctx.edit?.dispatchEvent(new InputEvent("input"));
}
}
} catch (er) {
errors.textContent = "completely failed to fetch ("
+ e.status + "): " + ctx.keyBuf.join(", ");
console.error(e, er);
}
});
}
}
/** @param {HTMLElement} target */
async function bindCodeEdit(target) {
const edit = target.querySelector("#code-edit");
if (!(edit instanceof HTMLTextAreaElement)) return;
const codeSize = target.querySelector("#code-size");
const errors = target.querySelector("#compiler-output");
if (!(true
&& codeSize instanceof HTMLSpanElement
&& errors instanceof HTMLPreElement
)) never();
const MAX_CODE_SIZE = parseInt(codeSize.innerHTML);
if (Number.isNaN(MAX_CODE_SIZE)) never();
const hbc = await getHbcInstance(), fmt = await getFmtInstance();
let importDiff = new Set();
/**@type{Post[]}*/
const packages = [{ path: "local.hb", code: "" }];
const debounce = 100;
let timeout = 0;
const ctx = { keyBuf: [], prevParams: new Set(), edit };
prevRoots.clear();
const onInput = () => {
fetchPackages(edit.value, importDiff, errors, ctx);
if (ctx.cancelation && importDiff.size !== 0) {
return;
}
loadCachedPackages(fmt, edit.value, ctx.keyBuf, packages, prevRoots);
errors.textContent = compileCode(hbc, packages);
const minified_size = modifyCode(fmt, edit.value, "minify")?.length;
if (minified_size) {
codeSize.textContent = (MAX_CODE_SIZE - minified_size) + "";
const perc = Math.min(100, Math.floor(100 * (minified_size / MAX_CODE_SIZE)));
codeSize.style.color = `color-mix(in srgb, light-dark(black, white), var(--error) ${perc}%)`;
}
timeout = 0;
};
edit.addEventListener("input", () => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(onInput, debounce)
});
edit.dispatchEvent(new InputEvent("input"));
}
/**
* @type {Array<string>}
* to be synched with `enum TokenGroup` in bytecode/src/fmt.rs */
const TOK_CLASSES = [
'Blank',
'Comment',
'Keyword',
'Identifier',
'Directive',
'Number',
'String',
'Op',
'Assign',
'Paren',
'Bracket',
'Colon',
'Comma',
'Dot',
'Ctor',
];
/** @type {{ [key: string]: (el: HTMLElement) => void | Promise<void> }} */
const applyFns = {
timestamp: (el) => {
const timestamp = el.innerText;
const date = new Date(parseInt(timestamp) * 1000);
el.innerText = date.toLocaleString();
},
fmt,
};
/**
* @param {HTMLElement} target */
async function fmt(target) {
const code = target.innerText;
const instance = await getFmtInstance();
const decoder = new TextDecoder('utf-8');
const fmt = modifyCode(instance, code, 'fmt');
if (typeof fmt !== "string") 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();
const vl = applyFns[funcname](elem);
if (vl instanceof Promise) proises.push(vl);
}
if (target === document.body) {
Promise.all(proises).then(() => document.body.hidden = false);
}
}
/** @param {HTMLElement} target */
function bindTextareaAutoResize(target) {
for (const textarea of target.querySelectorAll("textarea")) {
if (!(textarea instanceof HTMLTextAreaElement)) never();
const taCssMap = window.getComputedStyle(textarea);
const padding = parseInt(taCssMap.getPropertyValue('padding-top') ?? "0")
+ parseInt(taCssMap.getPropertyValue('padding-top') ?? "0");
textarea.style.height = "auto";
textarea.style.height = (textarea.scrollHeight - padding) + "px";
textarea.style.overflowY = "hidden";
textarea.addEventListener("input", function() {
let top = window.scrollY;
textarea.style.height = "auto";
textarea.style.height = (textarea.scrollHeight - padding) + "px";
window.scrollTo({ top });
});
textarea.onkeydown = (ev) => {
if (ev.key === "Tab") {
ev.preventDefault();
document.execCommand('insertText', false, "\t");
}
}
}
}
/** @param {HTMLElement} target */
function cacheInputs(target) {
/**@type {HTMLFormElement}*/ let form;
for (form of target.querySelectorAll('form')) {
const path = form.getAttribute('hx-post') || form.getAttribute('hx-delete');
if (!path) {
console.warn('form does not have a hx-post or hx-delete attribute', form);
continue;
}
for (const input of form.elements) {
if (input instanceof HTMLInputElement || input instanceof HTMLTextAreaElement) {
if ('password submit button'.includes(input.type)) continue;
const key = path + input.name;
input.value = localStorage.getItem(key) ?? '';
input.addEventListener("input", () => localStorage.setItem(key, input.value));
} else {
console.warn("unhandled form element: ", input);
}
}
}
}
/** @param {string} [path] */
function updateTab(path) {
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
|| elem.getAttribute("hx-push-url") === window.location.pathname;
}
}
if (window.location.hostname === 'localhost') {
let id; setInterval(async () => {
let new_id = await fetch('/hot-reload').then(reps => reps.text());
id ??= new_id;
if (id !== new_id) window.location.reload();
}, 300);
(async function test() {
{
const code = "main:=fn():void{return}";
const inst = await getFmtInstance()
const fmtd = modifyCode(inst, code, "fmt") ?? never();
if (typeof fmtd !== "string") never();
const prev = modifyCode(inst, fmtd, "minify") ?? never();
if (code != prev) console.error(code, prev);
}
{
const posts = [{
path: "foo.hb",
code: "main:=fn():int{return 42}",
}];
const res = compileCode(await getHbcInstance(), posts, 1) ?? never();
const expected = "exit code: 42";
if (expected != res) console.error(expected, res);
}
})()
}
document.body.addEventListener('htmx:afterSwap', (ev) => {
if (!(ev.target instanceof HTMLElement)) never();
wireUp(ev.target);
if (ev.target.tagName == "MAIN" || ev.target.tagName == "BODY")
updateTab(ev['detail'].pathInfo.finalRequestPath);
});
getFmtInstance().then(inst => {
document.body.addEventListener('htmx:configRequest', (ev) => {
const details = ev['detail'];
if (details.path === "/post" && details.verb === "post") {
details.parameters['code'] = modifyCode(inst, details.parameters['code'], "minify");
}
});
/** @param {string} query @param {string} target @returns {number} */
function fuzzyCost(query, target) {
let qi = 0, bi = 0, cost = 0, matched = false;
while (qi < query.length) {
if (query.charAt(qi) === target.charAt(bi++)) {
matched = true;
qi++;
} else {
cost++;
}
if (bi === target.length) (bi = 0, qi++);
}
return cost + (matched ? 0 : 100 * target.length);
}
let deps = undefined;
/** @param {HTMLInputElement} input @returns {void} */
function filterCodeDeps(input) {
deps ??= document.getElementById("deps");
if (!(deps instanceof HTMLElement)) never();
if (input.value === "") {
deps.textContent = "results show here...";
return;
}
deps.innerHTML = "";
for (const root of [...prevRoots.keys()]
.sort((a, b) => fuzzyCost(input.value, a) - fuzzyCost(input.value, b))) {
const pane = document.createElement("div");
const code = modifyCode(inst, localStorage["package-" + root], "fmt");
pane.innerHTML = `<div>${root}</div><pre>${code}</pre>`;
deps.appendChild(pane);
}
if (deps.innerHTML === "") {
deps.textContent = "no results";
}
}
Object.assign(window, { filterCodeDeps });
});
/** @param {HTMLElement} target */
function runPost(target) {
while (!target.matches("div[class=preview]")) target = target.parentElement ?? never();
const code = target.querySelector("pre[apply=fmt]");
if (!(code instanceof HTMLPreElement)) never();
const output = target.querySelector("pre[id=compiler-output]");
if (!(output instanceof HTMLPreElement)) never();
Promise.all([getHbcInstance(), getFmtInstance()]).then(async ([hbc, fmt]) => {
const ctx = { keyBuf: [], prevParams: new Set() };
await fetchPackages(code.innerText ?? never(), new Set(), output, ctx);
const posts = [{ path: "this", code: "" }];
loadCachedPackages(fmt, code.innerText ?? never(), ctx.keyBuf, posts, new Set());
output.textContent = compileCode(hbc, posts);
output.hidden = false;
});
let author = encodeURIComponent(target.dataset.author ?? never());
let name = encodeURIComponent(target.dataset.name ?? never());
fetch(`/post/run?author=${author}&name=${name}`, { method: "POST" })
}
Object.assign(window, { runPost });
updateTab();
wireUp(document.body);

1040
depell/src/main.rs Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@

55
depell/src/schema.sql Normal file
View file

@ -0,0 +1,55 @@
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS user(
name TEXT NOT NULL,
password_hash TEXT NOT NULL,
PRIMARY KEY (name)
) WITHOUT ROWID;
CREATE TABLE IF NOT EXISTS session(
id BLOB NOT NULL,
username TEXT NOT NULL,
expiration INTEGER NOT NULL,
FOREIGN KEY (username) REFERENCES user (name)
PRIMARY KEY (username)
) WITHOUT ROWID;
CREATE UNIQUE INDEX IF NOT EXISTS
session_id ON session (id);
CREATE TABLE IF NOT EXISTS post(
name TEXT NOT NULL,
author TEXT,
timestamp INTEGER,
code TEXT NOT NULL,
FOREIGN KEY (author) REFERENCES user(name) ON DELETE SET NULL,
PRIMARY KEY (author, name)
);
CREATE INDEX IF NOT EXISTS
post_timestamp ON post(timestamp DESC);
CREATE TABLE IF NOT EXISTS import(
from_name TEXT NOT NULL,
from_author TEXT,
to_name TEXT NOT NULL,
to_author TEXT,
FOREIGN KEY (from_name, from_author) REFERENCES post(name, author),
FOREIGN KEY (to_name, to_author) REFERENCES post(name, author)
);
CREATE INDEX IF NOT EXISTS
dependencies ON import(from_name, from_author);
CREATE INDEX IF NOT EXISTS
dependants ON import(to_name, to_author);
CREATE TABLE IF NOT EXISTS run(
code_name TEXT NOT NULL,
code_author TEXT NOT NULL,
runner TEXT NOT NULL,
FOREIGN KEY (code_name, code_author) REFERENCES post(name, author),
FOREIGN KEY (runner) REFERENCES user(name),
PRIMARY KEY (code_name, code_author, runner)
);

View file

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

@ -0,0 +1,11 @@
[package]
name = "wasm-hbfmt"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
hblang = { workspace = true, features = ["no_log"] }
wasm-rt = { version = "0.1.0", path = "../wasm-rt" }

View file

@ -0,0 +1,42 @@
#![no_std]
#![feature(str_from_raw_parts)]
#![feature(alloc_error_handler)]
use hblang::{fmt, parser};
wasm_rt::decl_runtime!(128 * 1024, 1024 * 4);
const MAX_OUTPUT_SIZE: usize = 1024 * 10;
wasm_rt::decl_buffer!(MAX_OUTPUT_SIZE, MAX_OUTPUT, OUTPUT, OUTPUT_LEN);
const MAX_INPUT_SIZE: usize = 1024 * 4;
wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
#[no_mangle]
unsafe extern "C" fn fmt() {
ALLOCATOR.reset();
let code = core::str::from_raw_parts(core::ptr::addr_of!(INPUT).cast(), INPUT_LEN);
let arena = parser::Arena::with_capacity(code.len() * parser::SOURCE_TO_AST_FACTOR);
let mut ctx = parser::Ctx::default();
let exprs = parser::Parser::parse(&mut ctx, code, "source.hb", &mut parser::no_loader, &arena);
let mut f = wasm_rt::Write(&mut OUTPUT[..]);
fmt::fmt_file(exprs, code, &mut f).unwrap();
OUTPUT_LEN = MAX_OUTPUT_SIZE - f.0.len();
}
#[no_mangle]
unsafe extern "C" fn tok() {
let code = core::slice::from_raw_parts_mut(
core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
OUTPUT_LEN = fmt::get_token_kinds(code);
}
#[no_mangle]
unsafe extern "C" fn minify() {
let code = core::str::from_raw_parts_mut(
core::ptr::addr_of_mut!(OUTPUT).cast(), OUTPUT_LEN);
OUTPUT_LEN = fmt::minify(code);
}

View file

@ -0,0 +1,14 @@
[package]
name = "wasm-hbc"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
hblang = { workspace = true, features = [] }
hbvm.workspace = true
log = { version = "0.4.22", features = ["release_max_level_error"] }
wasm-rt = { version = "0.1.0", path = "../wasm-rt", features = ["log"] }

128
depell/wasm-hbc/src/lib.rs Normal file
View file

@ -0,0 +1,128 @@
#![feature(alloc_error_handler)]
#![feature(slice_take)]
#![no_std]
use {
alloc::{string::String, vec::Vec},
core::ffi::CStr,
hblang::{
backend::hbvm::HbvmBackend,
son::{Codegen, CodegenCtx},
ty::Module,
Ent,
},
};
extern crate alloc;
const ARENA_CAP: usize = 128 * 16 * 1024;
wasm_rt::decl_runtime!(ARENA_CAP, 1024 * 4);
const MAX_INPUT_SIZE: usize = 32 * 4 * 1024;
wasm_rt::decl_buffer!(MAX_INPUT_SIZE, MAX_INPUT, INPUT, INPUT_LEN);
#[no_mangle]
unsafe fn compile_and_run(mut fuel: usize) {
ALLOCATOR.reset();
_ = log::set_logger(&wasm_rt::Logger);
log::set_max_level(log::LevelFilter::Error);
struct File<'a> {
path: &'a str,
code: &'a mut str,
}
let mut root = 0;
let files = {
let mut input_bytes =
core::slice::from_raw_parts_mut(core::ptr::addr_of_mut!(INPUT).cast::<u8>(), INPUT_LEN);
let mut files = Vec::with_capacity(32);
while let Some((&mut path_len, rest)) = input_bytes.split_first_chunk_mut() {
let (path, rest) = rest.split_at_mut(u16::from_le_bytes(path_len) as usize);
let (&mut code_len, rest) = rest.split_first_chunk_mut().unwrap();
let (code, rest) = rest.split_at_mut(u16::from_le_bytes(code_len) as usize);
files.push(File {
path: core::str::from_utf8_unchecked(path),
code: core::str::from_utf8_unchecked_mut(code),
});
input_bytes = rest;
}
let root_path = files[root].path;
hblang::quad_sort(&mut files, |a, b| a.path.cmp(b.path));
root = files.binary_search_by_key(&root_path, |p| p.path).unwrap();
files
};
let mut ctx = CodegenCtx::default();
let files = {
let paths = files.iter().map(|f| f.path).collect::<Vec<_>>();
let mut loader = |path: &str, _: &str, kind| match kind {
hblang::parser::FileKind::Module => Ok(paths.binary_search(&path).unwrap()),
hblang::parser::FileKind::Embed => Err("embeds are not supported".into()),
};
files
.into_iter()
.map(|f| {
hblang::parser::Ast::new(
f.path,
// since 'free' does nothing this is fine
String::from_raw_parts(f.code.as_mut_ptr(), f.code.len(), f.code.len()),
&mut ctx.parser,
&mut loader,
)
})
.collect::<Vec<_>>()
};
let mut ct = {
let mut backend = HbvmBackend::default();
Codegen::new(&mut backend, &files, &mut ctx).generate(Module::new(root));
if !ctx.parser.errors.borrow().is_empty() {
log::error!("{}", ctx.parser.errors.borrow());
return;
}
let mut c = Codegen::new(&mut backend, &files, &mut ctx);
c.assemble_comptime()
};
while fuel != 0 {
match ct.vm.run() {
Ok(hbvm::VmRunOk::End) => {
log::error!("exit code: {}", ct.vm.read_reg(1).0 as i64);
break;
}
Ok(hbvm::VmRunOk::Ecall) => {
let kind = ct.vm.read_reg(2).0;
match kind {
0 => {
let str = ct.vm.read_reg(3).0;
let str = unsafe { CStr::from_ptr(str as _) };
log::error!("{}", str.to_str().unwrap());
}
unknown => log::error!("unknown ecall: {unknown}"),
}
}
Ok(hbvm::VmRunOk::Timer) => {
fuel -= 1;
if fuel == 0 {
log::error!("program timed out");
}
}
Ok(hbvm::VmRunOk::Breakpoint) => todo!(),
Err(e) => {
log::error!("vm error: {e}");
break;
}
}
}
//log::error!("memory consumption: {}b / {}b", ALLOCATOR.used(), ARENA_CAP);
}

View file

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

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

@ -0,0 +1,162 @@
#![feature(alloc_error_handler)]
#![feature(pointer_is_aligned_to)]
#![feature(slice_take)]
#![no_std]
use core::{
alloc::{GlobalAlloc, Layout},
cell::UnsafeCell,
};
extern crate alloc;
#[macro_export]
macro_rules! decl_buffer {
($cap:expr, $export_cap:ident, $export_base:ident, $export_len:ident) => {
#[no_mangle]
static $export_cap: usize = $cap;
#[no_mangle]
static mut $export_base: [u8; $cap] = [0; $cap];
#[no_mangle]
static mut $export_len: usize = 0;
};
}
#[macro_export]
macro_rules! decl_runtime {
($memory_size:expr, $max_panic_size:expr) => {
#[cfg(debug_assertions)]
#[no_mangle]
static mut PANIC_MESSAGE: [u8; $max_panic_size] = [0; $max_panic_size];
#[cfg(debug_assertions)]
#[no_mangle]
static mut PANIC_MESSAGE_LEN: usize = 0;
#[cfg(target_arch = "wasm32")]
#[panic_handler]
pub fn handle_panic(_info: &core::panic::PanicInfo) -> ! {
#[cfg(debug_assertions)]
{
unsafe {
use core::fmt::Write;
let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
_ = writeln!(f, "{}", _info);
PANIC_MESSAGE_LEN = $max_panic_size - f.0.len();
}
}
core::arch::wasm32::unreachable();
}
#[global_allocator]
static ALLOCATOR: $crate::ArenaAllocator<{ $memory_size }> = $crate::ArenaAllocator::new();
#[cfg(target_arch = "wasm32")]
#[alloc_error_handler]
fn alloc_error(_: core::alloc::Layout) -> ! {
#[cfg(debug_assertions)]
{
unsafe {
use core::fmt::Write;
let mut f = $crate::Write(&mut PANIC_MESSAGE[..]);
_ = writeln!(f, "out of memory");
PANIC_MESSAGE_LEN = $max_panic_size - f.0.len();
}
}
core::arch::wasm32::unreachable()
}
};
}
#[cfg(feature = "log")]
pub struct Logger;
#[cfg(feature = "log")]
impl log::Log for Logger {
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
const MAX_LOG_MESSAGE: usize = 1024 * 8;
#[no_mangle]
static mut LOG_MESSAGES: [u8; MAX_LOG_MESSAGE] = [0; MAX_LOG_MESSAGE];
#[no_mangle]
static mut LOG_MESSAGES_LEN: usize = 0;
unsafe {
use core::fmt::Write;
let mut f = Write(&mut LOG_MESSAGES[LOG_MESSAGES_LEN..]);
_ = writeln!(f, "{}", record.args());
LOG_MESSAGES_LEN = MAX_LOG_MESSAGE - f.0.len();
}
}
}
fn flush(&self) {}
}
pub struct ArenaAllocator<const SIZE: usize> {
arena: UnsafeCell<[u8; SIZE]>,
head: UnsafeCell<*mut u8>,
}
impl<const SIZE: usize> ArenaAllocator<SIZE> {
#[expect(clippy::new_without_default)]
pub const fn new() -> Self {
ArenaAllocator {
arena: UnsafeCell::new([0; SIZE]),
head: UnsafeCell::new(core::ptr::null_mut()),
}
}
#[expect(clippy::missing_safety_doc)]
pub unsafe fn reset(&self) {
(*self.head.get()) = self.arena.get().cast::<u8>().add(SIZE);
}
pub fn used(&self) -> usize {
unsafe { self.arena.get() as usize + SIZE - (*self.head.get()) as usize }
}
}
unsafe impl<const SIZE: usize> Sync for ArenaAllocator<SIZE> {}
unsafe impl<const SIZE: usize> GlobalAlloc for ArenaAllocator<SIZE> {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = layout.size();
let align = layout.align();
let until = self.arena.get() as *mut u8;
let new_head = (*self.head.get()).sub(size);
let aligned_head = (new_head as usize & !(align - 1)) as *mut u8;
debug_assert!(aligned_head.is_aligned_to(align));
if until > aligned_head {
return core::ptr::null_mut();
}
*self.head.get() = aligned_head;
aligned_head
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
/* lol */
}
}
pub struct Write<'a>(pub &'a mut [u8]);
impl core::fmt::Write for Write<'_> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
if let Some(m) = self.0.take_mut(..s.len()) {
m.copy_from_slice(s.as_bytes());
Ok(())
} else {
Err(core::fmt::Error)
}
}
}

100
examples/asm/hello-name.hba Normal file
View file

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

Binary file not shown.

Binary file not shown.

View file

@ -1,22 +0,0 @@
[package]
name = "hbasm"
version = "0.1.0"
edition = "2021"
[dependencies]
ariadne = "0.3"
bytemuck = "1.13"
hashbrown = "0.14"
hbbytecode = { path = "../hbbytecode" }
literify = "0.1"
paste = "1.0"
[dependencies.lasso]
version = "0.7"
default-features = false
features = ["no-std"]
[dependencies.logos]
version = "0.13"
default-features = false
features = ["export_derive"]

View file

@ -1,11 +0,0 @@
addi r1, r0, 1024
addi r2, r1, 1024
addi r3, r2, 1024
addi r4, r3, 1024
addi r5, r4, 1024
addi r6, r5, 1024
addi r7, r6, 1024
addi r8, r7, 1024
addi r9, r8, 1024
ecall

View file

@ -1,2 +0,0 @@
L:
jal r0, r0, L

View file

@ -1,3 +0,0 @@
li r20, 1010
st r20, r24, 0, 1
addi r24, r0, 10

View file

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

View file

@ -1,104 +0,0 @@
//! Holey Bytes Assembler
//!
//! Some people claim:
//! > Write programs to handle text streams, because that is a universal interface.
//!
//! We at AbleCorp believe that nice programatic API is nicer than piping some text
//! into a program. It's less error-prone and faster.
//!
//! So this crate contains both assembleer with API for programs and a text assembler
//! for humans to write
#![no_std]
extern crate alloc;
mod macros;
use {
alloc::{vec, vec::Vec},
hashbrown::HashSet,
};
/// Assembler
///
/// - Opcode-generic, instruction-type-specific methods are named `i_param_<type>`
/// - You likely won't need to use them, but they are here, just in case :)
/// - Instruction-specific methods are named `i_<instruction>`
pub struct Assembler {
pub buf: Vec<u8>,
pub sub: HashSet<usize>,
}
impl Default for Assembler {
fn default() -> Self {
Self {
buf: vec![0; 4],
sub: Default::default(),
}
}
}
hbbytecode::invoke_with_def!(macros::text::gen_text);
impl Assembler {
hbbytecode::invoke_with_def!(macros::asm::impl_asm);
/// Append 12 zeroes (UN) at the end and add magic to the begining
///
/// # HoleyBytes lore
///
/// In reference HBVM implementation checks are done in
/// a separate phase before execution.
///
/// This way execution will be much faster as they have to
/// be done only once.
///
/// There was an issue. You cannot statically check register values and
/// `JAL` instruction could hop at the end of program to some byte, which
/// will be interpreted as some valid opcode and VM in attempt to decode
/// the instruction performed out-of-bounds read which leads to undefined behaviour.
///
/// Several options were considered to overcome this, but inserting some data at
/// program's end which when executed would lead to undesired behaviour, though
/// not undefined behaviour.
///
/// Newly created `UN` (as UNreachable) was chosen as
/// - It was a good idea to add some equivalent to `ud2` anyways
/// - It was chosen to be zero
/// - What if you somehow reached that code, it will appropriately bail :)
/// - (yes, originally `NOP` was considered)
///
/// Why 12 bytes? That's the size of largest instruction parameter part.
pub fn finalise(&mut self) {
self.buf.extend([0; 12]);
self.buf[0..4].copy_from_slice(&0xAB1E0B_u32.to_le_bytes());
}
}
/// Immediate value
///
/// # Implementor notice
/// It should insert exactly 8 bytes, otherwise output will be malformed.
/// This is not checked in any way
pub trait Imm {
/// Insert immediate value
fn insert(&self, asm: &mut Assembler);
}
/// Implement immediate values
macro_rules! impl_imm_le_bytes {
($($ty:ty),* $(,)?) => {
$(
impl Imm for $ty {
#[inline(always)]
fn insert(&self, asm: &mut Assembler) {
// Convert to little-endian bytes, insert.
asm.buf.extend(self.to_le_bytes());
}
}
)*
};
}
impl_imm_le_bytes!(u64, i64, f64);

View file

@ -1,89 +0,0 @@
//! Macros to generate [`crate::Assembler`]
/// Incremental token-tree muncher to implement specific instruction
/// functions based on generic function for instruction type
macro_rules! impl_asm_opcodes {
( // End case
$generic:ident
($($param_i:ident: $param_ty:ty),*)
=> []
) => {};
(
$generic:ident
($($param_i:ident: $param_ty:ty),*)
=> [$opcode:ident, $($rest:tt)*]
) => {
// Instruction-specific function
paste::paste! {
#[inline(always)]
pub fn [<i_ $opcode:lower>](&mut self, $($param_i: $param_ty),*) {
self.$generic(hbbytecode::opcode::$opcode, $($param_i),*)
}
}
// And recurse!
macros::asm::impl_asm_opcodes!(
$generic($($param_i: $param_ty),*)
=> [$($rest)*]
);
};
}
/// Numeric value insert
macro_rules! impl_asm_insert {
// Immediate - this is trait-based,
// the insertion is delegated to its implementation
($self:expr, $id:ident, I) => {
Imm::insert(&$id, $self)
};
// Length - cannot be more than 2048
($self:expr, $id:ident, L) => {{
assert!($id <= 2048);
$self.buf.extend($id.to_le_bytes())
}};
// Other numbers, just insert their bytes, little endian
($self:expr, $id:ident, $_:ident) => {
$self.buf.extend($id.to_le_bytes())
};
}
/// Implement assembler
macro_rules! impl_asm {
(
$(
$ityn:ident
($($param_i:ident: $param_ty:ident),* $(,)?)
=> [$($opcode:ident),* $(,)?],
)*
) => {
paste::paste! {
$(
// Opcode-generic functions specific for instruction types
pub fn [<i_param_ $ityn>](&mut self, opcode: u8, $($param_i: macros::asm::ident_map_ty!($param_ty)),*) {
self.buf.push(opcode);
$(macros::asm::impl_asm_insert!(self, $param_i, $param_ty);)*
}
// Generate opcode-specific functions calling the opcode-generic ones
macros::asm::impl_asm_opcodes!(
[<i_param_ $ityn>]($($param_i: macros::asm::ident_map_ty!($param_ty)),*)
=> [$($opcode,)*]
);
)*
}
};
}
/// Map operand type to Rust type
#[rustfmt::skip]
macro_rules! ident_map_ty {
(R) => { u8 }; // Register is just u8
(I) => { impl Imm }; // Immediate is anything implementing the trait
(L) => { u16 }; // Copy count
($id:ident) => { $id }; // Anything else → identity map
}
pub(crate) use {ident_map_ty, impl_asm, impl_asm_insert, impl_asm_opcodes};

View file

@ -1,6 +0,0 @@
//! And here the land of macros begin.
//!
//! They do not bite, really. Have you seen what Yandros is writing?
pub mod asm;
pub mod text;

View file

@ -1,293 +0,0 @@
//! Macros to generate text-code assembler at [`crate::text`]
// Refering in module which generates a module to that module — is that even legal? :D
/// Generate text code based assembler
macro_rules! gen_text {
(
$(
$ityn:ident
($($param_i:ident: $param_ty:ident),* $(,)?)
=> [$($opcode:ident),* $(,)?],
)*
) => {
/// # Text assembler
/// Text assembler generated simply calls methods in the [`crate::Assembler`] type.
///
/// # Syntax
/// ```text
/// instruction op1, op2, …
/// …
/// ```
/// - Opcode names are lowercase
/// - Registers are prefixed with `r` followed by number
/// - Operands are separated by `,`
/// - Instructions are separated by either line feed or `;` (αυτό δεν είναι ερωτηματικό!)
/// - Labels are defined by their names followed by colon `label:`
/// - Labels are referenced simply by their names
/// - Immediates are numbers, can be negative, floats are not yet supported
pub mod text {
use {
crate::{
Assembler,
macros::text::*,
},
hashbrown::HashMap,
lasso::{Key, Rodeo, Spur},
logos::{Lexer, Logos, Span},
};
paste::paste!(literify::literify! {
/// Assembly token
#[derive(Clone, Copy, Debug, PartialEq, Eq, Logos)]
#[logos(extras = Rodeo)]
#[logos(skip r"[ \t\t]+")]
#[logos(skip r"-- .*")]
pub enum Token {
$($(#[token(~([<$opcode:lower>]), |_| hbbytecode::opcode::[<$opcode:upper>])])*)*
Opcode(u8),
#[regex("[0-9]+", |l| l.slice().parse().ok())]
#[regex(
"-[0-9]+",
|lexer| {
Some(u64::from_ne_bytes(lexer.slice().parse::<i64>().ok()?.to_ne_bytes()))
},
)] Integer(u64),
#[regex(
"r[0-9]+",
|lexer| match lexer.slice()[1..].parse() {
Ok(n) => Some(n),
_ => None
},
)] Register(u8),
#[regex(
r"\p{XID_Start}\p{XID_Continue}*:",
|lexer| lexer.extras.get_or_intern(&lexer.slice()[..lexer.slice().len() - 1]),
)] Label(Spur),
#[regex(
r"\p{XID_Start}\p{XID_Continue}*",
|lexer| lexer.extras.get_or_intern(lexer.slice()),
)] Symbol(Spur),
#[token("\n")]
#[token(";")] ISep,
#[token(",")] PSep,
}
});
/// Type of error
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum ErrorKind {
UnexpectedToken,
InvalidToken,
UnexpectedEnd,
InvalidSymbol,
}
/// Text assembly error
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Error {
pub kind: ErrorKind,
pub span: Span,
}
/// Parse code and insert instructions
pub fn assemble(asm: &mut Assembler, code: &str) -> Result<(), Error> {
pub struct TextAsm<'a> {
asm: &'a mut Assembler,
lexer: Lexer<'a, Token>,
symloc: HashMap<Spur, usize>,
}
impl<'a> TextAsm<'a> {
fn next(&mut self) -> Result<Token, ErrorKind> {
match self.lexer.next() {
Some(Ok(t)) => Ok(t),
Some(Err(())) => Err(ErrorKind::InvalidToken),
None => Err(ErrorKind::UnexpectedEnd),
}
}
#[inline(always)]
fn run(&mut self) -> Result<(), ErrorKind> {
loop {
match self.lexer.next() {
// Got an opcode
Some(Ok(Token::Opcode(op))) => {
match op {
// Special-cased
hbbytecode::opcode::BRC => {
param_extract_itm!(
self,
p0: R,
p1: R,
p2: u8
);
self.asm.i_param_bbb(op, p0, p1, p2);
},
// Take all the opcodes and match them to their corresponding functions
$(
#[allow(unreachable_patterns)]
$(hbbytecode::opcode::$opcode)|* => paste::paste!({
param_extract_itm!(self, $($param_i: $param_ty),*);
self.asm.[<i_param_ $ityn>](op, $($param_i),*);
}),
)*
// Already matched in Logos, should not be able to obtain
// invalid opcode.
_ => unreachable!(),
}
}
// Insert label to table
Some(Ok(Token::Label(lbl))) => {
self.symloc.insert(lbl, self.asm.buf.len());
}
// Instruction separator (LF, ;)
Some(Ok(Token::ISep)) => (),
Some(Ok(_)) => return Err(ErrorKind::UnexpectedToken),
Some(Err(())) => return Err(ErrorKind::InvalidToken),
None => return Ok(()),
}
}
}
}
let mut asm = TextAsm {
asm,
lexer: Token::lexer(code),
symloc: HashMap::default(),
};
asm.run()
.map_err(|kind| Error { kind, span: asm.lexer.span() })?;
// Walk table and substitute labels
// for their addresses
for &loc in &asm.asm.sub {
// Extract indices from the code and get addresses from table
let val = asm.symloc
.get(
&Spur::try_from_usize(bytemuck::pod_read_unaligned::<u64>(
&asm.asm.buf[loc..loc + core::mem::size_of::<u64>()]) as _
).unwrap()
)
.ok_or(Error { kind: ErrorKind::InvalidSymbol, span: 0..0 })?
.to_le_bytes();
// New address
asm.asm.buf[loc..]
.iter_mut()
.zip(val)
.for_each(|(dst, src)| *dst = src);
}
Ok(())
}
// Fun fact: this is a little hack
// It may slow the things a little bit down, but
// it made the macro to be made pretty nice.
//
// If you have any idea how to get rid of this,
// contributions are welcome :)
// I *likely* won't try anymore.
enum InternalImm {
Const(u64),
Named(Spur),
}
impl $crate::Imm for InternalImm {
#[inline]
fn insert(&self, asm: &mut Assembler) {
match self {
// Constant immediate, just put it in
Self::Const(a) => a.insert(asm),
// Label
Self::Named(a) => {
// Insert to the sub table that substitution will be
// requested
asm.sub.insert(asm.buf.len());
// Insert value from interner in place
asm.buf.extend((a.into_usize() as u64).to_le_bytes());
},
}
}
}
}
};
}
/// Extract item by pattern, otherwise return [`ErrorKind::UnexpectedToken`]
macro_rules! extract_pat {
($self:expr, $pat:pat) => {
let $pat = $self.next()?
else { return Err(ErrorKind::UnexpectedToken) };
};
}
/// Generate extract macro
macro_rules! gen_extract {
// Integer types have same body
($($int:ident),* $(,)?) => {
/// Extract operand from code
macro_rules! extract {
// Register (require prefixing with r)
($self:expr, R, $id:ident) => {
extract_pat!($self, Token::Register($id));
};
($self:expr, L, $id:ident) => {
extract_pat!($self, Token::Integer($id));
if $id > 2048 {
return Err(ErrorKind::InvalidToken);
}
let $id = u16::try_from($id).unwrap();
};
// Immediate
($self:expr, I, $id:ident) => {
let $id = match $self.next()? {
// Either straight up integer
Token::Integer(a) => InternalImm::Const(a),
// …or a label
Token::Symbol(a) => InternalImm::Named(a),
_ => return Err(ErrorKind::UnexpectedToken),
};
};
// Get $int, if not fitting, the token is claimed invalid
$(($self:expr, $int, $id:ident) => {
extract_pat!($self, Token::Integer($id));
let $id = $int::try_from($id).map_err(|_| ErrorKind::InvalidToken)?;
});*;
}
};
}
gen_extract!(u8, u16, u32);
/// Parameter extract incremental token-tree muncher
///
/// What else would it mean?
macro_rules! param_extract_itm {
($self:expr, $($id:ident: $ty:ident)? $(, $($tt:tt)*)?) => {
// Extract pattern
$(extract!($self, $ty, $id);)?
$(
// Require operand separator
extract_pat!($self, Token::PSep);
// And go to the next (recursive)
// …munch munch… yummy token trees.
param_extract_itm!($self, $($tt)*);
)?
};
}
pub(crate) use {extract, extract_pat, gen_text, param_extract_itm};

View file

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

View file

@ -1,6 +0,0 @@
[package]
name = "hbbytecode"
version = "0.1.0"
edition = "2021"
[dependencies]

View file

@ -1,170 +0,0 @@
//! Generate HoleyBytes code validator
macro_rules! gen_valider {
(
$(
$ityn:ident
($($param_i:ident: $param_ty:ident),* $(,)?)
=> [$($opcode:ident),* $(,)?],
)*
) => {
#[allow(unreachable_code)]
pub mod valider {
//! Validate if program is sound to execute
/// Program validation error kind
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ErrorKind {
/// Unknown opcode
InvalidInstruction,
/// VM doesn't implement this valid opcode
Unimplemented,
/// Attempted to copy over register boundary
RegisterArrayOverflow,
/// Program is not validly terminated
InvalidEnd,
/// Program misses magic
MissingMagic
}
/// Error
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Error {
/// Kind
pub kind: ErrorKind,
/// Location in bytecode
pub index: usize,
}
/// Perform bytecode validation. If it passes, the program should be
/// sound to execute.
pub fn validate(mut program: &[u8]) -> Result<(), Error> {
// Validate magic
if program.get(0..4) != Some(&0xAB1E0B_u32.to_le_bytes()) {
return Err(Error {
kind: ErrorKind::MissingMagic,
index: 0,
});
}
// Program has to end with 12 zeroes, if there is less than
// 12 bytes, program is invalid.
if program.len() < 12 {
return Err(Error {
kind: ErrorKind::InvalidEnd,
index: 0,
});
}
// Verify that program ends with 12 zeroes
for (index, item) in program.iter().enumerate().skip(program.len() - 12) {
if *item != 0 {
return Err(Error {
kind: ErrorKind::InvalidEnd,
index,
});
}
}
let start = program;
program = &program[4..];
loop {
use crate::opcode::*;
program = match program {
// End of program
[] => return Ok(()),
// Memory load/store cannot go out-of-bounds register array
// B B D1 D2 D3 D4 D5 D6 D7 D8 H1 H2
[LD..=ST, reg, _, _, _, _, _, _, _, _, _, count_0, count_1, ..]
if usize::from(*reg) * 8
+ usize::from(u16::from_le_bytes([*count_0, *count_1]))
> 2048 =>
{
return Err(Error {
kind: ErrorKind::RegisterArrayOverflow,
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
});
}
// Block register copy cannot go out-of-bounds register array
[BRC, src, dst, count, ..]
if src.checked_add(*count).is_none()
|| dst.checked_add(*count).is_none() =>
{
return Err(Error {
kind: ErrorKind::RegisterArrayOverflow,
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
});
}
$(
$crate::gen_valider::inst_chk!(
rest, $ityn, $($opcode),*
)
)|* => rest,
// The plebs
_ => {
return Err(Error {
kind: ErrorKind::InvalidInstruction,
index: (program.as_ptr() as usize) - (start.as_ptr() as usize),
})
}
}
}
}
}
};
}
/// Generate instruction check pattern
macro_rules! inst_chk {
// Sadly this has hardcoded instruction types,
// as I cannot generate parts of patterns+
($rest:ident, bbbb, $($opcode:ident),*) => {
// B B B B
[$($opcode)|*, _, _, _, _, $rest @ ..]
};
($rest:ident, bbb, $($opcode:ident),*) => {
// B B B
[$($opcode)|*, _, _, _, $rest @ ..]
};
($rest:ident, bbdh, $($opcode:ident),*) => {
// B B D1 D2 D3 D4 D5 D6 D7 D8 H1 H2
[$($opcode)|*, _, _, _, _, _, _, _, _, _, _, _, _, $rest @ ..]
};
($rest:ident, bbd, $($opcode:ident),*) => {
// B B D1 D2 D3 D4 D5 D6 D7 D8
[$($opcode)|*, _, _, _, _, _, _, _, _, _, _, $rest @ ..]
};
($rest:ident, bbw, $($opcode:ident),*) => {
// B B W1 W2 W3 W4
[$($opcode)|*, _, _, _, _, _, _, $rest @ ..]
};
($rest:ident, bb, $($opcode:ident),*) => {
// B B
[$($opcode)|*, _, _, $rest @ ..]
};
($rest:ident, bd, $($opcode:ident),*) => {
// B D1 D2 D3 D4 D5 D6 D7 D8
[$($opcode)|*, _, _, _, _, _, _, _, _, _, $rest @ ..]
};
($rest:ident, n, $($opcode:ident),*) => {
[$($opcode)|*, $rest @ ..]
};
($_0:ident, $($_1:ident),*) => {
compile_error!("Invalid instruction type");
}
}
pub(crate) use {gen_valider, inst_chk};

View file

@ -1,162 +0,0 @@
#![no_std]
mod gen_valider;
macro_rules! constmod {
($vis:vis $mname:ident($repr:ty) {
$(#![doc = $mdoc:literal])?
$($cname:ident = $val:expr $(,$doc:literal)?;)*
}) => {
$(#[doc = $mdoc])?
$vis mod $mname {
$(
$(#[doc = $doc])?
pub const $cname: $repr = $val;
)*
}
};
}
#[allow(rustdoc::invalid_rust_codeblocks)]
/// Invoke macro with bytecode definition
/// # Input syntax
/// ```no_run
/// macro!(
/// INSTRUCTION_TYPE(p0: TYPE, p1: TYPE, …)
/// => [INSTRUCTION_A, INSTRUCTION_B, …],
/// …
/// );
/// ```
/// - Instruction type determines opcode-generic, instruction-type-specific
/// function. Name: `i_param_INSTRUCTION_TYPE`
/// - Per-instructions there will be generated opcode-specific functions calling the generic ones
/// - Operand types
/// - R: Register (u8)
/// - I: Immediate
/// - L: Memory load / store size (u16)
/// - Other types are identity-mapped
///
/// # BRC special-case
/// BRC's 3rd operand is plain byte, not a register. Encoding is the same, but for some cases it may matter.
///
/// Please, if you distinguish in your API between byte and register, special case this one.
///
/// Sorry for that :(
#[macro_export]
macro_rules! invoke_with_def {
($macro:path) => {
$macro!(
bbbb(p0: R, p1: R, p2: R, p3: R)
=> [DIR, DIRF, FMAF],
bbb(p0: R, p1: R, p2: R)
=> [ADD, SUB, MUL, AND, OR, XOR, SL, SR, SRS, CMP, CMPU, BRC, ADDF, SUBF, MULF],
bbdh(p0: R, p1: R, p2: I, p3: L)
=> [LD, ST],
bbd(p0: R, p1: R, p2: I)
=> [ADDI, MULI, ANDI, ORI, XORI, CMPI, CMPUI, BMC, JAL, JEQ, JNE, JLT, JGT, JLTU,
JGTU, ADDFI, MULFI],
bbw(p0: R, p1: R, p2: u32)
=> [SLI, SRI, SRSI],
bb(p0: R, p1: R)
=> [NEG, NOT, CP, SWA, NEGF, ITF, FTI],
bd(p0: R, p1: I)
=> [LI],
n()
=> [UN, TX, NOP, ECALL],
);
};
}
invoke_with_def!(gen_valider::gen_valider);
constmod!(pub opcode(u8) {
//! Opcode constant module
UN = 0, "N; Raises a trap";
TX = 1, "N; Terminate execution";
NOP = 2, "N; Do nothing";
ADD = 3, "BBB; #0 ← #1 + #2";
SUB = 4, "BBB; #0 ← #1 - #2";
MUL = 5, "BBB; #0 ← #1 × #2";
AND = 6, "BBB; #0 ← #1 & #2";
OR = 7, "BBB; #0 ← #1 | #2";
XOR = 8, "BBB; #0 ← #1 ^ #2";
SL = 9, "BBB; #0 ← #1 « #2";
SR = 10, "BBB; #0 ← #1 » #2";
SRS = 11, "BBB; #0 ← #1 » #2 (signed)";
CMP = 12, "BBB; #0 ← #1 <=> #2";
CMPU = 13, "BBB; #0 ← #1 <=> #2 (unsigned)";
DIR = 14, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3";
NEG = 15, "BB; #0 ← -#1";
NOT = 16, "BB; #0 ← !#1";
ADDI = 17, "BBD; #0 ← #1 + imm #2";
MULI = 18, "BBD; #0 ← #1 × imm #2";
ANDI = 19, "BBD; #0 ← #1 & imm #2";
ORI = 20, "BBD; #0 ← #1 | imm #2";
XORI = 21, "BBD; #0 ← #1 ^ imm #2";
SLI = 22, "BBW; #0 ← #1 « imm #2";
SRI = 23, "BBW; #0 ← #1 » imm #2";
SRSI = 24, "BBW; #0 ← #1 » imm #2 (signed)";
CMPI = 25, "BBD; #0 ← #1 <=> imm #2";
CMPUI = 26, "BBD; #0 ← #1 <=> imm #2 (unsigned)";
CP = 27, "BB; Copy #0 ← #1";
SWA = 28, "BB; Swap #0 and #1";
LI = 29, "BD; #0 ← imm #1";
LD = 30, "BBDB; #0 ← [#1 + imm #3], imm #4 bytes, overflowing";
ST = 31, "BBDB; [#1 + imm #3] ← #0, imm #4 bytes, overflowing";
BMC = 32, "BBD; [#0] ← [#1], imm #2 bytes";
BRC = 33, "BBB; #0 ← #1, imm #2 registers";
JAL = 34, "BD; Copy PC to #0 and unconditional jump [#1 + imm #2]";
JEQ = 35, "BBD; if #0 = #1 → jump imm #2";
JNE = 36, "BBD; if #0 ≠ #1 → jump imm #2";
JLT = 37, "BBD; if #0 < #1 → jump imm #2";
JGT = 38, "BBD; if #0 > #1 → jump imm #2";
JLTU = 39, "BBD; if #0 < #1 → jump imm #2 (unsigned)";
JGTU = 40, "BBD; if #0 > #1 → jump imm #2 (unsigned)";
ECALL = 41, "N; Issue system call";
ADDF = 42, "BBB; #0 ← #1 +. #2";
SUBF = 43, "BBB; #0 ← #1 -. #2";
MULF = 44, "BBB; #0 ← #1 +. #2";
DIRF = 45, "BBBB; #0 ← #2 / #3, #1 ← #2 % #3";
FMAF = 46, "BBBB; #0 ← (#1 * #2) + #3";
NEGF = 47, "BB; #0 ← -#1";
ITF = 48, "BB; #0 ← #1 as float";
FTI = 49, "BB; #0 ← #1 as int";
ADDFI = 50, "BBD; #0 ← #1 +. imm #2";
MULFI = 51, "BBD; #0 ← #1 *. imm #2";
});
#[repr(packed)]
pub struct ParamBBBB(pub u8, pub u8, pub u8, pub u8);
#[repr(packed)]
pub struct ParamBBB(pub u8, pub u8, pub u8);
#[repr(packed)]
pub struct ParamBBDH(pub u8, pub u8, pub u64, pub u16);
#[repr(packed)]
pub struct ParamBBD(pub u8, pub u8, pub u64);
#[repr(packed)]
pub struct ParamBBW(pub u8, pub u8, pub u32);
#[repr(packed)]
pub struct ParamBB(pub u8, pub u8);
#[repr(packed)]
pub struct ParamBD(pub u8, pub u64);
/// # Safety
/// Has to be valid to be decoded from bytecode.
pub unsafe trait ProgramVal {}
unsafe impl ProgramVal for ParamBBBB {}
unsafe impl ProgramVal for ParamBBB {}
unsafe impl ProgramVal for ParamBBDH {}
unsafe impl ProgramVal for ParamBBD {}
unsafe impl ProgramVal for ParamBBW {}
unsafe impl ProgramVal for ParamBB {}
unsafe impl ProgramVal for ParamBD {}
unsafe impl ProgramVal for u64 {}
unsafe impl ProgramVal for u8 {} // Opcode
unsafe impl ProgramVal for () {}

View file

@ -1,20 +0,0 @@
[package]
name = "hbvm"
version = "0.1.0"
edition = "2021"
[profile.release]
lto = true
[features]
default = ["alloc"]
alloc = []
nightly = []
[dependencies]
delegate = "0.9"
derive_more = "0.99"
hbbytecode.path = "../hbbytecode"
paste = "1.0"
sealed = "0.5"
static_assertions = "1.0"

Binary file not shown.

View file

@ -1,82 +0,0 @@
#![no_main]
use {
hbbytecode::valider::validate,
hbvm::{
mem::softpaging::{
paging::{PageTable, Permission},
HandlePageFault, PageSize, SoftPagedMem,
},
MemoryAccessReason, Vm,
},
libfuzzer_sys::fuzz_target,
};
fuzz_target!(|data: &[u8]| {
if validate(data).is_ok() {
let mut vm = unsafe {
Vm::<_, 16384>::new(
SoftPagedMem::<_, true> {
pf_handler: TestTrapHandler,
program: data,
root_pt: Box::into_raw(Default::default()),
icache: Default::default(),
},
0,
)
};
// Alloc and map some memory
let pages = [
alloc_and_map(&mut vm.memory, 0),
alloc_and_map(&mut vm.memory, 4096),
];
// Run VM
let _ = vm.run();
// Unmap and dealloc the memory
for (i, page) in pages.into_iter().enumerate() {
unmap_and_dealloc(&mut vm.memory, page, i as u64 * 4096);
}
let _ = unsafe { Box::from_raw(vm.memory.root_pt) };
}
});
fn alloc_and_map(memory: &mut SoftPagedMem<TestTrapHandler>, at: u64) -> *mut u8 {
let ptr = Box::into_raw(Box::<Page>::default()).cast();
unsafe {
memory
.map(ptr, at, Permission::Write, PageSize::Size4K)
.unwrap()
};
ptr
}
fn unmap_and_dealloc(memory: &mut SoftPagedMem<TestTrapHandler>, ptr: *mut u8, from: u64) {
memory.unmap(from).unwrap();
let _ = unsafe { Box::from_raw(ptr.cast::<Page>()) };
}
#[repr(align(4096))]
struct Page([u8; 4096]);
impl Default for Page {
fn default() -> Self {
unsafe { std::mem::MaybeUninit::zeroed().assume_init() }
}
}
struct TestTrapHandler;
impl HandlePageFault for TestTrapHandler {
fn page_fault(
&mut self,
_: MemoryAccessReason,
_: &mut PageTable,
_: u64,
_: PageSize,
_: *mut u8,
) -> bool {
false
}
}

View file

@ -1,155 +0,0 @@
//! Block memory copier state machine
use {
super::{Memory, MemoryAccessReason, VmRunError},
core::{mem::MaybeUninit, task::Poll},
};
/// Buffer size (defaults to 4 KiB, a smallest page size on most platforms)
const BUF_SIZE: usize = 4096;
/// Buffer of possibly uninitialised bytes, aligned to [`BUF_SIZE`]
#[repr(align(4096))]
struct AlignedBuf([MaybeUninit<u8>; BUF_SIZE]);
/// State for block memory copy
pub struct BlockCopier {
/// Source address
src: u64,
/// Destination address
dst: u64,
/// How many buffer sizes to copy?
n_buffers: usize,
/// …and what remainds after?
rem: usize,
}
impl BlockCopier {
/// Construct a new one
#[inline]
pub fn new(src: u64, dst: u64, count: usize) -> Self {
Self {
src,
dst,
n_buffers: count / BUF_SIZE,
rem: count % BUF_SIZE,
}
}
/// Copy one block
///
/// # Safety
/// - Same as for [`Memory::load`] and [`Memory::store`]
pub unsafe fn poll(&mut self, memory: &mut impl Memory) -> Poll<Result<(), BlkCopyError>> {
// Safety: Assuming uninit of array of MaybeUninit is sound
let mut buf = AlignedBuf(MaybeUninit::uninit().assume_init());
// We have at least one buffer size to copy
if self.n_buffers != 0 {
if let Err(e) = act(
memory,
self.src,
self.dst,
buf.0.as_mut_ptr().cast(),
BUF_SIZE,
) {
return Poll::Ready(Err(e));
}
// Bump source and destination address
//
// If we are over the address space, bail.
match self.src.checked_add(BUF_SIZE as u64) {
Some(n) => self.src = n,
None => return Poll::Ready(Err(BlkCopyError::OutOfBounds)),
};
match self.dst.checked_add(BUF_SIZE as u64) {
Some(n) => self.dst = n,
None => return Poll::Ready(Err(BlkCopyError::OutOfBounds)),
};
self.n_buffers -= 1;
return if self.n_buffers + self.rem == 0 {
// If there is nothing left, we are done
Poll::Ready(Ok(()))
} else {
// Otherwise let's advice to run it again
Poll::Pending
};
}
if self.rem != 0 {
if let Err(e) = act(
memory,
self.src,
self.dst,
buf.0.as_mut_ptr().cast(),
self.rem,
) {
return Poll::Ready(Err(e));
}
}
Poll::Ready(Ok(()))
}
}
/// Load to buffer and store from buffer
#[inline]
unsafe fn act(
memory: &mut impl Memory,
src: u64,
dst: u64,
buf: *mut u8,
count: usize,
) -> Result<(), BlkCopyError> {
// Load to buffer
memory
.load(src, buf, count)
.map_err(|super::LoadError(addr)| BlkCopyError::Access {
access_reason: MemoryAccessReason::Load,
addr,
})?;
// Store from buffer
memory
.store(dst, buf, count)
.map_err(|super::StoreError(addr)| BlkCopyError::Access {
access_reason: MemoryAccessReason::Store,
addr,
})?;
Ok(())
}
/// Error occured when copying a block of memory
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BlkCopyError {
/// Memory access error
Access {
/// Kind of access
access_reason: MemoryAccessReason,
/// VM Address
addr: u64,
},
/// Address out of bounds
OutOfBounds,
}
impl From<BlkCopyError> for VmRunError {
fn from(value: BlkCopyError) -> Self {
match value {
BlkCopyError::Access {
access_reason: MemoryAccessReason::Load,
addr,
} => Self::LoadAccessEx(addr),
BlkCopyError::Access {
access_reason: MemoryAccessReason::Store,
addr,
} => Self::StoreAccessEx(addr),
BlkCopyError::OutOfBounds => Self::AddrOutOfBounds,
}
}
}

View file

@ -1,558 +0,0 @@
//! HoleyBytes Virtual Machine
//!
//! # Alloc feature
//! - Enabled by default
//! - Provides mapping / unmapping, as well as [`Default`] and [`Drop`]
//! implementations for soft-paged memory implementation
// # General safety notice:
// - Validation has to assure there is 256 registers (r0 - r255)
// - Instructions have to be valid as specified (values and sizes)
// - Mapped pages should be at least 4 KiB
#![no_std]
#![cfg_attr(feature = "nightly", feature(fn_align))]
#![warn(missing_docs, clippy::missing_docs_in_private_items)]
#[cfg(feature = "alloc")]
extern crate alloc;
pub mod mem;
pub mod value;
mod bmc;
use {
bmc::BlockCopier,
core::{cmp::Ordering, mem::size_of, ops},
derive_more::Display,
hbbytecode::{
ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, ProgramVal,
},
value::{Value, ValueVariant},
};
/// HoleyBytes Virtual Machine
pub struct Vm<Mem, const TIMER_QUOTIENT: usize> {
/// Holds 256 registers
///
/// Writing to register 0 is considered undefined behaviour
/// in terms of HoleyBytes program execution
pub registers: [Value; 256],
/// Memory implementation
pub memory: Mem,
/// Program counter
pub pc: usize,
/// Program timer
timer: usize,
/// Saved block copier
copier: Option<BlockCopier>,
}
impl<Mem, const TIMER_QUOTIENT: usize> Vm<Mem, TIMER_QUOTIENT>
where
Mem: Memory,
{
/// Create a new VM with program and trap handler
///
/// # Safety
/// Program code has to be validated
pub unsafe fn new(memory: Mem, entry: u64) -> Self {
Self {
registers: [Value::from(0_u64); 256],
memory,
pc: entry as _,
timer: 0,
copier: None,
}
}
/// Execute program
///
/// Program can return [`VmRunError`] if a trap handling failed
#[cfg_attr(feature = "nightly", repr(align(4096)))]
pub fn run(&mut self) -> Result<VmRunOk, VmRunError> {
use hbbytecode::opcode::*;
loop {
// Big match
//
// Contribution guide:
// - Zero register shall never be overwitten. It's value has to always be 0.
// - Prefer `Self::read_reg` and `Self::write_reg` functions
// - Extract parameters using `param!` macro
// - Prioritise speed over code size
// - Memory is cheap, CPUs not that much
// - Do not heap allocate at any cost
// - Yes, user-provided trap handler may allocate,
// but that is not our »fault«.
// - Unsafe is kinda must, but be sure you have validated everything
// - Your contributions have to pass sanitizers and Miri
// - Strictly follow the spec
// - The spec does not specify how you perform actions, in what order,
// just that the observable effects have to be performed in order and
// correctly.
// - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU
// sorry 8 bit fans, HBVM won't run on your Speccy :(
unsafe {
match self
.memory
.prog_read::<u8>(self.pc as _)
.ok_or(VmRunError::ProgramFetchLoadEx(self.pc as _))?
{
UN => {
self.decode::<()>();
return Err(VmRunError::Unreachable);
}
TX => {
self.decode::<()>();
return Ok(VmRunOk::End);
}
NOP => self.decode::<()>(),
ADD => self.binary_op(u64::wrapping_add),
SUB => self.binary_op(u64::wrapping_sub),
MUL => self.binary_op(u64::wrapping_mul),
AND => self.binary_op::<u64>(ops::BitAnd::bitand),
OR => self.binary_op::<u64>(ops::BitOr::bitor),
XOR => self.binary_op::<u64>(ops::BitXor::bitxor),
SL => self.binary_op(|l, r| u64::wrapping_shl(l, r as u32)),
SR => self.binary_op(|l, r| u64::wrapping_shr(l, r as u32)),
SRS => self.binary_op(|l, r| i64::wrapping_shl(l, r as u32)),
CMP => {
// Compare a0 <=> a1
// < → -1
// > → 1
// = → 0
let ParamBBB(tg, a0, a1) = self.decode();
self.write_reg(
tg,
self.read_reg(a0)
.cast::<i64>()
.cmp(&self.read_reg(a1).cast::<i64>())
as i64,
);
}
CMPU => {
// Unsigned comparsion
let ParamBBB(tg, a0, a1) = self.decode();
self.write_reg(
tg,
self.read_reg(a0)
.cast::<u64>()
.cmp(&self.read_reg(a1).cast::<u64>())
as i64,
);
}
NOT => {
// Logical negation
let ParamBB(tg, a0) = self.decode();
self.write_reg(tg, !self.read_reg(a0).cast::<u64>());
}
NEG => {
// Bitwise negation
let ParamBB(tg, a0) = self.decode();
self.write_reg(
tg,
match self.read_reg(a0).cast::<u64>() {
0 => 1_u64,
_ => 0,
},
);
}
DIR => {
// Fused Division-Remainder
let ParamBBBB(dt, rt, a0, a1) = self.decode();
let a0 = self.read_reg(a0).cast::<u64>();
let a1 = self.read_reg(a1).cast::<u64>();
self.write_reg(dt, a0.checked_div(a1).unwrap_or(u64::MAX));
self.write_reg(rt, a0.checked_rem(a1).unwrap_or(u64::MAX));
}
ADDI => self.binary_op_imm(u64::wrapping_add),
MULI => self.binary_op_imm(u64::wrapping_sub),
ANDI => self.binary_op_imm::<u64>(ops::BitAnd::bitand),
ORI => self.binary_op_imm::<u64>(ops::BitOr::bitor),
XORI => self.binary_op_imm::<u64>(ops::BitXor::bitxor),
SLI => self.binary_op_ims(u64::wrapping_shl),
SRI => self.binary_op_ims(u64::wrapping_shr),
SRSI => self.binary_op_ims(i64::wrapping_shr),
CMPI => {
let ParamBBD(tg, a0, imm) = self.decode();
self.write_reg(
tg,
self.read_reg(a0)
.cast::<i64>()
.cmp(&Value::from(imm).cast::<i64>())
as i64,
);
}
CMPUI => {
let ParamBBD(tg, a0, imm) = self.decode();
self.write_reg(tg, self.read_reg(a0).cast::<u64>().cmp(&imm) as i64);
}
CP => {
let ParamBB(tg, a0) = self.decode();
self.write_reg(tg, self.read_reg(a0));
}
SWA => {
// Swap registers
let ParamBB(r0, r1) = self.decode();
match (r0, r1) {
(0, 0) => (),
(dst, 0) | (0, dst) => self.write_reg(dst, 0_u64),
(r0, r1) => {
core::ptr::swap(
self.registers.get_unchecked_mut(usize::from(r0)),
self.registers.get_unchecked_mut(usize::from(r1)),
);
}
}
}
LI => {
let ParamBD(tg, imm) = self.decode();
self.write_reg(tg, imm);
}
LD => {
// Load. If loading more than register size, continue on adjecent registers
let ParamBBDH(dst, base, off, count) = self.decode();
let n: u8 = match dst {
0 => 1,
_ => 0,
};
self.memory.load(
self.ldst_addr_uber(dst, base, off, count, n)?,
self.registers
.as_mut_ptr()
.add(usize::from(dst) + usize::from(n))
.cast(),
usize::from(count).saturating_sub(n.into()),
)?;
}
ST => {
// Store. Same rules apply as to LD
let ParamBBDH(dst, base, off, count) = self.decode();
self.memory.store(
self.ldst_addr_uber(dst, base, off, count, 0)?,
self.registers.as_ptr().add(usize::from(dst)).cast(),
count.into(),
)?;
}
BMC => {
// Block memory copy
match if let Some(copier) = &mut self.copier {
// There is some copier, poll.
copier.poll(&mut self.memory)
} else {
// There is none, make one!
let ParamBBD(src, dst, count) = self.decode();
// So we are still on BMC on next cycle
self.pc -= size_of::<ParamBBD>() + 1;
self.copier = Some(BlockCopier::new(
self.read_reg(src).cast(),
self.read_reg(dst).cast(),
count as _,
));
self.copier
.as_mut()
.unwrap_unchecked() // SAFETY: We just assigned there
.poll(&mut self.memory)
} {
// We are done, shift program counter
core::task::Poll::Ready(Ok(())) => {
self.copier = None;
self.pc += size_of::<ParamBBD>() + 1;
}
// Error, shift program counter (for consistency)
// and yield error
core::task::Poll::Ready(Err(e)) => {
self.pc += size_of::<ParamBBD>() + 1;
return Err(e.into());
}
// Not done yet, proceed to next cycle
core::task::Poll::Pending => (),
}
}
BRC => {
// Block register copy
let ParamBBB(src, dst, count) = self.decode();
if src.checked_add(count).is_none() || dst.checked_add(count).is_none() {
return Err(VmRunError::RegOutOfBounds);
}
core::ptr::copy(
self.registers.get_unchecked(usize::from(src)),
self.registers.get_unchecked_mut(usize::from(dst)),
usize::from(count),
);
}
JAL => {
// Jump and link. Save PC after this instruction to
// specified register and jump to reg + offset.
let ParamBBD(save, reg, offset) = self.decode();
self.write_reg(save, self.pc as u64);
self.pc =
(self.read_reg(reg).cast::<u64>().saturating_add(offset)) as usize;
}
// Conditional jumps, jump only to immediates
JEQ => self.cond_jmp::<u64>(Ordering::Equal),
JNE => {
let ParamBBD(a0, a1, jt) = self.decode();
if self.read_reg(a0).cast::<u64>() != self.read_reg(a1).cast::<u64>() {
self.pc = jt as usize;
}
}
JLT => self.cond_jmp::<u64>(Ordering::Less),
JGT => self.cond_jmp::<u64>(Ordering::Greater),
JLTU => self.cond_jmp::<i64>(Ordering::Less),
JGTU => self.cond_jmp::<i64>(Ordering::Greater),
ECALL => {
self.decode::<()>();
// So we don't get timer interrupt after ECALL
if TIMER_QUOTIENT != 0 {
self.timer = self.timer.wrapping_add(1);
}
return Ok(VmRunOk::Ecall);
}
ADDF => self.binary_op::<f64>(ops::Add::add),
SUBF => self.binary_op::<f64>(ops::Sub::sub),
MULF => self.binary_op::<f64>(ops::Mul::mul),
DIRF => {
let ParamBBBB(dt, rt, a0, a1) = self.decode();
let a0 = self.read_reg(a0).cast::<f64>();
let a1 = self.read_reg(a1).cast::<f64>();
self.write_reg(dt, a0 / a1);
self.write_reg(rt, a0 % a1);
}
FMAF => {
let ParamBBBB(dt, a0, a1, a2) = self.decode();
self.write_reg(
dt,
self.read_reg(a0).cast::<f64>() * self.read_reg(a1).cast::<f64>()
+ self.read_reg(a2).cast::<f64>(),
);
}
NEGF => {
let ParamBB(dt, a0) = self.decode();
self.write_reg(dt, -self.read_reg(a0).cast::<f64>());
}
ITF => {
let ParamBB(dt, a0) = self.decode();
self.write_reg(dt, self.read_reg(a0).cast::<i64>() as f64);
}
FTI => {
let ParamBB(dt, a0) = self.decode();
self.write_reg(dt, self.read_reg(a0).cast::<f64>() as i64);
}
ADDFI => self.binary_op_imm::<f64>(ops::Add::add),
MULFI => self.binary_op_imm::<f64>(ops::Mul::mul),
op => return Err(VmRunError::InvalidOpcode(op)),
}
}
if TIMER_QUOTIENT != 0 {
self.timer = self.timer.wrapping_add(1);
if self.timer % TIMER_QUOTIENT == 0 {
return Ok(VmRunOk::Timer);
}
}
}
}
/// Decode instruction operands
#[inline(always)]
unsafe fn decode<T: ProgramVal>(&mut self) -> T {
let pc1 = self.pc + 1;
let data = self.memory.prog_read_unchecked::<T>(pc1 as _);
self.pc += 1 + size_of::<T>();
data
}
/// Perform binary operating over two registers
#[inline(always)]
unsafe fn binary_op<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) {
let ParamBBB(tg, a0, a1) = self.decode();
self.write_reg(
tg,
op(self.read_reg(a0).cast::<T>(), self.read_reg(a1).cast::<T>()),
);
}
/// Perform binary operation over register and immediate
#[inline(always)]
unsafe fn binary_op_imm<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) {
let ParamBBD(tg, reg, imm) = self.decode();
self.write_reg(
tg,
op(self.read_reg(reg).cast::<T>(), Value::from(imm).cast::<T>()),
);
}
/// Perform binary operation over register and shift immediate
#[inline(always)]
unsafe fn binary_op_ims<T: ValueVariant>(&mut self, op: impl Fn(T, u32) -> T) {
let ParamBBW(tg, reg, imm) = self.decode();
self.write_reg(tg, op(self.read_reg(reg).cast::<T>(), imm));
}
/// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected
#[inline(always)]
unsafe fn cond_jmp<T: ValueVariant + Ord>(&mut self, expected: Ordering) {
let ParamBBD(a0, a1, ja) = self.decode();
if self
.read_reg(a0)
.cast::<T>()
.cmp(&self.read_reg(a1).cast::<T>())
== expected
{
self.pc = ja as usize;
}
}
/// Read register
#[inline(always)]
unsafe fn read_reg(&self, n: u8) -> Value {
*self.registers.get_unchecked(n as usize)
}
/// Write a register.
/// Writing to register 0 is no-op.
#[inline(always)]
unsafe fn write_reg(&mut self, n: u8, value: impl Into<Value>) {
if n != 0 {
*self.registers.get_unchecked_mut(n as usize) = value.into();
}
}
/// Load / Store Address check-computation überfunction
#[inline(always)]
unsafe fn ldst_addr_uber(
&self,
dst: u8,
base: u8,
offset: u64,
size: u16,
adder: u8,
) -> Result<u64, VmRunError> {
let reg = dst.checked_add(adder).ok_or(VmRunError::RegOutOfBounds)?;
if usize::from(reg) * 8 + usize::from(size) > 2048 {
Err(VmRunError::RegOutOfBounds)
} else {
self.read_reg(base)
.cast::<u64>()
.checked_add(offset)
.and_then(|x| x.checked_add(adder.into()))
.ok_or(VmRunError::AddrOutOfBounds)
}
}
}
/// Virtual machine halt error
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(u8)]
pub enum VmRunError {
/// Tried to execute invalid instruction
InvalidOpcode(u8),
/// Unhandled load access exception
LoadAccessEx(u64),
/// Unhandled instruction load access exception
ProgramFetchLoadEx(u64),
/// Unhandled store access exception
StoreAccessEx(u64),
/// Register out-of-bounds access
RegOutOfBounds,
/// Address out-of-bounds
AddrOutOfBounds,
/// Reached unreachable code
Unreachable,
}
/// Virtual machine halt ok
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum VmRunOk {
/// Program has eached its end
End,
/// Program was interrupted by a timer
Timer,
/// Environment call
Ecall,
}
/// Load-store memory access
pub trait Memory {
/// Load data from memory on address
///
/// # Safety
/// - Shall not overrun the buffer
unsafe fn load(&mut self, addr: u64, target: *mut u8, count: usize) -> Result<(), LoadError>;
/// Store data to memory on address
///
/// # Safety
/// - Shall not overrun the buffer
unsafe fn store(
&mut self,
addr: u64,
source: *const u8,
count: usize,
) -> Result<(), StoreError>;
/// Read from program memory to execute
///
/// # Safety
/// - Data read have to be valid
unsafe fn prog_read<T: ProgramVal>(&mut self, addr: u64) -> Option<T>;
/// Read from program memory to exectue
///
/// # Safety
/// - You have to be really sure that these bytes are there, understand?
unsafe fn prog_read_unchecked<T: ProgramVal>(&mut self, addr: u64) -> T;
}
/// Unhandled load access trap
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)]
#[display(fmt = "Load access error at address {_0:#x}")]
pub struct LoadError(pub u64);
/// Unhandled store access trap
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)]
#[display(fmt = "Store access error at address {_0:#x}")]
pub struct StoreError(pub u64);
/// Reason to access memory
#[derive(Clone, Copy, Display, Debug, PartialEq, Eq)]
pub enum MemoryAccessReason {
/// Memory was accessed for load (read)
Load,
/// Memory was accessed for store (write)
Store,
}
impl From<LoadError> for VmRunError {
fn from(value: LoadError) -> Self {
Self::LoadAccessEx(value.0)
}
}
impl From<StoreError> for VmRunError {
fn from(value: StoreError) -> Self {
Self::StoreAccessEx(value.0)
}
}

View file

@ -1,78 +0,0 @@
use {
hbbytecode::valider::validate,
hbvm::{
mem::softpaging::{paging::PageTable, HandlePageFault, PageSize, SoftPagedMem},
MemoryAccessReason, Vm,
},
std::io::{stdin, Read},
};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut prog = vec![];
stdin().read_to_end(&mut prog)?;
if let Err(e) = validate(&prog) {
eprintln!("Program validation error: {e:?}");
return Ok(());
} else {
unsafe {
let mut vm = Vm::<_, 0>::new(
SoftPagedMem::<_, true> {
pf_handler: TestTrapHandler,
program: &prog,
root_pt: Box::into_raw(Default::default()),
icache: Default::default(),
},
4,
);
let data = {
let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked(
4096, 4096,
));
if ptr.is_null() {
panic!("Alloc error tbhl");
}
ptr
};
vm.memory
.map(
data,
8192,
hbvm::mem::softpaging::paging::Permission::Write,
PageSize::Size4K,
)
.unwrap();
println!("Program interrupt: {:?}", vm.run());
println!("{:?}", vm.registers);
std::alloc::dealloc(
data,
std::alloc::Layout::from_size_align_unchecked(4096, 4096),
);
vm.memory.unmap(8192).unwrap();
let _ = Box::from_raw(vm.memory.root_pt);
}
}
Ok(())
}
pub fn time() -> u32 {
9
}
#[derive(Default)]
struct TestTrapHandler;
impl HandlePageFault for TestTrapHandler {
fn page_fault(
&mut self,
_: MemoryAccessReason,
_: &mut PageTable,
_: u64,
_: PageSize,
_: *mut u8,
) -> bool {
false
}
}

View file

@ -1,3 +0,0 @@
//! Memory implementations
pub mod softpaging;

View file

@ -1,79 +0,0 @@
//! HoleyBytes register value definition
use sealed::sealed;
/// Define [`Value`] union
///
/// # Safety
/// Union variants have to be sound to byte-reinterpretate
/// between each other. Otherwise the behaviour is undefined.
macro_rules! value_def {
($($ty:ident),* $(,)?) => {
/// HBVM register value
#[derive(Copy, Clone)]
#[repr(packed)]
pub union Value {
$(
#[doc = concat!(stringify!($ty), " type")]
pub $ty: $ty
),*
}
$(
impl From<$ty> for Value {
#[inline]
fn from(value: $ty) -> Self {
Self { $ty: value }
}
}
static_assertions::const_assert_eq!(
core::mem::size_of::<$ty>(),
core::mem::size_of::<Value>(),
);
#[sealed]
unsafe impl ValueVariant for $ty {}
)*
};
}
impl Value {
/// Byte reinterpret value to target variant
#[inline]
pub fn cast<V: ValueVariant>(self) -> V {
/// Evil.
///
/// Transmute cannot be performed with generic type
/// as size is unknown, so union is used.
///
/// # Safety
/// If [`ValueVariant`] implemented correctly, it's fine :)
///
/// :ferrisClueless:
union Transmute<Variant: ValueVariant> {
/// Self
src: Value,
/// Target variant
variant: Variant,
}
unsafe { Transmute { src: self }.variant }
}
}
/// # Safety
/// - N/A, not to be implemented manually
#[sealed]
pub unsafe trait ValueVariant: Copy + Into<Value> {}
value_def!(u64, i64, f64);
static_assertions::const_assert_eq!(core::mem::size_of::<Value>(), 8);
impl core::fmt::Debug for Value {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
// Print formatted as hexadecimal, unsigned integer
write!(f, "{:x}", self.cast::<u64>())
}
}

23
lang/Cargo.toml Normal file
View file

@ -0,0 +1,23 @@
[package]
name = "hblang"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "hbc"
path = "src/main.rs"
[[bin]]
name = "fuzz"
path = "src/fuzz_main.rs"
[dependencies]
hbbytecode = { workspace = true, features = ["disasm"] }
hbvm = { workspace = true, features = ["nightly", "alloc"] }
hashbrown = { version = "0.15.0", default-features = false, features = ["raw-entry"] }
log = "0.4.22"
[features]
default = ["std"]
std = []
no_log = ["log/max_level_off"]

2096
lang/README.md Normal file

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

5
lang/command-help.txt Normal file
View file

@ -0,0 +1,5 @@
--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]
--path-resolver <name> - choose between builtin path resolvers, options are: ableos

902
lang/src/backend/hbvm.rs Normal file
View file

@ -0,0 +1,902 @@
use {
super::{AssemblySpec, Backend},
crate::{
lexer::TokenKind,
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::{assert_matches::debug_assert_matches, mem, ops::Range},
hbbytecode::{self as instrs, *},
reg::Reg,
};
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,
// TODO: change to indices into common vec
relocs: Vec<TypedReloc>,
code: Vec<u8>,
}
impl Default for FuncDt {
fn default() -> Self {
Self { offset: u32::MAX, relocs: Default::default(), code: Default::default() }
}
}
struct GlobalDt {
offset: Offset,
}
impl Default for GlobalDt {
fn default() -> Self {
Self { offset: u32::MAX }
}
}
#[derive(Default)]
struct Assembler {
frontier: Vec<ty::Id>,
globals: Vec<ty::Global>,
funcs: Vec<ty::Func>,
}
#[derive(Default)]
pub struct HbvmBackend {
funcs: EntVec<ty::Func, FuncDt>,
globals: EntVec<ty::Global, GlobalDt>,
asm: Assembler,
ralloc: regalloc::Res,
ret_relocs: Vec<Reloc>,
relocs: Vec<TypedReloc>,
jump_relocs: Vec<(Nid, Reloc)>,
code: Vec<u8>,
offsets: Vec<Offset>,
}
impl HbvmBackend {
fn emit(&mut self, instr: (usize, [u8; instrs::MAX_SIZE])) {
emit(&mut self.code, instr);
}
}
impl Backend for HbvmBackend {
fn assemble_bin(&mut self, entry: ty::Func, types: &Types, 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);
let exe = AbleOsExecutableHeader {
magic_number: [0x15, 0x91, 0xD2],
executable_version: 0,
code_length,
data_length,
debug_length: 0,
config_length: 0,
metadata_length: 0,
};
Reloc::new(HEADER_SIZE, 3, 4).apply_jump(to, entry, 0);
unsafe { *to.as_mut_ptr().cast::<AbleOsExecutableHeader>() = exe }
}
fn assemble_reachable(
&mut self,
from: ty::Func,
types: &Types,
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.shadow(types.ins.globals.len());
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];
debug_assert!(!fuc.code.is_empty());
if fuc.offset != u32::MAX {
continue;
}
fuc.offset = 0;
self.asm.funcs.push(func);
self.asm.frontier.extend(fuc.relocs.iter().map(|r| r.target));
}
ty::Kind::Global(glob) => {
let glb = &mut self.globals[glob];
if glb.offset != u32::MAX {
continue;
}
glb.offset = 0;
self.asm.globals.push(glob);
}
_ => unreachable!(),
}
}
let init_len = to.len();
for &func in &self.asm.funcs {
let fuc = &mut self.funcs[func];
fuc.offset = to.len() as _;
debug_assert!(!fuc.code.is_empty());
to.extend(&fuc.code);
}
let code_length = to.len() - init_len;
for global in self.asm.globals.drain(..) {
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];
for rel in &fuc.relocs {
let offset = match rel.target.expand() {
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);
}
}
AssemblySpec {
code_length: code_length as _,
data_length: data_length as _,
entry: self.funcs[from].offset,
}
}
fn disasm<'a>(
&'a self,
mut sluce: &[u8],
eca_handler: &mut dyn FnMut(&mut &[u8]),
types: &'a Types,
files: &'a EntSlice<Module, parser::Ast>,
output: &mut String,
) -> Result<(), hbbytecode::DisasmError<'a>> {
use hbbytecode::DisasmItem;
let functions = types
.ins
.funcs
.iter()
.zip(self.funcs.iter())
.filter(|(_, f)| f.offset != u32::MAX)
.map(|(f, fd)| {
let name = if f.file != Module::default() {
let file = &files[f.file];
file.ident_str(f.name)
} else {
"target_fn"
};
(fd.offset, (name, fd.code.len() as u32, DisasmItem::Func))
})
.chain(
types
.ins
.globals
.iter()
.zip(self.globals.iter())
.filter(|(_, g)| g.offset != u32::MAX)
.map(|(g, gd)| {
let name = if g.file == Module::default() {
core::str::from_utf8(&g.data).unwrap_or("invalid utf-8")
} else {
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)
}
fn emit_ct_body(
&mut self,
id: ty::Func,
nodes: &Nodes,
tys: &Types,
files: &EntSlice<Module, parser::Ast>,
) {
self.emit_body(id, nodes, tys, files);
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: &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.len(), Offset::MAX);
let mut stack_size = 0;
'_compute_stack: {
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::Join
);
continue;
}
stack_size += tys.size_of(nodes[stck].ty);
self.offsets[stck as usize] = stack_size;
}
for &stck in mems.iter() {
if !matches!(nodes[stck].kind, Kind::Stck | Kind::Arg) {
continue;
}
self.offsets[stck as usize] = stack_size - self.offsets[stck as usize];
}
}
let (saved, tail) = self.emit_body_code(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()
.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();
}
for (nd, rel) in self.jump_relocs.drain(..) {
let offset = self.offsets[nd as usize];
rel.apply_jump(&mut self.code, offset, 0);
}
let end = self.code.len();
for ret_rel in self.ret_relocs.drain(..) {
ret_rel.apply_jump(&mut self.code, end as _, 0);
}
let mut stripped_prelude_size = 0;
'_close_function: {
let pushed = (saved as i64 + !tail as i64) * 8;
let stack = stack_size as i64;
let add_len = instrs::addi64(0, 0, 0).0;
let st_len = instrs::st(0, 0, 0, 0).0;
match (pushed, stack) {
(0, 0) => {
stripped_prelude_size = add_len + st_len;
self.code.drain(0..stripped_prelude_size);
break '_close_function;
}
(0, stack) => {
write_reloc(&mut self.code, 3, -stack, 8);
stripped_prelude_size = st_len;
let end = add_len + st_len;
self.code.drain(add_len..end);
self.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, stack as _));
break '_close_function;
}
_ => {}
}
write_reloc(&mut self.code, 3, -(pushed + stack), 8);
write_reloc(&mut self.code, 3 + 8 + 3, stack, 8);
write_reloc(&mut self.code, 3 + 8 + 3 + 8, pushed, 2);
self.emit(instrs::ld(
reg::RET_ADDR + tail as u8,
reg::STACK_PTR,
stack as _,
pushed as _,
));
self.emit(instrs::addi64(reg::STACK_PTR, reg::STACK_PTR, (pushed + stack) as _));
}
self.relocs.iter_mut().for_each(|r| r.reloc.offset -= stripped_prelude_size as u32);
if sig.ret != ty::Id::NEVER {
self.emit(instrs::jala(reg::ZERO, reg::RET_ADDR, 0));
}
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);
debug_assert_eq!(self.jump_relocs.len(), 0);
debug_assert_eq!(self.code.len(), 0);
}
}
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 { 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.is_locked(node.inputs[1]) && !self[node.inputs[1]].ty.is_float())
|| (self.is_const(node.inputs[2])
&& node.outputs.iter().all(|&n| self.uses_direct_offset_of(n, nid, tys)))
}
Kind::BinOp { op } => {
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.uses_direct_offset_of(n, nid, tys)
|| (matches!(self[n].kind, Kind::BinOp { op: TokenKind::Add })
&& self.is_never_used(n, tys))
}),
Kind::Load { .. } => node.ty.loc(tys) == Loc::Stack,
_ => false,
}
}
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 {
Self::Le | Self::Gt => 1,
Self::Ne | Self::Eq => 0,
Self::Ge | Self::Lt => (-1i64) as _,
_ => return None,
})
}
fn float_cmp(self, ty: ty::Id) -> Option<fn(u8, u8, u8) -> EncodedInstr> {
if !ty.is_float() {
return None;
}
let size = ty.simple_size().unwrap();
let ops = match self {
Self::Gt => [instrs::fcmpgt32, instrs::fcmpgt64],
Self::Lt => [instrs::fcmplt32, instrs::fcmplt64],
_ => return None,
};
Some(ops[size.ilog2() as usize - 2])
}
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,
Self::Lt => instrs::jltu,
Self::Ge if signed => instrs::jlts,
Self::Ge => instrs::jltu,
Self::Gt if signed => instrs::jgts,
Self::Gt => instrs::jgtu,
_ => return None,
},
matches!(self, Self::Lt | Self::Gt),
))
}
fn binop(self, ty: ty::Id) -> Option<fn(u8, u8, u8) -> EncodedInstr> {
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)),*]}; }
let signed = ty.is_signed();
let ops = match self {
Self::Add => [add8, add16, add32, add64],
Self::Sub => [sub8, sub16, sub32, sub64],
Self::Mul => [mul8, mul16, mul32, mul64],
Self::Div if signed => div!(dirs8, dirs16, dirs32, dirs64),
Self::Div => div!(diru8, diru16, diru32, diru64),
Self::Mod if signed => rem!(dirs8, dirs16, dirs32, dirs64),
Self::Mod => rem!(diru8, diru16, diru32, diru64),
Self::Band => return Some(and),
Self::Bor => return Some(or),
Self::Xor => return Some(xor),
Self::Shl => [slu8, slu16, slu32, slu64],
Self::Shr if signed => [srs8, srs16, srs32, srs64],
Self::Shr => [sru8, sru16, sru32, sru64],
_ => return None,
};
Some(ops[size.ilog2() as usize])
} else {
debug_assert!(ty.is_float(), "{self} {ty:?}");
let ops = match self {
Self::Add => [fadd32, fadd64],
Self::Sub => [fsub32, fsub64],
Self::Mul => [fmul32, fmul64],
Self::Div => [fdiv32, fdiv64],
_ => return None,
};
Some(ops[size.ilog2() as usize - 2])
}
}
fn imm_binop(self, ty: ty::Id) -> Option<fn(u8, u8, u64) -> EncodedInstr> {
macro_rules! def_op {
($name:ident |$a:ident, $b:ident, $c:ident| $($tt:tt)*) => {
macro_rules! $name {
($$($$op:ident),*) => {
[$$(
|$a, $b, $c: u64| $$op($($tt)*),
)*]
}
}
};
}
if ty.is_float() {
return None;
}
def_op!(basic_op | a, b, c | a, b, c as _);
def_op!(sub_op | a, b, c | a, b, c.wrapping_neg() as _);
let signed = ty.is_signed();
let ops = match self {
Self::Add => basic_op!(addi8, addi16, addi32, addi64),
Self::Sub => sub_op!(addi8, addi16, addi32, addi64),
Self::Mul => basic_op!(muli8, muli16, muli32, muli64),
Self::Band => return Some(andi),
Self::Bor => return Some(ori),
Self::Xor => return Some(xori),
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,
};
let size = ty.simple_size().unwrap();
Some(ops[size.ilog2() as usize])
}
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 => [
|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_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 - 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 - 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]);
}
fn binary_prelude(to: &mut Vec<u8>) {
emit(to, instrs::jal(reg::RET_ADDR, reg::ZERO, 0));
emit(to, instrs::tx());
}
#[derive(Default)]
pub struct LoggedMem {
pub mem: hbvm::mem::HostMemory,
logger: hbvm::mem::InstrLogger,
}
impl hbvm::mem::Memory for LoggedMem {
unsafe fn load(
&mut self,
addr: hbvm::mem::Address,
target: *mut u8,
count: usize,
) -> Result<(), hbvm::mem::LoadError> {
log::trace!(
"load: {:x} {}",
addr.get(),
AsHex(core::slice::from_raw_parts(addr.get() as *const u8, count))
);
self.mem.load(addr, target, count)
}
unsafe fn store(
&mut self,
addr: hbvm::mem::Address,
source: *const u8,
count: usize,
) -> Result<(), hbvm::mem::StoreError> {
log::trace!(
"store: {:x} {}",
addr.get(),
AsHex(core::slice::from_raw_parts(source, count))
);
self.mem.store(addr, source, count)
}
unsafe fn prog_read<T: Copy + 'static>(&mut self, addr: hbvm::mem::Address) -> T {
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]);
impl core::fmt::Display for AsHex<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
for &b in self.0 {
write!(f, "{b:02x}")?;
}
Ok(())
}
}
const VM_STACK_SIZE: usize = 1024 * 64;
pub struct Comptime {
pub vm: hbvm::Vm<LoggedMem, { 1024 * 10 }>,
stack: Box<[u8; VM_STACK_SIZE]>,
pub code: Vec<u8>,
depth: usize,
}
impl Comptime {
pub fn run(&mut self, ret_loc: &mut [u8], offset: u32) -> u64 {
self.vm.write_reg(reg::RET, ret_loc.as_mut_ptr() as u64);
let prev_pc = self.push_pc(offset);
loop {
match self.vm.run().expect("TODO") {
hbvm::VmRunOk::End => break,
hbvm::VmRunOk::Timer => todo!(),
hbvm::VmRunOk::Ecall => todo!(),
hbvm::VmRunOk::Breakpoint => todo!(),
}
}
self.pop_pc(prev_pc);
if let len @ 1..=8 = ret_loc.len() {
ret_loc.copy_from_slice(&self.vm.read_reg(reg::RET).0.to_ne_bytes()[..len])
}
self.vm.read_reg(reg::RET).0
}
pub fn reset(&mut self) {
let ptr = unsafe { self.stack.as_mut_ptr().cast::<u8>().add(VM_STACK_SIZE) as u64 };
self.vm.registers.fill(hbvm::value::Value(0));
self.vm.write_reg(reg::STACK_PTR, ptr);
self.vm.pc = hbvm::mem::Address::new(self.code.as_ptr() as u64 + HEADER_SIZE as u64);
}
fn push_pc(&mut self, offset: Offset) -> hbvm::mem::Address {
let entry = &mut self.code[offset as usize] as *mut _ as _;
core::mem::replace(&mut self.vm.pc, hbvm::mem::Address::new(entry))
- self.code.as_ptr() as usize
}
fn pop_pc(&mut self, prev_pc: hbvm::mem::Address) {
self.vm.pc = prev_pc + self.code.as_ptr() as usize;
}
pub fn clear(&mut self) {
self.code.clear();
}
#[must_use]
pub fn active(&self) -> bool {
self.depth != 0
}
pub fn activate(&mut self) {
self.depth += 1;
}
pub fn deactivate(&mut self) {
self.depth -= 1;
}
}
impl Default for Comptime {
fn default() -> Self {
let mut stack = Box::<[u8; VM_STACK_SIZE]>::new_uninit();
let mut vm = hbvm::Vm::default();
let ptr = unsafe { stack.as_mut_ptr().cast::<u8>().add(VM_STACK_SIZE) as u64 };
vm.write_reg(reg::STACK_PTR, ptr);
Self { vm, stack: unsafe { stack.assume_init() }, code: Default::default(), depth: 0 }
}
}
const HEADER_SIZE: usize = core::mem::size_of::<AbleOsExecutableHeader>();
#[repr(packed)]
#[expect(dead_code)]
pub struct AbleOsExecutableHeader {
magic_number: [u8; 3],
executable_version: u32,
code_length: u64,
data_length: u64,
debug_length: u64,
config_length: u64,
metadata_length: u64,
}
#[cfg(test)]
pub fn test_run_vm(out: &[u8], output: &mut String) {
use core::{ffi::CStr, fmt::Write};
let mut stack = [0_u64; 1024 * 20];
let mut vm = unsafe {
hbvm::Vm::<_, { 1024 * 100 }>::new(
LoggedMem::default(),
hbvm::mem::Address::new(out.as_ptr() as u64).wrapping_add(HEADER_SIZE),
)
};
vm.write_reg(reg::STACK_PTR, unsafe { stack.as_mut_ptr().add(stack.len()) } as u64);
let stat = loop {
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];
let layout = core::alloc::Layout::from_size_align(size, align).unwrap();
let ptr = unsafe { alloc::alloc::alloc(layout) };
vm.write_reg(1, ptr as u64);
}
96 => {
let [ptr, size, align] = [
vm.read_reg(3).0 as usize,
vm.read_reg(4).0 as usize,
vm.read_reg(5).0 as usize,
];
let layout = core::alloc::Layout::from_size_align(size, align).unwrap();
unsafe { alloc::alloc::dealloc(ptr as *mut u8, layout) };
}
3 => vm.write_reg(1, 42),
8 => {}
unknown => writeln!(output, "unknown ecall: {unknown:?}").unwrap(),
},
Ok(hbvm::VmRunOk::Timer) => {
writeln!(output, "timed out").unwrap();
break Ok(());
}
Ok(ev) => writeln!(output, "ev: {:?}", ev).unwrap(),
Err(e) => break Err(e),
}
};
writeln!(output, "code size: {}", out.len() - HEADER_SIZE).unwrap();
writeln!(output, "ret: {:?}", vm.read_reg(1).0).unwrap();
writeln!(output, "status: {:?}", stat).unwrap();
}

File diff suppressed because it is too large Load diff

675
lang/src/fmt.rs Normal file
View file

@ -0,0 +1,675 @@
use {
crate::{
lexer::{self, Lexer, TokenKind},
parser::{
self, CommentOr, CtorField, EnumField, Expr, FieldList, ListKind, Poser, Radix,
StructField, UnionField,
},
},
core::{
fmt::{self},
mem,
},
};
pub fn display_radix(radix: Radix, mut value: u64, buf: &mut [u8; 64]) -> &str {
fn conv_radix(d: u8) -> u8 {
match d {
0..=9 => d + b'0',
_ => d - 10 + b'A',
}
}
for (i, b) in buf.iter_mut().enumerate().rev() {
let d = (value % radix as u64) as u8;
value /= radix as u64;
*b = conv_radix(d);
if value == 0 {
return unsafe { core::str::from_utf8_unchecked(&buf[i..]) };
}
}
unreachable!()
}
#[repr(u8)]
enum TokenGroup {
Blank,
Comment,
Keyword,
Identifier,
Directive,
Number,
String,
Op,
Assign,
Paren,
Bracket,
Colon,
Comma,
Dot,
Ctor,
}
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..)
}
let mut writer = source.as_mut_ptr();
let mut reader = &source[..];
let mut prev_needs_whitecpace = false;
let mut prev_needs_newline = false;
loop {
let mut token = lexer::Lexer::new(reader).eat();
match token.kind {
TokenKind::Eof => break,
TokenKind::CtIdent | TokenKind::CtLoop | TokenKind::Directive => token.start -= 1,
_ => {}
}
let cpy_len = token.range().len();
let mut prefix = 0;
if prev_needs_whitecpace && needs_space(reader.as_bytes()[token.start as usize]) {
prefix = b' ';
debug_assert!(token.start != 0, "{reader}");
}
prev_needs_whitecpace = needs_space(reader.as_bytes()[token.end as usize - 1]);
let inbetween_new_lines =
reader[..token.start as usize].bytes().filter(|&b| b == b'\n').count()
+ token.kind.precedence().is_some() as usize;
let extra_prefix_new_lines = if inbetween_new_lines > 1 {
1 + token.kind.precedence().is_none() as usize
} else {
prev_needs_newline as usize
};
if token.kind == TokenKind::Comment && reader.as_bytes()[token.end as usize - 1] != b'/' {
prev_needs_newline = true;
prev_needs_whitecpace = false;
} else {
prev_needs_newline = false;
}
let sstr = reader[token.start as usize..].as_ptr();
reader = &reader[token.end as usize..];
unsafe {
if extra_prefix_new_lines != 0 {
for _ in 0..extra_prefix_new_lines {
writer.write(b'\n');
writer = writer.add(1);
}
} else if prefix != 0 {
writer.write(prefix);
writer = writer.add(1);
}
writer.copy_from(sstr, cpy_len);
writer = writer.add(cpy_len);
}
}
unsafe { writer.sub_ptr(source.as_mut_ptr()) }
}
pub struct Formatter<'a> {
source: &'a str,
depth: usize,
}
// we exclusively use `write_str` to reduce bloat
impl<'a> Formatter<'a> {
pub fn new(source: &'a str) -> Self {
Self { source, depth: 0 }
}
fn fmt_list<T: Poser, F: core::fmt::Write>(
&mut self,
f: &mut F,
trailing: bool,
end: &str,
sep: &str,
list: &[T],
fmt: impl Fn(&mut Self, &T, &mut F) -> fmt::Result,
) -> fmt::Result {
self.fmt_list_low(f, trailing, end, sep, list, |s, v, f| {
fmt(s, v, f)?;
Ok(true)
})
}
fn fmt_list_low<T: Poser, F: core::fmt::Write>(
&mut self,
f: &mut F,
trailing: bool,
end: &str,
sep: &str,
list: &[T],
fmt: impl Fn(&mut Self, &T, &mut F) -> Result<bool, fmt::Error>,
) -> fmt::Result {
if !trailing {
let mut first = true;
for expr in list {
if !core::mem::take(&mut first) {
f.write_str(sep)?;
f.write_str(" ")?;
}
first = !fmt(self, expr, f)?;
}
return f.write_str(end);
}
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() {
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(prev) = self.source.get(..expr.posi() as usize)
{
if sep.is_empty() && prev.trim_end().ends_with(';') {
f.write_str(";")?;
}
if count_trailing_newlines(prev) > 1 {
f.write_str("\n")?;
}
}
if add_sep {
f.write_str("\n")?;
}
}
Ok(())
})();
self.depth -= !end.is_empty() as usize;
if !end.is_empty() {
for _ in 0..self.depth {
f.write_str("\t")?;
}
f.write_str(end)?;
}
res
}
fn fmt_paren<F: core::fmt::Write>(
&mut self,
expr: &Expr,
f: &mut F,
cond: impl FnOnce(&Expr) -> bool,
) -> fmt::Result {
if cond(expr) {
f.write_str("(")?;
self.fmt(expr, f)?;
f.write_str(")")
} else {
self.fmt(expr, f)
}
}
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,)*) => {
$(
let $name = |e: &Expr| matches!(e, $pat);
)*
};
}
impl_parenter! {
unary => Expr::BinOp { .. },
postfix => Expr::UnOp { .. } | Expr::BinOp { .. },
consecutive => Expr::UnOp { .. },
}
match *expr {
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}\")"),
Expr::Field { target, name: field, .. } => {
self.fmt_paren(target, f, postfix)?;
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)?;
f.write_str("(")?;
self.fmt_list(f, false, ")", ",", args, Self::fmt)
}
Expr::Struct { fields, trailing_comma, packed, .. } => {
if packed {
f.write_str("packed ")?;
}
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)?;
}
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, postfix)?;
}
f.write_str(".{")?;
self.fmt_list(
f,
trailing_comma,
"}",
",",
fields,
|s: &mut Self, CtorField { name, value, .. }: &_, f| {
f.write_str(name)?;
if !matches!(value, &Expr::Ident { id, .. } if *name == &self.source[id.range()]) {
f.write_str(": ")?;
s.fmt(value, f)?;
}
Ok(())
},
)
}
Expr::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::List {
pos,
kind: term,
ty: Some(&Expr::Slice { pos: spos, size: None, item }),
fields,
trailing_comma,
},
f,
),
Expr::List { ty, kind: term, fields, trailing_comma, .. } => {
if let Some(ty) = ty {
self.fmt_paren(ty, f, postfix)?;
}
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("[")?;
if let Some(size) = size {
self.fmt(size, f)?;
}
f.write_str("]")?;
self.fmt_paren(item, f, unary)
}
Expr::Index { base, index } => {
self.fmt_paren(base, f, postfix)?;
f.write_str("[")?;
self.fmt(index, f)?;
f.write_str("]")
}
Expr::UnOp { op, val, .. } => {
f.write_str(op.name())?;
self.fmt_paren(val, f, unary)
}
Expr::Break { .. } => f.write_str("break"),
Expr::Continue { .. } => f.write_str("continue"),
Expr::If { cond, then, else_, .. } => {
f.write_str("if ")?;
self.fmt(cond, f)?;
f.write_str(" ")?;
self.fmt_paren(then, f, consecutive)?;
if let Some(e) = else_ {
f.write_str(" else ")?;
self.fmt(e, f)?;
}
Ok(())
}
Expr::Match { value, branches, .. } => {
f.write_str("match ")?;
self.fmt(value, f)?;
f.write_str(" {")?;
self.fmt_list(f, true, "}", ",", branches, |s, br, f| {
s.fmt(&br.pat, f)?;
f.write_str(" => ")?;
s.fmt(&br.body, f)
})
}
Expr::Loop { body, unrolled, .. } => {
f.write_str(if unrolled { "$loop " } else { "loop " })?;
self.fmt(body, f)
}
Expr::Closure { ret, body, args, .. } => {
f.write_str("fn(")?;
self.fmt_list(f, false, "", ",", args, |s, arg, f| {
if arg.is_ct {
f.write_str("$")?;
}
f.write_str(arg.name)?;
f.write_str(": ")?;
s.fmt(&arg.ty, f)
})?;
f.write_str("): ")?;
self.fmt(ret, f)?;
f.write_str(" ")?;
self.fmt_paren(body, f, consecutive)?;
Ok(())
}
Expr::Call { func, args, trailing_comma } => {
self.fmt_paren(func, f, postfix)?;
f.write_str("(")?;
self.fmt_list(f, trailing_comma, ")", ",", args, Self::fmt)
}
Expr::Return { val: Some(val), .. } => {
f.write_str("return ")?;
self.fmt(val, f)
}
Expr::Return { val: None, .. } => f.write_str("return"),
Expr::Wildcard { .. } => f.write_str("_"),
Expr::Ident { pos, is_ct, .. } => {
if is_ct {
f.write_str("$")?;
}
f.write_str(&self.source[Lexer::restore(self.source, pos).eat().range()])
}
Expr::Block { stmts, .. } => {
f.write_str("{")?;
self.fmt_list(f, true, "}", "", stmts, Self::fmt)
}
Expr::Number { value, radix, .. } => {
f.write_str(match radix {
Radix::Decimal => "",
Radix::Hex => "0x",
Radix::Octal => "0o",
Radix::Binary => "0b",
})?;
let mut buf = [0u8; 64];
f.write_str(display_radix(radix, value as u64, &mut buf))
}
Expr::Float { pos, .. } => {
f.write_str(&self.source[Lexer::restore(self.source, pos).eat().range()])
}
Expr::Bool { value, .. } => f.write_str(if value { "true" } else { "false" }),
Expr::Idk { .. } => f.write_str("idk"),
Expr::Die { .. } => f.write_str("die"),
Expr::Null { .. } => f.write_str("null"),
Expr::BinOp {
left,
op: TokenKind::Assign,
right: &Expr::BinOp { left: lleft, op, right, .. },
..
} if left.pos() == lleft.pos() => {
self.fmt(left, f)?;
f.write_str(" ")?;
f.write_str(op.name())?;
f.write_str("= ")?;
self.fmt(right, f)
}
Expr::BinOp { right, op, left, .. } => {
let prec_miss_left = |e: &Expr| {
matches!(
e, Expr::BinOp { op: lop, .. } if op.precedence() > lop.precedence()
)
};
let prec_miss_right = |e: &Expr| {
matches!(
e, Expr::BinOp { op: lop, .. }
if (op.precedence() == lop.precedence() && !op.is_comutative())
|| op.precedence() > lop.precedence()
)
};
self.fmt_paren(left, f, prec_miss_left)?;
if let Some(mut prev) = self.source.get(..right.pos() as usize) {
prev = prev.trim_end();
let estimate_bound =
prev.rfind(|c: char| c.is_ascii_whitespace()).map_or(prev.len(), |i| i + 1);
let exact_bound = lexer::Lexer::new(&prev[estimate_bound..]).last().start;
prev = &prev[..exact_bound as usize + estimate_bound];
if count_trailing_newlines(prev) > 0 {
f.write_str("\n")?;
for _ in 0..self.depth + 1 {
f.write_str("\t")?;
}
f.write_str(op.name())?;
f.write_str(" ")?;
} else {
if op != TokenKind::Colon {
f.write_str(" ")?;
}
f.write_str(op.name())?;
f.write_str(" ")?;
}
} else {
f.write_str(" ")?;
f.write_str(op.name())?;
f.write_str(" ")?;
}
self.fmt_paren(right, f, prec_miss_right)
}
}
}
}
pub fn count_trailing_newlines(source: &str) -> usize {
source[source.trim_end().len()..].bytes().filter(|&c| c == b'\n').count()
}
impl core::fmt::Display for parser::Ast {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_file(self.exprs(), &self.file, f)
}
}
pub fn fmt_file(exprs: &[Expr], file: &str, f: &mut impl fmt::Write) -> fmt::Result {
for (i, expr) in exprs.iter().enumerate() {
Formatter::new(file).fmt(expr, f)?;
if let Some(expr) = exprs.get(i + 1)
&& let Some(prefix) = file.get(..expr.pos() as usize)
{
if prefix.trim_end().ends_with(';') {
f.write_str(";")?;
}
if count_trailing_newlines(prefix) > 1 {
f.write_str("\n")?;
}
}
if i + 1 != exprs.len() {
writeln!(f)?;
}
}
Ok(())
}
#[cfg(test)]
pub mod test {
use {
crate::parser::{self, Ctx},
alloc::borrow::ToOwned,
std::{fmt::Write, string::String},
};
pub fn format(ident: &str, input: &str) {
let mut minned = input.to_owned();
let len = crate::fmt::minify(&mut minned);
minned.truncate(len);
let mut ctx = Ctx::default();
let ast = parser::Ast::new(ident, minned, &mut ctx, &mut parser::no_loader);
log::info!("{}", ctx.errors.borrow());
let mut output = String::new();
write!(output, "{ast}").unwrap();
let input_path = format!("formatter_{ident}.expected");
let output_path = format!("formatter_{ident}.actual");
std::fs::write(&input_path, input).unwrap();
std::fs::write(&output_path, output).unwrap();
let success = std::process::Command::new("diff")
.arg("-u")
.arg("--color")
.arg(&input_path)
.arg(&output_path)
.status()
.unwrap()
.success();
std::fs::remove_file(&input_path).unwrap();
std::fs::remove_file(&output_path).unwrap();
assert!(success, "test failed");
}
macro_rules! test {
($($name:ident => $input:expr;)*) => {$(
#[test]
fn $name() {
format(stringify!($name), $input);
}
)*};
}
test! {
comments => "// comment\n// comment\n\n// comment\n\n\
/* comment */\n/* comment */\n\n/* comment */";
some_ordinary_code => "loft := fn(): int return loft(1, 2, 3)";
some_arg_per_line_code => "loft := fn(): int return loft(\
\n\t1,\n\t2,\n\t3,\n)";
some_ordinary_struct => "loft := fn(): int return loft.{a: 1, b: 2}";
some_ordinary_fild_per_lin_struct => "loft := fn(): int return loft.{\
\n\ta: 1,\n\tb: 2,\n}";
code_block => "loft := fn(): int {\n\tloft()\n\treturn 1\n}";
}
}

449
lang/src/fs.rs Normal file
View file

@ -0,0 +1,449 @@
use {
crate::{
backend::hbvm::HbvmBackend,
parser::{Ast, Ctx, FileKind},
son::{self},
ty, FnvBuildHasher,
},
alloc::{string::String, vec::Vec},
core::{fmt::Write, num::NonZeroUsize, ops::Deref},
hashbrown::hash_map,
std::{
borrow::ToOwned,
collections::VecDeque,
eprintln,
ffi::OsStr,
io::{self, Write as _},
path::{Path, PathBuf},
string::ToString,
sync::Mutex,
},
};
type HashMap<K, V> = hashbrown::HashMap<K, V, FnvBuildHasher>;
pub struct Logger;
impl log::Log for Logger {
fn enabled(&self, _: &log::Metadata) -> bool {
true
}
fn log(&self, record: &log::Record) {
if self.enabled(record.metadata()) {
eprintln!("{}", record.args())
}
}
fn flush(&self) {}
}
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<'a> {
pub fmt: bool,
pub fmt_stdout: bool,
pub dump_asm: bool,
pub extra_threads: usize,
pub resolver: Option<PathResolver<'a>>,
}
impl<'a> Options<'a> {
pub fn from_args(
args: &[&str],
out: &mut Vec<u8>,
resolvers: &'a [(&str, PathResolver)],
) -> std::io::Result<Self> {
if args.contains(&"--help") || args.contains(&"-h") {
writeln!(out, "Usage: hbc [OPTIONS...] <FILE>")?;
writeln!(out, include_str!("../command-help.txt"))?;
return Err(std::io::ErrorKind::Other.into());
}
Ok(Options {
fmt: args.contains(&"--fmt"),
fmt_stdout: args.contains(&"--fmt-stdout"),
dump_asm: args.contains(&"--dump-asm"),
extra_threads: args
.iter()
.position(|&a| a == "--threads")
.map(|i| {
args[i + 1].parse::<NonZeroUsize>().map_err(|e| {
writeln!(out, "--threads expects non zero integer: {e}")
.err()
.unwrap_or(std::io::ErrorKind::Other.into())
})
})
.transpose()?
.map_or(1, NonZeroUsize::get)
- 1,
resolver: args
.iter()
.position(|&a| a == "--path-resolver")
.map(|i| {
resolvers.iter().find(|&&(n, _)| args[i + 1] == n).map(|&(_, r)| r).ok_or_else(
|| {
writeln!(
out,
"--path-resolver can only be one of: {}",
resolvers
.iter()
.map(|&(n, _)| n)
.intersperse(", ")
.collect::<String>()
)
.err()
.unwrap_or(std::io::ErrorKind::Other.into())
},
)
})
.transpose()?,
})
}
}
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),
)?;
if (options.fmt || options.fmt_stdout) && !parsed.errors.is_empty() {
*out = parsed.errors.into_bytes();
return Err(std::io::Error::other("fmt fialed (errors are in out)"));
}
if options.fmt {
let mut output = String::new();
for ast in parsed.ast {
write!(output, "{ast}").unwrap();
if ast.file.deref().trim() != output.as_str().trim() {
std::fs::write(&*ast.path, &output)?;
}
output.clear();
}
} else if options.fmt_stdout {
write!(out, "{}", &parsed.ast[0])?;
} else {
let mut backend = HbvmBackend::default();
let mut ctx = crate::son::CodegenCtx::default();
*ctx.parser.errors.get_mut() = parsed.errors;
let mut codegen = son::Codegen::new(&mut backend, &parsed.ast, &mut ctx);
codegen.push_embeds(parsed.embeds);
codegen.generate(ty::Module::MAIN);
*warnings = core::mem::take(&mut *codegen.warnings.borrow_mut());
if !codegen.errors.borrow().is_empty() {
drop(codegen);
*out = ctx.parser.errors.into_inner().into_bytes();
return Err(std::io::Error::other("compilation faoled (errors are in out)"));
}
codegen.assemble(out);
if options.dump_asm {
let mut disasm = String::new();
let err = codegen.disasm(&mut disasm, out).map_err(|e| io::Error::other(e.to_string()));
*out = disasm.into_bytes();
err?
}
}
Ok(())
}
struct TaskQueue<T> {
inner: Mutex<TaskQueueInner<T>>,
}
impl<T> TaskQueue<T> {
fn new(max_waiters: usize) -> Self {
Self { inner: Mutex::new(TaskQueueInner::new(max_waiters)) }
}
pub fn push(&self, message: T) {
self.extend([message]);
}
pub fn extend(&self, messages: impl IntoIterator<Item = T>) {
self.inner.lock().unwrap().push(messages);
}
pub fn pop(&self) -> Option<T> {
TaskQueueInner::pop(&self.inner)
}
}
enum TaskSlot<T> {
Waiting,
Delivered(T),
Closed,
}
struct TaskQueueInner<T> {
max_waiters: usize,
messages: VecDeque<T>,
parked: VecDeque<(*mut TaskSlot<T>, std::thread::Thread)>,
}
unsafe impl<T: Send> Send for TaskQueueInner<T> {}
unsafe impl<T: Send + Sync> Sync for TaskQueueInner<T> {}
impl<T> TaskQueueInner<T> {
fn new(max_waiters: usize) -> Self {
Self { max_waiters, messages: Default::default(), parked: Default::default() }
}
fn push(&mut self, messages: impl IntoIterator<Item = T>) {
for msg in messages {
if let Some((dest, thread)) = self.parked.pop_front() {
unsafe { *dest = TaskSlot::Delivered(msg) };
thread.unpark();
} else {
self.messages.push_back(msg);
}
}
}
fn pop(s: &Mutex<Self>) -> Option<T> {
let mut res = TaskSlot::Waiting;
{
let mut s = s.lock().unwrap();
if let Some(msg) = s.messages.pop_front() {
return Some(msg);
}
if s.max_waiters == s.parked.len() + 1 {
for (dest, thread) in s.parked.drain(..) {
unsafe { *dest = TaskSlot::Closed };
thread.unpark();
}
return None;
}
s.parked.push_back((&mut res, std::thread::current()));
}
loop {
std::thread::park();
let _s = s.lock().unwrap();
match core::mem::replace(&mut res, TaskSlot::Waiting) {
TaskSlot::Delivered(msg) => return Some(msg),
TaskSlot::Closed => return None,
TaskSlot::Waiting => {}
}
}
}
}
pub struct Loaded {
ast: Vec<Ast>,
embeds: Vec<Vec<u8>>,
errors: String,
}
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 })
}
/// 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: {}", display_rel_path(&self.path),)
}
}
impl core::error::Error for CantLoadFile {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(&self.source)
}
}
impl From<CantLoadFile> for io::Error {
fn from(e: CantLoadFile) -> Self {
io::Error::new(io::ErrorKind::InvalidData, e)
}
}
pub fn parse_from_fs(
extra_threads: usize,
root: &str,
resolve: PathResolver,
) -> io::Result<Loaded> {
type Task = (usize, PathBuf);
let seen_modules = Mutex::new(HashMap::<PathBuf, usize>::default());
let seen_embeds = Mutex::new(HashMap::<PathBuf, usize>::default());
let tasks = TaskQueue::<Task>::new(extra_threads + 1);
let ast = Mutex::new(Vec::<io::Result<Ast>>::new());
let embeds = Mutex::new(Vec::<Vec<u8>>::new());
let loader = |path: &str, from: &str, kind: FileKind, tmp: &mut _| {
let mut physiscal_path = resolve(path, from, tmp)?;
match kind {
FileKind::Module => {
let id = {
let mut seen = seen_modules.lock().unwrap();
let len = seen.len();
match seen.entry(physiscal_path) {
hash_map::Entry::Occupied(entry) => {
return Ok(*entry.get());
}
hash_map::Entry::Vacant(entry) => {
physiscal_path = entry.insert_entry(len as _).key().clone();
len
}
}
};
if !physiscal_path.exists() {
return Err(io::Error::new(
io::ErrorKind::NotFound,
format!("can't find file: {}", display_rel_path(&physiscal_path)),
));
}
tasks.push((id, physiscal_path));
Ok(id)
}
FileKind::Embed => {
let id = {
let mut seen = seen_embeds.lock().unwrap();
let len = seen.len();
match seen.entry(physiscal_path) {
hash_map::Entry::Occupied(entry) => {
return Ok(*entry.get());
}
hash_map::Entry::Vacant(entry) => {
physiscal_path = entry.insert_entry(len as _).key().clone();
len
}
}
};
let content = std::fs::read(&physiscal_path).map_err(|e| {
io::Error::new(
e.kind(),
format!(
"can't load embed file: {}: {e}",
display_rel_path(&physiscal_path)
),
)
})?;
let mut embeds = embeds.lock().unwrap();
if id >= embeds.len() {
embeds.resize(id + 1, Default::default());
}
embeds[id] = content;
Ok(id)
}
}
};
let execute_task = |ctx: &mut _, (_, path): Task, tmp: &mut _| {
let path = path.to_str().ok_or_else(|| {
io::Error::new(
io::ErrorKind::InvalidData,
format!("path contains invalid characters: {}", display_rel_path(&path)),
)
})?;
Ok(Ast::new(path, std::fs::read_to_string(path)?, ctx, &mut |path, from, kind| {
loader(path, from, kind, tmp).map_err(|e| e.to_string())
}))
};
let thread = || {
let mut ctx = Ctx::default();
let mut tmp = PathBuf::new();
while let Some(task @ (indx, ..)) = tasks.pop() {
let res = execute_task(&mut ctx, task, &mut tmp);
let mut ast = ast.lock().unwrap();
let len = ast.len().max(indx + 1);
ast.resize_with(len, || Err(io::ErrorKind::InvalidData.into()));
ast[indx] = res;
}
ctx.errors.into_inner()
};
let path = Path::new(root).canonicalize().map_err(|e| {
io::Error::new(e.kind(), format!("can't canonicalize root file path ({root})"))
})?;
seen_modules.lock().unwrap().insert(path.clone(), 0);
tasks.push((0, path));
let errors = if extra_threads == 0 {
thread()
} else {
std::thread::scope(|s| {
(0..extra_threads + 1)
.map(|_| s.spawn(thread))
.collect::<Vec<_>>()
.into_iter()
.map(|t| t.join().unwrap())
.collect::<String>()
})
};
Ok(Loaded {
ast: ast.into_inner().unwrap().into_iter().collect::<io::Result<Vec<_>>>()?,
embeds: embeds.into_inner().unwrap(),
errors,
})
}
pub fn display_rel_path(path: &(impl AsRef<OsStr> + ?Sized)) -> std::path::Display {
static CWD: std::sync::LazyLock<PathBuf> =
std::sync::LazyLock::new(|| std::env::current_dir().unwrap_or_default());
std::path::Path::new(path).strip_prefix(&*CWD).unwrap_or(std::path::Path::new(path)).display()
}

142
lang/src/fuzz.rs Normal file
View file

@ -0,0 +1,142 @@
use {
crate::{
backend::hbvm::HbvmBackend,
lexer::TokenKind,
parser,
son::{Codegen, CodegenCtx},
ty::Module,
},
alloc::string::String,
core::{fmt::Write, hash::BuildHasher, ops::Range},
};
#[derive(Default)]
struct Rand(pub u64);
impl Rand {
pub fn next(&mut self) -> u64 {
self.0 = crate::FnvBuildHasher::default().hash_one(self.0);
self.0
}
pub fn range(&mut self, min: u64, max: u64) -> u64 {
self.next() % (max - min) + min
}
fn bool(&mut self) -> bool {
self.next() % 2 == 0
}
}
#[derive(Default)]
struct FuncGen {
rand: Rand,
buf: String,
vars: u64,
}
impl FuncGen {
fn gen(&mut self, seed: u64) -> &str {
self.rand = Rand(seed);
self.buf.clear();
self.buf.push_str("main := fn(): void ");
self.block().unwrap();
&self.buf
}
fn block(&mut self) -> core::fmt::Result {
let prev_vars = self.vars;
self.buf.push('{');
for _ in 0..self.rand.range(1, 10) {
self.stmt()?;
}
self.buf.push('}');
self.vars = prev_vars;
Ok(())
}
fn stmt(&mut self) -> core::fmt::Result {
match self.rand.range(0, 100) {
0..4 => _ = self.block(),
4..10 => {
write!(self.buf, "var{} := ", self.vars)?;
self.expr()?;
self.vars += 1;
}
10..20 if self.vars != 0 => {
write!(self.buf, "var{} = ", self.rand.range(0, self.vars))?;
self.expr()?;
}
20..23 => {
self.buf.push_str("if ");
self.expr()?;
self.block()?;
if self.rand.bool() {
self.buf.push_str(" else ");
self.block()?;
}
}
_ => {
self.buf.push_str("return ");
self.expr()?;
}
}
self.buf.push(';');
Ok(())
}
fn expr(&mut self) -> core::fmt::Result {
match self.rand.range(0, 100) {
0..80 => {
write!(self.buf, "{}", self.rand.next())
}
80..90 if self.vars != 0 => {
write!(self.buf, "var{}", self.rand.range(0, self.vars))
}
80..100 => {
self.expr()?;
let ops = [
TokenKind::Add,
TokenKind::Sub,
TokenKind::Mul,
TokenKind::Div,
TokenKind::Shl,
TokenKind::Eq,
TokenKind::Ne,
TokenKind::Lt,
TokenKind::Gt,
TokenKind::Le,
TokenKind::Ge,
TokenKind::Band,
TokenKind::Bor,
TokenKind::Xor,
TokenKind::Mod,
TokenKind::Shr,
];
let op = ops[self.rand.range(0, ops.len() as u64) as usize];
write!(self.buf, " {op} ")?;
self.expr()
}
_ => unreachable!(),
}
}
}
pub fn fuzz(seed_range: Range<u64>) {
let mut gen = FuncGen::default();
let mut ctx = CodegenCtx::default();
for i in seed_range {
ctx.clear();
let src = gen.gen(i);
let parsed = parser::Ast::new("fuzz", src, &mut ctx.parser, &mut parser::no_loader);
assert!(ctx.parser.errors.get_mut().is_empty());
let mut backend = HbvmBackend::default();
let mut cdg = Codegen::new(&mut backend, core::slice::from_ref(&parsed), &mut ctx);
cdg.generate(Module::MAIN);
}
}

3
lang/src/fuzz_main.rs Normal file
View file

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

625
lang/src/lexer.rs Normal file
View file

@ -0,0 +1,625 @@
const fn ascii_mask(chars: &[u8]) -> u128 {
let mut eq = 0;
let mut i = 0;
while i < chars.len() {
let b = chars[i];
eq |= 1 << b;
i += 1;
}
eq
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub struct Token {
pub kind: TokenKind,
pub start: u32,
pub end: u32,
}
impl Token {
pub fn range(&self) -> core::ops::Range<usize> {
self.start as usize..self.end as usize
}
}
macro_rules! gen_token_kind {
($(
#[$atts:meta])*
$vis:vis enum $name:ident {
#[patterns] $(
$pattern:ident,
)*
#[keywords] $(
$keyword:ident = $keyword_lit:literal,
)*
#[const_keywords] $(
$const_keyword:ident = $const_keyword_lit:literal,
)*
#[punkt] $(
$punkt:ident = $punkt_lit:literal,
)*
#[ops] $(
#[$prec:ident] $(
$op:ident = $op_lit:literal $(=> $assign:ident)?,
)*
)*
}
) => {
impl core::fmt::Display for $name {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
f.write_str(self.name())
}
}
impl $name {
pub const OPS: &[Self] = &[$($(Self::$op),*),*];
pub fn name(&self) -> &str {
let sf = unsafe { &*(self as *const _ as *const u8) } ;
match *self {
$( Self::$pattern => concat!('<', stringify!($pattern), '>'), )*
$( Self::$keyword => stringify!($keyword_lit), )*
$( Self::$const_keyword => concat!('$', $const_keyword_lit), )*
$( Self::$punkt => stringify!($punkt_lit), )*
$($( Self::$op => $op_lit,
$(Self::$assign => concat!($op_lit, "="),)?)*)*
_ => unsafe { core::str::from_utf8_unchecked(core::slice::from_ref(&sf)) },
}
}
#[inline(always)]
pub fn precedence(&self) -> Option<u8> {
Some(match self {
$($(Self::$op => ${ignore($prec)} ${index(1)},
$(Self::$assign => 0,)?)*)*
_ => return None,
} + 1)
}
#[allow(non_upper_case_globals)]
fn from_ident(ident: &[u8]) -> Self {
$(const $keyword: &[u8] = $keyword_lit.as_bytes();)*
match ident {
$($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,
}
}
}
};
}
#[derive(PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
#[repr(u8)]
pub enum TokenKind {
Not = b'!',
DQuote = b'"',
Pound = b'#',
CtIdent = b'$',
Mod = b'%',
Band = b'&',
Quote = b'\'',
LParen = b'(',
RParen = b')',
Mul = b'*',
Add = b'+',
Comma = b',',
Sub = b'-',
Dot = b'.',
Div = b'/',
// Unused = 2-6
Shl = b'<' - 5,
// Unused = 8
Shr = b'>' - 5,
Colon = b':',
Semi = b';',
Lt = b'<',
Assign = b'=',
Gt = b'>',
Que = b'?',
Directive = b'@',
Comment,
Ident,
Number,
Float,
Eof,
Ct,
Ctor,
Tupl,
Arr,
TArrow,
Range,
Or,
And,
// Unused = R-Z
LBrack = b'[',
BSlash = b'\\',
RBrack = b']',
Xor = 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'|',
RBrace = b'}',
Tilde = b'~',
Decl = b':' + 128,
Eq = b'=' + 128,
Ne = b'!' + 128,
Le = b'<' + 128,
Ge = b'>' + 128,
BorAss = b'|' + 128,
AddAss = b'+' + 128,
SubAss = b'-' + 128,
MulAss = b'*' + 128,
DivAss = b'/' + 128,
ModAss = b'%' + 128,
XorAss = b'^' + 128,
BandAss = b'&' + 128,
ShrAss = b'>' - 5 + 128,
ShlAss = b'<' - 5 + 128,
}
impl core::fmt::Debug for TokenKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl TokenKind {
pub fn ass_op(self) -> Option<Self> {
let id = (self as u8).saturating_sub(128);
if ascii_mask(b"|+-*/%^&79") & (1u128 << id) == 0 {
return None;
}
Some(unsafe { core::mem::transmute::<u8, Self>(id) })
}
pub fn is_comutative(self) -> bool {
use TokenKind as S;
matches!(self, S::Eq | S::Ne | S::Bor | S::Xor | S::Band | S::Add | S::Mul)
}
pub fn is_compatison(self) -> bool {
matches!(self, Self::Lt | Self::Gt | Self::Ge | Self::Le | Self::Ne | Self::Eq)
}
pub fn is_supported_float_op(self) -> bool {
matches!(
self,
Self::Add
| Self::Sub
| Self::Mul
| Self::Div
| Self::Eq
| Self::Ne
| Self::Le
| Self::Ge
| Self::Lt
| Self::Gt
)
}
pub fn apply_binop(self, a: i64, b: i64, float: bool) -> i64 {
if float {
debug_assert!(self.is_supported_float_op());
let [a, b] = [f64::from_bits(a as _), f64::from_bits(b as _)];
let res = match self {
Self::Add => a + b,
Self::Sub => a - b,
Self::Mul => a * b,
Self::Div => a / b,
Self::Eq => return (a == b) as i64,
Self::Ne => return (a != b) as i64,
Self::Lt => return (a < b) as i64,
Self::Gt => return (a > b) as i64,
Self::Le => return (a >= b) as i64,
Self::Ge => return (a <= b) as i64,
_ => todo!("floating point op: {self}"),
};
return res.to_bits() as _;
}
match self {
Self::Add => a.wrapping_add(b),
Self::Sub => a.wrapping_sub(b),
Self::Mul => a.wrapping_mul(b),
Self::Div if b == 0 => 0,
Self::Div => a.wrapping_div(b),
Self::Shl => a.wrapping_shl(b as _),
Self::Eq => (a == b) as i64,
Self::Ne => (a != b) as i64,
Self::Lt => (a < b) as i64,
Self::Gt => (a > b) as i64,
Self::Le => (a >= b) as i64,
Self::Ge => (a <= b) as i64,
Self::Band => a & b,
Self::Bor => a | b,
Self::Xor => a ^ b,
Self::Mod if b == 0 => 0,
Self::Mod => a.wrapping_rem(b),
Self::Shr => a.wrapping_shr(b as _),
s => todo!("{s}"),
}
}
pub fn is_homogenous(&self) -> bool {
self.precedence() != Self::Eq.precedence()
&& self.precedence() != Self::Gt.precedence()
&& self.precedence() != Self::Eof.precedence()
}
pub fn apply_unop(&self, value: i64, float: bool) -> i64 {
match self {
Self::Sub if float => (-f64::from_bits(value as _)).to_bits() as _,
Self::Sub => value.wrapping_neg(),
Self::Not => (value == 0) as _,
Self::Float if float => value,
Self::Float => (value as f64).to_bits() as _,
Self::Number if float => f64::from_bits(value as _) as _,
Self::Number => value,
s => todo!("{s}"),
}
}
pub fn closing(&self) -> Option<TokenKind> {
Some(match self {
Self::Ctor => Self::RBrace,
Self::Tupl => Self::RParen,
Self::LParen => Self::RParen,
Self::LBrack => Self::RBrack,
Self::LBrace => Self::RBrace,
_ => return None,
})
}
}
gen_token_kind! {
pub enum TokenKind {
#[patterns]
CtIdent,
Ident,
Number,
Float,
Eof,
Directive,
#[keywords]
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]
// this also includess all `<op>=` tokens
Decl = ":=",
Assign = "=",
#[prec]
Or = "||",
#[prec]
And = "&&",
#[prec]
Bor = "|" => BorAss,
#[prec]
Xor = "^" => XorAss,
#[prec]
Band = "&" => BandAss,
#[prec]
Eq = "==",
Ne = "!=",
#[prec]
Le = "<=",
Ge = ">=",
Lt = "<",
Gt = ">",
#[prec]
Shl = "<<" => ShlAss,
Shr = ">>" => ShrAss,
#[prec]
Add = "+" => AddAss,
Sub = "-" => SubAss,
#[prec]
Mul = "*" => MulAss,
Div = "/" => DivAss,
Mod = "%" => ModAss,
}
}
pub struct Lexer<'a> {
pos: u32,
source: &'a [u8],
}
impl<'a> Lexer<'a> {
pub fn new(input: &'a str) -> Self {
Self::restore(input, 0)
}
pub fn uses(input: &'a str) -> impl Iterator<Item = &'a str> {
let mut s = Self::new(input);
core::iter::from_fn(move || loop {
let t = s.eat();
if t.kind == TokenKind::Eof {
return None;
}
if t.kind == TokenKind::Directive
&& s.slice(t.range()) == "use"
&& s.eat().kind == TokenKind::LParen
{
let t = s.eat();
if t.kind == TokenKind::DQuote {
return Some(&s.slice(t.range())[1..t.range().len() - 1]);
}
}
})
}
pub fn restore(input: &'a str, pos: u32) -> Self {
Self { pos, source: input.as_bytes() }
}
pub fn source(&self) -> &'a str {
unsafe { core::str::from_utf8_unchecked(self.source) }
}
pub fn slice(&self, tok: core::ops::Range<usize>) -> &'a str {
unsafe { core::str::from_utf8_unchecked(&self.source[tok]) }
}
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
} else {
Some(unsafe { *self.source.get_unchecked(self.pos as usize) })
}
}
fn advance(&mut self) -> Option<u8> {
let c = self.peek()?;
self.pos += 1;
Some(c)
}
pub fn last(&mut self) -> Token {
let mut token = self.eat();
loop {
let next = self.eat();
if next.kind == TokenKind::Eof {
break;
}
token = next;
}
token
}
pub fn eat(&mut self) -> Token {
use TokenKind as T;
loop {
let mut start = self.pos;
let Some(c) = self.advance() else {
return Token { kind: T::Eof, start, end: self.pos };
};
let advance_ident = |s: &mut Self| {
while let Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'_' | 127..) = s.peek() {
s.advance();
}
};
let identity = |s: u8| unsafe { core::mem::transmute::<u8, T>(s) };
let kind = match c {
..=b' ' => continue,
b'0' if self.advance_if(b'x') => {
while let Some(b'0'..=b'9' | b'A'..=b'F' | b'a'..=b'f') = self.peek() {
self.advance();
}
T::Number
}
b'0' if self.advance_if(b'b') => {
while let Some(b'0' | b'1') = self.peek() {
self.advance();
}
T::Number
}
b'0' if self.advance_if(b'o') => {
while let Some(b'0'..=b'7') = self.peek() {
self.advance();
}
T::Number
}
b'0'..=b'9' => {
while let Some(b'0'..=b'9') = self.peek() {
self.advance();
}
if self
.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
} else {
T::Number
}
}
b'a'..=b'z' | b'A'..=b'Z' | b'_' | 127.. => {
advance_ident(self);
let ident = &self.source[start as usize..self.pos as usize];
T::from_ident(ident)
}
b'"' | b'\'' => loop {
match self.advance() {
Some(b'\\') => _ = self.advance(),
Some(nc) if nc == c => break identity(c),
Some(_) => {}
None => break T::Eof,
}
},
b'/' if self.advance_if(b'/') => {
while let Some(l) = self.peek()
&& l != b'\n'
{
self.pos += 1;
}
let end = self.source[..self.pos as usize]
.iter()
.rposition(|&b| !b.is_ascii_whitespace())
.map_or(self.pos, |i| i as u32 + 1);
return Token { kind: T::Comment, start, end };
}
b'/' if self.advance_if(b'*') => {
let mut depth = 1;
while let Some(l) = self.advance() {
match l {
b'/' if self.advance_if(b'*') => depth += 1,
b'*' if self.advance_if(b'/') => match depth {
1 => break,
_ => depth -= 1,
},
_ => {}
}
}
T::Comment
}
b'.' if self.advance_if(b'{') => T::Ctor,
b'.' if self.advance_if(b'(') => T::Tupl,
b'.' if self.advance_if(b'[') => T::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'@' => {
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)
}
b':' | b'=' | b'!' | b'<' | b'>' | b'|' | b'+' | b'-' | b'*' | b'/' | b'%'
| b'^' | b'&'
if self.advance_if(b'=') =>
{
identity(c + 128)
}
_ => identity(c),
};
return Token { kind, start, end: self.pos };
}
}
fn advance_if(&mut self, arg: u8) -> bool {
if self.peek() == Some(arg) {
self.advance();
true
} else {
false
}
}
}
pub fn line_col(bytes: &[u8], pos: u32) -> (usize, usize) {
bytes[..pos as usize]
.split(|&b| b == b'\n')
.map(<[u8]>::len)
.enumerate()
.last()
.map(|(line, col)| (line + 1, col + 1))
.unwrap_or((1, 1))
}

523
lang/src/lib.rs Normal file
View file

@ -0,0 +1,523 @@
#![feature(
iter_array_chunks,
assert_matches,
let_chains,
if_let_guard,
macro_metavar_expr,
anonymous_lifetime_in_impl_trait,
core_intrinsics,
never_type,
unwrap_infallible,
slice_partition_dedup,
portable_simd,
iter_collect_into,
ptr_metadata,
slice_ptr_get,
slice_take,
map_try_insert,
extract_if,
ptr_internals,
iter_intersperse,
str_from_raw_parts,
ptr_sub_ptr,
slice_from_ptr_range,
iter_next_chunk,
pointer_is_aligned_to,
maybe_uninit_fill,
array_chunks,
array_windows
)]
#![warn(clippy::dbg_macro)]
#![expect(internal_features)]
#![no_std]
#[cfg(feature = "std")]
pub use fs::*;
pub use utils::Ent;
use {self::ty::Builtin, alloc::vec::Vec};
#[macro_use]
extern crate alloc;
#[cfg(any(feature = "std", test))]
extern crate std;
#[cfg(test)]
#[macro_export]
macro_rules! run_tests {
($runner:path: $($name:ident;)*) => {$(
#[test]
fn $name() {
$crate::run_test(core::any::type_name_of_val(&$name), stringify!($name), $crate::README, $runner);
}
)*};
}
pub mod fmt;
#[cfg(any(feature = "std", test))]
pub mod fs;
pub mod fuzz;
pub mod lexer;
pub mod nodes;
pub mod parser;
pub mod son;
pub mod ty;
pub mod backend {
use {
crate::{
nodes::Nodes,
parser,
ty::{self, Module, Types},
utils::EntSlice,
},
alloc::{string::String, vec::Vec},
};
pub mod hbvm;
pub struct AssemblySpec {
pub entry: u32,
pub code_length: u64,
pub data_length: u64,
}
pub trait Backend {
fn assemble_reachable(
&mut self,
from: ty::Func,
types: &Types,
to: &mut Vec<u8>,
) -> AssemblySpec;
fn disasm<'a>(
&'a self,
sluce: &[u8],
eca_handler: &mut dyn FnMut(&mut &[u8]),
types: &'a Types,
files: &'a EntSlice<Module, parser::Ast>,
output: &mut String,
) -> Result<(), hbbytecode::DisasmError<'a>>;
fn emit_body(
&mut self,
id: ty::Func,
ci: &Nodes,
tys: &Types,
files: &EntSlice<Module, parser::Ast>,
);
fn emit_ct_body(
&mut self,
id: ty::Func,
ci: &Nodes,
tys: &Types,
files: &EntSlice<Module, parser::Ast>,
) {
self.emit_body(id, ci, tys, files);
}
fn assemble_bin(&mut self, from: ty::Func, types: &Types, to: &mut Vec<u8>) {
self.assemble_reachable(from, types, to);
}
}
}
mod utils;
mod debug {
use core::fmt::Debug;
pub fn panicking() -> bool {
#[cfg(feature = "std")]
{
std::thread::panicking()
}
#[cfg(not(feature = "std"))]
{
false
}
}
#[cfg(all(debug_assertions, feature = "std"))]
pub type Trace = std::rc::Rc<(std::backtrace::Backtrace, std::string::String)>;
#[cfg(not(all(debug_assertions, feature = "std")))]
pub type Trace = ();
pub fn trace(_hint: impl Debug) -> Trace {
#[cfg(all(debug_assertions, feature = "std"))]
{
std::rc::Rc::new((std::backtrace::Backtrace::capture(), format!("{_hint:?}")))
}
#[cfg(not(all(debug_assertions, feature = "std")))]
{}
}
}
mod ctx_map {
use core::hash::BuildHasher;
pub type Hash = u64;
pub type HashBuilder = core::hash::BuildHasherDefault<IdentityHasher>;
#[derive(Default)]
pub struct IdentityHasher(u64);
impl core::hash::Hasher for IdentityHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, _: &[u8]) {
unimplemented!()
}
fn write_u64(&mut self, i: u64) {
self.0 = i;
}
}
#[derive(Clone)]
pub struct Key<T> {
pub value: T,
pub hash: Hash,
}
impl<T> core::hash::Hash for Key<T> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.hash);
}
}
pub trait CtxEntry {
type Ctx: ?Sized;
type Key<'a>: Eq + core::hash::Hash;
fn key<'a>(&self, ctx: &'a Self::Ctx) -> Self::Key<'a>;
}
#[derive(Clone)]
pub struct CtxMap<T> {
inner: hashbrown::HashMap<Key<T>, (), HashBuilder>,
}
impl<T> Default for CtxMap<T> {
fn default() -> Self {
Self { inner: Default::default() }
}
}
impl<T: CtxEntry> CtxMap<T> {
pub fn entry<'a, 'b>(
&'a mut self,
key: T::Key<'b>,
ctx: &'b T::Ctx,
) -> (hashbrown::hash_map::RawEntryMut<'a, Key<T>, (), HashBuilder>, Hash) {
let hash = crate::FnvBuildHasher::default().hash_one(&key);
(self.inner.raw_entry_mut().from_hash(hash, |k| k.value.key(ctx) == key), hash)
}
pub fn get<'a>(&self, key: T::Key<'a>, ctx: &'a T::Ctx) -> Option<&T> {
let hash = crate::FnvBuildHasher::default().hash_one(&key);
self.inner
.raw_entry()
.from_hash(hash, |k| k.value.key(ctx) == key)
.map(|(k, _)| &k.value)
}
pub fn clear(&mut self) {
self.inner.clear();
}
pub fn remove(&mut self, value: &T, ctx: &T::Ctx) -> Option<T> {
let (entry, _) = self.entry(value.key(ctx), ctx);
match entry {
hashbrown::hash_map::RawEntryMut::Occupied(o) => Some(o.remove_entry().0.value),
hashbrown::hash_map::RawEntryMut::Vacant(_) => None,
}
}
pub fn insert<'a>(&mut self, key: T::Key<'a>, value: T, ctx: &'a T::Ctx) {
let (entry, hash) = self.entry(key, ctx);
match entry {
hashbrown::hash_map::RawEntryMut::Occupied(_) => unreachable!(),
hashbrown::hash_map::RawEntryMut::Vacant(v) => {
_ = v.insert(Key { hash, value }, ())
}
}
}
pub fn get_or_insert<'a>(
&mut self,
key: T::Key<'a>,
ctx: &'a mut T::Ctx,
with: impl FnOnce(&'a mut T::Ctx) -> T,
) -> &mut T {
let (entry, hash) = self.entry(key, unsafe { &mut *(&mut *ctx as *mut _) });
match entry {
hashbrown::hash_map::RawEntryMut::Occupied(o) => &mut o.into_key_value().0.value,
hashbrown::hash_map::RawEntryMut::Vacant(v) => {
&mut v.insert(Key { hash, value: with(ctx) }, ()).0.value
}
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
pub struct Ident(u32);
impl Ident {
pub const INVALID: Self = Self(u32::MAX);
const LEN_BITS: u32 = 6;
pub fn len(self) -> u32 {
self.0 & ((1 << Self::LEN_BITS) - 1)
}
pub fn is_type(self) -> bool {
ty::Builtin::try_from(self) == Ok(ty::Builtin::TYPE)
}
pub fn is_empty(self) -> bool {
self.len() == 0
}
pub fn is_null(self) -> bool {
(self.0 >> Self::LEN_BITS) == 0
}
pub fn pos(self) -> u32 {
(self.0 >> Self::LEN_BITS).saturating_sub(1)
}
pub fn new(pos: u32, len: u32) -> Option<Self> {
(len < (1 << Self::LEN_BITS)).then_some(((pos + 1) << Self::LEN_BITS) | len).map(Self)
}
pub fn range(self) -> core::ops::Range<usize> {
let (len, pos) = (self.len() as usize, self.pos() as usize);
pos..pos + len
}
fn builtin(builtin: Builtin) -> Ident {
Self(builtin.index() as _)
}
}
fn endoce_string(
literal: &str,
str: &mut Vec<u8>,
report: impl Fn(&core::str::Bytes, &str),
) -> Option<usize> {
let report = |bytes: &core::str::Bytes, msg: &_| {
report(bytes, msg);
None::<u8>
};
let decode_braces = |str: &mut Vec<u8>, bytes: &mut core::str::Bytes| {
while let Some(b) = bytes.next()
&& b != b'}'
{
let c = bytes.next().or_else(|| report(bytes, "incomplete escape sequence"))?;
let decode = |b: u8| {
Some(match b {
b'0'..=b'9' => b - b'0',
b'a'..=b'f' => b - b'a' + 10,
b'A'..=b'F' => b - b'A' + 10,
_ => report(bytes, "expected hex digit or '}'")?,
})
};
str.push(decode(b)? << 4 | decode(c)?);
}
Some(())
};
let mut bytes = literal.bytes();
let mut char_len = 0;
while let Some(b) = bytes.next() {
char_len += 1;
if b != b'\\' {
str.push(b);
continue;
}
let b = match bytes.next().or_else(|| report(&bytes, "incomplete escape sequence"))? {
b'n' => b'\n',
b'r' => b'\r',
b't' => b'\t',
b'\\' => b'\\',
b'\'' => b'\'',
b'"' => b'"',
b'0' => b'\0',
b'{' => {
decode_braces(str, &mut bytes);
continue;
}
_ => report(&bytes, "unknown escape sequence, expected [nrt\\\"'{0]")?,
};
str.push(b);
}
Some(char_len)
}
pub fn quad_sort<T>(mut slice: &mut [T], mut cmp: impl FnMut(&T, &T) -> core::cmp::Ordering) {
while let Some(it) = slice.take_first_mut() {
for ot in &mut *slice {
if cmp(it, ot) == core::cmp::Ordering::Greater {
core::mem::swap(it, ot);
}
}
}
debug_assert!(slice.is_sorted_by(|a, b| cmp(a, b) != core::cmp::Ordering::Greater));
}
type FnvBuildHasher = core::hash::BuildHasherDefault<FnvHasher>;
struct FnvHasher(u64);
impl core::hash::Hasher for FnvHasher {
fn finish(&self) -> u64 {
self.0
}
fn write(&mut self, bytes: &[u8]) {
self.0 = bytes.iter().fold(self.0, |hash, &byte| {
let mut hash = hash;
hash ^= byte as u64;
hash = hash.wrapping_mul(0x100000001B3);
hash
});
}
}
impl Default for FnvHasher {
fn default() -> Self {
Self(0xCBF29CE484222325)
}
}
#[cfg(test)]
pub fn run_test(
name: &str,
ident: &str,
input: &str,
test: fn(&str, &str, &mut alloc::string::String),
) {
use std::{
io::Write,
path::PathBuf,
string::{String, ToString},
};
let filter = std::env::var("PT_FILTER").unwrap_or_default();
if !filter.is_empty() && !name.contains(&filter) {
return;
}
let mut output = String::new();
{
struct DumpOut<'a>(&'a mut String);
impl Drop for DumpOut<'_> {
fn drop(&mut self) {
if std::thread::panicking() {
std::println!("{}", self.0);
}
}
}
let dump = DumpOut(&mut output);
test(ident, input, dump.0);
}
let mut root = PathBuf::from(
std::env::var("PT_TEST_ROOT")
.unwrap_or(concat!(env!("CARGO_MANIFEST_DIR"), "/tests").to_string()),
);
root.push(name.replace("::", "_").replace(concat!(env!("CARGO_PKG_NAME"), "_"), ""));
root.set_extension("txt");
let expected = std::fs::read_to_string(&root).unwrap_or_default();
if output == expected {
return;
}
if std::env::var("PT_UPDATE").is_ok() {
std::fs::write(&root, output).unwrap();
return;
}
if !root.exists() {
std::fs::create_dir_all(root.parent().unwrap()).unwrap();
std::fs::write(&root, vec![]).unwrap();
}
let mut proc = std::process::Command::new("diff")
.arg("-u")
.arg("--color")
.arg(&root)
.arg("-")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::inherit())
.spawn()
.unwrap();
proc.stdin.as_mut().unwrap().write_all(output.as_bytes()).unwrap();
proc.wait().unwrap();
panic!("test failed");
}
#[cfg(test)]
fn test_parse_files(
ident: &str,
input: &str,
ctx: &mut parser::Ctx,
) -> (Vec<parser::Ast>, Vec<Vec<u8>>) {
use {
self::parser::FileKind,
std::{borrow::ToOwned, string::ToString},
};
let mut module_map = Vec::new();
let mut embed_map = Vec::new();
let mut last_start = 0;
let mut last_module_name = "test.hb";
for (i, m) in input.match_indices("// in module: ") {
if last_module_name.ends_with(".hb") {
fmt::test::format(ident, input[last_start..i].trim());
module_map.push((last_module_name, &input[last_start..i]));
} else {
embed_map.push((last_module_name, &input[last_start..i]));
}
let (module_name, _) = input[i + m.len()..].split_once('\n').unwrap();
last_module_name = module_name;
last_start = i + m.len() + module_name.len() + 1;
}
if last_module_name.ends_with(".hb") {
fmt::test::format(ident, input[last_start..].trim());
module_map.push((last_module_name, &input[last_start..]));
} else {
embed_map.push((last_module_name, &input[last_start..]));
}
let mut loader = |path: &str, _: &str, kind| match kind {
FileKind::Module => module_map
.iter()
.position(|&(name, _)| name == path)
.ok_or("Module Not Found".to_string()),
FileKind::Embed => embed_map
.iter()
.position(|&(name, _)| name == path)
.ok_or("Embed Not Found".to_string()),
};
(
module_map
.iter()
.map(|&(path, content)| parser::Ast::new(path, content.to_owned(), ctx, &mut loader))
.collect(),
embed_map.iter().map(|&(_, content)| content.to_owned().into_bytes()).collect(),
)
}

31
lang/src/main.rs Normal file
View file

@ -0,0 +1,31 @@
#[cfg(feature = "std")]
fn main() {
use std::io::Write;
fn run(out: &mut Vec<u8>, warnings: &mut String) -> std::io::Result<()> {
let args = std::env::args().collect::<Vec<_>>();
let args = args.iter().map(String::as_str).collect::<Vec<_>>();
let resolvers = &[("ableos", hblang::ABLEOS_PATH_RESOLVER)];
let opts = hblang::Options::from_args(&args, out, resolvers)?;
let file = args.iter().filter(|a| !a.starts_with('-')).nth(1).copied().unwrap_or("main.hb");
hblang::run_compiler(file, opts, out, warnings)
}
log::set_logger(&hblang::fs::Logger).unwrap();
log::set_max_level(log::LevelFilter::Error);
let mut out = Vec::new();
let mut warnings = String::new();
match run(&mut out, &mut warnings) {
Ok(_) => {
std::io::stderr().write_all(warnings.as_bytes()).unwrap();
std::io::stdout().write_all(&out).unwrap()
}
Err(_) => {
std::io::stderr().write_all(warnings.as_bytes()).unwrap();
std::io::stderr().write_all(&out).unwrap();
std::process::exit(1);
}
}
}

2253
lang/src/nodes.rs Normal file

File diff suppressed because it is too large Load diff

1848
lang/src/parser.rs Normal file

File diff suppressed because it is too large Load diff

4445
lang/src/son.rs Normal file

File diff suppressed because it is too large Load diff

1383
lang/src/ty.rs Normal file

File diff suppressed because it is too large Load diff

689
lang/src/utils.rs Normal file
View file

@ -0,0 +1,689 @@
use {
alloc::alloc,
core::{
alloc::Layout,
fmt::Debug,
hint::unreachable_unchecked,
marker::PhantomData,
mem::MaybeUninit,
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: BitSetUnit,
alloced: Unique<AllocedBitSet>,
}
impl Debug for BitSet {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl Clone for BitSet {
fn clone(&self) -> Self {
if self.is_inline() {
Self { inline: unsafe { self.inline } }
} else {
let (data, _) = self.data_and_len();
let (layout, _) = Self::layout(data.len());
unsafe {
let ptr = alloc::alloc(layout);
ptr.copy_from_nonoverlapping(self.alloced.as_ptr() as _, layout.size());
Self { alloced: Unique::new_unchecked(ptr as _) }
}
}
}
}
impl Drop for BitSet {
fn drop(&mut self) {
if !self.is_inline() {
unsafe {
let cap = self.alloced.as_ref().cap;
alloc::dealloc(self.alloced.as_ptr() as _, Self::layout(cap).0);
}
}
}
}
impl Default for BitSet {
fn default() -> Self {
Self { inline: Self::FLAG }
}
}
impl BitSet {
const FLAG: BitSetUnit = 1 << (Self::UNIT - 1);
const INLINE_ELEMS: usize = Self::UNIT - 1;
pub const UNIT: usize = core::mem::size_of::<BitSetUnit>() * 8;
pub fn with_capacity(len: usize) -> Self {
let mut s = Self::default();
s.reserve(len);
s
}
fn is_inline(&self) -> bool {
unsafe { self.inline & Self::FLAG != 0 }
}
fn data_and_len(&self) -> (&[BitSetUnit], usize) {
unsafe {
if self.is_inline() {
(core::slice::from_ref(&self.inline), Self::INLINE_ELEMS)
} else {
let small_vec = self.alloced.as_ref();
(
core::slice::from_raw_parts(
&small_vec.data as *const _ as *const BitSetUnit,
small_vec.cap,
),
small_vec.cap * Self::UNIT,
)
}
}
}
fn data_mut_and_len(&mut self) -> (&mut [BitSetUnit], usize) {
unsafe {
if self.is_inline() {
(core::slice::from_mut(&mut self.inline), INLINE_ELEMS)
} else {
let small_vec = self.alloced.as_mut();
(
core::slice::from_raw_parts_mut(
&mut small_vec.data as *mut _ as *mut BitSetUnit,
small_vec.cap,
),
small_vec.cap * Self::UNIT,
)
}
}
}
fn indexes(index: usize) -> (usize, usize) {
(index / Self::UNIT, index % Self::UNIT)
}
pub fn get(&self, index: Nid) -> bool {
let index = index as usize;
let (data, len) = self.data_and_len();
if index >= len {
return false;
}
let (elem, bit) = Self::indexes(index);
(unsafe { *data.get_unchecked(elem) }) & (1 << bit) != 0
}
pub fn set(&mut self, index: Nid) -> bool {
let index = index as usize;
let (mut data, len) = self.data_mut_and_len();
if core::intrinsics::unlikely(index >= len) {
self.grow((index + 1).next_power_of_two().max(4 * Self::UNIT));
(data, _) = self.data_mut_and_len();
}
let (elem, bit) = Self::indexes(index);
debug_assert!(elem < data.len(), "{} < {}", elem, data.len());
let elem = unsafe { data.get_unchecked_mut(elem) };
let prev = *elem;
*elem |= 1 << bit;
*elem != prev
}
fn grow(&mut self, size: usize) {
debug_assert!(size.is_power_of_two());
let slot_count = size / Self::UNIT;
let (layout, off) = Self::layout(slot_count);
let (ptr, prev_len) = unsafe {
if self.is_inline() {
let ptr = alloc::alloc(layout);
*ptr.add(off).cast::<BitSetUnit>() = self.inline & !Self::FLAG;
(ptr, 1)
} else {
let prev_len = self.alloced.as_ref().cap;
let (prev_layout, _) = Self::layout(prev_len);
(alloc::realloc(self.alloced.as_ptr() as _, prev_layout, layout.size()), prev_len)
}
};
unsafe {
MaybeUninit::fill(
core::slice::from_raw_parts_mut(
ptr.add(off).cast::<MaybeUninit<BitSetUnit>>().add(prev_len),
slot_count - prev_len,
),
0,
);
*ptr.cast::<usize>() = slot_count;
core::ptr::write(self, Self { alloced: Unique::new_unchecked(ptr as _) });
}
}
fn layout(slot_count: usize) -> (core::alloc::Layout, usize) {
unsafe {
core::alloc::Layout::new::<AllocedBitSet>()
.extend(Layout::array::<BitSetUnit>(slot_count).unwrap_unchecked())
.unwrap_unchecked()
}
}
pub fn iter(&self) -> BitSetIter {
if self.is_inline() {
BitSetIter { index: 0, current: unsafe { self.inline & !Self::FLAG }, remining: &[] }
} else {
let &[current, ref remining @ ..] = self.data_and_len().0 else {
unsafe { unreachable_unchecked() }
};
BitSetIter { index: 0, current, remining }
}
}
pub fn clear(&mut self, len: usize) {
self.reserve(len);
self.clear_as_is();
}
pub fn clear_as_is(&mut self) {
if self.is_inline() {
unsafe { self.inline &= Self::FLAG };
} else {
self.data_mut_and_len().0.fill(0);
}
}
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)
} else {
self.data_and_len().0
}
}
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 set_range(&mut self, proj_range: Range<usize>) {
if proj_range.is_empty() {
return;
}
self.reserve(proj_range.end);
let (units, _) = self.data_mut_and_len();
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: BitSetUnit,
remining: &'a [BitSetUnit],
}
impl Iterator for BitSetIter<'_> {
type Item = usize;
fn next(&mut self) -> Option<Self::Item> {
while self.current == 0 {
self.current = *self.remining.take_first()?;
self.index += 1;
}
let sub_idx = self.current.trailing_zeros() as usize;
self.current &= self.current - 1;
Some(self.index * BitSet::UNIT + sub_idx)
}
}
struct AllocedBitSet {
cap: usize,
data: [BitSetUnit; 0],
}
#[cfg(test)]
#[test]
fn test_small_bit_set() {
use std::vec::Vec;
let mut sv = BitSet::default();
sv.set(10);
debug_assert!(sv.get(10));
sv.set(100);
debug_assert!(sv.get(100));
sv.set(10000);
debug_assert!(sv.get(10000));
debug_assert_eq!(sv.iter().collect::<Vec<_>>(), &[10, 100, 10000]);
sv.clear(10000);
debug_assert_eq!(sv.iter().collect::<Vec<_>>(), &[]);
}
pub union Vc {
inline: InlineVc,
alloced: AllocedVc,
}
impl Default for Vc {
fn default() -> Self {
Vc { inline: InlineVc { elems: MaybeUninit::uninit(), cap: Default::default() } }
}
}
impl Debug for Vc {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
self.as_slice().fmt(f)
}
}
impl FromIterator<Nid> for Vc {
fn from_iter<T: IntoIterator<Item = Nid>>(iter: T) -> Self {
let mut slf = Self::default();
for i in iter {
slf.push(i);
}
slf
}
}
const INLINE_ELEMS: usize = VC_SIZE / 2 - 1;
const VC_SIZE: usize = 16;
impl Vc {
fn is_inline(&self) -> bool {
unsafe { self.inline.cap <= INLINE_ELEMS as Nid }
}
fn layout(&self) -> Option<core::alloc::Layout> {
unsafe {
self.is_inline().not().then(|| {
core::alloc::Layout::array::<Nid>(self.alloced.cap as _).unwrap_unchecked()
})
}
}
pub fn len(&self) -> usize {
unsafe {
if self.is_inline() {
self.inline.cap as _
} else {
self.alloced.len as _
}
}
}
fn len_mut(&mut self) -> &mut Nid {
unsafe {
if self.is_inline() {
&mut self.inline.cap
} else {
&mut self.alloced.len
}
}
}
fn as_ptr(&self) -> *const Nid {
unsafe {
match self.is_inline() {
true => self.inline.elems.as_ptr().cast(),
false => self.alloced.base.as_ptr(),
}
}
}
fn as_mut_ptr(&mut self) -> *mut Nid {
unsafe {
match self.is_inline() {
true => self.inline.elems.as_mut_ptr().cast(),
false => self.alloced.base.as_ptr(),
}
}
}
pub fn as_slice(&self) -> &[Nid] {
unsafe { core::slice::from_raw_parts(self.as_ptr(), self.len()) }
}
fn as_slice_mut(&mut self) -> &mut [Nid] {
unsafe { core::slice::from_raw_parts_mut(self.as_mut_ptr(), self.len()) }
}
pub fn push(&mut self, value: Nid) {
if let Some(layout) = self.layout() {
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;
}
}
unsafe {
*self.len_mut() += 1;
self.as_mut_ptr().add(self.len() - 1).write(value);
}
}
unsafe fn alloc(cap: usize, len: usize) -> Self {
debug_assert!(cap > INLINE_ELEMS);
let layout = unsafe { core::alloc::Layout::array::<Nid>(cap).unwrap_unchecked() };
let alloc = unsafe { alloc::alloc(layout) };
unsafe {
Vc {
alloced: AllocedVc {
base: Unique::new_unchecked(alloc.cast()),
len: len as _,
cap: cap as _,
},
}
}
}
pub fn swap_remove(&mut self, index: usize) {
let len = self.len() - 1;
self.as_slice_mut().swap(index, len);
*self.len_mut() -= 1;
}
pub fn remove(&mut self, index: usize) {
self.as_slice_mut().copy_within(index + 1.., index);
*self.len_mut() -= 1;
}
}
impl Drop for Vc {
fn drop(&mut self) {
if let Some(layout) = self.layout() {
unsafe {
alloc::dealloc(self.alloced.base.as_ptr().cast(), layout);
}
}
}
}
impl Clone for Vc {
fn clone(&self) -> Self {
self.as_slice().into()
}
}
impl IntoIterator for Vc {
type IntoIter = VcIntoIter;
type Item = Nid;
fn into_iter(self) -> Self::IntoIter {
VcIntoIter { start: 0, end: self.len(), vc: self }
}
}
pub struct VcIntoIter {
start: usize,
end: usize,
vc: Vc,
}
impl Iterator for VcIntoIter {
type Item = Nid;
fn next(&mut self) -> Option<Self::Item> {
if self.start == self.end {
return None;
}
let ret = unsafe { core::ptr::read(self.vc.as_slice().get_unchecked(self.start)) };
self.start += 1;
Some(ret)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.end - self.start;
(len, Some(len))
}
}
impl DoubleEndedIterator for VcIntoIter {
fn next_back(&mut self) -> Option<Self::Item> {
if self.start == self.end {
return None;
}
self.end -= 1;
Some(unsafe { core::ptr::read(self.vc.as_slice().get_unchecked(self.end)) })
}
}
impl ExactSizeIterator for VcIntoIter {}
impl<const SIZE: usize> From<[Nid; SIZE]> for Vc {
fn from(value: [Nid; SIZE]) -> Self {
value.as_slice().into()
}
}
impl<'a> From<&'a [Nid]> for Vc {
fn from(value: &'a [Nid]) -> Self {
if value.len() <= INLINE_ELEMS {
let mut dflt = Self::default();
unsafe {
core::ptr::copy_nonoverlapping(value.as_ptr(), dflt.as_mut_ptr(), value.len())
};
dflt.inline.cap = value.len() as _;
dflt
} else {
let mut allcd = unsafe { Self::alloc(value.len(), value.len()) };
unsafe {
core::ptr::copy_nonoverlapping(value.as_ptr(), allcd.as_mut_ptr(), value.len())
};
allcd
}
}
}
impl Deref for Vc {
type Target = [Nid];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl DerefMut for Vc {
fn deref_mut(&mut self) -> &mut Self::Target {
self.as_slice_mut()
}
}
#[derive(Clone, Copy)]
#[repr(C)]
struct InlineVc {
cap: Nid,
elems: MaybeUninit<[Nid; INLINE_ELEMS]>,
}
#[derive(Clone, Copy)]
#[repr(C)]
struct AllocedVc {
cap: Nid,
len: Nid,
base: Unique<Nid>,
}
pub trait Ent: Copy {
fn new(index: usize) -> Self;
fn index(self) -> usize;
}
#[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

@ -0,0 +1,47 @@
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LI32 r32, 1148846080w
CP r2, r32
JAL r31, r0, :sin
CP r33, r1
FMUL32 r32, r33, r32
FTI32 r32, r32, 1b
CP r1, r32
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
sin:
CP r13, r2
LI32 r14, 1124073472w
LI32 r15, 1078530011w
FMUL32 r14, r13, r14
FDIV32 r14, r14, r15
FTI32 r14, r14, 1b
ANDI r15, r14, 255d
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: 1311
ret: 826
status: Ok(())

View file

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

View file

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

View file

@ -0,0 +1,32 @@
main:
ADDI64 r254, r254, -56d
ST r31, r254, 24a, 32h
LI64 r32, 1d
ADDI64 r33, r254, 0d
ST r32, r254, 0a, 8h
LI64 r34, 2d
ST r34, r254, 8a, 8h
LI64 r34, 4d
ST r34, r254, 16a, 8h
CP r2, r33
JAL r31, r0, :pass
CP r33, r1
ADD64 r32, r33, r32
CP r1, r32
LD r31, r254, 24a, 32h
ADDI64 r254, r254, 56d
JALA r0, r31, 0a
pass:
CP r13, r2
LD r14, r13, 8a, 8h
MULI64 r15, r14, 8d
LD r16, r13, 0a, 8h
ADD64 r13, r15, r13
ADD64 r14, r14, r16
LD r13, r13, 0a, 8h
ADD64 r13, r13, r14
CP r1, r13
JALA r0, r31, 0a
code size: 246
ret: 8
status: Ok(())

View file

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

View file

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

View file

@ -0,0 +1,32 @@
main:
ADDI64 r254, r254, -24d
ST r31, r254, 0a, 24h
LRA r32, r0, :"abඞ\n\r\t56789\0"
CP r2, r32
JAL r31, r0, :str_len
CP r32, r1
LRA r33, r0, :"fff\0"
CP r2, r33
JAL r31, r0, :str_len
CP r33, r1
ADD64 r32, r33, r32
CP r1, r32
LD r31, r254, 0a, 24h
ADDI64 r254, r254, 24d
JALA r0, r31, 0a
str_len:
CP 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 r13, r13, 1d
ADDI64 r14, r14, 1d
JMP :2
1: JALA r0, r31, 0a
code size: 216
ret: 16
status: Ok(())

View file

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

View file

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

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
main:
CP r1, r0
JALA r0, r31, 0a
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

@ -0,0 +1,6 @@
main:
CP r1, r0
JALA r0, r31, 0a
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

@ -0,0 +1,11 @@
fun:
UN
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

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

View file

@ -0,0 +1,21 @@
main:
ADDI64 r254, r254, -16d
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
CP r1, r0
ADDI64 r254, r254, 16d
JALA r0, r31, 0a
ev: Ecall
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

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

View file

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

View file

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

View file

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

View file

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

@ -0,0 +1,38 @@
add:
CP r13, r2
CP r14, r3
ADD64 r13, r13, r14
CP r1, r13
JALA r0, r31, 0a
add:
CP r13, r2
CP r14, r3
ADD32 r13, r13, r14
CP r1, r13
JALA r0, r31, 0a
add:
JALA r0, r31, 0a
main:
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 r33, 3d
LI64 r34, 1d
CP r2, r34
CP r3, r33
JAL r31, r0, :add
CP r33, r1
ANDI r32, r32, 4294967295d
SUB64 r32, r32, r33
CP r1, r32
LD r31, r254, 0a, 32h
ADDI64 r254, r254, 32d
JALA r0, r31, 0a
code size: 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

@ -0,0 +1,219 @@
deinit:
ADDI64 r254, r254, -40d
ST r31, r254, 0a, 40h
CP r32, r2
LD r33, r32, 16a, 8h
LI64 r34, 8d
MUL64 r33, r33, r34
LD r35, r32, 0a, 8h
CP r2, r35
CP r3, r33
CP r4, r34
JAL r31, r0, :free
CP r1, r32
JAL r31, r0, :new
LD r31, r254, 0a, 40h
ADDI64 r254, r254, 40d
JALA r0, r31, 0a
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
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
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
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 r13, r2
CP r14, r3
LRA r15, r0, :malloc_sys_call
LD r15, r15, 0a, 8h
CP r2, r15
CP r3, r13
CP r4, r14
ECA
CP r13, r1
CP r1, r13
JALA r0, r31, 0a
new:
ADDI64 r254, r254, -24d
CP 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, -80d
ST r31, r254, 0a, 80h
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: CP r2, r32
CP r3, r35
JAL r31, r0, :malloc
ST r32, r36, 16a, 8h
CP r34, r1
JNE r34, r0, :3
CP r1, r0
JMP :4
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
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
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
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

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

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