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 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).
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.
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.
| Platform | Hard Float | Hard AES | JIT | Native | purego | Notes |
|:-----------:|:----------:|:--------:|:---:|:------:|:------:|:----------------:|
| **386** | ✅ | ❌ | ❌ | ✅ | ✅ | |
| **amd64** | ✅ | ✅ | ✅* | ✅ | ✅ | JIT only on Unix |
| **arm** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **arm64** | ✅ | ❌ | ❌ | ✅ | ✅ | |
| **mips** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **mips64** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **riscv64** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **wasm** | ❌ | ❌ | ❌ | ❌ | ✅ | |
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.
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.
Native hard float can be added with supporting rounding mode under _asm_.
JIT only supported under Unix systems (Linux, *BSD, macOS), and can be hard-disabled via the `disable_jit` build flag, or at runtime.

View file

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

View file

@ -3,29 +3,29 @@
package aes
import (
_ "git.gammaspectra.live/P2Pool/go-randomx/v2/asm"
_ "git.gammaspectra.live/P2Pool/go-randomx/v3/asm"
"golang.org/x/sys/cpu"
_ "unsafe"
)
//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)
//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)
//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)
//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)
//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)
var supportsAES = cpu.X86.HasAES

View file

@ -1,8 +1,8 @@
package randomx
import (
"git.gammaspectra.live/P2Pool/go-randomx/v2/argon2"
"git.gammaspectra.live/P2Pool/go-randomx/v2/keys"
"git.gammaspectra.live/P2Pool/go-randomx/v3/argon2"
"git.gammaspectra.live/P2Pool/go-randomx/v3/keys"
"runtime"
"slices"
"unsafe"
@ -15,7 +15,7 @@ func (m *MemoryBlock) GetLine(addr uint64) *RegisterLine {
return (*RegisterLine)(unsafe.Pointer(unsafe.SliceData(m[addr : addr+8 : addr+8])))
}
type Randomx_Cache struct {
type Cache struct {
Blocks []MemoryBlock
Programs [RANDOMX_PROGRAM_COUNT]SuperScalarProgram
@ -25,36 +25,20 @@ type Randomx_Cache struct {
Flags uint64
}
func Randomx_alloc_cache(flags uint64) *Randomx_Cache {
func NewCache(flags uint64) *Cache {
if flags == RANDOMX_FLAG_DEFAULT {
flags = RANDOMX_FLAG_JIT
}
return &Randomx_Cache{
return &Cache{
Flags: flags,
}
}
func (cache *Randomx_Cache) HasJIT() bool {
func (cache *Cache) HasJIT() bool {
return cache.Flags&RANDOMX_FLAG_JIT > 0 && cache.JitPrograms[0] != nil
}
func (cache *Randomx_Cache) VM_Initialize() *VM {
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 {
func (cache *Cache) Close() error {
for _, p := range cache.JitPrograms {
if p != nil {
err := p.Close()
@ -66,10 +50,12 @@ func (cache *Randomx_Cache) Close() error {
return nil
}
func (cache *Randomx_Cache) Init(key []byte) {
// Lock due to external JIT madness
runtime.LockOSThread()
defer runtime.UnlockOSThread()
func (cache *Cache) Init(key []byte) {
if cache.Flags&RANDOMX_FLAG_JIT > 0 {
// Lock due to external JIT madness
runtime.LockOSThread()
defer runtime.UnlockOSThread()
}
kkey := slices.Clone(key)
@ -93,7 +79,7 @@ func (cache *Randomx_Cache) Init(key []byte) {
const Mask = CacheSize/CacheLineSize - 1
// 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
@ -101,7 +87,7 @@ func (cache *Randomx_Cache) GetMixBlock(addr uint64) *RegisterLine {
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
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
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) {
panic("todo")
func (cache *Cache) InitDataset(dataset []RegisterLine, startItem, endItem uint64) {
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
import "git.gammaspectra.live/P2Pool/go-randomx/v2/argon2"
import "git.gammaspectra.live/P2Pool/go-randomx/v3/argon2"
// see reference configuration.h
// 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.
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 RANDOMX_DATASET_ITEM_SIZE uint64 = 64

View file

@ -1,8 +1,30 @@
package randomx
type Randomx_Dataset interface {
InitDataset(startItem, endItem uint64)
ReadDataset(address uint64, r, cache *RegisterLine)
import "sync"
type Dataset interface {
InitDataset(startItem, itemCount uint64)
ReadDataset(address uint64, r *RegisterLine)
PrefetchDataset(address 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
type Randomx_DatasetLight struct {
Cache *Randomx_Cache
Memory []uint64
type DatasetLight struct {
cache *Cache
}
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) {
if d.Cache.HasJIT() {
d.Cache.InitDatasetItemJIT(cache, address/CacheLineSize)
func (d *DatasetLight) ReadDataset(address uint64, r *RegisterLine) {
var cache RegisterLine
if d.cache.HasJIT() {
d.cache.InitDatasetItemJIT(&cache, address/CacheLineSize)
} else {
d.Cache.InitDatasetItem(cache, address/CacheLineSize)
d.cache.InitDatasetItem(&cache, address/CacheLineSize)
}
for i := range r {
@ -21,10 +27,14 @@ func (d *Randomx_DatasetLight) ReadDataset(address uint64, r, cache *RegisterLin
}
}
func (d *Randomx_DatasetLight) Flags() uint64 {
return d.Cache.Flags
func (d *DatasetLight) Flags() uint64 {
return d.cache.Flags
}
func (d *Randomx_DatasetLight) InitDataset(startItem, endItem uint64) {
//d.Cache.initDataset(d.Cache.Programs)
func (d *DatasetLight) Cache() *Cache {
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

View file

@ -5,7 +5,7 @@ package randomx
import (
"bytes"
"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 (
"fmt"
"os"
"runtime"
"slices"
)
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
}
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 {
@ -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()
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()
tt := Tests[0]
c := Randomx_alloc_cache(0)
c.Init(tt.key)
defer func() {
err := c.Close()
if err != nil {
b.Error(err)
}
}()
vm := c.VM_Initialize()
vm := NewVM(BenchmarkDatasetLight)
defer vm.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var output_hash [32]byte
vm.CalculateHash(tt.input, &output_hash)
vm.CalculateHash(BenchmarkTest.input, &output_hash)
runtime.KeepAlive(output_hash)
}
}
func Benchmark_RandomXParallel(b *testing.B) {
func Benchmark_RandomXFull(b *testing.B) {
b.ReportAllocs()
tt := Tests[0]
c := Randomx_alloc_cache(0)
c.Init(tt.key)
defer func() {
err := c.Close()
if err != nil {
b.Error(err)
}
}()
vm := NewVM(BenchmarkDatasetFull)
defer vm.Close()
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) {
var output_hash [32]byte
vm := c.VM_Initialize()
vm := NewVM(BenchmarkDatasetLight)
defer vm.Close()
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)
}
})

View file

@ -307,11 +307,11 @@ var slot10 = []*Instruction{&IMUL_RCP}
// SuperScalarInstruction superscalar program is built with superscalar instructions
type SuperScalarInstruction struct {
Opcode byte
Dst_Reg int
Src_Reg int
Dst int
Src int
Mod byte
Imm32 uint32
Type int
Imm64 uint64
OpGroup int
OpGroupPar int
GroupParIsSource int
@ -320,17 +320,15 @@ type SuperScalarInstruction struct {
}
func (sins *SuperScalarInstruction) FixSrcReg() {
if sins.Src_Reg >= 0 {
// do nothing
} else {
sins.Src_Reg = sins.Dst_Reg
if sins.Src == 0xff {
sins.Src = sins.Dst
}
}
func (sins *SuperScalarInstruction) Reset() {
sins.Opcode = 99
sins.Src_Reg = -1
sins.Dst_Reg = -1
sins.Src = 0xff
sins.Dst = 0xff
sins.CanReuse = false
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
default:
@ -450,11 +450,11 @@ func CreateSuperScalarInstruction(sins *SuperScalarInstruction, gen *Blake2Gener
type SuperScalarProgram []SuperScalarInstruction
func (p SuperScalarProgram) setAddressRegister(addressRegister int) {
p[0].Dst_Reg = addressRegister
p[0].Dst = addressRegister
}
func (p SuperScalarProgram) AddressRegister() int {
return p[0].Dst_Reg
return p[0].Dst
}
func (p SuperScalarProgram) Program() []SuperScalarInstruction {
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
if macro_op_index == sins.ins.ResultOP { // fix me
registers[sins.Dst_Reg].Latency = depcycle
registers[sins.Dst_Reg].LastOpGroup = sins.OpGroup
registers[sins.Dst_Reg].LastOpPar = sins.OpGroupPar
registers[sins.Dst].Latency = depcycle
registers[sins.Dst].LastOpGroup = sins.OpGroup
registers[sins.Dst].LastOpPar = sins.OpGroupPar
}
@ -609,12 +609,12 @@ func Build_SuperScalar_Program(gen *Blake2Generator) SuperScalarProgram {
if i == 0 {
continue
}
lastdst := asic_latencies[program[i].Dst_Reg] + 1
lastdst := asic_latencies[program[i].Dst] + 1
lastsrc := 0
if program[i].Dst_Reg != program[i].Src_Reg {
lastsrc = asic_latencies[program[i].Src_Reg] + 1
if program[i].Dst != program[i].Src {
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
@ -719,18 +719,18 @@ func (sins *SuperScalarInstruction) SelectSource(preAllocatedAvailableRegisters
if len(available_registers) == 2 && sins.Opcode == S_IADD_RS {
if available_registers[0] == RegisterNeedsDisplacement || available_registers[1] == RegisterNeedsDisplacement {
sins.Src_Reg = RegisterNeedsDisplacement
sins.OpGroupPar = sins.Src_Reg
sins.Src = RegisterNeedsDisplacement
sins.OpGroupPar = sins.Src
return true
}
}
if selectRegister(available_registers, gen, &sins.Src_Reg) {
if selectRegister(available_registers, gen, &sins.Src) {
if sins.GroupParIsSource == 0 {
} else {
sins.OpGroupPar = sins.Src_Reg
sins.OpGroupPar = sins.Src
}
return true
}
@ -741,7 +741,7 @@ func (sins *SuperScalarInstruction) SelectDestination(preAllocatedAvailableRegis
preAllocatedAvailableRegisters = preAllocatedAvailableRegisters[:0]
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) &&
(Registers[i].LastOpGroup != sins.OpGroup || Registers[i].LastOpPar != sins.OpGroupPar) &&
(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 {
@ -776,25 +776,25 @@ func executeSuperscalar(p []SuperScalarInstruction, r *RegisterLine) {
ins := &p[i]
switch ins.Opcode {
case S_ISUB_R:
r[ins.Dst_Reg] -= r[ins.Src_Reg]
r[ins.Dst] -= r[ins.Src]
case S_IXOR_R:
r[ins.Dst_Reg] ^= r[ins.Src_Reg]
r[ins.Dst] ^= r[ins.Src]
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:
r[ins.Dst_Reg] *= r[ins.Src_Reg]
r[ins.Dst] *= r[ins.Src]
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:
r[ins.Dst_Reg] += signExtend2sCompl(ins.Imm32)
r[ins.Dst] += signExtend2sCompl(ins.Imm32)
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:
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:
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:
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 {
instr := &p[i]
dst := instr.Dst_Reg % RegistersCount
src := instr.Src_Reg % RegistersCount
dst := instr.Dst % RegistersCount
src := instr.Src % RegistersCount
switch instr.Opcode {
case S_ISUB_R:
@ -80,9 +80,9 @@ func generateSuperscalarCode(scalarProgram SuperScalarProgram) SuperScalarProgra
program = append(program, byte(0xc2+8*dst))
case S_IMUL_RCP:
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, byte(0xc0+8*instr.Dst_Reg))
program = append(program, byte(0xc0+8*instr.Dst))
default:
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
import (
"git.gammaspectra.live/P2Pool/go-randomx/v2/aes"
"git.gammaspectra.live/P2Pool/go-randomx/v3/aes"
"math"
"runtime"
"unsafe"
@ -45,11 +45,25 @@ type REG struct {
type VM struct {
ScratchPad ScratchPad
Dataset Randomx_Dataset
Dataset Dataset
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
// 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
@ -86,7 +100,7 @@ func (vm *VM) Run(inputHash [64]byte, roundingMode uint8) (reg RegisterFile) {
addressRegisters >>= 1
}
datasetOffset := (entropy[13] % (DATASETEXTRAITEMS + 1)) * CacheLineSize
datasetOffset := (entropy[13] % (DatasetExtraItems + 1)) * CacheLineSize
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
spAddr1 := mem.ma
var rlCache RegisterLine
if vm.JITProgram != nil {
if vm.Dataset.Flags()&RANDOMX_FLAG_SECURE > 0 {
mapProgramRW(vm.JITProgram)
@ -143,7 +155,7 @@ func (vm *VM) Run(inputHash [64]byte, roundingMode uint8) (reg RegisterFile) {
vm.Dataset.PrefetchDataset(datasetOffset + mem.mx)
// 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
mem.mx, mem.ma = mem.ma, mem.mx

View file

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

View file

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