Note: I've just migrated to a different physical server to run Spivey's Corner,
with a new architecture, a new operating system, a new version of PHP, and an updated version of MediaWiki.
Please let me know if anything needs adjustment! – Mike

Calling convention for OBC

Copyright © 2024 J. M. Spivey
Jump to navigation Jump to search

The bytecode machine uses an extension of the calling convention for C-language functions that is used by the host machine. This extended calling convention allows procedures implemented in bytecode to call native-code primitives and vice-versa, and can be extended to implement translation on first call in a JIT scheme.

The calling convention uses a second stack (which we'll call the Oberon stack) in addition to the ordinary 'C' stack of the host machine. Calling an Oberon procedure entails creating a frame on the Oberon stack in addition to the usual frame on the C stack. (When one bytecode procedure calls another bytecode procedure, the bytecode interpreter avoids calling itself recursively, so no frame on the C stack is created in this case.)

Each Oberon procedure is represented by the address of its 'constant pool', so named because among other things it contains any large constants and global addresses that are referred to by the bytecode of the procedure. When the bytecode machine is running, its Context Pointer CP contains the address of the Constant Pool of the Current Procedure. The first few words of the constant pool are laid out as follows (in rel-2.8):

  • 0: Address of the native-code routine to invoke when the procedure is called. (For bytecode procedures, this is the address of the bytecode interpreter interp; but it may also be the address of a native-code primitive provided by the runtime system, or it may be the address of a native translation of an Oberon procedure.)
  • 4: For bytecode procedures, the address of the bytecode. This word is not used by native-code procedures.
  • 8: Size of bytecode in bytes.
  • 12: Size of arguments on the stack in words. This is used by the RETURN instruction to pop the arguments before returning to the caller.
  • 16: Size of stack frame in words. This much space is allocated on the stack when the procedure is called.
  • 20: Garbage collector map for the stack frame.
  • 24: Address of table of GC maps for the evaluation stack, one for each call site in the procedure where one or more pointers remain on the evaluation stack during the call, apart from actual parameters of the call.
  • 28: First of the contants used in the body of the procedure.

(see CP_PRIM etc. in obcommon.h.)

The native-code routine must have the heading

value *func(value *sp)

Before the function is called, the arguments and also the return address, the dynamic link and the CP for the procedure being called are pushed onto the Oberon stack, and execution of a bytecode procedure begins with the bytecode interpreter loading its CP register from the stack, then using it to find the bytecode to run.

The function typically returns the same address that it received as an argument; the only exception is that coroutines are implemented in the bytecode interpreter by switching the stack, so that a call the the procedure Coroutines.Transfer appears to return in a different coroutine from the one that called it, and it does this by returning an address in a different stack. You are not expected to understand this, as they say – and it has no relevance to writing native-code translators for Oberon.