Re: C of the '80s
So we'll build it for an imaginary computer with an imaginary instruction set and then shoehorn that imaginary binary onto real hardware ? -FAIL-
THAT is the problem with all those catch-all compilers (and languages) we have today.
They compile for something with an architecture that does not exist and an instruction set that does not exist.
I've picked apart output of several compiler for embedded systems. Sending "Hello world" to a serial port. strings are null terminated like c.
in pseudo code :
string = {"hello world",0x00} // 12 bytes of rom space , null terminated string (11 chars + null)
MOV DPTR #string. // load the address of the string into the data pointer
:next_char
SNZ @DPTR // skip next instruction if not zero
JMP exit
:wait_for_port
SBC SCON,02 // Skip next instruction if bit clear register Serial CONtrol , bit 2
JMP wait_for_port
MOVI TX @DPTR // send character to TX register Move with post-increment DPTR
JMP next_char // see if there is something else
:exit
I've seen compilers that produce this : ( because they did not know the MOVI instruction)
MOV TX @DPTR // send character to TX register Move with post-increment DPTR
INCR DPTR
or this (because they did not understand DPTR is a hardware register and treat it as a variable)
MOV TX @DPTR // send character to TX register Move with post-increment DPTR
MOV A, DPTR // move DPTR to accumulator
INCR A // increment accumulator
MOV DPTR,A // move accumulator back
On a harvard machine you have dedicate mov operation that change depending if you are moving
ram to ram
ram to register
register to ram
io to ram
ram to io
rom to ram
rom to io
I've seen compilers that take that string in rom , allocate 12 bytes of ram , copy the string from rom to ram and send it out. Slightly smarter ones do it character by character but still need a rom to ram copy first.
Those are clear examples of the misery with generating intermediate machine code. They have one print function that is designed to take a ram pointer. Try to get a string from rom and it needs copying over first.
Optimization is not something to be done afterwards. Optimization needs to be done first. see what instructions are in the machine and map the source in the most efficient way.
Jump tables are a prime example of that. Switch case statements can easily be translated into jump tables. Depending on the selected branch all you do is add an offset to the code pointer so it lands in the jump table . There it finds a single move operation with the new target.
Your switch case statement translates to a constant speed operation (one ADD , one JMP), no need for testing anything.
Now, i do realize this is different on machines with different architecture or dynamically loaded programs. The above is just an example of how bad compilers can be when doing the compile to imaginary machine"
It pays to take a look at compilers that make code for one and one system only. They can heavily optimize for the architecture. Portability is in the source code, No need for an intermediate layer.