lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


  It seems I'm in a large reply mode tonight.  Warning:  long post ahead
mateys!  Aaaaahhhhrrrrrrrrrrrrr!

It was thus said that the Great Andrew Starks once stated:
> On Thu, Sep 11, 2014 at 2:38 PM, Sean Conner <sean@conman.org> wrote:
> 
> >   I wrote a big [sic] on this a few years ago:
> >
> >         http://boston.conman.org/2009/12/01.2
>
> I just read your blog post. Great stuff. It summarized the motivation
> behind my question.
> 
> 1: My code looks great! Things are tidy and clean and commented. But there
> is no error checking, so this is just a prototype.
> 2: I'm getting tired of programming, because typing out these errors and
> checks for errors is a pain. now my code looks like crap and the mere
> process of doing this has led me to bloat it in other ways.
> 
> It's not a Lua problem, though.

  You're right, it's not, but I have been thinking on this and I'd like to
take the time to describe how I handle errors in a C project I did, a
Motorola 6809 emulator [1].

  Most of the 6809 emulators (heck, most emulators where I've seen the code)
tend to use global data for everything, but taking a page from Lua, I
thought it might be nice to package everything up in a single context
structure.  

  That left dealing with "memory".  I didn't want to restrict anyone with a
fixed memory layout.  One person might want to use all 64K as RAM, while
someone else might want RAM at $0000-$03FF [2], ROM at $F000-$FFFF with
nothing in between (and further more, no executing code from $0000-$03FF,
and no writes to $F000-$FFFF).  That's up to the user to decide.

  It's an easy enough fix---include a call back in the context structure for
reading/writing memory.  But how to handle errors?  Using the "return error"
style problem, and the library code would look like:

	case 0x0C:
	     cpu->cycles += 5;
	     mc6809_direct(cpu);
	     
	     if ((rc = (*cpu->read)(cpu,cpu->ea.w,false,&acc)) != 0)
	       return rc;
	     acc = op_inc(cpu,acc); /* to update the condition codes */
	     if ((rc = (*cpu->write)(cpu,cpu->ea.w,acc)) != 0)
	       return rc;
	     break;

  Ugly.  And with 316 calls to cpu->read() ... it was this that I started
wishing C had exceptions.  Well, it does, kind of.  Sort of. 

                             setjmp()/longjmp()

  Hard to use correctly [3] but in this case, where I don't obtain any
resources, makes it an easy choice.  Include a jmp_buf inside the context
structure, call setjmp() at the start of the main routine [4], and then the
person using the library can do:

	static mc6809byte__t my_read(mc6809__t *cpu,mc6809addr__t addr,bool iscode)
	{
	  if ((addr > 0x03FF) && (addr < 0xFF00))
	    longjmp(cpu->err,NO_MEMORY);

	  if (iscode && (addr < 0x0400))
	    longjmp(cpu->err,EXEC_FROM_RAM);
	  
	  /* ... rest of code to return a byte */
	}
	
and the main driver (the caller of mc6809_step()) can handle the error. The
library code can now look like:

	case 0x0C:
             cpu->cycles += 5;
             mc6809_direct(cpu);
             (*cpu->write)(cpu,cpu->ea.w,op_inc(cpu,(*cpu->read)(cpu,cpu->ea.w,false)));
             break;

Ahh, much nicer.

Okay, so that's memory.  But what about things like unused opcodes, or
illegal addressing modes?  How to handle that?  That's harder to say,
because again, as a library, I might now know what the user is expecting. 
The real MC6809 CPU didn't trap unused opcodes or illegal addressing
modes---it just kept running although the results were undefined (per the
documentation).  Some people might be fine with that; others might want to
trap such code because it's buggy.  

  Who am I to say what's the right course?
  
  But I did have this jmp_buf in the context structure.  
  
  So, one more call back, this time to handle such faults.  There's enough
context in the context structure, in addtion to the error code, to handle
the situation.  So, one user can write:

	static void my_fault(mc6809__t *cpu,mc6809fault__t fault)
	{
	  (void)cpu;
	  (void)fault;
	}
	
because they want to keep calm and carry one, while someone else can write:

	static void my_fault(mc6809__t *cpu,mc6809fault__t fault)
	{
	  longjmp(cpu->err,fault);
	}

to stop the world it's done gone crazy!  And yet a third person might do:

	static void my_fault(mc6809__t *cpu,mc6809fault__t fault)
	{
	  if (fault != MC6809_FAULT_INSTRUCTION)
	    longjmp(cpu->err,fault);
	  
	  if (cpu->inst == 0x01)
	    cpu->A = getchar();
	  else if (cpu->inst == 0x02)
	    putchar(cpu->A);
	  else
	    longjmp(cpu->err,fault);
	}
	
to "define" two new opcodes for some easy I/O into the emulator.  What I
ended up doing was implementing Common Lisp's conditions/restarts [5].  This
approach could also be done in Lua, but it's up to the programmer writing
the code (a module most likely) to put the proper hooks into place to call
back into the user's code to handle ambiguous conditions (maybe they want to
throw an exception ... maybe they can handle it because of greater context
... maybe they just return an error).  
	
  -spc (Just some late night musings ... )

[1]	https://github.com/spc476/mc6809

[2]	Old school style hex constants.  What can I say, I'm a child of the
	80s.

[3]	setjmp() pretty much saves the current contents of the registers,
	incl uding the stack pointer, and longjmp() restores the contents,
	thus resetting the stack pointer.  Great, as long as you haven't
	allocated any memory, opened any files, or in general, obtained any
	form of resources ...

[4]	mc6809_step(), which runs one instruction.

[5]	http://www.gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html