// A very basic implementation of a stdlib.h but in assembly.
// should(tm) work with riscemu.
//
// Copyright (c) 2023 Anton Lydike
// SPDX-License-Identifier: MIT

.data

_rand_seed:
.word   0x76767676
_atexit_calls:
// leave room for 8 atexit handlers here for now
.word   0x00, 0x00, 0x00, 0x00
.word   0x00, 0x00, 0x00, 0x00
_atexit_count:
.word   0x00

_malloc_base_ptr:
// first word is a pointer to some space
// second word is the offset inside that space
// space is always MALLOC_PAGE_SIZE bytes
.word   0x00, 0x00
.equ    MALLOC_PAGE_SIZE 4069

.text

// malloc/free

.globl malloc
.globl free

// malloc(size_t size)
malloc:
        // set aside size in s0
        sw  s0, -4(sp)
        mv  s0, a0
        la  t0, _malloc_base_ptr
        lw  t1, 0(t0)
        beq t1, zero, _malloc_init
_malloc_post_init:
        // if we are here, we always have
        // t0 = (&_malloc_base_ptr)
        // t1 = *(&_malloc_base_ptr)
        // now we load
        // t2 = base_ptr_offset
        lw  t2, 4(t0)
        // add allocated size to offset
        add t2, t2, s0
        // check for overflow
        li  t4, MALLOC_PAGE_SIZE
        bge t2, t4, _malloc_fail
        // save the new offset
        sw  t2, 4(t0)
        // calculate base_ptr + offset
        add a0, t2, t1
        // return that
        lw  s0, -4(sp)
        ret

_malloc_init:
        // call mmap2()
        li  a0, 0       // addr = 0, let OS choose address
        li  a1, 4096    // size
        li  a2, 3       // PROT_READ   | PROT_WRITE
        li  a3, 5       // MAP_PRIVATE | MAP_ANONYMOUS
        li  a7, SCALL_MMAP2
        ecall           // invoke syscall
        // check for error code
        li  t2, -1
        beq a0, t2, _malloc_fail
        // if succeeded, load &_malloc_base_ptr
        // put value of _malloc_base_ptr into t1
        mv  t1, a0
        // save base ptr to _malloc_base_ptr
        sw  t1, 0(t0)
        // jump to post_init
        j   _malloc_post_init
_malloc_fail:
        li a0, 0
        ret

// free is a nop, that's valid, but not very good^^
free:
        ret



// exit, atexit
.globl exit
.globl atexit

// we can happily use saved registers here because we don't care at all if we
// destroy the calling registers. This is __noreturn__ anyways!
// register layout:
// s0 = &_atexit_count
// s2 = &_atexit_calls
// s1 = updated value of atexit
// s3 = exit code
exit:
        // save exit code to s3
        mv      s3, a0
_exit_start:
        la      s0, _atexit_count       // s0 = &_atexit_count
        lw      s1, 0(s0)               // s1 = *(&_atexit_count)
        // exit if no atexit() calls remain
        beq     s1, zero, _exit
        // decrement
        addi    s1, s1, -4              // s1--
        // save decremented value
        sw      s1, 0(s0)               // _atexit_count = s1
        li      s2, _atexit_calls
        add     s1, s1, s2              // s1 = &_atexit_calls + (s1)
        lw      s1, 0(s1)               // s1 = *s1
        la      ra, _exit_start         // set ra up to point to exit
        jalr    zero, s1, 0             // jump to address in s1
        // jalr will call the other function, which will then return back
        // to the beginning of exit.
_exit:
        mv      a0, s3
        li      a7, 93
        ecall

// atexit a0 = funcptr
atexit:
        sw      t0, -4(sp)
        sw      t2, -8(sp)
        // load _atexit_count
        la      t0, _atexit_count
        lw      t2, 0(t0)
        // if >= 8, fail
        li      t1, 8
        bge     t2, t1, _atexit_fail
        // increment atexit_count by 4 (one word)
        addi    t2, t2, 4
        sw      t2, 0(t0)
        // load address of _atexit_calls
        la      t0, _atexit_calls
        // add new _atexit_count to _atexit_calls
        add     t0, t0, t2
        sw      a0, -4(t0)
        li      a0, 0
        lw      t0, -4(sp)
        lw      t2, -8(sp)
        ret

_atexit_fail:
        li      a0, -1
        lw      s0, -4(sp)
        lw      s1, -8(sp)
        ret






// rand, srand

.globl  rand
.globl  srand

// simple xorshift rand implementation
rand:
        // load seed
        la  t1, _rand_seed
        lw  a0, 0(t1)
        // three rounds of shifts:
        sll a0, t0, 13          // x ^= x << 13;
        srl a0, t0, 17          // x ^= x >> 17;
        sll a0, t0, 5           // x ^= x << 5;
	    sw  a0, 0(t1)
	    ret

srand:
        la  t1, _rand_seed
        sw  a0, 0(t1)
        ret
