From e90b72ec3082588b296956730bd2930cf8d5d671 Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Fri, 10 Jul 2026 11:31:46 +0800 Subject: refactor: promote new architecture to repository root Archive the deprecated implementation under lume-legacy, make the new kernel the default root-level build, and update repository entry points without changing kernel behavior. --- src/kernel/proc/exec.cc | 260 ------------------- src/kernel/proc/proc.cc | 654 ------------------------------------------------ src/kernel/proc/swtch.S | 37 --- 3 files changed, 951 deletions(-) delete mode 100644 src/kernel/proc/exec.cc delete mode 100644 src/kernel/proc/proc.cc delete mode 100644 src/kernel/proc/swtch.S (limited to 'src/kernel/proc') diff --git a/src/kernel/proc/exec.cc b/src/kernel/proc/exec.cc deleted file mode 100644 index 8d8c467..0000000 --- a/src/kernel/proc/exec.cc +++ /dev/null @@ -1,260 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "common/types.h" -#include "kernel/riscv.h" -#include "kernel/proc.h" -#include "kernel/mm.h" -#include "kernel/elf.h" -#include "fs/fs.h" -#include "lib/string.h" -#include "drivers/uart.h" -#include "kernel/trap.h" - -extern char trampoline[]; - -// RAII: Page Table Resource Management -class VmGuard -{ -public: - uint64 *pagetable = nullptr; - uint64 sz = 0; - - VmGuard() : pagetable(VM::uvmcreate()) {} - - ~VmGuard() - { - if (pagetable) - { - VM::uvmfree(pagetable, sz); - } - } - - // Copying is prohibited - VmGuard(const VmGuard &) = delete; - VmGuard &operator=(const VmGuard &) = delete; - - // Submit resources: Ownership transfer, destructor no longer releases - uint64 *commit() - { - uint64 *pt = pagetable; - pagetable = nullptr; - return pt; - } -}; - -// RAII: Inode Lock Management -class InodeGuard -{ -public: - Inode *ip; - InodeGuard(Inode *i) : ip(i) - { - if (ip) - VFS::ilock(ip); - } - ~InodeGuard() - { - if (ip) - { - VFS::iunlockput(ip); // Automatically unlock and release references - } - } - // Copying is prohibited - InodeGuard(const InodeGuard &) = delete; - InodeGuard &operator=(const InodeGuard &) = delete; -}; - -// laod segment -static int loadseg(uint64 *pagetable, uint64 va, Inode *ip, uint64 offset, uint64 sz) -{ - for (uint64 i = 0; i < sz; i += PGSIZE) - { - uint64 pa = VM::walkaddr(pagetable, va + i); - if (pa == 0) - { - Drivers::uart_puts("[Exec Debug] loadseg: walkaddr failed\n"); - return -1; - } - - uint64 n = PGSIZE - (va + i) % PGSIZE; - if (i + n > sz) - n = sz - i; - - // Read physical address (isUser=0) - if (ip->read((char *)(pa + (va + i) % PGSIZE), offset + i, n, 0) != (int)n) - { - Drivers::uart_puts("[Exec Debug] loadseg: read failed\n"); - return -1; - } - } - return 0; -} - -namespace Exec -{ - int exec(char *path, char **argv) - { - // 1. Get Inode - Inode *raw_ip = VFS::namei(path); - if (!raw_ip) - { - Drivers::uart_puts("[Exec Debug] namei failed for path: "); - Drivers::uart_puts(path); - Drivers::uart_puts("\n"); - return -1; - } - InodeGuard iguard(raw_ip); - - // Get ELF head - struct elfhdr elf; - if (raw_ip->read((char *)&elf, 0, sizeof(elf), 0) != sizeof(elf)) - { - Drivers::uart_puts("[Exec Debug] read elf header failed\n"); - return -1; - } - if (elf.magic != ELF_MAGIC) - { - Drivers::uart_puts("[Exec Debug] invalid elf magic\n"); - return -1; - } - - // Create new pagetable - VmGuard vm_guard; - if (!vm_guard.pagetable) - { - Drivers::uart_puts("[Exec Debug] uvmcreate failed\n"); - return -1; - } - - // Load Program Segment - struct proghdr ph; - for (int i = 0; i < elf.phnum; i++) - { - uint64 off = elf.phoff + i * sizeof(ph); - if (raw_ip->read((char *)&ph, off, sizeof(ph), 0) != sizeof(ph)) - { - Drivers::uart_puts("[Exec Debug] read proghdr failed\n"); - return -1; - } - - if (ph.type != ELF_PROG_LOAD) - continue; - if (ph.memsz < ph.filesz) - return -1; - if (ph.vaddr + ph.memsz < ph.vaddr) - return -1; - - uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, vm_guard.sz, ph.vaddr + ph.memsz, PTE_W | PTE_X | PTE_R | PTE_U); - if (sz1 == 0) - { - Drivers::uart_puts("[Exec Debug] uvmalloc failed (segment)\n"); - return -1; - } - vm_guard.sz = sz1; - - if (loadseg(vm_guard.pagetable, ph.vaddr, raw_ip, ph.off, ph.filesz) < 0) - return -1; - } - - // Prepare user stack - uint64 sz = PGROUNDUP(vm_guard.sz); - - const int stack_data_pages = 16; - const int stack_total_pages = stack_data_pages + 1; - - uint64 sz1 = VM::uvmalloc(vm_guard.pagetable, sz, sz + stack_total_pages * PGSIZE, PTE_W | PTE_R | PTE_U); - if (sz1 == 0) - { - Drivers::uart_puts("[Exec Debug] uvmalloc failed (stack)\n"); - return -1; - } - vm_guard.sz = sz1; - - VM::uvmunmap(vm_guard.pagetable, sz, 1, 0); - - uint64 sp = sz1; - uint64 stackbase = sp - stack_data_pages * PGSIZE; - uint64 ustack[32]; - uint64 argc = 0; - - // Push parameter string - for (; argv[argc]; argc++) - { - if (argc >= 31) - return -1; - sp -= strlen(argv[argc]) + 1; - sp -= sp % 16; - if (sp < stackbase) - { - Drivers::uart_puts("[Exec Debug] stack overflow (args)\n"); - return -1; - } - - if (VM::copyout(vm_guard.pagetable, sp, argv[argc], strlen(argv[argc]) + 1) < 0) - { - Drivers::uart_puts("[Exec Debug] copyout arg string failed\n"); - return -1; - } - ustack[argc] = sp; - } - ustack[argc] = 0; - - // Push into parameter array - sp -= (argc + 1) * sizeof(uint64); - sp -= sp % 16; - if (sp < stackbase) - { - Drivers::uart_puts("[Exec Debug] stack overflow (argv array)\n"); - return -1; - } - if (VM::copyout(vm_guard.pagetable, sp, (char *)ustack, (argc + 1) * sizeof(uint64)) < 0) - { - Drivers::uart_puts("[Exec Debug] copyout argv array failed\n"); - return -1; - } - - // Commit Changes - struct Proc *p = myproc(); - if (VM::mappages(vm_guard.pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0) - { - Drivers::uart_puts("[Exec Debug] mappages TRAPFRAME failed\n"); - return -1; - } - - uint64 oldsz = p->sz; - uint64 *oldpagetable = p->pagetable; - - p->tf->a0 = argc; - p->tf->a1 = sp; - p->tf->sp = sp; - p->tf->epc = elf.entry; - - char *s = path, *last = path; - while (*s) - { - if (*s == '/') - last = s + 1; - s++; - } - - size_t len = 0; - while (last[len] && len < sizeof(p->name) - 1) - { - p->name[len] = last[len]; - len++; - } - p->name[len] = '\0'; - - // change pagetable - p->pagetable = vm_guard.commit(); - p->sz = vm_guard.sz; - - // release old pagetable - VM::uvmfree(oldpagetable, oldsz); - asm volatile("fence.i"); - - return 0; // iguard unlocks and releases IP upon destruction - } -} \ No newline at end of file diff --git a/src/kernel/proc/proc.cc b/src/kernel/proc/proc.cc deleted file mode 100644 index 3923fe8..0000000 --- a/src/kernel/proc/proc.cc +++ /dev/null @@ -1,654 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2026 Richard QIn - */ -#include "kernel/proc.h" -#include "kernel/proc_def.h" -#include "kernel/runqueue.h" -#include "kernel/cpu.h" -#include "kernel/spinlock.h" -#include "kernel/pmm.h" -#include "kernel/mm.h" -#include "kernel/trap.h" -#include "kernel/riscv.h" -#include "kernel/slab.h" -#include "drivers/uart.h" -#include "fs/fs.h" -#include "fs/file.h" -#include "lib/string.h" - -// #define NPROC 64 -#define NCPU 8 - -extern "C" void swtch(struct Context *old_ctx, struct Context *new_ctx); -extern char uservec[], trampoline[]; -extern "C" void usertrap(); -extern "C" void userret(uint64, uint64); -extern char initcode_start[], initcode_end[]; - -// struct Proc procs[NPROC]; -struct Proc *proc_list_head = nullptr; -struct Proc *initproc = nullptr; -struct Spinlock proc_mem_lock; -struct cpu cpus[NCPU]; -struct RunQueue runqueues[NCPU]; - - -static int nextpid = 1; - - -struct cpu *mycpu() -{ - int id = r_tp(); - return &cpus[id]; -} - - -void RunQueue::init(const char *name) -{ - lock.init(name); - count = 0; - for (int i = 0; i < N_PRIO; i++) - { - heads[i] = nullptr; - tails[i] = nullptr; - } -} - -void RunQueue::enqueue(struct Proc *p) -{ - if (p->priority < 0 || p->priority >= N_PRIO) - p->priority = PRIO_MIN; - p->next = nullptr; - if (heads[p->priority] == nullptr) - { - heads[p->priority] = p; - tails[p->priority] = p; - } - else - { - tails[p->priority]->next = p; - tails[p->priority] = p; - } - count++; -} - -struct Proc *RunQueue::dequeue() -{ - for (int i = 0; i < N_PRIO; i++) - { - if (heads[i]) - { - struct Proc *p = heads[i]; - heads[i] = p->next; - if (!heads[i]) - tails[i] = nullptr; - p->next = nullptr; - count--; - return p; - } - } - return nullptr; -} - -struct Proc *RunQueue::steal() -{ - for (int i = N_PRIO - 1; i >= 0; i--) - { - if (heads[i]) - { - struct Proc *p = heads[i]; - heads[i] = p->next; - if (!heads[i]) - tails[i] = nullptr; - p->next = nullptr; - count--; - return p; - } - } - return nullptr; -} - -// Trap Return Functions - -void usertrapret() -{ - struct Proc *p = myproc(); - - intr_off(); - - // Set stvec to point to uservec in the trampoline - // When an interrupt occurs in user mode, the CPU will jump here - uint64 trampoline_uservec = TRAMPOLINE + (uservec - trampoline); - w_stvec(trampoline_uservec); - - // Fill in the Trapframe, used when uservec saves the kernel state - p->tf->kernel_satp = r_satp(); - p->tf->kernel_sp = (uint64)p->kstack + PGSIZE; - p->tf->kernel_trap = (uint64)usertrap; - p->tf->kernel_hartid = r_tp(); - - // Set SSTATUS - // SPP = 0 (return to User Mode) - // SPIE = 1 (enable interrupts) - unsigned long x = r_sstatus(); - x &= ~SSTATUS_SPP; - x |= SSTATUS_SPIE; - w_sstatus(x); - - // Set SEPC to the user program counter - w_sepc(p->tf->epc); - - // Prepare user page table SATP - uint64 satp = MAKE_SATP(p->pagetable); - - // Calculate the virtual address of userret in the trampoline - uint64 fn = TRAMPOLINE + ((uint64)userret - (uint64)trampoline); - - // Jump to userret, with parameters being the TRAPFRAME address and the user page table SATP - ((void (*)(uint64, uint64))fn)(TRAPFRAME, satp); -} - - -void forkret() -{ - - struct cpu *c = mycpu(); - c->runqueue->lock.release(); - - // Debug - // Drivers::uart_puts("[Proc] forkret: jumping to user.\n"); - - // Enter user mode - usertrapret(); -} - -// ProcManager Implementation - -static Proc *allocproc() -{ - struct Proc *p; - proc_mem_lock.acquire(); - - p = (struct Proc *)Slab::kmalloc(sizeof(struct Proc)); - - if (!p) - { - proc_mem_lock.release(); - return 0; - } - - memset(p, 0, sizeof(struct Proc)); - - p->all_next = proc_list_head; - p->all_prev = nullptr; - if (proc_list_head) - proc_list_head->all_prev = p; - proc_list_head = p; - - - p->state = USED; - p->pid = nextpid++; - p->priority = 0; - p->killed = 0; - p->cpu = mycpu(); - p->cwd = nullptr; - for (int i = 0; i < NOFILE; i++) - p->ofile[i] = nullptr; - - if ((p->tf = (struct Trapframe *)PMM::alloc_page()) == 0) - { - p->state = UNUSED; - if (p->all_next) - p->all_next->all_prev = p->all_prev; - if (p->all_prev) - p->all_prev->all_next = p->all_next; - if (p == proc_list_head) - proc_list_head = p->all_next; - Slab::kfree(p); - proc_mem_lock.release(); - return 0; - } - - if ((p->kstack = PMM::alloc_page()) == 0) - { - PMM::free_page(p->tf); - p->state = UNUSED; - if (p->all_next) - p->all_next->all_prev = p->all_prev; - if (p->all_prev) - p->all_prev->all_next = p->all_next; - if (p == proc_list_head) - proc_list_head = p->all_next; - Slab::kfree(p); - - proc_mem_lock.release(); - return 0; - } - memset(p->kstack, 0, PGSIZE); - - memset(&p->context, 0, sizeof(p->context)); - - // Set the context so that the next time this process is scheduled, it starts executing directly from forkret - p->context.ra = (uint64)forkret; - p->context.sp = (uint64)p->kstack + PGSIZE; - return p; -} - -static void freeproc(struct Proc *p) -{ - if (p->tf) - PMM::free_page(p->tf); - p->tf = 0; - if (p->kstack) - PMM::free_page(p->kstack); - p->kstack = 0; - if (p->pagetable) - VM::uvmfree(p->pagetable, p->sz); - for (int i = 0; i < NOFILE; i++) - { - if (p->ofile[i]) - { - FileTable::close(p->ofile[i]); - p->ofile[i] = nullptr; - } - } - if (p->cwd) - { - VFS::iput(p->cwd); - p->cwd = nullptr; - } - - if (p->all_prev) - p->all_prev->all_next = p->all_next; - else - proc_list_head = p->all_next; - - if (p->all_next) - p->all_next->all_prev = p->all_prev; - - Slab::kfree(p); -} - -int fork() -{ - struct Proc *np; - struct Proc *p = myproc(); - - if ((np = allocproc()) == 0) - return -1; - - np->pagetable = VM::uvmcreate(); - np->sz = p->sz; - if (VM::uvmcopy(p->pagetable, np->pagetable, p->sz) < 0) - { - freeproc(np); - proc_mem_lock.release(); - return -1; - } - - - if (VM::mappages(np->pagetable, TRAPFRAME, PGSIZE, (uint64)np->tf, PTE_R | PTE_W) < 0) - { - freeproc(np); - proc_mem_lock.release(); - return -1; - } - - *(np->tf) = *(p->tf); - np->tf->a0 = 0; - strcpy(np->name, p->name); - np->parent = p; - np->priority = p->priority; - - for (int i = 0; i < NOFILE; i++) - { - if (p->ofile[i]) - { - np->ofile[i] = FileTable::dup(p->ofile[i]); - } - } - - if (p->cwd) - { - np->cwd = VFS::idup(p->cwd); - } - - proc_mem_lock.release(); - - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - np->state = RUNNABLE; - c->runqueue->enqueue(np); - c->runqueue->lock.release(); - - return np->pid; -} - -namespace ProcManager -{ - - void init() - { - proc_mem_lock.init("proc_mem"); - - proc_list_head = nullptr; - - for (int i = 0; i < NCPU; i++) - { - cpus[i].runqueue = &runqueues[i]; - cpus[i].runqueue->init("runqueue"); - } - } - - void scheduler() - { - struct Proc *p; - struct cpu *c = mycpu(); - c->proc = 0; - Drivers::uart_puts("[Proc] Scheduler started (Per-CPU).\n"); - - for (;;) - { - intr_on(); - - // Get from the local queue - c->runqueue->lock.acquire(); - p = c->runqueue->dequeue(); - c->runqueue->lock.release(); - - // Work Stealing - if (!p) - { - for (int i = 0; i < NCPU; i++) - { - if ((uint64)i == r_tp()) - continue; - - struct cpu *victim = &cpus[i]; - victim->runqueue->lock.acquire(); - if (victim->runqueue->count > 0) - p = victim->runqueue->steal(); - victim->runqueue->lock.release(); - if (p) - break; - } - } - - if (p) - { - p->state = RUNNING; - c->proc = p; - - c->runqueue->lock.acquire(); - swtch(&c->context, &p->context); - c->runqueue->lock.release(); - - c->proc = 0; - } - } - } - - void yield() - { - struct Proc *p = myproc(); - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - if (p && p->state == RUNNING) - { - p->state = RUNNABLE; - if (p->priority < PRIO_MIN) - p->priority++; - c->runqueue->enqueue(p); - swtch(&p->context, &c->context); - } - c->runqueue->lock.release(); - } - - void user_init() - { - struct Proc *p = allocproc(); - if (!p) - return; - - initproc = p; - - p->pagetable = VM::uvmcreate(); - - uint64 initcode_sz = initcode_end - initcode_start; - Drivers::uart_puts("[Proc] initcode size: "); - Drivers::uart_put_int(initcode_sz); - Drivers::uart_puts(" bytes\n"); - - uint64 code_pages = PGROUNDUP(initcode_sz) / PGSIZE; - - for (uint64 i = 0; i < code_pages; i++) - { - void *mem = PMM::alloc_page(); - if (!mem) - { - Drivers::uart_puts("user_init: out of memory for code\n"); - return; - } - memset(mem, 0, PGSIZE); - - uint64 src_offset = i * PGSIZE; - uint64 copy_len = PGSIZE; - if (src_offset + copy_len > initcode_sz) - { - copy_len = initcode_sz - src_offset; - } - memmove(mem, (void *)(initcode_start + src_offset), copy_len); - - if (VM::mappages(p->pagetable, i * PGSIZE, PGSIZE, (uint64)mem, PTE_R | PTE_W | PTE_X | PTE_U) < 0) - { - Drivers::uart_puts("user_init: mappages code failed\n"); - return; - } - } - - uint64 stack_va = (code_pages + 1) * PGSIZE; - - void *stack = PMM::alloc_page(); - if (!stack) - return; - memset(stack, 0, PGSIZE); - - if (VM::mappages(p->pagetable, stack_va, PGSIZE, (uint64)stack, PTE_R | PTE_W | PTE_U) < 0) - return; - - if (VM::mappages(p->pagetable, TRAPFRAME, PGSIZE, (uint64)p->tf, PTE_R | PTE_W) < 0) - return; - - p->tf->epc = 0; - p->tf->sp = stack_va + PGSIZE; - p->sz = stack_va + PGSIZE; - strcpy(p->name, "initcode"); - - p->cwd = nullptr; - - proc_mem_lock.release(); - - struct cpu *c = &cpus[0]; - c->runqueue->lock.acquire(); - p->state = RUNNABLE; - c->runqueue->enqueue(p); - c->runqueue->lock.release(); - - Drivers::uart_puts("[Proc] user_init success. PID: 1\n"); - asm volatile("fence.i"); - } - - void sleep(void *chan, Spinlock *lk) - { - struct Proc *p = myproc(); - struct cpu *c = mycpu(); - - if (lk && lk != &proc_mem_lock) - lk->release(); - - c->runqueue->lock.acquire(); - p->chan = chan; - p->state = SLEEPING; - swtch(&p->context, &c->context); - p->chan = 0; - c->runqueue->lock.release(); - - if (lk && lk != &proc_mem_lock) - lk->acquire(); - } - - void wakeup(void *chan) - { - for (struct Proc *p = proc_list_head; p != nullptr; p = p->all_next) - { - if (p->state == SLEEPING && p->chan == chan) - { - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - p->state = RUNNABLE; - c->runqueue->enqueue(p); - c->runqueue->lock.release(); - } - } - } - - void exit(int status) - { - struct Proc *p = myproc(); - if (p == initproc) - while (1) - ; - - wakeup(p->parent); - - proc_mem_lock.acquire(); - - for (struct Proc *pp = proc_list_head; pp != nullptr; pp = pp->all_next) - { - if (pp->parent == p) - { - pp->parent = initproc; - wakeup(initproc); - } - } - - proc_mem_lock.release(); - - p->xstate = status; - - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - p->state = ZOMBIE; - swtch(&p->context, &c->context); - while (1) - ; - } - - int wait(uint64 addr) - { - - struct Proc *np; - int havekids, pid; - struct Proc *p = myproc(); - for (;;) - { - havekids = 0; - proc_mem_lock.acquire(); - - for (np = proc_list_head; np != nullptr; np = np->all_next) - { - if (np->parent == p) - { - havekids = 1; - if (np->state == ZOMBIE) - { - - struct cpu *child_cpu = np->cpu; - if (child_cpu) - { - proc_mem_lock.release(); - child_cpu->runqueue->lock.acquire(); - child_cpu->runqueue->lock.release(); - proc_mem_lock.acquire(); - } - - pid = np->pid; - if (addr != 0 && VM::copyout(p->pagetable, addr, (char *)&np->xstate, sizeof(np->xstate)) < 0) - { - proc_mem_lock.release(); - return -1; - } - freeproc(np); - proc_mem_lock.release(); - return pid; - } - } - } - proc_mem_lock.release(); - - if (!havekids || p->state == ZOMBIE) - return -1; - sleep(p, &proc_mem_lock); - } - } - - int kill(int pid) - { - Proc* p; - proc_mem_lock.acquire(); - for (p = proc_list_head; p != nullptr; p = p->all_next) - { - if (p->pid == pid) - { - p->killed = 1; - - if (p->state == SLEEPING) - { - p->state = RUNNABLE; - - struct cpu *c = mycpu(); - c->runqueue->lock.acquire(); - c->runqueue->enqueue(p); - c->runqueue->lock.release(); - } - - proc_mem_lock.release(); - return 0; - } - } - proc_mem_lock.release(); - return -1; - } - - int growproc(int n) - { - uint64 sz; - struct Proc *p = myproc(); - - sz = p->sz; - - if (n > 0) - { - // Call VM::uvmalloc to allocate memory - // Parameters: page table, old size, new size, permissions (PTE_W) - if ((sz = VM::uvmalloc(p->pagetable, sz, sz + n, PTE_W | PTE_R | PTE_U)) == 0) - { - return -1; - } - } - else if (n < 0) - { - sz = VM::uvmdealloc(p->pagetable, sz, sz + n); - } - - p->sz = sz; - return 0; - } - - void create_kernel_thread(void (*func)(), const char *name) - { - (void)func; - (void)name; - } -} \ No newline at end of file diff --git a/src/kernel/proc/swtch.S b/src/kernel/proc/swtch.S deleted file mode 100644 index d55c03e..0000000 --- a/src/kernel/proc/swtch.S +++ /dev/null @@ -1,37 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-or-later -# Copyright (C) 2026 Richard Qin - -.global swtch - -swtch: - sd ra, 0(a0) - sd sp, 8(a0) - sd s0, 16(a0) - sd s1, 24(a0) - sd s2, 32(a0) - sd s3, 40(a0) - sd s4, 48(a0) - sd s5, 56(a0) - sd s6, 64(a0) - sd s7, 72(a0) - sd s8, 80(a0) - sd s9, 88(a0) - sd s10, 96(a0) - sd s11, 104(a0) - - ld ra, 0(a1) - ld sp, 8(a1) - ld s0, 16(a1) - ld s1, 24(a1) - ld s2, 32(a1) - ld s3, 40(a1) - ld s4, 48(a1) - ld s5, 56(a1) - ld s6, 64(a1) - ld s7, 72(a1) - ld s8, 80(a1) - ld s9, 88(a1) - ld s10, 96(a1) - ld s11, 104(a1) - - ret -- cgit v1.2.3