1
0
forked from Mirror/wren

Make ip an actual pointer.

This commit is contained in:
Bob Nystrom
2013-12-22 13:46:13 -08:00
parent 7868287f89
commit 7625de7536
4 changed files with 123 additions and 86 deletions

View File

@ -1,55 +0,0 @@
class Base {
foo {
io.write("a")
inner
io.write("b")
}
}
class Mid is Base {}
class Derived is Mid {
foo {
io.write("c")
}
}
var d = Derived.new
d.foo
// find base-most method "foo" (on Base)
// invoke it
// when inner is hit, walk down inheritance chain to find nearest override
// (in Derived)
// invoke it
for every class, store two lists:
1. list of methods this class defines.
2. mapping of method name to which method body should be invoked first for
that method. this will be the base-most class's definition of a given
method name.
typedef struct
{
// The method body to invoke. This will be the base-most definition of a
// method for a given name.
Method* method;
// If [method] calls `inner`, this is the class whose inner method is invoked.
// If *that* method in turn calls `inner`, we look up the [inner] property of
// this method on that class's dispatch table to chain to the next one.
//
// This is `NULL` if there is no inner method to call.
ClassObj* inner;
} Dispatch;
typedef struct
{
Dispatch dispatchTable[MAX_SYMBOLS];
} ClassObj;
with normal overridding, can unify those. with inner(), need both.
whenever a method is invoked, look it up in 2, then call that method body.

80
doc/error-handling.txt Normal file
View File

@ -0,0 +1,80 @@
Q: Can we use fibers for error-handling?
The goal here is to avoid adding support for exception handling if we're already
going to support fibers. A potential bonus would be being able to have
restartable error-handling.
The general idea is that instead of putting code in a "try" block, you throw it
onto a new fiber. If an error occurs, that fiber is paused, and returns control
back to the spawning fiber. The parent fiber can then decipher the error and
either abandon the fiber, or try to fix the error and resume somehow.
The first question is what kinds of errors is this useful for. For things like
parsing strings where failure is common and error-handling needs to be
lightweight, I think using fibers is too heavy, both in performance and code.
A better answer there is to lean on dynamic typing and return null on parse
failure.
On the other hand, it might be nice to be able to resume here if the code that
provided the string is far away and you don't want to have to manually propagate
the error out.
Programmatic errors like unvalid argument types should halt the fiber but the
programmer will not want to resume that at runtime. Using the mechanism here is
fine since it would then dump a stack trace, etc. But it won't take advantage
of resuming.
Resuming is probably useful for things like IO errors where the error can't be
easily predicted beforehand but where you may want to handle it gracefully. For
example, if a file can't be opened, the caller may want to wait a while and
try again.
--
After thinking about it, maybe resuming is a bridge too far. Erlang's model is
that a failure just kills the process. I'll note that Erlang does have try and
catch, though.
The goals for error-handling in a scripting language are:
0. Have simple semantics and implementation.
1. Make it easy for developers to track down programmatic errors so they can
fix them. This means bugs like wrong argument types should fail immediately
and loudly, and should provide context (a callstack) about where the error
occurred.
2. For runtime errors like parsing an invalid string or opening a missing file,
the program should be able to easily detect the error at handle it.
3. It *may* be useful for programmers to be able to trap all errors and try to
keep the program alive, or at least log the error in a meaningful way. When
you have user-defined scripts, or a lot of code, or code authored by
non-technical people, it's nice if a failure in one part can be reported but
not take down the entire system.
Two close-at-hand examples:
- The REPL. A bug in code in the REPL shouldn't kill the whole REPL session.
- The test framework. In order to write tests in Wren that test programmatic
runtime errors, we need to be able to detect them and output something.
The test runner could just parse the error output when the entire process
dies, but that means you can only have one error test per test file.
Given those, I'm thinking:
1. Programmatic errors take down the entire fiber and dump a callstack.
Normally, they will also take down the parent fiber and so on until the
entire program goes down.
2. Runtime errors return error codes (or null). Things like parsing a string to
a number, etc. should just return an error that you are responsible for
handling.
3. When handing off control to a fiber, there is a "guarded run" method that
will run the fiber. If it fails with a programmatic error, the invoked fiber
dies, but the parent does not. It gets the callstack and error as some sort
of object it can poke at.

View File

@ -594,16 +594,15 @@ Value interpret(WrenVM* vm, Value function)
#define PUSH(value) (fiber->stack[fiber->stackSize++] = value)
#define POP() (fiber->stack[--fiber->stackSize])
#define PEEK() (fiber->stack[fiber->stackSize - 1])
#define READ_ARG() (frame->ip++, bytecode[ip++])
#define READ_ARG() (*ip++)
// Hoist these into local variables. They are accessed frequently in the loop
// but assigned less frequently. Keeping them in locals and updating them when
// a call frame has been pushed or popped gives a large speed boost.
register CallFrame* frame;
register int ip;
register unsigned char* ip;
register ObjFn* fn;
register Upvalue** upvalues;
register unsigned char* bytecode;
// Use this before a CallFrame is pushed to store the local variables back
// into the current one.
@ -623,8 +622,7 @@ Value interpret(WrenVM* vm, Value function)
{ \
fn = AS_CLOSURE(frame->fn)->fn; \
upvalues = AS_CLOSURE(frame->fn)->upvalues; \
} \
bytecode = fn->bytecode
}
#if WREN_COMPUTED_GOTO
@ -696,11 +694,11 @@ Value interpret(WrenVM* vm, Value function)
#define INTERPRET_LOOP DISPATCH();
#define CASE_CODE(name) code_##name
#define DISPATCH() goto *dispatchTable[instruction = bytecode[ip++]]
#define DISPATCH() goto *dispatchTable[instruction = *ip++]
#else
#define INTERPRET_LOOP for (;;) switch (instruction = bytecode[ip++])
#define INTERPRET_LOOP for (;;) switch (instruction = *ip++)
#define CASE_CODE(name) case CODE_##name
#define DISPATCH() break
@ -711,9 +709,7 @@ Value interpret(WrenVM* vm, Value function)
Code instruction;
INTERPRET_LOOP
{
CASE_CODE(CONSTANT):
PUSH(fn->constants[READ_ARG()]);
DISPATCH();
CASE_CODE(POP): POP(); DISPATCH();
CASE_CODE(NULL): PUSH(NULL_VAL); DISPATCH();
CASE_CODE(FALSE): PUSH(FALSE_VAL); DISPATCH();
@ -786,6 +782,24 @@ Value interpret(WrenVM* vm, Value function)
DISPATCH();
}
CASE_CODE(LOAD_LOCAL):
{
int local = READ_ARG();
PUSH(fiber->stack[frame->stackStart + local]);
DISPATCH();
}
CASE_CODE(STORE_LOCAL):
{
int local = READ_ARG();
fiber->stack[frame->stackStart + local] = PEEK();
DISPATCH();
}
CASE_CODE(CONSTANT):
PUSH(fn->constants[READ_ARG()]);
DISPATCH();
CASE_CODE(SUPER_0):
CASE_CODE(SUPER_1):
CASE_CODE(SUPER_2):
@ -859,20 +873,6 @@ Value interpret(WrenVM* vm, Value function)
DISPATCH();
}
CASE_CODE(LOAD_LOCAL):
{
int local = READ_ARG();
PUSH(fiber->stack[frame->stackStart + local]);
DISPATCH();
}
CASE_CODE(STORE_LOCAL):
{
int local = READ_ARG();
fiber->stack[frame->stackStart + local] = PEEK();
DISPATCH();
}
CASE_CODE(LOAD_UPVALUE):
{
ASSERT(upvalues != NULL,
@ -934,7 +934,6 @@ Value interpret(WrenVM* vm, Value function)
}
CASE_CODE(DUP): PUSH(PEEK()); DISPATCH();
CASE_CODE(POP): POP(); DISPATCH();
CASE_CODE(JUMP):
{
@ -957,6 +956,7 @@ Value interpret(WrenVM* vm, Value function)
Value condition = POP();
// False is the only falsey value.
// TODO: Null should be falsey too.
if (IS_FALSE(condition)) ip += offset;
DISPATCH();
}
@ -967,6 +967,7 @@ Value interpret(WrenVM* vm, Value function)
Value condition = PEEK();
// False is the only falsey value.
// TODO: Null should be falsey too.
if (!IS_FALSE(condition))
{
// Discard the condition and evaluate the right hand side.
@ -986,6 +987,7 @@ Value interpret(WrenVM* vm, Value function)
Value condition = PEEK();
// False is the only falsey value.
// TODO: Null should be falsey too.
if (IS_FALSE(condition))
{
// Discard the condition and evaluate the right hand side.
@ -1155,9 +1157,19 @@ Value interpret(WrenVM* vm, Value function)
void wrenCallFunction(Fiber* fiber, Value function, int numArgs)
{
// TODO: Check for stack overflow.
fiber->frames[fiber->numFrames].fn = function;
fiber->frames[fiber->numFrames].ip = 0;
fiber->frames[fiber->numFrames].stackStart = fiber->stackSize - numArgs;
CallFrame* frame = &fiber->frames[fiber->numFrames];
frame->fn = function;
frame->stackStart = fiber->stackSize - numArgs;
frame->ip = 0;
if (IS_FN(function))
{
frame->ip = AS_FN(function)->bytecode;
}
else
{
frame->ip = AS_CLOSURE(function)->fn->bytecode;
}
fiber->numFrames++;
}

View File

@ -244,9 +244,9 @@ struct WrenVM
// TODO: Move into wren_vm.c.
typedef struct
{
// Index of the current (really next-to-be-executed) instruction in the
// block's bytecode.
int ip;
// Pointer to the current (really next-to-be-executed) instruction in the
// function's bytecode.
unsigned char* ip;
// The function or closure being executed.
Value fn;