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:
2026-05-24 14:19:56 -07:00
parent 0afa6a7f7d
commit 2ef852030b
13 changed files with 824 additions and 140 deletions
+11 -7
View File
@@ -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
+46 -22
View File
@@ -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
View File
@@ -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")
+59
View File
@@ -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))
}
}
+90
View File
@@ -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
}
+12
View File
@@ -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
View File
@@ -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)
}
+91
View File
@@ -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))
}
}
+91
View File
@@ -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])
}
}
})
}
}
+19
View File
@@ -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
}
+184
View File
@@ -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:])
}
+62
View File
@@ -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")
+116
View File
@@ -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
}