Files
precompile/dex/gpu/gpu.go
T

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
)