diff options
| author | 2025-08-19 12:15:10 +0330 | |
|---|---|---|
| committer | 2025-08-19 12:15:10 +0330 | |
| commit | de25a0291820dcb953dfdc3f13241bb3cb56e56b (patch) | |
| tree | dca89e2bfc06b0da08a63f2aa68920558c07c43d /pu2 | |
| parent | remove ppu from readme (diff) | |
improved pu2 (vm speed ~1GHz on 1.8GHz cpu), reduced code emission, added another compile flag
Diffstat (limited to 'pu2')
| -rw-r--r-- | pu2/pu2.c | 247 | ||||
| -rw-r--r-- | pu2/pu2.out.c | 149 |
2 files changed, 292 insertions, 104 deletions
@@ -1,7 +1,13 @@ #define _POSIX_C_SOURCE 200809L #include "pu2.out.c" +// +#include <errno.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/time.h> #include <time.h> +#include <unistd.h> typedef char *string; @@ -87,7 +93,7 @@ long powi(long x, unsigned n) { p *= p, n /= 2; } - return r; + return p == 0 ? 0 : r; } emu() { @@ -163,9 +169,9 @@ skip_space(string *I) { // IR parser for pu2's as2 // clang-format off struct { - int hlt, imm, lod, str, swp, add, sub, and, not, acc, a2u, u2a, b2u, u2b; + int hlt, imm, lod, str, swp, add, sub, and, not, acc, a2u, u2a, b2u, u2b, nop, cb1, cb2, cb3, cb4; } opcode = { - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 }; // clang-format on @@ -194,16 +200,6 @@ clean_slice_ref(slice *val) { free(val->ref); } #define static_len(G) (sizeof(G) / sizeof((G)[ 0 ])) -#define emit(OP, VAL) \ - ({ \ - *Code++ = (OP); \ - *Code++ = (VAL); \ - }) -#define store() \ - ({ \ - emit(opcode.acc, 1); \ - emit(opcode.str, 0); \ - }) #define run_err(result, ...) \ ({ \ ERR_code = init_I; \ @@ -262,34 +258,15 @@ clean_slice_ref(slice *val) { free(val->ref); } value; \ }) -#define op(mode) emit(opcode.mode, 0) -#define swap() emit(opcode.swp, 0) -#define imm(value) emit(opcode.imm, value) -#define load() emit(opcode.lod, 0) -#define imm_in_mem(__mem, __imm) \ - ({ \ - imm(__imm); \ - swap(); \ - imm(__mem); \ - store(); \ - }) -#define mov_mem_mem(__mem1, __mem2) \ - ({ \ - imm(__mem2); \ - swap(); \ - load(); \ - swap(); \ - imm(__mem1); \ - store(); \ - }) -#define mem_in_a(__mem) \ - ({ \ - op(b2u); \ - imm(__mem); \ - swap(); \ - load(); \ - op(u2b); \ - }) +// #define emit(OP, VAL) (*Code++ = (OP), *Code++ = (VAL)) +#define store() (emit(opcode.acc, 1), emit(opcode.str, 0)) +#define op(mode) emit(opcode.mode, 0) +#define swap() emit(opcode.swp, 0) +#define imm(value) emit(opcode.imm, value) +#define load() emit(opcode.lod, 0) +#define imm_in_mem(__mem, __imm) (imm(__mem), emit(opcode.cb3, __imm)) +#define mov_mem_mem(__mem1, __mem2) (imm(__mem1), emit(opcode.cb4, __mem2)) +#define mem_in_a(__mem) emit(opcode.cb2, __mem) #define perform_op(operation) \ ({ \ mem_in_a(mem2_index); \ @@ -306,6 +283,13 @@ string ERR; string ERR_code; int *Code = &m[ 4000 ]; int *Data = &m[ static_len(m) - 1 ]; // grows downwards + +__attribute__((always_inline)) emit(int op, int val) { + *Code++ = op; + *Code++ = val; +} + +bool has_jump_warned = false; _ir2(string I, macro macros[ 1 << 9 ]) { string init_I = I; ERR = "all good"; @@ -328,6 +312,12 @@ _ir2(string I, macro macros[ 1 << 9 ]) { checkskip('=', "expected assigment after memory blob (hint: only +-&! are supported)"); skip_space(&I); + if (mem_index == 7 && !has_jump_warned) { + has_jump_warned = true; + printf("\nwarn: writing to memory index 7 is equivalent to a jump\n"); + printf(" if this behavior is intended, use the jump blob instead.\n\n"); + } + if (*I == '@') { I++; if (!isdigit(*I)) { run_err(I, "expected a digit after @"); } @@ -360,6 +350,8 @@ _ir2(string I, macro macros[ 1 << 9 ]) { swap(); mem_in_a(mem_index); emit(opcode.add, 0); + swap(); + imm(mem_index); store(); } else if (extra_op == '-') { imm(imm); @@ -633,12 +625,12 @@ _ir2(string I, macro macros[ 1 << 9 ]) { } ir2(string I) { - macro macros[ 1 << 9 ] = {0}; + macro macros[ 1 << 9 ] = { 0 }; _ir2(I, macros); } -string op_names[] = { "hlt", "imm", "lod", "str", "swp", "add", "sub", - "and", "not", "acc", "a2u", "u2a", "b2u", "u2b" }; +string op_names[] = { "hlt", "imm", "lod", "str", "swp", "add", "sub", "and", "not", "acc", + "a2u", "u2a", "b2u", "u2b", "nop", "cb1", "cb2", "cb3", "cb4" }; unsigned long long rdtsc(void) { unsigned hi, lo; @@ -646,6 +638,14 @@ unsigned long long rdtsc(void) { return ((unsigned long long) hi << 32) | lo; } +clock_t ntime() { + struct timespec t; + clock_gettime(CLOCK_MONOTONIC_RAW, &t); + + return t.tv_nsec + t.tv_sec * 1e9; +} + +// bench: measure using clock() double bench_cycle(int instr) { int i; for (i = 0; i < S - 2; i += 2) { @@ -658,15 +658,29 @@ double bench_cycle(int instr) { m[ 7 ] = 0; c = 0; - clock_t start = clock(); + clock_t start = ntime(); + size_t wstart = rdtsc(); pu2(); - clock_t end = clock(); - - double seconds = (double) (end - start) / CLOCKS_PER_SEC; - printf("time: %.6f s\n", seconds); - return (double) m[ 7 ] / seconds / 1e6; + size_t wend = rdtsc(); + clock_t end = ntime(); + + double nanoseconds = (end - start); + double inst_count = (double) m[ 7 ] / 2; + double insts_per_ns = inst_count / nanoseconds; + double insts_per_s = insts_per_ns * 1e9; + + printf( + "%-8s | time: %.3fms | insts: %.0f (%li real) | ips: %.3fM inst/s\n", + op_names[ instr ], + nanoseconds / 1000000, + inst_count, + end - start, + insts_per_ns * 1e3); + + return insts_per_s / 1e6; // return M inst/s } +// bench2: measure using rdtsc double bench2_cycle(int instr) { unsigned long long start, end; double vminst_count, cpu_cycles; @@ -681,58 +695,116 @@ double bench2_cycle(int instr) { c = 0; start = rdtsc(); pu2(); - end = rdtsc(); - cpu_cycles = (double) (end - start); - vminst_count = (double) m[ 7 ]; - printf("pu2/real: %f\n", cpu_cycles / vminst_count); - return cpu_cycles / vminst_count; + end = rdtsc(); + cpu_cycles = (double) (end - start); + vminst_count = (double) m[ 7 ] / 2; + double cycles_per_inst = cpu_cycles / vminst_count; + + printf( + "%-6s | cycles/inst: %8.3f | cycles: %14.0f | insts: %12.0f\n", + op_names[ instr ], + cycles_per_inst, + cpu_cycles, + vminst_count); + return cycles_per_inst; +} + +sleep_nano(int nanoseconds) { + struct timespec req = { 0 }; + req.tv_sec = nanoseconds / 1000000000L; // Whole seconds + req.tv_nsec = nanoseconds % 1000000000L; // Remaining nanoseconds + + // nanosleep might be interrupted, so loop until it completes + while (nanosleep(&req, &req) == -1 && errno == EINTR) { + continue; // Continue with remaining time if interrupted + } } -benchmark(double (*target)(int)) { - double results[ 13 ]; +[[clang::always_inline]] sleep_micro(int microseconds) { sleep_nano(microseconds * 1e3); } +[[clang::always_inline]] sleep_milli(int milliseconds) { sleep_nano(milliseconds * 1e6); } + +// benchmark: run target for all opcodes and print summary +benchmark(double (*target)(int), const char *mode_label) { + double results[ 19 ]; double sum = 0; int count = 0; int i; - for (i = 1; i < 14; i++) { - printf("running: %s\n", op_names[ i ]); + + printf("\n=== pu2 benchmark (%s) ===\n", mode_label); + printf("op | result details\n"); + printf("---------------------------------------------\n"); + + for (i = 1; i < 19; i++) { + // printf("running: %s\n", op_names[ i ]); results[ i ] = target(i); sum += results[ i ]; count++; } sum /= count; - unsigned long long start, end; - start = rdtsc(); - sleep(1); - end = rdtsc(); - - float clock_speed = (end - start) / 1e6; - printf("results sum: %f\n", sum); - if (target == bench_cycle) { - printf("clock speed ~ %.6f MHz\n", clock_speed); - printf("instructions per cycle: %f\n", clock_speed / sum); + printf("---------------------------------------------\n"); + + size_t start = rdtsc(); + sleep_milli(100); + size_t end = rdtsc(); + + double clock_speed_mhz = ((end - start) / 1e6) * 10; + + if (strcmp(mode_label, "time") == 0) { + // printf("average : %.3f M inst/s\n", sum); + printf("cpu clock : ~%.3f MHz (measured via rdtsc & msleep(100))\n", clock_speed_mhz); + printf("vm clock (i/s) : %.3fMHz (ipn, higher is better)\n", sum); + // printf("estimated : %.3f cycles per instruction (cpi, lower is better)\n", + // clock_speed_mhz / sum); + } else { + // mode_label == "rdtsc" + printf("average cycles/inst : %.3f cycles\n", sum); + // printf("cpu clock : ~%.3f MHz (measured via rdtsc & msleep(100))\n", + // clock_speed_mhz); + printf("estimated ipc : %.3f (1 / cycles_per_inst, higher is better)\n", 1.0 / sum); + // printf("estimated cpi : %.3f (cycles_per_inst, lower is better)\n", sum); } + printf("=== end benchmark ===\n\n"); } -long nstime() { +nstime() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_nsec; } +volatile long out; + main(int argc, char **argv) { if (argc == 1) { return printf( "\nusage:\n" " %s <file_name> open and compile ir2-compliant code\n" - " %s --bench benchmark pu2\n\n", + " %s benchmark benchmark pu2 (two modes: time and rdtsc)\n\n", argv[ 0 ], argv[ 0 ]); } - if (strcmp(argv[ 1 ], "--bench") == 0) { - benchmark(bench_cycle); - benchmark(bench2_cycle); + if (strcmp(argv[ 1 ], "benchmark") == 0) { + benchmark(bench_cycle, "time"); + + const long N = S / 2; // printably the same insts you report + long sum = 0; + unsigned aux; + unsigned long long t0 = rdtsc(); + int C; + for (C = 0; C < N; ++C) { + sum += 1; + __asm__ volatile(""); + } + unsigned long long t1 = rdtsc(); + out = sum; // single volatile write to make sum observable + printf( + "N=%llu cycles=%llu cycles/iter=%.6f\n", + (unsigned long long) N, + (unsigned long long) (t1 - t0), + (t1 - t0) / (double) N); + // benchmark(bench2_cycle, "rdtsc"); exit(0); } @@ -745,35 +817,16 @@ main(int argc, char **argv) { } size_t o_size; - long before = nstime(); - long cycles_bef = rdtsc(); + long before = nstime(); + long cycles_bef = rdtsc(); ir2(contents); long cycles_aft = rdtsc(); - long after = nstime(); - m[ 7 ] = 4000; + long after = nstime(); + m[ 7 ] = 4000; - // fprintf(stderr, "assembly took %li nanoseconds (%li cycles)\n", (after - before), (cycles_aft - cycles_bef)); free(contents); size_t res_size = Code - &m[ 4000 ]; - // --- BYTES PRINTING --- - - // for(i = 0; i < res_size; i++) { - // printf("%X%X ", m[4000+i]>>4, m[4000+i]&0b1111); - // if(i % 8 == 7) printf("\n"); - // } - // fflush(stdout); - // printf("\n\n"); - - // --- OPCODES PRINTING --- - - // for (i = 0; i < res_size; i++) { - // int op = m[ 4000 + i++ ]; - // int data = m[ 4000 + i ]; - - // printf("%s(%i);\n", op_names[ op ], data); - // } - // printf("\n\n"); emu(); } diff --git a/pu2/pu2.out.c b/pu2/pu2.out.c index d6b2a5f..d168321 100644 --- a/pu2/pu2.out.c +++ b/pu2/pu2.out.c @@ -7,7 +7,6 @@ // pu2! // maybe compacter? idk -// last cpu: 167 chars #define A 8 // change this for error reporting on unknown instructions! @@ -17,15 +16,151 @@ // #define A my_custom_cpu_error_trap() // ^ calls my_custom_cpu_error_trap() -// clang-format off -#define S 1<<28 +#define __PU2_SPEED_IMPROVEMENT +#define __PU2_EXTENDED_INSTRUCTION_SET + +#define S (1 << 25) #define J while #define H !--c -m[S],c,v,a,b,u;pu2(){J(c=m[m[7]++],v=m[m[7]++],c){H?a=v:H?a=m[b]:H?u?m[a]=b:0:H?v=a,a=b,b=v:H?a+=b:H?a-=b:H?a&=b:H?a=!a:H?u=v:H?u=a:H?a=u:H?u=b:H?b=u:A;}} -// clang-format on + +#ifndef __PU2_SPEED_IMPROVEMENT + + #ifndef __PU2_EXTENDED_INSTRUCTION_SET + +// clang-format off + m[S],c,v,a,b,u;pu2(){J(c=m[m[7]++],v=m[m[7]++],c){H?a=v:H?a=m[b]:H?u?m[a]=b:0:H?v=a,a=b,b=v:H?a+=b:H?a-=b:H?a&=b:H?a=!a:H?u=v:H?u=a:H?a=u:H?u=b:H?b=u:A;}} + // clang-format on + + #else + +// clang-format off + m[S],c,v,a,b,u;pu2(){J(c=m[m[7]++],v=m[m[7]++],c){H?a=v:H?a=m[b]:H?u?m[a]=b:0:H?v=a,a=b,b=v:H?a+=b:H?a-=b:H?a&=b:H?a=!a:H?u=v:H?u=a:H?a=u:H?u=b:H?b=u:H?m[0]+=v*2:H?m[v]=m[a]:H?a=m[v]:H?m[a]=v:H?m[a]=m[v]:A;}} + // clang-format on + + #endif + +#else + + #include <stdio.h> + + #undef A + #define A \ + ({ \ + printf("ERROR: Invalid instruction!\n"); \ + exit(1); \ + }) + +// pu2 optimized for speed implements a few new instructions for speed, +// and has a "bake" mode which will preprocess the memory and perform the required modifications. +// +// 14: nop +// 15: m[imm] = m[a] +// 16: a = m[imm] +// 17: m[a] = imm +// 18: m[a] = m[imm] + +// not implemented yet, was lazy +// bake(int start, int end) {} + +__attribute__((aligned(64))) m[ S ], a, b, u, c; +typedef struct { + int c; + int v; +} vminst; +pu2() { + vminst *M = (vminst *) m; + + int s1, s2, s3; + + const __attribute__((aligned(64))) void *PRECOMPUTED_TABLE[] + = { &&C_ZERO, &&C_ONE, &&C_TWO, &&C_THREE, &&C_FOUR, + &&C_FIVE, &&C_SIX, &&C_SEVEN, &&C_EIGHT, &&C_NINE, + &&C_TEN, &&C_ELEVEN, &&C_TWELVE, &&C_THIRTEEN, &&CB_FOURTEEN, + &&CB_FIFTEEN, &&CB_SIXTEEN, &&CB_SEVENTEEN, &&CB_EIGHTEEN }; + int pc = m[ 7 ] >> 1; + register vminst inst; + J(inst = M[ pc++ ], inst.c) { + if (__builtin_expect(inst.c > 18, 0)) A; + + goto *PRECOMPUTED_TABLE[ inst.c ]; + + C_ZERO: + break; + C_ONE: + a = inst.v; + continue; + C_TWO: + a = m[ b ]; + continue; + C_THREE: + if (u) { + if (__builtin_expect(a == 7, 0)) pc = b >> 1; + m[ a ] = b; + } + + continue; + C_FOUR: + s1 = a; + a = b; + b = s1; + + continue; + C_FIVE: + a += b; + continue; + C_SIX: + a -= b; + continue; + C_SEVEN: + a = !a; + continue; + C_EIGHT: + u = inst.v; + continue; + C_NINE: + u = inst.v; + continue; + C_TEN: + u = a; + continue; + C_ELEVEN: + a = u; + continue; + C_TWELVE: + u = b; + continue; + C_THIRTEEN: + b = u; + continue; + CB_FOURTEEN: // nop + // this instruction allows for essentially embedding data inside a program, as pu2 will skip + // right over it + pc += inst.v; + continue; + CB_FIFTEEN: + if (__builtin_expect(inst.v == 7, 0)) pc = m[ a ] >> 1; + m[ inst.v ] = m[ a ]; + continue; + CB_SIXTEEN: + a = m[ inst.v ]; + continue; + CB_SEVENTEEN: + if (__builtin_expect(a == 7, 0)) pc = inst.v >> 1; + m[ a ] = inst.v; + continue; + CB_EIGHTEEN: + if (__builtin_expect(a == 7, 0)) pc = m[ inst.v ] >> 1; + m[ a ] = m[ inst.v ]; + continue; + } + + m[ 7 ] = pc << 1; +} + +#endif // there's just memory. no, really -// ok actually there's two registers called a and b +// ok actually there's c_one registers called a and b // 0 HLT: halt // 1 IMM: a = imm // 2 LOD: a = m[b] @@ -56,7 +191,7 @@ m[S],c,v,a,b,u;pu2(){J(c=m[m[7]++],v=m[m[7]++],c){H?a=v:H?a=m[b]:H?u?m[a]=b:0:H? // therefore, i recommend you store a copy somewhere! // clang-format off -char*C="hltimmlodstrswpaddsubandnotacca2uu2ab2uu2b",*Y[1<<10],*E,*L,i;*K;as2(char*I){J(c=*I++){if(c==35){J(*++I-10);c=*I++;}L=C;(!isspace(c)?c-39?c-125?c-123?c-64?({i=0,I--;J(i++,*L)strncmp(L,I,3)?L+=3:({I+=3;*K++=i-1;*K++=(i-2&&i-9)?0:strtol(I,&I,10);goto Q;});E=I;return 1;Q:;}):as2(Y[strtol(I,&I,10)]):({Y[strtol(I,&I,10)]=I+1;J(*I++-125);}):({break;}):({*K++=1;*K++=*I++;}):0);}} +char*C="hltimmlodstrswpaddsubandnotacca2uu2ab2uu2bnopcb1cb2cb3",*Y[1<<10],*E,*L,i;*K;as2(char*I){J(c=*I++){if(c==35){J(*++I-10);c=*I++;}L=C;(!isspace(c)?c-39?c-125?c-123?c-64?({i=0,I--;J(i++,*L)strncmp(L,I,3)?L+=3:({I+=3;*K++=i-1;*K++=(i-2&&i-9)?0:strtol(I,&I,10);goto Q;});E=I;return 1;Q:;}):as2(Y[strtol(I,&I,10)]):({Y[strtol(I,&I,10)]=I+1;J(*I++-125);}):({break;}):({*K++=1;*K++=*I++;}):0);}} // clang-format on // #undef S |
