2 |
3 | Spent the rest of the day cleaning up and improving process virtual memory layout. The code was pretty messy and I found two bugs in there. Maybe I fixed more that I don't know about!
4 |
--------------------------------------------------------------------------------
/etc/sublime-text/rsm/SymbolIndex.tmPreferences:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | scope
5 |
6 | settings
7 |
8 | showInIndexedSymbolList
9 | 1
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/.logbook/2022-09-13.md:
--------------------------------------------------------------------------------
1 | Back home and spent the full day working on `vm_map_findspace`.
2 |
3 | I ended up breaking down the problem into two smaller problems:
4 | 1. Iterating over pages in the vm_map
5 | 2. Finding free regions of pages (using the iterator function)
6 |
7 | And it works! Code is still disabled because it's really messy,
8 | but tomorrow I'll clean it up and do some more testing.
9 |
--------------------------------------------------------------------------------
/examples/factorial.rsm:
--------------------------------------------------------------------------------
1 | fun factorial(i32) i32 {
2 | R1 = R0 // ACC = n (argument 0)
3 | R0 = 1 // RES (return value 0)
4 | ifz R1 end // if n==0 goto end
5 | b1: // <- [b0] b1
6 | R0 = R1 * R0 // RES = ACC * RES
7 | R1 = R1 - 1 // ACC = ACC - 1
8 | if R1 b1 // if n!=0 goto b1
9 | end: // <- b0 [b1]
10 | ret // RES is at R0
11 | }
12 |
--------------------------------------------------------------------------------
/etc/sublime-text/rsm/Indent.tmPreferences:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | name
5 | Indentation Rules
6 | scope
7 | source.rsm
8 | settings
9 |
10 | decreaseIndentPattern
11 | ^(.*\*/)?\s*\}.*$
12 | increaseIndentPattern
13 | ^.*\{[^}"']*$
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/mem.c:
--------------------------------------------------------------------------------
1 | // misc memory functions
2 | // SPDX-License-Identifier: Apache-2.0
3 | #include "rsmimpl.h"
4 |
5 | bool rmem_align(rmem_t* region, usize alignment) {
6 | assertf(IS_POW2(alignment), "%zu", alignment);
7 | assert(RMEM_IS_VALID(*region));
8 | uintptr addr = ALIGN2((uintptr)region->p, alignment);
9 | usize size_reduction = (usize)(addr - (uintptr)region->p);
10 | if (check_sub_overflow(region->size, size_reduction, ®ion->size))
11 | return false;
12 | region->p = (void*)addr;
13 | return true;
14 | }
15 |
--------------------------------------------------------------------------------
/examples/cello.rsm:
--------------------------------------------------------------------------------
1 | //!exe2-only
2 |
3 | // Demonstrates editing a string and writing to stdout
4 |
5 | fun main(i32) {
6 | const STDOUT = 1
7 | data message = "Hello\n"
8 | stkmem 8 // 8 bytes of stack memory
9 |
10 | R2 = 6 // length of string
11 | R0 = SP - R2 // destination address
12 | R1 = message // source address
13 | mcopy R0 R1 R2 // copy string to stack
14 |
15 | R1 = 0x43 // character 'C'
16 | store1 R1 R0 0 // str[0] = 'C'
17 |
18 | R0 = write R0 R2 STDOUT
19 | stkmem -8
20 | }
21 |
--------------------------------------------------------------------------------
/src/syscall.h:
--------------------------------------------------------------------------------
1 | // system calls
2 | // SPDX-License-Identifier: Apache-2.0
3 | #pragma once
4 |
5 | #define RSM_FOREACH_SYSCALL(_) /* _(name, code, args, description) */ \
6 | _( SC_EXIT, 0, "status i32", "exit program" )\
7 | _( SC_SLEEP, 1, "nsec u64", "sleep for up to nsec; returns remaining time or error" )\
8 | \
9 | _( SC_TEXIT, _SC_MAX, "", "exit task" )\
10 | // end RSM_FOREACH_SYSCALL
11 |
12 | enum syscall_op {
13 | _SC_MAX = RSM_MAX_Au,
14 | #define _(name, code, ...) name = code,
15 | RSM_FOREACH_SYSCALL(_)
16 | #undef _
17 | };
18 |
--------------------------------------------------------------------------------
/etc/sched-lab.rsm:
--------------------------------------------------------------------------------
1 | // lab program for developing the new scheduler
2 | data s i8[500] = "hello" // for testing vm layout
3 | const SC_SLEEP = 1
4 |
5 | fun main() {
6 | stkmem 4194296 // allocate 64B of stack memory
7 |
8 | // // spawn new task
9 | // tspawn foo
10 |
11 | // // sleep os thread for 500ms
12 | // R0 = 500000000
13 | // syscall SC_SLEEP
14 |
15 | stkmem -4194296 // deallocate 64B of stack memory
16 | }
17 |
18 | fun foo() {
19 | R1 = 1
20 | // sleep os thread for 400ms
21 | R0 = 400000000
22 | syscall SC_SLEEP
23 | }
24 |
--------------------------------------------------------------------------------
/src/machine.h:
--------------------------------------------------------------------------------
1 | // virtual machine
2 | // SPDX-License-Identifier: Apache-2.0
3 | #pragma once
4 | #include "sched.h"
5 | RSM_ASSUME_NONNULL_BEGIN
6 |
7 | // rmachine_t: state of an entire virtual machine instance
8 | struct rmachine_ {
9 | rmm_t* mm; // memory manager
10 | rmemalloc_t* malloc; // memory allocator
11 | rsched_t sched; // scheduler
12 |
13 | // remaining free memory (tail of page-aligned rmachine_t)
14 | u16 tailmemcap; static_assert(PAGE_SIZE <= U16_MAX, "");
15 | u8 tailmem[];
16 | };
17 |
18 | RSM_ASSUME_NONNULL_END
19 |
--------------------------------------------------------------------------------
/.logbook/2022-09-22.md:
--------------------------------------------------------------------------------
1 | Okay I implemented eliding of stack splitting when stkmem_grow can expand the current stack by mapping in pages just above the current stack (at lower addresses.) It was pretty trivial. However is comes at the cost of a non standard task-stack size (after a stack is grown) which means tasks with enlarged stacks can't be reused. In some distant future it could be interesting to measure if this actually has a real-world impact or not. It probably hasn't. Plus, I want RSM to be as simple as it can be, taking on complexity cost only when it has a clear benefit.
2 |
--------------------------------------------------------------------------------
/.logbook/2022-09-21.md:
--------------------------------------------------------------------------------
1 | Over the past few days I've been reworking the virtual memory management functions: iter, add, del, access etc. Some were sketch quality (like del) and others suffered from messy implementation (like add) while there were several bugs crawling around.
2 |
3 | Now stack splitting works, which is used as a test case for virtual memory management as it does all the things except access: grow, find empty spac, add mappings and delete mappings. All at non-trivial addresses.
4 |
5 | I need to leave for a meeting now but later this afternoon (or whenever I get to it) I'll be looking at eliding stack splitting when stkmem_grow is able to expand the stack.
6 |
--------------------------------------------------------------------------------
/etc/website/_css/fonts.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: Inter_v;
3 | font-style: oblique 0deg 10deg;
4 | font-weight: 100 900;
5 | font-display: block;
6 | src: url('Inter-V.var.woff2') format("woff2");
7 | }
8 | @font-face {
9 | font-family: 'jbmono';
10 | font-weight: 100 800;
11 | font-style: normal;
12 | font-named-instance: 'Regular';
13 | src: url("https://rsms.me/res/fonts/jbm/jetbrains-mono-wght.woff2") format("woff2");
14 | }
15 | @font-face {
16 | font-family: 'jbmono';
17 | font-weight: 100 800;
18 | font-style: italic;
19 | font-named-instance: 'Italic';
20 | src: url("https://rsms.me/res/fonts/jbm/jetbrains-mono-italic_wght.woff2") format("woff2");
21 | }
22 |
--------------------------------------------------------------------------------
/.logbook/2022-09-07.md:
--------------------------------------------------------------------------------
1 | Started implementing vm_map that can find "any suitable virtual address", like an allocator. Inspired by FreeBSD's API, I changed (cleaned up?) the vm API. vm_pagedir_t is now called vm_map_t ("virtual memory map") and functions vm_map and vm_unmap were renamed to vm_map_add for adding a mapping and vm_map_del for removing a mapping.
2 |
3 | Doing all this I also realized that having an old-style brk/sbrk API for the process heap is just a dumb idea, so I ripped out the heap code from the scheduler's memory layout function. Instead, a process that needs a heap can:
4 | - Allocate an initial heap by defining data, e.g. `data initheap i8[4096]`
5 | - Allocate additional memory pages during runtime to grow its heap with `syscall(SC_MMAP)`
6 |
7 |
--------------------------------------------------------------------------------
/etc/website/isa/op.copyv.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: copyv
3 | template: template-op
4 | ---
5 |
6 | `copyv ABv → reg`
7 |
8 | Materializes a large constant from values following the {{title}} instruction as one or more trailing 32-bit chunks.
9 |
10 | ## Semantics
11 |
12 | dstreg = arg(A)
13 | n = arg(B)
14 | R(dstreg) = instr[PC+1]
15 | PC ++
16 | if n > 2
17 | R(dstreg) = (R(dstreg) << 32) | instr[PC+2]
18 | PC ++
19 |
20 | ## Examples
21 |
22 | ```rsm
23 | copyv R0 400000000
24 | // encoded as: copyv R0 0x1 0x17d78400
25 |
26 | copyv R1 0xdeadbeefbabeface
27 | // encoded as: copyv R1 0x2 0xdeadbeef 0xbabeface
28 |
29 | // Equivalent "assignment" syntax:
30 | R1 = 0xdeadbeefbabeface
31 | // encoded as: copyv R1 0x2 0xdeadbeef 0xbabeface
32 | ```
33 |
--------------------------------------------------------------------------------
/examples/fail-arith-overflow.rsm:
--------------------------------------------------------------------------------
1 | // This demonstrates how integer overflow behaves.
2 | // add, sub and mul wraps the bits around, treating operands as unsigned.
3 | // adds, subs and muls panics on overflow.
4 | // Note: In the future, overflow will not panic RSM but do something more
5 | // useful that the program can handle. E.g. something akin to signals.
6 |
7 | const U64_MAX = 0xffffffffffffffff
8 | const I64_MAX = 0x7fffffffffffffff
9 |
10 | fun main() {
11 | // overflow from non-"s" (sign agnostic) instructions wrap around
12 | R1 = U64_MAX
13 | R1 = add R1 1 // result is 0
14 |
15 | R2 = I64_MAX
16 | R2 = add R2 1 // result is I64_MIN
17 |
18 | // overflow from "s" (signed) instructions panics
19 | R3 = I64_MAX
20 | R3 = adds R3 1
21 | // we won't get here
22 | }
23 |
--------------------------------------------------------------------------------
/.logbook/2022-09-08.md:
--------------------------------------------------------------------------------
1 | Arrived to a cabin by a lake in Michigan yesterday. It's really nice and warm here. I won't be working much while here but felt compelled to make some progress on the virtual memory page allocator.
2 |
3 | I've been thinking about using existing procedures for allocation, like the heap I wrote for the generic allocator or the buddy allocator used by the memory manager (for managing host memory.) It took me a while to realize that neither of those would work here since the vm_map_add and del functions would need to manage a separate structure (heap_t or buddy structure.) Plus, the vm_map implementation needs to support allocation of arbitrary number of pages; the buddy allocator is limited to allocating ranges that are power of two in size. Anyhow, I decided to implement `vm_map_findspace` tailored to vm_map.
4 |
5 |
--------------------------------------------------------------------------------
/etc/wasm-rt.html:
--------------------------------------------------------------------------------
1 |
2 | rsm
3 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/etc/website/_template.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ page.title.startsWith("RSM") ? page.title : page.title + " • RSM" }}
6 |
7 |
8 |
9 |
10 |
11 |
13 |
14 |
15 |
16 | {{! page.body }}
17 |
18 |
19 |
--------------------------------------------------------------------------------
/test.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | cd "$(dirname "$0")"
4 |
5 | OUTDIR=out/test-$(uname -s)-$(uname -m)
6 | RSM=$OUTDIR/rsm
7 |
8 | ./build.sh -out="$OUTDIR" "$@"
9 |
10 | for srcfile in examples/*.rsm; do
11 | echo "——————————————————————————————————————————————————————————————————————————"
12 | FILENAME=${srcfile##*/}
13 |
14 | # skip files matching "fail-*.rsm"
15 | case "$FILENAME" in
16 | fail-*) continue ;;
17 | esac
18 |
19 | # skip source files containing "//!exe2-only"
20 | grep -qE "^\/\/\!exe2-only" "$srcfile" && continue
21 |
22 | ROM=$OUTDIR/test_${FILENAME%*.rsm}.rom
23 |
24 | echo "rsm -o '$ROM' '$srcfile'"
25 | $RSM -o "$ROM" "$srcfile"
26 |
27 | # Note: null stdin to avoid read on stdin blocking the test script
28 | echo "rsm -R0=3 '$ROM'"
29 | $RSM -R0=3 "$ROM" "$TMPFILE"
8 |
9 | TITLE=
10 | while IFS= read -r line; do
11 | case "$line" in
12 | "##"*) TITLE=$(echo ${line:2}) ;;
13 | *)
14 | if [ -n "$TITLE" ]; then
15 | YTID=$line
16 | SHORTTITLE=ep$(echo "$TITLE" | sed -E 's/[^0-9]+//g')
17 | SUBFILE=$SHORTTITLE.vtt
18 | if ! [ -f "$SUBFILE" ]; then
19 | youtube-dl --sub-lang en --write-auto-sub --skip-download --id \
20 | https://www.youtube.com/watch\?v\=$YTID
21 | mv $YTID.en.vtt $SUBFILE || echo "no subs for $TITLE -- (try again later)"
22 | fi
23 | [ "$SUBFILE" -nt "$SHORTTITLE.html" ] &&
24 | node _gentranscript.js "$SUBFILE" "$TITLE" "$SHORTTITLE.html"
25 | TITLE= # skip extra/additional videos
26 | fi
27 | ;;
28 | esac
29 | done < "$TMPFILE"
30 |
31 | # youtube-dl --sub-lang en --write-auto-sub --skip-download https://www.youtube.com/watch\?v\=A9KtyRzk40Q
32 |
--------------------------------------------------------------------------------
/.logbook/2022-09-09.md:
--------------------------------------------------------------------------------
1 | Found a little time this morning to tinker with `vm_map_findspace`.
2 | Trying a (potentially effective?) trick for minimizing misses when starting the search by tracking the minimum VFN that's free, in `vm_map_t.min_free_vfn`. When `vm_map_findspace` starts a search, it adjusts the desired minimum vaddr to min_free_vfn if needed and starts the search there.
3 |
4 | A realization about page table size "classes": Since the vm_map is constructed from a hierarchy of page tables (currently four levels, 6 bits of address per level), I can skip certain levels and focus on others depending on the number of pages requested for allocation. For example, if 800 pages are requested I know that an L4 page table won't be enough, so any L4 table that is not _completely empty_ can be skipped. (This algorithm is quite similar to `bits_set_range` and `bitset_find_unset_range`.)
5 |
6 | Each page table level has an increment of VM_PTAB_BITS (6 bits, currently) capacity
7 | - L1_size 68719476736 pages
8 | - L2_size 134217728 pages
9 | - L3_size 262144 pages
10 | - L4_size 512 pages
11 |
12 | I.e.
13 |
14 | L4_size = VM_PTAB_LEN;
15 | L3_size = L4_size << VM_PTAB_BITS;
16 | L2_size = L3_size << VM_PTAB_BITS;
17 | L1_size = L2_size << VM_PTAB_BITS; // root
18 |
19 |
--------------------------------------------------------------------------------
/etc/sublime-text/rsm/Comments.tmPreferences:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | name
5 | Comments
6 | scope
7 | source.rsm
8 | settings
9 |
10 | shellVariables
11 |
12 |
13 | name
14 | TM_COMMENT_START
15 | value
16 | //
17 |
18 |
19 | name
20 | TM_COMMENT_DISABLE_INDENT
21 | value
22 | no
23 |
24 |
25 | name
26 | TM_LINE_TERMINATOR
27 | value
28 | ;
29 |
30 |
31 | name
32 | TM_COMMENT_START_2
33 | value
34 | /*
35 |
36 |
37 | name
38 | TM_COMMENT_END_2
39 | value
40 | */
41 |
42 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/.logbook/2022-08-31.md:
--------------------------------------------------------------------------------
1 | Put together a tool for keeping a logbook.
2 | Since I'm a very creative person I named it "logbook".
3 |
4 | But really, today is about getting RSM building on Linux with GCC.
5 | It turns out there are a lot of things that GCC doesn't like but
6 | which clang is happy with. For example, a CPP macro that includes
7 | `__has_builtin` will cause GCC to cry a little so I'm unboxing HAS_LIBC_BUILTIN.
8 |
9 | It's been a few hours now and it finally builds without any warnings in GCC 11.
10 |
11 | Interestingly, GCC defines uint64_t as unsigned long on x86_64 and
12 | sizeof(long long) == sizeof(long). Clang does what I would expect and defines
13 | uint64_t as unsigned long long. Very odd. This GCC weirdness meant I had to change
14 | A TON of printf-like calls that use %ll for u64 values. Instead of the madness that
15 | is PRIu64, I ended up with typedef unsigned long long ull_t (and ill_t) which are
16 | used in typecasts for a u64 value when it is printed.
17 |
18 | Thought: Maybe define i64 as long long instead.
19 | It would require some tricky stuff for the API header rsm.h which uses stdint
20 | uint64_t types. I.e. without changing all uint64_t types in rsm.h to something like
21 | rsm_u64_t, its ABI would be incorrect; i.e. on GCC the prototype would say "long"
22 | but the implementation would use "long long."
23 |
24 | A nut to crack on another day.
25 |
--------------------------------------------------------------------------------
/.logbook/2022-09-17.md:
--------------------------------------------------------------------------------
1 | Fixed the bug in vm_map_iter mentioned yesterday. It was pretty trivial (just pass along the parent VFN; the offset.)
2 |
3 | Most of the morning was spent on `vm_map_del`, the function used to remove, or "unmap" a range of virtual addresses. Using the new iterator API, I was able to write a pretty small and relatively elegant implementation.
4 |
5 | However in doing so I discovered what I thought was a bug in rmm, the memory manager (host page allocator.) I spent the rest of the day on this, until 18:30! Ended up realizing I simply had a logic issue in vm_map: when freeing a mapping that had backing pages assigned, it would (incorrectly) be freed back to the rmm. This works for single backing pages allocated on demand, but breaks the expectations of `rmm_freepages` when a vm_map mapping was made with explicit backing host pages, in fact, with _more than one_ explicit backing host page.
6 |
7 | The fix was to introduce a "purgeable" flag for vm_map PTEs, a flag set for pages which have backing pages assigned on demand. The flag is not set when mapping with a explicit backing host pages, as the caller of such a mapping is expected to manage those backing host pages.
8 |
9 | To prevent this from happening again I added a `npages` argument to `rmm_freepages` and logic to detect "over free" and "under free"; attempts to free a block of pages that is larger or smaller than the actual block.
10 |
--------------------------------------------------------------------------------
/examples/stack-growth.rsm:
--------------------------------------------------------------------------------
1 | //!exe2-only (requires exe engine v2)
2 | //
3 | // This demonstrates automatic stack growth.
4 | // Using the stkmem instruction instead of directly incrementing
5 | // and decrementing SP prevents stack overflow.
6 | //
7 | // stkmem +N allocates N bytes of stack space (decrements SP by N.)
8 | // If the stack is not large enough for N more bytes,
9 | // the following steps are taken:
10 | // 1. Attempt to grow the current stack by mapping enough extra
11 | // memory pages "above" (at lower address) the current stack.
12 | // If that memory is in use, allocate any region of memory
13 | // large enough for the requested stack space and "split" the
14 | // stack: essentially forms a linked list of stacks.
15 | // 3. Finally, update SP to point to the (potentially new) stack
16 | // offset by N.
17 | //
18 | // stkmem -N deallocates N bytes of stack space (increments SP by N.)
19 | // If SP drops to a link to a split stack, the current stack is freed
20 | // and SP is set to the linked "previous" stack.
21 | //
22 | // In this example we allocate 2MB which is guaranteed to push SP
23 | // beyond the initial stack capacity, which is 1MB.
24 |
25 | fun main() {
26 | stkmem 2097152 // allocate 2MB of stack memory
27 |
28 | // store to the top of the stack
29 | R1 = 0xdeadbeefface
30 | store R1 SP -8
31 |
32 | stkmem -2097152 // deallocate 2MB of stack memory
33 | }
34 |
--------------------------------------------------------------------------------
/src/machine.c:
--------------------------------------------------------------------------------
1 | // virtual machine
2 | // SPDX-License-Identifier: Apache-2.0
3 | #include "rsmimpl.h"
4 | #include "machine.h"
5 |
6 |
7 | rmachine_t* nullable rmachine_create(rmm_t* mm) {
8 | rmemalloc_t* malloc = rmem_allocator_create(mm, 4 * MiB);
9 | if (!malloc)
10 | return NULL;
11 |
12 | // note that we must allocate pages in pow2 orders
13 | usize npages = CEIL_POW2((sizeof(rmachine_t) + PAGE_SIZE-1) / PAGE_SIZE);
14 | u16 tailmemcap = (u16)(npages * PAGE_SIZE) - sizeof(rmachine_t);
15 |
16 | rmachine_t* m = rmm_allocpages(mm, npages);
17 | if UNLIKELY(!m) {
18 | log("rmm_allocpages(%zu) failed", npages);
19 | goto error;
20 | }
21 |
22 | dlog("%s allocating %zu pages " RMEM_FMT " rmachine_t=%zu",
23 | __FUNCTION__, npages, RMEM_FMT_ARGS(RMEM(m, npages*PAGE_SIZE)), sizeof(rmachine_t));
24 |
25 | m->mm = mm;
26 | m->malloc = malloc;
27 | m->tailmemcap = tailmemcap;
28 |
29 | rerr_t err = rsched_init(&m->sched, m);
30 | if UNLIKELY(err) {
31 | log("rsched_init failed: %s", rerr_str(err));
32 | goto error;
33 | }
34 |
35 | return m;
36 |
37 | error:
38 | rmem_allocator_free(malloc);
39 | if (m)
40 | rmm_freepages(mm, m, npages);
41 | return NULL;
42 | }
43 |
44 |
45 | rerr_t rmachine_execrom(rmachine_t* m, rrom_t* rom) {
46 | return rsched_execrom(&m->sched, rom);
47 | }
48 |
49 |
50 | void rmachine_dispose(rmachine_t* m) {
51 | rsched_dispose(&m->sched);
52 | rmem_allocator_free(m->malloc);
53 | }
54 |
55 |
--------------------------------------------------------------------------------
/examples/askname.rsm:
--------------------------------------------------------------------------------
1 | // This program asks for your name on stdin and then greets you
2 | const STDIN = 0
3 | const STDOUT = 1
4 | const STKSIZE = 64 // stack space (must be a multiple of 8)
5 |
6 | data ask_msg = "What is your name?\n"
7 | data readerr_msg = "Failed to read stdin\n"
8 | data no_name_msg = "I see, you don't want to tell me.\n"
9 | data reply_msg1 = "Hello "
10 | data reply_msg2 = "! Nice to meet ya\n"
11 |
12 | fun main(i32) {
13 | // reserve STKSIZE bytes of stack space
14 | SP = sub SP STKSIZE
15 |
16 | // ask the user for their name
17 | call print ask_msg 19
18 | if R0 end
19 |
20 | // read up to STKSIZE bytes from STDIN, to stack memory at SP
21 | R0 = STKSIZE // number of bytes to read
22 | R8 = read SP R0 STDIN
23 | R0 = lts R8 0 // check if read failed ()
24 | if R0 readerr
25 | R8 = R8 - 1 // exclude any line break
26 | R0 = lts R8 1 // check if read was empty
27 | if R0 noname
28 |
29 | // reply with the user's name
30 | call print reply_msg1 6
31 | call print SP R8 // SP = address of name read
32 | call print reply_msg2 18
33 | jump end
34 |
35 | noname:
36 | call print no_name_msg 34
37 | jump end
38 | readerr:
39 | // note: To test this, use "0<&-" in csh, e.g. out/rsm askname.rsm 0<&-
40 | call print readerr_msg 21
41 | end:
42 | SP = add SP STKSIZE // restore stack pointer
43 | }
44 |
45 | fun print(addr i64, size i64) ok i1 {
46 | R0 = write R0 R1 STDOUT
47 | R0 = lts R0 R1 // return 1 on failed or short write
48 | }
49 |
--------------------------------------------------------------------------------
/etc/website/_template-op.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ page.title }} • RSM instructions
6 |
7 |
8 |
9 |
10 |
11 |
13 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | {{title}}
30 |
31 | {{! page.body }}
32 |
33 |
34 |
35 |
36 |
37 | {{!
38 | for (let op of site.rsm_ops) {
39 | let clsattr = op.name == page.title ? 'class="selected"' : ''
40 | print(`${op.name}
`)
41 | }
42 | }}
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/.logbook/2022-09-05.md:
--------------------------------------------------------------------------------
1 | I'm traveling for about a week, currently in Chicago and poked at stack splitting on the airplane; when SP reaches the end of the allocated stack memory, grow the stack by allocating another chunk, saving the current stack range to it (so it can be restored later) and then setting SP to the new stack.
2 |
3 | I got the weirdest crashes. Head scratcher! After a little while I gave up, put my computer away and listened to some music.
4 |
5 | Yesterday at the hotel I gave it another go. I only had a short while to poke at it and didn't make any progress.
6 |
7 | Today I found some more time and opened up the laptop. Discovered I had made a really dumb mistake—which to my defence I thought my compilter would catch—where I was accessing memory outside the array of vm caches.
8 |
9 | Each scheduler M has a set of virtual memory-lookup caches:
10 |
11 | struct M {
12 | vm_cache_t vmcache[3]
13 | }
14 |
15 | There are three caches for each unique combination of permissions: read-only, write-only and read+write. The vm_perm_t constants for these are 1, 2 and 3 (not 0, 1 and 2!) So, what happened was this:
16 |
17 | vm_cache_t vmcache = vmcache[3] // 3 = VM_PERM_RW
18 |
19 | I'm surprised Clang didn't catch this, since the array is explicitly sized to 3.
20 | Anyhow, I added a function for accessing M's vmcache field:
21 |
22 | inline static vm_cache_t* m_vm_cache(M* m, vm_perm_t perm) {
23 | assertf(perm > 0 && (perm-1) < countof(m->vmcache), "%u", (u32)perm);
24 | return &m->vmcache[perm-1];
25 | }
26 |
27 | Now, the stack splitting code works. (`stkmem_*` in sched_exec.c) Nice!
28 |
--------------------------------------------------------------------------------
/etc/exe-v1-memory.txt:
--------------------------------------------------------------------------------
1 | > This is a legacy document, for execution engine v1
2 |
3 | Runtime memory layout
4 |
5 | Linear model like WASM where vm code does not live in runtime memory.
6 | Simple and easy to understand. Coroutines would have to allocate their
7 | stacks on the heap since the stack area can't grow. However growing the
8 | heap is trivial (just allow higher addresses.)
9 |
10 | 0x0 datasize heapbase
11 | ┌─────────────┼─────────────┼───────────···
12 | │ data │ ← stack │ heap →
13 | └─────────────┴─────────────┴───────────···
14 |
15 | If we want to use memory-mapped I/O for stuff like devices, those mapped
16 | things would go first so that their addresses are nice and short.
17 |
18 | 0x0 0xffff datasize heapbase
19 | ┌─────────────┼─────────────┼─────────────┼───────────···
20 | │ I/O map │ data │ ← stack │ heap →
21 | └─────────────┴─────────────┴─────────────┴───────────···
22 |
23 | If we have virtual memory, a page table, we could use a layout like this
24 | where the stack and heap are allocated in far-distance pages. But this would
25 | make porting RSM to other platforms harder since you'd need a page table.
26 | Growing the stack is possible in this model either simply by convention,
27 | or a mprotect'ed guard page separating the stack and heap, on systems that
28 | support it.
29 |
30 | 0x0 heapbase 0xFFFFFFFF
31 | ┌─────────────┼────────── ··· ─────────────┤
32 | │ data │ heap → ··· ← stack │
33 | └─────────────┴────────── ··· ─────────────┘
34 |
35 |
--------------------------------------------------------------------------------
/examples/test-memload.rsm:
--------------------------------------------------------------------------------
1 | // this tests memory loads -- returns 0 on success
2 | fun main() {
3 | // set all bits in R8 (0xffffffffffffffff)
4 | // note: we avoid using data constants to reduce the test surface
5 | R8 = 0 ; R8 = binv R8
6 |
7 | // i64
8 | R1 = R8
9 | store R1 R31 -8 // store 0xffffffffffffffff to stack
10 | load R5 R31 -8 // R5 should be 0xffffffffffffffff
11 | R0 = neq R5 R1 ; if R0 fail
12 | load4u R5 R31 -8 // R5 should be 0x00000000ffffffff
13 | R1 = R1 >> 32 ; R0 = neq R5 R1 ; if R0 fail
14 | load2u R5 R31 -8 // R5 should be 0x000000000000ffff
15 | R1 = 0xffff ; R0 = neq R5 R1 ; if R0 fail
16 | load1u R5 R31 -8 // R5 should be 0x00000000000000ff
17 | R1 = 0xff ; R0 = neq R5 R1 ; if R0 fail
18 |
19 | // i32
20 | R1 = R8 >> 32 // 0xffffffff
21 | store4 R1 R31 -4 // store 0xffffffff to stack
22 | load4u R5 R31 -4 // R5 should be 0xffffffff
23 | R0 = neq R5 R1 ; if R0 fail
24 | load2u R5 R31 -4 // R5 should be 0x0000ffff
25 | R1 = 0xffff ; R0 = neq R5 R1 ; if R0 fail
26 | load1u R5 R31 -4 // R5 should be 0x000000ff
27 | R1 = 0xff ; R0 = neq R5 R1 ; if R0 fail
28 |
29 | // i16
30 | R1 = 0xffff
31 | store2 R1 R31 -2 // store 0xffff to stack
32 | load2u R5 R31 -2 // R5 should be 0xffff
33 | R0 = neq R5 R1 ; if R0 fail
34 | load1u R5 R31 -2 // R5 should be 0x00ff
35 | R1 = 0xff ; R0 = neq R5 R1 ; if R0 fail
36 |
37 | // i8
38 | R1 = 0xff
39 | store1 R1 R31 -1 // store 0xff to stack
40 | load1u R5 R31 -1 // R5 should be 0xff
41 | R0 = neq R5 R1 ; if R0 fail
42 |
43 | R0 = 0
44 | ret
45 | fail:
46 | R0 = 0xbad
47 | }
48 |
--------------------------------------------------------------------------------
/.github/workflows/pages.yml:
--------------------------------------------------------------------------------
1 | name: Deploy website to GitHub Pages
2 |
3 | on:
4 | # Runs on pushes targeting the default branch
5 | push:
6 | branches: ["main"]
7 | paths:
8 | - "etc/website/**"
9 | - src/rsm.h
10 |
11 | # Allows you to run this workflow manually from the Actions tab
12 | workflow_dispatch:
13 |
14 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
15 | permissions:
16 | contents: read
17 | pages: write
18 | id-token: write
19 |
20 | # Allow one concurrent deployment
21 | concurrency:
22 | group: "pages"
23 | cancel-in-progress: true
24 |
25 | # Default to bash
26 | defaults:
27 | run:
28 | shell: bash
29 |
30 | jobs:
31 | deploy:
32 | environment:
33 | name: github-pages
34 | url: ${{ steps.deployment.outputs.page_url }}
35 | runs-on: ubuntu-latest
36 | steps:
37 | - name: Install NodeJS
38 | uses: actions/setup-node@v3
39 | with:
40 | node-version: "16"
41 | cache: ${{ steps.detect-package-manager.outputs.manager }}
42 | - name: Checkout Source
43 | uses: actions/checkout@v3
44 | with:
45 | submodules: recursive
46 | - name: Setup Pages
47 | id: pages
48 | uses: actions/configure-pages@v2
49 | - name: Build website
50 | run: bash etc/website/build.sh -baseurl /rsm/ -opt -verbose
51 | - name: Upload artifact
52 | uses: actions/upload-pages-artifact@v1
53 | with:
54 | path: ./etc/website/_site
55 | - name: Deploy to GitHub Pages
56 | id: deployment
57 | uses: actions/deploy-pages@v1
58 |
--------------------------------------------------------------------------------
/src/hash.h:
--------------------------------------------------------------------------------
1 | // hash functions and PRNG
2 | // SPDX-License-Identifier: Apache-2.0
3 | #pragma once
4 | RSM_ASSUME_NONNULL_BEGIN
5 |
6 | // fastrand updates the PRNG and returns the next "random" number
7 | u32 fastrand();
8 | void fastrand_seed(u64 seed); // (re)sets the seed of fastrand
9 |
10 | // hash_t is the storage type for hash functions
11 | #if defined(__wasm__)
12 | typedef u64 hash_t;
13 | #define HASHCODE_MAX U64_MAX
14 | #else
15 | typedef usize hash_t;
16 | #define HASHCODE_MAX USIZE_MAX
17 | #endif
18 |
19 | // hash computes a hash code for data p of size bytes length
20 | static hash_t hash(const void* p, usize size, hash_t seed);
21 | hash_t hash_2(const void* p, hash_t seed); // 2 bytes (eg. i16, u16)
22 | hash_t hash_4(const void* p, hash_t seed); // 4 bytes (eg. i32, u32)
23 | hash_t hash_8(const void* p, hash_t seed); // 8 bytes (eg. i64, u64)
24 | hash_t hash_f32(const f32* p, hash_t seed); // f32, supports ±0 and NaN
25 | hash_t hash_f64(const f64* p, hash_t seed); // f64, supports ±0 and NaN
26 | hash_t hash_mem(const void* p, usize size, hash_t seed); // size bytes at p
27 | inline static hash_t hash(const void* p, usize size, hash_t seed) {
28 | switch (size) {
29 | case 2: return hash_2(p, seed);
30 | case 4: return hash_4(p, seed);
31 | case 8: return hash_8(p, seed);
32 | default: return hash_mem(p, size, seed);
33 | }
34 | }
35 |
36 | // uintptr hash_ptr(const void* p, uintptr seed)
37 | // Must be a macro rather than inline function so that we can take its address.
38 | #if UINTPTR_MAX >= 0xFFFFFFFFFFFFFFFFu
39 | #define hash_ptr hash_8
40 | #else
41 | #define hash_ptr hash_4
42 | #endif
43 |
44 | RSM_ASSUME_NONNULL_END
45 |
--------------------------------------------------------------------------------
/etc/vm-store-example-x86_64.s:
--------------------------------------------------------------------------------
1 | // calling VM_STORE(u32, cache, pagedir, vaddr, value)
2 | // See https://godbolt.org/z/x11EKveod
3 | //
4 | // inline static uintptr vm_translate(
5 | // vm_cache_t* cache [rdi],
6 | // vm_pagedir_t* pagedir [rsi],
7 | // u64 vaddr [rdx],
8 | // u64 align,
9 | // vm_op_t op)
10 | // {
11 | mov rbx, rdx // copy vaddr to long-lived (caller-owned) register rbx
12 | // u64 index = ((u64)(vaddr) >> PAGE_SIZE_BITS) & VM_CACHE_INDEX_VFN_MASK;
13 | mov eax, ebx // copy most significant 32 bits of vaddr to eax (index)
14 | shr eax, 12 // index = index >> PAGE_SIZE_BITS
15 | movzx eax, al // copy bottom 8 bits of index and zero extend the value
16 | //
17 | // u64 tag = vaddr & (VM_ADDR_PAGE_MASK ^ (align - 1llu));
18 | mov rcx, rdx // copy vaddr to rcx
19 | and rcx, -4093
20 | //
21 | // return ( // get host page address
22 | // UNLIKELY( cache->entries[index].tag != tag ) ?
23 | shl rax, 4
24 | cmp qword ptr [rdi + rax + 8], rcx
25 | jne .cache_miss
26 | //
27 | // _vm_cache_miss(cache, pagedir, vaddr, op) :
28 | //
29 | // cache->entries[index].haddr
30 | mov rax, qword ptr [rdi + rax] // load cache entry's haddr value to rax
31 | .b1:
32 | //
33 | // ) + VM_ADDR_OFFSET(vaddr);
34 | and ebx, 4095 // offset = vaddr & VM_ADDR_OFFS_MASK
35 | // }
36 |
37 | // VM_STORE
38 | // *(u32*)haddr = v;
39 | mov dword ptr [rax + rbx], ebp // store 4B from stack (ebp) to haddr+offset
40 |
41 | // _vm_cache_miss(cache, pagedir, vaddr, op) :
42 | .cache_miss:
43 | mov rdx, rbx // copy address offset to rdx
44 | mov ecx, 4
45 | call _vm_cache_miss
46 | jmp .b1
47 |
48 |
--------------------------------------------------------------------------------
/examples/fib.rsm:
--------------------------------------------------------------------------------
1 | // prints the first N Fibonacci numbers
2 | // E.g. rsm -R0=10 fib.rsm => 1 1 2 3 5 8 13 21 34 55
3 | // Pseudo-code to explain the implementation:
4 | // fun fib(n i64) i64 {
5 | // var curr, prev u64 = 0, 0
6 | // while (n--)
7 | // const value = curr + prev
8 | // prev = value ? curr : 1
9 | // curr = value
10 | // println_u64(curr)
11 | // curr
12 | // }
13 | //
14 | fun fib(n i64) i64 {
15 | // Use caller-owned registers to avoid saving around call
16 | ifz R0 end // return 0 if n == 0
17 | R19 = R0 // n
18 | R20 = 0 // curr
19 | R21 = 1 // prev
20 | loop:
21 | R0 = R20 + R21 // value = curr + prev
22 | R1 = ! R0 // tmp = !value
23 | R21 = R20 + R1 // prev = value ? curr : 1
24 | R20 = R0 // curr = value
25 | call println_u64 R20
26 | R19 = R19 - 1 // n--
27 | if R19 loop // loop if n > 0
28 | R0 = R20
29 | end:
30 | ret
31 | }
32 |
33 | // print_u64 writes a u64 as decimal number and a linebreak.
34 | // returns 0 on success, 1 on failed or short write.
35 | fun println_u64(value i64) ok i1 {
36 | const STDOUT = 1
37 | const ZERO_CHAR = 0x30
38 | const LF_CHAR = 0x0A
39 | // max u64 decimal number is 20 bytes long "18446744073709551615"
40 | SP = sub SP 24 // reserve 24B of stack space (8B aligned)
41 | R1 = SP + 23 // define string storage memory address
42 | R2 = LF_CHAR // load address of line feed character
43 | store1 R2 R1 0 // store " " (space) to end of string
44 | loop:
45 | R1 = R1 - 1 // decrement storage address
46 | R2 = R0 % 10 ; R2 = R2 + ZERO_CHAR // ch = '0' + (value % 10)
47 | store1 R2 R1 0 // store byte R2 at address R1
48 | R0 = R0 / 10 // value = value / 10
49 | if R0 loop // loop if there's another digit to print
50 | // end
51 | R2 = SP + 24 ; R2 = R2 - R1 // count bytes (start - end)
52 | R0 = write R1 R2 STDOUT
53 | SP = add SP 24 // restore stack pointer
54 | }
55 |
--------------------------------------------------------------------------------
/etc/wasm.html:
--------------------------------------------------------------------------------
1 |
2 | rsm
3 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/sched_os.c:
--------------------------------------------------------------------------------
1 | // scheduler:
2 | // SPDX-License-Identifier: Apache-2.0
3 | #include "rsmimpl.h"
4 | #include "sched.h"
5 |
6 |
7 | // TODO: different thread APIs
8 | #if !defined(RSM_NO_LIBC)
9 | #include
10 | #include
11 | #include
12 | // #elif !__STDC_NO_THREADS__
13 | // #include
14 | #else
15 | #error TODO
16 | #endif
17 |
18 |
19 | uintptr m_spawn_osthread(M* m, rerr_t(*mainf)(M*)) {
20 | pthread_attr_t attr;
21 | int err = pthread_attr_init(&attr);
22 | if (err != 0) {
23 | dlog("pthread_attr_init failed (err %d)", err);
24 | return 0;
25 | }
26 |
27 | // // find out OS stack size
28 | // uintptr stacksize = 0;
29 | // if ((err = pthread_attr_getstacksize(&attr, &stacksize)) != 0) {
30 | // dlog("pthread_attr_getstacksize failed (err %d)", err);
31 | // goto error;
32 | // }
33 | // dlog("OS thread stack size: %zu B", stacksize);
34 | // //m->t0.stack.hi = stacksize; // for m_start
35 |
36 | // Tell the pthread library we won't join with this thread and that
37 | // the system should reclaim the thread storage upon exit.
38 | if ((err = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) != 0) {
39 | dlog("pthread_attr_setdetachstate failed (err %d)", err);
40 | goto error;
41 | }
42 |
43 | // disable signal delivery of all signals while we create the thread
44 | sigset_t sigset_prev;
45 | sigset_t sigset_all; // saved signal mask
46 | memset(&sigset_all, 0xff, sizeof(sigset_t));
47 | if (sigprocmask(SIG_SETMASK, &sigset_all, &sigset_prev) != 0) {
48 | dlog("sigprocmask failed (errno %d)", errno);
49 | goto error;
50 | }
51 |
52 | // create the thread, executing mainf
53 | DIAGNOSTIC_IGNORE_PUSH("-Wcast-function-type")
54 | pthread_t t;
55 | err = pthread_create(&t, &attr, (void*nullable(*_Nonnull)(void*nullable))mainf, m);
56 | DIAGNOSTIC_IGNORE_POP()
57 |
58 | // restore signal mask
59 | sigprocmask(SIG_SETMASK, &sigset_prev, NULL);
60 |
61 | if (err != 0) {
62 | dlog("pthread_create failed (err %d)", err);
63 | goto error;
64 | }
65 |
66 | return (uintptr)t;
67 |
68 | error:
69 | pthread_attr_destroy(&attr);
70 | return 0;
71 | }
72 |
--------------------------------------------------------------------------------
/.logbook/2022-09-10.md:
--------------------------------------------------------------------------------
1 | Tried to make progress on `vm_map_findspace` but quickly realized my approach--based on `vm_map_add` and `vm_map_access` is just not right. It will become quite complex this way, I think. I might even want to change `vm_map_add` (and "del") to whatever I come up with for findspace. I'm on vacation. This is too much thinking.
2 |
3 | Instead, I made this nice illustration for the vm documentation:
4 |
5 | Page table B-tree illustration
6 |
7 | The page table data structure is essentially a B-tree, indexed by VFN (page address.)
8 | Each page table entry holds a pointer to the next table or leaf along with metadata.
9 | In this diagram, VFNs 0x1, 0x200 and 0x8000000 are mapped to a host page.
10 | I.e. addresses 0x1000, 0x200000 and 0x8000000000.
11 |
12 | ┌───────────┬───────────┬─────┬───────────┬───────────┐
13 | root │ 000000000 │ 008000000 │ ··· │ ff0000000 │ ff8000000 │
14 | └─────●─────┴─────●─────┴─────┴─────◯─────┴─────◯─────┘
15 | ┏━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━┓
16 | ┡━━━━━━━━━┯━━━━━━━━━┯━━━━━┯━━━━━━━━━┑ ┡━━━━━━━━━┯━━━━━━━━━┯━━━━━┯━━━━━━━━━┑
17 | │ 0000000 │ 0040000 │ ··· │ 7fc0000 │ │ 8000000 │ 8040000 │ ··· │ ffc0000 │ ···
18 | └────●────┴────◯────┴─────┴────◯────┘ └────●────┴────◯────┴─────┴────◯────┘
19 | ┏━━━━━━━┛ ┏━━━━━━┛
20 | ┡━━━━━━━┯━━━━━━━┯━━━━━┯━━━━━━━┑ ┡━━━━━━━━━┯━━━━━┯━━━━━━━━━┑
21 | │ 00000 │ 00200 │ ··· │ 3fe00 │ │ 8000000 │ ··· │ 803fe00 │ ···
22 | └───●───┴───●───┴─────┴───◯───┘ └────●────┴─────┴────◯────┘
23 | ┏━━━━━━┛ ┗━━━━━━━━━━┓ ┗━━━━━━┓
24 | ┡━━━━━┯━━━━━┯━━━━━┯━━━━━┑ ┡━━━━━┯━━━━━┯━━━━━┯━━━━━┑ ┡━━━━━━━━━┯━━━━━┯━━━━━━━━━┑
25 | │ 000 │ 001 │ ··· │ 1ff │ │ 200 │ 201 │ ··· │ 3ff │ │ 8000000 │ ··· │ 80001ff │ ···
26 | └──◯──┴──●──┴─────┴──◯──┘ └──●──┴──◯──┴─────┴──◯──┘ └────●────┴─────┴────◯────┘
27 | ┌──┸────────┐ ┌──┸────────┐ ┌──┸────────┐
28 | │ host page │ │ host page │ │ host page │
29 | └───────────┘ └───────────┘ └───────────┘
30 |
31 | Empty cirlce "◯" signifies a NULL pointer, meaning that entry is not mapped.
32 |
33 |
--------------------------------------------------------------------------------
/src/bits.h:
--------------------------------------------------------------------------------
1 | // operations on bits
2 | // SPDX-License-Identifier: Apache-2.0
3 | #pragma once
4 | RSM_ASSUME_NONNULL_BEGIN
5 |
6 |
7 | inline static bool bit_get(const void* bits, usize bit) {
8 | return !!( ((u8*)bits)[bit / 8] & (1lu << (bit % 8)) );
9 | }
10 |
11 | inline static void bit_set(void* bits, usize bit) {
12 | ((u8*)bits)[bit / 8] |= (1 << (bit % 8));
13 | }
14 |
15 | inline static void bit_clear(void* bits, usize bit) {
16 | ((u8*)bits)[bit / 8] &= ~(1 << (bit % 8));
17 | }
18 |
19 | void bits_set_range(u8* bits, usize start, usize len, bool on);
20 |
21 | // set of bits
22 | typedef struct {
23 | u8* data;
24 | usize len; // number of bits at data
25 | } bitset_t;
26 |
27 |
28 | inline static void bitset_init(bitset_t* bset, u8* data, usize len) {
29 | bset->data = data;
30 | bset->len = len;
31 | }
32 |
33 | inline static bool bitset_get(const bitset_t bset, usize index) {
34 | assert(index < bset.len);
35 | return bit_get(bset.data, index);
36 | }
37 |
38 | inline static void bitset_set_range(bitset_t bset, usize start, usize len, bool on) {
39 | assertf(start + len <= bset.len,
40 | "%zu + %zu = %zu, bset->len = %zu", start, len, start + len, bset.len);
41 | bits_set_range(bset.data, start, len, on);
42 | }
43 |
44 | // bitset_find_unset_range searches for a contiguous region of unset bits.
45 | // start: #bit to start the search at.
46 | // minlen: minimum number of unset bytes needed. Must be >0.
47 | // maxlen: maximum number of unset bytes to consider. Must be >0.
48 | // stride: how many buckets to advance during scanning (aka alignment). Must be >0.
49 | // Returns >=minlen if a range was found (and updates startp.)
50 | // Returns 0 if no range large enough was found (may still update startp.)
51 | usize bitset_find_unset_range(
52 | bitset_t bset, usize* startp, usize minlen, usize maxlen, usize stride);
53 |
54 | // bitset_find_best_fit searches for the smallest hole that is >=minlen large
55 | usize bitset_find_best_fit(bitset_t bset, usize* startp, usize minlen, usize stride);
56 |
57 | // bitset_find_first_fit searches for the first hole that is >=minlen large
58 | inline static usize bitset_find_first_fit(
59 | bitset_t bset, usize* startp, usize minlen, usize stride)
60 | {
61 | return bitset_find_unset_range(bset, startp, minlen, minlen, stride);
62 | }
63 |
64 | RSM_ASSUME_NONNULL_END
65 |
--------------------------------------------------------------------------------
/etc/constants.txt:
--------------------------------------------------------------------------------
1 |
2 | Constants are immutable chunks of data bundled with a program.
3 |
4 | A constant is loaded into a register using a LOAD instruction:
5 | LOADI R(A) = I(B) -- load immediate constant
6 | LOADK R(A) = K(B) -- load constant from address ⟵ this is the tricky one
7 |
8 | Unfortunately B is not very large when the A argument is used, only 19 bits
9 | which limits the sum of constant data to 524 kB.
10 |
11 | So how do we increase the addressable size of constant data? Ideas:
12 | 1. Be fine with the 524 kB limit
13 | 2. Use some fancy compression (like two bits for log2 alignment)
14 | 3. Use an implicit reg instead of naming one in A; B is now 24 bits (16 MB)
15 | 4. Lookup table, address constant by index
16 |
17 | Packing the constant data: order ascendingly by alignment.
18 |
19 | Thought playground:
20 |
21 | ┌────────────┬───────────┬──────────┬───────────────┐
22 | │ Name │ Alignment │ K(n) │ Length×T │
23 | ├────────────┼───────────┼──────────┼───────────────┤
24 | │ hello │ 1 │ 0 │ 7×i8 │
25 | │ world │ 1 │ 7 │ 7×i8 │
26 | │ idlist │ 4 │ 10 │ 5×i32 │
27 | │ times │ 8 │ 28 │ 2×i64 │
28 | │ ipv6addr │ 16 │ 40 │ 2×i128 │
29 | └────────────┴───────────┴──────────┴───────────────┘
30 |
31 | 0 1 2 3 4 5 6 7 8 9 a b c d e f
32 | ┌─────────────────────────────────────────────────
33 | 0000 │ h e l l o \0 w o r l d \n \0 hello, world
34 | 0010 │ 00 00 00 01 00 00 00 02 00 00 00 03 00 00 00 04 idlist…
35 | 0020 │ 00 00 00 05 00 00 00 00 00 00 00 01 …idlist, times…
36 | 0030 │ 00 00 00 00 00 00 00 02 …times
37 | 0040 │ 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 ipv6addr
38 | 0050 │
39 | 0060 │
40 |
41 | Idea: constants are numbered, vm uses a lookup table:
42 |
43 | func getnum () i32
44 | const i32 x = 123 // K0
45 | r0 = x
46 | ret
47 |
48 | u8 constdat[4] = {
49 | 0x00, 0x00, 0x00, 0x01,
50 | };
51 | usize consttab[] = {
52 | 0, // K0 => offset 0 in constdat
53 | };
54 |
55 | LOADK R0 0 // essentially: R0 = *(i32*)&constdat[consttab[0]]
56 | RET
57 |
58 |
--------------------------------------------------------------------------------
/src/abuf.h:
--------------------------------------------------------------------------------
1 | // string append buffer
2 | // SPDX-License-Identifier: Apache-2.0
3 | #pragma once
4 | RSM_ASSUME_NONNULL_BEGIN
5 |
6 | // abuf_t is a string append buffer for implementing snprintf-style functions which
7 | // writes to a limited buffer and separately keeps track of the number of bytes
8 | // that are appended independent of the buffer's limit.
9 | typedef struct abuf {
10 | char* p;
11 | char* lastp;
12 | usize len;
13 | } abuf_t;
14 | // Here is a template for use with functions that uses abuf:
15 | //
16 | // // It writes at most bufcap-1 of the characters to the output buf (the bufcap'th
17 | // // character then gets the terminating '\0'). If the return value is greater than or
18 | // // equal to the bufcap argument, buf was too short and some of the characters were
19 | // // discarded. The output is always null-terminated, unless size is 0.
20 | // // Returns the number of characters that would have been printed if bufcap was
21 | // // unlimited (not including the final `\0').
22 | // usize myprint(char* buf, usize bufcap, int somearg) {
23 | // abuf_t s = abuf_make(buf, bufcap);
24 | // // call abuf_append functions here
25 | // return abuf_terminate(&s);
26 | // }
27 | //
28 | extern char abuf_zeroc;
29 | #define abuf_make(p,size) ({ /* abuf_t abuf_make(char* buf, usize bufcap) */\
30 | usize z__ = (usize)(size); char* p__ = (p); \
31 | UNLIKELY(z__ == 0) ? \
32 | (abuf_t){ &abuf_zeroc, &abuf_zeroc, 0 } : (abuf_t){ p__, p__+z__-1, 0 }; \
33 | })
34 |
35 | // append functions
36 | void abuf_append(abuf_t* s, const char* p, usize len);
37 | void abuf_c(abuf_t* s, char c);
38 | void abuf_u64(abuf_t* s, u64 v, u32 base);
39 | void abuf_fill(abuf_t* s, char c, usize len); // like memset
40 | void abuf_repr(abuf_t* s, const void* p, usize len);
41 | void abuf_reprhex(abuf_t* s, const void* p, usize len);
42 | void abuf_fmt(abuf_t* s, const char* fmt, ...) ATTR_FORMAT(printf, 2, 3);
43 | void abuf_fmtv(abuf_t* s, const char* fmt, va_list);
44 | inline static void abuf_str(abuf_t* s, const char* cstr) { abuf_append(s, cstr, strlen(cstr)); }
45 |
46 | inline static usize abuf_terminate(abuf_t* s) { *s->p = 0; return s->len; }
47 | inline static usize abuf_avail(const abuf_t* s) { return (usize)(uintptr)(s->lastp - s->p); }
48 | bool abuf_endswith(const abuf_t* s, const char* str, usize len);
49 |
50 | RSM_ASSUME_NONNULL_END
51 |
--------------------------------------------------------------------------------
/etc/website/_hl/abnf.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {string} value
3 | * @returns {RegExp}
4 | * */
5 |
6 | /**
7 | * @param {RegExp | string } re
8 | * @returns {string}
9 | */
10 | function source(re) {
11 | if (!re) return null;
12 | if (typeof re === "string") return re;
13 |
14 | return re.source;
15 | }
16 |
17 | /**
18 | * @param {...(RegExp | string) } args
19 | * @returns {string}
20 | */
21 | function concat(...args) {
22 | const joined = args.map((x) => source(x)).join("");
23 | return joined;
24 | }
25 |
26 | /*
27 | Language: Augmented Backus-Naur Form
28 | Author: Alex McKibben
29 | Website: https://tools.ietf.org/html/rfc5234
30 | Audit: 2020
31 | */
32 |
33 | /** @type LanguageFn */
34 | function abnf(hljs) {
35 | const KEYWORDS = [
36 | "ALPHA",
37 | "BIT",
38 | "CHAR",
39 | "CR",
40 | "CRLF",
41 | "CTL",
42 | "DIGIT",
43 | "DQUOTE",
44 | "HEXDIG",
45 | "HTAB",
46 | "LF",
47 | "LWSP",
48 | "OCTET",
49 | "SP",
50 | "VCHAR",
51 | "WSP"
52 | ];
53 |
54 | const COMMENTS = [
55 | hljs.COMMENT('//', '$'),
56 | hljs.COMMENT('/\\*', '\\*/'),
57 | ]
58 |
59 | const TERMINAL_BINARY = {
60 | scope: "symbol",
61 | match: /%b[0-1]+(-[0-1]+|(\.[0-1]+)+)?/
62 | };
63 |
64 | const TERMINAL_DECIMAL = {
65 | scope: "symbol",
66 | match: /%d[0-9]+(-[0-9]+|(\.[0-9]+)+)?/
67 | };
68 |
69 | const TERMINAL_HEXADECIMAL = {
70 | scope: "symbol",
71 | match: /%x[0-9A-F]+(-[0-9A-F]+|(\.[0-9A-F]+)+)?/
72 | };
73 |
74 | const CASE_SENSITIVITY = {
75 | scope: "symbol",
76 | match: /%[si](?=".*")/
77 | };
78 |
79 | const RULE_DECLARATION = {
80 | scope: "attribute",
81 | // match: concat(IDENT, /(?=\s*=)/)
82 | match: /^\s*[a-zA-Z][a-zA-Z0-9-_]*(?=\s*=)/
83 | };
84 |
85 | const ASSIGNMENT = {
86 | scope: "operator",
87 | match: /=\/?/
88 | };
89 |
90 | return {
91 | name: 'Augmented Backus-Naur Form',
92 | illegal: /[!@#$^&',?+~`|:]/,
93 | keywords: KEYWORDS,
94 | contains: [
95 | ASSIGNMENT,
96 | RULE_DECLARATION,
97 | ...COMMENTS,
98 | TERMINAL_BINARY,
99 | TERMINAL_DECIMAL,
100 | TERMINAL_HEXADECIMAL,
101 | CASE_SENSITIVITY,
102 | hljs.QUOTE_STRING_MODE,
103 | hljs.NUMBER_MODE
104 | ]
105 | };
106 | }
107 |
108 | module.exports = abnf;
109 |
--------------------------------------------------------------------------------
/src/fmt.c:
--------------------------------------------------------------------------------
1 | // string formatting
2 | // SPDX-License-Identifier: Apache-2.0
3 | #include "rsmimpl.h"
4 | #include "abuf.h"
5 |
6 | #define _fr_c(v) ({ \
7 | u32 r__ = (v); \
8 | r__ == RSM_MAX_REG ? abuf_fmt(s, "\t" "\e[9%cm" "SP" "\e[39m", REG_FMTCOLORC(r__)) : \
9 | abuf_fmt(s, "\t" REG_FMTNAME_PAT, REG_FMTNAME(r__)); \
10 | })
11 | #define _fr_nc(v) abuf_fmt(s, "\tR%u", v)
12 | #define _fu(v) abuf_fmt(s, "\t0x%x", v)
13 | #define _fs(v) abuf_fmt(s, "\t%d", (i32)v)
14 |
15 | #define fr(N) ( (fl&RSM_FMT_COLOR) ? _fr_c(RSM_GET_##N(in)) : _fr_nc(RSM_GET_##N(in)) )
16 | #define fu(N) ( RSM_GET_i(in) ? _fu(RSM_GET_##N##u(in)) : fr(N) )
17 | #define fs(N) ( RSM_GET_i(in) ? _fs(RSM_GET_##N##s(in)) : fr(N) )
18 |
19 |
20 | u32 fmtinstr(abuf_t* s, rin_t in, rfmtflag_t fl) {
21 | #define fi__ break;
22 | #define fi_A fr(A); break;
23 | #define fi_Au fu(A); break;
24 | #define fi_As fs(A); break;
25 | #define fi_AB fr(A); fr(B); break;
26 | #define fi_ABv fr(A); fu(B); assert(RSM_GET_OP(in)==rop_COPYV); return 1+RSM_GET_Bu(in);
27 | #define fi_ABu fr(A); fu(B); break;
28 | #define fi_ABs fr(A); fs(B); break;
29 | #define fi_ABC fr(A); fr(B); fr(C); break;
30 | #define fi_ABCu fr(A); fr(B); fu(C); break;
31 | #define fi_ABCs fr(A); fr(B); fs(C); break;
32 | #define fi_ABCD fr(A); fr(B); fr(C); fr(D); break;
33 | #define fi_ABCDu fr(A); fr(B); fr(C); fu(D); break;
34 | #define fi_ABCDs fr(A); fr(B); fr(C); fs(D); break;
35 | abuf_str(s, rop_name(RSM_GET_OP(in)));
36 | switch (RSM_GET_OP(in)) {
37 | #define _(OP, ENC, ...) case rop_##OP: fi_##ENC
38 | RSM_FOREACH_OP(_)
39 | #undef _
40 | }
41 | return 1;
42 | }
43 |
44 | usize rsm_fmtinstr(char* buf, usize bufcap, rin_t in, u32* pcaddp, rfmtflag_t fl) {
45 | abuf_t s = abuf_make(buf, bufcap);
46 | u32 pcadd = fmtinstr(&s, in, fl);
47 | if (pcaddp)
48 | *pcaddp = pcadd;
49 | return abuf_terminate(&s);
50 | }
51 |
52 | usize rsm_fmtprog(
53 | char* buf, usize bufcap, const rin_t* nullable ip, usize ilen, rfmtflag_t fl)
54 | {
55 | assert(ip != NULL || ilen == 0); // ok to pass NULL,0 but not NULL,>0
56 | abuf_t s1 = abuf_make(buf, bufcap); abuf_t* s = &s1;
57 | for (usize i = 0; i < ilen; i++) {
58 | if (i)
59 | abuf_c(s, '\n');
60 | rin_t in = ip[i];
61 | abuf_fmt(s, "%4zx ", i);
62 | u32 pcadd = fmtinstr(s, in, fl);
63 |
64 | // variable imm
65 | while (--pcadd)
66 | abuf_fmt(s, " 0x%08x", ip[++i]);
67 | }
68 | return abuf_terminate(s);
69 | }
70 |
--------------------------------------------------------------------------------
/etc/website/v1/_gentranscript.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs")
2 | const vttparse = require("./_vttparse").parse
3 |
4 | if (process.argv.length < 5)
5 | throw "usage: prog "
6 |
7 | let INFILE = process.argv[2]
8 | let TITLE = process.argv[3]
9 | let OUTFILE_HTML = process.argv[4]
10 |
11 | const parsed = vttparse(fs.readFileSync(INFILE, "utf8"), { strict: false })
12 | // console.log(parsed)
13 | // for (let cue of parsed.cues) {
14 | // console.log(cue.text)
15 | // }
16 |
17 | let endtime = 0
18 | for (let cue of parsed.cues) {
19 | endtime = Math.max(endtime, cue.end)
20 | }
21 |
22 | let prevtext = ""
23 | let seconds = 0
24 | let lastsec = -9999999
25 | let buf = []
26 | let outplain = []
27 | let outmd = []
28 | let outhtml = []
29 |
30 | function flush() {
31 | let time = seconds
32 | let hour = Math.floor(seconds / 3600); seconds %= 3600
33 | let min = Math.floor(seconds / 60); seconds = Math.floor(seconds % 60)
34 | let timestamp = ""
35 | if (endtime >= 60*60)
36 | timestamp += (hour + ":").padStart(3, '0')
37 | timestamp += (min + ":").padStart(3, '0')
38 | timestamp += String(seconds).padStart(2, '0')
39 | outplain.push(timestamp + ": " + buf.join(" "))
40 | outhtml.push(
41 | `${timestamp} ${buf.join(" ")}
`)
42 | outmd.push('_' + timestamp + ":_ " + buf.join(" ") + "
")
43 | buf = []
44 | seconds = 0
45 | }
46 |
47 | for (let cue of parsed.cues) {
48 | let text = cue.text.split("\n")[0].trim()
49 | if (text == prevtext)
50 | continue
51 | prevtext = text
52 | if (text == "")
53 | continue
54 | buf.push(text)
55 | if (seconds == 0)
56 | seconds = cue.start
57 | if (cue.start - lastsec > 2 && buf.length > 1)
58 | flush()
59 | lastsec = cue.start
60 | }
61 |
62 | // final lines
63 | let lastcue = parsed.cues[parsed.cues.length - 1]
64 | let text = lastcue.text.split("\n").slice(1).join(" ")
65 | text = text.replace(/<[^>]+>/g, " ").replace(/ +/g, " ").trim()
66 | if (!prevtext.startsWith(text))
67 | buf.push(text)
68 | if (seconds == 0)
69 | seconds = lastcue.start
70 | flush()
71 |
72 | // console.log(outplain.join("\n"))
73 | // console.log(outhtml.join("\n"))
74 | // console.log(outmd.join("\n"))
75 |
76 | let html = `---
77 | layout: simple2
78 | title: "Transcript of RSM ${TITLE}"
79 | ---
80 |
81 | {{page.title}}
82 | RSM project page
83 |
84 | ${outhtml.join("\n")}
85 |
86 | `
87 |
88 | fs.writeFileSync(OUTFILE_HTML, html, "utf8")
89 |
--------------------------------------------------------------------------------
/src/array.c:
--------------------------------------------------------------------------------
1 | // dynamically sized array
2 | // SPDX-License-Identifier: Apache-2.0
3 | #include "rsmimpl.h"
4 | #include "array.h"
5 |
6 | void _rarray_remove(rarray* a, u32 elemsize, u32 start, u32 len) {
7 | if (len == 0)
8 | return;
9 | safecheckf(start+len <= a->len, "end=%u > len=%u", start+len, a->len);
10 | if (start+len < a->len) {
11 | void* dst = a->v + elemsize*start;
12 | void* src = dst + elemsize*len;
13 | memmove(dst, src, elemsize*(a->len - start - len));
14 | }
15 | a->len -= len;
16 | }
17 |
18 | bool rarray_grow(rarray* a, rmemalloc_t* ma, u32 elemsize, u32 addl) {
19 | u32 newcap = a->cap ? (u32)MIN((u64)a->cap * 2, U32_MAX) : MAX(addl, 4u);
20 | usize newsize;
21 | if (check_mul_overflow((usize)newcap, (usize)elemsize, &newsize))
22 | return false;
23 | rmem_t m = { a->v, (usize)(a->cap * elemsize) };
24 | if UNLIKELY(!rmem_resize(ma, &m, newsize))
25 | return false;
26 | assertf(m.size/(usize)elemsize >= newcap, "bug in rmem_resize %u", a->cap * elemsize);
27 | a->v = m.p;
28 | a->cap = (u32)(m.size / (usize)elemsize);
29 | return true;
30 | }
31 |
32 | bool _rarray_reserve(rarray* a, rmemalloc_t* ma, u32 elemsize, u32 addl) {
33 | u32 len;
34 | if (check_add_overflow(a->len, addl, &len))
35 | return false;
36 | if (len >= a->cap && UNLIKELY(!rarray_grow(a, ma, elemsize, addl)))
37 | return false;
38 | return true;
39 | }
40 |
41 | void _arotatemem(u32 stride, void* v, u32 first, u32 mid, u32 last) {
42 | assert(first <= mid); // if equal (zero length), do nothing
43 | assert(mid < last);
44 | usize tmp[16]; assert(sizeof(u32) <= sizeof(tmp));
45 | u32 next = mid;
46 | while (first != next) {
47 | // swap
48 | memcpy(tmp, v + first*stride, stride); // tmp = v[first]
49 | memcpy(v + first*stride, v + next*stride, stride); // v[first] = v[next]
50 | memcpy(v + next*stride, tmp, stride); // v[next] = tmp
51 | first++;
52 | next++;
53 | if (next == last) {
54 | next = mid;
55 | } else if (first == mid) {
56 | mid = next;
57 | }
58 | }
59 | }
60 |
61 | #define DEF_AROTATE(NAME, T) \
62 | void NAME(T* const v, u32 first, u32 mid, u32 last) { \
63 | assert(first <= mid); \
64 | assert(mid < last); \
65 | u32 next = mid; \
66 | while (first != next) { \
67 | T tmp = v[first]; v[first++] = v[next]; v[next++] = tmp; \
68 | if (next == last) next = mid; \
69 | else if (first == mid) mid = next; \
70 | } \
71 | }
72 |
73 | DEF_AROTATE(_arotate32, u32)
74 | DEF_AROTATE(_arotate64, u64)
75 |
--------------------------------------------------------------------------------
/etc/website/_css/code.css:
--------------------------------------------------------------------------------
1 | pre {
2 | --fg-comment: rgba(var(--foreground-color-rgb),0.5);
3 | --fg-commenterr: rgb(186, 94, 81);
4 | --fg-keyword: #d73a49;
5 | --fg-type: var(--fg-keyword);
6 | --fg-data: #333377;
7 | --fg-string: #333377;
8 | --fg-diff-add: #55a532;
9 | --bg-diff-add: #eaffea;
10 | --fg-diff-rem: #bd2c00;
11 | --bg-diff-rem: #ffecec;
12 |
13 | @media (prefers-color-scheme: dark) {
14 | --fg-comment: rgba(var(--foreground-color-rgb),0.4);
15 | --fg-commenterr: #735048;
16 | --fg-keyword: #ff7b72;
17 | --fg-data: #abe0ec;
18 | --fg-string: rgb(165, 214, 255);
19 | --fg-diff-add: #55a532;
20 | --bg-diff-add: #eaffea;
21 | --fg-diff-rem: #bd2c00;
22 | --bg-diff-rem: #ffecec;
23 | }
24 |
25 | background: var(--code-bg);
26 | font-size: 0.875rem; /* 14dp @ font-size 16 */
27 | /* font-size: 0.9375rem; */ /* 15dp @ font-size 16 */
28 | padding: 0.625rem; /* 10dp @ font-size 16 */
29 | border-radius: 0.3125rem; /* 5dp @ font-size 16 */
30 | -webkit-text-size-adjust: none;
31 | }
32 |
33 | /* comments */
34 | .hl-comment {
35 | color: var(--fg-comment);
36 | & .hl-errormsg { color: var(--fg-commenterr) }
37 | }
38 |
39 | /* meta (?) */
40 | .hl-meta { color: var(--fg-keyword) }
41 |
42 | /* variable */
43 | .hl-variable,
44 | .hl-template-variable {
45 | /* color: rgba(var(--foreground-color-rgb),0.6); */
46 | }
47 |
48 | /* type */
49 | .hl-type {
50 | color: var(--fg-type);
51 | }
52 |
53 | /* keywords */
54 | .hl-keyword,
55 | .hl-selector-tag,
56 | .hl-section,
57 | .hl-name,
58 | .hl-tag,
59 | .hl-attr,
60 | .hl-selector-id,
61 | .hl-selector-class,
62 | .hl-selector-attr,
63 | .hl-selector-pseudo,
64 | .hl-built_in {
65 | color: var(--fg-keyword);
66 | }
67 |
68 | /* data */
69 | .hl-strong,
70 | .hl-emphasis,
71 | .hl-number,
72 | .hl-literal,
73 | .hl-bullet,
74 | .hl-attribute,
75 | .hl span.n + span.o + span.n {
76 | color: var(--fg-data);
77 | }
78 |
79 | /* string literal data */
80 | .hl-string,
81 | .hl-quote {
82 | color: var(--fg-string);
83 | }
84 |
85 | .hl-typedef {
86 | .hl-title {
87 | font-weight: 600;
88 | }
89 | }
90 |
91 | .hl-symbol {
92 | font-weight: 500;
93 | }
94 |
95 | .hl-function {
96 | .hl-title {
97 | font-weight: 600;
98 | }
99 | .hl-params {}
100 | .hl-params + .hl-title {
101 | color: var(--fg-type);
102 | font-weight: initial;
103 | }
104 | }
105 |
106 | /* diff */
107 | .hl-addition { color:var(--fg-diff-add); background-color:var(--bg-diff-add); }
108 | .hl-deletion { color:var(--fg-diff-rem); background-color:var(--bg-diff-rem); }
109 |
110 | /* hyperlink..? */
111 | .hl-link {}
112 |
113 | /* shell prompt */
114 | .language-shell > .language-bash {
115 | font-weight: 600;
116 | }
117 |
--------------------------------------------------------------------------------
/src/array.h:
--------------------------------------------------------------------------------
1 | // dynamically sized array
2 | // SPDX-License-Identifier: Apache-2.0
3 | #pragma once
4 | RSM_ASSUME_NONNULL_BEGIN
5 |
6 | typedef struct rarray {
7 | u8* nullable v; // u8 so we get -Wincompatible-pointer-types if we access .v directly
8 | u32 len, cap;
9 | } rarray;
10 |
11 | #define rarray_at(T, a, index) (((T*)(a)->v) + (index))
12 | #define rarray_at_safe(T, a, i) ({safecheck((i)<(a)->len);rarray_at(T,(a),(i));})
13 | #define rarray_push(T, a, m) ((T*)_rarray_push((a),(m),sizeof(T)))
14 | #define rarray_remove(T, a, start, len) _rarray_remove((a),sizeof(T),(start),(len))
15 | #define rarray_move(T, a, dst, start, end) _array_move(sizeof(T),(a)->v,(dst),(start),(end))
16 | #define rarray_reserve(T, a, m, addl) _rarray_reserve((a),(m),sizeof(T),(addl))
17 | #define rarray_free(T, a, ma) ( \
18 | (a)->v ? rmem_free( (ma), RMEM((a)->v, (usize)(a)->cap * sizeof(T)) ) : ((void)0) \
19 | )
20 |
21 | bool rarray_grow(rarray* a, rmemalloc_t*, u32 elemsize, u32 addl);
22 | bool _rarray_reserve(rarray* a, rmemalloc_t*, u32 elemsize, u32 addl);
23 | void _rarray_remove(rarray* a, u32 elemsize, u32 start, u32 len);
24 |
25 | inline static void* nullable _rarray_push(rarray* a, rmemalloc_t* ma, u32 elemsize) {
26 | if (a->len == a->cap && UNLIKELY(!rarray_grow(a, ma, elemsize, 1)))
27 | return NULL;
28 | return a->v + elemsize*(a->len++);
29 | }
30 |
31 | // _array_move moves the chunk [src,src+len) to index dst. For example:
32 | // _array_move(z, v, 5, 1, 1+2) = [1 2 3 4 5|6 7 8] ⟹ [1 4 5 2 3 6 7 8]
33 | // _array_move(z, v, 1, 4, 4+2) = [1|2 3 4 5 6 7 8] ⟹ [1 5 6 2 3 4 7 8]
34 | #define _array_move(elemsize, v, dst, start, end) ( \
35 | (elemsize) == 4 ? _AMOVE_ROTATE(_arotate32,(dst),(start),(end),(u32* const)(v)) : \
36 | (elemsize) == 8 ? _AMOVE_ROTATE(_arotate64,(dst),(start),(end),(u64* const)(v)) : \
37 | _AMOVE_ROTATE(_arotatemem,(dst),(start),(end),(elemsize),(v)) )
38 | #define _AMOVE_ROTATE(f, dst, start, end, args...) ( \
39 | ((start)==(dst)||(start)==(end)) ? ((void)0) : \
40 | ((start) > (dst)) ? (f)(args, (dst), (start), (end)) : \
41 | (f)(args, (start), (end), (dst)) )
42 |
43 | // arotate rotates the order of v in the range [first,last) in such a way
44 | // that the element pointed to by "mid" becomes the new "first" element.
45 | // Assumes first <= mid < last.
46 | #define arotate(elemsize, v, first, mid, last) ( \
47 | (elemsize) == 4 ? _arotate32((u32* const)(v), (first), (mid), (last)) : \
48 | (elemsize) == 8 ? _arotate64((u64* const)(v), (first), (mid), (last)) : \
49 | _arotatemem((elemsize), (v), (first), (mid), (last)) )
50 | void _arotatemem(u32 stride, void* v, u32 first, u32 mid, u32 last);
51 | void _arotate32(u32* const v, u32 first, u32 mid, u32 last);
52 | void _arotate64(u64* const v, u32 first, u32 mid, u32 last);
53 |
54 | RSM_ASSUME_NONNULL_END
55 |
--------------------------------------------------------------------------------
/etc/website/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: RSM virtual computer
3 | ---
4 |
5 | # {{ title }}
6 |
7 | What is RSM?
8 | RSM is a virtual computer; an imaginary set of hardware with a minimal OS foundation.
9 | It defines things like a [CPU instruction set](isa/),
10 | [memory semantics](virtual-memory/) and [program images](rom/) which together forms a computing environment that is truly portable — an RSM program running in a web browser, on macOS, Linux (or some wild system dreamed up by you) behaves the same.
11 | RSM runs on top of other "real" OSes and hardware.
12 |
13 | But... wat... I can't even...
14 | You might be wondering what I've been smoking. Why do something like this? Why WHY?!?
15 |
16 | Software becomes unusable way too quickly. Remember that game or app you really loved ten years ago? It doesn't work anymore because in this world of relentless industrialization of software that we've built for ourselves, only today matters; there's always a new version with new features. More more more. A culture of putting many disparate pieces together into fragile houses of cards, all for the purpose of satisfying a never-ending thirst for short-lived novelty.
17 |
18 | I'd very much like software to have a longer life. After all, writing a good program takes a lot of effort and emotion.
19 |
20 | So how could you make software that can run ten years from now? Or even in 50 years? Computers will keep on evolving, hardware architectures come and go, and OSes will keep on changing. The only option is to build a system that can run or be emulated on top of whatever computer we will have tomorrow. RSM is an attempt at building such a system.
21 |
22 | Another reason I'm excited to work on RSM is because it [gives me joy](v1/) to build something like this. Almost every day of working on this project has [taught me something](https://github.com/rsms/rsm/tree/main/.logbook).
23 |
24 | So, summarizing the reason RSM exists:
25 |
26 | - Offer an option for software longevity: I want to be able to run a program in 10+ years
27 | - Substrate, a portable platform that can be archived
28 | - For the fun of it, for the joy of learning and building
29 |
30 |
31 | ## Installing
32 |
33 | [Download the latest release](https://github.com/rsms/rsm/releases/latest)
34 |
35 | Source code and build instructions can be found at
36 | [github.com/rsms/rsm](https://github.com/rsms/rsm)
37 |
38 | > **Status of this project:** This is a passion project and thus is not "production grade" stuff. The instruction set and semantics are changing. I'd be thrilled and happy if you play with RSM and build stuff on it, but please do keep in mind that stuff will change. Contributions are welcome, especially contributions of the intellectual kind; conversations and ideas.
39 |
40 |
41 | ## Documentation
42 |
43 | - [Instruction Set Architecture](isa/)
44 | - [Assembler](assembler/)
45 | - [Virtual Memory](virtual-memory/)
46 | - [ROM image layout](rom/)
47 |
48 |
49 | ## History
50 |
51 | - [Initial conversation on twitter](https://twitter.com/rsms/status/1492582847982440448)
52 | and [related tweets](https://twitter.com/search?q=from%3Arsms%20%22rsm%22&f=live)
53 | - [Video diary of the inception of RSM](v1/)
54 | - [Web browser playground](play/) (old outdated version)
55 |
--------------------------------------------------------------------------------
/etc/website/_hl/rsm.js:
--------------------------------------------------------------------------------
1 | // RSM syntax highlighting for highlight.js
2 | const ops = require("../ops.json")
3 | module.exports = function(hljs) {
4 | const IREGS = Array.apply(null, Array(32)).map((_,n) => "R"+n)
5 | const FREGS = Array.apply(null, Array(32)).map((_,n) => "F"+n)
6 |
7 | const IDENT_RE = '[A-Za-z$_][0-9A-Za-z$_]*';
8 |
9 | const KEYWORDS = {
10 | type: [ "i1", "i8", "i16", "i32", "i64", "f32", "f64" ],
11 | keyword: [ "fun", "const" ],
12 | built_in: ops.map(op => op.name),
13 | literal: IREGS.concat(FREGS),
14 | }
15 |
16 | const COMMENTS = [
17 | hljs.COMMENT('/\\*', '\\*/'),
18 | { className: 'comment',
19 | begin: '//',
20 | end: '$',
21 | contains: [
22 | {
23 | className: 'errormsg',
24 | begin: /error:/,
25 | end: /$/,
26 | },
27 | ]
28 | },
29 | ]
30 |
31 | return {
32 | name: 'RSM',
33 | aliases: [],
34 | keywords: KEYWORDS,
35 | illegal: '',
36 | contains: [
37 | hljs.SHEBANG({ binary: "rsm" }),
38 |
39 | ...COMMENTS,
40 |
41 | { className: 'string',
42 | variants: [
43 | hljs.QUOTE_STRING_MODE, // "str"
44 | hljs.APOS_STRING_MODE, // 'c'
45 | ]
46 | },
47 |
48 | { className: 'number',
49 | variants: [
50 | { begin: hljs.C_NUMBER_RE + '[i]', relevance: 1 },
51 | hljs.C_NUMBER_MODE
52 | ]
53 | },
54 |
55 | {
56 | className: 'function',
57 | beginKeywords: 'fun',
58 | end: '[(]|$',
59 | returnBegin: true,
60 | excludeEnd: true,
61 | keywords: KEYWORDS,
62 | relevance: 5,
63 | contains: [
64 | {
65 | begin: hljs.UNDERSCORE_IDENT_RE + '\\s*\\(',
66 | returnBegin: true,
67 | relevance: 0,
68 | contains: [ hljs.UNDERSCORE_TITLE_MODE ]
69 | },
70 | {
71 | className: 'type',
72 | begin: /,
73 | end: />/,
74 | keywords: 'reified',
75 | relevance: 0
76 | },
77 | {
78 | className: 'params',
79 | begin: /\(/,
80 | end: /\)/,
81 | endsParent: true,
82 | keywords: KEYWORDS,
83 | relevance: 0,
84 | contains: [
85 | ...COMMENTS
86 | ]
87 | },
88 | ...COMMENTS
89 | ]
90 | },
91 |
92 | {
93 | begin: [
94 | /const/,
95 | /\s+/,
96 | hljs.UNDERSCORE_IDENT_RE
97 | ],
98 | className: {
99 | 1: "keyword",
100 | 3: "variable"
101 | }
102 | },
103 |
104 | {
105 | begin: [
106 | /data/,
107 | /\s+/,
108 | hljs.UNDERSCORE_IDENT_RE
109 | ],
110 | className: {
111 | 1: "keyword",
112 | 3: "variable"
113 | }
114 | },
115 |
116 | {
117 | className: 'symbol',
118 | match: '^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:',
119 | },
120 |
121 | ]
122 | };
123 | }
124 |
--------------------------------------------------------------------------------
/etc/sinspect.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | if [ $1 = "-w" ]; then shift; exec autorun -no-banner "$1" -- "$0" "$@"; fi
4 |
5 | mkdir -p out/sinspect
6 | IF=$(basename "$1")
7 | S=out/sinspect/$IF-verbatim.s
8 | O=out/sinspect/$IF.o
9 | S2=out/sinspect/$IF.s
10 | S3=out/sinspect/$IF-objdump.s
11 | B=out/sinspect/$IF-stripped-prev.s
12 | D=out/sinspect/$IF.s.diff
13 | PF=out/sinspect/$IF.sh
14 |
15 | echo "$S2"
16 |
17 | [ -x /usr/local/opt/llvm/bin/clang ] && export PATH=/usr/local/opt/llvm/bin:$PATH
18 | [ -x /opt/homebrew/opt/llvm/bin/clang ] && export PATH=/opt/homebrew/opt/llvm/bin:$PATH
19 | CCDEF=cc ; command -v clang >/dev/null && CCDEF=clang
20 | CC=${CC:-$CCDEF}
21 |
22 | # copy previous, compile new
23 | [ -f $S2 ] && cp $S2 $B
24 | $CC -Oz -std=c11 -g -S -o $S "$@" &
25 | $CC -Oz -std=c11 -g -c -o $O "$@"
26 | wait
27 |
28 | OBJDUMP=$(command -v clang)
29 | [ -n "$OBJDUMP" ] && OBJDUMP=$(dirname "$OBJDUMP")/llvm-objdump
30 | [ -x "$OBJDUMP" ] || OBJDUMP=$(dirname "$OBJDUMP")/objdump
31 | [ -x "$OBJDUMP" ] && "$OBJDUMP" -S --no-show-raw-insn -l $O > $S3
32 |
33 | eval "$(cat "$PF" 2>/dev/null)" || true
34 |
35 | IGN_PAT='^(?:\s*[;#]|\s*\.|Ltmp\d+:)'
36 | LABEL_PAT='^[0-9A-Za-z_\.]+:'
37 | ARCH=$(uname -m)
38 | case "$(command -v $CC)" in
39 | */clang) ARCH=$(clang -print-effective-triple "$@" | cut -d- -f1) ;;
40 | esac
41 |
42 | case "$ARCH" in
43 | arm64)
44 | BR_PAT='^\s*(?:B(?:\.\w+)?|Bcc|BLR|CBN?Z|TBN?Z)\s+[^_]' # excludes "BR"
45 | CALL_PAT='^\s*XXX\b' # TODO
46 | ;;
47 | x86_64)
48 | BR_PAT='^\s*J[A-LN-Z][A-Z]*\b' # excludes "JMP"
49 | CALL_PAT='^\s*call[A-Z]?\b'
50 | ;;
51 | *)
52 | echo "don't know how to find stats for $ARCH" >&2
53 | exit 1
54 | esac
55 |
56 | grep -Ev "$IGN_PAT" $S > $S2 # strip comments
57 |
58 | NBYTE=$(stat -f %z $S2)
59 | NINSTR=$(grep -Eiv "$LABEL_PAT" $S2 | wc -l | awk '{print $1}')
60 | NLABEL=$(grep -Ei "$LABEL_PAT" $S2 | wc -l | awk '{print $1}')
61 | NCALL=$(grep -Ei "$CALL_PAT" $S2 | wc -l | awk '{print $1}')
62 | NBR=$(grep -Ei "$BR_PAT" $S2 | wc -l | awk '{print $1}')
63 |
64 | cat < "$PF"
65 | PREV_NBYTE=$NBYTE
66 | PREV_NINSTR=$NINSTR
67 | PREV_NBR=$NBR
68 | PREV_NLABEL=$NLABEL
69 | PREV_NCALL=$NCALL
70 | END
71 |
72 | if [ -f $B ]; then
73 | # if [ -z "$DIFF_FILTER" ]; then
74 | # DIFF_FILTER=/usr/share/git-core/contrib/diff-highlight
75 | # if command -v brew >/dev/null; then
76 | # DIFF_FILTER=$(brew --prefix git)/share/git-core/contrib/diff-highlight/diff-highlight
77 | # fi
78 | # [ -x "$DIFF_FILTER" ] || DIFF_FILTER=cat
79 | # fi
80 | # diff -w -d -q $B $S2 >/dev/null ||
81 | # diff -U 1 -w -d --suppress-common-lines $B $S2 > $D || cat $D | $DIFF_FILTER
82 | diff -U 1 -w -d --suppress-common-lines $B $S2 > $D || true
83 | fi
84 |
85 | # ANSI colors: (\e[3Nm or \e[9Nm) 1 red, 2 green, 3 yellow, 4 blue, 5 magenta, 6 cyan
86 | _report() {
87 | local LABEL=$1;shift
88 | local DELTA=$(( $1 - ${2:-$1} ))
89 | local ICON="—"
90 | printf "%-12s %6d" "$LABEL" $1
91 | (( $DELTA > 0 )) && printf "\e[91m" && ICON="▲"
92 | (( $DELTA < 0 )) && printf "\e[92m" && ICON="▼"
93 | (( $DELTA == 0 )) && printf "\e[2m"
94 | printf " %4d %s\e[0m\n" $DELTA "$ICON"
95 | }
96 |
97 | _report "instructions" $NINSTR $PREV_NINSTR
98 | _report "branches" $NBR $PREV_NBR
99 | _report "calls" $NCALL $PREV_NCALL
100 | _report "labels" $NLABEL $PREV_NLABEL
101 | _report "file size" $NBYTE $PREV_NBYTE
102 |
--------------------------------------------------------------------------------
/.github/workflows/build-rsm.yml:
--------------------------------------------------------------------------------
1 | # Builds rsm either when a version tag is created or source is changed.
2 | #
3 | # When a version tag is created, a release is automatically created.
4 | #
5 | # Otherwise, when source changes without a tag being created,
6 | # the build artifacts are uploaded to github and saved for 1 day.
7 | # They can be found at https://github.com/rsms/rsm/actions/runs/RUNID
8 | name: Build rsm
9 |
10 | on:
11 | push:
12 | branches: ["main"]
13 | paths:
14 | - "src/**"
15 | - build.sh
16 | tags:
17 | - "v*"
18 | workflow_dispatch:
19 |
20 | defaults:
21 | run:
22 | shell: bash
23 |
24 | jobs:
25 | build:
26 | runs-on: ubuntu-latest
27 | permissions:
28 | contents: write
29 | strategy:
30 | fail-fast: false
31 | matrix:
32 | target:
33 | - x86_64-linux-musl
34 | - i386-linux-musl
35 | - aarch64-linux-musl
36 | - riscv64-linux-musl
37 | - riscv64-linux-musl
38 | - x86_64-macos-gnu
39 | - aarch64-macos-gnu
40 | #- x86_64-windows-gnu
41 |
42 | name: ${{ matrix.target }}
43 |
44 | env:
45 | prerelease: false
46 |
47 | steps:
48 | - name: Checkout Source
49 | uses: actions/checkout@v3
50 | with:
51 | submodules: recursive
52 |
53 | - name: Install ninja
54 | run: sudo apt-get install -y ninja-build
55 |
56 | - name: Install zig
57 | run: |
58 | curl -L https://ziglang.org/download/0.9.1/zig-linux-x86_64-0.9.1.tar.xz | tar -xJ
59 |
60 | - name: Define version (tag)
61 | if: startsWith(github.ref, 'refs/tags/v')
62 | run: |
63 | VERSION=${{ github.ref }}
64 | VERSION=${VERSION:11} # refs/tags/v1.2.3 => 1.2.3
65 | echo "rsm_version=$VERSION" >> $GITHUB_ENV
66 |
67 | - name: Define version (branch)
68 | if: ${{ ! startsWith(github.ref, 'refs/tags/v') }}
69 | run: |
70 | VERSION=${{ github.sha }}
71 | echo "rsm_version=${VERSION:0:10}" >> $GITHUB_ENV
72 |
73 | - name: Build rsm
74 | run: |
75 | CFLAGS_HOST="-target ${{ matrix.target }}" \
76 | LDFLAGS_HOST="-target ${{ matrix.target }}" \
77 | CC="$PWD/zig-linux-x86_64-0.9.1/zig cc" \
78 | bash build.sh -static -out=out-${{ matrix.target }} "$@"
79 |
80 | - name: Create archive
81 | run: |
82 | # TARGET = os-arch
83 | TARGET=$(echo "${{ matrix.target }}" | cut -d- -f2)
84 | TARGET=$TARGET-$(echo "${{ matrix.target }}" | cut -d- -f1)
85 | NAME=rsm-$TARGET-${{ env.rsm_version }}
86 | ARCHIVE=$NAME.tar.gz
87 | echo "rsm_archive=$ARCHIVE" >> $GITHUB_ENV
88 | mkdir $NAME
89 | mv out-${{ matrix.target }}/rsm $NAME/
90 | tar -czvf "$ARCHIVE" "$NAME"
91 |
92 | - name: Upload archive (unless tag)
93 | if: ${{ ! startsWith(github.ref, 'refs/tags/v') }}
94 | uses: actions/upload-artifact@v3
95 | with:
96 | name: "${{ env.rsm_archive }}"
97 | path: "${{ env.rsm_archive }}"
98 | retention-days: 1
99 |
100 | - name: Publish release (if tag)
101 | if: startsWith(github.ref, 'refs/tags/v')
102 | uses: softprops/action-gh-release@v1
103 | with:
104 | name: "rsm ${{ env.rsm_version }}"
105 | body: "rsm ${{ env.rsm_version }} built from ${{ github.sha }}"
106 | files: "${{ env.rsm_archive }}"
107 |
--------------------------------------------------------------------------------
/etc/website/_hl/wasm.js:
--------------------------------------------------------------------------------
1 | /*
2 | Language: WebAssembly
3 | Website: https://webassembly.org
4 | Description: Wasm is designed as a portable compilation target for programming languages, enabling deployment on the web for client and server applications.
5 | Category: web
6 | Audit: 2020
7 | */
8 |
9 | /** @type LanguageFn */
10 | function wasm(hljs) {
11 | const BLOCK_COMMENT = hljs.COMMENT(/\(;/, /;\)/);
12 | BLOCK_COMMENT.contains.push("self");
13 | const LINE_COMMENT = hljs.COMMENT(/;;/, /$/);
14 |
15 | const KWS = [
16 | "anyfunc",
17 | "block",
18 | "br",
19 | "br_if",
20 | "br_table",
21 | "call",
22 | "call_indirect",
23 | "data",
24 | "drop",
25 | "elem",
26 | "else",
27 | "end",
28 | "export",
29 | "func",
30 | "global.get",
31 | "global.set",
32 | "local.get",
33 | "local.set",
34 | "local.tee",
35 | "get_global",
36 | "get_local",
37 | "global",
38 | "if",
39 | "import",
40 | "local",
41 | "loop",
42 | "memory",
43 | "memory.grow",
44 | "memory.size",
45 | "module",
46 | "mut",
47 | "nop",
48 | "offset",
49 | "param",
50 | "result",
51 | "return",
52 | "select",
53 | "set_global",
54 | "set_local",
55 | "start",
56 | "table",
57 | "tee_local",
58 | "then",
59 | "type",
60 | "unreachable"
61 | ];
62 |
63 | const FUNCTION_REFERENCE = {
64 | begin: [
65 | /(?:func|call|call_indirect)/,
66 | /\s+/,
67 | /\$[^\s)]+/
68 | ],
69 | className: {
70 | 1: "keyword",
71 | 3: "title.function"
72 | }
73 | };
74 |
75 | const ARGUMENT = {
76 | className: "variable",
77 | begin: /\$[\w_]+/
78 | };
79 |
80 | const PARENS = {
81 | match: /(\((?!;)|\))+/,
82 | className: "punctuation",
83 | relevance: 0
84 | };
85 |
86 | const NUMBER = {
87 | className: "number",
88 | relevance: 0,
89 | // borrowed from Prism, TODO: split out into variants
90 | match: /[+-]?\b(?:\d(?:_?\d)*(?:\.\d(?:_?\d)*)?(?:[eE][+-]?\d(?:_?\d)*)?|0x[\da-fA-F](?:_?[\da-fA-F])*(?:\.[\da-fA-F](?:_?[\da-fA-D])*)?(?:[pP][+-]?\d(?:_?\d)*)?)\b|\binf\b|\bnan(?::0x[\da-fA-F](?:_?[\da-fA-D])*)?\b/
91 | };
92 |
93 | const TYPE = {
94 | // look-ahead prevents us from gobbling up opcodes
95 | match: /(i32|i64|f32|f64)(?!\.)/,
96 | className: "type"
97 | };
98 |
99 | const MATH_OPERATIONS = {
100 | className: "keyword",
101 | // borrowed from Prism, TODO: split out into variants
102 | match: /\b(f32|f64|i32|i64)(?:\.(?:abs|add|and|ceil|clz|const|convert_[su]\/i(?:32|64)|copysign|ctz|demote\/f64|div(?:_[su])?|eqz?|extend_[su]\/i32|floor|ge(?:_[su])?|gt(?:_[su])?|le(?:_[su])?|load(?:(?:8|16|32)_[su])?|lt(?:_[su])?|max|min|mul|nearest|neg?|or|popcnt|promote\/f32|reinterpret\/[fi](?:32|64)|rem_[su]|rot[lr]|shl|shr_[su]|store(?:8|16|32)?|sqrt|sub|trunc(?:_[su]\/f(?:32|64))?|wrap\/i64|xor))\b/
103 | };
104 |
105 | const OFFSET_ALIGN = {
106 | match: [
107 | /(?:offset|align)/,
108 | /\s*/,
109 | /=/
110 | ],
111 | className: {
112 | 1: "keyword",
113 | 3: "operator"
114 | }
115 | };
116 |
117 | return {
118 | name: 'WebAssembly',
119 | keywords: {
120 | $pattern: /[\w.]+/,
121 | keyword: KWS
122 | },
123 | contains: [
124 | LINE_COMMENT,
125 | BLOCK_COMMENT,
126 | OFFSET_ALIGN,
127 | ARGUMENT,
128 | PARENS,
129 | FUNCTION_REFERENCE,
130 | hljs.QUOTE_STRING_MODE,
131 | TYPE,
132 | MATH_OPERATIONS,
133 | NUMBER
134 | ]
135 | };
136 | }
137 |
138 | module.exports = wasm;
139 |
--------------------------------------------------------------------------------
/src/map.h:
--------------------------------------------------------------------------------
1 | // smap is a byte string to pointer map, implemented as a hash map
2 | // SPDX-License-Identifier: Apache-2.0
3 | #pragma once
4 | #include "hash.h"
5 | RSM_ASSUME_NONNULL_BEGIN
6 |
7 | typedef struct smap smap; // string-keyed map
8 | typedef struct smapent smapent; // smap entry
9 | typedef u8 maplf; // load factor
10 | struct smapent {
11 | const char* nullable key; // NULL if this entry is empty
12 | usize keylen;
13 | uintptr value;
14 | };
15 | struct smap {
16 | u32 cap; // capacity of entries
17 | u32 len; // number of items currently stored in the map (count)
18 | u32 gcap; // growth watermark cap
19 | maplf lf; // growth watermark load factor (shift value; 1|2|3|4)
20 | hash_t hash0; // hash seed
21 | union {
22 | smapent* entries;
23 | rmem_t entries_mem;
24 | };
25 | rmemalloc_t* memalloc;
26 | };
27 | enum maplf {
28 | MAPLF_1 = 1, // grow when 50% full; recommended for maps w/ balanced hit & miss lookups
29 | MAPLF_2 = 2, // grow when 75% full; recommended for maps of mostly hit lookups
30 | MAPLF_3 = 3, // grow when 88% full; miss (no match) lookups are expensive
31 | MAPLF_4 = 4, // grow when 94% full; miss lookups are very expensive
32 | } RSM_END_ENUM(maplf)
33 |
34 | // smap_make initializes a new map m.
35 | // hint can be 0 and provides a hint as to how many items will initially be stored.
36 | // Returns m on success, NULL on memory allocation failure or overflow from large hint.
37 | smap* nullable smap_make(smap* m, rmemalloc_t*, u32 hint, maplf);
38 |
39 | // smap_copy creates a copy of srcm at dstm, using entries_mem.
40 | // Assumes entries_mem is owned by ma; any modifications to the map will cause
41 | // entries_mem to be passed to the ma.
42 | // If there's not enough room in entries_mem, the copy is not completed.
43 | // Returns the number of bytes needed for a copy as if entries_mem.size was infinite.
44 | usize smap_copy(smap* dstm, const smap* srcm, rmem_t entries_mem, rmemalloc_t* ma);
45 |
46 | // smap_dispose frees m->entries. m is invalid (use smap_make to reuse m)
47 | void smap_dispose(smap* m);
48 |
49 | // smap_clear removes all items. m remains valid
50 | void smap_clear(smap* m);
51 |
52 | // smap_assign assigns to the map, returning the location for its value,
53 | // or NULL if memory allocation during growth failed. May return an existing item's value.
54 | uintptr* nullable smap_assign(smap* m, const char* key, usize keylen);
55 |
56 | // smap_lookup retrieves the value for key; NULL if not found.
57 | uintptr* nullable smap_lookup(const smap* m, const char* key, usize keylen);
58 |
59 | // smap_del removes an entry for key, returning whether an entry was deleted or not
60 | bool smap_del(smap* m, const char* key, usize keylen);
61 |
62 | // smap_itstart and smap_itnext iterates over a map.
63 | // You can change the value of an entry during iteration but must not change the key.
64 | // Any mutation to the map during iteration will invalidate the iterator.
65 | // Example use:
66 | // for (smapent* e = smap_itstart(m); smap_itnext(m, &e); )
67 | // log("%.*s => %lx", (int)e->keylen, e->key, e->value);
68 | inline static const smapent* nullable smap_itstart(const smap* m) { return m->entries; }
69 | bool smap_itnext(const smap* m, const smapent** ep);
70 |
71 | // smap_optimize tries to improve the key distribution of m by trying different
72 | // hash seeds. Returns the best smap_score or <0.0 if rmem_alloc(ma) failed,
73 | // leaving m with at least as good key distribution as before the call.
74 | // ma is used to allocate temporary space for m's entries;
75 | // space needed is m->entries*sizeof(smapent).
76 | double smap_optimize(smap* m, usize iterations, rmemalloc_t* ma);
77 |
78 | // smap_cfmt prints C code for a constant static representation of m
79 | usize smap_cfmt(char* buf, usize bufcap, const smap* m, const char* name);
80 |
81 | RSM_ASSUME_NONNULL_END
82 |
--------------------------------------------------------------------------------
/src/time.c:
--------------------------------------------------------------------------------
1 | #include "rsmimpl.h"
2 |
3 | #ifndef RSM_NO_LIBC
4 | #include
5 | #include
6 | #include
7 | #if defined __APPLE__
8 | #include
9 | #endif
10 | #endif
11 |
12 | #if defined(__APPLE__)
13 | // fraction to multiply a value in mach tick units with to convert it to nanoseconds
14 | static mach_timebase_info_data_t tbase;
15 | #endif
16 |
17 |
18 | WASM_IMPORT rerr_t unixtime(i64* sec, u64* nsec);
19 | #ifdef CLOCK_REALTIME
20 | rerr_t unixtime(i64* sec, u64* nsec) {
21 | struct timespec ts;
22 | if (clock_gettime(CLOCK_REALTIME, &ts))
23 | return rerr_errno(errno);
24 | *sec = (i64)ts.tv_sec;
25 | *nsec = (u64)ts.tv_nsec;
26 | return 0;
27 | }
28 | #elif !defined(RSM_NO_LIBC)
29 | rerr_t unixtime(i64* sec, u64* nsec) {
30 | struct timeval tv;
31 | if (gettimeofday(&tv, 0) != 0)
32 | return rerr_errno(errno);
33 | *sec = (i64)tv.tv_sec;
34 | *nsec = ((u64)tv.tv_usec) * 1000;
35 | return 0;
36 | }
37 | #elif !defined(__wasm__)
38 | #warning TODO RSM_NO_LIBC unixtime
39 | rerr_t unixtime(i64* sec, u64* nsec) {
40 | return rerr_not_supported;
41 | }
42 | #endif
43 |
44 |
45 | #if defined(__wasm__)
46 | WASM_IMPORT double wasm_nanotime(void);
47 | #endif
48 |
49 | #if !defined(CLOCK_MONOTONIC)
50 | #error CLOCK_MONOTONIC not defined
51 | #endif
52 |
53 | u64 nanotime(void) {
54 | #if defined(__APPLE__)
55 | u64 t = mach_absolute_time();
56 | return (t * tbase.numer) / tbase.denom;
57 | #elif defined(CLOCK_MONOTONIC)
58 | struct timespec ts;
59 | safecheckexpr(clock_gettime(CLOCK_MONOTONIC, &ts), 0);
60 | return ((u64)(ts.tv_sec) * 1000000000) + ts.tv_nsec;
61 | // TODO #elif (defined _MSC_VER && (defined _M_IX86 || defined _M_X64))
62 | // QueryPerformanceCounter
63 | #elif !defined(RSM_NO_LIBC)
64 | struct timeval tv;
65 | safecheckexpr(gettimeofday(&tv, NULL), 0);
66 | return ((u64)(tv.tv_sec) * 1000000000) + ((u64)(tv.tv_usec) * 1000);
67 | #elif defined(__wasm__)
68 | return (u64)wasm_nanotime();
69 | #else
70 | #warning TODO RSM_NO_LIBC nanotime
71 | return 0;
72 | #endif
73 | }
74 |
75 |
76 | // U64_MAX = 584.9 years (18446744073709551615/1000000000/60/60/24/365)
77 | u64 rsm_nanosleep(u64 nsec) {
78 | #ifdef CO_NO_LIBC
79 | #warning TODO non-libc microsleep
80 | #else
81 | struct timespec ts = {
82 | .tv_sec = nsec / 1000000000llu,
83 | .tv_nsec = (long)(nsec % 1000000000llu),
84 | };
85 | if (nanosleep(&ts, &ts) != 0) {
86 | u64 remaining_nsec = ((u64)ts.tv_sec * 1000000000llu) + (u64)ts.tv_nsec;
87 | assert(remaining_nsec <= nsec);
88 | return remaining_nsec;
89 | }
90 | #endif
91 | return 0llu;
92 | }
93 |
94 |
95 | usize fmtduration(char buf[25], u64 duration_ns) {
96 | // max value: "18446744073709551615.1ms\0"
97 | const char* unit = "ns";
98 | u64 d = duration_ns;
99 | u64 f = 0;
100 | if (duration_ns >= 1000000000) {
101 | f = d % 1000000000;
102 | d /= 1000000000;
103 | unit = "s\0";
104 | } else if (duration_ns >= 1000000) {
105 | f = d % 1000000;
106 | d /= 1000000;
107 | unit = "ms";
108 | } else if (duration_ns >= 1000) {
109 | d /= 1000;
110 | unit = "us\0";
111 | }
112 | usize i = stru64(buf, d, 10);
113 | if (unit[0] != 'u' && unit[0] != 'n') {
114 | // one decimal for units larger than microseconds
115 | buf[i++] = '.';
116 | char buf2[20];
117 | UNUSED usize n = stru64(buf2, f, 10);
118 | assert(n > 0);
119 | buf[i++] = buf2[0]; // TODO: round instead of effectively ceil
120 | }
121 | buf[i++] = unit[0];
122 | buf[i++] = unit[1];
123 | buf[i] = 0;
124 | return i;
125 | }
126 |
127 | rerr_t init_time() {
128 | #if defined(__APPLE__)
129 | if (mach_timebase_info(&tbase) != KERN_SUCCESS)
130 | return rerr_not_supported;
131 | #endif
132 | return 0;
133 | }
134 |
--------------------------------------------------------------------------------
/src/asm.h:
--------------------------------------------------------------------------------
1 | // assembler internals, shared by all asm*.c files and rom_build
2 | // SPDX-License-Identifier: Apache-2.0
3 | #pragma once
4 | #ifndef RSM_NO_ASM
5 | RSM_ASSUME_NONNULL_BEGIN
6 |
7 | #define kBlock0Name "b0" // name of first block
8 |
9 | typedef struct gstate gstate;
10 | typedef struct pstate pstate;
11 |
12 | typedef struct rposrange {
13 | rsrcpos_t start, focus, end;
14 | } rposrange_t;
15 |
16 | // rasm._internal[0] -- negated diaghandler return value
17 | #define rasm_stop(a) ( (bool)(a)->_internal[0] )
18 | #define rasm_stop_set(a,v) ( (a)->_internal[0] = (uintptr)(v) )
19 |
20 | // rasm._internal[1]-- reusable internal codegen state
21 | #define rasm_gstate(a) ( (gstate*)(a)->_internal[1] )
22 | #define rasm_gstate_set(a,v) ( (a)->_internal[1] = (uintptr)(v) )
23 |
24 | // rasm._internal[2]-- reusable internal codegen state
25 | #define rasm_pstate(a) ( (pstate*)(a)->_internal[2] )
26 | #define rasm_pstate_set(a,v) ( (a)->_internal[2] = (uintptr)(v) )
27 |
28 | const char* tokname(rtok_t t);
29 |
30 | // tokis* returns true if t is classified as such in the language
31 | #define tokistype(t) ( (RT_I1 <= (t) && (t) <= RT_I64) || ((t) == RT_ARRAY) )
32 | #define tokisintlit(t) ( RT_INTLIT2 <= (t) && (t) <= RT_SINTLIT16 )
33 | #define tokislit(t) tokisintlit(t)
34 | #define tokissint(t) (((t) - RT_SINTLIT16) % 2 == 0) // assumption: tokisintlit(t)
35 | #define tokisoperand(t) ( (t) == RT_IREG || (t) == RT_FREG || tokislit(t) || (t) == RT_NAME )
36 | #define tokisexpr(t) (tokisoperand(t) || (t) == RT_STRLIT)
37 | #define tokhasname(t) ( (t) == RT_NAME || (t) == RT_COMMENT || \
38 | (t) == RT_LABEL || (t) == RT_FUN || \
39 | (t) == RT_CONST || (t) == RT_DATA )
40 |
41 | inline static bool nodename_eq(const rnode_t* n, const char* str, usize len) {
42 | return n->sval.len == len && memcmp(n->sval.p, str, len) == 0;
43 | }
44 |
45 | rnode_t* nullable nlastchild(rnode_t* n);
46 |
47 | rposrange_t nposrange(rnode_t*);
48 |
49 | void errf(rasm_t*, rposrange_t, const char* fmt, ...) ATTR_FORMAT(printf, 3, 4);
50 | void warnf(rasm_t*, rposrange_t, const char* fmt, ...) ATTR_FORMAT(printf, 3, 4);
51 | void reportv(rasm_t*, rposrange_t, int code, const char* fmt, va_list ap);
52 |
53 | typedef struct rrombuild {
54 | const rin_t* code; // vm instructions array
55 | usize codelen; // vm instructions array length
56 | usize datasize; // data segment size
57 | u8 dataalign; // data segment alignment
58 | rasmflag_t flags;
59 | void* userdata;
60 | rerr_t(*filldata)(void* dst, void* userdata);
61 | } rrombuild_t;
62 |
63 | rerr_t rom_build(rrombuild_t* rb, rmemalloc_t* ma, rrom_t* rom);
64 |
65 | static void dlog_asm(rmemalloc_t* ma, const rin_t* iv, usize icount);
66 | #if DEBUG
67 | void _dlog_asm(rmemalloc_t* ma, const rin_t* iv, usize icount);
68 | inline static void dlog_asm(rmemalloc_t* ma, const rin_t* iv, usize icount) {
69 | _dlog_asm(ma, iv, icount);
70 | }
71 | #else
72 | inline static void dlog_asm(rmemalloc_t* ma, const rin_t* iv, usize icount) {
73 | }
74 | #endif
75 |
76 | // ————————————————
77 | // bufslab
78 |
79 | #define BUFSLAB_MIN_CAP 512u
80 | #define BUFSLAB_ALIGN 16u
81 | static_assert(BUFSLAB_MIN_CAP >= BUFSLAB_ALIGN, "");
82 | static_assert(IS_ALIGN2(BUFSLAB_MIN_CAP, BUFSLAB_ALIGN), "");
83 |
84 | typedef struct bufslabs bufslabs;
85 | typedef struct bufslab bufslab;
86 |
87 | // The chain of slabs looks like this: ("free" slabs only when recycled)
88 | //
89 | // full ←—→ full ←—→ partial ←—→ free ←—→ free ←—→ free
90 | // | |
91 | // head tail
92 | //
93 | struct bufslabs {
94 | bufslab* head;
95 | bufslab* tail;
96 | };
97 | struct bufslab {
98 | bufslab* nullable prev;
99 | bufslab* nullable next;
100 | usize len;
101 | usize cap;
102 | u8 data[];
103 | };
104 |
105 | void* nullable bufslab_alloc(bufslabs* slabs, rmemalloc_t*, usize nbyte);
106 | void bufslabs_reset(bufslabs* slabs); // set all slab->len=0 and set slabs->head=tail
107 | void bufslab_freerest(bufslab* s, rmemalloc_t*); // free all slabs after s
108 |
109 |
110 | RSM_ASSUME_NONNULL_END
111 | #endif // RSM_NO_ASM
112 |
--------------------------------------------------------------------------------
/src/vm_map_del.c:
--------------------------------------------------------------------------------
1 | // virtual memory mapping
2 | // SPDX-License-Identifier: Apache-2.0
3 | #include "rsmimpl.h"
4 | #define vmtrace trace
5 | #include "vm.h"
6 |
7 | // VM_MAP_DEL_TRACE: define to enable logging a lot of info via dlog
8 | //#define VM_MAP_DEL_TRACE
9 |
10 | #if (defined(VM_MAP_DEL_TRACE) || defined(VM_MAP_TRACE) || defined(VM_TRACE)) && \
11 | defined(DEBUG)
12 | #undef VM_MAP_DEL_TRACE
13 | #define VM_MAP_DEL_TRACE
14 | #define trace(fmt, args...) dlog("[vm_map_del] " fmt, ##args)
15 | static void trace_table(vm_table_t* table, u32 level, u64 vfn) {
16 | u64 block_vfn = VM_BLOCK_VFN(vfn, level);
17 | u64 vfn_offset = vfn > block_vfn ? vfn - block_vfn : 0;
18 | trace("L%u %012llx is %s (offset 0x%llx, nuse %u)",
19 | level+1, VM_VFN_VADDR(block_vfn),
20 | ( (*(u64*)table == 0) ? "empty" :
21 | (table->nuse == VM_PTAB_LEN) ? "full" :
22 | "partial"
23 | ),
24 | vfn_offset << PAGE_SIZE_BITS, table->nuse);
25 | }
26 | #else
27 | #ifdef VM_MAP_DEL_TRACE
28 | #warning VM_MAP_DEL_TRACE has no effect unless DEBUG is enabled
29 | #undef VM_MAP_DEL_TRACE
30 | #endif
31 | #define trace(...) ((void)0)
32 | #define trace_table(...) ((void)0)
33 | #endif
34 |
35 |
36 | typedef struct {
37 | u64 npages; // remaining number of pages to unmap
38 | u64 vaddr; // start address
39 | rerr_t err;
40 | } delctx_t;
41 |
42 |
43 | static rerr_t unmap_pages(vm_table_t* table, vm_ptab_t ptab, u64 vfn, delctx_t* ctx) {
44 | u32 index = vm_vfn_ptab_index(vfn, VM_PTAB_LEVELS-1);
45 | u64 end_index_need = ctx->npages + (u64)index;
46 |
47 | u32 end_index = vm_ptab_page_end_index(vfn);
48 | if (end_index_need < (u64)end_index)
49 | end_index = (u32)end_index_need;
50 |
51 | u32 npages = end_index - index;
52 |
53 | #ifdef VM_MAP_DEL_TRACE
54 | u64 vaddr = VM_VFN_VADDR(vfn);
55 | trace("unmap %u pages L%u[%u:%u] %012llx…%012llx",
56 | npages, VM_PTAB_LEVELS-1, index, end_index,
57 | vaddr, vaddr+((u64)npages*PAGE_SIZE)-PAGE_SIZE);
58 | #endif
59 |
60 | #if DEBUG
61 | for (u32 i = index; i < end_index; i++) {
62 | if (*(u64*)&ptab[i] == 0)
63 | dlog("[vm_map_del] page %012llx is not mapped", VM_VFN_VADDR(vfn + i));
64 | }
65 | #endif
66 |
67 | // pave entries at ptab[index:end_index]
68 | memset(&ptab[index], 0, sizeof(ptab[0]) * (usize)npages);
69 |
70 | if UNLIKELY(table->nuse < npages) {
71 | table->nuse = 0;
72 | return rerr_not_found;
73 | }
74 | table->nuse -= npages;
75 |
76 | assert(ctx->npages >= (u64)npages);
77 | ctx->npages -= (u64)npages;
78 |
79 | return 0;
80 | }
81 |
82 |
83 | static u32 visit_table(
84 | vm_table_t* table, u32 level, vm_ptab_t ptab, u32 index, u64 vfn, uintptr data)
85 | {
86 | delctx_t* ctx = (delctx_t*)data;
87 |
88 | u64 block_vfn_mask = VM_BLOCK_VFN_MASK(level);
89 | u64 block_npages = VM_PTAB_NPAGES(level+1);
90 | u32 i = index;
91 |
92 | for (;i < VM_PTAB_LEN; i++, vfn = (vfn & block_vfn_mask) + block_npages) {
93 | assert(level < VM_PTAB_LEVELS-1);
94 | vm_table_t* subtable = &ptab[i].table;
95 | trace_table(subtable, level, vfn);
96 |
97 | if UNLIKELY(*(u64*)subtable == 0) {
98 | dlog("[vm_map_del] vaddr %llx not mapped", VM_VFN_VADDR(vfn));
99 | ctx->err = rerr_not_found;
100 | return 0;
101 | }
102 |
103 | // table of tables needs to be traversed
104 | if (level < VM_PTAB_LEVELS-2) {
105 | i++;
106 | break;
107 | }
108 |
109 | // page table
110 | rerr_t err = unmap_pages(subtable, vm_table_ptab(subtable), vfn, ctx);
111 | if UNLIKELY(err) {
112 | ctx->err = err;
113 | return 0;
114 | }
115 | if (ctx->npages == 0) {
116 | trace("done");
117 | return 0;
118 | }
119 | }
120 |
121 | trace("advance %u", i - index);
122 | return i - index;
123 | }
124 |
125 |
126 | rerr_t vm_map_del(vm_map_t* map, u64 vaddr, u64 npages) {
127 | if (npages == 0)
128 | return 0;
129 |
130 | vaddr = VM_PAGE_ADDR(vaddr);
131 |
132 | vm_map_assert_locked(map);
133 |
134 | if ((VM_ADDR_MIN > vaddr) | (vaddr > VM_ADDR_MAX)) {
135 | assertf(0, "invalid vaddr 0x%llx", vaddr);
136 | return rerr_invalid;
137 | }
138 |
139 | trace("unmap %llu pages at %012llx…%012llx",
140 | npages, vaddr, vaddr + (npages-1)*PAGE_SIZE);
141 |
142 | delctx_t ctx = {
143 | .vaddr = vaddr,
144 | .npages = npages,
145 | };
146 |
147 | vm_map_iter(map, vaddr, &visit_table, (uintptr)&ctx);
148 |
149 | return ctx.err;
150 | }
151 |
--------------------------------------------------------------------------------
/etc/website/assembler/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Assembler
3 | ---
4 |
5 | # {{title}}
6 |
7 | RSM includes an integrated assembler, making it possible to compile & run code at runtime.
8 | The assembler can be disabled/removed from builds with a macro, if desired.
9 |
10 | Includes an AST API for code generation without an intermediate assembly step, useful if you want to make a compiler that targets RSM.
11 |
12 |
13 | ## Compiling
14 |
15 | Assembly programs can be compiled just in time when executing a program or ahead of time with the `rsm` tool's `-o` flag to produce a ROM image.
16 |
17 | hello.rsm
18 |
19 | ```rsm
20 | data message = "Hello world\n"
21 | fun main(i32) {
22 | R0 = message // address of message
23 | R1 = 12 // length of message
24 | R0 = write R0 R1 1 // write to stdout
25 | }
26 | ```
27 |
28 | Running it directly:
29 |
30 | ```shell
31 | $ rsm hello.rsm
32 | Hello world
33 | ```
34 |
35 | Compiling to a ROM image and then running it:
36 |
37 | ```shell
38 | $ rsm -o hello.rom hello.rsm
39 | $ rsm hello.rom
40 | Hello world
41 | ```
42 |
43 |
44 | ## Assembly language
45 |
46 | ### Example
47 |
48 | ```rsm
49 | // This program asks for your name on stdin and then greets you
50 | const STDIN = 0
51 | const STDOUT = 1
52 | const STKSIZE = 64 // stack space (must be a multiple of 8)
53 |
54 | data ask_msg = "What is your name?\n"
55 | data readerr_msg = "Failed to read stdin\n"
56 | data no_name_msg = "I see, you don't want to tell me.\n"
57 | data reply_msg1 = "Hello "
58 | data reply_msg2 = "! Nice to meet ya\n"
59 |
60 | fun main(i32) {
61 | // reserve STKSIZE bytes of stack space
62 | SP = sub SP STKSIZE
63 |
64 | // ask the user for their name
65 | call print ask_msg 19
66 | if R0 end
67 |
68 | // read up to STKSIZE bytes from STDIN, to stack memory at SP
69 | R0 = STKSIZE // number of bytes to read
70 | R8 = read SP R0 STDIN
71 | R0 = lts R8 0 // check if read failed ()
72 | if R0 readerr
73 | R8 = R8 - 1 // exclude any line break
74 | R0 = lts R8 1 // check if read was empty
75 | if R0 noname
76 |
77 | // reply with the user's name
78 | call print reply_msg1 6
79 | call print SP R8 // SP = address of name read
80 | call print reply_msg2 18
81 | jump end
82 |
83 | noname:
84 | call print no_name_msg 34
85 | jump end
86 | readerr:
87 | // note: To test this, use "0<&-" in csh, e.g. out/rsm askname.rsm 0<&-
88 | call print readerr_msg 21
89 | end:
90 | SP = add SP STKSIZE // restore stack pointer
91 | }
92 |
93 | fun print(addr i64, size i64) ok i1 {
94 | R0 = write R0 R1 STDOUT
95 | R0 = lts R0 R1 // return 1 on failed or short write
96 | }
97 | ```
98 |
99 | Compile and run:
100 |
101 | ```shell
102 | $ rsm examples/askname.rsm
103 | What is your name?
104 | Robin
105 | Hello Robin! Nice to meet ya
106 | $
107 | ```
108 |
109 |
110 | ### Syntax
111 |
112 | Whitespace is ignored
113 |
114 | ```bnf
115 | file = (fundef | constdef | datadef)*
116 |
117 | constdef = "const" name type? "=" expr ";"
118 | datadef = "data" name (type ("=" expr)? | "=" expr) ";"
119 |
120 | fundef = "fun" name "(" params? ")" result? funbody?
121 | params = param ("," param)*
122 | result = param ("," param)*
123 | param = name type | type
124 | funbody = "{" block0? block* "}"
125 | block0 = blockstmt*
126 | block = name ":" blockstmt*
127 | blockstmt = operation | assignment | binop | constdef | datadef
128 |
129 | type = inttype | arraytype
130 | inttype = "i1" | "i8" | "i16" | "i32" | "i64"
131 | arraytype = type "[" intlit "]"
132 |
133 | operation = opcode operand*
134 | ; brz R1 end
135 | binop = operand ("-" | "+" | "*" | "/") operand
136 | ; x + 3
137 | assignment = reg "=" (operation | operand) ";"
138 | operand = reg | literal | name
139 |
140 | literal = intlit
141 | intlit = "-"? (binlit | declit | hexlit)
142 | binlit = "0b" ("0" | "1")+
143 | declit = (0-9)+
144 | hexlit = "0x" (0-9A-Fa-f)+
145 |
146 | name = ("_" | A-Za-z | uniprint) ("_" | A-Za-z | 0-9 | uniprint)
147 |
148 | uniprint =
149 |
150 | opcode {{!
151 | for (let i = 0; i < site.rsm_ops.length; i++) {
152 | let op = site.rsm_ops[i].name
153 | if (i % 7 == 6)
154 | print('\n ')
155 | print((i == 0 ? '= ' : ' | ') + op)
156 | }
157 | }}
158 |
159 | ```
160 |
161 | Comments are ignored and can appear wherever whitespace can appear
162 |
163 | ```bnf
164 | comment = linecomment | blockcomment
165 | linecomment = "//"
166 | blockcomment = "/*" "*/"
167 | ```
168 |
169 |
--------------------------------------------------------------------------------
/etc/website/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | set -e
3 | cd "$(dirname "$0")"
4 |
5 | ARGV0="$0"
6 | BUILD_DIR="$PWD/_build"
7 | DOWNLOAD_DIR="$BUILD_DIR/download"
8 |
9 | _err() { echo "$ARGV0:" "$@" >&2 ; exit 1 ; }
10 |
11 | # _checksum [-sha256|-sha512] []
12 | # Prints the sha1 (or sha256 or sha512) sum of file's content
13 | # (or stdin if no is given)
14 | _checksum() {
15 | local prog=sha1sum
16 | if [ "$1" = "-sha256" ]; then prog=sha256sum; shift; fi
17 | if [ "$1" = "-sha512" ]; then prog=sha512sum; shift; fi
18 | $prog "$@" | cut -f 1 -d ' '
19 | }
20 |
21 | # _verify_checksum [-silent] file checksum
22 | # checksum can be prefixed with sha1: sha256: or sha512: (e.g. sha256:checksum)
23 | _verify_checksum() {
24 | local silent
25 | if [ "$1" = "-silent" ]; then silent=y; shift; fi
26 | local file="$1"
27 | local expected="$2"
28 | local prog=sha1sum
29 | case "$expected" in
30 | sha1:*) expected=${expected:4};;
31 | sha256:*) expected=${expected:7}; prog=sha256sum ;;
32 | sha512:*) expected=${expected:7}; prog=sha512sum ;;
33 | esac
34 | [ -f "$file" ] || _err "_verify_checksum: $file not found"
35 | command -v "$prog" >/dev/null || _err "_verify_checksum: $prog not found in PATH"
36 | local actual=$("$prog" "$file" | cut -f 1 -d ' ')
37 | if [ "$expected" != "$actual" ]; then
38 | if [ -z "$silent" ]; then
39 | echo "Checksum mismatch: $file" >&2
40 | echo " Actual: $actual" >&2
41 | echo " Expected: $expected" >&2
42 | fi
43 | return 1
44 | fi
45 | }
46 |
47 | # _downloaded_file filename|url
48 | # Prints absolute path to a file downloaded by _download
49 | _downloaded_file() {
50 | echo "$DOWNLOAD_DIR/$(basename "$1")"
51 | }
52 |
53 | # _download url checksum [filename]
54 | # Download file from url. If filename is not given (basename url) is used.
55 | # If DOWNLOAD_DIR/filename exists, then only download if the checksum does not match.
56 | _download() {
57 | local url="$1"
58 | local checksum="$2"
59 | local filename="$DOWNLOAD_DIR/$(basename "${3:-"$url"}")"
60 | echo "filename $filename"
61 | while [ ! -e "$filename" ] || ! _verify_checksum -silent "$filename" "$checksum"; do
62 | if [ -n "$did_download" ]; then
63 | echo "Checksum for $filename failed" >&2
64 | echo " Actual: $(_checksum "$filename")" >&2
65 | echo " Expected: $checksum" >&2
66 | return 1
67 | fi
68 | rm -rf "$filename"
69 | echo "fetch $url"
70 | mkdir -p "$(dirname "$filename")"
71 | curl -L --progress-bar -o "$filename" "$url"
72 | did_download=y
73 | done
74 | }
75 |
76 | # _extract_tar tarfile outdir
77 | _extract_tar() {
78 | local tarfile="$1"
79 | local outdir="$2"
80 | local name=$(basename "$tarfile")
81 | [ -e "$tarfile" ] || _err "$tarfile not found"
82 | local extract_dir="$BUILD_DIR/.extract-$name"
83 | rm -rf "$extract_dir"
84 | mkdir -p "$extract_dir"
85 | echo "extracting ${tarfile##$PWD/} -> ${outdir##$PWD/}"
86 | XZ_OPT='-T0' tar -C "$extract_dir" -xf "$tarfile"
87 | rm -rf "$outdir"
88 | mkdir -p "$(dirname "$outdir")"
89 | mv -f "$extract_dir"/* "$outdir"
90 | rm -rf "$extract_dir"
91 | }
92 |
93 | # _file_is_newer ref_file src_file...
94 | # Returns true (0) if any src_file is newer (more recently modified) than ref_file.
95 | _file_is_newer() {
96 | local REF_FILE=$1 ; shift
97 | for f in $@; do
98 | [[ "$f" -nt "$REF_FILE" ]] && return 0
99 | done
100 | return 1
101 | }
102 |
103 | # npm info rsms-mkweb
104 | MKWEB_VERSION=0.2.4
105 | MKWEB_URL=https://registry.npmjs.org/rsms-mkweb/-/rsms-mkweb-${MKWEB_VERSION}.tgz
106 | MKWEB_SHA1=2c27a6cb96b5d81f04f92680b06e8c0bd8019572
107 | MKWEB_ARCHIVE=mkweb-${MKWEB_VERSION}.tgz
108 | MKWEB_EXE=$BUILD_DIR/mkweb/mkweb-${MKWEB_VERSION}
109 |
110 | if ! [ -f "$MKWEB_EXE" ]; then
111 | _download "$MKWEB_URL" $MKWEB_SHA1 "$MKWEB_ARCHIVE"
112 | MKWEB_DIR=$(dirname "$MKWEB_EXE")
113 | rm -rf "$MKWEB_DIR"
114 | _extract_tar "$(_downloaded_file "$MKWEB_ARCHIVE")" "$MKWEB_DIR"
115 | (cd "$MKWEB_DIR" &&
116 | npm i --omit dev --no-audit --no-bin-links --no-fund --no-package-lock)
117 | cp "$MKWEB_DIR/dist/mkweb" "$MKWEB_EXE"
118 | chmod +x "$MKWEB_EXE"
119 | fi
120 |
121 |
122 | # rebuild favicon in case the source files have changed
123 | if _file_is_newer favicon.ico _favicon/*.png ; then
124 | if which convert >/dev/null; then
125 | echo "generate favicon.ico from" _favicon/*.png
126 | convert _favicon/*.png favicon.ico
127 | else
128 | echo 'convert not found in $PATH. Skipping favicon.ico.' >&2
129 | echo '(try `brew install convert`)' >&2
130 | fi
131 | fi
132 |
133 |
134 | MKWEB_ARGS=( -opt )
135 | # MKWEB_ARGS=+( -verbose )
136 | # if [ -z "$1" -o "$1" != "-w" ]; then
137 | # MKWEB_ARGS+=( -opt )
138 | # fi
139 |
140 | #MKWEB_EXE=$HOME/src/mkweb/mkweb.js
141 |
142 | exec "$MKWEB_EXE" "${MKWEB_ARGS[@]}" "$@"
143 |
--------------------------------------------------------------------------------
/src/vm_map.c:
--------------------------------------------------------------------------------
1 | // virtual memory mapping
2 | // See vmem.txt for in-depth documentation
3 | // SPDX-License-Identifier: Apache-2.0
4 | #include "rsmimpl.h"
5 |
6 | #define vmtrace trace
7 | #include "vm.h"
8 |
9 | // VM_MAP_TRACE: define to enable logging a lot of info via dlog
10 | //#define VM_MAP_TRACE
11 |
12 |
13 | #if (defined(VM_MAP_TRACE) || defined(VM_TRACE)) && defined(DEBUG)
14 | #undef VM_MAP_TRACE
15 | #define VM_MAP_TRACE
16 | #define trace(fmt, args...) dlog("[vm_map] " fmt, ##args)
17 | #else
18 | #ifdef VM_MAP_TRACE
19 | #warning VM_MAP_TRACE has no effect unless DEBUG is enabled
20 | #undef VM_MAP_TRACE
21 | #endif
22 | #define trace(...) ((void)0)
23 | #endif
24 |
25 |
26 | vm_ptab_t nullable vm_ptab_alloc(rmm_t* mm) {
27 | // note: VM_PTAB_SIZE is always a multiple of PAGE_SIZE
28 | vm_ptab_t ptab = rmm_allocpages(mm, (usize)VM_PTAB_SIZE / PAGE_SIZE);
29 | if UNLIKELY(ptab == NULL)
30 | return NULL;
31 | memset(ptab, 0, VM_PTAB_SIZE);
32 | return ptab;
33 | }
34 |
35 |
36 | void vm_ptab_free(rmm_t* mm, vm_ptab_t ptab) {
37 | rmm_freepages(mm, ptab, (usize)VM_PTAB_SIZE / PAGE_SIZE);
38 | }
39 |
40 |
41 | rerr_t vm_map_init(vm_map_t* map, rmm_t* mm) {
42 | rerr_t err = rwmutex_init(&map->lock);
43 | if UNLIKELY(err)
44 | return err;
45 | vm_ptab_t ptab = vm_ptab_alloc(mm); // root page table
46 | if UNLIKELY(!ptab) {
47 | trace("failed to allocate root page table");
48 | return rerr_nomem;
49 | }
50 | trace("allocated root vm_ptab_t %p", ptab);
51 | map->root = ptab;
52 | map->mm = mm;
53 | map->min_free_vfn = 0;
54 | return 0;
55 | }
56 |
57 |
58 | static void vm_ptab_dispose(rmm_t* mm, vm_ptab_t ptab, u32 nuse, u32 level, u64 vfn) {
59 | assert(level < VM_PTAB_LEVELS);
60 |
61 | u64 npages = VM_PTAB_NPAGES(level+1);
62 |
63 | trace("vm_ptab_dispose L%u %012llx…%012llx 0x%llx nuse=%u",
64 | level+1, VM_VFN_VADDR(vfn), VM_VFN_VADDR(vfn + npages), npages, nuse);
65 |
66 | for (u32 i = 0; i < VM_PTAB_LEN && nuse != 0; i++) {
67 | bool isused = *(u64*)&ptab[i] != 0;
68 | nuse -= isused;
69 |
70 | if (level < VM_PTAB_LEVELS-1) {
71 | vm_table_t* subtable = &ptab[i].table;
72 | vm_ptab_t subptab = vm_table_ptab(subtable);
73 | if (subptab)
74 | vm_ptab_dispose(mm, subptab, subtable->nuse, level+1, vfn + npages*(u64)i);
75 | } else {
76 | vm_page_t* page = &ptab[i].page;
77 | if (page->purgeable && page->hfn) {
78 | void* haddr = (void*)vm_page_haddr(page);
79 | trace("free purgeable backing page %p", haddr);
80 | rmm_freepages(mm, haddr, 1);
81 | }
82 | }
83 | }
84 |
85 | vm_ptab_free(mm, ptab);
86 | }
87 |
88 |
89 | void vm_map_dispose(vm_map_t* map) {
90 | assert(!rwmutex_isrlocked(&map->lock)); // map should not be locked
91 | rwmutex_dispose(&map->lock);
92 | vm_ptab_dispose(map->mm, map->root, map->root_nuse, 0, 0);
93 | }
94 |
95 |
96 | static u64 alloc_backing_page(vm_map_t* map) {
97 | void* haddr = rmm_allocpages(map->mm, 1);
98 | if UNLIKELY(!haddr) {
99 | trace("FAILED to allocate backing page");
100 | panic("TODO: purge a least-recently-used page");
101 | }
102 | trace("allocated backing page %p", haddr);
103 | return (u64)haddr;
104 | }
105 |
106 |
107 | // vm_map_access returns the page table entry of a Virtual Frame Number
108 | vm_page_t* nullable vm_map_access(vm_map_t* map, u64 vfn, bool isaccess) {
109 | assertf(vfn <= VM_VFN_MAX, "invalid VFN 0x%llx", vfn);
110 | u64 index_vfn = vfn << ((sizeof(vfn)*8) - VM_VFN_BITS);
111 | vm_ptab_t ptab = map->root;
112 | vm_page_t* page = NULL;
113 |
114 | vm_map_assert_rlocked(map);
115 |
116 | for (u32 level = 0; ; level++) {
117 | u32 index = (u32)(index_vfn >> (64 - VM_PTAB_BITS));
118 |
119 | if (level == VM_PTAB_LEVELS-1) {
120 | page = &ptab[index].page;
121 | trace("access page %012llx L%u[%u] (%s)",
122 | VM_VFN_VADDR(VM_BLOCK_VFN(vfn, level)), level, index,
123 | *(u64*)page != 0 ? "mapped" : "unused");
124 |
125 | if UNLIKELY(*(u64*)page == 0) {
126 | page = NULL;
127 | break;
128 | }
129 |
130 | // if there's no backing page, allocate one
131 | if (page->hfn == 0) {
132 | u64 haddr = alloc_backing_page(map);
133 | vm_page_set_haddr(page, haddr);
134 | page->purgeable = true;
135 | }
136 | break;
137 | }
138 |
139 | vm_table_t* subtable = &ptab[index].table;
140 |
141 | trace("access L%u %012llx L%u[%u] (nuse %u)",
142 | level+1, VM_VFN_VADDR(VM_BLOCK_VFN(vfn, level)), level, index, subtable->nuse);
143 |
144 | index_vfn <<= VM_PTAB_BITS;
145 |
146 | if UNLIKELY(*(u64*)subtable == 0) {
147 | subtable = NULL;
148 | break;
149 | }
150 |
151 | subtable->accessed |= isaccess;
152 | ptab = vm_table_ptab(subtable);
153 | }
154 |
155 | return page;
156 | }
157 |
--------------------------------------------------------------------------------
/etc/website/isa/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Instruction Set Architecture
3 | ---
4 |
5 | # {{title}}
6 |
7 | RSM offers 30 general-purpose 64-bit integer [registers](#registers) and 30 64-bit floating-point registers. Instructions are 32 bits wide, fixed size. PC and jump- & branch destinations are expressed in #instructions rather than bytes.
8 |
9 |
10 | ## Instructions
11 |
12 | Most instructions accept register number or immediate value as their last argument.
13 | See [Instruction Encoding](#instruction-encoding) for more details.
14 |
15 |
16 |
17 | | Name | Inputs | Output | Semantics |
18 |
19 |
20 | {{
21 | for (let op of site.rsm_ops) {
22 | print(``)
23 | print(`| ${op.name} | `)
24 | print(`${op.args} | `)
25 | print(`${op.result == "nil" ? "–" : op.result} | `)
26 | print(`${op.semantics} | `)
27 | print(`
\n`)
28 | }
29 | }}
30 |
31 |
32 |
33 | ### Instruction encoding
34 |
35 | Instructions are fixed-size, 32 bits wide, little endian.
36 | PC and jump- & branch destinations are expressed in #instructions rather than bytes.
37 | There is room for 256 operations and 32+32 (int+fp) registers (8 bit OP, 5 bit reg)
38 | Most instructions accept reg or immediate (`i` bit is set) as last argument.
39 |
40 | ┌───────────────┬─────────┬─────────┬─────────┬─┬───────────────┐
41 | bit │3 3 2 2 2 2 2 2│2 2 2 2 1│1 1 1 1 1│1 1 1 1 │ │ │
42 | │1 0 9 8 7 6 5 4│3 2 1 0 9│8 7 6 5 4│3 2 1 0 9│8│7 6 5 4 3 2 1 0│
43 | ├───────────────┼─────────┼─────────┼─────────┼─┼───────────────┤
44 | ABCD │ D (8) │ C (5) │ B (5) │ A (5) │i│ OP (8) │
45 | ├───────────────┴─────────┼─────────┼─────────┼─┼───────────────┤
46 | ABCw │ C (13) │ B (5) │ A (5) │i│ OP (8) │
47 | ├─────────────────────────┴─────────┼─────────┼─┼───────────────┤
48 | ABw │ B (18) │ A (5) │i│ OP (8) │
49 | ├───────────────────────────────────┴─────────┼─┼───────────────┤
50 | Aw │ A (23) │i│ OP (8) │
51 | └─────────────────────────────────────────────┴─┴───────────────┘
52 |
53 | Min Max Min Max
54 | Au 0 8,388,607 As -4,194,304 4,194,303
55 | Bu 0 262,143 Bs -131,072 131,071
56 | Cu 0 8,191 Cs -4,096 4,095
57 | Du 0 255 Ds -128 127
58 |
59 |
60 | #### Instruction argument encoding
61 |
62 | | Encoding | Arguments
63 | |----------|------------------------------------
64 | | _ | (none)
65 | | A | R(A)
66 | | Au | R(A) or immediate unsigned value
67 | | As | R(A) or immediate signed value
68 | | ABv | R(A), with Bu immediate trailing u32 values
69 | | AB | R(A), R(B)
70 | | ABu | R(A), R(B) or immediate unsigned value
71 | | ABs | R(A), R(B) or immediate signed value
72 | | ABC | R(A), R(B), R(C)
73 | | ABCu | R(A), R(B), R(C) or immediate unsigned value
74 | | ABCs | R(A), R(B), R(C) or immediate signed value
75 | | ABCD | R(A), R(B), R(C), R(D)
76 | | ABCDu | R(A), R(B), R(C), R(D) or immediate unsigned value
77 | | ABCDs | R(A), R(B), R(C), R(D) or immediate signed value
78 |
79 |
80 | ## Registers
81 |
82 | ### Callee-owned registers
83 |
84 | Callee-owned (caller-saved, temporary) registers.
85 | Caller needs to save these before a call (if caller uses them.)
86 | Callee can freely use these registers.
87 |
88 | R0…R7 1st…8th integer argument/return value
89 | F0…F7 1st…8th floating-point argument/return value
90 | R8…R18 General purpose
91 | F8…F18 General purpose
92 |
93 | ### Caller-owned registers
94 |
95 | Caller-owned (callee-saved, long-lived) registers.
96 | Caller does not need to save these registers.
97 | Callee using these must save and later restore their values before returning.
98 |
99 | R19…R29 General purpose
100 | F19…F29 General purpose
101 | SP (R31) Stack pointer
102 |
103 |
104 | ### Special registers
105 |
106 | CTX (R30) Context (like AAPCS platform reg and Go's G)
107 | SP (R31) Stack pointer
108 | - (F30) Reserved (unused)
109 | FPSR (F31) Floating-point status
110 |
111 |
112 |
113 | ## Calling convention
114 |
115 | - first 8 integer argument/return values in R0…R7, rest on stack
116 | - first 8 F.P. argument/return values in F0…F7, rest on stack
117 | - anything larger than the register size goes on stack
118 | - caller saves R0…R18, F0…F18 (owned by callee)
119 | - callee saves R19…R29, F19…F29 (owned by caller)
120 | - convention inspired by [AAPCS64](https://github.com/ARM-software/abi-aa)
121 |
122 |
--------------------------------------------------------------------------------
/etc/website/style.css:
--------------------------------------------------------------------------------
1 | @import url("_css/fonts.css");
2 | @import url("_css/raster.css");
3 | @import url("_css/code.css");
4 |
5 | :root {
6 | --sansFont: Inter_v;
7 | --monoFont: ui-monospace, SFMono-Regular, SF Mono,
8 | Consolas, Liberation Mono, Menlo, monospace;
9 | @supports (font-variation-settings: normal) {
10 | --sansFont: Inter_v;
11 | --monoFont: jbmono,
12 | ui-monospace, SFMono-Regular, SF Mono,
13 | Consolas, Liberation Mono, Menlo, monospace;
14 | }
15 |
16 | /* base font size */
17 | --fontSize: 16px;
18 |
19 | /* heading size */
20 | --h1-size: 2.25rem;
21 | --h2-size: 1.5rem;
22 | --h3-size: 1.25rem;
23 | --h4-size: 1.1rem;
24 |
25 | font-weight: 460;
26 | font-synthesis: none;
27 | color-scheme: light dark;
28 |
29 | --background-color: white;
30 | --foreground-color-rgb: 0,0,0;
31 | --bgcolor-level1: rgba(0,0,0,0.1);
32 | --link-color: var(--blue);
33 | --link-underline-color: rgba(var(--foreground-color-rgb), 0.4);
34 | --blockquote-bg: #d4ffe1;
35 | --blockquote-fg: var(--foreground-color);
36 |
37 | --code-bg: #f6f8fa;
38 |
39 | @media (prefers-color-scheme: dark) {
40 | --background-color: rgb(25, 22, 20);
41 | --foreground-color-rgb: 217,209,201;
42 |
43 | --bgcolor-level1: rgba(0,0,0,0.3);
44 |
45 | --link-color: #70CAFC;
46 | --link-visited-color: #B79EFF;
47 | --link-underline-color: rgba(var(--foreground-color-rgb), 0.5);
48 | --blockquote-bg: #2a3e3a;
49 | --blockquote-fg: #d2ebe6;
50 |
51 | --code-bg: #242120;
52 |
53 | /* or just yolo:
54 | filter: invert() hue-rotate(180deg); */
55 | }
56 | }
57 |
58 | body {
59 | max-width: 800px;
60 | min-height: 100vh;
61 | box-sizing: border-box;
62 | margin: 0 auto;
63 | padding: calc(var(--lineHeight) * 2);
64 | padding-top: calc(var(--lineHeight) * 1.4);
65 |
66 | @media only screen and (max-width: 600px) {
67 | padding: var(--lineHeight);
68 | padding-bottom: calc(var(--lineHeight) * 2);
69 | }
70 | }
71 |
72 | ul > li,
73 | ol > li {
74 | margin-bottom: calc(var(--blockSpacingBottom) * 0.25);
75 | }
76 | p + ul, p + ol {
77 | margin-top: calc(var(--blockSpacingBottom) * -0.75);
78 | }
79 | li > p + ul,
80 | li > p + ol {
81 | margin-top: calc(var(--blockSpacingBottom) * -0.75);
82 | }
83 |
84 | h1, h2, h3, h4, h5, h6 {
85 | font-weight: 600;
86 | font-variation-settings: 'opsz' 32;
87 | }
88 | h1 { font-weight: 650; letter-spacing: -0.02em; }
89 | h2 { letter-spacing: 0em; }
90 | h3, h4, h5, h6 { font-family: var(--sansFont); letter-spacing: -0.01em; }
91 |
92 | h2, h3, h4, h5, h6 {
93 | & > a.anchor {
94 | position: relative;
95 | opacity: 0;
96 | height: 0;
97 | outline: none;
98 |
99 | &::after {
100 | position: absolute;
101 | opacity: 0.3;
102 | right: 0.0rem;
103 | content: "#";
104 | font-weight: 500;
105 | padding: 0 0.5em;
106 | text-align: center;
107 | }
108 | &:hover::after {
109 | opacity: 1;
110 | }
111 | /* no hover interaction? disable "hide until hover" */
112 | @media (hover: none) { opacity: 1; }
113 | /* reduced gutters? put anchor on the right side */
114 | @media only screen and (max-width: 600px) { display: block; }
115 | }
116 | &:hover > a.anchor {
117 | opacity: 1;
118 | }
119 | }
120 |
121 | em {
122 | letter-spacing: 0.01em;
123 | }
124 |
125 | .small, small { font-size: 0.75em } /* 12 @ font-size 16 */
126 |
127 | code, tt {
128 | font-feature-settings: 'calt' 0, 'kern' 1;
129 | font-weight: 400;
130 | }
131 | pre, pre > code { line-height: 1.5; }
132 |
133 | :not(pre) > code {
134 | background: var(--code-bg);
135 | border-radius: 0.2em;
136 | padding: 0 0.2em;
137 | font-size: 0.9375em; /* 15dp @ font-size 16 */
138 | }
139 |
140 | h1, h2, h3, h4, h5, h6 {
141 | & code {
142 | font-weight: inherit;
143 | background: none;
144 | border-radius: 0;
145 | }
146 | }
147 |
148 | blockquote {
149 | /* colorful callout */
150 | padding: 0.5em 1em;
151 | background: var(--blockquote-bg);
152 | color: var(--blockquote-fg);
153 | border-radius: 0.2em;
154 | /* color: rgba(var(--foreground-color-rgb), 0.8); */
155 |
156 | /* github-style indented dimmed */
157 | /* --color: rgba(var(--foreground-color-rgb), 0.2);
158 | padding-left: 1.0em;
159 | border-left: 0.2rem solid var(--color);
160 | font-weight: 449;
161 | color: rgba(var(--foreground-color-rgb), 0.7); */
162 | }
163 |
164 | table {
165 | width: 100%;
166 | & td {
167 | padding: calc(var(--baseline) * 0.5) 0.75em;
168 | }
169 | & th {
170 | padding: 0 0.75em;
171 | padding-top: calc(var(--baseline) * 0.5);
172 | padding-bottom: calc(var(--baseline) * 0.65);
173 | }
174 | }
175 |
176 | hr:not(:first-child) {
177 | margin-top: calc(var(--lineHeight) * 2);
178 | }
179 |
180 | img.logo {
181 | margin-left: -9px;
182 | margin-bottom: var(--blockSpacingBottom);
183 | }
184 |
185 | .dim { opacity:0.3 }
186 |
--------------------------------------------------------------------------------
/etc/rsm.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/etc/website/rsm.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/abuf.c:
--------------------------------------------------------------------------------
1 | // string append buffer
2 | // SPDX-License-Identifier: Apache-2.0
3 | #include "rsmimpl.h"
4 | #include "abuf.h"
5 |
6 | void abuf_c(abuf_t* s, char c) {
7 | *s->p = c;
8 | s->p = MIN(s->p + 1, s->lastp);
9 | s->len++;
10 | }
11 |
12 | void abuf_append(abuf_t* s, const char* p, usize len) {
13 | usize z = MIN(len, abuf_avail(s));
14 | memcpy(s->p, p, z);
15 | s->p += z;
16 | if (check_add_overflow(s->len, len, &s->len))
17 | s->len = USIZE_MAX;
18 | }
19 |
20 |
21 | void abuf_u64(abuf_t* s, u64 v, u32 base) {
22 | char buf[64];
23 | usize len = stru64(buf, v, base);
24 | return abuf_append(s, buf, len);
25 | }
26 |
27 |
28 | void abuf_fill(abuf_t* s, char c, usize len) {
29 | if (check_add_overflow(s->len, len, &s->len))
30 | s->len = USIZE_MAX;
31 | usize avail = abuf_avail(s);
32 | if (avail < len)
33 | len = avail;
34 | memset(s->p, c, len);
35 | s->p += len;
36 | }
37 |
38 |
39 | static const char* hexchars = "0123456789abcdef";
40 |
41 | void abuf_repr(abuf_t* s, const void* srcp, usize len) {
42 | char* p = s->p;
43 | char* lastp = s->lastp;
44 | usize nwrite = 0;
45 |
46 | for (usize i = 0; i < len; i++) {
47 | u8 c = *(u8*)srcp++;
48 | switch (c) {
49 | // \xHH
50 | case '\1'...'\x08':
51 | case 0x0E ... 0x1F:
52 | case 0x7f ... 0xFF:
53 | if (LIKELY( p + 3 < lastp )) {
54 | p[0] = '\\';
55 | p[1] = 'x';
56 | if (c < 0x10) {
57 | p[2] = '0';
58 | p[3] = hexchars[(int)c];
59 | } else {
60 | p[2] = hexchars[(int)c >> 4];
61 | p[3] = hexchars[(int)c & 0xf];
62 | }
63 | p += 4;
64 | } else {
65 | p = lastp;
66 | }
67 | nwrite += 4;
68 | break;
69 | // \c
70 | case '\t'...'\x0D':
71 | case '\\':
72 | case '"':
73 | case '\0': {
74 | static const char t[] = {'t','n','v','f','r'};
75 | if (LIKELY( p + 1 < lastp )) {
76 | p[0] = '\\';
77 | if (c == 0) p[1] = '0';
78 | else if (((usize)c - '\t') <= sizeof(t)) p[1] = t[c - '\t'];
79 | else p[1] = c;
80 | p += 2;
81 | } else {
82 | p = lastp;
83 | }
84 | nwrite++;
85 | break;
86 | }
87 | // verbatim
88 | default:
89 | *p = c;
90 | p = MIN(p + 1, lastp);
91 | nwrite++;
92 | break;
93 | }
94 | }
95 |
96 | if (check_add_overflow(s->len, nwrite, &s->len))
97 | s->len = USIZE_MAX;
98 | s->p = p;
99 | }
100 |
101 | void abuf_reprhex(abuf_t* s, const void* srcp, usize len) {
102 | char* p = s->p;
103 | char* lastp = s->lastp;
104 | usize nwrite = 0;
105 | for (usize i = 0; i < len; i++) {
106 | u8 c = *(u8*)srcp++;
107 | if (LIKELY( p + 2 < lastp )) {
108 | if (i)
109 | *p++ = ' ';
110 | if (c < 0x10) {
111 | p[0] = '0';
112 | p[1] = hexchars[c];
113 | } else {
114 | p[0] = hexchars[c >> 4];
115 | p[1] = hexchars[c & 0xf];
116 | }
117 | p += 2;
118 | } else {
119 | p = lastp;
120 | }
121 | if (i)
122 | nwrite++;
123 | nwrite += 2;
124 | }
125 | if (check_add_overflow(s->len, nwrite, &s->len))
126 | s->len = USIZE_MAX;
127 | s->p = p;
128 | }
129 |
130 |
131 | // void abuf_f64(abuf_t* s, f64 v, int ndec) {
132 | // #ifdef RSM_NO_LIBC
133 | // #warning TODO implement abuf_f64 for non-libc
134 | // assert(!"not implemented");
135 | // // TODO: consider using fmt_fp (stdio/vfprintf.c) in musl
136 | // #else
137 | // usize cap = abuf_avail(s);
138 | // int n;
139 | // if (ndec > -1) {
140 | // n = snprintf(s->p, cap+1, "%.*f", ndec, v);
141 | // } else {
142 | // n = snprintf(s->p, cap+1, "%f", v);
143 | // }
144 | // if (UNLIKELY( n <= 0 ))
145 | // return;
146 | // if (ndec < 0) {
147 | // // trim trailing zeros
148 | // char* p = &s->p[MIN((usize)n, cap) - 1];
149 | // while (*p == '0') {
150 | // p--;
151 | // }
152 | // if (*p == '.')
153 | // p++; // avoid "1.00" becoming "1." (instead, let it be "1.0")
154 | // n = (int)(uintptr)(p - s->p) + 1;
155 | // s->p[MIN((usize)n, cap)] = 0;
156 | // }
157 | // s->p += MIN((usize)n, cap);
158 | // s->len += n;
159 | // #endif
160 | // }
161 |
162 |
163 | void abuf_fmtv(abuf_t* s, const char* fmt, va_list ap) {
164 | #if defined(RSM_NO_LIBC) && !defined(__wasm__)
165 | dlog("abuf_fmtv not implemented");
166 | int n = vsnprintf(tmpbuf, sizeof(tmpbuf), format, ap);
167 | #else
168 | int n = vsnprintf(s->p, abuf_avail(s), fmt, ap);
169 | s->len += (usize)n;
170 | s->p = MIN(s->p + n, s->lastp);
171 | #endif
172 | }
173 |
174 |
175 | void abuf_fmt(abuf_t* s, const char* fmt, ...) {
176 | va_list ap;
177 | va_start(ap, fmt);
178 | abuf_fmtv(s, fmt, ap);
179 | va_end(ap);
180 | }
181 |
182 |
183 | bool abuf_endswith(const abuf_t* s, const char* str, usize len) {
184 | return s->len >= len && memcmp(s->p - len, str, len) == 0;
185 | }
186 |
--------------------------------------------------------------------------------
/etc/website/_config.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs")
2 | const os = require("os")
3 | const assert = require("assert")
4 |
5 | const rsm_h_file = "../../src/rsm.h"
6 | const ops_json_file = "ops.json"
7 |
8 | module.exports = ({
9 | site, // mutable object describing the site
10 | hljs, // HighlightJS module (NPM: highlight.js)
11 | markdown, // Markdown module (NPM: markdown-wasm)
12 | glob, // glob function (NPM: miniglob)
13 | cli_opts,
14 | mtime,
15 | build_site,
16 | read_file, // "node:fs/promises".readFile
17 | write_file, // "node:fs/promises".writeFile + mkdir if needed
18 | }) => {
19 | // called when program starts
20 | //site.outdir = ".."
21 |
22 | // og image comes from curl -L --silent https://github.com/rsms/rsm | grep og:image
23 | site.og_image = "https://repository-images.githubusercontent.com/459341066/b1653976-4237-49d3-b15b-a1c4dc8c3b35"
24 | //console.log(site)
25 |
26 | // configure highlight.js
27 | hljs.registerLanguage("rsm", require("./_hl/rsm"))
28 | hljs.registerLanguage("asmarm", require("./_hl/armasm"))
29 | hljs.registerLanguage("asmx86", require("./_hl/x86asm"))
30 | hljs.registerLanguage("wast", require("./_hl/wasm"))
31 | hljs.registerLanguage("bnf", require("./_hl/abnf"))
32 |
33 | // ignore some source files
34 | let defaultIgnoreFilter = site.ignoreFilter
35 | site.ignoreFilter = (name, path) =>
36 | defaultIgnoreFilter.call(site, name, path) ||
37 | path == "build.sh"
38 |
39 | // don't rebuild when ops_json_file changes
40 | site.ignoreWatchFilter = (name, path) =>
41 | path == ops_json_file
42 |
43 | // these optional callbacks can return a Promise to cause build process to wait
44 | //
45 | site.onBeforeBuild = async (files) => {
46 | // called when .pages has been populated
47 | // console.log("onBeforeBuild pages:", site.pages)
48 | // console.log("onBeforeBuild files:", files)
49 | site.rsm_ops = await gen_rsm_ops(mtime, read_file, write_file)
50 | }
51 |
52 | // site.onAfterBuild = (files) => {
53 | // // called after site has been generated
54 | // // console.log("onAfterBuild")
55 | // }
56 |
57 | // rebuild when rsm_h_file changes
58 | if (fs.existsSync(rsm_h_file) && cli_opts.watch) {
59 | let rebuild_timer = null
60 | fs.watch(rsm_h_file, {persistent:false}, (event, filename) => {
61 | clearTimeout(rebuild_timer)
62 | rebuild_timer = setTimeout(() => {
63 | if (mtime(rsm_h_file) > mtime(ops_json_file))
64 | build_site()
65 | rebuild_timer = null
66 | }, 50)
67 | })
68 | }
69 | }
70 |
71 |
72 | async function gen_rsm_ops(mtime, read_file, write_file) {
73 | if (!fs.existsSync(rsm_h_file) || mtime(rsm_h_file) < mtime(ops_json_file))
74 | return JSON.parse(fs.readFileSync(ops_json_file, {encoding:"utf8"}))
75 |
76 | console.log(`generate ${ops_json_file} from ${rsm_h_file}`)
77 |
78 | let rsm_h = fs.readFileSync(rsm_h_file, {encoding:"utf8"})
79 | let ops = []
80 |
81 | let lines = rsm_h.trim().split("\n")
82 | let start_prefix = '#define RSM_FOREACH_OP('
83 | let i = find_line_with_prefix(lines, start_prefix)
84 | assert(i != -1, `no line with prefix "${start_prefix}"`)
85 | for (i++; i < lines.length; i++) {
86 | let line = lines[i].trim()
87 | if (line[0] != '_') {
88 | if (line[0] != '\\') // end
89 | break
90 | continue
91 | }
92 | // line = "_(name, arguments, result, asmname, semantics)"
93 | // e.g.
94 | // _( COPY , ABu , reg , "copy" /* RA = Bu -- aka "move" */)
95 | let re =
96 | /^_\(\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*([^,\s]+)\s*,\s*("[^,\s]+")\s*,\s*(".*")\s*\)/
97 | let m = line.match(re)
98 | try {
99 | let [_, id, args, result, name, semantics] = m
100 | name = JSON.parse(name)
101 | semantics = JSON.parse(semantics)
102 | ops.push({id, args, result, name, semantics})
103 | //console.log({id, args, result, name, semantics})
104 | } catch (err) {
105 | console.error("offending line:\n" + line)
106 | console.error("line.match =>", m)
107 | throw err
108 | }
109 | }
110 |
111 | await Promise.all([
112 | write_file(ops_json_file, JSON.stringify(ops, null, "\t"), {encoding:"utf8"}),
113 | ].concat(
114 | ops.map(op => gen_rsm_op_page(op, mtime, read_file, write_file))
115 | ))
116 | return ops
117 | }
118 |
119 |
120 | async function gen_rsm_op_page(op, mtime, read_file, write_file) {
121 | let dstfile = "isa/op." + op.name + ".md"
122 | let curr_txt = ""
123 | if (mtime(dstfile) > 0) {
124 | curr_txt = await read_file(dstfile, "utf8")
125 | if (curr_txt.indexOf("x-autogenerated: true") == -1) {
126 | // don't touch customized page
127 | return
128 | }
129 | }
130 | let txt = `
131 | ---
132 | title: ${op.name}
133 | template: template-op
134 | x-autogenerated: true
135 | ---
136 |
137 | \`${op.name} ${op.args} → ${op.result}\`
138 |
139 | ${op.semantics}
140 | `.trim() + "\n"
141 | if (txt != curr_txt) {
142 | console.log(`generate ${dstfile}`)
143 | await write_file(dstfile, txt, "utf8")
144 | }
145 | }
146 |
147 |
148 | function find_line_with_prefix(lines, prefix) {
149 | for (let i = 0; i < lines.length; i++) {
150 | if (lines[i].startsWith('#define RSM_FOREACH_OP('))
151 | return i
152 | }
153 | return -1
154 | }
155 |
--------------------------------------------------------------------------------
/etc/website/_hl/armasm.js:
--------------------------------------------------------------------------------
1 | /*
2 | Language: ARM Assembly
3 | Author: Dan Panzarella
4 | Description: ARM Assembly including Thumb and Thumb2 instructions
5 | Category: assembler
6 | */
7 |
8 | /** @type LanguageFn */
9 | function armasm(hljs) {
10 | // local labels: %?[FB]?[AT]?\d{1,2}\w+
11 |
12 | const COMMENT = {
13 | variants: [
14 | hljs.COMMENT('^[ \\t]*(?=#)', '$', {
15 | relevance: 0,
16 | excludeBegin: true
17 | }),
18 | hljs.COMMENT('[;@]', '$', {
19 | relevance: 0
20 | }),
21 | hljs.C_LINE_COMMENT_MODE,
22 | hljs.C_BLOCK_COMMENT_MODE
23 | ]
24 | };
25 |
26 | return {
27 | name: 'ARM Assembly',
28 | case_insensitive: true,
29 | aliases: ['arm'],
30 | keywords: {
31 | $pattern: '\\.?' + hljs.IDENT_RE,
32 | meta:
33 | // GNU preprocs
34 | '.2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ' +
35 | // ARM directives
36 | 'ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ',
37 | built_in:
38 | 'r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 ' + // standard registers
39 | 'pc lr sp ip sl sb fp ' + // typical regs plus backward compatibility
40 | 'a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 ' + // more regs and fp
41 | 'p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 ' + // coprocessor regs
42 | 'c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 ' + // more coproc
43 | 'q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 ' + // advanced SIMD NEON regs
44 |
45 | // program status registers
46 | 'cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf ' +
47 | 'spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf ' +
48 |
49 | // NEON and VFP registers
50 | 's0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 ' +
51 | 's16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 ' +
52 | 'd0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 ' +
53 | 'd16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 ' +
54 |
55 | '{PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @'
56 | },
57 | contains: [
58 | {
59 | className: 'keyword',
60 | begin: '\\b(' + // mnemonics
61 | 'adc|' +
62 | '(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|' +
63 | 'and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|' +
64 | 'bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|' +
65 | 'setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|' +
66 | 'ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|' +
67 | 'mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|' +
68 | 'mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|' +
69 | 'mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|' +
70 | 'rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|' +
71 | 'stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|' +
72 | '[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|' +
73 | 'wfe|wfi|yield' +
74 | ')' +
75 | '(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?' + // condition codes
76 | '[sptrx]?' + // legal postfixes
77 | '(?=\\s)' // followed by space
78 | },
79 | COMMENT,
80 | hljs.QUOTE_STRING_MODE,
81 | {
82 | className: 'string',
83 | begin: '\'',
84 | end: '[^\\\\]\'',
85 | relevance: 0
86 | },
87 | {
88 | className: 'title',
89 | begin: '\\|',
90 | end: '\\|',
91 | illegal: '\\n',
92 | relevance: 0
93 | },
94 | {
95 | className: 'number',
96 | variants: [
97 | { // hex
98 | begin: '[#$=]?0x[0-9a-f]+'
99 | },
100 | { // bin
101 | begin: '[#$=]?0b[01]+'
102 | },
103 | { // literal
104 | begin: '[#$=]\\d+'
105 | },
106 | { // bare number
107 | begin: '\\b\\d+'
108 | }
109 | ],
110 | relevance: 0
111 | },
112 | {
113 | className: 'symbol',
114 | variants: [
115 | { // GNU ARM syntax
116 | begin: '^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:'
117 | },
118 | { // ARM syntax
119 | begin: '^[a-z_\\.\\$][a-z0-9_\\.\\$]+'
120 | },
121 | { // label reference
122 | begin: '[=#]\\w+'
123 | }
124 | ],
125 | relevance: 0
126 | }
127 | ]
128 | };
129 | }
130 |
131 | module.exports = armasm;
132 |
--------------------------------------------------------------------------------
/etc/website/v1/_vttparse.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | /**
4 | * See spec: https://www.w3.org/TR/webvtt1/#file-structure
5 | */
6 |
7 | function ParserError (message, error) {
8 | this.message = message;
9 | this.error = error;
10 | }
11 |
12 | ParserError.prototype = Object.create(Error.prototype);
13 |
14 | const TIMESTAMP_REGEXP = /([0-9]{1,2})?:?([0-9]{2}):([0-9]{2}\.[0-9]{2,3})/;
15 |
16 | function parse (input, options) {
17 | if (!options) {
18 | options = {};
19 | }
20 |
21 | const { meta = false, strict = true } = options;
22 |
23 | if (typeof input !== 'string') {
24 | throw new ParserError('Input must be a string');
25 | }
26 |
27 | input = input.trim();
28 | input = input.replace(/\r\n/g, '\n');
29 | input = input.replace(/\r/g, '\n');
30 |
31 | const parts = input.split('\n\n');
32 | const header = parts.shift();
33 |
34 | if (!header.startsWith('WEBVTT')) {
35 | throw new ParserError('Must start with "WEBVTT"');
36 | }
37 |
38 | const headerParts = header.split('\n');
39 |
40 | const headerComments = headerParts[0].replace('WEBVTT', '');
41 |
42 | if (headerComments.length > 0
43 | && (headerComments[0] !== ' ' && headerComments[0] !== '\t')
44 | ) {
45 | throw new ParserError('Header comment must start with space or tab');
46 | }
47 |
48 | // nothing of interests, return early
49 | if (parts.length === 0 && headerParts.length === 1) {
50 | return { valid: true, strict, cues: [], errors: [] };
51 | }
52 |
53 | // if (!meta && headerParts.length > 1 && headerParts[1] !== '') {
54 | // throw new ParserError('Missing blank line after signature');
55 | // }
56 |
57 | const { cues, errors } = parseCues(parts, strict);
58 |
59 | if (strict && errors.length > 0) {
60 | throw errors[0];
61 | }
62 |
63 | const headerMeta = meta ? parseMeta(headerParts) : null;
64 |
65 | const result = { valid: errors.length === 0, strict, cues, errors };
66 |
67 | if (meta) {
68 | result.meta = headerMeta;
69 | }
70 |
71 | return result;
72 | }
73 |
74 | function parseMeta (headerParts) {
75 | const meta = {};
76 | headerParts.slice(1).forEach(header => {
77 | const splitIdx = header.indexOf(':');
78 | const key = header.slice(0, splitIdx).trim();
79 | const value = header.slice(splitIdx + 1).trim();
80 | meta[key] = value;
81 | });
82 | return Object.keys(meta).length > 0 ? meta : null;
83 | }
84 |
85 | function parseCues (cues, strict) {
86 | const errors = [];
87 |
88 | const parsedCues = cues
89 | .map((cue, i) => {
90 | try {
91 | return parseCue(cue, i, strict);
92 | } catch (e) {
93 | errors.push(e);
94 | return null;
95 | }
96 | })
97 | .filter(Boolean);
98 |
99 | return {
100 | cues: parsedCues,
101 | errors
102 | };
103 | }
104 |
105 | /**
106 | * Parse a single cue block.
107 | *
108 | * @param {array} cue Array of content for the cue
109 | * @param {number} i Index of cue in array
110 | *
111 | * @returns {object} cue Cue object with start, end, text and styles.
112 | * Null if it's a note
113 | */
114 | function parseCue (cue, i, strict) {
115 | let identifier = '';
116 | let start = 0;
117 | let end = 0.01;
118 | let text = '';
119 | let styles = '';
120 |
121 | // split and remove empty lines
122 | const lines = cue.split('\n').filter(Boolean);
123 |
124 | if (lines.length > 0 && lines[0].trim().startsWith('NOTE')) {
125 | return null;
126 | }
127 |
128 | if (lines.length === 1 && !lines[0].includes('-->')) {
129 | throw new ParserError(`Cue identifier cannot be standalone (cue #${i})`);
130 | }
131 |
132 | if (lines.length > 1 &&
133 | !(lines[0].includes('-->') || lines[1].includes('-->'))) {
134 | const msg = `Cue identifier needs to be followed by timestamp (cue #${i})`;
135 | throw new ParserError(msg);
136 | }
137 |
138 | if (lines.length > 1 && lines[1].includes('-->')) {
139 | identifier = lines.shift();
140 | }
141 |
142 | const times = typeof lines[0] === 'string' && lines[0].split(' --> ');
143 |
144 | if (times.length !== 2 ||
145 | !validTimestamp(times[0]) ||
146 | !validTimestamp(times[1])) {
147 |
148 | throw new ParserError(`Invalid cue timestamp (cue #${i})`);
149 | }
150 |
151 | start = parseTimestamp(times[0]);
152 | end = parseTimestamp(times[1]);
153 |
154 | if (strict) {
155 | if (start > end) {
156 | throw new ParserError(`Start timestamp greater than end (cue #${i})`);
157 | }
158 |
159 | if (end <= start) {
160 | throw new ParserError(`End must be greater than start (cue #${i})`);
161 | }
162 | }
163 |
164 | if (!strict && end < start) {
165 | throw new ParserError(
166 | `End must be greater or equal to start when not strict (cue #${i})`
167 | );
168 | }
169 |
170 | // TODO better style validation
171 | styles = times[1].replace(TIMESTAMP_REGEXP, '').trim();
172 |
173 | lines.shift();
174 |
175 | text = lines.join('\n');
176 |
177 | if (!text) {
178 | return false;
179 | }
180 |
181 | return { identifier, start, end, text, styles };
182 | }
183 |
184 | function validTimestamp (timestamp) {
185 | return TIMESTAMP_REGEXP.test(timestamp);
186 | }
187 |
188 | function parseTimestamp (timestamp) {
189 | const matches = timestamp.match(TIMESTAMP_REGEXP);
190 | let secs = parseFloat(matches[1] || 0) * 60 * 60; // hours
191 | secs += parseFloat(matches[2]) * 60; // mins
192 | secs += parseFloat(matches[3]);
193 | // secs += parseFloat(matches[4]);
194 | return secs;
195 | }
196 |
197 | module.exports = { ParserError, parse };
198 |
--------------------------------------------------------------------------------
/src/wasm.c:
--------------------------------------------------------------------------------
1 | // WASM API
2 | // SPDX-License-Identifier: Apache-2.0
3 | #if defined(__wasm__) && !defined(__wasi__)
4 | #include "rsmimpl.h"
5 |
6 |
7 | // WASM memory layout
8 | // 0 __data_end __heap_base
9 | // ┌─────────────┼─────────────┼───────────···
10 | // │ data │ stack │ heap
11 | // └─────────────┴─────────────┴───────────···
12 | extern u8 __heap_base, __data_end; // symbols provided by wasm-ld
13 |
14 | // WASM instance state
15 | static rmem mem; // the one memory allocator, owning the wasm heap
16 | static u64 iregs[RSM_NREGS] = {0}; // register state
17 | static char* tmpbuf; // for temporary formatting etc
18 | static usize tmpbufcap;
19 |
20 |
21 | // WASM imports (provided by rsm.js)
22 | WASM_IMPORT void wasm_log(const char* s, usize len);
23 |
24 |
25 | // implementation of logv
26 | void logv(const char* _Nonnull format, va_list ap) {
27 | int n = vsnprintf(tmpbuf, tmpbufcap, format, ap);
28 | if (n < 0) n = 0;
29 | wasm_log(tmpbuf, MIN((usize)n, tmpbufcap));
30 | }
31 |
32 |
33 | // implementation of libc-compatible write()
34 | isize write(int fd, const void* buf, usize nbyte) {
35 | return rerr_not_supported;
36 | }
37 |
38 |
39 | // implementation of libc-compatible write()
40 | isize read(int fd, void* buf, usize nbyte) {
41 | return rerr_not_supported;
42 | }
43 |
44 |
45 | // memory allocator interface exported to WASM host
46 | WASM_EXPORT void* wmalloc(usize nbyte) { return rmem_alloc(mem, nbyte); }
47 | WASM_EXPORT void* wmresize(void* p, usize newsize, usize oldsize) {
48 | return rmem_resize(mem, p, newsize, oldsize); }
49 | WASM_EXPORT void wmfree(void* p, usize size) { return rmem_free(mem, p, size); }
50 |
51 |
52 | // winit is called by rsm.js to initialize the WASM instance state
53 | WASM_EXPORT bool winit(usize memsize) {
54 | void* heap = &__heap_base;
55 | usize heapsize = (memsize < (usize)heap) ? 0 : memsize - (usize)heap;
56 |
57 | #if DEBUG
58 | // we haven't allocated tmpbuf yet, so use stack space for early log messages
59 | char tmpbufstk[512];
60 | tmpbufcap = sizeof(tmpbufstk);
61 | tmpbuf = tmpbufstk;
62 | void* stackbase = &__data_end;
63 | log("data: %zu B, stack: %p-%p (%zu B), heap %p (%zu B)",
64 | (usize)stackbase, stackbase, heap, (usize)(heap - stackbase), heap, heapsize);
65 | #endif
66 |
67 | // first chunk of heap used for tmpbuf
68 | tmpbufcap = (heapsize > 4096*4) ? 4096 : 512;
69 | tmpbuf = heap;
70 | heap += tmpbufcap;
71 | heapsize -= tmpbufcap;
72 |
73 | // rest of heap is owned by our one allocator
74 | mem = rmem_mkbufalloc(heap, heapsize);
75 |
76 | // initialize RSM library
77 | return rsm_init();
78 | }
79 |
80 |
81 | //———————————————————————————————————————————————————————————————————————————————————
82 | #ifndef RSM_NO_ASM
83 |
84 |
85 | struct { // compilation result
86 | // IF THIS CHANGES, UPDATE rsm.js
87 | rromimg* rom_img;
88 | usize rom_imgsize;
89 | const char* nullable errmsg;
90 | } cresult;
91 |
92 |
93 | static bool wdiaghandler(const rdiag* d, void* _) {
94 | cresult.errmsg = d->msg;
95 | return d->code != 1; // stop on error
96 | }
97 |
98 |
99 | WASM_EXPORT void* wcompile(const char* srcname, const char* srcdata, usize srclen) {
100 | rasm a = {
101 | .mem = mem,
102 | .diaghandler = wdiaghandler,
103 | .srcname = srcname,
104 | .srcdata = srcdata,
105 | .srclen = srclen,
106 | };
107 |
108 | // reset result data
109 | cresult.errmsg = NULL;
110 | if (cresult.rom_img)
111 | rmem_free(mem, cresult.rom_img, cresult.rom_imgsize);
112 | cresult.rom_img = NULL;
113 | cresult.rom_imgsize = 0;
114 |
115 | // parse assembly source
116 | rnode* mod = rasm_parse(&a);
117 | if UNLIKELY(mod == NULL) {
118 | cresult.errmsg = "failed to allocate memory for parser";
119 | return &cresult;
120 | }
121 | if UNLIKELY(a.errcount)
122 | goto end;
123 |
124 | // build ROM
125 | rrom rom = {0};
126 | rerr_t err = rasm_gen(&a, mod, mem, &rom);
127 | if (err) {
128 | if (cresult.errmsg == NULL)
129 | cresult.errmsg = rerr_str(err);
130 | goto end;
131 | }
132 |
133 | cresult.rom_img = rom.img;
134 | cresult.rom_imgsize = rom.imgsize;
135 |
136 | end:
137 | rasm_free_rnode(&a, mod);
138 | return &cresult;
139 | }
140 |
141 |
142 | #endif // !defined(RSM_NO_ASM)
143 | //———————————————————————————————————————————————————————————————————————————————————
144 |
145 |
146 | WASM_EXPORT isize wfmtprog(char* buf, usize bufcap, rromimg* rom_img, usize rom_imgsize) {
147 | // WASM_EXPORT usize wfmtprog(char* buf, usize bufcap, rinstr* nullable ip, u32 ilen)
148 | if (!rom_img || rom_imgsize == 0)
149 | return 0;
150 | rrom rom = { .img=rom_img, .imgsize=rom_imgsize };
151 | rerr_t err = rsm_loadrom(&rom);
152 | if (err)
153 | return -1;
154 | return rsm_fmtprog(buf, bufcap, rom.code, rom.codelen, /*rfmtflag*/0);
155 | }
156 |
157 |
158 | WASM_EXPORT rerr_t wvmexec(u64* iregs, rromimg* rom_img, usize rom_imgsize) {
159 | // allocate instance memory
160 | static usize memsize = 1024*1024;
161 | void* membase = rmem_alloc(mem, memsize);
162 | if (!membase)
163 | return rerr_nomem;
164 |
165 | // load ROM
166 | rrom rom = { .img=rom_img, .imgsize=rom_imgsize };
167 | rerr_t err = rsm_loadrom(&rom);
168 |
169 | // run
170 | if (!err)
171 | err = rsm_vmexec(&rom, iregs, membase, memsize);
172 |
173 | // free instance memory
174 | rmem_free(mem, membase, memsize);
175 | return err;
176 | }
177 |
178 |
179 | WASM_EXPORT u64* wvmiregs() {
180 | return iregs;
181 | }
182 |
183 | #endif // __wasm__
184 |
--------------------------------------------------------------------------------
/etc/website/rom/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: ROM image layout
3 | ---
4 |
5 | # {{title}}
6 |
7 | > This documentation is work in progress
8 |
9 | ## Example
10 |
11 | Source code:
12 |
13 | ```rsm
14 | import console.putch
15 | data c = 0x63
16 | fun _helper() i32
17 | R0 = c
18 | ret
19 | fun upcase(i32) i32
20 | R0 = R0 - 0x20
21 | ret
22 | fun main()
23 | call _helper
24 | call upcase
25 | call console.putch
26 | ret
27 | ```
28 |
29 |
30 | ROM image:
31 |
32 | ```
33 | 0 1 2 3 4 5 6 7 8 9 a b c d e f
34 | ┌─────────────────────────────────────────────────
35 | 0000 │ 52 53 4d 00 00 "RSM\0" version 0
36 | … │ 00 01 types section, 10 bytes
37 | … │ 02 2 types
38 | … │ 00 00 type 0 ()->()
39 | … │ 01 03 01 03 type 1 (i32)->(i32)
40 | … │ 01 03 type 2 (i32)->() …
41 | 0010 │ 00 …
42 | … │ 03 10 import section, 16 bytes
43 | … │ 01 1 import
44 | … │ 07 63 6f 6e 73 6f 6c 65 01 "console", 1 function
45 | … │ 05 70 75 5 "pu …
46 | 0020 │ 74 63 02 … tc" (i32)->()
47 | … │ 04 11 export section, 17 bytes
48 | … │ 02 2 exports
49 | … │ 06 75 70 63 61 73 65 01 02 "upcase" (i32)->(i32) code[2]
50 | … │ 04 4 …
51 | 0030 │ 6d 61 69 6e 00 04 … "main" ()->() code[4]
52 | … │ 02 05 data section, 5 bytes
53 | … │ 02 4B alignment of data
54 | … │ 00 00 00 63 data i32 0x63
55 | … │ 01 22 code section, 34 bytes
56 | … │ 00 padding
57 | 0040 │ ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff vmcode …
58 | 0050 │ ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff … vmcode
59 | 0060 │ 05 1c name section, 28 bytes
60 | … │ 03 3 entries
61 | … │ 01 07 5f 68 65 6c 70 65 72 00 kind=func 7 "_helper" code[0]
62 | … │ 00 06 75 kind=func 6 "u …
63 | 0070 │ 70 63 61 73 65 00 02 … pcase" code[2]
64 | … │ 00 04 6d 61 69 6e 04 kind=func 4 "main" code[4]
65 | … │ 00 00 padding for constant data
66 | 0080 │ 00 00 d2 04 00 00 2e 16 00 00 constant data rest of file …
67 | ```
68 |
69 | ## Structure
70 |
71 | ```
72 | module:
73 | ┌────────────┬────────────┬──────────┬──────┐
74 | │ R S M 00 │ version u8 │ flags u8 │ body │
75 | └────────────┴────────────┴──────────┴──────┘
76 | body:
77 | if LZ4 in flags then compressed body:
78 | ┌───────────────────────────┬─────────────────────────┐
79 | │ uncompressed_size varuint │ compressed_data u8[...] │
80 | └───────────────────────────┴─────────────────────────┘
81 | else uncompressed body:
82 | ┌─────────┐
83 | │ section │*
84 | └─────────┘
85 | section:
86 | ┌─────────┬──────────────┬───────────────────┐
87 | │ kind u8 │ size varuint │ contents u8[size] │
88 | └─────────┴──────────────┴───────────────────┘
89 | 0x00 types section:
90 | ┌───────────────┬────────────────┐
91 | │ count varuint │ funtype[count] │
92 | └───────────────┴───────╥────────┘
93 | ╒════════════════╤════╩═════════╤═════════════════╤═══════════════╕
94 | │ paramc varuint │ type[paramc] │ resultc varuint │ type[resultc] │
95 | └────────────────┴──────────────┴─────────────────┴───────────────┘
96 | 0x01 code section:
97 | ┌─────────────────┐
98 | │ instr u32[...] │
99 | └─────────────────┘
100 | 0x02 data section:
101 | ┌──────────┬──────┐
102 | │ align u8 │ data │
103 | └──────────┴──────┘
104 | 0x03 import section:
105 | ┌────────────────┬─────────────────┐
106 | │ mcount varuint │ mimport[mcount] │
107 | └────────────────┴───────╥─────────┘
108 | ╒═════════════════╤════╩════════════════╤════════════════╤═════════════════╕
109 | │ namelen varuint │ namestr u8[namelen] │ fcount varuint │ fimport[fcount] │
110 | └─────────────────┴─────────────────────┴────────────────┴─╥───────────────┘
111 | ╒═════════════════╤═════════════════════╤══════════════╩╕
112 | │ namelen varuint │ namestr u8[namelen] │ typei varuint │
113 | └─────────────────┴─────────────────────┴───────────────┘
114 | 0x04 export section:
115 | ┌───────────────┬───────────────┐
116 | │ count varuint │ export[count] │
117 | └───────────────┴───────╥───────┘
118 | ╒═════════════════╤═══╩═════════════════╤═══════════════╤═══════════════╕
119 | │ namelen varuint │ namestr u8[namelen] │ typei varuint │ codei varuint │
120 | └─────────────────┴─────────────────────┴───────────────┴───────────────┘
121 | 0x05 name section:
122 | ┌───────────────┬─────────────┐
123 | │ count varuint │ name[count] │
124 | └───────────────┴──────╥──────┘
125 | ╒═════════╤══════════╩══════╤══════════════════╤═══════════════╕
126 | │ kind u8 │ namelen varuint │ name u8[namelen] │ codei varuint │
127 | └─────────┴─────────────────┴──────────────────┴───────────────┘
128 | kind: 00 module, 01 func, 02 label
129 | type:
130 | ┌────┐ 0x00 i1 0x03 i32 0x08 f32
131 | │ u8 │ 0x01 i8 0x04 i64 0x09 f64
132 | └────┘ 0x02 i16
133 |
134 | ```
135 |
--------------------------------------------------------------------------------
/etc/rom-layout.txt:
--------------------------------------------------------------------------------
1 |
2 | ROM image layout
3 |
4 | Example:
5 | Source code:
6 | import console.putch
7 | data c = 0x63
8 | fun _helper() i32
9 | R0 = c
10 | ret
11 | fun upcase(i32) i32
12 | R0 = R0 - 0x20
13 | ret
14 | fun main()
15 | call _helper
16 | call upcase
17 | call console.putch
18 | ret
19 |
20 | VM code:
21 | 0 loadk R0 0x00 // _helper
22 | 1 ret
23 | 2 mul R0 R0 R2 // upcase
24 | 3 ret
25 | 4 call -4 // main
26 | 5 call -3
27 | 6 icall 0
28 | 7 ret
29 |
30 | Encoding:
31 |
32 | 0 1 2 3 4 5 6 7 8 9 a b c d e f
33 | ┌─────────────────────────────────────────────────
34 | 0000 │ 52 53 4d 00 00 "RSM\0" version 0
35 | … │ 00 01 types section, 10 bytes
36 | … │ 02 2 types
37 | … │ 00 00 type 0 ()->()
38 | … │ 01 03 01 03 type 1 (i32)->(i32)
39 | … │ 01 03 type 2 (i32)->() …
40 | 0010 │ 00 …
41 | … │ 03 10 import section, 16 bytes
42 | … │ 01 1 import
43 | … │ 07 63 6f 6e 73 6f 6c 65 01 "console", 1 function
44 | … │ 05 70 75 5 "pu …
45 | 0020 │ 74 63 02 … tc" (i32)->()
46 | … │ 04 11 export section, 17 bytes
47 | … │ 02 2 exports
48 | … │ 06 75 70 63 61 73 65 01 02 "upcase" (i32)->(i32) code[2]
49 | … │ 04 4 …
50 | 0030 │ 6d 61 69 6e 00 04 … "main" ()->() code[4]
51 | … │ 02 05 data section, 5 bytes
52 | … │ 02 4B alignment of data
53 | … │ 00 00 00 63 data i32 0x63
54 | … │ 01 22 code section, 34 bytes
55 | … │ 00 padding
56 | 0040 │ ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff vmcode …
57 | 0050 │ ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff … vmcode
58 | 0060 │ 05 1c name section, 28 bytes
59 | … │ 03 3 entries
60 | … │ 01 07 5f 68 65 6c 70 65 72 00 kind=func 7 "_helper" code[0]
61 | … │ 00 06 75 kind=func 6 "u …
62 | 0070 │ 70 63 61 73 65 00 02 … pcase" code[2]
63 | … │ 00 04 6d 61 69 6e 04 kind=func 4 "main" code[4]
64 | … │ 00 00 padding for constant data
65 | 0080 │ 00 00 d2 04 00 00 2e 16 00 00 constant data rest of file …
66 |
67 |
68 | module:
69 | ┌────────────┬────────────┬──────────┬──────┐
70 | │ R S M 00 │ version u8 │ flags u8 │ body │
71 | └────────────┴────────────┴──────────┴──────┘
72 | body:
73 | if LZ4 in flags then compressed body:
74 | ┌───────────────────────────┬─────────────────────────┐
75 | │ uncompressed_size varuint │ compressed_data u8[...] │
76 | └───────────────────────────┴─────────────────────────┘
77 | else uncompressed body:
78 | ┌─────────┐
79 | │ section │*
80 | └─────────┘
81 | section:
82 | ┌─────────┬──────────────┬───────────────────┐
83 | │ kind u8 │ size varuint │ contents u8[size] │
84 | └─────────┴──────────────┴───────────────────┘
85 | 0x00 types section:
86 | ┌───────────────┬────────────────┐
87 | │ count varuint │ funtype[count] │
88 | └───────────────┴───────╥────────┘
89 | ╒════════════════╤════╩═════════╤═════════════════╤═══════════════╕
90 | │ paramc varuint │ type[paramc] │ resultc varuint │ type[resultc] │
91 | └────────────────┴──────────────┴─────────────────┴───────────────┘
92 | 0x01 code section:
93 | ┌─────────────────┐
94 | │ instr u32[...] │
95 | └─────────────────┘
96 | 0x02 data section:
97 | ┌──────────┬──────┐
98 | │ align u8 │ data │
99 | └──────────┴──────┘
100 | 0x03 import section:
101 | ┌────────────────┬─────────────────┐
102 | │ mcount varuint │ mimport[mcount] │
103 | └────────────────┴───────╥─────────┘
104 | ╒═════════════════╤════╩════════════════╤════════════════╤═════════════════╕
105 | │ namelen varuint │ namestr u8[namelen] │ fcount varuint │ fimport[fcount] │
106 | └─────────────────┴─────────────────────┴────────────────┴─╥───────────────┘
107 | ╒═════════════════╤═════════════════════╤══════════════╩╕
108 | │ namelen varuint │ namestr u8[namelen] │ typei varuint │
109 | └─────────────────┴─────────────────────┴───────────────┘
110 | 0x04 export section:
111 | ┌───────────────┬───────────────┐
112 | │ count varuint │ export[count] │
113 | └───────────────┴───────╥───────┘
114 | ╒═════════════════╤═══╩═════════════════╤═══════════════╤═══════════════╕
115 | │ namelen varuint │ namestr u8[namelen] │ typei varuint │ codei varuint │
116 | └─────────────────┴─────────────────────┴───────────────┴───────────────┘
117 | 0x05 name section:
118 | ┌───────────────┬─────────────┐
119 | │ count varuint │ name[count] │
120 | └───────────────┴──────╥──────┘
121 | ╒═════════╤══════════╩══════╤══════════════════╤═══════════════╕
122 | │ kind u8 │ namelen varuint │ name u8[namelen] │ codei varuint │
123 | └─────────┴─────────────────┴──────────────────┴───────────────┘
124 | kind: 00 module, 01 func, 02 label
125 | type:
126 | ┌────┐ 0x00 i1 0x03 i32 0x08 f32
127 | │ u8 │ 0x01 i8 0x04 i64 0x09 f64
128 | └────┘ 0x02 i16
129 |
130 |
131 |
--------------------------------------------------------------------------------
/src/vm_map_findspace.c:
--------------------------------------------------------------------------------
1 | // virtual memory mapping, finding free space
2 | // SPDX-License-Identifier: Apache-2.0
3 | #include "rsmimpl.h"
4 | #define vmtrace trace
5 | #include "vm.h"
6 |
7 | // VM_MAP_FINDSPACE_TRACE: define to enable logging a lot of info via dlog
8 | //#define VM_MAP_FINDSPACE_TRACE
9 |
10 |
11 | #if (defined(VM_MAP_FINDSPACE_TRACE) || defined(VM_MAP_TRACE) || defined(VM_TRACE)) && \
12 | defined(DEBUG)
13 | #undef VM_MAP_FINDSPACE_TRACE
14 | #define VM_MAP_FINDSPACE_TRACE
15 | #define trace(fmt, args...) dlog("[vm_map] " fmt, ##args)
16 | static void trace_table(vm_table_t* table, u32 level, u64 vfn) {
17 | u64 block_vfn = VM_BLOCK_VFN(vfn, level);
18 | u64 vfn_offset = vfn > block_vfn ? vfn - block_vfn : 0;
19 | trace("L%u at %012llx is %s (offset 0x%llx, nuse %u)",
20 | level+1, VM_VFN_VADDR(block_vfn),
21 | ( (*(u64*)table == 0) ? "empty" :
22 | (table->nuse == VM_PTAB_LEN) ? "full" :
23 | "partial"
24 | ),
25 | vfn_offset << PAGE_SIZE_BITS, table->nuse);
26 | }
27 | #else
28 | #ifdef VM_MAP_FINDSPACE_TRACE
29 | #warning VM_MAP_FINDSPACE_TRACE has no effect unless DEBUG is enabled
30 | #undef VM_MAP_FINDSPACE_TRACE
31 | #endif
32 | #define trace(...) ((void)0)
33 | #define trace_table(...) ((void)0)
34 | #endif
35 |
36 |
37 | typedef struct {
38 | vm_ptab_t ptab;
39 | u32 nuse;
40 | } findspace_nuse_t;
41 |
42 | typedef struct {
43 | u64 want_npages; // npages passed to vm_map_findspace
44 | u64 found_npages; // number of free consecutive pages found so far
45 | u64 start_vaddr; // address of first free page
46 | findspace_nuse_t nuse[VM_PTAB_LEVELS];
47 | } findspace_t;
48 |
49 |
50 | static void visit_page_table(vm_table_t* table, u64 vfn, findspace_t* ctx) {
51 | vm_ptab_t ptab = vm_table_ptab(table);
52 | u32 end_index = vm_ptab_page_end_index(vfn);
53 | u32 free_index = 0; // start of range of free pages
54 |
55 | for (u32 i = 0; i < end_index; i++) {
56 | //dlog("page %012llx %s", VM_VFN_VADDR(vfn + i), *(u64*)&ptab[i] ? "used" : "free");
57 |
58 | if (*(u64*)&ptab[i] == 0) // page is free
59 | continue;
60 |
61 | // check if we found enough free pages already
62 | u64 found_npages = (u64)(end_index - free_index);
63 | if (ctx->found_npages + found_npages >= ctx->want_npages) {
64 | ctx->found_npages += found_npages;
65 | return;
66 | }
67 |
68 | // fast forward to the next free page
69 | while (i < end_index && *(u64*)&ptab[i])
70 | i++;
71 |
72 | ctx->found_npages = 0;
73 | free_index = i;
74 |
75 | if (i == end_index) {
76 | // all remaining pages are used
77 | ctx->start_vaddr = 0;
78 | return;
79 | }
80 |
81 | ctx->start_vaddr = VM_VFN_VADDR(vfn + i);
82 | }
83 |
84 | // when we get here we found at least one free page in the tail of the table
85 | assert((end_index - free_index) > 0);
86 | ctx->found_npages += (u64)(end_index - free_index);
87 | }
88 |
89 |
90 | static u32 visit_table(
91 | vm_table_t* table, u32 level, vm_ptab_t ptab, u32 index, u64 vfn, uintptr data)
92 | {
93 | findspace_t* ctx = (findspace_t*)data;
94 |
95 | findspace_nuse_t* curr_nuse = &ctx->nuse[level];
96 | if (curr_nuse->ptab != ptab) {
97 | curr_nuse->ptab = ptab;
98 | curr_nuse->nuse = 0;
99 | }
100 |
101 | u64 block_vfn_mask = VM_BLOCK_VFN_MASK(level);
102 | u64 block_npages = VM_PTAB_NPAGES(level+1);
103 | u32 i = index;
104 |
105 | u64 block_vfn = vfn & block_vfn_mask;
106 | u64 vfnoffs = (vfn - block_vfn) * (vfn > block_vfn);
107 |
108 | for (;i < VM_PTAB_LEN; i++, vfn = (vfn & block_vfn_mask) + block_npages, vfnoffs = 0) {
109 | assertf(level < VM_PTAB_LEVELS-1, "unexpected page; expected a table");
110 | vm_table_t* subtable = &ptab[i].table;
111 | trace_table(subtable, level, vfn);
112 |
113 | // full table
114 | if (subtable->nuse == VM_PTAB_LEN) {
115 | ctx->start_vaddr = 0;
116 | ctx->found_npages = 0;
117 | continue;
118 | }
119 |
120 | // unused table
121 | if (*(u64*)subtable == 0) {
122 | ctx->found_npages += block_npages - vfnoffs;
123 | if (ctx->start_vaddr == 0)
124 | ctx->start_vaddr = VM_VFN_VADDR(vfn);
125 | if (ctx->found_npages >= ctx->want_npages)
126 | return 0;
127 | continue;
128 | }
129 |
130 | // Table of tables needs to be traversed.
131 | // Break out of the loop and advance iterator.
132 | if (level < VM_PTAB_LEVELS-2) {
133 | if (i == index) i++; // guarantee advance
134 | break;
135 | }
136 |
137 | // table of pages (second to last level)
138 | visit_page_table(subtable, vfn, ctx);
139 | if (ctx->found_npages >= ctx->want_npages)
140 | return 0;
141 | }
142 |
143 | // advance iterator by the number of entries we scanned
144 | return i - index;
145 | }
146 |
147 |
148 | rerr_t vm_map_findspace(vm_map_t* map, u64* vaddrp, u64 npages) {
149 | vm_map_assert_rlocked(map);
150 |
151 | // *vaddrp holds the minimum desired address
152 | u64 vaddr = *vaddrp;
153 | if (vaddr < VM_ADDR_MIN)
154 | vaddr = VM_ADDR_MIN;
155 |
156 | // is vaddr+npages beyond end of address range?
157 | if UNLIKELY(
158 | npages > VM_ADDR_MAX/PAGE_SIZE ||
159 | vaddr/PAGE_SIZE > VM_ADDR_MAX/PAGE_SIZE - npages)
160 | {
161 | return rerr_nomem;
162 | }
163 |
164 | // adjust start to minimum free VFN
165 | if (VM_VFN(vaddr) < map->min_free_vfn)
166 | vaddr = VM_VFN_VADDR(map->min_free_vfn);
167 |
168 | trace("vm_map_iter(start_vaddr=%llx, want_npages=%llu)", vaddr, npages);
169 | findspace_t ctx = { .want_npages = npages };
170 | vm_map_iter(map, vaddr, &visit_table, (uintptr)&ctx);
171 |
172 | if (ctx.found_npages < npages)
173 | return rerr_nomem;
174 |
175 | trace("vm_map_findspace\n"
176 | " expected min %10llu pages at >=%012llx\n"
177 | " found %10llu pages at %012llx…%012llx\n",
178 | npages, VM_PAGE_ADDR(vaddr),
179 | ctx.found_npages,
180 | ctx.start_vaddr, ctx.start_vaddr + ((ctx.found_npages-1) * PAGE_SIZE) );
181 |
182 | assert(vaddr <= ctx.start_vaddr);
183 | *vaddrp = ctx.start_vaddr;
184 | return 0;
185 | }
186 |
--------------------------------------------------------------------------------
/etc/website/play/rsm.js:
--------------------------------------------------------------------------------
1 | const ISIZE = 4 // sizeof(isize) (wasm32=4, wasm64=8)
2 | const PTR = Symbol("ptr")
3 |
4 | // UTF8 encoder & decoder
5 | // note: nodejs has require("util").TextEncoder & .TextDecoder
6 | const txt_enc = new TextEncoder("utf-8")
7 | const txt_dec = new TextDecoder("utf-8")
8 |
9 | class RSMInstance {
10 | constructor(memory) {
11 | this.memory = memory
12 | this.mem_u8 = new Uint8Array(memory.buffer)
13 | this.mem_i32 = new Int32Array(memory.buffer)
14 | this.mem_u32 = new Uint32Array(memory.buffer)
15 | if (typeof BigUint64Array != "undefined") {
16 | this.mem_u64 = new BigUint64Array(memory.buffer)
17 | this.u64 = (ptr) => this.mem_u64[ptr >>> 3]
18 | this.R = (iregno) => this.u64(this.iregvp + (iregno * 8))
19 | this.setR = (iregno, v) => {
20 | this.mem_u64[(this.iregvp + (iregno * 8)) >>> 3] = BigInt(v)
21 | }
22 | } else {
23 | this.R = (iregno) => this.u32(this.iregvp + (iregno * 8))
24 | this.setR = (iregno, v) => {
25 | this.setU32(this.iregvp + (iregno * 8), v)
26 | }
27 | }
28 | this.instance = null // set by wasm_load
29 | this.tmpbufcap = 0
30 | this.tmpbufp = 0
31 | }
32 |
33 | init(instance) {
34 | this.instance = instance
35 | instance.exports.winit(this.memory.buffer.byteLength)
36 | this.iregvp = this.instance.exports.wvmiregs()
37 | }
38 |
39 | log(message) { console.log(message) }
40 |
41 | memalloc(size) {
42 | const p = this.instance.exports.wmalloc(size)
43 | if (p == 0) throw new Error("out of memory")
44 | return p
45 | }
46 | memresize(p, currsize, newsize) {
47 | p = this.instance.exports.wmresize(p, currsize, newsize)
48 | if (p == 0) throw new Error("out of memory")
49 | return p
50 | }
51 | memfree(p, size) { this.instance.exports.wmfree(p, size) }
52 |
53 | u8(ptr) { return this.mem_u8[ptr] }
54 |
55 | i32(ptr) { return this.mem_i32[ptr >>> 2] }
56 | u32(ptr) { return this.mem_u32[ptr >>> 2] }
57 |
58 | setI32(ptr, v) { this.mem_u32[ptr >>> 2] = v }
59 | setU32(ptr, v) { this.mem_u32[ptr >>> 2] = (v >>> 0) }
60 |
61 | isize(ptr) { return this.mem_i32[ptr >>> 2] }
62 | usize(ptr) { return this.mem_u32[ptr >>> 2] >>> 0 }
63 |
64 | Rarray(count) {
65 | let a = []
66 | for (let i = 0; i < count; i++)
67 | a[i] = this.R(i)
68 | return a
69 | }
70 |
71 | str(ptr, len) {
72 | return txt_dec.decode(new DataView(this.memory.buffer, ptr, len))
73 | }
74 | cstr(ptr) {
75 | const len = this.mem_u8.indexOf(0, ptr) - ptr
76 | return txt_dec.decode(new DataView(this.memory.buffer, ptr, len))
77 | }
78 | setStr(dstptr, cap, jsstr) {
79 | let buf = txt_enc.encode(String(jsstr))
80 | return this.copyToWasm(dstptr, cap, buf)
81 | }
82 | setCStr(dstptr, cap, jsstr) {
83 | let buf = txt_enc.encode(String(jsstr)+"\0")
84 | return this.copyToWasm(dstptr, cap, buf)
85 | }
86 | copyToWasm(dstptr, cap, buf) {
87 | if (buf.length > cap)
88 | buf = buf.subarray(0, cap)
89 | this.mem_u8.set(buf, dstptr)
90 | return buf.length
91 | }
92 |
93 | tmpbuf(minsize) {
94 | if (this.tmpbufcap < minsize) {
95 | const cap = Math.max(4096, minsize)
96 | this.tmpbufp = (
97 | this.tmpbufcap == 0 ? this.memalloc(cap) :
98 | this.memresize(this.tmpbufp, this.tmpbufcap, cap) )
99 | this.tmpbufcap = cap
100 | }
101 | return [this.tmpbufp, this.tmpbufcap]
102 | }
103 |
104 | compile(source, filename) {
105 | let srcbuf = txt_enc.encode(String(source)) // Uint8Array
106 |
107 | if (!this.srcnamecap) {
108 | this.srcnamecap = 128
109 | this.srcnamep = this.memalloc(this.srcnamecap)
110 | }
111 | this.setCStr(this.srcnamep, this.srcnamecap, filename || "input")
112 |
113 | let [tmpbufp, tmpbufcap] = this.tmpbuf(srcbuf.length)
114 | let tmpbuflen = this.copyToWasm(tmpbufp, tmpbufcap, srcbuf)
115 |
116 | // rptr : struct compile_result {
117 | // usize c;
118 | // rinstr* v;
119 | // const char* nullable errmsg;
120 | // }
121 | const rptr = this.instance.exports.wcompile(this.srcnamep, tmpbufp, tmpbuflen)
122 | const ilen = this.u32(rptr)
123 | const iptr = this.u32(rptr + ISIZE)
124 | const errmsgp = this.u32(rptr + ISIZE*2)
125 | if (errmsgp)
126 | throw new Error(this.cstr(errmsgp))
127 |
128 | return { filename, length: ilen, [PTR]: iptr }
129 | }
130 |
131 | vmfmt(vmcode) {
132 | // usize wfmtprog(char* buf, usize bufcap, rinstr* nullable inv, u32 inc)
133 | let [tmpbufp, tmpbufcap] = this.tmpbuf(vmcode.length * 128)
134 | let len = this.instance.exports.wfmtprog(tmpbufp, tmpbufcap, vmcode[PTR], vmcode.length)
135 | return this.str(tmpbufp, len)
136 | }
137 |
138 | vmexec(vmcode, ...args) { // => [R0 ... R7]
139 | if (args.length > 8)
140 | throw new Error(`too many arguments`)
141 | for (let i = 0; i < args.length; i++) {
142 | let arg = args[i]
143 | if (typeof arg != "bigint") {
144 | if (typeof arg != "number")
145 | throw new Error(`argument ${i+1} (${typeof arg}) is not a number`)
146 | if (arg > 0xffffffff)
147 | throw new Error(`argument ${i+1} is too large`)
148 | }
149 | this.setR(i, arg)
150 | }
151 | this.instance.exports.wvmexec(this.iregvp, vmcode[PTR], vmcode.length)
152 | return this.Rarray(8)
153 | }
154 |
155 | vmfree(vmcode) {
156 | if (vmcode[PTR] == 0)
157 | return
158 | this.memfree(vmcode[PTR], vmcode.length * 4)
159 | vmcode.length = 0
160 | vmcode[PTR] = 0
161 | }
162 | }
163 |
164 | let wasm_mod = null
165 |
166 | function createNthInstance(instance, import_obj, nmempages) {
167 | return WebAssembly.instantiate(wasm_mod, import_obj).then(winstance => {
168 | instance.init(winstance)
169 | return instance
170 | })
171 | }
172 |
173 | // createRSMInstance creates a new RSM WASM module instance and initializes it.
174 | // nmempages is optional and specifies memory pages to allocate for the wasm instance.
175 | // One page is 65536 B (64 ikB).
176 | export async function createRSMInstance(urlorpromise, nmempages) {
177 | const memory = new WebAssembly.Memory({ initial: nmempages||64 }) // 4MB by default
178 | const instance = new RSMInstance(memory)
179 | const import_obj = {
180 | env: {
181 | memory,
182 | log1: (strp, len) => { instance.log(instance.str(strp, len)) },
183 | },
184 | }
185 | if (wasm_mod)
186 | return createNthInstance(instance, import_obj, nmempages)
187 | const fetchp = urlorpromise instanceof Promise ? urlorpromise : fetch(urlorpromise)
188 | const istream = WebAssembly.instantiateStreaming
189 | return (
190 | istream ? istream(fetchp, import_obj) :
191 | fetchp.then(r => r.arrayBuffer()).then(buf => WebAssembly.instantiate(buf, import_obj))
192 | ).then(r => {
193 | wasm_mod = r.module
194 | instance.init(r.instance)
195 | return instance
196 | })
197 | }
198 |
--------------------------------------------------------------------------------
/etc/website/v1/play/rsm.js:
--------------------------------------------------------------------------------
1 | const ISIZE = 4 // sizeof(isize) (wasm32=4, wasm64=8)
2 | const PTR = Symbol("ptr")
3 |
4 | // UTF8 encoder & decoder
5 | // note: nodejs has require("util").TextEncoder & .TextDecoder
6 | const txt_enc = new TextEncoder("utf-8")
7 | const txt_dec = new TextDecoder("utf-8")
8 |
9 | class RSMInstance {
10 | constructor(memory) {
11 | this.memory = memory
12 | this.mem_u8 = new Uint8Array(memory.buffer)
13 | this.mem_i32 = new Int32Array(memory.buffer)
14 | this.mem_u32 = new Uint32Array(memory.buffer)
15 | if (typeof BigUint64Array != "undefined") {
16 | this.mem_u64 = new BigUint64Array(memory.buffer)
17 | this.u64 = (ptr) => this.mem_u64[ptr >>> 3]
18 | this.R = (iregno) => this.u64(this.iregvp + (iregno * 8))
19 | this.setR = (iregno, v) => {
20 | this.mem_u64[(this.iregvp + (iregno * 8)) >>> 3] = BigInt(v)
21 | }
22 | } else {
23 | this.R = (iregno) => this.u32(this.iregvp + (iregno * 8))
24 | this.setR = (iregno, v) => {
25 | this.setU32(this.iregvp + (iregno * 8), v)
26 | }
27 | }
28 | this.instance = null // set by wasm_load
29 | this.tmpbufcap = 0
30 | this.tmpbufp = 0
31 | }
32 |
33 | init(instance) {
34 | this.instance = instance
35 | instance.exports.winit(this.memory.buffer.byteLength)
36 | this.iregvp = this.instance.exports.wvmiregs()
37 | }
38 |
39 | log(message) { console.log(message) }
40 |
41 | memalloc(size) {
42 | const p = this.instance.exports.wmalloc(size)
43 | if (p == 0) throw new Error("out of memory")
44 | return p
45 | }
46 | memresize(p, currsize, newsize) {
47 | p = this.instance.exports.wmresize(p, currsize, newsize)
48 | if (p == 0) throw new Error("out of memory")
49 | return p
50 | }
51 | memfree(p, size) { this.instance.exports.wmfree(p, size) }
52 |
53 | u8(ptr) { return this.mem_u8[ptr] }
54 |
55 | i32(ptr) { return this.mem_i32[ptr >>> 2] }
56 | u32(ptr) { return this.mem_u32[ptr >>> 2] }
57 |
58 | setI32(ptr, v) { this.mem_u32[ptr >>> 2] = v }
59 | setU32(ptr, v) { this.mem_u32[ptr >>> 2] = (v >>> 0) }
60 |
61 | isize(ptr) { return this.mem_i32[ptr >>> 2] }
62 | usize(ptr) { return this.mem_u32[ptr >>> 2] >>> 0 }
63 |
64 | Rarray(count) {
65 | let a = []
66 | for (let i = 0; i < count; i++)
67 | a[i] = this.R(i)
68 | return a
69 | }
70 |
71 | str(ptr, len) {
72 | return txt_dec.decode(new DataView(this.memory.buffer, ptr, len))
73 | }
74 | cstr(ptr) {
75 | const len = this.mem_u8.indexOf(0, ptr) - ptr
76 | return txt_dec.decode(new DataView(this.memory.buffer, ptr, len))
77 | }
78 | setStr(dstptr, cap, jsstr) {
79 | let buf = txt_enc.encode(String(jsstr))
80 | return this.copyToWasm(dstptr, cap, buf)
81 | }
82 | setCStr(dstptr, cap, jsstr) {
83 | let buf = txt_enc.encode(String(jsstr)+"\0")
84 | return this.copyToWasm(dstptr, cap, buf)
85 | }
86 | copyToWasm(dstptr, cap, buf) {
87 | if (buf.length > cap)
88 | buf = buf.subarray(0, cap)
89 | this.mem_u8.set(buf, dstptr)
90 | return buf.length
91 | }
92 |
93 | tmpbuf(minsize) {
94 | if (this.tmpbufcap < minsize) {
95 | const cap = Math.max(4096, minsize)
96 | this.tmpbufp = (
97 | this.tmpbufcap == 0 ? this.memalloc(cap) :
98 | this.memresize(this.tmpbufp, this.tmpbufcap, cap) )
99 | this.tmpbufcap = cap
100 | }
101 | return [this.tmpbufp, this.tmpbufcap]
102 | }
103 |
104 | compile(source, filename) {
105 | let srcbuf = txt_enc.encode(String(source)) // Uint8Array
106 |
107 | if (!this.srcnamecap) {
108 | this.srcnamecap = 128
109 | this.srcnamep = this.memalloc(this.srcnamecap)
110 | }
111 | this.setCStr(this.srcnamep, this.srcnamecap, filename || "input")
112 |
113 | let [tmpbufp, tmpbufcap] = this.tmpbuf(srcbuf.length)
114 | let tmpbuflen = this.copyToWasm(tmpbufp, tmpbufcap, srcbuf)
115 |
116 | // rptr : struct compile_result {
117 | // usize c;
118 | // rinstr* v;
119 | // const char* nullable errmsg;
120 | // }
121 | const rptr = this.instance.exports.wcompile(this.srcnamep, tmpbufp, tmpbuflen)
122 | const ilen = this.u32(rptr)
123 | const iptr = this.u32(rptr + ISIZE)
124 | const errmsgp = this.u32(rptr + ISIZE*2)
125 | if (errmsgp)
126 | throw new Error(this.cstr(errmsgp))
127 |
128 | return { filename, length: ilen, [PTR]: iptr }
129 | }
130 |
131 | vmfmt(vmcode) {
132 | // usize wfmtprog(char* buf, usize bufcap, rinstr* nullable inv, u32 inc)
133 | let [tmpbufp, tmpbufcap] = this.tmpbuf(vmcode.length * 128)
134 | let len = this.instance.exports.wfmtprog(tmpbufp, tmpbufcap, vmcode[PTR], vmcode.length)
135 | return this.str(tmpbufp, len)
136 | }
137 |
138 | vmexec(vmcode, ...args) { // => [R0 ... R7]
139 | if (args.length > 8)
140 | throw new Error(`too many arguments`)
141 | for (let i = 0; i < args.length; i++) {
142 | let arg = args[i]
143 | if (typeof arg != "bigint") {
144 | if (typeof arg != "number")
145 | throw new Error(`argument ${i+1} (${typeof arg}) is not a number`)
146 | if (arg > 0xffffffff)
147 | throw new Error(`argument ${i+1} is too large`)
148 | }
149 | this.setR(i, arg)
150 | }
151 | this.instance.exports.wvmexec(this.iregvp, vmcode[PTR], vmcode.length)
152 | return this.Rarray(8)
153 | }
154 |
155 | vmfree(vmcode) {
156 | if (vmcode[PTR] == 0)
157 | return
158 | this.memfree(vmcode[PTR], vmcode.length * 4)
159 | vmcode.length = 0
160 | vmcode[PTR] = 0
161 | }
162 | }
163 |
164 | let wasm_mod = null
165 |
166 | function createNthInstance(instance, import_obj, nmempages) {
167 | return WebAssembly.instantiate(wasm_mod, import_obj).then(winstance => {
168 | instance.init(winstance)
169 | return instance
170 | })
171 | }
172 |
173 | // createRSMInstance creates a new RSM WASM module instance and initializes it.
174 | // nmempages is optional and specifies memory pages to allocate for the wasm instance.
175 | // One page is 65536 B (64 ikB).
176 | export async function createRSMInstance(urlorpromise, nmempages) {
177 | const memory = new WebAssembly.Memory({ initial: nmempages||64 }) // 4MB by default
178 | const instance = new RSMInstance(memory)
179 | const import_obj = {
180 | env: {
181 | memory,
182 | log1: (strp, len) => { instance.log(instance.str(strp, len)) },
183 | },
184 | }
185 | if (wasm_mod)
186 | return createNthInstance(instance, import_obj, nmempages)
187 | const fetchp = urlorpromise instanceof Promise ? urlorpromise : fetch(urlorpromise)
188 | const istream = WebAssembly.instantiateStreaming
189 | return (
190 | istream ? istream(fetchp, import_obj) :
191 | fetchp.then(r => r.arrayBuffer()).then(buf => WebAssembly.instantiate(buf, import_obj))
192 | ).then(r => {
193 | wasm_mod = r.module
194 | instance.init(r.instance)
195 | return instance
196 | })
197 | }
198 |
--------------------------------------------------------------------------------