blob: 4a51fb3a86349a85a976d4bce8c439f0c510366c [file] [log] [blame]
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// System calls and other sys.stuff for ARM64, Darwin
// System calls are implemented in libSystem, this file contains
// trampolines that convert from Go to C calling convention.
#include "go_asm.h"
#include "go_tls.h"
#include "textflag.h"
#include "cgo/abi_arm64.h"
#define CLOCK_REALTIME 0
TEXT notok<>(SB),NOSPLIT,$0
MOVD $0, R8
MOVD R8, (R8)
B 0(PC)
TEXT runtime·open_trampoline(SB),NOSPLIT,$0
SUB $16, RSP
MOVW 8(R0), R1 // arg 2 flags
MOVW 12(R0), R2 // arg 3 mode
MOVW R2, (RSP) // arg 3 is variadic, pass on stack
MOVD 0(R0), R0 // arg 1 pathname
BL libc_open(SB)
ADD $16, RSP
RET
TEXT runtime·close_trampoline(SB),NOSPLIT,$0
MOVW 0(R0), R0 // arg 1 fd
BL libc_close(SB)
RET
TEXT runtime·write_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 buf
MOVW 16(R0), R2 // arg 3 count
MOVW 0(R0), R0 // arg 1 fd
BL libc_write(SB)
MOVD $-1, R1
CMP R0, R1
BNE noerr
BL libc_error(SB)
MOVW (R0), R0
NEG R0, R0 // caller expects negative errno value
noerr:
RET
TEXT runtime·read_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 buf
MOVW 16(R0), R2 // arg 3 count
MOVW 0(R0), R0 // arg 1 fd
BL libc_read(SB)
MOVD $-1, R1
CMP R0, R1
BNE noerr
BL libc_error(SB)
MOVW (R0), R0
NEG R0, R0 // caller expects negative errno value
noerr:
RET
TEXT runtime·pipe_trampoline(SB),NOSPLIT,$0
BL libc_pipe(SB) // pointer already in R0
CMP $0, R0
BEQ 3(PC)
BL libc_error(SB) // return negative errno value
NEG R0, R0
RET
TEXT runtime·exit_trampoline(SB),NOSPLIT|NOFRAME,$0
MOVW 0(R0), R0
BL libc_exit(SB)
MOVD $1234, R0
MOVD $1002, R1
MOVD R0, (R1) // fail hard
TEXT runtime·raiseproc_trampoline(SB),NOSPLIT,$0
MOVD 0(R0), R19 // signal
BL libc_getpid(SB)
// arg 1 pid already in R0 from getpid
MOVD R19, R1 // arg 2 signal
BL libc_kill(SB)
RET
TEXT runtime·mmap_trampoline(SB),NOSPLIT,$0
MOVD R0, R19
MOVD 0(R19), R0 // arg 1 addr
MOVD 8(R19), R1 // arg 2 len
MOVW 16(R19), R2 // arg 3 prot
MOVW 20(R19), R3 // arg 4 flags
MOVW 24(R19), R4 // arg 5 fd
MOVW 28(R19), R5 // arg 6 off
BL libc_mmap(SB)
MOVD $0, R1
MOVD $-1, R2
CMP R0, R2
BNE ok
BL libc_error(SB)
MOVW (R0), R1
MOVD $0, R0
ok:
MOVD R0, 32(R19) // ret 1 p
MOVD R1, 40(R19) // ret 2 err
RET
TEXT runtime·munmap_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 len
MOVD 0(R0), R0 // arg 1 addr
BL libc_munmap(SB)
CMP $0, R0
BEQ 2(PC)
BL notok<>(SB)
RET
TEXT runtime·madvise_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 len
MOVW 16(R0), R2 // arg 3 advice
MOVD 0(R0), R0 // arg 1 addr
BL libc_madvise(SB)
RET
TEXT runtime·mlock_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 len
MOVD 0(R0), R0 // arg 1 addr
BL libc_mlock(SB)
RET
TEXT runtime·setitimer_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 new
MOVD 16(R0), R2 // arg 3 old
MOVW 0(R0), R0 // arg 1 which
BL libc_setitimer(SB)
RET
TEXT runtime·walltime_trampoline(SB),NOSPLIT,$0
MOVD R0, R1 // arg 2 timespec
MOVW $CLOCK_REALTIME, R0 // arg 1 clock_id
BL libc_clock_gettime(SB)
RET
GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size)
TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40
MOVD R0, R19
BL libc_mach_absolute_time(SB)
MOVD R0, 0(R19)
MOVW timebase<>+machTimebaseInfo_numer(SB), R20
MOVD $timebase<>+machTimebaseInfo_denom(SB), R21
LDARW (R21), R21 // atomic read
CMP $0, R21
BNE initialized
SUB $(machTimebaseInfo__size+15)/16*16, RSP
MOVD RSP, R0
BL libc_mach_timebase_info(SB)
MOVW machTimebaseInfo_numer(RSP), R20
MOVW machTimebaseInfo_denom(RSP), R21
ADD $(machTimebaseInfo__size+15)/16*16, RSP
MOVW R20, timebase<>+machTimebaseInfo_numer(SB)
MOVD $timebase<>+machTimebaseInfo_denom(SB), R22
STLRW R21, (R22) // atomic write
initialized:
MOVW R20, 8(R19)
MOVW R21, 12(R19)
RET
TEXT runtime·sigfwd(SB),NOSPLIT,$0-32
MOVW sig+8(FP), R0
MOVD info+16(FP), R1
MOVD ctx+24(FP), R2
MOVD fn+0(FP), R11
BL (R11)
RET
TEXT runtime·sigtramp(SB),NOSPLIT|TOPFRAME,$176
// Save callee-save registers in the case of signal forwarding.
// Please refer to https://golang.org/issue/31827 .
SAVE_R19_TO_R28(8*4)
SAVE_F8_TO_F15(8*14)
// Save arguments.
MOVW R0, (8*1)(RSP) // sig
MOVD R1, (8*2)(RSP) // info
MOVD R2, (8*3)(RSP) // ctx
// this might be called in external code context,
// where g is not set.
BL runtime·load_g(SB)
#ifdef GOOS_ios
MOVD RSP, R6
CMP $0, g
BEQ nog
// iOS always use the main stack to run the signal handler.
// We need to switch to gsignal ourselves.
MOVD g_m(g), R11
MOVD m_gsignal(R11), R5
MOVD (g_stack+stack_hi)(R5), R6
nog:
// Restore arguments.
MOVW (8*1)(RSP), R0
MOVD (8*2)(RSP), R1
MOVD (8*3)(RSP), R2
// Reserve space for args and the stack pointer on the
// gsignal stack.
SUB $48, R6
// Save stack pointer.
MOVD RSP, R4
MOVD R4, (8*4)(R6)
// Switch to gsignal stack.
MOVD R6, RSP
// Save arguments.
MOVW R0, (8*1)(RSP)
MOVD R1, (8*2)(RSP)
MOVD R2, (8*3)(RSP)
#endif
// Call sigtrampgo.
MOVD $runtime·sigtrampgo(SB), R11
BL (R11)
#ifdef GOOS_ios
// Switch to old stack.
MOVD (8*4)(RSP), R5
MOVD R5, RSP
#endif
// Restore callee-save registers.
RESTORE_R19_TO_R28(8*4)
RESTORE_F8_TO_F15(8*14)
RET
TEXT runtime·cgoSigtramp(SB),NOSPLIT,$0
JMP runtime·sigtramp(SB)
TEXT runtime·sigprocmask_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 new
MOVD 16(R0), R2 // arg 3 old
MOVW 0(R0), R0 // arg 1 how
BL libc_pthread_sigmask(SB)
CMP $0, R0
BEQ 2(PC)
BL notok<>(SB)
RET
TEXT runtime·sigaction_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 new
MOVD 16(R0), R2 // arg 3 old
MOVW 0(R0), R0 // arg 1 how
BL libc_sigaction(SB)
CMP $0, R0
BEQ 2(PC)
BL notok<>(SB)
RET
TEXT runtime·usleep_trampoline(SB),NOSPLIT,$0
MOVW 0(R0), R0 // arg 1 usec
BL libc_usleep(SB)
RET
TEXT runtime·sysctl_trampoline(SB),NOSPLIT,$0
MOVW 8(R0), R1 // arg 2 miblen
MOVD 16(R0), R2 // arg 3 oldp
MOVD 24(R0), R3 // arg 4 oldlenp
MOVD 32(R0), R4 // arg 5 newp
MOVD 40(R0), R5 // arg 6 newlen
MOVD 0(R0), R0 // arg 1 mib
BL libc_sysctl(SB)
RET
TEXT runtime·sysctlbyname_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 oldp
MOVD 16(R0), R2 // arg 3 oldlenp
MOVD 24(R0), R3 // arg 4 newp
MOVD 32(R0), R4 // arg 5 newlen
MOVD 0(R0), R0 // arg 1 name
BL libc_sysctlbyname(SB)
RET
TEXT runtime·kqueue_trampoline(SB),NOSPLIT,$0
BL libc_kqueue(SB)
RET
TEXT runtime·kevent_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 keventt
MOVW 16(R0), R2 // arg 3 nch
MOVD 24(R0), R3 // arg 4 ev
MOVW 32(R0), R4 // arg 5 nev
MOVD 40(R0), R5 // arg 6 ts
MOVW 0(R0), R0 // arg 1 kq
BL libc_kevent(SB)
MOVD $-1, R2
CMP R0, R2
BNE ok
BL libc_error(SB)
MOVW (R0), R0 // errno
NEG R0, R0 // caller wants it as a negative error code
ok:
RET
TEXT runtime·fcntl_trampoline(SB),NOSPLIT,$0
SUB $16, RSP
MOVW 4(R0), R1 // arg 2 cmd
MOVW 8(R0), R2 // arg 3 arg
MOVW R2, (RSP) // arg 3 is variadic, pass on stack
MOVW 0(R0), R0 // arg 1 fd
BL libc_fcntl(SB)
ADD $16, RSP
RET
TEXT runtime·sigaltstack_trampoline(SB),NOSPLIT,$0
#ifdef GOOS_ios
// sigaltstack on iOS is not supported and will always
// run the signal handler on the main stack, so our sigtramp has
// to do the stack switch ourselves.
MOVW $43, R0
BL libc_exit(SB)
#else
MOVD 8(R0), R1 // arg 2 old
MOVD 0(R0), R0 // arg 1 new
CALL libc_sigaltstack(SB)
CBZ R0, 2(PC)
BL notok<>(SB)
#endif
RET
// Thread related functions
// mstart_stub is the first function executed on a new thread started by pthread_create.
// It just does some low-level setup and then calls mstart.
// Note: called with the C calling convention.
TEXT runtime·mstart_stub(SB),NOSPLIT,$160
// R0 points to the m.
// We are already on m's g0 stack.
// Save callee-save registers.
SAVE_R19_TO_R28(8)
SAVE_F8_TO_F15(88)
MOVD m_g0(R0), g
BL ·save_g(SB)
BL runtime·mstart(SB)
// Restore callee-save registers.
RESTORE_R19_TO_R28(8)
RESTORE_F8_TO_F15(88)
// Go is all done with this OS thread.
// Tell pthread everything is ok (we never join with this thread, so
// the value here doesn't really matter).
MOVD $0, R0
RET
TEXT runtime·pthread_attr_init_trampoline(SB),NOSPLIT,$0
MOVD 0(R0), R0 // arg 1 attr
BL libc_pthread_attr_init(SB)
RET
TEXT runtime·pthread_attr_getstacksize_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 size
MOVD 0(R0), R0 // arg 1 attr
BL libc_pthread_attr_getstacksize(SB)
RET
TEXT runtime·pthread_attr_setdetachstate_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 state
MOVD 0(R0), R0 // arg 1 attr
BL libc_pthread_attr_setdetachstate(SB)
RET
TEXT runtime·pthread_create_trampoline(SB),NOSPLIT,$0
SUB $16, RSP
MOVD 0(R0), R1 // arg 2 state
MOVD 8(R0), R2 // arg 3 start
MOVD 16(R0), R3 // arg 4 arg
MOVD RSP, R0 // arg 1 &threadid (which we throw away)
BL libc_pthread_create(SB)
ADD $16, RSP
RET
TEXT runtime·raise_trampoline(SB),NOSPLIT,$0
MOVW 0(R0), R0 // arg 1 sig
BL libc_raise(SB)
RET
TEXT runtime·pthread_mutex_init_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 attr
MOVD 0(R0), R0 // arg 1 mutex
BL libc_pthread_mutex_init(SB)
RET
TEXT runtime·pthread_mutex_lock_trampoline(SB),NOSPLIT,$0
MOVD 0(R0), R0 // arg 1 mutex
BL libc_pthread_mutex_lock(SB)
RET
TEXT runtime·pthread_mutex_unlock_trampoline(SB),NOSPLIT,$0
MOVD 0(R0), R0 // arg 1 mutex
BL libc_pthread_mutex_unlock(SB)
RET
TEXT runtime·pthread_cond_init_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 attr
MOVD 0(R0), R0 // arg 1 cond
BL libc_pthread_cond_init(SB)
RET
TEXT runtime·pthread_cond_wait_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 mutex
MOVD 0(R0), R0 // arg 1 cond
BL libc_pthread_cond_wait(SB)
RET
TEXT runtime·pthread_cond_timedwait_relative_np_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 mutex
MOVD 16(R0), R2 // arg 3 timeout
MOVD 0(R0), R0 // arg 1 cond
BL libc_pthread_cond_timedwait_relative_np(SB)
RET
TEXT runtime·pthread_cond_signal_trampoline(SB),NOSPLIT,$0
MOVD 0(R0), R0 // arg 1 cond
BL libc_pthread_cond_signal(SB)
RET
TEXT runtime·pthread_self_trampoline(SB),NOSPLIT,$0
MOVD R0, R19 // R19 is callee-save
BL libc_pthread_self(SB)
MOVD R0, 0(R19) // return value
RET
TEXT runtime·pthread_kill_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 sig
MOVD 0(R0), R0 // arg 1 thread
BL libc_pthread_kill(SB)
RET
TEXT runtime·pthread_key_create_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 destructor
MOVD 0(R0), R0 // arg 1 *key
BL libc_pthread_key_create(SB)
RET
TEXT runtime·pthread_setspecific_trampoline(SB),NOSPLIT,$0
MOVD 8(R0), R1 // arg 2 value
MOVD 0(R0), R0 // arg 1 key
BL libc_pthread_setspecific(SB)
RET
TEXT runtime·osinit_hack_trampoline(SB),NOSPLIT,$0
MOVD $0, R0 // arg 1 val
BL libc_notify_is_valid_token(SB)
BL libc_xpc_date_create_from_current(SB)
RET
// syscall calls a function in libc on behalf of the syscall package.
// syscall takes a pointer to a struct like:
// struct {
// fn uintptr
// a1 uintptr
// a2 uintptr
// a3 uintptr
// r1 uintptr
// r2 uintptr
// err uintptr
// }
// syscall must be called on the g0 stack with the
// C calling convention (use libcCall).
TEXT runtime·syscall(SB),NOSPLIT,$0
SUB $16, RSP // push structure pointer
MOVD R0, 8(RSP)
MOVD 0(R0), R12 // fn
MOVD 16(R0), R1 // a2
MOVD 24(R0), R2 // a3
MOVD 8(R0), R0 // a1
// If fn is declared as vararg, we have to pass the vararg arguments on the stack.
// (Because ios decided not to adhere to the standard arm64 calling convention, sigh...)
// The only libSystem calls we support that are vararg are open, fcntl, and ioctl,
// which are all of the form fn(x, y, ...). So we just need to put the 3rd arg
// on the stack as well.
// If we ever have other vararg libSystem calls, we might need to handle more cases.
MOVD R2, (RSP)
BL (R12)
MOVD 8(RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 32(R2) // save r1
MOVD R1, 40(R2) // save r2
CMPW $-1, R0
BNE ok
SUB $16, RSP // push structure pointer
MOVD R2, 8(RSP)
BL libc_error(SB)
MOVW (R0), R0
MOVD 8(RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 48(R2) // save err
ok:
RET
// syscallX calls a function in libc on behalf of the syscall package.
// syscallX takes a pointer to a struct like:
// struct {
// fn uintptr
// a1 uintptr
// a2 uintptr
// a3 uintptr
// r1 uintptr
// r2 uintptr
// err uintptr
// }
// syscallX must be called on the g0 stack with the
// C calling convention (use libcCall).
TEXT runtime·syscallX(SB),NOSPLIT,$0
SUB $16, RSP // push structure pointer
MOVD R0, (RSP)
MOVD 0(R0), R12 // fn
MOVD 16(R0), R1 // a2
MOVD 24(R0), R2 // a3
MOVD 8(R0), R0 // a1
BL (R12)
MOVD (RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 32(R2) // save r1
MOVD R1, 40(R2) // save r2
CMP $-1, R0
BNE ok
SUB $16, RSP // push structure pointer
MOVD R2, (RSP)
BL libc_error(SB)
MOVW (R0), R0
MOVD (RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 48(R2) // save err
ok:
RET
// syscallPtr is like syscallX except that the libc function reports an
// error by returning NULL and setting errno.
TEXT runtime·syscallPtr(SB),NOSPLIT,$0
SUB $16, RSP // push structure pointer
MOVD R0, (RSP)
MOVD 0(R0), R12 // fn
MOVD 16(R0), R1 // a2
MOVD 24(R0), R2 // a3
MOVD 8(R0), R0 // a1
BL (R12)
MOVD (RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 32(R2) // save r1
MOVD R1, 40(R2) // save r2
CMP $0, R0
BNE ok
SUB $16, RSP // push structure pointer
MOVD R2, (RSP)
BL libc_error(SB)
MOVW (R0), R0
MOVD (RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 48(R2) // save err
ok:
RET
// syscall6 calls a function in libc on behalf of the syscall package.
// syscall6 takes a pointer to a struct like:
// struct {
// fn uintptr
// a1 uintptr
// a2 uintptr
// a3 uintptr
// a4 uintptr
// a5 uintptr
// a6 uintptr
// r1 uintptr
// r2 uintptr
// err uintptr
// }
// syscall6 must be called on the g0 stack with the
// C calling convention (use libcCall).
TEXT runtime·syscall6(SB),NOSPLIT,$0
SUB $16, RSP // push structure pointer
MOVD R0, 8(RSP)
MOVD 0(R0), R12 // fn
MOVD 16(R0), R1 // a2
MOVD 24(R0), R2 // a3
MOVD 32(R0), R3 // a4
MOVD 40(R0), R4 // a5
MOVD 48(R0), R5 // a6
MOVD 8(R0), R0 // a1
// If fn is declared as vararg, we have to pass the vararg arguments on the stack.
// See syscall above. The only function this applies to is openat, for which the 4th
// arg must be on the stack.
MOVD R3, (RSP)
BL (R12)
MOVD 8(RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 56(R2) // save r1
MOVD R1, 64(R2) // save r2
CMPW $-1, R0
BNE ok
SUB $16, RSP // push structure pointer
MOVD R2, 8(RSP)
BL libc_error(SB)
MOVW (R0), R0
MOVD 8(RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 72(R2) // save err
ok:
RET
// syscall6X calls a function in libc on behalf of the syscall package.
// syscall6X takes a pointer to a struct like:
// struct {
// fn uintptr
// a1 uintptr
// a2 uintptr
// a3 uintptr
// a4 uintptr
// a5 uintptr
// a6 uintptr
// r1 uintptr
// r2 uintptr
// err uintptr
// }
// syscall6X must be called on the g0 stack with the
// C calling convention (use libcCall).
TEXT runtime·syscall6X(SB),NOSPLIT,$0
SUB $16, RSP // push structure pointer
MOVD R0, (RSP)
MOVD 0(R0), R12 // fn
MOVD 16(R0), R1 // a2
MOVD 24(R0), R2 // a3
MOVD 32(R0), R3 // a4
MOVD 40(R0), R4 // a5
MOVD 48(R0), R5 // a6
MOVD 8(R0), R0 // a1
BL (R12)
MOVD (RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 56(R2) // save r1
MOVD R1, 64(R2) // save r2
CMP $-1, R0
BNE ok
SUB $16, RSP // push structure pointer
MOVD R2, (RSP)
BL libc_error(SB)
MOVW (R0), R0
MOVD (RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 72(R2) // save err
ok:
RET
// syscall9 calls a function in libc on behalf of the syscall package.
// syscall9 takes a pointer to a struct like:
// struct {
// fn uintptr
// a1 uintptr
// a2 uintptr
// a3 uintptr
// a4 uintptr
// a5 uintptr
// a6 uintptr
// a7 uintptr
// a8 uintptr
// a9 uintptr
// r1 uintptr
// r2 uintptr
// err uintptr
// }
// syscall9 must be called on the g0 stack with the
// C calling convention (use libcCall).
TEXT runtime·syscall9(SB),NOSPLIT,$0
SUB $16, RSP // push structure pointer
MOVD R0, 8(RSP)
MOVD 0(R0), R12 // fn
MOVD 16(R0), R1 // a2
MOVD 24(R0), R2 // a3
MOVD 32(R0), R3 // a4
MOVD 40(R0), R4 // a5
MOVD 48(R0), R5 // a6
MOVD 56(R0), R6 // a7
MOVD 64(R0), R7 // a8
MOVD 72(R0), R8 // a9
MOVD 8(R0), R0 // a1
// If fn is declared as vararg, we have to pass the vararg arguments on the stack.
// See syscall above. The only function this applies to is openat, for which the 4th
// arg must be on the stack.
MOVD R3, (RSP)
BL (R12)
MOVD 8(RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 80(R2) // save r1
MOVD R1, 88(R2) // save r2
CMPW $-1, R0
BNE ok
SUB $16, RSP // push structure pointer
MOVD R2, 8(RSP)
BL libc_error(SB)
MOVW (R0), R0
MOVD 8(RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 96(R2) // save err
ok:
RET
// syscall_x509 is for crypto/x509. It is like syscall6 but does not check for errors,
// takes 5 uintptrs and 1 float64, and only returns one value,
// for use with standard C ABI functions.
TEXT runtime·syscall_x509(SB),NOSPLIT,$0
SUB $16, RSP // push structure pointer
MOVD R0, (RSP)
MOVD 0(R0), R12 // fn
MOVD 16(R0), R1 // a2
MOVD 24(R0), R2 // a3
MOVD 32(R0), R3 // a4
MOVD 40(R0), R4 // a5
FMOVD 48(R0), F0 // f1
MOVD 8(R0), R0 // a1
BL (R12)
MOVD (RSP), R2 // pop structure pointer
ADD $16, RSP
MOVD R0, 56(R2) // save r1
RET