mirror of
https://github.com/luxfi/dex.git
synced 2026-09-24 00:26:31 +00:00
dex/pkg/lx: AMM cross-platform + CLOB CUDA + ABI mirror asserts + parity
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.
This commit is contained in:
@@ -5,11 +5,11 @@ go 1.26.3
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||
github.com/luxfi/accel v1.0.7
|
||||
github.com/luxfi/crypto v1.19.0
|
||||
github.com/luxfi/accel v1.1.4
|
||||
github.com/luxfi/crypto v1.19.3
|
||||
github.com/luxfi/czmq/v4 v4.2.2
|
||||
github.com/luxfi/database v1.17.44
|
||||
github.com/luxfi/geth v1.16.85
|
||||
github.com/luxfi/geth v1.16.98
|
||||
github.com/luxfi/log v1.4.1
|
||||
github.com/luxfi/pq v1.0.3
|
||||
github.com/luxfi/rpc v1.0.2
|
||||
@@ -22,17 +22,14 @@ require (
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260311194731-d5b7577c683d // indirect
|
||||
github.com/bits-and-blooms/bitset v1.24.4 // indirect
|
||||
github.com/cloudflare/circl v1.6.3 // indirect
|
||||
github.com/consensys/gnark-crypto v0.20.1 // indirect
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0 // indirect
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/deckarep/golang-set/v2 v2.8.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.1 // indirect
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.7 // indirect
|
||||
github.com/ethereum/go-verkle v0.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/gorilla/rpc v1.2.1 // indirect
|
||||
github.com/holiman/uint256 v1.3.2 // indirect
|
||||
@@ -41,8 +38,9 @@ require (
|
||||
github.com/luxfi/compress v0.0.5 // indirect
|
||||
github.com/luxfi/concurrent v0.0.3 // indirect
|
||||
github.com/luxfi/container v0.0.4 // indirect
|
||||
github.com/luxfi/crypto/ipa v1.2.4 // indirect
|
||||
github.com/luxfi/ids v1.2.9 // indirect
|
||||
github.com/luxfi/math v1.2.4 // indirect
|
||||
github.com/luxfi/math v1.4.0 // indirect
|
||||
github.com/luxfi/math/big v0.1.0 // indirect
|
||||
github.com/luxfi/metric v1.5.1 // indirect
|
||||
github.com/luxfi/mock v0.1.1 // indirect
|
||||
@@ -69,3 +67,9 @@ require (
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
// Temporary while github.com/luxfi/crypto is being tagged with the new
|
||||
// backend.GPUDisabled / backend.RecordFallback / backend.FallbackReason
|
||||
// surface. Remove this replace once crypto >= the next tag is published
|
||||
// and bumped in `require` above.
|
||||
replace github.com/luxfi/crypto => ../../lux/crypto
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
filippo.io/hpke v0.4.0 h1:p575VVQ6ted4pL+it6M00V/f2qTZITO0zgmdKCkd5+A=
|
||||
filippo.io/hpke v0.4.0/go.mod h1:EmAN849/P3qdeK+PCMkDpDm83vRHM5cDipBJ8xbQLVY=
|
||||
github.com/ALTree/bigfloat v0.2.0 h1:AwNzawrpFuw55/YDVlcPw0F0cmmXrmngBHhVrvdXPvM=
|
||||
github.com/ALTree/bigfloat v0.2.0/go.mod h1:+NaH2gLeY6RPBPPQf4aRotPPStg+eXc8f9ZaE4vRfD4=
|
||||
github.com/DataDog/zstd v1.5.7 h1:ybO8RBeh29qrxIhCA9E8gKY6xfONU9T6G6aP9DTKfLE=
|
||||
github.com/DataDog/zstd v1.5.7/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260311194731-d5b7577c683d h1:EA+kZ8mxGb1W/ewiIBMzb/1gg5BiW1Fvr3r4qCUBJEg=
|
||||
github.com/ProjectZKM/Ziren/crates/go-runtime/zkvm_runtime v0.0.0-20260311194731-d5b7577c683d/go.mod h1:ioLG6R+5bUSO1oeGSDxOV3FADARuMoytZCSX6MEMQkI=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bits-and-blooms/bitset v1.24.4 h1:95H15Og1clikBrKr/DuzMXkQzECs1M6hhoGXLwLQOZE=
|
||||
@@ -30,8 +32,6 @@ github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0 h1:FYRiJMJG2iv+2Dy3fi14SVGjcPteZ5HAAUe4YWlJygc=
|
||||
github.com/crate-crypto/go-eth-kzg v1.5.0/go.mod h1:J9/u5sWfznSObptgfa92Jq8rTswn6ahQWEuiLHOjCUI=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA=
|
||||
@@ -52,12 +52,12 @@ github.com/ethereum/c-kzg-4844/v2 v2.1.7 h1:aat3CuITdDbPC6pmEGRT0zJ5eOxzrZj8TJT5
|
||||
github.com/ethereum/c-kzg-4844/v2 v2.1.7/go.mod h1:8HMkUZ5JRv4hpw/XUrYWSQNAUzhHMg2UDb/U+5m+XNw=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab h1:rvv6MJhy07IMfEKuARQ9TKojGqLVNxQajaXEp/BoqSk=
|
||||
github.com/ethereum/go-bigmodexpfix v0.0.0-20250911101455-f9e208c548ab/go.mod h1:IuLm4IsPipXKF7CW5Lzf68PIbZ5yl7FFd74l/E0o9A8=
|
||||
github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8=
|
||||
github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk=
|
||||
github.com/ferranbt/fastssz v1.0.0 h1:9EXXYsracSqQRBQiHeaVsG/KQeYblPf40hsQPb9Dzk8=
|
||||
github.com/ferranbt/fastssz v1.0.0/go.mod h1:Ea3+oeoRGGLGm5shYAeDgu6PGUlcvQhE2fILyD9+tGg=
|
||||
github.com/getsentry/sentry-go v0.44.1 h1:/cPtrA5qB7uMRrhgSn9TYtcEF36auGP3Y6+ThvD/yaI=
|
||||
github.com/getsentry/sentry-go v0.44.1/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
|
||||
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
|
||||
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
@@ -101,6 +101,8 @@ github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBF
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
|
||||
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@@ -109,8 +111,10 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/luxfi/accel v1.0.7 h1:ksHieAp50umwqxqgyHk9WiOmXM54kia3IEDb5H7FsM8=
|
||||
github.com/luxfi/accel v1.0.7/go.mod h1:iZD3oxffiMEIT/KvzD8bgwC/cBn4AYlMW3QJpbRa4RE=
|
||||
github.com/luxfi/accel v1.1.4 h1:UOvS/00vG6WByf2P1tGYRkoBcsUkFuCgw7o2xU03OoE=
|
||||
github.com/luxfi/accel v1.1.4/go.mod h1:K00BcnLzEYMPHwCFq8Tf/450ApmTs9xBVvYOnofJCkc=
|
||||
github.com/luxfi/age v1.4.0 h1:tU5q65RQSQdaVq64Z/DVUhiPQMoMPQT6pr41LsvG0AQ=
|
||||
github.com/luxfi/age v1.4.0/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
|
||||
github.com/luxfi/atomic v1.0.0 h1:xUV60MuzRvXngaQ1sM0yVC2v4TRoLlUGkkH7M9PS4yw=
|
||||
github.com/luxfi/atomic v1.0.0/go.mod h1:0G2mTlQ6TXWHICUHrUUPu1/qAiIyR4gSZ2tva9ci/bI=
|
||||
github.com/luxfi/cache v1.2.1 h1:kAzOS55/hmYeNKR+0HAKv4ma48Y6JjkI8UQeqdZ8bfI=
|
||||
@@ -121,28 +125,30 @@ github.com/luxfi/compress v0.0.5 h1:4tEUHw5MK1bu5UOjfYCt4OKMiH7yykIgmGPRA/BfJTM=
|
||||
github.com/luxfi/compress v0.0.5/go.mod h1:Cc1yxD2pfzrvpO32W2GDwLKff+CylHEvzZh2Ko8RSIU=
|
||||
github.com/luxfi/concurrent v0.0.3 h1:eJyv1fhaC0jMLMw6+QS774cUmp7GK+ouMgvLCqnC7cc=
|
||||
github.com/luxfi/concurrent v0.0.3/go.mod h1:Aj/FR5NpM0cB2P4Nt3+tz9+dV6V+LUW4HuMgSjwq5hw=
|
||||
github.com/luxfi/consensus v1.22.70 h1:IQlDxkNY2j6W55nYh11WenvdIsZ9g619qo+xS69GA00=
|
||||
github.com/luxfi/consensus v1.22.70/go.mod h1:y6y+bVjvpDLkkTBCCKLcch6R4PRW8EA+lTfr+6sB6F8=
|
||||
github.com/luxfi/consensus v1.22.84 h1:fKw4n7fY+lCWW5HSx/ciGeRSxv4knODQGWh6lBWAveQ=
|
||||
github.com/luxfi/consensus v1.22.84/go.mod h1:y6y+bVjvpDLkkTBCCKLcch6R4PRW8EA+lTfr+6sB6F8=
|
||||
github.com/luxfi/constants v1.4.7 h1:e/Qs+DQP3pugle3Zncq6fZCxKgqqtbyD/z7Gm4ZjsYg=
|
||||
github.com/luxfi/constants v1.4.7/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
|
||||
github.com/luxfi/container v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM=
|
||||
github.com/luxfi/container v0.0.4/go.mod h1:Z3SpmMF5d4t77MM0nHYXURpn+EMVaeu1fhbd/3BGaek=
|
||||
github.com/luxfi/crypto v1.17.55 h1:D3FqoN4a5vAxag8gexR4uEp6NLQk2FyX+XT1FEtBSd4=
|
||||
github.com/luxfi/crypto v1.17.55/go.mod h1:GnAkhQ7HNs3X0Tzx5nOONS3kl0yRmWHbDcRO5ffILsg=
|
||||
github.com/luxfi/crypto v1.19.0 h1:VtH6kvZrCEjCnkHPkhExDU+GJ0ZulYX4rnA+lXJdJHw=
|
||||
github.com/luxfi/crypto v1.19.0/go.mod h1:ee525i8Recbpb0jVTDZYZBr1MmvJ27OITJHZ/nlNMBw=
|
||||
github.com/luxfi/corona v0.4.1 h1:DGMdZMrENl6vmJf9CgiQMD8c5ORSTMymL0z9KgcZstk=
|
||||
github.com/luxfi/corona v0.4.1/go.mod h1:4aD7+ZqnlZ2aVuU/DBQ5aspIagv5ux45LW2sJ4+siY8=
|
||||
github.com/luxfi/crypto/ipa v1.2.4 h1:6xfwhI9/HrcDkF3Ti5/NxsNQIWbwYDJmRSNIHRQ/xfU=
|
||||
github.com/luxfi/crypto/ipa v1.2.4/go.mod h1:43J6f6rcfUMrZt4cQectMOZb6Ps+fAEj8ZTPC3Kk+gE=
|
||||
github.com/luxfi/czmq/v4 v4.2.2 h1:D4QDl99OfGoi8Gk3RI8GZ6pqt+040rolj+h36scSj2M=
|
||||
github.com/luxfi/czmq/v4 v4.2.2/go.mod h1:WFY9ldUbeU900P8bcIRr7Jim23veOhJL5A5zSpTogdU=
|
||||
github.com/luxfi/database v1.17.44 h1:hfiTls7sqbweW+o4iaZqB8P997paC+vpgWmhN6v5MJ0=
|
||||
github.com/luxfi/database v1.17.44/go.mod h1:6Ey5y3I0WNLHbxIlIdFqUuKfBg+b0fAgTA8FgRgQ8zg=
|
||||
github.com/luxfi/geth v1.16.85 h1:gMve+CXGg6zTjHEbsbZayhaPWVHOG29Es7xtCRaG/mQ=
|
||||
github.com/luxfi/geth v1.16.85/go.mod h1:mvGCzgHL6Jed/KTylqUE9wCZHJAHz3Y7T4Fc6OodNZg=
|
||||
github.com/luxfi/geth v1.16.98 h1:w187TtKuGStf3tm2bshuHVKBv2Frjx0lT54kQVXyNHA=
|
||||
github.com/luxfi/geth v1.16.98/go.mod h1:6kEzSExdk9CPQDPXALt6P3HfQqBq7KF1Jrrv9gBpxbU=
|
||||
github.com/luxfi/ids v1.2.9 h1:+yjdhXW99drnd2Zlp1u/p8k3G23W3/1btJQ4ogHawUI=
|
||||
github.com/luxfi/ids v1.2.9/go.mod h1:khJOEdOPxd22yn0jcVrnbX1ADa0GHn5Y74gvCzN5BYc=
|
||||
github.com/luxfi/lattice/v7 v7.1.0 h1:mr3HvN6olNTS2LT/xAW/JBhTqfvpsGmsopDMeR7BSJs=
|
||||
github.com/luxfi/lattice/v7 v7.1.0/go.mod h1:IaaUN+3ysnBG4BA8ILRYG0j80+qtYDP4C5lkaDb2pDE=
|
||||
github.com/luxfi/log v1.4.1 h1:rIfFRodb9jrD/w7KayaUk0Oc+37PaQQdKEEMJCjR8gw=
|
||||
github.com/luxfi/log v1.4.1/go.mod h1:64IE3xRMJcpkQwnPUfJw3pDj7wU0kRS7BZ9wM7R72jk=
|
||||
github.com/luxfi/math v1.2.4 h1:iVz7s0gToCNUU/2cLT8gUa8MLzc0YRp4jBmeXBeKdXk=
|
||||
github.com/luxfi/math v1.2.4/go.mod h1:i+Am9YvFsqDE75ZW8MPE62XCXGukXNiN/7mD9SKxxZY=
|
||||
github.com/luxfi/math v1.4.0 h1:/sb7Grw3hfO+5INWAWdB95jTvCeXg8fSQxsxDzcFtd4=
|
||||
github.com/luxfi/math v1.4.0/go.mod h1:iW0FOCC8qF2mPE+MakG780CAHA83848lb1L04thA1Pg=
|
||||
github.com/luxfi/math/big v0.1.0 h1:Vz4c0RsZVPdIKPsHPgAJChH/R3p15WHRUz7LkLf+NIQ=
|
||||
github.com/luxfi/math/big v0.1.0/go.mod h1:BuxSu22RbO93xBLk5Eam5nldFponoJ73xDFz4uJ3Huk=
|
||||
github.com/luxfi/metric v1.5.1 h1:6tVarXMnNR3Xzud8FYUHjtXabTll3HI/OEqG0tgLAcI=
|
||||
@@ -153,10 +159,8 @@ github.com/luxfi/p2p v1.19.2 h1:uqZq7ofmEDbXlTkv1QThtci01Q+dmDkNmAPeORIyP8E=
|
||||
github.com/luxfi/p2p v1.19.2/go.mod h1:tI9Bt1R0ouvVtJvXG4e20GlGeV4AR230k4mFF9Vglzk=
|
||||
github.com/luxfi/pq v1.0.3 h1:pFlQm1+5FuKTDUh2y/23bXWkN4I2Rc5iuxJypwDFFMs=
|
||||
github.com/luxfi/pq v1.0.3/go.mod h1:8bppZcRElfrVt0n3nYCZW3iX1TvhvzNbdjNdK1irgIE=
|
||||
github.com/luxfi/precompile v0.5.6 h1:d6qHVpRV1Nct97BxRpLuBQvlYmlg53sleGtq+iRWtyc=
|
||||
github.com/luxfi/precompile v0.5.6/go.mod h1:Jz14VhQrU90mVgm/nTStQHdq8cLSOBHMF9UpJQjPFec=
|
||||
github.com/luxfi/rpc v1.0.0 h1:G8o89gT+CT3SOj08A5FS5G92fovKrWxs941FmUz28CA=
|
||||
github.com/luxfi/rpc v1.0.0/go.mod h1:dhxzM5rFqNpEOOHB7+8YBQ9d/V4haa6/id9AQTIIJvQ=
|
||||
github.com/luxfi/precompile v0.5.27 h1:lEF5gXY8ny5xXp8Qg8PK0Y2RQlaRNOZeDU+wP6Sj24M=
|
||||
github.com/luxfi/precompile v0.5.27/go.mod h1:boKRJeoa4XEhCoylRnPI/0IGfBgm5bZnFNI9llDktWo=
|
||||
github.com/luxfi/rpc v1.0.2 h1:NLRcOYRW+io0d1d33RMkgOZea8nlhK09MbPgCXcU5wU=
|
||||
github.com/luxfi/rpc v1.0.2/go.mod h1:pgiHwMWgOuxYYIa0vsUBvrBI+Op6bhZ39guM9vtMUcE=
|
||||
github.com/luxfi/runtime v1.0.1 h1:cii3OsRiVSIl9jzfWFM6++62T1r6ZSGP+/3F0Ezhpqw=
|
||||
@@ -173,18 +177,28 @@ github.com/luxfi/vm v1.0.40 h1:kA0V/9p1VdC88EjgbljWb8t+fl9qz5bCyF3IxnV20yg=
|
||||
github.com/luxfi/vm v1.0.40/go.mod h1:Y5WUKhT76PR6HnbUykHBWI2f5sjssrm5BL6/o7b6IfQ=
|
||||
github.com/luxfi/warp v1.18.5 h1:yXFCT+lnvzJHs8nVvfUCQ9wNvhtgbDGaXJkJeiwgKf4=
|
||||
github.com/luxfi/warp v1.18.5/go.mod h1:SFyC529HDvbP/TWRAdYQSyJUliMa5JKFRtBrTLEElp4=
|
||||
github.com/luxfi/zapdb v1.8.0 h1:E9hXMW1MTuS6kmDVs07j62gIq5qI5gZOdyzVfiBc1wg=
|
||||
github.com/luxfi/zapdb v1.8.0/go.mod h1:Qukh3hDRD0MnxA6z+a28JTnXhN85AiLLgp6TYr4QAMc=
|
||||
github.com/luxfi/zapdb/v4 v4.9.3 h1:KV1DrbZqtZsZvrWyPw5D8WHR0DxzqW2jHjqx6obxgoM=
|
||||
github.com/luxfi/zapdb/v4 v4.9.3/go.mod h1:BrpLaDeYy+smON4ASDPCFKWrQ58CXhy3WNEPGna7vuU=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
|
||||
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.100 h1:ShkWi8Tyj9RtU57OQB2HIXKz4bFgtVib0bbT1sbtLI8=
|
||||
github.com/minio/minio-go/v7 v7.0.100/go.mod h1:EtGNKtlX20iL2yaYnxEigaIvj0G0GwSDnifnG8ClIdw=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/mitchellh/pointerstructure v1.2.1 h1:ZhBBeX8tSlRpu/FFhXH4RC4OJzFlqsQhoHZAz4x7TIw=
|
||||
github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
|
||||
github.com/montanaflynn/stats v0.9.0 h1:tsBJ0RXwph9BmAuFoCmqGv6e8xa0MENQ8m0ptKq29mQ=
|
||||
github.com/montanaflynn/stats v0.9.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
@@ -195,6 +209,8 @@ github.com/nats-io/nkeys v0.4.15 h1:JACV5jRVO9V856KOapQ7x+EY8Jo3qw1vJt/9Jpwzkk4=
|
||||
github.com/nats-io/nkeys v0.4.15/go.mod h1:CpMchTXC9fxA5zrMo4KpySxNjiDVvr8ANOSZdiNfUrs=
|
||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
|
||||
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
|
||||
github.com/pion/dtls/v2 v2.2.12 h1:KP7H5/c1EiVAAKUmXyCzPiQe5+bCJrpOeKg/L05dunk=
|
||||
github.com/pion/dtls/v2 v2.2.12/go.mod h1:d9SYc9fch0CqK90mRk1dC7AkzzpwJj6u2GU3u+9pqFE=
|
||||
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
@@ -221,6 +237,8 @@ github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0t
|
||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
@@ -235,6 +253,8 @@ github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5f
|
||||
github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48=
|
||||
github.com/tilinna/z85 v1.0.0 h1:uqFnJBlD01dosSeo5sK1G1YGbPuwqVHqR+12OJDRjUw=
|
||||
github.com/tilinna/z85 v1.0.0/go.mod h1:EfpFU/DUY4ddEy6CRvk2l+UQNEzHbh+bqBQS+04Nkxs=
|
||||
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
|
||||
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
@@ -247,6 +267,8 @@ github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAz
|
||||
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI=
|
||||
github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho=
|
||||
@@ -263,6 +285,8 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||
go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ=
|
||||
go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
|
||||
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
|
||||
golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA=
|
||||
|
||||
+33
-108
@@ -1,89 +1,32 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cgo && darwin
|
||||
//go:build cgo
|
||||
|
||||
package lx
|
||||
|
||||
// GPU-accelerated batch xy=k AMM evaluation via Metal.
|
||||
//
|
||||
// The Metal kernel lives in luxcpp/metal (one-home-per-backend policy):
|
||||
// kernel: $LUXCPP_DIR/metal/src/shaders/dex/amm_xyk.metal
|
||||
// driver: $LUXCPP_DIR/dex/gpu/metal/amm_xyk_driver.{h,mm}
|
||||
// -> built into libamm_xyk_metal.a in $LUXCPP_PREFIX/lib
|
||||
// header: $LUXCPP_PREFIX/include/lux/dex/metal/amm_xyk_driver.h
|
||||
// metallib: $LUXCPP_PREFIX/share/lux/dex/amm_xyk.metallib
|
||||
// (also at build-tree path as fallback)
|
||||
//
|
||||
// Linkage goes through the lux-dex-amm-metal pkg-config bundle. Build +
|
||||
// install once with:
|
||||
//
|
||||
// cmake -S $HOME/work/luxcpp/dex -B $HOME/work/luxcpp/dex/build \
|
||||
// -DCMAKE_INSTALL_PREFIX=$HOME/work/luxcpp/install \
|
||||
// -DBUILD_TESTS=OFF
|
||||
// cmake --build $HOME/work/luxcpp/dex/build --target amm_xyk_metal
|
||||
// cmake --install $HOME/work/luxcpp/dex/build
|
||||
//
|
||||
// The metallib's runtime path is read from $LUX_DEX_AMM_METALLIB; if unset
|
||||
// we fall back to the canonical build-tree location.
|
||||
|
||||
/*
|
||||
#cgo pkg-config: lux-dex-amm-metal
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "amm_xyk_driver.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/luxfi/crypto/backend"
|
||||
)
|
||||
|
||||
// resolveMetallibPath returns the absolute path to the precompiled
|
||||
// amm_xyk.metallib. We honour LUX_DEX_AMM_METALLIB so packagers can
|
||||
// relocate the file at install time, but otherwise we fall back to the
|
||||
// in-tree cmake build path so dev builds Just Work.
|
||||
var (
|
||||
metallibOnce sync.Once
|
||||
metallibPath string
|
||||
)
|
||||
|
||||
func resolveMetallibPath() string {
|
||||
metallibOnce.Do(func() {
|
||||
if p := os.Getenv("LUX_DEX_AMM_METALLIB"); p != "" {
|
||||
metallibPath = p
|
||||
return
|
||||
}
|
||||
// Default: ~/work/luxcpp/dex/build/amm_xyk.metallib. We can't
|
||||
// rely on ${SRCDIR} at runtime (cgo only expands it at compile
|
||||
// time), so we hardcode the canonical sibling layout that the
|
||||
// repo enforces: lx and luxcpp are sibling directories under
|
||||
// $LUX_WORKSPACE_ROOT (default ~/work).
|
||||
root := os.Getenv("LUX_WORKSPACE_ROOT")
|
||||
if root == "" {
|
||||
home, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
root = filepath.Join(home, "work")
|
||||
}
|
||||
}
|
||||
metallibPath = filepath.Join(root, "luxcpp", "dex", "build", "amm_xyk.metallib")
|
||||
})
|
||||
return metallibPath
|
||||
}
|
||||
|
||||
// BatchEvalConstantProduct dispatches an N-tuple xy=k batch onto the Metal
|
||||
// GPU and returns the per-tuple `out_amount`. Output is byte-identical to
|
||||
// the CPU oracle BatchEvalConstantProductCPU — that contract is enforced by
|
||||
// amm_gpu_test.go on every run.
|
||||
// BatchEvalConstantProduct dispatches an N-tuple xy=k batch onto the host
|
||||
// GPU and returns the per-tuple out_amount. Output is byte-identical to
|
||||
// the CPU oracle BatchEvalConstantProductCPU.
|
||||
//
|
||||
// On any non-Apple build the cgo file is excluded by the `darwin` build tag
|
||||
// and the nocgo fallback below answers via the CPU oracle.
|
||||
// Backend selection at compile time (CGo on):
|
||||
// darwin → amm_gpu_metal.go → Metal
|
||||
// linux → amm_gpu_cuda.go → CUDA
|
||||
// other → amm_gpu_stub.go → returns errAMMGPUUnsupported (caller falls back to CPU)
|
||||
//
|
||||
// Fallback policy (recorded via backend.RecordFallback):
|
||||
// LUX_GPU_DISABLE=1 → reason "disabled"
|
||||
// gpuBatchEval returns sentinel → reason "unsupported"
|
||||
// other gpuBatchEval error → surfaced to caller (no fallback)
|
||||
//
|
||||
// CGo off → amm_nogpu.go provides a CPU-only implementation of this
|
||||
// entry point. One and only one BatchEvalConstantProduct symbol per build.
|
||||
func BatchEvalConstantProduct(reserves []ReservePair, amounts []uint64) ([]uint64, error) {
|
||||
if len(reserves) != len(amounts) {
|
||||
return nil, fmt.Errorf("lx: reserves/amounts length mismatch: %d vs %d",
|
||||
@@ -94,41 +37,23 @@ func BatchEvalConstantProduct(reserves []ReservePair, amounts []uint64) ([]uint6
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
libPath := resolveMetallibPath()
|
||||
if _, err := os.Stat(libPath); err != nil {
|
||||
return nil, fmt.Errorf("lx: amm metallib not found at %q: %w "+
|
||||
"(set LUX_DEX_AMM_METALLIB or run cmake --build "+
|
||||
"~/work/luxcpp/dex/build --target amm_xyk_metal)", libPath, err)
|
||||
if backend.GPUDisabled() {
|
||||
backend.RecordFallback(backend.FallbackDisabled, "amm")
|
||||
return BatchEvalConstantProductCPU(reserves, amounts)
|
||||
}
|
||||
|
||||
out := make([]uint64, n)
|
||||
|
||||
// Pin the three host buffers and the metallib path C-string for the
|
||||
// duration of the cgo dispatch. The driver does its own internal
|
||||
// memcpy off these buffers into MTLBuffers, so the pin is only needed
|
||||
// for the in-flight call.
|
||||
var pinner runtime.Pinner
|
||||
defer pinner.Unpin()
|
||||
pinner.Pin(&reserves[0])
|
||||
pinner.Pin(&amounts[0])
|
||||
pinner.Pin(&out[0])
|
||||
|
||||
cPath := C.CString(libPath)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
rc := C.amm_xyk_batch_metal(
|
||||
(*C.LuxAmmReservePair)(unsafe.Pointer(&reserves[0])),
|
||||
(*C.uint64_t)(unsafe.Pointer(&amounts[0])),
|
||||
(*C.uint64_t)(unsafe.Pointer(&out[0])),
|
||||
C.uint32_t(n),
|
||||
cPath,
|
||||
)
|
||||
runtime.KeepAlive(reserves)
|
||||
runtime.KeepAlive(amounts)
|
||||
runtime.KeepAlive(out)
|
||||
|
||||
if rc != 0 {
|
||||
return nil, fmt.Errorf("lx: amm_xyk_batch_metal returned %d", int(rc))
|
||||
out, err := gpuBatchEval(reserves, amounts)
|
||||
if err == errAMMGPUUnsupported {
|
||||
backend.RecordFallback(backend.FallbackUnsupported, "amm")
|
||||
return BatchEvalConstantProductCPU(reserves, amounts)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// errAMMGPUUnsupported is returned by the per-platform gpuBatchEval
|
||||
// when no GPU dispatch is available on this host. The dispatcher above
|
||||
// turns that into a transparent CPU fallback.
|
||||
var errAMMGPUUnsupported = fmt.Errorf("lx: amm GPU dispatch not available on this host")
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cgo && linux
|
||||
|
||||
package lx
|
||||
|
||||
// CUDA backend for batch xy=k AMM evaluation. Linked via
|
||||
// lux-dex-amm-cuda pkg-config bundle
|
||||
// -> libamm_xyk_cuda.a (luxcpp/dex/gpu/cuda/amm_xyk_driver.cu
|
||||
// + luxcpp/cuda/kernels/dex/amm_xyk.cu)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: lux-dex-amm-cuda
|
||||
|
||||
#include <stdint.h>
|
||||
#include "amm_xyk_driver.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// gpuBatchEval is the CUDA implementation called by amm_gpu.go::BatchEvalConstantProduct.
|
||||
// When no NVIDIA device is present at runtime, the driver returns -2 and we
|
||||
// surface errAMMGPUUnsupported so the dispatcher falls back to CPU.
|
||||
func gpuBatchEval(reserves []ReservePair, amounts []uint64) ([]uint64, error) {
|
||||
n := len(reserves)
|
||||
out := make([]uint64, n)
|
||||
|
||||
var pinner runtime.Pinner
|
||||
defer pinner.Unpin()
|
||||
pinner.Pin(&reserves[0])
|
||||
pinner.Pin(&amounts[0])
|
||||
pinner.Pin(&out[0])
|
||||
|
||||
rc := C.amm_xyk_batch_cuda_host(
|
||||
(*C.AMMReservePair)(unsafe.Pointer(&reserves[0])),
|
||||
(*C.uint64_t)(unsafe.Pointer(&amounts[0])),
|
||||
(*C.uint64_t)(unsafe.Pointer(&out[0])),
|
||||
C.uint32_t(n),
|
||||
)
|
||||
runtime.KeepAlive(reserves)
|
||||
runtime.KeepAlive(amounts)
|
||||
runtime.KeepAlive(out)
|
||||
|
||||
switch rc {
|
||||
case 0:
|
||||
return out, nil
|
||||
case -2:
|
||||
// No CUDA device / driver — fall back to CPU.
|
||||
return nil, errAMMGPUUnsupported
|
||||
default:
|
||||
return nil, fmt.Errorf("lx: amm_xyk_batch_cuda_host rc=%d", int(rc))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cgo && darwin
|
||||
|
||||
package lx
|
||||
|
||||
// Metal backend for batch xy=k AMM evaluation. Linked via
|
||||
// lux-dex-amm-metal pkg-config bundle
|
||||
// -> libamm_xyk_metal.a (luxcpp/dex/gpu/metal/amm_xyk_driver.mm)
|
||||
// -> amm_xyk.metallib (path resolved at runtime via LUX_DEX_AMM_METALLIB)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: lux-dex-amm-metal
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "amm_xyk_driver.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sync"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
metallibOnce sync.Once
|
||||
metallibPath string
|
||||
)
|
||||
|
||||
// resolveMetallibPath returns the absolute path to the precompiled
|
||||
// amm_xyk.metallib. LUX_DEX_AMM_METALLIB overrides; otherwise we fall
|
||||
// back to the canonical sibling layout under $LUX_WORKSPACE_ROOT/luxcpp.
|
||||
func resolveMetallibPath() string {
|
||||
metallibOnce.Do(func() {
|
||||
if p := os.Getenv("LUX_DEX_AMM_METALLIB"); p != "" {
|
||||
metallibPath = p
|
||||
return
|
||||
}
|
||||
root := os.Getenv("LUX_WORKSPACE_ROOT")
|
||||
if root == "" {
|
||||
if home, err := os.UserHomeDir(); err == nil {
|
||||
root = filepath.Join(home, "work")
|
||||
}
|
||||
}
|
||||
metallibPath = filepath.Join(root, "luxcpp", "dex", "build", "amm_xyk.metallib")
|
||||
})
|
||||
return metallibPath
|
||||
}
|
||||
|
||||
// gpuBatchEval is the Metal implementation called by amm_gpu.go::BatchEvalConstantProduct.
|
||||
func gpuBatchEval(reserves []ReservePair, amounts []uint64) ([]uint64, error) {
|
||||
libPath := resolveMetallibPath()
|
||||
if _, err := os.Stat(libPath); err != nil {
|
||||
return nil, fmt.Errorf("lx: amm metallib not found at %q: %w (set LUX_DEX_AMM_METALLIB)", libPath, err)
|
||||
}
|
||||
|
||||
n := len(reserves)
|
||||
out := make([]uint64, n)
|
||||
|
||||
var pinner runtime.Pinner
|
||||
defer pinner.Unpin()
|
||||
pinner.Pin(&reserves[0])
|
||||
pinner.Pin(&amounts[0])
|
||||
pinner.Pin(&out[0])
|
||||
|
||||
cPath := C.CString(libPath)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
|
||||
rc := C.amm_xyk_batch_metal(
|
||||
(*C.AMMReservePair)(unsafe.Pointer(&reserves[0])),
|
||||
(*C.uint64_t)(unsafe.Pointer(&amounts[0])),
|
||||
(*C.uint64_t)(unsafe.Pointer(&out[0])),
|
||||
C.uint32_t(n),
|
||||
cPath,
|
||||
)
|
||||
runtime.KeepAlive(reserves)
|
||||
runtime.KeepAlive(amounts)
|
||||
runtime.KeepAlive(out)
|
||||
|
||||
if rc != 0 {
|
||||
return nil, fmt.Errorf("lx: amm_xyk_batch_metal rc=%d", int(rc))
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cgo && !darwin && !linux
|
||||
|
||||
package lx
|
||||
|
||||
// gpuBatchEval has no GPU backend on this OS. The dispatcher in
|
||||
// amm_gpu.go turns errAMMGPUUnsupported into a transparent CPU fallback.
|
||||
func gpuBatchEval(reserves []ReservePair, amounts []uint64) ([]uint64, error) {
|
||||
return nil, errAMMGPUUnsupported
|
||||
}
|
||||
+10
-3
@@ -1,12 +1,19 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build !cgo || !darwin
|
||||
//go:build !cgo
|
||||
|
||||
package lx
|
||||
|
||||
// BatchEvalConstantProduct on non-Apple / non-cgo builds runs the CPU
|
||||
// oracle. The Metal-backed implementation is in amm_gpu.go.
|
||||
import "fmt"
|
||||
|
||||
// BatchEvalConstantProduct under !cgo is the CPU oracle. The cgo build
|
||||
// provides this symbol via amm_gpu.go (which dispatches to the
|
||||
// per-platform Metal / CUDA / stub implementation).
|
||||
func BatchEvalConstantProduct(reserves []ReservePair, amounts []uint64) ([]uint64, error) {
|
||||
if len(reserves) != len(amounts) {
|
||||
return nil, fmt.Errorf("lx: reserves/amounts length mismatch: %d vs %d",
|
||||
len(reserves), len(amounts))
|
||||
}
|
||||
return BatchEvalConstantProductCPU(reserves, amounts)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build cgo && linux
|
||||
|
||||
package lx
|
||||
|
||||
// CUDA backend for the DEX CLOB match_order kernel. Linked via
|
||||
// lux-dex-clob-cuda pkg-config bundle
|
||||
// -> libdex_clob_cuda.a (luxcpp/dex/gpu/cuda/dex_clob_host.cu
|
||||
// + luxcpp/cuda/kernels/gpu/dex_swap.cu)
|
||||
|
||||
/*
|
||||
#cgo pkg-config: lux-dex-clob-cuda
|
||||
|
||||
#include <stdint.h>
|
||||
#include "dex_clob_host.h"
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// gpuMatchOrder is the CUDA implementation called by orderbook_gpu.go.
|
||||
// Returns errCLOBGPUUnsupported when no NVIDIA device is present so the
|
||||
// dispatcher falls back to MatchOrderCPU.
|
||||
func gpuMatchOrder(
|
||||
incoming *DEXOrder,
|
||||
book []DEXOrder,
|
||||
bookIndices []uint32,
|
||||
tradeIDBase uint64,
|
||||
timestamp uint64,
|
||||
) ([]DEXTrade, uint64, error) {
|
||||
if len(bookIndices) == 0 {
|
||||
return nil, incoming.Quantity, nil
|
||||
}
|
||||
|
||||
tradesOut := make([]DEXTrade, len(bookIndices))
|
||||
var (
|
||||
tradesWritten uint32
|
||||
remaining uint64
|
||||
)
|
||||
|
||||
var pinner runtime.Pinner
|
||||
defer pinner.Unpin()
|
||||
pinner.Pin(incoming)
|
||||
pinner.Pin(&book[0])
|
||||
pinner.Pin(&bookIndices[0])
|
||||
pinner.Pin(&tradesOut[0])
|
||||
pinner.Pin(&tradesWritten)
|
||||
pinner.Pin(&remaining)
|
||||
|
||||
rc := C.dex_clob_match_order_host(
|
||||
(*C.DEXOrder)(unsafe.Pointer(incoming)),
|
||||
(*C.DEXOrder)(unsafe.Pointer(&book[0])),
|
||||
(*C.uint32_t)(unsafe.Pointer(&bookIndices[0])),
|
||||
C.uint32_t(len(bookIndices)),
|
||||
(*C.DEXTrade)(unsafe.Pointer(&tradesOut[0])),
|
||||
C.uint64_t(tradeIDBase),
|
||||
C.uint64_t(timestamp),
|
||||
(*C.uint32_t)(unsafe.Pointer(&tradesWritten)),
|
||||
(*C.uint64_t)(unsafe.Pointer(&remaining)),
|
||||
)
|
||||
runtime.KeepAlive(incoming)
|
||||
runtime.KeepAlive(book)
|
||||
runtime.KeepAlive(bookIndices)
|
||||
runtime.KeepAlive(tradesOut)
|
||||
|
||||
switch rc {
|
||||
case 0:
|
||||
// Padding normalization: the host driver already zeros the
|
||||
// padding bytes for emitted trades and touched book rows, but
|
||||
// belt-and-braces here in case a future kernel revision
|
||||
// changes the contract.
|
||||
trades := tradesOut[:int(tradesWritten)]
|
||||
for i := range trades {
|
||||
trades[i].ClearPadding()
|
||||
}
|
||||
for i := range book {
|
||||
book[i].ClearPadding()
|
||||
}
|
||||
return trades, remaining, nil
|
||||
case -2:
|
||||
return nil, 0, errCLOBGPUUnsupported
|
||||
default:
|
||||
return nil, 0, fmt.Errorf("lx: dex_clob_match_order_host rc=%d", int(rc))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package lx
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestMatchOrder_GPUMatchesCPU pins the field-equality contract between
|
||||
// MatchOrderGPU and MatchOrderCPU. On hosts without CUDA the GPU path
|
||||
// transparently falls back to CPU, so this test still passes (it proves
|
||||
// CPU == CPU and exercises the dispatcher path). On Linux+NVIDIA hosts
|
||||
// it proves the CUDA dispatch produces the same trade list and remaining
|
||||
// quantity field-for-field.
|
||||
//
|
||||
// We compare via DEXTrade.EqualFields / DEXOrder.EqualFields, NOT `==`,
|
||||
// so padding bytes are not allowed to influence the result. Padding is
|
||||
// also explicitly cleared on both paths (host driver post-copy + Go
|
||||
// MatchOrderCPU) so the production objects stay deterministic too.
|
||||
func TestMatchOrder_GPUMatchesCPU(t *testing.T) {
|
||||
for seed := int64(1); seed <= 8; seed++ {
|
||||
seed := seed
|
||||
t.Run("", func(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(seed))
|
||||
|
||||
incoming := DEXOrder{
|
||||
OrderID: uint64(seed*1000 + 1),
|
||||
UserID: 1,
|
||||
Price: DEXPrice{Integer: uint64(100 + rng.Intn(20)), Fraction: 0},
|
||||
Quantity: uint64(50 + rng.Intn(450)),
|
||||
Timestamp: uint64(seed),
|
||||
Side: DEXSideBid,
|
||||
Type: DEXOrderLimit,
|
||||
Status: DEXStatusOpen,
|
||||
}
|
||||
incoming.Remaining = incoming.Quantity
|
||||
|
||||
bookSize := 16 + rng.Intn(64)
|
||||
book := make([]DEXOrder, bookSize)
|
||||
indices := make([]uint32, bookSize)
|
||||
for i := range book {
|
||||
book[i] = DEXOrder{
|
||||
OrderID: uint64(seed*10000) + uint64(i),
|
||||
UserID: 2 + uint64(i)%4,
|
||||
Price: DEXPrice{Integer: uint64(90 + rng.Intn(30)), Fraction: 0},
|
||||
Quantity: uint64(10 + rng.Intn(100)),
|
||||
Timestamp: uint64(seed*100) + uint64(i),
|
||||
Side: DEXSideAsk,
|
||||
Type: DEXOrderLimit,
|
||||
Status: DEXStatusOpen,
|
||||
}
|
||||
book[i].Remaining = book[i].Quantity
|
||||
indices[i] = uint32(i)
|
||||
}
|
||||
|
||||
// Deep-copy book so CPU and GPU runs start from identical state.
|
||||
bookCPU := append([]DEXOrder(nil), book...)
|
||||
bookGPU := append([]DEXOrder(nil), book...)
|
||||
|
||||
incomingCPU := incoming
|
||||
tradesCPU, remCPU := MatchOrderCPU(&incomingCPU, bookCPU, indices, 1_000_000, 9_999)
|
||||
|
||||
incomingGPU := incoming
|
||||
tradesGPU, remGPU, err := MatchOrderGPU(&incomingGPU, bookGPU, indices, 1_000_000, 9_999)
|
||||
if err != nil {
|
||||
t.Fatalf("MatchOrderGPU error: %v", err)
|
||||
}
|
||||
|
||||
if remCPU != remGPU {
|
||||
t.Fatalf("remaining mismatch: cpu=%d gpu=%d", remCPU, remGPU)
|
||||
}
|
||||
if len(tradesCPU) != len(tradesGPU) {
|
||||
t.Fatalf("trade count mismatch: cpu=%d gpu=%d", len(tradesCPU), len(tradesGPU))
|
||||
}
|
||||
for i := range tradesCPU {
|
||||
if !tradesCPU[i].EqualFields(tradesGPU[i]) {
|
||||
t.Fatalf("trade[%d] field mismatch:\n cpu=%+v\n gpu=%+v",
|
||||
i, tradesCPU[i], tradesGPU[i])
|
||||
}
|
||||
}
|
||||
for i := range bookCPU {
|
||||
if !bookCPU[i].EqualFields(bookGPU[i]) {
|
||||
t.Fatalf("book[%d] field mismatch:\n cpu=%+v\n gpu=%+v",
|
||||
i, bookCPU[i], bookGPU[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
//go:build !cgo || !linux
|
||||
|
||||
package lx
|
||||
|
||||
// gpuMatchOrder has no GPU backend on non-Linux or no-cgo builds. The
|
||||
// dispatcher in orderbook_gpu.go turns errCLOBGPUUnsupported into a
|
||||
// transparent fallback to MatchOrderCPU.
|
||||
func gpuMatchOrder(
|
||||
incoming *DEXOrder,
|
||||
book []DEXOrder,
|
||||
bookIndices []uint32,
|
||||
tradeIDBase uint64,
|
||||
timestamp uint64,
|
||||
) ([]DEXTrade, uint64, error) {
|
||||
return nil, 0, errCLOBGPUUnsupported
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package lx
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Go mirrors of the public C ABI types in
|
||||
// luxcpp/cuda/include/lux/cuda/dex_swap.h. Layouts MUST stay byte-equal
|
||||
// with the C structs — the same memory is reinterpreted on the cgo
|
||||
// boundary in orderbook_cuda.go.
|
||||
//
|
||||
// These types are deliberately a separate flat representation from
|
||||
// orderbook.go::Order (which has heap pointers, linked-list nodes, and
|
||||
// status fields the CUDA kernel doesn't model). Use this surface when
|
||||
// you want to drive the CUDA matching kernel directly — for parity
|
||||
// testing, batch backtests, or future architectures that snapshot the
|
||||
// book into a flat array.
|
||||
|
||||
// DEXPrice is fixed-point Q64.64. Maps to DEXPrice in dex_swap.h.
|
||||
type DEXPrice struct {
|
||||
Integer uint64
|
||||
Fraction uint64
|
||||
}
|
||||
|
||||
// DEXSide / DEXOrderType / DEXOrderStatus values mirror the kernel enums.
|
||||
const (
|
||||
DEXSideBid uint8 = 0
|
||||
DEXSideAsk uint8 = 1
|
||||
|
||||
DEXOrderLimit uint8 = 0
|
||||
DEXOrderMarket uint8 = 1
|
||||
DEXOrderIOC uint8 = 2
|
||||
DEXOrderFOK uint8 = 3
|
||||
|
||||
DEXStatusOpen uint8 = 0
|
||||
DEXStatusPartial uint8 = 1
|
||||
DEXStatusFilled uint8 = 2
|
||||
DEXStatusCancelled uint8 = 3
|
||||
)
|
||||
|
||||
// DEXOrder is the 64-byte cache-line-aligned order record. Layout is
|
||||
// byte-equal with DEXOrder in dex_swap.h and Order in dex_swap.cu.
|
||||
//
|
||||
// _pad is a named padding field (not a blank `_`) so it is addressable
|
||||
// and we can normalize it to zero after any data path that may leave
|
||||
// the bytes uninitialised. Same for DEXTrade.
|
||||
type DEXOrder struct {
|
||||
OrderID uint64 // 0..8
|
||||
UserID uint64 // 8..16
|
||||
Price DEXPrice // 16..32
|
||||
Quantity uint64 // 32..40
|
||||
Remaining uint64 // 40..48
|
||||
Timestamp uint64 // 48..56
|
||||
Side uint8 // 56
|
||||
Type uint8 // 57
|
||||
Status uint8 // 58
|
||||
_pad [5]uint8 // 59..64
|
||||
}
|
||||
|
||||
// DEXTrade is the 80-byte trade record produced by the matching kernel.
|
||||
// Layout is byte-equal with DEXTrade in dex_swap.h.
|
||||
type DEXTrade struct {
|
||||
TradeID uint64 // 0..8
|
||||
MakerOrderID uint64 // 8..16
|
||||
TakerOrderID uint64 // 16..24
|
||||
MakerUserID uint64 // 24..32
|
||||
TakerUserID uint64 // 32..40
|
||||
Price DEXPrice // 40..56
|
||||
Quantity uint64 // 56..64
|
||||
Timestamp uint64 // 64..72
|
||||
TakerSide uint8 // 72
|
||||
_pad [7]uint8 // 73..80
|
||||
}
|
||||
|
||||
// ClearPadding zeros the trailing padding bytes. Called by CPU oracle
|
||||
// output paths and after cgo device→host copies so structural
|
||||
// comparisons of DEXOrder / DEXTrade values are deterministic.
|
||||
func (o *DEXOrder) ClearPadding() { o._pad = [5]uint8{} }
|
||||
func (t *DEXTrade) ClearPadding() { t._pad = [7]uint8{} }
|
||||
|
||||
// EqualFields compares every named field of two DEXTrade values,
|
||||
// excluding padding bytes. Use this in tests and structural assertions
|
||||
// instead of `a == b` so a kernel that fails to zero its trailing pad
|
||||
// bytes does not cause a false-negative.
|
||||
func (t DEXTrade) EqualFields(o DEXTrade) bool {
|
||||
return t.TradeID == o.TradeID &&
|
||||
t.MakerOrderID == o.MakerOrderID &&
|
||||
t.TakerOrderID == o.TakerOrderID &&
|
||||
t.MakerUserID == o.MakerUserID &&
|
||||
t.TakerUserID == o.TakerUserID &&
|
||||
t.Price == o.Price &&
|
||||
t.Quantity == o.Quantity &&
|
||||
t.Timestamp == o.Timestamp &&
|
||||
t.TakerSide == o.TakerSide
|
||||
}
|
||||
|
||||
// EqualFields compares every named field of two DEXOrder values,
|
||||
// excluding padding bytes.
|
||||
func (o DEXOrder) EqualFields(other DEXOrder) bool {
|
||||
return o.OrderID == other.OrderID &&
|
||||
o.UserID == other.UserID &&
|
||||
o.Price == other.Price &&
|
||||
o.Quantity == other.Quantity &&
|
||||
o.Remaining == other.Remaining &&
|
||||
o.Timestamp == other.Timestamp &&
|
||||
o.Side == other.Side &&
|
||||
o.Type == other.Type &&
|
||||
o.Status == other.Status
|
||||
}
|
||||
|
||||
// init runs the Go-side ABI mirror of the C-side _Static_assert checks
|
||||
// in dex_swap.h. If a refactor reorders a field or changes a type, the
|
||||
// process panics at startup — the same compile-time guarantee the C
|
||||
// macros give for the C/CUDA side.
|
||||
func init() {
|
||||
assertDEXABI()
|
||||
}
|
||||
|
||||
func assertDEXABI() {
|
||||
if got := unsafe.Sizeof(DEXPrice{}); got != 16 {
|
||||
panic("lx: DEXPrice size drifted: " + sizeStr(uintptr(got)) + " != 16")
|
||||
}
|
||||
if got := unsafe.Offsetof(DEXPrice{}.Integer); got != 0 {
|
||||
panic("lx: DEXPrice.Integer offset drift")
|
||||
}
|
||||
if got := unsafe.Offsetof(DEXPrice{}.Fraction); got != 8 {
|
||||
panic("lx: DEXPrice.Fraction offset drift")
|
||||
}
|
||||
|
||||
if got := unsafe.Sizeof(DEXOrder{}); got != 64 {
|
||||
panic("lx: DEXOrder size drifted: " + sizeStr(uintptr(got)) + " != 64")
|
||||
}
|
||||
if unsafe.Offsetof(DEXOrder{}.OrderID) != 0 ||
|
||||
unsafe.Offsetof(DEXOrder{}.UserID) != 8 ||
|
||||
unsafe.Offsetof(DEXOrder{}.Price) != 16 ||
|
||||
unsafe.Offsetof(DEXOrder{}.Quantity) != 32 ||
|
||||
unsafe.Offsetof(DEXOrder{}.Remaining) != 40 ||
|
||||
unsafe.Offsetof(DEXOrder{}.Timestamp) != 48 ||
|
||||
unsafe.Offsetof(DEXOrder{}.Side) != 56 ||
|
||||
unsafe.Offsetof(DEXOrder{}.Type) != 57 ||
|
||||
unsafe.Offsetof(DEXOrder{}.Status) != 58 {
|
||||
panic("lx: DEXOrder field offset drift")
|
||||
}
|
||||
|
||||
if got := unsafe.Sizeof(DEXTrade{}); got != 80 {
|
||||
panic("lx: DEXTrade size drifted: " + sizeStr(uintptr(got)) + " != 80")
|
||||
}
|
||||
if unsafe.Offsetof(DEXTrade{}.TradeID) != 0 ||
|
||||
unsafe.Offsetof(DEXTrade{}.MakerOrderID) != 8 ||
|
||||
unsafe.Offsetof(DEXTrade{}.TakerOrderID) != 16 ||
|
||||
unsafe.Offsetof(DEXTrade{}.MakerUserID) != 24 ||
|
||||
unsafe.Offsetof(DEXTrade{}.TakerUserID) != 32 ||
|
||||
unsafe.Offsetof(DEXTrade{}.Price) != 40 ||
|
||||
unsafe.Offsetof(DEXTrade{}.Quantity) != 56 ||
|
||||
unsafe.Offsetof(DEXTrade{}.Timestamp) != 64 ||
|
||||
unsafe.Offsetof(DEXTrade{}.TakerSide) != 72 {
|
||||
panic("lx: DEXTrade field offset drift")
|
||||
}
|
||||
|
||||
if got := unsafe.Sizeof(ReservePair{}); got != 16 {
|
||||
panic("lx: ReservePair size drifted: " + sizeStr(uintptr(got)) + " != 16")
|
||||
}
|
||||
if unsafe.Offsetof(ReservePair{}.ReserveX) != 0 ||
|
||||
unsafe.Offsetof(ReservePair{}.ReserveY) != 8 {
|
||||
panic("lx: ReservePair field offset drift")
|
||||
}
|
||||
}
|
||||
|
||||
// sizeStr formats a uintptr as a decimal string without pulling in fmt.
|
||||
// Kept tiny so the init path stays allocation-free in the happy case.
|
||||
func sizeStr(n uintptr) string {
|
||||
if n == 0 {
|
||||
return "0"
|
||||
}
|
||||
var buf [20]byte
|
||||
i := len(buf)
|
||||
for n > 0 {
|
||||
i--
|
||||
buf[i] = byte('0' + n%10)
|
||||
n /= 10
|
||||
}
|
||||
return string(buf[i:])
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2025-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package lx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/luxfi/crypto/backend"
|
||||
)
|
||||
|
||||
// MatchOrderGPU matches one incoming taker against a subset of book
|
||||
// orders identified by bookIndices, dispatching to CUDA when available
|
||||
// and falling back to MatchOrderCPU otherwise. Output is byte-equal
|
||||
// across paths after padding normalization — that contract is enforced
|
||||
// by orderbook_cuda_parity_test.go on every run.
|
||||
//
|
||||
// This is the flat-buffer matching primitive used for parity testing,
|
||||
// batch backtests, and future GPU-resident architectures. The live
|
||||
// OrderBook.MatchOrders() engine retains its richer order-type and
|
||||
// linked-list semantics and is NOT touched by this dispatch.
|
||||
//
|
||||
// Fallback policy (recorded via backend.RecordFallback):
|
||||
// LUX_GPU_DISABLE=1 → reason "disabled"
|
||||
// gpuMatchOrder returns sentinel → reason "unsupported"
|
||||
// other gpuMatchOrder error → surfaced to caller (no fallback)
|
||||
//
|
||||
// Returns: trades emitted, remaining quantity on the incoming order.
|
||||
// `book` may be mutated in place to reflect updated Remaining / Status.
|
||||
func MatchOrderGPU(
|
||||
incoming *DEXOrder,
|
||||
book []DEXOrder,
|
||||
bookIndices []uint32,
|
||||
tradeIDBase uint64,
|
||||
timestamp uint64,
|
||||
) ([]DEXTrade, uint64, error) {
|
||||
if incoming == nil {
|
||||
return nil, 0, fmt.Errorf("lx: MatchOrderGPU: nil incoming")
|
||||
}
|
||||
|
||||
if backend.GPUDisabled() {
|
||||
backend.RecordFallback(backend.FallbackDisabled, "clob")
|
||||
t, r := MatchOrderCPU(incoming, book, bookIndices, tradeIDBase, timestamp)
|
||||
return t, r, nil
|
||||
}
|
||||
|
||||
trades, remaining, err := gpuMatchOrder(incoming, book, bookIndices, tradeIDBase, timestamp)
|
||||
if err == errCLOBGPUUnsupported {
|
||||
backend.RecordFallback(backend.FallbackUnsupported, "clob")
|
||||
t, r := MatchOrderCPU(incoming, book, bookIndices, tradeIDBase, timestamp)
|
||||
return t, r, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return trades, remaining, nil
|
||||
}
|
||||
|
||||
// errCLOBGPUUnsupported is returned by per-platform gpuMatchOrder when
|
||||
// no GPU dispatch is available. The dispatcher turns it into a CPU
|
||||
// fallback rather than surfacing the error.
|
||||
var errCLOBGPUUnsupported = fmt.Errorf("lx: CLOB CUDA dispatch not available on this host")
|
||||
@@ -0,0 +1,116 @@
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user