/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8     -*-│
│ vi: set noet ft=asm ts=8 sw=8 fenc=utf-8                                 :vi │
╞══════════════════════════════════════════════════════════════════════════════╡
│ Copyright 2020 Justine Alexandra Roberts Tunney                              │
│                                                                              │
│ Permission to use, copy, modify, and/or distribute this software for         │
│ any purpose with or without fee is hereby granted, provided that the         │
│ above copyright notice and this permission notice appear in all copies.      │
│                                                                              │
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
│ PERFORMANCE OF THIS SOFTWARE.                                                │
╚─────────────────────────────────────────────────────────────────────────────*/
#include "libc/dce.h"
#include "libc/macros.h"
#include "libc/thread/pt.internal.h"

#define SIG_IGN 1

/*                                           ▄▄▄
                       ▄▄▄                    ▀▓▓▒▄
                     ▄▓▒▒░                      ▀▓▒▒▒▄
                   ▄▓▓▓▒▀              ▄▄▄▄      ▒▓▒▒░▒▄
                  ▄▓▓▓▒▓        ▄▄▓██▓▓▓▓▒▒▒▒▓▓▄▄▓▓▒▒▒░░▒
                  ▓▓▓▓▒▒▒▄▄  ░▒█▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▓▒░░▒░
                  ██▓▓▓▒▒░░▒▒▒▒▓▓▓▓▓▓▒▓▒░▒▒░▀▒▒▒▒░▀░▒▒▒░▒
                  ▓▓▓▓▓▓▓▒▒▒▒▒▒▓▓▒▓▓▒▒▒░▒▒░░  ░▒▒░  ░▒▒▒▒
                   ▀▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒░░▒░░    ░▒▒  ░ ▀▒▒
                     ▀▓▓█▓▓▓▓▓▓▓▓▓▓▒▒░░▒▒░░   ░░░▓░ ▓░░░▒
                       ▀▀█▓███▓▓▓▓▓▒▒░░░▒░░  ░█▓░█▓░█▓▓▄▒░
                          ░▓██▓▓▓▓▓▒▒░░░▒░░  ░████▓▒▓█▓▀░▀▄
                          ░▓██▓▓▓▓▓▒▒▒░░░▒░░  ▒██▓▒▒▒▒▒▒░░░▒
                           ████▓▓▓▓▓▒▒▒▒▒▒▒▒▒░░▒▓▓▒░░░░▒░░░▒░ ░░░░░
                           ░▓███▓▓▓▓▓▒▒░░░░░░░▒▒▒▒▒▒▒▒▒▒▒░░░ ░░░░░   ░
                             ▓███▓▓▓▓▓▒▓▒▒▒▒░░░░░░░░░▒▓▒▒░▀ ░░░  ░░░░░
                              ▀▒██▓▓▓▓▒▒▒▓▓▓▓▒▒▒▒▒▒▒▓▀▀░    ░░░░░░░░░     ░
                                 ▓▓▓▓▓▓▓▒▓▒▒▒▒▓▓▓▒▀░ ░░░░░▄░░░  ░░░  ░░░░░░
                                 ▓▓▓▒▒▒▒▒▒▒▒▒▒▒▓     █▓▒░░▒░░░░ ░░░░░░░░
                                ▄▓▓▓▒▒▒▒▒░░░░░░░▒▄▄▄░▒▓▓▒▒░▀░
                               ░▓█▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▒░░░▒  besiyata
                               ▓▓█▓▓▒▓▓▓▒▒▒░░░░░░▒▓▓▓▓▒▒▒▒▒░   dishmaya
                               ▓▓█▓▓▓▓▓▓▒▒▒░░░░░░░▒▓▓▒▀▀▀
                               ▓▓██▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▀
                                █▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀
                               ▒▓▓▓▓▀░░▒▓▓▓▓▓▓▓▓▒▒░░▒
                              ▄▓▓▀░░░▄▓▓▓▓▒▒▒▒▒░░░░▄░
                             ▄███▄▄▓▓▓▓▓▓▓▒▒▒▒▒░░▒▒░
                           ▄▓▓▓█▓█▓▓███▓▓▓▓▓▓▓▓▓▓▓░
                       ▄░▓▓▓▓▓▓▀▒▓▓▓▒▒▓▒░░░▒▓▒░░░▓
               ▄▄▄░▒▓▓▓▓▓▓░▀▀   ▓▓▒░▓▒▒▒▒▒▒▒▒▒▒▄░░▀▀░░ ▄▄▄▄
     ▄▄▄▒▒▓▓█▓▓▓▓▓▀▀▀▀▀        ▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▀░░▀░░▒▒▒░░░   ░░░░░
  ▄▓▓▓▒▀▀                      ▓▒▓▓▓▓▓▒▒▒▒▒▒▒▒▓░░░       ▒▒▒░░░░░░░░▒
  █▓▓▒      ▄▄▄                  ▀▓▒▓▒▒▒▓▓▓▓▓▓▒▒▒░░░░░░░░░▒▒░░░░░░░
   ▀▓▓▓▓▒▄▄▒▒▒▒▒▒▄▄                    ▀▀▀▀░░▒▒▒▒░░░░░░
       ▀▀▀▓▓▓▓▒▒▒▒▒▓▓▄▄
╔────────────────────────────────────────────────────────────────────────────│─╗
│ cosmopolitan § bell system five » system call support                    ─╬─│┼
╚────────────────────────────────────────────────────────────────────────────│*/

	.initbss 300,_init_systemfive

//	Performs System Five System Call.
//
//	Cosmopolitan is designed to delegate all function calls into the
//	Linux, FreeBSD, OpenBSD, and XNU kernels via this function, with
//	few exceptions. This function should generally only be called by
//	generated thunks in the libc/sysv/syscalls/ directory.
//
//	It's safe to call this function on Windows, where it will always
//	return -1 with errno == ENOSYS. Further note that -1 is the only
//	return value that means error, a common anti-pattern is to check
//	for values less than 0 (which is more problematic on 32-bit).
//
//	It is important to consider that system calls are one order of a
//	magnitude more expensive than normal function calls. For example
//	getpid() on Linux usually takes 500ns, and cached i/o calls will
//	take 1µs or more. So we don't need to inline them like Chromium.
//
//	Another thing to consider is that BSDs only loosely follow the
//	System Five ABI for the SYSCALL instruction. For example Linux
//	always follows the six argument limit but the FreeBSD sendfile
//	system call accepts a seventh argument that is passed on stack
//	and OpenBSD modifies functions like mmap so that the sixth arg
//	is passed on the stack. There's also the carry flag convention
//	that XNU, FreeBSD, and OpenBSD inherited from 386BSD aka Jolix
//
//	@param	%rax function ordinal supplied by jump slot
//	@param	%rdi,%rsi,%rdx,%rcx,%r8,%r9 and rest on stack
//	@return	%rax:%rdx is result, or -1 w/ errno on error
//	@clob	%rcx,%r10,%r11
//	@see	syscalls.sh
__systemfive:
	.quad	0
	.endobj	__systemfive,globl,hidden
__pid:	.quad	0
	.endobj	__pid,globl,hidden
	.previous

systemfive_cp:
	push	%rbp
	mov	%rsp,%rbp		// so backtraces work
systemfive_cancellable:			// our pthread_cancel() miracle code
	cmpb	$0,__tls_enabled(%rip)	// inspired by the musl libc design!
	je	1f			// we handle linux and bsd together!
	mov	%gs:0x30,%r10		// CosmoTib::tib_self
	mov	0x28(%r10),%r10		// CosmoTib::tib_pthread
	test	%r10,%r10		// is it a posix thread?
	jz	1f			// it's spawn() probably
	testb	$PT_NOCANCEL,0x00(%r10)	// PosixThread::flags
	jnz	1f			// canceler no cancelling
#if IsModeDbg()
	testb	$PT_INCANCEL,0x00(%r10)
	jz	5f
#endif
	cmpl	$0,0x04(%r10)		// PosixThread::cancelled
	jne	systemfive_cancel	// we will tail call below
1:	mov	%rcx,%r10		// move the fourth argument
	clc				// no cancellable system calls exist
	syscall				// that have 7+ args on the bsd OSes
systemfive_cancellable_end:		// i/o calls park here for long time
	pop	%rbp
	jnc	2f
	neg	%rax			// turns bsd errno to system v errno
2:	cmp	$-4095,%rax		// but we still check again on eintr
	jae	3f			// branch because system call failed
	ret				// done if the system call succeeded
3:	neg	%eax			// now examine the nature of failure
	cmp	EINTR(%rip),%eax	// did the SIGTHR cancel our IO call
	jne	systemfive_errno	// werent interrupted by OnSigCancel
	cmpb	$0,__tls_enabled(%rip)	// make sure it's safe to grab %fs:0
	je	systemfive_errno	// tls is disabled we can't continue
	mov	%gs:0x30,%rcx		// CosmoTib::tib_self
	mov	0x28(%rcx),%rcx		// CosmoTib::tib_pthread
	test	%rcx,%rcx		// is it a posix thread?
	jz	systemfive_errno	// it's spawn() probably
	testb	$PT_NOCANCEL,0x00(%rcx)	// PosixThread::flags
	jnz	systemfive_errno	// cancellation is disabled
	cmpl	$0,0x04(%rcx)		// PosixThread::cancelled
	je	systemfive_errno	// we aren't actually cancelled
	jmp	4f			// now we are in fact cancelled
systemfive_cancel:			// SIGTHR will jump here too
	pop	%rbp
4:	jmp	_pthread_cancel_ack	// tail call
	.weak	_pthread_cancel_ack	// must be linked if we're cancelled
#if IsModeDbg()
not_a_cancellation_point:		// need BEGIN/END_CANCELLATION_POINT
	nop
	.weak	report_cancellation_point
5:	ezlea	report_cancellation_point,cx
	test	%rcx,%rcx
	jz	6f
	call	*%rcx
6:	ud2
	nop
#endif
	.globl	systemfive_cancellable_end
	.globl	systemfive_cancellable
	.globl	systemfive_cancel
	.endfn	systemfive_cp

.Lanchorpoint:
#if SupportsLinux() || SupportsMetal()
systemfive_linux:
	and	$0xfff,%eax		// remove nonlinux bits from ordinal
	cmp	$0xfff,%eax		// checks if unsupported by platform
	je	systemfive_enosys	// never taken branches cost nothing
	btr	$11,%eax		// 0x800 means a call is cancellable
	jc	systemfive_cp		// it is handled by the holiest code
	mov	%rcx,%r10		// syscall instruction clobbers %rcx
	push	%rbp			// linux never reads args from stack
	mov	%rsp,%rbp		// having frame will help backtraces
	syscall				// this is known as a context switch
	pop	%rbp			// next we check to see if it failed
	cmp	$-4095,%rax		// system five nexgen32e abi § a.2.1
	jae	systemfive_error	// encodes errno as neg return value
	ret
	.endfn	systemfive_linux,globl,hidden
systemfive_error:
	neg	%eax
//	𝑠𝑙𝑖𝑑𝑒
	.endfn	systemfive_error,globl,hidden
#endif
systemfive_errno:
	xchg	%eax,%ecx
	call	__errno_location
	mov	%ecx,(%rax)		// normalize to c library convention
	push	$-1			// negative one is only error result
	pop	%rax			// the push pop is to save code size
	ret
	.endfn	systemfive_errno,globl,hidden
systemfive_enosys:
	mov	ENOSYS(%rip),%eax
	jmp	systemfive_errno
	.endfn	systemfive_enosys,globl,hidden
#if SupportsNetbsd()
systemfive_netbsd:
	shr	$4*13,%rax
	jmp	systemfive_bsdscrub
	.endfn	systemfive_netbsd,globl,hidden
#endif
#if SupportsOpenbsd()
systemfive_openbsd:
	shr	$4*10,%rax
	jmp	systemfive_bsdscrub
	.endfn	systemfive_openbsd,globl,hidden
#endif
#if SupportsFreebsd()
systemfive_freebsd:
	shr	$4*7,%rax
	movzwl	%ax,%eax
//	𝑠𝑙𝑖𝑑𝑒
	.endfn	systemfive_freebsd,globl,hidden
#endif
#if SupportsBsd()
systemfive_bsdscrub:
	and	$0xfff,%eax
//	𝑠𝑙𝑖𝑑𝑒
	.endfn	systemfive_bsdscrub,globl,hidden
systemfive_bsd:
	cmp	$0xfff,%ax
	je	systemfive_enosys
	btr	$11,%eax		// checks/reset the 800 cancellable bit
	jc	systemfive_cp
	mov	%rcx,%r10
	syscall				// bsd will need arg on stack sometimes
	jc	systemfive_errno	// bsd sets carry flag if %rax is errno
	ret
	.endfn	systemfive_bsd
#endif
#if SupportsXnu()
systemfive_xnu:
//	0x?????????2153???		// how syscalls.sh encodes xnu ordinals
//	           │└┴┴┐
//	           │   ├┬┐
//	0x0000000002000153		// how xnu wants ordinals to be encoded
	mov	%eax,%r11d
	and	$0x0f000000,%r11d
	shl	$8,%eax
	shr	$20,%eax
	or	%r11d,%eax
	jmp	systemfive_bsd
	.endfn	systemfive_xnu,globl,hidden
#endif
	.previous

//	Initializes System Five system call support.
//
//	  (1) Extracts parameters passed by kernel
//	  (2) Detects OS without issuing system calls
//	  (3) Unpacks magnums from libc/sysv/consts.sh
//	  (4) Replaces stack with one we control
//
//	@param	%r15 is auxv
//	@note	OpenBSD devs: let us know if you start using auxv
	.init.start 300,_init_systemfive
	push	%rbx
	push	%rsi
//	Detect the operating system.
	mov	__hostos(%rip),%eax
#if SupportsWindows() || SupportsXnu() || SupportsFreebsd()
//	set by	libc/ape.S		for XNU
//	set by	libc/crt/crt.S		for XNU/FreeBSD
//	set by	libc/nt/winmain.greg.c	for New Technology
	test	%eax,%eax
	jnz	_init_systemfive_detected	// os is already known
#endif
#if SupportsOpenbsd()
	cmpq	$0,(%r15)			// OpenBSD has no auxv
	jnz	0f
	mov	$_HOSTOPENBSD,%al
	jmp	_init_systemfive_detected
0:
#endif
#if SupportsNetbsd()
	xor	%ecx,%ecx
0:	cmpq	$2014,(%r15,%rcx,8)		// NetBSD's AT_EXECFN
	jne	1f
	mov	$_HOSTNETBSD,%al
	jmp	_init_systemfive_detected
1:	cmpq	$0,(%r15,%rcx,8)
	lea	2(%ecx),%ecx
	jnz	0b
2:
#endif
	mov	$_HOSTLINUX,%al
_init_systemfive_detected:
	mov	%eax,__hostos(%rip)
	bsr	%eax,%eax
	mov	$_init_systemfive_jmptab,%ebx
	movzbl	(%rbx,%rax),%eax
	lea	(%rbx,%rax),%eax
	jmp	*%rax
_init_systemfive_jmptab:
	.byte	_init_systemfive_linux-_init_systemfive_jmptab
	.byte	_init_systemfive_metal-_init_systemfive_jmptab
	.byte	_init_systemfive_windows-_init_systemfive_jmptab
	.byte	_init_systemfive_xnu-_init_systemfive_jmptab
	.byte	_init_systemfive_openbsd-_init_systemfive_jmptab
	.byte	_init_systemfive_freebsd-_init_systemfive_jmptab
	.byte	_init_systemfive_netbsd-_init_systemfive_jmptab
	.endobj	_init_systemfive_jmptab
_init_systemfive_linux:
#if SupportsLinux()
	pushb	systemfive_linux-.Lanchorpoint
	mov	$syscon_linux,%esi
	jmp	_init_systemfive_os
#endif
	.endfn	_init_systemfive_linux
_init_systemfive_metal:
#if SupportsMetal()
	pushb	systemfive_enosys-.Lanchorpoint
	mov	$syscon_linux,%esi
	jmp	_init_systemfive_os
#endif
	.endfn	_init_systemfive_metal
_init_systemfive_windows:
#if SupportsWindows()
	pushb	systemfive_enosys-.Lanchorpoint
	mov	$syscon_windows,%esi
	jmp	_init_systemfive_os
#endif
	.endfn	_init_systemfive_windows
_init_systemfive_xnu:
#if SupportsXnu()
	pushb	systemfive_xnu-.Lanchorpoint
	mov	$syscon_xnu,%esi
	jmp	_init_systemfive_os
#endif
	.endfn	_init_systemfive_xnu
_init_systemfive_openbsd:
#if SupportsOpenbsd()
	pushb	systemfive_openbsd-.Lanchorpoint
	mov	$syscon_openbsd,%esi
	jmp	_init_systemfive_os
#endif
	.endfn	_init_systemfive_openbsd
_init_systemfive_freebsd:
#if SupportsFreebsd()
	pushb	systemfive_freebsd-.Lanchorpoint
	mov	$syscon_freebsd,%esi
	jmp	_init_systemfive_os
#endif
	.endfn	_init_systemfive_freebsd
_init_systemfive_netbsd:
#if SupportsNetbsd()
	pushb	systemfive_netbsd-.Lanchorpoint
	mov	$syscon_netbsd,%esi
//	𝑠𝑙𝑖𝑑𝑒
#endif
	.endfn	_init_systemfive_netbsd
_init_systemfive_os:
	pop	%rax
	add	$.Lanchorpoint,%eax
	stosq	#→ __systemfive
//	𝑠𝑙𝑖𝑑𝑒
	.endfn	_init_systemfive_os
_init_systemfive_magnums:
	push	%rdi
	mov	$syscon_start,%edi
2:	cmp	$syscon_end,%edi
	jnb	5f
	xor	%ebx,%ebx
	xor	%ecx,%ecx
	xor	%edx,%edx
3:	lodsb						// decodes uleb128
	movzbl	%al,%edx
	and	$127,%dl
	shl	%cl,%rdx
	or	%rdx,%rbx
	add	$7,%cl
	test	$128,%al
	jnz	3b
	xchg	%rbx,%rax
	stosq
	jmp	2b
5:	nop
	pop	%rdi
	pop	%rsi
	pop	%rbx
//	𝑠𝑙𝑖𝑑𝑒
	.endfn	_init_systemfive_magnums
#if SupportsSystemv()
_init_systemfive_pid:
	ezlea	__hostos,cx
	mov	(%rcx),%al
	mov	(%rdi),%eax
	testb	$_HOSTWINDOWS|_HOSTMETAL,(%rcx)
	jnz	1f
	mov	__NR_getpid(%rip),%eax
	syscall
1:	stosq
	.endfn	_init_systemfive_pid
#endif
#if SupportsBsd() && !defined(TINY)
_init_systemfive_sigsys:
	testb	IsBsd()					// BSDs will trap SIGSYS!
	jz	1f					// We want ENOSYS instead
	push	%rdi					// XNU removed some calls
	push	%rsi					// in past, so this makes
	xor	%eax,%eax				// troubleshooting easier
	push	%rax					// but it's non-essential
	push	%rax
	push	%rax
	push	%rax
	push	%rax
	push	$SIG_IGN				// sigaction_meta size 48
	mov	__NR_sigaction(%rip),%eax		// mag
	mov	SIGSYS(%rip),%edi			// sig
	mov	%rsp,%rsi				// new
	xor	%edx,%edx				// old
	mov	$8,%r10d				// for linux
	xor	%r8d,%r8d				// for netbsd
	syscall
	add	$6*8,%rsp
	pop	%rsi
	pop	%rdi
1:	.endfn	_init_systemfive_sigsys
#endif
_init_systemfive_done:
	nop
	.init.end 300,_init_systemfive,globl,hidden
