//go:build darwin && amd64

#include "textflag.h"

// fsevents_darwin_ffi_amd64.s: amd64 assembly for the FSEvents backend
//
// Contains two functions:
//
//  1. FSEventStreamCreate trampoline: shuffles the float64 latency arg
//     from R9 (integer register, where syscall6 puts it) into X0 (xmm0,
//     where the System V AMD64 ABI expects the first float argument),
//     and hardcodes the flags argument to 0x11
//     (kFSEventStreamCreateFlagUseCFTypes |
//     kFSEventStreamCreateFlagFileEvents).
//
//  2. fsEventsCallbackASM: the C-convention callback invoked by FSEvents
//     on a GCD dispatch queue thread. Retains/copies callback data into a
//     payload, writes the payload pointer to eventPipe to wake the Go event-loop
//     goroutine, then returns. Never enters Go ABI; stays entirely in System V
//     AMD64 calling convention.

// ---------------------------------------------------------------------------
// FSEventStreamCreate trampoline: shuffles the float64 latency argument.
//
// The runtime's syscall6 trampoline loads 6 args into registers:
//   DI=allocator  SI=callback  DX=ctx  CX=paths
//   R8=sinceWhen  R9=latency(bits)
//
// The C function expects latency in X0 (xmm0) and flags in R9.
// flags is always 0x11 (kFSEventStreamCreateFlagUseCFTypes |
// kFSEventStreamCreateFlagFileEvents), so we hardcode it.
// ---------------------------------------------------------------------------

TEXT fse_FSEventStreamCreate_trampoline<>(SB), NOSPLIT, $0-0
	MOVQ R9, X0
	MOVQ $0x11, R9
	JMP  fse_FSEventStreamCreate(SB)

GLOBL ·fse_FSEventStreamCreate_trampoline_addr(SB), RODATA, $8
DATA ·fse_FSEventStreamCreate_trampoline_addr(SB)/8, $fse_FSEventStreamCreate_trampoline<>(SB)

// ---------------------------------------------------------------------------
// FSEvents callback: called from a GCD dispatch queue with C convention.
//   DI=streamRef  SI=info  DX=numEvents  CX=paths  R8=flags  R9=ids
//
// `info` is a pointer to a streamCallback struct (see fsevents_darwin_ffi.go):
//   offset  0: eventPipeWrite fd    (8 bytes)
//
// Stays entirely in C context (no cgocallback). Saves args to the per-stream
// heap-allocated payload, writes its pointer to the stream's eventPipe to wake
// its Go event loop goroutine, then returns immediately.
//
// NOFRAME: this function is entered from C, not Go. We manage the frame
// ourselves following the System V AMD64 ABI.
//
// Frame layout (88 bytes, 16-byte aligned):
//   On entry from C, RSP ≡ 8 mod 16 (return address pushed by CALL).
//   SUB $88 → RSP ≡ 8-88 = 0 mod 16, aligned for CALL into libc.
//   RSP+ 0: payload pointer bytes written to eventPipe
//   RSP+ 8: saved info pointer
//   RSP+16: saved numEvents
//   RSP+24: saved original flags pointer
//   RSP+32: retained CFArray paths
//   RSP+40: copied flags pointer
//   RSP+48: saved original IDs pointer
//   RSP+56: copied IDs pointer
//   RSP+80: saved RBP  ← BP points here (C frame chain)
//   RSP+88: return address (pushed by C's CALL)
// ---------------------------------------------------------------------------

TEXT fsEventsCallbackASM<>(SB), NOSPLIT|NOFRAME, $0
	SUBQ $88, SP
	MOVQ BP, 80(SP)
	LEAQ 80(SP), BP

	MOVQ SI, 8(SP)  // info
	MOVQ DX, 16(SP) // numEvents
	MOVQ R8, 24(SP) // original flags
	MOVQ R9, 48(SP) // original IDs

	// Retain the CFArray paths because FSEvents owns the callback argument.
	MOVQ  CX, DI
	XORL  AX, AX
	CALL  fse_CFRetain(SB)
	TESTQ AX, AX
	JEQ   done
	MOVQ  AX, 32(SP)

	// Copy the flags array into C heap memory owned by the Go event loop.
	MOVQ  16(SP), DI
	SHLQ  $2, DI
	XORL  AX, AX
	CALL  fse_malloc(SB)
	TESTQ AX, AX
	JEQ   releasePaths
	MOVQ  AX, 40(SP)

	MOVQ AX, DI
	MOVQ 24(SP), SI
	MOVQ 16(SP), DX
	SHLQ $2, DX
	XORL AX, AX
	CALL fse_memcpy(SB)

	// Copy the event ID array into C heap memory owned by the Go event loop.
	MOVQ  16(SP), DI
	SHLQ  $3, DI
	XORL  AX, AX
	CALL  fse_malloc(SB)
	TESTQ AX, AX
	JEQ   freeFlags
	MOVQ  AX, 56(SP)

	MOVQ AX, DI
	MOVQ 48(SP), SI
	MOVQ 16(SP), DX
	SHLQ $3, DX
	XORL AX, AX
	CALL fse_memcpy(SB)

	// Allocate and populate fsEventsCallbackPayload.
	MOVQ  $32, DI
	XORL  AX, AX
	CALL  fse_malloc(SB)
	TESTQ AX, AX
	JEQ   freeIDs
	MOVQ  AX, 0(SP)

	MOVQ 16(SP), CX
	MOVQ CX, (0*8)(AX)
	MOVQ 32(SP), CX
	MOVQ CX, (1*8)(AX)
	MOVQ 40(SP), CX
	MOVQ CX, (2*8)(AX)
	MOVQ 56(SP), CX
	MOVQ CX, (3*8)(AX)

	// write(info->eventPipeWrite, &payload, sizeof(payload)).
writeAgain:
	MOVQ 8(SP), AX       // reload info
	MOVQ (0*8)(AX), DI   // eventPipeWrite
	LEAQ 0(SP), SI       // buf (payload pointer)
	MOVQ $8, DX          // count
	XORL AX, AX          // no float args
	CALL fse_write(SB)
	CMPQ AX, $8
	JEQ  done
	CMPQ AX, $-1
	JNE  freePayload
	XORL AX, AX          // no float args
	CALL fse___error(SB)
	MOVL (AX), AX
	CMPL AX, $4          // EINTR
	JEQ  writeAgain
	JMP  freePayload

freePayload:
	MOVQ 0(SP), DI
	XORL AX, AX
	CALL fse_free(SB)

freeIDs:
	MOVQ 56(SP), DI
	XORL AX, AX
	CALL fse_free(SB)

freeFlags:
	MOVQ 40(SP), DI
	XORL AX, AX
	CALL fse_free(SB)

releasePaths:
	MOVQ 32(SP), DI
	XORL AX, AX
	CALL fse_CFRelease(SB)

	// Return 0.
done:
	XORL AX, AX
	MOVQ 80(SP), BP
	ADDQ $88, SP
	RET

GLOBL ·fsEventsCallbackAsmAddr(SB), RODATA, $8
DATA ·fsEventsCallbackAsmAddr(SB)/8, $fsEventsCallbackASM<>(SB)
