#define _POSIX_C_SOURCE 200809L // #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)) 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 // 0: nop // 1: hlt // 2: mov [target], src // 3: add [target], src // 4: sub [target], src // 5: inc [target], ___ // 6: dec [target], ___ // 7: mul [target], src // 8: lsh [target], src // 9: rsh [target], src // 10: and [target], src // 11: not [target], src // 12: jmp [target], src // 13: jnz [target], src // 14: ??? // 15: ??? #define branchless(c, x, y) (((-(!!(c))) & (x)) | (~(-(!!(c))) & (y))) #define prep_raw() \ ({ \ rhs = (uint32_t) get_data_u16(cur); \ if (get_spare(cur) & 0x40) rhs = ctx.memory.memory_u32[ rhs ]; \ }) #define prep_vals() \ ({ \ spare = get_spare(cur); \ lhs = get_dest_u32(cur); \ rhs = (uint32_t) get_data_u16(cur); \ if (spare & 0x40) rhs = ctx.memory.memory_u32[ rhs ]; \ if (unlikely(spare & 0x80)) { \ out = lhs; \ lhs = rhs; \ rhs = out; \ } \ }) #define prep_only_left() ({ lhs = get_dest_u32(cur); }) pu3(pu3_ctx ctx) { uint64_t i = 0; pu3_inst_t cur; pu3_inst_t *pc = ctx.memory.memory_u64 + *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++; cur = *pc++; #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(); ctx.memory.memory_u32[ lhs ] = rhs; goto pu3_start; pu3_inst_3: // add prep_vals(); ctx.memory.memory_u32[ lhs ] += rhs; goto pu3_start; pu3_inst_4: // sub prep_vals(); ctx.memory.memory_u32[ lhs ] -= rhs; goto pu3_start; pu3_inst_5: // inc prep_only_left(); ctx.memory.memory_u32[ lhs ]++; goto pu3_start; pu3_inst_6: // dec prep_only_left(); ctx.memory.memory_u32[ lhs ]--; goto pu3_start; pu3_inst_7: // mul prep_vals(); ctx.memory.memory_u32[ lhs ] *= rhs; goto pu3_start; pu3_inst_8: // lsh prep_vals(); ctx.memory.memory_u32[ lhs ] <<= rhs; goto pu3_start; pu3_inst_9: // rsh prep_vals(); ctx.memory.memory_u32[ lhs ] >>= rhs; goto pu3_start; pu3_inst_10: // and prep_vals(); ctx.memory.memory_u32[ lhs ] &= rhs; goto pu3_start; pu3_inst_11: // not prep_vals(); ctx.memory.memory_u32[ lhs ] = !rhs; goto pu3_start; pu3_inst_12: // jmp prep_only_left(); pc = ctx.memory.memory_u64 + lhs; goto pu3_start; pu3_inst_13: // jnz prep_vals(); if (rhs) pc = ctx.memory.memory_u64 + lhs; goto pu3_start; pu3_inst_14: // unused pu3_inst_15: // unused pu3_inst_1: // hlt *ctx.pc = pc - ctx.memory.memory_u64; return *ctx.time_slice - i; } const string op_names[] = { "nop", "hlt", "mov", "add", "sub", "inc", "dec", "mul", "lsh", "rsh", "and", "not", "jmp", "jnz", "???", "???" }; // 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 ], 2); set_dest_u32(ctx.memory.memory_u64[ i ], 4); if (i == ctx.mem_size - 20) set_scroll_byte(ctx.memory.memory_u64[ i ], 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 } int main() { const uint32_t SIZE = 1 << 24; let mem_arr = calloc(SIZE, sizeof(pu3_inst_t)); pu3_ctx ctx = { .memory = { .memory_u64 = mem_arr, .memory_u32 = mem_arr }, .mem_size = SIZE, .pc = calloc(1, sizeof(uint32_t)), .time_slice = calloc(1, sizeof(uint32_t)) }; int i; int total = 0; double benchs = 0; for (i = 0; i < 12; i++) { if (i == 1) continue; total++; benchs += bench_cycle(i, ctx); } printf( "-------------------------------\n" "TOTAL: %fMHz\n", benchs / total); free(mem_arr); free(ctx.pc); free(ctx.time_slice); return 0; }