31 Commits
Author SHA1 Message Date
MutexRaceCondition 3d874cebbd Merged with main 2026-09-03 15:17:00 +02:00
MutexRaceCondition 4aca17aa40 Moved memory operations into mem.asm (and renamed them) 2026-09-03 15:15:54 +02:00
ShatteredMINT 0188969dab Merge pull request 'Added read_line console function' (#10) from Micha_i/symphony_stdlib:console-functions into main
Reviewed-on: #10
2026-09-03 14:56:12 +02:00
Micha_i e4edb56d08 Merge branch 'main' into console-functions 2026-09-03 09:04:03 +02:00
Michał Isalski aa8eef7b1f Fixed at-address 2026-09-02 22:56:24 +02:00
ShatteredMINT 37d0e556d2 Merge pull request 'clean up confusion about function template' (#14) from meta-documentation into main
Reviewed-on: #14
2026-09-02 20:49:38 +02:00
ShatteredMINT 26d3a4d4f4 clean up confusion about function template 2026-09-02 20:49:22 +02:00
Michał Isalski 870b6ebc39 Merge branch 'main' into console-functions 2026-09-02 20:41:38 +02:00
MutexRaceCondition 7b43e75a9d Added memcmp, memcpy, memset32, unsafe.memcmp 2026-09-02 19:41:56 +02:00
Michal Isalski ca1ba67fd8 Added a shift conversion LUT and finished read_line (except Writeback) 2026-09-02 13:20:55 +02:00
ShatteredMINT 786adfdb1c Merge pull request 'Changed docs of math functions to conform to new guidelines' (#12) from Micha_i/symphony_stdlib:math-functions-docs into main
Reviewed-on: #12
2026-09-02 11:46:14 +02:00
Michał Isalski 84636799b6 Changed docs of math functions to conform to new guidelines 2026-09-02 11:44:30 +02:00
ShatteredMINT aa2cbe4ad8 Merge pull request 'Add basic meta documentation' (#11) from meta-documentation into main
Documentation efforts are ongoing but this should be good enough for now
2026-09-02 11:39:25 +02:00
ShatteredMINT 8301d5988a create teaching directory 2026-09-02 11:32:46 +02:00
ShatteredMINT dbd4865f14 change stack start 2026-09-02 11:31:52 +02:00
ShatteredMINT 8f1b970a2f remove mention of non existent file 2026-09-02 11:31:33 +02:00
ShatteredMINT ddca02d8a8 relax r7 requirement for result stack 2026-09-02 11:21:03 +02:00
ShatteredMINT a3d6cb64b5 remove duplicate documentation from stdlib.asm 2026-09-02 11:17:18 +02:00
Michał Isalski f9bb69f1f5 Added storing shift status 2026-09-02 02:03:00 +02:00
ShatteredMINT 9816111f38 clarify stack arguments 2026-09-01 14:08:20 +02:00
ShatteredMINT f2e5c830b3 explain inline comment 2026-09-01 13:48:12 +02:00
Michal Isalski 65caf8aff9 Made read_line compliant to new doc guidelines 2026-09-01 13:47:09 +02:00
ShatteredMINT 60e7b6209f basic contribution guidelines 2026-09-01 13:21:17 +02:00
ShatteredMINT 33b7886e70 add arrays to readme 2026-09-01 13:07:13 +02:00
Michal Isalski 1dca814388 One instruction less by using another register 2026-09-01 10:54:29 +02:00
Michal Isalski 9dfb8248a6 Added handling for skipping non-renderable characters
Added handling for backspace character
2026-09-01 10:51:31 +02:00
Michał Isalski 4b962a1f87 Added read_line function 2026-09-01 00:38:37 +02:00
ShatteredMINT daf4168129 format calling convention 2026-08-31 10:17:25 +02:00
ShatteredMINT 4bce47209a fix remaining links 2026-08-31 10:12:57 +02:00
ShatteredMINT af5a750031 link test 2026-08-31 10:10:10 +02:00
ShatteredMINT 8e0fdf0d28 start readme 2026-08-31 10:08:44 +02:00
13 changed files with 250 additions and 115 deletions
View File
-21
View File
@@ -1,21 +0,0 @@
# Memory Map
To not have to allocate a ton of things at run time the Standard Library uses a static memory map for some purposes
## Overview
| Start | Use |
| ---- | ---- |
| `0x0` | Reset Vector |
| `0x16` | Zero Page |
| `0x100` | User Code |
| `?` | Library Code |
| `0x1_0000` | LUTs |
| `?` | heap |
| `0xXXF0_0000` | Stack |
| `0xXXFF_0000` | quick access |
## Zero Page
Some values are needed not often enough to get their own special register, but often enough that it makes sense to keep them at a quickly accessible location.
This is what the zero page is for. its position in memory allows us to load them into a register with a single instruction.
-1
View File
@@ -4,7 +4,6 @@ This is a standard library for symphony.
It is both intended as a practical toolkit to develop more complex software as well as a teaching resource.
If you just want to use the standard library [[stdlib.asm]] is your main header, include it after your code.
You also need to include [[globals.asm]] as the first line in your assembly file.
If you are using it as a learning resource have a look at the [teaching folder](teaching).
View File
View File
-3
View File
@@ -1,3 +0,0 @@
# Examples
Examples of how to use the standard library to accomplish a task.
-10
View File
@@ -1,10 +0,0 @@
jmp 0x100
@0x10
screen:
frambuffer_ptr: U32 0x0
size: U32 0x0
position_xy: U32 0x0
mode: U32 0x0
@0x100
View File
+174
View File
@@ -0,0 +1,174 @@
; int compare(uint8_t* a, uint8_t* b, size_t count);
; Compares two memory segment of equal length lexicographically.
;
; Arguments:
; - `r1`: A pointer to the first memory segment.
; - `r2`: A pointer to the second memory segment.
; - `r3`: The size of both memory segments.
; Results:
; - `r1`:
; - `0` if both segments are equal.
; - `<0` if the first segment is less than the second segment.
; - `>0` if the first segment is greater than the second segment.
;
pub compare:
; Exclusive end point of the first segment.
add r3, r3, r1
sub r3, r3, 4
_compare__loop:
load_32 r4, [r1]
add r1, r1, 4
load_32 r5, [r2]
add r2, r2, 4
; Comparing two sequences of 4 bytes lexicographically is equivalent to
; comparing the corresponding big endian 32 bit words.
cmp r4, r5
jne _compare__break
; Check if there are enough bytes left to continue with the vectorized loop.
cmp r1, r3
jbe _compare__loop
; `r3 + 4 - r1 = <remaining byte count> = r3 - r1 mod 4`
sub flags, r3, r1
; Check if one of the lowest 2 bits is non-zero
jbe _compare__rem
; If not, we are done. Both segments are equal.
mov r1, 0
jmp r13
_compare__break:
; `flags` is the comparison result in the format of `cmp`. Convert it to the desired format.
; 00 => 0x40000000 > 0
; 01 => 0x00000000 = 0
; 10 => 0xC0000000 < 0
xor r1, flags, 1
lsl r1, r1, 30
jmp r13
_compare__rem:
; Compute `S = 8*(4 - <remaining byte count>)` and
; [r1] >> S, [r2] >> S
mov r3, 8
load_32 r4, [r1]
sub r3, r3, flags
load_32 r5, [r2]
lsl r3, r3, 3
lsr r4, r4, r3
lsr r5, r5, r3
; Compare both values, now with garbage bytes removed.
cmp r4, r5
jmp _compare__break
; void copy(void* src, void* dest, size_t count);
; Copies `count` bytes from `src` to `dest`. The two memory segments must not overlap.
;
; Arguments:
; - `r1`: Pointer to the memory segment to be copied.
; - `r2`: Pointer to the memory segment to be copied into.
; - `r3`: Byte size of both the `src` and `dest` segments.
;
pub copy:
; Exclusive end point of the source segment.
add r3, r1, r3
; Last index from where we can safely copy 8 bytes per loop iteration.
sub r3, r3, 8
jmp _copy__loop_entry
_copy__loop:
; Copy 8 bytes from `src` to `dest`.
load_32 flags, [r1]
add r1, r1, 4
store_32 [r2], flags
add r2, r2, 4
load_32 flags, [r1]
add r1, r1, 4
store_32 [r2], flags
add r2, r2, 4
_copy__loop_entry:
; Check if we can process more data in the vectorized loop.
cmp r1, r3
jbe _copy__loop
; The remaining amount of bytes `R` is `R = r3 + 8 - r1 = r3 - r1 mod 8`.
sub flags, r3, r1
; Test if `R` is not a multiple of `4`, i.e. the lowest 2 bits are non-zero.
jbe _copy__rem
; `R` is a multiple of `4`. Special case this.
; Check if `R` is `0`, i.e. the third bit is also 0. In that case, we are already done.
; There are no conditional indirect jumps, so we can't return immediately.
jge _copy__ret
; `R = 4`. No need to update `r1` or `r2`, we don't need them anymore.
load_32 flags, [r1]
store_32 [r2], flags
_copy__ret:
; Return
jmp r13
_copy__rem:
; Optimize the remaining cases for code size.
; End point of the source segment.
add r3, r3, 8
; We already handled the case `R = 0` earlier,
; so no bounds check needed for the first iteration.
_copy__rem_loop:
; Copy 1 byte.
load_8 flags, [r1]
add r1, r1, 1
store_8 [r2], flags
add r2, r2, 1
; Check if we are still within the bounds.
cmp r1, r3
jb _copy__rem_loop
jmp r13
; void fill32(uint8_t* dest, size_t count, uint32_t value);
; Fills `count` bytes in `dest` with `value`. If `count` is not a multiple of 4,
; the least significant bytes of `value` are cut off for the last entry.
;
; Arguments:
; - `r1`: A pointer to the destination segment.
; - `r2`: The size of the destination segment.
; - `r3`: The 32 bit value that the segment is filled with.
;
pub fill32:
; Exclusive end point of the destination segment.
add r2, r1, r2
; Last index from where we can safely write 8 bytes per loop iteration.
sub r2, r2, 8
jmp _fill32__entry
_fill32__loop:
; Set 8 bytes per loop iteraion.
store_32 [r1], r3
add r1, r1, 4
store_32 [r1], r3
add r1, r1, 4
_fill32__entry:
; Check if we can process more data in the vectorized loop.
cmp r1, r2
jbe _fill32__loop
; The remaining amount of bytes `R` is `R = r2 + 8 - r1 = r2 - r1 mod 8`.
sub flags, r2, r1
; Check if the third bit of the remainder is cleared.
jge _fill32__r4
; Otherwise set 4 bytes.
store_32 [r1], r3
add r1, r1, 4
_fill32__r4:
; Check if the two least significant bits of the remainder are zero.
ja _fill32__ret
; Handle the remaining bytes `R` individually, in reverse order.
add r2, r2, 4
; `r1 + 4 - r2 = 4 - R`.
sub flags, r1, r2
; Exclusive end point of the destination segment.
add r2, r2, 4
; Shift out the least significant `8*(4 - R)` bits of the value.
lsl flags, flags, 3
lsr r3, r3, flags
jmp _fill32__loop2_entry
_fill32__loop2:
sub r2, r2, 1
; Write the least significant byte of the value...
store_8 [r2], r3
; and then shift it out.
lsr r3, r3, 8
_fill32__loop2_entry:
cmp r1, r2
jb _fill32__loop2
_fill32__ret:
jmp r13
-77
View File
@@ -1,77 +0,0 @@
; Returns the index of the first element matching the provided predicate function (or -1 if not found)
; Arguments:
; r1 - The array pointer
; r2 - The array length (number of items)
; r3 - The stride (size of one item) - either 1, 2 or 4 (bytes)
; r4 - The predicate
; r5 - Predicate context
; Result:
; r1 - The index of the first element matching the provided predicate function (or -1 if not found)
; Clobbers: r2, r3, r4, r5, r6, + what the predicate clobbers
; Info:
; The predicate function should follow the stdlib calling convention
; The predicate receives two arguments (the value and the predicate context) and should return either a zero when the value is not the one we search for
; , or any other result if it is the searched-for item.
pub find_index:
push r12 ; We will store the predicate pointer here
push r11 ; We will store the current pointer here
push r10 ; We will store the stride here
push r9 ; We will store the final address here
push r8 ; We will store the mask here
mov r12, r4
mov r11, r1
mov r10, r3
mov r9, r2
lsr r6, r3, 1 ; We turn the stride into a byte shift
lsl r9, r9, r6 ; We calculate bytes left
add r9, r9, r1 ; We add the start address to get the final address
push r13 ; We save up the return address because we will provide our own to the predicate
push r1 ; We need the array pointer to calculate the item index
counter r13
add r13, r13, 52 ; Point to just after the predicate call - we can set this up now so we don't waste loop cycles
nand r8, zr, zr ; We create a mask of 0xFFFFFFFF
mov r6, 4
sub r6, r6, r3 ; We create a "negative stride", e.g. 4 -> 0, 2 -> 2, 1 -> 3
lsl r6, r6, 3
lsr r8, r8, r6 ; We shift the mask by the negative stride to obtain the proper mask for a value
; e.g. stride 4 -> mask is 0xFFFFFFFF
; stride 2 -> mask is 0x0000FFFF
; stride 1 -> mask is 0x000000FF
push r5 ; We save the predicate context on the stack
find_index_loop:
load_32 r1, [r11] ; We load the element
and r1, r1, r8 ; We mask it to handle stride 2 and 1 cases
load_32 r2, [sp] ; We load the predicate context into r2
jmp r12 ; We call the predicate
cmp r1, zr
jne find_index_found_item ; If we found the item, we jump out
; If we didn't, move to next item
add r11, r11, r10 ; We add the stride to the pointer
cmp r11, r9 ; We compare with the final address
jne find_index_loop ; If we did not reach the end we jump back into the loop
find_index_not_found:
add sp, sp, 8 ; The predicate context and old array pointer are not useful
pop r13 ; We get our return address
nand r1, zr, zr ; We put -1 in r1
jmp find_index_postamble
find_index_found_item:
add sp, sp, 4 ; The predicate context is not useful
pop r1 ; We get the array pointer
pop r13 ; We get our return address
sub r1, r11, r1 ; We calculate the bytes from the start
lsr r10, r10, 1 ; We shift the stride to get the amount to shift the bytes for
lsr r1, r1, r10 ; We shift to get the index of the item
find_index_postamble:
pop r8
pop r9
pop r10
pop r11
pop r12
jmp r13 ; Return
+1
View File
@@ -1,5 +1,6 @@
pub include bit
pub include imath
pub include mem
pub include console
; Needs to be last!
-3
View File
@@ -1,3 +0,0 @@
# Tests
Tests for the standard library go here, tests are allowed to depend on the recommended spec.isa changes.
+75
View File
@@ -0,0 +1,75 @@
; int memcmp(uint8_t* a, uint8_t* b, size_t count);
; Compares two memory segment of equal length lexicographically.
; Temporarily modifies the byte at address `a + count`.
;
; Arguments:
; - `r1`: A pointer to the first memory segment.
; - `r2`: A pointer to the second memory segment.
; - `r3`: The size of both memory segments.
; Results:
; - `r1`:
; - `0` if both segments are equal.
; - `<0` if the first segment is less than the second segment.
; - `>0` if the first segment is greater than the second segment.
pub fn_memcmp:
; Exclusive end point of the second segment.
add r4, r3, r2
; Exclusive end point of the first segment.
add r3, r3, r1
load_8 r6, [r3]
load_8 flags, [r4]
; Check if the first bytes behind the sequences are equal.
cmp flags, r6
jne _memcmp__loop
; Change the byte directly behind the first segment.
xor r4, r6, 1
; This would be problematic if someone calls memcmp with a first segment
; whose end point overlaps the program memory of this function.
store_8 [r3], r4
_memcmp__loop:
load_32 r4, [r1]
add r1, r1, 4
load_32 r5, [r2]
add r2, r2, 4
; Comparing two sequences of 4 bytes lexicographically is equivalent to
; comparing the corresponding big endian 32 bit words.
cmp r4, r5
je _memcmp__loop
; We overshot in the loop; decrement r1 again. (Only by 2, we backtrack the rest if necessary later)
sub r1, r1, 2
; Restore the byte we changed.
store_8 [r3], r6
; We encountered two different words. Figure out what byte they differ on.
xor r4, r4, r5
; Store the flags for later, to figure out the return value.
mov r5, flags
; Check if at least one of the two most significant bytes is not 0.
cmp r4, 0xffff
jbe _memcmp__low2
; If it is, backtrack the remaining 2 indices.
; Shift the most significant bytes to the least significant ones.
sub r1, r1, 2
lsr r4, r4, 16
_memcmp__low2:
; r1 now points to a non-zero 16 bit value.
; If the 16 bit value at r1-2 is in-bounds, then it is 0.
; Check if the most significant byte of the 16 bit value is 0.
cmp r4, 0xff
ja _memcmp__low1
; If it is, our target is the least significant byte.
add r1, r1, 1
_memcmp__low1:
; Otherwise, the target is that non-zero byte.
; Check if the target is out of bounds, i.e. the loop terminated through the "bounds check".
cmp r1, r3
jae _memcmp__oob
; r5 is the comparison result in the format of `cmp`. Convert it to the desired format.
; 00 => 0x40000000 > 0
; 01 => 0x00000000 = 0
; 10 => 0xC0000000 < 0
xor r1, r5, 1
lsl r1, r1, 30
jmp r13
_memcmp__oob:
mov r1, 0
jmp r13