[Oberon] Obstacles when programming in RISC Oberon and proposed patches
Michael Schierl
schierlm at gmx.de
Sat May 24 22:13:07 CEST 2014
Hello,
in general I like the simplicity of Oberon (and that you usually can
find the cause of some problems quickly since Oberon itself is written
all in Oberon and small enough to find what you search) but at some
places, I think Oberon could be more pleasurable without making it a lot
more complex. Therefore I prepared a few patches - if you like them,
feel free to incorporate them, and if not, I can still use them :)
If there is a better place to submit patches (or Git pull requests) like
this, feel free to tell me.
1. Uninitialized local variables
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
When something is allocated on the heap with NEW, all bytes are zeroed
out (in Kernel.New). Variables declared at the top of the module are
also zeroed out (in Modules.Load). Only local variables that live on the
stack are not zeroed out, but stay what they are. It is easy to miss an
initialization, resulting in a program that "sometimes works". On the
other hand, it is also easy to patch the compiler to zero out these
stack areas:
https://github.com/schierlm/OberonEmulator/blob/master/ProposedPatches/initialize-local-variables.patch
When recompiling all Oberon from scratch and using it for a few days,
you will notice that System.Clear is no longer working (since its "buf"
variable was not initialized). Before it worked most of the time, but
TRAPped sometimes...
Patch to fix:
https://github.com/schierlm/OberonEmulator/blob/master/ProposedPatches/system-clear-init-buf.patch
2. Allocation failures and Garbage Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
One of the disadvantages of Oberon's simple garbage collector (but still
complex enough that I did not find time to wrap my head around it) is
that it can only run "between" commands. So, when you compile something
while 30% of the heap are used an allocation fails during compilation,
it is not possible to run the GC to collect these 30% and then continue
with compilation. Basically I guess the reason is that objects
referenced from stack are ignored by the garbage collector, so it would
collect "live" objects if it was run from within Kernel.New (but I'm not
sure there, correct me if I'm wrong). As I said, the GC is too complex
for me to "fix" (and probably it would make it even more complex if it
was fixed), so I'm trying a different approach. In practice, it is no
big deal to just run System.Collect followed by a recompilation if this
situation arises. The largest problem is when running own code, whether
the TRAP is a real programming error or just some allocation failure in
the middle of some core library. Therefore I created a patch which will
check in Oberon.Loop if there was a failed allocation since the last
invocation and prints a message to Oberon.Log:
https://github.com/schierlm/OberonEmulator/blob/master/ProposedPatches/log-allocation-failures.patch
That way, it is clearly visible whenever allocation fails and it may be
a good idea to just re-run the command after garbage collection.
3. Trap Backtraces
~~~~~~~~~~~~~~~~~~
Sometimes, when programming, you get some TRAP null dereference
somewhere deep in Texts of Files or another system module and need to
add some debugging statements to find out which of your calls to these
modules passed the NIL value. This is a tedious task, and other
programming languages like C or Java have a simple solution for it: On
an assertion or breakpoint, scan the stack for pointers to code that are
directly behind a branch (BL or BLR in case of RISC Oberon), and print
the offsets of these instructions. In case of C, you'll get some false
positives sometimes, but in practice that does not matter as it is still
a lot better than having no backtrace at all (in Java, the frames are
linked and therefore the JVM can tell exactly which of the values are
return pointers and which are data, therefore a backtrace is "exact" there).
I think it got most popular by laxly configured Java webservers, which
often print long stack traces on its HTTP 500 error pages visible to
customers.
In combination with debug symbols, these offsets can also be decoded
back to original line numbers.
Oberon does not have debug symbols, but traps already use some unused
bits in the BLR instruction to encode the position where the trap
occurred. So it is easy to change the other BLR instructions to include
the position at the same place. BL instructions, however, don't have
enough free bits to encode the position, so I decided to prefix every BL
instruction by a dummy BLR instruction that uses condition flags 0FH
(which can never be fulfilled) and encode the position in there.
To make the lines of the backtrace easier "followable", the position is
printed as 123 at ModuleName, and Edit.Locate is enhanced to detect this
syntax and if the marked viewer does not have ModuleName.Mod open, open
a new viewer with that module and locate the position in there. This can
certainly be enhanced to scan for an open viewer and use it, but in
practice where there are no more than 2 or 3 different interesting
modules in the backtrace, this simple solution suffices for me.
Patch is here:
https://github.com/schierlm/OberonEmulator/blob/master/ProposedPatches/trap-backtrace.patch
Tell me what you think about it - or if you have ideas to solve these
problems in a better way :)
Regards,
Michael
More information about the Oberon
mailing list