fixed instruction length issue
[dcpu16] / dcpu16.c
index cfa6d8a2b02e2faa3f11e144db8528b0338a8d5b..b21f0f9cda33298895f365c982ad7b09c1125871 100644 (file)
--- a/dcpu16.c
+++ b/dcpu16.c
@@ -21,6 +21,7 @@
  *    2012 04 06 - first functionality achieved
  *    2012 04 09 - minor cleanups
  *    2012 04 10 - moved cli to separate module
+ *    2012 04 12 - added basic callback support for address accesses
  *
  *  TODO
  *    drop checks for assigning to literals -- it won't affect anything anyhow
@@ -80,17 +81,66 @@ void dcpu16_trace_cb_set(void (*fn)(char *fmt, ...)) {
     trace_cb_ = fn;
 }
 
+
+/*  dcpu16_acct_add
+ *  Register callback fn to be triggered whenever event matching exactly mask_ev
+ *  and additionally matching any of mask events occur.
+ */
+int dcpu16_acct_add(struct dcpu16 *vm, dcpu16_acct_event_ match_all, dcpu16_acct_event_ match_any, void (*fn)(dcpu16_acct_event_, DCPU16_WORD)) {
+    struct dcpu16_acct_cb cb;
+
+    cb.match_all = match_all;
+    cb.match_any = match_any;
+    cb.fn = fn;
+
+    /* add to vm->cb_table_, vm->cb_table_entries_, vm->cb_table_allocated_ */
+    if (vm->cb_table_entries_ == vm->cb_table_allocated_) {
+        size_t new_entries = vm->cb_table_allocated_ + 32;
+        void *tmp_ptr = realloc(vm->cb_table_, new_entries * sizeof *(vm->cb_table_));
+        if (tmp_ptr == NULL) {
+            fprintf(stderr, "%s():%s", "realloc", strerror(errno));
+            return -1;
+        }
+        vm->cb_table_ = tmp_ptr;
+        vm->cb_table_allocated_ += 32;
+    }
+
+    memcpy(vm->cb_table_ + vm->cb_table_entries_, &cb, sizeof cb);
+    vm->cb_table_entries_++;
+
+    return 0;
+}
+
+/*  acct_event_
+ *  invokes callbacks for specified event
+ */
+static inline
+void acct_event_(struct dcpu16 *vm, dcpu16_acct_event_ ev, DCPU16_WORD addr) {
+    struct dcpu16_acct_cb *cb = vm->cb_table_;
+    size_t i;
+
+    for (i = 0; i < vm->cb_table_entries_; i++) {
+        if ( (cb[i].match_all & ev) == cb[i].match_all    /* exact match on event flags */
+        &&   (cb[i].match_any & ev) ) {                /* any match on rest */
+            cb[i].fn(ev, addr);
+        }
+    }
+}
+
 /*  value_decode_
  * sets *v to be the destination of the value
- * workv is buffer to use to accumulate literal value before use
+ * workv is buffer to use to accumulate literal value before use, one exists for either potential instruction operand
  * returns true if destination points to literal (id est *v should ignore writes)
  */
-static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WORD **v) {
+static
+unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WORD **v, dcpu16_acct_event_ *e, WORD *e_addr) {
     WORD nextword;
     unsigned int retval = 0;
 
     assert(value <= 0x3f);
 
+    *e = 0;
+
     /* does this value indicate a literal */
     if (value >= 0x1f)
         retval = 1;
@@ -98,7 +148,7 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
     /* if we're skipping this instruction, just advance the pc if needed */ 
     if (d->skip_) {
         TRACE(">>      SKIP decode");
-        if (value == 0x1e || value == 0x1f)
+        if ((value >= 0x10 && value <= 0x17) || value == 0x1e || value == 0x1f)
             d->pc++;
         return retval;
     }
@@ -115,6 +165,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
               regnames_[value&0x07],
               d->reg[value&0x07],
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = d->reg[(value & 0x07)];
 
     } else if (value <= 0x17) { /* [next word + register] */
         nextword = d->ram[ d->pc++ ];
@@ -125,6 +177,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
               nextword,
               d->reg[(value & 0x07)],
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = nextword + d->reg[(value & 0x07)];
 
     } else switch (value) {
         case 0x18: /* POP / [sp++] */
@@ -132,6 +186,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     POP [0x%04x] (0x%04x)",
               d->sp - 1,
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = d->sp - 1;
         break;
 
         case 0x19: /* PEEK / [sp] */
@@ -139,6 +195,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     PEEK [0x%04x] (0x%04x)",
               d->sp,
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = d->sp;
         break;
 
         case 0x1a: /* PUSH / [--sp] */
@@ -146,6 +204,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     PUSH [0x%04x] (0x%04x)",
               d->sp + 1,
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = d->sp + 1;
         break;
 
         case 0x1b: /* SP */
@@ -171,6 +231,8 @@ static unsigned int value_decode(struct dcpu16 *d, WORD value, WORD *work_v, WOR
         TRACE(">>     [nextword] [0x%04x] (0x%04x)",
               nextword,
               **v);
+        *e |= DCPU16_ACCT_RAM;
+        *e_addr = nextword;
         break;
 
         case 0x1f: /* next word (literal) / [pc++] */
@@ -210,12 +272,14 @@ struct opcode_entry {
 
 #define OP_IMPL(x) static void op_##x(struct dcpu16 *d, WORD val_a, WORD val_b)
 
-#define OP_NBI_ (void)val_b, (void)b
-#define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b)
+#define OP_NBI_ (void)val_b, (void)b, (void)ev_b, (void)ev_b_addr
+#define OP_BASIC_ (void)value_decode(d, val_b, &d->reg_work_[0], &b, &ev_b, &ev_b_addr)
 #define OP_TYPE(op_type) WORD *a, *b;\
     unsigned int lit_a;\
+    dcpu16_acct_event_ ev_a = 0, ev_b = 0;\
+    WORD ev_a_addr = 0, ev_b_addr = 0;\
     do {\
-        lit_a = value_decode(d, val_a, &d->reg_work_[0], &a);\
+        lit_a = value_decode(d, val_a, &d->reg_work_[0], &a, &ev_a, &ev_a_addr);\
         op_type;\
         if (d->skip_) {\
             TRACE("++ SKIPPED");\
@@ -226,6 +290,9 @@ struct opcode_entry {
 #define OP_BASIC(x) OP_TYPE(OP_BASIC_)
 #define OP_NBI(x) OP_TYPE(OP_NBI_)
 
+/* accounting helpers */
+#define ACCT_R(ev) do { if (ev) { acct_event_(d, ev | DCPU16_ACCT_EV_READ, ev##_addr); } } while (0)
+#define ACCT_W(ev) do { if (ev) { acct_event_(d, ev | DCPU16_ACCT_EV_WRITE, ev##_addr); } } while (0)
 
 /* extended opcodes */
 
@@ -248,6 +315,8 @@ OP_IMPL(nbi_jsr) {
     OP_NBI(nbi_jsr);
     /* pushes the address of the next instruction to the stack, then sets PC to a */
 
+    ACCT_R(ev_a);
+
     d->ram[ --d->sp ] = d->pc;
     d->pc = *a;
 
@@ -296,8 +365,11 @@ OP_IMPL(set) {
     OP_BASIC(set);
     /* sets a to b */
 
+    ACCT_R(ev_b);
+    ACCT_W(ev_a);
+
     /* only set non-literal target */
-    if (val_a < 0x1f) {
+    if (!lit_a) {
         *a = *b;
     }
 
@@ -309,12 +381,17 @@ OP_IMPL(add) {
     /* sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise */
     unsigned int acc = *a + *b;
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
+    if (!lit_a) {
         *a = acc;
     }
-    d->o = acc >> 16;
+    d->o = (acc > 0xffff);
 
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(sub) {
@@ -322,12 +399,16 @@ OP_IMPL(sub) {
     /* sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise */
     unsigned int acc = *a - *b;
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
+    if (!lit_a) {
         *a = acc;
     }
-    d->o = acc >> 16;
-
+    d->o = (acc > 0xffff);
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(mul) {
@@ -335,26 +416,34 @@ OP_IMPL(mul) {
     /* sets a to a*b, sets O to ((a*b)>>16)&0xffff */
     unsigned int acc = *a * *b;
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
+    if (!lit_a) {
         *a = acc;
     }
     d->o = acc >> 16;
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(div) {
     OP_BASIC(div);
     /* sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead. */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*b == 0) {
-        if (val_a < 0x1f) {
+        if (!lit_a) {
             *a = 0;
         }
         d->o = 0;
     } else {
         unsigned int acc = *a / *b;
 
-        if (val_a < 0x1f) {
+        if (!lit_a) {
             *a = acc;
         }
 
@@ -363,23 +452,30 @@ OP_IMPL(div) {
     }
 
     d->cycle += 3;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(mod) {
     OP_BASIC(mod);
     /* sets a to a%b. if b==0, sets a to 0 instead. */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*b == 0) {
-        if (val_a < 0x1f) {
+        if (!lit_a) {
             *a = 0;
         }
     } else {
-        if (val_a < 0x1f) {
+        if (!lit_a) {
             *a = *a % *b;
         }
     }
 
     d->cycle += 3;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(shl) {
@@ -387,12 +483,17 @@ OP_IMPL(shl) {
     /* sets a to a<<b, sets O to ((a<<b)>>16)&0xffff */
     unsigned int acc = *a << *b;
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
+    if (!lit_a) {
         *a = acc;
     }
     d->o = acc >> 16;
 
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(shr) {
@@ -400,44 +501,64 @@ OP_IMPL(shr) {
     /* sets a to a>>b, sets O to ((a<<16)>>b)&0xffff */
     unsigned int acc = *a >> *b;
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
+    if (!lit_a) {
         *a = acc;
     }
     d->o = (*a << 16) >> *b;
 
     d->cycle += 2;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(and) {
     OP_BASIC(and);
     /* sets a to a&b */
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
+    if (!lit_a) {
         *a = *a & *b;
     }
 
     d->cycle += 1;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(bor) {
     OP_BASIC(bor);
     /* sets a to a|b */
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
+    if (!lit_a) {
         *a = *a | *b;
     }
 
     d->cycle += 1;
+
+    ACCT_W(ev_a);
 }
 
 OP_IMPL(xor) {
     OP_BASIC(xor);
     /* sets a to a^b */
 
-    if (val_a < 0x1f) {
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
+    if (!lit_a) {
         *a = *a ^ *b;
     }
 
+    ACCT_W(ev_a);
+
     d->cycle += 1;
 }
 
@@ -445,6 +566,9 @@ OP_IMPL(ife) {
     OP_BASIC(ife);
     /* performs next instruction only if a==b */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*a == *b) {
         /* */
     } else {
@@ -459,6 +583,9 @@ OP_IMPL(ifn) {
     OP_BASIC(ifn);
     /* performs next instruction only if a!=b */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*a != *b) {
         /* */
     } else {
@@ -473,6 +600,9 @@ OP_IMPL(ifg) {
     OP_BASIC(ifg);
     /* performs next instruction only if a>b */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if (*a > *b) {
         /* */
     } else {
@@ -487,6 +617,9 @@ OP_IMPL(ifb) {
     OP_BASIC(ifb);
     /* performs next instruction only if (a&b)!=0 */
 
+    ACCT_R(ev_b);
+    ACCT_R(ev_a);
+
     if ((*a & *b) != 0) {
         /* */
     } else {
@@ -517,12 +650,13 @@ static const struct opcode_entry opcode_basic_entries[] = {
     {0x0, "", NULL }
 };
 
-void dump_value(WORD value, WORD nextword) {
-    if (value < 0x07) {
+static inline
+void dump_operand_value_(WORD value, WORD nextword) {
+    if (value <= 0x07) {
         printf(" %c", regnames_[value]);
-    } else if (value < 0x0f) {
+    } else if (value <= 0x0f) {
         printf(" [%c]", regnames_[value & 0x07]);
-    } else if (value < 0x17) {
+    } else if (value <= 0x17) {
         printf(" [0x%04x + %c]", nextword, regnames_[value & 0x07]);
     } else switch (value) {
         case 0x18: printf(" POP"); break;
@@ -537,50 +671,86 @@ void dump_value(WORD value, WORD nextword) {
     }
 }
 
-void dcpu16_disassemble_print(struct dcpu16 *d, WORD addr) {
+/* split a word into the parts of an instruction, and determine how many words it takes up in total */
+static inline
+void instruction_decode_(struct dcpu16 *d, WORD addr, WORD *opcode, WORD *a, WORD *b, unsigned int *instr_len) {
+    *opcode = d->ram[addr] & ((1 << OPCODE_BASIC_BITS) - 1);
+    *a = (d->ram[addr] >> OPCODE_BASIC_BITS) & ((1 << 6) - 1);
+    *b = (d->ram[addr] >> (OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1);
+    *instr_len = 1;
+    /* both basic and nbi opcodes use their b operand */
+    if ( (*b >= 0x10 && *b <= 0x17) || *b == 0x1e || *b == 0x1f )
+        *instr_len += 1;
+    /* but only basic uses a */
+    if (*opcode
+    &&  ((*a >= 0x10 && *a <= 0x17) || *a == 0x1e || *a == 0x1f) )
+            *instr_len += 1;
+}
+
+/*  dcpu16_disassemble_print
+    print the words of the instruction at addr, followed by its assembly
+    7c01 0030      ; SET A,
+    7de1 1000 0020 ; SET [0x1000], 0x0020
+    7803 1000      ; SUB A,
+    c00d           ; IFN A,
+ */
+WORD dcpu16_disassemble_print(struct dcpu16 *d, WORD addr) {
     WORD opcode, a, b;
-    unsigned int instr_len = 1;
+    unsigned int instr_len, i;
     const struct opcode_entry *e;
-
-    opcode = (d->ram[addr] >> OPCODE_BASIC_SHIFT) & ((1 << OPCODE_BASIC_BITS) - 1);
-    a = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS)) & ((1 << 6) - 1);
-    b = (d->ram[addr] >> (OPCODE_BASIC_SHIFT + OPCODE_BASIC_BITS + 6)) & ((1 << 6) - 1); 
-
-    assert(opcode <= 0x0f);
-    assert(a <= 0x3f);
-    assert(b <= 0x3f);
-
-    printf("%04x", d->ram[addr]);
-
-    if (opcode != 0)
-    {
-        if (a == 0x1e || a == 0x1f) {
-            printf(" %04x", d->ram[addr + instr_len]);
-            instr_len++;
+    unsigned int indent = 0;
+    unsigned int partial = 0;
+
+    /*
+        Check the previous instruction, to see if this one should be
+        indented.  This check isn't foolproof, as preceeding addresses
+        could be data which happen to match instructions..
+    */
+    for (i = 3; i; i--) {
+        instruction_decode_(d, addr - i, &opcode, &a, &b, &instr_len);
+        if (instr_len > i)
+            partial++;
+        if (instr_len == i && opcode >= 0xc) {
+            indent++;
+            break;
         }
     }
-    if (b == 0x1e || b == 0x1f) {
-        printf(" %04x", d->ram[addr + instr_len]);
-        instr_len++;
-    }
+
+    /* now get what we're really interested in */
+    instruction_decode_(d, addr, &opcode, &a, &b, &instr_len);
 
     if (opcode)
         e = opcode_basic_entries + opcode;
     else
         e = opcode_nbi_entries + ( (a < OPCODE_NBI_MAX) ? a : (OPCODE_NBI_MAX - 1) );
 
-    printf("%s%s ; %s",
+    /* show the raw words */
+    printf("%04x", d->ram[addr]);
+    for (i = 1; i < instr_len; i++) {
+        printf(" %04x", d->ram[addr + i]);
+    }
+
+    /* align things neatly, show the instruction */
+    printf("%s%s ;%s%s%s",
            instr_len < 3 ? "     " : "",
            instr_len < 2 ? "     " : "",
+           partial ? "*" : " ",
+           indent ? "  " : "",
            e->name);
-    if (opcode != 0) {
-        dump_value(a, d->ram[addr + 1]);
-        if (a == 0x1e || a == 0x1f)
+
+    /* show the operands */
+    i = 0;
+    if (opcode) {
+        dump_operand_value_(a, d->ram[addr + 1]);
+        if ((a >= 0x10 && a <= 0x17) || a == 0x1e || a == 0x1f)
             addr++;
         printf(",");
     }
 
-    dump_value(b, d->ram[addr + 1]);
+    if (opcode || a)
+        dump_operand_value_(b, d->ram[addr + 1]);
+
+    return instr_len;
 }
 
 void dcpu16_step(struct dcpu16 *d) {