All -- My previous post included a patch. This one doesn't because I can't work on this one away from my office. But, I'm going to put the idea out to the list, and perhaps someone will beat me to trying it (but, do please tell me if you are going to so I don't go duplicating your effort when I get back to my office).
I have a 32-bit system, so the discussion below will be geared to that environment. I think it shouldn't be too hard to adapt the technique to a 64-bit system, but that's not my area of specialization. After the bytecode is loaded, but before it is executed, put it through a stage of processing that requires about as much information as a disassembler would (which is why my op_info stuff from one of my previous patches is required). This process converts opcodes into pointers to the op functions, and arguments to pointers to the constant values or register entries. This means that we amortize the dereferences over all invocations of the op at each PC, which when tight loops are involved should make for noticable savings. In the case of my 32-bit machine, I could do the conversion in-place and hand the resulting "crystalized" bytecode over to a runops variant that knows what to expect. BTW, this could be controlled by an interpreter flag (but one that doesn't have a corresponding op): PARROT_CRYSTALIZE_FLAG Although, one wonders if these flags really should be part of the code object rather than part of the interpreter, so that they are local to their compilation unit. Perhaps the true answer will be some combination of interpreter flags and code flags combine to select the runops core that is used. One extra trick needed, though, is a version of process_opfunc.pl that compiles the ops so that they expect their arguments to already have the dereferencing done. This shouldn't be too hard, but we'd need room for another parallel opcode table, or we'd need to switch it in as appropriate. If I don't hear from someone that they are going to try this out, I'll take it on next time I'm in my office (possibly as early as tomorrow Morning, EST). Regards, -- Gregor
? include/parrot/vtable.h Index: basic_opcodes.ops =================================================================== RCS file: /home/perlcvs/parrot/basic_opcodes.ops,v retrieving revision 1.32 diff -a -u -r1.32 basic_opcodes.ops --- basic_opcodes.ops 2001/10/06 00:57:43 1.32 +++ basic_opcodes.ops 2001/10/06 22:27:13 @@ -699,3 +699,20 @@ AUTO_OP xor_i { INT_REG(P1) = INT_REG(P2) ^ INT_REG(P3); } + +/* BOUNDS_ic */ +AUTO_OP bounds_ic { + if (P1) { interpreter->flags |= PARROT_BOUNDS_FLAG; } + else { interpreter->flags &= ~PARROT_BOUNDS_FLAG; } + RESUME(3); /* After the end op which must follow bounds */ + RETURN(2); +} + +/* TRACE_ic */ +AUTO_OP trace_ic { + if (P1) { interpreter->flags |= PARROT_TRACE_FLAG; } + else { interpreter->flags &= ~PARROT_TRACE_FLAG; } + RESUME(3); /* After the end op which must follow trace */ + RETURN(2); +} + Index: interpreter.c =================================================================== RCS file: /home/perlcvs/parrot/interpreter.c,v retrieving revision 1.23 diff -a -u -r1.23 interpreter.c --- interpreter.c 2001/10/03 16:21:30 1.23 +++ interpreter.c 2001/10/06 22:27:13 @@ -13,6 +13,13 @@ #include "parrot/parrot.h" #include "parrot/interp_guts.h" +runops_core_f runops_cores[4] = { + runops_t0b0_core, + runops_t0b1_core, + runops_t1b0_core, + runops_t1b1_core +}; + char *op_names[2048]; int op_args[2048]; @@ -42,26 +49,44 @@ } } -/*=for api interpreter runops +/*=for api interpreter runops_t0b0_core * run parrot operations until the program is complete + * + * No tracing. + * No bounds checking. */ opcode_t * -runops_notrace_core (struct Parrot_Interp *interpreter) { +runops_t0b0_core (struct Parrot_Interp *interpreter, opcode_t * pc) { /* Move these out of the inner loop. No need to redeclare 'em each time through */ opcode_t *(* func)(); opcode_t *(**temp)(); + + while (*pc) { DO_OP(pc, temp, func, interpreter); } + + return pc; +} + +/*=for api interpreter runops_t0b1_core + * run parrot operations until the program is complete + * + * No tracing. + * With bounds checking. + */ +opcode_t * +runops_t0b1_core (struct Parrot_Interp *interpreter, opcode_t * pc) { + /* Move these out of the inner loop. No need to redeclare 'em each + time through */ + opcode_t *(* func)(); + opcode_t *(**temp)(); opcode_t * code_start; INTVAL code_size; opcode_t * code_end; - opcode_t * pc; code_start = (opcode_t *)interpreter->code->byte_code; code_size = interpreter->code->byte_code_size; code_end = (opcode_t *)(interpreter->code->byte_code + code_size); - pc = code_start; - while (pc >= code_start && pc < code_end && *pc) { DO_OP(pc, temp, func, interpreter); } @@ -70,14 +95,72 @@ } /* - *=for api interpreter trace_op + *=for api interpreter trace_op_b0 * TODO: This isn't really part of the API, but here's its documentation. Prints the PC, OP * and ARGS. Used by runops_trace. + * + * No bounds checking. */ void -trace_op(opcode_t * code_start, opcode_t * code_end, opcode_t *pc) { +trace_op_b0(opcode_t * code_start, opcode_t *pc) { int i; + fflush(NULL); /* Flush *ALL* output before printing trace info */ + + fprintf(stderr, "PC=%ld; OP=%ld (%s)", (long)(pc - code_start), *pc, +op_names[*pc]); + if (op_args[*pc]) { + fprintf(stderr, "; ARGS=("); + for(i = 0; i < op_args[*pc]; i++) { + if (i) { fprintf(stderr, ", "); } + fprintf(stderr, "%ld", (long) *(pc + i + 1)); + } + fprintf(stderr, ")"); + } + fprintf(stderr, "\n"); + + fflush(stderr); /* Flush *stderr* now that we've output the trace info */ +} + +/*=for api interpreter runops_t1b0_core + * TODO: Not really part of the API, but here's the docs. + * Passed to runops_generic() by runops_trace(). + * + * With tracing. + * No bounds checking. + */ +opcode_t * +runops_t1b0_core (struct Parrot_Interp *interpreter, opcode_t * pc) { + /* Move these out of the inner loop. No need to redeclare 'em each + time through */ + opcode_t *( *func)(); + opcode_t *(**temp)(); + opcode_t *code_start; + + code_start = (opcode_t *)interpreter->code->byte_code; + + trace_op_b0(code_start, pc); + + while (*pc) { + DO_OP(pc, temp, func, interpreter); + trace_op_b0(code_start, pc); + } + + return pc; +} + +/* + *=for api interpreter trace_op_b1 + * TODO: This isn't really part of the API, but here's its documentation. Prints the +PC, OP + * and ARGS. Used by runops_trace. + * + * With bounds checking. + */ +void +trace_op_b1(opcode_t * code_start, opcode_t * code_end, opcode_t *pc) { + int i; + + fflush(NULL); /* Flush *ALL* output before printing trace info */ + if (pc >= code_start && pc < code_end) { fprintf(stderr, "PC=%ld; OP=%ld (%s)", (long)(pc - code_start), *pc, op_names[*pc]); if (op_args[*pc]) { @@ -93,14 +176,19 @@ else { fprintf(stderr, "PC=%ld; OP=<err>\n", (long)(pc - code_start)); } + + fflush(stderr); /* Flush *stderr* now that we've output the trace info */ } -/*=for api interpreter runops_trace_core +/*=for api interpreter runops_t1b1_core * TODO: Not really part of the API, but here's the docs. * Passed to runops_generic() by runops_trace(). + * + * With tracing. + * With bounds checking. */ opcode_t * -runops_trace_core (struct Parrot_Interp *interpreter) { +runops_t1b1_core (struct Parrot_Interp *interpreter, opcode_t * pc) { /* Move these out of the inner loop. No need to redeclare 'em each time through */ opcode_t *( *func)(); @@ -108,20 +196,16 @@ opcode_t * code_start; INTVAL code_size; opcode_t * code_end; - opcode_t * pc; code_start = (opcode_t *)interpreter->code->byte_code; code_size = interpreter->code->byte_code_size; code_end = (opcode_t *)(interpreter->code->byte_code + code_size); - - pc = code_start; - trace_op(code_start, code_end, pc); + trace_op_b1(code_start, code_end, pc); while (pc >= code_start && pc < code_end && *pc) { DO_OP(pc, temp, func, interpreter); - - trace_op(code_start, code_end, pc); + trace_op_b1(code_start, code_end, pc); } return pc; @@ -132,11 +216,10 @@ * Generic runops, which takes a function pointer for the core. */ void -runops_generic (opcode_t * (*core)(struct Parrot_Interp *), struct Parrot_Interp *interpreter) { +runops_generic (opcode_t * (*core)(struct Parrot_Interp *, opcode_t *), struct +Parrot_Interp *interpreter, opcode_t * pc) { opcode_t * code_start; INTVAL code_size; opcode_t * code_end; - opcode_t * pc; check_fingerprint(interpreter); @@ -144,7 +227,7 @@ code_size = interpreter->code->byte_code_size; code_end = (opcode_t *)(interpreter->code->byte_code + code_size); - pc = core(interpreter); + pc = core(interpreter, pc); if (pc < code_start || pc >= code_end) { fprintf(stderr, "Error: Control left bounds of byte-code block (now at location %d)!\n", (int) (pc - code_start)); @@ -158,18 +241,25 @@ */ void runops (struct Parrot_Interp *interpreter, struct PackFile * code) { - opcode_t * (*core)(struct Parrot_Interp *); + opcode_t * (*core)(struct Parrot_Interp *, opcode_t *); - if (interpreter->flags & PARROT_TRACE_FLAG) { - core = runops_trace_core; - } - else { - core = runops_notrace_core; - } + interpreter->resume_addr = (opcode_t *)code->byte_code; + + while (interpreter->resume_addr) { + int which = 0; + opcode_t * pc = interpreter->resume_addr; + + interpreter->resume_addr = (opcode_t *)NULL; + + which |= interpreter->flags & PARROT_BOUNDS_FLAG ? 0x01 : 0x00; + which |= interpreter->flags & PARROT_TRACE_FLAG ? 0x02 : 0x00; - interpreter->code = code; + core = runops_cores[which]; - runops_generic(core, interpreter); + interpreter->code = code; + + runops_generic(core, interpreter, pc); + } } /*=for api interpreter make_interpreter @@ -252,6 +342,8 @@ /* Done. Return and be done with it */ interpreter->code = (struct PackFile *)NULL; + + interpreter->resume_addr = (opcode_t *)NULL; return interpreter; } Index: opcode_table =================================================================== RCS file: /home/perlcvs/parrot/opcode_table,v retrieving revision 1.22 diff -a -u -r1.22 opcode_table --- opcode_table 2001/09/24 16:27:48 1.22 +++ opcode_table 2001/10/06 22:27:13 @@ -181,3 +181,6 @@ shr_i_ic 3 I I i xor_i 3 I I I +bounds_ic 1 i +trace_ic 1 i + Index: process_opfunc.pl =================================================================== RCS file: /home/perlcvs/parrot/process_opfunc.pl,v retrieving revision 1.23 diff -a -u -r1.23 process_opfunc.pl --- process_opfunc.pl 2001/10/06 00:57:43 1.23 +++ process_opfunc.pl 2001/10/06 22:27:13 @@ -75,6 +75,7 @@ s/RETURN\(0\);/return 0;/; s/RETURN\((.*)\)/return cur_opcode + $1/; + s/RESUME\((.*)\)/interpreter->resume_addr = cur_opcode + $1/; s/\bP(\d+)\b/$param_sub[$1]/g; s/INT_REG\(([^)]+)\)/interpreter->int_reg->registers[$1]/g; Index: test_main.c =================================================================== RCS file: /home/perlcvs/parrot/test_main.c,v retrieving revision 1.14 diff -a -u -r1.14 test_main.c --- test_main.c 2001/10/06 00:57:43 1.14 +++ test_main.c 2001/10/06 22:27:13 @@ -16,23 +16,36 @@ main(int argc, char **argv) { int i; int tracing; + int bounds_checking; struct Parrot_Interp *interpreter; init_world(); interpreter = make_interpreter(); - /* Look for the '-t' tracing switch. We really should use getopt, but are we allowed? */ - - if (argc > 1 && strcmp(argv[1], "-t") == 0) { - tracing = 1; - for(i = 2; i < argc; i++) { - argv[i-1] = argv[i]; + /* + ** Look for the '-t' tracing and '-b' bounds checking switches. + ** We really should use getopt, but are we allowed? + */ + + tracing = 0; + bounds_checking = 0; + + while (argc > 1 && argv[1][0] == '-') { + if (argv[1][1] == 't' && argv[1][2] == '\0') { + tracing = 1; + for(i = 2; i < argc; i++) { + argv[i-1] = argv[i]; + } + argc--; + } + else if (argv[1][1] == 'b' && argv[1][2] == '\0') { + bounds_checking = 1; + for(i = 2; i < argc; i++) { + argv[i-1] = argv[i]; + } + argc--; } - argc--; - } - else { - tracing = 0; } /* If we got only the program name, complain */ @@ -101,6 +114,10 @@ if (tracing) { interpreter->flags |= PARROT_TRACE_FLAG; + } + + if (bounds_checking) { + interpreter->flags |= PARROT_BOUNDS_FLAG; } runops(interpreter, pf); Index: include/parrot/interpreter.h =================================================================== RCS file: /home/perlcvs/parrot/include/parrot/interpreter.h,v retrieving revision 1.6 diff -a -u -r1.6 interpreter.h --- include/parrot/interpreter.h 2001/10/02 14:01:31 1.6 +++ include/parrot/interpreter.h 2001/10/06 22:27:13 @@ -38,17 +38,36 @@ struct PackFile * code; /* The code we are executing */ + opcode_t * resume_addr; }; #define PARROT_DEBUG_FLAG 0x01 /* Bit in the flags that says we're debugging */ #define PARROT_TRACE_FLAG 0x02 /* We're tracing execution */ +#define PARROT_BOUNDS_FLAG 0x04 /* We're tracking byte code bounds +during execution */ #define PCONST(i) PF_CONST(interpreter->code, (i)) #define PNCONST PF_NCONST(interpreter->code) struct Parrot_Interp * make_interpreter(); + +typedef opcode_t * (*runops_core_f)(struct Parrot_Interp *, opcode_t *); + +opcode_t * +runops_t0b0_core(struct Parrot_Interp *, opcode_t *); + +opcode_t * +runops_t0b1_core(struct Parrot_Interp *, opcode_t *); + +opcode_t * +runops_t1b0_core(struct Parrot_Interp *, opcode_t *); + +opcode_t * +runops_t1b1_core(struct Parrot_Interp *, opcode_t *); + +void +runops_generic(); void runops(struct Parrot_Interp *, struct PackFile *); Index: t/trace.pasm =================================================================== RCS file: trace.pasm diff -N trace.pasm --- /dev/null Sat Oct 6 04:18:46 2001 +++ trace.pasm Sat Oct 6 15:27:13 2001 @@ -0,0 +1,8 @@ +print "Howdy!\n" +trace 1 +end +print "There!\n" +trace 0 +end +print "Partner!\n" +end