#define _GNU_SOURCE #define inline __attribute__((always_inline)) // #include #include #include #include #include #include #define auto __auto_type #define let auto // -- disable use of compiler intrinsics // #define PU3_NO_INTRINSICS // -- inaccurate clocks // #define PU3_BAD_CLOCK #ifndef PU3_BAD_CLOCK uint64_t ntime() { struct timespec t; clock_gettime(CLOCK_MONOTONIC_RAW, &t); return t.tv_nsec + t.tv_sec * 1e9; } #else uint64_t ntime() { struct timeval tv; gettimeofday(&tv, NULL); return tv.tv_sec * 1000000000 + tv.tv_usec * 1000; } #endif // it is time // the thing you've none been waiting for // pu3 typedef char *string; typedef unsigned char byte; typedef byte bool; typedef uint64_t pu3_inst_t; #ifndef PU3_NO_INTRINSICS #define unlikely(JJ) __builtin_expect((JJ), 0) #define likely(JJ) __builtin_expect((JJ), 1) #else #define unlikely(JJ) (JJ) #define likely(JJ) (JJ) #endif #define get_spare_nosh(__x) ((__x) & 0xFF00000000000000ULL) #define get_scroll_byte_nosh(__x) ((__x) & 0x00FF000000000000ULL) #define get_data_u16_nosh(__x) ((__x) & 0x0000FFFF00000000ULL) #define get_dest_u32_nosh(__x) ((__x) & 0x00000000FFFFFFFFULL) #define get_spare(__x) (get_spare_nosh(__x) >> 56) #define get_scroll_byte(__x) (get_scroll_byte_nosh(__x) >> 48) #define get_data_u16(__x) (get_data_u16_nosh(__x) >> 32) #define get_dest_u32(__x) (get_dest_u32_nosh(__x) >> 0) #define prep_spare(__x) (((__x) & 0x000000FFULL) << 56) #define prep_scroll_byte(__x) (((__x) & 0x000000FFULL) << 48) #define prep_data_u16(__x) (((__x) & 0x0000FFFFULL) << 32) #define prep_dest_u32(__x) (((__x) & 0xFFFFFFFFULL) << 00) #define set_spare(__v, __x) __v = (((__v) & 0x00FFFFFFFFFFFFFFULL) | prep_spare(__x)) #define set_scroll_byte(__v, __x) __v = (((__v) & 0xFF00FFFFFFFFFFFFULL) | prep_scroll_byte(__x)) #define set_data_u16(__v, __x) __v = (((__v) & 0xFFFF0000FFFFFFFFULL) | prep_data_u16(__x)) #define set_dest_u32(__v, __x) __v = (((__v) & 0xFFFFFFFF00000000ULL) | prep_dest_u32(__x)) // clang-format off struct { uint32_t nop, hlt, mov, add, sub, inc, dec, mul, lsh, rsh, and, not, jmp, jnz, dmv, ddm; } opcode = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; typedef uint32_t pu3_opcode; // clang-format on inline pu3_inst_t build_inst(bool swap, bool deref, pu3_opcode opcode, uint32_t lhs, uint16_t rhs) { pu3_inst_t output = 0; byte spare = ((swap & 1) << 7) | ((deref & 1) << 6); byte op = opcode & 0b1111; set_spare(output, spare); set_scroll_byte(output, op); set_data_u16(output, rhs); set_dest_u32(output, lhs); return output; } typedef struct { pu3_inst_t *memory_u64; uint32_t *memory_u32; } pu3_mem; typedef struct { pu3_mem memory; uint32_t mem_size; uint32_t *pc; uint32_t time_slice; } pu3_ctx; // instruct packing: // typedef struct { // byte __spare[ 1 ]; // byte op; // uint16_t data; // uint32_t dest; // } pu3_inst_t; // ------------------------------------------------------------------------------------------ // instructions list // 0: nop ________, ___ (( advances program counter by lhs )) // 1: hlt ________, ___ (( stops the cpu )) // 2: mov [target], src (( moves rhs to lhs )) // 3: add [target], src (( adds rhs to lhs )) // 4: sub [target], src (( subtracts rhs from lhs )) // 5: inc [target], ___ (( increments lhs )) // 6: dec [target], ___ (( decrements lhs )) // 7: mul [target], src (( multiplies lhs by rhs )) // 8: lsh [target], src (( left shifts lhs by rhs )) // 9: rsh [target], src (( right shifts lhs by rhs )) // 10: and [target], src (( logical ANDs lhs with rhs )) // 11: not [target], src (( stores !*lhs in lhs )) // 12: jmp [target], src (( jumps to lhs )) // 13: jnz [target], src (( jumps to lhs if rhs is non-zero )) // 14: dmv [target], ___ (( sets lhs to *lhs )) // 15: ddm [target], src (( set m[m[lhs]] to m[rhs] )) // ------------------------------------------------------------------------------------------ // explanation // we see lhs and rhs. these are, in order, the u32 dest and the u16 data. // the following are executed, in order, in each cycle (if required): // 1. lhs and rhs are swapped (to allow access to all of the u32 memory range)- // -if the leftmost bit of spare is set. (spare & 0b10000000 != 0) // 2. lhs is converted to memory[ lhs ] unconditionally // 3. rhs is converted to memory[ rhs ] only if the second leftmmost bit of spare is set. // (spare & 0b01000000 != 0) // ------------------------------------------------------------------------------------------ // WARNING: the fields left blank in the instruction list are NOT PROCESSED, // this means they're ignored even if there's a dereference or a swap. // ------------------------------------------------------------------------------------------ #define prep_raw() \ ({ \ rhs = (uint32_t) get_data_u16(cur); \ if (get_spare(cur) & 0x40) rhs = mem32[ rhs ]; \ }) #define prep_vals() \ ({ \ spare = get_spare(cur); \ if (unlikely(spare & 0x80)) { \ rhs = get_dest_u32(cur); \ lhs = (uint32_t) get_data_u16(cur); \ } else { \ lhs = get_dest_u32(cur); \ rhs = (uint32_t) get_data_u16(cur); \ } \ if (spare & 0x40) rhs = mem32[ rhs ]; \ }) #define prep_only_left() ({ lhs = get_dest_u32(cur); }) // spare = lhs; // spare = *spare; // void disassemble_pu3_inst(pu3_inst_t inst); pu3(pu3_ctx ctx) { uint64_t i = 0; pu3_inst_t cur; uint32_t *mem32 = ctx.memory.memory_u32; pu3_inst_t *mem64 = ctx.memory.memory_u64; pu3_inst_t *pc = mem64 + *ctx.pc; byte op; byte spare; // uint32_t lhs; // uint32_t rhs; uint32_t lhs; uint32_t rhs; uint32_t out; const __attribute__((aligned(64))) void *goto_table[] = { &&pu3_inst_0, &&pu3_inst_1, &&pu3_inst_2, &&pu3_inst_3, &&pu3_inst_4, &&pu3_inst_5, &&pu3_inst_6, &&pu3_inst_7, &&pu3_inst_8, &&pu3_inst_9, &&pu3_inst_10, &&pu3_inst_11, &&pu3_inst_12, &&pu3_inst_13, &&pu3_inst_14, &&pu3_inst_15 }; pu3_start: i++; if (unlikely(ctx.time_slice == i)) goto pu3_inst_1; cur = *pc++; // printf("%li: ", pc - mem64 - 1); // disassemble_pu3_inst(cur); #ifndef PU3_NO_INTRINSICS __builtin_prefetch(pc + 1, 0, 3); #endif op = get_scroll_byte(cur); goto *goto_table[ op & 0b1111 ]; pu3_inst_0: // nop prep_raw(); pc += rhs; goto pu3_start; pu3_inst_2: // mov prep_vals(); mem32[ lhs ] = rhs; goto pu3_start; pu3_inst_3: // add prep_vals(); mem32[ lhs ] += rhs; goto pu3_start; pu3_inst_4: // sub prep_vals(); mem32[ lhs ] -= rhs; goto pu3_start; pu3_inst_5: // inc prep_only_left(); mem32[ lhs ]++; goto pu3_start; pu3_inst_6: // dec prep_only_left(); mem32[ lhs ]--; goto pu3_start; pu3_inst_7: // mul prep_vals(); mem32[ lhs ] *= rhs; goto pu3_start; pu3_inst_8: // lsh prep_vals(); mem32[ lhs ] <<= rhs; goto pu3_start; pu3_inst_9: // rsh prep_vals(); mem32[ lhs ] >>= rhs; goto pu3_start; pu3_inst_10: // and prep_vals(); mem32[ lhs ] &= rhs; goto pu3_start; pu3_inst_11: // not prep_only_left(); mem32[ lhs ] = !mem32[ lhs ]; goto pu3_start; pu3_inst_12: // jmp prep_only_left(); pc = mem64 + lhs; goto pu3_start; pu3_inst_13: // jnz prep_vals(); if (rhs) pc = mem64 + lhs; goto pu3_start; pu3_inst_14: // dmv prep_vals(); mem32[ lhs ] = mem32[ mem32[ lhs ] ]; goto pu3_start; pu3_inst_15: // ddm prep_vals(); mem32[ mem32[ lhs ] ] = mem32[ rhs ]; goto pu3_start; pu3_inst_1: // hlt *ctx.pc = pc - mem64; return ctx.time_slice - i; } const string op_names[] = { "nop", "hlt", "mov", "add", "sub", "inc", "dec", "mul", "lsh", "rsh", "and", "not", "jmp", "jnz", "dmv", "ddm" }; // bench: measure using clock() double bench_cycle(byte instr, pu3_ctx ctx) { int i; for (i = 16; i < ctx.mem_size - 16; i++) { set_scroll_byte(ctx.memory.memory_u64[ i ], instr & 0b1111); set_data_u16(ctx.memory.memory_u64[ i ], 4); set_dest_u32(ctx.memory.memory_u64[ i ], i); if (i == ctx.mem_size - 20) { let j = 0; for (j = 0; j < 20; j++) { set_scroll_byte(ctx.memory.memory_u64[ ctx.mem_size - j ], 1); } } } set_scroll_byte(ctx.memory.memory_u64[ i ], 0); *ctx.pc = 8; uint64_t start = ntime(); pu3(ctx); uint64_t end = ntime(); double nanoseconds = (double) (end - start); double inst_count = *ctx.pc - 8; double insts_per_ns = inst_count / nanoseconds; double insts_per_s = insts_per_ns * 1000000000ULL; printf( "%-8s | time: %.3fms | insts: %.0f (%llu real) | ips: %.3fM inst/s\n", op_names[ instr ], nanoseconds / 1000000, inst_count, (unsigned long long) (end - start), insts_per_ns * 1e3); return insts_per_s / 1e6; // return M inst/s } #define S 1 << 23 pu3_ctx *init_pu3() { const uint32_t SIZE = S; let mem_arr = calloc(SIZE, sizeof(pu3_inst_t)); if (!mem_arr) { perror("memory allocation failed"); return NULL; } pu3_ctx *ctx = calloc(1, sizeof(pu3_ctx)); *ctx = (pu3_ctx) { .memory = { .memory_u64 = mem_arr, .memory_u32 = mem_arr }, .mem_size = SIZE, .pc = calloc(1, sizeof(uint32_t)), .time_slice = 0 }; return ctx; } pu3_benchmark() { pu3_ctx *__ctx = init_pu3(); pu3_ctx ctx = *__ctx; int i; int total = 0; double benchs = 0; for (i = 0; i < 16; i++) { if (i == 1 || i == 12 || i == 13) continue; total++; benchs += bench_cycle(i, ctx); } printf( "-------------------------------\n" "TOTAL: %fMHz\n", benchs / total); free(ctx.memory.memory_u32); free(ctx.pc); free(__ctx); return 0; }