// file : arch/v1/arch.c // project : Salis-VM // author : Paul Oliver // // Based on the original VM architecture from Salis-V1. // The only addition are the extra core-fields used for data aggregation. // index: // [section] includes // [section] getters // [section] statics // [section] callbacks // [section] validation // [section] data // [section] main // ---------------------------------------------------------------------------- // [section] includes // ---------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include "arch.h" #include "arch_spec.h" #include "arch_inst.h" #include "compress.h" #include "logger.h" #include "salis.h" #include "sql.h" // ---------------------------------------------------------------------------- // [section] getters // ---------------------------------------------------------------------------- uint64_t arch_proc_get_ip_addr(const struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); return proc_get(core, pix)->ip; } uint64_t arch_proc_get_sp_addr(const struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); return proc_get(core, pix)->sp; } uint64_t arch_proc_get_mb0_addr(const struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); return proc_get(core, pix)->mb0a; } uint64_t arch_proc_get_mb0_size(const struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); return proc_get(core, pix)->mb0s; } uint64_t arch_proc_get_mb1_addr(const struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); return proc_get(core, pix)->mb1a; } uint64_t arch_proc_get_mb1_size(const struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); return proc_get(core, pix)->mb1s; } uint64_t arch_proc_get_slice(const struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); (void)core; (void)pix; return 1; } // ---------------------------------------------------------------------------- // [section] statics // ---------------------------------------------------------------------------- static void increment_ip(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); proc->ip++; proc->sp = proc->ip; } static bool is_between(uint8_t inst, uint8_t lo, uint8_t hi) { assert(inst < ARCH_INST_COUNT); assert(lo < ARCH_INST_COUNT); assert(hi < ARCH_INST_COUNT); assert(lo < hi); return (inst >= lo) && (inst <= hi); } static bool is_key(uint8_t inst) { assert(inst < ARCH_INST_COUNT); return is_between(inst, keya, keyp); } #if !defined(NDEBUG) static bool is_lock(uint8_t inst) { assert(inst < ARCH_INST_COUNT); return is_between(inst, loka, lokp); } #endif static bool is_rmod(uint8_t inst) { assert(inst < ARCH_INST_COUNT); return is_between(inst, nop0, nop3); } static bool key_lock_match(uint8_t key, uint8_t lock) { assert(key < ARCH_INST_COUNT); assert(lock < ARCH_INST_COUNT); assert(is_key(key)); return (key - keya) == (lock - loka); } static bool seek(struct Core *core, uint64_t pix, bool fwrd) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); if (proc->ip + 1 >= MVEC_SIZE) { increment_ip(core, pix); return false; } uint8_t next = mvec_get_inst(core, proc->ip + 1); if (!is_key(next)) { increment_ip(core, pix); return false; } if (proc->sp >= MVEC_SIZE) { // HALT: process SP address is invalid core->hlts++; return false; } uint8_t spin = mvec_get_inst(core, proc->sp); if (key_lock_match(next, spin)) { return true; } if (fwrd) { proc->sp++; } else { proc->sp--; } return false; } static void jump(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); #if !defined(NDEBUG) uint8_t next = mvec_get_inst(core, proc->ip + 1); uint8_t spin = mvec_get_inst(core, proc->sp); assert(is_key(next)); assert(is_lock(spin)); assert(key_lock_match(next, spin)); #endif proc->ip = proc->sp; } static void get_reg_addr_list(struct Core *core, uint64_t pix, uint64_t **rlist, int rcount, bool offset) { assert(core); assert(mvec_proc_is_live(core, pix)); assert(rlist); assert(rcount); assert(rcount < 4); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); uint64_t madr = proc->ip + (offset ? 2 : 1); for (int i = 0; i < rcount; i++) { rlist[i] = &proc->r0x; } for (int i = 0; i < rcount; i++) { uint64_t mnxt = madr + i; if (mnxt >= MVEC_SIZE) { break; } uint8_t mins = mvec_get_inst(core, mnxt); if (!is_rmod(mins)) { break; } switch (mins) { case nop0: rlist[i] = &proc->r0x; break; case nop1: rlist[i] = &proc->r1x; break; case nop2: rlist[i] = &proc->r2x; break; case nop3: rlist[i] = &proc->r3x; break; } } } static void address(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); uint64_t *reg; #if !defined(NDEBUG) uint8_t next = mvec_get_inst(core, proc->ip + 1); uint8_t spin = mvec_get_inst(core, proc->sp); assert(is_key(next)); assert(is_lock(spin)); assert(key_lock_match(next, spin)); #endif get_reg_addr_list(core, pix, ®, 1, true); *reg = proc->sp; increment_ip(core, pix); } static void free_memory_block(struct Core *core, uint64_t addr, uint64_t size) { assert(core); assert(size); for (uint64_t i = 0; i < size; i++) { mvec_free(core, addr + i); } } static void free_child_memory_of(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); assert(proc->mb1s); free_memory_block(core, proc->mb1a, proc->mb1s); proc->mb1a = 0; proc->mb1s = 0; } static void alloc(struct Core *core, uint64_t pix, bool fwrd) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); uint64_t *regs[2]; get_reg_addr_list(core, pix, regs, 2, false); uint64_t bsize = *regs[0]; // Do nothing if block-size is zero if (!bsize) { increment_ip(core, pix); return; } // Do nothing if seek pointer is not adjacent to allocated memory block if (proc->mb1s) { uint64_t exp_addr = proc->mb1a; if (fwrd) { exp_addr += proc->mb1s; } else { exp_addr--; } if (proc->sp != exp_addr) { increment_ip(core, pix); return; } } // HALT: process SP address is invalid // Discard allocated memory if any... if (proc->sp >= MVEC_SIZE) { if (proc->mb1s) { free_child_memory_of(core, pix); } core->hlts++; return; } // Allocation was successful, store block address on register if (proc->mb1s == bsize) { increment_ip(core, pix); *regs[1] = proc->mb1a; return; } // Seek pointer collided with another allocated block. // Discard and keep looking... if (mvec_is_alloc(core, proc->sp)) { if (proc->mb1s) { free_child_memory_of(core, pix); } if (fwrd) { proc->sp++; } else { proc->sp--; } return; } // Free (non-allocated) byte found, enlarge child block 1 byte mvec_alloc(core, proc->sp); if (!proc->mb1s || !fwrd) { proc->mb1a = proc->sp; } proc->mb1s++; // Advance seek pointer if (fwrd) { proc->sp++; } else { proc->sp--; } } static void if_not_zero(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); uint64_t *reg; get_reg_addr_list(core, pix, ®, 1, false); uint64_t jmod = ((proc->ip + 1 < MVEC_SIZE) && is_rmod(mvec_get_inst(core, proc->ip + 1))) ? 1 : 0; uint64_t rmod = *reg ? 1 : 2; proc->ip += jmod + rmod; proc->sp = proc->ip; } static void mem_block_swap(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); if (proc->mb1s) { uint64_t tmpa = proc->mb0a; uint64_t tmps = proc->mb0s; proc->mb0a = proc->mb1a; proc->mb0s = proc->mb1s; proc->mb1a = tmpa; proc->mb1s = tmps; // Memory block swap events mark all addresses within both blocks for (uint64_t i = 0; i < proc->mb0s; i++) { core->xeva.data[proc->mb0a + i]++; } for (uint64_t i = 0; i < proc->mb1s; i++) { core->xeva.data[proc->mb1a + i]++; } } increment_ip(core, pix); } static void mem_block_clear(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); if (proc->mb1s) { free_child_memory_of(core, pix); } increment_ip(core, pix); } static void mem_block_split(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); if (proc->mb1s) { struct Proc child = { 0 }; child.ip = proc->mb1a; child.sp = proc->mb1a; child.mb0a = proc->mb1a; child.mb0s = proc->mb1s; proc->mb1a = 0; proc->mb1s = 0; // A new organism is born :) proc_new(core, &child); } else { assert(!proc->mb1a); } increment_ip(core, pix); } static void three_regs_op(struct Core *core, uint64_t pix, uint8_t inst) { assert(core); assert(mvec_proc_is_live(core, pix)); uint64_t *regs[3]; get_reg_addr_list(core, pix, regs, 3, false); // Organisms can do arithmetic using any sequence of 3 registers switch (inst) { case addn: *regs[0] = *regs[1] + *regs[2]; break; case subn: *regs[0] = *regs[1] - *regs[2]; break; case muln: *regs[0] = *regs[1] * *regs[2]; break; case divn: if (*regs[2]) { *regs[0] = *regs[1] / *regs[2]; } else { // HALT: division by zero core->hlts++; return; } break; default: assert(false); } increment_ip(core, pix); } static void one_reg_op(struct Core *core, uint64_t pix, uint8_t inst) { assert(core); assert(mvec_proc_is_live(core, pix)); uint64_t *reg; get_reg_addr_list(core, pix, ®, 1, false); switch (inst) { case incn: (*reg)++; break; case decn: (*reg)--; break; case notn: *reg = !(*reg); break; case shfl: *reg <<= 1; break; case shfr: *reg >>= 1; break; case zero: *reg = 0; break; case unit: *reg = 1; break; default: assert(false); } increment_ip(core, pix); } static int get_sp_dir(uint64_t src, uint64_t dst) { if (src == dst) { return 0; } else if (src - dst <= dst - src) { return -1; } else { return 1; } } static void load_reg(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); uint64_t *regs[2]; get_reg_addr_list(core, pix, regs, 2, false); int sp_dir = get_sp_dir(proc->sp, *regs[0]); if (*regs[0] >= MVEC_SIZE) { // Target address is invalid increment_ip(core, pix); } else if (sp_dir == 1) { proc->sp++; } else if (sp_dir == -1) { proc->sp--; } else { *regs[1] = mvec_get_inst(core, *regs[0]); increment_ip(core, pix); } } static bool is_writeable_by(const struct Core *core, uint64_t addr, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); return addr < MVEC_SIZE && (!mvec_is_alloc(core, addr) || mvec_is_proc_owner(core, addr, pix)); } static void write_reg(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); uint64_t *regs[2]; get_reg_addr_list(core, pix, regs, 2, false); int sp_dir = get_sp_dir(proc->sp, *regs[0]); if (sp_dir == 1) { proc->sp++; } else if (sp_dir == -1) { proc->sp--; } else { if (is_writeable_by(core, *regs[0], pix)) { uint64_t addr = *regs[0]; uint8_t inst = *regs[1] & ARCH_INST_MASK; // Store write event core->iwrt[inst]++; core->weva.data[addr]++; if (mvec_is_in_mb0_of_proc(core, addr, pix)) { core->wmb0++; } else if (mvec_is_in_mb1_of_proc(core, addr, pix)) { core->wmb1++; } else { core->wdea++; } // Write instruction mvec_set_inst(core, addr, inst); } increment_ip(core, pix); } } static void two_reg_op(struct Core *core, uint64_t pix, uint8_t inst) { assert(core); assert(mvec_proc_is_live(core, pix)); uint64_t *regs[2]; get_reg_addr_list(core, pix, regs, 2, false); switch (inst) { case rdup: *regs[1] = *regs[0]; break; case rswp: { uint64_t tmp = *regs[0]; *regs[0] = *regs[1]; *regs[1] = tmp; } break; default: assert(false); } increment_ip(core, pix); } static void push_to_stack(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); uint64_t *reg; get_reg_addr_list(core, pix, ®, 1, false); proc->s7 = proc->s6; proc->s6 = proc->s5; proc->s5 = proc->s4; proc->s4 = proc->s3; proc->s3 = proc->s2; proc->s2 = proc->s1; proc->s1 = proc->s0; proc->s0 = *reg; increment_ip(core, pix); } static void pop_from_stack(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); assert(proc->ip < MVEC_SIZE); uint64_t *reg; get_reg_addr_list(core, pix, ®, 1, false); *reg = proc->s0; proc->s0 = proc->s1; proc->s1 = proc->s2; proc->s2 = proc->s3; proc->s3 = proc->s4; proc->s4 = proc->s5; proc->s5 = proc->s6; proc->s6 = proc->s7; proc->s7 = 0; increment_ip(core, pix); } // ---------------------------------------------------------------------------- // [section] callbacks // ---------------------------------------------------------------------------- void arch_on_proc_step(struct Core *core, uint64_t pix) { assert(core); assert(mvec_proc_is_live(core, pix)); struct Proc *proc = proc_fetch(core, pix); if (proc->ip >= MVEC_SIZE) { // HALT: process IP address is invalid core->hlts++; return; } // Fetch instruction uint8_t inst = mvec_get_inst(core, proc->ip); core->iexe[inst]++; // Execute instruction switch (inst) { case jmpb: if (seek(core, pix, false)) { jump(core, pix); } break; case jmpf: if (seek(core, pix, true)) { jump(core, pix); } break; case adrb: if (seek(core, pix, false)) { address(core, pix); } break; case adrf: if (seek(core, pix, true)) { address(core, pix); } break; case malb: alloc(core, pix, false); break; case malf: alloc(core, pix, true); break; case ifnz: if_not_zero(core, pix); break; case bswp: mem_block_swap(core, pix); break; case bclr: mem_block_clear(core, pix); break; case bspl: mem_block_split(core, pix); break; case addn: case subn: case muln: case divn: three_regs_op(core, pix, inst); break; case incn: case decn: case notn: case shfl: case shfr: case zero: case unit: one_reg_op(core, pix, inst); break; case rloa: load_reg(core, pix); break; case rwrt: write_reg(core, pix); break; case rdup: case rswp: two_reg_op(core, pix, inst); break; case rpsh: push_to_stack(core, pix); break; case rpop: pop_from_stack(core, pix); break; default: increment_ip(core, pix); break; } } void arch_on_proc_kill(struct Core *core) { assert(core); assert(core->pnum > 1); struct Proc *pfst = proc_fetch(core, core->pfst); free_memory_block(core, pfst->mb0a, pfst->mb0s); if (pfst->mb1s) { free_memory_block(core, pfst->mb1a, pfst->mb1s); } memcpy(pfst, &g_null_proc, sizeof(struct Proc)); } // ---------------------------------------------------------------------------- // [section] validation // ---------------------------------------------------------------------------- #if !defined(NDEBUG) void arch_validate_core(struct Core *core) { assert(core); for (uint64_t pix = core->pfst; pix <= core->plst; pix++) { const struct Proc *proc = proc_get(core, pix); assert(proc->mb0s); if (proc->mb1a) { assert(proc->mb1s); } for (uint64_t i = 0; i < proc->mb0s; i++) { uint64_t addr = proc->mb0a + i; assert(mvec_is_alloc(core, addr)); assert(mvec_is_proc_owner(core, addr, pix)); } for (uint64_t i = 0; i < proc->mb1s; i++) { uint64_t addr = proc->mb1a + i; assert(mvec_is_alloc(core, addr)); assert(mvec_is_proc_owner(core, addr, pix)); } } } #endif // ---------------------------------------------------------------------------- // [section] data // ---------------------------------------------------------------------------- void arch_push_data_header(void) { assert(g_sim_db); log_info("Creating arch table in SQLite database"); // Empty blob columns help the data-server stay agnostic of DB schema. // These represent EVA data that will be appended by data server. sql_exec( NULL, NULL, "create table arch (" #define ARCH_SPEC_INST(label, symbol, core) \ #label "_pop_" #core " int not null, " \ #label "_exe_" #core " int not null, " \ #label "_wrt_" #core " int not null, " #define FOR_CORE(i) \ ARCH_SPEC_INST_SET(i) \ "wmb0_" #i " int not null, " \ "wmb1_" #i " int not null, " \ "wdea_" #i " int not null, " \ "hlts_" #i " int not null, " \ "wev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i " int not null, wev_" #i " blob, " \ "xev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i " int not null, xev_" #i " blob, " FOR_CORES #undef FOR_CORE #undef ARCH_SPEC_INST "step int not null" ");" ); } static int arch_count_inst_pop(struct Core *core) { assert(core); for (uint64_t i = 0; i < MVEC_SIZE; i++) { core->ipop[mvec_get_inst(core, i)]++; } #if !defined(NDEBUG) uint64_t pop_total = 0; for (uint64_t i = 0; i < ARCH_INST_COUNT; i++) { pop_total += core->ipop[i]; } assert(pop_total == MVEC_SIZE); #endif return 0; } void arch_prepare_data_line(void) { assert(g_sim_db); // Measure instruction population and compress event-arrays in parallel for (int i = 0; i < CORES; i++) { struct Core *core = &g_cores[i]; thrd_create(&core->ipop_thrd, (thrd_start_t)arch_count_inst_pop, core); thrd_create(&core->weva.thrd, (thrd_start_t)comp_deflate, &core->weva.params); thrd_create(&core->xeva.thrd, (thrd_start_t)comp_deflate, &core->xeva.params); } for (int i = 0; i < CORES; i++) { struct Core *core = &g_cores[i]; thrd_join(core->ipop_thrd, NULL); thrd_join(core->weva.thrd, NULL); thrd_join(core->xeva.thrd, NULL); core->weva.blob_size = core->weva.params.strm.total_out; core->xeva.blob_size = core->xeva.params.strm.total_out; } } void arch_push_data_line(FILE *eva_file) { assert(g_sim_db); assert(eva_file); assert(g_step % DATA_PUSH_INTERVAL == 0); log_info("Pushing row to arch table in SQLite database"); sql_exec( NULL, NULL, "insert into arch (" #define ARCH_SPEC_INST(label, symbol, core) \ #label "_pop_" #core ", " \ #label "_exe_" #core ", " \ #label "_wrt_" #core ", " #define FOR_CORE(i) \ ARCH_SPEC_INST_SET(i) \ "wmb0_" #i ", " \ "wmb1_" #i ", " \ "wdea_" #i ", " \ "hlts_" #i ", " \ "wev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i ", " \ "xev" SALIS_EVENT_ARRAY_SIZE_COL_MARKER #i ", " FOR_CORES #undef FOR_CORE #undef ARCH_SPEC_INST "step" ") values (" #define ARCH_SPEC_INST(label, symbol, core) \ "%ld, " \ "%ld, " \ "%ld, " #define FOR_CORE(i) \ ARCH_SPEC_INST_SET(i) \ "%ld, " \ "%ld, " \ "%ld, " \ "%ld, " \ "%ld, " \ "%ld, " FOR_CORES #undef FOR_CORE #undef ARCH_SPEC_INST "%ld" ");", #define ARCH_SPEC_INST(label, symbol, core) \ g_cores[core].ipop[label], \ g_cores[core].iexe[label], \ g_cores[core].iwrt[label], #define FOR_CORE(i) \ ARCH_SPEC_INST_SET(i) \ g_cores[i].wmb0, \ g_cores[i].wmb1, \ g_cores[i].wdea, \ g_cores[i].hlts, \ g_cores[i].weva.blob_size, \ g_cores[i].xeva.blob_size, FOR_CORES #undef FOR_CORE #undef ARCH_SPEC_INST g_step ); for (int i = 0; i < CORES; i++) { struct Core *core = &g_cores[i]; fwrite(core->weva.comp, sizeof(Bytef), core->weva.blob_size, eva_file); fwrite(core->xeva.comp, sizeof(Bytef), core->xeva.blob_size, eva_file); comp_deflate_end(&core->weva.params); comp_deflate_end(&core->xeva.params); } // Reset data aggregation fields for (int i = 0; i < CORES; i++) { struct Core *core = &g_cores[i]; memset(core->ipop, 0, sizeof(uint64_t) * ARCH_INST_COUNT); memset(core->iexe, 0, sizeof(uint64_t) * ARCH_INST_COUNT); memset(core->iwrt, 0, sizeof(uint64_t) * ARCH_INST_COUNT); core->wmb0 = 0; core->wmb1 = 0; core->wdea = 0; core->hlts = 0; memset(core->weva.data, 0, sizeof(uint64_t) * MVEC_SIZE); memset(core->xeva.data, 0, sizeof(uint64_t) * MVEC_SIZE); } } // ---------------------------------------------------------------------------- // [section] main // ---------------------------------------------------------------------------- #if defined(COMMAND_NEW) void arch_core_init(struct Core *core) { assert(core); for (uint64_t i = 0; i < CLONES; i++) { uint64_t addr_clone = (MVEC_SIZE / CLONES) * i; struct Proc *panc = proc_fetch(core, i); panc->mb0a = addr_clone; panc->mb0s = ANC_SIZE; panc->ip = addr_clone; panc->sp = addr_clone; } core_event_array_init(&core->weva); core_event_array_init(&core->xeva); } #endif #if defined(COMMAND_LOAD) void arch_core_load(struct Core *core, FILE *f) { assert(core); assert(f); fread(core->ipop, sizeof(uint64_t), ARCH_INST_COUNT, f); fread(core->iexe, sizeof(uint64_t), ARCH_INST_COUNT, f); fread(core->iwrt, sizeof(uint64_t), ARCH_INST_COUNT, f); fread(&core->wmb0, sizeof(uint64_t), 1, f); fread(&core->wmb1, sizeof(uint64_t), 1, f); fread(&core->wdea, sizeof(uint64_t), 1, f); fread(core->weva.data, sizeof(uint64_t), MVEC_SIZE, f); fread(core->xeva.data, sizeof(uint64_t), MVEC_SIZE, f); core_event_array_init(&core->weva); core_event_array_init(&core->xeva); } #endif void arch_core_save(const struct Core *core, FILE *f) { assert(core); assert(f); fwrite(core->ipop, sizeof(uint64_t), ARCH_INST_COUNT, f); fwrite(core->iexe, sizeof(uint64_t), ARCH_INST_COUNT, f); fwrite(core->iwrt, sizeof(uint64_t), ARCH_INST_COUNT, f); fwrite(&core->wmb0, sizeof(uint64_t), 1, f); fwrite(&core->wmb1, sizeof(uint64_t), 1, f); fwrite(&core->wdea, sizeof(uint64_t), 1, f); fwrite(core->weva.data, sizeof(uint64_t), MVEC_SIZE, f); fwrite(core->xeva.data, sizeof(uint64_t), MVEC_SIZE, f); } void arch_core_free(struct Core *core) { assert(core); (void)core; }