Compare commits

...

2 commits

Author SHA1 Message Date
DataHoarder 9f77218ff8
Version v3.0.0, support full datataset mode, modified api
Some checks are pending
continuous-integration/drone/push Build is running
2024-04-20 21:17:33 +02:00
DataHoarder 4903cd7407
Cleanup readme, superscalar 2024-04-20 20:22:05 +02:00
16 changed files with 308 additions and 153 deletions

View file

@ -1,29 +1,38 @@
# RandomX (Golang Implementation) # RandomX (Golang Implementation)
RandomX is a proof-of-work (PoW) algorithm that is optimized for general-purpose CPUs.
RandomX uses random code execution (hence the name) together with several memory-hard techniques to minimize the efficiency advantage of specialized hardware.
---
Fork from [git.dero.io/DERO_Foundation/RandomX](https://git.dero.io/DERO_Foundation/RandomX). Also related, their [Analysis of RandomX writeup](https://medium.com/deroproject/analysis-of-randomx-dde9dfe9bbc6). Fork from [git.dero.io/DERO_Foundation/RandomX](https://git.dero.io/DERO_Foundation/RandomX). Also related, their [Analysis of RandomX writeup](https://medium.com/deroproject/analysis-of-randomx-dde9dfe9bbc6).
Original code failed RandomX testcases and was implemented using big.Float. Original code failed RandomX testcases and was implemented using big.Float.
This package implements RandomX without CGO, using only Golang code, pure float64 ops and two small assembly sections to implement CFROUND modes, with optional soft float implementation. ---
This package implements RandomX without CGO, using only Golang code, native float64 ops, some assembly, but with optional soft float _purego_ implementation.
All test cases pass properly. All test cases pass properly.
JIT is supported on a few platforms but can be hard-disabled via the `disable_jit` build flag, or at runtime. Supports Full mode and Light mode.
For the C++ implementation and design of RandomX, see [github.com/tevador/RandomX](https://github.com/tevador/RandomX)
| Feature | 386 | amd64 | arm | arm64 | mips | mips64 | riscv64 | wasm |
|:----------------------------:|:---:|:-----:|:---:|:-----:|:----:|:------:|:-------:|:----:|
| purego | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Hardware Float Operations | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| Hardware AES Operations | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Native Superscalar Execution | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Superscalar JIT Execution | ❌ | ✅* | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Native VM Execution | ✅ | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| VM JIT Execution | ❌ | ✅* | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
A pure Golang implementation can be used on platforms without hard float support or via the `purego` build flag manually. A pure Golang implementation can be used on platforms without hard float support or via the `purego` build flag manually.
| Platform | Hard Float | Hard AES | JIT | Native | purego | Notes | Any platform with no hard float support or when enabled manually will use soft float, using [softfloat64](https://git.gammaspectra.live/P2Pool/softfloat64). This will be very slow.
|:-----------:|:----------:|:--------:|:---:|:------:|:------:|:----------------:|
| **386** | ✅ | ❌ | ❌ | ✅ | ✅ | |
| **amd64** | ✅ | ✅ | ✅* | ✅ | ✅ | JIT only on Unix |
| **arm** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **arm64** | ✅ | ❌ | ❌ | ✅ | ✅ | |
| **mips** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **mips64** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **riscv64** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **wasm** | ❌ | ❌ | ❌ | ❌ | ✅ | |
Native hard float can be added with supporting rounding mode under _asm_.
Any platform with no hard float support (soft float using [softfloat64](git.gammaspectra.live/P2Pool/softfloat64)) will be vastly slow. JIT only supported under Unix systems (Linux, *BSD, macOS), and can be hard-disabled via the `disable_jit` build flag, or at runtime.
Native hard float can be added with supporting rounding mode under _asm_.

View file

@ -30,7 +30,7 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package aes package aes
import ( import (
"git.gammaspectra.live/P2Pool/go-randomx/v2/keys" "git.gammaspectra.live/P2Pool/go-randomx/v3/keys"
"unsafe" "unsafe"
) )

View file

@ -3,29 +3,29 @@
package aes package aes
import ( import (
_ "git.gammaspectra.live/P2Pool/go-randomx/v2/asm" _ "git.gammaspectra.live/P2Pool/go-randomx/v3/asm"
"golang.org/x/sys/cpu" "golang.org/x/sys/cpu"
_ "unsafe" _ "unsafe"
) )
//go:noescape //go:noescape
//go:linkname hard_aesdec git.gammaspectra.live/P2Pool/go-randomx/v2/asm.aesdec //go:linkname hard_aesdec git.gammaspectra.live/P2Pool/go-randomx/v3/asm.aesdec
func hard_aesdec(state *[4]uint32, key *[4]uint32) func hard_aesdec(state *[4]uint32, key *[4]uint32)
//go:noescape //go:noescape
//go:linkname hard_aesenc git.gammaspectra.live/P2Pool/go-randomx/v2/asm.aesenc //go:linkname hard_aesenc git.gammaspectra.live/P2Pool/go-randomx/v3/asm.aesenc
func hard_aesenc(state *[4]uint32, key *[4]uint32) func hard_aesenc(state *[4]uint32, key *[4]uint32)
//go:noescape //go:noescape
//go:linkname hard_aesroundtrip_decenc git.gammaspectra.live/P2Pool/go-randomx/v2/asm.aesroundtrip_decenc //go:linkname hard_aesroundtrip_decenc git.gammaspectra.live/P2Pool/go-randomx/v3/asm.aesroundtrip_decenc
func hard_aesroundtrip_decenc(states *[4][4]uint32, keys *[4][4]uint32) func hard_aesroundtrip_decenc(states *[4][4]uint32, keys *[4][4]uint32)
//go:noescape //go:noescape
//go:linkname hard_aesroundtrip_encdec git.gammaspectra.live/P2Pool/go-randomx/v2/asm.aesroundtrip_encdec //go:linkname hard_aesroundtrip_encdec git.gammaspectra.live/P2Pool/go-randomx/v3/asm.aesroundtrip_encdec
func hard_aesroundtrip_encdec(states *[4][4]uint32, keys *[4][4]uint32) func hard_aesroundtrip_encdec(states *[4][4]uint32, keys *[4][4]uint32)
//go:noescape //go:noescape
//go:linkname hard_aesroundtrip_encdec1 git.gammaspectra.live/P2Pool/go-randomx/v2/asm.aesroundtrip_encdec1 //go:linkname hard_aesroundtrip_encdec1 git.gammaspectra.live/P2Pool/go-randomx/v3/asm.aesroundtrip_encdec1
func hard_aesroundtrip_encdec1(states *[4][4]uint32, key *[4]uint32) func hard_aesroundtrip_encdec1(states *[4][4]uint32, key *[4]uint32)
var supportsAES = cpu.X86.HasAES var supportsAES = cpu.X86.HasAES

View file

@ -1,8 +1,8 @@
package randomx package randomx
import ( import (
"git.gammaspectra.live/P2Pool/go-randomx/v2/argon2" "git.gammaspectra.live/P2Pool/go-randomx/v3/argon2"
"git.gammaspectra.live/P2Pool/go-randomx/v2/keys" "git.gammaspectra.live/P2Pool/go-randomx/v3/keys"
"runtime" "runtime"
"slices" "slices"
"unsafe" "unsafe"
@ -15,7 +15,7 @@ func (m *MemoryBlock) GetLine(addr uint64) *RegisterLine {
return (*RegisterLine)(unsafe.Pointer(unsafe.SliceData(m[addr : addr+8 : addr+8]))) return (*RegisterLine)(unsafe.Pointer(unsafe.SliceData(m[addr : addr+8 : addr+8])))
} }
type Randomx_Cache struct { type Cache struct {
Blocks []MemoryBlock Blocks []MemoryBlock
Programs [RANDOMX_PROGRAM_COUNT]SuperScalarProgram Programs [RANDOMX_PROGRAM_COUNT]SuperScalarProgram
@ -25,36 +25,20 @@ type Randomx_Cache struct {
Flags uint64 Flags uint64
} }
func Randomx_alloc_cache(flags uint64) *Randomx_Cache { func NewCache(flags uint64) *Cache {
if flags == RANDOMX_FLAG_DEFAULT { if flags == RANDOMX_FLAG_DEFAULT {
flags = RANDOMX_FLAG_JIT flags = RANDOMX_FLAG_JIT
} }
return &Randomx_Cache{ return &Cache{
Flags: flags, Flags: flags,
} }
} }
func (cache *Randomx_Cache) HasJIT() bool { func (cache *Cache) HasJIT() bool {
return cache.Flags&RANDOMX_FLAG_JIT > 0 && cache.JitPrograms[0] != nil return cache.Flags&RANDOMX_FLAG_JIT > 0 && cache.JitPrograms[0] != nil
} }
func (cache *Randomx_Cache) VM_Initialize() *VM { func (cache *Cache) Close() error {
vm := &VM{
Dataset: &Randomx_DatasetLight{
Cache: cache,
},
}
if cache.HasJIT() {
vm.JITProgram = mapProgram(nil, int(RandomXCodeSize))
if cache.Flags&RANDOMX_FLAG_SECURE == 0 {
mapProgramRWX(vm.JITProgram)
}
}
return vm
}
func (cache *Randomx_Cache) Close() error {
for _, p := range cache.JitPrograms { for _, p := range cache.JitPrograms {
if p != nil { if p != nil {
err := p.Close() err := p.Close()
@ -66,10 +50,12 @@ func (cache *Randomx_Cache) Close() error {
return nil return nil
} }
func (cache *Randomx_Cache) Init(key []byte) { func (cache *Cache) Init(key []byte) {
// Lock due to external JIT madness if cache.Flags&RANDOMX_FLAG_JIT > 0 {
runtime.LockOSThread() // Lock due to external JIT madness
defer runtime.UnlockOSThread() runtime.LockOSThread()
defer runtime.UnlockOSThread()
}
kkey := slices.Clone(key) kkey := slices.Clone(key)
@ -93,7 +79,7 @@ func (cache *Randomx_Cache) Init(key []byte) {
const Mask = CacheSize/CacheLineSize - 1 const Mask = CacheSize/CacheLineSize - 1
// GetMixBlock fetch a 64 byte block in uint64 form // GetMixBlock fetch a 64 byte block in uint64 form
func (cache *Randomx_Cache) GetMixBlock(addr uint64) *RegisterLine { func (cache *Cache) GetMixBlock(addr uint64) *RegisterLine {
addr = (addr & Mask) * CacheLineSize addr = (addr & Mask) * CacheLineSize
@ -101,7 +87,7 @@ func (cache *Randomx_Cache) GetMixBlock(addr uint64) *RegisterLine {
return cache.Blocks[block].GetLine(addr % 1024) return cache.Blocks[block].GetLine(addr % 1024)
} }
func (cache *Randomx_Cache) InitDatasetItem(rl *RegisterLine, itemNumber uint64) { func (cache *Cache) InitDatasetItem(rl *RegisterLine, itemNumber uint64) {
registerValue := itemNumber registerValue := itemNumber
rl[0] = (itemNumber + 1) * keys.SuperScalar_Constants[0] rl[0] = (itemNumber + 1) * keys.SuperScalar_Constants[0]
@ -129,7 +115,7 @@ func (cache *Randomx_Cache) InitDatasetItem(rl *RegisterLine, itemNumber uint64)
} }
} }
func (cache *Randomx_Cache) InitDatasetItemJIT(rl *RegisterLine, itemNumber uint64) { func (cache *Cache) InitDatasetItemJIT(rl *RegisterLine, itemNumber uint64) {
registerValue := itemNumber registerValue := itemNumber
rl[0] = (itemNumber + 1) * keys.SuperScalar_Constants[0] rl[0] = (itemNumber + 1) * keys.SuperScalar_Constants[0]
@ -155,9 +141,12 @@ func (cache *Randomx_Cache) InitDatasetItemJIT(rl *RegisterLine, itemNumber uint
} }
} }
func (cache *Randomx_Cache) initDataset(dataset []RegisterLine, startItem, endItem uint64) { func (cache *Cache) InitDataset(dataset []RegisterLine, startItem, endItem uint64) {
panic("todo")
for itemNumber := startItem; itemNumber < endItem; itemNumber, dataset = itemNumber+1, dataset[1:] { for itemNumber := startItem; itemNumber < endItem; itemNumber, dataset = itemNumber+1, dataset[1:] {
cache.InitDatasetItem(&dataset[0], itemNumber) if cache.HasJIT() {
cache.InitDatasetItemJIT(&dataset[0], itemNumber)
} else {
cache.InitDatasetItem(&dataset[0], itemNumber)
}
} }
} }

View file

@ -29,7 +29,7 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package randomx package randomx
import "git.gammaspectra.live/P2Pool/go-randomx/v2/argon2" import "git.gammaspectra.live/P2Pool/go-randomx/v3/argon2"
// see reference configuration.h // see reference configuration.h
// Cache size in KiB. Must be a power of 2. // Cache size in KiB. Must be a power of 2.
@ -81,7 +81,7 @@ const RANDOMX_JUMP_BITS = 8
// Jump condition mask offset in bits. The sum of RANDOMX_JUMP_BITS and RANDOMX_JUMP_OFFSET must not exceed 16. // Jump condition mask offset in bits. The sum of RANDOMX_JUMP_BITS and RANDOMX_JUMP_OFFSET must not exceed 16.
const RANDOMX_JUMP_OFFSET = 8 const RANDOMX_JUMP_OFFSET = 8
const DATASETEXTRAITEMS = RANDOMX_DATASET_EXTRA_SIZE / RANDOMX_DATASET_ITEM_SIZE const DatasetExtraItems = RANDOMX_DATASET_EXTRA_SIZE / RANDOMX_DATASET_ITEM_SIZE
const SuperscalarMaxSize = 3*RANDOMX_SUPERSCALAR_LATENCY + 2 const SuperscalarMaxSize = 3*RANDOMX_SUPERSCALAR_LATENCY + 2
const RANDOMX_DATASET_ITEM_SIZE uint64 = 64 const RANDOMX_DATASET_ITEM_SIZE uint64 = 64

View file

@ -1,8 +1,30 @@
package randomx package randomx
type Randomx_Dataset interface { import "sync"
InitDataset(startItem, endItem uint64)
ReadDataset(address uint64, r, cache *RegisterLine) type Dataset interface {
InitDataset(startItem, itemCount uint64)
ReadDataset(address uint64, r *RegisterLine)
PrefetchDataset(address uint64) PrefetchDataset(address uint64)
Flags() uint64 Flags() uint64
Cache() *Cache
}
func InitDatasetParallel(dataset Dataset, n int) {
n = max(1, n)
var wg sync.WaitGroup
for i := uint64(1); i < uint64(n); i++ {
a := (DatasetItemCount * i) / uint64(n)
b := (DatasetItemCount * (i + 1)) / uint64(n)
wg.Add(1)
go func(a, b uint64) {
defer wg.Done()
dataset.InitDataset(a, b-a)
}(a, b)
}
dataset.InitDataset(0, DatasetItemCount/uint64(n))
wg.Wait()
} }

46
dataset_full.go Normal file
View file

@ -0,0 +1,46 @@
package randomx
const DatasetSize = RANDOMX_DATASET_BASE_SIZE + RANDOMX_DATASET_EXTRA_SIZE
const DatasetItemCount = DatasetSize / CacheLineSize
type DatasetFull struct {
cache *Cache
Memory [DatasetItemCount]RegisterLine
}
func NewFullDataset(cache *Cache) *DatasetFull {
return &DatasetFull{
cache: cache,
}
}
func (d *DatasetFull) PrefetchDataset(address uint64) {
}
func (d *DatasetFull) ReadDataset(address uint64, r *RegisterLine) {
cache := &d.Memory[address/CacheLineSize]
for i := range r {
r[i] ^= cache[i]
}
}
func (d *DatasetFull) Cache() *Cache {
return d.cache
}
func (d *DatasetFull) Flags() uint64 {
return d.cache.Flags
}
func (d *DatasetFull) InitDataset(startItem, itemCount uint64) {
if startItem >= DatasetItemCount || itemCount > DatasetItemCount {
panic("out of range")
}
if startItem+itemCount > DatasetItemCount {
panic("out of range")
}
d.cache.InitDataset(d.Memory[startItem:startItem+itemCount], startItem, startItem+itemCount)
}

View file

@ -1,19 +1,25 @@
package randomx package randomx
type Randomx_DatasetLight struct { type DatasetLight struct {
Cache *Randomx_Cache cache *Cache
Memory []uint64
} }
func (d *Randomx_DatasetLight) PrefetchDataset(address uint64) { func NewLightDataset(cache *Cache) *DatasetLight {
return &DatasetLight{
cache: cache,
}
}
func (d *DatasetLight) PrefetchDataset(address uint64) {
} }
func (d *Randomx_DatasetLight) ReadDataset(address uint64, r, cache *RegisterLine) { func (d *DatasetLight) ReadDataset(address uint64, r *RegisterLine) {
if d.Cache.HasJIT() { var cache RegisterLine
d.Cache.InitDatasetItemJIT(cache, address/CacheLineSize) if d.cache.HasJIT() {
d.cache.InitDatasetItemJIT(&cache, address/CacheLineSize)
} else { } else {
d.Cache.InitDatasetItem(cache, address/CacheLineSize) d.cache.InitDatasetItem(&cache, address/CacheLineSize)
} }
for i := range r { for i := range r {
@ -21,10 +27,14 @@ func (d *Randomx_DatasetLight) ReadDataset(address uint64, r, cache *RegisterLin
} }
} }
func (d *Randomx_DatasetLight) Flags() uint64 { func (d *DatasetLight) Flags() uint64 {
return d.Cache.Flags return d.cache.Flags
} }
func (d *Randomx_DatasetLight) InitDataset(startItem, endItem uint64) { func (d *DatasetLight) Cache() *Cache {
//d.Cache.initDataset(d.Cache.Programs) return d.cache
}
func (d *DatasetLight) InitDataset(startItem, itemCount uint64) {
} }

2
go.mod
View file

@ -1,4 +1,4 @@
module git.gammaspectra.live/P2Pool/go-randomx/v2 module git.gammaspectra.live/P2Pool/go-randomx/v3
go 1.21 go 1.21

View file

@ -5,7 +5,7 @@ package randomx
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"git.gammaspectra.live/P2Pool/go-randomx/v2/asm" "git.gammaspectra.live/P2Pool/go-randomx/v3/asm"
) )
/* /*

View file

@ -31,7 +31,9 @@ package randomx
import ( import (
"fmt" "fmt"
"os"
"runtime" "runtime"
"slices"
) )
import "testing" import "testing"
@ -47,9 +49,9 @@ var Tests = []struct {
{[]byte("test key 001"), []byte("sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"), "e9ff4503201c0c2cca26d285c93ae883f9b1d30c9eb240b820756f2d5a7905fc"}, // test d {[]byte("test key 001"), []byte("sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"), "e9ff4503201c0c2cca26d285c93ae883f9b1d30c9eb240b820756f2d5a7905fc"}, // test d
} }
func Test_Randomx(t *testing.T) { func Test_RandomXLight(t *testing.T) {
c := Randomx_alloc_cache(0) c := NewCache(0)
for ix, tt := range Tests { for ix, tt := range Tests {
@ -62,7 +64,10 @@ func Test_Randomx(t *testing.T) {
} }
}() }()
vm := c.VM_Initialize() dataset := NewLightDataset(c)
dataset.InitDataset(0, DatasetItemCount)
vm := NewVM(dataset)
defer vm.Close() defer vm.Close()
var output_hash [32]byte var output_hash [32]byte
@ -74,57 +79,119 @@ func Test_Randomx(t *testing.T) {
} }
}) })
} }
} }
func Benchmark_RandomX(b *testing.B) { func Test_RandomXFull(t *testing.T) {
c := NewCache(0)
for ix, tt := range Tests {
t.Run(string(tt.key)+"_____"+string(tt.input), func(t *testing.T) {
c.Init(tt.key)
defer func() {
err := c.Close()
if err != nil {
t.Error(err)
}
}()
dataset := NewFullDataset(c)
InitDatasetParallel(dataset, runtime.NumCPU())
vm := NewVM(dataset)
defer vm.Close()
var output_hash [32]byte
vm.CalculateHash(tt.input, &output_hash)
actual := fmt.Sprintf("%x", output_hash)
if actual != tt.expected {
t.Errorf("#%d Fib(%v): expected %s, actual %s", ix, tt.key, tt.expected, actual)
}
})
// cleanup 2GiB between runs
runtime.GC()
}
}
var BenchmarkTest = Tests[0]
var BenchmarkCache *Cache
var BenchmarkDatasetLight *DatasetLight
var BenchmarkDatasetFull *DatasetFull
func TestMain(m *testing.M) {
if slices.Contains(os.Args, "-test.bench") {
//init light and full dataset
BenchmarkCache = NewCache(0)
BenchmarkCache.Init(BenchmarkTest.key)
BenchmarkDatasetLight = NewLightDataset(BenchmarkCache)
BenchmarkDatasetLight.InitDataset(0, DatasetItemCount)
BenchmarkDatasetFull = NewFullDataset(BenchmarkCache)
InitDatasetParallel(BenchmarkDatasetFull, runtime.NumCPU())
defer BenchmarkCache.Close()
}
os.Exit(m.Run())
}
func Benchmark_RandomXLight(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
tt := Tests[0] vm := NewVM(BenchmarkDatasetLight)
c := Randomx_alloc_cache(0)
c.Init(tt.key)
defer func() {
err := c.Close()
if err != nil {
b.Error(err)
}
}()
vm := c.VM_Initialize()
defer vm.Close() defer vm.Close()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
var output_hash [32]byte var output_hash [32]byte
vm.CalculateHash(tt.input, &output_hash) vm.CalculateHash(BenchmarkTest.input, &output_hash)
runtime.KeepAlive(output_hash) runtime.KeepAlive(output_hash)
} }
} }
func Benchmark_RandomXParallel(b *testing.B) { func Benchmark_RandomXFull(b *testing.B) {
b.ReportAllocs() b.ReportAllocs()
tt := Tests[0] vm := NewVM(BenchmarkDatasetFull)
defer vm.Close()
c := Randomx_alloc_cache(0)
c.Init(tt.key)
defer func() {
err := c.Close()
if err != nil {
b.Error(err)
}
}()
b.ResetTimer() b.ResetTimer()
for i := 0; i < b.N; i++ {
var output_hash [32]byte
vm.CalculateHash(BenchmarkTest.input, &output_hash)
runtime.KeepAlive(output_hash)
}
}
func Benchmark_RandomXLight_Parallel(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var output_hash [32]byte var output_hash [32]byte
vm := c.VM_Initialize()
vm := NewVM(BenchmarkDatasetLight)
defer vm.Close() defer vm.Close()
for pb.Next() { for pb.Next() {
vm.CalculateHash(tt.input, &output_hash) vm.CalculateHash(BenchmarkTest.input, &output_hash)
runtime.KeepAlive(output_hash)
}
})
}
func Benchmark_RandomXFull_Parallel(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
var output_hash [32]byte
vm := NewVM(BenchmarkDatasetFull)
defer vm.Close()
for pb.Next() {
vm.CalculateHash(BenchmarkTest.input, &output_hash)
runtime.KeepAlive(output_hash) runtime.KeepAlive(output_hash)
} }
}) })

View file

@ -307,11 +307,11 @@ var slot10 = []*Instruction{&IMUL_RCP}
// SuperScalarInstruction superscalar program is built with superscalar instructions // SuperScalarInstruction superscalar program is built with superscalar instructions
type SuperScalarInstruction struct { type SuperScalarInstruction struct {
Opcode byte Opcode byte
Dst_Reg int Dst int
Src_Reg int Src int
Mod byte Mod byte
Imm32 uint32 Imm32 uint32
Type int Imm64 uint64
OpGroup int OpGroup int
OpGroupPar int OpGroupPar int
GroupParIsSource int GroupParIsSource int
@ -320,17 +320,15 @@ type SuperScalarInstruction struct {
} }
func (sins *SuperScalarInstruction) FixSrcReg() { func (sins *SuperScalarInstruction) FixSrcReg() {
if sins.Src_Reg >= 0 { if sins.Src == 0xff {
// do nothing sins.Src = sins.Dst
} else {
sins.Src_Reg = sins.Dst_Reg
} }
} }
func (sins *SuperScalarInstruction) Reset() { func (sins *SuperScalarInstruction) Reset() {
sins.Opcode = 99 sins.Opcode = 99
sins.Src_Reg = -1 sins.Src = 0xff
sins.Dst_Reg = -1 sins.Dst = 0xff
sins.CanReuse = false sins.CanReuse = false
sins.GroupParIsSource = 0 sins.GroupParIsSource = 0
} }
@ -406,6 +404,8 @@ func create(sins *SuperScalarInstruction, ins *Instruction, gen *Blake2Generator
} }
} }
sins.Imm64 = randomx_reciprocal(sins.Imm32)
sins.OpGroup = S_IMUL_RCP sins.OpGroup = S_IMUL_RCP
default: default:
@ -450,11 +450,11 @@ func CreateSuperScalarInstruction(sins *SuperScalarInstruction, gen *Blake2Gener
type SuperScalarProgram []SuperScalarInstruction type SuperScalarProgram []SuperScalarInstruction
func (p SuperScalarProgram) setAddressRegister(addressRegister int) { func (p SuperScalarProgram) setAddressRegister(addressRegister int) {
p[0].Dst_Reg = addressRegister p[0].Dst = addressRegister
} }
func (p SuperScalarProgram) AddressRegister() int { func (p SuperScalarProgram) AddressRegister() int {
return p[0].Dst_Reg return p[0].Dst
} }
func (p SuperScalarProgram) Program() []SuperScalarInstruction { func (p SuperScalarProgram) Program() []SuperScalarInstruction {
return p[1:] return p[1:]
@ -569,9 +569,9 @@ func Build_SuperScalar_Program(gen *Blake2Generator) SuperScalarProgram {
depcycle = scheduleCycle + mop.GetLatency() // calculate when will the result be ready depcycle = scheduleCycle + mop.GetLatency() // calculate when will the result be ready
if macro_op_index == sins.ins.ResultOP { // fix me if macro_op_index == sins.ins.ResultOP { // fix me
registers[sins.Dst_Reg].Latency = depcycle registers[sins.Dst].Latency = depcycle
registers[sins.Dst_Reg].LastOpGroup = sins.OpGroup registers[sins.Dst].LastOpGroup = sins.OpGroup
registers[sins.Dst_Reg].LastOpPar = sins.OpGroupPar registers[sins.Dst].LastOpPar = sins.OpGroupPar
} }
@ -609,12 +609,12 @@ func Build_SuperScalar_Program(gen *Blake2Generator) SuperScalarProgram {
if i == 0 { if i == 0 {
continue continue
} }
lastdst := asic_latencies[program[i].Dst_Reg] + 1 lastdst := asic_latencies[program[i].Dst] + 1
lastsrc := 0 lastsrc := 0
if program[i].Dst_Reg != program[i].Src_Reg { if program[i].Dst != program[i].Src {
lastsrc = asic_latencies[program[i].Src_Reg] + 1 lastsrc = asic_latencies[program[i].Src] + 1
} }
asic_latencies[program[i].Dst_Reg] = max(lastdst, lastsrc) asic_latencies[program[i].Dst] = max(lastdst, lastsrc)
} }
asic_latency_max := 0 asic_latency_max := 0
@ -719,18 +719,18 @@ func (sins *SuperScalarInstruction) SelectSource(preAllocatedAvailableRegisters
if len(available_registers) == 2 && sins.Opcode == S_IADD_RS { if len(available_registers) == 2 && sins.Opcode == S_IADD_RS {
if available_registers[0] == RegisterNeedsDisplacement || available_registers[1] == RegisterNeedsDisplacement { if available_registers[0] == RegisterNeedsDisplacement || available_registers[1] == RegisterNeedsDisplacement {
sins.Src_Reg = RegisterNeedsDisplacement sins.Src = RegisterNeedsDisplacement
sins.OpGroupPar = sins.Src_Reg sins.OpGroupPar = sins.Src
return true return true
} }
} }
if selectRegister(available_registers, gen, &sins.Src_Reg) { if selectRegister(available_registers, gen, &sins.Src) {
if sins.GroupParIsSource == 0 { if sins.GroupParIsSource == 0 {
} else { } else {
sins.OpGroupPar = sins.Src_Reg sins.OpGroupPar = sins.Src
} }
return true return true
} }
@ -741,7 +741,7 @@ func (sins *SuperScalarInstruction) SelectDestination(preAllocatedAvailableRegis
preAllocatedAvailableRegisters = preAllocatedAvailableRegisters[:0] preAllocatedAvailableRegisters = preAllocatedAvailableRegisters[:0]
for i := range Registers { for i := range Registers {
if Registers[i].Latency <= cycle && (sins.CanReuse || i != sins.Src_Reg) && if Registers[i].Latency <= cycle && (sins.CanReuse || i != sins.Src) &&
(allowChainedMul || sins.OpGroup != S_IMUL_R || Registers[i].LastOpGroup != S_IMUL_R) && (allowChainedMul || sins.OpGroup != S_IMUL_R || Registers[i].LastOpGroup != S_IMUL_R) &&
(Registers[i].LastOpGroup != sins.OpGroup || Registers[i].LastOpPar != sins.OpGroupPar) && (Registers[i].LastOpGroup != sins.OpGroup || Registers[i].LastOpPar != sins.OpGroupPar) &&
(sins.Opcode != S_IADD_RS || i != RegisterNeedsDisplacement) { (sins.Opcode != S_IADD_RS || i != RegisterNeedsDisplacement) {
@ -749,7 +749,7 @@ func (sins *SuperScalarInstruction) SelectDestination(preAllocatedAvailableRegis
} }
} }
return selectRegister(preAllocatedAvailableRegisters, gen, &sins.Dst_Reg) return selectRegister(preAllocatedAvailableRegisters, gen, &sins.Dst)
} }
func selectRegister(available_registers []int, gen *Blake2Generator, reg *int) bool { func selectRegister(available_registers []int, gen *Blake2Generator, reg *int) bool {
@ -776,25 +776,25 @@ func executeSuperscalar(p []SuperScalarInstruction, r *RegisterLine) {
ins := &p[i] ins := &p[i]
switch ins.Opcode { switch ins.Opcode {
case S_ISUB_R: case S_ISUB_R:
r[ins.Dst_Reg] -= r[ins.Src_Reg] r[ins.Dst] -= r[ins.Src]
case S_IXOR_R: case S_IXOR_R:
r[ins.Dst_Reg] ^= r[ins.Src_Reg] r[ins.Dst] ^= r[ins.Src]
case S_IADD_RS: case S_IADD_RS:
r[ins.Dst_Reg] += r[ins.Src_Reg] << ins.Imm32 r[ins.Dst] += r[ins.Src] << ins.Imm32
case S_IMUL_R: case S_IMUL_R:
r[ins.Dst_Reg] *= r[ins.Src_Reg] r[ins.Dst] *= r[ins.Src]
case S_IROR_C: case S_IROR_C:
r[ins.Dst_Reg] = bits.RotateLeft64(r[ins.Dst_Reg], 0-int(ins.Imm32)) r[ins.Dst] = bits.RotateLeft64(r[ins.Dst], 0-int(ins.Imm32))
case S_IADD_C7, S_IADD_C8, S_IADD_C9: case S_IADD_C7, S_IADD_C8, S_IADD_C9:
r[ins.Dst_Reg] += signExtend2sCompl(ins.Imm32) r[ins.Dst] += signExtend2sCompl(ins.Imm32)
case S_IXOR_C7, S_IXOR_C8, S_IXOR_C9: case S_IXOR_C7, S_IXOR_C8, S_IXOR_C9:
r[ins.Dst_Reg] ^= signExtend2sCompl(ins.Imm32) r[ins.Dst] ^= signExtend2sCompl(ins.Imm32)
case S_IMULH_R: case S_IMULH_R:
r[ins.Dst_Reg], _ = bits.Mul64(r[ins.Dst_Reg], r[ins.Src_Reg]) r[ins.Dst], _ = bits.Mul64(r[ins.Dst], r[ins.Src])
case S_ISMULH_R: case S_ISMULH_R:
r[ins.Dst_Reg] = smulh(int64(r[ins.Dst_Reg]), int64(r[ins.Src_Reg])) r[ins.Dst] = smulh(int64(r[ins.Dst]), int64(r[ins.Src]))
case S_IMUL_RCP: case S_IMUL_RCP:
r[ins.Dst_Reg] *= randomx_reciprocal(ins.Imm32) r[ins.Dst] *= ins.Imm64
} }
} }

View file

@ -27,8 +27,8 @@ func generateSuperscalarCode(scalarProgram SuperScalarProgram) SuperScalarProgra
for i := range p { for i := range p {
instr := &p[i] instr := &p[i]
dst := instr.Dst_Reg % RegistersCount dst := instr.Dst % RegistersCount
src := instr.Src_Reg % RegistersCount src := instr.Src % RegistersCount
switch instr.Opcode { switch instr.Opcode {
case S_ISUB_R: case S_ISUB_R:
@ -80,9 +80,9 @@ func generateSuperscalarCode(scalarProgram SuperScalarProgram) SuperScalarProgra
program = append(program, byte(0xc2+8*dst)) program = append(program, byte(0xc2+8*dst))
case S_IMUL_RCP: case S_IMUL_RCP:
program = append(program, MOV_RAX_I...) program = append(program, MOV_RAX_I...)
program = binary.LittleEndian.AppendUint64(program, randomx_reciprocal(instr.Imm32)) program = binary.LittleEndian.AppendUint64(program, instr.Imm64)
program = append(program, REX_IMUL_RM...) program = append(program, REX_IMUL_RM...)
program = append(program, byte(0xc0+8*instr.Dst_Reg)) program = append(program, byte(0xc0+8*instr.Dst))
default: default:
panic("unreachable") panic("unreachable")
} }

24
vm.go
View file

@ -30,7 +30,7 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package randomx package randomx
import ( import (
"git.gammaspectra.live/P2Pool/go-randomx/v2/aes" "git.gammaspectra.live/P2Pool/go-randomx/v3/aes"
"math" "math"
"runtime" "runtime"
"unsafe" "unsafe"
@ -45,11 +45,25 @@ type REG struct {
type VM struct { type VM struct {
ScratchPad ScratchPad ScratchPad ScratchPad
Dataset Randomx_Dataset Dataset Dataset
JITProgram VMProgramFunc JITProgram VMProgramFunc
} }
func NewVM(dataset Dataset) *VM {
vm := &VM{
Dataset: dataset,
}
if dataset.Cache().HasJIT() {
vm.JITProgram = mapProgram(nil, int(RandomXCodeSize))
if dataset.Flags()&RANDOMX_FLAG_SECURE == 0 {
mapProgramRWX(vm.JITProgram)
}
}
return vm
}
// Run calculate hash based on input // Run calculate hash based on input
// Warning: Underlying callers will run float64 SetRoundingMode directly // Warning: Underlying callers will run float64 SetRoundingMode directly
// It is the caller's responsibility to set and restore the mode to IEEE 754 roundTiesToEven between full executions // It is the caller's responsibility to set and restore the mode to IEEE 754 roundTiesToEven between full executions
@ -86,7 +100,7 @@ func (vm *VM) Run(inputHash [64]byte, roundingMode uint8) (reg RegisterFile) {
addressRegisters >>= 1 addressRegisters >>= 1
} }
datasetOffset := (entropy[13] % (DATASETEXTRAITEMS + 1)) * CacheLineSize datasetOffset := (entropy[13] % (DatasetExtraItems + 1)) * CacheLineSize
eMask := [2]uint64{EMask(entropy[14]), EMask(entropy[15])} eMask := [2]uint64{EMask(entropy[14]), EMask(entropy[15])}
@ -95,8 +109,6 @@ func (vm *VM) Run(inputHash [64]byte, roundingMode uint8) (reg RegisterFile) {
spAddr0 := mem.mx spAddr0 := mem.mx
spAddr1 := mem.ma spAddr1 := mem.ma
var rlCache RegisterLine
if vm.JITProgram != nil { if vm.JITProgram != nil {
if vm.Dataset.Flags()&RANDOMX_FLAG_SECURE > 0 { if vm.Dataset.Flags()&RANDOMX_FLAG_SECURE > 0 {
mapProgramRW(vm.JITProgram) mapProgramRW(vm.JITProgram)
@ -143,7 +155,7 @@ func (vm *VM) Run(inputHash [64]byte, roundingMode uint8) (reg RegisterFile) {
vm.Dataset.PrefetchDataset(datasetOffset + mem.mx) vm.Dataset.PrefetchDataset(datasetOffset + mem.mx)
// execute diffuser superscalar program to get dataset 64 bytes // execute diffuser superscalar program to get dataset 64 bytes
vm.Dataset.ReadDataset(datasetOffset+mem.ma, &reg.R, &rlCache) vm.Dataset.ReadDataset(datasetOffset+mem.ma, &reg.R)
// swap the elements // swap the elements
mem.mx, mem.ma = mem.ma, mem.mx mem.mx, mem.ma = mem.ma, mem.mx

View file

@ -3,7 +3,7 @@
package randomx package randomx
import ( import (
"git.gammaspectra.live/P2Pool/go-randomx/v2/asm" "git.gammaspectra.live/P2Pool/go-randomx/v3/asm"
"math" "math"
"math/bits" "math/bits"
) )

View file

@ -30,7 +30,7 @@ USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package randomx package randomx
import ( import (
"git.gammaspectra.live/P2Pool/go-randomx/v2/aes" "git.gammaspectra.live/P2Pool/go-randomx/v3/aes"
"unsafe" "unsafe"
) )
import "encoding/binary" import "encoding/binary"