198 lines
5.5 KiB
Go
198 lines
5.5 KiB
Go
package randomx
|
|
|
|
import (
|
|
"errors"
|
|
"git.gammaspectra.live/P2Pool/go-randomx/v3/internal/argon2"
|
|
"git.gammaspectra.live/P2Pool/go-randomx/v3/internal/blake2"
|
|
"git.gammaspectra.live/P2Pool/go-randomx/v3/internal/keys"
|
|
"git.gammaspectra.live/P2Pool/go-randomx/v3/internal/memory"
|
|
"runtime"
|
|
"unsafe"
|
|
)
|
|
|
|
type MemoryBlock [argon2.BlockSize / 8]uint64
|
|
|
|
func (m *MemoryBlock) GetLine(addr uint64) *RegisterLine {
|
|
addr >>= 3
|
|
return (*RegisterLine)(unsafe.Pointer(unsafe.SliceData(m[addr : addr+8 : addr+8])))
|
|
}
|
|
|
|
type Cache struct {
|
|
blocks *[RANDOMX_ARGON_MEMORY]MemoryBlock
|
|
|
|
programs [RANDOMX_PROGRAM_COUNT]SuperScalarProgram
|
|
|
|
jitPrograms [RANDOMX_PROGRAM_COUNT]SuperScalarProgramFunc
|
|
|
|
flags Flags
|
|
}
|
|
|
|
// NewCache Creates a randomx_cache structure and allocates memory for RandomX Cache.
|
|
// *
|
|
// * @param flags is any combination of these 2 flags (each flag can be set or not set):
|
|
// * RANDOMX_FLAG_LARGE_PAGES - allocate memory in large pages
|
|
// * RANDOMX_FLAG_JIT - create cache structure with JIT compilation support; this makes
|
|
// * subsequent Dataset initialization faster
|
|
// * Optionally, one of these two flags may be selected:
|
|
// * RANDOMX_FLAG_ARGON2_SSSE3 - optimized Argon2 for CPUs with the SSSE3 instruction set
|
|
// * makes subsequent cache initialization faster
|
|
// * RANDOMX_FLAG_ARGON2_AVX2 - optimized Argon2 for CPUs with the AVX2 instruction set
|
|
// * makes subsequent cache initialization faster
|
|
// *
|
|
// * @return Pointer to an allocated randomx_cache structure.
|
|
// * Returns NULL if:
|
|
// * (1) memory allocation fails
|
|
// * (2) the RANDOMX_FLAG_JIT is set and JIT compilation is not supported on the current platform
|
|
// * (3) an invalid or unsupported RANDOMX_FLAG_ARGON2 value is set
|
|
// */
|
|
func NewCache(flags Flags) (c *Cache, err error) {
|
|
|
|
var blocks *[RANDOMX_ARGON_MEMORY]MemoryBlock
|
|
|
|
if flags.Has(RANDOMX_FLAG_LARGE_PAGES) {
|
|
if largePageAllocator == nil {
|
|
return nil, errors.New("huge pages not supported")
|
|
}
|
|
blocks, err = memory.Allocate[[RANDOMX_ARGON_MEMORY]MemoryBlock](largePageAllocator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
blocks, err = memory.Allocate[[RANDOMX_ARGON_MEMORY]MemoryBlock](cacheLineAlignedAllocator)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &Cache{
|
|
flags: flags,
|
|
blocks: blocks,
|
|
}, nil
|
|
}
|
|
|
|
func (c *Cache) hasInitializedJIT() bool {
|
|
return c.flags.HasJIT() && c.jitPrograms[0] != nil
|
|
}
|
|
|
|
// Close Releases all memory occupied by the Cache structure.
|
|
func (c *Cache) Close() error {
|
|
for _, p := range c.jitPrograms {
|
|
if p != nil {
|
|
err := p.Close()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.flags.Has(RANDOMX_FLAG_LARGE_PAGES) {
|
|
return memory.Free(largePageAllocator, c.blocks)
|
|
} else {
|
|
return memory.Free(cacheLineAlignedAllocator, c.blocks)
|
|
}
|
|
}
|
|
|
|
// Init Initializes the cache memory and SuperscalarHash using the provided key value.
|
|
// Does nothing if called again with the same key value.
|
|
func (c *Cache) Init(key []byte) {
|
|
//TODO: cache key and do not regenerate
|
|
|
|
argonBlocks := unsafe.Slice((*argon2.Block)(unsafe.Pointer(c.blocks)), len(c.blocks))
|
|
|
|
argon2.BuildBlocks(argonBlocks, key, []byte(RANDOMX_ARGON_SALT), RANDOMX_ARGON_ITERATIONS, RANDOMX_ARGON_MEMORY, RANDOMX_ARGON_LANES)
|
|
|
|
const nonce uint32 = 0
|
|
|
|
gen := blake2.New(key, nonce)
|
|
for i := range c.programs {
|
|
// build a superscalar program
|
|
prog := BuildSuperScalarProgram(gen)
|
|
|
|
if c.flags.HasJIT() {
|
|
c.jitPrograms[i] = generateSuperscalarCode(prog)
|
|
// fallback if can't compile program
|
|
if c.jitPrograms[i] == nil {
|
|
c.programs[i] = prog
|
|
} else if err := memory.PageReadExecute(c.jitPrograms[i]); err != nil {
|
|
c.programs[i] = prog
|
|
} else {
|
|
c.programs[i] = SuperScalarProgram{prog[0]}
|
|
}
|
|
} else {
|
|
c.programs[i] = prog
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
const Mask = CacheSize/CacheLineSize - 1
|
|
|
|
// getMixBlock fetch a 64 byte block in uint64 form
|
|
func (c *Cache) getMixBlock(addr uint64) *RegisterLine {
|
|
|
|
addr = (addr & Mask) * CacheLineSize
|
|
|
|
block := addr / 1024
|
|
return c.blocks[block].GetLine(addr % 1024)
|
|
}
|
|
|
|
func (c *Cache) GetMemory() *[RANDOMX_ARGON_MEMORY]MemoryBlock {
|
|
return c.blocks
|
|
}
|
|
|
|
func (c *Cache) initDataset(rl *RegisterLine, itemNumber uint64) {
|
|
registerValue := itemNumber
|
|
|
|
rl[0] = (itemNumber + 1) * keys.SuperScalar_Constants[0]
|
|
rl[1] = rl[0] ^ keys.SuperScalar_Constants[1]
|
|
rl[2] = rl[0] ^ keys.SuperScalar_Constants[2]
|
|
rl[3] = rl[0] ^ keys.SuperScalar_Constants[3]
|
|
rl[4] = rl[0] ^ keys.SuperScalar_Constants[4]
|
|
rl[5] = rl[0] ^ keys.SuperScalar_Constants[5]
|
|
rl[6] = rl[0] ^ keys.SuperScalar_Constants[6]
|
|
rl[7] = rl[0] ^ keys.SuperScalar_Constants[7]
|
|
|
|
if c.hasInitializedJIT() {
|
|
if c.flags.HasJIT() {
|
|
// Lock due to external JIT madness
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
}
|
|
|
|
for i := 0; i < RANDOMX_CACHE_ACCESSES; i++ {
|
|
mix := c.getMixBlock(registerValue)
|
|
|
|
c.jitPrograms[i].Execute(uintptr(unsafe.Pointer(rl)))
|
|
|
|
for q := range rl {
|
|
rl[q] ^= mix[q]
|
|
}
|
|
|
|
registerValue = rl[c.programs[i].AddressRegister()]
|
|
|
|
}
|
|
} else {
|
|
for i := 0; i < RANDOMX_CACHE_ACCESSES; i++ {
|
|
mix := c.getMixBlock(registerValue)
|
|
|
|
program := c.programs[i]
|
|
|
|
executeSuperscalar(program.Program(), rl)
|
|
|
|
for q := range rl {
|
|
rl[q] ^= mix[q]
|
|
}
|
|
|
|
registerValue = rl[program.AddressRegister()]
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
func (c *Cache) datasetInit(dataset []RegisterLine, startItem, endItem uint64) {
|
|
for itemNumber := startItem; itemNumber < endItem; itemNumber, dataset = itemNumber+1, dataset[1:] {
|
|
c.initDataset(&dataset[0], itemNumber)
|
|
}
|
|
}
|