diff options
| author | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:31:46 +0800 |
|---|---|---|
| committer | Richard Qin <richard.qin.x@gmail.com> | 2026-07-10 11:31:46 +0800 |
| commit | e90b72ec3082588b296956730bd2930cf8d5d671 (patch) | |
| tree | 36ff6916af9b4176ea5a9662219d11e721f0b691 /lume-legacy/src/kernel/proc | |
| parent | 1767d390fbf3d0c65389de46caf0b6d43ef3b6ad (diff) | |
| download | lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.gz lume-e90b72ec3082588b296956730bd2930cf8d5d671.tar.xz | |
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.
Diffstat (limited to 'lume-legacy/src/kernel/proc')
| -rw-r--r-- | lume-legacy/src/kernel/proc/exec.cc | 260 | ||||
| -rw-r--r-- | lume-legacy/src/kernel/proc/proc.cc | 654 | ||||
| -rw-r--r-- | lume-legacy/src/kernel/proc/swtch.S | 37 |
3 files changed, 951 insertions, 0 deletions
diff --git a/lume-legacy/src/kernel/proc/exec.cc b/lume-legacy/src/kernel/proc/exec.cc new file mode 100644 index 0000000..8d8c467 --- /dev/null +++ b/lume-legacy/src/kernel/proc/exec.cc @@ -0,0 +1,260 @@ +// 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/lume-legacy/src/kernel/proc/proc.cc b/lume-legacy/src/kernel/proc/proc.cc new file mode 100644 index 0000000..3923fe8 --- /dev/null +++ b/lume-legacy/src/kernel/proc/proc.cc @@ -0,0 +1,654 @@ +// 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/lume-legacy/src/kernel/proc/swtch.S b/lume-legacy/src/kernel/proc/swtch.S new file mode 100644 index 0000000..d55c03e --- /dev/null +++ b/lume-legacy/src/kernel/proc/swtch.S @@ -0,0 +1,37 @@ +# 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 |
