mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
AMM unification (one Go entry point, per-platform impls):
- amm_gpu.go (cgo) — BatchEvalConstantProduct dispatcher with
LUX_GPU_DISABLE kill switch + fallback metrics
- amm_gpu_metal.go (cgo && darwin) — Metal driver via lux-dex-amm-metal
- amm_gpu_cuda.go (cgo && linux) — CUDA driver via lux-dex-amm-cuda
- amm_gpu_stub.go (cgo && other) — errAMMGPUUnsupported sentinel
- amm_nogpu.go (!cgo) — pure CPU path
CLOB CUDA wire (the LP-108 missing piece — Backend enum can come back):
- orderbook_cuda_types.go — Go mirrors of DEXOrder (64B), DEXTrade (80B),
DEXPrice; assertDEXABI() in init() panics
on any size/offset drift vs the C header
- orderbook_match_cpu.go — MatchOrderCPU canonical Go oracle
- orderbook_cuda.go (cgo && linux) — gpuMatchOrder via
lux-dex-clob-cuda → libdex_clob_cuda.a →
luxcpp/cuda/kernels/gpu/dex_swap.cu
- orderbook_cuda_stub.go (other) — errCLOBGPUUnsupported sentinel
- orderbook_gpu.go — MatchOrderGPU dispatcher with strict fallback
policy + backend.RecordFallback observability
- orderbook_cuda_parity_test.go — 8 randomized parity subtests using
DEXTrade.EqualFields / DEXOrder.EqualFields
(NOT struct ==) so padding bytes can't
cause false negatives
Padding normalization: _ blank pads renamed to _pad (addressable);
ClearPadding() methods on DEXOrder/DEXTrade; CPU oracle + CUDA host
driver both zero pad bytes for deterministic structural comparison.
Branding: all `Lux*`/`LUX_*` types in the C ABI mirror dropped (DEXOrder,
DEXTrade, DEXPrice, AMMReservePair, DEX_SIDE_BID, etc.).
Temporary replace github.com/luxfi/crypto => ../../lux/crypto pending
publication of crypto with backend.GPUDisabled / RecordFallback /
FallbackReason. Remove the replace once crypto is tagged.
117 lines
3.1 KiB
Go
117 lines
3.1 KiB
Go
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package lx
|
|
|
|
// MatchOrderCPU is the canonical pure-Go oracle for the CUDA
|
|
// match_order_kernel in luxcpp/cuda/kernels/gpu/dex_swap.cu. Same
|
|
// semantics, byte-equal trade output (after both paths clear padding):
|
|
// every CUDA dispatch is required to produce the same trade list and
|
|
// remaining quantity for the same (incoming, book, indices) inputs.
|
|
//
|
|
// Semantics match the kernel:
|
|
// - one incoming order (taker) matched against book orders identified
|
|
// by `bookIndices`
|
|
// - a book order matches if its side is opposite incoming's AND the
|
|
// prices cross (bid >= ask)
|
|
// - book orders with status FILLED or CANCELLED are skipped
|
|
// - quantities filled at the maker's price; remaining is decremented;
|
|
// status flips to PARTIAL / FILLED accordingly
|
|
// - one DEXTrade emitted per fill, with maker = book order,
|
|
// taker = incoming, trade_id = tradeIDBase + i
|
|
//
|
|
// Returns: trades emitted, remaining quantity on the incoming order.
|
|
// Book orders in `book` may be mutated in place to reflect new
|
|
// Remaining / Status values, matching the kernel's atomic updates.
|
|
func MatchOrderCPU(
|
|
incoming *DEXOrder,
|
|
book []DEXOrder,
|
|
bookIndices []uint32,
|
|
tradeIDBase uint64,
|
|
timestamp uint64,
|
|
) (trades []DEXTrade, remaining uint64) {
|
|
remaining = incoming.Quantity
|
|
if incoming.Quantity == 0 || len(bookIndices) == 0 {
|
|
return nil, remaining
|
|
}
|
|
|
|
trades = make([]DEXTrade, 0, len(bookIndices))
|
|
|
|
for _, idx := range bookIndices {
|
|
if remaining == 0 {
|
|
break
|
|
}
|
|
if int(idx) >= len(book) {
|
|
continue
|
|
}
|
|
bo := &book[idx]
|
|
|
|
if !sideMatches(incoming.Side, bo.Side) {
|
|
continue
|
|
}
|
|
if !pricesCross(incoming.Side, incoming.Price, bo.Price) {
|
|
continue
|
|
}
|
|
if bo.Status != DEXStatusOpen && bo.Status != DEXStatusPartial {
|
|
continue
|
|
}
|
|
if bo.Remaining == 0 {
|
|
continue
|
|
}
|
|
|
|
fill := remaining
|
|
if bo.Remaining < fill {
|
|
fill = bo.Remaining
|
|
}
|
|
if fill == 0 {
|
|
continue
|
|
}
|
|
|
|
bo.Remaining -= fill
|
|
if bo.Remaining == 0 {
|
|
bo.Status = DEXStatusFilled
|
|
} else {
|
|
bo.Status = DEXStatusPartial
|
|
}
|
|
bo.ClearPadding()
|
|
|
|
trade := DEXTrade{
|
|
TradeID: tradeIDBase + uint64(len(trades)),
|
|
MakerOrderID: bo.OrderID,
|
|
TakerOrderID: incoming.OrderID,
|
|
MakerUserID: bo.UserID,
|
|
TakerUserID: incoming.UserID,
|
|
Price: bo.Price,
|
|
Quantity: fill,
|
|
Timestamp: timestamp,
|
|
TakerSide: incoming.Side,
|
|
}
|
|
trade.ClearPadding()
|
|
trades = append(trades, trade)
|
|
|
|
remaining -= fill
|
|
}
|
|
|
|
return trades, remaining
|
|
}
|
|
|
|
// sideMatches: taker's side must be opposite the maker's.
|
|
func sideMatches(takerSide, makerSide uint8) bool {
|
|
return (takerSide == DEXSideBid && makerSide == DEXSideAsk) ||
|
|
(takerSide == DEXSideAsk && makerSide == DEXSideBid)
|
|
}
|
|
|
|
// pricesCross: bid >= ask in fixed-point Q64.64.
|
|
func pricesCross(takerSide uint8, takerPx, makerPx DEXPrice) bool {
|
|
var bid, ask DEXPrice
|
|
if takerSide == DEXSideBid {
|
|
bid, ask = takerPx, makerPx
|
|
} else {
|
|
bid, ask = makerPx, takerPx
|
|
}
|
|
if bid.Integer != ask.Integer {
|
|
return bid.Integer > ask.Integer
|
|
}
|
|
return bid.Fraction >= ask.Fraction
|
|
}
|