Compare commits

..

No commits in common. "9f77218ff8ed36af001290a261ef6537d049cb3d" and "d20dd880cebca048011076865d3db44ec771df71" have entirely different histories.

16 changed files with 165 additions and 320 deletions

View file

@ -1,38 +1,29 @@
# 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, native float64 ops, some assembly, but with optional soft float _purego_ implementation.
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.
All test cases pass properly.
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 | ❌ | ✅* | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
JIT is supported on a few platforms but can be hard-disabled via the `disable_jit` build flag, or at runtime.
A pure Golang implementation can be used on platforms without hard float support or via the `purego` build flag manually.
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.
| Platform | Hard Float | Hard AES | JIT | Native | purego | Notes |
|:-----------:|:----------:|:--------:|:---:|:------:|:------:|:----------------:|
| **386** | ✅ | ❌ | ❌ | ✅ | ✅ | |
| **amd64** | ✅ | ✅ | ✅* | ✅ | ✅ | JIT only on Unix |
| **arm** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **arm64** | ✅ | ❌ | ❌ | ✅ | ✅ | |
| **mips** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **mips64** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **riscv64** | ❌ | ❌ | ❌ | ❌ | ✅ | |
| **wasm** | ❌ | ❌ | ❌ | ❌ | ✅ | |
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.
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_.

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/v3/keys"
"git.gammaspectra.live/P2Pool/go-randomx/v2/keys"
"unsafe"
)

View file

@ -3,29 +3,29 @@
package aes
import (
_ "git.gammaspectra.live/P2Pool/go-randomx/v3/asm"
_ "git.gammaspectra.live/P2Pool/go-randomx/v2/asm"
"golang.org/x/sys/cpu"
_ "unsafe"
)
//go:noescape
//go:linkname hard_aesdec git.gammaspectra.live/P2Pool/go-randomx/v3/asm.aesdec
//go:linkname hard_aesdec git.gammaspectra.live/P2Pool/go-randomx/v2/asm.aesdec
func hard_aesdec(state *[4]uint32, key *[4]uint32)
//go:noescape
//go:linkname hard_aesenc git.gammaspectra.live/P2Pool/go-randomx/v3/asm.aesenc
//go:linkname hard_aesenc git.gammaspectra.live/P2Pool/go-randomx/v2/asm.aesenc
func hard_aesenc(state *[4]uint32, key *[4]uint32)
//go:noescape
//go:linkname hard_aesroundtrip_decenc git.gammaspectra.live/P2Pool/go-randomx/v3/asm.aesroundtrip_decenc
//go:linkname hard_aesroundtrip_decenc git.gammaspectra.live/P2Pool/go-randomx/v2/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/v3/asm.aesroundtrip_encdec
//go:linkname hard_aesroundtrip_encdec git.gammaspectra.live/P2Pool/go-randomx/v2/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/v3/asm.aesroundtrip_encdec1
//go:linkname hard_aesroundtrip_encdec1 git.gammaspectra.live/P2Pool/go-randomx/v2/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/v3/argon2"
"git.gammaspectra.live/P2Pool/go-randomx/v3/keys"
"git.gammaspectra.live/P2Pool/go-randomx/v2/argon2"
"git.gammaspectra.live/P2Pool/go-randomx/v2/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 Cache struct {
type Randomx_Cache struct {
Blocks []MemoryBlock
Programs [RANDOMX_PROGRAM_COUNT]SuperScalarProgram
@ -25,20 +25,36 @@ type Cache struct {
Flags uint64
}
func NewCache(flags uint64) *Cache {
func Randomx_alloc_cache(flags uint64) *Randomx_Cache {
if flags == RANDOMX_FLAG_DEFAULT {
flags = RANDOMX_FLAG_JIT
}
return &Cache{
return &Randomx_Cache{
Flags: flags,
}
}
func (cache *Cache) HasJIT() bool {
func (cache *Randomx_Cache) HasJIT() bool {
return cache.Flags&RANDOMX_FLAG_JIT > 0 && cache.JitPrograms[0] != nil
}
func (cache *Cache) Close() error {
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 {
for _, p := range cache.JitPrograms {
if p != nil {
err := p.Close()
@ -50,12 +66,10 @@ func (cache *Cache) Close() error {
return nil
}
func (cache *Cache) Init(key []byte) {
if cache.Flags&RANDOMX_FLAG_JIT > 0 {
// Lock due to external JIT madness
runtime.LockOSThread()
defer runtime.UnlockOSThread()
}
func (cache *Randomx_Cache) Init(key []byte) {
// Lock due to external JIT madness
runtime.LockOSThread()
defer runtime.UnlockOSThread()
kkey := slices.Clone(key)
@ -79,7 +93,7 @@ func (cache *Cache) Init(key []byte) {
const Mask = CacheSize/CacheLineSize - 1
// GetMixBlock fetch a 64 byte block in uint64 form
func (cache *Cache) GetMixBlock(addr uint64) *RegisterLine {
func (cache *Randomx_Cache) GetMixBlock(addr uint64) *RegisterLine {
addr = (addr & Mask) * CacheLineSize
@ -87,7 +101,7 @@ func (cache *Cache) GetMixBlock(addr uint64) *RegisterLine {
return cache.Blocks[block].GetLine(addr % 1024)
}
func (cache *Cache) InitDatasetItem(rl *RegisterLine, itemNumber uint64) {
func (cache *Randomx_Cache) InitDatasetItem(rl *RegisterLine, itemNumber uint64) {
registerValue := itemNumber
rl[0] = (itemNumber + 1) * keys.SuperScalar_Constants[0]
@ -115,7 +129,7 @@ func (cache *Cache) InitDatasetItem(rl *RegisterLine, itemNumber uint64) {
}
}
func (cache *Cache) InitDatasetItemJIT(rl *RegisterLine, itemNumber uint64) {
func (cache *Randomx_Cache) InitDatasetItemJIT(rl *RegisterLine, itemNumber uint64) {
registerValue := itemNumber
rl[0] = (itemNumber + 1) * keys.SuperScalar_Constants[0]
@ -141,12 +155,9 @@ func (cache *Cache) InitDatasetItemJIT(rl *RegisterLine, itemNumber uint64) {
}
}
func (cache *Cache) InitDataset(dataset []RegisterLine, startItem, endItem uint64) {
func (cache *Randomx_Cache) initDataset(dataset []RegisterLine, startItem, endItem uint64) {
panic("todo")
for itemNumber := startItem; itemNumber < endItem; itemNumber, dataset = itemNumber+1, dataset[1:] {
if cache.HasJIT() {
cache.InitDatasetItemJIT(&dataset[0], itemNumber)
} else {
cache.InitDatasetItem(&dataset[0], itemNumber)
}
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/v3/argon2"
import "git.gammaspectra.live/P2Pool/go-randomx/v2/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,30 +1,8 @@
package randomx
import "sync"
type Dataset interface {
InitDataset(startItem, itemCount uint64)
ReadDataset(address uint64, r *RegisterLine)
type Randomx_Dataset interface {
InitDataset(startItem, endItem uint64)
ReadDataset(address uint64, r, cache *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()
}

View file

@ -1,46 +0,0 @@
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,25 +1,19 @@
package randomx
type DatasetLight struct {
cache *Cache
type Randomx_DatasetLight struct {
Cache *Randomx_Cache
Memory []uint64
}
func NewLightDataset(cache *Cache) *DatasetLight {
return &DatasetLight{
cache: cache,
}
}
func (d *DatasetLight) PrefetchDataset(address uint64) {
func (d *Randomx_DatasetLight) PrefetchDataset(address uint64) {
}
func (d *DatasetLight) ReadDataset(address uint64, r *RegisterLine) {
var cache RegisterLine
if d.cache.HasJIT() {
d.cache.InitDatasetItemJIT(&cache, address/CacheLineSize)
func (d *Randomx_DatasetLight) ReadDataset(address uint64, r, 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 {
@ -27,14 +21,10 @@ func (d *DatasetLight) ReadDataset(address uint64, r *RegisterLine) {
}
}
func (d *DatasetLight) Flags() uint64 {
return d.cache.Flags
func (d *Randomx_DatasetLight) Flags() uint64 {
return d.Cache.Flags
}
func (d *DatasetLight) Cache() *Cache {
return d.cache
}
func (d *DatasetLight) InitDataset(startItem, itemCount uint64) {
func (d *Randomx_DatasetLight) InitDataset(startItem, endItem uint64) {
//d.Cache.initDataset(d.Cache.Programs)
}

2
go.mod
View file

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

View file

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

View file

@ -31,9 +31,7 @@ package randomx
import (
"fmt"
"os"
"runtime"
"slices"
)
import "testing"
@ -49,9 +47,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_RandomXLight(t *testing.T) {
func Test_Randomx(t *testing.T) {
c := NewCache(0)
c := Randomx_alloc_cache(0)
for ix, tt := range Tests {
@ -64,10 +62,7 @@ func Test_RandomXLight(t *testing.T) {
}
}()
dataset := NewLightDataset(c)
dataset.InitDataset(0, DatasetItemCount)
vm := NewVM(dataset)
vm := c.VM_Initialize()
defer vm.Close()
var output_hash [32]byte
@ -79,119 +74,57 @@ func Test_RandomXLight(t *testing.T) {
}
})
}
}
func Test_RandomXFull(t *testing.T) {
func Benchmark_RandomX(b *testing.B) {
b.ReportAllocs()
c := NewCache(0)
tt := Tests[0]
for ix, tt := range Tests {
c := Randomx_alloc_cache(0)
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)
}
}()
c.Init(tt.key)
defer func() {
err := c.Close()
if err != nil {
b.Error(err)
}
}()
dataset := NewFullDataset(c)
InitDatasetParallel(dataset, runtime.NumCPU())
vm := c.VM_Initialize()
defer vm.Close()
b.ResetTimer()
for i := 0; i < b.N; i++ {
var output_hash [32]byte
vm.CalculateHash(tt.input, &output_hash)
runtime.KeepAlive(output_hash)
}
}
vm := NewVM(dataset)
defer vm.Close()
func Benchmark_RandomXParallel(b *testing.B) {
b.ReportAllocs()
var output_hash [32]byte
tt := Tests[0]
c := Randomx_alloc_cache(0)
c.Init(tt.key)
defer func() {
err := c.Close()
if err != nil {
b.Error(err)
}
}()
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
var output_hash [32]byte
vm := c.VM_Initialize()
defer vm.Close()
for pb.Next() {
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()
vm := NewVM(BenchmarkDatasetLight)
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_RandomXFull(b *testing.B) {
b.ReportAllocs()
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 := NewVM(BenchmarkDatasetLight)
defer vm.Close()
for pb.Next() {
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 int
Src int
Dst_Reg int
Src_Reg int
Mod byte
Imm32 uint32
Imm64 uint64
Type int
OpGroup int
OpGroupPar int
GroupParIsSource int
@ -320,15 +320,17 @@ type SuperScalarInstruction struct {
}
func (sins *SuperScalarInstruction) FixSrcReg() {
if sins.Src == 0xff {
sins.Src = sins.Dst
if sins.Src_Reg >= 0 {
// do nothing
} else {
sins.Src_Reg = sins.Dst_Reg
}
}
func (sins *SuperScalarInstruction) Reset() {
sins.Opcode = 99
sins.Src = 0xff
sins.Dst = 0xff
sins.Src_Reg = -1
sins.Dst_Reg = -1
sins.CanReuse = false
sins.GroupParIsSource = 0
}
@ -404,8 +406,6 @@ 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 = addressRegister
p[0].Dst_Reg = addressRegister
}
func (p SuperScalarProgram) AddressRegister() int {
return p[0].Dst
return p[0].Dst_Reg
}
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].Latency = depcycle
registers[sins.Dst].LastOpGroup = sins.OpGroup
registers[sins.Dst].LastOpPar = sins.OpGroupPar
registers[sins.Dst_Reg].Latency = depcycle
registers[sins.Dst_Reg].LastOpGroup = sins.OpGroup
registers[sins.Dst_Reg].LastOpPar = sins.OpGroupPar
}
@ -609,12 +609,12 @@ func Build_SuperScalar_Program(gen *Blake2Generator) SuperScalarProgram {
if i == 0 {
continue
}
lastdst := asic_latencies[program[i].Dst] + 1
lastdst := asic_latencies[program[i].Dst_Reg] + 1
lastsrc := 0
if program[i].Dst != program[i].Src {
lastsrc = asic_latencies[program[i].Src] + 1
if program[i].Dst_Reg != program[i].Src_Reg {
lastsrc = asic_latencies[program[i].Src_Reg] + 1
}
asic_latencies[program[i].Dst] = max(lastdst, lastsrc)
asic_latencies[program[i].Dst_Reg] = 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 = RegisterNeedsDisplacement
sins.OpGroupPar = sins.Src
sins.Src_Reg = RegisterNeedsDisplacement
sins.OpGroupPar = sins.Src_Reg
return true
}
}
if selectRegister(available_registers, gen, &sins.Src) {
if selectRegister(available_registers, gen, &sins.Src_Reg) {
if sins.GroupParIsSource == 0 {
} else {
sins.OpGroupPar = sins.Src
sins.OpGroupPar = sins.Src_Reg
}
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) &&
if Registers[i].Latency <= cycle && (sins.CanReuse || i != sins.Src_Reg) &&
(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)
return selectRegister(preAllocatedAvailableRegisters, gen, &sins.Dst_Reg)
}
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] -= r[ins.Src]
r[ins.Dst_Reg] -= r[ins.Src_Reg]
case S_IXOR_R:
r[ins.Dst] ^= r[ins.Src]
r[ins.Dst_Reg] ^= r[ins.Src_Reg]
case S_IADD_RS:
r[ins.Dst] += r[ins.Src] << ins.Imm32
r[ins.Dst_Reg] += r[ins.Src_Reg] << ins.Imm32
case S_IMUL_R:
r[ins.Dst] *= r[ins.Src]
r[ins.Dst_Reg] *= r[ins.Src_Reg]
case S_IROR_C:
r[ins.Dst] = bits.RotateLeft64(r[ins.Dst], 0-int(ins.Imm32))
r[ins.Dst_Reg] = bits.RotateLeft64(r[ins.Dst_Reg], 0-int(ins.Imm32))
case S_IADD_C7, S_IADD_C8, S_IADD_C9:
r[ins.Dst] += signExtend2sCompl(ins.Imm32)
r[ins.Dst_Reg] += signExtend2sCompl(ins.Imm32)
case S_IXOR_C7, S_IXOR_C8, S_IXOR_C9:
r[ins.Dst] ^= signExtend2sCompl(ins.Imm32)
r[ins.Dst_Reg] ^= signExtend2sCompl(ins.Imm32)
case S_IMULH_R:
r[ins.Dst], _ = bits.Mul64(r[ins.Dst], r[ins.Src])
r[ins.Dst_Reg], _ = bits.Mul64(r[ins.Dst_Reg], r[ins.Src_Reg])
case S_ISMULH_R:
r[ins.Dst] = smulh(int64(r[ins.Dst]), int64(r[ins.Src]))
r[ins.Dst_Reg] = smulh(int64(r[ins.Dst_Reg]), int64(r[ins.Src_Reg]))
case S_IMUL_RCP:
r[ins.Dst] *= ins.Imm64
r[ins.Dst_Reg] *= randomx_reciprocal(ins.Imm32)
}
}

View file

@ -27,8 +27,8 @@ func generateSuperscalarCode(scalarProgram SuperScalarProgram) SuperScalarProgra
for i := range p {
instr := &p[i]
dst := instr.Dst % RegistersCount
src := instr.Src % RegistersCount
dst := instr.Dst_Reg % RegistersCount
src := instr.Src_Reg % 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, instr.Imm64)
program = binary.LittleEndian.AppendUint64(program, randomx_reciprocal(instr.Imm32))
program = append(program, REX_IMUL_RM...)
program = append(program, byte(0xc0+8*instr.Dst))
program = append(program, byte(0xc0+8*instr.Dst_Reg))
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/v3/aes"
"git.gammaspectra.live/P2Pool/go-randomx/v2/aes"
"math"
"runtime"
"unsafe"
@ -45,25 +45,11 @@ type REG struct {
type VM struct {
ScratchPad ScratchPad
Dataset Dataset
Dataset Randomx_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
@ -100,7 +86,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])}
@ -109,6 +95,8 @@ 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)
@ -155,7 +143,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)
vm.Dataset.ReadDataset(datasetOffset+mem.ma, &reg.R, &rlCache)
// 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/v3/asm"
"git.gammaspectra.live/P2Pool/go-randomx/v2/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/v3/aes"
"git.gammaspectra.live/P2Pool/go-randomx/v2/aes"
"unsafe"
)
import "encoding/binary"