From 705d13262a58c93b8ec51ab4ca9016e6314159e5 Mon Sep 17 00:00:00 2001 From: Marcos Pernambuco Motta <1091485+mpernambuco@users.noreply.github.com> Date: Mon, 4 Mar 2024 10:44:09 -0300 Subject: [PATCH 1/2] feat: Add bindings to emulator C API --- CHANGELOG.md | 1 + cmd/cartesi-machine/README.md | 31 ++ cmd/cartesi-machine/main.go | 195 +++++++ pkg/emulator/.gitignore | 1 + pkg/emulator/emulator.go | 955 ++++++++++++++++++++++++++++++++++ pkg/emulator/emulator_test.go | 474 +++++++++++++++++ 6 files changed, 1657 insertions(+) create mode 100644 cmd/cartesi-machine/README.md create mode 100644 cmd/cartesi-machine/main.go create mode 100644 pkg/emulator/.gitignore create mode 100644 pkg/emulator/emulator.go create mode 100644 pkg/emulator/emulator_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c47ce7f5..002b801fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added `Makefile` to help node developers setup their environment - Added experimental sunodo validator mode - Added instructions on how to run the node with Docker +- Added Cartesi Machine C API wrapper ### Changed diff --git a/cmd/cartesi-machine/README.md b/cmd/cartesi-machine/README.md new file mode 100644 index 000000000..4b16c4422 --- /dev/null +++ b/cmd/cartesi-machine/README.md @@ -0,0 +1,31 @@ +# Go bindings to the Cartesi Machine C API + +## Quick Start + +Ensure that the emulator headers and libraries are installed or point to them with: +``` +export CGO_CFLAGS="-I/foo/machine-emulator/src" +export CGO_LDFLAGS="-L/foo/machine-emulator/src" + +``` + +Build +``` +go build +``` + +Point to the directory containing the image files +``` +export CARTESI_IMAGES_PATH= +``` + +Run +``` +go run cmd/cartesi-machine/main.go --help +go run cmd/cartesi-machine/main.go +go run cmd/cartesi-machine/main.go --command="ls -l" +go run cmd/cartesi-machine/main.go --max-mcycle=0 --store=/tmp/maquina +go run cmd/cartesi-machine/main.go --load=/tmp/maquina --command="ls -l" +go run cmd/cartesi-machine/main.go --load=/tmp/maquina --initial-hash --final-hash +go run cmd/cartesi-machine/main.go --remote-address="localhost:5000"--load=/tmp/maquina --initial-hash --final-hash --command="ls -l" +``` \ No newline at end of file diff --git a/cmd/cartesi-machine/main.go b/cmd/cartesi-machine/main.go new file mode 100644 index 000000000..490c7e6dd --- /dev/null +++ b/cmd/cartesi-machine/main.go @@ -0,0 +1,195 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +// Simple command line interface to the Cartesi Machine C API wrapper + +package main + +import ( + "flag" + "fmt" + "math" + "os" + "strings" + + "github.com/cartesi/rollups-node/pkg/emulator" +) + +func main() { + var machine *emulator.Machine + defer machine.Free() + var mgr *emulator.RemoteMachineManager + defer mgr.Free() + var err error + runtimeConfig := &emulator.MachineRuntimeConfig{} + + // Parse command line arguments + loadDir := flag.String("load", "", "load machine previously stored in ") + storeDir := flag.String("store", "", "store machine to , where \"%h\" is substituted by the state hash in the directory name") + remoteAddress := flag.String("remote-address", "", "use a remote cartesi machine listening to
instead of running a local cartesi machine") + remoteShutdown := flag.Bool("remote-shutdown", false, "shutdown the remote cartesi machine after the execution") + noRemoteCreate := flag.Bool("no-remote-create", false, "use existing cartesi machine in the remote server instead of creating a new one") + noRemoteDestroy := flag.Bool("no-remote-destroy", false, "do not destroy the cartesi machine in the remote server after the execution") + ramImage := flag.String("ram-image", "", "name of file containing RAM image") + dtbImage := flag.String("dtb-image", "", "name of file containing DTB image (default: auto generated flattened device tree)") + maxMcycle := flag.Uint64("max-mcycle", math.MaxUint64, "stop at a given mcycle") + initialHash := flag.Bool("initial-hash", false, "print initial state hash before running machine") + finalHash := flag.Bool("final-hash", false, "print final state hash when done") + commandLine := flag.String("command", "", "command to run in the machine") + flag.Parse() + + // Connect to remote server and load/get machine + if remoteAddress != nil && *remoteAddress != "" { + fmt.Println("Connecting to remote server at ", *remoteAddress) + if mgr, err = emulator.NewRemoteMachineManager(*remoteAddress); err != nil { + fmt.Fprintln(os.Stderr, "****** Error creating remote machine manager: ", err) + os.Exit(1) + } + if noRemoteCreate != nil && *noRemoteCreate { + fmt.Println("Using existing remote machine") + if machine, err = mgr.GetMachine(); err != nil { + fmt.Fprintln(os.Stderr, "****** Error getting remote machine: ", err) + os.Exit(1) + } + } else if loadDir != nil && *loadDir != "" { + fmt.Println("Loading remote machine from ", *loadDir) + if machine, err = mgr.LoadMachine(*loadDir, runtimeConfig); err != nil { + fmt.Fprintln(os.Stderr, "****** Error loading machine: ", err) + os.Exit(1) + } + } + } else if loadDir != nil && *loadDir != "" { + fmt.Println("Loading machine from ", *loadDir) + if machine, err = emulator.LoadMachine(*loadDir, runtimeConfig); err != nil { + fmt.Fprintln(os.Stderr, "****** Error loading machine: ", err) + os.Exit(1) + } + } + + // No machine yet: build configuration and create machine + if machine == nil { + // build machine configuration + images_path := strings.TrimRight(os.Getenv("CARTESI_IMAGES_PATH"), "/") + "/" + cfg := emulator.NewDefaultMachineConfig() + cfg.Processor.Mimpid = math.MaxUint64 + cfg.Processor.Marchid = math.MaxUint64 + cfg.Processor.Mvendorid = math.MaxUint64 + cfg.Ram.ImageFilename = images_path + "linux.bin" + if ramImage != nil && *ramImage != "" { + fmt.Println("Using RAM image: ", *ramImage) + cfg.Ram.ImageFilename = *ramImage + } + cfg.Ram.Length = 64 << 20 + cfg.FlashDrive = []emulator.MemoryRangeConfig{ + { + Start: 0x80000000000000, + Length: 0xffffffffffffffff, + Shared: false, + ImageFilename: images_path + "rootfs.ext2", + }, + } + cfg.Dtb.Bootargs = "quiet earlycon=sbi console=hvc0 rootfstype=ext2 root=/dev/pmem0 rw init=/usr/sbin/cartesi-init" + if dtbImage != nil && *dtbImage != "" { + cfg.Dtb.ImageFilename = *dtbImage + } + cfg.Dtb.Init = `echo "Opa!" + busybox mkdir -p /run/drive-label && echo "root" > /run/drive-label/pmem0\ + USER=dapp + ` + if commandLine != nil && *commandLine != "" { + cfg.Dtb.Init = *commandLine + } + // create machine using configuration + if mgr == nil { + fmt.Println("Creating local machine") + if machine, err = emulator.NewMachine(cfg, runtimeConfig); err != nil { + fmt.Fprintln(os.Stderr, "****** Error creating machine: ", err) + os.Exit(1) + } + } else { + fmt.Println("Creating remote machine") + if machine, err = mgr.NewMachine(cfg, runtimeConfig); err != nil { + fmt.Fprintln(os.Stderr, "****** Error creating remote machine: ", err) + os.Exit(1) + } + + } + } + + // No machine yet? Too bad + if machine == nil { + fmt.Fprintln(os.Stderr, "****** No machine to run") + os.Exit(1) + } + + // Print initial hash + if initialHash != nil && *initialHash { + var hash *emulator.MerkleTreeHash + if hash, err = machine.GetRootHash(); err != nil { + fmt.Fprintln(os.Stderr, "****** Error getting root hash: ", err) + os.Exit(1) + } + fmt.Println("Initial hash: ", hash.String()) + } + + // Run machine + var breakReason emulator.BreakReason + if breakReason, err = machine.Run(*maxMcycle); err != nil { + fmt.Fprintln(os.Stderr, "****** Error running machine: ", err) + os.Exit(1) + } + switch breakReason { + case emulator.BreakReasonFailed: + fmt.Println("Machine failed") + case emulator.BreakReasonHalted: + fmt.Println("Machine halted") + case emulator.BreakReasonYieldedManually: + fmt.Println("Machine yielded manually") + case emulator.BreakReasonYieldedAutomatically: + fmt.Println("Machine yielded automatically") + case emulator.BreakReasonYieldedSoftly: + fmt.Println("Machine yielded softly") + case emulator.BreakReasonReachedTargetMcycle: + fmt.Println("Machine reached target mcycle") + default: + fmt.Println("Machine stopped for unknown reason") + } + + cycle, _ := machine.ReadCSR(emulator.ProcCsrMcycle) + fmt.Println("mcycle: ", cycle) + + // Print final hash + if finalHash != nil && *finalHash { + var hash *emulator.MerkleTreeHash + if hash, err = machine.GetRootHash(); err == nil { + fmt.Println("Final hash: ", hash.String()) + } + } + + // Store machine + if storeDir != nil && *storeDir != "" { + fmt.Println("Storing machine in ", *storeDir) + if err = machine.Store(*storeDir); err != nil { + fmt.Fprintln(os.Stderr, "****** Error storing machine: ", err) + os.Exit(1) + } + } + + // Cleanup + if mgr != nil { + if !*noRemoteDestroy { + fmt.Println("Destroying remote machine") + if err = machine.Destroy(); err != nil { + fmt.Fprintln(os.Stderr, "****** Error destroying remote machine: ", err) + os.Exit(1) + } + } + if *remoteShutdown { + fmt.Println("Shutting down remote machine") + if err = mgr.Shutdown(); err != nil { + fmt.Fprintln(os.Stderr, "****** Error shutting down remote server: ", err) + os.Exit(1) + } + } + } +} diff --git a/pkg/emulator/.gitignore b/pkg/emulator/.gitignore new file mode 100644 index 000000000..496ee2ca6 --- /dev/null +++ b/pkg/emulator/.gitignore @@ -0,0 +1 @@ +.DS_Store \ No newline at end of file diff --git a/pkg/emulator/emulator.go b/pkg/emulator/emulator.go new file mode 100644 index 000000000..afd2a03ca --- /dev/null +++ b/pkg/emulator/emulator.go @@ -0,0 +1,955 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +// Cartesi Machine C API wrapper + +package emulator + +/* +#cgo LDFLAGS: -lcartesi -lcartesi_jsonrpc +#include "machine-c-api.h" +#include "jsonrpc-machine-c-api.h" +#include +*/ +import "C" + +import ( + "encoding/hex" + "fmt" + "unsafe" +) + +type ErrorCode int32 + +const ( + ErrorCodeOk ErrorCode = C.CM_ERROR_OK + ErrorCodeInvalidArgument ErrorCode = C.CM_ERROR_INVALID_ARGUMENT + ErrorCodeDomainError ErrorCode = C.CM_ERROR_DOMAIN_ERROR + ErrorCodeLengthError ErrorCode = C.CM_ERROR_LENGTH_ERROR + ErrorCodeOutOfRange ErrorCode = C.CM_ERROR_OUT_OF_RANGE + ErrorCodeLogicError ErrorCode = C.CM_ERROR_LOGIC_ERROR + ErrorCodeBadOptionalAccess ErrorCode = C.CM_ERROR_BAD_OPTIONAL_ACCESS + ErrorCodeRuntimeError ErrorCode = C.CM_ERROR_RUNTIME_ERROR + ErrorCodeRangeError ErrorCode = C.CM_ERROR_RANGE_ERROR + ErrorCodeOverflowError ErrorCode = C.CM_ERROR_OVERFLOW_ERROR + ErrorCodeUnderflowError ErrorCode = C.CM_ERROR_UNDERFLOW_ERROR + ErrorCodeRegexError ErrorCode = C.CM_ERROR_REGEX_ERROR + ErrorCodeSystemIosBaseFailure ErrorCode = C.CM_ERROR_SYSTEM_IOS_BASE_FAILURE + ErrorCodeFilesystemError ErrorCode = C.CM_ERROR_FILESYSTEM_ERROR + ErrorCodeAtomicTxError ErrorCode = C.CM_ERROR_ATOMIC_TX_ERROR + ErrorCodeNonexistingLocalTime ErrorCode = C.CM_ERROR_NONEXISTING_LOCAL_TIME + ErrorCodeAmbiguousLocalTime ErrorCode = C.CM_ERROR_AMBIGUOUS_LOCAL_TIME + ErrorCodeFormatError ErrorCode = C.CM_ERROR_FORMAT_ERROR + ErrorCodeBadTypeid ErrorCode = C.CM_ERROR_BAD_TYPEID + ErrorCodeBadCast ErrorCode = C.CM_ERROR_BAD_CAST + ErrorCodeBadAnyCast ErrorCode = C.CM_ERROR_BAD_ANY_CAST + ErrorCodeBadWeakPtr ErrorCode = C.CM_ERROR_BAD_WEAK_PTR + ErrorCodeBadFunctionCall ErrorCode = C.CM_ERROR_BAD_FUNCTION_CALL + ErrorCodeBadAlloc ErrorCode = C.CM_ERROR_BAD_ALLOC + ErrorCodeBadArrayNewLength ErrorCode = C.CM_ERROR_BAD_ARRAY_NEW_LENGTH + ErrorCodeBadException ErrorCode = C.CM_ERROR_BAD_EXCEPTION + ErrorCodeBadVariantAccess ErrorCode = C.CM_ERROR_BAD_VARIANT_ACCESS + ErrorCodeException ErrorCode = C.CM_ERROR_EXCEPTION + ErrorCodeUnknown ErrorCode = C.CM_ERROR_UNKNOWN +) + +func isFailure(cerr C.int) bool { + return ErrorCode(cerr) != ErrorCodeOk +} + +type Error struct { + Code ErrorCode + Msg string +} + +func (e *Error) Error() string { + return fmt.Sprintf("cartesi machine error %d (%s)", e.Code, e.Msg) +} + +func newError(code C.int, message *C.char) error { + defer C.cm_delete_cstring(message) + if code != C.CM_ERROR_OK { + return &Error{Code: ErrorCode(code), Msg: C.GoString(message)} + } + return nil +} + +type BreakReason int32 + +const ( + BreakReasonFailed BreakReason = C.CM_BREAK_REASON_FAILED + BreakReasonHalted BreakReason = C.CM_BREAK_REASON_HALTED + BreakReasonYieldedManually BreakReason = C.CM_BREAK_REASON_YIELDED_MANUALLY + BreakReasonYieldedAutomatically BreakReason = C.CM_BREAK_REASON_YIELDED_AUTOMATICALLY + BreakReasonYieldedSoftly BreakReason = C.CM_BREAK_REASON_YIELDED_SOFTLY + BreakReasonReachedTargetMcycle BreakReason = C.CM_BREAK_REASON_REACHED_TARGET_MCYCLE +) + +type ProcessorCSR int32 + +const ( + ProcCsrPc ProcessorCSR = C.CM_PROC_PC + ProcCsrFcsr ProcessorCSR = C.CM_PROC_FCSR + ProcCsrMvendorid ProcessorCSR = C.CM_PROC_MVENDORID + ProcCsrMarchid ProcessorCSR = C.CM_PROC_MARCHID + ProcCsrMimpid ProcessorCSR = C.CM_PROC_MIMPID + ProcCsrMcycle ProcessorCSR = C.CM_PROC_MCYCLE + ProcCsrIcycleinstret ProcessorCSR = C.CM_PROC_ICYCLEINSTRET + ProcCsrMstatus ProcessorCSR = C.CM_PROC_MSTATUS + ProcCsrMtvec ProcessorCSR = C.CM_PROC_MTVEC + ProcCsrMscratch ProcessorCSR = C.CM_PROC_MSCRATCH + ProcCsrMepc ProcessorCSR = C.CM_PROC_MEPC + ProcCsrMcause ProcessorCSR = C.CM_PROC_MCAUSE + ProcCsrMtval ProcessorCSR = C.CM_PROC_MTVAL + ProcCsrMisa ProcessorCSR = C.CM_PROC_MISA + ProcCsrMie ProcessorCSR = C.CM_PROC_MIE + ProcCsrMip ProcessorCSR = C.CM_PROC_MIP + ProcCsrMedeleg ProcessorCSR = C.CM_PROC_MEDELEG + ProcCsrMideleg ProcessorCSR = C.CM_PROC_MIDELEG + ProcCsrMcounteren ProcessorCSR = C.CM_PROC_MCOUNTEREN + ProcCsrMenvcfg ProcessorCSR = C.CM_PROC_MENVCFG + ProcCsrStvec ProcessorCSR = C.CM_PROC_STVEC + ProcCsrSscratch ProcessorCSR = C.CM_PROC_SSCRATCH + ProcCsrSepc ProcessorCSR = C.CM_PROC_SEPC + ProcCsrScause ProcessorCSR = C.CM_PROC_SCAUSE + ProcCsrStval ProcessorCSR = C.CM_PROC_STVAL + ProcCsrSatp ProcessorCSR = C.CM_PROC_SATP + ProcCsrScounteren ProcessorCSR = C.CM_PROC_SCOUNTEREN + ProcCsrSenvcfg ProcessorCSR = C.CM_PROC_SENVCFG + ProcCsrIlrsc ProcessorCSR = C.CM_PROC_ILRSC + ProcCsrIflags ProcessorCSR = C.CM_PROC_IFLAGS + ProcCsrClintMtimecmp ProcessorCSR = C.CM_PROC_CLINT_MTIMECMP + ProcCsrHtifTohost ProcessorCSR = C.CM_PROC_HTIF_TOHOST + ProcCsrHtifFromhost ProcessorCSR = C.CM_PROC_HTIF_FROMHOST + ProcCsrHtifIhalt ProcessorCSR = C.CM_PROC_HTIF_IHALT + ProcCsrHtifIconsole ProcessorCSR = C.CM_PROC_HTIF_ICONSOLE + ProcCsrHtifIyield ProcessorCSR = C.CM_PROC_HTIF_IYIELD + ProcCsrUarchPc ProcessorCSR = C.CM_PROC_UARCH_PC + ProcCsrUarchCycle ProcessorCSR = C.CM_PROC_UARCH_CYCLE + ProcCsrUarchHaltFlag ProcessorCSR = C.CM_PROC_UARCH_HALT_FLAG +) + +type MachineRuntimeConfig struct { + Concurrency ConcurrencyRuntimeConfig + Htif HtifRuntimeConfig + SkipRootHashCheck bool + SkipVersionCheck bool + SoftYield bool +} + +type HtifRuntimeConfig struct { + NoConsolePutchar bool +} + +type ConcurrencyRuntimeConfig struct { + UpdateMerkleTree uint64 +} + +type MachineConfig struct { + Processor ProcessorConfig + Ram RamConfig + Dtb DtbConfig + FlashDrive []MemoryRangeConfig + Tlb TlbConfig + Clint ClintConfig + Htif HtifConfig + Cmio CmioConfig + Uarch UarchConfig +} + +type ProcessorConfig struct { + X [32]uint64 + F [32]uint64 + Pc uint64 + Fcsr uint64 + Mvendorid uint64 + Marchid uint64 + Mimpid uint64 + Mcycle uint64 + Icycleinstret uint64 + Mstatus uint64 + Mtvec uint64 + Mscratch uint64 + Mepc uint64 + Mcause uint64 + Mtval uint64 + Misa uint64 + Mie uint64 + Mip uint64 + Medeleg uint64 + Mideleg uint64 + Mcounteren uint64 + Menvcfg uint64 + Stvec uint64 + Sscratch uint64 + Sepc uint64 + Scause uint64 + Stval uint64 + Satp uint64 + Scounteren uint64 + Senvcfg uint64 + Ilrsc uint64 + Iflags uint64 +} + +type RamConfig struct { + Length uint64 + ImageFilename string +} + +type DtbConfig struct { + Bootargs string + Init string + Entrypoint string + ImageFilename string +} + +type MemoryRangeConfig struct { + Start uint64 + Length uint64 + Shared bool + ImageFilename string +} + +type TlbConfig struct { + ImageFilename string +} + +type ClintConfig struct { + Mtimecmp uint64 +} + +type HtifConfig struct { + Fromhost uint64 + Tohost uint64 + ConsoleGetchar bool + YieldManual bool + YieldAutomatic bool +} + +type CmioConfig struct { + HsaValue bool + RxBuffer MemoryRangeConfig + TxBuffer MemoryRangeConfig +} + +type UarchRamConfig struct { + ImageFilename string +} + +type UarchProcessorConfig struct { + X [32]uint64 + Pc uint64 + Cycle uint64 + HaltFlag bool +} + +type UarchConfig struct { + Processor UarchProcessorConfig + Ram UarchRamConfig +} + +//////////////////////////////////////// +// Helpers and utils +//////////////////////////////////////// + +type ourMemoryRangeConfig struct { + cref *C.cm_memory_range_config +} + +func (config *MemoryRangeConfig) makeCRef() (ref *ourMemoryRangeConfig) { + ref = &ourMemoryRangeConfig{ + cref: (*C.cm_memory_range_config)(C.calloc(1, C.sizeof_cm_memory_range_config)), + } + c := ref.cref + c.start = (C.uint64_t)(config.Start) + c.length = (C.uint64_t)(config.Length) + c.shared = (C.bool)(config.Shared) + c.image_filename = makeCString(&config.ImageFilename) + return ref +} + +func (configRef *ourMemoryRangeConfig) free() { + if configRef == nil || configRef.cref == nil { + return + } + C.free(unsafe.Pointer(configRef.cref.image_filename)) + C.free(unsafe.Pointer(configRef.cref)) + configRef.cref = nil +} + +// cm_machine_runtime_config allocated by us +type ourMachineRuntimeConfigCRef struct { + cref *C.cm_machine_runtime_config +} + +func (config *MachineRuntimeConfig) makeCRef() (ref *ourMachineRuntimeConfigCRef) { + ref = &ourMachineRuntimeConfigCRef{ + cref: (*C.cm_machine_runtime_config)(C.calloc(1, C.sizeof_cm_machine_runtime_config)), + } + cRuntime := ref.cref + cRuntime.skip_root_hash_check = (C.bool)(config.SkipRootHashCheck) + cRuntime.skip_version_check = (C.bool)(config.SkipVersionCheck) + cRuntime.soft_yield = (C.bool)(config.SoftYield) + + cHtif := &ref.cref.htif + htif := &config.Htif + cHtif.no_console_putchar = (C.bool)(htif.NoConsolePutchar) + + cConcurrency := &ref.cref.concurrency + concurrency := &config.Concurrency + cConcurrency.update_merkle_tree = (C.uint64_t)(concurrency.UpdateMerkleTree) + + return ref +} + +func (configRef *ourMachineRuntimeConfigCRef) free() { + if configRef == nil || configRef.cref == nil { + return + } + C.free(unsafe.Pointer(configRef.cref)) + configRef.cref = nil +} + +// cm_machine_config allocated by us +type ourMachineConfigCRef struct { + cref *C.cm_machine_config +} + +func (config *MachineConfig) makeCRef() (ref *ourMachineConfigCRef) { + ref = &ourMachineConfigCRef{ + cref: (*C.cm_machine_config)(C.calloc(1, C.sizeof_cm_machine_config)), + } + // Processor + cProcessor := &ref.cref.processor + processor := &config.Processor + for i := 0; i < 31; i++ { + cProcessor.x[i+1] = (C.uint64_t)(processor.X[i]) + } + for i := 0; i < 31; i++ { + cProcessor.f[i+1] = (C.uint64_t)(processor.F[i]) + } + cProcessor.pc = (C.uint64_t)(processor.Pc) + cProcessor.fcsr = (C.uint64_t)(processor.Fcsr) + cProcessor.mvendorid = (C.uint64_t)(processor.Mvendorid) + cProcessor.marchid = (C.uint64_t)(processor.Marchid) + cProcessor.mimpid = (C.uint64_t)(processor.Mimpid) + cProcessor.mcycle = (C.uint64_t)(processor.Mcycle) + cProcessor.icycleinstret = (C.uint64_t)(processor.Icycleinstret) + cProcessor.mstatus = (C.uint64_t)(processor.Mstatus) + cProcessor.mtvec = (C.uint64_t)(processor.Mtvec) + cProcessor.mscratch = (C.uint64_t)(processor.Mscratch) + cProcessor.mepc = (C.uint64_t)(processor.Mepc) + cProcessor.mcause = (C.uint64_t)(processor.Mcause) + cProcessor.mtval = (C.uint64_t)(processor.Mtval) + cProcessor.misa = (C.uint64_t)(processor.Misa) + cProcessor.mie = (C.uint64_t)(processor.Mie) + cProcessor.mip = (C.uint64_t)(processor.Mip) + cProcessor.medeleg = (C.uint64_t)(processor.Medeleg) + cProcessor.mideleg = (C.uint64_t)(processor.Mideleg) + cProcessor.mcounteren = (C.uint64_t)(processor.Mcounteren) + cProcessor.menvcfg = (C.uint64_t)(processor.Menvcfg) + cProcessor.stvec = (C.uint64_t)(processor.Stvec) + cProcessor.sscratch = (C.uint64_t)(processor.Sscratch) + cProcessor.sepc = (C.uint64_t)(processor.Sepc) + cProcessor.scause = (C.uint64_t)(processor.Scause) + cProcessor.stval = (C.uint64_t)(processor.Stval) + cProcessor.satp = (C.uint64_t)(processor.Satp) + cProcessor.scounteren = (C.uint64_t)(processor.Scounteren) + cProcessor.senvcfg = (C.uint64_t)(processor.Senvcfg) + cProcessor.ilrsc = (C.uint64_t)(processor.Ilrsc) + cProcessor.iflags = (C.uint64_t)(processor.Iflags) + + cRam := &ref.cref.ram + ram := &config.Ram + cRam.length = (C.uint64_t)(ram.Length) + cRam.image_filename = makeCString(&ram.ImageFilename) + + cDtb := &ref.cref.dtb + dtb := &config.Dtb + cDtb.bootargs = makeCString(&dtb.Bootargs) + cDtb.init = makeCString(&dtb.Init) + cDtb.entrypoint = makeCString(&dtb.Entrypoint) + cDtb.image_filename = makeCString(&dtb.ImageFilename) + + // flash + cFlashDrive := &ref.cref.flash_drive + flashDrive := &config.FlashDrive + cFlashDrive.count = (C.ulong)(len(*flashDrive)) + cFlashDrive.entry = (*C.cm_memory_range_config)(C.calloc((C.ulong)(len(*flashDrive)), C.sizeof_cm_memory_range_config)) + for i, v := range *flashDrive { + offset := C.sizeof_cm_memory_range_config * i + addr := unsafe.Pointer(uintptr(unsafe.Pointer(cFlashDrive.entry)) + uintptr(offset)) + mr := (*C.cm_memory_range_config)(addr) + mr.start = (C.uint64_t)(v.Start) + mr.length = (C.uint64_t)(v.Length) + mr.shared = (C.bool)(v.Shared) + mr.image_filename = makeCString(&v.ImageFilename) + } + + cTlb := &ref.cref.tlb + tlb := &config.Tlb + cTlb.image_filename = makeCString(&tlb.ImageFilename) + + cClint := &ref.cref.clint + clint := &config.Clint + cClint.mtimecmp = (C.uint64_t)(clint.Mtimecmp) + + cHtif := &ref.cref.htif + htif := &config.Htif + cHtif.tohost = (C.uint64_t)(htif.Tohost) + cHtif.fromhost = (C.uint64_t)(htif.Fromhost) + cHtif.console_getchar = (C.bool)(htif.ConsoleGetchar) + cHtif.yield_manual = (C.bool)(htif.YieldManual) + cHtif.yield_automatic = (C.bool)(htif.YieldAutomatic) + + cCmio := &ref.cref.cmio + cmio := &config.Cmio + cCmio.has_value = (C.bool)(cmio.HsaValue) + cCmio.rx_buffer.start = (C.uint64_t)(cmio.RxBuffer.Start) + cCmio.rx_buffer.length = (C.uint64_t)(cmio.RxBuffer.Length) + cCmio.rx_buffer.shared = (C.bool)(cmio.RxBuffer.Shared) + cCmio.rx_buffer.image_filename = makeCString(&cmio.RxBuffer.ImageFilename) + cCmio.tx_buffer.start = (C.uint64_t)(cmio.TxBuffer.Start) + cCmio.tx_buffer.length = (C.uint64_t)(cmio.TxBuffer.Length) + cCmio.tx_buffer.shared = (C.bool)(cmio.TxBuffer.Shared) + cCmio.tx_buffer.image_filename = makeCString(&cmio.TxBuffer.ImageFilename) + + cUarch := &ref.cref.uarch + uarch := &config.Uarch + + cUarchProcessor := &cUarch.processor + uarchProcessor := &uarch.Processor + for i := 0; i < 32; i++ { + cUarchProcessor.x[i] = (C.uint64_t)(uarchProcessor.X[i]) + } + cUarchProcessor.pc = (C.uint64_t)(uarchProcessor.Pc) + cUarchProcessor.cycle = (C.uint64_t)(uarchProcessor.Cycle) + cUarchProcessor.halt_flag = (C.bool)(uarchProcessor.HaltFlag) + + cUarchRam := &cUarch.ram + uarchRam := &uarch.Ram + cUarchRam.image_filename = makeCString(&uarchRam.ImageFilename) + + return ref +} + +func (configCRef *ourMachineConfigCRef) free() { + if configCRef == nil || configCRef.cref == nil { + return + } + C.free(unsafe.Pointer(configCRef.cref.ram.image_filename)) + C.free(unsafe.Pointer(configCRef.cref.dtb.bootargs)) + C.free(unsafe.Pointer(configCRef.cref.dtb.init)) + C.free(unsafe.Pointer(configCRef.cref.dtb.entrypoint)) + C.free(unsafe.Pointer(configCRef.cref.dtb.image_filename)) + C.free(unsafe.Pointer(configCRef.cref.flash_drive.entry)) + + C.free(unsafe.Pointer(configCRef.cref.cmio.rx_buffer.image_filename)) + C.free(unsafe.Pointer(configCRef.cref.cmio.tx_buffer.image_filename)) + C.free(unsafe.Pointer(configCRef.cref.uarch.ram.image_filename)) + C.free(unsafe.Pointer(configCRef.cref)) + configCRef.cref = nil +} + +// cm_machine_config allocated by the emulator +type theirMachineConfigCRef struct { + cref *C.cm_machine_config +} + +func (configCRef *theirMachineConfigCRef) free() { + if configCRef != nil && configCRef.cref != nil { + C.cm_delete_machine_config(configCRef.cref) + configCRef.cref = nil + } +} + +func (configCRef *theirMachineConfigCRef) makeGoRef() (cfg *MachineConfig) { + cfg = &MachineConfig{} + c := configCRef.cref + // Processor + processor := &cfg.Processor + for i := 0; i < 30; i++ { + processor.X[i] = (uint64)(c.processor.x[i+1]) + } + for i := 0; i < 31; i++ { + processor.F[i] = (uint64)(c.processor.f[i+1]) + } + processor.Pc = (uint64)(c.processor.pc) + processor.Fcsr = (uint64)(c.processor.fcsr) + processor.Mvendorid = (uint64)(c.processor.mvendorid) + processor.Marchid = (uint64)(c.processor.marchid) + processor.Mimpid = (uint64)(c.processor.mimpid) + processor.Mcycle = (uint64)(c.processor.mcycle) + processor.Icycleinstret = (uint64)(c.processor.icycleinstret) + processor.Mstatus = (uint64)(c.processor.mstatus) + processor.Mtvec = (uint64)(c.processor.mtvec) + processor.Mscratch = (uint64)(c.processor.mscratch) + processor.Mepc = (uint64)(c.processor.mepc) + processor.Mcause = (uint64)(c.processor.mcause) + processor.Mtval = (uint64)(c.processor.mtval) + processor.Misa = (uint64)(c.processor.misa) + processor.Mie = (uint64)(c.processor.mie) + processor.Mip = (uint64)(c.processor.mip) + processor.Medeleg = (uint64)(c.processor.medeleg) + processor.Mideleg = (uint64)(c.processor.mideleg) + processor.Mcounteren = (uint64)(c.processor.mcounteren) + processor.Menvcfg = (uint64)(c.processor.menvcfg) + processor.Stvec = (uint64)(c.processor.stvec) + processor.Sscratch = (uint64)(c.processor.sscratch) + processor.Sepc = (uint64)(c.processor.sepc) + processor.Scause = (uint64)(c.processor.scause) + processor.Stval = (uint64)(c.processor.stval) + processor.Satp = (uint64)(c.processor.satp) + processor.Scounteren = (uint64)(c.processor.scounteren) + processor.Senvcfg = (uint64)(c.processor.senvcfg) + processor.Ilrsc = (uint64)(c.processor.ilrsc) + processor.Iflags = (uint64)(c.processor.iflags) + + // Ram + ram := &cfg.Ram + ram.Length = (uint64)(c.ram.length) + ram.ImageFilename = C.GoString(c.ram.image_filename) + + // Dtb + dtb := &cfg.Dtb + dtb.Bootargs = C.GoString(c.dtb.bootargs) + dtb.Init = C.GoString(c.dtb.init) + dtb.Entrypoint = C.GoString(c.dtb.entrypoint) + dtb.ImageFilename = C.GoString(c.dtb.image_filename) + + // FlashDrive + //flashDrive := &cfg.FlashDrive + for i := 0; i < int(c.flash_drive.count); i++ { + offset := C.sizeof_cm_memory_range_config * i + addr := unsafe.Pointer(uintptr(unsafe.Pointer(c.flash_drive.entry)) + uintptr(offset)) + mr := (*C.cm_memory_range_config)(addr) + cfg.FlashDrive = append(cfg.FlashDrive, MemoryRangeConfig{ + Start: (uint64)(mr.start), + Length: (uint64)(mr.length), + Shared: (bool)(mr.shared), + ImageFilename: C.GoString(mr.image_filename), + }) + } + + // Tlb + tlb := &cfg.Tlb + tlb.ImageFilename = C.GoString(c.tlb.image_filename) + + // Clint + clint := &cfg.Clint + clint.Mtimecmp = (uint64)(c.clint.mtimecmp) + + // Htif + htif := &cfg.Htif + htif.Tohost = (uint64)(c.htif.tohost) + htif.Fromhost = (uint64)(c.htif.fromhost) + htif.ConsoleGetchar = (bool)(c.htif.console_getchar) + htif.YieldManual = (bool)(c.htif.yield_manual) + htif.YieldAutomatic = (bool)(c.htif.yield_automatic) + + // CMIO + cmio := &cfg.Cmio + cmio.HsaValue = (bool)(c.cmio.has_value) + cmio.RxBuffer = MemoryRangeConfig{ + Start: (uint64)(c.cmio.rx_buffer.start), + Length: (uint64)(c.cmio.rx_buffer.length), + Shared: (bool)(c.cmio.rx_buffer.shared), + ImageFilename: C.GoString(c.cmio.rx_buffer.image_filename), + } + cmio.TxBuffer = MemoryRangeConfig{ + Start: (uint64)(c.cmio.tx_buffer.start), + Length: (uint64)(c.cmio.tx_buffer.length), + Shared: (bool)(c.cmio.tx_buffer.shared), + ImageFilename: C.GoString(c.cmio.tx_buffer.image_filename), + } + + // Uarch + uarch := &cfg.Uarch + uarchProcessor := &uarch.Processor + for i := 0; i < 32; i++ { + uarchProcessor.X[i] = (uint64)(c.uarch.processor.x[i]) + } + uarchProcessor.Pc = (uint64)(c.uarch.processor.pc) + uarchProcessor.Cycle = (uint64)(c.uarch.processor.cycle) + uarchProcessor.HaltFlag = (bool)(c.uarch.processor.halt_flag) + + uarchRam := &uarch.Ram + uarchRam.ImageFilename = C.GoString(c.uarch.ram.image_filename) + + return cfg +} + +func makeCString(s *string) *C.char { + if s == nil || *s == "" { + return nil + } + return C.CString(*s) +} + +/////////////////////////// +// Public API +/////////////////////////// + +func NewDefaultMachineConfig() *MachineConfig { + ref := theirMachineConfigCRef{} + defer ref.free() + ref.cref = C.cm_new_default_machine_config() + return ref.makeGoRef() +} + +// a connection to the remote jsonrpc machine manager +type RemoteMachineManager struct { + RemoteAddress string + cref *C.cm_jsonrpc_mg_mgr +} + +// a local or remote machine +type Machine struct { + cref *C.cm_machine + remoteManager *RemoteMachineManager +} + +func (mgr *RemoteMachineManager) Free() { + if mgr != nil && mgr.cref != nil { + C.cm_delete_jsonrpc_mg_mgr(mgr.cref) + mgr.cref = nil + } +} + +func (machine *Machine) Free() { + if machine == nil || machine.cref == nil { + return + } + C.cm_delete_machine(machine.cref) + machine.cref = nil +} + +func (machine *Machine) Store(dir string) error { + cDir := C.CString(dir) + defer C.free(unsafe.Pointer(cDir)) + var cerr *C.char + if e := C.cm_store(machine.cref, cDir, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func NewMachine(config *MachineConfig, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{} + configRef := config.makeCRef() + defer configRef.free() + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var cerr *C.char + if e := C.cm_create_machine(configRef.cref, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return machine, nil +} + +func LoadMachine(dir string, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{} + cDir := C.CString(dir) + defer C.free(unsafe.Pointer(cDir)) + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var cerr *C.char + if e := C.cm_load_machine(cDir, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + + } + return machine, nil +} + +func GetDefaultConfig() (*MachineConfig, error) { + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + var cerr *C.char + if e := C.cm_get_default_config(&theirCfg.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return theirCfg.makeGoRef(), nil +} + +func (m *Machine) Run(mcycleEnd uint64) (BreakReason, error) { + var cerr *C.char + var creason C.CM_BREAK_REASON + if e := C.cm_machine_run(m.cref, C.uint64_t(mcycleEnd), &creason, &cerr); isFailure(e) { + return BreakReasonFailed, newError(e, cerr) + } + return (BreakReason)(creason), nil +} + +func (machine *Machine) GetInitialConfig() (*MachineConfig, error) { + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + var cerr *C.char + if e := C.cm_get_initial_config(machine.cref, &theirCfg.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return theirCfg.makeGoRef(), nil +} + +func (machine *Machine) Destroy() error { + var cerr *C.char + if e := C.cm_destroy(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadCSR(r ProcessorCSR) (uint64, error) { + var cval C.uint64_t + var cerr *C.char + if e := C.cm_read_csr(machine.cref, C.CM_PROC_CSR(r), &cval, &cerr); isFailure(e) { + return 0, newError(e, cerr) + } + return uint64(cval), nil +} + +func (machine *Machine) WriteCSR(r ProcessorCSR, val uint64) error { + var cerr *C.char + if e := C.cm_write_csr(machine.cref, C.CM_PROC_CSR(r), C.uint64_t(val), &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadX(i int) (uint64, error) { + var cval C.uint64_t + var cerr *C.char + if e := C.cm_read_x(machine.cref, C.int(i), &cval, &cerr); isFailure(e) { + return 0, newError(e, cerr) + } + return uint64(cval), nil +} + +func (machine *Machine) WriteX(i int, val uint64) error { + var cerr *C.char + if e := C.cm_write_x(machine.cref, C.int(i), C.uint64_t(val), &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadF(i int) (uint64, error) { + var cval C.uint64_t + var cerr *C.char + if e := C.cm_read_f(machine.cref, C.int(i), &cval, &cerr); isFailure(e) { + return 0, newError(e, cerr) + } + return uint64(cval), nil +} + +func (machine *Machine) ReadIFlagsX() (bool, error) { + var cval C.bool + var cerr *C.char + if e := C.cm_read_iflags_X(machine.cref, &cval, &cerr); isFailure(e) { + return false, newError(e, cerr) + } + return bool(cval), nil +} + +func (machine *Machine) ResetIFlagsX() error { + var cerr *C.char + if e := C.cm_reset_iflags_X(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) SetIFlagsX() error { + var cerr *C.char + if e := C.cm_set_iflags_X(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadIFlagsY() (bool, error) { + var cval C.bool + var cerr *C.char + if e := C.cm_read_iflags_Y(machine.cref, &cval, &cerr); isFailure(e) { + return false, newError(e, cerr) + } + return bool(cval), nil +} + +func (machine *Machine) ResetIFlagsY() error { + var cerr *C.char + if e := C.cm_reset_iflags_Y(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) SetIFlagsY() error { + var cerr *C.char + if e := C.cm_set_iflags_Y(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadIFlagsH() (bool, error) { + var cval C.bool + var cerr *C.char + if e := C.cm_read_iflags_H(machine.cref, &cval, &cerr); isFailure(e) { + return false, newError(e, cerr) + } + return bool(cval), nil +} + +func (machine *Machine) SetIFlagsH() error { + var cerr *C.char + if e := C.cm_set_iflags_H(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +type MerkleTreeHash [32]byte + +func (hash *MerkleTreeHash) String() string { + return hex.EncodeToString(hash[:]) +} + +func (machine *Machine) GetRootHash() (*MerkleTreeHash, error) { + var chash C.cm_hash + var cerr *C.char + if e := C.cm_get_root_hash(machine.cref, &chash, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + hash := &MerkleTreeHash{} + for i := 0; i < 32; i++ { + hash[i] = byte(chash[i]) + } + return hash, nil +} + +func (machine *Machine) WriteF(i int, val uint64) error { + var cerr *C.char + if e := C.cm_write_f(machine.cref, C.int(i), C.uint64_t(val), &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func NewRemoteMachineManager(remoteAddress string) (*RemoteMachineManager, error) { + manager := &RemoteMachineManager{RemoteAddress: remoteAddress} + cRemoteAddress := C.CString(remoteAddress) + defer C.free(unsafe.Pointer(cRemoteAddress)) + var cerr *C.char + if e := C.cm_create_jsonrpc_mg_mgr(cRemoteAddress, &manager.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return manager, nil +} + +func (mgr *RemoteMachineManager) Shutdown() error { + var cerr *C.char + if e := C.cm_jsonrpc_shutdown(mgr.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (mgr *RemoteMachineManager) Fork() (*string, error) { + var cNewAddress *C.char = nil + var cerr *C.char + if e := C.cm_jsonrpc_fork(mgr.cref, &cNewAddress, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + newAddress := C.GoString(cNewAddress) + C.cm_delete_cstring(cNewAddress) + return &newAddress, nil +} + +func (mgr *RemoteMachineManager) NewMachine(config *MachineConfig, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{remoteManager: mgr} + configRef := config.makeCRef() + defer configRef.free() + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var cerr *C.char + if e := C.cm_create_jsonrpc_machine(mgr.cref, configRef.cref, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return machine, nil +} + +func (mgr *RemoteMachineManager) GetDefaultConfig() (*MachineConfig, error) { + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + var cerr *C.char + if e := C.cm_jsonrpc_get_default_config(mgr.cref, &theirCfg.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return theirCfg.makeGoRef(), nil +} + +func (mgr *RemoteMachineManager) LoadMachine(dir string, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{remoteManager: mgr} + cDir := C.CString(dir) + defer C.free(unsafe.Pointer(cDir)) + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var cerr *C.char + if e := C.cm_load_jsonrpc_machine(mgr.cref, cDir, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return machine, nil +} + +func (mgr *RemoteMachineManager) GetMachine() (*Machine, error) { + machine := &Machine{remoteManager: mgr} + var cerr *C.char + if e := C.cm_get_jsonrpc_machine(mgr.cref, &machine.cref, &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return machine, nil +} + +func (machine *Machine) WriteMemory(address uint64, data []byte) error { + var cerr *C.char + if e := C.cm_write_memory(machine.cref, C.uint64_t(address), (*C.uchar)(&data[0]), C.size_t(len(data)), &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) ReadMemory(address uint64, length uint64) ([]byte, error) { + data := make([]byte, length) + var cerr *C.char + if e := C.cm_read_memory(machine.cref, C.uint64_t(address), (*C.uchar)(&data[0]), C.uint64_t(length), &cerr); isFailure(e) { + return nil, newError(e, cerr) + } + return data, nil +} + +func (machine *Machine) ReplaceMemoryRange(newRange *MemoryRangeConfig) error { + newRangeRef := newRange.makeCRef() + defer newRangeRef.free() + var cerr *C.char + if e := C.cm_replace_memory_range(machine.cref, newRangeRef.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) Snapshot() error { + var cerr *C.char + if e := C.cm_snapshot(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} + +func (machine *Machine) Rollback() error { + var cerr *C.char + if e := C.cm_rollback(machine.cref, &cerr); isFailure(e) { + return newError(e, cerr) + } + return nil +} diff --git a/pkg/emulator/emulator_test.go b/pkg/emulator/emulator_test.go new file mode 100644 index 000000000..3711fbfc5 --- /dev/null +++ b/pkg/emulator/emulator_test.go @@ -0,0 +1,474 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +// Test Cartesi Machine C API wrapper + +package emulator + +import ( + "math" + "os" + "os/exec" + "strings" + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestGetDefaultConfig(t *testing.T) { + cfg, err := GetDefaultConfig() + assert.Nil(t, err) + assert.NotNil(t, cfg) +} + +func TestNewDefaultMachineConfig(t *testing.T) { + cfg := NewDefaultMachineConfig() + assert.NotNil(t, cfg) +} + +func TestNewLocalMachine(t *testing.T) { + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := NewMachine(cfg, runtimeConfig) + defer machine.Free() + assert.Nil(t, err) + assert.NotNil(t, machine) + sharedMachineTests(t, machine) +} + +func TestReadWriteMemoryOnLocalMachine(t *testing.T) { + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + defer machine.Free() + sharedTestReadWriteMemory(t, machine) +} + +func TestReadWriteMemoryOnRemoteMachine(t *testing.T) { + // launch remote server + cmd := launchRemoteServer(t) + defer cmd.Process.Kill() + // connect to remote server + mgr, err := NewRemoteMachineManager(makeRemoteAddress()) + assert.Nil(t, err) + assert.NotNil(t, mgr) + defer func() { + mgr.Shutdown() + defer mgr.Free() + }() + // create machine + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := mgr.NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, machine) + defer func() { + machine.Destroy() + machine.Free() + }() + sharedTestReadWriteMemory(t, machine) +} + +func sharedTestReadWriteMemory(t *testing.T, machine *Machine) { + // read existing data + existingData, err := machine.ReadMemory(rxBufferStartAddress, 1024) + assert.Nil(t, err) + assert.NotNil(t, existingData) + assert.Equal(t, 1024, len(existingData)) + // Prepare new data + var newData [1024]byte + for i := 0; i < 1024; i++ { + newData[i] = byte(i % 256) + } + assert.NotEqual(t, newData[:], existingData) + // write new data + err = machine.WriteMemory(rxBufferStartAddress, newData[:]) + assert.Nil(t, err) + // read back the data + readBackData, err := machine.ReadMemory(rxBufferStartAddress, 1024) + assert.Nil(t, err) + assert.NotNil(t, readBackData) + assert.Equal(t, newData[:], readBackData) + + // create a byte array full of 0xda of the size of rxBufferLength + var newRxBufferData [rxBufferLength]byte + for i := 0; i < rxBufferLength; i++ { + newRxBufferData[i] = 0xda + } + // create a temporary file and write the new data to it + tempFile, err := os.CreateTemp("", "rxBuffer") + assert.Nil(t, err) + defer os.Remove(tempFile.Name()) + _, err = tempFile.Write(newRxBufferData[:]) + assert.Nil(t, err) + tempFile.Close() + // initialize a MemoryRangeConfig pointing to this file + newRxBuffer := &MemoryRangeConfig{ + Start: rxBufferStartAddress, + Length: rxBufferLength, + Shared: false, + ImageFilename: tempFile.Name(), + } + // replace the rxBuffer with the new data + err = machine.ReplaceMemoryRange(newRxBuffer) + assert.Nil(t, err) + // read back memory and check if it was replaced + readBackRxData, err := machine.ReadMemory(rxBufferStartAddress, rxBufferLength) + assert.Nil(t, err) + assert.NotNil(t, readBackRxData) + assert.Equal(t, newRxBufferData[:], readBackRxData) + +} + +func TestRunLocalMachineHappyPath(t *testing.T) { + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := NewMachine(cfg, runtimeConfig) + defer machine.Free() + assert.Nil(t, err) + assert.NotNil(t, machine) + var iflagsH bool + var mcycle uint64 + var hashBefore *MerkleTreeHash + var hashAfter *MerkleTreeHash + var breakReason BreakReason + // assert initial state + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Equal(t, uint64(0), mcycle) + hashBefore, err = machine.GetRootHash() + assert.Nil(t, err) + // Advance one cycle + breakReason, err = machine.Run(1) + assert.Nil(t, err) + assert.Equal(t, BreakReasonReachedTargetMcycle, breakReason) + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) // still not halted + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Equal(t, uint64(1), mcycle) // advanced one cycle + hashAfter, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), hashAfter.String()) // hash changed + storedHash := hashAfter + // Store machine + tempDir, err := os.MkdirTemp("", "testmachines") + assert.Nil(t, err) + defer os.RemoveAll(tempDir) + storePath := tempDir + "/machine" + err = machine.Store(storePath) + assert.Nil(t, err) + // run until halt + hashBefore = hashAfter + breakReason, err = machine.Run(math.MaxUint64) + assert.Nil(t, err) + assert.Equal(t, BreakReasonHalted, breakReason) + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.True(t, iflagsH) // halted + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Greater(t, mcycle, uint64(1)) + hashAfter, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), hashAfter.String()) // hash changed again + + // load a second machine from empDir, err := ioutil.TempDir("", "example") + machine2, err := LoadMachine(storePath, runtimeConfig) + defer machine2.Free() + assert.Nil(t, err) + assert.NotNil(t, machine2) + var hashMachine2 *MerkleTreeHash + hashMachine2, err = machine2.GetRootHash() + assert.Nil(t, err) + assert.Equal(t, storedHash.String(), hashMachine2.String()) // hash is the same +} + +func TestNewRemoteMachine(t *testing.T) { + // launch remote server + cmd := launchRemoteServer(t) + defer cmd.Process.Kill() + // connect to remote server + mgr, err := NewRemoteMachineManager(makeRemoteAddress()) + assert.Nil(t, err) + assert.NotNil(t, mgr) + defer func() { + mgr.Shutdown() + defer mgr.Free() + }() + // create machine + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err := mgr.NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, machine) + defer func() { + machine.Destroy() + machine.Free() + }() + sharedMachineTests(t, machine) +} + +func TestRemoteMachineHappyPath(t *testing.T) { + var err error + remoteAddress := makeRemoteAddress() + // launch remote server + cmd := launchRemoteServer(t) + defer cmd.Process.Kill() + // Connect to remote server + var mgr *RemoteMachineManager + mgr, err = NewRemoteMachineManager(makeRemoteAddress()) + assert.Nil(t, err) + assert.NotNil(t, mgr) + defer func() { + mgr.Shutdown() + defer mgr.Free() + }() + // get default config from remote server + var remoteDefCfg *MachineConfig + remoteDefCfg, err = mgr.GetDefaultConfig() + assert.Nil(t, err) + assert.NotNil(t, remoteDefCfg) + + // create a remote machine + var machine *Machine + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err = mgr.NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, machine) + defer func() { + machine.Destroy() // destroy machine from server + machine.Free() + }() + // assert initial machine state + var iflagsH bool + var mcycle uint64 + var hashBefore *MerkleTreeHash + var finalHash *MerkleTreeHash + var breakReason BreakReason + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Equal(t, uint64(0), mcycle) + hashBefore, err = machine.GetRootHash() + assert.Nil(t, err) + // Advance one cycle + breakReason, err = machine.Run(1) + assert.Nil(t, err) + assert.Equal(t, BreakReasonReachedTargetMcycle, breakReason) + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) // still not halted + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Equal(t, uint64(1), mcycle) // advanced one cycle + finalHash, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), finalHash.String()) // hash changed + storedHash := finalHash + // Store machine + tempDir, err := os.MkdirTemp("", "testmachines") + assert.Nil(t, err) + defer os.RemoveAll(tempDir) + storePath := tempDir + "/machine" + err = machine.Store(storePath) + assert.Nil(t, err) + // run until halt + hashBefore = finalHash + breakReason, err = machine.Run(math.MaxUint64) + assert.Nil(t, err) + assert.Equal(t, BreakReasonHalted, breakReason) + iflagsH, err = machine.ReadIFlagsH() + assert.Nil(t, err) + assert.True(t, iflagsH) // halted + mcycle, err = machine.ReadCSR(ProcCsrMcycle) + assert.Nil(t, err) + assert.Greater(t, mcycle, uint64(1)) + finalHash, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), finalHash.String()) // hash changed again + // fork the remote server + var secondRemoteAddress *string + secondRemoteAddress, err = mgr.Fork() + assert.Nil(t, err) + assert.NotEqual(t, remoteAddress, secondRemoteAddress) + assert.NotEqual(t, "", secondRemoteAddress) + assert.NotEqual(t, remoteAddress, secondRemoteAddress) + // connect to the forked server + var mgr2 *RemoteMachineManager + mgr2, err = NewRemoteMachineManager(*secondRemoteAddress) + assert.Nil(t, err) + assert.NotNil(t, mgr2) + // Get forked machine + var forkedMachine *Machine + forkedMachine, err = mgr2.GetMachine() + assert.Nil(t, err) + assert.NotNil(t, forkedMachine) + defer func() { + forkedMachine.Destroy() + forkedMachine.Free() + }() + var forkedHash *MerkleTreeHash + forkedHash, err = forkedMachine.GetRootHash() + assert.Nil(t, err) + assert.Equal(t, finalHash.String(), forkedHash.String()) + // destroy existing machine on mgr2 + err = forkedMachine.Destroy() + assert.Nil(t, err) + // Load stored machine on mgr2 + var loadedMachine *Machine + loadedMachine, err = mgr2.LoadMachine(storePath, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, loadedMachine) + defer func() { + loadedMachine.Destroy() + loadedMachine.Free() + }() + var loadedHash *MerkleTreeHash + loadedHash, err = loadedMachine.GetRootHash() + assert.Nil(t, err) + assert.Equal(t, storedHash.String(), loadedHash.String()) +} + +func TestSnapshot(t *testing.T) { + // launch remote server + cmd := launchRemoteServer(t) + defer cmd.Process.Kill() + // Connect to remote server + var mgr *RemoteMachineManager + var err error + mgr, err = NewRemoteMachineManager(makeRemoteAddress()) + assert.Nil(t, err) + assert.NotNil(t, mgr) + defer func() { + mgr.Shutdown() + defer mgr.Free() + }() + // create a machine + var machine *Machine + cfg := makeMachineConfig() + runtimeConfig := &MachineRuntimeConfig{} + machine, err = mgr.NewMachine(cfg, runtimeConfig) + assert.Nil(t, err) + assert.NotNil(t, machine) + defer func() { + machine.Destroy() + machine.Free() + }() + // get current hash + var hashBefore *MerkleTreeHash + hashBefore, err = machine.GetRootHash() + assert.Nil(t, err) + // take a snapshot + err = machine.Snapshot() + assert.Nil(t, err) + // advance one cycle + var breakReason BreakReason + breakReason, err = machine.Run(1) + assert.Nil(t, err) + assert.Equal(t, BreakReasonReachedTargetMcycle, breakReason) + // get new hash + var hashAfter *MerkleTreeHash + hashAfter, err = machine.GetRootHash() + assert.Nil(t, err) + assert.NotEqual(t, hashBefore.String(), hashAfter.String()) + // rollback + err = machine.Rollback() + assert.Nil(t, err) + // get hash after rollback + var hashAfterRollback *MerkleTreeHash + hashAfterRollback, err = machine.GetRootHash() + assert.Nil(t, err) + assert.Equal(t, hashBefore.String(), hashAfterRollback.String()) + +} + +func sharedMachineTests(t *testing.T, m *Machine) { + cfg, err := m.GetInitialConfig() + assert.Nil(t, err) + assert.NotNil(t, cfg) + + // Toggle flags that control dapp execution + // read and toggle IFlagsY + var iflagsY bool + iflagsY, err = m.ReadIFlagsY() + assert.Nil(t, err) + assert.False(t, iflagsY) + err = m.SetIFlagsY() + assert.Nil(t, err) + iflagsY, err = m.ReadIFlagsY() + assert.Nil(t, err) + assert.True(t, iflagsY) + // read and toggle IFlagsH + var iflagsH bool + iflagsH, err = m.ReadIFlagsH() + assert.Nil(t, err) + assert.False(t, iflagsH) + err = m.SetIFlagsH() + assert.Nil(t, err) + iflagsH, err = m.ReadIFlagsH() + assert.Nil(t, err) + assert.True(t, iflagsH) +} + +const rxBufferStartAddress = 0x60000000 +const rxBufferLength = 1 << 21 + +func makeMachineConfig() *MachineConfig { + images_path := strings.TrimRight(os.Getenv("CARTESI_IMAGES_PATH"), "/") + "/" + cfg := NewDefaultMachineConfig() + cfg.Processor.Mimpid = math.MaxUint64 + cfg.Processor.Marchid = math.MaxUint64 + cfg.Processor.Mvendorid = math.MaxUint64 + cfg.Ram.ImageFilename = images_path + "linux.bin" + cfg.Ram.Length = 64 << 20 + cfg.FlashDrive = []MemoryRangeConfig{ + { + Start: 0x80000000000000, + Length: 0xffffffffffffffff, + Shared: false, + ImageFilename: images_path + "rootfs.ext2", + }, + } + cfg.Dtb.Bootargs = "quiet earlycon=sbi console=hvc0 rootfstype=ext2 root=/dev/pmem0 rw init=/usr/sbin/cartesi-init" + cfg.Dtb.Init = `echo "Opa!" + busybox mkdir -p /run/drive-label && echo "root" > /run/drive-label/pmem0\ + USER=dapp + ` + + cfg.Cmio.HsaValue = true + cfg.Cmio.RxBuffer = MemoryRangeConfig{ + Start: rxBufferStartAddress, + Length: rxBufferLength, + } + cfg.Cmio.TxBuffer = MemoryRangeConfig{ + Start: 0x60200000, + Length: 1 << 21, + } + + return cfg +} + +func makeRemoteAddress() string { + remoteServerPort := os.Getenv("JSONRPC_REMOTE_CARTESI_MACHINE_PORT") // example: 3333 + return "localhost:" + remoteServerPort +} + +func launchRemoteServer(t *testing.T) *exec.Cmd { + remoteMachineServerPath := os.Getenv("JSONRPC_REMOTE_CARTESI_MACHINE_PATH") + // launch remote server + cmd := exec.Command(remoteMachineServerPath, "--server-address="+makeRemoteAddress()) + err := cmd.Start() + assert.Nil(t, err) + time.Sleep(2 * time.Second) + return cmd +} From 8b7877890978a3759e1c338d7e8f227819135000 Mon Sep 17 00:00:00 2001 From: Renan Santos Date: Thu, 21 Mar 2024 16:42:47 -0300 Subject: [PATCH 2/2] refactor: remove tests (temporarily) and reorganize code to fit Go conventions --- cmd/cartesi-machine/main.go | 2 +- pkg/emulator/.gitignore | 1 - pkg/emulator/emulator.go | 433 ++++--------------------------- pkg/emulator/emulator_test.go | 474 ---------------------------------- pkg/emulator/machine.go | 224 ++++++++++++++++ pkg/emulator/remote.go | 92 +++++++ 6 files changed, 364 insertions(+), 862 deletions(-) delete mode 100644 pkg/emulator/.gitignore delete mode 100644 pkg/emulator/emulator_test.go create mode 100644 pkg/emulator/machine.go create mode 100644 pkg/emulator/remote.go diff --git a/cmd/cartesi-machine/main.go b/cmd/cartesi-machine/main.go index 490c7e6dd..78cf2ac1b 100644 --- a/cmd/cartesi-machine/main.go +++ b/cmd/cartesi-machine/main.go @@ -17,7 +17,7 @@ import ( func main() { var machine *emulator.Machine - defer machine.Free() + defer machine.Delete() var mgr *emulator.RemoteMachineManager defer mgr.Free() var err error diff --git a/pkg/emulator/.gitignore b/pkg/emulator/.gitignore deleted file mode 100644 index 496ee2ca6..000000000 --- a/pkg/emulator/.gitignore +++ /dev/null @@ -1 +0,0 @@ -.DS_Store \ No newline at end of file diff --git a/pkg/emulator/emulator.go b/pkg/emulator/emulator.go index afd2a03ca..3c316a6d4 100644 --- a/pkg/emulator/emulator.go +++ b/pkg/emulator/emulator.go @@ -1,16 +1,11 @@ // (c) Cartesi and individual authors (see AUTHORS) // SPDX-License-Identifier: Apache-2.0 (see LICENSE) -// Cartesi Machine C API wrapper - package emulator -/* -#cgo LDFLAGS: -lcartesi -lcartesi_jsonrpc -#include "machine-c-api.h" -#include "jsonrpc-machine-c-api.h" -#include -*/ +// #cgo LDFLAGS: -lcartesi -lcartesi_jsonrpc +// #include +// #include "cartesi-machine/jsonrpc-machine-c-api.h" import "C" import ( @@ -19,6 +14,10 @@ import ( "unsafe" ) +// ------------------------------------------------------------------------------------------------ +// Error +// ------------------------------------------------------------------------------------------------ + type ErrorCode int32 const ( @@ -53,10 +52,6 @@ const ( ErrorCodeUnknown ErrorCode = C.CM_ERROR_UNKNOWN ) -func isFailure(cerr C.int) bool { - return ErrorCode(cerr) != ErrorCodeOk -} - type Error struct { Code ErrorCode Msg string @@ -66,14 +61,18 @@ func (e *Error) Error() string { return fmt.Sprintf("cartesi machine error %d (%s)", e.Code, e.Msg) } -func newError(code C.int, message *C.char) error { - defer C.cm_delete_cstring(message) +func newError(code C.int, msg *C.char) error { + defer C.cm_delete_cstring(msg) if code != C.CM_ERROR_OK { - return &Error{Code: ErrorCode(code), Msg: C.GoString(message)} + return &Error{Code: ErrorCode(code), Msg: C.GoString(msg)} } return nil } +// ------------------------------------------------------------------------------------------------ +// Types +// ------------------------------------------------------------------------------------------------ + type BreakReason int32 const ( @@ -249,9 +248,37 @@ type UarchConfig struct { Ram UarchRamConfig } -//////////////////////////////////////// -// Helpers and utils -//////////////////////////////////////// +// ------------------------------------------------------------------------------------------------ +// MachineConfig +// ------------------------------------------------------------------------------------------------ + +func NewDefaultMachineConfig() *MachineConfig { + ref := theirMachineConfigCRef{} + defer ref.free() + ref.cref = C.cm_new_default_machine_config() + return ref.makeGoRef() +} + +func GetDefaultMachineConfig() (*MachineConfig, error) { + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + var msg *C.char + code := C.cm_get_default_config(&theirCfg.cref, &msg) + if err := newError(code, msg); err != nil { + return nil, err + } + return theirCfg.makeGoRef(), nil +} + +// ------------------------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------------------------ + +type MerkleTreeHash [32]byte + +func (hash *MerkleTreeHash) String() string { + return hex.EncodeToString(hash[:]) +} type ourMemoryRangeConfig struct { cref *C.cm_memory_range_config @@ -376,7 +403,8 @@ func (config *MachineConfig) makeCRef() (ref *ourMachineConfigCRef) { cFlashDrive := &ref.cref.flash_drive flashDrive := &config.FlashDrive cFlashDrive.count = (C.ulong)(len(*flashDrive)) - cFlashDrive.entry = (*C.cm_memory_range_config)(C.calloc((C.ulong)(len(*flashDrive)), C.sizeof_cm_memory_range_config)) + cFlashDrive.entry = (*C.cm_memory_range_config)(C.calloc((C.ulong)(len(*flashDrive)), + C.sizeof_cm_memory_range_config)) for i, v := range *flashDrive { offset := C.sizeof_cm_memory_range_config * i addr := unsafe.Pointer(uintptr(unsafe.Pointer(cFlashDrive.entry)) + uintptr(offset)) @@ -586,370 +614,3 @@ func makeCString(s *string) *C.char { } return C.CString(*s) } - -/////////////////////////// -// Public API -/////////////////////////// - -func NewDefaultMachineConfig() *MachineConfig { - ref := theirMachineConfigCRef{} - defer ref.free() - ref.cref = C.cm_new_default_machine_config() - return ref.makeGoRef() -} - -// a connection to the remote jsonrpc machine manager -type RemoteMachineManager struct { - RemoteAddress string - cref *C.cm_jsonrpc_mg_mgr -} - -// a local or remote machine -type Machine struct { - cref *C.cm_machine - remoteManager *RemoteMachineManager -} - -func (mgr *RemoteMachineManager) Free() { - if mgr != nil && mgr.cref != nil { - C.cm_delete_jsonrpc_mg_mgr(mgr.cref) - mgr.cref = nil - } -} - -func (machine *Machine) Free() { - if machine == nil || machine.cref == nil { - return - } - C.cm_delete_machine(machine.cref) - machine.cref = nil -} - -func (machine *Machine) Store(dir string) error { - cDir := C.CString(dir) - defer C.free(unsafe.Pointer(cDir)) - var cerr *C.char - if e := C.cm_store(machine.cref, cDir, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func NewMachine(config *MachineConfig, runtime *MachineRuntimeConfig) (*Machine, error) { - machine := &Machine{} - configRef := config.makeCRef() - defer configRef.free() - runtimeRef := runtime.makeCRef() - defer runtimeRef.free() - var cerr *C.char - if e := C.cm_create_machine(configRef.cref, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - return machine, nil -} - -func LoadMachine(dir string, runtime *MachineRuntimeConfig) (*Machine, error) { - machine := &Machine{} - cDir := C.CString(dir) - defer C.free(unsafe.Pointer(cDir)) - runtimeRef := runtime.makeCRef() - defer runtimeRef.free() - var cerr *C.char - if e := C.cm_load_machine(cDir, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { - return nil, newError(e, cerr) - - } - return machine, nil -} - -func GetDefaultConfig() (*MachineConfig, error) { - theirCfg := theirMachineConfigCRef{} - defer theirCfg.free() - var cerr *C.char - if e := C.cm_get_default_config(&theirCfg.cref, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - return theirCfg.makeGoRef(), nil -} - -func (m *Machine) Run(mcycleEnd uint64) (BreakReason, error) { - var cerr *C.char - var creason C.CM_BREAK_REASON - if e := C.cm_machine_run(m.cref, C.uint64_t(mcycleEnd), &creason, &cerr); isFailure(e) { - return BreakReasonFailed, newError(e, cerr) - } - return (BreakReason)(creason), nil -} - -func (machine *Machine) GetInitialConfig() (*MachineConfig, error) { - theirCfg := theirMachineConfigCRef{} - defer theirCfg.free() - var cerr *C.char - if e := C.cm_get_initial_config(machine.cref, &theirCfg.cref, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - return theirCfg.makeGoRef(), nil -} - -func (machine *Machine) Destroy() error { - var cerr *C.char - if e := C.cm_destroy(machine.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) ReadCSR(r ProcessorCSR) (uint64, error) { - var cval C.uint64_t - var cerr *C.char - if e := C.cm_read_csr(machine.cref, C.CM_PROC_CSR(r), &cval, &cerr); isFailure(e) { - return 0, newError(e, cerr) - } - return uint64(cval), nil -} - -func (machine *Machine) WriteCSR(r ProcessorCSR, val uint64) error { - var cerr *C.char - if e := C.cm_write_csr(machine.cref, C.CM_PROC_CSR(r), C.uint64_t(val), &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) ReadX(i int) (uint64, error) { - var cval C.uint64_t - var cerr *C.char - if e := C.cm_read_x(machine.cref, C.int(i), &cval, &cerr); isFailure(e) { - return 0, newError(e, cerr) - } - return uint64(cval), nil -} - -func (machine *Machine) WriteX(i int, val uint64) error { - var cerr *C.char - if e := C.cm_write_x(machine.cref, C.int(i), C.uint64_t(val), &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) ReadF(i int) (uint64, error) { - var cval C.uint64_t - var cerr *C.char - if e := C.cm_read_f(machine.cref, C.int(i), &cval, &cerr); isFailure(e) { - return 0, newError(e, cerr) - } - return uint64(cval), nil -} - -func (machine *Machine) ReadIFlagsX() (bool, error) { - var cval C.bool - var cerr *C.char - if e := C.cm_read_iflags_X(machine.cref, &cval, &cerr); isFailure(e) { - return false, newError(e, cerr) - } - return bool(cval), nil -} - -func (machine *Machine) ResetIFlagsX() error { - var cerr *C.char - if e := C.cm_reset_iflags_X(machine.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) SetIFlagsX() error { - var cerr *C.char - if e := C.cm_set_iflags_X(machine.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) ReadIFlagsY() (bool, error) { - var cval C.bool - var cerr *C.char - if e := C.cm_read_iflags_Y(machine.cref, &cval, &cerr); isFailure(e) { - return false, newError(e, cerr) - } - return bool(cval), nil -} - -func (machine *Machine) ResetIFlagsY() error { - var cerr *C.char - if e := C.cm_reset_iflags_Y(machine.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) SetIFlagsY() error { - var cerr *C.char - if e := C.cm_set_iflags_Y(machine.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) ReadIFlagsH() (bool, error) { - var cval C.bool - var cerr *C.char - if e := C.cm_read_iflags_H(machine.cref, &cval, &cerr); isFailure(e) { - return false, newError(e, cerr) - } - return bool(cval), nil -} - -func (machine *Machine) SetIFlagsH() error { - var cerr *C.char - if e := C.cm_set_iflags_H(machine.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -type MerkleTreeHash [32]byte - -func (hash *MerkleTreeHash) String() string { - return hex.EncodeToString(hash[:]) -} - -func (machine *Machine) GetRootHash() (*MerkleTreeHash, error) { - var chash C.cm_hash - var cerr *C.char - if e := C.cm_get_root_hash(machine.cref, &chash, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - hash := &MerkleTreeHash{} - for i := 0; i < 32; i++ { - hash[i] = byte(chash[i]) - } - return hash, nil -} - -func (machine *Machine) WriteF(i int, val uint64) error { - var cerr *C.char - if e := C.cm_write_f(machine.cref, C.int(i), C.uint64_t(val), &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func NewRemoteMachineManager(remoteAddress string) (*RemoteMachineManager, error) { - manager := &RemoteMachineManager{RemoteAddress: remoteAddress} - cRemoteAddress := C.CString(remoteAddress) - defer C.free(unsafe.Pointer(cRemoteAddress)) - var cerr *C.char - if e := C.cm_create_jsonrpc_mg_mgr(cRemoteAddress, &manager.cref, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - return manager, nil -} - -func (mgr *RemoteMachineManager) Shutdown() error { - var cerr *C.char - if e := C.cm_jsonrpc_shutdown(mgr.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (mgr *RemoteMachineManager) Fork() (*string, error) { - var cNewAddress *C.char = nil - var cerr *C.char - if e := C.cm_jsonrpc_fork(mgr.cref, &cNewAddress, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - newAddress := C.GoString(cNewAddress) - C.cm_delete_cstring(cNewAddress) - return &newAddress, nil -} - -func (mgr *RemoteMachineManager) NewMachine(config *MachineConfig, runtime *MachineRuntimeConfig) (*Machine, error) { - machine := &Machine{remoteManager: mgr} - configRef := config.makeCRef() - defer configRef.free() - runtimeRef := runtime.makeCRef() - defer runtimeRef.free() - var cerr *C.char - if e := C.cm_create_jsonrpc_machine(mgr.cref, configRef.cref, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - return machine, nil -} - -func (mgr *RemoteMachineManager) GetDefaultConfig() (*MachineConfig, error) { - theirCfg := theirMachineConfigCRef{} - defer theirCfg.free() - var cerr *C.char - if e := C.cm_jsonrpc_get_default_config(mgr.cref, &theirCfg.cref, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - return theirCfg.makeGoRef(), nil -} - -func (mgr *RemoteMachineManager) LoadMachine(dir string, runtime *MachineRuntimeConfig) (*Machine, error) { - machine := &Machine{remoteManager: mgr} - cDir := C.CString(dir) - defer C.free(unsafe.Pointer(cDir)) - runtimeRef := runtime.makeCRef() - defer runtimeRef.free() - var cerr *C.char - if e := C.cm_load_jsonrpc_machine(mgr.cref, cDir, runtimeRef.cref, &machine.cref, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - return machine, nil -} - -func (mgr *RemoteMachineManager) GetMachine() (*Machine, error) { - machine := &Machine{remoteManager: mgr} - var cerr *C.char - if e := C.cm_get_jsonrpc_machine(mgr.cref, &machine.cref, &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - return machine, nil -} - -func (machine *Machine) WriteMemory(address uint64, data []byte) error { - var cerr *C.char - if e := C.cm_write_memory(machine.cref, C.uint64_t(address), (*C.uchar)(&data[0]), C.size_t(len(data)), &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) ReadMemory(address uint64, length uint64) ([]byte, error) { - data := make([]byte, length) - var cerr *C.char - if e := C.cm_read_memory(machine.cref, C.uint64_t(address), (*C.uchar)(&data[0]), C.uint64_t(length), &cerr); isFailure(e) { - return nil, newError(e, cerr) - } - return data, nil -} - -func (machine *Machine) ReplaceMemoryRange(newRange *MemoryRangeConfig) error { - newRangeRef := newRange.makeCRef() - defer newRangeRef.free() - var cerr *C.char - if e := C.cm_replace_memory_range(machine.cref, newRangeRef.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) Snapshot() error { - var cerr *C.char - if e := C.cm_snapshot(machine.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} - -func (machine *Machine) Rollback() error { - var cerr *C.char - if e := C.cm_rollback(machine.cref, &cerr); isFailure(e) { - return newError(e, cerr) - } - return nil -} diff --git a/pkg/emulator/emulator_test.go b/pkg/emulator/emulator_test.go deleted file mode 100644 index 3711fbfc5..000000000 --- a/pkg/emulator/emulator_test.go +++ /dev/null @@ -1,474 +0,0 @@ -// (c) Cartesi and individual authors (see AUTHORS) -// SPDX-License-Identifier: Apache-2.0 (see LICENSE) - -// Test Cartesi Machine C API wrapper - -package emulator - -import ( - "math" - "os" - "os/exec" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" -) - -func TestGetDefaultConfig(t *testing.T) { - cfg, err := GetDefaultConfig() - assert.Nil(t, err) - assert.NotNil(t, cfg) -} - -func TestNewDefaultMachineConfig(t *testing.T) { - cfg := NewDefaultMachineConfig() - assert.NotNil(t, cfg) -} - -func TestNewLocalMachine(t *testing.T) { - cfg := makeMachineConfig() - runtimeConfig := &MachineRuntimeConfig{} - machine, err := NewMachine(cfg, runtimeConfig) - defer machine.Free() - assert.Nil(t, err) - assert.NotNil(t, machine) - sharedMachineTests(t, machine) -} - -func TestReadWriteMemoryOnLocalMachine(t *testing.T) { - cfg := makeMachineConfig() - runtimeConfig := &MachineRuntimeConfig{} - machine, err := NewMachine(cfg, runtimeConfig) - assert.Nil(t, err) - defer machine.Free() - sharedTestReadWriteMemory(t, machine) -} - -func TestReadWriteMemoryOnRemoteMachine(t *testing.T) { - // launch remote server - cmd := launchRemoteServer(t) - defer cmd.Process.Kill() - // connect to remote server - mgr, err := NewRemoteMachineManager(makeRemoteAddress()) - assert.Nil(t, err) - assert.NotNil(t, mgr) - defer func() { - mgr.Shutdown() - defer mgr.Free() - }() - // create machine - cfg := makeMachineConfig() - runtimeConfig := &MachineRuntimeConfig{} - machine, err := mgr.NewMachine(cfg, runtimeConfig) - assert.Nil(t, err) - assert.NotNil(t, machine) - defer func() { - machine.Destroy() - machine.Free() - }() - sharedTestReadWriteMemory(t, machine) -} - -func sharedTestReadWriteMemory(t *testing.T, machine *Machine) { - // read existing data - existingData, err := machine.ReadMemory(rxBufferStartAddress, 1024) - assert.Nil(t, err) - assert.NotNil(t, existingData) - assert.Equal(t, 1024, len(existingData)) - // Prepare new data - var newData [1024]byte - for i := 0; i < 1024; i++ { - newData[i] = byte(i % 256) - } - assert.NotEqual(t, newData[:], existingData) - // write new data - err = machine.WriteMemory(rxBufferStartAddress, newData[:]) - assert.Nil(t, err) - // read back the data - readBackData, err := machine.ReadMemory(rxBufferStartAddress, 1024) - assert.Nil(t, err) - assert.NotNil(t, readBackData) - assert.Equal(t, newData[:], readBackData) - - // create a byte array full of 0xda of the size of rxBufferLength - var newRxBufferData [rxBufferLength]byte - for i := 0; i < rxBufferLength; i++ { - newRxBufferData[i] = 0xda - } - // create a temporary file and write the new data to it - tempFile, err := os.CreateTemp("", "rxBuffer") - assert.Nil(t, err) - defer os.Remove(tempFile.Name()) - _, err = tempFile.Write(newRxBufferData[:]) - assert.Nil(t, err) - tempFile.Close() - // initialize a MemoryRangeConfig pointing to this file - newRxBuffer := &MemoryRangeConfig{ - Start: rxBufferStartAddress, - Length: rxBufferLength, - Shared: false, - ImageFilename: tempFile.Name(), - } - // replace the rxBuffer with the new data - err = machine.ReplaceMemoryRange(newRxBuffer) - assert.Nil(t, err) - // read back memory and check if it was replaced - readBackRxData, err := machine.ReadMemory(rxBufferStartAddress, rxBufferLength) - assert.Nil(t, err) - assert.NotNil(t, readBackRxData) - assert.Equal(t, newRxBufferData[:], readBackRxData) - -} - -func TestRunLocalMachineHappyPath(t *testing.T) { - cfg := makeMachineConfig() - runtimeConfig := &MachineRuntimeConfig{} - machine, err := NewMachine(cfg, runtimeConfig) - defer machine.Free() - assert.Nil(t, err) - assert.NotNil(t, machine) - var iflagsH bool - var mcycle uint64 - var hashBefore *MerkleTreeHash - var hashAfter *MerkleTreeHash - var breakReason BreakReason - // assert initial state - iflagsH, err = machine.ReadIFlagsH() - assert.Nil(t, err) - assert.False(t, iflagsH) - mcycle, err = machine.ReadCSR(ProcCsrMcycle) - assert.Nil(t, err) - assert.Equal(t, uint64(0), mcycle) - hashBefore, err = machine.GetRootHash() - assert.Nil(t, err) - // Advance one cycle - breakReason, err = machine.Run(1) - assert.Nil(t, err) - assert.Equal(t, BreakReasonReachedTargetMcycle, breakReason) - iflagsH, err = machine.ReadIFlagsH() - assert.Nil(t, err) - assert.False(t, iflagsH) // still not halted - mcycle, err = machine.ReadCSR(ProcCsrMcycle) - assert.Nil(t, err) - assert.Equal(t, uint64(1), mcycle) // advanced one cycle - hashAfter, err = machine.GetRootHash() - assert.Nil(t, err) - assert.NotEqual(t, hashBefore.String(), hashAfter.String()) // hash changed - storedHash := hashAfter - // Store machine - tempDir, err := os.MkdirTemp("", "testmachines") - assert.Nil(t, err) - defer os.RemoveAll(tempDir) - storePath := tempDir + "/machine" - err = machine.Store(storePath) - assert.Nil(t, err) - // run until halt - hashBefore = hashAfter - breakReason, err = machine.Run(math.MaxUint64) - assert.Nil(t, err) - assert.Equal(t, BreakReasonHalted, breakReason) - iflagsH, err = machine.ReadIFlagsH() - assert.Nil(t, err) - assert.True(t, iflagsH) // halted - mcycle, err = machine.ReadCSR(ProcCsrMcycle) - assert.Nil(t, err) - assert.Greater(t, mcycle, uint64(1)) - hashAfter, err = machine.GetRootHash() - assert.Nil(t, err) - assert.NotEqual(t, hashBefore.String(), hashAfter.String()) // hash changed again - - // load a second machine from empDir, err := ioutil.TempDir("", "example") - machine2, err := LoadMachine(storePath, runtimeConfig) - defer machine2.Free() - assert.Nil(t, err) - assert.NotNil(t, machine2) - var hashMachine2 *MerkleTreeHash - hashMachine2, err = machine2.GetRootHash() - assert.Nil(t, err) - assert.Equal(t, storedHash.String(), hashMachine2.String()) // hash is the same -} - -func TestNewRemoteMachine(t *testing.T) { - // launch remote server - cmd := launchRemoteServer(t) - defer cmd.Process.Kill() - // connect to remote server - mgr, err := NewRemoteMachineManager(makeRemoteAddress()) - assert.Nil(t, err) - assert.NotNil(t, mgr) - defer func() { - mgr.Shutdown() - defer mgr.Free() - }() - // create machine - cfg := makeMachineConfig() - runtimeConfig := &MachineRuntimeConfig{} - machine, err := mgr.NewMachine(cfg, runtimeConfig) - assert.Nil(t, err) - assert.NotNil(t, machine) - defer func() { - machine.Destroy() - machine.Free() - }() - sharedMachineTests(t, machine) -} - -func TestRemoteMachineHappyPath(t *testing.T) { - var err error - remoteAddress := makeRemoteAddress() - // launch remote server - cmd := launchRemoteServer(t) - defer cmd.Process.Kill() - // Connect to remote server - var mgr *RemoteMachineManager - mgr, err = NewRemoteMachineManager(makeRemoteAddress()) - assert.Nil(t, err) - assert.NotNil(t, mgr) - defer func() { - mgr.Shutdown() - defer mgr.Free() - }() - // get default config from remote server - var remoteDefCfg *MachineConfig - remoteDefCfg, err = mgr.GetDefaultConfig() - assert.Nil(t, err) - assert.NotNil(t, remoteDefCfg) - - // create a remote machine - var machine *Machine - cfg := makeMachineConfig() - runtimeConfig := &MachineRuntimeConfig{} - machine, err = mgr.NewMachine(cfg, runtimeConfig) - assert.Nil(t, err) - assert.NotNil(t, machine) - defer func() { - machine.Destroy() // destroy machine from server - machine.Free() - }() - // assert initial machine state - var iflagsH bool - var mcycle uint64 - var hashBefore *MerkleTreeHash - var finalHash *MerkleTreeHash - var breakReason BreakReason - iflagsH, err = machine.ReadIFlagsH() - assert.Nil(t, err) - assert.False(t, iflagsH) - mcycle, err = machine.ReadCSR(ProcCsrMcycle) - assert.Nil(t, err) - assert.Equal(t, uint64(0), mcycle) - hashBefore, err = machine.GetRootHash() - assert.Nil(t, err) - // Advance one cycle - breakReason, err = machine.Run(1) - assert.Nil(t, err) - assert.Equal(t, BreakReasonReachedTargetMcycle, breakReason) - iflagsH, err = machine.ReadIFlagsH() - assert.Nil(t, err) - assert.False(t, iflagsH) // still not halted - mcycle, err = machine.ReadCSR(ProcCsrMcycle) - assert.Nil(t, err) - assert.Equal(t, uint64(1), mcycle) // advanced one cycle - finalHash, err = machine.GetRootHash() - assert.Nil(t, err) - assert.NotEqual(t, hashBefore.String(), finalHash.String()) // hash changed - storedHash := finalHash - // Store machine - tempDir, err := os.MkdirTemp("", "testmachines") - assert.Nil(t, err) - defer os.RemoveAll(tempDir) - storePath := tempDir + "/machine" - err = machine.Store(storePath) - assert.Nil(t, err) - // run until halt - hashBefore = finalHash - breakReason, err = machine.Run(math.MaxUint64) - assert.Nil(t, err) - assert.Equal(t, BreakReasonHalted, breakReason) - iflagsH, err = machine.ReadIFlagsH() - assert.Nil(t, err) - assert.True(t, iflagsH) // halted - mcycle, err = machine.ReadCSR(ProcCsrMcycle) - assert.Nil(t, err) - assert.Greater(t, mcycle, uint64(1)) - finalHash, err = machine.GetRootHash() - assert.Nil(t, err) - assert.NotEqual(t, hashBefore.String(), finalHash.String()) // hash changed again - // fork the remote server - var secondRemoteAddress *string - secondRemoteAddress, err = mgr.Fork() - assert.Nil(t, err) - assert.NotEqual(t, remoteAddress, secondRemoteAddress) - assert.NotEqual(t, "", secondRemoteAddress) - assert.NotEqual(t, remoteAddress, secondRemoteAddress) - // connect to the forked server - var mgr2 *RemoteMachineManager - mgr2, err = NewRemoteMachineManager(*secondRemoteAddress) - assert.Nil(t, err) - assert.NotNil(t, mgr2) - // Get forked machine - var forkedMachine *Machine - forkedMachine, err = mgr2.GetMachine() - assert.Nil(t, err) - assert.NotNil(t, forkedMachine) - defer func() { - forkedMachine.Destroy() - forkedMachine.Free() - }() - var forkedHash *MerkleTreeHash - forkedHash, err = forkedMachine.GetRootHash() - assert.Nil(t, err) - assert.Equal(t, finalHash.String(), forkedHash.String()) - // destroy existing machine on mgr2 - err = forkedMachine.Destroy() - assert.Nil(t, err) - // Load stored machine on mgr2 - var loadedMachine *Machine - loadedMachine, err = mgr2.LoadMachine(storePath, runtimeConfig) - assert.Nil(t, err) - assert.NotNil(t, loadedMachine) - defer func() { - loadedMachine.Destroy() - loadedMachine.Free() - }() - var loadedHash *MerkleTreeHash - loadedHash, err = loadedMachine.GetRootHash() - assert.Nil(t, err) - assert.Equal(t, storedHash.String(), loadedHash.String()) -} - -func TestSnapshot(t *testing.T) { - // launch remote server - cmd := launchRemoteServer(t) - defer cmd.Process.Kill() - // Connect to remote server - var mgr *RemoteMachineManager - var err error - mgr, err = NewRemoteMachineManager(makeRemoteAddress()) - assert.Nil(t, err) - assert.NotNil(t, mgr) - defer func() { - mgr.Shutdown() - defer mgr.Free() - }() - // create a machine - var machine *Machine - cfg := makeMachineConfig() - runtimeConfig := &MachineRuntimeConfig{} - machine, err = mgr.NewMachine(cfg, runtimeConfig) - assert.Nil(t, err) - assert.NotNil(t, machine) - defer func() { - machine.Destroy() - machine.Free() - }() - // get current hash - var hashBefore *MerkleTreeHash - hashBefore, err = machine.GetRootHash() - assert.Nil(t, err) - // take a snapshot - err = machine.Snapshot() - assert.Nil(t, err) - // advance one cycle - var breakReason BreakReason - breakReason, err = machine.Run(1) - assert.Nil(t, err) - assert.Equal(t, BreakReasonReachedTargetMcycle, breakReason) - // get new hash - var hashAfter *MerkleTreeHash - hashAfter, err = machine.GetRootHash() - assert.Nil(t, err) - assert.NotEqual(t, hashBefore.String(), hashAfter.String()) - // rollback - err = machine.Rollback() - assert.Nil(t, err) - // get hash after rollback - var hashAfterRollback *MerkleTreeHash - hashAfterRollback, err = machine.GetRootHash() - assert.Nil(t, err) - assert.Equal(t, hashBefore.String(), hashAfterRollback.String()) - -} - -func sharedMachineTests(t *testing.T, m *Machine) { - cfg, err := m.GetInitialConfig() - assert.Nil(t, err) - assert.NotNil(t, cfg) - - // Toggle flags that control dapp execution - // read and toggle IFlagsY - var iflagsY bool - iflagsY, err = m.ReadIFlagsY() - assert.Nil(t, err) - assert.False(t, iflagsY) - err = m.SetIFlagsY() - assert.Nil(t, err) - iflagsY, err = m.ReadIFlagsY() - assert.Nil(t, err) - assert.True(t, iflagsY) - // read and toggle IFlagsH - var iflagsH bool - iflagsH, err = m.ReadIFlagsH() - assert.Nil(t, err) - assert.False(t, iflagsH) - err = m.SetIFlagsH() - assert.Nil(t, err) - iflagsH, err = m.ReadIFlagsH() - assert.Nil(t, err) - assert.True(t, iflagsH) -} - -const rxBufferStartAddress = 0x60000000 -const rxBufferLength = 1 << 21 - -func makeMachineConfig() *MachineConfig { - images_path := strings.TrimRight(os.Getenv("CARTESI_IMAGES_PATH"), "/") + "/" - cfg := NewDefaultMachineConfig() - cfg.Processor.Mimpid = math.MaxUint64 - cfg.Processor.Marchid = math.MaxUint64 - cfg.Processor.Mvendorid = math.MaxUint64 - cfg.Ram.ImageFilename = images_path + "linux.bin" - cfg.Ram.Length = 64 << 20 - cfg.FlashDrive = []MemoryRangeConfig{ - { - Start: 0x80000000000000, - Length: 0xffffffffffffffff, - Shared: false, - ImageFilename: images_path + "rootfs.ext2", - }, - } - cfg.Dtb.Bootargs = "quiet earlycon=sbi console=hvc0 rootfstype=ext2 root=/dev/pmem0 rw init=/usr/sbin/cartesi-init" - cfg.Dtb.Init = `echo "Opa!" - busybox mkdir -p /run/drive-label && echo "root" > /run/drive-label/pmem0\ - USER=dapp - ` - - cfg.Cmio.HsaValue = true - cfg.Cmio.RxBuffer = MemoryRangeConfig{ - Start: rxBufferStartAddress, - Length: rxBufferLength, - } - cfg.Cmio.TxBuffer = MemoryRangeConfig{ - Start: 0x60200000, - Length: 1 << 21, - } - - return cfg -} - -func makeRemoteAddress() string { - remoteServerPort := os.Getenv("JSONRPC_REMOTE_CARTESI_MACHINE_PORT") // example: 3333 - return "localhost:" + remoteServerPort -} - -func launchRemoteServer(t *testing.T) *exec.Cmd { - remoteMachineServerPath := os.Getenv("JSONRPC_REMOTE_CARTESI_MACHINE_PATH") - // launch remote server - cmd := exec.Command(remoteMachineServerPath, "--server-address="+makeRemoteAddress()) - err := cmd.Start() - assert.Nil(t, err) - time.Sleep(2 * time.Second) - return cmd -} diff --git a/pkg/emulator/machine.go b/pkg/emulator/machine.go new file mode 100644 index 000000000..7d06e141b --- /dev/null +++ b/pkg/emulator/machine.go @@ -0,0 +1,224 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package emulator + +// #include +// #include "cartesi-machine/jsonrpc-machine-c-api.h" +import "C" +import "unsafe" + +// A local or remote machine. +type Machine struct { + c *C.cm_machine + remote *RemoteMachineManager +} + +func (machine *Machine) GetInitialConfig() (*MachineConfig, error) { + var msg *C.char + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + code := C.cm_get_initial_config(machine.c, &theirCfg.cref, &msg) + if err := newError(code, msg); err != nil { + return nil, err + } + return theirCfg.makeGoRef(), nil +} + +func NewMachine(config *MachineConfig, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{} + configRef := config.makeCRef() + defer configRef.free() + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var msg *C.char + code := C.cm_create_machine(configRef.cref, runtimeRef.cref, &machine.c, &msg) + return machine, newError(code, msg) +} + +func LoadMachine(dir string, runtime *MachineRuntimeConfig) (*Machine, error) { + machine := &Machine{} + cDir := C.CString(dir) + defer C.free(unsafe.Pointer(cDir)) + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + var msg *C.char + code := C.cm_load_machine(cDir, runtimeRef.cref, &machine.c, &msg) + return machine, newError(code, msg) +} + +func (machine *Machine) Store(dir string) error { + cDir := C.CString(dir) + defer C.free(unsafe.Pointer(cDir)) + var msg *C.char + code := C.cm_store(machine.c, cDir, &msg) + return newError(code, msg) +} + +func (machine *Machine) Delete() { + if machine.c != nil { + C.cm_delete_machine(machine.c) + machine.c = nil + } +} + +func (machine *Machine) Destroy() error { + var msg *C.char + code := C.cm_destroy(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) Snapshot() error { + var msg *C.char + code := C.cm_snapshot(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) Rollback() error { + var msg *C.char + code := C.cm_rollback(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) Run(mcycleEnd uint64) (BreakReason, error) { + var msg *C.char + var reason C.CM_BREAK_REASON + code := C.cm_machine_run(machine.c, C.uint64_t(mcycleEnd), &reason, &msg) + if err := newError(code, msg); err != nil { + return BreakReasonFailed, err + } + return (BreakReason)(reason), nil +} + +func (machine *Machine) GetRootHash() (*MerkleTreeHash, error) { + var msg *C.char + var chash C.cm_hash + code := C.cm_get_root_hash(machine.c, &chash, &msg) + if err := newError(code, msg); err != nil { + return nil, err + } + hash := &MerkleTreeHash{} + for i := 0; i < 32; i++ { + hash[i] = byte(chash[i]) + } + return hash, nil +} + +func (machine *Machine) ReplaceMemoryRange(newRange *MemoryRangeConfig) error { + var msg *C.char + newRangeRef := newRange.makeCRef() + defer newRangeRef.free() + code := C.cm_replace_memory_range(machine.c, newRangeRef.cref, &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadMemory(address, length uint64) ([]byte, error) { + var msg *C.char + data := make([]byte, length) + code := C.cm_read_memory(machine.c, + C.uint64_t(address), + (*C.uchar)(unsafe.Pointer(&data[0])), + C.uint64_t(length), + &msg) + return data, newError(code, msg) +} + +func (machine *Machine) WriteMemory(address uint64, data []byte) error { + var msg *C.char + code := C.cm_write_memory(machine.c, + C.uint64_t(address), + (*C.uchar)(unsafe.Pointer(&data[0])), + C.size_t(len(data)), + &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadCSR(r ProcessorCSR) (uint64, error) { + var msg *C.char + var value C.uint64_t + code := C.cm_read_csr(machine.c, C.CM_PROC_CSR(r), &value, &msg) + return uint64(value), newError(code, msg) +} + +func (machine *Machine) WriteCSR(r ProcessorCSR, value uint64) error { + var msg *C.char + code := C.cm_write_csr(machine.c, C.CM_PROC_CSR(r), C.uint64_t(value), &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadX(i int) (uint64, error) { + var msg *C.char + var value C.uint64_t + code := C.cm_read_x(machine.c, C.int(i), &value, &msg) + return uint64(value), newError(code, msg) +} + +func (machine *Machine) WriteX(i int, value uint64) error { + var msg *C.char + code := C.cm_write_x(machine.c, C.int(i), C.uint64_t(value), &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadF(i int) (uint64, error) { + var msg *C.char + var value C.uint64_t + code := C.cm_read_f(machine.c, C.int(i), &value, &msg) + return uint64(value), newError(code, msg) +} + +func (machine *Machine) WriteF(i int, value uint64) error { + var msg *C.char + code := C.cm_write_f(machine.c, C.int(i), C.uint64_t(value), &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadIFlagsX() (bool, error) { + var msg *C.char + var value C.bool + code := C.cm_read_iflags_X(machine.c, &value, &msg) + return bool(value), newError(code, msg) +} + +func (machine *Machine) ResetIFlagsX() error { + var msg *C.char + code := C.cm_reset_iflags_X(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) SetIFlagsX() error { + var msg *C.char + code := C.cm_set_iflags_X(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadIFlagsY() (bool, error) { + var msg *C.char + var value C.bool + code := C.cm_read_iflags_Y(machine.c, &value, &msg) + return bool(value), newError(code, msg) +} + +func (machine *Machine) ResetIFlagsY() error { + var msg *C.char + code := C.cm_reset_iflags_Y(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) SetIFlagsY() error { + var msg *C.char + code := C.cm_set_iflags_Y(machine.c, &msg) + return newError(code, msg) +} + +func (machine *Machine) ReadIFlagsH() (bool, error) { + var msg *C.char + var value C.bool + code := C.cm_read_iflags_H(machine.c, &value, &msg) + return bool(value), newError(code, msg) +} + +func (machine *Machine) SetIFlagsH() error { + var msg *C.char + code := C.cm_set_iflags_H(machine.c, &msg) + return newError(code, msg) +} diff --git a/pkg/emulator/remote.go b/pkg/emulator/remote.go new file mode 100644 index 000000000..039e19e79 --- /dev/null +++ b/pkg/emulator/remote.go @@ -0,0 +1,92 @@ +// (c) Cartesi and individual authors (see AUTHORS) +// SPDX-License-Identifier: Apache-2.0 (see LICENSE) + +package emulator + +// #include +// #include "cartesi-machine/jsonrpc-machine-c-api.h" +import "C" +import "unsafe" + +// A connection to the remote jsonrpc machine manager. +type RemoteMachineManager struct { + c *C.cm_jsonrpc_mg_mgr + + Address string +} + +func NewRemoteMachineManager(address string) (*RemoteMachineManager, error) { + manager := &RemoteMachineManager{Address: address} + cRemoteAddress := C.CString(address) + defer C.free(unsafe.Pointer(cRemoteAddress)) + var msg *C.char + code := C.cm_create_jsonrpc_mg_mgr(cRemoteAddress, &manager.c, &msg) + return manager, newError(code, msg) +} + +func (remote *RemoteMachineManager) Delete() { + if remote.c != nil { + C.cm_delete_jsonrpc_mg_mgr(remote.c) + remote.c = nil + } +} + +func (remote *RemoteMachineManager) NewMachine( + config *MachineConfig, + runtime *MachineRuntimeConfig, +) (*Machine, error) { + var msg *C.char + machine := &Machine{remote: remote} + configRef := config.makeCRef() + defer configRef.free() + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + code := C.cm_create_jsonrpc_machine(remote.c, configRef.cref, runtimeRef.cref, &machine.c, &msg) + return machine, newError(code, msg) +} + +func (remote *RemoteMachineManager) LoadMachine( + directory string, + runtime *MachineRuntimeConfig, +) (*Machine, error) { + var msg *C.char + machine := &Machine{remote: remote} + dir := C.CString(directory) + defer C.free(unsafe.Pointer(dir)) + runtimeRef := runtime.makeCRef() + defer runtimeRef.free() + code := C.cm_load_jsonrpc_machine(remote.c, dir, runtimeRef.cref, &machine.c, &msg) + return machine, newError(code, msg) +} + +func (remote *RemoteMachineManager) GetMachine() (*Machine, error) { + var msg *C.char + machine := &Machine{remote: remote} + code := C.cm_get_jsonrpc_machine(remote.c, &machine.c, &msg) + return machine, newError(code, msg) +} + +func (remote *RemoteMachineManager) GetDefaultMachineConfig() (*MachineConfig, error) { + var msg *C.char + theirCfg := theirMachineConfigCRef{} + defer theirCfg.free() + code := C.cm_jsonrpc_get_default_config(remote.c, &theirCfg.cref, &msg) + if err := newError(code, msg); err != nil { + return nil, err + } + return theirCfg.makeGoRef(), nil +} + +func (remote *RemoteMachineManager) Fork() (newAddress string, _ error) { + var msg *C.char + var address *C.char + defer C.cm_delete_cstring(address) + code := C.cm_jsonrpc_fork(remote.c, &address, &msg) + return C.GoString(address), newError(code, msg) +} + +func (remote *RemoteMachineManager) Shutdown() error { + var msg *C.char + code := C.cm_jsonrpc_shutdown(remote.c, &msg) + return newError(code, msg) +}