mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
- pkg/lx/signed_order{,_cpu,_gpu,_nogpu}.go: SignedOrder with SigningHash
and BatchVerifyOrders cgo bridge
- pkg/lx/signed_order_metal_anchor_darwin.{c,go}: keep linker from
dropping libsecp256k1_metal.a from the static archive
- pkg/lx/amm{,_gpu,_nogpu}.go: ReservePair, ConstantProductOut,
BatchEvalConstantProduct via Metal kernel
- pkg/lx/{signed_order,amm_gpu}_test.go: tests
- pkg/lx/orderbook.go, test/benchmark/orderbook_bench_test.go: misc
End-to-end through cgo (Apple M1 Max):
BatchVerifyOrders CPU: 27.91 verifies/sec
BatchVerifyOrders Metal: 87,229 verifies/sec
90 lines
2.6 KiB
Go
90 lines
2.6 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package lx
|
|
|
|
// ReservePair is the (reserve_x, reserve_y) tuple for a single pool. The
|
|
// memory layout MUST match the C `LuxAmmReservePair` in
|
|
// luxcpp/dex/gpu/metal/amm_xyk_driver.h and the Metal `ReservePair` in
|
|
// amm_xyk.metal — the same buffer is reinterpreted on all three sides.
|
|
type ReservePair struct {
|
|
ReserveX uint64
|
|
ReserveY uint64
|
|
}
|
|
|
|
// ConstantProductOut is the canonical CPU oracle for the xy=k AMM curve:
|
|
//
|
|
// out = (amount * reserve_y) / (reserve_x + amount)
|
|
//
|
|
// Computed in 128-bit so amount*reserve_y can't silently overflow. Returns
|
|
// 0 for the degenerate (rx + amount == 0) case so it lines up with the
|
|
// kernel's behaviour.
|
|
//
|
|
// This is the byte-equality reference for the GPU path.
|
|
func ConstantProductOut(rx, ry, amount uint64) uint64 {
|
|
denom := rx + amount
|
|
if denom == 0 {
|
|
return 0
|
|
}
|
|
return mulDiv64(amount, ry, denom)
|
|
}
|
|
|
|
// mulDiv64 = (a * b) / d using 128-bit intermediates so a*b can exceed 2^64
|
|
// without truncating. The result is undefined (and the caller's bug) if the
|
|
// quotient itself doesn't fit in 64 bits.
|
|
func mulDiv64(a, b, d uint64) uint64 {
|
|
// Split into 32-bit halves; each partial product fits in 64 bits.
|
|
aLo, aHi := a&0xFFFFFFFF, a>>32
|
|
bLo, bHi := b&0xFFFFFFFF, b>>32
|
|
|
|
ll := aLo * bLo
|
|
lh := aLo * bHi
|
|
hl := aHi * bLo
|
|
hh := aHi * bHi
|
|
|
|
mid := (ll >> 32) + (lh & 0xFFFFFFFF) + (hl & 0xFFFFFFFF)
|
|
lo := (ll & 0xFFFFFFFF) | (mid << 32)
|
|
hi := hh + (lh >> 32) + (hl >> 32) + (mid >> 32)
|
|
|
|
return divU128byU64(hi, lo, d)
|
|
}
|
|
|
|
// divU128byU64 — straight shift-subtract long division. Same algorithm as the
|
|
// Metal kernel so a per-bit divergence between paths is impossible.
|
|
func divU128byU64(hi, lo, d uint64) uint64 {
|
|
if hi == 0 {
|
|
return lo / d
|
|
}
|
|
var quotient, remainder uint64
|
|
for i := 127; i >= 0; i-- {
|
|
remainder <<= 1
|
|
var bit uint64
|
|
if i >= 64 {
|
|
bit = (hi >> uint(i-64)) & 1
|
|
} else {
|
|
bit = (lo >> uint(i)) & 1
|
|
}
|
|
remainder |= bit
|
|
if remainder >= d {
|
|
remainder -= d
|
|
if i < 64 {
|
|
quotient |= uint64(1) << uint(i)
|
|
}
|
|
}
|
|
}
|
|
return quotient
|
|
}
|
|
|
|
// BatchEvalConstantProductCPU is the per-pool CPU oracle, exposed for tests
|
|
// and as the fallback when the GPU build path isn't available.
|
|
func BatchEvalConstantProductCPU(reserves []ReservePair, amounts []uint64) ([]uint64, error) {
|
|
if len(reserves) != len(amounts) {
|
|
return nil, ErrInvalidOrder
|
|
}
|
|
out := make([]uint64, len(reserves))
|
|
for i := range reserves {
|
|
out[i] = ConstantProductOut(reserves[i].ReserveX, reserves[i].ReserveY, amounts[i])
|
|
}
|
|
return out, nil
|
|
}
|