All --
I've had a couple of inspirations since the 0.0.2 release, and this
is the one I can do from home, without the op_info stuff from one
of my earlier patches.
Assume there is a configuration space of runops core behaviors that
is based on various settings of the interpreter flags. If there
were enough of them, we'd want to be able to do the combinatorics
with a script that generates the code, but for this example there
are only two such flags:
PARROT_TRACE_FLAG - If true, we print tracing info
PARROT_BOUNDS_FLAG - If true, we check bounds
I can imagine a third one, which I'll get to in more detail later:
PARROT_EVENT_FLAG - If true, we check for events
Now, we already had test_prog (BTW, when are we going to call this
*the* interpreter, instead of just a test program) set up to
intercept the -t flag to turn on tracing.
I've set up the -b flag to turn on bounds checking (off by default
now).
But, the most important part of this patch is the implementation
of dynamic switching of runops cores. This means that we have much
flexibility, but programs that don't use a feature don't have to
pay for it in the inner loop. But, those features can be turned
on and off at *run time*.
Here's how it works:
* A new element of the interpreter structure: resume_addr.
There's a new check in runops so that if we end, but have
a resume_addr set, we go back to the point where we
select a core based on the flags, and resume execution
with the new core.
* New ops 'trace_ic' and 'bounds_ic' (_i variants would be
reasonable, too).
These ops twiddle the appropriate bits of the interpreter's
flag, set up a resume address and return the PC for the
next instruction (just like any other op).
It is required that you have an 'end' op follow these ops
to force the DO_OP loop to terminate and trigger the
mechanism described above.
I've included a test program, t/trace.pasm, that demonstrates
this.
I've also run t/test.pasm with and without -b and noted a small
increase in performance without bounds checking for those that
want to live fast and dangerously :)
Now, on to PARROT_EVENT_FLAG. With the mechanism implemented
here, events could be turned off at the start (when the event
queue is empty), and ops that queue events could turn the
flag on and cause a resumption so that a core that checks for
events would be used. When the event queue is empty, we can
flip the flag back off again. Details, of course, to be
filled in by folks who want to do the event stuff.
Anyway, whether code that does events needs to leave the flag
on or can get by with flipping it like this, programs that
don't do events don't have to pay *any* inner loop cost for
having an interpreter that allows other programs to use them.
I think that counts as being The Parrot Way (TM).
With enough thought, chances are good that we could come up
with a program that generates runops_cores.[hc] from some
specification of the interpreter flags and some code fragments
with combination hints. Sort of a Poor Man's Aspect-Oriented
Programming for Parrot Inner Loops...
I'm sure you can imagine your own bits of switchable code for
runops cores, but one more that I've thought a little bit
about is:
PARROT_PROFILE_FLAG
for op profiling. I'm sure you can imagine what that would
look like.
I'm considering committing this patch, but I'll wait for some
feedback from others to see if I've missed something important.
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