pub const SOURCE: &str = "; A command-line operating system for Ternaria.\n;\n; Reads a TFS image through the block device and offers ls, cd, cat and mkdir\n; at a prompt. Runs at machine level: there is one program here and it is this\n; one, so nothing is gained by dropping privilege.\n;\n; Registers follow the platform calling convention (D-18): the sign of a\n; register number says who saves it. Non-negative is caller-saved, negative is\n; callee-saved, and there are no exceptions.\n;\n; r0 zero\n; r1 .. r8 arguments and return values, caller-saved\n; r9 .. r12 temporaries, caller-saved\n; r13 return address, caller-saved\n; r-1 .. r-11 general, callee-saved\n; r-12 frame pointer, callee-saved\n; r-13 stack pointer, callee-saved\n;\n; This file uses only part of that. It predates the convention and is being\n; replaced by a Ternex implementation; what matters is that the two agree at the\n; boundary, which they now do.\n\n; ---------------------------------------------------------------- guest memory\n\n; Every address a word is stored at must be a multiple of three, since `lw` and\n; `sw` require word alignment. The tryte-addressed buffers do not, but they are\n; aligned anyway so that the map stays easy to read.\n.equ STACK_TOP, 60000 ; grows down\n.equ LINEBUF, 41001 ; the command line as typed\n.equ ARGBUF, 41301 ; the arguments, one 27-tryte slot each\n.equ ENTBUF, 41403 ; one directory entry, in transit between blocks\n.equ FILEBUF, 42003 ; a file being composed, up to FS_DIRECT_BLOCKS\n.equ FILELEN, 45003 ; how much of FILEBUF is in use\n.equ SHELL_SP, 45006 ; the shell\'s stack pointer while a program runs\n.equ EXITED, 45009 ; set when a program has called exit\n.equ PROG_SP, 45012 ; the program\'s stack pointer while the handler runs\n.equ LOAD_ADDR, 100002 ; where a program is loaded and linked for\n.equ CWD, 41601 ; inode of the working directory\n.equ SB_INODES, 41604 ; cached superblock: first inode block\n.equ SB_DATA, 41607 ; cached superblock: first data block\n.equ SB_MAP, 41610 ; cached superblock: first map block\n.equ SB_TOTAL, 41613 ; cached superblock: total blocks\n.equ LINEMAX, 240\n\n; ------------------------------------------------------------------ entry point\n\nstart:\n addi r-13, r0, STACK_TOP\n addi r1, r0, SHELL_SP\n sw r-13, r1, 0 ; the stack the trap handler runs on\n addi r1, r0, @trap\n csrw r1, TVEC ; claim traps, so ecall reaches us\n jal r13, fs_mount\n jal r13, shell\n halt\n\n; ---------------------------------------------------------------- block device\n;\n; The window is the only path to storage. Every routine below stages through\n; it rather than addressing the image, because the image is not addressable.\n\n; bload: load block r1 into the window. Returns status in r1: 0 ok, -1 absent.\nbload:\n addi r2, r0, BLOCK_BASE\n sw r1, r2, BLOCK_SECTOR\n addi r3, r0, BLOCK_LOAD\n st r3, r2, BLOCK_CONTROL\n lt r1, r2, BLOCK_STATUS\n jalr r0, r13, 0\n\n; bstore: store the window to block r1. Returns status in r1.\nbstore:\n addi r2, r0, BLOCK_BASE\n sw r1, r2, BLOCK_SECTOR\n addi r3, r0, BLOCK_STORE\n st r3, r2, BLOCK_CONTROL\n lt r1, r2, BLOCK_STATUS\n jalr r0, r13, 0\n\n; win_get: read the tryte at window offset r1. Returns it in r1.\nwin_get:\n addi r2, r0, BLOCK_BASE\n add r2, r2, r1\n lt r1, r2, BLOCK_WINDOW\n jalr r0, r13, 0\n\n; win_put: write tryte r2 to window offset r1.\nwin_put:\n addi r3, r0, BLOCK_BASE\n add r3, r3, r1\n st r2, r3, BLOCK_WINDOW\n jalr r0, r13, 0\n\n; word_get: read a stored word at window tryte offset r1. Returns it in r1.\n;\n; Fields are stored in base 243, which is 3^5, so each digit is a trit shift\n; rather than a multiply. This is the whole reason for that radix.\nword_get:\n addi r3, r0, BLOCK_BASE\n add r3, r3, r1\n lt r4, r3, BLOCK_WINDOW\n lt r5, r3, BLOCK_WINDOW+1\n lt r6, r3, BLOCK_WINDOW+2\n shl r5, r5, FS_RADIX_TRITS\n shl r6, r6, FS_RADIX_TRITS\n shl r6, r6, FS_RADIX_TRITS\n add r1, r4, r5\n add r1, r1, r6\n jalr r0, r13, 0\n\n; word_put: write value r2 as a stored word at window tryte offset r1.\n;\n; Digits come off with `rem` rather than by shifting, because `shr` rounds to\n; nearest and digit extraction wants the floor. Subtracting the remainder first\n; leaves a multiple of 243, which `shr` then divides exactly.\nword_put:\n addi r3, r0, BLOCK_BASE\n add r3, r3, r1\n addi r4, r0, FS_RADIX\n rem r5, r2, r4\n st r5, r3, BLOCK_WINDOW\n sub r2, r2, r5\n shr r2, r2, FS_RADIX_TRITS\n rem r5, r2, r4\n st r5, r3, BLOCK_WINDOW+1\n sub r2, r2, r5\n shr r2, r2, FS_RADIX_TRITS\n st r2, r3, BLOCK_WINDOW+2\n jalr r0, r13, 0\n\n; ------------------------------------------------------------------- mounting\n\n; fs_mount: read the superblock and cache the fields the driver needs.\nfs_mount:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n\n addi r1, r0, 0\n jal r13, bload\n\n addi r1, r0, SB_INODE_START\n jal r13, word_at\n addi r2, r0, SB_INODES\n sw r1, r2, 0\n\n addi r1, r0, SB_DATA_START\n jal r13, word_at\n addi r2, r0, SB_DATA\n sw r1, r2, 0\n\n addi r1, r0, SB_MAP_START\n jal r13, word_at\n addi r2, r0, SB_MAP\n sw r1, r2, 0\n\n addi r1, r0, SB_TOTAL_BLOCKS\n jal r13, word_at\n addi r2, r0, SB_TOTAL\n sw r1, r2, 0\n\n ; The working directory starts at the root.\n addi r1, r0, FS_ROOT_INODE\n addi r2, r0, CWD\n sw r1, r2, 0\n\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; word_at: read stored word number r1 of the loaded block. Returns it in r1.\nword_at:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n add r2, r1, r1\n add r1, r1, r2 ; r1 = 3 * word index\n jal r13, word_get\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; --------------------------------------------------------------------- inodes\n\n; inode_open: load the block holding inode r1. Returns the inode\'s tryte\n; offset within that block in r1.\n;\n; An inode is 27 trytes and a block holds nine of them, so both the division\n; and the multiplication are trit shifts: 9 is 3^2 and 27 is 3^3.\ninode_open:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r2, r0, FS_INODES_PER_BLOCK\n rem r3, r1, r2 ; which inode within the block\n sub r4, r1, r3\n shr r4, r4, 2 ; which block of the table\n addi r5, r0, SB_INODES\n lw r5, r5, 0\n add r1, r4, r5 ; absolute block number\n shl r3, r3, 3 ; offset = index * 27\n addi r-13, r-13, -3\n sw r3, r-13, 0\n jal r13, bload\n lw r1, r-13, 0\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; inode_field: read word r2 of inode r1. Returns it in r1.\ninode_field:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r2, r-13, 0\n jal r13, inode_open\n lw r2, r-13, 0\n addi r-13, r-13, 3\n add r3, r2, r2\n add r3, r3, r2 ; 3 * word index\n add r1, r1, r3\n jal r13, word_get\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; inode_set: write value r3 to word r2 of inode r1.\n;\n; Reads the block, edits the window, and stores it back. The window is the\n; staging buffer, so a read-modify-write is the only way to change one field.\ninode_set:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r1, r-13, 0 ; keep the inode number for the store\n addi r-13, r-13, -3\n sw r3, r-13, 0\n addi r-13, r-13, -3\n sw r2, r-13, 0\n\n jal r13, inode_open ; r1 = offset, block is in the window\n lw r2, r-13, 0\n addi r-13, r-13, 3\n lw r3, r-13, 0\n addi r-13, r-13, 3\n add r4, r2, r2\n add r4, r4, r2\n add r1, r1, r4\n add r2, r3, r0 ; word_put takes the value in r2\n jal r13, word_put\n\n ; Recompute the block and write the window back.\n lw r1, r-13, 0\n addi r-13, r-13, 3\n addi r2, r0, FS_INODES_PER_BLOCK\n rem r3, r1, r2\n sub r4, r1, r3\n shr r4, r4, 2\n addi r5, r0, SB_INODES\n lw r5, r5, 0\n add r1, r4, r5\n jal r13, bstore\n\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; ---------------------------------------------------------------- directories\n\n; dir_entry: entry r2 of directory inode r1.\n;\n; Loads the block holding that entry and leaves it in the window. Returns the\n; entry\'s tryte offset within the window in r1, or -1 if the entry has no\n; block behind it.\ndir_entry:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r2, r-13, 0\n\n addi r3, r0, FS_DIRENTS_PER_BLOCK\n rem r4, r2, r3 ; slot within the block\n sub r5, r2, r4\n shr r5, r5, 2 ; which direct pointer\n addi r-13, r-13, -3\n sw r4, r-13, 0\n addi r2, r5, INO_DIRECT\n jal r13, inode_field ; r1 = the block number\n lw r4, r-13, 0\n addi r-13, r-13, 3\n\n brz r1, dir_entry_none\n addi r-13, r-13, -3\n sw r4, r-13, 0\n jal r13, bload\n lw r4, r-13, 0\n addi r-13, r-13, 3\n shl r1, r4, 3 ; offset = slot * 27\n jal r13, dir_entry_done\ndir_entry_none:\n addi r1, r0, -1\ndir_entry_done:\n addi r-13, r-13, 3 ; drop the saved r2\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; dir_index: the index of the entry named by ARGBUF in directory inode r1.\n; Returns the index in r1, or -1 if there is no such entry.\n;\n; Removal needs the index rather than the inode, so the search returns that and\n; dir_find reads the inode out of it.\ndir_index:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -6\n sw r1, r-13, 0 ; the directory\n addi r2, r0, INO_SIZE\n jal r13, inode_field\n sw r1, r-13, 3 ; how many entries\n addi r6, r0, 0 ; index\n\ndir_index_loop:\n lw r5, r-13, 3\n sub r4, r6, r5\n brz r4, dir_index_missing\n addi r-13, r-13, -3\n sw r6, r-13, 0\n lw r1, r-13, 3\n add r2, r6, r0\n jal r13, dir_entry\n lw r6, r-13, 0\n addi r-13, r-13, 3\n brn r1, dir_index_next\n\n ; A slot whose inode is zero is a hole and matches nothing.\n addi r-13, r-13, -6\n sw r1, r-13, 0\n sw r6, r-13, 3\n jal r13, word_get\n lw r6, r-13, 3\n lw r5, r-13, 0\n addi r-13, r-13, 6\n brz r1, dir_index_next\n\n addi r-13, r-13, -3\n sw r6, r-13, 0\n add r1, r5, r0\n jal r13, name_matches\n lw r6, r-13, 0\n addi r-13, r-13, 3\n brz r1, dir_index_next\n\n add r1, r6, r0\n jal r0, dir_index_done\n\ndir_index_next:\n addi r6, r6, 1\n jal r0, dir_index_loop\n\ndir_index_missing:\n addi r1, r0, -1\ndir_index_done:\n addi r-13, r-13, 6\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; dir_find: the inode named by ARGBUF in directory r1, or 0 if there is none.\n;\n; A thin layer over dir_index, so that lookup and removal cannot disagree about\n; which entry a name refers to.\ndir_find:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r1, r-13, 0\n jal r13, dir_index\n brn r1, dir_find_missing\n add r2, r1, r0\n lw r1, r-13, 0\n jal r13, dir_entry\n brn r1, dir_find_missing\n jal r13, word_get\n jal r0, dir_find_done\ndir_find_missing:\n addi r1, r0, 0\ndir_find_done:\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; name_matches: compare the name in the entry at window offset r1 with ARGBUF.\n; Returns 1 in r1 if they are equal, 0 otherwise.\n;\n; A name occupies the 24 trytes after the entry\'s inode word and is zero-padded\n; when short. The comparison therefore ends at the first zero on both sides.\nname_matches:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r2, r0, BLOCK_BASE\n add r2, r2, r1 ; window address of the entry\n addi r3, r0, ARGBUF\n addi r4, r0, 0 ; index\n\nname_loop:\n addi r5, r0, FS_NAME_TRYTES\n sub r6, r4, r5\n brz r6, name_same ; ran out of field; equal so far\n add r5, r2, r4\n lt r5, r5, BLOCK_WINDOW+3 ; the name starts after the inode word\n add r6, r3, r4\n lt r6, r6, 0\n sub r1, r5, r6\n brz r1, name_step\n addi r1, r0, 0\n jal r0, name_done\nname_step:\n brz r5, name_same ; both hit the terminator together\n addi r4, r4, 1\n jal r0, name_loop\nname_same:\n addi r1, r0, 1\nname_done:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; ------------------------------------------------------------------- console\n\n; putc: emit the tryte in r1. Values outside 0..255 are not bytes and would\n; fault the device, so they become a question mark instead.\nputc:\n addi r2, r0, CONSOLE_BASE\n addi r3, r1, -255\n brp r3, putc_bad\n brn r1, putc_bad\n st r1, r2, CONSOLE_DATA\n jalr r0, r13, 0\nputc_bad:\n addi r1, r0, 63\n st r1, r2, CONSOLE_DATA\n jalr r0, r13, 0\n\n; puts: emit the zero-terminated string at address r1.\nputs:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n add r3, r1, r0\nputs_loop:\n lt r1, r3, 0\n brz r1, puts_done\n addi r-13, r-13, -3\n sw r3, r-13, 0\n jal r13, putc\n lw r3, r-13, 0\n addi r-13, r-13, 3\n addi r3, r3, 1\n jal r0, puts_loop\nputs_done:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; newline: emit one line break.\nnewline:\n addi r1, r0, 10\n addi r2, r0, CONSOLE_BASE\n st r1, r2, CONSOLE_DATA\n jalr r0, r13, 0\n\n; getline: read a line into LINEBUF, terminated by a zero tryte.\n;\n; Returns 1 in r1 if a line was read and 0 if the input ran out, which is how\n; the shell learns to stop. A line ends at a newline or at LINEMAX trytes.\ngetline:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r3, r0, LINEBUF\n addi r4, r0, 0 ; length so far\n addi r5, r0, CONSOLE_BASE\n\ngetline_loop:\n ; The status is a trit: positive means a character is ready, zero means\n ; wait, negative means the input has ended.\n lt r6, r5, CONSOLE_STATUS\n brn r6, getline_eof\n brz r6, getline_loop ; nothing yet; wait for the host\n lt r6, r5, CONSOLE_DATA\n addi r1, r6, -10 ; newline ends the line\n brz r1, getline_end\n addi r1, r0, LINEMAX\n sub r1, r4, r1\n brz r1, getline_end ; full; drop the rest of the line\n add r1, r3, r4\n st r6, r1, 0\n addi r4, r4, 1\n jal r0, getline_loop\n\ngetline_end:\n add r1, r3, r4\n st r0, r1, 0 ; terminate\n addi r1, r0, 1\n jal r0, getline_done\ngetline_eof:\n ; The input has ended. A partial line still counts as a line.\n add r1, r3, r4\n st r0, r1, 0\n brz r4, getline_nothing\n addi r1, r0, 1\n jal r0, getline_done\ngetline_nothing:\n addi r1, r0, 0\ngetline_done:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; ------------------------------------------------------------- line splitting\n\n; split: copy the first word of LINEBUF into ARGBUF, and the second into\n; ARGBUF at a fixed second slot. Returns the number of words in r1.\n;\n; Words are separated by spaces. This is the whole of the shell\'s parsing,\n; which is as much as ls, cd, cat and mkdir need.\nsplit:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r3, r0, LINEBUF\n addi r4, r0, ARGBUF\n ; Empty both slots first. A command with one word leaves the second\n ; slot untouched, and unwritten memory is a trap on this machine\n ; rather than a zero.\n st r0, r4, 0\n st r0, r4, 27\n addi r5, r0, 0 ; words seen\n addi r6, r0, 0 ; read index\n\nsplit_word:\n ; Skip any run of spaces.\n add r1, r3, r6\n lt r1, r1, 0\n brz r1, split_done\n addi r2, r1, -32\n brz r2, split_skip\n jal r0, split_copy\nsplit_skip:\n addi r6, r6, 1\n jal r0, split_word\n\nsplit_copy:\n ; Copy one word to slot r5 of ARGBUF, then advance.\n addi r2, r0, 0 ; index within the word\nsplit_copy_loop:\n add r1, r3, r6\n lt r1, r1, 0\n brz r1, split_end_word\n addi r11, r1, -32\n brz r11, split_end_word\n addi r11, r0, FS_NAME_TRYTES\n sub r11, r2, r11\n brz r11, split_end_word ; truncate an over-long word\n add r11, r4, r2\n st r1, r11, 0\n addi r2, r2, 1\n addi r6, r6, 1\n jal r0, split_copy_loop\n\nsplit_end_word:\n add r11, r4, r2\n st r0, r11, 0 ; terminate this word\n addi r5, r5, 1\n addi r4, r4, 27 ; next slot, one entry apart\n addi r11, r0, 2\n sub r11, r5, r11\n brz r11, split_done ; two words is all the shell uses\n jal r0, split_word\n\nsplit_done:\n add r1, r5, r0\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; arg_second: copy the second word into ARGBUF, where name_matches looks.\narg_second:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r1, r0, ARGBUF\n addi r2, r0, 0\narg_second_loop:\n addi r3, r0, FS_NAME_TRYTES\n sub r3, r2, r3\n brz r3, arg_second_done\n add r3, r1, r2\n lt r4, r3, 27 ; read from the second slot\n st r4, r3, 0 ; write to the first\n brz r4, arg_second_done\n addi r2, r2, 1\n jal r0, arg_second_loop\narg_second_done:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; ------------------------------------------------------------------ commands\n\n; cmd_ls: print the entries of the working directory, one per line.\ncmd_ls:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r10, r-13, 0\n addi r-13, r-13, -6\n addi r1, r0, CWD\n lw r1, r1, 0\n sw r1, r-13, 0 ; the directory\n addi r2, r0, INO_SIZE\n jal r13, inode_field\n sw r1, r-13, 3 ; entry count\n addi r10, r0, 0 ; index\n\nls_loop:\n lw r5, r-13, 3\n sub r4, r10, r5\n brz r4, ls_done\n lw r1, r-13, 0\n add r2, r10, r0\n jal r13, dir_entry ; leaves the block in the window\n brn r1, ls_next\n jal r13, print_entry_name\nls_next:\n addi r10, r10, 1\n jal r0, ls_loop\n\nls_done:\n addi r-13, r-13, 6\n lw r10, r-13, 0\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; print_entry_name: print the name of the entry at window offset r1.\n;\n; An empty entry has inode zero and is skipped, which is how a deleted name\n; would be represented once deletion exists.\nprint_entry_name:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r1, r-13, 0\n jal r13, word_get\n brz r1, print_entry_skip\n lw r1, r-13, 0\n addi r2, r0, BLOCK_BASE\n add r2, r2, r1\n addi r3, r0, 0\nprint_name_loop:\n addi r4, r0, FS_NAME_TRYTES\n sub r4, r3, r4\n brz r4, print_name_end\n add r4, r2, r3\n lt r1, r4, BLOCK_WINDOW+3\n brz r1, print_name_end\n addi r-13, r-13, -6\n sw r2, r-13, 0\n sw r3, r-13, 3\n jal r13, putc\n lw r2, r-13, 0\n lw r3, r-13, 3\n addi r-13, r-13, 6\n addi r3, r3, 1\n jal r0, print_name_loop\nprint_name_end:\n jal r13, newline\nprint_entry_skip:\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; cmd_cd: change the working directory to the name in ARGBUF.\ncmd_cd:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r1, r0, CWD\n lw r1, r1, 0\n jal r13, dir_find\n brz r1, cd_missing\n addi r-13, r-13, -3\n sw r1, r-13, 0\n addi r2, r0, INO_KIND\n jal r13, inode_field\n addi r2, r0, KIND_DIRECTORY\n sub r2, r1, r2\n lw r1, r-13, 0\n addi r-13, r-13, 3\n brz r2, cd_ok\n addi r1, r0, MSG_NOTDIR\n jal r13, puts\n jal r0, cd_done\ncd_ok:\n addi r2, r0, CWD\n sw r1, r2, 0\n jal r0, cd_done\ncd_missing:\n addi r1, r0, MSG_NOSUCH\n jal r13, puts\ncd_done:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; cmd_cat: print the contents of the file named in ARGBUF.\ncmd_cat:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r10, r-13, 0\n addi r-13, r-13, -9\n addi r1, r0, CWD\n lw r1, r1, 0\n jal r13, dir_find\n brz r1, cat_missing\n sw r1, r-13, 0 ; the inode\n addi r2, r0, INO_SIZE\n jal r13, inode_field\n sw r1, r-13, 3 ; size in trytes\n addi r10, r0, 0 ; trytes printed\n\ncat_loop:\n lw r5, r-13, 3\n sub r4, r10, r5\n brz r4, cat_done\n ; Which block, and where in it.\n addi r3, r0, FS_BLOCK_TRYTES\n rem r4, r10, r3\n sub r5, r10, r4\n shr r5, r5, 5 ; block index; 243 is 3^5\n sw r4, r-13, 6 ; offset within the block\n lw r1, r-13, 0\n addi r2, r5, INO_DIRECT\n jal r13, inode_field\n brz r1, cat_done ; a hole ends the file\n jal r13, bload\n lw r1, r-13, 6\n jal r13, win_get\n jal r13, putc\n addi r10, r10, 1\n jal r0, cat_loop\n\ncat_missing:\n addi r1, r0, MSG_NOSUCH\n jal r13, puts\ncat_done:\n addi r-13, r-13, 9\n lw r10, r-13, 0\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; cmd_mkdir: create a directory named by ARGBUF in the working directory.\n;\n; Three steps, in this order: allocate an inode, link it into the parent, and\n; only then mark it a directory. If the link fails the inode is still free,\n; which is the safe direction to fail in.\ncmd_mkdir:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -6\n\n addi r1, r0, CWD\n lw r1, r1, 0\n jal r13, dir_find\n brz r1, mkdir_new\n addi r1, r0, MSG_EXISTS\n jal r13, puts\n jal r0, mkdir_done\n\nmkdir_new:\n jal r13, inode_alloc\n brz r1, mkdir_full\n sw r1, r-13, 0 ; the new inode\n\n addi r2, r0, INO_KIND\n addi r3, r0, KIND_DIRECTORY\n jal r13, inode_set\n lw r1, r-13, 0\n addi r2, r0, INO_SIZE\n addi r3, r0, 0\n jal r13, inode_set\n\n addi r1, r0, CWD\n lw r1, r1, 0\n lw r2, r-13, 0\n jal r13, dir_link\n jal r0, mkdir_done\n\nmkdir_full:\n addi r1, r0, MSG_FULL\n jal r13, puts\nmkdir_done:\n addi r-13, r-13, 6\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; ------------------------------------------------------------- allocation\n\n; inode_alloc: find a free inode and return it in r1, or 0 if there is none.\n;\n; Inode 0 means \"none\", so the search starts at 1. The table is small and this\n; is a linear scan; a free list would be faster and is not worth the format\n; complexity at this size.\ninode_alloc:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r10, r-13, 0\n addi r-13, r-13, -3\n addi r10, r0, 1\n\ninode_alloc_loop:\n addi r1, r0, 81 ; the table holds 81 inodes\n sub r1, r10, r1\n brz r1, inode_alloc_full\n sw r10, r-13, 0\n add r1, r10, r0\n addi r2, r0, INO_KIND\n jal r13, inode_field\n lw r10, r-13, 0\n brz r1, inode_alloc_found\n addi r10, r10, 1\n jal r0, inode_alloc_loop\n\ninode_alloc_found:\n add r1, r10, r0\n jal r0, inode_alloc_done\ninode_alloc_full:\n addi r1, r0, 0\ninode_alloc_done:\n addi r-13, r-13, 3\n lw r10, r-13, 0\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; block_alloc: find a free data block, mark it used, and return it in r1.\n;\n; The allocation map holds one tryte per block, so a scan is a walk through\n; one block of the map. Zero means free.\nblock_alloc:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r10, r-13, 0\n addi r-13, r-13, -3\n\n addi r1, r0, SB_MAP\n lw r1, r1, 0\n jal r13, bload\n addi r1, r0, SB_DATA\n lw r10, r1, 0 ; first data block\n\nblock_alloc_loop:\n addi r1, r0, SB_TOTAL\n lw r1, r1, 0\n sub r1, r10, r1\n brz r1, block_alloc_full\n sw r10, r-13, 0\n add r1, r10, r0\n jal r13, win_get\n lw r10, r-13, 0\n brz r1, block_alloc_found\n addi r10, r10, 1\n jal r0, block_alloc_loop\n\nblock_alloc_found:\n add r1, r10, r0\n addi r2, r0, 1\n jal r13, win_put ; mark it used\n addi r1, r0, SB_MAP\n lw r1, r1, 0\n jal r13, bstore\n add r1, r10, r0\n jal r0, block_alloc_done\nblock_alloc_full:\n addi r1, r0, 0\nblock_alloc_done:\n addi r-13, r-13, -3\n sw r1, r-13, 0 ; the result, past the restore below\n addi r-13, r-13, 6\n lw r10, r-13, 0\n addi r-13, r-13, -6\n lw r1, r-13, 0\n addi r-13, r-13, 9\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; dir_link: append an entry naming ARGBUF and pointing at inode r2, in\n; directory r1.\ndir_link:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -12\n sw r1, r-13, 0 ; the directory\n sw r2, r-13, 3 ; the target inode\n\n addi r2, r0, INO_SIZE\n jal r13, inode_field\n sw r1, r-13, 6 ; entries in use\n add r10, r1, r0\n\n ; Which direct pointer this entry falls in, and which slot.\n addi r3, r0, FS_DIRENTS_PER_BLOCK\n rem r4, r10, r3\n sub r5, r10, r4\n shr r5, r5, 2\n sw r4, r-13, 9 ; slot\n\n lw r1, r-13, 0\n addi r2, r5, INO_DIRECT\n addi r-13, r-13, -3\n sw r5, r-13, 0\n jal r13, inode_field\n lw r5, r-13, 0\n addi r-13, r-13, 3\n brz r1, dir_link_grow\n jal r0, dir_link_write\n\ndir_link_grow:\n ; No block behind this slot yet, so take one and record it.\n addi r-13, r-13, -3\n sw r5, r-13, 0\n jal r13, block_alloc\n lw r5, r-13, 0\n addi r-13, r-13, 3\n brz r1, dir_link_done ; out of space; leave the directory alone\n addi r-13, r-13, -3\n sw r1, r-13, 0\n add r3, r1, r0\n lw r1, r-13, 3\n addi r2, r5, INO_DIRECT\n jal r13, inode_set\n lw r1, r-13, 0\n addi r-13, r-13, 3\n\ndir_link_write:\n ; r1 is the block. Load it, write the entry, store it back.\n addi r-13, r-13, -3\n sw r1, r-13, 0\n jal r13, bload\n lw r4, r-13, 12 ; the slot, past the block pushed above\n shl r4, r4, 3 ; slot * 27\n addi r-13, r-13, -3\n sw r4, r-13, 0\n\n add r1, r4, r0\n lw r2, r-13, 9 ; the target inode, past two pushes\n jal r13, word_put\n lw r4, r-13, 0\n addi r-13, r-13, 3\n jal r13, write_entry_name\n\n lw r1, r-13, 0\n addi r-13, r-13, 3\n jal r13, bstore\n\n ; The directory now has one more entry.\n lw r1, r-13, 0\n lw r3, r-13, 6\n addi r3, r3, 1\n addi r2, r0, INO_SIZE\n jal r13, inode_set\n jal r0, dir_link_finish\n\ndir_link_done:\n addi r-13, r-13, 3\ndir_link_finish:\n addi r-13, r-13, 12\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; write_entry_name: copy ARGBUF into the name field of the entry at window\n; offset r4, padding the rest of the field with zeros.\nwrite_entry_name:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r1, r0, BLOCK_BASE\n add r1, r1, r4\n addi r2, r0, ARGBUF\n addi r3, r0, 0\n addi r5, r0, 0 ; set once the name has ended\n\nwrite_name_loop:\n addi r6, r0, FS_NAME_TRYTES\n sub r6, r3, r6\n brz r6, write_name_done\n ; Past the end of the name, pad without reading the source: the trytes\n ; beyond the terminator were never written.\n brp r5, write_name_pad\n add r6, r2, r3\n lt r6, r6, 0\n brz r6, write_name_end_now\n jal r0, write_name_store\nwrite_name_end_now:\n addi r5, r0, 1\nwrite_name_pad:\n addi r6, r0, 0\nwrite_name_store:\n add r11, r1, r3\n st r6, r11, BLOCK_WINDOW+3\n addi r3, r3, 1\n jal r0, write_name_loop\n\nwrite_name_done:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; ------------------------------------------------------------------- the shell\n\n; shell: prompt, read a line, dispatch, repeat until the input runs out.\n;\n; The dispatch is a chain of comparisons rather than a table, because there are\n; four commands and a table would need a name-to-address structure that nothing\n; else wants.\nshell:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n\nshell_loop:\n addi r1, r0, MSG_PROMPT\n jal r13, puts\n jal r13, getline\n brz r1, shell_exit\n jal r13, split\n brz r1, shell_loop ; an empty line just prompts again\n\n ; The first word is in ARGBUF. Compare it against each command.\n addi r1, r0, CMD_LS\n jal r13, arg_is\n brp r1, shell_ls\n addi r1, r0, CMD_CD\n jal r13, arg_is\n brp r1, shell_cd\n addi r1, r0, CMD_CAT\n jal r13, arg_is\n brp r1, shell_cat\n addi r1, r0, CMD_MKDIR\n jal r13, arg_is\n brp r1, shell_mkdir\n addi r1, r0, CMD_RM\n jal r13, arg_is\n brp r1, shell_rm\n addi r1, r0, CMD_TOUCH\n jal r13, arg_is\n brp r1, shell_touch\n addi r1, r0, CMD_WRITE\n jal r13, arg_is\n brp r1, shell_write\n addi r1, r0, CMD_EXIT\n jal r13, arg_is\n brp r1, shell_exit\n addi r1, r0, CMD_LOGOUT\n jal r13, arg_is\n brp r1, shell_exit\n addi r1, r0, CMD_RUN\n jal r13, arg_is\n brp r1, shell_run\n\n addi r1, r0, MSG_UNKNOWN\n jal r13, puts\n jal r0, shell_loop\n\nshell_ls:\n jal r13, cmd_ls\n jal r0, shell_loop\nshell_cd:\n jal r13, arg_second\n jal r13, cmd_cd\n jal r0, shell_loop\nshell_cat:\n jal r13, arg_second\n jal r13, cmd_cat\n jal r0, shell_loop\nshell_mkdir:\n jal r13, arg_second\n jal r13, cmd_mkdir\n jal r0, shell_loop\nshell_rm:\n jal r13, arg_second\n jal r13, cmd_rm\n jal r0, shell_loop\nshell_touch:\n jal r13, arg_second\n jal r13, cmd_touch\n jal r0, shell_loop\nshell_write:\n jal r13, arg_second\n jal r13, cmd_write\n jal r0, shell_loop\nshell_run:\n jal r13, arg_second\n jal r13, cmd_run\n jal r0, shell_loop\n\n; Leaving the shell returns to `start`, which halts. Going out this way rather\n; than halting here keeps the stack unwound, which matters if anything is ever\n; layered above the shell.\nshell_exit:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; arg_is: compare ARGBUF with the zero-terminated string at r1.\n; Returns 1 in r1 if they match, 0 otherwise.\narg_is:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n add r2, r1, r0\n addi r3, r0, ARGBUF\n addi r4, r0, 0\narg_is_loop:\n add r5, r2, r4\n lt r5, r5, 0\n add r6, r3, r4\n lt r6, r6, 0\n sub r1, r5, r6\n brz r1, arg_is_same_tryte\n addi r1, r0, 0\n jal r0, arg_is_done\narg_is_same_tryte:\n brz r5, arg_is_match ; both terminated together\n addi r4, r4, 1\n jal r0, arg_is_loop\narg_is_match:\n addi r1, r0, 1\narg_is_done:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; -------------------------------------------------------------------- removal\n\n; block_free: mark block r1 free in the allocation map.\nblock_free:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r1, r-13, 0\n addi r1, r0, SB_MAP\n lw r1, r1, 0\n jal r13, bload\n lw r1, r-13, 0\n addi r2, r0, 0\n jal r13, win_put\n addi r1, r0, SB_MAP\n lw r1, r1, 0\n jal r13, bstore\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; dir_block: the block holding entry r2 of directory r1, without loading it.\n;\n; dir_entry loads the block, which is what a reader wants and what a writer\n; cannot use: moving an entry between blocks needs both numbers before either\n; is in the window.\ndir_block:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r3, r0, FS_DIRENTS_PER_BLOCK\n rem r4, r2, r3\n sub r5, r2, r4\n shr r5, r5, 2\n addi r2, r5, INO_DIRECT\n jal r13, inode_field\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; dir_slot: the tryte offset of entry r2 within whichever block holds it.\ndir_slot:\n addi r3, r0, FS_DIRENTS_PER_BLOCK\n rem r1, r2, r3\n shl r1, r1, 3 ; slot * 27\n jalr r0, r13, 0\n\n; entry_save: copy the 27 trytes at window offset r1 into ENTBUF.\nentry_save:\n addi r2, r0, BLOCK_BASE\n add r2, r2, r1\n addi r3, r0, ENTBUF\n addi r4, r0, 0\nentry_save_loop:\n addi r5, r0, FS_DIRENT_TRYTES\n sub r5, r4, r5\n brz r5, entry_save_done\n add r5, r2, r4\n lt r5, r5, BLOCK_WINDOW\n add r6, r3, r4\n st r5, r6, 0\n addi r4, r4, 1\n jal r0, entry_save_loop\nentry_save_done:\n jalr r0, r13, 0\n\n; entry_load: copy ENTBUF into the 27 trytes at window offset r1.\nentry_load:\n addi r2, r0, BLOCK_BASE\n add r2, r2, r1\n addi r3, r0, ENTBUF\n addi r4, r0, 0\nentry_load_loop:\n addi r5, r0, FS_DIRENT_TRYTES\n sub r5, r4, r5\n brz r5, entry_load_done\n add r6, r3, r4\n lt r6, r6, 0\n add r5, r2, r4\n st r6, r5, BLOCK_WINDOW\n addi r4, r4, 1\n jal r0, entry_load_loop\nentry_load_done:\n jalr r0, r13, 0\n\n; dir_unlink: remove entry r2 from directory r1.\n;\n; The last entry is moved into the hole and the count is decremented, so a\n; directory stays dense and its size keeps meaning the number of entries. The\n; alternative -- leaving a tombstone -- would let a directory that churns grow\n; without bound, and every reader would have to skip holes forever.\ndir_unlink:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -12\n sw r1, r-13, 0 ; the directory\n sw r2, r-13, 3 ; the entry to remove\n\n addi r2, r0, INO_SIZE\n jal r13, inode_field\n addi r1, r1, -1\n sw r1, r-13, 6 ; index of the last entry\n\n lw r2, r-13, 3\n sub r3, r2, r1\n brz r3, unlink_shrink ; removing the last entry needs no move\n\n ; Copy the last entry out of its block.\n lw r1, r-13, 0\n lw r2, r-13, 6\n jal r13, dir_entry\n brn r1, unlink_shrink\n jal r13, entry_save\n\n ; Write it over the entry being removed. The window has to be reloaded\n ; first: dir_block reads an inode, and reading an inode loads the inode\n ; table over whatever the window was holding. Every routine here shares\n ; one window, so anything that touches the filesystem invalidates it.\n lw r1, r-13, 0\n lw r2, r-13, 3\n jal r13, dir_block\n sw r1, r-13, 9 ; the destination block\n jal r13, bload\n lw r2, r-13, 3\n jal r13, dir_slot\n jal r13, entry_load\n lw r1, r-13, 9\n jal r13, bstore\n\nunlink_shrink:\n lw r1, r-13, 0\n lw r3, r-13, 6 ; the new count is the old last index\n addi r2, r0, INO_SIZE\n jal r13, inode_set\n\n addi r-13, r-13, 12\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; cmd_rm: remove the name in ARGBUF from the working directory.\n;\n; Works on a file or on an empty directory. A directory with entries is\n; refused, because removing it would strand everything inside it: there is no\n; path back to an inode that nothing names.\ncmd_rm:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r10, r-13, 0\n addi r-13, r-13, -9\n\n addi r1, r0, CWD\n lw r1, r1, 0\n sw r1, r-13, 0 ; the parent\n jal r13, dir_index\n brn r1, rm_missing\n sw r1, r-13, 3 ; the entry index\n\n lw r1, r-13, 0\n lw r2, r-13, 3\n jal r13, dir_entry\n jal r13, word_get\n sw r1, r-13, 6 ; the target inode\n\n addi r2, r0, INO_KIND\n jal r13, inode_field\n addi r2, r0, KIND_DIRECTORY\n sub r2, r1, r2\n brz r2, rm_check_empty\n jal r0, rm_free\n\nrm_check_empty:\n lw r1, r-13, 6\n addi r2, r0, INO_SIZE\n jal r13, inode_field\n brz r1, rm_free\n addi r1, r0, MSG_NOTEMPTY\n jal r13, puts\n jal r0, rm_done\n\nrm_free:\n ; Return the data blocks to the allocation map, then the inode.\n addi r10, r0, 0\nrm_free_loop:\n addi r1, r0, FS_DIRECT_BLOCKS\n sub r1, r10, r1\n brz r1, rm_free_inode\n lw r1, r-13, 6\n addi r2, r10, INO_DIRECT\n jal r13, inode_field\n brz r1, rm_free_next\n jal r13, block_free\nrm_free_next:\n addi r10, r10, 1\n jal r0, rm_free_loop\n\nrm_free_inode:\n lw r1, r-13, 6\n addi r2, r0, INO_KIND\n addi r3, r0, KIND_FREE\n jal r13, inode_set\n lw r1, r-13, 6\n addi r2, r0, INO_SIZE\n addi r3, r0, 0\n jal r13, inode_set\n\n lw r1, r-13, 0\n lw r2, r-13, 3\n jal r13, dir_unlink\n jal r0, rm_done\n\nrm_missing:\n addi r1, r0, MSG_NOSUCH\n jal r13, puts\nrm_done:\n addi r-13, r-13, 9\n lw r10, r-13, 0\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; ------------------------------------------------------------ file creation\n\n; file_new: create a file named by ARGBUF holding FILELEN trytes of FILEBUF.\n;\n; Returns the inode in r1, or 0 if there was no room. The order matters:\n; allocate, fill, then link. A failure before the link leaves an inode marked\n; free and a directory that never mentioned it, which is the safe direction.\nfile_new:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r10, r-13, 0\n addi r-13, r-13, -12\n\n jal r13, inode_alloc\n brz r1, file_new_full\n sw r1, r-13, 0 ; the new inode\n\n addi r2, r0, INO_KIND\n addi r3, r0, KIND_FILE\n jal r13, inode_set\n\n addi r1, r0, FILELEN\n lw r1, r1, 0\n sw r1, r-13, 3 ; length in trytes\n lw r1, r-13, 0\n addi r2, r0, INO_SIZE\n lw r3, r-13, 3\n jal r13, inode_set\n\n addi r10, r0, 0 ; trytes written so far\n\nfile_new_block:\n lw r1, r-13, 3\n sub r1, r10, r1\n brz r1, file_new_link ; nothing left to write\n brp r1, file_new_link\n\n ; Take a block and record it in the next direct slot.\n jal r13, block_alloc\n brz r1, file_new_link ; out of space; keep what fits\n sw r1, r-13, 6 ; the block\n addi r1, r0, FS_BLOCK_TRYTES\n rem r2, r10, r1\n sub r2, r10, r2\n shr r2, r2, 5 ; which direct pointer\n lw r1, r-13, 0\n addi r2, r2, INO_DIRECT\n lw r3, r-13, 6\n jal r13, inode_set\n\n ; Fill the window from FILEBUF and store it.\n jal r13, fill_window\n lw r1, r-13, 6\n jal r13, bstore\n addi r1, r0, FS_BLOCK_TRYTES\n add r10, r10, r1\n jal r0, file_new_block\n\nfile_new_link:\n addi r1, r0, CWD\n lw r1, r1, 0\n lw r2, r-13, 0\n jal r13, dir_link\n lw r1, r-13, 0\n jal r0, file_new_done\n\nfile_new_full:\n addi r1, r0, 0\nfile_new_done:\n addi r-13, r-13, 12\n lw r10, r-13, 0\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; fill_window: copy one block of FILEBUF, starting at tryte r10, into the\n; window, zero-padding past the end of the file.\n;\n; The window is not cleared between blocks, so the padding is not optional:\n; without it the tail of a short final block would hold whatever the last\n; transfer left there.\nfill_window:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r1, r0, FILEBUF\n addi r2, r0, FILELEN\n lw r2, r2, 0 ; the file\'s length\n addi r3, r0, 0 ; index within the block\n\nfill_loop:\n addi r4, r0, FS_BLOCK_TRYTES\n sub r4, r3, r4\n brz r4, fill_done\n add r4, r10, r3 ; absolute index into the file\n sub r5, r4, r2\n brn r5, fill_live\n addi r6, r0, 0 ; past the end; pad\n jal r0, fill_store\nfill_live:\n add r5, r1, r4\n lt r6, r5, 0\nfill_store:\n addi r-13, r-13, -12\n sw r1, r-13, 0\n sw r2, r-13, 3\n sw r3, r-13, 6\n sw r6, r-13, 9\n add r1, r3, r0\n lw r2, r-13, 9\n jal r13, win_put\n lw r1, r-13, 0\n lw r2, r-13, 3\n lw r3, r-13, 6\n addi r-13, r-13, 12\n addi r3, r3, 1\n jal r0, fill_loop\n\nfill_done:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; cmd_touch: create an empty file named by ARGBUF.\ncmd_touch:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r1, r0, CWD\n lw r1, r1, 0\n jal r13, dir_find\n brz r1, touch_new\n addi r1, r0, MSG_EXISTS\n jal r13, puts\n jal r0, touch_done\ntouch_new:\n addi r1, r0, FILELEN\n sw r0, r1, 0\n jal r13, file_new\n brz r1, touch_full\n jal r0, touch_done\ntouch_full:\n addi r1, r0, MSG_FULL\n jal r13, puts\ntouch_done:\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; cmd_write: create a file named by ARGBUF from the lines that follow.\n;\n; Reads lines into FILEBUF until one holding a single full stop, which is the\n; convention `ed` used for the same reason: a terminal has no way to send an\n; end marker that is not also a character.\ncmd_write:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -3\n sw r10, r-13, 0\n addi r-13, r-13, -3\n\n addi r1, r0, CWD\n lw r1, r1, 0\n jal r13, dir_find\n brz r1, write_collect\n addi r1, r0, MSG_EXISTS\n jal r13, puts\n jal r0, write_done\n\nwrite_collect:\n ; ARGBUF holds the file name and must survive the lines that follow,\n ; so it is copied out of the way and restored before the file is made.\n jal r13, name_stash\n addi r10, r0, 0 ; trytes collected\n\nwrite_line:\n addi r1, r0, MSG_CONT\n jal r13, puts\n jal r13, getline\n brz r1, write_finish ; the input ended\n jal r13, line_is_dot\n brp r1, write_finish\n\n ; Append the line and a newline to FILEBUF.\n addi r1, r0, LINEBUF\n addi r2, r0, 0\nwrite_copy:\n add r3, r1, r2\n lt r3, r3, 0\n brz r3, write_copy_end\n addi r4, r0, 1701 ; FS_DIRECT_BLOCKS * FS_BLOCK_TRYTES\n sub r4, r10, r4\n brz r4, write_finish ; full; stop taking input\n addi r4, r0, FILEBUF\n add r4, r4, r10\n st r3, r4, 0\n addi r10, r10, 1\n addi r2, r2, 1\n jal r0, write_copy\nwrite_copy_end:\n addi r4, r0, FILEBUF\n add r4, r4, r10\n addi r3, r0, 10\n st r3, r4, 0\n addi r10, r10, 1\n jal r0, write_line\n\nwrite_finish:\n addi r1, r0, FILELEN\n sw r10, r1, 0\n jal r13, name_restore\n jal r13, file_new\n brz r1, write_full\n jal r0, write_done\nwrite_full:\n addi r1, r0, MSG_FULL\n jal r13, puts\nwrite_done:\n addi r-13, r-13, 3\n lw r10, r-13, 0\n addi r-13, r-13, 3\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; name_stash: copy ARGBUF into the third argument slot, out of the way of the\n; lines cmd_write is about to read.\nname_stash:\n addi r1, r0, ARGBUF\n addi r2, r0, 0\nname_stash_loop:\n addi r3, r0, FS_NAME_TRYTES\n sub r3, r2, r3\n brz r3, name_stash_done\n add r3, r1, r2\n lt r4, r3, 0\n st r4, r3, 54\n brz r4, name_stash_done\n addi r2, r2, 1\n jal r0, name_stash_loop\nname_stash_done:\n jalr r0, r13, 0\n\n; name_restore: copy the stashed name back into ARGBUF.\nname_restore:\n addi r1, r0, ARGBUF\n addi r2, r0, 0\nname_restore_loop:\n addi r3, r0, FS_NAME_TRYTES\n sub r3, r2, r3\n brz r3, name_restore_done\n add r3, r1, r2\n lt r4, r3, 54\n st r4, r3, 0\n brz r4, name_restore_done\n addi r2, r2, 1\n jal r0, name_restore_loop\nname_restore_done:\n jalr r0, r13, 0\n\n; line_is_dot: 1 in r1 if LINEBUF holds exactly a full stop, 0 otherwise.\nline_is_dot:\n addi r1, r0, LINEBUF\n lt r2, r1, 0\n addi r2, r2, -46 ; the character itself\n brz r2, line_dot_maybe\n addi r1, r0, 0\n jalr r0, r13, 0\nline_dot_maybe:\n lt r2, r1, 1\n brz r2, line_dot_yes\n addi r1, r0, 0\n jalr r0, r13, 0\nline_dot_yes:\n addi r1, r0, 1\n jalr r0, r13, 0\n\n; --------------------------------------------------------------- system calls\n;\n; A program runs at user level and asks for things with `ecall`. The call\n; number is in r1, arguments in r2 onward, and the result comes back in r1\n; (D-19). A negative result is an error; the range is symmetric, so reserving\n; the negative half costs nothing and no second error channel is needed.\n\n; trap: the handler. Entered at machine level with the program\'s state in the\n; registers and its program counter in TEPC.\ntrap:\n ; The handler needs a stack, and r-13 belongs to the program, which may\n ; never have set one: registers start at zero, and pushing onto address\n ; zero would write over the operating system. Switch to the shell\'s\n ; stack and give the program\'s back on the way out.\n addi r11, r0, PROG_SP\n sw r-13, r11, 0\n addi r11, r0, SHELL_SP\n lw r-13, r11, 0\n\n csrr r11, TCAUSE\n addi r12, r11, -6 ; CAUSE_ECALL\n brz r12, trap_ecall\n\n ; Anything else is a fault in the program. Report it and take the\n ; processor back rather than letting the program keep it.\n addi r1, r0, MSG_FAULT\n jal r13, puts\n jal r0, trap_return_to_shell\n\ntrap_ecall:\n ; Dispatch on the call number. Unknown numbers return an error rather\n ; than killing the program, so a program built against a newer system\n ; can notice and fall back.\n brz r1, sys_exit\n addi r12, r1, -1\n brz r12, sys_write\n addi r12, r1, -2\n brz r12, sys_read\n addi r1, r0, -1\n jal r0, trap_resume\n\n; sys_exit: the program is done. Restore the shell and continue there.\nsys_exit:\n jal r0, trap_return_to_shell\n\n; sys_write: r2 handle, r3 address, r4 length. Returns trytes written.\nsys_write:\n addi r12, r2, -1 ; handle 1 is the console\n brz r12, sys_write_ok\n addi r1, r0, -1\n jal r0, trap_resume\nsys_write_ok:\n addi r5, r0, 0 ; trytes written\nsys_write_loop:\n sub r6, r5, r4\n brz r6, sys_write_done\n add r6, r3, r5\n lt r1, r6, 0\n addi r-13, r-13, -12\n sw r3, r-13, 0\n sw r4, r-13, 3\n sw r5, r-13, 6\n jal r13, putc\n lw r3, r-13, 0\n lw r4, r-13, 3\n lw r5, r-13, 6\n addi r-13, r-13, 12\n addi r5, r5, 1\n jal r0, sys_write_loop\nsys_write_done:\n add r1, r5, r0\n jal r0, trap_resume\n\n; sys_read: r2 handle, r3 address, r4 length. Returns trytes read, 0 at end.\nsys_read:\n brz r2, sys_read_ok ; handle 0 is the console\n addi r1, r0, -1\n jal r0, trap_resume\nsys_read_ok:\n addi r5, r0, 0\n addi r6, r0, CONSOLE_BASE\nsys_read_loop:\n sub r11, r5, r4\n brz r11, sys_read_done\n lt r11, r6, CONSOLE_STATUS\n brn r11, sys_read_done ; the input has ended\n brz r11, sys_read_loop ; nothing yet; wait\n lt r11, r6, CONSOLE_DATA\n add r12, r3, r5\n st r11, r12, 0\n addi r5, r5, 1\n addi r12, r11, -10 ; a newline ends the read\n brz r12, sys_read_done\n jal r0, sys_read_loop\nsys_read_done:\n add r1, r5, r0\n jal r0, trap_resume\n\n; trap_resume: hand the result back and continue the program past the ecall.\n;\n; TEPC names the faulting instruction, not the one after it, so the handler\n; steps over the ecall itself.\ntrap_resume:\n csrr r11, TEPC\n addi r11, r11, 3\n csrw r11, TEPC\n addi r11, r0, PROG_SP\n lw r-13, r11, 0 ; the program\'s stack, as it left it\n tret\n\n; trap_return_to_shell: abandon the program and continue where run left off.\ntrap_return_to_shell:\n addi r1, r0, SHELL_SP\n lw r-13, r1, 0 ; the shell\'s stack, as run left it\n addi r1, r0, EXITED\n addi r2, r0, 1\n sw r2, r1, 0\n lw r13, r-13, 0 ; run\'s own return address\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; ------------------------------------------------------------- running programs\n\n; cmd_run: load the file named by ARGBUF and enter it at user level.\ncmd_run:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n\n jal r13, load_program\n brn r1, run_bad\n\n ; Remember where to come back to. The handler restores this stack and\n ; returns through the link stored on it, which lands after this call.\n addi r1, r0, SHELL_SP\n sw r-13, r1, 0\n addi r1, r0, EXITED\n sw r0, r1, 0\n\n addi r1, r0, LOAD_ADDR\n csrw r1, TEPC\n addi r1, r0, PRIV_USER\n csrw r1, TPRIV\n tret ; the program runs from here\n\nrun_bad:\n addi r1, r0, MSG_NOTEXE\n jal r13, puts\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; load_program: read the file named by ARGBUF into memory at LOAD_ADDR.\n;\n; Returns 0 in r1, or -1 if there is no such file or it is not an executable\n; linked for this address.\nload_program:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -12\n\n addi r1, r0, CWD\n lw r1, r1, 0\n jal r13, dir_find\n brz r1, load_fail\n sw r1, r-13, 0 ; the inode\n\n ; The header is in the first block. Check the magic and the address it\n ; was linked for; there is no relocation, so a mismatch is fatal.\n addi r2, r0, INO_DIRECT\n jal r13, inode_field\n brz r1, load_fail\n jal r13, bload\n addi r1, r0, 0\n jal r13, word_get\n addi r2, r0, EXE_MAGIC\n sub r2, r1, r2\n brz r2, load_checked\n jal r0, load_fail\nload_checked:\n addi r1, r0, 3 ; HDR_LINK, in trytes\n jal r13, word_get\n addi r2, r0, LOAD_ADDR\n sub r2, r1, r2\n brz r2, load_sized\n jal r0, load_fail\nload_sized:\n addi r1, r0, 9 ; HDR_CODE, in trytes\n jal r13, word_get\n sw r1, r-13, 3 ; trytes of code\n\n ; Copy the code into memory, one tryte at a time. The loop counts\n ; trytes rather than bytes: the byte offset is twice the tryte index,\n ; and doubling is an addition where halving would not be a trit shift.\n addi r10, r0, 0 ; tryte index\nload_loop:\n lw r2, r-13, 3\n sub r2, r10, r2\n brz r2, load_done\n\n add r1, r10, r10 ; byte offset of this tryte\n addi r1, r1, EXE_HEADER_TRYTES\n jal r13, file_byte ; the low digit\n sw r1, r-13, 6\n add r1, r10, r10\n addi r1, r1, EXE_HEADER_TRYTES\n addi r1, r1, 1\n jal r13, file_byte ; the high digit\n shl r1, r1, EXE_RADIX_TRITS\n lw r2, r-13, 6\n add r1, r1, r2\n addi r2, r0, EXE_BIAS\n sub r1, r1, r2 ; undo the bias\n\n addi r2, r0, LOAD_ADDR\n add r2, r2, r10\n st r1, r2, 0\n\n addi r10, r10, 1\n jal r0, load_loop\n\nload_done:\n addi r1, r0, 0\n jal r0, load_exit\nload_fail:\n addi r1, r0, -1\nload_exit:\n addi r-13, r-13, 12\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n\n; file_byte: byte r1 of the file whose inode is on the stack at r-13+0.\n;\n; Loads whichever block holds it, so the caller must not rely on the window\n; across a call.\nfile_byte:\n addi r-13, r-13, -3\n sw r13, r-13, 0\n addi r-13, r-13, -6\n sw r1, r-13, 0\n\n addi r2, r0, FS_BLOCK_TRYTES\n rem r3, r1, r2\n sw r3, r-13, 3 ; offset within the block\n sub r4, r1, r3\n shr r4, r4, 5 ; which direct pointer\n\n lw r1, r-13, 9 ; the inode, just past this frame\n addi r2, r4, INO_DIRECT\n jal r13, inode_field\n brz r1, file_byte_zero\n jal r13, bload\n lw r1, r-13, 3\n jal r13, win_get\n jal r0, file_byte_done\nfile_byte_zero:\n addi r1, r0, 0\nfile_byte_done:\n addi r-13, r-13, 6\n lw r13, r-13, 0\n addi r-13, r-13, 3\n jalr r0, r13, 0\n";Expand description
The operating system, in assembly.