4

This is a fairly basic question, and I almost feel ashamed to ask it; I'm guessing it can be answered in a single sentence.

There was a rather influential series of assemblers for some of IBM's electronic data-processing machines. Here is my understanding of the evolution: The first in the line was called the “Symbolic Assembly Program” or SAP; after being picked up by SHARE, the acronym came to stand for “SHARE Assembly Program”. When FORTRAN came along, a new version was coded for the compiler and called the “FORTRAN Assembly Program” or FAP.

From what I can find, documentation survives only for a 704 SAP from 1956-03-22, and for a few early-1960s versions for the 7090, which can be found on Bitsavers. It seems the assembler grew support for macros in this time.

Two pseudo-operations are provided to change the destination of assembled code. The first is ORG:

The ORG (Origin) pseudo-operation is used to set the “next location to be assigned by the assembler” to a desired value.

The effect of an ORG on the binary output of the assembler is to cause any words in the punch buffer to be written out, and to cause the next output to start at the new card origin.

The ORG pseudo-operation causes the next instruction to be assembled at the origin given (C28-6235-2, p. 25).

Then there is LOC:

The LOC (Location) pseudo-operation is used to set the program counter.

LOC will not cause any binary words in the punch buffer to be written out (ibid).

Later we learn the following:

The assembler uses two counters called the “location counter” and “program counter” to keep track of the “next instruction to be assigned”. Initially, both counters are set to zero. When an instruction, which is not a pseudo-operation, is processed, its location symbol, if any, is entered into the symbol table together with the current value of the program counter. Then both counters are increased by one or, for certain orders, by two. Pseudo-operations affect both counters in different ways. ORG sets both counters to the given value; LOC sets only the program counter (same document, page 61).

The title of this question is now trivially answered. But what is the purpose of having LOC as well as ORG? In what situation is LOC necessary or useful? When would de-synchronizing the two counters be desirable? Does it have something to do with relocatable assembly? I admit that that is an area I am not too clear about.

4

The title of this question is now trivially answered. But what is the purpose of having LOC as well as ORG?

To control location within generated binary fine independent from location assumed by the assembly code.

Keep in mind, early tools leave a way finer controll than one is used today - after all, there wasn't much of a canon how it should work, so best tools were the ones able to fit to each owns style.

In what situation is LOC necessary or useful? When would de-synchronizing the two counters be desirable?

For sure not in trivial programs, but once leaving that there are many ways to make it useful. The most important is to save disk/tape space and speed up loading.

Does it have something to do with relocatable assembly?

Kind of, but it's rather about modularization within a single file.

Relocation is about collecting information about code to address relation, which is used to patch a program at a later time to make it work on a specific address and/or to combine multiple programs by resolving pointers (addresses) inbetween. Essentially leaving the decision about location to a later stage of binding or as late as load time.

Relocation essentially defers any (well, most) decisions about location during assembly/compile to later stages.

Quite a handy thing, except, it does need a sophisticated system of linkers and loaders, depending on stage applied and capabilities needed. Similar it needs a set of conventions to work with - and as a side effect some constructs may be hard to create or need a lot of setup to configure them.

Having the ability to independent control of placing and assuming location direct in a programs source allows to gather many benefits of complex application structures without the need for sophisticated linkers, loaders and overlay managers.

In what situation is LOC necessary or useful?

Lets take the most obvious, overlays, as example. With rather limited memory overlays were the way to go (*1). Of course one could put every overlay into a dedicated file and load it in times of need using whatever the file system and loader offered. Rather laborious when having many smaller modules that not only need to be loaded independent, but as well use the same address reference to global memory items they had to work with. I guess we all remember (early) C programming with tons of include files and constant haggling with including just as much as needed and so on. Imagine doing the same with punch cards stacks. Similar, noone will love you if you deliver an application, or some functions as a series of dozends of files that had to be kept consistent (*2)?

Sounds like a nightmare? Well, it was. Not as bad as it sounds, but bad enough.

So what about putting all into one source stack (file), delivering a single binary? With an independent LOC this can easy be done. Imagine a structure like this:

        START   APROG

        ORG     0
        B       MAIN  * Goto MAIN

COMMON  DS      0D
...     DS      ...    * Some common code and data
...     DS      ...

        ORG     1*1024 * Real Programmers make code readable :))
MAIN    DS      0H
        ...     ...    * Main Program Code 

EOMAIN  DS      0D     * End of MAIN

* Lets define the buffer to execute overlays and its size
OVLBUF  EQU     4*1024 * Overlay Area starts at 4 Ki
OVLSIZ  EQU     1024   * Hey, 1 KiB is a lot of code!

        ORG     OVLBUF

OVLINIT DS      0H
        ...     Code for a initial overlay,
        ...     maybe containing initialisation code,
        ...     to be discarded after first run? 

EOLOAD  DS      0D     * End address of static loaded

* So far pretty standard, isn't it?
* But watch it when the next one comes

OVLSTRT 

        ORG     0*OVLSIZ+OVLSTRT * or 5*1024
        LOC     OVLBUF           * that is 4*1024

OVL0    DS      0H
        ...     Code for the first (real) overlay

* But watch it when the next one comes

        ORG     1*OVLSIZ+OVLSTRT * or 6*1024
        LOC     OVLBUF           * that is 4*1024

OVL1    DS      0H
        ...     Code for the second overlay

... and so on (*3)

        END

This in this program all overlays can be written once, they all use the same definition for global data and all get outputted into a single binary file. But the code is generated in a way as if each of the overlay sections would have been assembled at the buffers address.

At startup (*4) only the part up to EOLOAD is loaded and execution started. It contains the main application including the overlay manager. Whenever one of the overlays is needed, the 'manager' would go to the programs disk file, read that overlay into the buffer and call it.

Now, 'manager' sounds big, but in reality it takes an overlay number 0..n, multiplies it by OVLSIZ, adds OVLSTRT and reads from the overlay binary, starting at the value calculated, in length OVLSIZ, to OVLBUF. So essentially two assembly instructions and a file read call (*5).

Looks like a handy way to have arbitrary large programs in limited space, doesn't it? And everything done with just the LOC and a bit of brainpower. No need for any tools. Stuff like that will run on bare metal - which was as important back then (*6), as it wasn't just about size or less capable tools, but no tools at all. The Assembler is all needed.

Of course that is not the only usage. Another would be assembling a program that has parts that need to end up in specific memory locations. While it may (or may not) be assembled with ORG, it would end up with many 'empty' data inbetween. At least without segmentation control. And with segmentation it needs a loader capable of moving that segments into their designated spaces.

In fact, it is what compilers and their tools spit out: a binary blob, which does hold already relocated code, but not at the offsets (addresses) they are intended to run, so an OS' loader has to do that part. Hard on bare metal.

In fact, if one takes a close look at more modern tools like CC65, it would reveal that the binary produced by the linker is exactly that, a sequence of segments, located at one offset but intended to run at another. With a single segment it's a direct loadable one, with a more complex structure a tiny loader stub is required. The linkage file delivers all needed information :)) (*7)

Last but not least, the addition of LOC is the way to fir programs into any memory shape needed. Like when fitting stuff into code crated by a separate compiler, or creating compiler or an OS from scratch. Stuff important in the early days with its usefulness almost forgotten in a world cushioned on gigabytes of software layers.

With ORG and LOC an Assembly programmer can design the binary created exactly the way he wants it, creating the things he needs without (much) need for other tools.


*1 - This got as far as (later) transaction systems handling each and every transaction code as an overlay, loaded when that transaction was called -in memory strapped systems all into the same buffer, making it quite slow, but making it run.

*2 - Yes, I know, today it's common to simply deliver 40,000 files for a notepad like application and expect them all to arrive well and unmangled at some wired configured Windows machine at the end of the world - aka your next door neighbour.

The art of packaging got lost soon after DOOM.

*3 - And yes, I know, this is made to be packaged in a nice set of macros doing all the calculation, but this is about showing the workings.

*4 - Which, if the OS doesn't support partial load, might be done by a tiny startup stub, doing nothing else but replacing itself by the overlay files content up to EOLOAD.

*5 - Well, in a real one may add a check against max module number and as well provide a way to have overlays call each other - chaining that is, not subroutine call. And no, don't ask me about what spaghetti code that may end in. WE DID NOT HAVE THE MEANS TO WAST GIGABYTES ON SUCH MONDANE STUFF. So, call chaining it was :)

*6 - It still is for that tiny crew of bare metal programmers still around. The ones that bring all your nifty complex systems like Linux onto a new SoC.

*7 - I think I remember we already had exactly these two questions already about CC65 -wh does it produce a huge empty file when using ORG and how to load one that has segments.

3
  • A typical application today would be the code to update the flash ROM of an embedded system: It's supposed to sit in flash memory at a dedicated address, but not to run there - it's actually copied to RAM to, typically, another address, end executed from there to be safe from overwriting itself. But today, you'd create such a binary with a linker script. – tofro Apr 12 at 18:25
  • My problem was that I was still trying to fit SAP in the in-core assembler mould, where its output would be used to fill up the memory in one go, before the operator presses "run". But it's clearly more dynamic than that. – texdr.aft Apr 12 at 18:38
  • @texdr.aft I'n not firm in that detail, but I think a generic answer is more helpful on the long run anyway. – Raffzahn Apr 12 at 18:58

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.