├── etc ├── website │ ├── .gitignore │ ├── favicon.ico │ ├── play │ │ ├── rsm.wasm │ │ ├── basis33.woff2 │ │ └── rsm.js │ ├── Inter-V.var.woff2 │ ├── v1 │ │ ├── play │ │ │ ├── rsm.wasm │ │ │ ├── basis33.woff2 │ │ │ └── rsm.js │ │ ├── _update-subs.sh │ │ ├── _gentranscript.js │ │ └── _vttparse.js │ ├── _favicon │ │ ├── favicon-128.png │ │ ├── favicon-16.png │ │ └── favicon-32.png │ ├── isa │ │ ├── op.jump.md │ │ ├── op.or.md │ │ ├── op.ret.md │ │ ├── op.and.md │ │ ├── op.div.md │ │ ├── op.eq.md │ │ ├── op.gts.md │ │ ├── op.gtu.md │ │ ├── op.if.md │ │ ├── op.lts.md │ │ ├── op.ltu.md │ │ ├── op.mod.md │ │ ├── op.neq.md │ │ ├── op.shl.md │ │ ├── op.xor.md │ │ ├── op.gtes.md │ │ ├── op.gteu.md │ │ ├── op.ifz.md │ │ ├── op.ltes.md │ │ ├── op.lteu.md │ │ ├── op.pop.md │ │ ├── op.copy.md │ │ ├── op.load.md │ │ ├── op.push.md │ │ ├── op.call.md │ │ ├── op.mcopy.md │ │ ├── op.store.md │ │ ├── op.add.md │ │ ├── op.mcmp.md │ │ ├── op.mul.md │ │ ├── op.not.md │ │ ├── op.sub.md │ │ ├── op.adds.md │ │ ├── op.muls.md │ │ ├── op.read.md │ │ ├── op.subs.md │ │ ├── op.tspawn.md │ │ ├── op.write.md │ │ ├── op.shrs.md │ │ ├── op.shru.md │ │ ├── op.stkmem.md │ │ ├── op.store1.md │ │ ├── op.store2.md │ │ ├── op.store4.md │ │ ├── op.binv.md │ │ ├── op.syscall.md │ │ ├── op.load1s.md │ │ ├── op.load1u.md │ │ ├── op.load2s.md │ │ ├── op.load2u.md │ │ ├── op.load4s.md │ │ ├── op.load4u.md │ │ ├── op.copyv.md │ │ └── index.md │ ├── _css │ │ ├── fonts.css │ │ └── code.css │ ├── _template.html │ ├── _template-op.html │ ├── _hl │ │ ├── abnf.js │ │ ├── rsm.js │ │ ├── wasm.js │ │ └── armasm.js │ ├── index.md │ ├── assembler │ │ └── index.md │ ├── build.sh │ ├── style.css │ ├── rsm.svg │ ├── _config.js │ └── rom │ │ └── index.md ├── wasm.syms ├── ideas.txt ├── sublime-text │ ├── README.md │ └── rsm │ │ ├── Snippets │ │ └── fun.sublime-snippet │ │ ├── IndentComments.tmPreferences │ │ ├── IndentStrings.tmPreferences │ │ ├── SymbolIndex.tmPreferences │ │ ├── Indent.tmPreferences │ │ └── Comments.tmPreferences ├── sched-lab.rsm ├── wasm-rt.html ├── exe-v1-memory.txt ├── vm-store-example-x86_64.s ├── wasm.html ├── constants.txt ├── sinspect.sh ├── rsm.svg └── rom-layout.txt ├── .logbook ├── 2022-09-24.md ├── 2022-09-14.md ├── 2022-09-15.md ├── 2022-09-25.md ├── 2022-09-16.md ├── 2022-09-02.md ├── 2022-09-13.md ├── 2022-09-22.md ├── 2022-09-21.md ├── 2022-09-07.md ├── 2022-09-08.md ├── 2022-09-01.md ├── 2022-09-09.md ├── 2022-08-31.md ├── 2022-09-17.md ├── 2022-09-05.md └── 2022-09-10.md ├── .gitignore ├── examples ├── hello.rsm ├── hello-multiline.rsm ├── call.rsm ├── factorial.rsm ├── cello.rsm ├── fail-arith-overflow.rsm ├── data.rsm ├── mcopy.rsm ├── stack-growth.rsm ├── askname.rsm ├── test-memload.rsm └── fib.rsm ├── src ├── mem.c ├── syscall.h ├── machine.h ├── machine.c ├── hash.h ├── sched_os.c ├── bits.h ├── abuf.h ├── fmt.c ├── array.c ├── array.h ├── map.h ├── time.c ├── asm.h ├── vm_map_del.c ├── vm_map.c ├── abuf.c ├── wasm.c └── vm_map_findspace.c ├── test.sh └── .github └── workflows ├── pages.yml └── build-rsm.yml /etc/website/.gitignore: -------------------------------------------------------------------------------- 1 | /_site 2 | /_build 3 | -------------------------------------------------------------------------------- /.logbook/2022-09-24.md: -------------------------------------------------------------------------------- 1 | Making a simple website for RSM today 2 | -------------------------------------------------------------------------------- /etc/website/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsms/rsm/HEAD/etc/website/favicon.ico -------------------------------------------------------------------------------- /etc/website/play/rsm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsms/rsm/HEAD/etc/website/play/rsm.wasm -------------------------------------------------------------------------------- /etc/website/Inter-V.var.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsms/rsm/HEAD/etc/website/Inter-V.var.woff2 -------------------------------------------------------------------------------- /etc/website/v1/play/rsm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsms/rsm/HEAD/etc/website/v1/play/rsm.wasm -------------------------------------------------------------------------------- /etc/website/play/basis33.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsms/rsm/HEAD/etc/website/play/basis33.woff2 -------------------------------------------------------------------------------- /etc/wasm.syms: -------------------------------------------------------------------------------- 1 | # names of functions imported into the wasm module 2 | wasm_log 3 | wasm_nanotime 4 | unixtime 5 | -------------------------------------------------------------------------------- /etc/website/v1/play/basis33.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsms/rsm/HEAD/etc/website/v1/play/basis33.woff2 -------------------------------------------------------------------------------- /etc/website/_favicon/favicon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsms/rsm/HEAD/etc/website/_favicon/favicon-128.png -------------------------------------------------------------------------------- /etc/website/_favicon/favicon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsms/rsm/HEAD/etc/website/_favicon/favicon-16.png -------------------------------------------------------------------------------- /etc/website/_favicon/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rsms/rsm/HEAD/etc/website/_favicon/favicon-32.png -------------------------------------------------------------------------------- /etc/website/isa/op.jump.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: jump 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `jump Au → nil` 8 | 9 | PC = Au 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.or.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: or 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `or ABCu → reg` 8 | 9 | RA = RB | Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.ret.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ret 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `ret _ → nil` 8 | 9 | PC = pop() 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.and.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: and 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `and ABCu → reg` 8 | 9 | RA = RB & Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.div.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: div 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `div ABCu → reg` 8 | 9 | RA = RB / Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.eq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: eq 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `eq ABCu → reg` 8 | 9 | RA = RB == Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.gts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: gts 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `gts ABCs → reg` 8 | 9 | RA = RB > Cs 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.gtu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: gtu 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `gtu ABCu → reg` 8 | 9 | RA = RB > Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.if.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: if 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `if ABs → nil` 8 | 9 | if RA!=0 PC += Bs 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.lts.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: lts 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `lts ABCs → reg` 8 | 9 | RA = RB < Cs 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.ltu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ltu 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `ltu ABCu → reg` 8 | 9 | RA = RB < Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.mod.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mod 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `mod ABCu → reg` 8 | 9 | RA = RB % Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.neq.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: neq 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `neq ABCu → reg` 8 | 9 | RA = RB != Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.shl.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: shl 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `shl ABCu → reg` 8 | 9 | RA = RB << Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.xor.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: xor 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `xor ABCu → reg` 8 | 9 | RA = RB ^ Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.gtes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: gtes 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `gtes ABCs → reg` 8 | 9 | RA = RB >= Cs 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.gteu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: gteu 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `gteu ABCu → reg` 8 | 9 | RA = RB >= Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.ifz.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ifz 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `ifz ABs → nil` 8 | 9 | if RA==0 PC += Bs 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.ltes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: ltes 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `ltes ABCs → reg` 8 | 9 | RA = RB <= Cs 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.lteu.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: lteu 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `lteu ABCu → reg` 8 | 9 | RA = RB <= Cu 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.pop.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: pop 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `pop A → reg` 8 | 9 | A = mem[SP]; SP += 8 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.copy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: copy 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `copy ABu → reg` 8 | 9 | RA = Bu -- aka "move" 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.load.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: load 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `load ABCs → reg` 8 | 9 | RA = mem[RB + Cs : 8] 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.push.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: push 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `push Au → mem` 8 | 9 | SP -= 8; mem[SP] = Au 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /out 2 | /out-* 3 | /deps 4 | /work 5 | /build.ninja 6 | /_* 7 | /etc/rsm.min.js 8 | .DS_Store 9 | .ninja* 10 | ~* 11 | *.o 12 | *.d 13 | *.pch 14 | *.rom 15 | -------------------------------------------------------------------------------- /etc/website/isa/op.call.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: call 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `call Au → nil` 8 | 9 | R0...R7 = push(PC); PC=Au 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.mcopy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mcopy 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `mcopy ABCu → mem` 8 | 9 | mem[RA:Cu] = mem[RB:Cu] 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.store.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: store 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `store ABCs → mem` 8 | 9 | mem[RB + Cs : 8] = RA 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.add.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: add 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `add ABCu → reg` 8 | 9 | RA = RB + Cu -- wrap on overflow 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.mcmp.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mcmp 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `mcmp ABCDu → reg` 8 | 9 | RA = mem[RB:Du] <> mem[RC:Du] 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.mul.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: mul 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `mul ABCu → reg` 8 | 9 | RA = RB * Cu -- wrap on overflow 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.not.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: not 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `not ABu → reg` 8 | 9 | RA = !Bu -- boolean negation 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.sub.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: sub 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `sub ABCu → reg` 8 | 9 | RA = RB - Cu -- wrap on overflow 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.adds.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: adds 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `adds ABCs → reg` 8 | 9 | RA = RB + Cs -- panic on overflow 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.muls.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: muls 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `muls ABCs → reg` 8 | 9 | RA = RB * Cs -- panic on overflow 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.read.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: read 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `read ABCDu → reg` 8 | 9 | RA = read srcaddr=RB size=RC fd=Du 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.subs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: subs 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `subs ABCs → reg` 8 | 9 | RA = RB - Cs -- panic on overflow 10 | -------------------------------------------------------------------------------- /etc/ideas.txt: -------------------------------------------------------------------------------- 1 | Ideas 2 | 3 | Debug SP errors: on call, save SP and on ret, check if SP is properly restored. 4 | Catch bugs like: 5 | SP = sub SP 24 6 | // ... 7 | SP = add SP 16 8 | -------------------------------------------------------------------------------- /etc/website/isa/op.tspawn.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: tspawn 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `tspawn Au → nil` 8 | 9 | R0 = spawn_task(pc=Au, args=R0...R7) 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.write.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: write 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `write ABCDs → reg` 8 | 9 | RA = write dstaddr=RB size=RC fd=Ds 10 | -------------------------------------------------------------------------------- /.logbook/2022-09-14.md: -------------------------------------------------------------------------------- 1 | Starting the day with cleaning out stuff no longer needed with the new `vm_map_findspace` function. I need to do more testing of `vm_map_findspace`, especially `vm_map_iter0`. 2 | -------------------------------------------------------------------------------- /.logbook/2022-09-15.md: -------------------------------------------------------------------------------- 1 | Today was crazy. The news about Figma getting acquired by Adobe made my day mostly about that. Fun but stressful. Made a tiny bit of progress on vm_map_add using vm_map_iter. 2 | -------------------------------------------------------------------------------- /etc/website/isa/op.shrs.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: shrs 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `shrs ABCu → reg` 8 | 9 | RA = RB >> Cu -- sign-replicating (arithmetic) 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.shru.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: shru 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `shru ABCu → reg` 8 | 9 | RA = RB >> Cu -- zero-replicating (logical) 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.stkmem.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: stkmem 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `stkmem As → nil` 8 | 9 | SP = maybe_split_or_join_stack(); SP += As 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.store1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: store1 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `store1 ABCs → mem` 8 | 9 | mem[RB + Cs : 1] = RA -- wrap i64 to i8 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.store2.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: store2 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `store2 ABCs → mem` 8 | 9 | mem[RB + Cs : 2] = RA -- wrap i64 to i16 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.store4.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: store4 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `store4 ABCs → mem` 8 | 9 | mem[RB + Cs : 4] = RA -- wrap i64 to i32 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.binv.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: binv 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `binv ABu → reg` 8 | 9 | RA = ~Bu -- bitwise complement, invert bits 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.syscall.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: syscall 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `syscall Au → nil` 8 | 9 | R0...R7 = syscall(code=Au, args=R0...R18) 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.load1s.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: load1s 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `load1s ABCs → reg` 8 | 9 | RA = mem[RB + Cs : 1] -- sign-extend i8 to i64 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.load1u.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: load1u 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `load1u ABCs → reg` 8 | 9 | RA = mem[RB + Cs : 1] -- zero-extend i8 to i64 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.load2s.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: load2s 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `load2s ABCs → reg` 8 | 9 | RA = mem[RB + Cs : 2] -- sign-extend i16 to i64 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.load2u.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: load2u 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `load2u ABCs → reg` 8 | 9 | RA = mem[RB + Cs : 2] -- zero-extend i16 to i64 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.load4s.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: load4s 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `load4s ABCs → reg` 8 | 9 | RA = mem[RB + Cs : 4] -- sign-extend i32 to i64 10 | -------------------------------------------------------------------------------- /etc/website/isa/op.load4u.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: load4u 3 | template: template-op 4 | x-autogenerated: true 5 | --- 6 | 7 | `load4u ABCs → reg` 8 | 9 | RA = mem[RB + Cs : 4] -- zero-extend i32 to i64 10 | -------------------------------------------------------------------------------- /etc/sublime-text/README.md: -------------------------------------------------------------------------------- 1 | Sublime Text syntax package 2 | 3 | installing on macOS: 4 | 5 | ``` 6 | cd ~/Library/Application\ Support/Sublime\ Text*/Packages 7 | ln -s PATH_TO/rsm/etc/sublime-text/rsm 8 | ``` 9 | -------------------------------------------------------------------------------- /.logbook/2022-09-25.md: -------------------------------------------------------------------------------- 1 | Setup CI today using github actions. RSM is now built for a variety of platforms on every push of source and everytime a "v" version tag is created. When a version tag is created a release is published. 2 | -------------------------------------------------------------------------------- /examples/hello.rsm: -------------------------------------------------------------------------------- 1 | // Prints "Hello world" to the console 2 | const STDOUT = 1 3 | data message = "Hello world\n" 4 | 5 | fun main(i32) { 6 | R0 = message // address of string 7 | R1 = 12 // length of string 8 | R0 = write R0 R1 STDOUT 9 | } 10 | -------------------------------------------------------------------------------- /etc/sublime-text/rsm/Snippets/fun.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | fun 6 | source.rsm 7 | fun …(…)…{…} 8 | 9 | -------------------------------------------------------------------------------- /examples/hello-multiline.rsm: -------------------------------------------------------------------------------- 1 | // Demonstrates multiline strings 2 | 3 | const STDOUT = 1 4 | data hej = " 5 | |hello 6 | | world 7 | | how 8 | | are\nyou? 9 | |" 10 | 11 | fun main(i32) { 12 | R0 = hej // address 13 | R1 = 32 // length 14 | R0 = write R0 R1 STDOUT 15 | ret 16 | } 17 | -------------------------------------------------------------------------------- /etc/sublime-text/rsm/IndentComments.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | scope 5 | source.rsm comment 6 | settings 7 | 8 | preserveIndent 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /etc/sublime-text/rsm/IndentStrings.tmPreferences: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | scope 5 | source.rsm string 6 | settings 7 | 8 | preserveIndent 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/call.rsm: -------------------------------------------------------------------------------- 1 | fun main() { 2 | R0 = 3 3 | R1 = 4 4 | R3 = R0 // save R0 (just for testing) 5 | call foo 6 | R0 = R3 // restore R0 (just for testing) 7 | } 8 | 9 | fun bar(x i32, y i32) i32 { 10 | R0 = R0 * R1 11 | } 12 | 13 | fun foo(x i32, y i32) i32 { 14 | R0 = R0 * R1 15 | jump bar // tail call (never returns) 16 | } 17 | -------------------------------------------------------------------------------- /.logbook/2022-09-16.md: -------------------------------------------------------------------------------- 1 | Separated vm_map code into separate source files as the vm.c file was getting too big. 2 | Finished a working implemention of vm_map_add using vm_map_iter today and improved the vm_map_iter API along the way. However vm_map_iter still has a bug where the VFN is not correctly calculated; it is relative to the current table, not absolute. 3 | -------------------------------------------------------------------------------- /.logbook/2022-09-02.md: -------------------------------------------------------------------------------- 1 | Got the scheduler fully working, at least running the sched-lab.rsm test program. That's a big milestone! 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" 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 |

RSM instructions

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 <outfilehtml>" 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 | `<a class="time" name="t${time}" href="#t${time}">${timestamp}</a> ${buf.join(" ")}<br>`) 42 | outmd.push('_' + timestamp + ":_ " + buf.join(" ") + "<br>") 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 | <h1>{{page.title}}</h1> 82 | <p><a href="./">RSM project page</a></p> 83 | <p> 84 | ${outhtml.join("\n")} 85 | </p> 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?<br> 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...<br> 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 <<END > "$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 <errno.h> 5 | #include <time.h> 6 | #include <sys/time.h> 7 | #if defined __APPLE__ 8 | #include <mach/mach_time.h> 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 = <utf8 encoding of printable unicode codepoint> 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 = "//" <any character except LF> <LF> 166 | blockcomment = "/*" <any character> "*/" 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] [<file>] 12 | # Prints the sha1 (or sha256 or sha512) sum of file's content 13 | # (or stdin if no <file> 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 | <table> 16 | <thead> 17 | <tr><th>Name</th><th>Inputs</th><th>Output</th><th>Semantics</th></tr> 18 | </thead> 19 | <tbody> 20 | {{ 21 | for (let op of site.rsm_ops) { 22 | print(`<tr>`) 23 | print(`<td><a href="op.${op.name}.html">${op.name}</a></td>`) 24 | print(`<td>${op.args}</td>`) 25 | print(`<td>${op.result == "nil" ? "–" : op.result}</td>`) 26 | print(`<td>${op.semantics}</td>`) 27 | print(`</tr>\n`) 28 | } 29 | }} 30 | </tbody> 31 | </table> 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 | <svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M73.18 97.4447C62.5725 91.1603 51.9443 87.2154 43.5591 87.7371C34.8824 88.2769 27.949 93.8523 27.949 107.328C27.949 119.267 31.9483 131.113 39.5375 141.853C46.3824 151.54 55.1569 158.89 63.4813 164.767L63.5249 164.797L63.565 164.825L76.1224 173.571C76.1711 173.605 76.2198 173.638 76.2686 173.672C85.6826 180.073 91.5113 185.27 94.8001 189.538C96.2545 191.425 97.0156 192.883 97.4737 194.118C97.9411 195.379 98.4064 197.257 98.4064 200.002C98.4064 204.791 96.9626 206.587 94.2083 207.034C90.7748 207.592 84.5733 206.013 74.7762 200.357C65.8822 195.222 56.8359 189.175 50.7529 182.768C47.8642 179.725 46.5274 177.515 45.931 176.107C45.4799 175.041 44.8214 172.993 45.4144 169.531C46.4312 163.595 42.6763 155.188 37.0275 150.753C31.3788 146.317 25.9753 147.533 24.9586 153.469C23.6031 161.382 24.6766 170.084 28.4802 179.068C32.1386 187.71 37.5792 194.851 42.8327 200.384C53.0341 211.129 65.7312 219.135 74.7762 224.357C86.3823 231.058 97.1921 235.052 105.419 233.717C114.325 232.271 119.191 224.712 119.191 212.002C119.191 205.997 118.141 199.688 115.726 193.175C113.302 186.637 109.814 180.734 105.802 175.527C98.1756 165.631 88.2295 157.716 78.1832 150.879L65.7408 142.213C58.5011 137.098 54.1423 132.869 51.7439 129.475C50.0851 127.127 48.7336 124.386 48.7336 119.328C48.7336 116.804 49.6873 114.791 53.6297 114.546C57.8638 114.283 64.587 116.356 72.9084 121.286C81.9366 126.634 88.6803 131.884 92.8376 136.69C94.7579 138.91 95.7656 140.623 96.3217 141.914C96.8375 143.111 97.3548 144.838 97.3548 147.399C97.3548 154.026 102.008 162.085 107.747 165.399C113.487 168.713 118.139 166.026 118.139 159.399C118.139 152.711 116.685 145.71 113.652 138.669C110.659 131.722 106.54 125.685 102.15 120.611C93.6884 110.828 83.0806 103.31 73.18 97.4447Z" fill="#4582fa"/> 3 | <path fill-rule="evenodd" clip-rule="evenodd" d="M157.022 169.874C155.758 165.738 155.125 163.67 154.317 164.099C154.179 164.172 154.036 164.279 153.904 164.407C153.139 165.151 153.134 167.694 153.123 172.781C153.115 176.612 153.115 180.738 153.115 185.177V226.42C153.115 233.047 148.462 241.106 142.722 244.42C136.983 247.733 132.33 245.047 132.33 238.42L132.33 196.642C132.33 179.356 132.33 165.744 132.878 155.839C133.152 150.882 133.595 146.129 134.448 141.911C135.127 138.551 136.829 131.399 142.032 125.189C145.796 120.698 150.141 117.459 154.32 116.029C160.099 114.052 163.254 117.584 164.638 119.406C166.375 121.693 167.91 124.972 169.358 128.645C172.251 135.985 175.611 147.091 179.88 161.196L180.012 161.632C180.541 163.38 181.03 164.993 181.486 166.48C182.19 168.777 182.543 169.926 183.238 169.659C183.36 169.612 183.491 169.537 183.613 169.443C184.309 168.907 184.661 167.351 185.366 164.24C185.822 162.226 186.31 160.049 186.839 157.69L186.971 157.102C191.24 138.068 194.601 123.081 197.493 112.401C198.941 107.056 200.476 102.004 202.214 97.7111C203.598 94.2913 206.752 87.1167 212.531 82.4208C216.71 79.0249 221.056 77.2466 224.819 77.3922C230.022 77.5935 231.724 82.7807 232.403 85.356C233.256 88.5891 233.699 92.8316 233.973 97.4717C234.521 106.744 234.521 120.356 234.521 137.643V179.42C234.521 186.047 229.868 194.106 224.129 197.42C218.389 200.734 213.737 198.047 213.737 191.42V150.177C213.737 145.738 213.736 141.613 213.728 137.791C213.717 132.716 213.712 130.179 212.947 130.318C212.816 130.342 212.673 130.401 212.534 130.487C211.726 130.992 211.094 133.79 209.829 139.387C208.876 143.602 207.857 148.144 206.761 153.031L206.597 153.762C204.418 163.484 202.557 171.781 200.702 178.095C199.741 181.364 198.551 184.925 196.959 188.34C195.331 191.83 192.805 196.189 189.026 199.771C185.384 203.223 181.467 205.485 177.825 206.238C174.047 207.019 171.52 205.578 169.892 203.967C168.3 202.391 167.11 200.204 166.149 198.044C164.294 193.872 162.434 187.723 160.254 180.518L160.09 179.977C158.994 176.355 157.975 172.99 157.022 169.874ZM146.487 141.713C146.484 141.743 146.49 141.682 146.487 141.713V141.713ZM220.364 99.0598C220.361 99.0327 220.367 99.0869 220.364 99.0598V99.0598ZM212.086 102.448C212.096 102.415 212.076 102.48 212.086 102.448V102.448ZM154.765 135.542C154.775 135.563 154.755 135.521 154.765 135.542V135.542Z" fill="#ff462a"/> 4 | <path fill-rule="evenodd" clip-rule="evenodd" d="M40.3905 60.004C34.6491 63.3144 34.6491 68.6816 40.3905 71.9919C46.132 75.3023 55.4407 75.3023 61.1821 71.9919L84.7459 58.4056C85.3679 58.047 86.3763 58.047 86.9983 58.4056L111.308 72.4222C111.612 72.5975 111.781 72.8361 111.775 73.0838L110.948 108.851C110.848 113.142 116.801 116.667 124.244 116.724C131.686 116.782 137.799 113.349 137.899 109.058L138.39 87.7983C138.407 87.0938 139.74 86.6589 140.821 86.9866C146.011 88.5586 151.555 89.6362 157.439 90.0035C171.406 90.8755 184.04 87.5681 194.595 81.4824C205.587 75.1446 211.237 67.7554 209.008 59.6185C206.974 52.1899 198.755 45.9194 189.83 40.7734L142.183 13.3011C136.441 9.99072 127.133 9.99072 121.391 13.3011L40.3905 60.004ZM143.915 67.2467L107.79 46.4177C107.168 46.0591 107.168 45.4777 107.79 45.119L130.661 31.9323C131.283 31.5737 132.291 31.5737 132.913 31.9323L169.038 52.7613C176.573 57.1058 179.4 60.2008 179.965 62.2626C180.335 63.6162 180.137 65.8424 173.803 69.4945C167.898 72.8992 163.639 73.338 160.604 73.1486C156.81 72.9117 151.199 71.4464 143.915 67.2467Z" fill="#ffc423"/> 5 | </svg> 6 | -------------------------------------------------------------------------------- /etc/website/rsm.svg: -------------------------------------------------------------------------------- 1 | <svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg"> 2 | <path d="M73.18 97.4447C62.5725 91.1603 51.9443 87.2154 43.5591 87.7371C34.8824 88.2769 27.949 93.8523 27.949 107.328C27.949 119.267 31.9483 131.113 39.5375 141.853C46.3824 151.54 55.1569 158.89 63.4813 164.767L63.5249 164.797L63.565 164.825L76.1224 173.571C76.1711 173.605 76.2198 173.638 76.2686 173.672C85.6826 180.073 91.5113 185.27 94.8001 189.538C96.2545 191.425 97.0156 192.883 97.4737 194.118C97.9411 195.379 98.4064 197.257 98.4064 200.002C98.4064 204.791 96.9626 206.587 94.2083 207.034C90.7748 207.592 84.5733 206.013 74.7762 200.357C65.8822 195.222 56.8359 189.175 50.7529 182.768C47.8642 179.725 46.5274 177.515 45.931 176.107C45.4799 175.041 44.8214 172.993 45.4144 169.531C46.4312 163.595 42.6763 155.188 37.0275 150.753C31.3788 146.317 25.9753 147.533 24.9586 153.469C23.6031 161.382 24.6766 170.084 28.4802 179.068C32.1386 187.71 37.5792 194.851 42.8327 200.384C53.0341 211.129 65.7312 219.135 74.7762 224.357C86.3823 231.058 97.1921 235.052 105.419 233.717C114.325 232.271 119.191 224.712 119.191 212.002C119.191 205.997 118.141 199.688 115.726 193.175C113.302 186.637 109.814 180.734 105.802 175.527C98.1756 165.631 88.2295 157.716 78.1832 150.879L65.7408 142.213C58.5011 137.098 54.1423 132.869 51.7439 129.475C50.0851 127.127 48.7336 124.386 48.7336 119.328C48.7336 116.804 49.6873 114.791 53.6297 114.546C57.8638 114.283 64.587 116.356 72.9084 121.286C81.9366 126.634 88.6803 131.884 92.8376 136.69C94.7579 138.91 95.7656 140.623 96.3217 141.914C96.8375 143.111 97.3548 144.838 97.3548 147.399C97.3548 154.026 102.008 162.085 107.747 165.399C113.487 168.713 118.139 166.026 118.139 159.399C118.139 152.711 116.685 145.71 113.652 138.669C110.659 131.722 106.54 125.685 102.15 120.611C93.6884 110.828 83.0806 103.31 73.18 97.4447Z" fill="#4582fa"/> 3 | <path fill-rule="evenodd" clip-rule="evenodd" d="M157.022 169.874C155.758 165.738 155.125 163.67 154.317 164.099C154.179 164.172 154.036 164.279 153.904 164.407C153.139 165.151 153.134 167.694 153.123 172.781C153.115 176.612 153.115 180.738 153.115 185.177V226.42C153.115 233.047 148.462 241.106 142.722 244.42C136.983 247.733 132.33 245.047 132.33 238.42L132.33 196.642C132.33 179.356 132.33 165.744 132.878 155.839C133.152 150.882 133.595 146.129 134.448 141.911C135.127 138.551 136.829 131.399 142.032 125.189C145.796 120.698 150.141 117.459 154.32 116.029C160.099 114.052 163.254 117.584 164.638 119.406C166.375 121.693 167.91 124.972 169.358 128.645C172.251 135.985 175.611 147.091 179.88 161.196L180.012 161.632C180.541 163.38 181.03 164.993 181.486 166.48C182.19 168.777 182.543 169.926 183.238 169.659C183.36 169.612 183.491 169.537 183.613 169.443C184.309 168.907 184.661 167.351 185.366 164.24C185.822 162.226 186.31 160.049 186.839 157.69L186.971 157.102C191.24 138.068 194.601 123.081 197.493 112.401C198.941 107.056 200.476 102.004 202.214 97.7111C203.598 94.2913 206.752 87.1167 212.531 82.4208C216.71 79.0249 221.056 77.2466 224.819 77.3922C230.022 77.5935 231.724 82.7807 232.403 85.356C233.256 88.5891 233.699 92.8316 233.973 97.4717C234.521 106.744 234.521 120.356 234.521 137.643V179.42C234.521 186.047 229.868 194.106 224.129 197.42C218.389 200.734 213.737 198.047 213.737 191.42V150.177C213.737 145.738 213.736 141.613 213.728 137.791C213.717 132.716 213.712 130.179 212.947 130.318C212.816 130.342 212.673 130.401 212.534 130.487C211.726 130.992 211.094 133.79 209.829 139.387C208.876 143.602 207.857 148.144 206.761 153.031L206.597 153.762C204.418 163.484 202.557 171.781 200.702 178.095C199.741 181.364 198.551 184.925 196.959 188.34C195.331 191.83 192.805 196.189 189.026 199.771C185.384 203.223 181.467 205.485 177.825 206.238C174.047 207.019 171.52 205.578 169.892 203.967C168.3 202.391 167.11 200.204 166.149 198.044C164.294 193.872 162.434 187.723 160.254 180.518L160.09 179.977C158.994 176.355 157.975 172.99 157.022 169.874ZM146.487 141.713C146.484 141.743 146.49 141.682 146.487 141.713V141.713ZM220.364 99.0598C220.361 99.0327 220.367 99.0869 220.364 99.0598V99.0598ZM212.086 102.448C212.096 102.415 212.076 102.48 212.086 102.448V102.448ZM154.765 135.542C154.775 135.563 154.755 135.521 154.765 135.542V135.542Z" fill="#ff462a"/> 4 | <path fill-rule="evenodd" clip-rule="evenodd" d="M40.3905 60.004C34.6491 63.3144 34.6491 68.6816 40.3905 71.9919C46.132 75.3023 55.4407 75.3023 61.1821 71.9919L84.7459 58.4056C85.3679 58.047 86.3763 58.047 86.9983 58.4056L111.308 72.4222C111.612 72.5975 111.781 72.8361 111.775 73.0838L110.948 108.851C110.848 113.142 116.801 116.667 124.244 116.724C131.686 116.782 137.799 113.349 137.899 109.058L138.39 87.7983C138.407 87.0938 139.74 86.6589 140.821 86.9866C146.011 88.5586 151.555 89.6362 157.439 90.0035C171.406 90.8755 184.04 87.5681 194.595 81.4824C205.587 75.1446 211.237 67.7554 209.008 59.6185C206.974 52.1899 198.755 45.9194 189.83 40.7734L142.183 13.3011C136.441 9.99072 127.133 9.99072 121.391 13.3011L40.3905 60.004ZM143.915 67.2467L107.79 46.4177C107.168 46.0591 107.168 45.4777 107.79 45.119L130.661 31.9323C131.283 31.5737 132.291 31.5737 132.913 31.9323L169.038 52.7613C176.573 57.1058 179.4 60.2008 179.965 62.2626C180.335 63.6162 180.137 65.8424 173.803 69.4945C167.898 72.8992 163.639 73.338 160.604 73.1486C156.81 72.9117 151.199 71.4464 143.915 67.2467Z" fill="#ffc423"/> 5 | </svg> 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 <alsoelp@gmail.com> 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 | --------------------------------------------------------------------------------