Welcome, Guest. Please login or register.

Author Topic: Why the workbench.library cannot be made reset-resident  (Read 1363 times)

Description:

0 Members and 1 Guest are viewing this topic.

guest11527

  • Guest
Why the workbench.library cannot be made reset-resident
« on: September 29, 2017, 08:42:02 AM »
Since Olaf triggered a discussion of some "in-depth" AmigaOs development facts, let us continue this "mini-series" with another episode. You might have noticed that with Os 3.9, the workbench.library is a disk-based library in LIBS:, and not part of the ROM-Updates. Neither can you load it with "LoadModule", any attempt to do so will create a yellow alert on the next reboot. Here I'll try to answer why that is, as it also unveils some fun-facts about the SAS/C compiler.

The reason why LoadModule complains and workbench isn't ROM-able is that it modifies one single pointer in its data-segment. This clearly cannot work in ROM, and as LoadModule keeps a checksum over its modules, it also complains. The pointer it overwrites is "UtilityBase", a library that contains amongst other functions also 32bit multiplication and division functions workbench needs for its function. E.g. to compute the fill level of the disks and draw the rulers accordingly.

Now, if you use a "*" or "/" in your code that requires 32 bit math, SAS/C has multiple options to resolve this. Option #1 is inline code that calls the utility.library functions, and for that the inline code references UtilityBase relative to the base address register (typically a4). The other option is to call its own library functions with cryptic names, such as __CXD33 for the long division.

Now, the first option is attractive, but does not apply here. The workbench is a library, and thus does not have a "global data section", only a library base. There is a way around this, namely to tell SAS/C to load the library base a6 as base pointer a4, declare all library functions as __saveds so it loads a4 with a6, and there you go. Layers.library uses this approach, which is why you see all these "lea 0(a6),a4" instructions in the library code. There is another way around it, namely to create a define like
Code: [Select]
#define WbBASE (struct WorkbenchBase *)getreg(REG_A6)
#define UtilityBase WbBase->wb_UtilityBase
Now, this works, strangly enough - most of the time. SAS/C also has an option to preserve a6 over function calls - so the library base stays in a6 all the way, hence we can get it with the built-in function "getreg()" which you find in (note, it is , not or . The former is a compiler include, the latter an Os include).

SAS/C is smart enough to, whenever a user attempts to call a library function, to resolve any macro that defines its library base, and use this as library basis. So if I call - manually(!) - the long division function by UDivMod32(a,b), this will do what I want. Unfortunately, SAS/C does *not* resolve the macro for the inline code if I just write "a/b". There it generates a reference to an external symbol "_UtilityBase" or "_UtilityBase(a4)", depending on compiler options. The first would access a global data pointer, somewhere in RAM, but as it is not relative to a6, it must be global "somewhere". The latter does not apply since the workbench does not load a4 for its library base, but a6.

So, for the former option, we clearly need to load "UtilityBase" with something at some point, and need to modify some global data somewhere in RAM that must be somehow part of the library, though is not referenced through a6. Hence, this goes to the data segment. However, we already know that this makes the code non-ROM-able.

Ok, so the whole idea of inline-functions with SAS/C does not lead to something, and for that reason, the workbench does not use it (neither do many other system libraries). Instead, the workbench uses the other math option, namey defining the "cryptic functions" like __CXD33 instead of taking them from the SAS/C library. What these functions do is then load UtilityBase themselves into a6, and then call the utility.library. Wait! We still need to get UtilityBase from somewhere...

Ok, these are small assembly stub functions, so we can do anything we like. What about the following:
Code: [Select]
__CXD33:
       move.l a6,-(a7)
       move.l wb_UtilityBase(a6),a6
       jsr UDivMod32(a6)
       move.l (a7)+,a6
       rts
Remember, A6 is preserved across function calls, so it is workbench-base here, so all is well.... Well, not really. This approach works in a couple of places, and in fact, it is used by many system libraries (e.g. iffparse gets away with it), but not with workbench. *Sigh*

So the story does not end here. The problem is that workbench includes some custom components such as the ruler boopsi to show the fill level (remember?). This is a boopsi, and thus called from the intuition internal rendering function, and *therefore* a6 is equal to intuitionbase when the function is entered, and not equal to workbenchbase, which we would need. Thus, if you want to scale the ruler in the workbench implemented boopsi class, you are lost as the code would load some wierd base pointer relative from intuitionbase and not relative to workbenchbase.

What can we do? Apparently, the boopsi needs workbenchbase anyhow, so it is delivered as "user pointer" along with the boopsi class pointer, and I can override the library base function macros such as
Code: [Select]
#define WbBASE (struct WorkbenchBase *)(class->cl_UserData)
to reach all the Os functions I need. Except - and we had this before - that this does not work for the inline math, which is why we replace the __CXD33 function in first place.

While I don't know, I believe Olaf might have given up at this point and simply put a global _UtilityBase into the replacement __CXD33 function so we can fetch UtilityBase there,with the known consequences of generating non-ROMable code.

Can't we just try a little bit harder? Yes, of course. What about the following construction: Whenever we enter a boopsi function, we hack the workbench base address into a6 ourselfes:
Code: [Select]
putreg(REG_A6,(LONG)(class->cl_UserData)
Nice idea, but no cigar. "putreg()" is a function, at least it looks as one to the upper level of the compiler, so it preserves a6 across calls, which means... well, there is no effect. A6 is loaded, and then replaced again.

There are two working alternatives, though. Alternative 1 is the "take it easy" approach. We simply do not define __CXD33 at all, and let SAS/C link it its own functions. These are not 020-enabled, use only 16-bit divide and multiply instructions, but work, and need no base register. Workbench gets a tiny little bit longer, and a tiny little bit slower when scaling its rulers (not that you can notice), but it works and we get rom-able code.

Alternative 2 is the harder alternative: For every call-in into the workbench.library, define a small hook function that picks up a6 from the "user data" pointer, and then call the main implementation which as a prototype such as
Code: [Select]
LONG __asm RulerDispatcher(register __a6 struct WorkbenchBase *WbBase,....);
So, in other words, we force the compiler to override its "preserve a6" mechanism by explicitly telling it that one of the function parameters comes in a6. This works, too, but is a lot of work since there is not only the ruler boopsi. There is also an editor-boopsi, a callback for clipboard functions, a callback for the layer backfill hook....

And all that just because the SAS/C code generator could be a little bit smarter and expand macros while resolving the library bases for its inline-math code.
 

Offline aggro_mix

  • Full Member
  • ***
  • Join Date: May 2004
  • Posts: 150
    • Show only replies by aggro_mix
Re: Why the workbench.library cannot be made reset-resident
« Reply #1 on: September 29, 2017, 11:23:18 AM »
Not that I completely understand it, as I'm not an active developer, but thanks for the insight. I still find this very interesting knowledge! Please keep 'em coming. :)
 

Offline NorthWay

  • Full Member
  • ***
  • Join Date: Jun 2003
  • Posts: 209
    • Show only replies by NorthWay
Re: Why the workbench.library cannot be made reset-resident
« Reply #2 on: September 29, 2017, 02:41:41 PM »
Maybe I was not paying enough attention, but the immediate question I have is "What was different in the version you find in the 3.1 ROM"?
 

guest11527

  • Guest
Re: Why the workbench.library cannot be made reset-resident
« Reply #3 on: September 29, 2017, 06:20:23 PM »
Quote from: NorthWay;831143
Maybe I was not paying enough attention, but the immediate question I have is "What was different in the version you find in the 3.1 ROM"?

A very valid question, indeed. The 3.1 workbench uses the same utility stubs function as the 3.9 version, i.e. provides __CXD33 and friends, and depends on the compiler (for that version, Lattice C) to keep a6 loaded correctly. The difference is: The 3.1 version does not use boopsis, so there the problem goes away, and has a much simpler backfill hook that is entirely written in assembly language and neither requires division nor modulo operations, so the base pointer problem does not exist there either.

IOWs, the additional 3.9 eye-candy triggered the problem - in a way.
 

Offline grond

  • Full Member
  • ***
  • Join Date: Feb 2016
  • Posts: 154
    • Show only replies by grond
Re: Why the workbench.library cannot be made reset-resident
« Reply #4 on: September 30, 2017, 01:25:23 PM »
I didn't understand half of it but before I had done this I'd probably have written my own div/mul code using shift-subtract and shift-add algorithms...