cosmopolitan/libc/runtime/cxaatexit.S

182 lines
5.6 KiB
ArmAsm
Raw Normal View History

/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│
2020-06-15 14:18:57 +00:00
vi: set et ft=asm ts=8 sw=8 fenc=utf-8 :vi
Copyright 2020 Justine Alexandra Roberts Tunney
This program is free software; you can redistribute it and/or modify │
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License. │
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of │
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software │
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA
*/
#include "ape/relocations.h"
#include "libc/macros.h"
.section .text.exit,"ax",@progbits
.source __FILE__
2020-06-15 14:18:57 +00:00
/ Delegates to __cxa_atexit().
/
/ @param rdi callback typed void(*)(void) (nullable)
/ @return 0 on success or nonzero if out of space
atexit: xor %esi,%esi
xor %edx,%edx
/ 𝑠𝑙𝑖𝑑𝑒
/ Registers destructor to be called upon exit().
/
/ Destructors are called in reverse order. They won't be called
/ if the program aborts or _exit() is called. Invocations of this
/ function are usually generated by the C++ compiler.
/
/ @param rdi callback typed void(*)(T) (nullable)
/ @param rsi callback arg typed T (nullable)
/ @param rdx dso handle (nullable)
/ @return 0 on success or nonzero if out of space
/ @note folks have forked libc in past just to unbloat atexit()
/ @see news.ycombinator.com/item?id=20228082
/ @preinitsafe
__cxa_atexit:
push %rbp
mov %rsp,%rbp
.profilable
push %r15
ezlea g_cxa,cx
mov %rcx,%r15
mov (%r15),%rax # get state->block
mov 8(%r15),%rcx # get state->offset
test %rcx,%rcx
jz 2f
0: sub $24,%rcx
mov %rdx,(%rax,%rcx) # set cb->dso
mov %rsi,8(%rax,%rcx) # set cb->arg
mov %rdi,16(%rax,%rcx) # set cb->fn
mov %rcx,8(%r15) # set state->offset
xor %eax,%eax # success
1: pop %r15
pop %rbp
ret
2: .weak calloc
ezlea calloc,cx
test %rcx,%rcx
jz 1b # fail (no malloc)
push %rax
push %rdi
push %rsi
pushpop ATEXIT_MAX+1,%rdi
pushpop 16,%rsi
call *%rcx
pop %rsi
pop %rdi
pop %rcx # rax=new rcx=old
test %rax,%rax
jz 1b # fail (no memory)
mov $ATEXIT_MAX*8*3,%r8
mov %rax,(%r15) # set state->block
mov %rcx,(%rax,%r8) # set block->next
mov %r8,%rcx
jmp 0b
.endfn __cxa_atexit,globl
.endfn atexit,globl
/ Triggers destructors.
/
/ This implementation supports DSO handles, but is optimized for
/ cases when it's called only once by exit().
/
/ @param rdi is dso predicate or null to destroy all
/ @see libc/exit.c
__cxa_finalize:
push %rbp
mov %rsp,%rbp
.profilable
push %r14
push %r13
push %r12
mov g_cxa(%rip),%rsi
mov %rdi,%r14
0: mov %rsi,%r12 # loop through blocks
pushpop ATEXIT_MAX,%rcx
1: lodsq # dso # loop through callbacks
xchg %rax,%rdx
lodsq # arg
xchg %rax,%rdi
lodsq # fn
test %rax,%rax
jz 2f # ignore empty slots
test %r14,%r14
jmp 5f # null predicate match all
cmp %r14,%rdx
jne 2f # predicate mismatch
5: push %rsi
push %rcx
push %rcx
call *%rax
pop %rcx
pop %rcx
pop %rsi
xor %eax,%eax # clear slot (never reused)
mov %rax,-8(%rsi)
2: loop 1b
lodsq # get next block ptr
test %rax,%rax # don't free static block no. 1
jz 3f # which always has next == NULL
test %r14,%r14
jz 1f # don't free anything if just one dso
push %rax
mov %r12,%rdi
.weak free
call free # can't panic due to earlier test
1: pop %rsi
jmp 0b
3: pop %r12 # align stack for next call
test %r14,%r14 # no static dtor for dso exit
jnz 9f
ezlea __fini_array_end,ax # static dtors in reverse order
.weak __fini_array_end # could be called multiple times
ezlea __fini_array_start,cx # idempotency recommended
.weak __fini_array_start # or consider atexit()
8: sub $8,%rax # @see ape/ape.lds
cmp %rcx,%rax
jl 9f
push %rax
push %rcx
call *(%rax)
pop %rcx
pop %rax
jmp 8b
9: pop %r13
pop %r14
pop %rbp
ret
.endfn __cxa_finalize,globl,hidden
.bss
.align 16 # static/dynamic hybrid linked list
g_cxa: .quad 0 # last block ptr: (long (*)[32][3])
.quad 0 # block byte offset moves backwards
.endobj g_cxa
g_cxa_static:
.rept ATEXIT_MAX
.quad 0 # dso
.quad 0 # arg
.quad 0 # fn (or NULL for empty)
.endr
.quad 0 # next (always NULL in static block)
.endobj g_cxa_static
.previous
.init.start 300,_init_g_cxa
ezlea g_cxa,cx
lea g_cxa_static-g_cxa(%rcx),%rax
mov %rax,(%rcx)
movl $ATEXIT_MAX*8*3,8(%rcx)
.init.end 300,_init_g_cxa