mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
1102 lines
29 KiB
Go
1102 lines
29 KiB
Go
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// Package gpu provides GPU-accelerated batch operations for the DEX precompile.
|
|
// Uses Metal on Apple Silicon, CUDA on NVIDIA, with CPU fallback.
|
|
// Target: Sub-microsecond per swap in batch mode (1M+ swaps/sec).
|
|
package gpu
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
"runtime"
|
|
"sync"
|
|
"sync/atomic"
|
|
"unsafe"
|
|
|
|
"github.com/luxfi/geth/common"
|
|
)
|
|
|
|
// Backend represents the GPU backend type.
|
|
type Backend uint8
|
|
|
|
const (
|
|
BackendCPU Backend = iota
|
|
BackendMetal
|
|
BackendCUDA
|
|
BackendWebGPU
|
|
)
|
|
|
|
// Config holds GPU acceleration configuration.
|
|
type Config struct {
|
|
// Backend selection (auto-detect if not specified)
|
|
Backend Backend
|
|
|
|
// BatchThreshold: minimum batch size to use GPU (below uses CPU)
|
|
BatchThreshold int
|
|
|
|
// MaxBatchSize: maximum batch size per GPU dispatch
|
|
MaxBatchSize int
|
|
|
|
// AsyncDispatch: whether to use async GPU dispatch
|
|
AsyncDispatch bool
|
|
|
|
// PreallocBuffers: number of preallocated GPU buffers
|
|
PreallocBuffers int
|
|
}
|
|
|
|
// DefaultConfig returns sensible defaults for GPU acceleration.
|
|
func DefaultConfig() Config {
|
|
return Config{
|
|
Backend: detectBackend(),
|
|
BatchThreshold: 64, // GPU overhead not worth it below 64 ops
|
|
MaxBatchSize: 65536, // 64K ops per dispatch
|
|
AsyncDispatch: true,
|
|
PreallocBuffers: 8,
|
|
}
|
|
}
|
|
|
|
// detectBackend auto-detects the best available backend.
|
|
func detectBackend() Backend {
|
|
switch runtime.GOOS {
|
|
case "darwin":
|
|
return BackendMetal
|
|
case "linux", "windows":
|
|
// Check for CUDA availability
|
|
if hasCUDA() {
|
|
return BackendCUDA
|
|
}
|
|
return BackendCPU
|
|
default:
|
|
return BackendCPU
|
|
}
|
|
}
|
|
|
|
// hasCUDA checks if CUDA is available (stub - real impl checks device).
|
|
func hasCUDA() bool {
|
|
// In production, this would check for CUDA devices
|
|
return false
|
|
}
|
|
|
|
// Errors
|
|
var (
|
|
ErrGPUNotAvailable = errors.New("gpu: backend not available")
|
|
ErrBatchTooLarge = errors.New("gpu: batch size exceeds maximum")
|
|
ErrInvalidInput = errors.New("gpu: invalid input data")
|
|
ErrBufferAllocation = errors.New("gpu: buffer allocation failed")
|
|
ErrKernelExecution = errors.New("gpu: kernel execution failed")
|
|
ErrResultCopy = errors.New("gpu: result copy failed")
|
|
)
|
|
|
|
// =============================================================================
|
|
// Fixed-Point Types for GPU (128-bit Q64.96 format)
|
|
// =============================================================================
|
|
|
|
// SqrtPriceX96 is a 128-bit fixed-point sqrt(price) in Q64.96 format.
|
|
// Stored as two 64-bit words for GPU compatibility.
|
|
type SqrtPriceX96 struct {
|
|
Lo uint64 // Lower 64 bits
|
|
Hi uint64 // Upper 64 bits
|
|
}
|
|
|
|
// FromBigInt converts a big.Int to SqrtPriceX96.
|
|
func (s *SqrtPriceX96) FromBigInt(b *big.Int) {
|
|
if b == nil {
|
|
s.Lo, s.Hi = 0, 0
|
|
return
|
|
}
|
|
bytes := b.Bytes()
|
|
if len(bytes) > 16 {
|
|
bytes = bytes[len(bytes)-16:]
|
|
}
|
|
// Pad to 16 bytes
|
|
padded := make([]byte, 16)
|
|
copy(padded[16-len(bytes):], bytes)
|
|
|
|
s.Hi = uint64(padded[0])<<56 | uint64(padded[1])<<48 |
|
|
uint64(padded[2])<<40 | uint64(padded[3])<<32 |
|
|
uint64(padded[4])<<24 | uint64(padded[5])<<16 |
|
|
uint64(padded[6])<<8 | uint64(padded[7])
|
|
s.Lo = uint64(padded[8])<<56 | uint64(padded[9])<<48 |
|
|
uint64(padded[10])<<40 | uint64(padded[11])<<32 |
|
|
uint64(padded[12])<<24 | uint64(padded[13])<<16 |
|
|
uint64(padded[14])<<8 | uint64(padded[15])
|
|
}
|
|
|
|
// ToBigInt converts SqrtPriceX96 to big.Int.
|
|
func (s *SqrtPriceX96) ToBigInt() *big.Int {
|
|
bytes := make([]byte, 16)
|
|
bytes[0] = byte(s.Hi >> 56)
|
|
bytes[1] = byte(s.Hi >> 48)
|
|
bytes[2] = byte(s.Hi >> 40)
|
|
bytes[3] = byte(s.Hi >> 32)
|
|
bytes[4] = byte(s.Hi >> 24)
|
|
bytes[5] = byte(s.Hi >> 16)
|
|
bytes[6] = byte(s.Hi >> 8)
|
|
bytes[7] = byte(s.Hi)
|
|
bytes[8] = byte(s.Lo >> 56)
|
|
bytes[9] = byte(s.Lo >> 48)
|
|
bytes[10] = byte(s.Lo >> 40)
|
|
bytes[11] = byte(s.Lo >> 32)
|
|
bytes[12] = byte(s.Lo >> 24)
|
|
bytes[13] = byte(s.Lo >> 16)
|
|
bytes[14] = byte(s.Lo >> 8)
|
|
bytes[15] = byte(s.Lo)
|
|
return new(big.Int).SetBytes(bytes)
|
|
}
|
|
|
|
// Liquidity128 is a 128-bit unsigned liquidity value.
|
|
type Liquidity128 struct {
|
|
Lo uint64
|
|
Hi uint64
|
|
}
|
|
|
|
// FromBigInt converts big.Int to Liquidity128.
|
|
func (l *Liquidity128) FromBigInt(b *big.Int) {
|
|
if b == nil || b.Sign() < 0 {
|
|
l.Lo, l.Hi = 0, 0
|
|
return
|
|
}
|
|
words := b.Bits()
|
|
if len(words) > 0 {
|
|
l.Lo = uint64(words[0])
|
|
}
|
|
if len(words) > 1 {
|
|
l.Hi = uint64(words[1])
|
|
}
|
|
}
|
|
|
|
// ToBigInt converts Liquidity128 to big.Int.
|
|
func (l *Liquidity128) ToBigInt() *big.Int {
|
|
result := new(big.Int)
|
|
if l.Hi == 0 {
|
|
return result.SetUint64(l.Lo)
|
|
}
|
|
hi := new(big.Int).SetUint64(l.Hi)
|
|
hi.Lsh(hi, 64)
|
|
return hi.Add(hi, new(big.Int).SetUint64(l.Lo))
|
|
}
|
|
|
|
// =============================================================================
|
|
// Batch Operation Types
|
|
// =============================================================================
|
|
|
|
// SwapInput represents a single swap for batch processing.
|
|
type SwapInput struct {
|
|
PoolID [32]byte // Pool identifier
|
|
SqrtPriceX96 SqrtPriceX96 // Current sqrt price
|
|
Liquidity Liquidity128 // Current liquidity
|
|
Tick int32 // Current tick
|
|
ZeroForOne bool // Direction
|
|
ExactInput bool // Amount type
|
|
Amount Liquidity128 // Amount specified (use as 128-bit signed)
|
|
FeePips uint32 // Fee in pips (1 pip = 0.0001%)
|
|
SqrtPriceLimit SqrtPriceX96 // Price limit
|
|
}
|
|
|
|
// SwapOutput holds the result of a batch swap.
|
|
type SwapOutput struct {
|
|
Amount0Delta Liquidity128 // Token0 delta (signed as 2's complement)
|
|
Amount1Delta Liquidity128 // Token1 delta (signed as 2's complement)
|
|
SqrtPriceX96 SqrtPriceX96 // New sqrt price
|
|
Tick int32 // New tick
|
|
FeeGrowth Liquidity128 // Fee growth increment
|
|
Success bool // Whether swap succeeded
|
|
ErrorCode uint8 // Error code if failed
|
|
}
|
|
|
|
// LiquidityInput represents a liquidity operation for batch processing.
|
|
type LiquidityInput struct {
|
|
PoolID [32]byte // Pool identifier
|
|
SqrtPriceX96 SqrtPriceX96 // Current sqrt price
|
|
Liquidity Liquidity128 // Current pool liquidity
|
|
CurrentTick int32 // Current tick
|
|
TickLower int32 // Position lower tick
|
|
TickUpper int32 // Position upper tick
|
|
LiqDelta Liquidity128 // Liquidity delta (signed)
|
|
IsAdd bool // true = add, false = remove
|
|
}
|
|
|
|
// LiquidityOutput holds the result of a liquidity operation.
|
|
type LiquidityOutput struct {
|
|
Amount0 Liquidity128 // Token0 amount
|
|
Amount1 Liquidity128 // Token1 amount
|
|
FeeGrowth0 Liquidity128 // Fee growth token0
|
|
FeeGrowth1 Liquidity128 // Fee growth token1
|
|
Success bool
|
|
ErrorCode uint8
|
|
}
|
|
|
|
// RouteInput represents a multi-hop route for optimization.
|
|
type RouteInput struct {
|
|
PoolIDs [][32]byte // Pools in route (max 8 hops)
|
|
SqrtPrices []SqrtPriceX96
|
|
Liquidities []Liquidity128
|
|
Fees []uint32
|
|
AmountIn Liquidity128
|
|
NumHops uint8
|
|
}
|
|
|
|
// RouteOutput holds optimized route result.
|
|
type RouteOutput struct {
|
|
AmountOut Liquidity128
|
|
PriceImpact uint32 // Impact in basis points
|
|
GasEstimate uint64
|
|
Success bool
|
|
}
|
|
|
|
// =============================================================================
|
|
// GPU Accelerator
|
|
// =============================================================================
|
|
|
|
// Accelerator manages GPU resources and batch operations.
|
|
type Accelerator struct {
|
|
config Config
|
|
backend Backend
|
|
|
|
// Buffer pools
|
|
swapInputPool sync.Pool
|
|
swapOutputPool sync.Pool
|
|
liqInputPool sync.Pool
|
|
liqOutputPool sync.Pool
|
|
|
|
// Statistics
|
|
totalSwaps uint64
|
|
totalLiquidity uint64
|
|
totalRoutes uint64
|
|
gpuTime uint64 // nanoseconds
|
|
|
|
// Backend-specific handles (opaque)
|
|
metalDevice unsafe.Pointer
|
|
metalQueue unsafe.Pointer
|
|
metalLibrary unsafe.Pointer
|
|
cudaDevice int
|
|
cudaStream unsafe.Pointer
|
|
}
|
|
|
|
// NewAccelerator creates a GPU accelerator with the given config.
|
|
func NewAccelerator(cfg Config) (*Accelerator, error) {
|
|
acc := &Accelerator{
|
|
config: cfg,
|
|
backend: cfg.Backend,
|
|
}
|
|
|
|
// Initialize buffer pools
|
|
acc.swapInputPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return make([]SwapInput, 0, cfg.MaxBatchSize)
|
|
},
|
|
}
|
|
acc.swapOutputPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return make([]SwapOutput, 0, cfg.MaxBatchSize)
|
|
},
|
|
}
|
|
acc.liqInputPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return make([]LiquidityInput, 0, cfg.MaxBatchSize)
|
|
},
|
|
}
|
|
acc.liqOutputPool = sync.Pool{
|
|
New: func() interface{} {
|
|
return make([]LiquidityOutput, 0, cfg.MaxBatchSize)
|
|
},
|
|
}
|
|
|
|
// Initialize backend
|
|
switch acc.backend {
|
|
case BackendMetal:
|
|
if err := acc.initMetal(); err != nil {
|
|
// Fall back to CPU
|
|
acc.backend = BackendCPU
|
|
}
|
|
case BackendCUDA:
|
|
if err := acc.initCUDA(); err != nil {
|
|
acc.backend = BackendCPU
|
|
}
|
|
}
|
|
|
|
return acc, nil
|
|
}
|
|
|
|
// Close releases GPU resources.
|
|
func (a *Accelerator) Close() error {
|
|
switch a.backend {
|
|
case BackendMetal:
|
|
return a.closeMetal()
|
|
case BackendCUDA:
|
|
return a.closeCUDA()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Backend returns the active backend.
|
|
func (a *Accelerator) Backend() Backend {
|
|
return a.backend
|
|
}
|
|
|
|
// Stats returns acceleration statistics.
|
|
func (a *Accelerator) Stats() (swaps, liquidity, routes, gpuNs uint64) {
|
|
return atomic.LoadUint64(&a.totalSwaps),
|
|
atomic.LoadUint64(&a.totalLiquidity),
|
|
atomic.LoadUint64(&a.totalRoutes),
|
|
atomic.LoadUint64(&a.gpuTime)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Batch Swap
|
|
// =============================================================================
|
|
|
|
// BatchSwap processes multiple swaps in parallel on GPU.
|
|
// Returns outputs in same order as inputs.
|
|
func (a *Accelerator) BatchSwap(inputs []SwapInput) ([]SwapOutput, error) {
|
|
n := len(inputs)
|
|
if n == 0 {
|
|
return nil, nil
|
|
}
|
|
if n > a.config.MaxBatchSize {
|
|
return nil, ErrBatchTooLarge
|
|
}
|
|
|
|
// Use CPU for small batches
|
|
if n < a.config.BatchThreshold || a.backend == BackendCPU {
|
|
return a.batchSwapCPU(inputs)
|
|
}
|
|
|
|
// Dispatch to GPU
|
|
switch a.backend {
|
|
case BackendMetal:
|
|
return a.batchSwapMetal(inputs)
|
|
case BackendCUDA:
|
|
return a.batchSwapCUDA(inputs)
|
|
default:
|
|
return a.batchSwapCPU(inputs)
|
|
}
|
|
}
|
|
|
|
// batchSwapCPU processes swaps on CPU (fallback and small batches).
|
|
func (a *Accelerator) batchSwapCPU(inputs []SwapInput) ([]SwapOutput, error) {
|
|
n := len(inputs)
|
|
outputs := make([]SwapOutput, n)
|
|
|
|
// Process in parallel using goroutines
|
|
var wg sync.WaitGroup
|
|
chunkSize := 64 // Process 64 swaps per goroutine
|
|
|
|
for i := 0; i < n; i += chunkSize {
|
|
end := i + chunkSize
|
|
if end > n {
|
|
end = n
|
|
}
|
|
wg.Add(1)
|
|
go func(start, stop int) {
|
|
defer wg.Done()
|
|
for j := start; j < stop; j++ {
|
|
outputs[j] = a.executeSwapCPU(&inputs[j])
|
|
}
|
|
}(i, end)
|
|
}
|
|
|
|
wg.Wait()
|
|
atomic.AddUint64(&a.totalSwaps, uint64(n))
|
|
return outputs, nil
|
|
}
|
|
|
|
// executeSwapCPU executes a single swap on CPU.
|
|
// Implements Uniswap v3 swap math.
|
|
func (a *Accelerator) executeSwapCPU(input *SwapInput) SwapOutput {
|
|
output := SwapOutput{Success: true}
|
|
|
|
sqrtPrice := input.SqrtPriceX96.ToBigInt()
|
|
liquidity := input.Liquidity.ToBigInt()
|
|
amount := input.Amount.ToBigInt()
|
|
|
|
// Handle zero liquidity
|
|
if liquidity.Sign() == 0 {
|
|
output.Success = false
|
|
output.ErrorCode = 1 // No liquidity
|
|
return output
|
|
}
|
|
|
|
// Calculate amounts based on direction and input type
|
|
var amount0, amount1 *big.Int
|
|
|
|
if input.ZeroForOne {
|
|
// Swapping token0 for token1
|
|
if input.ExactInput {
|
|
amount0 = amount
|
|
amount1 = a.calculateOutput0For1(sqrtPrice, liquidity, amount0, input.FeePips)
|
|
amount1 = new(big.Int).Neg(amount1) // Output is negative (going to user)
|
|
} else {
|
|
amount1 = new(big.Int).Neg(amount) // Exact output
|
|
amount0 = a.calculateInput0For1(sqrtPrice, liquidity, new(big.Int).Neg(amount1), input.FeePips)
|
|
}
|
|
} else {
|
|
// Swapping token1 for token0
|
|
if input.ExactInput {
|
|
amount1 = amount
|
|
amount0 = a.calculateOutput1For0(sqrtPrice, liquidity, amount1, input.FeePips)
|
|
amount0 = new(big.Int).Neg(amount0)
|
|
} else {
|
|
amount0 = new(big.Int).Neg(amount)
|
|
amount1 = a.calculateInput1For0(sqrtPrice, liquidity, new(big.Int).Neg(amount0), input.FeePips)
|
|
}
|
|
}
|
|
|
|
// Calculate new sqrt price
|
|
newSqrtPrice := a.calculateNewSqrtPrice(sqrtPrice, liquidity, amount0, amount1)
|
|
|
|
// Calculate new tick
|
|
newTick := a.sqrtPriceToTick(newSqrtPrice)
|
|
|
|
// Calculate fee growth
|
|
var feeAmount *big.Int
|
|
if input.ZeroForOne {
|
|
feeAmount = a.calculateFee(amount0, input.FeePips)
|
|
} else {
|
|
feeAmount = a.calculateFee(amount1, input.FeePips)
|
|
}
|
|
|
|
// Set outputs
|
|
output.Amount0Delta.FromBigInt(amount0)
|
|
output.Amount1Delta.FromBigInt(amount1)
|
|
output.SqrtPriceX96.FromBigInt(newSqrtPrice)
|
|
output.Tick = newTick
|
|
output.FeeGrowth.FromBigInt(feeAmount)
|
|
|
|
return output
|
|
}
|
|
|
|
// calculateOutput0For1 calculates token1 output for token0 input.
|
|
func (a *Accelerator) calculateOutput0For1(sqrtPrice, liquidity, amount0 *big.Int, feePips uint32) *big.Int {
|
|
// Apply fee
|
|
amountAfterFee := a.applyFee(amount0, feePips)
|
|
|
|
// output = L * (sqrt(P) - sqrt(P_new)) / sqrt(P) / sqrt(P_new)
|
|
// Simplified: output = amountIn * L / (L + amountIn)
|
|
numerator := new(big.Int).Mul(amountAfterFee, liquidity)
|
|
denominator := new(big.Int).Add(liquidity, amountAfterFee)
|
|
if denominator.Sign() == 0 {
|
|
return big.NewInt(0)
|
|
}
|
|
return numerator.Div(numerator, denominator)
|
|
}
|
|
|
|
// calculateInput0For1 calculates token0 input for exact token1 output.
|
|
func (a *Accelerator) calculateInput0For1(sqrtPrice, liquidity, amount1Out *big.Int, feePips uint32) *big.Int {
|
|
// input = amount * L / (L - amount)
|
|
denominator := new(big.Int).Sub(liquidity, amount1Out)
|
|
if denominator.Sign() <= 0 {
|
|
return new(big.Int).Set(liquidity)
|
|
}
|
|
numerator := new(big.Int).Mul(amount1Out, liquidity)
|
|
result := numerator.Div(numerator, denominator)
|
|
// Add fee
|
|
return a.addFee(result, feePips)
|
|
}
|
|
|
|
// calculateOutput1For0 calculates token0 output for token1 input.
|
|
func (a *Accelerator) calculateOutput1For0(sqrtPrice, liquidity, amount1 *big.Int, feePips uint32) *big.Int {
|
|
amountAfterFee := a.applyFee(amount1, feePips)
|
|
numerator := new(big.Int).Mul(amountAfterFee, liquidity)
|
|
denominator := new(big.Int).Add(liquidity, amountAfterFee)
|
|
if denominator.Sign() == 0 {
|
|
return big.NewInt(0)
|
|
}
|
|
return numerator.Div(numerator, denominator)
|
|
}
|
|
|
|
// calculateInput1For0 calculates token1 input for exact token0 output.
|
|
func (a *Accelerator) calculateInput1For0(sqrtPrice, liquidity, amount0Out *big.Int, feePips uint32) *big.Int {
|
|
denominator := new(big.Int).Sub(liquidity, amount0Out)
|
|
if denominator.Sign() <= 0 {
|
|
return new(big.Int).Set(liquidity)
|
|
}
|
|
numerator := new(big.Int).Mul(amount0Out, liquidity)
|
|
result := numerator.Div(numerator, denominator)
|
|
return a.addFee(result, feePips)
|
|
}
|
|
|
|
// applyFee applies fee deduction: amount * (1 - fee/1e6).
|
|
func (a *Accelerator) applyFee(amount *big.Int, feePips uint32) *big.Int {
|
|
if feePips == 0 {
|
|
return new(big.Int).Set(amount)
|
|
}
|
|
// amount * (1e6 - fee) / 1e6
|
|
multiplier := 1_000_000 - int64(feePips)
|
|
result := new(big.Int).Mul(amount, big.NewInt(multiplier))
|
|
return result.Div(result, big.NewInt(1_000_000))
|
|
}
|
|
|
|
// addFee calculates input needed before fee: amount / (1 - fee/1e6).
|
|
func (a *Accelerator) addFee(amount *big.Int, feePips uint32) *big.Int {
|
|
if feePips == 0 {
|
|
return new(big.Int).Set(amount)
|
|
}
|
|
// amount * 1e6 / (1e6 - fee)
|
|
divisor := 1_000_000 - int64(feePips)
|
|
if divisor <= 0 {
|
|
divisor = 1
|
|
}
|
|
result := new(big.Int).Mul(amount, big.NewInt(1_000_000))
|
|
return result.Div(result, big.NewInt(divisor))
|
|
}
|
|
|
|
// calculateFee calculates fee amount.
|
|
func (a *Accelerator) calculateFee(amount *big.Int, feePips uint32) *big.Int {
|
|
if amount.Sign() <= 0 || feePips == 0 {
|
|
return big.NewInt(0)
|
|
}
|
|
absAmount := new(big.Int).Abs(amount)
|
|
fee := new(big.Int).Mul(absAmount, big.NewInt(int64(feePips)))
|
|
return fee.Div(fee, big.NewInt(1_000_000))
|
|
}
|
|
|
|
// calculateNewSqrtPrice calculates new sqrt price after swap.
|
|
func (a *Accelerator) calculateNewSqrtPrice(sqrtPrice, liquidity, amount0, amount1 *big.Int) *big.Int {
|
|
// Simplified: adjust sqrt price based on amounts
|
|
// Real implementation uses exact tick math
|
|
if liquidity.Sign() == 0 {
|
|
return new(big.Int).Set(sqrtPrice)
|
|
}
|
|
|
|
// deltaP = amount0 * sqrtPrice / liquidity
|
|
delta := new(big.Int).Mul(amount0, sqrtPrice)
|
|
delta.Div(delta, liquidity)
|
|
|
|
newPrice := new(big.Int).Sub(sqrtPrice, delta)
|
|
if newPrice.Sign() <= 0 {
|
|
newPrice = big.NewInt(1)
|
|
}
|
|
return newPrice
|
|
}
|
|
|
|
// sqrtPriceToTick converts sqrt price to tick.
|
|
func (a *Accelerator) sqrtPriceToTick(sqrtPrice *big.Int) int32 {
|
|
// tick = floor(log_1.0001(sqrtPrice^2 / 2^192))
|
|
// Simplified binary search implementation
|
|
q96 := new(big.Int).Lsh(big.NewInt(1), 96)
|
|
|
|
if sqrtPrice.Cmp(q96) == 0 {
|
|
return 0
|
|
}
|
|
if sqrtPrice.Cmp(q96) < 0 {
|
|
return -1 // Below 1.0
|
|
}
|
|
return 1 // Above 1.0
|
|
}
|
|
|
|
// =============================================================================
|
|
// Batch Liquidity
|
|
// =============================================================================
|
|
|
|
// BatchLiquidity processes multiple liquidity operations on GPU.
|
|
func (a *Accelerator) BatchLiquidity(inputs []LiquidityInput) ([]LiquidityOutput, error) {
|
|
n := len(inputs)
|
|
if n == 0 {
|
|
return nil, nil
|
|
}
|
|
if n > a.config.MaxBatchSize {
|
|
return nil, ErrBatchTooLarge
|
|
}
|
|
|
|
if n < a.config.BatchThreshold || a.backend == BackendCPU {
|
|
return a.batchLiquidityCPU(inputs)
|
|
}
|
|
|
|
switch a.backend {
|
|
case BackendMetal:
|
|
return a.batchLiquidityMetal(inputs)
|
|
case BackendCUDA:
|
|
return a.batchLiquidityCUDA(inputs)
|
|
default:
|
|
return a.batchLiquidityCPU(inputs)
|
|
}
|
|
}
|
|
|
|
// batchLiquidityCPU processes liquidity operations on CPU.
|
|
func (a *Accelerator) batchLiquidityCPU(inputs []LiquidityInput) ([]LiquidityOutput, error) {
|
|
n := len(inputs)
|
|
outputs := make([]LiquidityOutput, n)
|
|
|
|
var wg sync.WaitGroup
|
|
chunkSize := 64
|
|
|
|
for i := 0; i < n; i += chunkSize {
|
|
end := i + chunkSize
|
|
if end > n {
|
|
end = n
|
|
}
|
|
wg.Add(1)
|
|
go func(start, stop int) {
|
|
defer wg.Done()
|
|
for j := start; j < stop; j++ {
|
|
outputs[j] = a.executeLiquidityCPU(&inputs[j])
|
|
}
|
|
}(i, end)
|
|
}
|
|
|
|
wg.Wait()
|
|
atomic.AddUint64(&a.totalLiquidity, uint64(n))
|
|
return outputs, nil
|
|
}
|
|
|
|
// executeLiquidityCPU executes a single liquidity operation on CPU.
|
|
func (a *Accelerator) executeLiquidityCPU(input *LiquidityInput) LiquidityOutput {
|
|
output := LiquidityOutput{Success: true}
|
|
|
|
sqrtPrice := input.SqrtPriceX96.ToBigInt()
|
|
liqDelta := input.LiqDelta.ToBigInt()
|
|
|
|
// Check tick range
|
|
if input.TickLower >= input.TickUpper {
|
|
output.Success = false
|
|
output.ErrorCode = 1
|
|
return output
|
|
}
|
|
|
|
currentTick := input.CurrentTick
|
|
isActive := input.TickLower <= currentTick && currentTick < input.TickUpper
|
|
|
|
var amount0, amount1 *big.Int
|
|
|
|
if input.IsAdd {
|
|
// Adding liquidity
|
|
if isActive {
|
|
// Both tokens needed - split equally (simplified)
|
|
amount0 = new(big.Int).Rsh(liqDelta, 1)
|
|
amount1 = new(big.Int).Rsh(liqDelta, 1)
|
|
} else if currentTick < input.TickLower {
|
|
// Only token0 needed
|
|
amount0 = new(big.Int).Set(liqDelta)
|
|
amount1 = big.NewInt(0)
|
|
} else {
|
|
// Only token1 needed
|
|
amount0 = big.NewInt(0)
|
|
amount1 = new(big.Int).Set(liqDelta)
|
|
}
|
|
} else {
|
|
// Removing liquidity
|
|
absLiq := new(big.Int).Abs(liqDelta)
|
|
if isActive {
|
|
amount0 = new(big.Int).Neg(new(big.Int).Rsh(absLiq, 1))
|
|
amount1 = new(big.Int).Neg(new(big.Int).Rsh(absLiq, 1))
|
|
} else if currentTick < input.TickLower {
|
|
amount0 = new(big.Int).Neg(absLiq)
|
|
amount1 = big.NewInt(0)
|
|
} else {
|
|
amount0 = big.NewInt(0)
|
|
amount1 = new(big.Int).Neg(absLiq)
|
|
}
|
|
}
|
|
|
|
output.Amount0.FromBigInt(amount0)
|
|
output.Amount1.FromBigInt(amount1)
|
|
_ = sqrtPrice // Used in real implementation for exact amounts
|
|
|
|
return output
|
|
}
|
|
|
|
// =============================================================================
|
|
// Batch Route Optimization
|
|
// =============================================================================
|
|
|
|
// BatchRoute processes multiple route optimizations on GPU.
|
|
func (a *Accelerator) BatchRoute(inputs []RouteInput) ([]RouteOutput, error) {
|
|
n := len(inputs)
|
|
if n == 0 {
|
|
return nil, nil
|
|
}
|
|
if n > a.config.MaxBatchSize {
|
|
return nil, ErrBatchTooLarge
|
|
}
|
|
|
|
if n < a.config.BatchThreshold || a.backend == BackendCPU {
|
|
return a.batchRouteCPU(inputs)
|
|
}
|
|
|
|
switch a.backend {
|
|
case BackendMetal:
|
|
return a.batchRouteMetal(inputs)
|
|
case BackendCUDA:
|
|
return a.batchRouteCUDA(inputs)
|
|
default:
|
|
return a.batchRouteCPU(inputs)
|
|
}
|
|
}
|
|
|
|
// batchRouteCPU processes route optimizations on CPU.
|
|
func (a *Accelerator) batchRouteCPU(inputs []RouteInput) ([]RouteOutput, error) {
|
|
n := len(inputs)
|
|
outputs := make([]RouteOutput, n)
|
|
|
|
var wg sync.WaitGroup
|
|
chunkSize := 32 // Fewer goroutines for more complex ops
|
|
|
|
for i := 0; i < n; i += chunkSize {
|
|
end := i + chunkSize
|
|
if end > n {
|
|
end = n
|
|
}
|
|
wg.Add(1)
|
|
go func(start, stop int) {
|
|
defer wg.Done()
|
|
for j := start; j < stop; j++ {
|
|
outputs[j] = a.executeRouteCPU(&inputs[j])
|
|
}
|
|
}(i, end)
|
|
}
|
|
|
|
wg.Wait()
|
|
atomic.AddUint64(&a.totalRoutes, uint64(n))
|
|
return outputs, nil
|
|
}
|
|
|
|
// executeRouteCPU executes a single route optimization on CPU.
|
|
func (a *Accelerator) executeRouteCPU(input *RouteInput) RouteOutput {
|
|
output := RouteOutput{Success: true}
|
|
|
|
if input.NumHops == 0 || len(input.SqrtPrices) == 0 {
|
|
output.Success = false
|
|
return output
|
|
}
|
|
|
|
// Simulate multi-hop swap
|
|
currentAmount := input.AmountIn.ToBigInt()
|
|
totalImpact := uint32(0)
|
|
|
|
for i := uint8(0); i < input.NumHops && i < uint8(len(input.SqrtPrices)); i++ {
|
|
liquidity := input.Liquidities[i].ToBigInt()
|
|
fee := input.Fees[i]
|
|
|
|
if liquidity.Sign() == 0 {
|
|
output.Success = false
|
|
return output
|
|
}
|
|
|
|
// Apply swap math for this hop
|
|
amountAfterFee := a.applyFee(currentAmount, fee)
|
|
numerator := new(big.Int).Mul(amountAfterFee, liquidity)
|
|
denominator := new(big.Int).Add(liquidity, amountAfterFee)
|
|
currentAmount = numerator.Div(numerator, denominator)
|
|
|
|
// Accumulate price impact (simplified)
|
|
impact := a.calculatePriceImpact(amountAfterFee, liquidity)
|
|
totalImpact += impact
|
|
}
|
|
|
|
output.AmountOut.FromBigInt(currentAmount)
|
|
output.PriceImpact = totalImpact
|
|
output.GasEstimate = uint64(input.NumHops) * 30000 // ~30k gas per hop
|
|
|
|
return output
|
|
}
|
|
|
|
// calculatePriceImpact estimates price impact in basis points.
|
|
func (a *Accelerator) calculatePriceImpact(amount, liquidity *big.Int) uint32 {
|
|
if liquidity.Sign() == 0 {
|
|
return 10000 // 100% impact
|
|
}
|
|
// impact = amount * 10000 / liquidity
|
|
impact := new(big.Int).Mul(amount, big.NewInt(10000))
|
|
impact.Div(impact, liquidity)
|
|
if impact.Cmp(big.NewInt(10000)) > 0 {
|
|
return 10000
|
|
}
|
|
return uint32(impact.Uint64())
|
|
}
|
|
|
|
// =============================================================================
|
|
// Backend Initialization (Stubs - Real impl uses CGO)
|
|
// =============================================================================
|
|
|
|
func (a *Accelerator) initMetal() error {
|
|
// In production, this would:
|
|
// 1. Get default Metal device
|
|
// 2. Create command queue
|
|
// 3. Load compiled Metal library with DEX kernels
|
|
// For now, return nil to indicate Metal is available on macOS
|
|
if runtime.GOOS != "darwin" {
|
|
return ErrGPUNotAvailable
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (a *Accelerator) closeMetal() error {
|
|
return nil
|
|
}
|
|
|
|
func (a *Accelerator) initCUDA() error {
|
|
return ErrGPUNotAvailable
|
|
}
|
|
|
|
func (a *Accelerator) closeCUDA() error {
|
|
return nil
|
|
}
|
|
|
|
// =============================================================================
|
|
// Metal Dispatch (Stubs - Real impl uses CGO)
|
|
// =============================================================================
|
|
|
|
func (a *Accelerator) batchSwapMetal(inputs []SwapInput) ([]SwapOutput, error) {
|
|
// In production, this would:
|
|
// 1. Copy inputs to GPU buffer
|
|
// 2. Dispatch compute kernel
|
|
// 3. Wait for completion
|
|
// 4. Copy results back
|
|
// For now, use CPU fallback
|
|
return a.batchSwapCPU(inputs)
|
|
}
|
|
|
|
func (a *Accelerator) batchLiquidityMetal(inputs []LiquidityInput) ([]LiquidityOutput, error) {
|
|
return a.batchLiquidityCPU(inputs)
|
|
}
|
|
|
|
func (a *Accelerator) batchRouteMetal(inputs []RouteInput) ([]RouteOutput, error) {
|
|
return a.batchRouteCPU(inputs)
|
|
}
|
|
|
|
// =============================================================================
|
|
// CUDA Dispatch (Stubs)
|
|
// =============================================================================
|
|
|
|
func (a *Accelerator) batchSwapCUDA(inputs []SwapInput) ([]SwapOutput, error) {
|
|
return a.batchSwapCPU(inputs)
|
|
}
|
|
|
|
func (a *Accelerator) batchLiquidityCUDA(inputs []LiquidityInput) ([]LiquidityOutput, error) {
|
|
return a.batchLiquidityCPU(inputs)
|
|
}
|
|
|
|
func (a *Accelerator) batchRouteCUDA(inputs []RouteInput) ([]RouteOutput, error) {
|
|
return a.batchRouteCPU(inputs)
|
|
}
|
|
|
|
// =============================================================================
|
|
// Utility: Convert from DEX types
|
|
// =============================================================================
|
|
|
|
// SwapInputFromDEX converts pool_manager SwapParams to GPU-friendly format.
|
|
func SwapInputFromDEX(
|
|
poolID [32]byte,
|
|
sqrtPriceX96 *big.Int,
|
|
liquidity *big.Int,
|
|
tick int32,
|
|
zeroForOne bool,
|
|
amountSpecified *big.Int,
|
|
sqrtPriceLimit *big.Int,
|
|
fee uint32,
|
|
) SwapInput {
|
|
input := SwapInput{
|
|
PoolID: poolID,
|
|
Tick: tick,
|
|
ZeroForOne: zeroForOne,
|
|
ExactInput: amountSpecified.Sign() > 0,
|
|
FeePips: fee,
|
|
}
|
|
input.SqrtPriceX96.FromBigInt(sqrtPriceX96)
|
|
input.Liquidity.FromBigInt(liquidity)
|
|
input.Amount.FromBigInt(new(big.Int).Abs(amountSpecified))
|
|
input.SqrtPriceLimit.FromBigInt(sqrtPriceLimit)
|
|
return input
|
|
}
|
|
|
|
// BalanceDeltaFromOutput converts GPU output to DEX BalanceDelta.
|
|
func BalanceDeltaFromOutput(output *SwapOutput) (amount0, amount1 *big.Int) {
|
|
return output.Amount0Delta.ToBigInt(), output.Amount1Delta.ToBigInt()
|
|
}
|
|
|
|
// =============================================================================
|
|
// Global Accelerator Instance
|
|
// =============================================================================
|
|
|
|
var (
|
|
globalAccelerator *Accelerator
|
|
globalAccMu sync.Mutex
|
|
)
|
|
|
|
// Global returns the global GPU accelerator, initializing if needed.
|
|
func Global() *Accelerator {
|
|
globalAccMu.Lock()
|
|
defer globalAccMu.Unlock()
|
|
|
|
if globalAccelerator == nil {
|
|
var err error
|
|
globalAccelerator, err = NewAccelerator(DefaultConfig())
|
|
if err != nil {
|
|
// Return a CPU-only accelerator on error
|
|
globalAccelerator = &Accelerator{
|
|
config: DefaultConfig(),
|
|
backend: BackendCPU,
|
|
}
|
|
}
|
|
}
|
|
return globalAccelerator
|
|
}
|
|
|
|
// SetGlobal sets the global accelerator (for testing).
|
|
func SetGlobal(acc *Accelerator) {
|
|
globalAccMu.Lock()
|
|
defer globalAccMu.Unlock()
|
|
globalAccelerator = acc
|
|
}
|
|
|
|
// =============================================================================
|
|
// Tick Math Acceleration
|
|
// =============================================================================
|
|
|
|
// TickMath provides GPU-accelerated tick math operations.
|
|
type TickMath struct {
|
|
acc *Accelerator
|
|
}
|
|
|
|
// NewTickMath creates a new TickMath with the given accelerator.
|
|
func NewTickMath(acc *Accelerator) *TickMath {
|
|
return &TickMath{acc: acc}
|
|
}
|
|
|
|
// BatchTickToSqrtPrice converts multiple ticks to sqrt prices.
|
|
func (tm *TickMath) BatchTickToSqrtPrice(ticks []int32) ([]*big.Int, error) {
|
|
n := len(ticks)
|
|
results := make([]*big.Int, n)
|
|
|
|
// Q96 = 2^96
|
|
q96 := new(big.Int).Lsh(big.NewInt(1), 96)
|
|
|
|
for i := 0; i < n; i++ {
|
|
tick := ticks[i]
|
|
if tick == 0 {
|
|
results[i] = new(big.Int).Set(q96)
|
|
continue
|
|
}
|
|
|
|
// sqrt(1.0001^tick) * 2^96
|
|
// Use approximation: 1.0001^tick ~ e^(tick * ln(1.0001))
|
|
// ln(1.0001) ~ 0.0000999950003
|
|
absTick := tick
|
|
if tick < 0 {
|
|
absTick = -tick
|
|
}
|
|
|
|
// Start with Q128 precision
|
|
ratio := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
|
|
// Magic numbers for sqrt(1.0001^(2^i))
|
|
magics := []uint64{
|
|
0xfff97263e137, // 2^0
|
|
0xfff2e50f626c, // 2^1
|
|
0xffe5caca7e10, // 2^2
|
|
0xffcb9a979342, // 2^3
|
|
0xff97383c7e70, // 2^4
|
|
}
|
|
|
|
for bit, magic := range magics {
|
|
if int(absTick)&(1<<bit) != 0 {
|
|
ratio.Mul(ratio, new(big.Int).SetUint64(magic))
|
|
ratio.Rsh(ratio, 48)
|
|
}
|
|
}
|
|
|
|
// Invert for negative ticks
|
|
if tick < 0 {
|
|
maxU256 := new(big.Int).Lsh(big.NewInt(1), 256)
|
|
ratio = maxU256.Div(maxU256, ratio)
|
|
}
|
|
|
|
// Convert from Q128 to Q96
|
|
results[i] = ratio.Rsh(ratio, 32)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// BatchSqrtPriceToTick converts multiple sqrt prices to ticks.
|
|
func (tm *TickMath) BatchSqrtPriceToTick(sqrtPrices []*big.Int) ([]int32, error) {
|
|
n := len(sqrtPrices)
|
|
results := make([]int32, n)
|
|
|
|
q96 := new(big.Int).Lsh(big.NewInt(1), 96)
|
|
minSqrt := new(big.Int).SetUint64(4295128739)
|
|
maxSqrt, _ := new(big.Int).SetString("1461446703485210103287273052203988822378723970342", 10)
|
|
|
|
const minTick int32 = -887272
|
|
const maxTick int32 = 887272
|
|
|
|
for i := 0; i < n; i++ {
|
|
sqrtPrice := sqrtPrices[i]
|
|
if sqrtPrice == nil || sqrtPrice.Sign() <= 0 {
|
|
results[i] = 0
|
|
continue
|
|
}
|
|
|
|
if sqrtPrice.Cmp(minSqrt) <= 0 {
|
|
results[i] = minTick
|
|
continue
|
|
}
|
|
if sqrtPrice.Cmp(maxSqrt) >= 0 {
|
|
results[i] = maxTick
|
|
continue
|
|
}
|
|
|
|
// Binary search for tick
|
|
low := minTick
|
|
high := maxTick
|
|
|
|
for low < high {
|
|
mid := low + (high-low+1)/2
|
|
midPrice := tm.tickToSqrtPriceSingle(mid, q96)
|
|
|
|
if midPrice.Cmp(sqrtPrice) <= 0 {
|
|
low = mid
|
|
} else {
|
|
high = mid - 1
|
|
}
|
|
}
|
|
|
|
results[i] = low
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
func (tm *TickMath) tickToSqrtPriceSingle(tick int32, q96 *big.Int) *big.Int {
|
|
if tick == 0 {
|
|
return new(big.Int).Set(q96)
|
|
}
|
|
|
|
absTick := tick
|
|
if tick < 0 {
|
|
absTick = -tick
|
|
}
|
|
|
|
ratio := new(big.Int).Lsh(big.NewInt(1), 128)
|
|
|
|
magics := []uint64{
|
|
0xfff97263e137,
|
|
0xfff2e50f626c,
|
|
0xffe5caca7e10,
|
|
0xffcb9a979342,
|
|
0xff97383c7e70,
|
|
}
|
|
|
|
for bit, magic := range magics {
|
|
if int(absTick)&(1<<bit) != 0 {
|
|
ratio.Mul(ratio, new(big.Int).SetUint64(magic))
|
|
ratio.Rsh(ratio, 48)
|
|
}
|
|
}
|
|
|
|
if tick < 0 {
|
|
maxU256 := new(big.Int).Lsh(big.NewInt(1), 256)
|
|
ratio = maxU256.Div(maxU256, ratio)
|
|
}
|
|
|
|
return ratio.Rsh(ratio, 32)
|
|
}
|
|
|
|
// Convenience type aliases for external use
|
|
type (
|
|
PoolID = [32]byte
|
|
Address = common.Address
|
|
)
|