Home > Delphi, Language research, Object Pascal > LDef try/catch support

LDef try/catch support

January 14, 2017 Leave a comment Go to comments

Now this was a pickle: namely to support try/catch constructs in LDEF on assembly level. It may sound simple, but it all depends on how exactly the data-model stores individual instructions.

Since LDef has various blocks where code can be defined, abstracting the instruction buffers had to be done. With blocks I naturally mean code sections. A procedure for instance is such a block. A procedure contains instructions and calls to other code blocks. But – it can also contain sub-blocks. Consider the following:

/* block 1 */
public void SomeProc() {
  a = 12;
  b = 24;
  c = a + b;
  if (c >= 27) {
    /* Block 2 */
  } else {
    /* block 3 */
  }
}

The code above, ridicules in it’s simplicity, demonstrates a fundamental principle that all compilers must support, namely to execute different blocks based on some value. In this example block #2 will execute if “c” is more or equal to 27, or block #3 if its not.

This is pretty straight forward right? Well not quite. It all depends on how you store bytecodes in the data model. The first question you should ask is: how do we execute block #2 and not block #3. Remember that in assembly language (or bytecode) this is all one big chunk. Had this been machine code, the compiler would have to calculate the offset of block #3, also where block #3 ends. If the condition was false a jump to block #3 must be performed (skipping over block #2). Well, you get the idea I think.

LDEF was first written in Smart Pascal and runs on node.js and in vanilla html5 applications

LDEF was first written in Smart Pascal and runs on node.js and in vanilla html5 applications

Since LDef is very low-level, I have to come up with something similar. But I also wanted a solution that made things easier. Doing in-place forward calculations etc. is not hard, boring perhaps but not a showstopper by any means. But could I come up with a more flexible solution

First stop was to fragment the instruction blocks. So instead of having a single list of instructions associated with a procedure or function, these can now have as many instruction lists associated with it as memory can hold. The idea is that they are all glued together into a final list when the model is emitted to disk. But the ability to organize and work with chunks of code like this is really a step up from barebone assembly.

type
  TLDefModelParamType =
    (
    ptRegister,   // Parameter is a register
    ptVariable,   // Parameter is a variable (index follows in bytecode)
    ptConst,      // Parameter is a constant (index follows in bytecode)
    ptValue,      // Parameter is a direct value, raw data follows in bytecode
    ptDC          // Parameter is the data-control register
    );

  TLDefModelParam = class
  strict private
    FType:  TLDefModelParamType;  // Param type
    FIndex: integer;              // index (register only!)
    FData:  string;               // data (const + variable only!)
  public
    property  ParamType: TLDefModelParamType read FType write FType;
    property  Index: integer read FIndex write FIndex;
    property  Data: string read FData write FData;
  end;
  TLDefModelParamList = TObjectList;

  TLDefModelInstruction = class(TLDefModelSymbol)
  strict private
    FInstr:     integer;          // Index of instruction in dictionary
    FParams:    TLDefModelParamList;   // Parsed parameters
  public
    property    Id: integer read FInstr write FInstr;
    property    Params: TLDefModelParamList read FParams;
    constructor Create(const AParent: TParserModelObject); override;
    destructor  Destroy; override;
  end;

  TLDefModelInstructionIfThen = class(TLDefModelInstruction)
  strict private
    FThen:      TLDefModelInstructionList;
  public
    property    ThenCode: TLDefModelInstructionList read FThen;
    constructor Create(const AParent: TParserModelObject); override;
    destructor  Destroy; override;
  end;

  TLDefModelInstructionIfThenElse = class(TLDefModelInstructionIfThen)
  strict private
    FElse:      TLDefModelInstructionList;
  public
    property    ElseCode: TLDefModelInstructionList read FElse;
    constructor Create(const AParent: TParserModelObject); override;
    destructor  Destroy; override;
  end;

  TLDefModelInstructionTryCatch = class(TLDefModelInstruction)
  strict private
    FTryCode:   TLDefModelInstructionList;
    FCatchCode: TLDefModelInstructionList;
  public
    property  TryCode: TLDefModelInstructionList read FTryCode;
    property  CatchCode: TLDefModelInstructionList read FCatchCode;
    constructor Create(const AParent: TParserModelObject); override;
    destructor  Destroy; override;
  end;

  TLDefModelInstructionList = class(TLDefModelSymbol)
  strict protected
    function  GetItem(index: integer): TLDefModelInstruction;
  public
    property  Count: integer read ChildGetCount;
    property  Item[index: integer]: TLDefModelInstruction read GetItem;
    function  Add: TLDefModelInstruction; overload;
    function  Add(const NewInstance: TLDefModelInstruction): TLDefModelInstruction; overload;

    function  AddIfThen: TLDefModelInstructionIfThen;
    function  AddIfThenElse: TLDefModelInstructionIfThenElse;
    function  AddTryExcept: TLDefModelInstructionTryCatch;
  end;

  TLDefModelByteCodeChunk = class(TLDefCollectionSymbol)
  strict protected
    function  GetSegment(index: integer): TLDefModelInstructionList; virtual;
  public
    property  Count: integer read ChildGetCount;
    property  Segment[index: integer]: TLDefModelInstructionList read GetSegment;

    function  Add: TLDefModelInstructionList;
  end;

By splitting up TLDefMOdelInstructionList into these parts, especially the if/then, if/then/else and so on classes, working with conditional execution is no longer problematic. A list will always know it’s own size and length, so it’s not really that much work involved in emitting the jump instructions and test stuff.

Exceptions

Exceptions is an intricate part of the virtual machine. How to deal with them however is something I have thought long and hard about. I finally ended up with a system that is easy to use. The ES register will be 0 (zero) if no except has occured, otherwise it will contain the exception identifier.

When an exception occurs, the type and message is pushed on the stack by the virtual machine. A catch block then have to read them out and deal with them. You can also re-throw the exception via “rethrow;” or just throw a new one via “throw ”

    try {
      /* calc longs */
      move r1, count;
      mod r1, 8;
      move r2, count;
      move _longs, r1;
    } catch {
      /* The ES register contains the exception state,
         but the message will be on the stack */
      pop r0; /* get type */
      pop r1; /* get message */
      swap r0, r1; /* Syntax for showmessage wants text in r0 */
      syscall -rtl_showmessage;
    }

Well, fun times ahead! Cant wait to finish the emitters and get this puppy running 🙂

 

Advertisements
  1. stijnsanders
    January 16, 2017 at 8:52 am

    If I remember correctly, in the linux ecosphere, exception handling involves something called landing pads… http://refspecs.linuxfoundation.org/abi-eh-1.22.html

  1. No trackbacks yet.

Leave a Reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: